Source: scene/mesh.js

import { Object3D } from './object3d.js';
import { Geometry } from '../geometry/geometry.js';
import { Material } from '../material/material.js';

/**
 * Default ownership flag for geometry.
 * When true, `Mesh.dispose()` disposes geometry.
 *
 * @type {boolean}
 */
const DEFAULT_OWNS_GEOMETRY = true;

/**
 * Default ownership flag for material.
 * When true, `Mesh.dispose()` disposes material.
 *
 * @type {boolean}
 */
const DEFAULT_OWNS_MATERIAL = true;

/**
 * Ownership options for Mesh.
 *
 * @typedef {Object} MeshOwnershipOptions
 * @property {boolean} [ownsGeometry = true] - When true, `Mesh.dispose()` disposes geometry.
 * @property {boolean} [ownsMaterial = true] - When true, `Mesh.dispose()` disposes material.
 */

/**
 * Renderable object that combines geometry and material.
 */
export class Mesh extends Object3D {

    /**
     * Geometry used by this mesh (vertex/index buffers, VAO).
     *
     * @type {Geometry}
     * @private
     */
    #geometry;

    /**
     * Material used by this mesh (shader program + render state + uniforms).
     *
     * @type {Material}
     * @private
     */
    #material;

    /**
     * When true, `Mesh.dispose()` will dispose the geometry. Use false for shared geometries.
     *
     * @type {boolean}
     * @private
     */
    #ownsGeometry;

    /**
     * When true, `Mesh.dispose()` will dispose the material.
     * Use false when material (and its textures) is shared between meshes.
     *
     * @type {boolean}
     * @private
     */
    #ownsMaterial;

    /**
     * Indicates whether this mesh has been disposed. Disposed meshes should not be rendered.
     *
     * @type {boolean}
     * @private
     */
    #isDisposed = false;

    /**
     * @param {Geometry} geometry                       - Geometry, that provides vertex and index buffers for this mesh.
     * @param {Material} material                       - Material, that defines how the geometry should be shaded and rendered.
     * @param {MeshOwnershipOptions} [ownershipOptions] - Ownership flags for geometry/material.
     */
    constructor(geometry, material, ownershipOptions = {}) {
        super();

        if (!(geometry instanceof Geometry)) {
            throw new TypeError('Mesh constructor expects a Geometry instance.');
        }

        if (!(material instanceof Material)) {
            throw new TypeError('Mesh constructor expects a Material instance.');
        }

        if (ownershipOptions === null || typeof ownershipOptions !== 'object' || Array.isArray(ownershipOptions)) {
            throw new TypeError('Mesh constructor expects `ownershipOptions` as a plain object.');
        }

        const {
            ownsGeometry = DEFAULT_OWNS_GEOMETRY,
            ownsMaterial = DEFAULT_OWNS_MATERIAL
        } = ownershipOptions;

        if (typeof ownsGeometry !== 'boolean') {
            throw new TypeError('Mesh constructor option `ownsGeometry` must be a boolean.');
        }

        if (typeof ownsMaterial !== 'boolean') {
            throw new TypeError('Mesh constructor option `ownsMaterial` must be a boolean.');
        }

        this.#geometry     = geometry;
        this.#material     = material;
        this.#ownsGeometry = ownsGeometry;
        this.#ownsMaterial = ownsMaterial;
    }

    /**
     * Releases GPU resources owned by this mesh (geometry and/or material).
     * After dispose, the mesh can remain as a scene object, but it should not be rendered.
     * Important ownership rule, if `ownsMaterial=false`, `Mesh.dispose()` must NOT dispose the material (and its textures).
     */
    dispose() {
        if (this.#isDisposed) {
            return;
        }

        if (this.#ownsGeometry) {
            this.#geometry.dispose();
        }

        if (this.#ownsMaterial) {
            this.#material.dispose();
        }

        this.#isDisposed = true;
    }

    /**
     * @returns {Geometry}
     */
    get geometry() {
        return this.#geometry;
    }

    /**
     * @returns {Material}
     */
    get material() {
        return this.#material;
    }

    /**
     * @returns {boolean}
     */
    get ownsGeometry() {
        return this.#ownsGeometry;
    }

    /**
     * @returns {boolean}
     */
    get ownsMaterial() {
        return this.#ownsMaterial;
    }

    /**
     * @returns {boolean}
     */
    get isDisposed() {
        return this.#isDisposed;
    }
}