# include "weather.hpp"
# include <components/misc/rng.hpp>
# include <components/esm/esmreader.hpp>
# include <components/esm/esmwriter.hpp>
# include <components/esm/savedgame.hpp>
# include <components/esm/weatherstate.hpp>
# include <components/fallback/fallback.hpp>
# include "../mwbase/environment.hpp"
# include "../mwbase/world.hpp"
# include "../mwbase/soundmanager.hpp"
# include "../mwmechanics/actorutil.hpp"
# include "../mwsound/sound.hpp"
# include "../mwrender/renderingmanager.hpp"
# include "../mwrender/sky.hpp"
# include "player.hpp"
# include "esmstore.hpp"
# include "cellstore.hpp"
# include <cmath>
using namespace MWWorld ;
namespace
{
static const int invalidWeatherID = - 1 ;
// linear interpolate between x and y based on factor.
float lerp ( float x , float y , float factor )
{
return x * ( 1 - factor ) + y * factor ;
}
// linear interpolate between x and y based on factor.
osg : : Vec4f lerp ( const osg : : Vec4f & x , const osg : : Vec4f & y , float factor )
{
return x * ( 1 - factor ) + y * factor ;
}
}
template < typename T >
T TimeOfDayInterpolator < T > : : getValue ( const float gameHour , const TimeOfDaySettings & timeSettings ) const
{
// TODO: use pre/post sunset/sunrise time values in [Weather] section
// night
if ( gameHour < = timeSettings . mNightEnd | | gameHour > = timeSettings . mNightStart + 1 )
return mNightValue ;
// sunrise
else if ( gameHour > = timeSettings . mNightEnd & & gameHour < = timeSettings . mDayStart + 1 )
{
if ( gameHour < = timeSettings . mSunriseTime )
{
// fade in
float advance = timeSettings . mSunriseTime - gameHour ;
float factor = advance / 0.5f ;
return lerp ( mSunriseValue , mNightValue , factor ) ;
}
else
{
// fade out
float advance = gameHour - timeSettings . mSunriseTime ;
float factor = advance / 3.f ;
return lerp ( mSunriseValue , mDayValue , factor ) ;
}
}
// day
else if ( gameHour > = timeSettings . mDayStart + 1 & & gameHour < = timeSettings . mDayEnd - 1 )
return mDayValue ;
// sunset
else if ( gameHour > = timeSettings . mDayEnd - 1 & & gameHour < = timeSettings . mNightStart + 1 )
{
if ( gameHour < = timeSettings . mDayEnd + 1 )
{
// fade in
float advance = ( timeSettings . mDayEnd + 1 ) - gameHour ;
float factor = ( advance / 2 ) ;
return lerp ( mSunsetValue , mDayValue , factor ) ;
}
else
{
// fade out
float advance = gameHour - ( timeSettings . mDayEnd + 1 ) ;
float factor = advance / 2.f ;
return lerp ( mSunsetValue , mNightValue , factor ) ;
}
}
// shut up compiler
return T ( ) ;
}
template class MWWorld : : TimeOfDayInterpolator < float > ;
template class MWWorld : : TimeOfDayInterpolator < osg : : Vec4f > ;
Weather : : Weather ( const std : : string & name ,
const Fallback : : Map & fallback ,
float stormWindSpeed ,
float rainSpeed ,
const std : : string & particleEffect )
: mCloudTexture ( fallback . getFallbackString ( " Weather_ " + name + " _Cloud_Texture " ) )
, mSkyColor ( fallback . getFallbackColour ( " Weather_ " + name + " _Sky_Sunrise_Color " ) ,
fallback . getFallbackColour ( " Weather_ " + name + " _Sky_Day_Color " ) ,
fallback . getFallbackColour ( " Weather_ " + name + " _Sky_Sunset_Color " ) ,
fallback . getFallbackColour ( " Weather_ " + name + " _Sky_Night_Color " ) )
, mFogColor ( fallback . getFallbackColour ( " Weather_ " + name + " _Fog_Sunrise_Color " ) ,
fallback . getFallbackColour ( " Weather_ " + name + " _Fog_Day_Color " ) ,
fallback . getFallbackColour ( " Weather_ " + name + " _Fog_Sunset_Color " ) ,
fallback . getFallbackColour ( " Weather_ " + name + " _Fog_Night_Color " ) )
, mAmbientColor ( fallback . getFallbackColour ( " Weather_ " + name + " _Ambient_Sunrise_Color " ) ,
fallback . getFallbackColour ( " Weather_ " + name + " _Ambient_Day_Color " ) ,
fallback . getFallbackColour ( " Weather_ " + name + " _Ambient_Sunset_Color " ) ,
fallback . getFallbackColour ( " Weather_ " + name + " _Ambient_Night_Color " ) )
, mSunColor ( fallback . getFallbackColour ( " Weather_ " + name + " _Sun_Sunrise_Color " ) ,
fallback . getFallbackColour ( " Weather_ " + name + " _Sun_Day_Color " ) ,
fallback . getFallbackColour ( " Weather_ " + name + " _Sun_Sunset_Color " ) ,
fallback . getFallbackColour ( " Weather_ " + name + " _Sun_Night_Color " ) )
, mLandFogDepth ( fallback . getFallbackFloat ( " Weather_ " + name + " _Land_Fog_Day_Depth " ) ,
fallback . getFallbackFloat ( " Weather_ " + name + " _Land_Fog_Day_Depth " ) ,
fallback . getFallbackFloat ( " Weather_ " + name + " _Land_Fog_Day_Depth " ) ,
fallback . getFallbackFloat ( " Weather_ " + name + " _Land_Fog_Night_Depth " ) )
, mSunDiscSunsetColor ( fallback . getFallbackColour ( " Weather_ " + name + " _Sun_Disc_Sunset_Color " ) )
, mWindSpeed ( fallback . getFallbackFloat ( " Weather_ " + name + " _Wind_Speed " ) )
, mCloudSpeed ( fallback . getFallbackFloat ( " Weather_ " + name + " _Cloud_Speed " ) )
, mGlareView ( fallback . getFallbackFloat ( " Weather_ " + name + " _Glare_View " ) )
, mIsStorm ( mWindSpeed > stormWindSpeed )
, mRainSpeed ( rainSpeed )
, mRainFrequency ( fallback . getFallbackFloat ( " Weather_ " + name + " _Rain_Entrance_Speed " ) )
, mParticleEffect ( particleEffect )
, mRainEffect ( fallback . getFallbackBool ( " Weather_ " + name + " _Using_Precip " ) ? " meshes \\ raindrop.nif " : " " )
, mTransitionDelta ( fallback . getFallbackFloat ( " Weather_ " + name + " _Transition_Delta " ) )
, mCloudsMaximumPercent ( fallback . getFallbackFloat ( " Weather_ " + name + " _Clouds_Maximum_Percent " ) )
, mThunderFrequency ( fallback . getFallbackFloat ( " Weather_ " + name + " _Thunder_Frequency " ) )
, mThunderThreshold ( fallback . getFallbackFloat ( " Weather_ " + name + " _Thunder_Threshold " ) )
, mThunderSoundID ( )
, mFlashDecrement ( fallback . getFallbackFloat ( " Weather_ " + name + " _Flash_Decrement " ) )
, mFlashBrightness ( 0.0f )
{
mThunderSoundID [ 0 ] = fallback . getFallbackString ( " Weather_ " + name + " _Thunder_Sound_ID_0 " ) ;
mThunderSoundID [ 1 ] = fallback . getFallbackString ( " Weather_ " + name + " _Thunder_Sound_ID_1 " ) ;
mThunderSoundID [ 2 ] = fallback . getFallbackString ( " Weather_ " + name + " _Thunder_Sound_ID_2 " ) ;
mThunderSoundID [ 3 ] = fallback . getFallbackString ( " Weather_ " + name + " _Thunder_Sound_ID_3 " ) ;
// TODO: support weathers that have both "Ambient Loop Sound ID" and "Rain Loop Sound ID", need to play both sounds at the same time.
if ( ! mRainEffect . empty ( ) ) // NOTE: in vanilla, the weathers with rain seem to be hardcoded; changing Using_Precip has no effect
{
mAmbientLoopSoundID = fallback . getFallbackString ( " Weather_ " + name + " _Rain_Loop_Sound_ID " ) ;
if ( mAmbientLoopSoundID . empty ( ) ) // default to "rain" if not set
mAmbientLoopSoundID = " rain " ;
}
else
mAmbientLoopSoundID = fallback . getFallbackString ( " Weather_ " + name + " _Ambient_Loop_Sound_ID " ) ;
if ( Misc : : StringUtils : : ciEqual ( mAmbientLoopSoundID , " None " ) )
mAmbientLoopSoundID . clear ( ) ;
/*
Unhandled :
Rain Diameter = 600 ?
Rain Height Min = 200 ?
Rain Height Max = 700 ?
Rain Threshold = 0.6 ?
Max Raindrops = 650 ?
*/
}
float Weather : : transitionDelta ( ) const
{
// Transition Delta describes how quickly transitioning to the weather in question will take, in Hz. Note that the
// measurement is in real time, not in-game time.
return mTransitionDelta ;
}
float Weather : : cloudBlendFactor ( const float transitionRatio ) const
{
// Clouds Maximum Percent affects how quickly the sky transitions from one sky texture to the next.
return transitionRatio / mCloudsMaximumPercent ;
}
float Weather : : calculateThunder ( const float transitionRatio , const float elapsedSeconds , const bool isPaused )
{
// When paused, the flash brightness remains the same and no new strikes can occur.
if ( ! isPaused )
{
// Morrowind doesn't appear to do any calculations unless the transition ratio is higher than the Thunder Threshold.
if ( transitionRatio > = mThunderThreshold & & mThunderFrequency > 0.0f )
{
flashDecrement ( elapsedSeconds ) ;
if ( Misc : : Rng : : rollProbability ( ) < = thunderChance ( transitionRatio , elapsedSeconds ) )
{
lightningAndThunder ( ) ;
}
}
else
{
mFlashBrightness = 0.0f ;
}
}
return mFlashBrightness ;
}
inline void Weather : : flashDecrement ( const float elapsedSeconds )
{
// The Flash Decrement is measured in whole units per second. This means that if the flash brightness was
// currently 1.0, then it should take approximately 0.25 seconds to decay to 0.0 (the minimum).
float decrement = mFlashDecrement * elapsedSeconds ;
mFlashBrightness = decrement > mFlashBrightness ? 0.0f : mFlashBrightness - decrement ;
}
inline float Weather : : thunderChance ( const float transitionRatio , const float elapsedSeconds ) const
{
// This formula is reversed from the observation that with Thunder Frequency set to 1, there are roughly 10 strikes
// per minute. It doesn't appear to be tied to in game time as Timescale doesn't affect it. Various values of
// Thunder Frequency seem to change the average number of strikes in a linear fashion.. During a transition, it appears to
// scaled based on how far past it is past the Thunder Threshold.
float scaleFactor = ( transitionRatio - mThunderThreshold ) / ( 1.0f - mThunderThreshold ) ;
return ( ( mThunderFrequency * 10.0f ) / 60.0f ) * elapsedSeconds * scaleFactor ;
}
inline void Weather : : lightningAndThunder ( void )
{
// Morrowind seems to vary the intensity of the brightness based on which of the four sound IDs it selects.
// They appear to go from 0 (brightest, closest) to 3 (faintest, farthest). The value of 0.25 per distance
// was derived by setting the Flash Decrement to 0.1 and measuring how long each value took to decay to 0.
// TODO: Determine the distribution of each distance to see if it's evenly weighted.
unsigned int distance = Misc : : Rng : : rollDice ( 4 ) ;
// Flash brightness appears additive, since if multiple strikes occur, it takes longer for it to decay to 0.
mFlashBrightness + = 1 - ( distance * 0.25f ) ;
MWBase : : Environment : : get ( ) . getSoundManager ( ) - > playSound ( mThunderSoundID [ distance ] , 1.0 , 1.0 ) ;
}
RegionWeather : : RegionWeather ( const ESM : : Region & region )
: mWeather ( invalidWeatherID )
, mChances ( )
{
mChances . reserve ( 10 ) ;
mChances . push_back ( region . mData . mClear ) ;
mChances . push_back ( region . mData . mCloudy ) ;
mChances . push_back ( region . mData . mFoggy ) ;
mChances . push_back ( region . mData . mOvercast ) ;
mChances . push_back ( region . mData . mRain ) ;
mChances . push_back ( region . mData . mThunder ) ;
mChances . push_back ( region . mData . mAsh ) ;
mChances . push_back ( region . mData . mBlight ) ;
mChances . push_back ( region . mData . mA ) ;
mChances . push_back ( region . mData . mB ) ;
}
RegionWeather : : RegionWeather ( const ESM : : RegionWeatherState & state )
: mWeather ( state . mWeather )
, mChances ( state . mChances )
{
}
RegionWeather : : operator ESM : : RegionWeatherState ( ) const
{
ESM : : RegionWeatherState state =
{
mWeather ,
mChances
} ;
return state ;
}
void RegionWeather : : setChances ( const std : : vector < char > & chances )
{
if ( mChances . size ( ) < chances . size ( ) )
{
mChances . reserve ( chances . size ( ) ) ;
}
std : : vector < char > : : const_iterator it = chances . begin ( ) ;
for ( size_t i = 0 ; it ! = chances . end ( ) ; + + it , + + i )
{
mChances [ i ] = * it ;
}
// Regional weather no longer supports the current type, select a new weather pattern.
if ( ( static_cast < size_t > ( mWeather ) > = mChances . size ( ) ) | | ( mChances [ mWeather ] = = 0 ) )
{
chooseNewWeather ( ) ;
}
}
void RegionWeather : : setWeather ( int weatherID )
{
mWeather = weatherID ;
}
int RegionWeather : : getWeather ( )
{
// If the region weather was already set (by ChangeWeather, or by a previous call) then just return that value.
// Note that the region weather will be expired periodically when the weather update timer expires.
if ( mWeather = = invalidWeatherID )
{
chooseNewWeather ( ) ;
}
return mWeather ;
}
void RegionWeather : : chooseNewWeather ( )
{
// All probabilities must add to 100 (responsibility of the user).
// If chances A and B has values 30 and 70 then by generating 100 numbers 1..100, 30% will be lesser or equal 30
// and 70% will be greater than 30 (in theory).
int chance = Misc : : Rng : : rollDice ( 100 ) + 1 ; // 1..100
int sum = 0 ;
int i = 0 ;
for ( ; static_cast < size_t > ( i ) < mChances . size ( ) ; + + i )
{
sum + = mChances [ i ] ;
if ( chance < = sum )
{
mWeather = i ;
return ;
}
}
// if we hit this path then the chances don't add to 100, choose a default weather instead
mWeather = 0 ;
}
MoonModel : : MoonModel ( const std : : string & name , const Fallback : : Map & fallback )
: mFadeInStart ( fallback . getFallbackFloat ( " Moons_ " + name + " _Fade_In_Start " ) )
, mFadeInFinish ( fallback . getFallbackFloat ( " Moons_ " + name + " _Fade_In_Finish " ) )
, mFadeOutStart ( fallback . getFallbackFloat ( " Moons_ " + name + " _Fade_Out_Start " ) )
, mFadeOutFinish ( fallback . getFallbackFloat ( " Moons_ " + name + " _Fade_Out_Finish " ) )
, mAxisOffset ( fallback . getFallbackFloat ( " Moons_ " + name + " _Axis_Offset " ) )
, mSpeed ( fallback . getFallbackFloat ( " Moons_ " + name + " _Speed " ) )
, mDailyIncrement ( fallback . getFallbackFloat ( " Moons_ " + name + " _Daily_Increment " ) )
, mFadeStartAngle ( fallback . getFallbackFloat ( " Moons_ " + name + " _Fade_Start_Angle " ) )
, mFadeEndAngle ( fallback . getFallbackFloat ( " Moons_ " + name + " _Fade_End_Angle " ) )
, mMoonShadowEarlyFadeAngle ( fallback . getFallbackFloat ( " Moons_ " + name + " _Moon_Shadow_Early_Fade_Angle " ) )
{
// Morrowind appears to have a minimum speed in order to avoid situations where the moon couldn't conceivably
// complete a rotation in a single 24 hour period. The value of 180/23 was deduced from reverse engineering.
mSpeed = std : : min ( mSpeed , 180.0f / 23.0f ) ;
}
MWRender : : MoonState MoonModel : : calculateState ( const TimeStamp & gameTime ) const
{
float rotationFromHorizon = angle ( gameTime ) ;
MWRender : : MoonState state =
{
rotationFromHorizon ,
mAxisOffset , // Reverse engineered from Morrowind's scene graph rotation matrices.
static_cast < MWRender : : MoonState : : Phase > ( phase ( gameTime ) ) ,
shadowBlend ( rotationFromHorizon ) ,
earlyMoonShadowAlpha ( rotationFromHorizon ) * hourlyAlpha ( gameTime . getHour ( ) )
} ;
return state ;
}
inline float MoonModel : : angle ( const TimeStamp & gameTime ) const
{
// Morrowind's moons start travel on one side of the horizon (let's call it H-rise) and travel 180 degrees to the
// opposite horizon (let's call it H-set). Upon reaching H-set, they reset to H-rise until the next moon rise.
// When calculating the angle of the moon, several cases have to be taken into account:
// 1. Moon rises and then sets in one day.
// 2. Moon sets and doesn't rise in one day (occurs when the moon rise hour is >= 24).
// 3. Moon sets and then rises in one day.
float moonRiseHourToday = moonRiseHour ( gameTime . getDay ( ) ) ;
float moonRiseAngleToday = 0 ;
if ( gameTime . getHour ( ) < moonRiseHourToday )
{
float moonRiseHourYesterday = moonRiseHour ( gameTime . getDay ( ) - 1 ) ;
if ( moonRiseHourYesterday < 24 )
{
float moonRiseAngleYesterday = rotation ( 24 - moonRiseHourYesterday ) ;
if ( moonRiseAngleYesterday < 180 )
{
// The moon rose but did not set yesterday, so accumulate yesterday's angle with how much we've travelled today.
moonRiseAngleToday = rotation ( gameTime . getHour ( ) ) + moonRiseAngleYesterday ;
}
}
}
else
{
moonRiseAngleToday = rotation ( gameTime . getHour ( ) - moonRiseHourToday ) ;
}
if ( moonRiseAngleToday > = 180 )
{
// The moon set today, reset the angle to the horizon.
moonRiseAngleToday = 0 ;
}
return moonRiseAngleToday ;
}
inline float MoonModel : : moonRiseHour ( unsigned int daysPassed ) const
{
// This arises from the start date of 16 Last Seed, 427
// TODO: Find an alternate formula that doesn't rely on this day being fixed.
static const unsigned int startDay = 16 ;
// This odd formula arises from the fact that on 16 Last Seed, 17 increments have occurred, meaning
// that upon starting a new game, it must only calculate the moon phase as far back as 1 Last Seed.
// Note that we don't modulo after adding the latest daily increment because other calculations need to
// know if doing so would cause the moon rise to be postponed until the next day (which happens when
// the moon rise hour is >= 24 in Morrowind).
return mDailyIncrement + std : : fmod ( ( daysPassed - 1 + startDay ) * mDailyIncrement , 24.0f ) ;
}
inline float MoonModel : : rotation ( float hours ) const
{
// 15 degrees per hour was reverse engineered from the rotation matrices of the Morrowind scene graph.
// Note that this correlates to 360 / 24, which is a full rotation every 24 hours, so speed is a measure
// of whole rotations that could be completed in a day.
return 15.0f * mSpeed * hours ;
}
inline unsigned int MoonModel : : phase ( const TimeStamp & gameTime ) const
{
// Morrowind starts with a full moon on 16 Last Seed and then begins to wane 17 Last Seed, working on 3 day phase cycle.
// Note: this is an internal helper, and as such we don't want to return MWRender::MoonState::Phase since we can't
// forward declare it (C++11 strongly typed enums solve this).
// If the moon didn't rise yet today, use yesterday's moon phase.
if ( gameTime . getHour ( ) < moonRiseHour ( gameTime . getDay ( ) ) )
return ( gameTime . getDay ( ) / 3 ) % 8 ;
else
return ( ( gameTime . getDay ( ) + 1 ) / 3 ) % 8 ;
}
inline float MoonModel : : shadowBlend ( float angle ) const
{
// The Fade End Angle and Fade Start Angle describe a region where the moon transitions from a solid disk
// that is roughly the color of the sky, to a textured surface.
// Depending on the current angle, the following values describe the ratio between the textured moon
// and the solid disk:
// 1. From Fade End Angle 1 to Fade Start Angle 1 (during moon rise): 0..1
// 2. From Fade Start Angle 1 to Fade Start Angle 2 (between moon rise and moon set): 1 (textured)
// 3. From Fade Start Angle 2 to Fade End Angle 2 (during moon set): 1..0
// 4. From Fade End Angle 2 to Fade End Angle 1 (between moon set and moon rise): 0 (solid disk)
float fadeAngle = mFadeStartAngle - mFadeEndAngle ;
float fadeEndAngle2 = 180.0f - mFadeEndAngle ;
float fadeStartAngle2 = 180.0f - mFadeStartAngle ;
if ( ( angle > = mFadeEndAngle ) & & ( angle < mFadeStartAngle ) )
return ( angle - mFadeEndAngle ) / fadeAngle ;
else if ( ( angle > = mFadeStartAngle ) & & ( angle < fadeStartAngle2 ) )
return 1.0f ;
else if ( ( angle > = fadeStartAngle2 ) & & ( angle < fadeEndAngle2 ) )
return ( fadeEndAngle2 - angle ) / fadeAngle ;
else
return 0.0f ;
}
inline float MoonModel : : hourlyAlpha ( float gameHour ) const
{
// The Fade Out Start / Finish and Fade In Start / Finish describe the hours at which the moon
// appears and disappears.
// Depending on the current hour, the following values describe how transparent the moon is.
// 1. From Fade Out Start to Fade Out Finish: 1..0
// 2. From Fade Out Finish to Fade In Start: 0 (transparent)
// 3. From Fade In Start to Fade In Finish: 0..1
// 4. From Fade In Finish to Fade Out Start: 1 (solid)
if ( ( gameHour > = mFadeOutStart ) & & ( gameHour < mFadeOutFinish ) )
return ( mFadeOutFinish - gameHour ) / ( mFadeOutFinish - mFadeOutStart ) ;
else if ( ( gameHour > = mFadeOutFinish ) & & ( gameHour < mFadeInStart ) )
return 0.0f ;
else if ( ( gameHour > = mFadeInStart ) & & ( gameHour < mFadeInFinish ) )
return ( gameHour - mFadeInStart ) / ( mFadeInFinish - mFadeInStart ) ;
else
return 1.0f ;
}
inline float MoonModel : : earlyMoonShadowAlpha ( float angle ) const
{
// The Moon Shadow Early Fade Angle describes an arc relative to Fade End Angle.
// Depending on the current angle, the following values describe how transparent the moon is.
// 1. From Moon Shadow Early Fade Angle 1 to Fade End Angle 1 (during moon rise): 0..1
// 2. From Fade End Angle 1 to Fade End Angle 2 (between moon rise and moon set): 1 (solid)
// 3. From Fade End Angle 2 to Moon Shadow Early Fade Angle 2 (during moon set): 1..0
// 4. From Moon Shadow Early Fade Angle 2 to Moon Shadow Early Fade Angle 1: 0 (transparent)
float moonShadowEarlyFadeAngle1 = mFadeEndAngle - mMoonShadowEarlyFadeAngle ;
float fadeEndAngle2 = 180.0f - mFadeEndAngle ;
float moonShadowEarlyFadeAngle2 = fadeEndAngle2 + mMoonShadowEarlyFadeAngle ;
if ( ( angle > = moonShadowEarlyFadeAngle1 ) & & ( angle < mFadeEndAngle ) )
return ( angle - moonShadowEarlyFadeAngle1 ) / mMoonShadowEarlyFadeAngle ;
else if ( ( angle > = mFadeEndAngle ) & & ( angle < fadeEndAngle2 ) )
return 1.0f ;
else if ( ( angle > = fadeEndAngle2 ) & & ( angle < moonShadowEarlyFadeAngle2 ) )
return ( moonShadowEarlyFadeAngle2 - angle ) / mMoonShadowEarlyFadeAngle ;
else
return 0.0f ;
}
WeatherManager : : WeatherManager ( MWRender : : RenderingManager & rendering , const Fallback : : Map & fallback , MWWorld : : ESMStore & store )
: mStore ( store )
, mRendering ( rendering )
, mSunriseTime ( fallback . getFallbackFloat ( " Weather_Sunrise_Time " ) )
, mSunsetTime ( fallback . getFallbackFloat ( " Weather_Sunset_Time " ) )
, mSunriseDuration ( fallback . getFallbackFloat ( " Weather_Sunrise_Duration " ) )
, mSunsetDuration ( fallback . getFallbackFloat ( " Weather_Sunset_Duration " ) )
, mSunPreSunsetTime ( fallback . getFallbackFloat ( " Weather_Sun_Pre-Sunset_Time " ) )
, mNightFade ( 0 , 0 , 0 , 1 )
, mHoursBetweenWeatherChanges ( fallback . getFallbackFloat ( " Weather_Hours_Between_Weather_Changes " ) )
, mRainSpeed ( fallback . getFallbackFloat ( " Weather_Precip_Gravity " ) )
, mUnderwaterFog ( fallback . getFallbackFloat ( " Water_UnderwaterSunriseFog " ) ,
fallback . getFallbackFloat ( " Water_UnderwaterDayFog " ) ,
fallback . getFallbackFloat ( " Water_UnderwaterSunsetFog " ) ,
fallback . getFallbackFloat ( " Water_UnderwaterNightFog " ) )
, mWeatherSettings ( )
, mMasser ( " Masser " , fallback )
, mSecunda ( " Secunda " , fallback )
, mWindSpeed ( 0.f )
, mIsStorm ( false )
, mStormDirection ( 0 , 1 , 0 )
, mCurrentRegion ( )
, mTimePassed ( 0 )
, mFastForward ( false )
, mWeatherUpdateTime ( mHoursBetweenWeatherChanges )
, mTransitionFactor ( 0 )
, mCurrentWeather ( 0 )
, mNextWeather ( 0 )
, mQueuedWeather ( 0 )
, mRegions ( )
, mResult ( )
, mAmbientSound ( nullptr )
, mPlayingSoundID ( )
{
mTimeSettings . mNightStart = mSunsetTime + mSunsetDuration ;
mTimeSettings . mNightEnd = mSunriseTime - 0.5f ;
mTimeSettings . mDayStart = mSunriseTime + mSunriseDuration ;
mTimeSettings . mDayEnd = mSunsetTime ;
mTimeSettings . mSunriseTime = mSunriseTime ;
mWeatherSettings . reserve ( 10 ) ;
addWeather ( " Clear " , fallback ) ; // 0
addWeather ( " Cloudy " , fallback ) ; // 1
addWeather ( " Foggy " , fallback ) ; // 2
addWeather ( " Overcast " , fallback ) ; // 3
addWeather ( " Rain " , fallback ) ; // 4
addWeather ( " Thunderstorm " , fallback ) ; // 5
addWeather ( " Ashstorm " , fallback , " meshes \\ ashcloud.nif " ) ; // 6
addWeather ( " Blight " , fallback , " meshes \\ blightcloud.nif " ) ; // 7
addWeather ( " Snow " , fallback , " meshes \\ snow.nif " ) ; // 8
addWeather ( " Blizzard " , fallback , " meshes \\ blizzard.nif " ) ; // 9
Store < ESM : : Region > : : iterator it = store . get < ESM : : Region > ( ) . begin ( ) ;
for ( ; it ! = store . get < ESM : : Region > ( ) . end ( ) ; + + it )
{
std : : string regionID = Misc : : StringUtils : : lowerCase ( it - > mId ) ;
mRegions . insert ( std : : make_pair ( regionID , RegionWeather ( * it ) ) ) ;
}
forceWeather ( 0 ) ;
}
WeatherManager : : ~ WeatherManager ( )
{
stopSounds ( ) ;
}
void WeatherManager : : changeWeather ( const std : : string & regionID , const unsigned int weatherID )
{
// In Morrowind, this seems to have the following behavior, when applied to the current region:
// - When there is no transition in progress, start transitioning to the new weather.
// - If there is a transition in progress, queue up the transition and process it when the current one completes.
// - If there is a transition in progress, and a queued transition, overwrite the queued transition.
// - If multiple calls to ChangeWeather are made while paused (console up), only the last call will be used,
// meaning that if there was no transition in progress, only the last ChangeWeather will be processed.
// If the region isn't current, Morrowind will store the new weather for the region in question.
if ( weatherID < mWeatherSettings . size ( ) )
{
std : : string lowerCaseRegionID = Misc : : StringUtils : : lowerCase ( regionID ) ;
std : : map < std : : string , RegionWeather > : : iterator it = mRegions . find ( lowerCaseRegionID ) ;
if ( it ! = mRegions . end ( ) )
{
it - > second . setWeather ( weatherID ) ;
regionalWeatherChanged ( it - > first , it - > second ) ;
}
}
}
void WeatherManager : : modRegion ( const std : : string & regionID , const std : : vector < char > & chances )
{
// Sets the region's probability for various weather patterns. Note that this appears to be saved permanently.
// In Morrowind, this seems to have the following behavior when applied to the current region:
// - If the region supports the current weather, no change in current weather occurs.
// - If the region no longer supports the current weather, and there is no transition in progress, begin to
// transition to a new supported weather type.
// - If the region no longer supports the current weather, and there is a transition in progress, queue a
// transition to a new supported weather type.
std : : string lowerCaseRegionID = Misc : : StringUtils : : lowerCase ( regionID ) ;
std : : map < std : : string , RegionWeather > : : iterator it = mRegions . find ( lowerCaseRegionID ) ;
if ( it ! = mRegions . end ( ) )
{
it - > second . setChances ( chances ) ;
regionalWeatherChanged ( it - > first , it - > second ) ;
}
}
void WeatherManager : : playerTeleported ( )
{
// If the player teleports to an outdoors cell in a new region (for instance, by travelling), the weather needs to
// be changed immediately, and any transitions for the previous region discarded.
MWBase : : World * world = MWBase : : Environment : : get ( ) . getWorld ( ) ;
if ( world - > isCellExterior ( ) | | world - > isCellQuasiExterior ( ) )
{
std : : string playerRegion = Misc : : StringUtils : : lowerCase ( world - > getPlayerPtr ( ) . getCell ( ) - > getCell ( ) - > mRegion ) ;
std : : map < std : : string , RegionWeather > : : iterator it = mRegions . find ( playerRegion ) ;
if ( it ! = mRegions . end ( ) & & playerRegion ! = mCurrentRegion )
{
mCurrentRegion = playerRegion ;
forceWeather ( it - > second . getWeather ( ) ) ;
}
}
}
void WeatherManager : : update ( float duration , bool paused )
{
MWWorld : : ConstPtr player = MWMechanics : : getPlayer ( ) ;
MWBase : : World & world = * MWBase : : Environment : : get ( ) . getWorld ( ) ;
TimeStamp time = world . getTimeStamp ( ) ;
if ( ! paused | | mFastForward )
{
// Add new transitions when either the player's current external region changes.
std : : string playerRegion = Misc : : StringUtils : : lowerCase ( player . getCell ( ) - > getCell ( ) - > mRegion ) ;
if ( updateWeatherTime ( ) | | updateWeatherRegion ( playerRegion ) )
{
std : : map < std : : string , RegionWeather > : : iterator it = mRegions . find ( mCurrentRegion ) ;
if ( it ! = mRegions . end ( ) )
{
addWeatherTransition ( it - > second . getWeather ( ) ) ;
}
}
updateWeatherTransitions ( duration ) ;
}
const bool exterior = ( world . isCellExterior ( ) | | world . isCellQuasiExterior ( ) ) ;
if ( ! exterior )
{
mRendering . setSkyEnabled ( false ) ;
stopSounds ( ) ;
return ;
}
calculateWeatherResult ( time . getHour ( ) , duration , paused ) ;
mWindSpeed = mResult . mWindSpeed ;
mIsStorm = mResult . mIsStorm ;
if ( mIsStorm )
{
osg : : Vec3f playerPos ( player . getRefData ( ) . getPosition ( ) . asVec3 ( ) ) ;
osg : : Vec3f redMountainPos ( 19950 , 72032 , 27831 ) ;
mStormDirection = ( playerPos - redMountainPos ) ;
mStormDirection . z ( ) = 0 ;
mStormDirection . normalize ( ) ;
mRendering . getSkyManager ( ) - > setStormDirection ( mStormDirection ) ;
}
// disable sun during night
if ( time . getHour ( ) > = mTimeSettings . mNightStart | | time . getHour ( ) < = mSunriseTime )
mRendering . getSkyManager ( ) - > sunDisable ( ) ;
else
mRendering . getSkyManager ( ) - > sunEnable ( ) ;
// Update the sun direction. Run it east to west at a fixed angle from overhead.
// The sun's speed at day and night may differ, since mSunriseTime and mNightStart
// mark when the sun is level with the horizon.
{
// Shift times into a 24-hour window beginning at mSunriseTime...
float adjustedHour = time . getHour ( ) ;
float adjustedNightStart = mTimeSettings . mNightStart ;
if ( time . getHour ( ) < mSunriseTime )
adjustedHour + = 24.f ;
if ( mTimeSettings . mNightStart < mSunriseTime )
adjustedNightStart + = 24.f ;
const bool is_night = adjustedHour > = adjustedNightStart ;
const float dayDuration = adjustedNightStart - mSunriseTime ;
const float nightDuration = 24.f - dayDuration ;
double theta ;
if ( ! is_night ) {
theta = static_cast < float > ( osg : : PI ) * ( adjustedHour - mSunriseTime ) / dayDuration ;
} else {
theta = static_cast < float > ( osg : : PI ) * ( 1.f - ( adjustedHour - adjustedNightStart ) / nightDuration ) ;
}
osg : : Vec3f final (
static_cast < float > ( cos ( theta ) ) ,
- 0.268f , // approx tan( -15 degrees )
static_cast < float > ( sin ( theta ) ) ) ;
mRendering . setSunDirection ( final * - 1 ) ;
}
float underwaterFog = mUnderwaterFog . getValue ( time . getHour ( ) , mTimeSettings ) ;
float peakHour = mSunriseTime + ( mSunsetTime - mSunriseTime ) / 2 ;
if ( time . getHour ( ) < mSunriseTime | | time . getHour ( ) > mSunsetTime )
mRendering . getSkyManager ( ) - > setGlareTimeOfDayFade ( 0 ) ;
else if ( time . getHour ( ) < peakHour )
mRendering . getSkyManager ( ) - > setGlareTimeOfDayFade ( 1 - ( peakHour - time . getHour ( ) ) / ( peakHour - mSunriseTime ) ) ;
else
mRendering . getSkyManager ( ) - > setGlareTimeOfDayFade ( 1 - ( time . getHour ( ) - peakHour ) / ( mSunsetTime - peakHour ) ) ;
mRendering . getSkyManager ( ) - > setMasserState ( mMasser . calculateState ( time ) ) ;
mRendering . getSkyManager ( ) - > setSecundaState ( mSecunda . calculateState ( time ) ) ;
mRendering . configureFog ( mResult . mFogDepth , underwaterFog , mResult . mFogColor ) ;
mRendering . setAmbientColour ( mResult . mAmbientColor ) ;
mRendering . setSunColour ( mResult . mSunColor , mResult . mSunColor * mResult . mGlareView ) ;
mRendering . getSkyManager ( ) - > setWeather ( mResult ) ;
// Play sounds
if ( mPlayingSoundID ! = mResult . mAmbientLoopSoundID )
{
stopSounds ( ) ;
if ( ! mResult . mAmbientLoopSoundID . empty ( ) )
mAmbientSound = MWBase : : Environment : : get ( ) . getSoundManager ( ) - > playSound (
mResult . mAmbientLoopSoundID , mResult . mAmbientSoundVolume , 1.0 ,
MWSound : : Type : : Sfx , MWSound : : PlayMode : : Loop
) ;
mPlayingSoundID = mResult . mAmbientLoopSoundID ;
}
else if ( mAmbientSound )
mAmbientSound - > setVolume ( mResult . mAmbientSoundVolume ) ;
}
void WeatherManager : : stopSounds ( )
{
if ( mAmbientSound )
MWBase : : Environment : : get ( ) . getSoundManager ( ) - > stopSound ( mAmbientSound ) ;
mAmbientSound = nullptr ;
mPlayingSoundID . clear ( ) ;
}
float WeatherManager : : getWindSpeed ( ) const
{
return mWindSpeed ;
}
bool WeatherManager : : isInStorm ( ) const
{
return mIsStorm ;
}
osg : : Vec3f WeatherManager : : getStormDirection ( ) const
{
return mStormDirection ;
}
void WeatherManager : : advanceTime ( double hours , bool incremental )
{
// In Morrowind, when the player sleeps/waits, serves jail time, travels, or trains, all weather transitions are
// immediately applied, regardless of whatever transition time might have been remaining.
mTimePassed + = hours ;
mFastForward = ! incremental ? true : mFastForward ;
}
unsigned int WeatherManager : : getWeatherID ( ) const
{
return mCurrentWeather ;
}
bool WeatherManager : : isDark ( ) const
{
TimeStamp time = MWBase : : Environment : : get ( ) . getWorld ( ) - > getTimeStamp ( ) ;
bool exterior = ( MWBase : : Environment : : get ( ) . getWorld ( ) - > isCellExterior ( )
| | MWBase : : Environment : : get ( ) . getWorld ( ) - > isCellQuasiExterior ( ) ) ;
return exterior & & ( time . getHour ( ) < mSunriseTime | | time . getHour ( ) > mTimeSettings . mNightStart - 1 ) ;
}
void WeatherManager : : write ( ESM : : ESMWriter & writer , Loading : : Listener & progress )
{
ESM : : WeatherState state ;
state . mCurrentRegion = mCurrentRegion ;
state . mTimePassed = mTimePassed ;
state . mFastForward = mFastForward ;
state . mWeatherUpdateTime = mWeatherUpdateTime ;
state . mTransitionFactor = mTransitionFactor ;
state . mCurrentWeather = mCurrentWeather ;
state . mNextWeather = mNextWeather ;
state . mQueuedWeather = mQueuedWeather ;
std : : map < std : : string , RegionWeather > : : iterator it = mRegions . begin ( ) ;
for ( ; it ! = mRegions . end ( ) ; + + it )
{
state . mRegions . insert ( std : : make_pair ( it - > first , it - > second ) ) ;
}
writer . startRecord ( ESM : : REC_WTHR ) ;
state . save ( writer ) ;
writer . endRecord ( ESM : : REC_WTHR ) ;
}
bool WeatherManager : : readRecord ( ESM : : ESMReader & reader , uint32_t type )
{
if ( ESM : : REC_WTHR = = type )
{
static const int oldestCompatibleSaveFormat = 2 ;
if ( reader . getFormat ( ) < oldestCompatibleSaveFormat )
{
// Weather state isn't really all that important, so to preserve older save games, we'll just discard the
// older weather records, rather than fail to handle the record.
reader . skipRecord ( ) ;
}
else
{
ESM : : WeatherState state ;
state . load ( reader ) ;
mCurrentRegion . swap ( state . mCurrentRegion ) ;
mTimePassed = state . mTimePassed ;
mFastForward = state . mFastForward ;
mWeatherUpdateTime = state . mWeatherUpdateTime ;
mTransitionFactor = state . mTransitionFactor ;
mCurrentWeather = state . mCurrentWeather ;
mNextWeather = state . mNextWeather ;
mQueuedWeather = state . mQueuedWeather ;
mRegions . clear ( ) ;
importRegions ( ) ;
for ( std : : map < std : : string , ESM : : RegionWeatherState > : : iterator it = state . mRegions . begin ( ) ; it ! = state . mRegions . end ( ) ; + + it )
{
std : : map < std : : string , RegionWeather > : : iterator found = mRegions . find ( it - > first ) ;
if ( found ! = mRegions . end ( ) )
{
found - > second = RegionWeather ( it - > second ) ;
}
}
}
return true ;
}
return false ;
}
void WeatherManager : : clear ( )
{
stopSounds ( ) ;
mCurrentRegion = " " ;
mTimePassed = 0.0f ;
mWeatherUpdateTime = 0.0f ;
forceWeather ( 0 ) ;
mRegions . clear ( ) ;
importRegions ( ) ;
}
inline void WeatherManager : : addWeather ( const std : : string & name ,
const Fallback : : Map & fallback ,
const std : : string & particleEffect )
{
static const float fStromWindSpeed = mStore . get < ESM : : GameSetting > ( ) . find ( " fStromWindSpeed " ) - > getFloat ( ) ;
Weather weather ( name , fallback , fStromWindSpeed , mRainSpeed , particleEffect ) ;
mWeatherSettings . push_back ( weather ) ;
}
inline void WeatherManager : : importRegions ( )
{
Store < ESM : : Region > : : iterator it = mStore . get < ESM : : Region > ( ) . begin ( ) ;
for ( ; it ! = mStore . get < ESM : : Region > ( ) . end ( ) ; + + it )
{
std : : string regionID = Misc : : StringUtils : : lowerCase ( it - > mId ) ;
mRegions . insert ( std : : make_pair ( regionID , RegionWeather ( * it ) ) ) ;
}
}
inline void WeatherManager : : regionalWeatherChanged ( const std : : string & regionID , RegionWeather & region )
{
// If the region is current, then add a weather transition for it.
MWWorld : : ConstPtr player = MWMechanics : : getPlayer ( ) ;
if ( player . isInCell ( ) )
{
if ( Misc : : StringUtils : : ciEqual ( regionID , mCurrentRegion ) )
{
addWeatherTransition ( region . getWeather ( ) ) ;
}
}
}
inline bool WeatherManager : : updateWeatherTime ( )
{
mWeatherUpdateTime - = mTimePassed ;
mTimePassed = 0.0f ;
if ( mWeatherUpdateTime < = 0.0f )
{
// Expire all regional weather, so that any call to getWeather() will return a new weather ID.
std : : map < std : : string , RegionWeather > : : iterator it = mRegions . begin ( ) ;
for ( ; it ! = mRegions . end ( ) ; + + it )
{
it - > second . setWeather ( invalidWeatherID ) ;
}
mWeatherUpdateTime + = mHoursBetweenWeatherChanges ;
return true ;
}
return false ;
}
inline bool WeatherManager : : updateWeatherRegion ( const std : : string & playerRegion )
{
if ( ! playerRegion . empty ( ) & & playerRegion ! = mCurrentRegion )
{
mCurrentRegion = playerRegion ;
return true ;
}
return false ;
}
inline void WeatherManager : : updateWeatherTransitions ( const float elapsedRealSeconds )
{
// When a player chooses to train, wait, or serves jail time, any transitions will be fast forwarded to the last
// weather type set, regardless of the remaining transition time.
if ( ! mFastForward & & inTransition ( ) )
{
const float delta = mWeatherSettings [ mNextWeather ] . transitionDelta ( ) ;
mTransitionFactor - = elapsedRealSeconds * delta ;
if ( mTransitionFactor < = 0.0f )
{
mCurrentWeather = mNextWeather ;
mNextWeather = mQueuedWeather ;
mQueuedWeather = invalidWeatherID ;
// We may have begun processing the queued transition, so we need to apply the remaining time towards it.
if ( inTransition ( ) )
{
const float newDelta = mWeatherSettings [ mNextWeather ] . transitionDelta ( ) ;
const float remainingSeconds = - ( mTransitionFactor / delta ) ;
mTransitionFactor = 1.0f - ( remainingSeconds * newDelta ) ;
}
else
{
mTransitionFactor = 0.0f ;
}
}
}
else
{
if ( mQueuedWeather ! = invalidWeatherID )
{
mCurrentWeather = mQueuedWeather ;
}
else if ( mNextWeather ! = invalidWeatherID )
{
mCurrentWeather = mNextWeather ;
}
mNextWeather = invalidWeatherID ;
mQueuedWeather = invalidWeatherID ;
mFastForward = false ;
}
}
inline void WeatherManager : : forceWeather ( const int weatherID )
{
mTransitionFactor = 0.0f ;
mCurrentWeather = weatherID ;
mNextWeather = invalidWeatherID ;
mQueuedWeather = invalidWeatherID ;
}
inline bool WeatherManager : : inTransition ( )
{
return mNextWeather ! = invalidWeatherID ;
}
inline void WeatherManager : : addWeatherTransition ( const int weatherID )
{
// In order to work like ChangeWeather expects, this method begins transitioning to the new weather immediately if
// no transition is in progress, otherwise it queues it to be transitioned.
assert ( weatherID > = 0 & & static_cast < size_t > ( weatherID ) < mWeatherSettings . size ( ) ) ;
if ( ! inTransition ( ) & & ( weatherID ! = mCurrentWeather ) )
{
mNextWeather = weatherID ;
mTransitionFactor = 1.0f ;
}
else if ( inTransition ( ) & & ( weatherID ! = mNextWeather ) )
{
mQueuedWeather = weatherID ;
}
}
inline void WeatherManager : : calculateWeatherResult ( const float gameHour ,
const float elapsedSeconds ,
const bool isPaused )
{
float flash = 0.0f ;
if ( ! inTransition ( ) )
{
calculateResult ( mCurrentWeather , gameHour ) ;
flash = mWeatherSettings [ mCurrentWeather ] . calculateThunder ( 1.0f , elapsedSeconds , isPaused ) ;
}
else
{
calculateTransitionResult ( 1 - mTransitionFactor , gameHour ) ;
float currentFlash = mWeatherSettings [ mCurrentWeather ] . calculateThunder ( mTransitionFactor ,
elapsedSeconds ,
isPaused ) ;
float nextFlash = mWeatherSettings [ mNextWeather ] . calculateThunder ( 1 - mTransitionFactor ,
elapsedSeconds ,
isPaused ) ;
flash = currentFlash + nextFlash ;
}
osg : : Vec4f flashColor ( flash , flash , flash , 0.0f ) ;
mResult . mFogColor + = flashColor ;
mResult . mAmbientColor + = flashColor ;
mResult . mSunColor + = flashColor ;
}
inline void WeatherManager : : calculateResult ( const int weatherID , const float gameHour )
{
const Weather & current = mWeatherSettings [ weatherID ] ;
mResult . mCloudTexture = current . mCloudTexture ;
mResult . mCloudBlendFactor = 0 ;
mResult . mWindSpeed = current . mWindSpeed ;
mResult . mCloudSpeed = current . mCloudSpeed ;
mResult . mGlareView = current . mGlareView ;
mResult . mAmbientLoopSoundID = current . mAmbientLoopSoundID ;
mResult . mAmbientSoundVolume = 1.f ;
mResult . mEffectFade = 1.f ;
mResult . mIsStorm = current . mIsStorm ;
mResult . mRainSpeed = current . mRainSpeed ;
mResult . mRainFrequency = current . mRainFrequency ;
mResult . mParticleEffect = current . mParticleEffect ;
mResult . mRainEffect = current . mRainEffect ;
mResult . mNight = ( gameHour < mSunriseTime | | gameHour > mTimeSettings . mNightStart - 1 ) ;
mResult . mFogDepth = current . mLandFogDepth . getValue ( gameHour , mTimeSettings ) ;
mResult . mFogColor = current . mFogColor . getValue ( gameHour , mTimeSettings ) ;
mResult . mAmbientColor = current . mAmbientColor . getValue ( gameHour , mTimeSettings ) ;
mResult . mSunColor = current . mSunColor . getValue ( gameHour , mTimeSettings ) ;
mResult . mSkyColor = current . mSkyColor . getValue ( gameHour , mTimeSettings ) ;
mResult . mNightFade = mNightFade . getValue ( gameHour , mTimeSettings ) ;
if ( gameHour > = mSunsetTime - mSunPreSunsetTime )
{
float factor = ( gameHour - ( mSunsetTime - mSunPreSunsetTime ) ) / mSunPreSunsetTime ;
factor = std : : min ( 1.f , factor ) ;
mResult . mSunDiscColor = lerp ( osg : : Vec4f ( 1 , 1 , 1 , 1 ) , current . mSunDiscSunsetColor , factor ) ;
// The SunDiscSunsetColor in the INI isn't exactly the resulting color on screen, most likely because
// MW applied the color to the ambient term as well. After the ambient and emissive terms are added together, the fixed pipeline
// would then clamp the total lighting to (1,1,1). A noticeable change in color tone can be observed when only one of the color components gets clamped.
// Unfortunately that means we can't use the INI color as is, have to replicate the above nonsense.
mResult . mSunDiscColor = mResult . mSunDiscColor + osg : : componentMultiply ( mResult . mSunDiscColor , mResult . mAmbientColor ) ;
for ( int i = 0 ; i < 3 ; + + i )
mResult . mSunDiscColor [ i ] = std : : min ( 1.f , mResult . mSunDiscColor [ i ] ) ;
}
else
mResult . mSunDiscColor = osg : : Vec4f ( 1 , 1 , 1 , 1 ) ;
if ( gameHour > = mSunsetTime )
{
float fade = std : : min ( 1.f , ( gameHour - mSunsetTime ) / 2.f ) ;
fade = fade * fade ;
mResult . mSunDiscColor . a ( ) = 1.f - fade ;
}
else if ( gameHour > = mSunriseTime & & gameHour < = mSunriseTime + 1 )
{
mResult . mSunDiscColor . a ( ) = gameHour - mSunriseTime ;
}
else
mResult . mSunDiscColor . a ( ) = 1 ;
}
inline void WeatherManager : : calculateTransitionResult ( const float factor , const float gameHour )
{
calculateResult ( mCurrentWeather , gameHour ) ;
const MWRender : : WeatherResult current = mResult ;
calculateResult ( mNextWeather , gameHour ) ;
const MWRender : : WeatherResult other = mResult ;
mResult . mCloudTexture = current . mCloudTexture ;
mResult . mNextCloudTexture = other . mCloudTexture ;
mResult . mCloudBlendFactor = mWeatherSettings [ mNextWeather ] . cloudBlendFactor ( factor ) ;
mResult . mFogColor = lerp ( current . mFogColor , other . mFogColor , factor ) ;
mResult . mSunColor = lerp ( current . mSunColor , other . mSunColor , factor ) ;
mResult . mSkyColor = lerp ( current . mSkyColor , other . mSkyColor , factor ) ;
mResult . mAmbientColor = lerp ( current . mAmbientColor , other . mAmbientColor , factor ) ;
mResult . mSunDiscColor = lerp ( current . mSunDiscColor , other . mSunDiscColor , factor ) ;
mResult . mFogDepth = lerp ( current . mFogDepth , other . mFogDepth , factor ) ;
mResult . mWindSpeed = lerp ( current . mWindSpeed , other . mWindSpeed , factor ) ;
mResult . mCloudSpeed = lerp ( current . mCloudSpeed , other . mCloudSpeed , factor ) ;
mResult . mGlareView = lerp ( current . mGlareView , other . mGlareView , factor ) ;
mResult . mNightFade = lerp ( current . mNightFade , other . mNightFade , factor ) ;
mResult . mNight = current . mNight ;
if ( factor < 0.5 )
{
mResult . mIsStorm = current . mIsStorm ;
mResult . mParticleEffect = current . mParticleEffect ;
mResult . mRainEffect = current . mRainEffect ;
mResult . mParticleEffect = current . mParticleEffect ;
mResult . mRainSpeed = current . mRainSpeed ;
mResult . mRainFrequency = current . mRainFrequency ;
mResult . mAmbientSoundVolume = 1 - ( factor * 2 ) ;
mResult . mEffectFade = mResult . mAmbientSoundVolume ;
mResult . mAmbientLoopSoundID = current . mAmbientLoopSoundID ;
}
else
{
mResult . mIsStorm = other . mIsStorm ;
mResult . mParticleEffect = other . mParticleEffect ;
mResult . mRainEffect = other . mRainEffect ;
mResult . mParticleEffect = other . mParticleEffect ;
mResult . mRainSpeed = other . mRainSpeed ;
mResult . mRainFrequency = other . mRainFrequency ;
mResult . mAmbientSoundVolume = 2 * ( factor - 0.5f ) ;
mResult . mEffectFade = mResult . mAmbientSoundVolume ;
mResult . mAmbientLoopSoundID = other . mAmbientLoopSoundID ;
}
}