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

#include <k3dsdk/classes.h>
#include <k3dsdk/frames.h>
#include <k3dsdk/geometry.h>
#include <k3dsdk/iprojection.h>
#include <k3dsdk/iuser_interface.h>
#include <k3dsdk/iviewport_host.h>
#include <k3dsdk/object.h>
#include <k3dsdk/persistence.h>
#include <k3dsdk/measurement.h>
#include <k3dsdk/module.h>
#include <k3dsdk/property.h>
#include <k3dsdk/property_group_collection.h>
#include <k3dsdk/renderman.h>
#include <k3dsdk/time_source.h>
#include <k3dsdk/transformable.h>
#include <k3dsdk/viewport.h>

#include <sdpgl/sdpgl.h>

#include <iostream>

#ifdef	WIN32
#ifdef	near
#undef	near
#endif	//near
#ifdef	far
#undef	far
#endif	//far
#endif	//WIN32

namespace libk3dviewport
{

namespace detail
{

/// Functor object for initializing light sources during viewport drawing
class light_setup
{
public:
	light_setup() :
		m_light_number(0)
	{
	}

	void operator()(k3d::iobject* const Object)
	{
		k3d::viewport::ilight* const light = dynamic_cast<k3d::viewport::ilight*>(Object);
		if(light)
			light->setup_viewport_light(++m_light_number);
	}

private:
	unsigned long m_light_number;
};

/// Functor for drawing objects in the viewport
class draw
{
public:
	draw(const k3d::viewport::render_state& State) :
		m_state(State)
	{
	}

	void operator()(k3d::iobject* const Object)
	{
		k3d::viewport::idrawable* const drawable = dynamic_cast<k3d::viewport::idrawable*>(Object);
		if(drawable)
			drawable->viewport_draw(m_state);
	}

private:
	const k3d::viewport::render_state& m_state;
};

/// Functor for drawing objects in the viewport
class draw_selection
{
public:
	draw_selection(const k3d::viewport::render_state& State) :
		m_state(State)
	{
	}

