// ----------------------------------------------------------------------------
// Blitz-Irrlicht 1.8.3 Wrapper by Ed Musolino 2015
// Based off of Frank Dodd's Irrlicht 1.7 Freebasic Wrapper (2006-2010)
// ----------------------------------------------------------------------------

//  This software is provided 'as-is', without any express Or implied
//  warranty.  In no event will the authors be held liable For any damages
//  arising from the use of this software.

//  Permission is granted To anyone To use this software For any purpose,
//  including commercial applications, And To alter it And redistribute it
//  freely, subject To the following restrictions:

//  1. The Origin of this software must Not be misrepresented; you must not
//     claim that you wrote the original software. If you use this software
//     in a product, an acknowledgement in the product documentation would be
//     appreciated but is Not required.
//  2. Altered source versions must be clearly marked as such, And must Not be
//     misrepresented as being the original software.
//  3. This notice may Not be removed Or altered from any source distribution.
//
// /////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2002-2009 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 TerrainTileSceneNode 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.

#include "IVideoDriver.h"
#include "ISceneManager.h"
#include "ICameraSceneNode.h"
#include "SMeshBufferLightMap.h"
#include "SViewFrustum.h"
#include "irrMath.h"
#include "os.h"
#include "IGUIFont.h"
#include "IFileSystem.h"
#include "IReadFile.h"
#include "ITextSceneNode.h"
#include "IAnimatedMesh.h"
#include "CTerrainTileSceneNode.h"
#include "CTerrainTriangleSelector.h"

namespace irr
{
namespace scene
{

	//! constructor
	CTerrainTileSceneNode::CTerrainTileSceneNode(ISceneNode* parent, ISceneManager* mgr,
			io::IFileSystem* fs, s32 id, s32 maxLOD, E_TERRAIN_PATCH_SIZE patchSize,
			const core::vector3df& position,
			const core::vector3df& rotation,
			const core::vector3df& scale)
	: ITerrainSceneNode(parent, mgr, id, position, rotation, scale),
	TerrainData(patchSize, maxLOD, position, rotation, scale), RenderBuffer(0),
	VerticesToRender(0), IndicesToRender(0), DynamicSelectorUpdate(false),
	OverrideDistanceThreshold(false), UseDefaultRotationPivot(true), ForceRecalculation(false),
	OldCameraPosition(core::vector3df(-99999.9f, -99999.9f, -99999.9f)),
	OldCameraRotation(core::vector3df(-99999.9f, -99999.9f, -99999.9f)),
	OldCameraUp(core::vector3df(-99999.9f, -99999.9f, -99999.9f)),
	CameraMovementDelta(10.0f), CameraRotationDelta(1.0f),CameraFOVDelta(0.1f),
	TCoordScale1(1.0f), TCoordScale2(1.0f), FileSystem(fs)
	{
		#ifdef _DEBUG
		setDebugName("CTerrainTileSceneNode");
		#endif

		RenderBuffer = new CDynamicMeshBuffer(video::EVT_2TCOORDS, video::EIT_16BIT);
		RenderBuffer->setHardwareMappingHint(scene::EHM_STATIC, scene::EBT_VERTEX);
		RenderBuffer->setHardwareMappingHint(scene::EHM_DYNAMIC, scene::EBT_INDEX);

		if (FileSystem)
			FileSystem->grab();

		setAutomaticCulling( scene::EAC_OFF );

		adjacentTiles[ADJACENT_TOP] = NULL;
		adjacentTiles[ADJACENT_BOTTOM] = NULL;
		adjacentTiles[ADJACENT_LEFT] = NULL;
		adjacentTiles[ADJACENT_RIGHT] = NULL;
	}


	//! destructor
	CTerrainTileSceneNode::~CTerrainTileSceneNode()
	{
		delete [] TerrainData.Patches;

		if (FileSystem)
			FileSystem->drop();

		if (RenderBuffer)
			RenderBuffer->drop();
	}


	//! Initializes the terrain data. Loads the vertices from the heightMapFile
	bool CTerrainTileSceneNode::loadHeightMap(io::IReadFile* file, video::SColor vertexColor,
			s32 smoothFactor)
	{
		if (!file)
			return false;

		Mesh.MeshBuffers.clear();
		video::IImage* heightMap = SceneManager->getVideoDriver()->createImageFromFile(file);

		HeightmapFile = file->getFileName();

        bool result = loadHeightMap( file, vertexColor,smoothFactor );

		// drop heightMap, no longer needed
		heightMap->drop();

        return result;
	}

	//! Initializes the terrain data. Loads the vertices from the heightMapFile
	bool CTerrainTileSceneNode::loadHeightMapImage( video::IImage* heightMap,
													core::position2d<s32> dataPosition,
													s32 terrainSize,
													video::SColor vertexColor,
													s32 smoothFactor)
	{
		if (!heightMap)
		{
			return false;
		}

		// Get the dimension of the heightmap data
		TerrainData.Size = terrainSize;
//		TerrainData.Size = heightMap->getDimension().Width;

		switch (TerrainData.PatchSize)
		{
			case ETPS_9:
				if (TerrainData.MaxLOD > 3)
				{
					TerrainData.MaxLOD = 3;
				}
			break;
			case ETPS_17:
				if (TerrainData.MaxLOD > 4)
				{
					TerrainData.MaxLOD = 4;
				}
			break;
			case ETPS_33:
				if (TerrainData.MaxLOD > 5)
				{
					TerrainData.MaxLOD = 5;
				}
			break;
			case ETPS_65:
				if (TerrainData.MaxLOD > 6)
				{
					TerrainData.MaxLOD = 6;
				}
			break;
			case ETPS_129:
				if (TerrainData.MaxLOD > 7)
				{
					TerrainData.MaxLOD = 7;
				}
			break;
		}

		// --- Generate vertex data from heightmap ----
		// resize the vertex array for the mesh buffer one time (makes loading faster)
		//SMeshBufferLightMap* mb = new SMeshBufferLightMap();

		scene::CDynamicMeshBuffer *mb=0;

		const u32 numVertices = TerrainData.Size * TerrainData.Size;
		if (numVertices <= 65536)
		{
			//small enough for 16bit buffers
			mb=new scene::CDynamicMeshBuffer(video::EVT_2TCOORDS, video::EIT_16BIT);
			RenderBuffer->getIndexBuffer().setType(video::EIT_16BIT);
		}
		else
		{
			//we need 32bit buffers
			mb=new scene::CDynamicMeshBuffer(video::EVT_2TCOORDS, video::EIT_32BIT);
			RenderBuffer->getIndexBuffer().setType(video::EIT_32BIT);
		}

		mb->getVertexBuffer().set_used(numVertices);

		// Read the heightmap to get the vertex data
		// Apply positions changes, scaling changes
		const f32 tdSize = 1.0f/(f32)(TerrainData.Size-1);
		s32 index = 0;
		float fx=0.f;
		float fx2=0.f;
		for (s32 x = 0; x < TerrainData.Size; ++x)
		{
			float fz=0.f;
			float fz2=0.f;
			for (s32 z = 0; z < TerrainData.Size; ++z)
			{
				video::S3DVertex2TCoords& vertex= static_cast<video::S3DVertex2TCoords*>(mb->getVertexBuffer().pointer())[index++];
				vertex.Normal.set(0.0f, 1.0f, 0.0f);
				vertex.Color = vertexColor;
				vertex.Pos.X = fx;
//				vertex.Pos.Y = (f32) heightMap->getPixel(TerrainData.Size-x-1,z).getLuminance();
				vertex.Pos.Y = (f32) heightMap->getPixel(x + dataPosition.X, z + dataPosition.Y).getLuminance();
				vertex.Pos.Z = fz;

				vertex.TCoords.X = vertex.TCoords2.X = 1.f-fx2;
				vertex.TCoords.Y = vertex.TCoords2.Y = fz2;

				++fz;
				fz2 += tdSize;
			}
			++fx;
			fx2 += tdSize;
		}

		smoothTerrain(mb, smoothFactor);

		// calculate smooth normals for the vertices
		calculateNormals(mb);

		// add the MeshBuffer to the mesh
		Mesh.addMeshBuffer(mb);

		// We copy the data to the renderBuffer, after the normals have been calculated.
		RenderBuffer->getVertexBuffer().set_used(numVertices);

		for (u32 i = 0; i < numVertices; ++i)
		{
			RenderBuffer->getVertexBuffer()[i] = mb->getVertexBuffer()[i];
			RenderBuffer->getVertexBuffer()[i].Pos *= TerrainData.Scale;
			RenderBuffer->getVertexBuffer()[i].Pos += TerrainData.Position;
		}

		// We no longer need the mb
		mb->drop();

		// calculate all the necessary data for the patches and the terrain
		calculateDistanceThresholds();
		createPatches();
		calculatePatchData();

		// set the default rotation pivot point to the terrain nodes center
		TerrainData.RotationPivot = TerrainData.Center;

		// Rotate the vertices of the terrain by the rotation
		// specified. Must be done after calculating the terrain data,
		// so we know what the current center of the terrain is.
		setRotation(TerrainData.Rotation);

		// Pre-allocate memory for indices

		RenderBuffer->getIndexBuffer().set_used(
				TerrainData.PatchCount * TerrainData.PatchCount *
				TerrainData.CalcPatchSize * TerrainData.CalcPatchSize * 6);

		RenderBuffer->setDirty();

		return true;
	}


