Source: scene/sprite/SpriteView.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 { updateQuadBounds } from '../../utils/data/updateQuadBounds';

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

export class SpriteView implements View
{
    public readonly renderPipeId = 'sprite';
    public readonly owner: ViewObserver = emptyViewObserver;
    public readonly uid: number = uid('spriteView');
    public batched = true;
    public anchor: ObservablePoint;

    // sprite specific..
    /** @internal */
    public _texture: Texture;
    /** @internal */
    public _didUpdate = false;

    private _bounds: SimpleBounds = { left: 0, right: 1, top: 0, bottom: 0 };
    private _sourceBounds: SimpleBounds = { left: 0, right: 1, top: 0, bottom: 0 };
    private _boundsDirty = true;
    private _sourceBoundsDirty = true;

    public roundPixels: 0 | 1 = 0;

    constructor(texture: Texture)
    {
        this.anchor = new ObservablePoint(
            this,
            texture.defaultAnchor?.x || 0,
            texture.defaultAnchor?.y || 0,
        );

        this.texture = texture;
    }

    set texture(value: Texture)
    {
        value ||= Texture.EMPTY;

        if (this._texture === value) return;

        this._texture = value;

        this.onUpdate();
    }

    get texture()
    {
        return this._texture;
    }

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

        return this._bounds;
    }

    get sourceBounds()
    {
        if (this._sourceBoundsDirty)
        {
            this._updateSourceBounds();
            this._sourceBoundsDirty = false;
        }

        return this._sourceBounds;
    }

    // passed local space..
    public containsPoint(point: PointData)
    {
        const bounds = this.sourceBounds;

        if (point.x >= bounds.right && point.x <= bounds.left)
        {
            if (point.y >= bounds.bottom && point.y <= bounds.top)
            {
                return true;
            }
        }

        return false;
    }

    public addBounds(bounds: Bounds)
    {
        const _bounds = this._texture.trim ? this.sourceBounds : this.bounds;

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

    /**
     * @internal
     */
    public onUpdate()
    {
        this._didUpdate = true;

        this._sourceBoundsDirty = this._boundsDirty = true;

        this.owner.onViewUpdate();
    }

    private _updateBounds()
    {
        updateQuadBounds(this._bounds, this.anchor, this._texture, 0);
    }

    private _updateSourceBounds()
    {
        const anchor = this.anchor;
        const texture = this._texture;

        const sourceBounds = this._sourceBounds;

        const { width, height } = texture.orig;

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

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

    /**
     * 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;

        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;
        this._bounds = null;
        this._sourceBounds = null;
    }
}