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

#include <k3dsdk/classes.h>
#include <k3dsdk/bounded.h>
#include <k3dsdk/glutility.h>
#include <k3dsdk/imaterial.h>
#include <k3dsdk/measurement.h>
#include <k3dsdk/object.h>
#include <k3dsdk/persistence.h>
#include <k3dsdk/mesh.h>
#include <k3dsdk/mesh_filter.h>
#include <k3dsdk/module.h>
#include <k3dsdk/plugins.h>
#include <k3dsdk/renderman.h>
#include <k3dsdk/transformable.h>
#include <k3dsdk/viewport.h>

#include "blobby_polygonizer.h"
#include "subdiv_algorithms.h"

#include <limits>
#include <map>

namespace libk3dmesh
{

namespace detail
{

/// Calculates the center point (as an average) for a face
k3d::vector3 center_point(const k3d::face& Face)
{
	k3d::vector3 center(0, 0, 0);

	unsigned long count = 0;
	for(k3d::split_edge* edge = Face.first_edge; edge; edge = edge->face_clockwise)
		{
			count++;
			center += edge->vertex->position;
			if(Face.first_edge == edge->face_clockwise)
				break;
		}
	if(count)
		center /= count;

	return center;
}

/// Functor object for applying a transformation to a collection of points
struct transform_points
{
	transform_points(const k3d::matrix4& Matrix) :
		matrix(Matrix)
	{
	}

	void operator()(k3d::point* const Point)
	{
		Point->position = matrix * Point->position;
	}

