mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-11-06 11:57:13 +00:00
fix: look up the scene at a position as the client does, in constant time
Chunk bounds span (width - 1) * scale; the nearest scene map cell is used and the far edge is kept one cell inside. Chunks are indexed by grid cell when they form a grid. Skipping reads instead of seeking, so a cut-off file can't seek past the end. Terrain mesh indices follow the vertex order. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -15,7 +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}/dCommon/dClient" # SceneColor.h
|
||||
"${PROJECT_SOURCE_DIR}/dGame/dComponents" #InventoryComponent.h
|
||||
"${PROJECT_SOURCE_DIR}/dGame/dInventory" #InventoryComponent.h (transitive)
|
||||
"${PROJECT_SOURCE_DIR}/dGame/dBehaviors" #BehaviorSlot.h
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <fstream>
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <cmath>
|
||||
|
||||
namespace {
|
||||
constexpr uint32_t kMaxResolution = 4096;
|
||||
@@ -19,25 +20,39 @@ bool Chunk::IsValidForSceneLookup() const {
|
||||
&& scaleFactor > 0.0f && width > 1 && height > 1;
|
||||
}
|
||||
|
||||
uint8_t Chunk::GetSceneIDAtGrid(uint32_t i, uint32_t j) const {
|
||||
const float sceneMapI = (static_cast<float>(i) / static_cast<float>(width - 1)) * static_cast<float>(colorMapResolution - 1);
|
||||
const float sceneMapJ = (static_cast<float>(j) / static_cast<float>(height - 1)) * static_cast<float>(colorMapResolution - 1);
|
||||
const uint32_t sceneI = std::min(static_cast<uint32_t>(sceneMapI), colorMapResolution - 1);
|
||||
const uint32_t sceneJ = std::min(static_cast<uint32_t>(sceneMapJ), colorMapResolution - 1);
|
||||
const uint32_t sceneIndex = sceneI * colorMapResolution + sceneJ;
|
||||
uint8_t Chunk::GetSceneIDAtLocal(float localX, float localZ) const {
|
||||
// The nearest scene map cell
|
||||
const auto toCell = [this](float cell) {
|
||||
return static_cast<uint32_t>(std::clamp(static_cast<int32_t>(std::floor(cell + 0.5f)), 0, static_cast<int32_t>(colorMapResolution) - 1));
|
||||
};
|
||||
const uint32_t sceneIndex = toCell(sceneCellsPerUnitX * localX) * colorMapResolution + toCell(sceneCellsPerUnitZ * localZ);
|
||||
if (sceneIndex >= sceneMap.size()) return 0;
|
||||
return sceneMap[sceneIndex];
|
||||
}
|
||||
|
||||
NiPoint3 Chunk::GridToWorldPos(uint32_t i, uint32_t j) const {
|
||||
const float y = (i * width + j < heightMap.size()) ? heightMap[i * width + j] : 0.0f;
|
||||
return NiPoint3(
|
||||
(static_cast<float>(i) + (offsetX / scaleFactor)) * scaleFactor,
|
||||
y,
|
||||
(static_cast<float>(j) + (offsetZ / scaleFactor)) * scaleFactor
|
||||
);
|
||||
uint8_t Chunk::GetSceneIDAtGrid(uint32_t i, uint32_t j) const {
|
||||
return GetSceneIDAtLocal(static_cast<float>(i) * scaleFactor, static_cast<float>(j) * scaleFactor);
|
||||
}
|
||||
|
||||
NiPoint3 Chunk::GridToWorldPos(uint32_t i, uint32_t j) const {
|
||||
const uint32_t heightIndex = i * height + j;
|
||||
const float y = heightIndex < heightMap.size() ? heightMap[heightIndex] : 0.0f;
|
||||
return NiPoint3(offsetX + static_cast<float>(i) * scaleFactor, y, offsetZ + static_cast<float>(j) * scaleFactor);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Skip bytes, such as the byte version 30 and older files have in place of a scene map
|
||||
*/
|
||||
static bool Skip(std::istream& stream, size_t bytes) {
|
||||
if (bytes > kMaxBlobBytes) {
|
||||
LOG("Raw block of %zu bytes exceeds maximum %zu bytes", bytes, kMaxBlobBytes);
|
||||
return false;
|
||||
}
|
||||
// ignore, not seekg: the asset streams don't stop a seek at the end of the data
|
||||
stream.ignore(static_cast<std::streamsize>(bytes));
|
||||
return static_cast<size_t>(stream.gcount()) == bytes && !stream.fail();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Read flair attributes from stream
|
||||
*/
|
||||
@@ -80,7 +95,7 @@ NiPoint3 Chunk::GridToWorldPos(uint32_t i, uint32_t j) const {
|
||||
/**
|
||||
* @brief Read a chunk from stream
|
||||
*/
|
||||
static bool ReadChunk(std::istream& stream, Chunk& chunk, uint16_t version) {
|
||||
static bool ReadChunkData(std::istream& stream, Chunk& chunk, uint16_t version) {
|
||||
try {
|
||||
// Read basic chunk info
|
||||
BinaryIO::BinaryRead(stream, chunk.id);
|
||||
@@ -294,8 +309,8 @@ NiPoint3 Chunk::GridToWorldPos(uint32_t i, uint32_t j) const {
|
||||
chunk.sceneMap[row * chunk.colorMapResolution + col] = rawSceneMap[row * (chunk.colorMapResolution + 1) + col];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
stream.seekg(1, std::ios::cur);
|
||||
} else if (!Skip(stream, 1)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (stream.fail()) {
|
||||
@@ -353,6 +368,59 @@ NiPoint3 Chunk::GridToWorldPos(uint32_t i, uint32_t j) const {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Read a chunk and work out its scene map cell sizes
|
||||
*/
|
||||
static bool ReadChunk(std::istream& stream, Chunk& chunk, uint16_t version) {
|
||||
if (!ReadChunkData(stream, chunk, version)) return false;
|
||||
|
||||
if (chunk.IsValidForSceneLookup()) {
|
||||
chunk.sceneCellSizeX = static_cast<float>(chunk.width - 1) * chunk.scaleFactor / static_cast<float>(chunk.colorMapResolution);
|
||||
chunk.sceneCellSizeZ = static_cast<float>(chunk.height - 1) * chunk.scaleFactor / static_cast<float>(chunk.colorMapResolution);
|
||||
chunk.sceneCellsPerUnitX = 1.0f / chunk.sceneCellSizeX;
|
||||
chunk.sceneCellsPerUnitZ = 1.0f / chunk.sceneCellSizeZ;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Index the chunks by grid cell when they form a regular grid of equal chunks
|
||||
*/
|
||||
static void BuildChunkGrid(Raw& raw) {
|
||||
raw.chunkGrid.clear();
|
||||
const auto& first = raw.chunks.front();
|
||||
const float spanX = first.GetMaxX() - first.offsetX;
|
||||
const float spanZ = first.GetMaxZ() - first.offsetZ;
|
||||
if (spanX <= 0.0f || spanZ <= 0.0f) return;
|
||||
|
||||
const auto countX = static_cast<uint32_t>(std::lround((raw.maxBoundsX - raw.minBoundsX) / spanX));
|
||||
const auto countZ = static_cast<uint32_t>(std::lround((raw.maxBoundsZ - raw.minBoundsZ) / spanZ));
|
||||
if (countX == 0 || countZ == 0 || static_cast<size_t>(countX) * countZ > kMaxChunks) return;
|
||||
|
||||
std::vector<int32_t> grid(static_cast<size_t>(countX) * countZ, -1);
|
||||
for (size_t i = 0; i < raw.chunks.size(); ++i) {
|
||||
const auto& chunk = raw.chunks[i];
|
||||
const float cellX = (chunk.offsetX - raw.minBoundsX) / spanX;
|
||||
const float cellZ = (chunk.offsetZ - raw.minBoundsZ) / spanZ;
|
||||
const long x = std::lround(cellX);
|
||||
const long z = std::lround(cellZ);
|
||||
const bool sameSize = chunk.width == first.width && chunk.height == first.height && chunk.scaleFactor == first.scaleFactor;
|
||||
if (!sameSize || std::abs(cellX - x) > 0.01f || std::abs(cellZ - z) > 0.01f || x < 0 || z < 0
|
||||
|| x >= static_cast<long>(countX) || z >= static_cast<long>(countZ)) {
|
||||
LOG("Raw chunks are not a regular grid (chunk %u); scene lookups check every chunk", chunk.id);
|
||||
return;
|
||||
}
|
||||
auto& cell = grid[static_cast<size_t>(x) * countZ + z];
|
||||
if (cell == -1) cell = static_cast<int32_t>(i); // the client takes the first chunk that matches
|
||||
}
|
||||
|
||||
raw.chunkGrid = std::move(grid);
|
||||
raw.chunkGridCountX = countX;
|
||||
raw.chunkGridCountZ = countZ;
|
||||
raw.chunkSpanX = spanX;
|
||||
raw.chunkSpanZ = spanZ;
|
||||
}
|
||||
|
||||
bool ReadRaw(std::istream& stream, Raw& outRaw) {
|
||||
// Get stream size
|
||||
stream.seekg(0, std::ios::end);
|
||||
@@ -404,18 +472,14 @@ NiPoint3 Chunk::GridToWorldPos(uint32_t i, uint32_t j) const {
|
||||
outRaw.maxBoundsZ = std::numeric_limits<float>::lowest();
|
||||
|
||||
for (const auto& chunk : outRaw.chunks) {
|
||||
const float chunkMinX = chunk.offsetX;
|
||||
const float chunkMinZ = chunk.offsetZ;
|
||||
const float chunkMaxX = chunkMinX + (chunk.width * chunk.scaleFactor);
|
||||
const float chunkMaxZ = chunkMinZ + (chunk.height * chunk.scaleFactor);
|
||||
|
||||
outRaw.minBoundsX = std::min(outRaw.minBoundsX, chunkMinX);
|
||||
outRaw.minBoundsZ = std::min(outRaw.minBoundsZ, chunkMinZ);
|
||||
outRaw.maxBoundsX = std::max(outRaw.maxBoundsX, chunkMaxX);
|
||||
outRaw.maxBoundsZ = std::max(outRaw.maxBoundsZ, chunkMaxZ);
|
||||
outRaw.minBoundsX = std::min(outRaw.minBoundsX, chunk.offsetX);
|
||||
outRaw.minBoundsZ = std::min(outRaw.minBoundsZ, chunk.offsetZ);
|
||||
outRaw.maxBoundsX = std::max(outRaw.maxBoundsX, chunk.GetMaxX());
|
||||
outRaw.maxBoundsZ = std::max(outRaw.maxBoundsZ, chunk.GetMaxZ());
|
||||
}
|
||||
LOG("Raw terrain bounds: X[%.2f, %.2f], Z[%.2f, %.2f]",
|
||||
LOG_DEBUG("Raw terrain bounds: X[%.2f, %.2f], Z[%.2f, %.2f]",
|
||||
outRaw.minBoundsX, outRaw.maxBoundsX, outRaw.minBoundsZ, outRaw.maxBoundsZ);
|
||||
BuildChunkGrid(outRaw);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
@@ -424,6 +488,54 @@ NiPoint3 Chunk::GridToWorldPos(uint32_t i, uint32_t j) const {
|
||||
}
|
||||
}
|
||||
|
||||
// The client's TerrainManager::GetSceneAtPos: clamp to the terrain bounds, keep one scene cell inside the far edge,
|
||||
// find the chunk containing the position (edges moved in by 0.001) and take its nearest scene map cell
|
||||
std::optional<uint8_t> Raw::GetSceneIDAt(float x, float z) const {
|
||||
if (chunks.empty()) return std::nullopt;
|
||||
|
||||
constexpr float kEdgeEpsilon = 0.001f;
|
||||
const float posX = std::clamp(x, minBoundsX, maxBoundsX);
|
||||
const float posZ = std::clamp(z, minBoundsZ, maxBoundsZ);
|
||||
|
||||
const auto sceneInChunk = [&](const Chunk& chunk) -> std::optional<uint8_t> {
|
||||
if (!chunk.IsValidForSceneLookup()) return std::nullopt;
|
||||
const float chunkX = std::min(posX, maxBoundsX - chunk.sceneCellSizeX);
|
||||
const float chunkZ = std::min(posZ, maxBoundsZ - chunk.sceneCellSizeZ);
|
||||
if (chunk.offsetX - kEdgeEpsilon <= chunkX && chunkX < chunk.GetMaxX() - kEdgeEpsilon &&
|
||||
chunk.offsetZ - kEdgeEpsilon <= chunkZ && chunkZ < chunk.GetMaxZ() - kEdgeEpsilon) {
|
||||
return chunk.GetSceneIDAtLocal(chunkX - chunk.offsetX, chunkZ - chunk.offsetZ);
|
||||
}
|
||||
return std::nullopt;
|
||||
};
|
||||
|
||||
if (chunkGrid.empty()) {
|
||||
for (const auto& chunk : chunks) {
|
||||
if (const auto sceneID = sceneInChunk(chunk)) return sceneID;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// The grid cell the position falls in, checked with the client's test; for a position on a float edge the
|
||||
// neighbouring cells are checked too
|
||||
const auto toCell = [](float pos, float min, float span, uint32_t count) {
|
||||
const auto cell = static_cast<int32_t>(std::floor((pos + kEdgeEpsilon - min) / span));
|
||||
return std::clamp(cell, 0, static_cast<int32_t>(count) - 1);
|
||||
};
|
||||
const int32_t cellX = toCell(posX, minBoundsX, chunkSpanX, chunkGridCountX);
|
||||
const int32_t cellZ = toCell(posZ, minBoundsZ, chunkSpanZ, chunkGridCountZ);
|
||||
for (const int32_t offsetX : { 0, -1, 1 }) {
|
||||
for (const int32_t offsetZ : { 0, -1, 1 }) {
|
||||
const int32_t x = cellX + offsetX;
|
||||
const int32_t z = cellZ + offsetZ;
|
||||
if (x < 0 || z < 0 || x >= static_cast<int32_t>(chunkGridCountX) || z >= static_cast<int32_t>(chunkGridCountZ)) continue;
|
||||
const int32_t index = chunkGrid[static_cast<size_t>(x) * chunkGridCountZ + z];
|
||||
if (index < 0) continue;
|
||||
if (const auto sceneID = sceneInChunk(chunks[index])) return sceneID;
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void GenerateTerrainMesh(const Raw& raw, TerrainMesh& outMesh) {
|
||||
outMesh.vertices.clear();
|
||||
outMesh.triangles.clear();
|
||||
@@ -432,24 +544,22 @@ NiPoint3 Chunk::GridToWorldPos(uint32_t i, uint32_t j) const {
|
||||
return; // No scene data available
|
||||
}
|
||||
|
||||
LOG("GenerateTerrainMesh: Processing %zu chunks", raw.chunks.size());
|
||||
LOG_DEBUG("GenerateTerrainMesh: Processing %zu chunks", raw.chunks.size());
|
||||
|
||||
uint32_t vertexOffset = 0;
|
||||
|
||||
for (const auto& chunk : raw.chunks) {
|
||||
if (!chunk.IsValidForSceneLookup()) continue;
|
||||
|
||||
// One vertex per heightmap point, in [i * height + j] order
|
||||
for (uint32_t i = 0; i < chunk.width; ++i) {
|
||||
for (uint32_t j = 0; j < chunk.height; ++j) {
|
||||
const uint32_t heightIndex = chunk.width * i + j;
|
||||
if (heightIndex >= chunk.heightMap.size()) continue;
|
||||
|
||||
outMesh.vertices.emplace_back(chunk.GridToWorldPos(i, j), chunk.GetSceneIDAtGrid(i, j));
|
||||
if (i > 0 && j > 0) {
|
||||
const uint32_t currentVert = vertexOffset + chunk.width * i + j;
|
||||
const uint32_t currentVert = vertexOffset + chunk.height * 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;
|
||||
const uint32_t bottomLeftVert = vertexOffset + chunk.height * (i - 1) + j - 1;
|
||||
const uint32_t bottomVert = vertexOffset + chunk.height * (i - 1) + j;
|
||||
|
||||
// First triangle
|
||||
outMesh.triangles.push_back(currentVert);
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#define __RAW_H__
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <istream>
|
||||
@@ -61,7 +62,7 @@ struct TerrainMesh {
|
||||
|
||||
/**
|
||||
* @brief Terrain chunk structure
|
||||
* Represents a single chunk of terrain with heightmap, textures, and meshes
|
||||
* Represents a single chunk of terrain with heightmap, textures, flair, scene map and meshes
|
||||
*/
|
||||
struct Chunk {
|
||||
uint32_t id;
|
||||
@@ -69,40 +70,50 @@ struct Chunk {
|
||||
uint32_t height;
|
||||
float offsetX;
|
||||
float offsetZ;
|
||||
uint32_t shaderId;
|
||||
|
||||
uint32_t shaderId = 0; // version < 32 only
|
||||
|
||||
// Texture IDs (4 textures per chunk)
|
||||
std::vector<uint32_t> textureIds;
|
||||
|
||||
// Terrain scale factor
|
||||
|
||||
// Terrain scale factor (world units between two heightmap points)
|
||||
float scaleFactor;
|
||||
|
||||
|
||||
// Heightmap data (width * height floats)
|
||||
std::vector<float> heightMap;
|
||||
|
||||
// Version 32+ fields
|
||||
|
||||
// Color map: colorMapResolution^2 RGBA pixels (version < 32: width^2 pixels, resolution width - 1)
|
||||
uint32_t colorMapResolution = 0;
|
||||
std::vector<uint8_t> colorMap; // RGBA pixels (colorMap * colorMap * 4)
|
||||
std::vector<uint8_t> lightMap;
|
||||
|
||||
std::vector<uint8_t> colorMap;
|
||||
std::vector<uint8_t> lightMap; // DDS, version 32+
|
||||
|
||||
uint32_t textureMapResolution = 0;
|
||||
std::vector<uint8_t> textureMap; // (textureMapResolution * textureMapResolution * 4)
|
||||
uint8_t textureSettings = 0;
|
||||
std::vector<uint8_t> blendMap;
|
||||
|
||||
std::vector<uint8_t> textureMap; // textureMapResolution^2 RGBA pixels
|
||||
uint8_t textureSettings = 0; // version 32+
|
||||
std::vector<uint8_t> blendMap; // DDS, version 32+
|
||||
|
||||
// Flair data
|
||||
std::vector<FlairAttributes> flairs;
|
||||
|
||||
// Scene map (version 32+)
|
||||
|
||||
// The scene map is colorMapResolution * colorMapResolution scene IDs, indexed [x cell * resolution + z cell]
|
||||
std::vector<uint8_t> sceneMap;
|
||||
|
||||
// Mesh data
|
||||
|
||||
// Mesh data (version 32+)
|
||||
uint32_t vertSize = 0;
|
||||
std::vector<uint16_t> meshVertUsage;
|
||||
std::vector<uint16_t> meshVertSize;
|
||||
std::vector<MeshTri> meshTri;
|
||||
|
||||
// Scene map cells per world unit, and the size of one cell, along X and Z (set once the chunk is read)
|
||||
float sceneCellsPerUnitX = 0.0f;
|
||||
float sceneCellsPerUnitZ = 0.0f;
|
||||
float sceneCellSizeX = 0.0f;
|
||||
float sceneCellSizeZ = 0.0f;
|
||||
|
||||
bool IsValidForSceneLookup() const;
|
||||
float GetMaxX() const { return offsetX + static_cast<float>(width - 1) * scaleFactor; }
|
||||
float GetMaxZ() const { return offsetZ + static_cast<float>(height - 1) * scaleFactor; }
|
||||
// The scene ID at a position relative to the chunk's offset, as the client looks it up
|
||||
uint8_t GetSceneIDAtLocal(float localX, float localZ) const;
|
||||
uint8_t GetSceneIDAtGrid(uint32_t i, uint32_t j) const;
|
||||
NiPoint3 GridToWorldPos(uint32_t i, uint32_t j) const;
|
||||
};
|
||||
@@ -112,8 +123,8 @@ struct Chunk {
|
||||
* Complete representation of a .raw terrain file
|
||||
*/
|
||||
struct Raw {
|
||||
uint16_t version;
|
||||
uint8_t dev;
|
||||
uint16_t version = 0;
|
||||
uint8_t dev = 0;
|
||||
uint32_t numChunks = 0;
|
||||
uint32_t numChunksWidth = 0;
|
||||
uint32_t numChunksHeight = 0;
|
||||
@@ -124,6 +135,23 @@ struct Raw {
|
||||
float minBoundsZ = 0.0f;
|
||||
float maxBoundsX = 0.0f;
|
||||
float maxBoundsZ = 0.0f;
|
||||
|
||||
// Chunk lookup by position: the chunks of every client terrain form a regular grid, written X-major
|
||||
// ([x * chunks along Z + z]). chunkGrid holds the index into chunks per grid cell (-1 for none); it is empty when
|
||||
// the chunks don't form a grid, and lookups then check every chunk.
|
||||
std::vector<int32_t> chunkGrid;
|
||||
uint32_t chunkGridCountX = 0;
|
||||
uint32_t chunkGridCountZ = 0;
|
||||
float chunkSpanX = 0.0f;
|
||||
float chunkSpanZ = 0.0f;
|
||||
|
||||
/**
|
||||
* @brief The scene ID at a world position, looked up as the client's TerrainManager::GetSceneAtPos does
|
||||
* (height is ignored). Constant time on a chunk grid.
|
||||
*
|
||||
* @return The scene ID, or nullopt when there is no terrain with a scene map there
|
||||
*/
|
||||
std::optional<uint8_t> GetSceneIDAt(float x, float z) const;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
#include "CDZoneTableTable.h"
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
#include <set>
|
||||
#include "eObjectBits.h"
|
||||
#include "AssetManager.h"
|
||||
@@ -307,36 +306,8 @@ void dZoneManager::LoadWorldConfig() {
|
||||
LWOSCENEID dZoneManager::GetSceneIDFromPosition(const NiPoint3& position) const {
|
||||
if (!m_pZone) return LWOSCENEID_INVALID;
|
||||
|
||||
const auto& raw = m_pZone->GetZoneRaw();
|
||||
|
||||
// If no chunks, no scene data available
|
||||
if (raw.chunks.empty()) {
|
||||
return LWOSCENEID_INVALID;
|
||||
}
|
||||
|
||||
// Convert 3D position to 2D (XZ plane) and clamp to terrain bounds
|
||||
float posX = std::clamp(position.x, raw.minBoundsX, raw.maxBoundsX);
|
||||
float posZ = std::clamp(position.z, raw.minBoundsZ, raw.maxBoundsZ);
|
||||
|
||||
// Find the chunk containing this position
|
||||
// Reverse the world position calculation from GenerateTerrainMesh
|
||||
for (const auto& chunk : raw.chunks) {
|
||||
if (!chunk.IsValidForSceneLookup()) continue;
|
||||
|
||||
const float heightI = posX / chunk.scaleFactor - (chunk.offsetX / chunk.scaleFactor);
|
||||
const float heightJ = posZ / chunk.scaleFactor - (chunk.offsetZ / chunk.scaleFactor);
|
||||
|
||||
if (heightI >= 0.0f && heightI < static_cast<float>(chunk.width) &&
|
||||
heightJ >= 0.0f && heightJ < static_cast<float>(chunk.height)) {
|
||||
|
||||
const uint32_t gridI = std::min(static_cast<uint32_t>(heightI), chunk.width - 1);
|
||||
const uint32_t gridJ = std::min(static_cast<uint32_t>(heightJ), chunk.height - 1);
|
||||
return LWOSCENEID(chunk.GetSceneIDAtGrid(gridI, gridJ), 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Position not found in any chunk
|
||||
return LWOSCENEID_INVALID;
|
||||
const auto sceneID = m_pZone->GetZoneRaw().GetSceneIDAt(position.x, position.z);
|
||||
return sceneID ? LWOSCENEID(*sceneID, 0) : LWOSCENEID_INVALID;
|
||||
}
|
||||
|
||||
void dZoneManager::BuildSceneGraph() {
|
||||
|
||||
@@ -77,6 +77,11 @@ public:
|
||||
*/
|
||||
std::vector<LWOSCENEID> GetAdjacentScenes(LWOSCENEID sceneID) const;
|
||||
|
||||
/**
|
||||
* @brief (Re)builds the scene graph adjacency list from the loaded zone's scene transitions.
|
||||
*/
|
||||
void BuildSceneGraph();
|
||||
|
||||
// The world config should not be modified by a caller.
|
||||
const WorldConfig& GetWorldConfig() {
|
||||
if (!m_WorldConfig) LoadWorldConfig();
|
||||
@@ -84,10 +89,6 @@ public:
|
||||
};
|
||||
|
||||
private:
|
||||
/**
|
||||
* Builds the scene graph adjacency list from scene transitions
|
||||
*/
|
||||
void BuildSceneGraph();
|
||||
/**
|
||||
* The starting unique mission ID.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user