Source: material/solid-color-material.js

import { Material }      from './material.js';
import { ShaderProgram } from '../shader/shader-program.js';

/**
 * Attribute location used by vec3 position.
 *
 * @type {number}
 */
const POSITION_ATTRIBUTE_LOCATION = 0;

/**
 * Name of the matrix uniform in the shader.
 *
 * @type {string}
 */
const MATRIX_UNIFORM_NAME = 'u_matrix';

/**
 * Name of the color uniform in the shader.
 *
 * @type {string}
 */
const COLOR_UNIFORM_NAME = 'u_color';

/**
 * Opacity uniform name.
 *
 * @type {string}
 */
const OPACITY_UNIFORM_NAME = 'u_opacity';

/**
 * Number of components in a RGB color.
 *
 * @type {number}
 */
const COLOR_COMPONENT_COUNT = 3;

/**
 * Default solid color (white).
 *
 * @type {Float32Array}
 */
const DEFAULT_COLOR = new Float32Array([1.0, 1.0, 1.0]);

/**
 * GLSL vertex shader source code.
 *
 * @type {string}
 */
const VERTEX_SHADER_SOURCE = `#version 300 es
precision mediump float;
layout(location = ${POSITION_ATTRIBUTE_LOCATION}) in vec3 a_position;
uniform mat4 ${MATRIX_UNIFORM_NAME};

void main() {
    gl_Position = ${MATRIX_UNIFORM_NAME} * vec4(a_position, 1.0);
}
`;

/**
 * GLSL fragment shader source code.
 *
 * @type {string}
 */
const FRAGMENT_SHADER_SOURCE = `#version 300 es
precision mediump float;
uniform vec3  ${COLOR_UNIFORM_NAME};
uniform float ${OPACITY_UNIFORM_NAME};
out vec4 outColor;

void main() {
    outColor = vec4(${COLOR_UNIFORM_NAME}, ${OPACITY_UNIFORM_NAME});
}
`;

/**
 * Options used by SolidColorMaterial.
 *
 * @typedef {Object} SolidColorMaterialOptions
 * @property {Float32Array | number[]} [color] - RGB color [r,g,b] in 0..1 range.
 */

/**
 * SolidColorMaterial renders geometry using a single uniform color.
 */
export class SolidColorMaterial extends Material {
    /**
     * Current RGB color stored as Float32Array([r, g, b]).
     *
     * @type {Float32Array}
     * @private
     */
    #color = new Float32Array(DEFAULT_COLOR);

    /**
     * @param {WebGL2RenderingContext} webglContext         - WebGL2 rendering context used to compile shaders.
     * @param {SolidColorMaterialOptions} [options]         - Material options.
     */
    constructor(webglContext, options = {}) {
        if (options === null || typeof options !== 'object' || Array.isArray(options)) {
            throw new TypeError('SolidColorMaterial expects an options object (plain object).');
        }

        const shaderProgram = new ShaderProgram(webglContext, VERTEX_SHADER_SOURCE, FRAGMENT_SHADER_SOURCE);
        super(webglContext, shaderProgram, { ownsShaderProgram: true });
        const { color } = options;

        if (color !== undefined) {
            this.setColor(color);
        }
    }

    /**
     * Applies per-object uniforms.
     *
     * @param {Float32Array} matrix4 - Transformation matrix passed as u_matrix.
     */
    apply(matrix4) {
        this.shaderProgram.setMatrix4(MATRIX_UNIFORM_NAME, matrix4);
        this.shaderProgram.setVector3(COLOR_UNIFORM_NAME, this.#color);
        this.shaderProgram.setFloat(OPACITY_UNIFORM_NAME, this.opacity);
    }

    /**
     * Sets the RGB color.
     *
     * @param {Float32Array | number[]} color - [r, g, b] in 0..1 range.
     */
    setColor(color) {
        if (!Array.isArray(color) && !(color instanceof Float32Array)) {
            throw new TypeError('SolidColorMaterial.setColor expects a number[] or Float32Array.');
        }

        if (color.length !== COLOR_COMPONENT_COUNT) {
            throw new TypeError('SolidColorMaterial.setColor expects exactly 3 components [r, g, b].');
        }

        this.#color[0] = color[0];
        this.#color[1] = color[1];
        this.#color[2] = color[2];
    }

    /**
     * Returns the internal color buffer.
     * Note: returned Float32Array is mutable.
     *
     * @returns {Float32Array}
     */
    get color() {
        return this.#color;
    }
}