threepipe
Version:
A modern 3D viewer framework built on top of three.js, written in TypeScript, designed to make creating high-quality, modular, and extensible 3D experiences on the web simple and enjoyable.
333 lines • 12 kB
JavaScript
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
import { EventDispatcher, MathUtils, Spherical, Vector3 } from 'three';
import { now, serialize } from 'ts-browser-helpers';
import { uiInput, uiPanelContainer, uiToggle } from 'uiconfig.js';
// eslint-disable-next-line @typescript-eslint/naming-convention
const _lookDirection = new Vector3();
// eslint-disable-next-line @typescript-eslint/naming-convention
const _spherical = new Spherical();
// eslint-disable-next-line @typescript-eslint/naming-convention
const _target = new Vector3();
// eslint-disable-next-line @typescript-eslint/naming-convention
const _changeEvent = { type: 'change' };
// todo bug - this is not showing in the UI. To test, switch to threeFirstPerson controlsMode for Default Camera in the tweakpane editor
let FirstPersonControls2 = class FirstPersonControls2 extends EventDispatcher {
constructor(object, domElement) {
super();
// API
this.enabled = true;
this.enableKeys = true;
this.movementSpeed = 1.0;
this.lookSpeed = 0.005;
this.lookVertical = true;
this.autoForward = false;
this.activeLook = true;
this.heightSpeed = false;
this.heightCoef = 1.0;
this.heightMin = 0.0;
this.heightMax = 1.0;
this.constrainVertical = false;
this.verticalMin = 0;
this.verticalMax = Math.PI;
this.mouseDragOn = false;
// internals
this.autoSpeedFactor = 0.0;
this.pointerX = 0;
this.pointerY = 0;
this.moveForward = false;
this.moveBackward = false;
this.moveLeft = false;
this.moveRight = false;
this.moveUp = false;
this.moveDown = false;
this.viewHalfX = 0;
this.viewHalfY = 0;
// private variables
// eslint-disable-next-line @typescript-eslint/naming-convention
this.lat = 0;
// eslint-disable-next-line @typescript-eslint/naming-convention
this.lon = 0;
// eslint-disable-next-line @typescript-eslint/naming-convention
this.targetPosition = new Vector3();
this._lastTime = -1; // in ms
this.object = object;
this.domElement = domElement;
this.onPointerMove = this.onPointerMove.bind(this);
this.onPointerDown = this.onPointerDown.bind(this);
this.onPointerUp = this.onPointerUp.bind(this);
this.onKeyDown = this.onKeyDown.bind(this);
this.onKeyUp = this.onKeyUp.bind(this);
this.onContextMenu = this.onContextMenu.bind(this);
this.domElement.addEventListener('contextmenu', this.onContextMenu);
this.domElement.addEventListener('pointermove', this.onPointerMove);
this.domElement.addEventListener('pointerdown', this.onPointerDown);
this.domElement.addEventListener('pointerup', this.onPointerUp);
window.addEventListener('keydown', this.onKeyDown);
window.addEventListener('keyup', this.onKeyUp);
this.handleResize();
this.setOrientation();
}
setOrientation() {
const quaternion = this.object.quaternion;
_lookDirection.set(0, 0, -1).applyQuaternion(quaternion);
_spherical.setFromVector3(_lookDirection);
this.lat = 90 - MathUtils.radToDeg(_spherical.phi);
this.lon = MathUtils.radToDeg(_spherical.theta);
}
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;
}
}
onPointerDown(event) {
if (this.domElement !== document) {
this.domElement.focus();
}
if (this.activeLook) {
switch (event.button) {
case 0:
this.moveForward = true;
break;
case 2:
this.moveBackward = true;
break;
default: break;
}
}
this.mouseDragOn = true;
}
onPointerUp(event) {
if (this.activeLook) {
switch (event.button) {
case 0:
this.moveForward = false;
break;
case 2:
this.moveBackward = false;
break;
default: break;
}
}
this.mouseDragOn = false;
}
onPointerMove(event) {
if (this.domElement === document) {
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;
}
}
onKeyDown(event) {
if (!this.enableKeys)
return;
switch (event.code) {
case 'ArrowUp':
case 'KeyW':
this.moveForward = true;
break;
case 'ArrowLeft':
case 'KeyA':
this.moveLeft = true;
break;
case 'ArrowDown':
case 'KeyS':
this.moveBackward = true;
break;
case 'ArrowRight':
case 'KeyD':
this.moveRight = true;
break;
case 'KeyR':
this.moveUp = true;
break;
case 'KeyF':
this.moveDown = true;
break;
default: break;
}
}
onKeyUp(event) {
if (!this.enableKeys)
return;
switch (event.code) {
case 'ArrowUp':
case 'KeyW':
this.moveForward = false;
break;
case 'ArrowLeft':
case 'KeyA':
this.moveLeft = false;
break;
case 'ArrowDown':
case 'KeyS':
this.moveBackward = false;
break;
case 'ArrowRight':
case 'KeyD':
this.moveRight = false;
break;
case 'KeyR':
this.moveUp = false;
break;
case 'KeyF':
this.moveDown = false;
break;
default: break;
}
}
lookAt(x, y, z) {
if (x.isVector3) {
_target.copy(x);
}
else {
if (y === undefined || z === undefined)
console.error('FirstPersonControls2.lookAt: y and z parameters are required');
else
_target.set(x, y, z);
}
this.object.lookAt(_target);
this.setOrientation();
return this;
}
update() {
const time = now(); // in ms
const delta = (this._lastTime < 0 ? 16 : Math.min(time - this._lastTime, 1000)) / 1000; // in secs
this._lastTime = time;
// console.log(delta)
if (!this.enabled)
return;
if (this.heightSpeed) {
const y = MathUtils.clamp(this.object.position.y, this.heightMin, this.heightMax);
const heightDelta = y - this.heightMin;
this.autoSpeedFactor = delta * (heightDelta * this.heightCoef);
}
else {
this.autoSpeedFactor = 0.0;
}
const actualMoveSpeed = delta * this.movementSpeed;
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);
if (this.moveUp)
this.object.translateY(actualMoveSpeed);
if (this.moveDown)
this.object.translateY(-actualMoveSpeed);
let actualLookSpeed = delta * this.lookSpeed;
if (!this.activeLook) {
actualLookSpeed = 0;
}
let verticalLookRatio = 1;
if (this.constrainVertical) {
verticalLookRatio = Math.PI / (this.verticalMax - this.verticalMin);
}
this.lon -= this.pointerX * actualLookSpeed;
if (this.lookVertical)
this.lat -= this.pointerY * actualLookSpeed * verticalLookRatio;
this.lat = Math.max(-85, Math.min(85, this.lat));
let phi = MathUtils.degToRad(90 - this.lat);
const theta = MathUtils.degToRad(this.lon);
if (this.constrainVertical) {
phi = MathUtils.mapLinear(phi, 0, Math.PI, this.verticalMin, this.verticalMax);
}
const position = this.object.position;
this.targetPosition.setFromSphericalCoords(1, phi, theta).add(position);
this.object.lookAt(this.targetPosition);
this.dispatchEvent(_changeEvent);
}
dispose() {
this.domElement.removeEventListener('contextmenu', this.onContextMenu);
this.domElement.removeEventListener('pointerdown', this.onPointerDown);
this.domElement.removeEventListener('pointermove', this.onPointerMove);
this.domElement.removeEventListener('pointerup', this.onPointerUp);
window.removeEventListener('keydown', this.onKeyDown);
window.removeEventListener('keyup', this.onKeyUp);
}
onContextMenu(event) {
if (!this.enableKeys)
return;
event.preventDefault();
}
};
__decorate([
serialize(),
uiToggle()
], FirstPersonControls2.prototype, "enabled", void 0);
__decorate([
serialize(),
uiToggle()
], FirstPersonControls2.prototype, "enableKeys", void 0);
__decorate([
serialize(),
uiInput()
], FirstPersonControls2.prototype, "movementSpeed", void 0);
__decorate([
serialize(),
uiInput()
], FirstPersonControls2.prototype, "lookSpeed", void 0);
__decorate([
serialize(),
uiToggle()
], FirstPersonControls2.prototype, "lookVertical", void 0);
__decorate([
serialize(),
uiToggle()
], FirstPersonControls2.prototype, "autoForward", void 0);
__decorate([
serialize(),
uiToggle()
], FirstPersonControls2.prototype, "activeLook", void 0);
__decorate([
serialize(),
uiToggle()
], FirstPersonControls2.prototype, "heightSpeed", void 0);
__decorate([
serialize(),
uiInput()
], FirstPersonControls2.prototype, "heightCoef", void 0);
__decorate([
serialize(),
uiInput()
], FirstPersonControls2.prototype, "heightMin", void 0);
__decorate([
serialize(),
uiInput()
], FirstPersonControls2.prototype, "heightMax", void 0);
__decorate([
serialize(),
uiToggle()
], FirstPersonControls2.prototype, "constrainVertical", void 0);
__decorate([
serialize(),
uiInput()
], FirstPersonControls2.prototype, "verticalMin", void 0);
__decorate([
serialize(),
uiInput()
], FirstPersonControls2.prototype, "verticalMax", void 0);
__decorate([
serialize(),
uiToggle()
], FirstPersonControls2.prototype, "mouseDragOn", void 0);
FirstPersonControls2 = __decorate([
uiPanelContainer('First Person Controls')
], FirstPersonControls2);
export { FirstPersonControls2 };
//# sourceMappingURL=FirstPersonControls2.js.map