/* Copyright (C) 2022 Wildfire Games.
 * This file is part of 0 A.D.
 *
 * 0 A.D. is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * 0 A.D. is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "precompiled.h"

#include "graphics/Terrain.h"

#include "graphics/Patch.h"
#include "graphics/TerrainProperties.h"
#include "graphics/TerrainTextureEntry.h"
#include "graphics/TerrainTextureManager.h"
#include "lib/sysdep/cpu.h"
#include "maths/FixedVector3D.h"
#include "maths/MathUtil.h"
#include "ps/CLogger.h"
#include "renderer/Renderer.h"
#include "simulation2/helpers/Pathfinding.h"

#include <string.h>

///////////////////////////////////////////////////////////////////////////////
// CTerrain constructor
CTerrain::CTerrain()
: m_Heightmap(0), m_Patches(0), m_MapSize(0), m_MapSizePatches(0),
m_BaseColor(255, 255, 255, 255)
{
}

///////////////////////////////////////////////////////////////////////////////
// CTerrain constructor
CTerrain::~CTerrain()
{
	ReleaseData();
}


///////////////////////////////////////////////////////////////////////////////
// ReleaseData: delete any data allocated by this terrain
void CTerrain::ReleaseData()
{
	m_HeightMipmap.ReleaseData();

	delete[] m_Heightmap;
	delete[] m_Patches;
}


///////////////////////////////////////////////////////////////////////////////
// Initialise: initialise this terrain to the given size
// using given heightmap to setup elevation data
bool CTerrain::Initialize(ssize_t patchesPerSide, const u16* data)
{
	// clean up any previous terrain
	ReleaseData();

	// store terrain size
	m_MapSize = patchesPerSide * PATCH_SIZE + 1;
	m_MapSizePatches = patchesPerSide;
	// allocate data for new terrain
	m_Heightmap = new u16[m_MapSize * m_MapSize];
	m_Patches = new CPatch[m_MapSizePatches * m_MapSizePatches];

	// given a heightmap?
	if (data)
	{
		// yes; keep a copy of it
		memcpy(m_Heightmap, data, m_MapSize*m_MapSize*sizeof(u16));
	}
	else
	{
		// build a flat terrain
		memset(m_Heightmap, 0, m_MapSize*m_MapSize*sizeof(u16));
	}

	// setup patch parents, indices etc
	InitialisePatches();

	// initialise mipmap
	m_HeightMipmap.Initialize(m_MapSize, m_Heightmap);

	return true;
}

///////////////////////////////////////////////////////////////////////////////
// CalcPosition: calculate the world space position of the vertex at (i,j)
// If i,j is off the map, it acts as if the edges of the terrain are extended
// outwards to infinity
void CTerrain::CalcPosition(ssize_t i, ssize_t j, CVector3D& pos) const
{
	ssize_t hi = Clamp<ssize_t>(i, 0, m_MapSize - 1);
	ssize_t hj = Clamp<ssize_t>(j, 0, m_MapSize - 1);
	u16 height = m_Heightmap[hj*m_MapSize + hi];
	pos.X = float(i*TERRAIN_TILE_SIZE);
	pos.Y = float(height*HEIGHT_SCALE);
	pos.Z = float(j*TERRAIN_TILE_SIZE);
}

///////////////////////////////////////////////////////////////////////////////
// CalcPositionFixed: calculate the world space position of the vertex at (i,j)
void CTerrain::CalcPositionFixed(ssize_t i, ssize_t j, CFixedVector3D& pos) const
{
	ssize_t hi = Clamp<ssize_t>(i, 0, m_MapSize - 1);
	ssize_t hj = Clamp<ssize_t>(j, 0, m_MapSize - 1);
	u16 height = m_Heightmap[hj*m_MapSize + hi];
	pos.X = fixed::FromInt(i) * (int)TERRAIN_TILE_SIZE;
	// fixed max value is 32767, but height is a u16, so divide by two to avoid overflow
	pos.Y = fixed::FromInt(height/ 2 ) / ((int)HEIGHT_UNITS_PER_METRE / 2);
	pos.Z = fixed::FromInt(j) * (int)TERRAIN_TILE_SIZE;
}


///////////////////////////////////////////////////////////////////////////////
// CalcNormal: calculate the world space normal of the vertex at (i,j)
void CTerrain::CalcNormal(ssize_t i, ssize_t j, CVector3D& normal) const
{
	CVector3D left, right, up, down;

	// Calculate normals of the four half-tile triangles surrounding this vertex:

	// get position of vertex where normal is being evaluated
	CVector3D basepos;
	CalcPosition(i, j, basepos);

	if (i > 0) {
		CalcPosition(i-1, j, left);
		left -= basepos;
		left.Normalize();
	}

	if (i < m_MapSize-1) {
		CalcPosition(i+1, j, right);
		right -= basepos;
		right.Normalize();
	}

	if (j > 0) {
		CalcPosition(i, j-1, up);
		up -= basepos;
		up.Normalize();
	}

	if (j < m_MapSize-1) {
		CalcPosition(i, j+1, down);
		down -= basepos;
		down.Normalize();
	}

	CVector3D n0 = up.Cross(left);
	CVector3D n1 = left.Cross(down);
	CVector3D n2 = down.Cross(right);
	CVector3D n3 = right.Cross(up);

	// Compute the mean of the normals
	normal = n0 + n1 + n2 + n3;
	float nlen=normal.Length();
	if (nlen>0.00001f) normal*=1.0f/nlen;
}

///////////////////////////////////////////////////////////////////////////////
// CalcNormalFixed: calculate the world space normal of the vertex at (i,j)
void CTerrain::CalcNormalFixed(ssize_t i, ssize_t j, CFixedVector3D& normal) const
{
	CFixedVector3D left, right, up, down;

	// Calculate normals of the four half-tile triangles surrounding this vertex:

	// get position of vertex where normal is being evaluated
	CFixedVector3D basepos;
	CalcPositionFixed(i, j, basepos);

	if (i > 0) {
		CalcPositionFixed(i-1, j, left);
		left -= basepos;
		left.Normalize();
	}

	if (i < m_MapSize-1) {
		CalcPositionFixed(i+1, j, right);
		right -= basepos;
		right.Normalize();
	}

	if (j > 0) {
		CalcPositionFixed(i, j-1, up);
		up -= basepos;
		up.Normalize();
	}

	if (j < m_MapSize-1) {
		CalcPositionFixed(i, j+1, down);
		down -= basepos;
		down.Normalize();
	}

	CFixedVector3D n0 = up.Cross(left);
	CFixedVector3D n1 = left.Cross(down);
	CFixedVector3D n2 = down.Cross(right);
	CFixedVector3D n3 = right.Cross(up);

	// Compute the mean of the normals
	normal = n0 + n1 + n2 + n3;
	normal.Normalize();
}

CVector3D CTerrain::CalcExactNormal(float x, float z) const
{
	// Clamp to size-2 so we can use the tiles (xi,zi)-(xi+1,zi+1)
	const ssize_t xi = Clamp<ssize_t>(floor(x / TERRAIN_TILE_SIZE), 0, m_MapSize - 2);
	const ssize_t zi = Clamp<ssize_t>(floor(z / TERRAIN_TILE_SIZE), 0, m_MapSize - 2);

	const float xf = Clamp(x / TERRAIN_TILE_SIZE-xi, 0.0f, 1.0f);
	const float zf = Clamp(z / TERRAIN_TILE_SIZE-zi, 0.0f, 1.0f);

	float h00 = m_Heightmap[zi*m_MapSize + xi];
	float h01 = m_Heightmap[(zi+1)*m_MapSize + xi];
	float h10 = m_Heightmap[zi*m_MapSize + (xi+1)];
	float h11 = m_Heightmap[(zi+1)*m_MapSize + (xi+1)];

	// Determine which terrain triangle this point is on,
	// then compute the normal of that triangle's plane

	if (GetTriangulationDir(xi, zi))
	{
		if (xf + zf <= 1.f)
		{
			// Lower-left triangle (don't use h11)
			return -CVector3D(TERRAIN_TILE_SIZE, (h10-h00)*HEIGHT_SCALE, 0).Cross(CVector3D(0, (h01-h00)*HEIGHT_SCALE, TERRAIN_TILE_SIZE)).Normalized();
		}
		else
		{
			// Upper-right triangle (don't use h00)
			return -CVector3D(TERRAIN_TILE_SIZE, (h11-h01)*HEIGHT_SCALE, 0).Cross(CVector3D(0, (h11-h10)*HEIGHT_SCALE, TERRAIN_TILE_SIZE)).Normalized();
		}
	}
	else
	{
		if (xf <= zf)
		{
			// Upper-left triangle (don't use h10)
			return -CVector3D(TERRAIN_TILE_SIZE, (h11-h01)*HEIGHT_SCALE, 0).Cross(CVector3D(0, (h01-h00)*HEIGHT_SCALE, TERRAIN_TILE_SIZE)).Normalized();
		}
		else
		{
			// Lower-right triangle (don't use h01)
			return -CVector3D(TERRAIN_TILE_SIZE, (h10-h00)*HEIGHT_SCALE, 0).Cross(CVector3D(0, (h11-h10)*HEIGHT_SCALE, TERRAIN_TILE_SIZE)).Normalized();
		}
	}
}

///////////////////////////////////////////////////////////////////////////////
// GetPatch: return the patch at (i,j) in patch space, or null if the patch is
// out of bounds
CPatch* CTerrain::GetPatch(ssize_t i, ssize_t j) const
{
	// range check (invalid indices are passed in by the culling and
	// patch blend code because they iterate from 0..#patches and examine
	// neighbors without checking if they're already on the edge)
	if( (size_t)i >= (size_t)m_MapSizePatches || (size_t)j >= (size_t)m_MapSizePatches )
		return 0;

	return &m_Patches[(j*m_MapSizePatches)+i];
}


///////////////////////////////////////////////////////////////////////////////
// GetTile: return the tile at (i,j) in tile space, or null if the tile is out
// of bounds
CMiniPatch* CTerrain::GetTile(ssize_t i, ssize_t j) const
{
	// see comment above
	if( (size_t)i >= (size_t)(m_MapSize-1) || (size_t)j >= (size_t)(m_MapSize-1) )
		return 0;

	CPatch* patch=GetPatch(i/PATCH_SIZE, j/PATCH_SIZE);	// can't fail (due to above check)
	return &patch->m_MiniPatches[j%PATCH_SIZE][i%PATCH_SIZE];
}

float CTerrain::GetVertexGroundLevel(ssize_t i, ssize_t j) const
{
	i = Clamp<ssize_t>(i, 0, m_MapSize - 1);
	j = Clamp<ssize_t>(j, 0, m_MapSize - 1);
	return HEIGHT_SCALE * m_Heightmap[j*m_MapSize + i];
}

fixed CTerrain::GetVertexGroundLevelFixed(ssize_t i, ssize_t j) const
{
	i = Clamp<ssize_t>(i, 0, m_MapSize - 1);
	j = Clamp<ssize_t>(j, 0, m_MapSize - 1);
	// Convert to fixed metres (being careful to avoid intermediate overflows)
	return fixed::FromInt(m_Heightmap[j*m_MapSize + i] / 2) / (int)(HEIGHT_UNITS_PER_METRE / 2);
}

fixed CTerrain::GetSlopeFixed(ssize_t i, ssize_t j) const
{
	// Clamp to size-2 so we can use the tiles (i,j)-(i+1,j+1)
	i = Clamp<ssize_t>(i, 0, m_MapSize - 2);
	j = Clamp<ssize_t>(j, 0, m_MapSize - 2);

	u16 h00 = m_Heightmap[j*m_MapSize + i];
	u16 h01 = m_Heightmap[(j+1)*m_MapSize + i];
	u16 h10 = m_Heightmap[j*m_MapSize + (i+1)];
	u16 h11 = m_Heightmap[(j+1)*m_MapSize + (i+1)];

	// Difference of highest point from lowest point
	u16 delta = std::max(std::max(h00, h01), std::max(h10, h11)) -
	            std::min(std::min(h00, h01), std::min(h10, h11));

	// Compute fractional slope (being careful to avoid intermediate overflows)
	return fixed::FromInt(delta / TERRAIN_TILE_SIZE) / (int)HEIGHT_UNITS_PER_METRE;
}

fixed CTerrain::GetExactSlopeFixed(fixed x, fixed z) const
{
	// Clamp to size-2 so we can use the tiles (xi,zi)-(xi+1,zi+1)
	const ssize_t xi = Clamp<ssize_t>((x / static_cast<int>(TERRAIN_TILE_SIZE)).ToInt_RoundToZero(), 0, m_MapSize - 2);
	const ssize_t zi = Clamp<ssize_t>((z / static_cast<int>(TERRAIN_TILE_SIZE)).ToInt_RoundToZero(), 0, m_MapSize - 2);

	const fixed one = fixed::FromInt(1);

	const fixed xf = Clamp((x / static_cast<int>(TERRAIN_TILE_SIZE)) - fixed::FromInt(xi), fixed::Zero(), one);
	const fixed zf = Clamp((z / static_cast<int>(TERRAIN_TILE_SIZE)) - fixed::FromInt(zi), fixed::Zero(), one);

	u16 h00 = m_Heightmap[zi*m_MapSize + xi];
	u16 h01 = m_Heightmap[(zi+1)*m_MapSize + xi];
	u16 h10 = m_Heightmap[zi*m_MapSize + (xi+1)];
	u16 h11 = m_Heightmap[(zi+1)*m_MapSize + (xi+1)];

	u16 delta;
	if (GetTriangulationDir(xi, zi))
	{
		if (xf + zf <= one)
		{
			// Lower-left triangle (don't use h11)
			delta = std::max(std::max(h00, h01), h10) -
			        std::min(std::min(h00, h01), h10);
		}
		else
		{
			// Upper-right triangle (don't use h00)
			delta = std::max(std::max(h01, h10), h11) -
			        std::min(std::min(h01, h10), h11);
		}
	}
	else
	{
		if (xf <= zf)
		{
			// Upper-left triangle (don't use h10)
			delta = std::max(std::max(h00, h01), h11) -
			        std::min(std::min(h00, h01), h11);
		}
		else
		{
			// Lower-right triangle (don't use h01)
			delta = std::max(std::max(h00, h10), h11) -
			        std::min(std::min(h00, h10), h11);
		}
	}

	// Compute fractional slope (being careful to avoid intermediate overflows)
	return fixed::FromInt(delta / TERRAIN_TILE_SIZE) / (int)HEIGHT_UNITS_PER_METRE;
}

float CTerrain::GetFilteredGroundLevel(float x, float z, float radius) const
{
	// convert to [0,1] interval
	float nx = x / (TERRAIN_TILE_SIZE*m_MapSize);
	float nz = z / (TERRAIN_TILE_SIZE*m_MapSize);
	float nr = radius / (TERRAIN_TILE_SIZE*m_MapSize);

	// get trilinear filtered mipmap height
	return HEIGHT_SCALE * m_HeightMipmap.GetTrilinearGroundLevel(nx, nz, nr);
}

float CTerrain::GetExactGroundLevel(float x, float z) const
{
	// Clamp to size-2 so we can use the tiles (xi,zi)-(xi+1,zi+1)
	const ssize_t xi = Clamp<ssize_t>(floor(x / TERRAIN_TILE_SIZE), 0, m_MapSize - 2);
	const ssize_t zi = Clamp<ssize_t>(floor(z / TERRAIN_TILE_SIZE), 0, m_MapSize - 2);

	const float xf = Clamp(x / TERRAIN_TILE_SIZE - xi, 0.0f, 1.0f);
	const float zf = Clamp(z / TERRAIN_TILE_SIZE - zi, 0.0f, 1.0f);

	float h00 = m_Heightmap[zi*m_MapSize + xi];
	float h01 = m_Heightmap[(zi+1)*m_MapSize + xi];
	float h10 = m_Heightmap[zi*m_MapSize + (xi+1)];
	float h11 = m_Heightmap[(zi+1)*m_MapSize + (xi+1)];

	// Determine which terrain triangle this point is on,
	// then compute the linearly-interpolated height on that triangle's plane

	if (GetTriangulationDir(xi, zi))
	{
		if (xf + zf <= 1.f)
		{
			// Lower-left triangle (don't use h11)
			return HEIGHT_SCALE * (h00 + (h10-h00)*xf + (h01-h00)*zf);
		}
		else
		{
			// Upper-right triangle (don't use h00)
			return HEIGHT_SCALE * (h11 + (h01-h11)*(1-xf) + (h10-h11)*(1-zf));
		}
	}
	else
	{
		if (xf <= zf)
		{
			// Upper-left triangle (don't use h10)
			return HEIGHT_SCALE * (h00 + (h11-h01)*xf + (h01-h00)*zf);
		}
		else
		{
			// Lower-right triangle (don't use h01)
			return HEIGHT_SCALE * (h00 + (h10-h00)*xf + (h11-h10)*zf);
		}
	}
}

fixed CTerrain::GetExactGroundLevelFixed(fixed x, fixed z) const
{
	// Clamp to size-2 so we can use the tiles (xi,zi)-(xi+1,zi+1)
	const ssize_t xi = Clamp<ssize_t>((x / static_cast<int>(TERRAIN_TILE_SIZE)).ToInt_RoundToZero(), 0, m_MapSize - 2);
	const ssize_t zi = Clamp<ssize_t>((z / static_cast<int>(TERRAIN_TILE_SIZE)).ToInt_RoundToZero(), 0, m_MapSize - 2);

	const fixed one = fixed::FromInt(1);

	const fixed xf = Clamp((x / static_cast<int>(TERRAIN_TILE_SIZE)) - fixed::FromInt(xi), fixed::Zero(), one);
	const fixed zf = Clamp((z / static_cast<int>(TERRAIN_TILE_SIZE)) - fixed::FromInt(zi), fixed::Zero(), one);

	u16 h00 = m_Heightmap[zi*m_MapSize + xi];
	u16 h01 = m_Heightmap[(zi+1)*m_MapSize + xi];
	u16 h10 = m_Heightmap[zi*m_MapSize + (xi+1)];
	u16 h11 = m_Heightmap[(zi+1)*m_MapSize + (xi+1)];

	// Intermediate scaling of xf, so we don't overflow in the multiplications below
	// (h00 <= 65535, xf <= 1, max fixed is < 32768; divide by 2 here so xf1*h00 <= 32767.5)
	const fixed xf0 = xf / 2;
	const fixed xf1 = (one - xf) / 2;

	// Linearly interpolate
	return ((one - zf).Multiply(xf1 * h00 + xf0 * h10)
	              + zf.Multiply(xf1 * h01 + xf0 * h11)) / (int)(HEIGHT_UNITS_PER_METRE / 2);

	// TODO: This should probably be more like GetExactGroundLevel()
	// in handling triangulation properly
}

bool CTerrain::GetTriangulationDir(ssize_t i, ssize_t j) const
{
	// Clamp to size-2 so we can use the tiles (i,j)-(i+1,j+1)
	i = Clamp<ssize_t>(i, 0, m_MapSize - 2);
	j = Clamp<ssize_t>(j, 0, m_MapSize - 2);

	int h00 = m_Heightmap[j*m_MapSize + i];
	int h01 = m_Heightmap[(j+1)*m_MapSize + i];
	int h10 = m_Heightmap[j*m_MapSize + (i+1)];
	int h11 = m_Heightmap[(j+1)*m_MapSize + (i+1)];

	// Prefer triangulating in whichever direction means the midpoint of the diagonal
	// will be the highest. (In particular this means a diagonal edge will be straight
	// along the top, and jagged along the bottom, which makes sense for terrain.)
	int mid1 = h00+h11;
	int mid2 = h01+h10;
	return (mid1 < mid2);
}

void CTerrain::ResizeAndOffset(ssize_t size, ssize_t horizontalOffset, ssize_t verticalOffset)
{
	if (size == m_MapSizePatches && horizontalOffset == 0 && verticalOffset == 0)
	{
		// Inexplicable request to resize terrain to the same size, ignore it.
		return;
	}

	if (!m_Heightmap ||
		std::abs(horizontalOffset) >= size / 2 + m_MapSizePatches / 2 ||
		std::abs(verticalOffset) >= size / 2 + m_MapSizePatches / 2)
	{
		// We have not yet created a terrain, or we are offsetting outside the current source.
		// Let's build a default terrain of the given size now.
		Initialize(size, 0);
		return;
	}

	// Allocate data for new terrain.
	const ssize_t newMapSize = size * PATCH_SIZE + 1;
	u16* newHeightmap = new u16[newMapSize * newMapSize];
	memset(newHeightmap, 0, newMapSize * newMapSize * sizeof(u16));
	CPatch* newPatches = new CPatch[size * size];

	// O--------------------+
	// | Source             |
	// |                    |
	// | Source Center (SC) |
	// |          X         |
	// |             A------+----------------+
	// |             |      |    Destination |
	// |             |      |                |
	// +-------------+------B                |
	//               |    Dest. Center (DC)  |
	//               |           X           |
	//               |                       |
	//               |                       |
	//               |                       |
	//               |                       |
	//               +-----------------------+
	//
	// Calculations below should also account cases like:
	//
	// +----------+   +----------+     +----------+   +---+--+---+   +------+
	// |S         |   |D         |     |S         |   |S  |  |  D|   |D     |
	// |   +---+  |   |   +---+  |   +-+-+        |   |   |  |   |   |  +---+--+
	// |   | D |  |   |   | S |  |   |D| |        |   +---+--+---+   +--+---+  |
	// |   +---+  |   |   +---+  |   +-+-+        |                     |     S|
	// +----------+   +----------+     +----------+                     +------+
	//
	// O = (0, 0)
	// SC = (m_MapSizePatches / 2, m_MapSizePatches / 2)
	// DC - SC = (horizontalOffset, verticalOffset)
	//
	// Source upper left:
	// A = (max(0, (m_MapSizePatches - size) / 2 + horizontalOffset),
	//      max(0, (m_MapSizePatches - size) / 2 + verticalOffset))
	// Source bottom right:
	// B = (min(m_MapSizePatches, (m_MapSizePatches + size) / 2 + horizontalOffset),
	//      min(m_MapSizePatches, (m_MapSizePatches + size) / 2 + verticalOffset))
	//
	// A-B is the area that we have to copy from the source to the destination.

	// Restate center offset as a window over destination.
	// This has the effect of always considering the source to be the same size or smaller.
	const ssize_t sourceUpperLeftX = std::max(
		static_cast<ssize_t>(0), m_MapSizePatches / 2 - size / 2 + horizontalOffset);
	const ssize_t sourceUpperLeftZ = std::max(
		static_cast<ssize_t>(0), m_MapSizePatches / 2 - size / 2 + verticalOffset);

	const ssize_t destUpperLeftX = std::max(
		static_cast<ssize_t>(0), (size / 2 - m_MapSizePatches / 2 - horizontalOffset));
	const ssize_t destUpperLeftZ = std::max(
		static_cast<ssize_t>(0), (size / 2 - m_MapSizePatches / 2 - verticalOffset));

	const ssize_t width =
		std::min(m_MapSizePatches, m_MapSizePatches / 2 + horizontalOffset + size / 2) - sourceUpperLeftX;
	const ssize_t depth =
		std::min(m_MapSizePatches, m_MapSizePatches / 2 + verticalOffset + size / 2) - sourceUpperLeftZ;

	for (ssize_t j = 0; j < depth * PATCH_SIZE; ++j)
	{
		// Copy the main part from the source. Destination heightmap:
		// +----------+
		// |          |
		// |   1234   | < current j-th row for example.
		// |   5678   |
		// |          |
		// +----------+
		u16* dst = newHeightmap + (j + destUpperLeftZ * PATCH_SIZE) * newMapSize + destUpperLeftX * PATCH_SIZE;
		u16* src = m_Heightmap + (j + sourceUpperLeftZ * PATCH_SIZE) * m_MapSize + sourceUpperLeftX * PATCH_SIZE;
		std::copy_n(src, width * PATCH_SIZE, dst);
		if (destUpperLeftX > 0)
		{
			// Fill the preceding part by copying the first elements of the
			// main part. Destination heightmap:
			// +----------+
			// |          |
			// |1111234   |  < current j-th row for example.
			// |   5678   |
			// |          |
			// +----------+
			u16* dst_prefix = newHeightmap + (j + destUpperLeftZ * PATCH_SIZE) * newMapSize;
			std::fill_n(dst_prefix, destUpperLeftX * PATCH_SIZE, dst[0]);
		}
		if ((destUpperLeftX + width) * PATCH_SIZE < newMapSize)
		{
			// Fill the succeeding part by copying the last elements of the
			// main part. Destination heightmap:
			// +----------+
			// |          |
			// |1111234444|  < current j-th row for example.
			// |   5678   |
			// |          |
			// +----------+
			u16* dst_suffix = dst + width * PATCH_SIZE;
			std::fill_n(
				dst_suffix,
				newMapSize - (width + destUpperLeftX) * PATCH_SIZE,
				dst[width * PATCH_SIZE - 1]);
		}
	}
	// Copy over heights from the preceding row. Destination heightmap:
	// +----------+
	// |1111234444| < copied from the row below
	// |1111234444|
	// |5555678888|
	// |          |
	// +----------+
	for (ssize_t j = 0; j < destUpperLeftZ * PATCH_SIZE; ++j)
	{

		u16* dst = newHeightmap + j * newMapSize;
		u16* src = newHeightmap + destUpperLeftZ * PATCH_SIZE * newMapSize;
		std::copy_n(src, newMapSize, dst);
	}
	// Copy over heights from the succeeding row. Destination heightmap:
	// +----------+
	// |1111234444|
	// |1111234444|
	// |5555678888|
	// |5555678888| < copied from the row above
	// +----------+
	for (ssize_t j = (destUpperLeftZ + depth) * PATCH_SIZE; j < newMapSize; ++j)
	{
		u16* dst = newHeightmap + j * newMapSize;
		u16* src = newHeightmap + ((destUpperLeftZ + depth) * PATCH_SIZE - 1) * newMapSize;
		std::copy_n(src, newMapSize, dst);
	}

	// Now build new patches. The same process as for the heightmap.
	for (ssize_t j = 0; j < depth; ++j)
	{
		for (ssize_t i = 0; i < width; ++i)
		{
			const CPatch& src =
				m_Patches[(sourceUpperLeftZ + j) * m_MapSizePatches + sourceUpperLeftX + i];
			CPatch& dst =
				newPatches[(destUpperLeftZ + j) * size + destUpperLeftX + i];
			std::copy_n(&src.m_MiniPatches[0][0], PATCH_SIZE * PATCH_SIZE, &dst.m_MiniPatches[0][0]);
		}
		for (ssize_t i = 0; i < destUpperLeftX; ++i)
			for (ssize_t jPatch = 0; jPatch < PATCH_SIZE; ++jPatch)
			{
				const CMiniPatch& src =
					newPatches[(destUpperLeftZ + j) * size + destUpperLeftX]
						.m_MiniPatches[jPatch][0];
				for (ssize_t iPatch = 0; iPatch < PATCH_SIZE; ++iPatch)
				{
					CMiniPatch& dst =
						newPatches[(destUpperLeftZ + j) * size + i]
							.m_MiniPatches[jPatch][iPatch];
					dst = src;
				}
			}
		for (ssize_t i = destUpperLeftX + width; i < size; ++i)
		{
			for (ssize_t jPatch = 0; jPatch < PATCH_SIZE; ++jPatch)
			{
				const CMiniPatch& src =
					newPatches[(destUpperLeftZ + j) * size + destUpperLeftX + width - 1]
						.m_MiniPatches[jPatch][PATCH_SIZE - 1];
				for (ssize_t iPatch = 0; iPatch < PATCH_SIZE; ++iPatch)
				{
					CMiniPatch& dst =
						newPatches[(destUpperLeftZ + j) * size + i].m_MiniPatches[jPatch][iPatch];
					dst = src;
				}
			}
		}
	}

	for (ssize_t j = 0; j < destUpperLeftZ; ++j)
		for (ssize_t i = 0; i < size; ++i)
			for (ssize_t iPatch = 0; iPatch < PATCH_SIZE; ++iPatch)
			{
				const CMiniPatch& src =
					newPatches[destUpperLeftZ * size + i].m_MiniPatches[0][iPatch];
				for (ssize_t jPatch = 0; jPatch < PATCH_SIZE; ++jPatch)
				{
					CMiniPatch& dst =
						newPatches[j * size + i].m_MiniPatches[jPatch][iPatch];
					dst = src;
				}
			}
	for (ssize_t j = destUpperLeftZ + depth; j < size; ++j)
		for (ssize_t i = 0; i < size; ++i)
			for (ssize_t iPatch = 0; iPatch < PATCH_SIZE; ++iPatch)
			{
				const CMiniPatch& src =
					newPatches[(destUpperLeftZ + depth - 1) * size + i].m_MiniPatches[0][iPatch];
				for (ssize_t jPatch = 0; jPatch < PATCH_SIZE; ++jPatch)
				{
					CMiniPatch& dst =
						newPatches[j * size + i].m_MiniPatches[jPatch][iPatch];
					dst = src;
				}
			}

	// Release all the original data.
	ReleaseData();

	// Store new data.
	m_Heightmap = newHeightmap;
	m_Patches = newPatches;
	m_MapSize = newMapSize;
	m_MapSizePatches = size;

	// Initialise all the new patches.
	InitialisePatches();

	// Initialise mipmap.
	m_HeightMipmap.Initialize(m_MapSize, m_Heightmap);
}

///////////////////////////////////////////////////////////////////////////////
// InitialisePatches: initialise patch data
void CTerrain::InitialisePatches()
{
	for (ssize_t j = 0; j < m_MapSizePatches; j++)
	{
		for (ssize_t i = 0; i < m_MapSizePatches; i++)
		{
			CPatch* patch = GetPatch(i, j);	// can't fail
			patch->Initialize(this, i, j);
		}
	}
}

///////////////////////////////////////////////////////////////////////////////
// SetHeightMap: set up a new heightmap from 16-bit source data;
// assumes heightmap matches current terrain size
void CTerrain::SetHeightMap(u16* heightmap)
{
	// keep a copy of the given heightmap
	memcpy(m_Heightmap, heightmap, m_MapSize*m_MapSize*sizeof(u16));

	// recalculate patch bounds, invalidate vertices
	for (ssize_t j = 0; j < m_MapSizePatches; j++)
	{
		for (ssize_t i = 0; i < m_MapSizePatches; i++)
		{
			CPatch* patch = GetPatch(i, j);	// can't fail
			patch->InvalidateBounds();
			patch->SetDirty(RENDERDATA_UPDATE_VERTICES);
		}
	}

	// update mipmap
	m_HeightMipmap.Update(m_Heightmap);
}


///////////////////////////////////////////////////////////////////////////////

void CTerrain::MakeDirty(ssize_t i0, ssize_t j0, ssize_t i1, ssize_t j1, int dirtyFlags)
{
	// Finds the inclusive limits of the patches that include the specified range of tiles
	ssize_t pi0 = Clamp<ssize_t>( i0   /PATCH_SIZE, 0, m_MapSizePatches-1);
	ssize_t pi1 = Clamp<ssize_t>((i1-1)/PATCH_SIZE, 0, m_MapSizePatches-1);
	ssize_t pj0 = Clamp<ssize_t>( j0   /PATCH_SIZE, 0, m_MapSizePatches-1);
	ssize_t pj1 = Clamp<ssize_t>((j1-1)/PATCH_SIZE, 0, m_MapSizePatches-1);

	for (ssize_t j = pj0; j <= pj1; j++)
	{
		for (ssize_t i = pi0; i <= pi1; i++)
		{
			CPatch* patch = GetPatch(i, j);	// can't fail (i,j were clamped)
			if (dirtyFlags & RENDERDATA_UPDATE_VERTICES)
				patch->CalcBounds();
			patch->SetDirty(dirtyFlags);
		}
	}

	if (m_Heightmap)
	{
		m_HeightMipmap.Update(m_Heightmap,
			Clamp<ssize_t>(i0, 0, m_MapSize - 1),
			Clamp<ssize_t>(j0, 0, m_MapSize - 1),
			Clamp<ssize_t>(i1, 1, m_MapSize),
			Clamp<ssize_t>(j1, 1, m_MapSize)
		);
	}
}

void CTerrain::MakeDirty(int dirtyFlags)
{
	for (ssize_t j = 0; j < m_MapSizePatches; j++)
	{
		for (ssize_t i = 0; i < m_MapSizePatches; i++)
		{
			CPatch* patch = GetPatch(i, j);	// can't fail
			if (dirtyFlags & RENDERDATA_UPDATE_VERTICES)
				patch->CalcBounds();
			patch->SetDirty(dirtyFlags);
		}
	}

	if (m_Heightmap)
		m_HeightMipmap.Update(m_Heightmap);
}

CBoundingBoxAligned CTerrain::GetVertexesBound(ssize_t i0, ssize_t j0, ssize_t i1, ssize_t j1)
{
	i0 = Clamp<ssize_t>(i0, 0, m_MapSize - 1);
	j0 = Clamp<ssize_t>(j0, 0, m_MapSize - 1);
	i1 = Clamp<ssize_t>(i1, 0, m_MapSize - 1);
	j1 = Clamp<ssize_t>(j1, 0, m_MapSize - 1);

	u16 minH = 65535;
	u16 maxH = 0;

	for (ssize_t j = j0; j <= j1; ++j)
	{
		for (ssize_t i = i0; i <= i1; ++i)
		{
			minH = std::min(minH, m_Heightmap[j*m_MapSize + i]);
			maxH = std::max(maxH, m_Heightmap[j*m_MapSize + i]);
		}
	}

	CBoundingBoxAligned bound;
	bound[0].X = (float)(i0*TERRAIN_TILE_SIZE);
	bound[0].Y = (float)(minH*HEIGHT_SCALE);
	bound[0].Z = (float)(j0*TERRAIN_TILE_SIZE);
	bound[1].X = (float)(i1*TERRAIN_TILE_SIZE);
	bound[1].Y = (float)(maxH*HEIGHT_SCALE);
	bound[1].Z = (float)(j1*TERRAIN_TILE_SIZE);
	return bound;
}
