import { Disposable, Vector4, PRNG, Vector2 } from '@zephyr3d/base';
import { Primitive } from './primitive.js';
import '@zephyr3d/device';
import { createProgramH0, createProgramHk, createProgramFFT2H, createProgramFFT2V, createProgramPostFFT2 } from '../shaders/water.js';
import '../shaders/atmosphere.js';
import { fetchSampler } from '../utility/misc.js';
import { getDevice } from '../app/api.js';

function getDefaultBuildParams() {
    return {
        cascades: [
            {
                size: 400.0,
                strength: 0.4,
                croppiness: -1.5,
                minWave: 0,
                maxWave: 100
            },
            {
                size: 100.0,
                strength: 0.4,
                croppiness: -1.2,
                minWave: 0,
                maxWave: 100
            },
            {
                size: 15,
                strength: 0.2,
                croppiness: -0.5,
                minWave: 0,
                maxWave: 7
            }
        ],
        resolution: 256,
        wind: new Vector2(2, 2),
        alignment: 1,
        foamParams: new Vector2(1.2, 7.2),
        randomSeed: 0
    };
}
const RENDER_NONE = 0;
const RENDER_NORMAL = 1;
const RENDER_TWO_PASS = 2;
const THREAD_GROUP_SIZE = 16;
/**
 * This class generates a 2D ocean field using the Fast Fourier Transform (FFT) algorithm.
 * @public
 */ class FFTWaveGenerator extends Disposable {
    static _globals = null;
    _useComputeShader;
    _h0BindGroup;
    _hkBindGroup;
    _hkBindGroup2;
    _hkBindGroup4;
    _fft2hBindGroup;
    _fft2vBindGroup;
    _fft2hBindGroup2Used;
    _fft2hBindGroup2Free;
    _fft2hBindGroup4Used;
    _fft2hBindGroup4Free;
    _fft2vBindGroup2Used;
    _fft2vBindGroup2Free;
    _fft2vBindGroup4Used;
    _fft2vBindGroup4Free;
    _postfft2BindGroup;
    _postfft2BindGroup2;
    _postfft2BindGroup4;
    _updateRenderStates;
    _sizes;
    _croppinesses;
    _params;
    _instanceData;
    _ifftTextures;
    _cascades;
    _paramsChanged;
    _version;
    _resolutionChanged;
    _textureFormat;
    _h0TextureFormat;
    _dataTextureFormat;
    _renderMode;
    /**
   * Create a new instance of the FFTWaveGenerator class.
   * @param params - Ocean field build parameters. If not provided, default parameters will be used.
   */ constructor(params){
        super();
        const device = getDevice();
        const renderTargetFloat16 = device.getDeviceCaps().textureCaps.supportHalfFloatColorBuffer;
        const maxDrawBuffers = /*device.type !== 'webgl' && */ renderTargetFloat16 ? device.getDeviceCaps().framebufferCaps.maxDrawBuffers : 0;
        this._textureFormat = 'rgba16f';
        this._h0TextureFormat = 'rgba16f';
        this._dataTextureFormat = 'rgba16f';
        this._useComputeShader = device.type === 'webgpu';
        const maxSampleBytes = device.getDeviceCaps().framebufferCaps.maxColorAttachmentBytesPerSample;
        if (maxDrawBuffers === 0) {
            this._renderMode = RENDER_NONE;
        } else if (this._useComputeShader || maxSampleBytes >= 48) {
            this._renderMode = RENDER_NORMAL;
        } else {
            this._renderMode = RENDER_TWO_PASS;
        }
        if (this._renderMode !== RENDER_NONE) {
            FFTWaveGenerator._globals = FFTWaveGenerator._globals ?? {
                programs: {
                    h0Program: createProgramH0(this._useComputeShader, THREAD_GROUP_SIZE, this._h0TextureFormat),
                    hkProgram: this._renderMode === RENDER_NORMAL ? createProgramHk(this._useComputeShader, THREAD_GROUP_SIZE, this._dataTextureFormat) : null,
                    hkProgram2: this._renderMode === RENDER_TWO_PASS ? createProgramHk(false, 0, undefined, 2) : null,
                    hkProgram4: this._renderMode === RENDER_TWO_PASS ? createProgramHk(false, 0, undefined, 4) : null,
                    fft2hProgram: this._renderMode === RENDER_NORMAL ? createProgramFFT2H(this._useComputeShader, THREAD_GROUP_SIZE, this._dataTextureFormat) : null,
                    fft2hProgram2: this._renderMode === RENDER_TWO_PASS ? createProgramFFT2H(false, 0, undefined, 2) : null,
                    fft2hProgram4: this._renderMode === RENDER_TWO_PASS ? createProgramFFT2H(false, 0, undefined, 4) : null,
                    fft2vProgram: this._renderMode === RENDER_NORMAL ? createProgramFFT2V(this._useComputeShader, THREAD_GROUP_SIZE, this._dataTextureFormat) : null,
                    fft2vProgram2: this._renderMode === RENDER_TWO_PASS ? createProgramFFT2V(false, 0, undefined, 2) : null,
                    fft2vProgram4: this._renderMode === RENDER_TWO_PASS ? createProgramFFT2V(false, 0, undefined, 4) : null,
                    postfft2Program: this._renderMode === RENDER_NORMAL ? createProgramPostFFT2(this._useComputeShader, THREAD_GROUP_SIZE, this._dataTextureFormat) : null,
                    postfft2Program2: this._renderMode === RENDER_TWO_PASS ? createProgramPostFFT2(false, 0, undefined, 2) : null,
                    postfft2Program4: this._renderMode === RENDER_TWO_PASS ? createProgramPostFFT2(false, 0, undefined, 4) : null
                },
                quad: FFTWaveGenerator.createQuad(),
                noiseTextures: new Map(),
                butterflyTextures: new Map()
            };
            this._params = params ?? getDefaultBuildParams();
            const programs = FFTWaveGenerator._globals.programs;
            this._h0BindGroup = device.createBindGroup(programs.h0Program.bindGroupLayouts[0]);
            this._hkBindGroup = programs.hkProgram ? device.createBindGroup(programs.hkProgram.bindGroupLayouts[0]) : null;
            this._hkBindGroup2 = programs.hkProgram2 ? device.createBindGroup(programs.hkProgram2.bindGroupLayouts[0]) : null;
            this._hkBindGroup4 = programs.hkProgram4 ? device.createBindGroup(programs.hkProgram4.bindGroupLayouts[0]) : null;
            this._fft2hBindGroup = programs.fft2hProgram ? device.createBindGroup(programs.fft2hProgram.bindGroupLayouts[0]) : null;
            this._fft2vBindGroup = programs.fft2vProgram ? device.createBindGroup(programs.fft2vProgram.bindGroupLayouts[0]) : null;
            this._fft2hBindGroup2Used = [
                [],
                []
            ];
            this._fft2hBindGroup2Free = [
                [],
                []
            ];
            this._fft2hBindGroup4Used = [
                [],
                []
            ];
            this._fft2hBindGroup4Free = [
                [],
                []
            ];
            this._fft2vBindGroup2Used = [
                [],
                []
            ];
            this._fft2vBindGroup2Free = [
                [],
                []
            ];
            this._fft2vBindGroup4Used = [
                [],
                []
            ];
            this._fft2vBindGroup4Free = [
                [],
                []
            ];
            this._postfft2BindGroup = programs.postfft2Program ? device.createBindGroup(programs.postfft2Program.bindGroupLayouts[0]) : null;
            this._postfft2BindGroup2 = programs.postfft2Program2 ? device.createBindGroup(programs.postfft2Program2.bindGroupLayouts[0]) : null;
            this._postfft2BindGroup4 = programs.postfft2Program4 ? device.createBindGroup(programs.postfft2Program4.bindGroupLayouts[0]) : null;
            this._instanceData = null;
            this._ifftTextures = null;
            this._sizes = new Vector4();
            this._croppinesses = new Vector4();
            this._cascades = [
                new Vector4(),
                new Vector4(),
                new Vector4(),
                new Vector4()
            ];
            this._updateRenderStates = getDevice().createRenderStateSet();
            this._updateRenderStates.useRasterizerState().setCullMode('none');
            this._updateRenderStates.useDepthState().enableTest(false).enableWrite(false);
            this._version = 0;
            this.paramsChanged();
        }
    }
    get version() {
        return this._version;
    }
    clone() {
        return new FFTWaveGenerator(this._params);
    }
    /*
  get params() {
    return this._params;
  }
  set params(val: OceanFieldBuildParams) {
    if (val && val !== this._params) {
      this._params = val;
      this.paramsChanged();
    }
  }
  */ paramsChanged() {
        this._paramsChanged = true;
        this._version++;
    }
    /** Gets the wave alighment */ get alignment() {
        return this._params.alignment;
    }
    set alignment(val) {
        if (this._params.alignment !== val) {
            this._params.alignment = val;
            this.paramsChanged();
        }
    }
    /** Gets the wind vector */ get wind() {
        return this._params.wind;
    }
    set wind(val) {
        if (val !== this._params.wind && (val.x !== this._params.wind.x || val.y !== this._params.wind.y)) {
            this._params.wind.x = val.x;
            this._params.wind.y = val.y;
            this.paramsChanged();
        }
    }
    /** Gets the foam width */ get foamWidth() {
        return this._params.foamParams.x;
    }
    set foamWidth(val) {
        if (val !== this._params.foamParams.x) {
            this._params.foamParams.x = val;
            this.paramsChanged();
        }
    }
    /** Gets the foam contrast */ get foamContrast() {
        return this._params.foamParams.y;
    }
    set foamContrast(val) {
        if (val !== this._params.foamParams.y) {
            this._params.foamParams.y = val;
            this.paramsChanged();
        }
    }
    /** Gets the wave length for the specified cascade */ getWaveLength(cascade) {
        return this._params.cascades[cascade].size;
    }
    /**
   * Sets the wave length for the specified cascade
   * @param cascade - The cascade index
   * @param length - The new wave length for the specified cascade
   */ setWaveLength(cascade, length) {
        if (this._params.cascades[cascade].size !== length) {
            this._params.cascades[cascade].size = length;
            this.paramsChanged();
        }
    }
    /** Gets the wave strength for the specified cascade */ getWaveStrength(cascade) {
        return this._params.cascades[cascade].strength;
    }
    /**
   * Sets the wave strength for the specified cascade
   * @param cascade - The cascade index
   * @param strength - The new wave strength for the specified cascade
   */ setWaveStrength(cascade, strength) {
        if (this._params.cascades[cascade].strength !== strength) {
            this._params.cascades[cascade].strength = strength;
            this.paramsChanged();
        }
    }
    /** Gets the wave croppiness for the specified cascade */ getWaveCroppiness(cascade) {
        return this._params.cascades[cascade].croppiness;
    }
    /**
   * Sets the wave croppiness for the specified cascade
   * @param cascade - The cascade index
   * @param croppiness - The new wave croppiness for the specified cascade
   */ setWaveCroppiness(cascade, croppiness) {
        if (this._params.cascades[cascade].croppiness !== croppiness) {
            this._params.cascades[cascade].croppiness = croppiness;
            this.paramsChanged();
        }
    }
    /** @internal */ static createQuad() {
        const vertexData = new Float32Array([
            -1,
            -1,
            0,
            0.0,
            0.0,
            1,
            -1,
            0,
            1.0,
            0.0,
            1,
            1,
            0,
            1.0,
            1.0,
            -1,
            1,
            0,
            0.0,
            1.0
        ]);
        const indexData = new Uint32Array([
            0,
            1,
            2,
            0,
            2,
            3
        ]);
        const prim = new Primitive();
        prim.createAndSetVertexBuffer([
            'position_f32x3',
            'tex0_f32x2'
        ], vertexData);
        prim.createAndSetIndexBuffer(indexData);
        prim.primitiveType = 'triangle-list';
        prim.indexStart = 0;
        prim.indexCount = indexData.length;
        return prim;
    }
    /** @internal */ getButterflyTexture(size) {
        const device = getDevice();
        let tex = FFTWaveGenerator._globals.butterflyTextures.get(size);
        if (!tex) {
            tex = device.createTexture2D('rgba32f', Math.log2(size), size, {
                mipmapping: false
            });
            tex.name = `butterfly${size}`;
            tex.update(this.createButterflyTexture(size), 0, 0, tex.width, tex.height);
            FFTWaveGenerator._globals.butterflyTextures.set(size, tex);
        }
        return tex;
    }
    /** @internal */ generateInitialSpectrum() {
        const device = getDevice();
        const instanceData = this.getInstanceData();
        device.setProgram(FFTWaveGenerator._globals.programs.h0Program);
        device.setBindGroup(0, this._h0BindGroup);
        this._h0BindGroup.setTexture('noise', this.getNoiseTexture(this._params.resolution, this._params.randomSeed), fetchSampler('repeat_nearest_nomip'));
        this._h0BindGroup.setValue('resolution', this._params.resolution);
        this._h0BindGroup.setValue('wind', this._params.wind);
        this._h0BindGroup.setValue('alignment', this._params.alignment);
        for(let i = 0; i < this._params.cascades.length; i++){
            this._cascades[i].x = this._params.cascades[i].size;
            this._cascades[i].y = this._params.cascades[i].strength * 0.081 / (this._params.cascades[i].size * this._params.cascades[i].size);
            this._cascades[i].z = 2 * Math.PI / this._params.cascades[i].maxWave;
            this._cascades[i].w = 2 * Math.PI / this._params.cascades[i].minWave;
        }
        this._h0BindGroup.setValue('cascade0', this._cascades[0]);
        this._h0BindGroup.setValue('cascade1', this._cascades[1]);
        this._h0BindGroup.setValue('cascade2', this._cascades[2]);
        if (device.type === 'webgpu') {
            this._h0BindGroup.setTexture('spectrum', instanceData.h0Textures);
            device.compute(this._params.resolution / THREAD_GROUP_SIZE, this._params.resolution / THREAD_GROUP_SIZE, 1);
        } else {
            device.setFramebuffer(instanceData.h0Framebuffer);
            device.clearFrameBuffer(Vector4.zero(), null, null);
            FFTWaveGenerator._globals.quad.draw();
        }
    }
    /** @internal */ getNoiseTexture(size, randomSeed) {
        const device = getDevice();
        let tex = FFTWaveGenerator._globals.noiseTextures.get(size);
        if (!tex) {
            tex = device.createTexture2D(device.type === 'webgl' ? 'rgba32f' : 'rg32f', size, size, {
                mipmapping: false
            });
            tex.name = `noiseTex${size}`;
            tex.update(this.getNoise2d(size, randomSeed, device.type === 'webgl'), 0, 0, size, size);
            FFTWaveGenerator._globals.noiseTextures.set(size, tex);
        }
        return tex;
    }
    /** @internal */ getNoise2d(size, randomSeed, rgba) {
        const rand = new PRNG(randomSeed);
        if (rgba) {
            const array = new Float32Array(size * size * 4);
            for(let i = 0; i < size * size; i++){
                array[i * 4 + 0] = rand.get();
                array[i * 4 + 1] = rand.get();
            }
            return array;
        } else {
            return Float32Array.from([
                ...Array(size * size * 2)
            ].map(()=>rand.get()));
        }
    }
    /** @internal */ reverseBits(v, width) {
        return parseInt(v.toString(2).padStart(width, '0').split('').reverse().join(''), 2);
    }
    /** @internal */ createButterflyTexture(size) {
        const width = Math.log2(size);
        const height = size;
        const texture = new Float32Array(width * height * 4);
        const w = 2.0 * Math.PI / size;
        const bitReversed = [
            ...Array(size).keys()
        ].map((v)=>this.reverseBits(v, width));
        for(let j = 0; j < width; j++){
            for(let i = 0; i < height; i++){
                const k = i * (size >> j + 1);
                const c = Math.cos(k * w);
                const s = Math.sin(k * w);
                const span = 2 ** j;
                const wing = i % 2 ** (j + 1) < span ? 0 : 1; // 0 - top wing, 1 - bottom wing
                const texel = new Vector4();
                if (j === 0) {
                    if (wing === 0) {
                        texel.setXYZW(c, s, bitReversed[i], bitReversed[i + 1]);
                    } else {
                        texel.setXYZW(c, s, bitReversed[i - 1], bitReversed[i]);
                    }
                } else {
                    if (wing === 0) {
                        texel.setXYZW(c, s, i, i + span);
                    } else {
                        texel.setXYZW(c, s, i - span, i);
                    }
                }
                texture[(width * i + j) * 4] = texel[0];
                texture[(width * i + j) * 4 + 1] = texel[1];
                texture[(width * i + j) * 4 + 2] = texel[2];
                texture[(width * i + j) * 4 + 3] = texel[3];
            }
        }
        return texture;
    }
    /** @internal */ getInstanceData() {
        if (!this._instanceData) {
            const device = getDevice();
            const h0Textures = this.createNTextures(device, this._h0TextureFormat, this._params.resolution, 3, 'Water-h0', false);
            const dataTextures = this.createNTextures(device, this._dataTextureFormat, this._params.resolution, 6, 'Water-data', true);
            const spectrumTextures = this.createNTextures(device, this._textureFormat, this._params.resolution, 6, 'Water-spectrum', true);
            const pingpongTextures = this.createNTextures(device, this._textureFormat, this._params.resolution, 6, 'Water-pingpong', false);
            this._instanceData = {
                dataTextures,
                h0Textures: h0Textures,
                pingpongTextures: pingpongTextures,
                spectrumTextures: spectrumTextures,
                h0Framebuffer: this._useComputeShader ? null : device.createFrameBuffer(h0Textures, null),
                spectrumFramebuffer: !this._useComputeShader && this._renderMode === RENDER_NORMAL ? device.createFrameBuffer(spectrumTextures, null) : null,
                spectrumFramebuffer2: this._renderMode === RENDER_TWO_PASS ? device.createFrameBuffer(spectrumTextures.slice(4, 6), null) : null,
                spectrumFramebuffer4: this._renderMode === RENDER_TWO_PASS ? device.createFrameBuffer(spectrumTextures.slice(0, 4), null) : null,
                pingpongFramebuffer: !this._useComputeShader && this._renderMode === RENDER_NORMAL ? device.createFrameBuffer(pingpongTextures, null) : null,
                pingpongFramebuffer2: this._renderMode === RENDER_TWO_PASS ? device.createFrameBuffer(pingpongTextures.slice(4, 6), null) : null,
                pingpongFramebuffer4: this._renderMode === RENDER_TWO_PASS ? device.createFrameBuffer(pingpongTextures.slice(0, 4), null) : null,
                postIfft2Framebuffer: !this._useComputeShader && this._renderMode === RENDER_NORMAL ? device.createFrameBuffer(dataTextures, null) : null,
                postIfft2Framebuffer2: this._renderMode === RENDER_TWO_PASS ? device.createFrameBuffer(dataTextures.slice(4, 6), null) : null,
                postIfft2Framebuffer4: this._renderMode === RENDER_TWO_PASS ? device.createFrameBuffer(dataTextures.slice(0, 4), null) : null
            };
        }
        return this._instanceData;
    }
    /** @internal */ createNTextures(device, format, size, num, name, linear) {
        const options = {
            samplerOptions: {
                minFilter: linear ? 'linear' : 'nearest',
                magFilter: linear ? 'linear' : 'nearest',
                mipFilter: 'none',
                addressU: 'repeat',
                addressV: 'repeat'
            },
            writable: !!this._useComputeShader,
            mipmapping: false
        };
        if (this._useComputeShader) {
            const tex = device.createTexture2DArray(format, size, size, num, options);
            tex.name = name;
            return tex;
        } else {
            return Array.from({
                length: num
            }).map((val, index)=>{
                const tex = device.createTexture2D(format, size, size, options);
                tex.name = `${name}-${index}`;
                return tex;
            });
        }
    }
    /** {@inheritDoc WaveGenerator.update} */ update(time) {
        const device = getDevice();
        device.pushDeviceStates();
        device.setRenderStates(this._updateRenderStates);
        if (this._resolutionChanged) {
            this.disposeInstanceData();
        }
        if (this._paramsChanged) {
            this.generateInitialSpectrum();
        }
        this._resolutionChanged = false;
        this._paramsChanged = false;
        for(let i = 0; i < 3; i++){
            this._sizes[i] = this._params.cascades[i].size;
            this._croppinesses[i] = this._params.cascades[i].croppiness;
        }
        if (this._renderMode === RENDER_NORMAL) {
            this.generateSpectrum(time);
            this.ifft2();
            this.postIfft2();
        } else {
            this.generateSpectrumTwoPass(time);
            this.ifft2TwoPass();
            this.postIfft2TwoPass();
        }
        device.popDeviceStates();
    }
    /** {@inheritDoc WaveGenerator.needUpdate} */ needUpdate() {
        return true;
    }
    /** @internal */ disposeNTextures(texture) {
        if (Array.isArray(texture)) {
            texture.forEach((tex)=>tex.dispose());
        } else if (texture) {
            texture.dispose();
        }
    }
    /** @internal */ disposeInstanceData() {
        if (this._instanceData) {
            this.disposeNTextures(this._instanceData.dataTextures);
            this.disposeNTextures(this._instanceData.h0Textures);
            this.disposeNTextures(this._instanceData.pingpongTextures);
            this.disposeNTextures(this._instanceData.spectrumTextures);
            this._instanceData.h0Framebuffer?.dispose();
            this._instanceData.pingpongFramebuffer?.dispose();
            this._instanceData.pingpongFramebuffer2?.dispose();
            this._instanceData.pingpongFramebuffer4?.dispose();
            this._instanceData.spectrumFramebuffer?.dispose();
            this._instanceData.spectrumFramebuffer2?.dispose();
            this._instanceData.spectrumFramebuffer4?.dispose();
            this._instanceData.postIfft2Framebuffer?.dispose();
            this._instanceData.postIfft2Framebuffer2?.dispose();
            this._instanceData.postIfft2Framebuffer4?.dispose();
            this._instanceData = null;
        }
    }
    /** @internal */ generateSpectrum(time) {
        const device = getDevice();
        const instanceData = this.getInstanceData();
        const nearestRepeatSampler = fetchSampler('repeat_nearest_nomip');
        device.setProgram(FFTWaveGenerator._globals.programs.hkProgram);
        device.setBindGroup(0, this._hkBindGroup);
        this._hkBindGroup.setValue('t', time);
        this._hkBindGroup.setValue('resolution', this._params.resolution);
        this._hkBindGroup.setValue('sizes', this._sizes);
        if (this._useComputeShader) {
            this._hkBindGroup.setTexture('h0Texture', instanceData.h0Textures);
            this._hkBindGroup.setTexture('spectrum', instanceData.spectrumTextures);
            device.compute(this._params.resolution / THREAD_GROUP_SIZE, this._params.resolution / THREAD_GROUP_SIZE, 1);
        } else {
            const h0Textures = instanceData.h0Textures;
            this._hkBindGroup.setTexture('h0Texture0', h0Textures[0], nearestRepeatSampler);
            this._hkBindGroup.setTexture('h0Texture1', h0Textures[1], nearestRepeatSampler);
            this._hkBindGroup.setTexture('h0Texture2', h0Textures[2], nearestRepeatSampler);
            if (device.type === 'webgl') {
                this._hkBindGroup.setValue('h0TexSize', new Vector2(h0Textures[0].width, h0Textures[0].height));
            }
            device.setFramebuffer(instanceData.spectrumFramebuffer);
            FFTWaveGenerator._globals.quad.draw();
        }
    }
    /** @internal */ generateSpectrumTwoPass(time) {
        const device = getDevice();
        const instanceData = this.getInstanceData();
        const nearestRepeatSampler = fetchSampler('repeat_nearest_nomip');
        device.setProgram(FFTWaveGenerator._globals.programs.hkProgram4);
        device.setBindGroup(0, this._hkBindGroup4);
        const h0Textures = instanceData.h0Textures;
        this._hkBindGroup4.setValue('resolution', this._params.resolution);
        this._hkBindGroup4.setValue('sizes', this._sizes);
        this._hkBindGroup4.setTexture('h0Texture0', h0Textures[0], nearestRepeatSampler);
        this._hkBindGroup4.setTexture('h0Texture1', h0Textures[1], nearestRepeatSampler);
        this._hkBindGroup4.setValue('t', time);
        if (device.type === 'webgl') {
            this._hkBindGroup4.setValue('h0TexSize', new Vector2(h0Textures[0].width, h0Textures[0].height));
        }
        device.setFramebuffer(instanceData.spectrumFramebuffer4);
        FFTWaveGenerator._globals.quad.draw();
        device.setProgram(FFTWaveGenerator._globals.programs.hkProgram2);
        device.setBindGroup(0, this._hkBindGroup2);
        this._hkBindGroup2.setValue('resolution', this._params.resolution);
        this._hkBindGroup2.setValue('sizes', this._sizes);
        this._hkBindGroup2.setTexture('h0Texture2', h0Textures[2], nearestRepeatSampler);
        this._hkBindGroup2.setValue('t', time);
        if (device.type === 'webgl') {
            this._hkBindGroup2.setValue('h0TexSize', new Vector2(h0Textures[0].width, h0Textures[0].height));
        }
        device.setFramebuffer(instanceData.spectrumFramebuffer2);
        FFTWaveGenerator._globals.quad.draw();
    }
    /** @internal */ ifft2() {
        const device = getDevice();
        const instanceData = this.getInstanceData();
        const nearestRepeatSampler = fetchSampler('repeat_nearest_nomip');
        const phases = Math.log2(this._params.resolution);
        const pingPongTextures = [
            instanceData.spectrumTextures,
            instanceData.pingpongTextures
        ];
        const pingPongFramebuffers = this._useComputeShader ? [
            instanceData.pingpongTextures,
            instanceData.spectrumTextures
        ] : [
            instanceData.pingpongFramebuffer,
            instanceData.spectrumFramebuffer
        ];
        const butterflyTex = this.getButterflyTexture(this._params.resolution);
        let pingPong = 0;
        // horizontal ifft
        device.setProgram(FFTWaveGenerator._globals.programs.fft2hProgram);
        device.setBindGroup(0, this._fft2hBindGroup);
        this._fft2hBindGroup.setTexture('butterfly', butterflyTex, nearestRepeatSampler);
        for(let phase = 0; phase < phases; phase++){
            this._fft2hBindGroup.setValue('phase', phase);
            if (this._useComputeShader) {
                this._fft2hBindGroup.setTexture('spectrum', pingPongTextures[pingPong], nearestRepeatSampler);
                this._fft2hBindGroup.setTexture('ifft', pingPongFramebuffers[pingPong]);
                device.compute(this._params.resolution / THREAD_GROUP_SIZE, this._params.resolution / THREAD_GROUP_SIZE, 1);
            } else {
                const pingPongTexture = pingPongTextures[pingPong];
                device.setFramebuffer(pingPongFramebuffers[pingPong]);
                this._fft2hBindGroup.setTexture('spectrum0', pingPongTexture[0], nearestRepeatSampler);
                this._fft2hBindGroup.setTexture('spectrum1', pingPongTexture[1], nearestRepeatSampler);
                this._fft2hBindGroup.setTexture('spectrum2', pingPongTexture[2], nearestRepeatSampler);
                this._fft2hBindGroup.setTexture('spectrum3', pingPongTexture[3], nearestRepeatSampler);
                this._fft2hBindGroup.setTexture('spectrum4', pingPongTexture[4], nearestRepeatSampler);
                this._fft2hBindGroup.setTexture('spectrum5', pingPongTexture[5], nearestRepeatSampler);
                if (device.type === 'webgl') {
                    this._fft2hBindGroup.setValue('texSize', new Vector4(pingPongTexture[0].width, pingPongTexture[0].height, butterflyTex.width, butterflyTex.height));
                }
                FFTWaveGenerator._globals.quad.draw();
            }
            pingPong = 1 - pingPong; //(pingPong + 1) % 2;
        }
        // vertical ifft
        device.setProgram(FFTWaveGenerator._globals.programs.fft2vProgram);
        device.setBindGroup(0, this._fft2vBindGroup);
        this._fft2vBindGroup.setTexture('butterfly', butterflyTex, nearestRepeatSampler);
        for(let phase = 0; phase < phases; phase++){
            this._fft2vBindGroup.setValue('phase', phase);
            if (this._useComputeShader) {
                this._fft2vBindGroup.setTexture('spectrum', pingPongTextures[pingPong], nearestRepeatSampler);
                this._fft2vBindGroup.setTexture('ifft', pingPongFramebuffers[pingPong]);
                device.compute(this._params.resolution / THREAD_GROUP_SIZE, this._params.resolution / THREAD_GROUP_SIZE, 1);
            } else {
                const pingPongTexture = pingPongTextures[pingPong];
                device.setFramebuffer(pingPongFramebuffers[pingPong]);
                this._fft2vBindGroup.setTexture('spectrum0', pingPongTexture[0], nearestRepeatSampler);
                this._fft2vBindGroup.setTexture('spectrum1', pingPongTexture[1], nearestRepeatSampler);
                this._fft2vBindGroup.setTexture('spectrum2', pingPongTexture[2], nearestRepeatSampler);
                this._fft2vBindGroup.setTexture('spectrum3', pingPongTexture[3], nearestRepeatSampler);
                this._fft2vBindGroup.setTexture('spectrum4', pingPongTexture[4], nearestRepeatSampler);
                this._fft2vBindGroup.setTexture('spectrum5', pingPongTexture[5], nearestRepeatSampler);
                if (device.type === 'webgl') {
                    this._fft2vBindGroup.setValue('texSize', new Vector4(pingPongTexture[0].width, pingPongTexture[0].height, butterflyTex.width, butterflyTex.height));
                }
                FFTWaveGenerator._globals.quad.draw();
            }
            pingPong = 1 - pingPong;
        }
        this._ifftTextures = pingPongTextures[pingPong];
    }
    /** @internal */ getFFT2hBindGroup2(device, pingpong) {
        let bindGroup = this._fft2hBindGroup2Used[pingpong].pop();
        if (!bindGroup) {
            bindGroup = device.createBindGroup(FFTWaveGenerator._globals.programs.fft2hProgram2.bindGroupLayouts[0]);
        }
        this._fft2hBindGroup2Free[pingpong].push(bindGroup);
        return bindGroup;
    }
    /** @internal */ getFFT2hBindGroup4(device, pingpong) {
        let bindGroup = this._fft2hBindGroup4Used[pingpong].pop();
        if (!bindGroup) {
            bindGroup = device.createBindGroup(FFTWaveGenerator._globals.programs.fft2hProgram4.bindGroupLayouts[0]);
        }
        this._fft2hBindGroup4Free[pingpong].push(bindGroup);
        return bindGroup;
    }
    /** @internal */ getFFT2vBindGroup2(device, pingpong) {
        let bindGroup = this._fft2vBindGroup2Used[pingpong].pop();
        if (!bindGroup) {
            bindGroup = device.createBindGroup(FFTWaveGenerator._globals.programs.fft2vProgram2.bindGroupLayouts[0]);
        }
        this._fft2vBindGroup2Free[pingpong].push(bindGroup);
        return bindGroup;
    }
    /** @internal */ getFFT2vBindGroup4(device, pingpong) {
        let bindGroup = this._fft2vBindGroup4Used[pingpong].pop();
        if (!bindGroup) {
            bindGroup = device.createBindGroup(FFTWaveGenerator._globals.programs.fft2vProgram4.bindGroupLayouts[0]);
        }
        this._fft2vBindGroup4Free[pingpong].push(bindGroup);
        return bindGroup;
    }
    /** @internal */ ifft2TwoPass() {
        const device = getDevice();
        const instanceData = this.getInstanceData();
        const nearestRepeatSampler = fetchSampler('repeat_nearest_nomip');
        const phases = Math.log2(this._params.resolution);
        const pingPongTextures = [
            instanceData.spectrumTextures,
            instanceData.pingpongTextures
        ];
        const pingPongFramebuffers4 = [
            instanceData.pingpongFramebuffer4,
            instanceData.spectrumFramebuffer4
        ];
        const pingPongFramebuffers2 = [
            instanceData.pingpongFramebuffer2,
            instanceData.spectrumFramebuffer2
        ];
        const butterflyTex = this.getButterflyTexture(this._params.resolution);
        // horizontal ifft
        let pingPong = 0;
        for(let phase = 0; phase < phases; phase++){
            device.setFramebuffer(pingPongFramebuffers4[pingPong]);
            device.setProgram(FFTWaveGenerator._globals.programs.fft2hProgram4);
            const fft2hBindGroup4 = this.getFFT2hBindGroup4(device, pingPong);
            device.setBindGroup(0, fft2hBindGroup4);
            fft2hBindGroup4.setTexture('butterfly', butterflyTex, nearestRepeatSampler);
            fft2hBindGroup4.setValue('phase', phase);
            fft2hBindGroup4.setTexture('spectrum0', pingPongTextures[pingPong][0], nearestRepeatSampler);
            fft2hBindGroup4.setTexture('spectrum1', pingPongTextures[pingPong][1], nearestRepeatSampler);
            fft2hBindGroup4.setTexture('spectrum2', pingPongTextures[pingPong][2], nearestRepeatSampler);
            fft2hBindGroup4.setTexture('spectrum3', pingPongTextures[pingPong][3], nearestRepeatSampler);
            if (device.type === 'webgl') {
                fft2hBindGroup4.setValue('texSize', new Vector4(pingPongTextures[pingPong][0].width, pingPongTextures[pingPong][0].height, butterflyTex.width, butterflyTex.height));
            }
            FFTWaveGenerator._globals.quad.draw();
            device.setFramebuffer(pingPongFramebuffers2[pingPong]);
            device.setProgram(FFTWaveGenerator._globals.programs.fft2hProgram2);
            const fft2hBindGroup2 = this.getFFT2hBindGroup2(device, pingPong);
            device.setBindGroup(0, fft2hBindGroup2);
            fft2hBindGroup2.setTexture('butterfly', butterflyTex, nearestRepeatSampler);
            fft2hBindGroup2.setValue('phase', phase);
            fft2hBindGroup2.setTexture('spectrum4', pingPongTextures[pingPong][4], nearestRepeatSampler);
            fft2hBindGroup2.setTexture('spectrum5', pingPongTextures[pingPong][5], nearestRepeatSampler);
            if (device.type === 'webgl') {
                fft2hBindGroup2.setValue('texSize', new Vector4(pingPongTextures[pingPong][0].width, pingPongTextures[pingPong][0].height, butterflyTex.width, butterflyTex.height));
            }
            FFTWaveGenerator._globals.quad.draw();
            pingPong = (pingPong + 1) % 2;
        }
        // vertical ifft
        for(let phase = 0; phase < phases; phase++){
            device.setFramebuffer(pingPongFramebuffers4[pingPong]);
            device.setProgram(FFTWaveGenerator._globals.programs.fft2vProgram4);
            const fft2vBindGroup4 = this.getFFT2vBindGroup4(device, pingPong);
            device.setBindGroup(0, fft2vBindGroup4);
            fft2vBindGroup4.setTexture('butterfly', butterflyTex, nearestRepeatSampler);
            fft2vBindGroup4.setValue('phase', phase);
            fft2vBindGroup4.setTexture('spectrum0', pingPongTextures[pingPong][0], nearestRepeatSampler);
            fft2vBindGroup4.setTexture('spectrum1', pingPongTextures[pingPong][1], nearestRepeatSampler);
            fft2vBindGroup4.setTexture('spectrum2', pingPongTextures[pingPong][2], nearestRepeatSampler);
            fft2vBindGroup4.setTexture('spectrum3', pingPongTextures[pingPong][3], nearestRepeatSampler);
            if (device.type === 'webgl') {
                fft2vBindGroup4.setValue('texSize', new Vector4(pingPongTextures[pingPong][0].width, pingPongTextures[pingPong][0].height, butterflyTex.width, butterflyTex.height));
            }
            FFTWaveGenerator._globals.quad.draw();
            device.setFramebuffer(pingPongFramebuffers2[pingPong]);
            device.setProgram(FFTWaveGenerator._globals.programs.fft2vProgram2);
            const fft2vBindGroup2 = this.getFFT2vBindGroup2(device, pingPong);
            device.setBindGroup(0, fft2vBindGroup2);
            fft2vBindGroup2.setTexture('butterfly', butterflyTex, nearestRepeatSampler);
            fft2vBindGroup2.setValue('phase', phase);
            fft2vBindGroup2.setTexture('spectrum4', pingPongTextures[pingPong][4], nearestRepeatSampler);
            fft2vBindGroup2.setTexture('spectrum5', pingPongTextures[pingPong][5], nearestRepeatSampler);
            if (device.type === 'webgl') {
                fft2vBindGroup2.setValue('texSize', new Vector4(pingPongTextures[pingPong][0].width, pingPongTextures[pingPong][0].height, butterflyTex.width, butterflyTex.height));
            }
            FFTWaveGenerator._globals.quad.draw();
            pingPong = (pingPong + 1) % 2;
        }
        this._ifftTextures = pingPongTextures[pingPong];
        for(let i = 0; i < 2; i++){
            this._fft2hBindGroup2Used[i].push(...this._fft2hBindGroup2Free[i]);
            this._fft2hBindGroup2Free[i].length = 0;
            this._fft2hBindGroup4Used[i].push(...this._fft2hBindGroup4Free[i]);
            this._fft2hBindGroup4Free[i].length = 0;
            this._fft2vBindGroup2Used[i].push(...this._fft2vBindGroup2Free[i]);
            this._fft2vBindGroup2Free[i].length = 0;
            this._fft2vBindGroup4Used[i].push(...this._fft2vBindGroup4Free[i]);
            this._fft2vBindGroup4Free[i].length = 0;
        }
    }
    /** @internal */ postIfft2() {
        const device = getDevice();
        const instanceData = this.getInstanceData();
        const nearestRepeatSampler = fetchSampler('repeat_nearest_nomip');
        device.setProgram(FFTWaveGenerator._globals.programs.postfft2Program);
        device.setBindGroup(0, this._postfft2BindGroup);
        this._postfft2BindGroup.setValue('N2', this._params.resolution * this._params.resolution);
        if (this._useComputeShader) {
            this._postfft2BindGroup.setTexture('output', instanceData.dataTextures);
            this._postfft2BindGroup.setTexture('ifft', this._ifftTextures, nearestRepeatSampler);
            device.compute(this._params.resolution / THREAD_GROUP_SIZE, this._params.resolution / THREAD_GROUP_SIZE, 1);
        } else {
            const ifftTextures = this._ifftTextures;
            device.setFramebuffer(instanceData.postIfft2Framebuffer);
            this._postfft2BindGroup.setTexture('ifft0', ifftTextures[0], nearestRepeatSampler);
            this._postfft2BindGroup.setTexture('ifft1', ifftTextures[1], nearestRepeatSampler);
            this._postfft2BindGroup.setTexture('ifft2', ifftTextures[2], nearestRepeatSampler);
            this._postfft2BindGroup.setTexture('ifft3', ifftTextures[3], nearestRepeatSampler);
            this._postfft2BindGroup.setTexture('ifft4', ifftTextures[4], nearestRepeatSampler);
            this._postfft2BindGroup.setTexture('ifft5', ifftTextures[5], nearestRepeatSampler);
            if (device.type === 'webgl') {
                this._postfft2BindGroup.setValue('ifftTexSize', new Vector2(ifftTextures[0].width, ifftTextures[0].height));
            }
            FFTWaveGenerator._globals.quad.draw();
        }
    }
    /** @internal */ postIfft2TwoPass() {
        const device = getDevice();
        const instanceData = this.getInstanceData();
        const nearestRepeatSampler = fetchSampler('repeat_nearest_nomip');
        device.setFramebuffer(instanceData.postIfft2Framebuffer4);
        device.setProgram(FFTWaveGenerator._globals.programs.postfft2Program4);
        device.setBindGroup(0, this._postfft2BindGroup4);
        const ifftTextures = this._ifftTextures;
        this._postfft2BindGroup4.setValue('N2', this._params.resolution * this._params.resolution);
        this._postfft2BindGroup4.setTexture('ifft0', ifftTextures[0], nearestRepeatSampler);
        this._postfft2BindGroup4.setTexture('ifft1', ifftTextures[1], nearestRepeatSampler);
        this._postfft2BindGroup4.setTexture('ifft2', ifftTextures[2], nearestRepeatSampler);
        this._postfft2BindGroup4.setTexture('ifft3', ifftTextures[3], nearestRepeatSampler);
        if (device.type === 'webgl') {
            this._postfft2BindGroup4.setValue('ifftTexSize', new Vector2(ifftTextures[0].width, ifftTextures[0].height));
        }
        FFTWaveGenerator._globals.quad.draw();
        device.setFramebuffer(instanceData.postIfft2Framebuffer2);
        device.setProgram(FFTWaveGenerator._globals.programs.postfft2Program2);
        device.setBindGroup(0, this._postfft2BindGroup2);
        this._postfft2BindGroup2.setValue('N2', this._params.resolution * this._params.resolution);
        this._postfft2BindGroup2.setTexture('ifft4', ifftTextures[4], nearestRepeatSampler);
        this._postfft2BindGroup2.setTexture('ifft5', ifftTextures[5], nearestRepeatSampler);
        if (device.type === 'webgl') {
            this._postfft2BindGroup2.setValue('ifftTexSize', new Vector2(ifftTextures[0].width, ifftTextures[0].height));
        }
        FFTWaveGenerator._globals.quad.draw();
    }
    /** {@inheritDoc WaveGenerator.setupUniforms} */ setupUniforms(scope, uniformGroup) {
        const pb = scope.$builder;
        scope.sizes = pb.vec4().uniform(uniformGroup);
        scope.croppinesses = pb.vec4().uniform(uniformGroup);
        if (this._useComputeShader) {
            scope.dataTexture = pb.tex2DArray().uniform(uniformGroup);
        } else {
            scope.dx_hy_dz_dxdz0 = pb.tex2D().uniform(uniformGroup);
            scope.sx_sz_dxdx_dzdz0 = pb.tex2D().uniform(uniformGroup);
            scope.dx_hy_dz_dxdz1 = pb.tex2D().uniform(uniformGroup);
            scope.sx_sz_dxdx_dzdz1 = pb.tex2D().uniform(uniformGroup);
            scope.dx_hy_dz_dxdz2 = pb.tex2D().uniform(uniformGroup);
            scope.sx_sz_dxdx_dzdz2 = pb.tex2D().uniform(uniformGroup);
        }
        if (pb.shaderKind === 'fragment') {
            scope.foamParams = pb.vec2().uniform(uniformGroup);
        }
    }
    /** {@inheritDoc WaveGenerator.calcVertexPositionAndNormal} */ calcVertexPositionAndNormal(scope, inPos, outPos, outNormal) {
        const pb = scope.$builder;
        const that = this;
        pb.func('calcPositionAndNormal', [
            pb.vec3('inPos'),
            pb.vec3('outPos').out(),
            pb.vec3('outNormal').out()
        ], function() {
            this.$l.xz = this.inPos.xz;
            this.$l.uv0 = pb.div(this.xz, this.sizes.x);
            this.$l.uv1 = pb.div(this.xz, this.sizes.y);
            this.$l.uv2 = pb.div(this.xz, this.sizes.z);
            if (that._useComputeShader) {
                this.$l.a = pb.mul(pb.textureArraySampleLevel(this.dataTexture, this.uv0, 0, 0).rgb, pb.vec3(this.croppinesses.x, 1, this.croppinesses.x));
                this.$l.b = pb.mul(pb.textureArraySampleLevel(this.dataTexture, this.uv1, 2, 0).rgb, pb.vec3(this.croppinesses.y, 1, this.croppinesses.y));
                this.$l.c = pb.mul(pb.textureArraySampleLevel(this.dataTexture, this.uv2, 4, 0).rgb, pb.vec3(this.croppinesses.z, 1, this.croppinesses.z));
            } else {
                this.$l.a = pb.mul(pb.textureSampleLevel(this.dx_hy_dz_dxdz0, this.uv0, 0).rgb, pb.vec3(this.croppinesses.x, 1, this.croppinesses.x));
                this.$l.b = pb.mul(pb.textureSampleLevel(this.dx_hy_dz_dxdz1, this.uv1, 0).rgb, pb.vec3(this.croppinesses.y, 1, this.croppinesses.y));
                this.$l.c = pb.mul(pb.textureSampleLevel(this.dx_hy_dz_dxdz2, this.uv2, 0).rgb, pb.vec3(this.croppinesses.z, 1, this.croppinesses.z));
            }
            this.$l.displacement = pb.add(this.a, this.b, this.c);
            this.outPos = pb.add(this.inPos, this.displacement);
            this.outNormal = pb.vec3(0, 1, 0);
        });
        scope.calcPositionAndNormal(inPos, outPos, outNormal);
    }
    /** {@inheritDoc WaveGenerator.calcFragmentNormal} */ calcFragmentNormal(scope, xz, _vertexNormal) {
        const pb = scope.$builder;
        const that = this;
        pb.func('calcFragmentNormal', [
            pb.vec2('xz')
        ], function() {
            this.$l.uv0 = pb.div(this.xz, this.sizes.x);
            this.$l.uv1 = pb.div(this.xz, this.sizes.y);
            this.$l.uv2 = pb.div(this.xz, this.sizes.z);
            if (that._useComputeShader) {
                this.$l._sx_sz_dxdx_dzdz0 = pb.textureArraySampleLevel(this.dataTexture, this.uv0, 1, 0);
                this.$l._sx_sz_dxdx_dzdz1 = pb.textureArraySampleLevel(this.dataTexture, this.uv1, 3, 0);
                this.$l._sx_sz_dxdx_dzdz2 = pb.textureArraySampleLevel(this.dataTexture, this.uv2, 5, 0);
            } else {
                this.$l._sx_sz_dxdx_dzdz0 = pb.textureSampleLevel(this.sx_sz_dxdx_dzdz0, this.uv0, 0);
                this.$l._sx_sz_dxdx_dzdz1 = pb.textureSampleLevel(this.sx_sz_dxdx_dzdz1, this.uv1, 0);
                this.$l._sx_sz_dxdx_dzdz2 = pb.textureSampleLevel(this.sx_sz_dxdx_dzdz2, this.uv2, 0);
            }
            this.$l.sx = pb.add(this._sx_sz_dxdx_dzdz0.x, this._sx_sz_dxdx_dzdz1.x, this._sx_sz_dxdx_dzdz2.x);
            this.$l.sz = pb.add(this._sx_sz_dxdx_dzdz0.y, this._sx_sz_dxdx_dzdz1.y, this._sx_sz_dxdx_dzdz2.y);
            this.$l.dxdx_dzdz = pb.add(pb.mul(this._sx_sz_dxdx_dzdz0.zw, this.croppinesses.x), pb.mul(this._sx_sz_dxdx_dzdz1.zw, this.croppinesses.y), pb.mul(this._sx_sz_dxdx_dzdz2.zw, this.croppinesses.z));
            this.$l.slope = pb.vec2(pb.div(this.sx, pb.add(1.0, this.dxdx_dzdz.x)), pb.div(this.sz, pb.add(1.0, this.dxdx_dzdz.y)));
            this.$l.normal = pb.normalize(pb.vec3(pb.neg(this.slope.x), 1.0, pb.neg(this.slope.y)));
            this.$return(this.normal);
        });
        return scope.calcFragmentNormal(xz);
    }
    /** {@inheritDoc WaveGenerator.calcFragmentNormalAndFoam} */ calcFragmentNormalAndFoam(scope, xz) {
        const pb = scope.$builder;
        const that = this;
        pb.func('jacobian', [
            pb.float('dxdx'),
            pb.float('dxdz'),
            pb.float('dzdz')
        ], function() {
            this.$l.Jxx = pb.add(this.dxdx, 1);
            this.$l.Jxz = this.dxdz;
            this.$l.Jzz = pb.add(this.dzdz, 1);
            this.$return(pb.vec4(this.Jxx, this.Jxz, this.Jxz, this.Jzz));
        });
        pb.func('det', [
            pb.vec4('jacobian')
        ], function() {
            this.$return(pb.sub(pb.mul(this.jacobian.x, this.jacobian.w), pb.mul(this.jacobian.y, this.jacobian.z)));
        });
        pb.func('calcNormalAndFoam', [
            pb.vec2('xz')
        ], function() {
            this.$l.uv0 = pb.div(this.xz, this.sizes.x);
            this.$l.uv1 = pb.div(this.xz, this.sizes.y);
            this.$l.uv2 = pb.div(this.xz, this.sizes.z);
            if (that._useComputeShader) {
                this.$l._sx_sz_dxdx_dzdz0 = pb.textureArraySampleLevel(this.dataTexture, this.uv0, 1, 0);
                this.$l._sx_sz_dxdx_dzdz1 = pb.textureArraySampleLevel(this.dataTexture, this.uv1, 3, 0);
                this.$l._sx_sz_dxdx_dzdz2 = pb.textureArraySampleLevel(this.dataTexture, this.uv2, 5, 0);
            } else {
                this.$l._sx_sz_dxdx_dzdz0 = pb.textureSampleLevel(this.sx_sz_dxdx_dzdz0, this.uv0, 0);
                this.$l._sx_sz_dxdx_dzdz1 = pb.textureSampleLevel(this.sx_sz_dxdx_dzdz1, this.uv1, 0);
                this.$l._sx_sz_dxdx_dzdz2 = pb.textureSampleLevel(this.sx_sz_dxdx_dzdz2, this.uv2, 0);
            }
            this.$l.sx = pb.add(this._sx_sz_dxdx_dzdz0.x, this._sx_sz_dxdx_dzdz1.x, this._sx_sz_dxdx_dzdz2.x);
            this.$l.sz = pb.add(this._sx_sz_dxdx_dzdz0.y, this._sx_sz_dxdx_dzdz1.y, this._sx_sz_dxdx_dzdz2.y);
            this.$l.dxdx_dzdz = pb.add(pb.mul(this._sx_sz_dxdx_dzdz0.zw, this.croppinesses.x), pb.mul(this._sx_sz_dxdx_dzdz1.zw, this.croppinesses.y), pb.mul(this._sx_sz_dxdx_dzdz2.zw, this.croppinesses.z));
            this.$l.slope = pb.vec2(pb.div(this.sx, pb.add(1.0, this.dxdx_dzdz.x)), pb.div(this.sz, pb.add(1.0, this.dxdx_dzdz.y)));
            this.$l.normal = pb.normalize(pb.vec3(pb.neg(this.slope.x), 1.0, pb.neg(this.slope.y)));
            // foam
            this.$l.dxdx_dzdz0 = this._sx_sz_dxdx_dzdz0.zw;
            this.$l.dxdx_dzdz1 = this._sx_sz_dxdx_dzdz1.zw;
            this.$l.dxdx_dzdz2 = this._sx_sz_dxdx_dzdz2.zw;
            if (that._useComputeShader) {
                this.$l.dxdz0 = pb.textureArraySampleLevel(this.dataTexture, this.uv0, 0, 0).w;
                this.$l.dxdz1 = pb.textureArraySampleLevel(this.dataTexture, this.uv1, 2, 0).w;
                this.$l.dxdz2 = pb.textureArraySampleLevel(this.dataTexture, this.uv2, 4, 0).w;
            } else {
                this.$l.dxdz0 = pb.textureSampleLevel(this.dx_hy_dz_dxdz0, this.uv0, 0).w;
                this.$l.dxdz1 = pb.textureSampleLevel(this.dx_hy_dz_dxdz1, this.uv1, 0).w;
                this.$l.dxdz2 = pb.textureSampleLevel(this.dx_hy_dz_dxdz2, this.uv2, 0).w;
            }
            this.$l.dxdz = pb.add(pb.mul(this.dxdz0, this.croppinesses.x), pb.mul(this.dxdz1, this.croppinesses.y), pb.mul(this.dxdz2, this.croppinesses.z));
            this.$l.val = this.det(this.jacobian(this.dxdx_dzdz.x, this.dxdz, this.dxdx_dzdz.y));
            this.$l.foam = pb.abs(pb.pow(pb.neg(pb.min(0, pb.sub(this.val, this.foamParams.x))), this.foamParams.y));
            this.$return(pb.vec4(this.normal, this.foam));
        });
        return scope.calcNormalAndFoam(xz);
    }
    /** {@inheritDoc WaveGenerator.isOk} */ isOk() {
        return this._renderMode !== RENDER_NONE;
    }
    /** {@inheritDoc WaveGenerator.applyWaterBindGroup} */ applyWaterBindGroup(bindGroup) {
        const instanceData = this.getInstanceData();
        const linearRepeatSampler = fetchSampler('repeat_linear_nomip');
        if (this._useComputeShader) {
            bindGroup.setTexture('dataTexture', instanceData.dataTextures, linearRepeatSampler);
        } else {
            const dataTextures = instanceData.dataTextures;
            bindGroup.setTexture('dx_hy_dz_dxdz0', dataTextures[0], linearRepeatSampler);
            bindGroup.setTexture('sx_sz_dxdx_dzdz0', dataTextures[1], linearRepeatSampler);
            bindGroup.setTexture('dx_hy_dz_dxdz1', dataTextures[2], linearRepeatSampler);
            bindGroup.setTexture('sx_sz_dxdx_dzdz1', dataTextures[3], linearRepeatSampler);
            bindGroup.setTexture('dx_hy_dz_dxdz2', dataTextures[4], linearRepeatSampler);
            bindGroup.setTexture('sx_sz_dxdx_dzdz2', dataTextures[5], linearRepeatSampler);
        }
        bindGroup.setValue('foamParams', this._params.foamParams);
        bindGroup.setValue('sizes', this._sizes);
        bindGroup.setValue('croppinesses', this._croppinesses);
    }
    /** {@inheritDoc WaveGenerator.calcClipmapTileAABB} */ calcClipmapTileAABB(minX, maxX, minZ, maxZ, y, outAABB) {
        const disturb = Math.max(Math.abs(this.wind.x), Math.abs(this.wind.y), 2);
        outAABB.minPoint.setXYZ(minX - 8 * disturb, y - 8 * disturb, minZ - 8 * disturb);
        outAABB.maxPoint.setXYZ(maxX + 8 * disturb, y + 8 * disturb, maxZ + 8 * disturb);
    }
    /** {@inheritDoc WaveGenerator.getHash} */ getHash() {
        return 'FFTWaveGenerator';
    }
    onDispose() {
        super.onDispose();
        this.disposeInstanceData();
    }
}

export { FFTWaveGenerator };
//# sourceMappingURL=fft_wavegenerator.js.map
