Shader "Hidden/LookDev/Compositor"
{
    Properties
    {
        _Tex0MainView("Main View", 2D) = "white" {}
        _Tex0Shadows("First View shadow mask", 2D) = "white" {}
        _ShadowColor0("Shadow Color for first view", Color) = (1.0, 1.0, 1.0, 1.0)
        _Tex1MainView("Second View", 2D) = "white" {}
        _Tex1Shadows("Second View shadow mask", 2D) = "white" {}
        _ShadowColor1("Shadow Color for second view", Color) = (1.0, 1.0, 1.0, 1.0)
        _CompositingParams("Blend Factor, exposure for first and second view, and current selected side", Vector) = (0.0, 1.0, 1.0, 1.0)
        _CompositingParams2("Drag and drop zone and shadow multipliers", Vector) = (0.0, 1.0, 1.0, 1.0) // Drag and Drop zone Left == 1.0, Right == -1.0, None == 0.0
        _FirstViewColor("Gizmo Color for first view", Color) = (0.5, 0.5, 0.5, 0.5)
        _SecondViewColor("Gizmo Color for second view", Color) = (0.5, 0.5, 0.5, 0.5)
        _GizmoPosition("Position of split view gizmo", Vector) = (0.5, 0.5, 0.0, 0.0)
        _GizmoZoneCenter("Center of Zone view gizmo", Vector) = (0.5, 0.5, 0.0, 0.0)
        _GizmoSplitPlane("2D plane of the gizmo", Vector) = (1.0, 1.0, 0.0, 0.0)
        _GizmoSplitPlaneOrtho("2D plane orthogonal to the gizmo", Vector) = (1.0, 1.0, 0.0, 0.0)
        _GizmoLength("Gizmo Length", Float) = 0.2
        _GizmoThickness("Gizmo Thickness", Vector) = (0.01, 0.08, 0.0, 0.0)
        _GizmoCircleRadius("Gizmo extremities radius", Vector) = (0.05, 0.4, 0.0, 0.0)
        _GizmoRenderMode("Render gizmo mode", Float) = 0.0
        _GetBlendFactorMaxGizmoDistance("Distance on the gizmo where the blend circle stops", Float) = 0.2
        _BlendFactorCircleRadius("Visual radius of the blend factor gizmo", Float) = 0.01
        _ScreenRatio("Screen ratio", Vector) = (1.0, 1.0, 0.0, 0.0) // xy screen ratio, zw screen size
        _ToneMapCoeffs1("Parameters for neutral tonemap", Vector) = (0.0, 0.0, 0.0, 0.0)
        _ToneMapCoeffs2("Parameters for neutral tonemap", Vector) = (0.0, 0.0, 0.0, 0.0)
    }

    CGINCLUDE
    #include "UnityCG.cginc"
    #pragma vertex vert

            // Enum matching GizmoOperationType in LookDevViews.cs
    #define kNone 0.0f
    #define kTranslation 1.0f
    #define kRotationZone1 2.0f
    #define kRotationZone2 3.0f
    #define kAll 4.0f


    sampler2D   _Tex0MainView;
    sampler2D   _Tex0Shadows;
    float4      _ShadowColor0;
    sampler2D   _Tex1MainView;
    sampler2D   _Tex1Shadows;
    float4      _ShadowColor1;
    float4      _CompositingParams; // x BlendFactor, yz ExposureValue (first/second view), w current selected side
    float4      _CompositingParams2; // x current drag context, y apply tonemap (bool), z shadow multiplier
    float4      _FirstViewColor;
    float4      _SecondViewColor;
    float4      _GizmoPosition;
    float4      _GizmoZoneCenter;
    float4      _GizmoThickness;
    float4      _GizmoCircleRadius;
    float4      _GizmoSplitPlane;
    float4      _GizmoSplitPlaneOrtho;
    float       _GizmoLength;
    float       _GizmoRenderMode;
    float       _GetBlendFactorMaxGizmoDistance;
    float       _BlendFactorCircleRadius;
    float4      _ScreenRatio;
    float4      _ToneMapCoeffs1;
    float4      _ToneMapCoeffs2;

    float4      _Tex0MainView_ST;

    #define ShadowMultiplier0 _CompositingParams2.z
    #define ShadowMultiplier1 _CompositingParams2.w

    #define ExposureValue1 _CompositingParams.y
    #define ExposureValue2 _CompositingParams.z

    #define InBlack         _ToneMapCoeffs1.x
    #define OutBlack        _ToneMapCoeffs1.y
    #define InWhite         _ToneMapCoeffs1.z
    #define OutWhite        _ToneMapCoeffs1.w
    #define WhiteLevel      _ToneMapCoeffs2.z
    #define WhiteClip       _ToneMapCoeffs2.w

    struct appdata_t
    {
        float4 vertex : POSITION;
        float2 texcoord : TEXCOORD0;
    };

    struct v2f
    {
        float2 texcoord : TEXCOORD0;
        float4 vertex : SV_POSITION;
    };

    float DistanceToSplit(float2 pos, float3 splitPlane)
    {
        return dot(float3(pos, 1), splitPlane);
    }

    bool IsInsideGizmo(float2 normalizedCoord, float absDistanceToPlane, float distanceFromCenter, float side, float3 orthoPlane, float gizmoCircleRadius, float gizmoThickness, out float outSmoothing, float mode)
    {
        bool result = false;
        outSmoothing = 0.0;
        if (absDistanceToPlane < gizmoCircleRadius) // First "thick" bar, as large as the radius at extremities.
        {
            if (distanceFromCenter < (_GizmoLength + gizmoCircleRadius))
            {
                // side < 0 is cyan circle, side > 0 is orange widget
                if (mode == kAll ||
                    (mode == kRotationZone1 && side > 0) ||
                    (mode == kRotationZone2 && side < 0))
                {
                    if (distanceFromCenter >= (_GizmoLength - gizmoCircleRadius)) // Inside circle at the extremities ?
                    {
                        float2 circleCenter = _GizmoPosition.xy + side * orthoPlane.xy * _GizmoLength;
                        float d = length(normalizedCoord - circleCenter);
                        if (d <= gizmoCircleRadius)
                        {
                            outSmoothing = smoothstep(1.0, 0.8, d / gizmoCircleRadius);
                            result = true;
                        }
                    }
                }

                if (mode == kAll || mode == kTranslation)
                {
                    if (absDistanceToPlane < gizmoThickness && distanceFromCenter < _GizmoLength)
                    {
                        outSmoothing = max(outSmoothing, smoothstep(1.0, 0.0, absDistanceToPlane / gizmoThickness));
                        result = true;
                    }
                }
            }
        }

        return result;
    }

    float4 GetGizmoColor(float2 normalizedCoord, float3 splitPlane, float3 orthoPlane)
    {
        float distanceToPlane = DistanceToSplit(normalizedCoord, splitPlane);
        float absDistanceToPlane = abs(distanceToPlane);
        float distanceFromCenter = length(normalizedCoord.xy - _GizmoPosition.xy);
        float distanceToOrtho = DistanceToSplit(normalizedCoord, orthoPlane);

        float4 result = float4(0.0, 0.0, 0.0, 0.0);
        float side = 0.0;
        if (distanceToOrtho > 0.0)
        {
            result.rgb = _FirstViewColor.rgb;
            side = 1.0;
        }
        else
        {
            result.rgb = _SecondViewColor.rgb;
            side = -1.0;
        }

        result.a = 0.0;

        // "normal" gizmo
        float smoothing = 1.0;
        if (IsInsideGizmo(normalizedCoord, absDistanceToPlane, distanceFromCenter, side, orthoPlane, _GizmoCircleRadius.x, _GizmoThickness.x, smoothing, kAll))
        {
            result.a = 1.0 * smoothing;
        }

        // large gizmo when in translation mode
        if (IsInsideGizmo(normalizedCoord, absDistanceToPlane, distanceFromCenter, side, orthoPlane, _GizmoCircleRadius.y, _GizmoThickness.y, smoothing, _GizmoRenderMode))
        {
            result.a = max(result.a, 0.25 * smoothing);
        }

        // Blend factor selection disc
        float2 blendCircleCenter = _GizmoPosition.xy - _CompositingParams.x * orthoPlane.xy * _GetBlendFactorMaxGizmoDistance;
        float distanceToBlendCircle = length(normalizedCoord.xy - blendCircleCenter);
        if (distanceToBlendCircle < _BlendFactorCircleRadius)
        {
            float alpha = smoothstep(1.0, 0.6, distanceToBlendCircle / _BlendFactorCircleRadius);
            result = lerp(result, float4(1.0, 1.0, 1.0, alpha), alpha);
        }

        // Display transparent disc if near the center where the blend factor selection disc will automatically snap back
        if (abs(_CompositingParams.x) < _GizmoCircleRadius.y / _GetBlendFactorMaxGizmoDistance)
        {
            if (distanceFromCenter < _BlendFactorCircleRadius)
            {
                float alpha = smoothstep(1.0, 0.6, distanceFromCenter / _BlendFactorCircleRadius) * 0.75;
                result = lerp(result, float4(1.0, 1.0, 1.0, alpha), alpha);
            }
        }

        return result;
    }

    float GetZoneViewFeedbackCircleFactor(float2 normalizedCoord, float radius, float circleSize)
    {
        float distanceToCenter = abs(length(_GizmoZoneCenter.xy - normalizedCoord) - radius);
        return saturate((circleSize - distanceToCenter) / circleSize);
    }

    float ComputeBorderFactor(float borderSize, float2 screenPos, bool sideBySideView)
    {
        float4 borderSize4 = float4(borderSize, borderSize, borderSize, borderSize);
        float4 distanceToBorder = float4(screenPos.x, screenPos.y, abs(_ScreenRatio.z - screenPos.x), abs(_ScreenRatio.w - screenPos.y));

        float4 factors = saturate((borderSize4 - distanceToBorder) / borderSize4); // Lerp from 1.0 to 0.0 alpha from screen border to border size
        float factor = max(factors.x, max(factors.y, max(factors.z, factors.w)));

        // Add middle of the screen for side by side view
        if (sideBySideView)
        {
            float distanceToCenterLine = abs(_ScreenRatio.z * 0.5 - screenPos.x);
            float factorForCenterLine = saturate((borderSize - distanceToCenterLine) / borderSize);
            factor = max(factor, factorForCenterLine);
        }

        return factor;
    }

    float ComputeSelectedSideColorFactor(float side, float2 screenPos, float2 normalizedCoord, bool sideBySideView, bool zoneView)
    {
        float borderSize = 2.0;
        bool selectedSide = side * _CompositingParams.w > 0.0;

        float factor = ComputeBorderFactor(borderSize, screenPos, sideBySideView);

        // Add circle for zone view
        if (zoneView)
        {
            float selectionCircleFeedbackFactor = GetZoneViewFeedbackCircleFactor(normalizedCoord, _GizmoCircleRadius.y, 0.002);
            factor = max(factor, selectionCircleFeedbackFactor);
        }

        // If not on the selected side, make it more transparent
        if (!selectedSide)
        {
            factor = factor * 0.2;
        }

        return factor;
    }

    float4 ComputeDragColorFactor(float side, float2 screenPos, float2 normalizedCoord, bool sideBySideView, bool zoneView)
    {
        float factor = 0;
        float borderSize = 40.0;
        bool sideIsDragZone = (side > 0.0 && _CompositingParams2.x > 0.0) || (side < 0.0 && _CompositingParams2.x < 0.0);
        if (sideIsDragZone)
        {
            factor = ComputeBorderFactor(borderSize, screenPos, sideBySideView);

            // Add circle for zone view
            if (zoneView && side < 0.0)
            {
                float feedbackRadius = _GizmoLength * 2.0 * 0.3; // make it proprtional to selection zone
                factor = max(factor, GetZoneViewFeedbackCircleFactor(normalizedCoord, _GizmoLength * 2.0, feedbackRadius));
            }

            factor = pow(factor, 8) * 0.7; // Casimir magics values for optimum fadeout :)
        }

        return factor;
    }

    float4 ComputeFeedbackColor(float4 inputColor, float side, float2 screenPos, float2 normalizedCoord, bool sideBySideView, bool zoneView)
    {
        float factor = ComputeSelectedSideColorFactor(side, screenPos, normalizedCoord, sideBySideView, zoneView);
        factor = max(factor, ComputeDragColorFactor(side, screenPos, normalizedCoord, sideBySideView, zoneView));

        float4 result = float4(0.0, 0.0, 0.0, 0.0);
        if (side > 0.0)
        {
            result = lerp(inputColor, _FirstViewColor, factor);
        }
        else
        {
            result = lerp(inputColor, _SecondViewColor, factor);
        }

        return result;
    }

    v2f vert(appdata_t IN)
    {
        v2f OUT;
        OUT.vertex = UnityObjectToClipPos(IN.vertex);
        OUT.texcoord = TRANSFORM_TEX(IN.texcoord, _Tex0MainView);
        return OUT;
    }

    float3 evalCurve(float3 x, float A, float B, float C, float D, float E, float F)
    {
        return ((x*(A*x + C*B) + D*E) / (x*(A*x + B) + D*F)) - E / F;
    }

    float3 applyTonemapFilmicAD(float3 linearColor)
    {
        float blackRatio = InBlack / OutBlack;
        float whiteRatio = InWhite / OutWhite;

        // blend tunable coefficients
        float B = lerp(0.57, 0.37, blackRatio);
        float C = lerp(0.01, 0.24, whiteRatio);
        float D = lerp(0.02, 0.20, blackRatio);

        // constants
        float A = 0.2;
        float E = 0.02;
        float F = 0.30;

        // eval and correct for white point
        float3 whiteScale = 1.0f / evalCurve(WhiteLevel, A, B, C, D, E, F);
        float3 curr = evalCurve(linearColor *whiteScale, A, B, C, D, E, F);

        return curr*whiteScale;
    }

    float3 remapWhite(float3 inPixel, float whitePt)
    {
        //  var breakout for readability
        const float inBlack = 0;
        const float outBlack = 0;
        float inWhite = whitePt;
        const float outWhite = 1;

        // remap input range to output range
        float3 outPixel = ((inPixel.rgb) - inBlack.xxx) / (inWhite.xxx - inBlack.xxx) * (outWhite.xxx - outBlack.xxx) + outBlack.xxx;
        return (outPixel.rgb);
    }

    float3 NeutralTonemap(float3 x)
    {
        float3 finalColor = applyTonemapFilmicAD(x); // curve (dynamic coeffs differ per level)
        finalColor = remapWhite(finalColor, WhiteClip); // post-curve white point adjustment
        finalColor = saturate(finalColor);
        return finalColor;
    }

    float3 ApplyToneMap(float3 color)
    {
        if (_CompositingParams2.y > 0.0)
        {
            return NeutralTonemap(color);
        }
        else
        {
            return saturate(color);
        }
    }

    float3 ComputeColor(sampler2D texNormal, sampler2D texShadowMask, float shadowMultiplier, float4 shadowColor, float2 texcoord)
    {
        // Explanation of how this work:
        // To simulate the shadow of a directional light, we want to interpolate between two environments. One environment being the regular cubemap and the other a darkened version of the same cubemap
        // To create the lerp mask we render the scene with a white diffuse material and a single shadow casting directional light.
        // This will create a mask where the shadowed area is 0 and the lit area is 1 with a smooth NDotL transition in-between.
        // However, the DNotL will create an unwanted darkening of the scene (it's not actually part of the lighting equation)
        // so we sort it in order to avoid too much darkening.
        float3 color = tex2D(texNormal, texcoord).rgb;
        if (shadowMultiplier < 0.0)
        {
            // no need to composite as we do not want shadow in this case
            return color;
        }
        else
        {
            float3 shadowMask = sqrt(tex2D(texShadowMask, texcoord).rgb);
            return lerp(color * shadowColor.rgb * shadowMultiplier, color, saturate(shadowMask.r));
        }
    }

    ENDCG

    SubShader
    {
        Tags
        {
            "ForceSupported" = "True"
        }

        Lighting Off
        Cull Off
        ZTest Always
        ZWrite Off
        Blend One Zero

        // Single view 1
        Pass
        {
            CGPROGRAM
            #pragma fragment frag
            #pragma target 3.0

            float4 frag(float2 texcoord : TEXCOORD0,
            UNITY_VPOS_TYPE vpos : VPOS) : COLOR
            {
                float4 color = float4(ComputeColor(_Tex0MainView, _Tex0Shadows, ShadowMultiplier0, _ShadowColor0, texcoord) * exp2(ExposureValue1), 1.0);
                color.rgb = ApplyToneMap(color.rgb);
                color = ComputeFeedbackColor(color, 1.0, vpos.xy, float2(0.0, 0.0), false, false);
                return color;
            }
            ENDCG
        }

        // Single view 2
        Pass
        {
            CGPROGRAM
            #pragma fragment frag
            #pragma target 3.0

            float4 frag(float2 texcoord : TEXCOORD0,
                        UNITY_VPOS_TYPE vpos : VPOS) : COLOR
            {
                float4 color = float4(ComputeColor(_Tex1MainView, _Tex1Shadows, ShadowMultiplier1, _ShadowColor1, texcoord) * exp2(ExposureValue2), 1.0);
                color.rgb = ApplyToneMap(color.rgb);
                color = ComputeFeedbackColor(color, -1.0, vpos.xy, float2(0.0, 0.0), false, false);
                return color;
            }
            ENDCG
        }

        // split
        Pass
        {
            CGPROGRAM
            #pragma fragment frag
            #pragma target 3.0

            float4 frag(float2 texcoord : TEXCOORD0,
            UNITY_VPOS_TYPE vpos : VPOS) : COLOR
            {
                float3 color1 = ComputeColor(_Tex0MainView, _Tex0Shadows, ShadowMultiplier0, _ShadowColor0, texcoord) * exp2(ExposureValue1);
                float3 color2 = ComputeColor(_Tex1MainView, _Tex1Shadows, ShadowMultiplier1, _ShadowColor1, texcoord) * exp2(ExposureValue2);

                float2 normalizedCoord = ((texcoord * 2.0 - 1.0) * _ScreenRatio.xy);

                float side = DistanceToSplit(normalizedCoord, _GizmoSplitPlane) < 0.0f ? -1.0f : 1.0f;
                float blendFactor = 0.0f;
                if (side < 0.0)
                {
                    blendFactor = 1.0 - saturate(side * _CompositingParams.x);
                }
                else
                {
                    blendFactor = saturate(side * _CompositingParams.x);
                }

                float4 finalColor = float4(lerp(color1, color2, blendFactor), 1.0);
                finalColor.rgb = ApplyToneMap(finalColor.rgb);

                float4 gizmoColor = GetGizmoColor(normalizedCoord, _GizmoSplitPlane, _GizmoSplitPlaneOrtho);
                finalColor = lerp(finalColor, gizmoColor, gizmoColor.a);
                finalColor = ComputeFeedbackColor(finalColor, side, vpos.xy, float2(0.0, 0.0), false, false);

                return finalColor;
            }
            ENDCG
        }
    }
}
