Source: filters/defaults/displacement/DisplacementFilter.ts

import { Matrix } from '../../../maths/matrix/Matrix';
import { Point } from '../../../maths/point/Point';
import { GlProgram } from '../../../rendering/renderers/gl/shader/GlProgram';
import { GpuProgram } from '../../../rendering/renderers/gpu/shader/GpuProgram';
import { UniformGroup } from '../../../rendering/renderers/shared/shader/UniformGroup';
import { Sprite } from '../../../scene/sprite/Sprite';
import { deprecation, v8_0_0 } from '../../../utils/logging/deprecation';
import { Filter } from '../../Filter';
import fragment from './displacement.frag';
import vertex from './displacement.vert';
import source from './displacement.wgsl';

import type { PointData } from '../../../maths/point/PointData';
import type { Texture } from '../../../rendering/renderers/shared/texture/Texture';
import type { FilterOptions } from '../../Filter';
import type { FilterSystem } from '../../FilterSystem';

/**
 * Options for DisplacementFilter
 * @memberof filters
 */
export interface DisplacementFilterOptions extends FilterOptions
{
    /** The texture used for the displacement map. */
    sprite: Sprite,
    /** The scale of the displacement. */
    scale?: number | PointData,
}

/**
 * A Noise effect filter.
 *
 * original filter: https://github.com/evanw/glfx.js/blob/master/src/filters/adjust/noise.js
 * @memberof filters
 * @author Vico @vicocotea
 */
export class DisplacementFilter extends Filter
{
    private readonly _sprite: Sprite;

    /**
     * **Note:** Our docs parser struggles to properly understand the constructor signature.
     * This is the correct signature.
     * ```ts
     * new DisplacementFilter(options?: DisplacementFilterOptions);
     * ```
     * @param options - The options for the filter.
     * @param options.sprite - The texture used for the displacement map.
     * @param options.scale - The scale of the displacement.
     */
    constructor(options: Sprite | DisplacementFilterOptions);
    constructor(sprite: Sprite, scale?: number | PointData);
    constructor(...args: [Sprite | DisplacementFilterOptions] | [Sprite, (number | PointData)?])
    {
        let options = args[0];

        if (options instanceof Sprite)
        {
            // #if _DEBUG
            if (args[1])
            {
                deprecation(v8_0_0, 'DisplacementFilter now uses options object instead of params. {sprite, scale}');
            }
            // #endif

            options = { sprite: options, scale: args[1] };
        }

        const { sprite, scale: scaleOption, ...rest } = options;

        let scale = scaleOption ?? 20;

        // check if is a number or a point
        if (typeof scale === 'number')
        {
            scale = new Point(scale, scale);
        }

        const filterUniforms = new UniformGroup({
            uFilterMatrix: { value: new Matrix(), type: 'mat3x3<f32>' },
            uScale: { value: scale, type: 'vec2<f32>' },
            uRotation: { value: new Float32Array([0, 0, 0, 0]), type: 'mat2x2<f32>' },
        });

        const glProgram = GlProgram.from({
            vertex,
            fragment,
            name: 'displacement-filter'
        });

        const gpuProgram = GpuProgram.from({
            vertex: {
                source,
                entryPoint: 'mainVertex',
            },
            fragment: {
                source,
                entryPoint: 'mainFragment',
            },
        });

        const textureSource = sprite.texture.source;

        super({
            ...rest,
            gpuProgram,
            glProgram,
            resources: {
                filterUniforms,
                uMapTexture: textureSource,
                uMapSampler: textureSource.style,
            },
        });

        this._sprite = options.sprite;
        this._sprite.renderable = false;
    }

    /**
     * Applies the filter.
     * @param filterManager - The manager.
     * @param input - The input target.
     * @param output - The output target.
     * @param clearMode - clearMode.
     */
    public apply(
        filterManager: FilterSystem,
        input: Texture,
        output: Texture,
        clearMode: boolean
    ): void
    {
        const uniforms = this.resources.filterUniforms.uniforms;

        filterManager.calculateSpriteMatrix(
            uniforms.uFilterMatrix,
            this._sprite
        );

        // Extract rotation from world transform
        const wt = this._sprite.worldTransform;
        const lenX = Math.sqrt((wt.a * wt.a) + (wt.b * wt.b));
        const lenY = Math.sqrt((wt.c * wt.c) + (wt.d * wt.d));

        if (lenX !== 0 && lenY !== 0)
        {
            uniforms.uRotation[0] = wt.a / lenX;
            uniforms.uRotation[1] = wt.b / lenX;
            uniforms.uRotation[2] = wt.c / lenY;
            uniforms.uRotation[3] = wt.d / lenY;
        }

        this.resources.uMapTexture = this._sprite.texture.source;

        filterManager.applyFilter(this, input, output, clearMode);
    }

    /** scaleX, scaleY for displacements */
    get scale(): Point
    {
        return this.resources.filterUniforms.uniforms.uScale as Point;
    }
}