#include "Delaunay.hpp"
#include <vector>
#include <cmath>
#include "../misc/triplet.hpp"
#include <boost/foreach.hpp>
#ifdef RICH_MPI
#include <mpi.h>
#endif // RICH_MPI

namespace {
	pair<int, int> find_diff(const facet& f1, const facet& f2)
	{
		if (f1.vertices.first != f2.vertices.first &&
			f1.vertices.first != f2.vertices.second &&
			f1.vertices.first != f2.vertices.third)
			return pair<int, int>(f1.vertices.first, 0);
		else if (f1.vertices.second != f2.vertices.first &&
			f1.vertices.second != f2.vertices.second &&
			f1.vertices.second != f2.vertices.third)
			return pair<int, int>(f1.vertices.second, 1);
		else if (f1.vertices.third != f2.vertices.first &&
			f1.vertices.third != f2.vertices.second &&
			f1.vertices.third != f2.vertices.third)
			return pair<int, int>(f1.vertices.third, 2);
		else
			throw UniversalError("Delaunay, Couldn't find difference bewteen two facets");
	}
}

Delaunay::DataOnlyForBuild::DataOnlyForBuild() :copied(vector<vector<char> >())
{}

Delaunay::DataOnlyForBuild::DataOnlyForBuild(DataOnlyForBuild const& other) :copied(other.copied) {}

Delaunay::DataOnlyForBuild& Delaunay::DataOnlyForBuild::operator=
(DataOnlyForBuild const& other)
{
	if (this != &other)
	{
		copied = other.copied;
	}
	return *this;
}

Delaunay::Delaunay(void) :
	lastFacet(0), CalcRadius(false),
	radius(vector<double>()), cell_points(vector<Vector2D>()),
	PointWasAdded(false),
	last_facet_added(0),
	f(vector<facet>()),
	cor(vector<Vector2D>()),
	length(0),
	olength(0), location_pointer(0), last_loc(0),
	logger(0)
#ifdef RICH_MPI
	,OrgIndex(vector<int>())
#endif
{}

Delaunay::Delaunay(Delaunay const& other) :
	lastFacet(other.lastFacet),
	CalcRadius(other.CalcRadius),
	radius(other.radius), cell_points(other.cell_points),
	PointWasAdded(other.PointWasAdded),
	last_facet_added(other.last_facet_added),
	f(other.f),
	cor(other.cor),
	length(other.length),
	olength(other.olength),
	location_pointer(other.location_pointer),
	last_loc(other.last_loc),
	logger(other.logger)
#ifdef RICH_MPI
	,OrgIndex(other.OrgIndex)
#endif
{}

Delaunay::~Delaunay(void)
{
	cor.clear();
	f.clear();
	cell_points.clear();
}

namespace
{
	// Checks if a point is inside a triangle
	bool InTriangle(const TripleConstRef<Vector2D>& tri,
		const Vector2D& point)
	{
		return (orient2d(TripleConstRef<Vector2D>(tri.first,
			tri.second,
			point)) > 0) &&
			(orient2d(TripleConstRef<Vector2D>(tri.second,
				tri.third,
				point)) > 0) &&
			(orient2d(TripleConstRef<Vector2D>(tri.third,
				tri.first,
				point)) > 0);
	}

	// Assume cell is orederd in convexhull counterclockwise
	bool InCell(vector<Vector2D> const& points, Vector2D const& p)
	{
		int n = static_cast<int>(points.size());
		for (int i = 0; i < n; ++i)
		{
			if (CrossProduct(points[static_cast<size_t>(i)] - p, points[static_cast<size_t>((i + 1) % n)] - p) < 0)
				return false;
		}
		return true;
	}

	vector<double> CellSize(vector<Vector2D> const& points)
	{
		int n = static_cast<int>(points.size());
		double minx = points[0].x;
		double miny = points[0].y;
		double maxx = minx;
		double maxy = miny;
		for (int i = 1; i < n; ++i)
		{
			minx = min(points[static_cast<size_t>(i)].x, minx);
			miny = min(points[static_cast<size_t>(i)].y, miny);
			maxx = max(points[static_cast<size_t>(i)].x, maxx);
			maxy = max(points[static_cast<size_t>(i)].y, maxy);
		}
		vector<double> res(4);
		res[0] = minx;
		res[1] = maxx;
		res[2] = miny;
		res[3] = maxy;
		return res;
	}

	size_t find_index(facet const& fc, int i)
	{
		for (size_t j = 0; j < 3; ++j)
		{
			if (fc.neighbors[j] == i)
				return j;
		}
		throw UniversalError("Error in find_index: Index not found");
	}
}

void Delaunay::add_point(size_t index,stack<std::pair<size_t, size_t> > &flip_stack)
{
	// Check if point is inside big triangle
	assert(InTriangle(TripleConstRef<Vector2D>(cor[olength],
		cor[olength + 1],
		cor[olength + 2]),
		cor[index]));
	const size_t triangle = Walk(index);
	const Triplet<int> outer(f[triangle].vertices);
	const Triplet<int> temp_friends(f[triangle].neighbors);
	f[triangle].vertices.set(outer.third, outer.first, static_cast<int>(index));
	f[triangle].neighbors.set(temp_friends.third,
		location_pointer + 1,
		location_pointer + 2);
	f.push_back(facet(TripleConstRef<int>(outer.first,
		outer.second,
		static_cast<int>(index)),
		TripleConstRef<int>(temp_friends.first,
			location_pointer + 2,
			static_cast<int>(triangle))));
	f.push_back(facet(TripleConstRef<int>(outer.second,
		outer.third,
		static_cast<int>(index)),
		TripleConstRef<int>(temp_friends.second,
			static_cast<int>(triangle),
			location_pointer + 1)));
	// _update the friends list of the friends
	if (temp_friends.second != last_loc)
	{
		const size_t i = find_index(f[static_cast<size_t>(temp_friends.second)], static_cast<int>(triangle));
		f[static_cast<size_t>(temp_friends.second)].neighbors[i] = location_pointer + 2;
	}
	if (temp_friends.first != last_loc)
	{
		const size_t i = find_index(f[static_cast<size_t>(temp_friends.first)], static_cast<int>(triangle));
		f[static_cast<size_t>(temp_friends.first)].neighbors[i] = location_pointer + 1;
	}
	// Calculate radius if needed
	if (CalcRadius)
	{
		radius[static_cast<size_t>(triangle)] = CalculateRadius(static_cast<int>(triangle));
		int n = int(f.size());
		int m = int(radius.size());
		if (n > m - 1)
		{
			radius.push_back(CalculateRadius(location_pointer + 1));
			radius.push_back(CalculateRadius(location_pointer + 2));
		}
		else
			if (n > m)
			{
				radius[static_cast<size_t>(location_pointer) + 1] = CalculateRadius(location_pointer + 1);
				radius.push_back(CalculateRadius(location_pointer + 2));
			}
			else
			{
				radius[static_cast<size_t>(location_pointer) + 1] = CalculateRadius(location_pointer + 1);
				radius[static_cast<size_t>(location_pointer) + 2] = CalculateRadius(location_pointer + 2);
			}
	}

	// check if flipping is needed
	flip(triangle, static_cast<size_t>(temp_friends.third),flip_stack);
	flip(static_cast<size_t>(location_pointer) + 1, static_cast<size_t>(temp_friends.first),flip_stack);
	flip(static_cast<size_t>(location_pointer) + 2, static_cast<size_t>(temp_friends.second),flip_stack);

	// _update number of facets
	location_pointer += 2;
}

void Delaunay::flip(size_t i, size_t j, stack<std::pair<size_t, size_t> > &flip_stack)
{
	if (j == static_cast<size_t>(last_loc))
		return;
	flip_stack.push(std::pair<size_t, size_t>(i, j));
	while (!flip_stack.empty())
	{
		const pair<size_t, size_t> indexes = flip_stack.top();
		// Returns the index to the point to check in coordinates and the index of the point in the facet
		const pair<int, int> check = find_diff(f[indexes.second],
			f[indexes.first]);
		const pair<int, int> other = find_diff(f[indexes.first],
			f[indexes.second]);

		facet& prefetch_1 = f[indexes.first];
		if (incircle(cor[static_cast<size_t>(prefetch_1.vertices.first)],
			cor[static_cast<size_t>(prefetch_1.vertices.second)],
			cor[static_cast<size_t>(prefetch_1.vertices.third)],
			cor[static_cast<size_t>(check.first)]) > 0)
		{
			//The point is in a circle change the facets and their friends
			const int v1 = prefetch_1.vertices[static_cast<size_t>(other.second + 1) % 3];
			const int f1 = prefetch_1.neighbors[static_cast<size_t>(other.second)];
			const int f12 = prefetch_1.neighbors[static_cast<size_t>(other.second + 2) % 3];
			facet& prefetch_2 = f[indexes.second];
			const int v2 = prefetch_2.vertices[static_cast<size_t>(check.second + 1) % 3];
			const int f2 = prefetch_2.neighbors[static_cast<size_t>(check.second + 2) % 3];
			const int f22 = prefetch_2.neighbors[static_cast<size_t>(check.second)];
			prefetch_1.vertices.set(other.first, v1, check.first);
			prefetch_2.vertices.set(check.first, v2, other.first);
			prefetch_1.neighbors.set(f1, f2, static_cast<int>(indexes.second));
			prefetch_2.neighbors.set(f22, f12, static_cast<int>(indexes.first));
			// change the friends of the friends if needed
			if (f2 != last_loc)
			{
				f[static_cast<size_t>(f2)].neighbors[static_cast<size_t>(find_index(f[static_cast<size_t>(f2)], static_cast<int>(indexes.second)))] = static_cast<int>(indexes.first);
			}
			if (f12 != last_loc)
			{
				f[static_cast<size_t>(f12)].neighbors[static_cast<size_t>(find_index(f[static_cast<size_t>(f12)], static_cast<int>(indexes.first)))] = static_cast<int>(indexes.second);
			}
			// Calculate the new radius if needed
			if (CalcRadius)
			{
				radius[indexes.first] = CalculateRadius(static_cast<int>(indexes.first));
				radius[indexes.second] = CalculateRadius(static_cast<int>(indexes.second));
			}
			// clear the checked facets
			flip_stack.pop();
			// push into the stack the new facets to check
			if (prefetch_2.neighbors.first != last_loc)
				flip_stack.push(std::pair<size_t, size_t>(indexes.second,
					static_cast<size_t>(prefetch_2.neighbors.first)));
			if (prefetch_1.neighbors.second != last_loc)
				flip_stack.push(std::pair<size_t, size_t>(indexes.first,
					static_cast<size_t>(prefetch_1.neighbors.second)));
		}
		else
		{
			// clear the checked facets
			flip_stack.pop();
		}
	}
}

void Delaunay::build_delaunay(vector<Vector2D>const& vp, vector<Vector2D> const& cpoints)
{
	cell_points = cpoints;
	DataOnlyForBuild data;
	lastFacet = 0;
	CalcRadius = false;
	length = int(vp.size() + 3);
	int len = length - 3;
	olength = static_cast<size_t>(len);
	f.clear();
	cor.clear();
	f.reserve(static_cast<size_t>(2 * length + 1 + static_cast<int>(17 * sqrt(1.*length))));
	cor.reserve(static_cast<size_t>(length + 9 * static_cast<int>(sqrt(1.*length))));
	last_loc = INT_MAX;
	for (int i = 0; i < len; i++)
	{
		cor.push_back(vp[static_cast<size_t>(i)]);
	}
	// Check point input
	CheckInput();

	// add the 3 extreme points
	Vector2D p_temp;
	vector<double> cellsize = CellSize(cell_points);
	double width = cellsize[1] - cellsize[0];
	double height = cellsize[3] - cellsize[2];
	width = max(width, height);
	height = max(width, height);
	p_temp.x = cellsize[0] - 100 * width;
	p_temp.y = cellsize[2] - 100 * height;
	cor.push_back(p_temp);
	p_temp.x = cellsize[1] + 100 * width;
	p_temp.y = cellsize[2] - 100 * height;
	cor.push_back(p_temp);
	p_temp.x = (cellsize[0] + cellsize[1]) / 2.0;
	p_temp.y = cellsize[3] + 100 * height;
	cor.push_back(p_temp);
	// Create the big triangle, and assign friends
	facet f_temp;
	f.push_back(f_temp);
	f[0].vertices[0] = len;
	f[0].vertices[1] = len + 1;
	f[0].vertices[2] = len + 2;
	for (size_t i = 0; i < 3; i++)
		f[0].neighbors[i] = last_loc;
	location_pointer = 0;
	// add the points
	size_t nloop = static_cast<size_t>(length) - 3;
	stack<std::pair<size_t, size_t> > flip_stack;
	for (size_t i = 0; i < nloop; i++)
		add_point(i,flip_stack);
	// Calculate radius
	radius.resize(f.size());
	int n = int(f.size());
	for (int i = 0; i < n; ++i)
		radius[static_cast<size_t>(i)] = CalculateRadius(i);
	CalcRadius = true;
}

double Delaunay::triangle_area(int index)
{
	const TripleConstRef<Vector2D> p
		(cor[static_cast<size_t>(f[static_cast<size_t>(index)].vertices.first)],
			cor[static_cast<size_t>(f[static_cast<size_t>(index)].vertices.second)],
			cor[static_cast<size_t>(f[static_cast<size_t>(index)].vertices.third)]);
	const double x1 = p.third.x - p.first.x;
	const double x2 = p.second.x - p.first.x;
	const double y1 = p.third.y - p.first.y;
	const double y2 = p.second.y - p.first.y;
	return -0.5*(x1*y2 - x2*y1);
}

