#version 130

const float e = 2.7182818284;
const float pi = 3.1415926;


float Phong_diffuse(vec3 l, vec3 n, vec3 h, vec3 v, float shininess) {
	return max(dot(n, l), 0.);
}

float Phong_specular(vec3 l, vec3 n, vec3 h, vec3 v, float shininess) {
	return pow(max(dot(n, h), 0.), shininess);
}


float Cook_Torrance_diffuse(vec3 l, vec3 n, vec3 h, vec3 v, float shininess) {
	return max(dot(n, l), 0.);
}

float Cook_Torrance_specular(vec3 l, vec3 n, vec3 h, vec3 v, float shininess) {
	float NdotL    = max( dot( n, l ), 0. );
	float NdotV    = max( dot( n, v ), 0. );
	float NdotH    = max( dot( n, h ), 1.e-7 );
	float VdotH    = max( dot( v, h ), 0. );
	float geometric = 2. * NdotH / VdotH;
	geometric = min(1., geometric * min(NdotV, NdotL));
	float r_sq = 1. / (shininess * shininess);
	float NdotH_sq = NdotH * NdotH;
	float NdotH_sq_r = 1. / (NdotH_sq * r_sq);
	float roughness_exp = (NdotH_sq - 1.) * (NdotH_sq_r);
	float roughness = exp(roughness_exp) * NdotH_sq_r / (4. * NdotH_sq);
	float fresnel = 1. / (1. + NdotV);
	return min(1., (fresnel * geometric * roughness) / (NdotV * NdotL + 1.e-7));
}


float Minnaert_diffuse(vec3 l, vec3 n, vec3 h, vec3 v, float shininess) {
    return max(dot(n, l), 0.);
}

float Minnaert_specular(vec3 l, vec3 n, vec3 h, vec3 v, float shininess) {
	float k = shininess / 128.;
	float d1 = pow(max(dot(n, l), 0.), 1. + k);
	float d2 = pow(1. - dot(n, v), 1. - k);
	return d1*d2;
}


float fresnel(float x, float kf) {
	float	dx  = x - kf;
	float	d1 = 1.0 - kf;
	float	kf2 = kf * kf;

	return (1.0 / (dx * dx) - 1.0 / kf2) / (1.0 / (d1 * d1) - 1.0 / kf2 );
	//return 1.0;
}

float shadow(float x, float ks) {
	float	dx  = x - ks;
	float	d1 = 1.0 - ks;
	float	ks2 = ks * ks;

	//return (1.0 / (dx * dx) - 1.0 / ks2) / (1.0 / (d1 * d1) - 1.0 / ks2 );
	return 1.0;
}

const	float	smooth_ = 0.5;
const	float	transp = 0;
const float	k  = 0.1;
const float kf = 1.12;
const float ks = 1.01;

float Strauss_diffuse(vec3 l, vec3 n, vec3 h, vec3 v, float shininess) {
	vec3	h2 = reflect(l, n);
	float	metal = shininess / 20.;
	float 	nl = dot( n, l);
	float 	nv = dot( n, v);
	float	hv = dot( h2, v);
	float	f  = fresnel( nl, kf );
	float	s3 = smooth_ * smooth_ * smooth_;

			// diffuse term
	float	d    = ( 1.0 - metal * smooth_ );
	float	Rd   = ( 1.0 - s3 ) * ( 1.0 - transp );
	float	diff = nl * d * Rd;

			// inputs into the specular term

			// composite the final result, ensuring

	return max(diff, 0.);
}


float Strauss_specular(vec3 l, vec3 n, vec3 h, vec3 v, float shininess) {
	vec3	h2 = reflect(l, n);
	float	metal = shininess / 20.;
	float 	nl = dot( n, l);
	float 	nv = dot( n, v);
	float	hv = dot( h2, v);
	float	f  = fresnel( nl, kf );
	float	s3 = smooth_ * smooth_ * smooth_;

			// diffuse term
	float	d    = ( 1.0 - metal * smooth_ );
	float	Rd   = ( 1.0 - s3 ) * ( 1.0 - transp );
	float	diff = nl * d * Rd;

			// inputs into the specular term
	float	r       = (1.0 - transp) - Rd;
	float	j       = f * shadow ( nl, ks ) * shadow ( nv, ks );
	float	refl = min ( 1.0, r + j * ( r + k ) );
	float 	Cs      = 1. + metal * (1.0 - f);
	float	spec    = Cs * refl;

	spec *= pow ( -hv, 3.0 / (1.0 - smooth_) );

			// composite the final result, ensuring

	return max(spec, 0.);
}

uniform float a, b;

float Oren_Nayar_diffuse(vec3 l, vec3 n, vec3 h, vec3 v, float shininess) {
	float nl    = dot ( n, l );
	float nv    = dot ( n, v );
	vec3  lProj = normalize ( l - n * nl );
	vec3  vProj = normalize ( v - n * nv );
	float cx    = max ( dot ( lProj, vProj ), 0.0 );

	float cosAlpha = nl > nv ? nl : nv;
	float cosBeta  = nl > nv ? nv : nl;
	float dx       = sqrt ( ( 1.0 - cosAlpha * cosAlpha ) * ( 1.0 - cosBeta * cosBeta ) ) / cosBeta;

	return max(0. , nl) * (a + b * cx * dx);
    //return max(dot(n, l), 0.);
}

float Oren_Nayar_specular(vec3 l, vec3 n, vec3 h, vec3 v, float shininess) {
	return pow(max(dot(n, h), 0.), shininess);
}

/*
float light_diffuse(int model, vec3 l, vec3 n, vec3 h, vec3 v, float shininess) {
	if (model == 0) return Phong_diffuse(l, n, h, v, shininess);
	if (model == 1) return Cook_Torrance_diffuse(l, n, h, v, shininess);
	if (model == 2) return Minnaert_diffuse(l, n, h, v, shininess);
	if (model == 3) return Strauss_diffuse(l, n, h, v, shininess);
	if (model == 4) return Oren_Nayar_diffuse(l, n, h, v, shininess);
	return 0.;
}

float light_specular(int model, vec3 l, vec3 n, vec3 h, vec3 v, float shininess) {
	if (model == 0) return Phong_specular(l, n, h, v, shininess);
	if (model == 1) return Cook_Torrance_specular(l, n, h, v, shininess);
	if (model == 2) return Minnaert_specular(l, n, h, v, shininess);
	if (model == 3) return Strauss_specular(l, n, h, v, shininess);
	if (model == 4) return Oren_Nayar_specular(l, n, h, v, shininess);
	return 0.;
}*/
