1
0
Fork 1
mirror of https://github.com/TES3MP/openmw-tes3mp.git synced 2025-01-16 02:19:55 +00:00
openmw-tes3mp/apps/openmw/mwworld/weather.cpp
slothlife 3235cecddf Use Glare View for visibility of celestial bodies
Fixed memory leak from Sun and Moon objects by pulling Updaters back out
into separate objects. Removed code related to
mCelestialBodyTransparency.
2015-08-07 00:08:18 -05:00

950 lines
36 KiB
C++

#define _USE_MATH_DEFINES
#include <cmath>
#include "weather.hpp"
#include <components/misc/rng.hpp>
#include <components/esm/esmwriter.hpp>
#include <components/esm/weatherstate.hpp>
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
#include "../mwbase/soundmanager.hpp"
#include "../mwsound/sound.hpp"
#include "../mwrender/renderingmanager.hpp"
#include "../mwrender/sky.hpp"
#include "player.hpp"
#include "esmstore.hpp"
#include "fallback.hpp"
#include "cellstore.hpp"
using namespace MWWorld;
namespace
{
float lerp (float x, float y, float factor)
{
return x * (1-factor) + y * factor;
}
osg::Vec4f lerp (const osg::Vec4f& x, const osg::Vec4f& y, float factor)
{
return x * (1-factor) + y * factor;
}
}
MoonModel::MoonModel(const std::string& name, const MWWorld::Fallback& 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(unsigned int daysPassed, float gameHour) const
{
float rotationFromHorizon = angle(daysPassed, gameHour);
MWRender::MoonState state =
{
rotationFromHorizon,
mAxisOffset, // Reverse engineered from Morrowind's scene graph rotation matrices.
static_cast<MWRender::MoonState::Phase>(phase(daysPassed, gameHour)),
shadowBlend(rotationFromHorizon),
earlyMoonShadowAlpha(rotationFromHorizon) * hourlyAlpha(gameHour)
};
return state;
}
inline float MoonModel::angle(unsigned int daysPassed, float gameHour) 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(daysPassed);
float moonRiseAngleToday = 0;
if(gameHour < moonRiseHourToday)
{
float moonRiseHourYesterday = moonRiseHour(daysPassed - 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(gameHour) + moonRiseAngleYesterday;
}
}
}
else
{
moonRiseAngleToday = rotation(gameHour - 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(unsigned int daysPassed, float gameHour) 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(gameHour < moonRiseHour(daysPassed))
return (daysPassed / 3) % 8;
else
return ((daysPassed + 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;
}
void WeatherManager::setFallbackWeather(Weather& weather,const std::string& name)
{
std::string upper=name;
upper[0]=toupper(name[0]);
weather.mCloudsMaximumPercent = mFallback->getFallbackFloat("Weather_"+upper+"_Clouds_Maximum_Percent");
weather.mTransitionDelta = mFallback->getFallbackFloat("Weather_"+upper+"_Transition_Delta");
weather.mSkySunriseColor=mFallback->getFallbackColour("Weather_"+upper+"_Sky_Sunrise_Color");
weather.mSkyDayColor = mFallback->getFallbackColour("Weather_"+upper+"_Sky_Day_Color");
weather.mSkySunsetColor = mFallback->getFallbackColour("Weather_"+upper+"_Sky_Sunset_Color");
weather.mSkyNightColor = mFallback->getFallbackColour("Weather_"+upper+"_Sky_Night_Color");
weather.mFogSunriseColor = mFallback->getFallbackColour("Weather_"+upper+"_Fog_Sunrise_Color");
weather.mFogDayColor = mFallback->getFallbackColour("Weather_"+upper+"_Fog_Day_Color");
weather.mFogSunsetColor = mFallback->getFallbackColour("Weather_"+upper+"_Fog_Sunset_Color");
weather.mFogNightColor = mFallback->getFallbackColour("Weather_"+upper+"_Fog_Night_Color");
weather.mAmbientSunriseColor = mFallback->getFallbackColour("Weather_"+upper+"_Ambient_Sunrise_Color");
weather.mAmbientDayColor = mFallback->getFallbackColour("Weather_"+upper+"_Ambient_Day_Color");
weather.mAmbientSunsetColor = mFallback->getFallbackColour("Weather_"+upper+"_Ambient_Sunset_Color");
weather.mAmbientNightColor = mFallback->getFallbackColour("Weather_"+upper+"_Ambient_Night_Color");
weather.mSunSunriseColor = mFallback->getFallbackColour("Weather_"+upper+"_Sun_Sunrise_Color");
weather.mSunDayColor = mFallback->getFallbackColour("Weather_"+upper+"_Sun_Day_Color");
weather.mSunSunsetColor = mFallback->getFallbackColour("Weather_"+upper+"_Sun_Sunset_Color");
weather.mSunNightColor = mFallback->getFallbackColour("Weather_"+upper+"_Sun_Night_Color");
weather.mSunDiscSunsetColor = mFallback->getFallbackColour("Weather_"+upper+"_Sun_Disc_Sunset_Color");
weather.mLandFogDayDepth = mFallback->getFallbackFloat("Weather_"+upper+"_Land_Fog_Day_Depth");
weather.mLandFogNightDepth = mFallback->getFallbackFloat("Weather_"+upper+"_Land_Fog_Night_Depth");
weather.mWindSpeed = mFallback->getFallbackFloat("Weather_"+upper+"_Wind_Speed");
weather.mCloudSpeed = mFallback->getFallbackFloat("Weather_"+upper+"_Cloud_Speed");
weather.mGlareView = mFallback->getFallbackFloat("Weather_"+upper+"_Glare_View");
weather.mCloudTexture = mFallback->getFallbackString("Weather_"+upper+"_Cloud_Texture");
static const float fStromWindSpeed = mStore->get<ESM::GameSetting>().find("fStromWindSpeed")->getFloat();
weather.mIsStorm = weather.mWindSpeed > fStromWindSpeed;
bool usesPrecip = mFallback->getFallbackBool("Weather_"+upper+"_Using_Precip");
if (usesPrecip)
weather.mRainEffect = "meshes\\raindrop.nif";
weather.mRainSpeed = mRainSpeed;
weather.mRainFrequency = mFallback->getFallbackFloat("Weather_"+upper+"_Rain_Entrance_Speed");
/*
Unhandled:
Rain Diameter=600 ?
Rain Height Min=200 ?
Rain Height Max=700 ?
Rain Threshold=0.6 ?
Max Raindrops=650 ?
*/
mWeatherSettings[name] = weather;
}
WeatherManager::WeatherManager(MWRender::RenderingManager* rendering, MWWorld::Fallback* fallback, MWWorld::ESMStore* store) :
mHour(14), mWindSpeed(0.f), mIsStorm(false), mStormDirection(0,1,0), mFallback(fallback), mStore(store),
mRendering(rendering), mCurrentWeather("clear"), mNextWeather(""), mFirstUpdate(true),
mRemainingTransitionTime(0), mThunderFlash(0), mThunderChance(0), mThunderChanceNeeded(50),
mTimePassed(0), mWeatherUpdateTime(0), mThunderSoundDelay(0),
mMasser("Masser", *fallback), mSecunda("Secunda", *fallback)
{
//Globals
mThunderSoundID0 = mFallback->getFallbackString("Weather_Thunderstorm_Thunder_Sound_ID_0");
mThunderSoundID1 = mFallback->getFallbackString("Weather_Thunderstorm_Thunder_Sound_ID_1");
mThunderSoundID2 = mFallback->getFallbackString("Weather_Thunderstorm_Thunder_Sound_ID_2");
mThunderSoundID3 = mFallback->getFallbackString("Weather_Thunderstorm_Thunder_Sound_ID_3");
mSunriseTime = mFallback->getFallbackFloat("Weather_Sunrise_Time");
mSunsetTime = mFallback->getFallbackFloat("Weather_Sunset_Time");
mSunriseDuration = mFallback->getFallbackFloat("Weather_Sunrise_Duration");
mSunsetDuration = mFallback->getFallbackFloat("Weather_Sunset_Duration");
mHoursBetweenWeatherChanges = mFallback->getFallbackFloat("Weather_Hours_Between_Weather_Changes");
mWeatherUpdateTime = mHoursBetweenWeatherChanges * 3600;
mThunderFrequency = mFallback->getFallbackFloat("Weather_Thunderstorm_Thunder_Frequency");
mThunderThreshold = mFallback->getFallbackFloat("Weather_Thunderstorm_Thunder_Threshold");
mThunderSoundDelay = 0.25;
mRainSpeed = mFallback->getFallbackFloat("Weather_Precip_Gravity");
//Some useful values
/* TODO: Use pre-sunrise_time, pre-sunset_time,
* post-sunrise_time, and post-sunset_time to better
* describe sunrise/sunset time.
* These values are fallbacks attached to weather.
*/
mNightStart = mSunsetTime + mSunsetDuration;
mNightEnd = mSunriseTime - 0.5f;
mDayStart = mSunriseTime + mSunriseDuration;
mDayEnd = mSunsetTime;
//Weather
Weather clear;
setFallbackWeather(clear,"clear");
Weather cloudy;
setFallbackWeather(cloudy,"cloudy");
Weather foggy;
setFallbackWeather(foggy,"foggy");
Weather thunderstorm;
thunderstorm.mAmbientLoopSoundID = "rain heavy";
thunderstorm.mRainEffect = "meshes\\raindrop.nif";
setFallbackWeather(thunderstorm,"thunderstorm");
Weather rain;
rain.mAmbientLoopSoundID = "rain";
rain.mRainEffect = "meshes\\raindrop.nif";
setFallbackWeather(rain,"rain");
Weather overcast;
setFallbackWeather(overcast,"overcast");
Weather ashstorm;
ashstorm.mAmbientLoopSoundID = "ashstorm";
ashstorm.mParticleEffect = "meshes\\ashcloud.nif";
setFallbackWeather(ashstorm,"ashstorm");
Weather blight;
blight.mAmbientLoopSoundID = "blight";
blight.mParticleEffect = "meshes\\blightcloud.nif";
setFallbackWeather(blight,"blight");
Weather snow;
snow.mParticleEffect = "meshes\\snow.nif";
setFallbackWeather(snow, "snow");
Weather blizzard;
blizzard.mAmbientLoopSoundID = "BM Blizzard";
blizzard.mParticleEffect = "meshes\\blizzard.nif";
setFallbackWeather(blizzard,"blizzard");
}
WeatherManager::~WeatherManager()
{
stopSounds();
}
void WeatherManager::setWeather(const std::string& weather, bool instant)
{
if (weather == mCurrentWeather && mNextWeather == "")
{
mFirstUpdate = false;
return;
}
if (instant || mFirstUpdate)
{
mNextWeather = "";
mCurrentWeather = weather;
}
else
{
if (mNextWeather != "")
{
// transition more than 50% finished?
if (mRemainingTransitionTime/(mWeatherSettings[mCurrentWeather].mTransitionDelta * 24.f * 3600) <= 0.5)
mCurrentWeather = mNextWeather;
}
mNextWeather = weather;
mRemainingTransitionTime = mWeatherSettings[mCurrentWeather].mTransitionDelta * 24.f * 3600.f;
}
mFirstUpdate = false;
}
void WeatherManager::setResult(const std::string& weatherType)
{
const Weather& current = mWeatherSettings[weatherType];
mResult.mCloudTexture = current.mCloudTexture;
mResult.mCloudBlendFactor = 0;
mResult.mCloudOpacity = current.mCloudsMaximumPercent;
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.mSunColor = current.mSunDiscSunsetColor;
mResult.mIsStorm = current.mIsStorm;
mResult.mRainSpeed = current.mRainSpeed;
mResult.mRainFrequency = current.mRainFrequency;
mResult.mParticleEffect = current.mParticleEffect;
mResult.mRainEffect = current.mRainEffect;
mResult.mNight = (mHour < mSunriseTime || mHour > mNightStart - 1);
mResult.mFogDepth = mResult.mNight ? current.mLandFogNightDepth : current.mLandFogDayDepth;
// night
if (mHour <= mNightEnd || mHour >= mNightStart + 1)
{
mResult.mFogColor = current.mFogNightColor;
mResult.mAmbientColor = current.mAmbientNightColor;
mResult.mSunColor = current.mSunNightColor;
mResult.mSkyColor = current.mSkyNightColor;
mResult.mNightFade = 1.f;
}
// sunrise
else if (mHour >= mNightEnd && mHour <= mDayStart + 1)
{
if (mHour <= mSunriseTime)
{
// fade in
float advance = mSunriseTime - mHour;
float factor = advance / 0.5f;
mResult.mFogColor = lerp(current.mFogSunriseColor, current.mFogNightColor, factor);
mResult.mAmbientColor = lerp(current.mAmbientSunriseColor, current.mAmbientNightColor, factor);
mResult.mSunColor = lerp(current.mSunSunriseColor, current.mSunNightColor, factor);
mResult.mSkyColor = lerp(current.mSkySunriseColor, current.mSkyNightColor, factor);
mResult.mNightFade = factor;
}
else //if (mHour >= 6)
{
// fade out
float advance = mHour - mSunriseTime;
float factor = advance / 3.f;
mResult.mFogColor = lerp(current.mFogSunriseColor, current.mFogDayColor, factor);
mResult.mAmbientColor = lerp(current.mAmbientSunriseColor, current.mAmbientDayColor, factor);
mResult.mSunColor = lerp(current.mSunSunriseColor, current.mSunDayColor, factor);
mResult.mSkyColor = lerp(current.mSkySunriseColor, current.mSkyDayColor, factor);
}
}
// day
else if (mHour >= mDayStart + 1 && mHour <= mDayEnd - 1)
{
mResult.mFogColor = current.mFogDayColor;
mResult.mAmbientColor = current.mAmbientDayColor;
mResult.mSunColor = current.mSunDayColor;
mResult.mSkyColor = current.mSkyDayColor;
}
// sunset
else if (mHour >= mDayEnd - 1 && mHour <= mNightStart + 1)
{
if (mHour <= mDayEnd + 1)
{
// fade in
float advance = (mDayEnd + 1) - mHour;
float factor = (advance / 2);
mResult.mFogColor = lerp(current.mFogSunsetColor, current.mFogDayColor, factor);
mResult.mAmbientColor = lerp(current.mAmbientSunsetColor, current.mAmbientDayColor, factor);
mResult.mSunColor = lerp(current.mSunSunsetColor, current.mSunDayColor, factor);
mResult.mSkyColor = lerp(current.mSkySunsetColor, current.mSkyDayColor, factor);
}
else //if (mHour >= 19)
{
// fade out
float advance = mHour - (mDayEnd + 1);
float factor = advance / 2.f;
mResult.mFogColor = lerp(current.mFogSunsetColor, current.mFogNightColor, factor);
mResult.mAmbientColor = lerp(current.mAmbientSunsetColor, current.mAmbientNightColor, factor);
mResult.mSunColor = lerp(current.mSunSunsetColor, current.mSunNightColor, factor);
mResult.mSkyColor = lerp(current.mSkySunsetColor, current.mSkyNightColor, factor);
mResult.mNightFade = factor;
}
}
}
void WeatherManager::transition(float factor)
{
setResult(mCurrentWeather);
const MWRender::WeatherResult current = mResult;
setResult(mNextWeather);
const MWRender::WeatherResult other = mResult;
mResult.mCloudTexture = current.mCloudTexture;
mResult.mNextCloudTexture = other.mCloudTexture;
mResult.mCloudBlendFactor = factor;
mResult.mCloudOpacity = lerp(current.mCloudOpacity, other.mCloudOpacity, 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.mCloudOpacity = lerp(current.mCloudOpacity, other.mCloudOpacity, 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;
}
}
void WeatherManager::update(float duration, bool paused)
{
float timePassed = static_cast<float>(mTimePassed);
mTimePassed = 0;
mWeatherUpdateTime -= timePassed;
MWBase::World* world = MWBase::Environment::get().getWorld();
const bool exterior = (world->isCellExterior() || world->isCellQuasiExterior());
if (!exterior)
{
mRendering->setSkyEnabled(false);
//mRendering->getSkyManager()->setLightningStrength(0.f);
stopSounds();
return;
}
switchToNextWeather(false);
if (mNextWeather != "")
{
mRemainingTransitionTime -= timePassed;
if (mRemainingTransitionTime < 0)
{
mCurrentWeather = mNextWeather;
mNextWeather = "";
}
}
if (mNextWeather != "")
transition(1 - (mRemainingTransitionTime / (mWeatherSettings[mCurrentWeather].mTransitionDelta * 24.f * 3600)));
else
setResult(mCurrentWeather);
mWindSpeed = mResult.mWindSpeed;
mIsStorm = mResult.mIsStorm;
if (mIsStorm)
{
MWWorld::Ptr player = world->getPlayerPtr();
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);
}
mRendering->configureFog(mResult.mFogDepth, mResult.mFogColor);
// disable sun during night
if (mHour >= mNightStart || mHour <= 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 = mHour;
float adjustedNightStart = mNightStart;
if ( mHour < mSunriseTime )
adjustedHour += 24.f;
if ( 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 = M_PI * (adjustedHour - mSunriseTime) / dayDuration;
} else {
theta = M_PI * (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 );
}
TimeStamp time = MWBase::Environment::get().getWorld()->getTimeStamp();
mRendering->getSkyManager()->setMasserState(mMasser.calculateState(time.getDay(), time.getHour()));
mRendering->getSkyManager()->setSecundaState(mSecunda.calculateState(time.getDay(), time.getHour()));
if (!paused)
{
if (mCurrentWeather == "thunderstorm" && mNextWeather == "")
{
if (mThunderFlash > 0)
{
// play the sound after a delay
mThunderSoundDelay -= duration;
if (mThunderSoundDelay <= 0)
{
// pick a random sound
int sound = Misc::Rng::rollDice(4);
std::string* soundName = NULL;
if (sound == 0) soundName = &mThunderSoundID0;
else if (sound == 1) soundName = &mThunderSoundID1;
else if (sound == 2) soundName = &mThunderSoundID2;
else if (sound == 3) soundName = &mThunderSoundID3;
if (soundName)
MWBase::Environment::get().getSoundManager()->playSound(*soundName, 1.0, 1.0);
mThunderSoundDelay = 1000;
}
mThunderFlash -= duration;
//if (mThunderFlash > 0)
//mRendering->getSkyManager()->setLightningStrength( mThunderFlash / mThunderThreshold );
//else
{
mThunderChanceNeeded = static_cast<float>(Misc::Rng::rollDice(100));
mThunderChance = 0;
//mRendering->getSkyManager()->setLightningStrength( 0.f );
}
}
else
{
// no thunder active
mThunderChance += duration*4; // chance increases by 4 percent every second
if (mThunderChance >= mThunderChanceNeeded)
{
mThunderFlash = mThunderThreshold;
//mRendering->getSkyManager()->setLightningStrength( mThunderFlash / mThunderThreshold );
mThunderSoundDelay = 0.25;
}
}
}
//else
//mRendering->getSkyManager()->setLightningStrength(0.f);
}
mRendering->setAmbientColour(mResult.mAmbientColor);
mRendering->setSunColour(mResult.mSunColor);
mRendering->getSkyManager()->setWeather(mResult);
// Play sounds
if (mPlayingSoundID != mResult.mAmbientLoopSoundID)
{
stopSounds();
if (!mResult.mAmbientLoopSoundID.empty())
mAmbientSound = MWBase::Environment::get().getSoundManager()->playSound(mResult.mAmbientLoopSoundID, 1.0, 1.0, MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_Loop);
mPlayingSoundID = mResult.mAmbientLoopSoundID;
}
if (mAmbientSound.get())
mAmbientSound->setVolume(mResult.mAmbientSoundVolume);
}
void WeatherManager::stopSounds()
{
if (mAmbientSound.get())
{
MWBase::Environment::get().getSoundManager()->stopSound(mAmbientSound);
mAmbientSound.reset();
mPlayingSoundID.clear();
}
}
std::string WeatherManager::nextWeather(const ESM::Region* region) const
{
std::vector<char> probability;
RegionModMap::const_iterator iter = mRegionMods.find(Misc::StringUtils::lowerCase(region->mId));
if(iter != mRegionMods.end())
probability = iter->second;
else
{
probability.reserve(10);
probability.push_back(region->mData.mClear);
probability.push_back(region->mData.mCloudy);
probability.push_back(region->mData.mFoggy);
probability.push_back(region->mData.mOvercast);
probability.push_back(region->mData.mRain);
probability.push_back(region->mData.mThunder);
probability.push_back(region->mData.mAsh);
probability.push_back(region->mData.mBlight);
probability.push_back(region->mData.mA);
probability.push_back(region->mData.mB);
}
/*
* 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;
unsigned int i = 0;
for (; i < probability.size(); ++i)
{
sum += probability[i];
if (chance < sum)
break;
}
switch (i)
{
case 1:
return "cloudy";
case 2:
return "foggy";
case 3:
return "overcast";
case 4:
return "rain";
case 5:
return "thunderstorm";
case 6:
return "ashstorm";
case 7:
return "blight";
case 8:
return "snow";
case 9:
return "blizzard";
default: // case 0
return "clear";
}
}
void WeatherManager::setHour(const float hour)
{
mHour = hour;
}
unsigned int WeatherManager::getWeatherID() const
{
// Source: http://www.uesp.net/wiki/Tes3Mod:GetCurrentWeather
if (mCurrentWeather == "clear")
return 0;
else if (mCurrentWeather == "cloudy")
return 1;
else if (mCurrentWeather == "foggy")
return 2;
else if (mCurrentWeather == "overcast")
return 3;
else if (mCurrentWeather == "rain")
return 4;
else if (mCurrentWeather == "thunderstorm")
return 5;
else if (mCurrentWeather == "ashstorm")
return 6;
else if (mCurrentWeather == "blight")
return 7;
else if (mCurrentWeather == "snow")
return 8;
else if (mCurrentWeather == "blizzard")
return 9;
else
return 0;
}
void WeatherManager::changeWeather(const std::string& region, const unsigned int id)
{
// make sure this region exists
MWBase::Environment::get().getWorld()->getStore().get<ESM::Region>().find(region);
std::string weather;
if (id==0)
weather = "clear";
else if (id==1)
weather = "cloudy";
else if (id==2)
weather = "foggy";
else if (id==3)
weather = "overcast";
else if (id==4)
weather = "rain";
else if (id==5)
weather = "thunderstorm";
else if (id==6)
weather = "ashstorm";
else if (id==7)
weather = "blight";
else if (id==8)
weather = "snow";
else if (id==9)
weather = "blizzard";
else
weather = "clear";
mRegionOverrides[Misc::StringUtils::lowerCase(region)] = weather;
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
if (player.isInCell())
{
std::string playerRegion = player.getCell()->getCell()->mRegion;
if (Misc::StringUtils::ciEqual(region, playerRegion))
setWeather(weather);
}
}
void WeatherManager::modRegion(const std::string &regionid, const std::vector<char> &chances)
{
mRegionMods[Misc::StringUtils::lowerCase(regionid)] = chances;
// Start transitioning right away if the region no longer supports the current weather type
unsigned int current = getWeatherID();
if(current >= chances.size() || chances[current] == 0)
mWeatherUpdateTime = 0.0f;
}
float WeatherManager::getWindSpeed() const
{
return mWindSpeed;
}
bool WeatherManager::isDark() const
{
bool exterior = (MWBase::Environment::get().getWorld()->isCellExterior()
|| MWBase::Environment::get().getWorld()->isCellQuasiExterior());
return exterior && (mHour < mSunriseTime || mHour > mNightStart - 1);
}
void WeatherManager::write(ESM::ESMWriter& writer, Loading::Listener& progress)
{
ESM::WeatherState state;
state.mHour = mHour;
state.mWindSpeed = mWindSpeed;
state.mCurrentWeather = mCurrentWeather;
state.mNextWeather = mNextWeather;
state.mCurrentRegion = mCurrentRegion;
state.mFirstUpdate = mFirstUpdate;
state.mRemainingTransitionTime = mRemainingTransitionTime;
state.mTimePassed = mTimePassed;
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)
{
// load first so that if it fails, we haven't accidentally reset the state below
ESM::WeatherState state;
state.load(reader);
// swap in the loaded values now that we can't fail
mHour = state.mHour;
mWindSpeed = state.mWindSpeed;
mCurrentWeather.swap(state.mCurrentWeather);
mNextWeather.swap(state.mNextWeather);
mCurrentRegion.swap(state.mCurrentRegion);
mFirstUpdate = state.mFirstUpdate;
mRemainingTransitionTime = state.mRemainingTransitionTime;
mTimePassed = state.mTimePassed;
return true;
}
return false;
}
void WeatherManager::clear()
{
stopSounds();
mRegionOverrides.clear();
mRegionMods.clear();
mThunderFlash = 0.0;
mThunderChance = 0.0;
mThunderChanceNeeded = 50.0;
}
void WeatherManager::switchToNextWeather(bool instantly)
{
MWBase::World* world = MWBase::Environment::get().getWorld();
if (world->isCellExterior() || world->isCellQuasiExterior())
{
std::string regionstr = Misc::StringUtils::lowerCase(world->getPlayerPtr().getCell()->getCell()->mRegion);
if (mWeatherUpdateTime <= 0 || regionstr != mCurrentRegion)
{
mCurrentRegion = regionstr;
mWeatherUpdateTime = mHoursBetweenWeatherChanges * 3600;
std::string weatherType = "clear";
if (mRegionOverrides.find(regionstr) != mRegionOverrides.end())
{
weatherType = mRegionOverrides[regionstr];
}
else
{
// get weather probabilities for the current region
const ESM::Region *region = world->getStore().get<ESM::Region>().search (regionstr);
if (region != 0)
{
weatherType = nextWeather(region);
}
}
setWeather(weatherType, instantly);
}
}
}
bool WeatherManager::isInStorm() const
{
return mIsStorm;
}
osg::Vec3f WeatherManager::getStormDirection() const
{
return mStormDirection;
}
void WeatherManager::advanceTime(double hours)
{
mTimePassed += hours*3600;
}