diff --git a/include/core/coordinates.hpp b/include/core/coordinates.hpp new file mode 100644 index 00000000..98e97b92 --- /dev/null +++ b/include/core/coordinates.hpp @@ -0,0 +1,83 @@ +#pragma once + +#include +#include +#include +#include + +namespace wowee::core::coords { + +inline constexpr float TILE_SIZE = 533.33333f; +inline constexpr float ZEROPOINT = 32.0f * TILE_SIZE; + +// ---- Canonical WoW world coordinate system (per-map) ---- +// +X = North, +Y = West, +Z = Up (height) +// Origin (0,0,0) is the center of the 64x64 tile grid. +// Full extent: ±17066.66656 in X and Y. +// +// ---- Engine rendering coordinate system ---- +// renderX = wowY (west), renderY = wowX (north), renderZ = wowZ (up) +// Terrain vertices (MCNK) are stored directly in this space. +// +// ---- ADT file placement coordinate system ---- +// Used by MDDF (doodads) and MODF (WMOs) records in ADT files. +// Range [0, 34133.333] with center at ZEROPOINT (17066.666). +// adtY = height; adtX/adtZ are horizontal. + +// Convert between canonical WoW and engine rendering coordinates (just swap X/Y). +inline glm::vec3 canonicalToRender(const glm::vec3& wow) { + return glm::vec3(wow.y, wow.x, wow.z); +} + +inline glm::vec3 renderToCanonical(const glm::vec3& render) { + return glm::vec3(render.y, render.x, render.z); +} + +// ADT file placement data (MDDF/MODF) -> engine rendering coordinates. +inline glm::vec3 adtToWorld(float adtX, float adtY, float adtZ) { + return glm::vec3( + -(adtZ - ZEROPOINT), // renderX = ZP - adtZ (= wowY) + -(adtX - ZEROPOINT), // renderY = ZP - adtX (= wowX) + adtY // renderZ = adtY (= wowZ) + ); +} + +inline glm::vec3 adtToWorld(const glm::vec3& adt) { + return adtToWorld(adt.x, adt.y, adt.z); +} + +// Engine rendering coordinates -> ADT file placement data. +inline glm::vec3 worldToAdt(float renderX, float renderY, float renderZ) { + return glm::vec3( + ZEROPOINT - renderY, // adtX = ZP - renderY (= ZP - wowX) + renderZ, // adtY = renderZ (= wowZ, height) + ZEROPOINT - renderX // adtZ = ZP - renderX (= ZP - wowY) + ); +} + +inline glm::vec3 worldToAdt(const glm::vec3& world) { + return worldToAdt(world.x, world.y, world.z); +} + +// Engine rendering coordinates -> ADT tile indices. +// Returns (tileX, tileY) matching ADT filename: Map_{tileX}_{tileY}.adt +// Uses canonical formula: tileN = floor(32 - wowN / TILE_SIZE) +inline std::pair worldToTile(float renderX, float renderY) { + // renderY = wowX (north), renderX = wowY (west) + int tileX = static_cast(std::floor(32.0f - renderY / TILE_SIZE)); + int tileY = static_cast(std::floor(32.0f - renderX / TILE_SIZE)); + tileX = std::clamp(tileX, 0, 63); + tileY = std::clamp(tileY, 0, 63); + return {tileX, tileY}; +} + +// Canonical WoW coordinates -> ADT tile indices. +inline std::pair canonicalToTile(float wowX, float wowY) { + int tileX = static_cast(std::floor(32.0f - wowX / TILE_SIZE)); + int tileY = static_cast(std::floor(32.0f - wowY / TILE_SIZE)); + tileX = std::clamp(tileX, 0, 63); + tileY = std::clamp(tileY, 0, 63); + return {tileX, tileY}; +} + +} // namespace wowee::core::coords diff --git a/include/rendering/camera_controller.hpp b/include/rendering/camera_controller.hpp index f74debd2..ef8037aa 100644 --- a/include/rendering/camera_controller.hpp +++ b/include/rendering/camera_controller.hpp @@ -31,6 +31,11 @@ public: void processMouseWheel(float delta); void setFollowTarget(glm::vec3* target); + void setDefaultSpawn(const glm::vec3& position, float yawDeg, float pitchDeg) { + defaultPosition = position; + defaultYaw = yawDeg; + defaultPitch = pitchDeg; + } void reset(); diff --git a/include/rendering/minimap.hpp b/include/rendering/minimap.hpp index e91423fd..71cc7639 100644 --- a/include/rendering/minimap.hpp +++ b/include/rendering/minimap.hpp @@ -22,7 +22,8 @@ public: void setTerrainRenderer(TerrainRenderer* tr) { terrainRenderer = tr; } - void render(const Camera& playerCamera, int screenWidth, int screenHeight); + void render(const Camera& playerCamera, const glm::vec3& centerWorldPos, + int screenWidth, int screenHeight); void setEnabled(bool enabled) { this->enabled = enabled; } bool isEnabled() const { return enabled; } @@ -31,7 +32,7 @@ public: void setViewRadius(float radius) { viewRadius = radius; } private: - void renderTerrainToFBO(const Camera& playerCamera); + void renderTerrainToFBO(const Camera& playerCamera, const glm::vec3& centerWorldPos); void renderQuad(int screenWidth, int screenHeight); TerrainRenderer* terrainRenderer = nullptr; diff --git a/include/rendering/terrain_manager.hpp b/include/rendering/terrain_manager.hpp index 55f55076..cee40c12 100644 --- a/include/rendering/terrain_manager.hpp +++ b/include/rendering/terrain_manager.hpp @@ -188,7 +188,7 @@ public: private: /** - * Get tile coordinates from world position + * Get tile coordinates from GL world position */ TileCoord worldToTile(float worldX, float worldY) const; diff --git a/src/core/application.cpp b/src/core/application.cpp index f50254f4..db6c8901 100644 --- a/src/core/application.cpp +++ b/src/core/application.cpp @@ -1,4 +1,5 @@ #include "core/application.hpp" +#include "core/coordinates.hpp" #include "core/logger.hpp" #include "rendering/renderer.hpp" #include "rendering/camera.hpp" @@ -31,11 +32,84 @@ #include #include #include +#include +#include +#include +#include #include namespace wowee { namespace core { +namespace { + +struct SpawnPreset { + const char* key; + const char* label; + const char* mapName; // Map name for ADT paths (e.g., "Azeroth") + glm::vec3 spawnCanonical; // Canonical WoW coords: +X=North, +Y=West, +Z=Up + float yawDeg; + float pitchDeg; +}; + +const SpawnPreset* selectSpawnPreset(const char* envValue) { + // Spawn positions in canonical WoW world coordinates (X=north, Y=west, Z=up). + // Tile is computed from position via: tileN = floor(32 - wowN / 533.33333) + static const SpawnPreset presets[] = { + {"goldshire", "Goldshire", "Azeroth", glm::vec3( 62.0f, -9464.0f, 200.0f), 0.0f, -5.0f}, + {"stormwind", "Stormwind", "Azeroth", glm::vec3( -365.0f, -8345.0f, 180.0f), 35.0f, -8.0f}, + {"ironforge", "Ironforge Area", "Azeroth", glm::vec3( -300.0f,-11240.0f, 260.0f), -20.0f, -8.0f}, + {"westfall", "Westfall", "Azeroth", glm::vec3(-1820.0f, -9380.0f, 190.0f), 10.0f, -8.0f}, + }; + + if (!envValue || !*envValue) { + return &presets[0]; + } + + std::string key = envValue; + std::transform(key.begin(), key.end(), key.begin(), [](unsigned char c) { + return static_cast(std::tolower(c)); + }); + + for (const auto& preset : presets) { + if (key == preset.key) return &preset; + } + + LOG_WARNING("Unknown WOW_SPAWN='", key, "', falling back to goldshire"); + LOG_INFO("Available WOW_SPAWN presets: goldshire, stormwind, ironforge, westfall"); + return &presets[0]; +} + +std::optional parseVec3Csv(const char* raw) { + if (!raw || !*raw) return std::nullopt; + std::stringstream ss(raw); + std::string part; + float vals[3]; + for (int i = 0; i < 3; i++) { + if (!std::getline(ss, part, ',')) return std::nullopt; + try { + vals[i] = std::stof(part); + } catch (...) { + return std::nullopt; + } + } + return glm::vec3(vals[0], vals[1], vals[2]); +} + +std::optional> parseYawPitchCsv(const char* raw) { + if (!raw || !*raw) return std::nullopt; + std::stringstream ss(raw); + std::string part; + float yaw = 0.0f, pitch = 0.0f; + if (!std::getline(ss, part, ',')) return std::nullopt; + try { yaw = std::stof(part); } catch (...) { return std::nullopt; } + if (!std::getline(ss, part, ',')) return std::nullopt; + try { pitch = std::stof(part); } catch (...) { return std::nullopt; } + return std::make_pair(yaw, pitch); +} + +} // namespace + Application* Application::instance = nullptr; Application::Application() { @@ -293,14 +367,11 @@ void Application::update(float deltaTime) { npcManager->update(deltaTime, renderer->getCharacterRenderer()); } - // Sync character GL position → movementInfo WoW coords each frame + // Sync character render position → canonical WoW coords each frame if (renderer && gameHandler) { - glm::vec3 glPos = renderer->getCharacterPosition(); - constexpr float ZEROPOINT = 32.0f * 533.33333f; - float wowX = ZEROPOINT - glPos.y; - float wowY = glPos.z; - float wowZ = ZEROPOINT - glPos.x; - gameHandler->setPosition(wowX, wowY, wowZ); + glm::vec3 renderPos = renderer->getCharacterPosition(); + glm::vec3 canonical = core::coords::renderToCanonical(renderPos); + gameHandler->setPosition(canonical.x, canonical.y, canonical.z); // Sync orientation: camera yaw (degrees) → WoW orientation (radians) float yawDeg = renderer->getCharacterYaw(); @@ -816,11 +887,8 @@ void Application::spawnNpcs() { // derive it from the camera so targeting distance calculations work. const auto& movement = gameHandler->getMovementInfo(); if (movement.x == 0.0f && movement.y == 0.0f && movement.z == 0.0f) { - constexpr float ZEROPOINT = 32.0f * 533.33333f; - float wowX = ZEROPOINT - playerSpawnGL.y; - float wowY = playerSpawnGL.z; - float wowZ = ZEROPOINT - playerSpawnGL.x; - gameHandler->setPosition(wowX, wowY, wowZ); + glm::vec3 canonical = core::coords::renderToCanonical(playerSpawnGL); + gameHandler->setPosition(canonical.x, canonical.y, canonical.z); } npcsSpawned = true; @@ -848,6 +916,37 @@ void Application::startSinglePlayer() { loadEquippedWeapons(); // --- Loading screen: load terrain and wait for streaming before spawning --- + const SpawnPreset* spawnPreset = selectSpawnPreset(std::getenv("WOW_SPAWN")); + // Canonical WoW coords: +X=North, +Y=West, +Z=Up + glm::vec3 spawnCanonical = spawnPreset ? spawnPreset->spawnCanonical : glm::vec3(62.0f, -9464.0f, 200.0f); + std::string mapName = spawnPreset ? spawnPreset->mapName : "Azeroth"; + float spawnYaw = spawnPreset ? spawnPreset->yawDeg : 0.0f; + float spawnPitch = spawnPreset ? spawnPreset->pitchDeg : -5.0f; + + if (auto envSpawnPos = parseVec3Csv(std::getenv("WOW_SPAWN_POS"))) { + spawnCanonical = *envSpawnPos; + LOG_INFO("Using WOW_SPAWN_POS override (canonical WoW X,Y,Z): (", + spawnCanonical.x, ", ", spawnCanonical.y, ", ", spawnCanonical.z, ")"); + } + if (auto envSpawnRot = parseYawPitchCsv(std::getenv("WOW_SPAWN_ROT"))) { + spawnYaw = envSpawnRot->first; + spawnPitch = envSpawnRot->second; + LOG_INFO("Using WOW_SPAWN_ROT override: yaw=", spawnYaw, " pitch=", spawnPitch); + } + + // Convert canonical WoW → engine rendering coordinates (swap X/Y) + glm::vec3 spawnRender = core::coords::canonicalToRender(spawnCanonical); + if (renderer && renderer->getCameraController()) { + renderer->getCameraController()->setDefaultSpawn(spawnRender, spawnYaw, spawnPitch); + } + if (spawnPreset) { + LOG_INFO("Single-player spawn preset: ", spawnPreset->label, + " canonical=(", + spawnCanonical.x, ", ", spawnCanonical.y, ", ", spawnCanonical.z, + ") (set WOW_SPAWN to change)"); + LOG_INFO("Optional spawn overrides (canonical WoW X,Y,Z): WOW_SPAWN_POS=x,y,z WOW_SPAWN_ROT=yaw,pitch"); + } + rendering::LoadingScreen loadingScreen; bool loadingScreenOk = loadingScreen.initialize(); @@ -863,7 +962,11 @@ void Application::startSinglePlayer() { // Try to load test terrain if WOW_DATA_PATH is set bool terrainOk = false; if (renderer && assetManager && assetManager->isInitialized()) { - std::string adtPath = "World\\Maps\\Azeroth\\Azeroth_32_49.adt"; + // Compute ADT path from canonical spawn coordinates + auto [tileX, tileY] = core::coords::canonicalToTile(spawnCanonical.x, spawnCanonical.y); + std::string adtPath = "World\\Maps\\" + mapName + "\\" + mapName + "_" + + std::to_string(tileX) + "_" + std::to_string(tileY) + ".adt"; + LOG_INFO("Initial ADT tile [", tileX, ",", tileY, "] from canonical position"); terrainOk = renderer->loadTestTerrain(assetManager.get(), adtPath); if (!terrainOk) { LOG_WARNING("Could not load test terrain - atmospheric rendering only"); diff --git a/src/game/npc_manager.cpp b/src/game/npc_manager.cpp index 3e391c34..c5639261 100644 --- a/src/game/npc_manager.cpp +++ b/src/game/npc_manager.cpp @@ -1,5 +1,6 @@ #include "game/npc_manager.hpp" #include "game/entity.hpp" +#include "core/coordinates.hpp" #include "pipeline/asset_manager.hpp" #include "pipeline/m2_loader.hpp" #include "pipeline/dbc_loader.hpp" @@ -13,8 +14,6 @@ namespace wowee { namespace game { -static constexpr float ZEROPOINT = 32.0f * 533.33333f; - // Random emote animation IDs (humanoid only) static const uint32_t EMOTE_ANIMS[] = { 60, 66, 67, 70 }; // Talk, Bow, Wave, Laugh static constexpr int NUM_EMOTE_ANIMS = 4; @@ -315,11 +314,9 @@ void NpcManager::initialize(pipeline::AssetManager* am, unit->setHealth(s.health); unit->setMaxHealth(s.health); - // Convert GL position back to WoW coordinates for targeting system - float wowX = ZEROPOINT - glPos.y; - float wowY = glPos.z; - float wowZ = ZEROPOINT - glPos.x; - unit->setPosition(wowX, wowY, wowZ, s.rotation); + // Store canonical WoW coordinates for targeting/server compatibility + glm::vec3 canonical = core::coords::renderToCanonical(glPos); + unit->setPosition(canonical.x, canonical.y, canonical.z, s.rotation); em.addEntity(guid, unit); diff --git a/src/rendering/camera_controller.cpp b/src/rendering/camera_controller.cpp index bea8bc5f..f10e090c 100644 --- a/src/rendering/camera_controller.cpp +++ b/src/rendering/camera_controller.cpp @@ -9,6 +9,7 @@ #include #include #include +#include namespace wowee { namespace rendering { @@ -1010,32 +1011,137 @@ void CameraController::reset() { glm::vec3 spawnPos = defaultPosition; - // Snap spawn to a nearby valid floor, but reject outliers so we don't - // respawn under the city when collision data is noisy at this location. - std::optional terrainH; - std::optional wmoH; - std::optional m2H; - if (terrainManager) { - terrainH = terrainManager->getHeightAt(spawnPos.x, spawnPos.y); - } - float floorProbeZ = terrainH.value_or(spawnPos.z); - if (wmoRenderer) { - wmoH = wmoRenderer->getFloorHeight(spawnPos.x, spawnPos.y, floorProbeZ + 2.0f); - } - if (m2Renderer) { - m2H = m2Renderer->getFloorHeight(spawnPos.x, spawnPos.y, floorProbeZ + 2.0f); - } + auto evalFloorAt = [&](float x, float y, float refZ) -> std::optional { + std::optional terrainH; + std::optional wmoH; + std::optional m2H; + if (terrainManager) { + terrainH = terrainManager->getHeightAt(x, y); + } + float floorProbeZ = terrainH.value_or(refZ); + if (wmoRenderer) { + wmoH = wmoRenderer->getFloorHeight(x, y, floorProbeZ + 2.0f); + } + if (m2Renderer) { + m2H = m2Renderer->getFloorHeight(x, y, floorProbeZ + 2.0f); + } + auto h = selectReachableFloor(terrainH, wmoH, refZ, 16.0f); + if (!h) { + h = selectHighestFloor(terrainH, wmoH, m2H); + } + return h; + }; - std::optional h = selectReachableFloor(terrainH, wmoH, spawnPos.z, 16.0f); - if (!h) { - h = selectHighestFloor(terrainH, wmoH, m2H); + // Search nearby for a stable, non-steep spawn floor to avoid waterfall/ledge spawns. + float bestScore = std::numeric_limits::max(); + glm::vec3 bestPos = spawnPos; + bool foundBest = false; + constexpr float radii[] = {0.0f, 6.0f, 12.0f, 18.0f, 24.0f, 32.0f}; + constexpr int ANGLES = 16; + constexpr float PI = 3.14159265f; + for (float r : radii) { + int steps = (r <= 0.01f) ? 1 : ANGLES; + for (int i = 0; i < steps; i++) { + float a = (2.0f * PI * static_cast(i)) / static_cast(steps); + float x = defaultPosition.x + r * std::cos(a); + float y = defaultPosition.y + r * std::sin(a); + auto h = evalFloorAt(x, y, defaultPosition.z); + if (!h) continue; + + // Allow large downward snaps, but avoid snapping onto high roofs/odd geometry. + constexpr float MAX_SPAWN_SNAP_UP = 16.0f; + if (*h > defaultPosition.z + MAX_SPAWN_SNAP_UP) continue; + + float score = r * 0.02f; + if (terrainManager) { + // Penalize steep/unstable spots. + int slopeSamples = 0; + float slopeAccum = 0.0f; + constexpr float off = 2.5f; + const float dx[4] = {off, -off, 0.0f, 0.0f}; + const float dy[4] = {0.0f, 0.0f, off, -off}; + for (int s = 0; s < 4; s++) { + auto hn = terrainManager->getHeightAt(x + dx[s], y + dy[s]); + if (!hn) continue; + slopeAccum += std::abs(*hn - *h); + slopeSamples++; + } + if (slopeSamples > 0) { + score += (slopeAccum / static_cast(slopeSamples)) * 2.0f; + } + } + if (waterRenderer) { + auto wh = waterRenderer->getWaterHeightAt(x, y); + if (wh && *h < *wh - 0.2f) { + score += 8.0f; + } + } + if (wmoRenderer) { + const glm::vec3 from(x, y, *h + 0.20f); + const bool insideWMO = wmoRenderer->isInsideWMO(x, y, *h + 1.5f, nullptr); + + // Prefer outdoors for default hearth-like spawn points. + if (insideWMO) { + score += 120.0f; + } + + // Reject points embedded in nearby walls by probing tiny cardinal moves. + int wallHits = 0; + constexpr float probeStep = 0.85f; + const glm::vec3 probes[4] = { + glm::vec3(x + probeStep, y, *h + 0.20f), + glm::vec3(x - probeStep, y, *h + 0.20f), + glm::vec3(x, y + probeStep, *h + 0.20f), + glm::vec3(x, y - probeStep, *h + 0.20f), + }; + for (const auto& to : probes) { + glm::vec3 adjusted; + if (wmoRenderer->checkWallCollision(from, to, adjusted)) { + wallHits++; + } + } + if (wallHits >= 2) { + continue; // Likely wedged in geometry. + } + if (wallHits == 1) { + score += 30.0f; + } + + // If the point is inside a WMO, ensure there is an easy escape path. + // If almost all directions are blocked, treat it as invalid spawn. + if (insideWMO) { + int blocked = 0; + constexpr int radialChecks = 12; + constexpr float radialDist = 2.2f; + for (int ri = 0; ri < radialChecks; ri++) { + float ang = (2.0f * PI * static_cast(ri)) / static_cast(radialChecks); + glm::vec3 to( + x + std::cos(ang) * radialDist, + y + std::sin(ang) * radialDist, + *h + 0.20f + ); + glm::vec3 adjusted; + if (wmoRenderer->checkWallCollision(from, to, adjusted)) { + blocked++; + } + } + if (blocked >= 9) { + continue; // Enclosed by interior/wall geometry. + } + score += static_cast(blocked) * 3.0f; + } + } + + if (score < bestScore) { + bestScore = score; + bestPos = glm::vec3(x, y, *h + 0.05f); + foundBest = true; + } + } } - // Allow large downward snaps (prevents sky-fall spawns), but don't snap up - // onto distant roofs when a bad hit appears above us. - constexpr float MAX_SPAWN_SNAP_UP = 16.0f; - if (h && *h <= spawnPos.z + MAX_SPAWN_SNAP_UP) { - lastGroundZ = *h; - spawnPos.z = *h + 0.05f; + if (foundBest) { + spawnPos = bestPos; + lastGroundZ = spawnPos.z - 0.05f; } camera->setRotation(yaw, pitch); @@ -1055,7 +1161,7 @@ void CameraController::reset() { camera->setPosition(camPos); } else { // Free-fly mode keeps camera eye-height above ground. - if (h) { + if (foundBest) { spawnPos.z += eyeHeight; } smoothedCamPos = spawnPos; diff --git a/src/rendering/minimap.cpp b/src/rendering/minimap.cpp index 6139cb66..1aaf1d77 100644 --- a/src/rendering/minimap.cpp +++ b/src/rendering/minimap.cpp @@ -137,11 +137,12 @@ void Minimap::shutdown() { quadShader.reset(); } -void Minimap::render(const Camera& playerCamera, int screenWidth, int screenHeight) { +void Minimap::render(const Camera& playerCamera, const glm::vec3& centerWorldPos, + int screenWidth, int screenHeight) { if (!enabled || !terrainRenderer || !fbo) return; const auto now = std::chrono::steady_clock::now(); - glm::vec3 playerPos = playerCamera.getPosition(); + glm::vec3 playerPos = centerWorldPos; bool needsRefresh = !hasCachedFrame; if (!needsRefresh) { float moved = glm::length(glm::vec2(playerPos.x - lastUpdatePos.x, playerPos.y - lastUpdatePos.y)); @@ -151,7 +152,7 @@ void Minimap::render(const Camera& playerCamera, int screenWidth, int screenHeig // 1. Render terrain from top-down into FBO (throttled) if (needsRefresh) { - renderTerrainToFBO(playerCamera); + renderTerrainToFBO(playerCamera, centerWorldPos); lastUpdateTime = now; lastUpdatePos = playerPos; hasCachedFrame = true; @@ -161,7 +162,7 @@ void Minimap::render(const Camera& playerCamera, int screenWidth, int screenHeig renderQuad(screenWidth, screenHeight); } -void Minimap::renderTerrainToFBO(const Camera& playerCamera) { +void Minimap::renderTerrainToFBO(const Camera& /*playerCamera*/, const glm::vec3& centerWorldPos) { // Save current viewport GLint prevViewport[4]; glGetIntegerv(GL_VIEWPORT, prevViewport); @@ -173,7 +174,7 @@ void Minimap::renderTerrainToFBO(const Camera& playerCamera) { // Create a top-down camera at the player's XY position Camera topDownCamera; - glm::vec3 playerPos = playerCamera.getPosition(); + glm::vec3 playerPos = centerWorldPos; topDownCamera.setPosition(glm::vec3(playerPos.x, playerPos.y, playerPos.z + 5000.0f)); topDownCamera.setRotation(0.0f, -89.9f); // Look straight down topDownCamera.setAspectRatio(1.0f); diff --git a/src/rendering/renderer.cpp b/src/rendering/renderer.cpp index 362ba6e5..2dd54917 100644 --- a/src/rendering/renderer.cpp +++ b/src/rendering/renderer.cpp @@ -1066,7 +1066,11 @@ void Renderer::renderWorld(game::World* world) { // Render minimap overlay if (minimap && camera && window) { - minimap->render(*camera, window->getWidth(), window->getHeight()); + glm::vec3 minimapCenter = camera->getPosition(); + if (cameraController && cameraController->isThirdPerson()) { + minimapCenter = characterPosition; + } + minimap->render(*camera, minimapCenter, window->getWidth(), window->getHeight()); } // --- Resolve MSAA → non-MSAA texture --- diff --git a/src/rendering/terrain_manager.cpp b/src/rendering/terrain_manager.cpp index 5eaf7dc1..3fe2e53c 100644 --- a/src/rendering/terrain_manager.cpp +++ b/src/rendering/terrain_manager.cpp @@ -4,6 +4,7 @@ #include "rendering/m2_renderer.hpp" #include "rendering/wmo_renderer.hpp" #include "rendering/camera.hpp" +#include "core/coordinates.hpp" #include "pipeline/asset_manager.hpp" #include "pipeline/adt_loader.hpp" #include "pipeline/m2_loader.hpp" @@ -147,11 +148,9 @@ void TerrainManager::update(const Camera& camera, float deltaTime) { timeSinceLastUpdate = 0.0f; - // Get current tile from camera position - // GL coordinate mapping: GL Y = -(wowX - ZEROPOINT), GL X = -(wowZ - ZEROPOINT), GL Z = height - // worldToTile expects: worldX = -glY (maps to tileX), worldY = glX (maps to tileY) + // Get current tile from camera position. glm::vec3 camPos = camera.getPosition(); - TileCoord newTile = worldToTile(-camPos.y, camPos.x); + TileCoord newTile = worldToTile(camPos.x, camPos.y); // Check if we've moved to a different tile if (newTile.x != currentTile.x || newTile.y != currentTile.y) { @@ -293,20 +292,15 @@ std::unique_ptr TerrainManager::prepareTile(int x, int y) { // Store placement data for instance creation on main thread if (preparedModelIds.count(modelId)) { - const float ZEROPOINT = 32.0f * 533.33333f; - float wowX = placement.position[0]; float wowY = placement.position[1]; float wowZ = placement.position[2]; + glm::vec3 glPos = core::coords::adtToWorld(wowX, wowY, wowZ); PendingTile::M2Placement p; p.modelId = modelId; p.uniqueId = placement.uniqueId; - p.position = glm::vec3( - -(wowZ - ZEROPOINT), - -(wowX - ZEROPOINT), - wowY - ); + p.position = glPos; p.rotation = glm::vec3( -placement.rotation[2] * 3.14159f / 180.0f, -placement.rotation[0] * 3.14159f / 180.0f, @@ -368,13 +362,9 @@ std::unique_ptr TerrainManager::prepareTile(int x, int y) { } if (!wmoModel.groups.empty()) { - const float ZEROPOINT = 32.0f * 533.33333f; - - glm::vec3 pos( - -(placement.position[2] - ZEROPOINT), - -(placement.position[0] - ZEROPOINT), - placement.position[1] - ); + glm::vec3 pos = core::coords::adtToWorld(placement.position[0], + placement.position[1], + placement.position[2]); glm::vec3 rot( -placement.rotation[2] * 3.14159f / 180.0f, @@ -769,19 +759,8 @@ void TerrainManager::unloadAll() { } } -TileCoord TerrainManager::worldToTile(float worldX, float worldY) const { - // WoW world coordinate system: - // - Tiles are 8533.33 units wide (TILE_SIZE) - // - Tile (32, 32) is roughly at world origin for continents - // - Coordinates increase going east (X) and south (Y) - - int tileX = 32 + static_cast(std::floor(worldX / TILE_SIZE)); - int tileY = 32 - static_cast(std::floor(worldY / TILE_SIZE)); - - // Clamp to valid range (0-63) - tileX = std::max(0, std::min(63, tileX)); - tileY = std::max(0, std::min(63, tileY)); - +TileCoord TerrainManager::worldToTile(float glX, float glY) const { + auto [tileX, tileY] = core::coords::worldToTile(glX, glY); return {tileX, tileY}; } diff --git a/src/ui/game_screen.cpp b/src/ui/game_screen.cpp index eaf54344..195be59b 100644 --- a/src/ui/game_screen.cpp +++ b/src/ui/game_screen.cpp @@ -1,5 +1,6 @@ #include "ui/game_screen.hpp" #include "core/application.hpp" +#include "core/coordinates.hpp" #include "core/input.hpp" #include "rendering/renderer.hpp" #include "rendering/character_renderer.hpp" @@ -12,12 +13,6 @@ #include namespace { - constexpr float ZEROPOINT = 32.0f * 533.33333f; - - glm::vec3 wowToGL(float wowX, float wowY, float wowZ) { - return { -(wowZ - ZEROPOINT), -(wowX - ZEROPOINT), wowY }; - } - bool raySphereIntersect(const wowee::rendering::Ray& ray, const glm::vec3& center, float radius, float& tOut) { glm::vec3 oc = ray.origin - center; float b = glm::dot(oc, ray.direction); @@ -103,7 +98,7 @@ void GameScreen::render(game::GameHandler& gameHandler) { if (gameHandler.hasTarget()) { auto target = gameHandler.getTarget(); if (target) { - targetGLPos = wowToGL(target->getX(), target->getY(), target->getZ()); + targetGLPos = core::coords::canonicalToRender(glm::vec3(target->getX(), target->getY(), target->getZ())); renderer->setTargetPosition(&targetGLPos); } else { renderer->setTargetPosition(nullptr); @@ -411,7 +406,7 @@ void GameScreen::processTargetInput(game::GameHandler& gameHandler) { auto t = entity->getType(); if (t != game::ObjectType::UNIT && t != game::ObjectType::PLAYER) continue; - glm::vec3 entityGL = wowToGL(entity->getX(), entity->getY(), entity->getZ()); + glm::vec3 entityGL = core::coords::canonicalToRender(glm::vec3(entity->getX(), entity->getY(), entity->getZ())); // Add half-height offset so we target the body center, not feet entityGL.z += 3.0f;