///////////////////////////////////////////////////////////////////////////////
/// @file deTerrain.cpp
///
/// @brief terrain generation & display
///
/// @author Masterg, Assassin
///
/// This file is the intellectual property of Novus Delta, LLC.. Usage of the
/// contents of this file is subject to the Destiny3D Member License which
/// can be found at http://www.destiny3d.com.  Any other usage is prohibited.
///
/// This file is distributed "AS IS" without warranty of any kind.  Novus
/// Delta, LLC. does not guarantee the fitness of the contents of this file
/// for any particular purpose.
///
/// Copyright (C) 2001-2003 Novus Delta, LLC. All Rights Reserved.
///
/// <hr>
///                                 Change History
/// <hr>
///
/// @date Mar 2003
/// @author Masterg
/// @remarks Creation
///
///////////////////////////////////////////////////////////////////////////////

#include "deScene_priv.hpp"
#include "deBitmap.hpp"
#include "deMath.hpp"
#include "deCamera.hpp"
#include "dePortal.hpp"
#include "deRender.hpp"
#include "deRenderStates.hpp"
#include "deFunctors.hpp"

#define GMMVBSIZE (128)

extern IdeSceneLight* s_TestLight;
extern IdeSceneLight* s_TestLight2;
extern IdeSceneLight* s_TestLightBright;

static inline DWORD BiggestPow2SmallerThan(DWORD input)
{
#ifdef __GNUC__
	DWORD RetVal;
	RetVal = 1;
	do
	{
		RetVal = RetVal << 1;
	} while(RetVal < input);
	
	if (RetVal >= input)
		RetVal /= 2;
	return RetVal;
	
#else
	DWORD RetVal;
	__asm
	{
		mov ecx, input
			mov eax, 1
again:
		ror eax, 1
			test eax, ecx
			jz again
			mov RetVal, eax
	}

	if (RetVal == input)
		RetVal /= 2;
	return RetVal;
#endif
}

static inline DWORD SmallestPow2BiggerThan(DWORD input)
{
	return BiggestPow2SmallerThan(input)*2;
}

deSceneTerrain::deSceneTerrain()
:	deSceneObject(iface_IdeSceneTerrain)
{
	m_Scale = deOneVec;
	m_DetailRepeat.u = 1;
	m_DetailRepeat.v = 1;
	m_HeightMap = NULL;
	m_ColorMap = NULL;
	m_DetailMap = NULL;
	m_RenderTexture = NULL;
	m_GMMChunks = NULL;
	m_GMMRoot = NULL;
	
	// default node size
	m_GMMChunkSize = 32;
	m_LODBias = 3.5;
}

deSceneTerrain::~deSceneTerrain()
{
	ResetGMMChunks();

	if (m_HeightMap)
		m_HeightMap->Release();
	if (m_ColorMap)
		m_ColorMap->Release();
	if (m_DetailMap)
		m_DetailMap->Release();
	if (m_RenderTexture)
		m_RenderTexture->Release();
}

void deSceneTerrain::ResetGMMChunks()
{
	if (m_GMMChunks)
	{
		int x,z;
		for (z = 0; z < m_GMMChunksZSide; ++z)
		{
			for (x = 0; x < m_GMMChunksXSide; ++x)
			{
				DestroyGMMChunk(&m_GMMChunks[z*m_GMMChunksXSide + x]);
			}
		}
		delete [] m_GMMChunks;
		m_GMMChunks = NULL;
	}
	deReleasePtrsFunctor<IdeVertexBuffer> rel_iter;
	deIter_App(m_VBuffers.begin(), m_VBuffers.end(), rel_iter);
	m_VBuffers.Resize(0);
	deIter_App(m_IndexBuffers.begin(), m_IndexBuffers.end(), rel_iter);
	m_IndexBuffers.Resize(0);
	DestroyGMMQuadrant(m_GMMRoot);
}

IdeSceneObject::interface_t deSceneTerrain::GetSOIType()
{
	return iface_IdeSceneTerrain;
}

void* deSceneTerrain::GetSOInterface(interface_t i)
{
	if (i == iface_IdeSceneTerrain)
	{
		return (IdeSceneTerrain*)(this);
	}
	if (i == iface_IdeSceneObject)
	{
		return (IdeSceneObject*)(this);
	}
	return NULL;
}

deBoolean deSceneTerrain::SetTerrainHeightMap(IdeBitmapProxy * bmp)
{
	if (m_HeightMap)
		m_HeightMap->Release();
	this->m_HeightMap = bmp;
	if (m_HeightMap)
		m_HeightMap->Claim();

	return deTRUE;
}

deBoolean deSceneTerrain::SetTerrainColorMap(IdeBitmapProxy * bmp)
{
	if (m_ColorMap)
		m_ColorMap->Release();
	this->m_ColorMap = bmp;
	if (m_ColorMap)
		m_ColorMap->Claim();

	return deTRUE;
}

deBoolean deSceneTerrain::SetTerrainDetailMap(IdeBitmapProxy * bmp)
{
	if (m_DetailMap)
		m_DetailMap->Release();
	this->m_DetailMap = bmp;
	if (m_DetailMap)
		m_DetailMap->Claim();
	
	return deTRUE;
}

deVec3d deSceneTerrain::GetTerrainScale()
{
	return m_Scale;
}

deTexCoord deSceneTerrain::GetDetailRepeat()
{
	return m_DetailRepeat;
}

void deSceneTerrain::SetTerrainScale(deVec3d vScale)
{
	m_Scale = vScale;
}

void deSceneTerrain::SetTerrainScale(deFloat x, deFloat y, deFloat z)
{
	m_Scale.x = x;	
	m_Scale.y = y; 
	m_Scale.z = z;
}

void deSceneTerrain::SetDetailRepeat(deTexCoord Repeat)
{
	m_DetailRepeat = Repeat;
}

void deSceneTerrain::SetDetailRepeat(deFloat u, deFloat v)
{
	m_DetailRepeat.u = u;
	m_DetailRepeat.v = v;
}

void deSceneTerrain::SetHeightAt(unsigned char Height, long x, long y)
{
	IdeBitmap* bmp;
	if (m_HeightMap)
	{
		bmp = m_HeightMap->GetBitmap();
		if (bmp)
			bmp->SetColorAt(x, y, ARGB(255,Height,Height,Height));
	}
}

unsigned char deSceneTerrain::GetTrueHeightAt(long x, long y)
{
	// grab the color value
	if (!m_HeightMap)
		return 0;
	deARGB color = 0;
	IdeBitmap* bmp;
	bmp = m_HeightMap->GetBitmap();
	if (bmp)
		color = bmp->GetColorAt(x,y);

	// grab  the blue channel
	unsigned char height = (unsigned char)(color & 0xff);

	return height;
}

