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


#ifndef	__NTERRAIN__
#define	__NTERRAIN__


		#include "core/renderable.h"
		#include "core/item.h"
		#include "core/material.h"
		#include "geometry/rect.h"


namespace GS {
	namespace Core {
		class Terrain;
		class Geometry;

struct	Patch
{
	Terrain		*terrain;

	int			u, v, w, h;
	int			decimation;

	MinMax		minmax;

	iRect		GetRect() const { return iRect(u, v, u + w, v + h); }

	AutoPtr <Patch> children[4];

	Patch() : terrain(0) {}
};

/*
	@short	Terrain.
	@author	Emmanuel Julien (ejulien@gsworks.fr)
*/
class	Terrain : public Item, public Renderable
{
public:

struct	Attrib
{
	char		nx, ny, nz;
};

struct	Layer
{
	bool		enabled;

	String		diffuse, specular, normal, self;
	float		angle, tiling;

	Layer()
	{
		enabled = true;

		angle = 0.f;
		tiling = 1.f;
	}
};

protected:

		float			width, depth;

		Array <float>	heightmap;
		Array <Attrib>	attribmap;

		int				heightmap_w, heightmap_h;
		float			unit;

		AutoPtr <Patch>	root_node;
		uint			node_count;

		void			UpdatePatchMinMax(Patch *node) const;

		/// Cull terrain primitive and push to the render queue.
		void			CullRenderablePrimitiveList(const Camera &view, const Camera &default_view, Stack <Render::Primitive *> &, Context, Patch *);
		/// Helper function to create a geometry object for a given terrain patch.
		Geometry		*CreateNodeGeometry(const Patch *);
		/// Build the terrain quad-tree for a specific terrain part.
		Patch			*BuildTerrainStaticQuadtree(int u, int v, int w, int h, int decimation, int depth);

		/// Update a quadtree patch.
		void			UpdateQuadtreePatch(Patch *patch, iRect &update_rect);

		/// Layer to tag.
		NML::Tag		*LayerAsMetaTag(int index);

public:

		Layer			layer[4];

/// Terrain render data.
struct	RenderData
{
	struct	Layer
	{
		Render::sTexture diffuse, specular, normal, self;
	};

		Layer			layer[4];

		Render::sTexture blendmap;
		Render::sMaterial material;
};

///
struct	TraceResult
{
		bool			has_hit;
		Vector4			w_i;		///< Intersection in world space.
		Vector2			uv;
};

		String			material;

		String			heightmap_path,
						blendmap_path,
						shader_path;

		AutoPtr <RenderData> render_data;

		/// Compute the renderable bounding box.
virtual	void			ComputeRenderableMinMax(MinMax &);
		/// Get the renderable primitive list.
virtual	uint			GetRenderablePrimitiveList(const Camera &view, const Camera &default_view, Stack <Render::Primitive *> &, Context = Context_Default, bool cull = true);

		bool			Raytrace(const Vector4 &s, const Vector4 &d, TraceResult &, float len = -1.f);

		/*!
			@short	Apply several blur passes to the heightmap.
			@note	The normals are not blurred, so you might want to recompute
					them when done.
		*/
		void			ApplyBlur(int pass_count = 1);
		/// Compute normals from the heightmap.
		void			ComputeNormals(int u = 0, int v = 0, int w = 0, int h = 0);

		float			GetWidth() const { return width; }
		float			GetDepth() const { return depth; }

		/*!
			@short	Return the terrain normal for a given sample.
			@note	For performance critical code you might want to use the
					attribute map directly.
		*/
		Vector4			GetNormalAt(int u, int v) const;

		Attrib			*GetAttributesMap() const { return attribmap; }
		Attrib			*GetAttributesMapAt(int u, int v) const;

		float			*GetHeightmap() const { return heightmap; }
		int				GetHeightmapWidth() const { return heightmap_w; }
		int				GetHeightmapHeight() const { return heightmap_h; }
		int				GetHeightmapPitch() const { return heightmap_w + 1; }
		float			GetUnit() const { return unit; }

		bool			LocalToTexture(const Vector4 &, float w, float h, float &u, float &v) const;
		bool			LocalToHeightmap(const Vector4 &p, float &u, float &v) const;

		/// Sample height in heightmap space.
		float			SampleHeight(float u, float v) const;
		/// Sample height in local space.
		float			SampleHeight(const Vector4 &) const;

		/// Update a terrain quadtree zone.
		void			UpdateQuadtree(int u = 0, int v = 0, int w = 0, int h = 0);

		Patch			*BuildQuadtree();

		void			RenderSetup(ResourceFactories * = 0);

		/// Set heightmap from file.
		bool			FromPicture(const char *, int blur_pass_count = 0);

/*!
	@name	Height map I/O.
	@{
*/
		bool			LoadHeightmap(const char *);
		bool			SaveHeightmap(const char *);
/// @}

		/*!
			@short	Allocate terrain.
			@note	This function does not allocate terrain patches.
		*/
		bool			Allocate(uint w_res, uint h_res, float unit);
		void			Free();

		bool			FromMetaTag(NML::Tag &);
		NML::Tag		*AsMetaTag();

		Terrain();
virtual	~Terrain();
};

	} // Core
} // GS


#endif	// __NTERRAIN__
