import { SerializationHelper } from "../../Misc/decorators.serialization.js";
import { VertexBuffer } from "../../Buffers/buffer.js";
import { MaterialDefines } from "../../Materials/materialDefines.js";
import { PushMaterial } from "../../Materials/pushMaterial.js";
import { RegisterClass } from "../../Misc/typeStore.js";
import { AddClipPlaneUniforms, BindClipPlane } from "../clipPlaneMaterialHelper.js";
import { Camera } from "../../Cameras/camera.js";
import { ShadowDepthWrapper } from "../../Materials/shadowDepthWrapper.js";
import { ShaderMaterial } from "../../Materials/shaderMaterial.js";
import "../../Shaders/gaussianSplatting.fragment.js";
import "../../Shaders/gaussianSplatting.vertex.js";
import "../../ShadersWGSL/gaussianSplatting.fragment.js";
import "../../ShadersWGSL/gaussianSplatting.vertex.js";
import "../../Shaders/gaussianSplattingDepth.fragment.js";
import "../../Shaders/gaussianSplattingDepth.vertex.js";
import "../../ShadersWGSL/gaussianSplattingDepth.fragment.js";
import "../../ShadersWGSL/gaussianSplattingDepth.vertex.js";
import { BindFogParameters, BindLogDepth, PrepareAttributesForInstances, PrepareDefinesForAttributes, PrepareDefinesForFrameBoundValues, PrepareDefinesForMisc, PrepareUniformsAndSamplersList, } from "../materialHelper.functions.js";
/**
 * @internal
 */
class GaussianSplattingMaterialDefines extends MaterialDefines {
    /**
     * Constructor of the defines.
     */
    constructor() {
        super();
        this.FOG = false;
        this.THIN_INSTANCES = true;
        this.LOGARITHMICDEPTH = false;
        this.CLIPPLANE = false;
        this.CLIPPLANE2 = false;
        this.CLIPPLANE3 = false;
        this.CLIPPLANE4 = false;
        this.CLIPPLANE5 = false;
        this.CLIPPLANE6 = false;
        this.SH_DEGREE = 0;
        this.COMPENSATION = false;
        this.rebuild();
    }
}
/**
 * GaussianSplattingMaterial material used to render Gaussian Splatting
 * @experimental
 */