deFloat deSceneTerrain::GetScaledHeightAt(long x, long y)
{
	// grab the color value
	if (!m_HeightMap)
		return 0;
	deARGB color = 0;
	IdeBitmap* bmp;
	bmp = m_HeightMap->GetBitmap();
	if (bmp)
		color = bmp->GetColorAt(x,y);

	// grab  the blue channel
	deFloat height = deFloat((color & 0xff) * (m_Scale.y/255.0f));

	return height;
}

void deSceneTerrain::PreCache(IdeDriver* pDriver)
{
	int x,z;
	for (z = 0; z < m_GMMChunksZSide; ++z)
	{
		for (x = 0; x < m_GMMChunksXSide; ++x)
		{
			pDriver->UpdateVertexBuffer(m_GMMChunks[z*m_GMMChunksXSide + x].Vertices);
		}
	}
}

deBoolean deSceneTerrain::CreateTerrainTexture(IdeDriver * pDriver)
{
	if (!m_RenderTexture)
	{
		m_RenderTexture = IdeRenderState_CreateRenderTexture();
		if (!m_RenderTexture)
			return deFALSE;
	}
	
	if (!m_RenderTexture->Lock())
		return deFALSE;
	m_RenderTexture->SetBitmap(0, m_ColorMap);
	m_RenderTexture->SetBitmap(1, m_DetailMap);
	m_RenderTexture->SetColorArg(0, 1, IdeRenderTexture::TEXTUREARG_TEXTURE);
	m_RenderTexture->SetColorArg(0, 2, IdeRenderTexture::TEXTUREARG_DIFFUSE);
	m_RenderTexture->SetColorArg(1, 1, IdeRenderTexture::TEXTUREARG_TEXTURE);
	m_RenderTexture->SetColorArg(1, 2, IdeRenderTexture::TEXTUREARG_PREVIOUS);
	if (m_ColorMap)
	{
		m_RenderTexture->SetColorOperation(0, IdeRenderTexture::TEXTUREOP_MODULATE);
	}
	else
	{
		m_RenderTexture->SetColorOperation(0, IdeRenderTexture::TEXTUREOP_SELECTARG2);
	}
	if (m_DetailMap)
	{
		m_RenderTexture->SetColorOperation(1, IdeRenderTexture::TEXTUREOP_MODULATE);
	}
	else
	{
		m_RenderTexture->SetColorOperation(1, IdeRenderTexture::TEXTUREOP_SELECTARG2);
	}
	m_RenderTexture->Unlock();

	if (pDriver)
	{
		if (m_ColorMap)
			pDriver->AddBitmapProxy(m_ColorMap);
		if (m_DetailMap)
			pDriver->AddBitmapProxy(m_DetailMap);
	}

	return deTRUE;
}

deBoolean deSceneTerrain::BuildRenderable()
{
	if (!m_HeightMap)
		DEBUGRETURN(deFALSE, "BuildRenderable: No heightmap to use");

	if (!CreateTerrainTexture(NULL))
		return deFALSE;

	ResetGMMChunks();
	
	// create vertex buffer
	IdeVertexBuffer* pVB;
	m_VBuffers.ResizeQuick(1, deTRUE);
	pVB = IdeVertexBuffer_CreateVBuffer();
	m_VBuffers[0] = pVB;

	if (!pVB)
		return deFALSE;

	if (!pVB->Lock())
		return deFALSE;
	// make our data streams
	pVB->CreateBuffer(IdeVertexBuffer::BUFFER_POSITION, deFALSE);
	pVB->CreateBuffer(IdeVertexBuffer::BUFFER_NORMAL, deFALSE);
	pVB->CreateBuffer(IdeVertexBuffer::BUFFER_DIFFUSE, deFALSE);
	pVB->CreateBuffer(IdeVertexBuffer::BUFFER_TEXTURECOORDS0, deFALSE);
	pVB->CreateBuffer(IdeVertexBuffer::BUFFER_TEXTURECOORDS1, deFALSE);
	pVB->CreateBuffer(IdeVertexBuffer::BUFFER_INDICES32, deFALSE);

	int Width, Height;
	int x,z,k;
	deVertex* PositionBuffer;
	deVertex* NormalBuffer;
	deARGB* DiffuseBuffer;
	deTexCoord* TexCoords0;
	deTexCoord* TexCoords1;
	unsigned long* IndexBuffer;
	deFloat TerrainHeight;
	deFloat Percentage;
	deARGB color1, color2;

	// some brute force generation
	Width = m_HeightMap->GetWidth();
	Height = m_HeightMap->GetHeight();
//	Width = min(Width, 256);
//	Height = min(Width, 256);

	pVB->SetBufferSize(Width*Height, deFALSE);
	pVB->SetBufferSize((Width-1)*(Height-1)*6, deTRUE);

	PositionBuffer = (deVertex*)pVB->GetBuffer(IdeVertexBuffer::BUFFER_POSITION);
	NormalBuffer = (deVertex*)pVB->GetBuffer(IdeVertexBuffer::BUFFER_NORMAL);
	DiffuseBuffer = (deARGB*)pVB->GetBuffer(IdeVertexBuffer::BUFFER_DIFFUSE);
	TexCoords0 = (deTexCoord*)pVB->GetBuffer(IdeVertexBuffer::BUFFER_TEXTURECOORDS0);
	TexCoords1 = (deTexCoord*)pVB->GetBuffer(IdeVertexBuffer::BUFFER_TEXTURECOORDS1);
	IndexBuffer = (unsigned long*)pVB->GetBuffer(IdeVertexBuffer::BUFFER_INDICES32);

	deARGB ColorRange[] = 
	{
		0xff8b6a3f,
		0xffaf8750,
		0xff800000,
		0xffff0000,
		0xffffff00,
		0xff00ff00,
		0xff0000ff,
		0xff808080,
		0xffffffff,
	};
	for (z = 0; z < Height; ++z)
	{
		for (x = 0; x < Width; ++x)
		{
			TerrainHeight = GetScaledHeightAt(x,z);
			PositionBuffer[z*Width + x].x = (deFloat)(x * m_Scale.x);
			PositionBuffer[z*Width + x].y = (deFloat)(TerrainHeight);
			PositionBuffer[z*Width + x].z = (deFloat)(-z * m_Scale.z);

			TexCoords0[z*Width + x].u = (deFloat)x / Width;
			TexCoords0[z*Width + x].v = (deFloat)z / Height;

			TexCoords1[z*Width + x].u = (deFloat)x * m_DetailRepeat.u;
			TexCoords1[z*Width + x].v = (deFloat)z * m_DetailRepeat.v;
			
			TerrainHeight = (deFloat)(TerrainHeight * (sizeof(ColorRange)/sizeof(deARGB)-1) / m_Scale.y);
			Percentage = TerrainHeight - (int)TerrainHeight;
			color1 = ColorRange[(int)TerrainHeight];
			if (TerrainHeight < (sizeof(ColorRange)/sizeof(deARGB)))
				color2 = ColorRange[(int)TerrainHeight+1];
			else
				color2 = 0;
			DiffuseBuffer[z*Width + x] = (0xff << 24) | 
				(deARGB((1-Percentage)*((color1>>16)&0xff) + (Percentage)*((color2>>16)&0xff)) << 16) |
				(deARGB((1-Percentage)*((color1>> 8)&0xff) + (Percentage)*((color2>> 8)&0xff)) << 8) |
				(deARGB((1-Percentage)*((color1    )&0xff) + (Percentage)*((color2    )&0xff)));
		}
	}
	for (z = 0; z < Height; ++z)
	{
		deVertex crossx, crossz;
		for (x = 0; x < Width; ++x)
		{
			crossx = PositionBuffer[z*Width + x];
			crossz = crossx;
			if (x < Width-1)
			{
				crossx -= PositionBuffer[z*Width + x+1];
			}
			else
				crossx = deMake_Vertex(deVecX);
			if (z < Height-1)
			{
				crossz -= PositionBuffer[(z+1)*Width + x];
			}
			else
				crossz = deMake_Vertex(deVecZ);
			Vec3d_Cross(crossx, crossz, NormalBuffer[z*Width + x]);
		}
	}
	k = 0;
	for (z = 0; z < Height-1; ++z)
	{
		for (x = 0; x < Width-1; ++x)
		{
			IndexBuffer[k  ] = (z  )*Width + x;
			IndexBuffer[k+1] = (z+1)*Width + x;
			IndexBuffer[k+2] = (z  )*Width + x + 1;
			IndexBuffer[k+3] = (z  )*Width + x + 1;
			IndexBuffer[k+4] = (z+1)*Width + x;
			IndexBuffer[k+5] = (z+1)*Width + x + 1;
			k += 6;
		}
	}

	pVB->DirtyBuffer(IdeVertexBuffer::BUFFER_POSITION);
	pVB->DirtyBuffer(IdeVertexBuffer::BUFFER_NORMAL);
	pVB->DirtyBuffer(IdeVertexBuffer::BUFFER_DIFFUSE);
	pVB->DirtyBuffer(IdeVertexBuffer::BUFFER_TEXTURECOORDS0);
	pVB->DirtyBuffer(IdeVertexBuffer::BUFFER_TEXTURECOORDS1);
	pVB->DirtyBuffer(IdeVertexBuffer::BUFFER_INDICES32);

	pVB->SetRenderType(IdeVertexBuffer::RENDER_TRIANGLELIST);

	pVB->Unlock();

	return deTRUE;
}

