// K-3D
// Copyright (c) 1995-2004, Timothy M. Shead
//
// Contact: tshead@k-3d.com
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public
// License along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

/** \file
		\author Timothy M. Shead (tshead@k-3d.com)
*/

#include <k3dsdk/irenderman.h>
#include <k3dsdk/object.h>
#include <k3dsdk/persistence.h>
#include <k3dsdk/mesh_filter.h>
#include <k3dsdk/module.h>
#include <k3dsdk/plugins.h>
#include <k3dsdk/selection.h>

#include <iterator>

namespace libk3dmesh
{

/////////////////////////////////////////////////////////////////////////////
// smooth_shade_implementation

class smooth_shade_implementation :
	public k3d::mesh_filter<k3d::persistent<k3d::object> >
{
	typedef k3d::mesh_filter<k3d::persistent<k3d::object> > base;

public:
	smooth_shade_implementation(k3d::idocument& Document) :
		base(Document)
	{
		m_input_mesh.changed_signal().connect(SigC::slot(*this, &smooth_shade_implementation::on_reset_geometry));
		m_output_mesh.need_data_signal().connect(SigC::slot(*this, &smooth_shade_implementation::on_create_geometry));
	}
	
	void on_reset_geometry()
	{
		m_output_mesh.reset();
	}
	
	k3d::mesh* on_create_geometry()
	{
		// If we don't have any input mesh, we're done ...
		const k3d::mesh* const input = m_input_mesh.property_value();
		if(!input)
			return 0;

		// Otherwise, we make a copy of the input mesh and modify the copy ...
		k3d::mesh* const output = new k3d::mesh();
		k3d::deep_copy(*input, *output);
		modify_geometry(*input, *output);

		return output;
	}

	void set_normal(const bool Selected, const bool IgnoreSelection, const k3d::ri::normal& NewNormal, const k3d::ri::normal& DefaultNormal, k3d::parameters_t& Parameters)
	{
		if(IgnoreSelection || Selected)
			Parameters["N"] = NewNormal;
		else if(0 == Parameters.count("N"))
			Parameters["N"] = DefaultNormal;
	}

	void modify_geometry(const k3d::mesh& Source, k3d::mesh& Target)
	{
		const bool ignore_selection = !k3d::contains_selection(Source);

		for(k3d::mesh::polyhedra_t::iterator polyhedron = Target.polyhedra.begin(); polyhedron != Target.polyhedra.end(); ++polyhedron)
			{
				typedef std::map<k3d::split_edge*, k3d::vector3> edge_normal_map_t;
				edge_normal_map_t edge_normal_map;
				
				typedef std::map<k3d::point*, k3d::vector3> point_normal_map_t;
				point_normal_map_t point_normal_map;
				
				typedef std::map<k3d::point*, unsigned long> edge_count_map_t;
				edge_count_map_t edge_count_map;
				
				for(k3d::polyhedron::faces_t::const_iterator face = (*polyhedron)->faces.begin(); face != (*polyhedron)->faces.end(); ++face)
					{
						const k3d::vector3 face_normal = k3d::normal(**face);
						
						for(k3d::split_edge* edge = (*face)->first_edge; edge; edge = edge->face_clockwise)
							{
								edge_normal_map[edge] = face_normal;
								
								point_normal_map.insert(std::make_pair(edge->vertex, k3d::vector3(0, 0, 0))).first->second += face_normal;
								edge_count_map.insert(std::make_pair(edge->vertex, 0)).first->second += 1;
							
								if((*face)->first_edge == edge->face_clockwise)
									break;
							}
					}
			
				// Finish calculating average normals for all our points ...
				point_normal_map_t::iterator point = point_normal_map.begin();
				edge_count_map_t::const_iterator count = edge_count_map.begin();
				for(; point != point_normal_map.end() && count != edge_count_map.end(); ++point, ++count)
					{
						if(count->second != 0)
							point->second /= count->second;
					}
			
				// Now, store normals for every edge (i.e. facevarying) ... either from the face as a whole, or the average of all adjacent faces ...
				for(k3d::polyhedron::edges_t::const_iterator edge = (*polyhedron)->edges.begin(); edge != (*polyhedron)->edges.end(); ++edge)
					set_normal((*edge)->selected, ignore_selection, point_normal_map[(*edge)->vertex], edge_normal_map[*edge], (*edge)->facevarying_data);
			}
	}
	
	k3d::iplugin_factory& factory()
	{
		return get_factory();
	}

	static k3d::iplugin_factory& get_factory()
	{
		static k3d::plugin_factory<
			k3d::document_plugin<smooth_shade_implementation>,
			k3d::interface_list<k3d::imesh_source,
			k3d::interface_list<k3d::imesh_sink > > > factory(
				k3d::uuid(0x7eed702d, 0xba274e6b, 0xb8a5db73, 0xba9c2382),
				"SmoothShade",
				"Simulates smooth geometry by averaging polygon normals",
				"Objects",
				k3d::iplugin_factory::STABLE);

		return factory;
	}

private:
};

/////////////////////////////////////////////////////////////////////////////
// smooth_shade_factory

k3d::iplugin_factory& smooth_shade_factory()
{
	return smooth_shade_implementation::get_factory();
}

} // namespace libk3dmesh

