// 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 Torus K-3D object, which renders a RenderMan torus primitive
		\author Tim Shead (tshead@k-3d.com)
		\author Romain Behar (romainbehar@yahoo.com)
*/

#include <k3dsdk/algebra.h>
#include <k3dsdk/bounded.h>
#include <k3dsdk/classes.h>
#include <k3dsdk/geometry.h>
#include <k3dsdk/glutility.h>
#include <k3dsdk/iapplication.h>
#include <k3dsdk/imaterial.h>
#include <k3dsdk/object.h>
#include <k3dsdk/persistence.h>
#include <k3dsdk/material_collection.h>
#include <k3dsdk/measurement.h>
#include <k3dsdk/module.h>
#include <k3dsdk/renderman.h>
#include <k3dsdk/transformable.h>
#include <k3dsdk/viewport.h>

#include <sdpgl/sdpgl.h>

namespace
{

/////////////////////////////////////////////////////////////////////////////
// torus_implementation

class torus_implementation :
	public k3d::material_collection<k3d::bounded<k3d::viewport::drawable<k3d::ri::renderable<k3d::transformable<k3d::persistent<k3d::object> > > > > >
{
	typedef k3d::material_collection<k3d::bounded<k3d::viewport::drawable<k3d::ri::renderable<k3d::transformable<k3d::persistent<k3d::object> > > > > > base;

public:
	torus_implementation(k3d::idocument& Document) :
		base(Document),
		m_majorradius(k3d::init_name("majorradius") + k3d::init_description("Major Radius [distance]") + k3d::init_value(5.0) + k3d::init_document(Document) + k3d::init_precision( 2) + k3d::init_step_increment(0.1) + k3d::init_units(typeid(k3d::measurement::distance))),
		m_minorradius(k3d::init_name("minorradius") + k3d::init_description("Minor Radius [distance]") + k3d::init_value(2.0) + k3d::init_document(Document) + k3d::init_precision( 2) + k3d::init_step_increment(0.1) + k3d::init_units(typeid(k3d::measurement::distance))),
		m_phimin(k3d::init_name("phimin") + k3d::init_description("Phi Minimum [angle]") + k3d::init_value(0.0) + k3d::init_document(Document) + k3d::init_precision( 2) + k3d::init_step_increment(k3d::radians(1.0)) + k3d::init_units(typeid(k3d::measurement::angle))),
		m_phimax(k3d::init_name("phimax") + k3d::init_description("Phi Maximum [angle]") + k3d::init_value(k3d::radians(360.0)) + k3d::init_document(Document) + k3d::init_precision( 2) + k3d::init_step_increment(k3d::radians(1.0)) + k3d::init_units(typeid(k3d::measurement::angle))),
		m_thetamax(k3d::init_name("thetamax") + k3d::init_description("Theta Maximum [angle]") + k3d::init_value(k3d::radians(360.0)) + k3d::init_document(Document) + k3d::init_precision( 2) + k3d::init_step_increment(k3d::radians(1.0)) + k3d::init_units(typeid(k3d::measurement::angle)))
	{
		enable_serialization(k3d::persistence::proxy(m_majorradius));
		enable_serialization(k3d::persistence::proxy(m_minorradius));
		enable_serialization(k3d::persistence::proxy(m_phimin));
		enable_serialization(k3d::persistence::proxy(m_phimax));
		enable_serialization(k3d::persistence::proxy(m_thetamax));

		register_property(m_majorradius);
		register_property(m_minorradius);
		register_property(m_phimin);
		register_property(m_phimax);
		register_property(m_thetamax);
		
		m_majorradius.changed_signal().connect(SigC::slot(*this, &torus_implementation::reset_geometry));
		m_minorradius.changed_signal().connect(SigC::slot(*this, &torus_implementation::reset_geometry));
		m_phimin.changed_signal().connect(SigC::slot(*this, &torus_implementation::reset_geometry));
		m_phimax.changed_signal().connect(SigC::slot(*this, &torus_implementation::reset_geometry));
		m_thetamax.changed_signal().connect(SigC::slot(*this, &torus_implementation::reset_geometry));
		
		m_position.changed_signal().connect(SigC::slot(*this, &torus_implementation::async_redraw_all));
		m_orientation.changed_signal().connect(SigC::slot(*this, &torus_implementation::async_redraw_all));
		m_scale.changed_signal().connect(SigC::slot(*this, &torus_implementation::async_redraw_all));
	}

	void reset_geometry()
	{
		m_gl_control_points.clear();
		k3d::viewport::redraw_all(document(), k3d::iviewport::ASYNCHRONOUS);
	}

	const k3d::bounding_box extents()
	{
		const double majorradius = fabs(m_majorradius.property_value());
		const double minorradius = fabs(m_minorradius.property_value());
		
		return k3d::bounding_box(majorradius + minorradius, -(majorradius + minorradius), minorradius, -minorradius, majorradius + minorradius, -(majorradius + minorradius));
	}

	void draw(const nurbs_renderer_t Nurbs)
	{
		if(m_gl_control_points.empty())
			{
				const double majorradius = m_majorradius.property_value();
				const double minorradius = m_minorradius.property_value();
				const double phimin = m_phimin.property_value();
				const double phimax = m_phimax.property_value();
				const double thetamax = m_thetamax.property_value();

				std::vector<double> v_weights;
				std::vector<k3d::vector3> v_arc_points;
				k3d::nurbs_arc(k3d::vector3(0, 1, 0), k3d::vector3(0, 0, 1), phimin, phimax, 4, m_gl_v_knot_vector, v_weights, v_arc_points);

				std::vector<double> u_weights;
				std::vector<k3d::vector3> u_arc_points;
				k3d::nurbs_arc(k3d::vector3(1, 0, 0), k3d::vector3(0, 1, 0), 0, thetamax, 4, m_gl_u_knot_vector, u_weights, u_arc_points);

				for(unsigned long v = 0; v != v_arc_points.size(); ++v)
					{
						const k3d::vector3 offset = minorradius * v_arc_points[v][2] * k3d::vector3(0, 0, 1);
						const double radius2 = majorradius + (minorradius * v_arc_points[v][1]);
						const double v_weight = v_weights[v];

						for(unsigned long u = 0; u != u_arc_points.size(); ++u)
							{
								m_gl_control_points.push_back(v_weight * u_weights[u] * (radius2 * u_arc_points[u][0] + offset[0]));
								m_gl_control_points.push_back(v_weight * u_weights[u] * (radius2 * u_arc_points[u][1] + offset[1]));
								m_gl_control_points.push_back(v_weight * u_weights[u] * (radius2 * u_arc_points[u][2] + offset[2]));
								m_gl_control_points.push_back(v_weight * u_weights[u]);
							}
					}
			}

		gluBeginSurface(Nurbs);
		gluNurbsSurface(Nurbs, m_gl_u_knot_vector.size(), &m_gl_u_knot_vector[0], m_gl_v_knot_vector.size(), &m_gl_v_knot_vector[0], 4, 36, &m_gl_control_points[0], 3, 3, GL_MAP2_VERTEX_4);
		gluEndSurface(Nurbs);
	}

	void on_viewport_draw(const k3d::viewport::render_state& State)
	{
		k3d::viewport::setup_material(m_material.object());

		const nurbs_renderer_t nurbs = nurbs_renderer(State);
		
		glColor3dv(is_selected() ? k3d::vector3(1, 1, 1) : k3d::vector3(0, 0, 0));
		gluNurbsProperty(nurbs, GLU_DISPLAY_MODE, GLU_OUTLINE_PATCH);
		glDisable(GL_LIGHTING);
		glDisable(GL_AUTO_NORMAL);
		draw(nurbs);
				
		if(!State.draw_two_sided)
			glEnable(GL_CULL_FACE);

		gluNurbsProperty(nurbs, GLU_DISPLAY_MODE, GLU_FILL);
		glEnable(GL_LIGHTING);
		glEnable(GL_AUTO_NORMAL);
		glPolygonOffset(1.0, 1.0);
		glEnable(GL_POLYGON_OFFSET_FILL);
		draw(nurbs);
		glDisable(GL_POLYGON_OFFSET_FILL);
	}

	void on_viewport_select(const k3d::viewport::render_state& State)
	{
		const nurbs_renderer_t nurbs = nurbs_renderer(State);
		gluNurbsProperty(nurbs, GLU_DISPLAY_MODE, GLU_FILL);
		glDisable(GL_LIGHTING);
		glDisable(GL_AUTO_NORMAL);
		glDisable(GL_CULL_FACE);
				
		k3d::glPushName(this);
		draw(nurbs);
		k3d::glPopName();
	}

	void on_renderman_render(const k3d::ri::render_state& State)
	{
		const double majorradius = m_majorradius.property_value();
		const double minorradius = m_minorradius.property_value();
		const double phimin = k3d::degrees(m_phimin.property_value());
		const double phimax = k3d::degrees(m_phimax.property_value());
		const double thetamax = k3d::degrees(m_thetamax.property_value());

		State.engine.RiTransformBegin();
		k3d::ri::setup_material(m_material.object(), State);
		State.engine.RiTorusV(majorradius, minorradius, phimin, phimax, thetamax);
		State.engine.RiTransformEnd();
	}

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

	static k3d::iplugin_factory& get_factory()
	{
		static k3d::plugin_factory<
			k3d::document_plugin<torus_implementation>,
			k3d::interface_list<k3d::itransform_source,
			k3d::interface_list<k3d::itransform_sink > > > factory(
			k3d::classes::Torus(),
			"Torus",
			"Renders a RenderMan torus primitive",
			"Objects",
			k3d::iplugin_factory::STABLE);

		return factory;
	}

private:
	k3d_measurement_property(double, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_majorradius;
	k3d_measurement_property(double, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_minorradius;
	k3d_measurement_property(double, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_phimin;
	k3d_measurement_property(double, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_phimax;
	k3d_measurement_property(double, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_thetamax;

	std::vector<GLfloat> m_gl_u_knot_vector;
	std::vector<GLfloat> m_gl_v_knot_vector;
	std::vector<GLfloat> m_gl_control_points;
};

} // namespace

namespace libk3dcore
{

/////////////////////////////////////////////////////////////////////////////
// torus_factory

k3d::iplugin_factory& torus_factory()
{
	return torus_implementation::get_factory();
}

} // namespace libk3dcore