	//! Initializes the terrain data. Loads the vertices from the heightMapFile
	bool CTerrainTileSceneNode::loadHeightMapRAW(io::IReadFile* file,
			s32 bitsPerPixel, bool signedData, bool floatVals,
			s32 width, video::SColor vertexColor, s32 smoothFactor)
	{
		if (!file)
			return false;
		if (floatVals && bitsPerPixel != 32)
			return false;

		Mesh.MeshBuffers.clear();

		const s32 bytesPerPixel = bitsPerPixel / 8;

		// Get the dimension of the heightmap data
		const s32 filesize = file->getSize();
		if (!width)
			TerrainData.Size = core::floor32(sqrtf((f32)(filesize / bytesPerPixel)));
		else
		{
			if ((filesize-file->getPos())/bytesPerPixel>width*width)
			{
				return false;
			}
			TerrainData.Size = width;
		}

		switch (TerrainData.PatchSize)
		{
			case ETPS_9:
				if (TerrainData.MaxLOD > 3)
				{
					TerrainData.MaxLOD = 3;
				}
			break;
			case ETPS_17:
				if (TerrainData.MaxLOD > 4)
				{
					TerrainData.MaxLOD = 4;
				}
			break;
			case ETPS_33:
				if (TerrainData.MaxLOD > 5)
				{
					TerrainData.MaxLOD = 5;
				}
			break;
			case ETPS_65:
				if (TerrainData.MaxLOD > 6)
				{
					TerrainData.MaxLOD = 6;
				}
			break;
			case ETPS_129:
				if (TerrainData.MaxLOD > 7)
				{
					TerrainData.MaxLOD = 7;
				}
			break;
		}

		// --- Generate vertex data from heightmap ----
		// resize the vertex array for the mesh buffer one time (makes loading faster)
		scene::CDynamicMeshBuffer *mb=0;
		const u32 numVertices = TerrainData.Size * TerrainData.Size;
		if (numVertices <= 65536)
		{
			//small enough for 16bit buffers
			mb=new scene::CDynamicMeshBuffer(video::EVT_2TCOORDS, video::EIT_16BIT);
			RenderBuffer->getIndexBuffer().setType(video::EIT_16BIT);
		}
		else
		{
			//we need 32bit buffers
			mb=new scene::CDynamicMeshBuffer(video::EVT_2TCOORDS, video::EIT_32BIT);
			RenderBuffer->getIndexBuffer().setType(video::EIT_32BIT);
		}

		mb->getVertexBuffer().reallocate(numVertices);

		video::S3DVertex2TCoords vertex;
		vertex.Normal.set(0.0f, 1.0f, 0.0f);
		vertex.Color = vertexColor;

		// Read the heightmap to get the vertex data
		// Apply positions changes, scaling changes
		const f32 tdSize = 1.0f/(f32)(TerrainData.Size-1);
		float fx=0.f;
		float fx2=0.f;
		for (s32 x = 0; x < TerrainData.Size; ++x)
		{
			float fz=0.f;
			float fz2=0.f;
			for (s32 z = 0; z < TerrainData.Size; ++z)
			{
				bool failure=false;
				vertex.Pos.X = fx;
				if (floatVals)
				{
					if (file->read(&vertex.Pos.Y, bytesPerPixel) != bytesPerPixel)
						failure=true;
				}
				else if (signedData)
				{
					switch (bytesPerPixel)
					{
						case 1:
						{
							s8 val;
							if (file->read(&val, bytesPerPixel) != bytesPerPixel)
								failure=true;
							vertex.Pos.Y=val;
						}
						break;
						case 2:
						{
							s16 val;
							if (file->read(&val, bytesPerPixel) != bytesPerPixel)
								failure=true;
							vertex.Pos.Y=val/256.f;
						}
						break;
						case 4:
						{
							s32 val;
							if (file->read(&val, bytesPerPixel) != bytesPerPixel)
								failure=true;
							vertex.Pos.Y=val/16777216.f;
						}
						break;
					}
				}
				else
				{
					switch (bytesPerPixel)
					{
						case 1:
						{
							u8 val;
							if (file->read(&val, bytesPerPixel) != bytesPerPixel)
								failure=true;
							vertex.Pos.Y=val;
						}
						break;
						case 2:
						{
							u16 val;
							if (file->read(&val, bytesPerPixel) != bytesPerPixel)
								failure=true;
							vertex.Pos.Y=val/256.f;
						}
						break;
						case 4:
						{
							u32 val;
							if (file->read(&val, bytesPerPixel) != bytesPerPixel)
								failure=true;
							vertex.Pos.Y=val/16777216.f;
						}
						break;
					}
				}
				if (failure)
				{
					mb->drop();
					return false;
				}
				vertex.Pos.Z = fz;

				vertex.TCoords.X = vertex.TCoords2.X = 1.f-fx2;
				vertex.TCoords.Y = vertex.TCoords2.Y = fz2;

				mb->getVertexBuffer().push_back(vertex);
				++fz;
				fz2 += tdSize;
			}
			++fx;
			fx2 += tdSize;
		}

		smoothTerrain(mb, smoothFactor);

		// calculate smooth normals for the vertices
		calculateNormals(mb);

		// add the MeshBuffer to the mesh
		Mesh.addMeshBuffer(mb);
		const u32 vertexCount = mb->getVertexCount();

		// We copy the data to the renderBuffer, after the normals have been calculated.
		RenderBuffer->getVertexBuffer().set_used(vertexCount);

		for (u32 i = 0; i < vertexCount; i++)
		{
			RenderBuffer->getVertexBuffer()[i] = mb->getVertexBuffer()[i];
			RenderBuffer->getVertexBuffer()[i].Pos *= TerrainData.Scale;
			RenderBuffer->getVertexBuffer()[i].Pos += TerrainData.Position;
		}

		// We no longer need the mb
		mb->drop();

		// calculate all the necessary data for the patches and the terrain
		calculateDistanceThresholds();
		createPatches();
		calculatePatchData();

		// set the default rotation pivot point to the terrain nodes center
		TerrainData.RotationPivot = TerrainData.Center;

		// Rotate the vertices of the terrain by the rotation specified. Must be done
		// after calculating the terrain data, so we know what the current center of the
		// terrain is.
		setRotation(TerrainData.Rotation);

		// Pre-allocate memory for indices
		RenderBuffer->getIndexBuffer().set_used(
				TerrainData.PatchCount*TerrainData.PatchCount*
				TerrainData.CalcPatchSize*TerrainData.CalcPatchSize*6);

		return true;
	}


