#include "scene_elements.h"

SceneElement::SceneElement(string str) {
	name = new string(str);
}
SceneElement::~SceneElement() {
	delete name;
}


Camera::Camera() : SceneElement("Camera 1") {
	position[0]=0.0; position[1]=0.0; position[2]=0.0;
	center[0]=0.0; center[1]=0.0; center[2]=-1.0;
	up[0]=0.0; up[1]=1.0; up[2]=0.0;
	aperture = 0.0;
	image_plane = 1.0;
	recip_focal_length = MAX_DOUBLE; // = INF
	aperture_samples = 1;
}


Image::Image() : SceneElement("Ray Traced Image 1") {
	width = 400;
	height = 100;
	samples = 1;
}


void Material::init(void) {
	for (int i=0;i<NUM_POW_TABLE;++i) {
		pow_table[i] = pow((double)(i)/(double)(NUM_POW_TABLE-1),shininess);
	}
}
Material::Material(string name) : SceneElement(name) {
	for (int i=0;i<3;++i) {
		ambient[i] = diffuse[i] = specular[i] = 0.0;
	}
	shininess = 0.0;
	to_shadow = new map<Light*,int>();
	reflect = refract = false;
	samples_reflection = samples_refraction = 0;
	exponent_refractive = 1.0;
	texture = NULL;
}
Material::~Material() {
	delete to_shadow;
	if (texture!=NULL) SDL_FreeSurface(texture);
}


Object::Object(char type, string name) : SceneElement(name) {
	position[0]=position[1]=position[2]=0.0;

	material = new Material("Default Material");
	_has_default_material = true;

	this->type = type;
}
Object::~Object() {
	if (_has_default_material) delete material;
}


Point::Point() : Object(SCENE_OBJECT_TYPE_POINT,"Point") {}


Sphere::Sphere() : Object(SCENE_OBJECT_TYPE_SPHERE,"Sphere") {
	radius = 1.0;
}


Plane::Plane() : Object(SCENE_OBJECT_TYPE_PLANE,"Plane") {
	normal[0] = 0.0;
	normal[1] = 1.0;
	normal[2] = 0.0;
}


void Triangle::_bary_init(void) {
	glLibMathVec::subtract3(position2,position,_bary_vec0);
	glLibMathVec::subtract3(position3,position,_bary_vec1);
	_bary_dot00 = glLibMathVec::dot3(_bary_vec0,_bary_vec0);
	_bary_dot01 = glLibMathVec::dot3(_bary_vec0,_bary_vec1);
	_bary_dot11 = glLibMathVec::dot3(_bary_vec1,_bary_vec1);
}
void Triangle::init(void) {
	_bary_init();
	if (_must_calculate_normal) {
		glLibMathVec::cross(_bary_vec0,_bary_vec1,normal1);
		glLibMathVec::normalize3(normal3);
		glLibMathVec::copy3(normal1,normal2);
		glLibMathVec::copy3(normal1,normal3);
	}
}
void Triangle::barycentric(double hit_position[3], double quan1[], double quan2[], double quan3[], double result[], int length) {
	//Plane interpolation (probably barycentric interpolation)
	glLibMathVec::subtract3(hit_position,position,_bary_vec2);
	//Compute dots
	_bary_dot02 = glLibMathVec::dot3(_bary_vec0,_bary_vec2);
	_bary_dot12 = glLibMathVec::dot3(_bary_vec1,_bary_vec2);
	//Compute barycentric coordinates
	double inv_denom = 1.0 / (_bary_dot00 * _bary_dot11 - _bary_dot01 * _bary_dot01);
	double u = (_bary_dot11 * _bary_dot02 - _bary_dot01 * _bary_dot12) * inv_denom;
	double v = (_bary_dot00 * _bary_dot12 - _bary_dot01 * _bary_dot02) * inv_denom;
	//Interpolate
	glLibMathVec::copy(quan1,result,length);
	glLibMathVec::subtract(quan2,quan1,_bary_value_vec0,length);
	glLibMathVec::subtract(quan3,quan1,_bary_value_vec1,length);
	glLibMathVec::scaleadd(u,_bary_value_vec0,result,result,length);
	glLibMathVec::scaleadd(v,_bary_value_vec1,result,result,length);
}
Triangle::Triangle() : Object(SCENE_OBJECT_TYPE_TRIANGLE,"Triangle") {
	glLibMathVec::zero3(position2);
	glLibMathVec::zero3(position3);

	glLibMathVec::zero3(normal1);
	glLibMathVec::zero3(normal2);
	glLibMathVec::zero3(normal3);

	glLibMathVec::zero2(uv1);
	glLibMathVec::zero2(uv2);
	glLibMathVec::zero2(uv3);

	_must_calculate_normal = true;
}


void Light::init(void) {
	for (int i=0;i<NUM_RANDOM_SAMPLES;++i) {
		//http://www.cgafaq.info/wiki/Uniform_random_points_on_sphere
		double z = glLibMathRandom::get_uniform(-1.0,1.0);
		double t = glLibMathRandom::get_uniform(0.0,2.0*pi);
		double r = pow(1.0-z*z,0.5);
		_random_sphere_points[3*i] = r*cos(t);
		_random_sphere_points[3*i+1] = r*sin(t);
		_random_sphere_points[3*i+2] = z;
	}
}

void Light::get_random_point(double point[3]) {
	switch (primitive->type) {
		case    SCENE_OBJECT_TYPE_POINT: {
			glLibMathVec::copy3(primitive->position,point);
			break;
		}
		case   SCENE_OBJECT_TYPE_SPHERE: {
			int i = rand() % NUM_RANDOM_SAMPLES; //32767 is RAND_MAX, so this could be close
			glLibMathVec::copy3(_random_sphere_points+3*i,point);
			glLibMathVec::scale3(((Sphere*)(primitive))->radius,point);
			glLibMathVec::add3(primitive->position,point,point);
			break;
		}
		case    SCENE_OBJECT_TYPE_PLANE: {
			throw glLibError("Not implemented!");
			break;
		}
		case SCENE_OBJECT_TYPE_TRIANGLE: {
			throw glLibError("Not implemented!");
			break;
		}
		default: {
			throw glLibError("Not implemented!");
			break;
		}
	}
}

Light::Light(string name) : SceneElement(name) {
	primitive = new Point();
	for (int i=0;i<3;++i) this->color[i] = 1.0;
	_is_default = true;
}
Light::~Light() {
	delete primitive;
}
double Light::_random_sphere_points[NUM_RANDOM_SAMPLES*3];