mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2025-12-11 18:48:26 +00:00
Impl raw reading, and some slash commands to test with scenes
This commit is contained in:
@@ -817,6 +817,42 @@ void SlashCommandHandler::Startup() {
|
|||||||
};
|
};
|
||||||
RegisterCommand(ExecuteCommand);
|
RegisterCommand(ExecuteCommand);
|
||||||
|
|
||||||
|
Command GetSceneCommand{
|
||||||
|
.help = "Get the current scene ID and name at your position",
|
||||||
|
.info = "Displays the scene ID and name at the player's current position. Scenes do not care about height.",
|
||||||
|
.aliases = { "getscene", "scene" },
|
||||||
|
.handle = DEVGMCommands::GetScene,
|
||||||
|
.requiredLevel = eGameMasterLevel::DEVELOPER
|
||||||
|
};
|
||||||
|
RegisterCommand(GetSceneCommand);
|
||||||
|
|
||||||
|
Command GetAdjacentScenesCommand{
|
||||||
|
.help = "Get all scenes adjacent to your current scene",
|
||||||
|
.info = "Displays all scenes that are directly connected to the player's current scene via scene transitions.",
|
||||||
|
.aliases = { "getadjacentscenes", "adjacentscenes" },
|
||||||
|
.handle = DEVGMCommands::GetAdjacentScenes,
|
||||||
|
.requiredLevel = eGameMasterLevel::DEVELOPER
|
||||||
|
};
|
||||||
|
RegisterCommand(GetAdjacentScenesCommand);
|
||||||
|
|
||||||
|
Command SpawnScenePointsCommand{
|
||||||
|
.help = "Spawn bricks at points across your current scene",
|
||||||
|
.info = "Spawns bricks at sampled points across the player's current scene using terrain scene map data.",
|
||||||
|
.aliases = { "spawnscenepoints" },
|
||||||
|
.handle = DEVGMCommands::SpawnScenePoints,
|
||||||
|
.requiredLevel = eGameMasterLevel::DEVELOPER
|
||||||
|
};
|
||||||
|
RegisterCommand(SpawnScenePointsCommand);
|
||||||
|
|
||||||
|
Command SpawnAllScenePointsCommand{
|
||||||
|
.help = "Spawn bricks at ALL vertices in ALL scenes (high density, many entities)",
|
||||||
|
.info = "Spawns bricks at every vertex in the terrain mesh for all scenes in the zone. WARNING: Creates a massive number of entities for maximum accuracy visualization.",
|
||||||
|
.aliases = { "spawnallscenepoints", "spawnallscenes" },
|
||||||
|
.handle = DEVGMCommands::SpawnAllScenePoints,
|
||||||
|
.requiredLevel = eGameMasterLevel::DEVELOPER
|
||||||
|
};
|
||||||
|
RegisterCommand(SpawnAllScenePointsCommand);
|
||||||
|
|
||||||
// Register Greater Than Zero Commands
|
// Register Greater Than Zero Commands
|
||||||
|
|
||||||
Command KickCommand{
|
Command KickCommand{
|
||||||
|
|||||||
@@ -1836,4 +1836,219 @@ namespace DEVGMCommands {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GetScene(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
||||||
|
const auto position = entity->GetPosition();
|
||||||
|
|
||||||
|
// Get the scene ID from the zone manager
|
||||||
|
const auto sceneID = Game::zoneManager->GetSceneIDFromPosition(position);
|
||||||
|
|
||||||
|
if (sceneID == LWOSCENEID_INVALID) {
|
||||||
|
ChatPackets::SendSystemMessage(sysAddr, u"No scene found at current position.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the scene reference from the zone to get the name
|
||||||
|
const auto* zone = Game::zoneManager->GetZone();
|
||||||
|
if (!zone) {
|
||||||
|
ChatPackets::SendSystemMessage(sysAddr, u"No zone loaded.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the feedback message
|
||||||
|
std::ostringstream feedback;
|
||||||
|
feedback << "Scene ID: " << sceneID.GetSceneID();
|
||||||
|
feedback << " (Layer: " << sceneID.GetLayerID() << ")";
|
||||||
|
|
||||||
|
// Get the scene name
|
||||||
|
const auto* sceneRef = zone->GetScene(sceneID);
|
||||||
|
if (sceneRef && !sceneRef->name.empty()) {
|
||||||
|
feedback << " - Name: " << sceneRef->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::ASCIIToUTF16(feedback.str()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void GetAdjacentScenes(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
||||||
|
const auto position = entity->GetPosition();
|
||||||
|
|
||||||
|
// Get the scene ID from the zone manager
|
||||||
|
const auto sceneID = Game::zoneManager->GetSceneIDFromPosition(position);
|
||||||
|
|
||||||
|
if (sceneID == LWOSCENEID_INVALID) {
|
||||||
|
ChatPackets::SendSystemMessage(sysAddr, u"No scene found at current position.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the zone reference
|
||||||
|
const auto* zone = Game::zoneManager->GetZone();
|
||||||
|
if (!zone) {
|
||||||
|
ChatPackets::SendSystemMessage(sysAddr, u"No zone loaded.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get current scene info
|
||||||
|
const auto* currentScene = zone->GetScene(sceneID);
|
||||||
|
std::string currentSceneName = currentScene && !currentScene->name.empty() ? currentScene->name : "Unknown";
|
||||||
|
|
||||||
|
// Get adjacent scenes
|
||||||
|
const auto adjacentSceneIDs = Game::zoneManager->GetAdjacentScenes(sceneID);
|
||||||
|
|
||||||
|
if (adjacentSceneIDs.empty()) {
|
||||||
|
std::ostringstream feedback;
|
||||||
|
feedback << "Current Scene: " << sceneID.GetSceneID() << " (" << currentSceneName << ")";
|
||||||
|
feedback << " - No adjacent scenes found.";
|
||||||
|
ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::ASCIIToUTF16(feedback.str()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the feedback message with current scene
|
||||||
|
std::ostringstream feedback;
|
||||||
|
feedback << "Current Scene: " << sceneID.GetSceneID() << " (" << currentSceneName << ")";
|
||||||
|
ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::ASCIIToUTF16(feedback.str()));
|
||||||
|
|
||||||
|
// List all adjacent scenes
|
||||||
|
feedback.str("");
|
||||||
|
feedback << "Adjacent Scenes (" << adjacentSceneIDs.size() << "):";
|
||||||
|
ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::ASCIIToUTF16(feedback.str()));
|
||||||
|
|
||||||
|
for (const auto& adjSceneID : adjacentSceneIDs) {
|
||||||
|
feedback.str("");
|
||||||
|
feedback << " - Scene ID: " << adjSceneID.GetSceneID();
|
||||||
|
feedback << " (Layer: " << adjSceneID.GetLayerID() << ")";
|
||||||
|
|
||||||
|
// Get the scene name if available
|
||||||
|
const auto* sceneRef = zone->GetScene(adjSceneID);
|
||||||
|
if (sceneRef && !sceneRef->name.empty()) {
|
||||||
|
feedback << " - " << sceneRef->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::ASCIIToUTF16(feedback.str()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpawnScenePoints(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
||||||
|
// Hardcoded to use LOT 33
|
||||||
|
const uint32_t lot = 33;
|
||||||
|
|
||||||
|
// Get player's current position and scene
|
||||||
|
const auto position = entity->GetPosition();
|
||||||
|
const auto currentSceneID = Game::zoneManager->GetSceneIDFromPosition(position);
|
||||||
|
if (currentSceneID == LWOSCENEID_INVALID) {
|
||||||
|
ChatPackets::SendSystemMessage(sysAddr, u"No scene found at current position.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the zone
|
||||||
|
const auto* zone = Game::zoneManager->GetZone();
|
||||||
|
if (!zone) {
|
||||||
|
ChatPackets::SendSystemMessage(sysAddr, u"No zone loaded.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the pre-generated terrain mesh
|
||||||
|
const auto& terrainMesh = zone->GetTerrainMesh();
|
||||||
|
if (terrainMesh.vertices.empty()) {
|
||||||
|
ChatPackets::SendSystemMessage(sysAddr, u"Zone does not have valid terrain mesh data.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spawn at ALL vertices in the current scene without any filtering or spacing
|
||||||
|
uint32_t spawnedCount = 0;
|
||||||
|
|
||||||
|
for (const auto& vertex : terrainMesh.vertices) {
|
||||||
|
// Check if this vertex belongs to the current scene
|
||||||
|
if (LWOSCENEID(vertex.sceneID) == currentSceneID) {
|
||||||
|
// Use the vertex position
|
||||||
|
NiPoint3 spawnPos = vertex.position;
|
||||||
|
|
||||||
|
EntityInfo info;
|
||||||
|
info.lot = lot + currentSceneID.GetSceneID(); // to differentiate scenes
|
||||||
|
info.pos = spawnPos;
|
||||||
|
info.rot = QuatUtils::IDENTITY;
|
||||||
|
info.spawner = nullptr;
|
||||||
|
info.spawnerID = entity->GetObjectID();
|
||||||
|
info.spawnerNodeID = 0;
|
||||||
|
info.settings = { new LDFData<bool>(u"SpawnedFromSlashCommand", true) };
|
||||||
|
|
||||||
|
Entity* newEntity = Game::entityManager->CreateEntity(info, nullptr);
|
||||||
|
if (newEntity != nullptr) {
|
||||||
|
Game::entityManager->ConstructEntity(newEntity);
|
||||||
|
spawnedCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (spawnedCount == 0) {
|
||||||
|
std::ostringstream feedback;
|
||||||
|
feedback << "No spawn points found in current scene (ID: " << currentSceneID.GetSceneID() << ").";
|
||||||
|
ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::ASCIIToUTF16(feedback.str()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send feedback
|
||||||
|
const auto* sceneRef = zone->GetScene(currentSceneID);
|
||||||
|
const std::string sceneName = sceneRef ? sceneRef->name : "Unknown";
|
||||||
|
std::ostringstream feedback;
|
||||||
|
feedback << "Spawned LOT " << lot + currentSceneID.GetSceneID() << " at " << spawnedCount << " points in scene "
|
||||||
|
<< currentSceneID.GetSceneID() << " (" << sceneName << ").";
|
||||||
|
ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::ASCIIToUTF16(feedback.str()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpawnAllScenePoints(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
||||||
|
// Hardcoded to use LOT 33
|
||||||
|
const uint32_t lot = 33;
|
||||||
|
|
||||||
|
// Get the zone
|
||||||
|
const auto* zone = Game::zoneManager->GetZone();
|
||||||
|
if (!zone) {
|
||||||
|
ChatPackets::SendSystemMessage(sysAddr, u"No zone loaded.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the pre-generated terrain mesh
|
||||||
|
const auto& terrainMesh = zone->GetTerrainMesh();
|
||||||
|
if (terrainMesh.vertices.empty()) {
|
||||||
|
ChatPackets::SendSystemMessage(sysAddr, u"Zone does not have valid terrain mesh data.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spawn at ALL vertices without any filtering or spacing restrictions for maximum accuracy
|
||||||
|
uint32_t spawnedCount = 0;
|
||||||
|
std::map<uint8_t, uint32_t> sceneSpawnCounts; // Track spawns per scene
|
||||||
|
|
||||||
|
for (const auto& vertex : terrainMesh.vertices) {
|
||||||
|
// Skip invalid scenes (scene ID 0 typically means no scene)
|
||||||
|
if (vertex.sceneID == 0) continue;
|
||||||
|
EntityInfo info;
|
||||||
|
info.lot = lot + vertex.sceneID; // to show different scenes
|
||||||
|
info.pos = vertex.position;
|
||||||
|
info.rot = QuatUtils::IDENTITY;
|
||||||
|
info.spawner = nullptr;
|
||||||
|
info.spawnerID = entity->GetObjectID();
|
||||||
|
info.spawnerNodeID = 0;
|
||||||
|
info.settings = { new LDFData<bool>(u"SpawnedFromSlashCommand", true) };
|
||||||
|
|
||||||
|
Entity* newEntity = Game::entityManager->CreateEntity(info, nullptr);
|
||||||
|
if (newEntity != nullptr) {
|
||||||
|
Game::entityManager->ConstructEntity(newEntity);
|
||||||
|
spawnedCount++;
|
||||||
|
sceneSpawnCounts[vertex.sceneID]++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send detailed feedback
|
||||||
|
std::ostringstream feedback;
|
||||||
|
feedback << "Spawned LOT " << spawnedCount << " total points across "
|
||||||
|
<< sceneSpawnCounts.size() << " scenes:\n";
|
||||||
|
|
||||||
|
for (const auto& [sceneID, count] : sceneSpawnCounts) {
|
||||||
|
const auto* sceneRef = zone->GetScene(LWOSCENEID(sceneID));
|
||||||
|
const std::string sceneName = sceneRef ? sceneRef->name : "Unknown";
|
||||||
|
feedback << " Scene " << static_cast<int>(sceneID) << ", LOT: " << (lot + sceneID) << " (" << sceneName << "): " << count << " points\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::ASCIIToUTF16(feedback.str()));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -77,6 +77,10 @@ namespace DEVGMCommands {
|
|||||||
void Barfight(Entity* entity, const SystemAddress& sysAddr, const std::string args);
|
void Barfight(Entity* entity, const SystemAddress& sysAddr, const std::string args);
|
||||||
void Despawn(Entity* entity, const SystemAddress& sysAddr, const std::string args);
|
void Despawn(Entity* entity, const SystemAddress& sysAddr, const std::string args);
|
||||||
void Execute(Entity* entity, const SystemAddress& sysAddr, const std::string args);
|
void Execute(Entity* entity, const SystemAddress& sysAddr, const std::string args);
|
||||||
|
void GetScene(Entity* entity, const SystemAddress& sysAddr, const std::string args);
|
||||||
|
void GetAdjacentScenes(Entity* entity, const SystemAddress& sysAddr, const std::string args);
|
||||||
|
void SpawnScenePoints(Entity* entity, const SystemAddress& sysAddr, const std::string args);
|
||||||
|
void SpawnAllScenePoints(Entity* entity, const SystemAddress& sysAddr, const std::string args);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif //!DEVGMCOMMANDS_H
|
#endif //!DEVGMCOMMANDS_H
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
set(DZONEMANAGER_SOURCES "dZoneManager.cpp"
|
set(DZONEMANAGER_SOURCES "dZoneManager.cpp"
|
||||||
"Level.cpp"
|
"Level.cpp"
|
||||||
|
"Raw.cpp"
|
||||||
"Spawner.cpp"
|
"Spawner.cpp"
|
||||||
"Zone.cpp")
|
"Zone.cpp")
|
||||||
|
|
||||||
|
|||||||
353
dZoneManager/Raw.cpp
Normal file
353
dZoneManager/Raw.cpp
Normal file
@@ -0,0 +1,353 @@
|
|||||||
|
#include "Raw.h"
|
||||||
|
#include "BinaryIO.h"
|
||||||
|
#include "Logger.h"
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
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
|
||||||
|
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;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Raw
|
||||||
143
dZoneManager/Raw.h
Normal file
143
dZoneManager/Raw.h
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef __RAW_H__
|
||||||
|
#define __RAW_H__
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
#include <istream>
|
||||||
|
#include "NiPoint3.h"
|
||||||
|
|
||||||
|
namespace Raw {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Flair attributes structure
|
||||||
|
* Represents decorative elements on the terrain
|
||||||
|
*/
|
||||||
|
struct FlairAttributes {
|
||||||
|
uint32_t id;
|
||||||
|
float scaleFactor;
|
||||||
|
NiPoint3 position;
|
||||||
|
NiPoint3 rotation;
|
||||||
|
uint8_t colorR;
|
||||||
|
uint8_t colorG;
|
||||||
|
uint8_t colorB;
|
||||||
|
uint8_t colorA;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Mesh triangle structure
|
||||||
|
* Contains triangle indices for terrain mesh
|
||||||
|
*/
|
||||||
|
struct MeshTri {
|
||||||
|
uint16_t meshTriListSize;
|
||||||
|
std::vector<uint16_t> meshTriList;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Vertex with scene ID
|
||||||
|
* Used for the generated terrain mesh to enable fast scene lookups
|
||||||
|
*/
|
||||||
|
struct SceneVertex {
|
||||||
|
NiPoint3 position;
|
||||||
|
uint8_t sceneID;
|
||||||
|
|
||||||
|
SceneVertex() : position(), sceneID(0) {}
|
||||||
|
SceneVertex(const NiPoint3& pos, uint8_t scene) : position(pos), sceneID(scene) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Generated terrain mesh
|
||||||
|
* Contains vertices with scene IDs for fast scene lookups at arbitrary positions
|
||||||
|
*/
|
||||||
|
struct TerrainMesh {
|
||||||
|
std::vector<SceneVertex> vertices;
|
||||||
|
std::vector<uint32_t> triangles; // Indices into vertices array (groups of 3)
|
||||||
|
|
||||||
|
TerrainMesh() = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Terrain chunk structure
|
||||||
|
* Represents a single chunk of terrain with heightmap, textures, and meshes
|
||||||
|
*/
|
||||||
|
struct Chunk {
|
||||||
|
uint32_t id;
|
||||||
|
uint32_t width;
|
||||||
|
uint32_t height;
|
||||||
|
float offsetWorldX;
|
||||||
|
float offsetWorldZ;
|
||||||
|
uint32_t shaderId;
|
||||||
|
|
||||||
|
// Texture IDs (4 textures per chunk)
|
||||||
|
std::vector<uint32_t> textureIds;
|
||||||
|
|
||||||
|
// Terrain scale factor
|
||||||
|
float scaleFactor;
|
||||||
|
|
||||||
|
// Heightmap data (width * height floats)
|
||||||
|
std::vector<float> heightMap;
|
||||||
|
|
||||||
|
// Version 32+ fields
|
||||||
|
uint32_t colorMapResolution = 0;
|
||||||
|
std::vector<uint8_t> colorMap; // RGBA pixels (colorMap * colorMap * 4)
|
||||||
|
std::vector<uint8_t> lightMap;
|
||||||
|
|
||||||
|
uint32_t textureMapResolution = 0;
|
||||||
|
std::vector<uint8_t> textureMap; // (textureMapResolution * textureMapResolution * 4)
|
||||||
|
uint8_t textureSettings = 0;
|
||||||
|
std::vector<uint8_t> blendMap;
|
||||||
|
|
||||||
|
// Flair data
|
||||||
|
std::vector<FlairAttributes> flairs;
|
||||||
|
|
||||||
|
// Scene map (version 32+)
|
||||||
|
std::vector<uint8_t> sceneMap;
|
||||||
|
|
||||||
|
// Mesh data
|
||||||
|
uint32_t vertSize = 0;
|
||||||
|
std::vector<uint16_t> meshVertUsage;
|
||||||
|
std::vector<uint16_t> meshVertSize;
|
||||||
|
std::vector<MeshTri> meshTri;
|
||||||
|
|
||||||
|
// Unknown data for version < 32
|
||||||
|
std::vector<uint8_t> unknown1;
|
||||||
|
std::vector<uint8_t> unknown2;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief RAW terrain file structure
|
||||||
|
* Complete representation of a .raw terrain file
|
||||||
|
*/
|
||||||
|
struct Raw {
|
||||||
|
uint16_t version;
|
||||||
|
uint8_t dev;
|
||||||
|
uint32_t numChunks = 0;
|
||||||
|
uint32_t numChunksWidth = 0;
|
||||||
|
uint32_t numChunksHeight = 0;
|
||||||
|
std::vector<Chunk> chunks;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Read a RAW terrain file from an input stream
|
||||||
|
*
|
||||||
|
* @param stream Input stream containing RAW file data
|
||||||
|
* @param outRaw Output RAW file structure
|
||||||
|
* @return true if successfully read, false otherwise
|
||||||
|
*/
|
||||||
|
bool ReadRaw(std::istream& stream, Raw& outRaw);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Generate a terrain mesh from raw chunks
|
||||||
|
* Similar to dTerrain's GenerateFinalMeshFromChunks but creates a mesh with scene IDs
|
||||||
|
* per vertex for fast scene lookups at arbitrary positions.
|
||||||
|
*
|
||||||
|
* @param raw The RAW terrain data to generate mesh from
|
||||||
|
* @param outMesh Output terrain mesh with vertices and scene IDs
|
||||||
|
*/
|
||||||
|
void GenerateTerrainMesh(const Raw& raw, TerrainMesh& outMesh);
|
||||||
|
|
||||||
|
} // namespace Raw
|
||||||
|
|
||||||
|
#endif // __RAW_H__
|
||||||
@@ -20,6 +20,7 @@
|
|||||||
#include "eTriggerEventType.h"
|
#include "eTriggerEventType.h"
|
||||||
#include "eWaypointCommandType.h"
|
#include "eWaypointCommandType.h"
|
||||||
#include "dNavMesh.h"
|
#include "dNavMesh.h"
|
||||||
|
#include "Raw.h"
|
||||||
|
|
||||||
Zone::Zone(const LWOZONEID zoneID) :
|
Zone::Zone(const LWOZONEID zoneID) :
|
||||||
m_ZoneID(zoneID) {
|
m_ZoneID(zoneID) {
|
||||||
@@ -84,6 +85,23 @@ void Zone::LoadZoneIntoMemory() {
|
|||||||
BinaryIO::ReadString<uint8_t>(file, m_ZoneName, BinaryIO::ReadType::String);
|
BinaryIO::ReadString<uint8_t>(file, m_ZoneName, BinaryIO::ReadType::String);
|
||||||
BinaryIO::ReadString<uint8_t>(file, m_ZoneDesc, 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.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate terrain mesh for fast scene lookups
|
||||||
|
Raw::GenerateTerrainMesh(m_Raw, m_TerrainMesh);
|
||||||
|
LOG("Generated terrain mesh with %llu vertices and %llu triangles",
|
||||||
|
m_TerrainMesh.vertices.size(), m_TerrainMesh.triangles.size() / 3);
|
||||||
|
|
||||||
if (m_FileFormatVersion >= Zone::FileFormatVersion::PreAlpha) {
|
if (m_FileFormatVersion >= Zone::FileFormatVersion::PreAlpha) {
|
||||||
BinaryIO::BinaryRead(file, m_NumberOfSceneTransitionsLoaded);
|
BinaryIO::BinaryRead(file, m_NumberOfSceneTransitionsLoaded);
|
||||||
for (uint32_t i = 0; i < m_NumberOfSceneTransitionsLoaded; ++i) {
|
for (uint32_t i = 0; i < m_NumberOfSceneTransitionsLoaded; ++i) {
|
||||||
@@ -483,3 +501,9 @@ void Zone::LoadPath(std::istream& file) {
|
|||||||
}
|
}
|
||||||
m_Paths.push_back(path);
|
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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <map>
|
#include <map>
|
||||||
|
#include "Raw.h"
|
||||||
|
|
||||||
namespace LUTriggers {
|
namespace LUTriggers {
|
||||||
struct Trigger;
|
struct Trigger;
|
||||||
@@ -228,6 +229,12 @@ public:
|
|||||||
void SetSpawnPos(const NiPoint3& pos) { m_Spawnpoint = pos; }
|
void SetSpawnPos(const NiPoint3& pos) { m_Spawnpoint = pos; }
|
||||||
void SetSpawnRot(const NiQuaternion& rot) { m_SpawnpointRotation = rot; }
|
void SetSpawnRot(const NiQuaternion& rot) { m_SpawnpointRotation = rot; }
|
||||||
|
|
||||||
|
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<SceneTransition>& GetSceneTransitions() const { return m_SceneTransitions; }
|
||||||
|
const std::map<LWOSCENEID, SceneRef>& GetScenes() const { return m_Scenes; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
LWOZONEID m_ZoneID;
|
LWOZONEID m_ZoneID;
|
||||||
std::string m_ZoneFilePath;
|
std::string m_ZoneFilePath;
|
||||||
@@ -244,6 +251,8 @@ private:
|
|||||||
std::string m_ZoneName; //Name given to the zone by a level designer
|
std::string m_ZoneName; //Name given to the zone by a level designer
|
||||||
std::string m_ZoneDesc; //Description of the zone by a level designer
|
std::string m_ZoneDesc; //Description of the zone by a level designer
|
||||||
std::string m_ZoneRawPath; //Path to the .raw file of this zone.
|
std::string m_ZoneRawPath; //Path to the .raw file of this zone.
|
||||||
|
Raw::Raw m_Raw; // The Raw data for this zone
|
||||||
|
Raw::TerrainMesh m_TerrainMesh; // Pre-generated terrain mesh for fast scene lookups
|
||||||
|
|
||||||
std::map<LWOSCENEID, SceneRef> m_Scenes;
|
std::map<LWOSCENEID, SceneRef> m_Scenes;
|
||||||
std::vector<SceneTransition> m_SceneTransitions;
|
std::vector<SceneTransition> m_SceneTransitions;
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
#include "WorldConfig.h"
|
#include "WorldConfig.h"
|
||||||
#include "CDZoneTableTable.h"
|
#include "CDZoneTableTable.h"
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
#include <cmath>
|
||||||
#include "eObjectBits.h"
|
#include "eObjectBits.h"
|
||||||
#include "CDZoneTableTable.h"
|
#include "CDZoneTableTable.h"
|
||||||
#include "AssetManager.h"
|
#include "AssetManager.h"
|
||||||
@@ -62,6 +63,9 @@ void dZoneManager::Initialize(const LWOZONEID& zoneID) {
|
|||||||
|
|
||||||
m_pZone->Initalize();
|
m_pZone->Initalize();
|
||||||
|
|
||||||
|
// Build the scene graph after zone is loaded
|
||||||
|
BuildSceneGraph();
|
||||||
|
|
||||||
endTime = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now().time_since_epoch()).count();
|
endTime = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now().time_since_epoch()).count();
|
||||||
|
|
||||||
LoadWorldConfig();
|
LoadWorldConfig();
|
||||||
@@ -298,3 +302,109 @@ void dZoneManager::LoadWorldConfig() {
|
|||||||
|
|
||||||
LOG_DEBUG("Loaded WorldConfig into memory");
|
LOG_DEBUG("Loaded WorldConfig into memory");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LWOSCENEID dZoneManager::GetSceneIDFromPosition(const NiPoint3& position) const {
|
||||||
|
if (!m_pZone) return LWOSCENEID_INVALID;
|
||||||
|
|
||||||
|
const auto& terrainMesh = m_pZone->GetTerrainMesh();
|
||||||
|
|
||||||
|
// If mesh is empty, no scene data available
|
||||||
|
if (terrainMesh.vertices.empty() || terrainMesh.triangles.empty()) {
|
||||||
|
return LWOSCENEID_INVALID;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the triangle containing this position (ignoring Y coordinate for scene lookup)
|
||||||
|
// We iterate through all triangles and find the one that contains the point in 2D (XZ plane)
|
||||||
|
for (size_t i = 0; i < terrainMesh.triangles.size(); i += 3) {
|
||||||
|
const auto& v0 = terrainMesh.vertices[terrainMesh.triangles[i]];
|
||||||
|
const auto& v1 = terrainMesh.vertices[terrainMesh.triangles[i + 1]];
|
||||||
|
const auto& v2 = terrainMesh.vertices[terrainMesh.triangles[i + 2]];
|
||||||
|
|
||||||
|
// Check if position is inside this triangle using 2D (XZ) coordinates
|
||||||
|
// Using barycentric coordinates / cross product method
|
||||||
|
const float x = position.x;
|
||||||
|
const float z = position.z;
|
||||||
|
|
||||||
|
const float x0 = v0.position.x;
|
||||||
|
const float z0 = v0.position.z;
|
||||||
|
const float x1 = v1.position.x;
|
||||||
|
const float z1 = v1.position.z;
|
||||||
|
const float x2 = v2.position.x;
|
||||||
|
const float z2 = v2.position.z;
|
||||||
|
|
||||||
|
// Calculate barycentric coordinates
|
||||||
|
const float denom = (z1 - z2) * (x0 - x2) + (x2 - x1) * (z0 - z2);
|
||||||
|
if (std::abs(denom) < 0.0001f) continue; // Degenerate triangle
|
||||||
|
|
||||||
|
const float a = ((z1 - z2) * (x - x2) + (x2 - x1) * (z - z2)) / denom;
|
||||||
|
const float b = ((z2 - z0) * (x - x2) + (x0 - x2) * (z - z2)) / denom;
|
||||||
|
const float c = 1.0f - a - b;
|
||||||
|
|
||||||
|
// Point is inside triangle if all barycentric coordinates are non-negative
|
||||||
|
if (a >= 0.0f && b >= 0.0f && c >= 0.0f) {
|
||||||
|
// Return the scene ID from the first vertex (all vertices in a triangle should have the same scene ID)
|
||||||
|
return LWOSCENEID(v0.sceneID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Position not found in any triangle
|
||||||
|
return LWOSCENEID_INVALID;
|
||||||
|
}
|
||||||
|
|
||||||
|
void dZoneManager::BuildSceneGraph() {
|
||||||
|
if (!m_pZone) return;
|
||||||
|
|
||||||
|
// Clear any existing adjacency list
|
||||||
|
m_SceneAdjacencyList.clear();
|
||||||
|
|
||||||
|
// 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 (m_SceneAdjacencyList.find(sceneID) == m_SceneAdjacencyList.end()) {
|
||||||
|
m_SceneAdjacencyList[sceneID] = std::vector<LWOSCENEID>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build adjacency list from scene transitions
|
||||||
|
const auto& transitions = m_pZone->GetSceneTransitions();
|
||||||
|
for (const auto& transition : transitions) {
|
||||||
|
// Each transition has multiple points, each pointing to a scene
|
||||||
|
// We need to determine which scenes this transition connects
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
connectedScenes.insert(point.sceneID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create bidirectional edges between all scenes in this transition
|
||||||
|
// (transitions typically connect two scenes, but can be more complex)
|
||||||
|
std::vector<LWOSCENEID> sceneList(connectedScenes.begin(), connectedScenes.end());
|
||||||
|
|
||||||
|
for (size_t i = 0; i < sceneList.size(); ++i) {
|
||||||
|
for (size_t j = 0; j < sceneList.size(); ++j) {
|
||||||
|
if (i != j) {
|
||||||
|
LWOSCENEID fromScene = sceneList[i];
|
||||||
|
LWOSCENEID toScene = sceneList[j];
|
||||||
|
|
||||||
|
// Add edge if it doesn't already exist
|
||||||
|
auto& adjacentScenes = m_SceneAdjacencyList[fromScene];
|
||||||
|
if (std::find(adjacentScenes.begin(), adjacentScenes.end(), toScene) == adjacentScenes.end()) {
|
||||||
|
adjacentScenes.push_back(toScene);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<LWOSCENEID> dZoneManager::GetAdjacentScenes(LWOSCENEID sceneID) const {
|
||||||
|
auto it = m_SceneAdjacencyList.find(sceneID);
|
||||||
|
if (it != m_SceneAdjacencyList.end()) {
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
return std::vector<LWOSCENEID>();
|
||||||
|
}
|
||||||
|
|||||||
@@ -53,6 +53,30 @@ public:
|
|||||||
uint32_t GetUniqueMissionIdStartingValue();
|
uint32_t GetUniqueMissionIdStartingValue();
|
||||||
bool CheckIfAccessibleZone(LWOMAPID zoneID);
|
bool CheckIfAccessibleZone(LWOMAPID zoneID);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the scene ID at a given position. Scenes do not care about height (Y coordinate).
|
||||||
|
*
|
||||||
|
* @param position The position to query
|
||||||
|
* @return The scene ID at that position, or LWOSCENEID_INVALID if not found
|
||||||
|
*/
|
||||||
|
LWOSCENEID GetSceneIDFromPosition(const NiPoint3& position) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the adjacency list for the scene graph.
|
||||||
|
* The adjacency list maps each scene ID to a list of scene IDs it can transition to.
|
||||||
|
*
|
||||||
|
* @return A reference to the scene adjacency list
|
||||||
|
*/
|
||||||
|
const std::map<LWOSCENEID, std::vector<LWOSCENEID>>& GetSceneAdjacencyList() const { return m_SceneAdjacencyList; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get all scenes adjacent to (connected to) a given scene.
|
||||||
|
*
|
||||||
|
* @param sceneID The scene ID to query
|
||||||
|
* @return A vector of scene IDs that are directly connected to this scene, or empty vector if scene not found
|
||||||
|
*/
|
||||||
|
std::vector<LWOSCENEID> GetAdjacentScenes(LWOSCENEID sceneID) const;
|
||||||
|
|
||||||
// The world config should not be modified by a caller.
|
// The world config should not be modified by a caller.
|
||||||
const WorldConfig& GetWorldConfig() {
|
const WorldConfig& GetWorldConfig() {
|
||||||
if (!m_WorldConfig) LoadWorldConfig();
|
if (!m_WorldConfig) LoadWorldConfig();
|
||||||
@@ -60,6 +84,10 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
/**
|
||||||
|
* Builds the scene graph adjacency list from scene transitions
|
||||||
|
*/
|
||||||
|
void BuildSceneGraph();
|
||||||
/**
|
/**
|
||||||
* The starting unique mission ID.
|
* The starting unique mission ID.
|
||||||
*/
|
*/
|
||||||
@@ -75,4 +103,9 @@ private:
|
|||||||
std::optional<WorldConfig> m_WorldConfig = std::nullopt;
|
std::optional<WorldConfig> m_WorldConfig = std::nullopt;
|
||||||
|
|
||||||
Entity* m_ZoneControlObject = nullptr;
|
Entity* m_ZoneControlObject = nullptr;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scene graph adjacency list: maps each scene ID to a list of scenes it can transition to
|
||||||
|
*/
|
||||||
|
std::map<LWOSCENEID, std::vector<LWOSCENEID>> m_SceneAdjacencyList;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user