mirror of
https://github.com/overte-org/overte.git
synced 2025-05-06 15:49:39 +02:00
686 lines
23 KiB
JavaScript
686 lines
23 KiB
JavaScript
"use strict";
|
|
|
|
//
|
|
// vr-edit.js
|
|
//
|
|
// Created by David Rowe on 27 Jun 2017.
|
|
// Copyright 2017 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
|
|
//
|
|
|
|
(function () {
|
|
|
|
var APP_NAME = "VR EDIT", // TODO: App name.
|
|
APP_ICON_INACTIVE = "icons/tablet-icons/edit-i.svg", // TODO: App icons.
|
|
APP_ICON_ACTIVE = "icons/tablet-icons/edit-a.svg",
|
|
tablet,
|
|
button,
|
|
isAppActive = false,
|
|
|
|
VR_EDIT_SETTING = "io.highfidelity.isVREditing", // Note: This constant is duplicated in utils.js.
|
|
|
|
hands = [],
|
|
LEFT_HAND = 0,
|
|
RIGHT_HAND = 1,
|
|
|
|
UPDATE_LOOP_TIMEOUT = 16,
|
|
updateTimer = null,
|
|
|
|
Highlights,
|
|
Selection,
|
|
Laser,
|
|
Hand,
|
|
|
|
AVATAR_SELF_ID = "{00000000-0000-0000-0000-000000000001}",
|
|
NULL_UUID = "{00000000-0000-0000-0000-000000000000}",
|
|
HALF_TREE_SCALE = 16384;
|
|
|
|
Highlights = function (hand) {
|
|
// Draws highlights on selected entities.
|
|
|
|
var handOverlay,
|
|
entityOverlays = [],
|
|
HIGHLIGHT_COLOR = { red: 240, green: 240, blue: 0 },
|
|
HAND_HIGHLIGHT_ALPHA = 0.35,
|
|
ENTITY_HIGHLIGHT_ALPHA = 0.8,
|
|
HAND_HIGHLIGHT_DIMENSIONS = { x: 0.2, y: 0.2, z: 0.2 },
|
|
HAND_HIGHLIGHT_OFFSET = { x: 0.0, y: 0.11, z: 0.02 };
|
|
|
|
handOverlay = Overlays.addOverlay("sphere", {
|
|
dimensions: HAND_HIGHLIGHT_DIMENSIONS,
|
|
parentID: AVATAR_SELF_ID,
|
|
parentJointIndex: MyAvatar.getJointIndex(hand === LEFT_HAND
|
|
? "_CONTROLLER_LEFTHAND"
|
|
: "_CONTROLLER_RIGHTHAND"),
|
|
localPosition: HAND_HIGHLIGHT_OFFSET,
|
|
color: HIGHLIGHT_COLOR,
|
|
alpha: HAND_HIGHLIGHT_ALPHA,
|
|
solid: true,
|
|
drawInFront: true,
|
|
ignoreRayIntersection: true,
|
|
visible: false
|
|
});
|
|
|
|
function maybeAddEntityOverlay(index) {
|
|
if (index >= entityOverlays.length) {
|
|
entityOverlays.push(Overlays.addOverlay("cube", {
|
|
color: HIGHLIGHT_COLOR,
|
|
alpha: ENTITY_HIGHLIGHT_ALPHA,
|
|
solid: false,
|
|
drawInFront: true,
|
|
ignoreRayIntersection: true,
|
|
visible: false
|
|
}));
|
|
}
|
|
}
|
|
|
|
function editEntityOverlay(index, details) {
|
|
Overlays.editOverlay(entityOverlays[index], {
|
|
position: details.position,
|
|
registrationPoint: details.registrationPoint,
|
|
rotation: details.rotation,
|
|
dimensions: details.dimensions,
|
|
visible: true
|
|
});
|
|
}
|
|
|
|
function display(handSelected, selection) {
|
|
var i,
|
|
length;
|
|
|
|
// Show/hide hand overlay.
|
|
Overlays.editOverlay(handOverlay, { visible: handSelected });
|
|
|
|
// Add/edit entity overlay.
|
|
for (i = 0, length = selection.length; i < length; i += 1) {
|
|
maybeAddEntityOverlay(i);
|
|
editEntityOverlay(i, selection[i]);
|
|
}
|
|
|
|
// Delete extra entity overlays.
|
|
for (i = entityOverlays.length - 1, length = selection.length; i >= length; i -= 1) {
|
|
Overlays.deleteOverlay(entityOverlays[i]);
|
|
entityOverlays.splice(i, 1);
|
|
}
|
|
}
|
|
|
|
function clear() {
|
|
var i,
|
|
length;
|
|
|
|
// Hide hand overlay.
|
|
Overlays.editOverlay(handOverlay, { visible: false });
|
|
|
|
// Delete entity overlays.
|
|
for (i = 0, length = entityOverlays.length; i < length; i += 1) {
|
|
Overlays.deleteOverlay(entityOverlays[i]);
|
|
}
|
|
entityOverlays = [];
|
|
}
|
|
|
|
function destroy() {
|
|
clear();
|
|
Overlays.deleteOverlay(handOverlay);
|
|
}
|
|
|
|
if (!this instanceof Highlights) {
|
|
return new Highlights();
|
|
}
|
|
|
|
return {
|
|
display: display,
|
|
clear: clear,
|
|
destroy: destroy
|
|
};
|
|
};
|
|
|
|
Selection = function () {
|
|
// Manages set of selected entities. Currently supports just one set of linked entities.
|
|
|
|
var selection = [],
|
|
selectedEntityID = null,
|
|
selectionPosition = null,
|
|
selectionOrientation,
|
|
rootEntityID;
|
|
|
|
function traverseEntityTree(id, result) {
|
|
// Recursively traverses tree of entities and their children, gather IDs and properties.
|
|
var children,
|
|
properties,
|
|
SELECTION_PROPERTIES = ["position", "registrationPoint", "rotation", "dimensions"],
|
|
i,
|
|
length;
|
|
|
|
properties = Entities.getEntityProperties(id, SELECTION_PROPERTIES);
|
|
result.push({
|
|
id: id,
|
|
position: properties.position,
|
|
registrationPoint: properties.registrationPoint,
|
|
rotation: properties.rotation,
|
|
dimensions: properties.dimensions
|
|
});
|
|
|
|
children = Entities.getChildrenIDs(id);
|
|
for (i = 0, length = children.length; i < length; i += 1) {
|
|
traverseEntityTree(children[i], result);
|
|
}
|
|
}
|
|
|
|
function select(entityID) {
|
|
var entityProperties,
|
|
PARENT_PROPERTIES = ["parentID", "position", "rotation"];
|
|
|
|
// Find root parent.
|
|
rootEntityID = entityID;
|
|
entityProperties = Entities.getEntityProperties(rootEntityID, PARENT_PROPERTIES);
|
|
while (entityProperties.parentID !== NULL_UUID) {
|
|
rootEntityID = entityProperties.parentID;
|
|
entityProperties = Entities.getEntityProperties(rootEntityID, PARENT_PROPERTIES);
|
|
}
|
|
|
|
// Selection position and orientation is that of the root entity.
|
|
selectionPosition = entityProperties.position;
|
|
selectionOrientation = entityProperties.rotation;
|
|
|
|
// Find all children.
|
|
selection = [];
|
|
traverseEntityTree(rootEntityID, selection);
|
|
|
|
selectedEntityID = entityID;
|
|
}
|
|
|
|
function getSelection() {
|
|
return selection;
|
|
}
|
|
|
|
function getPositionAndOrientation() {
|
|
return {
|
|
position: selectionPosition,
|
|
orientation: selectionOrientation
|
|
};
|
|
}
|
|
|
|
function setPositionAndOrientation(position, orientation) {
|
|
selectionPosition = position;
|
|
selectionOrientation = orientation;
|
|
Entities.editEntity(rootEntityID, {
|
|
position: position,
|
|
rotation: orientation
|
|
});
|
|
}
|
|
|
|
function clear() {
|
|
selection = [];
|
|
selectedEntityID = null;
|
|
rootEntityID = null;
|
|
}
|
|
|
|
function destroy() {
|
|
clear();
|
|
}
|
|
|
|
if (!this instanceof Selection) {
|
|
return new Selection();
|
|
}
|
|
|
|
return {
|
|
select: select,
|
|
selection: getSelection,
|
|
getPositionAndOrientation: getPositionAndOrientation,
|
|
setPositionAndOrientation: setPositionAndOrientation,
|
|
clear: clear,
|
|
destroy: destroy
|
|
};
|
|
};
|
|
|
|
Laser = function (side) {
|
|
// Draws hand lasers.
|
|
// May intersect with entities or bounding box of other hand's selection.
|
|
|
|
var hand,
|
|
laserLine = null,
|
|
laserSphere = null,
|
|
|
|
searchDistance = 0.0,
|
|
|
|
SEARCH_SPHERE_SIZE = 0.013, // Per handControllerGrab.js multiplied by 1.2 per handControllerGrab.js.
|
|
SEARCH_SPHERE_FOLLOW_RATE = 0.5, // Per handControllerGrab.js.
|
|
COLORS_GRAB_SEARCHING_HALF_SQUEEZE = { red: 10, green: 10, blue: 255 }, // Per handControllgerGrab.js.
|
|
COLORS_GRAB_SEARCHING_FULL_SQUEEZE = { red: 250, green: 10, blue: 10 }; // Per handControllgerGrab.js.
|
|
|
|
hand = side;
|
|
laserLine = Overlays.addOverlay("line3d", {
|
|
lineWidth: 5,
|
|
alpha: 1.0,
|
|
glow: 1.0,
|
|
ignoreRayIntersection: true,
|
|
drawInFront: true,
|
|
parentID: AVATAR_SELF_ID,
|
|
parentJointIndex: MyAvatar.getJointIndex(hand === LEFT_HAND
|
|
? "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND"
|
|
: "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND"),
|
|
visible: false
|
|
});
|
|
laserSphere = Overlays.addOverlay("circle3d", {
|
|
innerAlpha: 1.0,
|
|
outerAlpha: 0.0,
|
|
solid: true,
|
|
ignoreRayIntersection: true,
|
|
drawInFront: true,
|
|
visible: false
|
|
});
|
|
|
|
function colorPow(color, power) {
|
|
return {
|
|
red: Math.pow(color.red / 255, power) * 255,
|
|
green: Math.pow(color.green / 255, power) * 255,
|
|
blue: Math.pow(color.blue / 255, power) * 255
|
|
};
|
|
}
|
|
|
|
function updateLine(start, end, color) {
|
|
Overlays.editOverlay(laserLine, {
|
|
start: start,
|
|
end: end,
|
|
color: color,
|
|
visible: true
|
|
});
|
|
}
|
|
|
|
function updateSphere(location, size, color) {
|
|
var rotation,
|
|
brightColor;
|
|
|
|
rotation = Quat.lookAt(location, Camera.getPosition(), Vec3.UP);
|
|
brightColor = colorPow(color, 0.06);
|
|
|
|
Overlays.editOverlay(laserSphere, {
|
|
position: location,
|
|
rotation: rotation,
|
|
innerColor: brightColor,
|
|
outerColor: color,
|
|
outerRadius: size,
|
|
visible: true
|
|
});
|
|
}
|
|
|
|
function update(origin, direction, distance, isClicked) {
|
|
var searchTarget,
|
|
sphereSize,
|
|
color;
|
|
|
|
searchDistance = SEARCH_SPHERE_FOLLOW_RATE * searchDistance + (1.0 - SEARCH_SPHERE_FOLLOW_RATE) * distance;
|
|
searchTarget = Vec3.sum(origin, Vec3.multiply(searchDistance, direction));
|
|
sphereSize = SEARCH_SPHERE_SIZE * searchDistance;
|
|
color = isClicked ? COLORS_GRAB_SEARCHING_FULL_SQUEEZE : COLORS_GRAB_SEARCHING_HALF_SQUEEZE;
|
|
|
|
updateLine(origin, searchTarget, color);
|
|
updateSphere(searchTarget, sphereSize, color);
|
|
}
|
|
|
|
function clear() {
|
|
Overlays.editOverlay(laserLine, { visible: false });
|
|
Overlays.editOverlay(laserSphere, { visible: false });
|
|
}
|
|
|
|
function destroy() {
|
|
Overlays.deleteOverlay(laserLine);
|
|
Overlays.deleteOverlay(laserSphere);
|
|
}
|
|
|
|
if (!this instanceof Laser) {
|
|
return new Laser();
|
|
}
|
|
|
|
return {
|
|
update: update,
|
|
clear: clear,
|
|
destroy: destroy
|
|
};
|
|
};
|
|
|
|
Hand = function (side) {
|
|
// Hand controller input.
|
|
// Each hand has a laser, an entity selection, and entity highlighter.
|
|
|
|
var hand,
|
|
handController,
|
|
controllerTrigger,
|
|
controllerTriggerClicked,
|
|
|
|
TRIGGER_ON_VALUE = 0.15, // Per handControllerGrab.js.
|
|
TRIGGER_OFF_VALUE = 0.1, // Per handControllerGrab.js.
|
|
GRAB_POINT_SPHERE_OFFSET = { x: 0.04, y: 0.13, z: 0.039 }, // Per HmdDisplayPlugin.cpp and controllers.js.
|
|
|
|
NEAR_GRAB_RADIUS = 0.1, // Per handControllerGrab.js.
|
|
|
|
PICK_MAX_DISTANCE = 500, // Per handControllerGrab.js.
|
|
PRECISION_PICKING = true,
|
|
NO_INCLUDE_IDS = [],
|
|
NO_EXCLUDE_IDS = [],
|
|
VISIBLE_ONLY = true,
|
|
|
|
isTriggerPressed = false,
|
|
|
|
isLaserOn = false,
|
|
hoveredEntityID = null,
|
|
|
|
EDITIBLE_ENTITY_QUERY_PROPERTYES = ["parentID", "visible", "locked", "type"],
|
|
NONEDITABLE_ENTITY_TYPES = ["Unknown", "Zone", "Light"],
|
|
|
|
isEditing = false,
|
|
initialHandPosition,
|
|
initialHandOrientationInverse,
|
|
initialHandToSelectionVector,
|
|
initialSelectionOrientation,
|
|
laserEditingDistance,
|
|
|
|
laser,
|
|
selection,
|
|
highlights;
|
|
|
|
hand = side;
|
|
if (hand === LEFT_HAND) {
|
|
handController = Controller.Standard.LeftHand;
|
|
controllerTrigger = Controller.Standard.LT;
|
|
controllerTriggerClicked = Controller.Standard.LTClick;
|
|
GRAB_POINT_SPHERE_OFFSET.x = -GRAB_POINT_SPHERE_OFFSET.x;
|
|
} else {
|
|
handController = Controller.Standard.RightHand;
|
|
controllerTrigger = Controller.Standard.RT;
|
|
controllerTriggerClicked = Controller.Standard.RTClick;
|
|
}
|
|
|
|
laser = new Laser(hand);
|
|
selection = new Selection();
|
|
highlights = new Highlights(hand);
|
|
|
|
function startEditing(handPose) {
|
|
var selectionPositionAndOrientation;
|
|
|
|
initialHandPosition = Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, handPose.translation), MyAvatar.position);
|
|
initialHandOrientationInverse = Quat.inverse(Quat.multiply(MyAvatar.orientation, handPose.rotation));
|
|
|
|
selectionPositionAndOrientation = selection.getPositionAndOrientation();
|
|
initialHandToSelectionVector = Vec3.subtract(selectionPositionAndOrientation.position, initialHandPosition);
|
|
initialSelectionOrientation = selectionPositionAndOrientation.orientation;
|
|
|
|
isEditing = true;
|
|
}
|
|
|
|
function applyEdit(handPose) {
|
|
var handPosition,
|
|
handOrientation,
|
|
deltaOrientation,
|
|
selectionPosition,
|
|
selectionOrientation;
|
|
|
|
handPosition = Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, handPose.translation), MyAvatar.position);
|
|
handOrientation = Quat.multiply(MyAvatar.orientation, handPose.rotation);
|
|
|
|
deltaOrientation = Quat.multiply(handOrientation, initialHandOrientationInverse);
|
|
selectionPosition = Vec3.sum(handPosition, Vec3.multiplyQbyV(deltaOrientation, initialHandToSelectionVector));
|
|
selectionOrientation = Quat.multiply(deltaOrientation, initialSelectionOrientation);
|
|
|
|
selection.setPositionAndOrientation(selectionPosition, selectionOrientation);
|
|
}
|
|
|
|
function stopEditing() {
|
|
isEditing = false;
|
|
}
|
|
|
|
function isEditableEntity(entityID) {
|
|
// Entity trees are moved as a group so check the root entity.
|
|
var properties = Entities.getEntityProperties(entityID, EDITIBLE_ENTITY_QUERY_PROPERTYES);
|
|
while (properties.parentID && properties.parentID !== NULL_UUID) {
|
|
properties = Entities.getEntityProperties(properties.parentID, EDITIBLE_ENTITY_QUERY_PROPERTYES);
|
|
}
|
|
return properties.visible && !properties.locked && NONEDITABLE_ENTITY_TYPES.indexOf(properties.type) === -1;
|
|
}
|
|
|
|
function update() {
|
|
var palmPosition,
|
|
entityID,
|
|
entityIDs,
|
|
entitySize,
|
|
size,
|
|
wasLaserOn,
|
|
handPose,
|
|
handPosition,
|
|
handOrientation,
|
|
deltaOrigin,
|
|
pickRay,
|
|
intersection,
|
|
laserLength,
|
|
isTriggerClicked,
|
|
wasEditing,
|
|
i,
|
|
length;
|
|
|
|
// Hand position and orientation.
|
|
handPose = Controller.getPoseValue(handController);
|
|
if (!handPose.valid) {
|
|
isLaserOn = false;
|
|
if (wasLaserOn) {
|
|
laser.clear();
|
|
selection.clear();
|
|
highlights.clear();
|
|
hoveredEntityID = null;
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Controller trigger.
|
|
isTriggerPressed = Controller.getValue(controllerTrigger) > (isTriggerPressed
|
|
? TRIGGER_OFF_VALUE : TRIGGER_ON_VALUE);
|
|
isTriggerClicked = Controller.getValue(controllerTriggerClicked);
|
|
|
|
// Hand-entity intersection, if any.
|
|
entityID = null;
|
|
palmPosition = hand === LEFT_HAND ? MyAvatar.getLeftPalmPosition() : MyAvatar.getRightPalmPosition();
|
|
entityIDs = Entities.findEntities(palmPosition, NEAR_GRAB_RADIUS);
|
|
if (entityIDs.length > 0) {
|
|
// Find smallest, editable entity.
|
|
entitySize = HALF_TREE_SCALE;
|
|
for (i = 0, length = entityIDs.length; i < length; i += 1) {
|
|
if (isEditableEntity(entityIDs[i])) {
|
|
size = Vec3.length(Entities.getEntityProperties(entityIDs[i], "dimensions").dimensions);
|
|
if (size < entitySize) {
|
|
entityID = entityIDs[i];
|
|
entitySize = size;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
intersection = {
|
|
intersects: entityID !== null,
|
|
entityID: entityID,
|
|
handSelected: true
|
|
};
|
|
|
|
// Laser-entity intersection, if any.
|
|
wasLaserOn = isLaserOn;
|
|
if (!intersection.intersects && isTriggerPressed) {
|
|
handPosition = Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, handPose.translation), MyAvatar.position);
|
|
handOrientation = Quat.multiply(MyAvatar.orientation, handPose.rotation);
|
|
deltaOrigin = Vec3.multiplyQbyV(handOrientation, GRAB_POINT_SPHERE_OFFSET);
|
|
pickRay = {
|
|
origin: Vec3.sum(handPosition, deltaOrigin), // Add a bit to ...
|
|
direction: Quat.getUp(handOrientation),
|
|
length: PICK_MAX_DISTANCE
|
|
};
|
|
intersection = Entities.findRayIntersection(pickRay, PRECISION_PICKING, NO_INCLUDE_IDS, NO_EXCLUDE_IDS,
|
|
VISIBLE_ONLY);
|
|
laserLength = isEditing
|
|
? laserEditingDistance
|
|
: (intersection.intersects ? intersection.distance : PICK_MAX_DISTANCE);
|
|
intersection.intersects = isEditableEntity(intersection.entityID);
|
|
isLaserOn = true;
|
|
} else {
|
|
isLaserOn = false;
|
|
}
|
|
|
|
// Laser update.
|
|
if (isLaserOn) {
|
|
laser.update(pickRay.origin, pickRay.direction, laserLength, isTriggerClicked);
|
|
} else if (wasLaserOn) {
|
|
laser.clear();
|
|
}
|
|
|
|
// Highlight / edit.
|
|
if (isTriggerClicked) {
|
|
if (isEditing) {
|
|
// Perform edit.
|
|
applyEdit(handPose);
|
|
} else if (intersection.intersects) {
|
|
// Start editing.
|
|
if (intersection.entityID !== hoveredEntityID) {
|
|
hoveredEntityID = intersection.entityID;
|
|
selection.select(hoveredEntityID);
|
|
}
|
|
highlights.clear();
|
|
if (isLaserOn) {
|
|
laserEditingDistance = laserLength;
|
|
}
|
|
startEditing(handPose);
|
|
}
|
|
} else {
|
|
wasEditing = isEditing;
|
|
if (isEditing) {
|
|
// Stop editing.
|
|
stopEditing();
|
|
}
|
|
if (intersection.intersects) {
|
|
// Hover entities.
|
|
if (wasEditing || intersection.entityID !== hoveredEntityID) {
|
|
hoveredEntityID = intersection.entityID;
|
|
selection.select(hoveredEntityID);
|
|
highlights.display(intersection.handSelected, selection.selection());
|
|
}
|
|
} else {
|
|
// Unhover entities.
|
|
if (hoveredEntityID) {
|
|
selection.clear();
|
|
highlights.clear();
|
|
hoveredEntityID = null;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function destroy() {
|
|
if (laser) {
|
|
laser.destroy();
|
|
laser = null;
|
|
}
|
|
if (selection) {
|
|
selection.destroy();
|
|
selection = null;
|
|
}
|
|
if (highlights) {
|
|
highlights.destroy();
|
|
highlights = null;
|
|
}
|
|
}
|
|
|
|
if (!this instanceof Hand) {
|
|
return new Hand();
|
|
}
|
|
|
|
return {
|
|
update: update,
|
|
destroy: destroy
|
|
};
|
|
};
|
|
|
|
function update() {
|
|
// Main update loop.
|
|
updateTimer = null;
|
|
|
|
hands[LEFT_HAND].update();
|
|
hands[RIGHT_HAND].update();
|
|
|
|
updateTimer = Script.setTimeout(update, UPDATE_LOOP_TIMEOUT);
|
|
}
|
|
|
|
function updateHandControllerGrab() {
|
|
// Communicate app status to handControllerGrab.js.
|
|
Settings.setValue(VR_EDIT_SETTING, isAppActive);
|
|
}
|
|
|
|
function onButtonClicked() {
|
|
isAppActive = !isAppActive;
|
|
updateHandControllerGrab();
|
|
button.editProperties({ isActive: isAppActive });
|
|
|
|
if (isAppActive) {
|
|
update();
|
|
} else {
|
|
Script.clearTimeout(updateTimer);
|
|
updateTimer = null;
|
|
}
|
|
}
|
|
|
|
function setUp() {
|
|
updateHandControllerGrab();
|
|
|
|
tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
|
|
if (!tablet) {
|
|
return;
|
|
}
|
|
|
|
// Tablet/toolbar button.
|
|
button = tablet.addButton({
|
|
icon: APP_ICON_INACTIVE,
|
|
activeIcon: APP_ICON_ACTIVE,
|
|
text: APP_NAME,
|
|
isActive: isAppActive
|
|
});
|
|
if (button) {
|
|
button.clicked.connect(onButtonClicked);
|
|
}
|
|
|
|
// Hands, each with a laser, selection, etc.
|
|
hands[LEFT_HAND] = new Hand(LEFT_HAND);
|
|
hands[RIGHT_HAND] = new Hand(RIGHT_HAND);
|
|
|
|
if (isAppActive) {
|
|
update();
|
|
}
|
|
}
|
|
|
|
function tearDown() {
|
|
if (updateTimer) {
|
|
Script.clearTimeout(updateTimer);
|
|
}
|
|
|
|
isAppActive = false;
|
|
updateHandControllerGrab();
|
|
|
|
if (!tablet) {
|
|
return;
|
|
}
|
|
|
|
if (button) {
|
|
button.clicked.disconnect(onButtonClicked);
|
|
tablet.removeButton(button);
|
|
button = null;
|
|
}
|
|
|
|
if (hands[LEFT_HAND]) {
|
|
hands[LEFT_HAND].destroy();
|
|
hands[LEFT_HAND] = null;
|
|
}
|
|
if (hands[RIGHT_HAND]) {
|
|
hands[RIGHT_HAND].destroy();
|
|
hands[RIGHT_HAND] = null;
|
|
}
|
|
|
|
tablet = null;
|
|
}
|
|
|
|
setUp();
|
|
Script.scriptEnding.connect(tearDown);
|
|
}());
|