// // 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; }());