	//! Sets the scale of the scene node.
	//! \param scale: New scale of the node
	void CTerrainTileSceneNode::setScale(const core::vector3df& scale)
	{
		TerrainData.Scale = scale;
		applyTransformation();
		ForceRecalculation = true;
	}


	//! Sets the rotation of the node. This only modifies
	//! the relative rotation of the node.
	//! \param rotation: New rotation of the node in degrees.
	void CTerrainTileSceneNode::setRotation(const core::vector3df& rotation)
	{
		TerrainData.Rotation = rotation;
		applyTransformation();
		ForceRecalculation = true;
	}


	//! Sets the pivot point for rotation of this node. This is useful for the TiledTerrainManager to
	//! rotate all terrain tiles around a global world point.
	//! NOTE: The default for the RotationPivot will be the center of the individual tile.
	void CTerrainTileSceneNode::setRotationPivot(const core::vector3df& pivot)
	{
		UseDefaultRotationPivot = false;
		TerrainData.RotationPivot = pivot;
	}


	//! Sets the position of the node.
	//! \param newpos: New postition of the scene node.
	void CTerrainTileSceneNode::setPosition(const core::vector3df& newpos)
	{
		TerrainData.Position = newpos;
		applyTransformation();
		ForceRecalculation = true;
	}


	//! Apply transformation changes(scale, position, rotation)
	void CTerrainTileSceneNode::applyTransformation()
	{
		if (!Mesh.getMeshBufferCount())
			return;

		TerrainData.Position = TerrainData.Position;
		s32 vtxCount = Mesh.getMeshBuffer(0)->getVertexCount();
		core::matrix4 rotMatrix;
		rotMatrix.setRotationDegrees(TerrainData.Rotation);

		for (s32 i = 0; i < vtxCount; ++i)
		{
			RenderBuffer->getVertexBuffer()[i].Pos = Mesh.getMeshBuffer(0)->getPosition(i) * TerrainData.Scale + TerrainData.Position;

			RenderBuffer->getVertexBuffer()[i].Pos -= TerrainData.RotationPivot;
			rotMatrix.inverseRotateVect(RenderBuffer->getVertexBuffer()[i].Pos);
			RenderBuffer->getVertexBuffer()[i].Pos += TerrainData.RotationPivot;
		}

		calculateDistanceThresholds(true);
		calculatePatchData();

		RenderBuffer->setDirty(EBT_VERTEX);
	}


	//! 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.
	void CTerrainTileSceneNode::OnRegisterSceneNode()
	{
		if (!IsVisible || !SceneManager->getActiveCamera())
			return;

		preRenderLODCalculations();
		preRenderIndicesCalculations();
		ISceneNode::OnRegisterSceneNode();
		ForceRecalculation = false;
	}


	void CTerrainTileSceneNode::preRenderLODCalculations()
	{
		scene::ICameraSceneNode * camera = SceneManager->getActiveCamera();
		if(!camera)
			return;

		SceneManager->registerNodeForRendering(this);
		// Do Not call ISceneNode::OnRegisterSceneNode(), this node should have no children

		// Determine the camera rotation, based on the camera direction.
		const core::vector3df cameraPosition = camera->getAbsolutePosition();
		const core::vector3df cameraRotation = core::line3d<f32>(cameraPosition, camera->getTarget()).getVector().getHorizontalAngle();
		core::vector3df cameraUp = camera->getUpVector();
		cameraUp.normalize();
		const f32 CameraFOV = SceneManager->getActiveCamera()->getFOV();

		// Only check on the Camera's Y Rotation
		if (!ForceRecalculation)
		{
			if ((fabsf(cameraRotation.X - OldCameraRotation.X) < CameraRotationDelta) &&
				(fabsf(cameraRotation.Y - OldCameraRotation.Y) < CameraRotationDelta))
			{
				if ((fabs(cameraPosition.X - OldCameraPosition.X) < CameraMovementDelta) &&
					(fabs(cameraPosition.Y - OldCameraPosition.Y) < CameraMovementDelta) &&
					(fabs(cameraPosition.Z - OldCameraPosition.Z) < CameraMovementDelta))
				{
					if (fabs(CameraFOV-OldCameraFOV) < CameraFOVDelta &&
						cameraUp.dotProduct(OldCameraUp) > (1.f - (cos(core::DEGTORAD * CameraRotationDelta))))
					{
						return;
					}
				}
			}
		}

		OldCameraPosition = cameraPosition;
		OldCameraRotation = cameraRotation;
		OldCameraUp = cameraUp;
		OldCameraFOV = CameraFOV;

		const SViewFrustum* frustum = SceneManager->getActiveCamera()->getViewFrustum();

		// Determine each patches LOD based on distance from camera (and whether or not they are in
		// the view frustum).
		const s32 count = TerrainData.PatchCount * TerrainData.PatchCount;
		for (s32 j = 0; j < count; ++j)
		{
			if (frustum->getBoundingBox().intersectsWithBox(TerrainData.Patches[j].BoundingBox))
			{
				const f32 distance = (cameraPosition.X - TerrainData.Patches[j].Center.X) * (cameraPosition.X - TerrainData.Patches[j].Center.X) +
					(cameraPosition.Y - TerrainData.Patches[j].Center.Y) * (cameraPosition.Y - TerrainData.Patches[j].Center.Y) +
					(cameraPosition.Z - TerrainData.Patches[j].Center.Z) * (cameraPosition.Z - TerrainData.Patches[j].Center.Z);

				for (s32 i = TerrainData.MaxLOD - 1; i >= 0; --i)
				{
					if (distance >= TerrainData.LODDistanceThreshold[i])
					{
						TerrainData.Patches[j].CurrentLOD = i;
						break;
					}
					//else if (i == 0)
					{
						// If we've turned off a patch from viewing, because of the frustum, and now we turn around and it's
						// too close, we need to turn it back on, at the highest LOD. The if above doesn't catch this.
						TerrainData.Patches[j].CurrentLOD = 0;
					}
				}
			}
			else
			{
				TerrainData.Patches[j].CurrentLOD = -1;
			}
		}
	}


