// 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 Bart Janssens <bart.janssens@lid.kviv.be>
*/

#include "subdiv_algorithms.h"

#include <k3dsdk/basic_math.h>
#include <k3dsdk/result.h>
#include <k3dsdk/irenderman.h>

#include <boost/multi_array.hpp>

#include <cmath>
#include <map>
#include <set>

#define DEBUG_MESSAGE(Message)
//#define DEBUG_MESSAGE(Message) Message

namespace subdiv
{

// Helper class to associate a vertex to an edge.
// Used to i.e. associate face vertices to edges
class vertex_edge_container
{
	typedef std::map<k3d::split_edge*, k3d::point*> vertex_map;
	typedef vertex_map::value_type vertex_map_t;
	typedef vertex_map::iterator vertex_map_it;

public:
	// Add a new edge-vertex pair, if the edge is not registered already
	void add(k3d::split_edge* edge, k3d::point* vertex)
	{
		if(!((m_vertices.insert(vertex_map_t(edge, vertex))).second))
			DEBUG_MESSAGE(std::cerr << error << "Edge already has a vertex associated" << std::endl;);
	}

	// Return the vertex associated with edge, if there is one.
	k3d::point* get(k3d::split_edge* edge)
	{
		vertex_map_it it = m_vertices.find(edge);
		if(it != m_vertices.end())
			return it->second;

		return 0;
	}

private:
	vertex_map m_vertices;
};

// Helper class to associate 2 edges
class edge_container
{
	typedef std::map<k3d::split_edge*, k3d::split_edge*> edge_map;
	typedef edge_map::value_type edge_map_t;
	typedef edge_map::iterator edge_map_it;

public:
	// Add a new pair
	void add(k3d::split_edge* key_edge, k3d::split_edge* edge)
	{
		if(!((m_edges.insert(edge_map_t(key_edge, edge))).second))
			DEBUG_MESSAGE(std::cerr << error << "Edge already has an edge associated" << std::endl;);
	}

	// Return the edge associated to <edge>
	k3d::split_edge* get(k3d::split_edge* edge)
	{
		edge_map_it it = m_edges.find(edge);
		if(it != m_edges.end())
			return it->second;

		return 0;
	}

private:
	edge_map m_edges;
};

// Extra data stored by the catmull-clark algorithm
class catmull_clark_data
{
	typedef std::set<k3d::point*> point_set;
	typedef std::set<k3d::point*>::value_type point_set_t;
	typedef std::set<k3d::point*>::iterator point_set_it;

public:
	catmull_clark_data(k3d::polyhedron* polyhedron, k3d::mesh::points_t* output_points, bool ignore_selection) : m_ignore_selection(ignore_selection)
	{
		m_polyhedron = polyhedron;
		m_original_faces = m_polyhedron->faces;
		m_original_edges = m_polyhedron->edges;
		m_next_face = m_original_faces.begin();
		m_output_points = output_points;
	}

	// Return the companion of an edge
	k3d::split_edge* companion(k3d::split_edge* edge)
	{
		// return the old companion, if there is one
		k3d::split_edge* old_companion = m_saved_companions.get(edge);
		if(old_companion)
			return old_companion;

		// otherwise return the real companion
		return edge->companion;
	}

	// Return the face_clockwise of an edge
	k3d::split_edge* clockwise(k3d::split_edge* edge)
	{
		// return the old clockwise, if there is one
		k3d::split_edge* old_clockwise = m_saved_clockwise.get(edge);
		if(old_clockwise)
			return old_clockwise;

		// otherwise return the real face_clockwise
		return edge->face_clockwise;
	}

	// Helper function to return the face vertex to the right of an edge
	k3d::point* face_vertex(k3d::split_edge* edge)
	{
		// If this face vertex was calculated earlier, return the pointer to it
		return_val_if_fail(edge, 0);
		k3d::point* existing_face_vertex = m_face_vertices.get(edge);
		if(existing_face_vertex)
			return existing_face_vertex;

		// Else calculate the new face vertex and add it to the list of calculated vertices.
		k3d::point* vf = new k3d::point(edge->vertex->position);
		// Number of vertices in the face
		int n = 1;
		k3d::split_edge* ej = edge->face_clockwise;
		while(ej != edge)
			{
				// Associate the new face vertex with all surrounding edges
				m_face_vertices.add(ej, vf);
				vf->position += ej->vertex->position;
				ej = ej->face_clockwise;
				n++;
			}

		m_face_vertices.add(edge, vf);
		vf->position /= n;
		m_output_points->push_back(vf);
		vf->selected = !m_ignore_selection;

		return vf;
	}

	// Return the edge vertex associated with a given edge and face vertex
	k3d::point* edge_vertex(k3d::split_edge* edge, k3d::point* vf)
	{
		// If this edge vertex exists, return a pointer to it
		k3d::point* existing_edge_vertex = m_edge_vertices.get(edge);
		if(existing_edge_vertex)
			return existing_edge_vertex;

		// Else calculate a new one
		k3d::split_edge* edge_companion = companion(edge);
		if(!(edge_companion) || (!m_ignore_selection && edge_companion->selected == false))
		{
			// if edge is a boundary edge or at the edge of the selection
			k3d::point* ev = new k3d::point((edge->vertex->position
					+ edge->face_clockwise->vertex->position)/2);
			m_output_points->push_back(ev);
			if(edge_companion)
				m_edge_vertices.add(edge_companion, ev);

			ev->selected = !m_ignore_selection;

			return ev;
		}

		// For a normal edge:
		k3d::point* ev = new k3d::point((edge->vertex->position
			+ edge->face_clockwise->vertex->position
			+ vf->position
			+ face_vertex(edge_companion)->position)/4);

		// The companion edge has the same edge vertex, so store it:
		m_edge_vertices.add(edge_companion, ev);
		m_output_points->push_back(ev);
		ev->selected = !m_ignore_selection;

		return ev;
	}