	const k3d::matrix4 matrix;
};

/////////////////////////////////////////////////////////////////////////////
// draw_blobby

/// Draws the components that make up a blobby (Visitor Design Pattern)
class draw_blobby :
	public k3d::blobby::visitor
{
public:
	void visit_constant(k3d::blobby::constant&)
	{
	}

	void visit_ellipsoid(k3d::blobby::ellipsoid& Ellipsoid)
	{
		glBegin(GL_POINTS);
			glVertex3dv(Ellipsoid.origin->position.n);
		glEnd();
	}

	void visit_segment(k3d::blobby::segment& Segment)
	{
		glLineWidth(Segment.radius);
		glBegin(GL_LINES);
			glVertex3dv(Segment.start->position.n);
			glVertex3dv(Segment.end->position.n);
		glEnd();
	}

	void visit_subtract(k3d::blobby::subtract& Subtract)
	{
		Subtract.subtrahend->accept(*this);
		Subtract.minuend->accept(*this);
	}

	void visit_divide(k3d::blobby::divide& Divide)
	{
		Divide.dividend->accept(*this);
		Divide.divisor->accept(*this);
	}

	void visit_add(k3d::blobby::add& Add)
	{
		Add.operands_accept(*this);
	}

	void visit_multiply(k3d::blobby::multiply& Multiply)
	{
		Multiply.operands_accept(*this);
	}

	void visit_min(k3d::blobby::min& Min)
	{
		Min.operands_accept(*this);
	}

	void visit_max(k3d::blobby::max& Max)
	{
		Max.operands_accept(*this);
	}
};

/////////////////////////////////////////////////////////////////////////////
// select_blobby

/// Draws the components that make up a blobby (Visitor Design Pattern)
class select_blobby :
	public k3d::blobby::visitor
{
public:
	void visit_constant(k3d::blobby::constant&)
	{
	}

	void visit_ellipsoid(k3d::blobby::ellipsoid& Ellipsoid)
	{
		k3d::glPushName(&Ellipsoid);
		glBegin(GL_POINTS);
			glVertex3dv(Ellipsoid.origin->position.n);
		glEnd();
		k3d::glPopName();
	}

	void visit_segment(k3d::blobby::segment& Segment)
	{
		k3d::glPushName(&Segment);
		glLineWidth(Segment.radius);
		glBegin(GL_LINES);
			glVertex3dv(Segment.start->position.n);
			glVertex3dv(Segment.end->position.n);
		glEnd();
		k3d::glPopName();
	}

	void visit_subtract(k3d::blobby::subtract& Subtract)
	{
		Subtract.subtrahend->accept(*this);
		Subtract.minuend->accept(*this);
	}

	void visit_divide(k3d::blobby::divide& Divide)
	{
		Divide.dividend->accept(*this);
		Divide.divisor->accept(*this);
	}

	void visit_add(k3d::blobby::add& Add)
	{
		Add.operands_accept(*this);
	}

	void visit_multiply(k3d::blobby::multiply& Multiply)
	{
		Multiply.operands_accept(*this);
	}

	void visit_min(k3d::blobby::min& Min)
	{
		Min.operands_accept(*this);
	}

	void visit_max(k3d::blobby::max& Max)
	{
		Max.operands_accept(*this);
	}
};

} // namespace detail

/////////////////////////////////////////////////////////////////////////////
// mesh_instance_implementation

class mesh_instance_implementation :
	public k3d::bounded<k3d::viewport::drawable<k3d::ri::renderable<k3d::mesh_filter<k3d::transformable<k3d::persistent<k3d::object> > > > > >
{
	typedef k3d::bounded<k3d::viewport::drawable<k3d::ri::renderable<k3d::mesh_filter<k3d::transformable<k3d::persistent<k3d::object> > > > > > base;

public:
	mesh_instance_implementation(k3d::idocument& Document) :
		base(Document),
		m_show_blobby_surface(k3d::init_name("blobby_surface") + k3d::init_description("Show blobbies surfaces [boolean]") + k3d::init_value(true) + k3d::init_document(Document)),
		m_sds_cache(0),
		m_sds_levels(k3d::init_name("sds_levels") + k3d::init_description("Subdivision Levels [integer]") + k3d::init_constraint(k3d::constraint::minimum(1UL)) + k3d::init_document(Document) + k3d::init_value(3) + k3d::init_precision(0) + k3d::init_step_increment(1) + k3d::init_units(typeid(k3d::measurement::scalar))),
		m_color(k3d::init_name("color") + k3d::init_description("Color [color]") + k3d::init_value(k3d::color(0, 0, 0)) + k3d::init_document(Document))
	{
		register_property(m_show_blobby_surface);
		register_property(m_sds_levels);
		register_property(m_color);

		enable_serialization(k3d::persistence::proxy(m_show_blobby_surface));
		enable_serialization(k3d::persistence::proxy(m_sds_levels));
		enable_serialization(k3d::persistence::proxy(m_color));

		m_input_mesh.changed_signal().connect(SigC::slot(*this, &mesh_instance_implementation::on_reset_cache));
		m_sds_levels.changed_signal().connect(SigC::slot(*this, &mesh_instance_implementation::on_reset_cache));

		m_input_mesh.changed_signal().connect(SigC::slot(*this, &mesh_instance_implementation::on_reset_geometry));
		m_input_matrix.changed_signal().connect(SigC::slot(*this, &mesh_instance_implementation::on_reset_geometry));
		m_position.changed_signal().connect(SigC::slot(*this, &mesh_instance_implementation::on_reset_geometry));
		m_orientation.changed_signal().connect(SigC::slot(*this, &mesh_instance_implementation::on_reset_geometry));
		m_scale.changed_signal().connect(SigC::slot(*this, &mesh_instance_implementation::on_reset_geometry));

		m_input_matrix.changed_signal().connect(SigC::slot(*this, &mesh_instance_implementation::async_redraw_all));
		m_position.changed_signal().connect(SigC::slot(*this, &mesh_instance_implementation::async_redraw_all));
		m_orientation.changed_signal().connect(SigC::slot(*this, &mesh_instance_implementation::async_redraw_all));
		m_scale.changed_signal().connect(SigC::slot(*this, &mesh_instance_implementation::async_redraw_all));
		m_show_blobby_surface.changed_signal().connect(SigC::slot(*this, &mesh_instance_implementation::async_redraw_all));
		m_color.changed_signal().connect(SigC::slot(*this, &mesh_instance_implementation::async_redraw_all));

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

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

	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();
		k3d::deep_copy(*input, *output);

		// Transform the points ...
		std::for_each(output->points.begin(), output->points.end(), detail::transform_points(matrix()));

		return output;
	}

	void on_reset_cache()
	{
		/** \todo When this method is called, it's time to reset our cache of tessellated / subdivision mesh drawing data */

		m_output_mesh.reset();

		// Schedule a display refresh ...
		k3d::viewport::redraw_all(document(), k3d::iviewport::ASYNCHRONOUS);

		// Clear blobby cache
		blobby_surfaces_vertices.clear();
		blobby_surfaces_normals.clear();
		blobby_surfaces_polygons.clear();
		blobby_cache_map.clear();

		// Clear SDS cache
		delete m_sds_cache;
		m_sds_cache = 0;
	}

	void on_create_cache()
	{
		/** \todo When this method is called, it's time to create our cache of tessellated / subdivision mesh drawing data */
		const k3d::mesh* const mesh = m_input_mesh.property_value();
		if(!mesh)
			return;

		// Create blobby polygonized surfaces cache
		for(k3d::mesh::blobbies_t::const_iterator blobby = mesh->blobbies.begin(); blobby != mesh->blobbies.end(); ++blobby)
			{
				blobby_cache_map_t::const_iterator cached_blobby = blobby_cache_map.find(*blobby);
				if(cached_blobby == blobby_cache_map.end())
					{
						// Cache polygonized surface for blobby
						detail::vertices_t blobby_vertices;
						detail::vertices_t blobby_normals;
						detail::polygons_t blobby_polygons;
						detail::polygonize_blobby(*blobby, 0, blobby_vertices, blobby_normals, blobby_polygons);

						// Save surface
						blobby_cache_map[*blobby] = blobby_surfaces_vertices.size();
						blobby_surfaces_vertices.push_back(blobby_vertices);
						blobby_surfaces_normals.push_back(blobby_normals);
						blobby_surfaces_polygons.push_back(blobby_polygons);
					}
			}

		// If no SDS cache, generate the cache for all SDS polyhedra
		if(!m_sds_cache)
			{
				bool is_sds = false;
				for(k3d::mesh::polyhedra_t::const_iterator it = mesh->polyhedra.begin(); it != mesh->polyhedra.end(); ++it)
					if (k3d::polyhedron::CATMULL_CLARK_SUBDIVISION_MESH == (*it)->type)
						is_sds = true;

				if(is_sds)
					{
						m_sds_cache = new k3d::mesh();
						subdiv::catmull_clark(m_sds_levels.property_value(), *mesh, *m_sds_cache);
					}
			}
	}

	const k3d::bounding_box extents()
	{
		k3d::bounding_box results;

		const k3d::mesh* const mesh = m_input_mesh.property_value();
		if(!mesh)
			return results;

		for(k3d::mesh::points_t::const_iterator point = mesh->points.begin(); point != mesh->points.end(); ++point)
			{
				results.px = std::max(results.px, (*point)->position[0]);
				results.py = std::max(results.py, (*point)->position[1]);
				results.pz = std::max(results.pz, (*point)->position[2]);
				results.nx = std::min(results.nx, (*point)->position[0]);
				results.ny = std::min(results.ny, (*point)->position[1]);
				results.nz = std::min(results.nz, (*point)->position[2]);
			}

		return results;
	}

	void on_viewport_draw(const k3d::viewport::render_state& State)
	{
		// No input, so we're done ...
		const k3d::mesh* mesh = m_input_mesh.property_value();
		if(!mesh)
			return;

		// Update the drawing cache as-needed ...
		on_create_cache();

		// Have a nurbs renderer cached if we need it ...
		nurbs_renderer_t nurbs = 0;

		const k3d::color color = m_color.property_value();
		const k3d::color selected_color(1, 1, 1);

		// Handle SDS
		if(m_sds_cache)
			{
			const k3d::mesh* const cage = m_input_mesh.property_value();
			// draw the cage, only points and edges
			draw_points(cage->points.begin(), cage->points.end(), true, selected_color);
			draw_points(cage->points.begin(), cage->points.end(), false, color);

			draw_point_groups(cage->point_groups.begin(), cage->point_groups.end(), true, selected_color);
			draw_point_groups(cage->point_groups.begin(), cage->point_groups.end(), false, color);
			// draw the edges
			if(!cage->polyhedra.empty()) {
				draw_polyhedron_edges(cage->polyhedra.begin(), cage->polyhedra.end(), true, selected_color);
				draw_polyhedron_edges(cage->polyhedra.begin(), cage->polyhedra.end(), false, color);
			}

			mesh = m_sds_cache;
			}

		if(State.draw_points)
			{
				draw_points(mesh->points.begin(), mesh->points.end(), true, selected_color);
				draw_points(mesh->points.begin(), mesh->points.end(), false, color);

				draw_point_groups(mesh->point_groups.begin(), mesh->point_groups.end(), true, selected_color);
				draw_point_groups(mesh->point_groups.begin(), mesh->point_groups.end(), false, color);
			}

		if(State.draw_linear_curves && !mesh->linear_curve_groups.empty())
			{
				draw_linear_curve_groups(mesh->linear_curve_groups.begin(), mesh->linear_curve_groups.end(), true, selected_color);
				draw_linear_curve_groups(mesh->linear_curve_groups.begin(), mesh->linear_curve_groups.end(), false, color);
			}

		if(State.draw_cubic_curves && !mesh->cubic_curve_groups.empty())
			{
				draw_cubic_curve_groups(mesh->cubic_curve_groups.begin(), mesh->cubic_curve_groups.end(), true, selected_color);
				draw_cubic_curve_groups(mesh->cubic_curve_groups.begin(), mesh->cubic_curve_groups.end(), false, color);
			}

		if(State.draw_nucurves && !mesh->nucurve_groups.empty())
			{
				if(!nurbs)
					nurbs = nurbs_renderer(State);

				draw_nucurve_groups(nurbs, mesh->nucurve_groups.begin(), mesh->nucurve_groups.end(), true, selected_color);
				draw_nucurve_groups(nurbs, mesh->nucurve_groups.begin(), mesh->nucurve_groups.end(), false, color);
			}

		if(State.draw_face_orientations && !mesh->polyhedra.empty())
			draw_polyhedron_orientations(mesh->polyhedra.begin(), mesh->polyhedra.end(), selected_color);

		if(State.draw_edges)
			{
				if(!mesh->polyhedra.empty())
					{
						draw_polyhedron_edges(mesh->polyhedra.begin(), mesh->polyhedra.end(), true, selected_color);
						draw_polyhedron_edges(mesh->polyhedra.begin(), mesh->polyhedra.end(), false, color);
					}

				if(!mesh->bilinear_patches.empty())
					{
						draw_bilinear_patch_edges(mesh->bilinear_patches.begin(), mesh->bilinear_patches.end(), true, selected_color);
						draw_bilinear_patch_edges(mesh->bilinear_patches.begin(), mesh->bilinear_patches.end(), false, color);
					}

				if(!mesh->bicubic_patches.empty())
					{
						draw_bicubic_patch_edges(mesh->bicubic_patches.begin(), mesh->bicubic_patches.end(), true, selected_color);
						draw_bicubic_patch_edges(mesh->bicubic_patches.begin(), mesh->bicubic_patches.end(), false, color);
					}

				if(!mesh->nupatches.empty())
					{
						if(!nurbs)
							nurbs = nurbs_renderer(State);

						draw_nupatch_edges(nurbs, mesh->nupatches.begin(), mesh->nupatches.end(), true, selected_color);
						draw_nupatch_edges(nurbs, mesh->nupatches.begin(), mesh->nupatches.end(), false, color);
					}
			}

		if(State.draw_faces && !mesh->polyhedra.empty())
			{
				draw_polyhedra(mesh->polyhedra.begin(), mesh->polyhedra.end(), State.draw_two_sided);
			}

		if(State.draw_bilinear_patches && !mesh->bilinear_patches.empty())
			{
				draw_bilinear_patches(mesh->bilinear_patches.begin(), mesh->bilinear_patches.end(), State.draw_two_sided);
			}

		if(State.draw_bicubic_patches && !mesh->bicubic_patches.empty())
			{
				draw_bicubic_patches(mesh->bicubic_patches.begin(), mesh->bicubic_patches.end(), State.draw_two_sided);
			}

		if(State.draw_nupatches && !mesh->nupatches.empty())
			{
				if(!nurbs)
					nurbs = nurbs_renderer(State);

				draw_nupatches(nurbs, mesh->nupatches.begin(), mesh->nupatches.end(), State.draw_two_sided);
			}

		if(State.draw_blobbies && !mesh->blobbies.empty())
			{
				draw_blobbies(mesh->blobbies.begin(), mesh->blobbies.end(), true, selected_color);
				draw_blobbies(mesh->blobbies.begin(), mesh->blobbies.end(), false, color);
			}
	}

	void on_viewport_select(const k3d::viewport::render_state& State)
	{
		// No input, so we're done ...
		const k3d::mesh* const mesh = m_input_mesh.property_value();
		if(!mesh)
			return;

		// Update the drawing cache as-needed ...
		on_create_cache();

		sdpgl::store_attributes attributes();

		// Have a nurbs renderer cached if we need it ...
		nurbs_renderer_t nurbs = 0;

		// At the top-level, provide selection of this object ...
		k3d::glPushName(this);

		// Then, provide selection of the underlying mesh ...
		k3d::glPushName(mesh);

		if(!mesh->points.empty())
			select_points(mesh->points.begin(), mesh->points.end());

		if(!mesh->point_groups.empty())
			select_point_groups(mesh->point_groups.begin(), mesh->point_groups.end());

		if(!mesh->polyhedra.empty())
			{
				select_polyhedra_edges(mesh->polyhedra.begin(), mesh->polyhedra.end());
				select_polyhedra(mesh->polyhedra.begin(), mesh->polyhedra.end());
			}

		if(!mesh->linear_curve_groups.empty())
			select_linear_curve_groups(mesh->linear_curve_groups.begin(), mesh->linear_curve_groups.end());

		if(!mesh->cubic_curve_groups.empty())
			select_cubic_curve_groups(mesh->cubic_curve_groups.begin(), mesh->cubic_curve_groups.end());

		if(!mesh->bilinear_patches.empty())
			select_bilinear_patches(mesh->bilinear_patches.begin(), mesh->bilinear_patches.end());

		if(!mesh->nucurve_groups.empty())
			{
				if(!nurbs)
					nurbs = nurbs_renderer(State);

				select_nucurves(nurbs, mesh->nucurve_groups.begin(), mesh->nucurve_groups.end());
			}

		if(!mesh->bicubic_patches.empty())
			select_bicubic_patches(mesh->bicubic_patches.begin(), mesh->bicubic_patches.end());

		if(!mesh->nupatches.empty())
			{
				if(!nurbs)
					nurbs = nurbs_renderer(State);

				select_nupatches(nurbs, mesh->nupatches.begin(), mesh->nupatches.end());
			}

		if(!mesh->blobbies.empty())
			select_blobbies(mesh->blobbies.begin(), mesh->blobbies.end());

		k3d::glPopName();
		k3d::glPopName();
	}

	void on_renderman_render(const k3d::ri::render_state& State)
	{
		// No input, so we're done ...
		const k3d::mesh* const mesh = m_input_mesh.property_value();
		if(!mesh)
			return;

		k3d::ri::render(*mesh, State);
	}

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

	static k3d::iplugin_factory& get_factory()
	{
		static k3d::plugin_factory<
			k3d::document_plugin<mesh_instance_implementation>,
			k3d::interface_list<k3d::imesh_source,
			k3d::interface_list<k3d::imesh_sink,
			k3d::interface_list<k3d::itransform_source,
			k3d::interface_list<k3d::itransform_sink > > > > > factory(
				k3d::classes::MeshInstance(),
				"MeshInstance",
				"Renders an instance of a geometric mesh",
				"Objects",
				k3d::iplugin_factory::STABLE);

		return factory;
	}

private:
	void draw_points(const k3d::mesh::points_t::const_iterator Begin, const k3d::mesh::points_t::const_iterator End, const bool SelectionState, const k3d::color& Color)
	{
		sdpgl::store_attributes attributes();

		glDisable(GL_LIGHTING);
		glColor3d(Color.red, Color.green, Color.blue);
//		glPointSize(3.0);

		for(k3d::mesh::points_t::const_iterator point = Begin; point != End; ++point)
			{
				if((*point)->selected != SelectionState)
					continue;

				glBegin(GL_POINTS);
				glVertex3dv((*point)->position.n);
				glEnd();
			}
	}

	void select_points(const k3d::mesh::points_t::const_iterator Begin, const k3d::mesh::points_t::const_iterator End)
	{
		for(k3d::mesh::points_t::const_iterator point = Begin; point != End; ++point)
			{
				k3d::glPushName(*point);

				glBegin(GL_POINTS);
				glVertex3dv((*point)->position.n);
				glEnd();

				k3d::glPopName();
			}
	}

	void draw_point_groups(const k3d::mesh::point_groups_t::const_iterator Begin, const k3d::mesh::point_groups_t::const_iterator End, const bool SelectionState, const k3d::color& Color)
	{
		sdpgl::store_attributes attributes();

		glDisable(GL_LIGHTING);
		glColor3d(Color.red, Color.green, Color.blue);
//		glPointSize(3.0);

		for(k3d::mesh::point_groups_t::const_iterator group = Begin; group != End; ++group)
			{
				if((*group)->selected != SelectionState)
					continue;

				glBegin(GL_POINTS);
				for(k3d::point_group::points_t::const_iterator point = (*group)->points.begin(); point != (*group)->points.end(); ++point)
					glVertex3dv((*point)->position.n);
				glEnd();
			}
	}

	void select_point_groups(const k3d::mesh::point_groups_t::const_iterator Begin, const k3d::mesh::point_groups_t::const_iterator End)
	{
		sdpgl::store_attributes attributes();

		glDisable(GL_LIGHTING);

		for(k3d::mesh::point_groups_t::const_iterator group = Begin; group != End; ++group)
			{
				k3d::glPushName(*group);

				glBegin(GL_POINTS);
				for(k3d::point_group::points_t::const_iterator point = (*group)->points.begin(); point != (*group)->points.end(); ++point)
					glVertex3dv((*point)->position.n);
				glEnd();

				k3d::glPopName();
			}
	}

	void draw_linear_curve_groups(const k3d::mesh::linear_curve_groups_t::const_iterator Begin, const k3d::mesh::linear_curve_groups_t::const_iterator End, const bool SelectionState, const k3d::color& Color)
	{
		sdpgl::store_attributes attributes();

		glDisable(GL_LIGHTING);
		glColor3d(Color.red, Color.green, Color.blue);
		glLineWidth(1.0);

		for(k3d::mesh::linear_curve_groups_t::const_iterator group = Begin; group != End; ++group)
			{
				const GLenum mode = (*group)->wrap ? GL_LINE_LOOP : GL_LINE_STRIP;
				for(k3d::linear_curve_group::curves_t::const_iterator curve = (*group)->curves.begin(); curve != (*group)->curves.end(); ++curve)
					{
						if((*curve)->selected != SelectionState)
							continue;

						glBegin(mode);
						for(k3d::linear_curve::control_points_t::const_iterator control_point = (*curve)->control_points.begin(); control_point != (*curve)->control_points.end(); ++control_point)
							glVertex3dv((*control_point)->position.n);
						glEnd();
					}
			}
	}

	void select_linear_curve_groups(const k3d::mesh::linear_curve_groups_t::const_iterator Begin, const k3d::mesh::linear_curve_groups_t::const_iterator End)
	{
		sdpgl::store_attributes attributes();

		glDisable(GL_LIGHTING);

		for(k3d::mesh::linear_curve_groups_t::const_iterator group = Begin; group != End; ++group)
			{
				k3d::glPushName(*group);

				const GLenum mode = (*group)->wrap ? GL_LINE_LOOP : GL_LINE_STRIP;
				for(k3d::linear_curve_group::curves_t::const_iterator curve = (*group)->curves.begin(); curve != (*group)->curves.end(); ++curve)
					{
						k3d::glPushName(*curve);

						glBegin(mode);
						for(k3d::linear_curve::control_points_t::const_iterator control_point = (*curve)->control_points.begin(); control_point != (*curve)->control_points.end(); ++control_point)
							glVertex3dv((*control_point)->position.n);
						glEnd();

						k3d::glPopName();
					}

				k3d::glPopName();
			}
	}

	void draw_cubic_curve_groups(const k3d::mesh::cubic_curve_groups_t::const_iterator Begin, const k3d::mesh::cubic_curve_groups_t::const_iterator End, const bool SelectionState, const k3d::color& Color)
	{
		sdpgl::store_attributes attributes();

		glDisable(GL_LIGHTING);
		glColor3d(Color.red, Color.green, Color.blue);
		glLineWidth(1.0);

		const unsigned int v_count = 8;
		const GLint v_order = 4;
		const GLint v_stride = 3;

		glEnable(GL_MAP1_VERTEX_3);
		glDisable(GL_AUTO_NORMAL);
		glMapGrid1d(v_count, 0.0, 1.0);

		GLdouble patch_points[4 * 3];
		for(k3d::mesh::cubic_curve_groups_t::const_iterator group = Begin; group != End; ++group)
			{
//				const GLenum mode = (*group)->wrap ? GL_LINE_LOOP : GL_LINE_STRIP;
				for(k3d::cubic_curve_group::curves_t::const_iterator curve = (*group)->curves.begin(); curve != (*group)->curves.end(); ++curve)
					{
						if((*curve)->selected != SelectionState)
							continue;

						GLdouble* pp = patch_points;
						for(k3d::cubic_curve::control_points_t::const_iterator control_point = (*curve)->control_points.begin(); control_point != (*curve)->control_points.end(); ++control_point)
							{
								const k3d::vector3& v = (*control_point)->position;
								*pp++ = v[0];
								*pp++ = v[1];
								*pp++ = v[2];
							}
						glMap1d(GL_MAP1_VERTEX_3, 0, 1, v_stride, v_order, patch_points);
						glEvalMesh1(GL_LINE, 0, v_count);
					}
			}
	}

	void select_cubic_curve_groups(const k3d::mesh::cubic_curve_groups_t::const_iterator Begin, const k3d::mesh::cubic_curve_groups_t::const_iterator End)
	{
		sdpgl::store_attributes attributes();

		glDisable(GL_LIGHTING);

		const unsigned int v_count = 8;
		const GLint v_order = 4;
		const GLint v_stride = 3;

		glEnable(GL_MAP1_VERTEX_3);
		glDisable(GL_AUTO_NORMAL);
		glMapGrid1d(v_count, 0.0, 1.0);

		GLdouble patch_points[4 * 3];
		for(k3d::mesh::cubic_curve_groups_t::const_iterator group = Begin; group != End; ++group)
			{
				k3d::glPushName(*group);

//				const GLenum mode = (*group)->wrap ? GL_LINE_LOOP : GL_LINE_STRIP;
				for(k3d::cubic_curve_group::curves_t::const_iterator curve = (*group)->curves.begin(); curve != (*group)->curves.end(); ++curve)
					{
						k3d::glPushName(*curve);

						GLdouble* pp = patch_points;
						for(k3d::cubic_curve::control_points_t::const_iterator control_point = (*curve)->control_points.begin(); control_point != (*curve)->control_points.end(); ++control_point)
							{
								const k3d::vector3& v = (*control_point)->position;
								*pp++ = v[0];
								*pp++ = v[1];
								*pp++ = v[2];
							}
						glMap1d(GL_MAP1_VERTEX_3, 0, 1, v_stride, v_order, patch_points);
						glEvalMesh1(GL_LINE, 0, v_count);

						k3d::glPopName();
					}

				k3d::glPopName();
			}
	}

	void draw_nucurve_groups(const nurbs_renderer_t Nurbs, const k3d::mesh::nucurve_groups_t::const_iterator Begin, const k3d::mesh::nucurve_groups_t::const_iterator End, const bool SelectionState, const k3d::color& Color)
	{
		sdpgl::store_attributes attributes();

		glDisable(GL_LIGHTING);
		glColor3d(Color.red, Color.green, Color.blue);

		for(k3d::mesh::nucurve_groups_t::const_iterator group = Begin; group != End; ++group)
			{
				for(k3d::nucurve_group::curves_t::const_iterator nucurve = (*group)->curves.begin(); nucurve != (*group)->curves.end(); ++nucurve)
					{
						k3d::nucurve& curve = **nucurve;
						if(curve.selected != SelectionState)
							continue;

						std::vector<GLfloat> gl_knot_vector(curve.knots.begin(), curve.knots.end());

						k3d::nucurve::control_points_t& control_points = curve.control_points;
						std::vector<GLfloat> gl_control_points;
						gl_control_points.reserve(4 * control_points.size());
						for(unsigned int i = 0; i != control_points.size(); ++i)
							{
								gl_control_points.push_back(control_points[i].weight * control_points[i].position->position[0]);
								gl_control_points.push_back(control_points[i].weight * control_points[i].position->position[1]);
								gl_control_points.push_back(control_points[i].weight * control_points[i].position->position[2]);
								gl_control_points.push_back(control_points[i].weight);
							}

						gluBeginCurve(Nurbs);
						gluNurbsCurve(Nurbs, gl_knot_vector.size(), &gl_knot_vector[0], 4, &gl_control_points[0], curve.order, GL_MAP1_VERTEX_4);
						gluEndCurve(Nurbs);
					}
			}
	}

	void select_nucurves(const nurbs_renderer_t Nurbs, const k3d::mesh::nucurve_groups_t::const_iterator Begin, const k3d::mesh::nucurve_groups_t::const_iterator End)
	{
		sdpgl::store_attributes attributes();
		glDisable(GL_LIGHTING);

		for(k3d::mesh::nucurve_groups_t::const_iterator group = Begin; group != End; ++group)
			{
				for(k3d::nucurve_group::curves_t::const_iterator nucurve = (*group)->curves.begin(); nucurve != (*group)->curves.end(); ++nucurve)
					{
						k3d::nucurve& curve = **nucurve;
						k3d::nucurve::control_points_t& control_points = curve.control_points;

						std::vector<GLfloat> gl_knot_vector(curve.knots.begin(), curve.knots.end());

						std::vector<GLfloat> gl_control_points;
						gl_control_points.reserve(4 * control_points.size());
						for(unsigned int i = 0; i != control_points.size(); ++i)
							{
								gl_control_points.push_back(control_points[i].weight * control_points[i].position->position[0]);
								gl_control_points.push_back(control_points[i].weight * control_points[i].position->position[1]);
								gl_control_points.push_back(control_points[i].weight * control_points[i].position->position[2]);
								gl_control_points.push_back(control_points[i].weight);
							}

						k3d::glPushName(*nucurve);

						gluBeginCurve(Nurbs);
						gluNurbsCurve(Nurbs, gl_knot_vector.size(), &gl_knot_vector[0], 4, &gl_control_points[0], curve.order, GL_MAP1_VERTEX_4);
						gluEndCurve(Nurbs);

						k3d::glPopName();
					}
			}
	}

	void draw_polyhedron_edges(const k3d::mesh::polyhedra_t::const_iterator Begin, const k3d::mesh::polyhedra_t::const_iterator End, const bool SelectionState, const k3d::color& Color)
	{
		sdpgl::store_attributes attributes();
		glDisable(GL_LIGHTING);

		glColor3d(Color.red, Color.green, Color.blue);

		for(k3d::mesh::polyhedra_t::const_iterator polyhedron = Begin; polyhedron != End; ++polyhedron)
			{
				// Draws all edges that are part of a specific face
/*
				for(k3d::polyhedron::faces_t::const_iterator face = (*polyhedron)->faces.begin(); face != (*polyhedron)->faces.end(); ++face)
					{
						for(k3d::split_edge* edge = (*face)->first_edge; edge; edge = edge->face_clockwise)
							{
								if(edge->selected == SelectionState)
									{
										glBegin(GL_LINES);
										glVertex3dv(edge->vertex->position);
										glVertex3dv(edge->face_clockwise->vertex->position);
										glEnd();
									}

								if((*face)->first_edge == edge->face_clockwise)
									break;
							}
					}
*/
				// Alternative implementation that draws every edge that can be drawn, whether properly connected to a face or not
				for(k3d::polyhedron::edges_t::const_iterator e = (*polyhedron)->edges.begin(); e != (*polyhedron)->edges.end(); ++e)
					{
						k3d::split_edge* const edge = *e;

						if(edge->selected != SelectionState)
							continue;

						if(edge->vertex)
							{
								if(edge->face_clockwise && edge->face_clockwise->vertex)
									{
										glBegin(GL_LINES);
										glVertex3dv(edge->vertex->position);
										glVertex3dv(edge->face_clockwise->vertex->position);
										glEnd();
										continue;
									}

								if(edge->companion && edge->companion->vertex)
									{
										glBegin(GL_LINES);
										glVertex3dv(edge->vertex->position);
										glVertex3dv(edge->companion->vertex->position);
										glEnd();
										continue;
									}
							}
					}
			}
	}

	void set_material_color(const k3d::color& color)
	{
		GLfloat diffuse[4];

		diffuse[0] = static_cast<GLfloat>(color.red);
		diffuse[1] = static_cast<GLfloat>(color.green);
		diffuse[2] = static_cast<GLfloat>(color.blue);
		diffuse[3] = 1.0f;

		glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, diffuse);
	}

