UNPKG

three-stdlib

Version:

stand-alone library of threejs examples

1 lines 88.7 kB
{"version":3,"file":"TransformControls.cjs","sources":["../../src/controls/TransformControls.ts"],"sourcesContent":["import {\n BoxGeometry,\n BufferGeometry,\n Color,\n CylinderGeometry,\n DoubleSide,\n Euler,\n Float32BufferAttribute,\n Line,\n LineBasicMaterial,\n Material,\n Matrix4,\n Mesh,\n MeshBasicMaterial,\n Object3D,\n OctahedronGeometry,\n OrthographicCamera,\n PerspectiveCamera,\n PlaneGeometry,\n Quaternion,\n Raycaster,\n SphereGeometry,\n Intersection,\n TorusGeometry,\n Vector3,\n Camera,\n Vector2,\n} from 'three'\n\nexport interface TransformControlsPointerObject {\n x: number\n y: number\n button: number\n}\n\nclass TransformControls<TCamera extends Camera = Camera> extends Object3D {\n public readonly isTransformControls = true\n\n public visible = false\n\n private domElement: HTMLElement | undefined\n\n private raycaster = new Raycaster()\n\n private gizmo: TransformControlsGizmo\n private plane: TransformControlsPlane\n\n private tempVector = new Vector3()\n private tempVector2 = new Vector3()\n private tempQuaternion = new Quaternion()\n private unit = {\n X: new Vector3(1, 0, 0),\n Y: new Vector3(0, 1, 0),\n Z: new Vector3(0, 0, 1),\n }\n\n private pointStart = new Vector3()\n private pointEnd = new Vector3()\n private offset = new Vector3()\n private rotationAxis = new Vector3()\n private startNorm = new Vector3()\n private endNorm = new Vector3()\n private rotationAngle = 0\n\n private cameraPosition = new Vector3()\n private cameraQuaternion = new Quaternion()\n private cameraScale = new Vector3()\n\n private parentPosition = new Vector3()\n private parentQuaternion = new Quaternion()\n private parentQuaternionInv = new Quaternion()\n private parentScale = new Vector3()\n\n private worldPositionStart = new Vector3()\n private worldQuaternionStart = new Quaternion()\n private worldScaleStart = new Vector3()\n\n private worldPosition = new Vector3()\n private worldQuaternion = new Quaternion()\n private worldQuaternionInv = new Quaternion()\n private worldScale = new Vector3()\n\n private eye = new Vector3()\n\n private positionStart = new Vector3()\n private quaternionStart = new Quaternion()\n private scaleStart = new Vector3()\n\n private camera: TCamera\n private object: Object3D | undefined\n private enabled = true\n private axis: string | null = null\n private mode: 'translate' | 'rotate' | 'scale' = 'translate'\n private translationSnap: number | null = null\n private rotationSnap: number | null = null\n private scaleSnap: number | null = null\n private space = 'world'\n private size = 1\n private dragging = false\n private showX = true\n private showY = true\n private showZ = true\n\n // events\n private changeEvent = { type: 'change' }\n private mouseDownEvent = { type: 'mouseDown', mode: this.mode }\n private mouseUpEvent = { type: 'mouseUp', mode: this.mode }\n private objectChangeEvent = { type: 'objectChange' }\n\n constructor(camera: TCamera, domElement: HTMLElement | undefined) {\n super()\n\n this.domElement = domElement\n this.camera = camera\n\n this.gizmo = new TransformControlsGizmo()\n this.add(this.gizmo)\n\n this.plane = new TransformControlsPlane()\n this.add(this.plane)\n\n // Defined getter, setter and store for a property\n const defineProperty = <TValue>(propName: string, defaultValue: TValue): void => {\n let propValue = defaultValue\n\n Object.defineProperty(this, propName, {\n get: function () {\n return propValue !== undefined ? propValue : defaultValue\n },\n\n set: function (value) {\n if (propValue !== value) {\n propValue = value\n this.plane[propName] = value\n this.gizmo[propName] = value\n\n this.dispatchEvent({ type: propName + '-changed', value: value })\n this.dispatchEvent(this.changeEvent)\n }\n },\n })\n\n //@ts-ignore\n this[propName] = defaultValue\n // @ts-ignore\n this.plane[propName] = defaultValue\n // @ts-ignore\n this.gizmo[propName] = defaultValue\n }\n\n defineProperty('camera', this.camera)\n defineProperty('object', this.object)\n defineProperty('enabled', this.enabled)\n defineProperty('axis', this.axis)\n defineProperty('mode', this.mode)\n defineProperty('translationSnap', this.translationSnap)\n defineProperty('rotationSnap', this.rotationSnap)\n defineProperty('scaleSnap', this.scaleSnap)\n defineProperty('space', this.space)\n defineProperty('size', this.size)\n defineProperty('dragging', this.dragging)\n defineProperty('showX', this.showX)\n defineProperty('showY', this.showY)\n defineProperty('showZ', this.showZ)\n defineProperty('worldPosition', this.worldPosition)\n defineProperty('worldPositionStart', this.worldPositionStart)\n defineProperty('worldQuaternion', this.worldQuaternion)\n defineProperty('worldQuaternionStart', this.worldQuaternionStart)\n defineProperty('cameraPosition', this.cameraPosition)\n defineProperty('cameraQuaternion', this.cameraQuaternion)\n defineProperty('pointStart', this.pointStart)\n defineProperty('pointEnd', this.pointEnd)\n defineProperty('rotationAxis', this.rotationAxis)\n defineProperty('rotationAngle', this.rotationAngle)\n defineProperty('eye', this.eye)\n\n // connect events\n if (domElement !== undefined) this.connect(domElement)\n }\n\n private intersectObjectWithRay = (\n object: Object3D,\n raycaster: Raycaster,\n includeInvisible?: boolean,\n ): false | Intersection => {\n const allIntersections = raycaster.intersectObject(object, true)\n\n for (let i = 0; i < allIntersections.length; i++) {\n if (allIntersections[i].object.visible || includeInvisible) {\n return allIntersections[i]\n }\n }\n\n return false\n }\n\n // Set current object\n public attach = (object: Object3D): this => {\n this.object = object\n this.visible = true\n\n return this\n }\n\n // Detatch from object\n public detach = (): this => {\n this.object = undefined\n this.visible = false\n this.axis = null\n\n return this\n }\n\n // Reset\n public reset = (): this => {\n if (!this.enabled) return this\n\n if (this.dragging) {\n if (this.object !== undefined) {\n this.object.position.copy(this.positionStart)\n this.object.quaternion.copy(this.quaternionStart)\n this.object.scale.copy(this.scaleStart)\n // @ts-ignore\n this.dispatchEvent(this.changeEvent)\n // @ts-ignore\n this.dispatchEvent(this.objectChangeEvent)\n this.pointStart.copy(this.pointEnd)\n }\n }\n\n return this\n }\n\n public updateMatrixWorld = (): void => {\n if (this.object !== undefined) {\n this.object.updateMatrixWorld()\n\n if (this.object.parent === null) {\n console.error('TransformControls: The attached 3D object must be a part of the scene graph.')\n } else {\n this.object.parent.matrixWorld.decompose(this.parentPosition, this.parentQuaternion, this.parentScale)\n }\n\n this.object.matrixWorld.decompose(this.worldPosition, this.worldQuaternion, this.worldScale)\n\n this.parentQuaternionInv.copy(this.parentQuaternion).invert()\n this.worldQuaternionInv.copy(this.worldQuaternion).invert()\n }\n\n this.camera.updateMatrixWorld()\n this.camera.matrixWorld.decompose(this.cameraPosition, this.cameraQuaternion, this.cameraScale)\n\n this.eye.copy(this.cameraPosition).sub(this.worldPosition).normalize()\n\n super.updateMatrixWorld()\n }\n\n private pointerHover = (pointer: TransformControlsPointerObject): void => {\n if (this.object === undefined || this.dragging === true) return\n\n this.raycaster.setFromCamera((pointer as unknown) as Vector2, this.camera)\n\n const intersect = this.intersectObjectWithRay(this.gizmo.picker[this.mode], this.raycaster)\n\n if (intersect) {\n this.axis = intersect.object.name\n } else {\n this.axis = null\n }\n }\n\n private pointerDown = (pointer: TransformControlsPointerObject): void => {\n if (this.object === undefined || this.dragging === true || pointer.button !== 0) return\n\n if (this.axis !== null) {\n this.raycaster.setFromCamera((pointer as unknown) as Vector2, this.camera)\n\n const planeIntersect = this.intersectObjectWithRay(this.plane, this.raycaster, true)\n\n if (planeIntersect) {\n let space = this.space\n\n if (this.mode === 'scale') {\n space = 'local'\n } else if (this.axis === 'E' || this.axis === 'XYZE' || this.axis === 'XYZ') {\n space = 'world'\n }\n\n if (space === 'local' && this.mode === 'rotate') {\n const snap = this.rotationSnap\n\n if (this.axis === 'X' && snap) this.object.rotation.x = Math.round(this.object.rotation.x / snap) * snap\n if (this.axis === 'Y' && snap) this.object.rotation.y = Math.round(this.object.rotation.y / snap) * snap\n if (this.axis === 'Z' && snap) this.object.rotation.z = Math.round(this.object.rotation.z / snap) * snap\n }\n\n this.object.updateMatrixWorld()\n\n if (this.object.parent) {\n this.object.parent.updateMatrixWorld()\n }\n\n this.positionStart.copy(this.object.position)\n this.quaternionStart.copy(this.object.quaternion)\n this.scaleStart.copy(this.object.scale)\n\n this.object.matrixWorld.decompose(this.worldPositionStart, this.worldQuaternionStart, this.worldScaleStart)\n\n this.pointStart.copy(planeIntersect.point).sub(this.worldPositionStart)\n }\n\n this.dragging = true\n this.mouseDownEvent.mode = this.mode\n // @ts-ignore\n this.dispatchEvent(this.mouseDownEvent)\n }\n }\n\n private pointerMove = (pointer: TransformControlsPointerObject): void => {\n const axis = this.axis\n const mode = this.mode\n const object = this.object\n let space = this.space\n\n if (mode === 'scale') {\n space = 'local'\n } else if (axis === 'E' || axis === 'XYZE' || axis === 'XYZ') {\n space = 'world'\n }\n\n if (object === undefined || axis === null || this.dragging === false || pointer.button !== -1) return\n\n this.raycaster.setFromCamera((pointer as unknown) as Vector2, this.camera)\n\n const planeIntersect = this.intersectObjectWithRay(this.plane, this.raycaster, true)\n\n if (!planeIntersect) return\n\n this.pointEnd.copy(planeIntersect.point).sub(this.worldPositionStart)\n\n if (mode === 'translate') {\n // Apply translate\n\n this.offset.copy(this.pointEnd).sub(this.pointStart)\n\n if (space === 'local' && axis !== 'XYZ') {\n this.offset.applyQuaternion(this.worldQuaternionInv)\n }\n\n if (axis.indexOf('X') === -1) this.offset.x = 0\n if (axis.indexOf('Y') === -1) this.offset.y = 0\n if (axis.indexOf('Z') === -1) this.offset.z = 0\n\n if (space === 'local' && axis !== 'XYZ') {\n this.offset.applyQuaternion(this.quaternionStart).divide(this.parentScale)\n } else {\n this.offset.applyQuaternion(this.parentQuaternionInv).divide(this.parentScale)\n }\n\n object.position.copy(this.offset).add(this.positionStart)\n\n // Apply translation snap\n\n if (this.translationSnap) {\n if (space === 'local') {\n object.position.applyQuaternion(this.tempQuaternion.copy(this.quaternionStart).invert())\n\n if (axis.search('X') !== -1) {\n object.position.x = Math.round(object.position.x / this.translationSnap) * this.translationSnap\n }\n\n if (axis.search('Y') !== -1) {\n object.position.y = Math.round(object.position.y / this.translationSnap) * this.translationSnap\n }\n\n if (axis.search('Z') !== -1) {\n object.position.z = Math.round(object.position.z / this.translationSnap) * this.translationSnap\n }\n\n object.position.applyQuaternion(this.quaternionStart)\n }\n\n if (space === 'world') {\n if (object.parent) {\n object.position.add(this.tempVector.setFromMatrixPosition(object.parent.matrixWorld))\n }\n\n if (axis.search('X') !== -1) {\n object.position.x = Math.round(object.position.x / this.translationSnap) * this.translationSnap\n }\n\n if (axis.search('Y') !== -1) {\n object.position.y = Math.round(object.position.y / this.translationSnap) * this.translationSnap\n }\n\n if (axis.search('Z') !== -1) {\n object.position.z = Math.round(object.position.z / this.translationSnap) * this.translationSnap\n }\n\n if (object.parent) {\n object.position.sub(this.tempVector.setFromMatrixPosition(object.parent.matrixWorld))\n }\n }\n }\n } else if (mode === 'scale') {\n if (axis.search('XYZ') !== -1) {\n let d = this.pointEnd.length() / this.pointStart.length()\n\n if (this.pointEnd.dot(this.pointStart) < 0) d *= -1\n\n this.tempVector2.set(d, d, d)\n } else {\n this.tempVector.copy(this.pointStart)\n this.tempVector2.copy(this.pointEnd)\n\n this.tempVector.applyQuaternion(this.worldQuaternionInv)\n this.tempVector2.applyQuaternion(this.worldQuaternionInv)\n\n this.tempVector2.divide(this.tempVector)\n\n if (axis.search('X') === -1) {\n this.tempVector2.x = 1\n }\n\n if (axis.search('Y') === -1) {\n this.tempVector2.y = 1\n }\n\n if (axis.search('Z') === -1) {\n this.tempVector2.z = 1\n }\n }\n\n // Apply scale\n\n object.scale.copy(this.scaleStart).multiply(this.tempVector2)\n\n if (this.scaleSnap && this.object) {\n if (axis.search('X') !== -1) {\n this.object.scale.x = Math.round(object.scale.x / this.scaleSnap) * this.scaleSnap || this.scaleSnap\n }\n\n if (axis.search('Y') !== -1) {\n object.scale.y = Math.round(object.scale.y / this.scaleSnap) * this.scaleSnap || this.scaleSnap\n }\n\n if (axis.search('Z') !== -1) {\n object.scale.z = Math.round(object.scale.z / this.scaleSnap) * this.scaleSnap || this.scaleSnap\n }\n }\n } else if (mode === 'rotate') {\n this.offset.copy(this.pointEnd).sub(this.pointStart)\n\n const ROTATION_SPEED =\n 20 / this.worldPosition.distanceTo(this.tempVector.setFromMatrixPosition(this.camera.matrixWorld))\n\n if (axis === 'E') {\n this.rotationAxis.copy(this.eye)\n this.rotationAngle = this.pointEnd.angleTo(this.pointStart)\n\n this.startNorm.copy(this.pointStart).normalize()\n this.endNorm.copy(this.pointEnd).normalize()\n\n this.rotationAngle *= this.endNorm.cross(this.startNorm).dot(this.eye) < 0 ? 1 : -1\n } else if (axis === 'XYZE') {\n this.rotationAxis.copy(this.offset).cross(this.eye).normalize()\n this.rotationAngle = this.offset.dot(this.tempVector.copy(this.rotationAxis).cross(this.eye)) * ROTATION_SPEED\n } else if (axis === 'X' || axis === 'Y' || axis === 'Z') {\n this.rotationAxis.copy(this.unit[axis])\n\n this.tempVector.copy(this.unit[axis])\n\n if (space === 'local') {\n this.tempVector.applyQuaternion(this.worldQuaternion)\n }\n\n this.rotationAngle = this.offset.dot(this.tempVector.cross(this.eye).normalize()) * ROTATION_SPEED\n }\n\n // Apply rotation snap\n\n if (this.rotationSnap) {\n this.rotationAngle = Math.round(this.rotationAngle / this.rotationSnap) * this.rotationSnap\n }\n\n // Apply rotate\n if (space === 'local' && axis !== 'E' && axis !== 'XYZE') {\n object.quaternion.copy(this.quaternionStart)\n object.quaternion\n .multiply(this.tempQuaternion.setFromAxisAngle(this.rotationAxis, this.rotationAngle))\n .normalize()\n } else {\n this.rotationAxis.applyQuaternion(this.parentQuaternionInv)\n object.quaternion.copy(this.tempQuaternion.setFromAxisAngle(this.rotationAxis, this.rotationAngle))\n object.quaternion.multiply(this.quaternionStart).normalize()\n }\n }\n\n // @ts-ignore\n this.dispatchEvent(this.changeEvent)\n // @ts-ignore\n this.dispatchEvent(this.objectChangeEvent)\n }\n\n private pointerUp = (pointer: TransformControlsPointerObject): void => {\n if (pointer.button !== 0) return\n\n if (this.dragging && this.axis !== null) {\n this.mouseUpEvent.mode = this.mode\n // @ts-ignore\n this.dispatchEvent(this.mouseUpEvent)\n }\n\n this.dragging = false\n this.axis = null\n }\n\n private getPointer = (event: Event): TransformControlsPointerObject => {\n if (this.domElement && this.domElement.ownerDocument?.pointerLockElement) {\n return {\n x: 0,\n y: 0,\n button: (event as MouseEvent).button,\n }\n } else {\n const pointer = (event as TouchEvent).changedTouches\n ? (event as TouchEvent).changedTouches[0]\n : (event as MouseEvent)\n\n const rect = this.domElement!.getBoundingClientRect()\n\n return {\n x: ((pointer.clientX - rect.left) / rect.width) * 2 - 1,\n y: (-(pointer.clientY - rect.top) / rect.height) * 2 + 1,\n button: (event as MouseEvent).button,\n }\n }\n }\n\n private onPointerHover = (event: Event): void => {\n if (!this.enabled) return\n\n switch ((event as PointerEvent).pointerType) {\n case 'mouse':\n case 'pen':\n this.pointerHover(this.getPointer(event))\n break\n }\n }\n\n private onPointerDown = (event: Event): void => {\n if (!this.enabled || !this.domElement) return\n\n this.domElement.style.touchAction = 'none' // disable touch scroll\n this.domElement.ownerDocument.addEventListener('pointermove', this.onPointerMove)\n this.pointerHover(this.getPointer(event))\n this.pointerDown(this.getPointer(event))\n }\n\n private onPointerMove = (event: Event): void => {\n if (!this.enabled) return\n\n this.pointerMove(this.getPointer(event))\n }\n\n private onPointerUp = (event: Event): void => {\n if (!this.enabled || !this.domElement) return\n\n this.domElement.style.touchAction! = ''\n this.domElement.ownerDocument.removeEventListener('pointermove', this.onPointerMove)\n\n this.pointerUp(this.getPointer(event))\n }\n\n public getMode = (): TransformControls['mode'] => this.mode\n\n public setMode = (mode: TransformControls['mode']): void => {\n this.mode = mode\n }\n\n public setTranslationSnap = (translationSnap: number): void => {\n this.translationSnap = translationSnap\n }\n\n public setRotationSnap = (rotationSnap: number): void => {\n this.rotationSnap = rotationSnap\n }\n\n public setScaleSnap = (scaleSnap: number): void => {\n this.scaleSnap = scaleSnap\n }\n\n public setSize = (size: number): void => {\n this.size = size\n }\n\n public setSpace = (space: string): void => {\n this.space = space\n }\n\n public update = (): void => {\n console.warn(\n 'THREE.TransformControls: update function has no more functionality and therefore has been deprecated.',\n )\n }\n\n public connect = (domElement: HTMLElement): void => {\n if ((domElement as any) === document) {\n console.error(\n 'THREE.OrbitControls: \"document\" should not be used as the target \"domElement\". Please use \"renderer.domElement\" instead.',\n )\n }\n this.domElement = domElement\n\n this.domElement.addEventListener('pointerdown', this.onPointerDown)\n this.domElement.addEventListener('pointermove', this.onPointerHover)\n this.domElement.ownerDocument.addEventListener('pointerup', this.onPointerUp)\n }\n\n public dispose = (): void => {\n this.domElement?.removeEventListener('pointerdown', this.onPointerDown)\n this.domElement?.removeEventListener('pointermove', this.onPointerHover)\n this.domElement?.ownerDocument?.removeEventListener('pointermove', this.onPointerMove)\n this.domElement?.ownerDocument?.removeEventListener('pointerup', this.onPointerUp)\n\n this.traverse((child) => {\n const mesh = child as Mesh<BufferGeometry, Material>\n if (mesh.geometry) {\n mesh.geometry.dispose()\n }\n if (mesh.material) {\n mesh.material.dispose()\n }\n })\n }\n}\n\ntype TransformControlsGizmoPrivateGizmos = {\n ['translate']: Object3D\n ['scale']: Object3D\n ['rotate']: Object3D\n ['visible']: boolean\n}\n\nclass TransformControlsGizmo extends Object3D {\n private isTransformControlsGizmo = true\n public type = 'TransformControlsGizmo'\n\n private tempVector = new Vector3(0, 0, 0)\n private tempEuler = new Euler()\n private alignVector = new Vector3(0, 1, 0)\n private zeroVector = new Vector3(0, 0, 0)\n private lookAtMatrix = new Matrix4()\n private tempQuaternion = new Quaternion()\n private tempQuaternion2 = new Quaternion()\n private identityQuaternion = new Quaternion()\n\n private unitX = new Vector3(1, 0, 0)\n private unitY = new Vector3(0, 1, 0)\n private unitZ = new Vector3(0, 0, 1)\n\n private gizmo: TransformControlsGizmoPrivateGizmos\n public picker: TransformControlsGizmoPrivateGizmos\n private helper: TransformControlsGizmoPrivateGizmos\n\n // these are set from parent class TransformControls\n private rotationAxis = new Vector3()\n\n private cameraPosition = new Vector3()\n\n private worldPositionStart = new Vector3()\n private worldQuaternionStart = new Quaternion()\n\n private worldPosition = new Vector3()\n private worldQuaternion = new Quaternion()\n\n private eye = new Vector3()\n\n private camera: PerspectiveCamera | OrthographicCamera = null!\n private enabled = true\n private axis: string | null = null\n private mode: 'translate' | 'rotate' | 'scale' = 'translate'\n private space = 'world'\n private size = 1\n private dragging = false\n private showX = true\n private showY = true\n private showZ = true\n\n constructor() {\n super()\n\n const gizmoMaterial = new MeshBasicMaterial({\n depthTest: false,\n depthWrite: false,\n transparent: true,\n side: DoubleSide,\n fog: false,\n toneMapped: false,\n })\n\n const gizmoLineMaterial = new LineBasicMaterial({\n depthTest: false,\n depthWrite: false,\n transparent: true,\n linewidth: 1,\n fog: false,\n toneMapped: false,\n })\n\n // Make unique material for each axis/color\n\n const matInvisible = gizmoMaterial.clone()\n matInvisible.opacity = 0.15\n\n const matHelper = gizmoMaterial.clone()\n matHelper.opacity = 0.33\n\n const matRed = gizmoMaterial.clone() as MeshBasicMaterial\n matRed.color.set(0xff0000)\n\n const matGreen = gizmoMaterial.clone() as MeshBasicMaterial\n matGreen.color.set(0x00ff00)\n\n const matBlue = gizmoMaterial.clone() as MeshBasicMaterial\n matBlue.color.set(0x0000ff)\n\n const matWhiteTransparent = gizmoMaterial.clone() as MeshBasicMaterial\n matWhiteTransparent.opacity = 0.25\n\n const matYellowTransparent = matWhiteTransparent.clone() as MeshBasicMaterial\n matYellowTransparent.color.set(0xffff00)\n\n const matCyanTransparent = matWhiteTransparent.clone() as MeshBasicMaterial\n matCyanTransparent.color.set(0x00ffff)\n\n const matMagentaTransparent = matWhiteTransparent.clone() as MeshBasicMaterial\n matMagentaTransparent.color.set(0xff00ff)\n\n const matYellow = gizmoMaterial.clone() as MeshBasicMaterial\n matYellow.color.set(0xffff00)\n\n const matLineRed = gizmoLineMaterial.clone() as LineBasicMaterial\n matLineRed.color.set(0xff0000)\n\n const matLineGreen = gizmoLineMaterial.clone() as LineBasicMaterial\n matLineGreen.color.set(0x00ff00)\n\n const matLineBlue = gizmoLineMaterial.clone() as LineBasicMaterial\n matLineBlue.color.set(0x0000ff)\n\n const matLineCyan = gizmoLineMaterial.clone() as LineBasicMaterial\n matLineCyan.color.set(0x00ffff)\n\n const matLineMagenta = gizmoLineMaterial.clone() as LineBasicMaterial\n matLineMagenta.color.set(0xff00ff)\n\n const matLineYellow = gizmoLineMaterial.clone() as LineBasicMaterial\n matLineYellow.color.set(0xffff00)\n\n const matLineGray = gizmoLineMaterial.clone() as LineBasicMaterial\n matLineGray.color.set(0x787878)\n\n const matLineYellowTransparent = matLineYellow.clone() as LineBasicMaterial\n matLineYellowTransparent.opacity = 0.25\n\n // reusable geometry\n\n const arrowGeometry = new CylinderGeometry(0, 0.05, 0.2, 12, 1, false)\n\n const scaleHandleGeometry = new BoxGeometry(0.125, 0.125, 0.125)\n\n const lineGeometry = new BufferGeometry()\n lineGeometry.setAttribute('position', new Float32BufferAttribute([0, 0, 0, 1, 0, 0], 3))\n\n const CircleGeometry = (radius: number, arc: number): BufferGeometry => {\n const geometry = new BufferGeometry()\n const vertices = []\n\n for (let i = 0; i <= 64 * arc; ++i) {\n vertices.push(0, Math.cos((i / 32) * Math.PI) * radius, Math.sin((i / 32) * Math.PI) * radius)\n }\n\n geometry.setAttribute('position', new Float32BufferAttribute(vertices, 3))\n\n return geometry\n }\n\n // Special geometry for transform helper. If scaled with position vector it spans from [0,0,0] to position\n\n const TranslateHelperGeometry = (): BufferGeometry => {\n const geometry = new BufferGeometry()\n\n geometry.setAttribute('position', new Float32BufferAttribute([0, 0, 0, 1, 1, 1], 3))\n\n return geometry\n }\n\n // Gizmo definitions - custom hierarchy definitions for setupGizmo() function\n\n const gizmoTranslate = {\n X: [\n [new Mesh(arrowGeometry, matRed), [1, 0, 0], [0, 0, -Math.PI / 2], null, 'fwd'],\n [new Mesh(arrowGeometry, matRed), [1, 0, 0], [0, 0, Math.PI / 2], null, 'bwd'],\n [new Line(lineGeometry, matLineRed)],\n ],\n Y: [\n [new Mesh(arrowGeometry, matGreen), [0, 1, 0], null, null, 'fwd'],\n [new Mesh(arrowGeometry, matGreen), [0, 1, 0], [Math.PI, 0, 0], null, 'bwd'],\n [new Line(lineGeometry, matLineGreen), null, [0, 0, Math.PI / 2]],\n ],\n Z: [\n [new Mesh(arrowGeometry, matBlue), [0, 0, 1], [Math.PI / 2, 0, 0], null, 'fwd'],\n [new Mesh(arrowGeometry, matBlue), [0, 0, 1], [-Math.PI / 2, 0, 0], null, 'bwd'],\n [new Line(lineGeometry, matLineBlue), null, [0, -Math.PI / 2, 0]],\n ],\n XYZ: [[new Mesh(new OctahedronGeometry(0.1, 0), matWhiteTransparent.clone()), [0, 0, 0], [0, 0, 0]]],\n XY: [\n [new Mesh(new PlaneGeometry(0.295, 0.295), matYellowTransparent.clone()), [0.15, 0.15, 0]],\n [new Line(lineGeometry, matLineYellow), [0.18, 0.3, 0], null, [0.125, 1, 1]],\n [new Line(lineGeometry, matLineYellow), [0.3, 0.18, 0], [0, 0, Math.PI / 2], [0.125, 1, 1]],\n ],\n YZ: [\n [new Mesh(new PlaneGeometry(0.295, 0.295), matCyanTransparent.clone()), [0, 0.15, 0.15], [0, Math.PI / 2, 0]],\n [new Line(lineGeometry, matLineCyan), [0, 0.18, 0.3], [0, 0, Math.PI / 2], [0.125, 1, 1]],\n [new Line(lineGeometry, matLineCyan), [0, 0.3, 0.18], [0, -Math.PI / 2, 0], [0.125, 1, 1]],\n ],\n XZ: [\n [\n new Mesh(new PlaneGeometry(0.295, 0.295), matMagentaTransparent.clone()),\n [0.15, 0, 0.15],\n [-Math.PI / 2, 0, 0],\n ],\n [new Line(lineGeometry, matLineMagenta), [0.18, 0, 0.3], null, [0.125, 1, 1]],\n [new Line(lineGeometry, matLineMagenta), [0.3, 0, 0.18], [0, -Math.PI / 2, 0], [0.125, 1, 1]],\n ],\n }\n\n const pickerTranslate = {\n X: [[new Mesh(new CylinderGeometry(0.2, 0, 1, 4, 1, false), matInvisible), [0.6, 0, 0], [0, 0, -Math.PI / 2]]],\n Y: [[new Mesh(new CylinderGeometry(0.2, 0, 1, 4, 1, false), matInvisible), [0, 0.6, 0]]],\n Z: [[new Mesh(new CylinderGeometry(0.2, 0, 1, 4, 1, false), matInvisible), [0, 0, 0.6], [Math.PI / 2, 0, 0]]],\n XYZ: [[new Mesh(new OctahedronGeometry(0.2, 0), matInvisible)]],\n XY: [[new Mesh(new PlaneGeometry(0.4, 0.4), matInvisible), [0.2, 0.2, 0]]],\n YZ: [[new Mesh(new PlaneGeometry(0.4, 0.4), matInvisible), [0, 0.2, 0.2], [0, Math.PI / 2, 0]]],\n XZ: [[new Mesh(new PlaneGeometry(0.4, 0.4), matInvisible), [0.2, 0, 0.2], [-Math.PI / 2, 0, 0]]],\n }\n\n const helperTranslate = {\n START: [[new Mesh(new OctahedronGeometry(0.01, 2), matHelper), null, null, null, 'helper']],\n END: [[new Mesh(new OctahedronGeometry(0.01, 2), matHelper), null, null, null, 'helper']],\n DELTA: [[new Line(TranslateHelperGeometry(), matHelper), null, null, null, 'helper']],\n X: [[new Line(lineGeometry, matHelper.clone()), [-1e3, 0, 0], null, [1e6, 1, 1], 'helper']],\n Y: [[new Line(lineGeometry, matHelper.clone()), [0, -1e3, 0], [0, 0, Math.PI / 2], [1e6, 1, 1], 'helper']],\n Z: [[new Line(lineGeometry, matHelper.clone()), [0, 0, -1e3], [0, -Math.PI / 2, 0], [1e6, 1, 1], 'helper']],\n }\n\n const gizmoRotate = {\n X: [\n [new Line(CircleGeometry(1, 0.5), matLineRed)],\n [new Mesh(new OctahedronGeometry(0.04, 0), matRed), [0, 0, 0.99], null, [1, 3, 1]],\n ],\n Y: [\n [new Line(CircleGeometry(1, 0.5), matLineGreen), null, [0, 0, -Math.PI / 2]],\n [new Mesh(new OctahedronGeometry(0.04, 0), matGreen), [0, 0, 0.99], null, [3, 1, 1]],\n ],\n Z: [\n [new Line(CircleGeometry(1, 0.5), matLineBlue), null, [0, Math.PI / 2, 0]],\n [new Mesh(new OctahedronGeometry(0.04, 0), matBlue), [0.99, 0, 0], null, [1, 3, 1]],\n ],\n E: [\n [new Line(CircleGeometry(1.25, 1), matLineYellowTransparent), null, [0, Math.PI / 2, 0]],\n [\n new Mesh(new CylinderGeometry(0.03, 0, 0.15, 4, 1, false), matLineYellowTransparent),\n [1.17, 0, 0],\n [0, 0, -Math.PI / 2],\n [1, 1, 0.001],\n ],\n [\n new Mesh(new CylinderGeometry(0.03, 0, 0.15, 4, 1, false), matLineYellowTransparent),\n [-1.17, 0, 0],\n [0, 0, Math.PI / 2],\n [1, 1, 0.001],\n ],\n [\n new Mesh(new CylinderGeometry(0.03, 0, 0.15, 4, 1, false), matLineYellowTransparent),\n [0, -1.17, 0],\n [Math.PI, 0, 0],\n [1, 1, 0.001],\n ],\n [\n new Mesh(new CylinderGeometry(0.03, 0, 0.15, 4, 1, false), matLineYellowTransparent),\n [0, 1.17, 0],\n [0, 0, 0],\n [1, 1, 0.001],\n ],\n ],\n XYZE: [[new Line(CircleGeometry(1, 1), matLineGray), null, [0, Math.PI / 2, 0]]],\n }\n\n const helperRotate = {\n AXIS: [[new Line(lineGeometry, matHelper.clone()), [-1e3, 0, 0], null, [1e6, 1, 1], 'helper']],\n }\n\n const pickerRotate = {\n X: [[new Mesh(new TorusGeometry(1, 0.1, 4, 24), matInvisible), [0, 0, 0], [0, -Math.PI / 2, -Math.PI / 2]]],\n Y: [[new Mesh(new TorusGeometry(1, 0.1, 4, 24), matInvisible), [0, 0, 0], [Math.PI / 2, 0, 0]]],\n Z: [[new Mesh(new TorusGeometry(1, 0.1, 4, 24), matInvisible), [0, 0, 0], [0, 0, -Math.PI / 2]]],\n E: [[new Mesh(new TorusGeometry(1.25, 0.1, 2, 24), matInvisible)]],\n XYZE: [[new Mesh(new SphereGeometry(0.7, 10, 8), matInvisible)]],\n }\n\n const gizmoScale = {\n X: [\n [new Mesh(scaleHandleGeometry, matRed), [0.8, 0, 0], [0, 0, -Math.PI / 2]],\n [new Line(lineGeometry, matLineRed), null, null, [0.8, 1, 1]],\n ],\n Y: [\n [new Mesh(scaleHandleGeometry, matGreen), [0, 0.8, 0]],\n [new Line(lineGeometry, matLineGreen), null, [0, 0, Math.PI / 2], [0.8, 1, 1]],\n ],\n Z: [\n [new Mesh(scaleHandleGeometry, matBlue), [0, 0, 0.8], [Math.PI / 2, 0, 0]],\n [new Line(lineGeometry, matLineBlue), null, [0, -Math.PI / 2, 0], [0.8, 1, 1]],\n ],\n XY: [\n [new Mesh(scaleHandleGeometry, matYellowTransparent), [0.85, 0.85, 0], null, [2, 2, 0.2]],\n [new Line(lineGeometry, matLineYellow), [0.855, 0.98, 0], null, [0.125, 1, 1]],\n [new Line(lineGeometry, matLineYellow), [0.98, 0.855, 0], [0, 0, Math.PI / 2], [0.125, 1, 1]],\n ],\n YZ: [\n [new Mesh(scaleHandleGeometry, matCyanTransparent), [0, 0.85, 0.85], null, [0.2, 2, 2]],\n [new Line(lineGeometry, matLineCyan), [0, 0.855, 0.98], [0, 0, Math.PI / 2], [0.125, 1, 1]],\n [new Line(lineGeometry, matLineCyan), [0, 0.98, 0.855], [0, -Math.PI / 2, 0], [0.125, 1, 1]],\n ],\n XZ: [\n [new Mesh(scaleHandleGeometry, matMagentaTransparent), [0.85, 0, 0.85], null, [2, 0.2, 2]],\n [new Line(lineGeometry, matLineMagenta), [0.855, 0, 0.98], null, [0.125, 1, 1]],\n [new Line(lineGeometry, matLineMagenta), [0.98, 0, 0.855], [0, -Math.PI / 2, 0], [0.125, 1, 1]],\n ],\n XYZX: [[new Mesh(new BoxGeometry(0.125, 0.125, 0.125), matWhiteTransparent.clone()), [1.1, 0, 0]]],\n XYZY: [[new Mesh(new BoxGeometry(0.125, 0.125, 0.125), matWhiteTransparent.clone()), [0, 1.1, 0]]],\n XYZZ: [[new Mesh(new BoxGeometry(0.125, 0.125, 0.125), matWhiteTransparent.clone()), [0, 0, 1.1]]],\n }\n\n const pickerScale = {\n X: [[new Mesh(new CylinderGeometry(0.2, 0, 0.8, 4, 1, false), matInvisible), [0.5, 0, 0], [0, 0, -Math.PI / 2]]],\n Y: [[new Mesh(new CylinderGeometry(0.2, 0, 0.8, 4, 1, false), matInvisible), [0, 0.5, 0]]],\n Z: [[new Mesh(new CylinderGeometry(0.2, 0, 0.8, 4, 1, false), matInvisible), [0, 0, 0.5], [Math.PI / 2, 0, 0]]],\n XY: [[new Mesh(scaleHandleGeometry, matInvisible), [0.85, 0.85, 0], null, [3, 3, 0.2]]],\n YZ: [[new Mesh(scaleHandleGeometry, matInvisible), [0, 0.85, 0.85], null, [0.2, 3, 3]]],\n XZ: [[new Mesh(scaleHandleGeometry, matInvisible), [0.85, 0, 0.85], null, [3, 0.2, 3]]],\n XYZX: [[new Mesh(new BoxGeometry(0.2, 0.2, 0.2), matInvisible), [1.1, 0, 0]]],\n XYZY: [[new Mesh(new BoxGeometry(0.2, 0.2, 0.2), matInvisible), [0, 1.1, 0]]],\n XYZZ: [[new Mesh(new BoxGeometry(0.2, 0.2, 0.2), matInvisible), [0, 0, 1.1]]],\n }\n\n const helperScale = {\n X: [[new Line(lineGeometry, matHelper.clone()), [-1e3, 0, 0], null, [1e6, 1, 1], 'helper']],\n Y: [[new Line(lineGeometry, matHelper.clone()), [0, -1e3, 0], [0, 0, Math.PI / 2], [1e6, 1, 1], 'helper']],\n Z: [[new Line(lineGeometry, matHelper.clone()), [0, 0, -1e3], [0, -Math.PI / 2, 0], [1e6, 1, 1], 'helper']],\n }\n\n // Creates an Object3D with gizmos described in custom hierarchy definition.\n // this is nearly impossible to Type so i'm leaving it\n const setupGizmo = (gizmoMap: any): Object3D => {\n const gizmo = new Object3D()\n\n for (let name in gizmoMap) {\n for (let i = gizmoMap[name].length; i--; ) {\n const object = gizmoMap[name][i][0].clone() as Mesh\n const position = gizmoMap[name][i][1]\n const rotation = gizmoMap[name][i][2]\n const scale = gizmoMap[name][i][3]\n const tag = gizmoMap[name][i][4]\n\n // name and tag properties are essential for picking and updating logic.\n object.name = name\n // @ts-ignore\n object.tag = tag\n\n if (position) {\n object.position.set(position[0], position[1], position[2])\n }\n\n if (rotation) {\n object.rotation.set(rotation[0], rotation[1], rotation[2])\n }\n\n if (scale) {\n object.scale.set(scale[0], scale[1], scale[2])\n }\n\n object.updateMatrix()\n\n const tempGeometry = object.geometry.clone()\n tempGeometry.applyMatrix4(object.matrix)\n object.geometry = tempGeometry\n object.renderOrder = Infinity\n\n object.position.set(0, 0, 0)\n object.rotation.set(0, 0, 0)\n object.scale.set(1, 1, 1)\n\n gizmo.add(object)\n }\n }\n\n return gizmo\n }\n\n this.gizmo = {} as TransformControlsGizmoPrivateGizmos\n this.picker = {} as TransformControlsGizmoPrivateGizmos\n this.helper = {} as TransformControlsGizmoPrivateGizmos\n\n this.add((this.gizmo['translate'] = setupGizmo(gizmoTranslate)))\n this.add((this.gizmo['rotate'] = setupGizmo(gizmoRotate)))\n this.add((this.gizmo['scale'] = setupGizmo(gizmoScale)))\n this.add((this.picker['translate'] = setupGizmo(pickerTranslate)))\n this.add((this.picker['rotate'] = setupGizmo(pickerRotate)))\n this.add((this.picker['scale'] = setupGizmo(pickerScale)))\n this.add((this.helper['translate'] = setupGizmo(helperTranslate)))\n this.add((this.helper['rotate'] = setupGizmo(helperRotate)))\n this.add((this.helper['scale'] = setupGizmo(helperScale)))\n\n // Pickers should be hidden always\n\n this.picker['translate'].visible = false\n this.picker['rotate'].visible = false\n this.picker['scale'].visible = false\n }\n\n // updateMatrixWorld will update transformations and appearance of individual handles\n public updateMatrixWorld = (): void => {\n let space = this.space\n\n if (this.mode === 'scale') {\n space = 'local' // scale always oriented to local rotation\n }\n\n const quaternion = space === 'local' ? this.worldQuaternion : this.identityQuaternion\n\n // Show only gizmos for current transform mode\n\n this.gizmo['translate'].visible = this.mode === 'translate'\n this.gizmo['rotate'].visible = this.mode === 'rotate'\n this.gizmo['scale'].visible = this.mode === 'scale'\n\n this.helper['translate'].visible = this.mode === 'translate'\n this.helper['rotate'].visible = this.mode === 'rotate'\n this.helper['scale'].visible = this.mode === 'scale'\n\n let handles: Array<Object3D & { tag?: string }> = []\n handles = handles.concat(this.picker[this.mode].children)\n handles = handles.concat(this.gizmo[this.mode].children)\n handles = handles.concat(this.helper[this.mode].children)\n\n for (let i = 0; i < handles.length; i++) {\n const handle = handles[i]\n\n // hide aligned to camera\n\n handle.visible = true\n handle.rotation.set(0, 0, 0)\n handle.position.copy(this.worldPosition)\n\n let factor\n\n if ((this.camera as OrthographicCamera).isOrthographicCamera) {\n factor =\n ((this.camera as OrthographicCamera).top - (this.camera as OrthographicCamera).bottom) /\n (this.camera as OrthographicCamera).zoom\n } else {\n factor =\n this.worldPosition.distanceTo(this.cameraPosition) *\n Math.min((1.9 * Math.tan((Math.PI * (this.camera as PerspectiveCamera).fov) / 360)) / this.camera.zoom, 7)\n }\n\n handle.scale.set(1, 1, 1).multiplyScalar((factor * this.size) / 7)\n\n // TODO: simplify helpers and consider decoupling from gizmo\n\n if (handle.tag === 'helper') {\n handle.visible = false\n\n if (handle.name === 'AXIS') {\n handle.position.copy(this.worldPositionStart)\n handle.visible = !!this.axis\n\n if (this.axis === 'X') {\n this.tempQuaternion.setFromEuler(this.tempEuler.set(0, 0, 0))\n handle.quaternion.copy(quaternion).multiply(this.tempQuaternion)\n\n if (Math.abs(this.alignVector.copy(this.unitX).applyQuaternion(quaternion).dot(this.eye)) > 0.9) {\n handle.visible = false\n }\n }\n\n if (this.axis === 'Y') {\n this.tempQuaternion.setFromEuler(this.tempEuler.set(0, 0, Math.PI / 2))\n handle.quaternion.copy(quaternion).multiply(this.tempQuaternion)\n\n if (Math.abs(this.alignVector.copy(this.unitY).applyQuaternion(quaternion).dot(this.eye)) > 0.9) {\n handle.visible = false\n }\n }\n\n if (this.axis === 'Z') {\n this.tempQuaternion.setFromEuler(this.tempEuler.set(0, Math.PI / 2, 0))\n handle.quaternion.copy(quaternion).multiply(this.tempQuaternion)\n\n if (Math.abs(this.alignVector.copy(this.unitZ).applyQuaternion(quaternion).dot(this.eye)) > 0.9) {\n handle.visible = false\n }\n }\n\n if (this.axis === 'XYZE') {\n this.tempQuaternion.setFromEuler(this.tempEuler.set(0, Math.PI / 2, 0))\n this.alignVector.copy(this.rotationAxis)\n handle.quaternion.setFromRotationMatrix(\n this.lookAtMatrix.lookAt(this.zeroVector, this.alignVector, this.unitY),\n )\n handle.quaternion.multiply(this.tempQuaternion)\n handle.visible = this.dragging\n }\n\n if (this.axis === 'E') {\n handle.visible = false\n }\n } else if (handle.name === 'START') {\n handle.position.copy(this.worldPositionStart)\n handle.visible = this.dragging\n } else if (handle.name === 'END') {\n handle.position.copy(this.worldPosition)\n handle.visible = this.dragging\n } else if (handle.name === 'DELTA') {\n handle.position.copy(this.worldPositionStart)\n handle.quaternion.copy(this.worldQuaternionStart)\n this.tempVector\n .set(1e-10, 1e-10, 1e-10)\n .add(this.worldPositionStart)\n .sub(this.worldPosition)\n .multiplyScalar(-1)\n this.tempVector.applyQuaternion(this.worldQuaternionStart.clone().invert())\n handle.scale.copy(this.tempVector)\n handle.visible = this.dragging\n } else {\n handle.quaternion.copy(quaternion)\n\n if (this.dragging) {\n handle.position.copy(this.worldPositionStart)\n } else {\n handle.position.copy(this.worldPosition)\n }\n\n if (this.axis) {\n handle.visible = this.axis.search(handle.name) !== -1\n }\n }\n\n // If updating helper, skip rest of the loop\n continue\n }\n\n // Align handles to current local or world rotation\n\n handle.quaternion.copy(quaternion)\n\n if (this.mode === 'translate' || this.mode === 'scale') {\n // Hide translate and scale axis facing the camera\n\n const AXIS_HIDE_TRESHOLD = 0.99\n const PLANE_HIDE_TRESHOLD = 0.2\n const AXIS_FLIP_TRESHOLD = 0.0\n\n if (handle.name === 'X' || handle.name === 'XYZX') {\n if (\n Math.abs(this.alignVector.copy(this.unitX).applyQuaternion(quaternion).dot(this.eye)) > AXIS_HIDE_TRESHOLD\n ) {\n handle.scale.set(1e-10, 1e-10, 1e-10)\n handle.visible = false\n }\n }\n\n if (handle.name === 'Y' || handle.name === 'XYZY') {\n if (\n Math.abs(this.alignVector.copy(this.unitY).applyQuaternion(quaternion).dot(this.eye)) > AXIS_HIDE_TRESHOLD\n ) {\n handle.scale.set(1e-10, 1e-10, 1e-10)\n handle.visible = false\n }\n }\n\n if (handle.name === 'Z' || handle.name === 'XYZZ') {\n if (\n Math.abs(this.alignVector.copy(this.unitZ).applyQuaternion(quaternion).dot(this.eye)) > AXIS_HIDE_TRESHOLD\n ) {\n handle.scale.set(1e-10, 1e-10, 1e-10)\n handle.visible = false\n }\n }\n\n if (handle.name === 'XY') {\n if (\n Math.abs(this.alignVector.copy(this.unitZ).applyQuaternion(quaternion).dot(this.eye)) < PLANE_HIDE_TRESHOLD\n ) {\n handle.scale.set(1e-10, 1e-10, 1e-10)\n handle.visible = false\n }\n }\n\n if (handle.name === 'YZ') {\n if (\n Math.abs(this.alignVector.copy(this.unitX).applyQuaternion(quaternion).dot(this.eye)) < PLANE_HIDE_TRESHOLD\n ) {\n handle.scale.set(1e-10, 1e-10, 1e-10)\n handle.visible = false\n }\n }\n\n if (handle.name === 'XZ') {\n if (\n Math.abs(this.alignVector.copy(this.unitY).applyQuaternion(quaternion).dot(this.eye)) < PLANE_HIDE_TRESHOLD\n ) {\n handle.scale.set(1e-10, 1e-10, 1e-10)\n handle.visible = false\n }\n }\n\n // Flip translate and scale axis ocluded behind another axis\n\n if (handle.name.search('X') !== -1) {\n if (this.alignVector.copy(this.unitX).applyQuaternion(quaternion).dot(this.eye) < AXIS_FLIP_TRESHOLD) {\n if (handle.tag === 'fwd') {\n handle.visible = false\n } else {\n handle.scale.x *= -1\n }\n } else if (handle.tag === 'bwd') {\n handle.visible = false\n }\n }\n\n if (handle.name.search('Y') !== -1) {\n if (this.alignVector.copy(this.unitY).applyQuaternion(quaternion).dot(this.eye) < AXIS_FLIP_TRESHOLD) {\n if (handle.tag === 'fwd') {\n handle.visible = false\n } else {\n handle.scale.y *= -1\n }\n } else if (handle.tag === 'bwd') {\n handle.visible = false\n }\n }\n\n if (handle.name.search('Z') !== -1) {\n if (this.alignVector.copy(this.unitZ).applyQuaternion(quaternion).dot(this.eye) < AXIS_FLIP_TRESHOLD) {\n if (handle.tag === 'fwd') {\n handle.visible = false\n } else {\n handle.scale.z *= -1\n }\n } else if (handle.tag === 'bwd') {\n handle.visible = false\n }\n }\n } else if (this.mode === 'rotate') {\n // Align handles to current local or world rotation\n\n this.tempQuaternion2.copy(quaternion)\n this.alignVector.copy(this.eye).applyQuaternion(this.tempQuaternion.copy(quaternion).invert())\n\n if (handle.name.search('E') !== -1) {\n handle.quaternion.setFromRotationMatrix(this.lookAtMatrix.lookAt(this.eye, this.zeroVector, this.unitY))\n }\n\n if (handle.name === 'X') {\n this.tempQuaternion.setFromAxisAngle(this.unitX, Math.atan2(-this.alignVector.y, this.alignVector.z))\n this.tempQuaternion.multiplyQuaternions(this.tempQuaternion2, this.tempQuaternion)\n handle.quaternion.copy(this.tempQuaternion)\n }\n\n if (handle.name === 'Y') {\n this.tempQuaternion.setFromAxisAngle(this.unitY, Math.atan2(this.alignVector.x, this.alignVector.z))\n this.tempQuaternion.multiplyQuaternions(this.tempQuaternion2, this.tempQuaternion)\n handle.quaternion.copy(this.tempQuaternion)\n }\n\n if (handle.name === 'Z') {\n this.tempQuaternion.setFromAxisAngle(this.unitZ, Math.atan2(this.alignVector.y, this.alignVector.x))\n this.tempQuaternion.multiplyQuaternions(this.tempQuaternion2, this.tempQuaternion)\n handle.quaternion.copy(this.tempQuaternion)\n }\n }\n\n // Hide disabled axes\n handle.visible = handle.visible && (handle.name.indexOf('X') === -1 || this.showX)\n handle.visible = handle.visible && (handle.name.indexOf('Y') === -1 || this.showY)\n handle.visible = handle.visible && (handle.name.indexOf('Z') === -1 || this.showZ)\n handle.visible = handle.visible && (handle.name.indexOf('E') === -1 || (this.showX && this.showY && this.showZ))\n\n // highlight selected axis\n\n //@ts-ignore\n handle.material.tempOpacity = handle.material.tempOpacity || handle.material.opacity\n //@ts-ignore\n handle.material.tempColor = handle.material.tempColor || handle.material.color.clone()\n //@ts-ignore\n handle.material.color.copy(handle.material.tempColor)\n //@ts-ignore\n handle.material.opacity = handle.material.tempOpacity\n\n if (!this.enabled) {\n //@ts-ignore\n handle.material.opacity *= 0.5\n //@ts-ignore\n handle.material.color.lerp(new Color(1, 1, 1), 0.5)\n } else if (this.axis) {\n if (handle.name === this.axis) {\n //@ts-ignore\n handle.material.opacity = 1.0\n //@ts-ignore\n handle.material.color.lerp(new Color(1, 1, 1), 0.5)\n } else if (\n this.axis.split('').some(function (a) {\n return handle.name === a\n })\n ) {\n //@ts-ignore\n handle.material.opacity = 1.0\n //@ts-ignore\n handle.material.color.lerp(new Color(1, 1, 1), 0.5)\n } else {\n //@ts-ignore\n handle.material.opacity *= 0.25\n //@ts-ignore\n handle.material.color.lerp(new Color(1, 1, 1), 0.5)\n }\n }\n }\n\n super.updateMatrixWorld()\n }\n}\n\nclass TransformControlsPlane extends Mesh<PlaneGeometry, MeshBasicMaterial> {\n private isTransformControlsPlane = true\n public type = 'TransformControlsPlane'\n\n constructor() {\n super(\n new PlaneGeometry(100000, 100000, 2, 2),\n new MeshBasicMaterial({\n visible: false,\n wireframe: true,\n side: DoubleSide,\n transparent: true,\n opacity: 0.1,\n toneMapped: false,\n }),\n )\n }\n\n private unitX = new Vector3(1, 0, 0)\n private unitY = new Vector3(0, 1, 0)\n private unitZ = new Vector3(0, 0, 1)\n\n private tempVector = new Vector3()\n private dirVector = new Vector3()\n private alignVector = new Vector3()\n private tempMatrix = new Matrix4()\n private identityQuaternion = new Quaternion()\n\n // these are set from parent class TransformControls\n private cameraQuaternion = new Quaternion()\n\n private worldPosition = new Vector3()\n private worldQuaternion = new Quaternion()\n\n private eye = n