/*
 * scene.h: classes for objects in scene
 *
 * This file is part of Pyrit Ray Tracer.
 *
 * Copyright 2006, 2007, 2008  Radek Brich
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#ifndef SCENE_H
#define SCENE_H

#include <vector>
#include <typeinfo>

#include "sampler.h"
#include "noise.h"
#include "vector.h"
#include "quaternion.h"

/*
triangle intersection alghoritm
chooses are:
TRI_PLUCKER
TRI_BARI
TRI_BARI_PRE
*/
#if !defined(TRI_PLUCKER) && !defined(TRI_BARI) && !defined(TRI_BARI_PRE)
#	define TRI_BARI_PRE
#endif

using namespace std;

class Ray
{
public:
	Vector3 o, dir;
	Ray(): o(), dir() {};
	Ray(const Vector3 &ao, const Vector3 &adir):
		o(ao), dir(adir) {};
};

/**
 * a camera
 */
class Camera
{
public:
	Vector3 eye, p, u, v;
	Float f,F;

	Camera(): eye(0,0,10), p(0,0,-1), u(-1,0,0), v(0,1,0), f(3.14/4.0), F(0.5/f) {};
	Camera(const Vector3 &C, const Vector3 &ap, const Vector3 &au, const Vector3 &av):
		eye(C), p(ap), u(au), v(av), f(3.14/4.0), F(0.5/f) {};
	void setEye(const Vector3 &aeye) { eye = aeye; };
	void setFocalLength(const Float af) { f = af; F = 0.5/f; };
	void rotate(const Quaternion &q);
	void move(const Float fw, const Float left, const Float up);

	Ray makeRay(Sample *samp);
};

/**
 * axis-aligned bounding box
 */
class BBox
{
public:
	Vector3 L;
	Vector3 H;
	BBox(): L(), H() {};
	BBox(const Vector3 aL, const Vector3 aH): L(aL), H(aH) {};
	Float w() { return H.x-L.x; };
	Float h() { return H.y-L.y; };
	Float d() { return H.z-L.z; };
	bool intersect(const Ray &ray, Float &a, Float &b);
};

/**
 * light object
 */
class Light
{
public:
	Vector3 pos;
	Colour colour;
	bool cast_shadows;

	Light():
		pos(Vector3(0,0,0)), colour(Colour(1,1,1)), cast_shadows(true) {};
	Light(const Vector3 &position, const Colour &acolour):
		pos(position), colour(acolour), cast_shadows(true) {};
	void castShadows(bool cast) { cast_shadows = cast; };
};

/**
 * texture
 */
class Texture
{
public:
	virtual ~Texture() {};
	virtual Colour evaluate(Vector3 point) = 0;
};

/**
 * material
 */
class Material
{
public:
	Colour colour;
	Float ambient, diffuse, specular, shininess; // Phong constants
	Float reflectivity; // how much reflective is the surface
	Float transmissivity, refract_index; // part of light which can be refracted; index of refraction
	Texture *texture;

	Material(const Colour &acolour): colour(acolour), texture(NULL)
	{
		ambient = 0.2;
		diffuse = 0.8;
		specular = 0.2;
		shininess = 0.5;
		reflectivity = 0.2;
		transmissivity = 0.0;
		refract_index = 1.3;
	}

	void setPhong(const Float amb, const Float dif, const Float spec, const Float shin)
		{ ambient = amb; diffuse = dif; specular = spec; shininess = shin; };
	void setReflectivity(const Float refl) { reflectivity = refl; };
	void setTransmissivity(const Float trans, const Float rindex)
		{ transmissivity = trans; refract_index = rindex; };
};

/**
 * shape
 */
class Shape
{
public:
	Material *material;
	Shape() {};
	virtual ~Shape() {};

	// first intersection point
	virtual bool intersect(const Ray &ray, Float &dist) const = 0;

	// all intersections (only for CSG)
	virtual bool intersect_all(const Ray &ray, Float dist, vector<Float> &allts) const = 0;

	// intersection with AABB
	virtual bool intersect_bbox(const BBox &bbox) const = 0;

	// normal at point P
	virtual const Vector3 normal(const Vector3 &P) const = 0;

	virtual BBox get_bbox() const = 0;
};

