From 184f21483e875bd9d976866f53cf285171cfd12b Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Fri, 9 Aug 2024 13:11:59 +0200 Subject: [PATCH] FlyControls: Derive from `Controls`. (#29095) --- docs/examples/en/controls/Controls.html | 3 +- docs/examples/en/controls/FlyControls.html | 36 +- examples/jsm/controls/FlyControls.js | 382 +++++++++++---------- 3 files changed, 200 insertions(+), 221 deletions(-) diff --git a/docs/examples/en/controls/Controls.html b/docs/examples/en/controls/Controls.html index af9f5575354d5a..971e4a958bcb7d 100644 --- a/docs/examples/en/controls/Controls.html +++ b/docs/examples/en/controls/Controls.html @@ -102,12 +102,11 @@

[method:undefined dispose] ()

Call this method if you no longer want use to the controls. It frees all internal resources and removes all event listeners.

-

[method:undefined update] ()

+

[method:undefined update] ( [param:Number delta] )

Controls should implement this method if they have to update their internal state per simulation step.

-

Source

diff --git a/docs/examples/en/controls/FlyControls.html b/docs/examples/en/controls/FlyControls.html index 8e50998b7ee9d6..89d0b9ce972d6f 100644 --- a/docs/examples/en/controls/FlyControls.html +++ b/docs/examples/en/controls/FlyControls.html @@ -7,6 +7,7 @@ + [page:Controls] →

[name]

@@ -54,35 +55,21 @@

change

Properties

+

See the base [page:Controls] class for common properties.

+

[property:Boolean autoForward]

If set to `true`, the camera automatically moves forward (and does not stop) when initially translated. Default is `false`.

-

[property:HTMLDOMElement domElement]

-

- The HTMLDOMElement used to listen for mouse / touch events. This must be passed in the constructor; changing it here will - not set up new event listeners. -

-

[property:Boolean dragToLook]

If set to `true`, you can only look around by performing a drag interaction. Default is `false`.

-

[property:Boolean enabled]

-

- When set to `false`, the controls will not respond to user input. Default is `true`. -

-

[property:Number movementSpeed]

- The movement speed. Default is *1*. -

- -

[property:Camera object]

-

- The camera to be controlled. + The movement speed. Default is `1`.

[property:Number rollSpeed]

@@ -92,20 +79,7 @@

[property:Number rollSpeed]

Methods

-

[method:undefined dispose] ()

-

- Should be called if the controls is no longer required. -

- -

[method:undefined update] ( [param:Number delta] )

-

-

- [page:Number delta]: Time delta value. -

-

- Updates the controls. Usually called in the animation loop. -

-

+

See the base [page:Controls] class for common methods.

Source

diff --git a/examples/jsm/controls/FlyControls.js b/examples/jsm/controls/FlyControls.js index 5026bde1319c78..b49153682fc740 100644 --- a/examples/jsm/controls/FlyControls.js +++ b/examples/jsm/controls/FlyControls.js @@ -1,24 +1,19 @@ import { - EventDispatcher, Quaternion, Vector3 } from 'three'; +import { Controls } from './Controls.js'; const _changeEvent = { type: 'change' }; -class FlyControls extends EventDispatcher { +const _EPS = 0.000001; +const _tmpQuaternion = new Quaternion(); - constructor( object, domElement ) { +class FlyControls extends Controls { - super(); + constructor( object, domElement = null ) { - this.object = object; - this.domElement = domElement; - - // API - - // Set to false to disable this control - this.enabled = true; + super( object, domElement ); this.movementSpeed = 1.0; this.rollSpeed = 0.005; @@ -26,301 +21,312 @@ class FlyControls extends EventDispatcher { this.dragToLook = false; this.autoForward = false; - // disable default target object behavior - // internals - const scope = this; + this._moveState = { up: 0, down: 0, left: 0, right: 0, forward: 0, back: 0, pitchUp: 0, pitchDown: 0, yawLeft: 0, yawRight: 0, rollLeft: 0, rollRight: 0 }; + this._moveVector = new Vector3( 0, 0, 0 ); + this._rotationVector = new Vector3( 0, 0, 0 ); + this._lastQuaternion = new Quaternion(); + this._lastPosition = new Vector3(); + this._status = 0; - const EPS = 0.000001; + // event listeners - const lastQuaternion = new Quaternion(); - const lastPosition = new Vector3(); + this._onKeyDown = onKeyDown.bind( this ); + this._onKeyUp = onKeyUp.bind( this ); + this._onPointerMove = onPointerMove.bind( this ); + this._onPointerDown = onPointerDown.bind( this ); + this._onPointerUp = onPointerUp.bind( this ); + this._onPointerCancel = onPointerCancel.bind( this ); + this._onContextMenu = onContextMenu.bind( this ); - this.tmpQuaternion = new Quaternion(); + // - this.status = 0; + if ( domElement !== null ) { - this.moveState = { up: 0, down: 0, left: 0, right: 0, forward: 0, back: 0, pitchUp: 0, pitchDown: 0, yawLeft: 0, yawRight: 0, rollLeft: 0, rollRight: 0 }; - this.moveVector = new Vector3( 0, 0, 0 ); - this.rotationVector = new Vector3( 0, 0, 0 ); + this.connect(); - this.keydown = function ( event ) { + } - if ( event.altKey || this.enabled === false ) { + } - return; + connect() { - } + window.addEventListener( 'keydown', this._onKeyDown ); + window.addEventListener( 'keyup', this._onKeyUp ); - switch ( event.code ) { + this.domElement.addEventListener( 'pointermove', this._onPointerMove ); + this.domElement.addEventListener( 'pointerdown', this._onPointerDown ); + this.domElement.addEventListener( 'pointerup', this._onPointerUp ); + this.domElement.addEventListener( 'pointercancel', this._onPointerCancel ); + this.domElement.addEventListener( 'contextmenu', this._onContextMenu ); - case 'ShiftLeft': - case 'ShiftRight': this.movementSpeedMultiplier = .1; break; + } - case 'KeyW': this.moveState.forward = 1; break; - case 'KeyS': this.moveState.back = 1; break; + disconnect() { - case 'KeyA': this.moveState.left = 1; break; - case 'KeyD': this.moveState.right = 1; break; + window.removeEventListener( 'keydown', this._onKeyDown ); + window.removeEventListener( 'keyup', this._onKeyUp ); - case 'KeyR': this.moveState.up = 1; break; - case 'KeyF': this.moveState.down = 1; break; + this.domElement.removeEventListener( 'pointermove', this._onPointerMove ); + this.domElement.removeEventListener( 'pointerdown', this._onPointerDown ); + this.domElement.removeEventListener( 'pointerup', this._onPointerUp ); + this.domElement.removeEventListener( 'pointercancel', this._onPointerCancel ); + this.domElement.removeEventListener( 'contextmenu', this._onContextMenu ); - case 'ArrowUp': this.moveState.pitchUp = 1; break; - case 'ArrowDown': this.moveState.pitchDown = 1; break; + } - case 'ArrowLeft': this.moveState.yawLeft = 1; break; - case 'ArrowRight': this.moveState.yawRight = 1; break; + dispose() { - case 'KeyQ': this.moveState.rollLeft = 1; break; - case 'KeyE': this.moveState.rollRight = 1; break; + this.disconnect(); - } + } - this.updateMovementVector(); - this.updateRotationVector(); + update( delta ) { - }; + if ( this.enabled === false ) return; - this.keyup = function ( event ) { + const object = this.object; - if ( this.enabled === false ) return; + const moveMult = delta * this.movementSpeed; + const rotMult = delta * this.rollSpeed; - switch ( event.code ) { + object.translateX( this._moveVector.x * moveMult ); + object.translateY( this._moveVector.y * moveMult ); + object.translateZ( this._moveVector.z * moveMult ); - case 'ShiftLeft': - case 'ShiftRight': this.movementSpeedMultiplier = 1; break; + _tmpQuaternion.set( this._rotationVector.x * rotMult, this._rotationVector.y * rotMult, this._rotationVector.z * rotMult, 1 ).normalize(); + object.quaternion.multiply( _tmpQuaternion ); - case 'KeyW': this.moveState.forward = 0; break; - case 'KeyS': this.moveState.back = 0; break; + if ( + this._lastPosition.distanceToSquared( object.position ) > _EPS || + 8 * ( 1 - this._lastQuaternion.dot( object.quaternion ) ) > _EPS + ) { - case 'KeyA': this.moveState.left = 0; break; - case 'KeyD': this.moveState.right = 0; break; + this.dispatchEvent( _changeEvent ); + this._lastQuaternion.copy( object.quaternion ); + this._lastPosition.copy( object.position ); - case 'KeyR': this.moveState.up = 0; break; - case 'KeyF': this.moveState.down = 0; break; + } - case 'ArrowUp': this.moveState.pitchUp = 0; break; - case 'ArrowDown': this.moveState.pitchDown = 0; break; + } - case 'ArrowLeft': this.moveState.yawLeft = 0; break; - case 'ArrowRight': this.moveState.yawRight = 0; break; + // private - case 'KeyQ': this.moveState.rollLeft = 0; break; - case 'KeyE': this.moveState.rollRight = 0; break; + _updateMovementVector() { - } + const forward = ( this._moveState.forward || ( this.autoForward && ! this._moveState.back ) ) ? 1 : 0; - this.updateMovementVector(); - this.updateRotationVector(); + this._moveVector.x = ( - this._moveState.left + this._moveState.right ); + this._moveVector.y = ( - this._moveState.down + this._moveState.up ); + this._moveVector.z = ( - forward + this._moveState.back ); - }; + //console.log( 'move:', [ this._moveVector.x, this._moveVector.y, this._moveVector.z ] ); - this.pointerdown = function ( event ) { + } - if ( this.enabled === false ) return; + _updateRotationVector() { - if ( this.dragToLook ) { + this._rotationVector.x = ( - this._moveState.pitchDown + this._moveState.pitchUp ); + this._rotationVector.y = ( - this._moveState.yawRight + this._moveState.yawLeft ); + this._rotationVector.z = ( - this._moveState.rollRight + this._moveState.rollLeft ); - this.status ++; + //console.log( 'rotate:', [ this._rotationVector.x, this._rotationVector.y, this._rotationVector.z ] ); - } else { + } - switch ( event.button ) { + _getContainerDimensions() { - case 0: this.moveState.forward = 1; break; - case 2: this.moveState.back = 1; break; + if ( this.domElement != document ) { - } + return { + size: [ this.domElement.offsetWidth, this.domElement.offsetHeight ], + offset: [ this.domElement.offsetLeft, this.domElement.offsetTop ] + }; - this.updateMovementVector(); + } else { - } + return { + size: [ window.innerWidth, window.innerHeight ], + offset: [ 0, 0 ] + }; - }; + } - this.pointermove = function ( event ) { + } - if ( this.enabled === false ) return; +} - if ( ! this.dragToLook || this.status > 0 ) { +function onKeyDown( event ) { - const container = this.getContainerDimensions(); - const halfWidth = container.size[ 0 ] / 2; - const halfHeight = container.size[ 1 ] / 2; + if ( event.altKey || this.enabled === false ) { - this.moveState.yawLeft = - ( ( event.pageX - container.offset[ 0 ] ) - halfWidth ) / halfWidth; - this.moveState.pitchDown = ( ( event.pageY - container.offset[ 1 ] ) - halfHeight ) / halfHeight; + return; - this.updateRotationVector(); + } + + switch ( event.code ) { - } + case 'ShiftLeft': + case 'ShiftRight': this.movementSpeedMultiplier = .1; break; - }; + case 'KeyW': this._moveState.forward = 1; break; + case 'KeyS': this._moveState.back = 1; break; - this.pointerup = function ( event ) { + case 'KeyA': this._moveState.left = 1; break; + case 'KeyD': this._moveState.right = 1; break; - if ( this.enabled === false ) return; + case 'KeyR': this._moveState.up = 1; break; + case 'KeyF': this._moveState.down = 1; break; - if ( this.dragToLook ) { + case 'ArrowUp': this._moveState.pitchUp = 1; break; + case 'ArrowDown': this._moveState.pitchDown = 1; break; - this.status --; + case 'ArrowLeft': this._moveState.yawLeft = 1; break; + case 'ArrowRight': this._moveState.yawRight = 1; break; - this.moveState.yawLeft = this.moveState.pitchDown = 0; + case 'KeyQ': this._moveState.rollLeft = 1; break; + case 'KeyE': this._moveState.rollRight = 1; break; - } else { + } - switch ( event.button ) { + this._updateMovementVector(); + this._updateRotationVector(); - case 0: this.moveState.forward = 0; break; - case 2: this.moveState.back = 0; break; +} - } +function onKeyUp( event ) { - this.updateMovementVector(); + if ( this.enabled === false ) return; - } + switch ( event.code ) { - this.updateRotationVector(); + case 'ShiftLeft': + case 'ShiftRight': this.movementSpeedMultiplier = 1; break; - }; + case 'KeyW': this._moveState.forward = 0; break; + case 'KeyS': this._moveState.back = 0; break; - this.pointercancel = function () { + case 'KeyA': this._moveState.left = 0; break; + case 'KeyD': this._moveState.right = 0; break; - if ( this.enabled === false ) return; + case 'KeyR': this._moveState.up = 0; break; + case 'KeyF': this._moveState.down = 0; break; - if ( this.dragToLook ) { + case 'ArrowUp': this._moveState.pitchUp = 0; break; + case 'ArrowDown': this._moveState.pitchDown = 0; break; - this.status = 0; + case 'ArrowLeft': this._moveState.yawLeft = 0; break; + case 'ArrowRight': this._moveState.yawRight = 0; break; - this.moveState.yawLeft = this.moveState.pitchDown = 0; + case 'KeyQ': this._moveState.rollLeft = 0; break; + case 'KeyE': this._moveState.rollRight = 0; break; - } else { + } - this.moveState.forward = 0; - this.moveState.back = 0; + this._updateMovementVector(); + this._updateRotationVector(); - this.updateMovementVector(); +} - } +function onPointerDown( event ) { - this.updateRotationVector(); + if ( this.enabled === false ) return; - }; + if ( this.dragToLook ) { - this.contextMenu = function ( event ) { + this._status ++; - if ( this.enabled === false ) return; + } else { - event.preventDefault(); + switch ( event.button ) { - }; + case 0: this._moveState.forward = 1; break; + case 2: this._moveState.back = 1; break; - this.update = function ( delta ) { + } - if ( this.enabled === false ) return; + this._updateMovementVector(); - const moveMult = delta * scope.movementSpeed; - const rotMult = delta * scope.rollSpeed; + } - scope.object.translateX( scope.moveVector.x * moveMult ); - scope.object.translateY( scope.moveVector.y * moveMult ); - scope.object.translateZ( scope.moveVector.z * moveMult ); +} - scope.tmpQuaternion.set( scope.rotationVector.x * rotMult, scope.rotationVector.y * rotMult, scope.rotationVector.z * rotMult, 1 ).normalize(); - scope.object.quaternion.multiply( scope.tmpQuaternion ); +function onPointerMove( event ) { - if ( - lastPosition.distanceToSquared( scope.object.position ) > EPS || - 8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS - ) { + if ( this.enabled === false ) return; - scope.dispatchEvent( _changeEvent ); - lastQuaternion.copy( scope.object.quaternion ); - lastPosition.copy( scope.object.position ); + if ( ! this.dragToLook || this._status > 0 ) { - } + const container = this._getContainerDimensions(); + const halfWidth = container.size[ 0 ] / 2; + const halfHeight = container.size[ 1 ] / 2; - }; + this._moveState.yawLeft = - ( ( event.pageX - container.offset[ 0 ] ) - halfWidth ) / halfWidth; + this._moveState.pitchDown = ( ( event.pageY - container.offset[ 1 ] ) - halfHeight ) / halfHeight; - this.updateMovementVector = function () { + this._updateRotationVector(); - const forward = ( this.moveState.forward || ( this.autoForward && ! this.moveState.back ) ) ? 1 : 0; + } - this.moveVector.x = ( - this.moveState.left + this.moveState.right ); - this.moveVector.y = ( - this.moveState.down + this.moveState.up ); - this.moveVector.z = ( - forward + this.moveState.back ); +} - //console.log( 'move:', [ this.moveVector.x, this.moveVector.y, this.moveVector.z ] ); +function onPointerUp( event ) { - }; + if ( this.enabled === false ) return; - this.updateRotationVector = function () { + if ( this.dragToLook ) { - this.rotationVector.x = ( - this.moveState.pitchDown + this.moveState.pitchUp ); - this.rotationVector.y = ( - this.moveState.yawRight + this.moveState.yawLeft ); - this.rotationVector.z = ( - this.moveState.rollRight + this.moveState.rollLeft ); + this._status --; - //console.log( 'rotate:', [ this.rotationVector.x, this.rotationVector.y, this.rotationVector.z ] ); + this._moveState.yawLeft = this._moveState.pitchDown = 0; - }; + } else { - this.getContainerDimensions = function () { + switch ( event.button ) { - if ( this.domElement != document ) { + case 0: this._moveState.forward = 0; break; + case 2: this._moveState.back = 0; break; - return { - size: [ this.domElement.offsetWidth, this.domElement.offsetHeight ], - offset: [ this.domElement.offsetLeft, this.domElement.offsetTop ] - }; + } - } else { + this._updateMovementVector(); - return { - size: [ window.innerWidth, window.innerHeight ], - offset: [ 0, 0 ] - }; + } - } + this._updateRotationVector(); - }; +} - this.dispose = function () { +function onPointerCancel() { - this.domElement.removeEventListener( 'contextmenu', _contextmenu ); - this.domElement.removeEventListener( 'pointerdown', _pointerdown ); - this.domElement.removeEventListener( 'pointermove', _pointermove ); - this.domElement.removeEventListener( 'pointerup', _pointerup ); - this.domElement.removeEventListener( 'pointercancel', _pointercancel ); + if ( this.enabled === false ) return; - window.removeEventListener( 'keydown', _keydown ); - window.removeEventListener( 'keyup', _keyup ); + if ( this.dragToLook ) { - }; + this._status = 0; - const _contextmenu = this.contextMenu.bind( this ); - const _pointermove = this.pointermove.bind( this ); - const _pointerdown = this.pointerdown.bind( this ); - const _pointerup = this.pointerup.bind( this ); - const _pointercancel = this.pointercancel.bind( this ); - const _keydown = this.keydown.bind( this ); - const _keyup = this.keyup.bind( this ); + this._moveState.yawLeft = this._moveState.pitchDown = 0; - this.domElement.addEventListener( 'contextmenu', _contextmenu ); - this.domElement.addEventListener( 'pointerdown', _pointerdown ); - this.domElement.addEventListener( 'pointermove', _pointermove ); - this.domElement.addEventListener( 'pointerup', _pointerup ); - this.domElement.addEventListener( 'pointercancel', _pointercancel ); + } else { - window.addEventListener( 'keydown', _keydown ); - window.addEventListener( 'keyup', _keyup ); + this._moveState.forward = 0; + this._moveState.back = 0; - this.updateMovementVector(); - this.updateRotationVector(); + this._updateMovementVector(); } + this._updateRotationVector(); + +} + +function onContextMenu( event ) { + + if ( this.enabled === false ) return; + + event.preventDefault(); + } export { FlyControls };