	// Return the next edge to be treated, or 0 if there is none
	k3d::split_edge* next_edge()
	{
		if(m_next_face != m_original_faces.end())
		{
			while(!m_ignore_selection)
			{
				if((*m_next_face)->selected)
					return (*(m_next_face++))->first_edge;
				else
					++m_next_face;

				if(m_next_face == m_original_faces.end())
					return 0;
			}

			return (*(m_next_face++))->first_edge;
		}

		return 0;
	}

	// Join 2 edges, and store the old companion of the first edge
	void save_join(k3d::split_edge* first, k3d::split_edge* second)
	{
		if(first == 0 || second == 0 || first->companion == 0)
			return;

		m_saved_companions.add(first, first->companion);
		k3d::join_edges(*first, *second);
	}

	// Set the clockwise on an edge, saving the old one
	void save_clockwise(k3d::split_edge* edge, k3d::split_edge* clockwise)
	{
		if(edge == 0 || clockwise == 0 || edge->face_clockwise == 0)
			return;

		m_saved_clockwise.add(edge, edge->face_clockwise);
		edge->face_clockwise = clockwise;
	}

	// Reposition a point
	void reposition_point(k3d::split_edge* edge)
	{
		// Valence of the vertex
		int n = 1;

		if(!(edge->companion))
		{
			// Boundary edge
			k3d::split_edge* ecl = edge->face_clockwise->face_clockwise->face_clockwise;
			if(!m_ignore_selection && ecl->selected == false)
				return;
			if(!m_ignore_selection && edge->face_clockwise->selected == false)
				return;

			k3d::vector3 v = 0.25*edge->face_clockwise->vertex->position;
			if(!(ecl->companion))
				v += 0.25*ecl->vertex->position;
			else
				v += 0.25*ecl->companion->face_clockwise->face_clockwise->face_clockwise->vertex->position;

			edge->vertex->position = v + 0.5*edge->vertex->position;

			return;
		}

		k3d::split_edge* ecl1 = edge->face_clockwise;
		if(!m_ignore_selection && ecl1->selected == false)
			return;
		k3d::split_edge* ecl2 = ecl1->face_clockwise;
		if(!m_ignore_selection && ecl2->selected == false)
			return;
		k3d::split_edge* ecl3 = ecl2->face_clockwise;
		if(!m_ignore_selection && ecl3->selected == false)
			return;

		k3d::vector3 v = ecl1->vertex->position; // first edge vertex
		v += ecl2->vertex->position; // first face vertex
		k3d::split_edge* ei = ecl3->companion;
		if(!ei)
			{
				// we started on the wrong edge around a valence 3 vertex
				reposition_point(edge->companion->face_clockwise);
				return;
			}

		while(ei != edge)
		{
			n++;
			ecl1 = ei->face_clockwise;
			if(!m_ignore_selection && ecl1->selected == false)
				return;
			ecl2 = ecl1->face_clockwise;
			if(!m_ignore_selection && ecl2->selected == false)
				return;
			ecl3 = ecl2->face_clockwise;
			if(!m_ignore_selection && ecl3->selected == false)
				return;

			v += ecl1->vertex->position;
			v += ecl2->vertex->position;
			ei = ecl3->companion;
			if(!ei)
				{
					// we started on the wrong edge around a valence 3 vertex
					reposition_point(edge->companion->face_clockwise);
					return;
				}
		}

		edge->vertex->position = (n-2.0)/n * edge->vertex->position + 1.0/(n*n) * v;
	}

