volopt = [ >= (strstr $volumetrictype $arg1) 0 ]

volumetricvariantshader = [
    local volumetrictype
    volumetrictype = $arg3
    maxsteps = $arg4
    spotlight = (>= $arg2 1)
    variantshader 0 $arg1 $arg2 (? (< $arg2 0) [
        in vec4 vvertex;
        uniform mat4 lightmatrix;
        void main(void)
        {
            gl_Position = lightmatrix * vvertex;
        }
    ]) [
        @(gfetchdefs tex3)
        @(if (volopt "p") [
            if (|| (volopt "g") (volopt "G")) [if (> $usetexgather 1) [result [
                uniform sampler2DShadow tex4;
            ]] [result [
                uniform sampler2D tex4;
            ]]] [result [
                uniform sampler2DRectShadow tex4;
            ]]
        ])
        uniform vec4 lightpos;
        uniform vec3 lightcolor; 
        @(? $spotlight [
            uniform vec4 spotparams;
        ])
        @(? (volopt "p") [
            uniform vec4 shadowparams;
            uniform vec2 shadowoffset;
        ])
        uniform vec3 camera;
        uniform mat4 worldmatrix;
        uniform vec4 fogdir;
        uniform vec3 fogcolor;
        uniform vec2 fogdensity;
        uniform vec4 radialfogscale;
        uniform vec2 shadowatlasscale;
        uniform vec4 volscale;
        uniform float volminstep;
        uniform float voldistclamp;
        uniform float volprefilter;
        layout(location = 0) out vec4 fragcolor;

        @(if (volopt "p") [
            ? $spotlight [
                vec3 getspottc(vec3 dir, float spotdist)
                {
                    vec2 mparams = shadowparams.xy / max(spotdist, 1e-5);
                    return vec3((dir.xy - spotparams.xy*(spotdist + (spotparams.z < 0.0 ? -1.0 : 1.0)*dir.z)*shadowparams.z) * mparams.x + shadowoffset, mparams.y + shadowparams.w);
                }
            ] [
                vec3 getshadowtc(vec3 dir)
                {
                    vec3 adir = abs(dir);
                    float m = max(adir.x, adir.y), mz = max(adir.z, m);
                    vec2 mparams = shadowparams.xy / max(mz, 1e-5);
                    vec4 proj;
                    if(adir.x > adir.y) proj = vec4(dir.zyx, 0.0); else proj = vec4(dir.xzy, 1.0);
                    if(adir.z > m) proj = vec4(dir, 2.0);
                    return vec3(proj.xy * mparams.x + vec2(proj.w, step(proj.z, 0.0)) * shadowparams.z + shadowoffset, mparams.y + shadowparams.w);
                }
            ]
        ])

        @(if (volopt "p") [
            if (|| (volopt "g") (volopt "G")) [
                ? (> $usetexgather 1) [
                    #define filtershadow(shadowtc) float(shadow(tex4, vec3(shadowtc.xy*shadowatlasscale, shadowtc.z)))
                ] [
                    #define filtershadow(shadowtc) step(shadowtc.z, float(texture(tex4, shadowtc.xy*shadowatlasscale)))
                ]
            ] [result [
                #define filtershadow(shadowtc) float(textureRect(tex4, shadowtc))
            ]]
        ])
    
        void main(void)
        {
            vec2 tc = gl_FragCoord.xy * volscale.xy;
            @(gdepthunpack depth [gfetch(tex3, tc)] [
                vec3 pos = (worldmatrix * vec4(depth*tc, depth, 1.0)).xyz;
            ] [
                vec4 pos = worldmatrix * vec4(tc, depth, 1.0);
                pos.xyz /= pos.w;
            ])
            vec3 ray = pos.xyz - camera;
            float dist2 = dot(ray, ray), invdist = inversesqrt(dist2), radialdist = min(dist2 * invdist, voldistclamp);
            ray *= invdist;
            vec3 camlight = lightpos.xyz - camera * lightpos.w;
            float camlight2 = dot(camlight, camlight), v = dot(camlight, ray), d = v*v + 1.0 - camlight2;
            float light = 0.0;
            if(d > 0)
            {
                d = sqrt(d);
                float front = v - d, back = v + d;
                @(? $spotlight [
                    float spotangle = 1.0 - 1.0/spotparams.w, spotangle2 = spotangle*spotangle,
                          rayspot = dot(ray, spotparams.xyz), camspot = dot(camlight, spotparams.xyz),
                          qa = spotangle2 - rayspot*rayspot,
                          qb = rayspot*camspot - spotangle2*v,
                          qc = spotangle2*camlight2 - camspot*camspot,
                          disc = qb*qb - qa*qc;
                    if(disc > 0)
                    {
                        disc = abs(sqrt(disc)/qa);
                        float t = -qb/qa, t0 = t - disc, t1 = t + disc;
                        if(t0*rayspot < camspot) front = max(front, t1);
                        else if(t1*rayspot < camspot) back = min(back, t0);
                        else { front = max(front, t0); back = min(back, t1); }
                ])
                float maxspace = back - front, stepdist = maxspace * @(divf 1.0 $maxsteps);
                front = max(front, 0.0);
                back = min(back, radialdist * lightpos.w);
                float space = back - front;
                if(space > volminstep*stepdist)
                {
                    float dither = dot(fract((gl_FragCoord.xy - 0.5).xyxy*vec4(0.5, 0.5, 0.25, 0.25)), vec4(0.375, 0.9375, 0.25, 0.125));
                    vec3 lightdir = ray * (back + stepdist*dither) - camlight;
                    vec3 raystep = ray * -stepdist;
                    for(int i = 0; i < @maxsteps; i++)
                    {
                        lightdir += raystep;
                        @(if $spotlight [result [
                            float lightdist2 = dot(lightdir, lightdir);
                            float lightinvdist = inversesqrt(lightdist2); 
                            float spotdist = dot(lightdir, spotparams.xyz);
                            float spotatten = 1.0 - (1.0 - lightinvdist * spotdist) * spotparams.w;
                            if(spotatten > 0.0)
                            {
                                float lightatten = clamp(1.0 - lightdist2 * lightinvdist, 0.0, 1.0) * spotatten;
                                @(? (volopt "p") [
                                    vec3 spottc = getspottc(lightdir, spotdist);
                                    lightatten *= filtershadow(spottc);
                                ])
                                light += lightatten;
                            }
                        ]] [result [
                            float lightatten = clamp(1.0 - length(lightdir), 0.0, 1.0);
                            @(? (volopt "p") [
                                vec3 shadowtc = getshadowtc(lightdir);
                                lightatten *= filtershadow(shadowtc);
                            ])
                            light += lightatten;
                        ]])
                        space -= stepdist;
                        if(space <= 0) break; 
                    }
                    float fogcoord = front/lightpos.w;
                    float foglerp = clamp(exp2(fogcoord*fogdensity.x)*fogdensity.y, 0.0, 1.0);
                    light *= foglerp * stepdist;
                    @(? $spotlight [
                        light /= min(maxspace, 1.0);
                    ])
                }
                @(? $spotlight [}])
            }
            vec2 weights = step(fwidth(radialdist), volprefilter) * (2.0*fract((gl_FragCoord.xy - 0.5)*0.5) - 0.5);
            light -= dFdx(light) * weights.x;
            light -= dFdy(light) * weights.y;
            fragcolor.rgb = light * lightcolor;
            fragcolor.a = 0.0;
        }
    ]
]

