diff options
| author | Agando <roduccomm@gmail.com> | 2026-07-09 06:00:32 +0200 |
|---|---|---|
| committer | Agando <roduccomm@gmail.com> | 2026-07-09 06:00:32 +0200 |
| commit | cf7461ffeb4f53f3f50c9c692a2e4fdcecf17e82 (patch) | |
| tree | e7780cd41271381cf108c9c81d71a3cb65cfd555 /downloads/TransformControls.js | |
| parent | ea4d09fb5ccba12ed774fcf7c913edfa1e6a851a (diff) | |
auto: 2026-07-09_06:00 changes
Diffstat (limited to 'downloads/TransformControls.js')
| -rw-r--r-- | downloads/TransformControls.js | 1471 |
1 files changed, 1471 insertions, 0 deletions
diff --git a/downloads/TransformControls.js b/downloads/TransformControls.js new file mode 100644 index 0000000..8422db8 --- /dev/null +++ b/downloads/TransformControls.js @@ -0,0 +1,1471 @@ +( function () { + + const _raycaster = new THREE.Raycaster(); + + const _tempVector = new THREE.Vector3(); + + const _tempVector2 = new THREE.Vector3(); + + const _tempQuaternion = new THREE.Quaternion(); + + const _unit = { + X: new THREE.Vector3( 1, 0, 0 ), + Y: new THREE.Vector3( 0, 1, 0 ), + Z: new THREE.Vector3( 0, 0, 1 ) + }; + const _changeEvent = { + type: 'change' + }; + const _mouseDownEvent = { + type: 'mouseDown' + }; + const _mouseUpEvent = { + type: 'mouseUp', + mode: null + }; + const _objectChangeEvent = { + type: 'objectChange' + }; + + class TransformControls extends THREE.Object3D { + + constructor( camera, domElement ) { + + super(); + + if ( domElement === undefined ) { + + console.warn( 'THREE.TransformControls: The second parameter "domElement" is now mandatory.' ); + domElement = document; + + } + + this.visible = false; + this.domElement = domElement; + + const _gizmo = new TransformControlsGizmo(); + + this._gizmo = _gizmo; + this.add( _gizmo ); + + const _plane = new TransformControlsPlane(); + + this._plane = _plane; + this.add( _plane ); + const scope = this; // Defined getter, setter and store for a property + + function defineProperty( propName, defaultValue ) { + + let propValue = defaultValue; + Object.defineProperty( scope, propName, { + get: function () { + + return propValue !== undefined ? propValue : defaultValue; + + }, + set: function ( value ) { + + if ( propValue !== value ) { + + propValue = value; + _plane[ propName ] = value; + _gizmo[ propName ] = value; + scope.dispatchEvent( { + type: propName + '-changed', + value: value + } ); + scope.dispatchEvent( _changeEvent ); + + } + + } + } ); + scope[ propName ] = defaultValue; + _plane[ propName ] = defaultValue; + _gizmo[ propName ] = defaultValue; + + } // Define properties with getters/setter + // Setting the defined property will automatically trigger change event + // Defined properties are passed down to gizmo and plane + + + defineProperty( 'camera', camera ); + defineProperty( 'object', undefined ); + defineProperty( 'enabled', true ); + defineProperty( 'axis', null ); + defineProperty( 'mode', 'translate' ); + defineProperty( 'translationSnap', null ); + defineProperty( 'rotationSnap', null ); + defineProperty( 'scaleSnap', null ); + defineProperty( 'space', 'world' ); + defineProperty( 'size', 1 ); + defineProperty( 'dragging', false ); + defineProperty( 'showX', true ); + defineProperty( 'showY', true ); + defineProperty( 'showZ', true ); // Reusable utility variables + + const worldPosition = new THREE.Vector3(); + const worldPositionStart = new THREE.Vector3(); + const worldQuaternion = new THREE.Quaternion(); + const worldQuaternionStart = new THREE.Quaternion(); + const cameraPosition = new THREE.Vector3(); + const cameraQuaternion = new THREE.Quaternion(); + const pointStart = new THREE.Vector3(); + const pointEnd = new THREE.Vector3(); + const rotationAxis = new THREE.Vector3(); + const rotationAngle = 0; + const eye = new THREE.Vector3(); // TODO: remove properties unused in plane and gizmo + + defineProperty( 'worldPosition', worldPosition ); + defineProperty( 'worldPositionStart', worldPositionStart ); + defineProperty( 'worldQuaternion', worldQuaternion ); + defineProperty( 'worldQuaternionStart', worldQuaternionStart ); + defineProperty( 'cameraPosition', cameraPosition ); + defineProperty( 'cameraQuaternion', cameraQuaternion ); + defineProperty( 'pointStart', pointStart ); + defineProperty( 'pointEnd', pointEnd ); + defineProperty( 'rotationAxis', rotationAxis ); + defineProperty( 'rotationAngle', rotationAngle ); + defineProperty( 'eye', eye ); + this._offset = new THREE.Vector3(); + this._startNorm = new THREE.Vector3(); + this._endNorm = new THREE.Vector3(); + this._cameraScale = new THREE.Vector3(); + this._parentPosition = new THREE.Vector3(); + this._parentQuaternion = new THREE.Quaternion(); + this._parentQuaternionInv = new THREE.Quaternion(); + this._parentScale = new THREE.Vector3(); + this._worldScaleStart = new THREE.Vector3(); + this._worldQuaternionInv = new THREE.Quaternion(); + this._worldScale = new THREE.Vector3(); + this._positionStart = new THREE.Vector3(); + this._quaternionStart = new THREE.Quaternion(); + this._scaleStart = new THREE.Vector3(); + this._getPointer = getPointer.bind( this ); + this._onPointerDown = onPointerDown.bind( this ); + this._onPointerHover = onPointerHover.bind( this ); + this._onPointerMove = onPointerMove.bind( this ); + this._onPointerUp = onPointerUp.bind( this ); + this.domElement.addEventListener( 'pointerdown', this._onPointerDown ); + this.domElement.addEventListener( 'pointermove', this._onPointerHover ); + this.domElement.ownerDocument.addEventListener( 'pointerup', this._onPointerUp ); + + } // updateMatrixWorld updates key transformation variables + + + updateMatrixWorld() { + + if ( this.object !== undefined ) { + + this.object.updateMatrixWorld(); + + if ( this.object.parent === null ) { + + console.error( 'TransformControls: The attached 3D object must be a part of the scene graph.' ); + + } else { + + this.object.parent.matrixWorld.decompose( this._parentPosition, this._parentQuaternion, this._parentScale ); + + } + + this.object.matrixWorld.decompose( this.worldPosition, this.worldQuaternion, this._worldScale ); + + this._parentQuaternionInv.copy( this._parentQuaternion ).invert(); + + this._worldQuaternionInv.copy( this.worldQuaternion ).invert(); + + } + + this.camera.updateMatrixWorld(); + this.camera.matrixWorld.decompose( this.cameraPosition, this.cameraQuaternion, this._cameraScale ); + this.eye.copy( this.cameraPosition ).sub( this.worldPosition ).normalize(); + super.updateMatrixWorld( this ); + + } + + pointerHover( pointer ) { + + if ( this.object === undefined || this.dragging === true ) return; + + _raycaster.setFromCamera( pointer, this.camera ); + + const intersect = intersectObjectWithRay( this._gizmo.picker[ this.mode ], _raycaster ); + + if ( intersect ) { + + this.axis = intersect.object.name; + + } else { + + this.axis = null; + + } + + } + + pointerDown( pointer ) { + + if ( this.object === undefined || this.dragging === true || pointer.button !== 0 ) return; + + if ( this.axis !== null ) { + + _raycaster.setFromCamera( pointer, this.camera ); + + const planeIntersect = intersectObjectWithRay( this._plane, _raycaster, true ); + + if ( planeIntersect ) { + + let space = this.space; + + if ( this.mode === 'scale' ) { + + space = 'local'; + + } else if ( this.axis === 'E' || this.axis === 'XYZE' || this.axis === 'XYZ' ) { + + space = 'world'; + + } + + if ( space === 'local' && this.mode === 'rotate' ) { + + const snap = this.rotationSnap; + if ( this.axis === 'X' && snap ) this.object.rotation.x = Math.round( this.object.rotation.x / snap ) * snap; + if ( this.axis === 'Y' && snap ) this.object.rotation.y = Math.round( this.object.rotation.y / snap ) * snap; + if ( this.axis === 'Z' && snap ) this.object.rotation.z = Math.round( this.object.rotation.z / snap ) * snap; + + } + + this.object.updateMatrixWorld(); + this.object.parent.updateMatrixWorld(); + + this._positionStart.copy( this.object.position ); + + this._quaternionStart.copy( this.object.quaternion ); + + this._scaleStart.copy( this.object.scale ); + + this.object.matrixWorld.decompose( this.worldPositionStart, this.worldQuaternionStart, this._worldScaleStart ); + this.pointStart.copy( planeIntersect.point ).sub( this.worldPositionStart ); + + } + + this.dragging = true; + _mouseDownEvent.mode = this.mode; + this.dispatchEvent( _mouseDownEvent ); + + } + + } + + pointerMove( pointer ) { + + const axis = this.axis; + const mode = this.mode; + const object = this.object; + let space = this.space; + + if ( mode === 'scale' ) { + + space = 'local'; + + } else if ( axis === 'E' || axis === 'XYZE' || axis === 'XYZ' ) { + + space = 'world'; + + } + + if ( object === undefined || axis === null || this.dragging === false || pointer.button !== - 1 ) return; + + _raycaster.setFromCamera( pointer, this.camera ); + + const planeIntersect = intersectObjectWithRay( this._plane, _raycaster, true ); + if ( ! planeIntersect ) return; + this.pointEnd.copy( planeIntersect.point ).sub( this.worldPositionStart ); + + if ( mode === 'translate' ) { + + // Apply translate + this._offset.copy( this.pointEnd ).sub( this.pointStart ); + + if ( space === 'local' && axis !== 'XYZ' ) { + + this._offset.applyQuaternion( this._worldQuaternionInv ); + + } + + if ( axis.indexOf( 'X' ) === - 1 ) this._offset.x = 0; + if ( axis.indexOf( 'Y' ) === - 1 ) this._offset.y = 0; + if ( axis.indexOf( 'Z' ) === - 1 ) this._offset.z = 0; + + if ( space === 'local' && axis !== 'XYZ' ) { + + this._offset.applyQuaternion( this._quaternionStart ).divide( this._parentScale ); + + } else { + + this._offset.applyQuaternion( this._parentQuaternionInv ).divide( this._parentScale ); + + } + + object.position.copy( this._offset ).add( this._positionStart ); // Apply translation snap + + if ( this.translationSnap ) { + + if ( space === 'local' ) { + + object.position.applyQuaternion( _tempQuaternion.copy( this._quaternionStart ).invert() ); + + if ( axis.search( 'X' ) !== - 1 ) { + + object.position.x = Math.round( object.position.x / this.translationSnap ) * this.translationSnap; + + } + + if ( axis.search( 'Y' ) !== - 1 ) { + + object.position.y = Math.round( object.position.y / this.translationSnap ) * this.translationSnap; + + } + + if ( axis.search( 'Z' ) !== - 1 ) { + + object.position.z = Math.round( object.position.z / this.translationSnap ) * this.translationSnap; + + } + + object.position.applyQuaternion( this._quaternionStart ); + + } + + if ( space === 'world' ) { + + if ( object.parent ) { + + object.position.add( _tempVector.setFromMatrixPosition( object.parent.matrixWorld ) ); + + } + + if ( axis.search( 'X' ) !== - 1 ) { + + object.position.x = Math.round( object.position.x / this.translationSnap ) * this.translationSnap; + + } + + if ( axis.search( 'Y' ) !== - 1 ) { + + object.position.y = Math.round( object.position.y / this.translationSnap ) * this.translationSnap; + + } + + if ( axis.search( 'Z' ) !== - 1 ) { + + object.position.z = Math.round( object.position.z / this.translationSnap ) * this.translationSnap; + + } + + if ( object.parent ) { + + object.position.sub( _tempVector.setFromMatrixPosition( object.parent.matrixWorld ) ); + + } + + } + + } + + } else if ( mode === 'scale' ) { + + if ( axis.search( 'XYZ' ) !== - 1 ) { + + let d = this.pointEnd.length() / this.pointStart.length(); + if ( this.pointEnd.dot( this.pointStart ) < 0 ) d *= - 1; + + _tempVector2.set( d, d, d ); + + } else { + + _tempVector.copy( this.pointStart ); + + _tempVector2.copy( this.pointEnd ); + + _tempVector.applyQuaternion( this._worldQuaternionInv ); + + _tempVector2.applyQuaternion( this._worldQuaternionInv ); + + _tempVector2.divide( _tempVector ); + + if ( axis.search( 'X' ) === - 1 ) { + + _tempVector2.x = 1; + + } + + if ( axis.search( 'Y' ) === - 1 ) { + + _tempVector2.y = 1; + + } + + if ( axis.search( 'Z' ) === - 1 ) { + + _tempVector2.z = 1; + + } + + } // Apply scale + + + object.scale.copy( this._scaleStart ).multiply( _tempVector2 ); + + if ( this.scaleSnap ) { + + if ( axis.search( 'X' ) !== - 1 ) { + + object.scale.x = Math.round( object.scale.x / this.scaleSnap ) * this.scaleSnap || this.scaleSnap; + + } + + if ( axis.search( 'Y' ) !== - 1 ) { + + object.scale.y = Math.round( object.scale.y / this.scaleSnap ) * this.scaleSnap || this.scaleSnap; + + } + + if ( axis.search( 'Z' ) !== - 1 ) { + + object.scale.z = Math.round( object.scale.z / this.scaleSnap ) * this.scaleSnap || this.scaleSnap; + + } + + } + + } else if ( mode === 'rotate' ) { + + this._offset.copy( this.pointEnd ).sub( this.pointStart ); + + const ROTATION_SPEED = 20 / this.worldPosition.distanceTo( _tempVector.setFromMatrixPosition( this.camera.matrixWorld ) ); + + if ( axis === 'E' ) { + + this.rotationAxis.copy( this.eye ); + this.rotationAngle = this.pointEnd.angleTo( this.pointStart ); + + this._startNorm.copy( this.pointStart ).normalize(); + + this._endNorm.copy( this.pointEnd ).normalize(); + + this.rotationAngle *= this._endNorm.cross( this._startNorm ).dot( this.eye ) < 0 ? 1 : - 1; + + } else if ( axis === 'XYZE' ) { + + this.rotationAxis.copy( this._offset ).cross( this.eye ).normalize(); + this.rotationAngle = this._offset.dot( _tempVector.copy( this.rotationAxis ).cross( this.eye ) ) * ROTATION_SPEED; + + } else if ( axis === 'X' || axis === 'Y' || axis === 'Z' ) { + + this.rotationAxis.copy( _unit[ axis ] ); + + _tempVector.copy( _unit[ axis ] ); + + if ( space === 'local' ) { + + _tempVector.applyQuaternion( this.worldQuaternion ); + + } + + this.rotationAngle = this._offset.dot( _tempVector.cross( this.eye ).normalize() ) * ROTATION_SPEED; + + } // Apply rotation snap + + + if ( this.rotationSnap ) this.rotationAngle = Math.round( this.rotationAngle / this.rotationSnap ) * this.rotationSnap; // Apply rotate + + if ( space === 'local' && axis !== 'E' && axis !== 'XYZE' ) { + + object.quaternion.copy( this._quaternionStart ); + object.quaternion.multiply( _tempQuaternion.setFromAxisAngle( this.rotationAxis, this.rotationAngle ) ).normalize(); + + } else { + + this.rotationAxis.applyQuaternion( this._parentQuaternionInv ); + object.quaternion.copy( _tempQuaternion.setFromAxisAngle( this.rotationAxis, this.rotationAngle ) ); + object.quaternion.multiply( this._quaternionStart ).normalize(); + + } + + } + + this.dispatchEvent( _changeEvent ); + this.dispatchEvent( _objectChangeEvent ); + + } + + pointerUp( pointer ) { + + if ( pointer.button !== 0 ) return; + + if ( this.dragging && this.axis !== null ) { + + _mouseUpEvent.mode = this.mode; + this.dispatchEvent( _mouseUpEvent ); + + } + + this.dragging = false; + this.axis = null; + + } + + dispose() { + + this.domElement.removeEventListener( 'pointerdown', this._onPointerDown ); + this.domElement.removeEventListener( 'pointermove', this._onPointerHover ); + this.domElement.ownerDocument.removeEventListener( 'pointermove', this._onPointerMove ); + this.domElement.ownerDocument.removeEventListener( 'pointerup', this._onPointerUp ); + this.traverse( function ( child ) { + + if ( child.geometry ) child.geometry.dispose(); + if ( child.material ) child.material.dispose(); + + } ); + + } // Set current object + + + attach( object ) { + + this.object = object; + this.visible = true; + return this; + + } // Detatch from object + + + detach() { + + this.object = undefined; + this.visible = false; + this.axis = null; + return this; + + } // TODO: deprecate + + + getMode() { + + return this.mode; + + } + + setMode( mode ) { + + this.mode = mode; + + } + + setTranslationSnap( translationSnap ) { + + this.translationSnap = translationSnap; + + } + + setRotationSnap( rotationSnap ) { + + this.rotationSnap = rotationSnap; + + } + + setScaleSnap( scaleSnap ) { + + this.scaleSnap = scaleSnap; + + } + + setSize( size ) { + + this.size = size; + + } + + setSpace( space ) { + + this.space = space; + + } + + update() { + + console.warn( 'THREE.TransformControls: update function has no more functionality and therefore has been deprecated.' ); + + } + + } + + TransformControls.prototype.isTransformControls = true; // mouse / touch event handlers + + function getPointer( event ) { + + if ( this.domElement.ownerDocument.pointerLockElement ) { + + return { + x: 0, + y: 0, + button: event.button + }; + + } else { + + const pointer = event.changedTouches ? event.changedTouches[ 0 ] : event; + const rect = this.domElement.getBoundingClientRect(); + return { + x: ( pointer.clientX - rect.left ) / rect.width * 2 - 1, + y: - ( pointer.clientY - rect.top ) / rect.height * 2 + 1, + button: event.button + }; + + } + + } + + function onPointerHover( event ) { + + if ( ! this.enabled ) return; + + switch ( event.pointerType ) { + + case 'mouse': + case 'pen': + this.pointerHover( this._getPointer( event ) ); + break; + + } + + } + + function onPointerDown( event ) { + + if ( ! this.enabled ) return; + this.domElement.style.touchAction = 'none'; // disable touch scroll + + this.domElement.ownerDocument.addEventListener( 'pointermove', this._onPointerMove ); + this.pointerHover( this._getPointer( event ) ); + this.pointerDown( this._getPointer( event ) ); + + } + + function onPointerMove( event ) { + + if ( ! this.enabled ) return; + this.pointerMove( this._getPointer( event ) ); + + } + + function onPointerUp( event ) { + + if ( ! this.enabled ) return; + this.domElement.style.touchAction = ''; + this.domElement.ownerDocument.removeEventListener( 'pointermove', this._onPointerMove ); + this.pointerUp( this._getPointer( event ) ); + + } + + function intersectObjectWithRay( object, raycaster, includeInvisible ) { + + const allIntersections = raycaster.intersectObject( object, true ); + + for ( let i = 0; i < allIntersections.length; i ++ ) { + + if ( allIntersections[ i ].object.visible || includeInvisible ) { + + return allIntersections[ i ]; + + } + + } + + return false; + + } // + // Reusable utility variables + + + const _tempEuler = new THREE.Euler(); + + const _alignVector = new THREE.Vector3( 0, 1, 0 ); + + const _zeroVector = new THREE.Vector3( 0, 0, 0 ); + + const _lookAtMatrix = new THREE.Matrix4(); + + const _tempQuaternion2 = new THREE.Quaternion(); + + const _identityQuaternion = new THREE.Quaternion(); + + const _dirVector = new THREE.Vector3(); + + const _tempMatrix = new THREE.Matrix4(); + + const _unitX = new THREE.Vector3( 1, 0, 0 ); + + const _unitY = new THREE.Vector3( 0, 1, 0 ); + + const _unitZ = new THREE.Vector3( 0, 0, 1 ); + + const _v1 = new THREE.Vector3(); + + const _v2 = new THREE.Vector3(); + + const _v3 = new THREE.Vector3(); + + class TransformControlsGizmo extends THREE.Object3D { + + constructor() { + + super(); + this.type = 'TransformControlsGizmo'; // shared materials + + const gizmoMaterial = new THREE.MeshBasicMaterial( { + depthTest: false, + depthWrite: false, + transparent: true, + side: THREE.DoubleSide, + fog: false, + toneMapped: false + } ); + const gizmoLineMaterial = new THREE.LineBasicMaterial( { + depthTest: false, + depthWrite: false, + transparent: true, + linewidth: 1, + fog: false, + toneMapped: false + } ); // Make unique material for each axis/color + + const matInvisible = gizmoMaterial.clone(); + matInvisible.opacity = 0.15; + const matHelper = gizmoMaterial.clone(); + matHelper.opacity = 0.33; + const matRed = gizmoMaterial.clone(); + matRed.color.set( 0xff0000 ); + const matGreen = gizmoMaterial.clone(); + matGreen.color.set( 0x00ff00 ); + const matBlue = gizmoMaterial.clone(); + matBlue.color.set( 0x0000ff ); + const matWhiteTransparent = gizmoMaterial.clone(); + matWhiteTransparent.opacity = 0.25; + const matYellowTransparent = matWhiteTransparent.clone(); + matYellowTransparent.color.set( 0xffff00 ); + const matCyanTransparent = matWhiteTransparent.clone(); + matCyanTransparent.color.set( 0x00ffff ); + const matMagentaTransparent = matWhiteTransparent.clone(); + matMagentaTransparent.color.set( 0xff00ff ); + const matYellow = gizmoMaterial.clone(); + matYellow.color.set( 0xffff00 ); + const matLineRed = gizmoLineMaterial.clone(); + matLineRed.color.set( 0xff0000 ); + const matLineGreen = gizmoLineMaterial.clone(); + matLineGreen.color.set( 0x00ff00 ); + const matLineBlue = gizmoLineMaterial.clone(); + matLineBlue.color.set( 0x0000ff ); + const matLineCyan = gizmoLineMaterial.clone(); + matLineCyan.color.set( 0x00ffff ); + const matLineMagenta = gizmoLineMaterial.clone(); + matLineMagenta.color.set( 0xff00ff ); + const matLineYellow = gizmoLineMaterial.clone(); + matLineYellow.color.set( 0xffff00 ); + const matLineGray = gizmoLineMaterial.clone(); + matLineGray.color.set( 0x787878 ); + const matLineYellowTransparent = matLineYellow.clone(); + matLineYellowTransparent.opacity = 0.25; // reusable geometry + + const arrowGeometry = new THREE.CylinderGeometry( 0, 0.05, 0.2, 12, 1, false ); + const scaleHandleGeometry = new THREE.BoxGeometry( 0.125, 0.125, 0.125 ); + const lineGeometry = new THREE.BufferGeometry(); + lineGeometry.setAttribute( 'position', new THREE.Float32BufferAttribute( [ 0, 0, 0, 1, 0, 0 ], 3 ) ); + + function CircleGeometry( radius, arc ) { + + const geometry = new THREE.BufferGeometry(); + const vertices = []; + + for ( let i = 0; i <= 64 * arc; ++ i ) { + + vertices.push( 0, Math.cos( i / 32 * Math.PI ) * radius, Math.sin( i / 32 * Math.PI ) * radius ); + + } + + geometry.setAttribute( 'position', new THREE.Float32BufferAttribute( vertices, 3 ) ); + return geometry; + + } // Special geometry for transform helper. If scaled with position vector it spans from [0,0,0] to position + + + function TranslateHelperGeometry() { + + const geometry = new THREE.BufferGeometry(); + geometry.setAttribute( 'position', new THREE.Float32BufferAttribute( [ 0, 0, 0, 1, 1, 1 ], 3 ) ); + return geometry; + + } // Gizmo definitions - custom hierarchy definitions for setupGizmo() function + + + const gizmoTranslate = { + X: [[ new THREE.Mesh( arrowGeometry, matRed ), [ 1, 0, 0 ], [ 0, 0, - Math.PI / 2 ], null, 'fwd' ], [ new THREE.Mesh( arrowGeometry, matRed ), [ 1, 0, 0 ], [ 0, 0, Math.PI / 2 ], null, 'bwd' ], [ new THREE.Line( lineGeometry, matLineRed ) ]], + Y: [[ new THREE.Mesh( arrowGeometry, matGreen ), [ 0, 1, 0 ], null, null, 'fwd' ], [ new THREE.Mesh( arrowGeometry, matGreen ), [ 0, 1, 0 ], [ Math.PI, 0, 0 ], null, 'bwd' ], [ new THREE.Line( lineGeometry, matLineGreen ), null, [ 0, 0, Math.PI / 2 ]]], + Z: [[ new THREE.Mesh( arrowGeometry, matBlue ), [ 0, 0, 1 ], [ Math.PI / 2, 0, 0 ], null, 'fwd' ], [ new THREE.Mesh( arrowGeometry, matBlue ), [ 0, 0, 1 ], [ - Math.PI / 2, 0, 0 ], null, 'bwd' ], [ new THREE.Line( lineGeometry, matLineBlue ), null, [ 0, - Math.PI / 2, 0 ]]], + XYZ: [[ new THREE.Mesh( new THREE.OctahedronGeometry( 0.1, 0 ), matWhiteTransparent.clone() ), [ 0, 0, 0 ], [ 0, 0, 0 ]]], + XY: [[ new THREE.Mesh( new THREE.PlaneGeometry( 0.295, 0.295 ), matYellowTransparent.clone() ), [ 0.15, 0.15, 0 ]], [ new THREE.Line( lineGeometry, matLineYellow ), [ 0.18, 0.3, 0 ], null, [ 0.125, 1, 1 ]], [ new THREE.Line( lineGeometry, matLineYellow ), [ 0.3, 0.18, 0 ], [ 0, 0, Math.PI / 2 ], [ 0.125, 1, 1 ]]], + YZ: [[ new THREE.Mesh( new THREE.PlaneGeometry( 0.295, 0.295 ), matCyanTransparent.clone() ), [ 0, 0.15, 0.15 ], [ 0, Math.PI / 2, 0 ]], [ new THREE.Line( lineGeometry, matLineCyan ), [ 0, 0.18, 0.3 ], [ 0, 0, Math.PI / 2 ], [ 0.125, 1, 1 ]], [ new THREE.Line( lineGeometry, matLineCyan ), [ 0, 0.3, 0.18 ], [ 0, - Math.PI / 2, 0 ], [ 0.125, 1, 1 ]]], + XZ: [[ new THREE.Mesh( new THREE.PlaneGeometry( 0.295, 0.295 ), matMagentaTransparent.clone() ), [ 0.15, 0, 0.15 ], [ - Math.PI / 2, 0, 0 ]], [ new THREE.Line( lineGeometry, matLineMagenta ), [ 0.18, 0, 0.3 ], null, [ 0.125, 1, 1 ]], [ new THREE.Line( lineGeometry, matLineMagenta ), [ 0.3, 0, 0.18 ], [ 0, - Math.PI / 2, 0 ], [ 0.125, 1, 1 ]]] + }; + const pickerTranslate = { + X: [[ new THREE.Mesh( new THREE.CylinderGeometry( 0.2, 0, 1, 4, 1, false ), matInvisible ), [ 0.6, 0, 0 ], [ 0, 0, - Math.PI / 2 ]]], + Y: [[ new THREE.Mesh( new THREE.CylinderGeometry( 0.2, 0, 1, 4, 1, false ), matInvisible ), [ 0, 0.6, 0 ]]], + Z: [[ new THREE.Mesh( new THREE.CylinderGeometry( 0.2, 0, 1, 4, 1, false ), matInvisible ), [ 0, 0, 0.6 ], [ Math.PI / 2, 0, 0 ]]], + XYZ: [[ new THREE.Mesh( new THREE.OctahedronGeometry( 0.2, 0 ), matInvisible ) ]], + XY: [[ new THREE.Mesh( new THREE.PlaneGeometry( 0.4, 0.4 ), matInvisible ), [ 0.2, 0.2, 0 ]]], + YZ: [[ new THREE.Mesh( new THREE.PlaneGeometry( 0.4, 0.4 ), matInvisible ), [ 0, 0.2, 0.2 ], [ 0, Math.PI / 2, 0 ]]], + XZ: [[ new THREE.Mesh( new THREE.PlaneGeometry( 0.4, 0.4 ), matInvisible ), [ 0.2, 0, 0.2 ], [ - Math.PI / 2, 0, 0 ]]] + }; + const helperTranslate = { + START: [[ new THREE.Mesh( new THREE.OctahedronGeometry( 0.01, 2 ), matHelper ), null, null, null, 'helper' ]], + END: [[ new THREE.Mesh( new THREE.OctahedronGeometry( 0.01, 2 ), matHelper ), null, null, null, 'helper' ]], + DELTA: [[ new THREE.Line( TranslateHelperGeometry(), matHelper ), null, null, null, 'helper' ]], + X: [[ new THREE.Line( lineGeometry, matHelper.clone() ), [ - 1e3, 0, 0 ], null, [ 1e6, 1, 1 ], 'helper' ]], + Y: [[ new THREE.Line( lineGeometry, matHelper.clone() ), [ 0, - 1e3, 0 ], [ 0, 0, Math.PI / 2 ], [ 1e6, 1, 1 ], 'helper' ]], + Z: [[ new THREE.Line( lineGeometry, matHelper.clone() ), [ 0, 0, - 1e3 ], [ 0, - Math.PI / 2, 0 ], [ 1e6, 1, 1 ], 'helper' ]] + }; + const gizmoRotate = { + X: [[ new THREE.Line( CircleGeometry( 1, 0.5 ), matLineRed ) ], [ new THREE.Mesh( new THREE.OctahedronGeometry( 0.04, 0 ), matRed ), [ 0, 0, 0.99 ], null, [ 1, 3, 1 ]]], + Y: [[ new THREE.Line( CircleGeometry( 1, 0.5 ), matLineGreen ), null, [ 0, 0, - Math.PI / 2 ]], [ new THREE.Mesh( new THREE.OctahedronGeometry( 0.04, 0 ), matGreen ), [ 0, 0, 0.99 ], null, [ 3, 1, 1 ]]], + Z: [[ new THREE.Line( CircleGeometry( 1, 0.5 ), matLineBlue ), null, [ 0, Math.PI / 2, 0 ]], [ new THREE.Mesh( new THREE.OctahedronGeometry( 0.04, 0 ), matBlue ), [ 0.99, 0, 0 ], null, [ 1, 3, 1 ]]], + E: [[ new THREE.Line( CircleGeometry( 1.25, 1 ), matLineYellowTransparent ), null, [ 0, Math.PI / 2, 0 ]], [ new THREE.Mesh( new THREE.CylinderGeometry( 0.03, 0, 0.15, 4, 1, false ), matLineYellowTransparent ), [ 1.17, 0, 0 ], [ 0, 0, - Math.PI / 2 ], [ 1, 1, 0.001 ]], [ new THREE.Mesh( new THREE.CylinderGeometry( 0.03, 0, 0.15, 4, 1, false ), matLineYellowTransparent ), [ - 1.17, 0, 0 ], [ 0, 0, Math.PI / 2 ], [ 1, 1, 0.001 ]], [ new THREE.Mesh( new THREE.CylinderGeometry( 0.03, 0, 0.15, 4, 1, false ), matLineYellowTransparent ), [ 0, - 1.17, 0 ], [ Math.PI, 0, 0 ], [ 1, 1, 0.001 ]], [ new THREE.Mesh( new THREE.CylinderGeometry( 0.03, 0, 0.15, 4, 1, false ), matLineYellowTransparent ), [ 0, 1.17, 0 ], [ 0, 0, 0 ], [ 1, 1, 0.001 ]]], + XYZE: [[ new THREE.Line( CircleGeometry( 1, 1 ), matLineGray ), null, [ 0, Math.PI / 2, 0 ]]] + }; + const helperRotate = { + AXIS: [[ new THREE.Line( lineGeometry, matHelper.clone() ), [ - 1e3, 0, 0 ], null, [ 1e6, 1, 1 ], 'helper' ]] + }; + const pickerRotate = { + X: [[ new THREE.Mesh( new THREE.TorusGeometry( 1, 0.1, 4, 24 ), matInvisible ), [ 0, 0, 0 ], [ 0, - Math.PI / 2, - Math.PI / 2 ]]], + Y: [[ new THREE.Mesh( new THREE.TorusGeometry( 1, 0.1, 4, 24 ), matInvisible ), [ 0, 0, 0 ], [ Math.PI / 2, 0, 0 ]]], + Z: [[ new THREE.Mesh( new THREE.TorusGeometry( 1, 0.1, 4, 24 ), matInvisible ), [ 0, 0, 0 ], [ 0, 0, - Math.PI / 2 ]]], + E: [[ new THREE.Mesh( new THREE.TorusGeometry( 1.25, 0.1, 2, 24 ), matInvisible ) ]], + XYZE: [[ new THREE.Mesh( new THREE.SphereGeometry( 0.7, 10, 8 ), matInvisible ) ]] + }; + const gizmoScale = { + X: [[ new THREE.Mesh( scaleHandleGeometry, matRed ), [ 0.8, 0, 0 ], [ 0, 0, - Math.PI / 2 ]], [ new THREE.Line( lineGeometry, matLineRed ), null, null, [ 0.8, 1, 1 ]]], + Y: [[ new THREE.Mesh( scaleHandleGeometry, matGreen ), [ 0, 0.8, 0 ]], [ new THREE.Line( lineGeometry, matLineGreen ), null, [ 0, 0, Math.PI / 2 ], [ 0.8, 1, 1 ]]], + Z: [[ new THREE.Mesh( scaleHandleGeometry, matBlue ), [ 0, 0, 0.8 ], [ Math.PI / 2, 0, 0 ]], [ new THREE.Line( lineGeometry, matLineBlue ), null, [ 0, - Math.PI / 2, 0 ], [ 0.8, 1, 1 ]]], + XY: [[ new THREE.Mesh( scaleHandleGeometry, matYellowTransparent ), [ 0.85, 0.85, 0 ], null, [ 2, 2, 0.2 ]], [ new THREE.Line( lineGeometry, matLineYellow ), [ 0.855, 0.98, 0 ], null, [ 0.125, 1, 1 ]], [ new THREE.Line( lineGeometry, matLineYellow ), [ 0.98, 0.855, 0 ], [ 0, 0, Math.PI / 2 ], [ 0.125, 1, 1 ]]], + YZ: [[ new THREE.Mesh( scaleHandleGeometry, matCyanTransparent ), [ 0, 0.85, 0.85 ], null, [ 0.2, 2, 2 ]], [ new THREE.Line( lineGeometry, matLineCyan ), [ 0, 0.855, 0.98 ], [ 0, 0, Math.PI / 2 ], [ 0.125, 1, 1 ]], [ new THREE.Line( lineGeometry, matLineCyan ), [ 0, 0.98, 0.855 ], [ 0, - Math.PI / 2, 0 ], [ 0.125, 1, 1 ]]], + XZ: [[ new THREE.Mesh( scaleHandleGeometry, matMagentaTransparent ), [ 0.85, 0, 0.85 ], null, [ 2, 0.2, 2 ]], [ new THREE.Line( lineGeometry, matLineMagenta ), [ 0.855, 0, 0.98 ], null, [ 0.125, 1, 1 ]], [ new THREE.Line( lineGeometry, matLineMagenta ), [ 0.98, 0, 0.855 ], [ 0, - Math.PI / 2, 0 ], [ 0.125, 1, 1 ]]], + XYZX: [[ new THREE.Mesh( new THREE.BoxGeometry( 0.125, 0.125, 0.125 ), matWhiteTransparent.clone() ), [ 1.1, 0, 0 ]]], + XYZY: [[ new THREE.Mesh( new THREE.BoxGeometry( 0.125, 0.125, 0.125 ), matWhiteTransparent.clone() ), [ 0, 1.1, 0 ]]], + XYZZ: [[ new THREE.Mesh( new THREE.BoxGeometry( 0.125, 0.125, 0.125 ), matWhiteTransparent.clone() ), [ 0, 0, 1.1 ]]] + }; + const pickerScale = { + X: [[ new THREE.Mesh( new THREE.CylinderGeometry( 0.2, 0, 0.8, 4, 1, false ), matInvisible ), [ 0.5, 0, 0 ], [ 0, 0, - Math.PI / 2 ]]], + Y: [[ new THREE.Mesh( new THREE.CylinderGeometry( 0.2, 0, 0.8, 4, 1, false ), matInvisible ), [ 0, 0.5, 0 ]]], + Z: [[ new THREE.Mesh( new THREE.CylinderGeometry( 0.2, 0, 0.8, 4, 1, false ), matInvisible ), [ 0, 0, 0.5 ], [ Math.PI / 2, 0, 0 ]]], + XY: [[ new THREE.Mesh( scaleHandleGeometry, matInvisible ), [ 0.85, 0.85, 0 ], null, [ 3, 3, 0.2 ]]], + YZ: [[ new THREE.Mesh( scaleHandleGeometry, matInvisible ), [ 0, 0.85, 0.85 ], null, [ 0.2, 3, 3 ]]], + XZ: [[ new THREE.Mesh( scaleHandleGeometry, matInvisible ), [ 0.85, 0, 0.85 ], null, [ 3, 0.2, 3 ]]], + XYZX: [[ new THREE.Mesh( new THREE.BoxGeometry( 0.2, 0.2, 0.2 ), matInvisible ), [ 1.1, 0, 0 ]]], + XYZY: [[ new THREE.Mesh( new THREE.BoxGeometry( 0.2, 0.2, 0.2 ), matInvisible ), [ 0, 1.1, 0 ]]], + XYZZ: [[ new THREE.Mesh( new THREE.BoxGeometry( 0.2, 0.2, 0.2 ), matInvisible ), [ 0, 0, 1.1 ]]] + }; + const helperScale = { + X: [[ new THREE.Line( lineGeometry, matHelper.clone() ), [ - 1e3, 0, 0 ], null, [ 1e6, 1, 1 ], 'helper' ]], + Y: [[ new THREE.Line( lineGeometry, matHelper.clone() ), [ 0, - 1e3, 0 ], [ 0, 0, Math.PI / 2 ], [ 1e6, 1, 1 ], 'helper' ]], + Z: [[ new THREE.Line( lineGeometry, matHelper.clone() ), [ 0, 0, - 1e3 ], [ 0, - Math.PI / 2, 0 ], [ 1e6, 1, 1 ], 'helper' ]] + }; // Creates an THREE.Object3D with gizmos described in custom hierarchy definition. + + function setupGizmo( gizmoMap ) { + + const gizmo = new THREE.Object3D(); + + for ( const name in gizmoMap ) { + + for ( let i = gizmoMap[ name ].length; i --; ) { + + const object = gizmoMap[ name ][ i ][ 0 ].clone(); + const position = gizmoMap[ name ][ i ][ 1 ]; + const rotation = gizmoMap[ name ][ i ][ 2 ]; + const scale = gizmoMap[ name ][ i ][ 3 ]; + const tag = gizmoMap[ name ][ i ][ 4 ]; // name and tag properties are essential for picking and updating logic. + + object.name = name; + object.tag = tag; + + if ( position ) { + + object.position.set( position[ 0 ], position[ 1 ], position[ 2 ] ); + + } + + if ( rotation ) { + + object.rotation.set( rotation[ 0 ], rotation[ 1 ], rotation[ 2 ] ); + + } + + if ( scale ) { + + object.scale.set( scale[ 0 ], scale[ 1 ], scale[ 2 ] ); + + } + + object.updateMatrix(); + const tempGeometry = object.geometry.clone(); + tempGeometry.applyMatrix4( object.matrix ); + object.geometry = tempGeometry; + object.renderOrder = Infinity; + object.position.set( 0, 0, 0 ); + object.rotation.set( 0, 0, 0 ); + object.scale.set( 1, 1, 1 ); + gizmo.add( object ); + + } + + } + + return gizmo; + + } // Gizmo creation + + + this.gizmo = {}; + this.picker = {}; + this.helper = {}; + this.add( this.gizmo[ 'translate' ] = setupGizmo( gizmoTranslate ) ); + this.add( this.gizmo[ 'rotate' ] = setupGizmo( gizmoRotate ) ); + this.add( this.gizmo[ 'scale' ] = setupGizmo( gizmoScale ) ); + this.add( this.picker[ 'translate' ] = setupGizmo( pickerTranslate ) ); + this.add( this.picker[ 'rotate' ] = setupGizmo( pickerRotate ) ); + this.add( this.picker[ 'scale' ] = setupGizmo( pickerScale ) ); + this.add( this.helper[ 'translate' ] = setupGizmo( helperTranslate ) ); + this.add( this.helper[ 'rotate' ] = setupGizmo( helperRotate ) ); + this.add( this.helper[ 'scale' ] = setupGizmo( helperScale ) ); // Pickers should be hidden always + + this.picker[ 'translate' ].visible = false; + this.picker[ 'rotate' ].visible = false; + this.picker[ 'scale' ].visible = false; + + } // updateMatrixWorld will update transformations and appearance of individual handles + + + updateMatrixWorld( force ) { + + const space = this.mode === 'scale' ? this.space : 'local'; // scale always oriented to local rotation + + const quaternion = space === 'local' ? this.worldQuaternion : _identityQuaternion; // Show only gizmos for current transform mode + + this.gizmo[ 'translate' ].visible = this.mode === 'translate'; + this.gizmo[ 'rotate' ].visible = this.mode === 'rotate'; + this.gizmo[ 'scale' ].visible = this.mode === 'scale'; + this.helper[ 'translate' ].visible = this.mode === 'translate'; + this.helper[ 'rotate' ].visible = this.mode === 'rotate'; + this.helper[ 'scale' ].visible = this.mode === 'scale'; + 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.rotation.set( 0, 0, 0 ); + handle.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 ); + + } + + handle.scale.set( 1, 1, 1 ).multiplyScalar( factor * this.size / 7 ); // TODO: simplify helpers and consider decoupling from gizmo + + if ( handle.tag === 'helper' ) { + + handle.visible = false; + + if ( handle.name === 'AXIS' ) { + + handle.position.copy( this.worldPositionStart ); + handle.visible = !! this.axis; + + if ( this.axis === 'X' ) { + + _tempQuaternion.setFromEuler( _tempEuler.set( 0, 0, 0 ) ); + + handle.quaternion.copy( quaternion ).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.quaternion.copy( quaternion ).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.quaternion.copy( quaternion ).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.quaternion.setFromRotationMatrix( _lookAtMatrix.lookAt( _zeroVector, _alignVector, _unitY ) ); + handle.quaternion.multiply( _tempQuaternion ); + handle.visible = this.dragging; + + } + + if ( this.axis === 'E' ) { + + handle.visible = false; + + } + + } else if ( handle.name === 'START' ) { + + handle.position.copy( this.worldPositionStart ); + handle.visible = this.dragging; + + } else if ( handle.name === 'END' ) { + + handle.position.copy( this.worldPosition ); + handle.visible = this.dragging; + + } else if ( handle.name === 'DELTA' ) { + + handle.position.copy( this.worldPositionStart ); + handle.quaternion.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.scale.copy( _tempVector ); + handle.visible = this.dragging; + + } else { + + handle.quaternion.copy( quaternion ); + + if ( this.dragging ) { + + handle.position.copy( this.worldPositionStart ); + + } else { + + handle.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.quaternion.copy( quaternion ); + + if ( this.mode === 'translate' || this.mode === 'scale' ) { + + // Hide translate and scale axis facing the camera + const AXIS_HIDE_TRESHOLD = 0.99; + const PLANE_HIDE_TRESHOLD = 0.2; + const AXIS_FLIP_TRESHOLD = 0.0; + + if ( handle.name === 'X' || handle.name === 'XYZX' ) { + + if ( Math.abs( _alignVector.copy( _unitX ).applyQuaternion( quaternion ).dot( this.eye ) ) > AXIS_HIDE_TRESHOLD ) { + + handle.scale.set( 1e-10, 1e-10, 1e-10 ); + handle.visible = false; + + } + + } + + if ( handle.name === 'Y' || handle.name === 'XYZY' ) { + + if ( Math.abs( _alignVector.copy( _unitY ).applyQuaternion( quaternion ).dot( this.eye ) ) > AXIS_HIDE_TRESHOLD ) { + + handle.scale.set( 1e-10, 1e-10, 1e-10 ); + handle.visible = false; + + } + + } + + if ( handle.name === 'Z' || handle.name === 'XYZZ' ) { + + if ( Math.abs( _alignVector.copy( _unitZ ).applyQuaternion( quaternion ).dot( this.eye ) ) > AXIS_HIDE_TRESHOLD ) { + + handle.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.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.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.scale.set( 1e-10, 1e-10, 1e-10 ); + handle.visible = false; + + } + + } // Flip translate and scale axis ocluded behind another axis + + + if ( handle.name.search( 'X' ) !== - 1 ) { + + if ( _alignVector.copy( _unitX ).applyQuaternion( quaternion ).dot( this.eye ) < AXIS_FLIP_TRESHOLD ) { + + if ( handle.tag === 'fwd' ) { + + handle.visible = false; + + } else { + + handle.scale.x *= - 1; + + } + + } else if ( handle.tag === 'bwd' ) { + + handle.visible = false; + + } + + } + + if ( handle.name.search( 'Y' ) !== - 1 ) { + + if ( _alignVector.copy( _unitY ).applyQuaternion( quaternion ).dot( this.eye ) < AXIS_FLIP_TRESHOLD ) { + + if ( handle.tag === 'fwd' ) { + + handle.visible = false; + + } else { + + handle.scale.y *= - 1; + + } + + } else if ( handle.tag === 'bwd' ) { + + handle.visible = false; + + } + + } + + if ( handle.name.search( 'Z' ) !== - 1 ) { + + if ( _alignVector.copy( _unitZ ).applyQuaternion( quaternion ).dot( this.eye ) < AXIS_FLIP_TRESHOLD ) { + + if ( handle.tag === 'fwd' ) { + + handle.visible = false; + + } else { + + handle.scale.z *= - 1; + + } + + } else if ( handle.tag === 'bwd' ) { + + handle.visible = false; + + } + + } + + } else if ( this.mode === '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.quaternion.setFromRotationMatrix( _lookAtMatrix.lookAt( this.eye, _zeroVector, _unitY ) ); + + } + + if ( handle.name === 'X' ) { + + _tempQuaternion.setFromAxisAngle( _unitX, Math.atan2( - _alignVector.y, _alignVector.z ) ); + + _tempQuaternion.multiplyQuaternions( _tempQuaternion2, _tempQuaternion ); + + handle.quaternion.copy( _tempQuaternion ); + + } + + if ( handle.name === 'Y' ) { + + _tempQuaternion.setFromAxisAngle( _unitY, Math.atan2( _alignVector.x, _alignVector.z ) ); + + _tempQuaternion.multiplyQuaternions( _tempQuaternion2, _tempQuaternion ); + + handle.quaternion.copy( _tempQuaternion ); + + } + + if ( handle.name === 'Z' ) { + + _tempQuaternion.setFromAxisAngle( _unitZ, Math.atan2( _alignVector.y, _alignVector.x ) ); + + _tempQuaternion.multiplyQuaternions( _tempQuaternion2, _tempQuaternion ); + + handle.quaternion.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 + + handle.material._opacity = handle.material._opacity || handle.material.opacity; + handle.material._color = handle.material._color || handle.material.color.clone(); + handle.material.color.copy( handle.material._color ); + handle.material.opacity = handle.material._opacity; + + if ( ! this.enabled ) { + + handle.material.opacity *= 0.5; + handle.material.color.lerp( new THREE.Color( 1, 1, 1 ), 0.5 ); + + } else if ( this.axis ) { + + if ( handle.name === this.axis ) { + + handle.material.opacity = 1.0; + handle.material.color.lerp( new THREE.Color( 1, 1, 1 ), 0.5 ); + + } else if ( this.axis.split( '' ).some( function ( a ) { + + return handle.name === a; + + } ) ) { + + handle.material.opacity = 1.0; + handle.material.color.lerp( new THREE.Color( 1, 1, 1 ), 0.5 ); + + } else { + + handle.material.opacity *= 0.25; + handle.material.color.lerp( new THREE.Color( 1, 1, 1 ), 0.5 ); + + } + + } + + } + + super.updateMatrixWorld( force ); + + } + + } + + TransformControlsGizmo.prototype.isTransformControlsGizmo = true; // + + class TransformControlsPlane extends THREE.Mesh { + + constructor() { + + super( new THREE.PlaneGeometry( 100000, 100000, 2, 2 ), new THREE.MeshBasicMaterial( { + visible: false, + wireframe: true, + side: THREE.DoubleSide, + transparent: true, + opacity: 0.1, + toneMapped: false + } ) ); + this.type = 'TransformControlsPlane'; + + } + + updateMatrixWorld( force ) { + + let space = this.space; + this.position.copy( this.worldPosition ); + if ( this.mode === 'scale' ) space = 'local'; // scale always oriented to local rotation + + _v1.copy( _unitX ).applyQuaternion( space === 'local' ? this.worldQuaternion : _identityQuaternion ); + + _v2.copy( _unitY ).applyQuaternion( space === 'local' ? this.worldQuaternion : _identityQuaternion ); + + _v3.copy( _unitZ ).applyQuaternion( space === 'local' ? this.worldQuaternion : _identityQuaternion ); // Align the plane for current transform mode, axis and space. + + + _alignVector.copy( _v2 ); + + switch ( this.mode ) { + + case 'translate': + case 'scale': + switch ( this.axis ) { + + case 'X': + _alignVector.copy( this.eye ).cross( _v1 ); + + _dirVector.copy( _v1 ).cross( _alignVector ); + + break; + + case 'Y': + _alignVector.copy( this.eye ).cross( _v2 ); + + _dirVector.copy( _v2 ).cross( _alignVector ); + + break; + + case 'Z': + _alignVector.copy( this.eye ).cross( _v3 ); + + _dirVector.copy( _v3 ).cross( _alignVector ); + + break; + + case 'XY': + _dirVector.copy( _v3 ); + + break; + + case 'YZ': + _dirVector.copy( _v1 ); + + break; + + case 'XZ': + _alignVector.copy( _v3 ); + + _dirVector.copy( _v2 ); + + break; + + case 'XYZ': + case 'E': + _dirVector.set( 0, 0, 0 ); + + break; + + } + + break; + + case 'rotate': + default: + // special case for rotate + _dirVector.set( 0, 0, 0 ); + + } + + if ( _dirVector.length() === 0 ) { + + // If in rotate mode, make the plane parallel to camera + this.quaternion.copy( this.cameraQuaternion ); + + } else { + + _tempMatrix.lookAt( _tempVector.set( 0, 0, 0 ), _dirVector, _alignVector ); + + this.quaternion.setFromRotationMatrix( _tempMatrix ); + + } + + super.updateMatrixWorld( force ); + + } + + } + + TransformControlsPlane.prototype.isTransformControlsPlane = true; + + THREE.TransformControls = TransformControls; + THREE.TransformControlsGizmo = TransformControlsGizmo; + THREE.TransformControlsPlane = TransformControlsPlane; + +} )(); |