//  ###### NO_VARIATIONS ###### <= this is a hint to the FX re-compiler, don't delete it

//#define TRIVIAL

#define RING_DISPLACEMENT_IN_VERTEX
#define EDGE_DETECT

#define EDGE_PUSH_OUT_MAX             5
#define EDGE_PULL_IN_MAX              9
#define EDGE_WEIGHT_FOR_MAX_PUSH_OUT  0.6
#define EDGE_WEIGHT_FOR_MAX_PULL_IN   1.0
#define EDGE_SPATIAL_FREQUENCY        8    // e.g. as this changes, tweak 'EDGE_WEIGHT_FOR_MAX_PUSH_OUT' to get good magnitude of effect
#define EDGE_TEMPORAL_FREQUENCY       4
#define BLUR_THRESHHOLD_FOR_PUSH_OUT  0.2

#define MITIGATION 1 
#define MITIGATION2 0.35

// setting this may or may not affect the tendency to see bright-red blobs at 8x rewind
// alone the edges of rewind-exempt stuff... need to test to see which way is better
// #define DESAT_DISPLACED

struct VS_OUTPUT {
    float4 position   : POSITION;
    float2 uv         : TEXCOORD0;
    float2 clamp      : TEXCOORD1;
    float2 uv_scroll  : TEXCOORD2;
    float3 noise_tc   : TEXCOORD3;
    float3 exempt_tc  : TEXCOORD4;

#ifdef RING_DISPLACEMENT_IN_VERTEX
    float3 ring_uv    : TEXCOORD5;
    float4 colors     : COLOR0;
#endif
};

float4x4 object_to_proj_matrix;

float2 texel_size;
float4 displace_time;
// .x = displace time
// .y = rewind_smoother
// .z = desaturation
// .w = flashing

float4 ring_fx;
// .x = ring location x
// .y = ring location y
// .z = ring effect strength
// .w = ring time

float4 more_fx;
// .x = scrolling offset x
// .y = scrolling offset y
// .z = edge time effect (basically a wall-time clock)
// .w = globals.interpolated_rewind_magnitude

float4 fade_out;

// rotation matrix for volume texture
float4 volume_s, volume_t, volume_u;
texture diffuse_texture;

// main screen data
sampler DiffuseTextureSampler = 
sampler_state {
    Texture = <diffuse_texture>;    
    MipFilter = NONE;
    MinFilter = LINEAR;
    MagFilter = LINEAR;
    AddressU = Clamp;
    AddressV = Clamp;
};

struct PS_OUTPUT {
    float4 color : COLOR0;  // Pixel color    
};

texture rewind_texture;
texture rewind_blurred;

// rewind exemption data
sampler RewindTextureSampler = 
sampler_state {
    Texture = <rewind_texture>;    
    MipFilter = NONE;
    MinFilter = LINEAR;
    MagFilter = LINEAR;
    AddressU = Clamp;
    AddressV = Clamp;
};

// rewind exemption data blurred, plus gradients
sampler RewindBlurredSampler = 
sampler_state {
    Texture = <rewind_blurred>;
    MipFilter = NONE;
    MinFilter = LINEAR;
    MagFilter = LINEAR;
    AddressU = Clamp;
    AddressV = Clamp;
};

texture3D fx_volume;
sampler3D FxVolumeSampler =
sampler_state {
	Texture = <fx_volume>;
	MipFilter = NONE;
	MinFilter = LINEAR;
	MagFilter = LINEAR;
	AddressU = Wrap;
	AddressV = Wrap;
	AddressW = Wrap;
};

float3 volume_tc(float u, float v, float time)
{
    float3 base = { u,v,time };
    float3 result = { dot(base, volume_s), dot(base, volume_t), dot(base, volume_u) };
    return result;
}

