// 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 Romain Behar (romainbehar@yahoo.com)
*/

#include <k3dsdk/measurement.h>
#include <k3dsdk/mesh_filter.h>
#include <k3dsdk/module.h>
#include <k3dsdk/mouse_event_observer.h>
#include <k3dsdk/object.h>
#include <k3dsdk/persistence.h>
#include <k3dsdk/plugins.h>
#include <k3dsdk/utility.h>
#include <k3dsdk/vectors.h>

namespace libk3dmesh
{

/////////////////////////////////////////////////////////////////////////////
// subdivide_faces_implementation

class subdivide_faces_implementation :
	public k3d::mesh_filter<k3d::persistent<k3d::object> >,
	public k3d::mouse_event_observer
{
	typedef k3d::mesh_filter<k3d::persistent<k3d::object> > base;

public:
	subdivide_faces_implementation(k3d::idocument& Document) :
		base(Document),
		mouse_event_observer("LMB Drag to offset midpoints"),
		m_subdivision_type(k3d::init_name("subdivision_type") + k3d::init_description("Subdivision type [enumeration]") + k3d::init_value(CENTERTOPOINTS) + k3d::init_enumeration(subdivision_values()) + k3d::init_document(Document)),
		m_middle_offset(k3d::init_name("middle_offset") + k3d::init_description("Middle offset [number]") + k3d::init_value(0.0) + k3d::init_precision(2) + k3d::init_step_increment(0.05) + k3d::init_units(typeid(k3d::measurement::distance)) + k3d::init_document(Document))
	{
		enable_serialization(k3d::persistence::proxy(m_subdivision_type));
		enable_serialization(k3d::persistence::proxy(m_middle_offset));

		register_property(m_subdivision_type);
		register_property(m_middle_offset);

		m_input_mesh.changed_signal().connect(SigC::slot(*this, &subdivide_faces_implementation::on_reset_geometry));
		m_subdivision_type.changed_signal().connect(SigC::slot(*this, &subdivide_faces_implementation::on_reset_geometry));

		m_middle_offset.changed_signal().connect(SigC::slot(*this, &subdivide_faces_implementation::on_reshape_geometry));

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

	bool OnLButtonDrag(const k3d::imouse_event_observer::event_state& State, const k3d::vector2& Current, const k3d::vector2& Last, const k3d::vector2& Start, const drag_type_t DragType)
	{
		const double sensitivity = 1.0;
		m_middle_offset.set_value(m_middle_offset.value() + sensitivity * (Current[0] - Last[0]));

		return true;
	}

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

	void on_reshape_geometry()
	{
		if(m_output_mesh.empty())
			return;

		reshape_geometry();

		m_output_mesh.changed_signal().emit();
	}

	k3d::mesh* on_create_geometry()
	{
		// Get the input geometry ...
		k3d::mesh* const input = m_input_mesh.property_value();
		if(!input)
			return 0;

		// Create output geometry ...
		k3d::mesh* const output = new k3d::mesh();

		// Duplicate points and map old ones with new ones ...
		std::map<k3d::point*, k3d::point*> point_map;
		point_map[0] = 0;
		for(k3d::mesh::points_t::const_iterator p = input->points.begin(); p != input->points.end(); ++p)
			{
				output->points.push_back(new k3d::point((*p)->position));
				point_map.insert(std::make_pair(*p, output->points.back()));
			}

		const subdivision_t subdivision_type = m_subdivision_type.property_value();

		// For each polyhedron ...
		for(k3d::mesh::polyhedra_t::iterator polyhedron = input->polyhedra.begin(); polyhedron != input->polyhedra.end(); ++polyhedron)
			{
				k3d::polyhedron* new_polyhedron = new k3d::polyhedron();
				return_val_if_fail(new_polyhedron, 0);
				new_polyhedron->material = (*polyhedron)->material;
				output->polyhedra.push_back(new_polyhedron);

				// For each face ...
				for(k3d::polyhedron::faces_t::iterator face = (*polyhedron)->faces.begin(); face != (*polyhedron)->faces.end(); ++face)
					{
						if(subdivision_type == CENTERTOPOINTS || subdivision_type == CENTERTOMIDPOINTS)
							{
								// Get the set of original points and compute barycenter ...
								k3d::mesh::points_t old_points;
								k3d::vector3 face_barycenter = k3d::vector3(0, 0, 0);
								for(k3d::split_edge* edge = (*face)->first_edge; edge && edge->face_clockwise; edge = edge->face_clockwise)
									{
										old_points.push_back(point_map[edge->vertex]);
										face_barycenter += edge->vertex->position;

										if(edge->face_clockwise == (*face)->first_edge)
											break;
									}

								const unsigned long vertex_count = old_points.size();
								face_barycenter /= static_cast<double>(vertex_count);

								// Create center point ...
								k3d::point* center_point = new k3d::point(face_barycenter);
								output->points.push_back(center_point);

								// Create new sub-faces ...
								if(subdivision_type == CENTERTOPOINTS)
									{
										for(unsigned long i = 0; i != vertex_count; ++i)
											{
												k3d::split_edge* edge_0 = new k3d::split_edge(old_points[i]);
												k3d::split_edge* edge_1 = new k3d::split_edge(old_points[(i+1) % vertex_count]);
												k3d::split_edge* edge_2 = new k3d::split_edge(center_point);

												edge_0->face_clockwise = edge_1;
												edge_1->face_clockwise = edge_2;
												edge_2->face_clockwise = edge_0;

												new_polyhedron->edges.push_back(edge_0);
												new_polyhedron->edges.push_back(edge_1);
												new_polyhedron->edges.push_back(edge_2);

												new_polyhedron->faces.push_back(new k3d::face(edge_0));
											}
									}
								else
									{
										k3d::mesh::points_t new_points;
										k3d::vector3 previous_point = old_points[vertex_count - 1]->position;
										for(unsigned long i = 0; i != vertex_count; ++i)
											{
												const k3d::vector3 current_point = old_points[i]->position;
												const k3d::vector3 midpoint = (previous_point + current_point) * 0.5;

												k3d::point* new_midpoint = new k3d::point(midpoint);
												new_points.push_back(new_midpoint);

												output->points.push_back(new_midpoint);

												m_middle_points.push_back(middle_point(previous_point, current_point, *new_midpoint));
												previous_point = current_point;
											}

										for(unsigned long i = 0; i != vertex_count; ++i)
											{
												k3d::split_edge* edge_0 = new k3d::split_edge(new_points[i]);
												k3d::split_edge* edge_1 = new k3d::split_edge(old_points[i]);
												k3d::split_edge* edge_2 = new k3d::split_edge(new_points[(i + 1) % vertex_count]);
												k3d::split_edge* edge_3 = new k3d::split_edge(center_point);

												edge_0->face_clockwise = edge_1;
												edge_1->face_clockwise = edge_2;
												edge_2->face_clockwise = edge_3;
												edge_3->face_clockwise = edge_0;

												new_polyhedron->edges.push_back(edge_0);
												new_polyhedron->edges.push_back(edge_1);
												new_polyhedron->edges.push_back(edge_2);
												new_polyhedron->edges.push_back(edge_3);

												new_polyhedron->faces.push_back(new k3d::face(edge_0));
											}
									}
							}
						else // subdivision_type == CONTIGUOUSMIDPOINTS*
							{
								// Get the set of original points ...
								k3d::mesh::points_t old_points;
								for(k3d::split_edge* edge = (*face)->first_edge; edge && edge->face_clockwise; edge = edge->face_clockwise)
									{
										old_points.push_back(point_map[edge->vertex]);

										if(edge->face_clockwise == (*face)->first_edge)
											break;
									}

								const unsigned long vertex_count = old_points.size();

								k3d::mesh::points_t new_points;
								k3d::vector3 previous_point = old_points[vertex_count - 1]->position;
								for(unsigned long i = 0; i != vertex_count; ++i)
									{
										const k3d::vector3 current_point = old_points[i]->position;
										const k3d::vector3 midpoint = (previous_point + current_point) / 2;

										k3d::point* new_midpoint = new k3d::point(midpoint);
										new_points.push_back(new_midpoint);

										output->points.push_back(new_midpoint);

										m_middle_points.push_back(middle_point(previous_point, current_point, *new_midpoint));
										previous_point = current_point;
									}

								// Add corner triangles ...
								if(subdivision_type != CONTIGUOUSMIDPOINTSCENTERONLY)
									{
										for(unsigned long i = 0; i != vertex_count; ++i)
											{
												k3d::split_edge* edge_0 = new k3d::split_edge(new_points[i]);
												k3d::split_edge* edge_1 = new k3d::split_edge(old_points[i]);
												k3d::split_edge* edge_2 = new k3d::split_edge(new_points[(i + 1) % vertex_count]);

												edge_0->face_clockwise = edge_1;
												edge_1->face_clockwise = edge_2;
												edge_2->face_clockwise = edge_0;

												new_polyhedron->edges.push_back(edge_0);
												new_polyhedron->edges.push_back(edge_1);
												new_polyhedron->edges.push_back(edge_2);

												new_polyhedron->faces.push_back(new k3d::face(edge_0));
											}
									}

								// Add center face ...
								if(subdivision_type != CONTIGUOUSMIDPOINTSCORNERSONLY)
									{
										k3d::polyhedron::edges_t edges;
										for(unsigned long i = 0; i != vertex_count; ++i)
											edges.push_back(new k3d::split_edge(new_points[i]));

										k3d::loop_edges(edges.begin(), edges.end());
										new_polyhedron->edges.insert(new_polyhedron->edges.end(), edges.begin(), edges.end());

										new_polyhedron->faces.push_back(new k3d::face(edges.front()));
									}
							}
					}

				assert_warning(is_valid(*new_polyhedron));
			}

		// Position all the new geometry we've created ...
		reshape_geometry();

		return output;
	}

	void reshape_geometry()
	{
		const double offset = m_middle_offset.property_value();

		for(middle_points_t::iterator mp = m_middle_points.begin(); mp != m_middle_points.end(); ++mp)
			mp->update(offset);
	}

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

	static k3d::iplugin_factory& get_factory()
	{
		static k3d::plugin_factory<
			k3d::document_plugin<subdivide_faces_implementation>,
				k3d::interface_list<k3d::imesh_source,
				k3d::interface_list<k3d::imesh_sink > > > factory(
				k3d::uuid(0xb9d4d815, 0x241c473d, 0xa22d3523, 0x406fa390),
				"SubdivideFaces",
				"Subdivide faces by joining face center with vertices or edge midpoints, or contiguous midpoints",
				"Objects",
				k3d::iplugin_factory::EXPERIMENTAL);

		return factory;
	}

private:
	typedef enum
	{
		CENTERTOPOINTS,
		CENTERTOMIDPOINTS,
		CONTIGUOUSMIDPOINTS,
		CONTIGUOUSMIDPOINTSCENTERONLY,
		CONTIGUOUSMIDPOINTSCORNERSONLY
	} subdivision_t;

	friend std::ostream& operator << (std::ostream& Stream, const subdivision_t& Value)
	{
		switch(Value)
			{
				case CENTERTOPOINTS:
					Stream << "center";
					break;
				case CENTERTOMIDPOINTS:
					Stream << "centermidpoints";
					break;
				case CONTIGUOUSMIDPOINTS:
					Stream << "midpoints";
					break;
				case CONTIGUOUSMIDPOINTSCENTERONLY:
					Stream << "midpointscenteronly";
					break;
				case CONTIGUOUSMIDPOINTSCORNERSONLY:
					Stream << "midpointscornersonly";
					break;
			}
		return Stream;
	}

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

		if(text == "center")
			Value = CENTERTOPOINTS;
		else if(text == "centermidpoints")
			Value = CENTERTOMIDPOINTS;
		else if(text == "midpoints")
			Value = CONTIGUOUSMIDPOINTS;
		else if(text == "midpointscenteronly")
			Value = CONTIGUOUSMIDPOINTSCENTERONLY;
		else if(text == "midpointscornersonly")
			Value = CONTIGUOUSMIDPOINTSCORNERSONLY;
		else
			std::cerr << __PRETTY_FUNCTION__ << ": unknown enumeration [" << text << "]" << std::endl;

		return Stream;
	}

	const k3d::ienumeration_property::values_t& subdivision_values()
	{
		static k3d::ienumeration_property::values_t values;
		if(values.empty())
			{
				values.push_back(k3d::ienumeration_property::value_t("Center", "center", "Joins each face vertex to face center; fun fact: produces triangles only"));
				values.push_back(k3d::ienumeration_property::value_t("CenterMidpoints", "centermidpoints", "Joins face center to each edge middle; innovative info: produces quads only"));
				values.push_back(k3d::ienumeration_property::value_t("Midpoints", "midpoints", "Joins contiguous edge midpoints; amusing anecdote: produces one triangle per original vertex, plus a polygon in the middle with as many edges as the original"));
				values.push_back(k3d::ienumeration_property::value_t("Midpointscenteronly", "midpointscenteronly", "Same as Midpoints, but leaves out the triangles - leaves holes"));
				values.push_back(k3d::ienumeration_property::value_t("Midpointscornersonly", "midpointscornersonly", "Same as Midpoints, but leaves out the polygons in the middle - leaves holes"));
			}

		return values;
	}

	k3d_enumeration_property(subdivision_t, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_subdivision_type;
	k3d_measurement_property(double, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_middle_offset;

	/// Caches new geometry for better interactive performance
	class middle_point
	{
	public:
		middle_point(const k3d::vector3& StartPosition, const k3d::vector3& EndPosition, k3d::point& Point) :
			start_position(StartPosition),
			end_position(EndPosition),
			point(&Point)
		{
		}

		void update(const double Offset)
		{
			point->position = start_position + (end_position - start_position) * (0.5 + Offset);
		}

	private:
		k3d::vector3 start_position;
		k3d::vector3 end_position;
		k3d::point* point;
	};

	/// Caches new geometry for better interactive performance
	typedef std::vector<middle_point> middle_points_t;
	/// Caches new geometry for better interactive performance
	middle_points_t m_middle_points;
};

/////////////////////////////////////////////////////////////////////////////
// subdivide_faces_factory

k3d::iplugin_factory& subdivide_faces_factory()
{
	return subdivide_faces_implementation::get_factory();
}

} // namespace libk3dmesh


