@woosh/meep-engine
Version:
Pure JavaScript game engine. Fully featured and production ready.
774 lines (563 loc) • 30.4 kB
JavaScript
import {
BoxGeometry,
BufferGeometry,
CylinderGeometry,
Euler,
Float32BufferAttribute,
LineBasicMaterial,
Matrix4,
MeshBasicMaterial,
OctahedronGeometry,
Quaternion,
SphereGeometry,
TorusGeometry,
Vector3
} from "three";
import { Transform } from "../../../src/engine/ecs/transform/Transform.js";
import { DrawMode } from "../../../src/engine/graphics/ecs/mesh-v2/DrawMode.js";
import { ShadedGeometry } from "../../../src/engine/graphics/ecs/mesh-v2/ShadedGeometry.js";
import { ShadedGeometryFlags } from "../../../src/engine/graphics/ecs/mesh-v2/ShadedGeometryFlags.js";
import { CircleGeometry } from "../../../src/engine/graphics/geometry/CircleGeometry.js";
import { GizmoNode } from "./GizmoNode.js";
import { TransformMode } from "./TransformMode.js";
import { TranslateHelperGeometry } from "./TranslateHelperGeometry.js";
// Reusable utility variables
const _alignVector = new Vector3(0, 1, 0);
const _identityQuaternion = new Quaternion();
const _tempVector = new Vector3();
const _tempQuaternion = new Quaternion();
const _tempEuler = new Euler();
const _zeroVector = new Vector3(0, 0, 0);
const _lookAtMatrix = new Matrix4();
const _tempQuaternion2 = new Quaternion();
const _unitX = new Vector3(1, 0, 0);
const _unitY = new Vector3(0, 1, 0);
const _unitZ = new Vector3(0, 0, 1);
class TransformControlsGizmo extends GizmoNode {
constructor() {
super();
this.isTransformControlsGizmo = true;
this.type = 'TransformControlsGizmo';
// shared materials
const gizmoMaterial = new MeshBasicMaterial({
depthTest: false,
depthWrite: false,
fog: false,
toneMapped: false,
transparent: true
});
const gizmoLineMaterial = new LineBasicMaterial({
depthTest: false,
depthWrite: false,
fog: false,
toneMapped: false,
transparent: true
});
// Make unique material for each axis/color
const matInvisible = gizmoMaterial.clone();
matInvisible.opacity = 0.15;
const matHelper = gizmoLineMaterial.clone();
matHelper.opacity = 0.5;
const matRed = gizmoMaterial.clone();
matRed.color.setHex(0xff0000);
const matGreen = gizmoMaterial.clone();
matGreen.color.setHex(0x00ff00);
const matBlue = gizmoMaterial.clone();
matBlue.color.setHex(0x0000ff);
const matRedTransparent = gizmoMaterial.clone();
matRedTransparent.color.setHex(0xff0000);
matRedTransparent.opacity = 0.5;
const matGreenTransparent = gizmoMaterial.clone();
matGreenTransparent.color.setHex(0x00ff00);
matGreenTransparent.opacity = 0.5;
const matBlueTransparent = gizmoMaterial.clone();
matBlueTransparent.color.setHex(0x0000ff);
matBlueTransparent.opacity = 0.5;
const matWhiteTransparent = gizmoMaterial.clone();
matWhiteTransparent.opacity = 0.25;
const matYellowTransparent = gizmoMaterial.clone();
matYellowTransparent.color.setHex(0xffff00);
matYellowTransparent.opacity = 0.25;
const matYellow = gizmoMaterial.clone();
matYellow.color.setHex(0xffff00);
const matGray = gizmoMaterial.clone();
matGray.color.setHex(0x787878);
// reusable geometry
const arrowGeometry = new CylinderGeometry(0, 0.04, 0.1, 12);
arrowGeometry.translate(0, 0.05, 0);
const scaleHandleGeometry = new BoxGeometry(0.08, 0.08, 0.08);
scaleHandleGeometry.translate(0, 0.04, 0);
const lineGeometry = new BufferGeometry();
lineGeometry.setAttribute('position', new Float32BufferAttribute([0, 0, 0, 1, 0, 0], 3));
const lineGeometry2 = new CylinderGeometry(0.0075, 0.0075, 0.5, 3);
lineGeometry2.translate(0, 0.25, 0);
// Gizmo definitions - custom hierarchy definitions for setupGizmo() function
const gizmoTranslate = {
X: [
[ShadedGeometry.from(arrowGeometry, matRed, DrawMode.Triangles), [0.5, 0, 0], [0, 0, -Math.PI / 2]],
[ShadedGeometry.from(arrowGeometry, matRed, DrawMode.Triangles), [-0.5, 0, 0], [0, 0, Math.PI / 2]],
[ShadedGeometry.from(lineGeometry2, matRed, DrawMode.Triangles), [0, 0, 0], [0, 0, -Math.PI / 2]]
],
Y: [
[ShadedGeometry.from(arrowGeometry, matGreen, DrawMode.Triangles), [0, 0.5, 0]],
[ShadedGeometry.from(arrowGeometry, matGreen, DrawMode.Triangles), [0, -0.5, 0], [Math.PI, 0, 0]],
[ShadedGeometry.from(lineGeometry2, matGreen, DrawMode.Triangles)]
],
Z: [
[ShadedGeometry.from(arrowGeometry, matBlue, DrawMode.Triangles), [0, 0, 0.5], [Math.PI / 2, 0, 0]],
[ShadedGeometry.from(arrowGeometry, matBlue, DrawMode.Triangles), [0, 0, -0.5], [-Math.PI / 2, 0, 0]],
[ShadedGeometry.from(lineGeometry2, matBlue, DrawMode.Triangles), null, [Math.PI / 2, 0, 0]]
],
XYZ: [
[ShadedGeometry.from(new OctahedronGeometry(0.1, 0), matWhiteTransparent.clone(), DrawMode.Triangles), [0, 0, 0]]
],
XY: [
[ShadedGeometry.from(new BoxGeometry(0.15, 0.15, 0.01), matBlueTransparent.clone(), DrawMode.Triangles), [0.15, 0.15, 0]]
],
YZ: [
[ShadedGeometry.from(new BoxGeometry(0.15, 0.15, 0.01), matRedTransparent.clone(), DrawMode.Triangles), [0, 0.15, 0.15], [0, Math.PI / 2, 0]]
],
XZ: [
[ShadedGeometry.from(new BoxGeometry(0.15, 0.15, 0.01), matGreenTransparent.clone(), DrawMode.Triangles), [0.15, 0, 0.15], [-Math.PI / 2, 0, 0]]
]
};
const pickerTranslate = {
X: [
[ShadedGeometry.from(new CylinderGeometry(0.2, 0, 0.6, 4), matInvisible, DrawMode.Triangles), [0.3, 0, 0], [0, 0, -Math.PI / 2]],
[ShadedGeometry.from(new CylinderGeometry(0.2, 0, 0.6, 4), matInvisible, DrawMode.Triangles), [-0.3, 0, 0], [0, 0, Math.PI / 2]]
],
Y: [
[ShadedGeometry.from(new CylinderGeometry(0.2, 0, 0.6, 4), matInvisible, DrawMode.Triangles), [0, 0.3, 0]],
[ShadedGeometry.from(new CylinderGeometry(0.2, 0, 0.6, 4), matInvisible, DrawMode.Triangles), [0, -0.3, 0], [0, 0, Math.PI]]
],
Z: [
[ShadedGeometry.from(new CylinderGeometry(0.2, 0, 0.6, 4), matInvisible, DrawMode.Triangles), [0, 0, 0.3], [Math.PI / 2, 0, 0]],
[ShadedGeometry.from(new CylinderGeometry(0.2, 0, 0.6, 4), matInvisible, DrawMode.Triangles), [0, 0, -0.3], [-Math.PI / 2, 0, 0]]
],
XYZ: [
[ShadedGeometry.from(new OctahedronGeometry(0.2, 0), matInvisible, DrawMode.Triangles)]
],
XY: [
[ShadedGeometry.from(new BoxGeometry(0.2, 0.2, 0.01), matInvisible, DrawMode.Triangles), [0.15, 0.15, 0]]
],
YZ: [
[ShadedGeometry.from(new BoxGeometry(0.2, 0.2, 0.01), matInvisible, DrawMode.Triangles), [0, 0.15, 0.15], [0, Math.PI / 2, 0]]
],
XZ: [
[ShadedGeometry.from(new BoxGeometry(0.2, 0.2, 0.01), matInvisible, DrawMode.Triangles), [0.15, 0, 0.15], [-Math.PI / 2, 0, 0]]
]
};
const helperTranslate = {
START: [
[ShadedGeometry.from(new OctahedronGeometry(0.01, 2), matHelper, DrawMode.Triangles), null, null, null, 'helper']
],
END: [
[ShadedGeometry.from(new OctahedronGeometry(0.01, 2), matHelper, DrawMode.Triangles), null, null, null, 'helper']
],
DELTA: [
[ShadedGeometry.from(TranslateHelperGeometry(), matHelper, DrawMode.Lines), null, null, null, 'helper']
],
X: [
[ShadedGeometry.from(lineGeometry, matHelper.clone(), DrawMode.Lines), [-1e3, 0, 0], null, [1e6, 1, 1], 'helper']
],
Y: [
[ShadedGeometry.from(lineGeometry, matHelper.clone(), DrawMode.Lines), [0, -1e3, 0], [0, 0, Math.PI / 2], [1e6, 1, 1], 'helper']
],
Z: [
[ShadedGeometry.from(lineGeometry, matHelper.clone(), DrawMode.Lines), [0, 0, -1e3], [0, -Math.PI / 2, 0], [1e6, 1, 1], 'helper']
]
};
const gizmoRotate = {
XYZE: [
[ShadedGeometry.from(CircleGeometry(0.5, 1), matGray, DrawMode.Triangles), null, [0, Math.PI / 2, 0]]
],
X: [
[ShadedGeometry.from(CircleGeometry(0.5, 0.5), matRed, DrawMode.Triangles)]
],
Y: [
[ShadedGeometry.from(CircleGeometry(0.5, 0.5), matGreen, DrawMode.Triangles), null, [0, 0, -Math.PI / 2]]
],
Z: [
[ShadedGeometry.from(CircleGeometry(0.5, 0.5), matBlue, DrawMode.Triangles), null, [0, Math.PI / 2, 0]]
],
E: [
[ShadedGeometry.from(CircleGeometry(0.75, 1), matYellowTransparent, DrawMode.Triangles), null, [0, Math.PI / 2, 0]]
]
};
const helperRotate = {
AXIS: [
[ShadedGeometry.from(lineGeometry, matHelper.clone(), DrawMode.Lines), [-1e3, 0, 0], null, [1e6, 1, 1], 'helper']
]
};
const pickerRotate = {
XYZE: [
[ShadedGeometry.from(new SphereGeometry(0.25, 10, 8), matInvisible, DrawMode.Triangles)]
],
X: [
[ShadedGeometry.from(new TorusGeometry(0.5, 0.1, 4, 24), matInvisible, DrawMode.Triangles), [0, 0, 0], [0, -Math.PI / 2, -Math.PI / 2]],
],
Y: [
[ShadedGeometry.from(new TorusGeometry(0.5, 0.1, 4, 24), matInvisible, DrawMode.Triangles), [0, 0, 0], [Math.PI / 2, 0, 0]],
],
Z: [
[ShadedGeometry.from(new TorusGeometry(0.5, 0.1, 4, 24), matInvisible, DrawMode.Triangles), [0, 0, 0], [0, 0, -Math.PI / 2]],
],
E: [
[ShadedGeometry.from(new TorusGeometry(0.75, 0.1, 2, 24), matInvisible, DrawMode.Triangles)]
]
};
const gizmoScale = {
X: [
[ShadedGeometry.from(scaleHandleGeometry, matRed), [0.5, 0, 0], [0, 0, -Math.PI / 2]],
[ShadedGeometry.from(lineGeometry2, matRed), [0, 0, 0], [0, 0, -Math.PI / 2]],
[ShadedGeometry.from(scaleHandleGeometry, matRed), [-0.5, 0, 0], [0, 0, Math.PI / 2]],
],
Y: [
[ShadedGeometry.from(scaleHandleGeometry, matGreen), [0, 0.5, 0]],
[ShadedGeometry.from(lineGeometry2, matGreen)],
[ShadedGeometry.from(scaleHandleGeometry, matGreen), [0, -0.5, 0], [0, 0, Math.PI]],
],
Z: [
[ShadedGeometry.from(scaleHandleGeometry, matBlue), [0, 0, 0.5], [Math.PI / 2, 0, 0]],
[ShadedGeometry.from(lineGeometry2, matBlue), [0, 0, 0], [Math.PI / 2, 0, 0]],
[ShadedGeometry.from(scaleHandleGeometry, matBlue), [0, 0, -0.5], [-Math.PI / 2, 0, 0]]
],
XY: [
[ShadedGeometry.from(new BoxGeometry(0.15, 0.15, 0.01), matBlueTransparent), [0.15, 0.15, 0]]
],
YZ: [
[ShadedGeometry.from(new BoxGeometry(0.15, 0.15, 0.01), matRedTransparent), [0, 0.15, 0.15], [0, Math.PI / 2, 0]]
],
XZ: [
[ShadedGeometry.from(new BoxGeometry(0.15, 0.15, 0.01), matGreenTransparent), [0.15, 0, 0.15], [-Math.PI / 2, 0, 0]]
],
XYZ: [
[ShadedGeometry.from(new BoxGeometry(0.1, 0.1, 0.1), matWhiteTransparent.clone())],
]
};
const pickerScale = {
X: [
[ShadedGeometry.from(new CylinderGeometry(0.2, 0, 0.6, 4), matInvisible), [0.3, 0, 0], [0, 0, -Math.PI / 2]],
[ShadedGeometry.from(new CylinderGeometry(0.2, 0, 0.6, 4), matInvisible), [-0.3, 0, 0], [0, 0, Math.PI / 2]]
],
Y: [
[ShadedGeometry.from(new CylinderGeometry(0.2, 0, 0.6, 4), matInvisible), [0, 0.3, 0]],
[ShadedGeometry.from(new CylinderGeometry(0.2, 0, 0.6, 4), matInvisible), [0, -0.3, 0], [0, 0, Math.PI]]
],
Z: [
[ShadedGeometry.from(new CylinderGeometry(0.2, 0, 0.6, 4), matInvisible), [0, 0, 0.3], [Math.PI / 2, 0, 0]],
[ShadedGeometry.from(new CylinderGeometry(0.2, 0, 0.6, 4), matInvisible), [0, 0, -0.3], [-Math.PI / 2, 0, 0]]
],
XY: [
[ShadedGeometry.from(new BoxGeometry(0.2, 0.2, 0.01), matInvisible), [0.15, 0.15, 0]],
],
YZ: [
[ShadedGeometry.from(new BoxGeometry(0.2, 0.2, 0.01), matInvisible), [0, 0.15, 0.15], [0, Math.PI / 2, 0]],
],
XZ: [
[ShadedGeometry.from(new BoxGeometry(0.2, 0.2, 0.01), matInvisible), [0.15, 0, 0.15], [-Math.PI / 2, 0, 0]],
],
XYZ: [
[ShadedGeometry.from(new BoxGeometry(0.2, 0.2, 0.2), matInvisible), [0, 0, 0]],
]
};
const helperScale = {
X: [
[ShadedGeometry.from(lineGeometry, matHelper.clone(), DrawMode.Lines), [-1e3, 0, 0], null, [1e6, 1, 1], 'helper']
],
Y: [
[ShadedGeometry.from(lineGeometry, matHelper.clone(), DrawMode.Lines), [0, -1e3, 0], [0, 0, Math.PI / 2], [1e6, 1, 1], 'helper']
],
Z: [
[ShadedGeometry.from(lineGeometry, matHelper.clone(), DrawMode.Lines), [0, 0, -1e3], [0, -Math.PI / 2, 0], [1e6, 1, 1], 'helper']
]
};
// Creates an Object3D with gizmos described in custom hierarchy definition.
/**
*
* @returns {EntityNode}
*/
function setupGizmo(gizmoMap) {
const gizmo = new GizmoNode();
for (const name in gizmoMap) {
const gizmo_elements = gizmoMap[name];
for (let i = gizmo_elements.length; i--;) {
const gizmo_element = gizmo_elements[i];
/**
* @type {ShadedGeometry}
*/
const shaded_geometry = gizmo_element[0].clone();
// disable shadows
shaded_geometry.clearFlag(ShadedGeometryFlags.CastShadow | ShadedGeometryFlags.ReceiveShadow);
// make it so that gizmo is always drawn using direct mode
shaded_geometry.draw_method = 0;
shaded_geometry.setFlag(ShadedGeometryFlags.DrawMethodLocked);
const node = new GizmoNode();
const transform = node.entity.getComponent(Transform);
node.entity.add(shaded_geometry);
/**
* @type {number[]|undefined}
*/
const position = gizmo_element[1];
/**
* @type {number[]|undefined}
*/
const rotation = gizmo_element[2];
/**
* @type {number[]|undefined}
*/
const scale = gizmo_element[3];
/**
* @type {string|undefined}
*/
const tag = gizmo_element[4];
// name and tag properties are essential for picking and updating logic.
node.name = name;
node.tag = tag;
if (position) {
transform.position.set(position[0], position[1], position[2]);
}
if (rotation) {
transform.rotation.fromEulerAnglesXYZ(rotation[0], rotation[1], rotation[2]);
}
if (scale) {
transform.scale.set(scale[0], scale[1], scale[2]);
}
const tempGeometry = shaded_geometry.geometry.clone();
const m4 = new Matrix4();
transform.toMatrix4(m4.elements);
tempGeometry.applyMatrix4(m4);
shaded_geometry.geometry = tempGeometry;
// TODO add support for render order to ShadedGeometry
shaded_geometry.renderOrder = Infinity;
transform.makeIdentity();
gizmo.addChild(node);
}
}
return gizmo;
}
// Gizmo creation
/**
*
* @type {Object<EntityNode>}
*/
this.gizmo = {};
/**
*
* @type {Object<EntityNode>}
*/
this.picker = {};
/**
*
* @type {Object<EntityNode>}
*/
this.helper = {};
this.addChild(this.gizmo[TransformMode.Translate] = setupGizmo(gizmoTranslate));
this.addChild(this.gizmo[TransformMode.Rotate] = setupGizmo(gizmoRotate));
this.addChild(this.gizmo[TransformMode.Scale] = setupGizmo(gizmoScale));
this.addChild(this.picker[TransformMode.Translate] = setupGizmo(pickerTranslate));
this.addChild(this.picker[TransformMode.Rotate] = setupGizmo(pickerRotate));
this.addChild(this.picker[TransformMode.Scale] = setupGizmo(pickerScale));
this.addChild(this.helper[TransformMode.Translate] = setupGizmo(helperTranslate));
this.addChild(this.helper[TransformMode.Rotate] = setupGizmo(helperRotate));
this.addChild(this.helper[TransformMode.Scale] = setupGizmo(helperScale));
// Pickers should be hidden always
this.picker[TransformMode.Translate].visible = false;
this.picker[TransformMode.Rotate].visible = false;
this.picker[TransformMode.Scale].visible = false;
}
// updateMatrixWorld will update transformations and appearance of individual handles
update(force) {
const space = (this.mode === TransformMode.Scale) ? 'local' : this.space; // scale always oriented to local rotation
const quaternion = (space === 'local') ? this.worldQuaternion : _identityQuaternion;
// Show only gizmos for current transform mode
this.gizmo[TransformMode.Translate].visible = this.mode === TransformMode.Translate;
this.gizmo[TransformMode.Rotate].visible = this.mode === TransformMode.Rotate;
this.gizmo[TransformMode.Scale].visible = this.mode === TransformMode.Scale;
this.helper[TransformMode.Translate].visible = this.mode === TransformMode.Translate;
this.helper[TransformMode.Rotate].visible = this.mode === TransformMode.Rotate;
this.helper[TransformMode.Scale].visible = this.mode === TransformMode.Scale;
/**
*
* @type {(GizmoNode|{name:string,tag:string})[]}
*/
let handles = [];
handles = handles.concat(this.picker[this.mode].children);
handles = handles.concat(this.gizmo[this.mode].children);
handles = handles.concat(this.helper[this.mode].children);
for (let i = 0; i < handles.length; i++) {
const handle = handles[i];
// hide aligned to camera
handle.visible = true;
handle.transform.rotation.fromEulerAnglesXYZ(0, 0, 0);
handle.transform.position.copy(this.worldPosition);
let factor;
if (this.camera.isOrthographicCamera) {
factor = (this.camera.top - this.camera.bottom) / this.camera.zoom;
} else {
factor = this.worldPosition.distanceTo(this.cameraPosition) * Math.min(1.9 * Math.tan(Math.PI * this.camera.fov / 360) / this.camera.zoom, 7);
}
if (factor === 0) {
// prevent 0 size
factor = 1e-10;
}
handle.transform.scale.setScalar(factor * this.size / 4);
// TODO: simplify helpers and consider decoupling from gizmo
if (handle.tag === 'helper') {
handle.visible = false;
if (handle.name === 'AXIS') {
handle.transform.position.copy(this.worldPositionStart);
handle.visible = !!this.axis;
if (this.axis === 'X') {
_tempQuaternion.setFromEuler(_tempEuler.set(0, 0, 0));
handle.transform.rotation.copy(quaternion);
handle.transform.rotation.multiply(_tempQuaternion);
if (Math.abs(_alignVector.copy(_unitX).applyQuaternion(quaternion).dot(this.eye)) > 0.9) {
handle.visible = false;
}
}
if (this.axis === 'Y') {
_tempQuaternion.setFromEuler(_tempEuler.set(0, 0, Math.PI / 2));
handle.transform.rotation.copy(quaternion);
handle.transform.rotation.multiply(_tempQuaternion);
if (Math.abs(_alignVector.copy(_unitY).applyQuaternion(quaternion).dot(this.eye)) > 0.9) {
handle.visible = false;
}
}
if (this.axis === 'Z') {
_tempQuaternion.setFromEuler(_tempEuler.set(0, Math.PI / 2, 0));
handle.transform.rotation.copy(quaternion);
handle.transform.rotation.multiply(_tempQuaternion);
if (Math.abs(_alignVector.copy(_unitZ).applyQuaternion(quaternion).dot(this.eye)) > 0.9) {
handle.visible = false;
}
}
if (this.axis === 'XYZE') {
_tempQuaternion.setFromEuler(_tempEuler.set(0, Math.PI / 2, 0));
_alignVector.copy(this.rotationAxis);
handle.transform.rotation.setFromRotationMatrix(_lookAtMatrix.lookAt(_zeroVector, _alignVector, _unitY).elements);
handle.transform.rotation.multiply(_tempQuaternion);
handle.visible = this.dragging;
}
if (this.axis === 'E') {
handle.visible = false;
}
} else if (handle.name === 'START') {
handle.transform.position.copy(this.worldPositionStart);
handle.visible = this.dragging;
} else if (handle.name === 'END') {
handle.transform.position.copy(this.worldPosition);
handle.visible = this.dragging;
} else if (handle.name === 'DELTA') {
handle.transform.position.copy(this.worldPositionStart);
handle.transform.rotation.copy(this.worldQuaternionStart);
_tempVector.set(1e-10, 1e-10, 1e-10).add(this.worldPositionStart).sub(this.worldPosition).multiplyScalar(-1);
_tempVector.applyQuaternion(this.worldQuaternionStart.clone().invert());
handle.transform.scale.copy(_tempVector);
handle.visible = this.dragging;
} else {
handle.transform.rotation.copy(quaternion);
if (this.dragging) {
handle.transform.position.copy(this.worldPositionStart);
} else {
handle.transform.position.copy(this.worldPosition);
}
if (this.axis) {
handle.visible = this.axis.search(handle.name) !== -1;
}
}
// If updating helper, skip rest of the loop
continue;
}
// Align handles to current local or world rotation
handle.transform.rotation.copy(quaternion);
if (this.mode === TransformMode.Translate || this.mode === TransformMode.Scale) {
// Hide translate and scale axis facing the camera
const AXIS_HIDE_TRESHOLD = 0.99;
const PLANE_HIDE_TRESHOLD = 0.2;
if (handle.name === 'X') {
if (Math.abs(_alignVector.copy(_unitX).applyQuaternion(quaternion).dot(this.eye)) > AXIS_HIDE_TRESHOLD) {
handle.transform.scale.set(1e-10, 1e-10, 1e-10);
handle.visible = false;
}
}
if (handle.name === 'Y') {
if (Math.abs(_alignVector.copy(_unitY).applyQuaternion(quaternion).dot(this.eye)) > AXIS_HIDE_TRESHOLD) {
handle.transform.scale.set(1e-10, 1e-10, 1e-10);
handle.visible = false;
}
}
if (handle.name === 'Z') {
if (Math.abs(_alignVector.copy(_unitZ).applyQuaternion(quaternion).dot(this.eye)) > AXIS_HIDE_TRESHOLD) {
handle.transform.scale.set(1e-10, 1e-10, 1e-10);
handle.visible = false;
}
}
if (handle.name === 'XY') {
if (Math.abs(_alignVector.copy(_unitZ).applyQuaternion(quaternion).dot(this.eye)) < PLANE_HIDE_TRESHOLD) {
handle.transform.scale.set(1e-10, 1e-10, 1e-10);
handle.visible = false;
}
}
if (handle.name === 'YZ') {
if (Math.abs(_alignVector.copy(_unitX).applyQuaternion(quaternion).dot(this.eye)) < PLANE_HIDE_TRESHOLD) {
handle.transform.scale.set(1e-10, 1e-10, 1e-10);
handle.visible = false;
}
}
if (handle.name === 'XZ') {
if (Math.abs(_alignVector.copy(_unitY).applyQuaternion(quaternion).dot(this.eye)) < PLANE_HIDE_TRESHOLD) {
handle.transform.scale.set(1e-10, 1e-10, 1e-10);
handle.visible = false;
}
}
} else if (this.mode === TransformMode.Rotate) {
// Align handles to current local or world rotation
_tempQuaternion2.copy(quaternion);
_alignVector.copy(this.eye).applyQuaternion(_tempQuaternion.copy(quaternion).invert());
if (handle.name.search('E') !== -1) {
handle.transform.rotation.setFromRotationMatrix(_lookAtMatrix.lookAt(this.eye, _zeroVector, _unitY).elements);
}
if (handle.name === 'X') {
_tempQuaternion.setFromAxisAngle(_unitX, Math.atan2(-_alignVector.y, _alignVector.z));
_tempQuaternion.multiplyQuaternions(_tempQuaternion2, _tempQuaternion);
handle.transform.rotation.copy(_tempQuaternion);
}
if (handle.name === 'Y') {
_tempQuaternion.setFromAxisAngle(_unitY, Math.atan2(_alignVector.x, _alignVector.z));
_tempQuaternion.multiplyQuaternions(_tempQuaternion2, _tempQuaternion);
handle.transform.rotation.copy(_tempQuaternion);
}
if (handle.name === 'Z') {
_tempQuaternion.setFromAxisAngle(_unitZ, Math.atan2(_alignVector.y, _alignVector.x));
_tempQuaternion.multiplyQuaternions(_tempQuaternion2, _tempQuaternion);
handle.transform.rotation.copy(_tempQuaternion);
}
}
// Hide disabled axes
handle.visible = handle.visible && (handle.name.indexOf('X') === -1 || this.showX);
handle.visible = handle.visible && (handle.name.indexOf('Y') === -1 || this.showY);
handle.visible = handle.visible && (handle.name.indexOf('Z') === -1 || this.showZ);
handle.visible = handle.visible && (handle.name.indexOf('E') === -1 || (this.showX && this.showY && this.showZ));
// highlight selected axis
const sg = handle.entity.getComponent(ShadedGeometry);
sg.material._color = sg.material._color || sg.material.color.clone();
sg.material._opacity = sg.material._opacity || sg.material.opacity;
sg.material.color.copy(sg.material._color);
sg.material.opacity = sg.material._opacity;
if (this.enabled && this.axis) {
if (handle.name === this.axis) {
sg.material.color.setHex(0xffff00);
sg.material.opacity = 1.0;
} else if (this.axis.split('').some(function (a) {
return handle.name === a;
})) {
sg.material.color.setHex(0xffff00);
sg.material.opacity = 1.0;
}
}
}
super.update();
}
}
export { TransformControlsGizmo };