void Delaunay::update(const vector<Vector2D>& points, vector<Vector2D>
	const& cpoints)
{
	if (logger)
		logger->output(cor, f);
	build_delaunay(points, cpoints);
}

namespace {

	int Triplet<int>::* walk_condition(const vector<Vector2D>& cor,
		const Triplet<int>& vertices,
		size_t point)
	{
		if (orient2d(TripleConstRef<Vector2D>
			(cor[static_cast<size_t>(vertices.first)],
				cor[static_cast<size_t>(vertices.second)],
				cor[point])) < 0)
			return &Triplet<int>::first;
		else if (orient2d(TripleConstRef<Vector2D>
			(cor[static_cast<size_t>(vertices.second)],
				cor[static_cast<size_t>(vertices.third)],
				cor[point])) < 0)
			return &Triplet<int>::second;
		else if (orient2d(TripleConstRef<Vector2D>
			(cor[static_cast<size_t>(vertices.third)],
				cor[static_cast<size_t>(vertices.first)],
				cor[point])) < 0)
			return &Triplet<int>::third;
		else
			return 0;
	}

	class WalkBookkeeper
	{
	public:

	private:

	};

	size_t find_new_facet(const vector<Vector2D>& cor,
		const vector<facet>& f,
		size_t point,
		size_t last_facet)
	{
		size_t res = last_facet;
		int Triplet<int>::* next = walk_condition(cor,
			f[res].vertices,
			point);
		while (next) {
			res = static_cast<size_t>(f[res].neighbors.*next);
			next = walk_condition(cor,
				f[res].vertices,
				point);
		}
		return res;
	}
}

size_t Delaunay::Walk(size_t point)
{
	lastFacet = static_cast<int>(find_new_facet(cor, f, point, static_cast<size_t>(lastFacet)));
	return static_cast<size_t>(lastFacet);
}

vector<int> Delaunay::FindContainingTetras(int StartTetra, int point)
{
	vector<int> res;
	FindContainingTetras(StartTetra, point, res);
	return res;
}

double Delaunay::FindMaxRadius(int point)
{
	const vector<int> vec = FindContainingTetras(static_cast<int>(Walk(static_cast<size_t>(point))), point);
	double r = 0;
	for (size_t i = 0; i < vec.size(); ++i)
		r = max(r, radius[static_cast<size_t>(vec[static_cast<size_t>(i)])]);
	return 2 * r;
}

void Delaunay::FindContainingTetras(int StartFacet, int point, vector<int> &result)
{
	result.clear();
	int PointLocation = FindPointInFacet(StartFacet, point);
	int NextFacet = f[static_cast<size_t>(StartFacet)].neighbors[static_cast<size_t>(PointLocation)];
	result.reserve(12);
	result.push_back(NextFacet);
	while (NextFacet != StartFacet)
	{
		PointLocation = FindPointInFacet(NextFacet, point);
		NextFacet = f[static_cast<size_t>(NextFacet)].neighbors[static_cast<size_t>(PointLocation)];
		result.push_back(NextFacet);
	}
}

int Delaunay::FindPointInFacet(int facet, int point)
{
	for (int i = 0; i < 3; ++i)
		if (f[static_cast<size_t>(facet)].vertices[static_cast<size_t>(i)] == point)
			return i;
	UniversalError eo("Error in Delaunay, FindPointInFacet");
	eo.AddEntry("Facet number", facet);
	eo.AddEntry("Point number", point);
	throw eo;
}

bool Delaunay::IsOuterFacet(int facet)const
{
	//int PointNum=length-1;
	for (int i = 0; i<3; ++i)
		for (size_t j = 0; j < 3; ++j)
			if (f[static_cast<size_t>(facet)].vertices[static_cast<size_t>(i)] == static_cast<int>(olength + j))
				return true;
	return false;
}

double Delaunay::CalculateRadius(int facet)
{
	const double big = 1e10;
	const double a = cor[static_cast<size_t>(f[static_cast<size_t>(facet)].vertices[0])].distance(cor[static_cast<size_t>(f[static_cast<size_t>(facet)].vertices[1])]);
	const double b = cor[static_cast<size_t>(f[static_cast<size_t>(facet)].vertices[0])].distance(cor[static_cast<size_t>(f[static_cast<size_t>(facet)].vertices[2])]);
	const double c = cor[static_cast<size_t>(f[static_cast<size_t>(facet)].vertices[2])].distance(cor[static_cast<size_t>(f[static_cast<size_t>(facet)].vertices[1])]);
	const double temp1 = b + c - a;
	if (temp1 <= 0)
	{
		if (a > big*b || a>big*c) // Do we have a small edge?
			return 0.5*a;
		else
			return 0.5*(b + c); // we have 3 points on a line
	}
	const double temp2 = c + a - b;
	if (temp2 <= 0)
	{
		if (b > big*a || b > big*c) // Do we have a small edge?
			return 0.5*b;
		else
			return 0.5*(a + c); // we have 3 points on a line
	}
	const double temp3 = b - c + a;
	if (temp3 <= 0)
	{
		if (c > big*b || c > big*a) // Do we have a small edge?
			return 0.5*c;
		else
			return 0.5*(b + a); // we have 3 points on a line
	}
	return a*b*c / sqrt((a + b + c)*temp1*temp2*temp3);
}

void Delaunay::CheckInput()
{
	for (size_t i = 0; i < cor.size(); ++i)
		assert(InCell(cell_points, cor[i]));
}

int Delaunay::GetOriginalIndex(int NewPoint) const
{
	return NewPoint;
}

double Delaunay::GetFacetRadius(int facet) const
{
	return radius[static_cast<size_t>(facet)];
}

void Delaunay::ChangeOlength(int n)
{
	olength = static_cast<size_t>(n);
}

void Delaunay::Changelength(int n)
{
	length = n + 3;
}

vector<Vector2D>& Delaunay::ChangeCor(void)
{
	return cor;
}

const vector<Vector2D>& Delaunay::getCor(void) const
{
	return cor;
}

const facet& Delaunay::get_facet(int index) const
{
	return f[static_cast<size_t>(index)];
}

double Delaunay::get_facet_coordinate(int Facet, int vertice, int dim)
{
	if (dim == 0)
		return cor[static_cast<size_t>(f[static_cast<size_t>(Facet)].vertices[static_cast<size_t>(vertice)])].x;
	else
		return cor[static_cast<size_t>(f[static_cast<size_t>(Facet)].vertices[static_cast<size_t>(vertice)])].y;
}

Vector2D Delaunay::get_point(size_t index) const
{
	return cor[index];
}

double Delaunay::get_cor(int index, int dim) const
{
	if (dim == 0)
		return cor[static_cast<size_t>(index)].x;
	else if (dim == 1)
		return cor[static_cast<size_t>(index)].y;
	else
		throw UniversalError("Error in Delaunay::get_cor. Invalid index");
}

int Delaunay::get_num_facet(void) const
{
	return static_cast<int>(f.size());
}

int Delaunay::get_length(void) const
{
	return length - 3;
}

int Delaunay::get_last_loc(void) const
{
	return last_loc;
}

void Delaunay::set_point(int index, Vector2D p)
{
	cor[static_cast<size_t>(index)] = p;
}

int Delaunay::GetOriginalLength(void) const
{
	return static_cast<int>(olength);
}

vector<Vector2D>& Delaunay::GetMeshPoints(void)
{
	return cor;
}

int Delaunay::GetTotalLength(void)
{
	return static_cast<int>(cor.size());
}

void Delaunay::AddBoundaryPoints(vector<Vector2D> const& points)
{
	int n = static_cast<int>(points.size());
	stack<std::pair<size_t, size_t> > flip_stack;
	//	vector<int> order=HilbertOrder(points,n);
	for (int i = 0; i < n; ++i)
	{
		cor.push_back(points[static_cast<size_t>(i)]);
		add_point(cor.size() - 1,flip_stack);
	}
}

void Delaunay::AddAditionalPoint(Vector2D const& vec)
{
	cor.push_back(vec);
}

int Delaunay::GetCorSize(void)const
{
	return static_cast<int>(cor.size());
}

bool Delaunay::IsTripleOut(int index) const
{
	int counter = 0;
	for (size_t i = 0; i < 3; ++i)
		if (IsOuterFacet(f[static_cast<size_t>(index)].neighbors[static_cast<size_t>(i)]))
			++counter;
	if (counter > 1)
		return true;
	else
		return false;
}

int Delaunay::FindTripleLoc(facet const& fct)const
{
	for (size_t i = 0; i < 3; ++i)
		if (!IsOuterFacet(fct.neighbors[static_cast<size_t>(i)]))
			return static_cast<int>((i + 1) % 3);
	throw UniversalError("Trouble in constructing boundary triangles. No inner neighbor");
}

namespace
{
	bool IsOuterQuick(facet const& f, int olength)
	{
		for (size_t i = 0; i < 3; ++i)
			if (f.vertices[static_cast<size_t>(i)] >= olength)
				return true;
		return false;
	}

	bool IsEdgeFacet(vector<facet> const& facets, facet const& f, int olength)
	{
		int counter = 0;
		for (size_t i = 0; i < 3; ++i)
		{
			if (f.vertices[static_cast<size_t>(i)] >= olength)
				return false;
			if (IsOuterQuick(facets[static_cast<size_t>(f.neighbors[static_cast<size_t>(i)])], olength))
				++counter;
		}
		if (counter > 0)
			return true;
		else
			return false;
	}

	bool CircleSegmentIntersect(Edge const& edge, Vector2D const& center, double R)
	{
		Vector2D AC = center - edge.vertices.first;
		Vector2D AB = edge.vertices.second - edge.vertices.first;
		double d = ScalarProd(AC, AB);
		if (d<0)
		{
			if (abs(AC)>R)
				return false;
			else
				return true;
		}
		double LAB = abs(AB);
		if (d > LAB*LAB)
		{
			if (abs(center - edge.vertices.second) > R)
				return false;
			else
				return true;
		}
		Vector2D closest = edge.vertices.first + AB*d / (LAB*LAB);
		if (abs(center - closest) > R)
			return false;
		else
			return true;
	}
}

vector<int> Delaunay::GetOuterFacets(int start_facet, int real_point, int olength2)
{
	int cur_facet = start_facet;
	vector<int> f_temp, containing_facets;
	f_temp.reserve(static_cast<size_t>(10 * sqrt(1.0*olength2)));
	int point_index = FindPointInFacet(cur_facet, real_point);
	if (IsOuterQuick(f[static_cast<size_t>(f[static_cast<size_t>(cur_facet)].neighbors[static_cast<size_t>(point_index)])], olength2))
	{
		point_index = (point_index + 1) % 3;
		real_point = f[static_cast<size_t>(cur_facet)].vertices[static_cast<size_t>(point_index)];
	}
	if (IsOuterQuick(f[static_cast<size_t>(f[static_cast<size_t>(cur_facet)].neighbors[static_cast<size_t>(point_index)])], olength2))
	{
		point_index = (point_index + 1) % 3;
		real_point = f[static_cast<size_t>(cur_facet)].vertices[static_cast<size_t>(point_index)];
	}
	do
	{
		FindContainingTetras(cur_facet, real_point, containing_facets);
		int old_current = cur_facet;
		for (size_t i = 0; i < containing_facets.size(); ++i)
		{
			if (IsEdgeFacet(f, f[static_cast<size_t>(containing_facets[static_cast<size_t>(i)])], olength2) &&
				containing_facets[static_cast<size_t>(i)] != old_current)
				cur_facet = containing_facets[static_cast<size_t>(i)];
			if (!IsOuterQuick(f[static_cast<size_t>(containing_facets[static_cast<size_t>(i)])], olength2))
				f_temp.push_back(containing_facets[static_cast<size_t>(i)]);
		}
		point_index = (1 + FindPointInFacet(cur_facet, real_point)) % 3;
		if (IsTripleOut(cur_facet))
			point_index = (point_index + 1) % 3;
		real_point = f[static_cast<size_t>(cur_facet)].vertices[static_cast<size_t>(point_index)];
	} while (start_facet != cur_facet);
	sort(f_temp.begin(), f_temp.end());
	f_temp = unique(f_temp);
	return f_temp;
}

vector<vector<int> > Delaunay::FindOuterPoints(vector<Edge> const& edges)
{
	// We add the points in a counter clockwise fashion
	vector<vector<int> > res(edges.size());
	if (olength < 100)
	{
		for (size_t j = 0; j < edges.size(); ++j)
		{
			res[static_cast<size_t>(j)].resize(static_cast<size_t>(olength));
			for (size_t i = 0; i < olength; ++i)
				res[static_cast<size_t>(j)][i] = static_cast<int>(i);
		}
		return res;
	}
	vector<int> res_temp, outer_points, f_temp, f_add(f.size(), 0);
	res_temp.reserve(static_cast<size_t>(20 * sqrt(1.0*static_cast<double>(olength))));
	f_temp.reserve(static_cast<size_t>(10 * sqrt(1.0*static_cast<double>(olength))));
	outer_points.reserve(static_cast<size_t>(10 * sqrt(1.0*static_cast<double>(olength))));
	// Walk to an outer point
	int cur_facet = static_cast<int>(Walk(olength));
	vector<vector<int> > toduplicate(edges.size());
	vector<bool> checked(f.size(), false);
	AddOuterFacets(cur_facet, toduplicate, edges, checked);
	for (size_t i = 0; i < edges.size(); ++i)
	{
		sort(toduplicate[static_cast<size_t>(i)].begin(), toduplicate[static_cast<size_t>(i)].end());
		toduplicate[static_cast<size_t>(i)] = unique(toduplicate[static_cast<size_t>(i)]);
	}
	return toduplicate;
}