	void draw_polyhedra(const k3d::mesh::polyhedra_t::const_iterator Begin, const k3d::mesh::polyhedra_t::const_iterator End, const bool TwoSided)
	{
		sdpgl::store_attributes attributes();
		glEnable(GL_LIGHTING);
		glColor3d(0.8, 0.8, 1);

		glFrontFace(GL_CW);
		glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);

		if(TwoSided)
			glDisable(GL_CULL_FACE);
		else
			glEnable(GL_CULL_FACE);

		glEnable(GL_POLYGON_OFFSET_FILL);
		glPolygonOffset(1.0, 1.0);

		// Cache color change (probably not necessary)
		bool color_changed = false;

		for(k3d::mesh::polyhedra_t::const_iterator polyhedron = Begin; polyhedron != End; ++polyhedron)
			{
				k3d::viewport::setup_material((*polyhedron)->material);

				// Draw polygon
				for(k3d::polyhedron::faces_t::const_iterator face = (*polyhedron)->faces.begin(); face != (*polyhedron)->faces.end(); ++face)
					{
						// Change color if specified
						k3d::parameters_t::const_iterator color = (*face)->uniform_data.find("Cs");
						if(color != (*face)->uniform_data.end())
							{
								set_material_color(boost::any_cast<k3d::color>(color->second));
								color_changed = true;
							}
						else if(color_changed)
							{
								set_material_color(k3d::color(0.8, 0.8, 1.0));
								color_changed = false;
							}

						// Check for point normals
						const k3d::vector3 face_normal = k3d::normal(**face);
						std::vector<k3d::vector3> points;
						std::vector<k3d::vector3> normals;
						k3d::parameters_t::const_iterator N;
						for(k3d::split_edge* edge = (*face)->first_edge; edge; edge = edge->face_clockwise)
							{
								points.push_back(edge->vertex->position);

								N = edge->facevarying_data.find("N");
								if(N != edge->facevarying_data.end())
									{
										normals.push_back(boost::any_cast<k3d::ri::normal>(N->second));
									}
								else
									{
										N = edge->vertex->vertex_data.find("N");
										if(N != edge->vertex->vertex_data.end())
											{
												normals.push_back(boost::any_cast<k3d::ri::normal>(N->second));
											}
										else
											{
												normals.push_back(face_normal);
											}
									}

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

						// Draw polygon
						glBegin(GL_POLYGON);
						std::vector<k3d::vector3>::iterator point = points.begin();
						std::vector<k3d::vector3>::iterator normal = normals.begin();
						for(; point != points.end(); point++, normal++)
							{
								glNormal3dv(*normal);
								glVertex3dv(*point);
							}
						glEnd();
					}
			}
	}

