Source: scene/mesh/shared/MeshView.ts

import { Polygon } from '../../../maths/shapes/Polygon';
import { State } from '../../../rendering/renderers/shared/state/State';
import { Texture } from '../../../rendering/renderers/shared/texture/Texture';
import { emptyViewObserver } from '../../../rendering/renderers/shared/view/View';
import { uid } from '../../../utils/data/uid';

import type { PointData } from '../../../maths/point/PointData';
import type { Shader } from '../../../rendering/renderers/shared/shader/Shader';
import type { View } from '../../../rendering/renderers/shared/view/View';
import type { Bounds } from '../../container/bounds/Bounds';
import type { DestroyOptions } from '../../container/destroyTypes';
import type { MeshGeometry } from './MeshGeometry';

const tempPolygon = new Polygon();

export interface TextureShader extends Shader
{
    texture: Texture;
}

export interface MeshViewOptions<
    GEOMETRY extends MeshGeometry = MeshGeometry,
    SHADER extends TextureShader = TextureShader
>
{
    geometry: GEOMETRY;
    shader?: SHADER;
    texture?: Texture;
}

export class MeshView<
    GEOMETRY extends MeshGeometry = MeshGeometry,
    SHADER extends TextureShader = TextureShader
>implements View
{
    public readonly uid: number = uid('meshView');
    public readonly renderPipeId = 'mesh';
    public readonly canBundle = true;
    public readonly owner = emptyViewObserver;
    public state = State.for2d();

    /** @ignore */
    public _texture: Texture;
    /** @ignore */
    public _geometry: GEOMETRY;
    /** @ignore */
    public _shader?: SHADER;

    public roundPixels: 0 | 1 = 0;

    constructor(options: MeshViewOptions<GEOMETRY, SHADER>)
    {
        this.shader = options.shader;
        this.texture = options.texture ?? this.shader?.texture ?? Texture.WHITE;

        this._geometry = options.geometry;
        this._geometry.on('update', this.onUpdate, this);
    }

    set shader(value: SHADER)
    {
        if (this._shader === value) return;

        this._shader = value;
        this.onUpdate();
    }

    get shader()
    {
        return this._shader;
    }

    set geometry(value: GEOMETRY)
    {
        if (this._geometry === value) return;

        this._geometry?.off('update', this.onUpdate, this);
        value.on('update', this.onUpdate, this);

        this._geometry = value;
        this.onUpdate();
    }

    get geometry()
    {
        return this._geometry;
    }

    set texture(value: Texture)
    {
        if (this._texture === value) return;

        if (this.shader)
        {
            this.shader.texture = value;
        }

        this._texture = value;
        this.onUpdate();
    }

    get texture()
    {
        return this._texture;
    }

    get batched()
    {
        if (this._shader) return false;

        if (this._geometry.batchMode === 'auto')
        {
            return this._geometry.positions.length / 2 <= 100;
        }

        return this._geometry.batchMode === 'batch';
    }

    public addBounds(bounds: Bounds)
    {
        bounds.addVertexData(this.geometry.positions, 0, this.geometry.positions.length);
    }

    public containsPoint(point: PointData)
    {
        const { x, y } = point;

        const vertices = this.geometry.getBuffer('aPosition').data;

        const points = tempPolygon.points;
        const indices = this.geometry.getIndex().data;
        const len = indices.length;
        const step = this.geometry.topology === 'triangle-strip' ? 3 : 1;

        for (let i = 0; i + 2 < len; i += step)
        {
            const ind0 = indices[i] * 2;
            const ind1 = indices[i + 1] * 2;
            const ind2 = indices[i + 2] * 2;

            points[0] = vertices[ind0];
            points[1] = vertices[ind0 + 1];
            points[2] = vertices[ind1];
            points[3] = vertices[ind1 + 1];
            points[4] = vertices[ind2];
            points[5] = vertices[ind2 + 1];

            if (tempPolygon.contains(x, y))
            {
                return true;
            }
        }

        return false;
    }

    /** Called when the geometry is updated. */
    protected onUpdate()
    {
        this.owner.onViewUpdate();
    }

    /**
     * Destroys this sprite renderable and optionally its texture.
     * @param options - Options parameter. A boolean will act as if all options
     *  have been set to that value
     * @param {boolean} [options.texture=false] - Should it destroy the current texture of the renderable as well
     * @param {boolean} [options.textureSource=false] - Should it destroy the textureSource of the renderable as well
     */
    public destroy(options: DestroyOptions = false): void
    {
        const destroyTexture = typeof options === 'boolean' ? options : options?.texture;

        if (destroyTexture)
        {
            const destroyTextureSource = typeof options === 'boolean' ? options : options?.textureSource;

            this._texture.destroy(destroyTextureSource);
        }

        this._geometry?.off('update', this.onUpdate, this);

        this._texture = null;
        this._geometry = null;
        this._shader = null;
    }
}