// K-3D
// Copyright (c) 1995-2004, Timothy M. Shead
//
// Contact: tshead@k-3d.com
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This program 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, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

/** \file
		\brief Implements the shadow_map object, which is used to generate RenderMan shadow maps for fast rendering
		\author Timothy M. Shead (tshead@k-3d.com)
*/

#include <k3dsdk/basic_math.h>
#include <k3dsdk/iprojection.h>
#include <k3dsdk/irender_farm.h>
#include <k3dsdk/iviewport_host.h>
#include <k3dsdk/renderman.h>
#include <k3dsdk/transformable.h>
#include <k3dsdk/object.h>
#include <k3dsdk/persistence.h>
#include <k3dsdk/measurement.h>
#include <k3dsdk/module.h>
#include <k3dsdk/renderman.h>
#include <k3dsdk/vectors.h>
#include <k3dsdk/viewport.h>

#include <sdpgl/sdpgl.h>

#ifdef	WIN32
#ifdef	near
#undef	near
#endif	//near
#ifdef	far
#undef	far
#endif	//far
#endif	//WIN32

namespace libk3drenderman
{

namespace detail
{

const unsigned long default_pixel_width = 256;
const unsigned long default_pixel_height = 256;
const double default_pixel_aspect_ratio = 1.0;
const double default_screen_aspect_ratio = default_pixel_aspect_ratio * static_cast<double>(default_pixel_width) / static_cast<double>(default_pixel_height);

} // namespace detail

/////////////////////////////////////////////////////////////////////////////
// shadow_map_implementation

class shadow_map_implementation :
	public k3d::viewport::drawable<k3d::transformable<k3d::persistent<k3d::object> > >,
	public k3d::iviewport_host,
	public k3d::ri::itexture
{
	typedef k3d::viewport::drawable<k3d::transformable<k3d::persistent<k3d::object> > > base;

public:
	shadow_map_implementation(k3d::idocument& Document) :
		base(Document),
		m_create_shadow_map(k3d::init_name("create") + k3d::init_description("Create shadow map during rendering [boolean]") + k3d::init_value(true) + k3d::init_document(Document)),
		m_view_shadow_map(k3d::init_name("view") + k3d::init_description("View shadow map creation during rendering [boolean]") + k3d::init_value(true) + k3d::init_document(Document)),
		m_pixel_width(k3d::init_name("pixel_width") + k3d::init_description("Output pixel width [positive integer]") + k3d::init_value(detail::default_pixel_width) + k3d::init_document(Document) + k3d::init_constraint(k3d::constraint::minimum(1UL)) + k3d::init_precision(0) + k3d::init_step_increment(1) + k3d::init_units(typeid(k3d::measurement::scalar))),
		m_pixel_height(k3d::init_name("pixel_height") + k3d::init_description("Output pixel height [positive integer]") + k3d::init_value(detail::default_pixel_height) + k3d::init_document(document()) + k3d::init_constraint(k3d::constraint::minimum(1UL)) + k3d::init_precision(0) + k3d::init_step_increment(1) + k3d::init_units(typeid(k3d::measurement::scalar))),
		m_pixel_aspect_ratio(k3d::init_name("pixel_aspect_ratio") + k3d::init_description("Output pixel aspect ratio [positive real]") + k3d::init_value(detail::default_pixel_aspect_ratio) + k3d::init_document(document()) + k3d::init_constraint(k3d::constraint::minimum(std::numeric_limits<double>::epsilon())) + k3d::init_precision(2) + k3d::init_step_increment(0.01) + k3d::init_units(typeid(k3d::measurement::scalar))),
		m_orthographic(k3d::init_name("orthographic") + k3d::init_description("Orthographic [boolean]") + k3d::init_value(false) + k3d::init_document(Document)),
		m_left(k3d::init_name("left") + k3d::init_description("Left [number]") + k3d::init_value(-0.5 * detail::default_screen_aspect_ratio) + k3d::init_document(Document) + k3d::init_precision(2) + k3d::init_step_increment(0.01) + k3d::init_units(typeid(k3d::measurement::scalar))),
		m_right(k3d::init_name("right") + k3d::init_description("Right [number]") + k3d::init_value(0.5 * detail::default_screen_aspect_ratio) + k3d::init_document(Document) + k3d::init_precision(2) + k3d::init_step_increment(0.01) + k3d::init_units(typeid(k3d::measurement::scalar))),
		m_top(k3d::init_name("top") + k3d::init_description("Top [number]") + k3d::init_value(0.5) + k3d::init_document(Document) + k3d::init_precision(2) + k3d::init_step_increment(0.01) + k3d::init_units(typeid(k3d::measurement::scalar))),
		m_bottom(k3d::init_name("bottom") + k3d::init_description("Bottom [number]") + k3d::init_value(-0.5) + k3d::init_document(Document) + k3d::init_precision(2) + k3d::init_step_increment(0.01) + k3d::init_units(typeid(k3d::measurement::scalar))),
		m_near(k3d::init_name("near") + k3d::init_description("Near plane distance [number]") + k3d::init_value(1.0) + k3d::init_constraint(k3d::constraint::minimum(0.0)) + k3d::init_document(Document) + k3d::init_precision(1) + k3d::init_step_increment(0.1) + k3d::init_units(typeid(k3d::measurement::distance))),
		m_far(k3d::init_name("far") + k3d::init_description("Far plane distance [number]") + k3d::init_value(1000.0) + k3d::init_constraint(k3d::constraint::minimum(0.0)) + k3d::init_document(Document) + k3d::init_precision(1) + k3d::init_step_increment(0.1) + k3d::init_units(typeid(k3d::measurement::distance))),
		m_perspective_projection(m_left, m_right, m_top, m_bottom, m_near, m_far),
		m_orthographic_projection(m_left, m_right, m_top, m_bottom, m_near, m_far)
	{
		enable_serialization(k3d::persistence::proxy(m_create_shadow_map));
		enable_serialization(k3d::persistence::proxy(m_view_shadow_map));
		enable_serialization(k3d::persistence::proxy(m_pixel_width));
		enable_serialization(k3d::persistence::proxy(m_pixel_height));
		enable_serialization(k3d::persistence::proxy(m_pixel_aspect_ratio));
		enable_serialization(k3d::persistence::proxy(m_orthographic));
		enable_serialization(k3d::persistence::proxy(m_left));
		enable_serialization(k3d::persistence::proxy(m_right));
		enable_serialization(k3d::persistence::proxy(m_top));
		enable_serialization(k3d::persistence::proxy(m_bottom));
		enable_serialization(k3d::persistence::proxy(m_near));
		enable_serialization(k3d::persistence::proxy(m_far));

		register_property(m_create_shadow_map);
		register_property(m_view_shadow_map);
		register_property(m_pixel_width);
		register_property(m_pixel_height);
		register_property(m_pixel_aspect_ratio);
		register_property(m_orthographic);
		register_property(m_left);
		register_property(m_right);
		register_property(m_top);
		register_property(m_bottom);
		register_property(m_near);
		register_property(m_far);

		m_orthographic.changed_signal().connect(SigC::slot(*this, &shadow_map_implementation::async_redraw_all));
		m_left.changed_signal().connect(SigC::slot(*this, &shadow_map_implementation::async_redraw_all));
		m_right.changed_signal().connect(SigC::slot(*this, &shadow_map_implementation::async_redraw_all));
		m_top.changed_signal().connect(SigC::slot(*this, &shadow_map_implementation::async_redraw_all));
		m_bottom.changed_signal().connect(SigC::slot(*this, &shadow_map_implementation::async_redraw_all));
		m_near.changed_signal().connect(SigC::slot(*this, &shadow_map_implementation::async_redraw_all));
		m_far.changed_signal().connect(SigC::slot(*this, &shadow_map_implementation::async_redraw_all));
				
		m_input_matrix.changed_signal().connect(SigC::slot(*this, &shadow_map_implementation::async_redraw_all));
		m_position.changed_signal().connect(SigC::slot(*this, &shadow_map_implementation::async_redraw_all));
		m_orientation.changed_signal().connect(SigC::slot(*this, &shadow_map_implementation::async_redraw_all));
		m_scale.changed_signal().connect(SigC::slot(*this, &shadow_map_implementation::async_redraw_all));
		
		m_pixel_width.changed_signal().connect(SigC::slot(*this, &shadow_map_implementation::on_pixel_width_changed));
		m_pixel_height.changed_signal().connect(SigC::slot(*this, &shadow_map_implementation::on_pixel_height_changed));
	}

	void constrain_screen_aspect_ratio(double& Ratio)
	{
		// Constrain the aspect ratio to match our output image
		Ratio = static_cast<double>(m_pixel_width.property_value()) / static_cast<double>(m_pixel_height.property_value());
	}
	
	k3d::iprojection* projection()
	{
		if(m_orthographic.property_value())
			return &m_orthographic_projection;
			
		return &m_perspective_projection;
	}

	aspect_ratio_changed_signal_t& aspect_ratio_changed_signal()
	{
		return m_aspect_ratio_changed_signal;
	}

	void on_pixel_width_changed()
	{
		const unsigned long pixel_width = m_pixel_width.property_value();
		const unsigned long pixel_height = m_pixel_height.property_value();
		
		return_if_fail(pixel_height);
		const double ratio = static_cast<double>(pixel_width) / static_cast<double>(pixel_height);
		
		m_left.set_value(-0.5 * ratio * std::abs(m_top.value() - m_bottom.value()));
		m_right.set_value(0.5 * ratio * std::abs(m_top.value() - m_bottom.value()));
		
		m_aspect_ratio_changed_signal.emit();
	}

	void on_pixel_height_changed()
	{
		const unsigned long pixel_width = m_pixel_width.property_value();
		const unsigned long pixel_height = m_pixel_height.property_value();
		
		return_if_fail(pixel_width);
		const double ratio = static_cast<double>(pixel_height) / static_cast<double>(pixel_width);
		
		m_top.set_value(0.5 * ratio * std::abs(m_right.value() - m_left.value()));
		m_bottom.set_value(-0.5 * ratio * std::abs(m_right.value() - m_left.value()));
		
		m_aspect_ratio_changed_signal.emit();
	}

	void on_viewport_draw(const k3d::viewport::render_state& State)
	{
		glDisable(GL_LIGHTING);
		glDisable(GL_TEXTURE_1D);
		glDisable(GL_TEXTURE_2D);

		glColor3d(0, 0, 0);
		glLineWidth(1.0f);
		glDisable(GL_LINE_STIPPLE);

		draw();
	}

	void on_viewport_select(const k3d::viewport::render_state& State)
	{
		draw();
	}

	void draw()
	{
		glBegin(GL_LINES);

		k3d::vector3 coords(0, 0, 0);

		glVertex3d(coords[0] + 1.0, coords[1], coords[2]);
		glVertex3d(coords[0] - 1.0, coords[1], coords[2]);
		glVertex3d(coords[0], coords[1] + 1.0, coords[2]);
		glVertex3d(coords[0], coords[1] - 1.0, coords[2]);
		glVertex3d(coords[0], coords[1], coords[2] + 1.0);
		glVertex3d(coords[0], coords[1], coords[2] - 1.0);

		glVertex3d(coords[0] + 0.4, coords[1] + 0.4, coords[2] + 0.4);
		glVertex3d(coords[0] - 0.4, coords[1] - 0.4, coords[2] - 0.4);
		glVertex3d(coords[0] - 0.4, coords[1] + 0.4, coords[2] + 0.4);
		glVertex3d(coords[0] + 0.4, coords[1] - 0.4, coords[2] - 0.4);
		glVertex3d(coords[0] + 0.4, coords[1] + 0.4, coords[2] - 0.4);
		glVertex3d(coords[0] - 0.4, coords[1] - 0.4, coords[2] + 0.4);
		glVertex3d(coords[0] - 0.4, coords[1] + 0.4, coords[2] - 0.4);
		glVertex3d(coords[0] + 0.4, coords[1] - 0.4, coords[2] + 0.4);

		glEnd();
	}

	void setup_renderman_texture(k3d::irender_frame& Frame, k3d::ri::irender_engine& Engine)
	{
		// Unless shadow-map creation is enabled, we're done ...
		if(!m_create_shadow_map.value())
			{
				m_shadow_map_path = boost::filesystem::path();
				return;
			}

		Engine.RiFrameBegin(0);

		m_shadow_map_path = Frame.add_output_file("shadowmap.z");

		Engine.RiDisplayV(m_shadow_map_path.native_file_string(), "shadow", k3d::ri::RI_Z());

		if(m_view_shadow_map.value())
			Engine.RiDisplayV("+" + name(), "zframebuffer", k3d::ri::RI_Z());

		Engine.RiFormat(m_pixel_width.property_value(), m_pixel_height.property_value(), 1);
		Engine.RiPixelSamples(1, 1);
		Engine.RiPixelFilter(k3d::ri::RI_BOX(), 1, 1);

		k3d::ri::parameter_list hider_parameters;
		hider_parameters.push_back(k3d::ri::parameter("depthfilter", k3d::ri::UNIFORM, k3d::ri::string("midpoint")));
		Engine.RiHiderV("hidden", hider_parameters);

		// Setup up viewing transformations
		Engine.RiComment("Setup shadowmap viewing transformations");

		if(m_orthographic.property_value())
			{
				Engine.RiProjectionV("orthographic");
				Engine.RiScreenWindow(m_left.property_value(), m_right.property_value(), m_bottom.property_value(), m_top.property_value());
				Engine.RiClipping(m_near.property_value(), m_far.property_value());
			}
		else
			{
				Engine.RiProjectionV("perspective");
				Engine.RiScreenWindow(m_left.property_value(), m_right.property_value(), m_bottom.property_value(), m_top.property_value());
				Engine.RiClipping(m_near.property_value(), m_far.property_value());
			}

		// Setup the camera viewing transform ...
		Engine.RiTransform(k3d::ri::convert(matrix().Inverse()));

		Engine.RiWorldBegin();

		// Render objects ...
		k3d::ri::render_state state(Frame, Engine, *projection(), k3d::ri::SHADOW_MAP, k3d::ri::sample_times_t(1, 0.0), 0, matrix());
		for(k3d::objects_t::const_iterator object = document().objects().collection().begin(); object != document().objects().collection().end(); ++object)
			{
				k3d::ri::irenderable* const renderable = dynamic_cast<k3d::ri::irenderable*>(*object);
				if(renderable)
					renderable->renderman_render(state);
			}

		Engine.RiWorldEnd();
		Engine.RiFrameEnd();
	}

	const boost::filesystem::path renderman_texture_path(const k3d::ri::render_state& State)
	{
		return m_shadow_map_path;
	}

	k3d::iplugin_factory& factory()
	{
		return get_factory();
	}

	static k3d::iplugin_factory& get_factory()
	{
		static k3d::plugin_factory<
			k3d::document_plugin<shadow_map_implementation>,
			k3d::interface_list<k3d::ri::itexture,
			k3d::interface_list<k3d::itransform_source,
			k3d::interface_list<k3d::itransform_sink > > > > factory(
			k3d::uuid(0x9bce6e2b, 0x083c4353, 0x829052e9, 0xf98c7023),
			"RenderManShadowMap",
			"A shadow_map source within the 3D workspace",
			"Objects",
			k3d::iplugin_factory::STABLE);

		return factory;
	}

private:
	k3d_data_property(bool, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_create_shadow_map;
	k3d_data_property(bool, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_view_shadow_map;
	k3d_measurement_property(unsigned long, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::with_constraint) m_pixel_width;
	k3d_measurement_property(unsigned long, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::with_constraint) m_pixel_height;
	k3d_measurement_property(double, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::with_constraint) m_pixel_aspect_ratio;
	k3d_data_property(bool, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_orthographic;
	k3d_measurement_property(double, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_left;
	k3d_measurement_property(double, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_right;
	k3d_measurement_property(double, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_top;
	k3d_measurement_property(double, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_bottom;
	k3d_measurement_property(double, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::with_constraint) m_near;
	k3d_measurement_property(double, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::with_constraint) m_far;

	class perspective_projection :
		public k3d::iperspective
	{
	public:
		perspective_projection(k3d::iproperty& Left, k3d::iproperty& Right, k3d::iproperty& Top, k3d::iproperty& Bottom, k3d::iproperty& Near, k3d::iproperty& Far) :
			m_left(Left),
			m_right(Right),
			m_top(Top),
			m_bottom(Bottom),
			m_near(Near),
			m_far(Far)
		{
		}
	
		k3d::iproperty& left()
		{
			return m_left;
		}
		
		k3d::iproperty& right()
		{
			return m_right;
		}
		
		k3d::iproperty& top()
		{
			return m_top;
		}
		
		k3d::iproperty& bottom()
		{
			return m_bottom;
		}
		
		k3d::iproperty& near()
		{
			return m_near;
		}
		
		k3d::iproperty& far()
		{
			return m_far;
		}
		
	private:
		k3d::iproperty& m_left;
		k3d::iproperty& m_right;
		k3d::iproperty& m_top;
		k3d::iproperty& m_bottom;
		k3d::iproperty& m_near;
		k3d::iproperty& m_far;
	};
	
	class orthographic_projection :
		public k3d::iorthographic
	{
	public:
		orthographic_projection(k3d::iproperty& Left, k3d::iproperty& Right, k3d::iproperty& Top, k3d::iproperty& Bottom, k3d::iproperty& Near, k3d::iproperty& Far) :
			m_left(Left),
			m_right(Right),
			m_top(Top),
			m_bottom(Bottom),
			m_near(Near),
			m_far(Far)
		{
		}
	
		k3d::iproperty& left()
		{
			return m_left;
		}
		
		k3d::iproperty& right()
		{
			return m_right;
		}
		
		k3d::iproperty& top()
		{
			return m_top;
		}
		
		k3d::iproperty& bottom()
		{
			return m_bottom;
		}
		
		k3d::iproperty& near()
		{
			return m_near;
		}
		
		k3d::iproperty& far()
		{
			return m_far;
		}
		
	private:
		k3d::iproperty& m_left;
		k3d::iproperty& m_right;
		k3d::iproperty& m_top;
		k3d::iproperty& m_bottom;
		k3d::iproperty& m_near;
		k3d::iproperty& m_far;
	};
	
	perspective_projection m_perspective_projection;
	orthographic_projection m_orthographic_projection;

	boost::filesystem::path m_shadow_map_path;
	
	aspect_ratio_changed_signal_t m_aspect_ratio_changed_signal;
};

/////////////////////////////////////////////////////////////////////////////
// shadow_map_factory

k3d::iplugin_factory& shadow_map_factory()
{
	return shadow_map_implementation::get_factory();
}

} // namespace libk3drenderman


