diff --git a/dCommon/NiColor.h b/dCommon/NiColor.h new file mode 100644 index 00000000..e139606b --- /dev/null +++ b/dCommon/NiColor.h @@ -0,0 +1,34 @@ +#ifndef NICOLOR_H +#define NICOLOR_H + +struct NiColor { + float m_Red; + float m_Green; + float m_Blue; + + NiColor(float red, float green, float blue) : m_Red(red), m_Green(green), m_Blue(blue) {} + NiColor() : NiColor(0.0f, 0.0f, 0.0f) {} + + /* reduce RGB files to grayscale, with or without alpha + * using the equation given in Poynton's ColorFAQ at + * // dead link + * Copyright (c) 1998-01-04 Charles Poynton poynton at inforamp.net + * + * Y = 0.212671 * R + 0.715160 * G + 0.072169 * B + * + * We approximate this with + * + * Y = 0.21268 * R + 0.7151 * G + 0.07217 * B + * + * which can be expressed with integers as + * + * Y = (6969 * R + 23434 * G + 2365 * B)/32768 + * + * The calculation is to be done in a linear colorspace. + * + * Other integer coefficents can be used via png_set_rgb_to_gray(). + */ + float ToXYZ() const { return (m_Red * 0.212671f) + (m_Green * 0.71516f) + (m_Blue * 0.072169f); }; +}; + +#endif // NICOLOR_H \ No newline at end of file diff --git a/dCommon/dClient/SceneColor.h b/dCommon/dClient/SceneColor.h new file mode 100644 index 00000000..bdee3024 --- /dev/null +++ b/dCommon/dClient/SceneColor.h @@ -0,0 +1,170 @@ +#ifndef SCENE_COLOR_H +#define SCENE_COLOR_H + +#include "NiColor.h" +#include + +class SceneColor { +public: + SceneColor() { + TEMPLATE_COLORS.resize(146); + + // these are not random values, they are the actual template colors used by the game + TEMPLATE_COLORS[0] = NiColor(0.5019608f, 0.5019608f, 0.5019608f); + TEMPLATE_COLORS[1] = NiColor(1.0f, 0.0f, 0.0f); + TEMPLATE_COLORS[2] = NiColor(0.0f, 1.0f, 0.0f); + TEMPLATE_COLORS[3] = NiColor(0.0f, 0.0f, 1.0f); + TEMPLATE_COLORS[4] = NiColor(1.0f, 1.0f, 0.0f); + TEMPLATE_COLORS[5] = NiColor(1.0f, 0.0f, 1.0f); + TEMPLATE_COLORS[6] = NiColor(0.0f, 1.0f, 1.0f); + TEMPLATE_COLORS[7] = NiColor(0.5019608f, 0.0f, 1.0f); + TEMPLATE_COLORS[8] = NiColor(1.0f, 0.5019608f, 0.0f); + TEMPLATE_COLORS[9] = NiColor(1.0f, 0.5019608f, 0.5019608f); + TEMPLATE_COLORS[10] = NiColor(0.5019608f, 0.2509804f, 0.0f); + TEMPLATE_COLORS[11] = NiColor(0.5019608f, 0.0f, 0.2509804f); + TEMPLATE_COLORS[12] = NiColor(0.0f, 0.5019608f, 0.2509804f); + TEMPLATE_COLORS[13] = NiColor(0.2509804f, 0.0f, 0.5019608f); + TEMPLATE_COLORS[14] = NiColor(0.8745098f, 0.0f, 0.2509804f); + TEMPLATE_COLORS[15] = NiColor(0.2509804f, 0.8745098f, 0.5019608f); + TEMPLATE_COLORS[16] = NiColor(1.0f, 0.7490196f, 0.0f); + TEMPLATE_COLORS[17] = NiColor(1.0f, 0.2509804f, 0.0627451f); + TEMPLATE_COLORS[18] = NiColor(0.2509804f, 0.0f, 0.8745098f); + TEMPLATE_COLORS[19] = NiColor(0.7490196f, 0.0627451f, 0.0627451f); + TEMPLATE_COLORS[20] = NiColor(0.0627451f, 0.7490196f, 0.0627451f); + TEMPLATE_COLORS[21] = NiColor(1.0f, 0.5019608f, 1.0f); + TEMPLATE_COLORS[22] = NiColor(0.9372549f, 0.8705882f, 0.8039216f); + TEMPLATE_COLORS[23] = NiColor(0.8039216f, 0.5843138f, 0.4588235f); + TEMPLATE_COLORS[24] = NiColor(0.9921569f, 0.8509804f, 0.7098039f); + TEMPLATE_COLORS[25] = NiColor(0.4705882f, 0.8588235f, 0.8862745f); + TEMPLATE_COLORS[26] = NiColor(0.5294118f, 0.6627451f, 0.4196078f); + TEMPLATE_COLORS[27] = NiColor(1.0f, 0.6431373f, 0.454902f); + TEMPLATE_COLORS[28] = NiColor(0.9803922f, 0.9058824f, 0.7098039f); + TEMPLATE_COLORS[29] = NiColor(0.6235294f, 0.5058824f, 0.4392157f); + TEMPLATE_COLORS[30] = NiColor(0.9921569f, 0.4862745f, 0.4313726f); + TEMPLATE_COLORS[31] = NiColor(0.0f, 0.0f, 0.0f); + TEMPLATE_COLORS[32] = NiColor(0.6745098f, 0.8980392f, 0.9333333f); + TEMPLATE_COLORS[33] = NiColor(0.1215686f, 0.4588235f, 0.9960784f); + TEMPLATE_COLORS[34] = NiColor(0.6352941f, 0.6352941f, 0.8156863f); + TEMPLATE_COLORS[35] = NiColor(0.4f, 0.6f, 0.8f); + TEMPLATE_COLORS[36] = NiColor(0.05098039f, 0.5960785f, 0.7294118f); + TEMPLATE_COLORS[37] = NiColor(0.4509804f, 0.4f, 0.7411765f); + TEMPLATE_COLORS[38] = NiColor(0.8705882f, 0.3647059f, 0.5137255f); + TEMPLATE_COLORS[39] = NiColor(0.7960784f, 0.254902f, 0.3294118f); + TEMPLATE_COLORS[40] = NiColor(0.7058824f, 0.4039216f, 0.3019608f); + TEMPLATE_COLORS[41] = NiColor(1.0f, 0.4980392f, 0.2862745f); + TEMPLATE_COLORS[42] = NiColor(0.9176471f, 0.4941176f, 0.3647059f); + TEMPLATE_COLORS[43] = NiColor(0.6901961f, 0.7176471f, 0.7764706f); + TEMPLATE_COLORS[44] = NiColor(1.0f, 1.0f, 0.6f); + TEMPLATE_COLORS[45] = NiColor(0.1098039f, 0.827451f, 0.6352941f); + TEMPLATE_COLORS[46] = NiColor(1.0f, 0.6666667f, 0.8f); + TEMPLATE_COLORS[47] = NiColor(0.8666667f, 0.2666667f, 0.572549f); + TEMPLATE_COLORS[48] = NiColor(0.1137255f, 0.6745098f, 0.8392157f); + TEMPLATE_COLORS[49] = NiColor(0.7372549f, 0.3647059f, 0.345098f); + TEMPLATE_COLORS[50] = NiColor(0.8666667f, 0.5803922f, 0.4588235f); + TEMPLATE_COLORS[51] = NiColor(0.6039216f, 0.8078431f, 0.9215686f); + TEMPLATE_COLORS[52] = NiColor(1.0f, 0.7372549f, 0.8509804f); + TEMPLATE_COLORS[53] = NiColor(0.9921569f, 0.8588235f, 0.427451f); + TEMPLATE_COLORS[54] = NiColor(0.1686275f, 0.4235294f, 0.7686275f); + TEMPLATE_COLORS[55] = NiColor(0.9372549f, 0.8039216f, 0.7215686f); + TEMPLATE_COLORS[56] = NiColor(0.4313726f, 0.3176471f, 0.3764706f); + TEMPLATE_COLORS[57] = NiColor(0.8078431f, 1.0f, 0.1137255f); + TEMPLATE_COLORS[58] = NiColor(0.427451f, 0.682353f, 0.5058824f); + TEMPLATE_COLORS[59] = NiColor(0.7647059f, 0.3921569f, 0.772549f); + TEMPLATE_COLORS[60] = NiColor(0.8f, 0.4f, 0.4f); + TEMPLATE_COLORS[61] = NiColor(0.9058824f, 0.7764706f, 0.5921569f); + TEMPLATE_COLORS[62] = NiColor(0.9882353f, 0.8509804f, 0.4588235f); + TEMPLATE_COLORS[63] = NiColor(0.6588235f, 0.8941177f, 0.627451f); + TEMPLATE_COLORS[64] = NiColor(0.5843138f, 0.5686275f, 0.5490196f); + TEMPLATE_COLORS[65] = NiColor(0.1098039f, 0.6745098f, 0.4705882f); + TEMPLATE_COLORS[66] = NiColor(0.06666667f, 0.3921569f, 0.7058824f); + TEMPLATE_COLORS[67] = NiColor(0.9411765f, 0.9098039f, 0.5686275f); + TEMPLATE_COLORS[68] = NiColor(1.0f, 0.1137255f, 0.8078431f); + TEMPLATE_COLORS[69] = NiColor(0.6980392f, 0.9254902f, 0.3647059f); + TEMPLATE_COLORS[70] = NiColor(0.3647059f, 0.4627451f, 0.7960784f); + TEMPLATE_COLORS[71] = NiColor(0.7921569f, 0.2156863f, 0.4039216f); + TEMPLATE_COLORS[72] = NiColor(0.2313726f, 0.6901961f, 0.5607843f); + TEMPLATE_COLORS[73] = NiColor(0.9882353f, 0.7058824f, 0.8352941f); + TEMPLATE_COLORS[74] = NiColor(1.0f, 0.9568627f, 0.3098039f); + TEMPLATE_COLORS[75] = NiColor(1.0f, 0.7411765f, 0.5333334f); + TEMPLATE_COLORS[76] = NiColor(0.9647059f, 0.3921569f, 0.6862745f); + TEMPLATE_COLORS[77] = NiColor(0.6666667f, 0.9411765f, 0.8196079f); + TEMPLATE_COLORS[78] = NiColor(0.8039216f, 0.2901961f, 0.2980392f); + TEMPLATE_COLORS[79] = NiColor(0.9294118f, 0.8196079f, 0.6117647f); + TEMPLATE_COLORS[80] = NiColor(0.5921569f, 0.6039216f, 0.6666667f); + TEMPLATE_COLORS[81] = NiColor(0.7843137f, 0.2196078f, 0.3529412f); + TEMPLATE_COLORS[82] = NiColor(0.9372549f, 0.5960785f, 0.6666667f); + TEMPLATE_COLORS[83] = NiColor(0.9921569f, 0.7372549f, 0.7058824f); + TEMPLATE_COLORS[84] = NiColor(0.1019608f, 0.282353f, 0.4627451f); + TEMPLATE_COLORS[85] = NiColor(0.1882353f, 0.7294118f, 0.5607843f); + TEMPLATE_COLORS[86] = NiColor(0.772549f, 0.2941177f, 0.5490196f); + TEMPLATE_COLORS[87] = NiColor(0.09803922f, 0.454902f, 0.8235294f); + TEMPLATE_COLORS[88] = NiColor(0.7294118f, 0.7215686f, 0.4235294f); + TEMPLATE_COLORS[89] = NiColor(1.0f, 0.4588235f, 0.2196078f); + TEMPLATE_COLORS[90] = NiColor(1.0f, 0.1686275f, 0.1686275f); + TEMPLATE_COLORS[91] = NiColor(0.972549f, 0.8352941f, 0.4078431f); + TEMPLATE_COLORS[92] = NiColor(0.9019608f, 0.6588235f, 0.8431373f); + TEMPLATE_COLORS[93] = NiColor(0.254902f, 0.2901961f, 0.2980392f); + TEMPLATE_COLORS[94] = NiColor(1.0f, 0.4313726f, 0.2901961f); + TEMPLATE_COLORS[95] = NiColor(0.1098039f, 0.6627451f, 0.7882353f); + TEMPLATE_COLORS[96] = NiColor(1.0f, 0.8117647f, 0.6705883f); + TEMPLATE_COLORS[97] = NiColor(0.772549f, 0.8156863f, 0.9019608f); + TEMPLATE_COLORS[98] = NiColor(0.9921569f, 0.8666667f, 0.9019608f); + TEMPLATE_COLORS[99] = NiColor(0.08235294f, 0.5019608f, 0.4705882f); + TEMPLATE_COLORS[100] = NiColor(0.9882353f, 0.454902f, 0.9921569f); + TEMPLATE_COLORS[101] = NiColor(0.9686275f, 0.5607843f, 0.654902f); + TEMPLATE_COLORS[102] = NiColor(0.5568628f, 0.2705882f, 0.5215687f); + TEMPLATE_COLORS[103] = NiColor(0.454902f, 0.2588235f, 0.7843137f); + TEMPLATE_COLORS[104] = NiColor(0.6156863f, 0.5058824f, 0.7294118f); + TEMPLATE_COLORS[105] = NiColor(1.0f, 0.2862745f, 0.4235294f); + TEMPLATE_COLORS[106] = NiColor(0.8392157f, 0.5411765f, 0.3490196f); + TEMPLATE_COLORS[107] = NiColor(0.4431373f, 0.2941177f, 0.1372549f); + TEMPLATE_COLORS[108] = NiColor(1.0f, 0.282353f, 0.8156863f); + TEMPLATE_COLORS[109] = NiColor(0.9333333f, 0.1254902f, 0.3019608f); + TEMPLATE_COLORS[110] = NiColor(1.0f, 0.3254902f, 0.2862745f); + TEMPLATE_COLORS[111] = NiColor(0.7529412f, 0.2666667f, 0.5607843f); + TEMPLATE_COLORS[112] = NiColor(0.1215686f, 0.8078431f, 0.7960784f); + TEMPLATE_COLORS[113] = NiColor(0.4705882f, 0.3176471f, 0.6627451f); + TEMPLATE_COLORS[114] = NiColor(1.0f, 0.6078432f, 0.6666667f); + TEMPLATE_COLORS[115] = NiColor(0.9882353f, 0.1568628f, 0.2784314f); + TEMPLATE_COLORS[116] = NiColor(0.4627451f, 1.0f, 0.4784314f); + TEMPLATE_COLORS[117] = NiColor(0.6235294f, 0.8862745f, 0.7490196f); + TEMPLATE_COLORS[118] = NiColor(0.6470588f, 0.4117647f, 0.3098039f); + TEMPLATE_COLORS[119] = NiColor(0.5411765f, 0.4745098f, 0.3647059f); + TEMPLATE_COLORS[120] = NiColor(0.2705882f, 0.8078431f, 0.6352941f); + TEMPLATE_COLORS[121] = NiColor(0.8039216f, 0.772549f, 0.7607843f); + TEMPLATE_COLORS[122] = NiColor(0.5019608f, 0.854902f, 0.9215686f); + TEMPLATE_COLORS[123] = NiColor(0.9254902f, 0.9176471f, 0.7450981f); + TEMPLATE_COLORS[124] = NiColor(1.0f, 0.8117647f, 0.282353f); + TEMPLATE_COLORS[125] = NiColor(0.9921569f, 0.3686275f, 0.3254902f); + TEMPLATE_COLORS[126] = NiColor(0.9803922f, 0.654902f, 0.4235294f); + TEMPLATE_COLORS[127] = NiColor(0.09411765f, 0.654902f, 0.7098039f); + TEMPLATE_COLORS[128] = NiColor(0.9215686f, 0.7803922f, 0.8745098f); + TEMPLATE_COLORS[129] = NiColor(0.9882353f, 0.5372549f, 0.6745098f); + TEMPLATE_COLORS[130] = NiColor(0.8588235f, 0.8431373f, 0.8235294f); + TEMPLATE_COLORS[131] = NiColor(0.8705882f, 0.6666667f, 0.5333334f); + TEMPLATE_COLORS[132] = NiColor(0.4666667f, 0.8666667f, 0.9058824f); + TEMPLATE_COLORS[133] = NiColor(1.0f, 1.0f, 0.4f); + TEMPLATE_COLORS[134] = NiColor(0.572549f, 0.4313726f, 0.682353f); + TEMPLATE_COLORS[135] = NiColor(0.1960784f, 0.2901961f, 0.6980392f); + TEMPLATE_COLORS[136] = NiColor(0.9686275f, 0.3254902f, 0.5803922f); + TEMPLATE_COLORS[137] = NiColor(1.0f, 0.627451f, 0.5372549f); + TEMPLATE_COLORS[138] = NiColor(0.5607843f, 0.3137255f, 0.6156863f); + TEMPLATE_COLORS[139] = NiColor(1.0f, 1.0f, 1.0f); + TEMPLATE_COLORS[140] = NiColor(0.6352941f, 0.6784314f, 0.8156863f); + TEMPLATE_COLORS[141] = NiColor(0.9882353f, 0.4235294f, 0.5215687f); + TEMPLATE_COLORS[142] = NiColor(0.8039216f, 0.6431373f, 0.8705882f); + TEMPLATE_COLORS[143] = NiColor(0.9882353f, 0.9098039f, 0.5137255f); + TEMPLATE_COLORS[144] = NiColor(0.772549f, 0.8901961f, 0.5176471f); + TEMPLATE_COLORS[145] = NiColor(1.0f, 0.682353f, 0.2588235f); + } + + const NiColor& Get(unsigned char index) const { + return (index < 146) ? TEMPLATE_COLORS[index] : FALLBACK_COLOR; + } + +private: + const NiColor FALLBACK_COLOR = NiColor(1.0f, 1.0f, 1.0f); + std::vector TEMPLATE_COLORS; +}; + +#endif // SCENE_COLOR_H diff --git a/dNavigation/CMakeLists.txt b/dNavigation/CMakeLists.txt index e2a1c6ef..2c776136 100644 --- a/dNavigation/CMakeLists.txt +++ b/dNavigation/CMakeLists.txt @@ -1,11 +1,5 @@ set(DNAVIGATION_SOURCES "dNavMesh.cpp") -add_subdirectory(dTerrain) - -foreach(file ${DNAVIGATIONS_DTERRAIN_SOURCES}) - set(DNAVIGATION_SOURCES ${DNAVIGATION_SOURCES} "dTerrain/${file}") -endforeach() - add_library(dNavigation OBJECT ${DNAVIGATION_SOURCES}) target_include_directories(dNavigation PUBLIC "." PRIVATE diff --git a/dNavigation/dNavMesh.cpp b/dNavigation/dNavMesh.cpp index d9584b00..d0432c89 100644 --- a/dNavigation/dNavMesh.cpp +++ b/dNavigation/dNavMesh.cpp @@ -1,6 +1,5 @@ #include "dNavMesh.h" -#include "RawFile.h" #include "Game.h" #include "Logger.h" diff --git a/dNavigation/dTerrain/CMakeLists.txt b/dNavigation/dTerrain/CMakeLists.txt deleted file mode 100644 index 91d0741b..00000000 --- a/dNavigation/dTerrain/CMakeLists.txt +++ /dev/null @@ -1,3 +0,0 @@ -set(DNAVIGATIONS_DTERRAIN_SOURCES "RawFile.cpp" - "RawChunk.cpp" - "RawHeightMap.cpp" PARENT_SCOPE) diff --git a/dNavigation/dTerrain/RawChunk.cpp b/dNavigation/dTerrain/RawChunk.cpp deleted file mode 100644 index fe7ac8a2..00000000 --- a/dNavigation/dTerrain/RawChunk.cpp +++ /dev/null @@ -1,92 +0,0 @@ -#include "RawChunk.h" - -#include "BinaryIO.h" - -#include "RawMesh.h" -#include "RawHeightMap.h" - -RawChunk::RawChunk(std::ifstream& stream) { - // Read the chunk index and info - - BinaryIO::BinaryRead(stream, m_ChunkIndex); - BinaryIO::BinaryRead(stream, m_Width); - BinaryIO::BinaryRead(stream, m_Height); - BinaryIO::BinaryRead(stream, m_X); - BinaryIO::BinaryRead(stream, m_Z); - - m_HeightMap = new RawHeightMap(stream, m_Height, m_Width); - - // We can just skip the rest of the data so we can read the next chunks, we don't need anymore data - - uint32_t colorMapSize; - BinaryIO::BinaryRead(stream, colorMapSize); - stream.seekg(static_cast(stream.tellg()) + (colorMapSize * colorMapSize * 4)); - - uint32_t lightmapSize; - BinaryIO::BinaryRead(stream, lightmapSize); - stream.seekg(static_cast(stream.tellg()) + (lightmapSize)); - - uint32_t colorMapSize2; - BinaryIO::BinaryRead(stream, colorMapSize2); - stream.seekg(static_cast(stream.tellg()) + (colorMapSize2 * colorMapSize2 * 4)); - - uint8_t unknown; - BinaryIO::BinaryRead(stream, unknown); - - uint32_t blendmapSize; - BinaryIO::BinaryRead(stream, blendmapSize); - stream.seekg(static_cast(stream.tellg()) + (blendmapSize)); - - uint32_t pointSize; - BinaryIO::BinaryRead(stream, pointSize); - stream.seekg(static_cast(stream.tellg()) + (pointSize * 9 * 4)); - - stream.seekg(static_cast(stream.tellg()) + (colorMapSize * colorMapSize)); - - uint32_t endCounter; - BinaryIO::BinaryRead(stream, endCounter); - stream.seekg(static_cast(stream.tellg()) + (endCounter * 2)); - - if (endCounter != 0) { - stream.seekg(static_cast(stream.tellg()) + (32)); - - for (int i = 0; i < 0x10; i++) { - uint16_t finalCountdown; - BinaryIO::BinaryRead(stream, finalCountdown); - stream.seekg(static_cast(stream.tellg()) + (finalCountdown * 2)); - } - } - - // Generate our mesh/geo data for this chunk - - this->GenerateMesh(); -} - -RawChunk::~RawChunk() { - if (m_Mesh) delete m_Mesh; - if (m_HeightMap) delete m_HeightMap; -} - -void RawChunk::GenerateMesh() { - RawMesh* meshData = new RawMesh(); - - for (int i = 0; i < m_Width; ++i) { - for (int j = 0; j < m_Height; ++j) { - float y = *std::next(m_HeightMap->m_FloatMap.begin(), m_Width * i + j); - - meshData->m_Vertices.push_back(NiPoint3(i, y, j)); - - if (i == 0 || j == 0) continue; - - meshData->m_Triangles.push_back(m_Width * i + j); - meshData->m_Triangles.push_back(m_Width * i + j - 1); - meshData->m_Triangles.push_back(m_Width * (i - 1) + j - 1); - - meshData->m_Triangles.push_back(m_Width * (i - 1) + j - 1); - meshData->m_Triangles.push_back(m_Width * (i - 1) + j); - meshData->m_Triangles.push_back(m_Width * i + j); - } - } - - m_Mesh = meshData; -} diff --git a/dNavigation/dTerrain/RawChunk.h b/dNavigation/dTerrain/RawChunk.h deleted file mode 100644 index 1e26f11d..00000000 --- a/dNavigation/dTerrain/RawChunk.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -#include -#include - -struct RawMesh; -class RawHeightMap; - -class RawChunk { -public: - RawChunk(std::ifstream& stream); - ~RawChunk(); - - void GenerateMesh(); - - uint32_t m_ChunkIndex; - uint32_t m_Width; - uint32_t m_Height; - float m_X; - float m_Z; - - RawHeightMap* m_HeightMap; - RawMesh* m_Mesh; -}; diff --git a/dNavigation/dTerrain/RawFile.cpp b/dNavigation/dTerrain/RawFile.cpp deleted file mode 100644 index efc2b39d..00000000 --- a/dNavigation/dTerrain/RawFile.cpp +++ /dev/null @@ -1,84 +0,0 @@ -#include "RawFile.h" - -#include "BinaryIO.h" -#include "RawChunk.h" -#include "RawMesh.h" -#include "RawHeightMap.h" - -RawFile::RawFile(std::string fileName) { - if (!BinaryIO::DoesFileExist(fileName)) return; - - std::ifstream file(fileName, std::ios::binary); - - // Read header - - BinaryIO::BinaryRead(file, m_Version); - BinaryIO::BinaryRead(file, m_Padding); - BinaryIO::BinaryRead(file, m_ChunkCount); - BinaryIO::BinaryRead(file, m_Width); - BinaryIO::BinaryRead(file, m_Height); - - - if (m_Version < 0x20) { - return; // Version is too old to be supported - } - - // Read in chunks - - m_Chunks = {}; - - for (uint32_t i = 0; i < m_ChunkCount; i++) { - RawChunk* chunk = new RawChunk(file); - m_Chunks.push_back(chunk); - } - - m_FinalMesh = new RawMesh(); - - this->GenerateFinalMeshFromChunks(); -} - -RawFile::~RawFile() { - if (m_FinalMesh) delete m_FinalMesh; - for (const auto* item : m_Chunks) { - if (item) delete item; - } -} - -void RawFile::GenerateFinalMeshFromChunks() { - uint32_t lenOfLastChunk = 0; // index of last vert set in the last chunk - - for (const auto& chunk : m_Chunks) { - for (const auto& vert : chunk->m_Mesh->m_Vertices) { - auto tempVert = vert; - - // Scale X and Z by the chunk's position in the world - // Scale Y by the chunk's heightmap scale factor - tempVert.SetX(tempVert.GetX() + (chunk->m_X / chunk->m_HeightMap->m_ScaleFactor)); - tempVert.SetY(tempVert.GetY() / chunk->m_HeightMap->m_ScaleFactor); - tempVert.SetZ(tempVert.GetZ() + (chunk->m_Z / chunk->m_HeightMap->m_ScaleFactor)); - - // Then scale it again for some reason - tempVert *= chunk->m_HeightMap->m_ScaleFactor; - - m_FinalMesh->m_Vertices.push_back(tempVert); - } - - for (const auto& tri : chunk->m_Mesh->m_Triangles) { - m_FinalMesh->m_Triangles.push_back(tri + lenOfLastChunk); - } - - lenOfLastChunk += chunk->m_Mesh->m_Vertices.size(); - } -} - -void RawFile::WriteFinalMeshToOBJ(std::string path) { - std::ofstream file(path); - - for (const auto& v : m_FinalMesh->m_Vertices) { - file << "v " << v.x << ' ' << v.y << ' ' << v.z << '\n'; - } - - for (int i = 0; i < m_FinalMesh->m_Triangles.size(); i += 3) { - file << "f " << *std::next(m_FinalMesh->m_Triangles.begin(), i) + 1 << ' ' << *std::next(m_FinalMesh->m_Triangles.begin(), i + 1) + 1 << ' ' << *std::next(m_FinalMesh->m_Triangles.begin(), i + 2) + 1 << '\n'; - } -} diff --git a/dNavigation/dTerrain/RawFile.h b/dNavigation/dTerrain/RawFile.h deleted file mode 100644 index b1fb4024..00000000 --- a/dNavigation/dTerrain/RawFile.h +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once - -#include -#include -#include - -class RawChunk; -struct RawMesh; - -class RawFile { -public: - RawFile(std::string filePath); - ~RawFile(); - -private: - - void GenerateFinalMeshFromChunks(); - void WriteFinalMeshToOBJ(std::string path); - - uint8_t m_Version; - uint16_t m_Padding; - uint32_t m_ChunkCount; - uint32_t m_Width; - uint32_t m_Height; - - std::vector m_Chunks; - RawMesh* m_FinalMesh = nullptr; -}; diff --git a/dNavigation/dTerrain/RawHeightMap.cpp b/dNavigation/dTerrain/RawHeightMap.cpp deleted file mode 100644 index e1310669..00000000 --- a/dNavigation/dTerrain/RawHeightMap.cpp +++ /dev/null @@ -1,27 +0,0 @@ -#include "RawHeightMap.h" - -#include "BinaryIO.h" - -RawHeightMap::RawHeightMap() {} - -RawHeightMap::RawHeightMap(std::ifstream& stream, float height, float width) { - // Read in height map data header and scale - - BinaryIO::BinaryRead(stream, m_Unknown1); - BinaryIO::BinaryRead(stream, m_Unknown2); - BinaryIO::BinaryRead(stream, m_Unknown3); - BinaryIO::BinaryRead(stream, m_Unknown4); - BinaryIO::BinaryRead(stream, m_ScaleFactor); - - // read all vertices in - - for (uint64_t i = 0; i < width * height; i++) { - float value; - BinaryIO::BinaryRead(stream, value); - m_FloatMap.push_back(value); - } -} - -RawHeightMap::~RawHeightMap() { - -} diff --git a/dNavigation/dTerrain/RawHeightMap.h b/dNavigation/dTerrain/RawHeightMap.h deleted file mode 100644 index 9a4bda3b..00000000 --- a/dNavigation/dTerrain/RawHeightMap.h +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once - -#include -#include -#include - -class RawHeightMap { -public: - RawHeightMap(); - RawHeightMap(std::ifstream& stream, float height, float width); - ~RawHeightMap(); - - uint32_t m_Unknown1; - uint32_t m_Unknown2; - uint32_t m_Unknown3; - uint32_t m_Unknown4; - - float m_ScaleFactor; - - std::vector m_FloatMap = {}; -}; diff --git a/dNavigation/dTerrain/RawMesh.h b/dNavigation/dTerrain/RawMesh.h deleted file mode 100644 index ed8eb4f3..00000000 --- a/dNavigation/dTerrain/RawMesh.h +++ /dev/null @@ -1,10 +0,0 @@ -#pragma once - -#include - -#include "NiPoint3.h" - -struct RawMesh { - std::vector m_Vertices; - std::vector m_Triangles; -}; diff --git a/dZoneManager/CMakeLists.txt b/dZoneManager/CMakeLists.txt index e1ef9d28..ba6eff68 100644 --- a/dZoneManager/CMakeLists.txt +++ b/dZoneManager/CMakeLists.txt @@ -15,6 +15,7 @@ target_include_directories(dZoneManager PUBLIC "." "${PROJECT_SOURCE_DIR}/dGame" # Entity.h "${PROJECT_SOURCE_DIR}/dGame/dEntity" # EntityInfo.h PRIVATE + "${PROJECT_SOURCE_DIR}/dCommon/dClient" # SceneColors.h "${PROJECT_SOURCE_DIR}/dGame/dComponents" #InventoryComponent.h "${PROJECT_SOURCE_DIR}/dGame/dInventory" #InventoryComponent.h (transitive) "${PROJECT_SOURCE_DIR}/dGame/dBehaviors" #BehaviorSlot.h diff --git a/dZoneManager/Raw.cpp b/dZoneManager/Raw.cpp index ad02cf76..16fc9063 100644 --- a/dZoneManager/Raw.cpp +++ b/dZoneManager/Raw.cpp @@ -1,7 +1,9 @@ #include "Raw.h" #include "BinaryIO.h" #include "Logger.h" +#include "SceneColor.h" #include +#include namespace Raw { @@ -279,16 +281,22 @@ namespace Raw { return; // No scene data available } + LOG("GenerateTerrainMesh: Processing %u chunks", raw.chunks.size()); + uint32_t vertexOffset = 0; for (const auto& chunk : raw.chunks) { // Skip chunks without scene maps if (chunk.sceneMap.empty() || chunk.colorMapResolution == 0 || chunk.heightMap.empty()) { + LOG("Skipping chunk %u (sceneMap: %zu, colorMapRes: %u, heightMap: %zu)", + chunk.id, chunk.sceneMap.size(), chunk.colorMapResolution, chunk.heightMap.size()); continue; } + LOG("Processing chunk %u: width=%u, height=%u, colorMapRes=%u, sceneMapSize=%zu", + chunk.id, chunk.width, chunk.height, chunk.colorMapResolution, chunk.sceneMap.size()); + // Generate vertices for this chunk - // Similar to RawChunk::GenerateMesh() in dTerrain but with scene IDs for (uint32_t i = 0; i < chunk.width; ++i) { for (uint32_t j = 0; j < chunk.height; ++j) { // Get height at this position @@ -310,21 +318,23 @@ namespace Raw { NiPoint3 worldPos(worldX, worldY, worldZ); - // Get scene ID at this position - // Map heightmap position to scene map position - const float sceneMapX = (static_cast(i) / static_cast(chunk.width - 1)) * static_cast(chunk.colorMapResolution - 1); - const float sceneMapZ = (static_cast(j) / static_cast(chunk.height - 1)) * static_cast(chunk.colorMapResolution - 1); - - const uint32_t sceneX = std::min(static_cast(sceneMapX), chunk.colorMapResolution - 1); - const uint32_t sceneZ = std::min(static_cast(sceneMapZ), chunk.colorMapResolution - 1); - const uint32_t sceneIndex = sceneZ * chunk.colorMapResolution + sceneX; + // Get scene ID at this position + // Map heightmap position to scene map position + // The scene map is colorMapResolution x colorMapResolution + // We need to map from heightmap coordinates (i, j) to scene map coordinates + const float sceneMapI = (static_cast(i) / static_cast(chunk.width - 1)) * static_cast(chunk.colorMapResolution - 1); + const float sceneMapJ = (static_cast(j) / static_cast(chunk.height - 1)) * static_cast(chunk.colorMapResolution - 1); + + const uint32_t sceneI = std::min(static_cast(sceneMapI), chunk.colorMapResolution - 1); + const uint32_t sceneJ = std::min(static_cast(sceneMapJ), chunk.colorMapResolution - 1); + // Scene map uses the same indexing pattern as heightmap: row * width + col + const uint32_t sceneIndex = sceneI * chunk.colorMapResolution + sceneJ; - uint8_t sceneID = 0; - if (sceneIndex < chunk.sceneMap.size()) { - sceneID = chunk.sceneMap[sceneIndex]; - } - - outMesh.vertices.emplace_back(worldPos, sceneID); + uint8_t sceneID = 0; + if (sceneIndex < chunk.sceneMap.size()) { + sceneID = chunk.sceneMap[sceneIndex]; + } + outMesh.vertices.emplace_back(worldPos, sceneID); // Generate triangles (same pattern as dTerrain) if (i > 0 && j > 0) { @@ -346,8 +356,49 @@ namespace Raw { } } - vertexOffset += chunk.width * chunk.height; - } + vertexOffset += chunk.width * chunk.height; } +} -} // namespace Raw +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; + } + + // Create instance of SceneColor for color lookup + SceneColor sceneColor; + + // Write vertices with colors + // OBJ format supports vertex colors as: v x y z r g b + for (const auto& v : mesh.vertices) { + file << "v " << v.position.x << ' ' << v.position.y << ' ' << v.position.z; + + uint8_t sceneID = v.sceneID; + + const NiColor& color = sceneColor.Get(sceneID); + file << ' ' << color.m_Red << ' ' << color.m_Green << ' ' << color.m_Blue; + file << '\n'; + } + + // Write faces (triangles) + for (size_t i = 0; i < mesh.triangles.size(); i += 3) { + // OBJ indices are 1-based + 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 \ No newline at end of file diff --git a/dZoneManager/Raw.h b/dZoneManager/Raw.h index 2409e987..fa65cded 100644 --- a/dZoneManager/Raw.h +++ b/dZoneManager/Raw.h @@ -7,7 +7,9 @@ #include #include #include +#include #include "NiPoint3.h" +#include "dCommonVars.h" namespace Raw { @@ -138,6 +140,17 @@ bool ReadRaw(std::istream& stream, Raw& outRaw); */ 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/dZoneManager/Zone.cpp b/dZoneManager/Zone.cpp index 29245ff6..bc3a79e5 100644 --- a/dZoneManager/Zone.cpp +++ b/dZoneManager/Zone.cpp @@ -8,6 +8,7 @@ #include "GeneralUtils.h" #include "BinaryIO.h" #include "LUTriggers.h" +#include "dConfig.h" #include "AssetManager.h" #include "CDClientManager.h" @@ -102,6 +103,14 @@ void Zone::LoadZoneIntoMemory() { LOG("Generated terrain mesh with %llu vertices and %llu triangles", m_TerrainMesh.vertices.size(), m_TerrainMesh.triangles.size() / 3); + // Optionally export terrain mesh to OBJ for debugging/visualization + if (Game::config->GetValue("export_terrain_to_obj") == "1") { + std::string objFileName = "terrain_" + std::to_string(m_ZoneID.GetMapID()) + ".obj"; + if (Raw::WriteTerrainMeshToOBJ(m_TerrainMesh, objFileName)) { + LOG("Exported terrain mesh to %s", objFileName.c_str()); + } + } + if (m_FileFormatVersion >= Zone::FileFormatVersion::PreAlpha) { BinaryIO::BinaryRead(file, m_NumberOfSceneTransitionsLoaded); for (uint32_t i = 0; i < m_NumberOfSceneTransitionsLoaded; ++i) { diff --git a/dZoneManager/dZoneManager.cpp b/dZoneManager/dZoneManager.cpp index a757858e..5cb376d4 100644 --- a/dZoneManager/dZoneManager.cpp +++ b/dZoneManager/dZoneManager.cpp @@ -399,6 +399,23 @@ void dZoneManager::BuildSceneGraph() { } } } + + // Scene 0 (global scene) is always loaded and adjacent to all other scenes + LWOSCENEID globalScene = LWOSCENEID(m_ZoneID.GetMapID(), 0); + for (auto& [sceneID, adjacentScenes] : m_SceneAdjacencyList) { + if (sceneID != globalScene) { + // Add global scene to this scene's adjacency list if not already present + if (std::find(adjacentScenes.begin(), adjacentScenes.end(), globalScene) == adjacentScenes.end()) { + adjacentScenes.push_back(globalScene); + } + + // Add this scene to global scene's adjacency list if not already present + auto& globalAdjacent = m_SceneAdjacencyList[globalScene]; + if (std::find(globalAdjacent.begin(), globalAdjacent.end(), sceneID) == globalAdjacent.end()) { + globalAdjacent.push_back(sceneID); + } + } + } } std::vector dZoneManager::GetAdjacentScenes(LWOSCENEID sceneID) const { diff --git a/enc_temp_folder/48a84d7fa3b1c27696794fcc62d106d/Raw.cpp b/enc_temp_folder/48a84d7fa3b1c27696794fcc62d106d/Raw.cpp new file mode 100644 index 00000000..6276f35d --- /dev/null +++ b/enc_temp_folder/48a84d7fa3b1c27696794fcc62d106d/Raw.cpp @@ -0,0 +1,403 @@ +#include "Raw.h" +#include "BinaryIO.h" +#include "Logger.h" +#include "SceneColor.h" +#include +#include + +namespace Raw { + + /** + * @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); + for (uint16_t i = 0; i < meshTri.meshTriListSize; ++i) { + BinaryIO::BinaryRead(stream, meshTri.meshTriList[i]); + } + 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.offsetWorldX); + BinaryIO::BinaryRead(stream, chunk.offsetWorldZ); + + 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 + uint32_t heightMapSize = chunk.width * chunk.height; + + chunk.heightMap.resize(heightMapSize); + for (uint32_t i = 0; i < heightMapSize; ++i) { + BinaryIO::BinaryRead(stream, chunk.heightMap[i]); + } + + if (stream.fail()) { + return false; + } + + // ColorMap (size varies by version) + if (version >= 32) { + BinaryIO::BinaryRead(stream, chunk.colorMapResolution); + } else { + chunk.colorMapResolution = chunk.width; // Default to chunk width for older versions + } + + uint32_t colorMapPixelCount = chunk.colorMapResolution * chunk.colorMapResolution * 4; // RGBA + chunk.colorMap.resize(colorMapPixelCount); + stream.read(reinterpret_cast(chunk.colorMap.data()), colorMapPixelCount); + + if (stream.fail()) { + return false; + } + + // LightMap DDS + uint32_t lightMapSize; + BinaryIO::BinaryRead(stream, lightMapSize); + + chunk.lightMap.resize(lightMapSize); + stream.read(reinterpret_cast(chunk.lightMap.data()), lightMapSize); + + if (stream.fail()) { + return false; + } + + // TextureMap (size varies by version) + if (version >= 32) { + BinaryIO::BinaryRead(stream, chunk.textureMapResolution); + } else { + chunk.textureMapResolution = chunk.width; // Default to chunk width for older versions + } + + uint32_t textureMapPixelCount = chunk.textureMapResolution * chunk.textureMapResolution * 4; + chunk.textureMap.resize(textureMapPixelCount); + stream.read(reinterpret_cast(chunk.textureMap.data()), textureMapPixelCount); + + if (stream.fail()) { + return false; + } + + // Texture settings + BinaryIO::BinaryRead(stream, chunk.textureSettings); + + // Blend map DDS + uint32_t blendMapDDSSize; + BinaryIO::BinaryRead(stream, blendMapDDSSize); + + chunk.blendMap.resize(blendMapDDSSize); + stream.read(reinterpret_cast(chunk.blendMap.data()), blendMapDDSSize); + + if (stream.fail()) { + return false; + } + + // Read flairs + uint32_t numFlairs; + BinaryIO::BinaryRead(stream, numFlairs); + + if (stream.fail()) { + return false; + } + + chunk.flairs.resize(numFlairs); + for (uint32_t i = 0; i < numFlairs; ++i) { + if (!ReadFlairAttributes(stream, chunk.flairs[i])) { + return false; + } + } + + // Scene map (version 32+ only) + if (version >= 32) { + uint32_t sceneMapSize = chunk.colorMapResolution * chunk.colorMapResolution; + + chunk.sceneMap.resize(sceneMapSize); + stream.read(reinterpret_cast(chunk.sceneMap.data()), sceneMapSize); + + if (stream.fail()) { + return false; + } + } + + // Mesh vertex usage (read size first, then check if empty) + BinaryIO::BinaryRead(stream, chunk.vertSize); + + if (stream.fail()) { + return false; + } + + // Mesh vert usage + chunk.meshVertUsage.resize(chunk.vertSize); + for (uint32_t i = 0; i < chunk.vertSize; ++i) { + BinaryIO::BinaryRead(stream, chunk.meshVertUsage[i]); + } + + if (stream.fail()) { + return false; + } + + // Only continue with mesh data if we have vertex usage data + if (chunk.vertSize == 0) { + return true; + } + + // Mesh vert size (16 elements) + chunk.meshVertSize.resize(16); + for (int i = 0; i < 16; ++i) { + BinaryIO::BinaryRead(stream, chunk.meshVertSize[i]); + } + + if (stream.fail()) { + return false; + } + + // Mesh triangles (16 elements) + 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); + + // 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; + } + } + } + + 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("GenerateTerrainMesh: Processing %u chunks", raw.chunks.size()); + + uint32_t vertexOffset = 0; + + for (const auto& chunk : raw.chunks) { + // Skip chunks without scene maps + if (chunk.sceneMap.empty() || chunk.colorMapResolution == 0 || chunk.heightMap.empty()) { + LOG("Skipping chunk %u (sceneMap: %zu, colorMapRes: %u, heightMap: %zu)", + chunk.id, chunk.sceneMap.size(), chunk.colorMapResolution, chunk.heightMap.size()); + continue; + } + + LOG("Processing chunk %u: width=%u, height=%u, colorMapRes=%u, sceneMapSize=%zu", + chunk.id, chunk.width, chunk.height, chunk.colorMapResolution, chunk.sceneMap.size()); + + // Generate vertices for this chunk + for (uint32_t i = 0; i < chunk.width; ++i) { + for (uint32_t j = 0; j < chunk.height; ++j) { + // Get height at this position + const uint32_t heightIndex = chunk.width * i + j; + if (heightIndex >= chunk.heightMap.size()) continue; + + const float y = chunk.heightMap[heightIndex]; + + // Calculate world position + // Based on RawFile::GenerateFinalMeshFromChunks in dTerrain: + // tempVert.SetX(tempVert.GetX() + (chunk->m_X / chunk->m_HeightMap->m_ScaleFactor)); + // tempVert.SetY(tempVert.GetY() / chunk->m_HeightMap->m_ScaleFactor); + // tempVert.SetZ(tempVert.GetZ() + (chunk->m_Z / chunk->m_HeightMap->m_ScaleFactor)); + // tempVert *= chunk->m_HeightMap->m_ScaleFactor; + + float worldX = (static_cast(i) + (chunk.offsetWorldX / chunk.scaleFactor)) * chunk.scaleFactor; + float worldY = (y / chunk.scaleFactor) * chunk.scaleFactor; + float worldZ = (static_cast(j) + (chunk.offsetWorldZ / chunk.scaleFactor)) * chunk.scaleFactor; + + NiPoint3 worldPos(worldX, worldY, worldZ); + + // Get scene ID at this position + // Map heightmap position to scene map position + // The scene map is colorMapResolution x colorMapResolution + // We need to map from heightmap coordinates (i, j) to scene map coordinates + const float sceneMapI = (static_cast(i) / static_cast(chunk.width - 1)) * static_cast(chunk.colorMapResolution - 1); + const float sceneMapJ = (static_cast(j) / static_cast(chunk.height - 1)) * static_cast(chunk.colorMapResolution - 1); + + const uint32_t sceneI = std::min(static_cast(sceneMapI), chunk.colorMapResolution - 1); + const uint32_t sceneJ = std::min(static_cast(sceneMapJ), chunk.colorMapResolution - 1); + // Scene map uses the same indexing pattern as heightmap: row * width + col + const uint32_t sceneIndex = sceneI * chunk.colorMapResolution + sceneJ; + + uint8_t sceneID = 0; + if (sceneIndex < chunk.sceneMap.size()) { + sceneID = chunk.sceneMap[sceneIndex]; + } outMesh.vertices.emplace_back(worldPos, sceneID); + + // Generate triangles (same pattern as dTerrain) + 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; + } + + // Create instance of SceneColor for color lookup + SceneColor sceneColor; + + // Write vertices with colors + // OBJ format supports vertex colors as: v x y z r g b + for (const auto& v : mesh.vertices) { + file << "v " << v.position.x << ' ' << v.position.y << ' ' << v.position.z; + + uint8_t sceneID = v.sceneID; + + const NiColor& color = sceneColor.Get(sceneID); + file << ' ' << color.m_Red << ' ' << color.m_Green << ' ' << color.m_Blue; + file << '\n'; + } + + // Write faces (triangles) + for (size_t i = 0; i < mesh.triangles.size(); i += 3) { + // OBJ indices are 1-based + 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 \ No newline at end of file diff --git a/resources/worldconfig.ini b/resources/worldconfig.ini index 38350086..a31b53d8 100644 --- a/resources/worldconfig.ini +++ b/resources/worldconfig.ini @@ -102,3 +102,7 @@ hardcore_disabled_worlds= # Keeps this percentage of a players' coins on death in hardcore hardcore_coin_keep= + +# Export terrain meshes to OBJ files when zones load +# OBJ files will be saved as terrain_.obj in the server directory +export_terrain_to_obj=0