import { Constants } from "@babylonjs/core/Engines/constants";
import { MaterialDefines } from "@babylonjs/core/Materials/materialDefines";
import { MaterialPluginBase } from "@babylonjs/core/Materials/materialPluginBase";
import { Color4 } from "@babylonjs/core/Maths/math.color";
import { MmdBufferKind } from "./mmdBufferKind";
import { sdefDeclaration } from "./Shader/sdefDeclaration";
import { sdefVertex } from "./Shader/sdefVertex";
/**
 * for convert MMD material to Babylon material
 *
 * use StandardMaterial as base class
 *
 * propertiy mapping:
 *
 * - diffuse[0..2]: diffuseColor
 * - specular: specularColor
 * - ambient: ambientColor
 * - diffuse[3](opaque): alpha
 * - shininess(reflect): specularPower
 * - isDoubleSided: backFaceCulling
 * - enabledToonEdge: (custom implementation)
 * - edgeColor: (custom implementation)
 * - edgeSize: (custom implementation)
 * - texture: diffuseTexture
 * - sphereTexture: (custom implementation)
 * - toonTexture: (custom implementation)
 *
 * using options:
 *
 * useAlphaFromDiffuseTexture
 *
 * additinal implementation:
 *
 * spherical deformation
 */
// https://cyos.babylonjs.com/
// spherical environment mapping reference:
// https://learn.microsoft.com/en-us/windows/win32/direct3d9/spherical-environment-mapping
export class MmdPluginMererialDefines extends MaterialDefines {
    /* eslint-disable @typescript-eslint/naming-convention */
    SPHERE_TEXTURE = false;
    SPHERE_TEXTURE_BLEND_MODE_MULTIPLY = false;
    SPHERE_TEXTURE_BLEND_MODE_ADD = false;
    TOON_TEXTURE = false;
    IGNORE_DIFFUSE_WHEN_TOON_TEXTURE_DISABLED = false;
    APPLY_AMBIENT_COLOR_TO_DIFFUSE = false;
    TEXTURE_COLOR = false;
    SPHERE_TEXTURE_COLOR = false;
    TOON_TEXTURE_COLOR = false;
    SDEF = false;
}
export var MmdPluginMaterialSphereTextureBlendMode;
(function (MmdPluginMaterialSphereTextureBlendMode) {
    MmdPluginMaterialSphereTextureBlendMode[MmdPluginMaterialSphereTextureBlendMode["Multiply"] = 1] = "Multiply";
    MmdPluginMaterialSphereTextureBlendMode[MmdPluginMaterialSphereTextureBlendMode["Add"] = 2] = "Add";
    MmdPluginMaterialSphereTextureBlendMode[MmdPluginMaterialSphereTextureBlendMode["SubTexture"] = 3] = "SubTexture";
})(MmdPluginMaterialSphereTextureBlendMode || (MmdPluginMaterialSphereTextureBlendMode = {}));
export class MmdPluginMaterial extends MaterialPluginBase {
    _sphereTexture = null;
    _sphereTextureBlendMode = MmdPluginMaterialSphereTextureBlendMode.Add;
    _toonTexture = null;
    _ignoreDiffuseWhenToonTextureIsNull = false;
    textureMultiplicativeColor = new Color4(1, 1, 1, 1);
    textureAdditiveColor = new Color4(0, 0, 0, 0);
    sphereTextureMultiplicativeColor = new Color4(1, 1, 1, 1);
    sphereTextureAdditiveColor = new Color4(0, 0, 0, 0);
    toonTextureMultiplicativeColor = new Color4(1, 1, 1, 1);
    toonTextureAdditiveColor = new Color4(0, 0, 0, 0);
    _applyAmbientColorToDiffuse = true;
    _useTextureColor = false;
    _useSphereTextureColor = false;
    _useToonTextureColor = false;
    _isEnabled = false;
    get isEnabled() {
        return this._isEnabled;
    }
    set isEnabled(value) {
        if (this._isEnabled === value)
            return;
        this._isEnabled = value;
        this.markAllDefinesAsDirty();
        this._enable(value);
    }
    get sphereTexture() {
        return this._sphereTexture;
    }
    set sphereTexture(value) {
        if (this._sphereTexture === value)
            return;
        this._sphereTexture = value;
        this.markAllSubMeshesAsTexturesDirty();
    }
    get sphereTextureBlendMode() {
        return this._sphereTextureBlendMode;
    }
    set sphereTextureBlendMode(value) {
        if (this._sphereTextureBlendMode === value)
            return;
        this._sphereTextureBlendMode = value;
        this.markAllDefinesAsDirty();
    }
    get toonTexture() {
        return this._toonTexture;
    }
    set toonTexture(value) {
        if (this._toonTexture === value)
            return;
        this._toonTexture = value;
        this.markAllSubMeshesAsTexturesDirty();
    }
    get ignoreDiffuseWhenToonTextureIsNull() {
        return this._ignoreDiffuseWhenToonTextureIsNull;
    }
    set ignoreDiffuseWhenToonTextureIsNull(value) {
        if (this._ignoreDiffuseWhenToonTextureIsNull === value)
            return;
        this._ignoreDiffuseWhenToonTextureIsNull = value;
        this.markAllDefinesAsDirty();
    }
    get applyAmbientColorToDiffuse() {
        return this._applyAmbientColorToDiffuse;
    }
    set applyAmbientColorToDiffuse(value) {
        if (this._applyAmbientColorToDiffuse === value)
            return;
        this._applyAmbientColorToDiffuse = value;
        this.markAllDefinesAsDirty();
    }
    get useTextureColor() {
        return this._useTextureColor;
    }
    set useTextureColor(value) {
        if (this._useTextureColor === value)
            return;
        this._useTextureColor = value;
        this.markAllDefinesAsDirty();
    }
    get useSphereTextureColor() {
        return this._useSphereTextureColor;
    }
    set useSphereTextureColor(value) {
        if (this._useSphereTextureColor === value)
            return;
        this._useSphereTextureColor = value;
        this.markAllDefinesAsDirty();
    }
    get useToonTextureColor() {
        return this._useToonTextureColor;
    }
    set useToonTextureColor(value) {
        if (this._useToonTextureColor === value)
            return;
        this._useToonTextureColor = value;
        this.markAllDefinesAsDirty();
    }
    _internalMarkAllSubMeshesAsTexturesDirty;
    markAllSubMeshesAsTexturesDirty() {
        this._enable(this._isEnabled);
        this._internalMarkAllSubMeshesAsTexturesDirty();
    }
    constructor(material, addtoPluginList = true) {
        super(material, "MmdMaterial", 100, new MmdPluginMererialDefines(), addtoPluginList);
        this._internalMarkAllSubMeshesAsTexturesDirty = material._dirtyCallbacks[Constants.MATERIAL_TextureDirtyFlag];
    }
    isReadyForSubMesh(defines, scene) {
        if (!this._isEnabled)
            return true;
        if (defines._areTexturesDirty && scene.texturesEnabled) {
            if (this._sphereTexture && !this._sphereTexture.isReadyOrNotBlocking()) {
                return false;
            }
        }
        return true;
    }
    bindForSubMesh(uniformBuffer, scene, _engine, subMesh) {
        if (!this._isEnabled)
            return;
        const defines = subMesh.materialDefines;
        const isFrozen = this._material.isFrozen;
        if (!uniformBuffer.useUbo || !isFrozen || !uniformBuffer.isSync) {
            if (defines.DIFFUSE && defines.TEXTURE_COLOR) {
                uniformBuffer.updateDirectColor4("textureMultiplicativeColor", this.textureMultiplicativeColor);
                uniformBuffer.updateDirectColor4("textureAdditiveColor", this.textureAdditiveColor);
            }
            if (defines.NORMAL && defines.SPHERE_TEXTURE && defines.SPHERE_TEXTURE_COLOR) {
                uniformBuffer.updateDirectColor4("sphereTextureMultiplicativeColor", this.sphereTextureMultiplicativeColor);
                uniformBuffer.updateDirectColor4("sphereTextureAdditiveColor", this.sphereTextureAdditiveColor);
            }
            if (defines.TOON_TEXTURE && defines.TOON_TEXTURE_COLOR) {
                uniformBuffer.updateDirectColor4("toonTextureMultiplicativeColor", this.toonTextureMultiplicativeColor);
                uniformBuffer.updateDirectColor4("toonTextureAdditiveColor", this.toonTextureAdditiveColor);
            }
            if (defines.SPHERE_TEXTURE && subMesh.effect !== null) {
                this._material.bindView(subMesh.effect);
            }
        }
        if (scene.texturesEnabled) {
            if (defines.NORMAL && this._sphereTexture)
                uniformBuffer.setTexture("sphereSampler", this._sphereTexture);
            if (this._toonTexture)
                uniformBuffer.setTexture("toonSampler", this._toonTexture);
        }
    }
    dispose(forceDisposeTextures) {
        if (forceDisposeTextures) {
            this._sphereTexture?.dispose();
            this._toonTexture?.dispose();
        }
    }
    getCustomCode(shaderType) {
        if (shaderType === "vertex") {
            const codes = {};
            codes["CUSTOM_VERTEX_DEFINITIONS"] = sdefDeclaration;
            codes[`!${this._escapeRegExp("finalWorld=finalWorld*influence;")}`] = /* glsl */ `\n${sdefVertex}\nfinalWorld=(finalWorld*influence);\n`;
            return codes;
        }
        if (shaderType === "fragment") {
            const codes = {};
            codes["CUSTOM_FRAGMENT_DEFINITIONS"] = /* glsl */ "\n#if defined(SPHERE_TEXTURE) && defined(NORMAL)\nuniform sampler2D sphereSampler;\n#endif\n#ifdef TOON_TEXTURE\nuniform sampler2D toonSampler;\n#endif\n";
            codes["CUSTOM_FRAGMENT_MAIN_BEGIN"] = /* glsl */ "\n#ifdef TOON_TEXTURE\nvec3 toonNdl;\n#endif\n";
            codes[`!${this._escapeRegExp("#if defined(REFLECTIONMAP_SPHERICAL) || defined(REFLECTIONMAP_PROJECTION) || defined(REFRACTION) || defined(PREPASS)\nuniform mat4 view;\n#endif")}`] = /* glsl */ "\n#if defined(REFLECTIONMAP_SPHERICAL) || defined(REFLECTIONMAP_PROJECTION) || defined(REFRACTION) || defined(PREPASS)\nuniform mat4 view;\n#elif defined(NORMAL) && defined(SPHERE_TEXTURE)\nuniform mat4 view;\n#endif\n";
            codes[`!${this._escapeRegExp("vec3 diffuseColor=vDiffuseColor.rgb;")}`] = /* glsl */ "\n#ifdef APPLY_AMBIENT_COLOR_TO_DIFFUSE\nvec3 diffuseColor=clamp(vDiffuseColor.rgb+vAmbientColor,0.0,1.0);\n#else\nvec3 diffuseColor=(vDiffuseColor.rgb);\n#endif\n";
            codes[`!${this._escapeRegExp("baseColor=texture2D(diffuseSampler,vDiffuseUV+uvOffset);")}`] = /* glsl */ "\n#if defined(DIFFUSE) && defined(TEXTURE_COLOR)\nbaseColor=texture2D(diffuseSampler,(vDiffuseUV+uvOffset));baseColor.rgb=mix(\nvec3(1.0),\nbaseColor.rgb*textureMultiplicativeColor.rgb,\ntextureMultiplicativeColor.a\n);baseColor.rgb=clamp(\nbaseColor.rgb+(baseColor.rgb-vec3(1.0))*textureAdditiveColor.a,\n0.0,\n1.0\n)+textureAdditiveColor.rgb;\n#else\nbaseColor=texture2D(diffuseSampler,(vDiffuseUV+uvOffset));\n#endif\n";
            codes[`!${this._escapeRegExp("struct lightingInfo\n{")}`] = /* glsl */ "\nstruct lightingInfo {\n#ifdef TOON_TEXTURE\n#if !defined(NDOTL)\nfloat ndl;\n#endif\nfloat isToon;\n#endif\n";
            // ndl might be clamped to 1.0
            codes[`!${this._escapeRegExp("result.diffuse=ndl*diffuseColor*attenuation;")}`] = /* glsl */ "\n#ifdef TOON_TEXTURE\nresult.diffuse=diffuseColor*attenuation;result.ndl=ndl;result.isToon=1.0;\n#elif defined(IGNORE_DIFFUSE_WHEN_TOON_TEXTURE_DISABLED) \nresult.diffuse=diffuseColor*attenuation;\n#else\nresult.diffuse=(ndl*diffuseColor*attenuation);\n#endif\n";
            codes[`!${this._escapeRegExp("diffuseBase+=info.diffuse*shadow;")}`] = /* glsl */ "\n#ifdef TOON_TEXTURE\ntoonNdl=vec3(clamp(info.ndl*shadow,0.02,0.98));toonNdl.r=texture2D(toonSampler,vec2(0.5,toonNdl.r)).r;toonNdl.g=texture2D(toonSampler,vec2(0.5,toonNdl.g)).g;toonNdl.b=texture2D(toonSampler,vec2(0.5,toonNdl.b)).b;\n#ifdef TOON_TEXTURE_COLOR\ntoonNdl=mix(\nvec3(1.0),\ntoonNdl*toonTextureMultiplicativeColor.rgb,\ntoonTextureMultiplicativeColor.a\n);toonNdl=clamp(\ntoonNdl+(toonNdl-vec3(1.0))*toonTextureAdditiveColor.a,\n0.0,\n1.0\n)+toonTextureAdditiveColor.rgb;\n#endif\ndiffuseBase+=mix(info.diffuse*shadow,toonNdl*info.diffuse,info.isToon);\n#elif defined(IGNORE_DIFFUSE_WHEN_TOON_TEXTURE_DISABLED)\ndiffuseBase+=info.diffuse;\n#else\ndiffuseBase+=(info.diffuse*shadow);\n#endif\n";
            const finalDiffuse = /* glsl */ "\n#ifdef EMISSIVEASILLUMINATION\nvec3 finalDiffuse=clamp(diffuseBase*diffuseColor+vAmbientColor,0.0,1.0)*baseColor.rgb;\n#else\n#ifdef LINKEMISSIVEWITHDIFFUSE\nvec3 finalDiffuse=clamp((diffuseBase+emissiveColor)*diffuseColor+vAmbientColor,0.0,1.0)*baseColor.rgb;\n#else\nvec3 finalDiffuse=clamp(diffuseBase*diffuseColor+emissiveColor+vAmbientColor,0.0,1.0)*baseColor.rgb;\n#endif\n#endif\n";
            codes[`!${this._escapeRegExp(finalDiffuse)}`] = /* glsl */ `\n#ifdef APPLY_AMBIENT_COLOR_TO_DIFFUSE\n#ifdef EMISSIVEASILLUMINATION\nvec3 finalDiffuse=clamp(diffuseBase*diffuseColor,0.0,1.0)*baseColor.rgb;\n#else\n#ifdef LINKEMISSIVEWITHDIFFUSE\nvec3 finalDiffuse=clamp((diffuseBase+emissiveColor)*diffuseColor,0.0,1.0)*baseColor.rgb;\n#else\nvec3 finalDiffuse=clamp(diffuseBase*diffuseColor+emissiveColor,0.0,1.0)*baseColor.rgb;\n#endif\n#endif\n#else\n${finalDiffuse.replace("diffuseBase", "(diffuseBase)")}#endif\n`;
            codes["CUSTOM_FRAGMENT_BEFORE_FOG"] = /* glsl */ "\n#if defined(NORMAL) && defined(SPHERE_TEXTURE)\nvec3 viewSpaceNormal=normalize(mat3(view)*vNormalW);vec2 sphereUV=viewSpaceNormal.xy*0.5+0.5;vec4 sphereReflectionColor=texture2D(sphereSampler,sphereUV);\n#ifdef SPHERE_TEXTURE_COLOR\nsphereReflectionColor.rgb=mix(\nvec3(1.0),\nsphereReflectionColor.rgb*sphereTextureMultiplicativeColor.rgb,\nsphereTextureMultiplicativeColor.a\n);sphereReflectionColor.rgb=clamp(\nsphereReflectionColor.rgb+(sphereReflectionColor.rgb-vec3(1.0))*sphereTextureAdditiveColor.a,\n0.0,\n1.0\n)+sphereTextureAdditiveColor.rgb;\n#endif\nsphereReflectionColor.rgb*=diffuseBase;\n#ifdef SPHERE_TEXTURE_BLEND_MODE_MULTIPLY\ncolor*=sphereReflectionColor;\n#elif defined(SPHERE_TEXTURE_BLEND_MODE_ADD)\ncolor=vec4(color.rgb+sphereReflectionColor.rgb,color.a);\n#endif\n#endif\n";
            return codes;
        }
        return null;
    }
    prepareDefines(defines, scene, mesh) {
        if (this._isEnabled) {
            const texturesEnabled = scene.texturesEnabled;
            defines.SPHERE_TEXTURE = this._sphereTexture !== null && texturesEnabled;
            defines.SPHERE_TEXTURE_BLEND_MODE_MULTIPLY = this._sphereTextureBlendMode === MmdPluginMaterialSphereTextureBlendMode.Multiply;
            defines.SPHERE_TEXTURE_BLEND_MODE_ADD = this._sphereTextureBlendMode === MmdPluginMaterialSphereTextureBlendMode.Add;
            // todo: support sub texture mode
            defines.TOON_TEXTURE = this._toonTexture !== null && texturesEnabled;
            defines.IGNORE_DIFFUSE_WHEN_TOON_TEXTURE_DISABLED = this._ignoreDiffuseWhenToonTextureIsNull;
            defines.APPLY_AMBIENT_COLOR_TO_DIFFUSE = this._applyAmbientColorToDiffuse;
            defines.TEXTURE_COLOR = this._useTextureColor;
            defines.SPHERE_TEXTURE_COLOR = this._useSphereTextureColor;
            defines.TOON_TEXTURE_COLOR = this._useToonTextureColor;
            defines.SDEF = mesh.useBones && mesh.computeBonesUsingShaders && (mesh.skeleton ? true : false) && mesh.isVerticesDataPresent(MmdBufferKind.MatricesSdefCKind);
        }
        else {
            defines.SPHERE_TEXTURE = false;
            defines.SPHERE_TEXTURE_BLEND_MODE_MULTIPLY = false;
            defines.SPHERE_TEXTURE_BLEND_MODE_ADD = false;
            defines.TOON_TEXTURE = false;
            defines.IGNORE_DIFFUSE_WHEN_TOON_TEXTURE_DISABLED = false;
            defines.APPLY_AMBIENT_COLOR_TO_DIFFUSE = false;
            defines.TEXTURE_COLOR = false;
            defines.SPHERE_TEXTURE_COLOR = false;
            defines.TOON_TEXTURE_COLOR = false;
            defines.SDEF = false;
        }
    }
    hasTexture(texture) {
        return this._sphereTexture === texture || this._toonTexture === texture;
    }
    getActiveTextures(activeTextures) {
        if (this._sphereTexture)
            activeTextures.push(this._sphereTexture);
        if (this._toonTexture)
            activeTextures.push(this._toonTexture);
    }
    getAnimatables(animatables) {
        if (this._sphereTexture && this._sphereTexture.animations && 0 < this._sphereTexture.animations.length) {
            animatables.push(this._sphereTexture);
        }
        if (this._toonTexture && this._toonTexture.animations && 0 < this._toonTexture.animations.length) {
            animatables.push(this._toonTexture);
        }
    }
    getSamplers(samplers) {
        samplers.push("sphereSampler", "toonSampler");
    }
    getAttributes(attributes, _scene, mesh) {
        if (this._isEnabled) {
            if (mesh.useBones && mesh.computeBonesUsingShaders && mesh.skeleton && mesh.isVerticesDataPresent(MmdBufferKind.MatricesSdefCKind)) {
                attributes.push(MmdBufferKind.MatricesSdefCKind);
                attributes.push(MmdBufferKind.MatricesSdefR0Kind);
                attributes.push(MmdBufferKind.MatricesSdefR1Kind);
            }
        }
    }
    getUniforms() {
        return {
            "ubo": [
                { name: "textureMultiplicativeColor", size: 4, type: "vec4" },
                { name: "textureAdditiveColor", size: 4, type: "vec4" },
                { name: "sphereTextureMultiplicativeColor", size: 4, type: "vec4" },
                { name: "sphereTextureAdditiveColor", size: 4, type: "vec4" },
                { name: "toonTextureMultiplicativeColor", size: 4, type: "vec4" },
                { name: "toonTextureAdditiveColor", size: 4, type: "vec4" }
            ],
            "fragment": "\n#if defined(DIFFUSE) && defined(TEXTURE_COLOR)\nuniform vec4 textureMultiplicativeColor;uniform vec4 textureAdditiveColor;\n#endif\n#if defined(SPHERE_TEXTURE) && defined(SPHERE_TEXTURE_COLOR)\nuniform vec4 sphereTextureMultiplicativeColor;uniform vec4 sphereTextureAdditiveColor;\n#endif\n#if defined(TOON_TEXTURE) && defined(TOON_TEXTURE_COLOR)\nuniform vec4 toonTextureMultiplicativeColor;uniform vec4 toonTextureAdditiveColor;\n#endif\n"
        };
    }
    getClassName() {
        return "MmdPluginMaterial";
    }
    _escapeRegExp(string) {
        return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
    }
}
