// 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 Cone K-3D object, which renders a RenderMan cone primitive
		\author Tim Shead (tshead@k-3d.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
{

/////////////////////////////////////////////////////////////////////////////
// cone_implementation

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

public:
	cone_implementation(k3d::idocument& Document) :
		base(Document),
		m_radius(k3d::init_name("radius") + k3d::init_description("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_height(k3d::init_name("height") + k3d::init_description("height [distance]") + k3d::init_value(10.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_radius));
		enable_serialization(k3d::persistence::proxy(m_height));
		enable_serialization(k3d::persistence::proxy(m_thetamax));

		register_property(m_radius);
		register_property(m_height);
		register_property(m_thetamax);
		
		m_radius.changed_signal().connect(SigC::slot(*this, &cone_implementation::reset_geometry));
		m_height.changed_signal().connect(SigC::slot(*this, &cone_implementation::reset_geometry));
		m_thetamax.changed_signal().connect(SigC::slot(*this, &cone_implementation::reset_geometry));
		
		m_position.changed_signal().connect(SigC::slot(*this, &cone_implementation::async_redraw_all));
		m_orientation.changed_signal().connect(SigC::slot(*this, &cone_implementation::async_redraw_all));
		m_scale.changed_signal().connect(SigC::slot(*this, &cone_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(), 1);
		m_gl_v_knot_vector.insert(m_gl_v_knot_vector.end(), 2, 2);
	}

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

	const k3d::bounding_box extents()
	{
		const double radius = m_radius.property_value();
		const double height = m_height.property_value();

		return k3d::bounding_box(radius, -radius, height, 0.0, radius, -radius);
	}

	void draw(const nurbs_renderer_t Nurbs)
	{
		if(m_gl_control_points.empty())
			{
				const double radius = m_radius.property_value();
				const double height = m_height.property_value();
				const double thetamax = m_thetamax.property_value();

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

				for(unsigned long i = 0; i <= 2; ++i)
					{
						const double radius2 = k3d::mix(radius, 0.001, static_cast<double>(i) / static_cast<double>(2));
						const k3d::vector3 offset = k3d::mix(0.0, height, static_cast<double>(i) / static_cast<double>(2)) * k3d::vector3(0, 0, 1);

						for(unsigned long j = 0; j != arc_points.size(); ++j)
							{
								m_gl_control_points.push_back(weights[j] * (radius2 * arc_points[j][0] + offset[0]));
								m_gl_control_points.push_back(weights[j] * (radius2 * arc_points[j][1] + offset[1]));
								m_gl_control_points.push_back(weights[j] * (radius2 * arc_points[j][2] + offset[2]));
								m_gl_control_points.push_back(weights[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 double radius = m_radius.property_value();
		const double height = m_height.property_value();
		const double thetamax = k3d::degrees(m_thetamax.property_value());

		State.engine.RiTransformBegin();
		k3d::ri::setup_material(m_material.object(), State);
		State.engine.RiConeV(height, radius, thetamax);
		State.engine.RiTransformEnd();
	}

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

	static k3d::iplugin_factory& get_factory()
	{
		static k3d::plugin_factory<
			k3d::document_plugin<cone_implementation>,
			k3d::interface_list<k3d::itransform_source,
			k3d::interface_list<k3d::itransform_sink > > > factory(
			k3d::classes::Cone(),
			"Cone",
			"Renders a RenderMan cone 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_radius;
	k3d_measurement_property(double, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_height;
	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
{

/////////////////////////////////////////////////////////////////////////////
// cone_factory

k3d::iplugin_factory& cone_factory()
{
	return cone_implementation::get_factory();
}

} // namespace libk3dcore



