/*
    This file is part of Mitsuba, a physically based rendering system.

    Copyright (c) 2007-2011 by Wenzel Jakob and others.

    Mitsuba is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License Version 3
    as published by the Free Software Foundation.

    Mitsuba is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

#if !defined(__RAY_H)
#define __RAY_H

#include <mitsuba/mitsuba.h>

MTS_NAMESPACE_BEGIN

/** \brief Simple three-dimensional ray data structure with
 * minimum / maximum extent information.
 *
 * The somewhat peculiar ordering of the attributes is due
 * to alignment purposes and should not be changed.
 *
 * \ingroup libcore
*/
struct Ray {
	Point o;     ///< Ray origin
	Float mint;  ///< Minimum range for intersection tests
	Vector d;    ///< Ray direction
	Float maxt;  ///< Maximum range for intersection tests
	Vector dRcp; ///< Componentwise reciprocals of the ray direction
	Float time;  ///< Time value associated with this ray

	/// Construct a new ray
	inline Ray() : mint(Epsilon), 
		maxt(std::numeric_limits<Float>::infinity()), time(0.0f) {
	}

	/// Copy constructor (1)
	inline Ray(const Ray &ray) 
	 : o(ray.o), mint(ray.mint), d(ray.d), maxt(ray.maxt), 
	   dRcp(ray.dRcp), time(ray.time) {
	}

	/// Copy constructor (2)
	inline Ray(const Ray &ray, Float mint, Float maxt) 
	 : o(ray.o), mint(mint), d(ray.d), maxt(maxt), dRcp(ray.dRcp), time(ray.time) {
	}

	/// Construct a new ray, while not specifying a direction yet
	inline Ray(Point o, Float time) : o(o), mint(Epsilon), maxt(std::numeric_limits<Float>::infinity()), time(time) {
	}

	/// Construct a new ray
	inline Ray(Point o, Vector _d, Float time)
		: o(o), mint(Epsilon),  d(_d), maxt(std::numeric_limits<Float>::infinity()), time(time) {
#ifdef MTS_DEBUG_FP
		bool state = disableFPExceptions();
#endif
		dRcp.x = (Float) 1.0f / _d.x;
		dRcp.y = (Float) 1.0f / _d.y;
		dRcp.z = (Float) 1.0f / _d.z;
#ifdef MTS_DEBUG_FP
		restoreFPExceptions(state);
#endif
	}

	/// Construct a new ray
	inline Ray(Point o, Vector _d, Float mint, Float maxt, Float time)
		: o(o), mint(mint),  d(_d), maxt(maxt), time(time) {
#ifdef MTS_DEBUG_FP
		bool state = disableFPExceptions();
#endif
		dRcp.x = (Float) 1.0f / _d.x;
		dRcp.y = (Float) 1.0f / _d.y;
		dRcp.z = (Float) 1.0f / _d.z;
#ifdef MTS_DEBUG_FP
		restoreFPExceptions(state);
#endif
	}

	/// Set the origin
	inline void setOrigin(const Point &oVal) { o = oVal; }

	/// Set the origin
	inline void setTime(const Float &tval) { time = tval; }
	
	/// Set the direction and update the reciprocal
	inline void setDirection(const Vector &dVal) {
		d = dVal;
#ifdef MTS_DEBUG_FP
		bool state = disableFPExceptions();
#endif
		dRcp.x = (Float) 1.0f / dVal.x;
		dRcp.y = (Float) 1.0f / dVal.y;
		dRcp.z = (Float) 1.0f / dVal.z;
#ifdef MTS_DEBUG_FP
		restoreFPExceptions(state);
#endif
	}

	/// Return 3d coordinates of a point on the ray
	inline Point operator() (Float t) const { return o + t * d; }

	/// Return a string representation of this ray
	inline std::string toString() const {
		std::ostringstream oss;
		oss << "Ray[origin=" << o.toString() << ", direction=" 
			<< d.toString() << ", mint=" << mint 
			<< ", maxt=" << maxt << ", time=" << time << "]";
		return oss.str();
	}
};

/** \brief %Ray differential -- enhances the basic ray class with 
   information about the rays of adjacent pixels on the view plane
   \ingroup libcore
*/
struct RayDifferential : public Ray {
	Point rxOrigin, ryOrigin;
	Vector rxDirection, ryDirection;
	bool hasDifferentials;

