feat: raw terrain parsing for scene data

Replace old dNavigation/dTerrain raw parser with new Raw module in
dZoneManager. Parse heightmaps, color maps, and scene maps from .raw
files to determine which scene a position belongs to. Build scene
adjacency graph from terrain data and scene transitions.

Adds NiColor type, SceneColor lookup table, eSceneType enum, terrain
mesh generation with OBJ export, and debug slash commands for scene
visualization.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Aaron Kimbrell
2026-06-24 18:31:28 -05:00
parent 35337291fa
commit feeaf339d4
23 changed files with 1404 additions and 316 deletions

View File

@@ -8,6 +8,7 @@
#include "GeneralUtils.h"
#include "BinaryIO.h"
#include "LUTriggers.h"
#include "dConfig.h"
#include "AssetManager.h"
#include "CDClientManager.h"
@@ -20,6 +21,7 @@
#include "eTriggerEventType.h"
#include "eWaypointCommandType.h"
#include "dNavMesh.h"
#include "Raw.h"
Zone::Zone(const LWOZONEID zoneID) :
m_ZoneID(zoneID) {
@@ -78,12 +80,51 @@ void Zone::LoadZoneIntoMemory() {
LoadScene(file);
}
//Read generic zone info:
BinaryIO::ReadString<uint8_t>(file, m_ZonePath, BinaryIO::ReadType::String);
// Zone boundary lines — skip past them for correct file positioning
uint8_t numBoundaries = 0;
BinaryIO::BinaryRead(file, numBoundaries);
for (uint8_t i = 0; i < numBoundaries; ++i) {
NiPoint3 normal, point, spawnLocation;
uint32_t packed, destSceneID;
BinaryIO::BinaryRead(file, normal);
BinaryIO::BinaryRead(file, point);
BinaryIO::BinaryRead(file, packed);
BinaryIO::BinaryRead(file, destSceneID);
BinaryIO::BinaryRead(file, spawnLocation);
}
BinaryIO::ReadString<uint8_t>(file, m_ZoneRawPath, BinaryIO::ReadType::String);
BinaryIO::ReadString<uint8_t>(file, m_ZoneName, BinaryIO::ReadType::String);
BinaryIO::ReadString<uint8_t>(file, m_ZoneDesc, BinaryIO::ReadType::String);
auto zoneFolderPath = m_ZoneFilePath.substr(0, m_ZoneFilePath.rfind('/') + 1);
if (!Game::assetManager->HasFile(zoneFolderPath + m_ZoneRawPath)) {
LOG("Failed to find %s", (zoneFolderPath + m_ZoneRawPath).c_str());
throw std::runtime_error("Aborting Zone loading due to no Zone Raw File.");
}
auto rawFile = Game::assetManager->GetFile(zoneFolderPath + m_ZoneRawPath);
if (!Raw::ReadRaw(rawFile, m_Raw)) {
LOG("Failed to parse %s", (zoneFolderPath + m_ZoneRawPath).c_str());
throw std::runtime_error("Aborting Zone loading due to invalid Raw File.");
}
LOG("Loaded Raw Terrain with %u chunks", m_Raw.numChunks);
// Optionally export terrain mesh to OBJ for debugging/visualization
if (Game::config->GetValue("export_terrain_to_obj") == "1") {
// Generate terrain mesh
Raw::GenerateTerrainMesh(m_Raw, m_TerrainMesh);
LOG("Generated terrain mesh with %zu vertices and %zu triangles", m_TerrainMesh.vertices.size(), m_TerrainMesh.triangles.size() / 3);
// Write to OBJ
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) {
@@ -236,20 +277,23 @@ void Zone::LoadScene(std::istream& file) {
}
if (m_FileFormatVersion >= Zone::FileFormatVersion::LatePreAlpha) {
BinaryIO::BinaryRead(file, scene.sceneType);
lwoSceneID.SetLayerID(scene.sceneType);
lwoSceneID.SetLayerID(static_cast<uint32_t>(scene.sceneType));
BinaryIO::ReadString<uint8_t>(file, scene.name, BinaryIO::ReadType::String);
}
if (m_FileFormatVersion == Zone::FileFormatVersion::LatePreAlpha) {
BinaryIO::BinaryRead(file, scene.unknown1);
BinaryIO::BinaryRead(file, scene.unknown2);
BinaryIO::BinaryRead(file, scene.scenePosition);
BinaryIO::BinaryRead(file, scene.sceneRadius);
}
if (m_FileFormatVersion >= Zone::FileFormatVersion::LatePreAlpha) {
BinaryIO::BinaryRead(file, scene.color_r);
BinaryIO::BinaryRead(file, scene.color_b);
BinaryIO::BinaryRead(file, scene.color_g);
uint8_t r, b, g;
BinaryIO::BinaryRead(file, r);
BinaryIO::BinaryRead(file, b);
BinaryIO::BinaryRead(file, g);
scene.color = NiColor(r / 255.0f, g / 255.0f, b / 255.0f);
}
m_Scenes[lwoSceneID] = std::move(scene);
@@ -350,7 +394,10 @@ void Zone::LoadSceneTransition(std::istream& file) {
SceneTransitionInfo Zone::LoadSceneTransitionInfo(std::istream& file) {
SceneTransitionInfo info;
BinaryIO::BinaryRead(file, info.sceneID);
uint32_t sceneID, layerID;
BinaryIO::BinaryRead(file, sceneID);
BinaryIO::BinaryRead(file, layerID);
info.sceneID = LWOSCENEID(static_cast<int32_t>(sceneID), layerID);
BinaryIO::BinaryRead(file, info.position);
return info;
}
@@ -422,7 +469,6 @@ void Zone::LoadPath(std::istream& file) {
BinaryIO::BinaryRead(file, waypoint.position.y);
BinaryIO::BinaryRead(file, waypoint.position.z);
if (path.pathType == PathType::Spawner || path.pathType == PathType::MovingPlatform || path.pathType == PathType::Race || path.pathType == PathType::Camera || path.pathType == PathType::Rail) {
BinaryIO::BinaryRead(file, waypoint.rotation.w);
BinaryIO::BinaryRead(file, waypoint.rotation.x);
@@ -483,7 +529,7 @@ void Zone::LoadPath(std::istream& file) {
}
// We verify the waypoint heights against the navmesh because in many movement paths,
// the waypoint is located near 0 height,
// the waypoint is located near 0 height,
if (path.pathType == PathType::Movement) {
if (dpWorld::IsLoaded()) {
// 2000 should be large enough for every world.
@@ -494,3 +540,9 @@ void Zone::LoadPath(std::istream& file) {
}
m_Paths.push_back(path);
}
const SceneRef* Zone::GetScene(LWOSCENEID sceneID) const {
auto it = m_Scenes.find(sceneID);
if (it != m_Scenes.end()) return &it->second;
return nullptr;
}