diff --git a/BUILD.md b/BUILD.md index c868a8e9d9..c51e40cb58 100644 --- a/BUILD.md +++ b/BUILD.md @@ -1,7 +1,7 @@ ###Dependencies * [cmake](http://www.cmake.org/cmake/resources/software.html) ~> 3.3.2 -* [Qt](http://www.qt.io/download-open-source) ~> 5.5.1 +* [Qt](http://www.qt.io/download-open-source) ~> 5.6.1 * [OpenSSL](https://www.openssl.org/community/binaries.html) ~> 1.0.1m * IMPORTANT: Using the recommended version of OpenSSL is critical to avoid security vulnerabilities. * [VHACD](https://github.com/virneo/v-hacd)(clone this repository)(Optional) @@ -41,14 +41,14 @@ If you would like to use a specific install of a dependency instead of the versi Hifi uses CMake to generate build files and project files for your platform. ####Qt -In order for CMake to find the Qt5 find modules, you will need to set an ENV variable pointing to your Qt installation. +In order for CMake to find the Qt5 find modules, you will need to set a QT_CMAKE_PREFIX_PATH environment variable pointing to your Qt installation. -For example, a Qt5 5.5.1 installation to /usr/local/qt5 would require that QT_CMAKE_PREFIX_PATH be set with the following command. This can either be entered directly into your shell session before you build or in your shell profile (e.g.: ~/.bash_profile, ~/.bashrc, ~/.zshrc - this depends on your shell and environment). +This can either be entered directly into your shell session before you build or in your shell profile (e.g.: ~/.bash_profile, ~/.bashrc, ~/.zshrc - this depends on your shell and environment). The path it needs to be set to will depend on where and how Qt5 was installed. e.g. - export QT_CMAKE_PREFIX_PATH=/usr/local/qt/5.5.1/clang_64/lib/cmake/ - export QT_CMAKE_PREFIX_PATH=/usr/local/Cellar/qt5/5.5.1/lib/cmake + export QT_CMAKE_PREFIX_PATH=/usr/local/qt/5.6.1/clang_64/lib/cmake/ + export QT_CMAKE_PREFIX_PATH=/usr/local/Cellar/qt5/5.6.1-1/lib/cmake export QT_CMAKE_PREFIX_PATH=/usr/local/opt/qt5/lib/cmake ####Generating build files @@ -65,7 +65,7 @@ Any variables that need to be set for CMake to find dependencies can be set as E For example, to pass the QT_CMAKE_PREFIX_PATH variable during build file generation: - cmake .. -DQT_CMAKE_PREFIX_PATH=/usr/local/qt/5.5.1/lib/cmake + cmake .. -DQT_CMAKE_PREFIX_PATH=/usr/local/qt/5.6.1/lib/cmake ####Finding Dependencies diff --git a/BUILD_OSX.md b/BUILD_OSX.md index 1c9c5a9796..55d4276aa0 100644 --- a/BUILD_OSX.md +++ b/BUILD_OSX.md @@ -1,25 +1,31 @@ Please read the [general build guide](BUILD.md) for information on dependencies required for all platforms. Only OS X specific instructions are found in this file. ###Homebrew -[Homebrew](http://brew.sh/) is an excellent package manager for OS X. It makes install of all High Fidelity dependencies very simple. +[Homebrew](http://brew.sh/) is an excellent package manager for OS X. It makes install of some High Fidelity dependencies very simple. brew tap homebrew/versions - brew install cmake openssl qt55 + brew install cmake openssl -We no longer require install of qt5 via our [homebrew formulas repository](https://github.com/highfidelity/homebrew-formulas). Versions of Qt that are 5.5.x provide a mechanism to disable the wireless scanning we previously had a custom patch for. +###OpenSSL -###OpenSSL and Qt - -Assuming you've installed OpenSSL or Qt 5 using the homebrew instructions above, you'll need to set OPENSSL_ROOT_DIR and QT_CMAKE_PREFIX_PATH so CMake can find your installations. +Assuming you've installed OpenSSL using the homebrew instructions above, you'll need to set OPENSSL_ROOT_DIR so CMake can find your installations. For OpenSSL installed via homebrew, set OPENSSL_ROOT_DIR: export OPENSSL_ROOT_DIR=/usr/local/Cellar/openssl/1.0.2h_1/ - -For Qt 5.5.1 installed via homebrew, set QT_CMAKE_PREFIX_PATH as follows. - export QT_CMAKE_PREFIX_PATH=/usr/local/Cellar/qt55/5.5.1/lib/cmake +Note that this uses the version from the homebrew formula at the time of this writing, and the version in the path will likely change. -Note that these use the versions from homebrew formulae at the time of this writing, and the version in the path will likely change. +###Qt +You can use the online installer or the offline installer. + +* [Download the online installer](http://www.qt.io/download-open-source/#section-2) + * When it asks you to select components, select the following: + * Qt > Qt 5.6 + +* [Download the offline installer](http://download.qt.io/official_releases/qt/5.6/5.6.1-1/qt-opensource-mac-x64-clang-5.6.1-1.dmg) + +Once Qt is installed, you need to manually configure the following: +* Set the QT_CMAKE_PREFIX_PATH environment variable to your `Qt5.6.1/5.6/clang_64/lib/cmake/` directory. ###Xcode If Xcode is your editor of choice, you can ask CMake to generate Xcode project files instead of Unix Makefiles. diff --git a/BUILD_WIN.md b/BUILD_WIN.md index 25e20952ca..b8adaad8d1 100644 --- a/BUILD_WIN.md +++ b/BUILD_WIN.md @@ -27,17 +27,17 @@ We expect nmake.exe to be located at the following path. ###Qt You can use the online installer or the offline installer. If you use the offline installer, be sure to select the "OpenGL" version. -* [Download the online installer](http://qt-project.org/downloads) +* [Download the online installer](http://www.qt.io/download-open-source/#section-2) * When it asks you to select components, ONLY select one of the following, 32- or 64-bit to match your build preference: - * Qt > Qt 5.5.1 > **msvc2013 32-bit** - * Qt > Qt 5.5.1 > **msvc2013 64-bit** + * Qt > Qt 5.6.1 > **msvc2013 32-bit** + * Qt > Qt 5.6.1 > **msvc2013 64-bit** * Download the offline installer, 32- or 64-bit to match your build preference: - * [32-bit](http://download.qt.io/official_releases/qt/5.5/5.5.1/qt-opensource-windows-x86-msvc2013-5.5.1.exe) - * [64-bit](http://download.qt.io/official_releases/qt/5.5/5.5.1/qt-opensource-windows-x86-msvc2013_64-5.5.1.exe) + * [32-bit](http://download.qt.io/official_releases/qt/5.6/5.6.1-1/qt-opensource-windows-x86-msvc2013-5.6.1-1.exe) + * [64-bit](http://download.qt.io/official_releases/qt/5.6/5.6.1-1/qt-opensource-windows-x86-msvc2013_64-5.6.1-1.exe) Once Qt is installed, you need to manually configure the following: -* Set the QT_CMAKE_PREFIX_PATH environment variable to your `Qt\5.5.1\msvc2013\lib\cmake` or `Qt\5.5.1\msvc2013_64\lib\cmake` directory. +* Set the QT_CMAKE_PREFIX_PATH environment variable to your `Qt\5.6.1\msvc2013\lib\cmake` or `Qt\5.6.1\msvc2013_64\lib\cmake` directory. * You can set an environment variable from Control Panel > System > Advanced System Settings > Environment Variables > New ###External Libraries diff --git a/assignment-client/CMakeLists.txt b/assignment-client/CMakeLists.txt index d0fd2c1176..54afabfd21 100644 --- a/assignment-client/CMakeLists.txt +++ b/assignment-client/CMakeLists.txt @@ -2,6 +2,11 @@ set(TARGET_NAME assignment-client) setup_hifi_project(Core Gui Network Script Quick Widgets WebSockets) +# Fix up the rpath so macdeployqt works +if (APPLE) + set_target_properties(${TARGET_NAME} PROPERTIES INSTALL_RPATH "@executable_path/../Frameworks") +endif () + # link in the shared libraries link_hifi_libraries( audio avatars octree gpu model fbx entities diff --git a/cmake/macros/FixupInterface.cmake b/cmake/macros/FixupInterface.cmake index 3e5ea7a3e2..93b5cc25fb 100644 --- a/cmake/macros/FixupInterface.cmake +++ b/cmake/macros/FixupInterface.cmake @@ -16,26 +16,6 @@ macro(fixup_interface) string(REPLACE " " "\\ " ESCAPED_INSTALL_PATH ${INTERFACE_INSTALL_DIR}) set(_INTERFACE_INSTALL_PATH "${ESCAPED_INSTALL_PATH}/${ESCAPED_BUNDLE_NAME}.app") - # install QtWebProcess from Qt to the application bundle - # since it is missed by macdeployqt - # https://bugreports.qt.io/browse/QTBUG-35211 - set(LIBEXEC_PATH "${_INTERFACE_INSTALL_PATH}/Contents/libexec") - install( - PROGRAMS "${QT_DIR}/libexec/QtWebProcess" - DESTINATION ${LIBEXEC_PATH} - COMPONENT ${CLIENT_COMPONENT} - ) - - set(QTWEBPROCESS_PATH "\${CMAKE_INSTALL_PREFIX}/${LIBEXEC_PATH}") - - # we also need a qt.conf in the directory of QtWebProcess - install(CODE " - file(WRITE ${QTWEBPROCESS_PATH}/qt.conf - \"[Paths]\nPlugins = ../PlugIns\nImports = ../Resources/qml\nQml2Imports = ../Resources/qml\" - )" - COMPONENT ${CLIENT_COMPONENT} - ) - find_program(MACDEPLOYQT_COMMAND macdeployqt PATHS "${QT_DIR}/bin" NO_DEFAULT_PATH) if (NOT MACDEPLOYQT_COMMAND AND (PRODUCTION_BUILD OR PR_BUILD)) @@ -49,7 +29,6 @@ macro(fixup_interface) execute_process(COMMAND ${MACDEPLOYQT_COMMAND}\ \${CMAKE_INSTALL_PREFIX}/${_INTERFACE_INSTALL_PATH}/\ -verbose=2 -qmldir=${CMAKE_SOURCE_DIR}/interface/resources/qml/\ - -executable=\${CMAKE_INSTALL_PREFIX}/${_INTERFACE_INSTALL_PATH}/Contents/libexec/QtWebProcess\ )" COMPONENT ${CLIENT_COMPONENT} ) diff --git a/cmake/macros/InstallBesideConsole.cmake b/cmake/macros/InstallBesideConsole.cmake index 0eb6025f38..d5777fff12 100644 --- a/cmake/macros/InstallBesideConsole.cmake +++ b/cmake/macros/InstallBesideConsole.cmake @@ -59,7 +59,12 @@ macro(install_beside_console) set(EXECUTABLE_NEEDING_FIXUP "\${CMAKE_INSTALL_PREFIX}/${COMPONENT_INSTALL_DIR}/${TARGET_NAME}") string(REPLACE " " "\\ " ESCAPED_EXECUTABLE_NAME ${EXECUTABLE_NEEDING_FIXUP}) + # configure Info.plist for COMPONENT_APP install(CODE " + set(MACOSX_BUNDLE_EXECUTABLE_NAME domain-server) + set(MACOSX_BUNDLE_GUI_IDENTIFIER com.highfidelity.server-components) + set(MACOSX_BUNDLE_BUNDLE_NAME Sandbox\\ Components) + configure_file(${HF_CMAKE_DIR}/templates/MacOSXBundleSandboxComponentsInfo.plist.in ${ESCAPED_BUNDLE_NAME}/Contents/Info.plist) execute_process(COMMAND ${MACDEPLOYQT_COMMAND} ${ESCAPED_BUNDLE_NAME} -verbose=2 -executable=${ESCAPED_EXECUTABLE_NAME})" COMPONENT ${SERVER_COMPONENT} ) diff --git a/cmake/templates/MacOSXBundleSandboxComponentsInfo.plist.in b/cmake/templates/MacOSXBundleSandboxComponentsInfo.plist.in new file mode 100644 index 0000000000..a466dc7c4e --- /dev/null +++ b/cmake/templates/MacOSXBundleSandboxComponentsInfo.plist.in @@ -0,0 +1,36 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + ${MACOSX_BUNDLE_EXECUTABLE_NAME} + CFBundleGetInfoString + ${MACOSX_BUNDLE_INFO_STRING} + CFBundleIconFile + ${MACOSX_BUNDLE_ICON_FILE} + CFBundleIdentifier + ${MACOSX_BUNDLE_GUI_IDENTIFIER} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleLongVersionString + ${MACOSX_BUNDLE_LONG_VERSION_STRING} + CFBundleName + ${MACOSX_BUNDLE_BUNDLE_NAME} + CFBundlePackageType + APPL + CFBundleShortVersionString + ${MACOSX_BUNDLE_SHORT_VERSION_STRING} + CFBundleSignature + ???? + CFBundleVersion + ${MACOSX_BUNDLE_BUNDLE_VERSION} + CSResourcesFileMapped + + LSRequiresCarbon + + NSHumanReadableCopyright + ${MACOSX_BUNDLE_COPYRIGHT} + + diff --git a/domain-server/CMakeLists.txt b/domain-server/CMakeLists.txt index b7eb8c0138..746e599d4e 100644 --- a/domain-server/CMakeLists.txt +++ b/domain-server/CMakeLists.txt @@ -9,6 +9,11 @@ endif () # setup the project and link required Qt modules setup_hifi_project(Network) +# Fix up the rpath so macdeployqt works +if (APPLE) + set_target_properties(${TARGET_NAME} PROPERTIES INSTALL_RPATH "@executable_path/../Frameworks") +endif () + # TODO: find a solution that will handle web file changes in resources on windows without a re-build. # Currently the resources are only copied on post-build. If one is changed but the domain-server is not, they will # not be re-copied. This is worked-around on OS X/UNIX by using a symlink. diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index b61fef8525..570db05871 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -1756,6 +1756,7 @@ bool DomainServer::handleHTTPSRequest(HTTPSConnection* connection, const QUrl &u .arg(authorizationCode, oauthRedirectURL().toString(), _oauthClientID, _oauthClientSecret); QNetworkRequest tokenRequest(tokenRequestUrl); + tokenRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); tokenRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); tokenRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); @@ -1949,6 +1950,7 @@ QNetworkReply* DomainServer::profileRequestGivenTokenReply(QNetworkReply* tokenR profileURL.setQuery(QString("%1=%2").arg(OAUTH_JSON_ACCESS_TOKEN_KEY, accessToken)); QNetworkRequest profileRequest(profileURL); + profileRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); profileRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); return NetworkAccessManager::getInstance().get(profileRequest); } diff --git a/ice-server/src/IceServer.cpp b/ice-server/src/IceServer.cpp index 84c48ac5f9..3f229d2f87 100644 --- a/ice-server/src/IceServer.cpp +++ b/ice-server/src/IceServer.cpp @@ -213,6 +213,7 @@ void IceServer::requestDomainPublicKey(const QUuid& domainID) { publicKeyURL.setPath(publicKeyPath); QNetworkRequest publicKeyRequest { publicKeyURL }; + publicKeyRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); publicKeyRequest.setAttribute(QNetworkRequest::User, domainID); qDebug() << "Requesting public key for domain with ID" << domainID; diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 1d436695b1..3e8b55431d 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -42,12 +42,12 @@ endif () if (ANDROID) set(PLATFORM_QT_COMPONENTS AndroidExtras) else () - set(PLATFORM_QT_COMPONENTS WebEngine WebEngineWidgets WebKitWidgets) + set(PLATFORM_QT_COMPONENTS WebEngine WebEngineWidgets) endif () find_package( Qt5 COMPONENTS - Gui Multimedia Network OpenGL Qml Quick Script ScriptTools Svg + Gui Multimedia Network OpenGL Qml Quick Script Svg ${PLATFORM_QT_COMPONENTS} WebChannel WebSockets ) @@ -123,7 +123,8 @@ if (APPLE) add_executable(${TARGET_NAME} MACOSX_BUNDLE ${INTERFACE_SRCS} ${QM}) # make sure the output name for the .app bundle is correct - set_target_properties(${TARGET_NAME} PROPERTIES OUTPUT_NAME ${INTERFACE_BUNDLE_NAME}) + # Fix up the rpath so macdeployqt works + set_target_properties(${TARGET_NAME} PROPERTIES INSTALL_RPATH "@executable_path/../Frameworks") elseif (WIN32) # configure an rc file for the chosen icon set(CONFIGURE_ICON_PATH "${CMAKE_CURRENT_SOURCE_DIR}/icon/${INTERFACE_ICON_FILENAME}") @@ -241,16 +242,10 @@ 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::ScriptTools Qt5::Svg - Qt5::WebChannel Qt5::WebEngine Qt5::WebEngineWidgets Qt5::WebKitWidgets + Qt5::Qml Qt5::Quick Qt5::Script Qt5::Svg + Qt5::WebChannel Qt5::WebEngine ) -# 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() - if (UNIX) target_link_libraries(${TARGET_NAME} pthread) endif(UNIX) diff --git a/interface/resources/qml/Browser.qml b/interface/resources/qml/Browser.qml index 050e10eead..62226859b6 100644 --- a/interface/resources/qml/Browser.qml +++ b/interface/resources/qml/Browser.qml @@ -223,12 +223,12 @@ ScrollingWindow { var newWindow = component.createObject(desktop); request.openIn(newWindow.webView) } - Component.onCompleted: { - desktop.initWebviewProfileHandlers(webview.profile) - } - - //profile: desktop.browserProfile + Component.onCompleted: { + desktop.initWebviewProfileHandlers(webview.profile) + } + + profile: desktop.browserProfile } } // item @@ -245,4 +245,4 @@ ScrollingWindow { break; } } -} // dialog \ No newline at end of file +} // dialog diff --git a/interface/resources/qml/controls/WebView.qml b/interface/resources/qml/controls/WebView.qml index 7285db22d2..84ef31e87f 100644 --- a/interface/resources/qml/controls/WebView.qml +++ b/interface/resources/qml/controls/WebView.qml @@ -5,7 +5,7 @@ WebEngineView { id: root property var newUrl; - profile.httpUserAgent: "Mozilla/5.0 Chrome (HighFidelityInterface)" + profile: desktop.browserProfile Component.onCompleted: { console.log("Connecting JS messaging to Hifi Logging") @@ -13,7 +13,6 @@ WebEngineView { root.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) { console.log("Web Window JS message: " + sourceID + " " + lineNumber + " " + message); }); - } // FIXME hack to get the URL with the auth token included. Remove when we move to Qt 5.6 @@ -61,8 +60,4 @@ WebEngineView { request.openIn(newWindow.webView); } } - - // This breaks the webchannel used for passing messages. Fixed in Qt 5.6 - // See https://bugreports.qt.io/browse/QTBUG-49521 - //profile: desktop.browserProfile } diff --git a/interface/resources/qml/dialogs/FileDialog.qml b/interface/resources/qml/dialogs/FileDialog.qml index 6a37886cb3..ff8be580db 100644 --- a/interface/resources/qml/dialogs/FileDialog.qml +++ b/interface/resources/qml/dialogs/FileDialog.qml @@ -52,7 +52,7 @@ ModalWindow { // Set from OffscreenUi::getOpenFile() property int options; // <-- FIXME unused - property string iconText: text !== "" ? hifi.glyphs.scriptUpload : "" + property string iconText: root.title !== "" ? hifi.glyphs.scriptUpload : "" property int iconSize: 40 property bool selectDirectory: false; diff --git a/interface/resources/qml/hifi/Card.qml b/interface/resources/qml/hifi/Card.qml index 53829eed9e..5d8cbc97fe 100644 --- a/interface/resources/qml/hifi/Card.qml +++ b/interface/resources/qml/hifi/Card.qml @@ -89,6 +89,7 @@ Rectangle { property int dropSamples: 9; property int dropSpread: 0; DropShadow { + visible: desktop.gradientsSupported; source: place; anchors.fill: place; horizontalOffset: dropHorizontalOffset; @@ -99,6 +100,7 @@ Rectangle { spread: dropSpread; } DropShadow { + visible: desktop.gradientsSupported; source: users; anchors.fill: users; horizontalOffset: dropHorizontalOffset; diff --git a/interface/resources/qml/windows/DefaultFrameDecoration.qml b/interface/resources/qml/windows/DefaultFrameDecoration.qml index 40e32aaa6b..1ddd83976e 100644 --- a/interface/resources/qml/windows/DefaultFrameDecoration.qml +++ b/interface/resources/qml/windows/DefaultFrameDecoration.qml @@ -109,7 +109,7 @@ Decoration { verticalOffset: 2 samples: 2 color: hifi.colors.baseGrayShadow60 - visible: (window && window.focus) + visible: (desktop.gradientsSupported && window && window.focus) cached: true } } diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index d846a5c6d1..8620b384ec 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -133,7 +133,6 @@ #include "scripting/LocationScriptingInterface.h" #include "scripting/MenuScriptingInterface.h" #include "scripting/SettingsScriptingInterface.h" -#include "scripting/WebWindowClass.h" #include "scripting/WindowScriptingInterface.h" #include "scripting/ControllerScriptingInterface.h" #include "scripting/ToolbarScriptingInterface.h" @@ -176,8 +175,6 @@ static const int MAX_CONCURRENT_RESOURCE_DOWNLOADS = 16; // For processing on QThreadPool, target 2 less than the ideal number of threads, leaving // 2 logical cores available for time sensitive tasks. static const int MIN_PROCESSING_THREAD_POOL_SIZE = 2; -static const int PROCESSING_THREAD_POOL_SIZE = std::max(MIN_PROCESSING_THREAD_POOL_SIZE, - QThread::idealThreadCount() - 2); static const QString SNAPSHOT_EXTENSION = ".jpg"; static const QString SVO_EXTENSION = ".svo"; @@ -537,7 +534,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : PluginContainer* pluginContainer = dynamic_cast(this); // set the container for any plugins that care PluginManager::getInstance()->setContainer(pluginContainer); - QThreadPool::globalInstance()->setMaxThreadCount(PROCESSING_THREAD_POOL_SIZE); + QThreadPool::globalInstance()->setMaxThreadCount(MIN_PROCESSING_THREAD_POOL_SIZE); thread()->setPriority(QThread::HighPriority); thread()->setObjectName("Main Thread"); @@ -707,6 +704,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : connect(addressManager.data(), &AddressManager::hostChanged, this, &Application::updateWindowTitle); connect(this, &QCoreApplication::aboutToQuit, addressManager.data(), &AddressManager::storeCurrentAddress); + connect(this, &Application::activeDisplayPluginChanged, this, &Application::updateThreadPoolCount); + // Save avatar location immediately after a teleport. connect(getMyAvatar(), &MyAvatar::positionGoneTo, DependencyManager::get().data(), &AddressManager::storeCurrentAddress); @@ -4858,7 +4857,6 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri scriptEngine->registerGetterSetter("location", LocationScriptingInterface::locationGetter, LocationScriptingInterface::locationSetter); - scriptEngine->registerFunction("WebWindow", WebWindowClass::constructor, 1); scriptEngine->registerFunction("OverlayWebWindow", QmlWebWindowClass::constructor); scriptEngine->registerFunction("OverlayWindow", QmlWindowClass::constructor); @@ -5034,6 +5032,7 @@ bool Application::askToLoadScript(const QString& scriptFilenameOrURL) { bool Application::askToWearAvatarAttachmentUrl(const QString& url) { QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); QNetworkRequest networkRequest = QNetworkRequest(url); + networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); networkRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); QNetworkReply* reply = networkAccessManager.get(networkRequest); int requestNumber = ++_avatarAttachmentRequest; @@ -5727,3 +5726,18 @@ void Application::sendHoverLeaveEntity(QUuid id, PointerEvent event) { EntityItemID entityItemID(id); emit getEntities()->hoverLeaveEntity(entityItemID, event); } + +// FIXME? perhaps two, one for the main thread and one for the offscreen UI rendering thread? +static const int UI_RESERVED_THREADS = 1; +// Windows won't let you have all the cores +static const int OS_RESERVED_THREADS = 1; + +void Application::updateThreadPoolCount() const { + auto reservedThreads = UI_RESERVED_THREADS + OS_RESERVED_THREADS + _displayPlugin->getRequiredThreadCount(); + auto availableThreads = QThread::idealThreadCount() - reservedThreads; + auto threadPoolSize = std::max(MIN_PROCESSING_THREAD_POOL_SIZE, availableThreads); + qDebug() << "Ideal Thread Count " << QThread::idealThreadCount(); + qDebug() << "Reserved threads " << reservedThreads; + qDebug() << "Setting thread pool size to " << threadPoolSize; + QThreadPool::globalInstance()->setMaxThreadCount(threadPoolSize); +} diff --git a/interface/src/Application.h b/interface/src/Application.h index a0c67a9e73..8bfae51179 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -286,6 +286,7 @@ public slots: bool exportEntities(const QString& filename, const QVector& entityIDs, const glm::vec3* givenOffset = nullptr); bool exportEntities(const QString& filename, float x, float y, float z, float scale); bool importEntities(const QString& url); + void updateThreadPoolCount() const; static void setLowVelocityFilter(bool lowVelocityFilter); Q_INVOKABLE void loadDialog(); diff --git a/interface/src/InterfaceActionFactory.cpp b/interface/src/InterfaceActionFactory.cpp index 1869980270..2bc4608e86 100644 --- a/interface/src/InterfaceActionFactory.cpp +++ b/interface/src/InterfaceActionFactory.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include "InterfaceActionFactory.h" @@ -29,6 +30,8 @@ EntityActionPointer interfaceActionFactory(EntityActionType type, const QUuid& i return std::make_shared(id, ownerEntity); case ACTION_TYPE_HOLD: return std::make_shared(id, ownerEntity); + case ACTION_TYPE_TRAVEL_ORIENTED: + return std::make_shared(id, ownerEntity); } Q_ASSERT_X(false, Q_FUNC_INFO, "Unknown entity action type"); diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 31fb2abe53..717a0eeac7 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -59,8 +59,6 @@ const float DISPLAYNAME_ALPHA = 1.0f; const float DISPLAYNAME_BACKGROUND_ALPHA = 0.4f; const glm::vec3 HAND_TO_PALM_OFFSET(0.0f, 0.12f, 0.08f); -const int SENSOR_TO_WORLD_MATRIX_INDEX = 65534; - namespace render { template <> const ItemKey payloadGetKey(const AvatarSharedPointer& avatar) { return ItemKey::Builder::opaqueShape(); @@ -853,32 +851,54 @@ glm::vec3 Avatar::getDefaultJointTranslation(int index) const { } glm::quat Avatar::getAbsoluteJointRotationInObjectFrame(int index) const { - if (index == SENSOR_TO_WORLD_MATRIX_INDEX) { - glm::mat4 sensorToWorldMatrix = getSensorToWorldMatrix(); - bool success; - Transform avatarTransform; - Transform::mult(avatarTransform, getParentTransform(success), getLocalTransform()); - glm::mat4 invAvatarMat = avatarTransform.getInverseMatrix(); - return glmExtractRotation(invAvatarMat * sensorToWorldMatrix); - } else { - glm::quat rotation; - _skeletonModel->getAbsoluteJointRotationInRigFrame(index, rotation); - return Quaternions::Y_180 * rotation; + switch(index) { + case SENSOR_TO_WORLD_MATRIX_INDEX: { + glm::mat4 sensorToWorldMatrix = getSensorToWorldMatrix(); + bool success; + Transform avatarTransform; + Transform::mult(avatarTransform, getParentTransform(success), getLocalTransform()); + glm::mat4 invAvatarMat = avatarTransform.getInverseMatrix(); + return glmExtractRotation(invAvatarMat * sensorToWorldMatrix); + } + case CONTROLLER_LEFTHAND_INDEX: { + Transform controllerLeftHandTransform = Transform(getControllerLeftHandMatrix()); + return controllerLeftHandTransform.getRotation(); + } + case CONTROLLER_RIGHTHAND_INDEX: { + Transform controllerRightHandTransform = Transform(getControllerRightHandMatrix()); + return controllerRightHandTransform.getRotation(); + } + default: { + glm::quat rotation; + _skeletonModel->getAbsoluteJointRotationInRigFrame(index, rotation); + return Quaternions::Y_180 * rotation; + } } } glm::vec3 Avatar::getAbsoluteJointTranslationInObjectFrame(int index) const { - if (index == SENSOR_TO_WORLD_MATRIX_INDEX) { - glm::mat4 sensorToWorldMatrix = getSensorToWorldMatrix(); - bool success; - Transform avatarTransform; - Transform::mult(avatarTransform, getParentTransform(success), getLocalTransform()); - glm::mat4 invAvatarMat = avatarTransform.getInverseMatrix(); - return extractTranslation(invAvatarMat * sensorToWorldMatrix); - } else { - glm::vec3 translation; - _skeletonModel->getAbsoluteJointTranslationInRigFrame(index, translation); - return Quaternions::Y_180 * translation; + switch(index) { + case SENSOR_TO_WORLD_MATRIX_INDEX: { + glm::mat4 sensorToWorldMatrix = getSensorToWorldMatrix(); + bool success; + Transform avatarTransform; + Transform::mult(avatarTransform, getParentTransform(success), getLocalTransform()); + glm::mat4 invAvatarMat = avatarTransform.getInverseMatrix(); + return extractTranslation(invAvatarMat * sensorToWorldMatrix); + } + case CONTROLLER_LEFTHAND_INDEX: { + Transform controllerLeftHandTransform = Transform(getControllerLeftHandMatrix()); + return controllerLeftHandTransform.getTranslation(); + } + case CONTROLLER_RIGHTHAND_INDEX: { + Transform controllerRightHandTransform = Transform(getControllerRightHandMatrix()); + return controllerRightHandTransform.getTranslation(); + } + default: { + glm::vec3 translation; + _skeletonModel->getAbsoluteJointTranslationInRigFrame(index, translation); + return Quaternions::Y_180 * translation; + } } } @@ -889,6 +909,10 @@ int Avatar::getJointIndex(const QString& name) const { Q_RETURN_ARG(int, result), Q_ARG(const QString&, name)); return result; } + int result = getFauxJointIndex(name); + if (result != -1) { + return result; + } return _skeletonModel->isActive() ? _skeletonModel->getFBXGeometry().getJointIndex(name) : -1; } diff --git a/interface/src/avatar/AvatarActionHold.cpp b/interface/src/avatar/AvatarActionHold.cpp index 5acee052f2..51171b9c6b 100644 --- a/interface/src/avatar/AvatarActionHold.cpp +++ b/interface/src/avatar/AvatarActionHold.cpp @@ -56,6 +56,10 @@ void AvatarActionHold::prepareForPhysicsSimulation() { } withWriteLock([&]{ + glm::vec3 avatarRigidBodyPosition; + glm::quat avatarRigidBodyRotation; + getAvatarRigidBodyLocation(avatarRigidBodyPosition, avatarRigidBodyRotation); + if (_ignoreIK) { return; } @@ -70,9 +74,6 @@ void AvatarActionHold::prepareForPhysicsSimulation() { palmRotation = holdingAvatar->getUncachedLeftPalmRotation(); } - glm::vec3 avatarRigidBodyPosition; - glm::quat avatarRigidBodyRotation; - getAvatarRigidBodyLocation(avatarRigidBodyPosition, avatarRigidBodyRotation); // determine the difference in translation and rotation between the avatar's // rigid body and the palm position. The avatar's rigid body will be moved by bullet @@ -124,13 +125,20 @@ bool AvatarActionHold::getTarget(float deltaTimeStep, glm::quat& rotation, glm:: if (pose.isValid()) { linearVelocity = pose.getVelocity(); angularVelocity = pose.getAngularVelocity(); + + if (isRightHand) { + pose = avatarManager->getMyAvatar()->getRightHandControllerPoseInAvatarFrame(); + } else { + pose = avatarManager->getMyAvatar()->getLeftHandControllerPoseInAvatarFrame(); + } } if (_ignoreIK && pose.isValid()) { - // We cannot ignore other avatars IK and this is not the point of this option - // This is meant to make the grabbing behavior more reactive. - palmPosition = pose.getTranslation(); - palmRotation = pose.getRotation(); + Transform avatarTransform; + auto myAvatar = DependencyManager::get()->getMyAvatar(); + avatarTransform = myAvatar->getTransform(); + palmPosition = avatarTransform.transform(pose.getTranslation() / myAvatar->getTargetScale()); + palmRotation = avatarTransform.getRotation() * pose.getRotation(); } else { glm::vec3 avatarRigidBodyPosition; glm::quat avatarRigidBodyRotation; @@ -159,11 +167,17 @@ bool AvatarActionHold::getTarget(float deltaTimeStep, glm::quat& rotation, glm:: } } else { // regular avatar if (isRightHand) { - palmPosition = holdingAvatar->getRightPalmPosition(); - palmRotation = holdingAvatar->getRightPalmRotation(); + Transform controllerRightTransform = Transform(holdingAvatar->getControllerRightHandMatrix()); + Transform avatarTransform = holdingAvatar->getTransform(); + palmRotation = avatarTransform.getRotation() * controllerRightTransform.getRotation(); + palmPosition = avatarTransform.getTranslation() + + (avatarTransform.getRotation() * controllerRightTransform.getTranslation()); } else { - palmPosition = holdingAvatar->getLeftPalmPosition(); - palmRotation = holdingAvatar->getLeftPalmRotation(); + Transform controllerLeftTransform = Transform(holdingAvatar->getControllerLeftHandMatrix()); + Transform avatarTransform = holdingAvatar->getTransform(); + palmRotation = avatarTransform.getRotation() * controllerLeftTransform.getRotation(); + palmPosition = avatarTransform.getTranslation() + + (avatarTransform.getRotation() * controllerLeftTransform.getTranslation()); } } diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 5687b5025c..24dbc62318 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -532,11 +532,21 @@ void MyAvatar::updateFromHMDSensorMatrix(const glm::mat4& hmdSensorMatrix) { _hmdSensorFacing = getFacingDir2D(_hmdSensorOrientation); } +void MyAvatar::updateJointFromController(controller::Action poseKey, ThreadSafeValueCache& matrixCache) { + assert(QThread::currentThread() == thread()); + auto userInputMapper = DependencyManager::get(); + controller::Pose controllerPose = userInputMapper->getPoseState(poseKey); + Transform transform; + transform.setTranslation(controllerPose.getTranslation()); + transform.setRotation(controllerPose.getRotation()); + glm::mat4 controllerMatrix = transform.getMatrix(); + matrixCache.set(controllerMatrix); +} + // best called at end of main loop, after physics. // update sensor to world matrix from current body position and hmd sensor. // This is so the correct camera can be used for rendering. void MyAvatar::updateSensorToWorldMatrix() { - // update the sensor mat so that the body position will end up in the desired // position when driven from the head. glm::mat4 desiredMat = createMatFromQuatAndPos(getOrientation(), getPosition()); @@ -545,10 +555,14 @@ void MyAvatar::updateSensorToWorldMatrix() { lateUpdatePalms(); if (_enableDebugDrawSensorToWorldMatrix) { - DebugDraw::getInstance().addMarker("sensorToWorldMatrix", glmExtractRotation(_sensorToWorldMatrix), extractTranslation(_sensorToWorldMatrix), glm::vec4(1)); + DebugDraw::getInstance().addMarker("sensorToWorldMatrix", glmExtractRotation(_sensorToWorldMatrix), + extractTranslation(_sensorToWorldMatrix), glm::vec4(1)); } _sensorToWorldMatrixCache.set(_sensorToWorldMatrix); + + updateJointFromController(controller::Action::LEFT_HAND, _controllerLeftHandMatrixCache); + updateJointFromController(controller::Action::RIGHT_HAND, _controllerRightHandMatrixCache); } // Update avatar head rotation with sensor data @@ -2215,3 +2229,31 @@ bool MyAvatar::didTeleport() { bool MyAvatar::hasDriveInput() const { return fabsf(_driveKeys[TRANSLATE_X]) > 0.0f || fabsf(_driveKeys[TRANSLATE_Y]) > 0.0f || fabsf(_driveKeys[TRANSLATE_Z]) > 0.0f; } + +glm::quat MyAvatar::getAbsoluteJointRotationInObjectFrame(int index) const { + switch(index) { + case CONTROLLER_LEFTHAND_INDEX: { + return getLeftHandControllerPoseInAvatarFrame().getRotation(); + } + case CONTROLLER_RIGHTHAND_INDEX: { + return getRightHandControllerPoseInAvatarFrame().getRotation(); + } + default: { + return Avatar::getAbsoluteJointRotationInObjectFrame(index); + } + } +} + +glm::vec3 MyAvatar::getAbsoluteJointTranslationInObjectFrame(int index) const { + switch(index) { + case CONTROLLER_LEFTHAND_INDEX: { + return getLeftHandControllerPoseInAvatarFrame().getTranslation(); + } + case CONTROLLER_RIGHTHAND_INDEX: { + return getRightHandControllerPoseInAvatarFrame().getTranslation(); + } + default: { + return Avatar::getAbsoluteJointTranslationInObjectFrame(index); + } + } +} diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 1f212a1fec..c4ffc08cbc 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -19,6 +19,7 @@ #include #include +#include #include "Avatar.h" #include "AtRestDetector.h" @@ -117,6 +118,9 @@ public: // as it moves through the world. void updateFromHMDSensorMatrix(const glm::mat4& hmdSensorMatrix); + // read the location of a hand controller and save the transform + void updateJointFromController(controller::Action poseKey, ThreadSafeValueCache& matrixCache); + // best called at end of main loop, just before rendering. // update sensor to world matrix from current body position and hmd sensor. // This is so the correct camera can be used for rendering. @@ -270,6 +274,9 @@ public: Q_INVOKABLE void setCharacterControllerEnabled(bool enabled); Q_INVOKABLE bool getCharacterControllerEnabled(); + virtual glm::quat getAbsoluteJointRotationInObjectFrame(int index) const override; + virtual glm::vec3 getAbsoluteJointTranslationInObjectFrame(int index) const override; + public slots: void increaseSize(); void decreaseSize(); @@ -410,9 +417,8 @@ private: bool _useSnapTurn { true }; bool _clearOverlayWhenMoving { true }; - // working copy of sensorToWorldMatrix. - // See AvatarData for thread-safe _sensorToWorldMatrixCache, used for outward facing access - glm::mat4 _sensorToWorldMatrix; + // working copies -- see AvatarData for thread-safe _sensorToWorldMatrixCache, used for outward facing access + glm::mat4 _sensorToWorldMatrix { glm::mat4() }; // cache of the current HMD sensor position and orientation // in sensor space. diff --git a/interface/src/scripting/WebWindowClass.cpp b/interface/src/scripting/WebWindowClass.cpp deleted file mode 100644 index 18beee4bbf..0000000000 --- a/interface/src/scripting/WebWindowClass.cpp +++ /dev/null @@ -1,182 +0,0 @@ -// -// WebWindowClass.cpp -// interface/src/scripting -// -// Created by Ryan Huffman on 11/06/14. -// Copyright 2014 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 -#include -#include -#include -#include -#include -#include -#include - -#include "Application.h" -#include "ui/DataWebPage.h" -#include "MainWindow.h" -#include "WebWindowClass.h" -#include "WindowScriptingInterface.h" - -ScriptEventBridge::ScriptEventBridge(QObject* parent) : QObject(parent) { -} - -void ScriptEventBridge::emitWebEvent(const QString& data) { - emit webEventReceived(data); -} - -void ScriptEventBridge::emitScriptEvent(const QString& data) { - emit scriptEventReceived(data); -} - -WebWindowClass::WebWindowClass(const QString& title, const QString& url, int width, int height) - : QObject(NULL), _eventBridge(new ScriptEventBridge(this)) { - auto dialogWidget = new QDialog(qApp->getWindow(), Qt::Window); - dialogWidget->setWindowTitle(title); - dialogWidget->resize(width, height); - dialogWidget->installEventFilter(this); - connect(dialogWidget, &QDialog::finished, this, &WebWindowClass::hasClosed); - - auto layout = new QVBoxLayout(dialogWidget); - layout->setContentsMargins(0, 0, 0, 0); - dialogWidget->setLayout(layout); - - _webView = new QWebView(dialogWidget); - - layout->addWidget(_webView); - - addEventBridgeToWindowObject(); - - _windowWidget = dialogWidget; - - auto style = QStyleFactory::create("fusion"); - if (style) { - _webView->setStyle(style); - } - - _webView->setPage(new DataWebPage()); - if (!url.startsWith("http") && !url.startsWith("file://")) { - _webView->setUrl(QUrl::fromLocalFile(url)); - } else { - _webView->setUrl(url); - } - - connect(this, &WebWindowClass::destroyed, _windowWidget, &QWidget::deleteLater); - connect(_webView->page()->mainFrame(), &QWebFrame::javaScriptWindowObjectCleared, - this, &WebWindowClass::addEventBridgeToWindowObject); -} - -WebWindowClass::~WebWindowClass() { -} - -bool WebWindowClass::eventFilter(QObject* sender, QEvent* event) { - if (sender == _windowWidget) { - if (event->type() == QEvent::Move) { - emit moved(getPosition()); - } - if (event->type() == QEvent::Resize) { - emit resized(getSize()); - } - } - - return false; -} - -void WebWindowClass::hasClosed() { - emit closed(); -} - -void WebWindowClass::addEventBridgeToWindowObject() { - _webView->page()->mainFrame()->addToJavaScriptWindowObject("EventBridge", _eventBridge); -} - -void WebWindowClass::setVisible(bool visible) { - if (visible) { - QMetaObject::invokeMethod(_windowWidget, "showNormal", Qt::AutoConnection); - QMetaObject::invokeMethod(_windowWidget, "raise", Qt::AutoConnection); - } - QMetaObject::invokeMethod(_windowWidget, "setVisible", Qt::AutoConnection, Q_ARG(bool, visible)); -} - -void WebWindowClass::setURL(const QString& url) { - if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "setURL", Qt::AutoConnection, Q_ARG(QString, url)); - return; - } - _webView->setUrl(url); -} - -QSizeF WebWindowClass::getSize() const { - QSizeF size = _windowWidget->size(); - return size; -} - -void WebWindowClass::setSize(QSizeF size) { - setSize(size.width(), size.height()); -} - -void WebWindowClass::setSize(int width, int height) { - if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "setSize", Qt::AutoConnection, Q_ARG(int, width), Q_ARG(int, height)); - return; - } - _windowWidget->resize(width, height); -} - -glm::vec2 WebWindowClass::getPosition() const { - QPoint position = _windowWidget->pos(); - return glm::vec2(position.x(), position.y()); -} - -void WebWindowClass::setPosition(glm::vec2 position) { - setPosition(position.x, position.y); -} - -void WebWindowClass::setPosition(int x, int y) { - if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "setPosition", Qt::AutoConnection, Q_ARG(int, x), Q_ARG(int, y)); - return; - } - _windowWidget->move(x, y); -} - -void WebWindowClass::raise() { - QMetaObject::invokeMethod(_windowWidget, "showNormal", Qt::AutoConnection); - QMetaObject::invokeMethod(_windowWidget, "raise", Qt::AutoConnection); -} - -QScriptValue WebWindowClass::constructor(QScriptContext* context, QScriptEngine* engine) { - WebWindowClass* retVal; - QString file = context->argument(0).toString(); - if (context->argument(4).toBool()) { - qWarning() << "ToolWindow views with WebWindow are no longer supported. Use OverlayWebWindow instead"; - return QScriptValue(); - } else { - qWarning() << "WebWindow views are deprecated. Use OverlayWebWindow instead"; - } - QMetaObject::invokeMethod(DependencyManager::get().data(), "doCreateWebWindow", Qt::BlockingQueuedConnection, - Q_RETURN_ARG(WebWindowClass*, retVal), - Q_ARG(const QString&, file), - Q_ARG(QString, context->argument(1).toString()), - Q_ARG(int, context->argument(2).toInteger()), - Q_ARG(int, context->argument(3).toInteger())); - - connect(engine, &QScriptEngine::destroyed, retVal, &WebWindowClass::deleteLater); - - return engine->newQObject(retVal); -} - -void WebWindowClass::setTitle(const QString& title) { - if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "setTitle", Qt::AutoConnection, Q_ARG(QString, title)); - return; - } - _windowWidget->setWindowTitle(title); -} diff --git a/interface/src/scripting/WebWindowClass.h b/interface/src/scripting/WebWindowClass.h deleted file mode 100644 index d7a610dd39..0000000000 --- a/interface/src/scripting/WebWindowClass.h +++ /dev/null @@ -1,80 +0,0 @@ -// -// WebWindowClass.h -// interface/src/scripting -// -// Created by Ryan Huffman on 11/06/14. -// Copyright 2014 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_WebWindowClass_h -#define hifi_WebWindowClass_h - -#include -#include -#include - -class ScriptEventBridge : public QObject { - Q_OBJECT -public: - ScriptEventBridge(QObject* parent = NULL); - -public slots: - void emitWebEvent(const QString& data); - void emitScriptEvent(const QString& data); - -signals: - void webEventReceived(const QString& data); - void scriptEventReceived(const QString& data); - -}; - -class WebWindowClass : public QObject { - Q_OBJECT - Q_PROPERTY(QObject* eventBridge READ getEventBridge) - Q_PROPERTY(QString url READ getURL) - Q_PROPERTY(glm::vec2 position READ getPosition WRITE setPosition); - Q_PROPERTY(QSizeF size READ getSize WRITE setSize); - -public: - WebWindowClass(const QString& title, const QString& url, int width, int height); - ~WebWindowClass(); - - static QScriptValue constructor(QScriptContext* context, QScriptEngine* engine); - -public slots: - void setVisible(bool visible); - glm::vec2 getPosition() const; - void setPosition(int x, int y); - void setPosition(glm::vec2 position); - QSizeF getSize() const; - void setSize(QSizeF size); - void setSize(int width, int height); - QString getURL() const { return _webView->url().url(); } - void setURL(const QString& url); - void raise(); - ScriptEventBridge* getEventBridge() const { return _eventBridge; } - void addEventBridgeToWindowObject(); - void setTitle(const QString& title); - -signals: - void visibilityChanged(bool visible); // Tool window - void moved(glm::vec2 position); - void resized(QSizeF size); - void closed(); - -protected: - virtual bool eventFilter(QObject* sender, QEvent* event) override; - -private slots: - void hasClosed(); - -private: - QWidget* _windowWidget; - QWebView* _webView; - ScriptEventBridge* _eventBridge; -}; - -#endif diff --git a/interface/src/scripting/WindowScriptingInterface.cpp b/interface/src/scripting/WindowScriptingInterface.cpp index 4eb8c67250..c528c26b99 100644 --- a/interface/src/scripting/WindowScriptingInterface.cpp +++ b/interface/src/scripting/WindowScriptingInterface.cpp @@ -21,7 +21,6 @@ #include "MainWindow.h" #include "Menu.h" #include "OffscreenUi.h" -#include "WebWindowClass.h" #include "WindowScriptingInterface.h" @@ -61,10 +60,6 @@ WindowScriptingInterface::WindowScriptingInterface() { }); } -WebWindowClass* WindowScriptingInterface::doCreateWebWindow(const QString& title, const QString& url, int width, int height) { - return new WebWindowClass(title, url, width, height); -} - QScriptValue WindowScriptingInterface::hasFocus() { return qApp->hasFocus(); } diff --git a/interface/src/scripting/WindowScriptingInterface.h b/interface/src/scripting/WindowScriptingInterface.h index 7a01be7fac..715d0657a3 100644 --- a/interface/src/scripting/WindowScriptingInterface.h +++ b/interface/src/scripting/WindowScriptingInterface.h @@ -16,9 +16,6 @@ #include #include -class WebWindowClass; - - class CustomPromptResult { public: QVariant value; @@ -65,9 +62,6 @@ signals: void snapshotTaken(const QString& path, bool notify); void snapshotShared(const QString& error); -private slots: - WebWindowClass* doCreateWebWindow(const QString& title, const QString& url, int width, int height); - private: QString getPreviousBrowseLocation() const; void setPreviousBrowseLocation(const QString& location); diff --git a/interface/src/ui/DataWebPage.cpp b/interface/src/ui/DataWebPage.cpp deleted file mode 100644 index 01feacc393..0000000000 --- a/interface/src/ui/DataWebPage.cpp +++ /dev/null @@ -1,54 +0,0 @@ -// -// DataWebPage.cpp -// interface/src/ui -// -// Created by Stephen Birarda on 2014-09-22. -// Copyright 2014 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 - -#include "Application.h" -#include -#include - -#include "DataWebPage.h" - -DataWebPage::DataWebPage(QObject* parent) : - QWebPage(parent) -{ - // use an OAuthNetworkAccessManager instead of regular QNetworkAccessManager so our requests are authed - setNetworkAccessManager(OAuthNetworkAccessManager::getInstance()); - - // give the page an empty stylesheet - settings()->setUserStyleSheetUrl(QUrl()); -} - -void DataWebPage::javaScriptConsoleMessage(const QString& message, int lineNumber, const QString& sourceID) { - qDebug() << "JS console message at line" << lineNumber << "from" << sourceID << "-" << message; -} - -bool DataWebPage::acceptNavigationRequest(QWebFrame* frame, const QNetworkRequest& request, QWebPage::NavigationType type) { - // Handle hifi:// links and links to files with particular extensions - QString urlString = request.url().toString(); - if (qApp->canAcceptURL(urlString)) { - if (qApp->acceptURL(urlString)) { - return false; // we handled it, so QWebPage doesn't need to handle it - } - } - - // Make hyperlinks with target="_blank" open in user's Web browser - if (type == QWebPage::NavigationTypeLinkClicked && frame == nullptr) { - qApp->openUrl(request.url()); - return false; // We handled it. - } - - return true; -} - -QString DataWebPage::userAgentForUrl(const QUrl& url) const { - return HIGH_FIDELITY_USER_AGENT; -} diff --git a/interface/src/ui/DataWebPage.h b/interface/src/ui/DataWebPage.h deleted file mode 100644 index f9aa5be8a8..0000000000 --- a/interface/src/ui/DataWebPage.h +++ /dev/null @@ -1,26 +0,0 @@ -// -// DataWebPage.h -// interface/src/ui -// -// Created by Stephen Birarda on 2014-09-22. -// Copyright 2014 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_DataWebPage_h -#define hifi_DataWebPage_h - -#include - -class DataWebPage : public QWebPage { -public: - DataWebPage(QObject* parent = 0); -protected: - void javaScriptConsoleMessage(const QString & message, int lineNumber, const QString & sourceID) override; - bool acceptNavigationRequest(QWebFrame* frame, const QNetworkRequest& request, QWebPage::NavigationType type) override; - virtual QString userAgentForUrl(const QUrl& url) const override; -}; - -#endif // hifi_DataWebPage_h diff --git a/interface/src/ui/ModelsBrowser.cpp b/interface/src/ui/ModelsBrowser.cpp index 91de4e36ac..430cc805ed 100644 --- a/interface/src/ui/ModelsBrowser.cpp +++ b/interface/src/ui/ModelsBrowser.cpp @@ -228,6 +228,7 @@ void ModelHandler::update() { QUrl url(_model.item(i,0)->data(Qt::UserRole).toString()); QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); QNetworkRequest request(url); + request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); request.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); QNetworkReply* reply = networkAccessManager.head(request); connect(reply, SIGNAL(finished()), SLOT(downloadFinished())); @@ -280,6 +281,7 @@ void ModelHandler::queryNewFiles(QString marker) { url.setQuery(query); QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); QNetworkRequest request(url); + request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); request.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); QNetworkReply* reply = networkAccessManager.get(request); connect(reply, SIGNAL(finished()), SLOT(downloadFinished())); diff --git a/interface/src/ui/ScriptEditorWidget.cpp b/interface/src/ui/ScriptEditorWidget.cpp index f76a5dff78..ada6b11355 100644 --- a/interface/src/ui/ScriptEditorWidget.cpp +++ b/interface/src/ui/ScriptEditorWidget.cpp @@ -161,6 +161,7 @@ void ScriptEditorWidget::loadFile(const QString& scriptPath) { } else { QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); QNetworkRequest networkRequest = QNetworkRequest(url); + networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); networkRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); QNetworkReply* reply = networkAccessManager.get(networkRequest); qDebug() << "Downloading included script at" << scriptPath; diff --git a/libraries/auto-updater/src/AutoUpdater.cpp b/libraries/auto-updater/src/AutoUpdater.cpp index ea4e43ff41..43563b1d71 100644 --- a/libraries/auto-updater/src/AutoUpdater.cpp +++ b/libraries/auto-updater/src/AutoUpdater.cpp @@ -33,6 +33,7 @@ void AutoUpdater::checkForUpdate() { void AutoUpdater::getLatestVersionData() { QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); QNetworkRequest latestVersionRequest(BUILDS_XML_URL); + latestVersionRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); latestVersionRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); QNetworkReply* reply = networkAccessManager.get(latestVersionRequest); connect(reply, &QNetworkReply::finished, this, &AutoUpdater::parseLatestVersionData); diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 9088ee0577..413180738a 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -374,6 +374,16 @@ QByteArray AvatarData::toByteArray(bool cullSmallChanges, bool sendAll) { } } + // faux joints + Transform controllerLeftHandTransform = Transform(getControllerLeftHandMatrix()); + destinationBuffer += packOrientationQuatToSixBytes(destinationBuffer, controllerLeftHandTransform.getRotation()); + destinationBuffer += packFloatVec3ToSignedTwoByteFixed(destinationBuffer, controllerLeftHandTransform.getTranslation(), + TRANSLATION_COMPRESSION_RADIX); + Transform controllerRightHandTransform = Transform(getControllerRightHandMatrix()); + destinationBuffer += packOrientationQuatToSixBytes(destinationBuffer, controllerRightHandTransform.getRotation()); + destinationBuffer += packFloatVec3ToSignedTwoByteFixed(destinationBuffer, controllerRightHandTransform.getTranslation(), + TRANSLATION_COMPRESSION_RADIX); + #ifdef WANT_DEBUG if (sendAll) { qDebug() << "AvatarData::toByteArray" << cullSmallChanges << sendAll @@ -429,6 +439,20 @@ bool AvatarData::shouldLogError(const quint64& now) { return false; } + +const unsigned char* unpackFauxJoint(const unsigned char* sourceBuffer, ThreadSafeValueCache& matrixCache) { + glm::quat orientation; + glm::vec3 position; + Transform transform; + sourceBuffer += unpackOrientationQuatFromSixBytes(sourceBuffer, orientation); + sourceBuffer += unpackFloatVec3FromSignedTwoByteFixed(sourceBuffer, position, TRANSLATION_COMPRESSION_RADIX); + transform.setTranslation(position); + transform.setRotation(orientation); + matrixCache.set(transform.getMatrix()); + return sourceBuffer; +} + + #define PACKET_READ_CHECK(ITEM_NAME, SIZE_TO_READ) \ if ((endPosition - sourceBuffer) < (int)SIZE_TO_READ) { \ if (shouldLogError(now)) { \ @@ -655,6 +679,10 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { } #endif + // faux joints + sourceBuffer = unpackFauxJoint(sourceBuffer, _controllerLeftHandMatrixCache); + sourceBuffer = unpackFauxJoint(sourceBuffer, _controllerRightHandMatrixCache); + int numBytesRead = sourceBuffer - startPosition; _averageBytesReceived.updateAverage(numBytesRead); return numBytesRead; @@ -915,7 +943,24 @@ void AvatarData::clearJointsData() { } } +int AvatarData::getFauxJointIndex(const QString& name) const { + if (name == "_SENSOR_TO_WORLD_MATRIX") { + return SENSOR_TO_WORLD_MATRIX_INDEX; + } + if (name == "_CONTROLLER_LEFTHAND") { + return CONTROLLER_LEFTHAND_INDEX; + } + if (name == "_CONTROLLER_RIGHTHAND") { + return CONTROLLER_RIGHTHAND_INDEX; + } + return -1; +} + int AvatarData::getJointIndex(const QString& name) const { + int result = getFauxJointIndex(name); + if (result != -1) { + return result; + } QReadLocker readLock(&_jointDataLock); return _jointIndices.value(name) - 1; } @@ -1161,6 +1206,7 @@ void AvatarData::updateJointMappings() { if (_skeletonModelURL.fileName().toLower().endsWith(".fst")) { QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); QNetworkRequest networkRequest = QNetworkRequest(_skeletonModelURL); + networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); networkRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); QNetworkReply* networkReply = networkAccessManager.get(networkRequest); connect(networkReply, &QNetworkReply::finished, this, &AvatarData::setJointMappingsFromNetworkReply); @@ -1743,6 +1789,17 @@ glm::mat4 AvatarData::getSensorToWorldMatrix() const { return _sensorToWorldMatrixCache.get(); } +// thread-safe +glm::mat4 AvatarData::getControllerLeftHandMatrix() const { + return _controllerLeftHandMatrixCache.get(); +} + +// thread-safe +glm::mat4 AvatarData::getControllerRightHandMatrix() const { + return _controllerRightHandMatrixCache.get(); +} + + QScriptValue RayToAvatarIntersectionResultToScriptValue(QScriptEngine* engine, const RayToAvatarIntersectionResult& value) { QScriptValue obj = engine->newObject(); obj.setProperty("intersects", value.intersects); diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 572657e921..715c24b8ee 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -173,6 +173,8 @@ class AvatarData : public QObject, public SpatiallyNestable { Q_PROPERTY(QUuid sessionUUID READ getSessionUUID) Q_PROPERTY(glm::mat4 sensorToWorldMatrix READ getSensorToWorldMatrix) + Q_PROPERTY(glm::mat4 controllerLeftHandMatrix READ getControllerLeftHandMatrix) + Q_PROPERTY(glm::mat4 controllerRightHandMatrix READ getControllerRightHandMatrix) public: @@ -356,6 +358,8 @@ public: // thread safe Q_INVOKABLE glm::mat4 getSensorToWorldMatrix() const; + Q_INVOKABLE glm::mat4 getControllerLeftHandMatrix() const; + Q_INVOKABLE glm::mat4 getControllerRightHandMatrix() const; public slots: void sendAvatarDataPacket(); @@ -369,6 +373,8 @@ public slots: virtual bool setAbsoluteJointRotationInObjectFrame(int index, const glm::quat& rotation) override { return false; } virtual bool setAbsoluteJointTranslationInObjectFrame(int index, const glm::vec3& translation) override { return false; } + float getTargetScale() { return _targetScale; } + protected: glm::vec3 _handPosition; @@ -433,6 +439,10 @@ protected: // used to transform any sensor into world space, including the _hmdSensorMat, or hand controllers. ThreadSafeValueCache _sensorToWorldMatrixCache { glm::mat4() }; + ThreadSafeValueCache _controllerLeftHandMatrixCache { glm::mat4() }; + ThreadSafeValueCache _controllerRightHandMatrixCache { glm::mat4() }; + + int getFauxJointIndex(const QString& name) const; private: friend void avatarStateFromFrame(const QByteArray& frameData, AvatarData* _avatar); @@ -519,5 +529,10 @@ Q_DECLARE_METATYPE(RayToAvatarIntersectionResult) QScriptValue RayToAvatarIntersectionResultToScriptValue(QScriptEngine* engine, const RayToAvatarIntersectionResult& results); void RayToAvatarIntersectionResultFromScriptValue(const QScriptValue& object, RayToAvatarIntersectionResult& results); +// faux joint indexes (-1 means invalid) +const int SENSOR_TO_WORLD_MATRIX_INDEX = 65534; // -2 +const int CONTROLLER_RIGHTHAND_INDEX = 65533; // -3 +const int CONTROLLER_LEFTHAND_INDEX = 65532; // -4 + #endif // hifi_AvatarData_h diff --git a/libraries/controllers/src/controllers/UserInputMapper.cpp b/libraries/controllers/src/controllers/UserInputMapper.cpp index 7490a44c11..ff44d5d13d 100755 --- a/libraries/controllers/src/controllers/UserInputMapper.cpp +++ b/libraries/controllers/src/controllers/UserInputMapper.cpp @@ -325,7 +325,6 @@ QString UserInputMapper::getActionName(Action action) const { return QString(); } - QVector UserInputMapper::getActionNames() const { Locker locker(_lock); QVector result; @@ -335,6 +334,12 @@ QVector UserInputMapper::getActionNames() const { return result; } +Pose UserInputMapper::getPoseState(Action action) const { + assert(QThread::currentThread() == thread()); + return _poseStates[toInt(action)]; +} + + bool UserInputMapper::triggerHapticPulse(float strength, float duration, controller::Hand hand) { Locker locker(_lock); bool toReturn = false; diff --git a/libraries/controllers/src/controllers/UserInputMapper.h b/libraries/controllers/src/controllers/UserInputMapper.h index 874e5054ea..baa05f2f9f 100644 --- a/libraries/controllers/src/controllers/UserInputMapper.h +++ b/libraries/controllers/src/controllers/UserInputMapper.h @@ -81,7 +81,7 @@ namespace controller { QVector getAllActions() const; QString getActionName(Action action) const; float getActionState(Action action) const { return _actionStates[toInt(action)]; } - Pose getPoseState(Action action) const { return _poseStates[toInt(action)]; } + Pose getPoseState(Action action) const; int findAction(const QString& actionName) const; QVector getActionNames() const; Input inputFromAction(Action action) const { return getActionInputs()[toInt(action)].first; } diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h index afd8f7d45b..ef15861843 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h @@ -69,6 +69,8 @@ public: virtual bool wantVsync() const { return true; } void setVsyncEnabled(bool vsyncEnabled) { _vsyncEnabled = vsyncEnabled; } bool isVsyncEnabled() const { return _vsyncEnabled; } + // Three threads, one for rendering, one for texture transfers, one reserved for the GL driver + int getRequiredThreadCount() const override { return 3; } protected: friend class PresentThread; diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp index 6904700be5..01f607b0ed 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp @@ -527,9 +527,11 @@ void HmdDisplayPlugin::compositeExtra() { if (_presentHandPoses[index] == IDENTITY_MATRIX) { return; } - const auto& points = _presentHandLaserPoints[index]; - const auto& lasers = _presentHandLasers[index]; - geometryCache->renderGlowLine(batch, points.first, points.second, lasers.color); + const auto& laser = _presentHandLasers[index]; + if (laser.valid()) { + const auto& points = _presentHandLaserPoints[index]; + geometryCache->renderGlowLine(batch, points.first, points.second, laser.color); + } }); }); } diff --git a/libraries/entities/src/EntityActionInterface.cpp b/libraries/entities/src/EntityActionInterface.cpp index ce9a93a6ac..2ce4ce5555 100644 --- a/libraries/entities/src/EntityActionInterface.cpp +++ b/libraries/entities/src/EntityActionInterface.cpp @@ -100,6 +100,9 @@ EntityActionType EntityActionInterface::actionTypeFromString(QString actionTypeS if (normalizedActionTypeString == "hold") { return ACTION_TYPE_HOLD; } + if (normalizedActionTypeString == "traveloriented") { + return ACTION_TYPE_TRAVEL_ORIENTED; + } qDebug() << "Warning -- EntityActionInterface::actionTypeFromString got unknown action-type name" << actionTypeString; return ACTION_TYPE_NONE; @@ -115,6 +118,8 @@ QString EntityActionInterface::actionTypeToString(EntityActionType actionType) { return "spring"; case ACTION_TYPE_HOLD: return "hold"; + case ACTION_TYPE_TRAVEL_ORIENTED: + return "travel-oriented"; } assert(false); return "none"; diff --git a/libraries/entities/src/EntityActionInterface.h b/libraries/entities/src/EntityActionInterface.h index 9a881cf94c..d9a901f1f6 100644 --- a/libraries/entities/src/EntityActionInterface.h +++ b/libraries/entities/src/EntityActionInterface.h @@ -28,7 +28,8 @@ enum EntityActionType { ACTION_TYPE_NONE = 0, ACTION_TYPE_OFFSET = 1000, ACTION_TYPE_SPRING = 2000, - ACTION_TYPE_HOLD = 3000 + ACTION_TYPE_HOLD = 3000, + ACTION_TYPE_TRAVEL_ORIENTED = 4000 }; diff --git a/libraries/fbx/src/FSTReader.cpp b/libraries/fbx/src/FSTReader.cpp index 6f5d0d7ec5..1f7999bdaa 100644 --- a/libraries/fbx/src/FSTReader.cpp +++ b/libraries/fbx/src/FSTReader.cpp @@ -191,6 +191,7 @@ FSTReader::ModelType FSTReader::predictModelType(const QVariantHash& mapping) { QVariantHash FSTReader::downloadMapping(const QString& url) { QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); QNetworkRequest networkRequest = QNetworkRequest(url); + networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); networkRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); QNetworkReply* reply = networkAccessManager.get(networkRequest); qDebug() << "Downloading avatar file at " << url; diff --git a/libraries/fbx/src/OBJReader.cpp b/libraries/fbx/src/OBJReader.cpp index 38121555ed..02b0afdf26 100644 --- a/libraries/fbx/src/OBJReader.cpp +++ b/libraries/fbx/src/OBJReader.cpp @@ -282,6 +282,7 @@ QNetworkReply* OBJReader::request(QUrl& url, bool isTest) { }); QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); QNetworkRequest netRequest(url); + netRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); QNetworkReply* netReply = isTest ? networkAccessManager.head(netRequest) : networkAccessManager.get(netRequest); if (!qApp || aboutToQuit) { netReply->deleteLater(); diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp index 2f4cbad26c..1c3850f8cb 100644 --- a/libraries/networking/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -218,7 +218,7 @@ void AccountManager::sendRequest(const QString& path, QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); QNetworkRequest networkRequest; - + networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); networkRequest.setHeader(QNetworkRequest::UserAgentHeader, _userAgentGetter()); networkRequest.setRawHeader(METAVERSE_SESSION_ID_HEADER, @@ -484,6 +484,7 @@ void AccountManager::requestAccessToken(const QString& login, const QString& pas QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); QNetworkRequest request; + request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); request.setHeader(QNetworkRequest::UserAgentHeader, _userAgentGetter()); QUrl grantURL = _authURL; @@ -578,6 +579,7 @@ void AccountManager::requestProfile() { profileURL.setPath("/api/v1/user/profile"); QNetworkRequest profileRequest(profileURL); + profileRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); profileRequest.setHeader(QNetworkRequest::UserAgentHeader, _userAgentGetter()); profileRequest.setRawHeader(ACCESS_TOKEN_AUTHORIZATION_HEADER, _accountInfo.getAccessToken().authorizationHeaderValue()); diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp index c9c9d73394..b4145c73f5 100644 --- a/libraries/networking/src/AddressManager.cpp +++ b/libraries/networking/src/AddressManager.cpp @@ -836,6 +836,7 @@ void AddressManager::ifLocalSandboxRunningElse(std::function localSandbo QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); QNetworkRequest sandboxStatus(SANDBOX_STATUS_URL); + sandboxStatus.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); sandboxStatus.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); QNetworkReply* reply = networkAccessManager.get(sandboxStatus); diff --git a/libraries/networking/src/HTTPResourceRequest.cpp b/libraries/networking/src/HTTPResourceRequest.cpp index 11ab436933..392654a419 100644 --- a/libraries/networking/src/HTTPResourceRequest.cpp +++ b/libraries/networking/src/HTTPResourceRequest.cpp @@ -49,6 +49,7 @@ void HTTPResourceRequest::cleanupTimer() { void HTTPResourceRequest::doSend() { QNetworkRequest networkRequest(_url); + networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); networkRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); if (_cacheEnabled) { diff --git a/libraries/networking/src/OAuthNetworkAccessManager.cpp b/libraries/networking/src/OAuthNetworkAccessManager.cpp index 92e7a2ff4f..15d5acbc67 100644 --- a/libraries/networking/src/OAuthNetworkAccessManager.cpp +++ b/libraries/networking/src/OAuthNetworkAccessManager.cpp @@ -37,6 +37,7 @@ QNetworkReply* OAuthNetworkAccessManager::createRequest(QNetworkAccessManager::O if (accountManager->hasValidAccessToken() && req.url().host() == NetworkingConstants::METAVERSE_SERVER_URL.host()) { QNetworkRequest authenticatedRequest(req); + authenticatedRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); authenticatedRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); authenticatedRequest.setRawHeader(ACCESS_TOKEN_AUTHORIZATION_HEADER, accountManager->getAccountInfo().getAccessToken().authorizationHeaderValue()); diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index 0d25d4f1be..c0272a644d 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -47,12 +47,12 @@ PacketVersion versionForPacketType(PacketType packetType) { case PacketType::EntityAdd: case PacketType::EntityEdit: case PacketType::EntityData: - return VERSION_WEB_ENTITIES_SUPPORT_DPI; + return VERSION_ENTITIES_ARROW_ACTION; case PacketType::AvatarIdentity: case PacketType::AvatarData: case PacketType::BulkAvatarData: case PacketType::KillAvatar: - return static_cast(AvatarMixerPacketVersion::SensorToWorldMat); + return static_cast(AvatarMixerPacketVersion::HandControllerJoints); case PacketType::ICEServerHeartbeat: return 18; // ICE Server Heartbeat signing case PacketType::AssetGetInfo: diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 3ecdb75a18..8d08585c2b 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -187,13 +187,15 @@ const PacketVersion VERSION_ENTITIES_PROPERLY_ENCODE_SHAPE_EDITS = 60; const PacketVersion VERSION_MODEL_ENTITIES_SUPPORT_STATIC_MESH = 61; const PacketVersion VERSION_MODEL_ENTITIES_SUPPORT_SIMPLE_HULLS = 62; const PacketVersion VERSION_WEB_ENTITIES_SUPPORT_DPI = 63; +const PacketVersion VERSION_ENTITIES_ARROW_ACTION = 64; enum class AvatarMixerPacketVersion : PacketVersion { TranslationSupport = 17, SoftAttachmentSupport, AvatarEntities, AbsoluteSixByteRotations, - SensorToWorldMat + SensorToWorldMat, + HandControllerJoints }; enum class DomainConnectRequestVersion : PacketVersion { diff --git a/libraries/physics/src/ObjectActionTravelOriented.cpp b/libraries/physics/src/ObjectActionTravelOriented.cpp new file mode 100644 index 0000000000..18d09d21d9 --- /dev/null +++ b/libraries/physics/src/ObjectActionTravelOriented.cpp @@ -0,0 +1,206 @@ +// +// ObjectActionTravelOriented.cpp +// libraries/physics/src +// +// Created by Seth Alves 2015-6-5 +// 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 + +#include "QVariantGLM.h" +#include "ObjectActionTravelOriented.h" + +const uint16_t ObjectActionTravelOriented::actionVersion = 1; + + +ObjectActionTravelOriented::ObjectActionTravelOriented(const QUuid& id, EntityItemPointer ownerEntity) : + ObjectAction(ACTION_TYPE_TRAVEL_ORIENTED, id, ownerEntity) { + #if WANT_DEBUG + qDebug() << "ObjectActionTravelOriented::ObjectActionTravelOriented"; + #endif +} + +ObjectActionTravelOriented::~ObjectActionTravelOriented() { + #if WANT_DEBUG + qDebug() << "ObjectActionTravelOriented::~ObjectActionTravelOriented"; + #endif +} + +void ObjectActionTravelOriented::updateActionWorker(btScalar deltaTimeStep) { + withReadLock([&] { + auto ownerEntity = _ownerEntity.lock(); + if (!ownerEntity) { + return; + } + void* physicsInfo = ownerEntity->getPhysicsInfo(); + if (!physicsInfo) { + return; + } + ObjectMotionState* motionState = static_cast(physicsInfo); + btRigidBody* rigidBody = motionState->getRigidBody(); + if (!rigidBody) { + qDebug() << "ObjectActionTravelOriented::updateActionWorker no rigidBody"; + return; + } + const float MAX_TIMESCALE = 600.0f; // 10 min is a long time + if (_angularTimeScale > MAX_TIMESCALE) { + return; + } + + // find normalized velocity + glm::vec3 velocity = bulletToGLM(rigidBody->getLinearVelocity()); + float speed = glm::length(velocity); + const float TRAVEL_ORIENTED_TOO_SLOW = 0.001f; // meters / second + if (speed < TRAVEL_ORIENTED_TOO_SLOW) { + return; + } + glm::vec3 direction = glm::normalize(velocity); + + // find current angle of "forward" + btQuaternion bodyRotation = rigidBody->getOrientation(); + glm::quat orientation = bulletToGLM(bodyRotation); + glm::vec3 forwardInWorldFrame = glm::normalize(orientation * _forward); + + // find the rotation that would line up direction and forward + glm::quat neededRotation = glm::rotation(forwardInWorldFrame, direction); + glm::quat rotationalTarget = neededRotation * orientation; + + btVector3 targetAngularVelocity(0.0f, 0.0f, 0.0f); + + auto alignmentDot = bodyRotation.dot(glmToBullet(rotationalTarget)); + const float ALMOST_ONE = 0.99999f; + if (glm::abs(alignmentDot) < ALMOST_ONE) { + btQuaternion target = glmToBullet(rotationalTarget); + if (alignmentDot < 0.0f) { + target = -target; + } + // if dQ is the incremental rotation that gets an object from Q0 to Q1 then: + // + // Q1 = dQ * Q0 + // + // solving for dQ gives: + // + // dQ = Q1 * Q0^ + btQuaternion deltaQ = target * bodyRotation.inverse(); + float speed = deltaQ.getAngle() / _angularTimeScale; + targetAngularVelocity = speed * deltaQ.getAxis(); + if (speed > rigidBody->getAngularSleepingThreshold()) { + rigidBody->activate(); + } + } + // this action is aggresively critically damped and defeats the current velocity + rigidBody->setAngularVelocity(targetAngularVelocity); + }); +} + +const float MIN_TIMESCALE = 0.1f; + + +bool ObjectActionTravelOriented::updateArguments(QVariantMap arguments) { + glm::vec3 forward; + float angularTimeScale; + + bool needUpdate = false; + bool somethingChanged = ObjectAction::updateArguments(arguments); + withReadLock([&]{ + bool ok = true; + forward = EntityActionInterface::extractVec3Argument("travel oriented action", arguments, "forward", ok, true); + if (!ok) { + forward = _forward; + } + ok = true; + angularTimeScale = + EntityActionInterface::extractFloatArgument("travel oriented action", arguments, "angularTimeScale", ok, false); + if (!ok) { + angularTimeScale = _angularTimeScale; + } + + if (somethingChanged || + forward != _forward || + angularTimeScale != _angularTimeScale) { + // something changed + needUpdate = true; + } + }); + + if (needUpdate) { + withWriteLock([&] { + _forward = forward; + _angularTimeScale = glm::max(MIN_TIMESCALE, glm::abs(angularTimeScale)); + _active = (_forward != glm::vec3()); + + auto ownerEntity = _ownerEntity.lock(); + if (ownerEntity) { + ownerEntity->setActionDataDirty(true); + ownerEntity->setActionDataNeedsTransmit(true); + } + }); + activateBody(); + } + + return true; +} + +QVariantMap ObjectActionTravelOriented::getArguments() { + QVariantMap arguments = ObjectAction::getArguments(); + withReadLock([&] { + arguments["forward"] = glmToQMap(_forward); + arguments["angularTimeScale"] = _angularTimeScale; + }); + return arguments; +} + +QByteArray ObjectActionTravelOriented::serialize() const { + QByteArray serializedActionArguments; + QDataStream dataStream(&serializedActionArguments, QIODevice::WriteOnly); + + dataStream << ACTION_TYPE_TRAVEL_ORIENTED; + dataStream << getID(); + dataStream << ObjectActionTravelOriented::actionVersion; + + withReadLock([&] { + dataStream << _forward; + dataStream << _angularTimeScale; + + dataStream << localTimeToServerTime(_expires); + dataStream << _tag; + }); + + return serializedActionArguments; +} + +void ObjectActionTravelOriented::deserialize(QByteArray serializedArguments) { + QDataStream dataStream(serializedArguments); + + EntityActionType type; + dataStream >> type; + assert(type == getType()); + + QUuid id; + dataStream >> id; + assert(id == getID()); + + uint16_t serializationVersion; + dataStream >> serializationVersion; + if (serializationVersion != ObjectActionTravelOriented::actionVersion) { + assert(false); + return; + } + + withWriteLock([&] { + dataStream >> _forward; + dataStream >> _angularTimeScale; + + quint64 serverExpires; + dataStream >> serverExpires; + _expires = serverTimeToLocalTime(serverExpires); + + dataStream >> _tag; + + _active = (_forward != glm::vec3()); + }); +} diff --git a/libraries/physics/src/ObjectActionTravelOriented.h b/libraries/physics/src/ObjectActionTravelOriented.h new file mode 100644 index 0000000000..66a425b409 --- /dev/null +++ b/libraries/physics/src/ObjectActionTravelOriented.h @@ -0,0 +1,39 @@ +// +// ObjectActionTravelOriented.h +// libraries/physics/src +// +// Created by Seth Alves 2016-8-28 +// 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_ObjectActionTravelOriented_h +#define hifi_ObjectActionTravelOriented_h + +#include "ObjectAction.h" + +class ObjectActionTravelOriented : public ObjectAction { +public: + ObjectActionTravelOriented(const QUuid& id, EntityItemPointer ownerEntity); + virtual ~ObjectActionTravelOriented(); + + virtual bool updateArguments(QVariantMap arguments) override; + virtual QVariantMap getArguments() override; + + virtual void updateActionWorker(float deltaTimeStep) override; + + virtual QByteArray serialize() const override; + virtual void deserialize(QByteArray serializedArguments) override; + +protected: + static const uint16_t actionVersion; + + glm::vec3 _forward { glm::vec3() }; // the vector in object space that should point in the direction of travel + float _angularTimeScale { 0.1f }; + + glm::vec3 _angularVelocityTarget; +}; + +#endif // hifi_ObjectActionTravelOriented_h diff --git a/libraries/plugins/src/plugins/DisplayPlugin.h b/libraries/plugins/src/plugins/DisplayPlugin.h index 5111bda95f..eac08716a1 100644 --- a/libraries/plugins/src/plugins/DisplayPlugin.h +++ b/libraries/plugins/src/plugins/DisplayPlugin.h @@ -128,6 +128,7 @@ public: Present = QEvent::User + 1 }; + virtual int getRequiredThreadCount() const { return 0; } virtual bool isHmd() const { return false; } virtual int getHmdScreen() const { return -1; } /// By default, all HMDs are stereo diff --git a/libraries/script-engine/src/ScriptsModel.cpp b/libraries/script-engine/src/ScriptsModel.cpp index 0ad2ad01a7..8755195932 100644 --- a/libraries/script-engine/src/ScriptsModel.cpp +++ b/libraries/script-engine/src/ScriptsModel.cpp @@ -185,6 +185,7 @@ void ScriptsModel::requestDefaultFiles(QString marker) { QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); QNetworkRequest request(url); + request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); request.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); QNetworkReply* reply = networkAccessManager.get(request); connect(reply, SIGNAL(finished()), SLOT(downloadFinished())); diff --git a/libraries/script-engine/src/XMLHttpRequestClass.cpp b/libraries/script-engine/src/XMLHttpRequestClass.cpp index 15b2576331..818521ecc5 100644 --- a/libraries/script-engine/src/XMLHttpRequestClass.cpp +++ b/libraries/script-engine/src/XMLHttpRequestClass.cpp @@ -45,6 +45,7 @@ XMLHttpRequestClass::XMLHttpRequestClass(QScriptEngine* engine) : _timer(this), _numRedirects(0) { + _request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); _timer.setSingleShot(true); } diff --git a/libraries/shared/src/RegisteredMetaTypes.cpp b/libraries/shared/src/RegisteredMetaTypes.cpp index 8e6c1ef6ed..171b58de17 100644 --- a/libraries/shared/src/RegisteredMetaTypes.cpp +++ b/libraries/shared/src/RegisteredMetaTypes.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include "RegisteredMetaTypes.h" @@ -32,6 +33,7 @@ int xColorMetaTypeId = qRegisterMetaType(); int pickRayMetaTypeId = qRegisterMetaType(); int collisionMetaTypeId = qRegisterMetaType(); int qMapURLStringMetaTypeId = qRegisterMetaType>(); +int socketErrorMetaTypeId = qRegisterMetaType(); void registerMetaTypes(QScriptEngine* engine) { qScriptRegisterMetaType(engine, mat4toScriptValue, mat4FromScriptValue); diff --git a/libraries/ui/src/VrMenu.cpp b/libraries/ui/src/VrMenu.cpp index c2732197d3..f4b365265d 100644 --- a/libraries/ui/src/VrMenu.cpp +++ b/libraries/ui/src/VrMenu.cpp @@ -155,7 +155,7 @@ void bindActionToQmlAction(QObject* qmlAction, QAction* action) { QObject::connect(qmlAction, SIGNAL(triggered()), action, SLOT(trigger())); } -class QQuickMenuItem; +class QQuickMenuItem1; void VrMenu::addAction(QMenu* menu, QAction* action) { Q_ASSERT(!MenuUserData::hasData(action)); @@ -167,10 +167,9 @@ void VrMenu::addAction(QMenu* menu, QAction* action) { } QObject* menuQml = findMenuObject(userData->uuid.toString()); Q_ASSERT(menuQml); - QQuickMenuItem* returnedValue { nullptr }; - + QQuickMenuItem1* returnedValue { nullptr }; bool invokeResult = QMetaObject::invokeMethod(menuQml, "addItem", Qt::DirectConnection, - Q_RETURN_ARG(QQuickMenuItem*, returnedValue), + Q_RETURN_ARG(QQuickMenuItem1*, returnedValue), Q_ARG(QString, action->text())); Q_ASSERT(invokeResult); @@ -206,10 +205,10 @@ void VrMenu::insertAction(QAction* before, QAction* action) { beforeQml = findMenuObject(beforeUserData->uuid.toString()); } QObject* menu = beforeQml->parent(); - QQuickMenuItem* returnedValue { nullptr }; + QQuickMenuItem1* returnedValue { nullptr }; // FIXME this needs to find the index of the beforeQml item and call insertItem(int, object) bool invokeResult = QMetaObject::invokeMethod(menu, "addItem", Qt::DirectConnection, - Q_RETURN_ARG(QQuickMenuItem*, returnedValue), + Q_RETURN_ARG(QQuickMenuItem1*, returnedValue), Q_ARG(QString, action->text())); Q_ASSERT(invokeResult); QObject* result = reinterpret_cast(returnedValue); // returnedValue.value(); diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.h b/plugins/openvr/src/OpenVrDisplayPlugin.h index 75f8c51b0e..025f879d84 100644 --- a/plugins/openvr/src/OpenVrDisplayPlugin.h +++ b/plugins/openvr/src/OpenVrDisplayPlugin.h @@ -58,6 +58,9 @@ public: void unsuppressKeyboard() override; bool isKeyboardVisible() override; + // Needs an additional thread for VR submission + int getRequiredThreadCount() const override { return Parent::getRequiredThreadCount() + 1; } + protected: bool internalActivate() override; void internalDeactivate() override; diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 60d41d9105..28488c9f3b 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -11,6 +11,7 @@ // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +/* global setEntityCustomData, getEntityCustomData, flatten, Xform, Script, Quat, Vec3, MyAvatar, Entities, Overlays, Settings, Reticle, Controller, Camera, Messages, Mat4 */ (function() { // BEGIN LOCAL_SCOPE @@ -24,6 +25,9 @@ var WANT_DEBUG = false; var WANT_DEBUG_STATE = false; var WANT_DEBUG_SEARCH_NAME = null; +var FORCE_IGNORE_IK = true; +var SHOW_GRAB_POINT_SPHERE = true; + // // these tune time-averaging and "on" value for analog trigger // @@ -59,6 +63,7 @@ var EQUIP_SPHERE_COLOR = { var EQUIP_SPHERE_ALPHA = 0.15; var EQUIP_SPHERE_SCALE_FACTOR = 0.65; + // // distant manipulation // @@ -87,21 +92,21 @@ var COLORS_GRAB_DISTANCE_HOLD = { blue: 214 }; - var PICK_MAX_DISTANCE = 500; // max length of pick-ray // // near grabbing // -var EQUIP_RADIUS = 0.1; // radius used for palm vs equip-hotspot for equipping. +var EQUIP_RADIUS = 0.2; // radius used for palm vs equip-hotspot for equipping. // if EQUIP_HOTSPOT_RENDER_RADIUS is greater than zero, the hotspot will appear before the hand // has reached the required position, and then grow larger once the hand is close enough to equip. var EQUIP_HOTSPOT_RENDER_RADIUS = 0.0; // radius used for palm vs equip-hotspot for rendering hot-spots +var MAX_EQUIP_HOTSPOT_RADIUS = 1.0; var NEAR_GRABBING_ACTION_TIMEFRAME = 0.05; // how quickly objects move to their new position -var NEAR_GRAB_RADIUS = 0.15; // radius used for palm vs object for near grabbing. +var NEAR_GRAB_RADIUS = 0.07; // radius used for palm vs object for near grabbing. var NEAR_GRAB_MAX_DISTANCE = 1.0; // you cannot grab objects that are this far away from your hand var NEAR_GRAB_PICK_RADIUS = 0.25; // radius used for search ray vs object for near grabbing. @@ -112,6 +117,13 @@ var NEAR_GRABBING_KINEMATIC = true; // force objects to be kinematic when near-g // if an equipped item is "adjusted" to be too far from the hand it's in, it will be unequipped. var CHECK_TOO_FAR_UNEQUIP_TIME = 0.3; // seconds, duration between checks + +var GRAB_POINT_SPHERE_OFFSET = { x: 0.0, y: 0.2, z: 0.0 }; +var GRAB_POINT_SPHERE_RADIUS = NEAR_GRAB_RADIUS; +var GRAB_POINT_SPHERE_COLOR = { red: 20, green: 90, blue: 238 }; +var GRAB_POINT_SPHERE_ALPHA = 0.85; + + // // other constants // @@ -168,7 +180,7 @@ var USE_BLACKLIST = true; var blacklist = []; var FORBIDDEN_GRAB_NAMES = ["Grab Debug Entity", "grab pointer"]; -var FORBIDDEN_GRAB_TYPES = ['Unknown', 'Light', 'PolyLine', 'Zone']; +var FORBIDDEN_GRAB_TYPES = ["Unknown", "Light", "PolyLine", "Zone"]; // states for the state machine var STATE_OFF = 0; @@ -183,7 +195,6 @@ var STATE_ENTITY_TOUCHING = 7; // "collidesWith" is specified by comma-separated list of group names // the possible group names are: static, dynamic, kinematic, myAvatar, otherAvatar var COLLIDES_WITH_WHILE_GRABBED = "dynamic,otherAvatar"; -var COLLIDES_WITH_WHILE_MULTI_GRABBED = "dynamic"; var HEART_BEAT_INTERVAL = 5 * MSECS_PER_SEC; var HEART_BEAT_TIMEOUT = 15 * MSECS_PER_SEC; @@ -273,11 +284,9 @@ function projectOntoEntityXYPlane(entityID, worldPos) { y: (1 - normalizedPos.y) * props.dimensions.y }; // flip y-axis } -function handLaserIntersectEntity(entityID, hand) { - var standardControllerValue = (hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; - var pose = Controller.getPoseValue(standardControllerValue); - var worldHandPosition = Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, pose.translation), MyAvatar.position); - var worldHandRotation = Quat.multiply(MyAvatar.orientation, pose.rotation); +function handLaserIntersectEntity(entityID, start) { + var worldHandPosition = start.position; + var worldHandRotation = start.orientation; var props = entityPropertiesCache.getProps(entityID); @@ -355,7 +364,7 @@ function entityIsGrabbedByOther(entityID) { for (var actionIndex = 0; actionIndex < actionIDs.length; actionIndex++) { var actionID = actionIDs[actionIndex]; var actionArguments = Entities.getActionArguments(entityID, actionID); - var tag = actionArguments["tag"]; + var tag = actionArguments.tag; if (tag == getTag()) { // we see a grab-*uuid* shaped tag, but it's our tag, so that's okay. continue; @@ -680,9 +689,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp) { if (overlayInfoSet.timestamp != timestamp && overlayInfoSet.currentSize <= 0.05) { // this is an old overlay, that has finished fading out, delete it! - overlayInfoSet.overlays.forEach(function(overlay) { - Overlays.deleteOverlay(overlay); - }); + overlayInfoSet.overlays.forEach(Overlays.deleteOverlay); delete this.map[keys[i]]; } else { // update overlay position, rotation to follow the object it's attached to. @@ -714,16 +721,36 @@ var equipHotspotBuddy = new EquipHotspotBuddy(); function MyController(hand) { this.hand = hand; - if (this.hand === RIGHT_HAND) { - this.getHandPosition = MyAvatar.getRightPalmPosition; - // this.getHandRotation = MyAvatar.getRightPalmRotation; - } else { - this.getHandPosition = MyAvatar.getLeftPalmPosition; - // this.getHandRotation = MyAvatar.getLeftPalmRotation; - } - this.getHandRotation = function() { - var controllerHandInput = (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; - return Quat.multiply(MyAvatar.orientation, Controller.getPoseValue(controllerHandInput).rotation); + this.autoUnequipCounter = 0; + + // handPosition is where the avatar's hand appears to be, in-world. + this.getHandPosition = function () { + if (this.hand === RIGHT_HAND) { + return MyAvatar.getRightPalmPosition(); + } else { + return MyAvatar.getLeftPalmPosition(); + } + }; + this.getHandRotation = function () { + if (this.hand === RIGHT_HAND) { + return MyAvatar.getRightPalmRotation(); + } else { + return MyAvatar.getLeftPalmRotation(); + } + }; + // controllerLocation is where the controller would be, in-world. + this.getControllerLocation = function (doOffset) { + var standardControllerValue = (hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; + var pose = Controller.getPoseValue(standardControllerValue); + + var orientation = Quat.multiply(MyAvatar.orientation, pose.rotation); + var position = Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, pose.translation), MyAvatar.position); + // add to the real position so the grab-point is out in front of the hand, a bit + if (doOffset) { + position = Vec3.sum(position, Vec3.multiplyQbyV(orientation, GRAB_POINT_SPHERE_OFFSET)); + } + + return {position: position, orientation: orientation}; }; this.actionID = null; // action this script created... @@ -830,6 +857,39 @@ function MyController(hand) { } }; + this.grabPointSphereOn = function() { + if (!SHOW_GRAB_POINT_SPHERE) { + return; + } + if (!MyAvatar.sessionUUID) { + return; + } + if (!this.grabPointSphere) { + this.grabPointSphere = Overlays.addOverlay("sphere", { + localPosition: GRAB_POINT_SPHERE_OFFSET, + localRotation: { x: 0, y: 0, z: 0, w: 1 }, + dimensions: GRAB_POINT_SPHERE_RADIUS, + color: GRAB_POINT_SPHERE_COLOR, + alpha: GRAB_POINT_SPHERE_ALPHA, + solid: true, + visible: true, + ignoreRayIntersection: true, + drawInFront: false, + parentID: MyAvatar.sessionUUID, + parentJointIndex: MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? + "_CONTROLLER_RIGHTHAND" : + "_CONTROLLER_LEFTHAND") + }); + } + }; + + this.grabPointSphereOff = function() { + if (this.grabPointSphere) { + Overlays.deleteOverlay(this.grabPointSphere); + this.grabPointSphere = null; + } + }; + this.searchSphereOn = function(location, size, color) { var rotation = Quat.lookAt(location, Camera.getPosition(), Vec3.UP); @@ -905,10 +965,14 @@ function MyController(hand) { var searchSphereLocation = Vec3.sum(distantPickRay.origin, Vec3.multiply(distantPickRay.direction, this.searchSphereDistance)); this.searchSphereOn(searchSphereLocation, SEARCH_SPHERE_SIZE * this.searchSphereDistance, - (this.triggerSmoothedGrab() || this.secondarySqueezed()) ? COLORS_GRAB_SEARCHING_FULL_SQUEEZE : COLORS_GRAB_SEARCHING_HALF_SQUEEZE); + (this.triggerSmoothedGrab() || this.secondarySqueezed()) ? + COLORS_GRAB_SEARCHING_FULL_SQUEEZE : + COLORS_GRAB_SEARCHING_HALF_SQUEEZE); if (PICK_WITH_HAND_RAY) { this.overlayLineOn(handPosition, searchSphereLocation, - (this.triggerSmoothedGrab() || this.secondarySqueezed()) ? COLORS_GRAB_SEARCHING_FULL_SQUEEZE : COLORS_GRAB_SEARCHING_HALF_SQUEEZE); + (this.triggerSmoothedGrab() || this.secondarySqueezed()) ? + COLORS_GRAB_SEARCHING_FULL_SQUEEZE : + COLORS_GRAB_SEARCHING_HALF_SQUEEZE); } }; @@ -958,7 +1022,8 @@ function MyController(hand) { this.turnOffVisualizations = function() { this.overlayLineOff(); - + this.grabPointSphereOff(); + this.lineOff(); this.searchSphereOff(); restore2DMode(); @@ -1029,19 +1094,20 @@ function MyController(hand) { } if (!this.waitForTriggerRelease && this.triggerSmoothedSqueezed()) { this.lastPickTime = 0; - var controllerHandInput = (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; - this.startingHandRotation = Controller.getPoseValue(controllerHandInput).rotation; + this.startingHandRotation = this.getControllerLocation(true).orientation; if (this.triggerSmoothedSqueezed()) { this.setState(STATE_SEARCHING, "trigger squeeze detected"); return; } } - var candidateEntities = Entities.findEntities(this.getHandPosition(), EQUIP_HOTSPOT_RENDER_RADIUS); + this.grabPointSphereOn(); + + var candidateEntities = Entities.findEntities(this.getControllerLocation(true).position, MAX_EQUIP_HOTSPOT_RADIUS); entityPropertiesCache.addEntities(candidateEntities); var potentialEquipHotspot = this.chooseBestEquipHotspot(candidateEntities); if (!this.waitForTriggerRelease) { - this.updateEquipHaptics(potentialEquipHotspot, this.getHandPosition()); + this.updateEquipHaptics(potentialEquipHotspot, this.getControllerLocation(true).position); } var nearEquipHotspots = this.chooseNearEquipHotspots(candidateEntities, EQUIP_HOTSPOT_RENDER_RADIUS); @@ -1060,23 +1126,27 @@ function MyController(hand) { !potentialEquipHotspot && this.prevPotentialEquipHotspot) { Controller.triggerHapticPulse(HAPTIC_TEXTURE_STRENGTH, HAPTIC_TEXTURE_DURATION, this.hand); this.lastHapticPulseLocation = currentLocation; - } else if (potentialEquipHotspot && Vec3.distance(this.lastHapticPulseLocation, currentLocation) > HAPTIC_TEXTURE_DISTANCE) { + } else if (potentialEquipHotspot && + Vec3.distance(this.lastHapticPulseLocation, currentLocation) > HAPTIC_TEXTURE_DISTANCE) { Controller.triggerHapticPulse(HAPTIC_TEXTURE_STRENGTH, HAPTIC_TEXTURE_DURATION, this.hand); this.lastHapticPulseLocation = currentLocation; } this.prevPotentialEquipHotspot = potentialEquipHotspot; }; + this.heartBeatIsStale = function(data) { + var now = Date.now(); + return data.heartBeat === undefined || now - data.heartBeat > HEART_BEAT_TIMEOUT; + }; + // Performs ray pick test from the hand controller into the world // @param {number} which hand to use, RIGHT_HAND or LEFT_HAND // @returns {object} returns object with two keys entityID and distance // this.calcRayPickInfo = function(hand) { - - var standardControllerValue = (hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; - var pose = Controller.getPoseValue(standardControllerValue); - var worldHandPosition = Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, pose.translation), MyAvatar.position); - var worldHandRotation = Quat.multiply(MyAvatar.orientation, pose.rotation); + var controllerLocation = this.getControllerLocation(true); + var worldHandPosition = controllerLocation.position; + var worldHandRotation = controllerLocation.orientation; var pickRay = { origin: PICK_WITH_HAND_RAY ? worldHandPosition : Camera.position, @@ -1196,7 +1266,7 @@ function MyController(hand) { var okToEquipFromOtherHand = ((this.getOtherHandController().state == STATE_NEAR_GRABBING || this.getOtherHandController().state == STATE_DISTANCE_HOLDING) && this.getOtherHandController().grabbedEntity == hotspot.entityID); - if (refCount > 0 && !okToEquipFromOtherHand) { + if (refCount > 0 && !this.heartBeatIsStale(grabProps) && !okToEquipFromOtherHand) { if (debug) { print("equip is skipping '" + props.name + "': grabbed by someone else"); } @@ -1213,20 +1283,21 @@ function MyController(hand) { var physical = propsArePhysical(props); var grabbable = false; var debug = (WANT_DEBUG_SEARCH_NAME && props.name === WANT_DEBUG_SEARCH_NAME); + var refCount = ("refCount" in grabProps) ? grabProps.refCount : 0; if (physical) { // physical things default to grabbable grabbable = true; } else { // non-physical things default to non-grabbable unless they are already grabbed - if ("refCount" in grabProps && grabProps.refCount > 0) { + if (refCount > 0) { grabbable = true; } else { grabbable = false; } } - if (grabbableProps.hasOwnProperty("grabbable")) { + if (grabbableProps.hasOwnProperty("grabbable") && refCount === 0) { grabbable = grabbableProps.grabbable; } @@ -1321,7 +1392,7 @@ function MyController(hand) { return _this.collectEquipHotspots(entityID); })).filter(function(hotspot) { return (_this.hotspotIsEquippable(hotspot) && - Vec3.distance(hotspot.worldPosition, _this.getHandPosition()) < hotspot.radius + distance); + Vec3.distance(hotspot.worldPosition, _this.getControllerLocation(true).position) < hotspot.radius + distance); }); return equippableHotspots; }; @@ -1332,8 +1403,8 @@ function MyController(hand) { if (equippableHotspots.length > 0) { // sort by distance equippableHotspots.sort(function(a, b) { - var aDistance = Vec3.distance(a.worldPosition, this.getHandPosition()); - var bDistance = Vec3.distance(b.worldPosition, this.getHandPosition()); + var aDistance = Vec3.distance(a.worldPosition, this.getControllerLocation(true).position); + var bDistance = Vec3.distance(b.worldPosition, this.getControllerLocation(true).position); return aDistance - bDistance; }); return equippableHotspots[0]; @@ -1359,6 +1430,8 @@ function MyController(hand) { this.isInitialGrab = false; this.shouldResetParentOnRelease = false; + this.grabPointSphereOn(); + this.checkForStrayChildren(); if (this.triggerSmoothedReleased()) { @@ -1366,7 +1439,7 @@ function MyController(hand) { return; } - var handPosition = this.getHandPosition(); + var handPosition = this.getControllerLocation(true).position; var rayPickInfo = this.calcRayPickInfo(this.hand); @@ -1374,10 +1447,10 @@ function MyController(hand) { entityPropertiesCache.addEntity(rayPickInfo.entityID); } - var candidateEntities = Entities.findEntities(handPosition, NEAR_GRAB_RADIUS); - entityPropertiesCache.addEntities(candidateEntities); + var candidateHotSpotEntities = Entities.findEntities(handPosition, MAX_EQUIP_HOTSPOT_RADIUS); + entityPropertiesCache.addEntities(candidateHotSpotEntities); - var potentialEquipHotspot = this.chooseBestEquipHotspot(candidateEntities); + var potentialEquipHotspot = this.chooseBestEquipHotspot(candidateHotSpotEntities); if (potentialEquipHotspot) { if (this.triggerSmoothedGrab()) { this.grabbedHotspot = potentialEquipHotspot; @@ -1387,6 +1460,7 @@ function MyController(hand) { } } + var candidateEntities = Entities.findEntities(handPosition, NEAR_GRAB_RADIUS); var grabbableEntities = candidateEntities.filter(function(entity) { return _this.entityIsNearGrabbable(entity, handPosition, NEAR_GRAB_MAX_DISTANCE); }); @@ -1546,18 +1620,13 @@ function MyController(hand) { }; this.distanceHoldingEnter = function() { - Messages.sendLocalMessage('Hifi-Teleport-Disabler','both'); + Messages.sendLocalMessage('Hifi-Teleport-Disabler', 'both'); this.clearEquipHaptics(); + this.grabPointSphereOff(); - // controller pose is in avatar frame - var device = (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; - var avatarControllerPose = Controller.getPoseValue(device); + var worldControllerPosition = this.getControllerLocation(true).position; - // transform it into world frame - var worldControllerPosition = Vec3.sum(MyAvatar.position, - Vec3.multiplyQbyV(MyAvatar.orientation, avatarControllerPose.translation)); - - // also transform the position into room space + // transform the position into room space var worldToSensorMat = Mat4.inverse(MyAvatar.getSensorToWorldMatrix()); var roomControllerPosition = Mat4.transformPoint(worldToSensorMat, worldControllerPosition); @@ -1620,14 +1689,10 @@ function MyController(hand) { this.heartBeat(this.grabbedEntity); - // controller pose is in avatar frame - var device = (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; - var avatarControllerPose = Controller.getPoseValue(device); - // transform it into world frame - var worldControllerPosition = Vec3.sum(MyAvatar.position, - Vec3.multiplyQbyV(MyAvatar.orientation, avatarControllerPose.translation)); - var worldControllerRotation = Quat.multiply(MyAvatar.orientation, avatarControllerPose.rotation); + var controllerLocation = this.getControllerLocation(true); + var worldControllerPosition = controllerLocation.position; + var worldControllerRotation = controllerLocation.orientation; // also transform the position into room space var worldToSensorMat = Mat4.inverse(MyAvatar.getSensorToWorldMatrix()); @@ -1698,8 +1763,6 @@ function MyController(hand) { } } - var handPosition = this.getHandPosition(); - // visualizations var rayPickInfo = this.calcRayPickInfo(this.hand); @@ -1764,10 +1827,7 @@ function MyController(hand) { }; this.dropGestureProcess = function(deltaTime) { - var standardControllerValue = (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; - var pose = Controller.getPoseValue(standardControllerValue); - var worldHandRotation = Quat.multiply(MyAvatar.orientation, pose.rotation); - + var worldHandRotation = this.getControllerLocation(true).orientation; var localHandUpAxis = this.hand === RIGHT_HAND ? { x: 1, y: 0, @@ -1806,14 +1866,14 @@ function MyController(hand) { this.nearGrabbingEnter = function() { if (this.hand === 0) { Messages.sendLocalMessage('Hifi-Teleport-Disabler', 'left'); - } if (this.hand === 1) { Messages.sendLocalMessage('Hifi-Teleport-Disabler', 'right'); - } + this.grabPointSphereOff(); this.lineOff(); this.overlayLineOff(); + this.searchSphereOff(); this.dropGestureReset(); this.clearEquipHaptics(); @@ -1835,12 +1895,23 @@ function MyController(hand) { var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES); this.activateEntity(this.grabbedEntity, grabbedProperties, false); - // var handRotation = this.getHandRotation(); - var handRotation = (this.hand === RIGHT_HAND) ? MyAvatar.getRightPalmRotation() : MyAvatar.getLeftPalmRotation(); - var handPosition = this.getHandPosition(); - var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA); - this.ignoreIK = grabbableData.ignoreIK ? grabbableData.ignoreIK : false; + if (FORCE_IGNORE_IK) { + this.ignoreIK = true; + } else { + this.ignoreIK = grabbableData.ignoreIK ? grabbableData.ignoreIK : false; + } + + var handRotation; + var handPosition; + if (this.ignoreIK) { + var controllerLocation = this.getControllerLocation(false); + handRotation = controllerLocation.orientation; + handPosition = controllerLocation.position; + } else { + handRotation = this.getHandRotation(); + handPosition = this.getHandPosition(); + } var hasPresetPosition = false; if (this.state == STATE_HOLD && this.grabbedHotspot) { @@ -1879,12 +1950,21 @@ function MyController(hand) { } Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({ action: 'grab', - grabbedEntity: this.grabbedEntity + grabbedEntity: this.grabbedEntity, + joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand" })); } else { // grab entity via parenting this.actionID = null; - var handJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"); + var handJointIndex; + if (this.ignoreIK) { + handJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? + "_CONTROLLER_RIGHTHAND" : + "_CONTROLLER_LEFTHAND"); + } else { + handJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"); + } + var reparentProps = { parentID: MyAvatar.sessionUUID, parentJointIndex: handJointIndex, @@ -1892,14 +1972,15 @@ function MyController(hand) { angularVelocity: {x: 0, y: 0, z: 0} }; if (hasPresetPosition) { - reparentProps["localPosition"] = this.offsetPosition; - reparentProps["localRotation"] = this.offsetRotation; + reparentProps.localPosition = this.offsetPosition; + reparentProps.localRotation = this.offsetRotation; } Entities.editEntity(this.grabbedEntity, reparentProps); Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({ action: 'equip', - grabbedEntity: this.grabbedEntity + grabbedEntity: this.grabbedEntity, + joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand" })); } @@ -1937,6 +2018,8 @@ function MyController(hand) { this.nearGrabbing = function(deltaTime, timestamp) { + this.grabPointSphereOff(); + if (this.state == STATE_NEAR_GRABBING && !this.triggerClicked) { this.callEntityMethodOnGrabbed("releaseGrab"); this.setState(STATE_OFF, "trigger released"); @@ -1967,9 +2050,10 @@ function MyController(hand) { // store the offset attach points into preferences. if (USE_ATTACH_POINT_SETTINGS && this.grabbedHotspot && this.grabbedEntity) { - var props = Entities.getEntityProperties(this.grabbedEntity, ["localPosition", "localRotation"]); - if (props && props.localPosition && props.localRotation) { - storeAttachPointForHotspotInSettings(this.grabbedHotspot, this.hand, props.localPosition, props.localRotation); + var prefprops = Entities.getEntityProperties(this.grabbedEntity, ["localPosition", "localRotation"]); + if (prefprops && prefprops.localPosition && prefprops.localRotation) { + storeAttachPointForHotspotInSettings(this.grabbedHotspot, this.hand, + prefprops.localPosition, prefprops.localRotation); } } @@ -1988,22 +2072,34 @@ function MyController(hand) { "position", "rotation", "dimensions", "registrationPoint"]); if (!props.position) { - // server may have reset, taking our equipped entity with it. move back to "off" stte + // server may have reset, taking our equipped entity with it. move back to "off" state this.callEntityMethodOnGrabbed("releaseGrab"); this.setState(STATE_OFF, "entity has no position property"); return; } var now = Date.now(); - if (now - this.lastUnequipCheckTime > MSECS_PER_SEC * CHECK_TOO_FAR_UNEQUIP_TIME) { + if (this.state == STATE_HOLD && now - this.lastUnequipCheckTime > MSECS_PER_SEC * CHECK_TOO_FAR_UNEQUIP_TIME) { this.lastUnequipCheckTime = now; if (props.parentID == MyAvatar.sessionUUID) { - var handPosition = this.getHandPosition(); + var handPosition; + if (this.ignoreIK) { + handPosition = this.getControllerLocation(false).position; + } else { + handPosition = this.getHandPosition(); + } var TEAR_AWAY_DISTANCE = 0.1; var dist = distanceBetweenPointAndEntityBoundingBox(handPosition, props); if (dist > TEAR_AWAY_DISTANCE) { + this.autoUnequipCounter += 1; + } else { + this.autoUnequipCounter = 0; + } + + if (this.autoUnequipCounter > 1) { + // for whatever reason, the held/equipped entity has been pulled away. ungrab or unequip. print("handControllerGrab -- autoreleasing held or equipped item because it is far from hand." + props.parentID + ", dist = " + dist); @@ -2076,16 +2172,15 @@ function MyController(hand) { }; this.nearTriggerEnter = function() { - this.clearEquipHaptics(); - + this.grabPointSphereOff(); Controller.triggerShortHapticPulse(1.0, this.hand); this.callEntityMethodOnGrabbed("startNearTrigger"); }; this.farTriggerEnter = function() { this.clearEquipHaptics(); - + this.grabPointSphereOff(); this.callEntityMethodOnGrabbed("startFarTrigger"); }; @@ -2105,10 +2200,9 @@ function MyController(hand) { return; } - var handPosition = this.getHandPosition(); var pickRay = { - origin: handPosition, - direction: Quat.getUp(this.getHandRotation()) + origin: this.getControllerLocation().position, + direction: Quat.getUp(this.getControllerLocation().orientation) }; var now = Date.now(); @@ -2137,7 +2231,7 @@ function MyController(hand) { this.entityTouchingEnter = function() { // test for intersection between controller laser and web entity plane. - var intersectInfo = handLaserIntersectEntity(this.grabbedEntity, this.hand); + var intersectInfo = handLaserIntersectEntity(this.grabbedEntity, this.getControllerLocation(true)); if (intersectInfo) { var pointerEvent = { type: "Press", @@ -2162,7 +2256,7 @@ function MyController(hand) { this.entityTouchingExit = function() { // test for intersection between controller laser and web entity plane. - var intersectInfo = handLaserIntersectEntity(this.grabbedEntity, this.hand); + var intersectInfo = handLaserIntersectEntity(this.grabbedEntity, this.getControllerLocation(true)); if (intersectInfo) { var pointerEvent; if (this.deadspotExpired) { @@ -2200,7 +2294,7 @@ function MyController(hand) { } // test for intersection between controller laser and web entity plane. - var intersectInfo = handLaserIntersectEntity(this.grabbedEntity, this.hand); + var intersectInfo = handLaserIntersectEntity(this.grabbedEntity, this.getControllerLocation(true)); if (intersectInfo) { if (Entities.keyboardFocusEntity != this.grabbedEntity) { @@ -2237,7 +2331,7 @@ function MyController(hand) { }; this.release = function() { - Messages.sendLocalMessage('Hifi-Teleport-Disabler','none'); + Messages.sendLocalMessage('Hifi-Teleport-Disabler', 'none'); this.turnOffVisualizations(); var noVelocity = false; @@ -2249,16 +2343,7 @@ function MyController(hand) { // If this looks like the release after adjusting something still held in the other hand, print the position // and rotation of the held thing to help content creators set the userData. var grabData = getEntityCustomData(GRAB_USER_DATA_KEY, this.grabbedEntity, {}); - if (grabData.refCount > 1) { - var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, ["localPosition", "localRotation"]); - if (grabbedProperties && grabbedProperties.localPosition && grabbedProperties.localRotation) { - print((this.hand === RIGHT_HAND ? '"LeftHand"' : '"RightHand"') + ":" + - '[{"x":' + grabbedProperties.localPosition.x + ', "y":' + grabbedProperties.localPosition.y + - ', "z":' + grabbedProperties.localPosition.z + '}, {"x":' + grabbedProperties.localRotation.x + - ', "y":' + grabbedProperties.localRotation.y + ', "z":' + grabbedProperties.localRotation.z + - ', "w":' + grabbedProperties.localRotation.w + '}]'); - } - } + this.printNewOffsets = (grabData.refCount > 1); if (this.actionID !== null) { Entities.deleteAction(this.grabbedEntity, this.actionID); @@ -2272,17 +2357,17 @@ function MyController(hand) { noVelocity = true; } } + + this.deactivateEntity(this.grabbedEntity, noVelocity); + + Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({ + action: 'release', + grabbedEntity: this.grabbedEntity, + joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand" + })); } - this.deactivateEntity(this.grabbedEntity, noVelocity); this.actionID = null; - - Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({ - action: 'release', - grabbedEntity: this.grabbedEntity, - joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand" - })); - this.grabbedEntity = null; this.grabbedHotspot = null; @@ -2293,13 +2378,14 @@ function MyController(hand) { this.cleanup = function() { this.release(); + this.grabPointSphereOff(); }; this.heartBeat = function(entityID) { var now = Date.now(); if (now - this.lastHeartBeat > HEART_BEAT_INTERVAL) { var data = getEntityCustomData(GRAB_USER_DATA_KEY, entityID, {}); - data["heartBeat"] = now; + data.heartBeat = now; setEntityCustomData(GRAB_USER_DATA_KEY, entityID, data); this.lastHeartBeat = now; } @@ -2308,12 +2394,14 @@ function MyController(hand) { this.resetAbandonedGrab = function(entityID) { print("cleaning up abandoned grab on " + entityID); var data = getEntityCustomData(GRAB_USER_DATA_KEY, entityID, {}); - data["refCount"] = 1; + data.refCount = 1; setEntityCustomData(GRAB_USER_DATA_KEY, entityID, data); this.deactivateEntity(entityID, false); }; this.activateEntity = function(entityID, grabbedProperties, wasLoaded) { + this.autoUnequipCounter = 0; + if (this.entityActivated) { return; } @@ -2327,29 +2415,29 @@ function MyController(hand) { // get re-instated after all the grabs have been released) be correct. Script.clearTimeout(delayedDeactivateTimeout); delayedDeactivateTimeout = null; - grabbedProperties["collidesWith"] = delayedDeactivateFunc(); + grabbedProperties.collidesWith = delayedDeactivateFunc(); } var data = getEntityCustomData(GRAB_USER_DATA_KEY, entityID, {}); var now = Date.now(); if (wasLoaded) { - data["refCount"] = 1; + data.refCount = 1; } else { - data["refCount"] = data["refCount"] ? data["refCount"] + 1 : 1; + data.refCount = data.refCount ? data.refCount + 1 : 1; // zero gravity and set ignoreForCollisions in a way that lets us put them back, after all grabs are done - if (data["refCount"] == 1) { - data["heartBeat"] = now; + if (data.refCount == 1) { + data.heartBeat = now; this.lastHeartBeat = now; this.isInitialGrab = true; - data["gravity"] = grabbedProperties.gravity; - data["collidesWith"] = grabbedProperties.collidesWith; - data["collisionless"] = grabbedProperties.collisionless; - data["dynamic"] = grabbedProperties.dynamic; - data["parentID"] = wasLoaded ? NULL_UUID : grabbedProperties.parentID; - data["parentJointIndex"] = grabbedProperties.parentJointIndex; + data.gravity = grabbedProperties.gravity; + data.collidesWith = grabbedProperties.collidesWith; + data.collisionless = grabbedProperties.collisionless; + data.dynamic = grabbedProperties.dynamic; + data.parentID = wasLoaded ? NULL_UUID : grabbedProperties.parentID; + data.parentJointIndex = grabbedProperties.parentJointIndex; var whileHeldProperties = { gravity: { @@ -2363,9 +2451,8 @@ function MyController(hand) { "collidesWith": COLLIDES_WITH_WHILE_GRABBED }; Entities.editEntity(entityID, whileHeldProperties); - } else if (data["refCount"] > 1) { - if (data["heartBeat"] === undefined || - now - data["heartBeat"] > HEART_BEAT_TIMEOUT) { + } else if (data.refCount > 1) { + if (this.heartBeatIsStale(data)) { // this entity has userData suggesting it is grabbed, but nobody is updating the hearbeat. // deactivate it before grabbing. this.resetAbandonedGrab(entityID); @@ -2380,7 +2467,8 @@ function MyController(hand) { // bootstrap themselves with the held object. This happens because the meaning of "otherAvatar" in // the collision mask hinges on who the physics simulation owner is. Entities.editEntity(entityID, { - "collidesWith": COLLIDES_WITH_WHILE_MULTI_GRABBED + // "collidesWith": removeAvatarsFromCollidesWith(grabbedProperties.collidesWith) + collisionless: true }); } } @@ -2393,6 +2481,10 @@ function MyController(hand) { // unhook them. var handJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"); var children = Entities.getChildrenIDsOfJoint(MyAvatar.sessionUUID, handJointIndex); + var controllerJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? + "_CONTROLLER_RIGHTHAND" : + "_CONTROLLER_LEFTHAND"); + children.concat(Entities.getChildrenIDsOfJoint(MyAvatar.sessionUUID, controllerJointIndex)); children.forEach(function(childID) { print("disconnecting stray child of hand: (" + _this.hand + ") " + childID); Entities.editEntity(childID, { @@ -2411,9 +2503,9 @@ function MyController(hand) { collidesWith: collidesWith }); var data = getEntityCustomData(GRAB_USER_DATA_KEY, entityID, {}); - if (data && data["refCount"]) { - data["refCount"] = data["refCount"] - 1; - if (data["refCount"] < 1) { + if (data && data.refCount) { + data.refCount = data.refCount - 1; + if (data.refCount < 1) { data = null; } } else { @@ -2433,24 +2525,24 @@ function MyController(hand) { var data = getEntityCustomData(GRAB_USER_DATA_KEY, entityID, {}); var doDelayedDeactivate = false; - if (data && data["refCount"]) { - data["refCount"] = data["refCount"] - 1; - if (data["refCount"] < 1) { + if (data && data.refCount) { + data.refCount = data.refCount - 1; + if (data.refCount < 1) { deactiveProps = { - gravity: data["gravity"], + gravity: data.gravity, // don't set collidesWith myAvatar back right away, because thrown things tend to bounce off the // avatar's capsule. - collidesWith: removeMyAvatarFromCollidesWith(data["collidesWith"]), - collisionless: data["collisionless"], - dynamic: data["dynamic"], - parentID: data["parentID"], - parentJointIndex: data["parentJointIndex"] + collidesWith: removeMyAvatarFromCollidesWith(data.collidesWith), + collisionless: data.collisionless, + dynamic: data.dynamic, + parentID: data.parentID, + parentJointIndex: data.parentJointIndex }; - doDelayedDeactivate = (data["collidesWith"].indexOf("myAvatar") >= 0); + doDelayedDeactivate = (data.collidesWith.indexOf("myAvatar") >= 0); if (doDelayedDeactivate) { - var delayedCollidesWith = data["collidesWith"]; + var delayedCollidesWith = data.collidesWith; var delayedEntityID = entityID; delayedDeactivateFunc = function() { // set collidesWith back to original value a bit later than the rest @@ -2470,19 +2562,19 @@ function MyController(hand) { if (!noVelocity && parentID == MyAvatar.sessionUUID && - Vec3.length(data["gravity"]) > 0.0 && - data["dynamic"] && - data["parentID"] == NULL_UUID && - !data["collisionless"]) { - deactiveProps["velocity"] = this.currentVelocity; + Vec3.length(data.gravity) > 0.0 && + data.dynamic && + data.parentID == NULL_UUID && + !data.collisionless) { + deactiveProps.velocity = this.currentVelocity; } if (noVelocity) { - deactiveProps["velocity"] = { + deactiveProps.velocity = { x: 0.0, y: 0.0, z: 0.0 }; - deactiveProps["angularVelocity"] = { + deactiveProps.angularVelocity = { x: 0.0, y: 0.0, z: 0.0 @@ -2508,6 +2600,17 @@ function MyController(hand) { } }; Entities.editEntity(entityID, deactiveProps); + + if (this.printNewOffsets) { + var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, ["localPosition", "localRotation"]); + if (grabbedProperties && grabbedProperties.localPosition && grabbedProperties.localRotation) { + print((this.hand === RIGHT_HAND ? '"LeftHand"' : '"RightHand"') + ":" + + '[{"x":' + grabbedProperties.localPosition.x + ', "y":' + grabbedProperties.localPosition.y + + ', "z":' + grabbedProperties.localPosition.z + '}, {"x":' + grabbedProperties.localRotation.x + + ', "y":' + grabbedProperties.localRotation.y + ', "z":' + grabbedProperties.localRotation.z + + ', "w":' + grabbedProperties.localRotation.w + '}]'); + } + } } else if (noVelocity) { Entities.editEntity(entityID, { velocity: { @@ -2520,7 +2623,7 @@ function MyController(hand) { y: 0.0, z: 0.0 }, - dynamic: data["dynamic"] + dynamic: data.dynamic }); } } else { @@ -2567,9 +2670,13 @@ function update(deltaTime) { if (handToDisable !== LEFT_HAND && handToDisable !== 'both') { leftController.update(deltaTime, timestamp); + } else { + leftController.release(); } if (handToDisable !== RIGHT_HAND && handToDisable !== 'both') { rightController.update(deltaTime, timestamp); + } else { + rightController.release(); } equipHotspotBuddy.update(deltaTime, timestamp); entityPropertiesCache.update(); @@ -2620,7 +2727,7 @@ var handleHandMessages = function(channel, message, sender) { selectedController.nearGrabbingEnter(); } catch (e) { - print("WARNING: error parsing Hifi-Hand-Grab message"); + print("WARNING: handControllerGrab.js -- error parsing Hifi-Hand-Grab message: " + message); } } else if (channel === 'Hifi-Hand-RayPick-Blacklist') { @@ -2640,7 +2747,7 @@ var handleHandMessages = function(channel, message, sender) { } } catch (e) { - print("WARNING: error parsing Hifi-Hand-RayPick-Blacklist message"); + print("WARNING: handControllerGrab.js -- error parsing Hifi-Hand-RayPick-Blacklist message: " + message); } } } diff --git a/scripts/system/controllers/teleport.js b/scripts/system/controllers/teleport.js index f3e94d23de..b4a8eefcd2 100644 --- a/scripts/system/controllers/teleport.js +++ b/scripts/system/controllers/teleport.js @@ -94,7 +94,6 @@ function Teleporter() { this.initialize = function() { this.createMappings(); - this.disableGrab(); }; this.createMappings = function() { @@ -218,10 +217,6 @@ function Teleporter() { this.updateConnected = null; this.inCoolIn = false; inTeleportMode = false; - - Script.setTimeout(function() { - _this.enableGrab(); - }, 200); }; this.update = function() { @@ -494,14 +489,6 @@ function Teleporter() { }); }; - this.disableGrab = function() { - Messages.sendLocalMessage('Hifi-Hand-Disabler', this.teleportHand); - }; - - this.enableGrab = function() { - Messages.sendLocalMessage('Hifi-Hand-Disabler', 'none'); - }; - this.triggerHaptics = function() { var hand = this.teleportHand === 'left' ? 0 : 1; var haptic = Controller.triggerShortHapticPulse(0.2, hand); diff --git a/scripts/system/directory.js b/scripts/system/directory.js index 2eae4fa9cf..ac65615a68 100644 --- a/scripts/system/directory.js +++ b/scripts/system/directory.js @@ -73,8 +73,7 @@ var toolBar = (function() { width: toolWidth, height: toolHeight, alpha: 0.9, - visible: true, - showButtonDown: true + visible: true }); toolBar.showTool(browseDirectoryButton, true); diff --git a/scripts/system/libraries/toolBars.js b/scripts/system/libraries/toolBars.js index 5a84bf9027..e49f8c4004 100644 --- a/scripts/system/libraries/toolBars.js +++ b/scripts/system/libraries/toolBars.js @@ -130,8 +130,6 @@ Tool = function(properties, selectable, selected) { // selectable and selected a if (update) { if (selectable) { this.toggle(); - } else if (properties.showButtonDown) { - this.buttonDown(true); } } return true; diff --git a/tests/gpu-test/src/TestFbx.cpp b/tests/gpu-test/src/TestFbx.cpp index cea356e125..538bb0a973 100644 --- a/tests/gpu-test/src/TestFbx.cpp +++ b/tests/gpu-test/src/TestFbx.cpp @@ -56,6 +56,7 @@ public: explicit FileDownloader(QUrl imageUrl, QObject *parent = 0) : QObject(parent) { connect(&m_WebCtrl, SIGNAL(finished(QNetworkReply*)), this, SLOT(fileDownloaded(QNetworkReply*))); QNetworkRequest request(imageUrl); + request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); m_WebCtrl.get(request); }