	// Reposition all the original points
	void reposition_points()
	{
		point_set visited_points;
		k3d::polyhedron::edges_t::iterator it = m_original_edges.begin();
		while(it != m_original_edges.end())
		{
			if(*it && (m_ignore_selection || (!m_ignore_selection && (*it)->selected == true)) && visited_points.find((*it)->vertex) == visited_points.end())
				{
					reposition_point(*it);
					visited_points.insert(point_set_t((*it)->vertex));
				}

			++it;
		}
	}

private:
	vertex_edge_container m_face_vertices;
	vertex_edge_container m_edge_vertices;
	edge_container m_saved_companions;
	edge_container m_saved_clockwise;
	k3d::polyhedron* m_polyhedron;
	k3d::polyhedron::faces_t m_original_faces;
	k3d::polyhedron::faces_t::iterator m_next_face;
	k3d::mesh::points_t* m_output_points;
	k3d::polyhedron::edges_t m_original_edges;
	const bool m_ignore_selection;
};

void catmull_clark(const unsigned long Levels, const k3d::mesh& Input, k3d::mesh& Output, const bool ignore_selection, const bool ignore_mesh_type)
{
	// clear the output
	Output.polyhedra.clear();
	k3d::deep_copy(Input, Output);
	for(k3d::mesh::polyhedra_t::iterator it = Output.polyhedra.begin(); it != Output.polyhedra.end(); ++it) {
		if((*it)->type == k3d::polyhedron::CATMULL_CLARK_SUBDIVISION_MESH || !ignore_selection || ignore_mesh_type) {
			k3d::polyhedron* output_polyhedron = *(it);
			for(unsigned long i = 1; i <= Levels; i++) {
				assert(output_polyhedron);
				return_if_fail(k3d::is_valid(*output_polyhedron));
				// extra containers and functions we need:
				catmull_clark_data data(output_polyhedron, &(Output.points), ignore_selection);
				DEBUG_MESSAGE(std::cerr << debug << "Going to SDS level " << i << std::endl;);

				// clear the faces of the output:
				if(ignore_selection) {
					output_polyhedron->faces.clear();
				} else {
					k3d::polyhedron::faces_t original_faces = output_polyhedron->faces;
					output_polyhedron->faces.clear();
					for(k3d::polyhedron::faces_t::iterator f = original_faces.begin(); f != original_faces.end(); ++f) {
						if(!((*f)->selected)) {
							output_polyhedron->faces.push_back(*f);
						}
					}
				}
				k3d::split_edge* e_start = 0;
				k3d::split_edge* ei = 0;
				k3d::split_edge* ei_old_clockwise = 0;
				k3d::split_edge* ev_edge = 0;
				k3d::split_edge* ev_center = 0;
				k3d::split_edge* ev_edge_last = 0;
				k3d::split_edge* vf_center = 0;
				k3d::split_edge* vf_center_last = 0;
				k3d::split_edge* vf_center_first = 0;
				k3d::point* vf = 0;
				k3d::point* ev_last = 0;
				k3d::point* ev = 0;
				k3d::point* ev_first = 0;
				// loop through all original faces:
				e_start = data.next_edge();
				while(e_start) {
					// face loop initialisation:
					ei = data.clockwise(e_start);
					vf = data.face_vertex(e_start);
					ev_last = data.edge_vertex(e_start, vf);
					// fix the neighbour face, to allow local subdivision
					k3d::split_edge* e_start_companion = data.companion(e_start);
					if(e_start_companion && (ignore_selection || (!ignore_selection && e_start_companion->selected == true))) {
						data.face_vertex(e_start_companion);
					}
					if(!(e_start->face_clockwise->vertex == ev_last)) {
						ev_edge_last = new k3d::split_edge(ev_last, ei, 0);
						ev_edge_last->selected = !ignore_selection;
						output_polyhedron->edges.push_back(ev_edge_last);
						data.save_join(e_start_companion, ev_edge_last);
						if(e_start_companion) {
							k3d::split_edge* ev_comp_edge = new k3d::split_edge(ev_last, (e_start_companion)->face_clockwise);
							ev_comp_edge->selected = (!ignore_selection && e_start_companion->selected);
							output_polyhedron->edges.push_back(ev_comp_edge);
							data.save_clockwise(e_start_companion,ev_comp_edge);
							data.save_join(e_start, ev_comp_edge);
						}
					} else {
						ev_edge_last = e_start->face_clockwise;
					}
					vf_center = new k3d::split_edge(vf, ev_edge_last, 0);
					vf_center->selected = !ignore_selection;
					output_polyhedron->edges.push_back(vf_center);
					vf_center_first = vf_center;
					vf_center_last = vf_center;
					ev_first = ev_last;
					// the loop for one face:
					while(ei != e_start) {
						ei_old_clockwise = data.clockwise(ei);
						ev = data.edge_vertex(ei, vf);
						// fix the neighbour face, to allow local subdivision
						k3d::split_edge* ei_companion = data.companion(ei);
						if(ei_companion && (ignore_selection || (!ignore_selection && ei_companion->selected == true))) {
							data.face_vertex(ei_companion);
						}
						if(!(ei->face_clockwise->vertex == ev)) {
							ev_edge = new k3d::split_edge(ev, ei->face_clockwise, 0);
							ev_edge->selected = !ignore_selection;
							output_polyhedron->edges.push_back(ev_edge);
							data.save_join(ei_companion, ev_edge);
							if(ei_companion) {
								k3d::split_edge* ev_comp_edge = new k3d::split_edge(ev, ei_companion->face_clockwise);
								ev_comp_edge->selected = (!ignore_selection && ei_companion->selected);
								output_polyhedron->edges.push_back(ev_comp_edge);
								data.save_clockwise(ei_companion, ev_comp_edge);
								data.save_join(ei, ev_comp_edge);
							}
						} else {
							ev_edge = ei->face_clockwise;
						}
						vf_center = new k3d::split_edge(vf, ev_edge, 0);
						vf_center->selected = !ignore_selection;
						output_polyhedron->edges.push_back(vf_center);
						ev_center = new k3d::split_edge(ev, vf_center_last, 0);
						ev_center->selected = !ignore_selection;
						output_polyhedron->edges.push_back(ev_center);
						k3d::join_edges(*vf_center, *ev_center);
						ei->face_clockwise = ev_center;
						vf_center_last = vf_center;
						output_polyhedron->faces.push_back(new k3d::face(ev_center));
						output_polyhedron->faces.back()->selected = !ignore_selection;
						ei = ei_old_clockwise;
					}
					// finish up the loose edges
					ev_center = new k3d::split_edge(ev_first, vf_center_last, 0);
					ev_center->selected = !ignore_selection;
					output_polyhedron->edges.push_back(ev_center);
					e_start->face_clockwise = ev_center;
					k3d::join_edges(*vf_center_first, *ev_center);
					output_polyhedron->faces.push_back(new k3d::face(ev_center));
					output_polyhedron->faces.back()->selected = !ignore_selection;
					e_start = data.next_edge();
				}
				data.reposition_points();
			}
			return_if_fail(k3d::is_valid(*output_polyhedron));
		}
	}
}

/// class link
class link
{
public:
	link(k3d::split_edge* Edge, bool dummy_edge = false) : edge(Edge), counter_clockwise(0), companion_clockwise(0), companion(0), m_Complete(false), m_Dummy_edge(dummy_edge)
	{
	}
	
