overte-JulianGro/scripts/system/libraries/entitySelectionTool.js
2018-04-17 20:31:30 +02:00

2416 lines
104 KiB
JavaScript

//
// entitySelectionTool.js
// examples
//
// Created by Brad hefta-Gaub on 10/1/14.
// Modified by Daniela Fontes * @DanielaFifo and Tiago Andrade @TagoWill on 4/7/2017
// Modified by David Back on 1/9/2018
// Copyright 2014 High Fidelity, Inc.
//
// This script implements a class useful for building tools for editing entities.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
/* global SPACE_LOCAL, SelectionManager */
SPACE_LOCAL = "local";
SPACE_WORLD = "world";
HIGHLIGHT_LIST_NAME = "editHandleHighlightList";
Script.include([
"./controllers.js",
"./utils.js"
]);
SelectionManager = (function() {
var that = {};
// FUNCTION: SUBSCRIBE TO UPDATE MESSAGES
function subscribeToUpdateMessages() {
Messages.subscribe("entityToolUpdates");
Messages.messageReceived.connect(handleEntitySelectionToolUpdates);
}
// FUNCTION: HANDLE ENTITY SELECTION TOOL UDPATES
function handleEntitySelectionToolUpdates(channel, message, sender) {
if (channel !== 'entityToolUpdates') {
return;
}
if (sender !== MyAvatar.sessionUUID) {
return;
}
var wantDebug = false;
var messageParsed;
try {
messageParsed = JSON.parse(message);
} catch (err) {
print("ERROR: entitySelectionTool.handleEntitySelectionToolUpdates - got malformed message: " + message);
return;
}
if (messageParsed.method === "selectEntity") {
if (wantDebug) {
print("setting selection to " + messageParsed.entityID);
}
that.setSelections([messageParsed.entityID]);
}
}
subscribeToUpdateMessages();
var COLOR_ORANGE_HIGHLIGHT = { red: 255, green: 99, blue: 9 }
var editHandleOutlineStyle = {
outlineUnoccludedColor: COLOR_ORANGE_HIGHLIGHT,
outlineOccludedColor: COLOR_ORANGE_HIGHLIGHT,
fillUnoccludedColor: COLOR_ORANGE_HIGHLIGHT,
fillOccludedColor: COLOR_ORANGE_HIGHLIGHT,
outlineUnoccludedAlpha: 1,
outlineOccludedAlpha: 0,
fillUnoccludedAlpha: 0,
fillOccludedAlpha: 0,
outlineWidth: 3,
isOutlineSmooth: true
};
//disabling this for now as it is causing rendering issues with the other handle overlays
//Selection.enableListHighlight(HIGHLIGHT_LIST_NAME, editHandleOutlineStyle);
that.savedProperties = {};
that.selections = [];
var listeners = [];
that.localRotation = Quat.IDENTITY;
that.localPosition = Vec3.ZERO;
that.localDimensions = Vec3.ZERO;
that.localRegistrationPoint = Vec3.HALF;
that.worldRotation = Quat.IDENTITY;
that.worldPosition = Vec3.ZERO;
that.worldDimensions = Vec3.ZERO;
that.worldRegistrationPoint = Vec3.HALF;
that.centerPosition = Vec3.ZERO;
that.saveProperties = function() {
that.savedProperties = {};
for (var i = 0; i < that.selections.length; i++) {
var entityID = that.selections[i];
that.savedProperties[entityID] = Entities.getEntityProperties(entityID);
}
};
that.addEventListener = function(func) {
listeners.push(func);
};
that.hasSelection = function() {
return that.selections.length > 0;
};
that.setSelections = function(entityIDs) {
that.selections = [];
for (var i = 0; i < entityIDs.length; i++) {
var entityID = entityIDs[i];
that.selections.push(entityID);
Selection.addToSelectedItemsList(HIGHLIGHT_LIST_NAME, "entity", entityID);
}
that._update(true);
};
that.addEntity = function(entityID, toggleSelection) {
if (entityID) {
var idx = -1;
for (var i = 0; i < that.selections.length; i++) {
if (entityID === that.selections[i]) {
idx = i;
break;
}
}
if (idx === -1) {
that.selections.push(entityID);
Selection.addToSelectedItemsList(HIGHLIGHT_LIST_NAME, "entity", entityID);
} else if (toggleSelection) {
that.selections.splice(idx, 1);
Selection.removeFromSelectedItemsList(HIGHLIGHT_LIST_NAME, "entity", entityID);
}
}
that._update(true);
};
that.removeEntity = function(entityID) {
var idx = that.selections.indexOf(entityID);
if (idx >= 0) {
that.selections.splice(idx, 1);
Selection.removeFromSelectedItemsList(HIGHLIGHT_LIST_NAME, "entity", entityID);
}
that._update(true);
};
that.clearSelections = function() {
that.selections = [];
that._update(true);
};
that.duplicateSelection = function() {
var duplicatedEntityIDs = [];
Object.keys(that.savedProperties).forEach(function(otherEntityID) {
var properties = that.savedProperties[otherEntityID];
if (!properties.locked && (!properties.clientOnly || properties.owningAvatarID === MyAvatar.sessionUUID)) {
duplicatedEntityIDs.push({
entityID: Entities.addEntity(properties),
properties: properties
});
}
});
return duplicatedEntityIDs;
}
that._update = function(selectionUpdated) {
var properties = null;
if (that.selections.length === 0) {
that.localDimensions = null;
that.localPosition = null;
that.worldDimensions = null;
that.worldPosition = null;
that.worldRotation = null;
} else if (that.selections.length === 1) {
properties = Entities.getEntityProperties(that.selections[0]);
that.localDimensions = properties.dimensions;
that.localPosition = properties.position;
that.localRotation = properties.rotation;
that.localRegistrationPoint = properties.registrationPoint;
that.worldDimensions = properties.boundingBox.dimensions;
that.worldPosition = properties.boundingBox.center;
that.worldRotation = properties.boundingBox.rotation;
SelectionDisplay.setSpaceMode(SPACE_LOCAL);
} else {
that.localRotation = null;
that.localDimensions = null;
that.localPosition = null;
properties = Entities.getEntityProperties(that.selections[0]);
var brn = properties.boundingBox.brn;
var tfl = properties.boundingBox.tfl;
for (var i = 1; i < that.selections.length; i++) {
properties = Entities.getEntityProperties(that.selections[i]);
var bb = properties.boundingBox;
brn.x = Math.min(bb.brn.x, brn.x);
brn.y = Math.min(bb.brn.y, brn.y);
brn.z = Math.min(bb.brn.z, brn.z);
tfl.x = Math.max(bb.tfl.x, tfl.x);
tfl.y = Math.max(bb.tfl.y, tfl.y);
tfl.z = Math.max(bb.tfl.z, tfl.z);
}
that.localDimensions = null;
that.localPosition = null;
that.worldDimensions = {
x: tfl.x - brn.x,
y: tfl.y - brn.y,
z: tfl.z - brn.z
};
that.worldPosition = {
x: brn.x + (that.worldDimensions.x / 2),
y: brn.y + (that.worldDimensions.y / 2),
z: brn.z + (that.worldDimensions.z / 2)
};
// For 1+ selections we can only modify selections in world space
SelectionDisplay.setSpaceMode(SPACE_WORLD);
}
for (var j = 0; j < listeners.length; j++) {
try {
listeners[j](selectionUpdated === true);
} catch (e) {
print("ERROR: entitySelectionTool.update got exception: " + JSON.stringify(e));
}
}
};
return that;
})();
// Normalize degrees to be in the range (-180, 180)
function normalizeDegrees(degrees) {
degrees = ((degrees + 180) % 360) - 180;
if (degrees <= -180) {
degrees += 360;
}
return degrees;
}
// SELECTION DISPLAY DEFINITION
SelectionDisplay = (function() {
var that = {};
var NEGATE_VECTOR = -1;
var COLOR_GREEN = { red:31, green:198, blue:166 };
var COLOR_BLUE = { red:0, green:147, blue:197 };
var COLOR_RED = { red:226, green:51, blue:77 };
var COLOR_HOVER = { red:227, green:227, blue:227 };
var COLOR_ROTATE_CURRENT_RING = { red: 255, green: 99, blue: 9 };
var COLOR_SCALE_EDGE = { red:87, green:87, blue:87 };
var COLOR_SCALE_CUBE = { red:106, green:106, blue:106 };
var COLOR_SCALE_CUBE_SELECTED = { red:18, green:18, blue:18 };
var TRANSLATE_ARROW_CYLINDER_OFFSET = 0.1;
var TRANSLATE_ARROW_CYLINDER_CAMERA_DISTANCE_MULTIPLE = 0.005;
var TRANSLATE_ARROW_CYLINDER_Y_MULTIPLE = 7.5;
var TRANSLATE_ARROW_CONE_CAMERA_DISTANCE_MULTIPLE = 0.025;
var TRANSLATE_ARROW_CONE_OFFSET_CYLINDER_DIMENSION_MULTIPLE = 0.83;
var ROTATE_RING_CAMERA_DISTANCE_MULTIPLE = 0.15;
var ROTATE_CTRL_SNAP_ANGLE = 22.5;
var ROTATE_DEFAULT_SNAP_ANGLE = 1;
var ROTATE_DEFAULT_TICK_MARKS_ANGLE = 5;
var ROTATE_RING_IDLE_INNER_RADIUS = 0.95;
var ROTATE_RING_SELECTED_INNER_RADIUS = 0.9;
// These are multipliers for sizing the rotation degrees display while rotating an entity
var ROTATE_DISPLAY_DISTANCE_MULTIPLIER = 2;
var ROTATE_DISPLAY_SIZE_X_MULTIPLIER = 0.2;
var ROTATE_DISPLAY_SIZE_Y_MULTIPLIER = 0.09;
var ROTATE_DISPLAY_LINE_HEIGHT_MULTIPLIER = 0.07;
var STRETCH_SPHERE_OFFSET = 0.06;
var STRETCH_SPHERE_CAMERA_DISTANCE_MULTIPLE = 0.01;
var STRETCH_MINIMUM_DIMENSION = 0.001;
var STRETCH_ALL_MINIMUM_DIMENSION = 0.01;
var STRETCH_DIRECTION_ALL_CAMERA_DISTANCE_MULTIPLE = 6;
var STRETCH_PANEL_WIDTH = 0.01;
var SCALE_CUBE_OFFSET = 0.5;
var SCALE_CUBE_CAMERA_DISTANCE_MULTIPLE = 0.015;
var CLONER_OFFSET = { x:0.9, y:-0.9, z:0.9 };
var CTRL_KEY_CODE = 16777249;
var AVATAR_COLLISIONS_OPTION = "Enable Avatar Collisions";
var TRANSLATE_DIRECTION = {
X : 0,
Y : 1,
Z : 2
}
var STRETCH_DIRECTION = {
X : 0,
Y : 1,
Z : 2,
ALL : 3
}
var SCALE_DIRECTION = {
LBN : 0,
RBN : 1,
LBF : 2,
RBF : 3,
LTN : 4,
RTN : 5,
LTF : 6,
RTF : 7
}
var ROTATE_DIRECTION = {
PITCH : 0,
YAW : 1,
ROLL : 2
}
var spaceMode = SPACE_LOCAL;
var overlayNames = [];
var lastCameraPosition = Camera.getPosition();
var lastCameraOrientation = Camera.getOrientation();
var lastControllerPoses = [
getControllerWorldLocation(Controller.Standard.LeftHand, true),
getControllerWorldLocation(Controller.Standard.RightHand, true)
];
var rotationZero;
var rotationNormal;
var rotationDegreesPosition;
var worldRotationX;
var worldRotationY;
var worldRotationZ;
var previousHandle = null;
var previousHandleHelper = null;
var previousHandleColor;
var ctrlPressed = false;
var handleStretchCollisionOverride = false;
var handlePropertiesTranslateArrowCones = {
shape: "Cone",
solid: true,
visible: false,
ignoreRayIntersection: false,
drawInFront: true
};
var handlePropertiesTranslateArrowCylinders = {
shape: "Cylinder",
solid: true,
visible: false,
ignoreRayIntersection: false,
drawInFront: true
};
var handleTranslateXCone = Overlays.addOverlay("shape", handlePropertiesTranslateArrowCones);
var handleTranslateXCylinder = Overlays.addOverlay("shape", handlePropertiesTranslateArrowCylinders);
Overlays.editOverlay(handleTranslateXCone, { color : COLOR_RED });
Overlays.editOverlay(handleTranslateXCylinder, { color : COLOR_RED });
var handleTranslateYCone = Overlays.addOverlay("shape", handlePropertiesTranslateArrowCones);
var handleTranslateYCylinder = Overlays.addOverlay("shape", handlePropertiesTranslateArrowCylinders);
Overlays.editOverlay(handleTranslateYCone, { color : COLOR_GREEN });
Overlays.editOverlay(handleTranslateYCylinder, { color : COLOR_GREEN });
var handleTranslateZCone = Overlays.addOverlay("shape", handlePropertiesTranslateArrowCones);
var handleTranslateZCylinder = Overlays.addOverlay("shape", handlePropertiesTranslateArrowCylinders);
Overlays.editOverlay(handleTranslateZCone, { color : COLOR_BLUE });
Overlays.editOverlay(handleTranslateZCylinder, { color : COLOR_BLUE });
var handlePropertiesRotateRings = {
alpha: 1,
solid: true,
startAt: 0,
endAt: 360,
innerRadius: ROTATE_RING_IDLE_INNER_RADIUS,
majorTickMarksAngle: ROTATE_DEFAULT_TICK_MARKS_ANGLE,
majorTickMarksLength: 0.1,
visible: false,
ignoreRayIntersection: false,
drawInFront: true
};
var handleRotatePitchRing = Overlays.addOverlay("circle3d", handlePropertiesRotateRings);
Overlays.editOverlay(handleRotatePitchRing, {
color : COLOR_RED,
majorTickMarksColor: COLOR_RED,
});
var handleRotateYawRing = Overlays.addOverlay("circle3d", handlePropertiesRotateRings);
Overlays.editOverlay(handleRotateYawRing, {
color : COLOR_GREEN,
majorTickMarksColor: COLOR_GREEN,
});
var handleRotateRollRing = Overlays.addOverlay("circle3d", handlePropertiesRotateRings);
Overlays.editOverlay(handleRotateRollRing, {
color : COLOR_BLUE,
majorTickMarksColor: COLOR_BLUE,
});
var handleRotateCurrentRing = Overlays.addOverlay("circle3d", {
alpha: 1,
color: COLOR_ROTATE_CURRENT_RING,
solid: true,
innerRadius: 0.9,
visible: false,
ignoreRayIntersection: true,
drawInFront: true
});
var rotationDegreesDisplay = Overlays.addOverlay("text3d", {
text: "",
color: { red: 0, green: 0, blue: 0 },
backgroundColor: { red: 255, green: 255, blue: 255 },
alpha: 0.7,
backgroundAlpha: 0.7,
visible: false,
isFacingAvatar: true,
drawInFront: true,
ignoreRayIntersection: true,
dimensions: { x: 0, y: 0 },
lineHeight: 0.0,
topMargin: 0,
rightMargin: 0,
bottomMargin: 0,
leftMargin: 0
});
var handlePropertiesStretchSpheres = {
shape: "Sphere",
solid: true,
visible: false,
ignoreRayIntersection: false,
drawInFront: true
};
var handleStretchXSphere = Overlays.addOverlay("shape", handlePropertiesStretchSpheres);
Overlays.editOverlay(handleStretchXSphere, { color : COLOR_RED });
var handleStretchYSphere = Overlays.addOverlay("shape", handlePropertiesStretchSpheres);
Overlays.editOverlay(handleStretchYSphere, { color : COLOR_GREEN });
var handleStretchZSphere = Overlays.addOverlay("shape", handlePropertiesStretchSpheres);
Overlays.editOverlay(handleStretchZSphere, { color : COLOR_BLUE });
var handlePropertiesStretchPanel = {
shape: "Quad",
alpha: 0.5,
solid: true,
visible: false,
ignoreRayIntersection: true,
drawInFront: true
}
var handleStretchXPanel = Overlays.addOverlay("shape", handlePropertiesStretchPanel);
Overlays.editOverlay(handleStretchXPanel, { color : COLOR_RED });
var handleStretchYPanel = Overlays.addOverlay("shape", handlePropertiesStretchPanel);
Overlays.editOverlay(handleStretchYPanel, { color : COLOR_GREEN });
var handleStretchZPanel = Overlays.addOverlay("shape", handlePropertiesStretchPanel);
Overlays.editOverlay(handleStretchZPanel, { color : COLOR_BLUE });
var handlePropertiesScaleCubes = {
size: 0.025,
color: COLOR_SCALE_CUBE,
solid: true,
visible: false,
ignoreRayIntersection: false,
drawInFront: true,
borderSize: 1.4
};
var handleScaleLBNCube = Overlays.addOverlay("cube", handlePropertiesScaleCubes); // (-x, -y, -z)
var handleScaleRBNCube = Overlays.addOverlay("cube", handlePropertiesScaleCubes); // ( x, -y, -z)
var handleScaleLBFCube = Overlays.addOverlay("cube", handlePropertiesScaleCubes); // (-x, -y, z)
var handleScaleRBFCube = Overlays.addOverlay("cube", handlePropertiesScaleCubes); // ( x, -y, z)
var handleScaleLTNCube = Overlays.addOverlay("cube", handlePropertiesScaleCubes); // (-x, y, -z)
var handleScaleRTNCube = Overlays.addOverlay("cube", handlePropertiesScaleCubes); // ( x, y, -z)
var handleScaleLTFCube = Overlays.addOverlay("cube", handlePropertiesScaleCubes); // (-x, y, z)
var handleScaleRTFCube = Overlays.addOverlay("cube", handlePropertiesScaleCubes); // ( x, y, z)
var handlePropertiesScaleEdge = {
color: COLOR_SCALE_EDGE,
visible: false,
ignoreRayIntersection: true,
drawInFront: true,
lineWidth: 0.2
}
var handleScaleTREdge = Overlays.addOverlay("line3d", handlePropertiesScaleEdge);
var handleScaleTLEdge = Overlays.addOverlay("line3d", handlePropertiesScaleEdge);
var handleScaleTFEdge = Overlays.addOverlay("line3d", handlePropertiesScaleEdge);
var handleScaleTNEdge = Overlays.addOverlay("line3d", handlePropertiesScaleEdge);
var handleScaleBREdge = Overlays.addOverlay("line3d", handlePropertiesScaleEdge);
var handleScaleBLEdge = Overlays.addOverlay("line3d", handlePropertiesScaleEdge);
var handleScaleBFEdge = Overlays.addOverlay("line3d", handlePropertiesScaleEdge);
var handleScaleBNEdge = Overlays.addOverlay("line3d", handlePropertiesScaleEdge);
var handleScaleNREdge = Overlays.addOverlay("line3d", handlePropertiesScaleEdge);
var handleScaleNLEdge = Overlays.addOverlay("line3d", handlePropertiesScaleEdge);
var handleScaleFREdge = Overlays.addOverlay("line3d", handlePropertiesScaleEdge);
var handleScaleFLEdge = Overlays.addOverlay("line3d", handlePropertiesScaleEdge);
var handleCloner = Overlays.addOverlay("cube", {
size: 0.05,
color: COLOR_GREEN,
solid: true,
visible: false,
ignoreRayIntersection: false,
drawInFront: true,
borderSize: 1.4
});
// setting to 0 alpha for now to keep this hidden vs using visible false
// because its used as the translate xz tool handle overlay
var selectionBox = Overlays.addOverlay("cube", {
size: 1,
color: COLOR_RED,
alpha: 0,
solid: false,
visible: false,
dashed: false
});
var allOverlays = [
handleTranslateXCone,
handleTranslateXCylinder,
handleTranslateYCone,
handleTranslateYCylinder,
handleTranslateZCone,
handleTranslateZCylinder,
handleRotatePitchRing,
handleRotateYawRing,
handleRotateRollRing,
handleRotateCurrentRing,
rotationDegreesDisplay,
handleStretchXSphere,
handleStretchYSphere,
handleStretchZSphere,
handleStretchXPanel,
handleStretchYPanel,
handleStretchZPanel,
handleScaleLBNCube,
handleScaleRBNCube,
handleScaleLBFCube,
handleScaleRBFCube,
handleScaleLTNCube,
handleScaleRTNCube,
handleScaleLTFCube,
handleScaleRTFCube,
handleScaleTREdge,
handleScaleTLEdge,
handleScaleTFEdge,
handleScaleTNEdge,
handleScaleBREdge,
handleScaleBLEdge,
handleScaleBFEdge,
handleScaleBNEdge,
handleScaleNREdge,
handleScaleNLEdge,
handleScaleFREdge,
handleScaleFLEdge,
handleCloner,
selectionBox
];
overlayNames[handleTranslateXCone] = "handleTranslateXCone";
overlayNames[handleTranslateXCylinder] = "handleTranslateXCylinder";
overlayNames[handleTranslateYCone] = "handleTranslateYCone";
overlayNames[handleTranslateYCylinder] = "handleTranslateYCylinder";
overlayNames[handleTranslateZCone] = "handleTranslateZCone";
overlayNames[handleTranslateZCylinder] = "handleTranslateZCylinder";
overlayNames[handleRotatePitchRing] = "handleRotatePitchRing";
overlayNames[handleRotateYawRing] = "handleRotateYawRing";
overlayNames[handleRotateRollRing] = "handleRotateRollRing";
overlayNames[handleRotateCurrentRing] = "handleRotateCurrentRing";
overlayNames[rotationDegreesDisplay] = "rotationDegreesDisplay";
overlayNames[handleStretchXSphere] = "handleStretchXSphere";
overlayNames[handleStretchYSphere] = "handleStretchYSphere";
overlayNames[handleStretchZSphere] = "handleStretchZSphere";
overlayNames[handleStretchXPanel] = "handleStretchXPanel";
overlayNames[handleStretchYPanel] = "handleStretchYPanel";
overlayNames[handleStretchZPanel] = "handleStretchZPanel";
overlayNames[handleScaleLBNCube] = "handleScaleLBNCube";
overlayNames[handleScaleRBNCube] = "handleScaleRBNCube";
overlayNames[handleScaleLBFCube] = "handleScaleLBFCube";
overlayNames[handleScaleRBFCube] = "handleScaleRBFCube";
overlayNames[handleScaleLTNCube] = "handleScaleLTNCube";
overlayNames[handleScaleRTNCube] = "handleScaleRTNCube";
overlayNames[handleScaleLTFCube] = "handleScaleLTFCube";
overlayNames[handleScaleRTFCube] = "handleScaleRTFCube";
overlayNames[handleScaleTREdge] = "handleScaleTREdge";
overlayNames[handleScaleTLEdge] = "handleScaleTLEdge";
overlayNames[handleScaleTFEdge] = "handleScaleTFEdge";
overlayNames[handleScaleTNEdge] = "handleScaleTNEdge";
overlayNames[handleScaleBREdge] = "handleScaleBREdge";
overlayNames[handleScaleBLEdge] = "handleScaleBLEdge";
overlayNames[handleScaleBFEdge] = "handleScaleBFEdge";
overlayNames[handleScaleBNEdge] = "handleScaleBNEdge";
overlayNames[handleScaleNREdge] = "handleScaleNREdge";
overlayNames[handleScaleNLEdge] = "handleScaleNLEdge";
overlayNames[handleScaleFREdge] = "handleScaleFREdge";
overlayNames[handleScaleFLEdge] = "handleScaleFLEdge";
overlayNames[handleCloner] = "handleCloner";
overlayNames[selectionBox] = "selectionBox";
var activeTool = null;
var handleTools = {};
that.shutdown = function() {
that.restoreAvatarCollisionsFromStretch();
}
Script.scriptEnding.connect(that.shutdown);
// We get mouseMoveEvents from the handControllers, via handControllerPointer.
// But we dont' get mousePressEvents.
that.triggerMapping = Controller.newMapping(Script.resolvePath('') + '-click');
Script.scriptEnding.connect(that.triggerMapping.disable);
that.TRIGGER_GRAB_VALUE = 0.85; // From handControllerGrab/Pointer.js. Should refactor.
that.TRIGGER_ON_VALUE = 0.4;
that.TRIGGER_OFF_VALUE = 0.15;
that.triggered = false;
var activeHand = Controller.Standard.RightHand;
function makeTriggerHandler(hand) {
return function (value) {
if (!that.triggered && (value > that.TRIGGER_GRAB_VALUE)) { // should we smooth?
that.triggered = true;
if (activeHand !== hand) {
// No switching while the other is already triggered, so no need to release.
activeHand = (activeHand === Controller.Standard.RightHand) ?
Controller.Standard.LeftHand : Controller.Standard.RightHand;
}
if (Reticle.pointingAtSystemOverlay || Overlays.getOverlayAtPoint(Reticle.position)) {
return;
}
that.mousePressEvent({});
} else if (that.triggered && (value < that.TRIGGER_OFF_VALUE)) {
that.triggered = false;
that.mouseReleaseEvent({});
}
};
}
that.triggerMapping.from(Controller.Standard.RT).peek().to(makeTriggerHandler(Controller.Standard.RightHand));
that.triggerMapping.from(Controller.Standard.LT).peek().to(makeTriggerHandler(Controller.Standard.LeftHand));
// FUNCTION DEF(s): Intersection Check Helpers
function testRayIntersect(queryRay, overlayIncludes, overlayExcludes) {
var wantDebug = false;
if ((queryRay === undefined) || (queryRay === null)) {
if (wantDebug) {
print("testRayIntersect - EARLY EXIT -> queryRay is undefined OR null!");
}
return null;
}
var intersectObj = Overlays.findRayIntersection(queryRay, true, overlayIncludes, overlayExcludes);
if (wantDebug) {
if (!overlayIncludes) {
print("testRayIntersect - no overlayIncludes provided.");
}
if (!overlayExcludes) {
print("testRayIntersect - no overlayExcludes provided.");
}
print("testRayIntersect - Hit: " + intersectObj.intersects);
print(" intersectObj.overlayID:" + intersectObj.overlayID + "[" + overlayNames[intersectObj.overlayID] + "]");
print(" OverlayName: " + overlayNames[intersectObj.overlayID]);
print(" intersectObj.distance:" + intersectObj.distance);
print(" intersectObj.face:" + intersectObj.face);
Vec3.print(" intersectObj.intersection:", intersectObj.intersection);
}
return intersectObj;
}
// FUNCTION: MOUSE PRESS EVENT
that.mousePressEvent = function (event) {
var wantDebug = false;
if (wantDebug) {
print("=============== eST::MousePressEvent BEG =======================");
}
if (!event.isLeftButton && !that.triggered) {
// EARLY EXIT-(if another mouse button than left is pressed ignore it)
return false;
}
var pickRay = generalComputePickRay(event.x, event.y);
// TODO_Case6491: Move this out to setup just to make it once
var interactiveOverlays = getMainTabletIDs();
for (var key in handleTools) {
if (handleTools.hasOwnProperty(key)) {
interactiveOverlays.push(key);
}
}
// Start with unknown mode, in case no tool can handle this.
activeTool = null;
var results = testRayIntersect(pickRay, interactiveOverlays);
if (results.intersects) {
var hitOverlayID = results.overlayID;
if ((HMD.tabletID && hitOverlayID === HMD.tabletID) || (HMD.tabletScreenID && hitOverlayID === HMD.tabletScreenID)
|| (HMD.homeButtonID && hitOverlayID === HMD.homeButtonID)) {
// EARLY EXIT-(mouse clicks on the tablet should override the edit affordances)
return false;
}
entityIconOverlayManager.setIconsSelectable(SelectionManager.selections, true);
var hitTool = handleTools[ hitOverlayID ];
if (hitTool) {
activeTool = hitTool;
if (activeTool.onBegin) {
activeTool.onBegin(event, pickRay, results);
} else {
print("ERROR: entitySelectionTool.mousePressEvent - ActiveTool(" + activeTool.mode + ") missing onBegin");
}
} else {
print("ERROR: entitySelectionTool.mousePressEvent - Hit unexpected object, check interactiveOverlays");
}// End_if (hitTool)
}// End_If(results.intersects)
if (wantDebug) {
print(" DisplayMode: " + getMode());
print("=============== eST::MousePressEvent END =======================");
}
// If mode is known then we successfully handled this;
// otherwise, we're missing a tool.
return activeTool;
};
that.resetPreviousHandleColor = function() {
if (previousHandle != null) {
Overlays.editOverlay(previousHandle, { color: previousHandleColor });
previousHandle = null;
}
if (previousHandleHelper != null) {
Overlays.editOverlay(previousHandleHelper, { color: previousHandleColor });
previousHandleHelper = null;
}
};
that.getHandleHelper = function(overlay) {
if (overlay === handleTranslateXCone) {
return handleTranslateXCylinder;
} else if (overlay === handleTranslateXCylinder) {
return handleTranslateXCone;
} else if (overlay === handleTranslateYCone) {
return handleTranslateYCylinder;
} else if (overlay === handleTranslateYCylinder) {
return handleTranslateYCone;
} else if (overlay === handleTranslateZCone) {
return handleTranslateZCylinder;
} else if (overlay === handleTranslateZCylinder) {
return handleTranslateZCone;
}
return Uuid.NULL;
};
// FUNCTION: MOUSE MOVE EVENT
that.mouseMoveEvent = function(event) {
var wantDebug = false;
if (wantDebug) {
print("=============== eST::MouseMoveEvent BEG =======================");
}
if (activeTool) {
if (wantDebug) {
print(" Trigger ActiveTool(" + activeTool.mode + ")'s onMove");
}
activeTool.onMove(event);
if (wantDebug) {
print(" Trigger SelectionManager::update");
}
SelectionManager._update();
if (wantDebug) {
print("=============== eST::MouseMoveEvent END =======================");
}
// EARLY EXIT--(Move handled via active tool)
return true;
}
// if no tool is active, then just look for handles to highlight...
var pickRay = generalComputePickRay(event.x, event.y);
var result = Overlays.findRayIntersection(pickRay);
var pickedColor;
var highlightNeeded = false;
if (result.intersects) {
switch (result.overlayID) {
case handleTranslateXCone:
case handleTranslateXCylinder:
case handleRotatePitchRing:
case handleStretchXSphere:
pickedColor = COLOR_RED;
highlightNeeded = true;
break;
case handleTranslateYCone:
case handleTranslateYCylinder:
case handleRotateYawRing:
case handleStretchYSphere:
pickedColor = COLOR_GREEN;
highlightNeeded = true;
break;
case handleTranslateZCone:
case handleTranslateZCylinder:
case handleRotateRollRing:
case handleStretchZSphere:
pickedColor = COLOR_BLUE;
highlightNeeded = true;
break;
case handleScaleLBNCube:
case handleScaleRBNCube:
case handleScaleLBFCube:
case handleScaleRBFCube:
case handleScaleLTNCube:
case handleScaleRTNCube:
case handleScaleLTFCube:
case handleScaleRTFCube:
pickedColor = COLOR_SCALE_CUBE;
highlightNeeded = true;
break;
default:
that.resetPreviousHandleColor();
break;
}
if (highlightNeeded) {
that.resetPreviousHandleColor();
Overlays.editOverlay(result.overlayID, { color: COLOR_HOVER });
previousHandle = result.overlayID;
previousHandleHelper = that.getHandleHelper(result.overlayID);
if (previousHandleHelper != null) {
Overlays.editOverlay(previousHandleHelper, { color: COLOR_HOVER });
}
previousHandleColor = pickedColor;
}
} else {
that.resetPreviousHandleColor();
}
if (wantDebug) {
print("=============== eST::MouseMoveEvent END =======================");
}
return false;
};
// FUNCTION: MOUSE RELEASE EVENT
that.mouseReleaseEvent = function(event) {
var wantDebug = false;
if (wantDebug) {
print("=============== eST::MouseReleaseEvent BEG =======================");
}
var showHandles = false;
if (activeTool) {
if (activeTool.onEnd) {
if (wantDebug) {
print(" Triggering ActiveTool(" + activeTool.mode + ")'s onEnd");
}
activeTool.onEnd(event);
} else if (wantDebug) {
print(" ActiveTool(" + activeTool.mode + ")'s missing onEnd");
}
}
showHandles = activeTool; // base on prior tool value
activeTool = null;
// if something is selected, then reset the "original" properties for any potential next click+move operation
if (SelectionManager.hasSelection()) {
if (showHandles) {
if (wantDebug) {
print(" Triggering that.select");
}
that.select(SelectionManager.selections[0], event);
}
}
if (wantDebug) {
print("=============== eST::MouseReleaseEvent END =======================");
}
};
// Control key remains active only while key is held down
that.keyReleaseEvent = function(key) {
if (key.key === CTRL_KEY_CODE) {
ctrlPressed = false;
that.updateActiveRotateRing();
}
}
// Triggers notification on specific key driven events
that.keyPressEvent = function(key) {
if (key.key === CTRL_KEY_CODE) {
ctrlPressed = true;
that.updateActiveRotateRing();
}
}
// NOTE: mousePressEvent and mouseMoveEvent from the main script should call us., so we don't hook these:
// Controller.mousePressEvent.connect(that.mousePressEvent);
// Controller.mouseMoveEvent.connect(that.mouseMoveEvent);
Controller.mouseReleaseEvent.connect(that.mouseReleaseEvent);
Controller.keyPressEvent.connect(that.keyPressEvent);
Controller.keyReleaseEvent.connect(that.keyReleaseEvent);
that.checkControllerMove = function() {
if (SelectionManager.hasSelection()) {
var controllerPose = getControllerWorldLocation(activeHand, true);
var hand = (activeHand === Controller.Standard.LeftHand) ? 0 : 1;
if (controllerPose.valid && lastControllerPoses[hand].valid) {
if (!Vec3.equal(controllerPose.position, lastControllerPoses[hand].position) ||
!Vec3.equal(controllerPose.rotation, lastControllerPoses[hand].rotation)) {
that.mouseMoveEvent({});
}
}
lastControllerPoses[hand] = controllerPose;
}
};
function controllerComputePickRay() {
var controllerPose = getControllerWorldLocation(activeHand, true);
if (controllerPose.valid && that.triggered) {
var controllerPosition = controllerPose.translation;
// This gets point direction right, but if you want general quaternion it would be more complicated:
var controllerDirection = Quat.getUp(controllerPose.rotation);
return {origin: controllerPosition, direction: controllerDirection};
}
}
function generalComputePickRay(x, y) {
return controllerComputePickRay() || Camera.computePickRay(x, y);
}
function getDistanceToCamera(position) {
var cameraPosition = Camera.getPosition();
var toCameraDistance = Vec3.length(Vec3.subtract(cameraPosition, position));
return toCameraDistance;
}
// @return string - The mode of the currently active tool;
// otherwise, "UNKNOWN" if there's no active tool.
function getMode() {
return (activeTool ? activeTool.mode : "UNKNOWN");
}
that.cleanup = function() {
for (var i = 0; i < allOverlays.length; i++) {
Overlays.deleteOverlay(allOverlays[i]);
}
};
that.select = function(entityID, event) {
var properties = Entities.getEntityProperties(SelectionManager.selections[0]);
lastCameraPosition = Camera.getPosition();
lastCameraOrientation = Camera.getOrientation();
if (event !== false) {
pickRay = generalComputePickRay(event.x, event.y);
var wantDebug = false;
if (wantDebug) {
print("select() with EVENT...... ");
print(" event.y:" + event.y);
Vec3.print(" current position:", properties.position);
}
}
that.updateHandles();
};
// FUNCTION: SET SPACE MODE
that.setSpaceMode = function(newSpaceMode) {
var wantDebug = false;
if (wantDebug) {
print("======> SetSpaceMode called. ========");
}
if (spaceMode !== newSpaceMode) {
if (wantDebug) {
print(" Updating SpaceMode From: " + spaceMode + " To: " + newSpaceMode);
}
spaceMode = newSpaceMode;
that.updateHandles();
} else if (wantDebug) {
print("WARNING: entitySelectionTool.setSpaceMode - Can't update SpaceMode. CurrentMode: " + spaceMode + " DesiredMode: " + newSpaceMode);
}
if (wantDebug) {
print("====== SetSpaceMode called. <========");
}
};
function addHandleTool(overlay, tool) {
handleTools[overlay] = tool;
return tool;
}
// @param: toolHandle: The overlayID associated with the tool
// that correlates to the tool you wish to query.
// @note: If toolHandle is null or undefined then activeTool
// will be checked against those values as opposed to
// the tool registered under toolHandle. Null & Undefined
// are treated as separate values.
// @return: bool - Indicates if the activeTool is that queried.
function isActiveTool(toolHandle) {
if (!toolHandle) {
// Allow isActiveTool(null) and similar to return true if there's
// no active tool
return (activeTool === toolHandle);
}
if (!handleTools.hasOwnProperty(toolHandle)) {
print("WARNING: entitySelectionTool.isActiveTool - Encountered unknown grabberToolHandle: " + toolHandle + ". Tools should be registered via addHandleTool.");
// EARLY EXIT
return false;
}
return (activeTool === handleTools[ toolHandle ]);
}
// FUNCTION: UPDATE HANDLES
that.updateHandles = function() {
var wantDebug = false;
if (wantDebug) {
print("======> Update Handles =======");
print(" Selections Count: " + SelectionManager.selections.length);
print(" SpaceMode: " + spaceMode);
print(" DisplayMode: " + getMode());
}
if (SelectionManager.selections.length === 0) {
that.setOverlaysVisible(false);
return;
}
if (SelectionManager.hasSelection()) {
var position = SelectionManager.worldPosition;
var rotation = spaceMode === SPACE_LOCAL ? SelectionManager.localRotation : SelectionManager.worldRotation;
var dimensions = spaceMode === SPACE_LOCAL ? SelectionManager.localDimensions : SelectionManager.worldDimensions;
var rotationInverse = Quat.inverse(rotation);
var toCameraDistance = getDistanceToCamera(position);
var localRotationX = Quat.fromPitchYawRollDegrees(0, 0, -90);
var rotationX = Quat.multiply(rotation, localRotationX);
worldRotationX = rotationX;
var localRotationY = Quat.fromPitchYawRollDegrees(0, 90, 0);
var rotationY = Quat.multiply(rotation, localRotationY);
worldRotationY = rotationY;
var localRotationZ = Quat.fromPitchYawRollDegrees(90, 0, 0);
var rotationZ = Quat.multiply(rotation, localRotationZ);
worldRotationZ = rotationZ;
// in HMD we clamp the overlays to the bounding box for now so lasers can hit them
var maxHandleDimension = 0;
if (HMD.active) {
maxHandleDimension = Math.max(dimensions.x, dimensions.y, dimensions.z);
}
// UPDATE ROTATION RINGS
// rotateDimension is used as the base dimension for all overlays
var rotateDimension = Math.max(maxHandleDimension, toCameraDistance * ROTATE_RING_CAMERA_DISTANCE_MULTIPLE);
var rotateDimensions = { x:rotateDimension, y:rotateDimension, z:rotateDimension };
if (!isActiveTool(handleRotatePitchRing)) {
Overlays.editOverlay(handleRotatePitchRing, {
position: position,
rotation: rotationY,
dimensions: rotateDimensions,
majorTickMarksAngle: ROTATE_DEFAULT_TICK_MARKS_ANGLE
});
}
if (!isActiveTool(handleRotateYawRing)) {
Overlays.editOverlay(handleRotateYawRing, {
position: position,
rotation: rotationZ,
dimensions: rotateDimensions,
majorTickMarksAngle: ROTATE_DEFAULT_TICK_MARKS_ANGLE
});
}
if (!isActiveTool(handleRotateRollRing)) {
Overlays.editOverlay(handleRotateRollRing, {
position: position,
rotation: rotationX,
dimensions: rotateDimensions,
majorTickMarksAngle: ROTATE_DEFAULT_TICK_MARKS_ANGLE
});
}
Overlays.editOverlay(handleRotateCurrentRing, { dimensions: rotateDimensions });
that.updateActiveRotateRing();
// UPDATE TRANSLATION ARROWS
var arrowCylinderDimension = rotateDimension * TRANSLATE_ARROW_CYLINDER_CAMERA_DISTANCE_MULTIPLE /
ROTATE_RING_CAMERA_DISTANCE_MULTIPLE;
var arrowCylinderDimensions = {
x:arrowCylinderDimension,
y:arrowCylinderDimension * TRANSLATE_ARROW_CYLINDER_Y_MULTIPLE,
z:arrowCylinderDimension
};
var arrowConeDimension = rotateDimension * TRANSLATE_ARROW_CONE_CAMERA_DISTANCE_MULTIPLE /
ROTATE_RING_CAMERA_DISTANCE_MULTIPLE;
var arrowConeDimensions = { x:arrowConeDimension, y:arrowConeDimension, z:arrowConeDimension };
var arrowCylinderOffset = rotateDimension * TRANSLATE_ARROW_CYLINDER_OFFSET / ROTATE_RING_CAMERA_DISTANCE_MULTIPLE;
var arrowConeOffset = arrowCylinderDimensions.y * TRANSLATE_ARROW_CONE_OFFSET_CYLINDER_DIMENSION_MULTIPLE;
var cylinderXPosition = { x:arrowCylinderOffset, y:0, z:0 };
cylinderXPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, cylinderXPosition));
Overlays.editOverlay(handleTranslateXCylinder, {
position: cylinderXPosition,
rotation: rotationX,
dimensions: arrowCylinderDimensions
});
var cylinderXOffset = Vec3.subtract(cylinderXPosition, position);
var coneXPosition = Vec3.sum(cylinderXPosition, Vec3.multiply(Vec3.normalize(cylinderXOffset), arrowConeOffset));
Overlays.editOverlay(handleTranslateXCone, {
position: coneXPosition,
rotation: rotationX,
dimensions: arrowConeDimensions
});
var cylinderYPosition = { x:0, y:arrowCylinderOffset, z:0 };
cylinderYPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, cylinderYPosition));
Overlays.editOverlay(handleTranslateYCylinder, {
position: cylinderYPosition,
rotation: rotationY,
dimensions: arrowCylinderDimensions
});
var cylinderYOffset = Vec3.subtract(cylinderYPosition, position);
var coneYPosition = Vec3.sum(cylinderYPosition, Vec3.multiply(Vec3.normalize(cylinderYOffset), arrowConeOffset));
Overlays.editOverlay(handleTranslateYCone, {
position: coneYPosition,
rotation: rotationY,
dimensions: arrowConeDimensions
});
var cylinderZPosition = { x:0, y:0, z:arrowCylinderOffset };
cylinderZPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, cylinderZPosition));
Overlays.editOverlay(handleTranslateZCylinder, {
position: cylinderZPosition,
rotation: rotationZ,
dimensions: arrowCylinderDimensions
});
var cylinderZOffset = Vec3.subtract(cylinderZPosition, position);
var coneZPosition = Vec3.sum(cylinderZPosition, Vec3.multiply(Vec3.normalize(cylinderZOffset), arrowConeOffset));
Overlays.editOverlay(handleTranslateZCone, {
position: coneZPosition,
rotation: rotationZ,
dimensions: arrowConeDimensions
});
// UPDATE SCALE CUBES
var scaleCubeOffsetX = SCALE_CUBE_OFFSET * dimensions.x;
var scaleCubeOffsetY = SCALE_CUBE_OFFSET * dimensions.y;
var scaleCubeOffsetZ = SCALE_CUBE_OFFSET * dimensions.z;
var scaleCubeDimension = rotateDimension * SCALE_CUBE_CAMERA_DISTANCE_MULTIPLE /
ROTATE_RING_CAMERA_DISTANCE_MULTIPLE;
var scaleCubeDimensions = { x:scaleCubeDimension, y:scaleCubeDimension, z:scaleCubeDimension };
var scaleCubeRotation = spaceMode === SPACE_LOCAL ? rotation : Quat.IDENTITY;
var scaleLBNCubePosition = { x:-scaleCubeOffsetX, y:-scaleCubeOffsetY, z:-scaleCubeOffsetZ };
scaleLBNCubePosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, scaleLBNCubePosition));
Overlays.editOverlay(handleScaleLBNCube, {
position: scaleLBNCubePosition,
rotation: scaleCubeRotation,
dimensions: scaleCubeDimensions
});
var scaleRBNCubePosition = { x:scaleCubeOffsetX, y:-scaleCubeOffsetY, z:-scaleCubeOffsetZ };
scaleRBNCubePosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, scaleRBNCubePosition));
Overlays.editOverlay(handleScaleRBNCube, {
position: scaleRBNCubePosition,
rotation: scaleCubeRotation,
dimensions: scaleCubeDimensions
});
var scaleLBFCubePosition = { x:-scaleCubeOffsetX, y:-scaleCubeOffsetY, z:scaleCubeOffsetZ };
scaleLBFCubePosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, scaleLBFCubePosition));
Overlays.editOverlay(handleScaleLBFCube, {
position: scaleLBFCubePosition,
rotation: scaleCubeRotation,
dimensions: scaleCubeDimensions
});
var scaleRBFCubePosition = { x:scaleCubeOffsetX, y:-scaleCubeOffsetY, z:scaleCubeOffsetZ };
scaleRBFCubePosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, scaleRBFCubePosition));
Overlays.editOverlay(handleScaleRBFCube, {
position: scaleRBFCubePosition,
rotation: scaleCubeRotation,
dimensions: scaleCubeDimensions
});
var scaleLTNCubePosition = { x:-scaleCubeOffsetX, y:scaleCubeOffsetY, z:-scaleCubeOffsetZ };
scaleLTNCubePosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, scaleLTNCubePosition));
Overlays.editOverlay(handleScaleLTNCube, {
position: scaleLTNCubePosition,
rotation: scaleCubeRotation,
dimensions: scaleCubeDimensions
});
var scaleRTNCubePosition = { x:scaleCubeOffsetX, y:scaleCubeOffsetY, z:-scaleCubeOffsetZ };
scaleRTNCubePosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, scaleRTNCubePosition));
Overlays.editOverlay(handleScaleRTNCube, {
position: scaleRTNCubePosition,
rotation: scaleCubeRotation,
dimensions: scaleCubeDimensions
});
var scaleLTFCubePosition = { x:-scaleCubeOffsetX, y:scaleCubeOffsetY, z:scaleCubeOffsetZ };
scaleLTFCubePosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, scaleLTFCubePosition));
Overlays.editOverlay(handleScaleLTFCube, {
position: scaleLTFCubePosition,
rotation: scaleCubeRotation,
dimensions: scaleCubeDimensions
});
var scaleRTFCubePosition = { x:scaleCubeOffsetX, y:scaleCubeOffsetY, z:scaleCubeOffsetZ };
scaleRTFCubePosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, scaleRTFCubePosition));
Overlays.editOverlay(handleScaleRTFCube, {
position: scaleRTFCubePosition,
rotation: scaleCubeRotation,
dimensions: scaleCubeDimensions
});
// UPDATE SCALE EDGES
Overlays.editOverlay(handleScaleTREdge, { start: scaleRTNCubePosition, end: scaleRTFCubePosition });
Overlays.editOverlay(handleScaleTLEdge, { start: scaleLTNCubePosition, end: scaleLTFCubePosition });
Overlays.editOverlay(handleScaleTFEdge, { start: scaleLTFCubePosition, end: scaleRTFCubePosition });
Overlays.editOverlay(handleScaleTNEdge, { start: scaleLTNCubePosition, end: scaleRTNCubePosition });
Overlays.editOverlay(handleScaleBREdge, { start: scaleRBNCubePosition, end: scaleRBFCubePosition });
Overlays.editOverlay(handleScaleBLEdge, { start: scaleLBNCubePosition, end: scaleLBFCubePosition });
Overlays.editOverlay(handleScaleBFEdge, { start: scaleLBFCubePosition, end: scaleRBFCubePosition });
Overlays.editOverlay(handleScaleBNEdge, { start: scaleLBNCubePosition, end: scaleRBNCubePosition });
Overlays.editOverlay(handleScaleNREdge, { start: scaleRTNCubePosition, end: scaleRBNCubePosition });
Overlays.editOverlay(handleScaleNLEdge, { start: scaleLTNCubePosition, end: scaleLBNCubePosition });
Overlays.editOverlay(handleScaleFREdge, { start: scaleRTFCubePosition, end: scaleRBFCubePosition });
Overlays.editOverlay(handleScaleFLEdge, { start: scaleLTFCubePosition, end: scaleLBFCubePosition });
// UPDATE STRETCH SPHERES
var stretchSphereDimension = rotateDimension * STRETCH_SPHERE_CAMERA_DISTANCE_MULTIPLE /
ROTATE_RING_CAMERA_DISTANCE_MULTIPLE;
var stretchSphereDimensions = { x:stretchSphereDimension, y:stretchSphereDimension, z:stretchSphereDimension };
var stretchSphereOffset = rotateDimension * STRETCH_SPHERE_OFFSET / ROTATE_RING_CAMERA_DISTANCE_MULTIPLE;
var stretchXPosition = { x:stretchSphereOffset, y:0, z:0 };
stretchXPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, stretchXPosition));
Overlays.editOverlay(handleStretchXSphere, {
position: stretchXPosition,
dimensions: stretchSphereDimensions
});
var stretchYPosition = { x:0, y:stretchSphereOffset, z:0 };
stretchYPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, stretchYPosition));
Overlays.editOverlay(handleStretchYSphere, {
position: stretchYPosition,
dimensions: stretchSphereDimensions
});
var stretchZPosition = { x:0, y:0, z:stretchSphereOffset };
stretchZPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, stretchZPosition));
Overlays.editOverlay(handleStretchZSphere, {
position: stretchZPosition,
dimensions: stretchSphereDimensions
});
// UPDATE STRETCH HIGHLIGHT PANELS
var scaleRBFCubePositionRotated = Vec3.multiplyQbyV(rotationInverse, scaleRBFCubePosition);
var scaleRTFCubePositionRotated = Vec3.multiplyQbyV(rotationInverse, scaleRTFCubePosition);
var scaleLTNCubePositionRotated = Vec3.multiplyQbyV(rotationInverse, scaleLTNCubePosition);
var scaleRTNCubePositionRotated = Vec3.multiplyQbyV(rotationInverse, scaleRTNCubePosition);
var stretchPanelXDimensions = Vec3.subtract(scaleRTNCubePositionRotated, scaleRBFCubePositionRotated);
var tempY = Math.abs(stretchPanelXDimensions.y);
stretchPanelXDimensions.x = STRETCH_PANEL_WIDTH;
stretchPanelXDimensions.y = Math.abs(stretchPanelXDimensions.z);
stretchPanelXDimensions.z = tempY;
var stretchPanelXPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, { x:dimensions.x / 2, y:0, z:0 }));
Overlays.editOverlay(handleStretchXPanel, {
position: stretchPanelXPosition,
rotation: rotationZ,
dimensions: stretchPanelXDimensions
});
var stretchPanelYDimensions = Vec3.subtract(scaleLTNCubePositionRotated, scaleRTFCubePositionRotated);
stretchPanelYDimensions.x = Math.abs(stretchPanelYDimensions.z);
stretchPanelYDimensions.y = STRETCH_PANEL_WIDTH;
stretchPanelYDimensions.z = Math.abs(stretchPanelYDimensions.x);
var stretchPanelYPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, { x:0, y:dimensions.y / 2, z:0 }));
Overlays.editOverlay(handleStretchYPanel, {
position: stretchPanelYPosition,
rotation: rotationY,
dimensions: stretchPanelYDimensions
});
var stretchPanelZDimensions = Vec3.subtract(scaleLTNCubePositionRotated, scaleRBFCubePositionRotated);
stretchPanelZDimensions.x = Math.abs(stretchPanelZDimensions.y);
stretchPanelZDimensions.y = Math.abs(stretchPanelZDimensions.x);
stretchPanelZDimensions.z = STRETCH_PANEL_WIDTH;
var stretchPanelZPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, { x:0, y:0, z:dimensions.z / 2 }));
Overlays.editOverlay(handleStretchZPanel, {
position: stretchPanelZPosition,
rotation: rotationX,
dimensions: stretchPanelZDimensions
});
// UPDATE SELECTION BOX (CURRENTLY INVISIBLE WITH 0 ALPHA FOR TRANSLATE XZ TOOL)
var inModeRotate = isActiveTool(handleRotatePitchRing) ||
isActiveTool(handleRotateYawRing) ||
isActiveTool(handleRotateRollRing);
Overlays.editOverlay(selectionBox, {
position: position,
rotation: rotation,
dimensions: dimensions,
visible: !inModeRotate
});
// UPDATE CLONER (CURRENTLY HIDDEN FOR NOW)
var handleClonerOffset = {
x:CLONER_OFFSET.x * dimensions.x,
y:CLONER_OFFSET.y * dimensions.y,
z:CLONER_OFFSET.z * dimensions.z
};
var handleClonerPos = Vec3.sum(position, Vec3.multiplyQbyV(rotation, handleClonerOffset));
Overlays.editOverlay(handleCloner, {
position: handleClonerPos,
rotation: rotation,
dimensions: scaleCubeDimensions
});
}
that.setHandleTranslateXVisible(!activeTool || isActiveTool(handleTranslateXCone) ||
isActiveTool(handleTranslateXCylinder));
that.setHandleTranslateYVisible(!activeTool || isActiveTool(handleTranslateYCone) ||
isActiveTool(handleTranslateYCylinder));
that.setHandleTranslateZVisible(!activeTool || isActiveTool(handleTranslateZCone) ||
isActiveTool(handleTranslateZCylinder));
that.setHandleRotatePitchVisible(!activeTool || isActiveTool(handleRotatePitchRing));
that.setHandleRotateYawVisible(!activeTool || isActiveTool(handleRotateYawRing));
that.setHandleRotateRollVisible(!activeTool || isActiveTool(handleRotateRollRing));
var showScaleStretch = !activeTool && SelectionManager.selections.length === 1;
that.setHandleStretchXVisible(showScaleStretch || isActiveTool(handleStretchXSphere));
that.setHandleStretchYVisible(showScaleStretch || isActiveTool(handleStretchYSphere));
that.setHandleStretchZVisible(showScaleStretch || isActiveTool(handleStretchZSphere));
that.setHandleScaleCubeVisible(showScaleStretch || isActiveTool(handleScaleLBNCube) ||
isActiveTool(handleScaleRBNCube) || isActiveTool(handleScaleLBFCube) ||
isActiveTool(handleScaleRBFCube) || isActiveTool(handleScaleLTNCube) ||
isActiveTool(handleScaleRTNCube) || isActiveTool(handleScaleLTFCube) ||
isActiveTool(handleScaleRTFCube) || isActiveTool(handleStretchXSphere) ||
isActiveTool(handleStretchYSphere) || isActiveTool(handleStretchZSphere));
var showOutlineForZone = (SelectionManager.selections.length === 1 &&
typeof SelectionManager.savedProperties[SelectionManager.selections[0]] !== "undefined" &&
SelectionManager.savedProperties[SelectionManager.selections[0]].type === "Zone");
that.setHandleScaleEdgeVisible(showOutlineForZone || (!isActiveTool(handleRotatePitchRing) &&
!isActiveTool(handleRotateYawRing) &&
!isActiveTool(handleRotateRollRing)));
//keep cloner always hidden for now since you can hold Alt to clone while
//translating an entity - we may bring cloner back for HMD only later
//that.setHandleClonerVisible(!activeTool || isActiveTool(handleCloner));
if (wantDebug) {
print("====== Update Handles <=======");
}
};
Script.update.connect(that.updateHandles);
// FUNCTION: UPDATE ACTIVE ROTATE RING
that.updateActiveRotateRing = function() {
var activeRotateRing = null;
if (isActiveTool(handleRotatePitchRing)) {
activeRotateRing = handleRotatePitchRing;
} else if (isActiveTool(handleRotateYawRing)) {
activeRotateRing = handleRotateYawRing;
} else if (isActiveTool(handleRotateRollRing)) {
activeRotateRing = handleRotateRollRing;
}
if (activeRotateRing != null) {
var tickMarksAngle = ctrlPressed ? ROTATE_CTRL_SNAP_ANGLE : ROTATE_DEFAULT_TICK_MARKS_ANGLE;
Overlays.editOverlay(activeRotateRing, { majorTickMarksAngle: tickMarksAngle });
}
};
// FUNCTION: SET OVERLAYS VISIBLE
that.setOverlaysVisible = function(isVisible) {
for (var i = 0; i < allOverlays.length; i++) {
Overlays.editOverlay(allOverlays[i], { visible: isVisible });
}
};
// FUNCTION: SET HANDLE TRANSLATE VISIBLE
that.setHandleTranslateVisible = function(isVisible) {
that.setHandleTranslateXVisible(isVisible);
that.setHandleTranslateYVisible(isVisible);
that.setHandleTranslateZVisible(isVisible);
};
that.setHandleTranslateXVisible = function(isVisible) {
Overlays.editOverlay(handleTranslateXCone, { visible: isVisible });
Overlays.editOverlay(handleTranslateXCylinder, { visible: isVisible });
};
that.setHandleTranslateYVisible = function(isVisible) {
Overlays.editOverlay(handleTranslateYCone, { visible: isVisible });
Overlays.editOverlay(handleTranslateYCylinder, { visible: isVisible });
};
that.setHandleTranslateZVisible = function(isVisible) {
Overlays.editOverlay(handleTranslateZCone, { visible: isVisible });
Overlays.editOverlay(handleTranslateZCylinder, { visible: isVisible });
};
// FUNCTION: SET HANDLE ROTATE VISIBLE
that.setHandleRotateVisible = function(isVisible) {
that.setHandleRotatePitchVisible(isVisible);
that.setHandleRotateYawVisible(isVisible);
that.setHandleRotateRollVisible(isVisible);
};
that.setHandleRotatePitchVisible = function(isVisible) {
Overlays.editOverlay(handleRotatePitchRing, { visible: isVisible });
};
that.setHandleRotateYawVisible = function(isVisible) {
Overlays.editOverlay(handleRotateYawRing, { visible: isVisible });
};
that.setHandleRotateRollVisible = function(isVisible) {
Overlays.editOverlay(handleRotateRollRing, { visible: isVisible });
};
// FUNCTION: SET HANDLE STRETCH VISIBLE
that.setHandleStretchVisible = function(isVisible) {
that.setHandleStretchXVisible(isVisible);
that.setHandleStretchYVisible(isVisible);
that.setHandleStretchZVisible(isVisible);
};
that.setHandleStretchXVisible = function(isVisible) {
Overlays.editOverlay(handleStretchXSphere, { visible: isVisible });
};
that.setHandleStretchYVisible = function(isVisible) {
Overlays.editOverlay(handleStretchYSphere, { visible: isVisible });
};
that.setHandleStretchZVisible = function(isVisible) {
Overlays.editOverlay(handleStretchZSphere, { visible: isVisible });
};
// FUNCTION: SET HANDLE SCALE VISIBLE
that.setHandleScaleVisible = function(isVisible) {
that.setHandleScaleCubeVisible(isVisible);
that.setHandleScaleEdgeVisible(isVisible);
};
that.setHandleScaleCubeVisible = function(isVisible) {
Overlays.editOverlay(handleScaleLBNCube, { visible: isVisible });
Overlays.editOverlay(handleScaleRBNCube, { visible: isVisible });
Overlays.editOverlay(handleScaleLBFCube, { visible: isVisible });
Overlays.editOverlay(handleScaleRBFCube, { visible: isVisible });
Overlays.editOverlay(handleScaleLTNCube, { visible: isVisible });
Overlays.editOverlay(handleScaleRTNCube, { visible: isVisible });
Overlays.editOverlay(handleScaleLTFCube, { visible: isVisible });
Overlays.editOverlay(handleScaleRTFCube, { visible: isVisible });
};
that.setHandleScaleEdgeVisible = function(isVisible) {
Overlays.editOverlay(handleScaleTREdge, { visible: isVisible });
Overlays.editOverlay(handleScaleTLEdge, { visible: isVisible });
Overlays.editOverlay(handleScaleTFEdge, { visible: isVisible });
Overlays.editOverlay(handleScaleTNEdge, { visible: isVisible });
Overlays.editOverlay(handleScaleBREdge, { visible: isVisible });
Overlays.editOverlay(handleScaleBLEdge, { visible: isVisible });
Overlays.editOverlay(handleScaleBFEdge, { visible: isVisible });
Overlays.editOverlay(handleScaleBNEdge, { visible: isVisible });
Overlays.editOverlay(handleScaleNREdge, { visible: isVisible });
Overlays.editOverlay(handleScaleNLEdge, { visible: isVisible });
Overlays.editOverlay(handleScaleFREdge, { visible: isVisible });
Overlays.editOverlay(handleScaleFLEdge, { visible: isVisible });
};
// FUNCTION: SET HANDLE CLONER VISIBLE
that.setHandleClonerVisible = function(isVisible) {
Overlays.editOverlay(handleCloner, { visible: isVisible });
};
// TOOL DEFINITION: TRANSLATE XZ TOOL
var initialXZPick = null;
var isConstrained = false;
var constrainMajorOnly = false;
var startPosition = null;
var duplicatedEntityIDs = null;
var translateXZTool = addHandleTool(selectionBox, {
mode: 'TRANSLATE_XZ',
pickPlanePosition: { x: 0, y: 0, z: 0 },
greatestDimension: 0.0,
startingDistance: 0.0,
startingElevation: 0.0,
onBegin: function(event, pickRay, pickResult, doClone) {
var wantDebug = false;
if (wantDebug) {
print("================== TRANSLATE_XZ(Beg) -> =======================");
Vec3.print(" pickRay", pickRay);
Vec3.print(" pickRay.origin", pickRay.origin);
Vec3.print(" pickResult.intersection", pickResult.intersection);
}
SelectionManager.saveProperties();
that.resetPreviousHandleColor();
that.setHandleTranslateVisible(false);
that.setHandleRotateVisible(false);
that.setHandleScaleCubeVisible(false);
that.setHandleStretchVisible(false);
that.setHandleClonerVisible(false);
startPosition = SelectionManager.worldPosition;
translateXZTool.pickPlanePosition = pickResult.intersection;
translateXZTool.greatestDimension = Math.max(Math.max(SelectionManager.worldDimensions.x,
SelectionManager.worldDimensions.y),
SelectionManager.worldDimensions.z);
translateXZTool.startingDistance = Vec3.distance(pickRay.origin, SelectionManager.position);
translateXZTool.startingElevation = translateXZTool.elevation(pickRay.origin, translateXZTool.pickPlanePosition);
if (wantDebug) {
print(" longest dimension: " + translateXZTool.greatestDimension);
print(" starting distance: " + translateXZTool.startingDistance);
print(" starting elevation: " + translateXZTool.startingElevation);
}
initialXZPick = rayPlaneIntersection(pickRay, translateXZTool.pickPlanePosition, {
x: 0,
y: 1,
z: 0
});
// Duplicate entities if alt is pressed. This will make a
// copy of the selected entities and move the _original_ entities, not
// the new ones.
if (event.isAlt || doClone) {
duplicatedEntityIDs = SelectionManager.duplicateSelection();
} else {
duplicatedEntityIDs = null;
}
isConstrained = false;
if (wantDebug) {
print("================== TRANSLATE_XZ(End) <- =======================");
}
},
onEnd: function(event, reason) {
pushCommandForSelections(duplicatedEntityIDs);
},
elevation: function(origin, intersection) {
return (origin.y - intersection.y) / Vec3.distance(origin, intersection);
},
onMove: function(event) {
var wantDebug = false;
pickRay = generalComputePickRay(event.x, event.y);
var pick = rayPlaneIntersection2(pickRay, translateXZTool.pickPlanePosition, {
x: 0,
y: 1,
z: 0
});
// If the pick ray doesn't hit the pick plane in this direction, do nothing.
// this will happen when someone drags across the horizon from the side they started on.
if (!pick) {
if (wantDebug) {
print(" "+ translateXZTool.mode + "Pick ray does not intersect XZ plane.");
}
// EARLY EXIT--(Invalid ray detected.)
return;
}
var vector = Vec3.subtract(pick, initialXZPick);
// If the mouse is too close to the horizon of the pick plane, stop moving
var MIN_ELEVATION = 0.02; // largest dimension of object divided by distance to it
var elevation = translateXZTool.elevation(pickRay.origin, pick);
if (wantDebug) {
print("Start Elevation: " + translateXZTool.startingElevation + ", elevation: " + elevation);
}
if ((translateXZTool.startingElevation > 0.0 && elevation < MIN_ELEVATION) ||
(translateXZTool.startingElevation < 0.0 && elevation > -MIN_ELEVATION)) {
if (wantDebug) {
print(" "+ translateXZTool.mode + " - too close to horizon!");
}
// EARLY EXIT--(Don't proceed past the reached limit.)
return;
}
// If the angular size of the object is too small, stop moving
var MIN_ANGULAR_SIZE = 0.01; // Radians
if (translateXZTool.greatestDimension > 0) {
var angularSize = Math.atan(translateXZTool.greatestDimension / Vec3.distance(pickRay.origin, pick));
if (wantDebug) {
print("Angular size = " + angularSize);
}
if (angularSize < MIN_ANGULAR_SIZE) {
return;
}
}
// If shifted, constrain to one axis
if (event.isShifted) {
if (Math.abs(vector.x) > Math.abs(vector.z)) {
vector.z = 0;
} else {
vector.x = 0;
}
if (!isConstrained) {
isConstrained = true;
}
} else {
if (isConstrained) {
isConstrained = false;
}
}
constrainMajorOnly = event.isControl;
var cornerPosition = Vec3.sum(startPosition, Vec3.multiply(-0.5, SelectionManager.worldDimensions));
vector = Vec3.subtract(
grid.snapToGrid(Vec3.sum(cornerPosition, vector), constrainMajorOnly),
cornerPosition);
// editing a parent will cause all the children to automatically follow along, so don't
// edit any entity who has an ancestor in SelectionManager.selections
var toMove = SelectionManager.selections.filter(function (selection) {
if (SelectionManager.selections.indexOf(SelectionManager.savedProperties[selection].parentID) >= 0) {
return false; // a parent is also being moved, so don't issue an edit for this entity
} else {
return true;
}
});
for (var i = 0; i < toMove.length; i++) {
var properties = SelectionManager.savedProperties[toMove[i]];
if (!properties) {
continue;
}
var newPosition = Vec3.sum(properties.position, {
x: vector.x,
y: 0,
z: vector.z
});
Entities.editEntity(toMove[i], {
position: newPosition
});
if (wantDebug) {
print("translateXZ... ");
Vec3.print(" vector:", vector);
Vec3.print(" newPosition:", properties.position);
Vec3.print(" newPosition:", newPosition);
}
}
SelectionManager._update();
}
});
// TOOL DEFINITION: HANDLE TRANSLATE TOOL
function addHandleTranslateTool(overlay, mode, direction) {
var pickNormal = null;
var lastPick = null;
var projectionVector = null;
addHandleTool(overlay, {
mode: mode,
onBegin: function(event, pickRay, pickResult) {
if (direction === TRANSLATE_DIRECTION.X) {
pickNormal = { x:0, y:1, z:1 };
} else if (direction === TRANSLATE_DIRECTION.Y) {
pickNormal = { x:1, y:0, z:1 };
} else if (direction === TRANSLATE_DIRECTION.Z) {
pickNormal = { x:1, y:1, z:0 };
}
var rotation = spaceMode === SPACE_LOCAL ? SelectionManager.localRotation : SelectionManager.worldRotation;
pickNormal = Vec3.multiplyQbyV(rotation, pickNormal);
lastPick = rayPlaneIntersection(pickRay, SelectionManager.worldPosition, pickNormal);
SelectionManager.saveProperties();
that.resetPreviousHandleColor();
that.setHandleTranslateXVisible(direction === TRANSLATE_DIRECTION.X);
that.setHandleTranslateYVisible(direction === TRANSLATE_DIRECTION.Y);
that.setHandleTranslateZVisible(direction === TRANSLATE_DIRECTION.Z);
that.setHandleRotateVisible(false);
that.setHandleStretchVisible(false);
that.setHandleScaleCubeVisible(false);
that.setHandleClonerVisible(false);
// Duplicate entities if alt is pressed. This will make a
// copy of the selected entities and move the _original_ entities, not
// the new ones.
if (event.isAlt) {
duplicatedEntityIDs = SelectionManager.duplicateSelection();
} else {
duplicatedEntityIDs = null;
}
},
onEnd: function(event, reason) {
pushCommandForSelections(duplicatedEntityIDs);
},
onMove: function(event) {
pickRay = generalComputePickRay(event.x, event.y);
var newIntersection = rayPlaneIntersection(pickRay, SelectionManager.worldPosition, pickNormal);
var vector = Vec3.subtract(newIntersection, lastPick);
if (direction === TRANSLATE_DIRECTION.X) {
projectionVector = { x:1, y:0, z:0 };
} else if (direction === TRANSLATE_DIRECTION.Y) {
projectionVector = { x:0, y:1, z:0 };
} else if (direction === TRANSLATE_DIRECTION.Z) {
projectionVector = { x:0, y:0, z:1 };
}
var rotation = spaceMode === SPACE_LOCAL ? SelectionManager.localRotation : SelectionManager.worldRotation;
projectionVector = Vec3.multiplyQbyV(rotation, projectionVector);
var dotVector = Vec3.dot(vector, projectionVector);
vector = Vec3.multiply(dotVector, projectionVector);
vector = grid.snapToGrid(vector);
var wantDebug = false;
if (wantDebug) {
print("translateUpDown... ");
print(" event.y:" + event.y);
Vec3.print(" newIntersection:", newIntersection);
Vec3.print(" vector:", vector);
}
// editing a parent will cause all the children to automatically follow along, so don't
// edit any entity who has an ancestor in SelectionManager.selections
var toMove = SelectionManager.selections.filter(function (selection) {
if (SelectionManager.selections.indexOf(SelectionManager.savedProperties[selection].parentID) >= 0) {
return false; // a parent is also being moved, so don't issue an edit for this entity
} else {
return true;
}
});
for (var i = 0; i < toMove.length; i++) {
var id = toMove[i];
var properties = SelectionManager.savedProperties[id];
var newPosition = Vec3.sum(properties.position, vector);
Entities.editEntity(id, { position: newPosition });
}
SelectionManager._update();
}
});
}
// FUNCTION: VEC 3 MULT
var vec3Mult = function(v1, v2) {
return {
x: v1.x * v2.x,
y: v1.y * v2.y,
z: v1.z * v2.z
};
};
that.restoreAvatarCollisionsFromStretch = function() {
if (handleStretchCollisionOverride) {
Menu.setIsOptionChecked(AVATAR_COLLISIONS_OPTION, true);
handleStretchCollisionOverride = false;
}
}
// TOOL DEFINITION: HANDLE STRETCH TOOL
function makeStretchTool(stretchMode, directionEnum, directionVec, pivot, offset, stretchPanel, scaleHandle) {
var directionFor3DStretch = directionVec;
var distanceFor3DStretch = 0;
var DISTANCE_INFLUENCE_THRESHOLD = 1.2;
var signs = {
x: directionVec.x < 0 ? -1 : (directionVec.x > 0 ? 1 : 0),
y: directionVec.y < 0 ? -1 : (directionVec.y > 0 ? 1 : 0),
z: directionVec.z < 0 ? -1 : (directionVec.z > 0 ? 1 : 0)
};
var mask = {
x: Math.abs(directionVec.x) > 0 ? 1 : 0,
y: Math.abs(directionVec.y) > 0 ? 1 : 0,
z: Math.abs(directionVec.z) > 0 ? 1 : 0
};
var numDimensions = mask.x + mask.y + mask.z;
var planeNormal = null;
var lastPick = null;
var lastPick3D = null;
var initialPosition = null;
var initialDimensions = null;
var initialIntersection = null;
var initialProperties = null;
var registrationPoint = null;
var deltaPivot = null;
var deltaPivot3D = null;
var pickRayPosition = null;
var pickRayPosition3D = null;
var rotation = null;
var onBegin = function(event, pickRay, pickResult) {
var properties = Entities.getEntityProperties(SelectionManager.selections[0]);
initialProperties = properties;
rotation = (spaceMode === SPACE_LOCAL) ? properties.rotation : Quat.IDENTITY;
if (spaceMode === SPACE_LOCAL) {
rotation = SelectionManager.localRotation;
initialPosition = SelectionManager.localPosition;
initialDimensions = SelectionManager.localDimensions;
registrationPoint = SelectionManager.localRegistrationPoint;
} else {
rotation = SelectionManager.worldRotation;
initialPosition = SelectionManager.worldPosition;
initialDimensions = SelectionManager.worldDimensions;
registrationPoint = SelectionManager.worldRegistrationPoint;
}
// Modify range of registrationPoint to be [-0.5, 0.5]
var centeredRP = Vec3.subtract(registrationPoint, {
x: 0.5,
y: 0.5,
z: 0.5
});
// Scale pivot to be in the same range as registrationPoint
var scaledPivot = Vec3.multiply(0.5, pivot);
deltaPivot = Vec3.subtract(centeredRP, scaledPivot);
var scaledOffset = Vec3.multiply(0.5, offset);
// Offset from the registration point
offsetRP = Vec3.subtract(scaledOffset, centeredRP);
// Scaled offset in world coordinates
var scaledOffsetWorld = vec3Mult(initialDimensions, offsetRP);
pickRayPosition = Vec3.sum(initialPosition, Vec3.multiplyQbyV(rotation, scaledOffsetWorld));
if (directionFor3DStretch) {
// pivot, offset and pickPlanePosition for 3D manipulation
var scaledPivot3D = Vec3.multiply(0.5, Vec3.multiply(1.0, directionFor3DStretch));
deltaPivot3D = Vec3.subtract(centeredRP, scaledPivot3D);
var scaledOffsetWorld3D = vec3Mult(initialDimensions,
Vec3.subtract(Vec3.multiply(0.5, Vec3.multiply(-1.0, directionFor3DStretch)), centeredRP));
pickRayPosition3D = Vec3.sum(initialPosition, Vec3.multiplyQbyV(rotation, scaledOffsetWorld));
}
var start = null;
var end = null;
if ((numDimensions === 1) && mask.x) {
start = Vec3.multiplyQbyV(rotation, {
x: -10000,
y: 0,
z: 0
});
start = Vec3.sum(start, properties.position);
end = Vec3.multiplyQbyV(rotation, {
x: 10000,
y: 0,
z: 0
});
end = Vec3.sum(end, properties.position);
}
if ((numDimensions === 1) && mask.y) {
start = Vec3.multiplyQbyV(rotation, {
x: 0,
y: -10000,
z: 0
});
start = Vec3.sum(start, properties.position);
end = Vec3.multiplyQbyV(rotation, {
x: 0,
y: 10000,
z: 0
});
end = Vec3.sum(end, properties.position);
}
if ((numDimensions === 1) && mask.z) {
start = Vec3.multiplyQbyV(rotation, {
x: 0,
y: 0,
z: -10000
});
start = Vec3.sum(start, properties.position);
end = Vec3.multiplyQbyV(rotation, {
x: 0,
y: 0,
z: 10000
});
end = Vec3.sum(end, properties.position);
}
if (numDimensions === 1) {
if (mask.x === 1) {
planeNormal = {
x: 0,
y: 1,
z: 0
};
} else if (mask.y === 1) {
planeNormal = {
x: 1,
y: 0,
z: 0
};
} else {
planeNormal = {
x: 0,
y: 1,
z: 0
};
}
} else if (numDimensions === 2) {
if (mask.x === 0) {
planeNormal = {
x: 1,
y: 0,
z: 0
};
} else if (mask.y === 0) {
planeNormal = {
x: 0,
y: 1,
z: 0
};
} else {
planeNormal = {
x: 0,
y: 0,
z: 1
};
}
}
planeNormal = Vec3.multiplyQbyV(rotation, planeNormal);
lastPick = rayPlaneIntersection(pickRay,
pickRayPosition,
planeNormal);
var planeNormal3D = {
x: 0,
y: 0,
z: 0
};
if (directionFor3DStretch) {
lastPick3D = rayPlaneIntersection(pickRay,
pickRayPosition3D,
planeNormal3D);
distanceFor3DStretch = Vec3.length(Vec3.subtract(pickRayPosition3D, pickRay.origin));
}
that.setHandleTranslateVisible(false);
that.setHandleRotateVisible(false);
that.setHandleScaleCubeVisible(true);
that.setHandleStretchXVisible(directionEnum === STRETCH_DIRECTION.X);
that.setHandleStretchYVisible(directionEnum === STRETCH_DIRECTION.Y);
that.setHandleStretchZVisible(directionEnum === STRETCH_DIRECTION.Z);
that.setHandleClonerVisible(false);
SelectionManager.saveProperties();
that.resetPreviousHandleColor();
if (stretchPanel != null) {
Overlays.editOverlay(stretchPanel, { visible: true });
}
if (scaleHandle != null) {
Overlays.editOverlay(scaleHandle, { color: COLOR_SCALE_CUBE_SELECTED });
}
if (Menu.isOptionChecked(AVATAR_COLLISIONS_OPTION)) {
Menu.setIsOptionChecked(AVATAR_COLLISIONS_OPTION, false);
handleStretchCollisionOverride = true;
}
};
var onEnd = function(event, reason) {
if (stretchPanel != null) {
Overlays.editOverlay(stretchPanel, { visible: false });
}
if (scaleHandle != null) {
Overlays.editOverlay(scaleHandle, { color: COLOR_SCALE_CUBE });
}
that.restoreAvatarCollisionsFromStretch();
pushCommandForSelections();
};
var onMove = function(event) {
var proportional = (spaceMode === SPACE_WORLD) || directionEnum === STRETCH_DIRECTION.ALL;
var position, dimensions, rotation;
if (spaceMode === SPACE_LOCAL) {
position = SelectionManager.localPosition;
dimensions = SelectionManager.localDimensions;
rotation = SelectionManager.localRotation;
} else {
position = SelectionManager.worldPosition;
dimensions = SelectionManager.worldDimensions;
rotation = SelectionManager.worldRotation;
}
var localDeltaPivot = deltaPivot;
var localSigns = signs;
var pickRay = generalComputePickRay(event.x, event.y);
// Are we using handControllers or Mouse - only relevant for 3D tools
var controllerPose = getControllerWorldLocation(activeHand, true);
var vector = null;
if (HMD.isHMDAvailable() && HMD.isHandControllerAvailable() &&
controllerPose.valid && that.triggered && directionFor3DStretch) {
localDeltaPivot = deltaPivot3D;
newPick = pickRay.origin;
vector = Vec3.subtract(newPick, lastPick3D);
vector = Vec3.multiplyQbyV(Quat.inverse(rotation), vector);
if (distanceFor3DStretch > DISTANCE_INFLUENCE_THRESHOLD) {
// Range of Motion
vector = Vec3.multiply(distanceFor3DStretch , vector);
}
localSigns = directionFor3DStretch;
} else {
newPick = rayPlaneIntersection(pickRay, pickRayPosition, planeNormal);
vector = Vec3.subtract(newPick, lastPick);
vector = Vec3.multiplyQbyV(Quat.inverse(rotation), vector);
vector = vec3Mult(mask, vector);
}
vector = grid.snapToSpacing(vector);
var changeInDimensions = Vec3.multiply(NEGATE_VECTOR, vec3Mult(localSigns, vector));
if (directionEnum === STRETCH_DIRECTION.ALL) {
var toCameraDistance = getDistanceToCamera(position);
var dimensionsMultiple = toCameraDistance * STRETCH_DIRECTION_ALL_CAMERA_DISTANCE_MULTIPLE;
changeInDimensions = Vec3.multiply(changeInDimensions, dimensionsMultiple);
}
var newDimensions;
if (proportional) {
var absoluteX = Math.abs(changeInDimensions.x);
var absoluteY = Math.abs(changeInDimensions.y);
var absoluteZ = Math.abs(changeInDimensions.z);
var percentChange = 0;
if (absoluteX > absoluteY && absoluteX > absoluteZ) {
percentChange = changeInDimensions.x / initialProperties.dimensions.x;
percentChange = changeInDimensions.x / initialDimensions.x;
} else if (absoluteY > absoluteZ) {
percentChange = changeInDimensions.y / initialProperties.dimensions.y;
percentChange = changeInDimensions.y / initialDimensions.y;
} else {
percentChange = changeInDimensions.z / initialProperties.dimensions.z;
percentChange = changeInDimensions.z / initialDimensions.z;
}
percentChange += 1.0;
newDimensions = Vec3.multiply(percentChange, initialDimensions);
} else {
newDimensions = Vec3.sum(initialDimensions, changeInDimensions);
}
var minimumDimension = directionEnum === STRETCH_DIRECTION.ALL ? STRETCH_ALL_MINIMUM_DIMENSION :
STRETCH_MINIMUM_DIMENSION;
newDimensions.x = Math.max(newDimensions.x, minimumDimension);
newDimensions.y = Math.max(newDimensions.y, minimumDimension);
newDimensions.z = Math.max(newDimensions.z, minimumDimension);
var changeInPosition = Vec3.multiplyQbyV(rotation, vec3Mult(localDeltaPivot, changeInDimensions));
if (directionEnum === STRETCH_DIRECTION.ALL) {
changeInPosition = { x:0, y:0, z:0 };
}
var newPosition = Vec3.sum(initialPosition, changeInPosition);
for (var i = 0; i < SelectionManager.selections.length; i++) {
Entities.editEntity(SelectionManager.selections[i], {
position: newPosition,
dimensions: newDimensions
});
}
var wantDebug = false;
if (wantDebug) {
print(stretchMode);
Vec3.print(" vector:", vector);
Vec3.print(" changeInDimensions:", changeInDimensions);
Vec3.print(" newDimensions:", newDimensions);
Vec3.print(" changeInPosition:", changeInPosition);
Vec3.print(" newPosition:", newPosition);
}
SelectionManager._update();
};// End of onMove def
return {
mode: stretchMode,
onBegin: onBegin,
onMove: onMove,
onEnd: onEnd
};
}
function addHandleStretchTool(overlay, mode, directionEnum) {
var directionVector, offset, stretchPanel;
if (directionEnum === STRETCH_DIRECTION.X) {
stretchPanel = handleStretchXPanel;
directionVector = { x:-1, y:0, z:0 };
} else if (directionEnum === STRETCH_DIRECTION.Y) {
stretchPanel = handleStretchYPanel;
directionVector = { x:0, y:-1, z:0 };
} else if (directionEnum === STRETCH_DIRECTION.Z) {
stretchPanel = handleStretchZPanel
directionVector = { x:0, y:0, z:-1 };
}
offset = Vec3.multiply(directionVector, NEGATE_VECTOR);
var tool = makeStretchTool(mode, directionEnum, directionVector, directionVector, offset, stretchPanel, null);
return addHandleTool(overlay, tool);
}
// TOOL DEFINITION: HANDLE SCALE TOOL
function addHandleScaleTool(overlay, mode, directionEnum) {
var directionVector, offset, selectedHandle;
if (directionEnum === SCALE_DIRECTION.LBN) {
directionVector = { x:1, y:1, z:1 };
selectedHandle = handleScaleLBNCube;
} else if (directionEnum === SCALE_DIRECTION.RBN) {
directionVector = { x:-1, y:1, z:1 };
selectedHandle = handleScaleRBNCube;
} else if (directionEnum === SCALE_DIRECTION.LBF) {
directionVector = { x:1, y:1, z:-1 };
selectedHandle = handleScaleLBFCube;
} else if (directionEnum === SCALE_DIRECTION.RBF) {
directionVector = { x:-1, y:1, z:-1 };
selectedHandle = handleScaleRBFCube;
} else if (directionEnum === SCALE_DIRECTION.LTN) {
directionVector = { x:1, y:-1, z:1 };
selectedHandle = handleScaleLTNCube;
} else if (directionEnum === SCALE_DIRECTION.RTN) {
directionVector = { x:-1, y:-1, z:1 };
selectedHandle = handleScaleRTNCube;
} else if (directionEnum === SCALE_DIRECTION.LTF) {
directionVector = { x:1, y:-1, z:-1 };
selectedHandle = handleScaleLTFCube;
} else if (directionEnum === SCALE_DIRECTION.RTF) {
directionVector = { x:-1, y:-1, z:-1 };
selectedHandle = handleScaleRTFCube;
}
offset = Vec3.multiply(directionVector, NEGATE_VECTOR);
var tool = makeStretchTool(mode, STRETCH_DIRECTION.ALL, directionVector,
directionVector, offset, null, selectedHandle);
return addHandleTool(overlay, tool);
}
// FUNCTION: UPDATE ROTATION DEGREES OVERLAY
function updateRotationDegreesOverlay(angleFromZero, position) {
var angle = angleFromZero * (Math.PI / 180);
var toCameraDistance = getDistanceToCamera(position);
var overlayProps = {
position: position,
dimensions: {
x: toCameraDistance * ROTATE_DISPLAY_SIZE_X_MULTIPLIER,
y: toCameraDistance * ROTATE_DISPLAY_SIZE_Y_MULTIPLIER
},
lineHeight: toCameraDistance * ROTATE_DISPLAY_LINE_HEIGHT_MULTIPLIER,
text: normalizeDegrees(-angleFromZero) + "°"
};
Overlays.editOverlay(rotationDegreesDisplay, overlayProps);
}
// FUNCTION DEF: updateSelectionsRotation
// Helper func used by rotation handle tools
function updateSelectionsRotation(rotationChange, initialPosition) {
if (!rotationChange) {
print("ERROR: entitySelectionTool.updateSelectionsRotation - Invalid arg specified!!");
// EARLY EXIT
return;
}
// Entities should only reposition if we are rotating multiple selections around
// the selections center point. Otherwise, the rotation will be around the entities
// registration point which does not need repositioning.
var reposition = (SelectionManager.selections.length > 1);
// editing a parent will cause all the children to automatically follow along, so don't
// edit any entity who has an ancestor in SelectionManager.selections
var toRotate = SelectionManager.selections.filter(function (selection) {
if (SelectionManager.selections.indexOf(SelectionManager.savedProperties[selection].parentID) >= 0) {
return false; // a parent is also being moved, so don't issue an edit for this entity
} else {
return true;
}
});
for (var i = 0; i < toRotate.length; i++) {
var entityID = toRotate[i];
var initialProperties = SelectionManager.savedProperties[entityID];
var newProperties = {
rotation: Quat.multiply(rotationChange, initialProperties.rotation)
};
if (reposition) {
var dPos = Vec3.subtract(initialProperties.position, initialPosition);
dPos = Vec3.multiplyQbyV(rotationChange, dPos);
newProperties.position = Vec3.sum(initialPosition, dPos);
}
Entities.editEntity(entityID, newProperties);
}
}
// TOOL DEFINITION: HANDLE ROTATION TOOL
function addHandleRotateTool(overlay, mode, direction) {
var selectedHandle = null;
var worldRotation = null;
var rotationCenter = null;
var initialRotation = null;
addHandleTool(overlay, {
mode: mode,
onBegin: function(event, pickRay, pickResult) {
var wantDebug = false;
if (wantDebug) {
print("================== " + getMode() + "(addHandleRotateTool onBegin) -> =======================");
}
SelectionManager.saveProperties();
that.resetPreviousHandleColor();
that.setHandleTranslateVisible(false);
that.setHandleRotatePitchVisible(direction === ROTATE_DIRECTION.PITCH);
that.setHandleRotateYawVisible(direction === ROTATE_DIRECTION.YAW);
that.setHandleRotateRollVisible(direction === ROTATE_DIRECTION.ROLL);
that.setHandleStretchVisible(false);
that.setHandleScaleCubeVisible(false);
that.setHandleClonerVisible(false);
if (direction === ROTATE_DIRECTION.PITCH) {
rotationNormal = { x: 1, y: 0, z: 0 };
worldRotation = worldRotationY;
selectedHandle = handleRotatePitchRing;
} else if (direction === ROTATE_DIRECTION.YAW) {
rotationNormal = { x: 0, y: 1, z: 0 };
worldRotation = worldRotationZ;
selectedHandle = handleRotateYawRing;
} else if (direction === ROTATE_DIRECTION.ROLL) {
rotationNormal = { x: 0, y: 0, z: 1 };
worldRotation = worldRotationX;
selectedHandle = handleRotateRollRing;
}
Overlays.editOverlay(selectedHandle, {
hasTickMarks: true,
solid: false,
innerRadius: ROTATE_RING_SELECTED_INNER_RADIUS
});
initialRotation = spaceMode === SPACE_LOCAL ? SelectionManager.localRotation : SelectionManager.worldRotation;
rotationNormal = Vec3.multiplyQbyV(initialRotation, rotationNormal);
rotationCenter = SelectionManager.worldPosition;
Overlays.editOverlay(rotationDegreesDisplay, { visible: true });
Overlays.editOverlay(handleRotateCurrentRing, {
position: rotationCenter,
rotation: worldRotation,
startAt: 0,
endAt: 0,
visible: true
});
// editOverlays may not have committed rotation changes.
// Compute zero position based on where the overlay will be eventually.
var result = rayPlaneIntersection(pickRay, rotationCenter, rotationNormal);
// In case of a parallel ray, this will be null, which will cause early-out
// in the onMove helper.
rotationZero = result;
var rotationCenterToZero = Vec3.subtract(rotationZero, rotationCenter);
var rotationCenterToZeroLength = Vec3.length(rotationCenterToZero);
rotationDegreesPosition = Vec3.sum(rotationCenter, Vec3.multiply(Vec3.normalize(rotationCenterToZero),
rotationCenterToZeroLength * ROTATE_DISPLAY_DISTANCE_MULTIPLIER));
updateRotationDegreesOverlay(0, rotationDegreesPosition);
if (wantDebug) {
print("================== " + getMode() + "(addHandleRotateTool onBegin) <- =======================");
}
},
onEnd: function(event, reason) {
var wantDebug = false;
if (wantDebug) {
print("================== " + getMode() + "(addHandleRotateTool onEnd) -> =======================");
}
Overlays.editOverlay(rotationDegreesDisplay, { visible: false });
Overlays.editOverlay(selectedHandle, {
hasTickMarks: false,
solid: true,
innerRadius: ROTATE_RING_IDLE_INNER_RADIUS
});
Overlays.editOverlay(handleRotateCurrentRing, { visible: false });
pushCommandForSelections();
if (wantDebug) {
print("================== " + getMode() + "(addHandleRotateTool onEnd) <- =======================");
}
},
onMove: function(event) {
if (!rotationZero) {
print("ERROR: entitySelectionTool.addHandleRotateTool.onMove - " +
"Invalid RotationZero Specified (missed rotation target plane?)");
// EARLY EXIT
return;
}
var wantDebug = false;
if (wantDebug) {
print("================== "+ getMode() + "(addHandleRotateTool onMove) -> =======================");
Vec3.print(" rotationZero: ", rotationZero);
}
var pickRay = generalComputePickRay(event.x, event.y);
var result = rayPlaneIntersection(pickRay, rotationCenter, rotationNormal);
if (result) {
var centerToZero = Vec3.subtract(rotationZero, rotationCenter);
var centerToIntersect = Vec3.subtract(result, rotationCenter);
if (wantDebug) {
Vec3.print(" RotationNormal: ", rotationNormal);
Vec3.print(" rotationZero: ", rotationZero);
Vec3.print(" rotationCenter: ", rotationCenter);
Vec3.print(" intersect: ", result);
Vec3.print(" centerToZero: ", centerToZero);
Vec3.print(" centerToIntersect: ", centerToIntersect);
}
// Note: orientedAngle which wants normalized centerToZero and centerToIntersect
// handles that internally, so it's to pass unnormalized vectors here.
var angleFromZero = Vec3.orientedAngle(centerToZero, centerToIntersect, rotationNormal);
var snapAngle = ctrlPressed ? ROTATE_CTRL_SNAP_ANGLE : ROTATE_DEFAULT_SNAP_ANGLE;
angleFromZero = Math.floor(angleFromZero / snapAngle) * snapAngle;
var rotationChange = Quat.angleAxis(angleFromZero, rotationNormal);
updateSelectionsRotation(rotationChange, rotationCenter);
updateRotationDegreesOverlay(-angleFromZero, rotationDegreesPosition);
var startAtCurrent = 0;
var endAtCurrent = angleFromZero;
if (angleFromZero < 0) {
startAtCurrent = 360 + angleFromZero;
endAtCurrent = 360;
}
Overlays.editOverlay(handleRotateCurrentRing, {
startAt: startAtCurrent,
endAt: endAtCurrent
});
// not sure why but this seems to be needed to fix an reverse rotation for yaw ring only
if (direction === ROTATE_DIRECTION.YAW) {
if (spaceMode === SPACE_LOCAL) {
Overlays.editOverlay(handleRotateCurrentRing, { rotation: worldRotationZ });
} else {
Overlays.editOverlay(handleRotateCurrentRing, {
rotation: Quat.fromPitchYawRollDegrees(-90, 0, 0)
});
}
}
}
if (wantDebug) {
print("================== "+ getMode() + "(addHandleRotateTool onMove) <- =======================");
}
}
});
}
// TOOL DEFINITION: HANDLE CLONER
addHandleTool(handleCloner, {
mode: "CLONE",
onBegin: function(event, pickRay, pickResult) {
var doClone = true;
translateXZTool.onBegin(event,pickRay,pickResult,doClone);
},
elevation: function (event) {
translateXZTool.elevation(event);
},
onEnd: function (event) {
translateXZTool.onEnd(event);
},
onMove: function (event) {
translateXZTool.onMove(event);
}
});
addHandleTranslateTool(handleTranslateXCone, "TRANSLATE_X", TRANSLATE_DIRECTION.X);
addHandleTranslateTool(handleTranslateXCylinder, "TRANSLATE_X", TRANSLATE_DIRECTION.X);
addHandleTranslateTool(handleTranslateYCone, "TRANSLATE_Y", TRANSLATE_DIRECTION.Y);
addHandleTranslateTool(handleTranslateYCylinder, "TRANSLATE_Y", TRANSLATE_DIRECTION.Y);
addHandleTranslateTool(handleTranslateZCone, "TRANSLATE_Z", TRANSLATE_DIRECTION.Z);
addHandleTranslateTool(handleTranslateZCylinder, "TRANSLATE_Z", TRANSLATE_DIRECTION.Z);
addHandleRotateTool(handleRotatePitchRing, "ROTATE_PITCH", ROTATE_DIRECTION.PITCH);
addHandleRotateTool(handleRotateYawRing, "ROTATE_YAW", ROTATE_DIRECTION.YAW);
addHandleRotateTool(handleRotateRollRing, "ROTATE_ROLL", ROTATE_DIRECTION.ROLL);
addHandleStretchTool(handleStretchXSphere, "STRETCH_X", STRETCH_DIRECTION.X);
addHandleStretchTool(handleStretchYSphere, "STRETCH_Y", STRETCH_DIRECTION.Y);
addHandleStretchTool(handleStretchZSphere, "STRETCH_Z", STRETCH_DIRECTION.Z);
addHandleScaleTool(handleScaleLBNCube, "SCALE_LBN", SCALE_DIRECTION.LBN);
addHandleScaleTool(handleScaleRBNCube, "SCALE_RBN", SCALE_DIRECTION.RBN);
addHandleScaleTool(handleScaleLBFCube, "SCALE_LBF", SCALE_DIRECTION.LBF);
addHandleScaleTool(handleScaleRBFCube, "SCALE_RBF", SCALE_DIRECTION.RBF);
addHandleScaleTool(handleScaleLTNCube, "SCALE_LTN", SCALE_DIRECTION.LTN);
addHandleScaleTool(handleScaleRTNCube, "SCALE_RTN", SCALE_DIRECTION.RTN);
addHandleScaleTool(handleScaleLTFCube, "SCALE_LTF", SCALE_DIRECTION.LTF);
addHandleScaleTool(handleScaleRTFCube, "SCALE_RTF", SCALE_DIRECTION.RTF);
return that;
}());