1
0
forked from 0ad/0ad
0ad/source/graphics/Terrain.cpp
pyrolink b5d9da29c1 #When appropriate (i.e. wheeled units), units will align with the slope of the terrain. *JS documentation
*added CTerrain::getSlopeAngle[Face]() which returns the angle in
radians of the slope of the terrain.  getSlopeAngleFace should be used
for entities because it takes into account which way the entity is
facing, which allows for negative angles.
*the max and min graphical rotation for the slope is under traits.pitch.
This is used so that you can have certain units such as infantry that
don't rotate very far go down steep slopes but not rotate too much.
*JS documentation in docs/JSReg.rtf -- I put in most of the relevant
global functions; still needs entity functions.

This was SVN commit r3794.
2006-04-22 05:52:23 +00:00

614 lines
16 KiB
C++
Executable File

///////////////////////////////////////////////////////////////////////////////
//
// Name: Terrain.cpp
// Author: Rich Cross
// Contact: rich@wildfiregames.com
//
///////////////////////////////////////////////////////////////////////////////
#include "precompiled.h"
#include "lib/res/graphics/ogl_tex.h"
#include "lib/res/mem.h"
#include "renderer/Renderer.h"
#include "renderer/WaterManager.h"
#include <string.h>
#include "Terrain.h"
#include "MathUtil.h"
///////////////////////////////////////////////////////////////////////////////
// CTerrain constructor
CTerrain::CTerrain() : m_Heightmap(0), m_Patches(0), m_MapSize(0), m_MapSizePatches(0)
{
}
///////////////////////////////////////////////////////////////////////////////
// CTerrain constructor
CTerrain::~CTerrain()
{
ReleaseData();
}
///////////////////////////////////////////////////////////////////////////////
// ReleaseData: delete any data allocated by this terrain
void CTerrain::ReleaseData()
{
delete[] m_Heightmap;
delete[] m_Patches;
}
///////////////////////////////////////////////////////////////////////////////
// Initialise: initialise this terrain to the given size (in patches per side);
// using given heightmap to setup elevation data
bool CTerrain::Initialize(u32 size,const u16* data)
{
// clean up any previous terrain
ReleaseData();
// store terrain size
m_MapSize=(size*PATCH_SIZE)+1;
m_MapSizePatches=size;
// 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
memcpy2(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();
return true;
}
///////////////////////////////////////////////////////////////////////////////
// CalcPosition: calculate the world space position of the vertex at (i,j)
void CTerrain::CalcPosition(i32 i, i32 j, CVector3D& pos) const
{
u16 height;
if ((u32)i < m_MapSize && (u32)j < m_MapSize) // will reject negative coordinates
height = m_Heightmap[j*m_MapSize + i];
else
height = 0;
pos.X = float(i*CELL_SIZE);
pos.Y = float(height*HEIGHT_SCALE);
pos.Z = float(j*CELL_SIZE);
}
///////////////////////////////////////////////////////////////////////////////
// CalcNormal: calculate the world space normal of the vertex at (i,j)
void CTerrain::CalcNormal(u32 i, u32 j, CVector3D& normal) const
{
CVector3D left, right, up, down;
left.Clear();
right.Clear();
up.Clear();
down.Clear();
// get position of vertex where normal is being evaluated
CVector3D basepos;
CalcPosition(i,j,basepos);
CVector3D tmp;
if (i>0) {
CalcPosition(i-1,j,tmp);
left=tmp-basepos;
}
if (i<m_MapSize-1) {
CalcPosition(i+1,j,tmp);
right=tmp-basepos;
}
if (j>0) {
CalcPosition(i,j-1,tmp);
up=tmp-basepos;
}
if (j<m_MapSize-1) {
CalcPosition(i,j+1,tmp);
down=tmp-basepos;
}
CVector3D n0 = up.Cross(left);
CVector3D n1 = left.Cross(down);
CVector3D n2 = down.Cross(right);
CVector3D n3 = right.Cross(up);
normal = n0 + n1 + n2 + n3;
float nlen=normal.GetLength();
if (nlen>0.00001f) normal*=1.0f/nlen;
}
///////////////////////////////////////////////////////////////////////////////
// GetPatch: return the patch at (i,j) in patch space, or null if the patch is
// out of bounds
CPatch* CTerrain::GetPatch(i32 i, i32 j) const
{
if (i<0 || i>=i32(m_MapSizePatches)) return 0;
if (j<0 || j>=i32(m_MapSizePatches)) return 0;
return &m_Patches[(j*m_MapSizePatches)+i];
}
///////////////////////////////////////////////////////////////////////////////
// GetPatch: return the tile at (i,j) in tile space, or null if the tile is out
// of bounds
CMiniPatch* CTerrain::GetTile(i32 i, i32 j) const
{
if (i<0 || i>=i32(m_MapSize)-1) return 0;
if (j<0 || j>=i32(m_MapSize)-1) return 0;
CPatch* patch=GetPatch(i/PATCH_SIZE, j/PATCH_SIZE);
return &patch->m_MiniPatches[j%PATCH_SIZE][i%PATCH_SIZE];
}
float CTerrain::getVertexGroundLevel(int i, int j) const
{
if (i < 0)
i = 0;
else if (i >= (int) m_MapSize)
i = m_MapSize - 1;
if (j < 0)
j = 0;
else if (j >= (int) m_MapSize)
j = m_MapSize - 1;
return HEIGHT_SCALE * m_Heightmap[j*m_MapSize + i];
}
float CTerrain::getSlope(float x, float z) const
{
x /= (float)CELL_SIZE;
z /= (float)CELL_SIZE;
int xi = (int)floor(x);
int zi = (int)floor(z);
if (xi < 0)
{
xi = 0;
}
else if (xi >= (int)m_MapSize-1)
{
xi = m_MapSize - 2;
}
if (zi < 0)
{
zi = 0;
}
else if (zi >= (int)m_MapSize-1)
{
zi = m_MapSize - 2;
}
float h00 = m_Heightmap[zi*m_MapSize + xi];
float h01 = m_Heightmap[zi*m_MapSize + xi + m_MapSize];
float h10 = m_Heightmap[zi*m_MapSize + xi + 1];
float h11 = m_Heightmap[zi*m_MapSize + xi + m_MapSize + 1];
//Difference of highest point from lowest point
return MAX(MAX(h00, h01), MAX(h10, h11)) -
MIN(MIN(h00, h01), MIN(h10, h11));
}
float CTerrain::getSlopeAngle( float x, float y ) const
{
float fCell = (float)CELL_SIZE;
x /= fCell;
y /= fCell;
int xi = (int)floor(x);
int yi = (int)floor(y);
//Keep it in bounds
if (xi < 0)
xi = 0;
else if (xi >= (int)m_MapSize-1)
xi = m_MapSize - 2;
if (yi < 0)
yi = 0;
else if (yi >= (int)m_MapSize-1)
yi = m_MapSize - 2;
float h00 = m_Heightmap[yi*m_MapSize + xi] * HEIGHT_SCALE;
float h01 = m_Heightmap[yi*m_MapSize + xi + m_MapSize] * HEIGHT_SCALE;
float h10 = m_Heightmap[yi*m_MapSize + xi + 1] * HEIGHT_SCALE;
float h11 = m_Heightmap[yi*m_MapSize + xi + m_MapSize + 1] * HEIGHT_SCALE;
CVector3D flat, elevated;
float low, high;
if ( h00 < h01 && h00 < h10 && h00 < h11 )
low = h00;
else if ( h01 < h10 && h01 < h11 )
low = h01;
else if ( h10 < h11 )
low = h10;
else
low = h11;
//Find correct vector representing the flat version of the vector from low to high points
if ( h00 > h01 && h00 > h10 && h00 > h11 )
{
high = h00;
if ( low == h10 )
flat = CVector3D( fCell, 0.0f, 0.0f );
else if ( low == h01 )
flat = CVector3D( 0.0f, 0.0f, fCell );
else if ( low == h11 )
flat = CVector3D( fCell, 0.0f, fCell );
}
else if ( h01 > h10 && h01 > h11 )
{
high = h01;
if ( low == h00 )
flat = CVector3D( 0.0f, 0.0f, fCell );
else if ( low == h01 )
flat = CVector3D( fCell, 0.0f, fCell );
else if ( low == h11 )
flat = CVector3D( fCell, 0.0f, 0.0f );
}
else if ( h10 > h11 )
{
high = h10;
if ( low == h00 )
flat = CVector3D( fCell, 0.0f, 0.0f );
else if ( low == h01 )
flat = CVector3D( fCell, 0.0f, fCell );
else if ( low == h11 )
flat = CVector3D( 0.0f, 0.0f, fCell );
}
else
{
high = h11;
if ( low == h10 )
flat = CVector3D( 0.0f, 0.0f, fCell );
else if ( low == h01 )
flat = CVector3D( fCell, 0.0f, 0.0f );
else if ( low == h00 )
flat = CVector3D( fCell, 0.0f, fCell );
}
elevated = flat;
elevated.Y = high - low;
elevated.Normalize();
flat.Normalize();
return acosf( flat.Dot( elevated ) );
}
float CTerrain::getSlopeAngleFace(float x, float y, float orientation) const
{
bool right; //true means use 0,0 and 1,1; false means use 1,0 and 0,1
bool invert;
float top, bottom;
x /= (float)CELL_SIZE;
y /= (float)CELL_SIZE;
int xi = (int)floor(x);
int yi = (int)floor(y);
CVector3D flat( (float)CELL_SIZE, 0.0f, (float)CELL_SIZE );
CVector3D elevated=flat;
float a0 = DEGTORAD(0.0f);
float a90 = DEGTORAD(90.0f);
float a180 = DEGTORAD(180.0f);
float neg = DEGTORAD(-90.0f);
float a45 = DEGTORAD(45.0f);
float a135 = DEGTORAD(135.0f);
//Find which side it's facing; use that and the opposite
if ( orientation > 0.0f && orientation < DEGTORAD(90.0f) )
right = true;
else if ( orientation > DEGTORAD(90.0f) && orientation < DEGTORAD(180.0f) )
right = false;
else if ( orientation < DEGTORAD(-180.0f) && orientation > DEGTORAD(-90.0f) )
right = true;
else
right = false;
//Keep it in bounds
if (xi < 0)
xi = 0;
else if (xi >= (int)m_MapSize-1)
xi = m_MapSize - 2;
if (yi < 0)
yi = 0;
else if (yi >= (int)m_MapSize-1)
yi = m_MapSize - 2;
if ( right )
{
bottom = m_Heightmap[yi*m_MapSize + xi]*HEIGHT_SCALE;
top = m_Heightmap[yi*m_MapSize+m_MapSize + xi + 1]*HEIGHT_SCALE;
if ( (orientation > DEGTORAD(-45.0f) && orientation < 0.0f) ||
(orientation < DEGTORAD(135.0f) && orientation > 0.0f) )
elevated.Y = top-bottom;
else
elevated.Y = bottom-top;
}
else
{
bottom = m_Heightmap[yi*m_MapSize + xi + 1]*HEIGHT_SCALE;
top = m_Heightmap[yi*m_MapSize+m_MapSize + xi]*HEIGHT_SCALE;
if ( (orientation > DEGTORAD(-135.0f) && orientation < 0.0f) ||
(orientation < DEGTORAD(45.0f) && orientation > 0.0f) )
elevated.Y = top-bottom;
else
elevated.Y = bottom-top;
}
if ( elevated.Y > 0.0f )
invert=false;
else
invert=true;
elevated.Y = fabs(elevated.Y);
elevated.Normalize();
flat.Normalize();
float ret = elevated.Dot(flat);
if (invert)
return -acosf(ret);
return acosf(ret);
}
float CTerrain::getExactGroundLevel(float x, float z) const
{
x /= (float)CELL_SIZE;
z /= (float)CELL_SIZE;
int xi = (int)floor(x);
int zi = (int)floor(z);
float xf = x - (float)xi;
float zf = z - (float)zi;
if (xi < 0)
{
xi = 0; xf = 0.0f;
}
else if (xi >= (int)m_MapSize-1)
{
xi = m_MapSize - 2; xf = 1.0f;
}
if (zi < 0)
{
zi = 0; zf = 0.0f;
}
else if (zi >= (int)m_MapSize-1)
{
zi = m_MapSize - 2; zf = 1.0f;
}
/*
debug_assert( isOnMap( x, y ) );
if( !isOnMap( x, y ) )
return 0.0f;
*/
float h00 = m_Heightmap[zi*m_MapSize + xi];
float h01 = m_Heightmap[zi*m_MapSize + xi + m_MapSize];
float h10 = m_Heightmap[zi*m_MapSize + xi + 1];
float h11 = m_Heightmap[zi*m_MapSize + xi + m_MapSize + 1];
return (HEIGHT_SCALE * (
(1 - zf) * ((1 - xf) * h00 + xf * h10)
+ zf * ((1 - xf) * h01 + xf * h11)));
}
///////////////////////////////////////////////////////////////////////////////
// Resize: resize this terrain to the given size (in patches per side)
void CTerrain::Resize(u32 size)
{
if (size==m_MapSizePatches) {
// inexplicable request to resize terrain to the same size .. ignore it
return;
}
if (!m_Heightmap) {
// not yet created a terrain; build a default terrain of the given size now
Initialize(size,0);
return;
}
// allocate data for new terrain
u32 newMapSize=(size*PATCH_SIZE)+1;
u16* newHeightmap=new u16[newMapSize*newMapSize];
CPatch* newPatches=new CPatch[size*size];
if (size>m_MapSizePatches) {
// new map is bigger than old one - zero the heightmap so we don't get uninitialised
// height data along the expanded edges
memset(newHeightmap,0,newMapSize*newMapSize);
}
// now copy over rows of data
u32 j;
u16* src=m_Heightmap;
u16* dst=newHeightmap;
u32 copysize=newMapSize>m_MapSize ? m_MapSize : newMapSize;
for (j=0;j<copysize;j++) {
memcpy2(dst,src,copysize*sizeof(u16));
dst+=copysize;
src+=m_MapSize;
if (newMapSize>m_MapSize) {
// entend the last height to the end of the row
for (u32 i=0;i<newMapSize-m_MapSize;i++) {
*dst++=*(src-1);
}
}
}
if (newMapSize>m_MapSize) {
// copy over heights of the last row to any remaining rows
src=newHeightmap+((m_MapSize-1)*newMapSize);
dst=src+newMapSize;
for (u32 i=0;i<newMapSize-m_MapSize;i++) {
memcpy2(dst,src,newMapSize*sizeof(u16));
dst+=newMapSize;
}
}
// now build new patches
for (j=0;j<size;j++) {
for (u32 i=0;i<size;i++) {
// copy over texture data from existing tiles, if possible
if (i<m_MapSizePatches && j<m_MapSizePatches) {
memcpy2(newPatches[j*size+i].m_MiniPatches,m_Patches[j*m_MapSizePatches+i].m_MiniPatches,sizeof(CMiniPatch)*PATCH_SIZE*PATCH_SIZE);
}
}
if (j<m_MapSizePatches && size>m_MapSizePatches) {
// copy over the last tile from each column
for (u32 n=0;n<size-m_MapSizePatches;n++) {
for (int m=0;m<PATCH_SIZE;m++) {
CMiniPatch& src=m_Patches[j*m_MapSizePatches+m_MapSizePatches-1].m_MiniPatches[m][15];
for (int k=0;k<PATCH_SIZE;k++) {
CMiniPatch& dst=newPatches[j*size+m_MapSizePatches+n].m_MiniPatches[m][k];
dst.Tex1=src.Tex1;
dst.Tex1Priority=src.Tex1Priority;
}
}
}
}
}
if (size>m_MapSizePatches) {
// copy over the last tile from each column
CPatch* srcpatch=&newPatches[(m_MapSizePatches-1)*size];
CPatch* dstpatch=srcpatch+size;
for (u32 p=0;p<size-m_MapSizePatches;p++) {
for (u32 n=0;n<size;n++) {
for (int m=0;m<PATCH_SIZE;m++) {
for (int k=0;k<PATCH_SIZE;k++) {
CMiniPatch& src=srcpatch->m_MiniPatches[15][k];
CMiniPatch& dst=dstpatch->m_MiniPatches[m][k];
dst.Tex1=src.Tex1;
dst.Tex1Priority=src.Tex1Priority;
}
}
srcpatch++;
dstpatch++;
}
}
}
// 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();
}
///////////////////////////////////////////////////////////////////////////////
// InitialisePatches: initialise patch data
void CTerrain::InitialisePatches()
{
for (u32 j=0;j<m_MapSizePatches;j++) {
for (u32 i=0;i<m_MapSizePatches;i++) {
CPatch* patch=GetPatch(i,j);
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
memcpy2(m_Heightmap,heightmap,m_MapSize*m_MapSize*sizeof(u16));
// recalculate patch bounds, invalidate vertices
for (u32 j=0;j<m_MapSizePatches;j++) {
for (u32 i=0;i<m_MapSizePatches;i++) {
CPatch* patch=GetPatch(i,j);
patch->InvalidateBounds();
patch->SetDirty(RENDERDATA_UPDATE_VERTICES);
}
}
}
///////////////////////////////////////////////////////////////////////////////
// FlattenArea: flatten out an area of terrain (specified in world space
// coords); return the average height of the flattened area
float CTerrain::FlattenArea(float x0, float x1, float z0, float z1)
{
u32 tx0=u32(clamp(int(float(x0/CELL_SIZE)), 0, int(m_MapSize)));
u32 tx1=u32(clamp(int(float(x1/CELL_SIZE)+1.0f), 0, int(m_MapSize)));
u32 tz0=u32(clamp(int(float(z0/CELL_SIZE)), 0, int(m_MapSize)));
u32 tz1=u32(clamp(int(float(z1/CELL_SIZE)+1.0f), 0, int(m_MapSize)));
u32 count=0;
u32 y=0;
for (u32 x=tx0;x<=tx1;x++) {
for (u32 z=tz0;z<=tz1;z++) {
y+=m_Heightmap[z*m_MapSize + x];
count++;
}
}
y/=count;
for (u32 x=tx0;x<=tx1;x++) {
for (u32 z=tz0;z<=tz1;z++) {
m_Heightmap[z*m_MapSize + x]=(u16)y;
CPatch* patch=GetPatch(x/PATCH_SIZE,z/PATCH_SIZE);
patch->SetDirty(RENDERDATA_UPDATE_VERTICES);
}
}
return y*HEIGHT_SCALE;
}
///////////////////////////////////////////////////////////////////////////////
void CTerrain::MakeDirty(int i0, int j0, int i1, int j1, int dirtyFlags)
{
// flag vertex data as dirty for affected patches, and rebuild bounds of these patches
int pi0 = clamp((i0/PATCH_SIZE)-1, 0, (int)m_MapSizePatches);
int pi1 = clamp((i1/PATCH_SIZE)+1, 0, (int)m_MapSizePatches);
int pj0 = clamp((j0/PATCH_SIZE)-1, 0, (int)m_MapSizePatches);
int pj1 = clamp((j1/PATCH_SIZE)+1, 0, (int)m_MapSizePatches);
for (int j = pj0; j < pj1; j++) {
for (int i = pi0; i < pi1; i++) {
CPatch* patch = GetPatch(i,j);
if (dirtyFlags & RENDERDATA_UPDATE_VERTICES)
patch->CalcBounds();
patch->SetDirty(dirtyFlags);
}
}
}
void CTerrain::MakeDirty(int dirtyFlags)
{
for (u32 j = 0; j < m_MapSizePatches; j++) {
for (u32 i = 0; i < m_MapSizePatches; i++) {
CPatch* patch = GetPatch(i,j);
if (dirtyFlags & RENDERDATA_UPDATE_VERTICES)
patch->CalcBounds();
patch->SetDirty(dirtyFlags);
}
}
}