//
//  entitySelectionToolClass.js
//  examples
//
//  Created by Brad hefta-Gaub on 10/1/14.
//  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
//

HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/";

SPACE_LOCAL = "local";
SPACE_WORLD = "world";

Script.include("./controllers.js");

function objectTranslationPlanePoint(position, dimensions) {
    var newPosition = { x: position.x, y: position.y, z: position.z };
    newPosition.y -= dimensions.y / 2.0;
    return newPosition;
}

SelectionManager = (function() {
    var that = {};

    function subscribeToUpdateMessages() {
        Messages.subscribe('entityToolUpdates');
        Messages.messageReceived.connect(handleEntitySelectionToolUpdates);
    }

    function handleEntitySelectionToolUpdates(channel, message, sender) {
        if (channel !== 'entityToolUpdates') {
            return;
        }
        if (sender !== MyAvatar.sessionUUID) {
            return;
        }

        if (message === 'callUpdate') {
            that._update();
        }
    }

    subscribeToUpdateMessages();

    that.savedProperties = {};
    that.selections = [];
    var listeners = [];

    that.localRotation = Quat.fromPitchYawRollDegrees(0, 0, 0);
    that.localPosition = {
        x: 0,
        y: 0,
        z: 0
    };
    that.localDimensions = {
        x: 0,
        y: 0,
        z: 0
    };
    that.localRegistrationPoint = {
        x: 0.5,
        y: 0.5,
        z: 0.5
    };

    that.worldRotation = Quat.fromPitchYawRollDegrees(0, 0, 0);
    that.worldPosition = {
        x: 0,
        y: 0,
        z: 0
    };
    that.worldDimensions = {
        x: 0,
        y: 0,
        z: 0
    };
    that.worldRegistrationPoint = {
        x: 0.5,
        y: 0.5,
        z: 0.5
    };
    that.centerPosition = {
        x: 0,
        y: 0,
        z: 0
    };

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

        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);
            } else if (toggleSelection) {
                that.selections.splice(idx, 1);
            }
        }

        that._update(true);
    };

    that.removeEntity = function(entityID) {
        var idx = that.selections.indexOf(entityID);
        if (idx >= 0) {
            that.selections.splice(idx, 1);
        }
        that._update(true);
    };

    that.clearSelections = function() {
        that.selections = [];
        that._update(true);
    };

    that._update = function(selectionUpdated) {
        if (that.selections.length == 0) {
            that.localDimensions = null;
            that.localPosition = null;
            that.worldDimensions = null;
            that.worldPosition = null;
        } else if (that.selections.length == 1) {
            var 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;

            SelectionDisplay.setSpaceMode(SPACE_LOCAL);
        } else {
            that.localRotation = null;
            that.localDimensions = null;
            that.localPosition = null;

            var 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 i = 0; i < listeners.length; i++) {
            try {
                listeners[i](selectionUpdated === true);
            } catch (e) {
                print("EntitySelectionTool got exception: " + JSON.stringify(e));
            }
        }
    };

    return that;
})();

// Normalize degrees to be in the range (-180, 180]
function normalizeDegrees(degrees) {
    while (degrees > 180) degrees -= 360;
    while (degrees <= -180) degrees += 360;
    return degrees;
}

// Return the enter position of an entity relative to it's registrationPoint
// A registration point of (0.5, 0.5, 0.5) will have an offset of (0, 0, 0)
// A registration point of (1.0, 1.0, 1.0) will have an offset of (-dimensions.x / 2, -dimensions.y / 2, -dimensions.z / 2)
function getRelativeCenterPosition(dimensions, registrationPoint) {
    return {
        x: -dimensions.x * (registrationPoint.x - 0.5),
        y: -dimensions.y * (registrationPoint.y - 0.5),
        z: -dimensions.z * (registrationPoint.z - 0.5),
    }
}

