// 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/ianimation_render_engine.h>
#include <k3dsdk/ioptions.h>
#include <k3dsdk/iprojection.h>
#include <k3dsdk/irender_farm.h>
#include <k3dsdk/istill_render_engine.h>
#include <k3dsdk/iviewport_host.h>
#include <k3dsdk/measurement.h>
#include <k3dsdk/module.h>
#include <k3dsdk/object.h>
#include <k3dsdk/persistence.h>
#include <k3dsdk/property.h>
#include <k3dsdk/property_group_collection.h>
#include <k3dsdk/renderman.h>
#include <k3dsdk/shaders.h>
#include <k3dsdk/string_modifiers.h>
#include <k3dsdk/time_source.h>
#include <k3dsdk/transformable.h>
#include <k3dsdk/viewport.h>

#include <boost/filesystem/fstream.hpp>

#include <iomanip>

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

namespace libk3drenderman
{

namespace detail
{

class pre_render
{
public:
	explicit pre_render(const k3d::ri::render_state& State) :
		m_state(State)
	{
	}

	void operator()(k3d::iobject* const Object)
	{
		k3d::ri::irenderable* const renderable = dynamic_cast<k3d::ri::irenderable*>(Object);
		if(renderable)
			renderable->renderman_pre_render(m_state);
	}

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

class setup_light
{
public:
	explicit setup_light(const k3d::ri::render_state& State) :
		m_state(State)
	{
	}

	void operator()(k3d::iobject* const Object)
	{
		k3d::ri::ilight* const light = dynamic_cast<k3d::ri::ilight*>(Object);
		if(light)
			light->setup_renderman_light(m_state);
	}

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

class setup_texture
{
public:
	explicit setup_texture(k3d::irender_frame& Frame, k3d::ri::irender_engine& Engine) :
		m_frame(Frame),
		m_engine(Engine)
	{
	}

	void operator()(k3d::iobject* const Object)
	{
		k3d::ri::itexture* const texture = dynamic_cast<k3d::ri::itexture*>(Object);
		if(texture)
			texture->setup_renderman_texture(m_frame, m_engine);
	}

private:
	k3d::irender_frame& m_frame;
	k3d::ri::irender_engine& m_engine;
};

class render
{
public:
	explicit render(const k3d::ri::render_state& State) :
		m_state(State)
	{
	}

