"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); }); })();