import { applyMaterialMixins } from '../../meshmaterial.js';
import { mixinTextureProps } from '../texture.js';
import { mixinPBRCommon } from '../pbr/common.js';
import { Vector3 } from '@zephyr3d/base';
import { mixinLight } from '../lit.js';
import { ShaderHelper } from '../../shader/helper.js';
import { MaterialVaryingFlags, LIGHT_TYPE_RECT, LIGHT_TYPE_POINT } from '../../../values.js';

/**
 * PBRSpecularGlossiness mixin
 *
 * @param BaseCls - Base class to mix in
 * @returns Mixed class
 *
 * @public
 */ function mixinPBRSpecularGlossness(BaseCls) {
    if (BaseCls.pbrSpecularGlossnessMixed) {
        return BaseCls;
    }
    const S = applyMaterialMixins(BaseCls, mixinPBRCommon, mixinLight, mixinTextureProps('specular'));
    const SPECULAR_FACTOR_UNFORM = S.defineInstanceUniform('specularFactor', 'rgb', 'SpecularFactor');
    const GLOSSINESS_FACTOR_UNIFORM = S.defineInstanceUniform('glossinessFactor', 'float', 'GlossinessFactor');
    return class extends S {
        static pbrSpecularGlossnessMixed = true;
        _specularFactor;
        _glossinessFactor;
        constructor(){
            super();
            this._specularFactor = Vector3.one();
            this._glossinessFactor = 1;
        }
        copyFrom(other) {
            super.copyFrom(other);
            this.specularFactor = other.specularFactor;
            this.glossinessFactor = other.glossinessFactor;
        }
        get specularFactor() {
            return this._specularFactor;
        }
        set specularFactor(val) {
            if (!val.equalsTo(this._specularFactor)) {
                this._specularFactor.set(val);
                this.uniformChanged();
            }
        }
        get glossinessFactor() {
            return this._glossinessFactor;
        }
        set glossinessFactor(val) {
            if (val !== this._glossinessFactor) {
                this._glossinessFactor = val;
                this.uniformChanged();
            }
        }
        vertexShader(scope) {
            super.vertexShader(scope);
            if (this.needFragmentColor() && this.drawContext.materialFlags & MaterialVaryingFlags.INSTANCING) {
                scope.$outputs.zSpecularFactor = this.getInstancedUniform(scope, SPECULAR_FACTOR_UNFORM);
                scope.$outputs.zGlossinessFactor = this.getInstancedUniform(scope, GLOSSINESS_FACTOR_UNIFORM);
            }
        }
        fragmentShader(scope) {
            super.fragmentShader(scope);
            if (this.needFragmentColor() && !(this.drawContext.materialFlags & MaterialVaryingFlags.INSTANCING)) {
                const pb = scope.$builder;
                scope.zSpecularFactor = pb.vec3().uniform(2);
                scope.zGlossinessFactor = pb.float().uniform(2);
            }
        }
        applyUniformValues(bindGroup, ctx, pass) {
            super.applyUniformValues(bindGroup, ctx, pass);
            if (this.needFragmentColor(ctx) && !(ctx.materialFlags & MaterialVaryingFlags.INSTANCING)) {
                bindGroup.setValue('zSpecularFactor', this._specularFactor);
                bindGroup.setValue('zGlossinessFactor', this._glossinessFactor);
            }
        }
        PBRLight(scope, worldPos, normal, viewVec, albedo, TBN, outRoughness) {
            const pb = scope.$builder;
            const funcName = 'Z_PBRSpecularGlossinessLight';
            const that = this;
            pb.func(funcName, [
                pb.vec3('worldPos'),
                pb.vec3('normal'),
                pb.mat3('TBN'),
                pb.vec3('viewVec'),
                pb.vec4('albedo'),
                ...outRoughness ? [
                    pb.vec4('outRoughness').out()
                ] : []
            ], function() {
                this.$l.pbrData = that.getCommonData(this, this.albedo, this.normal, this.viewVec, this.TBN);
                this.$l.lightingColor = pb.vec3(0);
                this.$l.emissiveColor = that.calculateEmissiveColor(this);
                if (outRoughness) {
                    that.indirectLighting(this, this.normal, this.viewVec, this.pbrData, this.lightingColor, this.outRoughness);
                } else {
                    that.indirectLighting(this, this.normal, this.viewVec, this.pbrData, this.lightingColor);
                }
                that.forEachLight(this, function(type, posRange, dirCutoff, colorIntensity, extra, shadow) {
                    this.$if(pb.equal(type, LIGHT_TYPE_RECT), function() {
                        that.directRectLight(this, this.worldPos, this.normal, this.viewVec, this.pbrData, posRange, dirCutoff, extra, colorIntensity, this.lightingColor);
                    }).$else(function() {
                        this.$l.diffuse = pb.vec3();
                        this.$l.specular = pb.vec3();
                        this.$l.diffuseScale = pb.float(1);
                        this.$l.specularScale = pb.float(1);
                        this.$l.sourceRadiusFactor = pb.float(0);
                        this.$if(pb.equal(type, LIGHT_TYPE_POINT), function() {
                            this.diffuseScale = extra.x;
                            this.specularScale = extra.y;
                            this.sourceRadiusFactor = pb.div(extra.z, pb.max(pb.distance(posRange.xyz, this.worldPos), 0.0001));
                        });
                        this.$l.lightAtten = that.calculateLightAttenuation(this, type, this.worldPos, posRange, dirCutoff);
                        this.$l.lightDir = that.calculateLightDirection(this, type, this.worldPos, posRange, dirCutoff);
                        this.$l.NoL = pb.clamp(pb.dot(this.normal, this.lightDir), 0, 1);
                        this.$l.lightColor = pb.mul(colorIntensity.rgb, colorIntensity.a, this.lightAtten, this.NoL);
                        if (shadow) {
                            this.lightColor = pb.mul(this.lightColor, that.calculateShadow(this, this.worldPos, this.NoL));
                        }
                        that.directLighting(this, this.lightDir, this.lightColor, this.normal, this.viewVec, this.pbrData, this.diffuseScale, this.specularScale, this.sourceRadiusFactor, this.lightingColor);
                    });
                });
                this.$return(pb.add(this.lightingColor, this.emissiveColor));
            });
            return outRoughness ? pb.getGlobalScope()[funcName](worldPos, normal, TBN, viewVec, albedo, outRoughness) : pb.getGlobalScope()[funcName](worldPos, normal, TBN, viewVec, albedo);
        }
        calculateCommonData(scope, albedo, normal, viewVec, TBN, data) {
            super.calculateCommonData(scope, albedo, normal, viewVec, TBN, data);
            const pb = scope.$builder;
            const instancing = !!(this.drawContext.materialFlags & MaterialVaryingFlags.INSTANCING);
            const specularFactor = instancing ? scope.$inputs.zSpecularFactor : scope.zSpecularFactor;
            const glossinessFactor = instancing ? scope.$inputs.zGlossinessFactor : scope.zGlossinessFactor;
            if (this.specularTexture) {
                scope.$l.specularTextureSample = this.sampleSpecularTexture(scope);
                data.roughness = pb.sub(1, pb.mul(glossinessFactor, scope.specularTextureSample.a));
                data.f0 = pb.vec4(pb.mul(scope.specularTextureSample.rgb, specularFactor), this.getF0(scope).a);
            } else {
                data.roughness = pb.sub(1, glossinessFactor);
                data.f0 = pb.vec4(specularFactor, this.getF0(scope).a);
            }
            data.roughness = pb.mul(data.roughness, ShaderHelper.getCameraRoughnessFactor(scope));
            data.metallic = pb.max(pb.max(data.f0.r, data.f0.g), data.f0.b);
            data.diffuse = pb.vec4(pb.mul(albedo.rgb, pb.sub(1, data.metallic)), albedo.a);
            data.specularWeight = 1;
            data.f90 = pb.vec3(1);
        }
    };
}

export { mixinPBRSpecularGlossness };
//# sourceMappingURL=pbrspecularglossness.js.map
