Viewport.js

'use strict';

const WebGLContext = require('./WebGLContext');

/**
 * Bind the viewport to the rendering context.
 *
 * @private
 *
 * @param {Viewport} viewport - The viewport object.
 * @param {number} x - The horizontal offset override.
 * @param {number} y - The vertical offset override.
 * @param {number} width - The width override.
 * @param {number} height - The height override.
 */
function set(viewport, x, y, width, height) {
	const gl = viewport.gl;
	x = (x !== undefined) ? x : viewport.x;
	y = (y !== undefined) ? y : viewport.y;
	width = (width !== undefined) ? width : viewport.width;
	height = (height !== undefined) ? height : viewport.height;
	gl.viewport(x, y, width, height);
}

/**
 * A viewport class for managing WebGL viewports.
 */
class Viewport {

	/**
	 * Instantiates a Viewport object.
	 *
	 * @param {Object} spec - The viewport specification object.
	 * @param {number} spec.width - The width of the viewport.
	 * @param {number} spec.height - The height of the viewport.
	 */
	constructor(spec = {}) {
		this.gl = WebGLContext.get();
		this.stack = [];
		// set size
		this.resize(
			spec.width || this.gl.canvas.width,
			spec.height || this.gl.canvas.height);
	}

	/**
	 * Updates the viewports width and height. This resizes the underlying canvas element.
	 *
	 * @param {number} width - The width of the viewport.
	 * @param {number} height - The height of the viewport.
	 *
	 * @returns {Viewport} The viewport object, for chaining.
	 */
	resize(width = 0, height = 0) {
		if (typeof width !== 'number' || width <= 0) {
			throw `Provided \`width\` of \`${width}\` is invalid`;
		}
		if (typeof height !== 'number' || height <= 0) {
			throw `Provided \`height\` of \`${height}\` is invalid`;
		}
		this.width = width;
		this.height = height;
		this.gl.canvas.width = width;
		this.gl.canvas.height = height;
		return this;
	}

	/**
	 * Sets the viewport dimensions and position. The underlying canvas element
	 * is not affected.
	 *
	 * @param {number} x - The horizontal offset override.
	 * @param {number} y - The vertical offset override.
	 * @param {number} width - The width override.
	 * @param {number} height - The height override.
	 *
	 * @returns {Viewport} - The viewport object, for chaining.
	 */
	push(x = 0, y = 0, width = this.width, height = this.height) {
		if (typeof x !== 'number') {
			throw `Provided \`x\` of \`${x}\` is invalid`;
		}
		if (typeof y !== 'number') {
			throw `Provided \`y\` of \`${y}\` is invalid`;
		}
		if (typeof width !== 'number' || width <= 0) {
			throw `Provided \`width\` of \`${width}\` is invalid`;
		}
		if (typeof height !== 'number' || height <= 0) {
			throw `Provided \`height\` of \`${height}\` is invalid`;
		}
		// push onto stack
		this.stack.push({
			x: x,
			y: y,
			width: width,
			height: height
		});
		// set viewport
		set(this, x, y, width, height);
		return this;
	}

	/**
	 * Pops current the viewport object and sets the viewport beneath it.
	 *
	 * @returns {Viewport} The viewport object, for chaining.
	 */
	pop() {
		if (this.stack.length === 0) {
			throw 'Viewport stack is empty';
		}
		this.stack.pop();
		if (this.stack.length > 0) {
			const top = this.stack[this.stack.length - 1];
			set(this, top.x, top.y, top.width, top.height);
		} else {
			set(this);
		}
		return this;
	}
}

module.exports = Viewport;