// 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.

/// <remarks>
/// TSampleCount: generic int - number of iterations.
/// </remarks>
shader PostEffectBoundingRay<int TSampleCount> : ImageEffectShader, DepthBase, Transformation, PositionStream4
{
    float3 ComputeColorOut() 
    {
        return 0;
    }

    float3 ComputeColorIn(float4 positionWS, float stepSize, int stepIndex) 
    {
        return 0;
    }

    int HashXYZ(float3 input)
    {
        return int(input.z * 313 + input.x * 1039 + input.y * 638359);
    }

    float RayStepJitter(float3 input, float stepSize) 
    {
        return stepSize * Math.FastRandom(HashXYZ(input));
    }

    float4 ComputeFinalColor(float3 lightAcc) 
    {
        return float4(lightAcc.xxx, 1.0f);
    }

    stage override void PSMain()
    {
        // minmax.x = min
        // minmax.y = max
        float2 minmax = Texture0.Sample(PointSampler, streams.TexCoord).xy;

        float backsideMin = Texture1.Sample(PointSampler, 0.0f).x;
        if(backsideMin < 1.0f)
            minmax.x = 0.0f;

        // Need at least a maximum value for this pixel to be contained in the bounding box
        if(minmax.y < 1.0f)
        {
            float currentZ = GetZProjDepthFromUV(streams.TexCoord);
            float minZ = minmax.x;
            float maxZ = min(minmax.y, currentZ);
            
            float minDistance = ComputeDepthFromZProj(minZ);
            float maxDistance = ComputeDepthFromZProj(maxZ);

            // Compute world space direction and position of the ending position
            float4 positionClipSpace = float4((1.0f - streams.TexCoord.xy * 2.0f) * float2(-1.0f, 1.0f), maxZ, 1.0f);
            float4 positionVS = mul(positionClipSpace, ProjectionInverse);
            positionVS.xyzw /= positionVS.w;
            float4 endingPosition = mul(positionVS, ViewInverse);
            float3 endingPositionDelta = endingPosition.xyz - Eye.xyz;
            float4 directionWS = float4(endingPositionDelta, 0.0f);
            directionWS = normalize(directionWS);
            
            // Compute depth slope and apply it to the world space direction so it can be multiplied by depth distances
            float depthSlope = length(endingPositionDelta) / maxDistance;
            directionWS *= depthSlope;

            float stepRange = (maxDistance - minDistance); 
            float stepSize = stepRange / (float)TSampleCount;

            float3 lightResult = 0.0f;

            // Expected by the directional shadow map to compute the cascades
            streams.DepthVS = minDistance;
            if(maxDistance > minDistance)
            {
                // Recalculate max distance by jittering the length of the ray to avoid banding artifacts
                stepSize = (maxDistance - minDistance) / (float)TSampleCount;

                // Starting position
                float4 positionWS = endingPosition + (RayStepJitter(positionVS.xyz, stepSize)-stepRange) * directionWS;
                streams.DepthVS = minDistance;

                for(int i = 0; i < TSampleCount; i++) 
                {
                    lightResult += this.ComputeColorIn(positionWS, stepSize, i);
                    positionWS += stepSize * directionWS;
                    streams.DepthVS += stepSize;
                }
            }

            streams.ColorTarget = ComputeFinalColor(lightResult);
        }
        else // Outside the bounding box
        {
            streams.ColorTarget = ComputeFinalColor(this.ComputeColorOut());
        }
    }
};