VS_OUTPUT vertex_shader(float4 input_position : POSITION, 
                        float2 uv: TEXCOORD0,
                        float2 uv2: TEXCOORD1,
                        float2 uv3: TEXCOORD2)
{
    VS_OUTPUT output;
    output.position = mul(input_position, object_to_proj_matrix);
    output.uv = uv;
    output.clamp = uv2;

    float aspect_ratio = texel_size.x / texel_size.y;

    float2 uvs = uv - more_fx.xy;
    output.uv_scroll = uvs;

#ifdef RING_DISPLACEMENT_IN_VERTEX
    if (ring_fx.z) {
        float2 ring_displacement_offset;
        float  ring_displacement_strength, ring_displacement_light;
        float  ring_displacement_weight;
        
        // compute distance to ring and vector away from it
        float2 center = ring_fx.xy;
        float2 dir = uv - center;
        dir.y *= aspect_ratio;
        float dist2 = dot(dir,dir);
        dir /= sqrt(dist2);
        
        // clamp distance to edge of bubble
        dist2 = max(dist2, 0.0105);
        
        // basic frequency/wavelength of rings at distance (gets scaled)
        float freq = pow(dist2,0.18);
        
        // compute everything for the ring
        ring_displacement_offset = dir * texel_size;
        ring_displacement_strength = sin(freq * 80 - ring_fx.w*4)/2+0.5;
        ring_displacement_weight = clamp(ring_fx.z*100,0,1);
        ring_displacement_strength *= 2/(1+dist2*4) * ring_fx.z;
        ring_displacement_light = sin(freq* 160 - ring_fx.w*4)/2 + 0.5;
        ring_displacement_light *= ring_fx.z * 0.03;
        ring_displacement_light *= 1/(1+dist2*2);
        
        output.ring_uv.xy = ring_displacement_offset * ring_displacement_strength;
        output.ring_uv.z  = ring_displacement_weight;
        output.colors = 0;
        output.colors.r = ring_displacement_light;
    } else {
        output.ring_uv = 0;
        output.colors = 0;
    }
#endif

    float  perpixel_displace_speed = 0.125;
    float  volume_sample_scale_x = 11;
    float  volume_sample_scale_y = 11;
    
    output.noise_tc = volume_tc(0.3 + volume_sample_scale_x*uvs.x,
                                0.1 + volume_sample_scale_y*uvs.y * aspect_ratio,
                                displace_time.x * perpixel_displace_speed);
    

    float exempt_noise_speed = 0.5;
    output.exempt_tc = volume_tc(20*uvs.x, 20*uvs.y * aspect_ratio, more_fx.z * exempt_noise_speed);

    return output;
}

float3 desaturate(float3 color, float strength)
{
    float3 rgb_to_y = { 0.299, 0.587, 0.114 };
    float desat = dot(color, rgb_to_y);
    return lerp(color, desat, strength);
}

float edge_detect_occluded(sampler tex, float2 tc, float2 off)
{
	// robert's cross
    float2 sample1 = tex2D(tex, tc + float2(-off.x,-off.y)).rg;
    float2 sample2 = tex2D(tex, tc + float2( off.x, off.y)).rg;
    float2 sample3 = tex2D(tex, tc + float2(-off.x, off.y)).rg;
    float2 sample4 = tex2D(tex, tc + float2( off.x,-off.y)).rg;

    float2 test1 = sample1 - sample2;
    float2 test2 = sample3 - sample4;

    test1 = abs(test1);
    test2 = abs(test2);
    
    // now, we have to be tricky about how to combine them, which is why this
    // isn't quite general purpose
    
    // now, an r edge is an edge visible even after occlusion
    // a g edge is an edge visible without occlusion
    // we only want edges that are visible for both
    test1 = min(test1.r, test1.g);
    test2 = min(test2.r, test2.g);
    
    float edge = (test1.r + test2.r)/2;
    return clamp(edge,0,1);
}

