#define MAX_BONES 100
#define FIXED_PIPELINE_TECHNIQUE 0xffffffffu

cbuffer LegacySkinnedFrame : register(b0)
{
    row_major float4x4 ViewProjection;
    row_major float4x4 World;
    row_major float4x4 Bones[MAX_BONES];
    float4 LightDirection;
    float4 LightColor;
    float4 AmbientColor;
    float4 EdgeColor;
    float OutlineWidth;
    uint ShaderTechnique;
    uint AlphaTestEnabled;
    uint AlphaFunction;
    float AlphaReference;
    float3 FramePadding;
};

Texture2D DiffuseTexture : register(t0);
Texture2D ToonTexture : register(t1);
SamplerState DiffuseSampler : register(s0);

struct VSInput
{
    float3 Position : POSITION0;
    float BlendWeight : BLENDWEIGHT0;
    uint4 BlendIndices : BLENDINDICES0;
    float3 Normal : NORMAL0;
    float2 TexCoord : TEXCOORD0;
};

struct VSOutput
{
    float4 Position : SV_POSITION;
    float3 Normal : NORMAL0;
    float2 TexCoord : TEXCOORD0;
    float ToonCoord : TEXCOORD1;
};

struct VSOutlineOutput
{
    float4 Position : SV_POSITION;
    float4 Color : COLOR0;
    float2 TexCoord : TEXCOORD0;
};

void SkinVertex(VSInput input, out float4 position, out float3 normal)
{
    const uint firstBone = min(input.BlendIndices.x, (uint)(MAX_BONES - 1));
    const uint secondBone = min(input.BlendIndices.y, (uint)(MAX_BONES - 1));
    const float firstWeight = saturate(input.BlendWeight);
    const float secondWeight = 1.0f - firstWeight;
    const float4 localPosition = float4(input.Position, 1.0f);
    position =
        mul(localPosition, Bones[firstBone]) * firstWeight +
        mul(localPosition, Bones[secondBone]) * secondWeight;
    normal =
        mul(input.Normal, (float3x3)Bones[firstBone]) * firstWeight +
        mul(input.Normal, (float3x3)Bones[secondBone]) * secondWeight;
}

VSOutput VSMain(VSInput input)
{
    VSOutput output;
    float4 skinnedPosition;
    float3 skinnedNormal;
    SkinVertex(input, skinnedPosition, skinnedNormal);
    output.Position = mul(skinnedPosition, ViewProjection);
    output.Normal = normalize(skinnedNormal);
    output.TexCoord = input.TexCoord;
    const float3 worldNormal = normalize(mul(skinnedNormal, (float3x3)World));
    output.ToonCoord = saturate(dot(worldNormal, normalize(LightDirection.xyz)));
    return output;
}

VSOutlineOutput VSOutline(VSInput input)
{
    VSOutlineOutput output;
    float4 skinnedPosition;
    float3 skinnedNormal;
    SkinVertex(input, skinnedPosition, skinnedNormal);
    output.Position = mul(skinnedPosition, ViewProjection);
    float4 projectedNormal = mul(float4(skinnedNormal, 0.0f), ViewProjection);
    projectedNormal = normalize(projectedNormal);
    output.Position += projectedNormal * OutlineWidth;
    output.Color = EdgeColor;
    output.TexCoord = input.TexCoord;
    return output;
}

bool PassesAlphaTest(float alpha)
{
    if (AlphaFunction == 1) return false;
    if (AlphaFunction == 2) return alpha < AlphaReference;
    if (AlphaFunction == 3) return alpha == AlphaReference;
    if (AlphaFunction == 4) return alpha <= AlphaReference;
    if (AlphaFunction == 5) return alpha > AlphaReference;
    if (AlphaFunction == 6) return alpha != AlphaReference;
    if (AlphaFunction == 7) return alpha >= AlphaReference;
    return true;
}

float4 PSMain(VSOutput input) : SV_TARGET
{
    const float4 diffuse = DiffuseTexture.Sample(DiffuseSampler, input.TexCoord);
    float4 color = diffuse;
    if (ShaderTechnique == FIXED_PIPELINE_TECHNIQUE)
    {
        const float lighting = saturate(dot(normalize(input.Normal), -normalize(LightDirection.xyz)));
        color *= AmbientColor + LightColor * lighting;
    }
    else
    {
        const bool cartoon = ShaderTechnique == 0 || ShaderTechnique == 1 || ShaderTechnique == 4 ||
                             ShaderTechnique == 5 || ShaderTechnique == 6 || ShaderTechnique == 7;
        if (cartoon)
        {
            const float3 toon = ToonTexture.Sample(DiffuseSampler, float2(input.ToonCoord, 0.0f)).rgb;
            const bool bright = ShaderTechnique == 6 || ShaderTechnique == 7;
            color.rgb = saturate(diffuse.rgb + toon - (bright ? 0.0f : 0.5f));
        }
    }
    if (AlphaTestEnabled != 0)
        clip(PassesAlphaTest(color.a) ? 1.0f : -1.0f);
    return color;
}

float4 PSOutline(VSOutlineOutput input) : SV_TARGET
{
    // Alpha character/equipment textures are often cards.  Discard their
    // transparent texels in the outline pass as D3D9 did, otherwise the
    // expanded back faces produce large black shapes around wings and hair.
    if (ShaderTechnique == 5 || ShaderTechnique == 7)
    {
        const float alpha = DiffuseTexture.Sample(DiffuseSampler, input.TexCoord).a;
        clip(PassesAlphaTest(alpha) ? 1.0f : -1.0f);
    }
    return input.Color;
}
