// Copyright (C) 2002-2007 Nikolaus Gebhardt
// This file is part of the "Irrlicht Engine".
// For conditions of distribution and use, see copyright notice in irrlicht.h

// The code for the TerrainSceneNode is based on the GeoMipMapSceneNode
// developed by Spintz. He made it available for Irrlicht and allowed it to be
// distributed under this licence. I only modified some parts. A lot of thanks go to him.

#ifndef __C_TERRAIN_SCENE_NODE_H__
#define __C_TERRAIN_SCENE_NODE_H__

#include "ITerrainSceneNode.h"
#include "SMesh.h"

namespace irr
{
namespace io
{
	class IFileSystem;
	class IReadFile;
}
namespace scene
{
	class ITextSceneNode;

	//! A scene node for displaying terrain using the geo mip map algorithm.
	class CTerrainSceneNode : public ITerrainSceneNode
	{
	public:

		//! constructor
		//! \param parent: The node which this node is a child of.  Making this node a child of another node, or
		//! making it a parent of another node is yet untested and most likely does not work properly.
		//! \param mgr: Pointer to the scene manager.
		//! \param id: The id of the node
		//! \param maxLOD: The maximum LOD ( Level of Detail ) for the node.
		//! \param patchSize: An E_GEOMIPMAP_PATCH_SIZE enumeration defining the size of each patch of the terrain.
		//! \param position: The absolute position of this node.
		//! \param rotation: The absolute rotation of this node. ( NOT YET IMPLEMENTED )
		//! \param scale: The scale factor for the terrain.  If you're using a heightmap of size 128x128 and would like
		//! your terrain to be 12800x12800 in game units, then use a scale factor of ( core::vector ( 100.0f, 100.0f, 100.0f ).
		//! If you use a Y scaling factor of 0.0f, then your terrain will be flat.
		CTerrainSceneNode(ISceneNode* parent, ISceneManager* mgr, io::IFileSystem* fs, s32 id,
			s32 maxLOD = 4, E_TERRAIN_PATCH_SIZE patchSize = ETPS_17,
			const core::vector3df& position = core::vector3df(0.0f, 0.0f, 0.0f),
			const core::vector3df& rotation = core::vector3df(0.0f, 0.0f, 0.0f),
			const core::vector3df& scale = core::vector3df(1.0f, 1.0f, 1.0f));

		virtual ~CTerrainSceneNode();

		//! Initializes the terrain data.  Loads the vertices from the heightMapFile.
		virtual bool loadHeightMap(io::IReadFile* file,
			video::SColor vertexColor = video::SColor ( 255, 255, 255, 255 ), s32 smoothFactor = 0 );

		//! Initializes the terrain data.  Loads the vertices from the heightMapFile.
		virtual bool loadHeightMapRAW(io::IReadFile* file, s32 bitsPerPixel = 16,
			video::SColor vertexColor = video::SColor ( 255, 255, 255, 255 ), s32 smoothFactor = 0 );

		//! Returns the material based on the zero based index i. This scene node only uses
		//! 1 material.
		//! \param i: Zero based index i. UNUSED, left in for virtual purposes.
		//! \return Returns the single material this scene node uses.
		virtual video::SMaterial& getMaterial ( u32 i )
		{
			return Mesh.getMeshBuffer(i)->getMaterial();
		}

		//! Returns amount of materials used by this scene node ( always 1 )
		//! \return Returns current count of materials used by this scene node ( always 1 )
		virtual u32 getMaterialCount() const
		{
			return Mesh.getMeshBufferCount();
		}

		//! Gets the last scaling factor applied to the scene node.  This value only represents the
		//! last scaling factor presented to the node.  For instance, if you make create the node
		//! with a scale factor of ( 1.0f, 1.0f, 1.0f ) then call setScale ( 50.0f, 5.0f, 50.0f ),
		//! then make another call to setScale with the values ( 2.0f, 2.0f, 2.0f ), this will return
		//! core::vector3df ( 2.0f, 2.0f, 2.0f ), although the total scaling of the scene node is
		//! core::vector3df ( 100.0f, 10.0f, 100.0f ).
		//! \return Returns the last scaling factor passed to the scene node.
		virtual const core::vector3df& getScale() const
		{
			return TerrainData.Scale;
		}

		//! Scales the scene nodes vertices by the vector specified.
		//! \param scale: Scaling factor to apply to the node.
		virtual void setScale(const core::vector3df& scale);

		//! Gets the last rotation factor applied to the scene node.
		//! \return Returns the last rotation factor applied to the scene node.
		virtual const core::vector3df& getRotation() const
		{
			return TerrainData.Rotation;
		}