	inline RayDifferential() 
		: hasDifferentials(false) {
	}

	inline RayDifferential(const Point &p, const Vector &d, Float time) 
		: Ray(p, d, time), hasDifferentials(false) {
	}

	inline explicit RayDifferential(const Ray &ray) 
		: Ray(ray), hasDifferentials(false) {
	}

	inline RayDifferential(const RayDifferential &ray) 
		: Ray(ray), rxOrigin(ray.rxOrigin), ryOrigin(ray.ryOrigin),
		  rxDirection(ray.rxDirection), ryDirection(ray.ryDirection),
		  hasDifferentials(ray.hasDifferentials) {
	}
    
	void scaleDifferential(Float amount) {
		rxOrigin = o + (rxOrigin - o) * amount;
		ryOrigin = o + (ryOrigin - o) * amount;
		rxDirection = d + (rxDirection - d) * amount;
		ryDirection = d + (ryDirection - d) * amount;
    }

	inline void operator=(const RayDifferential &ray) {
		o = ray.o;
		mint = ray.mint;
		d = ray.d;
		maxt = ray.maxt;
#ifdef MTS_DEBUG_FP
		bool state = disableFPExceptions();
#endif
		dRcp = ray.dRcp;
#ifdef MTS_DEBUG_FP
		restoreFPExceptions(state);
#endif
		hasDifferentials = ray.hasDifferentials;
		rxOrigin = ray.rxOrigin;
		ryOrigin = ray.ryOrigin;
		rxDirection = ray.rxDirection;
		ryDirection = ray.ryDirection;
	}

	inline void operator=(const Ray &ray) {
		o = ray.o;
		mint = ray.mint;
		d = ray.d;
		maxt = ray.maxt;
#ifdef MTS_DEBUG_FP
		bool state = disableFPExceptions();
#endif
		dRcp = ray.dRcp;
#ifdef MTS_DEBUG_FP
		restoreFPExceptions(state);
#endif
		hasDifferentials = false;
	}

	/// Return a string representation of this ray
	inline std::string toString() const {
		std::ostringstream oss;
		oss << "RayDifferential[" << endl
			<< "  origin = " << o.toString() << "," << endl
			<< "  direction  = " << d.toString() << "," << endl
			<< "  mint = " << mint << "," << endl
			<< "  maxt = " << maxt << "," << endl
			<< "  time = " << time << "," << endl
			<< "  rxOrigin = " << rxOrigin.toString() << "," << endl
			<< "  ryOrigin = " << ryOrigin.toString() << "," << endl
			<< "  rxDirection = " << rxDirection.toString() << "," << endl
			<< "  ryDirection = " << ryDirection.toString() << endl
			<< "]" << endl;
		return oss.str();
	}
};

#if defined(MTS_SSE)
/** \brief SIMD quad-packed ray for coherent ray tracing */
struct RayPacket4 {
	QuadVector o, d;
	QuadVector dRcp;
	uint8_t signs[4][4];

	inline RayPacket4() {
	}

	inline bool load(const Ray *rays) {
		for (int i=0; i<4; i++) {
			for (int axis=0; axis<3; axis++) {
				o[axis].f[i] = rays[i].o[axis];
				d[axis].f[i] = rays[i].d[axis];
				dRcp[axis].f[i] = rays[i].dRcp[axis];
				signs[axis][i] = rays[i].d[axis] < 0 ? 1 : 0;
				if (signs[axis][i] != signs[axis][0])
					return false;
			}
		}
		return true;
	}
};

struct RayInterval4 {
	SSEVector mint;
	SSEVector maxt;

	inline RayInterval4() {
		mint = SSEConstants::eps;
		maxt = SSEConstants::p_inf;
	}

	inline RayInterval4(const Ray *rays) {
		for (int i=0; i<4; i++) {
			mint.f[i] = rays[i].mint;
			maxt.f[i] = rays[i].maxt;
		}
	}
};

struct Intersection4 {
	SSEVector t;
	SSEVector u;
	SSEVector v;
	SSEVector primIndex;
	SSEVector shapeIndex;

	inline Intersection4() {
		t          = SSEConstants::p_inf;
		u          = SSEConstants::zero;
		v          = SSEConstants::zero;
		primIndex  = SSEConstants::ffffffff;
		shapeIndex = SSEConstants::ffffffff;
	}
};

#endif

MTS_NAMESPACE_END

#endif /* __RAY_H */
