Merge pull request #10002 from fayeli/tablet-photobooth

Photobooth Improvements: Using Tablet & Ability to Rotate Model
This commit is contained in:
Seth Alves 2017-04-03 16:05:13 -07:00 committed by GitHub
commit d1827cf12c
4 changed files with 340 additions and 334 deletions

0
interface/resources/fonts/hifi-glyphs.ttf Executable file → Normal file
View file

View file

@ -2,166 +2,151 @@
<head>
<title>Photo Booth</title>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<link rel="stylesheet" type="text/css" href="../../../../../system/html/css/edit-style.css">
<script type="text/javascript" src="qrc:///qtwebchannel/qwebchannel.js"></script>
<script>
var EventBridge;
var openEventBridge = function (callback) {
var WebChannel = new QWebChannel(qt.webChannelTransport, function (channel) {
EventBridge = WebChannel.objects.eventBridgeWrapper.eventBridge;
callback();
});
};
var emit = function (eventType, data) {
data = data || {};
data.type = eventType;
EventBridge.emitWebEvent(JSON.stringify(data));
};
function loaded () {
openEventBridge(function () {
emit("onLoad", {value: "faye"});
var elModelURL = document.getElementById("model-url");
var elReloadModelButton = document.getElementById("reload-model-button");
var elCamera = document.getElementById("property-camera");
//var elLightingPreset = document.getElementById("property-lighting-preset");
var elPictureButton = document.getElementById("picture-button");
elReloadModelButton.addEventListener('click', function() {
emit("onClickReloadModelButton", {value: elModelURL.value});
});
elCamera.addEventListener('change', function() {
emit("onSelectCamera", {value: this.value});
});
// elLightingPreset.addEventListener('change', function() {
// emit("onSelectLightingPreset", {value: "faye"});
// });
elPictureButton.addEventListener('click', function() {
emit("onClickPictureButton", {value: "faye"});
});
});
// Drop downs
function setDropdownText(dropdown) {
var lis = dropdown.parentNode.getElementsByTagName("li");
var text = "";
for (var i = 0; i < lis.length; i++) {
if (lis[i].getAttribute("value") === dropdown.value) {
text = lis[i].textContent;
}
}
dropdown.firstChild.textContent = text;
}
function toggleDropdown(event) {
var element = event.target;
if (element.nodeName !== "DT") {
element = element.parentNode;
}
element = element.parentNode;
var isDropped = element.getAttribute("dropped");
element.setAttribute("dropped", isDropped !== "true" ? "true" : "false");
}
function setDropdownValue(event) {
var dt = event.target.parentNode.parentNode.previousSibling;
dt.value = event.target.getAttribute("value");
dt.firstChild.textContent = event.target.textContent;
dt.parentNode.setAttribute("dropped", "false");
var evt = document.createEvent("HTMLEvents");
evt.initEvent("change", true, true);
dt.dispatchEvent(evt);
}
var elDropdowns = document.getElementsByTagName("select");
for (var i = 0; i < elDropdowns.length; i++) {
var options = elDropdowns[i].getElementsByTagName("option");
var selectedOption = 0;
for (var j = 0; j < options.length; j++) {
if (options[j].getAttribute("selected") === "selected") {
selectedOption = j;
}
}
var div = elDropdowns[i].parentNode;
var dl = document.createElement("dl");
div.appendChild(dl);
var dt = document.createElement("dt");
dt.name = elDropdowns[i].name;
dt.id = elDropdowns[i].id;
dt.addEventListener("click", toggleDropdown, true);
dl.appendChild(dt);
var span = document.createElement("span");
span.setAttribute("value", options[selectedOption].value);
span.textContent = options[selectedOption].firstChild.textContent;
dt.appendChild(span);
var span = document.createElement("span");
span.textContent = "5"; // caratDn
dt.appendChild(span);
var dd = document.createElement("dd");
dl.appendChild(dd);
var ul = document.createElement("ul");
dd.appendChild(ul);
for (var j = 0; j < options.length; j++) {
var li = document.createElement("li");
li.setAttribute("value", options[j].value);
li.textContent = options[j].firstChild.textContent;
li.addEventListener("click", setDropdownValue);
ul.appendChild(li);
}
}
elDropdowns = document.getElementsByTagName("select");
while (elDropdowns.length > 0) {
var el = elDropdowns[0];
el.parentNode.removeChild(el);
elDropdowns = document.getElementsByTagName("select");
}
}
</script>
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-slider/9.7.2/css/bootstrap-slider.min.css">
<style>
</style>
body {
margin: 0;
padding: 0;
width: 100%;
color: white;
}
.top-bar {
height: 90px;
background: linear-gradient(#2b2b2b, #1e1e1e);
font-family: Raleway-Bold;
padding-left: 30px;
padding-right: 30px;
display: flex;
align-items: center;
position: fixed;
width: 480px;
top: 0;
z-index: 1;
font-size: 16px;
}
.content {
margin-top: 90px;
padding: 30px;
}
.slider {
margin-left: 70px;
}
#camera-toggle {
font-family: Raleway-Bold;
font-size: 13px;
text-transform: uppercase;
vertical-align: top;
height: 28px;
min-width: 120px;
padding: 0px 18px;
margin-right: 0px;
border-radius: 5px;
border: none;
color: #121212;
background-color: #afafaf;
background: linear-gradient(#fff 20%, #afafaf 100%);
cursor: pointer;
}
.dropdown li {
background-color: #ffffff;
}
</style>
</head>
<body onload="loaded()">
<div id="properties-list">
<div class="property url refresh">
<label>Model URL</label>
<input type="text" id="model-url"></input>
<input type="button" id="reload-model-button" class="glyph" value="F">
</div>
<!--
<div class="property dropdown">
<label>Lighting Preset</label>
<select id="property-lighting-preset">
<option>Default Lighting</option>
<option>Sam's Cool Light</option>
<option>Alan's Light Magic</option>
</select>
</div>
-->
<div class="property dropdown">
<label>Camera</label>
<select id="property-camera">
<option>First Person Camera</option>
<option>Main Camera</option>
<option>Left Camera</option>
<option>Right Camera</option>
</select>
</div>
<div class="property">
<input id="picture-button" type="button" class="blue" value="Take Picture">
<body>
<div class="top-bar">
<div>Photobooth</div>
</div>
<div class="content">
<div id="properties-list">
<div class="property url refresh">
<label>Model URL</label>
<input type="text" id="model-url"></input>
<input type="button" id="reload-model-button" class="glyph" value="F">
</div>
<div class="property">
<label>Rotate Model</label>
<input
id="rotate-slider"
type="text"
data-provide="slider"
data-slider-ticks="[-180, 0, 180]"
data-slider-ticks-labels='["clockwise", "centre", "anti-clockwise"]'
data-slider-min="-180"
data-slider-max="180"
data-slider-step="1"
data-slider-value="0"
data-slider-tooltip="show"
>
</div>
<div class="property">
<label>Camera</label>
<div class="dropdown">
<button id="camera-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
First Person Camera
<span class="glyphicon glyphicon-menu-down"></span>
</button>
<ul class="dropdown-menu" aria-labelledby="dropdownMenu1">
<li>First Person Camera</li>
<li>Main Camera</li>
<li>Left Camera</li>
<li>Right Camera</li>
</ul>
</div>
</div>
<div class="property">
<input id="picture-button" type="button" class="blue" value="Take Picture">
</div>
</div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-slider/9.7.2/bootstrap-slider.min.js"></script>
<script>
// Helper function to emit web events to photoboothApp.js
function emit(eventType, eventData) {
var eventObject = {
"app": "photobooth",
"type": eventType,
"data": eventData
};
EventBridge.emitWebEvent(JSON.stringify(eventObject));
}
$(document).ready(function() {
// Send a ready event to hifi
emit("ready", null);
// Send an event when camera selection changes
$(".dropdown-menu li").click(function() {
console.log("clicked " + this.textContent);
$("#camera-toggle").text(this.textContent + " ");
$("#camera-toggle").append("<span class='glyphicon glyphicon-menu-down'></span>");
emit("onSelectCamera", {value: this.textContent});
});
// Send an event to hifi to trigger snapshot
$("#picture-button").click(function() {
emit("onClickPictureButton", null);
});
// Send an event to hifi for loading the given model URL
$("#reload-model-button").click(function() {
emit("onClickReloadModelButton", {value: $("#model-url").val()});
});
$("#rotate-slider").slider().on("slide", function(e){
console.log("slided " + e.value);
emit("onRotateSlider", {value: e.value});
});
});
</script>
</body>
</html>

View file

@ -1,178 +0,0 @@
(function () {
var SNAPSHOT_DELAY = 500; // 500ms
var PHOTOBOOTH_WINDOW_HTML_URL = Script.resolvePath("./html/photobooth.html");
var PHOTOBOOTH_SETUP_JSON_URL = Script.resolvePath("./photoboothSetup.json");
var toolbar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system");
var MODEL_BOUNDING_BOX_DIMENSIONS = {x: 1.0174,y: 1.1925,z: 1.0165};
var PhotoBooth = {};
PhotoBooth.init = function () {
var success = Clipboard.importEntities(PHOTOBOOTH_SETUP_JSON_URL);
var forwardFactor = 10;
var forwardUnitVector = Vec3.normalize(Quat.getForward(MyAvatar.orientation));
var forwardOffset = Vec3.multiply(forwardUnitVector,forwardFactor);
var rightFactor = 3;
// TODO: rightUnitVec is unused and spawnLocation declaration is incorrect
var rightUnitVec = Vec3.normalize(Quat.getRight(MyAvatar.orientation));
var spawnLocation = Vec3.sum(Vec3.sum(MyAvatar.position,forwardOffset),rightFactor);
if (success) {
this.pastedEntityIDs = Clipboard.pasteEntities(spawnLocation);
this.processPastedEntities();
}
};
PhotoBooth.processPastedEntities = function () {
var cameraResults = {};
var modelResult;
var modelPos;
this.pastedEntityIDs.forEach(function(id) {
var props = Entities.getEntityProperties(id);
var parts = props["name"].split(":");
if (parts[0] === "Photo Booth Camera") {
cameraResults[parts[1]] = id;
}
if (parts[0] === "Photo Booth Model") {
modelResult = id;
modelPos = props.position;
}
});
print(JSON.stringify(cameraResults));
print(JSON.stringify(modelResult));
this.cameraEntities = cameraResults;
this.modelEntityID = modelResult;
this.centrePos = modelPos;
};
// replace the model in scene with new model
PhotoBooth.changeModel = function (newModelURL) {
// deletes old model
Entities.deleteEntity(this.modelEntityID);
// create new model at centre of the photobooth
var newProps = {
name: "Photo Booth Model",
type: "Model",
modelURL: newModelURL,
position: this.centrePos
};
var newModelEntityID = Entities.addEntity(newProps);
// scale model dimensions to fit in bounding box
var scaleModel = function () {
newProps = Entities.getEntityProperties(newModelEntityID);
var myDimensions = newProps.dimensions;
print("myDimensions: " + JSON.stringify(myDimensions));
var k;
if (myDimensions.x > MODEL_BOUNDING_BOX_DIMENSIONS.x) {
k = MODEL_BOUNDING_BOX_DIMENSIONS.x / myDimensions.x;
myDimensions = Vec3.multiply(k, myDimensions);
}
if (myDimensions.y > MODEL_BOUNDING_BOX_DIMENSIONS.y) {
k = MODEL_BOUNDING_BOX_DIMENSIONS.y / myDimensions.y;
myDimensions = Vec3.multiply(k, myDimensions);
}
if (myDimensions.z > MODEL_BOUNDING_BOX_DIMENSIONS.z) {
k = MODEL_BOUNDING_BOX_DIMENSIONS.z / myDimensions.z;
myDimensions = Vec3.multiply(k, myDimensions);
}
// position the new model on the table
var y_offset = (MODEL_BOUNDING_BOX_DIMENSIONS.y - myDimensions.y) / 2;
var myPosition = Vec3.sum(newProps.position, {x:0, y:-y_offset, z:0});
Entities.editEntity(newModelEntityID,{position: myPosition, dimensions: myDimensions});
};
// add a delay before scaling to make sure the entity server have gotten the right model dimensions
Script.setTimeout(function () {
scaleModel();
}, 400);
this.modelEntityID = newModelEntityID;
};
PhotoBooth.destroy = function () {
this.pastedEntityIDs.forEach(function(id) {
Entities.deleteEntity(id);
});
Entities.deleteEntity(this.modelEntityID);
};
var main = function () {
PhotoBooth.init();
var photoboothWindowListener = {};
photoboothWindowListener.onLoad = function (event) {
print("loaded" + event.value);
if (!event.hasOwnProperty("value")){
return;
}
};
photoboothWindowListener.onSelectCamera = function (event) {
print("selected camera " + event.value);
if (!event.hasOwnProperty("value")){
return;
}
if (event.value === "First Person Camera") {
Camera.mode = "first person";
} else {
Camera.mode = "entity";
var cameraID = PhotoBooth.cameraEntities[event.value];
Camera.setCameraEntity(cameraID);
}
};
photoboothWindowListener.onSelectLightingPreset = function (event) {
print("selected lighting preset" + event.value);
if (!event.hasOwnProperty("value")){
return;
}
};
photoboothWindowListener.onClickPictureButton = function (event) {
print("clicked picture button");
// hide HUD tool bar
toolbar.writeProperty("visible", false);
// hide Overlays (such as Running Scripts or other Dialog UI)
Menu.setIsOptionChecked("Overlays", false);
// hide mouse cursor
Reticle.visible = false;
// giving a delay here before snapshotting so that there is time to hide toolbar and other UIs
// void WindowScriptingInterface::takeSnapshot(bool notify, bool includeAnimated, float aspectRatio)
Script.setTimeout(function () {
Window.takeSnapshot(false, false, 1.91);
// show hidden items after snapshot is taken
toolbar.writeProperty("visible", true);
Menu.setIsOptionChecked("Overlays", true);
// unknown issue: somehow we don't need to reset cursor to visible in script and the mouse still returns after snapshot
// Reticle.visible = true;
}, SNAPSHOT_DELAY);
};
photoboothWindowListener.onClickReloadModelButton = function (event) {
print("clicked reload model button " + event.value);
PhotoBooth.changeModel(event.value);
};
var photoboothWindow = new OverlayWebWindow({
title: 'Photo Booth',
source: PHOTOBOOTH_WINDOW_HTML_URL,
width: 450,
height: 450,
visible: true
});
photoboothWindow.webEventReceived.connect(function (data) {
var event = JSON.parse(data);
if (photoboothWindowListener[event.type]) {
photoboothWindowListener[event.type](event);
}
});
};
main();
function cleanup() {
Camera.mode = "first person";
PhotoBooth.destroy();
}
Script.scriptEnding.connect(cleanup);
}());

View file

@ -0,0 +1,199 @@
//
// photobooth.js
// scripts/developer/utilities/render/photobooth
//
// Created by Faye Li on 2 Nov 2016
// 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
//
/* globals Tablet, Toolbars, Script, HMD, Controller, Menu */
(function () {
var SNAPSHOT_DELAY = 500; // 500ms
var PHOTOBOOTH_WINDOW_HTML_URL = Script.resolvePath("./html/photobooth.html");
var PHOTOBOOTH_SETUP_JSON_URL = Script.resolvePath("./photoboothSetup.json");
var MODEL_BOUNDING_BOX_DIMENSIONS = {x: 1.0174,y: 1.1925,z: 1.0165};
var PhotoBooth = {};
var photoboothCreated = false;
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
var button = tablet.addButton({
icon: "icons/tablet-icons/snap-i.svg",
text: "PHOTOBOOTH"
});
function onClicked() {
if (photoboothCreated) {
tablet.gotoHomeScreen();
PhotoBooth.destroy();
} else {
tablet.gotoWebScreen(PHOTOBOOTH_WINDOW_HTML_URL);
PhotoBooth.init();
}
}
function onScreenChanged() {
if (photoboothCreated) {
tablet.gotoHomeScreen();
PhotoBooth.destroy();
button.editProperties({isActive: false});
} else {
button.editProperties({isActive: true});
}
}
tablet.screenChanged.connect(onScreenChanged);
button.clicked.connect(onClicked);
tablet.webEventReceived.connect(onWebEventReceived);
function onWebEventReceived(event) {
print("photobooth.js received a web event:" + event);
// Converts the event to a JavasScript Object
if (typeof event === "string") {
event = JSON.parse(event);
}
if (event.app === "photobooth") {
if (event.type === "onClickPictureButton") {
print("clicked picture button");
// // hide HUD tool bar
// toolbar.writeProperty("visible", false);
// hide Overlays (such as Running Scripts or other Dialog UI)
Menu.setIsOptionChecked("Overlays", false);
// hide mouse cursor
Reticle.visible = false;
// hide tablet
HMD.closeTablet();
// // giving a delay here before snapshotting so that there is time to hide other UIs
// void WindowScriptingInterface::takeSnapshot(bool notify, bool includeAnimated, float aspectRatio)
Script.setTimeout(function () {
Window.takeSnapshot(false, false, 1.91);
// show hidden items after snapshot is taken
// issue: currently there's no way to show tablet via a script command. user will have to manually open tablet again
// issue: somehow we don't need to reset cursor to visible in script and the mouse still returns after snapshot
// Reticle.visible = true;
// toolbar.writeProperty("visible", true);
Menu.setIsOptionChecked("Overlays", true);
}, SNAPSHOT_DELAY);
} else if (event.type === "onClickReloadModelButton") {
print("clicked reload model button " + event.data.value);
PhotoBooth.changeModel(event.data.value);
} else if (event.type === "onSelectCamera") {
print("selected camera " + event.data.value);
if (!event.data.hasOwnProperty("value")){
return;
}
if (event.data.value === "First Person Camera") {
Camera.mode = "first person";
} else {
Camera.mode = "entity";
var cameraID = PhotoBooth.cameraEntities[event.data.value];
Camera.setCameraEntity(cameraID);
}
} else if (event.type === "onRotateSlider") {
var props = {};
props.rotation = Quat.fromPitchYawRollDegrees(0, event.data.value, 0);
Entities.editEntity(PhotoBooth.modelEntityID, props);
}
}
}
PhotoBooth.init = function () {
photoboothCreated = true;
var success = Clipboard.importEntities(PHOTOBOOTH_SETUP_JSON_URL);
var frontFactor = 10;
// getForward is preffered as getFront function is deprecated
var frontUnitVec = Vec3.normalize(Quat.getFront(MyAvatar.orientation));
var frontOffset = Vec3.multiply(frontUnitVec,frontFactor);
var upFactor = 3;
var upUnitVec = Vec3.normalize(Quat.getUp(MyAvatar.orientation));
var upOffset = Vec3.multiply(upUnitVec, upFactor);
var spawnLocation = Vec3.sum(MyAvatar.position,frontOffset);
spawnLocation = Vec3.sum(spawnLocation, upOffset);
if (success) {
this.pastedEntityIDs = Clipboard.pasteEntities(spawnLocation);
this.processPastedEntities();
}
};
PhotoBooth.processPastedEntities = function () {
var cameraResults = {};
var modelResult;
var modelPos;
this.pastedEntityIDs.forEach(function(id) {
var props = Entities.getEntityProperties(id);
var parts = props["name"].split(":");
if (parts[0] === "Photo Booth Camera") {
cameraResults[parts[1]] = id;
}
if (parts[0] === "Photo Booth Model") {
modelResult = id;
modelPos = props.position;
}
});
print(JSON.stringify(cameraResults));
print(JSON.stringify(modelResult));
this.cameraEntities = cameraResults;
this.modelEntityID = modelResult;
this.centrePos = modelPos;
};
// replace the model in scene with new model
PhotoBooth.changeModel = function (newModelURL) {
// deletes old model
Entities.deleteEntity(this.modelEntityID);
// create new model at centre of the photobooth
var newProps = {
name: "Photo Booth Model",
type: "Model",
modelURL: newModelURL,
position: this.centrePos
};
var newModelEntityID = Entities.addEntity(newProps);
// scale model dimensions to fit in bounding box
var scaleModel = function () {
newProps = Entities.getEntityProperties(newModelEntityID);
var myDimensions = newProps.dimensions;
print("myDimensions: " + JSON.stringify(myDimensions));
var k;
if (myDimensions.x > MODEL_BOUNDING_BOX_DIMENSIONS.x) {
k = MODEL_BOUNDING_BOX_DIMENSIONS.x / myDimensions.x;
myDimensions = Vec3.multiply(k, myDimensions);
}
if (myDimensions.y > MODEL_BOUNDING_BOX_DIMENSIONS.y) {
k = MODEL_BOUNDING_BOX_DIMENSIONS.y / myDimensions.y;
myDimensions = Vec3.multiply(k, myDimensions);
}
if (myDimensions.z > MODEL_BOUNDING_BOX_DIMENSIONS.z) {
k = MODEL_BOUNDING_BOX_DIMENSIONS.z / myDimensions.z;
myDimensions = Vec3.multiply(k, myDimensions);
}
// position the new model on the table
var y_offset = (MODEL_BOUNDING_BOX_DIMENSIONS.y - myDimensions.y) / 2;
var myPosition = Vec3.sum(newProps.position, {x:0, y:-y_offset, z:0});
Entities.editEntity(newModelEntityID,{position: myPosition, dimensions: myDimensions});
};
// add a delay before scaling to make sure the entity server have gotten the right model dimensions
Script.setTimeout(function () {
scaleModel();
}, 400);
this.modelEntityID = newModelEntityID;
};
PhotoBooth.destroy = function () {
this.pastedEntityIDs.forEach(function(id) {
Entities.deleteEntity(id);
});
Entities.deleteEntity(this.modelEntityID);
photoboothCreated = false;
Camera.mode = "first person";
};
function cleanup() {
tablet.removeButton(button);
PhotoBooth.destroy();
}
Script.scriptEnding.connect(cleanup);
}());