texture.js

import { gl } from './common.js';
import { Math } from './math.js';
import { font_b64 } from './font.js';

/**
 * This Object has all textures indexed by name to be used in the texture draw methods.
 * @type {Object}
 * @memberof HB
 */
const textures = {};
/**
 * (DO NOT USE) Texture with the included font.
 * @readonly
 * @type {HB.Texture}
 * @memberof HB
 */
let font = undefined;

/**
 * Class with all of the initialization and methods for a texture.
 * @memberof HB
 */
class Texture {
	/**
	 * Construct this for the texture to be added to the {@link HB.textures} Object, so you do not have to assign the instance to a value, it gets added to 'out' automatically.
	 * @constructor
	 * @param {string} name - Name of the texture, for indexing in the 'out' Object.
	 * @param {string} path - Path in the file system to the texture file, [supported formats]{@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#supported_image_formats}.
	 * @param {Object} out=HB.textures - The Object to add the texture to.
	 * @param {Function} callback - Is called when the texture has finished loading, logs the loaded textures to the browser console by default.
	 * @param {number} filter=gl.LINEAR - WebGL enum value for texture filtering, see gl.TEXTURE_MAG/MIN_FILTER on [MDN]{@link https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/texParameter#parameters}.
	 * @param {number} wrap=gl.CLAMP_TO_EDGE - WebGL enum value for texture wrapping, see gl.TEXTURE_WRAP_S/T on [MDN]{@link https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/texParameter#parameters}.
	 * @memberof HB
	 */
	constructor(name, path, out = textures, callback = function () { console.log("Texture loaded: " + this.name); }, filter = gl.LINEAR, wrap = gl.CLAMP_TO_EDGE) {
		this.id = gl.createTexture();
		this.name = name;
		this.onLoadCallback = callback;
		this.createTexture(path, filter, wrap);

		if (out === undefined) {
			textures[this.name] = this;
		} else if (Array.isArray(out)) {
			out.push(this);
		} else if (out instanceof Object) {
			out[this.name] = this;
		}
	}

	/**
	 * (DO NOT USE) Internal method for creating the font, circle, error texture and blank textures needed for drawing text, ellipses and colored shapes.
	 * @readonly
	 */
	static init() {
		font = new Texture('Hummingbird_Font-Atlas', font_b64);

		new Texture('Hummingbird_Error');
		textures.Hummingbird_Error.onLoadCallback();

		const circleSize = 1000;
		const circle = new Uint8Array(circleSize * circleSize * 4);
		for (let x = 0; x < circleSize; x++) {
			for (let y = 0; y < circleSize; y++) {
				const index = (x * circleSize + y) * 4;

				if (Math.dist(x, y, circleSize / 2, circleSize / 2) < circleSize / 2) {
					circle[index] = 255;
					circle[index + 1] = 255;
					circle[index + 2] = 255;
					circle[index + 3] = 255;
				} else {
					circle[index] = 0;
					circle[index + 1] = 0;
					circle[index + 2] = 0;
					circle[index + 3] = 0;
				}
			}
		}
		new Texture('Hummingbird_Circle');
		textures.Hummingbird_Circle.bind();
		this.setTextureParameters(gl.LINEAR, gl.CLAMP_TO_EDGE);
		gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, circleSize, circleSize, 0, gl.RGBA, gl.UNSIGNED_BYTE, circle);
		textures.Hummingbird_Circle.onLoadCallback();

		{ // set a blank texture on texture slot 0
			const blankTexture = gl.createTexture();
			gl.activeTexture(gl.TEXTURE0);
			gl.bindTexture(gl.TEXTURE_2D, blankTexture);
			this.setTextureParameters(gl.NEAREST, gl.REPEAT);
			gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([255, 255, 255, 255]));
		}
	}

	/**
	 * (DO NOT USE) Method for setting an error texture in case of a texture failing to load etc.
	 * @readonly
	 */
	setErrorTexture() {
		const errorTexture = new Uint8Array([255, 255, 255, 255, 191, 191, 191, 255, 191, 191, 191, 255, 255, 255, 255, 255]);
		Texture.setTextureParameters(gl.NEAREST, gl.REPEAT);
		gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 2, 2, 0, gl.RGBA, gl.UNSIGNED_BYTE, errorTexture);
	}

	/**
	 * (DO NOT USE) Internal method for actually creating a texture, is called by constructor.
	 * @param {string} path - Path of the texture.
	 * @param {number} filter - WebGL enum value for texture filtering, see gl.TEXTURE_MAG/MIN_FILTER on [MDN]{@link https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/texParameter#parameters}.
	 * @param {number} wrap - WebGL enum value for texture wrapping, see gl.TEXTURE_WRAP_S/T on [MDN]{@link https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/texParameter#parameters}.
	 * @readonly
	 */
	createTexture(path, filter, wrap) {
		this.bind();
		this.setErrorTexture();

		if (path === undefined) return;

		const image = new Image();
		image.onload = () => {
			this.bind();
			Texture.setTextureParameters(filter, wrap);
			gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
			this.onLoadCallback();
		}
		image.src = path;
	}

	/**
	 * (DO NOT USE) Internal method for setting the texure parameters, is called by {@link HB.Texture#createTexture}.
	 * @param {number} filter - WebGL enum value for texture filtering, see gl.TEXTURE_MAG/MIN_FILTER on [MDN]{@link https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/texParameter#parameters}.
	 * @param {number} wrap - WebGL enum value for texture wrapping, see gl.TEXTURE_WRAP_S/T on [MDN]{@link https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/texParameter#parameters}.
	 * @readonly
	 */
	static setTextureParameters(filter, wrap) {
		gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, filter);
		gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, filter);
		gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, wrap);
		gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, wrap);
	}

	/**
	 * (DO NOT USE) Internal method binding the texture to a specific slot.
	 * @param {number} slot - Which slot.
	 * @readonly
	 */
	bind(slot = 1) {
		gl.activeTexture(gl['TEXTURE' + slot]);
		gl.bindTexture(gl.TEXTURE_2D, this.id);
	}
	/**
	 * (DO NOT USE) Internal method for binding an empty texture, or unbinding this one.
	 * @readonly
	 */
	unbind() { gl.bindTexture(gl.TEXTURE_2D, null); }
	/**
	 * (DO NOT USE) Internal method for deleting this texture, is called on all textures by {@link HB.Renderer#delete}.
	 * @readonly
	 */
	delete() {
		this.unbind();
		gl.deleteTexture(this.id);
	}
}

export {
	Texture,
	textures,
	font
};