// 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 Hyperboloid K-3D object, which renders a RenderMan hyperboloid primitive
		\author Romain Behar (romainbehar@yahoo.com)
*/

#include <k3dsdk/algebra.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
{

/////////////////////////////////////////////////////////////////////////////
// hyperboloid_implementation

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

public:
	hyperboloid_implementation(k3d::idocument& Document) :
		base(Document),
		m_x1(k3d::init_name("x1") + k3d::init_description("x1 [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_y1(k3d::init_name("y1") + k3d::init_description("y1 [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_z1(k3d::init_name("z1") + k3d::init_description("z1 [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_x2(k3d::init_name("x2") + k3d::init_description("x2 [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_y2(k3d::init_name("y2") + k3d::init_description("y2 [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_z2(k3d::init_name("z2") + k3d::init_description("z2 [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_thetamax(k3d::init_name("thetamax") + k3d::init_description("thetamax [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_x1));
		enable_serialization(k3d::persistence::proxy(m_y1));
		enable_serialization(k3d::persistence::proxy(m_z1));
		enable_serialization(k3d::persistence::proxy(m_x2));
		enable_serialization(k3d::persistence::proxy(m_y2));
		enable_serialization(k3d::persistence::proxy(m_z2));
		enable_serialization(k3d::persistence::proxy(m_thetamax));

		register_property(m_x1);
		register_property(m_y1);
		register_property(m_z1);
		register_property(m_x2);
		register_property(m_y2);
		register_property(m_z2);
		register_property(m_thetamax);
		
		m_x1.changed_signal().connect(SigC::slot(*this, &hyperboloid_implementation::reset_geometry));
		m_y1.changed_signal().connect(SigC::slot(*this, &hyperboloid_implementation::reset_geometry));
		m_z1.changed_signal().connect(SigC::slot(*this, &hyperboloid_implementation::reset_geometry));
		m_x2.changed_signal().connect(SigC::slot(*this, &hyperboloid_implementation::reset_geometry));
		m_y2.changed_signal().connect(SigC::slot(*this, &hyperboloid_implementation::reset_geometry));
		m_z2.changed_signal().connect(SigC::slot(*this, &hyperboloid_implementation::reset_geometry));
		m_thetamax.changed_signal().connect(SigC::slot(*this, &hyperboloid_implementation::reset_geometry));
		
		m_position.changed_signal().connect(SigC::slot(*this, &hyperboloid_implementation::async_redraw_all));
		m_orientation.changed_signal().connect(SigC::slot(*this, &hyperboloid_implementation::async_redraw_all));
		m_scale.changed_signal().connect(SigC::slot(*this, &hyperboloid_implementation::async_redraw_all));
	
		m_gl_v_knot_vector.insert(m_gl_v_knot_vector.end(), 2, 0);
		m_gl_v_knot_vector.insert(m_gl_v_knot_vector.end(), 2, 1);
	}

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

	void draw(const nurbs_renderer_t Nurbs)
	{
		if(m_gl_control_points.empty())
			{
				const double x1 = m_x1.property_value();
				const double y1 = m_y1.property_value();
				const double z1 = m_z1.property_value();
				const double x2 = m_x2.property_value();
				const double y2 = m_y2.property_value();
				const double z2 = m_z2.property_value();
				const double thetamax = m_thetamax.property_value();

				const double thetamin1 = atan2(y1, x1);
				const double thetamin2 = atan2(y2, x2);

				const double radius1 = sqrt(x1 * x1 + y1 * y1);
				const double radius2 = sqrt(x2 * x2 + y2 * y2);

				const k3d::vector3 offset1 = z1 * k3d::vector3(0, 0, 1);
				const k3d::vector3 offset2 = z2 * k3d::vector3(0, 0, 1);

				std::vector<double> weights1;
				std::vector<k3d::vector3> arc_points1;
				k3d::nurbs_arc(k3d::vector3(1, 0, 0), k3d::vector3(0, 1, 0), thetamin1, thetamin1 + thetamax, 4, m_gl_u_knot_vector, weights1, arc_points1);
				
				std::vector<double> weights2;
				std::vector<k3d::vector3> arc_points2;
				k3d::nurbs_arc(k3d::vector3(1, 0, 0), k3d::vector3(0, 1, 0), thetamin2, thetamin2 + thetamax, 4, m_gl_u_knot_vector, weights2, arc_points2);
				
				for(unsigned long j = 0; j != arc_points1.size(); ++j)
					{
						m_gl_control_points.push_back(weights1[j] * (radius1 * arc_points1[j][0] + offset1[0]));
						m_gl_control_points.push_back(weights1[j] * (radius1 * arc_points1[j][1] + offset1[1]));
						m_gl_control_points.push_back(weights1[j] * (radius1 * arc_points1[j][2] + offset1[2]));
						m_gl_control_points.push_back(weights1[j]);
					}
				
				for(unsigned long j = 0; j != arc_points2.size(); ++j)
					{
						m_gl_control_points.push_back(weights2[j] * (radius2 * arc_points2[j][0] + offset2[0]));
						m_gl_control_points.push_back(weights2[j] * (radius2 * arc_points2[j][1] + offset2[1]));
						m_gl_control_points.push_back(weights2[j] * (radius2 * arc_points2[j][2] + offset2[2]));
						m_gl_control_points.push_back(weights2[j]);
					}
			}

		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, 2, 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 k3d::ri::point p1(k3d::vector3(m_x1.property_value(), m_y1.property_value(), m_z1.property_value()));
		const k3d::ri::point p2(k3d::vector3(m_x2.property_value(), m_y2.property_value(), m_z2.property_value()));
		const double thetamax = k3d::degrees(m_thetamax.property_value());

		State.engine.RiTransformBegin();
		k3d::ri::setup_material(m_material.object(), State);
		State.engine.RiHyperboloidV(p1, p2, thetamax);
		State.engine.RiTransformEnd();
	}

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

	static k3d::iplugin_factory& get_factory()
	{
		static k3d::plugin_factory<
			k3d::document_plugin<hyperboloid_implementation>,
			k3d::interface_list<k3d::itransform_source,
			k3d::interface_list<k3d::itransform_source > > > factory(
			k3d::classes::Hyperboloid(),
			"Hyperboloid",
			"Renders a RenderMan hyperboloid 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_x1;
	k3d_measurement_property(double, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_y1;
	k3d_measurement_property(double, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_z1;
	k3d_measurement_property(double, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_x2;
	k3d_measurement_property(double, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_y2;
	k3d_measurement_property(double, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_z2;
	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
{

/////////////////////////////////////////////////////////////////////////////
// hyperboloid_factory

k3d::iplugin_factory& hyperboloid_factory()
{
	return hyperboloid_implementation::get_factory();
}

} // namespace libk3dcore








