UNPKG

@woosh/meep-engine

Version:

Pure JavaScript game engine. Fully featured and production ready.

774 lines (563 loc) • 30.4 kB
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 };