export class GaussianSplattingMaterial extends PushMaterial {
    /**
     * Instantiates a Gaussian Splatting Material in the given scene
     * @param name The friendly name of the material
     * @param scene The scene to add the material to
     */
    constructor(name, scene) {
        super(name, scene);
        /**
         * Point spread function (default 0.3). Can be overriden per GS material, otherwise, using default static `KernelSize` value
         */
        this.kernelSize = GaussianSplattingMaterial.KernelSize;
        this._compensation = GaussianSplattingMaterial.Compensation;
        // set to true when material defines are dirty
        this._isDirty = false;
        this._sourceMesh = null;
        this.backFaceCulling = false;
        this.shadowDepthWrapper = GaussianSplattingMaterial._MakeGaussianSplattingShadowDepthWrapper(scene, this.shaderLanguage);
    }
    /**
     * Set compensation default value is `GaussianSplattingMaterial.Compensation`
     */
    set compensation(value) {
        this._isDirty = this._isDirty != value;
        this._compensation = value;
    }
    /**
     * Get compensation
     */
    get compensation() {
        return this._compensation;
    }
    /**
     * Gets a boolean indicating that current material needs to register RTT
     */
    get hasRenderTargetTextures() {
        return false;
    }
    /**
     * Specifies whether or not this material should be rendered in alpha test mode.
     * @returns false
     */
    needAlphaTesting() {
        return false;
    }
    /**
     * Specifies whether or not this material should be rendered in alpha blend mode.
     * @returns true
     */
    needAlphaBlending() {
        return true;
    }
    /**
     * Checks whether the material is ready to be rendered for a given mesh.
     * @param mesh The mesh to render
     * @param subMesh The submesh to check against
     * @returns true if all the dependencies are ready (Textures, Effects...)
     */
    isReadyForSubMesh(mesh, subMesh) {
        const useInstances = true;
        const drawWrapper = subMesh._drawWrapper;
        let defines = subMesh.materialDefines;
        if (defines && this._isDirty) {
            defines.markAsUnprocessed();
        }
        if (drawWrapper.effect && this.isFrozen) {
            if (drawWrapper._wasPreviouslyReady && drawWrapper._wasPreviouslyUsingInstances === useInstances) {
                return true;
            }
        }
        if (!subMesh.materialDefines) {
            defines = subMesh.materialDefines = new GaussianSplattingMaterialDefines();
        }
        const scene = this.getScene();
        if (this._isReadyForSubMesh(subMesh)) {
            return true;
        }
        if (!this._sourceMesh) {
            return false;
        }
        const engine = scene.getEngine();
        const gsMesh = this._sourceMesh;
        // Misc.
        PrepareDefinesForMisc(mesh, scene, this._useLogarithmicDepth, this.pointsCloud, this.fogEnabled, false, defines, undefined, undefined, undefined, this._isVertexOutputInvariant);
        // Values that need to be evaluated on every frame
        PrepareDefinesForFrameBoundValues(scene, engine, this, defines, useInstances, null, true);
        // Attribs
        PrepareDefinesForAttributes(mesh, defines, false, false);
        // SH is disabled for webGL1
        if (engine.version > 1 || engine.isWebGPU) {
            defines["SH_DEGREE"] = gsMesh.shDegree;
        }
        // Compensation
        const splatMaterial = gsMesh.material;
        defines["COMPENSATION"] = splatMaterial && splatMaterial.compensation ? splatMaterial.compensation : GaussianSplattingMaterial.Compensation;
        // Get correct effect
        if (defines.isDirty) {
            defines.markAsProcessed();
            scene.resetCachedMaterial();
            //Attributes
            PrepareAttributesForInstances(GaussianSplattingMaterial._Attribs, defines);
            PrepareUniformsAndSamplersList({
                uniformsNames: GaussianSplattingMaterial._Uniforms,
                uniformBuffersNames: GaussianSplattingMaterial._UniformBuffers,
                samplers: GaussianSplattingMaterial._Samplers,
                defines: defines,
            });
            AddClipPlaneUniforms(GaussianSplattingMaterial._Uniforms);
            const join = defines.toString();
            const effect = scene.getEngine().createEffect("gaussianSplatting", {
                attributes: GaussianSplattingMaterial._Attribs,
                uniformsNames: GaussianSplattingMaterial._Uniforms,
                uniformBuffersNames: GaussianSplattingMaterial._UniformBuffers,
                samplers: GaussianSplattingMaterial._Samplers,
                defines: join,
                onCompiled: this.onCompiled,
                onError: this.onError,
                indexParameters: {},
                shaderLanguage: this._shaderLanguage,
                extraInitializationsAsync: async () => {
                    if (this._shaderLanguage === 1 /* ShaderLanguage.WGSL */) {
                        await Promise.all([import("../../ShadersWGSL/gaussianSplatting.fragment.js"), import("../../ShadersWGSL/gaussianSplatting.vertex.js")]);
                    }
                    else {
                        await Promise.all([import("../../Shaders/gaussianSplatting.fragment.js"), import("../../Shaders/gaussianSplatting.vertex.js")]);
                    }
                },
            }, engine);
            subMesh.setEffect(effect, defines, this._materialContext);
        }
        if (!subMesh.effect || !subMesh.effect.isReady()) {
            return false;
        }
        defines._renderId = scene.getRenderId();
        drawWrapper._wasPreviouslyReady = true;
        drawWrapper._wasPreviouslyUsingInstances = useInstances;
        this._isDirty = false;
        return true;
    }
    /**
     * GaussianSplattingMaterial belongs to a single mesh
     * @param mesh mesh this material belongs to
     */
    setSourceMesh(mesh) {
        this._sourceMesh = mesh;
    }
    /**
     * Bind material effect for a specific Gaussian Splatting mesh
     * @param mesh Gaussian splatting mesh
     * @param effect Splatting material or node material
     * @param scene scene that contains mesh and camera used for rendering
     */
    static BindEffect(mesh, effect, scene) {
        const engine = scene.getEngine();
        const camera = scene.activeCamera;
        const renderWidth = engine.getRenderWidth() * camera.viewport.width;
        const renderHeight = engine.getRenderHeight() * camera.viewport.height;
        const gsMaterial = mesh.material;
        if (!gsMaterial._sourceMesh) {
            return;
        }
        const gsMesh = gsMaterial._sourceMesh;
        // check if rigcamera, get number of rigs
        const numberOfRigs = camera?.rigParent?.rigCameras.length || 1;
        effect.setFloat2("invViewport", 1 / (renderWidth / numberOfRigs), 1 / renderHeight);
        let focal = 1000;
        if (camera) {
            /*
            more explicit version:
            const t = camera.getProjectionMatrix().m[5];
            const FovY = Math.atan(1.0 / t) * 2.0;
            focal = renderHeight / 2.0 / Math.tan(FovY / 2.0);
            Using a shorter version here to not have tan(atan) and 2.0 factor
            */
            const t = camera.getProjectionMatrix().m[5];
            if (camera.fovMode == Camera.FOVMODE_VERTICAL_FIXED) {
                focal = (renderHeight * t) / 2.0;
            }
            else {
                focal = (renderWidth * t) / 2.0;
            }
        }
        effect.setFloat2("focal", focal, focal);
        effect.setVector3("viewDirectionFactor", gsMesh.viewDirectionFactor);
        effect.setFloat("kernelSize", gsMaterial && gsMaterial.kernelSize ? gsMaterial.kernelSize : GaussianSplattingMaterial.KernelSize);
        effect.setFloat("alpha", gsMaterial.alpha);
        scene.bindEyePosition(effect, "eyePosition", true);
        if (gsMesh.covariancesATexture) {
            const textureSize = gsMesh.covariancesATexture.getSize();
            effect.setFloat2("dataTextureSize", textureSize.width, textureSize.height);
            effect.setTexture("covariancesATexture", gsMesh.covariancesATexture);
            effect.setTexture("covariancesBTexture", gsMesh.covariancesBTexture);
            effect.setTexture("centersTexture", gsMesh.centersTexture);
            effect.setTexture("colorsTexture", gsMesh.colorsTexture);
            if (gsMesh.shTextures) {
                for (let i = 0; i < gsMesh.shTextures?.length; i++) {
                    effect.setTexture(`shTexture${i}`, gsMesh.shTextures[i]);
                }
            }
        }
    }
    /**
     * Binds the submesh to this material by preparing the effect and shader to draw
     * @param world defines the world transformation matrix
     * @param mesh defines the mesh containing the submesh
     * @param subMesh defines the submesh to bind the material to
     */
    bindForSubMesh(world, mesh, subMesh) {
        const scene = this.getScene();
        const defines = subMesh.materialDefines;
        if (!defines) {
            return;
        }
        const effect = subMesh.effect;
        if (!effect) {
            return;
        }
        this._activeEffect = effect;
        // Matrices Mesh.
        mesh.getMeshUniformBuffer().bindToEffect(effect, "Mesh");
        mesh.transferToEffect(world);
        // Bind data
        const mustRebind = this._mustRebind(scene, effect, subMesh, mesh.visibility);
        if (mustRebind) {
            this.bindView(effect);
            this.bindViewProjection(effect);
            GaussianSplattingMaterial.BindEffect(mesh, this._activeEffect, scene);
            // Clip plane
            BindClipPlane(effect, this, scene);
        }
        else if (scene.getEngine()._features.needToAlwaysBindUniformBuffers) {
            this._needToBindSceneUbo = true;
        }
        // Fog
        BindFogParameters(scene, mesh, effect);
        // Log. depth
        if (this.useLogarithmicDepth) {
            BindLogDepth(defines, effect, scene);
        }
        this._afterBind(mesh, this._activeEffect, subMesh);
    }
    static _MakeGaussianSplattingShadowDepthWrapper(scene, shaderLanguage) {
        const shaderMaterial = new ShaderMaterial("gaussianSplattingDepth", scene, {
            vertex: "gaussianSplattingDepth",
            fragment: "gaussianSplattingDepth",
        }, {
            attributes: GaussianSplattingMaterial._Attribs,
            uniforms: GaussianSplattingMaterial._Uniforms,
            samplers: GaussianSplattingMaterial._Samplers,
            uniformBuffers: GaussianSplattingMaterial._UniformBuffers,
            shaderLanguage: shaderLanguage,
        });
        const shadowDepthWrapper = new ShadowDepthWrapper(shaderMaterial, scene, {
            standalone: true,
        });
        shaderMaterial.onBindObservable.add((mesh) => {
            const effect = shaderMaterial.getEffect();
            const gsMaterial = mesh.material;
            const gsMesh = mesh;
            mesh.getMeshUniformBuffer().bindToEffect(effect, "Mesh");
            shaderMaterial.bindView(effect);
            shaderMaterial.bindViewProjection(effect);
            const shadowmapWidth = scene.getEngine().getRenderWidth();
            const shadowmapHeight = scene.getEngine().getRenderHeight();
            effect.setFloat2("invViewport", 1 / shadowmapWidth, 1 / shadowmapHeight);
            const projection = scene.getProjectionMatrix();
            const t = projection.m[5];
            const focal = (shadowmapWidth * t) / 2.0;
            effect.setFloat2("focal", focal, focal);
            effect.setFloat("kernelSize", gsMaterial && gsMaterial.kernelSize ? gsMaterial.kernelSize : GaussianSplattingMaterial.KernelSize);
            if (gsMesh.covariancesATexture) {
                const textureSize = gsMesh.covariancesATexture.getSize();
                effect.setFloat2("dataTextureSize", textureSize.width, textureSize.height);
                effect.setTexture("covariancesATexture", gsMesh.covariancesATexture);
                effect.setTexture("covariancesBTexture", gsMesh.covariancesBTexture);
                effect.setTexture("centersTexture", gsMesh.centersTexture);
                effect.setTexture("colorsTexture", gsMesh.colorsTexture);
            }
        });
        return shadowDepthWrapper;
    }
    /**
     * Clones the material.
     * @param name The cloned name.
     * @returns The cloned material.
     */
    clone(name) {
        return SerializationHelper.Clone(() => new GaussianSplattingMaterial(name, this.getScene()), this);
    }
    /**
     * Serializes the current material to its JSON representation.
     * @returns The JSON representation.
     */
    serialize() {
        const serializationObject = super.serialize();
        serializationObject.customType = "BABYLON.GaussianSplattingMaterial";
        return serializationObject;
    }
    /**
     * Gets the class name of the material
     * @returns "GaussianSplattingMaterial"
     */
    getClassName() {
        return "GaussianSplattingMaterial";
    }
    /**
     * Parse a JSON input to create back a Gaussian Splatting material.
     * @param source The JSON data to parse
     * @param scene The scene to create the parsed material in
     * @param rootUrl The root url of the assets the material depends upon
     * @returns the instantiated GaussianSplattingMaterial.
     */
    static Parse(source, scene, rootUrl) {
        return SerializationHelper.Parse(() => new GaussianSplattingMaterial(source.name, scene), source, scene, rootUrl);
    }
}
/**
 * Point spread function (default 0.3). Can be overriden per GS material
 */
GaussianSplattingMaterial.KernelSize = 0.3;
/**
 * Compensation
 */
GaussianSplattingMaterial.Compensation = false;
GaussianSplattingMaterial._Attribs = [VertexBuffer.PositionKind, "splatIndex0", "splatIndex1", "splatIndex2", "splatIndex3"];
GaussianSplattingMaterial._Samplers = ["covariancesATexture", "covariancesBTexture", "centersTexture", "colorsTexture", "shTexture0", "shTexture1", "shTexture2"];
GaussianSplattingMaterial._UniformBuffers = ["Scene", "Mesh"];
GaussianSplattingMaterial._Uniforms = [
    "world",
    "view",
    "projection",
    "vFogInfos",
    "vFogColor",
    "logarithmicDepthConstant",
    "invViewport",
    "dataTextureSize",
    "focal",
    "eyePosition",
    "kernelSize",
    "viewDirectionFactor",
    "alpha",
];
RegisterClass("BABYLON.GaussianSplattingMaterial", GaussianSplattingMaterial);
//# sourceMappingURL=gaussianSplattingMaterial.js.map