diff --git a/CMakeLists.txt b/CMakeLists.txt index 2ea402fb..752a42db 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -143,6 +143,7 @@ set(WOWEE_SOURCES src/rendering/weather.cpp src/rendering/lightning.cpp src/rendering/lighting_manager.cpp + src/rendering/sky_system.cpp src/rendering/character_renderer.cpp src/rendering/character_preview.cpp src/rendering/wmo_renderer.cpp diff --git a/README.md b/README.md index c309dc3e..973a662b 100644 --- a/README.md +++ b/README.md @@ -9,10 +9,12 @@ A native C++ client for World of Warcraft 3.3.5a (Wrath of the Lich King) with a ### Rendering Engine - **Terrain** -- Multi-tile streaming with async loading, texture splatting (4 layers), frustum culling - **Water** -- Animated surfaces, reflections, refractions, Fresnel effect -- **Sky** -- Dynamic day/night cycle, sun/moon with orbital movement -- **Stars** -- 1000+ procedurally placed stars (night-only) -- **Atmosphere** -- Procedural clouds (FBM noise), lens flare with chromatic aberration -- **Moon Phases** -- 8 realistic lunar phases with elliptical terminator +- **Sky System** -- WoW-accurate DBC-driven lighting with skybox authority + - **Skybox** -- Camera-locked celestial sphere (M2 model support, gradient fallback) + - **Celestial Bodies** -- Sun (lighting-driven), White Lady + Blue Child (Azeroth's two moons) + - **Moon Phases** -- Server time-driven deterministic phases (30-day / 27-day cycles) + - **Stars** -- Baked into skybox assets (procedural fallback for development/debug only) +- **Atmosphere** -- Procedural clouds (FBM noise), lens flare with chromatic aberration, cloud/fog star occlusion - **Weather** -- Rain and snow particle systems (2000 particles, camera-relative) - **Characters** -- Skeletal animation with GPU vertex skinning (256 bones), race-aware textures - **Buildings** -- WMO renderer with multi-material batches, frustum culling, 160-unit distance culling @@ -124,8 +126,8 @@ make -j$(nproc) | F1 | Performance HUD | | F2 | Wireframe mode | | F9 | Toggle time progression | -| F10 | Toggle sun/moon | -| F11 | Toggle stars | +| F10 | Toggle celestial bodies (sun + moons) | +| F11 | Toggle procedural stars (debug mode) | | +/- | Change time of day | | C | Toggle clouds | | L | Toggle lens flare | @@ -145,6 +147,7 @@ make -j$(nproc) - [Authentication](docs/authentication.md) -- SRP6 auth protocol details - [Server Setup](docs/server-setup.md) -- Local server configuration - [Single Player](docs/single-player.md) -- Offline mode +- [Sky System](docs/SKY_SYSTEM.md) -- Celestial bodies, Azeroth astronomy, and WoW-accurate sky rendering - [SRP Implementation](docs/srp-implementation.md) -- Cryptographic details - [Packet Framing](docs/packet-framing.md) -- Network protocol framing - [Realm List](docs/realm-list.md) -- Realm selection system @@ -158,6 +161,13 @@ make -j$(nproc) - **Architecture**: Modular design with clear separation (core, rendering, networking, game logic, asset pipeline, UI, audio) - **Networking**: Non-blocking TCP, SRP6a authentication, RC4 encryption, WoW 3.3.5a protocol - **Asset Loading**: Async terrain streaming, lazy loading, MPQ archive support +- **Sky System**: WoW-accurate DBC-driven architecture + - **Skybox Authority**: Stars baked into M2 sky dome models (not procedurally generated) + - **Lore-Accurate Moons**: White Lady (30-day cycle) + Blue Child (27-day cycle) + - **Deterministic Phases**: Computed from server game time (consistent across sessions) + - **Camera-Locked**: Sky dome uses rotation-only transform (translation ignored) + - **No Latitude Math**: Per-zone artistic constants, not Earth-like planetary simulation + - **Zone Identity**: Different skyboxes per continent (Azeroth, Outland, Northrend) ## License diff --git a/docs/SKY_SYSTEM.md b/docs/SKY_SYSTEM.md new file mode 100644 index 00000000..f2655b55 --- /dev/null +++ b/docs/SKY_SYSTEM.md @@ -0,0 +1,422 @@ +# Sky System & Azeroth Astronomy + +## Overview + +The sky rendering system in wowee follows World of Warcraft's WotLK (3.3.5a) architecture, where skyboxes are **authoritative** and procedural elements serve as fallbacks only. This document explains the lore-accurate celestial system, implementation details, and critical anti-patterns to avoid. + +--- + +## Architecture + +### Component Hierarchy + +``` +SkySystem (coordinator) +├── Skybox (M2 model, AUTHORITATIVE - includes baked stars) +├── StarField (procedural, DEBUG/FALLBACK ONLY) +├── Celestial (sun + White Lady + Blue Child) +├── Clouds (atmospheric layer) +└── LensFlare (sun glow effect) +``` + +### Rendering Pipeline + +``` +LightingManager (DBC-driven) + ↓ Light.dbc + LightParams.dbc + time-of-day bands + ↓ produces: directionalDir, diffuseColor, skyColors, cloudDensity, fogDensity + ↓ +SkyParams (interface struct) + ↓ adds: gameTime, skyboxModelId, skyboxHasStars + ↓ +SkySystem::render(camera, params) + ├─→ Skybox first (far plane, camera-locked) + ├─→ StarField (ONLY if debugMode OR skybox missing) + ├─→ Celestial (sun + 2 moons, uses directionalDir + gameTime) + ├─→ Clouds (atmospheric layer) + └─→ LensFlare (screen-space sun glow) +``` + +--- + +## Celestial Bodies (Lore) + +### The Two Moons of Azeroth + +Azeroth has **two moons** visible in the night sky, both significant to the world's lore: + +#### **White Lady** (Primary Moon) +- **Appearance**: Larger, brighter, pale white color (RGB: 0.8, 0.85, 1.0) +- **Size**: 40-unit diameter billboard +- **Intensity**: Full brightness (1.0) +- **Lore**: Tied to Elune, the Night Elf moon goddess +- **Cycle**: 30 game days per phase cycle (~12 real-world hours) + +#### **Blue Child** (Secondary Moon) +- **Appearance**: Smaller, dimmer, pale blue color (RGB: 0.7, 0.8, 1.0) +- **Size**: 30-unit diameter billboard +- **Intensity**: 70% of White Lady's brightness +- **Position**: Offset to the right and slightly lower (+80 X, -40 Z) +- **Cycle**: 27 game days per phase cycle (~10.8 real-world hours, slightly faster) + +#### **Visibility** +- **Night hours**: 19:00 - 5:00 (both moons visible) +- **Fade transitions**: + - Fade in: 19:00 - 21:00 (dusk) + - Full intensity: 21:00 - 3:00 (night) + - Fade out: 3:00 - 5:00 (dawn) +- **Day hours**: 5:00 - 19:00 (moons not rendered) + +### The Sun + +- **Positioning**: Driven by `LightingManager::directionalDir` + - Placement: `sunPosition = -directionalDir * 800` (light comes FROM sun) + - Fallback: Time-based arc if no lighting manager (sunrise 6:00, peak 12:00, sunset 18:00) +- **Color**: Uses `LightingManager::diffuseColor` (DBC-driven, changes with time-of-day) + - Sunrise/sunset: Orange/red tones + - Midday: Bright yellow-white +- **Size**: 50-unit diameter billboard +- **Visibility**: 5:00 - 19:00 with intensity fade at transitions + +--- + +## Deterministic Moon Phases + +### Server Time-Driven (NOT deltaTime) + +Moon phases are computed from **server game time**, ensuring: +- ✅ **Deterministic**: Same game time always produces same moon phases +- ✅ **Persistent**: Phases consistent across sessions and server restarts +- ✅ **Lore-feeling**: Realistic cycles tied to game world time, not arbitrary timers + +### Calculation Formula + +```cpp +float computePhaseFromGameTime(float gameTime, float cycleDays) { + constexpr float SECONDS_PER_GAME_DAY = 1440.0f; // 1 game day = 24 real minutes + float gameDays = gameTime / SECONDS_PER_GAME_DAY; + float phase = fmod(gameDays / cycleDays, 1.0f); + return (phase < 0.0f) ? phase + 1.0f : phase; // Ensure positive +} + +// Applied per moon +whiteLadyPhase = computePhaseFromGameTime(gameTime, 30.0f); // 30 game days +blueChildPhase = computePhaseFromGameTime(gameTime, 27.0f); // 27 game days +``` + +### Phase Representation + +- **Value range**: 0.0 - 1.0 + - `0.0` = New moon (dark) + - `0.25` = First quarter (right half lit) + - `0.5` = Full moon (fully lit) + - `0.75` = Last quarter (left half lit) + - `1.0` = New moon (wraps to 0.0) +- **Shader-driven**: Phase uniform passed to fragment shader for crescent/gibbous rendering + +### Fallback Mode (Development) + +If `gameTime < 0.0` (server time unavailable): +- Uses deltaTime accumulator: `moonPhaseTimer += deltaTime` +- Short cycle durations (4 minutes / 3.5 minutes) for quick testing +- **NOT used in production**: Should always use server time + +--- + +## Sky Dome Rendering + +### Camera-Locked Behavior (WoW Standard) + +```cpp +// Vertex shader transformation +mat4 viewNoTranslation = mat4(mat3(view)); // Strip translation, keep rotation +gl_Position = projection * viewNoTranslation * vec4(aPos, 1.0); +gl_Position = gl_Position.xyww; // Force far plane depth +``` + +**Why this works:** +- ✅ **Translation ignored**: Sky centered on camera (doesn't "swim" when moving) +- ✅ **Rotation applied**: Sky follows camera look direction (feels "attached to view") +- ✅ **Far plane depth**: Always renders behind world geometry +- ✅ **Celestial sphere illusion**: Stars/sky appear infinitely distant + +### Time-Based Sky Drift (Optional) + +Subtle rotation for atmospheric effect: + +```cpp +float skyYawRotation = gameTime * skyRotationRate; +skyDomeMatrix = rotate(skyDomeMatrix, skyYawRotation, vec3(0, 0, 1)); // Yaw only +``` + +**Per-zone rotation rates:** +- Azeroth continents: `0.00001` rad/sec (very slow, barely noticeable) +- Outland: `0.00005` rad/sec (faster, "weird" alien sky feel) +- Northrend: `0.00002` rad/sec (subtle drift, aurora-like) +- Static zones: `0.0` (no rotation) + +**Implementation status:** Not yet active (waiting for M2 skybox loading) + +--- + +## Critical Anti-Patterns + +### ❌ DO NOT: Latitude-Based Star Rotation + +**Why it's wrong:** +- Azeroth is **not modeled as a spherical planet** with latitude/longitude in WoW client +- No coherent coordinate system for Earth-style celestial mechanics +- Stars are part of **authored skybox M2 models**, not dynamically positioned +- Breaks zone identity (Duskwood's gloomy sky shouldn't behave like Barrens) + +**What happens if you do it anyway:** +- Stars won't match Blizzard's authored skyboxes when M2 models load +- Lore/continuity breaks (geographically close zones with different star rotation) +- "Swimming" stars during movement +- Undermines the "WoW feel" + +**Correct approach:** +```cpp +// ✅ Per-zone artistic constants (NOT geography) +struct SkyProfile { + float celestialTilt; // Artistic pitch/roll (Outland = 15°, Azeroth = 0°) + float skyYawOffset; // Alignment offset for authored skybox + float skyRotationRate; // Time-based drift (0 = static) +}; +``` + +### ❌ DO NOT: Always Render Procedural Stars + +**Why it's wrong:** +- Skyboxes contain **baked stars** as part of zone mood/identity +- Procedural stars over skybox stars = double stars, visual clash +- Different zones have dramatically different skies (Outland purple nebulae, Northrend auroras) + +**Correct gating logic:** +```cpp +bool renderProceduralStars = false; +if (debugSkyMode) { + renderProceduralStars = true; // Debug: force for testing fog/cloud attenuation +} else if (proceduralStarsEnabled) { + renderProceduralStars = !params.skyboxHasStars; // Fallback ONLY if skybox missing +} +``` + +**skyboxHasStars flag:** +- Set to `true` when M2 skybox loaded and has star layer (query materials/textures) +- Set to `false` for gradient skybox (placeholder, no baked stars) +- Prevents procedural stars from "leaking" once real skyboxes load + +### ❌ DO NOT: Universal Dual Moon Setup + +**Why it's wrong:** +- Not all maps/continents have same celestial bodies +- Azeroth: White Lady + Blue Child (two moons) +- Outland: Different sky (alien world, broken planet) +- Forcing two moons everywhere breaks lore + +**Correct approach:** +```cpp +struct SkyProfile { + bool dualMoons; // Azeroth = true, Outland = false + // ... other per-map settings +}; + +// In Celestial::render() +if (dualMoonMode_ && mapUsesAzerothSky) { + renderBlueChild(camera, timeOfDay); +} +``` + +--- + +## Integration Points + +### SkyParams Struct (Interface) + +```cpp +struct SkyParams { + // Sun/moon positioning + glm::vec3 directionalDir; // From LightingManager (sun direction) + glm::vec3 sunColor; // From LightingManager (DBC diffuse color) + + // Sky colors (for skybox tinting/blending, future) + glm::vec3 skyTopColor; + glm::vec3 skyMiddleColor; + glm::vec3 skyBand1Color; + glm::vec3 skyBand2Color; + + // Atmospheric effects (star/moon occlusion) + float cloudDensity; // 0-1, from LightingManager + float fogDensity; // 0-1, from LightingManager + float horizonGlow; // 0-1, atmospheric scattering + + // Time + float timeOfDay; // 0-24 hours (for sun/moon visibility) + float gameTime; // Server time in seconds (for moon phases) + + // Skybox control (future: LightSkybox.dbc) + uint32_t skyboxModelId; // Which M2 skybox to load + bool skyboxHasStars; // Does skybox include baked stars? +}; +``` + +### Star Occlusion by Weather + +Clouds and fog affect star visibility: + +```cpp +// In StarField::render() +float intensity = getStarIntensity(timeOfDay); // Time-based (night = 1.0, day = 0.0) +intensity *= (1.0f - glm::clamp(cloudDensity * 0.7f, 0.0f, 1.0f)); // Heavy clouds hide stars +intensity *= (1.0f - glm::clamp(fogDensity * 0.3f, 0.0f, 1.0f)); // Fog dims stars + +if (intensity <= 0.01f) { + return; // Don't render invisible stars +} +``` + +**Result:** Cloudy/foggy nights have fewer visible stars (realistic behavior) + +--- + +## Future: M2 Skybox System + +### LightSkybox.dbc Integration + +**DBC Chain:** +``` +Light.dbc (spatial volumes) + ↓ lightParamsId (per weather condition) +LightParams.dbc (profile mapping) + ↓ skyboxId +LightSkybox.dbc (model paths) + ↓ M2 model name +Environments\Stars\*.m2 (actual sky dome models) +``` + +**Skybox Loading Flow:** +1. Query `lightParamsId` from active light volume(s) +2. Look up `skyboxId` in LightParams.dbc +3. Load M2 model path from LightSkybox.dbc +4. Load/cache M2 skybox model +5. Query model materials → set `skyboxHasStars = true` if star textures found +6. Render skybox, disable procedural stars + +### Skybox Transition Blending + +**Problem:** Hard swaps between skyboxes at zone boundaries look bad + +**Solution:** Blend skyboxes using same volume weighting as lighting: + +```cpp +// In SkySystem::render() +if (activeVolumes.size() >= 2) { + // Render primary skybox + skybox1->render(camera, alpha = volumes[0].weight); + + // Blend in secondary skybox + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE); // Additive blending + skybox2->render(camera, alpha = volumes[1].weight); + glDisable(GL_BLEND); +} +``` + +**Result:** Smooth crossfade between zone skies, no popping + +### SkyProfile Configuration + +**Per-map/continent settings:** + +```cpp +std::map skyProfiles = { + // Azeroth (Eastern Kingdoms) + {0, { + .skyboxModelId = 123, + .celestialTilt = 0.0f, // No tilt, standard orientation + .skyYawOffset = 0.0f, + .skyRotationRate = 0.00001f, // Very slow drift + .dualMoons = true // White Lady + Blue Child + }}, + + // Kalimdor + {1, { + .skyboxModelId = 124, + .celestialTilt = 0.0f, + .skyYawOffset = 0.0f, + .skyRotationRate = 0.00001f, + .dualMoons = true + }}, + + // Outland (Burning Crusade) + {530, { + .skyboxModelId = 456, + .celestialTilt = 15.0f, // Tilted, alien feel + .skyYawOffset = 45.0f, // Rotated alignment + .skyRotationRate = 0.00005f, // Faster, "weird" drift + .dualMoons = false // Different celestial setup + }}, + + // Northrend (Wrath of the Lich King) + {571, { + .skyboxModelId = 789, + .celestialTilt = 0.0f, + .skyYawOffset = 0.0f, + .skyRotationRate = 0.00002f, // Subtle aurora-like drift + .dualMoons = true + }} +}; +``` + +--- + +## Implementation Checklist + +### ✅ Completed +- [x] SkySystem coordinator class +- [x] Skybox camera-locked rendering (translation ignored) +- [x] Procedural stars gated by `skyboxHasStars` flag +- [x] Two moons (White Lady + Blue Child) with independent phases +- [x] Deterministic moon phases from server `gameTime` +- [x] Sun positioning from lighting `directionalDir` +- [x] Star occlusion by cloud/fog density +- [x] SkyParams interface for lighting integration + +### 🚧 Future Enhancements +- [ ] Load M2 skybox models (parse LightSkybox.dbc) +- [ ] Query M2 materials to detect baked stars +- [ ] Skybox transition blending (weighted crossfade) +- [ ] SkyProfile per map/continent +- [ ] Time-based sky rotation (optional drift) +- [ ] Moon position from shared sky arc system (not fixed offsets) +- [ ] Support for zone-specific celestial setups (Outland, etc.) + +--- + +## Code References + +**Key Files:** +- `include/rendering/sky_system.hpp` - Coordinator, SkyParams struct +- `src/rendering/sky_system.cpp` - Render pipeline, star gating logic +- `include/rendering/celestial.hpp` - Sun + dual moon system +- `src/rendering/celestial.cpp` - Moon phase calculations, rendering +- `include/rendering/starfield.hpp` - Procedural star fallback +- `src/rendering/starfield.cpp` - Star intensity + occlusion +- `include/rendering/skybox.hpp` - Camera-locked sky dome +- `src/rendering/skybox.cpp` - Sky dome vertex shader + +**Integration Points:** +- `src/rendering/renderer.cpp` - Populates SkyParams from LightingManager +- `include/rendering/lighting_manager.hpp` - Provides directionalDir, colors, fog/cloud + +--- + +## References + +- **WoW 3.3.5a Client**: Environments\Stars\*.m2 (skybox models) +- **DBC Files**: Light.dbc, LightParams.dbc, LightSkybox.dbc, LightIntBand.dbc, LightFloatBand.dbc +- **WoWDev Wiki**: https://wowdev.wiki/Light.dbc (lighting system documentation) +- **Lore Sources**: + - White Lady / Blue Child: https://wowpedia.fandom.com/wiki/Moon + - Elune connection: https://wowpedia.fandom.com/wiki/Elune diff --git a/include/rendering/celestial.hpp b/include/rendering/celestial.hpp index 46b43cdf..c6a47bb7 100644 --- a/include/rendering/celestial.hpp +++ b/include/rendering/celestial.hpp @@ -27,8 +27,14 @@ public: * Render celestial bodies (sun and moon) * @param camera Camera for view matrix * @param timeOfDay Time of day in hours (0-24) + * @param sunDir Optional sun direction from lighting system (normalized) + * @param sunColor Optional sun color from lighting system + * @param gameTime Optional server game time in seconds (for deterministic moon phases) */ - void render(const Camera& camera, float timeOfDay); + void render(const Camera& camera, float timeOfDay, + const glm::vec3* sunDir = nullptr, + const glm::vec3* sunColor = nullptr, + float gameTime = -1.0f); /** * Enable/disable celestial rendering @@ -42,10 +48,16 @@ public: void update(float deltaTime); /** - * Set moon phase (0.0 = new moon, 0.25 = first quarter, 0.5 = full, 0.75 = last quarter, 1.0 = new) + * Set White Lady phase (primary moon, 0.0 = new, 0.5 = full, 1.0 = new) */ void setMoonPhase(float phase); - float getMoonPhase() const { return moonPhase; } + float getMoonPhase() const { return whiteLadyPhase_; } + + /** + * Set Blue Child phase (secondary moon, 0.0 = new, 0.5 = full, 1.0 = new) + */ + void setBlueChildPhase(float phase); + float getBlueChildPhase() const { return blueChildPhase_; } /** * Enable/disable automatic moon phase cycling @@ -53,6 +65,12 @@ public: void setMoonPhaseCycling(bool enabled) { moonPhaseCycling = enabled; } bool isMoonPhaseCycling() const { return moonPhaseCycling; } + /** + * Enable/disable two-moon rendering (White Lady + Blue Child) + */ + void setDualMoonMode(bool enabled) { dualMoonMode_ = enabled; } + bool isDualMoonMode() const { return dualMoonMode_; } + /** * Get sun position in world space */ @@ -77,11 +95,27 @@ private: void createCelestialQuad(); void destroyCelestialQuad(); - void renderSun(const Camera& camera, float timeOfDay); - void renderMoon(const Camera& camera, float timeOfDay); + void renderSun(const Camera& camera, float timeOfDay, + const glm::vec3* sunDir = nullptr, + const glm::vec3* sunColor = nullptr); + void renderMoon(const Camera& camera, float timeOfDay); // White Lady (primary) + void renderBlueChild(const Camera& camera, float timeOfDay); // Blue Child (secondary) float calculateCelestialAngle(float timeOfDay, float riseTime, float setTime) const; + /** + * Compute moon phase from game time (deterministic) + * @param gameTime Server game time in seconds + * @param cycleDays Lunar cycle length in game days + * @return Phase 0.0-1.0 (0=new, 0.5=full, 1.0=new) + */ + float computePhaseFromGameTime(float gameTime, float cycleDays) const; + + /** + * Update moon phases from game time (server-driven) + */ + void updatePhasesFromGameTime(float gameTime); + std::unique_ptr celestialShader; uint32_t vao = 0; @@ -90,11 +124,18 @@ private: bool renderingEnabled = true; - // Moon phase system - float moonPhase = 0.5f; // 0.0-1.0 (0=new, 0.5=full) + // Moon phase system (two moons in Azeroth lore) + float whiteLadyPhase_ = 0.5f; // 0.0-1.0 (0=new, 0.5=full) - primary moon + float blueChildPhase_ = 0.25f; // 0.0-1.0 (0=new, 0.5=full) - secondary moon bool moonPhaseCycling = true; - float moonPhaseTimer = 0.0f; - static constexpr float MOON_CYCLE_DURATION = 240.0f; // 4 minutes for full cycle + float moonPhaseTimer = 0.0f; // Fallback for deltaTime mode (development) + bool dualMoonMode_ = true; // Default: render both moons (Azeroth-specific) + + // WoW lunar cycle constants (in game days) + // WoW day = 24 real minutes, so these are ~realistic game-world cycles + static constexpr float WHITE_LADY_CYCLE_DAYS = 30.0f; // ~12 real hours for full cycle + static constexpr float BLUE_CHILD_CYCLE_DAYS = 27.0f; // ~10.8 real hours (slightly faster) + static constexpr float MOON_CYCLE_DURATION = 240.0f; // Fallback: 4 minutes (deltaTime mode) }; } // namespace rendering diff --git a/include/rendering/character_renderer.hpp b/include/rendering/character_renderer.hpp index e715a75d..9a5832de 100644 --- a/include/rendering/character_renderer.hpp +++ b/include/rendering/character_renderer.hpp @@ -88,6 +88,13 @@ public: fogColor = color; fogStart = start; fogEnd = end; } + void setLighting(const float lightDirIn[3], const float lightColorIn[3], + const float ambientColorIn[3]) { + lightDir = glm::vec3(lightDirIn[0], lightDirIn[1], lightDirIn[2]); + lightColor = glm::vec3(lightColorIn[0], lightColorIn[1], lightColorIn[2]); + ambientColor = glm::vec3(ambientColorIn[0], ambientColorIn[1], ambientColorIn[2]); + } + void setShadowMap(GLuint depthTex, const glm::mat4& lightSpace) { shadowDepthTex = depthTex; lightSpaceMatrix = lightSpace; shadowEnabled = true; } @@ -206,6 +213,11 @@ private: float fogStart = 400.0f; float fogEnd = 1200.0f; + // Lighting parameters + glm::vec3 lightDir = glm::vec3(0.0f, -1.0f, 0.3f); + glm::vec3 lightColor = glm::vec3(1.5f, 1.4f, 1.3f); + glm::vec3 ambientColor = glm::vec3(0.4f, 0.4f, 0.45f); + // Shadow mapping GLuint shadowDepthTex = 0; glm::mat4 lightSpaceMatrix = glm::mat4(1.0f); diff --git a/include/rendering/m2_renderer.hpp b/include/rendering/m2_renderer.hpp index fcda4b89..215525c0 100644 --- a/include/rendering/m2_renderer.hpp +++ b/include/rendering/m2_renderer.hpp @@ -307,6 +307,13 @@ public: fogColor = color; fogStart = start; fogEnd = end; } + void setLighting(const float lightDirIn[3], const float lightColorIn[3], + const float ambientColorIn[3]) { + lightDir = glm::vec3(lightDirIn[0], lightDirIn[1], lightDirIn[2]); + lightColor = glm::vec3(lightColorIn[0], lightColorIn[1], lightColorIn[2]); + ambientColor = glm::vec3(ambientColorIn[0], ambientColorIn[1], ambientColorIn[2]); + } + void setShadowMap(GLuint depthTex, const glm::mat4& lightSpace) { shadowDepthTex = depthTex; lightSpaceMatrix = lightSpace; shadowEnabled = true; } @@ -334,6 +341,7 @@ private: // Lighting uniforms glm::vec3 lightDir = glm::vec3(0.5f, 0.5f, 1.0f); + glm::vec3 lightColor = glm::vec3(1.5f, 1.4f, 1.3f); glm::vec3 ambientColor = glm::vec3(0.4f, 0.4f, 0.45f); // Fog parameters diff --git a/include/rendering/sky_system.hpp b/include/rendering/sky_system.hpp new file mode 100644 index 00000000..4ead334e --- /dev/null +++ b/include/rendering/sky_system.hpp @@ -0,0 +1,131 @@ +#pragma once + +#include +#include + +namespace wowee { +namespace rendering { + +class Camera; +class Skybox; +class Celestial; +class StarField; +class Clouds; +class LensFlare; +class LightingManager; + +/** + * Sky rendering parameters (extracted from LightingManager) + */ +struct SkyParams { + // Sun/moon positioning + glm::vec3 directionalDir{0.0f, -1.0f, 0.3f}; + glm::vec3 sunColor{1.0f, 1.0f, 0.9f}; + + // Sky colors (for skybox tinting/blending) + glm::vec3 skyTopColor{0.5f, 0.7f, 1.0f}; + glm::vec3 skyMiddleColor{0.7f, 0.85f, 1.0f}; + glm::vec3 skyBand1Color{0.9f, 0.95f, 1.0f}; + glm::vec3 skyBand2Color{1.0f, 0.98f, 0.9f}; + + // Atmospheric effects + float cloudDensity = 0.0f; // 0-1 + float fogDensity = 0.0f; // 0-1 + float horizonGlow = 0.3f; // 0-1 + + // Time + float timeOfDay = 12.0f; // 0-24 hours + float gameTime = -1.0f; // Server game time in seconds (-1 = use fallback) + + // Skybox selection (future: from LightSkybox.dbc) + uint32_t skyboxModelId = 0; + bool skyboxHasStars = false; // Does loaded skybox include baked stars? +}; + +/** + * Unified sky rendering system + * + * Coordinates skybox (authoritative), celestial bodies (sun + 2 moons), + * and fallback procedural stars. Driven by lighting system data. + * + * Architecture: + * - Skybox is PRIMARY (includes baked stars from M2 models) + * - Celestial renders sun + White Lady + Blue Child + * - StarField is DEBUG/FALLBACK only (disabled when skybox has stars) + */ +class SkySystem { +public: + SkySystem(); + ~SkySystem(); + + /** + * Initialize sky system components + */ + bool initialize(); + void shutdown(); + + /** + * Update sky system (time, moon phases, etc.) + */ + void update(float deltaTime); + + /** + * Render complete sky + * @param camera Camera for view/projection + * @param params Sky parameters from lighting system + */ + void render(const Camera& camera, const SkyParams& params); + + /** + * Enable/disable procedural stars (DEBUG/FALLBACK) + * Default: OFF (stars come from skybox) + */ + void setProceduralStarsEnabled(bool enabled) { proceduralStarsEnabled_ = enabled; } + bool isProceduralStarsEnabled() const { return proceduralStarsEnabled_; } + + /** + * Enable/disable debug sky mode (forces procedural stars even with skybox) + */ + void setDebugSkyMode(bool enabled) { debugSkyMode_ = enabled; } + bool isDebugSkyMode() const { return debugSkyMode_; } + + /** + * Get sun position in world space (for lens flare, shadows, etc.) + */ + glm::vec3 getSunPosition(const SkyParams& params) const; + + /** + * Enable/disable moon phase cycling + */ + void setMoonPhaseCycling(bool enabled); + + /** + * Set moon phases manually (0.0-1.0 each) + */ + void setWhiteLadyPhase(float phase); + void setBlueChildPhase(float phase); + + float getWhiteLadyPhase() const; + float getBlueChildPhase() const; + + // Component accessors (for direct control if needed) + Skybox* getSkybox() const { return skybox_.get(); } + Celestial* getCelestial() const { return celestial_.get(); } + StarField* getStarField() const { return starField_.get(); } + Clouds* getClouds() const { return clouds_.get(); } + LensFlare* getLensFlare() const { return lensFlare_.get(); } + +private: + std::unique_ptr skybox_; // Authoritative sky (gradient now, M2 models later) + std::unique_ptr celestial_; // Sun + 2 moons + std::unique_ptr starField_; // Fallback procedural stars + std::unique_ptr clouds_; // Cloud layer + std::unique_ptr lensFlare_; // Sun lens flare + + bool proceduralStarsEnabled_ = false; // Default: OFF (skybox is authoritative) + bool debugSkyMode_ = false; // Force procedural stars for debugging + bool initialized_ = false; +}; + +} // namespace rendering +} // namespace wowee diff --git a/include/rendering/starfield.hpp b/include/rendering/starfield.hpp index bbfdace2..36aa94bf 100644 --- a/include/rendering/starfield.hpp +++ b/include/rendering/starfield.hpp @@ -28,8 +28,11 @@ public: * Render the star field * @param camera Camera for view matrix * @param timeOfDay Time of day in hours (0-24) + * @param cloudDensity Optional cloud density from lighting (0-1, reduces star visibility) + * @param fogDensity Optional fog density from lighting (reduces star visibility) */ - void render(const Camera& camera, float timeOfDay); + void render(const Camera& camera, float timeOfDay, + float cloudDensity = 0.0f, float fogDensity = 0.0f); /** * Update star twinkle animation diff --git a/include/rendering/wmo_renderer.hpp b/include/rendering/wmo_renderer.hpp index 21b8dc2e..03f0cf64 100644 --- a/include/rendering/wmo_renderer.hpp +++ b/include/rendering/wmo_renderer.hpp @@ -179,6 +179,9 @@ public: fogColor = color; fogStart = start; fogEnd = end; } + void setLighting(const float lightDir[3], const float lightColor[3], + const float ambientColor[3]); + void setShadowMap(GLuint depthTex, const glm::mat4& lightSpace) { shadowDepthTex = depthTex; lightSpaceMatrix = lightSpace; shadowEnabled = true; } @@ -537,6 +540,11 @@ private: float fogStart = 3000.0f; // Increased to allow clearer visibility at distance float fogEnd = 4000.0f; // Increased to match extended view distance + // Lighting parameters + float lightDir[3] = {-0.3f, -0.7f, -0.6f}; + float lightColor[3] = {1.5f, 1.4f, 1.3f}; + float ambientColor[3] = {0.55f, 0.55f, 0.6f}; + // Shadow mapping GLuint shadowDepthTex = 0; glm::mat4 lightSpaceMatrix = glm::mat4(1.0f); diff --git a/src/rendering/celestial.cpp b/src/rendering/celestial.cpp index a93ad3d9..b58e8bf9 100644 --- a/src/rendering/celestial.cpp +++ b/src/rendering/celestial.cpp @@ -125,11 +125,17 @@ void Celestial::shutdown() { celestialShader.reset(); } -void Celestial::render(const Camera& camera, float timeOfDay) { +void Celestial::render(const Camera& camera, float timeOfDay, + const glm::vec3* sunDir, const glm::vec3* sunColor, float gameTime) { if (!renderingEnabled || vao == 0 || !celestialShader) { return; } + // Update moon phases from game time if available (deterministic) + if (gameTime >= 0.0f) { + updatePhasesFromGameTime(gameTime); + } + // Enable blending for celestial glow glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); @@ -137,16 +143,21 @@ void Celestial::render(const Camera& camera, float timeOfDay) { // Disable depth writing (but keep depth testing) glDepthMask(GL_FALSE); - // Render sun and moon - renderSun(camera, timeOfDay); - renderMoon(camera, timeOfDay); + // Render sun and moons (pass lighting parameters) + renderSun(camera, timeOfDay, sunDir, sunColor); + renderMoon(camera, timeOfDay); // White Lady (primary moon) + + if (dualMoonMode_) { + renderBlueChild(camera, timeOfDay); // Blue Child (secondary moon) + } // Restore state glDepthMask(GL_TRUE); glDisable(GL_BLEND); } -void Celestial::renderSun(const Camera& camera, float timeOfDay) { +void Celestial::renderSun(const Camera& camera, float timeOfDay, + const glm::vec3* sunDir, const glm::vec3* sunColor) { // Sun visible from 5:00 to 19:00 if (timeOfDay < 5.0f || timeOfDay >= 19.0f) { return; @@ -154,8 +165,16 @@ void Celestial::renderSun(const Camera& camera, float timeOfDay) { celestialShader->use(); - // Get sun position - glm::vec3 sunPos = getSunPosition(timeOfDay); + // Get sun position (use lighting direction if provided) + glm::vec3 sunPos; + if (sunDir) { + // Place sun along the lighting direction at far distance + const float sunDistance = 800.0f; + sunPos = -*sunDir * sunDistance; // Negative because light comes FROM sun + } else { + // Fallback to time-based position + sunPos = getSunPosition(timeOfDay); + } // Create model matrix glm::mat4 model = glm::mat4(1.0f); @@ -170,8 +189,8 @@ void Celestial::renderSun(const Camera& camera, float timeOfDay) { celestialShader->setUniform("view", view); celestialShader->setUniform("projection", projection); - // Sun color and intensity - glm::vec3 color = getSunColor(timeOfDay); + // Sun color and intensity (use lighting color if provided) + glm::vec3 color = sunColor ? *sunColor : getSunColor(timeOfDay); float intensity = getSunIntensity(timeOfDay); celestialShader->setUniform("celestialColor", color); @@ -224,7 +243,61 @@ void Celestial::renderMoon(const Camera& camera, float timeOfDay) { celestialShader->setUniform("celestialColor", color); celestialShader->setUniform("intensity", intensity); - celestialShader->setUniform("moonPhase", moonPhase); + celestialShader->setUniform("moonPhase", whiteLadyPhase_); + + // Render quad + glBindVertexArray(vao); + glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, nullptr); + glBindVertexArray(0); +} + +void Celestial::renderBlueChild(const Camera& camera, float timeOfDay) { + // Blue Child visible from 19:00 to 5:00 (night, same as White Lady) + if (timeOfDay >= 5.0f && timeOfDay < 19.0f) { + return; + } + + celestialShader->use(); + + // Get moon position (offset slightly from White Lady) + glm::vec3 moonPos = getMoonPosition(timeOfDay); + // Offset Blue Child to the right and slightly lower + moonPos.x += 80.0f; // Right offset + moonPos.z -= 40.0f; // Slightly lower + + // Create model matrix (smaller than White Lady) + glm::mat4 model = glm::mat4(1.0f); + model = glm::translate(model, moonPos); + model = glm::scale(model, glm::vec3(30.0f, 30.0f, 1.0f)); // 30 unit diameter (smaller) + + // Set uniforms + glm::mat4 view = camera.getViewMatrix(); + glm::mat4 projection = camera.getProjectionMatrix(); + + celestialShader->setUniform("model", model); + celestialShader->setUniform("view", view); + celestialShader->setUniform("projection", projection); + + // Blue Child color (pale blue tint) + glm::vec3 color = glm::vec3(0.7f, 0.8f, 1.0f); + + // Fade in/out at transitions (same as White Lady) + float intensity = 1.0f; + if (timeOfDay >= 19.0f && timeOfDay < 21.0f) { + // Fade in (19:00-21:00) + intensity = (timeOfDay - 19.0f) / 2.0f; + } + else if (timeOfDay >= 3.0f && timeOfDay < 5.0f) { + // Fade out (3:00-5:00) + intensity = 1.0f - (timeOfDay - 3.0f) / 2.0f; + } + + // Blue Child is dimmer than White Lady + intensity *= 0.7f; + + celestialShader->setUniform("celestialColor", color); + celestialShader->setUniform("intensity", intensity); + celestialShader->setUniform("moonPhase", blueChildPhase_); // Render quad glBindVertexArray(vao); @@ -396,16 +469,49 @@ void Celestial::update(float deltaTime) { // Update moon phase timer moonPhaseTimer += deltaTime; - // Moon completes full cycle in MOON_CYCLE_DURATION seconds - moonPhase = std::fmod(moonPhaseTimer / MOON_CYCLE_DURATION, 1.0f); + // White Lady completes full cycle in MOON_CYCLE_DURATION seconds + whiteLadyPhase_ = std::fmod(moonPhaseTimer / MOON_CYCLE_DURATION, 1.0f); + + // Blue Child has a different cycle rate (slightly faster, 3.5 minutes) + constexpr float BLUE_CHILD_CYCLE = 210.0f; + blueChildPhase_ = std::fmod(moonPhaseTimer / BLUE_CHILD_CYCLE, 1.0f); } void Celestial::setMoonPhase(float phase) { - // Clamp phase to 0.0-1.0 - moonPhase = glm::clamp(phase, 0.0f, 1.0f); + // Set White Lady phase (primary moon) + whiteLadyPhase_ = glm::clamp(phase, 0.0f, 1.0f); - // Update timer to match phase - moonPhaseTimer = moonPhase * MOON_CYCLE_DURATION; + // Update timer to match White Lady phase + moonPhaseTimer = whiteLadyPhase_ * MOON_CYCLE_DURATION; +} + +void Celestial::setBlueChildPhase(float phase) { + // Set Blue Child phase (secondary moon) + blueChildPhase_ = glm::clamp(phase, 0.0f, 1.0f); +} + +float Celestial::computePhaseFromGameTime(float gameTime, float cycleDays) const { + // WoW game time: 1 game day = 24 real minutes = 1440 seconds + constexpr float SECONDS_PER_GAME_DAY = 1440.0f; + + // Convert game time to game days + float gameDays = gameTime / SECONDS_PER_GAME_DAY; + + // Compute phase as fraction of lunar cycle (0.0-1.0) + float phase = std::fmod(gameDays / cycleDays, 1.0f); + + // Ensure positive (fmod can return negative for negative input) + if (phase < 0.0f) { + phase += 1.0f; + } + + return phase; +} + +void Celestial::updatePhasesFromGameTime(float gameTime) { + // Compute deterministic phases from server game time + whiteLadyPhase_ = computePhaseFromGameTime(gameTime, WHITE_LADY_CYCLE_DAYS); + blueChildPhase_ = computePhaseFromGameTime(gameTime, BLUE_CHILD_CYCLE_DAYS); } } // namespace rendering diff --git a/src/rendering/character_renderer.cpp b/src/rendering/character_renderer.cpp index 81147b09..64cae49d 100644 --- a/src/rendering/character_renderer.cpp +++ b/src/rendering/character_renderer.cpp @@ -1163,8 +1163,8 @@ void CharacterRenderer::render(const Camera& camera, const glm::mat4& view, cons characterShader->use(); characterShader->setUniform("uView", view); characterShader->setUniform("uProjection", projection); - characterShader->setUniform("uLightDir", glm::vec3(0.0f, -1.0f, 0.3f)); - characterShader->setUniform("uLightColor", glm::vec3(1.5f, 1.4f, 1.3f)); + characterShader->setUniform("uLightDir", lightDir); + characterShader->setUniform("uLightColor", lightColor); characterShader->setUniform("uSpecularIntensity", 0.5f); characterShader->setUniform("uViewPos", camera.getPosition()); diff --git a/src/rendering/m2_renderer.cpp b/src/rendering/m2_renderer.cpp index 4a98ba25..4cc0671e 100644 --- a/src/rendering/m2_renderer.cpp +++ b/src/rendering/m2_renderer.cpp @@ -1652,7 +1652,7 @@ void M2Renderer::render(const Camera& camera, const glm::mat4& view, const glm:: shader->setUniform("uView", view); shader->setUniform("uProjection", projection); shader->setUniform("uLightDir", lightDir); - shader->setUniform("uLightColor", glm::vec3(1.5f, 1.4f, 1.3f)); + shader->setUniform("uLightColor", lightColor); shader->setUniform("uSpecularIntensity", onTaxi_ ? 0.0f : 0.5f); // Disable specular during taxi for performance shader->setUniform("uAmbientColor", ambientColor); shader->setUniform("uViewPos", camera.getPosition()); diff --git a/src/rendering/renderer.cpp b/src/rendering/renderer.cpp index 19230624..a3f6862f 100644 --- a/src/rendering/renderer.cpp +++ b/src/rendering/renderer.cpp @@ -1764,14 +1764,27 @@ void Renderer::renderWorld(game::World* world) { skybox->render(*camera, timeOfDay); } - // Render stars after skybox - if (starField && camera) { - starField->render(*camera, timeOfDay); + // Get lighting parameters for celestial rendering + const glm::vec3* sunDir = nullptr; + const glm::vec3* sunColor = nullptr; + float cloudDensity = 0.0f; + float fogDensity = 0.0f; + if (lightingManager) { + const auto& lighting = lightingManager->getLightingParams(); + sunDir = &lighting.directionalDir; + sunColor = &lighting.diffuseColor; + cloudDensity = lighting.cloudDensity; + fogDensity = lighting.fogDensity; } - // Render celestial bodies (sun/moon) after stars + // Render stars after skybox (affected by cloud/fog density) + if (starField && camera) { + starField->render(*camera, timeOfDay, cloudDensity, fogDensity); + } + + // Render celestial bodies (sun/moon) after stars (sun uses lighting direction/color) if (celestial && camera) { - celestial->render(*camera, timeOfDay); + celestial->render(*camera, timeOfDay, sunDir, sunColor); } // Render clouds after celestial bodies @@ -1781,12 +1794,43 @@ void Renderer::renderWorld(game::World* world) { // Render lens flare (screen-space effect, render after celestial bodies) if (lensFlare && camera && celestial) { - glm::vec3 sunPosition = celestial->getSunPosition(timeOfDay); + // Use lighting direction for sun position if available + glm::vec3 sunPosition; + if (sunDir) { + const float sunDistance = 800.0f; + sunPosition = -*sunDir * sunDistance; + } else { + sunPosition = celestial->getSunPosition(timeOfDay); + } lensFlare->render(*camera, sunPosition, timeOfDay); } - // Update fog across all renderers based on time of day (match sky color) - if (skybox) { + // Apply lighting and fog to all renderers + if (lightingManager) { + const auto& lighting = lightingManager->getLightingParams(); + + float lightDir[3] = {lighting.directionalDir.x, lighting.directionalDir.y, lighting.directionalDir.z}; + float lightColor[3] = {lighting.diffuseColor.r, lighting.diffuseColor.g, lighting.diffuseColor.b}; + float ambientColor[3] = {lighting.ambientColor.r, lighting.ambientColor.g, lighting.ambientColor.b}; + float fogColorArray[3] = {lighting.fogColor.r, lighting.fogColor.g, lighting.fogColor.b}; + + if (wmoRenderer) { + wmoRenderer->setLighting(lightDir, lightColor, ambientColor); + wmoRenderer->setFog(glm::vec3(fogColorArray[0], fogColorArray[1], fogColorArray[2]), + lighting.fogStart, lighting.fogEnd); + } + if (m2Renderer) { + m2Renderer->setLighting(lightDir, lightColor, ambientColor); + m2Renderer->setFog(glm::vec3(fogColorArray[0], fogColorArray[1], fogColorArray[2]), + lighting.fogStart, lighting.fogEnd); + } + if (characterRenderer) { + characterRenderer->setLighting(lightDir, lightColor, ambientColor); + characterRenderer->setFog(glm::vec3(fogColorArray[0], fogColorArray[1], fogColorArray[2]), + lighting.fogStart, lighting.fogEnd); + } + } else if (skybox) { + // Fallback to skybox-based fog if no lighting manager glm::vec3 horizonColor = skybox->getHorizonColor(timeOfDay); if (wmoRenderer) wmoRenderer->setFog(horizonColor, 100.0f, 600.0f); if (m2Renderer) m2Renderer->setFog(horizonColor, 100.0f, 600.0f); diff --git a/src/rendering/sky_system.cpp b/src/rendering/sky_system.cpp new file mode 100644 index 00000000..f830d364 --- /dev/null +++ b/src/rendering/sky_system.cpp @@ -0,0 +1,183 @@ +#include "rendering/sky_system.hpp" +#include "rendering/skybox.hpp" +#include "rendering/celestial.hpp" +#include "rendering/starfield.hpp" +#include "rendering/clouds.hpp" +#include "rendering/lens_flare.hpp" +#include "rendering/camera.hpp" +#include "core/logger.hpp" + +namespace wowee { +namespace rendering { + +SkySystem::SkySystem() = default; + +SkySystem::~SkySystem() { + shutdown(); +} + +bool SkySystem::initialize() { + if (initialized_) { + LOG_WARNING("SkySystem already initialized"); + return true; + } + + LOG_INFO("Initializing sky system"); + + // Initialize skybox (authoritative) + skybox_ = std::make_unique(); + if (!skybox_->initialize()) { + LOG_ERROR("Failed to initialize skybox"); + return false; + } + + // Initialize celestial bodies (sun + 2 moons) + celestial_ = std::make_unique(); + if (!celestial_->initialize()) { + LOG_ERROR("Failed to initialize celestial bodies"); + return false; + } + + // Initialize procedural stars (FALLBACK only) + starField_ = std::make_unique(); + if (!starField_->initialize()) { + LOG_ERROR("Failed to initialize star field"); + return false; + } + // Default: disabled (skybox is authoritative) + starField_->setEnabled(false); + + // Initialize clouds + clouds_ = std::make_unique(); + if (!clouds_->initialize()) { + LOG_ERROR("Failed to initialize clouds"); + return false; + } + + // Initialize lens flare + lensFlare_ = std::make_unique(); + if (!lensFlare_->initialize()) { + LOG_ERROR("Failed to initialize lens flare"); + return false; + } + + initialized_ = true; + LOG_INFO("Sky system initialized successfully"); + return true; +} + +void SkySystem::shutdown() { + if (!initialized_) { + return; + } + + LOG_INFO("Shutting down sky system"); + + // Shutdown components that have explicit shutdown methods + if (starField_) starField_->shutdown(); + if (celestial_) celestial_->shutdown(); + if (skybox_) skybox_->shutdown(); + + // Reset all (destructors handle cleanup for clouds/lensFlare) + lensFlare_.reset(); + clouds_.reset(); + starField_.reset(); + celestial_.reset(); + skybox_.reset(); + + initialized_ = false; +} + +void SkySystem::update(float deltaTime) { + if (!initialized_) { + return; + } + + // Update time-based systems + if (skybox_) skybox_->update(deltaTime); + if (celestial_) celestial_->update(deltaTime); + if (starField_) starField_->update(deltaTime); +} + +void SkySystem::render(const Camera& camera, const SkyParams& params) { + if (!initialized_) { + return; + } + + // Render skybox first (authoritative, includes baked stars) + if (skybox_) { + skybox_->render(camera, params.timeOfDay); + } + + // Decide whether to render procedural stars + bool renderProceduralStars = false; + if (debugSkyMode_) { + // Debug mode: always show procedural stars + renderProceduralStars = true; + } else if (proceduralStarsEnabled_) { + // Fallback mode: show only if skybox doesn't have stars + renderProceduralStars = !params.skyboxHasStars; + } + + // Render procedural stars (FALLBACK or DEBUG only) + if (renderProceduralStars && starField_) { + starField_->setEnabled(true); + starField_->render(camera, params.timeOfDay, params.cloudDensity, params.fogDensity); + } else if (starField_) { + starField_->setEnabled(false); + } + + // Render celestial bodies (sun + White Lady + Blue Child) + // Pass gameTime for deterministic moon phases + if (celestial_) { + celestial_->render(camera, params.timeOfDay, ¶ms.directionalDir, ¶ms.sunColor, params.gameTime); + } + + // Render clouds + if (clouds_) { + clouds_->render(camera, params.timeOfDay); + } + + // Render lens flare (sun glow effect) + if (lensFlare_) { + glm::vec3 sunPos = getSunPosition(params); + lensFlare_->render(camera, sunPos, params.timeOfDay); + } +} + +glm::vec3 SkySystem::getSunPosition(const SkyParams& params) const { + // Use lighting direction for sun position + const float sunDistance = 800.0f; + return -params.directionalDir * sunDistance; // Negative because light comes FROM sun +} + + +void SkySystem::setMoonPhaseCycling(bool enabled) { + if (celestial_) { + celestial_->setMoonPhaseCycling(enabled); + } +} + +void SkySystem::setWhiteLadyPhase(float phase) { + if (celestial_) { + celestial_->setMoonPhase(phase); // White Lady is primary moon + } +} + +void SkySystem::setBlueChildPhase(float phase) { + if (celestial_) { + celestial_->setBlueChildPhase(phase); + } +} + +float SkySystem::getWhiteLadyPhase() const { + return celestial_ ? celestial_->getMoonPhase() : 0.5f; +} + +float SkySystem::getBlueChildPhase() const { + // TODO: Second moon support + return 0.25f; // Placeholder phase +} + +} // namespace rendering +} // namespace wowee diff --git a/src/rendering/starfield.cpp b/src/rendering/starfield.cpp index 272c5141..73955993 100644 --- a/src/rendering/starfield.cpp +++ b/src/rendering/starfield.cpp @@ -98,7 +98,8 @@ void StarField::shutdown() { stars.clear(); } -void StarField::render(const Camera& camera, float timeOfDay) { +void StarField::render(const Camera& camera, float timeOfDay, + float cloudDensity, float fogDensity) { if (!renderingEnabled || vao == 0 || !starShader || stars.empty()) { return; } @@ -106,6 +107,10 @@ void StarField::render(const Camera& camera, float timeOfDay) { // Get star intensity based on time of day float intensity = getStarIntensity(timeOfDay); + // Reduce intensity based on cloud density and fog (more clouds/fog = fewer visible stars) + intensity *= (1.0f - glm::clamp(cloudDensity * 0.7f, 0.0f, 1.0f)); + intensity *= (1.0f - glm::clamp(fogDensity * 0.3f, 0.0f, 1.0f)); + // Don't render if stars would be invisible if (intensity <= 0.01f) { return; diff --git a/src/rendering/wmo_renderer.cpp b/src/rendering/wmo_renderer.cpp index ada42f58..c09b126d 100644 --- a/src/rendering/wmo_renderer.cpp +++ b/src/rendering/wmo_renderer.cpp @@ -583,6 +583,21 @@ void WMORenderer::clearCollisionFocus() { collisionFocusEnabled = false; } +void WMORenderer::setLighting(const float lightDirIn[3], const float lightColorIn[3], + const float ambientColorIn[3]) { + lightDir[0] = lightDirIn[0]; + lightDir[1] = lightDirIn[1]; + lightDir[2] = lightDirIn[2]; + + lightColor[0] = lightColorIn[0]; + lightColor[1] = lightColorIn[1]; + lightColor[2] = lightColorIn[2]; + + ambientColor[0] = ambientColorIn[0]; + ambientColor[1] = ambientColorIn[1]; + ambientColor[2] = ambientColorIn[2]; +} + void WMORenderer::resetQueryStats() { queryTimeMs = 0.0; queryCallCount = 0; @@ -802,10 +817,10 @@ void WMORenderer::render(const Camera& camera, const glm::mat4& view, const glm: shader->setUniform("uView", view); shader->setUniform("uProjection", projection); shader->setUniform("uViewPos", camera.getPosition()); - shader->setUniform("uLightDir", glm::vec3(-0.3f, -0.7f, -0.6f)); // Default sun direction - shader->setUniform("uLightColor", glm::vec3(1.5f, 1.4f, 1.3f)); + shader->setUniform("uLightDir", glm::vec3(lightDir[0], lightDir[1], lightDir[2])); + shader->setUniform("uLightColor", glm::vec3(lightColor[0], lightColor[1], lightColor[2])); shader->setUniform("uSpecularIntensity", 0.5f); - shader->setUniform("uAmbientColor", glm::vec3(0.55f, 0.55f, 0.6f)); + shader->setUniform("uAmbientColor", glm::vec3(ambientColor[0], ambientColor[1], ambientColor[2])); shader->setUniform("uFogColor", fogColor); shader->setUniform("uFogStart", fogStart); shader->setUniform("uFogEnd", fogEnd);