/**
 * list of shapes
 */
class ShapeList: public vector<Shape*>
{
};

/**
 * sphere shape
 */
class Sphere: public Shape
{
	Float sqr_radius;
	Float inv_radius;
public:
	Vector3 center;
	Float radius;

	Sphere(const Vector3 &acenter, const Float aradius, Material *amaterial):
		sqr_radius(aradius*aradius), inv_radius(1.0f/aradius),
		center(acenter), radius(aradius) { material = amaterial; }
	bool intersect(const Ray &ray, Float &dist) const;
	bool intersect_all(const Ray &ray, Float dist, vector<Float> &allts) const;
	bool intersect_bbox(const BBox &bbox) const;
	const Vector3 normal(const Vector3 &P) const { return (P - center) * inv_radius; };
	BBox get_bbox() const;
};

/**
 * box shape
 */
class Box: public Shape
{
	Vector3 L;
	Vector3 H;
public:
	Box(const Vector3 &aL, const Vector3 &aH, Material *amaterial): L(aL), H(aH)
	{
		for (int i = 0; i < 3; i++)
			if (L.cell[i] > H.cell[i])
				swap(L.cell[i], H.cell[i]);
		material = amaterial;
	};
	bool intersect(const Ray &ray, Float &dist) const;
	bool intersect_all(const Ray &ray, Float dist, vector<Float> &allts) const { return false; };
	bool intersect_bbox(const BBox &bbox) const;
	const Vector3 normal(const Vector3 &P) const;
	BBox get_bbox() const { return BBox(L, H); };
};

/**
 * triangle vertex
 */
class Vertex
{
public:
	Vector3 P;
	Vertex(const Vector3 &aP): P(aP) {};
};

/**
 * triangle vertex with normal
 */
class NormalVertex: public Vertex
{
public:
	Vector3 N;
	NormalVertex(const Vector3 &aP): Vertex(aP) {};
	NormalVertex(const Vector3 &aP, const Vector3 &aN): Vertex(aP), N(aN) {};
	const Vector3 &getNormal() { return N; };
	void setNormal(const Vector3 &aN) { N = aN; };
};

/**
 * triangle shape
 */
class Triangle: public Shape
{
#ifdef TRI_BARI_PRE
	Float nu, nv, nd;
	int k; // dominant axis
	Float bnu, bnv;
	Float cnu, cnv;
#endif
#ifdef TRI_BARI
	int k; // dominant axis
#endif
#ifdef TRI_PLUCKER
	Float pla[6], plb[6], plc[6];
#endif
	Vector3 N;
	bool smooth;
	const Vector3 smooth_normal(const Vector3 &P) const
	{
#ifdef TRI_BARI_PRE
		const Vector3 &NA = static_cast<NormalVertex*>(A)->N;
		const Vector3 &NB = static_cast<NormalVertex*>(B)->N;
		const Vector3 &NC = static_cast<NormalVertex*>(C)->N;
		static const int modulo3[5] = {0,1,2,0,1};
		register const int ku = modulo3[k+1];
		register const int kv = modulo3[k+2];
		const Float pu = P[ku] - A->P[ku];
		const Float pv = P[kv] - A->P[kv];
		const Float u = pv * bnu + pu * bnv;
		const Float v = pu * cnv + pv * cnu;
		Vector3 n = NA + u * (NB - NA) + v * (NC - NA);
		n.normalize();
		return n;
#else
		return N; // not implemented for other algorithms
#endif
	};
public:
	Vertex *A, *B, *C;

	Triangle(Vertex *aA, Vertex *aB, Vertex *aC, Material *amaterial);
	bool intersect(const Ray &ray, Float &dist) const;
	bool intersect_all(const Ray &ray, Float dist, vector<Float> &allts) const {return false;};
	bool intersect_bbox(const BBox &bbox) const;
	const Vector3 normal(const Vector3 &P) const { return (smooth ? smooth_normal(P) : N); };
	const Vector3 getNormal() const { return N; };
	void setSmooth() { smooth = true; };//(typeid(*A) == typeid(*B) == typeid(*C) == typeid(NormalVertex)); };
	void setFlat() { smooth = false; };
	bool getSmooth() const { return smooth; };
	BBox get_bbox() const;
};

#endif
