overte/scripts/system/miniTablet.js
2018-08-23 13:06:28 +12:00

709 lines
25 KiB
JavaScript

//
// miniTablet.js
//
// Created by David Rowe on 9 Aug 2018.
// Copyright 2018 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
//
/* global getTabletWidthFromSettings */
(function () {
"use strict";
Script.include("./libraries/utils.js");
var // Base overlay
proxyOverlay = null,
PROXY_MODEL = Script.resolvePath("./assets/models/tinyTablet.fbx"),
PROXY_DIMENSIONS = { x: 0.0637, y: 0.0965, z: 0.0046 }, // Proportional to tablet proper.
PROXY_POSITION_LEFT_HAND = {
x: 0,
y: 0.07, // Distance from joint.
z: 0.07 // Distance above palm.
},
PROXY_POSITION_RIGHT_HAND = {
x: 0,
y: 0.07, // Distance from joint.
z: 0.07 // Distance above palm.
},
/*
// Aligned cross-palm.
PROXY_ROTATION_LEFT_HAND = Quat.fromVec3Degrees({ x: 0, y: 180, z: 90 }),
PROXY_ROTATION_RIGHT_HAND = Quat.fromVec3Degrees({ x: 0, y: 180, z: -90 }),
*/
// Aligned with palm.
PROXY_ROTATION_LEFT_HAND = Quat.fromVec3Degrees({ x: 0, y: 180, z: 0 }),
PROXY_ROTATION_RIGHT_HAND = Quat.fromVec3Degrees({ x: 0, y: 180, z: 0 }),
// UI overlay.
proxyUIOverlay = null,
PROXY_UI_HTML = Script.resolvePath("./html/miniTablet.html"),
PROXY_UI_DIMENSIONS = { x: 0.0577, y: 0.0905 },
PROXY_UI_WIDTH_PIXELS = 150,
METERS_TO_INCHES = 39.3701,
PROXY_UI_DPI = PROXY_UI_WIDTH_PIXELS / (PROXY_UI_DIMENSIONS.x * METERS_TO_INCHES),
PROXY_UI_OFFSET = 0.001, // Above model surface.
PROXY_UI_LOCAL_POSITION = { x: 0, y: 0, z: -(PROXY_DIMENSIONS.z / 2 + PROXY_UI_OFFSET) },
PROXY_UI_LOCAL_ROTATION = Quat.fromVec3Degrees({ x: 0, y: 180, z: 0 }),
proxyUIOverlayEnabled = false,
PROXY_UI_OVERLAY_ENABLED_DELAY = 500,
proxyOverlayObject = null,
MUTE_ON_ICON = Script.resourcesPath() + "icons/tablet-icons/mic-mute-a.svg",
MUTE_OFF_ICON = Script.resourcesPath() + "icons/tablet-icons/mic-unmute-i.svg",
BUBBLE_ON_ICON = Script.resourcesPath() + "icons/tablet-icons/bubble-a.svg",
BUBBLE_OFF_ICON = Script.resourcesPath() + "icons/tablet-icons/bubble-i.svg",
// State machine
PROXY_DISABLED = 0,
PROXY_HIDDEN = 1,
PROXY_HIDING = 2,
PROXY_SHOWING = 3,
PROXY_VISIBLE = 4,
PROXY_EXPANDING = 5,
TABLET_OPEN = 6,
STATE_STRINGS = ["PROXY_DISABLED", "PROXY_HIDDEN", "PROXY_HIDING", "PROXY_SHOWING", "PROXY_VISIBLE", "PROXY_EXPANDING",
"TABLET_OPEN"],
STATE_MACHINE,
rezzerState = PROXY_DISABLED,
proxyHand,
PROXY_SCALE_DURATION = 150,
PROXY_SCALE_TIMEOUT = 20,
proxyScaleTimer = null,
proxyScaleStart,
PROXY_EXPAND_HANDLES = [ // Normalized coordinates in range [-0.5, 0.5] about center of mini tablet.
{ x: 0.5, y: -0.75, z: 0 },
{ x: -0.5, y: -0.75, z: 0 }
],
PROXY_EXPAND_DELTA_ROTATION = Quat.fromVec3Degrees({ x: -45, y: 0, z: 0 }),
proxyExpandHand,
proxyExpandLocalPosition,
proxyExpandLocalRotation = Quat.IDENTITY,
PROXY_EXPAND_DURATION = 250,
PROXY_EXPAND_TIMEOUT = 20,
proxyExpandTimer = null,
proxyExpandStart,
proxyInitialWidth,
proxyTargetWidth,
proxyTargetLocalRotation,
// EventBridge
READY_MESSAGE = "ready", // Engine <== Dialog
HOVER_MESSAGE = "hover", // Engine <== Dialog
MUTE_MESSAGE = "mute", // Engine <=> Dialog
BUBBLE_MESSAGE = "bubble", // Engine <=> Dialog
EXPAND_MESSAGE = "expand", // Engine <== Dialog
// Events
MIN_HAND_CAMERA_ANGLE = 30,
DEGREES_180 = 180,
MIN_HAND_CAMERA_ANGLE_COS = Math.cos(Math.PI * MIN_HAND_CAMERA_ANGLE / DEGREES_180),
updateTimer = null,
UPDATE_INTERVAL = 300,
HIFI_OBJECT_MANIPULATION_CHANNEL = "Hifi-Object-Manipulation",
avatarScale = 1,
// Sounds
HOVER_SOUND = "./assets/sounds/button-hover.wav",
HOVER_VOLUME = 0.5,
CLICK_SOUND = "./assets/sounds/button-click.wav",
CLICK_VOLUME = 0.8,
hoverSound = SoundCache.getSound(Script.resolvePath(HOVER_SOUND)),
clickSound = SoundCache.getSound(Script.resolvePath(CLICK_SOUND)),
// Hands
LEFT_HAND = 0,
RIGHT_HAND = 1,
HAND_NAMES = ["LeftHand", "RightHand"],
// Miscellaneous.
tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"),
DEBUG = false;
// #region Utilities =======================================================================================================
function debug(message) {
if (!DEBUG) {
return;
}
print("DEBUG: " + message);
}
function error(message) {
print("ERROR: " + message);
}
function handJointName(hand) {
var jointName;
if (hand === LEFT_HAND) {
if (Camera.mode === "first person") {
jointName = "_CONTROLLER_LEFTHAND";
} else if (Camera.mode === "third person") {
jointName = "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND";
} else {
jointName = "LeftHand";
}
} else {
if (Camera.mode === "first person") {
jointName = "_CONTROLLER_RIGHTHAND";
} else if (Camera.mode === "third person") {
jointName = "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND";
} else {
jointName = "RightHand";
}
}
return jointName;
}
function handJointIndex(hand) {
return MyAvatar.getJointIndex(handJointName(hand));
}
function otherHand(hand) {
return hand === LEFT_HAND ? RIGHT_HAND : LEFT_HAND;
}
function playSound(sound, volume) {
Audio.playSound(sound, {
position: proxyHand === LEFT_HAND ? MyAvatar.getLeftPalmPosition() : MyAvatar.getRightPalmPosition(),
volume: volume,
localOnly: true
});
}
// #endregion
// #region Communications ==================================================================================================
function updateMiniTabletID() {
// Send mini-tablet overlay ID to controllerDispatcher so that it can use a smaller near grab distance.
Messages.sendLocalMessage("Hifi-MiniTablet-ID", proxyOverlay);
// Send mini-tablet UI overlay ID to stylusInput so that it styluses can be used on it.
Messages.sendLocalMessage("Hifi-MiniTablet-UI-ID", proxyUIOverlay);
}
function updateMutedStatus() {
var isMuted = Audio.muted;
proxyOverlayObject.emitScriptEvent(JSON.stringify({
type: MUTE_MESSAGE,
on: isMuted,
icon: isMuted ? MUTE_ON_ICON : MUTE_OFF_ICON
}));
}
function updateBubbleStatus() {
var isBubbleOn = Users.getIgnoreRadiusEnabled();
proxyOverlayObject.emitScriptEvent(JSON.stringify({
type: BUBBLE_MESSAGE,
on: isBubbleOn,
icon: isBubbleOn ? BUBBLE_ON_ICON : BUBBLE_OFF_ICON
}));
}
function onWebEventReceived(data) {
var message;
try {
message = JSON.parse(data);
} catch (e) {
console.error("EventBridge message error");
return;
}
switch (message.type) {
case READY_MESSAGE:
// Send initial button statuses.
updateMutedStatus();
updateBubbleStatus();
break;
case HOVER_MESSAGE:
// Audio feedback.
playSound(hoverSound, HOVER_VOLUME);
break;
case MUTE_MESSAGE:
// Toggle mute.
playSound(clickSound, CLICK_VOLUME);
Audio.muted = !Audio.muted;
break;
case BUBBLE_MESSAGE:
// Toggle bubble.
playSound(clickSound, CLICK_VOLUME);
Users.toggleIgnoreRadius();
break;
case EXPAND_MESSAGE:
// Expand tablet;
playSound(clickSound, CLICK_VOLUME);
setState(PROXY_EXPANDING, proxyHand);
break;
}
}
// #endregion
// #region UI ==============================================================================================================
function createUI() {
proxyOverlay = Overlays.addOverlay("model", {
url: PROXY_MODEL,
dimensions: Vec3.multiply(avatarScale, PROXY_DIMENSIONS),
solid: true,
grabbable: true,
showKeyboardFocusHighlight: false,
displayInFront: true,
visible: false
});
proxyUIOverlay = Overlays.addOverlay("web3d", {
url: PROXY_UI_HTML,
parentID: proxyOverlay,
localPosition: Vec3.multiply(avatarScale, PROXY_UI_LOCAL_POSITION),
localRotation: PROXY_UI_LOCAL_ROTATION,
dimensions: Vec3.multiply(avatarScale, PROXY_UI_DIMENSIONS),
dpi: PROXY_UI_DPI / avatarScale,
alpha: 0, // Hide overlay while its content is being created.
grabbable: false,
showKeyboardFocusHighlight: false,
displayInFront: true,
visible: false
});
proxyUIOverlayEnabled = false; // This and alpha = 0 hides overlay while its content is being created.
proxyOverlayObject = Overlays.getOverlayObject(proxyUIOverlay);
proxyOverlayObject.webEventReceived.connect(onWebEventReceived);
// updateMiniTabletID(); Other scripts relying on this may not be ready yet so do this in showUI().
}
function showUI() {
var initialScale = 0.01; // Start very small.
Overlays.editOverlay(proxyOverlay, {
parentID: MyAvatar.SELF_ID,
parentJointIndex: handJointIndex(proxyHand),
localPosition: Vec3.multiply(avatarScale,
proxyHand === LEFT_HAND ? PROXY_POSITION_LEFT_HAND : PROXY_POSITION_RIGHT_HAND),
localRotation: proxyHand === LEFT_HAND ? PROXY_ROTATION_LEFT_HAND : PROXY_ROTATION_RIGHT_HAND,
dimensions: Vec3.multiply(initialScale, PROXY_DIMENSIONS),
visible: true
});
Overlays.editOverlay(proxyUIOverlay, {
localPosition: Vec3.multiply(avatarScale, PROXY_UI_LOCAL_POSITION),
localRotation: PROXY_UI_LOCAL_ROTATION,
dimensions: Vec3.multiply(initialScale, PROXY_UI_DIMENSIONS),
dpi: PROXY_UI_DPI / initialScale,
visible: true
});
updateMiniTabletID();
if (!proxyUIOverlayEnabled) {
// Overlay content is created the first time it is visible to the user. The initial creation displays artefacts.
// Delay showing UI overlay until after giving it time for its content to be created.
Script.setTimeout(function () {
Overlays.editOverlay(proxyUIOverlay, { alpha: 1.0 });
}, PROXY_UI_OVERLAY_ENABLED_DELAY);
}
}
function sizeUI(scaleFactor) {
// Scale UI in place.
Overlays.editOverlay(proxyOverlay, {
dimensions: Vec3.multiply(scaleFactor, PROXY_DIMENSIONS)
});
Overlays.editOverlay(proxyUIOverlay, {
localPosition: Vec3.multiply(scaleFactor, PROXY_UI_LOCAL_POSITION),
dimensions: Vec3.multiply(scaleFactor, PROXY_UI_DIMENSIONS),
dpi: PROXY_UI_DPI / scaleFactor
});
}
function sizeUIAboutHandles(scaleFactor) {
// Scale UI and move per handles.
var tabletScaleFactor = avatarScale * (1 + scaleFactor * (proxyTargetWidth - proxyInitialWidth) / proxyInitialWidth);
var dimensions = Vec3.multiply(tabletScaleFactor, PROXY_DIMENSIONS);
var localRotation = Quat.mix(proxyExpandLocalRotation, proxyTargetLocalRotation, scaleFactor);
var localPosition =
Vec3.sum(proxyExpandLocalPosition,
Vec3.multiplyQbyV(proxyExpandLocalRotation,
Vec3.multiply(-tabletScaleFactor,
Vec3.multiplyVbyV(PROXY_EXPAND_HANDLES[proxyExpandHand], PROXY_DIMENSIONS)))
);
localPosition = Vec3.sum(localPosition,
Vec3.multiplyQbyV(proxyExpandLocalRotation, { x: 0, y: 0.5 * -dimensions.y, z: 0 }));
localPosition = Vec3.sum(localPosition,
Vec3.multiplyQbyV(localRotation, { x: 0, y: 0.5 * dimensions.y, z: 0 }));
Overlays.editOverlay(proxyOverlay, {
localPosition: localPosition,
localRotation: localRotation,
dimensions: dimensions
});
Overlays.editOverlay(proxyUIOverlay, {
localPosition: Vec3.multiply(tabletScaleFactor, PROXY_UI_LOCAL_POSITION),
dimensions: Vec3.multiply(tabletScaleFactor, PROXY_UI_DIMENSIONS),
dpi: PROXY_UI_DPI / tabletScaleFactor
});
}
function hideUI() {
Overlays.editOverlay(proxyOverlay, {
parentID: Uuid.NULL, // Release hold so that hand can grab tablet proper.
visible: false
});
Overlays.editOverlay(proxyUIOverlay, {
visible: false
});
}
function destroyUI() {
if (proxyOverlayObject) {
proxyOverlayObject.webEventReceived.disconnect(onWebEventReceived);
Overlays.deleteOverlay(proxyUIOverlay);
Overlays.deleteOverlay(proxyOverlay);
proxyOverlayObject = null;
proxyUIOverlay = null;
proxyOverlay = null;
updateMiniTabletID();
}
}
// #endregion
// #region State Machine ===================================================================================================
function enterProxyDisabled() {
// Stop updates.
if (updateTimer !== null) {
Script.clearTimeout(updateTimer);
updateTimer = null;
}
// Stop monitoring mute and bubble changes.
Audio.mutedChanged.disconnect(updateMutedStatus);
Users.ignoreRadiusEnabledChanged.disconnect(updateBubbleStatus);
// Don't keep overlays prepared if in desktop mode.
destroyUI();
}
function exitProxyDisabled() {
// Create UI so that it's ready to be displayed without seeing artefacts from creating the UI.
createUI();
// Start monitoring mute and bubble changes.
Audio.mutedChanged.connect(updateMutedStatus);
Users.ignoreRadiusEnabledChanged.connect(updateBubbleStatus);
// Start updates.
updateTimer = Script.setTimeout(updateState, UPDATE_INTERVAL);
}
function shouldShowProxy(hand) {
// Should show tablet proxy if hand is oriented toward the camera and the camera is oriented toward the proxy tablet.
var pose,
jointIndex,
handPosition,
handOrientation,
cameraToHandDirection;
pose = Controller.getPoseValue(hand === LEFT_HAND ? Controller.Standard.LeftHand : Controller.Standard.RightHand);
if (!pose.valid) {
return false;
}
jointIndex = handJointIndex(hand);
handPosition = Vec3.sum(MyAvatar.position,
Vec3.multiplyQbyV(MyAvatar.orientation, MyAvatar.getAbsoluteJointTranslationInObjectFrame(jointIndex)));
handOrientation = Quat.multiply(MyAvatar.orientation, MyAvatar.getAbsoluteJointRotationInObjectFrame(jointIndex));
cameraToHandDirection = Vec3.normalize(Vec3.subtract(handPosition, Camera.position));
return Vec3.dot(cameraToHandDirection, Quat.getForward(handOrientation)) > MIN_HAND_CAMERA_ANGLE_COS;
}
function enterProxyHidden() {
hideUI();
}
function updateProxyHidden() {
// Don't show proxy if tablet is already displayed or in toolbar mode.
if (HMD.showTablet || tablet.toolbarMode) {
return;
}
// Compare palm directions of hands with vectors from palms to camera.
if (shouldShowProxy(LEFT_HAND)) {
setState(PROXY_SHOWING, LEFT_HAND);
} else if (shouldShowProxy(RIGHT_HAND)) {
setState(PROXY_SHOWING, RIGHT_HAND);
}
}
function scaleProxyDown() {
var scaleFactor = (Date.now() - proxyScaleStart) / PROXY_SCALE_DURATION;
if (scaleFactor < 1) {
sizeUI((1 - scaleFactor) * avatarScale);
proxyScaleTimer = Script.setTimeout(scaleProxyDown, PROXY_SCALE_TIMEOUT);
return;
}
proxyScaleTimer = null;
setState(PROXY_HIDDEN);
}
function enterProxyHiding() {
proxyScaleStart = Date.now();
proxyScaleTimer = Script.setTimeout(scaleProxyDown, PROXY_SCALE_TIMEOUT);
}
function updateProxyHiding() {
if (HMD.showTablet) {
setState(PROXY_HIDDEN);
}
}
function exitProxyHiding() {
if (proxyScaleTimer) {
Script.clearTimeout(proxyScaleTimer);
proxyScaleTimer = null;
}
}
function scaleProxyUp() {
var scaleFactor = (Date.now() - proxyScaleStart) / PROXY_SCALE_DURATION;
if (scaleFactor < 1) {
sizeUI(scaleFactor * avatarScale);
proxyScaleTimer = Script.setTimeout(scaleProxyUp, PROXY_SCALE_TIMEOUT);
return;
}
proxyScaleTimer = null;
sizeUI(avatarScale);
setState(PROXY_VISIBLE);
}
function enterProxyShowing(hand) {
proxyHand = hand;
showUI();
proxyScaleStart = Date.now();
proxyScaleTimer = Script.setTimeout(scaleProxyUp, PROXY_SCALE_TIMEOUT);
}
function updateProxyShowing() {
if (HMD.showTablet) {
setState(PROXY_HIDDEN);
}
}
function exitProxyShowing() {
if (proxyScaleTimer) {
Script.clearTimeout(proxyScaleTimer);
proxyScaleTimer = null;
}
}
function updateProxyVisible() {
// Hide proxy if tablet has been displayed by other means.
if (HMD.showTablet) {
setState(PROXY_HIDDEN);
return;
}
// Check that palm direction of proxy hand still less than maximum angle.
if (!shouldShowProxy(proxyHand)) {
setState(PROXY_HIDING);
}
}
function expandProxy() {
var scaleFactor = (Date.now() - proxyExpandStart) / PROXY_EXPAND_DURATION;
if (scaleFactor < 1) {
sizeUIAboutHandles(scaleFactor);
proxyExpandTimer = Script.setTimeout(expandProxy, PROXY_EXPAND_TIMEOUT);
return;
}
proxyExpandTimer = null;
setState(TABLET_OPEN);
}
function enterProxyExpanding(hand) {
// Grab details.
var properties = Overlays.getProperties(proxyOverlay, ["localPosition", "localRotation"]);
proxyExpandHand = hand;
proxyExpandLocalRotation = properties.localRotation;
proxyExpandLocalPosition = Vec3.sum(properties.localPosition,
Vec3.multiplyQbyV(proxyExpandLocalRotation,
Vec3.multiplyVbyV(PROXY_EXPAND_HANDLES[proxyExpandHand], PROXY_DIMENSIONS)));
// Start expanding.
proxyInitialWidth = PROXY_DIMENSIONS.x;
proxyTargetWidth = getTabletWidthFromSettings();
proxyTargetLocalRotation = Quat.multiply(proxyExpandLocalRotation, PROXY_EXPAND_DELTA_ROTATION);
proxyExpandStart = Date.now();
proxyExpandTimer = Script.setTimeout(expandProxy, PROXY_EXPAND_TIMEOUT);
}
function updateProxyExanding() {
// Hide proxy immediately if tablet has been displayed by other means.
if (HMD.showTablet) {
setState(PROXY_HIDDEN);
}
}
function exitProxyExpanding() {
if (proxyExpandTimer !== null) {
Script.clearTimeout(proxyExpandTimer);
proxyExpandTimer = null;
}
}
function enterTabletOpen() {
var proxyOverlayProperties = Overlays.getProperties(proxyOverlay, ["position", "orientation"]);
hideUI();
Overlays.editOverlay(HMD.tabletID, {
position: proxyOverlayProperties.position,
orientation: proxyOverlayProperties.orientation
});
HMD.openTablet(true);
}
function updateTabletOpen() {
// Immediately transition back to PROXY_HIDDEN.
setState(PROXY_HIDDEN);
}
STATE_MACHINE = {
PROXY_DISABLED: { // Tablet proxy cannot be shown because in desktop mode.
enter: enterProxyDisabled,
update: null,
exit: exitProxyDisabled
},
PROXY_HIDDEN: { // Tablet proxy could be shown but isn't because hand is oriented to show it or aren't in HMD mode.
enter: enterProxyHidden,
update: updateProxyHidden,
exit: null
},
PROXY_HIDING: { // Tablet proxy is reducing from PROXY_VISIBLE to PROXY_HIDDEN.
enter: enterProxyHiding,
update: updateProxyHiding,
exit: exitProxyHiding
},
PROXY_SHOWING: { // Tablet proxy is expanding from PROXY_HIDDN to PROXY_VISIBLE.
enter: enterProxyShowing,
update: updateProxyShowing,
exit: exitProxyShowing
},
PROXY_VISIBLE: { // Tablet proxy is visible and attached to hand.
enter: null,
update: updateProxyVisible,
exit: null
},
PROXY_EXPANDING: { // Tablet proxy has been grabbed and is expanding before showing tablet proper.
enter: enterProxyExpanding,
update: updateProxyExanding,
exit: exitProxyExpanding
},
TABLET_OPEN: { // Tablet proper is being displayed.
enter: enterTabletOpen,
update: updateTabletOpen,
exit: null
}
};
function setState(state, data) {
if (state !== rezzerState) {
debug("State transition from " + STATE_STRINGS[rezzerState] + " to " + STATE_STRINGS[state]);
if (STATE_MACHINE[STATE_STRINGS[rezzerState]].exit) {
STATE_MACHINE[STATE_STRINGS[rezzerState]].exit(data);
}
if (STATE_MACHINE[STATE_STRINGS[state]].enter) {
STATE_MACHINE[STATE_STRINGS[state]].enter(data);
}
rezzerState = state;
} else {
error("Null state transition: " + state + "!");
}
}
function updateState() {
if (STATE_MACHINE[STATE_STRINGS[rezzerState]].update) {
STATE_MACHINE[STATE_STRINGS[rezzerState]].update();
}
updateTimer = Script.setTimeout(updateState, UPDATE_INTERVAL);
}
// #endregion
// #region Events ==========================================================================================================
function onScaleChanged() {
avatarScale = MyAvatar.scale;
// Clamp scale in order to work around M17434.
avatarScale = Math.max(MyAvatar.getDomainMinScale(), Math.min(MyAvatar.getDomainMaxScale(), avatarScale));
}
function onMessageReceived(channel, data, senderID, localOnly) {
var message, hand;
if (channel !== HIFI_OBJECT_MANIPULATION_CHANNEL) {
return;
}
message = JSON.parse(data);
if (message.grabbedEntity !== proxyOverlay) {
return;
}
if (message.action === "grab" && rezzerState === PROXY_VISIBLE) {
hand = message.joint === HAND_NAMES[proxyHand] ? proxyHand : otherHand(proxyHand);
setState(PROXY_EXPANDING, hand);
}
}
function onDisplayModeChanged() {
// Tablet proxy only available when HMD is active.
if (HMD.active) {
setState(PROXY_HIDDEN);
} else {
setState(PROXY_DISABLED);
}
}
// #endregion
// #region Start-up and tear-down ==========================================================================================
function setUp() {
MyAvatar.scaleChanged.connect(onScaleChanged);
Messages.subscribe(HIFI_OBJECT_MANIPULATION_CHANNEL);
Messages.messageReceived.connect(onMessageReceived);
HMD.displayModeChanged.connect(onDisplayModeChanged);
if (HMD.active) {
setState(PROXY_HIDDEN);
}
}
function tearDown() {
if (updateTimer !== null) {
Script.clearTimeout(updateTimer);
updateTimer = null;
}
setState(PROXY_DISABLED);
HMD.displayModeChanged.disconnect(onDisplayModeChanged);
Messages.messageReceived.disconnect(onMessageReceived);
Messages.unsubscribe(HIFI_OBJECT_MANIPULATION_CHANNEL);
MyAvatar.scaleChanged.disconnect(onScaleChanged);
}
setUp();
Script.scriptEnding.connect(tearDown);
// #endregion
}());