/*
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
*
* This program 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.
*
* This program 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 this program. If not, see .
*/
#include "TerrainBuilder.h"
#include "Log.h"
#include "MapDefines.h"
#include "MapTree.h"
#include "MMapDefines.h"
#include "Memory.h"
#include "ModelInstance.h"
#include "StringFormat.h"
#include "Util.h"
#include "VMapManager.h"
#include
namespace MMAP
{
std::unique_ptr (*CreateVMapManager)(uint32 mapId);
TerrainBuilder::TerrainBuilder(boost::filesystem::path const& inputDirectory, bool skipLiquid) :
m_inputDirectory(inputDirectory),
m_skipLiquid (skipLiquid)
{
}
/**************************************************************************/
void TerrainBuilder::getLoopVars(Spot portion, int& loopStart, int& loopEnd, int& loopInc)
{
switch (portion)
{
case ENTIRE:
loopStart = 0;
loopEnd = V8_SIZE_SQ;
loopInc = 1;
break;
case TOP:
loopStart = 0;
loopEnd = V8_SIZE;
loopInc = 1;
break;
case LEFT:
loopStart = 0;
loopEnd = V8_SIZE_SQ - V8_SIZE + 1;
loopInc = V8_SIZE;
break;
case RIGHT:
loopStart = V8_SIZE - 1;
loopEnd = V8_SIZE_SQ;
loopInc = V8_SIZE;
break;
case BOTTOM:
loopStart = V8_SIZE_SQ - V8_SIZE;
loopEnd = V8_SIZE_SQ;
loopInc = 1;
break;
}
}
/**************************************************************************/
void TerrainBuilder::loadMap(uint32 mapID, uint32 tileX, uint32 tileY, MeshData& meshData, VMAP::VMapManager* vmapManager)
{
if (loadMap(mapID, tileX, tileY, meshData, vmapManager, ENTIRE))
{
loadMap(mapID, tileX, tileY+1, meshData, vmapManager, LEFT);
loadMap(mapID, tileX, tileY-1, meshData, vmapManager, RIGHT);
loadMap(mapID, tileX+1, tileY, meshData, vmapManager, TOP);
loadMap(mapID, tileX-1, tileY, meshData, vmapManager, BOTTOM);
}
}
/**************************************************************************/
bool TerrainBuilder::loadMap(uint32 mapID, uint32 tileX, uint32 tileY, MeshData& meshData, VMAP::VMapManager* vmapManager, Spot portion)
{
std::string mapFileName = Trinity::StringFormat("{}/maps/{:04}_{:02}_{:02}.map", m_inputDirectory.generic_string(), mapID, tileX, tileY);
auto mapFile = Trinity::make_unique_ptr_with_deleter<&::fclose>(fopen(mapFileName.c_str(), "rb"));
if (!mapFile)
{
int32 parentMapId = vmapManager->getParentMapId(mapID);
while (!mapFile && parentMapId != -1)
{
mapFileName = Trinity::StringFormat("{}/maps/{:04}_{:02}_{:02}.map", m_inputDirectory.generic_string(), parentMapId, tileX, tileY);
mapFile.reset(fopen(mapFileName.c_str(), "rb"));
parentMapId = vmapManager->getParentMapId(parentMapId);
}
}
if (!mapFile)
return false;
map_fileheader fheader;
if (fread(&fheader, sizeof(map_fileheader), 1, mapFile.get()) != 1 ||
fheader.versionMagic != MapVersionMagic)
{
TC_LOG_ERROR("maps.mmapgen", "{} is the wrong version, please extract new .map files", mapFileName);
return false;
}
map_heightHeader hheader;
fseek(mapFile.get(), fheader.heightMapOffset, SEEK_SET);
bool haveTerrain = false;
bool haveLiquid = false;
if (fread(&hheader, sizeof(map_heightHeader), 1, mapFile.get()) == 1)
{
haveTerrain = !hheader.flags.HasFlag(map_heightHeaderFlags::NoHeight);
haveLiquid = fheader.liquidMapOffset && !m_skipLiquid;
}
// no data in this map file
if (!haveTerrain && !haveLiquid)
return false;
// data used later
uint8 holes[16][16][8] = { };
uint16 liquid_entry[16][16] = { };
map_liquidHeaderTypeFlags liquid_flags[16][16] = { };
std::vector ltriangles;
std::vector ttriangles;
// terrain data
if (haveTerrain)
{
float heightMultiplier;
float V9[V9_SIZE_SQ], V8[V8_SIZE_SQ];
size_t expected = V9_SIZE_SQ + V8_SIZE_SQ;
if (hheader.flags.HasFlag(map_heightHeaderFlags::HeightAsInt8))
{
uint8 v9[V9_SIZE_SQ];
uint8 v8[V8_SIZE_SQ];
size_t count = 0;
count += fread(v9, sizeof(uint8), V9_SIZE_SQ, mapFile.get());
count += fread(v8, sizeof(uint8), V8_SIZE_SQ, mapFile.get());
if (count != expected)
TC_LOG_ERROR("maps.mmapgen", "TerrainBuilder::loadMap: Failed to read {} height data expected {}, read {}", mapFileName, expected, count);
heightMultiplier = (hheader.gridMaxHeight - hheader.gridHeight) / 255;
for (int i = 0; i < V9_SIZE_SQ; ++i)
V9[i] = (float)v9[i]*heightMultiplier + hheader.gridHeight;
for (int i = 0; i < V8_SIZE_SQ; ++i)
V8[i] = (float)v8[i]*heightMultiplier + hheader.gridHeight;
}
else if (hheader.flags.HasFlag(map_heightHeaderFlags::HeightAsInt16))
{
uint16 v9[V9_SIZE_SQ];
uint16 v8[V8_SIZE_SQ];
size_t count = 0;
count += fread(v9, sizeof(uint16), V9_SIZE_SQ, mapFile.get());
count += fread(v8, sizeof(uint16), V8_SIZE_SQ, mapFile.get());
if (count != expected)
TC_LOG_ERROR("maps.mmapgen", "TerrainBuilder::loadMap: Failed to read {} height data expected {}, read {}", mapFileName, expected, count);
heightMultiplier = (hheader.gridMaxHeight - hheader.gridHeight) / 65535;
for (int i = 0; i < V9_SIZE_SQ; ++i)
V9[i] = (float)v9[i]*heightMultiplier + hheader.gridHeight;
for (int i = 0; i < V8_SIZE_SQ; ++i)
V8[i] = (float)v8[i]*heightMultiplier + hheader.gridHeight;
}
else
{
size_t count = 0;
count += fread(V9, sizeof(float), V9_SIZE_SQ, mapFile.get());
count += fread(V8, sizeof(float), V8_SIZE_SQ, mapFile.get());
if (count != expected)
TC_LOG_ERROR("maps.mmapgen", "TerrainBuilder::loadMap: Failed to read {} height data expected {}, read {}", mapFileName, expected, count);
}
// hole data
if (fheader.holesSize != 0)
{
memset(holes, 0, fheader.holesSize);
fseek(mapFile.get(), fheader.holesOffset, SEEK_SET);
if (fread(holes, fheader.holesSize, 1, mapFile.get()) != 1)
TC_LOG_ERROR("maps.mmapgen", "TerrainBuilder::loadMap: Failed to read {} holes data expected {}, read {}", mapFileName, 1, 0);
}
int count = meshData.solidVerts.size() / 3;
meshData.solidVerts.resize(meshData.solidVerts.size() + (V9_SIZE_SQ + V8_SIZE_SQ) * 3);
float* solidVerts = meshData.solidVerts.data() + count * 3;
float xoffset = (float(tileX)-32)*GRID_SIZE;
float yoffset = (float(tileY)-32)*GRID_SIZE;
for (int i = 0; i < V9_SIZE_SQ; ++i)
{
getHeightCoord(i, GRID_V9, xoffset, yoffset, solidVerts, V9);
solidVerts += 3;
}
for (int i = 0; i < V8_SIZE_SQ; ++i)
{
getHeightCoord(i, GRID_V8, xoffset, yoffset, solidVerts, V8);
solidVerts += 3;
}
int loopStart = 0, loopEnd = 0, loopInc = 0;
getLoopVars(portion, loopStart, loopEnd, loopInc);
for (int i = loopStart; i < loopEnd; i+=loopInc)
{
std::size_t trianglesOffset = ttriangles.size();
ttriangles.resize(ttriangles.size() + 12);
getHeightTriangle(i, TOP, ttriangles.data() + trianglesOffset + 0, count);
getHeightTriangle(i, RIGHT, ttriangles.data() + trianglesOffset + 3, count);
getHeightTriangle(i, LEFT, ttriangles.data() + trianglesOffset + 6, count);
getHeightTriangle(i, BOTTOM, ttriangles.data() + trianglesOffset + 9, count);
}
}
// liquid data
if (haveLiquid)
{
map_liquidHeader lheader;
fseek(mapFile.get(), fheader.liquidMapOffset, SEEK_SET);
if (fread(&lheader, sizeof(map_liquidHeader), 1, mapFile.get()) != 1)
TC_LOG_ERROR("maps.mmapgen", "TerrainBuilder::loadMap: Failed to read {} liquid header expected {}, read {}", mapFileName, 1, 0);
std::unique_ptr liquid_map;
if (!lheader.flags.HasFlag(map_liquidHeaderFlags::NoType))
{
if (fread(liquid_entry, sizeof(liquid_entry), 1, mapFile.get()) != 1)
TC_LOG_ERROR("maps.mmapgen", "TerrainBuilder::loadMap: Failed to read {} liquid id expected {}, read {}", mapFileName, 1, 0);
if (fread(liquid_flags, sizeof(liquid_flags), 1, mapFile.get()) != 1)
TC_LOG_ERROR("maps.mmapgen", "TerrainBuilder::loadMap: Failed to read {} liquid flags expected {}, read {}", mapFileName, 1, 0);
}
else
{
std::fill_n(&liquid_entry[0][0], 16 * 16, lheader.liquidType);
std::fill_n(&liquid_flags[0][0], 16 * 16, lheader.liquidFlags);
}
if (!lheader.flags.HasFlag(map_liquidHeaderFlags::NoHeight))
{
uint32 toRead = lheader.width * lheader.height;
liquid_map = std::make_unique(toRead);
if (fread(liquid_map.get(), sizeof(float), toRead, mapFile.get()) != toRead)
{
TC_LOG_ERROR("maps.mmapgen", "TerrainBuilder::loadMap: Failed to read {} liquid header expected {}, read {}", mapFileName, toRead, 0);
liquid_map = nullptr;
}
}
int count = meshData.liquidVerts.size() / 3;
meshData.liquidVerts.resize(meshData.liquidVerts.size() + V9_SIZE_SQ * 3);
float* liquidVerts = meshData.liquidVerts.data();
float xoffset = (float(tileX)-32)*GRID_SIZE;
float yoffset = (float(tileY)-32)*GRID_SIZE;
int row, col;
// generate coordinates
if (!lheader.flags.HasFlag(map_liquidHeaderFlags::NoHeight))
{
int j = 0;
for (int i = 0; i < V9_SIZE_SQ; ++i)
{
row = i / V9_SIZE;
col = i % V9_SIZE;
if (row < lheader.offsetY || row >= lheader.offsetY + lheader.height ||
col < lheader.offsetX || col >= lheader.offsetX + lheader.width)
{
// dummy vert using invalid height
liquidVerts[(count + i) * 3 + 0] = (yoffset + col * GRID_PART_SIZE) * -1;
liquidVerts[(count + i) * 3 + 1] = INVALID_MAP_LIQ_HEIGHT;
liquidVerts[(count + i) * 3 + 2] = (xoffset + row * GRID_PART_SIZE) * -1;
continue;
}
getLiquidCoord(i, j, xoffset, yoffset, &liquidVerts[(count + i) * 3], liquid_map.get());
j++;
}
}
else
{
for (int i = 0; i < V9_SIZE_SQ; ++i)
{
row = i / V9_SIZE;
col = i % V9_SIZE;
liquidVerts[(count + i) * 3 + 0] = (yoffset + col * GRID_PART_SIZE) * -1;
liquidVerts[(count + i) * 3 + 1] = lheader.liquidLevel;
liquidVerts[(count + i) * 3 + 2] = (xoffset + row * GRID_PART_SIZE) * -1;
}
}
int loopStart = 0, loopEnd = 0, loopInc = 0;
getLoopVars(portion, loopStart, loopEnd, loopInc);
// generate triangles
for (int i = loopStart; i < loopEnd; i += loopInc)
{
std::size_t trianglesOffset = ltriangles.size();
ltriangles.resize(ltriangles.size() + 6);
getHeightTriangle(i, TOP, ltriangles.data() + trianglesOffset + 0, count, true);
getHeightTriangle(i, BOTTOM, ltriangles.data() + trianglesOffset + 3, count, true);
}
}
// now that we have gathered the data, we can figure out which parts to keep:
// liquid above ground, ground above liquid
int loopStart = 0, loopEnd = 0, loopInc = 0, tTriCount = 4;
bool useTerrain, useLiquid;
float* lverts = meshData.liquidVerts.data();
int* ltris = ltriangles.data();
float* tverts = meshData.solidVerts.data();
int* ttris = ttriangles.data();
if ((ltriangles.size() + ttriangles.size()) == 0)
return false;
// make a copy of liquid vertices
// used to pad right-bottom frame due to lost vertex data at extraction
std::unique_ptr lverts_copy;
if (!meshData.liquidVerts.empty())
{
lverts_copy = std::make_unique(meshData.liquidVerts.size());
memcpy(lverts_copy.get(), lverts, sizeof(float)* meshData.liquidVerts.size());
}
getLoopVars(portion, loopStart, loopEnd, loopInc);
for (int i = loopStart; i < loopEnd; i+=loopInc)
{
for (int j = 0; j < 2; ++j)
{
// default is true, will change to false if needed
useTerrain = true;
useLiquid = true;
EnumFlag liquidType = map_liquidHeaderTypeFlags::NoWater;
uint8 navLiquidType = NAV_AREA_EMPTY;
// if there is no liquid, don't use liquid
if (meshData.liquidVerts.empty() || ltriangles.empty())
useLiquid = false;
else
{
liquidType = getLiquidType(i, liquid_flags);
if (liquidType.HasFlag(map_liquidHeaderTypeFlags::DarkWater))
{
// players should not be here, so logically neither should creatures
useTerrain = false;
useLiquid = false;
}
else if (liquidType.HasFlag(map_liquidHeaderTypeFlags::Water | map_liquidHeaderTypeFlags::Ocean))
navLiquidType = NAV_AREA_WATER;
else if (liquidType.HasFlag(map_liquidHeaderTypeFlags::Magma | map_liquidHeaderTypeFlags::Slime))
navLiquidType = NAV_AREA_MAGMA_SLIME;
else
useLiquid = false;
}
// if there is no terrain, don't use terrain
if (ttriangles.empty())
useTerrain = false;
// while extracting ADT data we are losing right-bottom vertices
// this code adds fair approximation of lost data
if (useLiquid)
{
float quadHeight = 0;
uint32 validCount = 0;
for(uint32 idx = 0; idx < 3; idx++)
{
float h = lverts_copy[ltris[idx]*3 + 1];
if (h != INVALID_MAP_LIQ_HEIGHT && h < INVALID_MAP_LIQ_HEIGHT_MAX)
{
quadHeight += h;
validCount++;
}
}
// update vertex height data
if (validCount > 0 && validCount < 3)
{
quadHeight /= validCount;
for(uint32 idx = 0; idx < 3; idx++)
{
float h = lverts[ltris[idx]*3 + 1];
if (h == INVALID_MAP_LIQ_HEIGHT || h > INVALID_MAP_LIQ_HEIGHT_MAX)
lverts[ltris[idx]*3 + 1] = quadHeight;
}
}
// no valid vertexes - don't use this poly at all
if (validCount == 0)
useLiquid = false;
}
// if there is a hole here, don't use the terrain
if (useTerrain && fheader.holesSize != 0)
useTerrain = !isHole(i, holes);
// we use only one terrain kind per quad - pick higher one
if (useTerrain && useLiquid)
{
float minLLevel = INVALID_MAP_LIQ_HEIGHT_MAX;
float maxLLevel = INVALID_MAP_LIQ_HEIGHT;
for(uint32 x = 0; x < 3; x++)
{
float h = lverts[ltris[x]*3 + 1];
if (minLLevel > h)
minLLevel = h;
if (maxLLevel < h)
maxLLevel = h;
}
float maxTLevel = INVALID_MAP_LIQ_HEIGHT;
float minTLevel = INVALID_MAP_LIQ_HEIGHT_MAX;
for(uint32 x = 0; x < 6; x++)
{
float h = tverts[ttris[x]*3 + 1];
if (maxTLevel < h)
maxTLevel = h;
if (minTLevel > h)
minTLevel = h;
}
// terrain under the liquid?
if (minLLevel > maxTLevel)
useTerrain = false;
//liquid under the terrain?
if (minTLevel > maxLLevel)
useLiquid = false;
}
// store the result
if (useLiquid)
{
meshData.liquidTris.insert(meshData.liquidTris.end(), <ris[0], <ris[3]);
meshData.liquidType.push_back(navLiquidType);
}
if (useTerrain)
meshData.solidTris.insert(meshData.solidTris.end(), &ttris[0], &ttris[3 * tTriCount / 2]);
// advance to next set of triangles
ltris += 3;
ttris += 3*tTriCount/2;
}
}
return !meshData.solidTris.empty() || !meshData.liquidTris.empty();
}
/**************************************************************************/
inline void TerrainBuilder::getHeightCoord(int index, Grid grid, float xOffset, float yOffset, float* coord, float* v)
{
// wow coords: x, y, height
// coord is mirroed about the horizontal axes
switch (grid)
{
case GRID_V9:
coord[0] = (yOffset + (int)(index % V9_SIZE) * GRID_PART_SIZE) * -1.f;
coord[1] = v[index];
coord[2] = (xOffset + (int)(index / (V9_SIZE)) * GRID_PART_SIZE) * -1.f;
break;
case GRID_V8:
coord[0] = (yOffset + (int)(index % V8_SIZE) * GRID_PART_SIZE + GRID_PART_SIZE / 2.f) * -1.f;
coord[1] = v[index];
coord[2] = (xOffset + (int)(index / (V8_SIZE)) * GRID_PART_SIZE + GRID_PART_SIZE / 2.f) * -1.f;
break;
}
}
/**************************************************************************/
inline void TerrainBuilder::getHeightTriangle(int square, Spot triangle, int* indices, int offset, bool liquid/* = false*/)
{
int rowOffset = square/V8_SIZE;
if (!liquid)
switch (triangle)
{
case TOP:
indices[0] = V9_SIZE_SQ + square + offset; // 0-----1 .... 128
indices[1] = square + 1 + rowOffset + offset; // |\ T /|
indices[2] = square + rowOffset + offset; // | \ / |
break; // |L 0 R| .. 127
case LEFT: // | / \ |
indices[0] = square + V9_SIZE + rowOffset + offset; // |/ B \|
indices[1] = V9_SIZE_SQ + square + offset; // 129---130 ... 386
indices[2] = square + rowOffset + offset; // |\ /|
break; // | \ / |
case RIGHT: // | 128 | .. 255
indices[0] = V9_SIZE_SQ + square + offset; // | / \ |
indices[1] = square + V9_SIZE + 1 + rowOffset + offset; // |/ \|
indices[2] = square + 1 + rowOffset + offset; // 258---259 ... 515
break;
case BOTTOM:
indices[0] = square + V9_SIZE + rowOffset + offset;
indices[1] = square + V9_SIZE + 1 + rowOffset + offset;
indices[2] = V9_SIZE_SQ + square + offset;
break;
default: break;
}
else
switch (triangle)
{ // 0-----1 .... 128
case TOP: // |\ |
indices[0] = square + V9_SIZE + 1 + rowOffset + offset; // | \ T |
indices[1] = square + 1 + rowOffset + offset; // | \ |
indices[2] = square + rowOffset + offset; // | B \ |
break; // | \|
case BOTTOM: // 129---130 ... 386
indices[0] = square + V9_SIZE + rowOffset + offset; // |\ |
indices[1] = square + V9_SIZE + 1 + rowOffset + offset; // | \ |
indices[2] = square + rowOffset + offset; // | \ |
break; // | \ |
default: break; // | \|
} // 258---259 ... 515
}
/**************************************************************************/
inline void TerrainBuilder::getLiquidCoord(int index, int index2, float xOffset, float yOffset, float* coord, float* v)
{
// wow coords: x, y, height
// coord is mirroed about the horizontal axes
coord[0] = (yOffset + (int)(index % V9_SIZE) * GRID_PART_SIZE) * -1.f;
coord[1] = v[index2];
coord[2] = (xOffset + (int)(index / (V9_SIZE)) * GRID_PART_SIZE) * -1.f;
}
/**************************************************************************/
inline bool TerrainBuilder::isHole(int square, uint8 const (&holes)[16][16][8])
{
int row = square / 128;
int col = square % 128;
int cellRow = row / 8; // 8 squares per cell
int cellCol = col / 8;
int holeRow = row % 8;
int holeCol = col % 8;
return (holes[cellRow][cellCol][holeRow] & (1 << holeCol)) != 0;
}
/**************************************************************************/
inline map_liquidHeaderTypeFlags TerrainBuilder::getLiquidType(int square, map_liquidHeaderTypeFlags const (&liquid_type)[16][16])
{
int row = square / 128;
int col = square % 128;
int cellRow = row / 8; // 8 squares per cell
int cellCol = col / 8;
return liquid_type[cellRow][cellCol];
}
/**************************************************************************/
bool TerrainBuilder::loadVMap(uint32 mapID, uint32 tileX, uint32 tileY, MeshData& meshData, VMAP::VMapManager* vmapManager)
{
VMAP::LoadResult result = vmapManager->loadMap((m_inputDirectory / "vmaps").string(), mapID, tileX, tileY);
if (result != VMAP::LoadResult::Success)
return false;
auto vmapTile = Trinity::make_unique_ptr_with_deleter(vmapManager, [=](VMAP::VMapManager* mgr)
{
mgr->unloadMap(mapID, tileX, tileY);
});
std::span models = vmapManager->getModelsOnMap(mapID);
if (models.empty())
return false;
bool retval = false;
for (VMAP::ModelInstance const& instance : models)
{
// model instances exist in tree even though there are instances of that model in this tile
VMAP::WorldModel const* worldModel = instance.getWorldModel();
if (!worldModel)
continue;
// now we have a model to add to the meshdata
retval = true;
G3D::Vector3 position = instance.iPos;
position.x -= 32 * GRID_SIZE;
position.y -= 32 * GRID_SIZE;
loadVMapModel(worldModel, position, instance.GetInvRot(), instance.iScale, meshData, vmapManager);
}
return retval;
}
void TerrainBuilder::loadVMapModel(VMAP::WorldModel const* worldModel, G3D::Vector3 const& position, G3D::Matrix3 const& rotation, float scale,
MeshData& meshData, VMAP::VMapManager* vmapManager)
{
std::vector const& groupModels = worldModel->getGroupModels();
// all M2s need to have triangle indices reversed
bool isM2 = worldModel->IsM2();
// transform data
for (std::vector::const_iterator it = groupModels.begin(); it != groupModels.end(); ++it)
{
// first handle collision mesh
int offset = meshData.solidVerts.size() / 3;
transformVertices(it->GetVertices(), meshData.solidVerts, scale, rotation, position);
copyIndices(it->GetTriangles(), meshData.solidTris, offset, isM2);
// now handle liquid data
VMAP::WmoLiquid const* liquid = it->GetLiquid();
if (liquid && liquid->GetFlagsStorage())
{
uint32 tilesX, tilesY;
G3D::Vector3 corner;
liquid->getPosInfo(tilesX, tilesY, corner);
uint32 vertsX = tilesX + 1;
uint32 vertsY = tilesY + 1;
uint8 const* flags = liquid->GetFlagsStorage();
float const* data = liquid->GetHeightStorage();
uint8 type = NAV_AREA_EMPTY;
// convert liquid type to NavTerrain
EnumFlag liquidFlags = map_liquidHeaderTypeFlags(vmapManager->GetLiquidFlagsPtr(liquid->GetType()));
if (liquidFlags.HasFlag(map_liquidHeaderTypeFlags::Water | map_liquidHeaderTypeFlags::Ocean))
type = NAV_AREA_WATER;
else if (liquidFlags.HasFlag(map_liquidHeaderTypeFlags::Magma | map_liquidHeaderTypeFlags::Slime))
type = NAV_AREA_MAGMA_SLIME;
// indexing is weird...
// after a lot of trial and error, this is what works:
// vertex = y*vertsX+x
// tile = x*tilesY+y
// flag = y*tilesY+x
uint32 liqOffset = meshData.liquidVerts.size() / 3;
meshData.liquidVerts.resize(meshData.liquidVerts.size() + vertsX * vertsY * 3);
float* liquidVerts = meshData.liquidVerts.data();
for (uint32 x = 0; x < vertsX; ++x)
{
for (uint32 y = 0; y < vertsY; ++y)
{
G3D::Vector3 vert = G3D::Vector3(corner.x + x * GRID_PART_SIZE, corner.y + y * GRID_PART_SIZE, data[y * vertsX + x]);
vert = vert * rotation * scale + position;
vert.x *= -1.f;
vert.y *= -1.f;
liquidVerts[(liqOffset + x * vertsY + y) * 3 + 0] = vert.y;
liquidVerts[(liqOffset + x * vertsY + y) * 3 + 1] = vert.z;
liquidVerts[(liqOffset + x * vertsY + y) * 3 + 2] = vert.x;
}
}
std::size_t liquidSquares = 0;
for (uint32 x = 0; x < tilesX; ++x)
{
for (uint32 y = 0; y < tilesY; ++y)
{
if ((flags[x + y * tilesX] & 0x0f) != 0x0f)
{
uint32 square = x * tilesY + y;
int idx1 = square + x;
int idx2 = square + 1 + x;
int idx3 = square + tilesY + 1 + 1 + x;
int idx4 = square + tilesY + 1 + x;
std::size_t liquidTriOffset = meshData.liquidTris.size();
meshData.liquidTris.resize(liquidTriOffset + 6);
int* liquidTris = meshData.liquidTris.data() + liquidTriOffset;
// top triangle
liquidTris[0] = idx2 + liqOffset;
liquidTris[1] = idx1 + liqOffset;
liquidTris[2] = idx3 + liqOffset;
// bottom triangle
liquidTris[3] = idx3 + liqOffset;
liquidTris[4] = idx1 + liqOffset;
liquidTris[5] = idx4 + liqOffset;
++liquidSquares;
}
}
}
meshData.liquidType.resize(meshData.liquidType.size() + liquidSquares * 2, type);
}
}
}
/**************************************************************************/
void TerrainBuilder::transformVertices(std::vector const& source, std::vector& dest, float scale, G3D::Matrix3 const& rotation, G3D::Vector3 const& position)
{
std::size_t offset = dest.size();
dest.resize(dest.size() + source.size() * 3);
float* d = dest.data();
for (std::size_t i = 0; i < source.size(); ++i)
{
// apply tranform, then mirror along the horizontal axes
G3D::Vector3 v(source[i] * rotation * scale + position);
v.x *= -1.f;
v.y *= -1.f;
d[offset + i * 3 + 0] = v.y;
d[offset + i * 3 + 1] = v.z;
d[offset + i * 3 + 2] = v.x;
}
}
/**************************************************************************/
void TerrainBuilder::copyIndices(std::vector const& source, std::vector& dest, int offset, bool flip)
{
std::size_t destOffset = dest.size();
dest.resize(dest.size() + source.size() * 3);
int* d = dest.data();
if (flip)
{
for (VMAP::MeshTriangle const& triangle : source)
{
d[destOffset++] = triangle.idx2 + offset;
d[destOffset++] = triangle.idx1 + offset;
d[destOffset++] = triangle.idx0 + offset;
}
}
else
{
static_assert(sizeof(VMAP::MeshTriangle) == 3 * sizeof(uint32));
std::ranges::transform(reinterpret_cast(source.data()), reinterpret_cast(source.data() + source.size()),
dest.data() + destOffset, [&](int src) { return src + offset; });
}
}
/**************************************************************************/
void TerrainBuilder::copyIndices(std::vector const& source, std::vector& dest, int offset)
{
std::size_t destOffset = dest.size();
dest.resize(destOffset + source.size());
std::ranges::transform(source, dest.data() + destOffset, [&](int src) { return src + offset; });
}
/**************************************************************************/
void TerrainBuilder::cleanVertices(std::vector& verts, std::vector& tris)
{
std::unordered_map vertMap;
vertMap.reserve(tris.size());
int* t = tris.data();
float* v = verts.data();
std::vector cleanVerts;
cleanVerts.reserve(verts.size());
int count = 0;
// collect all the vertex indices from triangle
for (std::size_t i = 0; i < tris.size(); ++i)
{
auto [vertItr, isNew] = vertMap.try_emplace(t[i], count);
if (!isNew)
continue;
std::ptrdiff_t index = t[i];
cleanVerts.insert(cleanVerts.end(), &v[index * 3], &v[index * 3 + 3]);
count++;
}
verts = std::move(cleanVerts);
// update triangles to use new indices
for (std::size_t i = 0; i < tris.size(); ++i)
{
auto it = vertMap.find(t[i]);
if (it == vertMap.end())
continue;
t[i] = it->second;
}
}
/**************************************************************************/
void TerrainBuilder::loadOffMeshConnections(uint32 mapID, uint32 tileX, uint32 tileY, MeshData &meshData, std::vector const& offMeshConnections)
{
for (OffMeshData const& offMeshConnection : offMeshConnections)
{
if (mapID != offMeshConnection.MapId || tileX != offMeshConnection.TileX || tileY != offMeshConnection.TileY)
continue;
meshData.offMeshConnections.push_back(offMeshConnection.From[1]);
meshData.offMeshConnections.push_back(offMeshConnection.From[2]);
meshData.offMeshConnections.push_back(offMeshConnection.From[0]);
meshData.offMeshConnections.push_back(offMeshConnection.To[1]);
meshData.offMeshConnections.push_back(offMeshConnection.To[2]);
meshData.offMeshConnections.push_back(offMeshConnection.To[0]);
meshData.offMeshConnectionDirs.push_back(offMeshConnection.ConnectionFlags & OFFMESH_CONNECTION_FLAG_BIDIRECTIONAL);
meshData.offMeshConnectionRads.push_back(offMeshConnection.Radius); // agent size equivalent
// can be used same way as polygon flags
meshData.offMeshConnectionsAreas.push_back(offMeshConnection.AreaId);
meshData.offMeshConnectionsFlags.push_back(offMeshConnection.Flags);
}
}
}