Correct scene making, merged the old raw into the new.

added option to automatically write the raw obj file
Added scene colors to the obj
use proper scene colors from hf
This commit is contained in:
Aaron Kimbrell
2025-10-15 19:56:46 -05:00
parent 12fd9d0a21
commit accdb4f9a1
19 changed files with 720 additions and 314 deletions

34
dCommon/NiColor.h Normal file
View File

@@ -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
* <http://www.inforamp.net/~poynton/> // 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

View File

@@ -0,0 +1,170 @@
#ifndef SCENE_COLOR_H
#define SCENE_COLOR_H
#include "NiColor.h"
#include <vector>
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<NiColor> TEMPLATE_COLORS;
};
#endif // SCENE_COLOR_H

View File

@@ -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

View File

@@ -1,6 +1,5 @@
#include "dNavMesh.h"
#include "RawFile.h"
#include "Game.h"
#include "Logger.h"

View File

@@ -1,3 +0,0 @@
set(DNAVIGATIONS_DTERRAIN_SOURCES "RawFile.cpp"
"RawChunk.cpp"
"RawHeightMap.cpp" PARENT_SCOPE)

View File

@@ -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<uint32_t>(stream.tellg()) + (colorMapSize * colorMapSize * 4));
uint32_t lightmapSize;
BinaryIO::BinaryRead(stream, lightmapSize);
stream.seekg(static_cast<uint32_t>(stream.tellg()) + (lightmapSize));
uint32_t colorMapSize2;
BinaryIO::BinaryRead(stream, colorMapSize2);
stream.seekg(static_cast<uint32_t>(stream.tellg()) + (colorMapSize2 * colorMapSize2 * 4));
uint8_t unknown;
BinaryIO::BinaryRead(stream, unknown);
uint32_t blendmapSize;
BinaryIO::BinaryRead(stream, blendmapSize);
stream.seekg(static_cast<uint32_t>(stream.tellg()) + (blendmapSize));
uint32_t pointSize;
BinaryIO::BinaryRead(stream, pointSize);
stream.seekg(static_cast<uint32_t>(stream.tellg()) + (pointSize * 9 * 4));
stream.seekg(static_cast<uint32_t>(stream.tellg()) + (colorMapSize * colorMapSize));
uint32_t endCounter;
BinaryIO::BinaryRead(stream, endCounter);
stream.seekg(static_cast<uint32_t>(stream.tellg()) + (endCounter * 2));
if (endCounter != 0) {
stream.seekg(static_cast<uint32_t>(stream.tellg()) + (32));
for (int i = 0; i < 0x10; i++) {
uint16_t finalCountdown;
BinaryIO::BinaryRead(stream, finalCountdown);
stream.seekg(static_cast<uint32_t>(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;
}

View File

@@ -1,24 +0,0 @@
#pragma once
#include <cstdint>
#include <fstream>
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;
};

View File

@@ -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';
}
}

View File

@@ -1,28 +0,0 @@
#pragma once
#include <string>
#include <vector>
#include <cstdint>
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<RawChunk*> m_Chunks;
RawMesh* m_FinalMesh = nullptr;
};

View File

@@ -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() {
}

View File

@@ -1,21 +0,0 @@
#pragma once
#include <cstdint>
#include <vector>
#include <fstream>
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<float> m_FloatMap = {};
};

View File

@@ -1,10 +0,0 @@
#pragma once
#include <vector>
#include "NiPoint3.h"
struct RawMesh {
std::vector<NiPoint3> m_Vertices;
std::vector<uint32_t> m_Triangles;
};

View File

@@ -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

View File

@@ -1,7 +1,9 @@
#include "Raw.h"
#include "BinaryIO.h"
#include "Logger.h"
#include "SceneColor.h"
#include <fstream>
#include <algorithm>
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<float>(i) / static_cast<float>(chunk.width - 1)) * static_cast<float>(chunk.colorMapResolution - 1);
const float sceneMapZ = (static_cast<float>(j) / static_cast<float>(chunk.height - 1)) * static_cast<float>(chunk.colorMapResolution - 1);
const uint32_t sceneX = std::min(static_cast<uint32_t>(sceneMapX), chunk.colorMapResolution - 1);
const uint32_t sceneZ = std::min(static_cast<uint32_t>(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<float>(i) / static_cast<float>(chunk.width - 1)) * static_cast<float>(chunk.colorMapResolution - 1);
const float sceneMapJ = (static_cast<float>(j) / static_cast<float>(chunk.height - 1)) * static_cast<float>(chunk.colorMapResolution - 1);
const uint32_t sceneI = std::min(static_cast<uint32_t>(sceneMapI), chunk.colorMapResolution - 1);
const uint32_t sceneJ = std::min(static_cast<uint32_t>(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

View File

@@ -7,7 +7,9 @@
#include <vector>
#include <string>
#include <istream>
#include <map>
#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__

View File

@@ -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) {

View File

@@ -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<LWOSCENEID> dZoneManager::GetAdjacentScenes(LWOSCENEID sceneID) const {

View File

@@ -0,0 +1,403 @@
#include "Raw.h"
#include "BinaryIO.h"
#include "Logger.h"
#include "SceneColor.h"
#include <fstream>
#include <algorithm>
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<char*>(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<char*>(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<char*>(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<char*>(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<char*>(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<float>(i) + (chunk.offsetWorldX / chunk.scaleFactor)) * chunk.scaleFactor;
float worldY = (y / chunk.scaleFactor) * chunk.scaleFactor;
float worldZ = (static_cast<float>(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<float>(i) / static_cast<float>(chunk.width - 1)) * static_cast<float>(chunk.colorMapResolution - 1);
const float sceneMapJ = (static_cast<float>(j) / static_cast<float>(chunk.height - 1)) * static_cast<float>(chunk.colorMapResolution - 1);
const uint32_t sceneI = std::min(static_cast<uint32_t>(sceneMapI), chunk.colorMapResolution - 1);
const uint32_t sceneJ = std::min(static_cast<uint32_t>(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

View File

@@ -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_<zoneID>.obj in the server directory
export_terrain_to_obj=0