/* -----------------------------------------------------------------------------
	GSFramework
	Copyright 2001-2013 Emmanuel Julien. All Rights Reserved.
----------------------------------------------------------------------------- */


		#include "core/camera.h"
		#include "core/terrain.h"
		#include "core/geometry.h"
		#include "core/resource_factories.h"
		#include "core/render_resource_factory.h"
		#include "picture/pict.h"
		#include "picture/pict_io.h"
		#include "filesystem/filesystem.h"
		#include "log/log.h"

		using namespace GS;
		using namespace GS::Core;


//------------------------------------------------------------------------------
void				Terrain::RenderSetup(ResourceFactories *f)
{
	if	(LoadHeightmap(heightmap_path))
	{
		ComputeNormals();
		BuildQuadtree();
	}

	ComputeNormals();
	BuildQuadtree();

	if	(render_data = new RenderData)
		if	(f && f->render)
		{
			render_data->blendmap = f->render->LoadTexture(blendmap_path);
			render_data->material = f->render->LoadMaterial(material);

			for	(uint n = 0; n < 4; ++n)
			{
				render_data->layer[n].diffuse = f->render->LoadTexture(layer[n].diffuse);
				render_data->layer[n].specular = f->render->LoadTexture(layer[n].specular);
				render_data->layer[n].normal = f->render->LoadTexture(layer[n].normal);
				render_data->layer[n].self = f->render->LoadTexture(layer[n].self);
			}
		}
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
bool				Terrain::LocalToTexture(const Vector4 &p, float w, float h, float &u, float &v) const
{
	float lx = p.x + width * 0.5f,
		  lz = p.z + depth * 0.5f;

	u = (lx / width) * w;
	v = (lz / depth) * h;

	return asbool((u >= 0) && (u < w) && (v >= 0) && (v < h));
}
bool				Terrain::LocalToHeightmap(const Vector4 &p, float &u, float &v) const
{
	return LocalToTexture(p, (float)heightmap_w, (float)heightmap_h, u, v);
}
float				Terrain::SampleHeight(float u, float v) const
{
	// Fetch samples coordinates.
	int s_u[4], s_v[4];
	s_u[0] = Types::Clamp(int(u), 0, heightmap_w); s_v[0] = Types::Clamp(int(v), 0, heightmap_h);
	s_u[1] = Types::Clamp(s_u[0] + 1, 0, heightmap_w); s_v[1] = Types::Clamp(s_v[0] + 0, 0, heightmap_h);
	s_u[2] = Types::Clamp(s_u[0] + 0, 0, heightmap_w); s_v[2] = Types::Clamp(s_v[0] + 1, 0, heightmap_h);
	s_u[3] = Types::Clamp(s_u[0] + 1, 0, heightmap_w); s_v[3] = Types::Clamp(s_v[0] + 1, 0, heightmap_h);

	// Fetch samples.
	float s[4];
	for	(int n = 0; n < 4; ++n)
		s[n] = heightmap[s_v[n] * GetHeightmapPitch() + s_u[n]];

	// Bilinear.
	float k_u = u - int(u), k_v = v - (int)v;
	return (s[0] * (1 - k_u) + s[1] * k_u) * (1 - k_v) + (s[2] * (1 - k_u) + s[3] * k_u) * k_v;
}
float				Terrain::SampleHeight(const Vector4 &p) const
{
	float u, v;
	if	(!LocalToHeightmap(p, u, v))
		return 0;
	return SampleHeight(u, v);
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void				Terrain::ComputeRenderableMinMax(MinMax &mm)
{
	if	(root_node)
	{
		OBB obb(root_node->minmax);
		obb.Transform(GetMatrix());
		obb.ComputeMinMax(mm);
	}
}
void				Terrain::CullRenderablePrimitiveList(const Camera &view, const Camera &default_view, Stack <Render::Primitive *> &list, Context context, Patch *node)
{
	MinMax &minmax = node->minmax;
	if	(view.frustum.ClassifyMinMax(minmax, &GetMatrix()) == Frustum::Outside)
		return;

	// Check error for the current level.
	Vector4 center = minmax.GetCenter() * GetMatrix();

	float lod_d = (minmax.mx - minmax.mn).Len() * 1.5f,
		  /*v_d = nVector::Dist(center, view.GetMatrix().GetRow(3)),*/
		  d_d = Vector4::Dist(center, default_view.GetMatrix().GetRow(3));

	float d = d_d;// Types::Min(v_d, d_d);		// Ensure high-resolution close to the viewer and the light source.

	if	((d > lod_d) || !node->children[0])
		list.Push(new Render::Primitive(node, this, 0));
	else
		for	(int n = 0; n < 4; ++n)
			if	(node->children[n])
				CullRenderablePrimitiveList(view, default_view, list, context, node->children[n]);
}
uint				Terrain::GetRenderablePrimitiveList(const Camera &view, const Camera &default_view, Stack <Render::Primitive *> &list, Context context, bool nUnused(cull))
{
	if	(root_node)
		CullRenderablePrimitiveList(view, default_view, list, context, root_node);
	return node_count;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
Geometry			*Terrain::CreateNodeGeometry(const Patch *node)
{
	Geometry *geometry = new Geometry;

	int	patch_w = node->w / node->decimation,
		patch_h = node->h / node->decimation;

	// Create vertices.
	geometry->vtx.Allocate((patch_w + 1) * (patch_h + 1));

	float step_w = width / heightmap_w, step_h = depth / heightmap_h;

	Vector4	p(node->u * step_w * GetUnit(), 0, -node->v * step_h * GetUnit()),
			*pv = geometry->vtx;

	for	(int _v = node->v; _v <= (node->v + node->h); _v += node->decimation)
	{
		float *ph = &heightmap[_v * heightmap_w + node->u];

		for	(int _u = node->u; _u <= (node->u + node->w); _u += node->decimation)
		{
			pv->Set(p.x, ph[0], p.z);
			pv++;

			ph += node->decimation;
			p.x += step_w * node->decimation * GetUnit();
		}
		p.x = node->u * step_w * GetUnit();
		p.z += step_h * node->decimation * GetUnit();
	}

	// Create polygons.
	geometry->AllocatePolygon(patch_w * patch_h);
	for	(uint n = 0; n < geometry->pol.GetCount(); ++n)
	{
		geometry->pol[n].vtx_count = 4;
		geometry->pol[n].material = 0;
	}
	geometry->AllocatePolygonBinding();

	Polygon *polygon = geometry->pol;
	for	(int _v = 0; _v < patch_h; ++_v)
		for	(int _u = 0; _u < patch_w; ++_u)
		{
			polygon->vtx_count = 4;
			polygon->material = 0;

			polygon->binding[0] = _v * (patch_w + 1) + _u;
			polygon->binding[1] = _v * (patch_w + 1) + _u + 1;
			polygon->binding[2] = (_v + 1) * (patch_w + 1) + _u + 1;
			polygon->binding[3] = (_v + 1) * (patch_w + 1) + _u;
			
			polygon++;
		}

	geometry->ComputeVertexNormal();
	return geometry;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void			Terrain::UpdatePatchMinMax(Patch *node) const
{
	float *ph = &heightmap[node->v * GetHeightmapPitch() + node->u];
	float min_height, max_height;

	min_height = max_height = ph[0];
	for	(int _v = 0; _v < node->h; _v += node->decimation)
	{
		for	(int _u = 0; _u < node->w; _u += node->decimation)
		{
			if	(ph[_u] < min_height)
				min_height = ph[_u];
			if	(ph[_u] > max_height)
				max_height = ph[_u];
		}
		ph += GetHeightmapPitch() * node->decimation;
	}

	node->minmax.mn.Set(node->u * GetUnit() - width * 0.5f, min_height, (node->v + node->h) * GetUnit() - depth * 0.5f);
	node->minmax.mx.Set((node->u + node->w) * GetUnit() - width * 0.5f, max_height, node->v * GetUnit() - depth * 0.5f);
}
Patch			*Terrain::BuildTerrainStaticQuadtree(int u, int v, int w, int h, int decimation, int tree_depth)
{
	if	(!decimation || (tree_depth == 8))
		return NULL;
	if	(!w || !h)
		return NULL;

	// Create a new node.
	Patch *node = new Patch;

	node->terrain = this;
	node->u = u; node->v = v;
	node->w = w; node->h = h;
	node->decimation = decimation;

	// Create node geometry (helper function, should normally be done on the fly by the renderer).
//	CreateNodeGeometry(node);

	// Update patch minmax.
	UpdatePatchMinMax(node);

	// Split to create children.
	int hw = w / 2, hh = h / 2;

	node->children[0] = BuildTerrainStaticQuadtree(node->u, node->v, hw, hh, decimation / 2, tree_depth + 1);
	node->children[1] = BuildTerrainStaticQuadtree(node->u + hw, node->v, node->w - hw, hh, decimation / 2, tree_depth + 1);
	node->children[2] = BuildTerrainStaticQuadtree(node->u, node->v + hh, hw, node->h - hh, decimation / 2, tree_depth + 1);
	node->children[3] = BuildTerrainStaticQuadtree(node->u + hw, node->v + hh, node->w - hw, node->h - hh, decimation / 2, tree_depth + 1);

	node_count++;
	return node;
}
Patch			*Terrain::BuildQuadtree()
{
	node_count = 0;
	return root_node = BuildTerrainStaticQuadtree(0, 0, heightmap_w, heightmap_h, heightmap_w / 64, 0);
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void				Terrain::UpdateQuadtreePatch(Patch *patch, iRect &update_rect)
{
	iRect patch_rect = patch->GetRect();

	if	(!patch_rect.Intersect(update_rect))
		return;

	UpdatePatchMinMax(patch);
	for (int n = 0; n < 4; ++n)
		if	(patch->children[n])
			UpdateQuadtreePatch(patch->children[n], update_rect);
}
void				Terrain::UpdateQuadtree(int u, int v, int w, int h)
{
	iRect update_rect(u, v, u + w, v + h);
	UpdateQuadtreePatch(root_node, update_rect);
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void				Terrain::ComputeNormals(int u, int v, int w, int h)
{
	if	(!attribmap)
		return;

	if	(!u && !v && !w && !h)
	{
		w = heightmap_w;
		h = heightmap_h;
	}

	u = Types::Clamp(u, 0, heightmap_w);
	v = Types::Clamp(v, 0, heightmap_h);

	if	(u + w > heightmap_w) w = heightmap_w - u;
	if	(v + h > heightmap_h) h = heightmap_h - v;

	float *ph = &heightmap[v * GetHeightmapPitch() + u];
	Attrib *pv = &attribmap[v * GetHeightmapPitch() + u];

	for	(int _v = 0; _v < h; ++_v)
	{
		float *sh = ph;
		Attrib *sv = pv;
		for (int _u = 0; _u < w; ++_u)
		{
			Vector4	vu(unit, sh[1] - sh[0], 0),
					vv(0, sh[GetHeightmapPitch()] - sh[0], unit),
					n = vv.Normalized().Cross(vu.Normalized());

			sv[0].nx = (char)(n.x * 127.f);
			sv[0].ny = (char)(n.y * 127.f);
			sv[0].nz = (char)(n.z * 127.f);
			++sh;
			++sv;
		}
		pv += GetHeightmapPitch();
		ph += GetHeightmapPitch();
	}
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
bool				Terrain::Allocate(uint w_res, uint h_res, float _unit)
{
	Free();

	// Global heightmap.
	heightmap_w = w_res;
	heightmap_h = h_res;

	if	(!attribmap.Allocate((heightmap_w + 1) * (heightmap_h + 1)) || !heightmap.Allocate((heightmap_w + 1) * (heightmap_h + 1)))
		__ERR__(__LOG_E__ << "Failed to allocate terrain heightmap.\n", false)

	Memory::Set(&attribmap[0], 0, (heightmap_w + 1) * (heightmap_h + 1) * sizeof(Attrib));
	Memory::Set(&heightmap[0], 0, sizeof(float) * (heightmap_w + 1) * (heightmap_h + 1));

	width = w_res * _unit;
	depth = h_res * _unit;
	unit = _unit;
	return true;
}
void				Terrain::Free()
{
	width = 0;
	depth = 0;
	attribmap.Free();
	heightmap.Free();
	heightmap_w = 0;
	heightmap_h = 0;
	node_count = 0;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
bool				Terrain::Raytrace(const Vector4 &w_s, const Vector4 &w_d, TraceResult &trace, float len)
{
	trace.has_hit = false;

	// Move ray to terrain space.
	Vector4	s = w_s * GetInverseMatrix(),
			d = w_d * GetRotationMatrix().Transposed();

	// Clip ray against the terrain bounding box.
	float tmin, tmax;
	if	(!root_node->minmax.IntersectRay(s, d, tmin, tmax))
		return false;

	tmin -= 0.1f; tmax += 0.1f;
	Vector4	e = s + d * tmax;
	if	(tmin > 0)
		s += d * tmin;

	// Walk along the ray, looking for an intersection point.
	bool side = SampleHeight(s) < s.y;

	Vector4 dt = e - s;
	float max_dist = dt.Len();

	dt /= max_dist;
	dt *= 1.f;	// minimum step size is 1 meters (so as not to spend too much time stepping)
	float dt_len = dt.Len();

	float dist = 0.f;
	for (Vector4 p = s + dt; dist < max_dist; p += dt)
	{
		if	((SampleHeight(p) < p.y) != side)
		{
		#if	1
			// [EJ] Extra precision at terrain crossing boundary.
			s = p - dt;
			for (int n = 0; n < 24; ++n)
			{
				Vector4 m = (s + p) * 0.5f;
				if	((SampleHeight(m) < m.y) != side)
						p = m;
				else	s = m;
			}
		#endif

			// Final hit.
			trace.has_hit = true;
			trace.w_i = p * GetMatrix();
			LocalToHeightmap(p, trace.uv.x, trace.uv.y);
			return true;
		}
		dist += dt_len;
	}
	return false;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void				Terrain::ApplyBlur(int pass_count)
{
	if	(heightmap)
		for	(int p = 0; p < pass_count; ++p)
		{
			int n = (heightmap_w + 1) + 1;
			for	(int v = 1; v < heightmap_h; ++v)
			{
				for	(int u = 1; u < heightmap_w; ++u)
				{
					heightmap[n] = (heightmap[n] + heightmap[n - 1] + heightmap[n - (heightmap_w + 1)] + heightmap[n + 1] + heightmap[n + (heightmap_w + 1)]) / 5.f;
					++n;
				}
				n += 2;
			}
		}
}
bool				Terrain::FromPicture(const char *path, int blur_pass_count)
{
	// Initial values.
	Picture pic;
	PictureIO::Get().Load(pic, path);

	int n = 0;
	for	(int v = 0; v < (heightmap_h + 1); ++v)
		for	(int u = 0; u < (heightmap_w + 1); ++u)
			heightmap[n++] = pic.SampleColor((float)u / heightmap_w, (float)v / heightmap_h).x * 16.f;

	ApplyBlur(blur_pass_count);
	return true;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
Terrain::Attrib		*Terrain::GetAttributesMapAt(int u, int v) const
{	return &attribmap[(heightmap_w + 1) * v + u];	}
Vector4				Terrain::GetNormalAt(int u, int v) const
{
	Attrib *n = GetAttributesMapAt(u, v);
	return Vector4((float)n->nx / 127.f, (float)n->ny / 127.f, (float)n->nz / 127.f);
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
Terrain::Terrain()
{
	width = 0;
	depth = 0;

	heightmap_w = 0;
	heightmap_h = 0;
	node_count = 0;

	for (uint n = 1; n < 4; ++n)
		layer[n].enabled = false;
}
Terrain::~Terrain()
{
	Free();
}
//------------------------------------------------------------------------------