	void CTerrainTileSceneNode::preRenderIndicesCalculations()
	{
		scene::IIndexBuffer& indexBuffer = RenderBuffer->getIndexBuffer();
		IndicesToRender = 0;
		indexBuffer.set_used(0);

		s32 index = 0;
		// Then generate the indices for all patches that are visible.
		for (s32 i = 0; i < TerrainData.PatchCount; ++i)
		{
			for (s32 j = 0; j < TerrainData.PatchCount; ++j)
			{
				if (TerrainData.Patches[index].CurrentLOD >= 0)
				{
					s32 x = 0;
					s32 z = 0;

					// calculate the step we take this patch, based on the patches current LOD
					const s32 step = 1 << TerrainData.Patches[index].CurrentLOD;

					// Loop through patch and generate indices
					while (z < TerrainData.CalcPatchSize)
					{
						const s32 index11 = getIndex(j, i, index, x, z);
						const s32 index21 = getIndex(j, i, index, x + step, z);
						const s32 index12 = getIndex(j, i, index, x, z + step);
						const s32 index22 = getIndex(j, i, index, x + step, z + step);

						indexBuffer.push_back(index12);
						indexBuffer.push_back(index11);
						indexBuffer.push_back(index22);
						indexBuffer.push_back(index22);
						indexBuffer.push_back(index11);
						indexBuffer.push_back(index21);
						IndicesToRender+=6;

						// increment index position horizontally
						x += step;

						// we've hit an edge
						if (x >= TerrainData.CalcPatchSize)
						{
							x = 0;
							z += step;
						}
					}
				}
				++index;
			}
		}

		RenderBuffer->setDirty(EBT_INDEX);

		if (DynamicSelectorUpdate && TriangleSelector)
		{
			CTerrainTriangleSelector* selector = (CTerrainTriangleSelector*)TriangleSelector;
			selector->setTriangleData(this, -1);
		}
	}


	//! Render the scene node
	void CTerrainTileSceneNode::render()
	{
		if (!IsVisible || !SceneManager->getActiveCamera())
			return;

		if (!Mesh.getMeshBufferCount())
			return;

		video::IVideoDriver* driver = SceneManager->getVideoDriver();

		driver->setTransform (video::ETS_WORLD, core::IdentityMatrix);
		driver->setMaterial(Mesh.getMeshBuffer(0)->getMaterial());

		RenderBuffer->getIndexBuffer().set_used(IndicesToRender);

		// For use with geomorphing
		driver->drawMeshBuffer(RenderBuffer);

		RenderBuffer->getIndexBuffer().set_used(RenderBuffer->getIndexBuffer().allocated_size());

		// for debug purposes only:
		if (DebugDataVisible)
		{
			video::SMaterial m;
			m.Lighting = false;
			driver->setMaterial(m);
			if (DebugDataVisible & scene::EDS_BBOX)
				driver->draw3DBox(TerrainData.BoundingBox, video::SColor(255,255,255,255));

			const s32 count = TerrainData.PatchCount * TerrainData.PatchCount;
			s32 visible = 0;
			if (DebugDataVisible & scene::EDS_BBOX_BUFFERS)
			{
				for (s32 j = 0; j < count; ++j)
				{
					driver->draw3DBox(TerrainData.Patches[j].BoundingBox, video::SColor(255,255,0,0));
					visible += (TerrainData.Patches[j].CurrentLOD >= 0);
				}
			}

			if (DebugDataVisible & scene::EDS_NORMALS)
			{
				IAnimatedMesh * arrow = SceneManager->addArrowMesh(
						"__debugnormal", 0xFFECEC00,
						0xFF999900, 4, 8, 1.f, 0.6f, 0.05f,
						0.3f);
				if (0 == arrow)
				{
					arrow = SceneManager->getMesh( "__debugnormal");
				}
				IMesh *mesh = arrow->getMesh(0);

				// find a good scaling factor

				core::matrix4 m2;

				// draw normals
				driver->setTransform(video::ETS_WORLD, core::IdentityMatrix);
				for (u32 i=0; i != RenderBuffer->getVertexCount(); ++i)
				{
					const core::vector3df& v = RenderBuffer->getNormal(i);
					// align to v->Normal
					if (core::vector3df(0,-1,0)==v)
					{
						m2.makeIdentity();
						m2[5]=-m2[5];
					}
					else
					{
						core::quaternion quatRot;
						m2=quatRot.rotationFromTo(v,core::vector3df(0,1,0)).getMatrix();
					}

					m2.setTranslation(RenderBuffer->getPosition(i));
					m2=AbsoluteTransformation*m2;

					driver->setTransform(video::ETS_WORLD, m2 );
					for (u32 a = 0; a != mesh->getMeshBufferCount(); ++a)
						driver->drawMeshBuffer(mesh->getMeshBuffer(a));
				}
				driver->setTransform(video::ETS_WORLD, AbsoluteTransformation);
			}
		}
	}


	//! Return the bounding box of the entire terrain.
	const core::aabbox3d<f32>& CTerrainTileSceneNode::getBoundingBox() const
	{
		return TerrainData.BoundingBox;
	}


	//! Return the bounding box of a patch
	const core::aabbox3d<f32>& CTerrainTileSceneNode::getBoundingBox(s32 patchX, s32 patchZ) const
	{
		return TerrainData.Patches[patchX * TerrainData.PatchCount + patchZ].BoundingBox;
	}


	//! Gets the meshbuffer data based on a specified Level of Detail.
	//! \param mb: A reference to an SMeshBuffer object
	//! \param LOD: The Level Of Detail you want the indices from.
	void CTerrainTileSceneNode::getMeshBufferForLOD(IDynamicMeshBuffer& mb, s32 LOD ) const
	{
		if (!Mesh.getMeshBufferCount())
			return;

		LOD = core::clamp(LOD, 0, TerrainData.MaxLOD - 1);

		const u32 numVertices = Mesh.getMeshBuffer(0)->getVertexCount();
		mb.getVertexBuffer().reallocate(numVertices);
		video::S3DVertex2TCoords* vertices = (video::S3DVertex2TCoords*)Mesh.getMeshBuffer(0)->getVertices();

		for (u32 n=0; n<numVertices; ++n)
			mb.getVertexBuffer().push_back(vertices[n]);

		mb.getIndexBuffer().setType(RenderBuffer->getIndexBuffer().getType());

		// calculate the step we take for all patches, since LOD is the same
		const s32 step = 1 << LOD;

		// Generate the indices for all patches at the specified LOD
		s32 index = 0;
		for (s32 i=0; i<TerrainData.PatchCount; ++i)
		{
			for (s32 j=0; j<TerrainData.PatchCount; ++j)
			{
				s32 x = 0;
				s32 z = 0;

				// Loop through patch and generate indices
				while (z < TerrainData.CalcPatchSize)
				{
					const s32 index11 = getIndex(j, i, index, x, z);
					const s32 index21 = getIndex(j, i, index, x + step, z);
					const s32 index12 = getIndex(j, i, index, x, z + step);
					const s32 index22 = getIndex(j, i, index, x + step, z + step);

					mb.getIndexBuffer().push_back(index12);
					mb.getIndexBuffer().push_back(index11);
					mb.getIndexBuffer().push_back(index22);
					mb.getIndexBuffer().push_back(index22);
					mb.getIndexBuffer().push_back(index11);
					mb.getIndexBuffer().push_back(index21);

					// increment index position horizontally
					x += step;

					if (x >= TerrainData.CalcPatchSize) // we've hit an edge
					{
						x = 0;
						z += step;
					}
				}
				++index;
			}
		}
	}


