// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.

namespace Stride.Rendering.Images
{
    /// <summary>
    /// Film-grain shader. 
    /// Adapted from the shader of Martins Upitis.
    /// http://devlog-martinsh.blogspot.ca/2013/05/image-imperfections-and-film-grain-post.html
    /// </summary>
    internal shader FilmGrainShader : ColorTransformShader, Texturing
    {
        // Amount
        float Amount;

        // Time changing at each frame for the animation
        float Time;

        // Size of the grain
        float GrainSize;

        // How the luminance influences the amount of grain.
        float LuminanceFactor;

        override float4 Compute(float4 color)
        {
            float2 texCoord = streams.TexCoord;
    
            float2 rotCoordsR = coordRot(texCoord, Time);
            float2 newCoord = rotCoordsR / Texture0TexelSize / GrainSize;
            float n = pnoise3D(float3(newCoord, 0.0));
            float3 noiseFactor = float3(n, n, n); 

            float3 col = color.rgb;

            // Noisiness response curve based on scene luminance
            float luminance = lerp(0.0, LuminanceUtils.Luma(col), LuminanceFactor);
            float lum = smoothstep(0.2, 0.0, luminance) + luminance;

            noiseFactor = saturate( lerp(noiseFactor, float3(0.0, 0.0, 0.0), pow(lum, 4.0)));
            color.rgb += noiseFactor * Amount;
   
            return color;
        }

        // Random texture generation
        float4 rnm(float2 tc) 
        {
            float noiseFactor =  sin(dot(tc + float2(Time, Time), float2(12.9898, 78.233))) * 43758.5453;

            float4 result = float4( frac(noiseFactor),
                                    frac(noiseFactor * 1.2154),
                                    frac(noiseFactor * 1.3453),
                                    frac(noiseFactor * 1.3647));
            return result * 2.0 - 1;
        }

        float fade(float t) {
            return Math.Quintic(t);
        }

        float pnoise3D(float3 p)
        {
            float permTexUnit = 1.0 / 256.0;
            float permTexUnitHalf = permTexUnit * 0.5;

            float3 pi = permTexUnit * floor(p) + permTexUnitHalf; // Integer part, scaled so +1 moves permTexUnit texel
            // and offset 1/2 texel to sample texel centers
            float3 pf = frac(p);     // Fractional part for interpolation

            // Noise contributions from (x=0, y=0), z=0 and z=1
            float perm00 = rnm(pi.xy).a ;
            float3  grad000 = rnm(float2(perm00, pi.z)).rgb * 4.0 - 1.0;
            float n000 = dot(grad000, pf);
            float3  grad001 = rnm(float2(perm00, pi.z + permTexUnit)).rgb * 4.0 - 1.0;
            float n001 = dot(grad001, pf - float3(0.0, 0.0, 1.0));

            // Noise contributions from (x=0, y=1), z=0 and z=1
            float perm01 = rnm(pi.xy + float2(0.0, permTexUnit)).a ;
            float3  grad010 = rnm(float2(perm01, pi.z)).rgb * 4.0 - 1.0;
            float n010 = dot(grad010, pf - float3(0.0, 1.0, 0.0));
            float3  grad011 = rnm(float2(perm01, pi.z + permTexUnit)).rgb * 4.0 - 1.0;
            float n011 = dot(grad011, pf - float3(0.0, 1.0, 1.0));

            // Noise contributions from (x=1, y=0), z=0 and z=1
            float perm10 = rnm(pi.xy + float2(permTexUnit, 0.0)).a ;
            float3  grad100 = rnm(float2(perm10, pi.z)).rgb * 4.0 - 1.0;
            float n100 = dot(grad100, pf - float3(1.0, 0.0, 0.0));
            float3  grad101 = rnm(float2(perm10, pi.z + permTexUnit)).rgb * 4.0 - 1.0;
            float n101 = dot(grad101, pf - float3(1.0, 0.0, 1.0));

            // Noise contributions from (x=1, y=1), z=0 and z=1
            float perm11 = rnm(pi.xy + float2(permTexUnit, permTexUnit)).a ;
            float3  grad110 = rnm(float2(perm11, pi.z)).rgb * 4.0 - 1.0;
            float n110 = dot(grad110, pf - float3(1.0, 1.0, 0.0));
            float3  grad111 = rnm(float2(perm11, pi.z + permTexUnit)).rgb * 4.0 - 1.0;
            float n111 = dot(grad111, pf - float3(1.0, 1.0, 1.0));

            // Blend contributions along x
            float4 n_x = lerp(float4(n000, n001, n010, n011), float4(n100, n101, n110, n111), fade(pf.x));

            // Blend contributions along y
            float2 n_xy = lerp(n_x.xy, n_x.zw, fade(pf.y));

            // Blend contributions along z
            float n_xyz = lerp(n_xy.x, n_xy.y, fade(pf.z));

            // We're done, return the final noise value.
            return n_xyz;
        }

        float2 coordRot(float2 tc, float angle)
        {
            float aspect = Texture0TexelSize.y / Texture0TexelSize.x;
            float rotX = ((tc.x * 2.0 - 1.0) * aspect * cos(angle)) - ((tc.y * 2.0 - 1.0) * sin(angle));
            float rotY = ((tc.y * 2.0 - 1.0) * cos(angle)) + ((tc.x * 2.0 - 1.0) * aspect * sin(angle));
            rotX = ((rotX/aspect)*0.5+0.5);
            rotY = rotY * 0.5 + 0.5;
            return float2(rotX, rotY);
        }

    };
}