	void operator()(k3d::iobject* const Object)
	{
		k3d::ri::irenderable* const renderable = dynamic_cast<k3d::ri::irenderable*>(Object);
		if(renderable)
			renderable->renderman_render(m_state);
	}

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

const unsigned long default_pixel_width = 320;
const unsigned long default_pixel_height = 240;
const double default_pixel_aspect_ratio = 1.0;
const double default_screen_aspect_ratio = default_pixel_aspect_ratio * static_cast<double>(default_pixel_width) / static_cast<double>(default_pixel_height);

std::string default_renderman_render_engine()
{
	std::string type;
	std::string engine;
	std::string name;
	
	k3d::application().options().default_render_engine(type, engine, name);
	if(type == "ri")
		return engine;
		
	return "aqsis";
}

} // namespace detail

/////////////////////////////////////////////////////////////////////////////
// render_engine

class render_engine :
	public k3d::viewport::drawable<k3d::transformable<k3d::persistent<k3d::object> > >,
	public k3d::ianimation_render_engine,
	public k3d::istill_render_engine,
	public k3d::iviewport_host,
	public k3d::property_group_collection
{
	typedef k3d::viewport::drawable<k3d::transformable<k3d::persistent<k3d::object> > > base;

public:
	render_engine(k3d::idocument& Document) :
		base(Document),
		m_render_engine(k3d::init_name("render_engine") + k3d::init_description("Render Engine [string]") + k3d::init_value(detail::default_renderman_render_engine()) + k3d::init_document(Document) + k3d::init_values(render_engine_values())),
		m_pixel_width(k3d::init_name("pixel_width") + k3d::init_description("Output pixel width [positive integer]") + k3d::init_value(detail::default_pixel_width) + k3d::init_document(Document) + k3d::init_constraint(k3d::constraint::minimum(1UL)) + k3d::init_precision(0) + k3d::init_step_increment(1) + k3d::init_units(typeid(k3d::measurement::scalar))),
		m_pixel_height(k3d::init_name("pixel_height") + k3d::init_description("Output pixel height [positive integer]") + k3d::init_value(detail::default_pixel_height) + k3d::init_document(document()) + k3d::init_constraint(k3d::constraint::minimum(1UL)) + k3d::init_precision(0) + k3d::init_step_increment(1) + k3d::init_units(typeid(k3d::measurement::scalar))),
		m_pixel_aspect_ratio(k3d::init_name("pixel_aspect_ratio") + k3d::init_description("Output pixel aspect ratio [positive real]") + k3d::init_value(detail::default_pixel_aspect_ratio) + k3d::init_document(document()) + k3d::init_constraint(k3d::constraint::minimum(std::numeric_limits<double>::epsilon())) + k3d::init_precision(2) + k3d::init_step_increment(0.01) + k3d::init_units(typeid(k3d::measurement::scalar))),
		m_default_atmosphere_shader(k3d::init_name("default_atmosphere_shader") + k3d::init_description("Default atmosphere shader [object]") + k3d::init_object_value(0) + k3d::init_document(Document)),
		m_default_interior_shader(k3d::init_name("default_interior_shader") + k3d::init_description("Default interior shader [object]") + k3d::init_object_value(0) + k3d::init_document(Document)),
		m_default_exterior_shader(k3d::init_name("default_exterior_shader") + k3d::init_description("Default exterior shader [object]") + k3d::init_object_value(0) + k3d::init_document(Document)),
		m_imager_shader(k3d::init_name("imager_shader") + k3d::init_description("Imager shader [object]") + k3d::init_object_value(0) + k3d::init_document(Document)),
		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 * detail::default_screen_aspect_ratio) + 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 * detail::default_screen_aspect_ratio) + 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_bucket_width(k3d::init_name("bucket_width") + k3d::init_description("Bucket Width [positive integer]") + k3d::init_value(16) + k3d::init_document(Document) + k3d::init_constraint(k3d::constraint::minimum(1UL)) + k3d::init_precision(0) + k3d::init_step_increment(1) + k3d::init_units(typeid(k3d::measurement::scalar))),
		m_bucket_height(k3d::init_name("bucket_height") + k3d::init_description("Bucket Height [positive integer]") + k3d::init_value(16) + k3d::init_document(Document) + k3d::init_constraint(k3d::constraint::minimum(1UL)) + k3d::init_precision(0) + k3d::init_step_increment(1) + k3d::init_units(typeid(k3d::measurement::scalar))),
		m_grid_size(k3d::init_name("grid_size") + k3d::init_description("Grid Size [positive integer]") + k3d::init_value(256) + k3d::init_document(Document) + k3d::init_constraint(k3d::constraint::minimum(1UL)) + k3d::init_precision(0) + k3d::init_step_increment(1) + k3d::init_units(typeid(k3d::measurement::scalar))),
		m_eye_splits(k3d::init_name("eye_splits") + k3d::init_description("Eye Splits [positive integer]") + k3d::init_value(10) + k3d::init_document(Document) + k3d::init_constraint(k3d::constraint::minimum(1UL)) + k3d::init_precision(0) + k3d::init_step_increment(1) + k3d::init_units(typeid(k3d::measurement::scalar))),
		m_texture_memory(k3d::init_name("texture_memory") + k3d::init_description("Texture Memory [positive integer]") + k3d::init_value(1024) + k3d::init_document(Document) + k3d::init_constraint(k3d::constraint::minimum(1UL)) + k3d::init_precision(0) + k3d::init_step_increment(1) + k3d::init_units(typeid(k3d::measurement::scalar))),
		m_render_alpha(k3d::init_name("render_alpha") + k3d::init_description("Render Alpha [boolean]") + k3d::init_value(true) + k3d::init_document(Document)),
		m_pixel_xsamples(k3d::init_name("pixel_xsamples") + k3d::init_description("Pixel X Samples [positive real]") + k3d::init_value(1.0) + k3d::init_document(document()) + k3d::init_constraint(k3d::constraint::minimum(std::numeric_limits<double>::epsilon())) + k3d::init_precision(2) + k3d::init_step_increment(1.0) + k3d::init_units(typeid(k3d::measurement::scalar))),
		m_pixel_ysamples(k3d::init_name("pixel_ysamples") + k3d::init_description("Pixel Y Samples [positive real]") + k3d::init_value(1.0) + k3d::init_document(document()) + k3d::init_constraint(k3d::constraint::minimum(std::numeric_limits<double>::epsilon())) + k3d::init_precision(2) + k3d::init_step_increment(1.0) + k3d::init_units(typeid(k3d::measurement::scalar))),
		m_pixel_filter(k3d::init_name("pixel_filter") + k3d::init_description("Pixel Filter [string]") + k3d::init_value(k3d::ri::RI_GAUSSIAN()) + k3d::init_document(document()) + k3d::init_values(pixel_filter_values())),
		m_pixel_filter_width(k3d::init_name("pixel_filter_width") + k3d::init_description("Pixel Filter Width [positive real]") + k3d::init_value(2.0) + k3d::init_document(document()) + k3d::init_constraint(k3d::constraint::minimum(std::numeric_limits<double>::epsilon())) + k3d::init_precision(2) + k3d::init_step_increment(1.0) + k3d::init_units(typeid(k3d::measurement::scalar))),
		m_pixel_filter_height(k3d::init_name("pixel_filter_height") + k3d::init_description("Pixel Filter Height [positive real]") + k3d::init_value(2.0) + k3d::init_document(document()) + k3d::init_constraint(k3d::constraint::minimum(std::numeric_limits<double>::epsilon())) + k3d::init_precision(2) + k3d::init_step_increment(1.0) + k3d::init_units(typeid(k3d::measurement::scalar))),
		m_exposure(k3d::init_name("exposure") + k3d::init_description("Exposure [real]") + k3d::init_value(1.0) + k3d::init_document(Document) + k3d::init_precision(2) + k3d::init_step_increment(0.01) + k3d::init_units(typeid(k3d::measurement::scalar))),
		m_gamma(k3d::init_name("gamma") + k3d::init_description("Gamma [real]") + k3d::init_value(1.0) + k3d::init_document(Document) + k3d::init_precision(2) + k3d::init_step_increment(0.01) + k3d::init_units(typeid(k3d::measurement::scalar))),
		m_dof(k3d::init_name("dof") + k3d::init_description("Depth of Field [boolean]") + k3d::init_value(false) + k3d::init_document(Document)),
		m_fstop(k3d::init_name("fstop") + k3d::init_description("f-stop [real]") + k3d::init_value(0.3) + k3d::init_document(Document) + k3d::init_precision(2) + k3d::init_step_increment(0.01) + k3d::init_units(typeid(k3d::measurement::scalar))),
		m_focal_length(k3d::init_name("focal_length") + k3d::init_description("Focal Length [distance]") + k3d::init_value(1.6) + k3d::init_document(Document) + k3d::init_precision(2) + k3d::init_step_increment(0.01) + k3d::init_units(typeid(k3d::measurement::distance))),
		m_focus_plane(k3d::init_name("focus_plane") + k3d::init_description("Focus Plane [distance]") + k3d::init_value(30.0) + k3d::init_document(Document) + k3d::init_precision(2) + k3d::init_step_increment(0.01) + k3d::init_units(typeid(k3d::measurement::distance))),
		m_shading_rate(k3d::init_name("shading_rate") + k3d::init_description("Shading Rate [real]") + k3d::init_value(1.0) + k3d::init_document(Document) + k3d::init_precision(2) + k3d::init_step_increment(0.1) + k3d::init_units(typeid(k3d::measurement::scalar))),
		m_shading_interpolation(k3d::init_name("shading_interpolation") + k3d::init_description("Shading Interpolation [string]") + k3d::init_value(k3d::ri::RI_CONSTANT()) + k3d::init_document(Document) + k3d::init_values(shading_interpolation_values())),
		m_two_sided(k3d::init_name("two_sided") + k3d::init_description("Two Sided [boolean]") + k3d::init_value(true) + k3d::init_document(Document)),
		m_crop_window_left(k3d::init_name("crop_window_left") + k3d::init_description("Crop Window Left [real]") + k3d::init_value(0.0) + k3d::init_document(Document) + k3d::init_precision(2) + k3d::init_step_increment(0.01) + k3d::init_units(typeid(k3d::measurement::scalar))),
		m_crop_window_right(k3d::init_name("crop_window_right") + k3d::init_description("Crop Window Right [real]") + k3d::init_value(1.0) + k3d::init_document(Document) + k3d::init_precision(2) + k3d::init_step_increment(0.01) + k3d::init_units(typeid(k3d::measurement::scalar))),
		m_crop_window_top(k3d::init_name("crop_window_top") + k3d::init_description("Crop Window Top [real]") + k3d::init_value(0.0) + k3d::init_document(Document) + k3d::init_precision(2) + k3d::init_step_increment(0.01) + k3d::init_units(typeid(k3d::measurement::scalar))),
		m_crop_window_bottom(k3d::init_name("crop_window_bottom") + k3d::init_description("Crop Window Bottom [real]") + k3d::init_value(1.0) + k3d::init_document(Document) + k3d::init_precision(2) + k3d::init_step_increment(0.01) + k3d::init_units(typeid(k3d::measurement::scalar))),
		m_motion_blur(k3d::init_name("motion_blur") + k3d::init_description("Motion Blur [boolean]") + k3d::init_value(false) + k3d::init_document(Document)),
		m_render_motion_blur(k3d::init_name("render_motion_blur") + k3d::init_description("Render Motion Blur [boolean]") + k3d::init_value(false) + 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_render_engine));
		enable_serialization(k3d::persistence::proxy(m_pixel_width));
		enable_serialization(k3d::persistence::proxy(m_pixel_height));
		enable_serialization(k3d::persistence::proxy(m_pixel_aspect_ratio));
		enable_serialization(k3d::persistence::object_proxy(m_default_atmosphere_shader));
		enable_serialization(k3d::persistence::object_proxy(m_default_interior_shader));
		enable_serialization(k3d::persistence::object_proxy(m_default_exterior_shader));
		enable_serialization(k3d::persistence::object_proxy(m_imager_shader));
		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_bucket_width));
		enable_serialization(k3d::persistence::proxy(m_bucket_height));
		enable_serialization(k3d::persistence::proxy(m_grid_size));
		enable_serialization(k3d::persistence::proxy(m_eye_splits));
		enable_serialization(k3d::persistence::proxy(m_texture_memory));
		enable_serialization(k3d::persistence::proxy(m_render_alpha));
		enable_serialization(k3d::persistence::proxy(m_pixel_xsamples));
		enable_serialization(k3d::persistence::proxy(m_pixel_ysamples));
		enable_serialization(k3d::persistence::proxy(m_pixel_filter));
		enable_serialization(k3d::persistence::proxy(m_pixel_filter_width));
		enable_serialization(k3d::persistence::proxy(m_pixel_filter_height));
		enable_serialization(k3d::persistence::proxy(m_exposure));
		enable_serialization(k3d::persistence::proxy(m_gamma));
		enable_serialization(k3d::persistence::proxy(m_dof));
		enable_serialization(k3d::persistence::proxy(m_fstop));
		enable_serialization(k3d::persistence::proxy(m_focal_length));
		enable_serialization(k3d::persistence::proxy(m_focus_plane));
		enable_serialization(k3d::persistence::proxy(m_shading_rate));
		enable_serialization(k3d::persistence::proxy(m_shading_interpolation));
		enable_serialization(k3d::persistence::proxy(m_two_sided));
		enable_serialization(k3d::persistence::proxy(m_crop_window_left));
		enable_serialization(k3d::persistence::proxy(m_crop_window_right));
		enable_serialization(k3d::persistence::proxy(m_crop_window_top));
		enable_serialization(k3d::persistence::proxy(m_crop_window_bottom));
		enable_serialization(k3d::persistence::proxy(m_motion_blur));
		enable_serialization(k3d::persistence::proxy(m_render_motion_blur));

		register_property(m_render_engine);
		register_property(m_pixel_width);
		register_property(m_pixel_height);
		register_property(m_pixel_aspect_ratio);
		register_property(m_default_atmosphere_shader);
		register_property(m_default_interior_shader);
		register_property(m_default_exterior_shader);
		register_property(m_imager_shader);
		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_bucket_width);
		register_property(m_bucket_height);
		register_property(m_grid_size);
		register_property(m_eye_splits);
		register_property(m_texture_memory);
		register_property(m_render_alpha);
		register_property(m_pixel_xsamples);
		register_property(m_pixel_ysamples);
		register_property(m_pixel_filter);
		register_property(m_pixel_filter_width);
		register_property(m_pixel_filter_height);
		register_property(m_exposure);
		register_property(m_gamma);
		register_property(m_dof);
		register_property(m_fstop);
		register_property(m_focal_length);
		register_property(m_focus_plane);
		register_property(m_shading_rate);
		register_property(m_shading_interpolation);
		register_property(m_two_sided);
		register_property(m_crop_window_left);
		register_property(m_crop_window_right);
		register_property(m_crop_window_top);
		register_property(m_crop_window_bottom);
		register_property(m_motion_blur);
		register_property(m_render_motion_blur);

		k3d::iproperty_group_collection::group output_group("Output");
		output_group.properties.push_back(&static_cast<k3d::iproperty&>(m_render_engine));
		output_group.properties.push_back(&static_cast<k3d::iproperty&>(m_pixel_width));
		output_group.properties.push_back(&static_cast<k3d::iproperty&>(m_pixel_height));
		output_group.properties.push_back(&static_cast<k3d::iproperty&>(m_pixel_aspect_ratio));
		output_group.properties.push_back(&static_cast<k3d::iproperty&>(m_default_atmosphere_shader));
		output_group.properties.push_back(&static_cast<k3d::iproperty&>(m_default_interior_shader));
		output_group.properties.push_back(&static_cast<k3d::iproperty&>(m_default_exterior_shader));
		output_group.properties.push_back(&static_cast<k3d::iproperty&>(m_imager_shader));
		output_group.properties.push_back(&static_cast<k3d::iproperty&>(m_render_alpha));
		output_group.properties.push_back(&static_cast<k3d::iproperty&>(m_crop_window_left));
		output_group.properties.push_back(&static_cast<k3d::iproperty&>(m_crop_window_right));
		output_group.properties.push_back(&static_cast<k3d::iproperty&>(m_crop_window_top));
		output_group.properties.push_back(&static_cast<k3d::iproperty&>(m_crop_window_bottom));
		
		k3d::iproperty_group_collection::group sampling_group("Sampling");
		sampling_group.properties.push_back(&static_cast<k3d::iproperty&>(m_bucket_width));
		sampling_group.properties.push_back(&static_cast<k3d::iproperty&>(m_bucket_height));
		sampling_group.properties.push_back(&static_cast<k3d::iproperty&>(m_grid_size));
		sampling_group.properties.push_back(&static_cast<k3d::iproperty&>(m_eye_splits));
		sampling_group.properties.push_back(&static_cast<k3d::iproperty&>(m_pixel_xsamples));
		sampling_group.properties.push_back(&static_cast<k3d::iproperty&>(m_pixel_ysamples));
		sampling_group.properties.push_back(&static_cast<k3d::iproperty&>(m_pixel_filter));
		sampling_group.properties.push_back(&static_cast<k3d::iproperty&>(m_pixel_filter_width));
		sampling_group.properties.push_back(&static_cast<k3d::iproperty&>(m_pixel_filter_height));
		sampling_group.properties.push_back(&static_cast<k3d::iproperty&>(m_shading_rate));
		sampling_group.properties.push_back(&static_cast<k3d::iproperty&>(m_shading_interpolation));
		sampling_group.properties.push_back(&static_cast<k3d::iproperty&>(m_render_motion_blur));

		k3d::iproperty_group_collection::group lens_group("Lens");
		lens_group.properties.push_back(&static_cast<k3d::iproperty&>(m_exposure));
		lens_group.properties.push_back(&static_cast<k3d::iproperty&>(m_gamma));
		lens_group.properties.push_back(&static_cast<k3d::iproperty&>(m_dof));
		lens_group.properties.push_back(&static_cast<k3d::iproperty&>(m_fstop));
		lens_group.properties.push_back(&static_cast<k3d::iproperty&>(m_focal_length));
		lens_group.properties.push_back(&static_cast<k3d::iproperty&>(m_focus_plane));
		
		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(output_group);
		register_property_group(sampling_group);
		register_property_group(lens_group);
		register_property_group(projection_group);

		m_orthographic.changed_signal().connect(SigC::slot(*this, &render_engine::async_redraw_all));
		m_left.changed_signal().connect(SigC::slot(*this, &render_engine::async_redraw_all));
		m_right.changed_signal().connect(SigC::slot(*this, &render_engine::async_redraw_all));
		m_top.changed_signal().connect(SigC::slot(*this, &render_engine::async_redraw_all));
		m_bottom.changed_signal().connect(SigC::slot(*this, &render_engine::async_redraw_all));
		m_near.changed_signal().connect(SigC::slot(*this, &render_engine::async_redraw_all));
		m_far.changed_signal().connect(SigC::slot(*this, &render_engine::async_redraw_all));
				
		m_input_matrix.changed_signal().connect(SigC::slot(*this, &render_engine::async_redraw_all));
		m_position.changed_signal().connect(SigC::slot(*this, &render_engine::async_redraw_all));
		m_orientation.changed_signal().connect(SigC::slot(*this, &render_engine::async_redraw_all));
		m_scale.changed_signal().connect(SigC::slot(*this, &render_engine::async_redraw_all));
		
		m_pixel_width.changed_signal().connect(SigC::slot(*this, &render_engine::on_pixel_width_changed));
		m_pixel_height.changed_signal().connect(SigC::slot(*this, &render_engine::on_pixel_height_changed));
	}

	void constrain_screen_aspect_ratio(double& Ratio)
	{
		// Constrain the aspect ratio to match our output image
		Ratio = static_cast<double>(m_pixel_width.property_value()) / static_cast<double>(m_pixel_height.property_value());
	}

	k3d::iprojection* projection()
	{
		if(m_orthographic.property_value())
			return &m_orthographic_projection;
			
		return &m_perspective_projection;
	}

	aspect_ratio_changed_signal_t& aspect_ratio_changed_signal()
	{
		return m_aspect_ratio_changed_signal;
	}
	
	void on_pixel_width_changed()
	{
		const unsigned long pixel_width = m_pixel_width.property_value();
		const unsigned long pixel_height = m_pixel_height.property_value();
		
		return_if_fail(pixel_height);
		const double ratio = static_cast<double>(pixel_width) / static_cast<double>(pixel_height);
		
		m_left.set_value(-0.5 * ratio * std::abs(m_top.value() - m_bottom.value()));
		m_right.set_value(0.5 * ratio * std::abs(m_top.value() - m_bottom.value()));
		
		m_aspect_ratio_changed_signal.emit();
	}

	void on_pixel_height_changed()
	{
		const unsigned long pixel_width = m_pixel_width.property_value();
		const unsigned long pixel_height = m_pixel_height.property_value();
		
		return_if_fail(pixel_width);
		const double ratio = static_cast<double>(pixel_height) / static_cast<double>(pixel_width);
		
		m_top.set_value(0.5 * ratio * std::abs(m_right.value() - m_left.value()));
		m_bottom.set_value(-0.5 * ratio * std::abs(m_right.value() - m_left.value()));
		
		m_aspect_ratio_changed_signal.emit();
	}
			
	void on_viewport_draw(const k3d::viewport::render_state& State)
	{
		glDisable(GL_LIGHTING);
		glDisable(GL_TEXTURE_1D);
		glDisable(GL_TEXTURE_2D);

		glColor3d(0, 0, 0);
		glLineWidth(1.0f);
		glDisable(GL_LINE_STIPPLE);

		draw();
	}

	void on_viewport_select(const k3d::viewport::render_state& State)
	{
		draw();
	}

	void draw()
	{
		// Our dimensions
		const double bodylength = 0.5 * 0.5;
		const double bodywidth = 0.25 * 0.5;
		const double bodyheight = 0.25 * 0.5;
		const double lenslength = 0.25 * 0.5;
		const double lenswidth = 0.15 * 0.5;
		const double lensheight = 0.1 * 0.5;
		const double lensoffset = 0.05;
		const double filmradius = 0.2;
		const double filmwidth = 0.125 * 0.5;

		// Draw the camera body ...
		glBegin(GL_LINE_LOOP);
		glVertex3d(-bodywidth, bodyheight, bodylength);
		glVertex3d(bodywidth, bodyheight, bodylength);
		glVertex3d(bodywidth, bodyheight, -bodylength);
		glVertex3d(-bodywidth, bodyheight, -bodylength);
		glEnd();

		glBegin(GL_LINE_LOOP);
		glVertex3d(-bodywidth, -bodyheight, bodylength);
		glVertex3d(bodywidth, -bodyheight, bodylength);
		glVertex3d(bodywidth, -bodyheight, -bodylength);
		glVertex3d(-bodywidth, -bodyheight, -bodylength);
		glEnd();

		glBegin(GL_LINES);
		glVertex3d(-bodywidth, bodyheight, bodylength);
		glVertex3d(-bodywidth, -bodyheight, bodylength);
		glVertex3d(bodywidth, bodyheight, bodylength);
		glVertex3d(bodywidth, -bodyheight, bodylength);
		glVertex3d(bodywidth, bodyheight, -bodylength);
		glVertex3d(bodywidth, -bodyheight, -bodylength);
		glVertex3d(-bodywidth, bodyheight, -bodylength);
		glVertex3d(-bodywidth, -bodyheight, -bodylength);
		glEnd();

		// Draw the camera lens ...
		glBegin(GL_LINE_LOOP);
		glVertex3d(-lenswidth, lensheight, bodylength);
		glVertex3d(lenswidth, lensheight, bodylength);
		glVertex3d(lenswidth, -lensheight, bodylength);
		glVertex3d(-lenswidth, -lensheight, bodylength);
		glEnd();

		glBegin(GL_LINE_LOOP);
		glVertex3d(-lenswidth - lensoffset, lensheight + lensoffset, bodylength + lenslength);
		glVertex3d(lenswidth + lensoffset, lensheight + lensoffset, bodylength + lenslength);
		glVertex3d(lenswidth + lensoffset, -lensheight - lensoffset, bodylength + lenslength);
		glVertex3d(-lenswidth - lensoffset, -lensheight - lensoffset, bodylength + lenslength);
		glEnd();

		glBegin(GL_LINES);
		glVertex3d(-lenswidth, lensheight, bodylength);
		glVertex3d(-lenswidth - lensoffset, lensheight + lensoffset, bodylength + lenslength);
		glVertex3d(lenswidth, lensheight, bodylength);
		glVertex3d(lenswidth + lensoffset, lensheight + lensoffset, bodylength + lenslength);
		glVertex3d(lenswidth, -lensheight, bodylength);
		glVertex3d(lenswidth + lensoffset, -lensheight - lensoffset, bodylength + lenslength);
		glVertex3d(-lenswidth, -lensheight, bodylength);
		glVertex3d(-lenswidth - lensoffset, -lensheight - lensoffset, bodylength + lenslength);
		glEnd();

		// Draw the film can ...
		glBegin(GL_LINE_LOOP);
		glVertex3d(-filmwidth, bodyheight, -bodylength);
		glVertex3d(-filmwidth, bodyheight, -bodylength + filmradius);
		glVertex3d(-filmwidth, bodyheight + 0.8 * filmradius, -bodylength + 0.8 * filmradius);
		glVertex3d(-filmwidth, bodyheight + filmradius, -bodylength);
		glVertex3d(-filmwidth, bodyheight + 0.8 * filmradius, -bodylength - 0.8 * filmradius);
		glVertex3d(-filmwidth, bodyheight, -bodylength - filmradius);
		glVertex3d(-filmwidth, bodyheight - 0.8 * filmradius, -bodylength - 0.8 * filmradius);
		glVertex3d(-filmwidth, bodyheight - filmradius, -bodylength);
		glEnd();

		glBegin(GL_LINE_LOOP);
		glVertex3d(filmwidth, bodyheight, -bodylength);
		glVertex3d(filmwidth, bodyheight, -bodylength + filmradius);
		glVertex3d(filmwidth, bodyheight + 0.8 * filmradius, -bodylength + 0.8 * filmradius);
		glVertex3d(filmwidth, bodyheight + filmradius, -bodylength);
		glVertex3d(filmwidth, bodyheight + 0.8 * filmradius, -bodylength - 0.8 * filmradius);
		glVertex3d(filmwidth, bodyheight, -bodylength - filmradius);
		glVertex3d(filmwidth, bodyheight - 0.8 * filmradius, -bodylength - 0.8 * filmradius);
		glVertex3d(filmwidth, bodyheight - filmradius, -bodylength);
		glEnd();

		glBegin(GL_LINES);
		glVertex3d(-filmwidth, bodyheight, -bodylength + filmradius);
		glVertex3d(filmwidth, bodyheight, -bodylength + filmradius);
		glVertex3d(-filmwidth, bodyheight + 0.8 * filmradius, -bodylength + 0.8 * filmradius);
		glVertex3d(filmwidth, bodyheight + 0.8 * filmradius, -bodylength + 0.8 * filmradius);
		glVertex3d(-filmwidth, bodyheight + filmradius, -bodylength);
		glVertex3d(filmwidth, bodyheight + filmradius, -bodylength);
		glVertex3d(-filmwidth, bodyheight + 0.8 * filmradius, -bodylength - 0.8 * filmradius);
		glVertex3d(filmwidth, bodyheight + 0.8 * filmradius, -bodylength - 0.8 * filmradius);
		glVertex3d(-filmwidth, bodyheight, -bodylength - filmradius);
		glVertex3d(filmwidth, bodyheight, -bodylength - filmradius);
		glVertex3d(-filmwidth, bodyheight - 0.8 * filmradius, -bodylength - 0.8 * filmradius);
		glVertex3d(filmwidth, bodyheight - 0.8 * filmradius, -bodylength - 0.8 * filmradius);
		glVertex3d(-filmwidth, bodyheight - filmradius, -bodylength);
		glVertex3d(filmwidth, bodyheight - filmradius, -bodylength);
		glEnd();
	}

	bool render_preview()
	{
		// Keep track of compiled shaders ...
		k3d::ri::render_engine::shaders_t shaders;

		// Start a new render job ...
		k3d::irender_job& job = k3d::application().render_farm().create_job("k3d-preview");

		// Add a single render frame to the job ...
		k3d::irender_frame& frame = job.create_frame("frame");

		// Create an output image path ...
		const boost::filesystem::path outputimagepath = frame.add_output_file("outputimage");
		return_val_if_fail(!outputimagepath.empty(), false);

		// Render it (visible rendering) ...
		return_val_if_fail(render(frame, outputimagepath, true, shaders), false);

		// Start the job running ...
		k3d::application().render_farm().start_job(job);

		return true;
	}

	bool render_frame(const boost::filesystem::path& OutputImage, const bool ViewImage)
	{
		// Sanity checks ...
		return_val_if_fail(!OutputImage.empty(), false);

		// Keep track of compiled shaders ...
		k3d::ri::render_engine::shaders_t shaders;

		// Start a new render job ...
		k3d::irender_job& job = k3d::application().render_farm().create_job("k3d-render-frame");

		// Add a single render frame to the job ...
		k3d::irender_frame& frame = job.create_frame("frame");

		// Create an output image path ...
		const boost::filesystem::path outputimagepath = frame.add_output_file("outputimage");
		return_val_if_fail(!outputimagepath.empty(), false);

		// Copy the output image to its requested destination ...
		frame.add_copy_operation(outputimagepath, OutputImage);

		// View the output image when it's done ...
		if(ViewImage)
			frame.add_view_operation(OutputImage);

		// Render it (hidden rendering) ...
		return_val_if_fail(render(frame, outputimagepath, false, shaders), false);

		// Start the job running ...
		k3d::application().render_farm().start_job(job);

		return true;
	}

	bool render_animation(const boost::filesystem::path& OutputImages, const bool ViewCompletedImages)
	{
		// Sanity checks ...
		return_val_if_fail(!OutputImages.empty(), false);

		// Ensure that the document has animation capabilities, first ...
		k3d::iproperty* const start_time_property = k3d::get_start_time(document());
		k3d::iproperty* const end_time_property = k3d::get_end_time(document());
		k3d::iproperty* const frame_rate_property = k3d::get_frame_rate(document());
		k3d::iwritable_property* const time_property = dynamic_cast<k3d::iwritable_property*>(k3d::get_time(document()));
		return_val_if_fail(start_time_property && end_time_property && frame_rate_property && time_property, false);

		// Test the output images filepath to make sure it can hold all the frames we're going to generate ...
		const double start_time = boost::any_cast<double>(k3d::get_property_value(document().dag(), *start_time_property));
		const double end_time = boost::any_cast<double>(k3d::get_property_value(document().dag(), *end_time_property));
		const double frame_rate = boost::any_cast<double>(k3d::get_property_value(document().dag(), *frame_rate_property));
		
		const long start_frame = static_cast<long>(k3d::round(frame_rate * start_time));
		const long end_frame = static_cast<long>(k3d::round(frame_rate * end_time));
		
		k3d::frames frames(OutputImages, start_frame, end_frame);
		return_val_if_fail(frames.max_frame() >= end_frame, false);

		// Keep track of compiled shaders ...
		k3d::ri::render_engine::shaders_t shaders;

		// Start a new render job ...
		k3d::irender_job& job = k3d::application().render_farm().create_job("k3d-render-animation");

		// For each frame to be rendered ...
		for(long view_frame = start_frame; view_frame < end_frame; ++view_frame)
			{
				// Set the frame time ...
				time_property->set_value(view_frame / frame_rate);

				// Redraw everything ...
				k3d::viewport::redraw_all(document(), k3d::iviewport::SYNCHRONOUS);

				// Add a render frame to the job ...
				std::stringstream buffer;
				buffer << "frame-" << std::setw(frames.frame_digits()) << std::setfill('0') << view_frame;
				k3d::irender_frame& frame = job.create_frame(buffer.str());

				// Create an output image path ...
				const boost::filesystem::path outputimagepath = frame.add_output_file("outputimage");
				return_val_if_fail(!outputimagepath.empty(), false);

				// Copy the output image to its requested destination ...
				boost::filesystem::path destination;
				frames.frame(view_frame, destination);
				frame.add_copy_operation(outputimagepath, destination);

				// View the output image when it's done ...
				if(ViewCompletedImages)
					frame.add_view_operation(destination);

				// Render it (hidden rendering) ...
				return_val_if_fail(render(frame, outputimagepath, false, shaders), false);
			}

		// Start the job running ...
		k3d::application().render_farm().start_job(job);

		return true;
	}

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

	static k3d::iplugin_factory& get_factory()
	{
		static k3d::plugin_factory<
			k3d::document_plugin<render_engine>,
				 k3d::interface_list<k3d::iviewport_host,
				 k3d::interface_list<k3d::itransform_source,
				 k3d::interface_list<k3d::itransform_sink,
				 k3d::interface_list<k3d::ianimation_render_engine,
				 k3d::interface_list<k3d::istill_render_engine > > > > > > factory(
					k3d::classes::RenderManEngine(),
					"RenderManEngine",
					"RenderMan Render Engine",
					"Objects",
					k3d::iplugin_factory::STABLE);

		return factory;
	}

