// 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 RenderMan Blobby primitives and operators
		\author Romain Behar (romainbehar@yahoo.com)
*/

#include <k3dsdk/color.h>
#include <k3dsdk/measurement.h>
#include <k3dsdk/mesh_source.h>
#include <k3dsdk/module.h>
#include <k3dsdk/object.h>
#include <k3dsdk/persistence.h>
#include <k3dsdk/plugins.h>
#include <k3dsdk/transform.h>

namespace libk3dmesh
{

/////////////////////////////////////////////////////////////////////////////
// blobby_ellipsoid_implementation

class blobby_ellipsoid_implementation :
	public k3d::mesh_source<k3d::persistent<k3d::object> >
{
	typedef k3d::mesh_source<k3d::persistent<k3d::object> > base;

public:
	blobby_ellipsoid_implementation(k3d::idocument& Document) :
		base(Document),
		m_x(k3d::init_name("x") + k3d::init_description("X position [number]") + k3d::init_document(Document) + k3d::init_value(0.0) + k3d::init_precision(2) + k3d::init_step_increment(0.1) + k3d::init_units(typeid(k3d::measurement::distance))),
		m_y(k3d::init_name("y") + k3d::init_description("Y position [number]") + k3d::init_document(Document) + k3d::init_value(0.0) + k3d::init_precision(2) + k3d::init_step_increment(0.1) + k3d::init_units(typeid(k3d::measurement::distance))),
		m_z(k3d::init_name("z") + k3d::init_description("Z position [number]") + k3d::init_document(Document) + k3d::init_value(0.0) + k3d::init_precision(2) + k3d::init_step_increment(0.1) + k3d::init_units(typeid(k3d::measurement::distance))),
		m_size_x(k3d::init_name("size_x") + k3d::init_description("X size [number]") + k3d::init_document(Document) + k3d::init_value(1.0) + k3d::init_precision(2) + k3d::init_step_increment(0.1) + k3d::init_constraint(k3d::constraint::minimum(0.1)) + k3d::init_units(typeid(k3d::measurement::distance))),
		m_size_y(k3d::init_name("size_y") + k3d::init_description("Y size [number]") + k3d::init_document(Document) + k3d::init_value(1.0) + k3d::init_precision(2) + k3d::init_step_increment(0.1) + k3d::init_constraint(k3d::constraint::minimum(0.1)) + k3d::init_units(typeid(k3d::measurement::distance))),
		m_size_z(k3d::init_name("size_z") + k3d::init_description("Z size [number]") + k3d::init_document(Document) + k3d::init_value(1.0) + k3d::init_precision(2) + k3d::init_step_increment(0.1) + k3d::init_constraint(k3d::constraint::minimum(0.1)) + k3d::init_units(typeid(k3d::measurement::distance))),
		m_color(k3d::init_name("color") + k3d::init_description("Color [color]") + k3d::init_value(k3d::color(1, 1, 1)) + k3d::init_document(Document))
	{
		register_property(m_color);
		enable_serialization(k3d::persistence::proxy(m_color));

		enable_serialization(k3d::persistence::proxy(m_x));
		enable_serialization(k3d::persistence::proxy(m_y));
		enable_serialization(k3d::persistence::proxy(m_z));

		register_property(m_x);
		register_property(m_y);
		register_property(m_z);

		enable_serialization(k3d::persistence::proxy(m_size_x));
		enable_serialization(k3d::persistence::proxy(m_size_y));
		enable_serialization(k3d::persistence::proxy(m_size_z));

		register_property(m_size_x);
		register_property(m_size_y);
		register_property(m_size_z);

		m_x.changed_signal().connect(SigC::slot(*this, &blobby_ellipsoid_implementation::on_reset_geometry));
		m_y.changed_signal().connect(SigC::slot(*this, &blobby_ellipsoid_implementation::on_reset_geometry));
		m_z.changed_signal().connect(SigC::slot(*this, &blobby_ellipsoid_implementation::on_reset_geometry));

		m_size_x.changed_signal().connect(SigC::slot(*this, &blobby_ellipsoid_implementation::on_reset_geometry));
		m_size_y.changed_signal().connect(SigC::slot(*this, &blobby_ellipsoid_implementation::on_reset_geometry));
		m_size_z.changed_signal().connect(SigC::slot(*this, &blobby_ellipsoid_implementation::on_reset_geometry));

		m_output_mesh.need_data_signal().connect(SigC::slot(*this, &blobby_ellipsoid_implementation::on_create_geometry));
	}

	void on_reset_geometry()
	{
		m_output_mesh.reset();
	}

	k3d::mesh* on_create_geometry()
	{
		std::auto_ptr<k3d::mesh> mesh(new k3d::mesh());

		k3d::point* position = new k3d::point(m_x.property_value(), m_y.property_value(), m_z.property_value());
		mesh->points.push_back(position);

		k3d::matrix4 transformation = k3d::scaling3D(k3d::vector3(m_size_x.property_value(), m_size_y.property_value(), m_size_z.property_value()));
		k3d::blobby::ellipsoid* ellipsoid = new k3d::blobby::ellipsoid(position, transformation);
		ellipsoid->vertex_data["Cs"] = m_color.property_value();

		mesh->blobbies.push_back(new k3d::blobby(ellipsoid));

		return mesh.release();
	}

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

	static k3d::iplugin_factory& get_factory()
	{
		static k3d::plugin_factory<k3d::document_plugin<blobby_ellipsoid_implementation> > factory(
			k3d::uuid(0x76ba2f7c, 0xd49945e2, 0xa54d32b0, 0x0f756a94),
			"BlobbyEllipsoid",
			"Creates a RenderMan Blobby Ellipsoid",
			"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_x;
	k3d_measurement_property(double, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_y;
	k3d_measurement_property(double, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_z;
	k3d_measurement_property(double, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::with_constraint) m_size_x;
	k3d_measurement_property(double, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::with_constraint) m_size_y;
	k3d_measurement_property(double, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::with_constraint) m_size_z;
	k3d_data_property(k3d::color, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_color;
};

/////////////////////////////////////////////////////////////////////////////
// blobby_segment_implementation

class blobby_segment_implementation :
	public k3d::mesh_source<k3d::persistent<k3d::object> >
{
	typedef k3d::mesh_source<k3d::persistent<k3d::object> > base;

public:
	blobby_segment_implementation(k3d::idocument& Document) :
		base(Document),
		m_radius(k3d::init_name("radius") + k3d::init_description("Radius [number]") + k3d::init_value(1.0) + k3d::init_precision(2) + k3d::init_step_increment(0.1) + k3d::init_units(typeid(k3d::measurement::distance)) + k3d::init_document(Document)),
		m_x1(k3d::init_name("x1") + k3d::init_description("X1 position [number]") + k3d::init_document(Document) + k3d::init_value(0.0) + 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 position [number]") + k3d::init_document(Document) + k3d::init_value(0.0) + 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 position [number]") + k3d::init_document(Document) + k3d::init_value(0.0) + 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 position [number]") + k3d::init_document(Document) + k3d::init_value(3.0) + 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 position [number]") + k3d::init_document(Document) + k3d::init_value(0.0) + 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 position [number]") + k3d::init_document(Document) + k3d::init_value(0.0) + k3d::init_precision(2) + k3d::init_step_increment(0.1) + k3d::init_units(typeid(k3d::measurement::distance))),
		m_color(k3d::init_name("color") + k3d::init_description("Color [color]") + k3d::init_value(k3d::color(1, 1, 1)) + k3d::init_document(Document))
	{
		enable_serialization(k3d::persistence::proxy(m_radius));
		register_property(m_radius);

		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));

		register_property(m_x1);
		register_property(m_y1);
		register_property(m_z1);
		register_property(m_x2);
		register_property(m_y2);
		register_property(m_z2);

		enable_serialization(k3d::persistence::proxy(m_color));
		register_property(m_color);

		m_x1.changed_signal().connect(SigC::slot(*this, &blobby_segment_implementation::on_reset_geometry));
		m_y1.changed_signal().connect(SigC::slot(*this, &blobby_segment_implementation::on_reset_geometry));
		m_z1.changed_signal().connect(SigC::slot(*this, &blobby_segment_implementation::on_reset_geometry));
		m_x2.changed_signal().connect(SigC::slot(*this, &blobby_segment_implementation::on_reset_geometry));
		m_y2.changed_signal().connect(SigC::slot(*this, &blobby_segment_implementation::on_reset_geometry));
		m_z2.changed_signal().connect(SigC::slot(*this, &blobby_segment_implementation::on_reset_geometry));

		m_radius.changed_signal().connect(SigC::slot(*this, &blobby_segment_implementation::on_reset_geometry));

		m_output_mesh.need_data_signal().connect(SigC::slot(*this, &blobby_segment_implementation::on_create_geometry));
	}

	void on_reset_geometry()
	{
		m_output_mesh.reset();
	}

	k3d::mesh* on_create_geometry()
	{
		std::auto_ptr<k3d::mesh> mesh(new k3d::mesh());

		k3d::point* start = new k3d::point(m_x1.property_value(), m_y1.property_value(), m_z1.property_value());
		k3d::point* end = new k3d::point(m_x2.property_value(), m_y2.property_value(), m_z2.property_value());
		mesh->points.push_back(start);
		mesh->points.push_back(end);

		k3d::matrix4 id = k3d::identity3D();
		k3d::blobby::segment* segment = new k3d::blobby::segment(start, end, m_radius.property_value(), id);
		segment->vertex_data["Cs"] = m_color.property_value();

		mesh->blobbies.push_back(new k3d::blobby(segment));

		return mesh.release();
	}

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

	static k3d::iplugin_factory& get_factory()
	{
		static k3d::plugin_factory<k3d::document_plugin<blobby_segment_implementation> > factory(
			k3d::uuid(0x975d22ad, 0xe55e41a5, 0x9fdb26d2, 0x529952e0),
			"BlobbySegment",
			"Creates a RenderMan Blobby Segment",
			"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_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_data_property(k3d::color, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_color;
};

/////////////////////////////////////////////////////////////////////////////
// blobby_add_operator_implementation

class blobby_add_operator_implementation :
	public k3d::mesh_source<k3d::persistent<k3d::object> >
{
	typedef k3d::mesh_source<k3d::persistent<k3d::object> > base;

public:
	blobby_add_operator_implementation(k3d::idocument& Document) :
		base(Document),
		m_input_mesh1(k3d::init_name("input_mesh1") + k3d::init_description("Input mesh 1") + k3d::init_value<k3d::mesh*>(0) + k3d::init_document(Document)),
		m_input_mesh2(k3d::init_name("input_mesh2") + k3d::init_description("Input mesh 2") + k3d::init_value<k3d::mesh*>(0) + k3d::init_document(Document))
	{
		register_property(m_input_mesh1);
		register_property(m_input_mesh2);

		m_input_mesh1.changed_signal().connect(SigC::slot(*this, &blobby_add_operator_implementation::on_reset_geometry));
		m_input_mesh2.changed_signal().connect(SigC::slot(*this, &blobby_add_operator_implementation::on_reset_geometry));

		m_output_mesh.need_data_signal().connect(SigC::slot(*this, &blobby_add_operator_implementation::on_create_geometry));
	}

	void on_reset_geometry()
	{
		m_output_mesh.reset();
	}

	k3d::mesh* on_create_geometry()
	{
		// Get the input geometry ...
		const k3d::mesh* const input_mesh1 = m_input_mesh1.property_value();
		const k3d::mesh* const input_mesh2 = m_input_mesh2.property_value();

		// Create output geometry ...
		k3d::mesh* const output = new k3d::mesh();
		if(input_mesh1)
			k3d::deep_copy(*input_mesh1, *output);

		if(input_mesh2)
			k3d::deep_copy(*input_mesh2, *output);

		k3d::blobby::add* addition = new k3d::blobby::add();
		for(k3d::mesh::blobbies_t::const_iterator blob = output->blobbies.begin(); blob != output->blobbies.end(); blob++)
			addition->add_operand((*blob)->root);

		output->blobbies.clear();
		output->blobbies.push_back(new k3d::blobby(addition));

		return output;
	}

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

	static k3d::iplugin_factory& get_factory()
	{
		static k3d::plugin_factory<k3d::document_plugin<blobby_add_operator_implementation> > factory(
			k3d::uuid(0x580c8ebb, 0x300d49dc, 0x9c43cbf6, 0x2808872a),
			"BlobbyAddOperator",
			"Blobby Add operator",
			"Objects",
			k3d::iplugin_factory::STABLE);

		return factory;
	}

private:
	k3d_data_property(k3d::mesh*, k3d::immutable_name, k3d::change_signal, k3d::no_undo, k3d::local_storage, k3d::no_constraint) m_input_mesh1;
	k3d_data_property(k3d::mesh*, k3d::immutable_name, k3d::change_signal, k3d::no_undo, k3d::local_storage, k3d::no_constraint) m_input_mesh2;
};

/////////////////////////////////////////////////////////////////////////////
// blobby_mult_operator_implementation

class blobby_mult_operator_implementation :
	public k3d::mesh_source<k3d::persistent<k3d::object> >
{
	typedef k3d::mesh_source<k3d::persistent<k3d::object> > base;

public:
	blobby_mult_operator_implementation(k3d::idocument& Document) :
		base(Document),
		m_input_mesh1(k3d::init_name("input_mesh1") + k3d::init_description("Input mesh 1") + k3d::init_value<k3d::mesh*>(0) + k3d::init_document(Document)),
		m_input_mesh2(k3d::init_name("input_mesh2") + k3d::init_description("Input mesh 2") + k3d::init_value<k3d::mesh*>(0) + k3d::init_document(Document))
	{
		register_property(m_input_mesh1);
		register_property(m_input_mesh2);

		m_input_mesh1.changed_signal().connect(SigC::slot(*this, &blobby_mult_operator_implementation::on_reset_geometry));
		m_input_mesh2.changed_signal().connect(SigC::slot(*this, &blobby_mult_operator_implementation::on_reset_geometry));

		m_output_mesh.need_data_signal().connect(SigC::slot(*this, &blobby_mult_operator_implementation::on_create_geometry));
	}

	void on_reset_geometry()
	{
		m_output_mesh.reset();
	}

	k3d::mesh* on_create_geometry()
	{
		// Get the input geometry ...
		const k3d::mesh* const input_mesh1 = m_input_mesh1.property_value();
		const k3d::mesh* const input_mesh2 = m_input_mesh2.property_value();

		// Create output geometry ...
		k3d::mesh* const output = new k3d::mesh();
		if(input_mesh1)
			k3d::deep_copy(*input_mesh1, *output);

		if(input_mesh2)
			k3d::deep_copy(*input_mesh2, *output);

		k3d::blobby::multiply* multiplication = new k3d::blobby::multiply();
		for(k3d::mesh::blobbies_t::const_iterator blob = output->blobbies.begin(); blob != output->blobbies.end(); blob++)
			multiplication->add_operand((*blob)->root);

		output->blobbies.clear();
		output->blobbies.push_back(new k3d::blobby(multiplication));

		return output;
	}

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

	static k3d::iplugin_factory& get_factory()
	{
		static k3d::plugin_factory<k3d::document_plugin<blobby_mult_operator_implementation> > factory(
			k3d::uuid(0x51777220, 0xbf7346fc, 0xbf66e8bf, 0x349853b9),
			"BlobbyMultOperator",
			"Blobby Mult operator",
			"Objects",
			k3d::iplugin_factory::STABLE);

		return factory;
	}

private:
	k3d_data_property(k3d::mesh*, k3d::immutable_name, k3d::change_signal, k3d::no_undo, k3d::local_storage, k3d::no_constraint) m_input_mesh1;
	k3d_data_property(k3d::mesh*, k3d::immutable_name, k3d::change_signal, k3d::no_undo, k3d::local_storage, k3d::no_constraint) m_input_mesh2;
};

/////////////////////////////////////////////////////////////////////////////
// blobby_max_operator_implementation

class blobby_max_operator_implementation :
	public k3d::mesh_source<k3d::persistent<k3d::object> >
{
	typedef k3d::mesh_source<k3d::persistent<k3d::object> > base;

public:
	blobby_max_operator_implementation(k3d::idocument& Document) :
		base(Document),
		m_input_mesh1(k3d::init_name("input_mesh1") + k3d::init_description("Input mesh 1") + k3d::init_value<k3d::mesh*>(0) + k3d::init_document(Document)),
		m_input_mesh2(k3d::init_name("input_mesh2") + k3d::init_description("Input mesh 2") + k3d::init_value<k3d::mesh*>(0) + k3d::init_document(Document))
	{
		register_property(m_input_mesh1);
		register_property(m_input_mesh2);

		m_input_mesh1.changed_signal().connect(SigC::slot(*this, &blobby_max_operator_implementation::on_reset_geometry));
		m_input_mesh2.changed_signal().connect(SigC::slot(*this, &blobby_max_operator_implementation::on_reset_geometry));

		m_output_mesh.need_data_signal().connect(SigC::slot(*this, &blobby_max_operator_implementation::on_create_geometry));
	}

	void on_reset_geometry()
	{
		m_output_mesh.reset();
	}

	k3d::mesh* on_create_geometry()
	{
		// Get the input geometry ...
		const k3d::mesh* const input_mesh1 = m_input_mesh1.property_value();
		const k3d::mesh* const input_mesh2 = m_input_mesh2.property_value();

		// Create output geometry ...
		k3d::mesh* const output = new k3d::mesh();
		if(input_mesh1)
			k3d::deep_copy(*input_mesh1, *output);

		if(input_mesh2)
			k3d::deep_copy(*input_mesh2, *output);

		k3d::blobby::max* maximum = new k3d::blobby::max();
		for(k3d::mesh::blobbies_t::const_iterator blob = output->blobbies.begin(); blob != output->blobbies.end(); blob++)
			maximum->add_operand((*blob)->root);

		output->blobbies.clear();
		output->blobbies.push_back(new k3d::blobby(maximum));

		return output;
	}

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

	static k3d::iplugin_factory& get_factory()
	{
		static k3d::plugin_factory<k3d::document_plugin<blobby_max_operator_implementation> > factory(
			k3d::uuid(0x4a8e2958, 0x58d546aa, 0xad7bfb37, 0x5c67450b),
			"BlobbyMaxOperator",
			"Blobby Max operator",
			"Objects",
			k3d::iplugin_factory::STABLE);

		return factory;
	}

private:
	k3d_data_property(k3d::mesh*, k3d::immutable_name, k3d::change_signal, k3d::no_undo, k3d::local_storage, k3d::no_constraint) m_input_mesh1;
	k3d_data_property(k3d::mesh*, k3d::immutable_name, k3d::change_signal, k3d::no_undo, k3d::local_storage, k3d::no_constraint) m_input_mesh2;
};

/////////////////////////////////////////////////////////////////////////////
// blobby_min_operator_implementation

class blobby_min_operator_implementation :
	public k3d::mesh_source<k3d::persistent<k3d::object> >
{
	typedef k3d::mesh_source<k3d::persistent<k3d::object> > base;

public:
	blobby_min_operator_implementation(k3d::idocument& Document) :
		base(Document),
		m_input_mesh1(k3d::init_name("input_mesh1") + k3d::init_description("Input mesh 1") + k3d::init_value<k3d::mesh*>(0) + k3d::init_document(Document)),
		m_input_mesh2(k3d::init_name("input_mesh2") + k3d::init_description("Input mesh 2") + k3d::init_value<k3d::mesh*>(0) + k3d::init_document(Document))
	{
		register_property(m_input_mesh1);
		register_property(m_input_mesh2);

		m_input_mesh1.changed_signal().connect(SigC::slot(*this, &blobby_min_operator_implementation::on_reset_geometry));
		m_input_mesh2.changed_signal().connect(SigC::slot(*this, &blobby_min_operator_implementation::on_reset_geometry));

		m_output_mesh.need_data_signal().connect(SigC::slot(*this, &blobby_min_operator_implementation::on_create_geometry));
	}

	void on_reset_geometry()
	{
		m_output_mesh.reset();
	}

	k3d::mesh* on_create_geometry()
	{
		// Get the input geometry ...
		const k3d::mesh* const input_mesh1 = m_input_mesh1.property_value();
		const k3d::mesh* const input_mesh2 = m_input_mesh2.property_value();

		// Create output geometry ...
		k3d::mesh* const output = new k3d::mesh();
		if(input_mesh1)
			k3d::deep_copy(*input_mesh1, *output);

		if(input_mesh2)
			k3d::deep_copy(*input_mesh2, *output);

		k3d::blobby::min* minimum = new k3d::blobby::min();
		for(k3d::mesh::blobbies_t::const_iterator blob = output->blobbies.begin(); blob != output->blobbies.end(); blob++)
			minimum->add_operand((*blob)->root);

		output->blobbies.clear();
		output->blobbies.push_back(new k3d::blobby(minimum));

		return output;
	}

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

	static k3d::iplugin_factory& get_factory()
	{
		static k3d::plugin_factory<k3d::document_plugin<blobby_min_operator_implementation> > factory(
			k3d::uuid(0xc8e5eabc, 0x86f44610, 0x8e2a647d, 0x847de77a),
			"BlobbyMinOperator",
			"Blobby Min operator",
			"Objects",
			k3d::iplugin_factory::STABLE);

		return factory;
	}

private:
	k3d_data_property(k3d::mesh*, k3d::immutable_name, k3d::change_signal, k3d::no_undo, k3d::local_storage, k3d::no_constraint) m_input_mesh1;
	k3d_data_property(k3d::mesh*, k3d::immutable_name, k3d::change_signal, k3d::no_undo, k3d::local_storage, k3d::no_constraint) m_input_mesh2;
};

/////////////////////////////////////////////////////////////////////////////
// blobby_sub_operator_implementation

class blobby_sub_operator_implementation :
	public k3d::mesh_source<k3d::persistent<k3d::object> >
{
	typedef k3d::mesh_source<k3d::persistent<k3d::object> > base;

public:
	blobby_sub_operator_implementation(k3d::idocument& Document) :
		base(Document),
		m_input_mesh1(k3d::init_name("input_mesh1") + k3d::init_description("Input mesh 1") + k3d::init_value<k3d::mesh*>(0) + k3d::init_document(Document)),
		m_input_mesh2(k3d::init_name("input_mesh2") + k3d::init_description("Input mesh 2") + k3d::init_value<k3d::mesh*>(0) + k3d::init_document(Document)),
		m_type(k3d::init_name("subtraction_type") + k3d::init_description("Operation [enumeration]") + k3d::init_enumeration(subtract_values()) + k3d::init_value(SUBTRACTION) + k3d::init_document(Document))
	{
		register_property(m_input_mesh1);
		register_property(m_input_mesh2);

		m_input_mesh1.changed_signal().connect(SigC::slot(*this, &blobby_sub_operator_implementation::on_reset_geometry));
		m_input_mesh2.changed_signal().connect(SigC::slot(*this, &blobby_sub_operator_implementation::on_reset_geometry));

		m_output_mesh.need_data_signal().connect(SigC::slot(*this, &blobby_sub_operator_implementation::on_create_geometry));
	}

	void on_reset_geometry()
	{
		m_output_mesh.reset();
	}

	k3d::mesh* on_create_geometry()
	{
		// Get the input geometry ...
		const k3d::mesh* const input_mesh1 = m_input_mesh1.property_value();
		const k3d::mesh* const input_mesh2 = m_input_mesh2.property_value();

		// Create output geometry ...
		k3d::mesh* const output = new k3d::mesh();
		if(input_mesh1)
			k3d::deep_copy(*input_mesh1, *output);

		if(input_mesh2)
			k3d::deep_copy(*input_mesh2, *output);

		if(output->blobbies.size() >= 2)
			{
				k3d::mesh::blobbies_t::const_iterator blobby = output->blobbies.begin();
				k3d::blobby::opcode* opcode1 = (*blobby++)->root;
				k3d::blobby::opcode* opcode2 = (*blobby++)->root;
				k3d::blobby::subtract* subtraction = new k3d::blobby::subtract(opcode1, opcode2);

				output->blobbies.clear();
				output->blobbies.push_back(new k3d::blobby(subtraction));
			}

		return output;
	}

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

	static k3d::iplugin_factory& get_factory()
	{
		static k3d::plugin_factory<k3d::document_plugin<blobby_sub_operator_implementation> > factory(
			k3d::uuid(0x351e67c1, 0x12034c4c, 0x974516db, 0xbcb767d9),
			"BlobbySubOperator",
			"Blobby Sub operator",
			"Objects",
			k3d::iplugin_factory::STABLE);

		return factory;
	}

private:
	typedef enum
	{
		SUBTRACTION,
		REVERSE_SUBTRACTION
	} subtract_t;

	friend const k3d::ienumeration_property::values_t& subtract_values()
	{
		static k3d::ienumeration_property::values_t values;
		if(values.empty())
			{
				values.push_back(k3d::ienumeration_property::value_t("Subtraction", "subtraction", "Subtract blobbies"));
				values.push_back(k3d::ienumeration_property::value_t("Reverse Subtraction", "reverse_subtraction", "Subtract blobbies"));
			}

		return values;
	}

	friend std::ostream& operator<<(std::ostream& Stream, const subtract_t& Value)
	{
		switch(Value)
			{
				case SUBTRACTION:
					Stream << "subtraction";
					break;
				case REVERSE_SUBTRACTION:
					Stream << "reverse_subtraction";
					break;
			}

		return Stream;
	}

	friend std::istream& operator>>(std::istream& Stream, subtract_t& Value)
	{
		std::string text;
		Stream >> text;

		if(text == "subtraction")
			Value = SUBTRACTION;
		else if(text == "reverse_subtraction")
			Value = REVERSE_SUBTRACTION;
		else
			std::cerr << __PRETTY_FUNCTION__ << ": unknown enumeration [" << text << "]"<< std::endl;

		return Stream;
	}

	k3d_data_property(k3d::mesh*, k3d::immutable_name, k3d::change_signal, k3d::no_undo, k3d::local_storage, k3d::no_constraint) m_input_mesh1;
	k3d_data_property(k3d::mesh*, k3d::immutable_name, k3d::change_signal, k3d::no_undo, k3d::local_storage, k3d::no_constraint) m_input_mesh2;
	k3d_enumeration_property(subtract_t, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_type;
};

/////////////////////////////////////////////////////////////////////////////
// blobby_div_operator_implementation

class blobby_div_operator_implementation :
	public k3d::mesh_source<k3d::persistent<k3d::object> >
{
	typedef k3d::mesh_source<k3d::persistent<k3d::object> > base;

public:
	blobby_div_operator_implementation(k3d::idocument& Document) :
		base(Document),
		m_input_mesh1(k3d::init_name("input_mesh1") + k3d::init_description("Input mesh 1") + k3d::init_value<k3d::mesh*>(0) + k3d::init_document(Document)),
		m_input_mesh2(k3d::init_name("input_mesh2") + k3d::init_description("Input mesh 2") + k3d::init_value<k3d::mesh*>(0) + k3d::init_document(Document)),
		m_type(k3d::init_name("division_type") + k3d::init_description("Operation [enumeration]") + k3d::init_enumeration(divide_values()) + k3d::init_value(DIVISION) + k3d::init_document(Document))
	{
		register_property(m_input_mesh1);
		register_property(m_input_mesh2);

		m_input_mesh1.changed_signal().connect(SigC::slot(*this, &blobby_div_operator_implementation::on_reset_geometry));
		m_input_mesh2.changed_signal().connect(SigC::slot(*this, &blobby_div_operator_implementation::on_reset_geometry));

		m_output_mesh.need_data_signal().connect(SigC::slot(*this, &blobby_div_operator_implementation::on_create_geometry));
	}

	void on_reset_geometry()
	{
		m_output_mesh.reset();
	}

	k3d::mesh* on_create_geometry()
	{
		// Get the input geometry ...
		const k3d::mesh* const input_mesh1 = m_input_mesh1.property_value();
		const k3d::mesh* const input_mesh2 = m_input_mesh2.property_value();

		// Create output geometry ...
		k3d::mesh* const output = new k3d::mesh();
		if(input_mesh1)
			k3d::deep_copy(*input_mesh1, *output);

		if(input_mesh2)
			k3d::deep_copy(*input_mesh2, *output);

		if(output->blobbies.size() >= 2)
			{
				k3d::mesh::blobbies_t::const_iterator blobby = output->blobbies.begin();
				k3d::blobby::opcode* opcode1 = (*blobby++)->root;
				k3d::blobby::opcode* opcode2 = (*blobby++)->root;
				k3d::blobby::divide* division = new k3d::blobby::divide(opcode1, opcode2);

				output->blobbies.clear();
				output->blobbies.push_back(new k3d::blobby(division));
			}

		return output;
	}

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

	static k3d::iplugin_factory& get_factory()
	{
		static k3d::plugin_factory<k3d::document_plugin<blobby_div_operator_implementation> > factory(
			k3d::uuid(0xf650ce52, 0x2fc64539, 0xa184ef26, 0x03bbe6d7),
			"BlobbyDivOperator",
			"Blobby Div operator",
			"Objects",
			k3d::iplugin_factory::STABLE);

		return factory;
	}

private:
	typedef enum
	{
		DIVISION,
		REVERSE_DIVISION
	} divide_t;

	friend const k3d::ienumeration_property::values_t& divide_values()
	{
		static k3d::ienumeration_property::values_t values;
		if(values.empty())
			{
				values.push_back(k3d::ienumeration_property::value_t("Division", "division", "Divide blobbies"));
				values.push_back(k3d::ienumeration_property::value_t("Reverse Division", "reverse_division", "Divide blobbies"));
			}

		return values;
	}

	friend std::ostream& operator<<(std::ostream& Stream, const divide_t& Value)
	{
		switch(Value)
			{
				case DIVISION:
					Stream << "division";
					break;
				case REVERSE_DIVISION:
					Stream << "reverse_division";
					break;
			}

		return Stream;
	}

	friend std::istream& operator>>(std::istream& Stream, divide_t& Value)
	{
		std::string text;
		Stream >> text;

		if(text == "division")
			Value = DIVISION;
		else if(text == "reverse_division")
			Value = REVERSE_DIVISION;
		else
			std::cerr << __PRETTY_FUNCTION__ << ": unknown enumeration [" << text << "]"<< std::endl;

		return Stream;
	}

	k3d_data_property(k3d::mesh*, k3d::immutable_name, k3d::change_signal, k3d::no_undo, k3d::local_storage, k3d::no_constraint) m_input_mesh1;
	k3d_data_property(k3d::mesh*, k3d::immutable_name, k3d::change_signal, k3d::no_undo, k3d::local_storage, k3d::no_constraint) m_input_mesh2;
	k3d_enumeration_property(divide_t, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_type;
};

/////////////////////////////////////////////////////////////////////////////
// blobby factories

k3d::iplugin_factory& blobby_ellipsoid_factory()
{
	return blobby_ellipsoid_implementation::get_factory();
}

k3d::iplugin_factory& blobby_segment_factory()
{
	return blobby_segment_implementation::get_factory();
}

k3d::iplugin_factory& blobby_add_operator_factory()
{
	return blobby_add_operator_implementation::get_factory();
}

k3d::iplugin_factory& blobby_mult_operator_factory()
{
	return blobby_mult_operator_implementation::get_factory();
}

k3d::iplugin_factory& blobby_max_operator_factory()
{
	return blobby_max_operator_implementation::get_factory();
}

k3d::iplugin_factory& blobby_min_operator_factory()
{
	return blobby_min_operator_implementation::get_factory();
}

k3d::iplugin_factory& blobby_sub_operator_factory()
{
	return blobby_sub_operator_implementation::get_factory();
}

k3d::iplugin_factory& blobby_div_operator_factory()
{
	return blobby_div_operator_implementation::get_factory();
}

} // namespace libk3dmesh


