Skip to content

Instantly share code, notes, and snippets.

@exreplay
Last active February 18, 2024 01:27
Show Gist options
  • Star 9 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save exreplay/090323d2865dcf2df4b2d47c1daf20a3 to your computer and use it in GitHub Desktop.
Save exreplay/090323d2865dcf2df4b2d47c1daf20a3 to your computer and use it in GitHub Desktop.
ThreeJS PointerLockControls with touch support
/* eslint-disable camelcase */
import { Camera, Euler, EventDispatcher, Vector3 } from 'three';
const _changeEvent = { type: 'change' };
const _lockEvent = { type: 'lock' };
const _unlockEvent = { type: 'unlock' };
const _PI_2 = Math.PI / 2;
class PointerLockControls extends EventDispatcher {
domElement: HTMLElement;
camera: Camera;
isLocked = false;
minPolarAngle = 0; // radians
maxPolarAngle = Math.PI; // radians
vector = new Vector3();
euler = new Euler(0, 0, 0, 'YXZ');
previousTouch?: Touch;
onMouseMoveBind = this.onMouseMove.bind(this);
onPointerlockChangeBind = this.onPointerlockChange.bind(this);
onPointerlockErrorBind = this.onPointerlockError.bind(this);
onTouchMoveBind = this.onTouchMove.bind(this);
onTouchEndBind = this.onTouchEnd.bind(this);
constructor(camera: Camera, domElement: HTMLElement) {
super();
if (domElement === undefined) {
console.warn(
'THREE.PointerLockControls: The second parameter "domElement" is now mandatory.'
);
domElement = document.body;
}
this.camera = camera;
this.domElement = domElement;
this.connect();
}
onTouchMove(e: TouchEvent) {
let touch: Touch | undefined;
switch (e.touches.length) {
case 1:
if (e.touches[0].target === this.domElement) touch = e.touches[0];
break;
case 2:
if (e.touches[0].target === this.domElement) touch = e.touches[0];
else if (e.touches[1].target === this.domElement) touch = e.touches[1];
break;
}
if (!touch) return;
console.log(touch.target);
const movementX = this.previousTouch
? touch.pageX - this.previousTouch.pageX
: 0;
const movementY = this.previousTouch
? touch.pageY - this.previousTouch.pageY
: 0;
this.updatePosition(movementX, movementY, 0.004);
this.previousTouch = touch;
}
onTouchEnd() {
this.previousTouch = undefined;
}
onMouseMove(event: MouseEvent) {
if (this.isLocked === false) return;
const movementX: number =
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
event.movementX || event.mozMovementX || event.webkitMovementX || 0;
const movementY: number =
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
event.movementY || event.mozMovementY || event.webkitMovementY || 0;
this.updatePosition(movementX, movementY, 0.002);
}
updatePosition(movementX: number, movementY: number, multiplier: number) {
this.euler.setFromQuaternion(this.camera.quaternion);
this.euler.y -= movementX * multiplier;
this.euler.x -= movementY * multiplier;
this.euler.x = Math.max(
_PI_2 - this.maxPolarAngle,
Math.min(_PI_2 - this.minPolarAngle, this.euler.x)
);
this.camera.quaternion.setFromEuler(this.euler);
this.dispatchEvent(_changeEvent);
}
onPointerlockChange() {
if (this.domElement.ownerDocument.pointerLockElement === this.domElement) {
this.dispatchEvent(_lockEvent);
this.isLocked = true;
} else {
this.dispatchEvent(_unlockEvent);
this.isLocked = false;
}
}
onPointerlockError() {
console.error('THREE.PointerLockControls: Unable to use Pointer Lock API');
}
connect() {
this.domElement.addEventListener('touchmove', this.onTouchMoveBind, false);
this.domElement.addEventListener('touchend', this.onTouchEndBind, false);
this.domElement.ownerDocument.addEventListener(
'mousemove',
this.onMouseMoveBind
);
this.domElement.ownerDocument.addEventListener(
'pointerlockchange',
this.onPointerlockChangeBind
);
this.domElement.ownerDocument.addEventListener(
'pointerlockerror',
this.onPointerlockErrorBind
);
}
disconnect() {
this.domElement.removeEventListener(
'touchmove',
this.onTouchMoveBind,
false
);
this.domElement.removeEventListener('touchend', this.onTouchEndBind, false);
this.domElement.ownerDocument.removeEventListener(
'mousemove',
this.onMouseMoveBind
);
this.domElement.ownerDocument.removeEventListener(
'pointerlockchange',
this.onPointerlockChangeBind
);
this.domElement.ownerDocument.removeEventListener(
'pointerlockerror',
this.onPointerlockErrorBind
);
}
dispose() {
this.disconnect();
}
getObject() {
return this.camera;
}
getDirection() {
const direction = new Vector3(0, 0, -1);
return (v: Vector3) => {
return v.copy(direction).applyQuaternion(this.camera.quaternion);
};
}
moveForward(distance: number) {
this.vector.setFromMatrixColumn(this.camera.matrix, 0);
this.vector.crossVectors(this.camera.up, this.vector);
this.camera.position.addScaledVector(this.vector, distance);
}
moveRight(distance: number) {
this.vector.setFromMatrixColumn(this.camera.matrix, 0);
this.camera.position.addScaledVector(this.vector, distance);
}
lock() {
if (typeof this.domElement.requestPointerLock !== 'undefined')
this.domElement.requestPointerLock();
}
unlock() {
if (typeof this.domElement.requestPointerLock !== 'undefined')
this.domElement.ownerDocument.exitPointerLock();
}
}
export { PointerLockControls };
@exreplay
Copy link
Author

@noreff yeah sure i can do that. what would you prefer? i could also make a repo and publish it as a package on npm.

@noreff
Copy link

noreff commented Jul 19, 2022

@exreplay thank you for a quick response anything that will not take a lot of time from you, repo works just fine for me, npm package too

@Wow1012
Copy link

Wow1012 commented Sep 22, 2023

@exreplay Thanks for sharing your code. I tried to implement this on myside but it is not working well.
Could you share with me something working like codepen project plz?
Hope to hear from you soon.

@Magvin
Copy link

Magvin commented Oct 10, 2023

Is there any update with repos :)

@mohsentajik
Copy link

@noreff hi , this solution have a hook example ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment