mirror of
https://github.com/overte-org/overte.git
synced 2025-04-29 18:22:38 +02:00
388 lines
13 KiB
JavaScript
388 lines
13 KiB
JavaScript
//
|
|
// tabletRezzer.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 // Overlay
|
|
proxyOverlay = null,
|
|
TABLET_PROXY_MODEL = Script.resolvePath("./assets/models/tinyTablet.fbx"),
|
|
TABLET_PROXY_DIMENSIONS = { x: 0.0637, y: 0.0965, z: 0.0046 }, // Proportional to tablet proper.
|
|
TABLET_PROXY_POSITION_LEFT_HAND = {
|
|
x: 0,
|
|
y: 0.07, // Distance from joint.
|
|
z: 0.07 // Distance above palm.
|
|
},
|
|
TABLET_PROXY_POSITION_RIGHT_HAND = {
|
|
x: 0,
|
|
y: 0.07, // Distance from joint.
|
|
z: 0.07 // Distance above palm.
|
|
},
|
|
/*
|
|
// Aligned cross-palm.
|
|
TABLET_PROXY_ROTATION_LEFT_HAND = Quat.fromVec3Degrees({ x: 0, y: 180, z: 90 }),
|
|
TABLET_PROXY_ROTATION_RIGHT_HAND = Quat.fromVec3Degrees({ x: 0, y: 180, z: -90 }),
|
|
*/
|
|
// Aligned with palm.
|
|
TABLET_PROXY_ROTATION_LEFT_HAND = Quat.fromVec3Degrees({ x: 0, y: 180, z: 0 }),
|
|
TABLET_PROXY_ROTATION_RIGHT_HAND = Quat.fromVec3Degrees({ x: 0, y: 180, z: 0 }),
|
|
|
|
// State machine
|
|
PROXY_HIDDEN = 0,
|
|
PROXY_VISIBLE = 1,
|
|
PROXY_GRABBED = 2,
|
|
PROXY_EXPANDING = 3,
|
|
TABLET_OPEN = 4,
|
|
STATE_STRINGS = ["PROXY_HIDDEN", "PROXY_VISIBLE", "PROXY_GRABBED", "PROXY_EXPANDING", "TABLET_OPEN"],
|
|
STATE_MACHINE,
|
|
rezzerState = PROXY_HIDDEN,
|
|
proxyHand,
|
|
PROXY_EXPAND_DURATION = 500,
|
|
PROXY_EXPAND_TIMEOUT = 25,
|
|
proxyExpandTimer = null,
|
|
proxyExpandStart,
|
|
proxyInitialWidth,
|
|
proxyTargetWidth,
|
|
tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"),
|
|
|
|
// 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,
|
|
|
|
LEFT_HAND = 0,
|
|
RIGHT_HAND = 1,
|
|
HAND_NAMES = ["LeftHand", "RightHand"],
|
|
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));
|
|
}
|
|
|
|
// #endregion
|
|
|
|
// #region State Machine ===================================================================================================
|
|
|
|
function enterProxyHidden() {
|
|
if (proxyOverlay) {
|
|
Overlays.deleteOverlay(proxyOverlay);
|
|
proxyOverlay = null;
|
|
}
|
|
}
|
|
|
|
function enterProxyVisible(hand) {
|
|
proxyHand = hand;
|
|
proxyOverlay = Overlays.addOverlay("model", {
|
|
url: TABLET_PROXY_MODEL,
|
|
parentID: MyAvatar.SELF_ID,
|
|
parentJointIndex: handJointIndex(proxyHand),
|
|
localPosition: Vec3.multiply(avatarScale,
|
|
proxyHand === LEFT_HAND ? TABLET_PROXY_POSITION_LEFT_HAND : TABLET_PROXY_POSITION_RIGHT_HAND),
|
|
localRotation: proxyHand === LEFT_HAND ? TABLET_PROXY_ROTATION_LEFT_HAND : TABLET_PROXY_ROTATION_RIGHT_HAND,
|
|
dimensions: Vec3.multiply(avatarScale, TABLET_PROXY_DIMENSIONS),
|
|
solid: true,
|
|
grabbable: true,
|
|
displayInFront: true,
|
|
visible: true
|
|
});
|
|
}
|
|
|
|
function expandProxy() {
|
|
var scaleFactor = (Date.now() - proxyExpandStart) / PROXY_EXPAND_DURATION;
|
|
if (scaleFactor < 1) {
|
|
Overlays.editOverlay(proxyOverlay, {
|
|
dimensions: Vec3.multiply(
|
|
avatarScale * (1 + scaleFactor * (proxyTargetWidth - proxyInitialWidth) / proxyInitialWidth),
|
|
TABLET_PROXY_DIMENSIONS)
|
|
});
|
|
proxyExpandTimer = Script.setTimeout(expandProxy, PROXY_EXPAND_TIMEOUT);
|
|
return;
|
|
}
|
|
|
|
proxyExpandTimer = null;
|
|
setState(TABLET_OPEN);
|
|
}
|
|
|
|
function enterProxyExpanding() {
|
|
// Detach from hand.
|
|
Overlays.editOverlay(proxyOverlay, {
|
|
parentID: Uuid.NULL,
|
|
parentJointIndex: -1
|
|
});
|
|
|
|
// Start expanding.
|
|
proxyInitialWidth = TABLET_PROXY_DIMENSIONS.x;
|
|
proxyTargetWidth = getTabletWidthFromSettings();
|
|
proxyExpandStart = Date.now();
|
|
proxyExpandTimer = Script.setTimeout(expandProxy, PROXY_EXPAND_TIMEOUT);
|
|
}
|
|
|
|
function exitProxyExpanding() {
|
|
if (proxyExpandTimer !== null) {
|
|
Script.clearTimeout(proxyExpandTimer);
|
|
proxyExpandTimer = null;
|
|
}
|
|
}
|
|
|
|
function enterTabletOpen() {
|
|
var proxyOverlayProperties = Overlays.getProperties(proxyOverlay, ["position", "orientation"]);
|
|
|
|
Overlays.deleteOverlay(proxyOverlay);
|
|
proxyOverlay = null;
|
|
|
|
Overlays.editOverlay(HMD.tabletID, {
|
|
position: proxyOverlayProperties.position,
|
|
orientation: proxyOverlayProperties.orientation
|
|
});
|
|
HMD.openTablet(true);
|
|
}
|
|
|
|
STATE_MACHINE = {
|
|
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: null,
|
|
exit: null
|
|
},
|
|
PROXY_VISIBLE: { // Tablet proxy is visible and attached to hand.
|
|
enter: enterProxyVisible,
|
|
update: null,
|
|
exit: null
|
|
},
|
|
PROXY_GRABBED: { // Other hand has grabbed and is holding the tablet proxy.
|
|
enter: null,
|
|
update: null,
|
|
exit: null
|
|
},
|
|
PROXY_EXPANDING: { // Tablet proxy has been released from grab and is expanding before showing tablet proper.
|
|
enter: enterProxyExpanding,
|
|
update: null,
|
|
exit: exitProxyExpanding
|
|
},
|
|
TABLET_OPEN: { // Tablet proper is being displayed.
|
|
enter: enterTabletOpen,
|
|
update: null,
|
|
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 + "!");
|
|
}
|
|
}
|
|
|
|
// #endregion
|
|
|
|
// #region Events ==========================================================================================================
|
|
|
|
function shouldShowProxy(hand) {
|
|
// Should show tablet proxy if hand is oriented towards the camera.
|
|
var pose,
|
|
jointIndex,
|
|
handPosition,
|
|
handOrientation;
|
|
|
|
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));
|
|
|
|
return Vec3.dot(Vec3.normalize(Vec3.subtract(handPosition, Camera.position)), Quat.getForward(handOrientation))
|
|
> MIN_HAND_CAMERA_ANGLE_COS;
|
|
}
|
|
|
|
function update() {
|
|
// Assumes that is HMD.mounted.
|
|
switch (rezzerState) {
|
|
case PROXY_HIDDEN:
|
|
// Don't show proxy is tablet is already displayed or are in toolbar mode.
|
|
if (HMD.showTablet || tablet.toolbarMode) {
|
|
break;
|
|
}
|
|
// Compare palm directions of hands with vectors from palms to camera.
|
|
if (shouldShowProxy(LEFT_HAND)) {
|
|
setState(PROXY_VISIBLE, LEFT_HAND);
|
|
} else if (shouldShowProxy(RIGHT_HAND)) {
|
|
setState(PROXY_VISIBLE, RIGHT_HAND);
|
|
}
|
|
break;
|
|
case PROXY_VISIBLE:
|
|
// Hide proxy if tablet has been displayed by other means.
|
|
if (HMD.showTablet) {
|
|
setState(PROXY_HIDDEN);
|
|
break;
|
|
}
|
|
// Check that palm direction of proxy hand still less than maximum angle.
|
|
if (!shouldShowProxy(proxyHand)) {
|
|
setState(PROXY_HIDDEN);
|
|
}
|
|
break;
|
|
case PROXY_GRABBED:
|
|
case PROXY_EXPANDING:
|
|
// Hide proxy if tablet has been displayed by other means.
|
|
if (HMD.showTablet) {
|
|
setState(PROXY_HIDDEN);
|
|
}
|
|
break;
|
|
case TABLET_OPEN:
|
|
// Immediately transition back to PROXY_HIDDEN.
|
|
setState(PROXY_HIDDEN);
|
|
break;
|
|
default:
|
|
error("Missing case: " + rezzerState);
|
|
}
|
|
|
|
updateTimer = Script.setTimeout(update, UPDATE_INTERVAL);
|
|
}
|
|
|
|
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;
|
|
|
|
if (channel !== HIFI_OBJECT_MANIPULATION_CHANNEL) {
|
|
return;
|
|
}
|
|
|
|
message = JSON.parse(data);
|
|
if (message.grabbedEntity !== proxyOverlay) {
|
|
return;
|
|
}
|
|
|
|
// Don't transition into PROXY_GRABBED unless the tablet proxy is grabbed with "other" hand. However, once it has been
|
|
// grabbed then the original hand can grab it back and grow it from there.
|
|
if (message.action === "grab" && message.joint !== HAND_NAMES[proxyHand] && rezzerState === PROXY_VISIBLE) {
|
|
setState(PROXY_GRABBED);
|
|
} else if (message.action === "release" && rezzerState === PROXY_GRABBED) {
|
|
setState(PROXY_EXPANDING);
|
|
}
|
|
}
|
|
|
|
function onMountedChanged() {
|
|
// Tablet proxy only available when HMD is mounted.
|
|
|
|
if (HMD.mounted) {
|
|
if (updateTimer === null) {
|
|
update();
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (updateTimer !== null) {
|
|
Script.clearTimeout(updateTimer);
|
|
updateTimer = null;
|
|
}
|
|
if (rezzerState !== PROXY_HIDDEN) {
|
|
setState(PROXY_HIDDEN);
|
|
}
|
|
}
|
|
|
|
// #endregion
|
|
|
|
// #region Start-up and tear-down ==========================================================================================
|
|
|
|
function setUp() {
|
|
MyAvatar.scaleChanged.connect(onScaleChanged);
|
|
|
|
Messages.subscribe(HIFI_OBJECT_MANIPULATION_CHANNEL);
|
|
Messages.messageReceived.connect(onMessageReceived);
|
|
|
|
HMD.mountedChanged.connect(onMountedChanged);
|
|
HMD.displayModeChanged.connect(onMountedChanged); // For the case that the HMD is already worn when the script starts.
|
|
if (HMD.mounted) {
|
|
update();
|
|
}
|
|
}
|
|
|
|
function tearDown() {
|
|
if (updateTimer !== null) {
|
|
Script.clearTimeout(updateTimer);
|
|
updateTimer = null;
|
|
}
|
|
|
|
setState(PROXY_HIDDEN);
|
|
|
|
HMD.displayModeChanged.disconnect(onMountedChanged);
|
|
HMD.mountedChanged.disconnect(onMountedChanged);
|
|
|
|
Messages.messageReceived.disconnect(onMessageReceived);
|
|
Messages.unsubscribe(HIFI_OBJECT_MANIPULATION_CHANNEL);
|
|
|
|
MyAvatar.scaleChanged.disconnect(onScaleChanged);
|
|
}
|
|
|
|
setUp();
|
|
Script.scriptEnding.connect(tearDown);
|
|
|
|
// #endregion
|
|
|
|
}());
|