﻿// 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.Materials
{
    /// <summary>
    /// Calculates the diffuse lighting for hair.
    /// </summary>
    shader MaterialSurfaceShadingDiffuseHair<bool TIsEnergyConservative, int ShadingModel, bool DebugRenderPasses> :
        IMaterialSurfaceShading, // Required for "ComputeDirectLightContribution()" and the like.
        MaterialPixelShadingStream,
        Math,   // Required for "PI".
        LightStream,    // Required for "streams.lightColor" and "streams.lightDirectionWS". 
        MaterialHairShared,
        Transformation, // Required for "WorldInverseTranspose".
        NormalStream // Required for "streams.normalWS", "streams.meshNormal", "streams.meshTangent".
    {
        compose IMaterialHairLightAttenuationFunction hairLightAttenuationFunction;
        compose IMaterialHairDirectionFunction hairDirectionFunction;
        compose IMaterialHairShadowingFunction hairShadowingFunction;
        compose IMaterialHairDiscardFunction hairDiscardFunction;
        
        stream float3 hairDirection;

        override void PrepareForLightingAndShading()
        {
            streams.hairDirection = hairDirectionFunction.Compute(streams.meshNormal, streams.meshTangent, (float3x3)WorldInverseTranspose);
        }
        
        float3 ComputeHairLightingDiffuse(float3 hairDirection, float3 toLightVec, float3 diffuseLighting)
        {
            float3 diffuse = diffuseLighting;
            //float4 diffuse = LambertBRDF(surfaceData); // Mizuchi version  // TODO: Can we delete this line?
            
            if(ShadingModel == HAIR_SHADING_KAJIYAKAY_SHIFTED)
            {
                // in Kajiya's model: diffuse component: sin(t, l)
                float cosTL = dot(hairDirection, toLightVec);
                float sinTL = sqrt(1.0 - cosTL * cosTL);
                diffuse *= sinTL;
            }

            return diffuse;
        }

        float HairDiffuseAttenuation(float dotNL)  // "dotNL" must be unsaturated!
        {
            if(ShadingModel == HAIR_SHADING_KAJIYAKAY_SHIFTED)
            {
                // No ad-hoc attenuation in Kajiya-Kay formula
                // The coefficient for diffuse is applied during ComputeHairLightingDiffuse(),
                // therefore we do not apply dotNL.
                return 1.0;
            }
            else
            {
                //------------------------------------------------------------------------------
                // From Shader X3, Chapter 2.14 (Hair Rendering and Shading), P.243 :
                // "Kajiya-kay diffuse term is too bright"
                // "standard dotNL diffuse is too dark in areas facing away from the light"
                // "a good compromise is a tweaked dotNL term"
                //------------------------------------------------------------------------------
                return saturate(0.75 * dotNL + 0.25);
            }
        }

        override float3 ComputeDirectLightContribution()
        {
            if(DebugRenderPasses)
            {
                return 0.0;   // Return 0.0 because the indirect lighting function already returns the debug color.
            }

            var diffuseColor = streams.matDiffuseVisible;   // This already includes the multiplication by the cavity map.
            if (TIsEnergyConservative)
            {
                // Approximation see: http://research.tri-ace.com/Data/course_note_practical_implementation_at_triace.pdf
                diffuseColor *= (1 - streams.matSpecularVisible);
            }

            float3 diffuseLighting = diffuseColor / PI;
            diffuseLighting *= streams.lightColor * streams.lightAttenuation; //diffuseLighting *= streams.lightColorNdotL;  // Distance- & normal-attenuated light color.
            diffuseLighting *= streams.matDiffuseSpecularAlphaBlend.x;
            diffuseLighting *= hairShadowingFunction.Compute(); // Apply shadowing/scattering.
            diffuseLighting *= streams.lightDirectAmbientOcclusion;

            const float NdotLUnsaturated = dot(streams.normalWS, streams.lightDirectionWS);
            
            // TODO: Multiply by alpha or not?
            float3 finalLighting = ComputeHairLightingDiffuse(streams.hairDirection, streams.lightDirectionWS, diffuseLighting);
            finalLighting *= HairDiffuseAttenuation(NdotLUnsaturated);
            finalLighting *= hairLightAttenuationFunction.Compute();

            return finalLighting;
        }

        override float3 ComputeEnvironmentLightContribution()
        {
            if(DebugRenderPasses)
            {
                return GetDebugColor(PassID);
            }

           // TODO: The indirect diffuse hair lighting could be much better.
           // For example by taking the hair structure into account just like for the specular lighting.
           // But that's difficult because the diffuse environmental lighting is calculated somewhere else.

            // TODO: Check how to factorize this with DirectLight
            var diffuseColor = streams.matDiffuseVisible;   // This already includes the multiplication by the cavity map.
            if (TIsEnergyConservative)
            {
                diffuseColor *= (1 - streams.matSpecularVisible);
            }
            
            // TODO: Multiply by alpha or not?
            return diffuseColor * streams.envLightDiffuseColor;
            //return diffuseColor * streams.envLightDiffuseColor * streams.matAmbientOcclusion; // TODO: Does AO work without this?
        }

        /*
        // TODO: Enabling this allows the diffuse hair shading model to be used independently of the specular one.
        //       But this would mean if both the specular and the diffuse models are present, the shader will have two conditional discards, which will cause a shader compilation error.
        //       Need to find a way to fix that.
        override void AfterLightingAndShading()
        {
            hairDiscardFunction.Discard();
        }
        */
    };
}