From 2882df3981c20ac06b5c7d2d40195493f040fe5b Mon Sep 17 00:00:00 2001 From: mrdoob Date: Fri, 6 Mar 2026 06:37:15 -0800 Subject: [PATCH] FirstPersonControls: Update interaction model. (#33124) Co-authored-by: Claude Opus 4.6 --- examples/jsm/controls/FirstPersonControls.js | 112 ++++++++++--------- examples/webaudio_sandbox.html | 2 - examples/webgl_geometry_minecraft.html | 2 - examples/webgl_geometry_terrain.html | 2 - examples/webgl_shadowmap_performance.html | 2 - 5 files changed, 58 insertions(+), 62 deletions(-) diff --git a/examples/jsm/controls/FirstPersonControls.js b/examples/jsm/controls/FirstPersonControls.js index 74c9af8db21828..761c34236e1698 100644 --- a/examples/jsm/controls/FirstPersonControls.js +++ b/examples/jsm/controls/FirstPersonControls.js @@ -60,14 +60,6 @@ class FirstPersonControls extends Controls { */ this.autoForward = false; - /** - * Whether it's possible to look around or not. - * - * @type {boolean} - * @default true - */ - this.activeLook = true; - /** * Whether or not the camera's height influences the forward movement speed. * Use the properties `heightCoef`, `heightMin` and `heightMax` for configuration. @@ -141,14 +133,16 @@ class FirstPersonControls extends Controls { this._pointerX = 0; this._pointerY = 0; + this._pointerDownX = 0; + this._pointerDownY = 0; + + this._pointerCount = 0; + this._moveForward = false; this._moveBackward = false; this._moveLeft = false; this._moveRight = false; - this._viewHalfX = 0; - this._viewHalfY = 0; - this._lat = 0; this._lon = 0; @@ -167,8 +161,6 @@ class FirstPersonControls extends Controls { this.connect( domElement ); - this.handleResize(); - } this._setOrientation(); @@ -187,6 +179,8 @@ class FirstPersonControls extends Controls { this.domElement.addEventListener( 'pointerup', this._onPointerUp ); this.domElement.addEventListener( 'contextmenu', this._onContextMenu ); + this.domElement.style.touchAction = 'none'; // disable touch scroll + } disconnect() { @@ -199,6 +193,8 @@ class FirstPersonControls extends Controls { this.domElement.removeEventListener( 'pointerup', this._onPointerUp ); this.domElement.removeEventListener( 'contextmenu', this._onContextMenu ); + this.domElement.style.touchAction = 'auto'; + } dispose() { @@ -207,25 +203,6 @@ class FirstPersonControls extends Controls { } - /** - * Must be called if the application window is resized. - */ - handleResize() { - - if ( this.domElement === document ) { - - this._viewHalfX = window.innerWidth / 2; - this._viewHalfY = window.innerHeight / 2; - - } else { - - this._viewHalfX = this.domElement.offsetWidth / 2; - this._viewHalfY = this.domElement.offsetHeight / 2; - - } - - } - /** * Rotates the camera towards the defined target position. * @@ -282,13 +259,7 @@ class FirstPersonControls extends Controls { if ( this._moveUp ) this.object.translateY( actualMoveSpeed ); if ( this._moveDown ) this.object.translateY( - actualMoveSpeed ); - let actualLookSpeed = delta * this.lookSpeed; - - if ( ! this.activeLook ) { - - actualLookSpeed = 0; - - } + const actualLookSpeed = delta * this.lookSpeed; let verticalLookRatio = 1; @@ -298,8 +269,12 @@ class FirstPersonControls extends Controls { } - this._lon -= this._pointerX * actualLookSpeed; - if ( this.lookVertical ) this._lat -= this._pointerY * actualLookSpeed * verticalLookRatio; + if ( this.mouseDragOn ) { + + this._lon -= this._pointerX * actualLookSpeed; + if ( this.lookVertical ) this._lat -= this._pointerY * actualLookSpeed * verticalLookRatio; + + } this._lat = Math.max( - 85, Math.min( 85, this._lat ) ); @@ -332,6 +307,15 @@ class FirstPersonControls extends Controls { } + /** + * @deprecated, r184. The controls now handle resize internally. + */ + handleResize() { + + console.warn( 'THREE.FirstPersonControls: handleResize() has been removed. The controls now handle resize internally.' ); + + } + } function onPointerDown( event ) { @@ -342,7 +326,16 @@ function onPointerDown( event ) { } - if ( this.activeLook ) { + this.domElement.setPointerCapture( event.pointerId ); + + this._pointerCount ++; + + if ( event.pointerType === 'touch' ) { + + this._moveForward = this._pointerCount === 1; + this._moveBackward = this._pointerCount >= 2; + + } else { switch ( event.button ) { @@ -353,13 +346,28 @@ function onPointerDown( event ) { } + this._pointerDownX = event.pageX; + this._pointerDownY = event.pageY; + + this._pointerX = 0; + this._pointerY = 0; + this.mouseDragOn = true; } function onPointerUp( event ) { - if ( this.activeLook ) { + this.domElement.releasePointerCapture( event.pointerId ); + + this._pointerCount --; + + if ( event.pointerType === 'touch' ) { + + this._moveForward = this._pointerCount === 1; + this._moveBackward = false; + + } else { switch ( event.button ) { @@ -370,23 +378,19 @@ function onPointerUp( event ) { } - this.mouseDragOn = false; + this._pointerX = 0; + this._pointerY = 0; + + if ( this._pointerCount === 0 ) this.mouseDragOn = false; } function onPointerMove( event ) { - if ( this.domElement === document ) { + if ( this.mouseDragOn === false ) return; - this._pointerX = event.pageX - this._viewHalfX; - this._pointerY = event.pageY - this._viewHalfY; - - } else { - - this._pointerX = event.pageX - this.domElement.offsetLeft - this._viewHalfX; - this._pointerY = event.pageY - this.domElement.offsetTop - this._viewHalfY; - - } + this._pointerX = event.pageX - this._pointerDownX; + this._pointerY = event.pageY - this._pointerDownY; } diff --git a/examples/webaudio_sandbox.html b/examples/webaudio_sandbox.html index c50503cbb0ed7c..0187a46407cf50 100644 --- a/examples/webaudio_sandbox.html +++ b/examples/webaudio_sandbox.html @@ -239,8 +239,6 @@ renderer.setSize( window.innerWidth, window.innerHeight ); - controls.handleResize(); - } function animate() { diff --git a/examples/webgl_geometry_minecraft.html b/examples/webgl_geometry_minecraft.html index 528c5f8bb4b099..6bc20069c59e78 100644 --- a/examples/webgl_geometry_minecraft.html +++ b/examples/webgl_geometry_minecraft.html @@ -192,8 +192,6 @@ renderer.setSize( window.innerWidth, window.innerHeight ); - controls.handleResize(); - } function generateHeight( width, height ) { diff --git a/examples/webgl_geometry_terrain.html b/examples/webgl_geometry_terrain.html index 57bd1507cbd947..d9736ef725f54d 100644 --- a/examples/webgl_geometry_terrain.html +++ b/examples/webgl_geometry_terrain.html @@ -109,8 +109,6 @@ renderer.setSize( window.innerWidth, window.innerHeight ); - controls.handleResize(); - } function generateHeight( width, height ) { diff --git a/examples/webgl_shadowmap_performance.html b/examples/webgl_shadowmap_performance.html index c98570a9581c2b..56a742af2e35f6 100644 --- a/examples/webgl_shadowmap_performance.html +++ b/examples/webgl_shadowmap_performance.html @@ -141,8 +141,6 @@ renderer.setSize( SCREEN_WIDTH, SCREEN_HEIGHT ); - controls.handleResize(); - } function createScene( ) {