	//! Gets the indices for a specified patch at a specified Level of Detail.
	//! \param mb: 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 if indices put into the buffer.
	s32 CTerrainTileSceneNode::getIndicesForPatch(core::array<u32>& indices, s32 patchX, s32 patchZ, s32 LOD)
	{
		if (patchX < 0 || patchX > TerrainData.PatchCount-1 ||
				patchZ < 0 || patchZ > TerrainData.PatchCount-1)
			return -1;

		if (LOD < -1 || LOD > TerrainData.MaxLOD - 1)
			return -1;

		core::array<s32> cLODs;
		bool setLODs = false;

		// If LOD of -1 was passed in, use the CurrentLOD of the patch specified
		if (LOD == -1)
		{
			LOD = TerrainData.Patches[patchX * TerrainData.PatchCount + patchZ].CurrentLOD;
		}
		else
		{
			getCurrentLODOfPatches(cLODs);
			setCurrentLODOfPatches(LOD);
			setLODs = true;
		}

		if (LOD < 0)
			return -2; // Patch not visible, don't generate indices.

		// calculate the step we take for this LOD
		const s32 step = 1 << LOD;

		// Generate the indices for the specified patch at the specified LOD
		const s32 index = patchX * TerrainData.PatchCount + patchZ;

		s32 x = 0;
		s32 z = 0;

		indices.set_used(TerrainData.PatchSize * TerrainData.PatchSize * 6);

		// Loop through patch and generate indices
		s32 rv=0;
		while (z<TerrainData.CalcPatchSize)
		{
			const s32 index11 = getIndex(patchZ, patchX, index, x, z);
			const s32 index21 = getIndex(patchZ, patchX, index, x + step, z);
			const s32 index12 = getIndex(patchZ, patchX, index, x, z + step);
			const s32 index22 = getIndex(patchZ, patchX, index, x + step, z + step);

			indices[rv++] = index12;
			indices[rv++] = index11;
			indices[rv++] = index22;
			indices[rv++] = index22;
			indices[rv++] = index11;
			indices[rv++] = index21;

			// increment index position horizontally
			x += step;

			if (x >= TerrainData.CalcPatchSize) // we've hit an edge
			{
				x = 0;
				z += step;
			}
		}

		if (setLODs)
			setCurrentLODOfPatches(cLODs);

		return rv;
	}


	//! 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
	s32 CTerrainTileSceneNode::getCurrentLODOfPatches(core::array<s32>& LODs) const
	{
		s32 numLODs;
		LODs.clear();

		const s32 count = TerrainData.PatchCount * TerrainData.PatchCount;
		for (numLODs = 0; numLODs < count; numLODs++)
			LODs.push_back(TerrainData.Patches[numLODs].CurrentLOD);

		return LODs.size();
	}


	//! 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.
	void CTerrainTileSceneNode::setLODOfPatch(s32 patchX, s32 patchZ, s32 LOD)
	{
		TerrainData.Patches[patchX * TerrainData.PatchCount + patchZ].CurrentLOD = LOD;
	}


	//! Override the default generation of distance thresholds for determining the LOD a patch
	//! is rendered at.
	bool CTerrainTileSceneNode::overrideLODDistance(s32 LOD, f64 newDistance)
	{
		OverrideDistanceThreshold = true;

		if (LOD < 0 || LOD > TerrainData.MaxLOD - 1)
			return false;

		TerrainData.LODDistanceThreshold[LOD] = newDistance * newDistance;

		return true;
	}

	//! Colors the verticies on the terrain
	//! \param colorMap: the image file containing the color information
	//! \param dataPosition: the coordinate of the color information in the image file
	void CTerrainTileSceneNode::loadColor( video::IImage * colorMap, core::position2d<s32> dataPosition )
	{
		u32 index = 0;
		s32 inversePos = TerrainData.Size - 1;

		for ( s32 x = 0; x < TerrainData.Size; ++x )
		{
			for ( s32 z = 0; z < TerrainData.Size; ++z )
			{
			    RenderBuffer->getVertexBuffer()[index].Color.color = colorMap->getPixel(
						inversePos - (x + dataPosition.X), z + dataPosition.Y ).color;

				++index;
			}
		}

		RenderBuffer->setDirty(EBT_VERTEX);
	}

	//! Adjusts the heights and UV Scaling on the terrain
	//! \param colorMap: the image file containing the color information
	//! \param dataPosition: the coordinate of the color information in the image file
	void CTerrainTileSceneNode::loadStructure( video::IImage * structureMap, core::position2d<s32> dataPosition,
			s32 startRow, s32 endRow )
	{
		u32 index = startRow * TerrainData.Size;
		float imageStep = 1 / (float)structureMap->getDimension().Width;
		float tX = (float)dataPosition.X * imageStep;
		float tY = (float)(dataPosition.Y + startRow) * imageStep;
		s32 inversePos = TerrainData.Size - 1;

		if ( endRow > TerrainData.Size )
			endRow = TerrainData.Size;

		video::S3DVertex2TCoords* Vertices = (video::S3DVertex2TCoords*)Mesh.getMeshBuffer( 0 )->getVertices();

		for ( s32 x = startRow; x < endRow; ++x )
		{
			for ( s32 z = 0; z < TerrainData.Size; ++z )
			{
			    // get the pixel containing the information for this vertex
			    video::SColor thisPixel = structureMap->getPixel(
						inversePos - (x + dataPosition.X), z + dataPosition.Y );

			    // Load the height of the terrain from the alpha pixel
				Vertices[index].Pos.Y = (f32)thisPixel.getAlpha();

                // Load the vertex color from the color data
                Vertices[index].Color = thisPixel;

                // Set the color texture UV based on the data position
                ((video::S3DVertex2TCoords&)RenderBuffer->getVertexBuffer()[index]).TCoords2.X = (f32)(x + dataPosition.X) * imageStep;
                ((video::S3DVertex2TCoords&)RenderBuffer->getVertexBuffer()[index]).TCoords2.Y = (f32)(z + dataPosition.Y) * imageStep;

                // next vertex
				++index;
			}
		}

		RenderBuffer->setDirty(EBT_VERTEX);
	}

	//! resets the position of the node.
	//! \param newpos: New postition of the scene node.
	void CTerrainTileSceneNode::loadPosition ( const core::vector3df& newpos, s32 startRow, s32 endRow )
	{
		TerrainData.Position = newpos;

		if( !Mesh.getMeshBufferCount() )
			return;

		video::S3DVertex2TCoords* meshVertices = (video::S3DVertex2TCoords*)Mesh.getMeshBuffer( 0 )->getVertices();
		s32 vtxCount = Mesh.getMeshBuffer( 0 )->getVertexCount();
		core::matrix4 rotMatrix;
		rotMatrix.setRotationDegrees( TerrainData.Rotation );

		// convert the rows into vertex indicies
		startRow *= TerrainData.Size;
		endRow *= TerrainData.Size;

		if ( endRow > vtxCount )
			endRow = vtxCount;

		for( s32 i = startRow; i < endRow; ++i )
		{
		    RenderBuffer->getVertexBuffer()[i].Pos = meshVertices[i].Pos * TerrainData.Scale + TerrainData.Position;
		    RenderBuffer->getVertexBuffer()[i].Pos -= TerrainData.RotationPivot;

			rotMatrix.inverseRotateVect( RenderBuffer->getVertexBuffer()[i].Pos );
			RenderBuffer->getVertexBuffer()[i].Pos += TerrainData.RotationPivot;
		}

        RenderBuffer->setDirty(EBT_VERTEX);
	}


	//! recalculates the patch data from the new vertex information
	void CTerrainTileSceneNode::loadPatches ( void )
	{
		calculateDistanceThresholds( true );
		calculatePatchData();
		ForceRecalculation = true;
	}


