mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-25 08:30:13 +00:00
Unify coordinate systems with canonical WoW world coordinates
Centralizes all coordinate conversions in core/coordinates.hpp with proper canonical WoW coords (+X=North, +Y=West, +Z=Up). Fixes critical tile calculation bug that was loading wrong surrounding tiles during terrain streaming, and fixes position sync sending ADT-raw format instead of canonical coordinates to the server.
This commit is contained in:
parent
ee9efa3478
commit
6690910712
11 changed files with 367 additions and 93 deletions
83
include/core/coordinates.hpp
Normal file
83
include/core/coordinates.hpp
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
#pragma once
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <utility>
|
||||
|
||||
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<int, int> worldToTile(float renderX, float renderY) {
|
||||
// renderY = wowX (north), renderX = wowY (west)
|
||||
int tileX = static_cast<int>(std::floor(32.0f - renderY / TILE_SIZE));
|
||||
int tileY = static_cast<int>(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<int, int> canonicalToTile(float wowX, float wowY) {
|
||||
int tileX = static_cast<int>(std::floor(32.0f - wowX / TILE_SIZE));
|
||||
int tileY = static_cast<int>(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
|
||||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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 <GL/glew.h>
|
||||
#include <chrono>
|
||||
#include <cstdlib>
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <optional>
|
||||
#include <sstream>
|
||||
#include <set>
|
||||
|
||||
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<char>(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<glm::vec3> 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<std::pair<float, float>> 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");
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
#include <glm/glm.hpp>
|
||||
#include <imgui.h>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
|
||||
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<float> terrainH;
|
||||
std::optional<float> wmoH;
|
||||
std::optional<float> 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<float> {
|
||||
std::optional<float> terrainH;
|
||||
std::optional<float> wmoH;
|
||||
std::optional<float> 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<float> 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<float>::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<float>(i)) / static_cast<float>(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<float>(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<float>(ri)) / static_cast<float>(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<float>(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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 ---
|
||||
|
|
|
|||
|
|
@ -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<PendingTile> 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<PendingTile> 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<int>(std::floor(worldX / TILE_SIZE));
|
||||
int tileY = 32 - static_cast<int>(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};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 <unordered_set>
|
||||
|
||||
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;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue