Source: geometry/custom-geometry.js

import { Geometry } from './geometry.js';
import {
    createColorsFromSpec,
    createIndexArray,
    createWireframeIndicesFromSolidIndices
} from './geometry-utils.js';

/**
 * Default wireframe indices input value.
 * When used, wireframe indices are generated from solid indices.
 *
 * @type {null}
 */
const DEFAULT_WIREFRAME_INDICES = null;

/**
 * Default colors input value. When used, no color buffer is created.
 *
 * @type {null}
 */
const DEFAULT_COLORS = null;

/**
 * Default UV input value. When used, no UV buffer is created.
 *
 * @type {null}
 */
const DEFAULT_UVS = null;

/**
 * Default normals input value. When used, no normal buffer is created.
 *
 * @type {null}
 */
const DEFAULT_NORMALS = null;

/**
 * Number of position components per vertex.
 *
 * @type {number}
 */
const POSITION_COMPONENT_COUNT = 3;

/**
 * Zero value used for numeric comparisons.
 *
 * @type {number}
 */
const ZERO_VALUE = 0;

/**
 * Options used by `CustomGeometry`.
 *
 * @typedef {Object} CustomGeometryOptions
 * @property {Float32Array} positions                                              - Vertex positions (xyz).
 * @property {number[] | Uint16Array | Uint32Array} indices                        - Solid triangle indices.
 * @property {number[] | Uint16Array | Uint32Array | null} [wireframeIndices=null] - Wireframe indices (lines).
 * @property {Float32Array | null} [colors=null]                                   - Optional colors (rgb) per vertex or uniform spec.
 * @property {Float32Array | null} [uvs=null]                                      - Optional UV coordinates (uv).
 * @property {Float32Array | null} [normals=null]                                  - Optional normals (xyz).
 */

/**
 * `CustomGeometry` allows creating geometry from user-provided buffers.
 * It supports: positions, optional colors, uvs, normals and index buffers.
 */
export class CustomGeometry extends Geometry {

    /**
     * @param {WebGL2RenderingContext} webglContext - WebGL2 rendering context.
     * @param {CustomGeometryOptions} options       - Geometry buffers.
     */
    constructor(webglContext, options = {}) {
        if (options === null || typeof options !== 'object' || Array.isArray(options)) {
            throw new TypeError('`CustomGeometry` expects options as a plain object.');
        }

        const {
            positions,
            indices,
            wireframeIndices = DEFAULT_WIREFRAME_INDICES,
            colors           = DEFAULT_COLORS,
            uvs              = DEFAULT_UVS,
            normals          = DEFAULT_NORMALS
        } = options;

        if (!(positions instanceof Float32Array)) {
            throw new TypeError('`CustomGeometry` expects `positions` as `Float32Array`.');
        }

        if ((positions.length % POSITION_COMPONENT_COUNT) !== ZERO_VALUE) {
            throw new RangeError('`CustomGeometry` expects `positions` length to be a multiple of 3.');
        }

        const vertexCount      = positions.length / POSITION_COMPONENT_COUNT;
        const solidIndexBuffer = CustomGeometry.#normalizeIndices(vertexCount, indices, 'indices');
        const wireIndexBuffer  = CustomGeometry.#normalizeWireframeIndices(vertexCount, solidIndexBuffer, wireframeIndices);
        const colorBuffer      = CustomGeometry.#normalizeColors(vertexCount, colors);
        const uvBuffer         = CustomGeometry.#normalizeOptionalFloat32Array(uvs, 'uvs');
        const normalBuffer     = CustomGeometry.#normalizeOptionalFloat32Array(normals, 'normals');

        super(
            webglContext,
            positions,
            colorBuffer,
            solidIndexBuffer,
            wireIndexBuffer,
            uvBuffer,
            normalBuffer
        );
    }

    /**
     * Normalizes solid indices input to a typed array.
     *
     * @param {number} vertexCount                           - Total vertex count.
     * @param {number[] | Uint16Array | Uint32Array} indices - Input indices.
     * @param {string} optionName                            - Option name for error reporting.
     * @returns {Uint16Array | Uint32Array}
     * @private
     */
    static #normalizeIndices(vertexCount, indices, optionName) {
        if (Array.isArray(indices)) {
            return createIndexArray(vertexCount, indices);
        }

        if (indices instanceof Uint16Array || indices instanceof Uint32Array) {
            return indices;
        }

        throw new TypeError(`\`CustomGeometry\` expects \`${optionName}\` as an array, Uint16Array or Uint32Array.`);
    }

    /**
     * Normalizes wireframe indices input.
     *
     * @param {number} vertexCount                                           - Total vertex count.
     * @param {Uint16Array | Uint32Array} solidIndices                       - Solid triangle indices.
     * @param {number[] | Uint16Array | Uint32Array | null} wireframeIndices - Wireframe indices.
     * @returns {Uint16Array | Uint32Array}
     * @private
     */
    static #normalizeWireframeIndices(vertexCount, solidIndices, wireframeIndices) {
        if (wireframeIndices === null || wireframeIndices === undefined) {
            return createWireframeIndicesFromSolidIndices(vertexCount, solidIndices);
        }

        if (Array.isArray(wireframeIndices)) {
            return createIndexArray(vertexCount, wireframeIndices);
        }

        if (wireframeIndices instanceof Uint16Array || wireframeIndices instanceof Uint32Array) {
            return wireframeIndices;
        }

        throw new TypeError('`CustomGeometry` expects `wireframeIndices` as an array, `Uint16Array`, `Uint32Array` or null.');
    }

    /**
     * Normalizes optional colors input.
     *
     * @param {number} vertexCount         - Total vertex count.
     * @param {Float32Array | null} colors - Colors input.
     * @returns {Float32Array | null}
     * @private
     */
    static #normalizeColors(vertexCount, colors) {
        if (colors === null || colors === undefined) {
            return null;
        }

        if (!(colors instanceof Float32Array)) {
            throw new TypeError('`CustomGeometry` expects `colors` as `Float32Array` or null.');
        }

        return createColorsFromSpec(vertexCount, colors);
    }

    /**
     * Normalizes optional float arrays.
     *
     * @param {Float32Array | null} value - Buffer value.
     * @param {string} optionName         - Option name for error reporting.
     * @returns {Float32Array | null}
     * @private
     */
    static #normalizeOptionalFloat32Array(value, optionName) {
        if (value === null || value === undefined) {
            return null;
        }

        if (!(value instanceof Float32Array)) {
            throw new TypeError(`\`CustomGeometry\` expects \`${optionName}\` as \`Float32Array\` or null.`);
        }

        return value;
    }
}