void deSceneTerrain::SetGMMChunkSize(long ChunkSize)
{
	m_GMMChunkSize = ChunkSize;
	if (m_GMMChunkSize < 8)
		m_GMMChunkSize = 8;
	else if (m_GMMChunkSize > 128)
		m_GMMChunkSize = 128;
	m_GMMChunkSize = SmallestPow2BiggerThan(m_GMMChunkSize);
	if (ChunkSize != m_GMMChunkSize)
		DEBUGMSG1("SetGMMChunkSize: ChunkSize (%d) was not a valid power of 2", ChunkSize);
}

long deSceneTerrain::GetGMMChunkSize()
{
	return m_GMMChunkSize;
}

deDouble deSceneTerrain::GetLODBias()
{
	return m_LODBias;
}

void deSceneTerrain::SetLODBias(deDouble Bias)
{
	m_LODBias = Bias;
}

int deSceneTerrain::GetGMMChunkNumber(long x, long z)
{
	return ((z*m_GMMChunksXSide)+x);
}

deBoolean deSceneTerrain::BuildGeoMipMap(IdeDriver* pDriver)
{
	if (!m_HeightMap)
		DEBUGRETURN(deFALSE, "BuildRenderable: No heightmap to use");
	
	ResetGMMChunks();

	if (!CreateTerrainTexture(pDriver))
		return deFALSE;
	
	{
		IdeVertexBuffer* pVB;
		u16* IndexBuffer;
		int x,z,j,k, LODLevels, ChunkRowVerts, JumpNum, GridVerts;
		ChunkRowVerts = m_GMMChunkSize+1;
		LODLevels = 0;
		k = m_GMMChunkSize;
		while (k > 0)
		{
			LODLevels++;
			k /= 2;
		}
		m_IndexBuffers.ResizeQuick(LODLevels, deTRUE);
		pVB = IdeVertexBuffer_CreateVBuffer();
		if (!pVB->Lock())
			return deFALSE;
		pVB->CreateBuffer(IdeVertexBuffer::BUFFER_INDICES16, deFALSE);
		pVB->Unlock();
		for (k = 0; k < LODLevels; ++k)
		{
			m_IndexBuffers[k] = IdeVertexBuffer_CreateSubVBuffer(pVB);
			if (!m_IndexBuffers[k])
				return deFALSE;
			m_IndexBuffers[k]->Lock();
			GridVerts = ChunkRowVerts*ChunkRowVerts;
			x = (1<<(LODLevels-k-1));
			z = x*x*6;
			z += 6*4*x; // add the skirts on
			m_IndexBuffers[k]->SetBufferSize(z, deTRUE);
			m_IndexBuffers[k]->SetRenderType(IdeVertexBuffer::RENDER_TRIANGLELIST);
			IndexBuffer = (u16*)m_IndexBuffers[k]->GetBuffer(IdeVertexBuffer::BUFFER_INDICES16);
			j = 0;
			JumpNum = (1<<k);
			for (z = 0; z < m_GMMChunkSize; z += JumpNum)
			{
				for (x = 0; x < m_GMMChunkSize; x += JumpNum)
				{
					IndexBuffer[j  ] = (u16)((z        )*ChunkRowVerts + x);
					IndexBuffer[j+1] = (u16)((z+JumpNum)*ChunkRowVerts + x);
					IndexBuffer[j+2] = (u16)(IndexBuffer[j  ] + JumpNum);
					IndexBuffer[j+3] = IndexBuffer[j+2];
					IndexBuffer[j+4] = IndexBuffer[j+1];
					IndexBuffer[j+5] = (u16)(IndexBuffer[j+1] + JumpNum);
					j += 6;
				}
			}
			for (z = 0; z < 4; ++z)
			{
				for (x = 0; x < m_GMMChunkSize; x += JumpNum)
				{
					switch (z)
					{
					case 0:
						IndexBuffer[j  ] = (u16)(x + GridVerts);
						IndexBuffer[j+1] = (u16)(x);
						IndexBuffer[j+2] = (u16)(IndexBuffer[j  ] + JumpNum);
						IndexBuffer[j+5] = (u16)(IndexBuffer[j+1] + JumpNum);
						break;
					case 1:
						IndexBuffer[j  ] = (u16)((x  )*ChunkRowVerts);
						IndexBuffer[j+1] = (u16)(x + GridVerts + ChunkRowVerts);
						IndexBuffer[j+2] = (u16)(IndexBuffer[j  ] + JumpNum*ChunkRowVerts);
						IndexBuffer[j+5] = (u16)(IndexBuffer[j+1] + JumpNum);
						break;
					case 2:
						IndexBuffer[j  ] = (u16)(x + m_GMMChunkSize*ChunkRowVerts);
						IndexBuffer[j+1] = (u16)(x + GridVerts + 2*ChunkRowVerts);
						IndexBuffer[j+2] = (u16)(IndexBuffer[j  ] + JumpNum);
						IndexBuffer[j+5] = (u16)(IndexBuffer[j+1] + JumpNum);
						break;
					case 3:
						IndexBuffer[j  ] = (u16)(x + GridVerts + 3*ChunkRowVerts);
						IndexBuffer[j+1] = (u16)((x)*ChunkRowVerts + m_GMMChunkSize);
						IndexBuffer[j+2] = (u16)(IndexBuffer[j  ] + JumpNum);
						IndexBuffer[j+5] = (u16)(IndexBuffer[j+1] + JumpNum*ChunkRowVerts);
						break;
					default:
						DE_ASSERT(z >= 0 && z < 4);
						break;
					}
					IndexBuffer[j+3] = IndexBuffer[j+2];
					IndexBuffer[j+4] = IndexBuffer[j+1];
					j += 6;
				}
			}
			m_IndexBuffers[k]->Unlock();
		}
		pVB->Release();
		pVB = NULL;

		m_GMMChunksXSide = (m_HeightMap->GetWidth() - 2) / m_GMMChunkSize + 1;
		m_GMMChunksZSide = (m_HeightMap->GetHeight() - 2) / m_GMMChunkSize + 1;
		m_GMMChunks = new deTerrainGMMChunk[m_GMMChunksXSide*m_GMMChunksZSide];
		memset(m_GMMChunks, 0, m_GMMChunksXSide*m_GMMChunksZSide*sizeof(deTerrainGMMChunk));
		
		// pre-allocate space for all our chunk geometry
		x = SmallestPow2BiggerThan(m_HeightMap->GetWidth()-1);
		x = (x/GMMVBSIZE);
		x *= x;
		x = max(1, x); // number of VB's needed
		m_VBuffers.ResizeQuick(x, deTRUE);

		z = (GMMVBSIZE/m_GMMChunkSize); // number of chunks along a VB edge
		z *= z; // square to get total number of chunks in a VB
		z *= (ChunkRowVerts+4)*ChunkRowVerts; // multiply by num of verts in a chunk to get total verts

		for (k = 0; k < x; ++k)
		{
			pVB = IdeVertexBuffer_CreateVBuffer();
			if (!pVB)
				return deFALSE;
			
			if (!pVB->Lock())
				return deFALSE;
			else
			{
				pVB->CreateBuffer(IdeVertexBuffer::BUFFER_POSITION, deFALSE);
				pVB->CreateBuffer(IdeVertexBuffer::BUFFER_NORMAL, deFALSE);
				pVB->CreateBuffer(IdeVertexBuffer::BUFFER_DIFFUSE, deFALSE);
				pVB->CreateBuffer(IdeVertexBuffer::BUFFER_TEXTURECOORDS0, deFALSE);
				pVB->CreateBuffer(IdeVertexBuffer::BUFFER_TEXTURECOORDS1, deFALSE);
				pVB->SetBufferSize(z, deFALSE);
				pVB->Unlock();
			}
			m_VBuffers[k] = pVB;
		}

		for (z=0; z<m_GMMChunksZSide; z++)
		{
			for (x=0; x<m_GMMChunksXSide; x++)
			{
				if (!BuildGMMChunk(x, z, pDriver))
					return deFALSE;
			}
		}
	}

	if (!BuildGMMQuadtree())
		return deFALSE;

	SetBoundCenter(m_GMMRoot->BSphere.Center);
	SetBoundRadius(m_GMMRoot->BSphere.Radius);
	SetLocalAABB(m_GMMRoot->BBox.Min, m_GMMRoot->BBox.Max);
		
	if (pDriver)
	{
		size_t k;
		for (k = 0; k < m_IndexBuffers.Length(); ++k)
		{
			if (!pDriver->UpdateVertexBuffer(m_IndexBuffers[k]))
				return deFALSE;
		}
		for (k = 0; k < m_VBuffers.Length(); ++k)
		{
			if (!pDriver->UpdateVertexBuffer(m_VBuffers[k]))
				return deFALSE;
		}
	}

	return deTRUE;
}

