//// miniTablet.js//// Created by David Rowe on August 9th, 2018.// Copyright 2018 High Fidelity, Inc.// Copyright 2023 Overte e.V.//// 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, handsAreTracked, TRIGGER_OFF_VALUE, Controller, Script, Camera, Tablet, MyAvatar, Quat, SoundCache, HMD, Vec3, Uuid, Messages */ (function () { "use strict"; Script.include("./libraries/utils.js"); Script.include("./libraries/controllerDispatcherUtils.js"); var controllerStandard = Controller.Standard; var UI, ui = null, State, miniState = null, miniTabletEnabled = true, // Hands. NO_HAND = -1, LEFT_HAND = 0, RIGHT_HAND = 1, HAND_NAMES = ["LeftHand", "RightHand"], // Track controller grabbing state. HIFI_OBJECT_MANIPULATION_CHANNEL = "Hifi-Object-Manipulation", grabbingHand = NO_HAND, grabbedItem = null, // Miscellaneous. tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"), DEBUG = false; 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" || Camera.mode === "first person look at") { jointName = "_CONTROLLER_LEFTHAND"; } else if (Camera.mode === "third person") { jointName = "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND"; } else { jointName = "LeftHand"; } } else { if (Camera.mode === "first person" || Camera.mode === "first person look at") { 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; } UI = function () { if (!(this instanceof UI)) { return new UI(); } var // Base overlay miniOverlay = null, MINI_MODEL = Script.resolvePath("./assets/models/miniTabletBlank.fbx"), MINI_DIMENSIONS = { x: 0.0637, y: 0.0965, z: 0.0045 }, // Proportional to tablet proper. MINI_POSITIONS = [ { x: -0.01, // Distance across hand. y: 0.08, // Distance from joint. z: 0.06 // Distance above palm. }, { x: 0.01, // Distance across hand. y: 0.08, // Distance from joint. z: 0.06 // Distance above palm. } ], DEGREES_180 = 180, MINI_PICTH = 40, MINI_ROTATIONS = [ Quat.fromVec3Degrees({ x: 0, y: DEGREES_180 - MINI_PICTH, z: 90 }), Quat.fromVec3Degrees({ x: 0, y: DEGREES_180 + MINI_PICTH, z: -90 }) ], // UI overlay. uiHand = LEFT_HAND, miniUIOverlay = null, MINI_UI_HTML = Script.resolvePath("./html/miniTablet.html"), MINI_HAND_UI_HTML = Script.resolvePath("./html/miniHandsTablet.html"), MINI_UI_DIMENSIONS = { x: 0.059, y: 0.0865, z: 0.001 }, MINI_UI_WIDTH_PIXELS = 150, METERS_TO_INCHES = 39.3701, MINI_UI_DPI = MINI_UI_WIDTH_PIXELS / (MINI_UI_DIMENSIONS.x * METERS_TO_INCHES), MINI_UI_OFFSET = 0.001, // Above model surface. MINI_UI_LOCAL_POSITION = { x: 0.0002, y: 0.0024, z: -(MINI_DIMENSIONS.z / 2 + MINI_UI_OFFSET) }, MINI_UI_LOCAL_ROTATION = Quat.fromVec3Degrees({ x: 0, y: 180, z: 0 }), miniUIOverlayEnabled = false, MINI_UI_OVERLAY_ENABLED_DELAY = 500, miniOverlayObject = null, // Button icons. MUTE_ON_ICON = Script.resourcesPath() + "icons/tablet-icons/mic-mute-a.svg", MUTE_OFF_ICON = Script.resourcesPath() + "icons/tablet-icons/mic-unmute-i.svg", GOTO_ICON = Script.resourcesPath() + "icons/tablet-icons/goto-i.svg", // Expansion to tablet. MINI_EXPAND_HANDLES = [ // Normalized coordinates in range [-0.5, 0.5] about center of mini tablet. { x: 0.5, y: -0.65, z: 0 }, { x: -0.5, y: -0.65, z: 0 } ], MINI_EXPAND_DELTA_ROTATION = Quat.fromVec3Degrees({ x: -5, y: 0, z: 0 }), MINI_EXPAND_HANDLES_OTHER = [ // Different handles when expanding after being grabbed by other hand, { x: 0.5, y: -0.4, z: 0 }, { x: -0.5, y: -0.4, z: 0 } ], MINI_EXPAND_DELTA_ROTATION_OTHER = Quat.IDENTITY, miniExpandHand, miniExpandHandles = MINI_EXPAND_HANDLES, miniExpandDeltaRotation = MINI_EXPAND_HANDLES_OTHER, miniExpandLocalPosition, miniExpandLocalRotation = Quat.IDENTITY, miniInitialWidth, miniTargetWidth, miniTargetLocalRotation, // Laser pointing at. isBodyHovered = false, // EventBridge. READY_MESSAGE = "ready", // Engine <== Dialog HOVER_MESSAGE = "hover", // Engine <== Dialog UNHOVER_MESSAGE = "unhover", // Engine <== Dialog MUTE_MESSAGE = "mute", // Engine <=> Dialog GOTO_MESSAGE = "goto", // Engine <=> Dialog EXPAND_MESSAGE = "expand", // Engine <== Dialog // 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)); function updateMutedStatus() { if (miniOverlayObject) { var isMuted = Audio.muted; miniOverlayObject.emitScriptEvent(JSON.stringify({ type: MUTE_MESSAGE, on: isMuted, icon: isMuted ? MUTE_ON_ICON : MUTE_OFF_ICON })); } } function setGotoIcon() { if (miniOverlayObject) { miniOverlayObject.emitScriptEvent(JSON.stringify({ type: GOTO_MESSAGE, icon: GOTO_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(); setGotoIcon(); break; case HOVER_MESSAGE: if (message.target === "body") { // Laser status. isBodyHovered = true; } else if (message.target === "button") { // Audio feedback. playSound(hoverSound, HOVER_VOLUME); } break; case UNHOVER_MESSAGE: if (message.target === "body") { // Laser status. isBodyHovered = false; } break; case MUTE_MESSAGE: // Toggle mute. playSound(clickSound, CLICK_VOLUME); Audio.muted = !Audio.muted; break; case GOTO_MESSAGE: // Goto. playSound(clickSound, CLICK_VOLUME); miniState.setState(miniState.MINI_EXPANDING, { hand: uiHand, goto: true }); break; case EXPAND_MESSAGE: // Expand tablet; playSound(clickSound, CLICK_VOLUME); miniState.setState(miniState.MINI_EXPANDING, { hand: uiHand, goto: false }); break; } } function updateMiniTabletID() { HMD.miniTabletID = miniOverlay; HMD.miniTabletScreenID = miniUIOverlay; HMD.miniTabletHand = miniOverlay ? uiHand : -1; } function playSound(sound, volume) { Audio.playSound(sound, { position: uiHand === LEFT_HAND ? MyAvatar.getLeftPalmPosition() : MyAvatar.getRightPalmPosition(), volume: volume, localOnly: true }); } function getUIPosition(hand) { return MINI_POSITIONS[hand]; } function getMiniTabletID() { return miniOverlay; } function getMiniTabletProperties() { var properties = Entities.getEntityProperties(miniOverlay, ["position", "orientation"]); return { position: properties.position, orientation: properties.orientation }; } function isLaserPointingAt() { return isBodyHovered; } function show(hand) { var initialScale = 0.01; // Start very small. uiHand = hand; Entities.editEntity(miniOverlay, { "parentID": MyAvatar.SELF_ID, "parentJointIndex": handJointIndex(hand), "localPosition": Vec3.multiply(MyAvatar.sensorToWorldScale, MINI_POSITIONS[hand]), "localRotation": MINI_ROTATIONS[hand], "dimensions": Vec3.multiply(initialScale, MINI_DIMENSIONS), "grab": { "grabbable": true }, "visible": true }); Entities.editEntity(miniUIOverlay, { "sourceUrl": handsAreTracked() ? MINI_HAND_UI_HTML : MINI_UI_HTML, "localPosition": Vec3.multiply(MyAvatar.sensorToWorldScale, MINI_UI_LOCAL_POSITION), "localRotation": MINI_UI_LOCAL_ROTATION, "dimensions": Vec3.multiply(initialScale, MINI_UI_DIMENSIONS), "dpi": MINI_UI_DPI / initialScale, "visible": true }); updateMiniTabletID(); if (!miniUIOverlayEnabled) { // 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 () { Entities.editEntity(miniUIOverlay, { "alpha": 1.0 }); }, MINI_UI_OVERLAY_ENABLED_DELAY); } } function size(scaleFactor) { // Scale UI in place. Entities.editEntity(miniOverlay, { "dimensions": Vec3.multiply(scaleFactor, MINI_DIMENSIONS) }); Entities.editEntity(miniUIOverlay, { "localPosition": Vec3.multiply(scaleFactor, MINI_UI_LOCAL_POSITION), "dimensions": Vec3.multiply(scaleFactor, MINI_UI_DIMENSIONS), "dpi": MINI_UI_DPI / scaleFactor }); updateRotation(); } function startExpandingTablet(hand) { // Expansion details. if (hand === uiHand) { miniExpandHandles = MINI_EXPAND_HANDLES; miniExpandDeltaRotation = MINI_EXPAND_DELTA_ROTATION; } else { miniExpandHandles = MINI_EXPAND_HANDLES_OTHER; miniExpandDeltaRotation = MINI_EXPAND_DELTA_ROTATION_OTHER; } // Grab details. var properties = Entities.getEntityProperties(miniOverlay, ["localPosition", "localRotation"]); miniExpandHand = hand; miniExpandLocalRotation = properties.localRotation; miniExpandLocalPosition = Vec3.sum(properties.localPosition, Vec3.multiplyQbyV(miniExpandLocalRotation, Vec3.multiplyVbyV(miniExpandHandles[miniExpandHand], MINI_DIMENSIONS))); // Start expanding. miniInitialWidth = MINI_DIMENSIONS.x; miniTargetWidth = getTabletWidthFromSettings(); miniTargetLocalRotation = Quat.multiply(miniExpandLocalRotation, miniExpandDeltaRotation); } function sizeAboutHandles(scaleFactor) { // Scale UI and move per handles. var tabletScaleFactor, dimensions, localRotation, localPosition; tabletScaleFactor = MyAvatar.sensorToWorldScale * (1 + scaleFactor * (miniTargetWidth - miniInitialWidth) / miniInitialWidth); dimensions = Vec3.multiply(tabletScaleFactor, MINI_DIMENSIONS); localRotation = Quat.mix(miniExpandLocalRotation, miniTargetLocalRotation, scaleFactor); localPosition = Vec3.sum(miniExpandLocalPosition, Vec3.multiplyQbyV(miniExpandLocalRotation, Vec3.multiply(-tabletScaleFactor, Vec3.multiplyVbyV(miniExpandHandles[miniExpandHand], MINI_DIMENSIONS))) ); localPosition = Vec3.sum(localPosition, Vec3.multiplyQbyV(miniExpandLocalRotation, { 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 })); Entities.editEntity(miniOverlay, { "localPosition": localPosition, "localRotation": localRotation, "dimensions": dimensions }); // FIXME: Temporary code change to try not displaying UI when mini tablet is expanding to become the tablet proper. Entities.editEntity(miniUIOverlay, { /* "localPosition": Vec3.multiply(tabletScaleFactor, MINI_UI_LOCAL_POSITION), "dimensions": Vec3.multiply(tabletScaleFactor, MINI_UI_DIMENSIONS), "dpi": MINI_UI_DPI / tabletScaleFactor */ "visible": false }); } function updateRotation() { // Update the rotation of the tablet about its face normal so that its base is horizontal. var COS_5_DEGREES = 0.996, RADIANS_TO_DEGREES = DEGREES_180 / Math.PI, defaultLocalRotation, handOrientation, defaultOrientation, faceNormal, desiredOrientation, defaultYAxis, desiredYAxis, cross, dot, deltaAngle, deltaRotation, localRotation; if (Entities.getEntityProperties(miniOverlay, ["parentJointIndex"]).parentJointIndex !== handJointIndex(uiHand)) { // Overlay has been grabbed by other hand but this script hasn't received notification yet. return; } defaultLocalRotation = MINI_ROTATIONS[uiHand]; handOrientation = Quat.multiply(MyAvatar.orientation, MyAvatar.getAbsoluteJointRotationInObjectFrame(handJointIndex(uiHand))); defaultOrientation = Quat.multiply(handOrientation, defaultLocalRotation); faceNormal = Vec3.multiplyQbyV(defaultOrientation, Vec3.UNIT_Z); if (Math.abs(Vec3.dot(faceNormal, Vec3.UNIT_Y)) > COS_5_DEGREES) { // Don't rotate mini tablet if almost flat in the x-z plane. return; } else { // Rotate the tablet so that its base is parallel with the x-z plane. desiredOrientation = Quat.lookAt(Vec3.ZERO, Vec3.multiplyQbyV(defaultOrientation, Vec3.UNIT_Z), Vec3.UNIT_Y); defaultYAxis = Vec3.multiplyQbyV(defaultOrientation, Vec3.UNIT_Y); desiredYAxis = Vec3.multiplyQbyV(desiredOrientation, Vec3.UNIT_Y); cross = Vec3.cross(defaultYAxis, desiredYAxis); dot = Vec3.dot(defaultYAxis, desiredYAxis); deltaAngle = Math.atan2(Vec3.length(cross), dot) * RADIANS_TO_DEGREES; if (Vec3.dot(cross, Vec3.multiplyQbyV(desiredOrientation, Vec3.UNIT_Z)) > 0) { deltaAngle = -deltaAngle; } deltaRotation = Quat.angleAxis(deltaAngle, Vec3.multiplyQbyV(defaultLocalRotation, Vec3.UNIT_Z)); localRotation = Quat.multiply(deltaRotation, defaultLocalRotation); } Entities.editEntity(miniOverlay, { "localRotation": localRotation }); } function release() { Entities.editEntity(miniOverlay, { "parentID": Uuid.NONE, // Release hold so that hand can grab tablet proper. "grab": { "grabbable": false } }); } function hide() { Entities.editEntity(miniOverlay, { "visible": false }); Entities.editEntity(miniUIOverlay, { "visible": false }); } function checkEventBridge() { // The miniUIOverlay overlay's overlay object is not available immediately the overlay is created so we have to // provide a means to check for and connect it when it does become available. if (miniOverlayObject) { return; } miniOverlayObject = Entities.getEntityObject(miniUIOverlay); if (miniOverlayObject) { miniOverlayObject.webEventReceived.connect(onWebEventReceived); } } function create() { miniOverlay = Entities.addEntity({ "type": "Model", "modelURL": MINI_MODEL, "dimensions": Vec3.multiply(MyAvatar.sensorToWorldScale, MINI_DIMENSIONS), "primitiveMode": "solid", "grab": { "grabbable": true }, "renderLayer": "world", "visible": false }, "local"); miniUIOverlay = Entities.addEntity({ "type": "Web", "sourceUrl": handsAreTracked() ? MINI_HAND_UI_HTML : MINI_UI_HTML, "parentID": miniOverlay, "localPosition": Vec3.multiply(MyAvatar.sensorToWorldScale, MINI_UI_LOCAL_POSITION), "localRotation": MINI_UI_LOCAL_ROTATION, "dimensions": Vec3.multiply(MyAvatar.sensorToWorldScale, MINI_UI_DIMENSIONS), "dpi": MINI_UI_DPI / MyAvatar.sensorToWorldScale, "alpha": 0, // Hide overlay while its content is being created. "grab": { "grabbable": false }, "showKeyboardFocusHighlight": false, "renderLayer": "world", "visible": falseLines 1–500 of 1162. Load more
// // miniTablet.js // // Created by David Rowe on August 9th, 2018. // Copyright 2018 High Fidelity, Inc. // Copyright 2023 Overte e.V. // // 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, handsAreTracked, TRIGGER_OFF_VALUE, Controller, Script, Camera, Tablet, MyAvatar, Quat, SoundCache, HMD, Vec3, Uuid, Messages */ (function () { "use strict"; Script.include("./libraries/utils.js"); Script.include("./libraries/controllerDispatcherUtils.js"); var controllerStandard = Controller.Standard; var UI, ui = null, State, miniState = null, miniTabletEnabled = true, // Hands. NO_HAND = -1, LEFT_HAND = 0, RIGHT_HAND = 1, HAND_NAMES = ["LeftHand", "RightHand"], // Track controller grabbing state. HIFI_OBJECT_MANIPULATION_CHANNEL = "Hifi-Object-Manipulation", grabbingHand = NO_HAND, grabbedItem = null, // Miscellaneous. tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"), DEBUG = false; 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" || Camera.mode === "first person look at") { jointName = "_CONTROLLER_LEFTHAND"; } else if (Camera.mode === "third person") { jointName = "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND"; } else { jointName = "LeftHand"; } } else { if (Camera.mode === "first person" || Camera.mode === "first person look at") { 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; } UI = function () { if (!(this instanceof UI)) { return new UI(); } var // Base overlay miniOverlay = null, MINI_MODEL = Script.resolvePath("./assets/models/miniTabletBlank.fbx"), MINI_DIMENSIONS = { x: 0.0637, y: 0.0965, z: 0.0045 }, // Proportional to tablet proper. MINI_POSITIONS = [ { x: -0.01, // Distance across hand. y: 0.08, // Distance from joint. z: 0.06 // Distance above palm. }, { x: 0.01, // Distance across hand. y: 0.08, // Distance from joint. z: 0.06 // Distance above palm. } ], DEGREES_180 = 180, MINI_PICTH = 40, MINI_ROTATIONS = [ Quat.fromVec3Degrees({ x: 0, y: DEGREES_180 - MINI_PICTH, z: 90 }), Quat.fromVec3Degrees({ x: 0, y: DEGREES_180 + MINI_PICTH, z: -90 }) ], // UI overlay. uiHand = LEFT_HAND, miniUIOverlay = null, MINI_UI_HTML = Script.resolvePath("./html/miniTablet.html"), MINI_HAND_UI_HTML = Script.resolvePath("./html/miniHandsTablet.html"), MINI_UI_DIMENSIONS = { x: 0.059, y: 0.0865, z: 0.001 }, MINI_UI_WIDTH_PIXELS = 150, METERS_TO_INCHES = 39.3701, MINI_UI_DPI = MINI_UI_WIDTH_PIXELS / (MINI_UI_DIMENSIONS.x * METERS_TO_INCHES), MINI_UI_OFFSET = 0.001, // Above model surface. MINI_UI_LOCAL_POSITION = { x: 0.0002, y: 0.0024, z: -(MINI_DIMENSIONS.z / 2 + MINI_UI_OFFSET) }, MINI_UI_LOCAL_ROTATION = Quat.fromVec3Degrees({ x: 0, y: 180, z: 0 }), miniUIOverlayEnabled = false, MINI_UI_OVERLAY_ENABLED_DELAY = 500, miniOverlayObject = null, // Button icons. MUTE_ON_ICON = Script.resourcesPath() + "icons/tablet-icons/mic-mute-a.svg", MUTE_OFF_ICON = Script.resourcesPath() + "icons/tablet-icons/mic-unmute-i.svg", GOTO_ICON = Script.resourcesPath() + "icons/tablet-icons/goto-i.svg", // Expansion to tablet. MINI_EXPAND_HANDLES = [ // Normalized coordinates in range [-0.5, 0.5] about center of mini tablet. { x: 0.5, y: -0.65, z: 0 }, { x: -0.5, y: -0.65, z: 0 } ], MINI_EXPAND_DELTA_ROTATION = Quat.fromVec3Degrees({ x: -5, y: 0, z: 0 }), MINI_EXPAND_HANDLES_OTHER = [ // Different handles when expanding after being grabbed by other hand, { x: 0.5, y: -0.4, z: 0 }, { x: -0.5, y: -0.4, z: 0 } ], MINI_EXPAND_DELTA_ROTATION_OTHER = Quat.IDENTITY, miniExpandHand, miniExpandHandles = MINI_EXPAND_HANDLES, miniExpandDeltaRotation = MINI_EXPAND_HANDLES_OTHER, miniExpandLocalPosition, miniExpandLocalRotation = Quat.IDENTITY, miniInitialWidth, miniTargetWidth, miniTargetLocalRotation, // Laser pointing at. isBodyHovered = false, // EventBridge. READY_MESSAGE = "ready", // Engine <== Dialog HOVER_MESSAGE = "hover", // Engine <== Dialog UNHOVER_MESSAGE = "unhover", // Engine <== Dialog MUTE_MESSAGE = "mute", // Engine <=> Dialog GOTO_MESSAGE = "goto", // Engine <=> Dialog EXPAND_MESSAGE = "expand", // Engine <== Dialog // 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)); function updateMutedStatus() { if (miniOverlayObject) { var isMuted = Audio.muted; miniOverlayObject.emitScriptEvent(JSON.stringify({ type: MUTE_MESSAGE, on: isMuted, icon: isMuted ? MUTE_ON_ICON : MUTE_OFF_ICON })); } } function setGotoIcon() { if (miniOverlayObject) { miniOverlayObject.emitScriptEvent(JSON.stringify({ type: GOTO_MESSAGE, icon: GOTO_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(); setGotoIcon(); break; case HOVER_MESSAGE: if (message.target === "body") { // Laser status. isBodyHovered = true; } else if (message.target === "button") { // Audio feedback. playSound(hoverSound, HOVER_VOLUME); } break; case UNHOVER_MESSAGE: if (message.target === "body") { // Laser status. isBodyHovered = false; } break; case MUTE_MESSAGE: // Toggle mute. playSound(clickSound, CLICK_VOLUME); Audio.muted = !Audio.muted; break; case GOTO_MESSAGE: // Goto. playSound(clickSound, CLICK_VOLUME); miniState.setState(miniState.MINI_EXPANDING, { hand: uiHand, goto: true }); break; case EXPAND_MESSAGE: // Expand tablet; playSound(clickSound, CLICK_VOLUME); miniState.setState(miniState.MINI_EXPANDING, { hand: uiHand, goto: false }); break; } } function updateMiniTabletID() { HMD.miniTabletID = miniOverlay; HMD.miniTabletScreenID = miniUIOverlay; HMD.miniTabletHand = miniOverlay ? uiHand : -1; } function playSound(sound, volume) { Audio.playSound(sound, { position: uiHand === LEFT_HAND ? MyAvatar.getLeftPalmPosition() : MyAvatar.getRightPalmPosition(), volume: volume, localOnly: true }); } function getUIPosition(hand) { return MINI_POSITIONS[hand]; } function getMiniTabletID() { return miniOverlay; } function getMiniTabletProperties() { var properties = Entities.getEntityProperties(miniOverlay, ["position", "orientation"]); return { position: properties.position, orientation: properties.orientation }; } function isLaserPointingAt() { return isBodyHovered; } function show(hand) { var initialScale = 0.01; // Start very small. uiHand = hand; Entities.editEntity(miniOverlay, { "parentID": MyAvatar.SELF_ID, "parentJointIndex": handJointIndex(hand), "localPosition": Vec3.multiply(MyAvatar.sensorToWorldScale, MINI_POSITIONS[hand]), "localRotation": MINI_ROTATIONS[hand], "dimensions": Vec3.multiply(initialScale, MINI_DIMENSIONS), "grab": { "grabbable": true }, "visible": true }); Entities.editEntity(miniUIOverlay, { "sourceUrl": handsAreTracked() ? MINI_HAND_UI_HTML : MINI_UI_HTML, "localPosition": Vec3.multiply(MyAvatar.sensorToWorldScale, MINI_UI_LOCAL_POSITION), "localRotation": MINI_UI_LOCAL_ROTATION, "dimensions": Vec3.multiply(initialScale, MINI_UI_DIMENSIONS), "dpi": MINI_UI_DPI / initialScale, "visible": true }); updateMiniTabletID(); if (!miniUIOverlayEnabled) { // 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 () { Entities.editEntity(miniUIOverlay, { "alpha": 1.0 }); }, MINI_UI_OVERLAY_ENABLED_DELAY); } } function size(scaleFactor) { // Scale UI in place. Entities.editEntity(miniOverlay, { "dimensions": Vec3.multiply(scaleFactor, MINI_DIMENSIONS) }); Entities.editEntity(miniUIOverlay, { "localPosition": Vec3.multiply(scaleFactor, MINI_UI_LOCAL_POSITION), "dimensions": Vec3.multiply(scaleFactor, MINI_UI_DIMENSIONS), "dpi": MINI_UI_DPI / scaleFactor }); updateRotation(); } function startExpandingTablet(hand) { // Expansion details. if (hand === uiHand) { miniExpandHandles = MINI_EXPAND_HANDLES; miniExpandDeltaRotation = MINI_EXPAND_DELTA_ROTATION; } else { miniExpandHandles = MINI_EXPAND_HANDLES_OTHER; miniExpandDeltaRotation = MINI_EXPAND_DELTA_ROTATION_OTHER; } // Grab details. var properties = Entities.getEntityProperties(miniOverlay, ["localPosition", "localRotation"]); miniExpandHand = hand; miniExpandLocalRotation = properties.localRotation; miniExpandLocalPosition = Vec3.sum(properties.localPosition, Vec3.multiplyQbyV(miniExpandLocalRotation, Vec3.multiplyVbyV(miniExpandHandles[miniExpandHand], MINI_DIMENSIONS))); // Start expanding. miniInitialWidth = MINI_DIMENSIONS.x; miniTargetWidth = getTabletWidthFromSettings(); miniTargetLocalRotation = Quat.multiply(miniExpandLocalRotation, miniExpandDeltaRotation); } function sizeAboutHandles(scaleFactor) { // Scale UI and move per handles. var tabletScaleFactor, dimensions, localRotation, localPosition; tabletScaleFactor = MyAvatar.sensorToWorldScale * (1 + scaleFactor * (miniTargetWidth - miniInitialWidth) / miniInitialWidth); dimensions = Vec3.multiply(tabletScaleFactor, MINI_DIMENSIONS); localRotation = Quat.mix(miniExpandLocalRotation, miniTargetLocalRotation, scaleFactor); localPosition = Vec3.sum(miniExpandLocalPosition, Vec3.multiplyQbyV(miniExpandLocalRotation, Vec3.multiply(-tabletScaleFactor, Vec3.multiplyVbyV(miniExpandHandles[miniExpandHand], MINI_DIMENSIONS))) ); localPosition = Vec3.sum(localPosition, Vec3.multiplyQbyV(miniExpandLocalRotation, { 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 })); Entities.editEntity(miniOverlay, { "localPosition": localPosition, "localRotation": localRotation, "dimensions": dimensions }); // FIXME: Temporary code change to try not displaying UI when mini tablet is expanding to become the tablet proper. Entities.editEntity(miniUIOverlay, { /* "localPosition": Vec3.multiply(tabletScaleFactor, MINI_UI_LOCAL_POSITION), "dimensions": Vec3.multiply(tabletScaleFactor, MINI_UI_DIMENSIONS), "dpi": MINI_UI_DPI / tabletScaleFactor */ "visible": false }); } function updateRotation() { // Update the rotation of the tablet about its face normal so that its base is horizontal. var COS_5_DEGREES = 0.996, RADIANS_TO_DEGREES = DEGREES_180 / Math.PI, defaultLocalRotation, handOrientation, defaultOrientation, faceNormal, desiredOrientation, defaultYAxis, desiredYAxis, cross, dot, deltaAngle, deltaRotation, localRotation; if (Entities.getEntityProperties(miniOverlay, ["parentJointIndex"]).parentJointIndex !== handJointIndex(uiHand)) { // Overlay has been grabbed by other hand but this script hasn't received notification yet. return; } defaultLocalRotation = MINI_ROTATIONS[uiHand]; handOrientation = Quat.multiply(MyAvatar.orientation, MyAvatar.getAbsoluteJointRotationInObjectFrame(handJointIndex(uiHand))); defaultOrientation = Quat.multiply(handOrientation, defaultLocalRotation); faceNormal = Vec3.multiplyQbyV(defaultOrientation, Vec3.UNIT_Z); if (Math.abs(Vec3.dot(faceNormal, Vec3.UNIT_Y)) > COS_5_DEGREES) { // Don't rotate mini tablet if almost flat in the x-z plane. return; } else { // Rotate the tablet so that its base is parallel with the x-z plane. desiredOrientation = Quat.lookAt(Vec3.ZERO, Vec3.multiplyQbyV(defaultOrientation, Vec3.UNIT_Z), Vec3.UNIT_Y); defaultYAxis = Vec3.multiplyQbyV(defaultOrientation, Vec3.UNIT_Y); desiredYAxis = Vec3.multiplyQbyV(desiredOrientation, Vec3.UNIT_Y); cross = Vec3.cross(defaultYAxis, desiredYAxis); dot = Vec3.dot(defaultYAxis, desiredYAxis); deltaAngle = Math.atan2(Vec3.length(cross), dot) * RADIANS_TO_DEGREES; if (Vec3.dot(cross, Vec3.multiplyQbyV(desiredOrientation, Vec3.UNIT_Z)) > 0) { deltaAngle = -deltaAngle; } deltaRotation = Quat.angleAxis(deltaAngle, Vec3.multiplyQbyV(defaultLocalRotation, Vec3.UNIT_Z)); localRotation = Quat.multiply(deltaRotation, defaultLocalRotation); } Entities.editEntity(miniOverlay, { "localRotation": localRotation }); } function release() { Entities.editEntity(miniOverlay, { "parentID": Uuid.NONE, // Release hold so that hand can grab tablet proper. "grab": { "grabbable": false } }); } function hide() { Entities.editEntity(miniOverlay, { "visible": false }); Entities.editEntity(miniUIOverlay, { "visible": false }); } function checkEventBridge() { // The miniUIOverlay overlay's overlay object is not available immediately the overlay is created so we have to // provide a means to check for and connect it when it does become available. if (miniOverlayObject) { return; } miniOverlayObject = Entities.getEntityObject(miniUIOverlay); if (miniOverlayObject) { miniOverlayObject.webEventReceived.connect(onWebEventReceived); } } function create() { miniOverlay = Entities.addEntity({ "type": "Model", "modelURL": MINI_MODEL, "dimensions": Vec3.multiply(MyAvatar.sensorToWorldScale, MINI_DIMENSIONS), "primitiveMode": "solid", "grab": { "grabbable": true }, "renderLayer": "world", "visible": false }, "local"); miniUIOverlay = Entities.addEntity({ "type": "Web", "sourceUrl": handsAreTracked() ? MINI_HAND_UI_HTML : MINI_UI_HTML, "parentID": miniOverlay, "localPosition": Vec3.multiply(MyAvatar.sensorToWorldScale, MINI_UI_LOCAL_POSITION), "localRotation": MINI_UI_LOCAL_ROTATION, "dimensions": Vec3.multiply(MyAvatar.sensorToWorldScale, MINI_UI_DIMENSIONS), "dpi": MINI_UI_DPI / MyAvatar.sensorToWorldScale, "alpha": 0, // Hide overlay while its content is being created. "grab": { "grabbable": false }, "showKeyboardFocusHighlight": false, "renderLayer": "world", "visible": false