SelectionDisplay = (function() {
    var that = {};

    var MINIMUM_DIMENSION = 0.001;

    var GRABBER_DISTANCE_TO_SIZE_RATIO = 0.0075;

    // These are multipliers for sizing the rotation degrees display while rotating an entity
    var ROTATION_DISPLAY_DISTANCE_MULTIPLIER = 1.2;
    var ROTATION_DISPLAY_SIZE_X_MULTIPLIER = 0.6;
    var ROTATION_DISPLAY_SIZE_Y_MULTIPLIER = 0.18;
    var ROTATION_DISPLAY_LINE_HEIGHT_MULTIPLIER = 0.14;

    var ROTATE_ARROW_WEST_NORTH_URL = HIFI_PUBLIC_BUCKET + "images/rotate-arrow-west-north.svg";
    var ROTATE_ARROW_WEST_SOUTH_URL = HIFI_PUBLIC_BUCKET + "images/rotate-arrow-west-south.svg";

    var showExtendedStretchHandles = false;

    var spaceMode = SPACE_LOCAL;
    var mode = "UNKNOWN";
    var overlayNames = new Array();
    var lastCameraPosition = Camera.getPosition();
    var lastCameraOrientation = Camera.getOrientation();

    var handleHoverColor = {
        red: 224,
        green: 67,
        blue: 36
    };
    var handleHoverAlpha = 1.0;

    var rotateOverlayTargetSize = 10000; // really big target
    var innerSnapAngle = 22.5; // the angle which we snap to on the inner rotation tool
    var innerRadius;
    var outerRadius;
    var yawHandleRotation;
    var pitchHandleRotation;
    var rollHandleRotation;
    var yawCenter;
    var pitchCenter;
    var rollCenter;
    var yawZero;
    var pitchZero;
    var rollZero;
    var yawNormal;
    var pitchNormal;
    var rollNormal;
    var rotationNormal;

    var originalRotation;
    var originalPitch;
    var originalYaw;
    var originalRoll;


    var handleColor = {
        red: 255,
        green: 255,
        blue: 255
    };
    var handleAlpha = 0.7;

    var highlightedHandleColor = {
        red: 183,
        green: 64,
        blue: 44
    };
    var highlightedHandleAlpha = 0.9;

    var previousHandle = false;
    var previousHandleColor;
    var previousHandleAlpha;

    var grabberSizeCorner = 0.025; // These get resized by updateHandleSizes().
    var grabberSizeEdge = 0.015;
    var grabberSizeFace = 0.025;
    var grabberAlpha = 1;
    var grabberColorCorner = {
        red: 120,
        green: 120,
        blue: 120
    };
    var grabberColorEdge = {
        red: 0,
        green: 0,
        blue: 0
    };
    var grabberColorFace = {
        red: 120,
        green: 120,
        blue: 120
    };
    var grabberLineWidth = 0.5;
    var grabberSolid = true;
    var grabberMoveUpPosition = {
        x: 0,
        y: 0,
        z: 0
    };

    var lightOverlayColor = {
        red: 255,
        green: 153,
        blue: 0
    };

    var grabberPropertiesCorner = {
        position: {
            x: 0,
            y: 0,
            z: 0
        },
        size: grabberSizeCorner,
        color: grabberColorCorner,
        alpha: 1,
        solid: grabberSolid,
        visible: false,
        dashed: false,
        lineWidth: grabberLineWidth,
        drawInFront: true,
        borderSize: 1.4,
    };

    var grabberPropertiesEdge = {
        position: {
            x: 0,
            y: 0,
            z: 0
        },
        size: grabberSizeEdge,
        color: grabberColorEdge,
        alpha: 1,
        solid: grabberSolid,
        visible: false,
        dashed: false,
        lineWidth: grabberLineWidth,
        drawInFront: true,
        borderSize: 1.4,
    };

    var grabberPropertiesFace = {
        position: {
            x: 0,
            y: 0,
            z: 0
        },
        size: grabberSizeFace,
        color: grabberColorFace,
        alpha: 1,
        solid: grabberSolid,
        visible: false,
        dashed: false,
        lineWidth: grabberLineWidth,
        drawInFront: true,
        borderSize: 1.4,
    };

    var spotLightLineProperties = {
        color: lightOverlayColor,
        lineWidth: 1.5,
    };

    var highlightBox = Overlays.addOverlay("cube", {
        position: {
            x: 0,
            y: 0,
            z: 0
        },
        size: 1,
        color: {
            red: 90,
            green: 90,
            blue: 90
        },
        alpha: 1,
        solid: false,
        visible: false,
        dashed: true,
        lineWidth: 2.0,
        ignoreRayIntersection: true, // this never ray intersects
        drawInFront: true
    });

    var selectionBox = Overlays.addOverlay("cube", {
        position: {
            x: 0,
            y: 0,
            z: 0
        },
        size: 1,
        color: {
            red: 255,
            green: 0,
            blue: 0
        },
        alpha: 1,
        solid: false,
        visible: false,
        dashed: false,
        lineWidth: 1.0,
    });

    var selectionBoxes = [];

    var rotationDegreesDisplay = Overlays.addOverlay("text3d", {
        position: {
            x: 0,
            y: 0,
            z: 0
        },
        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 grabberMoveUp = Overlays.addOverlay("image3d", {
        url: HIFI_PUBLIC_BUCKET + "images/up-arrow.svg",
        position: {
            x: 0,
            y: 0,
            z: 0
        },
        color: handleColor,
        alpha: handleAlpha,
        visible: false,
        size: 0.1,
        scale: 0.1,
        isFacingAvatar: true,
        drawInFront: true,
    });

    // var normalLine = Overlays.addOverlay("line3d", {
    //                 visible: true,
    //                 lineWidth: 2.0,
    //                 start: { x: 0, y: 0, z: 0 },
    //                 end: { x: 0, y: 0, z: 0 },
    //                 color: { red: 255, green: 255, blue: 0 },
    //                 ignoreRayIntersection: true,
    // });

    var grabberLBN = Overlays.addOverlay("cube", grabberPropertiesCorner);
    var grabberRBN = Overlays.addOverlay("cube", grabberPropertiesCorner);
    var grabberLBF = Overlays.addOverlay("cube", grabberPropertiesCorner);
    var grabberRBF = Overlays.addOverlay("cube", grabberPropertiesCorner);
    var grabberLTN = Overlays.addOverlay("cube", grabberPropertiesCorner);
    var grabberRTN = Overlays.addOverlay("cube", grabberPropertiesCorner);
    var grabberLTF = Overlays.addOverlay("cube", grabberPropertiesCorner);
    var grabberRTF = Overlays.addOverlay("cube", grabberPropertiesCorner);

    var grabberTOP = Overlays.addOverlay("cube", grabberPropertiesFace);
    var grabberBOTTOM = Overlays.addOverlay("cube", grabberPropertiesFace);
    var grabberLEFT = Overlays.addOverlay("cube", grabberPropertiesFace);
    var grabberRIGHT = Overlays.addOverlay("cube", grabberPropertiesFace);
    var grabberNEAR = Overlays.addOverlay("cube", grabberPropertiesFace);
    var grabberFAR = Overlays.addOverlay("cube", grabberPropertiesFace);

    var grabberEdgeTR = Overlays.addOverlay("cube", grabberPropertiesEdge);
    var grabberEdgeTL = Overlays.addOverlay("cube", grabberPropertiesEdge);
    var grabberEdgeTF = Overlays.addOverlay("cube", grabberPropertiesEdge);
    var grabberEdgeTN = Overlays.addOverlay("cube", grabberPropertiesEdge);
    var grabberEdgeBR = Overlays.addOverlay("cube", grabberPropertiesEdge);
    var grabberEdgeBL = Overlays.addOverlay("cube", grabberPropertiesEdge);
    var grabberEdgeBF = Overlays.addOverlay("cube", grabberPropertiesEdge);
    var grabberEdgeBN = Overlays.addOverlay("cube", grabberPropertiesEdge);
    var grabberEdgeNR = Overlays.addOverlay("cube", grabberPropertiesEdge);
    var grabberEdgeNL = Overlays.addOverlay("cube", grabberPropertiesEdge);
    var grabberEdgeFR = Overlays.addOverlay("cube", grabberPropertiesEdge);
    var grabberEdgeFL = Overlays.addOverlay("cube", grabberPropertiesEdge);

    var grabberSpotLightCircle = Overlays.addOverlay("circle3d", {
        color: lightOverlayColor,
        isSolid: false,
        visible: false
    });
    var grabberSpotLightLineT = Overlays.addOverlay("line3d", spotLightLineProperties);
    var grabberSpotLightLineB = Overlays.addOverlay("line3d", spotLightLineProperties);
    var grabberSpotLightLineL = Overlays.addOverlay("line3d", spotLightLineProperties);
    var grabberSpotLightLineR = Overlays.addOverlay("line3d", spotLightLineProperties);

    var grabberSpotLightCenter = Overlays.addOverlay("cube", grabberPropertiesEdge);
    var grabberSpotLightRadius = Overlays.addOverlay("cube", grabberPropertiesEdge);
    var grabberSpotLightL = Overlays.addOverlay("cube", grabberPropertiesEdge);
    var grabberSpotLightR = Overlays.addOverlay("cube", grabberPropertiesEdge);
    var grabberSpotLightT = Overlays.addOverlay("cube", grabberPropertiesEdge);
    var grabberSpotLightB = Overlays.addOverlay("cube", grabberPropertiesEdge);

    var grabberPointLightCircleX = Overlays.addOverlay("circle3d", {
        rotation: Quat.fromPitchYawRollDegrees(0, 90, 0),
        color: lightOverlayColor,
        isSolid: false,
        visible: false
    });
    var grabberPointLightCircleY = Overlays.addOverlay("circle3d", {
        rotation: Quat.fromPitchYawRollDegrees(90, 0, 0),
        color: lightOverlayColor,
        isSolid: false,
        visible: false
    });
    var grabberPointLightCircleZ = Overlays.addOverlay("circle3d", {
        rotation: Quat.fromPitchYawRollDegrees(0, 0, 0),
        color: lightOverlayColor,
        isSolid: false,
        visible: false
    });
    var grabberPointLightT = Overlays.addOverlay("cube", grabberPropertiesEdge);
    var grabberPointLightB = Overlays.addOverlay("cube", grabberPropertiesEdge);
    var grabberPointLightL = Overlays.addOverlay("cube", grabberPropertiesEdge);
    var grabberPointLightR = Overlays.addOverlay("cube", grabberPropertiesEdge);
    var grabberPointLightF = Overlays.addOverlay("cube", grabberPropertiesEdge);
    var grabberPointLightN = Overlays.addOverlay("cube", grabberPropertiesEdge);

    var stretchHandles = [
        grabberLBN,
        grabberRBN,
        grabberLBF,
        grabberRBF,
        grabberLTN,
        grabberRTN,
        grabberLTF,
        grabberRTF,
        grabberTOP,
        grabberBOTTOM,
        grabberLEFT,
        grabberRIGHT,
        grabberNEAR,
        grabberFAR,
        grabberEdgeTR,
        grabberEdgeTL,
        grabberEdgeTF,
        grabberEdgeTN,
        grabberEdgeBR,
        grabberEdgeBL,
        grabberEdgeBF,
        grabberEdgeBN,
        grabberEdgeNR,
        grabberEdgeNL,
        grabberEdgeFR,
        grabberEdgeFL,

        grabberSpotLightLineT,
        grabberSpotLightLineB,
        grabberSpotLightLineL,
        grabberSpotLightLineR,

        grabberSpotLightCenter,
        grabberSpotLightRadius,
        grabberSpotLightL,
        grabberSpotLightR,
        grabberSpotLightT,
        grabberSpotLightB,

        grabberPointLightT,
        grabberPointLightB,
        grabberPointLightL,
        grabberPointLightR,
        grabberPointLightF,
        grabberPointLightN,
    ];


    var baseOverlayAngles = {
        x: 0,
        y: 0,
        z: 0
    };
    var baseOverlayRotation = Quat.fromVec3Degrees(baseOverlayAngles);
    var baseOfEntityProjectionOverlay = Overlays.addOverlay("rectangle3d", {
        position: {
            x: 1,
            y: 0,
            z: 0
        },
        color: {
            red: 51,
            green: 152,
            blue: 203
        },
        alpha: 0.5,
        solid: true,
        visible: false,
        width: 300,
        height: 200,
        rotation: baseOverlayRotation,
        ignoreRayIntersection: true, // always ignore this
    });

    var yawOverlayAngles = {
        x: 90,
        y: 0,
        z: 0
    };
    var yawOverlayRotation = Quat.fromVec3Degrees(yawOverlayAngles);
    var pitchOverlayAngles = {
        x: 0,
        y: 90,
        z: 0
    };
    var pitchOverlayRotation = Quat.fromVec3Degrees(pitchOverlayAngles);
    var rollOverlayAngles = {
        x: 0,
        y: 180,
        z: 0
    };
    var rollOverlayRotation = Quat.fromVec3Degrees(rollOverlayAngles);

    var xRailOverlay = Overlays.addOverlay("line3d", {
        visible: false,
        lineWidth: 1.0,
        start: {
            x: 0,
            y: 0,
            z: 0
        },
        end: {
            x: 0,
            y: 0,
            z: 0
        },
        color: {
            red: 255,
            green: 0,
            blue: 0
        },
        ignoreRayIntersection: true, // always ignore this
        visible: false,
    });
    var yRailOverlay = Overlays.addOverlay("line3d", {
        visible: false,
        lineWidth: 1.0,
        start: {
            x: 0,
            y: 0,
            z: 0
        },
        end: {
            x: 0,
            y: 0,
            z: 0
        },
        color: {
            red: 0,
            green: 255,
            blue: 0
        },
        ignoreRayIntersection: true, // always ignore this
        visible: false,
    });
    var zRailOverlay = Overlays.addOverlay("line3d", {
        visible: false,
        lineWidth: 1.0,
        start: {
            x: 0,
            y: 0,
            z: 0
        },
        end: {
            x: 0,
            y: 0,
            z: 0
        },
        color: {
            red: 0,
            green: 0,
            blue: 255
        },
        ignoreRayIntersection: true, // always ignore this
        visible: false,
    });

    var rotateZeroOverlay = Overlays.addOverlay("line3d", {
        visible: false,
        lineWidth: 2.0,
        start: {
            x: 0,
            y: 0,
            z: 0
        },
        end: {
            x: 0,
            y: 0,
            z: 0
        },
        color: {
            red: 255,
            green: 0,
            blue: 0
        },
        ignoreRayIntersection: true, // always ignore this
    });

    var rotateCurrentOverlay = Overlays.addOverlay("line3d", {
        visible: false,
        lineWidth: 2.0,
        start: {
            x: 0,
            y: 0,
            z: 0
        },
        end: {
            x: 0,
            y: 0,
            z: 0
        },
        color: {
            red: 0,
            green: 0,
            blue: 255
        },
        ignoreRayIntersection: true, // always ignore this
    });


    var rotateOverlayTarget = Overlays.addOverlay("circle3d", {
        position: {
            x: 0,
            y: 0,
            z: 0
        },
        size: rotateOverlayTargetSize,
        color: {
            red: 0,
            green: 0,
            blue: 0
        },
        alpha: 0.0,
        solid: true,
        visible: false,
        rotation: yawOverlayRotation,
    });

    var rotateOverlayInner = Overlays.addOverlay("circle3d", {
        position: {
            x: 0,
            y: 0,
            z: 0
        },
        size: 1,
        color: {
            red: 51,
            green: 152,
            blue: 203
        },
        alpha: 0.2,
        solid: true,
        visible: false,
        rotation: yawOverlayRotation,
        hasTickMarks: true,
        majorTickMarksAngle: innerSnapAngle,
        minorTickMarksAngle: 0,
        majorTickMarksLength: -0.25,
        minorTickMarksLength: 0,
        majorTickMarksColor: {
            red: 0,
            green: 0,
            blue: 0
        },
        minorTickMarksColor: {
            red: 0,
            green: 0,
            blue: 0
        },
        ignoreRayIntersection: true, // always ignore this
    });

    var rotateOverlayOuter = Overlays.addOverlay("circle3d", {
        position: {
            x: 0,
            y: 0,
            z: 0
        },
        size: 1,
        color: {
            red: 51,
            green: 152,
            blue: 203
        },
        alpha: 0.2,
        solid: true,
        visible: false,
        rotation: yawOverlayRotation,

        hasTickMarks: true,
        majorTickMarksAngle: 45.0,
        minorTickMarksAngle: 5,
        majorTickMarksLength: 0.25,
        minorTickMarksLength: 0.1,
        majorTickMarksColor: {
            red: 0,
            green: 0,
            blue: 0
        },
        minorTickMarksColor: {
            red: 0,
            green: 0,
            blue: 0
        },
        ignoreRayIntersection: true, // always ignore this
    });

    var rotateOverlayCurrent = Overlays.addOverlay("circle3d", {
        position: {
            x: 0,
            y: 0,
            z: 0
        },
        size: 1,
        color: {
            red: 224,
            green: 67,
            blue: 36
        },
        alpha: 0.8,
        solid: true,
        visible: false,
        rotation: yawOverlayRotation,
        ignoreRayIntersection: true, // always ignore this
        hasTickMarks: true,
        majorTickMarksColor: {
            red: 0,
            green: 0,
            blue: 0
        },
        minorTickMarksColor: {
            red: 0,
            green: 0,
            blue: 0
        },
    });

    var yawHandle = Overlays.addOverlay("image3d", {
        url: ROTATE_ARROW_WEST_NORTH_URL,
        position: {
            x: 0,
            y: 0,
            z: 0
        },
        color: handleColor,
        alpha: handleAlpha,
        visible: false,
        size: 0.1,
        scale: 0.1,
        isFacingAvatar: false,
        drawInFront: true,
    });


    var pitchHandle = Overlays.addOverlay("image3d", {
        url: ROTATE_ARROW_WEST_NORTH_URL,
        position: {
            x: 0,
            y: 0,
            z: 0
        },
        color: handleColor,
        alpha: handleAlpha,
        visible: false,
        size: 0.1,
        scale: 0.1,
        isFacingAvatar: false,
        drawInFront: true,
    });


    var rollHandle = Overlays.addOverlay("image3d", {
        url: ROTATE_ARROW_WEST_NORTH_URL,
        position: {
            x: 0,
            y: 0,
            z: 0
        },
        color: handleColor,
        alpha: handleAlpha,
        visible: false,
        size: 0.1,
        scale: 0.1,
        isFacingAvatar: false,
        drawInFront: true,
    });

    var allOverlays = [
        highlightBox,
        selectionBox,
        grabberMoveUp,
        yawHandle,
        pitchHandle,
        rollHandle,
        rotateOverlayTarget,
        rotateOverlayInner,
        rotateOverlayOuter,
        rotateOverlayCurrent,
        rotateZeroOverlay,
        rotateCurrentOverlay,
        rotationDegreesDisplay,
        xRailOverlay,
        yRailOverlay,
        zRailOverlay,
        baseOfEntityProjectionOverlay,
        grabberSpotLightCircle,
        grabberPointLightCircleX,
        grabberPointLightCircleY,
        grabberPointLightCircleZ,
    ].concat(stretchHandles);

    overlayNames[highlightBox] = "highlightBox";
    overlayNames[selectionBox] = "selectionBox";
    overlayNames[baseOfEntityProjectionOverlay] = "baseOfEntityProjectionOverlay";
    overlayNames[grabberMoveUp] = "grabberMoveUp";
    overlayNames[grabberLBN] = "grabberLBN";
    overlayNames[grabberLBF] = "grabberLBF";
    overlayNames[grabberRBN] = "grabberRBN";
    overlayNames[grabberRBF] = "grabberRBF";
    overlayNames[grabberLTN] = "grabberLTN";
    overlayNames[grabberLTF] = "grabberLTF";
    overlayNames[grabberRTN] = "grabberRTN";
    overlayNames[grabberRTF] = "grabberRTF";

    overlayNames[grabberTOP] = "grabberTOP";
    overlayNames[grabberBOTTOM] = "grabberBOTTOM";
    overlayNames[grabberLEFT] = "grabberLEFT";
    overlayNames[grabberRIGHT] = "grabberRIGHT";
    overlayNames[grabberNEAR] = "grabberNEAR";
    overlayNames[grabberFAR] = "grabberFAR";

    overlayNames[grabberEdgeTR] = "grabberEdgeTR";
    overlayNames[grabberEdgeTL] = "grabberEdgeTL";
    overlayNames[grabberEdgeTF] = "grabberEdgeTF";
    overlayNames[grabberEdgeTN] = "grabberEdgeTN";
    overlayNames[grabberEdgeBR] = "grabberEdgeBR";
    overlayNames[grabberEdgeBL] = "grabberEdgeBL";
    overlayNames[grabberEdgeBF] = "grabberEdgeBF";
    overlayNames[grabberEdgeBN] = "grabberEdgeBN";
    overlayNames[grabberEdgeNR] = "grabberEdgeNR";
    overlayNames[grabberEdgeNL] = "grabberEdgeNL";
    overlayNames[grabberEdgeFR] = "grabberEdgeFR";
    overlayNames[grabberEdgeFL] = "grabberEdgeFL";

    overlayNames[yawHandle] = "yawHandle";
    overlayNames[pitchHandle] = "pitchHandle";
    overlayNames[rollHandle] = "rollHandle";

    overlayNames[rotateOverlayTarget] = "rotateOverlayTarget";
    overlayNames[rotateOverlayInner] = "rotateOverlayInner";
    overlayNames[rotateOverlayOuter] = "rotateOverlayOuter";
    overlayNames[rotateOverlayCurrent] = "rotateOverlayCurrent";

    overlayNames[rotateZeroOverlay] = "rotateZeroOverlay";
    overlayNames[rotateCurrentOverlay] = "rotateCurrentOverlay";

    var activeTool = null;
    var grabberTools = {};

    // 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;
                }
                var eventResult = that.mousePressEvent({});
                if (!eventResult || (eventResult === 'selectionBox')) {
                    var pickRay = controllerComputePickRay();
                    if (pickRay) {
                        var entityIntersection = Entities.findRayIntersection(pickRay, true);


                        var overlayIntersection = Overlays.findRayIntersection(pickRay);
                        if (entityIntersection.intersects &&
                            (!overlayIntersection.intersects || (entityIntersection.distance < overlayIntersection.distance))) {

                            if (HMD.tabletID === entityIntersection.entityID) {
                                return;
                            }

                            selectionManager.setSelections([entityIntersection.entityID]);
                        }
                    }
                }
            } 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 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 addGrabberTool(overlay, tool) {
        grabberTools[overlay] = {
            mode: tool.mode,
            onBegin: tool.onBegin,
            onMove: tool.onMove,
            onEnd: tool.onEnd,
        }
    }


    that.cleanup = function() {
        for (var i = 0; i < allOverlays.length; i++) {
            Overlays.deleteOverlay(allOverlays[i]);
        }
        for (var i = 0; i < selectionBoxes.length; i++) {
            Overlays.deleteOverlay(selectionBoxes[i]);
        }
    };

    that.highlightSelectable = function(entityID) {
        var properties = Entities.getEntityProperties(entityID);
        Overlays.editOverlay(highlightBox, {
            visible: true,
            position: properties.boundingBox.center,
            dimensions: properties.dimensions,
            rotation: properties.rotation
        });
    };

    that.unhighlightSelectable = function(entityID) {
        Overlays.editOverlay(highlightBox, {
            visible: false
        });
    };

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


        }

        Overlays.editOverlay(highlightBox, {
            visible: false
        });

        that.updateHandles();
    }

    that.updateRotationHandles = function() {
        var diagonal = (Vec3.length(selectionManager.worldDimensions) / 2) * 1.1;
        var halfDimensions = Vec3.multiply(selectionManager.worldDimensions, 0.5);
        var innerActive = false;
        var innerAlpha = 0.2;
        var outerAlpha = 0.2;
        if (innerActive) {
            innerAlpha = 0.5;
        } else {
            outerAlpha = 0.5;
        }

        var rotateHandleOffset = 0.05;

        var top, far, left, bottom, near, right, boundsCenter, objectCenter, BLN, BRN, BLF, TLN, TRN, TLF, TRF;

        var dimensions, rotation;
        if (spaceMode == SPACE_LOCAL) {
            rotation = SelectionManager.localRotation;
        } else {
            rotation = SelectionManager.worldRotation;
        }
        objectCenter = SelectionManager.worldPosition;
        dimensions = SelectionManager.worldDimensions;
        var position = objectCenter;

        top = objectCenter.y + (dimensions.y / 2);
        far = objectCenter.z + (dimensions.z / 2);
        left = objectCenter.x + (dimensions.x / 2);

        bottom = objectCenter.y - (dimensions.y / 2);
        near = objectCenter.z - (dimensions.z / 2);
        right = objectCenter.x - (dimensions.x / 2);

        boundsCenter = objectCenter;

        var yawCorner;
        var pitchCorner;
        var rollCorner;

        // determine which bottom corner we are closest to
        /*------------------------------
          example:

            BRF +--------+ BLF
                |        |
                |        |
            BRN +--------+ BLN

                   *

        ------------------------------*/

        var cameraPosition = Camera.getPosition();
        if (cameraPosition.x > objectCenter.x) {
            // must be BRF or BRN
            if (cameraPosition.z < objectCenter.z) {

                yawHandleRotation = Quat.fromVec3Degrees({
                    x: 270,
                    y: 90,
                    z: 0
                });
                pitchHandleRotation = Quat.fromVec3Degrees({
                    x: 0,
                    y: 90,
                    z: 0
                });
                rollHandleRotation = Quat.fromVec3Degrees({
                    x: 0,
                    y: 0,
                    z: 0
                });

                yawNormal = {
                    x: 0,
                    y: 1,
                    z: 0
                };
                pitchNormal = {
                    x: 1,
                    y: 0,
                    z: 0
                };
                rollNormal = {
                    x: 0,
                    y: 0,
                    z: 1
                };

                yawCorner = {
                    x: left + rotateHandleOffset,
                    y: bottom - rotateHandleOffset,
                    z: near - rotateHandleOffset
                };

                pitchCorner = {
                    x: right - rotateHandleOffset,
                    y: top + rotateHandleOffset,
                    z: near - rotateHandleOffset
                };

                rollCorner = {
                    x: left + rotateHandleOffset,
                    y: top + rotateHandleOffset,
                    z: far + rotateHandleOffset
                };

                yawCenter = {
                    x: boundsCenter.x,
                    y: bottom,
                    z: boundsCenter.z
                };
                pitchCenter = {
                    x: right,
                    y: boundsCenter.y,
                    z: boundsCenter.z
                };
                rollCenter = {
                    x: boundsCenter.x,
                    y: boundsCenter.y,
                    z: far
                };


                Overlays.editOverlay(pitchHandle, {
                    url: ROTATE_ARROW_WEST_SOUTH_URL
                });
                Overlays.editOverlay(rollHandle, {
                    url: ROTATE_ARROW_WEST_SOUTH_URL
                });


            } else {

                yawHandleRotation = Quat.fromVec3Degrees({
                    x: 270,
                    y: 0,
                    z: 0
                });
                pitchHandleRotation = Quat.fromVec3Degrees({
                    x: 180,
                    y: 270,
                    z: 0
                });
                rollHandleRotation = Quat.fromVec3Degrees({
                    x: 0,
                    y: 0,
                    z: 90
                });

                yawNormal = {
                    x: 0,
                    y: 1,
                    z: 0
                };
                pitchNormal = {
                    x: 1,
                    y: 0,
                    z: 0
                };
                rollNormal = {
                    x: 0,
                    y: 0,
                    z: 1
                };


                yawCorner = {
                    x: left + rotateHandleOffset,
                    y: bottom - rotateHandleOffset,
                    z: far + rotateHandleOffset
                };

                pitchCorner = {
                    x: right - rotateHandleOffset,
                    y: top + rotateHandleOffset,
                    z: far + rotateHandleOffset
                };

                rollCorner = {
                    x: left + rotateHandleOffset,
                    y: top + rotateHandleOffset,
                    z: near - rotateHandleOffset
                };


                yawCenter = {
                    x: boundsCenter.x,
                    y: bottom,
                    z: boundsCenter.z
                };
                pitchCenter = {
                    x: right,
                    y: boundsCenter.y,
                    z: boundsCenter.z
                };
                rollCenter = {
                    x: boundsCenter.x,
                    y: boundsCenter.y,
                    z: near
                };

                Overlays.editOverlay(pitchHandle, {
                    url: ROTATE_ARROW_WEST_NORTH_URL
                });
                Overlays.editOverlay(rollHandle, {
                    url: ROTATE_ARROW_WEST_NORTH_URL
                });
            }
        } else {

            // must be BLF or BLN
            if (cameraPosition.z < objectCenter.z) {

                yawHandleRotation = Quat.fromVec3Degrees({
                    x: 270,
                    y: 180,
                    z: 0
                });
                pitchHandleRotation = Quat.fromVec3Degrees({
                    x: 90,
                    y: 0,
                    z: 90
                });
                rollHandleRotation = Quat.fromVec3Degrees({
                    x: 0,
                    y: 0,
                    z: 180
                });

                yawNormal = {
                    x: 0,
                    y: 1,
                    z: 0
                };
                pitchNormal = {
                    x: 1,
                    y: 0,
                    z: 0
                };
                rollNormal = {
                    x: 0,
                    y: 0,
                    z: 1
                };

                yawCorner = {
                    x: right - rotateHandleOffset,
                    y: bottom - rotateHandleOffset,
                    z: near - rotateHandleOffset
                };

                pitchCorner = {
                    x: left + rotateHandleOffset,
                    y: top + rotateHandleOffset,
                    z: near - rotateHandleOffset
                };

                rollCorner = {
                    x: right - rotateHandleOffset,
                    y: top + rotateHandleOffset,
                    z: far + rotateHandleOffset
                };

                yawCenter = {
                    x: boundsCenter.x,
                    y: bottom,
                    z: boundsCenter.z
                };
                pitchCenter = {
                    x: left,
                    y: boundsCenter.y,
                    z: boundsCenter.z
                };
                rollCenter = {
                    x: boundsCenter.x,
                    y: boundsCenter.y,
                    z: far
                };

                Overlays.editOverlay(pitchHandle, {
                    url: ROTATE_ARROW_WEST_NORTH_URL
                });
                Overlays.editOverlay(rollHandle, {
                    url: ROTATE_ARROW_WEST_NORTH_URL
                });

            } else {

                yawHandleRotation = Quat.fromVec3Degrees({
                    x: 270,
                    y: 270,
                    z: 0
                });
                pitchHandleRotation = Quat.fromVec3Degrees({
                    x: 180,
                    y: 270,
                    z: 0
                });
                rollHandleRotation = Quat.fromVec3Degrees({
                    x: 0,
                    y: 0,
                    z: 180
                });

                yawNormal = {
                    x: 0,
                    y: 1,
                    z: 0
                };
                rollNormal = {
                    x: 0,
                    y: 0,
                    z: 1
                };
                pitchNormal = {
                    x: 1,
                    y: 0,
                    z: 0
                };

                yawCorner = {
                    x: right - rotateHandleOffset,
                    y: bottom - rotateHandleOffset,
                    z: far + rotateHandleOffset
                };

                rollCorner = {
                    x: right - rotateHandleOffset,
                    y: top + rotateHandleOffset,
                    z: near - rotateHandleOffset
                };

                pitchCorner = {
                    x: left + rotateHandleOffset,
                    y: top + rotateHandleOffset,
                    z: far + rotateHandleOffset
                };

                yawCenter = {
                    x: boundsCenter.x,
                    y: bottom,
                    z: boundsCenter.z
                };
                rollCenter = {
                    x: boundsCenter.x,
                    y: boundsCenter.y,
                    z: near
                };
                pitchCenter = {
                    x: left,
                    y: boundsCenter.y,
                    z: boundsCenter.z
                };

                Overlays.editOverlay(pitchHandle, {
                    url: ROTATE_ARROW_WEST_NORTH_URL
                });
                Overlays.editOverlay(rollHandle, {
                    url: ROTATE_ARROW_WEST_NORTH_URL
                });

            }
        }

        var rotateHandlesVisible = true;
        var rotationOverlaysVisible = false;
        var translateHandlesVisible = true;
        var stretchHandlesVisible = true;
        var selectionBoxVisible = true;
        var isPointLight = false;

        if (selectionManager.selections.length == 1) {
            var properties = Entities.getEntityProperties(selectionManager.selections[0]);
            isPointLight = properties.type == "Light" && !properties.isSpotlight;
        }

        if (mode == "ROTATE_YAW" || mode == "ROTATE_PITCH" || mode == "ROTATE_ROLL" || mode == "TRANSLATE_X in case they Z") {
            rotationOverlaysVisible = true;
            rotateHandlesVisible = false;
            translateHandlesVisible = false;
            stretchHandlesVisible = false;
            selectionBoxVisible = false;
        } else if (mode == "TRANSLATE_UP_DOWN" || isPointLight) {
            rotateHandlesVisible = false;
            stretchHandlesVisible = false;
        } else if (mode != "UNKNOWN") {
            // every other mode is a stretch mode...
            rotateHandlesVisible = false;
            translateHandlesVisible = false;
        }

        var rotation = selectionManager.worldRotation;
        var dimensions = selectionManager.worldDimensions;
        var position = selectionManager.worldPosition;


        Overlays.editOverlay(rotateOverlayTarget, {
            visible: rotationOverlaysVisible
        });
        Overlays.editOverlay(rotateZeroOverlay, {
            visible: rotationOverlaysVisible
        });
        Overlays.editOverlay(rotateCurrentOverlay, {
            visible: rotationOverlaysVisible
        });

        // TODO: we have not implemented the rotating handle/controls yet... so for now, these handles are hidden
        Overlays.editOverlay(yawHandle, {
            visible: rotateHandlesVisible,
            position: yawCorner,
            rotation: yawHandleRotation
        });
        Overlays.editOverlay(pitchHandle, {
            visible: rotateHandlesVisible,
            position: pitchCorner,
            rotation: pitchHandleRotation
        });
        Overlays.editOverlay(rollHandle, {
            visible: rotateHandlesVisible,
            position: rollCorner,
            rotation: rollHandleRotation
        });
    };

    that.setSpaceMode = function(newSpaceMode) {
        if (spaceMode != newSpaceMode) {
            spaceMode = newSpaceMode;
            that.updateHandles();
        }
    };

    that.toggleSpaceMode = function() {
        if (spaceMode == SPACE_WORLD && SelectionManager.selections.length > 1) {
            print("Local space editing is not available with multiple selections");
            return;
        }
        spaceMode = spaceMode == SPACE_LOCAL ? SPACE_WORLD : SPACE_LOCAL;
        that.updateHandles();
    };

    that.unselectAll = function() {};

    that.updateHandles = function() {
        if (SelectionManager.selections.length == 0) {
            that.setOverlaysVisible(false);
            return;
        }

        that.updateRotationHandles();

        var rotation, dimensions, position, registrationPoint;

        if (spaceMode == SPACE_LOCAL) {
            rotation = SelectionManager.localRotation;
            dimensions = SelectionManager.localDimensions;
            position = SelectionManager.localPosition;
            registrationPoint = SelectionManager.localRegistrationPoint;
        } else {
            rotation = Quat.fromPitchYawRollDegrees(0, 0, 0);
            dimensions = SelectionManager.worldDimensions;
            position = SelectionManager.worldPosition;
            registrationPoint = SelectionManager.worldRegistrationPoint;
        }

        var registrationPointDimensions = {
            x: dimensions.x * registrationPoint.x,
            y: dimensions.y * registrationPoint.y,
            z: dimensions.z * registrationPoint.z,
        };

        // Center of entity, relative to registration point
        center = getRelativeCenterPosition(dimensions, registrationPoint);

        // Distances in world coordinates relative to the registration point
        var left = -registrationPointDimensions.x;
        var right = dimensions.x - registrationPointDimensions.x;
        var bottom = -registrationPointDimensions.y;
        var top = dimensions.y - registrationPointDimensions.y;
        var near = -registrationPointDimensions.z;
        var front = far = dimensions.z - registrationPointDimensions.z;

        var worldTop = SelectionManager.worldDimensions.y / 2;

        var LBN = {
            x: left,
            y: bottom,
            z: near
        };
        var RBN = {
            x: right,
            y: bottom,
            z: near
        };
        var LBF = {
            x: left,
            y: bottom,
            z: far
        };
        var RBF = {
            x: right,
            y: bottom,
            z: far
        };
        var LTN = {
            x: left,
            y: top,
            z: near
        };
        var RTN = {
            x: right,
            y: top,
            z: near
        };
        var LTF = {
            x: left,
            y: top,
            z: far
        };
        var RTF = {
            x: right,
            y: top,
            z: far
        };

        var TOP = {
            x: center.x,
            y: top,
            z: center.z
        };
        var BOTTOM = {
            x: center.x,
            y: bottom,
            z: center.z
        };
        var LEFT = {
            x: left,
            y: center.y,
            z: center.z
        };
        var RIGHT = {
            x: right,
            y: center.y,
            z: center.z
        };
        var NEAR = {
            x: center.x,
            y: center.y,
            z: near
        };
        var FAR = {
            x: center.x,
            y: center.y,
            z: far
        };

        var EdgeTR = {
            x: right,
            y: top,
            z: center.z
        };
        var EdgeTL = {
            x: left,
            y: top,
            z: center.z
        };
        var EdgeTF = {
            x: center.x,
            y: top,
            z: front
        };
        var EdgeTN = {
            x: center.x,
            y: top,
            z: near
        };
        var EdgeBR = {
            x: right,
            y: bottom,
            z: center.z
        };
        var EdgeBL = {
            x: left,
            y: bottom,
            z: center.z
        };
        var EdgeBF = {
            x: center.x,
            y: bottom,
            z: front
        };
        var EdgeBN = {
            x: center.x,
            y: bottom,
            z: near
        };
        var EdgeNR = {
            x: right,
            y: center.y,
            z: near
        };
        var EdgeNL = {
            x: left,
            y: center.y,
            z: near
        };
        var EdgeFR = {
            x: right,
            y: center.y,
            z: front
        };
        var EdgeFL = {
            x: left,
            y: center.y,
            z: front
        };

        LBN = Vec3.multiplyQbyV(rotation, LBN);
        RBN = Vec3.multiplyQbyV(rotation, RBN);
        LBF = Vec3.multiplyQbyV(rotation, LBF);
        RBF = Vec3.multiplyQbyV(rotation, RBF);
        LTN = Vec3.multiplyQbyV(rotation, LTN);
        RTN = Vec3.multiplyQbyV(rotation, RTN);
        LTF = Vec3.multiplyQbyV(rotation, LTF);
        RTF = Vec3.multiplyQbyV(rotation, RTF);

        TOP = Vec3.multiplyQbyV(rotation, TOP);
        BOTTOM = Vec3.multiplyQbyV(rotation, BOTTOM);
        LEFT = Vec3.multiplyQbyV(rotation, LEFT);
        RIGHT = Vec3.multiplyQbyV(rotation, RIGHT);
        NEAR = Vec3.multiplyQbyV(rotation, NEAR);
        FAR = Vec3.multiplyQbyV(rotation, FAR);

        EdgeTR = Vec3.multiplyQbyV(rotation, EdgeTR);
        EdgeTL = Vec3.multiplyQbyV(rotation, EdgeTL);
        EdgeTF = Vec3.multiplyQbyV(rotation, EdgeTF);
        EdgeTN = Vec3.multiplyQbyV(rotation, EdgeTN);
        EdgeBR = Vec3.multiplyQbyV(rotation, EdgeBR);
        EdgeBL = Vec3.multiplyQbyV(rotation, EdgeBL);
        EdgeBF = Vec3.multiplyQbyV(rotation, EdgeBF);
        EdgeBN = Vec3.multiplyQbyV(rotation, EdgeBN);
        EdgeNR = Vec3.multiplyQbyV(rotation, EdgeNR);
        EdgeNL = Vec3.multiplyQbyV(rotation, EdgeNL);
        EdgeFR = Vec3.multiplyQbyV(rotation, EdgeFR);
        EdgeFL = Vec3.multiplyQbyV(rotation, EdgeFL);

        LBN = Vec3.sum(position, LBN);
        RBN = Vec3.sum(position, RBN);
        LBF = Vec3.sum(position, LBF);
        RBF = Vec3.sum(position, RBF);
        LTN = Vec3.sum(position, LTN);
        RTN = Vec3.sum(position, RTN);
        LTF = Vec3.sum(position, LTF);
        RTF = Vec3.sum(position, RTF);

        TOP = Vec3.sum(position, TOP);
        BOTTOM = Vec3.sum(position, BOTTOM);
        LEFT = Vec3.sum(position, LEFT);
        RIGHT = Vec3.sum(position, RIGHT);
        NEAR = Vec3.sum(position, NEAR);
        FAR = Vec3.sum(position, FAR);

        EdgeTR = Vec3.sum(position, EdgeTR);
        EdgeTL = Vec3.sum(position, EdgeTL);
        EdgeTF = Vec3.sum(position, EdgeTF);
        EdgeTN = Vec3.sum(position, EdgeTN);
        EdgeBR = Vec3.sum(position, EdgeBR);
        EdgeBL = Vec3.sum(position, EdgeBL);
        EdgeBF = Vec3.sum(position, EdgeBF);
        EdgeBN = Vec3.sum(position, EdgeBN);
        EdgeNR = Vec3.sum(position, EdgeNR);
        EdgeNL = Vec3.sum(position, EdgeNL);
        EdgeFR = Vec3.sum(position, EdgeFR);
        EdgeFL = Vec3.sum(position, EdgeFL);

        var stretchHandlesVisible = spaceMode == SPACE_LOCAL;
        var extendedStretchHandlesVisible = stretchHandlesVisible && showExtendedStretchHandles;

        if (selectionManager.selections.length == 1) {
            var properties = Entities.getEntityProperties(selectionManager.selections[0]);
            if (properties.type == "Light" && properties.isSpotlight == true) {
                var stretchHandlesVisible = false;
                var extendedStretchHandlesVisible = false;

                Overlays.editOverlay(grabberSpotLightCenter, {
                    position: position,
                    visible: false,
                });
                Overlays.editOverlay(grabberSpotLightRadius, {
                    position: NEAR,
                    rotation: rotation,
                    visible: true,
                });
                var distance = (properties.dimensions.z / 2) * Math.sin(properties.cutoff * (Math.PI / 180));

                Overlays.editOverlay(grabberSpotLightL, {
                    position: EdgeNL,
                    rotation: rotation,
                    visible: true,
                });
                Overlays.editOverlay(grabberSpotLightR, {
                    position: EdgeNR,
                    rotation: rotation,
                    visible: true,
                });
                Overlays.editOverlay(grabberSpotLightT, {
                    position: EdgeTN,
                    rotation: rotation,
                    visible: true,
                });
                Overlays.editOverlay(grabberSpotLightB, {
                    position: EdgeBN,
                    rotation: rotation,
                    visible: true,
                });
                Overlays.editOverlay(grabberSpotLightCircle, {
                    position: NEAR,
                    dimensions: {
                        x: distance,
                        y: distance,
                        z: 1
                    },
                    lineWidth: 1.5,
                    rotation: rotation,
                    visible: true,
                });

                Overlays.editOverlay(grabberSpotLightLineT, {
                    start: position,
                    end: EdgeTN,
                    visible: true,
                });
                Overlays.editOverlay(grabberSpotLightLineB, {
                    start: position,
                    end: EdgeBN,
                    visible: true,
                });
                Overlays.editOverlay(grabberSpotLightLineR, {
                    start: position,
                    end: EdgeNR,
                    visible: true,
                });
                Overlays.editOverlay(grabberSpotLightLineL, {
                    start: position,
                    end: EdgeNL,
                    visible: true,
                });

                Overlays.editOverlay(grabberPointLightCircleX, {
                    visible: false
                });
                Overlays.editOverlay(grabberPointLightCircleY, {
                    visible: false
                });
                Overlays.editOverlay(grabberPointLightCircleZ, {
                    visible: false
                });
                Overlays.editOverlay(grabberPointLightT, {
                    visible: false
                });
                Overlays.editOverlay(grabberPointLightB, {
                    visible: false
                });
                Overlays.editOverlay(grabberPointLightL, {
                    visible: false
                });
                Overlays.editOverlay(grabberPointLightR, {
                    visible: false
                });
                Overlays.editOverlay(grabberPointLightF, {
                    visible: false
                });
                Overlays.editOverlay(grabberPointLightN, {
                    visible: false
                });
            } else if (properties.type == "Light" && properties.isSpotlight == false) {
                var stretchHandlesVisible = false;
                var extendedStretchHandlesVisible = false;
                Overlays.editOverlay(grabberPointLightT, {
                    position: TOP,
                    rotation: rotation,
                    visible: true,
                });
                Overlays.editOverlay(grabberPointLightB, {
                    position: BOTTOM,
                    rotation: rotation,
                    visible: true,
                });
                Overlays.editOverlay(grabberPointLightL, {
                    position: LEFT,
                    rotation: rotation,
                    visible: true,
                });
                Overlays.editOverlay(grabberPointLightR, {
                    position: RIGHT,
                    rotation: rotation,
                    visible: true,
                });
                Overlays.editOverlay(grabberPointLightF, {
                    position: FAR,
                    rotation: rotation,
                    visible: true,
                });
                Overlays.editOverlay(grabberPointLightN, {
                    position: NEAR,
                    rotation: rotation,
                    visible: true,
                });
                Overlays.editOverlay(grabberPointLightCircleX, {
                    position: position,
                    rotation: Quat.multiply(rotation, Quat.fromPitchYawRollDegrees(0, 90, 0)),
                    dimensions: {
                        x: properties.dimensions.z / 2.0,
                        y: properties.dimensions.z / 2.0,
                        z: 1
                    },
                    visible: true,
                });
                Overlays.editOverlay(grabberPointLightCircleY, {
                    position: position,
                    rotation: Quat.multiply(rotation, Quat.fromPitchYawRollDegrees(90, 0, 0)),
                    dimensions: {
                        x: properties.dimensions.z / 2.0,
                        y: properties.dimensions.z / 2.0,
                        z: 1
                    },
                    visible: true,
                });
                Overlays.editOverlay(grabberPointLightCircleZ, {
                    position: position,
                    rotation: rotation,
                    dimensions: {
                        x: properties.dimensions.z / 2.0,
                        y: properties.dimensions.z / 2.0,
                        z: 1
                    },
                    visible: true,
                });

                Overlays.editOverlay(grabberSpotLightRadius, {
                    visible: false
                });
                Overlays.editOverlay(grabberSpotLightL, {
                    visible: false
                });
                Overlays.editOverlay(grabberSpotLightR, {
                    visible: false
                });
                Overlays.editOverlay(grabberSpotLightT, {
                    visible: false
                });
                Overlays.editOverlay(grabberSpotLightB, {
                    visible: false
                });
                Overlays.editOverlay(grabberSpotLightCircle, {
                    visible: false
                });
                Overlays.editOverlay(grabberSpotLightLineL, {
                    visible: false
                });
                Overlays.editOverlay(grabberSpotLightLineR, {
                    visible: false
                });
                Overlays.editOverlay(grabberSpotLightLineT, {
                    visible: false
                });
                Overlays.editOverlay(grabberSpotLightLineB, {
                    visible: false
                });
            } else {
                Overlays.editOverlay(grabberSpotLightCenter, {
                    visible: false
                });
                Overlays.editOverlay(grabberSpotLightRadius, {
                    visible: false
                });
                Overlays.editOverlay(grabberSpotLightL, {
                    visible: false
                });
                Overlays.editOverlay(grabberSpotLightR, {
                    visible: false
                });
                Overlays.editOverlay(grabberSpotLightT, {
                    visible: false
                });
                Overlays.editOverlay(grabberSpotLightB, {
                    visible: false
                });
                Overlays.editOverlay(grabberSpotLightCircle, {
                    visible: false
                });
                Overlays.editOverlay(grabberSpotLightLineL, {
                    visible: false
                });
                Overlays.editOverlay(grabberSpotLightLineR, {
                    visible: false
                });
                Overlays.editOverlay(grabberSpotLightLineT, {
                    visible: false
                });
                Overlays.editOverlay(grabberSpotLightLineB, {
                    visible: false
                });

                Overlays.editOverlay(grabberPointLightCircleX, {
                    visible: false
                });
                Overlays.editOverlay(grabberPointLightCircleY, {
                    visible: false
                });
                Overlays.editOverlay(grabberPointLightCircleZ, {
                    visible: false
                });
                Overlays.editOverlay(grabberPointLightT, {
                    visible: false
                });
                Overlays.editOverlay(grabberPointLightB, {
                    visible: false
                });
                Overlays.editOverlay(grabberPointLightL, {
                    visible: false
                });
                Overlays.editOverlay(grabberPointLightR, {
                    visible: false
                });
                Overlays.editOverlay(grabberPointLightF, {
                    visible: false
                });
                Overlays.editOverlay(grabberPointLightN, {
                    visible: false
                });
            }
        }



        Overlays.editOverlay(grabberLBN, {
            visible: stretchHandlesVisible,
            rotation: rotation,
            position: LBN
        });
        Overlays.editOverlay(grabberRBN, {
            visible: stretchHandlesVisible,
            rotation: rotation,
            position: RBN
        });
        Overlays.editOverlay(grabberLBF, {
            visible: stretchHandlesVisible,
            rotation: rotation,
            position: LBF
        });
        Overlays.editOverlay(grabberRBF, {
            visible: stretchHandlesVisible,
            rotation: rotation,
            position: RBF
        });

        Overlays.editOverlay(grabberLTN, {
            visible: extendedStretchHandlesVisible,
            rotation: rotation,
            position: LTN
        });
        Overlays.editOverlay(grabberRTN, {
            visible: extendedStretchHandlesVisible,
            rotation: rotation,
            position: RTN
        });
        Overlays.editOverlay(grabberLTF, {
            visible: extendedStretchHandlesVisible,
            rotation: rotation,
            position: LTF
        });
        Overlays.editOverlay(grabberRTF, {
            visible: extendedStretchHandlesVisible,
            rotation: rotation,
            position: RTF
        });

        Overlays.editOverlay(grabberTOP, {
            visible: stretchHandlesVisible,
            rotation: rotation,
            position: TOP
        });
        Overlays.editOverlay(grabberBOTTOM, {
            visible: stretchHandlesVisible,
            rotation: rotation,
            position: BOTTOM
        });
        Overlays.editOverlay(grabberLEFT, {
            visible: extendedStretchHandlesVisible,
            rotation: rotation,
            position: LEFT
        });
        Overlays.editOverlay(grabberRIGHT, {
            visible: extendedStretchHandlesVisible,
            rotation: rotation,
            position: RIGHT
        });
        Overlays.editOverlay(grabberNEAR, {
            visible: extendedStretchHandlesVisible,
            rotation: rotation,
            position: NEAR
        });
        Overlays.editOverlay(grabberFAR, {
            visible: extendedStretchHandlesVisible,
            rotation: rotation,
            position: FAR
        });

        var boxPosition = Vec3.multiplyQbyV(rotation, center);
        boxPosition = Vec3.sum(position, boxPosition);
        Overlays.editOverlay(selectionBox, {
            position: boxPosition,
            dimensions: dimensions,
            rotation: rotation,
            visible: !(mode == "ROTATE_YAW" || mode == "ROTATE_PITCH" || mode == "ROTATE_ROLL"),
        });

        // Create more selection box overlays if we don't have enough
        var overlaysNeeded = selectionManager.selections.length - selectionBoxes.length;
        for (var i = 0; i < overlaysNeeded; i++) {
            selectionBoxes.push(
                Overlays.addOverlay("cube", {
                    position: {
                        x: 0,
                        y: 0,
                        z: 0
                    },
                    size: 1,
                    color: {
                        red: 255,
                        green: 153,
                        blue: 0
                    },
                    alpha: 1,
                    solid: false,
                    visible: false,
                    dashed: false,
                    lineWidth: 1.0,
                    ignoreRayIntersection: true,
                }));
        }

        var i = 0;
        // Only show individual selections boxes if there is more than 1 selection
        if (selectionManager.selections.length > 1) {
            for (; i < selectionManager.selections.length; i++) {
                var properties = Entities.getEntityProperties(selectionManager.selections[i]);

                // Adjust overlay position to take registrationPoint into account
                // centeredRP = registrationPoint with range [-0.5, 0.5]
                var centeredRP = Vec3.subtract(properties.registrationPoint, {
                    x: 0.5,
                    y: 0.5,
                    z: 0.5
                });
                var offset = vec3Mult(properties.dimensions, centeredRP);
                offset = Vec3.multiply(-1, offset);
                offset = Vec3.multiplyQbyV(properties.rotation, offset);
                var boxPosition = Vec3.sum(properties.position, offset);

                var color = {red: 255, green: 128, blue: 0};
                if (i >= selectionManager.selections.length - 1) color = {red: 255, green: 255, blue: 64};

                Overlays.editOverlay(selectionBoxes[i], {
                    position: boxPosition,
                    color: color,
                    rotation: properties.rotation,
                    dimensions: properties.dimensions,
                    visible: true,
                });
            }
        }
        // Hide any remaining selection boxes
        for (; i < selectionBoxes.length; i++) {
            Overlays.editOverlay(selectionBoxes[i], {
                visible: false
            });
        }

        Overlays.editOverlay(grabberEdgeTR, {
            visible: extendedStretchHandlesVisible,
            rotation: rotation,
            position: EdgeTR
        });
        Overlays.editOverlay(grabberEdgeTL, {
            visible: extendedStretchHandlesVisible,
            rotation: rotation,
            position: EdgeTL
        });
        Overlays.editOverlay(grabberEdgeTF, {
            visible: extendedStretchHandlesVisible,
            rotation: rotation,
            position: EdgeTF
        });
        Overlays.editOverlay(grabberEdgeTN, {
            visible: extendedStretchHandlesVisible,
            rotation: rotation,
            position: EdgeTN
        });
        Overlays.editOverlay(grabberEdgeBR, {
            visible: stretchHandlesVisible,
            rotation: rotation,
            position: EdgeBR
        });
        Overlays.editOverlay(grabberEdgeBL, {
            visible: stretchHandlesVisible,
            rotation: rotation,
            position: EdgeBL
        });
        Overlays.editOverlay(grabberEdgeBF, {
            visible: stretchHandlesVisible,
            rotation: rotation,
            position: EdgeBF
        });
        Overlays.editOverlay(grabberEdgeBN, {
            visible: stretchHandlesVisible,
            rotation: rotation,
            position: EdgeBN
        });
        Overlays.editOverlay(grabberEdgeNR, {
            visible: extendedStretchHandlesVisible,
            rotation: rotation,
            position: EdgeNR
        });
        Overlays.editOverlay(grabberEdgeNL, {
            visible: extendedStretchHandlesVisible,
            rotation: rotation,
            position: EdgeNL
        });
        Overlays.editOverlay(grabberEdgeFR, {
            visible: extendedStretchHandlesVisible,
            rotation: rotation,
            position: EdgeFR
        });
        Overlays.editOverlay(grabberEdgeFL, {
            visible: extendedStretchHandlesVisible,
            rotation: rotation,
            position: EdgeFL
        });

        var grabberMoveUpOffset = 0.1;
        grabberMoveUpPosition = {
            x: position.x,
            y: position.y + worldTop + grabberMoveUpOffset,
            z: position.z
        }
        Overlays.editOverlay(grabberMoveUp, {
            visible: activeTool == null || mode == "TRANSLATE_UP_DOWN"
        });

        Overlays.editOverlay(baseOfEntityProjectionOverlay, {
            visible: mode != "ROTATE_YAW" && mode != "ROTATE_PITCH" && mode != "ROTATE_ROLL",
            solid: true,
            position: {
                x: selectionManager.worldPosition.x,
                y: grid.getOrigin().y,
                z: selectionManager.worldPosition.z
            },
            dimensions: {
                x: selectionManager.worldDimensions.x,
                y: selectionManager.worldDimensions.z
            },
            rotation: Quat.fromPitchYawRollDegrees(90, 0, 0),
        });


    };

    that.setOverlaysVisible = function(isVisible) {
        var length = allOverlays.length;
        for (var i = 0; i < length; i++) {
            Overlays.editOverlay(allOverlays[i], {
                visible: isVisible
            });
        }
        length = selectionBoxes.length;
        for (var i = 0; i < length; i++) {
            Overlays.editOverlay(selectionBoxes[i], {
                visible: isVisible
            });
        }
    };

    that.unselect = function(entityID) {};

    var initialXZPick = null;
    var isConstrained = false;
    var constrainMajorOnly = false;
    var startPosition = null;
    var duplicatedEntityIDs = null;

    var translateXZTool = {
        mode: 'TRANSLATE_XZ',
        pickPlanePosition: { x: 0, y: 0, z: 0 },
        greatestDimension: 0.0,
        startingDistance: 0.0,
        startingElevation: 0.0,
        onBegin: function(event) {
            SelectionManager.saveProperties();
            startPosition = SelectionManager.worldPosition;
            var dimensions = SelectionManager.worldDimensions;

            var pickRay = generalComputePickRay(event.x, event.y);
            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) {
                duplicatedEntityIDs = [];
                for (var otherEntityID in SelectionManager.savedProperties) {
                    var properties = SelectionManager.savedProperties[otherEntityID];
                    if (!properties.locked) {
                        var entityID = Entities.addEntity(properties);
                        duplicatedEntityIDs.push({
                            entityID: entityID,
                            properties: properties,
                        });
                    }
                }
            } else {
                duplicatedEntityIDs = null;
            }

            isConstrained = false;
        },
        onEnd: function(event, reason) {
            pushCommandForSelections(duplicatedEntityIDs);

            Overlays.editOverlay(xRailOverlay, {
                visible: false
            });
            Overlays.editOverlay(zRailOverlay, {
                visible: false
            });
        },
        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("Pick ray does not intersect XZ plane.");
                }
                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("too close to horizon!");
                }
                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) {
                    Overlays.editOverlay(xRailOverlay, {
                        visible: true
                    });
                    var xStart = Vec3.sum(startPosition, {
                        x: -10000,
                        y: 0,
                        z: 0
                    });
                    var xEnd = Vec3.sum(startPosition, {
                        x: 10000,
                        y: 0,
                        z: 0
                    });
                    var zStart = Vec3.sum(startPosition, {
                        x: 0,
                        y: 0,
                        z: -10000
                    });
                    var zEnd = Vec3.sum(startPosition, {
                        x: 0,
                        y: 0,
                        z: 10000
                    });
                    Overlays.editOverlay(xRailOverlay, {
                        start: xStart,
                        end: xEnd,
                        visible: true
                    });
                    Overlays.editOverlay(zRailOverlay, {
                        start: zStart,
                        end: zEnd,
                        visible: true
                    });
                    isConstrained = true;
                }
            } else {
                if (isConstrained) {
                    Overlays.editOverlay(xRailOverlay, {
                        visible: false
                    });
                    Overlays.editOverlay(zRailOverlay, {
                        visible: false
                    });
                    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);



            for (var i = 0; i < SelectionManager.selections.length; i++) {
                var properties = SelectionManager.savedProperties[SelectionManager.selections[i]];
                if (!properties) {
                    continue;
                }
                var newPosition = Vec3.sum(properties.position, {
                    x: vector.x,
                    y: 0,
                    z: vector.z
                });
                Entities.editEntity(SelectionManager.selections[i], {
                    position: newPosition,
                });

                if (wantDebug) {
                    print("translateXZ... ");
                    Vec3.print("                 vector:", vector);
                    Vec3.print("            newPosition:", properties.position);
                    Vec3.print("            newPosition:", newPosition);
                }
            }

            SelectionManager._update();
        }
    };

    var lastXYPick = null
    var upDownPickNormal = null;
    addGrabberTool(grabberMoveUp, {
        mode: "TRANSLATE_UP_DOWN",
        onBegin: function(event) {
            pickRay = generalComputePickRay(event.x, event.y);

            upDownPickNormal = Quat.getFront(lastCameraOrientation);
            // Remove y component so the y-axis lies along the plane we picking on - this will
            // give movements that follow the mouse.
            upDownPickNormal.y = 0;
            lastXYPick = rayPlaneIntersection(pickRay, SelectionManager.worldPosition, upDownPickNormal);

            SelectionManager.saveProperties();

            // 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 = [];
                for (var otherEntityID in SelectionManager.savedProperties) {
                    var properties = SelectionManager.savedProperties[otherEntityID];
                    if (!properties.locked) {
                        var entityID = Entities.addEntity(properties);
                        duplicatedEntityIDs.push({
                            entityID: entityID,
                            properties: properties,
                        });
                    }
                }
            } else {
                duplicatedEntityIDs = null;
            }
        },
        onEnd: function(event, reason) {
            pushCommandForSelections(duplicatedEntityIDs);
        },
        onMove: function(event) {
            pickRay = generalComputePickRay(event.x, event.y);

            // translate mode left/right based on view toward entity
            var newIntersection = rayPlaneIntersection(pickRay, SelectionManager.worldPosition, upDownPickNormal);

            var vector = Vec3.subtract(newIntersection, lastXYPick);
            vector = grid.snapToGrid(vector);

            // we only care about the Y axis
            vector.x = 0;
            vector.z = 0;

            var wantDebug = false;
            if (wantDebug) {
                print("translateUpDown... ");
                print("                event.y:" + event.y);
                Vec3.print("        newIntersection:", newIntersection);
                Vec3.print("                 vector:", vector);
                Vec3.print("            newPosition:", newPosition);
            }
            for (var i = 0; i < SelectionManager.selections.length; i++) {
                var id = SelectionManager.selections[i];
                var properties = selectionManager.savedProperties[id];

                var original = properties.position;
                var newPosition = Vec3.sum(properties.position, vector);

                Entities.editEntity(id, {
                    position: newPosition,
                });
            }

            SelectionManager._update();
        },
    });

    var vec3Mult = function(v1, v2) {
            return {
                x: v1.x * v2.x,
                y: v1.y * v2.y,
                z: v1.z * v2.z
            };
        }
        // stretchMode - name of mode
        // direction - direction to stretch in
        // pivot - point to use as a pivot
        // offset - the position of the overlay tool relative to the selections center position
    var makeStretchTool = function(stretchMode, direction, pivot, offset, customOnMove) {
        var signs = {
            x: direction.x < 0 ? -1 : (direction.x > 0 ? 1 : 0),
            y: direction.y < 0 ? -1 : (direction.y > 0 ? 1 : 0),
            z: direction.z < 0 ? -1 : (direction.z > 0 ? 1 : 0),
        };

        var mask = {
            x: Math.abs(direction.x) > 0 ? 1 : 0,
            y: Math.abs(direction.y) > 0 ? 1 : 0,
            z: Math.abs(direction.z) > 0 ? 1 : 0,
        };

        var numDimensions = mask.x + mask.y + mask.z;

        var planeNormal = null;
        var lastPick = null;
        var initialPosition = null;
        var initialDimensions = null;
        var initialIntersection = null;
        var initialProperties = null;
        var registrationPoint = null;
        var deltaPivot = null;
        var pickRayPosition = null;
        var rotation = null;

        var onBegin = function(event) {
            var properties = Entities.getEntityProperties(SelectionManager.selections[0]);
            initialProperties = properties;
            rotation = spaceMode == SPACE_LOCAL ? properties.rotation : Quat.fromPitchYawRollDegrees(0, 0, 0);

            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 (numDimensions == 1 && mask.x) {
                var start = Vec3.multiplyQbyV(rotation, {
                    x: -10000,
                    y: 0,
                    z: 0
                });
                start = Vec3.sum(start, properties.position);
                var end = Vec3.multiplyQbyV(rotation, {
                    x: 10000,
                    y: 0,
                    z: 0
                });
                end = Vec3.sum(end, properties.position);
                Overlays.editOverlay(xRailOverlay, {
                    start: start,
                    end: end,
                    visible: true,
                });
            }
            if (numDimensions == 1 && mask.y) {
                var start = Vec3.multiplyQbyV(rotation, {
                    x: 0,
                    y: -10000,
                    z: 0
                });
                start = Vec3.sum(start, properties.position);
                var end = Vec3.multiplyQbyV(rotation, {
                    x: 0,
                    y: 10000,
                    z: 0
                });
                end = Vec3.sum(end, properties.position);
                Overlays.editOverlay(yRailOverlay, {
                    start: start,
                    end: end,
                    visible: true,
                });
            }
            if (numDimensions == 1 && mask.z) {
                var start = Vec3.multiplyQbyV(rotation, {
                    x: 0,
                    y: 0,
                    z: -10000
                });
                start = Vec3.sum(start, properties.position);
                var end = Vec3.multiplyQbyV(rotation, {
                    x: 0,
                    y: 0,
                    z: 10000
                });
                end = Vec3.sum(end, properties.position);
                Overlays.editOverlay(zRailOverlay, {
                    start: start,
                    end: end,
                    visible: true,
                });
            }
            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: z
                    };
                }
            }
            planeNormal = Vec3.multiplyQbyV(rotation, planeNormal);
            var pickRay = generalComputePickRay(event.x, event.y);
            lastPick = rayPlaneIntersection(pickRay,
                pickRayPosition,
                planeNormal);

            SelectionManager.saveProperties();
        };

        var onEnd = function(event, reason) {
            Overlays.editOverlay(xRailOverlay, {
                visible: false
            });
            Overlays.editOverlay(yRailOverlay, {
                visible: false
            });
            Overlays.editOverlay(zRailOverlay, {
                visible: false
            });

            pushCommandForSelections();
        };

        var onMove = function(event) {
            var proportional = spaceMode == SPACE_WORLD || event.isShifted || activeTool.mode == "STRETCH_RADIUS";

            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 pickRay = generalComputePickRay(event.x, event.y);
            newPick = rayPlaneIntersection(pickRay,
                pickRayPosition,
                planeNormal);
            var vector = Vec3.subtract(newPick, lastPick);

            vector = Vec3.multiplyQbyV(Quat.inverse(rotation), vector);

            vector = vec3Mult(mask, vector);

            if (customOnMove) {
                var change = Vec3.multiply(-1, vec3Mult(signs, vector));
                customOnMove(vector, change);
            } else {
                vector = grid.snapToSpacing(vector);

                var changeInDimensions = Vec3.multiply(-1, vec3Mult(signs, vector));
                var newDimensions;
                if (proportional) {
                    var absX = Math.abs(changeInDimensions.x);
                    var absY = Math.abs(changeInDimensions.y);
                    var absZ = Math.abs(changeInDimensions.z);
                    var pctChange = 0;
                    if (absX > absY && absX > absZ) {
                        pctChange = changeInDimensions.x / initialProperties.dimensions.x;
                        pctChange = changeInDimensions.x / initialDimensions.x;
                    } else if (absY > absZ) {
                        pctChange = changeInDimensions.y / initialProperties.dimensions.y;
                        pctChange = changeInDimensions.y / initialDimensions.y;
                    } else {
                        pctChange = changeInDimensions.z / initialProperties.dimensions.z;
                        pctChange = changeInDimensions.z / initialDimensions.z;
                    }
                    pctChange += 1.0;
                    newDimensions = Vec3.multiply(pctChange, initialDimensions);
                } else {
                    newDimensions = Vec3.sum(initialDimensions, changeInDimensions);
                }

                newDimensions.x = Math.max(newDimensions.x, MINIMUM_DIMENSION);
                newDimensions.y = Math.max(newDimensions.y, MINIMUM_DIMENSION);
                newDimensions.z = Math.max(newDimensions.z, MINIMUM_DIMENSION);

                var changeInPosition = Vec3.multiplyQbyV(rotation, vec3Mult(deltaPivot, changeInDimensions));
                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("        newIntersection:", newIntersection);
                    Vec3.print("                 vector:", vector);
                    //Vec3.print("           oldPOS:", oldPOS);
                    //Vec3.print("                newPOS:", newPOS);
                    Vec3.print("            changeInDimensions:", changeInDimensions);
                    Vec3.print("                 newDimensions:", newDimensions);

                    Vec3.print("              changeInPosition:", changeInPosition);
                    Vec3.print("                   newPosition:", newPosition);
                }

                SelectionManager._update();
            }

        };

        return {
            mode: stretchMode,
            onBegin: onBegin,
            onMove: onMove,
            onEnd: onEnd
        };
    };

    function addStretchTool(overlay, mode, pivot, direction, offset, handleMove) {
        if (!pivot) {
            pivot = direction;
        }
        var tool = makeStretchTool(mode, direction, pivot, offset, handleMove);

        addGrabberTool(overlay, tool);
    }

    function cutoffStretchFunc(vector, change) {
        vector = change;
        Vec3.print("Radius stretch: ", vector);
        var length = vector.x + vector.y + vector.z;
        var props = selectionManager.savedProperties[selectionManager.selections[0]];

        var radius = props.dimensions.z / 2;
        var originalCutoff = props.cutoff;

        var originalSize = radius * Math.tan(originalCutoff * (Math.PI / 180));
        var newSize = originalSize + length;
        var cutoff = Math.atan2(newSize, radius) * 180 / Math.PI;

        Entities.editEntity(selectionManager.selections[0], {
            cutoff: cutoff,
        });

        SelectionManager._update();
    };

    function radiusStretchFunc(vector, change) {
        var props = selectionManager.savedProperties[selectionManager.selections[0]];

        // Find the axis being adjusted
        var size;
        if (Math.abs(change.x) > 0) {
            size = props.dimensions.x + change.x;
        } else if (Math.abs(change.y) > 0) {
            size = props.dimensions.y + change.y;
        } else if (Math.abs(change.z) > 0) {
            size = props.dimensions.z + change.z;
        }

        var newDimensions = {
            x: size,
            y: size,
            z: size
        };

        Entities.editEntity(selectionManager.selections[0], {
            dimensions: newDimensions,
        });

        SelectionManager._update();
    }

    addStretchTool(grabberNEAR, "STRETCH_NEAR", {
        x: 0,
        y: 0,
        z: 1
    }, {
        x: 0,
        y: 0,
        z: 1
    }, {
        x: 0,
        y: 0,
        z: -1
    });
    addStretchTool(grabberFAR, "STRETCH_FAR", {
        x: 0,
        y: 0,
        z: -1
    }, {
        x: 0,
        y: 0,
        z: -1
    }, {
        x: 0,
        y: 0,
        z: 1
    });
    addStretchTool(grabberTOP, "STRETCH_TOP", {
        x: 0,
        y: -1,
        z: 0
    }, {
        x: 0,
        y: -1,
        z: 0
    }, {
        x: 0,
        y: 1,
        z: 0
    });
    addStretchTool(grabberBOTTOM, "STRETCH_BOTTOM", {
        x: 0,
        y: 1,
        z: 0
    }, {
        x: 0,
        y: 1,
        z: 0
    }, {
        x: 0,
        y: -1,
        z: 0
    });
    addStretchTool(grabberRIGHT, "STRETCH_RIGHT", {
        x: -1,
        y: 0,
        z: 0
    }, {
        x: -1,
        y: 0,
        z: 0
    }, {
        x: 1,
        y: 0,
        z: 0
    });
    addStretchTool(grabberLEFT, "STRETCH_LEFT", {
        x: 1,
        y: 0,
        z: 0
    }, {
        x: 1,
        y: 0,
        z: 0
    }, {
        x: -1,
        y: 0,
        z: 0
    });

    addStretchTool(grabberSpotLightRadius, "STRETCH_RADIUS", {
        x: 0,
        y: 0,
        z: 0
    }, {
        x: 0,
        y: 0,
        z: 1
    }, {
        x: 0,
        y: 0,
        z: -1
    });
    addStretchTool(grabberSpotLightT, "STRETCH_CUTOFF_T", {
        x: 0,
        y: 0,
        z: 0
    }, {
        x: 0,
        y: -1,
        z: 0
    }, {
        x: 0,
        y: 1,
        z: 0
    }, cutoffStretchFunc);
    addStretchTool(grabberSpotLightB, "STRETCH_CUTOFF_B", {
        x: 0,
        y: 0,
        z: 0
    }, {
        x: 0,
        y: 1,
        z: 0
    }, {
        x: 0,
        y: -1,
        z: 0
    }, cutoffStretchFunc);
    addStretchTool(grabberSpotLightL, "STRETCH_CUTOFF_L", {
        x: 0,
        y: 0,
        z: 0
    }, {
        x: 1,
        y: 0,
        z: 0
    }, {
        x: -1,
        y: 0,
        z: 0
    }, cutoffStretchFunc);
    addStretchTool(grabberSpotLightR, "STRETCH_CUTOFF_R", {
        x: 0,
        y: 0,
        z: 0
    }, {
        x: -1,
        y: 0,
        z: 0
    }, {
        x: 1,
        y: 0,
        z: 0
    }, cutoffStretchFunc);

    addStretchTool(grabberPointLightT, "STRETCH_RADIUS_T", {
        x: 0,
        y: 0,
        z: 0
    }, {
        x: 0,
        y: -1,
        z: 0
    }, {
        x: 0,
        y: 0,
        z: 1
    }, radiusStretchFunc);
    addStretchTool(grabberPointLightB, "STRETCH_RADIUS_B", {
        x: 0,
        y: 0,
        z: 0
    }, {
        x: 0,
        y: 1,
        z: 0
    }, {
        x: 0,
        y: 0,
        z: 1
    }, radiusStretchFunc);
    addStretchTool(grabberPointLightL, "STRETCH_RADIUS_L", {
        x: 0,
        y: 0,
        z: 0
    }, {
        x: 1,
        y: 0,
        z: 0
    }, {
        x: 0,
        y: 0,
        z: 1
    }, radiusStretchFunc);
    addStretchTool(grabberPointLightR, "STRETCH_RADIUS_R", {
        x: 0,
        y: 0,
        z: 0
    }, {
        x: -1,
        y: 0,
        z: 0
    }, {
        x: 0,
        y: 0,
        z: 1
    }, radiusStretchFunc);
    addStretchTool(grabberPointLightF, "STRETCH_RADIUS_F", {
        x: 0,
        y: 0,
        z: 0
    }, {
        x: 0,
        y: 0,
        z: -1
    }, {
        x: 0,
        y: 0,
        z: 1
    }, radiusStretchFunc);
    addStretchTool(grabberPointLightN, "STRETCH_RADIUS_N", {
        x: 0,
        y: 0,
        z: 0
    }, {
        x: 0,
        y: 0,
        z: 1
    }, {
        x: 0,
        y: 0,
        z: -1
    }, radiusStretchFunc);

    addStretchTool(grabberLBN, "STRETCH_LBN", null, {
        x: 1,
        y: 0,
        z: 1
    }, {
        x: -1,
        y: -1,
        z: -1
    });
    addStretchTool(grabberRBN, "STRETCH_RBN", null, {
        x: -1,
        y: 0,
        z: 1
    }, {
        x: 1,
        y: -1,
        z: -1
    });
    addStretchTool(grabberLBF, "STRETCH_LBF", null, {
        x: 1,
        y: 0,
        z: -1
    }, {
        x: -1,
        y: -1,
        z: 1
    });
    addStretchTool(grabberRBF, "STRETCH_RBF", null, {
        x: -1,
        y: 0,
        z: -1
    }, {
        x: 1,
        y: -1,
        z: 1
    });
    addStretchTool(grabberLTN, "STRETCH_LTN", null, {
        x: 1,
        y: 0,
        z: 1
    }, {
        x: -1,
        y: 1,
        z: -1
    });
    addStretchTool(grabberRTN, "STRETCH_RTN", null, {
        x: -1,
        y: 0,
        z: 1
    }, {
        x: 1,
        y: 1,
        z: -1
    });
    addStretchTool(grabberLTF, "STRETCH_LTF", null, {
        x: 1,
        y: 0,
        z: -1
    }, {
        x: -1,
        y: 1,
        z: 1
    });
    addStretchTool(grabberRTF, "STRETCH_RTF", null, {
        x: -1,
        y: 0,
        z: -1
    }, {
        x: 1,
        y: 1,
        z: 1
    });

    addStretchTool(grabberEdgeTR, "STRETCH_EdgeTR", null, {
        x: 1,
        y: 1,
        z: 0
    }, {
        x: 1,
        y: 1,
        z: 0
    });
    addStretchTool(grabberEdgeTL, "STRETCH_EdgeTL", null, {
        x: -1,
        y: 1,
        z: 0
    }, {
        x: -1,
        y: 1,
        z: 0
    });
    addStretchTool(grabberEdgeTF, "STRETCH_EdgeTF", null, {
        x: 0,
        y: 1,
        z: -1
    }, {
        x: 0,
        y: 1,
        z: -1
    });
    addStretchTool(grabberEdgeTN, "STRETCH_EdgeTN", null, {
        x: 0,
        y: 1,
        z: 1
    }, {
        x: 0,
        y: 1,
        z: 1
    });
    addStretchTool(grabberEdgeBR, "STRETCH_EdgeBR", null, {
        x: -1,
        y: 0,
        z: 0
    }, {
        x: 1,
        y: -1,
        z: 0
    });
    addStretchTool(grabberEdgeBL, "STRETCH_EdgeBL", null, {
        x: 1,
        y: 0,
        z: 0
    }, {
        x: -1,
        y: -1,
        z: 0
    });
    addStretchTool(grabberEdgeBF, "STRETCH_EdgeBF", null, {
        x: 0,
        y: 0,
        z: -1
    }, {
        x: 0,
        y: -1,
        z: -1
    });
    addStretchTool(grabberEdgeBN, "STRETCH_EdgeBN", null, {
        x: 0,
        y: 0,
        z: 1
    }, {
        x: 0,
        y: -1,
        z: 1
    });
    addStretchTool(grabberEdgeNR, "STRETCH_EdgeNR", null, {
        x: -1,
        y: 0,
        z: 1
    }, {
        x: 1,
        y: 0,
        z: -1
    });
    addStretchTool(grabberEdgeNL, "STRETCH_EdgeNL", null, {
        x: 1,
        y: 0,
        z: 1
    }, {
        x: -1,
        y: 0,
        z: -1
    });
    addStretchTool(grabberEdgeFR, "STRETCH_EdgeFR", null, {
        x: -1,
        y: 0,
        z: -1
    }, {
        x: 1,
        y: 0,
        z: 1
    });
    addStretchTool(grabberEdgeFL, "STRETCH_EdgeFL", null, {
        x: 1,
        y: 0,
        z: -1
    }, {
        x: -1,
        y: 0,
        z: 1
    });

    function updateRotationDegreesOverlay(angleFromZero, handleRotation, centerPosition) {
        var angle = angleFromZero * (Math.PI / 180);
        var position = {
            x: Math.cos(angle) * outerRadius * ROTATION_DISPLAY_DISTANCE_MULTIPLIER,
            y: Math.sin(angle) * outerRadius * ROTATION_DISPLAY_DISTANCE_MULTIPLIER,
            z: 0,
        };
        position = Vec3.multiplyQbyV(handleRotation, position);
        position = Vec3.sum(centerPosition, position);
        Overlays.editOverlay(rotationDegreesDisplay, {
            position: position,
            dimensions: {
                x: innerRadius * ROTATION_DISPLAY_SIZE_X_MULTIPLIER,
                y: innerRadius * ROTATION_DISPLAY_SIZE_Y_MULTIPLIER
            },
            lineHeight: innerRadius * ROTATION_DISPLAY_LINE_HEIGHT_MULTIPLIER,
            text: normalizeDegrees(angleFromZero) + "°",
        });
    }

    var initialPosition = SelectionManager.worldPosition;
    addGrabberTool(yawHandle, {
        mode: "ROTATE_YAW",
        onBegin: function(event) {
            SelectionManager.saveProperties();
            initialPosition = SelectionManager.worldPosition;

            // Size the overlays to the current selection size
            var diagonal = (Vec3.length(selectionManager.worldDimensions) / 2) * 1.1;
            var halfDimensions = Vec3.multiply(selectionManager.worldDimensions, 0.5);
            innerRadius = diagonal;
            outerRadius = diagonal * 1.15;
            var innerAlpha = 0.2;
            var outerAlpha = 0.2;
            Overlays.editOverlay(rotateOverlayInner, {
                visible: true,
                size: innerRadius,
                innerRadius: 0.9,
                startAt: 0,
                endAt: 360,
                alpha: innerAlpha
            });

            Overlays.editOverlay(rotateOverlayOuter, {
                visible: true,
                size: outerRadius,
                innerRadius: 0.9,
                startAt: 0,
                endAt: 360,
                alpha: outerAlpha,
            });

            Overlays.editOverlay(rotateOverlayCurrent, {
                visible: true,
                size: outerRadius,
                startAt: 0,
                endAt: 0,
                innerRadius: 0.9,
            });

            Overlays.editOverlay(rotationDegreesDisplay, {
                visible: true,
            });

            updateRotationDegreesOverlay(0, yawHandleRotation, yawCenter);
        },
        onEnd: function(event, reason) {
            Overlays.editOverlay(rotateOverlayInner, {
                visible: false
            });
            Overlays.editOverlay(rotateOverlayOuter, {
                visible: false
            });
            Overlays.editOverlay(rotateOverlayCurrent, {
                visible: false
            });
            Overlays.editOverlay(rotationDegreesDisplay, {
                visible: false
            });

            pushCommandForSelections();
        },
        onMove: function(event) {
            var pickRay = generalComputePickRay(event.x, event.y);
            Overlays.editOverlay(selectionBox, {
                ignoreRayIntersection: true,
                visible: false
            });
            Overlays.editOverlay(baseOfEntityProjectionOverlay, {
                ignoreRayIntersection: true,
                visible: false
            });
            Overlays.editOverlay(rotateOverlayTarget, {
                ignoreRayIntersection: false
            });

            var result = Overlays.findRayIntersection(pickRay);

            if (result.intersects) {
                var center = yawCenter;
                var zero = yawZero;
                var centerToZero = Vec3.subtract(center, zero);
                var centerToIntersect = Vec3.subtract(center, result.intersection);
                var angleFromZero = Vec3.orientedAngle(centerToZero, centerToIntersect, rotationNormal);
                var distanceFromCenter = Vec3.distance(center, result.intersection);
                var snapToInner = distanceFromCenter < innerRadius;
                var snapAngle = snapToInner ? innerSnapAngle : 1.0;
                angleFromZero = Math.floor(angleFromZero / snapAngle) * snapAngle;
                var yawChange = Quat.fromVec3Degrees({
                    x: 0,
                    y: angleFromZero,
                    z: 0
                });

                // 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;
                for (var i = 0; i < SelectionManager.selections.length; i++) {
                    var entityID = SelectionManager.selections[i];
                    var properties = Entities.getEntityProperties(entityID);
                    var initialProperties = SelectionManager.savedProperties[entityID];

                    var newProperties = {
                        rotation: Quat.multiply(yawChange, initialProperties.rotation),
                    };

                    if (reposition) {
                        var dPos = Vec3.subtract(initialProperties.position, initialPosition);
                        dPos = Vec3.multiplyQbyV(yawChange, dPos);
                        newProperties.position = Vec3.sum(initialPosition, dPos);
                    }

                    Entities.editEntity(entityID, newProperties);
                }

                updateRotationDegreesOverlay(angleFromZero, yawHandleRotation, yawCenter);

                // update the rotation display accordingly...
                var startAtCurrent = 0;
                var endAtCurrent = angleFromZero;
                var startAtRemainder = angleFromZero;
                var endAtRemainder = 360;
                if (angleFromZero < 0) {
                    startAtCurrent = 360 + angleFromZero;
                    endAtCurrent = 360;
                    startAtRemainder = 0;
                    endAtRemainder = startAtCurrent;
                }
                if (snapToInner) {
                    Overlays.editOverlay(rotateOverlayOuter, {
                        startAt: 0,
                        endAt: 360
                    });
                    Overlays.editOverlay(rotateOverlayInner, {
                        startAt: startAtRemainder,
                        endAt: endAtRemainder
                    });
                    Overlays.editOverlay(rotateOverlayCurrent, {
                        startAt: startAtCurrent,
                        endAt: endAtCurrent,
                        size: innerRadius,
                        majorTickMarksAngle: innerSnapAngle,
                        minorTickMarksAngle: 0,
                        majorTickMarksLength: -0.25,
                        minorTickMarksLength: 0,
                    });
                } else {
                    Overlays.editOverlay(rotateOverlayInner, {
                        startAt: 0,
                        endAt: 360
                    });
                    Overlays.editOverlay(rotateOverlayOuter, {
                        startAt: startAtRemainder,
                        endAt: endAtRemainder
                    });
                    Overlays.editOverlay(rotateOverlayCurrent, {
                        startAt: startAtCurrent,
                        endAt: endAtCurrent,
                        size: outerRadius,
                        majorTickMarksAngle: 45.0,
                        minorTickMarksAngle: 5,
                        majorTickMarksLength: 0.25,
                        minorTickMarksLength: 0.1,
                    });
                }

            }
        }
    });

    addGrabberTool(pitchHandle, {
        mode: "ROTATE_PITCH",
        onBegin: function(event) {
            SelectionManager.saveProperties();
            initialPosition = SelectionManager.worldPosition;

            // Size the overlays to the current selection size
            var diagonal = (Vec3.length(selectionManager.worldDimensions) / 2) * 1.1;
            var halfDimensions = Vec3.multiply(selectionManager.worldDimensions, 0.5);
            innerRadius = diagonal;
            outerRadius = diagonal * 1.15;
            var innerAlpha = 0.2;
            var outerAlpha = 0.2;
            Overlays.editOverlay(rotateOverlayInner, {
                visible: true,
                size: innerRadius,
                innerRadius: 0.9,
                startAt: 0,
                endAt: 360,
                alpha: innerAlpha
            });

            Overlays.editOverlay(rotateOverlayOuter, {
                visible: true,
                size: outerRadius,
                innerRadius: 0.9,
                startAt: 0,
                endAt: 360,
                alpha: outerAlpha,
            });

            Overlays.editOverlay(rotateOverlayCurrent, {
                visible: true,
                size: outerRadius,
                startAt: 0,
                endAt: 0,
                innerRadius: 0.9,
            });

            Overlays.editOverlay(rotationDegreesDisplay, {
                visible: true,
            });

            updateRotationDegreesOverlay(0, pitchHandleRotation, pitchCenter);
        },
        onEnd: function(event, reason) {
            Overlays.editOverlay(rotateOverlayInner, {
                visible: false
            });
            Overlays.editOverlay(rotateOverlayOuter, {
                visible: false
            });
            Overlays.editOverlay(rotateOverlayCurrent, {
                visible: false
            });
            Overlays.editOverlay(rotationDegreesDisplay, {
                visible: false
            });

            pushCommandForSelections();
        },
        onMove: function(event) {
            var pickRay = generalComputePickRay(event.x, event.y);
            Overlays.editOverlay(selectionBox, {
                ignoreRayIntersection: true,
                visible: false
            });
            Overlays.editOverlay(baseOfEntityProjectionOverlay, {
                ignoreRayIntersection: true,
                visible: false
            });
            Overlays.editOverlay(rotateOverlayTarget, {
                ignoreRayIntersection: false
            });
            var result = Overlays.findRayIntersection(pickRay);

            if (result.intersects) {
                var properties = Entities.getEntityProperties(selectionManager.selections[0]);
                var center = pitchCenter;
                var zero = pitchZero;
                var centerToZero = Vec3.subtract(center, zero);
                var centerToIntersect = Vec3.subtract(center, result.intersection);
                var angleFromZero = Vec3.orientedAngle(centerToZero, centerToIntersect, rotationNormal);

                var distanceFromCenter = Vec3.distance(center, result.intersection);
                var snapToInner = distanceFromCenter < innerRadius;
                var snapAngle = snapToInner ? innerSnapAngle : 1.0;
                angleFromZero = Math.floor(angleFromZero / snapAngle) * snapAngle;

                var pitchChange = Quat.fromVec3Degrees({
                    x: angleFromZero,
                    y: 0,
                    z: 0
                });

                for (var i = 0; i < SelectionManager.selections.length; i++) {
                    var entityID = SelectionManager.selections[i];
                    var properties = Entities.getEntityProperties(entityID);
                    var initialProperties = SelectionManager.savedProperties[entityID];
                    var dPos = Vec3.subtract(initialProperties.position, initialPosition);
                    dPos = Vec3.multiplyQbyV(pitchChange, dPos);

                    Entities.editEntity(entityID, {
                        position: Vec3.sum(initialPosition, dPos),
                        rotation: Quat.multiply(pitchChange, initialProperties.rotation),
                    });
                }

                updateRotationDegreesOverlay(angleFromZero, pitchHandleRotation, pitchCenter);

                // update the rotation display accordingly...
                var startAtCurrent = 0;
                var endAtCurrent = angleFromZero;
                var startAtRemainder = angleFromZero;
                var endAtRemainder = 360;
                if (angleFromZero < 0) {
                    startAtCurrent = 360 + angleFromZero;
                    endAtCurrent = 360;
                    startAtRemainder = 0;
                    endAtRemainder = startAtCurrent;
                }
                if (snapToInner) {
                    Overlays.editOverlay(rotateOverlayOuter, {
                        startAt: 0,
                        endAt: 360
                    });
                    Overlays.editOverlay(rotateOverlayInner, {
                        startAt: startAtRemainder,
                        endAt: endAtRemainder
                    });
                    Overlays.editOverlay(rotateOverlayCurrent, {
                        startAt: startAtCurrent,
                        endAt: endAtCurrent,
                        size: innerRadius,
                        majorTickMarksAngle: innerSnapAngle,
                        minorTickMarksAngle: 0,
                        majorTickMarksLength: -0.25,
                        minorTickMarksLength: 0,
                    });
                } else {
                    Overlays.editOverlay(rotateOverlayInner, {
                        startAt: 0,
                        endAt: 360
                    });
                    Overlays.editOverlay(rotateOverlayOuter, {
                        startAt: startAtRemainder,
                        endAt: endAtRemainder
                    });
                    Overlays.editOverlay(rotateOverlayCurrent, {
                        startAt: startAtCurrent,
                        endAt: endAtCurrent,
                        size: outerRadius,
                        majorTickMarksAngle: 45.0,
                        minorTickMarksAngle: 5,
                        majorTickMarksLength: 0.25,
                        minorTickMarksLength: 0.1,
                    });
                }
            }
        }
    });

    addGrabberTool(rollHandle, {
        mode: "ROTATE_ROLL",
        onBegin: function(event) {
            SelectionManager.saveProperties();
            initialPosition = SelectionManager.worldPosition;

            // Size the overlays to the current selection size
            var diagonal = (Vec3.length(selectionManager.worldDimensions) / 2) * 1.1;
            var halfDimensions = Vec3.multiply(selectionManager.worldDimensions, 0.5);
            innerRadius = diagonal;
            outerRadius = diagonal * 1.15;
            var innerAlpha = 0.2;
            var outerAlpha = 0.2;
            Overlays.editOverlay(rotateOverlayInner, {
                visible: true,
                size: innerRadius,
                innerRadius: 0.9,
                startAt: 0,
                endAt: 360,
                alpha: innerAlpha
            });

            Overlays.editOverlay(rotateOverlayOuter, {
                visible: true,
                size: outerRadius,
                innerRadius: 0.9,
                startAt: 0,
                endAt: 360,
                alpha: outerAlpha,
            });

            Overlays.editOverlay(rotateOverlayCurrent, {
                visible: true,
                size: outerRadius,
                startAt: 0,
                endAt: 0,
                innerRadius: 0.9,
            });

            Overlays.editOverlay(rotationDegreesDisplay, {
                visible: true,
            });

            updateRotationDegreesOverlay(0, rollHandleRotation, rollCenter);
        },
        onEnd: function(event, reason) {
            Overlays.editOverlay(rotateOverlayInner, {
                visible: false
            });
            Overlays.editOverlay(rotateOverlayOuter, {
                visible: false
            });
            Overlays.editOverlay(rotateOverlayCurrent, {
                visible: false
            });
            Overlays.editOverlay(rotationDegreesDisplay, {
                visible: false
            });

            pushCommandForSelections();
        },
        onMove: function(event) {
            var pickRay = generalComputePickRay(event.x, event.y);
            Overlays.editOverlay(selectionBox, {
                ignoreRayIntersection: true,
                visible: false
            });
            Overlays.editOverlay(baseOfEntityProjectionOverlay, {
                ignoreRayIntersection: true,
                visible: false
            });
            Overlays.editOverlay(rotateOverlayTarget, {
                ignoreRayIntersection: false
            });
            var result = Overlays.findRayIntersection(pickRay);

            if (result.intersects) {
                var properties = Entities.getEntityProperties(selectionManager.selections[0]);
                var center = rollCenter;
                var zero = rollZero;
                var centerToZero = Vec3.subtract(center, zero);
                var centerToIntersect = Vec3.subtract(center, result.intersection);
                var angleFromZero = Vec3.orientedAngle(centerToZero, centerToIntersect, rotationNormal);

                var distanceFromCenter = Vec3.distance(center, result.intersection);
                var snapToInner = distanceFromCenter < innerRadius;
                var snapAngle = snapToInner ? innerSnapAngle : 1.0;
                angleFromZero = Math.floor(angleFromZero / snapAngle) * snapAngle;

                var rollChange = Quat.fromVec3Degrees({
                    x: 0,
                    y: 0,
                    z: angleFromZero
                });
                for (var i = 0; i < SelectionManager.selections.length; i++) {
                    var entityID = SelectionManager.selections[i];
                    var properties = Entities.getEntityProperties(entityID);
                    var initialProperties = SelectionManager.savedProperties[entityID];
                    var dPos = Vec3.subtract(initialProperties.position, initialPosition);
                    dPos = Vec3.multiplyQbyV(rollChange, dPos);

                    Entities.editEntity(entityID, {
                        position: Vec3.sum(initialPosition, dPos),
                        rotation: Quat.multiply(rollChange, initialProperties.rotation),
                    });
                }

                updateRotationDegreesOverlay(angleFromZero, rollHandleRotation, rollCenter);

                // update the rotation display accordingly...
                var startAtCurrent = 0;
                var endAtCurrent = angleFromZero;
                var startAtRemainder = angleFromZero;
                var endAtRemainder = 360;
                if (angleFromZero < 0) {
                    startAtCurrent = 360 + angleFromZero;
                    endAtCurrent = 360;
                    startAtRemainder = 0;
                    endAtRemainder = startAtCurrent;
                }
                if (snapToInner) {
                    Overlays.editOverlay(rotateOverlayOuter, {
                        startAt: 0,
                        endAt: 360
                    });
                    Overlays.editOverlay(rotateOverlayInner, {
                        startAt: startAtRemainder,
                        endAt: endAtRemainder
                    });
                    Overlays.editOverlay(rotateOverlayCurrent, {
                        startAt: startAtCurrent,
                        endAt: endAtCurrent,
                        size: innerRadius,
                        majorTickMarksAngle: innerSnapAngle,
                        minorTickMarksAngle: 0,
                        majorTickMarksLength: -0.25,
                        minorTickMarksLength: 0,
                    });
                } else {
                    Overlays.editOverlay(rotateOverlayInner, {
                        startAt: 0,
                        endAt: 360
                    });
                    Overlays.editOverlay(rotateOverlayOuter, {
                        startAt: startAtRemainder,
                        endAt: endAtRemainder
                    });
                    Overlays.editOverlay(rotateOverlayCurrent, {
                        startAt: startAtCurrent,
                        endAt: endAtCurrent,
                        size: outerRadius,
                        majorTickMarksAngle: 45.0,
                        minorTickMarksAngle: 5,
                        majorTickMarksLength: 0.25,
                        minorTickMarksLength: 0.1,
                    });
                }
            }
        }
    });

    that.checkMove = function() {
        if (SelectionManager.hasSelection()) {

            // FIXME - this cause problems with editing in the entity properties window
            //SelectionManager._update();

            if (!Vec3.equal(Camera.getPosition(), lastCameraPosition) ||
                !Quat.equal(Camera.getOrientation(), lastCameraOrientation)) {

                that.updateRotationHandles();
            }
        }
    };

    that.mousePressEvent = function(event) {
        var wantDebug = false;
        if (!event.isLeftButton && !that.triggered) {
            // if another mouse button than left is pressed ignore it
            return false;
        }

        var somethingClicked = false;
        var pickRay = generalComputePickRay(event.x, event.y);

        var result = Overlays.findRayIntersection(pickRay, true, [HMD.tabletID, HMD.tabletScreenID, HMD.homeButtonID]);
        if (result.intersects) {
            // mouse clicks on the tablet should override the edit affordances
            return false;
        }

        // before we do a ray test for grabbers, disable the ray intersection for our selection box
        Overlays.editOverlay(selectionBox, {
            ignoreRayIntersection: true
        });
        Overlays.editOverlay(yawHandle, {
            ignoreRayIntersection: true
        });
        Overlays.editOverlay(pitchHandle, {
            ignoreRayIntersection: true
        });
        Overlays.editOverlay(rollHandle, {
            ignoreRayIntersection: true
        });
        var result = Overlays.findRayIntersection(pickRay);

        if (result.intersects) {


            if (wantDebug) {
                print("something intersects... ");
                print("   result.overlayID:" + result.overlayID + "[" + overlayNames[result.overlayID] + "]");
                print("   result.intersects:" + result.intersects);
                print("   result.overlayID:" + result.overlayID);
                print("   result.distance:" + result.distance);
                print("   result.face:" + result.face);
                Vec3.print("   result.intersection:", result.intersection);
            }

            var tool = grabberTools[result.overlayID];
            if (tool) {
                activeTool = tool;
                mode = tool.mode;
                somethingClicked = 'tool';
                if (activeTool && activeTool.onBegin) {
                    activeTool.onBegin(event);
                }
            } else {
                switch (result.overlayID) {
                    case grabberMoveUp:
                        mode = "TRANSLATE_UP_DOWN";
                        somethingClicked = mode;

                        // in translate mode, we hide our stretch handles...
                        for (var i = 0; i < stretchHandles.length; i++) {
                            Overlays.editOverlay(stretchHandles[i], {
                                visible: false
                            });
                        }
                        break;


                    case grabberNEAR:
                    case grabberEdgeTN: // TODO: maybe this should be TOP+NEAR stretching?
                    case grabberEdgeBN: // TODO: maybe this should be BOTTOM+FAR stretching?
                        mode = "STRETCH_NEAR";
                        somethingClicked = mode;
                        break;

                    case grabberFAR:
                    case grabberEdgeTF: // TODO: maybe this should be TOP+FAR stretching?
                    case grabberEdgeBF: // TODO: maybe this should be BOTTOM+FAR stretching?
                        mode = "STRETCH_FAR";
                        somethingClicked = mode;
                        break;
                    case grabberTOP:
                        mode = "STRETCH_TOP";
                        somethingClicked = mode;
                        break;
                    case grabberBOTTOM:
                        mode = "STRETCH_BOTTOM";
                        somethingClicked = mode;
                        break;
                    case grabberRIGHT:
                    case grabberEdgeTR: // TODO: maybe this should be TOP+RIGHT stretching?
                    case grabberEdgeBR: // TODO: maybe this should be BOTTOM+RIGHT stretching?
                        mode = "STRETCH_RIGHT";
                        somethingClicked = mode;
                        break;
                    case grabberLEFT:
                    case grabberEdgeTL: // TODO: maybe this should be TOP+LEFT stretching?
                    case grabberEdgeBL: // TODO: maybe this should be BOTTOM+LEFT stretching?
                        mode = "STRETCH_LEFT";
                        somethingClicked = mode;
                        break;

                    default:
                        mode = "UNKNOWN";
                        break;
                }
            }
        }

        // if one of the items above was clicked, then we know we are in translate or stretch mode, and we
        // should hide our rotate handles...
        if (somethingClicked) {
            Overlays.editOverlay(yawHandle, {
                visible: false
            });
            Overlays.editOverlay(pitchHandle, {
                visible: false
            });
            Overlays.editOverlay(rollHandle, {
                visible: false
            });

            if (mode != "TRANSLATE_UP_DOWN") {
                Overlays.editOverlay(grabberMoveUp, {
                    visible: false
                });
            }
        }

        if (!somethingClicked) {

            if (wantDebug) {
                print("rotate handle case...");
            }


            // After testing our stretch handles, then check out rotate handles
            Overlays.editOverlay(yawHandle, {
                ignoreRayIntersection: false
            });
            Overlays.editOverlay(pitchHandle, {
                ignoreRayIntersection: false
            });
            Overlays.editOverlay(rollHandle, {
                ignoreRayIntersection: false
            });
            var result = Overlays.findRayIntersection(pickRay);

            var overlayOrientation;
            var overlayCenter;

            var properties = Entities.getEntityProperties(selectionManager.selections[0]);
            var angles = Quat.safeEulerAngles(properties.rotation);
            var pitch = angles.x;
            var yaw = angles.y;
            var roll = angles.z;

            originalRotation = properties.rotation;
            originalPitch = pitch;
            originalYaw = yaw;
            originalRoll = roll;

            if (result.intersects) {
                var tool = grabberTools[result.overlayID];
                if (tool) {
                    activeTool = tool;
                    mode = tool.mode;
                    somethingClicked = 'tool';
                    if (activeTool && activeTool.onBegin) {
                        activeTool.onBegin(event);
                    }
                }
                switch (result.overlayID) {
                    case yawHandle:
                        mode = "ROTATE_YAW";
                        somethingClicked = mode;
                        overlayOrientation = yawHandleRotation;
                        overlayCenter = yawCenter;
                        yawZero = result.intersection;
                        rotationNormal = yawNormal;
                        break;

                    case pitchHandle:
                        mode = "ROTATE_PITCH";
                        initialPosition = SelectionManager.worldPosition;
                        somethingClicked = mode;
                        overlayOrientation = pitchHandleRotation;
                        overlayCenter = pitchCenter;
                        pitchZero = result.intersection;
                        rotationNormal = pitchNormal;
                        break;

                    case rollHandle:
                        mode = "ROTATE_ROLL";
                        somethingClicked = mode;
                        overlayOrientation = rollHandleRotation;
                        overlayCenter = rollCenter;
                        rollZero = result.intersection;
                        rotationNormal = rollNormal;
                        break;

                    default:
                        if (wantDebug) {
                            print("mousePressEvent()...... " + overlayNames[result.overlayID]);
                        }
                        mode = "UNKNOWN";
                        break;
                }
            }
            if (wantDebug) {
                print("    somethingClicked:" + somethingClicked);
                print("                mode:" + mode);
            }

            if (somethingClicked) {

                Overlays.editOverlay(rotateOverlayTarget, {
                    visible: true,
                    rotation: overlayOrientation,
                    position: overlayCenter
                });
                Overlays.editOverlay(rotateOverlayInner, {
                    visible: true,
                    rotation: overlayOrientation,
                    position: overlayCenter
                });
                Overlays.editOverlay(rotateOverlayOuter, {
                    visible: true,
                    rotation: overlayOrientation,
                    position: overlayCenter,
                    startAt: 0,
                    endAt: 360
                });
                Overlays.editOverlay(rotateOverlayCurrent, {
                    visible: true,
                    rotation: overlayOrientation,
                    position: overlayCenter,
                    startAt: 0,
                    endAt: 0
                });
                Overlays.editOverlay(yawHandle, {
                    visible: false
                });
                Overlays.editOverlay(pitchHandle, {
                    visible: false
                });
                Overlays.editOverlay(rollHandle, {
                    visible: false
                });


                Overlays.editOverlay(yawHandle, {
                    visible: false
                });
                Overlays.editOverlay(pitchHandle, {
                    visible: false
                });
                Overlays.editOverlay(rollHandle, {
                    visible: false
                });
                Overlays.editOverlay(grabberMoveUp, {
                    visible: false
                });
                Overlays.editOverlay(grabberLBN, {
                    visible: false
                });
                Overlays.editOverlay(grabberLBF, {
                    visible: false
                });
                Overlays.editOverlay(grabberRBN, {
                    visible: false
                });
                Overlays.editOverlay(grabberRBF, {
                    visible: false
                });
                Overlays.editOverlay(grabberLTN, {
                    visible: false
                });
                Overlays.editOverlay(grabberLTF, {
                    visible: false
                });
                Overlays.editOverlay(grabberRTN, {
                    visible: false
                });
                Overlays.editOverlay(grabberRTF, {
                    visible: false
                });

                Overlays.editOverlay(grabberTOP, {
                    visible: false
                });
                Overlays.editOverlay(grabberBOTTOM, {
                    visible: false
                });
                Overlays.editOverlay(grabberLEFT, {
                    visible: false
                });
                Overlays.editOverlay(grabberRIGHT, {
                    visible: false
                });
                Overlays.editOverlay(grabberNEAR, {
                    visible: false
                });
                Overlays.editOverlay(grabberFAR, {
                    visible: false
                });

                Overlays.editOverlay(grabberEdgeTR, {
                    visible: false
                });
                Overlays.editOverlay(grabberEdgeTL, {
                    visible: false
                });
                Overlays.editOverlay(grabberEdgeTF, {
                    visible: false
                });
                Overlays.editOverlay(grabberEdgeTN, {
                    visible: false
                });
                Overlays.editOverlay(grabberEdgeBR, {
                    visible: false
                });
                Overlays.editOverlay(grabberEdgeBL, {
                    visible: false
                });
                Overlays.editOverlay(grabberEdgeBF, {
                    visible: false
                });
                Overlays.editOverlay(grabberEdgeBN, {
                    visible: false
                });
                Overlays.editOverlay(grabberEdgeNR, {
                    visible: false
                });
                Overlays.editOverlay(grabberEdgeNL, {
                    visible: false
                });
                Overlays.editOverlay(grabberEdgeFR, {
                    visible: false
                });
                Overlays.editOverlay(grabberEdgeFL, {
                    visible: false
                });
            }
        }

        if (!somethingClicked) {
            Overlays.editOverlay(selectionBox, {
                ignoreRayIntersection: false
            });
            var result = Overlays.findRayIntersection(pickRay);
            if (result.intersects) {
                switch (result.overlayID) {
                    case selectionBox:
                        activeTool = translateXZTool;
                        translateXZTool.pickPlanePosition = result.intersection;
                        translateXZTool.greatestDimension = Math.max(Math.max(SelectionManager.worldDimensions.x, SelectionManager.worldDimensions.y),
                            SelectionManager.worldDimensions.z);
                        if (wantDebug) {
                            print("longest dimension: " + translateXZTool.greatestDimension);
                            translateXZTool.startingDistance = Vec3.distance(pickRay.origin, SelectionManager.position);
                            print("starting distance: " + translateXZTool.startingDistance);
                            translateXZTool.startingElevation = translateXZTool.elevation(pickRay.origin, translateXZTool.pickPlanePosition);
                            print(" starting elevation: " + translateXZTool.startingElevation);
                        }

                        mode = translateXZTool.mode;
                        activeTool.onBegin(event);
                        somethingClicked = 'selectionBox';
                        break;
                    default:
                        if (wantDebug) {
                            print("mousePressEvent()...... " + overlayNames[result.overlayID]);
                        }
                        mode = "UNKNOWN";
                        break;
                }
            }
        }

        if (somethingClicked) {
            pickRay = generalComputePickRay(event.x, event.y);
            if (wantDebug) {
                print("mousePressEvent()...... " + overlayNames[result.overlayID]);
            }
        }

        // reset everything as intersectable...
        // TODO: we could optimize this since some of these were already flipped back
        Overlays.editOverlay(selectionBox, {
            ignoreRayIntersection: false
        });
        Overlays.editOverlay(yawHandle, {
            ignoreRayIntersection: false
        });
        Overlays.editOverlay(pitchHandle, {
            ignoreRayIntersection: false
        });
        Overlays.editOverlay(rollHandle, {
            ignoreRayIntersection: false
        });

        return somethingClicked;
    };

    that.mouseMoveEvent = function(event) {
        if (activeTool) {
            activeTool.onMove(event);
            SelectionManager._update();
            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 pickedAlpha;
        var highlightNeeded = false;

        if (result.intersects) {
            switch (result.overlayID) {
                case yawHandle:
                case pitchHandle:
                case rollHandle:
                    pickedColor = handleColor;
                    pickedAlpha = handleAlpha;
                    highlightNeeded = true;
                    break;

                case grabberMoveUp:
                    pickedColor = handleColor;
                    pickedAlpha = handleAlpha;
                    highlightNeeded = true;
                    break;

                case grabberLBN:
                case grabberLBF:
                case grabberRBN:
                case grabberRBF:
                case grabberLTN:
                case grabberLTF:
                case grabberRTN:
                case grabberRTF:
                    pickedColor = grabberColorCorner;
                    pickedAlpha = grabberAlpha;
                    highlightNeeded = true;
                    break;

                case grabberTOP:
                case grabberBOTTOM:
                case grabberLEFT:
                case grabberRIGHT:
                case grabberNEAR:
                case grabberFAR:
                    pickedColor = grabberColorFace;
                    pickedAlpha = grabberAlpha;
                    highlightNeeded = true;
                    break;

                case grabberEdgeTR:
                case grabberEdgeTL:
                case grabberEdgeTF:
                case grabberEdgeTN:
                case grabberEdgeBR:
                case grabberEdgeBL:
                case grabberEdgeBF:
                case grabberEdgeBN:
                case grabberEdgeNR:
                case grabberEdgeNL:
                case grabberEdgeFR:
                case grabberEdgeFL:
                case grabberSpotLightRadius:
                case grabberSpotLightT:
                case grabberSpotLightB:
                case grabberSpotLightL:
                case grabberSpotLightR:
                case grabberPointLightT:
                case grabberPointLightB:
                case grabberPointLightR:
                case grabberPointLightL:
                case grabberPointLightN:
                case grabberPointLightF:
                    pickedColor = grabberColorEdge;
                    pickedAlpha = grabberAlpha;
                    highlightNeeded = true;
                    break;

                default:
                    if (previousHandle) {
                        Overlays.editOverlay(previousHandle, {
                            color: previousHandleColor,
                            alpha: previousHandleAlpha
                        });
                        previousHandle = false;
                    }
                    break;
            }

            if (highlightNeeded) {
                if (previousHandle) {
                    Overlays.editOverlay(previousHandle, {
                        color: previousHandleColor,
                        alpha: previousHandleAlpha
                    });
                    previousHandle = false;
                }
                Overlays.editOverlay(result.overlayID, {
                    color: highlightedHandleColor,
                    alpha: highlightedHandleAlpha
                });
                previousHandle = result.overlayID;
                previousHandleColor = pickedColor;
                previousHandleAlpha = pickedAlpha;
            }

        } else {
            if (previousHandle) {
                Overlays.editOverlay(previousHandle, {
                    color: previousHandleColor,
                    alpha: previousHandleAlpha
                });
                previousHandle = false;
            }
        }

        return false;
    };


    that.updateHandleSizes = function() {
        if (selectionManager.hasSelection()) {
            var diff = Vec3.subtract(selectionManager.worldPosition, Camera.getPosition());
            var grabberSize = Vec3.length(diff) * GRABBER_DISTANCE_TO_SIZE_RATIO * 5;
            var dimensions = SelectionManager.worldDimensions;
            var avgDimension = (dimensions.x + dimensions.y + dimensions.z) / 3;
            grabberSize = Math.min(grabberSize, avgDimension / 10);

            for (var i = 0; i < stretchHandles.length; i++) {
                Overlays.editOverlay(stretchHandles[i], {
                    size: grabberSize,
                });
            }
            var handleSize = Vec3.length(diff) * GRABBER_DISTANCE_TO_SIZE_RATIO * 7;
            handleSize = Math.min(handleSize, avgDimension / 3);

            Overlays.editOverlay(yawHandle, {
                scale: handleSize,
            });
            Overlays.editOverlay(pitchHandle, {
                scale: handleSize,
            });
            Overlays.editOverlay(rollHandle, {
                scale: handleSize,
            });
            var pos = Vec3.sum(grabberMoveUpPosition, {
                x: 0,
                y: Vec3.length(diff) * GRABBER_DISTANCE_TO_SIZE_RATIO * 3,
                z: 0
            });
            Overlays.editOverlay(grabberMoveUp, {
                position: pos,
                scale: handleSize / 1.25,
            });
        }
    }
    Script.update.connect(that.updateHandleSizes);

    that.mouseReleaseEvent = function(event) {
        var showHandles = false;
        if (activeTool && activeTool.onEnd) {
            activeTool.onEnd(event);
        }
        activeTool = null;
        // hide our rotation overlays..., and show our handles
        if (mode == "ROTATE_YAW" || mode == "ROTATE_PITCH" || mode == "ROTATE_ROLL") {
            Overlays.editOverlay(rotateOverlayTarget, {
                visible: false
            });
            Overlays.editOverlay(rotateOverlayInner, {
                visible: false
            });
            Overlays.editOverlay(rotateOverlayOuter, {
                visible: false
            });
            Overlays.editOverlay(rotateOverlayCurrent, {
                visible: false
            });
            showHandles = true;
        }

        if (mode != "UNKNOWN") {
            showHandles = true;
        }

        mode = "UNKNOWN";

        // if something is selected, then reset the "original" properties for any potential next click+move operation
        if (SelectionManager.hasSelection()) {
            if (showHandles) {
                that.select(SelectionManager.selections[0], event);
            }
        }

    };

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



    return that;

}());