Add transport system, fix NPC spawning, and improve water rendering

Transport System (Phases 1-7):
- Implement TransportManager with Catmull-Rom spline path interpolation
- Add WMO dynamic transforms for moving transport instances
- Implement player attachment via world position composition
- Add test transport with circular path around Stormwind harbor
- Add /transport board and /transport leave console commands
- Reuse taxi flight spline system and external follow camera mode

NPC Spawn Fixes:
- Add smart ocean spawn filter: blocks land creatures at high altitude over water (Z>50)
- Allow legitimate water creatures at sea level (Z≤50) to spawn correctly
- Fixes Elder Grey Bears, Highland Striders, and Plainscreepers spawning over ocean
- Snap online creatures to terrain height when valid ground exists

NpcManager Removal:
- Remove deprecated NpcManager (offline mode no longer supported)
- Delete npc_manager.hpp and npc_manager.cpp
- Simplify NPC animation callbacks to use only creatureInstances_ map
- Move NPC callbacks to game initialization in application.cpp

Water Rendering:
- Fix tile seam gaps caused by per-vertex wave randomization
- Add distance-based blending: seamless waves up close (<150u), grid effect far away (>400u)
- Smooth transition between seamless and grid modes (150-400 unit range)
- Preserves aesthetic grid pattern at horizon while eliminating gaps when swimming
This commit is contained in:
Kelsi 2026-02-10 21:29:10 -08:00
parent c91e0bb916
commit 2e923311d0
13 changed files with 711 additions and 1079 deletions

View file

@ -554,6 +554,49 @@ void WMORenderer::setInstancePosition(uint32_t instanceId, const glm::vec3& posi
rebuildSpatialIndex();
}
void WMORenderer::setInstanceTransform(uint32_t instanceId, const glm::mat4& transform) {
auto idxIt = instanceIndexById.find(instanceId);
if (idxIt == instanceIndexById.end()) return;
auto& inst = instances[idxIt->second];
// Decompose transform to position/rotation/scale
inst.position = glm::vec3(transform[3]);
// Extract rotation (assuming uniform scale)
glm::mat3 rotationMatrix(transform);
float scaleX = glm::length(glm::vec3(transform[0]));
float scaleY = glm::length(glm::vec3(transform[1]));
float scaleZ = glm::length(glm::vec3(transform[2]));
inst.scale = scaleX; // Assume uniform scale
if (scaleX > 0.0001f) rotationMatrix[0] /= scaleX;
if (scaleY > 0.0001f) rotationMatrix[1] /= scaleY;
if (scaleZ > 0.0001f) rotationMatrix[2] /= scaleZ;
inst.rotation = glm::vec3(0.0f); // Euler angles not directly used, so zero them
// Update model matrix and bounds
inst.modelMatrix = transform;
inst.invModelMatrix = glm::inverse(transform);
auto modelIt = loadedModels.find(inst.modelId);
if (modelIt != loadedModels.end()) {
const ModelData& model = modelIt->second;
transformAABB(inst.modelMatrix, model.boundingBoxMin, model.boundingBoxMax,
inst.worldBoundsMin, inst.worldBoundsMax);
inst.worldGroupBounds.clear();
inst.worldGroupBounds.reserve(model.groups.size());
for (const auto& group : model.groups) {
glm::vec3 gMin, gMax;
transformAABB(inst.modelMatrix, group.boundingBoxMin, group.boundingBoxMax, gMin, gMax);
gMin -= glm::vec3(0.5f);
gMax += glm::vec3(0.5f);
inst.worldGroupBounds.emplace_back(gMin, gMax);
}
}
rebuildSpatialIndex();
}
void WMORenderer::removeInstance(uint32_t instanceId) {
auto it = std::find_if(instances.begin(), instances.end(),
[instanceId](const WMOInstance& inst) { return inst.id == instanceId; });