	~link()
	{
		if (!is_complete() && m_Dummy_edge && edge)
		{
			delete edge;
		}
	}

	/// the split_edge associated with this link
	k3d::split_edge* edge;
	/// counter-clockwise link to this one
	link* counter_clockwise;
	/// clockwise link to the companion of this one
	link* companion_clockwise;
	/// link with the companion edge
	link* companion;
	/// true if the link is completed
	bool is_complete()
	{
		return m_Complete;
	}
	/// complete the link, i.e. set the actual companion and face_clockwise on the k3d::split_edge that is represented by the link
	void complete(bool recurse = true)
	{
		if (is_complete())
		{
			return;
		}
		m_Complete = true;
		return_if_fail(edge);
		return_if_fail(edge->vertex);
		return_if_fail(counter_clockwise);
		return_if_fail(companion_clockwise);
		return_if_fail(companion);
		if (recurse)
		{
			companion->complete(false);
		}
		return_if_fail(companion->edge);
		return_if_fail(companion_clockwise->edge);
		companion->edge->face_clockwise = companion_clockwise->edge;
		return_if_fail(counter_clockwise->edge);
		counter_clockwise->edge->face_clockwise = edge;
		if (!(edge->companion))
			k3d::join_edges(*edge, *(companion->edge));
	}
private:
	bool m_Complete;
	bool m_Dummy_edge;
};

class point
{
	friend void splitter::make_creases();
public:
	point() : vertex(0), m_Reorder(false) {}
	point(k3d::point* Point) : vertex(Point), m_Reorder(false), m_Updated(false) {}
	virtual ~point()
	{
		for (unsigned long i = 0; i < links.size(); ++i)
		{
			delete links[i];
		}
	}
	/// the point represented
	k3d::point* vertex;
	/// update the relations between the links. Works correctly only if links are ordered clockwise around the vertex. Use reorder_on if this is not the case.
	virtual void update()
	{
		m_Updated = true;
		if (m_Reorder)
			reorder();
		for (unsigned long i = 0; i < links.size(); ++i)
		{
			links[i]->counter_clockwise = links[(i+1) % links.size()]->companion;
			links[i]->companion_clockwise = links[(links.size()+i-1) % links.size()];
		}
	}
	virtual link* join(point* other_point, bool recurse = true)
	{
		link* new_link = new link(new k3d::split_edge(vertex));
		links.push_back(new_link);
		if (recurse)
		{
			new_link->companion = other_point->join(this, false);
			new_link->companion->companion = new_link;
		}
		return new_link;
	}
		
	/// complete all links
	virtual void complete()
	{
		return_if_fail(m_Updated);
		for (unsigned long i = 0; i < links.size(); ++i)
		{
			links[i]->complete();
		}
	}
	
	/// Turn on automatic link reordering, arranging links clockwise around the face normal.
	void reorder_on(k3d::vector3& normal)
	{
		m_Reorder = true;
		m_Normal = normal / normal.Length();
	}
	
private:
	/// reorder the links around the vertex so they are arranged clockwise around the normal vector of the face.
	void reorder()
	{
		if (links.size() < 1)
			return;
		std::map<double, link*> angle_links;
		
		k3d::vector3 v0 = links[0]->companion->edge->vertex->position - vertex->position;
		
		for (unsigned long i = 0; i < links.size(); ++i)
		{
			k3d::vector3 v = links[i]->companion->edge->vertex->position - vertex->position;
			angle_links[angle(v0, v)] = links[i];
		}
		
		unsigned long i = 0;
		for (std::map<double, link*>::iterator it = angle_links.begin(); it != angle_links.end(); ++it)
		{
			links[i] = it->second;
			++i;
		}
	}
	/// Calculate the angle between 2 vectors
	double angle(k3d::vector3& v1, k3d::vector3& v2)
	{
		// cosine of the angle:
		double cos_th = (v1 * v2) / (v1.Length() * v2.Length());
		//sometimes this value is greater than 1
		if (cos_th > 1.0)
			cos_th = 1.0;
		if (cos_th < -1.0)
			cos_th = -1.0;
		k3d::vector3 normal = (v1 ^ v2);
		normal /= normal.Length();
		double tol = 0.00001;
		// the angle
		double th = (normal + m_Normal).Length() > tol ? acos(cos_th) : k3d::pi_times_2() - acos(cos_th);
		//std::cerr << debug << "cos_th: " << cos_th << ". angle is " << th * 180 / k3d::pi() << " degrees" << std::endl;
		return th;
	}
	k3d::vector3 m_Normal;
	bool m_Reorder;
	bool m_Updated;
protected:
	/// a vector of all links from this point
	std::vector<link*> links;
};

// point at a t-junction. links[0] is the incomplete link (the leg of the T)
class t_point : public point
{
public:
	t_point(k3d::point* Point, k3d::split_edge* To, k3d::split_edge* From, bool dummy_edge = false)
	{
		this->vertex = Point;
		link* leg = new link(new k3d::split_edge(vertex), dummy_edge);
		leg->counter_clockwise = new link(To);
		leg->companion_clockwise = new link(From);
		links.push_back(leg);
		m_linked = false;
	}
	
	~t_point()
	{
 		delete links[0]->counter_clockwise;
 		delete links[0]->companion_clockwise;
	}
	
