// 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
		\author Timothy M. Shead (tshead@k-3d.com)
*/

#include <k3dsdk/classes.h>
#include <k3dsdk/ibounded.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/plugins.h>
#include <k3dsdk/renderman.h>
#include <k3dsdk/transformable.h>
#include <k3dsdk/viewport.h>

namespace libk3drenderman
{

/////////////////////////////////////////////////////////////////////////////
// array_2d_implementation

class array_2d_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:
	array_2d_implementation(k3d::idocument& Document) :
		base(Document),
		m_instance(k3d::init_name("instance") + k3d::init_description("Instance [object]") + k3d::init_object_value(0) + k3d::init_document(Document)),
		m_u_count(k3d::init_name("ucount") + k3d::init_description("U Axis Count [integer]") + k3d::init_value(5) + k3d::init_document(Document) + k3d::init_constraint(k3d::constraint::minimum(0UL)) + k3d::init_precision(0) + k3d::init_step_increment(1.0) + k3d::init_units(typeid(k3d::measurement::scalar))),
		m_u_offsetx(k3d::init_name("uoffsetx") + k3d::init_description("U Axis X Offset [number]") + 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_u_offsety(k3d::init_name("uoffsety") + k3d::init_description("U Axis Y Offset [number]") + 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_u_offsetz(k3d::init_name("uoffsetz") + k3d::init_description("U Axis Z Offset [number]") + 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_u_rotatex(k3d::init_name("urotatex") + k3d::init_description("U Axis X Rotation [number]") + 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_u_rotatey(k3d::init_name("urotatey") + k3d::init_description("U Axis Y Rotation [number]") + 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_u_rotatez(k3d::init_name("urotatez") + k3d::init_description("U Axis Z Rotation [number]") + 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_v_count(k3d::init_name("vcount") + k3d::init_description("V Axis Count [integer]") + k3d::init_value(5) + k3d::init_document(Document) + k3d::init_constraint(k3d::constraint::minimum(0UL)) + k3d::init_precision(0) + k3d::init_step_increment(1.0) + k3d::init_units(typeid(k3d::measurement::scalar))),
		m_v_offsetx(k3d::init_name("voffsetx") + k3d::init_description("V Axis X Offset [number]") + 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_v_offsety(k3d::init_name("voffsety") + k3d::init_description("V Axis Y Offset [number]") + 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_v_offsetz(k3d::init_name("voffsetz") + k3d::init_description("V Axis Z Offset [number]") + 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_v_rotatex(k3d::init_name("vrotatex") + k3d::init_description("V Axis X Rotation [number]") + 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_v_rotatey(k3d::init_name("vrotatey") + k3d::init_description("V Axis Y Rotation [number]") + 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_v_rotatez(k3d::init_name("vrotatez") + k3d::init_description("V Axis Z Rotation [number]") + 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)))
	{
		enable_serialization(k3d::persistence::object_proxy(m_instance));
		enable_serialization(k3d::persistence::proxy(m_u_count));
		enable_serialization(k3d::persistence::proxy(m_u_offsetx));
		enable_serialization(k3d::persistence::proxy(m_u_offsety));
		enable_serialization(k3d::persistence::proxy(m_u_offsetz));
		enable_serialization(k3d::persistence::proxy(m_u_rotatex));
		enable_serialization(k3d::persistence::proxy(m_u_rotatey));
		enable_serialization(k3d::persistence::proxy(m_u_rotatez));
		enable_serialization(k3d::persistence::proxy(m_v_count));
		enable_serialization(k3d::persistence::proxy(m_v_offsetx));
		enable_serialization(k3d::persistence::proxy(m_v_offsety));
		enable_serialization(k3d::persistence::proxy(m_v_offsetz));
		enable_serialization(k3d::persistence::proxy(m_v_rotatex));
		enable_serialization(k3d::persistence::proxy(m_v_rotatey));
		enable_serialization(k3d::persistence::proxy(m_v_rotatez));

		register_property(m_instance);
		register_property(m_u_count);
		register_property(m_u_offsetx);
		register_property(m_u_offsety);
		register_property(m_u_offsetz);
		register_property(m_u_rotatex);
		register_property(m_u_rotatey);
		register_property(m_u_rotatez);
		register_property(m_v_count);
		register_property(m_v_offsetx);
		register_property(m_v_offsety);
		register_property(m_v_offsetz);
		register_property(m_v_rotatex);
		register_property(m_v_rotatey);
		register_property(m_v_rotatez);
	
		m_instance.changed_signal().connect(SigC::slot(*this, &array_2d_implementation::async_redraw_all));		
		m_u_count.changed_signal().connect(SigC::slot(*this, &array_2d_implementation::async_redraw_all));		
		m_u_offsetx.changed_signal().connect(SigC::slot(*this, &array_2d_implementation::async_redraw_all));		
		m_u_offsety.changed_signal().connect(SigC::slot(*this, &array_2d_implementation::async_redraw_all));		
		m_u_offsetz.changed_signal().connect(SigC::slot(*this, &array_2d_implementation::async_redraw_all));		
		m_u_rotatex.changed_signal().connect(SigC::slot(*this, &array_2d_implementation::async_redraw_all));		
		m_u_rotatey.changed_signal().connect(SigC::slot(*this, &array_2d_implementation::async_redraw_all));		
		m_u_rotatez.changed_signal().connect(SigC::slot(*this, &array_2d_implementation::async_redraw_all));		
		m_v_count.changed_signal().connect(SigC::slot(*this, &array_2d_implementation::async_redraw_all));		
		m_v_offsetx.changed_signal().connect(SigC::slot(*this, &array_2d_implementation::async_redraw_all));		
		m_v_offsety.changed_signal().connect(SigC::slot(*this, &array_2d_implementation::async_redraw_all));		
		m_v_offsetz.changed_signal().connect(SigC::slot(*this, &array_2d_implementation::async_redraw_all));		
		m_v_rotatex.changed_signal().connect(SigC::slot(*this, &array_2d_implementation::async_redraw_all));		
		m_v_rotatey.changed_signal().connect(SigC::slot(*this, &array_2d_implementation::async_redraw_all));		
		m_v_rotatez.changed_signal().connect(SigC::slot(*this, &array_2d_implementation::async_redraw_all));		
		
		m_position.changed_signal().connect(SigC::slot(*this, &array_2d_implementation::async_redraw_all));
		m_orientation.changed_signal().connect(SigC::slot(*this, &array_2d_implementation::async_redraw_all));
		m_scale.changed_signal().connect(SigC::slot(*this, &array_2d_implementation::async_redraw_all));
	}

	~array_2d_implementation()
	{
	}

	void draw(const k3d::viewport::render_state& State)
	{
		const unsigned long u_count = m_u_count.property_value();
		const double u_offsetx = m_u_offsetx.property_value();
		const double u_offsety = m_u_offsety.property_value();
		const double u_offsetz = m_u_offsetz.property_value();
		const double u_rotatex = m_u_rotatex.property_value();
		const double u_rotatey = m_u_rotatey.property_value();
		const double u_rotatez = m_u_rotatez.property_value();
		const unsigned long v_count = m_v_count.property_value();
		const double v_offsetx = m_v_offsetx.property_value();
		const double v_offsety = m_v_offsety.property_value();
		const double v_offsetz = m_v_offsetz.property_value();
		const double v_rotatex = m_v_rotatex.property_value();
		const double v_rotatey = m_v_rotatey.property_value();
		const double v_rotatez = m_v_rotatez.property_value();

		const k3d::vector3 u_offset(u_offsetx, u_offsety, u_offsetz);
		const k3d::vector3 v_offset(v_offsetx, v_offsety, v_offsetz);
		
		const k3d::vector3 u_rotation(u_rotatex, u_rotatey, u_rotatez);
		const k3d::vector3 v_rotation(v_rotatex, v_rotatey, v_rotatez);

		glDisable(GL_LIGHTING);
		glColor3d(0, 1, 1);

		glBegin(GL_POINTS);
		for(unsigned long u = 0; u != u_count; ++u)
			{
				for(unsigned long v = 0; v != v_count; ++v)
					{
						glVertex3dv((u * u_offset) + (v * v_offset));
					}
			}
		glEnd();

		k3d::ibounded* const bounded = dynamic_cast<k3d::ibounded*>(m_instance.interface());
		if(!bounded)
			return;

		const k3d::bounding_box bounds = bounded->extents();
		if(bounds.empty())
			return;
		
		glMatrixMode(GL_MODELVIEW);
		for(unsigned long u = 0; u != u_count; ++u)
			{
				for(unsigned long v = 0; v != v_count; ++v)
					{
						glPushMatrix();
						k3d::push_opengl_matrix(k3d::translation3D(u * u_offset) * k3d::translation3D(v * v_offset) * k3d::rotation3D(u * u_rotation) * k3d::rotation3D(v * v_rotation));
						k3d::viewport::draw_bounding_box(bounds);
						glPopMatrix();
					}
			}
	}

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

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

	void on_renderman_render(const k3d::ri::render_state& State)
	{
		k3d::ri::irenderable* const renderable = m_instance.interface();
		if(!renderable)
			return;

		// Make sure we don't enter an infinite loop trying to render ourself ...
		if(renderable == this)
			{
				std::cerr << error << factory().name() << " [" << name() << "] cannot instance itself" << std::endl;
				return;
			}

		const k3d::ri::object_handle handle = State.engine.RiObjectBegin();
			k3d::ri::render_state state(State);
			state.render_context = k3d::ri::OBJECT_INSTANCE;
			renderable->renderman_render(state);
		State.engine.RiObjectEnd();

		const unsigned long u_count = m_u_count.property_value();
		const double u_offsetx = m_u_offsetx.property_value();
		const double u_offsety = m_u_offsety.property_value();
		const double u_offsetz = m_u_offsetz.property_value();
		const double u_rotatex = m_u_rotatex.property_value();
		const double u_rotatey = m_u_rotatey.property_value();
		const double u_rotatez = m_u_rotatez.property_value();
		
		const unsigned long v_count = m_v_count.property_value();
		const double v_offsetx = m_v_offsetx.property_value();
		const double v_offsety = m_v_offsety.property_value();
		const double v_offsetz = m_v_offsetz.property_value();
		const double v_rotatex = m_v_rotatex.property_value();
		const double v_rotatey = m_v_rotatey.property_value();
		const double v_rotatez = m_v_rotatez.property_value();
		
		const k3d::vector3 u_offset(u_offsetx, u_offsety, u_offsetz);
		const k3d::vector3 v_offset(v_offsetx, v_offsety, v_offsetz);
		
		const k3d::vector3 u_rotation(u_rotatex, u_rotatey, u_rotatez);
		const k3d::vector3 v_rotation(v_rotatex, v_rotatey, v_rotatez);

		k3d::ri::setup_material(m_material.interface(), State);

		for(unsigned long u = 0; u != u_count; ++u)
			{
				for(unsigned long v = 0; v != v_count; ++v)
					{
						State.engine.RiAttributeBegin();
						State.engine.RiConcatTransform(k3d::ri::convert(k3d::translation3D(u * u_offset) * k3d::translation3D(v * v_offset) * k3d::rotation3D(u * u_rotation) * k3d::rotation3D(v * v_rotation)));
						State.engine.RiObjectInstance(handle);
						State.engine.RiAttributeEnd();
					}
			}
	}

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

	static k3d::iplugin_factory& get_factory()
	{
		static k3d::plugin_factory<
			k3d::document_plugin<array_2d_implementation> > factory(
				k3d::uuid(0x5a7881eb, 0xf8e14075, 0x977fe1fb, 0x2036f517),
				"RenderManArray2D",
				"Renders a two-dimensional array of geometric object instances",
				"Objects",
				k3d::iplugin_factory::EXPERIMENTAL);

		return factory;
	}

private:
	k3d_object_property(k3d::ri::irenderable, k3d::immutable_name, k3d::with_undo, k3d::local_storage) m_instance;
	k3d_measurement_property(unsigned long, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::with_constraint) m_u_count;
	k3d_measurement_property(double, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_u_offsetx;
	k3d_measurement_property(double, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_u_offsety;
	k3d_measurement_property(double, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_u_offsetz;
	k3d_measurement_property(double, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_u_rotatex;
	k3d_measurement_property(double, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_u_rotatey;
	k3d_measurement_property(double, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_u_rotatez;
	k3d_measurement_property(unsigned long, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::with_constraint) m_v_count;
	k3d_measurement_property(double, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_v_offsetx;
	k3d_measurement_property(double, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_v_offsety;
	k3d_measurement_property(double, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_v_offsetz;
	k3d_measurement_property(double, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_v_rotatex;
	k3d_measurement_property(double, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_v_rotatey;
	k3d_measurement_property(double, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_v_rotatez;
};

/////////////////////////////////////////////////////////////////////////////
// array_2d_factory

k3d::iplugin_factory& array_2d_factory()
{
	return array_2d_implementation::get_factory();
}

} // namespace libk3drenderman