	void draw_polyhedron_orientations(const k3d::mesh::polyhedra_t::const_iterator Begin, const k3d::mesh::polyhedra_t::const_iterator End, const k3d::color& Color)
	{
		sdpgl::store_attributes attributes();

		glDisable(GL_LIGHTING);
		glColor3d(Color.red, Color.green, Color.blue);

		for(k3d::mesh::polyhedra_t::const_iterator polyhedron = Begin; polyhedron != End; ++polyhedron)
			{
				for(k3d::polyhedron::faces_t::const_iterator face = (*polyhedron)->faces.begin(); face != (*polyhedron)->faces.end(); ++face)
					{
						const k3d::split_edge* const edge1 = (*face)->first_edge;
						if(!edge1)
							continue;

						const k3d::split_edge* const edge2 = edge1->face_clockwise;
						if(!edge2)
							continue;

						k3d::vector3 center = detail::center_point(**face);
						k3d::vector3 point1 = k3d::mix(center, edge1->vertex->position, 0.8);
						k3d::vector3 point2 = k3d::mix(center, edge2->vertex->position, 0.8);

						glBegin(GL_LINES);
						glVertex3dv(point1);
						glVertex3dv(point2);
						glEnd();

						glBegin(GL_POINTS);
						glVertex3dv(point1);
						glEnd();
					}
			}
	}