#ifdef TRIVIAL
PS_OUTPUT pixel_shader(VS_OUTPUT input)
{
	PS_OUTPUT output;
	output.color = float4(1,0,1,0);//tex2D(DiffuseTextureSampler, input.uv);
	return output;
}
#else
PS_OUTPUT pixel_shader(VS_OUTPUT input)
{
    // general setup and constants
    
    PS_OUTPUT output;
    float2 uv = input.uv;
    float aspect_ratio = texel_size.x / texel_size.y;

    // compute coordinates displaced by the global scrolling, for generating world-space fx
    float2 uvs = input.uv_scroll;//uv - more_fx.xy;

    ///////////////////////////////////////////////////////////////////////
    //
    //   compute the non-displaced sample
    //
    
    float4 texture_color    = tex2D(DiffuseTextureSampler, input.uv);
    float4 rewind_level     = tex2D(RewindTextureSampler, input.uv);

    ///////////////////////////////////////////////////////////////////////
    //
    //   compute the effect of the ring
    //

#ifdef RING_DISPLACEMENT_IN_VERTEX
    float3 ring_displacement_offset = input.ring_uv;
    float ring_displacement_light = input.colors.r;
#else
    float3 ring_displacement_offset;
    float  ring_displacement_light;

	// compute distance to ring and vector away from it
    float2 center = ring_fx.xy;
    float2 dir = uv - center;
    dir.y *= aspect_ratio;
    float dist2 = dot(dir,dir);
    dir /= sqrt(dist2);

    // clamp distance to edge of bubble
    dist2 = max(dist2, 0.0105);

    // basic frequency/wavelength of rings at distance (gets scaled)
    float freq = pow(dist2,0.18);
    
    // compute everything for the ring
    ring_displacement_offset.xy = dir * texel_size;
    ring_displacement_offset.z = 0;
    ring_displacement_offset *= sin(freq * 80 - ring_fx.w*4)/2+0.5;
    ring_displacement_offset *= 2/(1+dist2*4);
    ring_displacement_light = sin(freq*160 - ring_fx.w*4)/2 + 0.5;
    ring_displacement_light *= ring_fx.z * 0.03;
    ring_displacement_light *= 1/(1+dist2*2);
    ring_displacement_offset.z = clamp(ring_fx.z*100,0,1);
#endif


    ////////////////////////////////////////////////////////////////////////
    // 
    //  compute the rewind displacement
    //

    float3 rewind_displacement_offset;
    float  rewind_displacement_strength;
    float  rewind_displacement_weight;

    // sample per-pixel noise to use as the displacement
    
    // compute a per-pixel displacement
    float2 noise2 = tex3D(FxVolumeSampler, input.noise_tc).ra;
    
    noise2 = (noise2 * 2) - 1;   // rescale to -1 .. 1

    float2 perpixel_displacement = noise2 * texel_size;
    float perpixel_strength = 12 * displace_time.y * displace_time.y * MITIGATION;

    // bias to be more horizontal
    float m = more_fx.w;
    m = (m * m) / 4.0f;
    perpixel_displacement.x *= MITIGATION2 * 1.5 * perpixel_strength * (m + 1) * (1 / 12.0);
    perpixel_displacement.y *= MITIGATION2 * 0.3;

    // compute the complete rewind displacement
    
    rewind_displacement_offset.z = 0;
    rewind_displacement_offset.xy = perpixel_displacement * perpixel_strength;

    ////////////////////////////////////////////////////////////////////////
    // 
    //  compute the displacement due to rewind-exempt edges
    //

    float3 edge_info = tex2D(RewindBlurredSampler, input.uv);
    float2 edge_displacement_offset=0;
    float edge_displacement_coloration=0;
    float edge_displacement_weight=0;
    
    if (edge_info.r) {
        float2 edge_dir;
        edge_dir = edge_info.gb;
        
        edge_displacement_offset.xy = (edge_info.gb*2-1)*texel_size;
        
        float edge_dist = edge_info.r;
        float edge_displacement_wave_raw = sin(edge_dist * EDGE_SPATIAL_FREQUENCY + more_fx.z*EDGE_TEMPORAL_FREQUENCY);
        float edge_displacement_wave = (edge_displacement_wave_raw + 1)/2; // remap 0..1

        float2 edge_stuff;
        edge_stuff = lerp(
                 float2(EDGE_PUSH_OUT_MAX, EDGE_WEIGHT_FOR_MAX_PUSH_OUT),
                 float2(-EDGE_PULL_IN_MAX, EDGE_WEIGHT_FOR_MAX_PULL_IN),
                 edge_displacement_wave);

        edge_displacement_offset *= edge_stuff.x;
        
        edge_displacement_weight = clamp(edge_info.r * 20,0,1);
        edge_displacement_weight *= clamp(edge_info.r*2,0,1);

        // note, it's important to do both of these... the first suppresses the request for displacement,
        // the second makes sure if some OTHER displacement is requested, we don't still try to do this displacement...
        // if we didn't it would be bad e.g. when this displacement is suppressed due to occlusion, but then we rewind
        edge_displacement_weight *= (1 - rewind_level.g); // don't displace "edge" inside object!
        //edge_displacement_offset *= (1 - rewind_level.g); // don't displace "edge" inside object!

        edge_displacement_offset *= edge_displacement_weight;

        edge_displacement_coloration = (1+edge_displacement_wave)/2 * 0.25 * edge_displacement_weight;
        edge_displacement_weight *= edge_stuff.y; // reduce weight if we're pulling in
        if (edge_info.r < BLUR_THRESHHOLD_FOR_PUSH_OUT)
           edge_displacement_weight *= clamp(edge_displacement_wave_raw,0,1);
    }
	
    ////////////////////////////////////////////////////////////////////////
    // 
    //  compute the final displacement and sample it
    //

    float2 displace = rewind_displacement_offset + ring_displacement_offset + edge_displacement_offset;

    displace *= input.clamp; // clamp to not go offscreen; this is better than texture clamping,
                             // since it's applied smoothly as you approach the edge

    float2 displaced_uv = uv + displace;

    float4 displaced_color  = tex2D(DiffuseTextureSampler, displaced_uv);
    float4 displaced_rewind = tex2D(RewindTextureSampler, displaced_uv);
    
    ///////////////////////////////////////////////////////////////
    //
    //   choose between the displaced pixel and the non-displaced pixel
    //   (if rewind-exempt, then prefer non-displaced)
    //
    //   also, if rewinding, desaturate the displaced pixel
    
    float rewindable = 1 - rewind_level.b;

    // compute the preference for the displaced sample: only if
    // origin pixel is 0 AND displaced is 0
    float displaced = rewindable * (1-displaced_rewind.b);

    // minimize ghosting along non-rewindable boundaries
    displaced *= displaced;

#ifdef DESAT_DISPLACED
    displaced_color.rgb = desaturate(displaced_color, rewindable * displace_time.z);
#endif

    rewind_displacement_weight = displace_time.y * displaced;  // move to after min(sum) to prevent tearing away
    float ring_plus_rewind = rewind_displacement_weight;
    if (edge_displacement_weight >= 0.0001) ring_plus_rewind = 0;
    float ring_scaler = 1 - edge_displacement_weight;
    ring_plus_rewind += ring_displacement_offset.z * ring_scaler*ring_scaler;
    float displace_weight = min(ring_plus_rewind + edge_displacement_weight, 1);
    float4 final_color = lerp(texture_color, displaced_color, displace_weight);

    // desaturate displaced color
#ifndef DESAT_DISPLACED
    final_color.rgb = desaturate(final_color, rewindable*displace_time.z);
#endif

    ////////////////////////////////////////////////////////////////////////
    // 
    //  highlight the edges of displaced objects
    //

#ifdef EDGE_DETECT
    // check if we're in blurred region; if not, can't be edge
    if (edge_info.r) {
        float2 rc_off = 2.4 * texel_size;
        float edge = edge_detect_occluded(RewindTextureSampler, input.uv, rc_off);

        // tail off the effect in a nicer way
        edge *= edge;

        float exempt_noise = tex3D(FxVolumeSampler, input.exempt_tc).g;

    #if 1    // this is done in the texture now
        // run through some formulas to make it look nicer
        exempt_noise = clamp(exempt_noise*3-1,0,1);
        exempt_noise = clamp(exempt_noise*exempt_noise*2,0,1);
        exempt_noise = clamp(exempt_noise*2-1,0,0.75);
    #endif

        // apply only to the edges
        edge *= exempt_noise;

        float4 edge_color = fade_out * float4(0.3, 1, 0.3, 1);
        final_color = lerp(final_color, edge_color, edge);
//final_color = float4(1,0,1,0);
    }
#endif

    // apply the greening effect for the edge-displacement distortion
    float4 green_color = fade_out * float4(0.3, 1, 0.3, 1);
    output.color = lerp(final_color, green_color, edge_displacement_coloration);

    // apply the lightening effect for the ring
    output.color.rgb += ring_displacement_light;

#ifdef SHOW_MRT
    // this shows MRT-2 in r,g, and MRT-1 in monochrom in b for debugging
    texture_color.b = dot(texture_color.rgb, float3(0.299, 0.587, 0.114));
    texture_color.rg = rewind_level.rg;
    output.color.rgb = texture_color;
#endif

#ifdef SHOW_BLUR
    output.color.b = dot(output.color.rgb, float3(0.299, 0.587, 0.114));
	output.color.rg = tex2D(RewindBlurredSampler, input.uv);
#endif

#if 0
    output.color.g = dot(output.color.rgb, float3(0.299, 0.587, 0.114))/2;
    output.color.r = rewind_level.r;
    output.color.b = rewind_level.b;
#endif
	
    return output;
}
#endif

