Skip to content

⬅️ Back to Table of Contents

📄 OrbitControls.js

📊 Analysis Summary

Metric Count
🔧 Functions 58
🧱 Classes 1
📦 Imports 10
📊 Variables & Constants 50

📚 Table of Contents

🛠️ File Location:

📂 examples/jsm/controls/OrbitControls.js

📦 Imports

Name Source
Controls three
MOUSE three
Quaternion three
Spherical three
TOUCH three
Vector2 three
Vector3 three
Plane three
Ray three
MathUtils three

Variables & Constants

Name Type Kind Value Exported
_changeEvent any let/var { type: 'change' }
_startEvent any let/var { type: 'start' }
_endEvent any let/var { type: 'end' }
_ray any let/var new Ray()
_plane any let/var new Plane()
_v any let/var new Vector3()
_twoPI number let/var 2 * Math.PI
_STATE { NONE: number; ROTATE: number; DOLLY... let/var { NONE: - 1, ROTATE: 0, DOLLY: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_PAN: 4, TOUC...
_EPS 0.000001 let/var 0.000001
position any let/var this.object.position
min number let/var this.minAzimuthAngle
max number let/var this.maxAzimuthAngle
zoomChanged boolean let/var false
prevRadius any let/var this._spherical.radius
newRadius any let/var null
radiusDelta number let/var prevRadius - newRadius
mouseBefore any let/var new Vector3( this._mouse.x, this._mouse.y, 0 )
prevZoom any let/var this.object.zoom
mouseAfter any let/var new Vector3( this._mouse.x, this._mouse.y, 0 )
prevZoom any let/var this.object.zoom
element any let/var this.domElement
position any let/var this.object.position
dx number let/var x - rect.left
dy number let/var y - rect.top
w any let/var rect.width
h any let/var rect.height
element any let/var this.domElement
needsUpdate boolean let/var false
x number let/var 0.5 * ( event.pageX + position.x )
y number let/var 0.5 * ( event.pageY + position.y )
x number let/var 0.5 * ( event.pageX + position.x )
y number let/var 0.5 * ( event.pageY + position.y )
dx number let/var event.pageX - position.x
dy number let/var event.pageY - position.y
x number let/var 0.5 * ( event.pageX + position.x )
y number let/var 0.5 * ( event.pageY + position.y )
element any let/var this.domElement
x number let/var 0.5 * ( event.pageX + position.x )
y number let/var 0.5 * ( event.pageY + position.y )
dx number let/var event.pageX - position.x
dy number let/var event.pageY - position.y
centerX number let/var ( event.pageX + position.x ) * 0.5
centerY number let/var ( event.pageY + position.y ) * 0.5
position any let/var this._pointerPositions[ event.pointerId ]
pointerId any let/var ( event.pointerId === this._pointers[ 0 ] ) ? this._pointers[ 1 ] : this._poi...
mode any let/var event.deltaMode
newEvent { clientX: any; clientY: any; deltaY:... let/var { clientX: event.clientX, clientY: event.clientY, deltaY: event.deltaY, }
pointerId any let/var this._pointers[ 0 ]
position any let/var this._pointerPositions[ pointerId ]
mouseAction any let/var *not shown*

Functions

OrbitControls.connect(element: any): void

Parameters:

  • element any

Returns: void

Calls:

  • super.connect
  • this.domElement.addEventListener
  • this.domElement.getRootNode
  • document.addEventListener
Code
connect( element ) {

        super.connect( element );

        this.domElement.addEventListener( 'pointerdown', this._onPointerDown );
        this.domElement.addEventListener( 'pointercancel', this._onPointerUp );

        this.domElement.addEventListener( 'contextmenu', this._onContextMenu );
        this.domElement.addEventListener( 'wheel', this._onMouseWheel, { passive: false } );

        const document = this.domElement.getRootNode(); // offscreen canvas compatibility
        document.addEventListener( 'keydown', this._interceptControlDown, { passive: true, capture: true } );

        this.domElement.style.touchAction = 'none'; // disable touch scroll

    }

OrbitControls.disconnect(): void

Returns: void

Calls:

  • this.domElement.removeEventListener
  • this.stopListenToKeyEvents
  • this.domElement.getRootNode
  • document.removeEventListener
Code
disconnect() {

        this.domElement.removeEventListener( 'pointerdown', this._onPointerDown );
        this.domElement.removeEventListener( 'pointermove', this._onPointerMove );
        this.domElement.removeEventListener( 'pointerup', this._onPointerUp );
        this.domElement.removeEventListener( 'pointercancel', this._onPointerUp );

        this.domElement.removeEventListener( 'wheel', this._onMouseWheel );
        this.domElement.removeEventListener( 'contextmenu', this._onContextMenu );

        this.stopListenToKeyEvents();

        const document = this.domElement.getRootNode(); // offscreen canvas compatibility
        document.removeEventListener( 'keydown', this._interceptControlDown, { capture: true } );

        this.domElement.style.touchAction = 'auto';

    }

OrbitControls.dispose(): void

Returns: void

Calls:

  • this.disconnect
Code
dispose() {

        this.disconnect();

    }

OrbitControls.getPolarAngle(): number

JSDoc:

/**
     * Get the current vertical rotation, in radians.
     *
     * @return {number} The current vertical rotation, in radians.
     */

Returns: number

Code
getPolarAngle() {

        return this._spherical.phi;

    }

OrbitControls.getAzimuthalAngle(): number

JSDoc:

/**
     * Get the current horizontal rotation, in radians.
     *
     * @return {number} The current horizontal rotation, in radians.
     */

Returns: number

Code
getAzimuthalAngle() {

        return this._spherical.theta;

    }

OrbitControls.getDistance(): number

JSDoc:

/**
     * Returns the distance from the camera to the target.
     *
     * @return {number} The distance from the camera to the target.
     */

Returns: number

Calls:

  • this.object.position.distanceTo
Code
getDistance() {

        return this.object.position.distanceTo( this.target );

    }

OrbitControls.listenToKeyEvents(domElement: HTMLDOMElement): void

JSDoc:

/**
     * Adds key event listeners to the given DOM element.
     * `window` is a recommended argument for using this method.
     *
     * @param {HTMLDOMElement} domElement - The DOM element
     */

Parameters:

  • domElement HTMLDOMElement

Returns: void

Calls:

  • domElement.addEventListener
Code
listenToKeyEvents( domElement ) {

        domElement.addEventListener( 'keydown', this._onKeyDown );
        this._domElementKeyEvents = domElement;

    }

OrbitControls.stopListenToKeyEvents(): void

JSDoc:

/**
     * Removes the key event listener previously defined with `listenToKeyEvents()`.
     */

Returns: void

Calls:

  • this._domElementKeyEvents.removeEventListener
Code
stopListenToKeyEvents() {

        if ( this._domElementKeyEvents !== null ) {

            this._domElementKeyEvents.removeEventListener( 'keydown', this._onKeyDown );
            this._domElementKeyEvents = null;

        }

    }

OrbitControls.saveState(): void

JSDoc:

/**
     * Save the current state of the controls. This can later be recovered with `reset()`.
     */

Returns: void

Calls:

  • this.target0.copy
  • this.position0.copy
Code
saveState() {

        this.target0.copy( this.target );
        this.position0.copy( this.object.position );
        this.zoom0 = this.object.zoom;

    }

OrbitControls.reset(): void

JSDoc:

/**
     * Reset the controls to their state from either the last time the `saveState()`
     * was called, or the initial state.
     */

Returns: void

Calls:

  • this.target.copy
  • this.object.position.copy
  • this.object.updateProjectionMatrix
  • this.dispatchEvent
  • this.update
Code
reset() {

        this.target.copy( this.target0 );
        this.object.position.copy( this.position0 );
        this.object.zoom = this.zoom0;

        this.object.updateProjectionMatrix();
        this.dispatchEvent( _changeEvent );

        this.update();

        this.state = _STATE.NONE;

    }

OrbitControls.update(deltaTime: any): boolean

Parameters:

  • deltaTime any

Returns: boolean

Calls:

  • _v.copy( position ).sub
  • _v.applyQuaternion
  • this._spherical.setFromVector3
  • this._rotateLeft
  • this._getAutoRotationAngle
  • isFinite
  • Math.max
  • Math.min
  • this._spherical.makeSafe
  • this.target.addScaledVector
  • this.target.add
  • this.target.sub
  • this.target.clampLength
  • this._clampDistance
  • _v.setFromSpherical
  • position.copy( this.target ).add
  • this.object.lookAt
  • this._panOffset.multiplyScalar
  • this._sphericalDelta.set
  • this._panOffset.set
  • _v.length
  • this.object.position.addScaledVector
  • this.object.updateMatrixWorld
  • mouseBefore.unproject
  • this.object.updateProjectionMatrix
  • mouseAfter.unproject
  • this.object.position.sub( mouseAfter ).add
  • console.warn
  • this.target.set( 0, 0, - 1 ) .transformDirection( this.object.matrix ) .multiplyScalar( newRadius ) .add
  • _ray.origin.copy
  • _ray.direction.set( 0, 0, - 1 ).transformDirection
  • Math.abs
  • this.object.up.dot
  • _plane.setFromNormalAndCoplanarPoint
  • _ray.intersectPlane
  • this._lastPosition.distanceToSquared
  • this._lastQuaternion.dot
  • this._lastTargetPosition.distanceToSquared
  • this.dispatchEvent
  • this._lastPosition.copy
  • this._lastQuaternion.copy
  • this._lastTargetPosition.copy

Internal Comments:

// rotate offset to "y-axis-is-up" space (x4)
// angle from z-axis around y-axis (x5)
// restrict theta to be between desired limits (x2)
// restrict phi to be between desired limits (x5)
// move target to panned location
// Limit the target distance from the cursor to create a sphere around the center of interest (x5)
// adjust the camera position based on zoom only if we're not zooming to the cursor or if it's an ortho camera
// we adjust zoom later in these cases
// rotate offset back to "camera-up-vector-is-up" space (x4)
// adjust camera position
// move the camera down the pointer ray (x2)
// this method avoids floating point error (x2)
// adjust the ortho camera position based on zoom changes (x2)
// handle the placement of the target
// position the orbit target in front of the new camera position (x11)
// get the ray and translation plane to compute target (x5)
// if the camera is 20 degrees above the horizon then don't adjust the focus target to avoid
// extremely large values
// update condition is:
// min(camera displacement, camera rotation in radians)^2 > EPS
// using small-angle approximation cos(x/2) = 1 - x^2 / 8

Code
update( deltaTime = null ) {

        const position = this.object.position;

        _v.copy( position ).sub( this.target );

        // rotate offset to "y-axis-is-up" space
        _v.applyQuaternion( this._quat );

        // angle from z-axis around y-axis
        this._spherical.setFromVector3( _v );

        if ( this.autoRotate && this.state === _STATE.NONE ) {

            this._rotateLeft( this._getAutoRotationAngle( deltaTime ) );

        }

        if ( this.enableDamping ) {

            this._spherical.theta += this._sphericalDelta.theta * this.dampingFactor;
            this._spherical.phi += this._sphericalDelta.phi * this.dampingFactor;

        } else {

            this._spherical.theta += this._sphericalDelta.theta;
            this._spherical.phi += this._sphericalDelta.phi;

        }

        // restrict theta to be between desired limits

        let min = this.minAzimuthAngle;
        let max = this.maxAzimuthAngle;

        if ( isFinite( min ) && isFinite( max ) ) {

            if ( min < - Math.PI ) min += _twoPI; else if ( min > Math.PI ) min -= _twoPI;

            if ( max < - Math.PI ) max += _twoPI; else if ( max > Math.PI ) max -= _twoPI;

            if ( min <= max ) {

                this._spherical.theta = Math.max( min, Math.min( max, this._spherical.theta ) );

            } else {

                this._spherical.theta = ( this._spherical.theta > ( min + max ) / 2 ) ?
                    Math.max( min, this._spherical.theta ) :
                    Math.min( max, this._spherical.theta );

            }

        }

        // restrict phi to be between desired limits
        this._spherical.phi = Math.max( this.minPolarAngle, Math.min( this.maxPolarAngle, this._spherical.phi ) );

        this._spherical.makeSafe();


        // move target to panned location

        if ( this.enableDamping === true ) {

            this.target.addScaledVector( this._panOffset, this.dampingFactor );

        } else {

            this.target.add( this._panOffset );

        }

        // Limit the target distance from the cursor to create a sphere around the center of interest
        this.target.sub( this.cursor );
        this.target.clampLength( this.minTargetRadius, this.maxTargetRadius );
        this.target.add( this.cursor );

        let zoomChanged = false;
        // adjust the camera position based on zoom only if we're not zooming to the cursor or if it's an ortho camera
        // we adjust zoom later in these cases
        if ( this.zoomToCursor && this._performCursorZoom || this.object.isOrthographicCamera ) {

            this._spherical.radius = this._clampDistance( this._spherical.radius );

        } else {

            const prevRadius = this._spherical.radius;
            this._spherical.radius = this._clampDistance( this._spherical.radius * this._scale );
            zoomChanged = prevRadius != this._spherical.radius;

        }

        _v.setFromSpherical( this._spherical );

        // rotate offset back to "camera-up-vector-is-up" space
        _v.applyQuaternion( this._quatInverse );

        position.copy( this.target ).add( _v );

        this.object.lookAt( this.target );

        if ( this.enableDamping === true ) {

            this._sphericalDelta.theta *= ( 1 - this.dampingFactor );
            this._sphericalDelta.phi *= ( 1 - this.dampingFactor );

            this._panOffset.multiplyScalar( 1 - this.dampingFactor );

        } else {

            this._sphericalDelta.set( 0, 0, 0 );

            this._panOffset.set( 0, 0, 0 );

        }

        // adjust camera position
        if ( this.zoomToCursor && this._performCursorZoom ) {

            let newRadius = null;
            if ( this.object.isPerspectiveCamera ) {

                // move the camera down the pointer ray
                // this method avoids floating point error
                const prevRadius = _v.length();
                newRadius = this._clampDistance( prevRadius * this._scale );

                const radiusDelta = prevRadius - newRadius;
                this.object.position.addScaledVector( this._dollyDirection, radiusDelta );
                this.object.updateMatrixWorld();

                zoomChanged = !! radiusDelta;

            } else if ( this.object.isOrthographicCamera ) {

                // adjust the ortho camera position based on zoom changes
                const mouseBefore = new Vector3( this._mouse.x, this._mouse.y, 0 );
                mouseBefore.unproject( this.object );

                const prevZoom = this.object.zoom;
                this.object.zoom = Math.max( this.minZoom, Math.min( this.maxZoom, this.object.zoom / this._scale ) );
                this.object.updateProjectionMatrix();

                zoomChanged = prevZoom !== this.object.zoom;

                const mouseAfter = new Vector3( this._mouse.x, this._mouse.y, 0 );
                mouseAfter.unproject( this.object );

                this.object.position.sub( mouseAfter ).add( mouseBefore );
                this.object.updateMatrixWorld();

                newRadius = _v.length();

            } else {

                console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - zoom to cursor disabled.' );
                this.zoomToCursor = false;

            }

            // handle the placement of the target
            if ( newRadius !== null ) {

                if ( this.screenSpacePanning ) {

                    // position the orbit target in front of the new camera position
                    this.target.set( 0, 0, - 1 )
                        .transformDirection( this.object.matrix )
                        .multiplyScalar( newRadius )
                        .add( this.object.position );

                } else {

                    // get the ray and translation plane to compute target
                    _ray.origin.copy( this.object.position );
                    _ray.direction.set( 0, 0, - 1 ).transformDirection( this.object.matrix );

                    // if the camera is 20 degrees above the horizon then don't adjust the focus target to avoid
                    // extremely large values
                    if ( Math.abs( this.object.up.dot( _ray.direction ) ) < _TILT_LIMIT ) {

                        this.object.lookAt( this.target );

                    } else {

                        _plane.setFromNormalAndCoplanarPoint( this.object.up, this.target );
                        _ray.intersectPlane( _plane, this.target );

                    }

                }

            }

        } else if ( this.object.isOrthographicCamera ) {

            const prevZoom = this.object.zoom;
            this.object.zoom = Math.max( this.minZoom, Math.min( this.maxZoom, this.object.zoom / this._scale ) );

            if ( prevZoom !== this.object.zoom ) {

                this.object.updateProjectionMatrix();
                zoomChanged = true;

            }

        }

        this._scale = 1;
        this._performCursorZoom = false;

        // update condition is:
        // min(camera displacement, camera rotation in radians)^2 > EPS
        // using small-angle approximation cos(x/2) = 1 - x^2 / 8

        if ( zoomChanged ||
            this._lastPosition.distanceToSquared( this.object.position ) > _EPS ||
            8 * ( 1 - this._lastQuaternion.dot( this.object.quaternion ) ) > _EPS ||
            this._lastTargetPosition.distanceToSquared( this.target ) > _EPS ) {

            this.dispatchEvent( _changeEvent );

            this._lastPosition.copy( this.object.position );
            this._lastQuaternion.copy( this.object.quaternion );
            this._lastTargetPosition.copy( this.target );

            return true;

        }

        return false;

    }

OrbitControls._getAutoRotationAngle(deltaTime: any): number

Parameters:

  • deltaTime any

Returns: number

Code
_getAutoRotationAngle( deltaTime ) {

        if ( deltaTime !== null ) {

            return ( _twoPI / 60 * this.autoRotateSpeed ) * deltaTime;

        } else {

            return _twoPI / 60 / 60 * this.autoRotateSpeed;

        }

    }

OrbitControls._getZoomScale(delta: any): number

Parameters:

  • delta any

Returns: number

Calls:

  • Math.abs
  • Math.pow
Code
_getZoomScale( delta ) {

        const normalizedDelta = Math.abs( delta * 0.01 );
        return Math.pow( 0.95, this.zoomSpeed * normalizedDelta );

    }

OrbitControls._rotateLeft(angle: any): void

Parameters:

  • angle any

Returns: void

Code
_rotateLeft( angle ) {

        this._sphericalDelta.theta -= angle;

    }

OrbitControls._rotateUp(angle: any): void

Parameters:

  • angle any

Returns: void

Code
_rotateUp( angle ) {

        this._sphericalDelta.phi -= angle;

    }

OrbitControls._panLeft(distance: any, objectMatrix: any): void

Parameters:

  • distance any
  • objectMatrix any

Returns: void

Calls:

  • _v.setFromMatrixColumn
  • _v.multiplyScalar
  • this._panOffset.add
Code
_panLeft( distance, objectMatrix ) {

        _v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix
        _v.multiplyScalar( - distance );

        this._panOffset.add( _v );

    }

OrbitControls._panUp(distance: any, objectMatrix: any): void

Parameters:

  • distance any
  • objectMatrix any

Returns: void

Calls:

  • _v.setFromMatrixColumn
  • _v.crossVectors
  • _v.multiplyScalar
  • this._panOffset.add
Code
_panUp( distance, objectMatrix ) {

        if ( this.screenSpacePanning === true ) {

            _v.setFromMatrixColumn( objectMatrix, 1 );

        } else {

            _v.setFromMatrixColumn( objectMatrix, 0 );
            _v.crossVectors( this.object.up, _v );

        }

        _v.multiplyScalar( distance );

        this._panOffset.add( _v );

    }

OrbitControls._pan(deltaX: any, deltaY: any): void

Parameters:

  • deltaX any
  • deltaY any

Returns: void

Calls:

  • _v.copy( position ).sub
  • _v.length
  • Math.tan
  • this._panLeft
  • this._panUp
  • console.warn

Internal Comments:

// perspective (x2)
// half of the fov is center to top of screen (x3)
// we use only clientHeight here so aspect ratio does not distort speed (x4)
// orthographic (x4)
// camera neither orthographic nor perspective (x4)

Code
_pan( deltaX, deltaY ) {

        const element = this.domElement;

        if ( this.object.isPerspectiveCamera ) {

            // perspective
            const position = this.object.position;
            _v.copy( position ).sub( this.target );
            let targetDistance = _v.length();

            // half of the fov is center to top of screen
            targetDistance *= Math.tan( ( this.object.fov / 2 ) * Math.PI / 180.0 );

            // we use only clientHeight here so aspect ratio does not distort speed
            this._panLeft( 2 * deltaX * targetDistance / element.clientHeight, this.object.matrix );
            this._panUp( 2 * deltaY * targetDistance / element.clientHeight, this.object.matrix );

        } else if ( this.object.isOrthographicCamera ) {

            // orthographic
            this._panLeft( deltaX * ( this.object.right - this.object.left ) / this.object.zoom / element.clientWidth, this.object.matrix );
            this._panUp( deltaY * ( this.object.top - this.object.bottom ) / this.object.zoom / element.clientHeight, this.object.matrix );

        } else {

            // camera neither orthographic nor perspective
            console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' );
            this.enablePan = false;

        }

    }

OrbitControls._dollyOut(dollyScale: any): void

Parameters:

  • dollyScale any

Returns: void

Calls:

  • console.warn
Code
_dollyOut( dollyScale ) {

        if ( this.object.isPerspectiveCamera || this.object.isOrthographicCamera ) {

            this._scale /= dollyScale;

        } else {

            console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' );
            this.enableZoom = false;

        }

    }

OrbitControls._dollyIn(dollyScale: any): void

Parameters:

  • dollyScale any

Returns: void

Calls:

  • console.warn
Code
_dollyIn( dollyScale ) {

        if ( this.object.isPerspectiveCamera || this.object.isOrthographicCamera ) {

            this._scale *= dollyScale;

        } else {

            console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' );
            this.enableZoom = false;

        }

    }

OrbitControls._updateZoomParameters(x: any, y: any): void

Parameters:

  • x any
  • y any

Returns: void

Calls:

  • this.domElement.getBoundingClientRect
  • this._dollyDirection.set( this._mouse.x, this._mouse.y, 1 ).unproject( this.object ).sub( this.object.position ).normalize
Code
_updateZoomParameters( x, y ) {

        if ( ! this.zoomToCursor ) {

            return;

        }

        this._performCursorZoom = true;

        const rect = this.domElement.getBoundingClientRect();
        const dx = x - rect.left;
        const dy = y - rect.top;
        const w = rect.width;
        const h = rect.height;

        this._mouse.x = ( dx / w ) * 2 - 1;
        this._mouse.y = - ( dy / h ) * 2 + 1;

        this._dollyDirection.set( this._mouse.x, this._mouse.y, 1 ).unproject( this.object ).sub( this.object.position ).normalize();

    }

OrbitControls._clampDistance(dist: any): number

Parameters:

  • dist any

Returns: number

Calls:

  • Math.max
  • Math.min
Code
_clampDistance( dist ) {

        return Math.max( this.minDistance, Math.min( this.maxDistance, dist ) );

    }

OrbitControls._handleMouseDownRotate(event: any): void

Parameters:

  • event any

Returns: void

Calls:

  • this._rotateStart.set
Code
_handleMouseDownRotate( event ) {

        this._rotateStart.set( event.clientX, event.clientY );

    }

OrbitControls._handleMouseDownDolly(event: any): void

Parameters:

  • event any

Returns: void

Calls:

  • this._updateZoomParameters
  • this._dollyStart.set
Code
_handleMouseDownDolly( event ) {

        this._updateZoomParameters( event.clientX, event.clientX );
        this._dollyStart.set( event.clientX, event.clientY );

    }

OrbitControls._handleMouseDownPan(event: any): void

Parameters:

  • event any

Returns: void

Calls:

  • this._panStart.set
Code
_handleMouseDownPan( event ) {

        this._panStart.set( event.clientX, event.clientY );

    }

OrbitControls._handleMouseMoveRotate(event: any): void

Parameters:

  • event any

Returns: void

Calls:

  • this._rotateEnd.set
  • this._rotateDelta.subVectors( this._rotateEnd, this._rotateStart ).multiplyScalar
  • this._rotateLeft
  • this._rotateUp
  • this._rotateStart.copy
  • this.update
Code
_handleMouseMoveRotate( event ) {

        this._rotateEnd.set( event.clientX, event.clientY );

        this._rotateDelta.subVectors( this._rotateEnd, this._rotateStart ).multiplyScalar( this.rotateSpeed );

        const element = this.domElement;

        this._rotateLeft( _twoPI * this._rotateDelta.x / element.clientHeight ); // yes, height

        this._rotateUp( _twoPI * this._rotateDelta.y / element.clientHeight );

        this._rotateStart.copy( this._rotateEnd );

        this.update();

    }

OrbitControls._handleMouseMoveDolly(event: any): void

Parameters:

  • event any

Returns: void

Calls:

  • this._dollyEnd.set
  • this._dollyDelta.subVectors
  • this._dollyOut
  • this._getZoomScale
  • this._dollyIn
  • this._dollyStart.copy
  • this.update
Code
_handleMouseMoveDolly( event ) {

        this._dollyEnd.set( event.clientX, event.clientY );

        this._dollyDelta.subVectors( this._dollyEnd, this._dollyStart );

        if ( this._dollyDelta.y > 0 ) {

            this._dollyOut( this._getZoomScale( this._dollyDelta.y ) );

        } else if ( this._dollyDelta.y < 0 ) {

            this._dollyIn( this._getZoomScale( this._dollyDelta.y ) );

        }

        this._dollyStart.copy( this._dollyEnd );

        this.update();

    }

OrbitControls._handleMouseMovePan(event: any): void

Parameters:

  • event any

Returns: void

Calls:

  • this._panEnd.set
  • this._panDelta.subVectors( this._panEnd, this._panStart ).multiplyScalar
  • this._pan
  • this._panStart.copy
  • this.update
Code
_handleMouseMovePan( event ) {

        this._panEnd.set( event.clientX, event.clientY );

        this._panDelta.subVectors( this._panEnd, this._panStart ).multiplyScalar( this.panSpeed );

        this._pan( this._panDelta.x, this._panDelta.y );

        this._panStart.copy( this._panEnd );

        this.update();

    }

OrbitControls._handleMouseWheel(event: any): void

Parameters:

  • event any

Returns: void

Calls:

  • this._updateZoomParameters
  • this._dollyIn
  • this._getZoomScale
  • this._dollyOut
  • this.update
Code
_handleMouseWheel( event ) {

        this._updateZoomParameters( event.clientX, event.clientY );

        if ( event.deltaY < 0 ) {

            this._dollyIn( this._getZoomScale( event.deltaY ) );

        } else if ( event.deltaY > 0 ) {

            this._dollyOut( this._getZoomScale( event.deltaY ) );

        }

        this.update();

    }

OrbitControls._handleKeyDown(event: any): void

Parameters:

  • event any

Returns: void

Calls:

  • this._rotateUp
  • this._pan
  • this._rotateLeft
  • event.preventDefault
  • this.update

Internal Comments:

// prevent the browser from scrolling on cursor keys (x4)

Code
_handleKeyDown( event ) {

        let needsUpdate = false;

        switch ( event.code ) {

            case this.keys.UP:

                if ( event.ctrlKey || event.metaKey || event.shiftKey ) {

                    if ( this.enableRotate ) {

                        this._rotateUp( _twoPI * this.keyRotateSpeed / this.domElement.clientHeight );

                    }

                } else {

                    if ( this.enablePan ) {

                        this._pan( 0, this.keyPanSpeed );

                    }

                }

                needsUpdate = true;
                break;

            case this.keys.BOTTOM:

                if ( event.ctrlKey || event.metaKey || event.shiftKey ) {

                    if ( this.enableRotate ) {

                        this._rotateUp( - _twoPI * this.keyRotateSpeed / this.domElement.clientHeight );

                    }

                } else {

                    if ( this.enablePan ) {

                        this._pan( 0, - this.keyPanSpeed );

                    }

                }

                needsUpdate = true;
                break;

            case this.keys.LEFT:

                if ( event.ctrlKey || event.metaKey || event.shiftKey ) {

                    if ( this.enableRotate ) {

                        this._rotateLeft( _twoPI * this.keyRotateSpeed / this.domElement.clientHeight );

                    }

                } else {

                    if ( this.enablePan ) {

                        this._pan( this.keyPanSpeed, 0 );

                    }

                }

                needsUpdate = true;
                break;

            case this.keys.RIGHT:

                if ( event.ctrlKey || event.metaKey || event.shiftKey ) {

                    if ( this.enableRotate ) {

                        this._rotateLeft( - _twoPI * this.keyRotateSpeed / this.domElement.clientHeight );

                    }

                } else {

                    if ( this.enablePan ) {

                        this._pan( - this.keyPanSpeed, 0 );

                    }

                }

                needsUpdate = true;
                break;

        }

        if ( needsUpdate ) {

            // prevent the browser from scrolling on cursor keys
            event.preventDefault();

            this.update();

        }


    }

OrbitControls._handleTouchStartRotate(event: any): void

Parameters:

  • event any

Returns: void

Calls:

  • this._rotateStart.set
  • this._getSecondPointerPosition
Code
_handleTouchStartRotate( event ) {

        if ( this._pointers.length === 1 ) {

            this._rotateStart.set( event.pageX, event.pageY );

        } else {

            const position = this._getSecondPointerPosition( event );

            const x = 0.5 * ( event.pageX + position.x );
            const y = 0.5 * ( event.pageY + position.y );

            this._rotateStart.set( x, y );

        }

    }

OrbitControls._handleTouchStartPan(event: any): void

Parameters:

  • event any

Returns: void

Calls:

  • this._panStart.set
  • this._getSecondPointerPosition
Code
_handleTouchStartPan( event ) {

        if ( this._pointers.length === 1 ) {

            this._panStart.set( event.pageX, event.pageY );

        } else {

            const position = this._getSecondPointerPosition( event );

            const x = 0.5 * ( event.pageX + position.x );
            const y = 0.5 * ( event.pageY + position.y );

            this._panStart.set( x, y );

        }

    }

OrbitControls._handleTouchStartDolly(event: any): void

Parameters:

  • event any

Returns: void

Calls:

  • this._getSecondPointerPosition
  • Math.sqrt
  • this._dollyStart.set
Code
_handleTouchStartDolly( event ) {

        const position = this._getSecondPointerPosition( event );

        const dx = event.pageX - position.x;
        const dy = event.pageY - position.y;

        const distance = Math.sqrt( dx * dx + dy * dy );

        this._dollyStart.set( 0, distance );

    }

OrbitControls._handleTouchStartDollyPan(event: any): void

Parameters:

  • event any

Returns: void

Calls:

  • this._handleTouchStartDolly
  • this._handleTouchStartPan
Code
_handleTouchStartDollyPan( event ) {

        if ( this.enableZoom ) this._handleTouchStartDolly( event );

        if ( this.enablePan ) this._handleTouchStartPan( event );

    }

OrbitControls._handleTouchStartDollyRotate(event: any): void

Parameters:

  • event any

Returns: void

Calls:

  • this._handleTouchStartDolly
  • this._handleTouchStartRotate
Code
_handleTouchStartDollyRotate( event ) {

        if ( this.enableZoom ) this._handleTouchStartDolly( event );

        if ( this.enableRotate ) this._handleTouchStartRotate( event );

    }

OrbitControls._handleTouchMoveRotate(event: any): void

Parameters:

  • event any

Returns: void

Calls:

  • this._rotateEnd.set
  • this._getSecondPointerPosition
  • this._rotateDelta.subVectors( this._rotateEnd, this._rotateStart ).multiplyScalar
  • this._rotateLeft
  • this._rotateUp
  • this._rotateStart.copy
Code
_handleTouchMoveRotate( event ) {

        if ( this._pointers.length == 1 ) {

            this._rotateEnd.set( event.pageX, event.pageY );

        } else {

            const position = this._getSecondPointerPosition( event );

            const x = 0.5 * ( event.pageX + position.x );
            const y = 0.5 * ( event.pageY + position.y );

            this._rotateEnd.set( x, y );

        }

        this._rotateDelta.subVectors( this._rotateEnd, this._rotateStart ).multiplyScalar( this.rotateSpeed );

        const element = this.domElement;

        this._rotateLeft( _twoPI * this._rotateDelta.x / element.clientHeight ); // yes, height

        this._rotateUp( _twoPI * this._rotateDelta.y / element.clientHeight );

        this._rotateStart.copy( this._rotateEnd );

    }

OrbitControls._handleTouchMovePan(event: any): void

Parameters:

  • event any

Returns: void

Calls:

  • this._panEnd.set
  • this._getSecondPointerPosition
  • this._panDelta.subVectors( this._panEnd, this._panStart ).multiplyScalar
  • this._pan
  • this._panStart.copy
Code
_handleTouchMovePan( event ) {

        if ( this._pointers.length === 1 ) {

            this._panEnd.set( event.pageX, event.pageY );

        } else {

            const position = this._getSecondPointerPosition( event );

            const x = 0.5 * ( event.pageX + position.x );
            const y = 0.5 * ( event.pageY + position.y );

            this._panEnd.set( x, y );

        }

        this._panDelta.subVectors( this._panEnd, this._panStart ).multiplyScalar( this.panSpeed );

        this._pan( this._panDelta.x, this._panDelta.y );

        this._panStart.copy( this._panEnd );

    }

OrbitControls._handleTouchMoveDolly(event: any): void

Parameters:

  • event any

Returns: void

Calls:

  • this._getSecondPointerPosition
  • Math.sqrt
  • this._dollyEnd.set
  • this._dollyDelta.set
  • Math.pow
  • this._dollyOut
  • this._dollyStart.copy
  • this._updateZoomParameters
Code
_handleTouchMoveDolly( event ) {

        const position = this._getSecondPointerPosition( event );

        const dx = event.pageX - position.x;
        const dy = event.pageY - position.y;

        const distance = Math.sqrt( dx * dx + dy * dy );

        this._dollyEnd.set( 0, distance );

        this._dollyDelta.set( 0, Math.pow( this._dollyEnd.y / this._dollyStart.y, this.zoomSpeed ) );

        this._dollyOut( this._dollyDelta.y );

        this._dollyStart.copy( this._dollyEnd );

        const centerX = ( event.pageX + position.x ) * 0.5;
        const centerY = ( event.pageY + position.y ) * 0.5;

        this._updateZoomParameters( centerX, centerY );

    }

OrbitControls._handleTouchMoveDollyPan(event: any): void

Parameters:

  • event any

Returns: void

Calls:

  • this._handleTouchMoveDolly
  • this._handleTouchMovePan
Code
_handleTouchMoveDollyPan( event ) {

        if ( this.enableZoom ) this._handleTouchMoveDolly( event );

        if ( this.enablePan ) this._handleTouchMovePan( event );

    }

OrbitControls._handleTouchMoveDollyRotate(event: any): void

Parameters:

  • event any

Returns: void

Calls:

  • this._handleTouchMoveDolly
  • this._handleTouchMoveRotate
Code
_handleTouchMoveDollyRotate( event ) {

        if ( this.enableZoom ) this._handleTouchMoveDolly( event );

        if ( this.enableRotate ) this._handleTouchMoveRotate( event );

    }

OrbitControls._addPointer(event: any): void

Parameters:

  • event any

Returns: void

Calls:

  • this._pointers.push
Code
_addPointer( event ) {

        this._pointers.push( event.pointerId );

    }

OrbitControls._removePointer(event: any): void

Parameters:

  • event any

Returns: void

Calls:

  • this._pointers.splice
Code
_removePointer( event ) {

        delete this._pointerPositions[ event.pointerId ];

        for ( let i = 0; i < this._pointers.length; i ++ ) {

            if ( this._pointers[ i ] == event.pointerId ) {

                this._pointers.splice( i, 1 );
                return;

            }

        }

    }

OrbitControls._isTrackingPointer(event: any): boolean

Parameters:

  • event any

Returns: boolean

Code
_isTrackingPointer( event ) {

        for ( let i = 0; i < this._pointers.length; i ++ ) {

            if ( this._pointers[ i ] == event.pointerId ) return true;

        }

        return false;

    }

OrbitControls._trackPointer(event: any): void

Parameters:

  • event any

Returns: void

Calls:

  • position.set
Code
_trackPointer( event ) {

        let position = this._pointerPositions[ event.pointerId ];

        if ( position === undefined ) {

            position = new Vector2();
            this._pointerPositions[ event.pointerId ] = position;

        }

        position.set( event.pageX, event.pageY );

    }

OrbitControls._getSecondPointerPosition(event: any): any

Parameters:

  • event any

Returns: any

Code
_getSecondPointerPosition( event ) {

        const pointerId = ( event.pointerId === this._pointers[ 0 ] ) ? this._pointers[ 1 ] : this._pointers[ 0 ];

        return this._pointerPositions[ pointerId ];

    }

OrbitControls._customWheelEvent(event: any): { clientX: any; clientY: any; deltaY: any; }

Parameters:

  • event any

Returns: { clientX: any; clientY: any; deltaY: any; }

Internal Comments:

// minimal wheel event altered to meet delta-zoom demand (x2)
// detect if event was triggered by pinching

Code
_customWheelEvent( event ) {

        const mode = event.deltaMode;

        // minimal wheel event altered to meet delta-zoom demand
        const newEvent = {
            clientX: event.clientX,
            clientY: event.clientY,
            deltaY: event.deltaY,
        };

        switch ( mode ) {

            case 1: // LINE_MODE
                newEvent.deltaY *= 16;
                break;

            case 2: // PAGE_MODE
                newEvent.deltaY *= 100;
                break;

        }

        // detect if event was triggered by pinching
        if ( event.ctrlKey && ! this._controlActive ) {

            newEvent.deltaY *= 10;

        }

        return newEvent;

    }

onPointerDown(event: any): void

Parameters:

  • event any

Returns: void

Calls:

  • this.domElement.setPointerCapture
  • this.domElement.addEventListener
  • this._isTrackingPointer
  • this._addPointer
  • this._onTouchStart
  • this._onMouseDown

Internal Comments:

// (x5)

Code
function onPointerDown( event ) {

    if ( this.enabled === false ) return;

    if ( this._pointers.length === 0 ) {

        this.domElement.setPointerCapture( event.pointerId );

        this.domElement.addEventListener( 'pointermove', this._onPointerMove );
        this.domElement.addEventListener( 'pointerup', this._onPointerUp );

    }

    //

    if ( this._isTrackingPointer( event ) ) return;

    //

    this._addPointer( event );

    if ( event.pointerType === 'touch' ) {

        this._onTouchStart( event );

    } else {

        this._onMouseDown( event );

    }

}

onPointerMove(event: any): void

Parameters:

  • event any

Returns: void

Calls:

  • this._onTouchMove
  • this._onMouseMove
Code
function onPointerMove( event ) {

    if ( this.enabled === false ) return;

    if ( event.pointerType === 'touch' ) {

        this._onTouchMove( event );

    } else {

        this._onMouseMove( event );

    }

}

onPointerUp(event: any): void

Parameters:

  • event any

Returns: void

Calls:

  • this._removePointer
  • this.domElement.releasePointerCapture
  • this.domElement.removeEventListener
  • this.dispatchEvent
  • this._onTouchStart

Internal Comments:

// minimal placeholder event - allows state correction on pointer-up (x4)

Code
function onPointerUp( event ) {

    this._removePointer( event );

    switch ( this._pointers.length ) {

        case 0:

            this.domElement.releasePointerCapture( event.pointerId );

            this.domElement.removeEventListener( 'pointermove', this._onPointerMove );
            this.domElement.removeEventListener( 'pointerup', this._onPointerUp );

            this.dispatchEvent( _endEvent );

            this.state = _STATE.NONE;

            break;

        case 1:

            const pointerId = this._pointers[ 0 ];
            const position = this._pointerPositions[ pointerId ];

            // minimal placeholder event - allows state correction on pointer-up
            this._onTouchStart( { pointerId: pointerId, pageX: position.x, pageY: position.y } );

            break;

    }

}

onMouseDown(event: any): void

Parameters:

  • event any

Returns: void

Calls:

  • this._handleMouseDownDolly
  • this._handleMouseDownPan
  • this._handleMouseDownRotate
  • this.dispatchEvent
Code
function onMouseDown( event ) {

    let mouseAction;

    switch ( event.button ) {

        case 0:

            mouseAction = this.mouseButtons.LEFT;
            break;

        case 1:

            mouseAction = this.mouseButtons.MIDDLE;
            break;

        case 2:

            mouseAction = this.mouseButtons.RIGHT;
            break;

        default:

            mouseAction = - 1;

    }

    switch ( mouseAction ) {

        case MOUSE.DOLLY:

            if ( this.enableZoom === false ) return;

            this._handleMouseDownDolly( event );

            this.state = _STATE.DOLLY;

            break;

        case MOUSE.ROTATE:

            if ( event.ctrlKey || event.metaKey || event.shiftKey ) {

                if ( this.enablePan === false ) return;

                this._handleMouseDownPan( event );

                this.state = _STATE.PAN;

            } else {

                if ( this.enableRotate === false ) return;

                this._handleMouseDownRotate( event );

                this.state = _STATE.ROTATE;

            }

            break;

        case MOUSE.PAN:

            if ( event.ctrlKey || event.metaKey || event.shiftKey ) {

                if ( this.enableRotate === false ) return;

                this._handleMouseDownRotate( event );

                this.state = _STATE.ROTATE;

            } else {

                if ( this.enablePan === false ) return;

                this._handleMouseDownPan( event );

                this.state = _STATE.PAN;

            }

            break;

        default:

            this.state = _STATE.NONE;

    }

    if ( this.state !== _STATE.NONE ) {

        this.dispatchEvent( _startEvent );

    }

}

onMouseMove(event: any): void

Parameters:

  • event any

Returns: void

Calls:

  • this._handleMouseMoveRotate
  • this._handleMouseMoveDolly
  • this._handleMouseMovePan
Code
function onMouseMove( event ) {

    switch ( this.state ) {

        case _STATE.ROTATE:

            if ( this.enableRotate === false ) return;

            this._handleMouseMoveRotate( event );

            break;

        case _STATE.DOLLY:

            if ( this.enableZoom === false ) return;

            this._handleMouseMoveDolly( event );

            break;

        case _STATE.PAN:

            if ( this.enablePan === false ) return;

            this._handleMouseMovePan( event );

            break;

    }

}

onMouseWheel(event: any): void

Parameters:

  • event any

Returns: void

Calls:

  • event.preventDefault
  • this.dispatchEvent
  • this._handleMouseWheel
  • this._customWheelEvent
Code
function onMouseWheel( event ) {

    if ( this.enabled === false || this.enableZoom === false || this.state !== _STATE.NONE ) return;

    event.preventDefault();

    this.dispatchEvent( _startEvent );

    this._handleMouseWheel( this._customWheelEvent( event ) );

    this.dispatchEvent( _endEvent );

}

onKeyDown(event: any): void

Parameters:

  • event any

Returns: void

Calls:

  • this._handleKeyDown
Code
function onKeyDown( event ) {

    if ( this.enabled === false ) return;

    this._handleKeyDown( event );

}

onTouchStart(event: any): void

Parameters:

  • event any

Returns: void

Calls:

  • this._trackPointer
  • this._handleTouchStartRotate
  • this._handleTouchStartPan
  • this._handleTouchStartDollyPan
  • this._handleTouchStartDollyRotate
  • this.dispatchEvent
Code
function onTouchStart( event ) {

    this._trackPointer( event );

    switch ( this._pointers.length ) {

        case 1:

            switch ( this.touches.ONE ) {

                case TOUCH.ROTATE:

                    if ( this.enableRotate === false ) return;

                    this._handleTouchStartRotate( event );

                    this.state = _STATE.TOUCH_ROTATE;

                    break;

                case TOUCH.PAN:

                    if ( this.enablePan === false ) return;

                    this._handleTouchStartPan( event );

                    this.state = _STATE.TOUCH_PAN;

                    break;

                default:

                    this.state = _STATE.NONE;

            }

            break;

        case 2:

            switch ( this.touches.TWO ) {

                case TOUCH.DOLLY_PAN:

                    if ( this.enableZoom === false && this.enablePan === false ) return;

                    this._handleTouchStartDollyPan( event );

                    this.state = _STATE.TOUCH_DOLLY_PAN;

                    break;

                case TOUCH.DOLLY_ROTATE:

                    if ( this.enableZoom === false && this.enableRotate === false ) return;

                    this._handleTouchStartDollyRotate( event );

                    this.state = _STATE.TOUCH_DOLLY_ROTATE;

                    break;

                default:

                    this.state = _STATE.NONE;

            }

            break;

        default:

            this.state = _STATE.NONE;

    }

    if ( this.state !== _STATE.NONE ) {

        this.dispatchEvent( _startEvent );

    }

}

onTouchMove(event: any): void

Parameters:

  • event any

Returns: void

Calls:

  • this._trackPointer
  • this._handleTouchMoveRotate
  • this.update
  • this._handleTouchMovePan
  • this._handleTouchMoveDollyPan
  • this._handleTouchMoveDollyRotate
Code
function onTouchMove( event ) {

    this._trackPointer( event );

    switch ( this.state ) {

        case _STATE.TOUCH_ROTATE:

            if ( this.enableRotate === false ) return;

            this._handleTouchMoveRotate( event );

            this.update();

            break;

        case _STATE.TOUCH_PAN:

            if ( this.enablePan === false ) return;

            this._handleTouchMovePan( event );

            this.update();

            break;

        case _STATE.TOUCH_DOLLY_PAN:

            if ( this.enableZoom === false && this.enablePan === false ) return;

            this._handleTouchMoveDollyPan( event );

            this.update();

            break;

        case _STATE.TOUCH_DOLLY_ROTATE:

            if ( this.enableZoom === false && this.enableRotate === false ) return;

            this._handleTouchMoveDollyRotate( event );

            this.update();

            break;

        default:

            this.state = _STATE.NONE;

    }

}

onContextMenu(event: any): void

Parameters:

  • event any

Returns: void

Calls:

  • event.preventDefault
Code
function onContextMenu( event ) {

    if ( this.enabled === false ) return;

    event.preventDefault();

}

interceptControlDown(event: any): void

Parameters:

  • event any

Returns: void

Calls:

  • this.domElement.getRootNode
  • document.addEventListener
Code
function interceptControlDown( event ) {

    if ( event.key === 'Control' ) {

        this._controlActive = true;

        const document = this.domElement.getRootNode(); // offscreen canvas compatibility

        document.addEventListener( 'keyup', this._interceptControlUp, { passive: true, capture: true } );

    }

}

interceptControlUp(event: any): void

Parameters:

  • event any

Returns: void

Calls:

  • this.domElement.getRootNode
  • document.removeEventListener
Code
function interceptControlUp( event ) {

    if ( event.key === 'Control' ) {

        this._controlActive = false;

        const document = this.domElement.getRootNode(); // offscreen canvas compatibility

        document.removeEventListener( 'keyup', this._interceptControlUp, { passive: true, capture: true } );

    }

}

Classes

OrbitControls

Class Code
class OrbitControls extends Controls {

    /**
     * Constructs a new controls instance.
     *
     * @param {Object3D} object - The object that is managed by the controls.
     * @param {?HTMLDOMElement} domElement - The HTML element used for event listeners.
     */
    constructor( object, domElement = null ) {

        super( object, domElement );

        this.state = _STATE.NONE;

        /**
         * The focus point of the controls, the `object` orbits around this.
         * It can be updated manually at any point to change the focus of the controls.
         *
         * @type {Vector3}
         */
        this.target = new Vector3();

        /**
         * The focus point of the `minTargetRadius` and `maxTargetRadius` limits.
         * It can be updated manually at any point to change the center of interest
         * for the `target`.
         *
         * @type {Vector3}
         */
        this.cursor = new Vector3();

        /**
         * How far you can dolly in (perspective camera only).
         *
         * @type {number}
         * @default 0
         */
        this.minDistance = 0;

        /**
         * How far you can dolly out (perspective camera only).
         *
         * @type {number}
         * @default Infinity
         */
        this.maxDistance = Infinity;

        /**
         * How far you can zoom in (orthographic camera only).
         *
         * @type {number}
         * @default 0
         */
        this.minZoom = 0;

        /**
         * How far you can zoom out (orthographic camera only).
         *
         * @type {number}
         * @default Infinity
         */
        this.maxZoom = Infinity;

        /**
         * How close you can get the target to the 3D `cursor`.
         *
         * @type {number}
         * @default 0
         */
        this.minTargetRadius = 0;

        /**
         * How far you can move the target from the 3D `cursor`.
         *
         * @type {number}
         * @default Infinity
         */
        this.maxTargetRadius = Infinity;

        /**
         * How far you can orbit vertically, lower limit. Range is `[0, Math.PI]` radians.
         *
         * @type {number}
         * @default 0
         */
        this.minPolarAngle = 0;

        /**
         * How far you can orbit vertically, upper limit. Range is `[0, Math.PI]` radians.
         *
         * @type {number}
         * @default Math.PI
         */
        this.maxPolarAngle = Math.PI;

        /**
         * How far you can orbit horizontally, lower limit. If set, the interval `[ min, max ]`
         * must be a sub-interval of `[ - 2 PI, 2 PI ]`, with `( max - min < 2 PI )`.
         *
         * @type {number}
         * @default -Infinity
         */
        this.minAzimuthAngle = - Infinity;

        /**
         * How far you can orbit horizontally, upper limit. If set, the interval `[ min, max ]`
         * must be a sub-interval of `[ - 2 PI, 2 PI ]`, with `( max - min < 2 PI )`.
         *
         * @type {number}
         * @default -Infinity
         */
        this.maxAzimuthAngle = Infinity;

        /**
         * Set to `true` to enable damping (inertia), which can be used to give a sense of weight
         * to the controls. Note that if this is enabled, you must call `update()` in your animation
         * loop.
         *
         * @type {boolean}
         * @default false
         */
        this.enableDamping = false;

        /**
         * The damping inertia used if `enableDamping` is set to `true`.
         *
         * Note that for this to work, you must call `update()` in your animation loop.
         *
         * @type {number}
         * @default 0.05
         */
        this.dampingFactor = 0.05;

        /**
         * Enable or disable zooming (dollying) of the camera.
         *
         * @type {boolean}
         * @default true
         */
        this.enableZoom = true;

        /**
         * Speed of zooming / dollying.
         *
         * @type {number}
         * @default 1
         */
        this.zoomSpeed = 1.0;

        /**
         * Enable or disable horizontal and vertical rotation of the camera.
         *
         * Note that it is possible to disable a single axis by setting the min and max of the
         * `minPolarAngle` or `minAzimuthAngle` to the same value, which will cause the vertical
         * or horizontal rotation to be fixed at that value.
         *
         * @type {boolean}
         * @default true
         */
        this.enableRotate = true;

        /**
         * Speed of rotation.
         *
         * @type {number}
         * @default 1
         */
        this.rotateSpeed = 1.0;

        /**
         * How fast to rotate the camera when the keyboard is used.
         *
         * @type {number}
         * @default 1
         */
        this.keyRotateSpeed = 1.0;

        /**
         * Enable or disable camera panning.
         *
         * @type {boolean}
         * @default true
         */
        this.enablePan = true;

        /**
         * Speed of panning.
         *
         * @type {number}
         * @default 1
         */
        this.panSpeed = 1.0;

        /**
         * Defines how the camera's position is translated when panning. If `true`, the camera pans
         * in screen space. Otherwise, the camera pans in the plane orthogonal to the camera's up
         * direction.
         *
         * @type {boolean}
         * @default true
         */
        this.screenSpacePanning = true;

        /**
         * How fast to pan the camera when the keyboard is used in
         * pixels per keypress.
         *
         * @type {number}
         * @default 7
         */
        this.keyPanSpeed = 7.0;

        /**
         * Setting this property to `true` allows to zoom to the cursor's position.
         *
         * @type {boolean}
         * @default false
         */
        this.zoomToCursor = false;

        /**
         * Set to true to automatically rotate around the target
         *
         * Note that if this is enabled, you must call `update()` in your animation loop.
         * If you want the auto-rotate speed to be independent of the frame rate (the refresh
         * rate of the display), you must pass the time `deltaTime`, in seconds, to `update()`.
         *
         * @type {boolean}
         * @default false
         */
        this.autoRotate = false;

        /**
         * How fast to rotate around the target if `autoRotate` is `true`. The default  equates to 30 seconds
         * per orbit at 60fps.
         *
         * Note that if `autoRotate` is enabled, you must call `update()` in your animation loop.
         *
         * @type {number}
         * @default 2
         */
        this.autoRotateSpeed = 2.0;

        /**
         * This object contains references to the keycodes for controlling camera panning.
         *
         * ```js
         * controls.keys = {
         *  LEFT: 'ArrowLeft', //left arrow
         *  UP: 'ArrowUp', // up arrow
         *  RIGHT: 'ArrowRight', // right arrow
         *  BOTTOM: 'ArrowDown' // down arrow
         * }
         * ```
         * @type {Object}
         */
        this.keys = { LEFT: 'ArrowLeft', UP: 'ArrowUp', RIGHT: 'ArrowRight', BOTTOM: 'ArrowDown' };

        /**
         * This object contains references to the mouse actions used by the controls.
         *
         * ```js
         * controls.mouseButtons = {
         *  LEFT: THREE.MOUSE.ROTATE,
         *  MIDDLE: THREE.MOUSE.DOLLY,
         *  RIGHT: THREE.MOUSE.PAN
         * }
         * ```
         * @type {Object}
         */
        this.mouseButtons = { LEFT: MOUSE.ROTATE, MIDDLE: MOUSE.DOLLY, RIGHT: MOUSE.PAN };

        /**
         * This object contains references to the touch actions used by the controls.
         *
         * ```js
         * controls.mouseButtons = {
         *  ONE: THREE.TOUCH.ROTATE,
         *  TWO: THREE.TOUCH.DOLLY_PAN
         * }
         * ```
         * @type {Object}
         */
        this.touches = { ONE: TOUCH.ROTATE, TWO: TOUCH.DOLLY_PAN };

        /**
         * Used internally by `saveState()` and `reset()`.
         *
         * @type {Vector3}
         */
        this.target0 = this.target.clone();

        /**
         * Used internally by `saveState()` and `reset()`.
         *
         * @type {Vector3}
         */
        this.position0 = this.object.position.clone();

        /**
         * Used internally by `saveState()` and `reset()`.
         *
         * @type {number}
         */
        this.zoom0 = this.object.zoom;

        // the target DOM element for key events
        this._domElementKeyEvents = null;

        // internals

        this._lastPosition = new Vector3();
        this._lastQuaternion = new Quaternion();
        this._lastTargetPosition = new Vector3();

        // so camera.up is the orbit axis
        this._quat = new Quaternion().setFromUnitVectors( object.up, new Vector3( 0, 1, 0 ) );
        this._quatInverse = this._quat.clone().invert();

        // current position in spherical coordinates
        this._spherical = new Spherical();
        this._sphericalDelta = new Spherical();

        this._scale = 1;
        this._panOffset = new Vector3();

        this._rotateStart = new Vector2();
        this._rotateEnd = new Vector2();
        this._rotateDelta = new Vector2();

        this._panStart = new Vector2();
        this._panEnd = new Vector2();
        this._panDelta = new Vector2();

        this._dollyStart = new Vector2();
        this._dollyEnd = new Vector2();
        this._dollyDelta = new Vector2();

        this._dollyDirection = new Vector3();
        this._mouse = new Vector2();
        this._performCursorZoom = false;

        this._pointers = [];
        this._pointerPositions = {};

        this._controlActive = false;

        // event listeners

        this._onPointerMove = onPointerMove.bind( this );
        this._onPointerDown = onPointerDown.bind( this );
        this._onPointerUp = onPointerUp.bind( this );
        this._onContextMenu = onContextMenu.bind( this );
        this._onMouseWheel = onMouseWheel.bind( this );
        this._onKeyDown = onKeyDown.bind( this );

        this._onTouchStart = onTouchStart.bind( this );
        this._onTouchMove = onTouchMove.bind( this );

        this._onMouseDown = onMouseDown.bind( this );
        this._onMouseMove = onMouseMove.bind( this );

        this._interceptControlDown = interceptControlDown.bind( this );
        this._interceptControlUp = interceptControlUp.bind( this );

        //

        if ( this.domElement !== null ) {

            this.connect( this.domElement );

        }

        this.update();

    }

    connect( element ) {

        super.connect( element );

        this.domElement.addEventListener( 'pointerdown', this._onPointerDown );
        this.domElement.addEventListener( 'pointercancel', this._onPointerUp );

        this.domElement.addEventListener( 'contextmenu', this._onContextMenu );
        this.domElement.addEventListener( 'wheel', this._onMouseWheel, { passive: false } );

        const document = this.domElement.getRootNode(); // offscreen canvas compatibility
        document.addEventListener( 'keydown', this._interceptControlDown, { passive: true, capture: true } );

        this.domElement.style.touchAction = 'none'; // disable touch scroll

    }

    disconnect() {

        this.domElement.removeEventListener( 'pointerdown', this._onPointerDown );
        this.domElement.removeEventListener( 'pointermove', this._onPointerMove );
        this.domElement.removeEventListener( 'pointerup', this._onPointerUp );
        this.domElement.removeEventListener( 'pointercancel', this._onPointerUp );

        this.domElement.removeEventListener( 'wheel', this._onMouseWheel );
        this.domElement.removeEventListener( 'contextmenu', this._onContextMenu );

        this.stopListenToKeyEvents();

        const document = this.domElement.getRootNode(); // offscreen canvas compatibility
        document.removeEventListener( 'keydown', this._interceptControlDown, { capture: true } );

        this.domElement.style.touchAction = 'auto';

    }

    dispose() {

        this.disconnect();

    }

    /**
     * Get the current vertical rotation, in radians.
     *
     * @return {number} The current vertical rotation, in radians.
     */
    getPolarAngle() {

        return this._spherical.phi;

    }

    /**
     * Get the current horizontal rotation, in radians.
     *
     * @return {number} The current horizontal rotation, in radians.
     */
    getAzimuthalAngle() {

        return this._spherical.theta;

    }

    /**
     * Returns the distance from the camera to the target.
     *
     * @return {number} The distance from the camera to the target.
     */
    getDistance() {

        return this.object.position.distanceTo( this.target );

    }

    /**
     * Adds key event listeners to the given DOM element.
     * `window` is a recommended argument for using this method.
     *
     * @param {HTMLDOMElement} domElement - The DOM element
     */
    listenToKeyEvents( domElement ) {

        domElement.addEventListener( 'keydown', this._onKeyDown );
        this._domElementKeyEvents = domElement;

    }

    /**
     * Removes the key event listener previously defined with `listenToKeyEvents()`.
     */
    stopListenToKeyEvents() {

        if ( this._domElementKeyEvents !== null ) {

            this._domElementKeyEvents.removeEventListener( 'keydown', this._onKeyDown );
            this._domElementKeyEvents = null;

        }

    }

    /**
     * Save the current state of the controls. This can later be recovered with `reset()`.
     */
    saveState() {

        this.target0.copy( this.target );
        this.position0.copy( this.object.position );
        this.zoom0 = this.object.zoom;

    }

    /**
     * Reset the controls to their state from either the last time the `saveState()`
     * was called, or the initial state.
     */
    reset() {

        this.target.copy( this.target0 );
        this.object.position.copy( this.position0 );
        this.object.zoom = this.zoom0;

        this.object.updateProjectionMatrix();
        this.dispatchEvent( _changeEvent );

        this.update();

        this.state = _STATE.NONE;

    }

    update( deltaTime = null ) {

        const position = this.object.position;

        _v.copy( position ).sub( this.target );

        // rotate offset to "y-axis-is-up" space
        _v.applyQuaternion( this._quat );

        // angle from z-axis around y-axis
        this._spherical.setFromVector3( _v );

        if ( this.autoRotate && this.state === _STATE.NONE ) {

            this._rotateLeft( this._getAutoRotationAngle( deltaTime ) );

        }

        if ( this.enableDamping ) {

            this._spherical.theta += this._sphericalDelta.theta * this.dampingFactor;
            this._spherical.phi += this._sphericalDelta.phi * this.dampingFactor;

        } else {

            this._spherical.theta += this._sphericalDelta.theta;
            this._spherical.phi += this._sphericalDelta.phi;

        }

        // restrict theta to be between desired limits

        let min = this.minAzimuthAngle;
        let max = this.maxAzimuthAngle;

        if ( isFinite( min ) && isFinite( max ) ) {

            if ( min < - Math.PI ) min += _twoPI; else if ( min > Math.PI ) min -= _twoPI;

            if ( max < - Math.PI ) max += _twoPI; else if ( max > Math.PI ) max -= _twoPI;

            if ( min <= max ) {

                this._spherical.theta = Math.max( min, Math.min( max, this._spherical.theta ) );

            } else {

                this._spherical.theta = ( this._spherical.theta > ( min + max ) / 2 ) ?
                    Math.max( min, this._spherical.theta ) :
                    Math.min( max, this._spherical.theta );

            }

        }

        // restrict phi to be between desired limits
        this._spherical.phi = Math.max( this.minPolarAngle, Math.min( this.maxPolarAngle, this._spherical.phi ) );

        this._spherical.makeSafe();


        // move target to panned location

        if ( this.enableDamping === true ) {

            this.target.addScaledVector( this._panOffset, this.dampingFactor );

        } else {

            this.target.add( this._panOffset );

        }

        // Limit the target distance from the cursor to create a sphere around the center of interest
        this.target.sub( this.cursor );
        this.target.clampLength( this.minTargetRadius, this.maxTargetRadius );
        this.target.add( this.cursor );

        let zoomChanged = false;
        // adjust the camera position based on zoom only if we're not zooming to the cursor or if it's an ortho camera
        // we adjust zoom later in these cases
        if ( this.zoomToCursor && this._performCursorZoom || this.object.isOrthographicCamera ) {

            this._spherical.radius = this._clampDistance( this._spherical.radius );

        } else {

            const prevRadius = this._spherical.radius;
            this._spherical.radius = this._clampDistance( this._spherical.radius * this._scale );
            zoomChanged = prevRadius != this._spherical.radius;

        }

        _v.setFromSpherical( this._spherical );

        // rotate offset back to "camera-up-vector-is-up" space
        _v.applyQuaternion( this._quatInverse );

        position.copy( this.target ).add( _v );

        this.object.lookAt( this.target );

        if ( this.enableDamping === true ) {

            this._sphericalDelta.theta *= ( 1 - this.dampingFactor );
            this._sphericalDelta.phi *= ( 1 - this.dampingFactor );

            this._panOffset.multiplyScalar( 1 - this.dampingFactor );

        } else {

            this._sphericalDelta.set( 0, 0, 0 );

            this._panOffset.set( 0, 0, 0 );

        }

        // adjust camera position
        if ( this.zoomToCursor && this._performCursorZoom ) {

            let newRadius = null;
            if ( this.object.isPerspectiveCamera ) {

                // move the camera down the pointer ray
                // this method avoids floating point error
                const prevRadius = _v.length();
                newRadius = this._clampDistance( prevRadius * this._scale );

                const radiusDelta = prevRadius - newRadius;
                this.object.position.addScaledVector( this._dollyDirection, radiusDelta );
                this.object.updateMatrixWorld();

                zoomChanged = !! radiusDelta;

            } else if ( this.object.isOrthographicCamera ) {

                // adjust the ortho camera position based on zoom changes
                const mouseBefore = new Vector3( this._mouse.x, this._mouse.y, 0 );
                mouseBefore.unproject( this.object );

                const prevZoom = this.object.zoom;
                this.object.zoom = Math.max( this.minZoom, Math.min( this.maxZoom, this.object.zoom / this._scale ) );
                this.object.updateProjectionMatrix();

                zoomChanged = prevZoom !== this.object.zoom;

                const mouseAfter = new Vector3( this._mouse.x, this._mouse.y, 0 );
                mouseAfter.unproject( this.object );

                this.object.position.sub( mouseAfter ).add( mouseBefore );
                this.object.updateMatrixWorld();

                newRadius = _v.length();

            } else {

                console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - zoom to cursor disabled.' );
                this.zoomToCursor = false;

            }

            // handle the placement of the target
            if ( newRadius !== null ) {

                if ( this.screenSpacePanning ) {

                    // position the orbit target in front of the new camera position
                    this.target.set( 0, 0, - 1 )
                        .transformDirection( this.object.matrix )
                        .multiplyScalar( newRadius )
                        .add( this.object.position );

                } else {

                    // get the ray and translation plane to compute target
                    _ray.origin.copy( this.object.position );
                    _ray.direction.set( 0, 0, - 1 ).transformDirection( this.object.matrix );

                    // if the camera is 20 degrees above the horizon then don't adjust the focus target to avoid
                    // extremely large values
                    if ( Math.abs( this.object.up.dot( _ray.direction ) ) < _TILT_LIMIT ) {

                        this.object.lookAt( this.target );

                    } else {

                        _plane.setFromNormalAndCoplanarPoint( this.object.up, this.target );
                        _ray.intersectPlane( _plane, this.target );

                    }

                }

            }

        } else if ( this.object.isOrthographicCamera ) {

            const prevZoom = this.object.zoom;
            this.object.zoom = Math.max( this.minZoom, Math.min( this.maxZoom, this.object.zoom / this._scale ) );

            if ( prevZoom !== this.object.zoom ) {

                this.object.updateProjectionMatrix();
                zoomChanged = true;

            }

        }

        this._scale = 1;
        this._performCursorZoom = false;

        // update condition is:
        // min(camera displacement, camera rotation in radians)^2 > EPS
        // using small-angle approximation cos(x/2) = 1 - x^2 / 8

        if ( zoomChanged ||
            this._lastPosition.distanceToSquared( this.object.position ) > _EPS ||
            8 * ( 1 - this._lastQuaternion.dot( this.object.quaternion ) ) > _EPS ||
            this._lastTargetPosition.distanceToSquared( this.target ) > _EPS ) {

            this.dispatchEvent( _changeEvent );

            this._lastPosition.copy( this.object.position );
            this._lastQuaternion.copy( this.object.quaternion );
            this._lastTargetPosition.copy( this.target );

            return true;

        }

        return false;

    }

    _getAutoRotationAngle( deltaTime ) {

        if ( deltaTime !== null ) {

            return ( _twoPI / 60 * this.autoRotateSpeed ) * deltaTime;

        } else {

            return _twoPI / 60 / 60 * this.autoRotateSpeed;

        }

    }

    _getZoomScale( delta ) {

        const normalizedDelta = Math.abs( delta * 0.01 );
        return Math.pow( 0.95, this.zoomSpeed * normalizedDelta );

    }

    _rotateLeft( angle ) {

        this._sphericalDelta.theta -= angle;

    }

    _rotateUp( angle ) {

        this._sphericalDelta.phi -= angle;

    }

    _panLeft( distance, objectMatrix ) {

        _v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix
        _v.multiplyScalar( - distance );

        this._panOffset.add( _v );

    }

    _panUp( distance, objectMatrix ) {

        if ( this.screenSpacePanning === true ) {

            _v.setFromMatrixColumn( objectMatrix, 1 );

        } else {

            _v.setFromMatrixColumn( objectMatrix, 0 );
            _v.crossVectors( this.object.up, _v );

        }

        _v.multiplyScalar( distance );

        this._panOffset.add( _v );

    }

    // deltaX and deltaY are in pixels; right and down are positive
    _pan( deltaX, deltaY ) {

        const element = this.domElement;

        if ( this.object.isPerspectiveCamera ) {

            // perspective
            const position = this.object.position;
            _v.copy( position ).sub( this.target );
            let targetDistance = _v.length();

            // half of the fov is center to top of screen
            targetDistance *= Math.tan( ( this.object.fov / 2 ) * Math.PI / 180.0 );

            // we use only clientHeight here so aspect ratio does not distort speed
            this._panLeft( 2 * deltaX * targetDistance / element.clientHeight, this.object.matrix );
            this._panUp( 2 * deltaY * targetDistance / element.clientHeight, this.object.matrix );

        } else if ( this.object.isOrthographicCamera ) {

            // orthographic
            this._panLeft( deltaX * ( this.object.right - this.object.left ) / this.object.zoom / element.clientWidth, this.object.matrix );
            this._panUp( deltaY * ( this.object.top - this.object.bottom ) / this.object.zoom / element.clientHeight, this.object.matrix );

        } else {

            // camera neither orthographic nor perspective
            console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' );
            this.enablePan = false;

        }

    }

    _dollyOut( dollyScale ) {

        if ( this.object.isPerspectiveCamera || this.object.isOrthographicCamera ) {

            this._scale /= dollyScale;

        } else {

            console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' );
            this.enableZoom = false;

        }

    }

    _dollyIn( dollyScale ) {

        if ( this.object.isPerspectiveCamera || this.object.isOrthographicCamera ) {

            this._scale *= dollyScale;

        } else {

            console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' );
            this.enableZoom = false;

        }

    }

    _updateZoomParameters( x, y ) {

        if ( ! this.zoomToCursor ) {

            return;

        }

        this._performCursorZoom = true;

        const rect = this.domElement.getBoundingClientRect();
        const dx = x - rect.left;
        const dy = y - rect.top;
        const w = rect.width;
        const h = rect.height;

        this._mouse.x = ( dx / w ) * 2 - 1;
        this._mouse.y = - ( dy / h ) * 2 + 1;

        this._dollyDirection.set( this._mouse.x, this._mouse.y, 1 ).unproject( this.object ).sub( this.object.position ).normalize();

    }

    _clampDistance( dist ) {

        return Math.max( this.minDistance, Math.min( this.maxDistance, dist ) );

    }

    //
    // event callbacks - update the object state
    //

    _handleMouseDownRotate( event ) {

        this._rotateStart.set( event.clientX, event.clientY );

    }

    _handleMouseDownDolly( event ) {

        this._updateZoomParameters( event.clientX, event.clientX );
        this._dollyStart.set( event.clientX, event.clientY );

    }

    _handleMouseDownPan( event ) {

        this._panStart.set( event.clientX, event.clientY );

    }

    _handleMouseMoveRotate( event ) {

        this._rotateEnd.set( event.clientX, event.clientY );

        this._rotateDelta.subVectors( this._rotateEnd, this._rotateStart ).multiplyScalar( this.rotateSpeed );

        const element = this.domElement;

        this._rotateLeft( _twoPI * this._rotateDelta.x / element.clientHeight ); // yes, height

        this._rotateUp( _twoPI * this._rotateDelta.y / element.clientHeight );

        this._rotateStart.copy( this._rotateEnd );

        this.update();

    }

    _handleMouseMoveDolly( event ) {

        this._dollyEnd.set( event.clientX, event.clientY );

        this._dollyDelta.subVectors( this._dollyEnd, this._dollyStart );

        if ( this._dollyDelta.y > 0 ) {

            this._dollyOut( this._getZoomScale( this._dollyDelta.y ) );

        } else if ( this._dollyDelta.y < 0 ) {

            this._dollyIn( this._getZoomScale( this._dollyDelta.y ) );

        }

        this._dollyStart.copy( this._dollyEnd );

        this.update();

    }

    _handleMouseMovePan( event ) {

        this._panEnd.set( event.clientX, event.clientY );

        this._panDelta.subVectors( this._panEnd, this._panStart ).multiplyScalar( this.panSpeed );

        this._pan( this._panDelta.x, this._panDelta.y );

        this._panStart.copy( this._panEnd );

        this.update();

    }

    _handleMouseWheel( event ) {

        this._updateZoomParameters( event.clientX, event.clientY );

        if ( event.deltaY < 0 ) {

            this._dollyIn( this._getZoomScale( event.deltaY ) );

        } else if ( event.deltaY > 0 ) {

            this._dollyOut( this._getZoomScale( event.deltaY ) );

        }

        this.update();

    }

    _handleKeyDown( event ) {

        let needsUpdate = false;

        switch ( event.code ) {

            case this.keys.UP:

                if ( event.ctrlKey || event.metaKey || event.shiftKey ) {

                    if ( this.enableRotate ) {

                        this._rotateUp( _twoPI * this.keyRotateSpeed / this.domElement.clientHeight );

                    }

                } else {

                    if ( this.enablePan ) {

                        this._pan( 0, this.keyPanSpeed );

                    }

                }

                needsUpdate = true;
                break;

            case this.keys.BOTTOM:

                if ( event.ctrlKey || event.metaKey || event.shiftKey ) {

                    if ( this.enableRotate ) {

                        this._rotateUp( - _twoPI * this.keyRotateSpeed / this.domElement.clientHeight );

                    }

                } else {

                    if ( this.enablePan ) {

                        this._pan( 0, - this.keyPanSpeed );

                    }

                }

                needsUpdate = true;
                break;

            case this.keys.LEFT:

                if ( event.ctrlKey || event.metaKey || event.shiftKey ) {

                    if ( this.enableRotate ) {

                        this._rotateLeft( _twoPI * this.keyRotateSpeed / this.domElement.clientHeight );

                    }

                } else {

                    if ( this.enablePan ) {

                        this._pan( this.keyPanSpeed, 0 );

                    }

                }

                needsUpdate = true;
                break;

            case this.keys.RIGHT:

                if ( event.ctrlKey || event.metaKey || event.shiftKey ) {

                    if ( this.enableRotate ) {

                        this._rotateLeft( - _twoPI * this.keyRotateSpeed / this.domElement.clientHeight );

                    }

                } else {

                    if ( this.enablePan ) {

                        this._pan( - this.keyPanSpeed, 0 );

                    }

                }

                needsUpdate = true;
                break;

        }

        if ( needsUpdate ) {

            // prevent the browser from scrolling on cursor keys
            event.preventDefault();

            this.update();

        }


    }

    _handleTouchStartRotate( event ) {

        if ( this._pointers.length === 1 ) {

            this._rotateStart.set( event.pageX, event.pageY );

        } else {

            const position = this._getSecondPointerPosition( event );

            const x = 0.5 * ( event.pageX + position.x );
            const y = 0.5 * ( event.pageY + position.y );

            this._rotateStart.set( x, y );

        }

    }

    _handleTouchStartPan( event ) {

        if ( this._pointers.length === 1 ) {

            this._panStart.set( event.pageX, event.pageY );

        } else {

            const position = this._getSecondPointerPosition( event );

            const x = 0.5 * ( event.pageX + position.x );
            const y = 0.5 * ( event.pageY + position.y );

            this._panStart.set( x, y );

        }

    }

    _handleTouchStartDolly( event ) {

        const position = this._getSecondPointerPosition( event );

        const dx = event.pageX - position.x;
        const dy = event.pageY - position.y;

        const distance = Math.sqrt( dx * dx + dy * dy );

        this._dollyStart.set( 0, distance );

    }

    _handleTouchStartDollyPan( event ) {

        if ( this.enableZoom ) this._handleTouchStartDolly( event );

        if ( this.enablePan ) this._handleTouchStartPan( event );

    }

    _handleTouchStartDollyRotate( event ) {

        if ( this.enableZoom ) this._handleTouchStartDolly( event );

        if ( this.enableRotate ) this._handleTouchStartRotate( event );

    }

    _handleTouchMoveRotate( event ) {

        if ( this._pointers.length == 1 ) {

            this._rotateEnd.set( event.pageX, event.pageY );

        } else {

            const position = this._getSecondPointerPosition( event );

            const x = 0.5 * ( event.pageX + position.x );
            const y = 0.5 * ( event.pageY + position.y );

            this._rotateEnd.set( x, y );

        }

        this._rotateDelta.subVectors( this._rotateEnd, this._rotateStart ).multiplyScalar( this.rotateSpeed );

        const element = this.domElement;

        this._rotateLeft( _twoPI * this._rotateDelta.x / element.clientHeight ); // yes, height

        this._rotateUp( _twoPI * this._rotateDelta.y / element.clientHeight );

        this._rotateStart.copy( this._rotateEnd );

    }

    _handleTouchMovePan( event ) {

        if ( this._pointers.length === 1 ) {

            this._panEnd.set( event.pageX, event.pageY );

        } else {

            const position = this._getSecondPointerPosition( event );

            const x = 0.5 * ( event.pageX + position.x );
            const y = 0.5 * ( event.pageY + position.y );

            this._panEnd.set( x, y );

        }

        this._panDelta.subVectors( this._panEnd, this._panStart ).multiplyScalar( this.panSpeed );

        this._pan( this._panDelta.x, this._panDelta.y );

        this._panStart.copy( this._panEnd );

    }

    _handleTouchMoveDolly( event ) {

        const position = this._getSecondPointerPosition( event );

        const dx = event.pageX - position.x;
        const dy = event.pageY - position.y;

        const distance = Math.sqrt( dx * dx + dy * dy );

        this._dollyEnd.set( 0, distance );

        this._dollyDelta.set( 0, Math.pow( this._dollyEnd.y / this._dollyStart.y, this.zoomSpeed ) );

        this._dollyOut( this._dollyDelta.y );

        this._dollyStart.copy( this._dollyEnd );

        const centerX = ( event.pageX + position.x ) * 0.5;
        const centerY = ( event.pageY + position.y ) * 0.5;

        this._updateZoomParameters( centerX, centerY );

    }

    _handleTouchMoveDollyPan( event ) {

        if ( this.enableZoom ) this._handleTouchMoveDolly( event );

        if ( this.enablePan ) this._handleTouchMovePan( event );

    }

    _handleTouchMoveDollyRotate( event ) {

        if ( this.enableZoom ) this._handleTouchMoveDolly( event );

        if ( this.enableRotate ) this._handleTouchMoveRotate( event );

    }

    // pointers

    _addPointer( event ) {

        this._pointers.push( event.pointerId );

    }

    _removePointer( event ) {

        delete this._pointerPositions[ event.pointerId ];

        for ( let i = 0; i < this._pointers.length; i ++ ) {

            if ( this._pointers[ i ] == event.pointerId ) {

                this._pointers.splice( i, 1 );
                return;

            }

        }

    }

    _isTrackingPointer( event ) {

        for ( let i = 0; i < this._pointers.length; i ++ ) {

            if ( this._pointers[ i ] == event.pointerId ) return true;

        }

        return false;

    }

    _trackPointer( event ) {

        let position = this._pointerPositions[ event.pointerId ];

        if ( position === undefined ) {

            position = new Vector2();
            this._pointerPositions[ event.pointerId ] = position;

        }

        position.set( event.pageX, event.pageY );

    }

    _getSecondPointerPosition( event ) {

        const pointerId = ( event.pointerId === this._pointers[ 0 ] ) ? this._pointers[ 1 ] : this._pointers[ 0 ];

        return this._pointerPositions[ pointerId ];

    }

    //

    _customWheelEvent( event ) {

        const mode = event.deltaMode;

        // minimal wheel event altered to meet delta-zoom demand
        const newEvent = {
            clientX: event.clientX,
            clientY: event.clientY,
            deltaY: event.deltaY,
        };

        switch ( mode ) {

            case 1: // LINE_MODE
                newEvent.deltaY *= 16;
                break;

            case 2: // PAGE_MODE
                newEvent.deltaY *= 100;
                break;

        }

        // detect if event was triggered by pinching
        if ( event.ctrlKey && ! this._controlActive ) {

            newEvent.deltaY *= 10;

        }

        return newEvent;

    }

}

Methods

connect(element: any): void
Code
connect( element ) {

        super.connect( element );

        this.domElement.addEventListener( 'pointerdown', this._onPointerDown );
        this.domElement.addEventListener( 'pointercancel', this._onPointerUp );

        this.domElement.addEventListener( 'contextmenu', this._onContextMenu );
        this.domElement.addEventListener( 'wheel', this._onMouseWheel, { passive: false } );

        const document = this.domElement.getRootNode(); // offscreen canvas compatibility
        document.addEventListener( 'keydown', this._interceptControlDown, { passive: true, capture: true } );

        this.domElement.style.touchAction = 'none'; // disable touch scroll

    }
disconnect(): void
Code
disconnect() {

        this.domElement.removeEventListener( 'pointerdown', this._onPointerDown );
        this.domElement.removeEventListener( 'pointermove', this._onPointerMove );
        this.domElement.removeEventListener( 'pointerup', this._onPointerUp );
        this.domElement.removeEventListener( 'pointercancel', this._onPointerUp );

        this.domElement.removeEventListener( 'wheel', this._onMouseWheel );
        this.domElement.removeEventListener( 'contextmenu', this._onContextMenu );

        this.stopListenToKeyEvents();

        const document = this.domElement.getRootNode(); // offscreen canvas compatibility
        document.removeEventListener( 'keydown', this._interceptControlDown, { capture: true } );

        this.domElement.style.touchAction = 'auto';

    }
dispose(): void
Code
dispose() {

        this.disconnect();

    }
getPolarAngle(): number
Code
getPolarAngle() {

        return this._spherical.phi;

    }
getAzimuthalAngle(): number
Code
getAzimuthalAngle() {

        return this._spherical.theta;

    }
getDistance(): number
Code
getDistance() {

        return this.object.position.distanceTo( this.target );

    }
listenToKeyEvents(domElement: HTMLDOMElement): void
Code
listenToKeyEvents( domElement ) {

        domElement.addEventListener( 'keydown', this._onKeyDown );
        this._domElementKeyEvents = domElement;

    }
stopListenToKeyEvents(): void
Code
stopListenToKeyEvents() {

        if ( this._domElementKeyEvents !== null ) {

            this._domElementKeyEvents.removeEventListener( 'keydown', this._onKeyDown );
            this._domElementKeyEvents = null;

        }

    }
saveState(): void
Code
saveState() {

        this.target0.copy( this.target );
        this.position0.copy( this.object.position );
        this.zoom0 = this.object.zoom;

    }
reset(): void
Code
reset() {

        this.target.copy( this.target0 );
        this.object.position.copy( this.position0 );
        this.object.zoom = this.zoom0;

        this.object.updateProjectionMatrix();
        this.dispatchEvent( _changeEvent );

        this.update();

        this.state = _STATE.NONE;

    }
update(deltaTime: any): boolean
Code
update( deltaTime = null ) {

        const position = this.object.position;

        _v.copy( position ).sub( this.target );

        // rotate offset to "y-axis-is-up" space
        _v.applyQuaternion( this._quat );

        // angle from z-axis around y-axis
        this._spherical.setFromVector3( _v );

        if ( this.autoRotate && this.state === _STATE.NONE ) {

            this._rotateLeft( this._getAutoRotationAngle( deltaTime ) );

        }

        if ( this.enableDamping ) {

            this._spherical.theta += this._sphericalDelta.theta * this.dampingFactor;
            this._spherical.phi += this._sphericalDelta.phi * this.dampingFactor;

        } else {

            this._spherical.theta += this._sphericalDelta.theta;
            this._spherical.phi += this._sphericalDelta.phi;

        }

        // restrict theta to be between desired limits

        let min = this.minAzimuthAngle;
        let max = this.maxAzimuthAngle;

        if ( isFinite( min ) && isFinite( max ) ) {

            if ( min < - Math.PI ) min += _twoPI; else if ( min > Math.PI ) min -= _twoPI;

            if ( max < - Math.PI ) max += _twoPI; else if ( max > Math.PI ) max -= _twoPI;

            if ( min <= max ) {

                this._spherical.theta = Math.max( min, Math.min( max, this._spherical.theta ) );

            } else {

                this._spherical.theta = ( this._spherical.theta > ( min + max ) / 2 ) ?
                    Math.max( min, this._spherical.theta ) :
                    Math.min( max, this._spherical.theta );

            }

        }

        // restrict phi to be between desired limits
        this._spherical.phi = Math.max( this.minPolarAngle, Math.min( this.maxPolarAngle, this._spherical.phi ) );

        this._spherical.makeSafe();


        // move target to panned location

        if ( this.enableDamping === true ) {

            this.target.addScaledVector( this._panOffset, this.dampingFactor );

        } else {

            this.target.add( this._panOffset );

        }

        // Limit the target distance from the cursor to create a sphere around the center of interest
        this.target.sub( this.cursor );
        this.target.clampLength( this.minTargetRadius, this.maxTargetRadius );
        this.target.add( this.cursor );

        let zoomChanged = false;
        // adjust the camera position based on zoom only if we're not zooming to the cursor or if it's an ortho camera
        // we adjust zoom later in these cases
        if ( this.zoomToCursor && this._performCursorZoom || this.object.isOrthographicCamera ) {

            this._spherical.radius = this._clampDistance( this._spherical.radius );

        } else {

            const prevRadius = this._spherical.radius;
            this._spherical.radius = this._clampDistance( this._spherical.radius * this._scale );
            zoomChanged = prevRadius != this._spherical.radius;

        }

        _v.setFromSpherical( this._spherical );

        // rotate offset back to "camera-up-vector-is-up" space
        _v.applyQuaternion( this._quatInverse );

        position.copy( this.target ).add( _v );

        this.object.lookAt( this.target );

        if ( this.enableDamping === true ) {

            this._sphericalDelta.theta *= ( 1 - this.dampingFactor );
            this._sphericalDelta.phi *= ( 1 - this.dampingFactor );

            this._panOffset.multiplyScalar( 1 - this.dampingFactor );

        } else {

            this._sphericalDelta.set( 0, 0, 0 );

            this._panOffset.set( 0, 0, 0 );

        }

        // adjust camera position
        if ( this.zoomToCursor && this._performCursorZoom ) {

            let newRadius = null;
            if ( this.object.isPerspectiveCamera ) {

                // move the camera down the pointer ray
                // this method avoids floating point error
                const prevRadius = _v.length();
                newRadius = this._clampDistance( prevRadius * this._scale );

                const radiusDelta = prevRadius - newRadius;
                this.object.position.addScaledVector( this._dollyDirection, radiusDelta );
                this.object.updateMatrixWorld();

                zoomChanged = !! radiusDelta;

            } else if ( this.object.isOrthographicCamera ) {

                // adjust the ortho camera position based on zoom changes
                const mouseBefore = new Vector3( this._mouse.x, this._mouse.y, 0 );
                mouseBefore.unproject( this.object );

                const prevZoom = this.object.zoom;
                this.object.zoom = Math.max( this.minZoom, Math.min( this.maxZoom, this.object.zoom / this._scale ) );
                this.object.updateProjectionMatrix();

                zoomChanged = prevZoom !== this.object.zoom;

                const mouseAfter = new Vector3( this._mouse.x, this._mouse.y, 0 );
                mouseAfter.unproject( this.object );

                this.object.position.sub( mouseAfter ).add( mouseBefore );
                this.object.updateMatrixWorld();

                newRadius = _v.length();

            } else {

                console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - zoom to cursor disabled.' );
                this.zoomToCursor = false;

            }

            // handle the placement of the target
            if ( newRadius !== null ) {

                if ( this.screenSpacePanning ) {

                    // position the orbit target in front of the new camera position
                    this.target.set( 0, 0, - 1 )
                        .transformDirection( this.object.matrix )
                        .multiplyScalar( newRadius )
                        .add( this.object.position );

                } else {

                    // get the ray and translation plane to compute target
                    _ray.origin.copy( this.object.position );
                    _ray.direction.set( 0, 0, - 1 ).transformDirection( this.object.matrix );

                    // if the camera is 20 degrees above the horizon then don't adjust the focus target to avoid
                    // extremely large values
                    if ( Math.abs( this.object.up.dot( _ray.direction ) ) < _TILT_LIMIT ) {

                        this.object.lookAt( this.target );

                    } else {

                        _plane.setFromNormalAndCoplanarPoint( this.object.up, this.target );
                        _ray.intersectPlane( _plane, this.target );

                    }

                }

            }

        } else if ( this.object.isOrthographicCamera ) {

            const prevZoom = this.object.zoom;
            this.object.zoom = Math.max( this.minZoom, Math.min( this.maxZoom, this.object.zoom / this._scale ) );

            if ( prevZoom !== this.object.zoom ) {

                this.object.updateProjectionMatrix();
                zoomChanged = true;

            }

        }

        this._scale = 1;
        this._performCursorZoom = false;

        // update condition is:
        // min(camera displacement, camera rotation in radians)^2 > EPS
        // using small-angle approximation cos(x/2) = 1 - x^2 / 8

        if ( zoomChanged ||
            this._lastPosition.distanceToSquared( this.object.position ) > _EPS ||
            8 * ( 1 - this._lastQuaternion.dot( this.object.quaternion ) ) > _EPS ||
            this._lastTargetPosition.distanceToSquared( this.target ) > _EPS ) {

            this.dispatchEvent( _changeEvent );

            this._lastPosition.copy( this.object.position );
            this._lastQuaternion.copy( this.object.quaternion );
            this._lastTargetPosition.copy( this.target );

            return true;

        }

        return false;

    }
_getAutoRotationAngle(deltaTime: any): number
Code
_getAutoRotationAngle( deltaTime ) {

        if ( deltaTime !== null ) {

            return ( _twoPI / 60 * this.autoRotateSpeed ) * deltaTime;

        } else {

            return _twoPI / 60 / 60 * this.autoRotateSpeed;

        }

    }
_getZoomScale(delta: any): number
Code
_getZoomScale( delta ) {

        const normalizedDelta = Math.abs( delta * 0.01 );
        return Math.pow( 0.95, this.zoomSpeed * normalizedDelta );

    }
_rotateLeft(angle: any): void
Code
_rotateLeft( angle ) {

        this._sphericalDelta.theta -= angle;

    }
_rotateUp(angle: any): void
Code
_rotateUp( angle ) {

        this._sphericalDelta.phi -= angle;

    }
_panLeft(distance: any, objectMatrix: any): void
Code
_panLeft( distance, objectMatrix ) {

        _v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix
        _v.multiplyScalar( - distance );

        this._panOffset.add( _v );

    }
_panUp(distance: any, objectMatrix: any): void
Code
_panUp( distance, objectMatrix ) {

        if ( this.screenSpacePanning === true ) {

            _v.setFromMatrixColumn( objectMatrix, 1 );

        } else {

            _v.setFromMatrixColumn( objectMatrix, 0 );
            _v.crossVectors( this.object.up, _v );

        }

        _v.multiplyScalar( distance );

        this._panOffset.add( _v );

    }
_pan(deltaX: any, deltaY: any): void
Code
_pan( deltaX, deltaY ) {

        const element = this.domElement;

        if ( this.object.isPerspectiveCamera ) {

            // perspective
            const position = this.object.position;
            _v.copy( position ).sub( this.target );
            let targetDistance = _v.length();

            // half of the fov is center to top of screen
            targetDistance *= Math.tan( ( this.object.fov / 2 ) * Math.PI / 180.0 );

            // we use only clientHeight here so aspect ratio does not distort speed
            this._panLeft( 2 * deltaX * targetDistance / element.clientHeight, this.object.matrix );
            this._panUp( 2 * deltaY * targetDistance / element.clientHeight, this.object.matrix );

        } else if ( this.object.isOrthographicCamera ) {

            // orthographic
            this._panLeft( deltaX * ( this.object.right - this.object.left ) / this.object.zoom / element.clientWidth, this.object.matrix );
            this._panUp( deltaY * ( this.object.top - this.object.bottom ) / this.object.zoom / element.clientHeight, this.object.matrix );

        } else {

            // camera neither orthographic nor perspective
            console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' );
            this.enablePan = false;

        }

    }
_dollyOut(dollyScale: any): void
Code
_dollyOut( dollyScale ) {

        if ( this.object.isPerspectiveCamera || this.object.isOrthographicCamera ) {

            this._scale /= dollyScale;

        } else {

            console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' );
            this.enableZoom = false;

        }

    }
_dollyIn(dollyScale: any): void
Code
_dollyIn( dollyScale ) {

        if ( this.object.isPerspectiveCamera || this.object.isOrthographicCamera ) {

            this._scale *= dollyScale;

        } else {

            console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' );
            this.enableZoom = false;

        }

    }
_updateZoomParameters(x: any, y: any): void
Code
_updateZoomParameters( x, y ) {

        if ( ! this.zoomToCursor ) {

            return;

        }

        this._performCursorZoom = true;

        const rect = this.domElement.getBoundingClientRect();
        const dx = x - rect.left;
        const dy = y - rect.top;
        const w = rect.width;
        const h = rect.height;

        this._mouse.x = ( dx / w ) * 2 - 1;
        this._mouse.y = - ( dy / h ) * 2 + 1;

        this._dollyDirection.set( this._mouse.x, this._mouse.y, 1 ).unproject( this.object ).sub( this.object.position ).normalize();

    }
_clampDistance(dist: any): number
Code
_clampDistance( dist ) {

        return Math.max( this.minDistance, Math.min( this.maxDistance, dist ) );

    }
_handleMouseDownRotate(event: any): void
Code
_handleMouseDownRotate( event ) {

        this._rotateStart.set( event.clientX, event.clientY );

    }
_handleMouseDownDolly(event: any): void
Code
_handleMouseDownDolly( event ) {

        this._updateZoomParameters( event.clientX, event.clientX );
        this._dollyStart.set( event.clientX, event.clientY );

    }
_handleMouseDownPan(event: any): void
Code
_handleMouseDownPan( event ) {

        this._panStart.set( event.clientX, event.clientY );

    }
_handleMouseMoveRotate(event: any): void
Code
_handleMouseMoveRotate( event ) {

        this._rotateEnd.set( event.clientX, event.clientY );

        this._rotateDelta.subVectors( this._rotateEnd, this._rotateStart ).multiplyScalar( this.rotateSpeed );

        const element = this.domElement;

        this._rotateLeft( _twoPI * this._rotateDelta.x / element.clientHeight ); // yes, height

        this._rotateUp( _twoPI * this._rotateDelta.y / element.clientHeight );

        this._rotateStart.copy( this._rotateEnd );

        this.update();

    }
_handleMouseMoveDolly(event: any): void
Code
_handleMouseMoveDolly( event ) {

        this._dollyEnd.set( event.clientX, event.clientY );

        this._dollyDelta.subVectors( this._dollyEnd, this._dollyStart );

        if ( this._dollyDelta.y > 0 ) {

            this._dollyOut( this._getZoomScale( this._dollyDelta.y ) );

        } else if ( this._dollyDelta.y < 0 ) {

            this._dollyIn( this._getZoomScale( this._dollyDelta.y ) );

        }

        this._dollyStart.copy( this._dollyEnd );

        this.update();

    }
_handleMouseMovePan(event: any): void
Code
_handleMouseMovePan( event ) {

        this._panEnd.set( event.clientX, event.clientY );

        this._panDelta.subVectors( this._panEnd, this._panStart ).multiplyScalar( this.panSpeed );

        this._pan( this._panDelta.x, this._panDelta.y );

        this._panStart.copy( this._panEnd );

        this.update();

    }
_handleMouseWheel(event: any): void
Code
_handleMouseWheel( event ) {

        this._updateZoomParameters( event.clientX, event.clientY );

        if ( event.deltaY < 0 ) {

            this._dollyIn( this._getZoomScale( event.deltaY ) );

        } else if ( event.deltaY > 0 ) {

            this._dollyOut( this._getZoomScale( event.deltaY ) );

        }

        this.update();

    }
_handleKeyDown(event: any): void
Code
_handleKeyDown( event ) {

        let needsUpdate = false;

        switch ( event.code ) {

            case this.keys.UP:

                if ( event.ctrlKey || event.metaKey || event.shiftKey ) {

                    if ( this.enableRotate ) {

                        this._rotateUp( _twoPI * this.keyRotateSpeed / this.domElement.clientHeight );

                    }

                } else {

                    if ( this.enablePan ) {

                        this._pan( 0, this.keyPanSpeed );

                    }

                }

                needsUpdate = true;
                break;

            case this.keys.BOTTOM:

                if ( event.ctrlKey || event.metaKey || event.shiftKey ) {

                    if ( this.enableRotate ) {

                        this._rotateUp( - _twoPI * this.keyRotateSpeed / this.domElement.clientHeight );

                    }

                } else {

                    if ( this.enablePan ) {

                        this._pan( 0, - this.keyPanSpeed );

                    }

                }

                needsUpdate = true;
                break;

            case this.keys.LEFT:

                if ( event.ctrlKey || event.metaKey || event.shiftKey ) {

                    if ( this.enableRotate ) {

                        this._rotateLeft( _twoPI * this.keyRotateSpeed / this.domElement.clientHeight );

                    }

                } else {

                    if ( this.enablePan ) {

                        this._pan( this.keyPanSpeed, 0 );

                    }

                }

                needsUpdate = true;
                break;

            case this.keys.RIGHT:

                if ( event.ctrlKey || event.metaKey || event.shiftKey ) {

                    if ( this.enableRotate ) {

                        this._rotateLeft( - _twoPI * this.keyRotateSpeed / this.domElement.clientHeight );

                    }

                } else {

                    if ( this.enablePan ) {

                        this._pan( - this.keyPanSpeed, 0 );

                    }

                }

                needsUpdate = true;
                break;

        }

        if ( needsUpdate ) {

            // prevent the browser from scrolling on cursor keys
            event.preventDefault();

            this.update();

        }


    }
_handleTouchStartRotate(event: any): void
Code
_handleTouchStartRotate( event ) {

        if ( this._pointers.length === 1 ) {

            this._rotateStart.set( event.pageX, event.pageY );

        } else {

            const position = this._getSecondPointerPosition( event );

            const x = 0.5 * ( event.pageX + position.x );
            const y = 0.5 * ( event.pageY + position.y );

            this._rotateStart.set( x, y );

        }

    }
_handleTouchStartPan(event: any): void
Code
_handleTouchStartPan( event ) {

        if ( this._pointers.length === 1 ) {

            this._panStart.set( event.pageX, event.pageY );

        } else {

            const position = this._getSecondPointerPosition( event );

            const x = 0.5 * ( event.pageX + position.x );
            const y = 0.5 * ( event.pageY + position.y );

            this._panStart.set( x, y );

        }

    }
_handleTouchStartDolly(event: any): void
Code
_handleTouchStartDolly( event ) {

        const position = this._getSecondPointerPosition( event );

        const dx = event.pageX - position.x;
        const dy = event.pageY - position.y;

        const distance = Math.sqrt( dx * dx + dy * dy );

        this._dollyStart.set( 0, distance );

    }
_handleTouchStartDollyPan(event: any): void
Code
_handleTouchStartDollyPan( event ) {

        if ( this.enableZoom ) this._handleTouchStartDolly( event );

        if ( this.enablePan ) this._handleTouchStartPan( event );

    }
_handleTouchStartDollyRotate(event: any): void
Code
_handleTouchStartDollyRotate( event ) {

        if ( this.enableZoom ) this._handleTouchStartDolly( event );

        if ( this.enableRotate ) this._handleTouchStartRotate( event );

    }
_handleTouchMoveRotate(event: any): void
Code
_handleTouchMoveRotate( event ) {

        if ( this._pointers.length == 1 ) {

            this._rotateEnd.set( event.pageX, event.pageY );

        } else {

            const position = this._getSecondPointerPosition( event );

            const x = 0.5 * ( event.pageX + position.x );
            const y = 0.5 * ( event.pageY + position.y );

            this._rotateEnd.set( x, y );

        }

        this._rotateDelta.subVectors( this._rotateEnd, this._rotateStart ).multiplyScalar( this.rotateSpeed );

        const element = this.domElement;

        this._rotateLeft( _twoPI * this._rotateDelta.x / element.clientHeight ); // yes, height

        this._rotateUp( _twoPI * this._rotateDelta.y / element.clientHeight );

        this._rotateStart.copy( this._rotateEnd );

    }
_handleTouchMovePan(event: any): void
Code
_handleTouchMovePan( event ) {

        if ( this._pointers.length === 1 ) {

            this._panEnd.set( event.pageX, event.pageY );

        } else {

            const position = this._getSecondPointerPosition( event );

            const x = 0.5 * ( event.pageX + position.x );
            const y = 0.5 * ( event.pageY + position.y );

            this._panEnd.set( x, y );

        }

        this._panDelta.subVectors( this._panEnd, this._panStart ).multiplyScalar( this.panSpeed );

        this._pan( this._panDelta.x, this._panDelta.y );

        this._panStart.copy( this._panEnd );

    }
_handleTouchMoveDolly(event: any): void
Code
_handleTouchMoveDolly( event ) {

        const position = this._getSecondPointerPosition( event );

        const dx = event.pageX - position.x;
        const dy = event.pageY - position.y;

        const distance = Math.sqrt( dx * dx + dy * dy );

        this._dollyEnd.set( 0, distance );

        this._dollyDelta.set( 0, Math.pow( this._dollyEnd.y / this._dollyStart.y, this.zoomSpeed ) );

        this._dollyOut( this._dollyDelta.y );

        this._dollyStart.copy( this._dollyEnd );

        const centerX = ( event.pageX + position.x ) * 0.5;
        const centerY = ( event.pageY + position.y ) * 0.5;

        this._updateZoomParameters( centerX, centerY );

    }
_handleTouchMoveDollyPan(event: any): void
Code
_handleTouchMoveDollyPan( event ) {

        if ( this.enableZoom ) this._handleTouchMoveDolly( event );

        if ( this.enablePan ) this._handleTouchMovePan( event );

    }
_handleTouchMoveDollyRotate(event: any): void
Code
_handleTouchMoveDollyRotate( event ) {

        if ( this.enableZoom ) this._handleTouchMoveDolly( event );

        if ( this.enableRotate ) this._handleTouchMoveRotate( event );

    }
_addPointer(event: any): void
Code
_addPointer( event ) {

        this._pointers.push( event.pointerId );

    }
_removePointer(event: any): void
Code
_removePointer( event ) {

        delete this._pointerPositions[ event.pointerId ];

        for ( let i = 0; i < this._pointers.length; i ++ ) {

            if ( this._pointers[ i ] == event.pointerId ) {

                this._pointers.splice( i, 1 );
                return;

            }

        }

    }
_isTrackingPointer(event: any): boolean
Code
_isTrackingPointer( event ) {

        for ( let i = 0; i < this._pointers.length; i ++ ) {

            if ( this._pointers[ i ] == event.pointerId ) return true;

        }

        return false;

    }
_trackPointer(event: any): void
Code
_trackPointer( event ) {

        let position = this._pointerPositions[ event.pointerId ];

        if ( position === undefined ) {

            position = new Vector2();
            this._pointerPositions[ event.pointerId ] = position;

        }

        position.set( event.pageX, event.pageY );

    }
_getSecondPointerPosition(event: any): any
Code
_getSecondPointerPosition( event ) {

        const pointerId = ( event.pointerId === this._pointers[ 0 ] ) ? this._pointers[ 1 ] : this._pointers[ 0 ];

        return this._pointerPositions[ pointerId ];

    }
_customWheelEvent(event: any): { clientX: any; clientY: any; deltaY: any; }
Code
_customWheelEvent( event ) {

        const mode = event.deltaMode;

        // minimal wheel event altered to meet delta-zoom demand
        const newEvent = {
            clientX: event.clientX,
            clientY: event.clientY,
            deltaY: event.deltaY,
        };

        switch ( mode ) {

            case 1: // LINE_MODE
                newEvent.deltaY *= 16;
                break;

            case 2: // PAGE_MODE
                newEvent.deltaY *= 100;
                break;

        }

        // detect if event was triggered by pinching
        if ( event.ctrlKey && ! this._controlActive ) {

            newEvent.deltaY *= 10;

        }

        return newEvent;

    }