	void select_polyhedra_edges(const k3d::mesh::polyhedra_t::const_iterator Begin, const k3d::mesh::polyhedra_t::const_iterator End)
	{
		sdpgl::store_attributes attributes();

		for(k3d::mesh::polyhedra_t::const_iterator polyhedron = Begin; polyhedron != End; ++polyhedron)
			{
/*
				for(k3d::polyhedron::faces_t::const_iterator face = (*polyhedron)->faces.begin(); face != (*polyhedron)->faces.end(); ++face)
					{
						for(k3d::split_edge* edge = (*face)->first_edge; edge; edge = edge->face_clockwise)
							{
								k3d::glPushName(edge);

								glBegin(GL_LINES);
								glVertex3dv(edge->vertex->position);
								glVertex3dv(edge->face_clockwise->vertex->position);
								glEnd();

								k3d::glPopName();

								if((*face)->first_edge == edge->face_clockwise)
									break;
							}
					}
*/
				// Alternative implementation that draws every edge that can be drawn, whether properly connected to a face or not
				for(k3d::polyhedron::edges_t::const_iterator e = (*polyhedron)->edges.begin(); e != (*polyhedron)->edges.end(); ++e)
					{
						k3d::split_edge* const edge = *e;

						if(edge->vertex)
							{
								if(edge->face_clockwise && edge->face_clockwise->vertex)
									{
										k3d::glPushName(edge);

										glBegin(GL_LINES);
										glVertex3dv(edge->vertex->position);
										glVertex3dv(edge->face_clockwise->vertex->position);
										glEnd();

										k3d::glPopName();

										continue;
									}

								if(edge->companion && edge->companion->vertex)
									{
										k3d::glPushName(edge);

										glBegin(GL_LINES);
										glVertex3dv(edge->vertex->position);
										glVertex3dv(edge->companion->vertex->position);
										glEnd();

										k3d::glPopName();

										continue;
									}
							}
					}
			}
	}

