849 lines
27 KiB
C++
849 lines
27 KiB
C++
/* Copyright (C) 2020 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/>.
|
|
*/
|
|
|
|
/*
|
|
* Describes ground via heightmap and array of CPatch.
|
|
*/
|
|
|
|
#include "precompiled.h"
|
|
|
|
#include "lib/res/graphics/ogl_tex.h"
|
|
#include "lib/sysdep/cpu.h"
|
|
|
|
#include "renderer/Renderer.h"
|
|
|
|
#include "TerrainProperties.h"
|
|
#include "TerrainTextureEntry.h"
|
|
#include "TerrainTextureManager.h"
|
|
|
|
#include <string.h>
|
|
#include "Terrain.h"
|
|
#include "Patch.h"
|
|
#include "maths/FixedVector3D.h"
|
|
#include "maths/MathUtil.h"
|
|
#include "ps/CLogger.h"
|
|
#include "simulation2/helpers/Pathfinding.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;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
CStr8 CTerrain::GetMovementClass(ssize_t i, ssize_t j) const
|
|
{
|
|
CMiniPatch* tile = GetTile(i, j);
|
|
if (tile && tile->GetTextureEntry())
|
|
return tile->GetTextureEntry()->GetProperties().GetMovementClass();
|
|
|
|
return "default";
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// 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(i, static_cast<ssize_t>(0), m_MapSize - 1);
|
|
ssize_t hj = Clamp(j, static_cast<ssize_t>(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(i, static_cast<ssize_t>(0), m_MapSize - 1);
|
|
ssize_t hj = Clamp(j, static_cast<ssize_t>(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(static_cast<ssize_t>(floor(x / TERRAIN_TILE_SIZE)), static_cast<ssize_t>(0), m_MapSize - 2);
|
|
const ssize_t zi = Clamp(static_cast<ssize_t>(floor(z / TERRAIN_TILE_SIZE)), static_cast<ssize_t>(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(i, static_cast<ssize_t>(0), m_MapSize - 1);
|
|
j = Clamp(j, static_cast<ssize_t>(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(i, static_cast<ssize_t>(0), m_MapSize - 1);
|
|
j = Clamp(j, static_cast<ssize_t>(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(i, static_cast<ssize_t>(0), m_MapSize - 2);
|
|
j = Clamp(j, static_cast<ssize_t>(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(i, static_cast<ssize_t>(0), m_MapSize - 2);
|
|
j = Clamp(j, static_cast<ssize_t>(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( i0 /PATCH_SIZE, static_cast<ssize_t>(0), m_MapSizePatches-1);
|
|
ssize_t pi1 = Clamp((i1-1)/PATCH_SIZE, static_cast<ssize_t>(0), m_MapSizePatches-1);
|
|
ssize_t pj0 = Clamp( j0 /PATCH_SIZE, static_cast<ssize_t>(0), m_MapSizePatches-1);
|
|
ssize_t pj1 = Clamp((j1-1)/PATCH_SIZE, static_cast<ssize_t>(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(i0, static_cast<ssize_t>(0), m_MapSize - 1),
|
|
Clamp(j0, static_cast<ssize_t>(0), m_MapSize - 1),
|
|
Clamp(i1, static_cast<ssize_t>(1), m_MapSize),
|
|
Clamp(j1, static_cast<ssize_t>(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(i0, static_cast<ssize_t>(0), m_MapSize - 1);
|
|
j0 = Clamp(j0, static_cast<ssize_t>(0), m_MapSize - 1);
|
|
i1 = Clamp(i1, static_cast<ssize_t>(0), m_MapSize - 1);
|
|
j1 = Clamp(j1, static_cast<ssize_t>(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;
|
|
}
|