UNPKG

@woosh/meep-engine

Version:

Pure JavaScript game engine. Fully featured and production ready.

268 lines (209 loc) • 7.57 kB
import { Matrix4 } from "three"; import { assert } from "../../../../../core/assert.js"; import { eulerAnglesFromMatrix } from "../../../../../core/geom/3d/mat4/eulerAnglesFromMatrix.js"; import { normalize_angle_rad } from "../../../../../core/geom/normalize_angle_rad.js"; import Quaternion from "../../../../../core/geom/Quaternion.js"; import Vector3 from '../../../../../core/geom/Vector3.js'; import { lerp } from "../../../../../core/math/lerp.js"; import { max2 } from "../../../../../core/math/max2.js"; import { sign_not_zero } from "../../../../../core/math/sign_not_zero.js"; const m4 = new Matrix4(); const angles = []; const panOffset = new Vector3(); /** * * @param {number} displacement_x * @param {number} displacement_y * @param {TopDownCameraController} input source thing being rotated * @param {TopDownCameraController} output target thing where the result is written to */ export function rotate_from_view(displacement_x, displacement_y, input, output) { // crop yaw and pitch to safe range of -PI to PI const i_pitch = normalize_angle_rad(input.pitch); const i_yaw = normalize_angle_rad(input.yaw); const i_roll = normalize_angle_rad(input.roll); const quaternion = Quaternion.fromEulerAngles(i_pitch, i_yaw, i_roll); const forward = Vector3.up.clone(); forward.applyQuaternion(quaternion); // flip rotation axis when pitch is over 90deg in either direction, that is UP vector is pointing down const d_yaw = sign_not_zero(forward.y) * displacement_x; output.yaw = i_yaw + d_yaw; output.pitch = i_pitch - displacement_y; } /** * pass in distance in world space to move left * @param {number} distance * @param {Object3D} object * @param {Vector3} result * @param {Vector3} multiplier */ function panLeft(distance, object, result, multiplier) { const te = object.matrix.elements; // get X column of matrix panOffset.set(te[0], te[1], te[2]); panOffset.multiplyScalar(-distance); //result.add(panOffset); result._sub(panOffset.x * multiplier.x, panOffset.y * multiplier.y, panOffset.z * multiplier.z); } /** * pass in distance in world space to move up * @param {number} distance * @param {Object3D} object * @param {Vector3} result * @param {Vector3} multiplier */ function panUp(distance, object, result, multiplier) { const te = object.matrix.elements; // get Y column of matrix panOffset.set(te[4], te[5], te[6]); panOffset.multiplyScalar(distance); //result.add(panOffset); result._sub(panOffset.x * multiplier.x, panOffset.y * multiplier.y, panOffset.z * multiplier.z); } const deg2_in_rad = Math.PI / 360; /** * main entry point; pass in Vector2 of change desired in pixel space, right and down are positive * @param {Vector2} delta * @param {Object3D} object * @param {Element} element * @param {number} targetDistance * @param {number} fov * @param {Vector3} result * @param {Vector3} multiplier */ function pan(delta, object, element, targetDistance, fov, result, multiplier = Vector3.one) { assert.isNumber(fov, 'fov'); // half of the fov is center to top of screen const d = targetDistance * Math.tan(fov * deg2_in_rad); // NOTE: we actually don't use screenWidth, since perspective camera is fixed to screen height const m = 2 * d / element.clientHeight; panLeft(delta.x * m, object, result, multiplier); panUp(delta.y * m, object, result, multiplier); } class TopDownCameraController { constructor(options) { this.target = new Vector3(); this.pitch = 0; this.yaw = 0; this.roll = 0; this.distance = 0; this.distanceMin = 1; this.distanceMax = 200; // if (options !== void 0) { this.fromJSON(options); } } /** * Set target, rotation and distance from 2 vectors * @param {Vector3} eye * @param {Vector3} target * @param {Vector3} [up] */ setFromEyeAndTarget(eye, target, up = Vector3.up) { /* NOTE: I'm not sure why, but three.js camera points in the opposite direction to normal objects See: https://github.com/mrdoob/three.js/blob/412b99a7f26e117ea97f40eb53d010ab81aa3279/src/core/Object3D.js#L282 */ m4.lookAt(target, eye, up); eulerAnglesFromMatrix(angles, m4.elements, 2, 1, 0) this.pitch = angles[2]; this.yaw = angles[1]; this.roll = angles[0]; this.distance = eye.distanceTo(target); // ensure we can maintain this distance this.distanceMax = max2(this.distance, this.distanceMax); this.target.copy(target); } /** * * @param {TopDownCameraController} other * @returns {boolean} */ equals(other) { return this.target.equals(other.target) && this.pitch === other.pitch && this.yaw === other.yaw && this.roll === other.roll && this.distance === other.distance && this.distanceMin === other.distanceMin && this.distanceMax === other.distanceMax ; } /** * * @param {TopDownCameraController} other */ copy(other) { this.target.copy(other.target); this.pitch = other.pitch; this.yaw = other.yaw; this.roll = other.roll; this.distance = other.distance; this.distanceMax = other.distanceMax; this.distanceMin = other.distanceMin; } /** * * @returns {TopDownCameraController} */ clone() { const r = new TopDownCameraController(); r.copy(this); return r; } /** * * @param {TopDownCameraController} a * @param {TopDownCameraController} b * @param {number} t */ lerp(a, b, t) { this.target.lerpVectors(a.target, b.target, t); this.pitch = lerp(a.pitch, b.pitch, t); this.yaw = lerp(a.yaw, b.yaw, t); this.roll = lerp(a.roll, b.roll, t); this.distance = lerp(a.distance, b.distance, t); this.distanceMin = lerp(a.distanceMin, b.distanceMin, t); this.distanceMax = lerp(a.distanceMax, b.distanceMax, t); } toJSON() { return { target: this.target.toJSON(), pitch: this.pitch, yaw: this.yaw, roll: this.roll, distance: this.distance, distanceMin: this.distanceMin, distanceMax: this.distanceMax }; } fromJSON( { distance = 0, distanceMin = 0, distanceMax = 0, pitch = 0, yaw = 0, roll = 0, target = Vector3.zero, } ) { this.distance = distance; this.distanceMin = distanceMin; this.distanceMax = distanceMax; this.pitch = pitch; this.roll = roll; this.yaw = yaw; this.target.fromJSON(target); } static fromJSON(j) { const r = new TopDownCameraController(); r.fromJSON(j); return r; } } TopDownCameraController.typeName = "TopDownCameraController"; TopDownCameraController.pan = pan; TopDownCameraController.rotate = rotate_from_view; export default TopDownCameraController;