Source: scene/sprite-tiling/TilingSpriteView.ts

import { ObservablePoint } from '../../maths/point/ObservablePoint';
import { Texture } from '../../rendering/renderers/shared/texture/Texture';
import { emptyViewObserver } from '../../rendering/renderers/shared/view/View';
import { uid } from '../../utils/data/uid';
import { Transform } from '../../utils/misc/Transform';

import type { PointData } from '../../maths/point/PointData';
import type { View } from '../../rendering/renderers/shared/view/View';
import type { Bounds, SimpleBounds } from '../container/bounds/Bounds';
import type { TextureDestroyOptions, TypeOrBool } from '../container/destroyTypes';

/**
 * Options for the TilingSprite constructor.
 * @memberof scene
 */
export interface TilingSpriteViewOptions
{
    /**
     * The texture to use for the sprite.
     * @default Texture.WHITE
     */
    texture?: Texture
    /**
     * The width of the tiling sprite. #
     * @default 256
     */
    width?: number
    /**
     * The height of the tiling sprite.
     * @default 256
     */
    height?: number
    // TODO needs a better name..
    /**
     * @todo
     * @default false
     */
    applyAnchorToTexture?: boolean
}

export class TilingSpriteView implements View
{
    public static defaultOptions: TilingSpriteViewOptions = {
        texture: Texture.EMPTY,
        width: 256,
        height: 256,
        applyAnchorToTexture: false,
    };

    public readonly owner = emptyViewObserver;
    public readonly uid = uid('tilingSpriteView');
    public readonly renderPipeId = 'tilingSprite';
    public batched = true;
    public anchor: ObservablePoint;

    /** @internal */
    public _tileTransform: Transform;
    /** @internal */
    public _texture: Texture;
    /** @internal */
    public _applyAnchorToTexture: boolean;
    /** @internal */
    public _didUpdate: boolean;

    public roundPixels: 0 | 1 = 0;

    private _bounds: SimpleBounds = { left: 0, right: 1, top: 0, bottom: 0 };
    private _boundsDirty = true;
    private _width: number;
    private _height: number;

    constructor(options: TilingSpriteViewOptions)
    {
        options = { ...TilingSpriteView.defaultOptions, ...options };

        this.anchor = new ObservablePoint(this, 0, 0);

        this._applyAnchorToTexture = options.applyAnchorToTexture;

        this.texture = options.texture;
        this._width = options.width;
        this._height = options.height;
        this._tileTransform = new Transform({ observer: this });
    }

    get bounds()
    {
        if (this._boundsDirty)
        {
            this._updateBounds();
            this._boundsDirty = false;
        }

        return this._bounds;
    }

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

        this._texture = value;

        this.onUpdate();
    }

    get texture()
    {
        return this._texture;
    }

    set width(value: number)
    {
        this._width = value;
        this.onUpdate();
    }

    get width()
    {
        return this._width;
    }

    set height(value: number)
    {
        this._height = value;
        this.onUpdate();
    }

    get height()
    {
        return this._height;
    }

    private _updateBounds()
    {
        const bounds = this._bounds;

        const anchor = this.anchor;

        const width = this._width;
        const height = this._height;

        bounds.right = -anchor._x * width;
        bounds.left = bounds.right + width;

        bounds.bottom = -anchor._y * height;
        bounds.top = bounds.bottom + height;
    }

    public addBounds(bounds: Bounds)
    {
        const _bounds = this.bounds;

        bounds.addFrame(
            _bounds.left,
            _bounds.top,
            _bounds.right,
            _bounds.bottom,
        );
    }

    public containsPoint(point: PointData)
    {
        const width = this.bounds.left;
        const height = this.bounds.top;
        const x1 = -width * this.anchor.x;
        let y1 = 0;

        if (point.x >= x1 && point.x <= x1 + width)
        {
            y1 = -height * this.anchor.y;

            if (point.y >= y1 && point.y <= y1 + height) return true;
        }

        return false;
    }

    /**
     * @internal
     */
    public onUpdate()
    {
        this._boundsDirty = true;
        this._didUpdate = true;
        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: TypeOrBool<TextureDestroyOptions> = false)
    {
        this.anchor = null;
        this._tileTransform = null;
        this._bounds = null;

        const destroyTexture = typeof options === 'boolean' ? options : options?.texture;

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

            this._texture.destroy(destroyTextureSource);
        }

        this._texture = null;
    }
}