/*
    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(__CAMERA_H)
#define __CAMERA_H

#include <mitsuba/render/film.h>

MTS_NAMESPACE_BEGIN

/** \brief Abstract camera base class. A camera turns a sample on
 * the image plane into a 3D ray. It uses two supporting child 
 * objects: a \re Sampler and a \ref Film instance.
 */
class MTS_EXPORT_RENDER Camera : public ConfigurableObject {
public:
	// =============================================================
	//! @{ \name Ray generation
	// =============================================================

	/// Create a ray from the given sample
	virtual void generateRay(const Point2 &sample, 
		const Point2 &lensSample,
		Float timeSample, Ray &ray) const = 0;

	/// Create ray differentials from the given sample
	void generateRayDifferential(const Point2 &sample, 
		const Point2 &lensSample, Float timeSample, 
		RayDifferential &ray) const;
	
	//! @}
	// =============================================================

	// =============================================================
	//! @{ \name Other query functions
	// =============================================================

	/**
	 * Turn a world-space position into fractional pixel coordinates.
	 * Returns false if the computed position is not visible through 
	 * the film's crop window
	 */
	virtual bool positionToSample(const Point &p, Point2 &sample) const = 0;

	/// Does generateRay() expect a proper lens sample?
	virtual bool needsLensSample() const = 0;
	
	/// Does generateRay() expect a proper time sample?
	inline bool needsTimeSample() const { return m_shutterOpenTime > 0; }

	/// Return the time value of the shutter opening event
	inline Float getShutterOpen() const { return m_shutterOpen; }
	
	/// Return the length, for which the shutter remains open
	inline Float getShutterOpenTime() const { return m_shutterOpenTime; }

	/// Return the time value of the shutter closing event
	inline Float getShutterClose() const { return m_shutterOpen; }

	//! @}
	// =============================================================
	
	// =============================================================
	//! @{ \name Other query functions
	// =============================================================

	/// Return the camera position (approximate in the case of finite sensor area)
	inline const Point &getPosition() const { return m_position; }

	/// Return the camera position at the provided screen sample
	virtual Point getPosition(const Point2 &sample) const;

	/// Return the view transformation
	inline const Transform &getViewTransform() const { return m_worldToCamera; }

	/// Return the inverse view transformation
	inline const Transform &getInverseViewTransform() const { return m_cameraToWorld; }

	/// Set the view transformation
	inline void setViewTransform(const Transform &pTransform) {
		m_worldToCamera = pTransform;
		m_cameraToWorld = m_worldToCamera.inverse();
		m_position = m_cameraToWorld(Point(0,0,0));
	}
	
	/// Set the inverse view transformation
	inline void setInverseViewTransform(const Transform &pTransform) {
		m_cameraToWorld = pTransform;
		m_worldToCamera = m_cameraToWorld.inverse();
		m_position = m_cameraToWorld(Point(0,0,0));
	}

	/// Return the image plane normal
	inline Normal getImagePlaneNormal() const {
		return Normal(normalize(m_cameraToWorld(Vector(0, 0, 1))));
	}

	/**
	 * Calculate the pixel area density at a position on the image plane.
	 * Returns zero for cameras with an infinitesimal sensor (e.g. pinhole cameras).
	 */
	virtual Float areaDensity(const Point2 &p) const = 0;

	//! @}
	// =============================================================
	
	// =============================================================
	//! @{ \name Miscellaneous
	// =============================================================

	/**
	 * \brief Return the camera's sampler.
	 *
	 * This is the 'root' sampler, which will later be cloned a 
	 * number of times to provide each participating worker thread 
	 * with its own instance (see \ref Scene::getSampler()). 
	 * Therefore, this sampler should never be used for anything 
	 * except creating clones.
	 */
	inline Sampler *getSampler() { return m_sampler; }

	/**
	 * \brief Return the camera's sampler (const version).
	 *
	 * This is the 'root' sampler, which will later be cloned a 
	 * number of times to provide each participating worker thread 
	 * with its own instance (see \ref Scene::getSampler()). 
	 * Therefore, this sampler should never be used for anything 
	 * except creating clones.
	 */
	inline const Sampler *getSampler() const { return m_sampler.get(); }

	/// Set the camera's film
	void setFilm(Film *film);

	/// Return the camera's film
	inline Film *getFilm() { return m_film; }

	/// Return the camera's film
	inline const Film *getFilm() const { return m_film.get(); }

	/// Return a pointer to the medium that surrounds the camera
	inline Medium *getMedium() { return m_medium; }
	
	/// Return a pointer to the medium that surrounds the camera (const version)
	inline const Medium *getMedium() const { return m_medium.get(); }

	/// Set the medium that surrounds the camera
	inline void setMedium(Medium *medium) { m_medium = medium; }