	void operator()(k3d::iobject* const Object)
	{
		k3d::viewport::idrawable* const drawable = dynamic_cast<k3d::viewport::idrawable*>(Object);
		if(drawable)
			drawable->viewport_select(m_state);
	}

private:
	const k3d::viewport::render_state& m_state;
};

} // namespace detail

/////////////////////////////////////////////////////////////////////////////
// viewport_implementation

class viewport_implementation :
	public k3d::transformable<k3d::persistent<k3d::object> >,
	public k3d::iviewport,
	public k3d::viewport::iselection_engine,
	public k3d::property_group_collection
{
	typedef k3d::transformable<k3d::persistent<k3d::object> > base;

public:
	viewport_implementation(k3d::idocument& Document) :
		base(Document),
		m_point_size(k3d::init_name("point_size") + k3d::init_description("Point size [integer]") + k3d::init_value(4) + k3d::init_precision(1) + k3d::init_step_increment(1) + k3d::init_units(typeid(k3d::measurement::scalar)) + k3d::init_document(Document)),
		m_background_color(k3d::init_name("background_color") + k3d::init_description("Background color [color]") + k3d::init_value(k3d::color(0.8, 0.8, 0.8)) + k3d::init_document(Document)),
		m_fog(k3d::init_name("fog") + k3d::init_description("Fog [boolean]") + k3d::init_value(false) + k3d::init_document(Document)),
		m_fog_near(k3d::init_name("fog_near") + k3d::init_description("Fog near distance [number]") + k3d::init_value(0.0) + k3d::init_constraint(k3d::constraint::minimum(0.0)) + k3d::init_document(Document) + k3d::init_precision(1) + k3d::init_step_increment(0.1) + k3d::init_units(typeid(k3d::measurement::distance))),
		m_fog_far(k3d::init_name("fog_far") + k3d::init_description("Fog far distance [number]") + k3d::init_value(100.0) + k3d::init_constraint(k3d::constraint::minimum(0.0)) + k3d::init_document(Document) + k3d::init_precision(1) + k3d::init_step_increment(0.1) + k3d::init_units(typeid(k3d::measurement::distance))),
		m_orthographic(k3d::init_name("orthographic") + k3d::init_description("Orthographic [boolean]") + k3d::init_value(false) + k3d::init_document(Document)),
		m_left(k3d::init_name("left") + k3d::init_description("Left [number]") + k3d::init_value(-0.5) + k3d::init_document(Document) + k3d::init_precision(2) + k3d::init_step_increment(0.01) + k3d::init_units(typeid(k3d::measurement::scalar))),
		m_right(k3d::init_name("right") + k3d::init_description("Right [number]") + k3d::init_value(0.5) + k3d::init_document(Document) + k3d::init_precision(2) + k3d::init_step_increment(0.01) + k3d::init_units(typeid(k3d::measurement::scalar))),
		m_top(k3d::init_name("top") + k3d::init_description("Top [number]") + k3d::init_value(0.5) + k3d::init_document(Document) + k3d::init_precision(2) + k3d::init_step_increment(0.01) + k3d::init_units(typeid(k3d::measurement::scalar))),
		m_bottom(k3d::init_name("bottom") + k3d::init_description("Bottom [number]") + k3d::init_value(-0.5) + k3d::init_document(Document) + k3d::init_precision(2) + k3d::init_step_increment(0.01) + k3d::init_units(typeid(k3d::measurement::scalar))),
		m_near(k3d::init_name("near") + k3d::init_description("Near plane distance [number]") + k3d::init_value(1.0) + k3d::init_constraint(k3d::constraint::minimum(0.0)) + k3d::init_document(Document) + k3d::init_precision(1) + k3d::init_step_increment(0.1) + k3d::init_units(typeid(k3d::measurement::distance))),
		m_far(k3d::init_name("far") + k3d::init_description("Far plane distance [number]") + k3d::init_value(1000.0) + k3d::init_constraint(k3d::constraint::minimum(0.0)) + k3d::init_document(Document) + k3d::init_precision(1) + k3d::init_step_increment(0.1) + k3d::init_units(typeid(k3d::measurement::distance))),
		m_headlight(k3d::init_name("headlight") + k3d::init_description("Headlight [boolean]") + k3d::init_value(true) + k3d::init_document(Document)),
		m_show_lights(k3d::init_name("show_lights") + k3d::init_description("Show Lights [boolean]") + k3d::init_value(false) + k3d::init_document(Document)),
		m_draw_points(k3d::init_name("draw_points") + k3d::init_description("Draw Points [boolean]") + k3d::init_value(false) + k3d::init_document(Document)),
		m_draw_edges(k3d::init_name("draw_edges") + k3d::init_description("Draw Edges [boolean]") + k3d::init_value(true) + k3d::init_document(Document)),
		m_draw_faces(k3d::init_name("draw_faces") + k3d::init_description("Draw Faces [boolean]") + k3d::init_value(true) + k3d::init_document(Document)),
		m_draw_linear_curves(k3d::init_name("draw_linear_curves") + k3d::init_description("Draw Linear Curves [boolean]") + k3d::init_value(true) + k3d::init_document(Document)),
		m_draw_cubic_curves(k3d::init_name("draw_cubic_curves") + k3d::init_description("Draw Cubic Curves [boolean]") + k3d::init_value(true) + k3d::init_document(Document)),
		m_draw_nucurves(k3d::init_name("draw_nucurves") + k3d::init_description("Draw NURBS Curves [boolean]") + k3d::init_value(true) + k3d::init_document(Document)),
		m_draw_bilinear_patches(k3d::init_name("draw_bilinear_patches") + k3d::init_description("Draw Bilinear Patches [boolean]") + k3d::init_value(true) + k3d::init_document(Document)),
		m_draw_bicubic_patches(k3d::init_name("draw_bicubic_patches") + k3d::init_description("Draw Bicubic Patches [boolean]") + k3d::init_value(true) + k3d::init_document(Document)),
		m_draw_nupatches(k3d::init_name("draw_nupatches") + k3d::init_description("Draw NURBS Patches [boolean]") + k3d::init_value(true) + k3d::init_document(Document)),
		m_draw_blobbies(k3d::init_name("draw_blobbies") + k3d::init_description("Draw Blobbies [boolean]") + k3d::init_value(true) + k3d::init_document(Document)),
		m_draw_face_orientations(k3d::init_name("draw_face_orientations") + k3d::init_description("Draw Face Orientations [boolean]") + k3d::init_value(false) + k3d::init_document(Document)),
		m_draw_two_sided(k3d::init_name("draw_two_sided") + k3d::init_description("Draw Two Sided [boolean]") + k3d::init_value(false) + k3d::init_document(Document)),
		m_draw_safe_zone(k3d::init_name("draw_safe_zone") + k3d::init_description("Draw Safe Zone [boolean]") + k3d::init_value(false) + k3d::init_document(Document)),
		m_host(k3d::init_name("viewport_host") + k3d::init_description("Viewport Host [object]") + k3d::init_object_value(0) + k3d::init_document(Document)),
		m_perspective_projection(m_left, m_right, m_top, m_bottom, m_near, m_far),
		m_orthographic_projection(m_left, m_right, m_top, m_bottom, m_near, m_far)
	{
		enable_serialization(k3d::persistence::proxy(m_point_size));
		enable_serialization(k3d::persistence::proxy(m_background_color));
		enable_serialization(k3d::persistence::proxy(m_fog));
		enable_serialization(k3d::persistence::proxy(m_fog_near));
		enable_serialization(k3d::persistence::proxy(m_fog_far));
		enable_serialization(k3d::persistence::proxy(m_orthographic));
		enable_serialization(k3d::persistence::proxy(m_left));
		enable_serialization(k3d::persistence::proxy(m_right));
		enable_serialization(k3d::persistence::proxy(m_top));
		enable_serialization(k3d::persistence::proxy(m_bottom));
		enable_serialization(k3d::persistence::proxy(m_near));
		enable_serialization(k3d::persistence::proxy(m_far));
		enable_serialization(k3d::persistence::proxy(m_headlight));
		enable_serialization(k3d::persistence::proxy(m_show_lights));
		enable_serialization(k3d::persistence::proxy(m_draw_points));
		enable_serialization(k3d::persistence::proxy(m_draw_edges));
		enable_serialization(k3d::persistence::proxy(m_draw_faces));
		enable_serialization(k3d::persistence::proxy(m_draw_linear_curves));
		enable_serialization(k3d::persistence::proxy(m_draw_cubic_curves));
		enable_serialization(k3d::persistence::proxy(m_draw_nucurves));
		enable_serialization(k3d::persistence::proxy(m_draw_bilinear_patches));
		enable_serialization(k3d::persistence::proxy(m_draw_bicubic_patches));
		enable_serialization(k3d::persistence::proxy(m_draw_nupatches));
		enable_serialization(k3d::persistence::proxy(m_draw_blobbies));
		enable_serialization(k3d::persistence::proxy(m_draw_face_orientations));
		enable_serialization(k3d::persistence::proxy(m_draw_two_sided));
		enable_serialization(k3d::persistence::proxy(m_draw_safe_zone));
		enable_serialization(k3d::persistence::object_proxy(m_host));

		register_property(m_point_size);
		register_property(m_background_color);
		register_property(m_fog);
		register_property(m_fog_near);
		register_property(m_fog_far);
		register_property(m_orthographic);
		register_property(m_left);
		register_property(m_right);
		register_property(m_top);
		register_property(m_bottom);
		register_property(m_near);
		register_property(m_far);
		register_property(m_headlight);
		register_property(m_show_lights);
		register_property(m_draw_points);
		register_property(m_draw_edges);
		register_property(m_draw_faces);
		register_property(m_draw_linear_curves);
		register_property(m_draw_cubic_curves);
		register_property(m_draw_nucurves);
		register_property(m_draw_bilinear_patches);
		register_property(m_draw_bicubic_patches);
		register_property(m_draw_nupatches);
		register_property(m_draw_blobbies);
		register_property(m_draw_face_orientations);
		register_property(m_draw_two_sided);
		register_property(m_draw_safe_zone);
		register_property(m_host);

		k3d::iproperty_group_collection::group visibility_group("Visibility");
		visibility_group.properties.push_back(&static_cast<k3d::iproperty&>(m_draw_points));
		visibility_group.properties.push_back(&static_cast<k3d::iproperty&>(m_draw_edges));
		visibility_group.properties.push_back(&static_cast<k3d::iproperty&>(m_draw_faces));
		visibility_group.properties.push_back(&static_cast<k3d::iproperty&>(m_draw_linear_curves));
		visibility_group.properties.push_back(&static_cast<k3d::iproperty&>(m_draw_cubic_curves));
		visibility_group.properties.push_back(&static_cast<k3d::iproperty&>(m_draw_nucurves));
		visibility_group.properties.push_back(&static_cast<k3d::iproperty&>(m_draw_bilinear_patches));
		visibility_group.properties.push_back(&static_cast<k3d::iproperty&>(m_draw_bicubic_patches));
		visibility_group.properties.push_back(&static_cast<k3d::iproperty&>(m_draw_nupatches));
		visibility_group.properties.push_back(&static_cast<k3d::iproperty&>(m_draw_blobbies));
		visibility_group.properties.push_back(&static_cast<k3d::iproperty&>(m_draw_face_orientations));
		visibility_group.properties.push_back(&static_cast<k3d::iproperty&>(m_draw_two_sided));
		visibility_group.properties.push_back(&static_cast<k3d::iproperty&>(m_draw_safe_zone));

		k3d::iproperty_group_collection::group projection_group("Projection");
		projection_group.properties.push_back(&static_cast<k3d::iproperty&>(m_orthographic));
		projection_group.properties.push_back(&static_cast<k3d::iproperty&>(m_left));
		projection_group.properties.push_back(&static_cast<k3d::iproperty&>(m_right));
		projection_group.properties.push_back(&static_cast<k3d::iproperty&>(m_top));
		projection_group.properties.push_back(&static_cast<k3d::iproperty&>(m_bottom));
		projection_group.properties.push_back(&static_cast<k3d::iproperty&>(m_near));
		projection_group.properties.push_back(&static_cast<k3d::iproperty&>(m_far));

		register_property_group(visibility_group);
		register_property_group(projection_group);

		m_point_size.changed_signal().connect(SigC::bind(m_redraw_request_signal.slot(), k3d::iviewport::ASYNCHRONOUS));
		m_background_color.changed_signal().connect(SigC::bind(m_redraw_request_signal.slot(), k3d::iviewport::ASYNCHRONOUS));
		m_fog.changed_signal().connect(SigC::bind(m_redraw_request_signal.slot(), k3d::iviewport::ASYNCHRONOUS));
		m_fog_near.changed_signal().connect(SigC::bind(m_redraw_request_signal.slot(), k3d::iviewport::ASYNCHRONOUS));
		m_fog_far.changed_signal().connect(SigC::bind(m_redraw_request_signal.slot(), k3d::iviewport::ASYNCHRONOUS));
		m_orthographic.changed_signal().connect(SigC::bind(m_redraw_request_signal.slot(), k3d::iviewport::ASYNCHRONOUS));
		m_left.changed_signal().connect(SigC::bind(m_redraw_request_signal.slot(), k3d::iviewport::ASYNCHRONOUS));
		m_right.changed_signal().connect(SigC::bind(m_redraw_request_signal.slot(), k3d::iviewport::ASYNCHRONOUS));
		m_top.changed_signal().connect(SigC::bind(m_redraw_request_signal.slot(), k3d::iviewport::ASYNCHRONOUS));
		m_bottom.changed_signal().connect(SigC::bind(m_redraw_request_signal.slot(), k3d::iviewport::ASYNCHRONOUS));
		m_near.changed_signal().connect(SigC::bind(m_redraw_request_signal.slot(), k3d::iviewport::ASYNCHRONOUS));
		m_far.changed_signal().connect(SigC::bind(m_redraw_request_signal.slot(), k3d::iviewport::ASYNCHRONOUS));
		m_headlight.changed_signal().connect(SigC::bind(m_redraw_request_signal.slot(), k3d::iviewport::ASYNCHRONOUS));
		m_show_lights.changed_signal().connect(SigC::bind(m_redraw_request_signal.slot(), k3d::iviewport::ASYNCHRONOUS));
		m_draw_points.changed_signal().connect(SigC::bind(m_redraw_request_signal.slot(), k3d::iviewport::ASYNCHRONOUS));
		m_draw_edges.changed_signal().connect(SigC::bind(m_redraw_request_signal.slot(), k3d::iviewport::ASYNCHRONOUS));
		m_draw_faces.changed_signal().connect(SigC::bind(m_redraw_request_signal.slot(), k3d::iviewport::ASYNCHRONOUS));
		m_draw_linear_curves.changed_signal().connect(SigC::bind(m_redraw_request_signal.slot(), k3d::iviewport::ASYNCHRONOUS));
		m_draw_cubic_curves.changed_signal().connect(SigC::bind(m_redraw_request_signal.slot(), k3d::iviewport::ASYNCHRONOUS));
		m_draw_nucurves.changed_signal().connect(SigC::bind(m_redraw_request_signal.slot(), k3d::iviewport::ASYNCHRONOUS));
		m_draw_bilinear_patches.changed_signal().connect(SigC::bind(m_redraw_request_signal.slot(), k3d::iviewport::ASYNCHRONOUS));
		m_draw_bicubic_patches.changed_signal().connect(SigC::bind(m_redraw_request_signal.slot(), k3d::iviewport::ASYNCHRONOUS));
		m_draw_nupatches.changed_signal().connect(SigC::bind(m_redraw_request_signal.slot(), k3d::iviewport::ASYNCHRONOUS));
		m_draw_blobbies.changed_signal().connect(SigC::bind(m_redraw_request_signal.slot(), k3d::iviewport::ASYNCHRONOUS));
		m_draw_face_orientations.changed_signal().connect(SigC::bind(m_redraw_request_signal.slot(), k3d::iviewport::ASYNCHRONOUS));
		m_draw_two_sided.changed_signal().connect(SigC::bind(m_redraw_request_signal.slot(), k3d::iviewport::ASYNCHRONOUS));
		m_draw_safe_zone.changed_signal().connect(SigC::bind(m_redraw_request_signal.slot(), k3d::iviewport::ASYNCHRONOUS));
		m_host.changed_signal().connect(SigC::slot(*this, &viewport_implementation::on_host_changed));
	}

	~viewport_implementation()
	{
	}

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

	static k3d::iplugin_factory& get_factory()
	{
		static k3d::plugin_factory<k3d::document_plugin<viewport_implementation> > factory(
			k3d::classes::Viewport(),
			"Viewport",
			"Viewport",
			"Objects",
			k3d::iplugin_factory::STABLE);

		return factory;
	}

	void setup_projection()
	{
		k3d::iprojection& projection = this->projection();

		k3d::iperspective* const perspective = dynamic_cast<k3d::iperspective*>(&projection);
		if(perspective)
			{
				glFrustum(
					boost::any_cast<double>(k3d::get_property_value(document().dag(), perspective->left())),
					boost::any_cast<double>(k3d::get_property_value(document().dag(), perspective->right())),
					boost::any_cast<double>(k3d::get_property_value(document().dag(), perspective->bottom())),
					boost::any_cast<double>(k3d::get_property_value(document().dag(), perspective->top())),
					boost::any_cast<double>(k3d::get_property_value(document().dag(), perspective->near())),
					boost::any_cast<double>(k3d::get_property_value(document().dag(), perspective->far())));
				return;
			}

		k3d::iorthographic* const orthographic = dynamic_cast<k3d::iorthographic*>(&projection);
		if(orthographic)
			{
				glOrtho(
					boost::any_cast<double>(k3d::get_property_value(document().dag(), orthographic->left())),
					boost::any_cast<double>(k3d::get_property_value(document().dag(), orthographic->right())),
					boost::any_cast<double>(k3d::get_property_value(document().dag(), orthographic->bottom())),
					boost::any_cast<double>(k3d::get_property_value(document().dag(), orthographic->top())),
					boost::any_cast<double>(k3d::get_property_value(document().dag(), orthographic->near())),
					boost::any_cast<double>(k3d::get_property_value(document().dag(), orthographic->far())));
				return;
			}

		std::cerr << error << __PRETTY_FUNCTION__ << ": unknown projection type" << std::endl;
	}

	void constrain_screen_aspect_ratio(double& Ratio)
	{
		// If we're attached to a host, give it first shot; otherwise, let the ratio float ...
		if(m_host.interface())
			m_host.interface()->constrain_screen_aspect_ratio(Ratio);

		// Update our viewing frustum to match
		m_left.set_value(-0.5 * Ratio * std::abs(m_bottom.value() - m_top.value()));
		m_right.set_value(0.5 * Ratio * std::abs(m_bottom.value() - m_top.value()));
	}

	k3d::iprojection& projection()
	{
		k3d::iprojection* result = m_host.interface() ? m_host.interface()->projection() : 0;
		if(!result)
			{
				if(m_orthographic.property_value())
					result = &m_orthographic_projection;
				else
					result = &m_perspective_projection;
			}

		return *result;
	}

	void set_host(k3d::iviewport_host* Host)
	{
		m_host.set_object(dynamic_cast<k3d::iobject*>(Host));
	}

	k3d::iviewport_host* host()
	{
		return m_host.interface();
	}
	
	k3d::iviewport::host_changed_signal_t& host_changed_signal()
	{
		return m_host.changed_signal();
	}

	aspect_ratio_changed_signal_t& aspect_ratio_changed_signal()
	{
		return m_aspect_ratio_changed_signal;
	}

	void on_host_changed()
	{
		// Any time the host changes, reset our transformation matrix dependencies
		k3d::idag::dependencies_t dependencies;
		dependencies.insert(std::make_pair(&m_input_matrix.property(), m_host.interface() ? k3d::get_typed_property<k3d::matrix4>(*m_host.interface(), "output_matrix") : static_cast<k3d::iproperty*>(0)));
		document().dag().set_dependencies(dependencies);

		m_host_aspect_ratio_changed_connection.disconnect();

		if(m_host.interface())
			m_host_aspect_ratio_changed_connection = m_host.interface()->aspect_ratio_changed_signal().connect(m_aspect_ratio_changed_signal.slot());
	}

	void load_complete()
	{
		base::load_complete();

		if(m_host.interface())
			m_host_aspect_ratio_changed_connection = m_host.interface()->aspect_ratio_changed_signal().connect(m_aspect_ratio_changed_signal.slot());

		if(k3d::application().user_interface())
			k3d::application().user_interface()->show_viewport(*this);
	}

	void redraw(const unsigned long PixelWidth, const unsigned long PixelHeight, const unsigned long FontListBase)
	{
		// If width or height are zero, we're done ...
		if(!PixelWidth || !PixelHeight)
			return;

		// Clear background ...
		glClearDepth(1.0);
		const k3d::color background_color(m_background_color.property_value());
		glClearColor(background_color.red, background_color.green, background_color.blue, 0.0f);
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

		// Set point size ...
		glPointSize(m_point_size.property_value());

		// Setup stippling for path normals / bezier control lines ...
		glLineStipple(2, 0xaaaa);
		glDisable(GL_LINE_STIPPLE);
		glDisable(GL_POLYGON_STIPPLE);

		// Setup culling ...
		glFrontFace(GL_CW);
		glCullFace(GL_BACK);

		// Setup dithering ...
		glDisable(GL_DITHER);

		// Setup antialiasing ...
		glDisable(GL_LINE_SMOOTH);
		glDisable(GL_BLEND);
		glLineWidth(1.0f);

		// Disable stencil ...
		glDisable(GL_STENCIL_TEST);
		glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
		glStencilMask(0x00);

		// Set Z buffer options ...
		glDepthMask(GL_TRUE);
		glDepthFunc(GL_LESS);
		glEnable(GL_DEPTH_TEST);

		// Normalization ...
		glShadeModel(GL_SMOOTH);
		glEnable(GL_NORMALIZE);
		glEnable(GL_AUTO_NORMAL);

		// Prepare texture options ...
		glDisable(GL_TEXTURE_2D);

		// Setup texture matrix
		glMatrixMode(GL_TEXTURE);
		glLoadIdentity();

		// Cache render state to pass to clients ...
		k3d::viewport::render_state state;
		state.draw_points = m_draw_points.property_value();
		state.draw_edges = m_draw_edges.property_value();
		state.draw_faces = m_draw_faces.property_value();
		state.draw_linear_curves = m_draw_linear_curves.property_value();
		state.draw_cubic_curves = m_draw_cubic_curves.property_value();
		state.draw_nucurves = m_draw_nucurves.property_value();
		state.draw_bilinear_patches = m_draw_bilinear_patches.property_value();
		state.draw_bicubic_patches = m_draw_bicubic_patches.property_value();
		state.draw_nupatches = m_draw_nupatches.property_value();
		state.draw_blobbies = m_draw_blobbies.property_value();
		state.draw_face_orientations = m_draw_face_orientations.property_value();
		state.draw_two_sided = m_draw_two_sided.property_value();
		state.gl_ascii_font_list_base = FontListBase;

		// Setup viewport ...
		glViewport(0, 0, PixelWidth, PixelHeight);
		glGetIntegerv(GL_VIEWPORT, state.gl_viewport);

		// Setup projection ...
		glMatrixMode(GL_PROJECTION);
		glLoadIdentity();
		setup_projection();
		glGetFloatv(GL_PROJECTION_MATRIX, state.gl_projection_matrix);

		// Setup lights ...
		glEnable(GL_LIGHTING);
		glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE);
		glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, GL_TRUE);