	//! Creates a planar texture mapping on the terrain
	//! \param resolution: resolution of the planar mapping. This is the value
	//! specifying the relation between world space and texture coordinate space.
	void CTerrainTileSceneNode::scaleTexture(f32 resolution, f32 resolution2)
	{
		TCoordScale1 = resolution;
		TCoordScale2 = resolution2;

		const f32 resBySize = resolution / (f32)(TerrainData.Size-1);
		const f32 res2BySize = resolution2 / (f32)(TerrainData.Size-1);
		u32 index = 0;
		f32 xval = 0.f;
		f32 x2val = 0.f;
		for (s32 x=0; x<TerrainData.Size; ++x)
		{
			f32 zval=0.f;
			f32 z2val=0.f;
			for (s32 z=0; z<TerrainData.Size; ++z)
			{
				RenderBuffer->getVertexBuffer()[index].TCoords.X = 1.f-xval;
				RenderBuffer->getVertexBuffer()[index].TCoords.Y = zval;

				if (RenderBuffer->getVertexType()==video::EVT_2TCOORDS)
				{
					if (resolution2 == 0)
					{
						((video::S3DVertex2TCoords&)RenderBuffer->getVertexBuffer()[index]).TCoords2 = RenderBuffer->getVertexBuffer()[index].TCoords;
					}
					else
					{
						((video::S3DVertex2TCoords&)RenderBuffer->getVertexBuffer()[index]).TCoords2.X = 1.f-x2val;
						((video::S3DVertex2TCoords&)RenderBuffer->getVertexBuffer()[index]).TCoords2.Y = z2val;
					}
				}

				++index;
				zval += resBySize;
				z2val += res2BySize;
			}
			xval += resBySize;
			x2val += res2BySize;
		}

		RenderBuffer->setDirty(EBT_VERTEX);
	}


	//! used to get the indices when generating index data for patches at varying levels of detail.
	u32 CTerrainTileSceneNode::getIndex(const s32 PatchX, const s32 PatchZ,
					const s32 PatchIndex, u32 vX, u32 vZ) const
	{
		// top border
		if (vZ == 0)
		{
			if (TerrainData.Patches[PatchIndex].Top &&
				TerrainData.Patches[PatchIndex].CurrentLOD < TerrainData.Patches[PatchIndex].Top->CurrentLOD &&
				(vX % (1 << TerrainData.Patches[PatchIndex].Top->CurrentLOD)) != 0 )
			{
				vX -= vX % (1 << TerrainData.Patches[PatchIndex].Top->CurrentLOD);
			}
		}
		else
		if (vZ == (u32)TerrainData.CalcPatchSize) // bottom border
		{
			if (TerrainData.Patches[PatchIndex].Bottom &&
				TerrainData.Patches[PatchIndex].CurrentLOD < TerrainData.Patches[PatchIndex].Bottom->CurrentLOD &&
				(vX % (1 << TerrainData.Patches[PatchIndex].Bottom->CurrentLOD)) != 0)
			{
				vX -= vX % (1 << TerrainData.Patches[PatchIndex].Bottom->CurrentLOD);
			}
		}

		// left border
		if (vX == 0)
		{
			if (TerrainData.Patches[PatchIndex].Left &&
				TerrainData.Patches[PatchIndex].CurrentLOD < TerrainData.Patches[PatchIndex].Left->CurrentLOD &&
				(vZ % (1 << TerrainData.Patches[PatchIndex].Left->CurrentLOD)) != 0)
			{
				vZ -= vZ % (1 << TerrainData.Patches[PatchIndex].Left->CurrentLOD);
			}
		}
		else
		if (vX == (u32)TerrainData.CalcPatchSize) // right border
		{
			if (TerrainData.Patches[PatchIndex].Right &&
				TerrainData.Patches[PatchIndex].CurrentLOD < TerrainData.Patches[PatchIndex].Right->CurrentLOD &&
				(vZ % (1 << TerrainData.Patches[PatchIndex].Right->CurrentLOD)) != 0)
			{
				vZ -= vZ % (1 << TerrainData.Patches[PatchIndex].Right->CurrentLOD);
			}
		}

		if (vZ >= (u32)TerrainData.PatchSize)
			vZ = TerrainData.CalcPatchSize;

		if (vX >= (u32)TerrainData.PatchSize)
			vX = TerrainData.CalcPatchSize;

		return (vZ + ((TerrainData.CalcPatchSize) * PatchZ)) * TerrainData.Size +
			(vX + ((TerrainData.CalcPatchSize) * PatchX));
	}


	//! smooth the terrain
	void CTerrainTileSceneNode::smoothTerrain(CDynamicMeshBuffer* mb, s32 smoothFactor)
	{
		for (s32 run = 0; run < smoothFactor; ++run)
		{
			s32 yd = TerrainData.Size;
			for (s32 y = 1; y < TerrainData.Size - 1; ++y)
			{
				for (s32 x = 1; x < TerrainData.Size - 1; ++x)
				{
					mb->getVertexBuffer()[x + yd].Pos.Y =
						(mb->getVertexBuffer()[x-1 + yd].Pos.Y +
						mb->getVertexBuffer()[x+1 + yd].Pos.Y +
						mb->getVertexBuffer()[x   + yd - TerrainData.Size].Pos.Y +
						mb->getVertexBuffer()[x   + yd - TerrainData.Size].Pos.Y) * 0.25f;
				}
				yd += TerrainData.Size;
			}
		}
	}


	//! calculate smooth normals
	void CTerrainTileSceneNode::calculateNormals(CDynamicMeshBuffer* mb)
	{
		s32 count;
		core::vector3df a, b, c, t;

		for (s32 x=0; x<TerrainData.Size; ++x)
		{
			for (s32 z=0; z<TerrainData.Size; ++z)
			{
				count = 0;
				core::vector3df normal;

				// top left
				if (x>0 && z>0)
				{
					a = mb->getVertexBuffer()[(x-1)*TerrainData.Size+z-1].Pos;
					b = mb->getVertexBuffer()[(x-1)*TerrainData.Size+z].Pos;
					c = mb->getVertexBuffer()[x*TerrainData.Size+z].Pos;
					b -= a;
					c -= a;
					t = b.crossProduct(c);
					t.normalize();
					normal += t;

					a = mb->getVertexBuffer()[(x-1)*TerrainData.Size+z-1].Pos;
					b = mb->getVertexBuffer()[x*TerrainData.Size+z-1].Pos;
					c = mb->getVertexBuffer()[x*TerrainData.Size+z].Pos;
					b -= a;
					c -= a;
					t = b.crossProduct(c);
					t.normalize();
					normal += t;

					count += 2;
				}

				// top right
				if (x>0 && z<TerrainData.Size-1)
				{
					a = mb->getVertexBuffer()[(x-1)*TerrainData.Size+z].Pos;
					b = mb->getVertexBuffer()[(x-1)*TerrainData.Size+z+1].Pos;
					c = mb->getVertexBuffer()[x*TerrainData.Size+z+1].Pos;
					b -= a;
					c -= a;
					t = b.crossProduct(c);
					t.normalize();
					normal += t;

					a = mb->getVertexBuffer()[(x-1)*TerrainData.Size+z].Pos;
					b = mb->getVertexBuffer()[x*TerrainData.Size+z+1].Pos;
					c = mb->getVertexBuffer()[x*TerrainData.Size+z].Pos;
					b -= a;
					c -= a;
					t = b.crossProduct(c);
					t.normalize();
					normal += t;

					count += 2;
				}

				// bottom right
				if (x<TerrainData.Size-1 && z<TerrainData.Size-1)
				{
					a = mb->getVertexBuffer()[x*TerrainData.Size+z+1].Pos;
					b = mb->getVertexBuffer()[x*TerrainData.Size+z].Pos;
					c = mb->getVertexBuffer()[(x+1)*TerrainData.Size+z+1].Pos;
					b -= a;
					c -= a;
					t = b.crossProduct(c);
					t.normalize();
					normal += t;

					a = mb->getVertexBuffer()[x*TerrainData.Size+z+1].Pos;
					b = mb->getVertexBuffer()[(x+1)*TerrainData.Size+z+1].Pos;
					c = mb->getVertexBuffer()[(x+1)*TerrainData.Size+z].Pos;
					b -= a;
					c -= a;
					t = b.crossProduct(c);
					t.normalize();
					normal += t;

					count += 2;
				}

				// bottom left
				if (x<TerrainData.Size-1 && z>0)
				{
					a = mb->getVertexBuffer()[x*TerrainData.Size+z-1].Pos;
					b = mb->getVertexBuffer()[x*TerrainData.Size+z].Pos;
					c = mb->getVertexBuffer()[(x+1)*TerrainData.Size+z].Pos;
					b -= a;
					c -= a;
					t = b.crossProduct(c);
					t.normalize();
					normal += t;

					a = mb->getVertexBuffer()[x*TerrainData.Size+z-1].Pos;
					b = mb->getVertexBuffer()[(x+1)*TerrainData.Size+z].Pos;
					c = mb->getVertexBuffer()[(x+1)*TerrainData.Size+z-1].Pos;
					b -= a;
					c -= a;
					t = b.crossProduct(c);
					t.normalize();
					normal += t;

					count += 2;
				}

				if (count != 0)
				{
					normal.normalize();
				}
				else
				{
					normal.set(0.0f, 1.0f, 0.0f);
				}

				mb->getVertexBuffer()[x * TerrainData.Size + z].Normal = normal;
			}
		}
	}


