Home Reference Source

src/effects/ASCIIEffect.js

import { Color, Uniform, Vector2, Vector4 } from "three";
import { ASCIITexture } from "../textures/ASCIITexture.js";
import { Effect } from "./Effect.js";

import fragmentShader from "./glsl/ascii.frag";

/**
 * An ASCII effect.
 *
 * Warning: This effect cannot be merged with convolution effects.
 */

export class ASCIIEffect extends Effect {

	/**
	 * Constructs a new ASCII effect.
	 *
	 * @param {Object} [options] - The options.
	 * @param {ASCIITexture} [options.asciiTexture] - An ASCII character lookup texture.
	 * @param {Number} [options.cellSize=16] - The cell size. It's recommended to use even numbers.
	 * @param {Number} [options.color=null] - A color to use instead of the scene colors.
	 * @param {Boolean} [options.inverted=false] - Inverts the effect.
	 */

	constructor({
		asciiTexture = new ASCIITexture(),
		cellSize = 16,
		color = null,
		inverted = false
	} = {}) {

		super("ASCIIEffect", fragmentShader, {
			uniforms: new Map([
				["asciiTexture", new Uniform(null)],
				["cellCount", new Uniform(new Vector4())],
				["color", new Uniform(new Color())]
			])
		});

		/**
		 * @see {@link cellSize}
		 * @type {Number}
		 * @private
		 */

		this._cellSize = -1;

		/**
		 * The current resolution.
		 *
		 * @type {Vector2}
		 * @private
		 */

		this.resolution = new Vector2();

		this.asciiTexture = asciiTexture;
		this.cellSize = cellSize;
		this.color = color;
		this.inverted = inverted;

	}

	/**
	 * The current ASCII lookup texture.
	 *
	 * @type {ASCIITexture}
	 */

	get asciiTexture() {

		return this.uniforms.get("asciiTexture").value;

	}

	set asciiTexture(value) {

		const currentTexture = this.uniforms.get("asciiTexture").value;
		this.uniforms.get("asciiTexture").value = value;

		if(currentTexture !== null && currentTexture !== value) {

			currentTexture.dispose();

		}

		if(value !== null) {

			const cellCount = value.cellCount;

			this.defines.set("CHAR_COUNT_MINUS_ONE", (value.characterCount - 1).toFixed(1));
			this.defines.set("TEX_CELL_COUNT", cellCount.toFixed(1));
			this.defines.set("INV_TEX_CELL_COUNT", (1.0 / cellCount).toFixed(9));

			this.setChanged();

		}

	}

	/**
	 * A color that overrides the scene colors.
	 *
	 * @type {Color | String | Number | null}
	 */

	get color() {

		return this.uniforms.get("color").value;

	}

	set color(value) {

		if(value !== null) {

			this.uniforms.get("color").value.set(value);

		}

		if(this.defines.has("USE_COLOR") && value === null) {

			this.defines.delete("USE_COLOR");
			this.setChanged();

		} else if(!this.defines.has("USE_COLOR") && value !== null) {

			this.defines.set("USE_COLOR", "1");
			this.setChanged();

		}

	}

	/**
	 * Controls whether the effect should be inverted.
	 *
	 * @type {Boolean}
	 */

	get inverted() {

		return this.defines.has("INVERTED");

	}

	set inverted(value) {

		if(this.inverted !== value) {

			if(value) {

				this.defines.set("INVERTED", "1");

			} else {

				this.defines.delete("INVERTED");

			}

			this.setChanged();

		}

	}

	/**
	 * The cell size.
	 *
	 * @type {Number}
	 */

	get cellSize() {

		return this._cellSize;

	}

	set cellSize(value) {

		if(this._cellSize !== value) {

			this._cellSize = value;
			this.updateCellCount();

		}

	}

	/**
	 * Updates the cell count uniform.
	 *
	 * @private
	 */

	updateCellCount() {

		const cellCount = this.uniforms.get("cellCount").value;
		const resolution = this.resolution;

		cellCount.x = resolution.width / this.cellSize;
		cellCount.y = resolution.height / this.cellSize;
		cellCount.z = 1.0 / cellCount.x;
		cellCount.w = 1.0 / cellCount.y;

	}

	/**
	 * Updates the size of this pass.
	 *
	 * @param {Number} width - The width.
	 * @param {Number} height - The height.
	 */

	setSize(width, height) {

		this.resolution.set(width, height);
		this.updateCellCount();

	}

	/**
	 * Deletes internal render targets and textures.
	 */

	dispose() {

		if(this.asciiTexture !== null) {

			this.asciiTexture.dispose();

		}

		super.dispose();

	}

}