Enhanced sky atmosphere with DBC-driven colors, sun lighting, and zone weather

- Skybox now uses DBC sky colors (skyTop/skyMiddle/skyBand1/skyBand2) instead
  of hardcoded C++ color curves, with 3-band gradient and Rayleigh/Mie scattering
- Clouds receive sun direction for lit edges, self-shadowing, and silver lining
- Fixed sun quad box artifact with proper edge fade in celestial shader
- Lens flare attenuated by fog, cloud density, and weather intensity
- Replaced garish green/purple lens flare ghosts with warm natural palette
- Added zone-based weather system for single-player mode with per-zone rain/snow
  configuration, probability-based activation, and smooth intensity transitions
- Server SMSG_WEATHER remains authoritative when connected to a server
This commit is contained in:
Kelsi 2026-02-22 23:20:13 -08:00
parent 085fd09b9d
commit 6563eebb60
18 changed files with 434 additions and 252 deletions

View file

@ -1,4 +1,5 @@
#include "rendering/clouds.hpp"
#include "rendering/sky_system.hpp"
#include "rendering/vk_context.hpp"
#include "rendering/vk_shader.hpp"
#include "rendering/vk_pipeline.hpp"
@ -40,11 +41,11 @@ bool Clouds::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout) {
VkPipelineShaderStageCreateInfo fragStage = fragModule.stageInfo(VK_SHADER_STAGE_FRAGMENT_BIT);
// ------------------------------------------------------------------ push constants
// Fragment-only push: vec4 cloudColor + float density + float windOffset = 24 bytes
// Fragment-only push: 3 x vec4 = 48 bytes
VkPushConstantRange pushRange{};
pushRange.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
pushRange.offset = 0;
pushRange.size = sizeof(CloudPush); // 24 bytes
pushRange.size = sizeof(CloudPush); // 48 bytes
// ------------------------------------------------------------------ pipeline layout
pipelineLayout_ = createPipelineLayout(device, {perFrameLayout}, {pushRange});
@ -54,10 +55,9 @@ bool Clouds::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout) {
}
// ------------------------------------------------------------------ vertex input
// Vertex: vec3 pos only, stride = 12 bytes
VkVertexInputBindingDescription binding{};
binding.binding = 0;
binding.stride = sizeof(glm::vec3); // 12 bytes
binding.stride = sizeof(glm::vec3);
binding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
VkVertexInputAttributeDescription posAttr{};
@ -77,7 +77,7 @@ bool Clouds::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout) {
.setVertexInput({binding}, {posAttr})
.setTopology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST)
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
.setDepthTest(true, false, VK_COMPARE_OP_LESS_OR_EQUAL) // test on, write off (sky layer)
.setDepthTest(true, false, VK_COMPARE_OP_LESS_OR_EQUAL)
.setColorBlendAttachment(PipelineBuilder::blendAlpha())
.setMultisample(vkCtx_->getMsaaSamples())
.setLayout(pipelineLayout_)
@ -122,7 +122,6 @@ void Clouds::recreatePipelines() {
VkPipelineShaderStageCreateInfo vertStage = vertModule.stageInfo(VK_SHADER_STAGE_VERTEX_BIT);
VkPipelineShaderStageCreateInfo fragStage = fragModule.stageInfo(VK_SHADER_STAGE_FRAGMENT_BIT);
// Vertex input (same as initialize)
VkVertexInputBindingDescription binding{};
binding.binding = 0;
binding.stride = sizeof(glm::vec3);
@ -182,25 +181,35 @@ void Clouds::shutdown() {
// Render
// ---------------------------------------------------------------------------
void Clouds::render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, float timeOfDay) {
void Clouds::render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, const SkyParams& params) {
if (!enabled_ || pipeline_ == VK_NULL_HANDLE) {
return;
}
glm::vec3 color = getCloudColor(timeOfDay);
// Derive cloud base color from DBC horizon band, slightly brightened
glm::vec3 cloudBaseColor = params.skyBand1Color * 1.1f;
cloudBaseColor = glm::clamp(cloudBaseColor, glm::vec3(0.0f), glm::vec3(1.0f));
// Sun direction (opposite of light direction)
glm::vec3 sunDir = -glm::normalize(params.directionalDir);
float sunAboveHorizon = glm::clamp(sunDir.z, 0.0f, 1.0f);
// Sun intensity based on elevation
float sunIntensity = sunAboveHorizon;
// Ambient light — brighter during day, dimmer at night
float ambient = glm::mix(0.3f, 0.7f, sunAboveHorizon);
CloudPush push{};
push.cloudColor = glm::vec4(color, 1.0f);
push.density = density_;
push.windOffset = windOffset_;
push.cloudColor = glm::vec4(cloudBaseColor, 1.0f);
push.sunDirDensity = glm::vec4(sunDir, density_);
push.windAndLight = glm::vec4(windOffset_, sunIntensity, ambient, 0.0f);
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_);
// Bind per-frame UBO (set 0 — vertex shader reads view/projection from here)
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout_,
0, 1, &perFrameSet, 0, nullptr);
// Push cloud params to fragment shader
vkCmdPushConstants(cmd, pipelineLayout_,
VK_SHADER_STAGE_FRAGMENT_BIT,
0, sizeof(push), &push);
@ -223,29 +232,6 @@ void Clouds::update(float deltaTime) {
windOffset_ += deltaTime * windSpeed_ * 0.05f; // Slow drift
}
// ---------------------------------------------------------------------------
// Cloud colour (unchanged logic from GL version)
// ---------------------------------------------------------------------------
glm::vec3 Clouds::getCloudColor(float timeOfDay) const {
glm::vec3 dayColor(0.95f, 0.95f, 1.0f);
if (timeOfDay >= 5.0f && timeOfDay < 7.0f) {
// Dawn — orange tint fading to day
float t = (timeOfDay - 5.0f) / 2.0f;
return glm::mix(glm::vec3(1.0f, 0.7f, 0.5f), dayColor, t);
} else if (timeOfDay >= 17.0f && timeOfDay < 19.0f) {
// Dusk — day fading to orange/pink
float t = (timeOfDay - 17.0f) / 2.0f;
return glm::mix(dayColor, glm::vec3(1.0f, 0.6f, 0.4f), t);
} else if (timeOfDay >= 20.0f || timeOfDay < 5.0f) {
// Night — dark blue-grey
return glm::vec3(0.15f, 0.15f, 0.25f);
}
return dayColor;
}
// ---------------------------------------------------------------------------
// Density setter
// ---------------------------------------------------------------------------
@ -265,7 +251,7 @@ void Clouds::generateMesh() {
// Upper hemisphere — Z-up world: altitude goes into Z, horizontal spread in X/Y
for (int ring = 0; ring <= RINGS; ++ring) {
float phi = (ring / static_cast<float>(RINGS)) * (static_cast<float>(M_PI) * 0.5f);
float altZ = RADIUS * std::cos(phi); // altitude → world Z (up)
float altZ = RADIUS * std::cos(phi);
float ringRadius = RADIUS * std::sin(phi);
for (int seg = 0; seg <= SEGMENTS; ++seg) {

View file

@ -211,28 +211,27 @@ void LensFlare::generateFlareElements() {
flareElements.push_back({0.0f, 0.3f, glm::vec3(1.0f, 0.95f, 0.8f), 0.8f});
// Flare ghosts along sun-to-center axis
// These appear at various positions between sun and opposite side
// Bright white ghost near sun
flareElements.push_back({0.2f, 0.08f, glm::vec3(1.0f, 1.0f, 1.0f), 0.5f});
// Blue-tinted ghost
flareElements.push_back({0.4f, 0.15f, glm::vec3(0.3f, 0.5f, 1.0f), 0.4f});
flareElements.push_back({0.4f, 0.15f, glm::vec3(0.4f, 0.55f, 0.9f), 0.35f});
// Small bright spot
flareElements.push_back({0.6f, 0.05f, glm::vec3(1.0f, 0.8f, 0.6f), 0.6f});
flareElements.push_back({0.6f, 0.05f, glm::vec3(1.0f, 0.8f, 0.6f), 0.5f});
// Green-tinted ghost (chromatic aberration)
flareElements.push_back({0.8f, 0.12f, glm::vec3(0.4f, 1.0f, 0.5f), 0.3f});
// Warm amber ghost (replaced oversaturated green)
flareElements.push_back({0.8f, 0.10f, glm::vec3(0.9f, 0.75f, 0.5f), 0.2f});
// Large halo on opposite side
flareElements.push_back({-0.5f, 0.25f, glm::vec3(1.0f, 0.7f, 0.4f), 0.2f});
flareElements.push_back({-0.5f, 0.22f, glm::vec3(1.0f, 0.8f, 0.5f), 0.15f});
// Purple ghost far from sun
flareElements.push_back({-0.8f, 0.1f, glm::vec3(0.8f, 0.4f, 1.0f), 0.25f});
// Faint blue ghost far from sun
flareElements.push_back({-0.8f, 0.08f, glm::vec3(0.6f, 0.5f, 0.9f), 0.15f});
// Small red ghost
flareElements.push_back({-1.2f, 0.06f, glm::vec3(1.0f, 0.3f, 0.3f), 0.3f});
// Small warm ghost
flareElements.push_back({-1.2f, 0.05f, glm::vec3(1.0f, 0.6f, 0.4f), 0.2f});
}
glm::vec2 LensFlare::worldToScreen(const Camera& camera, const glm::vec3& worldPos) const {
@ -287,7 +286,9 @@ float LensFlare::calculateSunVisibility(const Camera& camera, const glm::vec3& s
return angleFactor * edgeFade;
}
void LensFlare::render(VkCommandBuffer cmd, const Camera& camera, const glm::vec3& sunPosition, float timeOfDay) {
void LensFlare::render(VkCommandBuffer cmd, const Camera& camera, const glm::vec3& sunPosition,
float timeOfDay, float fogDensity, float cloudDensity,
float weatherIntensity) {
if (!enabled || pipeline == VK_NULL_HANDLE) {
return;
}
@ -312,6 +313,16 @@ void LensFlare::render(VkCommandBuffer cmd, const Camera& camera, const glm::vec
return;
}
// Atmospheric attenuation — fog, clouds, and weather reduce lens flare
float atmosphericFactor = 1.0f;
atmosphericFactor *= (1.0f - glm::clamp(fogDensity * 0.8f, 0.0f, 0.9f)); // Heavy fog nearly kills flare
atmosphericFactor *= (1.0f - glm::clamp(cloudDensity * 0.6f, 0.0f, 0.7f)); // Clouds attenuate
atmosphericFactor *= (1.0f - glm::clamp(weatherIntensity * 0.9f, 0.0f, 0.95f)); // Rain/snow heavily attenuates
if (atmosphericFactor < 0.01f) {
return;
}
// Get sun screen position
glm::vec2 sunScreen = worldToScreen(camera, anchoredSunPos);
glm::vec2 screenCenter(0.0f, 0.0f);
@ -333,8 +344,8 @@ void LensFlare::render(VkCommandBuffer cmd, const Camera& camera, const glm::vec
// Calculate position along sun-to-center axis
glm::vec2 position = sunScreen + sunToCenter * element.position;
// Apply visibility and intensity
float brightness = element.brightness * visibility * intensityMultiplier;
// Apply visibility, intensity, and atmospheric attenuation
float brightness = element.brightness * visibility * intensityMultiplier * atmosphericFactor;
// Set push constants
FlarePushConstants push{};

View file

@ -2417,10 +2417,21 @@ void Renderer::update(float deltaTime) {
if (weather && gh) {
uint32_t wType = gh->getWeatherType();
float wInt = gh->getWeatherIntensity();
if (wType == 1) weather->setWeatherType(Weather::Type::RAIN);
else if (wType == 2) weather->setWeatherType(Weather::Type::SNOW);
else weather->setWeatherType(Weather::Type::NONE);
weather->setIntensity(wInt);
if (wType != 0) {
// Server-driven weather (SMSG_WEATHER) — authoritative
if (wType == 1) weather->setWeatherType(Weather::Type::RAIN);
else if (wType == 2) weather->setWeatherType(Weather::Type::SNOW);
else weather->setWeatherType(Weather::Type::NONE);
weather->setIntensity(wInt);
} else {
// No server weather — use zone-based weather configuration
weather->updateZoneWeather(currentZoneId, deltaTime);
}
weather->setEnabled(true);
} else if (weather) {
// No game handler (single-player without network) — zone weather only
weather->updateZoneWeather(currentZoneId, deltaTime);
weather->setEnabled(true);
}
}
auto light2 = std::chrono::high_resolution_clock::now();
@ -3200,6 +3211,11 @@ void Renderer::renderWorld(game::World* world, game::GameHandler* gameHandler) {
skyParams.horizonGlow = lighting.horizonGlow;
}
// Weather attenuation for lens flare
if (gameHandler) {
skyParams.weatherIntensity = gameHandler->getWeatherIntensity();
}
skyParams.skyboxModelId = 0;
skyParams.skyboxHasStars = false;
@ -3866,6 +3882,7 @@ void Renderer::renderReflectionPass() {
skyParams.fogDensity = lp.fogDensity;
skyParams.horizonGlow = lp.horizonGlow;
}
// weatherIntensity left at default 0 for reflection pass (no game handler in scope)
skySystem->render(currentCmd, reflPerFrameDescSet, *camera, skyParams);
}
if (terrainRenderer && terrainEnabled) {

View file

@ -105,9 +105,9 @@ void SkySystem::render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet,
return;
}
// --- Skybox (authoritative sky gradient) ---
// --- Skybox (authoritative sky gradient, DBC-driven colors) ---
if (skybox_) {
skybox_->render(cmd, perFrameSet, params.timeOfDay);
skybox_->render(cmd, perFrameSet, params);
}
// --- Procedural stars (debug / fallback) ---
@ -133,15 +133,17 @@ void SkySystem::render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet,
&params.directionalDir, &params.sunColor, params.gameTime);
}
// --- Clouds ---
// --- Clouds (DBC-driven colors + sun lighting) ---
if (clouds_) {
clouds_->render(cmd, perFrameSet, params.timeOfDay);
clouds_->render(cmd, perFrameSet, params);
}
// --- Lens flare ---
// --- Lens flare (attenuated by atmosphere) ---
if (lensFlare_) {
glm::vec3 sunPos = getSunPosition(params);
lensFlare_->render(cmd, camera, sunPos, params.timeOfDay);
lensFlare_->render(cmd, camera, sunPos, params.timeOfDay,
params.fogDensity, params.cloudDensity,
params.weatherIntensity);
}
}

View file

@ -1,4 +1,5 @@
#include "rendering/skybox.hpp"
#include "rendering/sky_system.hpp"
#include "rendering/vk_context.hpp"
#include "rendering/vk_shader.hpp"
#include "rendering/vk_pipeline.hpp"
@ -10,6 +11,16 @@
namespace wowee {
namespace rendering {
// Push constant struct — must match skybox.frag.glsl layout
struct SkyPushConstants {
glm::vec4 zenithColor; // DBC skyTopColor
glm::vec4 midColor; // DBC skyMiddleColor
glm::vec4 horizonColor; // DBC skyBand1Color
glm::vec4 fogColor; // DBC skyBand2Color / fogColor blend
glm::vec4 sunDirAndTime; // xyz = sun direction, w = timeOfDay
};
static_assert(sizeof(SkyPushConstants) == 80, "SkyPushConstants size mismatch");
Skybox::Skybox() = default;
Skybox::~Skybox() {
@ -39,11 +50,11 @@ bool Skybox::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout) {
VkPipelineShaderStageCreateInfo vertStage = vertModule.stageInfo(VK_SHADER_STAGE_VERTEX_BIT);
VkPipelineShaderStageCreateInfo fragStage = fragModule.stageInfo(VK_SHADER_STAGE_FRAGMENT_BIT);
// Push constant range: horizonColor (vec4) + zenithColor (vec4) + timeOfDay (float) = 36 bytes
// Push constant range: 5 x vec4 = 80 bytes
VkPushConstantRange pushRange{};
pushRange.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT;
pushRange.offset = 0;
pushRange.size = sizeof(glm::vec4) + sizeof(glm::vec4) + sizeof(float); // 36 bytes
pushRange.size = sizeof(SkyPushConstants); // 80 bytes
// Create pipeline layout with perFrameLayout (set 0) + push constants
pipelineLayout = createPipelineLayout(device, {perFrameLayout}, {pushRange});
@ -148,24 +159,20 @@ void Skybox::shutdown() {
vkCtx = nullptr;
}
void Skybox::render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, float time) {
void Skybox::render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, const SkyParams& params) {
if (pipeline == VK_NULL_HANDLE || !renderingEnabled) {
return;
}
// Push constant data
struct SkyPushConstants {
glm::vec4 horizonColor;
glm::vec4 zenithColor;
float timeOfDay;
};
// Compute sun direction from directionalDir (light points toward scene, sun is opposite)
glm::vec3 sunDir = -glm::normalize(params.directionalDir);
SkyPushConstants push{};
glm::vec3 horizon = getHorizonColor(time);
glm::vec3 zenith = getZenithColor(time);
push.horizonColor = glm::vec4(horizon, 1.0f);
push.zenithColor = glm::vec4(zenith, 1.0f);
push.timeOfDay = time;
push.zenithColor = glm::vec4(params.skyTopColor, 1.0f);
push.midColor = glm::vec4(params.skyMiddleColor, 1.0f);
push.horizonColor = glm::vec4(params.skyBand1Color, 1.0f);
push.fogColor = glm::vec4(params.skyBand2Color, 1.0f);
push.sunDirAndTime = glm::vec4(sunDir, params.timeOfDay);
// Bind pipeline
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
@ -202,103 +209,5 @@ void Skybox::setTimeOfDay(float time) {
timeOfDay = time;
}
glm::vec3 Skybox::getHorizonColor(float time) const {
// Time-based horizon colors
// 0-6: Night (dark blue)
// 6-8: Dawn (orange/pink)
// 8-16: Day (light blue)
// 16-18: Dusk (orange/red)
// 18-24: Night (dark blue)
if (time < 5.0f || time >= 21.0f) {
// Night - dark blue/purple horizon
return glm::vec3(0.05f, 0.05f, 0.15f);
}
else if (time >= 5.0f && time < 7.0f) {
// Dawn - blend from night to orange
float t = (time - 5.0f) / 2.0f;
glm::vec3 night = glm::vec3(0.05f, 0.05f, 0.15f);
glm::vec3 dawn = glm::vec3(1.0f, 0.5f, 0.2f);
return glm::mix(night, dawn, t);
}
else if (time >= 7.0f && time < 9.0f) {
// Morning - blend from orange to blue
float t = (time - 7.0f) / 2.0f;
glm::vec3 dawn = glm::vec3(1.0f, 0.5f, 0.2f);
glm::vec3 day = glm::vec3(0.6f, 0.7f, 0.9f);
return glm::mix(dawn, day, t);
}
else if (time >= 9.0f && time < 17.0f) {
// Day - light blue horizon
return glm::vec3(0.6f, 0.7f, 0.9f);
}
else if (time >= 17.0f && time < 19.0f) {
// Dusk - blend from blue to orange/red
float t = (time - 17.0f) / 2.0f;
glm::vec3 day = glm::vec3(0.6f, 0.7f, 0.9f);
glm::vec3 dusk = glm::vec3(1.0f, 0.4f, 0.1f);
return glm::mix(day, dusk, t);
}
else {
// Evening - blend from orange to night
float t = (time - 19.0f) / 2.0f;
glm::vec3 dusk = glm::vec3(1.0f, 0.4f, 0.1f);
glm::vec3 night = glm::vec3(0.05f, 0.05f, 0.15f);
return glm::mix(dusk, night, t);
}
}
glm::vec3 Skybox::getZenithColor(float time) const {
// Zenith (top of sky) colors
if (time < 5.0f || time >= 21.0f) {
// Night - very dark blue, almost black
return glm::vec3(0.01f, 0.01f, 0.05f);
}
else if (time >= 5.0f && time < 7.0f) {
// Dawn - blend from night to light blue
float t = (time - 5.0f) / 2.0f;
glm::vec3 night = glm::vec3(0.01f, 0.01f, 0.05f);
glm::vec3 dawn = glm::vec3(0.3f, 0.4f, 0.7f);
return glm::mix(night, dawn, t);
}
else if (time >= 7.0f && time < 9.0f) {
// Morning - blend to bright blue
float t = (time - 7.0f) / 2.0f;
glm::vec3 dawn = glm::vec3(0.3f, 0.4f, 0.7f);
glm::vec3 day = glm::vec3(0.2f, 0.5f, 1.0f);
return glm::mix(dawn, day, t);
}
else if (time >= 9.0f && time < 17.0f) {
// Day - bright blue zenith
return glm::vec3(0.2f, 0.5f, 1.0f);
}
else if (time >= 17.0f && time < 19.0f) {
// Dusk - blend to darker blue
float t = (time - 17.0f) / 2.0f;
glm::vec3 day = glm::vec3(0.2f, 0.5f, 1.0f);
glm::vec3 dusk = glm::vec3(0.1f, 0.2f, 0.4f);
return glm::mix(day, dusk, t);
}
else {
// Evening - blend to night
float t = (time - 19.0f) / 2.0f;
glm::vec3 dusk = glm::vec3(0.1f, 0.2f, 0.4f);
glm::vec3 night = glm::vec3(0.01f, 0.01f, 0.05f);
return glm::mix(dusk, night, t);
}
}
glm::vec3 Skybox::getSkyColor(float altitude, float time) const {
// Blend between horizon and zenith based on altitude
glm::vec3 horizon = getHorizonColor(time);
glm::vec3 zenith = getZenithColor(time);
// Use power curve for more natural gradient
float t = std::pow(std::max(altitude, 0.0f), 0.5f);
return glm::mix(horizon, zenith, t);
}
} // namespace rendering
} // namespace wowee

View file

@ -369,5 +369,134 @@ void Weather::shutdown() {
particlePositions.clear();
}
// ---------------------------------------------------------------------------
// Zone-based weather configuration
// ---------------------------------------------------------------------------
void Weather::setZoneWeather(uint32_t zoneId, Type type, float minIntensity, float maxIntensity, float probability) {
zoneWeatherTable_[zoneId] = {type, minIntensity, maxIntensity, probability};
}
void Weather::initializeZoneWeatherDefaults() {
if (zoneWeatherInitialized_) return;
zoneWeatherInitialized_ = true;
// Eastern Kingdoms zones
setZoneWeather(10, Type::RAIN, 0.2f, 0.6f, 0.3f); // Duskwood — frequent rain
setZoneWeather(11, Type::RAIN, 0.1f, 0.4f, 0.15f); // Wetlands — moderate rain
setZoneWeather(8, Type::RAIN, 0.1f, 0.5f, 0.2f); // Swamp of Sorrows
setZoneWeather(33, Type::RAIN, 0.2f, 0.7f, 0.25f); // Stranglethorn Vale
setZoneWeather(44, Type::RAIN, 0.1f, 0.3f, 0.1f); // Redridge Mountains — light rain
setZoneWeather(36, Type::RAIN, 0.1f, 0.4f, 0.15f); // Alterac Mountains
setZoneWeather(45, Type::RAIN, 0.1f, 0.3f, 0.1f); // Arathi Highlands
setZoneWeather(267, Type::RAIN, 0.2f, 0.5f, 0.2f); // Hillsbrad Foothills
setZoneWeather(28, Type::RAIN, 0.1f, 0.3f, 0.1f); // Western Plaguelands — occasional rain
setZoneWeather(139, Type::RAIN, 0.1f, 0.3f, 0.1f); // Eastern Plaguelands
// Snowy zones
setZoneWeather(1, Type::SNOW, 0.2f, 0.6f, 0.3f); // Dun Morogh
setZoneWeather(51, Type::SNOW, 0.1f, 0.5f, 0.2f); // Searing Gorge (occasional)
setZoneWeather(41, Type::SNOW, 0.1f, 0.4f, 0.15f); // Deadwind Pass
setZoneWeather(2817, Type::SNOW, 0.3f, 0.7f, 0.4f); // Crystalsong Forest
setZoneWeather(67, Type::SNOW, 0.2f, 0.6f, 0.35f); // Storm Peaks
setZoneWeather(65, Type::SNOW, 0.2f, 0.5f, 0.3f); // Dragonblight
setZoneWeather(394, Type::SNOW, 0.1f, 0.4f, 0.2f); // Grizzly Hills
setZoneWeather(495, Type::SNOW, 0.3f, 0.8f, 0.5f); // Howling Fjord
setZoneWeather(210, Type::SNOW, 0.2f, 0.5f, 0.25f); // Icecrown
setZoneWeather(3537, Type::SNOW, 0.2f, 0.6f, 0.3f); // Borean Tundra
setZoneWeather(4742, Type::SNOW, 0.2f, 0.5f, 0.3f); // Hrothgar's Landing
// Kalimdor zones
setZoneWeather(15, Type::RAIN, 0.1f, 0.4f, 0.15f); // Dustwallow Marsh
setZoneWeather(16, Type::RAIN, 0.1f, 0.3f, 0.1f); // Azshara
setZoneWeather(148, Type::RAIN, 0.1f, 0.4f, 0.15f); // Darkshore
setZoneWeather(331, Type::RAIN, 0.1f, 0.3f, 0.1f); // Ashenvale
setZoneWeather(405, Type::RAIN, 0.1f, 0.3f, 0.1f); // Desolace
setZoneWeather(15, Type::RAIN, 0.2f, 0.5f, 0.2f); // Dustwallow Marsh
setZoneWeather(490, Type::RAIN, 0.1f, 0.4f, 0.15f); // Un'Goro Crater
setZoneWeather(493, Type::RAIN, 0.1f, 0.3f, 0.1f); // Moonglade
// Winterspring is snowy
setZoneWeather(618, Type::SNOW, 0.2f, 0.6f, 0.3f); // Winterspring
// Outland
setZoneWeather(3483, Type::RAIN, 0.1f, 0.3f, 0.1f); // Hellfire Peninsula (occasional)
setZoneWeather(3521, Type::RAIN, 0.1f, 0.4f, 0.15f); // Zangarmarsh
setZoneWeather(3519, Type::RAIN, 0.1f, 0.3f, 0.1f); // Terokkar Forest
}
void Weather::updateZoneWeather(uint32_t zoneId, float deltaTime) {
if (!zoneWeatherInitialized_) {
initializeZoneWeatherDefaults();
}
// Zone changed — reset weather cycle
if (zoneId != currentWeatherZone_) {
currentWeatherZone_ = zoneId;
zoneWeatherTimer_ = 0.0f;
auto it = zoneWeatherTable_.find(zoneId);
if (it == zoneWeatherTable_.end()) {
// Zone has no configured weather — clear gradually
targetIntensity_ = 0.0f;
} else {
// Roll whether weather is active based on probability
float roll = static_cast<float>(rand()) / static_cast<float>(RAND_MAX);
zoneWeatherActive_ = (roll < it->second.probability);
if (zoneWeatherActive_) {
weatherType = it->second.type;
// Random intensity within configured range
float t = static_cast<float>(rand()) / static_cast<float>(RAND_MAX);
targetIntensity_ = glm::mix(it->second.minIntensity, it->second.maxIntensity, t);
// Random cycle duration: 3-8 minutes
zoneWeatherCycleDuration_ = 180.0f + static_cast<float>(rand()) / static_cast<float>(RAND_MAX) * 300.0f;
} else {
targetIntensity_ = 0.0f;
zoneWeatherCycleDuration_ = 120.0f + static_cast<float>(rand()) / static_cast<float>(RAND_MAX) * 180.0f;
}
}
}
// Smooth intensity transitions
float transitionSpeed = 0.15f * deltaTime; // ~7 seconds to full transition
if (intensity < targetIntensity_) {
intensity = std::min(intensity + transitionSpeed, targetIntensity_);
} else if (intensity > targetIntensity_) {
intensity = std::max(intensity - transitionSpeed, targetIntensity_);
}
// If intensity reached zero and target is zero, clear weather type
if (intensity <= 0.01f && targetIntensity_ <= 0.01f) {
if (weatherType != Type::NONE) {
weatherType = Type::NONE;
particles.clear();
}
}
// Weather cycling — periodically re-roll weather
zoneWeatherTimer_ += deltaTime;
if (zoneWeatherTimer_ >= zoneWeatherCycleDuration_ && zoneWeatherCycleDuration_ > 0.0f) {
zoneWeatherTimer_ = 0.0f;
auto it = zoneWeatherTable_.find(zoneId);
if (it != zoneWeatherTable_.end()) {
float roll = static_cast<float>(rand()) / static_cast<float>(RAND_MAX);
zoneWeatherActive_ = (roll < it->second.probability);
if (zoneWeatherActive_) {
weatherType = it->second.type;
float t = static_cast<float>(rand()) / static_cast<float>(RAND_MAX);
targetIntensity_ = glm::mix(it->second.minIntensity, it->second.maxIntensity, t);
} else {
targetIntensity_ = 0.0f;
}
// New cycle duration
zoneWeatherCycleDuration_ = 180.0f + static_cast<float>(rand()) / static_cast<float>(RAND_MAX) * 300.0f;
}
}
}
} // namespace rendering
} // namespace wowee