void Delaunay::AddRigid(vector<Edge> const& edges,
	vector<vector<int> > &toduplicate)
{
	vector<int> toremove;
	for (size_t i = 0; i < edges.size(); ++i)
	{
		toremove.clear();
		if (toduplicate[i].empty())
			continue;
		vector<Vector2D> toadd;
		toadd.reserve(toduplicate[i].size());
		Vector2D par(Parallel(edges[i]));
		par = par / abs(par);
		for (size_t j = 0; j < toduplicate[i].size(); ++j)
		{
			Vector2D temp = cor[static_cast<size_t>(toduplicate[i][j])] - edges[i].vertices.first;
			temp = 2 * par*ScalarProd(par, temp) - temp + edges[i].vertices.first;
			if (InTriangle(TripleConstRef<Vector2D>
				(cor[static_cast<size_t>(olength)],
					cor[static_cast<size_t>(olength + 1)],
					cor[static_cast<size_t>(olength + 2)]),
				temp))
				toadd.push_back(temp);
			else
				toremove.push_back(static_cast<int>(j));
		}
		RemoveVector(toduplicate[i], toremove);
		vector<int> order = HilbertOrder(toadd, static_cast<int>(toadd.size()));
		ReArrangeVector(toadd, order);
		try
		{
			AddBoundaryPoints(toadd);
		}
		catch (UniversalError &eo)
		{
			eo.AddEntry("Error in AddRigid", 0);
			throw;
		}
		ReArrangeVector(toduplicate[i], order);
	}
}

namespace
{
	vector<Edge> GetCornerEdges(OuterBoundary const* obc)
	{
		const double dx = obc->GetGridBoundary(Right) - obc->GetGridBoundary(Left);
		const double dy = obc->GetGridBoundary(Up) - obc->GetGridBoundary(Down);
		vector<Edge> res;
		const Vector2D RU(obc->GetGridBoundary(Right), obc->GetGridBoundary(Up));
		const Vector2D LU(obc->GetGridBoundary(Left), obc->GetGridBoundary(Up));
		const Vector2D LD(obc->GetGridBoundary(Left), obc->GetGridBoundary(Down));
		const Vector2D RD(obc->GetGridBoundary(Right), obc->GetGridBoundary(Down));
		res.push_back(Edge(RU, Vector2D(dx, 0) + RU, 0, 0));
		res.push_back(Edge(RU, Vector2D(0, dy) + RU, 0, 0));
		res.push_back(Edge(LU, Vector2D(0, dy) + LU, 0, 0));
		res.push_back(Edge(LU, Vector2D(-dx, 0) + LU, 0, 0));
		res.push_back(Edge(LD, Vector2D(-dx, 0) + LD, 0, 0));
		res.push_back(Edge(LD, Vector2D(0, -dy) + LD, 0, 0));
		res.push_back(Edge(RD, Vector2D(0, -dy) + RD, 0, 0));
		res.push_back(Edge(RD, Vector2D(dx, 0) + RD, 0, 0));
		return res;
	}
}

vector<vector<int> > Delaunay::AddPeriodic(OuterBoundary const* obc, vector<Edge> const& edges,
	vector<vector<int> > &toduplicate)
{
	const double dx = obc->GetGridBoundary(Right) - obc->GetGridBoundary(Left);
	const double dy = obc->GetGridBoundary(Up) - obc->GetGridBoundary(Down);
	for (size_t i = 0; i < edges.size(); ++i)
	{
		if (toduplicate[static_cast<size_t>(i)].empty())
			continue;
		Vector2D change;
		switch (i)
		{
		case(0) :
			change.x = -dx;
			break;
		case(1) :
			change.y = -dy;
			break;
		case(2) :
			change.x = dx;
			break;
		case(3) :
			change.y = dy;
			break;
		}
		vector<Vector2D> toadd;
		toadd.reserve(toduplicate[static_cast<size_t>(i)].size());
		//vector<int> pointstemp(toduplicate[static_cast<size_t>(i)].size());
		for (size_t j = 0; j < toduplicate[static_cast<size_t>(i)].size(); ++j)
		{
			toadd.push_back(cor[static_cast<size_t>(toduplicate[static_cast<size_t>(i)][static_cast<size_t>(j)])] + change);
			//	pointstemp[j]=j;
		}
		vector<int> order = HilbertOrder(toadd, static_cast<int>(toadd.size()));
		ReArrangeVector(toadd, order);
		AddBoundaryPoints(toadd);
		ReArrangeVector(toduplicate[static_cast<size_t>(i)], order);
		//toduplicate[i]=pointstemp;
	}
	// Done with sides do corners now
	vector<Edge> corneredges = GetCornerEdges(obc);
	vector<vector<int> > corners(toduplicate.size());
	for (size_t i = 0; i < toduplicate.size(); ++i)
	{
		for (size_t j = 0; j < toduplicate[static_cast<size_t>(i)].size(); ++j)
		{
			const int facet_loc = static_cast<int>(Walk(static_cast<size_t>(toduplicate[static_cast<size_t>(i)][static_cast<size_t>(j)])));
			const Vector2D center = cor[static_cast<size_t>(toduplicate[static_cast<size_t>(i)][static_cast<size_t>(j)])];
			const double R = 2 * GetMaxRadius(toduplicate[static_cast<size_t>(i)][static_cast<size_t>(j)], facet_loc);
			if (CircleSegmentIntersect(corneredges[2 * static_cast<size_t>(i)], center, R))
				corners[static_cast<size_t>(i)].push_back(toduplicate[static_cast<size_t>(i)][static_cast<size_t>(j)]);
			if (CircleSegmentIntersect(corneredges[(2 * static_cast<size_t>(i) + 7) % 8], center, R))
				corners[(static_cast<size_t>(i) + 3) % 4].push_back(toduplicate[static_cast<size_t>(i)][static_cast<size_t>(j)]);
		}
	}
	for (size_t i = 0; i < corners.size(); ++i)
	{
		if (corners[static_cast<size_t>(i)].empty())
			continue;
		sort(corners[static_cast<size_t>(i)].begin(), corners[static_cast<size_t>(i)].end());
		corners[static_cast<size_t>(i)] = unique(corners[static_cast<size_t>(i)]);
		Vector2D change;
		switch (i)
		{
		case(0) :
			change.x = -dx;
			change.y = -dy;
			break;
		case(1) :
			change.y = -dy;
			change.x = dx;
			break;
		case(2) :
			change.x = dx;
			change.y = dy;
			break;
		case(3) :
			change.y = dy;
			change.x = -dx;
			break;
		}
		vector<Vector2D> toadd;
		toadd.reserve(corners[static_cast<size_t>(i)].size());
		//		vector<int> pointstemp(corners[i].size());
		for (size_t j = 0; j < corners[static_cast<size_t>(i)].size(); ++j)
		{
			toadd.push_back(cor[static_cast<size_t>(corners[static_cast<size_t>(i)][static_cast<size_t>(j)])] + change);
			//		pointstemp[j]=j;
		}
		vector<int> order = HilbertOrder(toadd, static_cast<int>(toadd.size()));
		ReArrangeVector(toadd, order);
		AddBoundaryPoints(toadd);
		ReArrangeVector(corners[static_cast<size_t>(i)], order);
		//	corners[i]=pointstemp;
	}
	return corners;
}

void Delaunay::AddHalfPeriodic(OuterBoundary const* obc, vector<Edge> const& edges,
	vector<vector<int> > &toduplicate)
{
	const double dx = obc->GetGridBoundary(Right) - obc->GetGridBoundary(Left);
	//	const double dy=obc->GetGridBoundary(Up)-obc->GetGridBoundary(Down);
	for (size_t i = 0; i < edges.size(); ++i)
	{
		if (toduplicate[static_cast<size_t>(i)].empty())
			continue;
		Vector2D change;
		switch (i)
		{
		case(0) :
			change.x = -dx;
			break;
		case(1) :
			break;
		case(2) :
			change.x = dx;
			break;
		case(3) :
			break;
		}
		vector<Vector2D> toadd;
		toadd.reserve(toduplicate[static_cast<size_t>(i)].size());
		//vector<int> pointstemp(toduplicate[static_cast<size_t>(i)].size());
		Vector2D par(Parallel(edges[static_cast<size_t>(i)]));
		par = par / abs(par);
		for (size_t j = 0; j < toduplicate[static_cast<size_t>(i)].size(); ++j)
		{
			Vector2D temp = cor[static_cast<size_t>(toduplicate[static_cast<size_t>(i)][static_cast<size_t>(j)])];
			if (i % 2 == 1)
			{
				temp -= edges[static_cast<size_t>(i)].vertices.first;
				temp = 2 * par*ScalarProd(par, temp) - temp + edges[static_cast<size_t>(i)].vertices.first;
			}
			toadd.push_back(temp + change);
			//pointstemp[j]=j;
		}
		vector<int> order = HilbertOrder(toadd, static_cast<int>(toadd.size()));
		ReArrangeVector(toadd, order);
		AddBoundaryPoints(toadd);
		ReArrangeVector(toduplicate[static_cast<size_t>(i)], order);
		//toduplicate[i]=pointstemp;
	}
}

vector<vector<int> > Delaunay::BuildBoundary(OuterBoundary const* obc, vector<Edge> const& edges)
{
	vector<vector<int> > toduplicate = FindOuterPoints(edges);
#ifdef RICH_MPI
	OrgIndex.clear();
#endif
	if (obc->GetBoundaryType() == Rectengular)
	{
		AddRigid(edges, toduplicate);
#ifdef RICH_MPI
		for (size_t i = 0; i < toduplicate.size(); ++i)
			for (size_t j = 0; j < toduplicate[i].size(); ++j)
				OrgIndex.push_back(toduplicate[i][j]);
#endif
	}
	else
	{
		if (obc->GetBoundaryType() == Periodic)
		{
			vector<vector<int> > corners = AddPeriodic(obc, edges, toduplicate);
			for (size_t i = 0; i < 4; ++i)
				toduplicate.push_back(corners[static_cast<size_t>(i)]);
		}
		else
		{
			AddHalfPeriodic(obc, edges, toduplicate);
		}
	}
	return toduplicate;
}

Vector2D Delaunay::GetCircleCenter(int index)const
{
	Vector2D center;
	facet const& F = f[static_cast<size_t>(index)];
	double x1 = cor[static_cast<size_t>(F.vertices[0])].x;
	double x2 = cor[static_cast<size_t>(F.vertices[1])].x;
	double x3 = cor[static_cast<size_t>(F.vertices[2])].x;
	double y1 = cor[static_cast<size_t>(F.vertices[0])].y;
	double y2 = cor[static_cast<size_t>(F.vertices[1])].y;
	double y3 = cor[static_cast<size_t>(F.vertices[2])].y;
	// Do we have a case where two point are very close compared to the third?
	double d12 = (x1 - x2)*(x1 - x2) + (y1 - y2)*(y1 - y2);
	double d23 = (x3 - x2)*(x3 - x2) + (y3 - y2)*(y3 - y2);
	double d13 = (x1 - x3)*(x1 - x3) + (y1 - y3)*(y1 - y3);
	int scenario = 0;
	if (d12 < 0.1*(d23 + d13))
		scenario = 1;
	else
		if (d23 < 0.1*(d13 + d12))
			scenario = 3;
		else
			if (d13 < 0.1*(d23 + d12))
				scenario = 2;
	switch (scenario)
	{
	case(0) :
	case(1) :
	case(2) :
	{
		x2 -= x1;
		x3 -= x1;
		y2 -= y1;
		y3 -= y1;
		double d_inv = 1 / (2 * (x2*y3 - y2*x3));
		center.Set((y3*(x2*x2 + y2*y2) - y2*(x3*x3 + y3*y3))*d_inv + x1,
			(-x3*(x2*x2 + y2*y2) + x2*(x3*x3 + y3*y3))*d_inv + y1);
		break;
	}
	case(3) :
	{
		x1 -= x2;
		x3 -= x2;
		y1 -= y2;
		y3 -= y2;
		double d_inv = 1 / (2 * (x3*y1 - y3*x1));
		center.Set((y1*(x3*x3 + y3*y3) - y3*(x1*x1 + y1*y1))*d_inv + x2,
			(x3*(x1*x1 + y1*y1) - x1*(x3*x3 + y3*y3))*d_inv + y2);
		break;
	}
	default:
		throw UniversalError("Unhandled case in switch statement VoronoiMesh::get_center");
	}
	return center;
}

double Delaunay::GetMaxRadius(int point, int startfacet)
{
	double res = 0;
	vector<int> neigh = FindContainingTetras(startfacet, point);
	for (size_t i = 0; i < neigh.size(); ++i)
		res = max(res, radius[static_cast<size_t>(neigh[static_cast<size_t>(i)])]);
	return res;
}

