@polygonjs/polygonjs
Version:
node-based WebGL 3D engine https://polygonjs.com
188 lines (164 loc) • 5.73 kB
text/typescript
import {BaseEvent, Euler, Camera, EventDispatcher, Vector3, Spherical} from 'three';
import {CorePlayer} from '../../../core/player/Player';
import {CorePlayerKeyEvents} from '../../../core/player/KeyEvents';
const changeEvent: BaseEvent<'change'> = {type: 'change' as 'change'};
const lockEvent: BaseEvent<'lock'> = {type: 'lock'};
const unlockEvent: BaseEvent<'unlock'> = {type: 'unlock'};
const PI_2 = Math.PI / 2;
const tmpCameraUnproject = new Vector3();
const spherical = new Spherical();
const LOCK_ELEMENT_DEFAULT_HTML = `
<div style="
text-align:center;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%,-50%);
cursor: pointer;
padding: 5px 10px;
background:gray;
border:white;
color: white;
">
<div style="font-size: 1rem">CLICK TO START</div>
<div style="font-size: 0.6rem">press ESC to show your cursor</div>
</div>
`;
interface PointerLockControlsOptions {
lockHTMLElement?: HTMLElement;
}
export class PointerLockControls extends EventDispatcher<{change: any}> {
private isLocked = false;
public minPolarAngle = 0; // radians
public maxPolarAngle = Math.PI; // radians
public rotateSpeed = 1;
private euler = new Euler(0, 0, 0, 'YXZ');
private boundMethods = {
lock: this.lock.bind(this),
onMouseMove: this.onMouseMove.bind(this),
onPointerlockChange: this.onPointerlockChange.bind(this),
onPointerlockError: this.onPointerlockError.bind(this),
};
private _azimuthalAngle: number = 0;
private _corePlayerKeyEvents: CorePlayerKeyEvents | undefined;
constructor(
private camera: Camera,
public readonly domElement: HTMLElement,
private options: PointerLockControlsOptions,
private player?: CorePlayer
) {
super();
this.connect();
this._showUnlockHTMLElement();
}
onMouseMove(event: MouseEvent) {
if (this.isLocked === false) return;
var movementX = event.movementX || (event as any).mozMovementX || (event as any).webkitMovementX || 0;
var movementY = event.movementY || (event as any).mozMovementY || (event as any).webkitMovementY || 0;
this.euler.setFromQuaternion(this.camera.quaternion);
this.euler.y -= movementX * 0.002 * this.rotateSpeed;
this.euler.x -= movementY * 0.002 * this.rotateSpeed;
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._computeAzimuthalAngle();
this.dispatchEvent(changeEvent);
}
private _computeAzimuthalAngle() {
this.camera.updateMatrixWorld();
tmpCameraUnproject.set(0, 0, 1);
this.camera.localToWorld(tmpCameraUnproject);
tmpCameraUnproject.sub(this.camera.position);
spherical.setFromVector3(tmpCameraUnproject);
this._azimuthalAngle = spherical.theta;
}
onPointerlockChange() {
// this.velocity.set(0, 0, 0);
if (this.domElement.ownerDocument.pointerLockElement === this.domElement) {
this.dispatchEvent(lockEvent);
this.isLocked = true;
this._removeHTMLElement();
if (this.player) {
this._corePlayerKeyEvents = this._corePlayerKeyEvents || new CorePlayerKeyEvents(this.player);
this._corePlayerKeyEvents.addEvents();
}
} else {
this.dispatchEvent(unlockEvent);
this.isLocked = false;
this._showUnlockHTMLElement();
this._corePlayerKeyEvents?.removeEvents();
this.player?.stop();
}
}
onPointerlockError() {
console.error(
'THREE.PointerLockControls: Unable to use Pointer Lock API (Note that you need to wait for 2 seconds to lock the pointer after having just unlocked it)'
);
}
connect() {
this.domElement.ownerDocument.addEventListener('mousemove', this.boundMethods.onMouseMove);
this.domElement.ownerDocument.addEventListener('pointerlockchange', this.boundMethods.onPointerlockChange);
this.domElement.ownerDocument.addEventListener('pointerlockerror', this.boundMethods.onPointerlockError);
}
disconnect() {
this.domElement.ownerDocument.removeEventListener('mousemove', this.boundMethods.onMouseMove);
this.domElement.ownerDocument.removeEventListener('pointerlockchange', this.boundMethods.onPointerlockChange);
this.domElement.ownerDocument.removeEventListener('pointerlockerror', this.boundMethods.onPointerlockError);
}
dispose() {
this.disconnect();
this._removeHTMLElement();
}
getObject() {
// retaining this method for backward compatibility
return this.camera;
}
lock() {
this.domElement.requestPointerLock();
}
unlock() {
this.domElement.ownerDocument.exitPointerLock();
}
update(delta: number) {
if (this.player) {
this.player.setAzimuthalAngle(this._azimuthalAngle);
this.player.update(delta);
}
}
//
//
// HTML Element
//
//
private __unlockHTMLElement: HTMLElement | undefined;
private _unlockHTMLElementParent() {
return this.domElement.parentElement;
}
private _unlockHTMLElement() {
return (this.__unlockHTMLElement = this.__unlockHTMLElement || this._getUnlockHTMLElement());
}
private _showUnlockHTMLElement() {
const el = this._unlockHTMLElement();
if (!el) {
return;
}
this._unlockHTMLElementParent()?.append(el);
}
private _getUnlockHTMLElement(): HTMLElement | undefined {
const element = this.options.lockHTMLElement || this._createUnlockHTMLElement();
element.addEventListener('pointerdown', this.boundMethods.lock);
return element;
}
private _createUnlockHTMLElement(): HTMLElement {
const el = document.createElement('div');
el.innerHTML = LOCK_ELEMENT_DEFAULT_HTML;
return el;
}
private _removeHTMLElement() {
if (!this.__unlockHTMLElement) {
return;
}
this._unlockHTMLElementParent()?.removeChild(this.__unlockHTMLElement);
this.__unlockHTMLElement.removeEventListener('pointerdown', this.boundMethods.lock);
this.__unlockHTMLElement = undefined;
}
}