/////////////////////////////////////////////////////////////////////////////// // // 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 #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; } /////////////////////////////////////////////////////////////////////////////// // CalcFromPosition: calculate the vertex underneath the world space position void CTerrain::CalcFromPosition(const CVector3D& pos, i32& i, i32& j) const { i = pos.X / CELL_SIZE; j = pos.Z / 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 (i0) { CalcPosition(i,j-1,tmp); up=tmp-basepos; } if (j0.00001f) normal*=1.0f/nlen; } /////////////////////////////////////////////////////////////////////////////// // GetPatch: return the patch at (x,z) in patch space, or null if the patch is // out of bounds CPatch* CTerrain::GetPatch(i32 x, i32 z) const { if (x<0 || x>=i32(m_MapSizePatches)) return 0; if (z<0 || z>=i32(m_MapSizePatches)) return 0; return &m_Patches[(z*m_MapSizePatches)+x]; } /////////////////////////////////////////////////////////////////////////////// // GetPatch: return the tile at (x,z) in tile space, or null if the tile is out // of bounds CMiniPatch* CTerrain::GetTile(i32 x, i32 z) const { if (x<0 || x>=i32(m_MapSize)-1) return 0; if (z<0 || z>=i32(m_MapSize)-1) return 0; CPatch* patch=GetPatch(x/PATCH_SIZE,z/PATCH_SIZE); return &patch->m_MiniPatches[z%PATCH_SIZE][x%PATCH_SIZE]; } float CTerrain::getVertexGroundLevel(int x, int z) const { if (x < 0) { x = 0; } else if (x >= (int) m_MapSize) { x = m_MapSize - 1; } if (z < 0) { z = 0; } else if (z >= (int) m_MapSize) { z = m_MapSize - 1; } return HEIGHT_SCALE * m_Heightmap[z*m_MapSize + x]; } float CTerrain::getExactGroundLevel(float x, float y) const { x /= (float)CELL_SIZE; y /= (float)CELL_SIZE; int xi = (int)floor(x); int yi = (int)floor(y); float xf = x - (float)xi; float yf = y - (float)yi; if (xi < 0) { xi = 0; xf = 0.0f; } else if (xi >= (int)m_MapSize) { xi = m_MapSize - 1; xf = 1.0f; } if (yi < 0) { yi = 0; yf = 0.0f; } else if (yi >= (int)m_MapSize) { yi = m_MapSize - 1; yf = 1.0f; } /* debug_assert( isOnMap( x, y ) ); if( !isOnMap( x, y ) ) return 0.0f; */ float h00 = m_Heightmap[yi*m_MapSize + xi]; float h01 = m_Heightmap[yi*m_MapSize + xi + m_MapSize]; float h10 = m_Heightmap[yi*m_MapSize + xi + 1]; float h11 = m_Heightmap[yi*m_MapSize + xi + m_MapSize + 1]; return (HEIGHT_SCALE * ( (1 - yf) * ((1 - xf) * h00 + xf * h10) + yf * ((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;jm_MapSize) { // entend the last height to the end of the row for (u32 i=0;im_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;im_MapSizePatches) { // copy over the last tile from each column for (u32 n=0;nm_MapSizePatches) { // copy over the last tile from each column CPatch* srcpatch=&newPatches[(m_MapSizePatches-1)*size]; CPatch* dstpatch=srcpatch+size; for (u32 p=0;pm_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;jInitialize(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;jInvalidateBounds(); 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::RaiseVertex(int x, int z, int amount) { // Ignore out-of-bounds vertices if ((unsigned)x >= m_MapSize || (unsigned)z >= m_MapSize) return; m_Heightmap[x + z*m_MapSize] = (u16)clamp(m_Heightmap[x + z*m_MapSize] + amount, 0, 65535); } void CTerrain::MakeDirty(int x0, int z0, int x1, int z1) { // flag vertex data as dirty for affected patches, and rebuild bounds of these patches int px0 = clamp((x0/PATCH_SIZE)-1, 0, (int)m_MapSizePatches); int px1 = clamp((x1/PATCH_SIZE)+1, 0, (int)m_MapSizePatches); int pz0 = clamp((z0/PATCH_SIZE)-1, 0, (int)m_MapSizePatches); int pz1 = clamp((z1/PATCH_SIZE)+1, 0, (int)m_MapSizePatches); for (int j = pz0; j < pz1; j++) { for (int i = px0; i < px1; i++) { CPatch* patch = GetPatch(i,j); patch->CalcBounds(); patch->SetDirty(RENDERDATA_UPDATE_VERTICES); } } }