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 };