		// Make sure all lights are turned off initially ...
		GLint maxlights = 0;
		glGetIntegerv(GL_MAX_LIGHTS, &maxlights);
		for(GLint i = 0; i < maxlights; ++i)
			glDisable(GLenum(GL_LIGHT0 + i));

		// Setup modelview matrix
		glMatrixMode(GL_MODELVIEW);
		glLoadIdentity();

		// Setup the headlight ...
		if(m_headlight.property_value())
			{
				// Enable the light ...
				glEnable(GL_LIGHT0);

				// Setup color ...
				const GLfloat color[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
				glLightfv(GL_LIGHT0, GL_AMBIENT, color);
				glLightfv(GL_LIGHT0, GL_DIFFUSE, color);
				glLightfv(GL_LIGHT0, GL_SPECULAR, color);

				// Setup light direction ...
				const GLfloat position[4] = { 0.0f, 0.0f, 1.0f, 0.0f };
				glLightfv(GL_LIGHT0, GL_POSITION, position);
			}

		const k3d::matrix4 transform_matrix(matrix());
		const k3d::angle_axis orientation(k3d::euler_angles(transform_matrix, k3d::euler_angles::ZXYstatic));
		const k3d::vector3 position(k3d::extractTranslation(transform_matrix));

		glScaled(1.0, 1.0, -1.0);
		glRotated(-k3d::degrees(orientation.angle), orientation.axis[0], orientation.axis[1], orientation.axis[2]);
		glTranslated(-position[0], -position[1], -position[2]);

		// Setup fog ...
		if(m_fog.property_value())
			{
				const k3d::color background_color(m_background_color.property_value());

				GLfloat fogdata[4];
				fogdata[0] = background_color.red;
				fogdata[1] = background_color.green;
				fogdata[2] = background_color.blue;
				fogdata[3] = 1.0f;

				glFogfv(GL_FOG_COLOR, fogdata);
				glFogf(GL_FOG_START, static_cast<GLfloat>(m_fog_near.property_value()));
				glFogf(GL_FOG_END, static_cast<GLfloat>(m_fog_far.property_value()));
				glHint(GL_FOG_HINT, GL_NICEST);
				glFogi(GL_FOG_MODE, GL_LINEAR);
				glEnable(GL_FOG);
			}
		else
			{
				glDisable(GL_FOG);
			}

		if(m_show_lights.property_value())
			std::for_each(document().objects().collection().begin(), document().objects().collection().end(), detail::light_setup());

		std::for_each(document().objects().collection().begin(), document().objects().collection().end(), detail::draw(state));

		// Optionally draw a safe zone for video ...
		if(m_draw_safe_zone.property_value())
			{
				glMatrixMode(GL_PROJECTION);
				glLoadIdentity();
				glOrtho(-1, 1, -1, 1, -1, 1);

				glMatrixMode(GL_MODELVIEW);
				glLoadIdentity();

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

				glBegin(GL_LINE_LOOP);
				glVertex2d(-0.8, 0.8);
				glVertex2d(0.8, 0.8);
				glVertex2d(0.8, -0.8);
				glVertex2d(-0.8, -0.8);
				glEnd();
			}

/* I really hate to lose this feedback, but the GLU NURBS routines generate large numbers of errors, which ruins its utility :-(
		for(GLenum gl_error = glGetError(); gl_error != GL_NO_ERROR; gl_error = glGetError())
			std::cerr << error << "OpenGL error: " << reinterpret_cast<const char*>(gluErrorString(gl_error)) << std::endl;
*/
	}