	void select_polyhedra(const k3d::mesh::polyhedra_t::const_iterator Begin, const k3d::mesh::polyhedra_t::const_iterator End)
	{
		sdpgl::store_attributes attributes();

		glFrontFace(GL_CW);
		glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
		glDisable(GL_CULL_FACE);

		for(k3d::mesh::polyhedra_t::const_iterator polyhedron = Begin; polyhedron != End; ++polyhedron)
			{
				for(k3d::polyhedron::faces_t::const_iterator face = (*polyhedron)->faces.begin(); face != (*polyhedron)->faces.end(); ++face)
					{
						k3d::glPushName(*face);

						glBegin(GL_POLYGON);
						for(k3d::split_edge* edge = (*face)->first_edge; edge; edge = edge->face_clockwise)
							{
								glVertex3dv(edge->vertex->position);
								if((*face)->first_edge == edge->face_clockwise)
									break;
							}
						glEnd();

						k3d::glPopName();
					}
			}
	}

	void draw_bilinear_patch_edges(const k3d::mesh::bilinear_patches_t::const_iterator Begin, const k3d::mesh::bilinear_patches_t::const_iterator End, const bool SelectionState, const k3d::color& Color)
	{
		sdpgl::store_attributes attributes();

		glDisable(GL_LIGHTING);
		glColor3d(Color.red, Color.green, Color.blue);

		const unsigned int u_count = 10;
		const unsigned int v_count = 10;
		const GLint u_order = 2;
		const GLint v_order = 2;
		const GLint u_stride = 3;
		const GLint v_stride = 2 * u_stride;

		glEnable(GL_MAP2_VERTEX_3);
		glDisable(GL_AUTO_NORMAL);
		glMapGrid2d(u_count, 0.0, 1.0, v_count, 0.0, 1.0);

		GLdouble patch_points[2 * 2 * 3];
		for(k3d::mesh::bilinear_patches_t::const_iterator patch = Begin; patch != End; ++patch)
			{
				if((*patch)->selected != SelectionState)
					continue;

				GLdouble* pp = patch_points;
				for(k3d::bilinear_patch::control_points_t::const_iterator control_point = (*patch)->control_points.begin(); control_point != (*patch)->control_points.end(); ++control_point)
					{
						const k3d::vector3& v = (*control_point)->position;

						*pp++ = v[0];
						*pp++ = v[1];
						*pp++ = v[2];
					}

				glMap2d(GL_MAP2_VERTEX_3, 0, 1, u_stride, u_order, 0, 1, v_stride, v_order, &patch_points[0]);

				glEvalMesh2(GL_LINE, 0, 0, 0, v_count);
				glEvalMesh2(GL_LINE, u_count, u_count, 0, v_count);
				glEvalMesh2(GL_LINE, 0, u_count, 0, 0);
				glEvalMesh2(GL_LINE, 0, u_count, v_count, v_count);
			}
	}

	void draw_bilinear_patches(const k3d::mesh::bilinear_patches_t::const_iterator Begin, const k3d::mesh::bilinear_patches_t::const_iterator End, const bool TwoSided)
	{
		sdpgl::store_attributes attributes();
		glEnable(GL_LIGHTING);

		const unsigned int u_count = 10;
		const unsigned int v_count = 10;
		const GLint u_order = 2;
		const GLint v_order = 2;
		const GLint u_stride = 3;
		const GLint v_stride = 2 * u_stride;

		glFrontFace(GL_CCW);
		glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
		if(TwoSided)
			glDisable(GL_CULL_FACE);
		else
			glEnable(GL_CULL_FACE);

		glEnable(GL_MAP2_VERTEX_3);
		glEnable(GL_AUTO_NORMAL);
		glMapGrid2d(u_count, 0.0, 1.0, v_count, 0.0, 1.0);

		GLdouble patch_points[2 * 2 * 3];
		for(k3d::mesh::bilinear_patches_t::const_iterator patch = Begin; patch != End; ++patch)
			{
				k3d::viewport::setup_material((*patch)->material);

				GLdouble* pp = patch_points;
				for(k3d::bilinear_patch::control_points_t::const_iterator control_point = (*patch)->control_points.begin(); control_point != (*patch)->control_points.end(); ++control_point)
					{
						const k3d::vector3& v = (*control_point)->position;

						*pp++ = v[0];
						*pp++ = v[1];
						*pp++ = v[2];
					}

				glMap2d(GL_MAP2_VERTEX_3, 0, 1, u_stride, u_order, 0, 1, v_stride, v_order, &patch_points[0]);
				glEvalMesh2(GL_FILL, 0, u_count, 0, v_count);
			}
	}

	void select_bilinear_patches(const k3d::mesh::bilinear_patches_t::const_iterator Begin, const k3d::mesh::bilinear_patches_t::const_iterator End)
	{
		sdpgl::store_attributes attributes();
		glDisable(GL_LIGHTING);

		const unsigned int u_count = 10;
		const unsigned int v_count = 10;
		const GLint u_order = 2;
		const GLint v_order = 2;
		const GLint u_stride = 3;
		const GLint v_stride = 2 * u_stride;

		glFrontFace(GL_CCW);
		glDisable(GL_CULL_FACE);
		glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);

