overte-JulianGro/unpublishedScripts/marketplace/shapes/modules/handles.js

372 lines
13 KiB
JavaScript

//
// handles.js
//
// Created by David Rowe on 21 Jul 2017.
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
/* global Handles:true */
Handles = function (side) {
// Draws scaling handles.
"use strict";
var boundingBoxOverlay,
boundingBoxDimensions,
boundingBoxLocalCenter,
cornerIndexes = [],
cornerHandleOverlays = [],
faceHandleOverlays = [],
faceHandleOffsets,
BOUNDING_BOX_COLOR = { red: 0, green: 240, blue: 240 },
BOUNDING_BOX_ALPHA = 0.8,
HANDLE_NORMAL_COLOR = { red: 0, green: 240, blue: 240 },
HANDLE_HOVER_COLOR = { red: 0, green: 255, blue: 120 },
HANDLE_NORMAL_ALPHA = 0.7,
HANDLE_HOVER_ALPHA = 0.9,
NUM_CORNERS = 8,
NUM_CORNER_HANDLES = 2,
CORNER_HANDLE_OVERLAY_DIMENSIONS = { x: 0.1, y: 0.1, z: 0.1 },
CORNER_HANDLE_OVERLAY_AXES,
NUM_FACE_HANDLES = 6,
FACE_HANDLE_OVERLAY_DIMENSIONS = { x: 0.1, y: 0.12, z: 0.1 },
FACE_HANDLE_OVERLAY_AXES,
FACE_HANDLE_OVERLAY_OFFSETS,
FACE_HANDLE_OVERLAY_ROTATIONS,
FACE_HANDLE_OVERLAY_SCALE_AXES,
DISTANCE_MULTIPLIER_MULTIPLIER = 0.25,
hoveredOverlayID = null,
isVisible = false,
// Scaling.
scalingBoundingBoxDimensions,
scalingBoundingBoxLocalCenter,
i;
if (!(this instanceof Handles)) {
return new Handles(side);
}
CORNER_HANDLE_OVERLAY_AXES = [
// Ordered such that items 4 apart are opposite corners - used in display().
{ x: -0.5, y: -0.5, z: -0.5 },
{ x: -0.5, y: -0.5, z: 0.5 },
{ x: -0.5, y: 0.5, z: -0.5 },
{ x: -0.5, y: 0.5, z: 0.5 },
{ x: 0.5, y: 0.5, z: 0.5 },
{ x: 0.5, y: 0.5, z: -0.5 },
{ x: 0.5, y: -0.5, z: 0.5 },
{ x: 0.5, y: -0.5, z: -0.5 }
];
FACE_HANDLE_OVERLAY_AXES = [
{ x: -0.5, y: 0, z: 0 },
{ x: 0.5, y: 0, z: 0 },
{ x: 0, y: -0.5, z: 0 },
{ x: 0, y: 0.5, z: 0 },
{ x: 0, y: 0, z: -0.5 },
{ x: 0, y: 0, z: 0.5 }
];
FACE_HANDLE_OVERLAY_OFFSETS = {
x: FACE_HANDLE_OVERLAY_DIMENSIONS.y,
y: FACE_HANDLE_OVERLAY_DIMENSIONS.y,
z: FACE_HANDLE_OVERLAY_DIMENSIONS.y
};
FACE_HANDLE_OVERLAY_ROTATIONS = [
Quat.fromVec3Degrees({ x: 0, y: 0, z: 90 }),
Quat.fromVec3Degrees({ x: 0, y: 0, z: -90 }),
Quat.fromVec3Degrees({ x: 180, y: 0, z: 0 }),
Quat.fromVec3Degrees({ x: 0, y: 0, z: 0 }),
Quat.fromVec3Degrees({ x: -90, y: 0, z: 0 }),
Quat.fromVec3Degrees({ x: 90, y: 0, z: 0 })
];
FACE_HANDLE_OVERLAY_SCALE_AXES = [
Vec3.UNIT_NEG_X,
Vec3.UNIT_X,
Vec3.UNIT_NEG_Y,
Vec3.UNIT_Y,
Vec3.UNIT_NEG_Z,
Vec3.UNIT_Z
];
function isAxisHandle(overlayID) {
return faceHandleOverlays.indexOf(overlayID) !== -1;
}
function isCornerHandle(overlayID) {
return cornerHandleOverlays.indexOf(overlayID) !== -1;
}
function isHandle(overlayID) {
return isAxisHandle(overlayID) || isCornerHandle(overlayID);
}
function handleOffset(overlayID) {
// Distance from overlay position to entity surface.
if (isCornerHandle(overlayID)) {
return 0; // Corner overlays are centered on the corner.
}
return faceHandleOffsets.y / 2;
}
function getOverlays() {
return [].concat(cornerHandleOverlays, faceHandleOverlays);
}
function scalingAxis(overlayID) {
var axesIndex;
if (isCornerHandle(overlayID)) {
axesIndex = CORNER_HANDLE_OVERLAY_AXES[cornerIndexes[cornerHandleOverlays.indexOf(overlayID)]];
return Vec3.normalize(Vec3.multiplyVbyV(axesIndex, boundingBoxDimensions));
}
return FACE_HANDLE_OVERLAY_SCALE_AXES[faceHandleOverlays.indexOf(overlayID)];
}
function scalingDirections(overlayID) {
if (isCornerHandle(overlayID)) {
return Vec3.ONE;
}
return Vec3.abs(FACE_HANDLE_OVERLAY_SCALE_AXES[faceHandleOverlays.indexOf(overlayID)]);
}
function display(rootEntityID, boundingBox, isMultipleEntities, isSuppressZAxis) {
var boundingBoxCenter,
boundingBoxOrientation,
cameraPosition,
boundingBoxVector,
distanceMultiplier,
cameraUp,
cornerPosition,
cornerVector,
crossProductScale,
maxCrossProductScale,
rightCornerIndex,
leftCornerIndex,
cornerHandleDimensions,
faceHandleDimensions,
i;
isVisible = true;
boundingBoxDimensions = boundingBox.dimensions;
boundingBoxCenter = boundingBox.center;
boundingBoxLocalCenter = boundingBox.localCenter;
boundingBoxOrientation = boundingBox.orientation;
// Selection bounding box.
boundingBoxOverlay = Overlays.addOverlay("cube", {
parentID: rootEntityID,
localPosition: boundingBoxLocalCenter,
localRotation: Quat.ZERO,
dimensions: boundingBoxDimensions,
color: BOUNDING_BOX_COLOR,
alpha: BOUNDING_BOX_ALPHA,
solid: false,
drawInFront: true,
ignoreRayIntersection: true,
visible: true
});
// Somewhat maintain general angular size of scale handles per bounding box center but make more distance ones
// display smaller in order to give comfortable depth cue.
cameraPosition = Camera.position;
boundingBoxVector = Vec3.subtract(boundingBox.center, Camera.position);
distanceMultiplier = Vec3.length(boundingBoxVector);
distanceMultiplier = DISTANCE_MULTIPLIER_MULTIPLIER
* (distanceMultiplier + (1 - Math.LOG10E * Math.log(distanceMultiplier + 1)));
// Corner scale handles.
// At right-most and opposite corners of bounding box.
cameraUp = Quat.getUp(Camera.orientation);
maxCrossProductScale = 0;
for (i = 0; i < NUM_CORNERS; i++) {
cornerPosition = Vec3.sum(boundingBoxCenter,
Vec3.multiplyQbyV(boundingBoxOrientation,
Vec3.multiplyVbyV(CORNER_HANDLE_OVERLAY_AXES[i], boundingBoxDimensions)));
cornerVector = Vec3.subtract(cornerPosition, cameraPosition);
crossProductScale = Vec3.dot(Vec3.cross(cornerVector, boundingBoxVector), cameraUp);
if (crossProductScale > maxCrossProductScale) {
maxCrossProductScale = crossProductScale;
rightCornerIndex = i;
}
}
leftCornerIndex = (rightCornerIndex + 4) % NUM_CORNERS;
cornerIndexes[0] = leftCornerIndex;
cornerIndexes[1] = rightCornerIndex;
cornerHandleDimensions = Vec3.multiply(distanceMultiplier, CORNER_HANDLE_OVERLAY_DIMENSIONS);
for (i = 0; i < NUM_CORNER_HANDLES; i++) {
cornerHandleOverlays[i] = Overlays.addOverlay("sphere", {
parentID: rootEntityID,
localPosition: Vec3.sum(boundingBoxLocalCenter,
Vec3.multiplyVbyV(CORNER_HANDLE_OVERLAY_AXES[cornerIndexes[i]], boundingBoxDimensions)),
localRotation: Quat.ZERO,
dimensions: cornerHandleDimensions,
color: HANDLE_NORMAL_COLOR,
alpha: HANDLE_NORMAL_ALPHA,
solid: true,
drawInFront: true,
ignoreRayIntersection: false,
visible: true
});
}
// Face scale handles.
// Only valid for a single entity because for multiple entities, some may be at an angle relative to the root entity
// which would necessitate a (non-existent) shear transform be applied to them when scaling a face of the set.
if (!isMultipleEntities) {
faceHandleDimensions = Vec3.multiply(distanceMultiplier, FACE_HANDLE_OVERLAY_DIMENSIONS);
faceHandleOffsets = Vec3.multiply(distanceMultiplier, FACE_HANDLE_OVERLAY_OFFSETS);
for (i = 0; i < NUM_FACE_HANDLES; i++) {
if (!isSuppressZAxis || FACE_HANDLE_OVERLAY_AXES[i].z === 0) {
faceHandleOverlays[i] = Overlays.addOverlay("shape", {
parentID: rootEntityID,
localPosition: Vec3.sum(boundingBoxLocalCenter,
Vec3.multiplyVbyV(FACE_HANDLE_OVERLAY_AXES[i], Vec3.sum(boundingBoxDimensions, faceHandleOffsets))),
localRotation: FACE_HANDLE_OVERLAY_ROTATIONS[i],
dimensions: faceHandleDimensions,
shape: "Cone",
color: HANDLE_NORMAL_COLOR,
alpha: HANDLE_NORMAL_ALPHA,
solid: true,
drawInFront: true,
ignoreRayIntersection: false,
visible: true
});
}
}
} else {
faceHandleOverlays = [];
}
}
function startScaling() {
// Nothing to do.
}
function scale(scale3D) {
// Scale relative to dimensions and positions at start of scaling.
// Selection bounding box.
scalingBoundingBoxDimensions = Vec3.multiplyVbyV(scale3D, boundingBoxLocalCenter);
scalingBoundingBoxLocalCenter = Vec3.multiplyVbyV(scale3D, boundingBoxDimensions);
Overlays.editOverlay(boundingBoxOverlay, {
localPosition: scalingBoundingBoxDimensions,
dimensions: scalingBoundingBoxLocalCenter
});
// Corner scale handles.
for (i = 0; i < NUM_CORNER_HANDLES; i++) {
Overlays.editOverlay(cornerHandleOverlays[i], {
localPosition: Vec3.sum(scalingBoundingBoxDimensions,
Vec3.multiplyVbyV(CORNER_HANDLE_OVERLAY_AXES[cornerIndexes[i]], scalingBoundingBoxLocalCenter))
});
}
// Face scale handles.
if (faceHandleOverlays.length > 0) {
for (i = 0; i < NUM_FACE_HANDLES; i++) {
Overlays.editOverlay(faceHandleOverlays[i], {
localPosition: Vec3.sum(scalingBoundingBoxDimensions,
Vec3.multiplyVbyV(FACE_HANDLE_OVERLAY_AXES[i],
Vec3.sum(scalingBoundingBoxLocalCenter, faceHandleOffsets)))
});
}
}
}
function finishScaling() {
// Adopt final scale.
boundingBoxLocalCenter = scalingBoundingBoxDimensions;
boundingBoxDimensions = scalingBoundingBoxLocalCenter;
}
function hover(overlayID) {
if (overlayID !== hoveredOverlayID) {
if (hoveredOverlayID !== null) {
Overlays.editOverlay(hoveredOverlayID, { color: HANDLE_NORMAL_COLOR });
hoveredOverlayID = null;
}
if (overlayID !== null
&& (faceHandleOverlays.indexOf(overlayID) !== -1 || cornerHandleOverlays.indexOf(overlayID) !== -1)) {
Overlays.editOverlay(overlayID, {
color: HANDLE_HOVER_COLOR,
alpha: HANDLE_HOVER_ALPHA
});
hoveredOverlayID = overlayID;
}
}
}
function grab(overlayID) {
var overlay,
isShowAll = overlayID === null,
color = isShowAll ? HANDLE_NORMAL_COLOR : HANDLE_HOVER_COLOR,
alpha = isShowAll ? HANDLE_NORMAL_ALPHA : HANDLE_HOVER_ALPHA,
i,
length;
for (i = 0, length = cornerHandleOverlays.length; i < length; i++) {
overlay = cornerHandleOverlays[i];
Overlays.editOverlay(overlay, {
visible: isVisible && (isShowAll || overlay === overlayID),
color: color,
alpha: alpha
});
}
for (i = 0, length = faceHandleOverlays.length; i < length; i++) {
overlay = faceHandleOverlays[i];
Overlays.editOverlay(overlay, {
visible: isVisible && (isShowAll || overlay === overlayID),
color: color,
alpha: alpha
});
}
}
function clear() {
var i,
length;
Overlays.deleteOverlay(boundingBoxOverlay);
for (i = 0; i < NUM_CORNER_HANDLES; i++) {
Overlays.deleteOverlay(cornerHandleOverlays[i]);
}
for (i = 0, length = faceHandleOverlays.length; i < length; i++) {
Overlays.deleteOverlay(faceHandleOverlays[i]);
}
isVisible = false;
}
function destroy() {
clear();
}
return {
display: display,
overlays: getOverlays,
isHandle: isHandle,
handleOffset: handleOffset,
scalingAxis: scalingAxis,
scalingDirections: scalingDirections,
startScaling: startScaling,
scale: scale,
finishScaling: finishScaling,
hover: hover,
grab: grab,
clear: clear,
destroy: destroy
};
};
Handles.prototype = {};