	redraw_request_signal_t& redraw_request_signal()
	{
		return m_redraw_request_signal;
	}

	void select(const unsigned long PixelWidth, const unsigned long PixelHeight, const unsigned long FontListBase, const k3d::rectangle& Region)
	{
		// If width or height are zero, we're done ...
		if(!PixelWidth || !PixelHeight)
			return;

		// Setup culling ...
		glFrontFace(GL_CW);
		glCullFace(GL_BACK);

		// Setup dithering ...
		glDisable(GL_DITHER);

		// Setup antialiasing ...
		glDisable(GL_LINE_SMOOTH);
		glDisable(GL_BLEND);
		glLineWidth(1.0f);

		// Disable stencil ...
		glDisable(GL_STENCIL_TEST);
		glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
		glStencilMask(0x00);

		// Set Z buffer options ...
		glDepthMask(GL_TRUE);
		glDepthFunc(GL_LESS);
		glEnable(GL_DEPTH_TEST);

		// Normalization ...
		glShadeModel(GL_FLAT);
		glDisable(GL_NORMALIZE);
		glDisable(GL_AUTO_NORMAL);

		glDisable(GL_TEXTURE_2D);

		// Cache render state to pass to clients ...
		k3d::viewport::render_state state;
		state.draw_points = m_draw_points.property_value();
		state.draw_edges = m_draw_edges.property_value();
		state.draw_faces = m_draw_faces.property_value();
		state.draw_linear_curves = m_draw_linear_curves.property_value();
		state.draw_cubic_curves = m_draw_cubic_curves.property_value();
		state.draw_nucurves = m_draw_nucurves.property_value();
		state.draw_bilinear_patches = m_draw_bilinear_patches.property_value();
		state.draw_bicubic_patches = m_draw_bicubic_patches.property_value();
		state.draw_nupatches = m_draw_nupatches.property_value();
		state.draw_blobbies = m_draw_blobbies.property_value();
		state.draw_face_orientations = m_draw_face_orientations.property_value();
		state.draw_two_sided = m_draw_two_sided.property_value();
		state.gl_ascii_font_list_base = FontListBase;

		// Setup viewport ...
		glViewport(0, 0, PixelWidth, PixelHeight);
		glGetIntegerv(GL_VIEWPORT, state.gl_viewport);

		// First, setup the projection matrix as we would normally do for drawing (so we can store the projection matrix, unaffected by the pick matrix) ...
		glMatrixMode(GL_PROJECTION);
		glLoadIdentity();
		setup_projection();
		glGetFloatv(GL_PROJECTION_MATRIX, state.gl_projection_matrix);

		// Second, setup the projection matrix with the pick matrix ...
		glLoadIdentity();

		const double width  = Region.Width();
		const double height = Region.Height();
		gluPickMatrix(Region.Left() + (width * 0.5), state.gl_viewport[3] - (Region.Top() + (height * 0.5)), width, height, state.gl_viewport);
		setup_projection();

		// Setup modelview matrix
		glMatrixMode(GL_MODELVIEW);
		glLoadIdentity();

		const k3d::matrix4 transform_matrix(matrix());
		const k3d::angle_axis orientation(k3d::euler_angles(transform_matrix, k3d::euler_angles::ZXYstatic));
		const k3d::vector3 position(k3d::extractTranslation(transform_matrix));

		glScaled(1.0, 1.0, -1.0);
		glRotated(-k3d::degrees(orientation.angle), orientation.axis[0], orientation.axis[1], orientation.axis[2]);
		glTranslated(-position[0], -position[1], -position[2]);

		// Clear background ...
		glClear(GL_DEPTH_BUFFER_BIT);

		glDisable(GL_LIGHTING);

		std::for_each(document().objects().collection().begin(), document().objects().collection().end(), detail::draw_selection(state));

/* I really hate to lose this feedback, but the glu NURBS routines generate large numbers of errors, which ruins its utility :-(
		for(GLenum gl_error = glGetError(); gl_error != GL_NO_ERROR; gl_error = glGetError())
			std::cerr << error << "OpenGL error: " << reinterpret_cast<const char*>(gluErrorString(gl_error)) << std::endl;
*/
	}

private:
	redraw_request_signal_t m_redraw_request_signal;

