UNPKG

@matematrolii/sketchbook

Version:

3D matematrolii playground built on three.js and cannon.js

222 lines (195 loc) 6.38 kB
import * as THREE from "three"; import * as Utils from "./FunctionLibrary"; import { World } from "../world/World"; import { IInputReceiver } from "../interfaces/IInputReceiver"; import { KeyBinding } from "./KeyBinding"; import { Character } from "../characters/Character"; import _ = require("lodash"); import { IUpdatable } from "../interfaces/IUpdatable"; export class CameraOperator implements IInputReceiver, IUpdatable { public updateOrder: number = 4; public world: World; public camera: THREE.Camera; public target: THREE.Vector3; public sensitivity: THREE.Vector2; public radius: number = 1; public theta: number; public phi: number; public onMouseDownPosition: THREE.Vector2; public onMouseDownTheta: any; public onMouseDownPhi: any; public targetRadius: number = 1; public movementSpeed: number; public actions: { [action: string]: KeyBinding }; public upVelocity: number = 0; public forwardVelocity: number = 0; public rightVelocity: number = 0; public followMode: boolean = false; public characterCaller: Character; constructor( world: World, camera: THREE.Camera, sensitivityX: number = 1, sensitivityY: number = sensitivityX * 0.8 ) { this.world = world; this.camera = camera; this.target = new THREE.Vector3(); this.sensitivity = new THREE.Vector2(sensitivityX, sensitivityY); this.movementSpeed = 0.06; this.radius = 3; this.theta = 0; this.phi = 0; this.onMouseDownPosition = new THREE.Vector2(); this.onMouseDownTheta = this.theta; this.onMouseDownPhi = this.phi; this.actions = { forward: new KeyBinding("KeyW"), back: new KeyBinding("KeyS"), left: new KeyBinding("KeyA"), right: new KeyBinding("KeyD"), up: new KeyBinding("KeyE"), down: new KeyBinding("KeyQ"), fast: new KeyBinding("ShiftLeft"), }; world.registerUpdatable(this); } public setSensitivity( sensitivityX: number, sensitivityY: number = sensitivityX ): void { this.sensitivity = new THREE.Vector2(sensitivityX, sensitivityY); } public setRadius(value: number, instantly: boolean = false): void { this.targetRadius = Math.max(0.001, value); if (instantly === true) { this.radius = value; } } public move(deltaX: number, deltaY: number): void { this.theta -= deltaX * (this.sensitivity.x / 2); this.theta %= 360; this.phi += deltaY * (this.sensitivity.y / 2); const minRotationFromGround = -10; const maxRotationFromGround = 50; this.phi = Math.min( maxRotationFromGround, Math.max(minRotationFromGround, this.phi) ); } public update(timeScale: number): void { if (this.followMode === true) { this.camera.position.y = THREE.MathUtils.clamp( this.camera.position.y, this.target.y, Number.POSITIVE_INFINITY ); this.camera.lookAt(this.target); let newPos = this.target .clone() .add( new THREE.Vector3() .subVectors(this.camera.position, this.target) .normalize() .multiplyScalar(this.targetRadius) ); this.camera.position.x = newPos.x; this.camera.position.y = newPos.y; this.camera.position.z = newPos.z; } else { this.radius = THREE.MathUtils.lerp(this.radius, this.targetRadius, 0.1); this.camera.position.x = this.target.x + this.radius * Math.sin((this.theta * Math.PI) / 180) * Math.cos((this.phi * Math.PI) / 180); this.camera.position.y = this.target.y + this.radius * Math.sin((this.phi * Math.PI) / 180); this.camera.position.z = this.target.z + this.radius * Math.cos((this.theta * Math.PI) / 180) * Math.cos((this.phi * Math.PI) / 180); this.camera.updateMatrix(); this.camera.lookAt(this.target); } } public handleKeyboardEvent( event: KeyboardEvent, code: string, pressed: boolean ): void { // Free camera if (code === "KeyC" && pressed === true && event.shiftKey === true) { if (this.characterCaller !== undefined) { this.world.inputManager.setInputReceiver(this.characterCaller); this.characterCaller = undefined; } } else { for (const action in this.actions) { if (this.actions.hasOwnProperty(action)) { const binding = this.actions[action]; if (_.includes(binding.eventCodes, code)) { binding.isPressed = pressed; } } } } } public handleMouseWheel(event: WheelEvent, value: number): void { //this.world.scrollTheTimeScale(value); } public handleMouseButton( event: MouseEvent, code: string, pressed: boolean ): void { for (const action in this.actions) { if (this.actions.hasOwnProperty(action)) { const binding = this.actions[action]; if (_.includes(binding.eventCodes, code)) { binding.isPressed = pressed; } } } } public handleMouseMove( event: MouseEvent, deltaX: number, deltaY: number ): void { this.move(deltaX, deltaY); } public inputReceiverInit(): void { this.target.copy(this.camera.position); this.setRadius(0, true); // this.world.dirLight.target = this.world.camera; } public inputReceiverUpdate(timeStep: number): void { // Set fly speed let speed = this.movementSpeed * (this.actions.fast.isPressed ? timeStep * 600 : timeStep * 60); const up = Utils.getUp(this.camera); const right = Utils.getRight(this.camera); const forward = Utils.getBack(this.camera); this.upVelocity = THREE.MathUtils.lerp( this.upVelocity, +this.actions.up.isPressed - +this.actions.down.isPressed, 0.3 ); this.forwardVelocity = THREE.MathUtils.lerp( this.forwardVelocity, +this.actions.forward.isPressed - +this.actions.back.isPressed, 0.3 ); this.rightVelocity = THREE.MathUtils.lerp( this.rightVelocity, +this.actions.right.isPressed - +this.actions.left.isPressed, 0.3 ); this.target.add(up.multiplyScalar(speed * this.upVelocity)); this.target.add(forward.multiplyScalar(speed * this.forwardVelocity)); this.target.add(right.multiplyScalar(speed * this.rightVelocity)); } }