diff --git a/BUILD.md b/BUILD.md index 30302d611b..4d321146c3 100644 --- a/BUILD.md +++ b/BUILD.md @@ -2,7 +2,7 @@ - [cmake](https://cmake.org/download/): 3.9 - [Qt](https://www.qt.io/download-open-source): 5.9.1 -- [OpenSSL](https://www.openssl.org/): Use the latest available version of OpenSSL to avoid security vulnerabilities. +- [OpenSSL](https://www.openssl.org/): Use the latest available 1.0 version (**NOT** 1.1) of OpenSSL to avoid security vulnerabilities. - [VHACD](https://github.com/virneo/v-hacd)(clone this repository)(Optional) ### CMake External Project Dependencies diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index 620d593ebc..dbbcc004ca 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -272,22 +272,22 @@ void DomainGatekeeper::updateNodePermissions() { userPerms.permissions |= NodePermissions::Permission::canWriteToAssetServer; userPerms.permissions |= NodePermissions::Permission::canReplaceDomainContent; } else { - // this node is an agent - const QHostAddress& addr = node->getLocalSocket().getAddress(); - bool isLocalUser = (addr == limitedNodeList->getLocalSockAddr().getAddress() || - addr == QHostAddress::LocalHost); - // at this point we don't have a sending socket for packets from this node - assume it is the active socket // or the public socket if we haven't activated a socket for the node yet HifiSockAddr connectingAddr = node->getActiveSocket() ? *node->getActiveSocket() : node->getPublicSocket(); QString hardwareAddress; QUuid machineFingerprint; + bool isLocalUser { false }; DomainServerNodeData* nodeData = static_cast(node->getLinkedData()); if (nodeData) { hardwareAddress = nodeData->getHardwareAddress(); machineFingerprint = nodeData->getMachineFingerprint(); + + auto sendingAddress = nodeData->getSendingSockAddr().getAddress(); + isLocalUser = (sendingAddress == limitedNodeList->getLocalSockAddr().getAddress() || + sendingAddress == QHostAddress::LocalHost); } userPerms = setPermissionsForUser(isLocalUser, verifiedUsername, connectingAddr.getAddress(), hardwareAddress, machineFingerprint); diff --git a/interface/resources/controllers/standard.json b/interface/resources/controllers/standard.json index 0d5c095585..28f15605e0 100644 --- a/interface/resources/controllers/standard.json +++ b/interface/resources/controllers/standard.json @@ -25,7 +25,7 @@ }, { "from": "Standard.RX", - "when": [ "Application.InHMD", "Application.SnapTurn" ], + "when": [ "Application.SnapTurn" ], "to": "Actions.StepYaw", "filters": [ @@ -128,4 +128,4 @@ { "from": "Standard.TrackedObject14", "to" : "Actions.TrackedObject14" }, { "from": "Standard.TrackedObject15", "to" : "Actions.TrackedObject15" } ] -} +} \ No newline at end of file diff --git a/interface/resources/qml/controls-uit/CheckBoxQQC2.qml b/interface/resources/qml/controls-uit/CheckBoxQQC2.qml new file mode 100644 index 0000000000..92bad04d01 --- /dev/null +++ b/interface/resources/qml/controls-uit/CheckBoxQQC2.qml @@ -0,0 +1,113 @@ +// +// CheckBox2.qml +// +// Created by Vlad Stelmahovsky on 10 Aug 2017 +// Copyright 2017 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 +// + +import QtQuick 2.7 +import QtQuick.Controls 2.2 + +import "../styles-uit" +import "../controls-uit" as HiFiControls + +CheckBox { + id: checkBox + + HifiConstants { id: hifi; } + + padding: 0 + leftPadding: 0 + property int colorScheme: hifi.colorSchemes.light + property string color: hifi.colors.lightGrayText + readonly property bool isLightColorScheme: colorScheme === hifi.colorSchemes.light + property bool isRedCheck: false + property bool isRound: false + property int boxSize: 14 + property int boxRadius: isRound ? boxSize : 3 + property bool wrap: true; + readonly property int checkSize: Math.max(boxSize - 8, 10) + readonly property int checkRadius: isRound ? checkSize / 2 : 2 + focusPolicy: Qt.ClickFocus + + indicator: Rectangle { + id: box + implicitWidth: boxSize + implicitHeight: boxSize + radius: boxRadius + x: checkBox.leftPadding + y: parent.height / 2 - height / 2 + border.width: 1 + border.color: pressed || hovered + ? hifi.colors.checkboxCheckedBorder + : (checkBox.isLightColorScheme ? hifi.colors.checkboxLightFinish : hifi.colors.checkboxDarkFinish) + + gradient: Gradient { + GradientStop { + position: 0.2 + color: pressed || hovered + ? (checkBox.isLightColorScheme ? hifi.colors.checkboxChecked : hifi.colors.checkboxLightStart) + : (checkBox.isLightColorScheme ? hifi.colors.checkboxLightStart : hifi.colors.checkboxDarkStart) + } + GradientStop { + position: 1.0 + color: pressed || hovered + ? (checkBox.isLightColorScheme ? hifi.colors.checkboxChecked : hifi.colors.checkboxLightFinish) + : (checkBox.isLightColorScheme ? hifi.colors.checkboxLightFinish : hifi.colors.checkboxDarkFinish) + } + } + + Rectangle { + visible: pressed || hovered + anchors.centerIn: parent + id: innerBox + width: checkSize - 4 + height: width + radius: checkRadius + color: hifi.colors.checkboxCheckedBorder + } + + Rectangle { + id: check + width: checkSize + height: checkSize + radius: checkRadius + anchors.centerIn: parent + color: isRedCheck ? hifi.colors.checkboxCheckedRed : hifi.colors.checkboxChecked + border.width: 2 + border.color: isRedCheck? hifi.colors.checkboxCheckedBorderRed : hifi.colors.checkboxCheckedBorder + visible: checked && !pressed || !checked && pressed + } + + Rectangle { + id: disabledOverlay + visible: !enabled + width: boxSize + height: boxSize + radius: boxRadius + border.width: 1 + border.color: hifi.colors.baseGrayHighlight + color: hifi.colors.baseGrayHighlight + opacity: 0.5 + } + } + + contentItem: Text { + id: root + FontLoader { id: ralewaySemiBold; source: pathToFonts + "fonts/Raleway-SemiBold.ttf"; } + font.pixelSize: hifi.fontSizes.inputLabel + font.family: ralewaySemiBold.name + text: checkBox.text + color: checkBox.color + x: 2 + wrapMode: checkBox.wrap ? Text.Wrap : Text.NoWrap + elide: checkBox.wrap ? Text.ElideNone : Text.ElideRight + enabled: checkBox.enabled + verticalAlignment: Text.AlignVCenter + leftPadding: checkBox.indicator.width + checkBox.spacing + } +} + diff --git a/interface/resources/qml/hifi/audio/Audio.qml b/interface/resources/qml/hifi/audio/Audio.qml index a819e032eb..b1f80ac5e8 100644 --- a/interface/resources/qml/hifi/audio/Audio.qml +++ b/interface/resources/qml/hifi/audio/Audio.qml @@ -12,7 +12,7 @@ // import QtQuick 2.5 -import QtQuick.Controls 1.4 +import QtQuick.Controls 2.2 import QtQuick.Layouts 1.3 import "../../styles-uit" @@ -36,7 +36,41 @@ Rectangle { return (root.parent !== null) && root.parent.objectName == "loader"; } + + property bool isVR: Audio.context === "VR" + property real rightMostInputLevelPos: 0 + //placeholder for control sizes and paddings + //recalculates dynamically in case of UI size is changed + QtObject { + id: margins + property real paddings: root.width / 20.25 + + property real sizeCheckBox: root.width / 13.5 + property real sizeText: root.width / 2.5 + property real sizeLevel: root.width / 5.8 + property real sizeDesktop: root.width / 5.8 + property real sizeVR: root.width / 13.5 + } + + TabBar { + id: bar + spacing: 0 + width: parent.width + height: 42 + currentIndex: isVR ? 1 : 0 + + AudioControls.AudioTabButton { + height: parent.height + text: qsTr("Desktop") + } + AudioControls.AudioTabButton { + height: parent.height + text: qsTr("VR") + } + } + property bool showPeaks: true; + function enablePeakValues() { Audio.devices.input.peakValuesEnabled = true; Audio.devices.input.peakValuesEnabledChanged.connect(function(enabled) { @@ -45,6 +79,7 @@ Rectangle { } }); } + function disablePeakValues() { root.showPeaks = false; Audio.devices.input.peakValuesEnabled = false; @@ -55,29 +90,32 @@ Rectangle { onVisibleChanged: visible ? enablePeakValues() : disablePeakValues(); Column { - y: 16; // padding does not work - spacing: 16; + spacing: 12; + anchors.top: bar.bottom + anchors.bottom: parent.bottom + anchors.bottomMargin: 5 width: parent.width; + Separator { } + RalewayRegular { - x: 16; // padding does not work + x: margins.paddings + muteMic.boxSize + muteMic.spacing; size: 16; color: "white"; - text: root.title; - - visible: root.showTitle(); + text: qsTr("Input Device Settings") } - Separator { visible: root.showTitle() } - ColumnLayout { - x: 16; // padding does not work + x: margins.paddings; spacing: 16; + width: parent.width; // mute is in its own row RowLayout { AudioControls.CheckBox { + id: muteMic text: qsTr("Mute microphone"); + spacing: margins.sizeCheckBox - boxSize isRedCheck: true; checked: Audio.muted; onClicked: { @@ -88,8 +126,9 @@ Rectangle { } RowLayout { - spacing: 16; + spacing: muteMic.spacing*2; //make it visually distinguish AudioControls.CheckBox { + spacing: muteMic.spacing text: qsTr("Enable noise reduction"); checked: Audio.noiseReduction; onClicked: { @@ -98,24 +137,33 @@ Rectangle { } } AudioControls.CheckBox { + spacing: muteMic.spacing text: qsTr("Show audio level meter"); checked: AvatarInputs.showAudioTools; onClicked: { AvatarInputs.showAudioTools = checked; checked = Qt.binding(function() { return AvatarInputs.showAudioTools; }); // restore binding } + onXChanged: rightMostInputLevelPos = x + width } } } Separator {} - RowLayout { + Item { + x: margins.paddings; + width: parent.width - margins.paddings*2 + height: 36 + HiFiGlyphs { + width: margins.sizeCheckBox text: hifi.glyphs.mic; color: hifi.colors.primaryHighlight; + anchors.left: parent.left + anchors.leftMargin: -size/4 //the glyph has empty space at left about 25% anchors.verticalCenter: parent.verticalCenter; - size: 28; + size: 30; } RalewayRegular { anchors.verticalCenter: parent.verticalCenter; @@ -126,90 +174,114 @@ Rectangle { } ListView { - anchors { left: parent.left; right: parent.right; leftMargin: 70 } - height: 125; - spacing: 0; + id: inputView + width: parent.width - margins.paddings*2 + x: margins.paddings + height: Math.min(150, contentHeight); + spacing: 4; snapMode: ListView.SnapToItem; clip: true; model: Audio.devices.input; delegate: Item { - width: parent.width; - height: 36; - + width: rightMostInputLevelPos + height: margins.sizeCheckBox > checkBoxInput.implicitHeight ? + margins.sizeCheckBox : checkBoxInput.implicitHeight + AudioControls.CheckBox { - id: checkbox - anchors.verticalCenter: parent.verticalCenter + id: checkBoxInput anchors.left: parent.left - text: display; - wrap: false; - checked: selected; - enabled: false; + spacing: margins.sizeCheckBox - boxSize + anchors.verticalCenter: parent.verticalCenter + width: parent.width - inputLevel.width + clip: true + checkable: !checked + checked: bar.currentIndex === 0 ? selectedDesktop : selectedHMD; + boxSize: margins.sizeCheckBox / 2 + isRound: true + text: devicename + onPressed: { + if (!checked) { + Audio.setInputDevice(info, bar.currentIndex === 1); + } + } } - - MouseArea { - anchors.fill: checkbox - onClicked: Audio.setInputDevice(info); - } - InputPeak { - id: inputPeak; - visible: Audio.devices.input.peakValuesAvailable; + id: inputLevel + anchors.right: parent.right peak: model.peak; anchors.verticalCenter: parent.verticalCenter - anchors.right: parent.right - anchors.rightMargin: 30 + visible: (bar.currentIndex === 1 && selectedHMD && isVR) || + (bar.currentIndex === 0 && selectedDesktop && !isVR) && + Audio.devices.input.peakValuesAvailable; } } } Separator {} - RowLayout { - Column { - RowLayout { - HiFiGlyphs { - text: hifi.glyphs.unmuted; - color: hifi.colors.primaryHighlight; - anchors.verticalCenter: parent.verticalCenter; - size: 36; - } - RalewayRegular { - anchors.verticalCenter: parent.verticalCenter; - size: 16; - color: hifi.colors.lightGrayText; - text: qsTr("CHOOSE OUTPUT DEVICE"); - } - } + Item { + x: margins.paddings; + width: parent.width - margins.paddings*2 + height: 36 - PlaySampleSound { anchors { left: parent.left; leftMargin: 60 }} + HiFiGlyphs { + anchors.left: parent.left + anchors.leftMargin: -size/4 //the glyph has empty space at left about 25% + anchors.verticalCenter: parent.verticalCenter; + width: margins.sizeCheckBox + text: hifi.glyphs.unmuted; + color: hifi.colors.primaryHighlight; + size: 36; + } + + RalewayRegular { + width: margins.sizeText + margins.sizeLevel + anchors.left: parent.left + anchors.leftMargin: margins.sizeCheckBox + anchors.verticalCenter: parent.verticalCenter; + size: 16; + color: hifi.colors.lightGrayText; + text: qsTr("CHOOSE OUTPUT DEVICE"); } } ListView { - anchors { left: parent.left; right: parent.right; leftMargin: 70 } - height: Math.min(250, contentHeight); - spacing: 0; + id: outputView + width: parent.width - margins.paddings*2 + x: margins.paddings + height: Math.min(360 - inputView.height, contentHeight); + spacing: 4; snapMode: ListView.SnapToItem; clip: true; model: Audio.devices.output; delegate: Item { - width: parent.width; - height: 36; + width: rightMostInputLevelPos + height: margins.sizeCheckBox > checkBoxOutput.implicitHeight ? + margins.sizeCheckBox : checkBoxOutput.implicitHeight AudioControls.CheckBox { - id: checkbox - anchors.verticalCenter: parent.verticalCenter - anchors.left: parent.left - text: display; - checked: selected; - enabled: false; - } - - MouseArea { - anchors.fill: checkbox - onClicked: Audio.setOutputDevice(info); + id: checkBoxOutput + width: parent.width + spacing: margins.sizeCheckBox - boxSize + boxSize: margins.sizeCheckBox / 2 + isRound: true + checked: bar.currentIndex === 0 ? selectedDesktop : selectedHMD; + checkable: !checked + text: devicename + onPressed: { + if (!checked) { + Audio.setOutputDevice(info, bar.currentIndex === 1); + } + } } } } + PlaySampleSound { + x: margins.paddings + + visible: (bar.currentIndex === 1 && isVR) || + (bar.currentIndex === 0 && !isVR); + anchors { left: parent.left; leftMargin: margins.paddings } + } } } diff --git a/interface/resources/qml/hifi/audio/AudioTabButton.qml b/interface/resources/qml/hifi/audio/AudioTabButton.qml new file mode 100644 index 0000000000..3a3ed90f5e --- /dev/null +++ b/interface/resources/qml/hifi/audio/AudioTabButton.qml @@ -0,0 +1,35 @@ +// +// AudioTabButton.qml +// qml/hifi/audio +// +// Created by Vlad Stelmahovsky on 8/16/2017 +// Copyright 2017 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 +// + +import QtQuick 2.7 +import QtQuick.Controls 2.2 +import "../../controls-uit" as HifiControls +import "../../styles-uit" + +TabButton { + id: control + font.pixelSize: height / 2 + + HifiConstants { id: hifi; } + + contentItem: RalewaySemiBold { + text: control.text + font: control.font + color: "white" + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + elide: Text.ElideRight + } + + background: Rectangle { + color: control.checked ? hifi.colors.baseGray : "black" + } +} diff --git a/interface/resources/qml/hifi/audio/CheckBox.qml b/interface/resources/qml/hifi/audio/CheckBox.qml index 1f632ac479..3a954d4004 100644 --- a/interface/resources/qml/hifi/audio/CheckBox.qml +++ b/interface/resources/qml/hifi/audio/CheckBox.qml @@ -9,10 +9,10 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -import QtQuick 2.5 +import QtQuick 2.7 import "../../controls-uit" as HifiControls -HifiControls.CheckBox { +HifiControls.CheckBoxQQC2 { color: "white" } diff --git a/interface/resources/qml/styles-uit/HiFiGlyphs.qml b/interface/resources/qml/styles-uit/HiFiGlyphs.qml index cbd6fa1d68..f78d6c6f59 100644 --- a/interface/resources/qml/styles-uit/HiFiGlyphs.qml +++ b/interface/resources/qml/styles-uit/HiFiGlyphs.qml @@ -9,8 +9,6 @@ // import QtQuick 2.5 -import QtQuick.Controls 1.4 -import QtQuick.Controls.Styles 1.4 Text { id: root diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 5700bb6a72..e1c3af1939 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2057,6 +2057,7 @@ void Application::cleanupBeforeQuit() { // this must happen after QML, as there are unexplained audio crashes originating in qtwebengine DependencyManager::destroy(); DependencyManager::destroy(); + DependencyManager::destroy(); qCDebug(interfaceapp) << "Application::cleanupBeforeQuit() complete"; } @@ -5159,12 +5160,6 @@ void Application::update(float deltaTime) { } } - { - PROFILE_RANGE_EX(app, "Overlays", 0xffff0000, (uint64_t)getActiveDisplayPlugin()->presentCount()); - PerformanceTimer perfTimer("overlays"); - _overlays.update(deltaTime); - } - { PROFILE_RANGE(app, "RayPickManager"); _rayPickManager.update(); @@ -5175,6 +5170,12 @@ void Application::update(float deltaTime) { _laserPointerManager.update(); } + { + PROFILE_RANGE_EX(app, "Overlays", 0xffff0000, (uint64_t)getActiveDisplayPlugin()->presentCount()); + PerformanceTimer perfTimer("overlays"); + _overlays.update(deltaTime); + } + // Update _viewFrustum with latest camera and view frustum data... // NOTE: we get this from the view frustum, to make it simpler, since the // loadViewFrumstum() method will get the correct details from the camera diff --git a/interface/src/raypick/LaserPointer.cpp b/interface/src/raypick/LaserPointer.cpp index 9d64bcc476..b696afa8e5 100644 --- a/interface/src/raypick/LaserPointer.cpp +++ b/interface/src/raypick/LaserPointer.cpp @@ -86,7 +86,9 @@ void LaserPointer::editRenderState(const std::string& state, const QVariant& sta void LaserPointer::updateRenderStateOverlay(const OverlayID& id, const QVariant& props) { if (!id.isNull() && props.isValid()) { - qApp->getOverlays().editOverlay(id, props); + QVariantMap propMap = props.toMap(); + propMap.remove("visible"); + qApp->getOverlays().editOverlay(id, propMap); } } diff --git a/interface/src/raypick/LaserPointerScriptingInterface.cpp b/interface/src/raypick/LaserPointerScriptingInterface.cpp index a976a00893..d5e435f490 100644 --- a/interface/src/raypick/LaserPointerScriptingInterface.cpp +++ b/interface/src/raypick/LaserPointerScriptingInterface.cpp @@ -95,6 +95,7 @@ const RenderState LaserPointerScriptingInterface::buildRenderState(const QVarian if (propMap["start"].isValid()) { QVariantMap startMap = propMap["start"].toMap(); if (startMap["type"].isValid()) { + startMap.remove("visible"); startID = qApp->getOverlays().addOverlay(startMap["type"].toString(), startMap); } } @@ -104,6 +105,7 @@ const RenderState LaserPointerScriptingInterface::buildRenderState(const QVarian QVariantMap pathMap = propMap["path"].toMap(); // right now paths must be line3ds if (pathMap["type"].isValid() && pathMap["type"].toString() == "line3d") { + pathMap.remove("visible"); pathID = qApp->getOverlays().addOverlay(pathMap["type"].toString(), pathMap); } } @@ -112,6 +114,7 @@ const RenderState LaserPointerScriptingInterface::buildRenderState(const QVarian if (propMap["end"].isValid()) { QVariantMap endMap = propMap["end"].toMap(); if (endMap["type"].isValid()) { + endMap.remove("visible"); endID = qApp->getOverlays().addOverlay(endMap["type"].toString(), endMap); } } diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp index 9719c23885..f9c1a95fb5 100644 --- a/interface/src/scripting/Audio.cpp +++ b/interface/src/scripting/Audio.cpp @@ -135,10 +135,10 @@ void Audio::setReverbOptions(const AudioEffectOptions* options) { DependencyManager::get()->setReverbOptions(options); } -void Audio::setInputDevice(const QAudioDeviceInfo& device) { - _devices.chooseInputDevice(device); +void Audio::setInputDevice(const QAudioDeviceInfo& device, bool isHMD) { + _devices.chooseInputDevice(device, isHMD); } -void Audio::setOutputDevice(const QAudioDeviceInfo& device) { - _devices.chooseOutputDevice(device); +void Audio::setOutputDevice(const QAudioDeviceInfo& device, bool isHMD) { + _devices.chooseOutputDevice(device, isHMD); } diff --git a/interface/src/scripting/Audio.h b/interface/src/scripting/Audio.h index bd40de4303..abd2312cf0 100644 --- a/interface/src/scripting/Audio.h +++ b/interface/src/scripting/Audio.h @@ -50,8 +50,8 @@ public: void showMicMeter(bool show); void setInputVolume(float volume); - Q_INVOKABLE void setInputDevice(const QAudioDeviceInfo& device); - Q_INVOKABLE void setOutputDevice(const QAudioDeviceInfo& device); + Q_INVOKABLE void setInputDevice(const QAudioDeviceInfo& device, bool isHMD); + Q_INVOKABLE void setOutputDevice(const QAudioDeviceInfo& device, bool isHMD); Q_INVOKABLE void setReverb(bool enable); Q_INVOKABLE void setReverbOptions(const AudioEffectOptions* options); diff --git a/interface/src/scripting/AudioDevices.cpp b/interface/src/scripting/AudioDevices.cpp index e5cc43f9df..a130b46877 100644 --- a/interface/src/scripting/AudioDevices.cpp +++ b/interface/src/scripting/AudioDevices.cpp @@ -38,15 +38,17 @@ Setting::Handle& getSetting(bool contextIsHMD, QAudio::Mode mode) { } enum AudioDeviceRole { - DisplayRole = Qt::DisplayRole, - CheckStateRole = Qt::CheckStateRole, - PeakRole = Qt::UserRole, - InfoRole = Qt::UserRole + 1 + DeviceNameRole = Qt::UserRole, + SelectedDesktopRole, + SelectedHMDRole, + PeakRole, + InfoRole }; QHash AudioDeviceList::_roles { - { DisplayRole, "display" }, - { CheckStateRole, "selected" }, + { DeviceNameRole, "devicename" }, + { SelectedDesktopRole, "selectedDesktop" }, + { SelectedHMDRole, "selectedHMD" }, { PeakRole, "peak" }, { InfoRole, "info" } }; @@ -68,15 +70,64 @@ static QString getTargetDevice(bool hmd, QAudio::Mode mode) { Qt::ItemFlags AudioDeviceList::_flags { Qt::ItemIsSelectable | Qt::ItemIsEnabled }; +AudioDeviceList::AudioDeviceList(QAudio::Mode mode) : _mode(mode) { + auto& setting1 = getSetting(true, QAudio::AudioInput); + if (setting1.isSet()) { + qDebug() << "Device name in settings for HMD, Input" << setting1.get(); + } else { + qDebug() << "Device name in settings for HMD, Input not set"; + } + + auto& setting2 = getSetting(true, QAudio::AudioOutput); + if (setting2.isSet()) { + qDebug() << "Device name in settings for HMD, Output" << setting2.get(); + } else { + qDebug() << "Device name in settings for HMD, Output not set"; + } + + auto& setting3 = getSetting(false, QAudio::AudioInput); + if (setting3.isSet()) { + qDebug() << "Device name in settings for Desktop, Input" << setting3.get(); + } else { + qDebug() << "Device name in settings for Desktop, Input not set"; + } + + auto& setting4 = getSetting(false, QAudio::AudioOutput); + if (setting4.isSet()) { + qDebug() << "Device name in settings for Desktop, Output" << setting4.get(); + } else { + qDebug() << "Device name in settings for Desktop, Output not set"; + } +} + +AudioDeviceList::~AudioDeviceList() { + //save all selected devices + auto& settingHMD = getSetting(true, _mode); + auto& settingDesktop = getSetting(false, _mode); + // store the selected device + foreach(std::shared_ptr adevice, _devices) { + if (adevice->selectedDesktop) { + qDebug() << "Saving Desktop for" << _mode << "name" << adevice->info.deviceName(); + settingDesktop.set(adevice->info.deviceName()); + } + if (adevice->selectedHMD) { + qDebug() << "Saving HMD for" << _mode << "name" << adevice->info.deviceName(); + settingHMD.set(adevice->info.deviceName()); + } + } +} + QVariant AudioDeviceList::data(const QModelIndex& index, int role) const { if (!index.isValid() || index.row() >= rowCount()) { return QVariant(); } - if (role == DisplayRole) { + if (role == DeviceNameRole) { return _devices.at(index.row())->display; - } else if (role == CheckStateRole) { - return _devices.at(index.row())->selected; + } else if (role == SelectedDesktopRole) { + return _devices.at(index.row())->selectedDesktop; + } else if (role == SelectedHMDRole) { + return _devices.at(index.row())->selectedHMD; } else if (role == InfoRole) { return QVariant::fromValue(_devices.at(index.row())->info); } else { @@ -130,37 +181,48 @@ void AudioDeviceList::resetDevice(bool contextIsHMD) { #endif } -void AudioDeviceList::onDeviceChanged(const QAudioDeviceInfo& device) { - auto oldDevice = _selectedDevice; - _selectedDevice = device; +void AudioDeviceList::onDeviceChanged(const QAudioDeviceInfo& device, bool isHMD) { + auto oldDevice = isHMD ? _selectedHMDDevice : _selectedDesktopDevice; + QAudioDeviceInfo& selectedDevice = isHMD ? _selectedHMDDevice : _selectedDesktopDevice; + selectedDevice = device; - for (auto i = 0; i < rowCount(); ++i) { - AudioDevice& device = *_devices[i]; - - if (device.selected && device.info != _selectedDevice) { - device.selected = false; - } else if (device.info == _selectedDevice) { - device.selected = true; + for (auto i = 0; i < _devices.size(); ++i) { + std::shared_ptr device = _devices[i]; + bool &isSelected = isHMD ? device->selectedHMD : device->selectedDesktop; + if (isSelected && device->info != selectedDevice) { + isSelected = false; + } else if (device->info == selectedDevice) { + isSelected = true; } } - emit deviceChanged(_selectedDevice); + emit deviceChanged(selectedDevice); emit dataChanged(createIndex(0, 0), createIndex(rowCount() - 1, 0)); } -void AudioDeviceList::onDevicesChanged(const QList& devices) { +void AudioDeviceList::onDevicesChanged(const QList& devices, bool isHMD) { + QAudioDeviceInfo& selectedDevice = isHMD ? _selectedHMDDevice : _selectedDesktopDevice; + + const QString& savedDeviceName = isHMD ? _hmdSavedDeviceName : _desktopSavedDeviceName; beginResetModel(); _devices.clear(); foreach(const QAudioDeviceInfo& deviceInfo, devices) { AudioDevice device; + bool &isSelected = isHMD ? device.selectedHMD : device.selectedDesktop; device.info = deviceInfo; device.display = device.info.deviceName() .replace("High Definition", "HD") .remove("Device") .replace(" )", ")"); - device.selected = (device.info == _selectedDevice); + if (!selectedDevice.isNull()) { + isSelected = (device.info == selectedDevice); + } else { + //no selected device for context. fallback to saved + isSelected = (device.info.deviceName() == savedDeviceName); + } + qDebug() << "adding audio device:" << device.display << device.selectedDesktop << device.selectedHMD << _mode; _devices.push_back(newDevice(device)); } @@ -203,22 +265,32 @@ AudioDevices::AudioDevices(bool& contextIsHMD) : _contextIsHMD(contextIsHMD) { connect(client.data(), &AudioClient::devicesChanged, this, &AudioDevices::onDevicesChanged, Qt::QueuedConnection); connect(client.data(), &AudioClient::peakValueListChanged, &_inputs, &AudioInputDeviceList::onPeakValueListChanged, Qt::QueuedConnection); + _inputs.onDeviceChanged(client->getActiveAudioDevice(QAudio::AudioInput), contextIsHMD); + _outputs.onDeviceChanged(client->getActiveAudioDevice(QAudio::AudioOutput), contextIsHMD); + // connections are made after client is initialized, so we must also fetch the devices - _inputs.onDeviceChanged(client->getActiveAudioDevice(QAudio::AudioInput)); - _outputs.onDeviceChanged(client->getActiveAudioDevice(QAudio::AudioOutput)); - _inputs.onDevicesChanged(client->getAudioDevices(QAudio::AudioInput)); - _outputs.onDevicesChanged(client->getAudioDevices(QAudio::AudioOutput)); + const QList& devicesInput = client->getAudioDevices(QAudio::AudioInput); + const QList& devicesOutput = client->getAudioDevices(QAudio::AudioOutput); + //setup HMD devices + _inputs.onDevicesChanged(devicesInput, true); + _outputs.onDevicesChanged(devicesOutput, true); + //setup Desktop devices + _inputs.onDevicesChanged(devicesInput, false); + _outputs.onDevicesChanged(devicesOutput, false); } +AudioDevices::~AudioDevices() {} + void AudioDevices::onContextChanged(const QString& context) { _inputs.resetDevice(_contextIsHMD); _outputs.resetDevice(_contextIsHMD); } -void AudioDevices::onDeviceSelected(QAudio::Mode mode, const QAudioDeviceInfo& device, const QAudioDeviceInfo& previousDevice) { +void AudioDevices::onDeviceSelected(QAudio::Mode mode, const QAudioDeviceInfo& device, + const QAudioDeviceInfo& previousDevice, bool isHMD) { QString deviceName = device.isNull() ? QString() : device.deviceName(); - auto& setting = getSetting(_contextIsHMD, mode); + auto& setting = getSetting(isHMD, mode); // check for a previous device auto wasDefault = setting.get().isNull(); @@ -254,42 +326,94 @@ void AudioDevices::onDeviceSelected(QAudio::Mode mode, const QAudioDeviceInfo& d void AudioDevices::onDeviceChanged(QAudio::Mode mode, const QAudioDeviceInfo& device) { if (mode == QAudio::AudioInput) { if (_requestedInputDevice == device) { - onDeviceSelected(QAudio::AudioInput, device, _inputs._selectedDevice); + onDeviceSelected(QAudio::AudioInput, device, + _contextIsHMD ? _inputs._selectedHMDDevice : _inputs._selectedDesktopDevice, + _contextIsHMD); _requestedInputDevice = QAudioDeviceInfo(); } - _inputs.onDeviceChanged(device); + _inputs.onDeviceChanged(device, _contextIsHMD); } else { // if (mode == QAudio::AudioOutput) if (_requestedOutputDevice == device) { - onDeviceSelected(QAudio::AudioOutput, device, _outputs._selectedDevice); + onDeviceSelected(QAudio::AudioOutput, device, + _contextIsHMD ? _outputs._selectedHMDDevice : _outputs._selectedDesktopDevice, + _contextIsHMD); _requestedOutputDevice = QAudioDeviceInfo(); } - _outputs.onDeviceChanged(device); + _outputs.onDeviceChanged(device, _contextIsHMD); } } void AudioDevices::onDevicesChanged(QAudio::Mode mode, const QList& devices) { static std::once_flag once; + std::call_once(once, [&] { + //readout settings + auto client = DependencyManager::get(); + + _inputs._hmdSavedDeviceName = getTargetDevice(true, QAudio::AudioInput); + _inputs._desktopSavedDeviceName = getTargetDevice(false, QAudio::AudioInput); + + //fallback to default device + if (_inputs._desktopSavedDeviceName.isEmpty()) { + _inputs._desktopSavedDeviceName = client->getActiveAudioDevice(QAudio::AudioInput).deviceName(); + } + //fallback to desktop device + if (_inputs._hmdSavedDeviceName.isEmpty()) { + _inputs._hmdSavedDeviceName = _inputs._desktopSavedDeviceName; + } + + _outputs._hmdSavedDeviceName = getTargetDevice(true, QAudio::AudioOutput); + _outputs._desktopSavedDeviceName = getTargetDevice(false, QAudio::AudioOutput); + + if (_outputs._desktopSavedDeviceName.isEmpty()) { + _outputs._desktopSavedDeviceName = client->getActiveAudioDevice(QAudio::AudioOutput).deviceName(); + } + if (_outputs._hmdSavedDeviceName.isEmpty()) { + _outputs._hmdSavedDeviceName = _outputs._desktopSavedDeviceName; + } + onContextChanged(QString()); + }); + + //set devices for both contexts if (mode == QAudio::AudioInput) { - _inputs.onDevicesChanged(devices); + _inputs.onDevicesChanged(devices, _contextIsHMD); + _inputs.onDevicesChanged(devices, !_contextIsHMD); } else { // if (mode == QAudio::AudioOutput) - _outputs.onDevicesChanged(devices); + _outputs.onDevicesChanged(devices, _contextIsHMD); + _outputs.onDevicesChanged(devices, !_contextIsHMD); } - std::call_once(once, [&] { onContextChanged(QString()); }); } -void AudioDevices::chooseInputDevice(const QAudioDeviceInfo& device) { - auto client = DependencyManager::get(); - _requestedInputDevice = device; - QMetaObject::invokeMethod(client.data(), "switchAudioDevice", - Q_ARG(QAudio::Mode, QAudio::AudioInput), - Q_ARG(const QAudioDeviceInfo&, device)); +void AudioDevices::chooseInputDevice(const QAudioDeviceInfo& device, bool isHMD) { + //check if current context equals device to change + if (_contextIsHMD == isHMD) { + auto client = DependencyManager::get(); + _requestedInputDevice = device; + QMetaObject::invokeMethod(client.data(), "switchAudioDevice", + Q_ARG(QAudio::Mode, QAudio::AudioInput), + Q_ARG(const QAudioDeviceInfo&, device)); + } else { + //context is different. just save device in settings + onDeviceSelected(QAudio::AudioInput, device, + isHMD ? _inputs._selectedHMDDevice : _inputs._selectedDesktopDevice, + isHMD); + _inputs.onDeviceChanged(device, isHMD); + } } -void AudioDevices::chooseOutputDevice(const QAudioDeviceInfo& device) { - auto client = DependencyManager::get(); - _requestedOutputDevice = device; - QMetaObject::invokeMethod(client.data(), "switchAudioDevice", - Q_ARG(QAudio::Mode, QAudio::AudioOutput), - Q_ARG(const QAudioDeviceInfo&, device)); +void AudioDevices::chooseOutputDevice(const QAudioDeviceInfo& device, bool isHMD) { + //check if current context equals device to change + if (_contextIsHMD == isHMD) { + auto client = DependencyManager::get(); + _requestedOutputDevice = device; + QMetaObject::invokeMethod(client.data(), "switchAudioDevice", + Q_ARG(QAudio::Mode, QAudio::AudioOutput), + Q_ARG(const QAudioDeviceInfo&, device)); + } else { + //context is different. just save device in settings + onDeviceSelected(QAudio::AudioOutput, device, + isHMD ? _outputs._selectedHMDDevice : _outputs._selectedDesktopDevice, + isHMD); + _outputs.onDeviceChanged(device, isHMD); + } } diff --git a/interface/src/scripting/AudioDevices.h b/interface/src/scripting/AudioDevices.h index 4c1820e9c8..36f1653e38 100644 --- a/interface/src/scripting/AudioDevices.h +++ b/interface/src/scripting/AudioDevices.h @@ -25,15 +25,16 @@ class AudioDevice { public: QAudioDeviceInfo info; QString display; - bool selected { false }; + bool selectedDesktop { false }; + bool selectedHMD { false }; }; class AudioDeviceList : public QAbstractListModel { Q_OBJECT public: - AudioDeviceList(QAudio::Mode mode = QAudio::AudioOutput) : _mode(mode) {} - ~AudioDeviceList() = default; + AudioDeviceList(QAudio::Mode mode = QAudio::AudioOutput); + virtual ~AudioDeviceList(); virtual std::shared_ptr newDevice(const AudioDevice& device) { return std::make_shared(device); } @@ -52,8 +53,8 @@ signals: void deviceChanged(const QAudioDeviceInfo& device); protected slots: - void onDeviceChanged(const QAudioDeviceInfo& device); - void onDevicesChanged(const QList& devices); + void onDeviceChanged(const QAudioDeviceInfo& device, bool isHMD); + void onDevicesChanged(const QList& devices, bool isHMD); protected: friend class AudioDevices; @@ -61,8 +62,11 @@ protected: static QHash _roles; static Qt::ItemFlags _flags; const QAudio::Mode _mode; - QAudioDeviceInfo _selectedDevice; + QAudioDeviceInfo _selectedDesktopDevice; + QAudioDeviceInfo _selectedHMDDevice; QList> _devices; + QString _hmdSavedDeviceName; + QString _desktopSavedDeviceName; }; class AudioInputDevice : public AudioDevice { @@ -102,7 +106,6 @@ protected: void setPeakValuesEnabled(bool enable); bool _peakValuesEnabled { false }; }; - class Audio; class AudioDevices : public QObject { @@ -112,15 +115,18 @@ class AudioDevices : public QObject { public: AudioDevices(bool& contextIsHMD); - void chooseInputDevice(const QAudioDeviceInfo& device); - void chooseOutputDevice(const QAudioDeviceInfo& device); + virtual ~AudioDevices(); + + void chooseInputDevice(const QAudioDeviceInfo& device, bool isHMD); + void chooseOutputDevice(const QAudioDeviceInfo& device, bool isHMD); signals: void nop(); private slots: void onContextChanged(const QString& context); - void onDeviceSelected(QAudio::Mode mode, const QAudioDeviceInfo& device, const QAudioDeviceInfo& previousDevice); + void onDeviceSelected(QAudio::Mode mode, const QAudioDeviceInfo& device, + const QAudioDeviceInfo& previousDevice, bool isHMD); void onDeviceChanged(QAudio::Mode mode, const QAudioDeviceInfo& device); void onDevicesChanged(QAudio::Mode mode, const QList& devices); diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 4e7cc08919..cbfcd473dc 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -1725,14 +1725,6 @@ int AudioClient::setOutputBufferSize(int numFrames, bool persist) { if (persist) { _outputBufferSizeFrames.set(numFrames); } - - if (_audioOutput) { - // The buffer size can't be adjusted after QAudioOutput::start() has been called, so - // recreate the device by switching to the default. - QAudioDeviceInfo outputDeviceInfo = defaultAudioDeviceForMode(QAudio::AudioOutput); - qCDebug(audioclient) << __FUNCTION__ << "about to send changeDevice signal outputDeviceInfo: [" << outputDeviceInfo.deviceName() << "]"; - emit changeDevice(outputDeviceInfo); // On correct thread, please, as setOutputBufferSize can be called from main thread. - } } return numFrames; } diff --git a/scripts/system/controllers/controllerDispatcher.js b/scripts/system/controllers/controllerDispatcher.js index 63657e9b6f..22987245a4 100644 --- a/scripts/system/controllers/controllerDispatcher.js +++ b/scripts/system/controllers/controllerDispatcher.js @@ -140,10 +140,8 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); }; this.setIgnoreTablet = function() { - if (HMD.tabletID !== _this.tabletID) { - RayPick.setIgnoreOverlays(_this.leftControllerRayPick, [HMD.tabletID]); - RayPick.setIgnoreOverlays(_this.rightControllerRayPick, [HMD.tabletID]); - } + RayPick.setIgnoreOverlays(_this.leftControllerRayPick, [HMD.tabletID]); + RayPick.setIgnoreOverlays(_this.rightControllerRayPick, [HMD.tabletID]); }; this.update = function () { diff --git a/scripts/system/controllers/controllerModules/overlayLaserInput.js b/scripts/system/controllers/controllerModules/overlayLaserInput.js index 218122e876..d67672ca7c 100644 --- a/scripts/system/controllers/controllerModules/overlayLaserInput.js +++ b/scripts/system/controllers/controllerModules/overlayLaserInput.js @@ -16,6 +16,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); Script.include("/~/system/libraries/controllers.js"); (function() { + var TouchEventUtils = Script.require("/~/system/libraries/touchEventUtils.js"); var halfPath = { type: "line3d", color: COLORS_GRAB_SEARCHING_HALF_SQUEEZE, @@ -88,186 +89,6 @@ Script.include("/~/system/libraries/controllers.js"); var HAPTIC_STYLUS_STRENGTH = 1.0; var HAPTIC_STYLUS_DURATION = 20.0; - function laserTargetHasKeyboardFocus(laserTarget) { - if (laserTarget && laserTarget !== NULL_UUID) { - return Overlays.keyboardFocusOverlay === laserTarget; - } - } - - function setKeyboardFocusOnLaserTarget(laserTarget) { - if (laserTarget && laserTarget !== NULL_UUID) { - Overlays.keyboardFocusOverlay = laserTarget; - Entities.keyboardFocusEntity = NULL_UUID; - } - } - - function sendHoverEnterEventToLaserTarget(hand, laserTarget) { - if (!laserTarget) { - return; - } - var pointerEvent = { - type: "Move", - id: hand + 1, // 0 is reserved for hardware mouse - pos2D: laserTarget.position2D, - pos3D: laserTarget.position, - normal: laserTarget.normal, - direction: Vec3.subtract(ZERO_VEC, laserTarget.normal), - button: "None" - }; - - if (laserTarget.overlayID && laserTarget.overlayID !== NULL_UUID) { - Overlays.sendHoverEnterOverlay(laserTarget.overlayID, pointerEvent); - } - } - - function sendHoverOverEventToLaserTarget(hand, laserTarget) { - - if (!laserTarget) { - return; - } - var pointerEvent = { - type: "Move", - id: hand + 1, // 0 is reserved for hardware mouse - pos2D: laserTarget.position2D, - pos3D: laserTarget.position, - normal: laserTarget.normal, - direction: Vec3.subtract(ZERO_VEC, laserTarget.normal), - button: "None" - }; - - if (laserTarget.overlayID && laserTarget.overlayID !== NULL_UUID) { - Overlays.sendMouseMoveOnOverlay(laserTarget.overlayID, pointerEvent); - Overlays.sendHoverOverOverlay(laserTarget.overlayID, pointerEvent); - } - } - - function sendTouchStartEventToLaserTarget(hand, laserTarget) { - if (!laserTarget) { - return; - } - - var pointerEvent = { - type: "Press", - id: hand + 1, // 0 is reserved for hardware mouse - pos2D: laserTarget.position2D, - pos3D: laserTarget.position, - normal: laserTarget.normal, - direction: Vec3.subtract(ZERO_VEC, laserTarget.normal), - button: "Primary", - isPrimaryHeld: true - }; - - if (laserTarget.overlayID && laserTarget.overlayID !== NULL_UUID) { - Overlays.sendMousePressOnOverlay(laserTarget.overlayID, pointerEvent); - } - } - - function sendTouchEndEventToLaserTarget(hand, laserTarget) { - if (!laserTarget) { - return; - } - var pointerEvent = { - type: "Release", - id: hand + 1, // 0 is reserved for hardware mouse - pos2D: laserTarget.position2D, - pos3D: laserTarget.position, - normal: laserTarget.normal, - direction: Vec3.subtract(ZERO_VEC, laserTarget.normal), - button: "Primary" - }; - - if (laserTarget.overlayID && laserTarget.overlayID !== NULL_UUID) { - Overlays.sendMouseReleaseOnOverlay(laserTarget.overlayID, pointerEvent); - Overlays.sendMouseReleaseOnOverlay(laserTarget.overlayID, pointerEvent); - } - } - - function sendTouchMoveEventToLaserTarget(hand, laserTarget) { - if (!laserTarget) { - return; - } - var pointerEvent = { - type: "Move", - id: hand + 1, // 0 is reserved for hardware mouse - pos2D: laserTarget.position2D, - pos3D: laserTarget.position, - normal: laserTarget.normal, - direction: Vec3.subtract(ZERO_VEC, laserTarget.normal), - button: "Primary", - isPrimaryHeld: true - }; - - if (laserTarget.overlayID && laserTarget.overlayID !== NULL_UUID) { - Overlays.sendMouseReleaseOnOverlay(laserTarget.overlayID, pointerEvent); - } - } - - // will return undefined if overlayID does not exist. - function calculateLaserTargetFromOverlay(worldPos, overlayID) { - var overlayPosition = Overlays.getProperty(overlayID, "position"); - if (overlayPosition === undefined) { - return null; - } - - // project stylusTip onto overlay plane. - var overlayRotation = Overlays.getProperty(overlayID, "rotation"); - if (overlayRotation === undefined) { - return null; - } - var normal = Vec3.multiplyQbyV(overlayRotation, {x: 0, y: 0, z: 1}); - var distance = Vec3.dot(Vec3.subtract(worldPos, overlayPosition), normal); - - // calclulate normalized position - var invRot = Quat.inverse(overlayRotation); - var localPos = Vec3.multiplyQbyV(invRot, Vec3.subtract(worldPos, overlayPosition)); - var dpi = Overlays.getProperty(overlayID, "dpi"); - - var dimensions; - if (dpi) { - // Calculate physical dimensions for web3d overlay from resolution and dpi; "dimensions" property - // is used as a scale. - var resolution = Overlays.getProperty(overlayID, "resolution"); - if (resolution === undefined) { - return null; - } - resolution.z = 1;// Circumvent divide-by-zero. - var scale = Overlays.getProperty(overlayID, "dimensions"); - if (scale === undefined) { - return null; - } - scale.z = 0.01;// overlay dimensions are 2D, not 3D. - dimensions = Vec3.multiplyVbyV(Vec3.multiply(resolution, INCHES_TO_METERS / dpi), scale); - } else { - dimensions = Overlays.getProperty(overlayID, "dimensions"); - if (dimensions === undefined) { - return null; - } - if (!dimensions.z) { - dimensions.z = 0.01;// sometimes overlay dimensions are 2D, not 3D. - } - } - var invDimensions = { x: 1 / dimensions.x, y: 1 / dimensions.y, z: 1 / dimensions.z }; - var normalizedPosition = Vec3.sum(Vec3.multiplyVbyV(localPos, invDimensions), DEFAULT_REGISTRATION_POINT); - - // 2D position on overlay plane in meters, relative to the bounding box upper-left hand corner. - var position2D = { - x: normalizedPosition.x * dimensions.x, - y: (1 - normalizedPosition.y) * dimensions.y // flip y-axis - }; - - return { - entityID: null, - overlayID: overlayID, - distance: distance, - position: worldPos, - position2D: position2D, - normal: normal, - normalizedPosition: normalizedPosition, - dimensions: dimensions, - valid: true - }; - } - function distance2D(a, b) { var dx = (a.x - b.x); var dy = (a.y - b.y); @@ -277,16 +98,11 @@ Script.include("/~/system/libraries/controllers.js"); function OverlayLaserInput(hand) { this.hand = hand; this.active = false; - this.previousLaserClikcedTarget = false; + this.previousLaserClickedTarget = false; this.laserPressingTarget = false; - this.tabletScreenID = null; this.mode = "none"; - this.laserTargetID = null; this.laserTarget = null; this.pressEnterLaserTarget = null; - this.hover = false; - this.target = null; - this.lastValidTargetID = this.tabletTargetID; this.parameters = makeDispatcherModuleParameters( @@ -307,23 +123,51 @@ Script.include("/~/system/libraries/controllers.js"); return (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; }; - this.stealTouchFocus = function(laserTarget) { - this.requestTouchFocus(laserTarget); + this.hasTouchFocus = function(laserTarget) { + return (laserTarget.overlayID === this.hoverOverlay); }; this.requestTouchFocus = function(laserTarget) { - if (laserTarget !== null || laserTarget !== undefined) { - sendHoverEnterEventToLaserTarget(this.hand, this.laserTarget); - this.lastValidTargetID = laserTarget; + if (laserTarget.overlayID && + laserTarget.overlayID !== this.hoverOverlay) { + this.hoverOverlay = laserTarget.overlayID; + TouchEventUtils.sendHoverEnterEventToTouchTarget(this.hand, laserTarget); } }; this.relinquishTouchFocus = function() { // send hover leave event. - var pointerEvent = { type: "Move", id: this.hand + 1 }; - Overlays.sendMouseMoveOnOverlay(this.lastValidTargetID, pointerEvent); - Overlays.sendHoverOverOverlay(this.lastValidTargetID, pointerEvent); - Overlays.sendHoverLeaveOverlay(this.lastValidID, pointerEvent); + if (this.hoverOverlay) { + var pointerEvent = { type: "Move", id: this.hand + 1 }; + Overlays.sendMouseMoveOnOverlay(this.hoverOverlay, pointerEvent); + Overlays.sendHoverOverOverlay(this.hoverOverlay, pointerEvent); + Overlays.sendHoverLeaveOverlay(this.hoverOverlay, pointerEvent); + this.hoverOverlay = null; + } + }; + + this.relinquishStylusTargetTouchFocus = function(laserTarget) { + var stylusModuleNames = ["LeftTabletStylusInput", "RightTabletStylusError"]; + for (var i = 0; i < stylusModuleNames.length; i++) { + var stylusModule = getEnabledModuleByName(stylusModuleNames[i]); + if (stylusModule) { + if (stylusModule.hoverOverlay === laserTarget.overlayID) { + stylusModule.relinquishTouchFocus(); + } + } + } + }; + + this.stealTouchFocus = function(laserTarget) { + if (laserTarget.overlayID === this.getOtherModule().hoverOverlay) { + this.getOtherModule().relinquishTouchFocus(); + } + + // If the focus target we want to request is the same of one of the stylus + // tell the stylus to relinquish it focus on our target + this.relinquishStylusTargetTouchFocus(laserTarget); + + this.requestTouchFocus(laserTarget); }; this.updateLaserPointer = function(controllerData) { @@ -345,38 +189,23 @@ Script.include("/~/system/libraries/controllers.js"); this.processControllerTriggers = function(controllerData) { if (controllerData.triggerClicks[this.hand]) { this.mode = "full"; - this.laserPressingTarget = true; - this.hover = false; } else if (controllerData.triggerValues[this.hand] > TRIGGER_ON_VALUE) { this.mode = "half"; - this.laserPressingTarget = false; - this.hover = true; - this.requestTouchFocus(this.laserTargetID); } else { this.mode = "none"; - this.laserPressingTarget = false; - this.hover = false; - this.relinquishTouchFocus(); - } }; - this.hovering = function() { - if (!laserTargetHasKeyboardFocus(this.laserTagetID)) { - setKeyboardFocusOnLaserTarget(this.laserTargetID); - } - sendHoverOverEventToLaserTarget(this.hand, this.laserTarget); - }; - this.laserPressEnter = function () { - sendTouchStartEventToLaserTarget(this.hand, this.laserTarget); + this.stealTouchFocus(this.laserTarget); + TouchEventUtils.sendTouchStartEventToTouchTarget(this.hand, this.laserTarget); Controller.triggerHapticPulse(HAPTIC_STYLUS_STRENGTH, HAPTIC_STYLUS_DURATION, this.hand); this.touchingEnterTimer = 0; this.pressEnterLaserTarget = this.laserTarget; this.deadspotExpired = false; - var LASER_PRESS_TO_MOVE_DEADSPOT = 0.026; + var LASER_PRESS_TO_MOVE_DEADSPOT = 0.094; this.deadspotRadius = Math.tan(LASER_PRESS_TO_MOVE_DEADSPOT) * this.laserTarget.distance; }; @@ -386,15 +215,15 @@ Script.include("/~/system/libraries/controllers.js"); } // special case to handle home button. - if (this.laserTargetID === HMD.homeButtonID) { - Messages.sendLocalMessage("home", this.laserTargetID); + if (this.laserTarget.overlayID === HMD.homeButtonID) { + Messages.sendLocalMessage("home", this.laserTarget.overlayID); } // send press event if (this.deadspotExpired) { - sendTouchEndEventToLaserTarget(this.hand, this.laserTarget); + TouchEventUtils.sendTouchEndEventToTouchTarget(this.hand, this.laserTarget); } else { - sendTouchEndEventToLaserTarget(this.hand, this.pressEnterLaserTarget); + TouchEventUtils.sendTouchEndEventToTouchTarget(this.hand, this.pressEnterLaserTarget); } }; @@ -402,41 +231,84 @@ Script.include("/~/system/libraries/controllers.js"); this.touchingEnterTimer += dt; if (this.laserTarget) { - var POINTER_PRESS_TO_MOVE_DELAY = 0.33; // seconds - if (this.deadspotExpired || this.touchingEnterTimer > POINTER_PRESS_TO_MOVE_DELAY || - distance2D( this.laserTarget.position2D, - this.pressEnterLaserTarget.position2D) > this.deadspotRadius) { - sendTouchMoveEventToLaserTarget(this.hand, this.laserTarget); - this.deadspotExpired = true; + if (controllerData.triggerClicks[this.hand]) { + var POINTER_PRESS_TO_MOVE_DELAY = 0.33; // seconds + if (this.deadspotExpired || this.touchingEnterTimer > POINTER_PRESS_TO_MOVE_DELAY || + distance2D(this.laserTarget.position2D, + this.pressEnterLaserTarget.position2D) > this.deadspotRadius) { + TouchEventUtils.sendTouchMoveEventToTouchTarget(this.hand, this.laserTarget); + this.deadspotExpired = true; + } + } else { + this.laserPressingTarget = false; } } else { this.laserPressingTarget = false; } }; - this.releaseTouchEvent = function() { - sendTouchEndEventToLaserTarget(this.hand, this.pressEnterLaserTarget); + this.processLaser = function(controllerData) { + if (this.shouldExit(controllerData) || this.getOtherModule().active) { + this.exitModule(); + return false; + } + var intersection = controllerData.rayPicks[this.hand]; + var laserTarget = TouchEventUtils.composeTouchTargetFromIntersection(intersection); + + if (controllerData.triggerClicks[this.hand]) { + this.laserTarget = laserTarget; + this.laserPressingTarget = true; + } else { + this.requestTouchFocus(laserTarget); + + if (!TouchEventUtils.touchTargetHasKeyboardFocus(laserTarget)) { + TouchEventUtils.setKeyboardFocusOnTouchTarget(laserTarget); + } + + if (this.hasTouchFocus(laserTarget) && !this.laserPressingTarget) { + TouchEventUtils.sendHoverOverEventToTouchTarget(this.hand, laserTarget); + } + } + + this.processControllerTriggers(controllerData); + this.updateLaserPointer(controllerData); + this.active = true; + return true; }; - - this.updateLaserTargets = function(controllerData) { - var intersection = controllerData.rayPicks[this.hand]; - this.laserTargetID = intersection.objectID; - this.laserTarget = calculateLaserTargetFromOverlay(intersection.intersection, intersection.objectID); + this.grabModuleWantsNearbyOverlay = function(controllerData) { + if (controllerData.triggerValues[this.hand] > TRIGGER_ON_VALUE) { + var nearGrabName = this.hand === RIGHT_HAND ? "RightNearParentingGrabOverlay" : "LeftNearParentingGrabOverlay"; + var nearGrabModule = getEnabledModuleByName(nearGrabName); + if (nearGrabModule) { + var candidateOverlays = controllerData.nearbyOverlayIDs[this.hand]; + var grabbableOverlays = candidateOverlays.filter(function(overlayID) { + return Overlays.getProperty(overlayID, "grabbable"); + }); + var target = nearGrabModule.getTargetID(grabbableOverlays, controllerData); + if (target) { + return true; + } + } + } + return false; }; this.shouldExit = function(controllerData) { var intersection = controllerData.rayPicks[this.hand]; - var nearGrabName = this.hand === RIGHT_HAND ? "RightNearParentingGrabOverlay" : "LeftNearParentingGrabOverlay"; - var nearGrabModule = getEnabledModuleByName(nearGrabName); - var status = nearGrabModule ? nearGrabModule.isReady(controllerData) : makeRunningValues(false, [], []); var offOverlay = (intersection.type !== RayPick.INTERSECTED_OVERLAY); var triggerOff = (controllerData.triggerValues[this.hand] < TRIGGER_OFF_VALUE); - return offOverlay || status.active || triggerOff; + var grabbingOverlay = this.grabModuleWantsNearbyOverlay(controllerData); + return offOverlay || grabbingOverlay || triggerOff; }; this.exitModule = function() { - this.releaseTouchEvent(); + if (this.laserPressingTarget) { + this.deadspotExpired = true; + this.laserPressExit(); + this.laserPressingTarget = false; + } + this.deleteContextOverlay(); this.relinquishTouchFocus(); this.reset(); this.updateLaserPointer(); @@ -444,12 +316,6 @@ Script.include("/~/system/libraries/controllers.js"); }; this.reset = function() { - this.hover = false; - this.pressEnterLaserTarget = null; - this.laserTarget = null; - this.laserTargetID = null; - this.laserPressingTarget = false; - this.previousLaserClickedTarget = null; this.mode = "none"; this.active = false; }; @@ -467,35 +333,13 @@ Script.include("/~/system/libraries/controllers.js"); }; this.isReady = function (controllerData) { - this.target = null; - var intersection = controllerData.rayPicks[this.hand]; - if (intersection.type === RayPick.INTERSECTED_OVERLAY) { - if (controllerData.triggerValues[this.hand] > TRIGGER_ON_VALUE && !this.getOtherModule().active) { - this.target = intersection.objectID; - this.active = true; - return makeRunningValues(true, [], []); - } else { - this.deleteContextOverlay(); - } + if (this.processLaser(controllerData)) { + return makeRunningValues(true, [], []); } - this.reset(); return makeRunningValues(false, [], []); }; this.run = function (controllerData, deltaTime) { - if (this.shouldExit(controllerData)) { - this.exitModule(); - return makeRunningValues(false, [], []); - } - - if (controllerData.triggerValues[this.hand] < TRIGGER_OFF_VALUE) { - this.deleteContextOverlay(); - } - - this.updateLaserTargets(controllerData); - this.processControllerTriggers(controllerData); - this.updateLaserPointer(controllerData); - if (!this.previousLaserClickedTarget && this.laserPressingTarget) { this.laserPressEnter(); } @@ -508,11 +352,11 @@ Script.include("/~/system/libraries/controllers.js"); this.laserPressing(controllerData, deltaTime); } - if (this.hover) { - this.hovering(); + if (this.processLaser(controllerData)) { + return makeRunningValues(true, [], []); + } else { + return makeRunningValues(false, [], []); } - - return makeRunningValues(true, [], []); }; this.cleanup = function () { diff --git a/scripts/system/controllers/controllerModules/tabletStylusInput.js b/scripts/system/controllers/controllerModules/tabletStylusInput.js index 230038adb5..9d01ceef65 100644 --- a/scripts/system/controllers/controllerModules/tabletStylusInput.js +++ b/scripts/system/controllers/controllerModules/tabletStylusInput.js @@ -16,238 +16,14 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); Script.include("/~/system/libraries/controllers.js"); (function() { - + var TouchEventUtils = Script.require("/~/system/libraries/touchEventUtils.js"); // triggered when stylus presses a web overlay/entity var HAPTIC_STYLUS_STRENGTH = 1.0; var HAPTIC_STYLUS_DURATION = 20.0; var WEB_DISPLAY_STYLUS_DISTANCE = 0.5; var WEB_STYLUS_LENGTH = 0.2; - var WEB_TOUCH_Y_OFFSET = 0.05; // how far forward (or back with a negative number) to slide stylus in hand - - - function stylusTargetHasKeyboardFocus(stylusTarget) { - if (stylusTarget.entityID && stylusTarget.entityID !== NULL_UUID) { - return Entities.keyboardFocusEntity === stylusTarget.entityID; - } else if (stylusTarget.overlayID && stylusTarget.overlayID !== NULL_UUID) { - return Overlays.keyboardFocusOverlay === stylusTarget.overlayID; - } - } - - function setKeyboardFocusOnStylusTarget(stylusTarget) { - if (stylusTarget.entityID && stylusTarget.entityID !== NULL_UUID && - Entities.wantsHandControllerPointerEvents(stylusTarget.entityID)) { - Overlays.keyboardFocusOverlay = NULL_UUID; - Entities.keyboardFocusEntity = stylusTarget.entityID; - } else if (stylusTarget.overlayID && stylusTarget.overlayID !== NULL_UUID) { - Overlays.keyboardFocusOverlay = stylusTarget.overlayID; - Entities.keyboardFocusEntity = NULL_UUID; - } - } - - function sendHoverEnterEventToStylusTarget(hand, stylusTarget) { - var pointerEvent = { - type: "Move", - id: hand + 1, // 0 is reserved for hardware mouse - pos2D: stylusTarget.position2D, - pos3D: stylusTarget.position, - normal: stylusTarget.normal, - direction: Vec3.subtract(ZERO_VEC, stylusTarget.normal), - button: "None" - }; - - if (stylusTarget.entityID && stylusTarget.entityID !== NULL_UUID) { - Entities.sendHoverEnterEntity(stylusTarget.entityID, pointerEvent); - } else if (stylusTarget.overlayID && stylusTarget.overlayID !== NULL_UUID) { - Overlays.sendHoverEnterOverlay(stylusTarget.overlayID, pointerEvent); - } - } - - function sendHoverOverEventToStylusTarget(hand, stylusTarget) { - var pointerEvent = { - type: "Move", - id: hand + 1, // 0 is reserved for hardware mouse - pos2D: stylusTarget.position2D, - pos3D: stylusTarget.position, - normal: stylusTarget.normal, - direction: Vec3.subtract(ZERO_VEC, stylusTarget.normal), - button: "None" - }; - - if (stylusTarget.entityID && stylusTarget.entityID !== NULL_UUID) { - Entities.sendMouseMoveOnEntity(stylusTarget.entityID, pointerEvent); - Entities.sendHoverOverEntity(stylusTarget.entityID, pointerEvent); - } else if (stylusTarget.overlayID && stylusTarget.overlayID !== NULL_UUID) { - Overlays.sendMouseMoveOnOverlay(stylusTarget.overlayID, pointerEvent); - Overlays.sendHoverOverOverlay(stylusTarget.overlayID, pointerEvent); - } - } - - function sendTouchStartEventToStylusTarget(hand, stylusTarget) { - var pointerEvent = { - type: "Press", - id: hand + 1, // 0 is reserved for hardware mouse - pos2D: stylusTarget.position2D, - pos3D: stylusTarget.position, - normal: stylusTarget.normal, - direction: Vec3.subtract(ZERO_VEC, stylusTarget.normal), - button: "Primary", - isPrimaryHeld: true - }; - - if (stylusTarget.entityID && stylusTarget.entityID !== NULL_UUID) { - Entities.sendMousePressOnEntity(stylusTarget.entityID, pointerEvent); - Entities.sendClickDownOnEntity(stylusTarget.entityID, pointerEvent); - } else if (stylusTarget.overlayID && stylusTarget.overlayID !== NULL_UUID) { - Overlays.sendMousePressOnOverlay(stylusTarget.overlayID, pointerEvent); - } - } - - function sendTouchEndEventToStylusTarget(hand, stylusTarget) { - var pointerEvent = { - type: "Release", - id: hand + 1, // 0 is reserved for hardware mouse - pos2D: stylusTarget.position2D, - pos3D: stylusTarget.position, - normal: stylusTarget.normal, - direction: Vec3.subtract(ZERO_VEC, stylusTarget.normal), - button: "Primary" - }; - - if (stylusTarget.entityID && stylusTarget.entityID !== NULL_UUID) { - Entities.sendMouseReleaseOnEntity(stylusTarget.entityID, pointerEvent); - Entities.sendClickReleaseOnEntity(stylusTarget.entityID, pointerEvent); - Entities.sendHoverLeaveEntity(stylusTarget.entityID, pointerEvent); - } else if (stylusTarget.overlayID && stylusTarget.overlayID !== NULL_UUID) { - Overlays.sendMouseReleaseOnOverlay(stylusTarget.overlayID, pointerEvent); - } - } - - function sendTouchMoveEventToStylusTarget(hand, stylusTarget) { - var pointerEvent = { - type: "Move", - id: hand + 1, // 0 is reserved for hardware mouse - pos2D: stylusTarget.position2D, - pos3D: stylusTarget.position, - normal: stylusTarget.normal, - direction: Vec3.subtract(ZERO_VEC, stylusTarget.normal), - button: "Primary", - isPrimaryHeld: true - }; - - if (stylusTarget.entityID && stylusTarget.entityID !== NULL_UUID) { - Entities.sendMouseMoveOnEntity(stylusTarget.entityID, pointerEvent); - Entities.sendHoldingClickOnEntity(stylusTarget.entityID, pointerEvent); - } else if (stylusTarget.overlayID && stylusTarget.overlayID !== NULL_UUID) { - Overlays.sendMouseMoveOnOverlay(stylusTarget.overlayID, pointerEvent); - } - } - - // will return undefined if overlayID does not exist. - function calculateStylusTargetFromOverlay(stylusTip, overlayID) { - var overlayPosition = Overlays.getProperty(overlayID, "position"); - if (overlayPosition === undefined) { - return; - } - - // project stylusTip onto overlay plane. - var overlayRotation = Overlays.getProperty(overlayID, "rotation"); - if (overlayRotation === undefined) { - return; - } - var normal = Vec3.multiplyQbyV(overlayRotation, {x: 0, y: 0, z: 1}); - var distance = Vec3.dot(Vec3.subtract(stylusTip.position, overlayPosition), normal); - var position = Vec3.subtract(stylusTip.position, Vec3.multiply(normal, distance)); - - // calclulate normalized position - var invRot = Quat.inverse(overlayRotation); - var localPos = Vec3.multiplyQbyV(invRot, Vec3.subtract(position, overlayPosition)); - var dpi = Overlays.getProperty(overlayID, "dpi"); - - var dimensions; - if (dpi) { - // Calculate physical dimensions for web3d overlay from resolution and dpi; "dimensions" property - // is used as a scale. - var resolution = Overlays.getProperty(overlayID, "resolution"); - if (resolution === undefined) { - return; - } - resolution.z = 1; // Circumvent divide-by-zero. - var scale = Overlays.getProperty(overlayID, "dimensions"); - if (scale === undefined) { - return; - } - scale.z = 0.01; // overlay dimensions are 2D, not 3D. - dimensions = Vec3.multiplyVbyV(Vec3.multiply(resolution, INCHES_TO_METERS / dpi), scale); - } else { - dimensions = Overlays.getProperty(overlayID, "dimensions"); - if (dimensions === undefined) { - return; - } - if (!dimensions.z) { - dimensions.z = 0.01; // sometimes overlay dimensions are 2D, not 3D. - } - } - var invDimensions = { x: 1 / dimensions.x, y: 1 / dimensions.y, z: 1 / dimensions.z }; - var normalizedPosition = Vec3.sum(Vec3.multiplyVbyV(localPos, invDimensions), DEFAULT_REGISTRATION_POINT); - - // 2D position on overlay plane in meters, relative to the bounding box upper-left hand corner. - var position2D = { - x: normalizedPosition.x * dimensions.x, - y: (1 - normalizedPosition.y) * dimensions.y // flip y-axis - }; - - return { - entityID: null, - overlayID: overlayID, - distance: distance, - position: position, - position2D: position2D, - normal: normal, - normalizedPosition: normalizedPosition, - dimensions: dimensions, - valid: true - }; - } - - // will return undefined if entity does not exist. - function calculateStylusTargetFromEntity(stylusTip, props) { - if (props.rotation === undefined) { - // if rotation is missing from props object, then this entity has probably been deleted. - return; - } - - // project stylus tip onto entity plane. - var normal = Vec3.multiplyQbyV(props.rotation, {x: 0, y: 0, z: 1}); - Vec3.multiplyQbyV(props.rotation, {x: 0, y: 1, z: 0}); - var distance = Vec3.dot(Vec3.subtract(stylusTip.position, props.position), normal); - var position = Vec3.subtract(stylusTip.position, Vec3.multiply(normal, distance)); - - // generate normalized coordinates - var invRot = Quat.inverse(props.rotation); - var localPos = Vec3.multiplyQbyV(invRot, Vec3.subtract(position, props.position)); - var invDimensions = { x: 1 / props.dimensions.x, y: 1 / props.dimensions.y, z: 1 / props.dimensions.z }; - var normalizedPosition = Vec3.sum(Vec3.multiplyVbyV(localPos, invDimensions), props.registrationPoint); - - // 2D position on entity plane in meters, relative to the bounding box upper-left hand corner. - var position2D = { - x: normalizedPosition.x * props.dimensions.x, - y: (1 - normalizedPosition.y) * props.dimensions.y // flip y-axis - }; - - return { - entityID: props.id, - entityProps: props, - overlayID: null, - distance: distance, - position: position, - position2D: position2D, - normal: normal, - normalizedPosition: normalizedPosition, - dimensions: props.dimensions, - valid: true - }; - } + var WEB_TOUCH_Y_OFFSET = 0.105; // how far forward (or back with a negative number) to slide stylus in hand function isNearStylusTarget(stylusTargets, edgeBorder, minNormalDistance, maxNormalDistance) { for (var i = 0; i < stylusTargets.length; i++) { @@ -330,7 +106,7 @@ Script.include("/~/system/libraries/controllers.js"); 100); this.getOtherHandController = function() { - return (this.hand === RIGHT_HAND) ? Controller.Standard.LeftHand : Controller.Standard.RightHand; + return (this.hand === RIGHT_HAND) ? leftTabletStylusInput : rightTabletStylusInput; }; this.handToController = function() { @@ -430,12 +206,12 @@ Script.include("/~/system/libraries/controllers.js"); stylusTarget.entityID !== this.hoverEntity && stylusTarget.entityID !== this.getOtherHandController().hoverEntity) { this.hoverEntity = stylusTarget.entityID; - sendHoverEnterEventToStylusTarget(this.hand, stylusTarget); + TouchEventUtils.sendHoverEnterEventToTouchTarget(this.hand, stylusTarget); } else if (stylusTarget.overlayID && stylusTarget.overlayID !== this.hoverOverlay && stylusTarget.overlayID !== this.getOtherHandController().hoverOverlay) { this.hoverOverlay = stylusTarget.overlayID; - sendHoverEnterEventToStylusTarget(this.hand, stylusTarget); + TouchEventUtils.sendHoverEnterEventToTouchTarget(this.hand, stylusTarget); } }; @@ -492,7 +268,7 @@ Script.include("/~/system/libraries/controllers.js"); for (i = 0; i < candidateEntities.length; i++) { props = candidateEntities[i]; if (props && props.type === "Web") { - stylusTarget = calculateStylusTargetFromEntity(this.stylusTip, candidateEntities[i]); + stylusTarget = TouchEventUtils.calculateTouchTargetFromEntity(this.stylusTip, candidateEntities[i]); if (stylusTarget) { stylusTargets.push(stylusTarget); } @@ -502,7 +278,7 @@ Script.include("/~/system/libraries/controllers.js"); // add the tabletScreen, if it is valid if (HMD.tabletScreenID && HMD.tabletScreenID !== NULL_UUID && Overlays.getProperty(HMD.tabletScreenID, "visible")) { - stylusTarget = calculateStylusTargetFromOverlay(this.stylusTip, HMD.tabletScreenID); + stylusTarget = TouchEventUtils.calculateTouchTargetFromOverlay(this.stylusTip, HMD.tabletScreenID); if (stylusTarget) { stylusTargets.push(stylusTarget); } @@ -511,7 +287,7 @@ Script.include("/~/system/libraries/controllers.js"); // add the tablet home button. if (HMD.homeButtonID && HMD.homeButtonID !== NULL_UUID && Overlays.getProperty(HMD.homeButtonID, "visible")) { - stylusTarget = calculateStylusTargetFromOverlay(this.stylusTip, HMD.homeButtonID); + stylusTarget = TouchEventUtils.calculateTouchTargetFromOverlay(this.stylusTip, HMD.homeButtonID); if (stylusTarget) { stylusTargets.push(stylusTarget); } @@ -530,9 +306,9 @@ Script.include("/~/system/libraries/controllers.js"); var sensorScaleFactor = MyAvatar.sensorToWorldScale; this.isNearStylusTarget = isNearStylusTarget(stylusTargets, - (EDGE_BORDER + hysteresisOffset) * sensorScaleFactor, - (TABLET_MIN_TOUCH_DISTANCE - hysteresisOffset) * sensorScaleFactor, - (WEB_DISPLAY_STYLUS_DISTANCE + hysteresisOffset) * sensorScaleFactor); + (EDGE_BORDER + hysteresisOffset) * sensorScaleFactor, + (TABLET_MIN_TOUCH_DISTANCE - hysteresisOffset) * sensorScaleFactor, + (WEB_DISPLAY_STYLUS_DISTANCE + hysteresisOffset) * sensorScaleFactor); if (this.isNearStylusTarget) { if (!this.useFingerInsteadOfStylus) { @@ -556,12 +332,12 @@ Script.include("/~/system/libraries/controllers.js"); this.requestTouchFocus(nearestStylusTarget); - if (!stylusTargetHasKeyboardFocus(nearestStylusTarget)) { - setKeyboardFocusOnStylusTarget(nearestStylusTarget); + if (!TouchEventUtils.touchTargetHasKeyboardFocus(nearestStylusTarget)) { + TouchEventUtils.setKeyboardFocusOnTouchTarget(nearestStylusTarget); } - if (this.hasTouchFocus(nearestStylusTarget)) { - sendHoverOverEventToStylusTarget(this.hand, nearestStylusTarget); + if (this.hasTouchFocus(nearestStylusTarget) && !this.stylusTouchingTarget) { + TouchEventUtils.sendHoverOverEventToTouchTarget(this.hand, nearestStylusTarget); } // filter out presses when tip is moving away from tablet. @@ -592,14 +368,14 @@ Script.include("/~/system/libraries/controllers.js"); this.stylusTouchingEnter = function () { this.stealTouchFocus(this.stylusTarget); - sendTouchStartEventToStylusTarget(this.hand, this.stylusTarget); + TouchEventUtils.sendTouchStartEventToTouchTarget(this.hand, this.stylusTarget); Controller.triggerHapticPulse(HAPTIC_STYLUS_STRENGTH, HAPTIC_STYLUS_DURATION, this.hand); this.touchingEnterTimer = 0; this.touchingEnterStylusTarget = this.stylusTarget; this.deadspotExpired = false; - var TOUCH_PRESS_TO_MOVE_DEADSPOT = 0.0381; + var TOUCH_PRESS_TO_MOVE_DEADSPOT = 0.0481; this.deadspotRadius = TOUCH_PRESS_TO_MOVE_DEADSPOT; }; @@ -616,9 +392,9 @@ Script.include("/~/system/libraries/controllers.js"); // send press event if (this.deadspotExpired) { - sendTouchEndEventToStylusTarget(this.hand, this.stylusTarget); + TouchEventUtils.sendTouchEndEventToTouchTarget(this.hand, this.stylusTarget); } else { - sendTouchEndEventToStylusTarget(this.hand, this.touchingEnterStylusTarget); + TouchEventUtils.sendTouchEndEventToTouchTarget(this.hand, this.touchingEnterStylusTarget); } }; @@ -627,9 +403,9 @@ Script.include("/~/system/libraries/controllers.js"); this.touchingEnterTimer += dt; if (this.stylusTarget.entityID) { - this.stylusTarget = calculateStylusTargetFromEntity(this.stylusTip, this.stylusTarget.entityProps); + this.stylusTarget = TouchEventUtils.calculateTouchTargetFromEntity(this.stylusTip, this.stylusTarget.entityProps); } else if (this.stylusTarget.overlayID) { - this.stylusTarget = calculateStylusTargetFromOverlay(this.stylusTip, this.stylusTarget.overlayID); + this.stylusTarget = TouchEventUtils.calculateTouchTargetFromOverlay(this.stylusTip, this.stylusTarget.overlayID); } var TABLET_MIN_TOUCH_DISTANCE = -0.1; @@ -642,7 +418,7 @@ Script.include("/~/system/libraries/controllers.js"); if (this.deadspotExpired || this.touchingEnterTimer > POINTER_PRESS_TO_MOVE_DELAY || distance2D(this.stylusTarget.position2D, this.touchingEnterStylusTarget.position2D) > this.deadspotRadius) { - sendTouchMoveEventToStylusTarget(this.hand, this.stylusTarget); + TouchEventUtils.sendTouchMoveEventToTouchTarget(this.hand, this.stylusTarget); this.deadspotExpired = true; } } else { @@ -654,12 +430,11 @@ Script.include("/~/system/libraries/controllers.js"); }; this.overlayLaserActive = function(controllerData) { - var overlayLaserModule = - getEnabledModuleByName(this.hand === RIGHT_HAND ? "RightOverlayLaserInput" : "LeftOverlayLaserInput"); - if (overlayLaserModule) { - return overlayLaserModule.isReady(controllerData).active; - } - return false; + var rightOverlayLaserModule = getEnabledModuleByName("RightOverlayLaserInput"); + var leftOverlayLaserModule = getEnabledModuleByName("LeftOverlayLaserInput"); + var rightModuleRunning = rightOverlayLaserModule ? !rightOverlayLaserModule.shouldExit(controllerData) : false; + var leftModuleRunning = leftOverlayLaserModule ? !leftOverlayLaserModule.shouldExit(controllerData) : false; + return leftModuleRunning || rightModuleRunning; }; this.isReady = function (controllerData) { diff --git a/scripts/system/libraries/WebTablet.js b/scripts/system/libraries/WebTablet.js index d1182f197c..c5f8168c30 100644 --- a/scripts/system/libraries/WebTablet.js +++ b/scripts/system/libraries/WebTablet.js @@ -49,9 +49,9 @@ function calcSpawnInfo(hand, landscape) { var headRot = (HMD.active && Camera.mode === "first person") ? HMD.orientation : Camera.orientation; var forward = Quat.getForward(headRot); - var FORWARD_OFFSET = 0.6 * MyAvatar.sensorToWorldScale; + var FORWARD_OFFSET = 0.5 * MyAvatar.sensorToWorldScale; finalPosition = Vec3.sum(headPos, Vec3.multiply(FORWARD_OFFSET, forward)); - var orientation = Quat.lookAt({x: 0, y: 0, z: 0}, forward, {x: 0, y: 1, z: 0}); + var orientation = Quat.lookAt({x: 0, y: 0, z: 0}, forward, Vec3.multiplyQbyV(MyAvatar.orientation, Vec3.UNIT_Y)); return { position: finalPosition, rotation: landscape ? Quat.multiply(orientation, ROT_LANDSCAPE) : Quat.multiply(orientation, ROT_Y_180) diff --git a/scripts/system/libraries/controllerDispatcherUtils.js b/scripts/system/libraries/controllerDispatcherUtils.js index 33eec74111..10931e4e93 100644 --- a/scripts/system/libraries/controllerDispatcherUtils.js +++ b/scripts/system/libraries/controllerDispatcherUtils.js @@ -318,6 +318,8 @@ if (typeof module !== 'undefined') { makeRunningValues: makeRunningValues, LEFT_HAND: LEFT_HAND, RIGHT_HAND: RIGHT_HAND, - BUMPER_ON_VALUE: BUMPER_ON_VALUE + BUMPER_ON_VALUE: BUMPER_ON_VALUE, + projectOntoOverlayXYPlane: projectOntoOverlayXYPlane, + projectOntoEntityXYPlane: projectOntoEntityXYPlane }; } diff --git a/scripts/system/libraries/touchEventUtils.js b/scripts/system/libraries/touchEventUtils.js new file mode 100644 index 0000000000..fbd56e16ae --- /dev/null +++ b/scripts/system/libraries/touchEventUtils.js @@ -0,0 +1,270 @@ +"use strict"; + +// touchEventUtils.js +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + +/* global Script, Entities, MyAvatar, Controller, RIGHT_HAND, LEFT_HAND, + controllerDispatcher.NULL_UUID, enableDispatcherModule, disableDispatcherModule, makeRunningValues, + Messages, Quat, Vec3, getControllerWorldLocation, makeDispatcherModuleParameters, Overlays, controllerDispatcher.ZERO_VEC, + AVATAR_SELF_ID, HMD, INCHES_TO_METERS, DEFAULT_REGISTRATION_POINT, Settings, getGrabPointSphereOffset +*/ + +var controllerDispatcher = Script.require("/~/system/libraries/controllerDispatcherUtils.js"); +function touchTargetHasKeyboardFocus(touchTarget) { + if (touchTarget.entityID && touchTarget.entityID !== controllerDispatcher.NULL_UUID) { + return Entities.keyboardFocusEntity === touchTarget.entityID; + } else if (touchTarget.overlayID && touchTarget.overlayID !== controllerDispatcher.NULL_UUID) { + return Overlays.keyboardFocusOverlay === touchTarget.overlayID; + } +} + +function setKeyboardFocusOnTouchTarget(touchTarget) { + if (touchTarget.entityID && touchTarget.entityID !== controllerDispatcher.NULL_UUID && + Entities.wantsHandControllerPointerEvents(touchTarget.entityID)) { + Overlays.keyboardFocusOverlay = controllerDispatcher.NULL_UUID; + Entities.keyboardFocusEntity = touchTarget.entityID; + } else if (touchTarget.overlayID && touchTarget.overlayID !== controllerDispatcher.NULL_UUID) { + Overlays.keyboardFocusOverlay = touchTarget.overlayID; + Entities.keyboardFocusEntity = controllerDispatcher.NULL_UUID; + } +} + +function sendHoverEnterEventToTouchTarget(hand, touchTarget) { + var pointerEvent = { + type: "Move", + id: hand + 1, // 0 is reserved for hardware mouse + pos2D: touchTarget.position2D, + pos3D: touchTarget.position, + normal: touchTarget.normal, + direction: Vec3.subtract(controllerDispatcher.ZERO_VEC, touchTarget.normal), + button: "None" + }; + + if (touchTarget.entityID && touchTarget.entityID !== controllerDispatcher.NULL_UUID) { + Entities.sendHoverEnterEntity(touchTarget.entityID, pointerEvent); + } else if (touchTarget.overlayID && touchTarget.overlayID !== controllerDispatcher.NULL_UUID) { + Overlays.sendHoverEnterOverlay(touchTarget.overlayID, pointerEvent); + } +} + +function sendHoverOverEventToTouchTarget(hand, touchTarget) { + var pointerEvent = { + type: "Move", + id: hand + 1, // 0 is reserved for hardware mouse + pos2D: touchTarget.position2D, + pos3D: touchTarget.position, + normal: touchTarget.normal, + direction: Vec3.subtract(controllerDispatcher.ZERO_VEC, touchTarget.normal), + button: "None" + }; + + if (touchTarget.entityID && touchTarget.entityID !== controllerDispatcher.NULL_UUID) { + Entities.sendMouseMoveOnEntity(touchTarget.entityID, pointerEvent); + Entities.sendHoverOverEntity(touchTarget.entityID, pointerEvent); + } else if (touchTarget.overlayID && touchTarget.overlayID !== controllerDispatcher.NULL_UUID) { + Overlays.sendMouseMoveOnOverlay(touchTarget.overlayID, pointerEvent); + Overlays.sendHoverOverOverlay(touchTarget.overlayID, pointerEvent); + } +} + +function sendTouchStartEventToTouchTarget(hand, touchTarget) { + var pointerEvent = { + type: "Press", + id: hand + 1, // 0 is reserved for hardware mouse + pos2D: touchTarget.position2D, + pos3D: touchTarget.position, + normal: touchTarget.normal, + direction: Vec3.subtract(controllerDispatcher.ZERO_VEC, touchTarget.normal), + button: "Primary", + isPrimaryHeld: true + }; + + if (touchTarget.entityID && touchTarget.entityID !== controllerDispatcher.NULL_UUID) { + Entities.sendMousePressOnEntity(touchTarget.entityID, pointerEvent); + Entities.sendClickDownOnEntity(touchTarget.entityID, pointerEvent); + } else if (touchTarget.overlayID && touchTarget.overlayID !== controllerDispatcher.NULL_UUID) { + Overlays.sendMousePressOnOverlay(touchTarget.overlayID, pointerEvent); + } +} + +function sendTouchEndEventToTouchTarget(hand, touchTarget) { + var pointerEvent = { + type: "Release", + id: hand + 1, // 0 is reserved for hardware mouse + pos2D: touchTarget.position2D, + pos3D: touchTarget.position, + normal: touchTarget.normal, + direction: Vec3.subtract(controllerDispatcher.ZERO_VEC, touchTarget.normal), + button: "Primary" + }; + + if (touchTarget.entityID && touchTarget.entityID !== controllerDispatcher.NULL_UUID) { + Entities.sendMouseReleaseOnEntity(touchTarget.entityID, pointerEvent); + Entities.sendClickReleaseOnEntity(touchTarget.entityID, pointerEvent); + Entities.sendHoverLeaveEntity(touchTarget.entityID, pointerEvent); + } else if (touchTarget.overlayID && touchTarget.overlayID !== controllerDispatcher.NULL_UUID) { + Overlays.sendMouseReleaseOnOverlay(touchTarget.overlayID, pointerEvent); + } +} + +function sendTouchMoveEventToTouchTarget(hand, touchTarget) { + var pointerEvent = { + type: "Move", + id: hand + 1, // 0 is reserved for hardware mouse + pos2D: touchTarget.position2D, + pos3D: touchTarget.position, + normal: touchTarget.normal, + direction: Vec3.subtract(controllerDispatcher.ZERO_VEC, touchTarget.normal), + button: "Primary", + isPrimaryHeld: true + }; + + if (touchTarget.entityID && touchTarget.entityID !== controllerDispatcher.NULL_UUID) { + Entities.sendMouseMoveOnEntity(touchTarget.entityID, pointerEvent); + Entities.sendHoldingClickOnEntity(touchTarget.entityID, pointerEvent); + } else if (touchTarget.overlayID && touchTarget.overlayID !== controllerDispatcher.NULL_UUID) { + Overlays.sendMouseMoveOnOverlay(touchTarget.overlayID, pointerEvent); + } +} + +function composeTouchTargetFromIntersection(intersection) { + var isEntity = (intersection.type === RayPick.INTERSECTED_ENTITY); + var objectID = intersection.objectID; + var worldPos = intersection.intersection; + var props = null; + if (isEntity) { + props = Entities.getProperties(intersection.objectID); + } + + var position2D =(isEntity ? controllerDispatcher.projectOntoEntityXYPlane(objectID, worldPos, props) : + controllerDispatcher.projectOntoOverlayXYPlane(objectID, worldPos)); + return { + entityID: isEntity ? objectID : null, + overlayID: isEntity ? null : objectID, + distance: intersection.distance, + position: worldPos, + position2D: position2D, + normal: intersection.surfaceNormal + }; +} + +// will return undefined if overlayID does not exist. +function calculateTouchTargetFromOverlay(touchTip, overlayID) { + var overlayPosition = Overlays.getProperty(overlayID, "position"); + if (overlayPosition === undefined) { + return; + } + + // project touchTip onto overlay plane. + var overlayRotation = Overlays.getProperty(overlayID, "rotation"); + if (overlayRotation === undefined) { + return; + } + var normal = Vec3.multiplyQbyV(overlayRotation, {x: 0, y: 0, z: 1}); + var distance = Vec3.dot(Vec3.subtract(touchTip.position, overlayPosition), normal); + var position = Vec3.subtract(touchTip.position, Vec3.multiply(normal, distance)); + + // calclulate normalized position + var invRot = Quat.inverse(overlayRotation); + var localPos = Vec3.multiplyQbyV(invRot, Vec3.subtract(position, overlayPosition)); + var dpi = Overlays.getProperty(overlayID, "dpi"); + + var dimensions; + if (dpi) { + // Calculate physical dimensions for web3d overlay from resolution and dpi; "dimensions" property + // is used as a scale. + var resolution = Overlays.getProperty(overlayID, "resolution"); + if (resolution === undefined) { + return; + } + resolution.z = 1; // Circumvent divide-by-zero. + var scale = Overlays.getProperty(overlayID, "dimensions"); + if (scale === undefined) { + return; + } + scale.z = 0.01; // overlay dimensions are 2D, not 3D. + dimensions = Vec3.multiplyVbyV(Vec3.multiply(resolution, INCHES_TO_METERS / dpi), scale); + } else { + dimensions = Overlays.getProperty(overlayID, "dimensions"); + if (dimensions === undefined) { + return; + } + if (!dimensions.z) { + dimensions.z = 0.01; // sometimes overlay dimensions are 2D, not 3D. + } + } + var invDimensions = { x: 1 / dimensions.x, y: 1 / dimensions.y, z: 1 / dimensions.z }; + var normalizedPosition = Vec3.sum(Vec3.multiplyVbyV(localPos, invDimensions), DEFAULT_REGISTRATION_POINT); + + // 2D position on overlay plane in meters, relative to the bounding box upper-left hand corner. + var position2D = { + x: normalizedPosition.x * dimensions.x, + y: (1 - normalizedPosition.y) * dimensions.y // flip y-axis + }; + + return { + entityID: null, + overlayID: overlayID, + distance: distance, + position: position, + position2D: position2D, + normal: normal, + normalizedPosition: normalizedPosition, + dimensions: dimensions, + valid: true + }; +} + +// will return undefined if entity does not exist. +function calculateTouchTargetFromEntity(touchTip, props) { + if (props.rotation === undefined) { + // if rotation is missing from props object, then this entity has probably been deleted. + return; + } + + // project touch tip onto entity plane. + var normal = Vec3.multiplyQbyV(props.rotation, {x: 0, y: 0, z: 1}); + Vec3.multiplyQbyV(props.rotation, {x: 0, y: 1, z: 0}); + var distance = Vec3.dot(Vec3.subtract(touchTip.position, props.position), normal); + var position = Vec3.subtract(touchTip.position, Vec3.multiply(normal, distance)); + + // generate normalized coordinates + var invRot = Quat.inverse(props.rotation); + var localPos = Vec3.multiplyQbyV(invRot, Vec3.subtract(position, props.position)); + var invDimensions = { x: 1 / props.dimensions.x, y: 1 / props.dimensions.y, z: 1 / props.dimensions.z }; + var normalizedPosition = Vec3.sum(Vec3.multiplyVbyV(localPos, invDimensions), props.registrationPoint); + + // 2D position on entity plane in meters, relative to the bounding box upper-left hand corner. + var position2D = { + x: normalizedPosition.x * props.dimensions.x, + y: (1 - normalizedPosition.y) * props.dimensions.y // flip y-axis + }; + + return { + entityID: props.id, + entityProps: props, + overlayID: null, + distance: distance, + position: position, + position2D: position2D, + normal: normal, + normalizedPosition: normalizedPosition, + dimensions: props.dimensions, + valid: true + }; +} + +module.exports = { + calculateTouchTargetFromEntity: calculateTouchTargetFromEntity, + calculateTouchTargetFromOverlay: calculateTouchTargetFromOverlay, + touchTargetHasKeyboardFocus: touchTargetHasKeyboardFocus, + setKeyboardFocusOnTouchTarget: setKeyboardFocusOnTouchTarget, + sendHoverEnterEventToTouchTarget: sendHoverEnterEventToTouchTarget, + sendHoverOverEventToTouchTarget: sendHoverOverEventToTouchTarget, + sendTouchStartEventToTouchTarget: sendTouchStartEventToTouchTarget, + sendTouchEndEventToTouchTarget: sendTouchEndEventToTouchTarget, + sendTouchMoveEventToTouchTarget: sendTouchMoveEventToTouchTarget, + composeTouchTargetFromIntersection: composeTouchTargetFromIntersection +}; diff --git a/scripts/system/makeUserConnection.js b/scripts/system/makeUserConnection.js index d95ad919b6..bfad959ffc 100644 --- a/scripts/system/makeUserConnection.js +++ b/scripts/system/makeUserConnection.js @@ -16,6 +16,7 @@ var request = Script.require('request').request; + var WANT_DEBUG = Settings.getValue('MAKE_USER_CONNECTION_DEBUG', false); var LABEL = "makeUserConnection"; var MAX_AVATAR_DISTANCE = 0.2; // m var GRIP_MIN = 0.75; // goes from 0-1, so 75% pressed is pressed @@ -120,6 +121,9 @@ var successfulHandshakeSound; function debug() { + if (!WANT_DEBUG) { + return; + } var stateString = "<" + STATE_STRINGS[state] + ">"; var connecting = "[" + connectingId + "/" + connectingHandJointIndex + "]"; var current = "[" + currentHand + "/" + currentHandJointIndex + "]" @@ -372,7 +376,7 @@ var myHeadIndex = MyAvatar.getJointIndex("Head"); var otherHeadIndex = avatar.getJointIndex("Head"); var diff = (avatar.getJointPosition(otherHeadIndex).y - MyAvatar.getJointPosition(myHeadIndex).y) / 2; - print("head height difference: " + diff); + debug("head height difference: " + diff); updateAnimationData(diff); } } diff --git a/unpublishedScripts/marketplace/record/record.js b/unpublishedScripts/marketplace/record/record.js index 5439d68c9a..6110721ff9 100644 --- a/unpublishedScripts/marketplace/record/record.js +++ b/unpublishedScripts/marketplace/record/record.js @@ -571,7 +571,7 @@ function onTabletScreenChanged(type, url) { // Opened/closed dialog in tablet or window. - var RECORD_URL = "/scripts/system/html/record.html"; + var RECORD_URL = "/html/record.html"; if (type === "Web" && url.slice(-RECORD_URL.length) === RECORD_URL) { if (Dialog.finishOnOpen()) {