	k3d_measurement_property(unsigned long, k3d::immutable_name, k3d::change_signal, k3d::no_undo, k3d::local_storage, k3d::no_constraint) m_point_size;
	k3d_data_property(k3d::color, k3d::immutable_name, k3d::change_signal, k3d::no_undo, k3d::local_storage, k3d::no_constraint) m_background_color;
	k3d_data_property(bool, k3d::immutable_name, k3d::change_signal, k3d::no_undo, k3d::local_storage, k3d::no_constraint) m_fog;
	k3d_measurement_property(double, k3d::immutable_name, k3d::change_signal, k3d::no_undo, k3d::local_storage, k3d::with_constraint) m_fog_near;
	k3d_measurement_property(double, k3d::immutable_name, k3d::change_signal, k3d::no_undo, k3d::local_storage, k3d::with_constraint) m_fog_far;
	k3d_data_property(bool, k3d::immutable_name, k3d::change_signal, k3d::no_undo, k3d::local_storage, k3d::no_constraint) m_orthographic;
	k3d_measurement_property(double, k3d::immutable_name, k3d::change_signal, k3d::no_undo, k3d::local_storage, k3d::no_constraint) m_left;
	k3d_measurement_property(double, k3d::immutable_name, k3d::change_signal, k3d::no_undo, k3d::local_storage, k3d::no_constraint) m_right;
	k3d_measurement_property(double, k3d::immutable_name, k3d::change_signal, k3d::no_undo, k3d::local_storage, k3d::no_constraint) m_top;
	k3d_measurement_property(double, k3d::immutable_name, k3d::change_signal, k3d::no_undo, k3d::local_storage, k3d::no_constraint) m_bottom;
	k3d_measurement_property(double, k3d::immutable_name, k3d::change_signal, k3d::no_undo, k3d::local_storage, k3d::with_constraint) m_near;
	k3d_measurement_property(double, k3d::immutable_name, k3d::change_signal, k3d::no_undo, k3d::local_storage, k3d::with_constraint) m_far;
	k3d_data_property(bool, k3d::immutable_name, k3d::change_signal, k3d::no_undo, k3d::local_storage, k3d::no_constraint) m_headlight;
	k3d_data_property(bool, k3d::immutable_name, k3d::change_signal, k3d::no_undo, k3d::local_storage, k3d::no_constraint) m_show_lights;
	k3d_data_property(bool, k3d::immutable_name, k3d::change_signal, k3d::no_undo, k3d::local_storage, k3d::no_constraint) m_draw_points;
	k3d_data_property(bool, k3d::immutable_name, k3d::change_signal, k3d::no_undo, k3d::local_storage, k3d::no_constraint) m_draw_edges;
	k3d_data_property(bool, k3d::immutable_name, k3d::change_signal, k3d::no_undo, k3d::local_storage, k3d::no_constraint) m_draw_faces;
	k3d_data_property(bool, k3d::immutable_name, k3d::change_signal, k3d::no_undo, k3d::local_storage, k3d::no_constraint) m_draw_linear_curves;
	k3d_data_property(bool, k3d::immutable_name, k3d::change_signal, k3d::no_undo, k3d::local_storage, k3d::no_constraint) m_draw_cubic_curves;
	k3d_data_property(bool, k3d::immutable_name, k3d::change_signal, k3d::no_undo, k3d::local_storage, k3d::no_constraint) m_draw_nucurves;
	k3d_data_property(bool, k3d::immutable_name, k3d::change_signal, k3d::no_undo, k3d::local_storage, k3d::no_constraint) m_draw_bilinear_patches;
	k3d_data_property(bool, k3d::immutable_name, k3d::change_signal, k3d::no_undo, k3d::local_storage, k3d::no_constraint) m_draw_bicubic_patches;
	k3d_data_property(bool, k3d::immutable_name, k3d::change_signal, k3d::no_undo, k3d::local_storage, k3d::no_constraint) m_draw_nupatches;
	k3d_data_property(bool, k3d::immutable_name, k3d::change_signal, k3d::no_undo, k3d::local_storage, k3d::no_constraint) m_draw_blobbies;
	k3d_data_property(bool, k3d::immutable_name, k3d::change_signal, k3d::no_undo, k3d::local_storage, k3d::no_constraint) m_draw_face_orientations;
	k3d_data_property(bool, k3d::immutable_name, k3d::change_signal, k3d::no_undo, k3d::local_storage, k3d::no_constraint) m_draw_two_sided;
	k3d_data_property(bool, k3d::immutable_name, k3d::change_signal, k3d::no_undo, k3d::local_storage, k3d::no_constraint) m_draw_safe_zone;

