This commit is contained in:
Daniela 2017-09-21 16:20:51 +01:00
commit d960a7c42e
24 changed files with 927 additions and 688 deletions

View file

@ -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

View file

@ -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);

View file

@ -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" }
] ]
} }

View 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
}
}

View file

@ -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 }
}
} }
} }

View 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"
}
}

View file

@ -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"
} }

View file

@ -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

View file

@ -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

View file

@ -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);
} }
} }

View file

@ -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);
} }
} }

View file

@ -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);
} }

View file

@ -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);

View file

@ -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);
}
} }

View file

@ -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);

View file

@ -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;
} }

View file

@ -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 () {

View file

@ -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 () {

View file

@ -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) {

View file

@ -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)

View file

@ -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
}; };
} }

View 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
};

View file

@ -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);
} }
} }

View file

@ -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()) {