diff --git a/dCommon/CMakeLists.txt b/dCommon/CMakeLists.txt index 6055cd914..0aafc0622 100644 --- a/dCommon/CMakeLists.txt +++ b/dCommon/CMakeLists.txt @@ -35,7 +35,6 @@ set(DCOMMON_SOURCES "VanityEvents.cpp" "ZoneFile.cpp" "LevelFile.cpp" - "Raw.cpp" "ZoneScenes.cpp" "Process.cpp" "Permissions.cpp" @@ -58,6 +57,7 @@ endforeach() add_library(dCommon STATIC ${DCOMMON_SOURCES}) target_include_directories(dCommon PUBLIC "." "dClient" "dEnums" + "${PROJECT_SOURCE_DIR}/dZoneManager" # Raw.h (ZoneScenes.h); its reader is dZoneManager's PRIVATE "${PROJECT_SOURCE_DIR}/dDatabase/GameDatabase" "${PROJECT_SOURCE_DIR}/dDatabase/GameDatabase/ITables" diff --git a/dCommon/Raw.cpp b/dCommon/Raw.cpp deleted file mode 100644 index b5208d587..000000000 --- a/dCommon/Raw.cpp +++ /dev/null @@ -1,513 +0,0 @@ -#include "Raw.h" -#include "BinaryIO.h" -#include "Logger.h" -#include "SceneColor.h" -#include -#include -#include -#include - -namespace { -constexpr uint32_t kMaxResolution = 4096; -constexpr size_t kMaxBlobBytes = 64ULL * 1024 * 1024; // 64 MiB -constexpr uint32_t kMaxChunks = 1024; -} // namespace - -namespace Raw { - - // Little-endian values as the file has them, in one read (the heights of a chunk are millions of floats) - template - static void ReadArray(std::istream& stream, std::vector& values) { - stream.read(reinterpret_cast(values.data()), static_cast(values.size() * sizeof(T))); - if (!stream) throw std::runtime_error("Failed to read from istream."); - } - -bool Chunk::IsValidForSceneLookup() const { - return !sceneMap.empty() && colorMapResolution > 0 && !heightMap.empty() - && scaleFactor > 0.0f && width > 1 && height > 1; -} - -uint8_t Chunk::GetSceneIDAtGrid(uint32_t i, uint32_t j) const { - const float sceneMapI = (static_cast(i) / static_cast(width - 1)) * static_cast(colorMapResolution - 1); - const float sceneMapJ = (static_cast(j) / static_cast(height - 1)) * static_cast(colorMapResolution - 1); - const uint32_t sceneI = std::min(static_cast(sceneMapI), colorMapResolution - 1); - const uint32_t sceneJ = std::min(static_cast(sceneMapJ), colorMapResolution - 1); - const uint32_t sceneIndex = sceneI * colorMapResolution + sceneJ; - if (sceneIndex >= sceneMap.size()) return 0; - return sceneMap[sceneIndex]; -} - -NiPoint3 Chunk::GridToWorldPos(uint32_t i, uint32_t j) const { - const float y = (i * width + j < heightMap.size()) ? heightMap[i * width + j] : 0.0f; - return NiPoint3( - (static_cast(i) + (offsetX / scaleFactor)) * scaleFactor, - y, - (static_cast(j) + (offsetZ / scaleFactor)) * scaleFactor - ); -} - - /** - * @brief Read flair attributes from stream - */ - static bool ReadFlairAttributes(std::istream& stream, FlairAttributes& flair) { - try { - BinaryIO::BinaryRead(stream, flair.id); - BinaryIO::BinaryRead(stream, flair.scaleFactor); - BinaryIO::BinaryRead(stream, flair.position.x); - BinaryIO::BinaryRead(stream, flair.position.y); - BinaryIO::BinaryRead(stream, flair.position.z); - BinaryIO::BinaryRead(stream, flair.rotation.x); - BinaryIO::BinaryRead(stream, flair.rotation.y); - BinaryIO::BinaryRead(stream, flair.rotation.z); - BinaryIO::BinaryRead(stream, flair.colorR); - BinaryIO::BinaryRead(stream, flair.colorG); - BinaryIO::BinaryRead(stream, flair.colorB); - BinaryIO::BinaryRead(stream, flair.colorA); - return true; - } catch (const std::exception&) { - return false; - } - } - - /** - * @brief Read mesh triangle data from stream - */ - static bool ReadMeshTri(std::istream& stream, MeshTri& meshTri) { - try { - BinaryIO::BinaryRead(stream, meshTri.meshTriListSize); - meshTri.meshTriList.resize(meshTri.meshTriListSize); - ReadArray(stream, meshTri.meshTriList); - return true; - } catch (const std::exception&) { - return false; - } - } - - /** - * @brief Read a chunk from stream - */ - static bool ReadChunk(std::istream& stream, Chunk& chunk, uint16_t version) { - try { - // Read basic chunk info - BinaryIO::BinaryRead(stream, chunk.id); - if (stream.fail()) { - return false; - } - - BinaryIO::BinaryRead(stream, chunk.width); - BinaryIO::BinaryRead(stream, chunk.height); - BinaryIO::BinaryRead(stream, chunk.offsetX); - BinaryIO::BinaryRead(stream, chunk.offsetZ); - - if (stream.fail()) { - return false; - } - - // For version < 32, shader ID comes before texture IDs - if (version < 32) { - BinaryIO::BinaryRead(stream, chunk.shaderId); - } - - // Read texture IDs (4 textures) - chunk.textureIds.resize(4); - for (int i = 0; i < 4; ++i) { - BinaryIO::BinaryRead(stream, chunk.textureIds[i]); - } - - if (stream.fail()) { - return false; - } - - // Read scale factor - BinaryIO::BinaryRead(stream, chunk.scaleFactor); - - if (stream.fail()) { - return false; - } - - // Read heightmap - const size_t width = static_cast(chunk.width); - const size_t height = static_cast(chunk.height); - - if (width > kMaxResolution || height > kMaxResolution) { - LOG("Chunk %u heightmap dimensions exceed maximum resolution %u: width=%zu, height=%zu", chunk.id, kMaxResolution, width, height); - return false; - } - - if (height != 0 && width > std::numeric_limits::max() / height) { - LOG("Chunk %u heightmap size multiplication overflows: width=%zu, height=%zu", chunk.id, width, height); - return false; - } - - const size_t heightMapSize = width * height; - const size_t elementSize = sizeof(chunk.heightMap[0]); - - if (elementSize != 0 && heightMapSize > std::numeric_limits::max() / elementSize) { - LOG("Chunk %u heightmap byte size overflows: elements=%zu, elementSize=%zu", chunk.id, heightMapSize, elementSize); - return false; - } - - const size_t totalBytes = heightMapSize * elementSize; - if (totalBytes > kMaxBlobBytes) { - LOG("Chunk %u heightmap total size invalid: bytes=%zu (max %zu)", chunk.id, totalBytes, kMaxBlobBytes); - return false; - } - - chunk.heightMap.resize(heightMapSize); - ReadArray(stream, chunk.heightMap); - - if (stream.fail()) { - return false; - } - - // ColorMap - if (version >= 32) { - BinaryIO::BinaryRead(stream, chunk.colorMapResolution); - } else { - chunk.colorMapResolution = chunk.width > 0 ? chunk.width - 1 : 0; // no color map for a width of 0 - } - - if (chunk.colorMapResolution > kMaxResolution) { - LOG("Chunk colorMapResolution %u exceeds maximum %u", chunk.colorMapResolution, kMaxResolution); - return false; - } - - if (version >= 32) { - const size_t colorMapPixelCount = static_cast(chunk.colorMapResolution) * chunk.colorMapResolution * 4; - if (colorMapPixelCount > kMaxBlobBytes) { - LOG("Chunk colorMap size %zu exceeds maximum %zu bytes", colorMapPixelCount, kMaxBlobBytes); - return false; - } - chunk.colorMap.resize(colorMapPixelCount); - stream.read(reinterpret_cast(chunk.colorMap.data()), static_cast(colorMapPixelCount)); - } else if (chunk.width > 0) { - // width x width BGRA pixels, of which the client keeps the (width - 1) x (width - 1) before the last row and - // column, as RGBA (RAWReadColorandLightMaps) - const size_t legacyColorBytes = static_cast(chunk.width) * chunk.width * 4; - if (legacyColorBytes > kMaxBlobBytes) { - LOG("Chunk legacy colorMap size %zu exceeds maximum %zu bytes", legacyColorBytes, kMaxBlobBytes); - return false; - } - std::vector pixels(legacyColorBytes); - stream.read(reinterpret_cast(pixels.data()), static_cast(legacyColorBytes)); - chunk.colorMap.reserve(static_cast(chunk.colorMapResolution) * chunk.colorMapResolution * 4); - for (uint32_t y = 0; y < chunk.colorMapResolution; ++y) { - for (uint32_t x = 0; x < chunk.colorMapResolution; ++x) { - const auto* bgra = &pixels[(static_cast(y) * chunk.width + x) * 4]; - chunk.colorMap.insert(chunk.colorMap.end(), { bgra[2], bgra[1], bgra[0], bgra[3] }); - } - } - } - - if (stream.fail()) { - return false; - } - - // LightMap/diffusemap.dds (v>=32 only) - if (version >= 32) { - uint32_t lightMapSize; - BinaryIO::BinaryRead(stream, lightMapSize); - - if (lightMapSize > kMaxBlobBytes) { - LOG("Chunk lightMap size %u exceeds maximum %zu bytes", lightMapSize, kMaxBlobBytes); - return false; - } - chunk.lightMap.resize(lightMapSize); - stream.read(reinterpret_cast(chunk.lightMap.data()), static_cast(lightMapSize)); - - if (stream.fail()) { - return false; - } - } - - // Blend/texture map - BinaryIO::BinaryRead(stream, chunk.textureMapResolution); - - if (chunk.textureMapResolution > kMaxResolution) { - LOG("Chunk textureMapResolution %u exceeds maximum %u", chunk.textureMapResolution, kMaxResolution); - return false; - } - const size_t textureMapPixelCount = static_cast(chunk.textureMapResolution) * chunk.textureMapResolution * 4; - if (textureMapPixelCount > kMaxBlobBytes) { - LOG("Chunk textureMap size %zu exceeds maximum %zu bytes", textureMapPixelCount, kMaxBlobBytes); - return false; - } - chunk.textureMap.resize(textureMapPixelCount); - stream.read(reinterpret_cast(chunk.textureMap.data()), static_cast(textureMapPixelCount)); - // Before version 32 the pixels are BGRA; the client keeps them as RGBA (0x0103aaf0) - if (version < 32) { - for (size_t i = 0; i + 3 < chunk.textureMap.size(); i += 4) std::swap(chunk.textureMap[i], chunk.textureMap[i + 2]); - } - - if (stream.fail()) { - return false; - } - - // Texture settings + blend map DDS (v>=32 only) - if (version >= 32) { - BinaryIO::BinaryRead(stream, chunk.textureSettings); - - uint32_t blendMapDDSSize; - BinaryIO::BinaryRead(stream, blendMapDDSSize); - - if (blendMapDDSSize > kMaxBlobBytes) { - LOG("Chunk blendMap size %u exceeds maximum %zu bytes", blendMapDDSSize, kMaxBlobBytes); - return false; - } - chunk.blendMap.resize(blendMapDDSSize); - stream.read(reinterpret_cast(chunk.blendMap.data()), static_cast(blendMapDDSSize)); - - if (stream.fail()) { - return false; - } - } - - // Read flairs - uint32_t numFlairs; - BinaryIO::BinaryRead(stream, numFlairs); - - if (stream.fail()) { - return false; - } - - const size_t flairBytes = static_cast(numFlairs) * sizeof(FlairAttributes); - if (flairBytes > kMaxBlobBytes) { - LOG("Chunk %u flair count %u exceeds maximum (byte size %zu > %zu)", chunk.id, numFlairs, flairBytes, kMaxBlobBytes); - return false; - } - chunk.flairs.resize(numFlairs); - for (uint32_t i = 0; i < numFlairs; ++i) { - if (!ReadFlairAttributes(stream, chunk.flairs[i])) { - return false; - } - } - - // Scene map - if (version >= 32) { - const size_t sceneMapSize = static_cast(chunk.colorMapResolution) * chunk.colorMapResolution; - - if (sceneMapSize > kMaxBlobBytes) { - LOG("Chunk sceneMap size %zu exceeds maximum %zu bytes", sceneMapSize, kMaxBlobBytes); - return false; - } - chunk.sceneMap.resize(sceneMapSize); - stream.read(reinterpret_cast(chunk.sceneMap.data()), static_cast(sceneMapSize)); - } else if (version == 31) { - const size_t sceneMapCells = chunk.width > 0 ? static_cast(chunk.colorMapResolution + 1) * (chunk.colorMapResolution + 1) : 0; - if (sceneMapCells > kMaxBlobBytes) { - LOG("Chunk v31 sceneMap size %zu exceeds maximum %zu bytes", sceneMapCells, kMaxBlobBytes); - return false; - } - std::vector rawSceneMap(sceneMapCells); - stream.read(reinterpret_cast(rawSceneMap.data()), static_cast(sceneMapCells)); - chunk.sceneMap.resize(static_cast(chunk.colorMapResolution) * chunk.colorMapResolution); - for (uint32_t row = 0; row < chunk.colorMapResolution; ++row) { - for (uint32_t col = 0; col < chunk.colorMapResolution; ++col) { - chunk.sceneMap[row * chunk.colorMapResolution + col] = rawSceneMap[row * (chunk.colorMapResolution + 1) + col]; - } - } - } else { - // Before version 31 a chunk has no scene map, only a byte; the client's is all scene 0 (RAWReadSceneMap) - stream.seekg(1, std::ios::cur); - chunk.sceneMap.assign(static_cast(chunk.colorMapResolution) * chunk.colorMapResolution, 0); - } - - if (stream.fail()) { - return false; - } - - // Mesh data (v>=32 only) - if (version < 32) { - return true; - } - - BinaryIO::BinaryRead(stream, chunk.vertSize); - - if (stream.fail()) { - return false; - } - - if (chunk.vertSize == 0) { - return true; - } - - const size_t vertBytes = static_cast(chunk.vertSize) * sizeof(uint16_t); - if (vertBytes > kMaxBlobBytes) { - LOG("Chunk %u vertSize %u exceeds maximum (byte size %zu > %zu)", chunk.id, chunk.vertSize, vertBytes, kMaxBlobBytes); - return false; - } - chunk.meshVertUsage.resize(chunk.vertSize); - ReadArray(stream, chunk.meshVertUsage); - - if (stream.fail()) { - return false; - } - - chunk.meshVertSize.resize(16); - for (int i = 0; i < 16; ++i) { - BinaryIO::BinaryRead(stream, chunk.meshVertSize[i]); - } - - if (stream.fail()) { - return false; - } - - chunk.meshTri.resize(16); - for (int i = 0; i < 16; ++i) { - if (!ReadMeshTri(stream, chunk.meshTri[i])) { - return false; - } - } - - return true; - } catch (const std::exception&) { - return false; - } - } - - bool ReadRaw(std::istream& stream, Raw& outRaw) { - // Get stream size - stream.seekg(0, std::ios::end); - auto streamSize = stream.tellg(); - stream.seekg(0, std::ios::beg); - - if (streamSize <= 0) { - return false; - } - - try { - // Read header - BinaryIO::BinaryRead(stream, outRaw.version); - - if (stream.fail()) { - return false; - } - - BinaryIO::BinaryRead(stream, outRaw.dev); - - if (stream.fail()) { - return false; - } - - // Only read chunks if dev == 0 - if (outRaw.dev == 0) { - BinaryIO::BinaryRead(stream, outRaw.numChunks); - BinaryIO::BinaryRead(stream, outRaw.numChunksWidth); - BinaryIO::BinaryRead(stream, outRaw.numChunksHeight); - - if (outRaw.numChunks > kMaxChunks) { - LOG("Raw numChunks %u exceeds maximum %u", outRaw.numChunks, kMaxChunks); - return false; - } - - // Read all chunks - outRaw.chunks.resize(outRaw.numChunks); - for (uint32_t i = 0; i < outRaw.numChunks; ++i) { - if (!ReadChunk(stream, outRaw.chunks[i], outRaw.version)) { - return false; - } - } - - // Calculate terrain bounds from all chunks - if (!outRaw.chunks.empty()) { - outRaw.minBoundsX = std::numeric_limits::max(); - outRaw.minBoundsZ = std::numeric_limits::max(); - outRaw.maxBoundsX = std::numeric_limits::lowest(); - outRaw.maxBoundsZ = std::numeric_limits::lowest(); - - for (const auto& chunk : outRaw.chunks) { - const float chunkMinX = chunk.offsetX; - const float chunkMinZ = chunk.offsetZ; - const float chunkMaxX = chunkMinX + (chunk.width * chunk.scaleFactor); - const float chunkMaxZ = chunkMinZ + (chunk.height * chunk.scaleFactor); - - outRaw.minBoundsX = std::min(outRaw.minBoundsX, chunkMinX); - outRaw.minBoundsZ = std::min(outRaw.minBoundsZ, chunkMinZ); - outRaw.maxBoundsX = std::max(outRaw.maxBoundsX, chunkMaxX); - outRaw.maxBoundsZ = std::max(outRaw.maxBoundsZ, chunkMaxZ); - } - LOG_DEBUG("Raw terrain bounds: X[%.2f, %.2f], Z[%.2f, %.2f]", - outRaw.minBoundsX, outRaw.maxBoundsX, outRaw.minBoundsZ, outRaw.maxBoundsZ); - } - } - return true; - } catch (const std::exception&) { - return false; - } - } - - void GenerateTerrainMesh(const Raw& raw, TerrainMesh& outMesh) { - outMesh.vertices.clear(); - outMesh.triangles.clear(); - - if (raw.chunks.empty() || raw.version < 32) { - return; // No scene data available - } - - LOG_DEBUG("GenerateTerrainMesh: Processing %zu chunks", raw.chunks.size()); - - uint32_t vertexOffset = 0; - - for (const auto& chunk : raw.chunks) { - if (!chunk.IsValidForSceneLookup()) continue; - - for (uint32_t i = 0; i < chunk.width; ++i) { - for (uint32_t j = 0; j < chunk.height; ++j) { - const uint32_t heightIndex = chunk.width * i + j; - if (heightIndex >= chunk.heightMap.size()) continue; - - outMesh.vertices.emplace_back(chunk.GridToWorldPos(i, j), chunk.GetSceneIDAtGrid(i, j)); - if (i > 0 && j > 0) { - const uint32_t currentVert = vertexOffset + chunk.width * i + j; - const uint32_t leftVert = currentVert - 1; - const uint32_t bottomLeftVert = vertexOffset + chunk.width * (i - 1) + j - 1; - const uint32_t bottomVert = vertexOffset + chunk.width * (i - 1) + j; - - // First triangle - outMesh.triangles.push_back(currentVert); - outMesh.triangles.push_back(leftVert); - outMesh.triangles.push_back(bottomLeftVert); - - // Second triangle - outMesh.triangles.push_back(bottomLeftVert); - outMesh.triangles.push_back(bottomVert); - outMesh.triangles.push_back(currentVert); - } - } - } - - vertexOffset += chunk.width * chunk.height; - } - } - - bool WriteTerrainMeshToOBJ(const TerrainMesh& mesh, const std::string& path) { - try { - std::ofstream file(path); - if (!file.is_open()) { - LOG("Failed to open OBJ file for writing: %s", path.c_str()); - return false; - } - - for (const auto& v : mesh.vertices) { - const NiColor& color = SceneColor::Get(v.sceneID); - file << "v " << v.position.x << ' ' << v.position.y << ' ' << v.position.z - << ' ' << color.m_Red << ' ' << color.m_Green << ' ' << color.m_Blue << '\n'; - } - - for (size_t i = 0; i < mesh.triangles.size(); i += 3) { - file << "f " << (mesh.triangles[i] + 1) << ' ' - << (mesh.triangles[i + 1] + 1) << ' ' - << (mesh.triangles[i + 2] + 1) << '\n'; - } - - file.close(); - LOG("Successfully wrote terrain mesh to OBJ: %s (%zu vertices, %zu triangles)", - path.c_str(), mesh.vertices.size(), mesh.triangles.size() / 3); - return true; - } catch (const std::exception& e) { - LOG("Exception while writing OBJ file: %s", e.what()); - return false; - } - } - -} // namespace Raw diff --git a/dCommon/Raw.h b/dCommon/Raw.h deleted file mode 100644 index fd4afbd04..000000000 --- a/dCommon/Raw.h +++ /dev/null @@ -1,168 +0,0 @@ -#pragma once - -#ifndef __RAW_H__ -#define __RAW_H__ - -#include -#include -#include -#include -#include "NiPoint3.h" -#include "dCommonVars.h" - -/** - * A zone's terrain file (.raw), read whole: per chunk its heights, texture ids, color and texture blend maps, the - * scene each terrain cell belongs to, the flairs (grass, flowers and small rocks strewn over it) and the mesh data - * the client renders from. Guards against malformed files: sizes are capped before anything is allocated. Lives in - * dCommon so the dashboard's 3D views read terrain with the same code. - */ -namespace Raw { - -/** - * @brief Flair attributes structure - * Represents decorative elements on the terrain: id is a FlairTable row (its asset is the model), rotation is in - * radians about x, y and z, and the color tints the model (the client's Flair.fx multiplies the lighting by it) - */ -struct FlairAttributes { - uint32_t id; - float scaleFactor; - NiPoint3 position; - NiPoint3 rotation; - uint8_t colorR; - uint8_t colorG; - uint8_t colorB; - uint8_t colorA; -}; - -/** - * @brief Mesh triangle structure - * Contains triangle indices for terrain mesh - */ -struct MeshTri { - uint16_t meshTriListSize; - std::vector meshTriList; -}; - -/** - * @brief Vertex with scene ID - * Used for the generated terrain mesh to enable fast scene lookups - */ -struct SceneVertex { - NiPoint3 position; - uint8_t sceneID; - - SceneVertex() : position(), sceneID(0) {} - SceneVertex(const NiPoint3& pos, uint8_t scene) : position(pos), sceneID(scene) {} -}; - -/** - * @brief Generated terrain mesh - * Contains vertices with scene IDs for fast scene lookups at arbitrary positions - */ -struct TerrainMesh { - std::vector vertices; - std::vector triangles; // Indices into vertices array (groups of 3) - - TerrainMesh() = default; -}; - -/** - * @brief Terrain chunk structure - * Represents a single chunk of terrain with heightmap, textures, and meshes - */ -struct Chunk { - uint32_t id; - uint32_t width; - uint32_t height; - float offsetX; - float offsetZ; - uint32_t shaderId; - - // Texture IDs (4 textures per chunk) - std::vector textureIds; - - // Terrain scale factor - float scaleFactor; - - // Heightmap data (width * height floats) - std::vector heightMap; - - // Version 32+ fields - uint32_t colorMapResolution = 0; - std::vector colorMap; // RGBA pixels (colorMap * colorMap * 4) - std::vector lightMap; - - uint32_t textureMapResolution = 0; - std::vector textureMap; // (textureMapResolution * textureMapResolution * 4) - uint8_t textureSettings = 0; - std::vector blendMap; - - // Flair data - std::vector flairs; - - // Scene map (version 32+) - std::vector sceneMap; - - // Mesh data - uint32_t vertSize = 0; - std::vector meshVertUsage; - std::vector meshVertSize; - std::vector meshTri; - - bool IsValidForSceneLookup() const; - uint8_t GetSceneIDAtGrid(uint32_t i, uint32_t j) const; - NiPoint3 GridToWorldPos(uint32_t i, uint32_t j) const; -}; - -/** - * @brief RAW terrain file structure - * Complete representation of a .raw terrain file - */ -struct Raw { - uint16_t version; - uint8_t dev; - uint32_t numChunks = 0; - uint32_t numChunksWidth = 0; - uint32_t numChunksHeight = 0; - std::vector chunks; - - // Calculated bounds of the entire terrain - float minBoundsX = 0.0f; - float minBoundsZ = 0.0f; - float maxBoundsX = 0.0f; - float maxBoundsZ = 0.0f; -}; - -/** - * @brief Read a RAW terrain file from an input stream - * - * @param stream Input stream containing RAW file data - * @param outRaw Output RAW file structure - * @return true if successfully read, false otherwise - */ -bool ReadRaw(std::istream& stream, Raw& outRaw); - -/** - * @brief Generate a terrain mesh from raw chunks - * Similar to dTerrain's GenerateFinalMeshFromChunks but creates a mesh with scene IDs - * per vertex for fast scene lookups at arbitrary positions. - * - * @param raw The RAW terrain data to generate mesh from - * @param outMesh Output terrain mesh with vertices and scene IDs - */ -void GenerateTerrainMesh(const Raw& raw, TerrainMesh& outMesh); - -/** - * @brief Write terrain mesh to OBJ file for debugging/visualization - * Merged from dTerrain's WriteFinalMeshToOBJ functionality - * Vertices are colored based on their scene ID using a hash function - * - * @param mesh The terrain mesh to export - * @param path Output path for the OBJ file - * @return true if successfully written, false otherwise - */ -bool WriteTerrainMeshToOBJ(const TerrainMesh& mesh, const std::string& path); - -} // namespace Raw - -#endif // __RAW_H__ diff --git a/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp b/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp index 97f78f555..2260f3283 100644 --- a/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp +++ b/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp @@ -2028,7 +2028,7 @@ namespace DEVGMCommands { // Build the feedback message std::ostringstream feedback; feedback << "Scene ID: " << sceneID.GetSceneID(); - feedback << " (Layer: " << sceneID.GetLayerID() << ")"; + feedback << " (Layer: " << GeneralUtils::ToUnderlying(sceneID.GetLayerID()) << ")"; // Get the scene name const auto* sceneRef = zone->GetScene(sceneID); @@ -2071,7 +2071,7 @@ namespace DEVGMCommands { } else { feedback << "\nAdjacent Scenes (" << adjacentSceneIDs.size() << "):"; for (const auto& adjSceneID : adjacentSceneIDs) { - feedback << "\n - Scene ID: " << adjSceneID.GetSceneID() << " (Layer: " << adjSceneID.GetLayerID() << ")"; + feedback << "\n - Scene ID: " << adjSceneID.GetSceneID() << " (Layer: " << GeneralUtils::ToUnderlying(adjSceneID.GetLayerID()) << ")"; // Get the scene name if available const auto* sceneRef = zone->GetScene(adjSceneID); diff --git a/dZoneManager/LWOSCENEID.h b/dZoneManager/LWOSCENEID.h index 92ae36d7a..a857a5d2a 100644 --- a/dZoneManager/LWOSCENEID.h +++ b/dZoneManager/LWOSCENEID.h @@ -11,6 +11,8 @@ public: constexpr LWOSCENEID() noexcept { m_sceneID = -1; m_layerID = eSceneType::General; } constexpr LWOSCENEID(int32_t sceneID) noexcept { m_sceneID = sceneID; m_layerID = eSceneType::General; } constexpr LWOSCENEID(int32_t sceneID, eSceneType layerID) noexcept { m_sceneID = sceneID; m_layerID = layerID; } + // The layer as the zone file and the terrain number it (0 General, 1 Audio) + constexpr LWOSCENEID(int32_t sceneID, uint32_t layerID) noexcept { m_sceneID = sceneID; m_layerID = static_cast(layerID); } constexpr LWOSCENEID& operator=(const LWOSCENEID& rhs) noexcept { m_sceneID = rhs.m_sceneID; m_layerID = rhs.m_layerID; return *this; } constexpr LWOSCENEID& operator=(const int32_t rhs) noexcept { m_sceneID = rhs; m_layerID = eSceneType::General; return *this; } diff --git a/dZoneManager/Raw.cpp b/dZoneManager/Raw.cpp index 1bcacb576..76eab7044 100644 --- a/dZoneManager/Raw.cpp +++ b/dZoneManager/Raw.cpp @@ -157,14 +157,23 @@ NiPoint3 Chunk::GridToWorldPos(uint32_t i, uint32_t j) const { } chunk.colorMap.resize(colorMapPixelCount); stream.read(reinterpret_cast(chunk.colorMap.data()), static_cast(colorMapPixelCount)); - } else { + } else if (chunk.width > 0) { + // width x width BGRA pixels, of which the client keeps the (width - 1) x (width - 1) before the last row and + // column, as RGBA (RAWReadColorandLightMaps) const size_t legacyColorBytes = static_cast(chunk.width) * chunk.width * 4; if (legacyColorBytes > kMaxBlobBytes) { LOG("Chunk legacy colorMap size %zu exceeds maximum %zu bytes", legacyColorBytes, kMaxBlobBytes); return false; } - chunk.colorMap.resize(legacyColorBytes); - stream.read(reinterpret_cast(chunk.colorMap.data()), static_cast(legacyColorBytes)); + std::vector pixels(legacyColorBytes); + stream.read(reinterpret_cast(pixels.data()), static_cast(legacyColorBytes)); + chunk.colorMap.reserve(static_cast(chunk.colorMapResolution) * chunk.colorMapResolution * 4); + for (uint32_t y = 0; y < chunk.colorMapResolution; ++y) { + for (uint32_t x = 0; x < chunk.colorMapResolution; ++x) { + const auto* bgra = &pixels[(static_cast(y) * chunk.width + x) * 4]; + chunk.colorMap.insert(chunk.colorMap.end(), { bgra[2], bgra[1], bgra[0], bgra[3] }); + } + } } if (stream.fail()) { @@ -202,6 +211,10 @@ NiPoint3 Chunk::GridToWorldPos(uint32_t i, uint32_t j) const { } chunk.textureMap.resize(textureMapPixelCount); stream.read(reinterpret_cast(chunk.textureMap.data()), static_cast(textureMapPixelCount)); + // Before version 32 the pixels are BGRA; the client keeps them as RGBA (0x0103aaf0) + if (version < 32) { + for (size_t i = 0; i + 3 < chunk.textureMap.size(); i += 4) std::swap(chunk.textureMap[i], chunk.textureMap[i + 2]); + } if (stream.fail()) { return false; diff --git a/tests/dCommonTests/CMakeLists.txt b/tests/dCommonTests/CMakeLists.txt index 772126890..463596f94 100644 --- a/tests/dCommonTests/CMakeLists.txt +++ b/tests/dCommonTests/CMakeLists.txt @@ -60,6 +60,8 @@ endif() target_link_libraries(dCommonTests ${COMMON_LIBRARIES} MD5 GTest::gtest_main) # SpareBackoff.h (header only) target_include_directories(dCommonTests PRIVATE "${PROJECT_SOURCE_DIR}/dMasterServer") +# The terrain reader (ClientZoneFilesTests reads every client .raw), without the rest of dZoneManager +target_sources(dCommonTests PRIVATE "${PROJECT_SOURCE_DIR}/dZoneManager/Raw.cpp") # ChatFilterCore.h (header only) target_include_directories(dCommonTests PRIVATE "${PROJECT_SOURCE_DIR}/dChatFilter")