		//! Rotates the node. This only modifies the relative rotation of the node.
		//! \param rotation: New rotation of the node in degrees.
		virtual void setRotation(const core::vector3df& rotation);

		//! Sets the pivot point for rotation of this node.
		//! NOTE: The default for the RotationPivot will be the center of the individual tile.
		virtual void setRotationPivot( const core::vector3df& pivot );

		//! Gets the last positioning vector applied to the scene node.
		//! \return Returns the last position vector applied to the scene node.
		virtual const core::vector3df& getPosition() const
		{
			return TerrainData.Position;
		}

		//! Moves the scene nodes vertices by the vector specified.
		//! \param newpos: Vector specifying how much to move each vertex of the scene node.
		virtual void setPosition(const core::vector3df& newpos);

		//! Updates the scene nodes indices if the camera has moved or rotated by a certain
		//! threshold, which can be changed using the SetCameraMovementDeltaThreshold and
		//! SetCameraRotationDeltaThreshold functions.  This also determines if a given patch
		//! for the scene node is within the view frustum and if it's not the indices are not
		//! generated for that patch.
		virtual void OnRegisterSceneNode();

		//! Render the scene node
		virtual void render();

		//! Return the bounding box of the entire terrain.
		virtual const core::aabbox3d<f32>& getBoundingBox() const;

		//! Return the bounding box of a patch
		virtual const core::aabbox3d<f32>& getBoundingBox(s32 patchX, s32 patchZ) const;

		//! Return the number of indices currently used to draw the scene node.
		virtual u32 getIndexCount() const { return IndicesToRender; }

		//! Returns the mesh
		virtual IMesh* getMesh() { return &Mesh; }

		//! Returns a pointer to the buffer used by the terrain (most users will not need this)
		virtual IMeshBuffer* getRenderBuffer() { return &RenderBuffer; }


		//! Gets the meshbuffer data based on a specified Level of Detail.
		//! \param mb: A reference to an SMeshBufferLightMap object
		//! \param LOD: The Level Of Detail you want the indices from.
		virtual void getMeshBufferForLOD(SMeshBufferLightMap& mb, s32 LOD ) const;

		//! Gets the indices for a specified patch at a specified Level of Detail.
		//! \param indices: A reference to an array of u32 indices.
		//! \param patchX: Patch x coordinate.
		//! \param patchZ: Patch z coordinate.
		//! \param LOD: The level of detail to get for that patch.  If -1, then get
		//! the CurrentLOD.  If the CurrentLOD is set to -1, meaning it's not shown,
		//! then it will retrieve the triangles at the highest LOD ( 0 ).
		//! \return: Number of indices put into the buffer.
		virtual s32 getIndicesForPatch(core::array<u32>& indices,
			s32 patchX, s32 patchZ, s32 LOD = 0 );

		//! Populates an array with the CurrentLOD of each patch.
		//! \param LODs: A reference to a core::array<s32> to hold the values
		//! \return Returns the number of elements in the array
		virtual s32 getCurrentLODOfPatches(core::array<s32>& LODs) const;

		//! Manually sets the LOD of a patch
		//! \param patchX: Patch x coordinate.
		//! \param patchZ: Patch z coordinate.
		//! \param LOD: The level of detail to set the patch to.
		virtual void setLODOfPatch( s32 patchX, s32 patchZ, s32 LOD );

		//! Returns center of terrain.
		virtual const core::vector3df& getTerrainCenter() const
		{
			return TerrainData.Center;
		}

		//! Returns center of terrain.
		virtual f32 getHeight( f32 x, f32 y ) const;

		//! Sets the movement camera threshold which is used to determine when to recalculate
		//! indices for the scene node.  The default value is 10.0f.
		virtual void setCameraMovementDelta(f32 delta)
		{
			CameraMovementDelta = delta;
		}

		//! Sets the rotation camera threshold which is used to determine when to recalculate
		//! indices for the scene node.  The default value is 1.0f.
		virtual void setCameraRotationDelta(f32 delta)
		{
			CameraRotationDelta = delta;
		}

		//! Sets whether or not the node should dynamically update it's associated selector when
		//! the geomipmap data changes.
		//! param bVal: Boolean value representing whether or not to update selector dynamically.
		//! NOTE: Temporarily disabled while working out issues with DynamicSelectorUpdate
		virtual void setDynamicSelectorUpdate(bool bVal ) { DynamicSelectorUpdate = false; }
		//virtual void setDynamicSelectorUpdate ( bool bVal ) { DynamicSelectorUpdate = bVal; }

