mirror of
https://github.com/overte-org/overte.git
synced 2025-08-10 12:12:32 +02:00
Merge pull request #10629 from zfox23/SpectatorCamera_BasicTabletApp
Spectator Camera: Basic tablet app
This commit is contained in:
commit
0418622315
7 changed files with 834 additions and 71 deletions
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
300
interface/resources/qml/hifi/SpectatorCamera.qml
Normal file
300
interface/resources/qml/hifi/SpectatorCamera.qml
Normal file
|
@ -0,0 +1,300 @@
|
|||
//
|
||||
// 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 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;
|
||||
|
||||
//
|
||||
// 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: {
|
||||
console.log("FIXME! Add popup pointing to 'Learn More' page");
|
||||
}
|
||||
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: "Camera On";
|
||||
boxSize: 24;
|
||||
onClicked: {
|
||||
sendToScript({method: (checked ? 'spectatorCameraOn' : 'spectatorCameraOff')});
|
||||
}
|
||||
}
|
||||
|
||||
// Spectator Camera Preview
|
||||
Image {
|
||||
id: spectatorCameraPreview;
|
||||
height: 250;
|
||||
anchors.left: parent.left;
|
||||
anchors.top: cameraToggleCheckBox.bottom;
|
||||
anchors.topMargin: 20;
|
||||
anchors.right: parent.right;
|
||||
fillMode: Image.PreserveAspectFit;
|
||||
horizontalAlignment: Image.AlignHCenter;
|
||||
verticalAlignment: Image.AlignVCenter;
|
||||
source: "http://1.bp.blogspot.com/-1GABEq__054/T03B00j_OII/AAAAAAAAAa8/jo55LcvEPHI/s1600/Winning.jpg";
|
||||
}
|
||||
|
||||
// "Monitor Shows" Switch Label Glyph
|
||||
HiFiGlyphs {
|
||||
id: monitorShowsSwitchLabelGlyph;
|
||||
text: hifi.glyphs.screen;
|
||||
size: 32;
|
||||
color: hifi.colors.blueHighlight;
|
||||
anchors.top: spectatorCameraPreview.bottom;
|
||||
anchors.topMargin: 12;
|
||||
anchors.left: parent.left;
|
||||
}
|
||||
// "Monitor Shows" Switch Label
|
||||
RalewayLight {
|
||||
id: monitorShowsSwitchLabel;
|
||||
text: "MONITOR SHOWS:";
|
||||
anchors.top: spectatorCameraPreview.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: (checked ? 'showCameraViewOnMonitor' : 'showHmdPreviewOnMonitor')});
|
||||
}
|
||||
}
|
||||
|
||||
// "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: "Pressing Vive's Left Thumbpad Switches Monitor View";
|
||||
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;
|
||||
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
|
||||
|
||||
// Base colors
|
||||
readonly property color baseGray: "#404040"
|
||||
readonly property color baseGray: "#393939"
|
||||
readonly property color darkGray: "#121212"
|
||||
readonly property color baseGrayShadow: "#252525"
|
||||
readonly property color baseGrayHighlight: "#575757"
|
||||
|
|
|
@ -25,6 +25,7 @@ var DEFAULT_SCRIPTS_COMBINED = [
|
|||
"system/tablet-goto.js",
|
||||
"system/marketplaces/marketplaces.js",
|
||||
"system/edit.js",
|
||||
"system/spectatorCamera.js",
|
||||
"system/selectAudioDevice.js",
|
||||
"system/notifications.js",
|
||||
"system/dialTone.js",
|
||||
|
|
|
@ -1,70 +0,0 @@
|
|||
function inFrontOf(distance, position, orientation) {
|
||||
return Vec3.sum(position || MyAvatar.position,
|
||||
Vec3.multiply(distance, Quat.getForward(orientation || MyAvatar.orientation)));
|
||||
}
|
||||
var aroundY = Quat.fromPitchYawRollDegrees(0, 180, 0);
|
||||
function flip(rotation) { return Quat.multiply(rotation, aroundY); }
|
||||
// Specifying the following userData makes the camera near-grabbable in HMD.
|
||||
// We really want it to also be far-grabbable and mouse-grabbable,
|
||||
// but that requires dynamic:1, but alas that causes the camera to drift
|
||||
// when let go. Maybe we'll restore the old "zero velocity on release" code
|
||||
// to (handController)grab.js, and also make the camera collisionless?
|
||||
var isDynamic = false;
|
||||
|
||||
var viewFinderOverlay, camera = Entities.addEntity({
|
||||
type: 'Box',
|
||||
dimensions: {x: 0.4, y: 0.2, z: 0.4},
|
||||
userData: '{"grabbableKey":{"grabbable":true}}',
|
||||
dynamic: isDynamic ? 1 : 0,
|
||||
color: {red: 255, green: 0, blue: 0},
|
||||
name: 'SpectatorCamera'
|
||||
}); // FIXME: avatar entity so that you don't need rez rights.
|
||||
|
||||
var config = Render.getConfig("SelfieFrame");
|
||||
var config2 = Render.getConfig("BeginSelfie");
|
||||
function updateRenderFromCamera() {
|
||||
var cameraData = Entities.getEntityProperties(camera, ['position', 'rotation']);
|
||||
// FIXME: don't muck with config if properties haven't changed.
|
||||
config2.position = cameraData.position;
|
||||
config2.orientation = cameraData.rotation;
|
||||
// BUG: image3d overlays don't retain their locations properly when parented to a dynamic object
|
||||
if (isDynamic) {
|
||||
Overlays.editOverlay(viewFinderOverlay, { orientation: flip(cameraData.rotation) });
|
||||
}
|
||||
}
|
||||
Script.setTimeout(function () { // delay for a bit in case this script is running at startup
|
||||
// Set the special texture size based on the window in which it will eventually be displayed.
|
||||
var size = Controller.getViewportDimensions();
|
||||
config.resetSize(size.x, size.y);
|
||||
Script.update.connect(updateRenderFromCamera);
|
||||
config.enabled = config2.enabled = true;
|
||||
Script.setTimeout(function () { // FIXME: do we need this delay? why?
|
||||
var cameraRotation = MyAvatar.orientation, cameraPosition = inFrontOf(2);
|
||||
// Put the camera in front of me so that I can find it.
|
||||
Entities.editEntity(camera, {
|
||||
position: cameraPosition,
|
||||
rotation: cameraRotation
|
||||
});
|
||||
// Put an image3d overlay on the near face, as a viewFinder.
|
||||
viewFinderOverlay = Overlays.addOverlay("image3d", {
|
||||
url: "http://selfieFrame",
|
||||
//url: "http://1.bp.blogspot.com/-1GABEq__054/T03B00j_OII/AAAAAAAAAa8/jo55LcvEPHI/s1600/Winning.jpg",
|
||||
parentID: camera,
|
||||
alpha: 1,
|
||||
position: inFrontOf(-0.25, cameraPosition, cameraRotation),
|
||||
// FIXME: We shouldn't need the flip and the negative scale.
|
||||
// e.g., This isn't necessary using an ordinary .jpg with lettering, above.
|
||||
// Must be something about the view frustum projection matrix?
|
||||
// But don't go changing that in (c++ code) without getting all the way to a desktop display!
|
||||
orientation: flip(cameraRotation),
|
||||
scale: -0.35,
|
||||
});
|
||||
}, 500);
|
||||
}, 1000);
|
||||
|
||||
Script.scriptEnding.connect(function () {
|
||||
config.enabled = config2.enabled = false;
|
||||
Script.update.disconnect(updateRenderFromCamera);
|
||||
Overlays.deleteOverlay(viewFinderOverlay);
|
||||
Entities.deleteEntity(camera);
|
||||
})
|
338
scripts/system/spectatorCamera.js
Normal file
338
scripts/system/spectatorCamera.js
Normal file
|
@ -0,0 +1,338 @@
|
|||
"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, onTabletScreenChanged, fromQml, onTabletButtonClicked, wireEventBridge, startup, shutdown;
|
||||
|
||||
|
||||
|
||||
//
|
||||
// Function Name: inFrontOf(), flip()
|
||||
//
|
||||
// Description:
|
||||
// Spectator camera utility functions and variables.
|
||||
//
|
||||
function inFrontOf(distance, position, orientation) {
|
||||
return Vec3.sum(position || MyAvatar.position,
|
||||
Vec3.multiply(distance, Quat.getForward(orientation || MyAvatar.orientation)));
|
||||
}
|
||||
var aroundY = Quat.fromPitchYawRollDegrees(0, 180, 0);
|
||||
function flip(rotation) { return Quat.multiply(rotation, aroundY); }
|
||||
|
||||
//
|
||||
// Function Name: updateRenderFromCamera()
|
||||
//
|
||||
// Relevant Variables:
|
||||
// spectatorFrameRenderConfig: The render configuration of the spectator camera
|
||||
// render job. Controls size.
|
||||
// beginSpectatorFrameRenderConfig: The render configuration of the spectator camera
|
||||
// render job. Controls position and orientation.
|
||||
// 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 while we figure out why dynamic, parented overlays
|
||||
// drift with respect to their parent
|
||||
//
|
||||
// Arguments:
|
||||
// None
|
||||
//
|
||||
// Description:
|
||||
// The update function for the spectator camera. Modifies the camera's position
|
||||
// and orientation.
|
||||
//
|
||||
var spectatorFrameRenderConfig = Render.getConfig("SelfieFrame");
|
||||
var beginSpectatorFrameRenderConfig = Render.getConfig("BeginSelfie");
|
||||
var viewFinderOverlay = false;
|
||||
var camera = false;
|
||||
var cameraIsDynamic = false;
|
||||
function updateRenderFromCamera() {
|
||||
var cameraData = Entities.getEntityProperties(camera, ['position', 'rotation']);
|
||||
// FIXME: don't muck with config if properties haven't changed.
|
||||
beginSpectatorFrameRenderConfig.position = cameraData.position;
|
||||
beginSpectatorFrameRenderConfig.orientation = cameraData.rotation;
|
||||
if (cameraIsDynamic) {
|
||||
// BUG: image3d overlays don't retain their locations properly when parented to a dynamic object
|
||||
Overlays.editOverlay(viewFinderOverlay, { orientation: flip(cameraData.rotation) });
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Function Name: spectatorCameraOn()
|
||||
//
|
||||
// Relevant Variables:
|
||||
// isUpdateRenderWired: Bool storing whether or not the camera's update
|
||||
// function is wired.
|
||||
//
|
||||
// Arguments:
|
||||
// None
|
||||
//
|
||||
// Description:
|
||||
// Call this function to set up the spectator camera and
|
||||
// spawn the camera entity.
|
||||
//
|
||||
var isUpdateRenderWired = false;
|
||||
function spectatorCameraOn() {
|
||||
// Set the special texture size based on the window in which it will eventually be displayed.
|
||||
var size = Controller.getViewportDimensions(); // FIXME: Need a signal to hook into when the dimensions change.
|
||||
spectatorFrameRenderConfig.resetSize(size.x, size.y);
|
||||
spectatorFrameRenderConfig.enabled = beginSpectatorFrameRenderConfig.enabled = true;
|
||||
var cameraRotation = MyAvatar.orientation, cameraPosition = inFrontOf(2);
|
||||
Script.update.connect(updateRenderFromCamera);
|
||||
isUpdateRenderWired = true;
|
||||
camera = Entities.addEntity({
|
||||
type: 'Box',
|
||||
dimensions: { x: 0.4, y: 0.2, z: 0.4 },
|
||||
userData: '{"grabbableKey":{"grabbable":true}}',
|
||||
dynamic: cameraIsDynamic,
|
||||
color: { red: 255, green: 0, blue: 0 },
|
||||
name: 'SpectatorCamera',
|
||||
position: cameraPosition, // Put the camera in front of me so that I can find it.
|
||||
rotation: cameraRotation
|
||||
}, true);
|
||||
// Put an image3d overlay on the near face, as a viewFinder.
|
||||
viewFinderOverlay = Overlays.addOverlay("image3d", {
|
||||
url: "http://selfieFrame",
|
||||
//url: "http://1.bp.blogspot.com/-1GABEq__054/T03B00j_OII/AAAAAAAAAa8/jo55LcvEPHI/s1600/Winning.jpg",
|
||||
parentID: camera,
|
||||
alpha: 1,
|
||||
position: inFrontOf(-0.25, cameraPosition, cameraRotation),
|
||||
// FIXME: We shouldn't need the flip and the negative scale.
|
||||
// e.g., This isn't necessary using an ordinary .jpg with lettering, above.
|
||||
// Must be something about the view frustum projection matrix?
|
||||
// But don't go changing that in (c++ code) without getting all the way to a desktop display!
|
||||
orientation: flip(cameraRotation),
|
||||
scale: -0.35,
|
||||
});
|
||||
}
|
||||
|
||||
//
|
||||
// Function Name: spectatorCameraOff()
|
||||
//
|
||||
// Relevant Variables:
|
||||
// None
|
||||
//
|
||||
// Arguments:
|
||||
// None
|
||||
//
|
||||
// Description:
|
||||
// Call this function to shut down the spectator camera and
|
||||
// destroy the camera entity.
|
||||
//
|
||||
function spectatorCameraOff() {
|
||||
spectatorFrameRenderConfig.enabled = beginSpectatorFrameRenderConfig.enabled = false;
|
||||
if (isUpdateRenderWired) {
|
||||
Script.update.disconnect(updateRenderFromCamera);
|
||||
isUpdateRenderWired = false;
|
||||
}
|
||||
if (camera) {
|
||||
Entities.deleteEntity(camera);
|
||||
print("ZACH FOX GOODBYE");
|
||||
}
|
||||
if (viewFinderOverlay) {
|
||||
Overlays.deleteOverlay(viewFinderOverlay);
|
||||
}
|
||||
camera = false;
|
||||
viewFinderOverlay = false;
|
||||
}
|
||||
|
||||
//
|
||||
// Function Name: startup()
|
||||
//
|
||||
// Relevant Variables:
|
||||
// button: The tablet button.
|
||||
// buttonName: The name of the button.
|
||||
// tablet: The tablet instance to be modified.
|
||||
//
|
||||
// Arguments:
|
||||
// None
|
||||
//
|
||||
// Description:
|
||||
// startup() will be called when the script is loaded.
|
||||
//
|
||||
var button;
|
||||
var buttonName = "SPECTATOR";
|
||||
var tablet = null;
|
||||
function startup() {
|
||||
tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
|
||||
button = tablet.addButton({
|
||||
text: buttonName
|
||||
});
|
||||
button.clicked.connect(onTabletButtonClicked);
|
||||
tablet.screenChanged.connect(onTabletScreenChanged);
|
||||
Window.domainChanged.connect(spectatorCameraOff);
|
||||
viewFinderOverlay = false;
|
||||
camera = false;
|
||||
}
|
||||
|
||||
//
|
||||
// Function Name: wireEventBridge()
|
||||
//
|
||||
// Relevant Variables:
|
||||
// hasEventBridge: true/false depending on whether we've already connected the event bridge
|
||||
//
|
||||
// Arguments:
|
||||
// on: Enable or disable the event bridge
|
||||
//
|
||||
// Description:
|
||||
// Used to connect/disconnect the script's response to the tablet's "fromQml" signal.
|
||||
//
|
||||
var hasEventBridge = false;
|
||||
function wireEventBridge(on) {
|
||||
if (on) {
|
||||
if (!hasEventBridge) {
|
||||
tablet.fromQml.connect(fromQml);
|
||||
hasEventBridge = true;
|
||||
}
|
||||
} else {
|
||||
if (hasEventBridge) {
|
||||
tablet.fromQml.disconnect(fromQml);
|
||||
hasEventBridge = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Function Name: onTabletButtonClicked()
|
||||
//
|
||||
// Relevant Variables:
|
||||
// onSpectatorCameraScreen: true/false depending on whether we're looking at the spectator camera app
|
||||
// shouldActivateButton: true/false depending on whether we should show the button as white or gray the
|
||||
// next time we edit the button's properties
|
||||
//
|
||||
// Arguments:
|
||||
// None
|
||||
//
|
||||
// Description:
|
||||
// Fired when the Spectator Camera app button is pressed.
|
||||
//
|
||||
var onSpectatorCameraScreen = false;
|
||||
var shouldActivateButton = false;
|
||||
function onTabletButtonClicked() {
|
||||
if (onSpectatorCameraScreen) {
|
||||
// for toolbar-mode: go back to home screen, this will close the window.
|
||||
tablet.gotoHomeScreen();
|
||||
} else {
|
||||
shouldActivateButton = true;
|
||||
tablet.loadQMLSource("../SpectatorCamera.qml");
|
||||
onSpectatorCameraScreen = true;
|
||||
sendToQml({ method: 'updateSpectatorCameraCheckbox', params: !!camera });
|
||||
sendToQml({ method: 'updateMonitorShowsSwitch', params: !!Settings.getValue('spectatorCamera/monitorShowsCameraView', false) });
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Function Name: onTabletScreenChanged()
|
||||
//
|
||||
// Relevant Variables:
|
||||
// None
|
||||
//
|
||||
// Arguments:
|
||||
// type: "Home", "Web", "Menu", "QML", "Closed"
|
||||
// url: Only valid for Web and QML.
|
||||
//
|
||||
// Description:
|
||||
// Called when the TabletScriptingInterface::screenChanged() signal is emitted.
|
||||
//
|
||||
function onTabletScreenChanged(type, url) {
|
||||
wireEventBridge(shouldActivateButton);
|
||||
// for toolbar mode: change button to active when window is first openend, false otherwise.
|
||||
button.editProperties({ isActive: shouldActivateButton });
|
||||
shouldActivateButton = false;
|
||||
onSpectatorCameraScreen = false;
|
||||
}
|
||||
|
||||
//
|
||||
// Function Name: sendToQml()
|
||||
//
|
||||
// Relevant Variables:
|
||||
// None
|
||||
//
|
||||
// Arguments:
|
||||
// message: The message to send to the SpectatorCamera QML.
|
||||
// Messages are in format "{method, params}", like json-rpc. See also fromQml().
|
||||
//
|
||||
// Description:
|
||||
// Use this function to send a message to the QML (i.e. to change appearances).
|
||||
//
|
||||
function sendToQml(message) {
|
||||
tablet.sendToQml(message);
|
||||
}
|
||||
|
||||
//
|
||||
// Function Name: fromQml()
|
||||
//
|
||||
// Relevant Variables:
|
||||
// None
|
||||
//
|
||||
// Arguments:
|
||||
// message: The message sent from the SpectatorCamera QML.
|
||||
// Messages are in format "{method, params}", like json-rpc. See also sendToQml().
|
||||
//
|
||||
// Description:
|
||||
// Called when a message is received from SpectatorCamera.qml.
|
||||
//
|
||||
function fromQml(message) {
|
||||
switch (message.method) {
|
||||
case 'spectatorCameraOn':
|
||||
spectatorCameraOn();
|
||||
break;
|
||||
case 'spectatorCameraOff':
|
||||
spectatorCameraOff();
|
||||
break;
|
||||
case 'showHmdPreviewOnMonitor':
|
||||
print('FIXME: showHmdPreviewOnMonitor');
|
||||
Settings.setValue('spectatorCamera/monitorShowsCameraView', false);
|
||||
break;
|
||||
case 'showCameraViewOnMonitor':
|
||||
print('FIXME: showCameraViewOnMonitor');
|
||||
Settings.setValue('spectatorCamera/monitorShowsCameraView', true);
|
||||
break;
|
||||
case 'changeSwitchViewFromControllerPreference':
|
||||
print('FIXME: Preference is now: ' + message.params);
|
||||
break;
|
||||
default:
|
||||
print('Unrecognized message from SpectatorCamera.qml:', JSON.stringify(message));
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Function Name: shutdown()
|
||||
//
|
||||
// Relevant Variables:
|
||||
// None
|
||||
//
|
||||
// Arguments:
|
||||
// None
|
||||
//
|
||||
// Description:
|
||||
// shutdown() will be called when the script ends (i.e. is stopped).
|
||||
//
|
||||
function shutdown() {
|
||||
spectatorCameraOff();
|
||||
Window.domainChanged.disconnect(spectatorCameraOff);
|
||||
tablet.removeButton(button);
|
||||
button.clicked.disconnect(onTabletButtonClicked);
|
||||
tablet.screenChanged.disconnect(onTabletScreenChanged);
|
||||
}
|
||||
|
||||
//
|
||||
// These functions will be called when the script is loaded.
|
||||
//
|
||||
startup();
|
||||
Script.scriptEnding.connect(shutdown);
|
||||
|
||||
}()); // END LOCAL_SCOPE
|
Loading…
Reference in a new issue