feat: add eSceneType enum, filter scene graph to general scenes, zone parsing improvements

- Add eSceneType enum (General, Audio) replacing raw uint32_t in SceneRef
- Filter BuildSceneGraph to only include General scenes
- Skip transitions referencing non-general scenes in adjacency graph
- Rename SceneRef unknown fields to scenePosition/sceneRadius
- Zone parsing and Level improvements

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Aaron Kimbrell
2026-06-21 11:38:47 -05:00
parent 1aeede3cd1
commit 7dd918d894
7 changed files with 386 additions and 112 deletions

View File

@@ -107,7 +107,7 @@ void Level::ReadChunks(std::istream& file) {
uint32_t initPos = uint32_t(file.tellg());
uint32_t header = 0;
BinaryIO::BinaryRead(file, header);
if (header == CHNK_HEADER) { //Make sure we're reading a valid CHNK
if (header == CHNK_HEADER) {
Header header;
BinaryIO::BinaryRead(file, header.id);
BinaryIO::BinaryRead(file, header.chunkVersion);
@@ -118,83 +118,39 @@ void Level::ReadChunks(std::istream& file) {
uint32_t target = initPos + header.size;
file.seekg(header.startPosition);
//We're currently not loading env or particle data
if (header.id == ChunkTypeID::FileInfo) {
ReadFileInfoChunk(file, header);
} else if (header.id == ChunkTypeID::SceneEnviroment) {
ReadEnvironmentChunk(file, header);
} else if (header.id == ChunkTypeID::SceneObjectData) {
ReadSceneObjectDataChunk(file, header);
} else if (header.id == ChunkTypeID::SceneParticleData) {
ReadParticleChunk(file, header);
}
m_ChunkHeaders.insert(std::make_pair(header.id, header));
file.seekg(target);
} else {
if (initPos == std::streamoff(0)) { //Really old chunk version
if (initPos == std::streamoff(0)) {
// Old LVL format without CHNK headers — environment + objects inline
file.seekg(0);
Header header;
header.id = ChunkTypeID::FileInfo;
BinaryIO::BinaryRead(file, header.chunkVersion);
BinaryIO::BinaryRead(file, header.chunkType);
uint8_t important = 0;
BinaryIO::BinaryRead(file, important);
// file.ignore(1); //probably used
uint8_t hasEditorData = 0;
BinaryIO::BinaryRead(file, hasEditorData);
if (header.chunkVersion > 36) {
BinaryIO::BinaryRead(file, header.fileInfo.revision);
}
// HARDCODED 3
if (header.chunkVersion >= 45) file.ignore(4);
file.ignore(4 * (4 * 3));
if (header.chunkVersion >= 31) {
if (header.chunkVersion >= 39) {
file.ignore(12 * 4);
if (header.chunkVersion >= 40) {
uint32_t s = 0;
BinaryIO::BinaryRead(file, s);
for (uint32_t i = 0; i < s; ++i) {
file.ignore(4 * 3); //a uint and two floats
}
}
} else {
file.ignore(8);
}
file.ignore(3 * 4);
}
if (header.chunkVersion >= 36) {
file.ignore(3 * 4);
}
if (header.chunkVersion < 42) {
file.ignore(3 * 4);
if (header.chunkVersion >= 33) {
file.ignore(4 * 4);
}
}
// skydome info
uint32_t count = 0;
BinaryIO::BinaryRead(file, count);
file.ignore(count);
if (header.chunkVersion >= 33) {
for (uint32_t i = 0; i < 5; ++i) {
uint32_t count = 0;
BinaryIO::BinaryRead(file, count);
file.ignore(count);
}
}
// editor settings
if (!important && header.chunkVersion >= 37){
file.ignore(4);
uint32_t count = 0;
BinaryIO::BinaryRead(file, count);
file.ignore(count * 12);
// Read environment data inline (no absolute offsets in old format)
ReadLighting(file, header.chunkVersion);
ReadSkydome(file, header.chunkVersion);
if (!hasEditorData && header.chunkVersion >= 37) {
ReadEditor(file);
}
m_HasEnvironment = true;
header.id = ChunkTypeID::SceneObjectData;
header.fileInfo.version = header.chunkVersion;
@@ -213,6 +169,157 @@ void Level::ReadFileInfoChunk(std::istream& file, Header& header) {
BinaryIO::BinaryRead(file, header.fileInfo.particleChunkStart);
}
void Level::ReadLighting(std::istream& file, uint32_t version) {
auto& li = m_Environment.lighting;
if (version >= 45) BinaryIO::BinaryRead(file, li.blendTime);
for (float& f : li.ambient) BinaryIO::BinaryRead(file, f);
for (float& f : li.specular) BinaryIO::BinaryRead(file, f);
for (float& f : li.upperHemi) BinaryIO::BinaryRead(file, f);
BinaryIO::BinaryRead(file, li.position);
if (version >= 39) {
li.hasDrawDistances = true;
auto readDD = [&](LvlDrawDistances& dd) {
BinaryIO::BinaryRead(file, dd.fogNear);
BinaryIO::BinaryRead(file, dd.fogFar);
BinaryIO::BinaryRead(file, dd.postFogSolid);
BinaryIO::BinaryRead(file, dd.postFogFade);
BinaryIO::BinaryRead(file, dd.staticObjDistance);
BinaryIO::BinaryRead(file, dd.dynamicObjDistance);
};
readDD(li.minDraw);
readDD(li.maxDraw);
}
if (version >= 40) {
uint32_t numCull = 0;
BinaryIO::BinaryRead(file, numCull);
li.cullVals.reserve(numCull);
for (uint32_t i = 0; i < numCull; ++i) {
LvlCullVal cv;
BinaryIO::BinaryRead(file, cv.groupID);
BinaryIO::BinaryRead(file, cv.min);
BinaryIO::BinaryRead(file, cv.max);
li.cullVals.push_back(cv);
}
}
if (version >= 31 && version < 39) {
BinaryIO::BinaryRead(file, li.fogNear);
BinaryIO::BinaryRead(file, li.fogFar);
}
if (version >= 31) {
for (float& f : li.fogColor) BinaryIO::BinaryRead(file, f);
}
if (version >= 36) {
for (float& f : li.dirLight) BinaryIO::BinaryRead(file, f);
}
if (version < 42) {
li.hasSpawn = true;
BinaryIO::BinaryRead(file, li.startPosition);
if (version >= 33) {
BinaryIO::BinaryRead(file, li.startRotation.w);
BinaryIO::BinaryRead(file, li.startRotation.x);
BinaryIO::BinaryRead(file, li.startRotation.y);
BinaryIO::BinaryRead(file, li.startRotation.z);
}
}
}
void Level::ReadSkydome(std::istream& file, uint32_t version) {
auto& si = m_Environment.skydome;
BinaryIO::ReadString<uint32_t>(file, si.filename, BinaryIO::ReadType::String);
if (version >= 34) {
BinaryIO::ReadString<uint32_t>(file, si.skyLayerFilename, BinaryIO::ReadType::String);
for (auto& rl : si.ringLayer) {
BinaryIO::ReadString<uint32_t>(file, rl, BinaryIO::ReadType::String);
}
}
}
void Level::ReadEditor(std::istream& file) {
auto& ed = m_Environment.editor;
uint32_t blockSize = 0;
BinaryIO::BinaryRead(file, blockSize);
uint32_t numColors = 0;
BinaryIO::BinaryRead(file, numColors);
ed.savedColors.reserve(numColors);
for (uint32_t i = 0; i < numColors; ++i) {
LvlEditorColor c;
BinaryIO::BinaryRead(file, c.r);
BinaryIO::BinaryRead(file, c.g);
BinaryIO::BinaryRead(file, c.b);
ed.savedColors.push_back(c);
}
m_Environment.hasEditor = true;
}
void Level::ReadEnvironmentChunk(std::istream& file, Header& header) {
uint32_t version = 0;
// Find the version from the fib chunk if we've already read it
auto fibIt = m_ChunkHeaders.find(ChunkTypeID::FileInfo);
if (fibIt != m_ChunkHeaders.end()) {
version = fibIt->second.fileInfo.version;
}
// Environment chunk payload: 3 absolute u32 offsets
uint32_t ofsLighting, ofsSkydome, ofsEditor;
BinaryIO::BinaryRead(file, ofsLighting);
BinaryIO::BinaryRead(file, ofsSkydome);
BinaryIO::BinaryRead(file, ofsEditor);
if (ofsLighting > 0) {
file.seekg(ofsLighting);
ReadLighting(file, version);
}
if (ofsSkydome > 0) {
file.seekg(ofsSkydome);
ReadSkydome(file, version);
}
if (version >= 37 && ofsEditor > 0) {
file.seekg(ofsEditor);
ReadEditor(file);
}
m_HasEnvironment = true;
}
void Level::ReadParticleChunk(std::istream& file, Header& header) {
uint32_t version = 0;
auto fibIt = m_ChunkHeaders.find(ChunkTypeID::FileInfo);
if (fibIt != m_ChunkHeaders.end()) {
version = fibIt->second.fileInfo.version;
}
uint32_t count = 0;
BinaryIO::BinaryRead(file, count);
m_Particles.reserve(count);
for (uint32_t i = 0; i < count; ++i) {
LvlParticle p;
if (version >= 43) BinaryIO::BinaryRead(file, p.priority);
BinaryIO::BinaryRead(file, p.position);
BinaryIO::BinaryRead(file, p.rotation.w);
BinaryIO::BinaryRead(file, p.rotation.x);
BinaryIO::BinaryRead(file, p.rotation.y);
BinaryIO::BinaryRead(file, p.rotation.z);
// effect_names: u4_wstr
BinaryIO::ReadString<uint32_t>(file, p.effectNames, BinaryIO::ReadType::WideString);
// null terminator (version < 46)
if (version < 46) {
uint16_t null_term;
BinaryIO::BinaryRead(file, null_term);
}
// config: u4_wstr parsed as LDF
std::string configStr;
BinaryIO::ReadString<uint32_t>(file, configStr, BinaryIO::ReadType::WideString);
for (const auto& token : GeneralUtils::SplitString(configStr, '\n')) {
p.config.ParseInsert(token);
}
m_Particles.push_back(std::move(p));
}
}
void Level::ReadSceneObjectDataChunk(std::istream& file, Header& header) {
uint32_t objectsCount = 0;
BinaryIO::BinaryRead(file, objectsCount);
@@ -236,9 +343,9 @@ void Level::ReadSceneObjectDataChunk(std::istream& file, Header& header) {
BinaryIO::BinaryRead(file, obj.lot);
if (header.fileInfo.version >= 38) {
int32_t tmp = 1;
BinaryIO::BinaryRead(file, tmp);
if (tmp > -1 && tmp < 11) obj.nodeType = tmp;
uint32_t nodeType;
BinaryIO::BinaryRead(file, nodeType);
obj.nodeType = (nodeType <= 15) ? nodeType : 1;
}
if (header.fileInfo.version >= 32) {
@@ -249,7 +356,29 @@ void Level::ReadSceneObjectDataChunk(std::istream& file, Header& header) {
BinaryIO::BinaryRead(file, obj.rotation);
BinaryIO::BinaryRead(file, obj.scale);
BinaryIO::ReadString<uint32_t>(file, ldfString);
BinaryIO::BinaryRead(file, obj.value3);
if (header.fileInfo.version >= 7) {
uint32_t numRenderAttrs = 0;
BinaryIO::BinaryRead(file, numRenderAttrs);
if (numRenderAttrs > 0) {
char nameBuf[64]{};
file.read(nameBuf, 64);
obj.renderTechnique.name.assign(nameBuf, strnlen(nameBuf, 64));
obj.renderTechnique.attrs.resize(numRenderAttrs);
for (uint32_t a = 0; a < numRenderAttrs; ++a) {
auto& attr = obj.renderTechnique.attrs[a];
char attrName[64]{};
file.read(attrName, 64);
attr.name.assign(attrName, strnlen(attrName, 64));
BinaryIO::BinaryRead(file, attr.numFloats);
uint8_t isColor = 0;
BinaryIO::BinaryRead(file, isColor);
attr.isColor = isColor != 0;
for (float& f : attr.values) BinaryIO::BinaryRead(file, f);
}
}
}
//This is a little bit of a bodge, but because the alpha client (HF) doesn't store the
//spawn position / rotation like the later versions do, we need to check the LOT for the spawn pos & set it.

View File

@@ -4,6 +4,67 @@
#include <iostream>
#include "Zone.h"
struct LvlDrawDistances {
float fogNear{};
float fogFar{};
float postFogSolid{};
float postFogFade{};
float staticObjDistance{};
float dynamicObjDistance{};
};
struct LvlCullVal {
uint32_t groupID{};
float min{};
float max{};
};
struct LvlLightingInfo {
float blendTime{};
float ambient[3]{};
float specular[3]{};
float upperHemi[3]{};
NiPoint3 position;
bool hasDrawDistances{};
LvlDrawDistances minDraw;
LvlDrawDistances maxDraw;
std::vector<LvlCullVal> cullVals;
float fogNear{};
float fogFar{};
float fogColor[3]{};
float dirLight[3]{};
NiPoint3 startPosition;
NiQuaternion startRotation = QuatUtils::IDENTITY;
bool hasSpawn{};
};
struct LvlSkydomeInfo {
std::string filename;
std::string skyLayerFilename;
std::string ringLayer[4];
};
struct LvlEditorColor { float r{}, g{}, b{}; };
struct LvlEditorSettings {
std::vector<LvlEditorColor> savedColors;
};
struct LvlEnvironmentData {
LvlLightingInfo lighting;
LvlSkydomeInfo skydome;
LvlEditorSettings editor;
bool hasEditor{};
};
struct LvlParticle {
uint16_t priority{};
NiPoint3 position;
NiQuaternion rotation = QuatUtils::IDENTITY;
std::string effectNames;
LwoNameValue config;
};
class Level {
public:
enum ChunkTypeID : uint16_t {
@@ -43,11 +104,19 @@ public:
static void MakeSpawner(const SceneObject& obj);
std::map<uint32_t, Header> m_ChunkHeaders;
LvlEnvironmentData m_Environment;
bool m_HasEnvironment{};
std::vector<LvlParticle> m_Particles;
private:
Zone* m_ParentZone;
//private functions:
void ReadChunks(std::istream& file);
void ReadFileInfoChunk(std::istream& file, Header& header);
void ReadEnvironmentChunk(std::istream& file, Header& header);
void ReadLighting(std::istream& file, uint32_t version);
void ReadSkydome(std::istream& file, uint32_t version);
void ReadEditor(std::istream& file);
void ReadSceneObjectDataChunk(std::istream& file, Header& header);
void ReadParticleChunk(std::istream& file, Header& header);
};

View File

@@ -102,9 +102,6 @@ struct Chunk {
std::vector<uint16_t> meshVertSize;
std::vector<MeshTri> meshTri;
// Unknown data for version < 32
std::vector<uint8_t> unknown1;
std::vector<uint8_t> unknown2;
};
/**

View File

@@ -85,11 +85,29 @@ void Zone::LoadZoneIntoMemory() {
LoadScene(file);
}
//Read generic zone info:
BinaryIO::ReadString<uint8_t>(file, m_ZonePath, BinaryIO::ReadType::String);
// Zone boundary lines
uint8_t numBoundaries = 0;
BinaryIO::BinaryRead(file, numBoundaries);
m_Boundaries.reserve(numBoundaries);
for (uint8_t i = 0; i < numBoundaries; ++i) {
ZoneBoundary boundary;
BinaryIO::BinaryRead(file, boundary.normal);
BinaryIO::BinaryRead(file, boundary.point);
uint32_t packed;
BinaryIO::BinaryRead(file, packed);
boundary.destMapID = static_cast<uint16_t>(packed & 0xFFFF);
boundary.destInstanceID = static_cast<uint16_t>((packed >> 16) & 0xFFFF);
BinaryIO::BinaryRead(file, boundary.destSceneID);
BinaryIO::BinaryRead(file, boundary.spawnLocation);
m_Boundaries.push_back(boundary);
}
// Zone info strings
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);
if (m_FileFormatVersion > Zone::FileFormatVersion::PrePreAlpha) {
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)) {
@@ -271,14 +289,15 @@ 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) {
@@ -385,11 +404,39 @@ 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;
}
static void ReadLdfConfig(std::istream& file, PathWaypoint& waypoint, PathType pathType) {
uint32_t count;
BinaryIO::BinaryRead(file, count);
for (uint32_t i = 0; i < count; ++i) {
std::string parameter;
BinaryIO::ReadString<uint8_t>(file, parameter, BinaryIO::ReadType::WideString);
std::string value;
BinaryIO::ReadString<uint8_t>(file, value, BinaryIO::ReadType::WideString);
if (pathType == PathType::Movement || pathType == PathType::Rail) {
parameter.erase(std::remove_if(parameter.begin(), parameter.end(), ::isspace), parameter.end());
auto waypointCommand = WaypointCommandType::StringToWaypointCommandType(parameter);
if (waypointCommand == eWaypointCommandType::DELAY) value.erase(std::remove_if(value.begin(), value.end(), ::isspace), value.end());
if (waypointCommand != eWaypointCommandType::INVALID) {
auto& command = waypoint.commands.emplace_back();
command.command = waypointCommand;
command.data = value;
} else LOG("Tried to load invalid waypoint command '%s'", parameter.c_str());
} else {
waypoint.config.ParseInsert(parameter + "=" + value);
}
}
}
void Zone::LoadPath(std::istream& file) {
Path path = Path();
@@ -397,6 +444,34 @@ void Zone::LoadPath(std::istream& file) {
BinaryIO::ReadString<uint8_t>(file, path.pathName, BinaryIO::ReadType::WideString);
if (path.pathVersion <= 2) {
// Legacy format: type_name string present, no behavior field,
// unified waypoint format (pos + rot + lock + speed + wait + config).
std::string typeName;
BinaryIO::ReadString<uint8_t>(file, typeName, BinaryIO::ReadType::WideString);
BinaryIO::BinaryRead(file, path.pathType);
BinaryIO::BinaryRead(file, path.flags);
BinaryIO::BinaryRead(file, path.waypointCount);
path.pathWaypoints.reserve(path.waypointCount);
for (uint32_t i = 0; i < path.waypointCount; ++i) {
PathWaypoint waypoint = PathWaypoint();
BinaryIO::BinaryRead(file, waypoint.position);
BinaryIO::BinaryRead(file, waypoint.rotation.w);
BinaryIO::BinaryRead(file, waypoint.rotation.x);
BinaryIO::BinaryRead(file, waypoint.rotation.y);
BinaryIO::BinaryRead(file, waypoint.rotation.z);
BinaryIO::BinaryRead(file, waypoint.movingPlatform.lockPlayer);
BinaryIO::BinaryRead(file, waypoint.speed);
BinaryIO::BinaryRead(file, waypoint.movingPlatform.wait);
ReadLdfConfig(file, waypoint, path.pathType);
path.pathWaypoints.push_back(waypoint);
}
m_Paths.push_back(path);
return;
}
BinaryIO::BinaryRead(file, path.pathType);
BinaryIO::BinaryRead(file, path.flags);
BinaryIO::BinaryRead(file, path.pathBehavior);
@@ -435,7 +510,6 @@ void Zone::LoadPath(std::istream& file) {
BinaryIO::ReadString<uint8_t>(file, path.camera.nextPath, BinaryIO::ReadType::WideString);
if (path.pathVersion >= 14) {
BinaryIO::BinaryRead(file, path.camera.rotatePlayer);
}
} else if (path.pathType == PathType::Spawner) {
BinaryIO::BinaryRead(file, path.spawner.spawnedLOT);
@@ -443,7 +517,9 @@ void Zone::LoadPath(std::istream& file) {
BinaryIO::BinaryRead(file, path.spawner.maxToSpawn);
BinaryIO::BinaryRead(file, path.spawner.amountMaintained);
BinaryIO::BinaryRead(file, path.spawner.spawnerObjID);
BinaryIO::BinaryRead(file, path.spawner.spawnerNetActive);
if (path.pathVersion >= 9) {
BinaryIO::BinaryRead(file, path.spawner.spawnerNetActive);
}
}
// Read waypoints
@@ -457,7 +533,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);
@@ -491,37 +566,11 @@ void Zone::LoadPath(std::istream& file) {
// object LDF configs
if (path.pathType == PathType::Movement || path.pathType == PathType::Spawner || path.pathType == PathType::Rail) {
uint32_t count;
BinaryIO::BinaryRead(file, count);
for (uint32_t i = 0; i < count; ++i) {
std::string parameter;
BinaryIO::ReadString<uint8_t>(file, parameter, BinaryIO::ReadType::WideString);
std::string value;
BinaryIO::ReadString<uint8_t>(file, value, BinaryIO::ReadType::WideString);
if (path.pathType == PathType::Movement || path.pathType == PathType::Rail) {
// cause NetDevil puts spaces in things that don't need spaces
parameter.erase(std::remove_if(parameter.begin(), parameter.end(), ::isspace), parameter.end());
auto waypointCommand = WaypointCommandType::StringToWaypointCommandType(parameter);
if (waypointCommand == eWaypointCommandType::DELAY) value.erase(std::remove_if(value.begin(), value.end(), ::isspace), value.end());
if (waypointCommand != eWaypointCommandType::INVALID) {
auto& command = waypoint.commands.emplace_back();
command.command = waypointCommand;
command.data = value;
} else LOG("Tried to load invalid waypoint command '%s'", parameter.c_str());
} else {
waypoint.config.ParseInsert(parameter + "=" + value);
}
}
ReadLdfConfig(file, waypoint, path.pathType);
}
// We verify the waypoint heights against the navmesh because in many movement paths,
// the waypoint is located near 0 height,
if (path.pathType == PathType::Movement) {
if (dpWorld::IsLoaded()) {
// 2000 should be large enough for every world.
waypoint.position.y = dpWorld::GetNavMesh()->GetHeightAtPoint(waypoint.position, 2000.0f);
}
}

View File

@@ -22,13 +22,18 @@ struct WaypointCommand {
};
enum class eSceneType : uint32_t {
General = 0,
Audio = 1,
};
struct SceneRef {
std::string filename;
uint32_t id{};
uint32_t sceneType{}; //0 = general, 1 = audio?
eSceneType sceneType{};
std::string name;
NiPoint3 unknown1;
float unknown2{};
NiPoint3 scenePosition; // version 33 only: editor bounding sphere center
float sceneRadius{}; // version 33 only: editor bounding sphere radius
uint8_t color_r{};
uint8_t color_g{};
uint8_t color_b{};
@@ -36,8 +41,17 @@ struct SceneRef {
std::map<uint32_t, LUTriggers::Trigger*> triggers;
};
struct ZoneBoundary {
NiPoint3 normal;
NiPoint3 point;
uint16_t destMapID{};
uint16_t destInstanceID{};
uint32_t destSceneID{};
NiPoint3 spawnLocation;
};
struct SceneTransitionInfo {
uint64_t sceneID{}; //id of the scene being transitioned to.
LWOSCENEID sceneID;
NiPoint3 position;
};
@@ -232,6 +246,7 @@ public:
const Raw::Raw& GetZoneRaw() const { return m_Raw; }
const Raw::TerrainMesh& GetTerrainMesh() const { return m_TerrainMesh; }
const SceneRef* GetScene(LWOSCENEID sceneID) const;
const std::vector<ZoneBoundary>& GetBoundaries() const { return m_Boundaries; }
const std::vector<SceneTransition>& GetSceneTransitions() const { return m_SceneTransitions; }
const std::map<LWOSCENEID, SceneRef>& GetScenes() const { return m_Scenes; }
@@ -255,6 +270,7 @@ private:
Raw::TerrainMesh m_TerrainMesh; // Pre-generated terrain mesh for fast scene lookups
std::map<LWOSCENEID, SceneRef> m_Scenes;
std::vector<ZoneBoundary> m_Boundaries;
std::vector<SceneTransition> m_SceneTransitions;
uint32_t m_PathDataLength;

View File

@@ -4,6 +4,20 @@
#include "NiQuaternion.h"
#include "LDFFormat.h"
#include <vector>
#include <string>
#include <array>
struct RenderAttr {
std::string name;
uint32_t numFloats{};
bool isColor{};
std::array<float, 4> values{};
};
struct RenderTechnique {
std::string name;
std::vector<RenderAttr> attrs;
};
struct SceneObject {
LWOOBJID id;
@@ -13,7 +27,7 @@ struct SceneObject {
NiPoint3 position;
NiQuaternion rotation = QuatUtils::IDENTITY;
float scale = 1.0f;
uint32_t value3;
RenderTechnique renderTechnique;
LwoNameValue settings;
};

View File

@@ -368,7 +368,7 @@ void dZoneManager::BuildSceneGraph() {
// Initialize adjacency list with all scenes
const auto& scenes = m_pZone->GetScenes();
for (const auto& [sceneID, sceneRef] : scenes) {
// Ensure every scene has an entry, even if it has no transitions
if (sceneRef.sceneType != eSceneType::General) continue;
m_SceneAdjacencyList.try_emplace(sceneID, std::vector<LWOSCENEID>());
}
@@ -381,7 +381,7 @@ void dZoneManager::BuildSceneGraph() {
// Group transition points by their scene IDs to find unique connections
std::set<LWOSCENEID> connectedScenes;
for (const auto& point : transition.points) {
if (point.sceneID != LWOSCENEID_INVALID) {
if (point.sceneID != LWOSCENEID_INVALID && m_SceneAdjacencyList.contains(point.sceneID)) {
connectedScenes.insert(point.sceneID);
}
}