/*! setPiecesDirector.js Created by David Rowe on 8 Aug 2019. Copyright 2019 David Rowe. Distributed under the Apache License, Version 2.0. See: http://www.apache.org/licenses/LICENSE-2.0.html */ /* global Entities, OverlayWebWindow Vec3 */ (function () { "use strict"; var APP_ICON_INACTIVE = Script.resolvePath("./assets/set-pieces-i.svg"), APP_ICON_ACTIVE = Script.resolvePath("./assets/set-pieces-a.svg"), APP_NAME = "SET PIECES", HTML_FILE = "html/setPiecesDirector.html", setPieces, // JSON data plus runtime augmentations. /* Note: When out, all entities specified in the JSON are set to dynamic=false and collisionless=false, unless these properties are set to "true" in the JSON. Note: When in, all entities specified in the JSON are set to dynamic=false and collisionless=true so as to reduce the likelihood of their causing problems. Note: For a group of entities that are parented to each other, you need only include the root entity in the JSON. Note: In a group of entities that are parented to each other, the child entities currently do NOT have their dynamic and collisionless properties changed or set when in or out. Note: If you edit the JSON while Interface is running, you need to reload all scripts ("RELOAD ALL" button in the Running Scripts dialog) in order to use the updated JSON. */ setPiecesIndexes = {}, // Entity ID to JSON index. movingSetPieces = [], // Indexes in setPieces of those that are currently moving. Dialog, tablet = null, button = null; Dialog = (function () { var WINDOW_TITLE = "Set Pieces Director", WINDOW_DEFAULT_WIDTH = 400, WINDOW_DEFAULT_HEIGHT = 600, // Event bridge commands. ADD_GROUP = "add-group", // Main script ==> Dialog SET_BUTTON = "set-button", // Main script ==> Dialog TOGGLE_BUTTON = "toggle-button", // Main script <== Dialog SET_COUNTDOWN = "set-countdowm", // Main script ==> Dialog dialogMessageCallback, windowClosedCallback, window, isVisible = false; function getVisible() { return isVisible; } function setVisible(visible) { window.setVisible(visible); if (visible) { window.raise(); } isVisible = visible; } function toggleVisible() { setVisible(!isVisible); } function onWindowClosed() { isVisible = false; windowClosedCallback(); } function addGroup(id, group) { window.emitScriptEvent(JSON.stringify({ command: ADD_GROUP, id: id, group: group })); } function setButtonOut(id, isOut) { window.emitScriptEvent(JSON.stringify({ command: SET_BUTTON, id: id, isOut: isOut })); } function setCountDownValue(id, value) { window.emitScriptEvent(JSON.stringify({ command: SET_COUNTDOWN, id: id, value: value })); } function onWebEventReceived(message) { var data; data = JSON.parse(message); switch (data.command) { case TOGGLE_BUTTON: dialogMessageCallback(data); break; default: print("ERROR: Unexpected message! - " + message); } } function setUp(theDialogMessageCallback, theWindowClosedCallback) { var html, size; dialogMessageCallback = theDialogMessageCallback; windowClosedCallback = theWindowClosedCallback; html = Script.resolvePath(HTML_FILE); size = { width: WINDOW_DEFAULT_WIDTH, height: WINDOW_DEFAULT_HEIGHT }; window = new OverlayWebWindow(WINDOW_TITLE, html, size.width, size.height, false); window.setVisible(false); window.webEventReceived.connect(onWebEventReceived); window.closed.connect(onWindowClosed); } function tearDown() { window.webEventReceived.disconnect(onWebEventReceived); } return { addGroup: addGroup, setButtonOut: setButtonOut, setCountDownValue: setCountDownValue, isVisible: getVisible, setVisible: setVisible, toggleVisible: toggleVisible, setUp: setUp, tearDown: tearDown }; }()); function onButtonClicked() { Dialog.toggleVisible(); button.editProperties({ isActive: Dialog.isVisible() }); } function onWindowClosed() { button.editProperties({ isActive: false }); } function finishMovingEntities(setPieceIndex, moveOut) { var setPiece = setPieces[setPieceIndex], NUDGE_VELOCITY = { x: 0, y: 0.1, z: 0 }, // To kick off physics for dynamic entities. i; // Stop moving in update loop. movingSetPieces.splice(movingSetPieces.indexOf(setPieceIndex), 1); // Set to exact target position. for (i = 0; i < setPiece.entities.length; i++) { Entities.editEntity(setPiece.entities[i].id, { velocity: moveOut && setPiece.entities[i].dynamic === true ? NUDGE_VELOCITY : Vec3.ZERO, dynamic: moveOut ? setPiece.entities[i].dynamic === true : false, collisionless: moveOut ? setPiece.entities[i].collisionless === true : true, position: moveOut ? setPiece.entities[i]["out"].position : setPiece.entities[i]["in"].position }); } // Finish counting down. if (setPiece.countDownTimer) { Script.clearInterval(setPiece.countDownTimer); setPiece.countDownTimer = null; } setPiece.countDownValue = 0; Dialog.setCountDownValue(setPiece.id, 0); setPiece.finishMovingEntitiesTimer = null; } function moveEntities(setPieceIndex, moveOut) { var setPiece = setPieces[setPieceIndex], movingIndex, entity, i; // Clear any current movement. movingIndex = movingSetPieces.indexOf(setPieceIndex); if (movingIndex !== -1) { movingSetPieces.splice(movingIndex, 1); } if (setPiece.finishMovingEntitiesTimer) { Script.clearTimeout(setPiece.finishMovingEntitiesTimer); setPiece.finishMovingEntitiesTimer = null; } // Start/restart count-down indicator. if (setPiece.countDownTimer) { Script.clearInterval(setPiece.countDownTimer); setPiece.countDownTimer = null; } setPiece.countDownValue = setPiece.duration; Dialog.setCountDownValue(setPiece.id, setPiece.countDownValue); setPiece.countDownTimer = Script.setInterval(function () { setPiece.countDownValue -= 0.5; Dialog.setCountDownValue(setPiece.id, setPiece.countDownValue); }, 500); // Set collisionless and non-dynamic while moving. for (i = 0; i < setPiece.entities.length; i++) { Entities.editEntity(setPiece.entities[i].id, { collisionless: true, dynamic: false, velocity: Vec3.ZERO }); } // Start moving in update loop. setPiece.movementTargetTime = Date.now() + setPiece.duration * 1000; for (i = 0; i < setPiece.entities.length; i++) { entity = setPiece.entities[i]; entity.movementTarget = moveOut ? entity["out"].position : entity["in"].position; entity.movementVector = Vec3.subtract(entity.movementTarget, Entities.getEntityProperties(entity.id, "position").position); } movingSetPieces.push(setPieceIndex); // Schedule completion of movement. setPiece.finishMovingEntitiesTimer = Script.setTimeout(function () { finishMovingEntities(setPieceIndex, moveOut); }, setPiece.duration * 1000); } function onUpdate() { var setPiece, nowTime = Date.now(), entity, factor, i, iLength, j, jLength; for (i = 0, iLength = movingSetPieces.length; i < iLength; i++) { setPiece = setPieces[movingSetPieces[i]]; factor = (setPiece.movementTargetTime - nowTime) / (setPiece.duration * 1000); for (j = 0, jLength = setPiece.entities.length; j < jLength; j++) { entity = setPiece.entities[j]; Entities.editEntity(entity.id, { position: Vec3.subtract(entity.movementTarget, Vec3.multiply(factor, entity.movementVector)) }); } } } function onDialogMessage(data) { var id, index, isOut; id = data.id; index = setPiecesIndexes[id]; isOut = !setPieces[index].isOut; setPieces[index].isOut = isOut; Dialog.setButtonOut(id, isOut); moveEntities(index, isOut); } function setUpSetPieces() { var id, i; setPieces = Script.require("./setPiecesDirector.json"); for (i = 0; i < setPieces.length; i++) { // Generate an ID to identify the group in HTML. id = "group" + i; setPieces[i].id = id; setPiecesIndexes[id] = i; // Generate the group in HTML. setPieces[i].isOut = true; // Assume that the set pieces are out, so that director can move all in at start. Dialog.addGroup(id, setPieces[i]); // Explicitly record that timers aren't running. setPieces[i].finishMovingEntitiesTimer = null; setPieces[i].countDownTimer = null; } } function setUp() { Dialog.setUp(onDialogMessage, onWindowClosed); Script.setTimeout(function () { // Event bridge isn't available straight away. setUpSetPieces(); // Wait for other scripts to set themselves up so as to avoid contention. tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); if (tablet) { button = tablet.addButton({ icon: APP_ICON_INACTIVE, activeIcon: APP_ICON_ACTIVE, text: APP_NAME, isActive: Dialog.isVisible() }); } if (button) { button.clicked.connect(onButtonClicked); } }, 2500); Script.update.connect(onUpdate); } function tearDown() { Script.update.disconnect(onUpdate); Dialog.setVisible(false); if (button) { button.clicked.disconnect(onButtonClicked); if (tablet) { tablet.removeButton(button); tablet = null; } button = null; } Dialog.tearDown(); } setUp(); Script.scriptEnding.connect(tearDown); }());