technique render {
    pass P0 {
        VertexShader = compile vs_3_0 vertex_shader();
        PixelShader  = compile ps_3_0 pixel_shader(); // @TODO: this went to 3_0 to give more freedom in the pixel shader, but move it back to 2_0 once we're done

        AlphaBlendEnable = False;
        AlphaTestEnable = False;

        CullMode = CW;  // allow mesh to invert and see what that looks like... answer: lame
        ZEnable = False;
        ZWriteEnable = False;
    }
}


#if 0

// old code just in case
#if VARIATION == 6
    output.color = float4(0,0, tex3D(FxVolumeSampler, volume_tc(3*uv.x, 3*uv.y * aspect_ratio, displace_time.x / 5)).b, 1);
#endif

#if VARIATION == 7
    output.color = float4(0, tex3D(FxVolumeSampler, volume_tc(6*uv.x, 6*uv.y * aspect_ratio, displace_time.x / 4)).g, 0, 1);
#endif

#if VARIATION == 8
    noise = tex3D(FxVolumeSampler, volume_tc(6*uv.x, 6*uv.y * aspect_ratio, displace_time.x / 4)).g;
    noise = noise*2-1;
    noise *= 1.5;
    noise = clamp(noise,-1,1);
    noise = (noise+1)/2;
    output.color = float4(0, noise, 0, 1);