	k3d_object_property(k3d::iviewport_host, k3d::immutable_name, k3d::with_undo, k3d::local_storage) m_host;

	class perspective_projection :
		public k3d::iperspective
	{
	public:
		perspective_projection(k3d::iproperty& Left, k3d::iproperty& Right, k3d::iproperty& Top, k3d::iproperty& Bottom, k3d::iproperty& Near, k3d::iproperty& Far) :
			m_left(Left),
			m_right(Right),
			m_top(Top),
			m_bottom(Bottom),
			m_near(Near),
			m_far(Far)
		{
		}

		k3d::iproperty& left()
		{
			return m_left;
		}

		k3d::iproperty& right()
		{
			return m_right;
		}

		k3d::iproperty& top()
		{
			return m_top;
		}

		k3d::iproperty& bottom()
		{
			return m_bottom;
		}

		k3d::iproperty& near()
		{
			return m_near;
		}

		k3d::iproperty& far()
		{
			return m_far;
		}

	private:
		k3d::iproperty& m_left;
		k3d::iproperty& m_right;
		k3d::iproperty& m_top;
		k3d::iproperty& m_bottom;
		k3d::iproperty& m_near;
		k3d::iproperty& m_far;
	};

	class orthographic_projection :
		public k3d::iorthographic
	{
	public:
		orthographic_projection(k3d::iproperty& Left, k3d::iproperty& Right, k3d::iproperty& Top, k3d::iproperty& Bottom, k3d::iproperty& Near, k3d::iproperty& Far) :
			m_left(Left),
			m_right(Right),
			m_top(Top),
			m_bottom(Bottom),
			m_near(Near),
			m_far(Far)
		{
		}

		k3d::iproperty& left()
		{
			return m_left;
		}

		k3d::iproperty& right()
		{
			return m_right;
		}

		k3d::iproperty& top()
		{
			return m_top;
		}

		k3d::iproperty& bottom()
		{
			return m_bottom;
		}

		k3d::iproperty& near()
		{
			return m_near;
		}

		k3d::iproperty& far()
		{
			return m_far;
		}

	private:
		k3d::iproperty& m_left;
		k3d::iproperty& m_right;
		k3d::iproperty& m_top;
		k3d::iproperty& m_bottom;
		k3d::iproperty& m_near;
		k3d::iproperty& m_far;
	};

	perspective_projection m_perspective_projection;
	orthographic_projection m_orthographic_projection;

	SigC::Connection m_host_aspect_ratio_changed_connection;

	aspect_ratio_changed_signal_t m_aspect_ratio_changed_signal;
};

/////////////////////////////////////////////////////////////////////////////
// viewport_factory

k3d::iplugin_factory& viewport_factory()
{
	return viewport_implementation::get_factory();
}

} // namespace libk3dviewport

K3D_MODULE_START(k3d::uuid(0xae701c78, 0x095243c1, 0xa3739654, 0xe43c80db), Registry)
	Registry.register_factory(libk3dviewport::viewport_factory());
K3D_MODULE_END
