Files
azerothcore-wotlk/src/server/game/Grids/GridTerrainData.cpp
2025-02-14 22:11:27 +01:00

618 lines
22 KiB
C++

#include "DBCStores.h"
#include "GridDefines.h"
#include "GridTerrainData.h"
#include "Log.h"
#include "MapDefines.h"
#include <filesystem>
#include <G3D/Ray.h>
uint16 const holetab_h[4] = { 0x1111, 0x2222, 0x4444, 0x8888 };
uint16 const holetab_v[4] = { 0x000F, 0x00F0, 0x0F00, 0xF000 };
GridTerrainData::GridTerrainData()
{
_gridGetHeight = &GridTerrainData::getHeightFromFlat;
}
TerrainMapDataReadResult GridTerrainData::Load(std::string const& mapFileName)
{
// Check if file exists, we do this first as we need to
// differentiate between file existing and any other file errors
if (!std::filesystem::exists(mapFileName))
return TerrainMapDataReadResult::NotFound;
// Start the input stream and check for any errors
std::ifstream fileStream(mapFileName, std::ios::binary);
if (fileStream.fail())
return TerrainMapDataReadResult::ReadError;
// Read the map header
map_fileheader header;
if (!fileStream.read(reinterpret_cast<char*>(&header), sizeof(header)))
return TerrainMapDataReadResult::ReadError;
// Check for valid map and version magics
if (header.mapMagic != MapMagic.asUInt || header.versionMagic != MapVersionMagic)
return TerrainMapDataReadResult::InvalidMagic;
// Load area data
if (header.areaMapOffset && !LoadAreaData(fileStream, header.areaMapOffset))
return TerrainMapDataReadResult::InvalidAreaData;
// Load height data
if (header.heightMapOffset && !LoadHeightData(fileStream, header.heightMapOffset))
return TerrainMapDataReadResult::InvalidHeightData;
// Load liquid data
if (header.liquidMapOffset && !LoadLiquidData(fileStream, header.liquidMapOffset))
return TerrainMapDataReadResult::InvalidLiquidData;
// Load hole data
if (header.holesSize && !LoadHolesData(fileStream, header.holesOffset))
return TerrainMapDataReadResult::InvalidHoleData;
return TerrainMapDataReadResult::Success;
}
bool GridTerrainData::LoadAreaData(std::ifstream& fileStream, uint32 const offset)
{
fileStream.seekg(offset);
map_areaHeader header;
if (!fileStream.read(reinterpret_cast<char*>(&header), sizeof(header)) || header.fourcc != MapAreaMagic.asUInt)
return false;
_loadedAreaData = std::make_unique<LoadedAreaData>();
_loadedAreaData->gridArea = header.gridArea;
if (!(header.flags & MAP_AREA_NO_AREA))
{
_loadedAreaData->areaMap = std::make_unique<LoadedAreaData::AreaMapType>();
if (!fileStream.read(reinterpret_cast<char*>(_loadedAreaData->areaMap.get()), sizeof(LoadedAreaData::AreaMapType)))
return false;
}
return true;
}
bool GridTerrainData::LoadHeightData(std::ifstream& fileStream, uint32 const offset)
{
fileStream.seekg(offset);
map_heightHeader header;
if (!fileStream.read(reinterpret_cast<char*>(&header), sizeof(header)) || header.fourcc != MapHeightMagic.asUInt)
return false;
_loadedHeightData = std::make_unique<LoadedHeightData>();
_loadedHeightData->gridHeight = header.gridHeight;
if (!(header.flags & MAP_HEIGHT_NO_HEIGHT))
{
if ((header.flags & MAP_HEIGHT_AS_INT16))
{
_loadedHeightData->uint16HeightData = std::make_unique<LoadedHeightData::Uint16HeightData>();
if (!fileStream.read(reinterpret_cast<char*>(&_loadedHeightData->uint16HeightData->v9), sizeof(_loadedHeightData->uint16HeightData->v9))
|| !fileStream.read(reinterpret_cast<char*>(&_loadedHeightData->uint16HeightData->v8), sizeof(_loadedHeightData->uint16HeightData->v8)))
return false;
_loadedHeightData->uint16HeightData->gridIntHeightMultiplier = (header.gridMaxHeight - header.gridHeight) / 65535;
_gridGetHeight = &GridTerrainData::getHeightFromUint16;
}
else if ((header.flags & MAP_HEIGHT_AS_INT8))
{
_loadedHeightData->uint8HeightData = std::make_unique<LoadedHeightData::Uint8HeightData>();
if (!fileStream.read(reinterpret_cast<char*>(&_loadedHeightData->uint8HeightData->v9), sizeof(_loadedHeightData->uint8HeightData->v9))
|| !fileStream.read(reinterpret_cast<char*>(&_loadedHeightData->uint8HeightData->v8), sizeof(_loadedHeightData->uint8HeightData->v8)))
return false;
_loadedHeightData->uint8HeightData->gridIntHeightMultiplier = (header.gridMaxHeight - header.gridHeight) / 255;
_gridGetHeight = &GridTerrainData::getHeightFromUint8;
}
else
{
_loadedHeightData->floatHeightData = std::make_unique<LoadedHeightData::FloatHeightData>();
if (!fileStream.read(reinterpret_cast<char*>(&_loadedHeightData->floatHeightData->v9), sizeof(_loadedHeightData->floatHeightData->v9))
|| !fileStream.read(reinterpret_cast<char*>(&_loadedHeightData->floatHeightData->v8), sizeof(_loadedHeightData->floatHeightData->v8)))
return false;
_gridGetHeight = &GridTerrainData::getHeightFromFloat;
}
}
else
_gridGetHeight = &GridTerrainData::getHeightFromFlat;
if (header.flags & MAP_HEIGHT_HAS_FLIGHT_BOUNDS)
{
std::array<int16, 9> maxHeights;
std::array<int16, 9> minHeights;
if (!fileStream.read(reinterpret_cast<char*>(maxHeights.data()), sizeof(maxHeights)) ||
!fileStream.read(reinterpret_cast<char*>(minHeights.data()), sizeof(minHeights)))
return false;
static uint32 constexpr indices[8][3] =
{
{ 3, 0, 4 },
{ 0, 1, 4 },
{ 1, 2, 4 },
{ 2, 5, 4 },
{ 5, 8, 4 },
{ 8, 7, 4 },
{ 7, 6, 4 },
{ 6, 3, 4 }
};
static float constexpr boundGridCoords[9][2] =
{
{ 0.0f, 0.0f },
{ 0.0f, -266.66666f },
{ 0.0f, -533.33331f },
{ -266.66666f, 0.0f },
{ -266.66666f, -266.66666f },
{ -266.66666f, -533.33331f },
{ -533.33331f, 0.0f },
{ -533.33331f, -266.66666f },
{ -533.33331f, -533.33331f }
};
_loadedHeightData->minHeightPlanes = std::make_unique<LoadedHeightData::HeightPlanesType>();
for (uint32 quarterIndex = 0; quarterIndex < _loadedHeightData->minHeightPlanes->size(); ++quarterIndex)
_loadedHeightData->minHeightPlanes->at(quarterIndex) = G3D::Plane(
G3D::Vector3(boundGridCoords[indices[quarterIndex][0]][0], boundGridCoords[indices[quarterIndex][0]][1], minHeights[indices[quarterIndex][0]]),
G3D::Vector3(boundGridCoords[indices[quarterIndex][1]][0], boundGridCoords[indices[quarterIndex][1]][1], minHeights[indices[quarterIndex][1]]),
G3D::Vector3(boundGridCoords[indices[quarterIndex][2]][0], boundGridCoords[indices[quarterIndex][2]][1], minHeights[indices[quarterIndex][2]])
);
}
return true;
}
bool GridTerrainData::LoadLiquidData(std::ifstream& fileStream, uint32 const offset)
{
fileStream.seekg(offset);
map_liquidHeader header;
if (!fileStream.read(reinterpret_cast<char*>(&header), sizeof(header)) || header.fourcc != MapLiquidMagic.asUInt)
return false;
_loadedLiquidData = std::make_unique<LoadedLiquidData>();
_loadedLiquidData->liquidGlobalEntry = header.liquidType;
_loadedLiquidData->liquidGlobalFlags = header.liquidFlags;
_loadedLiquidData->liquidOffX = header.offsetX;
_loadedLiquidData->liquidOffY = header.offsetY;
_loadedLiquidData->liquidWidth = header.width;
_loadedLiquidData->liquidHeight = header.height;
_loadedLiquidData->liquidLevel = header.liquidLevel;
if (!(header.flags & MAP_LIQUID_NO_TYPE))
{
_loadedLiquidData->liquidEntry = std::make_unique<LoadedLiquidData::LiquidEntryType>();
if (!fileStream.read(reinterpret_cast<char*>(_loadedLiquidData->liquidEntry.get()), sizeof(LoadedLiquidData::LiquidEntryType)))
return false;
_loadedLiquidData->liquidFlags = std::make_unique<LoadedLiquidData::LiquidFlagsType>();
if (!fileStream.read(reinterpret_cast<char*>(_loadedLiquidData->liquidFlags.get()), sizeof(LoadedLiquidData::LiquidFlagsType)))
return false;
}
if (!(header.flags & MAP_LIQUID_NO_HEIGHT))
{
_loadedLiquidData->liquidMap = std::make_unique<LoadedLiquidData::LiquidMapType>();
_loadedLiquidData->liquidMap->resize(_loadedLiquidData->liquidWidth * _loadedLiquidData->liquidHeight);
if (!fileStream.read(reinterpret_cast<char*>(_loadedLiquidData->liquidMap->data()), _loadedLiquidData->liquidMap->size() * sizeof(float)))
return false;
}
return true;
}
bool GridTerrainData::LoadHolesData(std::ifstream& fileStream, uint32 const offset)
{
fileStream.seekg(offset);
_loadedHoleData = std::make_unique<LoadedHoleData>();
if (!fileStream.read(reinterpret_cast<char*>(&_loadedHoleData->holes), sizeof(_loadedHoleData->holes)))
return false;
return true;
}
uint16 GridTerrainData::getArea(float x, float y) const
{
if (!_loadedAreaData)
return 0;
if (!_loadedAreaData->areaMap)
return _loadedAreaData->gridArea;
x = 16 * (32 - x / SIZE_OF_GRIDS);
y = 16 * (32 - y / SIZE_OF_GRIDS);
int lx = (int)x & 15;
int ly = (int)y & 15;
return _loadedAreaData->areaMap->at(lx * 16 + ly);
}
float GridTerrainData::getHeightFromFlat(float /*x*/, float /*y*/) const
{
if (!_loadedHeightData)
return INVALID_HEIGHT;
return _loadedHeightData->gridHeight;
}
float GridTerrainData::getHeightFromFloat(float x, float y) const
{
if (!_loadedHeightData || !_loadedHeightData->floatHeightData)
return INVALID_HEIGHT;
x = MAP_RESOLUTION * (32 - x / SIZE_OF_GRIDS);
y = MAP_RESOLUTION * (32 - y / SIZE_OF_GRIDS);
int x_int = (int)x;
int y_int = (int)y;
x -= x_int;
y -= y_int;
x_int &= (MAP_RESOLUTION - 1);
y_int &= (MAP_RESOLUTION - 1);
if (isHole(x_int, y_int))
return INVALID_HEIGHT;
// Height stored as: h5 - its v8 grid, h1-h4 - its v9 grid
// +--------------> X
// | h1-------h2 Coordinates is:
// | | \ 1 / | h1 0, 0
// | | \ / | h2 0, 1
// | | 2 h5 3 | h3 1, 0
// | | / \ | h4 1, 1
// | | / 4 \ | h5 1/2, 1/2
// | h3-------h4
// V Y
// For find height need
// 1 - detect triangle
// 2 - solve linear equation from triangle points
// Calculate coefficients for solve h = a*x + b*y + c
float a, b, c;
// Select triangle:
if (x + y < 1)
{
if (x > y)
{
// 1 triangle (h1, h2, h5 points)
float h1 = _loadedHeightData->floatHeightData->v9[(x_int) * 129 + y_int];
float h2 = _loadedHeightData->floatHeightData->v9[(x_int + 1) * 129 + y_int];
float h5 = 2 * _loadedHeightData->floatHeightData->v8[x_int * 128 + y_int];
a = h2 - h1;
b = h5 - h1 - h2;
c = h1;
}
else
{
// 2 triangle (h1, h3, h5 points)
float h1 = _loadedHeightData->floatHeightData->v9[x_int * 129 + y_int];
float h3 = _loadedHeightData->floatHeightData->v9[x_int * 129 + y_int + 1];
float h5 = 2 * _loadedHeightData->floatHeightData->v8[x_int * 128 + y_int];
a = h5 - h1 - h3;
b = h3 - h1;
c = h1;
}
}
else
{
if (x > y)
{
// 3 triangle (h2, h4, h5 points)
float h2 = _loadedHeightData->floatHeightData->v9[(x_int + 1) * 129 + y_int];
float h4 = _loadedHeightData->floatHeightData->v9[(x_int + 1) * 129 + y_int + 1];
float h5 = 2 * _loadedHeightData->floatHeightData->v8[x_int * 128 + y_int];
a = h2 + h4 - h5;
b = h4 - h2;
c = h5 - h4;
}
else
{
// 4 triangle (h3, h4, h5 points)
float h3 = _loadedHeightData->floatHeightData->v9[(x_int) * 129 + y_int + 1];
float h4 = _loadedHeightData->floatHeightData->v9[(x_int + 1) * 129 + y_int + 1];
float h5 = 2 * _loadedHeightData->floatHeightData->v8[x_int * 128 + y_int];
a = h4 - h3;
b = h3 + h4 - h5;
c = h5 - h4;
}
}
// Calculate height
return a * x + b * y + c;
}
float GridTerrainData::getHeightFromUint8(float x, float y) const
{
if (!_loadedHeightData || !_loadedHeightData->uint8HeightData)
return INVALID_HEIGHT;
x = MAP_RESOLUTION * (32 - x / SIZE_OF_GRIDS);
y = MAP_RESOLUTION * (32 - y / SIZE_OF_GRIDS);
int x_int = (int)x;
int y_int = (int)y;
x -= x_int;
y -= y_int;
x_int &= (MAP_RESOLUTION - 1);
y_int &= (MAP_RESOLUTION - 1);
if (isHole(x_int, y_int))
return INVALID_HEIGHT;
int32 a, b, c;
uint8* V9_h1_ptr = &_loadedHeightData->uint8HeightData->v9[x_int * 128 + x_int + y_int];
if (x + y < 1)
{
if (x > y)
{
// 1 triangle (h1, h2, h5 points)
int32 h1 = V9_h1_ptr[0];
int32 h2 = V9_h1_ptr[129];
int32 h5 = 2 * _loadedHeightData->uint8HeightData->v8[x_int * 128 + y_int];
a = h2 - h1;
b = h5 - h1 - h2;
c = h1;
}
else
{
// 2 triangle (h1, h3, h5 points)
int32 h1 = V9_h1_ptr[0];
int32 h3 = V9_h1_ptr[1];
int32 h5 = 2 * _loadedHeightData->uint8HeightData->v8[x_int * 128 + y_int];
a = h5 - h1 - h3;
b = h3 - h1;
c = h1;
}
}
else
{
if (x > y)
{
// 3 triangle (h2, h4, h5 points)
int32 h2 = V9_h1_ptr[129];
int32 h4 = V9_h1_ptr[130];
int32 h5 = 2 * _loadedHeightData->uint8HeightData->v8[x_int * 128 + y_int];
a = h2 + h4 - h5;
b = h4 - h2;
c = h5 - h4;
}
else
{
// 4 triangle (h3, h4, h5 points)
int32 h3 = V9_h1_ptr[1];
int32 h4 = V9_h1_ptr[130];
int32 h5 = 2 * _loadedHeightData->uint8HeightData->v8[x_int * 128 + y_int];
a = h4 - h3;
b = h3 + h4 - h5;
c = h5 - h4;
}
}
// Calculate height
return (float)((a * x) + (b * y) + c) * _loadedHeightData->uint8HeightData->gridIntHeightMultiplier + _loadedHeightData->gridHeight;
}
float GridTerrainData::getHeightFromUint16(float x, float y) const
{
if (!_loadedHeightData || !_loadedHeightData->uint16HeightData)
return INVALID_HEIGHT;
x = MAP_RESOLUTION * (32 - x / SIZE_OF_GRIDS);
y = MAP_RESOLUTION * (32 - y / SIZE_OF_GRIDS);
int x_int = (int)x;
int y_int = (int)y;
x -= x_int;
y -= y_int;
x_int &= (MAP_RESOLUTION - 1);
y_int &= (MAP_RESOLUTION - 1);
if (isHole(x_int, y_int))
return INVALID_HEIGHT;
int32 a, b, c;
uint16* V9_h1_ptr = &_loadedHeightData->uint16HeightData->v9[x_int * 128 + x_int + y_int];
if (x + y < 1)
{
if (x > y)
{
// 1 triangle (h1, h2, h5 points)
int32 h1 = V9_h1_ptr[0];
int32 h2 = V9_h1_ptr[129];
int32 h5 = 2 * _loadedHeightData->uint16HeightData->v8[x_int * 128 + y_int];
a = h2 - h1;
b = h5 - h1 - h2;
c = h1;
}
else
{
// 2 triangle (h1, h3, h5 points)
int32 h1 = V9_h1_ptr[0];
int32 h3 = V9_h1_ptr[1];
int32 h5 = 2 * _loadedHeightData->uint16HeightData->v8[x_int * 128 + y_int];
a = h5 - h1 - h3;
b = h3 - h1;
c = h1;
}
}
else
{
if (x > y)
{
// 3 triangle (h2, h4, h5 points)
int32 h2 = V9_h1_ptr[129];
int32 h4 = V9_h1_ptr[130];
int32 h5 = 2 * _loadedHeightData->uint16HeightData->v8[x_int * 128 + y_int];
a = h2 + h4 - h5;
b = h4 - h2;
c = h5 - h4;
}
else
{
// 4 triangle (h3, h4, h5 points)
int32 h3 = V9_h1_ptr[1];
int32 h4 = V9_h1_ptr[130];
int32 h5 = 2 * _loadedHeightData->uint16HeightData->v8[x_int * 128 + y_int];
a = h4 - h3;
b = h3 + h4 - h5;
c = h5 - h4;
}
}
// Calculate height
return (float)((a * x) + (b * y) + c) * _loadedHeightData->uint16HeightData->gridIntHeightMultiplier + _loadedHeightData->gridHeight;
}
bool GridTerrainData::isHole(int row, int col) const
{
if (!_loadedHoleData)
return false;
int cellRow = row / 8; // 8 squares per cell
int cellCol = col / 8;
int holeRow = row % 8 / 2;
int holeCol = (col - (cellCol * 8)) / 2;
uint16 hole = _loadedHoleData->holes[cellRow * 16 + cellCol];
return (hole & holetab_h[holeCol] & holetab_v[holeRow]) != 0;
}
float GridTerrainData::getMinHeight(float x, float y) const
{
if (!_loadedHeightData || !_loadedHeightData->minHeightPlanes)
return MIN_HEIGHT;
GridCoord gridCoord = Acore::ComputeGridCoordSimple(x, y);
int32 doubleGridX = int32(std::floor(-(x - MAP_HALFSIZE) / CENTER_GRID_OFFSET));
int32 doubleGridY = int32(std::floor(-(y - MAP_HALFSIZE) / CENTER_GRID_OFFSET));
float gx = x - (int32(gridCoord.x_coord) - CENTER_GRID_ID + 1) * SIZE_OF_GRIDS;
float gy = y - (int32(gridCoord.y_coord) - CENTER_GRID_ID + 1) * SIZE_OF_GRIDS;
uint32 quarterIndex = 0;
if (doubleGridY & 1)
{
if (doubleGridX & 1)
quarterIndex = 4 + (gx <= gy);
else
quarterIndex = 2 + ((-SIZE_OF_GRIDS - gx) > gy);
}
else if (doubleGridX & 1)
quarterIndex = 6 + ((-SIZE_OF_GRIDS - gx) <= gy);
else
quarterIndex = gx > gy;
G3D::Ray ray = G3D::Ray::fromOriginAndDirection(G3D::Vector3(gx, gy, 0.0f), G3D::Vector3::unitZ());
return ray.intersection(_loadedHeightData->minHeightPlanes->at(quarterIndex)).z;
}
float GridTerrainData::getLiquidLevel(float x, float y) const
{
if (!_loadedLiquidData)
return INVALID_HEIGHT;
if (!_loadedLiquidData->liquidMap)
return _loadedLiquidData->liquidLevel;
x = MAP_RESOLUTION * (32 - x / SIZE_OF_GRIDS);
y = MAP_RESOLUTION * (32 - y / SIZE_OF_GRIDS);
int cx_int = ((int)x & (MAP_RESOLUTION - 1)) - _loadedLiquidData->liquidOffY;
int cy_int = ((int)y & (MAP_RESOLUTION - 1)) - _loadedLiquidData->liquidOffX;
if (cx_int < 0 || cx_int >= _loadedLiquidData->liquidHeight)
return INVALID_HEIGHT;
if (cy_int < 0 || cy_int >= _loadedLiquidData->liquidWidth)
return INVALID_HEIGHT;
return _loadedLiquidData->liquidMap->at(cx_int * _loadedLiquidData->liquidWidth + cy_int);
}
// Get water state on map
LiquidData const GridTerrainData::GetLiquidData(float x, float y, float z, float collisionHeight, uint8 ReqLiquidType) const
{
LiquidData liquidData;
if (!_loadedLiquidData)
return liquidData;
// Check water type (if no water return)
if (_loadedLiquidData->liquidGlobalFlags || _loadedLiquidData->liquidFlags)
{
// Get cell
float cx = MAP_RESOLUTION * (32 - x / SIZE_OF_GRIDS);
float cy = MAP_RESOLUTION * (32 - y / SIZE_OF_GRIDS);
int x_int = (int)cx & (MAP_RESOLUTION - 1);
int y_int = (int)cy & (MAP_RESOLUTION - 1);
// Check water type in cell
int idx = (x_int >> 3) * 16 + (y_int >> 3);
uint8 type = _loadedLiquidData->liquidFlags ? _loadedLiquidData->liquidFlags->at(idx) : _loadedLiquidData->liquidGlobalFlags;
uint32 entry = _loadedLiquidData->liquidEntry ? _loadedLiquidData->liquidEntry->at(idx) : _loadedLiquidData->liquidGlobalEntry;
if (LiquidTypeEntry const* liquidEntry = sLiquidTypeStore.LookupEntry(entry))
{
type &= MAP_LIQUID_TYPE_DARK_WATER;
uint32 liqTypeIdx = liquidEntry->Type;
if (entry < 21)
{
if (AreaTableEntry const* area = sAreaTableStore.LookupEntry(getArea(x, y)))
{
uint32 overrideLiquid = area->LiquidTypeOverride[liquidEntry->Type];
if (!overrideLiquid && area->zone)
{
area = sAreaTableStore.LookupEntry(area->zone);
if (area)
overrideLiquid = area->LiquidTypeOverride[liquidEntry->Type];
}
if (LiquidTypeEntry const* liq = sLiquidTypeStore.LookupEntry(overrideLiquid))
{
entry = overrideLiquid;
liqTypeIdx = liq->Type;
}
}
}
type |= 1 << liqTypeIdx;
}
// Check req liquid type mask
if (type != 0 && (!ReqLiquidType || (ReqLiquidType & type) != 0))
{
// Check water level:
// Check water height map
int lx_int = x_int - _loadedLiquidData->liquidOffY;
int ly_int = y_int - _loadedLiquidData->liquidOffX;
if (lx_int >= 0 && lx_int < _loadedLiquidData->liquidHeight && ly_int >= 0 && ly_int < _loadedLiquidData->liquidWidth)
{
// Get water level
float liquid_level = _loadedLiquidData->liquidMap ? _loadedLiquidData->liquidMap->at(lx_int * _loadedLiquidData->liquidWidth + ly_int) : _loadedLiquidData->liquidLevel;
// Get ground level
float ground_level = getHeight(x, y);
// Check water level and ground level (sub 0.2 for fix some errors)
if (liquid_level >= ground_level && z >= ground_level - 0.2f)
{
// All ok in water -> store data
liquidData.Entry = entry;
liquidData.Flags = type;
liquidData.Level = liquid_level;
liquidData.DepthLevel = ground_level;
// For speed check as int values
float delta = liquid_level - z;
if (delta > collisionHeight)
liquidData.Status = LIQUID_MAP_UNDER_WATER;
else if (delta > 0.0f)
liquidData.Status = LIQUID_MAP_IN_WATER;
else if (delta > -0.1f)
liquidData.Status = LIQUID_MAP_WATER_WALK;
else
liquidData.Status = LIQUID_MAP_ABOVE_WATER;
}
}
}
}
return liquidData;
}