diff --git a/CHANGELOG.md b/CHANGELOG.md index 2097c4bbaa..df8a81ceac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -176,6 +176,8 @@ Feature #7652: Sort inactive post processing shaders list properly Feature #7698: Implement sAbsorb, sDamage, sDrain, sFortify and sRestore Feature #7709: Improve resolution selection in Launcher + Feature #7792: Support Timescale Clouds + Feature #7795: Support MaxNumberRipples INI setting Task #5896: Do not use deprecated MyGUI properties Task #6624: Drop support for saves made prior to 0.45 Task #7113: Move from std::atoi to std::from_char diff --git a/apps/openmw/mwrender/ripplesimulation.cpp b/apps/openmw/mwrender/ripplesimulation.cpp index abfb7ba9cd..b7661ca664 100644 --- a/apps/openmw/mwrender/ripplesimulation.cpp +++ b/apps/openmw/mwrender/ripplesimulation.cpp @@ -80,6 +80,25 @@ namespace node->setStateSet(stateset); } + + int findOldestParticleAlive(const osgParticle::ParticleSystem* partsys) + { + int oldest = -1; + float oldestAge = 0.f; + for (int i = 0; i < partsys->numParticles(); ++i) + { + const osgParticle::Particle* particle = partsys->getParticle(i); + if (!particle->isAlive()) + continue; + const float age = particle->getAge(); + if (oldest == -1 || age > oldestAge) + { + oldest = i; + oldestAge = age; + } + } + return oldest; + } } namespace MWRender @@ -87,6 +106,7 @@ namespace MWRender RippleSimulation::RippleSimulation(osg::Group* parent, Resource::ResourceSystem* resourceSystem) : mParent(parent) + , mMaxNumberRipples(Fallback::Map::getInt("Water_MaxNumberRipples")) { mParticleSystem = new osgParticle::ParticleSystem; @@ -159,9 +179,6 @@ namespace MWRender currentPos.z() = mParticleNode->getPosition().z(); - if (mParticleSystem->numParticles() - mParticleSystem->numDeadParticles() > 500) - continue; // TODO: remove the oldest particle to make room? - emitRipple(currentPos); } } @@ -226,7 +243,19 @@ namespace MWRender } else { + if (mMaxNumberRipples == 0) + return; + osgParticle::ParticleSystem::ScopedWriteLock lock(*mParticleSystem->getReadWriteMutex()); + if (mParticleSystem->numParticles() - mParticleSystem->numDeadParticles() > mMaxNumberRipples) + { + // osgParticle::ParticleSystem design requires this to be O(N) + // However, the number of particles we'll have to go through is not large + // If the user makes the limit absurd and manages to actually hit it this could be a problem + const int oldest = findOldestParticleAlive(mParticleSystem); + if (oldest != -1) + mParticleSystem->reuseParticle(oldest); + } osgParticle::Particle* p = mParticleSystem->createParticle(nullptr); p->setPosition(osg::Vec3f(pos.x(), pos.y(), 0.f)); p->setAngle(osg::Vec3f(0, 0, Misc::Rng::rollProbability() * osg::PI * 2 - osg::PI)); diff --git a/apps/openmw/mwrender/ripplesimulation.hpp b/apps/openmw/mwrender/ripplesimulation.hpp index 1f09797bf4..10b47f5679 100644 --- a/apps/openmw/mwrender/ripplesimulation.hpp +++ b/apps/openmw/mwrender/ripplesimulation.hpp @@ -74,6 +74,8 @@ namespace MWRender std::vector mEmitters; Ripples* mRipples = nullptr; + + int mMaxNumberRipples; }; } diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 57c3e902d8..af41d2c590 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -240,6 +240,7 @@ namespace MWRender , mIsStorm(false) , mDay(0) , mMonth(0) + , mTimescaleClouds(Fallback::Map::getBool("Weather_Timescale_Clouds")) , mCloudAnimationTimer(0.f) , mRainTimer(0.f) , mStormParticleDirection(MWWorld::Weather::defaultDirection()) @@ -558,8 +559,17 @@ namespace MWRender mParticleNode->setAttitude(quat); } + const float timeScale = MWBase::Environment::get().getWorld()->getTimeManager()->getGameTimeScale(); + // UV Scroll the clouds - mCloudAnimationTimer += duration * mCloudSpeed * 0.003; + float cloudDelta = duration * mCloudSpeed / 400.f; + if (mTimescaleClouds) + cloudDelta *= timeScale / 60.f; + + mCloudAnimationTimer += cloudDelta; + if (mCloudAnimationTimer >= 4.f) + mCloudAnimationTimer -= 4.f; + mNextCloudUpdater->setTextureCoord(mCloudAnimationTimer); mCloudUpdater->setTextureCoord(mCloudAnimationTimer); @@ -575,8 +585,7 @@ namespace MWRender } // rotate the stars by 360 degrees every 4 days - mAtmosphereNightRoll += MWBase::Environment::get().getWorld()->getTimeManager()->getGameTimeScale() * duration - * osg::DegreesToRadians(360.f) / (3600 * 96.f); + mAtmosphereNightRoll += timeScale * duration * osg::DegreesToRadians(360.f) / (3600 * 96.f); if (mAtmosphereNightNode->getNodeMask() != 0) mAtmosphereNightNode->setAttitude(osg::Quat(mAtmosphereNightRoll, osg::Vec3f(0, 0, 1))); mPrecipitationOccluder->update(); diff --git a/apps/openmw/mwrender/sky.hpp b/apps/openmw/mwrender/sky.hpp index 96af2e6ec9..e0cd26f98a 100644 --- a/apps/openmw/mwrender/sky.hpp +++ b/apps/openmw/mwrender/sky.hpp @@ -165,6 +165,7 @@ namespace MWRender int mDay; int mMonth; + bool mTimescaleClouds; float mCloudAnimationTimer; float mRainTimer; diff --git a/components/fallback/validate.cpp b/components/fallback/validate.cpp index 8dfcd97570..571f0f9b19 100644 --- a/components/fallback/validate.cpp +++ b/components/fallback/validate.cpp @@ -7,12 +7,12 @@ static const std::set allowedKeysInt = { "LightAttenuation_LinearMethod", "LightAttenuation_OutQuadInLin", "LightAttenuation_QuadraticMethod", "LightAttenuation_UseConstant", - "LightAttenuation_UseLinear", "LightAttenuation_UseQuadratic", "Water_NearWaterRadius", "Water_NearWaterPoints", - "Water_RippleFrameCount", "Water_SurfaceTileCount", "Water_SurfaceFrameCount", "Weather_Clear_Using_Precip", - "Weather_Cloudy_Using_Precip", "Weather_Foggy_Using_Precip", "Weather_Overcast_Using_Precip", - "Weather_Rain_Using_Precip", "Weather_Thunderstorm_Using_Precip", "Weather_Ashstorm_Using_Precip", - "Weather_Blight_Using_Precip", "Weather_Snow_Using_Precip", "Weather_Blizzard_Using_Precip", "Weather_Rain_Ripples", - "Weather_Snow_Ripples" }; + "LightAttenuation_UseLinear", "LightAttenuation_UseQuadratic", "Water_MaxNumberRipples", "Water_NearWaterRadius", + "Water_NearWaterPoints", "Water_RippleFrameCount", "Water_SurfaceTileCount", "Water_SurfaceFrameCount", + "Weather_Clear_Using_Precip", "Weather_Cloudy_Using_Precip", "Weather_Foggy_Using_Precip", + "Weather_Overcast_Using_Precip", "Weather_Rain_Using_Precip", "Weather_Thunderstorm_Using_Precip", + "Weather_Ashstorm_Using_Precip", "Weather_Blight_Using_Precip", "Weather_Snow_Using_Precip", + "Weather_Blizzard_Using_Precip", "Weather_Rain_Ripples", "Weather_Snow_Ripples", "Weather_Timescale_Clouds" }; static const std::set allowedKeysFloat = { "General_Werewolf_FOV", "Inventory_DirectionalAmbientB", "Inventory_DirectionalAmbientG", "Inventory_DirectionalAmbientR", "Inventory_DirectionalDiffuseB", @@ -186,7 +186,7 @@ static const std::set allowedKeysNonNumeric = { "Blood_Model_0 "Weather_Thunderstorm_Sun_Disc_Sunset_Color", "Weather_Thunderstorm_Sun_Night_Color", "Weather_Thunderstorm_Sun_Sunrise_Color", "Weather_Thunderstorm_Sun_Sunset_Color", "Weather_Thunderstorm_Thunder_Sound_ID_0", "Weather_Thunderstorm_Thunder_Sound_ID_1", - "Weather_Thunderstorm_Thunder_Sound_ID_2", "Weather_Thunderstorm_Thunder_Sound_ID_3", "Weather_Timescale_Clouds", + "Weather_Thunderstorm_Thunder_Sound_ID_2", "Weather_Thunderstorm_Thunder_Sound_ID_3", "Weather_Clear_Thunder_Sound_ID_0", "Weather_Clear_Thunder_Sound_ID_1", "Weather_Clear_Thunder_Sound_ID_2", "Weather_Clear_Thunder_Sound_ID_3", "Weather_Cloudy_Thunder_Sound_ID_0", "Weather_Cloudy_Thunder_Sound_ID_1", "Weather_Cloudy_Thunder_Sound_ID_2", "Weather_Cloudy_Thunder_Sound_ID_3", "Weather_Foggy_Thunder_Sound_ID_0", @@ -218,7 +218,7 @@ static const std::set allowedKeysUnused = { "Inventory_Uniform "Map_Travel_Boat_Blue", "Map_Travel_Boat_Green", "Map_Travel_Boat_Red", "Map_Travel_Magic_Blue", "Map_Travel_Magic_Green", "Map_Travel_Magic_Red", "Map_Travel_Siltstrider_Blue", "Map_Travel_Siltstrider_Green", "Map_Travel_Siltstrider_Red", "Movies_Game_Logo", "PixelWater_Resolution", "PixelWater_SurfaceFPS", - "PixelWater_TileCount", "Movies_Loading", "Movies_Options_Menu", "Movies_Project_Logo", "Water_MaxNumberRipples", + "PixelWater_TileCount", "Movies_Loading", "Movies_Options_Menu", "Movies_Project_Logo", "Water_NearWaterUnderwaterFreq", "Water_NearWaterUnderwaterVolume", "Water_PSWaterReflectTerrain", "Water_PSWaterReflectUpdate", "Water_RippleAlphas", "Water_RippleScale", "Water_SurfaceTextureSize", "Water_TileTextureDivisor", "Weather_AlphaReduce", "Weather_Ashstorm_Storm_Threshold",