volumetricshader = [
    volumetrictype = (concatword $arg1 $arg2)
    shadername = (concatword "volumetric" $volumetrictype $arg3)
    volumetricvariantshader $shadername -1 $arg1 $arg3
    volumetricvariantshader $shadername 0 (concatword $arg1 $arg2) $arg3
    if (volopt "s") [
        volumetricvariantshader $shadername 1 $arg1 $arg3
        volumetricvariantshader $shadername 2 (concatword $arg1 $arg2) $arg3    
    ]
]

volumetricbilateralvariantshader = [
    numtaps = $arg2
    reduced = $arg3 
    filterdir = $arg4
    shader 0 $arg1 [
        in vec4 vvertex;
        @(if $reduced [result [
            @(screentexcoord 0)
            out vec2 texcoord0;
        ]])
        void main(void)
        {
            gl_Position = vvertex;
            @(? $reduced [texcoord0 = vtexcoord0;])
        }
    ] [
        @(gfetchdefs tex3)
        uniform sampler2DRect tex0;
        uniform vec2 bilateralparams;
        @(? $reduced [in vec2 texcoord0;])
        layout(location = 0) out vec4 fragcolor;
        void main(void)
        {
            #define tc gl_FragCoord.xy
            #define depthtc @(? $reduced [texcoord0] [gl_FragCoord.xy])
            #define tapvec(type, i) @(? (=s $filterdir "x") [type(i, 0.0)] [type(0.0, i)])
            #define texval(i) textureRect(tex0, tc + tapvec(vec2, i))
            #define texvaloffset(i) textureRectOffset(tex0, tc, tapvec(ivec2, i))
            #define depthval(i) gfetch(tex3, depthtc + tapvec(vec2, i))
            #define depthvaloffset(i) gfetchoffset(tex3, depthtc, tapvec(ivec2, i))
            vec3 color = textureRect(tex0, tc).rgb;
            @(gdepthunpack depth [gfetch(tex3, depthtc)])
            float weights = 1.0;
            @(loopconcat i (* 2 $numtaps) [
                curtap = (- $i $numtaps)
                if (>= $curtap 0) [curtap = (+ $curtap 1)]
                curtapoffset = (* $curtap 2)
                curdepthoffset = (<< $curtapoffset $reduced)
                curtexval = [texval@(? (<= $mintexrectoffset $curtapoffset $maxtexrectoffset) "offset")(@(+f $curtapoffset))]
                curdepthval = [depthval@(? (<= $mintexrectoffset $curdepthoffset $maxtexrectoffset) "offset")(@(+f $curdepthoffset))]
                result [
                    vec3 color@[i] = @[curtexval].rgb;
                    @(gdepthunpack [depth@[i]] $curdepthval)
                    depth@[i] -= depth;
                    float weight@[i] = exp2(@(-f 0 (* $curtap $curtap))*bilateralparams.x - depth@[i]*depth@[i]*bilateralparams.y); 
                    weights += weight@[i];
                    color += weight@[i] * color@[i];
                ]
            ])
            fragcolor = vec4(color / weights, 0.0);
        }
    ]
]

volumetricbilateralshader = [
    volumetricbilateralvariantshader (concatword "volumetricbilateralx" $arg1 $arg2) $arg1 $arg2 x
    volumetricbilateralvariantshader (concatword "volumetricbilateraly" $arg1 $arg2) $arg1 $arg2 y
]

