mirror of
https://github.com/overte-org/overte.git
synced 2025-08-08 14:18:24 +02:00
Merge pull request #10875 from highfidelity/spectator-camera
Spectator Camera
This commit is contained in:
commit
17fcfad957
30 changed files with 1617 additions and 93 deletions
19
interface/resources/icons/tablet-icons/spectator-a.svg
Normal file
19
interface/resources/icons/tablet-icons/spectator-a.svg
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 21.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 50 50" style="enable-background:new 0 0 50 50;" xml:space="preserve">
|
||||||
|
<path d="M43.9,13.3c-1.3-0.6-2.4-0.3-3.5,0.4c-1.6,1.2-3.3,2.4-5,3.5c-1.4-3.4-3.3-5-6.3-5c-5.9-0.1-11.9-0.1-17.9,0
|
||||||
|
c-3.8,0.1-6.4,3.1-6.4,7.3c0,3.7-0.1,7.6,0,11.4c0,1.1,0.2,2.1,0.6,3.1c1.2,2.7,3.3,3.8,6,3.8c5.6,0,11-0.1,16.5,0
|
||||||
|
c3.5,0.1,6-1.5,7.4-5.1c1.7,1.2,3.4,2.4,5.1,3.5c1.1,0.7,2.2,1.1,3.5,0.3c1.2-0.7,1.6-1.9,1.6-3.3c0-5.6,0-11,0-16.6
|
||||||
|
C45.5,15.3,45.2,14.1,43.9,13.3z M32.2,30.5c0,2.5-1,3.6-3.4,3.6c-2.9,0-5.8,0-8.7,0s-5.6,0-8.5,0.1c-2.4-0.1-3.4-1.2-3.4-3.7
|
||||||
|
c0-3.7,0-7.5,0-11.2c0-2.2,1.1-3.4,3.1-3.4c5.9,0,11.9,0,17.8,0c2,0,3.1,1.2,3.1,3.4C32.2,23,32.2,26.8,32.2,30.5z M41.9,32.8
|
||||||
|
c-2.1-1.4-4.2-2.9-6.3-4.3c-0.1-0.1-0.2-0.4-0.2-0.7c0-1.9,0-3.7,0-5.5c0-0.3,0.1-0.7,0.3-0.8c2-1.4,4-2.8,6.2-4.3
|
||||||
|
C41.9,22.3,41.9,27.4,41.9,32.8z"/>
|
||||||
|
<path d="M27.4,25C27.4,24.7,27.4,25.2,27.4,25c0-1.1-0.1-2-0.2-2.7c-0.2-1.4-0.7-2.7-1.6-4c-0.2-0.3-0.5-0.5-1-0.6
|
||||||
|
c-0.4-0.1-0.9,0-1.3,0.2c-0.5,0.3-0.7,1.3-0.3,1.8c1.2,1.6,1.4,3,1.4,4.8c0.1,2.1-0.2,3.4-1.5,5.2c-0.2,0.3-0.2,1.1,0.1,1.6
|
||||||
|
c0.1,0.2,0.3,0.4,0.6,0.6c0.2,0.1,0.3,0.1,0.5,0.1c0.5,0,1-0.3,1.3-0.9C27,29.3,27.3,27.3,27.4,25L27.4,25z"/>
|
||||||
|
<ellipse cx="15.2" cy="24.7" rx="2.1" ry="2.4"/>
|
||||||
|
<path d="M22.3,24.8C22.3,24.7,22.3,25,22.3,24.8c0-0.7-0.1-1.5-0.1-1.9c-0.2-1-0.6-2.1-1.3-3c-0.1-0.2-0.4-0.5-0.9-0.5
|
||||||
|
c-0.7,0-0.9,0.2-1.2,0.4c-0.4,0.2-0.5,0.9-0.2,1.3c0.9,1.2,1,2.1,1.1,3.5c0,1.6-0.2,2.5-1.1,3.8c-0.1,0.2-0.1,0.7,0,1.2
|
||||||
|
c0.1,0.2,0.2,0.3,0.5,0.4c0.1,0,0.2,0.1,0.3,0.1c0.5,0.2,1.2,0.1,1.5-0.5C21.7,28,22.2,26.5,22.3,24.8L22.3,24.8z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.8 KiB |
22
interface/resources/icons/tablet-icons/spectator-i.svg
Normal file
22
interface/resources/icons/tablet-icons/spectator-i.svg
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 21.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 50 50" style="enable-background:new 0 0 50 50;" xml:space="preserve">
|
||||||
|
<style type="text/css">
|
||||||
|
.st0{fill:#FFFFFF;}
|
||||||
|
</style>
|
||||||
|
<path class="st0" d="M43.9,13.3c-1.3-0.6-2.4-0.3-3.5,0.4c-1.6,1.2-3.3,2.4-5,3.5c-1.4-3.4-3.3-5-6.3-5c-5.9-0.1-11.9-0.1-17.9,0
|
||||||
|
c-3.8,0.1-6.4,3.1-6.4,7.3c0,3.7-0.1,7.6,0,11.4c0,1.1,0.2,2.1,0.6,3.1c1.2,2.7,3.3,3.8,6,3.8c5.6,0,11-0.1,16.5,0
|
||||||
|
c3.5,0.1,6-1.5,7.4-5.1c1.7,1.2,3.4,2.4,5.1,3.5c1.1,0.7,2.2,1.1,3.5,0.3c1.2-0.7,1.6-1.9,1.6-3.3c0-5.6,0-11,0-16.6
|
||||||
|
C45.5,15.3,45.2,14.1,43.9,13.3z M32.2,30.5c0,2.5-1,3.6-3.4,3.6c-2.9,0-5.8,0-8.7,0s-5.6,0-8.5,0.1c-2.4-0.1-3.4-1.2-3.4-3.7
|
||||||
|
c0-3.7,0-7.5,0-11.2c0-2.2,1.1-3.4,3.1-3.4c5.9,0,11.9,0,17.8,0c2,0,3.1,1.2,3.1,3.4C32.2,23,32.2,26.8,32.2,30.5z M41.9,32.8
|
||||||
|
c-2.1-1.4-4.2-2.9-6.3-4.3c-0.1-0.1-0.2-0.4-0.2-0.7c0-1.9,0-3.7,0-5.5c0-0.3,0.1-0.7,0.3-0.8c2-1.4,4-2.8,6.2-4.3
|
||||||
|
C41.9,22.3,41.9,27.4,41.9,32.8z"/>
|
||||||
|
<path class="st0" d="M27.4,25C27.4,24.7,27.4,25.2,27.4,25c0-1.1-0.1-2-0.2-2.7c-0.2-1.4-0.7-2.7-1.6-4c-0.2-0.3-0.5-0.5-1-0.6
|
||||||
|
c-0.4-0.1-0.9,0-1.3,0.2c-0.5,0.3-0.7,1.3-0.3,1.8c1.2,1.6,1.4,3,1.4,4.8c0.1,2.1-0.2,3.4-1.5,5.2c-0.2,0.3-0.2,1.1,0.1,1.6
|
||||||
|
c0.1,0.2,0.3,0.4,0.6,0.6c0.2,0.1,0.3,0.1,0.5,0.1c0.5,0,1-0.3,1.3-0.9C27,29.3,27.3,27.3,27.4,25L27.4,25z"/>
|
||||||
|
<ellipse class="st0" cx="15.2" cy="24.7" rx="2.1" ry="2.4"/>
|
||||||
|
<path class="st0" d="M22.3,24.8C22.3,24.7,22.3,25,22.3,24.8c0-0.7-0.1-1.5-0.1-1.9c-0.2-1-0.6-2.1-1.3-3c-0.1-0.2-0.4-0.5-0.9-0.5
|
||||||
|
c-0.7,0-0.9,0.2-1.2,0.4c-0.4,0.2-0.5,0.9-0.2,1.3c0.9,1.2,1,2.1,1.1,3.5c0,1.6-0.2,2.5-1.1,3.8c-0.1,0.2-0.1,0.7,0,1.2
|
||||||
|
c0.1,0.2,0.2,0.3,0.5,0.4c0.1,0,0.2,0.1,0.3,0.1c0.5,0.2,1.2,0.1,1.5-0.5C21.7,28,22.2,26.5,22.3,24.8L22.3,24.8z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.9 KiB |
BIN
interface/resources/images/static.gif
Normal file
BIN
interface/resources/images/static.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 899 KiB |
|
@ -18,7 +18,7 @@ Original.CheckBox {
|
||||||
id: checkBox
|
id: checkBox
|
||||||
|
|
||||||
property int colorScheme: hifi.colorSchemes.light
|
property int colorScheme: hifi.colorSchemes.light
|
||||||
property string color: hifi.colors.lightGray
|
property string color: hifi.colors.lightGrayText
|
||||||
readonly property bool isLightColorScheme: colorScheme == hifi.colorSchemes.light
|
readonly property bool isLightColorScheme: colorScheme == hifi.colorSchemes.light
|
||||||
property bool isRedCheck: false
|
property bool isRedCheck: false
|
||||||
property int boxSize: 14
|
property int boxSize: 14
|
||||||
|
|
38
interface/resources/qml/controls-uit/Separator.qml
Normal file
38
interface/resources/qml/controls-uit/Separator.qml
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
//
|
||||||
|
// Separator.qml
|
||||||
|
//
|
||||||
|
// Created by Zach Fox on 2017-06-06
|
||||||
|
// 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 "../styles-uit"
|
||||||
|
|
||||||
|
Item {
|
||||||
|
// Size
|
||||||
|
height: 2;
|
||||||
|
Rectangle {
|
||||||
|
// Size
|
||||||
|
width: parent.width;
|
||||||
|
height: 1;
|
||||||
|
// Anchors
|
||||||
|
anchors.left: parent.left;
|
||||||
|
anchors.bottom: parent.bottom;
|
||||||
|
anchors.bottomMargin: height;
|
||||||
|
// Style
|
||||||
|
color: hifi.colors.baseGrayShadow;
|
||||||
|
}
|
||||||
|
Rectangle {
|
||||||
|
// Size
|
||||||
|
width: parent.width;
|
||||||
|
height: 1;
|
||||||
|
// Anchors
|
||||||
|
anchors.left: parent.left;
|
||||||
|
anchors.bottom: parent.bottom;
|
||||||
|
// Style
|
||||||
|
color: hifi.colors.baseGrayHighlight;
|
||||||
|
}
|
||||||
|
}
|
156
interface/resources/qml/controls-uit/Switch.qml
Normal file
156
interface/resources/qml/controls-uit/Switch.qml
Normal file
|
@ -0,0 +1,156 @@
|
||||||
|
//
|
||||||
|
// Switch.qml
|
||||||
|
//
|
||||||
|
// Created by Zach Fox on 2017-06-06
|
||||||
|
// 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 as Original
|
||||||
|
import QtQuick.Controls.Styles 1.4
|
||||||
|
|
||||||
|
import "../styles-uit"
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: rootSwitch;
|
||||||
|
|
||||||
|
property int colorScheme: hifi.colorSchemes.light;
|
||||||
|
readonly property bool isLightColorScheme: colorScheme == hifi.colorSchemes.light;
|
||||||
|
property int switchWidth: 70;
|
||||||
|
readonly property int switchRadius: height/2;
|
||||||
|
property string labelTextOff: "";
|
||||||
|
property string labelGlyphOffText: "";
|
||||||
|
property int labelGlyphOffSize: 32;
|
||||||
|
property string labelTextOn: "";
|
||||||
|
property string labelGlyphOnText: "";
|
||||||
|
property int labelGlyphOnSize: 32;
|
||||||
|
property alias checked: originalSwitch.checked;
|
||||||
|
signal onCheckedChanged;
|
||||||
|
signal clicked;
|
||||||
|
|
||||||
|
Original.Switch {
|
||||||
|
id: originalSwitch;
|
||||||
|
activeFocusOnPress: true;
|
||||||
|
anchors.top: rootSwitch.top;
|
||||||
|
anchors.left: rootSwitch.left;
|
||||||
|
anchors.leftMargin: rootSwitch.width/2 - rootSwitch.switchWidth/2;
|
||||||
|
onCheckedChanged: rootSwitch.onCheckedChanged();
|
||||||
|
onClicked: rootSwitch.clicked();
|
||||||
|
|
||||||
|
style: SwitchStyle {
|
||||||
|
|
||||||
|
padding {
|
||||||
|
top: 3;
|
||||||
|
left: 3;
|
||||||
|
right: 3;
|
||||||
|
bottom: 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
groove: Rectangle {
|
||||||
|
color: "#252525";
|
||||||
|
implicitWidth: rootSwitch.switchWidth;
|
||||||
|
implicitHeight: rootSwitch.height;
|
||||||
|
radius: rootSwitch.switchRadius;
|
||||||
|
}
|
||||||
|
|
||||||
|
handle: Rectangle {
|
||||||
|
id: switchHandle;
|
||||||
|
implicitWidth: rootSwitch.height - padding.top - padding.bottom;
|
||||||
|
implicitHeight: implicitWidth;
|
||||||
|
radius: implicitWidth/2;
|
||||||
|
border.color: hifi.colors.lightGrayText;
|
||||||
|
color: hifi.colors.lightGray;
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent;
|
||||||
|
hoverEnabled: true;
|
||||||
|
onEntered: parent.color = hifi.colors.blueHighlight;
|
||||||
|
onExited: parent.color = hifi.colors.lightGray;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// OFF Label
|
||||||
|
Item {
|
||||||
|
anchors.right: originalSwitch.left;
|
||||||
|
anchors.rightMargin: 10;
|
||||||
|
anchors.top: rootSwitch.top;
|
||||||
|
height: rootSwitch.height;
|
||||||
|
|
||||||
|
RalewaySemiBold {
|
||||||
|
id: labelOff;
|
||||||
|
text: labelTextOff;
|
||||||
|
size: hifi.fontSizes.inputLabel;
|
||||||
|
color: originalSwitch.checked ? hifi.colors.lightGrayText : "#FFFFFF";
|
||||||
|
anchors.top: parent.top;
|
||||||
|
anchors.right: parent.right;
|
||||||
|
width: paintedWidth;
|
||||||
|
height: parent.height;
|
||||||
|
verticalAlignment: Text.AlignVCenter;
|
||||||
|
}
|
||||||
|
|
||||||
|
HiFiGlyphs {
|
||||||
|
id: labelGlyphOff;
|
||||||
|
text: labelGlyphOffText;
|
||||||
|
size: labelGlyphOffSize;
|
||||||
|
color: labelOff.color;
|
||||||
|
anchors.top: parent.top;
|
||||||
|
anchors.topMargin: 2;
|
||||||
|
anchors.right: labelOff.left;
|
||||||
|
anchors.rightMargin: 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.top: parent.top;
|
||||||
|
anchors.bottom: parent.bottom;
|
||||||
|
anchors.left: labelGlyphOff.left;
|
||||||
|
anchors.right: labelOff.right;
|
||||||
|
onClicked: {
|
||||||
|
originalSwitch.checked = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ON Label
|
||||||
|
Item {
|
||||||
|
anchors.left: originalSwitch.right;
|
||||||
|
anchors.leftMargin: 10;
|
||||||
|
anchors.top: rootSwitch.top;
|
||||||
|
height: rootSwitch.height;
|
||||||
|
|
||||||
|
RalewaySemiBold {
|
||||||
|
id: labelOn;
|
||||||
|
text: labelTextOn;
|
||||||
|
size: hifi.fontSizes.inputLabel;
|
||||||
|
color: originalSwitch.checked ? "#FFFFFF" : hifi.colors.lightGrayText;
|
||||||
|
anchors.top: parent.top;
|
||||||
|
anchors.left: parent.left;
|
||||||
|
width: paintedWidth;
|
||||||
|
height: parent.height;
|
||||||
|
verticalAlignment: Text.AlignVCenter;
|
||||||
|
}
|
||||||
|
|
||||||
|
HiFiGlyphs {
|
||||||
|
id: labelGlyphOn;
|
||||||
|
text: labelGlyphOnText;
|
||||||
|
size: labelGlyphOnSize;
|
||||||
|
color: labelOn.color;
|
||||||
|
anchors.top: parent.top;
|
||||||
|
anchors.left: labelOn.right;
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.top: parent.top;
|
||||||
|
anchors.bottom: parent.bottom;
|
||||||
|
anchors.left: labelOn.left;
|
||||||
|
anchors.right: labelGlyphOn.right;
|
||||||
|
onClicked: {
|
||||||
|
originalSwitch.checked = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -32,14 +32,15 @@ Item {
|
||||||
radius: popupRadius
|
radius: popupRadius
|
||||||
}
|
}
|
||||||
Rectangle {
|
Rectangle {
|
||||||
width: Math.max(parent.width * 0.75, 400)
|
id: textContainer;
|
||||||
|
width: Math.max(parent.width * 0.8, 400)
|
||||||
height: contentContainer.height + 50
|
height: contentContainer.height + 50
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
radius: popupRadius
|
radius: popupRadius
|
||||||
color: "white"
|
color: "white"
|
||||||
Item {
|
Item {
|
||||||
id: contentContainer
|
id: contentContainer
|
||||||
width: parent.width - 60
|
width: parent.width - 50
|
||||||
height: childrenRect.height
|
height: childrenRect.height
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
Item {
|
Item {
|
||||||
|
@ -92,7 +93,7 @@ Item {
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
anchors.topMargin: -20
|
anchors.topMargin: -20
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.rightMargin: -25
|
anchors.rightMargin: -20
|
||||||
MouseArea {
|
MouseArea {
|
||||||
anchors.fill: closeGlyphButton
|
anchors.fill: closeGlyphButton
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
|
@ -127,11 +128,51 @@ Item {
|
||||||
color: hifi.colors.darkGray
|
color: hifi.colors.darkGray
|
||||||
wrapMode: Text.WordWrap
|
wrapMode: Text.WordWrap
|
||||||
textFormat: Text.StyledText
|
textFormat: Text.StyledText
|
||||||
|
onLinkActivated: {
|
||||||
|
Qt.openUrlExternally(link)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
// Left gray MouseArea
|
||||||
MouseArea {
|
MouseArea {
|
||||||
anchors.fill: parent
|
anchors.left: parent.left;
|
||||||
|
anchors.right: textContainer.left;
|
||||||
|
anchors.top: textContainer.top;
|
||||||
|
anchors.bottom: textContainer.bottom;
|
||||||
|
acceptedButtons: Qt.LeftButton
|
||||||
|
onClicked: {
|
||||||
|
letterbox.visible = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Right gray MouseArea
|
||||||
|
MouseArea {
|
||||||
|
anchors.left: textContainer.left;
|
||||||
|
anchors.right: parent.left;
|
||||||
|
anchors.top: textContainer.top;
|
||||||
|
anchors.bottom: textContainer.bottom;
|
||||||
|
acceptedButtons: Qt.LeftButton
|
||||||
|
onClicked: {
|
||||||
|
letterbox.visible = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Top gray MouseArea
|
||||||
|
MouseArea {
|
||||||
|
anchors.left: parent.left;
|
||||||
|
anchors.right: parent.right;
|
||||||
|
anchors.top: parent.top;
|
||||||
|
anchors.bottom: textContainer.top;
|
||||||
|
acceptedButtons: Qt.LeftButton
|
||||||
|
onClicked: {
|
||||||
|
letterbox.visible = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Bottom gray MouseArea
|
||||||
|
MouseArea {
|
||||||
|
anchors.left: parent.left;
|
||||||
|
anchors.right: parent.right;
|
||||||
|
anchors.top: textContainer.bottom;
|
||||||
|
anchors.bottom: parent.bottom;
|
||||||
acceptedButtons: Qt.LeftButton
|
acceptedButtons: Qt.LeftButton
|
||||||
onClicked: {
|
onClicked: {
|
||||||
letterbox.visible = false
|
letterbox.visible = false
|
||||||
|
|
374
interface/resources/qml/hifi/SpectatorCamera.qml
Normal file
374
interface/resources/qml/hifi/SpectatorCamera.qml
Normal file
|
@ -0,0 +1,374 @@
|
||||||
|
//
|
||||||
|
// SpectatorCamera.qml
|
||||||
|
// qml/hifi
|
||||||
|
//
|
||||||
|
// Spectator Camera
|
||||||
|
//
|
||||||
|
// Created by Zach Fox on 2017-06-05
|
||||||
|
// Copyright 2016 High Fidelity, Inc.
|
||||||
|
//
|
||||||
|
// Distributed under the Apache License, Version 2.0.
|
||||||
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
//
|
||||||
|
|
||||||
|
import Hifi 1.0 as Hifi
|
||||||
|
import QtQuick 2.5
|
||||||
|
import QtQuick.Controls 1.4
|
||||||
|
import "../styles-uit"
|
||||||
|
import "../controls-uit" as HifiControlsUit
|
||||||
|
import "../controls" as HifiControls
|
||||||
|
|
||||||
|
// references HMD, XXX from root context
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
HifiConstants { id: hifi; }
|
||||||
|
|
||||||
|
id: spectatorCamera;
|
||||||
|
// Style
|
||||||
|
color: hifi.colors.baseGray;
|
||||||
|
|
||||||
|
// The letterbox used for popup messages
|
||||||
|
LetterboxMessage {
|
||||||
|
id: letterboxMessage;
|
||||||
|
z: 999; // Force the popup on top of everything else
|
||||||
|
}
|
||||||
|
function letterbox(headerGlyph, headerText, message) {
|
||||||
|
letterboxMessage.headerGlyph = headerGlyph;
|
||||||
|
letterboxMessage.headerText = headerText;
|
||||||
|
letterboxMessage.text = message;
|
||||||
|
letterboxMessage.visible = true;
|
||||||
|
letterboxMessage.popupRadius = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// TITLE BAR START
|
||||||
|
//
|
||||||
|
Item {
|
||||||
|
id: titleBarContainer;
|
||||||
|
// Size
|
||||||
|
width: spectatorCamera.width;
|
||||||
|
height: 50;
|
||||||
|
// Anchors
|
||||||
|
anchors.left: parent.left;
|
||||||
|
anchors.top: parent.top;
|
||||||
|
|
||||||
|
// "Spectator" text
|
||||||
|
RalewaySemiBold {
|
||||||
|
id: titleBarText;
|
||||||
|
text: "Spectator";
|
||||||
|
// Text size
|
||||||
|
size: hifi.fontSizes.overlayTitle;
|
||||||
|
// Anchors
|
||||||
|
anchors.fill: parent;
|
||||||
|
anchors.leftMargin: 16;
|
||||||
|
// Style
|
||||||
|
color: hifi.colors.lightGrayText;
|
||||||
|
// Alignment
|
||||||
|
horizontalAlignment: Text.AlignHLeft;
|
||||||
|
verticalAlignment: Text.AlignVCenter;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Separator
|
||||||
|
HifiControlsUit.Separator {
|
||||||
|
anchors.left: parent.left;
|
||||||
|
anchors.right: parent.right;
|
||||||
|
anchors.bottom: parent.bottom;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//
|
||||||
|
// TITLE BAR END
|
||||||
|
//
|
||||||
|
|
||||||
|
//
|
||||||
|
// SPECTATOR APP DESCRIPTION START
|
||||||
|
//
|
||||||
|
Item {
|
||||||
|
id: spectatorDescriptionContainer;
|
||||||
|
// Size
|
||||||
|
width: spectatorCamera.width;
|
||||||
|
height: childrenRect.height;
|
||||||
|
// Anchors
|
||||||
|
anchors.left: parent.left;
|
||||||
|
anchors.top: titleBarContainer.bottom;
|
||||||
|
|
||||||
|
// (i) Glyph
|
||||||
|
HiFiGlyphs {
|
||||||
|
id: spectatorDescriptionGlyph;
|
||||||
|
text: hifi.glyphs.info;
|
||||||
|
// Size
|
||||||
|
width: 20;
|
||||||
|
height: parent.height;
|
||||||
|
size: 60;
|
||||||
|
// Anchors
|
||||||
|
anchors.left: parent.left;
|
||||||
|
anchors.leftMargin: 20;
|
||||||
|
anchors.top: parent.top;
|
||||||
|
anchors.topMargin: 0;
|
||||||
|
// Style
|
||||||
|
color: hifi.colors.lightGrayText;
|
||||||
|
horizontalAlignment: Text.AlignHLeft;
|
||||||
|
verticalAlignment: Text.AlignTop;
|
||||||
|
}
|
||||||
|
|
||||||
|
// "Spectator" app description text
|
||||||
|
RalewayLight {
|
||||||
|
id: spectatorDescriptionText;
|
||||||
|
text: "Spectator lets you change what your monitor displays while you're using a VR headset. Use Spectator when streaming and recording video.";
|
||||||
|
// Text size
|
||||||
|
size: 14;
|
||||||
|
// Size
|
||||||
|
width: 350;
|
||||||
|
height: paintedHeight;
|
||||||
|
// Anchors
|
||||||
|
anchors.top: parent.top;
|
||||||
|
anchors.topMargin: 15;
|
||||||
|
anchors.left: spectatorDescriptionGlyph.right;
|
||||||
|
anchors.leftMargin: 40;
|
||||||
|
// Style
|
||||||
|
color: hifi.colors.lightGrayText;
|
||||||
|
wrapMode: Text.WordWrap;
|
||||||
|
// Alignment
|
||||||
|
horizontalAlignment: Text.AlignHLeft;
|
||||||
|
verticalAlignment: Text.AlignVCenter;
|
||||||
|
}
|
||||||
|
|
||||||
|
// "Learn More" text
|
||||||
|
RalewayRegular {
|
||||||
|
id: spectatorLearnMoreText;
|
||||||
|
text: "Learn More About Spectator";
|
||||||
|
// Text size
|
||||||
|
size: 14;
|
||||||
|
// Size
|
||||||
|
width: paintedWidth;
|
||||||
|
height: paintedHeight;
|
||||||
|
// Anchors
|
||||||
|
anchors.top: spectatorDescriptionText.bottom;
|
||||||
|
anchors.topMargin: 10;
|
||||||
|
anchors.left: spectatorDescriptionText.anchors.left;
|
||||||
|
anchors.leftMargin: spectatorDescriptionText.anchors.leftMargin;
|
||||||
|
// Style
|
||||||
|
color: hifi.colors.blueAccent;
|
||||||
|
wrapMode: Text.WordWrap;
|
||||||
|
font.underline: true;
|
||||||
|
// Alignment
|
||||||
|
horizontalAlignment: Text.AlignHLeft;
|
||||||
|
verticalAlignment: Text.AlignVCenter;
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent;
|
||||||
|
hoverEnabled: enabled;
|
||||||
|
onClicked: {
|
||||||
|
letterbox(hifi.glyphs.question,
|
||||||
|
"Spectator Camera",
|
||||||
|
"By default, your monitor shows a preview of what you're seeing in VR. " +
|
||||||
|
"Using the Spectator Camera app, your monitor can display the view " +
|
||||||
|
"from a virtual hand-held camera - perfect for taking selfies or filming " +
|
||||||
|
"your friends!<br>" +
|
||||||
|
"<h3>Streaming and Recording</h3>" +
|
||||||
|
"We recommend OBS for streaming and recording the contents of your monitor to services like " +
|
||||||
|
"Twitch, YouTube Live, and Facebook Live.<br><br>" +
|
||||||
|
"To get started using OBS, click this link now. The page will open in an external browser:<br>" +
|
||||||
|
'<font size="4"><a href="https://obsproject.com/forum/threads/official-overview-guide.402/">OBS Official Overview Guide</a></font>');
|
||||||
|
}
|
||||||
|
onEntered: parent.color = hifi.colors.blueHighlight;
|
||||||
|
onExited: parent.color = hifi.colors.blueAccent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Separator
|
||||||
|
HifiControlsUit.Separator {
|
||||||
|
anchors.left: parent.left;
|
||||||
|
anchors.right: parent.right;
|
||||||
|
anchors.top: spectatorLearnMoreText.bottom;
|
||||||
|
anchors.topMargin: spectatorDescriptionText.anchors.topMargin;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//
|
||||||
|
// SPECTATOR APP DESCRIPTION END
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
// SPECTATOR CONTROLS START
|
||||||
|
//
|
||||||
|
Item {
|
||||||
|
id: spectatorControlsContainer;
|
||||||
|
// Size
|
||||||
|
height: spectatorCamera.height - spectatorDescriptionContainer.height - titleBarContainer.height;
|
||||||
|
// Anchors
|
||||||
|
anchors.top: spectatorDescriptionContainer.bottom;
|
||||||
|
anchors.topMargin: 20;
|
||||||
|
anchors.left: parent.left;
|
||||||
|
anchors.leftMargin: 25;
|
||||||
|
anchors.right: parent.right;
|
||||||
|
anchors.rightMargin: anchors.leftMargin;
|
||||||
|
|
||||||
|
// "Camera On" Checkbox
|
||||||
|
HifiControlsUit.CheckBox {
|
||||||
|
id: cameraToggleCheckBox;
|
||||||
|
colorScheme: hifi.colorSchemes.dark;
|
||||||
|
anchors.left: parent.left;
|
||||||
|
anchors.top: parent.top;
|
||||||
|
text: "Spectator Camera On";
|
||||||
|
boxSize: 24;
|
||||||
|
onClicked: {
|
||||||
|
sendToScript({method: (checked ? 'spectatorCameraOn' : 'spectatorCameraOff')});
|
||||||
|
spectatorCameraPreview.ready = checked;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Instructions or Preview
|
||||||
|
Rectangle {
|
||||||
|
id: spectatorCameraImageContainer;
|
||||||
|
anchors.left: parent.left;
|
||||||
|
anchors.top: cameraToggleCheckBox.bottom;
|
||||||
|
anchors.topMargin: 20;
|
||||||
|
anchors.right: parent.right;
|
||||||
|
height: 250;
|
||||||
|
color: cameraToggleCheckBox.checked ? "transparent" : "black";
|
||||||
|
|
||||||
|
AnimatedImage {
|
||||||
|
source: "../../images/static.gif"
|
||||||
|
visible: !cameraToggleCheckBox.checked;
|
||||||
|
anchors.fill: parent;
|
||||||
|
opacity: 0.15;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Instructions (visible when display texture isn't set)
|
||||||
|
FiraSansRegular {
|
||||||
|
id: spectatorCameraInstructions;
|
||||||
|
text: "Turn on Spectator Camera for a preview\nof what your monitor shows.";
|
||||||
|
size: 16;
|
||||||
|
color: hifi.colors.lightGrayText;
|
||||||
|
visible: !cameraToggleCheckBox.checked;
|
||||||
|
anchors.fill: parent;
|
||||||
|
horizontalAlignment: Text.AlignHCenter;
|
||||||
|
verticalAlignment: Text.AlignVCenter;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spectator Camera Preview
|
||||||
|
Hifi.ResourceImageItem {
|
||||||
|
id: spectatorCameraPreview;
|
||||||
|
visible: cameraToggleCheckbox.checked;
|
||||||
|
url: monitorShowsSwitch.checked ? "resource://spectatorCameraFrame" : "resource://hmdPreviewFrame";
|
||||||
|
ready: cameraToggleCheckBox.checked;
|
||||||
|
mirrorVertically: true;
|
||||||
|
anchors.fill: parent;
|
||||||
|
onVisibleChanged: {
|
||||||
|
ready = cameraToggleCheckBox.checked;
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// "Monitor Shows" Switch Label Glyph
|
||||||
|
HiFiGlyphs {
|
||||||
|
id: monitorShowsSwitchLabelGlyph;
|
||||||
|
text: hifi.glyphs.screen;
|
||||||
|
size: 32;
|
||||||
|
color: hifi.colors.blueHighlight;
|
||||||
|
anchors.top: spectatorCameraImageContainer.bottom;
|
||||||
|
anchors.topMargin: 13;
|
||||||
|
anchors.left: parent.left;
|
||||||
|
}
|
||||||
|
// "Monitor Shows" Switch Label
|
||||||
|
RalewayLight {
|
||||||
|
id: monitorShowsSwitchLabel;
|
||||||
|
text: "MONITOR SHOWS:";
|
||||||
|
anchors.top: spectatorCameraImageContainer.bottom;
|
||||||
|
anchors.topMargin: 20;
|
||||||
|
anchors.left: monitorShowsSwitchLabelGlyph.right;
|
||||||
|
anchors.leftMargin: 6;
|
||||||
|
size: 16;
|
||||||
|
width: paintedWidth;
|
||||||
|
height: paintedHeight;
|
||||||
|
color: hifi.colors.lightGrayText;
|
||||||
|
verticalAlignment: Text.AlignVCenter;
|
||||||
|
}
|
||||||
|
// "Monitor Shows" Switch
|
||||||
|
HifiControlsUit.Switch {
|
||||||
|
id: monitorShowsSwitch;
|
||||||
|
height: 30;
|
||||||
|
anchors.left: parent.left;
|
||||||
|
anchors.right: parent.right;
|
||||||
|
anchors.top: monitorShowsSwitchLabel.bottom;
|
||||||
|
anchors.topMargin: 10;
|
||||||
|
labelTextOff: "HMD Preview";
|
||||||
|
labelTextOn: "Camera View";
|
||||||
|
labelGlyphOnText: hifi.glyphs.alert;
|
||||||
|
onCheckedChanged: {
|
||||||
|
sendToScript({method: 'setMonitorShowsCameraView', params: checked});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// "Switch View From Controller" Checkbox
|
||||||
|
HifiControlsUit.CheckBox {
|
||||||
|
id: switchViewFromControllerCheckBox;
|
||||||
|
colorScheme: hifi.colorSchemes.dark;
|
||||||
|
anchors.left: parent.left;
|
||||||
|
anchors.top: monitorShowsSwitch.bottom;
|
||||||
|
anchors.topMargin: 25;
|
||||||
|
text: "";
|
||||||
|
boxSize: 24;
|
||||||
|
onClicked: {
|
||||||
|
sendToScript({method: 'changeSwitchViewFromControllerPreference', params: checked});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//
|
||||||
|
// SPECTATOR CONTROLS END
|
||||||
|
//
|
||||||
|
|
||||||
|
//
|
||||||
|
// FUNCTION DEFINITIONS START
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Function Name: fromScript()
|
||||||
|
//
|
||||||
|
// Relevant Variables:
|
||||||
|
// None
|
||||||
|
//
|
||||||
|
// Arguments:
|
||||||
|
// message: The message sent from the SpectatorCamera JavaScript.
|
||||||
|
// Messages are in format "{method, params}", like json-rpc.
|
||||||
|
//
|
||||||
|
// Description:
|
||||||
|
// Called when a message is received from spectatorCamera.js.
|
||||||
|
//
|
||||||
|
function fromScript(message) {
|
||||||
|
switch (message.method) {
|
||||||
|
case 'updateSpectatorCameraCheckbox':
|
||||||
|
cameraToggleCheckBox.checked = message.params;
|
||||||
|
break;
|
||||||
|
case 'updateMonitorShowsSwitch':
|
||||||
|
monitorShowsSwitch.checked = message.params;
|
||||||
|
break;
|
||||||
|
case 'updateControllerMappingCheckbox':
|
||||||
|
switchViewFromControllerCheckBox.checked = message.setting;
|
||||||
|
switchViewFromControllerCheckBox.enabled = true;
|
||||||
|
if (message.controller === "OculusTouch") {
|
||||||
|
switchViewFromControllerCheckBox.text = "Clicking Touch's Left Thumbstick Switches Monitor View";
|
||||||
|
} else if (message.controller === "Vive") {
|
||||||
|
switchViewFromControllerCheckBox.text = "Clicking Left Thumb Pad Switches Monitor View";
|
||||||
|
} else {
|
||||||
|
switchViewFromControllerCheckBox.text = "Pressing Ctrl+0 Switches Monitor View";
|
||||||
|
switchViewFromControllerCheckBox.checked = true;
|
||||||
|
switchViewFromControllerCheckBox.enabled = false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'showPreviewTextureNotInstructions':
|
||||||
|
console.log('showPreviewTextureNotInstructions recvd', JSON.stringify(message));
|
||||||
|
spectatorCameraPreview.url = message.url;
|
||||||
|
spectatorCameraPreview.visible = message.setting;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.log('Unrecognized message from spectatorCamera.js:', JSON.stringify(message));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
signal sendToScript(var message);
|
||||||
|
|
||||||
|
//
|
||||||
|
// FUNCTION DEFINITIONS END
|
||||||
|
//
|
||||||
|
}
|
|
@ -50,7 +50,7 @@ Item {
|
||||||
id: colors
|
id: colors
|
||||||
|
|
||||||
// Base colors
|
// Base colors
|
||||||
readonly property color baseGray: "#404040"
|
readonly property color baseGray: "#393939"
|
||||||
readonly property color darkGray: "#121212"
|
readonly property color darkGray: "#121212"
|
||||||
readonly property color baseGrayShadow: "#252525"
|
readonly property color baseGrayShadow: "#252525"
|
||||||
readonly property color baseGrayHighlight: "#575757"
|
readonly property color baseGrayHighlight: "#575757"
|
||||||
|
|
|
@ -167,6 +167,7 @@
|
||||||
#if defined(Q_OS_MAC) || defined(Q_OS_WIN)
|
#if defined(Q_OS_MAC) || defined(Q_OS_WIN)
|
||||||
#include "SpeechRecognizer.h"
|
#include "SpeechRecognizer.h"
|
||||||
#endif
|
#endif
|
||||||
|
#include "ui/ResourceImageItem.h"
|
||||||
#include "ui/AddressBarDialog.h"
|
#include "ui/AddressBarDialog.h"
|
||||||
#include "ui/AvatarInputs.h"
|
#include "ui/AvatarInputs.h"
|
||||||
#include "ui/DialogsManager.h"
|
#include "ui/DialogsManager.h"
|
||||||
|
@ -1971,7 +1972,7 @@ void Application::initializeGL() {
|
||||||
static const QString RENDER_FORWARD = "HIFI_RENDER_FORWARD";
|
static const QString RENDER_FORWARD = "HIFI_RENDER_FORWARD";
|
||||||
bool isDeferred = !QProcessEnvironment::systemEnvironment().contains(RENDER_FORWARD);
|
bool isDeferred = !QProcessEnvironment::systemEnvironment().contains(RENDER_FORWARD);
|
||||||
_renderEngine->addJob<UpdateSceneTask>("UpdateScene");
|
_renderEngine->addJob<UpdateSceneTask>("UpdateScene");
|
||||||
_renderEngine->addJob<SecondaryCameraRenderTask>("SecondaryCameraFrame", cullFunctor);
|
_renderEngine->addJob<SecondaryCameraRenderTask>("SecondaryCameraJob", cullFunctor);
|
||||||
_renderEngine->addJob<RenderViewTask>("RenderMainView", cullFunctor, isDeferred);
|
_renderEngine->addJob<RenderViewTask>("RenderMainView", cullFunctor, isDeferred);
|
||||||
_renderEngine->load();
|
_renderEngine->load();
|
||||||
_renderEngine->registerScene(_main3DScene);
|
_renderEngine->registerScene(_main3DScene);
|
||||||
|
@ -2019,6 +2020,7 @@ void Application::initializeUi() {
|
||||||
LoginDialog::registerType();
|
LoginDialog::registerType();
|
||||||
Tooltip::registerType();
|
Tooltip::registerType();
|
||||||
UpdateDialog::registerType();
|
UpdateDialog::registerType();
|
||||||
|
qmlRegisterType<ResourceImageItem>("Hifi", 1, 0, "ResourceImageItem");
|
||||||
qmlRegisterType<Preference>("Hifi", 1, 0, "Preference");
|
qmlRegisterType<Preference>("Hifi", 1, 0, "Preference");
|
||||||
|
|
||||||
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
||||||
|
|
|
@ -9,9 +9,11 @@
|
||||||
// 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
|
||||||
//
|
//
|
||||||
|
|
||||||
|
#include "Application.h"
|
||||||
#include "SecondaryCamera.h"
|
#include "SecondaryCamera.h"
|
||||||
#include <TextureCache.h>
|
#include <TextureCache.h>
|
||||||
#include <gpu/Context.h>
|
#include <gpu/Context.h>
|
||||||
|
#include <EntityScriptingInterface.h>
|
||||||
|
|
||||||
using RenderArgsPointer = std::shared_ptr<RenderArgs>;
|
using RenderArgsPointer = std::shared_ptr<RenderArgs>;
|
||||||
|
|
||||||
|
@ -27,40 +29,33 @@ void MainRenderTask::build(JobModel& task, const render::Varying& inputs, render
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SecondaryCameraRenderTaskConfig::resetSize(int width, int height) { // FIXME: Add an arg here for "destinationFramebuffer"
|
class SecondaryCameraJob { // Changes renderContext for our framebuffer and view.
|
||||||
bool wasEnabled = isEnabled();
|
QUuid _attachedEntityId{};
|
||||||
setEnabled(false);
|
|
||||||
auto textureCache = DependencyManager::get<TextureCache>();
|
|
||||||
textureCache->resetSpectatorCameraFramebuffer(width, height); // FIXME: Call the correct reset function based on the "destinationFramebuffer" arg
|
|
||||||
setEnabled(wasEnabled);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SecondaryCameraRenderTaskConfig::resetSizeSpectatorCamera(int width, int height) { // Carefully adjust the framebuffer / texture.
|
|
||||||
resetSize(width, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
class BeginSecondaryCameraFrame { // Changes renderContext for our framebuffer and and view.
|
|
||||||
glm::vec3 _position{};
|
glm::vec3 _position{};
|
||||||
glm::quat _orientation{};
|
glm::quat _orientation{};
|
||||||
float _vFoV{};
|
float _vFoV{};
|
||||||
float _nearClipPlaneDistance{};
|
float _nearClipPlaneDistance{};
|
||||||
float _farClipPlaneDistance{};
|
float _farClipPlaneDistance{};
|
||||||
|
EntityPropertyFlags _attachedEntityPropertyFlags;
|
||||||
|
QSharedPointer<EntityScriptingInterface> _entityScriptingInterface;
|
||||||
public:
|
public:
|
||||||
using Config = BeginSecondaryCameraFrameConfig;
|
using Config = SecondaryCameraJobConfig;
|
||||||
using JobModel = render::Job::ModelO<BeginSecondaryCameraFrame, RenderArgsPointer, Config>;
|
using JobModel = render::Job::ModelO<SecondaryCameraJob, RenderArgsPointer, Config>;
|
||||||
BeginSecondaryCameraFrame() {
|
SecondaryCameraJob() {
|
||||||
_cachedArgsPointer = std::make_shared<RenderArgs>(_cachedArgs);
|
_cachedArgsPointer = std::make_shared<RenderArgs>(_cachedArgs);
|
||||||
|
_entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
|
||||||
|
_attachedEntityPropertyFlags += PROP_POSITION;
|
||||||
|
_attachedEntityPropertyFlags += PROP_ROTATION;
|
||||||
}
|
}
|
||||||
|
|
||||||
void configure(const Config& config) {
|
void configure(const Config& config) {
|
||||||
if (config.enabled || config.alwaysEnabled) {
|
_attachedEntityId = config.attachedEntityId;
|
||||||
_position = config.position;
|
_position = config.position;
|
||||||
_orientation = config.orientation;
|
_orientation = config.orientation;
|
||||||
_vFoV = config.vFoV;
|
_vFoV = config.vFoV;
|
||||||
_nearClipPlaneDistance = config.nearClipPlaneDistance;
|
_nearClipPlaneDistance = config.nearClipPlaneDistance;
|
||||||
_farClipPlaneDistance = config.farClipPlaneDistance;
|
_farClipPlaneDistance = config.farClipPlaneDistance;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
void run(const render::RenderContextPointer& renderContext, RenderArgsPointer& cachedArgs) {
|
void run(const render::RenderContextPointer& renderContext, RenderArgsPointer& cachedArgs) {
|
||||||
auto args = renderContext->args;
|
auto args = renderContext->args;
|
||||||
|
@ -83,8 +78,14 @@ public:
|
||||||
});
|
});
|
||||||
|
|
||||||
auto srcViewFrustum = args->getViewFrustum();
|
auto srcViewFrustum = args->getViewFrustum();
|
||||||
|
if (!_attachedEntityId.isNull()) {
|
||||||
|
EntityItemProperties entityProperties = _entityScriptingInterface->getEntityProperties(_attachedEntityId, _attachedEntityPropertyFlags);
|
||||||
|
srcViewFrustum.setPosition(entityProperties.getPosition());
|
||||||
|
srcViewFrustum.setOrientation(entityProperties.getRotation());
|
||||||
|
} else {
|
||||||
srcViewFrustum.setPosition(_position);
|
srcViewFrustum.setPosition(_position);
|
||||||
srcViewFrustum.setOrientation(_orientation);
|
srcViewFrustum.setOrientation(_orientation);
|
||||||
|
}
|
||||||
srcViewFrustum.setProjection(glm::perspective(glm::radians(_vFoV), ((float)args->_viewport.z / (float)args->_viewport.w), _nearClipPlaneDistance, _farClipPlaneDistance));
|
srcViewFrustum.setProjection(glm::perspective(glm::radians(_vFoV), ((float)args->_viewport.z / (float)args->_viewport.w), _nearClipPlaneDistance, _farClipPlaneDistance));
|
||||||
// Without calculating the bound planes, the secondary camera will use the same culling frustum as the main camera,
|
// Without calculating the bound planes, the secondary camera will use the same culling frustum as the main camera,
|
||||||
// which is not what we want here.
|
// which is not what we want here.
|
||||||
|
@ -99,6 +100,41 @@ protected:
|
||||||
RenderArgsPointer _cachedArgsPointer;
|
RenderArgsPointer _cachedArgsPointer;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
void SecondaryCameraJobConfig::setPosition(glm::vec3 pos) {
|
||||||
|
if (attachedEntityId.isNull()) {
|
||||||
|
position = pos;
|
||||||
|
emit dirty();
|
||||||
|
} else {
|
||||||
|
qDebug() << "ERROR: Cannot set position of SecondaryCamera while attachedEntityId is set.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SecondaryCameraJobConfig::setOrientation(glm::quat orient) {
|
||||||
|
if (attachedEntityId.isNull()) {
|
||||||
|
orientation = orient;
|
||||||
|
emit dirty();
|
||||||
|
} else {
|
||||||
|
qDebug() << "ERROR: Cannot set orientation of SecondaryCamera while attachedEntityId is set.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SecondaryCameraJobConfig::enableSecondaryCameraRenderConfigs(bool enabled) {
|
||||||
|
qApp->getRenderEngine()->getConfiguration()->getConfig<SecondaryCameraRenderTask>()->setEnabled(enabled);
|
||||||
|
setEnabled(enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SecondaryCameraJobConfig::resetSizeSpectatorCamera(int width, int height) { // Carefully adjust the framebuffer / texture.
|
||||||
|
qApp->getRenderEngine()->getConfiguration()->getConfig<SecondaryCameraRenderTask>()->resetSize(width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SecondaryCameraRenderTaskConfig::resetSize(int width, int height) { // FIXME: Add an arg here for "destinationFramebuffer"
|
||||||
|
bool wasEnabled = isEnabled();
|
||||||
|
setEnabled(false);
|
||||||
|
auto textureCache = DependencyManager::get<TextureCache>();
|
||||||
|
textureCache->resetSpectatorCameraFramebuffer(width, height); // FIXME: Call the correct reset function based on the "destinationFramebuffer" arg
|
||||||
|
setEnabled(wasEnabled);
|
||||||
|
}
|
||||||
|
|
||||||
class EndSecondaryCameraFrame { // Restores renderContext.
|
class EndSecondaryCameraFrame { // Restores renderContext.
|
||||||
public:
|
public:
|
||||||
using JobModel = render::Job::ModelI<EndSecondaryCameraFrame, RenderArgsPointer>;
|
using JobModel = render::Job::ModelI<EndSecondaryCameraFrame, RenderArgsPointer>;
|
||||||
|
@ -119,7 +155,7 @@ public:
|
||||||
};
|
};
|
||||||
|
|
||||||
void SecondaryCameraRenderTask::build(JobModel& task, const render::Varying& inputs, render::Varying& outputs, render::CullFunctor cullFunctor) {
|
void SecondaryCameraRenderTask::build(JobModel& task, const render::Varying& inputs, render::Varying& outputs, render::CullFunctor cullFunctor) {
|
||||||
const auto cachedArg = task.addJob<BeginSecondaryCameraFrame>("BeginSecondaryCamera");
|
const auto cachedArg = task.addJob<SecondaryCameraJob>("SecondaryCamera");
|
||||||
const auto items = task.addJob<RenderFetchCullSortTask>("FetchCullSort", cullFunctor);
|
const auto items = task.addJob<RenderFetchCullSortTask>("FetchCullSort", cullFunctor);
|
||||||
assert(items.canCast<RenderFetchCullSortTask::Output>());
|
assert(items.canCast<RenderFetchCullSortTask::Output>());
|
||||||
task.addJob<RenderDeferredTask>("RenderDeferredTask", items);
|
task.addJob<RenderDeferredTask>("RenderDeferredTask", items);
|
||||||
|
|
|
@ -28,34 +28,40 @@ public:
|
||||||
void build(JobModel& task, const render::Varying& inputs, render::Varying& outputs, render::CullFunctor cullFunctor, bool isDeferred = true);
|
void build(JobModel& task, const render::Varying& inputs, render::Varying& outputs, render::CullFunctor cullFunctor, bool isDeferred = true);
|
||||||
};
|
};
|
||||||
|
|
||||||
class BeginSecondaryCameraFrameConfig : public render::Task::Config { // Exposes secondary camera parameters to JavaScript.
|
class SecondaryCameraJobConfig : public render::Task::Config { // Exposes secondary camera parameters to JavaScript.
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
Q_PROPERTY(glm::vec3 position MEMBER position NOTIFY dirty) // of viewpoint to render from
|
Q_PROPERTY(QUuid attachedEntityId MEMBER attachedEntityId NOTIFY dirty) // entity whose properties define camera position and orientation
|
||||||
Q_PROPERTY(glm::quat orientation MEMBER orientation NOTIFY dirty) // of viewpoint to render from
|
Q_PROPERTY(glm::vec3 position READ getPosition WRITE setPosition) // of viewpoint to render from
|
||||||
|
Q_PROPERTY(glm::quat orientation READ getOrientation WRITE setOrientation) // of viewpoint to render from
|
||||||
Q_PROPERTY(float vFoV MEMBER vFoV NOTIFY dirty) // Secondary camera's vertical field of view. In degrees.
|
Q_PROPERTY(float vFoV MEMBER vFoV NOTIFY dirty) // Secondary camera's vertical field of view. In degrees.
|
||||||
Q_PROPERTY(float nearClipPlaneDistance MEMBER nearClipPlaneDistance NOTIFY dirty) // Secondary camera's near clip plane distance. In meters.
|
Q_PROPERTY(float nearClipPlaneDistance MEMBER nearClipPlaneDistance NOTIFY dirty) // Secondary camera's near clip plane distance. In meters.
|
||||||
Q_PROPERTY(float farClipPlaneDistance MEMBER farClipPlaneDistance NOTIFY dirty) // Secondary camera's far clip plane distance. In meters.
|
Q_PROPERTY(float farClipPlaneDistance MEMBER farClipPlaneDistance NOTIFY dirty) // Secondary camera's far clip plane distance. In meters.
|
||||||
public:
|
public:
|
||||||
|
QUuid attachedEntityId{};
|
||||||
glm::vec3 position{};
|
glm::vec3 position{};
|
||||||
glm::quat orientation{};
|
glm::quat orientation{};
|
||||||
float vFoV{ 45.0f };
|
float vFoV{ DEFAULT_FIELD_OF_VIEW_DEGREES };
|
||||||
float nearClipPlaneDistance{ 0.1f };
|
float nearClipPlaneDistance{ DEFAULT_NEAR_CLIP };
|
||||||
float farClipPlaneDistance{ 100.0f };
|
float farClipPlaneDistance{ DEFAULT_FAR_CLIP };
|
||||||
BeginSecondaryCameraFrameConfig() : render::Task::Config(false) {}
|
SecondaryCameraJobConfig() : render::Task::Config(false) {}
|
||||||
signals:
|
signals:
|
||||||
void dirty();
|
void dirty();
|
||||||
|
public slots:
|
||||||
|
glm::vec3 getPosition() { return position; }
|
||||||
|
void setPosition(glm::vec3 pos);
|
||||||
|
glm::quat getOrientation() { return orientation; }
|
||||||
|
void setOrientation(glm::quat orient);
|
||||||
|
void enableSecondaryCameraRenderConfigs(bool enabled);
|
||||||
|
void resetSizeSpectatorCamera(int width, int height);
|
||||||
};
|
};
|
||||||
|
|
||||||
class SecondaryCameraRenderTaskConfig : public render::Task::Config {
|
class SecondaryCameraRenderTaskConfig : public render::Task::Config {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
SecondaryCameraRenderTaskConfig() : render::Task::Config(false) {}
|
SecondaryCameraRenderTaskConfig() : render::Task::Config(false) {}
|
||||||
private:
|
|
||||||
void resetSize(int width, int height);
|
void resetSize(int width, int height);
|
||||||
signals:
|
signals:
|
||||||
void dirty();
|
void dirty();
|
||||||
public slots:
|
|
||||||
void resetSizeSpectatorCamera(int width, int height);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class SecondaryCameraRenderTask {
|
class SecondaryCameraRenderTask {
|
||||||
|
|
114
interface/src/ui/ResourceImageItem.cpp
Normal file
114
interface/src/ui/ResourceImageItem.cpp
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
//
|
||||||
|
// ResourceImageItem.cpp
|
||||||
|
//
|
||||||
|
// Created by David Kelly and Howard Stearns on 2017/06/08
|
||||||
|
// 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 "Application.h"
|
||||||
|
#include "ResourceImageItem.h"
|
||||||
|
|
||||||
|
#include <QOpenGLFramebufferObjectFormat>
|
||||||
|
#include <QOpenGLFunctions>
|
||||||
|
#include <QOpenGLExtraFunctions>
|
||||||
|
#include <QOpenGLContext>
|
||||||
|
|
||||||
|
ResourceImageItem::ResourceImageItem() : QQuickFramebufferObject() {
|
||||||
|
auto textureCache = DependencyManager::get<TextureCache>();
|
||||||
|
connect(textureCache.data(), SIGNAL(spectatorCameraFramebufferReset()), this, SLOT(update()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResourceImageItem::setUrl(const QString& url) {
|
||||||
|
if (url != m_url) {
|
||||||
|
m_url = url;
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResourceImageItem::setReady(bool ready) {
|
||||||
|
if (ready != m_ready) {
|
||||||
|
m_ready = ready;
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResourceImageItemRenderer::onUpdateTimer() {
|
||||||
|
if (_ready) {
|
||||||
|
if (_networkTexture && _networkTexture->isLoaded()) {
|
||||||
|
if(_fboMutex.tryLock()) {
|
||||||
|
invalidateFramebufferObject();
|
||||||
|
qApp->getActiveDisplayPlugin()->copyTextureToQuickFramebuffer(_networkTexture, _copyFbo, &_fenceSync);
|
||||||
|
_fboMutex.unlock();
|
||||||
|
} else {
|
||||||
|
qDebug() << "couldn't get a lock, using last frame";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_networkTexture = DependencyManager::get<TextureCache>()->getTexture(_url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
ResourceImageItemRenderer::ResourceImageItemRenderer() : QQuickFramebufferObject::Renderer() {
|
||||||
|
connect(&_updateTimer, SIGNAL(timeout()), this, SLOT(onUpdateTimer()));
|
||||||
|
auto textureCache = DependencyManager::get<TextureCache>();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResourceImageItemRenderer::synchronize(QQuickFramebufferObject* item) {
|
||||||
|
ResourceImageItem* resourceImageItem = static_cast<ResourceImageItem*>(item);
|
||||||
|
|
||||||
|
resourceImageItem->setFlag(QQuickItem::ItemHasContents);
|
||||||
|
|
||||||
|
_url = resourceImageItem->getUrl();
|
||||||
|
_ready = resourceImageItem->getReady();
|
||||||
|
_visible = resourceImageItem->isVisible();
|
||||||
|
_window = resourceImageItem->window();
|
||||||
|
|
||||||
|
_networkTexture = DependencyManager::get<TextureCache>()->getTexture(_url);
|
||||||
|
static const int UPDATE_TIMER_DELAY_IN_MS = 100; // 100 ms = 10 hz for now
|
||||||
|
if (_ready && _visible && !_updateTimer.isActive()) {
|
||||||
|
_updateTimer.start(UPDATE_TIMER_DELAY_IN_MS);
|
||||||
|
} else if (!(_ready && _visible) && _updateTimer.isActive()) {
|
||||||
|
_updateTimer.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QOpenGLFramebufferObject* ResourceImageItemRenderer::createFramebufferObject(const QSize& size) {
|
||||||
|
if (_copyFbo) {
|
||||||
|
delete _copyFbo;
|
||||||
|
}
|
||||||
|
QOpenGLFramebufferObjectFormat format;
|
||||||
|
format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
|
||||||
|
_copyFbo = new QOpenGLFramebufferObject(size, format);
|
||||||
|
_copyFbo->bind();
|
||||||
|
return new QOpenGLFramebufferObject(size, format);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResourceImageItemRenderer::render() {
|
||||||
|
auto f = QOpenGLContext::currentContext()->extraFunctions();
|
||||||
|
|
||||||
|
if (_fenceSync) {
|
||||||
|
f->glWaitSync(_fenceSync, 0, GL_TIMEOUT_IGNORED);
|
||||||
|
f->glDeleteSync(_fenceSync);
|
||||||
|
_fenceSync = 0;
|
||||||
|
}
|
||||||
|
if (_ready) {
|
||||||
|
_fboMutex.lock();
|
||||||
|
_copyFbo->bind();
|
||||||
|
QOpenGLFramebufferObject::blitFramebuffer(framebufferObject(), _copyFbo, GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT, GL_NEAREST);
|
||||||
|
|
||||||
|
// this clears the copyFbo texture
|
||||||
|
// so next frame starts fresh - helps
|
||||||
|
// when aspect ratio changes
|
||||||
|
_copyFbo->takeTexture();
|
||||||
|
_copyFbo->bind();
|
||||||
|
_copyFbo->release();
|
||||||
|
|
||||||
|
_fboMutex.unlock();
|
||||||
|
}
|
||||||
|
glFlush();
|
||||||
|
_window->resetOpenGLState();
|
||||||
|
}
|
63
interface/src/ui/ResourceImageItem.h
Normal file
63
interface/src/ui/ResourceImageItem.h
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
//
|
||||||
|
// ResourceImageItem.h
|
||||||
|
//
|
||||||
|
// Created by David Kelly and Howard Stearns on 2017/06/08
|
||||||
|
// 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
|
||||||
|
//
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#ifndef hifi_ResourceImageItem_h
|
||||||
|
#define hifi_ResourceImageItem_h
|
||||||
|
|
||||||
|
#include "Application.h"
|
||||||
|
|
||||||
|
#include <QQuickFramebufferObject>
|
||||||
|
#include <QQuickWindow>
|
||||||
|
#include <QTimer>
|
||||||
|
|
||||||
|
#include <TextureCache.h>
|
||||||
|
|
||||||
|
class ResourceImageItemRenderer : public QObject, public QQuickFramebufferObject::Renderer {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
ResourceImageItemRenderer();
|
||||||
|
QOpenGLFramebufferObject* createFramebufferObject(const QSize& size) override;
|
||||||
|
void synchronize(QQuickFramebufferObject* item) override;
|
||||||
|
void render() override;
|
||||||
|
private:
|
||||||
|
bool _ready;
|
||||||
|
QString _url;
|
||||||
|
bool _visible;
|
||||||
|
|
||||||
|
NetworkTexturePointer _networkTexture;
|
||||||
|
QQuickWindow* _window;
|
||||||
|
QMutex _fboMutex;
|
||||||
|
QOpenGLFramebufferObject* _copyFbo { nullptr };
|
||||||
|
GLsync _fenceSync { 0 };
|
||||||
|
QTimer _updateTimer;
|
||||||
|
public slots:
|
||||||
|
void onUpdateTimer();
|
||||||
|
};
|
||||||
|
|
||||||
|
class ResourceImageItem : public QQuickFramebufferObject {
|
||||||
|
Q_OBJECT
|
||||||
|
Q_PROPERTY(QString url READ getUrl WRITE setUrl)
|
||||||
|
Q_PROPERTY(bool ready READ getReady WRITE setReady)
|
||||||
|
public:
|
||||||
|
ResourceImageItem();
|
||||||
|
QString getUrl() const { return m_url; }
|
||||||
|
void setUrl(const QString& url);
|
||||||
|
bool getReady() const { return m_ready; }
|
||||||
|
void setReady(bool ready);
|
||||||
|
QQuickFramebufferObject::Renderer* createRenderer() const override { return new ResourceImageItemRenderer; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
QString m_url;
|
||||||
|
bool m_ready { false };
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // hifi_ResourceImageItem_h
|
|
@ -37,9 +37,11 @@ QVariant Billboard3DOverlay::getProperty(const QString &property) {
|
||||||
return Planar3DOverlay::getProperty(property);
|
return Planar3DOverlay::getProperty(property);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Billboard3DOverlay::applyTransformTo(Transform& transform, bool force) {
|
bool Billboard3DOverlay::applyTransformTo(Transform& transform, bool force) {
|
||||||
|
bool transformChanged = false;
|
||||||
if (force || usecTimestampNow() > _transformExpiry) {
|
if (force || usecTimestampNow() > _transformExpiry) {
|
||||||
PanelAttachable::applyTransformTo(transform, true);
|
transformChanged = PanelAttachable::applyTransformTo(transform, true);
|
||||||
pointTransformAtCamera(transform, getOffsetRotation());
|
transformChanged |= pointTransformAtCamera(transform, getOffsetRotation());
|
||||||
}
|
}
|
||||||
|
return transformChanged;
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@ public:
|
||||||
QVariant getProperty(const QString& property) override;
|
QVariant getProperty(const QString& property) override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void applyTransformTo(Transform& transform, bool force = false) override;
|
virtual bool applyTransformTo(Transform& transform, bool force = false) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_Billboard3DOverlay_h
|
#endif // hifi_Billboard3DOverlay_h
|
||||||
|
|
|
@ -28,7 +28,7 @@ QVariant Billboardable::getProperty(const QString &property) {
|
||||||
return QVariant();
|
return QVariant();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Billboardable::pointTransformAtCamera(Transform& transform, glm::quat offsetRotation) {
|
bool Billboardable::pointTransformAtCamera(Transform& transform, glm::quat offsetRotation) {
|
||||||
if (isFacingAvatar()) {
|
if (isFacingAvatar()) {
|
||||||
glm::vec3 billboardPos = transform.getTranslation();
|
glm::vec3 billboardPos = transform.getTranslation();
|
||||||
glm::vec3 cameraPos = qApp->getCamera().getPosition();
|
glm::vec3 cameraPos = qApp->getCamera().getPosition();
|
||||||
|
@ -38,5 +38,7 @@ void Billboardable::pointTransformAtCamera(Transform& transform, glm::quat offse
|
||||||
glm::quat rotation(glm::vec3(elevation, azimuth, 0));
|
glm::quat rotation(glm::vec3(elevation, azimuth, 0));
|
||||||
transform.setRotation(rotation);
|
transform.setRotation(rotation);
|
||||||
transform.postRotate(offsetRotation);
|
transform.postRotate(offsetRotation);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@ protected:
|
||||||
void setProperties(const QVariantMap& properties);
|
void setProperties(const QVariantMap& properties);
|
||||||
QVariant getProperty(const QString& property);
|
QVariant getProperty(const QString& property);
|
||||||
|
|
||||||
void pointTransformAtCamera(Transform& transform, glm::quat offsetRotation = {1, 0, 0, 0});
|
bool pointTransformAtCamera(Transform& transform, glm::quat offsetRotation = {1, 0, 0, 0});
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool _isFacingAvatar = false;
|
bool _isFacingAvatar = false;
|
||||||
|
|
|
@ -101,8 +101,12 @@ void Image3DOverlay::render(RenderArgs* args) {
|
||||||
float alpha = getAlpha();
|
float alpha = getAlpha();
|
||||||
|
|
||||||
Transform transform = getTransform();
|
Transform transform = getTransform();
|
||||||
applyTransformTo(transform, true);
|
bool transformChanged = applyTransformTo(transform, true);
|
||||||
|
// If the transform is not modified, setting the transform to
|
||||||
|
// itself will cause drift over time due to floating point errors.
|
||||||
|
if (transformChanged) {
|
||||||
setTransform(transform);
|
setTransform(transform);
|
||||||
|
}
|
||||||
transform.postScale(glm::vec3(getDimensions(), 1.0f));
|
transform.postScale(glm::vec3(getDimensions(), 1.0f));
|
||||||
|
|
||||||
batch->setModelTransform(transform);
|
batch->setModelTransform(transform);
|
||||||
|
|
|
@ -61,7 +61,7 @@ void PanelAttachable::setProperties(const QVariantMap& properties) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void PanelAttachable::applyTransformTo(Transform& transform, bool force) {
|
bool PanelAttachable::applyTransformTo(Transform& transform, bool force) {
|
||||||
if (force || usecTimestampNow() > _transformExpiry) {
|
if (force || usecTimestampNow() > _transformExpiry) {
|
||||||
const quint64 TRANSFORM_UPDATE_PERIOD = 100000; // frequency is 10 Hz
|
const quint64 TRANSFORM_UPDATE_PERIOD = 100000; // frequency is 10 Hz
|
||||||
_transformExpiry = usecTimestampNow() + TRANSFORM_UPDATE_PERIOD;
|
_transformExpiry = usecTimestampNow() + TRANSFORM_UPDATE_PERIOD;
|
||||||
|
@ -71,7 +71,9 @@ void PanelAttachable::applyTransformTo(Transform& transform, bool force) {
|
||||||
transform.postTranslate(getOffsetPosition());
|
transform.postTranslate(getOffsetPosition());
|
||||||
transform.postRotate(getOffsetRotation());
|
transform.postRotate(getOffsetRotation());
|
||||||
transform.postScale(getOffsetScale());
|
transform.postScale(getOffsetScale());
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,7 +67,7 @@ protected:
|
||||||
|
|
||||||
/// set position, rotation and scale on transform based on offsets, and parent panel offsets
|
/// set position, rotation and scale on transform based on offsets, and parent panel offsets
|
||||||
/// if force is false, only apply transform if it hasn't been applied in the last .1 seconds
|
/// if force is false, only apply transform if it hasn't been applied in the last .1 seconds
|
||||||
virtual void applyTransformTo(Transform& transform, bool force = false);
|
virtual bool applyTransformTo(Transform& transform, bool force = false);
|
||||||
quint64 _transformExpiry = 0;
|
quint64 _transformExpiry = 0;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
#include <QtOpenGL/QGLWidget>
|
#include <QtOpenGL/QGLWidget>
|
||||||
#include <QtGui/QImage>
|
#include <QtGui/QImage>
|
||||||
|
#include <QOpenGLFramebufferObject>
|
||||||
#if defined(Q_OS_MAC)
|
#if defined(Q_OS_MAC)
|
||||||
#include <OpenGL/CGLCurrent.h>
|
#include <OpenGL/CGLCurrent.h>
|
||||||
#endif
|
#endif
|
||||||
|
@ -41,7 +42,7 @@
|
||||||
#include <ui-plugins/PluginContainer.h>
|
#include <ui-plugins/PluginContainer.h>
|
||||||
#include <ui/Menu.h>
|
#include <ui/Menu.h>
|
||||||
#include <CursorManager.h>
|
#include <CursorManager.h>
|
||||||
|
#include <TextureCache.h>
|
||||||
#include "CompositorHelper.h"
|
#include "CompositorHelper.h"
|
||||||
#include "Logging.h"
|
#include "Logging.h"
|
||||||
|
|
||||||
|
@ -496,16 +497,48 @@ void OpenGLDisplayPlugin::submitFrame(const gpu::FramePointer& newFrame) {
|
||||||
_newFrameQueue.push(newFrame);
|
_newFrameQueue.push(newFrame);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void OpenGLDisplayPlugin::renderFromTexture(gpu::Batch& batch, const gpu::TexturePointer texture, glm::ivec4 viewport, const glm::ivec4 scissor) {
|
void OpenGLDisplayPlugin::renderFromTexture(gpu::Batch& batch, const gpu::TexturePointer texture, glm::ivec4 viewport, const glm::ivec4 scissor) {
|
||||||
|
renderFromTexture(batch, texture, viewport, scissor, gpu::FramebufferPointer());
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenGLDisplayPlugin::renderFromTexture(gpu::Batch& batch, const gpu::TexturePointer texture, glm::ivec4 viewport, const glm::ivec4 scissor, gpu::FramebufferPointer copyFbo /*=gpu::FramebufferPointer()*/) {
|
||||||
|
auto fbo = gpu::FramebufferPointer();
|
||||||
batch.enableStereo(false);
|
batch.enableStereo(false);
|
||||||
batch.resetViewTransform();
|
batch.resetViewTransform();
|
||||||
batch.setFramebuffer(gpu::FramebufferPointer());
|
batch.setFramebuffer(fbo);
|
||||||
batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLOR0, vec4(0));
|
batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLOR0, vec4(0));
|
||||||
batch.setStateScissorRect(scissor);
|
batch.setStateScissorRect(scissor);
|
||||||
batch.setViewportTransform(viewport);
|
batch.setViewportTransform(viewport);
|
||||||
batch.setResourceTexture(0, texture);
|
batch.setResourceTexture(0, texture);
|
||||||
batch.setPipeline(_presentPipeline);
|
batch.setPipeline(_presentPipeline);
|
||||||
batch.draw(gpu::TRIANGLE_STRIP, 4);
|
batch.draw(gpu::TRIANGLE_STRIP, 4);
|
||||||
|
if (copyFbo) {
|
||||||
|
gpu::Vec4i copyFboRect(0, 0, copyFbo->getWidth(), copyFbo->getHeight());
|
||||||
|
gpu::Vec4i sourceRect(scissor.x, scissor.y, scissor.x + scissor.z, scissor.y + scissor.w);
|
||||||
|
float aspectRatio = (float)scissor.w / (float) scissor.z; // height/width
|
||||||
|
// scale width first
|
||||||
|
int xOffset = 0;
|
||||||
|
int yOffset = 0;
|
||||||
|
int newWidth = copyFbo->getWidth();
|
||||||
|
int newHeight = std::round(aspectRatio * (float) copyFbo->getWidth());
|
||||||
|
if (newHeight > copyFbo->getHeight()) {
|
||||||
|
// ok, so now fill height instead
|
||||||
|
newHeight = copyFbo->getHeight();
|
||||||
|
newWidth = std::round((float)copyFbo->getHeight() / aspectRatio);
|
||||||
|
xOffset = (copyFbo->getWidth() - newWidth) / 2;
|
||||||
|
} else {
|
||||||
|
yOffset = (copyFbo->getHeight() - newHeight) / 2;
|
||||||
|
}
|
||||||
|
gpu::Vec4i copyRect(xOffset, yOffset, xOffset + newWidth, yOffset + newHeight);
|
||||||
|
batch.setFramebuffer(copyFbo);
|
||||||
|
|
||||||
|
batch.resetViewTransform();
|
||||||
|
batch.setViewportTransform(copyFboRect);
|
||||||
|
batch.setStateScissorRect(copyFboRect);
|
||||||
|
batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLOR0, {0.0f, 0.0f, 0.0f, 1.0f});
|
||||||
|
batch.blit(fbo, sourceRect, copyFbo, copyRect);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void OpenGLDisplayPlugin::updateFrameData() {
|
void OpenGLDisplayPlugin::updateFrameData() {
|
||||||
|
@ -821,3 +854,53 @@ void OpenGLDisplayPlugin::updateCompositeFramebuffer() {
|
||||||
_compositeFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("OpenGLDisplayPlugin::composite", gpu::Element::COLOR_RGBA_32, renderSize.x, renderSize.y));
|
_compositeFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("OpenGLDisplayPlugin::composite", gpu::Element::COLOR_RGBA_32, renderSize.x, renderSize.y));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void OpenGLDisplayPlugin::copyTextureToQuickFramebuffer(NetworkTexturePointer networkTexture, QOpenGLFramebufferObject* target, GLsync* fenceSync) {
|
||||||
|
auto glBackend = const_cast<OpenGLDisplayPlugin&>(*this).getGLBackend();
|
||||||
|
withMainThreadContext([&] {
|
||||||
|
GLuint sourceTexture = glBackend->getTextureID(networkTexture->getGPUTexture());
|
||||||
|
GLuint targetTexture = target->texture();
|
||||||
|
GLuint fbo[2] {0, 0};
|
||||||
|
|
||||||
|
// need mipmaps for blitting texture
|
||||||
|
glGenerateTextureMipmap(sourceTexture);
|
||||||
|
|
||||||
|
// create 2 fbos (one for initial texture, second for scaled one)
|
||||||
|
glCreateFramebuffers(2, fbo);
|
||||||
|
|
||||||
|
// setup source fbo
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, fbo[0]);
|
||||||
|
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, sourceTexture, 0);
|
||||||
|
|
||||||
|
GLint texWidth = networkTexture->getWidth();
|
||||||
|
GLint texHeight = networkTexture->getHeight();
|
||||||
|
|
||||||
|
// setup destination fbo
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, fbo[1]);
|
||||||
|
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, targetTexture, 0);
|
||||||
|
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
|
||||||
|
glClear(GL_COLOR_BUFFER_BIT);
|
||||||
|
|
||||||
|
|
||||||
|
// maintain aspect ratio, filling the width first if possible. If that makes the height too
|
||||||
|
// much, fill height instead. TODO: only do this when texture changes
|
||||||
|
GLint newX = 0;
|
||||||
|
GLint newY = 0;
|
||||||
|
float aspectRatio = (float)texHeight / (float)texWidth;
|
||||||
|
GLint newWidth = target->width();
|
||||||
|
GLint newHeight = std::round(aspectRatio * (float) target->width());
|
||||||
|
if (newHeight > target->height()) {
|
||||||
|
newHeight = target->height();
|
||||||
|
newWidth = std::round((float)target->height() / aspectRatio);
|
||||||
|
newX = (target->width() - newWidth) / 2;
|
||||||
|
} else {
|
||||||
|
newY = (target->height() - newHeight) / 2;
|
||||||
|
}
|
||||||
|
glBlitNamedFramebuffer(fbo[0], fbo[1], 0, 0, texWidth, texHeight, newX, newY, newX + newWidth, newY + newHeight, GL_DEPTH_BUFFER_BIT|GL_COLOR_BUFFER_BIT, GL_NEAREST);
|
||||||
|
|
||||||
|
// don't delete the textures!
|
||||||
|
glDeleteFramebuffers(2, fbo);
|
||||||
|
*fenceSync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -79,6 +79,8 @@ public:
|
||||||
// Three threads, one for rendering, one for texture transfers, one reserved for the GL driver
|
// Three threads, one for rendering, one for texture transfers, one reserved for the GL driver
|
||||||
int getRequiredThreadCount() const override { return 3; }
|
int getRequiredThreadCount() const override { return 3; }
|
||||||
|
|
||||||
|
void copyTextureToQuickFramebuffer(NetworkTexturePointer source, QOpenGLFramebufferObject* target, GLsync* fenceSync) override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
friend class PresentThread;
|
friend class PresentThread;
|
||||||
|
|
||||||
|
@ -111,6 +113,7 @@ protected:
|
||||||
// Plugin specific functionality to send the composed scene to the output window or device
|
// Plugin specific functionality to send the composed scene to the output window or device
|
||||||
virtual void internalPresent();
|
virtual void internalPresent();
|
||||||
|
|
||||||
|
void renderFromTexture(gpu::Batch& batch, const gpu::TexturePointer texture, glm::ivec4 viewport, const glm::ivec4 scissor, gpu::FramebufferPointer fbo);
|
||||||
void renderFromTexture(gpu::Batch& batch, const gpu::TexturePointer texture, glm::ivec4 viewport, const glm::ivec4 scissor);
|
void renderFromTexture(gpu::Batch& batch, const gpu::TexturePointer texture, glm::ivec4 viewport, const glm::ivec4 scissor);
|
||||||
virtual void updateFrameData();
|
virtual void updateFrameData();
|
||||||
|
|
||||||
|
|
|
@ -243,6 +243,8 @@ void HmdDisplayPlugin::internalPresent() {
|
||||||
glm::ivec4 viewport = getViewportForSourceSize(sourceSize);
|
glm::ivec4 viewport = getViewportForSourceSize(sourceSize);
|
||||||
glm::ivec4 scissor = viewport;
|
glm::ivec4 scissor = viewport;
|
||||||
|
|
||||||
|
auto fbo = gpu::FramebufferPointer();
|
||||||
|
|
||||||
render([&](gpu::Batch& batch) {
|
render([&](gpu::Batch& batch) {
|
||||||
|
|
||||||
if (_monoPreview) {
|
if (_monoPreview) {
|
||||||
|
@ -285,11 +287,15 @@ void HmdDisplayPlugin::internalPresent() {
|
||||||
viewport = ivec4(scissorOffset - scaledShiftLeftBy, viewportOffset, viewportSizeX, viewportSizeY);
|
viewport = ivec4(scissorOffset - scaledShiftLeftBy, viewportOffset, viewportSizeX, viewportSizeY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: only bother getting and passing in the hmdPreviewFramebuffer if the camera is on
|
||||||
|
fbo = DependencyManager::get<TextureCache>()->getHmdPreviewFramebuffer(windowSize.x, windowSize.y);
|
||||||
|
|
||||||
viewport.z *= 2;
|
viewport.z *= 2;
|
||||||
}
|
}
|
||||||
renderFromTexture(batch, _compositeFramebuffer->getRenderBuffer(0), viewport, scissor);
|
renderFromTexture(batch, _compositeFramebuffer->getRenderBuffer(0), viewport, scissor, fbo);
|
||||||
});
|
});
|
||||||
swapBuffers();
|
swapBuffers();
|
||||||
|
|
||||||
} else if (_clearPreviewFlag) {
|
} else if (_clearPreviewFlag) {
|
||||||
QImage image;
|
QImage image;
|
||||||
if (_vsyncEnabled) {
|
if (_vsyncEnabled) {
|
||||||
|
|
|
@ -54,6 +54,7 @@ const std::string TextureCache::KTX_EXT { "ktx" };
|
||||||
|
|
||||||
static const QString RESOURCE_SCHEME = "resource";
|
static const QString RESOURCE_SCHEME = "resource";
|
||||||
static const QUrl SPECTATOR_CAMERA_FRAME_URL("resource://spectatorCameraFrame");
|
static const QUrl SPECTATOR_CAMERA_FRAME_URL("resource://spectatorCameraFrame");
|
||||||
|
static const QUrl HMD_PREVIEW_FRAME_URL("resource://hmdPreviewFrame");
|
||||||
|
|
||||||
static const float SKYBOX_LOAD_PRIORITY { 10.0f }; // Make sure skybox loads first
|
static const float SKYBOX_LOAD_PRIORITY { 10.0f }; // Make sure skybox loads first
|
||||||
static const float HIGH_MIPS_LOAD_PRIORITY { 9.0f }; // Make sure high mips loads after skybox but before models
|
static const float HIGH_MIPS_LOAD_PRIORITY { 9.0f }; // Make sure high mips loads after skybox but before models
|
||||||
|
@ -969,23 +970,44 @@ void ImageReader::read() {
|
||||||
Q_ARG(int, texture->getHeight()));
|
Q_ARG(int, texture->getHeight()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
NetworkTexturePointer TextureCache::getResourceTexture(QUrl resourceTextureUrl) {
|
NetworkTexturePointer TextureCache::getResourceTexture(QUrl resourceTextureUrl) {
|
||||||
gpu::TexturePointer texture;
|
gpu::TexturePointer texture;
|
||||||
if (resourceTextureUrl == SPECTATOR_CAMERA_FRAME_URL) {
|
if (resourceTextureUrl == SPECTATOR_CAMERA_FRAME_URL) {
|
||||||
if (!_spectatorCameraNetworkTexture) {
|
if (!_spectatorCameraNetworkTexture) {
|
||||||
_spectatorCameraNetworkTexture.reset(new NetworkTexture(resourceTextureUrl));
|
_spectatorCameraNetworkTexture.reset(new NetworkTexture(resourceTextureUrl));
|
||||||
}
|
}
|
||||||
|
if (_spectatorCameraFramebuffer) {
|
||||||
texture = _spectatorCameraFramebuffer->getRenderBuffer(0);
|
texture = _spectatorCameraFramebuffer->getRenderBuffer(0);
|
||||||
if (texture) {
|
if (texture) {
|
||||||
_spectatorCameraNetworkTexture->setImage(texture, texture->getWidth(), texture->getHeight());
|
_spectatorCameraNetworkTexture->setImage(texture, texture->getWidth(), texture->getHeight());
|
||||||
return _spectatorCameraNetworkTexture;
|
return _spectatorCameraNetworkTexture;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
// FIXME: Generalize this, DRY up this code
|
||||||
|
if (resourceTextureUrl == HMD_PREVIEW_FRAME_URL) {
|
||||||
|
if (!_hmdPreviewNetworkTexture) {
|
||||||
|
_hmdPreviewNetworkTexture.reset(new NetworkTexture(resourceTextureUrl));
|
||||||
|
}
|
||||||
|
if (_hmdPreviewFramebuffer) {
|
||||||
|
texture = _hmdPreviewFramebuffer->getRenderBuffer(0);
|
||||||
|
if (texture) {
|
||||||
|
_hmdPreviewNetworkTexture->setImage(texture, texture->getWidth(), texture->getHeight());
|
||||||
|
return _hmdPreviewNetworkTexture;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return NetworkTexturePointer();
|
return NetworkTexturePointer();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const gpu::FramebufferPointer& TextureCache::getHmdPreviewFramebuffer(int width, int height) {
|
||||||
|
if (!_hmdPreviewFramebuffer || _hmdPreviewFramebuffer->getWidth() != width || _hmdPreviewFramebuffer->getHeight() != height) {
|
||||||
|
_hmdPreviewFramebuffer.reset(gpu::Framebuffer::create("hmdPreview",gpu::Element::COLOR_SRGBA_32, width, height));
|
||||||
|
}
|
||||||
|
return _hmdPreviewFramebuffer;
|
||||||
|
}
|
||||||
|
|
||||||
const gpu::FramebufferPointer& TextureCache::getSpectatorCameraFramebuffer() {
|
const gpu::FramebufferPointer& TextureCache::getSpectatorCameraFramebuffer() {
|
||||||
if (!_spectatorCameraFramebuffer) {
|
if (!_spectatorCameraFramebuffer) {
|
||||||
resetSpectatorCameraFramebuffer(2048, 1024);
|
resetSpectatorCameraFramebuffer(2048, 1024);
|
||||||
|
@ -996,4 +1018,5 @@ const gpu::FramebufferPointer& TextureCache::getSpectatorCameraFramebuffer() {
|
||||||
void TextureCache::resetSpectatorCameraFramebuffer(int width, int height) {
|
void TextureCache::resetSpectatorCameraFramebuffer(int width, int height) {
|
||||||
_spectatorCameraFramebuffer.reset(gpu::Framebuffer::create("spectatorCamera", gpu::Element::COLOR_SRGBA_32, width, height));
|
_spectatorCameraFramebuffer.reset(gpu::Framebuffer::create("spectatorCamera", gpu::Element::COLOR_SRGBA_32, width, height));
|
||||||
_spectatorCameraNetworkTexture.reset();
|
_spectatorCameraNetworkTexture.reset();
|
||||||
|
emit spectatorCameraFramebufferReset();
|
||||||
}
|
}
|
||||||
|
|
|
@ -170,6 +170,10 @@ public:
|
||||||
NetworkTexturePointer getResourceTexture(QUrl resourceTextureUrl);
|
NetworkTexturePointer getResourceTexture(QUrl resourceTextureUrl);
|
||||||
const gpu::FramebufferPointer& getSpectatorCameraFramebuffer();
|
const gpu::FramebufferPointer& getSpectatorCameraFramebuffer();
|
||||||
void resetSpectatorCameraFramebuffer(int width, int height);
|
void resetSpectatorCameraFramebuffer(int width, int height);
|
||||||
|
const gpu::FramebufferPointer& getHmdPreviewFramebuffer(int width, int height);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void spectatorCameraFramebufferReset();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
// Overload ResourceCache::prefetch to allow specifying texture type for loads
|
// Overload ResourceCache::prefetch to allow specifying texture type for loads
|
||||||
|
@ -202,6 +206,9 @@ private:
|
||||||
|
|
||||||
NetworkTexturePointer _spectatorCameraNetworkTexture;
|
NetworkTexturePointer _spectatorCameraNetworkTexture;
|
||||||
gpu::FramebufferPointer _spectatorCameraFramebuffer;
|
gpu::FramebufferPointer _spectatorCameraFramebuffer;
|
||||||
|
|
||||||
|
NetworkTexturePointer _hmdPreviewNetworkTexture;
|
||||||
|
gpu::FramebufferPointer _hmdPreviewFramebuffer;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_TextureCache_h
|
#endif // hifi_TextureCache_h
|
||||||
|
|
|
@ -22,9 +22,10 @@
|
||||||
#include <RegisteredMetaTypes.h>
|
#include <RegisteredMetaTypes.h>
|
||||||
#include <shared/Bilateral.h>
|
#include <shared/Bilateral.h>
|
||||||
#include <gpu/Forward.h>
|
#include <gpu/Forward.h>
|
||||||
|
|
||||||
#include "Plugin.h"
|
#include "Plugin.h"
|
||||||
|
|
||||||
|
class QOpenGLFramebufferObject;
|
||||||
|
|
||||||
class QImage;
|
class QImage;
|
||||||
|
|
||||||
enum Eye {
|
enum Eye {
|
||||||
|
@ -60,6 +61,10 @@ namespace gpu {
|
||||||
using TexturePointer = std::shared_ptr<Texture>;
|
using TexturePointer = std::shared_ptr<Texture>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class NetworkTexture;
|
||||||
|
using NetworkTexturePointer = QSharedPointer<NetworkTexture>;
|
||||||
|
typedef struct __GLsync *GLsync;
|
||||||
|
|
||||||
// Stereo display functionality
|
// Stereo display functionality
|
||||||
// TODO move out of this file don't derive DisplayPlugin from this. Instead use dynamic casting when
|
// TODO move out of this file don't derive DisplayPlugin from this. Instead use dynamic casting when
|
||||||
// displayPlugin->isStereo returns true
|
// displayPlugin->isStereo returns true
|
||||||
|
@ -208,6 +213,8 @@ public:
|
||||||
// Hardware specific stats
|
// Hardware specific stats
|
||||||
virtual QJsonObject getHardwareStats() const { return QJsonObject(); }
|
virtual QJsonObject getHardwareStats() const { return QJsonObject(); }
|
||||||
|
|
||||||
|
virtual void copyTextureToQuickFramebuffer(NetworkTexturePointer source, QOpenGLFramebufferObject* target, GLsync* fenceSync) = 0;
|
||||||
|
|
||||||
uint32_t presentCount() const { return _presentedFrameIndex; }
|
uint32_t presentCount() const { return _presentedFrameIndex; }
|
||||||
// Time since last call to incrementPresentCount (only valid if DEBUG_PAINT_DELAY is defined)
|
// Time since last call to incrementPresentCount (only valid if DEBUG_PAINT_DELAY is defined)
|
||||||
int64_t getPaintDelayUsecs() const;
|
int64_t getPaintDelayUsecs() const;
|
||||||
|
|
BIN
unpublishedScripts/marketplace/spectator-camera/cameraOn.wav
Normal file
BIN
unpublishedScripts/marketplace/spectator-camera/cameraOn.wav
Normal file
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,514 @@
|
||||||
|
"use strict";
|
||||||
|
/*jslint vars:true, plusplus:true, forin:true*/
|
||||||
|
/*global Tablet, Script, */
|
||||||
|
/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */
|
||||||
|
//
|
||||||
|
// spectatorCamera.js
|
||||||
|
//
|
||||||
|
// Created by Zach Fox on 2017-06-05
|
||||||
|
// 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
|
||||||
|
//
|
||||||
|
|
||||||
|
(function () { // BEGIN LOCAL_SCOPE
|
||||||
|
|
||||||
|
// FUNCTION VAR DECLARATIONS
|
||||||
|
var sendToQml, addOrRemoveButton, onTabletScreenChanged, fromQml,
|
||||||
|
onTabletButtonClicked, wireEventBridge, startup, shutdown, registerButtonMappings;
|
||||||
|
|
||||||
|
// Function Name: inFrontOf()
|
||||||
|
//
|
||||||
|
// Description:
|
||||||
|
// -Returns the position in front of the given "position" argument, where the forward vector is based off
|
||||||
|
// the "orientation" argument and the amount in front is based off the "distance" argument.
|
||||||
|
function inFrontOf(distance, position, orientation) {
|
||||||
|
return Vec3.sum(position || MyAvatar.position,
|
||||||
|
Vec3.multiply(distance, Quat.getForward(orientation || MyAvatar.orientation)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function Name: spectatorCameraOn()
|
||||||
|
//
|
||||||
|
// Description:
|
||||||
|
// -Call this function to set up the spectator camera and
|
||||||
|
// spawn the camera entity.
|
||||||
|
//
|
||||||
|
// Relevant Variables:
|
||||||
|
// -spectatorCameraConfig: The render configuration of the spectator camera
|
||||||
|
// render job. It controls various attributes of the Secondary Camera, such as:
|
||||||
|
// -The entity ID to follow
|
||||||
|
// -Position
|
||||||
|
// -Orientation
|
||||||
|
// -Rendered texture size
|
||||||
|
// -Vertical field of view
|
||||||
|
// -Near clip plane distance
|
||||||
|
// -Far clip plane distance
|
||||||
|
// -viewFinderOverlay: The in-world overlay that displays the spectator camera's view.
|
||||||
|
// -camera: The in-world entity that corresponds to the spectator camera.
|
||||||
|
// -cameraIsDynamic: "false" for now - maybe it shouldn't be? False means that the camera won't drift when you let go...
|
||||||
|
// -cameraRotation: The rotation of the spectator camera.
|
||||||
|
// -cameraPosition: The position of the spectator camera.
|
||||||
|
// -glassPaneWidth: The width of the glass pane above the spectator camera that holds the viewFinderOverlay.
|
||||||
|
// -viewFinderOverlayDim: The x, y, and z dimensions of the viewFinderOverlay.
|
||||||
|
// -camera: The camera model which is grabbable.
|
||||||
|
// -viewFinderOverlay: The preview of what the spectator camera is viewing, placed inside the glass pane.
|
||||||
|
var spectatorCameraConfig = Render.getConfig("SecondaryCamera");
|
||||||
|
var viewFinderOverlay = false;
|
||||||
|
var camera = false;
|
||||||
|
var cameraIsDynamic = false;
|
||||||
|
var cameraRotation;
|
||||||
|
var cameraPosition;
|
||||||
|
var glassPaneWidth = 0.16;
|
||||||
|
// The negative y dimension for viewFinderOverlay is necessary for now due to the way Image3DOverlay
|
||||||
|
// draws textures, but should be looked into at some point. Also the z dimension shouldn't affect
|
||||||
|
// the overlay since it is an Image3DOverlay so it is set to 0.
|
||||||
|
var viewFinderOverlayDim = { x: glassPaneWidth, y: -glassPaneWidth, z: 0 };
|
||||||
|
function spectatorCameraOn() {
|
||||||
|
// Sets the special texture size based on the window it is displayed in, which doesn't include the menu bar
|
||||||
|
spectatorCameraConfig.enableSecondaryCameraRenderConfigs(true);
|
||||||
|
spectatorCameraConfig.resetSizeSpectatorCamera(Window.innerWidth, Window.innerHeight);
|
||||||
|
cameraRotation = Quat.multiply(MyAvatar.orientation, Quat.fromPitchYawRollDegrees(15, -155, 0)), cameraPosition = inFrontOf(0.85, Vec3.sum(MyAvatar.position, { x: 0, y: 0.28, z: 0 }));
|
||||||
|
camera = Entities.addEntity({
|
||||||
|
"angularDamping": 1,
|
||||||
|
"damping": 1,
|
||||||
|
"collidesWith": "static,dynamic,kinematic,",
|
||||||
|
"collisionMask": 7,
|
||||||
|
"dynamic": cameraIsDynamic,
|
||||||
|
"modelURL": Script.resolvePath("spectator-camera.fbx"),
|
||||||
|
"registrationPoint": {
|
||||||
|
"x": 0.56,
|
||||||
|
"y": 0.545,
|
||||||
|
"z": 0.23
|
||||||
|
},
|
||||||
|
"rotation": cameraRotation,
|
||||||
|
"position": cameraPosition,
|
||||||
|
"shapeType": "simple-compound",
|
||||||
|
"type": "Model",
|
||||||
|
"userData": "{\"grabbableKey\":{\"grabbable\":true}}"
|
||||||
|
}, true);
|
||||||
|
spectatorCameraConfig.attachedEntityId = camera;
|
||||||
|
updateOverlay();
|
||||||
|
setDisplay(monitorShowsCameraView);
|
||||||
|
// Change button to active when window is first openend OR if the camera is on, false otherwise.
|
||||||
|
if (button) {
|
||||||
|
button.editProperties({ isActive: onSpectatorCameraScreen || camera });
|
||||||
|
}
|
||||||
|
Audio.playSound(CAMERA_ON_SOUND, {
|
||||||
|
volume: 0.15,
|
||||||
|
position: cameraPosition,
|
||||||
|
localOnly: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function Name: spectatorCameraOff()
|
||||||
|
//
|
||||||
|
// Description:
|
||||||
|
// -Call this function to shut down the spectator camera and
|
||||||
|
// destroy the camera entity. "isChangingDomains" is true when this function is called
|
||||||
|
// from the "Window.domainChanged()" signal.
|
||||||
|
var WAIT_AFTER_DOMAIN_SWITCH_BEFORE_CAMERA_DELETE_MS = 1 * 1000;
|
||||||
|
function spectatorCameraOff(isChangingDomains) {
|
||||||
|
function deleteCamera() {
|
||||||
|
Entities.deleteEntity(camera);
|
||||||
|
camera = false;
|
||||||
|
if (button) {
|
||||||
|
// Change button to active when window is first openend OR if the camera is on, false otherwise.
|
||||||
|
button.editProperties({ isActive: onSpectatorCameraScreen || camera });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
spectatorCameraConfig.attachedEntityId = false;
|
||||||
|
spectatorCameraConfig.enableSecondaryCameraRenderConfigs(false);
|
||||||
|
if (camera) {
|
||||||
|
// Workaround for Avatar Entities not immediately having properties after
|
||||||
|
// the "Window.domainChanged()" signal is emitted.
|
||||||
|
// Should be removed after FB6155 is fixed.
|
||||||
|
if (isChangingDomains) {
|
||||||
|
Script.setTimeout(function () {
|
||||||
|
deleteCamera();
|
||||||
|
spectatorCameraOn();
|
||||||
|
}, WAIT_AFTER_DOMAIN_SWITCH_BEFORE_CAMERA_DELETE_MS);
|
||||||
|
} else {
|
||||||
|
deleteCamera();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (viewFinderOverlay) {
|
||||||
|
Overlays.deleteOverlay(viewFinderOverlay);
|
||||||
|
}
|
||||||
|
viewFinderOverlay = false;
|
||||||
|
setDisplay(monitorShowsCameraView);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function Name: addOrRemoveButton()
|
||||||
|
//
|
||||||
|
// Description:
|
||||||
|
// -Used to add or remove the "SPECTATOR" app button from the HUD/tablet. Set the "isShuttingDown" argument
|
||||||
|
// to true if you're calling this function upon script shutdown. Set the "isHMDmode" to true if the user is
|
||||||
|
// in HMD; otherwise set to false.
|
||||||
|
//
|
||||||
|
// Relevant Variables:
|
||||||
|
// -button: The tablet button.
|
||||||
|
// -buttonName: The name of the button.
|
||||||
|
// -showSpectatorInDesktop: Set to "true" to show the "SPECTATOR" app in desktop mode.
|
||||||
|
var button = false;
|
||||||
|
var buttonName = "SPECTATOR";
|
||||||
|
var showSpectatorInDesktop = false;
|
||||||
|
function addOrRemoveButton(isShuttingDown, isHMDMode) {
|
||||||
|
if (!tablet) {
|
||||||
|
print("Warning in addOrRemoveButton(): 'tablet' undefined!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!button) {
|
||||||
|
if ((isHMDMode || showSpectatorInDesktop) && !isShuttingDown) {
|
||||||
|
button = tablet.addButton({
|
||||||
|
text: buttonName,
|
||||||
|
icon: "icons/tablet-icons/spectator-i.svg",
|
||||||
|
activeIcon: "icons/tablet-icons/spectator-a.svg"
|
||||||
|
});
|
||||||
|
button.clicked.connect(onTabletButtonClicked);
|
||||||
|
}
|
||||||
|
} else if (button) {
|
||||||
|
if ((!isHMDMode && !showSpectatorInDesktop) || isShuttingDown) {
|
||||||
|
button.clicked.disconnect(onTabletButtonClicked);
|
||||||
|
tablet.removeButton(button);
|
||||||
|
button = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
print("ERROR adding/removing Spectator button!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function Name: startup()
|
||||||
|
//
|
||||||
|
// Description:
|
||||||
|
// -startup() will be called when the script is loaded.
|
||||||
|
//
|
||||||
|
// Relevant Variables:
|
||||||
|
// -tablet: The tablet instance to be modified.
|
||||||
|
var tablet = null;
|
||||||
|
function startup() {
|
||||||
|
tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
|
||||||
|
addOrRemoveButton(false, HMD.active);
|
||||||
|
tablet.screenChanged.connect(onTabletScreenChanged);
|
||||||
|
Window.domainChanged.connect(onDomainChanged);
|
||||||
|
Window.geometryChanged.connect(resizeViewFinderOverlay);
|
||||||
|
Controller.keyPressEvent.connect(keyPressEvent);
|
||||||
|
HMD.displayModeChanged.connect(onHMDChanged);
|
||||||
|
viewFinderOverlay = false;
|
||||||
|
camera = false;
|
||||||
|
registerButtonMappings();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function Name: wireEventBridge()
|
||||||
|
//
|
||||||
|
// Description:
|
||||||
|
// -Used to connect/disconnect the script's response to the tablet's "fromQml" signal. Set the "on" argument to enable or
|
||||||
|
// disable to event bridge.
|
||||||
|
//
|
||||||
|
// Relevant Variables:
|
||||||
|
// -hasEventBridge: true/false depending on whether we've already connected the event bridge.
|
||||||
|
var hasEventBridge = false;
|
||||||
|
function wireEventBridge(on) {
|
||||||
|
if (!tablet) {
|
||||||
|
print("Warning in wireEventBridge(): 'tablet' undefined!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (on) {
|
||||||
|
if (!hasEventBridge) {
|
||||||
|
tablet.fromQml.connect(fromQml);
|
||||||
|
hasEventBridge = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (hasEventBridge) {
|
||||||
|
tablet.fromQml.disconnect(fromQml);
|
||||||
|
hasEventBridge = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function Name: setDisplay()
|
||||||
|
//
|
||||||
|
// Description:
|
||||||
|
// -There are two bool variables that determine what the "url" argument to "setDisplayTexture(url)" should be:
|
||||||
|
// Camera on/off switch, and the "Monitor Shows" on/off switch.
|
||||||
|
// This results in four possible cases for the argument. Those four cases are:
|
||||||
|
// 1. Camera is off; "Monitor Shows" is "HMD Preview": "url" is ""
|
||||||
|
// 2. Camera is off; "Monitor Shows" is "Camera View": "url" is ""
|
||||||
|
// 3. Camera is on; "Monitor Shows" is "HMD Preview": "url" is ""
|
||||||
|
// 4. Camera is on; "Monitor Shows" is "Camera View": "url" is "resource://spectatorCameraFrame"
|
||||||
|
function setDisplay(showCameraView) {
|
||||||
|
|
||||||
|
var url = (camera) ? (showCameraView ? "resource://spectatorCameraFrame" : "resource://hmdPreviewFrame") : "";
|
||||||
|
sendToQml({ method: 'showPreviewTextureNotInstructions', setting: !!url, url: url});
|
||||||
|
|
||||||
|
// FIXME: temporary hack to avoid setting the display texture to hmdPreviewFrame
|
||||||
|
// until it is the correct mono.
|
||||||
|
if (url === "resource://hmdPreviewFrame") {
|
||||||
|
Window.setDisplayTexture("");
|
||||||
|
} else {
|
||||||
|
Window.setDisplayTexture(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const MONITOR_SHOWS_CAMERA_VIEW_DEFAULT = false;
|
||||||
|
var monitorShowsCameraView = !!Settings.getValue('spectatorCamera/monitorShowsCameraView', MONITOR_SHOWS_CAMERA_VIEW_DEFAULT);
|
||||||
|
function setMonitorShowsCameraView(showCameraView) {
|
||||||
|
if (showCameraView === monitorShowsCameraView) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
monitorShowsCameraView = showCameraView;
|
||||||
|
setDisplay(showCameraView);
|
||||||
|
Settings.setValue('spectatorCamera/monitorShowsCameraView', showCameraView);
|
||||||
|
}
|
||||||
|
function setMonitorShowsCameraViewAndSendToQml(showCameraView) {
|
||||||
|
setMonitorShowsCameraView(showCameraView);
|
||||||
|
sendToQml({ method: 'updateMonitorShowsSwitch', params: showCameraView });
|
||||||
|
}
|
||||||
|
function keyPressEvent(event) {
|
||||||
|
if ((event.text === "0") && !event.isAutoRepeat && !event.isShifted && !event.isMeta && event.isControl && !event.isAlt) {
|
||||||
|
setMonitorShowsCameraViewAndSendToQml(!monitorShowsCameraView);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function updateOverlay() {
|
||||||
|
// The only way I found to update the viewFinderOverlay without turning the spectator camera on and off is to delete and recreate the
|
||||||
|
// overlay, which is inefficient but resizing the window shouldn't be performed often
|
||||||
|
if (viewFinderOverlay) {
|
||||||
|
Overlays.deleteOverlay(viewFinderOverlay);
|
||||||
|
}
|
||||||
|
viewFinderOverlay = Overlays.addOverlay("image3d", {
|
||||||
|
url: "resource://spectatorCameraFrame",
|
||||||
|
emissive: true,
|
||||||
|
parentID: camera,
|
||||||
|
alpha: 1,
|
||||||
|
localRotation: { w: 1, x: 0, y: 0, z: 0 },
|
||||||
|
localPosition: { x: 0, y: 0.13, z: 0.126 },
|
||||||
|
dimensions: viewFinderOverlayDim
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function Name: resizeViewFinderOverlay()
|
||||||
|
//
|
||||||
|
// Description:
|
||||||
|
// -A function called when the window is moved/resized, which changes the viewFinderOverlay's texture and dimensions to be
|
||||||
|
// appropriately altered to fit inside the glass pane while not distorting the texture. The "geometryChanged" argument gives information
|
||||||
|
// on how the window changed, including x, y, width, and height.
|
||||||
|
//
|
||||||
|
// Relevant Variables:
|
||||||
|
// -glassPaneRatio: The aspect ratio of the glass pane, currently set as a 16:9 aspect ratio (change if model changes).
|
||||||
|
// -verticalScale: The amount the viewFinderOverlay should be scaled if the window size is vertical.
|
||||||
|
// -squareScale: The amount the viewFinderOverlay should be scaled if the window size is not vertical but is more square than the
|
||||||
|
// glass pane's aspect ratio.
|
||||||
|
function resizeViewFinderOverlay(geometryChanged) {
|
||||||
|
var glassPaneRatio = 16 / 9;
|
||||||
|
var verticalScale = 1 / glassPaneRatio;
|
||||||
|
var squareScale = verticalScale * (1 + (1 - (1 / (geometryChanged.width / geometryChanged.height))));
|
||||||
|
|
||||||
|
if (geometryChanged.height > geometryChanged.width) { //vertical window size
|
||||||
|
viewFinderOverlayDim = { x: (glassPaneWidth * verticalScale), y: (-glassPaneWidth * verticalScale), z: 0 };
|
||||||
|
} else if ((geometryChanged.width / geometryChanged.height) < glassPaneRatio) { //square-ish window size, in-between vertical and horizontal
|
||||||
|
viewFinderOverlayDim = { x: (glassPaneWidth * squareScale), y: (-glassPaneWidth * squareScale), z: 0 };
|
||||||
|
} else { //horizontal window size
|
||||||
|
viewFinderOverlayDim = { x: glassPaneWidth, y: -glassPaneWidth, z: 0 };
|
||||||
|
}
|
||||||
|
updateOverlay();
|
||||||
|
spectatorCameraConfig.resetSizeSpectatorCamera(geometryChanged.width, geometryChanged.height);
|
||||||
|
setDisplay(monitorShowsCameraView);
|
||||||
|
}
|
||||||
|
|
||||||
|
const SWITCH_VIEW_FROM_CONTROLLER_DEFAULT = false;
|
||||||
|
var switchViewFromController = !!Settings.getValue('spectatorCamera/switchViewFromController', SWITCH_VIEW_FROM_CONTROLLER_DEFAULT);
|
||||||
|
function setControllerMappingStatus(status) {
|
||||||
|
if (!controllerMapping) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (status) {
|
||||||
|
controllerMapping.enable();
|
||||||
|
} else {
|
||||||
|
controllerMapping.disable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function setSwitchViewFromController(setting) {
|
||||||
|
if (setting === switchViewFromController) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
switchViewFromController = setting;
|
||||||
|
setControllerMappingStatus(switchViewFromController);
|
||||||
|
Settings.setValue('spectatorCamera/switchViewFromController', setting);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function Name: registerButtonMappings()
|
||||||
|
//
|
||||||
|
// Description:
|
||||||
|
// -Updates controller button mappings for Spectator Camera.
|
||||||
|
//
|
||||||
|
// Relevant Variables:
|
||||||
|
// -controllerMappingName: The name of the controller mapping.
|
||||||
|
// -controllerMapping: The controller mapping itself.
|
||||||
|
// -controllerType: "OculusTouch", "Vive", "Other".
|
||||||
|
var controllerMappingName;
|
||||||
|
var controllerMapping;
|
||||||
|
var controllerType = "Other";
|
||||||
|
function registerButtonMappings() {
|
||||||
|
var VRDevices = Controller.getDeviceNames().toString();
|
||||||
|
if (VRDevices) {
|
||||||
|
if (VRDevices.indexOf("Vive") !== -1) {
|
||||||
|
controllerType = "Vive";
|
||||||
|
} else if (VRDevices.indexOf("OculusTouch") !== -1) {
|
||||||
|
controllerType = "OculusTouch";
|
||||||
|
} else {
|
||||||
|
sendToQml({ method: 'updateControllerMappingCheckbox', setting: switchViewFromController, controller: controllerType });
|
||||||
|
return; // Neither Vive nor Touch detected
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
controllerMappingName = 'Hifi-SpectatorCamera-Mapping';
|
||||||
|
controllerMapping = Controller.newMapping(controllerMappingName);
|
||||||
|
if (controllerType === "OculusTouch") {
|
||||||
|
controllerMapping.from(Controller.Standard.LS).to(function (value) {
|
||||||
|
if (value === 1.0) {
|
||||||
|
setMonitorShowsCameraViewAndSendToQml(!monitorShowsCameraView);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
});
|
||||||
|
} else if (controllerType === "Vive") {
|
||||||
|
controllerMapping.from(Controller.Standard.LeftPrimaryThumb).to(function (value) {
|
||||||
|
if (value === 1.0) {
|
||||||
|
setMonitorShowsCameraViewAndSendToQml(!monitorShowsCameraView);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setControllerMappingStatus(switchViewFromController);
|
||||||
|
sendToQml({ method: 'updateControllerMappingCheckbox', setting: switchViewFromController, controller: controllerType });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function Name: onTabletButtonClicked()
|
||||||
|
//
|
||||||
|
// Description:
|
||||||
|
// -Fired when the Spectator Camera app button is pressed.
|
||||||
|
//
|
||||||
|
// Relevant Variables:
|
||||||
|
// -SPECTATOR_CAMERA_QML_SOURCE: The path to the SpectatorCamera QML
|
||||||
|
// -onSpectatorCameraScreen: true/false depending on whether we're looking at the spectator camera app.
|
||||||
|
var SPECTATOR_CAMERA_QML_SOURCE = Script.resourcesPath() + "qml/hifi/SpectatorCamera.qml";
|
||||||
|
var onSpectatorCameraScreen = false;
|
||||||
|
function onTabletButtonClicked() {
|
||||||
|
if (!tablet) {
|
||||||
|
print("Warning in onTabletButtonClicked(): 'tablet' undefined!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (onSpectatorCameraScreen) {
|
||||||
|
// for toolbar-mode: go back to home screen, this will close the window.
|
||||||
|
tablet.gotoHomeScreen();
|
||||||
|
} else {
|
||||||
|
tablet.loadQMLSource(SPECTATOR_CAMERA_QML_SOURCE);
|
||||||
|
sendToQml({ method: 'updateSpectatorCameraCheckbox', params: !!camera });
|
||||||
|
sendToQml({ method: 'updateMonitorShowsSwitch', params: monitorShowsCameraView });
|
||||||
|
if (!controllerMapping) {
|
||||||
|
registerButtonMappings();
|
||||||
|
} else {
|
||||||
|
sendToQml({ method: 'updateControllerMappingCheckbox', setting: switchViewFromController, controller: controllerType });
|
||||||
|
}
|
||||||
|
Menu.setIsOptionChecked("Disable Preview", false);
|
||||||
|
Menu.setIsOptionChecked("Mono Preview", true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function Name: onTabletScreenChanged()
|
||||||
|
//
|
||||||
|
// Description:
|
||||||
|
// -Called when the TabletScriptingInterface::screenChanged() signal is emitted. The "type" argument can be either the string
|
||||||
|
// value of "Home", "Web", "Menu", "QML", or "Closed". The "url" argument is only valid for Web and QML.
|
||||||
|
function onTabletScreenChanged(type, url) {
|
||||||
|
onSpectatorCameraScreen = (type === "QML" && url === SPECTATOR_CAMERA_QML_SOURCE);
|
||||||
|
wireEventBridge(onSpectatorCameraScreen);
|
||||||
|
// Change button to active when window is first openend OR if the camera is on, false otherwise.
|
||||||
|
if (button) {
|
||||||
|
button.editProperties({ isActive: onSpectatorCameraScreen || camera });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function Name: sendToQml()
|
||||||
|
//
|
||||||
|
// Description:
|
||||||
|
// -Use this function to send a message to the QML (i.e. to change appearances). The "message" argument is what is sent to
|
||||||
|
// SpectatorCamera QML in the format "{method, params}", like json-rpc. See also fromQml().
|
||||||
|
function sendToQml(message) {
|
||||||
|
tablet.sendToQml(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function Name: fromQml()
|
||||||
|
//
|
||||||
|
// Description:
|
||||||
|
// -Called when a message is received from SpectatorCamera.qml. The "message" argument is what is sent from the SpectatorCamera QML
|
||||||
|
// in the format "{method, params}", like json-rpc. See also sendToQml().
|
||||||
|
function fromQml(message) {
|
||||||
|
switch (message.method) {
|
||||||
|
case 'spectatorCameraOn':
|
||||||
|
spectatorCameraOn();
|
||||||
|
break;
|
||||||
|
case 'spectatorCameraOff':
|
||||||
|
spectatorCameraOff();
|
||||||
|
break;
|
||||||
|
case 'setMonitorShowsCameraView':
|
||||||
|
setMonitorShowsCameraView(message.params);
|
||||||
|
break;
|
||||||
|
case 'changeSwitchViewFromControllerPreference':
|
||||||
|
setSwitchViewFromController(message.params);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
print('Unrecognized message from SpectatorCamera.qml:', JSON.stringify(message));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function Name: onHMDChanged()
|
||||||
|
//
|
||||||
|
// Description:
|
||||||
|
// -Called from C++ when HMD mode is changed. The argument "isHMDMode" is true if HMD is on; false otherwise.
|
||||||
|
function onHMDChanged(isHMDMode) {
|
||||||
|
if (!controllerMapping) {
|
||||||
|
registerButtonMappings();
|
||||||
|
}
|
||||||
|
setDisplay(monitorShowsCameraView);
|
||||||
|
addOrRemoveButton(false, isHMDMode);
|
||||||
|
if (!isHMDMode && !showSpectatorInDesktop) {
|
||||||
|
spectatorCameraOff();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function Name: shutdown()
|
||||||
|
//
|
||||||
|
// Description:
|
||||||
|
// -shutdown() will be called when the script ends (i.e. is stopped).
|
||||||
|
function shutdown() {
|
||||||
|
spectatorCameraOff();
|
||||||
|
Window.domainChanged.disconnect(onDomainChanged);
|
||||||
|
Window.geometryChanged.disconnect(resizeViewFinderOverlay);
|
||||||
|
addOrRemoveButton(true, HMD.active);
|
||||||
|
if (tablet) {
|
||||||
|
tablet.screenChanged.disconnect(onTabletScreenChanged);
|
||||||
|
if (onSpectatorCameraScreen) {
|
||||||
|
tablet.gotoHomeScreen();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
HMD.displayModeChanged.disconnect(onHMDChanged);
|
||||||
|
Controller.keyPressEvent.disconnect(keyPressEvent);
|
||||||
|
if (controllerMapping) {
|
||||||
|
controllerMapping.disable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function Name: onDomainChanged()
|
||||||
|
//
|
||||||
|
// Description:
|
||||||
|
// -A small utility function used when the Window.domainChanged() signal is fired.
|
||||||
|
function onDomainChanged() {
|
||||||
|
spectatorCameraOff(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// These functions will be called when the script is loaded.
|
||||||
|
var CAMERA_ON_SOUND = SoundCache.getSound(Script.resolvePath("cameraOn.wav"));
|
||||||
|
startup();
|
||||||
|
Script.scriptEnding.connect(shutdown);
|
||||||
|
|
||||||
|
}()); // END LOCAL_SCOPE
|
Loading…
Reference in a new issue