	//! create patches, stuff that needs to be done only once for patches goes here.
	void CTerrainTileSceneNode::createPatches()
	{
		TerrainData.PatchCount = (TerrainData.Size - 1) / (TerrainData.CalcPatchSize);

		if (TerrainData.Patches)
			delete [] TerrainData.Patches;

		TerrainData.Patches = new SPatch[TerrainData.PatchCount * TerrainData.PatchCount];
	}


	//! used to calculate the internal STerrainData structure both at creation and after scaling/position calls.
	void CTerrainTileSceneNode::calculatePatchData()
	{
		// Reset the Terrains Bounding Box for re-calculation
		TerrainData.BoundingBox = core::aabbox3df(999999.9f, 999999.9f, 999999.9f, -999999.9f, -999999.9f, -999999.9f);

		for (s32 x = 0; x < TerrainData.PatchCount; ++x)
		{
			for (s32 z = 0; z < TerrainData.PatchCount; ++z)
			{
				const s32 index = x * TerrainData.PatchCount + z;
				TerrainData.Patches[index].CurrentLOD = 0;

				// For each patch, calculate the bounding box (mins and maxes)
				TerrainData.Patches[index].BoundingBox = core::aabbox3df(999999.9f, 999999.9f, 999999.9f,
					-999999.9f, -999999.9f, -999999.9f);

				for (s32 xx = x*(TerrainData.CalcPatchSize); xx <= (x + 1) * TerrainData.CalcPatchSize; ++xx)
					for (s32 zz = z*(TerrainData.CalcPatchSize); zz <= (z + 1) * TerrainData.CalcPatchSize; ++zz)
						TerrainData.Patches[index].BoundingBox.addInternalPoint(RenderBuffer->getVertexBuffer()[xx * TerrainData.Size + zz].Pos);


				// Reconfigure the bounding box of the terrain as a whole
				TerrainData.BoundingBox.addInternalBox(TerrainData.Patches[index].BoundingBox);

				// get center of Patch
				TerrainData.Patches[index].Center = TerrainData.Patches[index].BoundingBox.getCenter();

				// Assign Neighbours
				// Top
				if (x > 0)
					TerrainData.Patches[index].Top = &TerrainData.Patches[(x-1) * TerrainData.PatchCount + z];
				else
					if ( adjacentTiles[ADJACENT_TOP] )
						TerrainData.Patches[index].Top = &adjacentTiles[ADJACENT_TOP]->TerrainData.Patches[
								(adjacentTiles[ADJACENT_TOP]->TerrainData.PatchCount - 1) *
								adjacentTiles[ADJACENT_TOP]->TerrainData.PatchCount + z];
					else
						TerrainData.Patches[index].Top = 0;

				// Bottom
				if (x < TerrainData.PatchCount - 1)
					TerrainData.Patches[index].Bottom = &TerrainData.Patches[(x+1) * TerrainData.PatchCount + z];
				else
					if ( adjacentTiles[ADJACENT_BOTTOM] )
						TerrainData.Patches[index].Bottom = &adjacentTiles[ADJACENT_BOTTOM]->TerrainData.Patches[z];
                    else
                        TerrainData.Patches[index].Bottom = 0;

				// Left
				if (z > 0)
					TerrainData.Patches[index].Left = &TerrainData.Patches[x * TerrainData.PatchCount + z - 1];
				else
					if ( adjacentTiles[ADJACENT_LEFT] )
						TerrainData.Patches[index].Left = &adjacentTiles[ADJACENT_LEFT]->TerrainData.Patches[
								x *	adjacentTiles[ADJACENT_LEFT]->TerrainData.PatchCount +
								adjacentTiles[ADJACENT_LEFT]->TerrainData.PatchCount - 1];
					else
                        TerrainData.Patches[index].Left = 0;

				// Right
				if (z < TerrainData.PatchCount - 1)
					TerrainData.Patches[index].Right = &TerrainData.Patches[x * TerrainData.PatchCount + z + 1];
				else
					if ( adjacentTiles[ADJACENT_RIGHT] )
						TerrainData.Patches[index].Right = &adjacentTiles[ADJACENT_RIGHT]->TerrainData.Patches[
								x *	adjacentTiles[ADJACENT_RIGHT]->TerrainData.PatchCount];
					else
                        TerrainData.Patches[index].Right = 0;
			}
		}

		// get center of Terrain
		TerrainData.Center = TerrainData.BoundingBox.getCenter();

		// if the default rotation pivot is still being used, update it.
		if (UseDefaultRotationPivot)
		{
			TerrainData.RotationPivot = TerrainData.Center;
		}
	}


	//! used to attach an adjacent tile to this tile
	void CTerrainTileSceneNode::attachTile( CTerrainTileSceneNode *tile, unsigned int edge )
	{
		// record the adjacency
		adjacentTiles[edge] = tile;

		switch ( edge )
		{
		case ADJACENT_TOP: // Top
			for( s32 i = 0; i < TerrainData.PatchCount; ++i )
				TerrainData.Patches[i].Top =
						&tile->TerrainData.Patches[(tile->TerrainData.PatchCount-1) *
						tile->TerrainData.PatchCount + i];

            {
                // copy normals from the attached tile
                video::S3DVertex2TCoords * tarV = ((video::S3DVertex2TCoords*)RenderBuffer->getVertexBuffer().pointer());
                video::S3DVertex2TCoords * srcV = ((video::S3DVertex2TCoords*)RenderBuffer->getVertexBuffer().pointer());
                srcV += (tile->TerrainData.Size - tile->TerrainData.CalcPatchSize) * tile->TerrainData.Size;

                for ( s32 i = 0; i < TerrainData.Size; i++ )
                {
                    tarV->Normal = srcV->Normal;

                    tarV++;
                    srcV++;
                }
            }
			break;

		case ADJACENT_BOTTOM: // Bottom
			for( s32 i = 0; i < TerrainData.PatchCount; ++i )
				TerrainData.Patches[(TerrainData.PatchCount-1) * TerrainData.PatchCount + i].Bottom =
						&tile->TerrainData.Patches[i];
			break;

		case ADJACENT_LEFT: // Left
			for( s32 i = 0; i < TerrainData.PatchCount; ++i )
				TerrainData.Patches[TerrainData.PatchCount * i].Left =
						&tile->TerrainData.Patches[tile->TerrainData.PatchCount * i +
												   tile->TerrainData.PatchCount - 1];
            {
                // copy normals from the attached tile
                video::S3DVertex2TCoords * tarV = ((video::S3DVertex2TCoords*)RenderBuffer->getVertexBuffer().pointer());
                video::S3DVertex2TCoords * srcV = ((video::S3DVertex2TCoords*)tile->RenderBuffer->getVertexBuffer().pointer());
                srcV += tile->TerrainData.Size - tile->TerrainData.CalcPatchSize;

                for ( s32 i = 0; i < TerrainData.Size; i++ )
                {
                    tarV->Normal = srcV->Normal;

                    tarV += TerrainData.Size;
                    srcV += TerrainData.Size;
                }
            }
			break;

		case ADJACENT_RIGHT: // Right
			for( s32 i = 0; i < TerrainData.PatchCount; ++i )
				TerrainData.Patches[TerrainData.PatchCount * i + TerrainData.PatchCount-1].Right =
						&tile->TerrainData.Patches[tile->TerrainData.PatchCount * i];
			break;
		}
	}


