mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-23 15:50:20 +00:00
553 lines
22 KiB
C++
553 lines
22 KiB
C++
|
|
#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
|