deBoolean deSceneTerrain::BuildGMMChunk(long XCoord, long ZCoord, IdeDriver* /*pDriver*/)
{
	deTerrainGMMChunk* Chunk;
	int Width, Height;
	int StopX, StopZ;
	int RealX, RealZ;
	int x,z,k, EdgeNum;
	deVertex* PositionBuffer;
	deVertex* NormalBuffer;
	deARGB* DiffuseBuffer;
	deTexCoord* TexCoords0;
	deTexCoord* TexCoords1;
	deFloat TerrainHeight;
	deFloat Percentage;
	deDouble EdgeMins[4];
	deARGB color1, color2;

	Chunk = &m_GMMChunks[ZCoord*m_GMMChunksXSide + XCoord];
	Chunk->BBox.Min.x = m_GMMChunkSize * (XCoord) * m_Scale.x;
	Chunk->BBox.Max.x = m_GMMChunkSize * (XCoord+1) * m_Scale.x;
	Chunk->BBox.Min.z = m_GMMChunkSize * (ZCoord+1) * -m_Scale.z;
	Chunk->BBox.Max.z = m_GMMChunkSize * (ZCoord) * -m_Scale.z;
	Chunk->BBox.Min.y = m_Scale.y;
	Chunk->BBox.Max.y = 0;
	Chunk->XCoord = XCoord;
	Chunk->ZCoord = ZCoord;
	Chunk->CurrentLOD = -1;
	if (!Chunk->Vertices)
	{
		IdeVertexBuffer* ParentBuffer;
		x = SmallestPow2BiggerThan(m_HeightMap->GetWidth()-1);
		x /= GMMVBSIZE;
		x = max(1,x);
		z = m_GMMChunksZSide/x;
		x = m_GMMChunksXSide/x;
		RealX = (XCoord)/(z);
		RealZ = (ZCoord)/(z);
		ParentBuffer = m_VBuffers[RealZ*x + RealX];
		Chunk->Vertices = IdeVertexBuffer_CreateSubVBuffer(ParentBuffer);
		if (!Chunk->Vertices)
			return deFALSE;
	}
	IdeVertexBuffer * NewBuffer;
	NewBuffer = Chunk->Vertices;
	if (!NewBuffer->Lock())
		return deFALSE;

	NewBuffer->CreateBuffer(IdeVertexBuffer::BUFFER_POSITION, deFALSE);
	NewBuffer->CreateBuffer(IdeVertexBuffer::BUFFER_NORMAL, deFALSE);
	NewBuffer->CreateBuffer(IdeVertexBuffer::BUFFER_DIFFUSE, deFALSE);
	NewBuffer->CreateBuffer(IdeVertexBuffer::BUFFER_TEXTURECOORDS0, deFALSE);
	NewBuffer->CreateBuffer(IdeVertexBuffer::BUFFER_TEXTURECOORDS1, deFALSE);
	
	Width = m_HeightMap->GetWidth();
	Height = m_HeightMap->GetHeight();
	k = m_GMMChunkSize+1; // verts = chunks+1
	k = k*k + 4*k; // square + skirts
	NewBuffer->SetBufferSize(k, deFALSE);
	NewBuffer->SetRenderType(IdeVertexBuffer::RENDER_TRIANGLELIST);
	
	PositionBuffer = (deVertex*)NewBuffer->GetBuffer(IdeVertexBuffer::BUFFER_POSITION);
	NormalBuffer = (deVertex*)NewBuffer->GetBuffer(IdeVertexBuffer::BUFFER_NORMAL);
	DiffuseBuffer = (deARGB*)NewBuffer->GetBuffer(IdeVertexBuffer::BUFFER_DIFFUSE);
	TexCoords0 = (deTexCoord*)NewBuffer->GetBuffer(IdeVertexBuffer::BUFFER_TEXTURECOORDS0);
	TexCoords1 = (deTexCoord*)NewBuffer->GetBuffer(IdeVertexBuffer::BUFFER_TEXTURECOORDS1);

	deARGB ColorRange[] = 
	{
		0xff45351f,
		0xffaf8750,
		0xff800000,
		0xffff0000,
		0xffffff00,
		0xff00ff00,
		0xff008000,
		0xff0000ff,
		0xff808080,
		0xffffffff,
	};
	for (z = 0; z < 4; ++z)
		EdgeMins[z] = m_Scale.y;
	k = 0;
	StopX = m_GMMChunkSize * (XCoord+1) + 1;
	StopZ = m_GMMChunkSize * (ZCoord+1) + 1;
	for (z = m_GMMChunkSize * (ZCoord); z < StopZ; ++z)
	{
		if (z == m_GMMChunkSize * (ZCoord))
			EdgeNum = 1 << 0;
		else if (z == (StopZ - 1))
			EdgeNum = 1 << 2;
		else
			EdgeNum = 0;
		RealZ = min(z,Height-1);
		for (x = m_GMMChunkSize * (XCoord); x < StopX; ++x)
		{
			if (x == m_GMMChunkSize * (XCoord))
				EdgeNum |= 1 << 1;
			else if (x == (StopX - 1))
				EdgeNum |= 1 << 3;
			else
				EdgeNum &= (1<<0 | 1<<2);
			RealX = min(x,Width-1);
			TerrainHeight = GetScaledHeightAt(RealX,RealZ);
			PositionBuffer[k].x = (deFloat)(RealX * m_Scale.x);
			PositionBuffer[k].y = (deFloat)(TerrainHeight);
			PositionBuffer[k].z = (deFloat)(-RealZ * m_Scale.z);

			Chunk->BBox.Min.y = min(Chunk->BBox.Min.y, PositionBuffer[k].y);
			Chunk->BBox.Max.y = max(Chunk->BBox.Max.y, PositionBuffer[k].y);
			if (EdgeNum & 1)
				EdgeMins[0] = min(EdgeMins[0], PositionBuffer[k].y);
			else if (EdgeNum & 4)
				EdgeMins[2] = min(EdgeMins[2], PositionBuffer[k].y);
			if (EdgeNum & 2)
				EdgeMins[1] = min(EdgeMins[1], PositionBuffer[k].y);
			else if (EdgeNum & 8)
				EdgeMins[3] = min(EdgeMins[3], PositionBuffer[k].y);
			
			TexCoords0[k].u = (deFloat)RealX / Width;
			TexCoords0[k].v = (deFloat)RealZ / Height;
			
			TexCoords1[k].u = (deFloat)RealX * m_DetailRepeat.u;
			TexCoords1[k].v = (deFloat)RealZ * m_DetailRepeat.v;
			
			TerrainHeight = (deFloat)(TerrainHeight * (sizeof(ColorRange)/sizeof(deARGB)-1) / m_Scale.y);
			Percentage = TerrainHeight - (int)TerrainHeight;
			color1 = ColorRange[(int)TerrainHeight];
			if (TerrainHeight < sizeof(ColorRange)/sizeof(deARGB))
				color2 = ColorRange[(int)TerrainHeight+1];
			else
				color2 = 0;
			DiffuseBuffer[k] = (0xff << 24) | 
				(deARGB((1-Percentage)*((color1>>16)&0xff) + (Percentage)*((color2>>16)&0xff)) << 16) |
				(deARGB((1-Percentage)*((color1>> 8)&0xff) + (Percentage)*((color2>> 8)&0xff)) << 8) |
				(deARGB((1-Percentage)*((color1    )&0xff) + (Percentage)*((color2    )&0xff)));
			DiffuseBuffer[k] = 0xffffffff;

			++k;
		}
	}
	k = 0;
	for (z = m_GMMChunkSize * (ZCoord); z < StopZ; ++z)
	{
		deVertex crossx, crossz;
		for (x = m_GMMChunkSize * (XCoord); x < StopX; ++x)
		{
			if (x < StopX-1)
			{
				crossx = PositionBuffer[k+1];
				crossx -= PositionBuffer[k];
			}
			else
				crossx = deMake_Vertex(deVecX) * (float)m_Scale.x;
			if (z < StopZ-1)
			{
				crossz = PositionBuffer[k+m_GMMChunkSize+1];
				crossz -= PositionBuffer[k];
			}
			else
				crossz = deMake_Vertex(deVecZ) * (float)-m_Scale.z;
			Vec3d_Cross(crossx, crossz, NormalBuffer[k]);
			Vertex_Normalize(NormalBuffer[k]);

			//NormalBuffer[k] = deMake_Vertex(deVecY);

			++k;
		}
	}
	// get the min/max of this chunk
	deGeometry::AABBCenter(Chunk->BBox, Chunk->ChunkCenterPos);
	Chunk->Diagonal = deGeometry::AABBDiagonal(Chunk->BBox);
	// make some skirts (top,left,bottom,right)
	for (z = 0; z < 4; ++z)
	{
		EdgeMins[z] -= 0.01 * m_Scale.y;
		if (z%2 == 0)
		{
			RealZ = (z==0)? (m_GMMChunkSize * (ZCoord)) : (StopZ-1);
			RealZ = min(RealZ,Height-1);
			for (x = m_GMMChunkSize * (XCoord); x < StopX; ++x)
			{
				RealX = min(x,Width-1);
				TerrainHeight = GetScaledHeightAt(RealX,RealZ);
				PositionBuffer[k].x = (deFloat)(RealX * m_Scale.x);
				PositionBuffer[k].y = (deFloat)(EdgeMins[z]);
				PositionBuffer[k].z = (deFloat)(-RealZ * m_Scale.z);

				NormalBuffer[k] = deMake_Vertex(deVecY);

				TexCoords0[k].u = (deFloat)RealX / Width;
				TexCoords0[k].v = (deFloat)RealZ / Height;
				
				TexCoords1[k].u = (deFloat)RealX * m_DetailRepeat.u;
				TexCoords1[k].v = (deFloat)RealZ * m_DetailRepeat.v;

				TerrainHeight = (deFloat)(TerrainHeight * (sizeof(ColorRange)/sizeof(deARGB)-1) / m_Scale.y);
				Percentage = TerrainHeight - (int)TerrainHeight;
				color1 = ColorRange[(int)TerrainHeight];
				if (TerrainHeight < sizeof(ColorRange)/sizeof(deARGB))
					color2 = ColorRange[(int)TerrainHeight+1];
				else
					color2 = 0;
				DiffuseBuffer[k] = (0xff << 24) | 
					(deARGB((1-Percentage)*((color1>>16)&0xff) + (Percentage)*((color2>>16)&0xff)) << 16) |
					(deARGB((1-Percentage)*((color1>> 8)&0xff) + (Percentage)*((color2>> 8)&0xff)) << 8) |
					(deARGB((1-Percentage)*((color1    )&0xff) + (Percentage)*((color2    )&0xff)));
				
				k++;
			}
		}
		else
		{
			RealX = (z==1)? (m_GMMChunkSize * (XCoord)) : (StopX-1);
			RealX = min(RealX,Width-1);
			for (x = m_GMMChunkSize * (ZCoord); x < StopZ; ++x)
			{
				RealZ = min(x,Height-1);
				TerrainHeight = GetScaledHeightAt(RealX,RealZ);
				PositionBuffer[k].x = (deFloat)(RealX * m_Scale.x);
				PositionBuffer[k].y = (deFloat)(EdgeMins[z]);
				PositionBuffer[k].z = (deFloat)(-RealZ * m_Scale.z);

				NormalBuffer[k] = deMake_Vertex(deVecY);
				
				TexCoords0[k].u = (deFloat)RealX / Width;
				TexCoords0[k].v = (deFloat)RealZ / Height;
				
				TexCoords1[k].u = (deFloat)RealX * m_DetailRepeat.u;
				TexCoords1[k].v = (deFloat)RealZ * m_DetailRepeat.v;
				
				TerrainHeight = (deFloat)(TerrainHeight * (sizeof(ColorRange)/sizeof(deARGB)-1) / m_Scale.y);
				Percentage = TerrainHeight - (int)TerrainHeight;
				color1 = ColorRange[(int)TerrainHeight];
				if (TerrainHeight < sizeof(ColorRange)/sizeof(deARGB))
					color2 = ColorRange[(int)TerrainHeight+1];
				else
					color2 = 0;
				DiffuseBuffer[k] = (0xff << 24) | 
					(deARGB((1-Percentage)*((color1>>16)&0xff) + (Percentage)*((color2>>16)&0xff)) << 16) |
					(deARGB((1-Percentage)*((color1>> 8)&0xff) + (Percentage)*((color2>> 8)&0xff)) << 8) |
					(deARGB((1-Percentage)*((color1    )&0xff) + (Percentage)*((color2    )&0xff)));
				
				k++;
			}
		}
	}
	deFloat HeightVals[4];
	deFloat Percentage2;
	int u,v,j;
	memset(Chunk->UnitError, 0, sizeof(Chunk->UnitError)); // capital sin of sizeof(var) but bleh!
	for (j = 0; j < 8; ++j)
	{
		k = 1 << (j+1);
		if (k > m_GMMChunkSize)
			break;
		for (z = 0; z < m_GMMChunkSize; z += k)
		{
			for (x = 0; x < m_GMMChunkSize; x += k)
			{
				HeightVals[0] = PositionBuffer[(z  )*(m_GMMChunkSize+1) + x  ].y;
				HeightVals[1] = PositionBuffer[(z  )*(m_GMMChunkSize+1) + x+k].y;
				HeightVals[2] = PositionBuffer[(z+k)*(m_GMMChunkSize+1) + x  ].y;
				HeightVals[3] = PositionBuffer[(z+k)*(m_GMMChunkSize+1) + x+k].y;
				for (v = 1; v < k; ++v)
				{
					Percentage2 = (deFloat)v / (k);
					for (u = 1; u < k; ++u)
					{
						Percentage = (deFloat)u / (k);
						TerrainHeight = (1-Percentage2)*((1-Percentage)*HeightVals[0] + (Percentage)*HeightVals[1]) +
										(  Percentage2)*((1-Percentage)*HeightVals[2] + (Percentage)*HeightVals[3]);
						TerrainHeight -= PositionBuffer[(z+v)*(m_GMMChunkSize+1) + x+u].y;
						TerrainHeight = (deFloat)fabs(TerrainHeight);
						Chunk->UnitError[j] = max(Chunk->UnitError[j], TerrainHeight);
					}
				}
			}
		}
	}

	NewBuffer->DirtyBuffer(IdeVertexBuffer::BUFFER_POSITION);
	NewBuffer->DirtyBuffer(IdeVertexBuffer::BUFFER_NORMAL);
	NewBuffer->DirtyBuffer(IdeVertexBuffer::BUFFER_DIFFUSE);
	NewBuffer->DirtyBuffer(IdeVertexBuffer::BUFFER_TEXTURECOORDS0);
	NewBuffer->DirtyBuffer(IdeVertexBuffer::BUFFER_TEXTURECOORDS1);
	NewBuffer->Unlock();

	return deTRUE;
}