		glEnable(GL_MAP2_VERTEX_3);
		glDisable(GL_AUTO_NORMAL);
		glMapGrid2d(u_count, 0.0, 1.0, v_count, 0.0, 1.0);

		GLdouble patch_points[2 * 2 * 3];
		for(k3d::mesh::bilinear_patches_t::const_iterator patch = Begin; patch != End; ++patch)
			{
				k3d::glPushName(*patch);

				GLdouble* pp = patch_points;
				for(k3d::bilinear_patch::control_points_t::const_iterator control_point = (*patch)->control_points.begin(); control_point != (*patch)->control_points.end(); ++control_point)
					{
						const k3d::vector3& v = (*control_point)->position;

						*pp++ = v[0];
						*pp++ = v[1];
						*pp++ = v[2];
					}

				glMap2d(GL_MAP2_VERTEX_3, 0, 1, u_stride, u_order, 0, 1, v_stride, v_order, &patch_points[0]);
				glEvalMesh2(GL_FILL, 0, u_count, 0, v_count);

				k3d::glPopName();
			}
	}

	void draw_bicubic_patch_edges(const k3d::mesh::bicubic_patches_t::const_iterator Begin, const k3d::mesh::bicubic_patches_t::const_iterator End, const bool SelectionState, const k3d::color& Color)
	{
		sdpgl::store_attributes attributes();

		glDisable(GL_LIGHTING);
		glColor3d(Color.red, Color.green, Color.blue);

		const unsigned int u_count = 8;
		const unsigned int v_count = 8;
		const GLint u_order = 4;
		const GLint v_order = 4;
		const GLint u_stride = 3;
		const GLint v_stride = 4 * u_stride;

		glEnable(GL_MAP2_VERTEX_3);
		glDisable(GL_AUTO_NORMAL);
		glMapGrid2d(u_count, 0.0, 1.0, v_count, 0.0, 1.0);

		GLdouble patch_points[4 * 4 * 3];
		for(k3d::mesh::bicubic_patches_t::const_iterator patch = Begin; patch != End; ++patch)
			{
				if((*patch)->selected != SelectionState)
					continue;

				GLdouble* pp = patch_points;
				for(k3d::bicubic_patch::control_points_t::const_iterator control_point = (*patch)->control_points.begin(); control_point != (*patch)->control_points.end(); ++control_point)
					{
						const k3d::vector3& v = (*control_point)->position;

						*pp++ = v[0];
						*pp++ = v[1];
						*pp++ = v[2];
					}

				glMap2d(GL_MAP2_VERTEX_3, 0, 1, u_stride, u_order, 0, 1, v_stride, v_order, patch_points);

				glEvalMesh2(GL_LINE, 0, 0, 0, v_count);
				glEvalMesh2(GL_LINE, u_count, u_count, 0, v_count);
				glEvalMesh2(GL_LINE, 0, u_count, 0, 0);
				glEvalMesh2(GL_LINE, 0, u_count, v_count, v_count);
			}
	}

	void draw_bicubic_patches(const k3d::mesh::bicubic_patches_t::const_iterator Begin, const k3d::mesh::bicubic_patches_t::const_iterator End, const bool TwoSided)
	{
		sdpgl::store_attributes attributes();
		glEnable(GL_LIGHTING);

		const unsigned int u_count = 5;
		const unsigned int v_count = 5;
		const GLint u_order = 4;
		const GLint v_order = 4;
		const GLint u_stride = 3;
		const GLint v_stride = 4 * u_stride;

		glFrontFace(GL_CCW);
		glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
		if(TwoSided)
			glDisable(GL_CULL_FACE);
		else
			glEnable(GL_CULL_FACE);

		glEnable(GL_MAP2_VERTEX_3);
		glEnable(GL_AUTO_NORMAL);
		glMapGrid2d(u_count, 0.0, 1.0, v_count, 0.0, 1.0);

		GLdouble patch_points[4 * 4 * 3];
		for(k3d::mesh::bicubic_patches_t::const_iterator patch = Begin; patch != End; ++patch)
			{
				k3d::viewport::setup_material((*patch)->material);

				GLdouble* pp = patch_points;
				for(k3d::bicubic_patch::control_points_t::const_iterator control_point = (*patch)->control_points.begin(); control_point != (*patch)->control_points.end(); ++control_point)
					{
						const k3d::vector3& v = (*control_point)->position;

						*pp++ = v[0];
						*pp++ = v[1];
						*pp++ = v[2];
					}

				glMap2d(GL_MAP2_VERTEX_3, 0, 1, u_stride, u_order, 0, 1, v_stride, v_order, &patch_points[0]);
				glEvalMesh2(GL_FILL, 0, u_count, 0, v_count);
			}
	}

	void select_bicubic_patches(const k3d::mesh::bicubic_patches_t::const_iterator Begin, const k3d::mesh::bicubic_patches_t::const_iterator End)
	{
		sdpgl::store_attributes attributes();
		glDisable(GL_LIGHTING);

		const unsigned int u_count = 8;
		const unsigned int v_count = 8;
		const GLint u_order = 4;
		const GLint v_order = 4;
		const GLint u_stride = 3;
		const GLint v_stride = 4 * u_stride;

		glFrontFace(GL_CCW);
		glDisable(GL_CULL_FACE);
		glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);

		glEnable(GL_MAP2_VERTEX_3);
		glDisable(GL_AUTO_NORMAL);
		glMapGrid2d(u_count, 0.0, 1.0, v_count, 0.0, 1.0);

		GLdouble patch_points[4 * 4 * 3];
		for(k3d::mesh::bicubic_patches_t::const_iterator patch = Begin; patch != End; ++patch)
			{
				k3d::glPushName(*patch);

				GLdouble* pp = patch_points;
				for(k3d::bicubic_patch::control_points_t::const_iterator control_point = (*patch)->control_points.begin(); control_point != (*patch)->control_points.end(); ++control_point)
					{
						const k3d::vector3& v = (*control_point)->position;

						*pp++ = v[0];
						*pp++ = v[1];
						*pp++ = v[2];
					}

				glMap2d(GL_MAP2_VERTEX_3, 0, 1, u_stride, u_order, 0, 1, v_stride, v_order, &patch_points[0]);
				glEvalMesh2(GL_FILL, 0, u_count, 0, v_count);

				k3d::glPopName();
			}
	}

	void draw_nupatch_edges(const nurbs_renderer_t Nurbs, const k3d::mesh::nupatches_t::const_iterator Begin, const k3d::mesh::nupatches_t::const_iterator End, const bool SelectionState, const k3d::color& Color)
	{
		sdpgl::store_attributes attributes();

		glDisable(GL_LIGHTING);
		glColor3d(Color.red, Color.green, Color.blue);

		gluNurbsProperty(Nurbs, GLU_DISPLAY_MODE, GLU_OUTLINE_PATCH);

		for(k3d::mesh::nupatches_t::const_iterator nupatch = Begin; nupatch != End; ++nupatch)
			{
				if((**nupatch).selected != SelectionState)
					continue;

				render_nupatch(Nurbs, **nupatch);
			}
	}

	void draw_nupatches(const nurbs_renderer_t Nurbs, const k3d::mesh::nupatches_t::const_iterator Begin, const k3d::mesh::nupatches_t::const_iterator End, const bool TwoSided)
	{
		sdpgl::store_attributes attributes();

		glEnable(GL_LIGHTING);
		glEnable(GL_AUTO_NORMAL);

		if(TwoSided)
			glDisable(GL_CULL_FACE);
		else
			glEnable(GL_CULL_FACE);

		glPolygonOffset(1.0, 1.0);
		glEnable(GL_POLYGON_OFFSET_FILL);

		gluNurbsProperty(Nurbs, GLU_DISPLAY_MODE, GLU_FILL);

		for(k3d::mesh::nupatches_t::const_iterator nupatch = Begin; nupatch != End; ++nupatch)
			{
				k3d::viewport::setup_material((**nupatch).material);
				render_nupatch(Nurbs, **nupatch);
			}

		glDisable(GL_POLYGON_OFFSET_FILL);
	}

	void select_nupatches(const nurbs_renderer_t Nurbs, const k3d::mesh::nupatches_t::const_iterator Begin, const k3d::mesh::nupatches_t::const_iterator End)
	{
		sdpgl::store_attributes attributes();

		glDisable(GL_LIGHTING);
		glDisable(GL_AUTO_NORMAL);
		glDisable(GL_CULL_FACE);

		gluNurbsProperty(Nurbs, GLU_DISPLAY_MODE, GLU_FILL);

		for(k3d::mesh::nupatches_t::const_iterator nupatch = Begin; nupatch != End; ++nupatch)
			{
				k3d::glPushName(*nupatch);
				render_nupatch(Nurbs, **nupatch);
				k3d::glPopName();
			}
	}

	void render_nupatch(const nurbs_renderer_t Nurbs, const k3d::nupatch& Patch)
	{
		const unsigned int u_control_points_count = Patch.u_knots.size() - Patch.u_order;
		const unsigned int v_control_points_count = Patch.v_knots.size() - Patch.v_order;

		assert_warning(u_control_points_count * v_control_points_count == Patch.control_points.size());

		std::vector<GLfloat> gl_u_knot_vector(Patch.u_knots.begin(), Patch.u_knots.end());
		std::vector<GLfloat> gl_v_knot_vector(Patch.v_knots.begin(), Patch.v_knots.end());

		const GLint gl_u_stride = 4;
		const GLint gl_v_stride = gl_u_stride * u_control_points_count;

		const k3d::nupatch::control_points_t& control_points = Patch.control_points;
		std::vector<GLfloat> gl_control_points;
		gl_control_points.reserve(4 * control_points.size());
		for(unsigned int i = 0; i != control_points.size(); ++i)
			{
				gl_control_points.push_back(control_points[i].weight * control_points[i].position->position[0]);
				gl_control_points.push_back(control_points[i].weight * control_points[i].position->position[1]);
				gl_control_points.push_back(control_points[i].weight * control_points[i].position->position[2]);
				gl_control_points.push_back(control_points[i].weight);
			}

		gluBeginSurface(Nurbs);

		gluNurbsSurface(Nurbs,
			gl_u_knot_vector.size(),
			&gl_u_knot_vector[0],
			gl_v_knot_vector.size(),
			&gl_v_knot_vector[0],
			gl_u_stride,
			gl_v_stride,
			&gl_control_points[0],
			Patch.u_order,
			Patch.v_order,
			GL_MAP2_VERTEX_4);

		gluEndSurface(Nurbs);
	}

	void draw_blobbies(const k3d::mesh::blobbies_t::const_iterator Begin, const k3d::mesh::blobbies_t::const_iterator End, const bool SelectionState, const k3d::color& Color)
	{
		sdpgl::store_attributes attributes();

		glDisable(GL_LIGHTING);
		glColor3d(Color.red, Color.green, Color.blue);

		detail::draw_blobby draw_blobby;
		for(k3d::mesh::blobbies_t::const_iterator blobby = Begin; blobby != End; ++blobby)
			(*blobby)->accept(draw_blobby);

		// Offset solid geometry, so it's cleanly visible ...
		glEnable(GL_POLYGON_OFFSET_FILL);
		glPolygonOffset(1.0, 1.0);

		// Solid drawing ...
		glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
		glEnable(GL_LIGHTING);

		if(m_show_blobby_surface.property_value())
			for(k3d::mesh::blobbies_t::const_iterator blobby = Begin; blobby != End; ++blobby)
				render_blobby_surface(*blobby);
	}

	void select_blobbies(const k3d::mesh::blobbies_t::const_iterator Begin, const k3d::mesh::blobbies_t::const_iterator End)
	{
		sdpgl::store_attributes attributes();

		glDisable(GL_LIGHTING);

		detail::select_blobby select_blobby;
		for(k3d::mesh::blobbies_t::const_iterator blobby = Begin; blobby != End; ++blobby)
			(*blobby)->accept(select_blobby);

		glFrontFace(GL_CW);
		glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
		glDisable(GL_CULL_FACE);

		if(m_show_blobby_surface.property_value())
			for(k3d::mesh::blobbies_t::const_iterator blobby = Begin; blobby != End; ++blobby)
				{
					k3d::glPushName((*blobby)->root);
					render_blobby_surface(*blobby);
					k3d::glPopName();
				}
	}

	void render_blobby_surface(k3d::blobby* Opcode)
	{
		// Get surface number
		blobby_cache_map_t::const_iterator surface = blobby_cache_map.find(Opcode);
		if(surface == blobby_cache_map.end())
			return;

		unsigned long surface_number = surface->second;

		// Output cached surface
		const detail::vertices_t& blobby_vertices = blobby_surfaces_vertices[surface_number];
		const detail::vertices_t& blobby_normals = blobby_surfaces_normals[surface_number];
		const detail::polygons_t& blobby_polygons = blobby_surfaces_polygons[surface_number];
		for(unsigned long p = 0; p < blobby_polygons.size(); ++p)
			{
				detail::polygon_t path = blobby_polygons[p];

				// Calculate normal
				unsigned long pointcount = path.size();
				if(pointcount < 3)
					continue;

				// Draw polygon
				glBegin(GL_POLYGON);
				for(unsigned long i = 0; i < path.size(); ++i)
					{
						// Invert normal (Blobby field decreases from in to out)
						k3d::vector3 normal = -blobby_normals[path[i]];
						glNormal3dv(normal);
						k3d::vector3 vertex = blobby_vertices[path[i]];
						glVertex3dv(vertex);
					}
				glEnd();
			}
	}

	/// Blobby polygonized surfaces caching variables
	typedef std::map<k3d::blobby*, unsigned long> blobby_cache_map_t;
	blobby_cache_map_t blobby_cache_map;

	typedef std::vector<detail::vertices_t> blobby_surfaces_vertices_t;
	typedef std::vector<detail::polygons_t> blobby_surfaces_polygons_t;

	blobby_surfaces_vertices_t blobby_surfaces_vertices;
	blobby_surfaces_vertices_t blobby_surfaces_normals;
	blobby_surfaces_polygons_t blobby_surfaces_polygons;

	/// Blobby preview type
	k3d_data_property(bool, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_show_blobby_surface;

	/// Cached subdivision data
	k3d::mesh* m_sds_cache;
	/// SDS preview levels
	k3d_measurement_property(unsigned long, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::with_constraint) m_sds_levels;

	/// Color highlighting for the editor viewport
	k3d_data_property(k3d::color, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_color;
};

/////////////////////////////////////////////////////////////////////////////
// mesh_instance_factory

k3d::iplugin_factory& mesh_instance_factory()
{
	return mesh_instance_implementation::get_factory();
}

} // namespace libk3dmesh


