diff --git a/mouse-look.ts b/mouse-look.ts index 348acd1..762ccd1 100644 --- a/mouse-look.ts +++ b/mouse-look.ts @@ -2,13 +2,15 @@ import {Component} from '@wonderlandengine/api'; import {property} from '@wonderlandengine/api/decorators.js'; import {vec3} from 'gl-matrix'; -const preventDefault = (e: Event) => { e.preventDefault(); }; +const preventDefault = (e: Event) => { + e.preventDefault(); +}; /** - * Controls the camera orientation through mouse movement. + * Controls the camera orientation through mouse and touch movement. * * Efficiently implemented to affect object orientation only - * when the mouse moves. + * when the mouse or touch moves. */ export class MouseLookComponent extends Component { static TypeName = 'mouse-look'; @@ -17,8 +19,8 @@ export class MouseLookComponent extends Component { @property.float(0.25) sensitity = 0.25; - /** Require a mouse button to be pressed to control view. - * Otherwise view will allways follow mouse movement */ + /** Require a mouse button or touch to be pressed to control view. + * Otherwise view will always follow mouse or touch movement */ @property.bool(true) requireMouseDown = true; @@ -38,70 +40,104 @@ export class MouseLookComponent extends Component { private rotationX = 0; private rotationY = 0; private mouseDown = false; + /** Pointerlock spec prevents calling pointerlock right after user exiting it via esc for ~1 second */ + private pointerLockCooldown = false; onActivate() { - document.addEventListener('mousemove', this.onMouseMove); - const canvas = this.engine.canvas; + if (this.mouseButtonIndex === 2) { + canvas.addEventListener('contextmenu', preventDefault, false); + } + canvas.addEventListener('pointermove', this.onPointerMove); if (this.pointerLockOnClick) { - canvas.addEventListener('mousedown', this.requestPointerLock); + canvas.addEventListener('pointerdown', this.onPointerDown); + document.addEventListener('pointerlockchange', this.onPointerLockChange); } - if (this.requireMouseDown) { - if (this.mouseButtonIndex === 2) { - canvas.addEventListener('contextmenu', preventDefault, false); - } - canvas.addEventListener('mousedown', this.onMouseDown); - canvas.addEventListener('mouseup', this.onMouseUp); + if (this.requireMouseDown && !this.pointerLockOnClick) { + canvas.addEventListener('pointerdown', this.onPointerDown); + canvas.addEventListener('pointerup', this.onPointerUp); } } onDeactivate() { - document.removeEventListener('mousemove', this.onMouseMove); - const canvas = this.engine.canvas; + canvas.removeEventListener('pointermove', this.onPointerMove); + if (this.pointerLockOnClick) { - canvas.removeEventListener('mousedown', this.requestPointerLock); + canvas.removeEventListener('pointerdown', this.onPointerDown); + document.removeEventListener('pointerlockchange', this.onPointerLockChange); } - if (this.requireMouseDown) { + if (this.requireMouseDown && !this.pointerLockOnClick) { if (this.mouseButtonIndex === 2) { canvas.removeEventListener('contextmenu', preventDefault, false); } - canvas.removeEventListener('mousedown', this.onMouseDown); - canvas.removeEventListener('mouseup', this.onMouseUp); + canvas.removeEventListener('pointerdown', this.onPointerDown); + canvas.removeEventListener('pointerup', this.onPointerUp); } } - requestPointerLock = () => { + async requestPointerLock() { const canvas = this.engine.canvas; canvas.requestPointerLock = canvas.requestPointerLock || (canvas as any).mozRequestPointerLock || (canvas as any).webkitRequestPointerLock; - canvas.requestPointerLock(); - } - onMouseDown = (e: MouseEvent) => { - if (e.button === this.mouseButtonIndex) { - this.mouseDown = true; - document.body.style.cursor = 'grabbing'; - if (e.button === 1) { - e.preventDefault(); - /* Prevent scrolling */ - return false; - } + try { + await navigator.locks.request('pointer-lock', async (lock) => { + if (!document.pointerLockElement) { + await canvas.requestPointerLock(); + } + }); + } catch (error) { + console.error('Pointer lock request failed:', error); } } - onMouseUp = (e: MouseEvent) => { - if (e.button === this.mouseButtonIndex) { + onPointerLockChange = () => { + const canvas = this.engine.canvas; + if (document.pointerLockElement === canvas) return; + this.mouseDown = false; + this.pointerLockCooldown = true; + document.body.style.cursor = 'initial'; + + setTimeout(() => { + this.pointerLockCooldown = false; + }, 1500); + }; + + onPointerDown = (e: PointerEvent) => { + if ( + this.pointerLockCooldown || + !(e.button === this.mouseButtonIndex || e.pointerType === 'touch') + ) + return; + this.mouseDown = true; + document.body.style.cursor = 'grabbing'; + if (e.button === 2) { + e.preventDefault(); + } + + if (this.pointerLockOnClick && document.pointerLockElement !== this.engine.canvas) { + this.requestPointerLock(); + } + if (e.button === 1) { + e.preventDefault(); + /* Prevent scrolling */ + return false; + } + }; + + onPointerUp = (e: PointerEvent) => { + if (e.button === this.mouseButtonIndex || e.pointerType === 'touch') { this.mouseDown = false; document.body.style.cursor = 'initial'; } - } + }; - onMouseMove = (e: MouseEvent) => { + onPointerMove = (e: PointerEvent) => { if (this.active && (this.mouseDown || !this.requireMouseDown)) { this.rotationY = (-this.sensitity * e.movementX) / 100; this.rotationX = (-this.sensitity * e.movementY) / 100; @@ -126,5 +162,5 @@ export class MouseLookComponent extends Component { this.object.rotateAxisAngleRadLocal([0, 1, 0], this.currentRotationY); this.object.translateLocal(this.origin); } - } + }; }