313 lines
11 KiB
JavaScript
313 lines
11 KiB
JavaScript
"use strict";
|
|
|
|
// foodDispenserApp.js
|
|
//
|
|
// Created by Thijs Wenker on 5/1/17.
|
|
// Copyright 2017 High Fidelity, Inc.
|
|
//
|
|
// Hand crafted tablet app for the show Eat My Way Through VR
|
|
//
|
|
// Distributed under the Apache License, Version 2.0.
|
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
|
//
|
|
|
|
(function() {
|
|
var APP_PAGE = 'foodDispenserApp.html';
|
|
var APP_BUTTON_NAME = 'FOOD';
|
|
var APP_BUTTON_SORT_ORDER = -1;
|
|
var FOOD_LIFETIME = 300; // seconds
|
|
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
|
|
var isOnAppPage = false;
|
|
var isTabletWebHandlerConnected = false;
|
|
|
|
var FULL_APP_URL = Script.resolvePath(APP_PAGE);
|
|
var ZERO_UUID = '{00000000-0000-0000-0000-000000000000}';
|
|
var SANETIZE_PROPERTIES = ['childEntities', 'parentID', 'id'];
|
|
|
|
|
|
var EDIBLE_SCRIPT = 'https://hifi-content.s3.amazonaws.com/liv/dev/ColorChangeWand/Edible.js';
|
|
// TODO: make this readable:
|
|
var COLLISION_MASK = 7;
|
|
var dispensers = [];
|
|
|
|
var debug = function(message) {
|
|
print(message);
|
|
};
|
|
|
|
function entityListToTree(entitiesList) {
|
|
function entityListToTreeRecursive(properties) {
|
|
properties.childEntities = [];
|
|
entitiesList.forEach(function(entityProperties) {
|
|
if (properties.id === entityProperties.parentID) {
|
|
properties.childEntities.push(entityListToTreeRecursive(entityProperties));
|
|
}
|
|
});
|
|
return properties;
|
|
}
|
|
var entityTree = [];
|
|
entitiesList.forEach(function(entityProperties) {
|
|
if (entityProperties.parentID === undefined || entityProperties.parentID === ZERO_UUID) {
|
|
entityTree.push(entityListToTreeRecursive(entityProperties));
|
|
}
|
|
});
|
|
return entityTree;
|
|
}
|
|
|
|
function importEntitiesJSON(importLink, parentProperties, overrideProperties) {
|
|
if (parentProperties === undefined) {
|
|
parentProperties = {};
|
|
}
|
|
if (overrideProperties !== undefined) {
|
|
parentProperties.overrideProperties = overrideProperties;
|
|
}
|
|
try {
|
|
parentProperties.childEntities = entityListToTree(Script.require(importLink).Entities);
|
|
return parentProperties;
|
|
} catch (e) {
|
|
debug('Failed importing entities JSON because: ' + JSON.stringify(e));
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// Creates an entity and returns a mixed object of the creation properties and the assigned entityID
|
|
var createEntity = function(entityProperties, parent, overrideProperties) {
|
|
// JSON.stringify -> JSON.parse trick to create a fresh copy of JSON data
|
|
var newEntityProperties = JSON.parse(JSON.stringify(entityProperties));
|
|
if (overrideProperties !== undefined) {
|
|
Object.keys(overrideProperties).forEach(function(key) {
|
|
newEntityProperties[key] = overrideProperties[key];
|
|
});
|
|
}
|
|
if (parent.rotation !== undefined) {
|
|
if (newEntityProperties.rotation !== undefined) {
|
|
newEntityProperties.rotation = Quat.multiply(parent.rotation, newEntityProperties.rotation);
|
|
} else {
|
|
newEntityProperties.rotation = parent.rotation;
|
|
}
|
|
}
|
|
if (parent.position !== undefined) {
|
|
var localPosition = (parent.rotation !== undefined) ?
|
|
Vec3.multiplyQbyV(parent.rotation, newEntityProperties.position) : newEntityProperties.position;
|
|
newEntityProperties.position = Vec3.sum(localPosition, parent.position);
|
|
}
|
|
if (parent.id !== undefined) {
|
|
newEntityProperties.parentID = parent.id;
|
|
}
|
|
newEntityProperties.id = Entities.addEntity(newEntityProperties);
|
|
return newEntityProperties;
|
|
};
|
|
|
|
var createEntitiesFromTree = function(entityTree, parent, overrideProperties) {
|
|
if (parent === undefined) {
|
|
parent = {};
|
|
}
|
|
if (parent.overrideProperties !== undefined) {
|
|
overrideProperties = parent.overrideProperties;
|
|
}
|
|
var createdTree = [];
|
|
entityTree.forEach(function(entityProperties) {
|
|
var sanetizedProperties = {};
|
|
Object.keys(entityProperties).forEach(function(propertyKey) {
|
|
if (!entityProperties.hasOwnProperty(propertyKey) || SANETIZE_PROPERTIES.indexOf(propertyKey) !== -1) {
|
|
return true;
|
|
}
|
|
sanetizedProperties[propertyKey] = entityProperties[propertyKey];
|
|
});
|
|
|
|
// Allow for non-entity parent objects, this allows us to offset groups of entities to a specific position/rotation
|
|
var parentProperties = sanetizedProperties;
|
|
if (entityProperties.type !== undefined) {
|
|
parentProperties = createEntity(sanetizedProperties, parent, overrideProperties);
|
|
}
|
|
if (entityProperties.childEntities !== undefined) {
|
|
parentProperties.childEntities =
|
|
createEntitiesFromTree(entityProperties.childEntities, parentProperties, overrideProperties);
|
|
}
|
|
createdTree.push(parentProperties);
|
|
});
|
|
return createdTree;
|
|
};
|
|
|
|
function getDispenserByID(id) {
|
|
var result = null;
|
|
dispensers.forEach(function(dispenser) {
|
|
if (id === dispenser.id) {
|
|
result = dispenser;
|
|
}
|
|
});
|
|
return result;
|
|
}
|
|
|
|
function sendJSONDataToPage(data) {
|
|
tablet.emitScriptEvent(JSON.stringify(data));
|
|
}
|
|
|
|
function refreshDispensers() {
|
|
var newDispensers = [];
|
|
Entities.findEntities(MyAvatar.position, 32000).forEach(function(entityID) {
|
|
try {
|
|
var userData = Entities.getEntityProperties(entityID, 'userData').userData;
|
|
if (userData === undefined || userData === '') {
|
|
return;
|
|
}
|
|
var parsedUserData = JSON.parse(userData);
|
|
if (parsedUserData.foodDispenser !== undefined) {
|
|
var newData = {
|
|
id: entityID,
|
|
name: parsedUserData.foodDispenser.name
|
|
};
|
|
var oldData = getDispenserByID(entityID);
|
|
if (oldData !== null) {
|
|
if (oldData.filepath !== undefined) {
|
|
newData.filepath = oldData.filepath;
|
|
newData.filename = oldData.filename;
|
|
}
|
|
}
|
|
newDispensers.push(newData);
|
|
}
|
|
} catch (e) {
|
|
// e
|
|
}
|
|
});
|
|
dispensers = newDispensers;
|
|
dispensers.sort(function(a, b) {
|
|
if (a.name < b.name) {
|
|
return -1;
|
|
} else if (a.name > b.name) {
|
|
return 1;
|
|
}
|
|
return 0;
|
|
});
|
|
|
|
sendJSONDataToPage({
|
|
action: 'dispensersUpdate',
|
|
dispensers: dispensers
|
|
});
|
|
}
|
|
|
|
function launchDispenser(id) {
|
|
var dispenser = getDispenserByID(id);
|
|
if (dispenser !== null && dispenser.filepath !== undefined) {
|
|
var position = Entities.getEntityProperties(id, 'position').position;
|
|
var importEntities = [
|
|
importEntitiesJSON(dispenser.filepath, {}, {
|
|
position: position,
|
|
lifetime: FOOD_LIFETIME,
|
|
script: EDIBLE_SCRIPT,
|
|
collisionMask: COLLISION_MASK,
|
|
friction: 0,
|
|
gravity: {x: 0, y: -2, z: 0},
|
|
velocity: {x: 0, y: -2, z: 0},
|
|
dynamic: true,
|
|
userData: JSON.stringify({
|
|
grabbableKey: {
|
|
grabbable: true
|
|
}
|
|
}),
|
|
linearDamping: 0.1
|
|
})
|
|
];
|
|
importEntities.forEach(function(entityProperties) {
|
|
entityProperties.childEntities.forEach(function(subProperties) {
|
|
if (subProperties.shapeType === 'static-mesh') {
|
|
subProperties.shapeType = 'simple-hull';
|
|
print('is now: ' + subProperties.shapeType);
|
|
}
|
|
});
|
|
});
|
|
//print(JSON.stringify(importEntities));
|
|
createEntitiesFromTree(importEntities);
|
|
}
|
|
}
|
|
|
|
function launchDispensers() {
|
|
dispensers.forEach(function(dispenser) {
|
|
launchDispenser(dispenser.id);
|
|
});
|
|
}
|
|
|
|
function onWebEventReceived(event) {
|
|
var data = JSON.parse(event);
|
|
var action = data.action;
|
|
|
|
switch (action) {
|
|
case 'refresh':
|
|
refreshDispensers();
|
|
break;
|
|
case 'setDispenserJSON':
|
|
var id = data.id;
|
|
var dispenser = getDispenserByID(id);
|
|
if (dispenser !== null) {
|
|
var newPath = Window.browse('Import Entities', '', '*.json');
|
|
if (newPath !== null) {
|
|
dispenser.filepath = newPath;
|
|
dispenser.filename = newPath.substring(newPath.lastIndexOf('/') + 1);
|
|
}
|
|
|
|
}
|
|
refreshDispensers();
|
|
break;
|
|
case 'launch':
|
|
launchDispensers();
|
|
break;
|
|
default:
|
|
print('Got unknown action: ' + action + ' with event: ' + event);
|
|
}
|
|
}
|
|
|
|
function connectAppBridge() {
|
|
if (isTabletWebHandlerConnected) {
|
|
return;
|
|
}
|
|
try {
|
|
tablet.webEventReceived.connect(onWebEventReceived);
|
|
} catch (e) {
|
|
// e
|
|
return;
|
|
}
|
|
isTabletWebHandlerConnected = true;
|
|
}
|
|
|
|
function disconnectAppBridge() {
|
|
if (!isTabletWebHandlerConnected) {
|
|
return;
|
|
}
|
|
try {
|
|
tablet.webEventReceived.disconnect(onWebEventReceived);
|
|
} catch (e) {
|
|
// e
|
|
return;
|
|
}
|
|
isTabletWebHandlerConnected = false;
|
|
}
|
|
|
|
var tabletAppButton = tablet.addButton({
|
|
icon: "icons/tablet-icons/menu-i.svg",
|
|
activeIcon: "icons/tablet-icons/menu-a.svg",
|
|
text: APP_BUTTON_NAME,
|
|
sortOrder: APP_BUTTON_SORT_ORDER
|
|
});
|
|
|
|
tabletAppButton.clicked.connect(function() {
|
|
// Remove any old app bridge connections
|
|
disconnectAppBridge();
|
|
isOnAppPage = false;
|
|
tablet.gotoWebScreen(FULL_APP_URL);
|
|
});
|
|
|
|
tablet.screenChanged.connect(function(type, url) {
|
|
if (type === 'Web' && url === FULL_APP_URL && !isOnAppPage) {
|
|
isOnAppPage = true;
|
|
connectAppBridge();
|
|
} else if (isOnAppPage) {
|
|
isOnAppPage = false;
|
|
disconnectAppBridge();
|
|
}
|
|
});
|
|
|
|
// Clean-up
|
|
Script.scriptEnding.connect(function() {
|
|
disconnectAppBridge();
|
|
tablet.removeButton(tabletAppButton);
|
|
});
|
|
})();
|