diff --git a/interface/resources/images/loader-calibrate-blue.png b/interface/resources/images/loader-calibrate-blue.png new file mode 100644 index 0000000000..808f5fa3d2 Binary files /dev/null and b/interface/resources/images/loader-calibrate-blue.png differ diff --git a/interface/resources/images/loader-calibrate-white.png b/interface/resources/images/loader-calibrate-white.png new file mode 100644 index 0000000000..3b1b7b0e21 Binary files /dev/null and b/interface/resources/images/loader-calibrate-white.png differ diff --git a/interface/resources/qml/controls-uit/CheckBox.qml b/interface/resources/qml/controls-uit/CheckBox.qml index a637207781..36161e3c3e 100644 --- a/interface/resources/qml/controls-uit/CheckBox.qml +++ b/interface/resources/qml/controls-uit/CheckBox.qml @@ -22,8 +22,8 @@ Original.CheckBox { readonly property bool isLightColorScheme: colorScheme == hifi.colorSchemes.light property bool isRedCheck: false property int boxSize: 14 + property int boxRadius: 3 property bool wrap: true; - readonly property int boxRadius: 3 readonly property int checkSize: Math.max(boxSize - 8, 10) readonly property int checkRadius: 2 activeFocusOnPress: true diff --git a/interface/resources/qml/controls-uit/ComboBox.qml b/interface/resources/qml/controls-uit/ComboBox.qml index be6a439e57..3ce297ef80 100644 --- a/interface/resources/qml/controls-uit/ComboBox.qml +++ b/interface/resources/qml/controls-uit/ComboBox.qml @@ -20,6 +20,7 @@ FocusScope { HifiConstants { id: hifi } property alias model: comboBox.model; + property alias editable: comboBox.editable property alias comboBox: comboBox readonly property alias currentText: comboBox.currentText; property alias currentIndex: comboBox.currentIndex; diff --git a/interface/resources/qml/hifi/tablet/CalibratingScreen.qml b/interface/resources/qml/hifi/tablet/CalibratingScreen.qml new file mode 100644 index 0000000000..338a76989f --- /dev/null +++ b/interface/resources/qml/hifi/tablet/CalibratingScreen.qml @@ -0,0 +1,213 @@ +// +// Created by Dante Ruiz on 6/1/17. +// 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.5 + +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import QtGraphicalEffects 1.0 +import QtQuick.Controls.Styles 1.4 +import "../../styles-uit" +import "../../controls" +import "../../controls-uit" as HifiControls + + +Rectangle { + id: info + + + signal canceled() + signal restart() + + property int count: 3 + property string calibratingText: "CALIBRATING..." + property string calibratingCountText: "CALIBRATION STARTING IN" + property string calibrationSuccess: "CALIBRATION COMPLETED" + property string calibrationFailed: "CALIBRATION FAILED" + + HifiConstants { id: hifi } + visible: true + color: hifi.colors.baseGray + + property string whiteIndicator: "../../../images/loader-calibrate-white.png" + property string blueIndicator: "../../../images/loader-calibrate-blue.png" + + Image { + id: busyIndicator + width: 350 + height: 350 + + property bool running: true + + anchors { + horizontalCenter: parent.horizontalCenter + top: parent.top + topMargin: 60 + } + visible: busyIndicator.running + source: blueIndicator + NumberAnimation on rotation { + id: busyRotation + running: busyIndicator.running + loops: Animation.Infinite + duration: 1000 + from: 0 ; to: 360 + } + } + + + HiFiGlyphs { + id: image + text: hifi.glyphs.avatar1 + size: 190 + color: hifi.colors.white + + anchors { + top: busyIndicator.top + topMargin: 40 + horizontalCenter: busyIndicator.horizontalCenter + } + } + + RalewayBold { + id: statusText + text: info.calibratingCountText + size: 16 + color: hifi.colors.blueHighlight + + anchors { + top: image.bottom + topMargin: 15 + horizontalCenter: image.horizontalCenter + } + } + + + RalewayBold { + id: countDown + text: info.count + color: hifi.colors.blueHighlight + + anchors { + top: statusText.bottom + topMargin: 12 + horizontalCenter: statusText.horizontalCenter + } + } + + + RalewayBold { + id: directions + + anchors { + top: busyIndicator.bottom + topMargin: 100 + horizontalCenter: parent.horizontalCenter + } + + color: hifi.colors.white + size: hifi.fontSizes.rootMenuDisclosure + text: "Please stand in a T-Pose during calibration" + } + + NumberAnimation { + id: numberAnimation + target: info + property: "count" + to: 0 + } + + Timer { + id: timer + + repeat: false + interval: 3000 + onTriggered: { + info.calibrating(); + } + } + + Timer { + id: closeWindow + repeat: false + interval: 3000 + onTriggered: stack.pop(); + } + + Row { + + spacing: 20 + anchors { + top: directions.bottom + topMargin: 30 + horizontalCenter: parent.horizontalCenter + } + + + HifiControls.Button { + width: 120 + height: 30 + color: hifi.buttons.red + text: "RESTART" + + onClicked: { + restart(); + numberAnimation.stop(); + info.count = (timer.interval / 1000); + numberAnimation.start(); + timer.restart(); + } + } + + HifiControls.Button { + width: 120 + height: 30 + color: hifi.buttons.black + text: "CANCEL" + + onClicked: { + canceled(); + stack.pop() + } + } + } + + function start(interval, countNumber) { + countDown.visible = true; + statusText.color = hifi.colors.blueHighlight; + numberAnimation.duration = interval + info.count = countNumber; + timer.interval = interval; + numberAnimation.start(); + timer.start(); + } + + function calibrating() { + countDown.visible = false; + busyIndicator.source = whiteIndicator; + busyRotation.from = 360 + busyRotation.to = 0 + statusText.text = info.calibratingText; + statusText.color = hifi.colors.white + } + + function success() { + busyIndicator.running = false; + statusText.text = info.calibrationSuccess + statusText.color = hifi.colors.greenHighlight + closeWindow.start(); + } + + function failure() { + busyIndicator.running = false; + statusText.text = info.calibrationFailed + statusText.color = hifi.colors.redHighlight + closeWindow.start(); + } +} diff --git a/interface/resources/qml/hifi/tablet/ControllerSettings.qml b/interface/resources/qml/hifi/tablet/ControllerSettings.qml new file mode 100644 index 0000000000..e1ba93a840 --- /dev/null +++ b/interface/resources/qml/hifi/tablet/ControllerSettings.qml @@ -0,0 +1,218 @@ +// +// Created by Dante Ruiz on 6/1/17. +// 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.5 +import QtQuick.Controls 1.4 +import QtGraphicalEffects 1.0 +import "../../styles-uit" +import "../../controls" +import "../../controls-uit" as HifiControls + +StackView { + id: stack + initialItem: inputConfiguration + Rectangle { + id: inputConfiguration + anchors.fill: parent + + HifiConstants { id: hifi } + + color: hifi.colors.baseGray + + property var pluginSettings: null + + Rectangle { + width: inputConfiguration.width + height: 1 + color: hifi.colors.baseGrayShadow + x: -hifi.dimensions.contentMargin.x + } + + RalewayRegular { + id: header + text: "Controller Settings" + size: 22 + color: "white" + + anchors.top: inputConfiguration.top + anchors.left: inputConfiguration.left + anchors.leftMargin: 20 + anchors.topMargin: 20 + } + + + Separator { + id: headerSeparator + width: inputConfiguration.width + anchors.top: header.bottom + anchors.topMargin: 10 + } + + HiFiGlyphs { + id: sourceGlyph + text: hifi.glyphs.source + size: 36 + color: hifi.colors.blueHighlight + + anchors.top: headerSeparator.bottom + anchors.left: inputConfiguration.left + anchors.leftMargin: 40 + anchors.topMargin: 20 + } + + RalewayRegular { + id: configuration + text: "SELECT DEVICE" + size: 15 + color: hifi.colors.lightGrayText + + + anchors.top: headerSeparator.bottom + anchors.left: sourceGlyph.right + anchors.leftMargin: 10 + anchors.topMargin: 30 + } + + Row { + id: configRow + z: 999 + anchors.top: sourceGlyph.bottom + anchors.topMargin: 20 + anchors.left: sourceGlyph.left + anchors.leftMargin: 40 + spacing: 10 + HifiControls.ComboBox { + id: box + width: 160 + z: 999 + editable: true + colorScheme: hifi.colorSchemes.dark + model: inputPlugins() + label: "" + + onCurrentIndexChanged: { + changeSource(); + } + } + + HifiControls.CheckBox { + id: checkBox + colorScheme: hifi.colorSchemes.dark + text: "show all input devices" + + onClicked: { + inputPlugins(); + changeSource(); + } + } + } + + + Separator { + id: configurationSeparator + z: 0 + width: inputConfiguration.width + anchors.top: configRow.bottom + anchors.topMargin: 10 + } + + + HiFiGlyphs { + id: sliderGlyph + text: hifi.glyphs.sliders + size: 36 + color: hifi.colors.blueHighlight + + anchors.top: configurationSeparator.bottom + anchors.left: inputConfiguration.left + anchors.leftMargin: 40 + anchors.topMargin: 20 + } + + RalewayRegular { + id: configurationHeader + text: "CONFIGURATION" + size: 15 + color: hifi.colors.lightGrayText + + + anchors.top: configurationSeparator.bottom + anchors.left: sliderGlyph.right + anchors.leftMargin: 10 + anchors.topMargin: 30 + } + + Loader { + id: loader + asynchronous: false + + width: inputConfiguration.width + anchors.left: inputConfiguration.left + anchors.right: inputConfiguration.right + anchors.top: configurationHeader.bottom + anchors.topMargin: 10 + anchors.bottom: inputConfiguration.bottom + + source: InputConfiguration.configurationLayout(box.currentText); + onLoaded: { + if (loader.item.hasOwnProperty("pluginName")) { + if (box.currentText === "Vive") { + loader.item.pluginName = "OpenVR"; + } else { + loader.item.pluginName = box.currentText; + } + } + + if (loader.item.hasOwnProperty("displayInformation")) { + loader.item.displayConfiguration(); + } + } + } + } + + + function inputPlugins() { + if (checkBox.checked) { + return InputConfiguration.inputPlugins(); + } else { + return InputConfiguration.activeInputPlugins(); + } + } + + function initialize() { + changeSource(); + } + + function changeSource() { + loader.source = ""; + var source = ""; + if (box.currentText == "Vive") { + source = InputConfiguration.configurationLayout("OpenVR"); + } else { + source = InputConfiguration.configurationLayout(box.currentText); + } + + loader.source = source; + if (source === "") { + box.label = "(not configurable)"; + } else { + box.label = ""; + } + } + + Timer { + id: timer + repeat: false + interval: 300 + onTriggered: initialize() + } + + Component.onCompleted: { + timer.start(); + } +} diff --git a/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml b/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml new file mode 100644 index 0000000000..52a935ab19 --- /dev/null +++ b/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml @@ -0,0 +1,879 @@ +// +// Created by Dante Ruiz on 6/5/17. +// 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.5 +import QtQuick.Controls 1.4 +import QtGraphicalEffects 1.0 +import QtQuick.Controls 1.4 as Original +import QtQuick.Controls.Styles 1.4 +import "../../styles-uit" +import "../../controls" +import "../../controls-uit" as HifiControls +import "." + + +Rectangle { + id: openVrConfiguration + + width: parent.width + height: parent.height + anchors.fill: parent + + property int leftMargin: 75 + property int countDown: 0 + property string pluginName: "" + property var displayInformation: null + + readonly property bool feetChecked: feetBox.checked + readonly property bool hipsChecked: hipBox.checked + readonly property bool chestChecked: chestBox.checked + readonly property bool shouldersChecked: shoulderBox.checked + readonly property bool hmdHead: headBox.checked + readonly property bool headPuck: headPuckBox.checked + readonly property bool handController: handBox.checked + readonly property bool handPuck: handPuckBox.checked + + property int state: buttonState.disabled + property var lastConfiguration: null + + HifiConstants { id: hifi } + + Component { id: screen; CalibratingScreen {} } + QtObject { + id: buttonState + readonly property int disabled: 0 + readonly property int apply: 1 + readonly property int applyAndCalibrate: 2 + readonly property int calibrate: 3 + + } + + + MouseArea { + id: mouseArea + + anchors.fill: parent + propagateComposedEvents: true + onPressed: { + parent.forceActiveFocus() + mouse.accepted = false; + } + } + color: hifi.colors.baseGray + + RalewayBold { + id: head + + text: "Head:" + size: 12 + + color: "white" + + anchors.left: parent.left + anchors.leftMargin: leftMargin + } + + Row { + id: headConfig + anchors.top: head.bottom + anchors.topMargin: 5 + anchors.left: openVrConfiguration.left + anchors.leftMargin: leftMargin + 10 + spacing: 10 + + HifiControls.CheckBox { + id: headBox + width: 15 + height: 15 + boxRadius: 7 + + onClicked: { + if (checked) { + headPuckBox.checked = false; + } else { + checked = true; + } + sendConfigurationSettings(); + } + } + + RalewayBold { + size: 12 + text: "Vive HMD" + color: hifi.colors.lightGrayText + } + + HifiControls.CheckBox { + id: headPuckBox + width: 15 + height: 15 + boxRadius: 7 + + onClicked: { + if (checked) { + headBox.checked = false; + } else { + checked = true; + } + sendConfigurationSettings(); + } + } + + RalewayBold { + size: 12 + text: "Tracker" + color: hifi.colors.lightGrayText + } + } + + Row { + id: headOffsets + anchors.top: headConfig.bottom + anchors.topMargin: 5 + anchors.left: openVrConfiguration.left + anchors.leftMargin: leftMargin + 10 + spacing: 10 + visible: headPuckBox.checked + HifiControls.SpinBox { + id: headYOffset + decimals: 4 + width: 112 + label: "Y: offset" + minimumValue: -10 + stepSize: 0.0254 + colorScheme: hifi.colorSchemes.dark + + onEditingFinished: { + sendConfigurationSettings(); + } + } + + + HifiControls.SpinBox { + id: headZOffset + width: 112 + label: "Z: offset" + minimumValue: -10 + stepSize: 0.0254 + decimals: 4 + colorScheme: hifi.colorSchemes.dark + + onEditingFinished: { + sendConfigurationSettings(); + } + } + } + + + RalewayBold { + id: hands + + text: "Hands:" + size: 12 + + color: "white" + + anchors.top: (headOffsets.visible ? headOffsets.bottom : headConfig.bottom) + anchors.topMargin: (headOffsets.visible ? 22 : 10) + anchors.left: parent.left + anchors.leftMargin: leftMargin + } + + Row { + id: handConfig + anchors.top: hands.bottom + anchors.topMargin: 5 + anchors.left: openVrConfiguration.left + anchors.leftMargin: leftMargin + 10 + spacing: 10 + + HifiControls.CheckBox { + id: handBox + width: 15 + height: 15 + boxRadius: 7 + + onClicked: { + if (checked) { + handPuckBox.checked = false; + } else { + checked = true; + } + sendConfigurationSettings(); + } + } + + RalewayBold { + size: 12 + text: "Controllers" + color: hifi.colors.lightGrayText + } + + HifiControls.CheckBox { + id: handPuckBox + width: 12 + height: 15 + boxRadius: 7 + + onClicked: { + if (checked) { + handBox.checked = false; + } else { + checked = true; + } + sendConfigurationSettings(); + } + } + + RalewayBold { + size: 12 + text: "Trackers" + color: hifi.colors.lightGrayText + } + } + + Row { + id: handOffset + visible: handPuckBox.checked + anchors.top: handConfig.bottom + anchors.topMargin: 5 + anchors.left: openVrConfiguration.left + anchors.leftMargin: leftMargin + 10 + spacing: 10 + + HifiControls.SpinBox { + id: handYOffset + decimals: 4 + width: 112 + label: "Y: offset" + minimumValue: -10 + stepSize: 0.0254 + colorScheme: hifi.colorSchemes.dark + + onEditingFinished: { + sendConfigurationSettings(); + } + } + + + HifiControls.SpinBox { + id: handZOffset + width: 112 + label: "Z: offset" + minimumValue: -10 + stepSize: 0.0254 + decimals: 4 + colorScheme: hifi.colorSchemes.dark + + onEditingFinished: { + sendConfigurationSettings(); + } + } + } + + RalewayBold { + id: additional + + text: "Additional Trackers" + size: 12 + + color: hifi.colors.white + + anchors.top: (handOffset.visible ? handOffset.bottom : handConfig.bottom) + anchors.topMargin: (handOffset.visible ? 22 : 10) + anchors.left: parent.left + anchors.leftMargin: leftMargin + } + + Row { + id: feetConfig + anchors.top: additional.bottom + anchors.topMargin: 15 + anchors.left: openVrConfiguration.left + anchors.leftMargin: leftMargin + 10 + spacing: 10 + + HifiControls.CheckBox { + id: feetBox + width: 15 + height: 15 + boxRadius: 7 + + onClicked: { + if (!checked) { + shoulderBox.checked = false; + chestBox.checked = false; + hipBox.checked = false; + } + sendConfigurationSettings(); + } + } + + RalewayBold { + size: 12 + text: "Feet" + color: hifi.colors.lightGrayText + } + } + + Row { + id: hipConfig + anchors.top: feetConfig.bottom + anchors.topMargin: 15 + anchors.left: openVrConfiguration.left + anchors.leftMargin: leftMargin + 10 + spacing: 10 + + HifiControls.CheckBox { + id: hipBox + width: 15 + height: 15 + boxRadius: 7 + + onClicked: { + if (checked) { + feetBox.checked = true; + } + + if (chestChecked) { + checked = true; + } + sendConfigurationSettings(); + } + } + + RalewayBold { + size: 12 + text: "Hips" + color: hifi.colors.lightGrayText + } + + RalewayRegular { + size: 12 + text: "requires feet" + color: hifi.colors.lightGray + } + } + + + Row { + id: chestConfig + anchors.top: hipConfig.bottom + anchors.topMargin: 15 + anchors.left: openVrConfiguration.left + anchors.leftMargin: leftMargin + 10 + spacing: 10 + + HifiControls.CheckBox { + id: chestBox + width: 15 + height: 15 + boxRadius: 7 + + onClicked: { + if (checked) { + hipBox.checked = true; + feetBox.checked = true; + } + sendConfigurationSettings(); + } + } + + RalewayBold { + size: 12 + text: "Chest" + color: hifi.colors.lightGrayText + } + + RalewayRegular { + size: 12 + text: "requires hips" + color: hifi.colors.lightGray + } + } + + + Row { + id: shoulderConfig + anchors.top: chestConfig.bottom + anchors.topMargin: 15 + anchors.left: openVrConfiguration.left + anchors.leftMargin: leftMargin + 10 + spacing: 10 + + HifiControls.CheckBox { + id: shoulderBox + width: 15 + height: 15 + boxRadius: 7 + + onClicked: { + if (checked) { + hipBox.checked = true; + feetBox.checked = true; + } + sendConfigurationSettings(); + } + } + + RalewayBold { + size: 12 + text: "Shoulders" + color: hifi.colors.lightGrayText + } + + RalewayRegular { + size: 12 + text: "requires hips" + color: hifi.colors.lightGray + } + } + + Separator { + id: bottomSeperator + width: parent.width + anchors.top: shoulderConfig.bottom + anchors.topMargin: 10 + } + + + + Rectangle { + id: calibrationButton + property int color: hifi.buttons.blue + property int colorScheme: hifi.colorSchemes.light + property string glyph: hifi.glyphs.avatar1 + property bool enabled: false + property bool pressed: false + property bool hovered: false + property int size: 32 + property string text: "apply" + property int padding: 12 + + width: glyphButton.width + calibrationText.width + padding + height: hifi.dimensions.controlLineHeight + anchors.top: bottomSeperator.bottom + anchors.topMargin: 10 + anchors.left: parent.left + anchors.leftMargin: leftMargin + + radius: hifi.buttons.radius + + gradient: Gradient { + GradientStop { + position: 0.2 + color: { + if (!calibrationButton.enabled) { + hifi.buttons.disabledColorStart[calibrationButton.colorScheme] + } else if (calibrationButton.pressed) { + hifi.buttons.pressedColor[calibrationButton.color] + } else if (calibrationButton.hovered) { + hifi.buttons.hoveredColor[calibrationButton.color] + } else { + hifi.buttons.colorStart[calibrationButton.color] + } + } + } + + GradientStop { + position: 1.0 + color: { + if (!calibrationButton.enabled) { + hifi.buttons.disabledColorFinish[calibrationButton.colorScheme] + } else if (calibrationButton.pressed) { + hifi.buttons.pressedColor[calibrationButton.color] + } else if (calibrationButton.hovered) { + hifi.buttons.hoveredColor[calibrationButton.color] + } else { + hifi.buttons.colorFinish[calibrationButton.color] + } + } + } + } + + + + + HiFiGlyphs { + id: glyphButton + color: enabled ? hifi.buttons.textColor[calibrationButton.color] + : hifi.buttons.disabledTextColor[calibrationButton.colorScheme] + text: calibrationButton.glyph + size: calibrationButton.size + + anchors { + top: parent.top + bottom: parent.bottom + bottomMargin: 1 + } + } + + RalewayBold { + id: calibrationText + font.capitalization: Font.AllUppercase + color: enabled ? hifi.buttons.textColor[calibrationButton.color] + : hifi.buttons.disabledTextColor[calibrationButton.colorScheme] + size: hifi.fontSizes.buttonLabel + text: calibrationButton.text + + anchors { + left: glyphButton.right + top: parent.top + topMargin: 7 + } + } + + + MouseArea { + anchors.fill: parent + hoverEnabled: true + onClicked: { + if (calibrationButton.enabled) { + if (openVrConfiguration.state === buttonState.apply) { + InputConfiguration.uncalibratePlugin(pluginName); + updateCalibrationButton(); + } else { + calibrationTimer.interval = timeToCalibrate.value * 1000 + openVrConfiguration.countDown = timeToCalibrate.value; + var calibratingScreen = screen.createObject(); + stack.push(calibratingScreen); + calibratingScreen.canceled.connect(cancelCalibration); + calibratingScreen.restart.connect(restartCalibration); + calibratingScreen.start(calibrationTimer.interval, timeToCalibrate.value); + calibrationTimer.start(); + } + } + } + + onPressed: { + calibrationButton.pressed = true; + } + + onReleased: { + calibrationButton.pressed = false; + } + + onEntered: { + calibrationButton.hovered = true; + } + + onExited: { + calibrationButton.hovered = false; + } + } + } + + Timer { + id: calibrationTimer + repeat: false + interval: 20 + onTriggered: { + InputConfiguration.calibratePlugin(pluginName) + } + } + + Timer { + id: displayTimer + repeat: false + interval: 3000 + onTriggered: { + } + } + + Component.onCompleted: { + InputConfiguration.calibrationStatus.connect(calibrationStatusInfo); + lastConfiguration = composeConfigurationSettings(); + } + + HifiControls.SpinBox { + id: timeToCalibrate + width: 70 + anchors.top: calibrationButton.bottom + anchors.topMargin: 40 + anchors.left: parent.left + anchors.leftMargin: leftMargin + + minimumValue: 3 + value: 3 + colorScheme: hifi.colorSchemes.dark + + onEditingFinished: { + calibrationTimer.interval = value * 1000; + openVrConfiguration.countDown = value; + numberAnimation.duration = calibrationTimer.interval; + } + } + + RalewayBold { + id: delayTextInfo + size: 10 + text: "Delay Before Calibration Starts" + color: hifi.colors.white + + anchors { + left: timeToCalibrate.right + leftMargin: 20 + verticalCenter: timeToCalibrate.verticalCenter + } + } + + RalewayRegular { + size: 12 + text: "sec" + color: hifi.colors.lightGray + + anchors { + left: delayTextInfo.right + leftMargin: 10 + verticalCenter: delayTextInfo.verticalCenter + } + } + + NumberAnimation { + id: numberAnimation + target: openVrConfiguration + property: "countDown" + to: 0 + } + + function calibrationStatusInfo(status) { + var calibrationScreen = stack.currentItem; + if (status["calibrated"]) { + calibrationScreen.success(); + } else if (!status["calibrated"]) { + var uncalibrated = status["success"]; + if (!uncalibrated) { + calibrationScreen.failure(); + } + } + + updateCalibrationButton(); + } + + + function trackersForConfiguration() { + var pucksNeeded = 0; + + if (headPuckBox.checked) { + pucksNeeded++; + } + + if (feetBox.checked) { + pucksNeeded++; + } + + if (hipBox.checked) { + pucksNeeded++; + } + + if (chestBox.checked) { + pucksNeeded++; + } + + if (shoulderBox.checked) { + pucksNeeded++; + } + + return pucksNeeded; + } + + function cancelCalibration() { + calibrationTimer.stop(); + } + + function restartCalibration() { + calibrationTimer.restart(); + } + + function displayConfiguration() { + var settings = InputConfiguration.configurationSettings(pluginName); + var configurationType = settings["trackerConfiguration"]; + displayTrackerConfiguration(configurationType); + + + var HmdHead = settings["HMDHead"]; + var viveController = settings["handController"]; + + if (HmdHead) { + headBox.checked = true; + headPuckBox.checked = false; + } else { + headPuckBox.checked = true; + headBox.checked = false; + } + + if (viveController) { + handBox.checked = true; + handPuckBox.checked = false; + } else { + handPuckBox.checked = true; + handBox.checked = false; + } + + initializeButtonState(); + updateCalibrationText(); + } + + function displayTrackerConfiguration(type) { + if (type === "Feet") { + feetBox.checked = true; + } else if (type === "FeetAndHips") { + feetBox.checked = true; + hipBox.checked = true; + } else if (type === "FeetHipsChest") { + feetBox.checked = true; + hipBox.checked = true; + chestBox.checked = true; + } else if (type === "FeetHipsAndShoulders") { + feetBox.checked = true; + hipBox.checked = true; + shoulderBox.checked = true; + } else if (type === "FeetHipsChestAndShoulders") { + feetBox.checked = true; + hipBox.checked = true; + chestBox.checked = true; + shoulderBox.checked = true; + } + } + + function updateButtonState() { + var settings = composeConfigurationSettings(); + var bodySetting = settings["bodyConfiguration"]; + var headSetting = settings["headConfiguration"]; + var headOverride = headSetting["override"]; + var handSetting = settings["handConfiguration"]; + var handOverride = handSetting["override"]; + + var settingsChanged = false; + + if (lastConfiguration["bodyConfiguration"] !== bodySetting) { + settingsChanged = true; + } + + var lastHead = lastConfiguration["headConfiguration"]; + if (lastHead["override"] !== headOverride) { + settingsChanged = true; + } + + var lastHand = lastConfiguration["handConfiguration"]; + if (lastHand["override"] !== handOverride) { + settingsChanged = true; + } + + if (settingsChanged) { + if ((!handOverride) && (!headOverride) && (bodySetting === "None")) { + state = buttonState.apply; + } else { + state = buttonState.applyAndCalibrate; + } + } else { + if (state == buttonState.apply) { + state = buttonState.disabled; + } else if (state == buttonState.applyAndCalibrate) { + state = buttonState.calibrate; + } + } + + lastConfiguration = settings; + } + + function initializeButtonState() { + var settings = composeConfigurationSettings(); + var bodySetting = settings["bodyConfiguration"]; + var headSetting = settings["headConfiguration"]; + var headOverride = headSetting["override"]; + var handSetting = settings["handConfiguration"]; + var handOverride = handSetting["override"]; + + + if ((!handOverride) && (!headOverride) && (bodySetting === "None")) { + state = buttonState.disabled; + } else { + state = buttonState.calibrate; + } + } + + function updateCalibrationButton() { + updateButtonState(); + updateCalibrationText(); + } + + function updateCalibrationText() { + if (buttonState.disabled == state) { + calibrationButton.enabled = false; + calibrationButton.text = "Apply"; + } else if (buttonState.apply == state) { + calibrationButton.enabled = true; + calibrationButton.text = "Apply"; + } else if (buttonState.applyAndCalibrate == state) { + calibrationButton.enabled = true; + calibrationButton.text = "Apply And Calibrate"; + } else if (buttonState.calibrate == state) { + calibrationButton.enabled = true; + calibrationButton.text = "Calibrate"; + } + } + + function composeConfigurationSettings() { + var trackerConfiguration = ""; + var overrideHead = false; + var overrideHandController = false; + + if (shouldersChecked && chestChecked) { + trackerConfiguration = "FeetHipsChestAndShoulders"; + } else if (shouldersChecked) { + trackerConfiguration = "FeetHipsAndShoulders"; + } else if (chestChecked) { + trackerConfiguration = "FeetHipsAndChest"; + } else if (hipsChecked) { + trackerConfiguration = "FeetAndHips"; + } else if (feetChecked) { + trackerConfiguration = "Feet"; + } else { + trackerConfiguration = "None"; + } + + if (headPuck) { + overrideHead = true; + } else if (hmdHead) { + overrideHead = false; + } + + if (handController) { + overrideHandController = false; + } else if (handPuck) { + overrideHandController = true; + } + + var headObject = { + "override": overrideHead, + "Y": headYOffset.value, + "Z": headZOffset.value + } + + var handObject = { + "override": overrideHandController, + "Y": handYOffset.value, + "Z": handZOffset.value + } + + var settingsObject = { + "bodyConfiguration": trackerConfiguration, + "headConfiguration": headObject, + "handConfiguration": handObject + } + + return settingsObject; + } + + function sendConfigurationSettings() { + var settings = composeConfigurationSettings(); + InputConfiguration.setConfigurationSettings(settings, pluginName); + updateCalibrationButton(); + } +} diff --git a/interface/resources/qml/styles-uit/HifiConstants.qml b/interface/resources/qml/styles-uit/HifiConstants.qml index f321f49478..ca39326102 100644 --- a/interface/resources/qml/styles-uit/HifiConstants.qml +++ b/interface/resources/qml/styles-uit/HifiConstants.qml @@ -333,6 +333,7 @@ Item { readonly property string vol_x_2: "\ue015" readonly property string vol_x_3: "\ue016" readonly property string vol_x_4: "\ue017" + readonly property string source: "\ue01c" readonly property string playback_play: "\ue01d" readonly property string stop_square: "\ue01e" } diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index fa36ddd345..5b58d829be 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -109,6 +109,7 @@ #include #include #include +#include #include #include #include @@ -539,6 +540,7 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) { DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); + DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); @@ -2025,6 +2027,7 @@ void Application::initializeUi() { surfaceContext->setContextProperty("TextureCache", DependencyManager::get().data()); surfaceContext->setContextProperty("ModelCache", DependencyManager::get().data()); surfaceContext->setContextProperty("SoundCache", DependencyManager::get().data()); + surfaceContext->setContextProperty("InputConfiguration", DependencyManager::get().data()); surfaceContext->setContextProperty("Account", AccountScriptingInterface::getInstance()); surfaceContext->setContextProperty("Tablet", DependencyManager::get().data()); diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index bc771594a9..8c9baa7c43 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -37,6 +38,7 @@ #include "MainWindow.h" #include "render/DrawStatus.h" #include "scripting/MenuScriptingInterface.h" +#include "scripting/HMDScriptingInterface.h" #include "ui/DialogsManager.h" #include "ui/StandAloneJSConsole.h" #include "InterfaceLogging.h" @@ -309,6 +311,17 @@ Menu::Menu() { QString("../../hifi/tablet/TabletLodPreferences.qml"), "LodPreferencesDialog"); }); + action = addActionToQMenuAndActionHash(settingsMenu, "Controller Settings"); + connect(action, &QAction::triggered, [] { + auto tablet = DependencyManager::get()->getTablet("com.highfidelity.interface.tablet.system"); + auto hmd = DependencyManager::get(); + tablet->loadQMLSource("ControllerSettings.qml"); + + if (!hmd->getShouldShowTablet()) { + hmd->toggleShouldShowTablet(); + } + }); + // Settings > Control with Speech [advanced] #if defined(Q_OS_MAC) || defined(Q_OS_WIN) auto speechRecognizer = DependencyManager::get(); diff --git a/interface/src/ui/overlays/Web3DOverlay.cpp b/interface/src/ui/overlays/Web3DOverlay.cpp index a532e9d8fd..b67ca06aa3 100644 --- a/interface/src/ui/overlays/Web3DOverlay.cpp +++ b/interface/src/ui/overlays/Web3DOverlay.cpp @@ -50,6 +50,7 @@ #include "ui/AvatarInputs.h" #include "avatar/AvatarManager.h" #include "scripting/GlobalServicesScriptingInterface.h" +#include #include "ui/Snapshot.h" #include "SoundCache.h" @@ -182,6 +183,7 @@ void Web3DOverlay::loadSourceURL() { if (_webSurface->getRootItem() && _webSurface->getRootItem()->objectName() == "tabletRoot") { auto tabletScriptingInterface = DependencyManager::get(); auto flags = tabletScriptingInterface->getFlags(); + _webSurface->getSurfaceContext()->setContextProperty("offscreenFlags", flags); _webSurface->getSurfaceContext()->setContextProperty("AddressManager", DependencyManager::get().data()); _webSurface->getSurfaceContext()->setContextProperty("Account", AccountScriptingInterface::getInstance()); @@ -200,6 +202,7 @@ void Web3DOverlay::loadSourceURL() { _webSurface->getSurfaceContext()->setContextProperty("GlobalServices", GlobalServicesScriptingInterface::getInstance()); _webSurface->getSurfaceContext()->setContextProperty("AvatarList", DependencyManager::get().data()); _webSurface->getSurfaceContext()->setContextProperty("DialogsManager", DialogsManagerScriptingInterface::getInstance()); + _webSurface->getSurfaceContext()->setContextProperty("InputConfiguration", DependencyManager::get().data()); _webSurface->getSurfaceContext()->setContextProperty("SoundCache", DependencyManager::get().data()); _webSurface->getSurfaceContext()->setContextProperty("pathToFonts", "../../"); diff --git a/libraries/plugins/src/plugins/InputConfiguration.cpp b/libraries/plugins/src/plugins/InputConfiguration.cpp new file mode 100644 index 0000000000..04b1e3b370 --- /dev/null +++ b/libraries/plugins/src/plugins/InputConfiguration.cpp @@ -0,0 +1,143 @@ +// +// Created by Dante Ruiz on 6/1/17. +// 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 +// + + +#include "InputConfiguration.h" + +#include + +#include "DisplayPlugin.h" +#include "InputPlugin.h" +#include "PluginManager.h" + +InputConfiguration::InputConfiguration() { +} + +QStringList InputConfiguration::inputPlugins() { + if (QThread::currentThread() != thread()) { + QStringList result; + QMetaObject::invokeMethod(this, "inputPlugins", Qt::BlockingQueuedConnection, + Q_RETURN_ARG(QStringList, result)); + return result; + } + + QStringList inputPlugins; + for (auto plugin : PluginManager::getInstance()->getInputPlugins()) { + QString pluginName = plugin->getName(); + if (pluginName == QString("OpenVR")) { + inputPlugins << QString("Vive"); + } else { + inputPlugins << pluginName; + } + } + return inputPlugins; +} + + +QStringList InputConfiguration::activeInputPlugins() { + if (QThread::currentThread() != thread()) { + QStringList result; + QMetaObject::invokeMethod(this, "activeInputPlugins", Qt::BlockingQueuedConnection, + Q_RETURN_ARG(QStringList, result)); + return result; + } + + QStringList activePlugins; + for (auto plugin : PluginManager::getInstance()->getInputPlugins()) { + if (plugin->configurable()) { + QString pluginName = plugin->getName(); + if (pluginName == QString("OpenVR")) { + activePlugins << QString("Vive"); + } else { + activePlugins << pluginName; + } + } + } + return activePlugins; +} + +QString InputConfiguration::configurationLayout(QString pluginName) { + if (QThread::currentThread() != thread()) { + QString result; + QMetaObject::invokeMethod(this, "configurationLayout", Qt::BlockingQueuedConnection, + Q_RETURN_ARG(QString, result), + Q_ARG(QString, pluginName)); + return result; + } + + QString sourcePath; + for (auto plugin : PluginManager::getInstance()->getInputPlugins()) { + if (plugin->getName() == pluginName) { + return plugin->configurationLayout(); + } + } + return sourcePath; +} + +void InputConfiguration::setConfigurationSettings(QJsonObject configurationSettings, QString pluginName) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "setConfigurationSettings", Qt::BlockingQueuedConnection, + Q_ARG(QJsonObject, configurationSettings), + Q_ARG(QString, pluginName)); + return; + } + + for (auto plugin : PluginManager::getInstance()->getInputPlugins()) { + if (plugin->getName() == pluginName) { + plugin->setConfigurationSettings(configurationSettings); + } + } +} + +QJsonObject InputConfiguration::configurationSettings(QString pluginName) { + if (QThread::currentThread() != thread()) { + QJsonObject result; + QMetaObject::invokeMethod(this, "configurationSettings", Qt::BlockingQueuedConnection, + Q_RETURN_ARG(QJsonObject, result), + Q_ARG(QString, pluginName)); + return result; + } + + for (auto plugin : PluginManager::getInstance()->getInputPlugins()) { + if (plugin->getName() == pluginName) { + return plugin->configurationSettings(); + } + } + return QJsonObject(); +} + +void InputConfiguration::calibratePlugin(QString pluginName) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "calibratePlugin", Qt::BlockingQueuedConnection); + return; + } + + for (auto plugin : PluginManager::getInstance()->getInputPlugins()) { + if (plugin->getName() == pluginName) { + plugin->calibrate(); + } + } +} + + +bool InputConfiguration::uncalibratePlugin(QString pluginName) { + if (QThread::currentThread() != thread()) { + bool result; + QMetaObject::invokeMethod(this, "uncalibratePlugin", Qt::BlockingQueuedConnection, + Q_ARG(bool, result)); + return result; + } + + for (auto plugin : PluginManager::getInstance()->getInputPlugins()) { + if (plugin->getName() == pluginName) { + return plugin->uncalibrate(); + } + } + + return false; +} diff --git a/libraries/plugins/src/plugins/InputConfiguration.h b/libraries/plugins/src/plugins/InputConfiguration.h new file mode 100644 index 0000000000..27591edc34 --- /dev/null +++ b/libraries/plugins/src/plugins/InputConfiguration.h @@ -0,0 +1,37 @@ +// +// Created by Dante Ruiz on 6/1/17. +// 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 +// + +#ifndef hifi_InputConfiguration_h +#define hifi_InputConfiguration_h + +#include + +#include +#include +#include +#include +#include + +class InputConfiguration : public QObject, public Dependency { + Q_OBJECT +public: + InputConfiguration(); + + Q_INVOKABLE QStringList inputPlugins(); + Q_INVOKABLE QStringList activeInputPlugins(); + Q_INVOKABLE QString configurationLayout(QString pluginName); + Q_INVOKABLE void setConfigurationSettings(QJsonObject configurationSettings, QString pluginName); + Q_INVOKABLE void calibratePlugin(QString pluginName); + Q_INVOKABLE QJsonObject configurationSettings(QString pluginName); + Q_INVOKABLE bool uncalibratePlugin(QString pluginName); + +signals: + void calibrationStatus(const QJsonObject& status); +}; + +#endif diff --git a/libraries/plugins/src/plugins/InputPlugin.h b/libraries/plugins/src/plugins/InputPlugin.h index 2a4737b5a1..5d02964c97 100644 --- a/libraries/plugins/src/plugins/InputPlugin.h +++ b/libraries/plugins/src/plugins/InputPlugin.h @@ -11,6 +11,7 @@ #pragma once #include "Plugin.h" +#include namespace controller { struct InputCalibrationData; @@ -24,7 +25,12 @@ public: // Some input plugins are comprised of multiple subdevices (SDL2, for instance). // If an input plugin is only a single device, it will only return it's primary name. virtual QStringList getSubdeviceNames() { return { getName() }; }; + virtual void setConfigurationSettings(const QJsonObject configurationSettings) { } + virtual QJsonObject configurationSettings() { return QJsonObject(); } + virtual QString configurationLayout() { return QString(); } + virtual void calibrate() {} + virtual bool uncalibrate() { return false; } + virtual bool configurable() { return false; } virtual bool isHandController() const { return false; } virtual bool isHeadController() const { return false; } }; - diff --git a/plugins/hifiCodec/CMakeLists.txt b/plugins/hifiCodec/CMakeLists.txt index 28c1dc3807..15572e8266 100644 --- a/plugins/hifiCodec/CMakeLists.txt +++ b/plugins/hifiCodec/CMakeLists.txt @@ -8,7 +8,7 @@ set(TARGET_NAME hifiCodec) setup_hifi_client_server_plugin() -link_hifi_libraries(audio plugins) +link_hifi_libraries(audio plugins input-plugins display-plugins) add_dependency_external_projects(hifiAudioCodec) target_include_directories(${TARGET_NAME} PRIVATE ${HIFIAUDIOCODEC_INCLUDE_DIRS}) target_link_libraries(${TARGET_NAME} ${HIFIAUDIOCODEC_LIBRARIES}) diff --git a/plugins/hifiKinect/CMakeLists.txt b/plugins/hifiKinect/CMakeLists.txt index fc4c2f6f7c..a7088eb3b4 100644 --- a/plugins/hifiKinect/CMakeLists.txt +++ b/plugins/hifiKinect/CMakeLists.txt @@ -11,7 +11,7 @@ if (WIN32) if (KINECT_FOUND) set(TARGET_NAME hifiKinect) setup_hifi_plugin(Script Qml Widgets) - link_hifi_libraries(shared controllers ui plugins input-plugins) + link_hifi_libraries(shared controllers ui plugins input-plugins display-plugins) # need to setup appropriate externals... target_kinect() diff --git a/plugins/hifiNeuron/CMakeLists.txt b/plugins/hifiNeuron/CMakeLists.txt index a9ed8cca6e..e3a725ca2f 100644 --- a/plugins/hifiNeuron/CMakeLists.txt +++ b/plugins/hifiNeuron/CMakeLists.txt @@ -10,7 +10,7 @@ if (APPLE OR WIN32) set(TARGET_NAME hifiNeuron) setup_hifi_plugin(Script Qml Widgets) - link_hifi_libraries(shared controllers ui plugins input-plugins) + link_hifi_libraries(shared controllers ui plugins input-plugins display-plugins) target_neuron() endif() diff --git a/plugins/hifiSdl2/CMakeLists.txt b/plugins/hifiSdl2/CMakeLists.txt index 7e499e314a..86bda5a15d 100644 --- a/plugins/hifiSdl2/CMakeLists.txt +++ b/plugins/hifiSdl2/CMakeLists.txt @@ -8,5 +8,5 @@ set(TARGET_NAME hifiSdl2) setup_hifi_plugin(Script Qml Widgets) -link_hifi_libraries(shared controllers ui plugins input-plugins script-engine) +link_hifi_libraries(shared controllers ui plugins input-plugins script-engine display-plugins) target_sdl2() diff --git a/plugins/hifiSixense/CMakeLists.txt b/plugins/hifiSixense/CMakeLists.txt index 14676217db..54884bddff 100644 --- a/plugins/hifiSixense/CMakeLists.txt +++ b/plugins/hifiSixense/CMakeLists.txt @@ -9,6 +9,6 @@ if (NOT ANDROID) set(TARGET_NAME hifiSixense) setup_hifi_plugin(Script Qml Widgets) - link_hifi_libraries(shared controllers ui plugins ui-plugins input-plugins) + link_hifi_libraries(shared controllers ui plugins ui-plugins input-plugins display-plugins) target_sixense() endif () diff --git a/plugins/openvr/src/ViveControllerManager.cpp b/plugins/openvr/src/ViveControllerManager.cpp index 7330daf228..d914cdcfad 100644 --- a/plugins/openvr/src/ViveControllerManager.cpp +++ b/plugins/openvr/src/ViveControllerManager.cpp @@ -30,7 +30,7 @@ #include #include - +#include #include @@ -39,7 +39,7 @@ extern PoseData _nextSimPoseData; vr::IVRSystem* acquireOpenVrSystem(); void releaseOpenVrSystem(); - +static const QString OPENVR_LAYOUT = QString("OpenVrConfiguration.qml"); static const char* CONTROLLER_MODEL_STRING = "vr_controller_05_wireless_b"; const quint64 CALIBRATION_TIMELAPSE = 1 * USECS_PER_SECOND; @@ -47,18 +47,23 @@ static const char* MENU_PARENT = "Avatar"; static const char* MENU_NAME = "Vive Controllers"; static const char* MENU_PATH = "Avatar" ">" "Vive Controllers"; static const char* RENDER_CONTROLLERS = "Render Hand Controllers"; + +static const int MIN_HEAD = 1; static const int MIN_PUCK_COUNT = 2; static const int MIN_FEET_AND_HIPS = 3; static const int MIN_FEET_HIPS_CHEST = 4; -static const int MIN_FEET_HIPS_HEAD = 4; static const int MIN_FEET_HIPS_SHOULDERS = 5; -static const int MIN_FEET_HIPS_CHEST_HEAD = 5; +static const int MIN_FEET_HIPS_CHEST_SHOULDERS = 6; + static const int FIRST_FOOT = 0; static const int SECOND_FOOT = 1; static const int HIP = 2; static const int CHEST = 3; + static float HEAD_PUCK_Y_OFFSET = -0.0254f; static float HEAD_PUCK_Z_OFFSET = -0.152f; +static float HAND_PUCK_Y_OFFSET = -0.0508f; +static float HAND_PUCK_Z_OFFSET = 0.0254f; const char* ViveControllerManager::NAME { "OpenVR" }; @@ -84,7 +89,7 @@ static bool sortPucksXPosition(PuckPosePair firstPuck, PuckPosePair secondPuck) return (firstPuck.second.translation.x < secondPuck.second.translation.x); } -static bool determineFeetOrdering(const controller::Pose& poseA, const controller::Pose& poseB, glm::vec3 axis, glm::vec3 axisOrigin) { +static bool determineLimbOrdering(const controller::Pose& poseA, const controller::Pose& poseB, glm::vec3 axis, glm::vec3 axisOrigin) { glm::vec3 poseAPosition = poseA.getTranslation(); glm::vec3 poseBPosition = poseB.getTranslation(); @@ -116,10 +121,42 @@ static QString deviceTrackingResultToString(vr::ETrackingResult trackingResult) return result; } +void ViveControllerManager::calibrate() { + if (isSupported()) { + _inputDevice->calibrateNextFrame(); + } +} + +bool ViveControllerManager::uncalibrate() { + if (isSupported()) { + _inputDevice->uncalibrate(); + return true; + } + return false; +} + bool ViveControllerManager::isSupported() const { return openVrSupported(); } +void ViveControllerManager::setConfigurationSettings(const QJsonObject configurationSettings) { + if (isSupported()) { + _inputDevice->configureCalibrationSettings(configurationSettings); + } +} + +QJsonObject ViveControllerManager::configurationSettings() { + if (isSupported()) { + return _inputDevice->configurationSettings(); + } + + return QJsonObject(); +} + +QString ViveControllerManager::configurationLayout() { + return OPENVR_LAYOUT; +} + bool ViveControllerManager::activate() { InputPlugin::activate(); @@ -207,17 +244,11 @@ void ViveControllerManager::pluginUpdate(float deltaTime, const controller::Inpu ViveControllerManager::InputDevice::InputDevice(vr::IVRSystem*& system) : controller::InputDevice("Vive"), _system(system) { - _configStringMap[Config::Auto] = QString("Auto"); - _configStringMap[Config::Feet] = QString("Feet"); - _configStringMap[Config::FeetAndHips] = QString("FeetAndHips"); - _configStringMap[Config::FeetHipsAndChest] = QString("FeetHipsAndChest"); + _configStringMap[Config::None] = QString("None"); + _configStringMap[Config::Feet] = QString("Feet"); + _configStringMap[Config::FeetAndHips] = QString("FeetAndHips"); + _configStringMap[Config::FeetHipsAndChest] = QString("FeetHipsAndChest"); _configStringMap[Config::FeetHipsAndShoulders] = QString("FeetHipsAndShoulders"); - _configStringMap[Config::FeetHipsChestAndHead] = QString("FeetHipsChestAndHead"); - _configStringMap[Config::FeetHipsAndHead] = QString("FeetHipsAndHead"); - - if (openVrSupported()) { - createPreferences(); - } } void ViveControllerManager::InputDevice::update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) { @@ -265,6 +296,14 @@ void ViveControllerManager::InputDevice::update(float deltaTime, const controlle } _trackedControllers = numTrackedControllers; + calibrateFromHandController(inputCalibrationData); + calibrateFromUI(inputCalibrationData); + + updateCalibratedLimbs(); + _lastSimPoseData = _nextSimPoseData; +} + +void ViveControllerManager::InputDevice::calibrateFromHandController(const controller::InputCalibrationData& inputCalibrationData) { if (checkForCalibrationEvent()) { quint64 currentTime = usecTimestampNow(); if (!_timeTilCalibrationSet) { @@ -280,9 +319,82 @@ void ViveControllerManager::InputDevice::update(float deltaTime, const controlle _triggersPressedHandled = false; _timeTilCalibrationSet = false; } +} - updateCalibratedLimbs(); - _lastSimPoseData = _nextSimPoseData; +void ViveControllerManager::InputDevice::calibrateFromUI(const controller::InputCalibrationData& inputCalibrationData) { + if (_calibrate) { + uncalibrate(); + calibrate(inputCalibrationData); + _calibrate = false; + } +} + +void ViveControllerManager::InputDevice::configureCalibrationSettings(const QJsonObject configurationSettings) { + Locker locker(_lock); + if (!configurationSettings.empty()) { + auto iter = configurationSettings.begin(); + auto end = configurationSettings.end(); + while (iter != end) { + if (iter.key() == "bodyConfiguration") { + setConfigFromString(iter.value().toString()); + } else if (iter.key() == "headConfiguration") { + QJsonObject headObject = iter.value().toObject(); + bool overrideHead = headObject["override"].toBool(); + if (overrideHead) { + _headConfig = HeadConfig::Puck; + HEAD_PUCK_Y_OFFSET = headObject["Y"].toDouble(); + HEAD_PUCK_Z_OFFSET = headObject["Z"].toDouble(); + } else { + _headConfig = HeadConfig::HMD; + } + } else if (iter.key() == "handConfiguration") { + QJsonObject handsObject = iter.value().toObject(); + bool overrideHands = handsObject["override"].toBool(); + if (overrideHands) { + _handConfig = HandConfig::Pucks; + HAND_PUCK_Y_OFFSET = handsObject["Y"].toDouble(); + HAND_PUCK_Z_OFFSET = handsObject["Z"].toDouble(); + } else { + _handConfig = HandConfig::HandController; + } + } + iter++; + } + } +} + +void ViveControllerManager::InputDevice::calibrateNextFrame() { + Locker locker(_lock); + _calibrate = true; +} + +QJsonObject ViveControllerManager::InputDevice::configurationSettings() { + Locker locker(_lock); + QJsonObject configurationSettings; + configurationSettings["trackerConfiguration"] = configToString(_preferedConfig); + configurationSettings["HMDHead"] = (_headConfig == HeadConfig::HMD) ? true : false; + configurationSettings["handController"] = (_handConfig == HandConfig::HandController) ? true : false; + return configurationSettings; +} + +void ViveControllerManager::InputDevice::emitCalibrationStatus(const bool success) { + auto inputConfiguration = DependencyManager::get(); + QJsonObject status = QJsonObject(); + + if (_calibrated && success) { + status["calibrated"] = _calibrated; + status["configuration"] = configToString(_preferedConfig); + } else if (!_calibrated && !success) { + status["calibrated"] = _calibrated; + status["success"] = success; + } else if (!_calibrated && success) { + status["calibrated"] = _calibrated; + status["success"] = success; + status["configuration"] = configToString(_preferedConfig); + status["puckCount"] = (int)_validTrackedObjects.size(); + } + + emit inputConfiguration->calibrationStatus(status); //inputConfiguration->calibrated(status); } void ViveControllerManager::InputDevice::handleTrackedObject(uint32_t deviceIndex, const controller::InputCalibrationData& inputCalibrationData) { @@ -330,102 +442,142 @@ void ViveControllerManager::InputDevice::calibrateOrUncalibrate(const controller calibrate(inputCalibration); } else { uncalibrate(); + emitCalibrationStatus(true); } } void ViveControllerManager::InputDevice::calibrate(const controller::InputCalibrationData& inputCalibration) { qDebug() << "Puck Calibration: Starting..."; - // convert the hmd head from sensor space to avatar space - glm::mat4 hmdSensorFlippedMat = inputCalibration.hmdSensorMat * Matrices::Y_180; - glm::mat4 sensorToAvatarMat = glm::inverse(inputCalibration.avatarMat) * inputCalibration.sensorToWorldMat; - glm::mat4 hmdAvatarMat = sensorToAvatarMat * hmdSensorFlippedMat; - - // cancel the roll and pitch for the hmd head - glm::quat hmdRotation = cancelOutRollAndPitch(glmExtractRotation(hmdAvatarMat)); - glm::vec3 hmdTranslation = extractTranslation(hmdAvatarMat); - glm::mat4 currentHmd = createMatFromQuatAndPos(hmdRotation, hmdTranslation); - - // calculate the offset from the centerOfEye to defaultHeadMat - glm::mat4 defaultHeadOffset = glm::inverse(inputCalibration.defaultCenterEyeMat) * inputCalibration.defaultHeadMat; - - glm::mat4 currentHead = currentHmd * defaultHeadOffset; - - // calculate the defaultToRefrenceXform - glm::mat4 defaultToReferenceMat = currentHead * glm::inverse(inputCalibration.defaultHeadMat); int puckCount = (int)_validTrackedObjects.size(); qDebug() << "Puck Calibration: " << puckCount << " pucks found for calibration"; - _config = _preferedConfig; - if (_config != Config::Auto && puckCount < MIN_PUCK_COUNT) { - qDebug() << "Puck Calibration: Failed: Could not meet the minimal # of pucks"; + + if (puckCount == 0) { uncalibrate(); + emitCalibrationStatus(false); return; - } else if (_config == Config::Auto){ - if (puckCount == MIN_PUCK_COUNT) { - _config = Config::Feet; - qDebug() << "Puck Calibration: Auto Config: " << configToString(_config) << " configuration"; - } else if (puckCount == MIN_FEET_AND_HIPS) { - _config = Config::FeetAndHips; - qDebug() << "Puck Calibration: Auto Config: " << configToString(_config) << " configuration"; - } else if (puckCount >= MIN_FEET_HIPS_CHEST) { - _config = Config::FeetHipsAndChest; - qDebug() << "Puck Calibration: Auto Config: " << configToString(_config) << " configuration"; - } else { - qDebug() << "Puck Calibration: Auto Config Failed: Could not meet the minimal # of pucks"; - uncalibrate(); - return; - } } - std::sort(_validTrackedObjects.begin(), _validTrackedObjects.end(), sortPucksYPosition); + glm::mat4 defaultToReferenceMat = glm::mat4(); + if (_headConfig == HeadConfig::HMD) { + defaultToReferenceMat = calculateDefaultToReferenceForHmd(inputCalibration); + } else if (_headConfig == HeadConfig::Puck) { + defaultToReferenceMat = calculateDefaultToReferenceForHeadPuck(inputCalibration); + } + + _config = _preferedConfig; + + bool headConfigured = configureHead(defaultToReferenceMat, inputCalibration); + bool handsConfigured = configureHands(defaultToReferenceMat, inputCalibration); + bool bodyConfigured = configureBody(defaultToReferenceMat, inputCalibration); - if (_config == Config::Feet) { + if (!headConfigured || !handsConfigured || !bodyConfigured) { + uncalibrate(); + emitCalibrationStatus(false); + } else { + _calibrated = true; + emitCalibrationStatus(true); + qDebug() << "PuckCalibration: " << configToString(_config) << " Configuration Successful"; + } +} + +bool ViveControllerManager::InputDevice::configureHands(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration) { + std::sort(_validTrackedObjects.begin(), _validTrackedObjects.end(), sortPucksXPosition); + int puckCount = (int)_validTrackedObjects.size(); + if (_handConfig == HandConfig::Pucks && puckCount >= MIN_PUCK_COUNT) { + glm::vec3 headXAxis = getReferenceHeadXAxis(defaultToReferenceMat, inputCalibration.defaultHeadMat); + glm::vec3 headPosition = getReferenceHeadPosition(defaultToReferenceMat, inputCalibration.defaultHeadMat); + size_t FIRST_INDEX = 0; + size_t LAST_INDEX = _validTrackedObjects.size() -1; + auto& firstHand = _validTrackedObjects[FIRST_INDEX]; + auto& secondHand = _validTrackedObjects[LAST_INDEX]; + controller::Pose& firstHandPose = firstHand.second; + controller::Pose& secondHandPose = secondHand.second; + + if (determineLimbOrdering(firstHandPose, secondHandPose, headXAxis, headPosition)) { + calibrateLeftHand(defaultToReferenceMat, inputCalibration, firstHand); + calibrateRightHand(defaultToReferenceMat, inputCalibration, secondHand); + _validTrackedObjects.erase(_validTrackedObjects.begin()); + _validTrackedObjects.erase(_validTrackedObjects.end() - 1); + _overrideHands = true; + return true; + } else { + calibrateLeftHand(defaultToReferenceMat, inputCalibration, secondHand); + calibrateRightHand(defaultToReferenceMat, inputCalibration, firstHand); + _validTrackedObjects.erase(_validTrackedObjects.begin()); + _validTrackedObjects.erase(_validTrackedObjects.end() - 1); + _overrideHands = true; + return true; + } + } else if (_handConfig == HandConfig::HandController) { + _overrideHands = false; + return true; + } + return false; +} + +bool ViveControllerManager::InputDevice::configureHead(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration) { + std::sort(_validTrackedObjects.begin(), _validTrackedObjects.end(), sortPucksYPosition); + int puckCount = (int)_validTrackedObjects.size(); + if (_headConfig == HeadConfig::Puck && puckCount >= MIN_HEAD) { + calibrateHead(defaultToReferenceMat, inputCalibration); + _validTrackedObjects.erase(_validTrackedObjects.end() - 1); + _overrideHead = true; + return true; + } else if (_headConfig == HeadConfig::HMD) { + return true; + } + return false; +} + +bool ViveControllerManager::InputDevice::configureBody(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration) { + std::sort(_validTrackedObjects.begin(), _validTrackedObjects.end(), sortPucksYPosition); + int puckCount = (int)_validTrackedObjects.size(); + glm::vec3 headXAxis = getReferenceHeadXAxis(defaultToReferenceMat, inputCalibration.defaultHeadMat); + glm::vec3 headPosition = getReferenceHeadPosition(defaultToReferenceMat, inputCalibration.defaultHeadMat); + if (_config == Config::None) { + return true; + } else if (_config == Config::Feet && puckCount >= MIN_PUCK_COUNT) { calibrateFeet(defaultToReferenceMat, inputCalibration); + return true; } else if (_config == Config::FeetAndHips && puckCount >= MIN_FEET_AND_HIPS) { calibrateFeet(defaultToReferenceMat, inputCalibration); calibrateHips(defaultToReferenceMat, inputCalibration); + return true; } else if (_config == Config::FeetHipsAndChest && puckCount >= MIN_FEET_HIPS_CHEST) { calibrateFeet(defaultToReferenceMat, inputCalibration); calibrateHips(defaultToReferenceMat, inputCalibration); calibrateChest(defaultToReferenceMat, inputCalibration); + return true; } else if (_config == Config::FeetHipsAndShoulders && puckCount >= MIN_FEET_HIPS_SHOULDERS) { calibrateFeet(defaultToReferenceMat, inputCalibration); calibrateHips(defaultToReferenceMat, inputCalibration); int firstShoulderIndex = 3; int secondShoulderIndex = 4; calibrateShoulders(defaultToReferenceMat, inputCalibration, firstShoulderIndex, secondShoulderIndex); - } else if (_config == Config::FeetHipsAndHead && puckCount == MIN_FEET_HIPS_HEAD) { - glm::mat4 headPuckDefaultToReferenceMat = recalculateDefaultToReferenceForHeadPuck(inputCalibration); - glm::vec3 headXAxis = getReferenceHeadXAxis(headPuckDefaultToReferenceMat, inputCalibration.defaultHeadMat); - glm::vec3 headPosition = getReferenceHeadPosition(headPuckDefaultToReferenceMat, inputCalibration.defaultHeadMat); - calibrateFeet(headPuckDefaultToReferenceMat, inputCalibration, headXAxis, headPosition); - calibrateHips(headPuckDefaultToReferenceMat, inputCalibration); - calibrateHead(headPuckDefaultToReferenceMat, inputCalibration); - _overrideHead = true; - } else if (_config == Config::FeetHipsChestAndHead && puckCount == MIN_FEET_HIPS_CHEST_HEAD) { - glm::mat4 headPuckDefaultToReferenceMat = recalculateDefaultToReferenceForHeadPuck(inputCalibration); - glm::vec3 headXAxis = getReferenceHeadXAxis(headPuckDefaultToReferenceMat, inputCalibration.defaultHeadMat); - glm::vec3 headPosition = getReferenceHeadPosition(headPuckDefaultToReferenceMat, inputCalibration.defaultHeadMat); - calibrateFeet(headPuckDefaultToReferenceMat, inputCalibration, headXAxis, headPosition); - calibrateHips(headPuckDefaultToReferenceMat, inputCalibration); - calibrateChest(headPuckDefaultToReferenceMat, inputCalibration); - calibrateHead(headPuckDefaultToReferenceMat, inputCalibration); - _overrideHead = true; - } else { - qDebug() << "Puck Calibration: " << configToString(_config) << " Config Failed: Could not meet the minimal # of pucks"; - uncalibrate(); - return; + return true; + } else if (_config == Config::FeetHipsChestAndShoulders && puckCount >= MIN_FEET_HIPS_CHEST_SHOULDERS) { + calibrateFeet(defaultToReferenceMat, inputCalibration); + calibrateHips(defaultToReferenceMat, inputCalibration); + calibrateChest(defaultToReferenceMat, inputCalibration); + int firstShoulderIndex = 4; + int secondShoulderIndex = 5; + calibrateShoulders(defaultToReferenceMat, inputCalibration, firstShoulderIndex, secondShoulderIndex); + return true; } - _calibrated = true; - qDebug() << "PuckCalibration: " << configToString(_config) << " Configuration Successful"; + qDebug() << "Puck Calibration: " << configToString(_config) << " Config Failed: Could not meet the minimal # of pucks"; + uncalibrate(); + emitCalibrationStatus(false); + return false; } void ViveControllerManager::InputDevice::uncalibrate() { - _config = Config::Auto; + _config = Config::None; _pucksOffset.clear(); _jointToPuckMap.clear(); _calibrated = false; _overrideHead = false; + _overrideHands = false; } void ViveControllerManager::InputDevice::updateCalibratedLimbs() { @@ -439,6 +591,11 @@ void ViveControllerManager::InputDevice::updateCalibratedLimbs() { if (_overrideHead) { _poseStateMap[controller::HEAD] = addOffsetToPuckPose(controller::HEAD); } + + if (_overrideHands) { + _poseStateMap[controller::LEFT_HAND] = addOffsetToPuckPose(controller::LEFT_HAND); + _poseStateMap[controller::RIGHT_HAND] = addOffsetToPuckPose(controller::RIGHT_HAND); + } } controller::Pose ViveControllerManager::InputDevice::addOffsetToPuckPose(int joint) const { @@ -503,8 +660,29 @@ void ViveControllerManager::InputDevice::handleHandController(float deltaTime, u } } } +glm::mat4 ViveControllerManager::InputDevice::calculateDefaultToReferenceForHmd(const controller::InputCalibrationData& inputCalibration) { + // convert the hmd head from sensor space to avatar space + glm::mat4 hmdSensorFlippedMat = inputCalibration.hmdSensorMat * Matrices::Y_180; + glm::mat4 sensorToAvatarMat = glm::inverse(inputCalibration.avatarMat) * inputCalibration.sensorToWorldMat; + glm::mat4 hmdAvatarMat = sensorToAvatarMat * hmdSensorFlippedMat; -glm::mat4 ViveControllerManager::InputDevice::recalculateDefaultToReferenceForHeadPuck(const controller::InputCalibrationData& inputCalibration) { + // cancel the roll and pitch for the hmd head + glm::quat hmdRotation = cancelOutRollAndPitch(glmExtractRotation(hmdAvatarMat)); + glm::vec3 hmdTranslation = extractTranslation(hmdAvatarMat); + glm::mat4 currentHmd = createMatFromQuatAndPos(hmdRotation, hmdTranslation); + + // calculate the offset from the centerOfEye to defaultHeadMat + glm::mat4 defaultHeadOffset = glm::inverse(inputCalibration.defaultCenterEyeMat) * inputCalibration.defaultHeadMat; + + glm::mat4 currentHead = currentHmd * defaultHeadOffset; + + // calculate the defaultToRefrenceXform + glm::mat4 defaultToReferenceMat = currentHead * glm::inverse(inputCalibration.defaultHeadMat); + + return defaultToReferenceMat; +} + +glm::mat4 ViveControllerManager::InputDevice::calculateDefaultToReferenceForHeadPuck(const controller::InputCalibrationData& inputCalibration) { glm::mat4 avatarToSensorMat = glm::inverse(inputCalibration.sensorToWorldMat) * inputCalibration.avatarMat; glm::mat4 sensorToAvatarMat = glm::inverse(inputCalibration.avatarMat) * inputCalibration.sensorToWorldMat; size_t headPuckIndex = _validTrackedObjects.size() - 1; @@ -709,33 +887,78 @@ void ViveControllerManager::InputDevice::hapticsHelper(float deltaTime, bool lef } } -void ViveControllerManager::InputDevice::calibrateFeet(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration) { - auto& firstFoot = _validTrackedObjects[FIRST_FOOT]; - auto& secondFoot = _validTrackedObjects[SECOND_FOOT]; - controller::Pose& firstFootPose = firstFoot.second; - controller::Pose& secondFootPose = secondFoot.second; - - if (firstFootPose.translation.x < secondFootPose.translation.x) { - _jointToPuckMap[controller::LEFT_FOOT] = firstFoot.first; - _pucksOffset[firstFoot.first] = computeOffset(defaultToReferenceMat, inputCalibration.defaultLeftFoot, firstFootPose); - _jointToPuckMap[controller::RIGHT_FOOT] = secondFoot.first; - _pucksOffset[secondFoot.first] = computeOffset(defaultToReferenceMat, inputCalibration.defaultRightFoot, secondFootPose); - } else { - _jointToPuckMap[controller::LEFT_FOOT] = secondFoot.first; - _pucksOffset[secondFoot.first] = computeOffset(defaultToReferenceMat, inputCalibration.defaultLeftFoot, secondFootPose); - _jointToPuckMap[controller::RIGHT_FOOT] = firstFoot.first; - _pucksOffset[firstFoot.first] = computeOffset(defaultToReferenceMat, inputCalibration.defaultRightFoot, firstFootPose); +void ViveControllerManager::InputDevice::calibrateLeftHand(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration, PuckPosePair& handPair) { + controller::Pose& handPose = handPair.second; + glm::mat4 handPoseAvatarMat = createMatFromQuatAndPos(handPose.getRotation(), handPose.getTranslation()); + glm::vec3 handPoseTranslation = extractTranslation(handPoseAvatarMat); + glm::vec3 handPoseZAxis = glmExtractRotation(handPoseAvatarMat) * glm::vec3(0.0f, 0.0f, 1.0f); + glm::vec3 avatarHandYAxis = transformVectorFast(inputCalibration.defaultLeftHand, glm::vec3(0.0f, 1.0f, 0.0f)); + const float EPSILON = 1.0e-4f; + if (fabsf(fabsf(glm::dot(glm::normalize(avatarHandYAxis), glm::normalize(handPoseZAxis))) - 1.0f) < EPSILON) { + handPoseZAxis = glm::vec3(0.0f, 0.0f, 1.0f); } + + glm::vec3 zPrime = handPoseZAxis; + glm::vec3 xPrime = glm::normalize(glm::cross(avatarHandYAxis, handPoseZAxis)); + glm::vec3 yPrime = glm::normalize(glm::cross(zPrime, xPrime)); + + glm::mat4 newHandMat = glm::mat4(glm::vec4(xPrime, 0.0f), glm::vec4(yPrime, 0.0f), + glm::vec4(zPrime, 0.0f), glm::vec4(0.0f, 0.0f, 0.0f, 1.0f)); + + + glm::vec3 translationOffset = glm::vec3(0.0f, HAND_PUCK_Y_OFFSET, HAND_PUCK_Z_OFFSET); + glm::quat initialRotation = glmExtractRotation(handPoseAvatarMat); + glm::quat finalRotation = glmExtractRotation(newHandMat); + + glm::quat rotationOffset = glm::inverse(initialRotation) * finalRotation; + + glm::mat4 offsetMat = createMatFromQuatAndPos(rotationOffset, translationOffset); + + _jointToPuckMap[controller::LEFT_HAND] = handPair.first; + _pucksOffset[handPair.first] = offsetMat; +} + +void ViveControllerManager::InputDevice::calibrateRightHand(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration, PuckPosePair& handPair) { + controller::Pose& handPose = handPair.second; + glm::mat4 handPoseAvatarMat = createMatFromQuatAndPos(handPose.getRotation(), handPose.getTranslation()); + glm::vec3 handPoseTranslation = extractTranslation(handPoseAvatarMat); + glm::vec3 handPoseZAxis = glmExtractRotation(handPoseAvatarMat) * glm::vec3(0.0f, 0.0f, 1.0f); + glm::vec3 avatarHandYAxis = transformVectorFast(inputCalibration.defaultRightHand, glm::vec3(0.0f, 1.0f, 0.0f)); + const float EPSILON = 1.0e-4f; + if (fabsf(fabsf(glm::dot(glm::normalize(avatarHandYAxis), glm::normalize(handPoseZAxis))) - 1.0f) < EPSILON) { + handPoseZAxis = glm::vec3(0.0f, 0.0f, 1.0f); + } + + glm::vec3 zPrime = handPoseZAxis; + glm::vec3 xPrime = glm::normalize(glm::cross(avatarHandYAxis, handPoseZAxis)); + glm::vec3 yPrime = glm::normalize(glm::cross(zPrime, xPrime)); + glm::mat4 newHandMat = glm::mat4(glm::vec4(xPrime, 0.0f), glm::vec4(yPrime, 0.0f), + glm::vec4(zPrime, 0.0f), glm::vec4(0.0f, 0.0f, 0.0f, 1.0f)); + + + + glm::vec3 translationOffset = glm::vec3(0.0f, HAND_PUCK_Y_OFFSET, HAND_PUCK_Z_OFFSET); + glm::quat initialRotation = glmExtractRotation(handPoseAvatarMat); + glm::quat finalRotation = glmExtractRotation(newHandMat); + + glm::quat rotationOffset = glm::inverse(initialRotation) * finalRotation; + + glm::mat4 offsetMat = createMatFromQuatAndPos(rotationOffset, translationOffset); + + _jointToPuckMap[controller::RIGHT_HAND] = handPair.first; + _pucksOffset[handPair.first] = offsetMat; } -void ViveControllerManager::InputDevice::calibrateFeet(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration, glm::vec3 headXAxis, glm::vec3 headPosition) { +void ViveControllerManager::InputDevice::calibrateFeet(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration) { + glm::vec3 headXAxis = getReferenceHeadXAxis(defaultToReferenceMat, inputCalibration.defaultHeadMat); + glm::vec3 headPosition = getReferenceHeadPosition(defaultToReferenceMat, inputCalibration.defaultHeadMat); auto& firstFoot = _validTrackedObjects[FIRST_FOOT]; auto& secondFoot = _validTrackedObjects[SECOND_FOOT]; controller::Pose& firstFootPose = firstFoot.second; controller::Pose& secondFootPose = secondFoot.second; - if (determineFeetOrdering(firstFootPose, secondFootPose, headXAxis, headPosition)) { + if (determineLimbOrdering(firstFootPose, secondFootPose, headXAxis, headPosition)) { _jointToPuckMap[controller::LEFT_FOOT] = firstFoot.first; _pucksOffset[firstFoot.first] = computeOffset(defaultToReferenceMat, inputCalibration.defaultLeftFoot, firstFootPose); _jointToPuckMap[controller::RIGHT_FOOT] = secondFoot.first; @@ -790,32 +1013,13 @@ void ViveControllerManager::InputDevice::calibrateHead(glm::mat4& defaultToRefer _pucksOffset[head.first] = computeOffset(defaultToReferenceMat, inputCalibration.defaultHeadMat, newHead); } - -void ViveControllerManager::InputDevice::loadSettings() { - Settings settings; - settings.beginGroup("PUCK_CONFIG"); - { - _preferedConfig = (Config)settings.value("configuration", QVariant((int)Config::Auto)).toInt(); - } - settings.endGroup(); -} - -void ViveControllerManager::InputDevice::saveSettings() const { - Settings settings; - settings.beginGroup("PUCK_CONFIG"); - { - settings.setValue(QString("configuration"), (int)_preferedConfig); - } - settings.endGroup(); -} - QString ViveControllerManager::InputDevice::configToString(Config config) { return _configStringMap[config]; } void ViveControllerManager::InputDevice::setConfigFromString(const QString& value) { - if (value == "Auto") { - _preferedConfig = Config::Auto; + if (value == "None") { + _preferedConfig = Config::None; } else if (value == "Feet") { _preferedConfig = Config::Feet; } else if (value == "FeetAndHips") { @@ -824,58 +1028,8 @@ void ViveControllerManager::InputDevice::setConfigFromString(const QString& valu _preferedConfig = Config::FeetHipsAndChest; } else if (value == "FeetHipsAndShoulders") { _preferedConfig = Config::FeetHipsAndShoulders; - } else if (value == "FeetHipsChestAndHead") { - _preferedConfig = Config::FeetHipsChestAndHead; - } else if (value == "FeetHipsAndHead") { - _preferedConfig = Config::FeetHipsAndHead; - } -} - -void ViveControllerManager::InputDevice::createPreferences() { - loadSettings(); - auto preferences = DependencyManager::get(); - static const QString VIVE_PUCKS_CONFIG = "Vive Pucks Configuration"; - - { - static const float MIN_VALUE = -3.0f; - static const float MAX_VALUE = 3.0f; - static const float STEP = 0.01f; - - auto getter = [this]()->float { return HEAD_PUCK_Y_OFFSET; }; - auto setter = [this](const float& value) { HEAD_PUCK_Y_OFFSET = value; }; - - auto preference = new SpinnerPreference(VIVE_PUCKS_CONFIG, "HeadPuckYOffset", getter, setter); - preference->setMin(MIN_VALUE); - preference->setMax(MAX_VALUE); - preference->setDecimals(3); - preference->setStep(STEP); - preferences->addPreference(preference); - } - - { - static const float MIN_VALUE = -3.0f; - static const float MAX_VALUE = 3.0f; - static const float STEP = 0.01f; - - auto getter = [this]()->float { return HEAD_PUCK_Z_OFFSET; }; - auto setter = [this](const float& value) { HEAD_PUCK_Z_OFFSET = value; }; - - auto preference = new SpinnerPreference(VIVE_PUCKS_CONFIG, "HeadPuckXOffset", getter, setter); - preference->setMin(MIN_VALUE); - preference->setMax(MAX_VALUE); - preference->setStep(STEP); - preference->setDecimals(3); - preferences->addPreference(preference); - } - - { - auto getter = [this]()->QString { return _configStringMap[_preferedConfig]; }; - auto setter = [this](const QString& value) { setConfigFromString(value); saveSettings(); }; - auto preference = new ComboBoxPreference(VIVE_PUCKS_CONFIG, "Configuration", getter, setter); - QStringList list = {"Auto", "Feet", "FeetAndHips", "FeetHipsAndChest", "FeetHipsAndShoulders", "FeetHipsAndHead"}; - preference->setItems(list); - preferences->addPreference(preference); - + } else if (value == "FeetHipsChestAndShoulders") { + _preferedConfig = Config::FeetHipsChestAndShoulders; } } diff --git a/plugins/openvr/src/ViveControllerManager.h b/plugins/openvr/src/ViveControllerManager.h index dadaeb37db..67a9ff46fd 100644 --- a/plugins/openvr/src/ViveControllerManager.h +++ b/plugins/openvr/src/ViveControllerManager.h @@ -39,6 +39,13 @@ public: const QString getName() const override { return NAME; } bool isHandController() const override { return true; } + bool configurable() override { return true; } + + QString configurationLayout() override; + void setConfigurationSettings(const QJsonObject configurationSettings) override; + QJsonObject configurationSettings() override; + void calibrate() override; + bool uncalibrate() override; bool isHeadController() const override { return true; } bool isHeadControllerMounted() const; @@ -61,14 +68,16 @@ private: QString getDefaultMappingConfig() const override; void update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) override; void focusOutEvent() override; - void createPreferences(); bool triggerHapticPulse(float strength, float duration, controller::Hand hand) override; void hapticsHelper(float deltaTime, bool leftHand); void calibrateOrUncalibrate(const controller::InputCalibrationData& inputCalibration); void calibrate(const controller::InputCalibrationData& inputCalibration); void uncalibrate(); + void configureCalibrationSettings(const QJsonObject configurationSettings); + QJsonObject configurationSettings(); controller::Pose addOffsetToPuckPose(int joint) const; - glm::mat4 recalculateDefaultToReferenceForHeadPuck(const controller::InputCalibrationData& inputCalibration); + glm::mat4 calculateDefaultToReferenceForHeadPuck(const controller::InputCalibrationData& inputCalibration); + glm::mat4 calculateDefaultToReferenceForHmd(const controller::InputCalibrationData& inputCalibration); void updateCalibratedLimbs(); bool checkForCalibrationEvent(); void handleHandController(float deltaTime, uint32_t deviceIndex, const controller::InputCalibrationData& inputCalibrationData, bool isLeftHand); @@ -83,16 +92,21 @@ private: void partitionTouchpad(int sButton, int xAxis, int yAxis, int centerPsuedoButton, int xPseudoButton, int yPseudoButton); void printDeviceTrackingResultChange(uint32_t deviceIndex); void setConfigFromString(const QString& value); - void loadSettings(); - void saveSettings() const; + bool configureHead(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration); + bool configureHands(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration); + bool configureBody(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration); + void calibrateLeftHand(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration, PuckPosePair& handPair); + void calibrateRightHand(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration, PuckPosePair& handPair); void calibrateFeet(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration); - void calibrateFeet(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration, glm::vec3 headXAxis, glm::vec3 headPosition); void calibrateHips(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration); void calibrateChest(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration); - void calibrateShoulders(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration, int firstShoulderIndex, int secondShoulderIndex); void calibrateHead(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration); + void calibrateFromHandController(const controller::InputCalibrationData& inputCalibrationData); + void calibrateFromUI(const controller::InputCalibrationData& inputCalibrationData); + void emitCalibrationStatus(const bool success); + void calibrateNextFrame(); class FilteredStick { @@ -119,16 +133,28 @@ private: glm::vec2 _stick { 0.0f, 0.0f }; }; enum class Config { - Auto, + None, Feet, FeetAndHips, FeetHipsAndChest, FeetHipsAndShoulders, - FeetHipsChestAndHead, - FeetHipsAndHead + FeetHipsChestAndShoulders, }; - Config _config { Config::Auto }; - Config _preferedConfig { Config::Auto }; + + enum class HeadConfig { + HMD, + Puck + }; + + enum class HandConfig { + HandController, + Pucks + }; + + Config _config { Config::None }; + Config _preferedConfig { Config::None }; + HeadConfig _headConfig { HeadConfig::HMD }; + HandConfig _handConfig { HandConfig::HandController }; FilteredStick _filteredLeftStick; FilteredStick _filteredRightStick; @@ -152,7 +178,9 @@ private: bool _triggersPressedHandled { false }; bool _calibrated { false }; bool _timeTilCalibrationSet { false }; + bool _calibrate { false }; bool _overrideHead { false }; + bool _overrideHands { false }; mutable std::recursive_mutex _lock; QString configToString(Config config); diff --git a/plugins/pcmCodec/CMakeLists.txt b/plugins/pcmCodec/CMakeLists.txt index 900a642a88..5e52705033 100644 --- a/plugins/pcmCodec/CMakeLists.txt +++ b/plugins/pcmCodec/CMakeLists.txt @@ -8,6 +8,6 @@ set(TARGET_NAME pcmCodec) setup_hifi_client_server_plugin() -link_hifi_libraries(shared plugins) +link_hifi_libraries(shared plugins input-plugins display-plugins) install_beside_console()