private:
	bool render(k3d::irender_frame& Frame, const boost::filesystem::path& OutputImagePath, const bool VisibleRender, k3d::ri::render_engine::shaders_t& Shaders)
	{
		// Sanity checks ...
		return_val_if_fail(!OutputImagePath.empty(), false);

		// Setup global time for this frame ...
		k3d::iproperty* const frame_rate_property = k3d::get_frame_rate(document());
		k3d::iproperty* const time_property = k3d::get_time(document());
		k3d::iwritable_property* const writable_time_property = dynamic_cast<k3d::iwritable_property*>(time_property);
		return_val_if_fail(frame_rate_property && time_property && writable_time_property, false);
		
		const double frame_delta = 1.0 / boost::any_cast<double>(k3d::get_property_value(document().dag(), *frame_rate_property));
		const double frame_time = boost::any_cast<double>(k3d::get_property_value(document().dag(), *time_property));

		// Start our RIB file ...
		const std::string ribfilename("world.rib");
		const boost::filesystem::path ribfilepath = Frame.add_input_file(ribfilename);
		return_val_if_fail(!ribfilepath.empty(), false);

		// Open the RIB file stream ...
		boost::filesystem::ofstream ribfile(ribfilepath);
		return_val_if_fail(ribfile.good(), false);

		// Setup the frame for RI rendering with the user's preferred engine ...
		Frame.add_render_operation("ri", m_render_engine.property_value(), boost::filesystem::path(ribfilename, boost::filesystem::native), VisibleRender);

		// Create the Ri render engine object ...
		k3d::ri::render_engine engine(ribfile);

		// Administrivia ...
		engine.RiNewline();
		engine.RiComment("Created with K-3D Version " + k3d::to_string(VERSION) + ", http://www.k-3d.org");

		// Set path options ...
		engine.RiNewline();
		engine.RiComment("Setup file search paths");

		k3d::ri::parameter_list searchpath_options;
		searchpath_options.push_back(k3d::ri::parameter("shader", k3d::ri::UNIFORM, k3d::application().shader_cache_path().native_file_string() + ":&"));
		engine.RiOptionV("searchpath", searchpath_options);

		// Perform frame-start operations for textures
		std::for_each(document().objects().collection().begin(), document().objects().collection().end(), detail::setup_texture(Frame, engine));

		// Start the (final output) frame ...
		engine.RiNewline();
		engine.RiFrameBegin(1);

		// Set limits options
		engine.RiNewline();
		engine.RiComment("Setup options");

		k3d::ri::integers bucketsize;
		bucketsize.push_back(m_bucket_width.property_value());
		bucketsize.push_back(m_bucket_height.property_value());

		k3d::ri::parameter_list limits;
		limits.push_back(k3d::ri::parameter("gridsize", k3d::ri::UNIFORM, static_cast<k3d::ri::integer>(m_grid_size.property_value())));
		limits.push_back(k3d::ri::parameter("bucketsize", k3d::ri::UNIFORM, bucketsize, bucketsize.size()));
		limits.push_back(k3d::ri::parameter("eyesplits", k3d::ri::UNIFORM, static_cast<k3d::ri::integer>(m_eye_splits.property_value())));
		limits.push_back(k3d::ri::parameter("texturememory", k3d::ri::UNIFORM, static_cast<k3d::ri::integer>(m_texture_memory.property_value())));
		engine.RiOptionV("limits", limits);

		// Set the display type ...
		engine.RiNewline();
		engine.RiComment("Setup Display");

		boost::filesystem::path outputimage(OutputImagePath);

		if(VisibleRender)
			engine.RiDisplayV(outputimage.leaf(), k3d::ri::RI_FRAMEBUFFER(), k3d::ri::RI_RGB());
		else
			{
				if(m_render_alpha.property_value())
					engine.RiDisplayV(outputimage.leaf(), k3d::ri::RI_FILE(), k3d::ri::RI_RGBA());
				else
					engine.RiDisplayV(outputimage.leaf(), k3d::ri::RI_FILE(), k3d::ri::RI_RGB());
			}

		engine.RiFormat(static_cast<int>(m_pixel_width.property_value()), static_cast<int>(m_pixel_height.property_value()), m_pixel_aspect_ratio.property_value());

		// Set pixel sampling rates ...
		engine.RiPixelSamples(m_pixel_xsamples.property_value(), m_pixel_ysamples.property_value());
		engine.RiPixelFilter(m_pixel_filter.property_value(), m_pixel_filter_width.property_value(), m_pixel_filter_height.property_value());

		// Set gain & gamma ...
		engine.RiExposure(m_exposure.property_value(), m_gamma.property_value());

		// Set depth-of-field options ...
		if(m_dof.property_value())
			engine.RiDepthOfField(
				m_fstop.property_value(),
				m_focal_length.property_value(),
				m_focus_plane.property_value());

		// Set global shading rate ...
		engine.RiShadingRate(m_shading_rate.property_value());

		// Set global shading interpolation ...
		engine.RiShadingInterpolation(m_shading_interpolation.property_value());

		// We use LH orientation, the way Our Lord Who Art In Heaven wants us to ...
		engine.RiOrientation(k3d::ri::RI_LH());

		// Double-sided surfaces ...
		engine.RiSides(m_two_sided.property_value() ? 2 : 1);

		// Crop window ...
		engine.RiCropWindow(m_crop_window_left.property_value(), m_crop_window_right.property_value(), m_crop_window_top.property_value(), m_crop_window_bottom.property_value());

		// Setup our motion-blur sampling loop ...
		k3d::ri::sample_times_t samples(1, 0.0);
		if(m_render_motion_blur.property_value())
			samples.push_back(1.0);

		const bool motion_blur_camera = m_motion_blur.property_value();
		std::vector<k3d::vector3> motion_blur_camera_position_samples;
		std::vector<k3d::angle_axis> motion_blur_camera_orientation_samples;

		for(boost::uint32_t sample_index = 0; sample_index < samples.size(); ++sample_index)
			{
				// Calculate the current time and animate the document ...
				const double sample_delta = (samples.size() > 1) ? frame_delta / static_cast<double>(samples.size() - 1) : frame_delta;
				const double sample_time = frame_time + (sample_index * sample_delta);

				writable_time_property->set_value(sample_time);

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

				motion_blur_camera_position_samples.push_back(position);
				motion_blur_camera_orientation_samples.push_back(orientation);

				// Setup render state ...
				k3d::ri::render_state state(Frame, engine, *projection(), k3d::ri::FINAL_FRAME, samples, sample_index, transform_matrix);

				if(k3d::ri::last_sample(state))
					{
						engine.RiNewline();
						engine.RiComment("Setup viewing transformation");
						
						if(m_orthographic.property_value())
							{
								engine.RiProjectionV("orthographic");
								engine.RiScreenWindow(m_left.property_value(), m_right.property_value(), m_bottom.property_value(), m_top.property_value());
								engine.RiClipping(m_near.property_value(), m_far.property_value());
							}
						else
							{
								engine.RiProjectionV("perspective");
								engine.RiScreenWindow(m_left.property_value(), m_right.property_value(), m_bottom.property_value(), m_top.property_value());
								engine.RiClipping(m_near.property_value(), m_far.property_value());
							}

						if(k3d::ri::motion_blur(state) && motion_blur_camera)
							{
								engine.RiMotionBeginV(state.sample_times);
								for(unsigned int i = 0; i < motion_blur_camera_orientation_samples.size(); ++i)
									{
										k3d::quaternion q(motion_blur_camera_orientation_samples[i]);
										k3d::euler_angles a(q, k3d::euler_angles::ZXYstatic);
										engine.RiRotate(-k3d::degrees(a[0]), 0.0f, 0.0f, 1.0f);
									}
								engine.RiMotionEnd();
								
								engine.RiMotionBeginV(state.sample_times);
								for(unsigned int i = 0; i < motion_blur_camera_orientation_samples.size(); ++i)
									{
										k3d::quaternion q(motion_blur_camera_orientation_samples[i]);
										k3d::euler_angles a(q, k3d::euler_angles::ZXYstatic);
										engine.RiRotate(-k3d::degrees(a[1]), 1.0f, 0.0f, 0.0f);
									}
								engine.RiMotionEnd();

								engine.RiMotionBeginV(state.sample_times);
								for(unsigned int i = 0; i < motion_blur_camera_orientation_samples.size(); ++i)
									{
										k3d::quaternion q(motion_blur_camera_orientation_samples[i]);
										k3d::euler_angles a(q, k3d::euler_angles::ZXYstatic);
										engine.RiRotate(-k3d::degrees(a[2]), 0.0f, 1.0f, 0.0f);
									}
								engine.RiMotionEnd();
								
								engine.RiMotionBeginV(state.sample_times);
								for(unsigned int i = 0; i < motion_blur_camera_position_samples.size(); ++i)
									engine.RiTranslate(-motion_blur_camera_position_samples[i][0], -motion_blur_camera_position_samples[i][1], -motion_blur_camera_position_samples[i][2]);
								engine.RiMotionEnd();
							}
						else
							{
								k3d::quaternion q(motion_blur_camera_orientation_samples[0]);
								k3d::euler_angles a(q, k3d::euler_angles::ZXYstatic);
								engine.RiRotate(-k3d::degrees(a[0]), 0.0f, 0.0f, 1.0f);

								q = k3d::quaternion(motion_blur_camera_orientation_samples[0]);
								a = k3d::euler_angles(q, k3d::euler_angles::ZXYstatic);
								engine.RiRotate(-k3d::degrees(a[1]), 1.0f, 0.0f, 0.0f);
								
								q = k3d::quaternion(motion_blur_camera_orientation_samples[0]);
								a = k3d::euler_angles(q, k3d::euler_angles::ZXYstatic);
								engine.RiRotate(-k3d::degrees(a[2]), 0.0f, 1.0f, 0.0f);
								
								engine.RiTranslate(-motion_blur_camera_position_samples[0][0], -motion_blur_camera_position_samples[0][1], -motion_blur_camera_position_samples[0][2]);
							}

						// Default shaders ...
						if(m_default_atmosphere_shader.interface())
							m_default_atmosphere_shader.interface()->setup_renderman_atmosphere_shader(state);
						if(m_default_interior_shader.interface())
							m_default_interior_shader.interface()->setup_renderman_interior_shader(state);
						if(m_default_exterior_shader.interface())
							m_default_exterior_shader.interface()->setup_renderman_exterior_shader(state);
						if(m_imager_shader.interface())
							m_imager_shader.interface()->setup_renderman_imager_shader(state);
							
						// Begin the world ...
						engine.RiNewline();
						engine.RiWorldBegin();
					}

				// Pre-render objects ...
				std::for_each(document().objects().collection().begin(), document().objects().collection().end(), detail::pre_render(state));

				// Setup lights ...
				std::for_each(document().objects().collection().begin(), document().objects().collection().end(), detail::setup_light(state));

				// Render objects ...
				std::for_each(document().objects().collection().begin(), document().objects().collection().end(), detail::render(state));

				if(k3d::ri::last_sample(state))
					{
						// Finish the world ...
						engine.RiWorldEnd();
					}
			}

		// Finish the frame ...
		engine.RiFrameEnd();

		// Reset the time back to the beginning of the frame ...
		writable_time_property->set_value(frame_time);

		// Synchronize shaders ...
		const k3d::ri::render_engine::shaders_t shaders = engine.shaders();
		for(k3d::ri::render_engine::shaders_t::const_iterator shader_name = shaders.begin(); shader_name != shaders.end(); ++shader_name)
			{
				// If this shader has already been synchronized, we're done ...
				if(Shaders.count(*shader_name))
					continue;

				// Find a matching shader ...
				for(sdpsl::shaders_t::const_iterator shader = k3d::application().shaders().begin(); shader != k3d::application().shaders().end(); ++shader)
					{
						// No match ...
						if(shader->name != *shader_name)
							continue;

						// Compile that bad-boy!
						if(!k3d::compile_shader(
							boost::filesystem::path(shader->file_path, boost::filesystem::native),
							"ri",
							m_render_engine.property_value()))
							std::cerr << "Error compiling shader [" << shader->name << "]" << std::endl;

						break;
					}

				// Keep track of this shader ...
				Shaders.insert(*shader_name);
			}

		return true;
	}

	k3d_list_property(std::string, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_render_engine;
	k3d_measurement_property(unsigned long, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::with_constraint) m_pixel_width;
	k3d_measurement_property(unsigned long, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::with_constraint) m_pixel_height;
	k3d_measurement_property(double, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::with_constraint) m_pixel_aspect_ratio;
	k3d_object_property(k3d::ri::ivolume_shader, k3d::immutable_name, k3d::with_undo, k3d::local_storage) m_default_atmosphere_shader;
	k3d_object_property(k3d::ri::ivolume_shader, k3d::immutable_name, k3d::with_undo, k3d::local_storage) m_default_interior_shader;
	k3d_object_property(k3d::ri::ivolume_shader, k3d::immutable_name, k3d::with_undo, k3d::local_storage) m_default_exterior_shader;
	k3d_object_property(k3d::ri::iimager_shader, k3d::immutable_name, k3d::with_undo, k3d::local_storage) m_imager_shader;
	k3d_data_property(bool, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_orthographic;
	k3d_measurement_property(double, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_left;
	k3d_measurement_property(double, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_right;
	k3d_measurement_property(double, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_top;
	k3d_measurement_property(double, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_bottom;
	k3d_measurement_property(double, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::with_constraint) m_near;
	k3d_measurement_property(double, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::with_constraint) m_far;
	k3d_measurement_property(unsigned long, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::with_constraint) m_bucket_width;
	k3d_measurement_property(unsigned long, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::with_constraint) m_bucket_height;
	k3d_measurement_property(unsigned long, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::with_constraint) m_grid_size;
	k3d_measurement_property(unsigned long, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::with_constraint) m_eye_splits;
	k3d_measurement_property(unsigned long, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::with_constraint) m_texture_memory;
	k3d_data_property(bool, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_render_alpha;
	k3d_measurement_property(double, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::with_constraint) m_pixel_xsamples;
	k3d_measurement_property(double, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::with_constraint) m_pixel_ysamples;
	k3d_list_property(std::string, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_pixel_filter;
	k3d_measurement_property(double, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::with_constraint) m_pixel_filter_width;
	k3d_measurement_property(double, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::with_constraint) m_pixel_filter_height;
	k3d_measurement_property(double, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_exposure;
	k3d_measurement_property(double, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_gamma;
	k3d_data_property(bool, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_dof;
	k3d_measurement_property(double, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_fstop;
	k3d_measurement_property(double, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_focal_length;
	k3d_measurement_property(double, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_focus_plane;
	k3d_measurement_property(double, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_shading_rate;
	k3d_list_property(std::string, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_shading_interpolation;
	k3d_data_property(bool, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_two_sided;
	k3d_measurement_property(double, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_crop_window_left;
	k3d_measurement_property(double, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_crop_window_right;
	k3d_measurement_property(double, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_crop_window_top;
	k3d_measurement_property(double, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_crop_window_bottom;
	k3d_data_property(bool, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_motion_blur;
	k3d_data_property(bool, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_render_motion_blur;

	aspect_ratio_changed_signal_t m_aspect_ratio_changed_signal;

	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;

	const k3d::ilist_property<std::string>::values_t& render_engine_values()
	{
		static k3d::ilist_property<std::string>::values_t values;
		if(values.empty())
			{
				const k3d::ioptions::render_engines_t engines = k3d::application().options().render_engines();
				for(k3d::ioptions::render_engines_t::const_iterator engine = engines.begin(); engine != engines.end(); ++engine)
					{
						if(engine->type == "ri")
							values.push_back(engine->engine);
					}
			}
		return values;
	}

	const k3d::ilist_property<std::string>::values_t& pixel_filter_values()
	{
		static k3d::ilist_property<std::string>::values_t values;
		if(values.empty())
			{
				values.push_back(k3d::ri::RI_GAUSSIAN());
				values.push_back(k3d::ri::RI_BOX());
				values.push_back(k3d::ri::RI_TRIANGLE());
				values.push_back(k3d::ri::RI_CATMULL_ROM());
				values.push_back(k3d::ri::RI_SINC());
			}
		return values;
	}

	const k3d::ilist_property<std::string>::values_t& shading_interpolation_values()
	{
		static k3d::ilist_property<std::string>::values_t values;
		if(values.empty())
			{
				values.push_back(k3d::ri::RI_CONSTANT());
				values.push_back(k3d::ri::RI_SMOOTH());
			}
		return values;
	}
};

k3d::iplugin_factory& render_engine_factory()
{
	return render_engine::get_factory();
}

} // namespace libk3drenderman


