diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index 23bee2c91a..4156619bb2 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -88,6 +88,10 @@ void Agent::playAvatarSound(SharedSoundPointer sound) { QMetaObject::invokeMethod(this, "playAvatarSound", Q_ARG(SharedSoundPointer, sound)); return; } else { + // TODO: seems to add occasional artifact in tests. I believe it is + // correct to do this, but need to figure out for sure, so commenting this + // out until I verify. + // _numAvatarSoundSentBytes = 0; setAvatarSound(sound); } } @@ -404,8 +408,37 @@ QUuid Agent::getSessionUUID() const { return DependencyManager::get()->getSessionUUID(); } +void Agent::setIsListeningToAudioStream(bool isListeningToAudioStream) { + // this must happen on Agent's main thread + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "setIsListeningToAudioStream", Q_ARG(bool, isListeningToAudioStream)); + return; + } + if (_isListeningToAudioStream) { + // have to tell just the audio mixer to KillAvatar. + + auto nodeList = DependencyManager::get(); + nodeList->eachMatchingNode( + [&](const SharedNodePointer& node)->bool { + return (node->getType() == NodeType::AudioMixer) && node->getActiveSocket(); + }, + [&](const SharedNodePointer& node) { + qDebug() << "sending KillAvatar message to Audio Mixers"; + auto packet = NLPacket::create(PacketType::KillAvatar, NUM_BYTES_RFC4122_UUID, true); + packet->write(getSessionUUID().toRfc4122()); + nodeList->sendPacket(std::move(packet), *node); + }); + + } + _isListeningToAudioStream = isListeningToAudioStream; +} void Agent::setIsAvatar(bool isAvatar) { + // this must happen on Agent's main thread + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "setIsAvatar", Q_ARG(bool, isAvatar)); + return; + } _isAvatar = isAvatar; if (_isAvatar && !_avatarIdentityTimer) { @@ -435,14 +468,16 @@ void Agent::setIsAvatar(bool isAvatar) { // when we stop sending identity, but then get woken up again by the mixer itself, which sends // identity packets to everyone. Here we explicitly tell the mixer to kill the entry for us. auto nodeList = DependencyManager::get(); - auto packetList = NLPacketList::create(PacketType::KillAvatar, QByteArray(), true, true); - packetList->write(getSessionUUID().toRfc4122()); nodeList->eachMatchingNode( [&](const SharedNodePointer& node)->bool { - return node->getType() == NodeType::AvatarMixer && node->getActiveSocket(); + return (node->getType() == NodeType::AvatarMixer || node->getType() == NodeType::AudioMixer) + && node->getActiveSocket(); }, [&](const SharedNodePointer& node) { - nodeList->sendPacketList(std::move(packetList), *node); + qDebug() << "sending KillAvatar message to Avatar and Audio Mixers"; + auto packet = NLPacket::create(PacketType::KillAvatar, NUM_BYTES_RFC4122_UUID, true); + packet->write(getSessionUUID().toRfc4122()); + nodeList->sendPacket(std::move(packet), *node); }); } emit stopAvatarAudioTimer(); diff --git a/assignment-client/src/Agent.h b/assignment-client/src/Agent.h index 939b51625a..c9b1707101 100644 --- a/assignment-client/src/Agent.h +++ b/assignment-client/src/Agent.h @@ -49,7 +49,7 @@ public: bool isPlayingAvatarSound() const { return _avatarSound != NULL; } bool isListeningToAudioStream() const { return _isListeningToAudioStream; } - void setIsListeningToAudioStream(bool isListeningToAudioStream) { _isListeningToAudioStream = isListeningToAudioStream; } + void setIsListeningToAudioStream(bool isListeningToAudioStream); float getLastReceivedAudioLoudness() const { return _lastReceivedAudioLoudness; } QUuid getSessionUUID() const; diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index bc356b8ce1..d785579c38 100644 --- a/assignment-client/src/audio/AudioMixer.cpp +++ b/assignment-client/src/audio/AudioMixer.cpp @@ -93,6 +93,7 @@ AudioMixer::AudioMixer(ReceivedMessage& message) : packetReceiver.registerListener(PacketType::NegotiateAudioFormat, this, "handleNegotiateAudioFormat"); packetReceiver.registerListener(PacketType::MuteEnvironment, this, "handleMuteEnvironmentPacket"); packetReceiver.registerListener(PacketType::NodeIgnoreRequest, this, "handleNodeIgnoreRequestPacket"); + packetReceiver.registerListener(PacketType::KillAvatar, this, "handleKillAvatarPacket"); connect(nodeList.data(), &NodeList::nodeKilled, this, &AudioMixer::handleNodeKilled); } @@ -590,14 +591,29 @@ void AudioMixer::handleNodeKilled(SharedNodePointer killedNode) { // enumerate the connected listeners to remove HRTF objects for the disconnected node auto nodeList = DependencyManager::get(); - nodeList->eachNode([](const SharedNodePointer& node) { + nodeList->eachNode([&killedNode](const SharedNodePointer& node) { auto clientData = dynamic_cast(node->getLinkedData()); if (clientData) { - clientData->removeHRTFsForNode(node->getUUID()); + clientData->removeHRTFsForNode(killedNode->getUUID()); } }); } +void AudioMixer::handleKillAvatarPacket(QSharedPointer packet, SharedNodePointer sendingNode) { + auto clientData = dynamic_cast(sendingNode->getLinkedData()); + if (clientData) { + clientData->removeAgentAvatarAudioStream(); + auto nodeList = DependencyManager::get(); + nodeList->eachNode([sendingNode](const SharedNodePointer& node){ + auto listenerClientData = dynamic_cast(node->getLinkedData()); + if (listenerClientData) { + listenerClientData->removeHRTFForStream(sendingNode->getUUID()); + } + }); + } +} + + void AudioMixer::handleNodeIgnoreRequestPacket(QSharedPointer packet, SharedNodePointer sendingNode) { sendingNode->parseIgnoreRequestMessage(packet); } diff --git a/assignment-client/src/audio/AudioMixer.h b/assignment-client/src/audio/AudioMixer.h index 764c54c6cb..3c68e4c6af 100644 --- a/assignment-client/src/audio/AudioMixer.h +++ b/assignment-client/src/audio/AudioMixer.h @@ -48,6 +48,7 @@ private slots: void handleNegotiateAudioFormat(QSharedPointer message, SharedNodePointer sendingNode); void handleNodeKilled(SharedNodePointer killedNode); void handleNodeIgnoreRequestPacket(QSharedPointer packet, SharedNodePointer sendingNode); + void handleKillAvatarPacket(QSharedPointer packet, SharedNodePointer sendingNode); void removeHRTFsForFinishedInjector(const QUuid& streamID); diff --git a/assignment-client/src/audio/AudioMixerClientData.cpp b/assignment-client/src/audio/AudioMixerClientData.cpp index 58d89697af..5b8c4aa105 100644 --- a/assignment-client/src/audio/AudioMixerClientData.cpp +++ b/assignment-client/src/audio/AudioMixerClientData.cpp @@ -73,11 +73,19 @@ void AudioMixerClientData::removeHRTFForStream(const QUuid& nodeID, const QUuid& } } +void AudioMixerClientData::removeAgentAvatarAudioStream() { + QWriteLocker writeLocker { &_streamsLock }; + auto it = _audioStreams.find(QUuid()); + if (it != _audioStreams.end()) { + _audioStreams.erase(it); + } + writeLocker.unlock(); +} + int AudioMixerClientData::parseData(ReceivedMessage& message) { PacketType packetType = message.getType(); if (packetType == PacketType::AudioStreamStats) { - // skip over header, appendFlag, and num stats packed message.seek(sizeof(quint8) + sizeof(quint16)); diff --git a/assignment-client/src/audio/AudioMixerClientData.h b/assignment-client/src/audio/AudioMixerClientData.h index 34263f9cbe..52c659c240 100644 --- a/assignment-client/src/audio/AudioMixerClientData.h +++ b/assignment-client/src/audio/AudioMixerClientData.h @@ -50,6 +50,8 @@ public: // removes an AudioHRTF object for a given stream void removeHRTFForStream(const QUuid& nodeID, const QUuid& streamID = QUuid()); + void removeAgentAvatarAudioStream(); + int parseData(ReceivedMessage& message) override; // attempt to pop a frame from each audio stream, and return the number of streams from this client diff --git a/cmake/templates/NSIS.template.in b/cmake/templates/NSIS.template.in index 568418afe1..d65612351c 100644 --- a/cmake/templates/NSIS.template.in +++ b/cmake/templates/NSIS.template.in @@ -571,7 +571,9 @@ Function HandlePostInstallOptions ; both launches use the explorer trick in case the user has elevated permissions for the installer ; it won't be possible to use this approach if either application should be launched with a command line param ${If} ${SectionIsSelected} ${@SERVER_COMPONENT_NAME@} - Exec '"$WINDIR\explorer.exe" "$INSTDIR\@CONSOLE_INSTALL_SUBDIR@\@CONSOLE_WIN_EXEC_NAME@"' + ; create shortcut with ARGUMENTS + CreateShortCut "$TEMP\SandboxShortcut.lnk" "$INSTDIR\@CONSOLE_INSTALL_SUBDIR@\@CONSOLE_WIN_EXEC_NAME@" "-- --launchInterface" + Exec '"$WINDIR\explorer.exe" "$TEMP\SandboxShortcut.lnk"' ${Else} Exec '"$WINDIR\explorer.exe" "$INSTDIR\@INTERFACE_WIN_EXEC_NAME@"' ${EndIf} diff --git a/cmake/templates/VersionInfo.rc.in b/cmake/templates/VersionInfo.rc.in new file mode 100644 index 0000000000..ad192ed87d --- /dev/null +++ b/cmake/templates/VersionInfo.rc.in @@ -0,0 +1,22 @@ +// Language and character set information as described at +// https://msdn.microsoft.com/en-us/library/windows/desktop/aa381049(v=vs.85).aspx +#define US_ENGLISH_UNICODE "040904B0" + +// More information about the format of this file can be found at +// https://msdn.microsoft.com/en-us/library/windows/desktop/aa381058(v=vs.85).aspx +1 VERSIONINFO +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK US_ENGLISH_UNICODE + BEGIN + VALUE "CompanyName", "@BUILD_ORGANIZATION@" + VALUE "FileDescription", "@APP_FULL_NAME@" + VALUE "FileVersion", "@BUILD_VERSION@" + VALUE "InternalName", "@TARGET_NAME@" + VALUE "OriginalFilename", "@TARGET_NAME@.exe" + VALUE "ProductName", "@APP_FULL_NAME@" + VALUE "ProductVersion", "@BUILD_VERSION@" + END + END +END diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index bf9d7d04a6..abe7ed176a 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -237,6 +237,7 @@ void DomainGatekeeper::updateNodePermissions() { userPerms.permissions |= NodePermissions::Permission::canAdjustLocks; userPerms.permissions |= NodePermissions::Permission::canRezPermanentEntities; userPerms.permissions |= NodePermissions::Permission::canRezTemporaryEntities; + userPerms.permissions |= NodePermissions::Permission::canWriteToAssetServer; } else { // this node is an agent const QHostAddress& addr = node->getLocalSocket().getAddress(); @@ -312,6 +313,7 @@ SharedNodePointer DomainGatekeeper::processAssignmentConnectRequest(const NodeCo userPerms.permissions |= NodePermissions::Permission::canAdjustLocks; userPerms.permissions |= NodePermissions::Permission::canRezPermanentEntities; userPerms.permissions |= NodePermissions::Permission::canRezTemporaryEntities; + userPerms.permissions |= NodePermissions::Permission::canWriteToAssetServer; newNode->setPermissions(userPerms); return newNode; } diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index b43376c374..131c4ee509 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -133,8 +133,12 @@ elseif (WIN32) set(CONFIGURE_ICON_RC_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/Icon.rc") configure_file("${HF_CMAKE_DIR}/templates/Icon.rc.in" ${CONFIGURE_ICON_RC_OUTPUT}) + set(APP_FULL_NAME "High Fidelity Interface") + set(CONFIGURE_VERSION_INFO_RC_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/VersionInfo.rc") + configure_file("${HF_CMAKE_DIR}/templates/VersionInfo.rc.in" ${CONFIGURE_VERSION_INFO_RC_OUTPUT}) + # add an executable that also has the icon itself and the configured rc file as resources - add_executable(${TARGET_NAME} WIN32 ${INTERFACE_SRCS} ${QM} ${CONFIGURE_ICON_RC_OUTPUT}) + add_executable(${TARGET_NAME} WIN32 ${INTERFACE_SRCS} ${QM} ${CONFIGURE_ICON_RC_OUTPUT} ${CONFIGURE_VERSION_INFO_RC_OUTPUT}) if (NOT DEV_BUILD) add_custom_command( diff --git a/interface/resources/html/help.html b/interface/resources/html/help.html index 6cc4dab6af..422f5c6b46 100644 --- a/interface/resources/html/help.html +++ b/interface/resources/html/help.html @@ -50,21 +50,33 @@ function showKbm() { document.getElementById("main_image").setAttribute("src", "img/controls-help-keyboard.png"); } - function showHandControllers() { + function showViveControllers() { document.getElementById("main_image").setAttribute("src", "img/controls-help-vive.png"); } - function showGameController() { + function showXboxController() { document.getElementById("main_image").setAttribute("src", "img/controls-help-gamepad.png"); } + function load() { + console.log("In help.html: ", window.location.href); + parts = window.location.href.split("?"); + if (parts.length > 0) { + var defaultTab = parts[1]; + if (defaultTab == "xbox") { + showXboxController(); + } else if (defaultTab == "vive") { + showViveControllers(); + } + } + } - +
- - + +
diff --git a/interface/resources/qml/UpdateDialog.qml b/interface/resources/qml/UpdateDialog.qml index ca3a2da577..5e05601ce4 100644 --- a/interface/resources/qml/UpdateDialog.qml +++ b/interface/resources/qml/UpdateDialog.qml @@ -20,10 +20,10 @@ ScrollingWindow { anchors.centerIn: parent UpdateDialog { id: updateDialog - + implicitWidth: backgroundRectangle.width implicitHeight: backgroundRectangle.height - + readonly property int contentWidth: 500 readonly property int logoSize: 60 readonly property int borderWidth: 30 @@ -36,7 +36,7 @@ ScrollingWindow { signal triggerBuildDownload signal closeUpdateDialog - + Rectangle { id: backgroundRectangle color: "#ffffff" @@ -47,7 +47,7 @@ ScrollingWindow { Image { id: logo - source: "../images/interface-logo.svg" + source: "../images/hifi-logo.svg" width: updateDialog.logoSize height: updateDialog.logoSize anchors { @@ -65,7 +65,7 @@ ScrollingWindow { topMargin: updateDialog.borderWidth top: parent.top } - + Rectangle { id: header width: parent.width - updateDialog.logoSize - updateDialog.inputSpacing diff --git a/interface/resources/qml/desktop/Desktop.qml b/interface/resources/qml/desktop/Desktop.qml index b207087be0..66b59f0aea 100644 --- a/interface/resources/qml/desktop/Desktop.qml +++ b/interface/resources/qml/desktop/Desktop.qml @@ -312,6 +312,7 @@ FocusScope { onPinnedChanged: { if (pinned) { + d.raiseWindow(desktop); desktop.focus = true; desktop.forceActiveFocus(); diff --git a/interface/resources/qml/hifi/Card.qml b/interface/resources/qml/hifi/Card.qml index 8c1b78af79..70eab82910 100644 --- a/interface/resources/qml/hifi/Card.qml +++ b/interface/resources/qml/hifi/Card.qml @@ -113,9 +113,8 @@ Rectangle { } FiraSansRegular { id: users; - visible: action === 'concurrency'; - text: onlineUsers; - size: textSize; + text: (action === 'concurrency') ? onlineUsers : 'snapshot'; + size: (action === 'concurrency') ? textSize : textSizeSmall; color: hifi.colors.white; anchors { verticalCenter: usersImage.verticalCenter; diff --git a/interface/resources/qml/hifi/Desktop.qml b/interface/resources/qml/hifi/Desktop.qml index 7f1fbcb174..db0c1ba724 100644 --- a/interface/resources/qml/hifi/Desktop.qml +++ b/interface/resources/qml/hifi/Desktop.qml @@ -51,27 +51,41 @@ OriginalDesktop.Desktop { Toolbar { id: sysToolbar; objectName: "com.highfidelity.interface.toolbar.system"; - // These values will be overridden by sysToolbar.x/y if there is a saved position in Settings - // On exit, the sysToolbar position is saved to settings - x: 30 + anchors.horizontalCenter: settings.constrainToolbarToCenterX ? desktop.horizontalCenter : undefined; + // Literal 50 is overwritten by settings from previous session, and sysToolbar.x comes from settings when not constrained. + x: sysToolbar.x y: 50 } + Settings { + id: settings; + category: "toolbar"; + property bool constrainToolbarToCenterX: true; + } + function setConstrainToolbarToCenterX(constrain) { // Learn about c++ preference change. + settings.constrainToolbarToCenterX = constrain; + } property var toolbars: (function (map) { // answer dictionary preloaded with sysToolbar map[sysToolbar.objectName] = sysToolbar; return map; })({}); + Component.onCompleted: { WebEngine.settings.javascriptCanOpenWindows = true; WebEngine.settings.javascriptCanAccessClipboard = false; WebEngine.settings.spatialNavigationEnabled = false; WebEngine.settings.localContentCanAccessRemoteUrls = true; - var toggleHudButton = sysToolbar.addButton({ - objectName: "hudToggle", - imageURL: "../../../icons/hud.svg", - visible: true, - pinned: true, + [ // Allocate the standard buttons in the correct order. They will get images, etc., via scripts. + "hmdToggle", "mute", "mod", "help", + "hudToggle", + "com.highfidelity.interface.system.editButton", "marketplace", "snapshot", "goto" + ].forEach(function (name) { + sysToolbar.addButton({objectName: name}); }); + var toggleHudButton = sysToolbar.findButton("hudToggle"); + toggleHudButton.imageURL = "../../../icons/hud.svg"; + toggleHudButton.pinned = true; + sysToolbar.updatePinned(); // automatic when adding buttons only IFF button is pinned at creation. toggleHudButton.buttonState = Qt.binding(function(){ return desktop.pinned ? 1 : 0 diff --git a/interface/resources/qml/hifi/dialogs/GeneralPreferencesDialog.qml b/interface/resources/qml/hifi/dialogs/GeneralPreferencesDialog.qml index 95f55f504b..9e46d86ecd 100644 --- a/interface/resources/qml/hifi/dialogs/GeneralPreferencesDialog.qml +++ b/interface/resources/qml/hifi/dialogs/GeneralPreferencesDialog.qml @@ -17,7 +17,7 @@ PreferencesDialog { id: root objectName: "GeneralPreferencesDialog" title: "General Settings" - showCategories: ["Snapshots", "Scripts", "Privacy", "Octree", "HMD", "Sixense Controllers"] + showCategories: ["UI", "Snapshots", "Scripts", "Privacy", "Octree", "HMD", "Sixense Controllers"] property var settings: Settings { category: root.objectName property alias x: root.x diff --git a/interface/resources/qml/hifi/toolbars/Toolbar.qml b/interface/resources/qml/hifi/toolbars/Toolbar.qml index 30989be688..01ce74cf6e 100644 --- a/interface/resources/qml/hifi/toolbars/Toolbar.qml +++ b/interface/resources/qml/hifi/toolbars/Toolbar.qml @@ -114,6 +114,9 @@ Window { // and allow scripts to be idempotent so they don't duplicate buttons if they're reloaded var result = findButton(properties.objectName); if (result) { + for (var property in properties) { + result[property] = properties[property]; + } return result; } properties.toolbar = this; diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 5fe15fa8e5..867761e012 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -523,6 +523,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo _mirrorViewRect(QRect(MIRROR_VIEW_LEFT_PADDING, MIRROR_VIEW_TOP_PADDING, MIRROR_VIEW_WIDTH, MIRROR_VIEW_HEIGHT)), _previousScriptLocation("LastScriptLocation", DESKTOP_LOCATION), _fieldOfView("fieldOfView", DEFAULT_FIELD_OF_VIEW_DEGREES), + _constrainToolbarPosition("toolbar/constrainToolbarToCenterX", true), _scaleMirror(1.0f), _rotateMirror(0.0f), _raiseMirror(0.0f), @@ -2150,12 +2151,27 @@ void Application::setFieldOfView(float fov) { } } +void Application::setSettingConstrainToolbarPosition(bool setting) { + _constrainToolbarPosition.set(setting); + DependencyManager::get()->setConstrainToolbarToCenterX(setting); +} + void Application::aboutApp() { InfoView::show(INFO_WELCOME_PATH); } void Application::showHelp() { - InfoView::show(INFO_HELP_PATH); + static const QString QUERY_STRING_XBOX = "xbox"; + static const QString QUERY_STRING_VIVE = "vive"; + + QString queryString = ""; + if (PluginUtils::isViveControllerAvailable()) { + queryString = QUERY_STRING_VIVE; + } else if (PluginUtils::isXboxControllerAvailable()) { + queryString = QUERY_STRING_XBOX; + } + + InfoView::show(INFO_HELP_PATH, false, queryString); } void Application::resizeEvent(QResizeEvent* event) { diff --git a/interface/src/Application.h b/interface/src/Application.h index 79c9ae735c..4c98be9c2d 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -206,6 +206,9 @@ public: float getFieldOfView() { return _fieldOfView.get(); } void setFieldOfView(float fov); + float getSettingConstrainToolbarPosition() { return _constrainToolbarPosition.get(); } + void setSettingConstrainToolbarPosition(bool setting); + NodeToOctreeSceneStats* getOcteeSceneStats() { return &_octreeServerSceneStats; } virtual controller::ScriptingInterface* getControllerScriptingInterface() { return _controllerScriptingInterface; } @@ -506,6 +509,7 @@ private: Setting::Handle _previousScriptLocation; Setting::Handle _fieldOfView; + Setting::Handle _constrainToolbarPosition; float _scaleMirror; float _rotateMirror; diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index 8e65b2fb57..7d3261aa78 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -68,6 +68,13 @@ void setupPreferences() { preferences->addPreference(new CheckPreference(AVATAR_BASICS, "Clear overlays when moving", getter, setter)); } + // UI + { + auto getter = []()->bool { return qApp->getSettingConstrainToolbarPosition(); }; + auto setter = [](bool value) { qApp->setSettingConstrainToolbarPosition(value); }; + preferences->addPreference(new CheckPreference("UI", "Constrain Toolbar Position to Horizontal Center", getter, setter)); + } + // Snapshots static const QString SNAPSHOTS { "Snapshots" }; { diff --git a/interface/src/ui/Snapshot.cpp b/interface/src/ui/Snapshot.cpp index 3334b0301b..1bf5f5de4e 100644 --- a/interface/src/ui/Snapshot.cpp +++ b/interface/src/ui/Snapshot.cpp @@ -89,7 +89,7 @@ QTemporaryFile* Snapshot::saveTempSnapshot(QImage image) { QFile* Snapshot::savedFileForSnapshot(QImage & shot, bool isTemporary) { // adding URL to snapshot - QUrl currentURL = DependencyManager::get()->currentAddress(); + QUrl currentURL = DependencyManager::get()->currentShareableAddress(); shot.setText(URL, currentURL.toString()); QString username = DependencyManager::get()->getAccountInfo().getUsername(); @@ -146,7 +146,10 @@ QFile* Snapshot::savedFileForSnapshot(QImage & shot, bool isTemporary) { void Snapshot::uploadSnapshot(const QString& filename) { const QString SNAPSHOT_UPLOAD_URL = "/api/v1/snapshots"; - static SnapshotUploader uploader; + // Alternatively to parseSnapshotData, we could pass the inWorldLocation through the call chain. This way is less disruptive to existing code. + SnapshotMetaData* snapshotData = Snapshot::parseSnapshotData(filename); + SnapshotUploader* uploader = new SnapshotUploader(snapshotData->getURL(), filename); + delete snapshotData; QFile* file = new QFile(filename); Q_ASSERT(file->exists()); @@ -163,7 +166,7 @@ void Snapshot::uploadSnapshot(const QString& filename) { multiPart->append(imagePart); auto accountManager = DependencyManager::get(); - JSONCallbackParameters callbackParams(&uploader, "uploadSuccess", &uploader, "uploadFailure"); + JSONCallbackParameters callbackParams(uploader, "uploadSuccess", uploader, "uploadFailure"); accountManager->sendRequest(SNAPSHOT_UPLOAD_URL, AccountManagerAuth::Required, diff --git a/interface/src/ui/SnapshotUploader.cpp b/interface/src/ui/SnapshotUploader.cpp index 5bc9bb386c..c36efddc14 100644 --- a/interface/src/ui/SnapshotUploader.cpp +++ b/interface/src/ui/SnapshotUploader.cpp @@ -15,9 +15,13 @@ #include "scripting/WindowScriptingInterface.h" #include "SnapshotUploader.h" +SnapshotUploader::SnapshotUploader(QUrl inWorldLocation, QString pathname) : + _inWorldLocation(inWorldLocation), + _pathname(pathname) { +} + void SnapshotUploader::uploadSuccess(QNetworkReply& reply) { const QString STORY_UPLOAD_URL = "/api/v1/user_stories"; - static SnapshotUploader uploader; // parse the reply for the thumbnail_url QByteArray contents = reply.readAll(); @@ -28,11 +32,8 @@ void SnapshotUploader::uploadSuccess(QNetworkReply& reply) { QString thumbnailUrl = dataObject.value("thumbnail_url").toString(); QString imageUrl = dataObject.value("image_url").toString(); auto addressManager = DependencyManager::get(); - QString placeName = addressManager->getPlaceName(); - if (placeName.isEmpty()) { - placeName = addressManager->getHost(); - } - QString currentPath = addressManager->currentPath(true); + QString placeName = _inWorldLocation.authority(); // We currently only upload shareable places, in which case this is just host. + QString currentPath = _inWorldLocation.path(); // create json post data QJsonObject rootObject; @@ -48,7 +49,7 @@ void SnapshotUploader::uploadSuccess(QNetworkReply& reply) { rootObject.insert("user_story", userStoryObject); auto accountManager = DependencyManager::get(); - JSONCallbackParameters callbackParams(&uploader, "createStorySuccess", &uploader, "createStoryFailure"); + JSONCallbackParameters callbackParams(this, "createStorySuccess", this, "createStoryFailure"); accountManager->sendRequest(STORY_UPLOAD_URL, AccountManagerAuth::Required, @@ -56,20 +57,23 @@ void SnapshotUploader::uploadSuccess(QNetworkReply& reply) { callbackParams, QJsonDocument(rootObject).toJson()); - } - else { + } else { emit DependencyManager::get()->snapshotShared(contents); + delete this; } } void SnapshotUploader::uploadFailure(QNetworkReply& reply) { - emit DependencyManager::get()->snapshotShared(reply.readAll()); + emit DependencyManager::get()->snapshotShared(reply.readAll()); // maybe someday include _inWorldLocation, _filename? + delete this; } void SnapshotUploader::createStorySuccess(QNetworkReply& reply) { emit DependencyManager::get()->snapshotShared(QString()); + delete this; } void SnapshotUploader::createStoryFailure(QNetworkReply& reply) { - emit DependencyManager::get()->snapshotShared(reply.readAll()); + emit DependencyManager::get()->snapshotShared(reply.readAll()); + delete this; } \ No newline at end of file diff --git a/interface/src/ui/SnapshotUploader.h b/interface/src/ui/SnapshotUploader.h index d4a5f86431..ae6d5d55ca 100644 --- a/interface/src/ui/SnapshotUploader.h +++ b/interface/src/ui/SnapshotUploader.h @@ -14,13 +14,19 @@ #include #include +#include class SnapshotUploader : public QObject { Q_OBJECT - public slots: +public: + SnapshotUploader(QUrl inWorldLocation, QString pathname); +public slots: void uploadSuccess(QNetworkReply& reply); void uploadFailure(QNetworkReply& reply); void createStorySuccess(QNetworkReply& reply); void createStoryFailure(QNetworkReply& reply); +private: + QUrl _inWorldLocation; + QString _pathname; }; #endif // hifi_SnapshotUploader_h \ No newline at end of file diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp index 11660a332d..05632cb1e6 100644 --- a/interface/src/ui/Stats.cpp +++ b/interface/src/ui/Stats.cpp @@ -302,7 +302,7 @@ void Stats::updateStats(bool force) { STAT_UPDATE(gpuTextureVirtualMemory, (int)BYTES_TO_MB(gpu::Texture::getTextureGPUVirtualMemoryUsage())); STAT_UPDATE(gpuTextureFramebufferMemory, (int)BYTES_TO_MB(gpu::Texture::getTextureGPUFramebufferMemoryUsage())); STAT_UPDATE(gpuTextureSparseMemory, (int)BYTES_TO_MB(gpu::Texture::getTextureGPUSparseMemoryUsage())); - STAT_UPDATE(gpuSparseTextureEnabled, gpu::Texture::getEnableSparseTextures() ? 1 : 0); + STAT_UPDATE(gpuSparseTextureEnabled, qApp->getGPUContext()->getBackend()->isTextureManagementSparseEnabled() ? 1 : 0); STAT_UPDATE(gpuFreeMemory, (int)BYTES_TO_MB(gpu::Context::getFreeGPUMemory())); STAT_UPDATE(rectifiedTextureCount, (int)RECTIFIED_TEXTURE_COUNT.load()); STAT_UPDATE(decimatedTextureCount, (int)DECIMATED_TEXTURE_COUNT.load()); diff --git a/libraries/animation/src/AnimSkeleton.cpp b/libraries/animation/src/AnimSkeleton.cpp index 351c09beee..a379ebd80a 100644 --- a/libraries/animation/src/AnimSkeleton.cpp +++ b/libraries/animation/src/AnimSkeleton.cpp @@ -118,11 +118,26 @@ void AnimSkeleton::convertAbsoluteRotationsToRelative(std::vector& ro } } +void AnimSkeleton::saveNonMirroredPoses(const AnimPoseVec& poses) const { + _nonMirroredPoses.clear(); + for (int i = 0; i < (int)_nonMirroredIndices.size(); ++i) { + _nonMirroredPoses.push_back(poses[_nonMirroredIndices[i]]); + } +} + +void AnimSkeleton::restoreNonMirroredPoses(AnimPoseVec& poses) const { + for (int i = 0; i < (int)_nonMirroredIndices.size(); ++i) { + int index = _nonMirroredIndices[i]; + poses[index] = _nonMirroredPoses[i]; + } +} void AnimSkeleton::mirrorRelativePoses(AnimPoseVec& poses) const { + saveNonMirroredPoses(poses); convertRelativePosesToAbsolute(poses); mirrorAbsolutePoses(poses); convertAbsolutePosesToRelative(poses); + restoreNonMirroredPoses(poses); } void AnimSkeleton::mirrorAbsolutePoses(AnimPoseVec& poses) const { @@ -189,8 +204,14 @@ void AnimSkeleton::buildSkeletonFromJoints(const std::vector& joints) } // build mirror map. + _nonMirroredIndices.clear(); _mirrorMap.reserve(_joints.size()); for (int i = 0; i < (int)joints.size(); i++) { + if (_joints[i].name.endsWith("tEye")) { + // HACK: we don't want to mirror some joints so we remember their indices + // so we can restore them after a future mirror operation + _nonMirroredIndices.push_back(i); + } int mirrorJointIndex = -1; if (_joints[i].name.startsWith("Left")) { QString mirrorJointName = QString(_joints[i].name).replace(0, 4, "Right"); diff --git a/libraries/animation/src/AnimSkeleton.h b/libraries/animation/src/AnimSkeleton.h index 68cce11326..e1c6ae95c8 100644 --- a/libraries/animation/src/AnimSkeleton.h +++ b/libraries/animation/src/AnimSkeleton.h @@ -57,6 +57,9 @@ public: void convertAbsoluteRotationsToRelative(std::vector& rotations) const; + void saveNonMirroredPoses(const AnimPoseVec& poses) const; + void restoreNonMirroredPoses(AnimPoseVec& poses) const; + void mirrorRelativePoses(AnimPoseVec& poses) const; void mirrorAbsolutePoses(AnimPoseVec& poses) const; @@ -75,6 +78,8 @@ protected: AnimPoseVec _absoluteDefaultPoses; AnimPoseVec _relativePreRotationPoses; AnimPoseVec _relativePostRotationPoses; + mutable AnimPoseVec _nonMirroredPoses; + std::vector _nonMirroredIndices; std::vector _mirrorMap; // no copies diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 5208b893ac..062991c187 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -85,18 +85,26 @@ public: } void beforeAboutToQuit() { + Lock lock(_checkDevicesMutex); _quit = true; } void run() override { - while (!_quit) { + while (true) { + { + Lock lock(_checkDevicesMutex); + if (_quit) { + break; + } + _audioClient->checkDevices(); + } QThread::msleep(DEVICE_CHECK_INTERVAL_MSECS); - _audioClient->checkDevices(); } } private: AudioClient* _audioClient { nullptr }; + Mutex _checkDevicesMutex; bool _quit { false }; }; diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 95d28f74f3..332a88e499 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -55,7 +55,10 @@ void RenderableModelEntityItem::setModelURL(const QString& url) { auto& currentURL = getParsedModelURL(); ModelEntityItem::setModelURL(url); - if (currentURL != getParsedModelURL() || !_model) { + if (currentURL != getParsedModelURL()) { + _needsModelReload = true; + } + if (_needsModelReload || !_model) { EntityTreePointer tree = getTree(); if (tree) { QMetaObject::invokeMethod(tree.get(), "callLoader", Qt::QueuedConnection, Q_ARG(EntityItemID, getID())); @@ -523,17 +526,24 @@ bool RenderableModelEntityItem::needsToCallUpdate() const { } void RenderableModelEntityItem::update(const quint64& now) { - if (!_dimensionsInitialized && _model && _model->isActive()) { - if (_model->isLoaded()) { - EntityItemProperties properties; - properties.setLastEdited(usecTimestampNow()); // we must set the edit time since we're editing it - auto extents = _model->getMeshExtents(); - properties.setDimensions(extents.maximum - extents.minimum); - qCDebug(entitiesrenderer) << "Autoresizing:" << (!getName().isEmpty() ? getName() : getModelURL()); - QMetaObject::invokeMethod(DependencyManager::get().data(), "editEntity", - Qt::QueuedConnection, - Q_ARG(QUuid, getEntityItemID()), - Q_ARG(EntityItemProperties, properties)); + if (!_dimensionsInitialized) { + if (_model) { + if (_model->isActive() && _model->isLoaded()) { + EntityItemProperties properties; + properties.setLastEdited(usecTimestampNow()); // we must set the edit time since we're editing it + auto extents = _model->getMeshExtents(); + properties.setDimensions(extents.maximum - extents.minimum); + qCDebug(entitiesrenderer) << "Autoresizing:" << (!getName().isEmpty() ? getName() : getModelURL()); + QMetaObject::invokeMethod(DependencyManager::get().data(), "editEntity", + Qt::QueuedConnection, + Q_ARG(QUuid, getEntityItemID()), + Q_ARG(EntityItemProperties, properties)); + } + } else if (_needsModelReload) { + EntityTreePointer tree = getTree(); + if (tree) { + QMetaObject::invokeMethod(tree.get(), "callLoader", Qt::QueuedConnection, Q_ARG(EntityItemID, getID())); + } } } diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp index a2ca2a7cfe..d1dd5cce8e 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp @@ -1035,50 +1035,53 @@ void RenderablePolyVoxEntityItem::copyUpperEdgesFromNeighbors() { return; } - EntityItemPointer currentXPNeighbor = _xPNeighbor.lock(); - EntityItemPointer currentYPNeighbor = _yPNeighbor.lock(); - EntityItemPointer currentZPNeighbor = _zPNeighbor.lock(); + auto currentXPNeighbor = getXPNeighbor(); + auto currentYPNeighbor = getYPNeighbor(); + auto currentZPNeighbor = getZPNeighbor(); - if (currentXPNeighbor) { - auto polyVoxXPNeighbor = std::dynamic_pointer_cast(currentXPNeighbor); - if (polyVoxXPNeighbor->getVoxelVolumeSize() == _voxelVolumeSize) { - withWriteLock([&] { + if (currentXPNeighbor && currentXPNeighbor->getVoxelVolumeSize() == _voxelVolumeSize) { + withWriteLock([&] { + for (int y = 0; y < _volData->getHeight(); y++) { + for (int z = 0; z < _volData->getDepth(); z++) { + uint8_t neighborValue = currentXPNeighbor->getVoxel(0, y, z); + if ((y == 0 || z == 0) && _volData->getVoxelAt(_volData->getWidth() - 1, y, z) != neighborValue) { + bonkNeighbors(); + } + _volData->setVoxelAt(_volData->getWidth() - 1, y, z, neighborValue); + } + } + }); + } + + + if (currentYPNeighbor && currentYPNeighbor->getVoxelVolumeSize() == _voxelVolumeSize) { + withWriteLock([&] { + for (int x = 0; x < _volData->getWidth(); x++) { + for (int z = 0; z < _volData->getDepth(); z++) { + uint8_t neighborValue = currentYPNeighbor->getVoxel(x, 0, z); + if ((x == 0 || z == 0) && _volData->getVoxelAt(x, _volData->getHeight() - 1, z) != neighborValue) { + bonkNeighbors(); + } + _volData->setVoxelAt(x, _volData->getHeight() - 1, z, neighborValue); + } + } + }); + } + + + if (currentZPNeighbor && currentZPNeighbor->getVoxelVolumeSize() == _voxelVolumeSize) { + withWriteLock([&] { + for (int x = 0; x < _volData->getWidth(); x++) { for (int y = 0; y < _volData->getHeight(); y++) { - for (int z = 0; z < _volData->getDepth(); z++) { - uint8_t neighborValue = polyVoxXPNeighbor->getVoxel(0, y, z); - _volData->setVoxelAt(_volData->getWidth() - 1, y, z, neighborValue); + uint8_t neighborValue = currentZPNeighbor->getVoxel(x, y, 0); + _volData->setVoxelAt(x, y, _volData->getDepth() - 1, neighborValue); + if ((x == 0 || y == 0) && _volData->getVoxelAt(x, y, _volData->getDepth() - 1) != neighborValue) { + bonkNeighbors(); } + _volData->setVoxelAt(x, y, _volData->getDepth() - 1, neighborValue); } - }); - } - } - - if (currentYPNeighbor) { - auto polyVoxYPNeighbor = std::dynamic_pointer_cast(currentYPNeighbor); - if (polyVoxYPNeighbor->getVoxelVolumeSize() == _voxelVolumeSize) { - withWriteLock([&] { - for (int x = 0; x < _volData->getWidth(); x++) { - for (int z = 0; z < _volData->getDepth(); z++) { - uint8_t neighborValue = polyVoxYPNeighbor->getVoxel(x, 0, z); - _volData->setVoxelAt(x, _volData->getWidth() - 1, z, neighborValue); - } - } - }); - } - } - - if (currentZPNeighbor) { - auto polyVoxZPNeighbor = std::dynamic_pointer_cast(currentZPNeighbor); - if (polyVoxZPNeighbor->getVoxelVolumeSize() == _voxelVolumeSize) { - withWriteLock([&] { - for (int x = 0; x < _volData->getWidth(); x++) { - for (int y = 0; y < _volData->getHeight(); y++) { - uint8_t neighborValue = polyVoxZPNeighbor->getVoxel(x, y, 0); - _volData->setVoxelAt(x, y, _volData->getDepth() - 1, neighborValue); - } - } - }); - } + } + }); } } @@ -1393,25 +1396,46 @@ void RenderablePolyVoxEntityItem::setZPNeighborID(const EntityItemID& zPNeighbor } } +std::shared_ptr RenderablePolyVoxEntityItem::getXNNeighbor() { + return std::dynamic_pointer_cast(_xNNeighbor.lock()); +} + +std::shared_ptr RenderablePolyVoxEntityItem::getYNNeighbor() { + return std::dynamic_pointer_cast(_yNNeighbor.lock()); +} + +std::shared_ptr RenderablePolyVoxEntityItem::getZNNeighbor() { + return std::dynamic_pointer_cast(_zNNeighbor.lock()); +} + +std::shared_ptr RenderablePolyVoxEntityItem::getXPNeighbor() { + return std::dynamic_pointer_cast(_xPNeighbor.lock()); +} + +std::shared_ptr RenderablePolyVoxEntityItem::getYPNeighbor() { + return std::dynamic_pointer_cast(_yPNeighbor.lock()); +} + +std::shared_ptr RenderablePolyVoxEntityItem::getZPNeighbor() { + return std::dynamic_pointer_cast(_zPNeighbor.lock()); +} + void RenderablePolyVoxEntityItem::bonkNeighbors() { // flag neighbors to the negative of this entity as needing to rebake their meshes. cacheNeighbors(); - EntityItemPointer currentXNNeighbor = _xNNeighbor.lock(); - EntityItemPointer currentYNNeighbor = _yNNeighbor.lock(); - EntityItemPointer currentZNNeighbor = _zNNeighbor.lock(); + auto currentXNNeighbor = getXNNeighbor(); + auto currentYNNeighbor = getYNNeighbor(); + auto currentZNNeighbor = getZNNeighbor(); - if (currentXNNeighbor && currentXNNeighbor->getType() == EntityTypes::PolyVox) { - auto polyVoxXNNeighbor = std::dynamic_pointer_cast(currentXNNeighbor); - polyVoxXNNeighbor->setVolDataDirty(); + if (currentXNNeighbor) { + currentXNNeighbor->setVolDataDirty(); } - if (currentYNNeighbor && currentYNNeighbor->getType() == EntityTypes::PolyVox) { - auto polyVoxYNNeighbor = std::dynamic_pointer_cast(currentYNNeighbor); - polyVoxYNNeighbor->setVolDataDirty(); + if (currentYNNeighbor) { + currentYNNeighbor->setVolDataDirty(); } - if (currentZNNeighbor && currentZNNeighbor->getType() == EntityTypes::PolyVox) { - auto polyVoxZNNeighbor = std::dynamic_pointer_cast(currentZNNeighbor); - polyVoxZNNeighbor->setVolDataDirty(); + if (currentZNNeighbor) { + currentZNNeighbor->setVolDataDirty(); } } diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h index 44186073b2..f84637ec95 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h @@ -116,6 +116,13 @@ public: virtual void setYPNeighborID(const EntityItemID& yPNeighborID) override; virtual void setZPNeighborID(const EntityItemID& zPNeighborID) override; + std::shared_ptr getXNNeighbor(); + std::shared_ptr getYNNeighbor(); + std::shared_ptr getZNNeighbor(); + std::shared_ptr getXPNeighbor(); + std::shared_ptr getYPNeighbor(); + std::shared_ptr getZPNeighbor(); + virtual void updateRegistrationPoint(const glm::vec3& value) override; void setVoxelsFromData(QByteArray uncompressedData, quint16 voxelXSize, quint16 voxelYSize, quint16 voxelZSize); diff --git a/libraries/gpu-gl/src/gpu/gl/GLBackend.cpp b/libraries/gpu-gl/src/gpu/gl/GLBackend.cpp index 2e1084e581..3513d7a05b 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLBackend.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLBackend.cpp @@ -56,6 +56,7 @@ BackendPointer GLBackend::createBackend() { } result->initInput(); result->initTransform(); + result->initTextureManagementStage(); INSTANCE = result.get(); void* voidInstance = &(*result); diff --git a/libraries/gpu-gl/src/gpu/gl/GLBackend.h b/libraries/gpu-gl/src/gpu/gl/GLBackend.h index f99d34393c..1be279b375 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLBackend.h +++ b/libraries/gpu-gl/src/gpu/gl/GLBackend.h @@ -176,6 +176,9 @@ public: virtual void releaseQuery(GLuint id) const; virtual void queueLambda(const std::function lambda) const; + bool isTextureManagementSparseEnabled() const override { return (_textureManagement._sparseCapable && Texture::getEnableSparseTextures()); } + bool isTextureManagementIncrementalTransferEnabled() const override { return (_textureManagement._incrementalTransferCapable && Texture::getEnableIncrementalTextureTransfers()); } + protected: void recycle() const override; @@ -364,6 +367,12 @@ protected: void resetStages(); + struct TextureManagementStageState { + bool _sparseCapable { false }; + bool _incrementalTransferCapable { false }; + } _textureManagement; + virtual void initTextureManagementStage() {} + typedef void (GLBackend::*CommandCall)(const Batch&, size_t); static CommandCall _commandCalls[Batch::NUM_COMMANDS]; friend class GLState; diff --git a/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp b/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp index 61a76c2d0b..3ce1c8e5c4 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp @@ -111,7 +111,7 @@ float GLTexture::getMemoryPressure() { } #else // Hardcode texture limit for sparse textures at 1 GB for now - availableTextureMemory = GPU_MEMORY_RESERVE_BYTES; + availableTextureMemory = TEXTURE_MEMORY_MIN_BYTES; #endif } diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h b/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h index 059156b4a3..643d54af6a 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h +++ b/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h @@ -32,6 +32,7 @@ public: static GLuint allocate(const Texture& texture); static const uint32_t DEFAULT_PAGE_DIMENSION = 128; static const uint32_t DEFAULT_MAX_SPARSE_LEVEL = 0xFFFF; + public: GL45Texture(const std::weak_ptr& backend, const Texture& texture, GLuint externalId); GL45Texture(const std::weak_ptr& backend, const Texture& texture, bool transferrable); @@ -132,6 +133,9 @@ protected: // Output stage void do_blit(const Batch& batch, size_t paramOffset) override; + + // Texture Management Stage + void initTextureManagementStage() override; }; } } diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp index 033f6298ee..1c03ad7a83 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp +++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp @@ -148,6 +148,24 @@ uint32_t SparseInfo::getPageCount(const uvec3& dimensions) const { return pageCounts.x * pageCounts.y * pageCounts.z; } + + +void GL45Backend::initTextureManagementStage() { + + // enable the Sparse Texture on gl45 + _textureManagement._sparseCapable = true; + _textureManagement._incrementalTransferCapable = true; + + // But now let s refine the behavior based on vendor + std::string vendor { (const char*)glGetString(GL_VENDOR) }; + if ((vendor.find("AMD") != std::string::npos) || (vendor.find("ATI") != std::string::npos) || (vendor.find("INTEL") != std::string::npos)) { + qCDebug(gpugllogging) << "GPU is sparse capable but force it off, vendor = " << vendor.c_str(); + _textureManagement._sparseCapable = false; + } else { + qCDebug(gpugllogging) << "GPU is sparse capable, vendor = " << vendor.c_str(); + } +} + using TransferState = GL45Backend::GL45Texture::TransferState; TransferState::TransferState(GL45Texture& texture) : texture(texture) { @@ -250,7 +268,8 @@ GL45Texture::GL45Texture(const std::weak_ptr& backend, const Texture& GL45Texture::GL45Texture(const std::weak_ptr& backend, const Texture& texture, bool transferrable) : GLTexture(backend, texture, allocate(texture), transferrable), _sparseInfo(*this), _transferState(*this) { - if (_transferrable && Texture::getEnableSparseTextures()) { + auto theBackend = _backend.lock(); + if (_transferrable && theBackend && theBackend->isTextureManagementSparseEnabled()) { _sparseInfo.maybeMakeSparse(); if (_sparseInfo.sparse) { Backend::incrementTextureGPUSparseCount(); @@ -362,7 +381,8 @@ void GL45Texture::startTransfer() { } bool GL45Texture::continueTransfer() { - if (!Texture::getEnableIncrementalTextureTransfers()) { + auto backend = _backend.lock(); + if (!backend || !backend->isTextureManagementIncrementalTransferEnabled()) { size_t maxFace = GL_TEXTURE_CUBE_MAP == _target ? CUBE_NUM_FACES : 1; for (uint8_t face = 0; face < maxFace; ++face) { for (uint16_t mipLevel = _minMip; mipLevel <= _maxMip; ++mipLevel) { diff --git a/libraries/gpu/src/gpu/Context.h b/libraries/gpu/src/gpu/Context.h index 763e91b3e4..e174e9d728 100644 --- a/libraries/gpu/src/gpu/Context.h +++ b/libraries/gpu/src/gpu/Context.h @@ -85,7 +85,8 @@ public: void getStats(ContextStats& stats) const { stats = _stats; } - + virtual bool isTextureManagementSparseEnabled() const = 0; + virtual bool isTextureManagementIncrementalTransferEnabled() const = 0; // These should only be accessed by Backend implementation to repport the buffer and texture allocations, // they are NOT public calls @@ -125,6 +126,7 @@ protected: friend class Context; ContextStats _stats; StereoState _stereo; + }; class Context { @@ -270,7 +272,6 @@ protected: static std::atomic _textureGPUFramebufferMemoryUsage; static std::atomic _textureGPUTransferCount; - friend class Backend; }; typedef std::shared_ptr ContextPointer; diff --git a/libraries/gpu/src/gpu/Texture.h b/libraries/gpu/src/gpu/Texture.h index 0f1022a0c9..1eacb46d77 100755 --- a/libraries/gpu/src/gpu/Texture.h +++ b/libraries/gpu/src/gpu/Texture.h @@ -147,6 +147,7 @@ class Texture : public Resource { static std::atomic _enableSparseTextures; static std::atomic _enableIncrementalTextureTransfers; + public: static uint32_t getTextureCPUCount(); static Size getTextureCPUMemoryUsage(); diff --git a/libraries/octree/src/OctreePacketData.cpp b/libraries/octree/src/OctreePacketData.cpp index 5380aaa6ce..5fd7e4dba3 100644 --- a/libraries/octree/src/OctreePacketData.cpp +++ b/libraries/octree/src/OctreePacketData.cpp @@ -451,6 +451,9 @@ bool OctreePacketData::appendValue(const QVector& value) { bit = 0; } } + if (bit != 0) { + destinationBuffer++; + } int boolsSize = destinationBuffer - start; success = append(start, boolsSize); if (success) { @@ -683,6 +686,10 @@ int OctreePacketData::unpackDataFromBytes(const unsigned char *dataBytes, QVecto uint16_t length; memcpy(&length, dataBytes, sizeof(uint16_t)); dataBytes += sizeof(length); + if (length * sizeof(glm::vec3) > MAX_OCTREE_UNCOMRESSED_PACKET_SIZE) { + result.resize(0); + return sizeof(uint16_t); + } result.resize(length); memcpy(result.data(), dataBytes, length * sizeof(glm::vec3)); return sizeof(uint16_t) + length * sizeof(glm::vec3); @@ -692,6 +699,10 @@ int OctreePacketData::unpackDataFromBytes(const unsigned char *dataBytes, QVecto uint16_t length; memcpy(&length, dataBytes, sizeof(uint16_t)); dataBytes += sizeof(length); + if (length * sizeof(glm::quat) > MAX_OCTREE_UNCOMRESSED_PACKET_SIZE) { + result.resize(0); + return sizeof(uint16_t); + } result.resize(length); const unsigned char *start = dataBytes; @@ -706,6 +717,10 @@ int OctreePacketData::unpackDataFromBytes(const unsigned char* dataBytes, QVecto uint16_t length; memcpy(&length, dataBytes, sizeof(uint16_t)); dataBytes += sizeof(length); + if (length * sizeof(float) > MAX_OCTREE_UNCOMRESSED_PACKET_SIZE) { + result.resize(0); + return sizeof(uint16_t); + } result.resize(length); memcpy(result.data(), dataBytes, length * sizeof(float)); return sizeof(uint16_t) + length * sizeof(float); @@ -715,6 +730,10 @@ int OctreePacketData::unpackDataFromBytes(const unsigned char* dataBytes, QVecto uint16_t length; memcpy(&length, dataBytes, sizeof(uint16_t)); dataBytes += sizeof(length); + if (length / 8 > MAX_OCTREE_UNCOMRESSED_PACKET_SIZE) { + result.resize(0); + return sizeof(uint16_t); + } result.resize(length); int bit = 0; diff --git a/libraries/physics/src/ShapeFactory.cpp b/libraries/physics/src/ShapeFactory.cpp index 9b9ee0e299..100dab0fd1 100644 --- a/libraries/physics/src/ShapeFactory.cpp +++ b/libraries/physics/src/ShapeFactory.cpp @@ -256,8 +256,18 @@ const btCollisionShape* ShapeFactory::createShapeFromInfo(const ShapeInfo& info) } break; case SHAPE_TYPE_SPHERE: { - float radius = info.getHalfExtents().x; - shape = new btSphereShape(radius); + glm::vec3 halfExtents = info.getHalfExtents(); + float radius = halfExtents.x; + if (radius == halfExtents.y && radius == halfExtents.z) { + shape = new btSphereShape(radius); + } else { + ShapeInfo::PointList points; + points.reserve(NUM_UNIT_SPHERE_DIRECTIONS); + for (uint32_t i = 0; i < NUM_UNIT_SPHERE_DIRECTIONS; ++i) { + points.push_back(bulletToGLM(_unitSphereDirections[i]) * halfExtents); + } + shape = createConvexHull(points); + } } break; case SHAPE_TYPE_CAPSULE_Y: { diff --git a/libraries/plugins/src/plugins/InputPlugin.h b/libraries/plugins/src/plugins/InputPlugin.h index f68be3edf6..0db0b24420 100644 --- a/libraries/plugins/src/plugins/InputPlugin.h +++ b/libraries/plugins/src/plugins/InputPlugin.h @@ -21,6 +21,9 @@ public: virtual void pluginFocusOutEvent() = 0; virtual void pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) = 0; + // Some input plugins are comprised of multiple subdevices (SDL2, for instance). + // If an input plugin is only a single device, it will only return it's primary name. + virtual QStringList getSubdeviceNames() { return { getName() }; }; virtual bool isHandController() const = 0; }; diff --git a/libraries/plugins/src/plugins/PluginManager.cpp b/libraries/plugins/src/plugins/PluginManager.cpp index 21b80e2370..21e652dc40 100644 --- a/libraries/plugins/src/plugins/PluginManager.cpp +++ b/libraries/plugins/src/plugins/PluginManager.cpp @@ -86,6 +86,7 @@ const LoaderList& getLoadedPlugins() { QString pluginPath = QCoreApplication::applicationDirPath() + "/plugins/"; #endif QDir pluginDir(pluginPath); + pluginDir.setSorting(QDir::Name); pluginDir.setFilter(QDir::Files); if (pluginDir.exists()) { qInfo() << "Loading runtime plugins from " << pluginPath; diff --git a/libraries/plugins/src/plugins/PluginUtils.cpp b/libraries/plugins/src/plugins/PluginUtils.cpp index bc53e8166a..48530bfe8c 100644 --- a/libraries/plugins/src/plugins/PluginUtils.cpp +++ b/libraries/plugins/src/plugins/PluginUtils.cpp @@ -32,3 +32,26 @@ bool PluginUtils::isHandControllerAvailable() { } return false; }; + +bool isSubdeviceContainingNameAvailable(QString name) { + for (auto& inputPlugin : PluginManager::getInstance()->getInputPlugins()) { + if (inputPlugin->isActive()) { + auto subdeviceNames = inputPlugin->getSubdeviceNames(); + for (auto& subdeviceName : subdeviceNames) { + if (subdeviceName.contains(name)) { + return true; + } + } + } + } + return false; +}; + +bool PluginUtils::isViveControllerAvailable() { + return isSubdeviceContainingNameAvailable("OpenVR"); +}; + +bool PluginUtils::isXboxControllerAvailable() { + return isSubdeviceContainingNameAvailable("X360 Controller"); +}; + diff --git a/libraries/plugins/src/plugins/PluginUtils.h b/libraries/plugins/src/plugins/PluginUtils.h index 727677ccd3..f1449bc3af 100644 --- a/libraries/plugins/src/plugins/PluginUtils.h +++ b/libraries/plugins/src/plugins/PluginUtils.h @@ -16,4 +16,6 @@ class PluginUtils { public: static bool isHMDAvailable(const QString& pluginName = ""); static bool isHandControllerAvailable(); + static bool isViveControllerAvailable(); + static bool isXboxControllerAvailable(); }; diff --git a/libraries/script-engine/src/Quat.cpp b/libraries/script-engine/src/Quat.cpp index c0b87c9a5c..6c2e7a349e 100644 --- a/libraries/script-engine/src/Quat.cpp +++ b/libraries/script-engine/src/Quat.cpp @@ -37,29 +37,30 @@ glm::quat Quat::lookAt(const glm::vec3& eye, const glm::vec3& center, const glm: glm::quat Quat::lookAtSimple(const glm::vec3& eye, const glm::vec3& center) { auto dir = glm::normalize(center - eye); // if the direction is nearly aligned with the Y axis, then use the X axis for 'up' - if (dir.x < 0.001f && dir.z < 0.001f) { + const float MAX_ABS_Y_COMPONENT = 0.9999991f; + if (fabsf(dir.y) > MAX_ABS_Y_COMPONENT) { return lookAt(eye, center, Vectors::UNIT_X); } return lookAt(eye, center, Vectors::UNIT_Y); } -glm::quat Quat::multiply(const glm::quat& q1, const glm::quat& q2) { - return q1 * q2; +glm::quat Quat::multiply(const glm::quat& q1, const glm::quat& q2) { + return q1 * q2; } -glm::quat Quat::fromVec3Degrees(const glm::vec3& eulerAngles) { - return glm::quat(glm::radians(eulerAngles)); +glm::quat Quat::fromVec3Degrees(const glm::vec3& eulerAngles) { + return glm::quat(glm::radians(eulerAngles)); } -glm::quat Quat::fromVec3Radians(const glm::vec3& eulerAngles) { - return glm::quat(eulerAngles); +glm::quat Quat::fromVec3Radians(const glm::vec3& eulerAngles) { + return glm::quat(eulerAngles); } -glm::quat Quat::fromPitchYawRollDegrees(float pitch, float yaw, float roll) { +glm::quat Quat::fromPitchYawRollDegrees(float pitch, float yaw, float roll) { return glm::quat(glm::radians(glm::vec3(pitch, yaw, roll))); } -glm::quat Quat::fromPitchYawRollRadians(float pitch, float yaw, float roll) { +glm::quat Quat::fromPitchYawRollRadians(float pitch, float yaw, float roll) { return glm::quat(glm::vec3(pitch, yaw, roll)); } diff --git a/libraries/shared/src/GPUIdent.cpp b/libraries/shared/src/GPUIdent.cpp index 02f92d87e7..fb6f291e19 100644 --- a/libraries/shared/src/GPUIdent.cpp +++ b/libraries/shared/src/GPUIdent.cpp @@ -10,9 +10,15 @@ #include + #ifdef Q_OS_WIN -#include -#include +#include + +//#include +//#include + +#include +#pragma comment(lib, "dxgi.lib") #elif defined(Q_OS_MAC) #include @@ -53,9 +59,101 @@ GPUIdent* GPUIdent::ensureQuery(const QString& vendor, const QString& renderer) CGLDestroyRendererInfo(rendererInfo); #elif defined(Q_OS_WIN) + + struct ConvertLargeIntegerToQString { + QString convert(const LARGE_INTEGER& version) { + QString value; + value.append(QString::number(uint32_t(((version.HighPart & 0xFFFF0000) >> 16) & 0x0000FFFF))); + value.append("."); + value.append(QString::number(uint32_t((version.HighPart) & 0x0000FFFF))); + value.append("."); + value.append(QString::number(uint32_t(((version.LowPart & 0xFFFF0000) >> 16) & 0x0000FFFF))); + value.append("."); + value.append(QString::number(uint32_t((version.LowPart) & 0x0000FFFF))); + return value; + } + } convertDriverVersionToString; + + // Create the DXGI factory + // Let s get into DXGI land: + HRESULT hr = S_OK; + + IDXGIFactory1* pFactory = nullptr; + hr = CreateDXGIFactory1(__uuidof(IDXGIFactory1), (void**)(&pFactory) ); + if (hr != S_OK || pFactory == nullptr) { + qCDebug(shared) << "Unable to create DXGI"; + return this; + } + + std::vector validAdapterList; + using AdapterEntry = std::pair, std::vector>; + std::vector adapterToOutputs; + // Enumerate adapters and outputs + { + UINT adapterNum = 0; + IDXGIAdapter1* pAdapter = nullptr; + while (pFactory->EnumAdapters1(adapterNum, &pAdapter) != DXGI_ERROR_NOT_FOUND) { + + // Found an adapter, get descriptor + DXGI_ADAPTER_DESC1 adapterDesc; + pAdapter->GetDesc1(&adapterDesc); + + LARGE_INTEGER version; + hr = pAdapter->CheckInterfaceSupport(__uuidof(IDXGIDevice), &version); + + std::wstring wDescription (adapterDesc.Description); + std::string description(wDescription.begin(), wDescription.end()); + qCDebug(shared) << "Found adapter: " << description.c_str() + << " Driver version: " << convertDriverVersionToString.convert(version); + + AdapterEntry adapterEntry; + adapterEntry.first.first = adapterDesc; + adapterEntry.first.second = version; + + + + UINT outputNum = 0; + IDXGIOutput * pOutput; + bool hasOutputConnectedToDesktop = false; + while (pAdapter->EnumOutputs(outputNum, &pOutput) != DXGI_ERROR_NOT_FOUND) { + + // FOund an output attached to the adapter, get descriptor + DXGI_OUTPUT_DESC outputDesc; + pOutput->GetDesc(&outputDesc); + + adapterEntry.second.push_back(outputDesc); + + std::wstring wDeviceName(outputDesc.DeviceName); + std::string deviceName(wDeviceName.begin(), wDeviceName.end()); + qCDebug(shared) << " Found output: " << deviceName.c_str() << " desktop: " << (outputDesc.AttachedToDesktop ? "true" : "false") + << " Rect [ l=" << outputDesc.DesktopCoordinates.left << " r=" << outputDesc.DesktopCoordinates.right + << " b=" << outputDesc.DesktopCoordinates.bottom << " t=" << outputDesc.DesktopCoordinates.top << " ]"; + + hasOutputConnectedToDesktop |= (bool) outputDesc.AttachedToDesktop; + + pOutput->Release(); + outputNum++; + } + + adapterToOutputs.push_back(adapterEntry); + + // add this adapter to the valid list if has output + if (hasOutputConnectedToDesktop && !adapterEntry.second.empty()) { + validAdapterList.push_back(adapterNum); + } + + pAdapter->Release(); + adapterNum++; + } + } + pFactory->Release(); + + + // THis was the previous technique used to detect the platform we are running on on windows. + /* // COM must be initialized already using CoInitialize. E.g., by the audio subsystem. CComPtr spLoc = NULL; - HRESULT hr = CoCreateInstance(CLSID_WbemLocator, 0, CLSCTX_SERVER, IID_IWbemLocator, (LPVOID *)&spLoc); + hr = CoCreateInstance(CLSID_WbemLocator, 0, CLSCTX_SERVER, IID_IWbemLocator, (LPVOID *)&spLoc); if (hr != S_OK || spLoc == NULL) { qCDebug(shared) << "Unable to connect to WMI"; return this; @@ -139,7 +237,7 @@ GPUIdent* GPUIdent::ensureQuery(const QString& vendor, const QString& renderer) var.ChangeType(CIM_UINT64); // We're going to receive some integral type, but it might not be uint. // We might be hosed here. The parameter is documented to be UINT32, but that's only 4 GB! const ULONGLONG BYTES_PER_MEGABYTE = 1024 * 1024; - _dedicatedMemoryMB = (uint) (var.ullVal / BYTES_PER_MEGABYTE); + _dedicatedMemoryMB = (uint64_t) (var.ullVal / BYTES_PER_MEGABYTE); } else { qCDebug(shared) << "Unable to get video AdapterRAM"; @@ -149,6 +247,22 @@ GPUIdent* GPUIdent::ensureQuery(const QString& vendor, const QString& renderer) } hr = spEnumInst->Next(WBEM_INFINITE, 1, &spInstance.p, &uNumOfInstances); } + */ + + if (!validAdapterList.empty()) { + auto& adapterEntry = adapterToOutputs[validAdapterList.front()]; + + std::wstring wDescription(adapterEntry.first.first.Description); + std::string description(wDescription.begin(), wDescription.end()); + _name = QString(description.c_str()); + + _driver = convertDriverVersionToString.convert(adapterEntry.first.second); + + const ULONGLONG BYTES_PER_MEGABYTE = 1024 * 1024; + _dedicatedMemoryMB = (uint64_t)(adapterEntry.first.first.DedicatedVideoMemory / BYTES_PER_MEGABYTE); + _isValid = true; + } + #endif return this; } diff --git a/libraries/shared/src/GPUIdent.h b/libraries/shared/src/GPUIdent.h index 4e844b0e54..8615e61b08 100644 --- a/libraries/shared/src/GPUIdent.h +++ b/libraries/shared/src/GPUIdent.h @@ -14,17 +14,19 @@ #ifndef hifi_GPUIdent_h #define hifi_GPUIdent_h +#include + class GPUIdent { public: - unsigned int getMemory() { return _dedicatedMemoryMB; } + uint64_t getMemory() { return _dedicatedMemoryMB; } QString getName() { return _name; } QString getDriver() { return _driver; } bool isValid() { return _isValid; } // E.g., GPUIdent::getInstance()->getMemory(); static GPUIdent* getInstance(const QString& vendor = "", const QString& renderer = "") { return _instance.ensureQuery(vendor, renderer); } private: - uint _dedicatedMemoryMB { 0 }; + uint64_t _dedicatedMemoryMB { 0 }; QString _name { "" }; QString _driver { "" }; bool _isQueried { false }; diff --git a/libraries/shared/src/RegisteredMetaTypes.cpp b/libraries/shared/src/RegisteredMetaTypes.cpp index 171b58de17..984529c4ba 100644 --- a/libraries/shared/src/RegisteredMetaTypes.cpp +++ b/libraries/shared/src/RegisteredMetaTypes.cpp @@ -263,6 +263,14 @@ void quatFromScriptValue(const QScriptValue& object, glm::quat &quat) { quat.y = object.property("y").toVariant().toFloat(); quat.z = object.property("z").toVariant().toFloat(); quat.w = object.property("w").toVariant().toFloat(); + + // enforce normalized quaternion + float length = glm::length(quat); + if (length > FLT_EPSILON) { + quat /= length; + } else { + quat = glm::quat(); + } } glm::quat quatFromVariant(const QVariant &object, bool& isValid) { @@ -273,6 +281,14 @@ glm::quat quatFromVariant(const QVariant &object, bool& isValid) { q.y = qvec3.y(); q.z = qvec3.z(); q.w = qvec3.scalar(); + + // enforce normalized quaternion + float length = glm::length(q); + if (length > FLT_EPSILON) { + q /= length; + } else { + q = glm::quat(); + } isValid = true; } else { auto map = object.toMap(); diff --git a/libraries/shared/src/ShapeInfo.cpp b/libraries/shared/src/ShapeInfo.cpp index 424c2bfa22..b8ea3a4272 100644 --- a/libraries/shared/src/ShapeInfo.cpp +++ b/libraries/shared/src/ShapeInfo.cpp @@ -33,13 +33,8 @@ void ShapeInfo::setParams(ShapeType type, const glm::vec3& halfExtents, QString _halfExtents = glm::vec3(0.0f); break; case SHAPE_TYPE_BOX: + case SHAPE_TYPE_SPHERE: break; - case SHAPE_TYPE_SPHERE: { - // sphere radius is max of halfExtents - float radius = glm::max(glm::max(halfExtents.x, halfExtents.y), halfExtents.z); - _halfExtents = glm::vec3(radius); - break; - } case SHAPE_TYPE_COMPOUND: case SHAPE_TYPE_STATIC_MESH: _url = QUrl(url); @@ -119,8 +114,7 @@ float ShapeInfo::computeVolume() const { break; } case SHAPE_TYPE_SPHERE: { - float radius = _halfExtents.x; - volume = 4.0f * PI * radius * radius * radius / 3.0f; + volume = 4.0f * PI * _halfExtents.x * _halfExtents.y * _halfExtents.z / 3.0f; break; } case SHAPE_TYPE_CYLINDER_Y: { diff --git a/libraries/ui/src/InfoView.cpp b/libraries/ui/src/InfoView.cpp index 6b6d6645f5..d2c72bf5f2 100644 --- a/libraries/ui/src/InfoView.cpp +++ b/libraries/ui/src/InfoView.cpp @@ -37,7 +37,7 @@ QString fetchVersion(const QUrl& url) { return r.trimmed(); } -void InfoView::show(const QString& path, bool firstOrChangedOnly) { +void InfoView::show(const QString& path, bool firstOrChangedOnly, QString urlQuery) { static bool registered{ false }; if (!registered) { registerType(); @@ -49,6 +49,8 @@ void InfoView::show(const QString& path, bool firstOrChangedOnly) { } else { url = QUrl::fromLocalFile(path); } + url.setQuery(urlQuery); + if (firstOrChangedOnly) { const QString lastVersion = infoVersion.get(); const QString version = fetchVersion(url); diff --git a/libraries/ui/src/InfoView.h b/libraries/ui/src/InfoView.h index 275effbfa5..ea6150a4d8 100644 --- a/libraries/ui/src/InfoView.h +++ b/libraries/ui/src/InfoView.h @@ -22,7 +22,7 @@ class InfoView : public QQuickItem { static const QString NAME; public: static void registerType(); - static void show(const QString& path, bool firstOrChangedOnly = false); + static void show(const QString& path, bool firstOrChangedOnly = false, QString urlQuery = ""); InfoView(QQuickItem* parent = nullptr); QUrl url(); diff --git a/libraries/ui/src/OffscreenUi.cpp b/libraries/ui/src/OffscreenUi.cpp index ca7d3f7c17..d9b15eebe0 100644 --- a/libraries/ui/src/OffscreenUi.cpp +++ b/libraries/ui/src/OffscreenUi.cpp @@ -371,6 +371,13 @@ void OffscreenUi::setPinned(bool pinned) { } } +void OffscreenUi::setConstrainToolbarToCenterX(bool constrained) { + bool invokeResult = QMetaObject::invokeMethod(_desktop, "setConstrainToolbarToCenterX", Q_ARG(QVariant, constrained)); + if (!invokeResult) { + qWarning() << "Failed to set toolbar constraint"; + } +} + void OffscreenUi::addMenuInitializer(std::function f) { if (!_vrMenu) { _queuedMenuInitializers.push_back(f); diff --git a/libraries/ui/src/OffscreenUi.h b/libraries/ui/src/OffscreenUi.h index 2e6e853336..3ab4fa0758 100644 --- a/libraries/ui/src/OffscreenUi.h +++ b/libraries/ui/src/OffscreenUi.h @@ -52,6 +52,7 @@ public: void setPinned(bool pinned = true); void togglePinned(); + void setConstrainToolbarToCenterX(bool constrained); bool eventFilter(QObject* originalDestination, QEvent* event) override; void addMenuInitializer(std::function f); diff --git a/plugins/hifiSdl2/src/Joystick.h b/plugins/hifiSdl2/src/Joystick.h index 25381d545a..a10e02d325 100644 --- a/plugins/hifiSdl2/src/Joystick.h +++ b/plugins/hifiSdl2/src/Joystick.h @@ -31,6 +31,8 @@ public: const QString& getName() const { return _name; } + SDL_GameController* getGameController() { return _sdlGameController; } + // Device functions virtual controller::Input::NamedVector getAvailableInputs() const override; virtual QString getDefaultMappingConfig() const override; diff --git a/plugins/hifiSdl2/src/SDL2Manager.cpp b/plugins/hifiSdl2/src/SDL2Manager.cpp index b9a19658e2..b6fa567aee 100644 --- a/plugins/hifiSdl2/src/SDL2Manager.cpp +++ b/plugins/hifiSdl2/src/SDL2Manager.cpp @@ -65,8 +65,10 @@ void SDL2Manager::init() { _openJoysticks[id] = joystick; auto userInputMapper = DependencyManager::get(); userInputMapper->registerDevice(joystick); + auto name = SDL_GameControllerName(controller); + _subdeviceNames << name; emit joystickAdded(joystick.get()); - emit subdeviceConnected(getName(), SDL_GameControllerName(controller)); + emit subdeviceConnected(getName(), name); } } } @@ -78,6 +80,10 @@ void SDL2Manager::init() { } } +QStringList SDL2Manager::getSubdeviceNames() { + return _subdeviceNames; +} + void SDL2Manager::deinit() { _openJoysticks.clear(); @@ -157,15 +163,19 @@ void SDL2Manager::pluginUpdate(float deltaTime, const controller::InputCalibrati Joystick::Pointer joystick = std::make_shared(id, controller); _openJoysticks[id] = joystick; userInputMapper->registerDevice(joystick); + QString name = SDL_GameControllerName(controller); emit joystickAdded(joystick.get()); - emit subdeviceConnected(getName(), SDL_GameControllerName(controller)); + emit subdeviceConnected(getName(), name); + _subdeviceNames << name; } } else if (event.type == SDL_CONTROLLERDEVICEREMOVED) { if (_openJoysticks.contains(event.cdevice.which)) { Joystick::Pointer joystick = _openJoysticks[event.cdevice.which]; _openJoysticks.remove(event.cdevice.which); userInputMapper->removeDevice(joystick->getDeviceID()); + QString name = SDL_GameControllerName(joystick->getGameController()); emit joystickRemoved(joystick.get()); + _subdeviceNames.removeOne(name); } } } diff --git a/plugins/hifiSdl2/src/SDL2Manager.h b/plugins/hifiSdl2/src/SDL2Manager.h index 44b75abd2f..fc1654bce1 100644 --- a/plugins/hifiSdl2/src/SDL2Manager.h +++ b/plugins/hifiSdl2/src/SDL2Manager.h @@ -26,6 +26,7 @@ public: bool isSupported() const override; const QString& getName() const override { return NAME; } + QStringList getSubdeviceNames() override; bool isHandController() const override { return false; } void init() override; @@ -79,6 +80,7 @@ private: QMap _openJoysticks; bool _isInitialized { false } ; static const QString NAME; + QStringList _subdeviceNames; }; #endif // hifi__SDL2Manager_h diff --git a/plugins/oculus/src/OculusControllerManager.cpp b/plugins/oculus/src/OculusControllerManager.cpp index 5d493f4c9d..f0edc5a465 100644 --- a/plugins/oculus/src/OculusControllerManager.cpp +++ b/plugins/oculus/src/OculusControllerManager.cpp @@ -117,6 +117,17 @@ void OculusControllerManager::stopHapticPulse(bool leftHand) { } } +QStringList OculusControllerManager::getSubdeviceNames() { + QStringList devices; + if (_touch) { + devices << _touch->getName(); + } + if (_remote) { + devices << _remote->getName(); + } + return devices; +} + using namespace controller; static const std::vector> BUTTON_MAP { { diff --git a/plugins/oculus/src/OculusControllerManager.h b/plugins/oculus/src/OculusControllerManager.h index 234acd7db2..1ca9e0f47e 100644 --- a/plugins/oculus/src/OculusControllerManager.h +++ b/plugins/oculus/src/OculusControllerManager.h @@ -27,6 +27,7 @@ public: const QString& getName() const override { return NAME; } bool isHandController() const override { return _touch != nullptr; } + QStringList getSubdeviceNames() override; bool activate() override; void deactivate() override; diff --git a/scripts/defaultScripts.js b/scripts/defaultScripts.js index 718b5f3d3e..4376960ea5 100644 --- a/scripts/defaultScripts.js +++ b/scripts/defaultScripts.js @@ -32,7 +32,8 @@ var DEFAULT_SCRIPTS = [ "system/controllers/toggleAdvancedMovementForHandControllers.js", "system/dialTone.js", "system/firstPersonHMD.js", - "system/snapshot.js" + "system/snapshot.js", + "system/help.js" ]; // add a menu item for debugging diff --git a/scripts/system/assets/images/tools/help.svg b/scripts/system/assets/images/tools/help.svg new file mode 100644 index 0000000000..b7fa8ca5cd --- /dev/null +++ b/scripts/system/assets/images/tools/help.svg @@ -0,0 +1,110 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/system/help.js b/scripts/system/help.js new file mode 100644 index 0000000000..e79ed0444c --- /dev/null +++ b/scripts/system/help.js @@ -0,0 +1,41 @@ +"use strict"; + +// +// help.js +// scripts/system/ +// +// Created by Howard Stearns on 2 Nov 2016 +// Copyright 2016 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 +// + +(function() { // BEGIN LOCAL_SCOPE + + var toolBar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system"); + var buttonName = "help"; // matching location reserved in Desktop.qml + var button = toolBar.addButton({ + objectName: buttonName, + imageURL: Script.resolvePath("assets/images/tools/help.svg"), + visible: true, + hoverState: 2, + defaultState: 1, + buttonState: 1, + alpha: 0.9 + }); + + // TODO: make button state reflect whether the window is opened or closed (independently from us). + + function onClicked(){ + Menu.triggerOption('Help...') + } + + button.clicked.connect(onClicked); + + Script.scriptEnding.connect(function () { + toolBar.removeButton(buttonName); + button.clicked.disconnect(onClicked); + }); + +}()); // END LOCAL_SCOPE diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index a7dfb048b8..d6d9098e21 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -1492,7 +1492,7 @@ function loaded() { var lis = dropdown.parentNode.getElementsByTagName("li"); var text = ""; for (var i = 0; i < lis.length; i++) { - if (lis[i].getAttribute("value") === dropdown.value) { + if (String(lis[i].getAttribute("value")) === String(dropdown.value)) { text = lis[i].textContent; } } diff --git a/server-console/packager.js b/server-console/packager.js index bc3b8989d2..89bcd7cb71 100644 --- a/server-console/packager.js +++ b/server-console/packager.js @@ -37,7 +37,7 @@ if (osType == "Darwin") { } else if (osType == "Windows_NT") { options["version-string"] = { CompanyName: "High Fidelity, Inc.", - FileDescription: SHORT_NAME, + FileDescription: FULL_NAME, ProductName: FULL_NAME, OriginalFilename: EXEC_NAME + ".exe" } diff --git a/server-console/src/main.js b/server-console/src/main.js index d8a6f30ac1..6c82230601 100644 --- a/server-console/src/main.js +++ b/server-console/src/main.js @@ -868,6 +868,12 @@ function onContentLoaded() { homeServer.start(); } + // If we were launched with the launchInterface option, then we need to launch interface now + if (argv.launchInterface) { + log.debug("Interface launch requested... argv.launchInterface:", argv.launchInterface); + startInterface(); + } + // If we were launched with the shutdownWatcher option, then we need to watch for the interface app // shutting down. The interface app will regularly update a running state file which we will check. // If the file doesn't exist or stops updating for a significant amount of time, we will shut down. diff --git a/tutorial/tutorial.js b/tutorial/tutorial.js index 4908465779..3d2b4ce36b 100644 --- a/tutorial/tutorial.js +++ b/tutorial/tutorial.js @@ -715,7 +715,8 @@ var stepTurnAround = function(name) { this.tempTag = name + "-temporary"; this.onActionBound = this.onAction.bind(this); - this.numTimesTurnPressed = 0; + this.numTimesSnapTurnPressed = 0; + this.numTimesSmoothTurnPressed = 0; } stepTurnAround.prototype = { start: function(onFinish) { @@ -724,19 +725,26 @@ stepTurnAround.prototype = { showEntitiesWithTag(this.tag); - this.numTimesTurnPressed = 0; + this.numTimesSnapTurnPressed = 0; + this.numTimesSmoothTurnPressed = 0; + this.smoothTurnDown = false; Controller.actionEvent.connect(this.onActionBound); this.interval = Script.setInterval(function() { - debug("TurnAround | Checking if finished", this.numTimesTurnPressed); + debug("TurnAround | Checking if finished", + this.numTimesSnapTurnPressed, this.numTimesSmoothTurnPressed); var FORWARD_THRESHOLD = 90; - var REQ_NUM_TIMES_PRESSED = 3; + var REQ_NUM_TIMES_SNAP_TURN_PRESSED = 3; + var REQ_NUM_TIMES_SMOOTH_TURN_PRESSED = 2; var dir = Quat.getFront(MyAvatar.orientation); var angle = Math.atan2(dir.z, dir.x); var angleDegrees = ((angle / Math.PI) * 180); - if (this.numTimesTurnPressed >= REQ_NUM_TIMES_PRESSED && Math.abs(angleDegrees) < FORWARD_THRESHOLD) { + var hasTurnedEnough = this.numTimesSnapTurnPressed >= REQ_NUM_TIMES_SNAP_TURN_PRESSED + || this.numTimesSmoothTurnPressed >= REQ_NUM_TIMES_SMOOTH_TURN_PRESSED; + var facingForward = Math.abs(angleDegrees) < FORWARD_THRESHOLD + if (hasTurnedEnough && facingForward) { Script.clearInterval(this.interval); this.interval = null; playSuccessSound(); @@ -746,9 +754,19 @@ stepTurnAround.prototype = { }, onAction: function(action, value) { var STEP_YAW_ACTION = 6; + var SMOOTH_YAW_ACTION = 4; + if (action == STEP_YAW_ACTION && value != 0) { - debug("TurnAround | Got yaw action"); - this.numTimesTurnPressed += 1; + debug("TurnAround | Got step yaw action"); + ++this.numTimesSnapTurnPressed; + } else if (action == SMOOTH_YAW_ACTION) { + debug("TurnAround | Got smooth yaw action"); + if (this.smoothTurnDown && value === 0) { + this.smoothTurnDown = false; + ++this.numTimesSmoothTurnPressed; + } else if (!this.smoothTurnDown && value !== 0) { + this.smoothTurnDown = true; + } } }, cleanup: function() { @@ -971,6 +989,7 @@ TutorialManager = function() { var currentStep = null; var startedTutorialAt = 0; var startedLastStepAt = 0; + var didFinishTutorial = false; var wentToEntryStepNum; var VERSION = 1; @@ -1032,6 +1051,7 @@ TutorialManager = function() { info("DONE WITH TUTORIAL"); currentStepNum = -1; currentStep = null; + didFinishTutorial = true; return false; } else { info("Starting step", currentStepNum); @@ -1069,8 +1089,11 @@ TutorialManager = function() { // This is a message sent from the "entry" portal in the courtyard, // after the tutorial has finished. this.enteredEntryPortal = function() { - info("Got enteredEntryPortal, tracking"); - this.trackStep("wentToEntry", wentToEntryStepNum); + info("Got enteredEntryPortal"); + if (didFinishTutorial) { + info("Tracking wentToEntry"); + this.trackStep("wentToEntry", wentToEntryStepNum); + } } }