void deSceneTerrain::DestroyGMMChunk(deTerrainGMMChunk* Chunk)
{
	if (Chunk)
	{
		if (Chunk->Vertices)
			Chunk->Vertices->Release();
	}
}

deBoolean deSceneTerrain::RenderGMMChunk(const deTerrainGMMChunk* Chunk, IdeSceneGraph::deSceneTraversal* Params, const deTransform& WorldTransform)
{
	int LOD = 0;
	{
		deDouble Distance, TempDbl, BlendPercent;
		
		{
			int k;
			deVec3d CamPos, DirToChunk, AABBPoints[8];
			WorldTransform.TransposeTransform(Params->cam->Position().Translation, CamPos);
			DirToChunk = CamPos;
			DirToChunk -= Chunk->ChunkCenterPos;
			Distance = Vec3d_Normalize(DirToChunk);
			if (deGeometry::PointInAABB(CamPos, Chunk->BBox))
				Distance = 0;
			else
			{
				deGeometry::GetPointsOfAABB(Chunk->BBox, AABBPoints);
				for (k = 0; k < 8; ++k)
				{
					AABBPoints[k] -= CamPos;
					TempDbl = fabs(Vec3d_Dot(DirToChunk, AABBPoints[LOD]));
					Distance = min(Distance, TempDbl);
				}
			}
		}

		if (Distance != 0)
		{
			int len;
			deDouble FOV, PixelError;
			deRect ViewRect;
			len = m_IndexBuffers.Length();
			ViewRect = Params->cam->GetRect();
			FOV = (ViewRect.Right - ViewRect.Left) / (2*tan(DEG2RAD(Params->cam->GetFOV())/2));
			PixelError = 0;
			for (LOD = len-1; LOD > 0; --LOD)
			{
				PixelError = FOV * (Chunk->UnitError[LOD-1]) / (Distance);
				if (PixelError <= m_LODBias)
					break;
			}
			if (LOD == 0)
				PixelError = 0;
			if (LOD < len-1)
			{
				TempDbl = FOV * (Chunk->UnitError[LOD]) / (Distance);
				BlendPercent = (m_LODBias - TempDbl)/(PixelError - TempDbl);
			}
			else
				BlendPercent = 0;
		}
		else
		{
			LOD = 0;
			BlendPercent = 1;
		}
	}

	{
		IdeRender::deRenderObject obj;
		memset(&obj, 0, sizeof(IdeRender::deRenderObject));
		obj.Transform = WorldTransform;
		obj.VB = Chunk->Vertices;
		obj.IndexOverride = m_IndexBuffers[LOD];
		obj.SurfaceDetail.Texture = m_RenderTexture;
		obj.SurfaceDetail.TexBaseIndex = 0;
#pragma fixme(bbox might be rotated)

		Params->CurrentSceneGraph->LightExtents(m_WorldTransform.Translation, Chunk->BBox, obj.Lights, 4);

		if (!obj.Lights[0])
			obj.Lights[0] = s_TestLightBright;
		//obj.Lights[1] = s_TestLight;
		
		Params->render->RenderPrepBuffer(obj);
	}

	return deTRUE;
}

