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

import { type InstructionSet } from '../../../rendering/renderers/shared/instructions/InstructionSet';
import { type InstructionPipe } from '../../../rendering/renderers/shared/instructions/RenderPipe';
import { type Renderer, type RenderPipes } from '../../../rendering/renderers/types';
import { type IRenderLayer } from '../../layers/RenderLayer';

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

/**
 * The CollectRenderablesMixin interface defines methods for collecting renderable objects
 * from a container and its children. These methods add the renderables to an instruction set,
 * which is used by the renderer to process and display the scene.
 */
export interface CollectRenderablesMixin
{
    /**
     * Collects all renderables from the container and its children, adding them to the instruction set.
     * This method decides whether to use a simple or advanced collection method based on the container's properties.
     * @param {InstructionSet} instructionSet - The set of instructions to which the renderables will be added.
     * @param {Renderer} renderer - The renderer responsible for rendering the scene.
     * @param {IRenderLayer} currentLayer - The current render layer being processed.
     * @memberof scene.Container#
     */
    collectRenderables(instructionSet: InstructionSet, renderer: Renderer, currentLayer: IRenderLayer): void;

    /**
     * Collects renderables using a simple method, suitable for containers marked as simple.
     * This method iterates over the container's children and adds their renderables to the instruction set.
     * @param {InstructionSet} instructionSet - The set of instructions to which the renderables will be added.
     * @param {Renderer} renderer - The renderer responsible for rendering the scene.
     * @param {IRenderLayer} currentLayer - The current render layer being processed.
     * @memberof scene.Container#
     */
    collectRenderablesSimple(instructionSet: InstructionSet, renderer: Renderer, currentLayer: IRenderLayer): void;

    /**
     * Collects renderables using an advanced method, suitable for containers with complex processing needs.
     * This method handles additional effects and transformations that may be applied to the renderables.
     * @param {InstructionSet} instructionSet - The set of instructions to which the renderables will be added.
     * @param {Renderer} renderer - The renderer responsible for rendering the scene.
     * @param {IRenderLayer} currentLayer - The current render layer being processed.
     * @memberof scene.Container#
     */
    collectRenderablesWithEffects(
        instructionSet: InstructionSet,
        renderer: Renderer,
        currentLayer: IRenderLayer,
    ): void;
}

/**
 * The collectRenderablesMixin provides implementations for the methods defined in the CollectRenderablesMixin interface.
 * It includes logic to determine the appropriate method for collecting renderables based on the container's properties.
 */
export const collectRenderablesMixin: Partial<Container> = {

    /**
     * Main method to collect renderables from the container and its children.
     * It checks the container's properties to decide whether to use a simple or advanced collection method.
     * @param {InstructionSet} instructionSet - The set of instructions to which the renderables will be added.
     * @param {Renderer} renderer - The renderer responsible for rendering the scene.
     * @param {IRenderLayer} currentLayer - The current render layer being processed.
     * @memberof scene.Container#
     */
    collectRenderables(instructionSet: InstructionSet, renderer: Renderer, currentLayer: IRenderLayer): void
    {
        // Skip processing if the container is not in the current render layer or is not fully visible.
        if ((this.parentRenderLayer && this.parentRenderLayer !== currentLayer)
            || this.globalDisplayStatus < 0b111 || !this.includeInBuild) return;

        // Sort children if the container has sortable children.
        if (this.sortableChildren)
        {
            this.sortChildren();
        }

        // Choose the appropriate method for collecting renderables based on the container's properties.
        if (this.isSimple)
        {
            this.collectRenderablesSimple(instructionSet, renderer, currentLayer);
        }
        else if (this.renderGroup)
        {
            renderer.renderPipes.renderGroup.addRenderGroup(this.renderGroup, instructionSet);
        }
        else
        {
            this.collectRenderablesWithEffects(instructionSet, renderer, currentLayer);
        }
    },

    /**
     * Simple method for collecting renderables from the container's children.
     * This method is efficient and used when the container is marked as simple.
     * @param {InstructionSet} instructionSet - The set of instructions to which the renderables will be added.
     * @param {Renderer} renderer - The renderer responsible for rendering the scene.
     * @param {IRenderLayer} currentLayer - The current render layer being processed.
     * @memberof scene.Container#
     */
    collectRenderablesSimple(
        instructionSet: InstructionSet,
        renderer: Renderer,
        currentLayer: IRenderLayer,
    ): void
    {
        const children = this.children;
        const length = children.length;

        // Iterate over each child and collect their renderables.
        for (let i = 0; i < length; i++)
        {
            children[i].collectRenderables(instructionSet, renderer, currentLayer);
        }
    },

    /**
     * Advanced method for collecting renderables, which handles additional effects.
     * This method is used when the container has complex processing needs.
     * @param {InstructionSet} instructionSet - The set of instructions to which the renderables will be added.
     * @param {Renderer} renderer - The renderer responsible for rendering the scene.
     * @param {IRenderLayer} currentLayer - The current render layer being processed.
     * @memberof scene.Container#
     */
    collectRenderablesWithEffects(
        instructionSet: InstructionSet,
        renderer: Renderer,
        currentLayer: IRenderLayer,
    ): void
    {
        const { renderPipes } = renderer;

        // Apply each effect to the renderables before collecting them.
        for (let i = 0; i < this.effects.length; i++)
        {
            const effect = this.effects[i];
            const pipe = renderPipes[effect.pipe as keyof RenderPipes] as InstructionPipe<any>;

            pipe.push(effect, this, instructionSet);
        }

        // Collect renderables using the simple method after applying effects.
        this.collectRenderablesSimple(instructionSet, renderer, currentLayer);

        // Remove effects from the renderables after collection, processing in reverse order.
        for (let i = this.effects.length - 1; i >= 0; i--)
        {
            const effect = this.effects[i];
            const pipe = renderPipes[effect.pipe as keyof RenderPipes] as InstructionPipe<any>;

            pipe.pop(effect, this, instructionSet);
        }
    }
} as Container;