	void update()
	{
		return_if_fail(links.size() == 1);
		if (!m_linked)
			return;
		link* leg = links[0];
		return_if_fail(leg->edge);
		return_if_fail(leg->counter_clockwise);
		return_if_fail(leg->companion);
		return_if_fail(leg->companion_clockwise);
	}
	link* join(point* other_point, bool recurse = true)
	{
		m_linked = true;
		link* leg = links[0];
		if (recurse)
		{
			leg->companion = other_point->join(this, false);
			leg->companion->companion = leg;
		}
		return leg;
	}
	void complete()
	{
		if (!m_linked)
			return;
		links[0]->complete();
	}
private:
	bool m_linked;
};

/// class splitter
splitter::splitter(k3d::polyhedron& Polyhedron, k3d::mesh::points_t& Points, bool Use_selection) : m_Polyhedron(Polyhedron), m_Points(Points)
{
	for (unsigned long i = 0; i < Polyhedron.faces.size(); ++i)
	{
		m_all_faces[Polyhedron.faces[i]->first_edge] = Polyhedron.faces[i];
		k3d::split_edge* last_edge = Polyhedron.faces[i]->first_edge;
		k3d::split_edge* this_edge = last_edge->face_clockwise;

		bool has_selected_edge = false;
		has_selected_edge = get_sharpness(*last_edge) > 0.0 ? true : has_selected_edge;
		
		if (Use_selection && last_edge->vertex->selected)
			has_selected_edge = true;

		k3d::point* centroid = new k3d::point(last_edge->vertex->position);

		unsigned long corners = 1;

		while(this_edge != Polyhedron.faces[i]->first_edge)
		{
			++corners;
			centroid->position += this_edge->vertex->position;
			has_selected_edge = get_sharpness(*this_edge) > 0.0 ? true : has_selected_edge;
			if (Use_selection && this_edge->vertex->selected)
				has_selected_edge = true;
			last_edge = this_edge;
			this_edge = this_edge->face_clockwise;
		}
		if (has_selected_edge)
		{
			point_edge_info info;
			info.second[1] = last_edge;
			centroid->position /= corners;
			last_edge = Polyhedron.faces[i]->first_edge;
			this_edge = last_edge->face_clockwise;
			info.second[0] = this_edge;
			info.second[2] = last_edge->companion;
			info.first[1] = add_point(centroid);
			m_edges[last_edge] = info;

			while(this_edge != Polyhedron.faces[i]->first_edge)
			{
				info.second[0] = this_edge->face_clockwise;
				info.second[1] = last_edge;
				info.second[2] = this_edge->companion;
				info.first[0] = add_t_point(this_edge->vertex, last_edge, this_edge, true);
				info.first[1] = add_point(centroid);
				m_edges[this_edge] = info;
				last_edge = this_edge;
				this_edge = this_edge->face_clockwise;
			}
			m_edges[Polyhedron.faces[i]->first_edge].first[0] = add_t_point(Polyhedron.faces[i]->first_edge->vertex, last_edge, Polyhedron.faces[i]->first_edge, true);
			m_faces[Polyhedron.faces[i]->first_edge] = Polyhedron.faces[i];
			m_centroids.push_back(centroid);
		}
		else
		{
			delete centroid;
		}
	}
}

splitter::~splitter()
{
 	for (unsigned long i = 0; i < m_centroids.size(); ++i)
 	{
 		delete m_centroids[i];
 	}
	
	for (unsigned long i = 0; i < m_added_points.size(); ++i)
	{
		delete m_added_points[i];
	}
}

point* splitter::split_edge(k3d::split_edge& Edge, const double Factor, point* Start, point* End)
{
	// first point of the edge:
	k3d::point* this_point = (!Start || (Start->vertex == 0)) ? Edge.vertex : Start->vertex;
	// second point of the edge:
	k3d::point* other_point = (!End || (End->vertex == 0)) ? Edge.face_clockwise->vertex : End->vertex;
	// new point position:
	k3d::vector3 new_pos;
	new_pos = k3d::mix<k3d::vector3>(this_point->position, other_point->position, Factor);
	// new point:
	k3d::point* new_point = new k3d::point(new_pos);
	// new edge:
	k3d::split_edge* new_edge = new k3d::split_edge(new_point, Edge.face_clockwise);
	Edge.face_clockwise = new_edge;
	// companion edge
	if(Edge.companion) {
		k3d::split_edge* new_comp_edge = new k3d::split_edge(new_point, Edge.companion->face_clockwise);
		Edge.companion->face_clockwise = new_comp_edge;
		k3d::join_edges(*Edge.companion, *new_edge);
		k3d::join_edges(Edge, *new_comp_edge);
		m_Polyhedron.edges.push_back(new_comp_edge);
	}
	m_Polyhedron.edges.push_back(new_edge);
	return add_t_point(new_point, &Edge, Edge.face_clockwise);
}

void splitter::split_edge_add_point(k3d::split_edge& Edge, const double factor, point* Start, point* End)
{
	point* p = split_edge(Edge, factor, Start, End);
	m_Points.push_back(p->vertex);
}

void splitter::split_near(k3d::split_edge& edge, const double factor)
{
	point* far_point = far(*companion(edge));
	if (far_point)
	{
		k3d::vector3 new_pos = k3d::mix(start(edge)->vertex->position, end(edge)->vertex->position, factor);
		far_point->vertex->position = k3d::mix(new_pos, far_point->vertex->position, 0.5);
		set_near(edge, *(add_t_point(far_point->vertex, 0, 0)));
	}
	else
	{
		point* new_point = split_edge(edge, factor, start(edge), end(edge));
		m_Points.push_back(new_point->vertex);
		set_near(edge, *new_point);
	}
}

void splitter::split_far(k3d::split_edge& edge, const double factor)
{
	point* near_point = near(*companion(edge));
	if (near_point)
	{
		k3d::vector3 new_pos = k3d::mix(start(edge)->vertex->position, end(edge)->vertex->position, factor);
		near_point->vertex->position = k3d::mix(new_pos, near_point->vertex->position, 0.5);
		set_far(edge, *(add_t_point(near_point->vertex, 0, 0)));
	}
	else if (near(edge))
	{
		point* new_point = split_edge(*(edge.face_clockwise), factor, start(edge), end(edge));
		m_Points.push_back(new_point->vertex);
		set_far(edge, *new_point);
	}
	else
	{
		point* new_point = split_edge(edge, factor, start(edge), end(edge));
		m_Points.push_back(new_point->vertex);
		set_far(edge, *new_point);
	}
}

void splitter::make_creases()
{
	// add the new points
	for (faces_t::iterator face = m_faces.begin(); face != m_faces.end(); ++face)
	{
		k3d::split_edge* first_edge = face->first;
		k3d::split_edge* this_edge = first_edge;
		bool start = true;
		while (this_edge != first_edge || start)
		{
			edges_t::iterator edge = find_info(*this_edge);
			double sharpness = get_sharpness(*(edge->first));
			double cl_sharpness = get_sharpness(*(edge->second.second[0]));
			if (sharpness > 0.0)
			{
				if (cl_sharpness > 0.0)
				{
					// Use the geometric mean of both factors to calculate the factor for lerping between the centroid and the corner
					double f = 2*sqrt(sharpness_to_factor(sharpness) * sharpness_to_factor(cl_sharpness));
					k3d::vector3 face_pos(k3d::mix<k3d::vector3>(edge->second.second[0]->vertex->position, edge->second.first[1]->vertex->position, f));
					point* face_vertex = add_point(new k3d::point(face_pos));
					m_Points.push_back(face_vertex->vertex);
					set_face_vertex(*(edge->second.second[0]), *face_vertex);
					if (!companion(*(edge->second.second[0])) || get_sharpness(*clockwise(*companion(*(edge->second.second[0])))) > 0.0)
					{
						// the clockwise edge
						split_near(*(edge->second.second[0]),sharpness_to_factor(sharpness));
						// the edge itself
						split_far(*(edge->first), 1-sharpness_to_factor(cl_sharpness));
					}
					else if (get_sharpness(*counter_clockwise(*companion(*(edge->first)))))
					{
						// the clockwise edge
						split_near(*(edge->second.second[0]),sharpness_to_factor(sharpness));
						// the edge itself
						split_far(*(edge->first), 1-sharpness_to_factor(cl_sharpness));
					}
				}
				else
				{
					split_near(*(edge->second.second[0]),sharpness_to_factor(sharpness));
				}
			}
			else if (cl_sharpness > 0.0)
			{
				split_far(*(edge->first), 1-sharpness_to_factor(cl_sharpness));
			}
			start = false;
			this_edge = clockwise(*this_edge);
		}
	}

	// correct clockwise and counter-clockwise edges
	for (faces_t::iterator face = m_faces.begin(); face != m_faces.end(); ++face)
	{
		k3d::split_edge* first_edge = face->first;
		k3d::split_edge* this_edge = first_edge;
		bool start = true;
		while (this_edge != first_edge || start)
		{
			point* end_v = end(*this_edge);
			point* near_v = near(*this_edge);
			point* far_v = far(*this_edge);
			point* companion_far_v = far(*companion(*this_edge));
			point* companion_near_v = near(*companion(*this_edge));
			if (near_v || companion_far_v)
			{
				if (near_v)
				{
					near_v->links[0]->counter_clockwise->edge = this_edge;
					near_v->links[0]->companion_clockwise->edge = this_edge->face_clockwise;
				}
				if (far_v || companion_near_v)
				{
					if (far_v)
					{
						far_v->links[0]->counter_clockwise->edge = this_edge->face_clockwise;
						far_v->links[0]->companion_clockwise->edge = this_edge->face_clockwise->face_clockwise;
					}
						end_v->links[0]->counter_clockwise->edge = this_edge->face_clockwise->face_clockwise;
				}
				else
				{
					end_v->links[0]->counter_clockwise->edge = this_edge->face_clockwise;
				}
			}
			else if (far_v || companion_near_v)
			{
				if (far_v)
				{
					far_v->links[0]->counter_clockwise->edge = this_edge;
					far_v->links[0]->companion_clockwise->edge = this_edge->face_clockwise;
				}
				end_v->links[0]->counter_clockwise->edge = this_edge->face_clockwise;
			}
			start = false;
			this_edge = clockwise(*this_edge);
		}
		
		this_edge = first_edge;
		start = true;
		// connect the new points
		while (this_edge != first_edge || start)
		{
			point* face_v = face_vertex(*this_edge);
			point* near_v = near(*this_edge);
			point* far_v = far(*this_edge);
			point* start_v = this->start(*this_edge);
			point* next_face_v = face_vertex(*clockwise(*this_edge));
			point* next_near_v = 0;
			if (face_v && !next_face_v)
				next_near_v = near(*clockwise(*this_edge));
			if (face_v)
			{
				if (near_v)
				{
					link* l = near_v->join(face_v);
					m_Polyhedron.edges.push_back(l->edge);
					m_Polyhedron.edges.push_back(l->companion->edge);
				}
				if (far_v)
				{
					if (next_face_v)
					{
						link* l = next_face_v->join(face_v);
						m_Polyhedron.edges.push_back(l->edge);
						m_Polyhedron.edges.push_back(l->companion->edge);
						l = far_v->join(next_face_v);
						m_Polyhedron.edges.push_back(l->edge);
						m_Polyhedron.edges.push_back(l->companion->edge);
					}
				}
			}
			if (face_v && !near_v && !far(*counter_clockwise(*this_edge)))
			{
				link* l = start_v->join(face_v);
				m_Polyhedron.edges.push_back(l->edge);
				m_Polyhedron.edges.push_back(l->companion->edge);
				k3d::vector3 n = k3d::normal(this_edge);
				face_v->reorder_on(n);
			}
			if (face_v && next_face_v && !far_v)
			{
				link* l = face_v->join(next_face_v);
				m_Polyhedron.edges.push_back(l->edge);
				m_Polyhedron.edges.push_back(l->companion->edge);
			}
			if (!face_v && far_v && next_face_v)
			{
				link* l = far_v->join(next_face_v);
				m_Polyhedron.edges.push_back(l->edge);
				m_Polyhedron.edges.push_back(l->companion->edge);
				k3d::vector3 n = k3d::normal(this_edge);
				next_face_v->reorder_on(n);
			}
			if (!next_face_v && far_v)
			{
				point* next_v = face_vertex(*clockwise(*clockwise(*this_edge)));
				if (next_v)
				{
					link* l = next_v->join(far_v);
					m_Polyhedron.edges.push_back(l->edge);
					m_Polyhedron.edges.push_back(l->companion->edge);
 				}
			}
			point* last_far_v = far(*counter_clockwise(*this_edge));
			if (!next_face_v && !face_v && last_far_v)
			{
				link* l = last_far_v->join(near(*clockwise(*this_edge)));
				m_Polyhedron.edges.push_back(l->edge);
				m_Polyhedron.edges.push_back(l->companion->edge);
			}

			if (next_near_v)
			{
				link* l = next_near_v->join(face_v);
				m_Polyhedron.edges.push_back(l->edge);
				m_Polyhedron.edges.push_back(l->companion->edge);
			}
 			start = false;
 			this_edge = clockwise(*this_edge);
		} // end while (face loop)
		
		this_edge = first_edge;
		start = true;
		// update all links
		while (this_edge != first_edge || start)
		{
			point* face_v = face_vertex(*this_edge);
			point* near_v = near(*this_edge);
			point* far_v = far(*this_edge);
			if (face_v)
			{
				face_v->update();
			}
			if (near_v)
			{
				near_v->update();
			}
			if (far_v)
			{
				far_v->update();
			}
			start = false;
 			this_edge = clockwise(*this_edge);
		} // end while (face loop)
		
		this_edge = first_edge;
		start = true;
		// complete the k3d::split_edges in the links
		while (this_edge != first_edge || start)
		{
			point* face_v = face_vertex(*this_edge);
			point* near_v = near(*this_edge);
			point* far_v = far(*this_edge);
			if (face_v)
			{
				face_v->complete();
			}
			if (near_v)
			{
				near_v->complete();
			}
			if (far_v)
			{
				far_v->complete();
			}
			start = false;
 			this_edge = clockwise(*this_edge);
		} // end while (face loop)
	} // end for (all faces)

	// add new faces
	for (k3d::polyhedron::edges_t::iterator edge = m_Polyhedron.edges.begin(); edge != m_Polyhedron.edges.end(); ++edge)
	{
		add_face(*(*edge));
	}
}

void splitter::link_points(point* A, point* B)
{
	return_if_fail(A || B || A->vertex || B->vertex);
	link* l = A->join(B);
	m_Polyhedron.edges.push_back(l->edge);
	m_Polyhedron.edges.push_back(l->companion->edge);
	A->update();
	B->update();
	A->complete();
	B->complete();
	add_face(*(l->edge));
	add_face(*(l->companion->edge));
}

void splitter::link_points()
{
	for (faces_t::iterator face = m_faces.begin(); face != m_faces.end(); ++face)
	{
		k3d::split_edge* first_edge = face->first;
		k3d::split_edge* this_edge = first_edge;
		bool start = true;
		std::vector<point*> points;
		while (this_edge != first_edge || start)
		{
			if (this_edge->vertex->selected && (!this_edge->face_clockwise->vertex->selected || this_edge->face_clockwise->face_clockwise->vertex->selected))
				points.push_back(this->start(*this_edge));
			start = false;
			this_edge = clockwise(*this_edge);
		}
		if (points.size() == 2)
			link_points(points[0], points[1]);
	}
}

void splitter::split_face_parallel(k3d::split_edge& Edge, double Factor)
{
	return_if_fail(&Edge);
	return_if_fail(Edge.face_clockwise);
	// get the counter-clockwise edge
	k3d::split_edge* ccl;
	for(ccl = &Edge; ccl && ccl->face_clockwise; ccl = ccl->face_clockwise)
	{
		if (ccl->face_clockwise == &Edge)
			break;
	}
	point* start = split_edge(*ccl, 1-Factor);
	m_Points.push_back(start->vertex);
	point* end = split_edge(*(Edge.face_clockwise), Factor);
	m_Points.push_back(end->vertex);
	link_points(start, end);
}

splitter::edges_t::iterator& splitter::find_info(k3d::split_edge& Edge)
{
	if (&Edge == m_cached_edge.first)
		return m_cached_edge.second;
	m_cached_edge.first = &Edge;
	m_cached_edge.second = m_edges.find(&Edge);
	return m_cached_edge.second;
}

k3d::split_edge* splitter::get_edge(k3d::split_edge& Edge, int Index)
{
	edges_t::iterator edge = find_info(Edge);
	if (edge != m_edges.end())
	{
		return edge->second.second[Index];
	}
	return 0;
}

k3d::split_edge* splitter::clockwise(k3d::split_edge& Edge)
{
	return get_edge(Edge, 0);
}

k3d::split_edge* splitter::counter_clockwise(k3d::split_edge& Edge)
{
	return get_edge(Edge, 1);
}

k3d::split_edge* splitter::companion(k3d::split_edge& Edge)
{
	return get_edge(Edge, 2);
}

point* splitter::get_point(k3d::split_edge& Edge, int Index)
{
	edges_t::iterator edge = find_info(Edge);
	if (edge != m_edges.end())
	{
		return edge->second.first[Index];
	}
	return 0;
}

point* splitter::start(k3d::split_edge& Edge)
{
	return get_point(Edge, 0);
}

point* splitter::end(k3d::split_edge& Edge)
{
	return start(*clockwise(Edge));
}

k3d::point* splitter::centroid(k3d::split_edge& Edge)
{
	return get_point(Edge, 1)->vertex;
}

point* splitter::near(k3d::split_edge& Edge)
{
	return get_point(Edge, 2);
}

point* splitter::far(k3d::split_edge& Edge)
{
	return get_point(Edge, 3);
}

point* splitter::face_vertex(k3d::split_edge& Edge)
{
	return get_point(Edge, 4);
}

void splitter::set_point(k3d::split_edge& Edge, point& Point, int Index)
{
	edges_t::iterator edge = find_info(Edge);
	if (edge != m_edges.end())
	{
		edge->second.first[Index] = &Point;
	}
}

void splitter::set_near(k3d::split_edge& Edge, point& Point)
{
	set_point(Edge, Point, 2);
}

void splitter::set_far(k3d::split_edge& Edge, point& Point)
{
	set_point(Edge, Point, 3);
}

void splitter::set_face_vertex(k3d::split_edge& Edge, point& Point)
{
	set_point(Edge, Point, 4);
}

bool splitter::is_face(k3d::split_edge& Edge)
{
	k3d::split_edge* last_edge = &Edge;
	k3d::split_edge* this_edge = Edge.face_clockwise;

	while(this_edge != &Edge)
		{
			if(m_all_faces.find(last_edge) != m_all_faces.end())
			{
				return true;
			}

			last_edge = this_edge;
			if (this_edge)
				this_edge = this_edge->face_clockwise;
			else
				return true;
		}

	return (m_all_faces.find(last_edge) != m_all_faces.end());
}

void splitter::add_face(k3d::split_edge& Edge)
{
	if (!(is_face(Edge)))
	{
		k3d::face* new_face = new k3d::face(&Edge);
		m_Polyhedron.faces.push_back(new_face);
		m_all_faces[&Edge] = new_face;
	}
}

point* splitter::add_point(k3d::point* Point)
{
	point* p = new point(Point);
	m_added_points.push_back(p);
	return p;
}

point* splitter::add_t_point(k3d::point* Point, k3d::split_edge* To, k3d::split_edge* From, bool dummy_edge)
{
	point* p = new t_point(Point, To, From, dummy_edge);
	m_added_points.push_back(p);
	return p;
}

double get_sharpness(const k3d::split_edge& Edge)
{
	for(k3d::parameters_t::const_iterator tag = Edge.tags.begin(); tag != Edge.tags.end(); ++tag)
	{
		if(tag->first == "crease" && (tag->second.type() == typeid(k3d::ri::real)))
		{
			return boost::any_cast<double>(tag->second);
		}
	}
	return 0.0;
}

void crease(const k3d::mesh& Input, k3d::mesh& Output)
{
	k3d::deep_copy(Input, Output);
	for(k3d::mesh::polyhedra_t::iterator it = Output.polyhedra.begin(); it != Output.polyhedra.end(); ++it)
	{
		k3d::polyhedron& Polyhedron = **it;
		return_if_fail(k3d::is_valid(Polyhedron));
		splitter Splitter(Polyhedron, Output.points);
		Splitter.make_creases();

		// remove crease info to avoid calculating creases twice
		for(unsigned long j = 0; j < Polyhedron.edges.size(); ++j)
			Polyhedron.edges[j]->tags.erase("crease");

		return_if_fail(k3d::is_valid(Polyhedron));
	}
}

void split_faces_parallel(const k3d::mesh& Input, k3d::mesh& Output, const double Factor)
{
	k3d::deep_copy(Input, Output);
	for(k3d::mesh::polyhedra_t::iterator it = Output.polyhedra.begin(); it != Output.polyhedra.end(); ++it)
	{
		k3d::polyhedron& Polyhedron = **it;
		return_if_fail(k3d::is_valid(Polyhedron));
		splitter Splitter(Polyhedron, Output.points);
		std::vector<k3d::split_edge*> selected_edges;
		for (k3d::polyhedron::edges_t::iterator edge = Polyhedron.edges.begin(); edge != Polyhedron.edges.end(); ++edge)
		{
			if ((*edge)->selected)
				selected_edges.push_back(*edge);
		}
		for (std::vector<k3d::split_edge*>::iterator edge = selected_edges.begin(); edge != selected_edges.end(); ++edge)
			Splitter.split_face_parallel(*(*edge), Factor);

		return_if_fail(k3d::is_valid(Polyhedron));
	}
}
} // namespace subdiv