deBoolean deSceneTerrain::BuildGMMQuadtree()
{
	DestroyGMMQuadrant(m_GMMRoot);

	m_GMMRoot = new deTerrainGMMQuadrant;
	return BuildGMMQuadrant(m_GMMRoot, 0, 0, m_GMMChunksXSide, m_GMMChunksZSide);
}

deBoolean deSceneTerrain::BuildGMMQuadrant(deTerrainGMMQuadrant* Parent, long StartX, long StartZ, long StopX, long StopZ)
{
	deBoolean DivideX, DivideZ;
	int DiffX, DiffZ;
	DiffX = StopX - StartX;
	DiffZ = StopZ - StartZ;
	DivideX = DiffX > 1;
	DivideZ = DiffZ > 1;
	DiffX /= 2;
	DiffZ /= 2;
	if (DivideX && DivideZ)
	{
		Parent->Children[0] = new deTerrainGMMQuadrant;
		Parent->Children[1] = new deTerrainGMMQuadrant;
		Parent->Children[2] = new deTerrainGMMQuadrant;
		Parent->Children[3] = new deTerrainGMMQuadrant;
	}
	else if (DivideX)
	{
		Parent->Children[0] = new deTerrainGMMQuadrant;
		Parent->Children[1] = new deTerrainGMMQuadrant;
		Parent->Children[2] = NULL;
		Parent->Children[3] = NULL;
	}
	else if (DivideZ)
	{
		Parent->Children[0] = new deTerrainGMMQuadrant;
		Parent->Children[1] = NULL;
		Parent->Children[2] = new deTerrainGMMQuadrant;
		Parent->Children[3] = NULL;
	}
	else
	{
		Parent->Children[0] = NULL;
		Parent->Children[1] = NULL;
		Parent->Children[2] = NULL;
		Parent->Children[3] = NULL;
		Parent->Geometry = &m_GMMChunks[StartZ*m_GMMChunksXSide + StartX];
		Parent->BBox = Parent->Geometry->BBox;
	}
	if (Parent->Children[0])
	{
		Parent->Geometry = NULL;
		if (!BuildGMMQuadrant(Parent->Children[0], StartX, StartZ, StartX+DiffX, StartZ+DiffZ))
			goto BuildFailure;
		Parent->BBox = Parent->Children[0]->BBox;
		if (Parent->Children[1])
		{
			if (!BuildGMMQuadrant(Parent->Children[1], StartX+DiffX, StartZ, StopX, StartZ+DiffZ))
				goto BuildFailure;
			deGeometry::AABBUnion(Parent->BBox, Parent->Children[1]->BBox);
		}
		if (Parent->Children[2])
		{
			if (!BuildGMMQuadrant(Parent->Children[2], StartX, StartZ+DiffZ, StartX+DiffX, StopZ))
				goto BuildFailure;
			deGeometry::AABBUnion(Parent->BBox, Parent->Children[2]->BBox);
		}
		if (Parent->Children[3])
		{
			if (!BuildGMMQuadrant(Parent->Children[3], StartX+DiffX, StartZ+DiffZ, StopX, StopZ))
				goto BuildFailure;
			deGeometry::AABBUnion(Parent->BBox, Parent->Children[3]->BBox);
		}
	}
	deGeometry::MakeBoundSphereFromAABB(Parent->BBox, Parent->BSphere);

	return deTRUE;

BuildFailure:
	delete Parent->Children[0];
	delete Parent->Children[1];
	delete Parent->Children[2];
	delete Parent->Children[3];
	Parent->Children[0] = NULL;
	Parent->Children[1] = NULL;
	Parent->Children[2] = NULL;
	Parent->Children[3] = NULL;
	return deFALSE;
}

