render_Camera.js

const vec = require("../geometry/vec.js");
const Animation = require("../other/Animation");
const { angleDiff } = require("../core/Common.js");

/**
 * Handles the game's camera. Accessed through `game.Render.camera`
 */
class Camera {
	/**
	 * Position of the camera
	 * @readonly
	 * @type {vec}
	 */
	position = new vec(0, 0);
	/**
	 * Field of view
	 * @readonly
	 * @type {number}
	 */
	fov = 2000;
	translation = new vec(0, 0);
	scale = 1;
	boundSize = 1000;
	Render = null;

	/**
	 * Creates a new camera object used by [Render](./Render.html)
	 * @param {Render} Render - Render object the camera for
	 */
	constructor(Render) {
		this.Render = Render;
	}

	/**
	 * Sets the camera position
	 * @param {vec} position - New position
	 */
	setPosition(position) {
		this.position.set(position);
	}

	/**
	 * Sets the camera's field of view
	 * @param {number} fov - New field of view
	 */
	setFov(fov) {
		this.fov = fov;
	}

	// ~ Point transformations
	screenPtToGame(point) {
		let { scale, translation } = this;
		let parentBounds = this.Render._parentBoundingBox;
		let parent = new vec(parentBounds.left - window.scrollX, parentBounds.top - window.scrollY);
		return new vec((point.x - translation.x - parent.x) / scale, (point.y - translation.y - parent.y) / scale);
	}
	gamePtToScreen(point) {
		let { scale, translation } =  this;
		let parentBounds = this.Render._parentBoundingBox;
		let parent = new vec(parentBounds.left - window.scrollX, parentBounds.top - window.scrollY);
		return new vec((point.x * scale + translation.x + parent.x), (point.y * scale + translation.y + parent.y));
	}

	/**
	 * 
	 * @param {number} [intensity=30] - How much the camera shakes
	 * @param {number} [duration=1] - How long the camera shakes, in seconds
	 * @param {function} [intensityCurve=Animation.ease.out.cubic] - Animation curve, see [Animation](./Animation.html) for ease functions
	 * @param {vec} [direction] - Direction the camera shakes. Shakes in all directions if left undefined
	 */
	async shake(intensity = 30, duration = 1, intensityCurve = Animation.ease.out.cubic, direction = undefined) {
		if (direction) {
			direction?.normalize2();
			direction.y *= -1;
		}

		let shakeDuration = 0.01; // duration of individual shakes
		let curIntensity = intensity;

		let intensityAnimation = new Animation({
			duration: duration,
			curve: intensityCurve,
			ontick: p => {
				curIntensity = intensity * (1 - p);
				shakeDuration = 0.01 + 0.05 * p;
			}
		});
		intensityAnimation.run();

		function getAngle(prevAngle) {
			if (direction) {
				return direction.mult(-Math.sign(direction.dot(new vec(prevAngle)))).angle;
			}
			else {
				return (angleDiff(prevAngle, Math.random() * Math.PI + Math.PI) + Math.PI * 2) % Math.PI * 2;
			}
		}
		
		let delta = new vec(0, 0);
		let lastAngle = getAngle(Math.random() * Math.PI * 2);
		while (intensityAnimation.isRunning() && duration - intensityAnimation.getTime() > shakeDuration) {
			let curDuration = shakeDuration;
			let angle = getAngle(lastAngle);
			lastAngle = angle;
			let nextDelta = new vec(Math.cos(angle) * curIntensity, Math.sin(angle) * curIntensity);
			let lastDelta = new vec(delta);
			let deltaDelta = nextDelta.sub(lastDelta); // trust me this isn't acceleration
			await new Animation({
				duration: curDuration,
				curve: Animation.ease.linear,
				ontick: p => {
					this.position.sub2(delta);
					delta.set(deltaDelta.mult(p).add(lastDelta));
					this.position.add2(delta);
				},
			}).run();
		}
		let lastDelta = new vec(delta);
		let deltaDelta = delta.mult(-1);
		await new Animation({
			duration: shakeDuration,
			curve: Animation.ease.linear,
			ontick: p => {
				this.position.sub2(delta);
				delta.set(deltaDelta.mult(p).add(lastDelta));
				this.position.add2(delta);
			},
		}).run();
	}
};
module.exports = Camera;