	/// Add a child ConfigurableObject
	virtual void addChild(const std::string &name, ConfigurableObject *child);

	/// Serialize this camera to a binary data stream	
	virtual void serialize(Stream *stream, InstanceManager *manager) const;

	// Set the parent node
	void setParent(ConfigurableObject *parent);

	/// Return the properties of this camera 
	inline const Properties &getProperties() const { return m_properties; }

	//! @}
	// =============================================================

	MTS_DECLARE_CLASS()
protected:
	/// Create a new camera
	Camera(const Properties &props);

	/// Unserialize a new camera
	Camera(Stream *stream, InstanceManager *manager);

	/// Virtual destructor
	virtual ~Camera();
protected:
	ref<Film> m_film;
	ref<Sampler> m_sampler;
	Transform m_worldToCamera, m_cameraToWorld;
	Point m_position;
	Properties m_properties;
	Float m_shutterOpen, m_shutterClose, m_shutterOpenTime;
	ref<Medium> m_medium;
};
	
class MTS_EXPORT_RENDER ProjectiveCamera : public Camera {
public:
	/// Return the projection transformation
	inline const Transform &getProjectionTransform() const { return m_cameraToScreen; }

	/// Return the projection transformation (using GL clip space coordinates)
	inline const Transform &getGLProjectionTransform() const { return m_cameraToScreenGL; }

	/// Return a projection transformation that includes a pixel offset (using GL clip space coordinates)
	virtual Transform getGLProjectionTransform(const Point2 &jitter) const = 0;

	/// Serialize this camera to a binary data stream
	virtual void serialize(Stream *stream, InstanceManager *manager) const;
	
	/** \brief Configure the object (called _once_ after construction
	   and addition of all child ConfigurableObjects. */
	virtual void configure();

	/// Return the near clip plane distance
	inline Float getNearClip() const { return m_nearClip; }

	/// Return the far clip plane distance
	inline Float getFarClip() const { return m_farClip; }

	MTS_DECLARE_CLASS()
protected:
	ProjectiveCamera(const Properties &props);
	ProjectiveCamera(Stream *stream, InstanceManager *manager);
	virtual ~ProjectiveCamera() { }
protected:
	Transform m_cameraToScreen, m_cameraToScreenGL;
	Float m_nearClip, m_farClip, m_aspect;
};

/**
 * Base class of all pinhole cameras. Provides solid angle computation
 * routines useful for importance-based integrators.
 */
class MTS_EXPORT_RENDER PerspectiveCamera : public ProjectiveCamera {
public:
	/** \brief Configure the object (called _once_ after construction
	   and addition of all child ConfigurableObjects. */
	virtual void configure();

	/// Serialize this camera to a binary data stream
	virtual void serialize(Stream *stream, InstanceManager *manager) const;

	/// Calculate the image plane size for a plane of the given distance
	Vector2 getImagePlaneSize(Float dist) const;

	/**
	 * \brief Calculate the importance of the given image-plane point
	 * expressed in fractional pixel coordinates. 
	 *
	 * Note that this value varies even within the individual pixels 
	 * due to the non-uniformity of rays generated by a strategy that
	 * uniformly samples points on the image plane.
	 */
	Float importance(const Point2 &p) const;

	/// Similar to importanceCamera(), but instead takes a world-space direction
	Float importance(const Vector &v) const;

	/// Return 

	/// Return the field of view along the X axis
	inline Float getXFov() const { return m_xfov; }

	/// Return the field of view along the Y axis
	inline Float getYFov() const { return m_yfov; }

	/// Get the field of view as specified in the scene file
	inline Float getFov() const { return m_fov; }

	/// Set the field of view as specified in the scene file
	void setFov(Float fov) { m_fov = fov; }

	/// Set the aperture radius
	void setApertureRadius(Float apertureRadius) { m_apertureRadius = apertureRadius; }

	/// Return the aperture radius
	inline Float getApertureRadius() const { return m_apertureRadius; }

	/// Set the focal distance
	void setFocusDepth(Float focusDepth) { m_focusDepth = focusDepth; }

	/// Return the focal distance
	inline Float getFocusDepth() const { return m_focusDepth; }

	/// Does generateRay() expect a proper lens sample?
	bool needsLensSample() const;

	MTS_DECLARE_CLASS()
protected:
	PerspectiveCamera(const Properties &props);
	PerspectiveCamera(Stream *stream, InstanceManager *manager);
	virtual ~PerspectiveCamera() { }
protected:
	Float m_fov, m_xfov, m_yfov;
	Vector2 m_imagePlaneSize;
	Vector2 m_imagePlanePixelSize;
	Float m_imagePlaneInvArea;
	Float m_apertureRadius;
	Float m_focusDepth;
	bool m_mapSmallerSide;
};

MTS_NAMESPACE_END

#endif /* __CAMERA_H */