void deSceneTerrain::DestroyGMMQuadrant(deTerrainGMMQuadrant* &Quadrant)
{
	if (Quadrant)
	{
		DestroyGMMQuadrant(Quadrant->Children[0]);
		DestroyGMMQuadrant(Quadrant->Children[1]);
		DestroyGMMQuadrant(Quadrant->Children[2]);
		DestroyGMMQuadrant(Quadrant->Children[3]);
		delete Quadrant;
		Quadrant = NULL;
	}
}

deBoolean deSceneTerrain::RenderVisibleGMMChunks(deTerrainGMMQuadrant* Quadrant, IdeSceneGraph::deSceneTraversal* Params, const deTransform& WorldTransform, deBoolean AllVisible)
{
	if (!Quadrant)
		return deFALSE;
	deBoolean Valid = deTRUE;
	deBoolean Visible = AllVisible;
	if (!AllVisible)
	{
		deBoundSphere newsphere;
		newsphere.Radius = Quadrant->BSphere.Radius;
		WorldTransform.Transform(Quadrant->BSphere.Center, newsphere.Center);
		Visible = Params->CurrentFrustum->TestSphereInside(newsphere, deZeroVec, AllVisible);
		if (Visible && !AllVisible)
		{
			deAABB newaabb;
			deVec3d oldmin, oldmax;
			WorldTransform.Transform(Quadrant->BBox.Min, oldmin);
			WorldTransform.Transform(Quadrant->BBox.Max, oldmax);
			newaabb.Min.x = min(oldmin.x, oldmax.x);
			newaabb.Min.y = min(oldmin.y, oldmax.y);
			newaabb.Min.z = min(oldmin.z, oldmax.z);
			newaabb.Max.x = max(oldmin.x, oldmax.x);
			newaabb.Max.y = max(oldmin.y, oldmax.y);
			newaabb.Max.z = max(oldmin.z, oldmax.z);
			Visible = Params->CurrentFrustum->TestAABBInside(newaabb, deZeroVec, AllVisible);
		}
	}
	if (Visible)
	{
		if (Quadrant->Geometry)
		{
			if (!RenderGMMChunk(Quadrant->Geometry, Params, WorldTransform))
				Valid = deFALSE;
		}
		else
		{
			int k;
			long DebugFlags = IdeSceneGraph_GetSceneGraphPtr()->GetDebugFlags();
			if (DebugFlags & IdeSceneGraph::deDrawAABB)
			{
				IdeRender::deRenderObject obj;
				deAABB aabb;
				memset(&obj, 0, sizeof(IdeRender::deRenderObject));
				for (k = -1; k < 4; ++k)
				{
					if (k < 0)
					{
						if (Quadrant != m_GMMRoot)
							continue;
						else
							aabb = Quadrant->BBox;
					}
					else
					{
						if (!Quadrant->Children[k])
							continue;
						aabb = Quadrant->Children[k]->BBox;
					}
					aabb.Max -= aabb.Min;
					//obj.Transform = m_WorldTransform;
					//obj.Transform.Translation = aabb.Min;
					WorldTransform.Transform(aabb.Min, obj.Transform.Translation);
					//obj.Transform.Translation = m_WorldTransform.Translation;
					{
						deVec3d X,Y,Z;
						X = WorldTransform.R;
						X *= aabb.Max.x;
						Y = WorldTransform.U;
						Y *= aabb.Max.y;
						Z = WorldTransform.I;
						Z *= aabb.Max.z;
						((deTransform&)obj.Transform).SetRightUpIn(X,Y,Z);
					}
					obj.VB = s_AABB_VB;
					Params->render->RenderPrepBuffer(obj);
				}
			}
			for (k = 0; k < 4; ++k)
			{
				if (!Quadrant->Children[k])
					continue;
				if (!RenderVisibleGMMChunks(Quadrant->Children[k], Params, WorldTransform, AllVisible))
					Valid = deFALSE;
			}
		}
	}

	return Valid;
}