void Delaunay::AddOuterFacets(int tri, vector<vector<int> > &toduplicate,
	vector<Edge> const& edges, vector<bool> &checked)
{
	stack<int> tocheck;
	tocheck.push(tri);
	while (!tocheck.empty())
	{
		int cur_facet = tocheck.top();
		tocheck.pop();
		for (size_t i = 0; i < 3; ++i)
		{
			bool added = false;
			if (checked[static_cast<size_t>(f[static_cast<size_t>(cur_facet)].vertices[static_cast<size_t>(i)])] || (f[static_cast<size_t>(cur_facet)].vertices[static_cast<size_t>(i)] >= static_cast<int>(olength)))
				continue;
			vector<int> neigh = FindContainingTetras(cur_facet, f[static_cast<size_t>(cur_facet)].vertices[static_cast<size_t>(i)]);
			for (size_t k = 0; k < neigh.size(); ++k)
			{
				Vector2D center = GetCircleCenter(neigh[static_cast<size_t>(k)]);
				for (size_t l = 0; l < edges.size(); ++l)
				{
					if (CircleSegmentIntersect(edges[static_cast<size_t>(l)], center, radius[static_cast<size_t>(neigh[static_cast<size_t>(k)])]))
					{
						toduplicate[static_cast<size_t>(l)].push_back(f[static_cast<size_t>(cur_facet)].vertices[static_cast<size_t>(i)]);
						added = true;
					}
				}
			}
			checked[static_cast<size_t>(f[static_cast<size_t>(cur_facet)].vertices[static_cast<size_t>(i)])] = true;
			if (added)
			{
				for (size_t j = 0; j < neigh.size(); ++j)
					tocheck.push(neigh[static_cast<size_t>(j)]);
			}
		}
	}
}

#ifdef RICH_MPI
int Delaunay::findSomeOuterPoint(void)
{
	const size_t cur_facet = Walk(olength);
	for (size_t i = 0; i < 3; ++i) {
		const int candidate = f.at(cur_facet).vertices[i];
		if (candidate < static_cast<int>(olength))
			return candidate;
	}
	assert(false && "something went wrong");
}

namespace
{
	vector<int>
		calc_neighbors_own_edges
		(const Tessellation& t_proc,
			const vector<Edge>& edge_list)
	{
		vector<int> res;
		int rank;
		MPI_Comm_rank(MPI_COMM_WORLD, &rank);
		BOOST_FOREACH(const Edge& edge, edge_list)
		{
			const int other = (edge.neighbors.first + edge.neighbors.second) -	rank;
			if (other < t_proc.GetPointNo()) {
				res.push_back(other);
			}
		}
		return res;
	}

	stack<int> initialise_tocheck
		(const vector<int>& neightemp)
	{
		stack<int> res;
		for (size_t i = 0; i < neightemp.size(); ++i)
			res.push(neightemp[i]);
		return res;
	}

	vector<int> calc_self_intersection
		(const vector<Edge>& edge_list,
			const Circle& circle)
	{
		vector<int> res;
		for (size_t i = 0; i < edge_list.size(); ++i) {
			const Edge& edge = edge_list.at(i);
			if (edge_circle_intersect(edge, circle))
				res.push_back(static_cast<int>(i));
		}
		return res;
	}
}

vector<vector<int> > Delaunay::AddOuterFacetsMPI
(int point,
	vector<vector<int> > &toduplicate,
	vector<int> &neigh,
	vector<bool> &checked,
	Tessellation const &tproc,
	const vector<Edge>& own_edges,
	bool recursive)
{
	int rank;
	MPI_Comm_rank(MPI_COMM_WORLD, &rank);
	vector<vector<int> > res;
	vector<int> vtemp;
	if (!recursive)
		res.resize(own_edges.size());
	stack<int> tocheck = initialise_tocheck
		(FindContainingTetras
			(static_cast<int>(Walk(static_cast<size_t>(point))), point));
	if (recursive)
	{
		vector<int> allouter;
		for (size_t i = 0; i < toduplicate.size(); ++i)
		{
			for (size_t j = 0; j < toduplicate[i].size(); ++j)
			{
				vector<int> temp = FindContainingTetras
					(static_cast<int>(Walk(static_cast<size_t>(toduplicate[i][j]))), toduplicate[i][j]);
				for (size_t k = 0; k < temp.size(); ++k)
					allouter.push_back(temp[k]);
			}
		}
		sort(allouter.begin(), allouter.end());
		allouter = unique(allouter);
		for (size_t i = 0; i < allouter.size(); ++i)
			tocheck.push(allouter[i]);
	}
	while (!tocheck.empty())
	{
		int cur_facet = tocheck.top();
		tocheck.pop();
		for (size_t i = 0; i < 3; ++i)
		{
			bool added = false;
			int max_neigh = 0;
			if (f[static_cast<size_t>(cur_facet)].vertices[i] >=
				static_cast<int>(olength))
				continue;
			if (checked[static_cast<size_t>
				(f[static_cast<size_t>(cur_facet)].vertices[i])])
				continue;
			vector<int> neighs = FindContainingTetras(cur_facet, f[static_cast<size_t>(cur_facet)].vertices[i]);
			for (size_t k = 0; k < neighs.size(); ++k)
			{
				Circle circ(GetCircleCenter(neighs[k]), radius[static_cast<size_t>(neighs[k])]);
				vector<int> cputosendto;
				if (recursive)
					find_affected_cells_recursive(tproc,rank, circ, cputosendto);
				else
					cputosendto = find_affected_cells
					(tproc, rank, circ,vtemp);
				sort(cputosendto.begin(), cputosendto.end());
				cputosendto = unique(cputosendto);

				RemoveVal(cputosendto,rank);
				if (!recursive)
				{
					const vector<int> self_intersection = calc_self_intersection(own_edges, circ);
					if (!self_intersection.empty())
						added = true;
					BOOST_FOREACH(int sindex, self_intersection)
						res[static_cast<size_t>(sindex)].push_back(f[static_cast<size_t>(cur_facet)].vertices[i]);
				}
				else
				{
					for (size_t jj = 0; jj < 3; ++jj)
						max_neigh = max(max_neigh, f[static_cast<size_t>(neighs[k])].vertices[jj]);
				}
				if (!cputosendto.empty())
				{
					added = true;
					for (size_t j = 0; j < cputosendto.size(); ++j)
					{
						size_t index = static_cast<size_t>(find(neigh.begin(), neigh.end(), cputosendto[j])
							- neigh.begin());
						if (index < neigh.size())
							toduplicate.at(index).push_back(f[static_cast<size_t>(cur_facet)].vertices[i]);
						else {
							neigh.push_back(cputosendto[j]);
							toduplicate.push_back
								(vector<int>(1, f[static_cast<size_t>(cur_facet)].vertices[i]));
						}
					}
				}
			}
			checked[static_cast<size_t>(f[static_cast<size_t>(cur_facet)].vertices[i])] = true;
			if (added||(recursive&&static_cast<size_t>(max_neigh)>=olength))
			{
				for (size_t j = 0; j < neighs.size(); ++j)
				{
					//if (!IsOuterFacet(neighs[j]))
						tocheck.push(neighs[j]);
				}
			}
		}
	}
	return res;
}

