Source: scene/mesh-perspective/PerspectiveMesh.ts

import { Texture } from '../../rendering/renderers/shared/texture/Texture';
import { definedProps } from '../container/utils/definedProps';
import { Mesh } from '../mesh/shared/Mesh';
import { PerspectivePlaneGeometry } from './PerspectivePlaneGeometry';

import type { MeshPlaneOptions } from '../mesh-plane/MeshPlane';
/**
 *
 * Constructor options used for `PerspectiveMesh` instances.
 * ```js
 * const meshPlane = new PerspectiveMesh({
 *  texture: Texture.from('snake.png'),
 *  verticesX: 20,
 *  verticesY: 20,
 *  x0: 0,
 *  y0: 0,
 *  x1: 100,
 *  y1: 0,
 *  x2: 100,
 *  y2: 100,
 *  x3: 0,
 *  y3: 100
 * });
 * @see PerspectiveMesh
 * @memberof scene
 */
export interface PerspectivePlaneOptions extends MeshPlaneOptions
{
    /** top left corner x value */
    x0?: number,
    /** top left corner y value */
    y0?: number,
    /** top right corner x value */
    x1?: number,
    /** top right corner y value */
    y1?: number,
    /** bottom right corner x value */
    x2?: number,
    /** bottom right corner y value */
    y2?: number,
    /** bottom left corner x value */
    x3?: number,
    /** bottom left corner y value */
    y3?: number
}

/**
 * A perspective mesh that allows you to draw a 2d plane with perspective. Where ever you move the corners
 * the texture will be projected to look like it is in 3d space. Great for mapping a 2D mesh into a 3D scene.
 *
 * The calculations is done at the uv level. This means that the more vertices you have the more smooth
 * the perspective will be. If you have a low amount of vertices you may see the texture stretch. Too many vertices
 * could be slower. It is a balance between performance and quality! We leave that to you to decide.
 *
 * IMPORTANT: This is not a full 3D mesh, it is a 2D mesh with a perspective projection applied to it :)
 * @example
 * ```js
 * const meshPlane = new PerspectiveMesh({
 *  texture: Texture.from('snake.png'),
 *  verticesX: 20,
 *  verticesY: 20,
 *  x0: 0,
 *  y0: 0,
 *  x1: 100,
 *  y1: 0,
 *  x2: 100,
 *  y2: 100,
 *  x3: 0,
 *  y3: 100
 * });
 * @see PerspectiveMesh
 * @memberof scene
 */
export class PerspectiveMesh extends Mesh<PerspectivePlaneGeometry>
{
    /** default options for the mesh */
    public static defaultOptions: PerspectivePlaneOptions = {
        texture: Texture.WHITE,
        verticesX: 10,
        verticesY: 10,
        x0: 0,
        y0: 0,
        x1: 100,
        y1: 0,
        x2: 100,
        y2: 100,
        x3: 0,
        y3: 100
    };

    /**
     * @param options - Options to be applied to PerspectiveMesh
     */
    constructor(options: PerspectivePlaneOptions)
    {
        options = { ...PerspectiveMesh.defaultOptions, ...options };

        const { texture, verticesX, verticesY, ...rest } = options;
        const planeGeometry = new PerspectivePlaneGeometry(definedProps({
            width: texture.width,
            height: texture.height,
            verticesX,
            verticesY,
        }));

        super(definedProps({ ...rest, geometry: planeGeometry }));

        this._texture = texture;

        this.geometry.setCorners(
            options.x0, options.y0,
            options.x1, options.y1,
            options.x2, options.y2,
            options.x3, options.y3
        );
    }

    /** Update the geometry when the texture is updated */
    protected textureUpdated(): void
    {
        const geometry: PerspectivePlaneGeometry = this.geometry as any;

        if (!geometry) return;

        const { width, height } = this.texture;

        if (geometry.width !== width || geometry.height !== height)
        {
            geometry.width = width;
            geometry.height = height;
            geometry.updateProjection();
        }
    }

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

        super.texture = value;

        this.textureUpdated();
    }

    /** The texture that the mesh uses */
    get texture(): Texture
    {
        return this._texture;
    }

    /**
     * Set the corners of the quad to the given coordinates
     * The mesh will then calculate the perspective so it looks correct!
     * @param x0 - x coordinate of the first corner
     * @param y0 - y coordinate of the first corner
     * @param x1 - x coordinate of the second corner
     * @param y1 - y coordinate of the second corner
     * @param x2 - x coordinate of the third corner
     * @param y2 - y coordinate of the third corner
     * @param x3 - x coordinate of the fourth corner
     * @param y3 - y coordinate of the fourth corner
     */
    public setCorners(x0: number, y0: number, x1: number, y1: number, x2: number, y2: number, x3: number, y3: number)
    {
        this.geometry.setCorners(x0, y0, x1, y1, x2, y2, x3, y3);
    }
}