/* 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 . */ #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 /////////////////////////////////////////////////////////////////////////////// // 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(i, 0, m_MapSize - 1); ssize_t hj = Clamp(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(i, 0, m_MapSize - 1); ssize_t hj = Clamp(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(floor(x / TERRAIN_TILE_SIZE), 0, m_MapSize - 2); const ssize_t zi = Clamp(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(i, 0, m_MapSize - 1); j = Clamp(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(i, 0, m_MapSize - 1); j = Clamp(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(i, 0, m_MapSize - 2); j = Clamp(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((x / static_cast(TERRAIN_TILE_SIZE)).ToInt_RoundToZero(), 0, m_MapSize - 2); const ssize_t zi = Clamp((z / static_cast(TERRAIN_TILE_SIZE)).ToInt_RoundToZero(), 0, m_MapSize - 2); const fixed one = fixed::FromInt(1); const fixed xf = Clamp((x / static_cast(TERRAIN_TILE_SIZE)) - fixed::FromInt(xi), fixed::Zero(), one); const fixed zf = Clamp((z / static_cast(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(floor(x / TERRAIN_TILE_SIZE), 0, m_MapSize - 2); const ssize_t zi = Clamp(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((x / static_cast(TERRAIN_TILE_SIZE)).ToInt_RoundToZero(), 0, m_MapSize - 2); const ssize_t zi = Clamp((z / static_cast(TERRAIN_TILE_SIZE)).ToInt_RoundToZero(), 0, m_MapSize - 2); const fixed one = fixed::FromInt(1); const fixed xf = Clamp((x / static_cast(TERRAIN_TILE_SIZE)) - fixed::FromInt(xi), fixed::Zero(), one); const fixed zf = Clamp((z / static_cast(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, 0, m_MapSize - 2); j = Clamp(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(0), m_MapSizePatches / 2 - size / 2 + horizontalOffset); const ssize_t sourceUpperLeftZ = std::max( static_cast(0), m_MapSizePatches / 2 - size / 2 + verticalOffset); const ssize_t destUpperLeftX = std::max( static_cast(0), (size / 2 - m_MapSizePatches / 2 - horizontalOffset)); const ssize_t destUpperLeftZ = std::max( static_cast(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, 0, m_MapSizePatches-1); ssize_t pi1 = Clamp((i1-1)/PATCH_SIZE, 0, m_MapSizePatches-1); ssize_t pj0 = Clamp( j0 /PATCH_SIZE, 0, m_MapSizePatches-1); ssize_t pj1 = Clamp((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(i0, 0, m_MapSize - 1), Clamp(j0, 0, m_MapSize - 1), Clamp(i1, 1, m_MapSize), Clamp(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(i0, 0, m_MapSize - 1); j0 = Clamp(j0, 0, m_MapSize - 1); i1 = Clamp(i1, 0, m_MapSize - 1); j1 = Clamp(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; }