Source: scene/container/container-mixins/getFastGlobalBoundsMixin.ts

import { Matrix } from '../../../maths/matrix/Matrix';
import { type Renderable } from '../../../rendering/renderers/shared/Renderable';
import { type IRenderLayer } from '../../layers/RenderLayer';
import { Bounds } from '../bounds/Bounds';
import { boundsPool } from '../bounds/utils/matrixAndBoundsPool';

import type { Container } from '../Container';

const tempMatrix = new Matrix();

/**
 * Interface for the GetFastGlobalBoundsMixin, which provides methods to compute
 * an approximate global bounding box for a container and its children.
 */
export interface GetFastGlobalBoundsMixin
{
    /**
     * Computes an approximate global bounding box for the container and its children.
     * This method is optimized for speed by using axis-aligned bounding boxes (AABBs),
     * and uses the last render results from when it updated the transforms. This function does not update them.
     * which may result in slightly larger bounds but never smaller than the actual bounds.
     *
     * for accurate (but less performant) results use `container.getGlobalBounds`
     * @param {boolean} [factorRenderLayers] - A flag indicating whether to consider render layers in the calculation.
     * @param {Bounds} [bounds] - The output bounds object to store the result. If not provided, a new one is created.
     * @returns {Bounds} The computed bounds.
     * @memberof scene.Container#
     */
    getFastGlobalBounds(factorRenderLayers?: boolean, bounds?: Bounds): Bounds;

    /**
     * Recursively calculates the global bounds for the container and its children.
     * This method is used internally by getFastGlobalBounds to traverse the scene graph.
     * @param {boolean} factorRenderLayers - A flag indicating whether to consider render layers in the calculation.
     * @param {Bounds} bounds - The bounds object to update with the calculated values.
     * @param {IRenderLayer} currentLayer - The current render layer being processed.
     * @memberof scene.Container#
     */
    _getGlobalBoundsRecursive(
        factorRenderLayers: boolean,
        bounds: Bounds,
        currentLayer: IRenderLayer,
    ): void;
}

/**
 * Mixin providing the implementation of the GetFastGlobalBoundsMixin interface.
 * It includes methods to compute and recursively calculate global bounds for containers.
 */
export const getFastGlobalBoundsMixin: Partial<Container> = {
    /**
     * Computes the global bounds for the container, considering its children and optionally
     * factoring in render layers. It starts by clearing the provided bounds object, then
     * recursively calculates the bounds, and finally applies the world transformation.
     * @param {boolean} [factorRenderLayers] - Whether to consider render layers in the calculation.
     * @param {Bounds} [bounds] - The bounds object to store the result. If not provided, a new one is created.
     * @returns {Bounds} The computed bounds.
     * @memberof scene.Container#
     */
    getFastGlobalBounds(factorRenderLayers?: boolean, bounds?: Bounds): Bounds
    {
        bounds ||= new Bounds();

        // Initialize the bounds for fresh calculations.
        bounds.clear();

        // Calculate bounds recursively, starting from the current container.
        this._getGlobalBoundsRecursive(!!factorRenderLayers, bounds, this.parentRenderLayer);

        // Validate the calculated bounds, resetting if invalid.
        if (!bounds.isValid)
        {
            bounds.set(0, 0, 0, 0);
        }

        // Apply the world transformation to the bounds.
        const renderGroup = this.renderGroup || this.parentRenderGroup;

        bounds.applyMatrix(renderGroup.worldTransform);

        return bounds;
    },

    /**
     * Recursively calculates the global bounds for the container and its children.
     * It considers visibility, measurability, and effects, and applies transformations
     * as necessary to compute the bounds accurately.
     * @param {boolean} factorRenderLayers - Whether to consider render layers in the calculation.
     * @param {Bounds} bounds - The bounds object to update with the calculated values.
     * @param {IRenderLayer} currentLayer - The current render layer being processed.
     * @memberof scene.Container#
     */
    _getGlobalBoundsRecursive(
        factorRenderLayers: boolean,
        bounds: Bounds,
        currentLayer: IRenderLayer,
    )
    {
        let localBounds = bounds;

        // Skip if the container is not in the current render layer when factoring render layers.
        if (factorRenderLayers && this.parentRenderLayer && this.parentRenderLayer !== currentLayer) return;

        // Skip if the container is not fully visible or not measurable.
        if (this.localDisplayStatus !== 0b111 || (!this.measurable))
        {
            return;
        }

        // Determine if effects need to be managed, requiring separate bounds handling.
        const manageEffects = !!this.effects.length;

        // Use a temporary bounds object if the container is a render group or has effects.
        if (this.renderGroup || manageEffects)
        {
            localBounds = boundsPool.get().clear();
        }

        // Add the container's own bounds area to the bounds if it exists.
        if (this.boundsArea)
        {
            bounds.addRect(this.boundsArea, this.worldTransform);
        }
        else
        {
            // If the container is renderable, add its bounds to the local bounds.
            if (this.renderPipeId)
            {
                const viewBounds = (this as Renderable).bounds;

                localBounds.addFrame(
                    viewBounds.minX,
                    viewBounds.minY,
                    viewBounds.maxX,
                    viewBounds.maxY,
                    this.groupTransform
                );
            }

            // Recursively process each child to include their bounds.
            const children = this.children;

            for (let i = 0; i < children.length; i++)
            {
                children[i]._getGlobalBoundsRecursive(factorRenderLayers, localBounds, currentLayer);
            }
        }

        // If effects are managed, apply them to the bounds.
        if (manageEffects)
        {
            let advanced = false;
            const renderGroup = this.renderGroup || this.parentRenderGroup;

            // Apply each effect that modifies bounds.
            for (let i = 0; i < this.effects.length; i++)
            {
                if (this.effects[i].addBounds)
                {
                    if (!advanced)
                    {
                        advanced = true;
                        localBounds.applyMatrix(renderGroup.worldTransform);
                    }
                    this.effects[i].addBounds(localBounds, true);
                }
            }

            // Adjust bounds back to the local coordinate space if advanced bounds were calculated.
            if (advanced)
            {
                localBounds.applyMatrix(renderGroup.worldTransform.copyTo(tempMatrix).invert());
                bounds.addBounds(localBounds, this.relativeGroupTransform);
            }

            // Add the local bounds to the final bounds and return the temporary bounds object.
            bounds.addBounds(localBounds);
            boundsPool.return(localBounds);
        }
        else if (this.renderGroup)
        {
            // If the container is a render group, add its local bounds to the final bounds.
            bounds.addBounds(localBounds, this.relativeGroupTransform);
            boundsPool.return(localBounds);
        }
    }

} as Container;