import { Mesh, ShaderMaterial, Vector4 } from '../babylonjs-import';
import { ClipPlane } from '../ClipPlane';
import { REQUIRES_WORLD_FRAGMENT, DITHER_DISTANCE_FADE, IGNORE_CLIP_PLANES_PROPERTY_DEFINE } from '../ShaderConstants';
class ClipPlanesStrings {
    MAX_CLIP_PLANES = 6;
    CLIP_PLANES = [];
    CLIP_PLANES_COLORS = [];
    CLIP_PLANES_FADE_SIZES = [];
    CLIP_PLANE_UNIFORMS = '';
    CLIP_PLANE_DISCARDING = '';
    CLIP_PLANE_COLOR_BLENDING = '';
    CLIP_PLANE_DEFINES = [];
    constructor() {
        for (let i = 1; i <= this.MAX_CLIP_PLANES; i++) {
            this.CLIP_PLANE_DEFINES[i - 1] = `CLIPPLANE${i}`;
            this.CLIP_PLANES.push('clipPlane' + i);
            this.CLIP_PLANES_COLORS.push('clipPlaneColor' + i);
            this.CLIP_PLANES_FADE_SIZES.push('clipPlaneFadeSize' + i);
            this.CLIP_PLANE_UNIFORMS += `#ifdef ${this.CLIP_PLANE_DEFINES[i - 1]}
                uniform vec4 clipPlane${i};
                uniform float clipPlaneFadeSize${i};
                #ifdef ${COLOR_BLENDING_ENABLED}
                    uniform vec3 clipPlaneColor${i};
                #endif
            #endif\n`;
            this.CLIP_PLANE_DISCARDING += `#ifdef ${this.CLIP_PLANE_DEFINES[i - 1]}
            float fClipDistance${i} = dot(vec4(worldSpaceVertex, 1.0), clipPlane${i});
            if (fClipDistance${i} > clipPlaneFadeSize${i} && clipPlaneFadeSize${i} != -1.0) {
                #ifdef ${IGNORE_CLIP_PLANES_PROPERTY_DEFINE}
                    /// Used by the depth shader to distinguish between regular Babylon objects and IFC meshes, and  regular objects obviously have no property pixel
                    #ifndef isBabylonMesh
                        if (!clipPlaneIgnore) {
                            discard;
                        }
                    #endif
                #else
                    discard;
                #endif
            }
            #endif\n`;
            this.CLIP_PLANE_COLOR_BLENDING += `#ifdef ${this.CLIP_PLANE_DEFINES[i - 1]}
            if (fClipDistance${i} > 0.0 && clipPlaneFadeSize${i} != -1.0) {
                float fade = 1.0 - ((clipPlaneFadeSize${i} - fClipDistance${i}) / clipPlaneFadeSize${i});
                #ifdef ${IGNORE_CLIP_PLANES_PROPERTY_DEFINE}
                    if (clipPlaneIgnore) {
                        fade = 0.0;
                    }
                #endif
                finalDiffuse = mix(finalDiffuse, clipPlaneColor${i}, fade);
            }
            #endif\n`;
        }
    }
}
export const SIZE = 'size';
export const DIFFUSE_SAMPLER = 'diffuseSampler';
export const COLOR_BLENDING_ENABLED = 'colorBlendingEnabled';
export const clipPlanesStrings = new ClipPlanesStrings();
export class CullingMaterial extends ShaderMaterial {
    _renderingInfo;
    _writeColorBlending;
    _excludeIgnoreDepthWriteCuttingPlanes;
    isDirty = true;
    _ignoreClipPlanesBitActive = false;
    _clipPlanes;
    constructor(name, scene, 
    /* Could be cube info or some other delimitation in the future, right now is global */
    _renderingInfo, options, _writeColorBlending, _excludeIgnoreDepthWriteCuttingPlanes) {
        super(name, scene, {
        // Intentionally blank
        }, options);
        this._renderingInfo = _renderingInfo;
        this._writeColorBlending = _writeColorBlending;
        this._excludeIgnoreDepthWriteCuttingPlanes = _excludeIgnoreDepthWriteCuttingPlanes;
        // Micro optimization. Iterating clipplanes with a iterator each frame actually shows up in the profiler so
        // we cache the array here and reuse it. We know that the clip plane objects themselves will not be replaced.
        this._clipPlanes = [...ClipPlane.values(this.visualSettings.ifc.clipPlanes)];
        const clipPlanes = ClipPlane.values(this.visualSettings.ifc.clipPlanes);
        for (const clipPlane of clipPlanes) {
            clipPlane.onPropertyChanged.add((propertyChange) => {
                // The only time we need to recompile the shader is when the enabled feature is changed (because depending on if it's enabled or not the instructions for
                // it in the shader is removed)
                if (propertyChange.property === 'enabled') {
                    this.isDirty = true;
                }
            });
        }
        this.onBindObservable.add((mesh) => {
            this.bindFunction(mesh);
        });
    }
    isReadyForSubMesh(mesh, subMesh, useInstances) {
        if (mesh instanceof Mesh) {
            if (mesh.twinfinity.ifc) {
                const shouldHaveIgnoreClipPlaneBit = this._renderingInfo.state.ignoreClipPlanesCount > 0;
                if (shouldHaveIgnoreClipPlaneBit !== this._ignoreClipPlanesBitActive) {
                    this.isDirty = true;
                    this._ignoreClipPlanesBitActive = shouldHaveIgnoreClipPlaneBit;
                }
            }
        }
        if (this.isDirty) {
            this.isDirty = false;
            this.updateShader();
        }
        return super.isReadyForSubMesh(mesh, subMesh, useInstances);
    }
    get visualSettings() {
        return this.getScene().twinfinity.viewer.visualSettings;
    }
    getUniformsAndDefinesAndSamplers() {
        const defines = [];
        const uniforms = [];
        const clipPlanes = ClipPlane.values(this.visualSettings.ifc.clipPlanes);
        let isAnyClipPlaneActive = false;
        let i = 0;
        for (const clipPlane of clipPlanes) {
            if (clipPlane.enabled && !(this._excludeIgnoreDepthWriteCuttingPlanes && clipPlane.ignoreDepthWrite)) {
                isAnyClipPlaneActive = true;
                defines.push(clipPlanesStrings.CLIP_PLANE_DEFINES[i]);
                uniforms.push(clipPlanesStrings.CLIP_PLANES[i]);
                uniforms.push(clipPlanesStrings.CLIP_PLANES_COLORS[i]);
                uniforms.push(clipPlanesStrings.CLIP_PLANES_FADE_SIZES[i]);
            }
            i++;
        }
        if (this._writeColorBlending) {
            defines.push(COLOR_BLENDING_ENABLED);
        }
        if (isAnyClipPlaneActive) {
            defines.push(REQUIRES_WORLD_FRAGMENT);
        }
        if (this._ignoreClipPlanesBitActive) {
            defines.push(IGNORE_CLIP_PLANES_PROPERTY_DEFINE);
        }
        uniforms.push(DITHER_DISTANCE_FADE);
        return {
            uniforms: uniforms,
            defines: defines,
            samplers: []
        };
    }
    bindFunction(mesh) {
        const effect = this.getEffect();
        let i = 0;
        for (const clipPlane of this._clipPlanes) {
            if (clipPlane.enabled && !(this._excludeIgnoreDepthWriteCuttingPlanes && clipPlane.ignoreDepthWrite)) {
                const plane = clipPlane.plane;
                const x = plane.normal.x;
                const y = plane.normal.y;
                const z = plane.normal.z;
                const d = plane.d;
                const fadeSize = clipPlane.fadeSize;
                effect.setFloat(clipPlanesStrings.CLIP_PLANES_FADE_SIZES[i], fadeSize);
                effect.setVector4(clipPlanesStrings.CLIP_PLANES[i], new Vector4(x, y, z, d));
                if (this._writeColorBlending) {
                    const color = clipPlane.color;
                    effect.setColor3(clipPlanesStrings.CLIP_PLANES_COLORS[i], color);
                }
            }
            i++;
        }
        effect.setTexture(DIFFUSE_SAMPLER, this.diffuseTexture.getTexture(mesh.getScene()));
    }
    updateShader() {
        const isFrozen = this.isFrozen;
        try {
            if (isFrozen) {
                this.unfreeze();
            }
            const uniformsAndDefines = this.getUniformsAndDefinesAndSamplers();
            const uniforms = uniformsAndDefines.uniforms;
            const vertexSource = this.getVertexSource();
            const fragmentSource = this.getFragmentSource();
            this.shaderPath = {
                vertexSource,
                fragmentSource
            };
            this.options.uniforms = uniforms;
            this.options.defines = uniformsAndDefines.defines;
            this.options.samplers = uniformsAndDefines.samplers;
        }
        finally {
            if (isFrozen) {
                this.freeze();
            }
        }
    }
}
//# sourceMappingURL=CullingMaterial.js.map