long deSceneTerrain::TraverseForRender(IdeSceneGraph::deSceneTraversal* Params)
{
	if (m_GMMChunks)
	{
		deTransform wtform;
		GetWorldTransform(wtform);
		RenderVisibleGMMChunks(m_GMMRoot, Params, wtform, deFALSE);
	}
	else if (m_VBuffers.size() > 0)
	{
		// the brute-force method (requires 32-bit index capabilities for larger than 65536 vertices)
		IdeRender::deRenderObject obj;
		deTransform tform;
		memset(&obj, 0, sizeof(IdeRender::deRenderObject));
		obj.Transform = tform;
		obj.VB = m_VBuffers[0];
		obj.SurfaceDetail.Texture = m_RenderTexture;
		obj.SurfaceDetail.TexBaseIndex = 0;
		
		Params->render->RenderPrepBuffer(obj);
	}

	return 0;
}

deBoolean deSceneTerrain::TestRoomCollision(const IdeCollision::deCollideParams & /*Params*/, IdeCollision * /*Collision*/)
{
#pragma todo (finish me)
	return deFALSE;
}

deBoolean deSceneTerrain::Serialize(IdeFile* File)
{
	if (!deSceneObject::Serialize(File))
		return deFALSE;
	
#pragma todo (write terrain info)
	return deFALSE;
}

deBoolean deSceneTerrain::DeSerialize(IdeFile* File, long DataLength)
{
	u32 AmtRead = 0;
	if (!deSceneObject::DeSerialize(File, DataLength, AmtRead))
		return deFALSE;
	
#pragma todo (read terrain info)
	return deFALSE;
}

deBoolean deSceneTerrain::DeSerializeLoad()
{
	if (!deSceneObject::DeSerializeLoad())
		return deFALSE;
	
#pragma todo (resolve terrain info)
	return deFALSE;
}
