// -*- mode: C; -*-
#version 110

varying vec3 viewerdir;
varying vec3 lightdir;
varying vec3 normal;
varying vec3 foamtexcoord;
#ifdef HQ_SFX
varying vec3 realcoordinates;
#else
varying vec4 reflectiontexcoord;	// x,y,w
#endif
varying vec4 foamamounttexcoord;	// x,y,w
varying vec2 noise_texc_0;
varying vec2 noise_texc_1;

uniform sampler2D tex_normal;		// normal map, RGB
uniform sampler2D tex_reflection;	// reflection, RGB
uniform sampler2D tex_foam;		// foam texture (tileable)
uniform sampler2D tex_foamamount;	// amount of foam per pixel

#ifdef HQ_SFX
uniform mat4 reflection_mvp;
const float virtualplane_height = 12.0;
#endif

const float water_shininess = 140.0;

void main()
{
	// compute normal vector
	vec3 N0 = vec3(texture2D(tex_normal, noise_texc_0) * 2.0 - 1.0);
	vec3 N1 = vec3(texture2D(tex_normal, noise_texc_1) * 2.0 - 1.0);
	vec3 N = normalize(normal+N0+N1);

	// compute direction to viewer
	vec3 E = normalize(viewerdir);

	// compute direction to light source
	vec3 L = normalize(lightdir);

	// half vector
	vec3 H = normalize(L + E);

	// store E, N dot product, we use it in fresnel and refraction factor
	float doten = dot(E, N);

	// compute fresnel term. we need to clamp because the product could be negative
	// when the angle between face normal and viewer is nearly perpendicular.
	// using abs gives ugly ring-like effects, but direct clamp leads to large areas with maximum
	// reflectivity. We need to tweak E in the vertex shader, so that this term can't be negative,
	// as work-around... fixme.
	// IDEA: if we would use a texture map as lookup for the fresnel term, we can avoid this:
	// - texture lookup could be faster!
	// - use a texture with clamping, but allow coordinate < 0 (by scaling, then clamp)
	// - fresnel term is 1/((x+1)^8), so fresnel(0)=1, BUT: fresnel(x) for x < 0,
	//   is erratic/noisy (smoothed) between 0 and 1, simulating rough water.
	//   That way the rings disappear,
	//   and are replaced by additional surface detail.
	// new result: without the abs() around dot(), it doesn't look worse. so keep out the abs()
	// this also means the case that E*N < 0 is not so problematic. So we don't need the lookup
	// texture.
	float fresnel = clamp(doten, 0.0, 1.0) + 1.0;
	// approximation for fresnel term is 1/((x+1)^8)
#if 1
	// using pow() seems a little bit faster (Geforce5700)
	fresnel = pow(fresnel, -8.0) * 0.8;
#else
	fresnel = fresnel * fresnel;	// ^2
	fresnel = fresnel * fresnel;	// ^4
	fresnel = fresnel * fresnel;	// ^8
	fresnel = (1.0/fresnel) * 0.8;	// never use full reflectivity, at most 0.8
	// that multiplication greatly increases the realism of the appearance!
#endif

	// possible optimization: make a 2d lookup texture, one dimension with fresnel term,
	// second dimension with specular term. Spares two pow() instructions. But we already used
	// all four texture units. Doubtful that performance would be higher...

	// compute specular light brightness (blinn shading with specular falloff/size control)
	vec3 specular_color = vec3(gl_LightSource[0].diffuse)
		* pow(max(0.0, dot(N, H)), water_shininess) * 6.0;

	// compute refraction color
	// float dl = max( dot( L N), 0.0);
	float dl = max(dot(L, N) * abs(1.0 - max(doten, 0.2)), 0.0);
	vec3 refractioncol = vec3(gl_Color) * dl;

	// mix reflection and refraction (upwelling) color, and add specular color
#ifdef HQ_SFX
	vec3 texc = N * (virtualplane_height * N.z) + realcoordinates;
	texc.z -= virtualplane_height;
	vec4 reflectiontexcoord = reflection_mvp * vec4(texc, 1.0);
#endif
	vec3 water_color = mix(refractioncol, vec3(texture2DProj(tex_reflection, reflectiontexcoord)),
			       fresnel) + specular_color;

	// fetch amount of foam, sum of texture and cresnel foam, multiplied with luminance
	// from foam texmap
	float foam_amount = min(texture2DProj(tex_foamamount, foamamounttexcoord).x
				+ foamtexcoord.z /*crest_foam*/, 1.0)
		* texture2D(tex_foam, foamtexcoord.xy).x;

	vec3 final_color = mix(water_color, vec3(gl_LightSource[0].diffuse), foam_amount);

	// add linear fog
	float fog_factor = clamp((gl_Fog.end - gl_FogFragCoord) * gl_Fog.scale, 0.0, 1.0);

	// output color is a mix between fog and final color
	gl_FragColor = vec4(mix(vec3(gl_Fog.color), final_color, fog_factor), 1.0);
}