#endif

#if VARIATION == 9
    noise = tex3D(FxVolumeSampler, volume_tc(6*uv.x, 6*uv.y * aspect_ratio, displace_time.x / 4)).g;
    noise = clamp(noise*3-1,0,1);
    noise = clamp(noise*noise*2,0,1);
    noise = clamp(noise*2-1,0,1);
    output.color = float4(0, noise, 0, 1);
#endif

#if VARIATION == 5
    float a = atan2(dir.y,dir.x);
    rewind_displacement_strength = sin(pow(dist,0.75) * 80 - displace_time.x*15 + a*20) / 2 + 0.5;
    rewind_displacement_strength *= lerp(1,0.25,1/(dist*16 + 1));
#endif

#if VARIATION == 11
    float noise1 = tex3D(FxVolumeSampler, volume_tc(1.3*uvs.x, 1.3*uvs.y * aspect_ratio, displace_time.x / 10)).g;
    noise1 = clamp(noise1*2-0.5,0,1);
    //noise = clamp(noise1*noise1*2,0,1);
    noise1 = clamp(noise1*2-0.5,0,10);
    noise1 = lerp(0.15,1, noise1);
    final_displace_strength *= noise1;
#endif

#if VARIATION == 2
    float blob = tex3D(FxVolumeSampler, volume_tc(3*uvs.x, 3*uvs.y * aspect_ratio, displace_time.x / 8)).b;
    blob = tex3D(FxVolumeSampler, volume_tc(1.5*uvs.x, 1.5*uvs.y * aspect_ratio, displace_time.x / 16)).b;
    float2 offset = (tex3D(FxVolumeSampler, volume_tc(uvs.x/2,uvs.y/2*aspect_ratio, displace_time.x / 16)).ra*2-1);
    float2 dsample = input.uv + offset/16 * displace_time.y;
    float4 inverted = tex2D(DiffuseTextureSampler, dsample);
    float4 inv_rewind = 1-tex2D(RewindTextureSampler, dsample);
    displaced_color.rgb = lerp(displaced_color, inverted, clamp(blob*4,0,1)*inv_rewind);
#endif
#endif
