mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-22 23:30:14 +00:00
Implement WoW 3.3.5a DBC-driven lighting system
Add complete Blizzard-style time-of-day lighting pipeline: Spatial Volume System (Light.dbc): - Light volumes with position + inner/outer radius - Distance-based weighting with smoothstep falloff - Multi-volume blending (top 2 with normalized weights) - X,Z,Y coordinate handling + LIGHT_COORD_SCALE for ×36 quirk - Smooth zone transitions without popping Profile Selection (LightParams.dbc): - Weather variants: clear/rain/underwater - Links to 18 color + 6 float band curves per profile - Block indexing: LightParamsID × 18/6 + channel Time-of-Day Band Sampling (LightIntBand/LightFloatBand): - Half-minutes format (0-2879) with time clamping - Keyframe interpolation with midnight wrap - Wrap-safe initialization for edge cases - BGR color unpacking Multi-Volume Blending: - Weighted sum of all lighting params - Proper direction blending: normalize(sum(dir × weight)) - Blends ambient, diffuse, fog, sky, cloud density Temporal Smoothing: - Exponential blend to prevent frame snapping - Smooths ALL parameters (colors, fog, direction, sky) Game Time Support: - Accepts server-sent game time (WoW standard) - Falls back to local time if not provided - Manual override for testing Debug Features: - Volume distance/weight logging - Fog params logging - Coordinate scale verification Also: Move buff bar to top-left under player frame
This commit is contained in:
parent
3c13cf4b12
commit
69fa4c6e03
4 changed files with 819 additions and 4 deletions
|
|
@ -142,6 +142,7 @@ set(WOWEE_SOURCES
|
|||
src/rendering/lens_flare.cpp
|
||||
src/rendering/weather.cpp
|
||||
src/rendering/lightning.cpp
|
||||
src/rendering/lighting_manager.cpp
|
||||
src/rendering/character_renderer.cpp
|
||||
src/rendering/character_preview.cpp
|
||||
src/rendering/wmo_renderer.cpp
|
||||
|
|
|
|||
263
include/rendering/lighting_manager.hpp
Normal file
263
include/rendering/lighting_manager.hpp
Normal file
|
|
@ -0,0 +1,263 @@
|
|||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
namespace wowee {
|
||||
namespace pipeline { class DBCFile; class AssetManager; }
|
||||
|
||||
namespace rendering {
|
||||
|
||||
/**
|
||||
* Time-of-day lighting parameters sampled from DBC curves
|
||||
*/
|
||||
struct LightingParams {
|
||||
glm::vec3 ambientColor{0.4f, 0.4f, 0.5f}; // Fill lighting
|
||||
glm::vec3 diffuseColor{1.0f, 0.95f, 0.8f}; // Directional sun color
|
||||
glm::vec3 directionalDir{0.0f, -1.0f, 0.5f}; // Sun direction (normalized)
|
||||
|
||||
glm::vec3 fogColor{0.5f, 0.6f, 0.7f}; // Fog color
|
||||
float fogStart = 100.0f; // Fog start distance
|
||||
float fogEnd = 1000.0f; // Fog end distance
|
||||
float fogDensity = 0.001f; // Fog density
|
||||
|
||||
glm::vec3 skyTopColor{0.5f, 0.7f, 1.0f}; // Sky zenith color
|
||||
glm::vec3 skyMiddleColor{0.7f, 0.85f, 1.0f}; // Sky horizon color
|
||||
glm::vec3 skyBand1Color{0.9f, 0.95f, 1.0f}; // Sky band 1
|
||||
glm::vec3 skyBand2Color{1.0f, 0.98f, 0.9f}; // Sky band 2
|
||||
|
||||
float cloudDensity = 1.0f; // Cloud density/opacity
|
||||
float horizonGlow = 0.3f; // Horizon glow intensity
|
||||
};
|
||||
|
||||
/**
|
||||
* Light set keyframe for time-of-day interpolation
|
||||
*/
|
||||
struct LightKeyframe {
|
||||
uint32_t time; // Time in minutes since midnight (0-1439)
|
||||
|
||||
// Colors stored as RGB int tuples (0-255) in DBC
|
||||
glm::vec3 ambientColor;
|
||||
glm::vec3 diffuseColor;
|
||||
glm::vec3 fogColor;
|
||||
glm::vec3 skyTopColor;
|
||||
glm::vec3 skyMiddleColor;
|
||||
glm::vec3 skyBand1Color;
|
||||
glm::vec3 skyBand2Color;
|
||||
|
||||
float fogStart;
|
||||
float fogEnd;
|
||||
float fogDensity;
|
||||
float cloudDensity;
|
||||
float horizonGlow;
|
||||
};
|
||||
|
||||
/**
|
||||
* Light volume from Light.dbc (spatial lighting)
|
||||
*/
|
||||
struct LightVolume {
|
||||
uint32_t lightId = 0;
|
||||
uint32_t mapId = 0;
|
||||
glm::vec3 position{0.0f}; // World position (note: DBC stores as x,z,y!)
|
||||
float innerRadius = 0.0f; // Full weight radius
|
||||
float outerRadius = 0.0f; // Fade-out radius
|
||||
|
||||
// LightParams IDs for different conditions
|
||||
uint32_t lightParamsId = 0; // Normal/clear weather
|
||||
uint32_t lightParamsIdRain = 0; // Rainy weather
|
||||
uint32_t lightParamsIdUnderwater = 0;
|
||||
// More variants exist for phases, death, etc.
|
||||
};
|
||||
|
||||
/**
|
||||
* Color band with time-of-day keyframes
|
||||
*/
|
||||
struct ColorBand {
|
||||
uint8_t numKeyframes = 0;
|
||||
uint16_t times[16]; // Time keyframes (half-minutes since midnight)
|
||||
glm::vec3 colors[16]; // Color values (RGB 0-1)
|
||||
};
|
||||
|
||||
/**
|
||||
* Float band with time-of-day keyframes
|
||||
*/
|
||||
struct FloatBand {
|
||||
uint8_t numKeyframes = 0;
|
||||
uint16_t times[16]; // Time keyframes (half-minutes since midnight)
|
||||
float values[16]; // Float values
|
||||
};
|
||||
|
||||
/**
|
||||
* LightParams profile with 18 color bands + 6 float bands
|
||||
*/
|
||||
struct LightParamsProfile {
|
||||
uint32_t lightParamsId = 0;
|
||||
|
||||
// 18 color channels (IntBand)
|
||||
enum ColorChannel {
|
||||
AMBIENT_COLOR = 0,
|
||||
DIFFUSE_COLOR = 1,
|
||||
SKY_TOP_COLOR = 2,
|
||||
SKY_MIDDLE_COLOR = 3,
|
||||
SKY_BAND1_COLOR = 4,
|
||||
SKY_BAND2_COLOR = 5,
|
||||
FOG_COLOR = 6,
|
||||
// ... more channels exist (ocean, river, shadow, etc.)
|
||||
COLOR_CHANNEL_COUNT = 18
|
||||
};
|
||||
|
||||
ColorBand colorBands[COLOR_CHANNEL_COUNT];
|
||||
|
||||
// 6 float channels (FloatBand)
|
||||
enum FloatChannel {
|
||||
FOG_END = 0,
|
||||
FOG_START_SCALAR = 1, // Multiplier for fog start
|
||||
CLOUD_DENSITY = 2,
|
||||
FOG_DENSITY = 3,
|
||||
// ... more channels
|
||||
FLOAT_CHANNEL_COUNT = 6
|
||||
};
|
||||
|
||||
FloatBand floatBands[FLOAT_CHANNEL_COUNT];
|
||||
};
|
||||
|
||||
/**
|
||||
* WoW DBC-driven lighting manager
|
||||
*
|
||||
* Implements WotLK's time-of-day lighting system:
|
||||
* - Loads Light.dbc, LightParams.dbc, LightIntBand.dbc, LightFloatBand.dbc
|
||||
* - Samples lighting curves based on time-of-day
|
||||
* - Interpolates between keyframes
|
||||
* - Provides lighting parameters for rendering
|
||||
*/
|
||||
class LightingManager {
|
||||
public:
|
||||
LightingManager();
|
||||
~LightingManager();
|
||||
|
||||
/**
|
||||
* Initialize lighting system and load DBCs
|
||||
*/
|
||||
bool initialize(pipeline::AssetManager* assetManager);
|
||||
|
||||
/**
|
||||
* Update lighting for current time and player position
|
||||
* @param playerPos Player world position
|
||||
* @param mapId Current map ID
|
||||
* @param gameTime Optional game time in seconds (use -1 for real time)
|
||||
* @param isRaining Whether it's raining
|
||||
* @param isUnderwater Whether player is underwater
|
||||
*
|
||||
* Note: WoW uses server-sent game time, not local PC time.
|
||||
* Pass gameTime from SMSG_LOGIN_SETTIMESPEED or similar.
|
||||
*/
|
||||
void update(const glm::vec3& playerPos, uint32_t mapId,
|
||||
float gameTime = -1.0f,
|
||||
bool isRaining = false, bool isUnderwater = false);
|
||||
|
||||
/**
|
||||
* Get current lighting parameters
|
||||
*/
|
||||
const LightingParams& getLightingParams() const { return currentParams_; }
|
||||
|
||||
/**
|
||||
* Set whether player is indoors (disables outdoor lighting)
|
||||
*/
|
||||
void setIndoors(bool indoors) { isIndoors_ = indoors; }
|
||||
|
||||
/**
|
||||
* Get current time of day (0.0-1.0)
|
||||
*/
|
||||
float getTimeOfDay() const { return timeOfDay_; }
|
||||
|
||||
/**
|
||||
* Manually set time of day for testing
|
||||
*/
|
||||
void setTimeOfDay(float tod) { timeOfDay_ = tod; manualTime_ = true; }
|
||||
|
||||
/**
|
||||
* Use real time for day/night cycle
|
||||
*/
|
||||
void useRealTime(bool use) { manualTime_ = !use; }
|
||||
|
||||
private:
|
||||
/**
|
||||
* Load Light.dbc
|
||||
*/
|
||||
bool loadLightDbc(pipeline::AssetManager* assetManager);
|
||||
|
||||
/**
|
||||
* Load LightParams.dbc for zone→light mapping
|
||||
*/
|
||||
bool loadLightParamsDbc(pipeline::AssetManager* assetManager);
|
||||
|
||||
/**
|
||||
* Load LightIntBand.dbc and LightFloatBand.dbc for time curves
|
||||
*/
|
||||
bool loadLightBandDbcs(pipeline::AssetManager* assetManager);
|
||||
|
||||
/**
|
||||
* Weighted light volume for blending
|
||||
*/
|
||||
struct WeightedVolume {
|
||||
const LightVolume* volume = nullptr;
|
||||
float weight = 0.0f;
|
||||
};
|
||||
|
||||
/**
|
||||
* Find light volumes for blending (up to 4 with weight > 0)
|
||||
*/
|
||||
std::vector<WeightedVolume> findLightVolumes(const glm::vec3& playerPos, uint32_t mapId) const;
|
||||
|
||||
/**
|
||||
* Get LightParams ID based on conditions
|
||||
*/
|
||||
uint32_t selectLightParamsId(const LightVolume* volume, bool isRaining, bool isUnderwater) const;
|
||||
|
||||
/**
|
||||
* Sample lighting from LightParams profile
|
||||
*/
|
||||
LightingParams sampleLightParams(const LightParamsProfile* profile, uint16_t timeHalfMinutes) const;
|
||||
|
||||
/**
|
||||
* Sample color from band
|
||||
*/
|
||||
glm::vec3 sampleColorBand(const ColorBand& band, uint16_t timeHalfMinutes) const;
|
||||
|
||||
/**
|
||||
* Sample float from band
|
||||
*/
|
||||
float sampleFloatBand(const FloatBand& band, uint16_t timeHalfMinutes) const;
|
||||
|
||||
/**
|
||||
* Convert DBC BGR color to RGB vec3
|
||||
*/
|
||||
glm::vec3 dbcColorToVec3(uint32_t dbcColor) const;
|
||||
|
||||
pipeline::AssetManager* assetManager_ = nullptr;
|
||||
|
||||
// Light volumes by map
|
||||
std::map<uint32_t, std::vector<LightVolume>> lightVolumesByMap_;
|
||||
|
||||
// LightParams profiles by ID
|
||||
std::map<uint32_t, LightParamsProfile> lightParamsProfiles_;
|
||||
|
||||
// Current state
|
||||
LightingParams currentParams_;
|
||||
LightingParams targetParams_; // For smooth blending
|
||||
std::vector<WeightedVolume> activeVolumes_;
|
||||
glm::vec3 currentPlayerPos_{0.0f};
|
||||
uint32_t currentMapId_ = 0;
|
||||
float timeOfDay_ = 0.5f; // Start at noon
|
||||
bool isIndoors_ = false;
|
||||
bool manualTime_ = false;
|
||||
bool initialized_ = false;
|
||||
|
||||
// Fallback lighting
|
||||
LightingParams fallbackParams_;
|
||||
};
|
||||
|
||||
} // namespace rendering
|
||||
} // namespace wowee
|
||||
552
src/rendering/lighting_manager.cpp
Normal file
552
src/rendering/lighting_manager.cpp
Normal file
|
|
@ -0,0 +1,552 @@
|
|||
#include "rendering/lighting_manager.hpp"
|
||||
#include "pipeline/asset_manager.hpp"
|
||||
#include "pipeline/dbc_loader.hpp"
|
||||
#include "core/logger.hpp"
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <ctime>
|
||||
|
||||
namespace wowee {
|
||||
namespace rendering {
|
||||
|
||||
// Light coordinate scaling (test with 1.0f first, then try 36.0f if distances seem off)
|
||||
constexpr float LIGHT_COORD_SCALE = 1.0f;
|
||||
|
||||
// Maximum volumes to blend (top 2-4)
|
||||
constexpr size_t MAX_BLEND_VOLUMES = 2;
|
||||
|
||||
LightingManager::LightingManager() {
|
||||
// Set fallback lighting (Elwynn Forest-ish outdoor daytime)
|
||||
fallbackParams_.ambientColor = glm::vec3(0.5f, 0.5f, 0.6f);
|
||||
fallbackParams_.diffuseColor = glm::vec3(1.0f, 0.95f, 0.85f);
|
||||
fallbackParams_.directionalDir = glm::normalize(glm::vec3(0.3f, -0.7f, 0.6f));
|
||||
fallbackParams_.fogColor = glm::vec3(0.6f, 0.7f, 0.85f);
|
||||
fallbackParams_.fogStart = 300.0f;
|
||||
fallbackParams_.fogEnd = 1500.0f;
|
||||
fallbackParams_.skyTopColor = glm::vec3(0.4f, 0.6f, 0.9f);
|
||||
fallbackParams_.skyMiddleColor = glm::vec3(0.6f, 0.75f, 0.95f);
|
||||
|
||||
currentParams_ = fallbackParams_;
|
||||
}
|
||||
|
||||
LightingManager::~LightingManager() {
|
||||
}
|
||||
|
||||
bool LightingManager::initialize(pipeline::AssetManager* assetManager) {
|
||||
if (!assetManager) {
|
||||
LOG_ERROR("LightingManager::initialize: null AssetManager");
|
||||
return false;
|
||||
}
|
||||
|
||||
assetManager_ = assetManager;
|
||||
|
||||
// Load DBCs (non-fatal if missing, will use fallback lighting)
|
||||
loadLightDbc(assetManager);
|
||||
loadLightParamsDbc(assetManager);
|
||||
loadLightBandDbcs(assetManager);
|
||||
|
||||
initialized_ = true;
|
||||
LOG_INFO("LightingManager initialized: ", lightVolumesByMap_.size(), " maps with lighting");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LightingManager::loadLightDbc(pipeline::AssetManager* assetManager) {
|
||||
auto dbcData = assetManager->readFile("DBFilesClient\\Light.dbc");
|
||||
if (dbcData.empty()) {
|
||||
LOG_WARNING("Light.dbc not found, using fallback lighting");
|
||||
return false;
|
||||
}
|
||||
|
||||
auto dbc = std::make_unique<pipeline::DBCFile>();
|
||||
if (!dbc->load(dbcData)) {
|
||||
LOG_ERROR("Failed to load Light.dbc");
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t recordCount = dbc->getRecordCount();
|
||||
LOG_INFO("Loading Light.dbc: ", recordCount, " light volumes");
|
||||
|
||||
// Parse light volumes
|
||||
// Light.dbc structure (WotLK 3.3.5a):
|
||||
// 0: uint32 ID
|
||||
// 1: uint32 MapID
|
||||
// 2-4: float X, Z, Y (note: z and y swapped!)
|
||||
// 5: float FalloffStart (inner radius)
|
||||
// 6: float FalloffEnd (outer radius)
|
||||
// 7: uint32 LightParamsID (clear weather)
|
||||
// 8: uint32 LightParamsID (overcast/rain)
|
||||
// 9: uint32 LightParamsID (underwater)
|
||||
// ... more params for death, phases, etc.
|
||||
|
||||
for (uint32_t i = 0; i < recordCount; ++i) {
|
||||
LightVolume volume;
|
||||
volume.lightId = dbc->getUInt32(i, 0);
|
||||
volume.mapId = dbc->getUInt32(i, 1);
|
||||
|
||||
// Position (note: DBC stores as x,z,y - need to swap!)
|
||||
float x = dbc->getFloat(i, 2);
|
||||
float z = dbc->getFloat(i, 3);
|
||||
float y = dbc->getFloat(i, 4);
|
||||
volume.position = glm::vec3(x, y, z); // Convert to x,y,z
|
||||
|
||||
volume.innerRadius = dbc->getFloat(i, 5);
|
||||
volume.outerRadius = dbc->getFloat(i, 6);
|
||||
|
||||
// LightParams IDs for different conditions
|
||||
volume.lightParamsId = dbc->getUInt32(i, 7);
|
||||
if (dbc->getFieldCount() > 8) {
|
||||
volume.lightParamsIdRain = dbc->getUInt32(i, 8);
|
||||
}
|
||||
if (dbc->getFieldCount() > 9) {
|
||||
volume.lightParamsIdUnderwater = dbc->getUInt32(i, 9);
|
||||
}
|
||||
|
||||
// Add to map-specific list
|
||||
lightVolumesByMap_[volume.mapId].push_back(volume);
|
||||
}
|
||||
|
||||
LOG_INFO("Loaded ", lightVolumesByMap_.size(), " maps with lighting volumes");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LightingManager::loadLightParamsDbc(pipeline::AssetManager* assetManager) {
|
||||
auto dbcData = assetManager->readFile("DBFilesClient\\LightParams.dbc");
|
||||
if (dbcData.empty()) {
|
||||
LOG_WARNING("LightParams.dbc not found");
|
||||
return false;
|
||||
}
|
||||
|
||||
auto dbc = std::make_unique<pipeline::DBCFile>();
|
||||
if (!dbc->load(dbcData)) {
|
||||
LOG_ERROR("Failed to load LightParams.dbc");
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t recordCount = dbc->getRecordCount();
|
||||
LOG_INFO("Loaded LightParams.dbc: ", recordCount, " profiles");
|
||||
|
||||
// Create profile entries (will be populated by band loading)
|
||||
for (uint32_t i = 0; i < recordCount; ++i) {
|
||||
uint32_t paramId = dbc->getUInt32(i, 0);
|
||||
LightParamsProfile profile;
|
||||
profile.lightParamsId = paramId;
|
||||
lightParamsProfiles_[paramId] = profile;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LightingManager::loadLightBandDbcs(pipeline::AssetManager* assetManager) {
|
||||
// Load LightIntBand.dbc for RGB color curves (18 channels per LightParams)
|
||||
auto intBandData = assetManager->readFile("DBFilesClient\\LightIntBand.dbc");
|
||||
if (!intBandData.empty()) {
|
||||
auto dbc = std::make_unique<pipeline::DBCFile>();
|
||||
if (dbc->load(intBandData)) {
|
||||
LOG_INFO("Loaded LightIntBand.dbc: ", dbc->getRecordCount(), " color bands");
|
||||
|
||||
// Parse int bands
|
||||
// Structure: ID, Entry (block index), NumValues, Time[16], Color[16]
|
||||
// Block index = LightParamsID * 18 + channel
|
||||
for (uint32_t i = 0; i < dbc->getRecordCount(); ++i) {
|
||||
uint32_t blockIndex = dbc->getUInt32(i, 1);
|
||||
uint32_t lightParamsId = blockIndex / 18;
|
||||
uint32_t channelIndex = blockIndex % 18;
|
||||
|
||||
auto it = lightParamsProfiles_.find(lightParamsId);
|
||||
if (it == lightParamsProfiles_.end()) continue;
|
||||
|
||||
if (channelIndex >= LightParamsProfile::COLOR_CHANNEL_COUNT) continue;
|
||||
|
||||
ColorBand& band = it->second.colorBands[channelIndex];
|
||||
band.numKeyframes = dbc->getUInt32(i, 2);
|
||||
if (band.numKeyframes > 16) band.numKeyframes = 16;
|
||||
|
||||
// Read time keys (field 3-18) - stored as uint16 half-minutes
|
||||
for (uint8_t k = 0; k < band.numKeyframes && k < 16; ++k) {
|
||||
uint32_t timeValue = dbc->getUInt32(i, 3 + k);
|
||||
band.times[k] = static_cast<uint16_t>(timeValue % 2880); // Clamp to valid range
|
||||
}
|
||||
|
||||
// Read color values (field 19-34) - stored as BGRA packed uint32
|
||||
for (uint8_t k = 0; k < band.numKeyframes && k < 16; ++k) {
|
||||
uint32_t colorBGRA = dbc->getUInt32(i, 19 + k);
|
||||
band.colors[k] = dbcColorToVec3(colorBGRA);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Load LightFloatBand.dbc for fog/intensity curves (6 channels per LightParams)
|
||||
auto floatBandData = assetManager->readFile("DBFilesClient\\LightFloatBand.dbc");
|
||||
if (!floatBandData.empty()) {
|
||||
auto dbc = std::make_unique<pipeline::DBCFile>();
|
||||
if (dbc->load(floatBandData)) {
|
||||
LOG_INFO("Loaded LightFloatBand.dbc: ", dbc->getRecordCount(), " float bands");
|
||||
|
||||
// Parse float bands
|
||||
// Structure: ID, Entry (block index), NumValues, Time[16], Value[16]
|
||||
// Block index = LightParamsID * 6 + channel
|
||||
for (uint32_t i = 0; i < dbc->getRecordCount(); ++i) {
|
||||
uint32_t blockIndex = dbc->getUInt32(i, 1);
|
||||
uint32_t lightParamsId = blockIndex / 6;
|
||||
uint32_t channelIndex = blockIndex % 6;
|
||||
|
||||
auto it = lightParamsProfiles_.find(lightParamsId);
|
||||
if (it == lightParamsProfiles_.end()) continue;
|
||||
|
||||
if (channelIndex >= LightParamsProfile::FLOAT_CHANNEL_COUNT) continue;
|
||||
|
||||
FloatBand& band = it->second.floatBands[channelIndex];
|
||||
band.numKeyframes = dbc->getUInt32(i, 2);
|
||||
if (band.numKeyframes > 16) band.numKeyframes = 16;
|
||||
|
||||
// Read time keys (field 3-18)
|
||||
for (uint8_t k = 0; k < band.numKeyframes && k < 16; ++k) {
|
||||
uint32_t timeValue = dbc->getUInt32(i, 3 + k);
|
||||
band.times[k] = static_cast<uint16_t>(timeValue % 2880); // Clamp to valid range
|
||||
}
|
||||
|
||||
// Read float values (field 19-34)
|
||||
for (uint8_t k = 0; k < band.numKeyframes && k < 16; ++k) {
|
||||
band.values[k] = dbc->getFloat(i, 19 + k);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LOG_INFO("Loaded bands for ", lightParamsProfiles_.size(), " LightParams profiles");
|
||||
return true;
|
||||
}
|
||||
|
||||
void LightingManager::update(const glm::vec3& playerPos, uint32_t mapId,
|
||||
float gameTime,
|
||||
bool isRaining, bool isUnderwater) {
|
||||
if (!initialized_) return;
|
||||
|
||||
// Update time
|
||||
if (!manualTime_) {
|
||||
if (gameTime >= 0.0f) {
|
||||
// Use server-sent game time (preferred!)
|
||||
// gameTime is typically seconds since midnight
|
||||
timeOfDay_ = std::fmod(gameTime / 86400.0f, 1.0f); // 0.0-1.0
|
||||
} else {
|
||||
// Fallback: use real time for day/night cycle
|
||||
std::time_t now = std::time(nullptr);
|
||||
std::tm* localTime = std::localtime(&now);
|
||||
float secondsSinceMidnight = localTime->tm_hour * 3600.0f +
|
||||
localTime->tm_min * 60.0f +
|
||||
localTime->tm_sec;
|
||||
timeOfDay_ = secondsSinceMidnight / 86400.0f; // 0.0-1.0
|
||||
}
|
||||
}
|
||||
// else: manualTime_ is set, use timeOfDay_ as-is
|
||||
|
||||
// Convert time to half-minutes (WoW DBC format: 0-2879)
|
||||
uint16_t timeHalfMinutes = static_cast<uint16_t>(timeOfDay_ * 2880.0f) % 2880;
|
||||
|
||||
// Update player position and map
|
||||
currentPlayerPos_ = playerPos;
|
||||
currentMapId_ = mapId;
|
||||
|
||||
// Find light volumes for blending
|
||||
activeVolumes_ = findLightVolumes(playerPos, mapId);
|
||||
|
||||
// Sample and blend lighting
|
||||
LightingParams newParams;
|
||||
|
||||
if (isIndoors_) {
|
||||
// Indoor lighting: static ambient-heavy
|
||||
newParams.ambientColor = glm::vec3(0.6f, 0.6f, 0.65f);
|
||||
newParams.diffuseColor = glm::vec3(0.3f, 0.3f, 0.35f);
|
||||
newParams.fogColor = glm::vec3(0.3f, 0.3f, 0.4f);
|
||||
newParams.fogStart = 50.0f;
|
||||
newParams.fogEnd = 300.0f;
|
||||
} else if (!activeVolumes_.empty()) {
|
||||
// Outdoor with DBC lighting - blend multiple volumes
|
||||
newParams = {}; // Zero-initialize
|
||||
glm::vec3 blendedDir(0.0f); // Accumulate weighted directions
|
||||
|
||||
for (const auto& wv : activeVolumes_) {
|
||||
uint32_t lightParamsId = selectLightParamsId(wv.volume, isRaining, isUnderwater);
|
||||
auto it = lightParamsProfiles_.find(lightParamsId);
|
||||
|
||||
if (it != lightParamsProfiles_.end()) {
|
||||
LightingParams params = sampleLightParams(&it->second, timeHalfMinutes);
|
||||
|
||||
// Blend this volume's contribution
|
||||
newParams.ambientColor += params.ambientColor * wv.weight;
|
||||
newParams.diffuseColor += params.diffuseColor * wv.weight;
|
||||
newParams.fogColor += params.fogColor * wv.weight;
|
||||
newParams.skyTopColor += params.skyTopColor * wv.weight;
|
||||
newParams.skyMiddleColor += params.skyMiddleColor * wv.weight;
|
||||
newParams.skyBand1Color += params.skyBand1Color * wv.weight;
|
||||
newParams.skyBand2Color += params.skyBand2Color * wv.weight;
|
||||
|
||||
newParams.fogStart += params.fogStart * wv.weight;
|
||||
newParams.fogEnd += params.fogEnd * wv.weight;
|
||||
newParams.fogDensity += params.fogDensity * wv.weight;
|
||||
newParams.cloudDensity += params.cloudDensity * wv.weight;
|
||||
newParams.horizonGlow += params.horizonGlow * wv.weight;
|
||||
|
||||
// Blend direction weighted (normalize at end)
|
||||
blendedDir += params.directionalDir * wv.weight;
|
||||
}
|
||||
}
|
||||
|
||||
// Normalize blended direction
|
||||
if (glm::length(blendedDir) > 0.001f) {
|
||||
newParams.directionalDir = glm::normalize(blendedDir);
|
||||
} else {
|
||||
// Fallback if all directions cancelled out
|
||||
newParams.directionalDir = glm::vec3(0.3f, -0.7f, 0.6f);
|
||||
}
|
||||
} else {
|
||||
// No light volume, use fallback with time-based animation
|
||||
newParams = fallbackParams_;
|
||||
|
||||
// Animate sun direction
|
||||
float angle = timeOfDay_ * 2.0f * 3.14159f;
|
||||
newParams.directionalDir = glm::normalize(glm::vec3(
|
||||
std::sin(angle) * 0.6f,
|
||||
-0.6f + std::cos(angle) * 0.4f,
|
||||
std::cos(angle) * 0.6f
|
||||
));
|
||||
|
||||
// Time-of-day color adjustments
|
||||
if (timeOfDay_ < 0.25f || timeOfDay_ > 0.75f) {
|
||||
// Night: darker, bluer
|
||||
float nightness = (timeOfDay_ < 0.25f) ? (0.25f - timeOfDay_) * 4.0f
|
||||
: (timeOfDay_ - 0.75f) * 4.0f;
|
||||
newParams.ambientColor *= (0.3f + 0.7f * (1.0f - nightness));
|
||||
newParams.diffuseColor *= (0.2f + 0.8f * (1.0f - nightness));
|
||||
newParams.ambientColor.b += nightness * 0.1f;
|
||||
}
|
||||
}
|
||||
|
||||
// Smooth temporal blending to avoid snapping (5.0 = blend rate)
|
||||
float deltaTime = 0.016f; // Assume ~60 FPS for now
|
||||
float blendFactor = 1.0f - std::exp(-deltaTime * 5.0f);
|
||||
|
||||
currentParams_.ambientColor = glm::mix(currentParams_.ambientColor, newParams.ambientColor, blendFactor);
|
||||
currentParams_.diffuseColor = glm::mix(currentParams_.diffuseColor, newParams.diffuseColor, blendFactor);
|
||||
currentParams_.fogColor = glm::mix(currentParams_.fogColor, newParams.fogColor, blendFactor);
|
||||
currentParams_.skyTopColor = glm::mix(currentParams_.skyTopColor, newParams.skyTopColor, blendFactor);
|
||||
currentParams_.skyMiddleColor = glm::mix(currentParams_.skyMiddleColor, newParams.skyMiddleColor, blendFactor);
|
||||
currentParams_.skyBand1Color = glm::mix(currentParams_.skyBand1Color, newParams.skyBand1Color, blendFactor);
|
||||
currentParams_.skyBand2Color = glm::mix(currentParams_.skyBand2Color, newParams.skyBand2Color, blendFactor);
|
||||
currentParams_.fogStart = glm::mix(currentParams_.fogStart, newParams.fogStart, blendFactor);
|
||||
currentParams_.fogEnd = glm::mix(currentParams_.fogEnd, newParams.fogEnd, blendFactor);
|
||||
currentParams_.fogDensity = glm::mix(currentParams_.fogDensity, newParams.fogDensity, blendFactor);
|
||||
currentParams_.cloudDensity = glm::mix(currentParams_.cloudDensity, newParams.cloudDensity, blendFactor);
|
||||
currentParams_.horizonGlow = glm::mix(currentParams_.horizonGlow, newParams.horizonGlow, blendFactor);
|
||||
currentParams_.directionalDir = glm::normalize(glm::mix(currentParams_.directionalDir, newParams.directionalDir, blendFactor));
|
||||
}
|
||||
|
||||
std::vector<LightingManager::WeightedVolume> LightingManager::findLightVolumes(const glm::vec3& playerPos, uint32_t mapId) const {
|
||||
auto it = lightVolumesByMap_.find(mapId);
|
||||
if (it == lightVolumesByMap_.end()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const std::vector<LightVolume>& volumes = it->second;
|
||||
if (volumes.empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
// Collect all volumes with weight > 0
|
||||
std::vector<WeightedVolume> weighted;
|
||||
weighted.reserve(volumes.size());
|
||||
|
||||
for (const auto& volume : volumes) {
|
||||
// Apply coordinate scaling (test with 1.0f, try 36.0f if distances are off)
|
||||
glm::vec3 scaledPos = volume.position * LIGHT_COORD_SCALE;
|
||||
float dist = glm::length(playerPos - scaledPos);
|
||||
|
||||
float weight = 0.0f;
|
||||
if (dist <= volume.innerRadius) {
|
||||
// Inside inner radius: full weight
|
||||
weight = 1.0f;
|
||||
} else if (dist < volume.outerRadius) {
|
||||
// Between inner and outer: fade out with smoothstep
|
||||
float t = (dist - volume.innerRadius) / (volume.outerRadius - volume.innerRadius);
|
||||
t = glm::clamp(t, 0.0f, 1.0f);
|
||||
weight = 1.0f - (t * t * (3.0f - 2.0f * t)); // Smoothstep
|
||||
}
|
||||
|
||||
if (weight > 0.0f) {
|
||||
weighted.push_back({&volume, weight});
|
||||
|
||||
// Debug logging for first few volumes
|
||||
if (weighted.size() <= 3) {
|
||||
LOG_INFO("Light volume ", volume.lightId, ": dist=", dist,
|
||||
" inner=", volume.innerRadius, " outer=", volume.outerRadius,
|
||||
" weight=", weight);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (weighted.empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
// Sort by weight descending
|
||||
std::sort(weighted.begin(), weighted.end(),
|
||||
[](const WeightedVolume& a, const WeightedVolume& b) {
|
||||
return a.weight > b.weight;
|
||||
});
|
||||
|
||||
// Keep top N volumes
|
||||
if (weighted.size() > MAX_BLEND_VOLUMES) {
|
||||
weighted.resize(MAX_BLEND_VOLUMES);
|
||||
}
|
||||
|
||||
// Normalize weights to sum to 1.0
|
||||
float totalWeight = 0.0f;
|
||||
for (const auto& wv : weighted) {
|
||||
totalWeight += wv.weight;
|
||||
}
|
||||
|
||||
if (totalWeight > 0.0f) {
|
||||
for (auto& wv : weighted) {
|
||||
wv.weight /= totalWeight;
|
||||
}
|
||||
}
|
||||
|
||||
return weighted;
|
||||
}
|
||||
|
||||
uint32_t LightingManager::selectLightParamsId(const LightVolume* volume, bool isRaining, bool isUnderwater) const {
|
||||
if (!volume) return 0;
|
||||
|
||||
// Select appropriate LightParams based on conditions
|
||||
if (isUnderwater && volume->lightParamsIdUnderwater != 0) {
|
||||
return volume->lightParamsIdUnderwater;
|
||||
} else if (isRaining && volume->lightParamsIdRain != 0) {
|
||||
return volume->lightParamsIdRain;
|
||||
} else {
|
||||
return volume->lightParamsId;
|
||||
}
|
||||
}
|
||||
|
||||
LightingParams LightingManager::sampleLightParams(const LightParamsProfile* profile, uint16_t timeHalfMinutes) const {
|
||||
if (!profile) return fallbackParams_;
|
||||
|
||||
LightingParams params;
|
||||
|
||||
// Sample color bands
|
||||
params.ambientColor = sampleColorBand(profile->colorBands[LightParamsProfile::AMBIENT_COLOR], timeHalfMinutes);
|
||||
params.diffuseColor = sampleColorBand(profile->colorBands[LightParamsProfile::DIFFUSE_COLOR], timeHalfMinutes);
|
||||
params.fogColor = sampleColorBand(profile->colorBands[LightParamsProfile::FOG_COLOR], timeHalfMinutes);
|
||||
params.skyTopColor = sampleColorBand(profile->colorBands[LightParamsProfile::SKY_TOP_COLOR], timeHalfMinutes);
|
||||
params.skyMiddleColor = sampleColorBand(profile->colorBands[LightParamsProfile::SKY_MIDDLE_COLOR], timeHalfMinutes);
|
||||
params.skyBand1Color = sampleColorBand(profile->colorBands[LightParamsProfile::SKY_BAND1_COLOR], timeHalfMinutes);
|
||||
params.skyBand2Color = sampleColorBand(profile->colorBands[LightParamsProfile::SKY_BAND2_COLOR], timeHalfMinutes);
|
||||
|
||||
// Sample float bands
|
||||
params.fogEnd = sampleFloatBand(profile->floatBands[LightParamsProfile::FOG_END], timeHalfMinutes);
|
||||
float fogStartScalar = sampleFloatBand(profile->floatBands[LightParamsProfile::FOG_START_SCALAR], timeHalfMinutes);
|
||||
params.fogStart = params.fogEnd * fogStartScalar; // Start is a scalar of end distance
|
||||
params.fogDensity = sampleFloatBand(profile->floatBands[LightParamsProfile::FOG_DENSITY], timeHalfMinutes);
|
||||
params.cloudDensity = sampleFloatBand(profile->floatBands[LightParamsProfile::CLOUD_DENSITY], timeHalfMinutes);
|
||||
|
||||
// Debug logging for fog params (first few samples only)
|
||||
static int debugCount = 0;
|
||||
if (debugCount < 3) {
|
||||
LOG_INFO("Fog params: start=", params.fogStart, " end=", params.fogEnd,
|
||||
" color=(", params.fogColor.r, ",", params.fogColor.g, ",", params.fogColor.b, ")");
|
||||
debugCount++;
|
||||
}
|
||||
|
||||
// Compute sun direction from time
|
||||
float angle = (timeHalfMinutes / 2880.0f) * 2.0f * 3.14159f;
|
||||
params.directionalDir = glm::normalize(glm::vec3(
|
||||
std::sin(angle) * 0.6f,
|
||||
-0.6f + std::cos(angle) * 0.4f,
|
||||
std::cos(angle) * 0.6f
|
||||
));
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
glm::vec3 LightingManager::sampleColorBand(const ColorBand& band, uint16_t timeHalfMinutes) const {
|
||||
if (band.numKeyframes == 0) {
|
||||
return glm::vec3(0.5f); // Fallback gray
|
||||
}
|
||||
|
||||
if (band.numKeyframes == 1) {
|
||||
return band.colors[0]; // Single keyframe
|
||||
}
|
||||
|
||||
// Safer initialization: default to wrapping last→first
|
||||
uint8_t idx1 = band.numKeyframes - 1;
|
||||
uint8_t idx2 = 0;
|
||||
|
||||
// Find surrounding keyframes
|
||||
for (uint8_t i = 0; i < band.numKeyframes; ++i) {
|
||||
if (timeHalfMinutes < band.times[i]) {
|
||||
idx2 = i;
|
||||
idx1 = (i > 0) ? (i - 1) : (band.numKeyframes - 1); // Wrap to last
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate interpolation factor
|
||||
uint16_t t1 = band.times[idx1];
|
||||
uint16_t t2 = band.times[idx2];
|
||||
|
||||
// Handle midnight wrap
|
||||
uint16_t timeSpan = (t2 > t1) ? (t2 - t1) : (2880 - t1 + t2);
|
||||
uint16_t elapsed = (timeHalfMinutes >= t1) ? (timeHalfMinutes - t1) : (2880 - t1 + timeHalfMinutes);
|
||||
|
||||
float t = (timeSpan > 0) ? (static_cast<float>(elapsed) / static_cast<float>(timeSpan)) : 0.0f;
|
||||
t = glm::clamp(t, 0.0f, 1.0f);
|
||||
|
||||
// Linear interpolation
|
||||
return glm::mix(band.colors[idx1], band.colors[idx2], t);
|
||||
}
|
||||
|
||||
float LightingManager::sampleFloatBand(const FloatBand& band, uint16_t timeHalfMinutes) const {
|
||||
if (band.numKeyframes == 0) {
|
||||
return 1.0f; // Fallback
|
||||
}
|
||||
|
||||
if (band.numKeyframes == 1) {
|
||||
return band.values[0];
|
||||
}
|
||||
|
||||
// Safer initialization: default to wrapping last→first
|
||||
uint8_t idx1 = band.numKeyframes - 1;
|
||||
uint8_t idx2 = 0;
|
||||
|
||||
// Find surrounding keyframes
|
||||
for (uint8_t i = 0; i < band.numKeyframes; ++i) {
|
||||
if (timeHalfMinutes < band.times[i]) {
|
||||
idx2 = i;
|
||||
idx1 = (i > 0) ? (i - 1) : (band.numKeyframes - 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t t1 = band.times[idx1];
|
||||
uint16_t t2 = band.times[idx2];
|
||||
|
||||
uint16_t timeSpan = (t2 > t1) ? (t2 - t1) : (2880 - t1 + t2);
|
||||
uint16_t elapsed = (timeHalfMinutes >= t1) ? (timeHalfMinutes - t1) : (2880 - t1 + timeHalfMinutes);
|
||||
|
||||
float t = (timeSpan > 0) ? (static_cast<float>(elapsed) / static_cast<float>(timeSpan)) : 0.0f;
|
||||
t = glm::clamp(t, 0.0f, 1.0f);
|
||||
|
||||
return glm::mix(band.values[idx1], band.values[idx2], t);
|
||||
}
|
||||
|
||||
glm::vec3 LightingManager::dbcColorToVec3(uint32_t dbcColor) const {
|
||||
// DBC colors are stored as BGR (0x00BBGGRR on little-endian)
|
||||
uint8_t b = (dbcColor >> 16) & 0xFF;
|
||||
uint8_t g = (dbcColor >> 8) & 0xFF;
|
||||
uint8_t r = dbcColor & 0xFF;
|
||||
|
||||
return glm::vec3(r / 255.0f, g / 255.0f, b / 255.0f);
|
||||
}
|
||||
|
||||
} // namespace rendering
|
||||
} // namespace wowee
|
||||
|
|
@ -3010,15 +3010,14 @@ void GameScreen::renderBuffBar(game::GameHandler& gameHandler) {
|
|||
}
|
||||
if (activeCount == 0) return;
|
||||
|
||||
auto* window = core::Application::getInstance().getWindow();
|
||||
float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f;
|
||||
auto* assetMgr = core::Application::getInstance().getAssetManager();
|
||||
|
||||
// Position below the minimap (minimap is 200px + 10px margin from top-right)
|
||||
// Position below the player frame in top-left
|
||||
constexpr float ICON_SIZE = 32.0f;
|
||||
constexpr int ICONS_PER_ROW = 8;
|
||||
float barW = ICONS_PER_ROW * (ICON_SIZE + 4.0f) + 8.0f;
|
||||
ImGui::SetNextWindowPos(ImVec2(screenW - barW - 10.0f, 220.0f), ImGuiCond_Always);
|
||||
// Dock under player frame in top-left (player frame is at 10, 30 with ~110px height)
|
||||
ImGui::SetNextWindowPos(ImVec2(10.0f, 145.0f), ImGuiCond_Always);
|
||||
ImGui::SetNextWindowSize(ImVec2(barW, 0), ImGuiCond_Always);
|
||||
|
||||
ImGuiWindowFlags flags = ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove |
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue