refactor: deduplicate terrain grid lookup logic, remove unnecessary cast

Add IsValidForSceneLookup(), GetSceneIDAtGrid(), and GridToWorldPos()
to Raw::Chunk. Use them in GenerateTerrainMesh, GetSceneIDFromPosition,
SpawnScenePoints, and SpawnAllScenePoints instead of duplicated inline
math. Remove unnecessary static_cast in LoadSceneTransitionInfo.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Aaron Kimbrell
2026-06-24 18:38:23 -05:00
parent feeaf339d4
commit 0eeb5b0285
5 changed files with 58 additions and 124 deletions

View File

@@ -2007,52 +2007,31 @@ namespace DEVGMCommands {
return;
}
// Spawn at all sceneMap points in the current scene
uint32_t spawnedCount = 0;
for (const auto& chunk : raw.chunks) {
if (chunk.sceneMap.empty() || chunk.colorMapResolution == 0 || chunk.heightMap.empty()
|| chunk.width <= 1 || chunk.height <= 1 || chunk.scaleFactor <= 0.0f) continue;
if (!chunk.IsValidForSceneLookup()) continue;
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;
if (i * chunk.width + j >= chunk.heightMap.size()) continue;
const float y = chunk.heightMap[heightIndex];
const uint8_t sceneID = chunk.GetSceneIDAtGrid(i, j);
if (sceneID != currentSceneID.GetSceneID()) continue;
const float sceneMapI = (static_cast<float>(i) / static_cast<float>(chunk.width - 1)) * static_cast<float>(chunk.colorMapResolution - 1);
const float sceneMapJ = (static_cast<float>(j) / static_cast<float>(chunk.height - 1)) * static_cast<float>(chunk.colorMapResolution - 1);
EntityInfo info;
info.lot = lot + sceneID;
info.pos = chunk.GridToWorldPos(i, j);
info.rot = QuatUtils::IDENTITY;
info.spawner = nullptr;
info.spawnerID = entity->GetObjectID();
info.spawnerNodeID = 0;
info.settings.Insert(u"SpawnedFromSlashCommand", true);
const uint32_t sceneI = std::min(static_cast<uint32_t>(sceneMapI), chunk.colorMapResolution - 1);
const uint32_t sceneJ = std::min(static_cast<uint32_t>(sceneMapJ), chunk.colorMapResolution - 1);
const uint32_t sceneIndex = sceneI * chunk.colorMapResolution + sceneJ;
uint8_t sceneID = 0;
if (sceneIndex < chunk.sceneMap.size()) {
sceneID = chunk.sceneMap[sceneIndex];
}
if (sceneID == currentSceneID.GetSceneID()) {
const float worldX = (static_cast<float>(i) + (chunk.offsetX / chunk.scaleFactor)) * chunk.scaleFactor;
const float worldY = y;
const float worldZ = (static_cast<float>(j) + (chunk.offsetZ / chunk.scaleFactor)) * chunk.scaleFactor;
NiPoint3 spawnPos(worldX, worldY, worldZ);
EntityInfo info;
info.lot = lot + currentSceneID.GetSceneID();
info.pos = spawnPos;
info.rot = QuatUtils::IDENTITY;
info.spawner = nullptr;
info.spawnerID = entity->GetObjectID();
info.spawnerNodeID = 0;
info.settings.Insert(u"SpawnedFromSlashCommand", true);
Entity* newEntity = Game::entityManager->CreateEntity(info, nullptr);
if (newEntity != nullptr) {
Game::entityManager->ConstructEntity(newEntity);
spawnedCount++;
}
Entity* newEntity = Game::entityManager->CreateEntity(info, nullptr);
if (newEntity != nullptr) {
Game::entityManager->ConstructEntity(newEntity);
spawnedCount++;
}
}
}
@@ -2092,43 +2071,22 @@ namespace DEVGMCommands {
return;
}
// Spawn at all sceneMap points across all scenes
uint32_t spawnedCount = 0;
std::map<uint8_t, uint32_t> sceneSpawnCounts; // Track spawns per scene
std::map<uint8_t, uint32_t> sceneSpawnCounts;
for (const auto& chunk : raw.chunks) {
if (chunk.sceneMap.empty() || chunk.colorMapResolution == 0 || chunk.heightMap.empty()
|| chunk.width <= 1 || chunk.height <= 1 || chunk.scaleFactor <= 0.0f) continue;
if (!chunk.IsValidForSceneLookup()) continue;
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;
const float y = chunk.heightMap[heightIndex];
const float sceneMapI = (static_cast<float>(i) / static_cast<float>(chunk.width - 1)) * static_cast<float>(chunk.colorMapResolution - 1);
const float sceneMapJ = (static_cast<float>(j) / static_cast<float>(chunk.height - 1)) * static_cast<float>(chunk.colorMapResolution - 1);
const uint32_t sceneI = std::min(static_cast<uint32_t>(sceneMapI), chunk.colorMapResolution - 1);
const uint32_t sceneJ = std::min(static_cast<uint32_t>(sceneMapJ), chunk.colorMapResolution - 1);
const uint32_t sceneIndex = sceneI * chunk.colorMapResolution + sceneJ;
uint8_t sceneID = 0;
if (sceneIndex < chunk.sceneMap.size()) {
sceneID = chunk.sceneMap[sceneIndex];
}
if (i * chunk.width + j >= chunk.heightMap.size()) continue;
const uint8_t sceneID = chunk.GetSceneIDAtGrid(i, j);
if (sceneID == 0) continue;
const float worldX = (static_cast<float>(i) + (chunk.offsetX / chunk.scaleFactor)) * chunk.scaleFactor;
const float worldY = y;
const float worldZ = (static_cast<float>(j) + (chunk.offsetZ / chunk.scaleFactor)) * chunk.scaleFactor;
NiPoint3 spawnPos(worldX, worldY, worldZ);
EntityInfo info;
info.lot = lot + sceneID;
info.pos = spawnPos;
info.pos = chunk.GridToWorldPos(i, j);
info.rot = QuatUtils::IDENTITY;
info.spawner = nullptr;
info.spawnerID = entity->GetObjectID();

View File

@@ -14,6 +14,30 @@ constexpr uint32_t kMaxChunks = 1024;
namespace Raw {
bool Chunk::IsValidForSceneLookup() const {
return !sceneMap.empty() && colorMapResolution > 0 && !heightMap.empty()
&& 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;
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
);
}
/**
* @brief Read flair attributes from stream
*/
@@ -386,45 +410,14 @@ namespace Raw {
uint32_t vertexOffset = 0;
for (const auto& chunk : raw.chunks) {
// Skip chunks without scene maps or with invalid dimensions/scale
if (chunk.sceneMap.empty() || chunk.colorMapResolution == 0 || chunk.heightMap.empty()
|| chunk.scaleFactor <= 0.0f || chunk.width <= 1 || chunk.height <= 1) {
LOG("Skipping chunk %u (sceneMap: %zu, colorMapRes: %u, heightMap: %zu, scaleFactor: %f, width: %u, height: %u)",
chunk.id, chunk.sceneMap.size(), chunk.colorMapResolution, chunk.heightMap.size(),
chunk.scaleFactor, chunk.width, chunk.height);
continue;
}
if (!chunk.IsValidForSceneLookup()) continue;
LOG("Processing chunk %u: width=%u, height=%u, colorMapRes=%u, sceneMapSize=%zu",
chunk.id, chunk.width, chunk.height, chunk.colorMapResolution, chunk.sceneMap.size());
// Generate vertices for this chunk
for (uint32_t i = 0; i < chunk.width; ++i) {
for (uint32_t j = 0; j < chunk.height; ++j) {
// Get height at this position
const uint32_t heightIndex = chunk.width * i + j;
if (heightIndex >= chunk.heightMap.size()) continue;
const float y = chunk.heightMap[heightIndex];
const float worldX = (static_cast<float>(i) + (chunk.offsetX / chunk.scaleFactor)) * chunk.scaleFactor;
const float worldY = y;
const float worldZ = (static_cast<float>(j) + (chunk.offsetZ / chunk.scaleFactor)) * chunk.scaleFactor;
const NiPoint3 worldPos(worldX, worldY, worldZ);
const float sceneMapI = (static_cast<float>(i) / static_cast<float>(chunk.width - 1)) * static_cast<float>(chunk.colorMapResolution - 1);
const float sceneMapJ = (static_cast<float>(j) / static_cast<float>(chunk.height - 1)) * static_cast<float>(chunk.colorMapResolution - 1);
const uint32_t sceneI = std::min(static_cast<uint32_t>(sceneMapI), chunk.colorMapResolution - 1);
const uint32_t sceneJ = std::min(static_cast<uint32_t>(sceneMapJ), chunk.colorMapResolution - 1);
const uint32_t sceneIndex = sceneI * chunk.colorMapResolution + sceneJ;
uint8_t sceneID = 0;
if (sceneIndex < chunk.sceneMap.size()) {
sceneID = chunk.sceneMap[sceneIndex];
}
outMesh.vertices.emplace_back(worldPos, sceneID);
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 leftVert = currentVert - 1;

View File

@@ -102,6 +102,9 @@ struct Chunk {
std::vector<uint16_t> meshVertSize;
std::vector<MeshTri> meshTri;
bool IsValidForSceneLookup() const;
uint8_t GetSceneIDAtGrid(uint32_t i, uint32_t j) const;
NiPoint3 GridToWorldPos(uint32_t i, uint32_t j) const;
};
/**

View File

@@ -397,7 +397,7 @@ SceneTransitionInfo Zone::LoadSceneTransitionInfo(std::istream& file) {
uint32_t sceneID, layerID;
BinaryIO::BinaryRead(file, sceneID);
BinaryIO::BinaryRead(file, layerID);
info.sceneID = LWOSCENEID(static_cast<int32_t>(sceneID), layerID);
info.sceneID = LWOSCENEID(sceneID, layerID);
BinaryIO::BinaryRead(file, info.position);
return info;
}

View File

@@ -321,37 +321,17 @@ LWOSCENEID dZoneManager::GetSceneIDFromPosition(const NiPoint3& position) const
// Find the chunk containing this position
// Reverse the world position calculation from GenerateTerrainMesh
for (const auto& chunk : raw.chunks) {
if (chunk.sceneMap.empty() || chunk.scaleFactor <= 0.0f || chunk.width <= 1 || chunk.height <= 1 || chunk.colorMapResolution == 0) continue;
if (!chunk.IsValidForSceneLookup()) continue;
// Reverse: worldX = (i + offsetX/scaleFactor) * scaleFactor
// Therefore: i = worldX/scaleFactor - offsetX/scaleFactor
const float heightI = posX / chunk.scaleFactor - (chunk.offsetX / chunk.scaleFactor);
const float heightJ = posZ / chunk.scaleFactor - (chunk.offsetZ / chunk.scaleFactor);
// Check if position is within this chunk's heightmap bounds
if (heightI >= 0.0f && heightI < static_cast<float>(chunk.width) &&
heightJ >= 0.0f && heightJ < static_cast<float>(chunk.height)) {
const float sceneMapI = (heightI / static_cast<float>(chunk.width - 1)) * static_cast<float>(chunk.colorMapResolution - 1);
const float sceneMapJ = (heightJ / static_cast<float>(chunk.height - 1)) * static_cast<float>(chunk.colorMapResolution - 1);
const uint32_t sceneI = std::min(static_cast<uint32_t>(sceneMapI), chunk.colorMapResolution - 1);
const uint32_t sceneJ = std::min(static_cast<uint32_t>(sceneMapJ), chunk.colorMapResolution - 1);
// Scene map uses the same indexing pattern as heightmap: row * width + col
const uint32_t sceneIndex = sceneI * chunk.colorMapResolution + sceneJ;
// Bounds check: if this chunk's sceneMap is inconsistent, skip this chunk
if (sceneIndex >= chunk.sceneMap.size()) {
LOG_DEBUG("GetSceneIDFromPosition: sceneIndex %u out of bounds (sceneMap size: %zu), skipping malformed chunk.", sceneIndex, chunk.sceneMap.size());
continue;
}
// Get scene ID from sceneMap
const uint8_t sceneID = chunk.sceneMap[sceneIndex];
// Return the scene ID
return LWOSCENEID(sceneID, 0);
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);
}
}