/* Computer Graphics, Assignment, Ray-tracing 1
 *
 * Student name .... Sander van Veen, Taddeüs Kroes
 * Student email ... sander@vo20.nl , taddeuskroes@gmail.com
 * Collegekaart .... 6167969        , 6054129 
 * Date ............
 * Comments ........
 */

#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include "shaders.h"
#include "v3math.h"
#include "intersection.h"
#include "scene.h"
#include "constants.h"

// shade_constant()
//
// Always return the same color. This shader does no real computations
// based on normal, light position, etc. As such, it merely creates
// a "silhouette" of an object.

vec3
shade_constant(intersection_point ip)
{
    return v3_create(1, 0, 0);
}

vec3
shade_matte(intersection_point ip)
{
	int i;

	float c = .0f,
	      dot;

	vec3 shade;

	for( i = 0; i < scene_num_lights; i++ )
	{
		shade = v3_normalize(v3_subtract(scene_lights[i].position, ip.p));
		dot = v3_dotprod(ip.n, shade);

		if( dot > .0f 
		    && !shadow_check(v3_add(ip.p, v3_multiply(shade, .001f)), shade) )
		{
			c += scene_lights[i].intensity * dot;
		}
	}

	c += scene_ambient_light;

	// Make sure c is in range [0, 1]
	if( c > 1.f )
		c = 1.f;
	
	return v3_create(c, c, c);
}

vec3
shade_blinn_phong(intersection_point ip)
{
	int i, alpha = 50;
	float c = .0f, dot, spec = .0f;
	vec3 shade;

	// Compute diffuse and specular illumination part
	for (i = 0; i < scene_num_lights; i++)
	{
		shade = v3_normalize(v3_subtract(scene_lights[i].position, ip.p));
		dot = v3_dotprod(ip.n, shade);

		if( dot > .0f
		    && !shadow_check(v3_add(ip.p, v3_multiply(shade, .001f)), shade) )
		{
			c += scene_lights[i].intensity * dot;
		}

		// Specular (color of a highlight)
		spec += scene_lights[i].intensity *
			pow(v3_dotprod(ip.n, v3_normalize(v3_add(ip.i, shade))), alpha);
	}

	// Apply k_d and k_s
	c *= .8f;
	spec *= .5f;

	c += scene_ambient_light;

	// Make sure color components are in [0, 1]
	spec = fmin(spec, 1.f);
	c = fmin(c + spec,  1.f);

	return v3_create(c, spec, spec);
}

vec3
shade_reflection(intersection_point ip)
{
	// Compute reflection vector also to prevent self-reflection. Add a
	// small bias to the surface point in the direction of the normal.
	vec3 r = v3_normalize(
	         v3_subtract(v3_multiply(ip.n, 2.f * v3_dotprod(ip.i, ip.n)), ip.i)
	     );
	
	r = ray_color(ip.ray_level + 1, v3_add(ip.p, v3_multiply(ip.n, .001f)), r);

	// Mix it with 75% matte shade
	return v3_add(v3_multiply(shade_matte(ip), .75f), v3_multiply(r, .25f));
}

// Returns the shaded color for the given point to shade.
// Calls the relevant shading function based on the material index.
vec3
shade(intersection_point ip)
{
	switch (ip.material)
	{
        case 0:
            return shade_constant(ip);
		case 1:
			return shade_matte(ip);
		case 2:
			return shade_blinn_phong(ip);
		case 3:
			return shade_reflection(ip);
        default:
            return shade_constant(ip);
	}
}

// Determine the surface color for the first object intersected by
// the given ray, or return the scene background color when no
// intersection is found
vec3
ray_color(int level, vec3 ray_origin, vec3 ray_direction)
{
    intersection_point  ip;

    // If this ray has been reflected too many times, simply
    // return the background color.
    if (level >= 3)
        return scene_background_color;

    // Check if the ray intersects anything in the scene
    if (find_first_intersection(&ip, ray_origin, ray_direction))
    {
        // Shade the found intersection point
        ip.ray_level = level;
        return shade(ip);
    }

    // Nothing was hit, return background color
    return scene_background_color;
}
