Quellcode durchsuchen

FirstPersonControls: Damping, separate move sources, release fix, E/Q keys. (#33874)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
mrdoob vor 2 Wochen
Ursprung
Commit
44bee29b90
1 geänderte Dateien mit 79 neuen und 42 gelöschten Zeilen
  1. 79 42
      examples/jsm/controls/FirstPersonControls.js

+ 79 - 42
examples/jsm/controls/FirstPersonControls.js

@@ -9,6 +9,7 @@ const _lookDirection = new Vector3();
 const _spherical = new Spherical();
 const _target = new Vector3();
 const _targetPosition = new Vector3();
+const _targetVelocity = new Vector3();
 
 /**
  * This class is an alternative implementation of {@link FlyControls}.
@@ -44,6 +45,15 @@ class FirstPersonControls extends Controls {
 		 */
 		this.lookSpeed = 0.005;
 
+		/**
+		 * How quickly the movement and look velocity catches up to the input. Lower
+		 * values feel heavier (more inertia), `1` disables damping.
+		 *
+		 * @type {number}
+		 * @default 0.1
+		 */
+		this.dampingFactor = 0.1;
+
 		/**
 		 * Whether it's possible to vertically look around or not.
 		 *
@@ -128,7 +138,7 @@ class FirstPersonControls extends Controls {
 
 		// internals
 
-		this._autoSpeedFactor = 0.0;
+		this._velocity = new Vector3();
 
 		this._pointerX = 0;
 		this._pointerY = 0;
@@ -138,14 +148,23 @@ class FirstPersonControls extends Controls {
 
 		this._pointerCount = 0;
 
-		this._moveForward = false;
-		this._moveBackward = false;
+		// forward / backward come from keys and the pointer, tracked per source so they don't
+		// clobber: while a forward / backward key is held, a click only looks
+		this._keyForward = false;
+		this._keyBackward = false;
+		this._pointerForward = false;
+		this._pointerBackward = false;
 		this._moveLeft = false;
 		this._moveRight = false;
+		this._moveUp = false;
+		this._moveDown = false;
 
 		this._lat = 0;
 		this._lon = 0;
 
+		this._lonVelocity = 0;
+		this._latVelocity = 0;
+
 		// event listeners
 
 		this._onPointerMove = onPointerMove.bind( this );
@@ -174,11 +193,15 @@ class FirstPersonControls extends Controls {
 		window.addEventListener( 'keydown', this._onKeyDown );
 		window.addEventListener( 'keyup', this._onKeyUp );
 
-		this.domElement.addEventListener( 'pointermove', this._onPointerMove );
 		this.domElement.addEventListener( 'pointerdown', this._onPointerDown );
-		this.domElement.addEventListener( 'pointerup', this._onPointerUp );
 		this.domElement.addEventListener( 'contextmenu', this._onContextMenu );
 
+		const { ownerDocument } = this.domElement;
+
+		ownerDocument.addEventListener( 'pointermove', this._onPointerMove );
+		ownerDocument.addEventListener( 'pointerup', this._onPointerUp );
+		ownerDocument.addEventListener( 'pointercancel', this._onPointerUp );
+
 		this.domElement.style.touchAction = 'none'; // Disable touch scroll
 
 	}
@@ -188,11 +211,15 @@ class FirstPersonControls extends Controls {
 		window.removeEventListener( 'keydown', this._onKeyDown );
 		window.removeEventListener( 'keyup', this._onKeyUp );
 
-		this.domElement.removeEventListener( 'pointermove', this._onPointerMove );
 		this.domElement.removeEventListener( 'pointerdown', this._onPointerDown );
-		this.domElement.removeEventListener( 'pointerup', this._onPointerUp );
 		this.domElement.removeEventListener( 'contextmenu', this._onContextMenu );
 
+		const { ownerDocument } = this.domElement;
+
+		ownerDocument.removeEventListener( 'pointermove', this._onPointerMove );
+		ownerDocument.removeEventListener( 'pointerup', this._onPointerUp );
+		ownerDocument.removeEventListener( 'pointercancel', this._onPointerUp );
+
 		this.domElement.style.touchAction = ''; // Restore touch scroll
 
 	}
@@ -235,31 +262,35 @@ class FirstPersonControls extends Controls {
 
 		if ( this.enabled === false ) return;
 
-		if ( this.heightSpeed ) {
+		const moveForward = this._keyForward || this._pointerForward;
+		const moveBackward = this._keyBackward || this._pointerBackward;
 
-			const y = MathUtils.clamp( this.object.position.y, this.heightMin, this.heightMax );
-			const heightDelta = y - this.heightMin;
+		const forward = moveForward || ( this.autoForward && ! moveBackward );
 
-			this._autoSpeedFactor = delta * ( heightDelta * this.heightCoef );
+		// target velocity in the object's local space
 
-		} else {
+		_targetVelocity.set(
+			( this._moveRight ? 1 : 0 ) - ( this._moveLeft ? 1 : 0 ),
+			( this._moveUp ? 1 : 0 ) - ( this._moveDown ? 1 : 0 ),
+			( moveBackward ? 1 : 0 ) - ( forward ? 1 : 0 )
+		).multiplyScalar( this.movementSpeed );
 
-			this._autoSpeedFactor = 0.0;
+		// faster forward movement the higher the camera is
 
-		}
+		if ( forward && this.heightSpeed ) {
 
-		const actualMoveSpeed = delta * this.movementSpeed;
+			const y = MathUtils.clamp( this.object.position.y, this.heightMin, this.heightMax );
+			_targetVelocity.z -= ( y - this.heightMin ) * this.heightCoef;
 
-		if ( this._moveForward || ( this.autoForward && ! this._moveBackward ) ) this.object.translateZ( - ( actualMoveSpeed + this._autoSpeedFactor ) );
-		if ( this._moveBackward ) this.object.translateZ( actualMoveSpeed );
+		}
 
-		if ( this._moveLeft ) this.object.translateX( - actualMoveSpeed );
-		if ( this._moveRight ) this.object.translateX( actualMoveSpeed );
+		// ease toward the target velocity for smooth acceleration and deceleration
 
-		if ( this._moveUp ) this.object.translateY( actualMoveSpeed );
-		if ( this._moveDown ) this.object.translateY( - actualMoveSpeed );
+		this._velocity.lerp( _targetVelocity, this.dampingFactor );
 
-		const actualLookSpeed = delta * this.lookSpeed;
+		this.object.translateX( this._velocity.x * delta );
+		this.object.translateY( this._velocity.y * delta );
+		this.object.translateZ( this._velocity.z * delta );
 
 		let verticalLookRatio = 1;
 
@@ -269,12 +300,16 @@ class FirstPersonControls extends Controls {
 
 		}
 
-		if ( this.mouseDragOn ) {
+		// target look velocity, zero when not dragging so the view eases to a stop
 
-			this._lon -= this._pointerX * actualLookSpeed;
-			if ( this.lookVertical ) this._lat -= this._pointerY * actualLookSpeed * verticalLookRatio;
+		const targetLon = this.mouseDragOn ? - this._pointerX * this.lookSpeed : 0;
+		const targetLat = ( this.mouseDragOn && this.lookVertical ) ? - this._pointerY * this.lookSpeed * verticalLookRatio : 0;
 
-		}
+		this._lonVelocity = MathUtils.lerp( this._lonVelocity, targetLon, this.dampingFactor );
+		this._latVelocity = MathUtils.lerp( this._latVelocity, targetLat, this.dampingFactor );
+
+		this._lon += this._lonVelocity * delta;
+		this._lat += this._latVelocity * delta;
 
 		this._lat = Math.max( - 85, Math.min( 85, this._lat ) );
 
@@ -332,15 +367,15 @@ function onPointerDown( event ) {
 
 	if ( event.pointerType === 'touch' ) {
 
-		this._moveForward = this._pointerCount === 1;
-		this._moveBackward = this._pointerCount >= 2;
+		this._pointerForward = this._pointerCount === 1;
+		this._pointerBackward = this._pointerCount >= 2;
 
 	} else {
 
 		switch ( event.button ) {
 
-			case 0: this._moveForward = true; break;
-			case 2: this._moveBackward = true; break;
+			case 0: if ( ! this._keyForward && ! this._keyBackward ) this._pointerForward = true; break;
+			case 2: if ( ! this._keyForward && ! this._keyBackward ) this._pointerBackward = true; break;
 
 		}
 
@@ -358,21 +393,23 @@ function onPointerDown( event ) {
 
 function onPointerUp( event ) {
 
+	if ( this.mouseDragOn === false ) return;
+
 	this.domElement.releasePointerCapture( event.pointerId );
 
 	this._pointerCount --;
 
 	if ( event.pointerType === 'touch' ) {
 
-		this._moveForward = this._pointerCount === 1;
-		this._moveBackward = false;
+		this._pointerForward = this._pointerCount === 1;
+		this._pointerBackward = false;
 
 	} else {
 
 		switch ( event.button ) {
 
-			case 0: this._moveForward = false; break;
-			case 2: this._moveBackward = false; break;
+			case 0: this._pointerForward = false; break;
+			case 2: this._pointerBackward = false; break;
 
 		}
 
@@ -399,19 +436,19 @@ function onKeyDown( event ) {
 	switch ( event.code ) {
 
 		case 'ArrowUp':
-		case 'KeyW': this._moveForward = true; break;
+		case 'KeyW': this._keyForward = true; break;
 
 		case 'ArrowLeft':
 		case 'KeyA': this._moveLeft = true; break;
 
 		case 'ArrowDown':
-		case 'KeyS': this._moveBackward = true; break;
+		case 'KeyS': this._keyBackward = true; break;
 
 		case 'ArrowRight':
 		case 'KeyD': this._moveRight = true; break;
 
-		case 'KeyR': this._moveUp = true; break;
-		case 'KeyF': this._moveDown = true; break;
+		case 'KeyE': this._moveUp = true; break;
+		case 'KeyQ': this._moveDown = true; break;
 
 	}
 
@@ -422,19 +459,19 @@ function onKeyUp( event ) {
 	switch ( event.code ) {
 
 		case 'ArrowUp':
-		case 'KeyW': this._moveForward = false; break;
+		case 'KeyW': this._keyForward = false; break;
 
 		case 'ArrowLeft':
 		case 'KeyA': this._moveLeft = false; break;
 
 		case 'ArrowDown':
-		case 'KeyS': this._moveBackward = false; break;
+		case 'KeyS': this._keyBackward = false; break;
 
 		case 'ArrowRight':
 		case 'KeyD': this._moveRight = false; break;
 
-		case 'KeyR': this._moveUp = false; break;
-		case 'KeyF': this._moveDown = false; break;
+		case 'KeyE': this._moveUp = false; break;
+		case 'KeyQ': this._moveDown = false; break;
 
 	}
 

粤ICP备19079148号