pair<vector<vector<int> >, vector<vector<int> > >
Delaunay::findOuterPoints
(const Tessellation& t_proc,
	const vector<Edge>& edge_list,
	const vector<Edge>& box_edges,
	vector<vector<int> > &NghostIndex)
{
	vector<int> neighbors_own_edges =
		calc_neighbors_own_edges(t_proc, edge_list);
	const size_t some_outer_point = findSomeOuterPoint();

	vector<vector<int> > to_duplicate(neighbors_own_edges.size());
	vector<bool> checked(olength, false);
	vector<vector<int> > self_points =
		AddOuterFacetsMPI
		(static_cast<int>(some_outer_point),
			to_duplicate, // indices of points to send
			neighbors_own_edges, // Rank of processes to send to
			checked,
			t_proc,
			box_edges);
	BOOST_FOREACH(vector<int>& line, to_duplicate)
	{
		sort(line.begin(), line.end());
		line = unique(line);
	}

	// Communication
	vector<vector<Vector2D> > incoming(neighbors_own_edges.size());
	vector<MPI_Request> req(neighbors_own_edges.size());
	vector<vector<double> > tosend(neighbors_own_edges.size());
	double dtemp = 0;
	for (size_t i = 0; i < neighbors_own_edges.size(); ++i)
	{
		const int dest = neighbors_own_edges.at(i);
		tosend[i] = list_serialize(VectorValues(cor, to_duplicate[i]));
		int size = static_cast<int>(tosend[i].size());
		if (size == 0)
			MPI_Isend(&dtemp, 1, MPI_DOUBLE,dest, 1, MPI_COMM_WORLD, &req[i]);
		else
		{
			if (size < 2)
				throw UniversalError("Wrong send size");
			MPI_Isend(&tosend[i][0], size, MPI_DOUBLE, dest, 0, MPI_COMM_WORLD, &req[i]);
		}
	}
	for (size_t i = 0; i < neighbors_own_edges.size(); ++i)
	{
		vector<double> temprecv;
		MPI_Status status;
		MPI_Probe(MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, &status);
		int count;
		MPI_Get_count(&status, MPI_DOUBLE, &count);
		temprecv.resize(static_cast<size_t>(max(count, 1)));
		MPI_Recv(&temprecv[0], count, MPI_DOUBLE, status.MPI_SOURCE, status.MPI_TAG, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
		if (status.MPI_TAG == 0)
		{
			size_t location = static_cast<size_t>(std::find(neighbors_own_edges.begin(), neighbors_own_edges.end(),
				status.MPI_SOURCE) - neighbors_own_edges.begin());
			if (location >= neighbors_own_edges.size())
				throw UniversalError("Bad location in mpi exchange");
			try
			{
				incoming[location] = list_unserialize(temprecv, cor[0]);
			}
			catch (UniversalError &eo)
			{
				eo.AddEntry("Error in first send in triangulation", 0.0);
				throw;
			}
		}
		else
			if (status.MPI_TAG != 1)
				throw UniversalError("Wrong mpi tag");
	}
	MPI_Waitall(static_cast<int>(req.size()), &req[0], MPI_STATUSES_IGNORE);
	// Incorporate points recieved into triangulation
	BOOST_FOREACH(vector<int> &line, self_points)
	{
		sort(line.begin(), line.end());
		line = unique(line);
	}
	AddRigid(box_edges,self_points);
	for (size_t i = 0; i < self_points.size(); ++i)
		for (size_t j = 0; j < self_points[i].size(); ++j)
			OrgIndex.push_back(self_points[i][j]);

	NghostIndex.clear();
	NghostIndex.resize(incoming.size());
	for (size_t i = 0; i < incoming.size(); ++i)
	{
		for (size_t j = 0; j < incoming.at(i).size(); ++j)
		{
			OrgIndex.push_back(static_cast<int>(cor.size() + j));
			NghostIndex[i].push_back(static_cast<int>(cor.size() + j));
		}
		AddBoundaryPoints(incoming.at(i));
	}
	MPI_Barrier(MPI_COMM_WORLD);
	return pair<vector<vector<int> >, vector<vector<int> > >
		(to_duplicate, self_points);
}

namespace {
	template<class T> bool is_in
		(const T& t,
			const vector<T>& v)
	{
		BOOST_FOREACH(const T&m, v) {
			if (t == m)
				return true;
		}
		return false;
	}
}

vector<vector<int> >
Delaunay::boundary_intersection_check
(const vector<Edge>& edges,
	const vector<vector<int> >& to_duplicate)
{
	vector<vector<int> > res;
	res.reserve(edges.size());
	BOOST_FOREACH(const Edge& edge, edges) {
		res.push_back(vector<int>());
		BOOST_FOREACH(const vector<int>& line, to_duplicate) {
			BOOST_FOREACH(const int index, line) {
				const Circle circle
					(GetCircleCenter(index), radius[index]);
				if (edge_circle_intersect(edge, circle))
					res.back().push_back(index);
			}
		}
	}
	return res;
}

pair<vector<vector<int> >, vector<int> > Delaunay::FindOuterPoints2
(const Tessellation& t_proc,
	const vector<Edge>& edge_list,
	vector<vector<int> > &to_duplicate,
	vector<vector<int> >& self_points,
	const vector<Edge>& box_edges,
	vector<vector<int> > &NghostIndex)
{
	const vector<vector<int> > boundary_points =
		boundary_intersection_check(box_edges, to_duplicate);
	vector<vector<int> > real_boundary_points
		(box_edges.size());
	for (size_t i = 0; i < boundary_points.size(); ++i)
	{
		sort(self_points.at(i).begin(), self_points.at(i).end());
		BOOST_FOREACH(int bp, boundary_points.at(i))
		{
			if (!binary_search(self_points.at(i).begin(),
				self_points.at(i).end(),
				bp))
			{
				real_boundary_points.at(i).push_back(bp);
			}
		}
		sort(real_boundary_points.at(i).begin(),
			real_boundary_points.at(i).end());
		real_boundary_points.at(i) = unique(real_boundary_points.at(i));
	}

	vector<vector<int> > to_duplicate_2 = to_duplicate;
	BOOST_FOREACH(vector<int>& line, to_duplicate_2)
		sort(line.begin(), line.end());
	vector<int> neighbors_own_edges =
		calc_neighbors_own_edges(t_proc, edge_list);
	vector<int> old_neighbors = neighbors_own_edges;
	assert(!to_duplicate.empty());
	BOOST_FOREACH(const vector<int>& line, to_duplicate)
		assert(!line.empty());
	vector<bool> checked(olength, false);
	const size_t some_outer_point = to_duplicate[0][0];
	AddOuterFacetsMPI
		(static_cast<int>(some_outer_point),
			to_duplicate, // indices of points to send
			neighbors_own_edges, // Rank of processes to send to
			checked,
			t_proc,
			edge_list,
			true); // recursive

	// Communication
	int wsize;
	MPI_Comm_size(MPI_COMM_WORLD, &wsize);
	vector<int> totalk(static_cast<size_t>(wsize), 0);
	vector<int> scounts(totalk.size(), 1);
	for (size_t i = 0; i < neighbors_own_edges.size(); ++i)
		totalk[neighbors_own_edges[i]] = 1;
	int nrecv;
	MPI_Reduce_scatter(&totalk[0], &nrecv, &scounts[0], MPI_INT, MPI_SUM,
		MPI_COMM_WORLD);

	vector<MPI_Request> req(neighbors_own_edges.size());
	for (size_t i = 0; i < neighbors_own_edges.size(); ++i)
		MPI_Isend(&wsize, 1, MPI_INT, neighbors_own_edges[i], 3, MPI_COMM_WORLD, &req[i]);
	vector<int> talkwithme;
	for (int i = 0; i < nrecv; ++i)
	{
		MPI_Status status;
		MPI_Recv(&wsize, 1, MPI_INT, MPI_ANY_SOURCE, 3, MPI_COMM_WORLD, &status);
		talkwithme.push_back(status.MPI_SOURCE);
	}
	vector<size_t> indices;
	for (size_t i = 0; i < neighbors_own_edges.size(); ++i)
	{
		if (is_in(neighbors_own_edges[i],talkwithme))
			indices.push_back(i);
	}

	// Symmetrisation
	neighbors_own_edges =
		VectorValues
		(neighbors_own_edges,
			indices);
	to_duplicate =
		VectorValues
		(to_duplicate,
			indices);
	// Get rid of duplicate points
	for (size_t i = 0; i < to_duplicate.size(); ++i)
	{
		sort(to_duplicate[i].begin(), to_duplicate[i].end());
		to_duplicate[i] = unique(to_duplicate[i]);
	}
	vector<vector<int> > messages(to_duplicate.size());
	for (size_t i = 0; i < to_duplicate.size(); ++i) {
		const vector<int>::const_iterator it =
			find(old_neighbors.begin(),
				old_neighbors.end(),
				neighbors_own_edges.at(i));
		for (size_t j = 0; j < to_duplicate.at(i).size(); ++j) {

			if (it != old_neighbors.end()) {
				const size_t my_index = static_cast<size_t>(it - old_neighbors.begin());
				if (!binary_search
					(to_duplicate_2.at(my_index).begin(),
						to_duplicate_2.at(my_index).end(),
						to_duplicate.at(i).at(j)))
					messages.at(i).push_back(to_duplicate.at(i).at(j));
			}
			else
				messages.at(i).push_back(to_duplicate.at(i).at(j));
		}
	}
	MPI_Waitall(static_cast<int>(neighbors_own_edges.size()), &req[0], MPI_STATUSES_IGNORE);
	MPI_Barrier(MPI_COMM_WORLD);
	// Point exchange
	req.clear();
	req.resize(neighbors_own_edges.size());
	double dtemp = 0;
	vector<vector<Vector2D> > incoming(neighbors_own_edges.size());
	vector<vector<double> > tosend(neighbors_own_edges.size());
	for (size_t i = 0; i < neighbors_own_edges.size(); ++i)
	{
		const int dest = neighbors_own_edges.at(i);
		tosend[i] = list_serialize(VectorValues(cor, messages.at(i)));
		int size = static_cast<int>(tosend[i].size());
		if (size > 0)
		{
			if (size < 2)
				throw UniversalError("Wrong send size");
			MPI_Isend(&tosend[i][0], size, MPI_DOUBLE, dest, 0, MPI_COMM_WORLD, &req[i]);
		}
		else
			MPI_Isend(&dtemp, 1, MPI_DOUBLE, dest,1, MPI_COMM_WORLD, &req[i]);
	}
	for (size_t i = 0; i < neighbors_own_edges.size(); ++i)
	{
		vector<double> temprecv;
		MPI_Status status;
		MPI_Probe(MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, &status);
		int count;
		MPI_Get_count(&status, MPI_DOUBLE, &count);
		temprecv.resize(static_cast<size_t>(count));
		MPI_Recv(&temprecv[0], count, MPI_DOUBLE, status.MPI_SOURCE, status.MPI_TAG, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
		if (status.MPI_TAG == 0)
		{
			size_t location = static_cast<size_t>(std::find(neighbors_own_edges.begin(), neighbors_own_edges.end(), status.MPI_SOURCE) -
				neighbors_own_edges.begin());
			if (location >= neighbors_own_edges.size())
				throw UniversalError("Bad location in mpi exchange");
			try
			{
				incoming[location] = list_unserialize(temprecv, cor[0]);
			}
			catch (UniversalError &eo)
			{
				eo.AddEntry("Error in second send in triangulation", 0.0);
				eo.AddEntry("Mpi status", static_cast<double>(status.MPI_SOURCE));
				eo.AddEntry("Mpi tag", static_cast<double>(status.MPI_TAG));
				eo.AddEntry("Mpi count", static_cast<double>(count));
				throw;
			}
		}
		else
			if (status.MPI_TAG != 1)
				throw UniversalError("Wrong mpi tag");
	}
	MPI_Waitall(static_cast<int>(req.size()), &req[0], MPI_STATUSES_IGNORE);
	MPI_Barrier(MPI_COMM_WORLD);
	// Incorporate points recieved into triangulation
	NghostIndex.resize(incoming.size());
	for (size_t i = 0; i < incoming.size(); ++i)
	{
		for (size_t j = 0; j < incoming.at(i).size(); ++j)
		{
			NghostIndex[i].push_back(static_cast<int>(cor.size() + j));
			OrgIndex.push_back(static_cast<int>(cor.size() + j));
		}
		AddBoundaryPoints(incoming.at(i));
	}

	AddRigid(box_edges,real_boundary_points);
	for (size_t i = 0; i < real_boundary_points.size(); ++i)
		for (size_t j = 0; j < real_boundary_points[i].size(); ++j)
			OrgIndex.push_back(real_boundary_points[i][j]);

	for (size_t i = 0; i < self_points.size(); ++i)
		if (!real_boundary_points[i].empty())
			self_points[i].insert(self_points[i].end(), real_boundary_points[i].begin(),
				real_boundary_points[i].end());

	return  pair<vector<vector<int> >,vector<int> > (to_duplicate,neighbors_own_edges);
}

pair<vector<vector<int> >, vector<int> > Delaunay::BuildBoundary
(OuterBoundary const* obc,
	Tessellation const& tproc,
	vector<vector<int> >& Nghost)
{
	vector<Edge> edges;
	OrgIndex.clear();
	int rank;
	MPI_Comm_rank(MPI_COMM_WORLD, &rank);
	vector<int> edge_index = tproc.GetCellEdges(rank);
	for (size_t i = 0; i < edge_index.size(); ++i)
		edges.push_back(tproc.GetEdge(edge_index[i]));
	vector<Edge> box_edges = obc->GetBoxEdges();
	pair<vector<vector<int> >, vector<vector<int> > > to_duplicate =
		findOuterPoints(tproc, edges, box_edges, Nghost);
	return FindOuterPoints2(tproc,edges,to_duplicate.first, to_duplicate.second,box_edges, Nghost);
}

int Delaunay::GetOrgIndex(int index)const
{
	if (index < static_cast<int>(olength))
		return static_cast<int>(olength);
	else
		return OrgIndex.at(index - 3 - static_cast<int>(olength));
}

#endif // RICH_MPI


// This file is distributed under the MIT license.
// See the LICENSE file for details.

#include <common/config.h>

#include <algorithm>
#include <cassert>
#include <chrono>
#include <cmath>
#include <exception>
#include <fstream>
#include <future>
#include <iomanip>
#include <iostream>
#include <istream>
#include <iterator>
#include <map>
#include <memory>
#include <mutex>
#include <new>
#include <ostream>
#include <set>
#include <sstream>
#include <string>
#include <thread>

#include <boost/filesystem.hpp>

#include <imgui.h>

#include <Support/CmdLine.h>
#include <Support/CmdLineUtil.h>

#include <visionaray/gl/bvh_outline_renderer.h>
#include <visionaray/gl/debug_callback.h>
#include <visionaray/math/math.h>
#include <visionaray/texture/texture.h>
#include <visionaray/aligned_vector.h>
#include <visionaray/area_light.h>
#include <visionaray/bvh.h>
#include <visionaray/generic_material.h>
#include <visionaray/kernels.h>
#include <visionaray/material.h>
#include <visionaray/pinhole_camera.h>
#include <visionaray/point_light.h>
#include <visionaray/scheduler.h>
#include <visionaray/thin_lens_camera.h>

#if defined(__INTEL_COMPILER) || defined(__MINGW32__) || defined(__MINGW64__)
#include <visionaray/detail/tbb_sched.h>
#endif

#include <common/manip/arcball_manipulator.h>
#include <common/manip/pan_manipulator.h>
#include <common/manip/zoom_manipulator.h>
#include <common/make_materials.h>
#include <common/model.h>
#include <common/sg.h>
#include <common/timer.h>
#include <common/viewer_glut.h>

#ifdef __CUDACC__
#include <common/cuda.h>
#endif

#if VSNRAY_COMMON_HAVE_PTEX
#include <common/ptex.h>
#endif

#include "call_kernel.h"
#include "host_device_rt.h"
#include "render.h"

using namespace visionaray;

using viewer_type = viewer_glut;

//-------------------------------------------------------------------------------------------------
// Renderer, stores state, geometry, normals, ...
//

struct renderer : viewer_type
{
    using primitive_type            = model::triangle_type;
    using normal_type               = model::normal_type;
    using tex_coord_type            = model::tex_coord_type;
    using host_bvh_type             = index_bvh<primitive_type>;
#ifdef __CUDACC__
    using device_bvh_type           = cuda_index_bvh<primitive_type>;
    using device_tex_type           = cuda_texture<vector<4, unorm<8>>, 2>;
    using device_tex_ref_type       = typename device_tex_type::ref_type;
#endif

    enum bvh_build_strategy
    {
        Binned = 0,  // Binned SAH builder, no spatial splits
        Split        // Split BVH, also binned and with SAH
    };

    enum texture_format { Ptex, UV };

    renderer()
        : viewer_type(800, 800, "Visionaray Viewer")
        , host_sched(std::thread::hardware_concurrency())
        , rt(
            host_device_rt::CPU,
            true /* double buffering */,
            true /* direct rendering */,
            host_device_rt::SRGB
            )
#ifdef __CUDACC__
        , device_sched(8, 8)
#endif
        , mouse_pos(0)
    {
        using namespace support;

        add_cmdline_option( cl::makeOption<std::set<std::string>&>(
            cl::Parser<>(),
            "filenames",
            cl::Desc("Input files in wavefront obj format"),
            cl::Positional,
            cl::OneOrMore,
            cl::init(filenames)
            ) );

        add_cmdline_option( cl::makeOption<std::string&>(
            cl::Parser<>(),
            "camera",
            cl::Desc("Text file with camera parameters"),
            cl::ArgRequired,
            cl::init(this->initial_camera)
            ) );

        add_cmdline_option( cl::makeOption<algorithm&>({
                { "simple",             Simple,         "Simple ray casting kernel" },
                { "whitted",            Whitted,        "Whitted style ray tracing kernel" },
                { "pathtracing",        Pathtracing,    "Pathtracing global illumination kernel" }
            },
            "algorithm",
            cl::Desc("Rendering algorithm"),
            cl::ArgRequired,
            cl::init(this->algo)
            ) );

        add_cmdline_option( cl::makeOption<bvh_build_strategy&>({
                { "default",            Binned,         "Binned SAH" },
                { "split",              Split,          "Binned SAH with spatial splits" }
            },
            "bvh",
            cl::Desc("BVH build strategy"),
            cl::ArgRequired,
            cl::init(this->builder)
            ) );

        add_cmdline_option( cl::makeOption<unsigned&>({
                { "1",      1,      "1x supersampling" },
                { "2",      2,      "2x supersampling" },
                { "4",      4,      "4x supersampling" },
                { "8",      8,      "8x supersampling" }
            },
            "ssaa",
            cl::Desc("Supersampling anti-aliasing factor"),
            cl::ArgRequired,
            cl::init(this->ssaa_samples)
            ) );

        add_cmdline_option( cl::makeOption<bool&>(
            cl::Parser<>(),
            "headlight",
            cl::Desc("Activate headlight"),
            cl::ArgRequired,
            cl::init(this->use_headlight)
            ) );

        add_cmdline_option( cl::makeOption<bool&>(
            cl::Parser<>(),
            "dof",
            cl::Desc("Activate depth of field"),
            cl::ArgRequired,
            cl::init(this->use_dof)
            ) );

        add_cmdline_option( cl::makeOption<unsigned&>(
            cl::Parser<>(),
            "bounces",
            cl::Desc("Number of bounces for recursive ray tracing"),
            cl::ArgRequired,
            cl::init(this->bounces)
            ) );

        add_cmdline_option( cl::makeOption<vec3&, cl::ScalarType>(
            [&](StringRef name, StringRef /*arg*/, vec3& value)
            {
                cl::Parser<>()(name + "-r", cmd_line_inst().bump(), value.x);
                cl::Parser<>()(name + "-g", cmd_line_inst().bump(), value.y);
                cl::Parser<>()(name + "-b", cmd_line_inst().bump(), value.z);
            },
            "ambient",
            cl::Desc("Ambient color"),
            cl::ArgDisallowed,
            cl::init(this->ambient)
            ) );

        add_cmdline_option( cl::makeOption<host_device_rt::color_space_type&>({
                { "rgb",  host_device_rt::RGB,  "RGB color space for display" },
                { "srgb", host_device_rt::SRGB, "sRGB color space for display" },
            },
            "colorspace",
            cl::Desc("Color space"),
            cl::ArgRequired,
            cl::init(rt.color_space())
            ) );

#ifdef __CUDACC__
        add_cmdline_option( cl::makeOption<host_device_rt::mode_type&>({
                { "cpu", host_device_rt::CPU, "Rendering on the CPU" },
                { "gpu", host_device_rt::GPU, "Rendering on the GPU" },
            },
            "device",
            cl::Desc("Rendering device"),
            cl::ArgRequired,
            cl::init(rt.mode())
            ) );
#endif
    }

    int                                         w               = 800;
    int                                         h               = 800;
    unsigned                                    frame_num       = 0;
    unsigned                                    bounces         = 0;
    unsigned                                    ssaa_samples    = 1;
    algorithm                                   algo            = Simple;
    bvh_build_strategy                          builder         = Binned;
    bool                                        use_headlight   = true;
    bool                                        use_dof         = false;
    bool                                        show_hud        = true;
    bool                                        show_hud_ext    = true;
    bool                                        show_bvh        = false;

    std::set<std::string>                       filenames;
    std::string                                 initial_camera;

    model                                       mod;
    vec3                                        ambient         = vec3(-1.0f);

    index_bvh<host_bvh_type::bvh_inst>          host_top_level_bvh;
    aligned_vector<host_bvh_type>               host_bvhs;
    aligned_vector<host_bvh_type::bvh_inst>     host_instances;
    aligned_vector<plastic<float>>              plastic_materials;
    aligned_vector<generic_material_t>          generic_materials;
    aligned_vector<point_light<float>>          point_lights;
    aligned_vector<area_light<float,
                   basic_triangle<3, float>>>   area_lights;
#if VSNRAY_COMMON_HAVE_PTEX
    aligned_vector<ptex::face_id_t>             ptex_tex_coords;
    aligned_vector<ptex::texture>               ptex_textures;
#endif
#ifdef __CUDACC__
    device_bvh_type                             device_bvh;
    thrust::device_vector<normal_type>          device_geometric_normals;
    thrust::device_vector<normal_type>          device_shading_normals;
    thrust::device_vector<tex_coord_type>       device_tex_coords;
    thrust::device_vector<plastic<float>>       device_plastic_materials;
    thrust::device_vector<generic_material_t>   device_generic_materials;
    std::map<std::string, device_tex_type>      device_texture_map;
    thrust::device_vector<device_tex_ref_type>  device_textures;
#endif

    host_sched_t<ray_type_cpu>                  host_sched;
    host_device_rt                              rt;
#ifdef __CUDACC__
    cuda_sched<ray_type_gpu>                    device_sched;
#endif
    thin_lens_camera                            cam;

    std::shared_ptr<visionaray::texture<vec4, 2>>
                                                environment_map = nullptr;

    // List of cameras, e.g. read from scene graph
    aligned_vector<std::pair<
            std::string, thin_lens_camera>>     cameras;

    mouse::pos                                  mouse_pos;

    texture_format                              tex_format = UV;

    visionaray::frame_counter                   counter;
    double                                      last_frame_time = 0.0;
    gl::bvh_outline_renderer                    outlines;
    gl::debug_callback                          gl_debug_callback;

    bool                                        render_async  = false;
    std::future<void>                           render_future;
    std::mutex                                  display_mutex;

    void build_bvhs();

protected:

    void on_close();
    void on_display();
    void on_key_press(visionaray::key_event const& event);
    void on_mouse_move(visionaray::mouse_event const& event);
    void on_resize(int w, int h);

private:

    void load_camera(std::string filename);
    void clear_frame();
    void render_hud();
    void render_impl();

};

//-------------------------------------------------------------------------------------------------
// I/O utility for camera lookat only - not fit for the general case!
//

std::istream& operator>>(std::istream& in, pinhole_camera& cam)
{
    vec3 eye;
    vec3 center;
    vec3 up;

    in >> eye >> std::ws >> center >> std::ws >> up >> std::ws;
    cam.look_at(eye, center, up);

    return in;
}

std::ostream& operator<<(std::ostream& out, pinhole_camera const& cam)
{
    out << cam.eye() << '\n';
    out << cam.center() << '\n';
    out << cam.up() << '\n';
    return out;
}

//-------------------------------------------------------------------------------------------------
// Reset triangle mesh flags to 0
//

struct reset_flags_visitor : sg::node_visitor
{
    using node_visitor::apply;

    void apply(sg::surface_properties& sp)
    {
        sp.flags() = 0;

        node_visitor::apply(sp);
    }

    void apply(sg::triangle_mesh& tm)
    {
        tm.flags() = 0;

        node_visitor::apply(tm);
    }
};

//-------------------------------------------------------------------------------------------------
// Traverse the scene graph to construct geometry, materials and BVH instances
//

struct build_bvhs_visitor : sg::node_visitor
{
    using node_visitor::apply;

    build_bvhs_visitor(
            aligned_vector<renderer::host_bvh_type>& bvhs,
            aligned_vector<size_t>& instance_indices,
            aligned_vector<mat4>& instance_transforms,
            aligned_vector<vec3>& shading_normals,
            aligned_vector<vec3>& geometric_normals,
            aligned_vector<vec2>& tex_coords,
#if VSNRAY_COMMON_HAVE_PTEX
            aligned_vector<ptex::face_id_t>& face_ids,
#endif
            aligned_vector<std::pair<std::string, thin_lens_camera>>& cameras
            )
        : bvhs_(bvhs)
        , instance_indices_(instance_indices)
        , instance_transforms_(instance_transforms)
        , shading_normals_(shading_normals)
        , geometric_normals_(geometric_normals)
        , tex_coords_(tex_coords)
#if VSNRAY_COMMON_HAVE_PTEX
        , face_ids_(face_ids)
#endif
        , cameras_(cameras)
        , environment_map(nullptr)
    {
    }

    void apply(sg::camera& c)
    {
        cameras_.push_back(std::make_pair(c.name(), static_cast<thin_lens_camera>(c)));

        node_visitor::apply(c);
    }

    void apply(sg::environment_light& el)
    {
        auto tex = std::dynamic_pointer_cast<sg::texture2d<vec4>>(el.texture());

        if (tex != nullptr)
        {
            environment_map = std::make_shared<visionaray::texture<vec4, 2>>(tex->width(), tex->height());
            environment_map->set_address_mode(tex->get_address_mode());
            environment_map->set_filter_mode(tex->get_filter_mode());
            environment_map->reset(tex->data());
        }

        node_visitor::apply(el);
    }

    void apply(sg::transform& t)
    {
        mat4 prev = current_transform_;

        current_transform_ = current_transform_ * t.matrix();

        node_visitor::apply(t);

        current_transform_ = prev;
    }

    void apply(sg::surface_properties& sp)
    {
        unsigned prev = current_geom_id_;

        if (sp.flags() == 0 && sp.material() && sp.textures().size() > 0)
        {
            std::shared_ptr<sg::material> material = sp.material();
            std::shared_ptr<sg::texture> texture = sp.textures()[0];

            auto surf = std::make_pair(material, texture);

            auto it = std::find(surfaces.begin(), surfaces.end(), surf);
            if (it == surfaces.end())
            {
                current_geom_id_ = static_cast<unsigned>(surfaces.size());
                surfaces.push_back(surf);
            }
            else
            {
                current_geom_id_ = static_cast<unsigned>(std::distance(surfaces.begin(), it));
            }

            sp.flags() = ~sp.flags();
        }

        node_visitor::apply(sp);

        current_geom_id_ = prev;
    }

    void apply(sg::triangle_mesh& tm)
    {
        if (tm.flags() == 0 && tm.vertices.size() > 0)
        {
            assert(tm.vertices.size() % 3 == 0);

            aligned_vector<basic_triangle<3, float>> triangles(tm.vertices.size() / 3);

            size_t first_geometric_normal = geometric_normals_.size();
            geometric_normals_.resize(geometric_normals_.size() + tm.vertices.size() / 3);

            shading_normals_.insert(shading_normals_.end(), tm.normals.begin(), tm.normals.end());

            tex_coords_.insert(tex_coords_.end(), tm.tex_coords.begin(), tm.tex_coords.end());

#if VSNRAY_COMMON_HAVE_PTEX
            face_ids_.insert(face_ids_.end(), tm.face_ids.begin(), tm.face_ids.end());
#endif

            for (size_t i = 0; i < tm.vertices.size(); i += 3)
            {
                vec3 v1 = tm.vertices[i];
                vec3 v2 = tm.vertices[i + 1];
                vec3 v3 = tm.vertices[i + 2];

                basic_triangle<3, float> tri(v1, v2 - v1, v3 - v1);
                tri.prim_id = current_prim_id_++;
                tri.geom_id = current_geom_id_;
                triangles[i / 3] = tri;

                vec3 gn = normalize(cross(v2 - v1, v3 - v1));

                geometric_normals_[first_geometric_normal + i / 3] = gn;
            }

            // Build single bvh
            bvhs_.emplace_back(build<renderer::host_bvh_type>(
                    triangles.data(),
                    triangles.size(),
                    false//builder == Split
                    ));

            tm.flags() = ~(bvhs_.size() - 1);
        }

        instance_indices_.push_back(~tm.flags());
        instance_transforms_.push_back(current_transform_);

        node_visitor::apply(tm);
    }

    // List of surface properties to derive geom_ids from
    std::vector<std::pair<std::shared_ptr<sg::material>, std::shared_ptr<sg::texture>>> surfaces;

    // Current transform along the path
    mat4 current_transform_ = mat4::identity();

    // Storage bvhs
    aligned_vector<renderer::host_bvh_type>& bvhs_;

    // Indices to construct instances from
    aligned_vector<size_t>& instance_indices_;

    // Transforms to construct instances from
    aligned_vector<mat4>& instance_transforms_;

    // Shading normals
    aligned_vector<vec3>& shading_normals_;

    // Geometric normals
    aligned_vector<vec3>& geometric_normals_;

    // Texture coordinates
    aligned_vector<vec2>& tex_coords_;

#if VSNRAY_COMMON_HAVE_PTEX
    // Ptex face ids
    aligned_vector<ptex::face_id_t>& face_ids_;
#endif

    // Cameras
    aligned_vector<std::pair<std::string, thin_lens_camera>>& cameras_;

    // Environment map
    std::shared_ptr<visionaray::texture<vec4, 2>> environment_map;

    // Assign consecutive prim ids
    unsigned current_prim_id_ = 0;

    // Assign consecutive geom ids for each encountered material
    unsigned current_geom_id_ = 0;

    // Index into the bvh list
    unsigned current_bvh_index_ = 0;

    // Index into the instance list
    unsigned current_instance_index_ = 0;

};

//-------------------------------------------------------------------------------------------------
// Build bvhs
//

void renderer::build_bvhs()
{
//  timer t;

    std::cout << "Creating BVH...\n";

    if (mod.scene_graph == nullptr)
    {
        // Single BVH
        host_bvhs.resize(1);
        host_bvhs[0] = build<host_bvh_type>(
                mod.primitives.data(),
                mod.primitives.size(),
                builder == Split
                );
    }
    else
    {
        reset_flags_visitor reset_visitor;
        mod.scene_graph->accept(reset_visitor);

        aligned_vector<size_t> instance_indices;
        aligned_vector<mat4> instance_transforms;

        build_bvhs_visitor build_visitor(
                host_bvhs,
                instance_indices,
                instance_transforms,
                mod.shading_normals, // TODO!!!
                mod.geometric_normals,
                mod.tex_coords,
#if VSNRAY_COMMON_HAVE_PTEX
                ptex_tex_coords,
#endif
                cameras
                );
        mod.scene_graph->accept(build_visitor);

        host_instances.resize(instance_indices.size());
        for (size_t i = 0; i < instance_indices.size(); ++i)
        {
            size_t index = instance_indices[i];
            host_instances[i] = host_bvhs[index].inst(instance_transforms[i]);
        }

        host_top_level_bvh = build<index_bvh<host_bvh_type::bvh_inst>>(
                host_instances.data(),
                host_instances.size(),
                false
                );

        tex_format = renderer::UV;

#if VSNRAY_COMMON_HAVE_PTEX
        // Simply check the first texture of the first surface
        // Scene has either Ptex textures, or it doesn't
        if (build_visitor.surfaces.size() > 0
            && std::dynamic_pointer_cast<sg::ptex_texture>(build_visitor.surfaces[0].second) != nullptr)
        {
            tex_format = renderer::Ptex;
            ptex_textures.resize(build_visitor.surfaces.size());
        }
#endif

        if (tex_format == renderer::UV)
        {
            mod.textures.resize(build_visitor.surfaces.size());
        }

        for (size_t i = 0; i < build_visitor.surfaces.size(); ++i)
        {
            auto const& surf = build_visitor.surfaces[i];

            auto disney = std::dynamic_pointer_cast<sg::disney_material>(surf.first);
            assert(disney != nullptr);

            model::material_type newmat = {};
            newmat.cd = disney->base_color.xyz();
            newmat.cs = vec3(0.0f);
            newmat.ior = vec3(disney->ior);
            newmat.transmission = disney->refractive; // TODO
            if (newmat.transmission > 0.0f)
            {
                newmat.illum = 4;
                newmat.cs = vec3(disney->spec_trans);
            }
            mod.materials.emplace_back(newmat); // TODO

#if VSNRAY_COMMON_HAVE_PTEX
            auto ptex_tex = std::dynamic_pointer_cast<sg::ptex_texture>(surf.second);
            if (ptex_tex != nullptr)
            {
                ptex_textures[i] = { ptex_tex->filename(), ptex_tex->cache() };
            }
#else
            auto tex = std::dynamic_pointer_cast<sg::texture2d<vector<4, unorm<8>>>>(surf.second);
            if (tex != nullptr)
            {
                model::texture_type texture(tex->width(), tex->height());
                texture.set_address_mode(tex->get_address_mode());
                texture.set_filter_mode(tex->get_filter_mode());
                texture.reset(tex->data());

                auto it = mod.texture_map.insert(std::make_pair(tex->name(), std::move(texture)));
                mod.textures[i] = model::texture_type::ref_type(it.first->second);
            }
#endif
        }

        environment_map = build_visitor.environment_map;

        mod.bbox = host_top_level_bvh.node(0).get_bounds();
        mod.materials.push_back({});

#if 1
        mod.scene_graph.reset();
#endif
    }

//  std::cout << t.elapsed() << std::endl;
}

//-------------------------------------------------------------------------------------------------
// Load camera from file, reset frame counter and clear frame
//

void renderer::load_camera(std::string filename)
{
    std::ifstream file(filename);
    if (file.good())
    {
        file >> cam;
        counter.reset();
        clear_frame();
        std::cout << "Load camera from file: " << filename << '\n';
    }
}

//-------------------------------------------------------------------------------------------------
// If path tracing, clear frame buffer and reset frame counter
//

void renderer::clear_frame()
{
    if (render_future.valid() && render_async)
    {
        render_future.wait();
    }

    frame_num = 0;

    if (algo == Pathtracing)
    {
        rt.clear_color_buffer();
    }
}

//-------------------------------------------------------------------------------------------------
// HUD
//

void renderer::render_hud()
{
    if (!have_imgui_support())
    {
        return;
    }

    // gather data to render

    int w = width();
    int h = height();

    int x = visionaray::clamp( mouse_pos.x, 0, w - 1 );
    int y = visionaray::clamp( mouse_pos.y, 0, h - 1 );
    auto color = rt.color();
    auto rgba = color[(h - 1 - y) * w + x];

    int num_nodes = 0;
    int num_leaves = 0;

    float focal_dist = cam.get_focal_distance();
    float lens_radius = cam.get_lens_radius();

    traverse_depth_first(
        host_bvhs[0],
        [&](renderer::host_bvh_type::node_type const& node)
        {
            ++num_nodes;

            if (is_leaf(node))
            {
                ++num_leaves;
            }
        }
        );

    static const std::string camera_file_base = "visionaray-camera";
    static const std::string camera_file_suffix = ".txt";

    std::vector<std::string> camera_names;

    camera_names.push_back("<reset...>");

    // Cameras from scene graph
    for (auto& c : cameras)
    {
        camera_names.push_back(c.first);
    }

    // Camera files
    int inc = 0;
    std::string inc_str = "";

    std::string filename = camera_file_base + inc_str + camera_file_suffix;

    while (boost::filesystem::exists(filename))
    {
        camera_names.push_back(filename);

        ++inc;
        inc_str = std::to_string(inc);

        while (inc_str.length() < 4)
        {
            inc_str = std::string("0") + inc_str;
        }

        inc_str = std::string("-") + inc_str;

        filename = camera_file_base + inc_str + camera_file_suffix;
    }

    ImGui::SetNextWindowPos(ImVec2(10, 10), ImGuiCond_FirstUseEver);
    ImGui::Begin("Settings", &show_hud);

    ImGui::Text("X: %4d", x);
    ImGui::SameLine();
    ImGui::Text("W: %4d", w);
    ImGui::SameLine();
    ImGui::Spacing();
    ImGui::SameLine();
    ImGui::Text("# Triangles: %zu", mod.primitives.size());

    ImGui::Text("Y: %4d", y);
    ImGui::SameLine();
    ImGui::Text("H: %4d", h);
    ImGui::SameLine();
    ImGui::Spacing();
    ImGui::SameLine();
    ImGui::Text("# BVH Nodes/Leaves: %d/%d", num_nodes, num_leaves);

    ImGui::Text("R: %5.2f", rgba.x);
    ImGui::SameLine();
    ImGui::Text("G: %5.2f", rgba.y);
    ImGui::SameLine();
    ImGui::Text("B: %5.2f", rgba.z);

    ImGui::Text("FPS: %6.2f", last_frame_time);
    ImGui::SameLine();
    ImGui::Spacing();
    ImGui::SameLine();
    if (algo == Pathtracing)
    {
        ImGui::Text("SPP: %7u", std::max(1U, frame_num));
    }
    else
    {
        ImGui::Text("SPP: %dx SSAA", ssaa_samples);
    }
    ImGui::SameLine();
    ImGui::Spacing();
    ImGui::SameLine();
    ImGui::Text("Device: %s", rt.mode() == host_device_rt::GPU ? "GPU" : "CPU");

    ImGui::Spacing();

    if (ImGui::Checkbox("Headlight", &use_headlight))
    {
        clear_frame();
    }
    ImGui::SameLine();
    if (ImGui::Checkbox("Render async", &render_async))
    {
        if (render_future.valid() && !render_async)
        {
            render_future.wait();
        }
    }

    vec3 amb = ambient.x < 0.0f ? vec3(0.0f) : ambient;
    if (ImGui::InputFloat3("Ambient Intensity", amb.data()))
    {
        ambient = amb;
        clear_frame();
    }

    if (ImGui::Checkbox("DoF", &use_dof) && algo == Pathtracing)
    {
        clear_frame();

        if (algo != Pathtracing)
        {
            std::cerr << "Warning: setting only affects pathtracing algorithm\n";
        }
    }
    ImGui::SameLine();
    ImGui::PushItemWidth(200);
    if (ImGui::SliderFloat("", &focal_dist, 0.1, 100.0f, "Focal Dist. %.1f"))
    {
        cam.set_focal_distance(focal_dist);
        if (use_dof && algo == Pathtracing)
        {
            clear_frame();
        }

        if (algo != Pathtracing)
        {
            std::cerr << "Warning: setting only affects pathtracing algorithm\n";
        }
    }
    ImGui::SameLine();
    ImGui::PushItemWidth(50);
    if (ImGui::InputFloat("Lens radius", &lens_radius))
    {
        cam.set_lens_radius(lens_radius);
        if (use_dof && algo == Pathtracing)
        {
            clear_frame();
        }

        if (algo != Pathtracing)
        {
            std::cerr << "Warning: setting only affects pathtracing algorithm\n";
        }
    }
    ImGui::PopItemWidth();

    // TODO: member?
    static std::string current = camera_names[0]; // <reset...>

    if (ImGui::BeginCombo("Cameras", current.c_str()))
    {
        for (size_t i = 0; i < camera_names.size(); ++i)
        {
            bool selected = current == camera_names[i];

            if (ImGui::Selectable(camera_names[i].c_str(), selected))
            {
                current = camera_names[i].c_str();

                auto it = std::find_if(
                        cameras.begin(),
                        cameras.end(),
                        [&](std::pair<std::string, thin_lens_camera> c)
                        {
                            return c.first == camera_names[i];
                        }
                        );

                if (it != cameras.end())
                {
                    // Preloaded
                    cam = it->second;

                    // Reset viewport and aspect
                    float fovy = cam.fovy();
                    float aspect = width() / static_cast<float>(height());
                    cam.set_viewport(0, 0, width(), height());
                    cam.perspective(fovy, aspect, 0.001f, 1000.0f);

                    counter.reset();
                    clear_frame();
                }
                else if (current == camera_names[0])
                {
                    // Reset...
                    float aspect = width() / static_cast<float>(height());

                    cam.perspective(45.0f * constants::degrees_to_radians<float>(), aspect, 0.001f, 1000.0f);
                    cam.set_lens_radius(0.1f);
                    cam.set_focal_distance(10.0f);
                    cam.view_all(mod.bbox);

                    counter.reset();
                    clear_frame();
                }
                else if (boost::filesystem::exists(current))
                {
                    // From file
                    load_camera(current);
                }
            }

            if (selected)
            {
                ImGui::SetItemDefaultFocus();
            }
        }

        ImGui::EndCombo();
    }

    ImGui::End();
}

void renderer::render_impl()
{
    point_lights.clear();

    if (use_headlight)
    {
        point_light<float> headlight;
        headlight.set_cl( vec3(1.0, 1.0, 1.0) );
        headlight.set_kl(1.0);
        headlight.set_position( cam.eye() );
        headlight.set_constant_attenuation(1.0);
        headlight.set_linear_attenuation(0.0);
        headlight.set_quadratic_attenuation(0.0);
        point_lights.push_back(headlight);
    }

    auto bounds     = mod.bbox;
    auto diagonal   = bounds.max - bounds.min;
    auto bounces    = this->bounces ? this->bounces : algo == Pathtracing ? 10U : 4U;
    auto epsilon    = std::max( 1E-3f, length(diagonal) * 1E-5f );
    auto amb        = ambient.x >= 0.0f // if set via cmdline
                            ? vec4(ambient, 1.0f)
                            : vec4(0.0)
                            ;

    camera_t camx;
    if (use_dof && algo == Pathtracing)
    {
        camx = cam;
    }
    else
    {
        camx = static_cast<pinhole_camera>(cam);
    }

    if (rt.mode() == host_device_rt::CPU)
    {
        if (host_top_level_bvh.num_primitives() > 0)
        {
            aligned_vector<generic_light_t> temp_lights;
            for (auto pl : point_lights)
            {
                temp_lights.push_back(pl);
            }

            if (tex_format == renderer::UV)
            {
                render_instances_cpp(
                        host_top_level_bvh,
                        mod.geometric_normals,
                        mod.shading_normals,
                        mod.tex_coords,
                        generic_materials,
                        mod.textures,
                        temp_lights,
                        bounces,
                        epsilon,
                        vec4(background_color(), 1.0f),
                        amb,
                        rt,
                        host_sched,
                        camx,
                        frame_num,
                        algo,
                        ssaa_samples
                        );
            }
#if VSNRAY_COMMON_HAVE_PTEX
            else if (tex_format == renderer::Ptex)
            {
                render_instances_ptex_cpp(
                        host_top_level_bvh,
                        mod.geometric_normals,
                        mod.shading_normals,
                        ptex_tex_coords,
                        generic_materials,
                        ptex_textures,
                        temp_lights,
                        bounces,
                        epsilon,
                        vec4(background_color(), 1.0f),
                        amb,
                        rt,
                        host_sched,
                        camx,
                        frame_num,
                        algo,
                        ssaa_samples
                        );
            }
#endif
        }
        else if (area_lights.size() > 0 && algo == Pathtracing)
        {
            render_generic_material_cpp(
                    host_bvhs[0],
                    mod.geometric_normals,
                    mod.shading_normals,
                    mod.tex_coords,
                    generic_materials,
                    mod.textures,
                    area_lights,
                    bounces,
                    epsilon,
                    vec4(background_color(), 1.0f),
                    amb,
                    rt,
                    host_sched,
                    camx,
                    frame_num,
                    algo,
                    ssaa_samples
                    );
        }
        else
        {
            render_plastic_cpp(
                    host_bvhs[0],
                    mod.geometric_normals,
                    mod.shading_normals,
                    mod.tex_coords,
                    plastic_materials,
                    mod.textures,
                    point_lights,
                    bounces,
                    epsilon,
                    vec4(background_color(), 1.0f),
                    amb,
                    rt,
                    host_sched,
                    camx,
                    frame_num,
                    algo,
                    ssaa_samples
                    );
        }
    }
#ifdef __CUDACC__
    else if (rt.mode() == host_device_rt::GPU)
    {
        if (area_lights.size() > 0 && algo == Pathtracing)
        {
            render_generic_material_cu(
                    device_bvh,
                    device_geometric_normals,
                    device_shading_normals,
                    device_tex_coords,
                    device_generic_materials,
                    device_textures,
                    area_lights,
                    bounces,
                    epsilon,
                    vec4(background_color(), 1.0f),
                    amb,
                    rt,
                    device_sched,
                    camx,
                    frame_num,
                    algo,
                    ssaa_samples
                    );
        }
        else
        {
            render_plastic_cu(
                    device_bvh,
                    device_geometric_normals,
                    device_shading_normals,
                    device_tex_coords,
                    device_plastic_materials,
                    device_textures,
                    point_lights,
                    bounces,
                    epsilon,
                    vec4(background_color(), 1.0f),
                    amb,
                    rt,
                    device_sched,
                    camx,
                    frame_num,
                    algo,
                    ssaa_samples
                    );
        }
    }
#endif

    last_frame_time = counter.register_frame();

#if VSNRAY_COMMON_HAVE_PTEX
//  if (ptex_textures.size() > 0)
//  {
//      PtexCache::Stats stats;
//      ptex_textures[0].cache.get()->get()->getStats(stats);
//      std::cout << "Mem used:        " << stats.memUsed << '\n';
//      std::cout << "Peak mem used:   " << stats.peakMemUsed << '\n';
//      std::cout << "Files open:      " << stats.filesOpen << '\n';
//      std::cout << "Peak files open: " << stats.peakFilesOpen << '\n';
//      std::cout << "Files accessed:  " << stats.filesAccessed << '\n';
//      std::cout << "File reopens:    " << stats.fileReopens << '\n';
//      std::cout << "Block reads:     " << stats.blockReads << '\n';
//  }
#endif
}

void renderer::on_close()
{
    outlines.destroy();
}

void renderer::on_display()
{
    if (render_async)
    {
        if (!render_future.valid() || render_future.wait_for(std::chrono::seconds(0)) == std::future_status::ready)
        {
            render_future = std::async(
                    std::launch::async,
                    [this]()
                    {
                        render_impl();

                        std::unique_lock<std::mutex> l(display_mutex);
                        rt.swap_buffers();
                    }
                    );
        }

        if (rt.width() == width() && rt.height() == height())
        {
            std::unique_lock<std::mutex> l(display_mutex);
            rt.display_color_buffer();
        }
    }
    else
    {
        render_impl();

        rt.swap_buffers();

        rt.display_color_buffer();
    }

    // OpenGL overlay rendering

    if (show_hud)
    {
        render_hud();
    }

    if (show_bvh)
    {
        outlines.frame(cam.get_view_matrix(), cam.get_proj_matrix());
    }
}

void renderer::on_key_press(key_event const& event)
{
    static const std::string camera_file_base = "visionaray-camera";
    static const std::string camera_file_suffix = ".txt";

    switch (event.key())
    {
    case '1':
        std::cout << "Switching algorithm: simple\n";
        rt.set_double_buffering(true);
        algo = Simple;
        counter.reset();
        clear_frame();
        break;

    case '2':
        std::cout << "Switching algorithm: whitted\n";
        rt.set_double_buffering(true);
        algo = Whitted;
        counter.reset();
        clear_frame();
        break;

    case '3':
        std::cout << "Switching algorithm: path tracing\n";
        if (render_future.valid())
        {
            render_future.wait();
        }
        // Double buffering does not work in case of pathtracing
        // because destination and source buffers need to be the same
        rt.set_double_buffering(false);
        algo = Pathtracing;
        counter.reset();
        clear_frame();
        break;

    case 'b':
        show_bvh = !show_bvh;

        if (show_bvh)
        {
            if (host_top_level_bvh.num_nodes() > 0)
            {
                outlines.init(host_top_level_bvh);
            }
            else
            {
                outlines.init(host_bvhs[0]);
            }
        }

        break;

    case 'c':
        if (rt.color_space() == host_device_rt::RGB)
        {
            rt.color_space() = host_device_rt::SRGB;
        }
        else
        {
            rt.color_space() = host_device_rt::RGB;
        }
        break;

     case 'h':
        show_hud = !show_hud;
        break;

    case 'l':
        use_headlight = !use_headlight;
        counter.reset();
        clear_frame();
        break;

   case 'm':
#ifdef __CUDACC__
        if (rt.mode() == host_device_rt::CPU)
        {
            rt.mode() = host_device_rt::GPU;
        }
        else
        {
            rt.mode() = host_device_rt::CPU;
        }
        counter.reset();
        clear_frame();
#endif
        break;

    case 's':
        ssaa_samples *= 2;
        if (ssaa_samples > 8)
        {
            ssaa_samples = 1;
        }

        if (algo != Pathtracing)
        {
            counter.reset();
            clear_frame();
        }
        break;

    case 'u':
        {
            int inc = 0;
            std::string inc_str = "";

            std::string filename = camera_file_base + inc_str + camera_file_suffix;

            while (boost::filesystem::exists(filename))
            {
                ++inc;
                inc_str = std::to_string(inc);

                while (inc_str.length() < 4)
                {
                    inc_str = std::string("0") + inc_str;
                }

                inc_str = std::string("-") + inc_str;

                filename = camera_file_base + inc_str + camera_file_suffix;
            }

            std::ofstream file(filename);
            if (file.good())
            {
                std::cout << "Storing camera to file: " << filename << '\n';
                file << cam;
            }
        }
        break;

    case 'v':
        {
            std::string filename = camera_file_base + camera_file_suffix;

            load_camera(filename);
        }
        break;

    default:
        break;
    }

    viewer_type::on_key_press(event);
}

void renderer::on_mouse_move(visionaray::mouse_event const& event)
{
    if (event.buttons() != mouse::NoButton)
    {
        clear_frame();
    }

    mouse_pos = event.pos();
    viewer_type::on_mouse_move(event);
}

void renderer::on_resize(int w, int h)
{
    if (render_future.valid() && algo != Pathtracing)
    {
        render_future.wait();
    }

    cam.set_viewport(0, 0, w, h);
    float fovy = cam.fovy();
    float aspect = w / static_cast<float>(h);
    float z_near = cam.z_near();
    float z_far = cam.z_far();
    cam.perspective(fovy, aspect, z_near, z_far);
    rt.resize(w, h);
    clear_frame();
    viewer_type::on_resize(w, h);
}

int main(int argc, char** argv)
{
    renderer rend;

#ifdef __CUDACC__
    if (rend.rt.direct_rendering() && cuda::init_gl_interop() != cudaSuccess)
    {
        std::cerr << "Cannot initialize CUDA OpenGL interop\n";
        return EXIT_FAILURE;
    }
#endif

    try
    {
        rend.init(argc, argv);
    }
    catch (std::exception const& e)
    {
        std::cerr << e.what() << '\n';
        return EXIT_FAILURE;
    }

	if (rend.filenames.empty())
	{
		std::cout << rend.cmd_line_inst().help(argv[0]) << "\n";
		return EXIT_FAILURE;
	}

    if (rend.algo == Pathtracing)
    {
        // Double buffering does not work in case of pathtracing
        // because destination and source buffers need to be the same
        rend.rt.set_double_buffering(false);
    }

    rend.gl_debug_callback.activate();

    // Load the scene
    std::cout << "Loading model...\n";

    std::vector<std::string> filenames;
    std::copy(rend.filenames.begin(), rend.filenames.end(), std::back_inserter(filenames));

    if (!rend.mod.load(filenames))
    {
        std::cerr << "Failed loading model\n";
        return EXIT_FAILURE;
    }

    rend.build_bvhs();

    // Generate a list with plastic materials
    rend.plastic_materials = make_materials(
            plastic<float>{},
            rend.mod.materials
            );

    // Generate another list with generic materials
    rend.generic_materials = make_materials(
            generic_material_t{},
            rend.mod.materials,
            [](aligned_vector<generic_material_t>& cont, model::material_type mat)
            {
                // Add emissive material if emissive component > 0
                if (length(mat.ce) > 0.0f)
                {
                    emissive<float> em;
                    em.ce() = from_rgb(mat.ce);
                    em.ls() = 1.0f;
                    cont.emplace_back(em);
                }
                else if (mat.illum == 1)
                {
                    matte<float> ma;
                    ma.ca() = from_rgb(mat.ca);
                    ma.cd() = from_rgb(mat.cd);
                    ma.ka() = 1.0f;
                    ma.kd() = 1.0f;
                    cont.emplace_back(ma);
                }
                else if (mat.illum == 3)
                {
                    mirror<float> mi;
                    mi.cr() = from_rgb(mat.cs);
                    mi.kr() = 1.0f;
                    mi.ior() = spectrum<float>(0.0f);
                    mi.absorption() = spectrum<float>(0.0f);
                    cont.emplace_back(mi);
                }
                else if (mat.illum == 4 && mat.transmission > 0.0f)
                {
                    glass<float> gl;
                    gl.ct() = from_rgb(mat.cd);
                    gl.kt() = 1.0f;
                    gl.cr() = from_rgb(mat.cs);
                    gl.kr() = 1.0f;
                    gl.ior() = from_rgb(mat.ior);
                    cont.push_back(gl);
                }
                else
                {
                    plastic<float> pl;
                    pl.ca() = from_rgb(mat.ca);
                    pl.cd() = from_rgb(mat.cd);
                    pl.cs() = from_rgb(mat.cs);
                    pl.ka() = 1.0f;
                    pl.kd() = 1.0f;
                    pl.ks() = 1.0f;
                    pl.specular_exp() = mat.specular_exp;
                    cont.emplace_back(pl);
                }
            }
            );

    // Loop over all triangles, check if their
    // material is emissive, and if so, build
    // BVHs to create area lights from.

    struct range
    {
        std::size_t begin;
        std::size_t end;
        unsigned    geom_id;
    };

    std::vector<range> ranges;

    for (std::size_t i = 0; i < rend.mod.primitives.size(); ++i)
    {
        auto pi = rend.mod.primitives[i];
        if (rend.generic_materials[pi.geom_id].as<emissive<float>>() != nullptr)
        {
            range r;
            r.begin = i;

            std::size_t j = i + 1;
            for (;j < rend.mod.primitives.size(); ++j)
            {
                auto pii = rend.mod.primitives[j];
                if (rend.generic_materials[pii.geom_id].as<emissive<float>>() == nullptr
                                || pii.geom_id != pi.geom_id)
                {
                    break;
                }
            }

            r.end = j;
            r.geom_id = pi.geom_id;
            ranges.push_back(r);

            i = r.end - 1;
        }
    }

    // Build vector with area light sources
    for (auto r : ranges)
    {
        for (std::size_t i = r.begin; i != r.end; ++i)
        {
            area_light<float, basic_triangle<3, float>> light(rend.mod.primitives[i]);
            auto mat = *rend.generic_materials[r.geom_id].as<emissive<float>>();
            light.set_cl(to_rgb(mat.ce()));
            light.set_kl(mat.ls());
            rend.area_lights.push_back(light);
        }
    }

    std::cout << "Ready\n";

#ifdef __CUDACC__
    // Copy data to GPU
    try
    {
        rend.device_bvh = renderer::device_bvh_type(rend.host_bvhs[0]);
        rend.device_geometric_normals = rend.mod.geometric_normals;
        rend.device_shading_normals = rend.mod.shading_normals;
        rend.device_tex_coords = rend.mod.tex_coords;
        rend.device_plastic_materials = rend.plastic_materials;
        rend.device_generic_materials = rend.generic_materials;

        // Copy textures and texture references to the GPU

        rend.device_textures.resize(rend.mod.textures.size());

        for (auto const& pair_host_tex : rend.mod.texture_map)
        {
            auto const& host_tex = pair_host_tex.second;
            renderer::device_tex_type device_tex(pair_host_tex.second);
            auto const& p = rend.device_texture_map.emplace(pair_host_tex.first, std::move(device_tex));

            assert(p.second /* inserted */);

            auto it = p.first;

            // Texture references ensure that we don't allocate storage
            // for the same texture map more than once.
            // By checking if the pointer in the ref contains the
            // address of the first texel of the map, we can identify
            // which texture_ref references which texture and recreate
            // that relation on the GPU.
            for (size_t i = 0; i < rend.mod.textures.size(); ++i)
            {
                if (rend.mod.textures[i].data() == host_tex.data())
                {
                    rend.device_textures[i] = renderer::device_tex_ref_type(it->second);
                }
            }
        }

        // Place some dummy textures where geometry has no texture
        for (size_t i = 0; i < rend.mod.textures.size(); ++i)
        {
            if (rend.mod.textures[i].width() == 0 || rend.mod.textures[i].height() == 0)
            {
                vector<4, unorm<8>>* dummy = nullptr;
                renderer::device_tex_type device_tex(dummy, 0, 0, Clamp, Nearest);

                // Try to insert the dummy texture into the
                // device texture map...
                auto p = rend.device_texture_map.emplace("", std::move(device_tex));

                // ... but maybe a dummy texture was already
                // inserted, then just find that
                if (!p.second)
                {
                    auto it = rend.device_texture_map.find("");
                    rend.device_textures[i] = renderer::device_tex_ref_type(it->second);

                }
                else
                {
                    auto it = p.first;
                    rend.device_textures[i] = renderer::device_tex_ref_type(it->second);
                }
            }
        }
    }
    catch (std::bad_alloc const&)
    {
        std::cerr << "GPU memory allocation failed" << std::endl;
        rend.device_bvh = renderer::device_bvh_type();
        rend.device_geometric_normals.clear();
        rend.device_geometric_normals.shrink_to_fit();
        rend.device_shading_normals.clear();
        rend.device_shading_normals.shrink_to_fit();
        rend.device_tex_coords.clear();
        rend.device_tex_coords.shrink_to_fit();
        rend.device_plastic_materials.clear();
        rend.device_plastic_materials.shrink_to_fit();
        rend.device_generic_materials.clear();
        rend.device_generic_materials.shrink_to_fit();
        rend.device_texture_map.clear();
        rend.device_textures.clear();
        rend.device_textures.shrink_to_fit();
    }
#endif

    float aspect = rend.width() / static_cast<float>(rend.height());

    rend.cam.perspective(45.0f * constants::degrees_to_radians<float>(), aspect, 0.001f, 1000.0f);
    rend.cam.set_lens_radius(0.1f);
    rend.cam.set_focal_distance(10.0f);

    // Load camera from file or set view-all
    std::ifstream file(rend.initial_camera);
    if (file.good())
    {
        file >> rend.cam;
    }
    else
    {
        rend.cam.view_all( rend.mod.bbox );
    }

    rend.add_manipulator( std::make_shared<arcball_manipulator>(rend.cam, mouse::Left) );
    rend.add_manipulator( std::make_shared<pan_manipulator>(rend.cam, mouse::Middle) );
    // Additional "Alt + LMB" pan manipulator for setups w/o middle mouse button
    rend.add_manipulator( std::make_shared<pan_manipulator>(rend.cam, mouse::Left, keyboard::Alt) );
    rend.add_manipulator( std::make_shared<zoom_manipulator>(rend.cam, mouse::Right) );

    rend.event_loop();

}