// 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 Paraboloid K-3D object, which renders a RenderMan paraboloid 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/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
{

/////////////////////////////////////////////////////////////////////////////
// paraboloid_implementation

class paraboloid_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:
	paraboloid_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_zmax(k3d::init_name("zmax") + k3d::init_description("zmax [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_zmin(k3d::init_name("zmin") + k3d::init_description("zmin [distance]") + k3d::init_value(0.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_zmax));
		enable_serialization(k3d::persistence::proxy(m_zmin));
		enable_serialization(k3d::persistence::proxy(m_thetamax));

		register_property(m_radius);
		register_property(m_zmin);
		register_property(m_zmax);
		register_property(m_thetamax);
		
		m_radius.changed_signal().connect(SigC::slot(*this, &paraboloid_implementation::async_redraw_all));
		m_zmin.changed_signal().connect(SigC::slot(*this, &paraboloid_implementation::async_redraw_all));
		m_zmax.changed_signal().connect(SigC::slot(*this, &paraboloid_implementation::async_redraw_all));
		m_thetamax.changed_signal().connect(SigC::slot(*this, &paraboloid_implementation::async_redraw_all));
		
		m_position.changed_signal().connect(SigC::slot(*this, &paraboloid_implementation::async_redraw_all));
		m_orientation.changed_signal().connect(SigC::slot(*this, &paraboloid_implementation::async_redraw_all));
		m_scale.changed_signal().connect(SigC::slot(*this, &paraboloid_implementation::async_redraw_all));
	}

	const k3d::bounding_box extents()
	{
		const double radius = m_radius.property_value();
		const double zmin = m_zmin.property_value();
		const double zmax = m_zmax.property_value();
		
		return k3d::bounding_box(radius, -radius, zmax, zmin, radius, -radius);
	}

	void draw()
	{
		const double radius = m_radius.property_value();
		const double zmin = std::max(0.0, m_zmin.property_value());
		const double zmax = m_zmax.property_value();
		const double thetamax = m_thetamax.property_value();

		const unsigned long nsides = 16;
		const unsigned long nlevels = 8;

		const double start = 0;
		const double step = thetamax / static_cast<double>(nsides);
		for(unsigned long j = 0; j < nlevels; j++)
			{
				const double zmax1 = zmin + (zmax - zmin) * static_cast<double>(j+1)/static_cast<double>(nlevels);
				const double zmax2 = zmin + (zmax - zmin) * static_cast<double>(j)/static_cast<double>(nlevels);
				const double radius1 = radius * sqrt(zmax1 / zmax);
				const double radius2 = std::max(radius * 0.001, radius * sqrt(zmax2 / zmax));

				for(unsigned long i = 0; i < nsides; i++)
					{
						glBegin(GL_POLYGON);
							double current = start + static_cast<double>(i) * step;
						
							k3d::vector3 p1(radius1*cos(current), radius1*sin(current), zmax1);
							k3d::vector3 p2(radius2*cos(current), radius2*sin(current), zmax2);
							k3d::vector3 p3(radius2*cos(current + step), radius2*sin(current + step), zmax2);
							k3d::vector3 p4(radius1*cos(current + step), radius1*sin(current + step), zmax1);
							
							glNormal3dv((p3 - p4) ^ (p3 - p2));
							
							glVertex3dv(p1);
							glVertex3dv(p2);
							glVertex3dv(p3);
							glVertex3dv(p4);
						
						glEnd();
					}
			}

	}

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

		glDisable(GL_CULL_FACE);
		glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
		glColor3dv(is_selected() ? k3d::vector3(1, 1, 1) : k3d::vector3(0, 0, 0));
		glDisable(GL_LIGHTING);
		draw();

		if(!State.draw_two_sided)
			glEnable(GL_CULL_FACE);

		glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
		glEnable(GL_LIGHTING);
		glPolygonOffset(1.0, 1.0);
		glEnable(GL_POLYGON_OFFSET_FILL);
		draw();
		glDisable(GL_POLYGON_OFFSET_FILL);
	}

	void on_viewport_select(const k3d::viewport::render_state& State)
	{
		glDisable(GL_LIGHTING);
		glDisable(GL_CULL_FACE);

		k3d::glPushName(this);
		draw();
		k3d::glPopName();
	}

	void on_renderman_render(const k3d::ri::render_state& State)
	{
		const double radius = m_radius.property_value();
		const double zmin = m_zmin.property_value();
		const double zmax = m_zmax.property_value();
		const double thetamax = k3d::degrees(m_thetamax.property_value());

		State.engine.RiTransformBegin();
		k3d::ri::setup_material(m_material.object(), State);
		State.engine.RiParaboloidV(radius, zmin, zmax, thetamax);
		State.engine.RiTransformEnd();
	}

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

	static k3d::iplugin_factory& get_factory()
	{
		static k3d::plugin_factory<
			k3d::document_plugin<paraboloid_implementation>,
			k3d::interface_list<k3d::itransform_source,
			k3d::interface_list<k3d::itransform_sink > > > factory(
			k3d::classes::Paraboloid(),
			"Paraboloid",
			"Renders a RenderMan paraboloid 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_zmax;
	k3d_measurement_property(double, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_zmin;
	k3d_measurement_property(double, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_thetamax;
};

} // namespace

namespace libk3dcore
{

/////////////////////////////////////////////////////////////////////////////
// paraboloid_factory

k3d::iplugin_factory& paraboloid_factory()
{
	return paraboloid_implementation::get_factory();
}

} // namespace libk3dcore



