132 lines
		
	
	
		
			3.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			132 lines
		
	
	
		
			3.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| import { quat, vec3, mat4 } from 'glm';
 | |
| 
 | |
| import { Transform } from '../core/Transform.js';
 | |
| 
 | |
| export class FirstPersonController {
 | |
| 
 | |
|     constructor(node, domElement, {
 | |
|         pitch = 0,
 | |
|         yaw = 0,
 | |
|         velocity = [0, 0, 0],
 | |
|         acceleration = 50,
 | |
|         maxSpeed = 5,
 | |
|         decay = 0.99999,
 | |
|         pointerSensitivity = 0.002,
 | |
|     } = {}) {
 | |
|         this.node = node;
 | |
|         this.domElement = domElement;
 | |
| 
 | |
|         this.keys = {};
 | |
| 
 | |
|         this.pitch = pitch;
 | |
|         this.yaw = yaw;
 | |
| 
 | |
|         this.velocity = velocity;
 | |
|         this.acceleration = acceleration;
 | |
|         this.maxSpeed = maxSpeed;
 | |
|         this.decay = decay;
 | |
|         this.pointerSensitivity = pointerSensitivity;
 | |
| 
 | |
|         this.initHandlers();
 | |
|     }
 | |
| 
 | |
|     initHandlers() {
 | |
|         this.pointermoveHandler = this.pointermoveHandler.bind(this);
 | |
|         this.keydownHandler = this.keydownHandler.bind(this);
 | |
|         this.keyupHandler = this.keyupHandler.bind(this);
 | |
| 
 | |
|         const element = this.domElement;
 | |
|         const doc = element.ownerDocument;
 | |
| 
 | |
|         doc.addEventListener('keydown', this.keydownHandler);
 | |
|         doc.addEventListener('keyup', this.keyupHandler);
 | |
| 
 | |
|         element.addEventListener('click', e => element.requestPointerLock());
 | |
|         doc.addEventListener('pointerlockchange', e => {
 | |
|             if (doc.pointerLockElement === element) {
 | |
|                 doc.addEventListener('pointermove', this.pointermoveHandler);
 | |
|             } else {
 | |
|                 doc.removeEventListener('pointermove', this.pointermoveHandler);
 | |
|             }
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     update(t, dt) {
 | |
|         // Calculate forward and right vectors.
 | |
|         const cos = Math.cos(this.yaw);
 | |
|         const sin = Math.sin(this.yaw);
 | |
|         const forward = [-sin, 0, -cos];
 | |
|         const right = [cos, 0, -sin];
 | |
| 
 | |
|         // Map user input to the acceleration vector.
 | |
|         const acc = vec3.create();
 | |
|         if (this.keys['KeyW']) {
 | |
|             vec3.add(acc, acc, forward);
 | |
|         }
 | |
|         if (this.keys['KeyS']) {
 | |
|             vec3.sub(acc, acc, forward);
 | |
|         }
 | |
|         if (this.keys['KeyD']) {
 | |
|             vec3.add(acc, acc, right);
 | |
|         }
 | |
|         if (this.keys['KeyA']) {
 | |
|             vec3.sub(acc, acc, right);
 | |
|         }
 | |
| 
 | |
|         // Update velocity based on acceleration.
 | |
|         vec3.scaleAndAdd(this.velocity, this.velocity, acc, dt * this.acceleration);
 | |
| 
 | |
|         // If there is no user input, apply decay.
 | |
|         if (!this.keys['KeyW'] &&
 | |
|             !this.keys['KeyS'] &&
 | |
|             !this.keys['KeyD'] &&
 | |
|             !this.keys['KeyA'])
 | |
|         {
 | |
|             const decay = Math.exp(dt * Math.log(1 - this.decay));
 | |
|             vec3.scale(this.velocity, this.velocity, decay);
 | |
|         }
 | |
| 
 | |
|         // Limit speed to prevent accelerating to infinity and beyond.
 | |
|         const speed = vec3.length(this.velocity);
 | |
|         if (speed > this.maxSpeed) {
 | |
|             vec3.scale(this.velocity, this.velocity, this.maxSpeed / speed);
 | |
|         }
 | |
| 
 | |
|         const transform = this.node.getComponentOfType(Transform);
 | |
|         if (transform) {
 | |
|             // Update translation based on velocity.
 | |
|             vec3.scaleAndAdd(transform.translation,
 | |
|                 transform.translation, this.velocity, dt);
 | |
| 
 | |
|             // Update rotation based on the Euler angles.
 | |
|             const rotation = quat.create();
 | |
|             quat.rotateY(rotation, rotation, this.yaw);
 | |
|             quat.rotateX(rotation, rotation, this.pitch);
 | |
|             transform.rotation = rotation;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     pointermoveHandler(e) {
 | |
|         const dx = e.movementX;
 | |
|         const dy = e.movementY;
 | |
| 
 | |
|         this.pitch -= dy * this.pointerSensitivity;
 | |
|         this.yaw   -= dx * this.pointerSensitivity;
 | |
| 
 | |
|         const twopi = Math.PI * 2;
 | |
|         const halfpi = Math.PI / 2;
 | |
| 
 | |
|         this.pitch = Math.min(Math.max(this.pitch, -halfpi), halfpi);
 | |
|         this.yaw = ((this.yaw % twopi) + twopi) % twopi;
 | |
|     }
 | |
| 
 | |
|     keydownHandler(e) {
 | |
|         this.keys[e.code] = true;
 | |
|     }
 | |
| 
 | |
|     keyupHandler(e) {
 | |
|         this.keys[e.code] = false;
 | |
|     }
 | |
| 
 | |
| }
 |