	//! used to calculate or recalculate the distance thresholds
	void CTerrainTileSceneNode::calculateDistanceThresholds(bool scalechanged)
	{
		// Only update the LODDistanceThreshold if it's not manually changed
		if (!OverrideDistanceThreshold)
		{
			TerrainData.LODDistanceThreshold.set_used(0);
			// Determine new distance threshold for determining what LOD to draw patches at
			TerrainData.LODDistanceThreshold.reallocate(TerrainData.MaxLOD);

			const f64 size = TerrainData.PatchSize * TerrainData.PatchSize *
					TerrainData.Scale.X * TerrainData.Scale.Z;
			for (s32 i=0; i<TerrainData.MaxLOD; ++i)
			{
				TerrainData.LODDistanceThreshold.push_back(size * ((i+1+ i / 2) * (i+1+ i / 2)));
			}
		}
	}


	void CTerrainTileSceneNode::setCurrentLODOfPatches(s32 lod)
	{
		const s32 count = TerrainData.PatchCount * TerrainData.PatchCount;
		for (s32 i=0; i< count; ++i)
			TerrainData.Patches[i].CurrentLOD = lod;
	}


	void CTerrainTileSceneNode::setCurrentLODOfPatches(const core::array<s32>& lodarray)
	{
		const s32 count = TerrainData.PatchCount * TerrainData.PatchCount;
		for (s32 i=0; i<count; ++i)
			TerrainData.Patches[i].CurrentLOD = lodarray[i];
	}


	//! Gets the height
	f32 CTerrainTileSceneNode::getHeight(f32 x, f32 z) const
	{
		if (!Mesh.getMeshBufferCount())
			return 0;

		f32 height = -999999.9f;

		core::matrix4 rotMatrix;
		rotMatrix.setRotationDegrees(TerrainData.Rotation);
		core::vector3df pos(x, 0.0f, z);
		rotMatrix.rotateVect(pos);
		pos -= TerrainData.Position;
		pos /= TerrainData.Scale;

		s32 X(core::floor32(pos.X));
		s32 Z(core::floor32(pos.Z));

		if (X >= 0 && X < TerrainData.Size-1 &&
				Z >= 0 && Z < TerrainData.Size-1)
		{
			const video::S3DVertex2TCoords* Vertices = (const video::S3DVertex2TCoords*)Mesh.getMeshBuffer(0)->getVertices();
			const core::vector3df& a = Vertices[X * TerrainData.Size + Z].Pos;
			const core::vector3df& b = Vertices[(X + 1) * TerrainData.Size + Z].Pos;
			const core::vector3df& c = Vertices[X * TerrainData.Size + (Z + 1)].Pos;
			const core::vector3df& d = Vertices[(X + 1) * TerrainData.Size + (Z + 1)].Pos;

			// offset from integer position
			const f32 dx = pos.X - X;
			const f32 dz = pos.Z - Z;

			if (dx > dz)
				height = a.Y + (d.Y - b.Y)*dz + (b.Y - a.Y)*dx;
			else
				height = a.Y + (d.Y - c.Y)*dx + (c.Y - a.Y)*dz;

			height *= TerrainData.Scale.Y;
			height += TerrainData.Position.Y;
		}

		return height;
	}


	//! Writes attributes of the scene node.
	void CTerrainTileSceneNode::serializeAttributes(io::IAttributes* out,
				io::SAttributeReadWriteOptions* options) const
	{
		ISceneNode::serializeAttributes(out, options);

		out->addString("Heightmap", HeightmapFile.c_str());
		out->addFloat("TextureScale1", TCoordScale1);
		out->addFloat("TextureScale2", TCoordScale2);
	}


	//! Reads attributes of the scene node.
	void CTerrainTileSceneNode::deserializeAttributes(io::IAttributes* in,
			io::SAttributeReadWriteOptions* options)
	{
		io::path newHeightmap = in->getAttributeAsString("Heightmap");
		f32 tcoordScale1 = in->getAttributeAsFloat("TextureScale1");
		f32 tcoordScale2 = in->getAttributeAsFloat("TextureScale2");

		// set possible new heightmap

		if (newHeightmap.size() != 0 && newHeightmap != HeightmapFile)
		{
			io::IReadFile* file = FileSystem->createAndOpenFile(newHeightmap.c_str());
			if (file)
			{
				loadHeightMap(file, video::SColor(255,255,255,255), 0);
				file->drop();
			}
		}

		// set possible new scale

		if (core::equals(tcoordScale1, 0.f))
			tcoordScale1 = 1.0f;

		if (core::equals(tcoordScale2, 0.f))
			tcoordScale2 = 1.0f;

		if (!core::equals(tcoordScale1, TCoordScale1) ||
			!core::equals(tcoordScale2, TCoordScale2))
		{
			scaleTexture(tcoordScale1, tcoordScale2);
		}

		ISceneNode::deserializeAttributes(in, options);
	}


	//! Creates a clone of this scene node and its children.
	ISceneNode* CTerrainTileSceneNode::clone(ISceneNode* newParent, ISceneManager* newManager)
	{
		if (!newParent)
			newParent = Parent;
		if (!newManager)
			newManager = SceneManager;

		CTerrainTileSceneNode* nb = new CTerrainTileSceneNode(
			newParent, newManager, FileSystem, ID,
			4, ETPS_17, getPosition(), getRotation(), getScale());

		nb->cloneMembers(this, newManager);

		// instead of cloning the data structures, recreate the terrain.
		// (temporary solution)

		// load file

		io::IReadFile* file = FileSystem->createAndOpenFile(HeightmapFile.c_str());
		if (file)
		{
			nb->loadHeightMap(file, video::SColor(255,255,255,255), 0);
			file->drop();
		}

		// scale textures

		nb->scaleTexture(TCoordScale1, TCoordScale2);

		// copy materials

		for (unsigned int m = 0; m<Mesh.getMeshBufferCount(); ++m)
		{
			if (nb->Mesh.getMeshBufferCount()>m &&
				nb->Mesh.getMeshBuffer(m) &&
				Mesh.getMeshBuffer(m))
			{
				nb->Mesh.getMeshBuffer(m)->getMaterial() =
					Mesh.getMeshBuffer(m)->getMaterial();
			}
		}

		nb->RenderBuffer->Material = RenderBuffer->Material;

		// finish

		nb->drop();
		return nb;
	}

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


