1
0
forked from 0ad/0ad
0ad/source/graphics/Terrain.cpp
wraitii 410d2e883a Remove leftover terrain-based movement cost code.
6581796103 removed the ability for terrain to affect movement speed. The
JPS pathfinder cannot support it, and the approach was poor anyways,
coupling rendering data with simulation data.
This lets us remove the dependency on CTerrainTextureManager everywhere.

Tested by: langbart
Differential Revision: https://code.wildfiregames.com/D4459
This was SVN commit r26269.
2022-01-29 08:22:28 +00:00

832 lines
26 KiB
C++

/* 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;
}