		//! Override the default generation of distance thresholds for determining the LOD a patch
		//! is rendered at. If any LOD is overridden, then the scene node will no longer apply
		//! scaling factors to these values. If you override these distances, and then apply
		//! a scale to the scene node, it is your responsibility to update the new distances to
		//! work best with your new terrain size.
		virtual bool overrideLODDistance( s32 LOD, f64 newDistance );

		//! Scales the two textures
		virtual void scaleTexture(f32 scale = 1.0f, f32 scale2 = 0.0f);

		//! Returns type of the scene node
		virtual ESCENE_NODE_TYPE getType() const {return ESNT_TERRAIN;}

		//! Writes attributes of the scene node.
		virtual void serializeAttributes(io::IAttributes* out,
				io::SAttributeReadWriteOptions* options=0) const;

		//! Reads attributes of the scene node.
		virtual void deserializeAttributes(io::IAttributes* in,
				io::SAttributeReadWriteOptions* options=0);

		//! Creates a clone of this scene node and its children.
		virtual ISceneNode* clone(ISceneNode* newParent,
				ISceneManager* newManager);

	private:

		friend class CTerrainTriangleSelector;

		struct SPatch
		{
			SPatch()
			: CurrentLOD(-1), Top(0), Bottom(0), Right(0), Left(0)
			{
			}

			s32			CurrentLOD;
			core::aabbox3df		BoundingBox;
			core::vector3df		Center;
			SPatch*			Top;
			SPatch*			Bottom;
			SPatch*			Right;
			SPatch*			Left;
		};

		struct STerrainData
		{
			STerrainData()
			: Size(0), PatchSize(0), CalcPatchSize(0),
				PatchCount(0), MaxLOD(0),
				BoundingBox(core::aabbox3df( 99999.9f, 99999.9f, 99999.9f, -99999.9f, -99999.9f, -99999.9f)),
				LODDistanceThreshold(0), Patches(0)
			{
			}

			STerrainData(s32 patchSize, s32 maxLOD, const core::vector3df& position, const core::vector3df& rotation, const core::vector3df& scale)
			: Size(0), Position(position), Rotation(rotation), Scale(scale),
				PatchSize(patchSize), CalcPatchSize(patchSize-1),
				PatchCount(0), MaxLOD(maxLOD),
				BoundingBox(core::aabbox3df( 99999.9f, 99999.9f, 99999.9f, -99999.9f, -99999.9f, -99999.9f)),
				LODDistanceThreshold(0), Patches(0)
			{
			}

			s32		Size;
			core::vector3df	Position;
			core::vector3df	Rotation;
			core::vector3df RotationPivot;
			core::vector3df	Scale;
			core::vector3df Center;
			s32		PatchSize;
			s32		CalcPatchSize;
			s32		PatchCount;
			s32		MaxLOD;
			core::aabbox3df	BoundingBox;
			f64*		LODDistanceThreshold;
			SPatch*		Patches;
		};

		virtual void preRenderLODCalculations();
		virtual void preRenderIndicesCalculations();

		//! get indices when generating index data for patches at varying levels of detail.
		u32 getIndex(const s32 PatchX, const s32 PatchZ, const s32 PatchIndex, u32 vX, u32 vZ) const;

		//! smooth the terrain
		void smoothTerrain(SMeshBufferLightMap* mb, s32 smoothFactor);

		//! calculate smooth normals
		void calculateNormals(SMeshBufferLightMap* mb);

		//! create patches, stuff that needs to only be done once for patches goes here.
		void createPatches();

		//! calculate the internal STerrainData structure
		void calculatePatchData();

		//! calculate or recalculate the distance thresholds
		void calculateDistanceThresholds(bool scalechanged = false);

		//! sets the CurrentLOD of all patches to the specified LOD
		void setCurrentLODOfPatches(s32 i);

		//! sets the CurrentLOD of TerrainData patches to the LODs specified in the array
		void setCurrentLODOfPatches(const core::array<s32>& lodarray);

		//! Apply transformation changes( scale, position, rotation )
		void applyTransformation();

		STerrainData TerrainData;
		SMesh Mesh;
		SMeshBufferLightMap RenderBuffer;
		u32 VerticesToRender;
		u32 IndicesToRender;

		bool DynamicSelectorUpdate;
		bool OverrideDistanceThreshold;
		bool UseDefaultRotationPivot;
		bool ForceRecalculation;

		core::vector3df	OldCameraPosition;
		core::vector3df	OldCameraRotation;
		f32 CameraMovementDelta;
		f32 CameraRotationDelta;

		// needed for (de)serialization
		f32 TCoordScale1;
		f32 TCoordScale2;
		core::stringc HeightmapFile;
		io::IFileSystem* FileSystem;
	};


} // end namespace scene
} // end namespace irr

#endif // __C_TERRAIN_SCENE_NODE_H__

