mirror of
https://github.com/overte-org/overte.git
synced 2025-07-23 13:24:02 +02:00
Merge branch 'master' of https://github.com/highfidelity/hifi
This commit is contained in:
commit
d960a7c42e
24 changed files with 927 additions and 688 deletions
2
BUILD.md
2
BUILD.md
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
- [cmake](https://cmake.org/download/): 3.9
|
- [cmake](https://cmake.org/download/): 3.9
|
||||||
- [Qt](https://www.qt.io/download-open-source): 5.9.1
|
- [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)
|
- [VHACD](https://github.com/virneo/v-hacd)(clone this repository)(Optional)
|
||||||
|
|
||||||
### CMake External Project Dependencies
|
### CMake External Project Dependencies
|
||||||
|
|
|
@ -272,22 +272,22 @@ void DomainGatekeeper::updateNodePermissions() {
|
||||||
userPerms.permissions |= NodePermissions::Permission::canWriteToAssetServer;
|
userPerms.permissions |= NodePermissions::Permission::canWriteToAssetServer;
|
||||||
userPerms.permissions |= NodePermissions::Permission::canReplaceDomainContent;
|
userPerms.permissions |= NodePermissions::Permission::canReplaceDomainContent;
|
||||||
} else {
|
} 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
|
// 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
|
// or the public socket if we haven't activated a socket for the node yet
|
||||||
HifiSockAddr connectingAddr = node->getActiveSocket() ? *node->getActiveSocket() : node->getPublicSocket();
|
HifiSockAddr connectingAddr = node->getActiveSocket() ? *node->getActiveSocket() : node->getPublicSocket();
|
||||||
|
|
||||||
QString hardwareAddress;
|
QString hardwareAddress;
|
||||||
QUuid machineFingerprint;
|
QUuid machineFingerprint;
|
||||||
|
bool isLocalUser { false };
|
||||||
|
|
||||||
DomainServerNodeData* nodeData = static_cast<DomainServerNodeData*>(node->getLinkedData());
|
DomainServerNodeData* nodeData = static_cast<DomainServerNodeData*>(node->getLinkedData());
|
||||||
if (nodeData) {
|
if (nodeData) {
|
||||||
hardwareAddress = nodeData->getHardwareAddress();
|
hardwareAddress = nodeData->getHardwareAddress();
|
||||||
machineFingerprint = nodeData->getMachineFingerprint();
|
machineFingerprint = nodeData->getMachineFingerprint();
|
||||||
|
|
||||||
|
auto sendingAddress = nodeData->getSendingSockAddr().getAddress();
|
||||||
|
isLocalUser = (sendingAddress == limitedNodeList->getLocalSockAddr().getAddress() ||
|
||||||
|
sendingAddress == QHostAddress::LocalHost);
|
||||||
}
|
}
|
||||||
|
|
||||||
userPerms = setPermissionsForUser(isLocalUser, verifiedUsername, connectingAddr.getAddress(), hardwareAddress, machineFingerprint);
|
userPerms = setPermissionsForUser(isLocalUser, verifiedUsername, connectingAddr.getAddress(), hardwareAddress, machineFingerprint);
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
},
|
},
|
||||||
|
|
||||||
{ "from": "Standard.RX",
|
{ "from": "Standard.RX",
|
||||||
"when": [ "Application.InHMD", "Application.SnapTurn" ],
|
"when": [ "Application.SnapTurn" ],
|
||||||
"to": "Actions.StepYaw",
|
"to": "Actions.StepYaw",
|
||||||
"filters":
|
"filters":
|
||||||
[
|
[
|
||||||
|
@ -128,4 +128,4 @@
|
||||||
{ "from": "Standard.TrackedObject14", "to" : "Actions.TrackedObject14" },
|
{ "from": "Standard.TrackedObject14", "to" : "Actions.TrackedObject14" },
|
||||||
{ "from": "Standard.TrackedObject15", "to" : "Actions.TrackedObject15" }
|
{ "from": "Standard.TrackedObject15", "to" : "Actions.TrackedObject15" }
|
||||||
]
|
]
|
||||||
}
|
}
|
113
interface/resources/qml/controls-uit/CheckBoxQQC2.qml
Normal file
113
interface/resources/qml/controls-uit/CheckBoxQQC2.qml
Normal file
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import QtQuick 2.5
|
import QtQuick 2.5
|
||||||
import QtQuick.Controls 1.4
|
import QtQuick.Controls 2.2
|
||||||
import QtQuick.Layouts 1.3
|
import QtQuick.Layouts 1.3
|
||||||
|
|
||||||
import "../../styles-uit"
|
import "../../styles-uit"
|
||||||
|
@ -36,7 +36,41 @@ Rectangle {
|
||||||
return (root.parent !== null) && root.parent.objectName == "loader";
|
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;
|
property bool showPeaks: true;
|
||||||
|
|
||||||
function enablePeakValues() {
|
function enablePeakValues() {
|
||||||
Audio.devices.input.peakValuesEnabled = true;
|
Audio.devices.input.peakValuesEnabled = true;
|
||||||
Audio.devices.input.peakValuesEnabledChanged.connect(function(enabled) {
|
Audio.devices.input.peakValuesEnabledChanged.connect(function(enabled) {
|
||||||
|
@ -45,6 +79,7 @@ Rectangle {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function disablePeakValues() {
|
function disablePeakValues() {
|
||||||
root.showPeaks = false;
|
root.showPeaks = false;
|
||||||
Audio.devices.input.peakValuesEnabled = false;
|
Audio.devices.input.peakValuesEnabled = false;
|
||||||
|
@ -55,29 +90,32 @@ Rectangle {
|
||||||
onVisibleChanged: visible ? enablePeakValues() : disablePeakValues();
|
onVisibleChanged: visible ? enablePeakValues() : disablePeakValues();
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
y: 16; // padding does not work
|
spacing: 12;
|
||||||
spacing: 16;
|
anchors.top: bar.bottom
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.bottomMargin: 5
|
||||||
width: parent.width;
|
width: parent.width;
|
||||||
|
|
||||||
|
Separator { }
|
||||||
|
|
||||||
RalewayRegular {
|
RalewayRegular {
|
||||||
x: 16; // padding does not work
|
x: margins.paddings + muteMic.boxSize + muteMic.spacing;
|
||||||
size: 16;
|
size: 16;
|
||||||
color: "white";
|
color: "white";
|
||||||
text: root.title;
|
text: qsTr("Input Device Settings")
|
||||||
|
|
||||||
visible: root.showTitle();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Separator { visible: root.showTitle() }
|
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
x: 16; // padding does not work
|
x: margins.paddings;
|
||||||
spacing: 16;
|
spacing: 16;
|
||||||
|
width: parent.width;
|
||||||
|
|
||||||
// mute is in its own row
|
// mute is in its own row
|
||||||
RowLayout {
|
RowLayout {
|
||||||
AudioControls.CheckBox {
|
AudioControls.CheckBox {
|
||||||
|
id: muteMic
|
||||||
text: qsTr("Mute microphone");
|
text: qsTr("Mute microphone");
|
||||||
|
spacing: margins.sizeCheckBox - boxSize
|
||||||
isRedCheck: true;
|
isRedCheck: true;
|
||||||
checked: Audio.muted;
|
checked: Audio.muted;
|
||||||
onClicked: {
|
onClicked: {
|
||||||
|
@ -88,8 +126,9 @@ Rectangle {
|
||||||
}
|
}
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
spacing: 16;
|
spacing: muteMic.spacing*2; //make it visually distinguish
|
||||||
AudioControls.CheckBox {
|
AudioControls.CheckBox {
|
||||||
|
spacing: muteMic.spacing
|
||||||
text: qsTr("Enable noise reduction");
|
text: qsTr("Enable noise reduction");
|
||||||
checked: Audio.noiseReduction;
|
checked: Audio.noiseReduction;
|
||||||
onClicked: {
|
onClicked: {
|
||||||
|
@ -98,24 +137,33 @@ Rectangle {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AudioControls.CheckBox {
|
AudioControls.CheckBox {
|
||||||
|
spacing: muteMic.spacing
|
||||||
text: qsTr("Show audio level meter");
|
text: qsTr("Show audio level meter");
|
||||||
checked: AvatarInputs.showAudioTools;
|
checked: AvatarInputs.showAudioTools;
|
||||||
onClicked: {
|
onClicked: {
|
||||||
AvatarInputs.showAudioTools = checked;
|
AvatarInputs.showAudioTools = checked;
|
||||||
checked = Qt.binding(function() { return AvatarInputs.showAudioTools; }); // restore binding
|
checked = Qt.binding(function() { return AvatarInputs.showAudioTools; }); // restore binding
|
||||||
}
|
}
|
||||||
|
onXChanged: rightMostInputLevelPos = x + width
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Separator {}
|
Separator {}
|
||||||
|
|
||||||
RowLayout {
|
Item {
|
||||||
|
x: margins.paddings;
|
||||||
|
width: parent.width - margins.paddings*2
|
||||||
|
height: 36
|
||||||
|
|
||||||
HiFiGlyphs {
|
HiFiGlyphs {
|
||||||
|
width: margins.sizeCheckBox
|
||||||
text: hifi.glyphs.mic;
|
text: hifi.glyphs.mic;
|
||||||
color: hifi.colors.primaryHighlight;
|
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;
|
anchors.verticalCenter: parent.verticalCenter;
|
||||||
size: 28;
|
size: 30;
|
||||||
}
|
}
|
||||||
RalewayRegular {
|
RalewayRegular {
|
||||||
anchors.verticalCenter: parent.verticalCenter;
|
anchors.verticalCenter: parent.verticalCenter;
|
||||||
|
@ -126,90 +174,114 @@ Rectangle {
|
||||||
}
|
}
|
||||||
|
|
||||||
ListView {
|
ListView {
|
||||||
anchors { left: parent.left; right: parent.right; leftMargin: 70 }
|
id: inputView
|
||||||
height: 125;
|
width: parent.width - margins.paddings*2
|
||||||
spacing: 0;
|
x: margins.paddings
|
||||||
|
height: Math.min(150, contentHeight);
|
||||||
|
spacing: 4;
|
||||||
snapMode: ListView.SnapToItem;
|
snapMode: ListView.SnapToItem;
|
||||||
clip: true;
|
clip: true;
|
||||||
model: Audio.devices.input;
|
model: Audio.devices.input;
|
||||||
delegate: Item {
|
delegate: Item {
|
||||||
width: parent.width;
|
width: rightMostInputLevelPos
|
||||||
height: 36;
|
height: margins.sizeCheckBox > checkBoxInput.implicitHeight ?
|
||||||
|
margins.sizeCheckBox : checkBoxInput.implicitHeight
|
||||||
|
|
||||||
AudioControls.CheckBox {
|
AudioControls.CheckBox {
|
||||||
id: checkbox
|
id: checkBoxInput
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
text: display;
|
spacing: margins.sizeCheckBox - boxSize
|
||||||
wrap: false;
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
checked: selected;
|
width: parent.width - inputLevel.width
|
||||||
enabled: false;
|
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 {
|
InputPeak {
|
||||||
id: inputPeak;
|
id: inputLevel
|
||||||
visible: Audio.devices.input.peakValuesAvailable;
|
anchors.right: parent.right
|
||||||
peak: model.peak;
|
peak: model.peak;
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
anchors.right: parent.right
|
visible: (bar.currentIndex === 1 && selectedHMD && isVR) ||
|
||||||
anchors.rightMargin: 30
|
(bar.currentIndex === 0 && selectedDesktop && !isVR) &&
|
||||||
|
Audio.devices.input.peakValuesAvailable;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Separator {}
|
Separator {}
|
||||||
|
|
||||||
RowLayout {
|
Item {
|
||||||
Column {
|
x: margins.paddings;
|
||||||
RowLayout {
|
width: parent.width - margins.paddings*2
|
||||||
HiFiGlyphs {
|
height: 36
|
||||||
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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
ListView {
|
||||||
anchors { left: parent.left; right: parent.right; leftMargin: 70 }
|
id: outputView
|
||||||
height: Math.min(250, contentHeight);
|
width: parent.width - margins.paddings*2
|
||||||
spacing: 0;
|
x: margins.paddings
|
||||||
|
height: Math.min(360 - inputView.height, contentHeight);
|
||||||
|
spacing: 4;
|
||||||
snapMode: ListView.SnapToItem;
|
snapMode: ListView.SnapToItem;
|
||||||
clip: true;
|
clip: true;
|
||||||
model: Audio.devices.output;
|
model: Audio.devices.output;
|
||||||
delegate: Item {
|
delegate: Item {
|
||||||
width: parent.width;
|
width: rightMostInputLevelPos
|
||||||
height: 36;
|
height: margins.sizeCheckBox > checkBoxOutput.implicitHeight ?
|
||||||
|
margins.sizeCheckBox : checkBoxOutput.implicitHeight
|
||||||
|
|
||||||
AudioControls.CheckBox {
|
AudioControls.CheckBox {
|
||||||
id: checkbox
|
id: checkBoxOutput
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
width: parent.width
|
||||||
anchors.left: parent.left
|
spacing: margins.sizeCheckBox - boxSize
|
||||||
text: display;
|
boxSize: margins.sizeCheckBox / 2
|
||||||
checked: selected;
|
isRound: true
|
||||||
enabled: false;
|
checked: bar.currentIndex === 0 ? selectedDesktop : selectedHMD;
|
||||||
}
|
checkable: !checked
|
||||||
|
text: devicename
|
||||||
MouseArea {
|
onPressed: {
|
||||||
anchors.fill: checkbox
|
if (!checked) {
|
||||||
onClicked: Audio.setOutputDevice(info);
|
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 }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
35
interface/resources/qml/hifi/audio/AudioTabButton.qml
Normal file
35
interface/resources/qml/hifi/audio/AudioTabButton.qml
Normal file
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,10 +9,10 @@
|
||||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
// 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
|
import "../../controls-uit" as HifiControls
|
||||||
|
|
||||||
HifiControls.CheckBox {
|
HifiControls.CheckBoxQQC2 {
|
||||||
color: "white"
|
color: "white"
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,8 +9,6 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import QtQuick 2.5
|
import QtQuick 2.5
|
||||||
import QtQuick.Controls 1.4
|
|
||||||
import QtQuick.Controls.Styles 1.4
|
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
id: root
|
id: root
|
||||||
|
|
|
@ -2057,6 +2057,7 @@ void Application::cleanupBeforeQuit() {
|
||||||
// this must happen after QML, as there are unexplained audio crashes originating in qtwebengine
|
// this must happen after QML, as there are unexplained audio crashes originating in qtwebengine
|
||||||
DependencyManager::destroy<AudioClient>();
|
DependencyManager::destroy<AudioClient>();
|
||||||
DependencyManager::destroy<AudioInjectorManager>();
|
DependencyManager::destroy<AudioInjectorManager>();
|
||||||
|
DependencyManager::destroy<AudioScriptingInterface>();
|
||||||
|
|
||||||
qCDebug(interfaceapp) << "Application::cleanupBeforeQuit() complete";
|
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");
|
PROFILE_RANGE(app, "RayPickManager");
|
||||||
_rayPickManager.update();
|
_rayPickManager.update();
|
||||||
|
@ -5175,6 +5170,12 @@ void Application::update(float deltaTime) {
|
||||||
_laserPointerManager.update();
|
_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...
|
// Update _viewFrustum with latest camera and view frustum data...
|
||||||
// NOTE: we get this from the view frustum, to make it simpler, since the
|
// NOTE: we get this from the view frustum, to make it simpler, since the
|
||||||
// loadViewFrumstum() method will get the correct details from the camera
|
// loadViewFrumstum() method will get the correct details from the camera
|
||||||
|
|
|
@ -86,7 +86,9 @@ void LaserPointer::editRenderState(const std::string& state, const QVariant& sta
|
||||||
|
|
||||||
void LaserPointer::updateRenderStateOverlay(const OverlayID& id, const QVariant& props) {
|
void LaserPointer::updateRenderStateOverlay(const OverlayID& id, const QVariant& props) {
|
||||||
if (!id.isNull() && props.isValid()) {
|
if (!id.isNull() && props.isValid()) {
|
||||||
qApp->getOverlays().editOverlay(id, props);
|
QVariantMap propMap = props.toMap();
|
||||||
|
propMap.remove("visible");
|
||||||
|
qApp->getOverlays().editOverlay(id, propMap);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -95,6 +95,7 @@ const RenderState LaserPointerScriptingInterface::buildRenderState(const QVarian
|
||||||
if (propMap["start"].isValid()) {
|
if (propMap["start"].isValid()) {
|
||||||
QVariantMap startMap = propMap["start"].toMap();
|
QVariantMap startMap = propMap["start"].toMap();
|
||||||
if (startMap["type"].isValid()) {
|
if (startMap["type"].isValid()) {
|
||||||
|
startMap.remove("visible");
|
||||||
startID = qApp->getOverlays().addOverlay(startMap["type"].toString(), startMap);
|
startID = qApp->getOverlays().addOverlay(startMap["type"].toString(), startMap);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -104,6 +105,7 @@ const RenderState LaserPointerScriptingInterface::buildRenderState(const QVarian
|
||||||
QVariantMap pathMap = propMap["path"].toMap();
|
QVariantMap pathMap = propMap["path"].toMap();
|
||||||
// right now paths must be line3ds
|
// right now paths must be line3ds
|
||||||
if (pathMap["type"].isValid() && pathMap["type"].toString() == "line3d") {
|
if (pathMap["type"].isValid() && pathMap["type"].toString() == "line3d") {
|
||||||
|
pathMap.remove("visible");
|
||||||
pathID = qApp->getOverlays().addOverlay(pathMap["type"].toString(), pathMap);
|
pathID = qApp->getOverlays().addOverlay(pathMap["type"].toString(), pathMap);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -112,6 +114,7 @@ const RenderState LaserPointerScriptingInterface::buildRenderState(const QVarian
|
||||||
if (propMap["end"].isValid()) {
|
if (propMap["end"].isValid()) {
|
||||||
QVariantMap endMap = propMap["end"].toMap();
|
QVariantMap endMap = propMap["end"].toMap();
|
||||||
if (endMap["type"].isValid()) {
|
if (endMap["type"].isValid()) {
|
||||||
|
endMap.remove("visible");
|
||||||
endID = qApp->getOverlays().addOverlay(endMap["type"].toString(), endMap);
|
endID = qApp->getOverlays().addOverlay(endMap["type"].toString(), endMap);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -135,10 +135,10 @@ void Audio::setReverbOptions(const AudioEffectOptions* options) {
|
||||||
DependencyManager::get<AudioClient>()->setReverbOptions(options);
|
DependencyManager::get<AudioClient>()->setReverbOptions(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Audio::setInputDevice(const QAudioDeviceInfo& device) {
|
void Audio::setInputDevice(const QAudioDeviceInfo& device, bool isHMD) {
|
||||||
_devices.chooseInputDevice(device);
|
_devices.chooseInputDevice(device, isHMD);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Audio::setOutputDevice(const QAudioDeviceInfo& device) {
|
void Audio::setOutputDevice(const QAudioDeviceInfo& device, bool isHMD) {
|
||||||
_devices.chooseOutputDevice(device);
|
_devices.chooseOutputDevice(device, isHMD);
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,8 +50,8 @@ public:
|
||||||
void showMicMeter(bool show);
|
void showMicMeter(bool show);
|
||||||
void setInputVolume(float volume);
|
void setInputVolume(float volume);
|
||||||
|
|
||||||
Q_INVOKABLE void setInputDevice(const QAudioDeviceInfo& device);
|
Q_INVOKABLE void setInputDevice(const QAudioDeviceInfo& device, bool isHMD);
|
||||||
Q_INVOKABLE void setOutputDevice(const QAudioDeviceInfo& device);
|
Q_INVOKABLE void setOutputDevice(const QAudioDeviceInfo& device, bool isHMD);
|
||||||
Q_INVOKABLE void setReverb(bool enable);
|
Q_INVOKABLE void setReverb(bool enable);
|
||||||
Q_INVOKABLE void setReverbOptions(const AudioEffectOptions* options);
|
Q_INVOKABLE void setReverbOptions(const AudioEffectOptions* options);
|
||||||
|
|
||||||
|
|
|
@ -38,15 +38,17 @@ Setting::Handle<QString>& getSetting(bool contextIsHMD, QAudio::Mode mode) {
|
||||||
}
|
}
|
||||||
|
|
||||||
enum AudioDeviceRole {
|
enum AudioDeviceRole {
|
||||||
DisplayRole = Qt::DisplayRole,
|
DeviceNameRole = Qt::UserRole,
|
||||||
CheckStateRole = Qt::CheckStateRole,
|
SelectedDesktopRole,
|
||||||
PeakRole = Qt::UserRole,
|
SelectedHMDRole,
|
||||||
InfoRole = Qt::UserRole + 1
|
PeakRole,
|
||||||
|
InfoRole
|
||||||
};
|
};
|
||||||
|
|
||||||
QHash<int, QByteArray> AudioDeviceList::_roles {
|
QHash<int, QByteArray> AudioDeviceList::_roles {
|
||||||
{ DisplayRole, "display" },
|
{ DeviceNameRole, "devicename" },
|
||||||
{ CheckStateRole, "selected" },
|
{ SelectedDesktopRole, "selectedDesktop" },
|
||||||
|
{ SelectedHMDRole, "selectedHMD" },
|
||||||
{ PeakRole, "peak" },
|
{ PeakRole, "peak" },
|
||||||
{ InfoRole, "info" }
|
{ InfoRole, "info" }
|
||||||
};
|
};
|
||||||
|
@ -68,15 +70,64 @@ static QString getTargetDevice(bool hmd, QAudio::Mode mode) {
|
||||||
|
|
||||||
Qt::ItemFlags AudioDeviceList::_flags { Qt::ItemIsSelectable | Qt::ItemIsEnabled };
|
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<AudioDevice> 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 {
|
QVariant AudioDeviceList::data(const QModelIndex& index, int role) const {
|
||||||
if (!index.isValid() || index.row() >= rowCount()) {
|
if (!index.isValid() || index.row() >= rowCount()) {
|
||||||
return QVariant();
|
return QVariant();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (role == DisplayRole) {
|
if (role == DeviceNameRole) {
|
||||||
return _devices.at(index.row())->display;
|
return _devices.at(index.row())->display;
|
||||||
} else if (role == CheckStateRole) {
|
} else if (role == SelectedDesktopRole) {
|
||||||
return _devices.at(index.row())->selected;
|
return _devices.at(index.row())->selectedDesktop;
|
||||||
|
} else if (role == SelectedHMDRole) {
|
||||||
|
return _devices.at(index.row())->selectedHMD;
|
||||||
} else if (role == InfoRole) {
|
} else if (role == InfoRole) {
|
||||||
return QVariant::fromValue<QAudioDeviceInfo>(_devices.at(index.row())->info);
|
return QVariant::fromValue<QAudioDeviceInfo>(_devices.at(index.row())->info);
|
||||||
} else {
|
} else {
|
||||||
|
@ -130,37 +181,48 @@ void AudioDeviceList::resetDevice(bool contextIsHMD) {
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioDeviceList::onDeviceChanged(const QAudioDeviceInfo& device) {
|
void AudioDeviceList::onDeviceChanged(const QAudioDeviceInfo& device, bool isHMD) {
|
||||||
auto oldDevice = _selectedDevice;
|
auto oldDevice = isHMD ? _selectedHMDDevice : _selectedDesktopDevice;
|
||||||
_selectedDevice = device;
|
QAudioDeviceInfo& selectedDevice = isHMD ? _selectedHMDDevice : _selectedDesktopDevice;
|
||||||
|
selectedDevice = device;
|
||||||
|
|
||||||
for (auto i = 0; i < rowCount(); ++i) {
|
for (auto i = 0; i < _devices.size(); ++i) {
|
||||||
AudioDevice& device = *_devices[i];
|
std::shared_ptr<AudioDevice> device = _devices[i];
|
||||||
|
bool &isSelected = isHMD ? device->selectedHMD : device->selectedDesktop;
|
||||||
if (device.selected && device.info != _selectedDevice) {
|
if (isSelected && device->info != selectedDevice) {
|
||||||
device.selected = false;
|
isSelected = false;
|
||||||
} else if (device.info == _selectedDevice) {
|
} else if (device->info == selectedDevice) {
|
||||||
device.selected = true;
|
isSelected = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
emit deviceChanged(_selectedDevice);
|
emit deviceChanged(selectedDevice);
|
||||||
emit dataChanged(createIndex(0, 0), createIndex(rowCount() - 1, 0));
|
emit dataChanged(createIndex(0, 0), createIndex(rowCount() - 1, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioDeviceList::onDevicesChanged(const QList<QAudioDeviceInfo>& devices) {
|
void AudioDeviceList::onDevicesChanged(const QList<QAudioDeviceInfo>& devices, bool isHMD) {
|
||||||
|
QAudioDeviceInfo& selectedDevice = isHMD ? _selectedHMDDevice : _selectedDesktopDevice;
|
||||||
|
|
||||||
|
const QString& savedDeviceName = isHMD ? _hmdSavedDeviceName : _desktopSavedDeviceName;
|
||||||
beginResetModel();
|
beginResetModel();
|
||||||
|
|
||||||
_devices.clear();
|
_devices.clear();
|
||||||
|
|
||||||
foreach(const QAudioDeviceInfo& deviceInfo, devices) {
|
foreach(const QAudioDeviceInfo& deviceInfo, devices) {
|
||||||
AudioDevice device;
|
AudioDevice device;
|
||||||
|
bool &isSelected = isHMD ? device.selectedHMD : device.selectedDesktop;
|
||||||
device.info = deviceInfo;
|
device.info = deviceInfo;
|
||||||
device.display = device.info.deviceName()
|
device.display = device.info.deviceName()
|
||||||
.replace("High Definition", "HD")
|
.replace("High Definition", "HD")
|
||||||
.remove("Device")
|
.remove("Device")
|
||||||
.replace(" )", ")");
|
.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));
|
_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::devicesChanged, this, &AudioDevices::onDevicesChanged, Qt::QueuedConnection);
|
||||||
connect(client.data(), &AudioClient::peakValueListChanged, &_inputs, &AudioInputDeviceList::onPeakValueListChanged, 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
|
// connections are made after client is initialized, so we must also fetch the devices
|
||||||
_inputs.onDeviceChanged(client->getActiveAudioDevice(QAudio::AudioInput));
|
const QList<QAudioDeviceInfo>& devicesInput = client->getAudioDevices(QAudio::AudioInput);
|
||||||
_outputs.onDeviceChanged(client->getActiveAudioDevice(QAudio::AudioOutput));
|
const QList<QAudioDeviceInfo>& devicesOutput = client->getAudioDevices(QAudio::AudioOutput);
|
||||||
_inputs.onDevicesChanged(client->getAudioDevices(QAudio::AudioInput));
|
//setup HMD devices
|
||||||
_outputs.onDevicesChanged(client->getAudioDevices(QAudio::AudioOutput));
|
_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) {
|
void AudioDevices::onContextChanged(const QString& context) {
|
||||||
_inputs.resetDevice(_contextIsHMD);
|
_inputs.resetDevice(_contextIsHMD);
|
||||||
_outputs.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();
|
QString deviceName = device.isNull() ? QString() : device.deviceName();
|
||||||
|
|
||||||
auto& setting = getSetting(_contextIsHMD, mode);
|
auto& setting = getSetting(isHMD, mode);
|
||||||
|
|
||||||
// check for a previous device
|
// check for a previous device
|
||||||
auto wasDefault = setting.get().isNull();
|
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) {
|
void AudioDevices::onDeviceChanged(QAudio::Mode mode, const QAudioDeviceInfo& device) {
|
||||||
if (mode == QAudio::AudioInput) {
|
if (mode == QAudio::AudioInput) {
|
||||||
if (_requestedInputDevice == device) {
|
if (_requestedInputDevice == device) {
|
||||||
onDeviceSelected(QAudio::AudioInput, device, _inputs._selectedDevice);
|
onDeviceSelected(QAudio::AudioInput, device,
|
||||||
|
_contextIsHMD ? _inputs._selectedHMDDevice : _inputs._selectedDesktopDevice,
|
||||||
|
_contextIsHMD);
|
||||||
_requestedInputDevice = QAudioDeviceInfo();
|
_requestedInputDevice = QAudioDeviceInfo();
|
||||||
}
|
}
|
||||||
_inputs.onDeviceChanged(device);
|
_inputs.onDeviceChanged(device, _contextIsHMD);
|
||||||
} else { // if (mode == QAudio::AudioOutput)
|
} else { // if (mode == QAudio::AudioOutput)
|
||||||
if (_requestedOutputDevice == device) {
|
if (_requestedOutputDevice == device) {
|
||||||
onDeviceSelected(QAudio::AudioOutput, device, _outputs._selectedDevice);
|
onDeviceSelected(QAudio::AudioOutput, device,
|
||||||
|
_contextIsHMD ? _outputs._selectedHMDDevice : _outputs._selectedDesktopDevice,
|
||||||
|
_contextIsHMD);
|
||||||
_requestedOutputDevice = QAudioDeviceInfo();
|
_requestedOutputDevice = QAudioDeviceInfo();
|
||||||
}
|
}
|
||||||
_outputs.onDeviceChanged(device);
|
_outputs.onDeviceChanged(device, _contextIsHMD);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioDevices::onDevicesChanged(QAudio::Mode mode, const QList<QAudioDeviceInfo>& devices) {
|
void AudioDevices::onDevicesChanged(QAudio::Mode mode, const QList<QAudioDeviceInfo>& devices) {
|
||||||
static std::once_flag once;
|
static std::once_flag once;
|
||||||
|
std::call_once(once, [&] {
|
||||||
|
//readout settings
|
||||||
|
auto client = DependencyManager::get<AudioClient>();
|
||||||
|
|
||||||
|
_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) {
|
if (mode == QAudio::AudioInput) {
|
||||||
_inputs.onDevicesChanged(devices);
|
_inputs.onDevicesChanged(devices, _contextIsHMD);
|
||||||
|
_inputs.onDevicesChanged(devices, !_contextIsHMD);
|
||||||
} else { // if (mode == QAudio::AudioOutput)
|
} 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) {
|
void AudioDevices::chooseInputDevice(const QAudioDeviceInfo& device, bool isHMD) {
|
||||||
auto client = DependencyManager::get<AudioClient>();
|
//check if current context equals device to change
|
||||||
_requestedInputDevice = device;
|
if (_contextIsHMD == isHMD) {
|
||||||
QMetaObject::invokeMethod(client.data(), "switchAudioDevice",
|
auto client = DependencyManager::get<AudioClient>();
|
||||||
Q_ARG(QAudio::Mode, QAudio::AudioInput),
|
_requestedInputDevice = device;
|
||||||
Q_ARG(const QAudioDeviceInfo&, 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) {
|
void AudioDevices::chooseOutputDevice(const QAudioDeviceInfo& device, bool isHMD) {
|
||||||
auto client = DependencyManager::get<AudioClient>();
|
//check if current context equals device to change
|
||||||
_requestedOutputDevice = device;
|
if (_contextIsHMD == isHMD) {
|
||||||
QMetaObject::invokeMethod(client.data(), "switchAudioDevice",
|
auto client = DependencyManager::get<AudioClient>();
|
||||||
Q_ARG(QAudio::Mode, QAudio::AudioOutput),
|
_requestedOutputDevice = device;
|
||||||
Q_ARG(const QAudioDeviceInfo&, 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,15 +25,16 @@ class AudioDevice {
|
||||||
public:
|
public:
|
||||||
QAudioDeviceInfo info;
|
QAudioDeviceInfo info;
|
||||||
QString display;
|
QString display;
|
||||||
bool selected { false };
|
bool selectedDesktop { false };
|
||||||
|
bool selectedHMD { false };
|
||||||
};
|
};
|
||||||
|
|
||||||
class AudioDeviceList : public QAbstractListModel {
|
class AudioDeviceList : public QAbstractListModel {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
AudioDeviceList(QAudio::Mode mode = QAudio::AudioOutput) : _mode(mode) {}
|
AudioDeviceList(QAudio::Mode mode = QAudio::AudioOutput);
|
||||||
~AudioDeviceList() = default;
|
virtual ~AudioDeviceList();
|
||||||
|
|
||||||
virtual std::shared_ptr<AudioDevice> newDevice(const AudioDevice& device)
|
virtual std::shared_ptr<AudioDevice> newDevice(const AudioDevice& device)
|
||||||
{ return std::make_shared<AudioDevice>(device); }
|
{ return std::make_shared<AudioDevice>(device); }
|
||||||
|
@ -52,8 +53,8 @@ signals:
|
||||||
void deviceChanged(const QAudioDeviceInfo& device);
|
void deviceChanged(const QAudioDeviceInfo& device);
|
||||||
|
|
||||||
protected slots:
|
protected slots:
|
||||||
void onDeviceChanged(const QAudioDeviceInfo& device);
|
void onDeviceChanged(const QAudioDeviceInfo& device, bool isHMD);
|
||||||
void onDevicesChanged(const QList<QAudioDeviceInfo>& devices);
|
void onDevicesChanged(const QList<QAudioDeviceInfo>& devices, bool isHMD);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
friend class AudioDevices;
|
friend class AudioDevices;
|
||||||
|
@ -61,8 +62,11 @@ protected:
|
||||||
static QHash<int, QByteArray> _roles;
|
static QHash<int, QByteArray> _roles;
|
||||||
static Qt::ItemFlags _flags;
|
static Qt::ItemFlags _flags;
|
||||||
const QAudio::Mode _mode;
|
const QAudio::Mode _mode;
|
||||||
QAudioDeviceInfo _selectedDevice;
|
QAudioDeviceInfo _selectedDesktopDevice;
|
||||||
|
QAudioDeviceInfo _selectedHMDDevice;
|
||||||
QList<std::shared_ptr<AudioDevice>> _devices;
|
QList<std::shared_ptr<AudioDevice>> _devices;
|
||||||
|
QString _hmdSavedDeviceName;
|
||||||
|
QString _desktopSavedDeviceName;
|
||||||
};
|
};
|
||||||
|
|
||||||
class AudioInputDevice : public AudioDevice {
|
class AudioInputDevice : public AudioDevice {
|
||||||
|
@ -102,7 +106,6 @@ protected:
|
||||||
void setPeakValuesEnabled(bool enable);
|
void setPeakValuesEnabled(bool enable);
|
||||||
bool _peakValuesEnabled { false };
|
bool _peakValuesEnabled { false };
|
||||||
};
|
};
|
||||||
|
|
||||||
class Audio;
|
class Audio;
|
||||||
|
|
||||||
class AudioDevices : public QObject {
|
class AudioDevices : public QObject {
|
||||||
|
@ -112,15 +115,18 @@ class AudioDevices : public QObject {
|
||||||
|
|
||||||
public:
|
public:
|
||||||
AudioDevices(bool& contextIsHMD);
|
AudioDevices(bool& contextIsHMD);
|
||||||
void chooseInputDevice(const QAudioDeviceInfo& device);
|
virtual ~AudioDevices();
|
||||||
void chooseOutputDevice(const QAudioDeviceInfo& device);
|
|
||||||
|
void chooseInputDevice(const QAudioDeviceInfo& device, bool isHMD);
|
||||||
|
void chooseOutputDevice(const QAudioDeviceInfo& device, bool isHMD);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void nop();
|
void nop();
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void onContextChanged(const QString& context);
|
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 onDeviceChanged(QAudio::Mode mode, const QAudioDeviceInfo& device);
|
||||||
void onDevicesChanged(QAudio::Mode mode, const QList<QAudioDeviceInfo>& devices);
|
void onDevicesChanged(QAudio::Mode mode, const QList<QAudioDeviceInfo>& devices);
|
||||||
|
|
||||||
|
|
|
@ -1725,14 +1725,6 @@ int AudioClient::setOutputBufferSize(int numFrames, bool persist) {
|
||||||
if (persist) {
|
if (persist) {
|
||||||
_outputBufferSizeFrames.set(numFrames);
|
_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;
|
return numFrames;
|
||||||
}
|
}
|
||||||
|
|
|
@ -140,10 +140,8 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js");
|
||||||
};
|
};
|
||||||
|
|
||||||
this.setIgnoreTablet = function() {
|
this.setIgnoreTablet = function() {
|
||||||
if (HMD.tabletID !== _this.tabletID) {
|
RayPick.setIgnoreOverlays(_this.leftControllerRayPick, [HMD.tabletID]);
|
||||||
RayPick.setIgnoreOverlays(_this.leftControllerRayPick, [HMD.tabletID]);
|
RayPick.setIgnoreOverlays(_this.rightControllerRayPick, [HMD.tabletID]);
|
||||||
RayPick.setIgnoreOverlays(_this.rightControllerRayPick, [HMD.tabletID]);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
this.update = function () {
|
this.update = function () {
|
||||||
|
|
|
@ -16,6 +16,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js");
|
||||||
Script.include("/~/system/libraries/controllers.js");
|
Script.include("/~/system/libraries/controllers.js");
|
||||||
|
|
||||||
(function() {
|
(function() {
|
||||||
|
var TouchEventUtils = Script.require("/~/system/libraries/touchEventUtils.js");
|
||||||
var halfPath = {
|
var halfPath = {
|
||||||
type: "line3d",
|
type: "line3d",
|
||||||
color: COLORS_GRAB_SEARCHING_HALF_SQUEEZE,
|
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_STRENGTH = 1.0;
|
||||||
var HAPTIC_STYLUS_DURATION = 20.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) {
|
function distance2D(a, b) {
|
||||||
var dx = (a.x - b.x);
|
var dx = (a.x - b.x);
|
||||||
var dy = (a.y - b.y);
|
var dy = (a.y - b.y);
|
||||||
|
@ -277,16 +98,11 @@ Script.include("/~/system/libraries/controllers.js");
|
||||||
function OverlayLaserInput(hand) {
|
function OverlayLaserInput(hand) {
|
||||||
this.hand = hand;
|
this.hand = hand;
|
||||||
this.active = false;
|
this.active = false;
|
||||||
this.previousLaserClikcedTarget = false;
|
this.previousLaserClickedTarget = false;
|
||||||
this.laserPressingTarget = false;
|
this.laserPressingTarget = false;
|
||||||
this.tabletScreenID = null;
|
|
||||||
this.mode = "none";
|
this.mode = "none";
|
||||||
this.laserTargetID = null;
|
|
||||||
this.laserTarget = null;
|
this.laserTarget = null;
|
||||||
this.pressEnterLaserTarget = null;
|
this.pressEnterLaserTarget = null;
|
||||||
this.hover = false;
|
|
||||||
this.target = null;
|
|
||||||
this.lastValidTargetID = this.tabletTargetID;
|
|
||||||
|
|
||||||
|
|
||||||
this.parameters = makeDispatcherModuleParameters(
|
this.parameters = makeDispatcherModuleParameters(
|
||||||
|
@ -307,23 +123,51 @@ Script.include("/~/system/libraries/controllers.js");
|
||||||
return (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand;
|
return (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand;
|
||||||
};
|
};
|
||||||
|
|
||||||
this.stealTouchFocus = function(laserTarget) {
|
this.hasTouchFocus = function(laserTarget) {
|
||||||
this.requestTouchFocus(laserTarget);
|
return (laserTarget.overlayID === this.hoverOverlay);
|
||||||
};
|
};
|
||||||
|
|
||||||
this.requestTouchFocus = function(laserTarget) {
|
this.requestTouchFocus = function(laserTarget) {
|
||||||
if (laserTarget !== null || laserTarget !== undefined) {
|
if (laserTarget.overlayID &&
|
||||||
sendHoverEnterEventToLaserTarget(this.hand, this.laserTarget);
|
laserTarget.overlayID !== this.hoverOverlay) {
|
||||||
this.lastValidTargetID = laserTarget;
|
this.hoverOverlay = laserTarget.overlayID;
|
||||||
|
TouchEventUtils.sendHoverEnterEventToTouchTarget(this.hand, laserTarget);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.relinquishTouchFocus = function() {
|
this.relinquishTouchFocus = function() {
|
||||||
// send hover leave event.
|
// send hover leave event.
|
||||||
var pointerEvent = { type: "Move", id: this.hand + 1 };
|
if (this.hoverOverlay) {
|
||||||
Overlays.sendMouseMoveOnOverlay(this.lastValidTargetID, pointerEvent);
|
var pointerEvent = { type: "Move", id: this.hand + 1 };
|
||||||
Overlays.sendHoverOverOverlay(this.lastValidTargetID, pointerEvent);
|
Overlays.sendMouseMoveOnOverlay(this.hoverOverlay, pointerEvent);
|
||||||
Overlays.sendHoverLeaveOverlay(this.lastValidID, 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) {
|
this.updateLaserPointer = function(controllerData) {
|
||||||
|
@ -345,38 +189,23 @@ Script.include("/~/system/libraries/controllers.js");
|
||||||
this.processControllerTriggers = function(controllerData) {
|
this.processControllerTriggers = function(controllerData) {
|
||||||
if (controllerData.triggerClicks[this.hand]) {
|
if (controllerData.triggerClicks[this.hand]) {
|
||||||
this.mode = "full";
|
this.mode = "full";
|
||||||
this.laserPressingTarget = true;
|
|
||||||
this.hover = false;
|
|
||||||
} else if (controllerData.triggerValues[this.hand] > TRIGGER_ON_VALUE) {
|
} else if (controllerData.triggerValues[this.hand] > TRIGGER_ON_VALUE) {
|
||||||
this.mode = "half";
|
this.mode = "half";
|
||||||
this.laserPressingTarget = false;
|
|
||||||
this.hover = true;
|
|
||||||
this.requestTouchFocus(this.laserTargetID);
|
|
||||||
} else {
|
} else {
|
||||||
this.mode = "none";
|
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 () {
|
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);
|
Controller.triggerHapticPulse(HAPTIC_STYLUS_STRENGTH, HAPTIC_STYLUS_DURATION, this.hand);
|
||||||
|
|
||||||
this.touchingEnterTimer = 0;
|
this.touchingEnterTimer = 0;
|
||||||
this.pressEnterLaserTarget = this.laserTarget;
|
this.pressEnterLaserTarget = this.laserTarget;
|
||||||
this.deadspotExpired = false;
|
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;
|
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.
|
// special case to handle home button.
|
||||||
if (this.laserTargetID === HMD.homeButtonID) {
|
if (this.laserTarget.overlayID === HMD.homeButtonID) {
|
||||||
Messages.sendLocalMessage("home", this.laserTargetID);
|
Messages.sendLocalMessage("home", this.laserTarget.overlayID);
|
||||||
}
|
}
|
||||||
|
|
||||||
// send press event
|
// send press event
|
||||||
if (this.deadspotExpired) {
|
if (this.deadspotExpired) {
|
||||||
sendTouchEndEventToLaserTarget(this.hand, this.laserTarget);
|
TouchEventUtils.sendTouchEndEventToTouchTarget(this.hand, this.laserTarget);
|
||||||
} else {
|
} 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;
|
this.touchingEnterTimer += dt;
|
||||||
|
|
||||||
if (this.laserTarget) {
|
if (this.laserTarget) {
|
||||||
var POINTER_PRESS_TO_MOVE_DELAY = 0.33; // seconds
|
if (controllerData.triggerClicks[this.hand]) {
|
||||||
if (this.deadspotExpired || this.touchingEnterTimer > POINTER_PRESS_TO_MOVE_DELAY ||
|
var POINTER_PRESS_TO_MOVE_DELAY = 0.33; // seconds
|
||||||
distance2D( this.laserTarget.position2D,
|
if (this.deadspotExpired || this.touchingEnterTimer > POINTER_PRESS_TO_MOVE_DELAY ||
|
||||||
this.pressEnterLaserTarget.position2D) > this.deadspotRadius) {
|
distance2D(this.laserTarget.position2D,
|
||||||
sendTouchMoveEventToLaserTarget(this.hand, this.laserTarget);
|
this.pressEnterLaserTarget.position2D) > this.deadspotRadius) {
|
||||||
this.deadspotExpired = true;
|
TouchEventUtils.sendTouchMoveEventToTouchTarget(this.hand, this.laserTarget);
|
||||||
|
this.deadspotExpired = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.laserPressingTarget = false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.laserPressingTarget = false;
|
this.laserPressingTarget = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.releaseTouchEvent = function() {
|
this.processLaser = function(controllerData) {
|
||||||
sendTouchEndEventToLaserTarget(this.hand, this.pressEnterLaserTarget);
|
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.grabModuleWantsNearbyOverlay = function(controllerData) {
|
||||||
this.updateLaserTargets = function(controllerData) {
|
if (controllerData.triggerValues[this.hand] > TRIGGER_ON_VALUE) {
|
||||||
var intersection = controllerData.rayPicks[this.hand];
|
var nearGrabName = this.hand === RIGHT_HAND ? "RightNearParentingGrabOverlay" : "LeftNearParentingGrabOverlay";
|
||||||
this.laserTargetID = intersection.objectID;
|
var nearGrabModule = getEnabledModuleByName(nearGrabName);
|
||||||
this.laserTarget = calculateLaserTargetFromOverlay(intersection.intersection, intersection.objectID);
|
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) {
|
this.shouldExit = function(controllerData) {
|
||||||
var intersection = controllerData.rayPicks[this.hand];
|
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 offOverlay = (intersection.type !== RayPick.INTERSECTED_OVERLAY);
|
||||||
var triggerOff = (controllerData.triggerValues[this.hand] < TRIGGER_OFF_VALUE);
|
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.exitModule = function() {
|
||||||
this.releaseTouchEvent();
|
if (this.laserPressingTarget) {
|
||||||
|
this.deadspotExpired = true;
|
||||||
|
this.laserPressExit();
|
||||||
|
this.laserPressingTarget = false;
|
||||||
|
}
|
||||||
|
this.deleteContextOverlay();
|
||||||
this.relinquishTouchFocus();
|
this.relinquishTouchFocus();
|
||||||
this.reset();
|
this.reset();
|
||||||
this.updateLaserPointer();
|
this.updateLaserPointer();
|
||||||
|
@ -444,12 +316,6 @@ Script.include("/~/system/libraries/controllers.js");
|
||||||
};
|
};
|
||||||
|
|
||||||
this.reset = function() {
|
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.mode = "none";
|
||||||
this.active = false;
|
this.active = false;
|
||||||
};
|
};
|
||||||
|
@ -467,35 +333,13 @@ Script.include("/~/system/libraries/controllers.js");
|
||||||
};
|
};
|
||||||
|
|
||||||
this.isReady = function (controllerData) {
|
this.isReady = function (controllerData) {
|
||||||
this.target = null;
|
if (this.processLaser(controllerData)) {
|
||||||
var intersection = controllerData.rayPicks[this.hand];
|
return makeRunningValues(true, [], []);
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
this.reset();
|
|
||||||
return makeRunningValues(false, [], []);
|
return makeRunningValues(false, [], []);
|
||||||
};
|
};
|
||||||
|
|
||||||
this.run = function (controllerData, deltaTime) {
|
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) {
|
if (!this.previousLaserClickedTarget && this.laserPressingTarget) {
|
||||||
this.laserPressEnter();
|
this.laserPressEnter();
|
||||||
}
|
}
|
||||||
|
@ -508,11 +352,11 @@ Script.include("/~/system/libraries/controllers.js");
|
||||||
this.laserPressing(controllerData, deltaTime);
|
this.laserPressing(controllerData, deltaTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.hover) {
|
if (this.processLaser(controllerData)) {
|
||||||
this.hovering();
|
return makeRunningValues(true, [], []);
|
||||||
|
} else {
|
||||||
|
return makeRunningValues(false, [], []);
|
||||||
}
|
}
|
||||||
|
|
||||||
return makeRunningValues(true, [], []);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
this.cleanup = function () {
|
this.cleanup = function () {
|
||||||
|
|
|
@ -16,238 +16,14 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js");
|
||||||
Script.include("/~/system/libraries/controllers.js");
|
Script.include("/~/system/libraries/controllers.js");
|
||||||
|
|
||||||
(function() {
|
(function() {
|
||||||
|
var TouchEventUtils = Script.require("/~/system/libraries/touchEventUtils.js");
|
||||||
// triggered when stylus presses a web overlay/entity
|
// triggered when stylus presses a web overlay/entity
|
||||||
var HAPTIC_STYLUS_STRENGTH = 1.0;
|
var HAPTIC_STYLUS_STRENGTH = 1.0;
|
||||||
var HAPTIC_STYLUS_DURATION = 20.0;
|
var HAPTIC_STYLUS_DURATION = 20.0;
|
||||||
|
|
||||||
var WEB_DISPLAY_STYLUS_DISTANCE = 0.5;
|
var WEB_DISPLAY_STYLUS_DISTANCE = 0.5;
|
||||||
var WEB_STYLUS_LENGTH = 0.2;
|
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
|
var WEB_TOUCH_Y_OFFSET = 0.105; // 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
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function isNearStylusTarget(stylusTargets, edgeBorder, minNormalDistance, maxNormalDistance) {
|
function isNearStylusTarget(stylusTargets, edgeBorder, minNormalDistance, maxNormalDistance) {
|
||||||
for (var i = 0; i < stylusTargets.length; i++) {
|
for (var i = 0; i < stylusTargets.length; i++) {
|
||||||
|
@ -330,7 +106,7 @@ Script.include("/~/system/libraries/controllers.js");
|
||||||
100);
|
100);
|
||||||
|
|
||||||
this.getOtherHandController = function() {
|
this.getOtherHandController = function() {
|
||||||
return (this.hand === RIGHT_HAND) ? Controller.Standard.LeftHand : Controller.Standard.RightHand;
|
return (this.hand === RIGHT_HAND) ? leftTabletStylusInput : rightTabletStylusInput;
|
||||||
};
|
};
|
||||||
|
|
||||||
this.handToController = function() {
|
this.handToController = function() {
|
||||||
|
@ -430,12 +206,12 @@ Script.include("/~/system/libraries/controllers.js");
|
||||||
stylusTarget.entityID !== this.hoverEntity &&
|
stylusTarget.entityID !== this.hoverEntity &&
|
||||||
stylusTarget.entityID !== this.getOtherHandController().hoverEntity) {
|
stylusTarget.entityID !== this.getOtherHandController().hoverEntity) {
|
||||||
this.hoverEntity = stylusTarget.entityID;
|
this.hoverEntity = stylusTarget.entityID;
|
||||||
sendHoverEnterEventToStylusTarget(this.hand, stylusTarget);
|
TouchEventUtils.sendHoverEnterEventToTouchTarget(this.hand, stylusTarget);
|
||||||
} else if (stylusTarget.overlayID &&
|
} else if (stylusTarget.overlayID &&
|
||||||
stylusTarget.overlayID !== this.hoverOverlay &&
|
stylusTarget.overlayID !== this.hoverOverlay &&
|
||||||
stylusTarget.overlayID !== this.getOtherHandController().hoverOverlay) {
|
stylusTarget.overlayID !== this.getOtherHandController().hoverOverlay) {
|
||||||
this.hoverOverlay = stylusTarget.overlayID;
|
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++) {
|
for (i = 0; i < candidateEntities.length; i++) {
|
||||||
props = candidateEntities[i];
|
props = candidateEntities[i];
|
||||||
if (props && props.type === "Web") {
|
if (props && props.type === "Web") {
|
||||||
stylusTarget = calculateStylusTargetFromEntity(this.stylusTip, candidateEntities[i]);
|
stylusTarget = TouchEventUtils.calculateTouchTargetFromEntity(this.stylusTip, candidateEntities[i]);
|
||||||
if (stylusTarget) {
|
if (stylusTarget) {
|
||||||
stylusTargets.push(stylusTarget);
|
stylusTargets.push(stylusTarget);
|
||||||
}
|
}
|
||||||
|
@ -502,7 +278,7 @@ Script.include("/~/system/libraries/controllers.js");
|
||||||
// add the tabletScreen, if it is valid
|
// add the tabletScreen, if it is valid
|
||||||
if (HMD.tabletScreenID && HMD.tabletScreenID !== NULL_UUID &&
|
if (HMD.tabletScreenID && HMD.tabletScreenID !== NULL_UUID &&
|
||||||
Overlays.getProperty(HMD.tabletScreenID, "visible")) {
|
Overlays.getProperty(HMD.tabletScreenID, "visible")) {
|
||||||
stylusTarget = calculateStylusTargetFromOverlay(this.stylusTip, HMD.tabletScreenID);
|
stylusTarget = TouchEventUtils.calculateTouchTargetFromOverlay(this.stylusTip, HMD.tabletScreenID);
|
||||||
if (stylusTarget) {
|
if (stylusTarget) {
|
||||||
stylusTargets.push(stylusTarget);
|
stylusTargets.push(stylusTarget);
|
||||||
}
|
}
|
||||||
|
@ -511,7 +287,7 @@ Script.include("/~/system/libraries/controllers.js");
|
||||||
// add the tablet home button.
|
// add the tablet home button.
|
||||||
if (HMD.homeButtonID && HMD.homeButtonID !== NULL_UUID &&
|
if (HMD.homeButtonID && HMD.homeButtonID !== NULL_UUID &&
|
||||||
Overlays.getProperty(HMD.homeButtonID, "visible")) {
|
Overlays.getProperty(HMD.homeButtonID, "visible")) {
|
||||||
stylusTarget = calculateStylusTargetFromOverlay(this.stylusTip, HMD.homeButtonID);
|
stylusTarget = TouchEventUtils.calculateTouchTargetFromOverlay(this.stylusTip, HMD.homeButtonID);
|
||||||
if (stylusTarget) {
|
if (stylusTarget) {
|
||||||
stylusTargets.push(stylusTarget);
|
stylusTargets.push(stylusTarget);
|
||||||
}
|
}
|
||||||
|
@ -530,9 +306,9 @@ Script.include("/~/system/libraries/controllers.js");
|
||||||
|
|
||||||
var sensorScaleFactor = MyAvatar.sensorToWorldScale;
|
var sensorScaleFactor = MyAvatar.sensorToWorldScale;
|
||||||
this.isNearStylusTarget = isNearStylusTarget(stylusTargets,
|
this.isNearStylusTarget = isNearStylusTarget(stylusTargets,
|
||||||
(EDGE_BORDER + hysteresisOffset) * sensorScaleFactor,
|
(EDGE_BORDER + hysteresisOffset) * sensorScaleFactor,
|
||||||
(TABLET_MIN_TOUCH_DISTANCE - hysteresisOffset) * sensorScaleFactor,
|
(TABLET_MIN_TOUCH_DISTANCE - hysteresisOffset) * sensorScaleFactor,
|
||||||
(WEB_DISPLAY_STYLUS_DISTANCE + hysteresisOffset) * sensorScaleFactor);
|
(WEB_DISPLAY_STYLUS_DISTANCE + hysteresisOffset) * sensorScaleFactor);
|
||||||
|
|
||||||
if (this.isNearStylusTarget) {
|
if (this.isNearStylusTarget) {
|
||||||
if (!this.useFingerInsteadOfStylus) {
|
if (!this.useFingerInsteadOfStylus) {
|
||||||
|
@ -556,12 +332,12 @@ Script.include("/~/system/libraries/controllers.js");
|
||||||
|
|
||||||
this.requestTouchFocus(nearestStylusTarget);
|
this.requestTouchFocus(nearestStylusTarget);
|
||||||
|
|
||||||
if (!stylusTargetHasKeyboardFocus(nearestStylusTarget)) {
|
if (!TouchEventUtils.touchTargetHasKeyboardFocus(nearestStylusTarget)) {
|
||||||
setKeyboardFocusOnStylusTarget(nearestStylusTarget);
|
TouchEventUtils.setKeyboardFocusOnTouchTarget(nearestStylusTarget);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.hasTouchFocus(nearestStylusTarget)) {
|
if (this.hasTouchFocus(nearestStylusTarget) && !this.stylusTouchingTarget) {
|
||||||
sendHoverOverEventToStylusTarget(this.hand, nearestStylusTarget);
|
TouchEventUtils.sendHoverOverEventToTouchTarget(this.hand, nearestStylusTarget);
|
||||||
}
|
}
|
||||||
|
|
||||||
// filter out presses when tip is moving away from tablet.
|
// filter out presses when tip is moving away from tablet.
|
||||||
|
@ -592,14 +368,14 @@ Script.include("/~/system/libraries/controllers.js");
|
||||||
|
|
||||||
this.stylusTouchingEnter = function () {
|
this.stylusTouchingEnter = function () {
|
||||||
this.stealTouchFocus(this.stylusTarget);
|
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);
|
Controller.triggerHapticPulse(HAPTIC_STYLUS_STRENGTH, HAPTIC_STYLUS_DURATION, this.hand);
|
||||||
|
|
||||||
this.touchingEnterTimer = 0;
|
this.touchingEnterTimer = 0;
|
||||||
this.touchingEnterStylusTarget = this.stylusTarget;
|
this.touchingEnterStylusTarget = this.stylusTarget;
|
||||||
this.deadspotExpired = false;
|
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;
|
this.deadspotRadius = TOUCH_PRESS_TO_MOVE_DEADSPOT;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -616,9 +392,9 @@ Script.include("/~/system/libraries/controllers.js");
|
||||||
|
|
||||||
// send press event
|
// send press event
|
||||||
if (this.deadspotExpired) {
|
if (this.deadspotExpired) {
|
||||||
sendTouchEndEventToStylusTarget(this.hand, this.stylusTarget);
|
TouchEventUtils.sendTouchEndEventToTouchTarget(this.hand, this.stylusTarget);
|
||||||
} else {
|
} 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;
|
this.touchingEnterTimer += dt;
|
||||||
|
|
||||||
if (this.stylusTarget.entityID) {
|
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) {
|
} 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;
|
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 ||
|
if (this.deadspotExpired || this.touchingEnterTimer > POINTER_PRESS_TO_MOVE_DELAY ||
|
||||||
distance2D(this.stylusTarget.position2D,
|
distance2D(this.stylusTarget.position2D,
|
||||||
this.touchingEnterStylusTarget.position2D) > this.deadspotRadius) {
|
this.touchingEnterStylusTarget.position2D) > this.deadspotRadius) {
|
||||||
sendTouchMoveEventToStylusTarget(this.hand, this.stylusTarget);
|
TouchEventUtils.sendTouchMoveEventToTouchTarget(this.hand, this.stylusTarget);
|
||||||
this.deadspotExpired = true;
|
this.deadspotExpired = true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -654,12 +430,11 @@ Script.include("/~/system/libraries/controllers.js");
|
||||||
};
|
};
|
||||||
|
|
||||||
this.overlayLaserActive = function(controllerData) {
|
this.overlayLaserActive = function(controllerData) {
|
||||||
var overlayLaserModule =
|
var rightOverlayLaserModule = getEnabledModuleByName("RightOverlayLaserInput");
|
||||||
getEnabledModuleByName(this.hand === RIGHT_HAND ? "RightOverlayLaserInput" : "LeftOverlayLaserInput");
|
var leftOverlayLaserModule = getEnabledModuleByName("LeftOverlayLaserInput");
|
||||||
if (overlayLaserModule) {
|
var rightModuleRunning = rightOverlayLaserModule ? !rightOverlayLaserModule.shouldExit(controllerData) : false;
|
||||||
return overlayLaserModule.isReady(controllerData).active;
|
var leftModuleRunning = leftOverlayLaserModule ? !leftOverlayLaserModule.shouldExit(controllerData) : false;
|
||||||
}
|
return leftModuleRunning || rightModuleRunning;
|
||||||
return false;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
this.isReady = function (controllerData) {
|
this.isReady = function (controllerData) {
|
||||||
|
|
|
@ -49,9 +49,9 @@ function calcSpawnInfo(hand, landscape) {
|
||||||
var headRot = (HMD.active && Camera.mode === "first person") ? HMD.orientation : Camera.orientation;
|
var headRot = (HMD.active && Camera.mode === "first person") ? HMD.orientation : Camera.orientation;
|
||||||
|
|
||||||
var forward = Quat.getForward(headRot);
|
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));
|
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 {
|
return {
|
||||||
position: finalPosition,
|
position: finalPosition,
|
||||||
rotation: landscape ? Quat.multiply(orientation, ROT_LANDSCAPE) : Quat.multiply(orientation, ROT_Y_180)
|
rotation: landscape ? Quat.multiply(orientation, ROT_LANDSCAPE) : Quat.multiply(orientation, ROT_Y_180)
|
||||||
|
|
|
@ -318,6 +318,8 @@ if (typeof module !== 'undefined') {
|
||||||
makeRunningValues: makeRunningValues,
|
makeRunningValues: makeRunningValues,
|
||||||
LEFT_HAND: LEFT_HAND,
|
LEFT_HAND: LEFT_HAND,
|
||||||
RIGHT_HAND: RIGHT_HAND,
|
RIGHT_HAND: RIGHT_HAND,
|
||||||
BUMPER_ON_VALUE: BUMPER_ON_VALUE
|
BUMPER_ON_VALUE: BUMPER_ON_VALUE,
|
||||||
|
projectOntoOverlayXYPlane: projectOntoOverlayXYPlane,
|
||||||
|
projectOntoEntityXYPlane: projectOntoEntityXYPlane
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
270
scripts/system/libraries/touchEventUtils.js
Normal file
270
scripts/system/libraries/touchEventUtils.js
Normal file
|
@ -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
|
||||||
|
};
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
var request = Script.require('request').request;
|
var request = Script.require('request').request;
|
||||||
|
|
||||||
|
var WANT_DEBUG = Settings.getValue('MAKE_USER_CONNECTION_DEBUG', false);
|
||||||
var LABEL = "makeUserConnection";
|
var LABEL = "makeUserConnection";
|
||||||
var MAX_AVATAR_DISTANCE = 0.2; // m
|
var MAX_AVATAR_DISTANCE = 0.2; // m
|
||||||
var GRIP_MIN = 0.75; // goes from 0-1, so 75% pressed is pressed
|
var GRIP_MIN = 0.75; // goes from 0-1, so 75% pressed is pressed
|
||||||
|
@ -120,6 +121,9 @@
|
||||||
var successfulHandshakeSound;
|
var successfulHandshakeSound;
|
||||||
|
|
||||||
function debug() {
|
function debug() {
|
||||||
|
if (!WANT_DEBUG) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
var stateString = "<" + STATE_STRINGS[state] + ">";
|
var stateString = "<" + STATE_STRINGS[state] + ">";
|
||||||
var connecting = "[" + connectingId + "/" + connectingHandJointIndex + "]";
|
var connecting = "[" + connectingId + "/" + connectingHandJointIndex + "]";
|
||||||
var current = "[" + currentHand + "/" + currentHandJointIndex + "]"
|
var current = "[" + currentHand + "/" + currentHandJointIndex + "]"
|
||||||
|
@ -372,7 +376,7 @@
|
||||||
var myHeadIndex = MyAvatar.getJointIndex("Head");
|
var myHeadIndex = MyAvatar.getJointIndex("Head");
|
||||||
var otherHeadIndex = avatar.getJointIndex("Head");
|
var otherHeadIndex = avatar.getJointIndex("Head");
|
||||||
var diff = (avatar.getJointPosition(otherHeadIndex).y - MyAvatar.getJointPosition(myHeadIndex).y) / 2;
|
var diff = (avatar.getJointPosition(otherHeadIndex).y - MyAvatar.getJointPosition(myHeadIndex).y) / 2;
|
||||||
print("head height difference: " + diff);
|
debug("head height difference: " + diff);
|
||||||
updateAnimationData(diff);
|
updateAnimationData(diff);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -571,7 +571,7 @@
|
||||||
|
|
||||||
function onTabletScreenChanged(type, url) {
|
function onTabletScreenChanged(type, url) {
|
||||||
// Opened/closed dialog in tablet or window.
|
// 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 (type === "Web" && url.slice(-RECORD_URL.length) === RECORD_URL) {
|
||||||
if (Dialog.finishOnOpen()) {
|
if (Dialog.finishOnOpen()) {
|
||||||
|
|
Loading…
Reference in a new issue