From 1e40d27318de5aed3f1b148c441ed9ba44e6f6f1 Mon Sep 17 00:00:00 2001 From: "glassmancody.info" Date: Sat, 23 Oct 2021 17:53:38 -0700 Subject: [PATCH 1/4] introduce sky shaders --- CHANGELOG.md | 4 + apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwrender/sky.cpp | 2430 ++++++++------------------- apps/openmw/mwrender/sky.hpp | 95 +- apps/openmw/mwrender/skyutil.cpp | 1142 +++++++++++++ apps/openmw/mwrender/skyutil.hpp | 343 ++++ apps/openmw/mwworld/weather.cpp | 2243 ++++++++++++------------ apps/openmw/mwworld/weather.hpp | 4 + components/shader/shadervisitor.cpp | 4 + files/shaders/CMakeLists.txt | 3 + files/shaders/lighting.glsl | 10 + files/shaders/objects_fragment.glsl | 8 +- files/shaders/objects_vertex.glsl | 8 +- files/shaders/sky_fragment.glsl | 84 + files/shaders/sky_vertex.glsl | 20 + files/shaders/skypasses.glsl | 7 + 16 files changed, 3453 insertions(+), 2954 deletions(-) create mode 100644 apps/openmw/mwrender/skyutil.cpp create mode 100644 apps/openmw/mwrender/skyutil.hpp create mode 100644 files/shaders/sky_fragment.glsl create mode 100644 files/shaders/sky_vertex.glsl create mode 100644 files/shaders/skypasses.glsl diff --git a/CHANGELOG.md b/CHANGELOG.md index b37fed31fd..188238f42f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ Bug #4602: Robert's Bodies: crash inside createInstance() Bug #4700: Editor: Incorrect command implementation Bug #4744: Invisible particles must still be processed + Bug #4752: UpdateCellCommand doesn't undo properly + Bug #5088: Sky abruptly changes direction during certain weather transitions Bug #5100: Persuasion doesn't always clamp the resulting disposition Bug #5120: Scripted object spawning updates physics system Bug #5207: Loose summons can be present in scene @@ -40,6 +42,7 @@ Bug #6133: Cannot reliably sneak or steal in the sight of the NPCs siding with player Bug #6143: Capturing a screenshot makes engine to be a temporary unresponsive Bug #6165: Paralyzed player character can pickup items when the inventory is open + Bug #6168: Weather particles flicker for a frame at start of storms Bug #6172: Some creatures can't open doors Bug #6174: Spellmaking and Enchanting sliders differences from vanilla Bug #6184: Command and Calm and Demoralize and Frenzy and Rally magic effects inconsistencies with vanilla @@ -73,6 +76,7 @@ Feature #6017: Separate persistent and temporary cell references when saving Feature #6032: Reverse-z depth buffer Feature #6078: First person should not clear depth buffer + Feature #6161: Refactor Sky to use shaders and GLES/GL3 friendly Feature #6162: Refactor GUI to use shaders and to be GLES and GL3+ friendly Feature #6199: Support FBO Rendering Feature #6249: Alpha testing support for Collada diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 7334d893db..425d859f31 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -19,7 +19,7 @@ set(GAME_HEADER source_group(game FILES ${GAME} ${GAME_HEADER}) add_openmw_dir (mwrender - actors objects renderingmanager animation rotatecontroller sky npcanimation vismask + actors objects renderingmanager animation rotatecontroller sky skyutil npcanimation vismask creatureanimation effectmanager util renderinginterface pathgrid rendermode weaponanimation screenshotmanager bulletdebugdraw globalmap characterpreview camera viewovershoulder localmap water terrainstorage ripplesimulation renderbin actoranimation landmanager navmesh actorspaths recastmesh fogmanager objectpaging groundcover postprocessor diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index ebf901b5ab..217f10f294 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -1,1306 +1,185 @@ #include "sky.hpp" -#include - -#include -#include -#include #include -#include -#include -#include -#include -#include -#include #include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include #include +#include +#include #include +#include -#include +#include -#include +#include +#include +#include +#include #include #include #include -#include -#include -#include -#include -#include -#include -#include - -#include +#include +#include #include +#include "../mwworld/weather.hpp" + #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "vismask.hpp" #include "renderbin.hpp" #include "util.hpp" +#include "skyutil.hpp" namespace { - osg::ref_ptr createAlphaTrackingUnlitMaterial() - { - osg::ref_ptr mat = new osg::Material; - mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 1.f)); - mat->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 1.f)); - mat->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(1.f, 1.f, 1.f, 1.f)); - mat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 0.f)); - mat->setColorMode(osg::Material::DIFFUSE); - return mat; - } - - osg::ref_ptr createUnlitMaterial() - { - osg::ref_ptr mat = new osg::Material; - mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 1.f)); - mat->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 1.f)); - mat->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(1.f, 1.f, 1.f, 1.f)); - mat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 0.f)); - mat->setColorMode(osg::Material::OFF); - return mat; - } - - osg::ref_ptr createTexturedQuad(int numUvSets=1, float scale=1.f) - { - osg::ref_ptr geom = new osg::Geometry; - - osg::ref_ptr verts = new osg::Vec3Array; - verts->push_back(osg::Vec3f(-0.5*scale, -0.5*scale, 0)); - verts->push_back(osg::Vec3f(-0.5*scale, 0.5*scale, 0)); - verts->push_back(osg::Vec3f(0.5*scale, 0.5*scale, 0)); - verts->push_back(osg::Vec3f(0.5*scale, -0.5*scale, 0)); - - geom->setVertexArray(verts); - - osg::ref_ptr texcoords = new osg::Vec2Array; - texcoords->push_back(osg::Vec2f(0, 0)); - texcoords->push_back(osg::Vec2f(0, 1)); - texcoords->push_back(osg::Vec2f(1, 1)); - texcoords->push_back(osg::Vec2f(1, 0)); - - osg::ref_ptr colors = new osg::Vec4Array; - colors->push_back(osg::Vec4(1.f, 1.f, 1.f, 1.f)); - geom->setColorArray(colors, osg::Array::BIND_OVERALL); - - for (int i=0; isetTexCoordArray(i, texcoords, osg::Array::BIND_PER_VERTEX); - - geom->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::QUADS,0,4)); - - return geom; - } - -} - -namespace MWRender -{ - -class AtmosphereUpdater : public SceneUtil::StateSetUpdater -{ -public: - void setEmissionColor(const osg::Vec4f& emissionColor) - { - mEmissionColor = emissionColor; - } - -protected: - void setDefaults(osg::StateSet* stateset) override - { - stateset->setAttributeAndModes(createAlphaTrackingUnlitMaterial(), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); - } - - void apply(osg::StateSet* stateset, osg::NodeVisitor* /*nv*/) override - { - osg::Material* mat = static_cast(stateset->getAttribute(osg::StateAttribute::MATERIAL)); - mat->setEmission(osg::Material::FRONT_AND_BACK, mEmissionColor); - } - -private: - osg::Vec4f mEmissionColor; -}; - -class AtmosphereNightUpdater : public SceneUtil::StateSetUpdater -{ -public: - AtmosphereNightUpdater(Resource::ImageManager* imageManager) - { - // we just need a texture, its contents don't really matter - mTexture = new osg::Texture2D(imageManager->getWarningImage()); - } - - void setFade(const float fade) - { - mColor.a() = fade; - } - -protected: - void setDefaults(osg::StateSet* stateset) override - { - osg::ref_ptr texEnv (new osg::TexEnvCombine); - texEnv->setCombine_Alpha(osg::TexEnvCombine::MODULATE); - texEnv->setSource0_Alpha(osg::TexEnvCombine::PREVIOUS); - texEnv->setSource1_Alpha(osg::TexEnvCombine::CONSTANT); - texEnv->setCombine_RGB(osg::TexEnvCombine::REPLACE); - texEnv->setSource0_RGB(osg::TexEnvCombine::PREVIOUS); - - stateset->setTextureAttributeAndModes(1, mTexture, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); - stateset->setTextureAttributeAndModes(1, texEnv, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); - } - - void apply(osg::StateSet* stateset, osg::NodeVisitor* /*nv*/) override - { - osg::TexEnvCombine* texEnv = static_cast(stateset->getTextureAttribute(1, osg::StateAttribute::TEXENV)); - texEnv->setConstantColor(mColor); - } - - osg::ref_ptr mTexture; - - osg::Vec4f mColor; -}; - -class CloudUpdater : public SceneUtil::StateSetUpdater -{ -public: - CloudUpdater() - : mAnimationTimer(0.f) - , mOpacity(0.f) - { - } - - void setAnimationTimer(float timer) - { - mAnimationTimer = timer; - } - - void setTexture(osg::ref_ptr texture) - { - mTexture = texture; - } - void setEmissionColor(const osg::Vec4f& emissionColor) - { - mEmissionColor = emissionColor; - } - void setOpacity(float opacity) - { - mOpacity = opacity; - } - -protected: - void setDefaults(osg::StateSet *stateset) override - { - osg::ref_ptr texmat (new osg::TexMat); - stateset->setTextureAttributeAndModes(0, texmat, osg::StateAttribute::ON); - stateset->setTextureAttributeAndModes(1, texmat, osg::StateAttribute::ON); - stateset->setAttribute(createAlphaTrackingUnlitMaterial(), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); - - // need to set opacity on a separate texture unit, diffuse alpha is used by the vertex colors already - osg::ref_ptr texEnvCombine (new osg::TexEnvCombine); - texEnvCombine->setSource0_RGB(osg::TexEnvCombine::PREVIOUS); - texEnvCombine->setSource0_Alpha(osg::TexEnvCombine::PREVIOUS); - texEnvCombine->setSource1_Alpha(osg::TexEnvCombine::CONSTANT); - texEnvCombine->setConstantColor(osg::Vec4f(1,1,1,1)); - texEnvCombine->setCombine_Alpha(osg::TexEnvCombine::MODULATE); - texEnvCombine->setCombine_RGB(osg::TexEnvCombine::REPLACE); - - stateset->setTextureAttributeAndModes(1, texEnvCombine, osg::StateAttribute::ON); - - stateset->setTextureMode(0, GL_TEXTURE_2D, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); - stateset->setTextureMode(1, GL_TEXTURE_2D, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); - } - - void apply(osg::StateSet *stateset, osg::NodeVisitor *nv) override - { - osg::TexMat* texMat = static_cast(stateset->getTextureAttribute(0, osg::StateAttribute::TEXMAT)); - texMat->setMatrix(osg::Matrix::translate(osg::Vec3f(0, -mAnimationTimer, 0.f))); - - stateset->setTextureAttribute(0, mTexture, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); - stateset->setTextureAttribute(1, mTexture, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); - - osg::Material* mat = static_cast(stateset->getAttribute(osg::StateAttribute::MATERIAL)); - mat->setEmission(osg::Material::FRONT_AND_BACK, mEmissionColor); - - osg::TexEnvCombine* texEnvCombine = static_cast(stateset->getTextureAttribute(1, osg::StateAttribute::TEXENV)); - texEnvCombine->setConstantColor(osg::Vec4f(1,1,1,mOpacity)); - } - -private: - float mAnimationTimer; - osg::ref_ptr mTexture; - osg::Vec4f mEmissionColor; - float mOpacity; -}; - -/// Transform that removes the eyepoint of the modelview matrix, -/// i.e. its children are positioned relative to the camera. -class CameraRelativeTransform : public osg::Transform -{ -public: - CameraRelativeTransform() - { - // Culling works in node-local space, not in camera space, so we can't cull this node correctly - // That's not a problem though, children of this node can be culled just fine - // Just make sure you do not place a CameraRelativeTransform deep in the scene graph - setCullingActive(false); - - addCullCallback(new CullCallback); - } - - CameraRelativeTransform(const CameraRelativeTransform& copy, const osg::CopyOp& copyop) - : osg::Transform(copy, copyop) - { - } - - META_Node(MWRender, CameraRelativeTransform) - - const osg::Vec3f& getLastViewPoint() const - { - return mViewPoint; - } - - bool computeLocalToWorldMatrix(osg::Matrix& matrix, osg::NodeVisitor* nv) const override - { - if (nv->getVisitorType() == osg::NodeVisitor::CULL_VISITOR) - { - mViewPoint = static_cast(nv)->getViewPoint(); - } - - if (_referenceFrame==RELATIVE_RF) - { - matrix.setTrans(osg::Vec3f(0.f,0.f,0.f)); - return false; - } - else // absolute - { - matrix.makeIdentity(); - return true; - } - } - - osg::BoundingSphere computeBound() const override - { - return osg::BoundingSphere(); - } - - class CullCallback : public SceneUtil::NodeCallback + class WrapAroundOperator : public osgParticle::Operator { public: - void operator() (osg::Node* node, osgUtil::CullVisitor* cv) + WrapAroundOperator(osg::Camera *camera, const osg::Vec3 &wrapRange) + : osgParticle::Operator() + , mCamera(camera) + , mWrapRange(wrapRange) + , mHalfWrapRange(mWrapRange / 2.0) { - // XXX have to remove unwanted culling plane of the water reflection camera + mPreviousCameraPosition = getCameraPosition(); + } - // Remove all planes that aren't from the standard frustum - unsigned int numPlanes = 4; - if (cv->getCullingMode() & osg::CullSettings::NEAR_PLANE_CULLING) - ++numPlanes; - if (cv->getCullingMode() & osg::CullSettings::FAR_PLANE_CULLING) - ++numPlanes; + osg::Object *cloneType() const override + { + return nullptr; + } - unsigned int mask = 0x1; - unsigned int resultMask = cv->getProjectionCullingStack().back().getFrustum().getResultMask(); - for (unsigned int i=0; igetProjectionCullingStack().back().getFrustum().getPlaneList().size(); ++i) + osg::Object *clone(const osg::CopyOp &op) const override + { + return nullptr; + } + + void operate(osgParticle::Particle *P, double dt) override + { + } + + void operateParticles(osgParticle::ParticleSystem *ps, double dt) override + { + osg::Vec3 position = getCameraPosition(); + osg::Vec3 positionDifference = position - mPreviousCameraPosition; + + osg::Matrix toWorld, toLocal; + + std::vector worldMatrices = ps->getWorldMatrices(); + + + if (!worldMatrices.empty()) { - if (i >= numPlanes) + toWorld = worldMatrices[0]; + toLocal.invert(toWorld); + } + + for (int i = 0; i < ps->numParticles(); ++i) + { + osgParticle::Particle *p = ps->getParticle(i); + p->setPosition(toWorld.preMult(p->getPosition())); + p->setPosition(p->getPosition() - positionDifference); + + for (int j = 0; j < 3; ++j) // wrap-around in all 3 dimensions { - // turn off this culling plane - resultMask &= (~mask); + osg::Vec3 pos = p->getPosition(); + + if (pos[j] < -mHalfWrapRange[j]) + pos[j] = mHalfWrapRange[j] + fmod(pos[j] - mHalfWrapRange[j],mWrapRange[j]); + else if (pos[j] > mHalfWrapRange[j]) + pos[j] = fmod(pos[j] + mHalfWrapRange[j],mWrapRange[j]) - mHalfWrapRange[j]; + + p->setPosition(pos); } - mask <<= 1; + p->setPosition(toLocal.preMult(p->getPosition())); } - cv->getProjectionCullingStack().back().getFrustum().setResultMask(resultMask); - cv->getCurrentCullingSet().getFrustum().setResultMask(resultMask); - - cv->getProjectionCullingStack().back().pushCurrentMask(); - cv->getCurrentCullingSet().pushCurrentMask(); - - traverse(node, cv); - - cv->getProjectionCullingStack().back().popCurrentMask(); - cv->getCurrentCullingSet().popCurrentMask(); - } - }; -private: - // viewPoint for the current frame - mutable osg::Vec3f mViewPoint; -}; - -class ModVertexAlphaVisitor : public osg::NodeVisitor -{ -public: - ModVertexAlphaVisitor(int meshType) - : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) - , mMeshType(meshType) - { - } - - void apply(osg::Drawable& drw) override - { - osg::Geometry* geom = drw.asGeometry(); - if (!geom) - return; - - osg::ref_ptr colors = new osg::Vec4Array(geom->getVertexArray()->getNumElements()); - for (unsigned int i=0; isize(); ++i) - { - float alpha = 1.f; - if (mMeshType == 0) alpha = (i%2) ? 0.f : 1.f; // this is a cylinder, so every second vertex belongs to the bottom-most row - else if (mMeshType == 1) - { - if (i>= 49 && i <= 64) alpha = 0.f; // bottom-most row - else if (i>= 33 && i <= 48) alpha = 0.25098; // second row - else alpha = 1.f; - } - else if (mMeshType == 2) - { - if (geom->getColorArray()) - { - osg::Vec4Array* origColors = static_cast(geom->getColorArray()); - alpha = ((*origColors)[i].x() == 1.f) ? 1.f : 0.f; - } - else - alpha = 1.f; - } - - (*colors)[i] = osg::Vec4f(0.f, 0.f, 0.f, alpha); - } - - geom->setColorArray(colors, osg::Array::BIND_PER_VERTEX); - } - -private: - int mMeshType; -}; - -/// @brief Hides the node subgraph if the eye point is below water. -/// @note Must be added as cull callback. -/// @note Meant to be used on a node that is child of a CameraRelativeTransform. -/// The current view point must be retrieved by the CameraRelativeTransform since we can't get it anymore once we are in camera-relative space. -class UnderwaterSwitchCallback : public SceneUtil::NodeCallback -{ -public: - UnderwaterSwitchCallback(CameraRelativeTransform* cameraRelativeTransform) - : mCameraRelativeTransform(cameraRelativeTransform) - , mEnabled(true) - , mWaterLevel(0.f) - { - } - - bool isUnderwater() - { - osg::Vec3f viewPoint = mCameraRelativeTransform->getLastViewPoint(); - return mEnabled && viewPoint.z() < mWaterLevel; - } - - void operator()(osg::Node* node, osg::NodeVisitor* nv) - { - if (isUnderwater()) - return; - - traverse(node, nv); - } - - void setEnabled(bool enabled) - { - mEnabled = enabled; - } - void setWaterLevel(float waterLevel) - { - mWaterLevel = waterLevel; - } - -private: - osg::ref_ptr mCameraRelativeTransform; - bool mEnabled; - float mWaterLevel; -}; - -/// A base class for the sun and moons. -class CelestialBody -{ -public: - CelestialBody(osg::Group* parentNode, float scaleFactor, int numUvSets, unsigned int visibleMask=~0u) - : mVisibleMask(visibleMask) - { - mGeom = createTexturedQuad(numUvSets); - mTransform = new osg::PositionAttitudeTransform; - mTransform->setNodeMask(mVisibleMask); - mTransform->setScale(osg::Vec3f(450,450,450) * scaleFactor); - mTransform->addChild(mGeom); - - parentNode->addChild(mTransform); - } - - virtual ~CelestialBody() {} - - virtual void adjustTransparency(const float ratio) = 0; - - void setVisible(bool visible) - { - mTransform->setNodeMask(visible ? mVisibleMask : 0); - } - -protected: - unsigned int mVisibleMask; - static const float mDistance; - osg::ref_ptr mTransform; - osg::ref_ptr mGeom; -}; - -const float CelestialBody::mDistance = 1000.0f; - -class Sun : public CelestialBody -{ -public: - Sun(osg::Group* parentNode, Resource::ImageManager& imageManager) - : CelestialBody(parentNode, 1.0f, 1, Mask_Sun) - , mUpdater(new Updater) - { - mTransform->addUpdateCallback(mUpdater); - - osg::ref_ptr sunTex (new osg::Texture2D(imageManager.getImage("textures/tx_sun_05.dds"))); - sunTex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); - sunTex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); - - mGeom->getOrCreateStateSet()->setTextureAttributeAndModes(0, sunTex, osg::StateAttribute::ON); - - osg::ref_ptr queryNode (new osg::Group); - // Need to render after the world geometry so we can correctly test for occlusions - osg::StateSet* stateset = queryNode->getOrCreateStateSet(); - stateset->setRenderBinDetails(RenderBin_OcclusionQuery, "RenderBin"); - stateset->setNestRenderBins(false); - // Set up alpha testing on the occlusion testing subgraph, that way we can get the occlusion tested fragments to match the circular shape of the sun - osg::ref_ptr alphaFunc (new osg::AlphaFunc); - alphaFunc->setFunction(osg::AlphaFunc::GREATER, 0.8); - stateset->setAttributeAndModes(alphaFunc, osg::StateAttribute::ON); - stateset->setTextureAttributeAndModes(0, sunTex, osg::StateAttribute::ON); - stateset->setAttributeAndModes(createUnlitMaterial(), osg::StateAttribute::ON); - // Disable writing to the color buffer. We are using this geometry for visibility tests only. - osg::ref_ptr colormask (new osg::ColorMask(0, 0, 0, 0)); - stateset->setAttributeAndModes(colormask, osg::StateAttribute::ON); - - mTransform->addChild(queryNode); - - mOcclusionQueryVisiblePixels = createOcclusionQueryNode(queryNode, true); - mOcclusionQueryTotalPixels = createOcclusionQueryNode(queryNode, false); - - createSunFlash(imageManager); - createSunGlare(); - } - - ~Sun() - { - mTransform->removeUpdateCallback(mUpdater); - destroySunFlash(); - destroySunGlare(); - } - - void setColor(const osg::Vec4f& color) - { - mUpdater->mColor.r() = color.r(); - mUpdater->mColor.g() = color.g(); - mUpdater->mColor.b() = color.b(); - } - - void adjustTransparency(const float ratio) override - { - mUpdater->mColor.a() = ratio; - if (mSunGlareCallback) - mSunGlareCallback->setGlareView(ratio); - if (mSunFlashCallback) - mSunFlashCallback->setGlareView(ratio); - } - - void setDirection(const osg::Vec3f& direction) - { - osg::Vec3f normalizedDirection = direction / direction.length(); - mTransform->setPosition(normalizedDirection * mDistance); - - osg::Quat quat; - quat.makeRotate(osg::Vec3f(0.0f, 0.0f, 1.0f), normalizedDirection); - mTransform->setAttitude(quat); - } - - void setGlareTimeOfDayFade(float val) - { - if (mSunGlareCallback) - mSunGlareCallback->setTimeOfDayFade(val); - } - -private: - class DummyComputeBoundCallback : public osg::Node::ComputeBoundingSphereCallback - { - public: - osg::BoundingSphere computeBound(const osg::Node& node) const override { return osg::BoundingSphere(); } - }; - - /// @param queryVisible If true, queries the amount of visible pixels. If false, queries the total amount of pixels. - osg::ref_ptr createOcclusionQueryNode(osg::Group* parent, bool queryVisible) - { - osg::ref_ptr oqn = new osg::OcclusionQueryNode; - oqn->setQueriesEnabled(true); - -#if OSG_VERSION_GREATER_OR_EQUAL(3, 6, 5) - // With OSG 3.6.5, the method of providing user defined query geometry has been completely replaced - osg::ref_ptr queryGeom = new osg::QueryGeometry(oqn->getName()); -#else - osg::ref_ptr queryGeom = oqn->getQueryGeometry(); -#endif - - // Make it fast! A DYNAMIC query geometry means we can't break frame until the flare is rendered (which is rendered after all the other geometry, - // so that would be pretty bad). STATIC should be safe, since our node's local bounds are static, thus computeBounds() which modifies the queryGeometry - // is only called once. - // Note the debug geometry setDebugDisplay(true) is always DYNAMIC and that can't be changed, not a big deal. - queryGeom->setDataVariance(osg::Object::STATIC); - - // Set up the query geometry to match the actual sun's rendering shape. osg::OcclusionQueryNode wasn't originally intended to allow this, - // normally it would automatically adjust the query geometry to match the sub graph's bounding box. The below hack is needed to - // circumvent this. - queryGeom->setVertexArray(mGeom->getVertexArray()); - queryGeom->setTexCoordArray(0, mGeom->getTexCoordArray(0), osg::Array::BIND_PER_VERTEX); - queryGeom->removePrimitiveSet(0, queryGeom->getNumPrimitiveSets()); - queryGeom->addPrimitiveSet(mGeom->getPrimitiveSet(0)); - - // Hack to disable unwanted awful code inside OcclusionQueryNode::computeBound. - oqn->setComputeBoundingSphereCallback(new DummyComputeBoundCallback); - // Still need a proper bounding sphere. - oqn->setInitialBound(queryGeom->getBound()); - -#if OSG_VERSION_GREATER_OR_EQUAL(3, 6, 5) - oqn->setQueryGeometry(queryGeom.release()); -#endif - - osg::StateSet* queryStateSet = new osg::StateSet; - if (queryVisible) - { - auto depth = SceneUtil::createDepth(); - // This is a trick to make fragments written by the query always use the maximum depth value, - // without having to retrieve the current far clipping distance. - // We want the sun glare to be "infinitely" far away. - double far = SceneUtil::getReverseZ() ? 0.0 : 1.0; - depth->setZNear(far); - depth->setZFar(far); - depth->setWriteMask(false); - queryStateSet->setAttributeAndModes(depth, osg::StateAttribute::ON); - } - else - { - queryStateSet->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); - } - oqn->setQueryStateSet(queryStateSet); - - parent->addChild(oqn); - - return oqn; - } - - void createSunFlash(Resource::ImageManager& imageManager) - { - osg::ref_ptr tex (new osg::Texture2D(imageManager.getImage("textures/tx_sun_flash_grey_05.dds"))); - tex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); - tex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); - - osg::ref_ptr group (new osg::Group); - - mTransform->addChild(group); - - const float scale = 2.6f; - osg::ref_ptr geom = createTexturedQuad(1, scale); - group->addChild(geom); - - osg::StateSet* stateset = geom->getOrCreateStateSet(); - - stateset->setTextureAttributeAndModes(0, tex, osg::StateAttribute::ON); - stateset->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); - stateset->setRenderBinDetails(RenderBin_SunGlare, "RenderBin"); - stateset->setNestRenderBins(false); - - mSunFlashNode = group; - - mSunFlashCallback = new SunFlashCallback(mOcclusionQueryVisiblePixels, mOcclusionQueryTotalPixels); - mSunFlashNode->addCullCallback(mSunFlashCallback); - } - void destroySunFlash() - { - if (mSunFlashNode) - { - mSunFlashNode->removeCullCallback(mSunFlashCallback); - mSunFlashCallback = nullptr; - } - } - - void createSunGlare() - { - osg::ref_ptr camera (new osg::Camera); - camera->setProjectionMatrix(osg::Matrix::identity()); - camera->setReferenceFrame(osg::Transform::ABSOLUTE_RF); // add to skyRoot instead? - camera->setViewMatrix(osg::Matrix::identity()); - camera->setClearMask(0); - camera->setRenderOrder(osg::Camera::NESTED_RENDER); - camera->setAllowEventFocus(false); - - osg::ref_ptr geom = osg::createTexturedQuadGeometry(osg::Vec3f(-1,-1,0), osg::Vec3f(2,0,0), osg::Vec3f(0,2,0)); - - camera->addChild(geom); - - osg::StateSet* stateset = geom->getOrCreateStateSet(); - - stateset->setRenderBinDetails(RenderBin_SunGlare, "RenderBin"); - stateset->setNestRenderBins(false); - stateset->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); - - // set up additive blending - osg::ref_ptr blendFunc (new osg::BlendFunc); - blendFunc->setSource(osg::BlendFunc::SRC_ALPHA); - blendFunc->setDestination(osg::BlendFunc::ONE); - stateset->setAttributeAndModes(blendFunc, osg::StateAttribute::ON); - - mSunGlareCallback = new SunGlareCallback(mOcclusionQueryVisiblePixels, mOcclusionQueryTotalPixels, mTransform); - mSunGlareNode = camera; - - mSunGlareNode->addCullCallback(mSunGlareCallback); - - mTransform->addChild(camera); - } - void destroySunGlare() - { - if (mSunGlareNode) - { - mSunGlareNode->removeCullCallback(mSunGlareCallback); - mSunGlareCallback = nullptr; - } - } - - class Updater : public SceneUtil::StateSetUpdater - { - public: - osg::Vec4f mColor; - - Updater() - : mColor(1.f, 1.f, 1.f, 1.f) - { - } - - void setDefaults(osg::StateSet* stateset) override - { - stateset->setAttributeAndModes(createUnlitMaterial(), osg::StateAttribute::ON); - } - - void apply(osg::StateSet* stateset, osg::NodeVisitor*) override - { - osg::Material* mat = static_cast(stateset->getAttribute(osg::StateAttribute::MATERIAL)); - mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0,0,0,mColor.a())); - mat->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(mColor.r(), mColor.g(), mColor.b(), 1)); - } - }; - - class OcclusionCallback - { - public: - OcclusionCallback(osg::ref_ptr oqnVisible, osg::ref_ptr oqnTotal) - : mOcclusionQueryVisiblePixels(oqnVisible) - , mOcclusionQueryTotalPixels(oqnTotal) - { + mPreviousCameraPosition = position; } protected: - float getVisibleRatio (osg::Camera* camera) + osg::Camera *mCamera; + osg::Vec3 mPreviousCameraPosition; + osg::Vec3 mWrapRange; + osg::Vec3 mHalfWrapRange; + + osg::Vec3 getCameraPosition() { - int visible = mOcclusionQueryVisiblePixels->getQueryGeometry()->getNumPixels(camera); - int total = mOcclusionQueryTotalPixels->getQueryGeometry()->getNumPixels(camera); - - float visibleRatio = 0.f; - if (total > 0) - visibleRatio = static_cast(visible) / static_cast(total); - - float dt = MWBase::Environment::get().getFrameDuration(); - - float lastRatio = mLastRatio[osg::observer_ptr(camera)]; - - float change = dt*10; - - if (visibleRatio > lastRatio) - visibleRatio = std::min(visibleRatio, lastRatio + change); - else - visibleRatio = std::max(visibleRatio, lastRatio - change); - - mLastRatio[osg::observer_ptr(camera)] = visibleRatio; - - return visibleRatio; + return mCamera->getInverseViewMatrix().getTrans(); } - - private: - osg::ref_ptr mOcclusionQueryVisiblePixels; - osg::ref_ptr mOcclusionQueryTotalPixels; - - std::map, float> mLastRatio; }; - /// SunFlashCallback handles fading/scaling of a node depending on occlusion query result. Must be attached as a cull callback. - class SunFlashCallback : public OcclusionCallback, public SceneUtil::NodeCallback + class WeatherAlphaOperator : public osgParticle::Operator { public: - SunFlashCallback(osg::ref_ptr oqnVisible, osg::ref_ptr oqnTotal) - : OcclusionCallback(oqnVisible, oqnTotal) - , mGlareView(1.f) + WeatherAlphaOperator(float& alpha, bool rain) + : mAlpha(alpha) + , mIsRain(rain) + { } + + osg::Object *cloneType() const override { + return nullptr; } - void operator()(osg::Node* node, osgUtil::CullVisitor* cv) + osg::Object *clone(const osg::CopyOp &op) const override { - float visibleRatio = getVisibleRatio(cv->getCurrentCamera()); - - osg::ref_ptr stateset; - - if (visibleRatio > 0.f) - { - const float fadeThreshold = 0.1; - if (visibleRatio < fadeThreshold) - { - float fade = 1.f - (fadeThreshold - visibleRatio) / fadeThreshold; - osg::ref_ptr mat (createUnlitMaterial()); - mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0,0,0,fade*mGlareView)); - stateset = new osg::StateSet; - stateset->setAttributeAndModes(mat, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); - } - else if (visibleRatio < 1.f) - { - const float threshold = 0.6; - visibleRatio = visibleRatio * (1.f - threshold) + threshold; - } - } - - float scale = visibleRatio; - - if (scale == 0.f) - { - // no traverse - return; - } - else if (scale == 1.f) - traverse(node, cv); - else - { - osg::Matrix modelView = *cv->getModelViewMatrix(); - - modelView.preMultScale(osg::Vec3f(scale, scale, scale)); - - if (stateset) - cv->pushStateSet(stateset); - - cv->pushModelViewMatrix(new osg::RefMatrix(modelView), osg::Transform::RELATIVE_RF); - - traverse(node, cv); - - cv->popModelViewMatrix(); - - if (stateset) - cv->popStateSet(); - } + return nullptr; } - void setGlareView(float value) + void operate(osgParticle::Particle *particle, double dt) override { - mGlareView = value; + constexpr float rainThreshold = 0.6f; // Rain_Threshold? + float alpha = mIsRain ? mAlpha * rainThreshold : mAlpha; + particle->setAlphaRange(osgParticle::rangef(alpha, alpha)); } private: - float mGlareView; + float &mAlpha; + bool mIsRain; }; - - /// SunGlareCallback controls a full-screen glare effect depending on occlusion query result and the angle between sun and camera. - /// Must be attached as a cull callback to the node above the glare node. - class SunGlareCallback : public OcclusionCallback, public SceneUtil::NodeCallback + // Updater for alpha value on a node's StateSet. Assumes the node has an existing Material StateAttribute. + class AlphaFader : public SceneUtil::StateSetUpdater { public: - SunGlareCallback(osg::ref_ptr oqnVisible, osg::ref_ptr oqnTotal, - osg::ref_ptr sunTransform) - : OcclusionCallback(oqnVisible, oqnTotal) - , mSunTransform(sunTransform) - , mTimeOfDayFade(1.f) - , mGlareView(1.f) - { - mColor = Fallback::Map::getColour("Weather_Sun_Glare_Fader_Color"); - mSunGlareFaderMax = Fallback::Map::getFloat("Weather_Sun_Glare_Fader_Max"); - mSunGlareFaderAngleMax = Fallback::Map::getFloat("Weather_Sun_Glare_Fader_Angle_Max"); - - // Replicating a design flaw in MW. The color was being set on both ambient and emissive properties, which multiplies the result by two, - // then finally gets clamped by the fixed function pipeline. With the default INI settings, only the red component gets clamped, - // so the resulting color looks more orange than red. - mColor *= 2; - for (int i=0; i<3; ++i) - mColor[i] = std::min(1.f, mColor[i]); - } - - void operator ()(osg::Node* node, osgUtil::CullVisitor* cv) - { - float angleRadians = getAngleToSunInRadians(*cv->getCurrentRenderStage()->getInitialViewMatrix()); - float visibleRatio = getVisibleRatio(cv->getCurrentCamera()); - - const float angleMaxRadians = osg::DegreesToRadians(mSunGlareFaderAngleMax); - - float value = 1.f - std::min(1.f, angleRadians / angleMaxRadians); - float fade = value * mSunGlareFaderMax; - - fade *= mTimeOfDayFade * mGlareView * visibleRatio; - - if (fade == 0.f) - { - // no traverse - return; - } - else - { - osg::ref_ptr stateset (new osg::StateSet); - - osg::ref_ptr mat (createUnlitMaterial()); - - mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0,0,0,fade)); - mat->setEmission(osg::Material::FRONT_AND_BACK, mColor); - - stateset->setAttributeAndModes(mat, osg::StateAttribute::ON); - - cv->pushStateSet(stateset); - traverse(node, cv); - cv->popStateSet(); - } - } - - void setTimeOfDayFade(float val) - { - mTimeOfDayFade = val; - } - - void setGlareView(float glareView) - { - mGlareView = glareView; - } - - private: - float getAngleToSunInRadians(const osg::Matrix& viewMatrix) const - { - osg::Vec3d eye, center, up; - viewMatrix.getLookAt(eye, center, up); - - osg::Vec3d forward = center - eye; - osg::Vec3d sun = mSunTransform->getPosition(); - - forward.normalize(); - sun.normalize(); - float angleRadians = std::acos(forward * sun); - return angleRadians; - } - - osg::ref_ptr mSunTransform; - float mTimeOfDayFade; - float mGlareView; - osg::Vec4f mColor; - float mSunGlareFaderMax; - float mSunGlareFaderAngleMax; - }; - - osg::ref_ptr mUpdater; - osg::ref_ptr mSunFlashCallback; - osg::ref_ptr mSunFlashNode; - osg::ref_ptr mSunGlareCallback; - osg::ref_ptr mSunGlareNode; - osg::ref_ptr mOcclusionQueryVisiblePixels; - osg::ref_ptr mOcclusionQueryTotalPixels; -}; - -class Moon : public CelestialBody -{ -public: - enum Type - { - Type_Masser = 0, - Type_Secunda - }; - - Moon(osg::Group* parentNode, Resource::ImageManager& imageManager, float scaleFactor, Type type) - : CelestialBody(parentNode, scaleFactor, 2) - , mType(type) - , mPhase(MoonState::Phase::Unspecified) - , mUpdater(new Updater(imageManager)) - { - setPhase(MoonState::Phase::Full); - setVisible(true); - - mGeom->addUpdateCallback(mUpdater); - } - - ~Moon() - { - mGeom->removeUpdateCallback(mUpdater); - } - - void adjustTransparency(const float ratio) override - { - mUpdater->mTransparency *= ratio; - } - - void setState(const MoonState& state) - { - float radsX = ((state.mRotationFromHorizon) * static_cast(osg::PI)) / 180.0f; - float radsZ = ((state.mRotationFromNorth) * static_cast(osg::PI)) / 180.0f; - - osg::Quat rotX(radsX, osg::Vec3f(1.0f, 0.0f, 0.0f)); - osg::Quat rotZ(radsZ, osg::Vec3f(0.0f, 0.0f, 1.0f)); - - osg::Vec3f direction = rotX * rotZ * osg::Vec3f(0.0f, 1.0f, 0.0f); - mTransform->setPosition(direction * mDistance); - - // The moon quad is initially oriented facing down, so we need to offset its X-axis - // rotation to rotate it to face the camera when sitting at the horizon. - osg::Quat attX((-static_cast(osg::PI) / 2.0f) + radsX, osg::Vec3f(1.0f, 0.0f, 0.0f)); - mTransform->setAttitude(attX * rotZ); - - setPhase(state.mPhase); - mUpdater->mTransparency = state.mMoonAlpha; - mUpdater->mShadowBlend = state.mShadowBlend; - } - - void setAtmosphereColor(const osg::Vec4f& color) - { - mUpdater->mAtmosphereColor = color; - } - - void setColor(const osg::Vec4f& color) - { - mUpdater->mMoonColor = color; - } - - unsigned int getPhaseInt() const - { - if (mPhase == MoonState::Phase::New) return 0; - else if (mPhase == MoonState::Phase::WaxingCrescent) return 1; - else if (mPhase == MoonState::Phase::WaningCrescent) return 1; - else if (mPhase == MoonState::Phase::FirstQuarter) return 2; - else if (mPhase == MoonState::Phase::ThirdQuarter) return 2; - else if (mPhase == MoonState::Phase::WaxingGibbous) return 3; - else if (mPhase == MoonState::Phase::WaningGibbous) return 3; - else if (mPhase == MoonState::Phase::Full) return 4; - return 0; - } - -private: - struct Updater : public SceneUtil::StateSetUpdater - { - Resource::ImageManager& mImageManager; - osg::ref_ptr mPhaseTex; - osg::ref_ptr mCircleTex; - float mTransparency; - float mShadowBlend; - osg::Vec4f mAtmosphereColor; - osg::Vec4f mMoonColor; - - Updater(Resource::ImageManager& imageManager) - : mImageManager(imageManager) - , mPhaseTex() - , mCircleTex() - , mTransparency(1.0f) - , mShadowBlend(1.0f) - , mAtmosphereColor(1.0f, 1.0f, 1.0f, 1.0f) - , mMoonColor(1.0f, 1.0f, 1.0f, 1.0f) - { - } + /// @param alpha the variable alpha value is recovered from + AlphaFader(const float& alpha) + : mAlpha(alpha) + { } void setDefaults(osg::StateSet* stateset) override { - stateset->setTextureAttributeAndModes(0, mPhaseTex, osg::StateAttribute::ON); - osg::ref_ptr texEnv = new osg::TexEnvCombine; - texEnv->setCombine_RGB(osg::TexEnvCombine::MODULATE); - texEnv->setSource0_RGB(osg::TexEnvCombine::CONSTANT); - texEnv->setSource1_RGB(osg::TexEnvCombine::TEXTURE); - texEnv->setConstantColor(osg::Vec4f(1.f, 0.f, 0.f, 1.f)); // mShadowBlend * mMoonColor - stateset->setTextureAttributeAndModes(0, texEnv, osg::StateAttribute::ON); - - stateset->setTextureAttributeAndModes(1, mCircleTex, osg::StateAttribute::ON); - osg::ref_ptr texEnv2 = new osg::TexEnvCombine; - texEnv2->setCombine_RGB(osg::TexEnvCombine::ADD); - texEnv2->setCombine_Alpha(osg::TexEnvCombine::MODULATE); - texEnv2->setSource0_Alpha(osg::TexEnvCombine::TEXTURE); - texEnv2->setSource1_Alpha(osg::TexEnvCombine::CONSTANT); - texEnv2->setSource0_RGB(osg::TexEnvCombine::PREVIOUS); - texEnv2->setSource1_RGB(osg::TexEnvCombine::CONSTANT); - texEnv2->setConstantColor(osg::Vec4f(0.f, 0.f, 0.f, 1.f)); // mAtmosphereColor.rgb, mTransparency - stateset->setTextureAttributeAndModes(1, texEnv2, osg::StateAttribute::ON); - - stateset->setAttributeAndModes(createUnlitMaterial(), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + // need to create a deep copy of StateAttributes we will modify + osg::Material* mat = static_cast(stateset->getAttribute(osg::StateAttribute::MATERIAL)); + stateset->setAttribute(osg::clone(mat, osg::CopyOp::DEEP_COPY_ALL), osg::StateAttribute::ON); } - void apply(osg::StateSet* stateset, osg::NodeVisitor*) override + void apply(osg::StateSet* stateset, osg::NodeVisitor* nv) override { - osg::TexEnvCombine* texEnv = static_cast(stateset->getTextureAttribute(0, osg::StateAttribute::TEXENV)); - texEnv->setConstantColor(mMoonColor * mShadowBlend); - - osg::TexEnvCombine* texEnv2 = static_cast(stateset->getTextureAttribute(1, osg::StateAttribute::TEXENV)); - texEnv2->setConstantColor(osg::Vec4f(mAtmosphereColor.x(), mAtmosphereColor.y(), mAtmosphereColor.z(), mTransparency)); + osg::Material* mat = static_cast(stateset->getAttribute(osg::StateAttribute::MATERIAL)); + mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, mAlpha)); } - void setTextures(const std::string& phaseTex, const std::string& circleTex) - { - mPhaseTex = new osg::Texture2D(mImageManager.getImage(phaseTex)); - mPhaseTex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); - mPhaseTex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); - mCircleTex = new osg::Texture2D(mImageManager.getImage(circleTex)); - mCircleTex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); - mCircleTex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); - - reset(); - } + protected: + const float &mAlpha; }; - Type mType; - MoonState::Phase mPhase; - osg::ref_ptr mUpdater; - - void setPhase(const MoonState::Phase& phase) - { - if(mPhase == phase) - return; - - mPhase = phase; - - std::string textureName = "textures/tx_"; - - if (mType == Moon::Type_Secunda) - textureName += "secunda_"; - else - textureName += "masser_"; - - if (phase == MoonState::Phase::New) textureName += "new"; - else if(phase == MoonState::Phase::WaxingCrescent) textureName += "one_wax"; - else if(phase == MoonState::Phase::FirstQuarter) textureName += "half_wax"; - else if(phase == MoonState::Phase::WaxingGibbous) textureName += "three_wax"; - else if(phase == MoonState::Phase::WaningCrescent) textureName += "one_wan"; - else if(phase == MoonState::Phase::ThirdQuarter) textureName += "half_wan"; - else if(phase == MoonState::Phase::WaningGibbous) textureName += "three_wan"; - else if(phase == MoonState::Phase::Full) textureName += "full"; - - textureName += ".dds"; - - if (mType == Moon::Type_Secunda) - mUpdater->setTextures(textureName, "textures/tx_mooncircle_full_s.dds"); - else - mUpdater->setTextures(textureName, "textures/tx_mooncircle_full_m.dds"); - } -}; - -SkyManager::SkyManager(osg::Group* parentNode, Resource::SceneManager* sceneManager) - : mSceneManager(sceneManager) - , mCamera(nullptr) - , mAtmosphereNightRoll(0.f) - , mCreated(false) - , mIsStorm(false) - , mDay(0) - , mMonth(0) - , mCloudAnimationTimer(0.f) - , mRainTimer(0.f) - , mStormDirection(0,1,0) - , mClouds() - , mNextClouds() - , mCloudBlendFactor(0.0f) - , mCloudSpeed(0.0f) - , mStarsOpacity(0.0f) - , mRemainingTransitionTime(0.0f) - , mRainEnabled(false) - , mRainSpeed(0) - , mRainDiameter(0) - , mRainMinHeight(0) - , mRainMaxHeight(0) - , mRainEntranceSpeed(1) - , mRainMaxRaindrops(0) - , mWindSpeed(0.f) - , mBaseWindSpeed(0.f) - , mEnabled(true) - , mSunEnabled(true) - , mPrecipitationAlpha(0.f) -{ - osg::ref_ptr skyroot (new CameraRelativeTransform); - skyroot->setName("Sky Root"); - // Assign empty program to specify we don't want shaders - // The shaders generated by the SceneManager can't handle everything we need - skyroot->getOrCreateStateSet()->setAttributeAndModes(new osg::Program(), osg::StateAttribute::OVERRIDE|osg::StateAttribute::PROTECTED|osg::StateAttribute::ON); - SceneUtil::ShadowManager::disableShadowsForStateSet(skyroot->getOrCreateStateSet()); - - skyroot->setNodeMask(Mask_Sky); - parentNode->addChild(skyroot); - - mRootNode = skyroot; - - mEarlyRenderBinRoot = new osg::Group; - // render before the world is rendered - mEarlyRenderBinRoot->getOrCreateStateSet()->setRenderBinDetails(RenderBin_Sky, "RenderBin"); - // Prevent unwanted clipping by water reflection camera's clipping plane - mEarlyRenderBinRoot->getOrCreateStateSet()->setMode(GL_CLIP_PLANE0, osg::StateAttribute::OFF); - mRootNode->addChild(mEarlyRenderBinRoot); - - mUnderwaterSwitch = new UnderwaterSwitchCallback(skyroot); -} - -void SkyManager::create() -{ - assert(!mCreated); - - mAtmosphereDay = mSceneManager->getInstance(Settings::Manager::getString("skyatmosphere", "Models"), mEarlyRenderBinRoot); - ModVertexAlphaVisitor modAtmosphere(0); - mAtmosphereDay->accept(modAtmosphere); - - mAtmosphereUpdater = new AtmosphereUpdater; - mAtmosphereDay->addUpdateCallback(mAtmosphereUpdater); - - mAtmosphereNightNode = new osg::PositionAttitudeTransform; - mAtmosphereNightNode->setNodeMask(0); - mEarlyRenderBinRoot->addChild(mAtmosphereNightNode); - - osg::ref_ptr atmosphereNight; - if (mSceneManager->getVFS()->exists(Settings::Manager::getString("skynight02", "Models"))) - atmosphereNight = mSceneManager->getInstance(Settings::Manager::getString("skynight02", "Models"), mAtmosphereNightNode); - else - atmosphereNight = mSceneManager->getInstance(Settings::Manager::getString("skynight01", "Models"), mAtmosphereNightNode); - atmosphereNight->getOrCreateStateSet()->setAttributeAndModes(createAlphaTrackingUnlitMaterial(), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); - ModVertexAlphaVisitor modStars(2); - atmosphereNight->accept(modStars); - mAtmosphereNightUpdater = new AtmosphereNightUpdater(mSceneManager->getImageManager()); - atmosphereNight->addUpdateCallback(mAtmosphereNightUpdater); - - mSun.reset(new Sun(mEarlyRenderBinRoot, *mSceneManager->getImageManager())); - - mMasser.reset(new Moon(mEarlyRenderBinRoot, *mSceneManager->getImageManager(), Fallback::Map::getFloat("Moons_Masser_Size")/125, Moon::Type_Masser)); - mSecunda.reset(new Moon(mEarlyRenderBinRoot, *mSceneManager->getImageManager(), Fallback::Map::getFloat("Moons_Secunda_Size")/125, Moon::Type_Secunda)); - - mCloudNode = new osg::PositionAttitudeTransform; - mEarlyRenderBinRoot->addChild(mCloudNode); - mCloudMesh = mSceneManager->getInstance(Settings::Manager::getString("skyclouds", "Models"), mCloudNode); - ModVertexAlphaVisitor modClouds(1); - mCloudMesh->accept(modClouds); - mCloudUpdater = new CloudUpdater; - mCloudUpdater->setOpacity(1.f); - mCloudMesh->addUpdateCallback(mCloudUpdater); - - mCloudMesh2 = mSceneManager->getInstance(Settings::Manager::getString("skyclouds", "Models"), mCloudNode); - mCloudMesh2->accept(modClouds); - mCloudUpdater2 = new CloudUpdater; - mCloudUpdater2->setOpacity(0.f); - mCloudMesh2->addUpdateCallback(mCloudUpdater2); - mCloudMesh2->setNodeMask(0); - - auto depth = SceneUtil::createDepth(); - depth->setWriteMask(false); - mEarlyRenderBinRoot->getOrCreateStateSet()->setAttributeAndModes(depth, osg::StateAttribute::ON); - mEarlyRenderBinRoot->getOrCreateStateSet()->setMode(GL_BLEND, osg::StateAttribute::ON); - mEarlyRenderBinRoot->getOrCreateStateSet()->setMode(GL_FOG, osg::StateAttribute::OFF); - - mMoonScriptColor = Fallback::Map::getColour("Moons_Script_Color"); - - mCreated = true; -} - -class RainCounter : public osgParticle::ConstantRateCounter -{ -public: - int numParticlesToCreate(double dt) const override - { - // limit dt to avoid large particle emissions if there are jumps in the simulation time - // 0.2 seconds is the same cap as used in Engine's frame loop - dt = std::min(dt, 0.2); - return ConstantRateCounter::numParticlesToCreate(dt); - } -}; - -class RainShooter : public osgParticle::Shooter -{ -public: - RainShooter() - : mAngle(0.f) - { - } - - void shoot(osgParticle::Particle* particle) const override - { - particle->setVelocity(mVelocity); - particle->setAngle(osg::Vec3f(-mAngle, 0, (Misc::Rng::rollProbability() * 2 - 1) * osg::PI)); - } - - void setVelocity(const osg::Vec3f& velocity) - { - mVelocity = velocity; - } - - void setAngle(float angle) - { - mAngle = angle; - } - - osg::Object* cloneType() const override - { - return new RainShooter; - } - osg::Object* clone(const osg::CopyOp &) const override - { - return new RainShooter(*this); - } - -private: - osg::Vec3f mVelocity; - float mAngle; -}; - -// Updater for alpha value on a node's StateSet. Assumes the node has an existing Material StateAttribute. -class AlphaFader : public SceneUtil::StateSetUpdater -{ -public: - /// @param alpha the variable alpha value is recovered from - AlphaFader(float& alpha) - : mAlpha(alpha) - { - } - - void setDefaults(osg::StateSet* stateset) override - { - // need to create a deep copy of StateAttributes we will modify - osg::Material* mat = static_cast(stateset->getAttribute(osg::StateAttribute::MATERIAL)); - stateset->setAttribute(osg::clone(mat, osg::CopyOp::DEEP_COPY_ALL), osg::StateAttribute::ON); - } - - void apply(osg::StateSet* stateset, osg::NodeVisitor* nv) override - { - osg::Material* mat = static_cast(stateset->getAttribute(osg::StateAttribute::MATERIAL)); - mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0,0,0,mAlpha)); - } - // Helper for adding AlphaFaders to a subgraph class SetupVisitor : public osg::NodeVisitor { public: - SetupVisitor(float &alpha) + SetupVisitor(const float &alpha) : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) , mAlpha(alpha) - { - } + { } void apply(osg::Node &node) override { @@ -1320,7 +199,7 @@ public: callback = callback->getNestedCallback(); } - osg::ref_ptr alphaFader (new AlphaFader(mAlpha)); + osg::ref_ptr alphaFader = new AlphaFader(mAlpha); if (composite) composite->addController(alphaFader); @@ -1333,608 +212,659 @@ public: } private: - float &mAlpha; + const float &mAlpha; }; - -protected: - float &mAlpha; -}; - -void SkyManager::setCamera(osg::Camera *camera) -{ - mCamera = camera; } -class WrapAroundOperator : public osgParticle::Operator +namespace MWRender { -public: - WrapAroundOperator(osg::Camera *camera, const osg::Vec3 &wrapRange): osgParticle::Operator(), - mCamera(camera), mWrapRange(wrapRange), mHalfWrapRange(mWrapRange / 2.0) + SkyManager::SkyManager(osg::Group* parentNode, Resource::SceneManager* sceneManager) + : mSceneManager(sceneManager) + , mCamera(nullptr) + , mAtmosphereNightRoll(0.f) + , mCreated(false) + , mIsStorm(false) + , mDay(0) + , mMonth(0) + , mCloudAnimationTimer(0.f) + , mRainTimer(0.f) + , mStormParticleDirection(MWWorld::Weather::defaultDirection()) + , mStormDirection(MWWorld::Weather::defaultDirection()) + , mClouds() + , mNextClouds() + , mCloudBlendFactor(0.f) + , mCloudSpeed(0.f) + , mStarsOpacity(0.f) + , mRemainingTransitionTime(0.f) + , mRainEnabled(false) + , mRainSpeed(0.f) + , mRainDiameter(0.f) + , mRainMinHeight(0.f) + , mRainMaxHeight(0.f) + , mRainEntranceSpeed(1.f) + , mRainMaxRaindrops(0) + , mWindSpeed(0.f) + , mBaseWindSpeed(0.f) + , mEnabled(true) + , mSunEnabled(true) + , mPrecipitationAlpha(0.f) { - mPreviousCameraPosition = getCameraPosition(); + osg::ref_ptr skyroot = new CameraRelativeTransform; + skyroot->setName("Sky Root"); + // Assign empty program to specify we don't want shaders when we are rendering in FFP pipeline + if (!mSceneManager->getForceShaders()) + skyroot->getOrCreateStateSet()->setAttributeAndModes(new osg::Program(), osg::StateAttribute::OVERRIDE|osg::StateAttribute::PROTECTED|osg::StateAttribute::ON); + SceneUtil::ShadowManager::disableShadowsForStateSet(skyroot->getOrCreateStateSet()); + + skyroot->setNodeMask(Mask_Sky); + parentNode->addChild(skyroot); + + mRootNode = skyroot; + + mEarlyRenderBinRoot = new osg::Group; + // render before the world is rendered + mEarlyRenderBinRoot->getOrCreateStateSet()->setRenderBinDetails(RenderBin_Sky, "RenderBin"); + // Prevent unwanted clipping by water reflection camera's clipping plane + mEarlyRenderBinRoot->getOrCreateStateSet()->setMode(GL_CLIP_PLANE0, osg::StateAttribute::OFF); + mRootNode->addChild(mEarlyRenderBinRoot); + + mUnderwaterSwitch = new UnderwaterSwitchCallback(skyroot); } - osg::Object *cloneType() const override + void SkyManager::create() { - return nullptr; - } + assert(!mCreated); - osg::Object *clone(const osg::CopyOp &op) const override - { - return nullptr; - } + bool forceShaders = mSceneManager->getForceShaders(); - void operate(osgParticle::Particle *P, double dt) override - { - } + mAtmosphereDay = mSceneManager->getInstance(Settings::Manager::getString("skyatmosphere", "Models"), mEarlyRenderBinRoot); + ModVertexAlphaVisitor modAtmosphere(ModVertexAlphaVisitor::Atmosphere); + mAtmosphereDay->accept(modAtmosphere); - void operateParticles(osgParticle::ParticleSystem *ps, double dt) override - { - osg::Vec3 position = getCameraPosition(); - osg::Vec3 positionDifference = position - mPreviousCameraPosition; + mAtmosphereUpdater = new AtmosphereUpdater; + mAtmosphereDay->addUpdateCallback(mAtmosphereUpdater); - osg::Matrix toWorld, toLocal; + mAtmosphereNightNode = new osg::PositionAttitudeTransform; + mAtmosphereNightNode->setNodeMask(0); + mEarlyRenderBinRoot->addChild(mAtmosphereNightNode); - std::vector worldMatrices = ps->getWorldMatrices(); - - if (!worldMatrices.empty()) + osg::ref_ptr atmosphereNight; + if (mSceneManager->getVFS()->exists(Settings::Manager::getString("skynight02", "Models"))) + atmosphereNight = mSceneManager->getInstance(Settings::Manager::getString("skynight02", "Models"), mAtmosphereNightNode); + else + atmosphereNight = mSceneManager->getInstance(Settings::Manager::getString("skynight01", "Models"), mAtmosphereNightNode); + atmosphereNight->getOrCreateStateSet()->setAttributeAndModes(createAlphaTrackingUnlitMaterial(), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + + ModVertexAlphaVisitor modStars(ModVertexAlphaVisitor::Stars); + atmosphereNight->accept(modStars); + mAtmosphereNightUpdater = new AtmosphereNightUpdater(mSceneManager->getImageManager(), forceShaders); + atmosphereNight->addUpdateCallback(mAtmosphereNightUpdater); + + mSun.reset(new Sun(mEarlyRenderBinRoot, *mSceneManager->getImageManager())); + mMasser.reset(new Moon(mEarlyRenderBinRoot, *mSceneManager, Fallback::Map::getFloat("Moons_Masser_Size")/125, Moon::Type_Masser)); + mSecunda.reset(new Moon(mEarlyRenderBinRoot, *mSceneManager, Fallback::Map::getFloat("Moons_Secunda_Size")/125, Moon::Type_Secunda)); + + mCloudNode = new osg::Group; + mEarlyRenderBinRoot->addChild(mCloudNode); + + mCloudMesh = new osg::PositionAttitudeTransform; + osg::ref_ptr cloudMeshChild = mSceneManager->getInstance(Settings::Manager::getString("skyclouds", "Models"), mCloudMesh); + mCloudUpdater = new CloudUpdater(forceShaders); + mCloudUpdater->setOpacity(1.f); + cloudMeshChild->addUpdateCallback(mCloudUpdater); + mCloudMesh->addChild(cloudMeshChild); + + mNextCloudMesh = new osg::PositionAttitudeTransform; + osg::ref_ptr nextCloudMeshChild = mSceneManager->getInstance(Settings::Manager::getString("skyclouds", "Models"), mNextCloudMesh); + mNextCloudUpdater = new CloudUpdater(forceShaders); + mNextCloudUpdater->setOpacity(0.f); + nextCloudMeshChild->addUpdateCallback(mNextCloudUpdater); + mNextCloudMesh->setNodeMask(0); + mNextCloudMesh->addChild(nextCloudMeshChild); + + mCloudNode->addChild(mCloudMesh); + mCloudNode->addChild(mNextCloudMesh); + + ModVertexAlphaVisitor modClouds(ModVertexAlphaVisitor::Clouds); + mCloudMesh->accept(modClouds); + mNextCloudMesh->accept(modClouds); + + if (mSceneManager->getForceShaders()) { - toWorld = worldMatrices[0]; - toLocal.invert(toWorld); + auto vertex = mSceneManager->getShaderManager().getShader("sky_vertex.glsl", {}, osg::Shader::VERTEX); + auto fragment = mSceneManager->getShaderManager().getShader("sky_fragment.glsl", {}, osg::Shader::FRAGMENT); + auto program = mSceneManager->getShaderManager().getProgram(vertex, fragment); + mEarlyRenderBinRoot->getOrCreateStateSet()->addUniform(new osg::Uniform("pass", -1)); + mEarlyRenderBinRoot->getOrCreateStateSet()->setAttributeAndModes(program, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); } - for (int i = 0; i < ps->numParticles(); ++i) - { - osgParticle::Particle *p = ps->getParticle(i); - p->setPosition(toWorld.preMult(p->getPosition())); - p->setPosition(p->getPosition() - positionDifference); + auto depth = SceneUtil::createDepth(); + depth->setWriteMask(false); + mEarlyRenderBinRoot->getOrCreateStateSet()->setAttributeAndModes(depth, osg::StateAttribute::ON); + mEarlyRenderBinRoot->getOrCreateStateSet()->setMode(GL_BLEND, osg::StateAttribute::ON); + mEarlyRenderBinRoot->getOrCreateStateSet()->setMode(GL_FOG, osg::StateAttribute::OFF); - for (int j = 0; j < 3; ++j) // wrap-around in all 3 dimensions - { - osg::Vec3 pos = p->getPosition(); + mMoonScriptColor = Fallback::Map::getColour("Moons_Script_Color"); - if (pos[j] < -mHalfWrapRange[j]) - pos[j] = mHalfWrapRange[j] + fmod(pos[j] - mHalfWrapRange[j],mWrapRange[j]); - else if (pos[j] > mHalfWrapRange[j]) - pos[j] = fmod(pos[j] + mHalfWrapRange[j],mWrapRange[j]) - mHalfWrapRange[j]; - - p->setPosition(pos); - } - - p->setPosition(toLocal.preMult(p->getPosition())); - } - - mPreviousCameraPosition = position; + mCreated = true; } -protected: - osg::Camera *mCamera; - osg::Vec3 mPreviousCameraPosition; - osg::Vec3 mWrapRange; - osg::Vec3 mHalfWrapRange; - - osg::Vec3 getCameraPosition() - { - return mCamera->getInverseViewMatrix().getTrans(); - } -}; - -class WeatherAlphaOperator : public osgParticle::Operator -{ -public: - WeatherAlphaOperator(float& alpha, bool rain) - : mAlpha(alpha) - , mIsRain(rain) + void SkyManager::setCamera(osg::Camera *camera) { + mCamera = camera; } - osg::Object *cloneType() const override + void SkyManager::createRain() { - return nullptr; - } + if (mRainNode) + return; - osg::Object *clone(const osg::CopyOp &op) const override - { - return nullptr; - } - - void operate(osgParticle::Particle *particle, double dt) override - { - constexpr float rainThreshold = 0.6f; // Rain_Threshold? - const float alpha = mIsRain ? mAlpha * rainThreshold : mAlpha; - particle->setAlphaRange(osgParticle::rangef(alpha, alpha)); - } - -private: - float &mAlpha; - bool mIsRain; -}; - -void SkyManager::createRain() -{ - if (mRainNode) - return; - - mRainNode = new osg::Group; - - mRainParticleSystem = new NifOsg::ParticleSystem; - osg::Vec3 rainRange = osg::Vec3(mRainDiameter, mRainDiameter, (mRainMinHeight+mRainMaxHeight)/2.f); - - mRainParticleSystem->setParticleAlignment(osgParticle::ParticleSystem::FIXED); - mRainParticleSystem->setAlignVectorX(osg::Vec3f(0.1,0,0)); - mRainParticleSystem->setAlignVectorY(osg::Vec3f(0,0,1)); - - osg::ref_ptr stateset (mRainParticleSystem->getOrCreateStateSet()); - - osg::ref_ptr raindropTex (new osg::Texture2D(mSceneManager->getImageManager()->getImage("textures/tx_raindrop_01.dds"))); - raindropTex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); - raindropTex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); - - stateset->setTextureAttributeAndModes(0, raindropTex, osg::StateAttribute::ON); - stateset->setNestRenderBins(false); - stateset->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); - stateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); - stateset->setMode(GL_BLEND, osg::StateAttribute::ON); - - osg::ref_ptr mat (new osg::Material); - mat->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1)); - mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1)); - mat->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE); - stateset->setAttributeAndModes(mat, osg::StateAttribute::ON); - - osgParticle::Particle& particleTemplate = mRainParticleSystem->getDefaultParticleTemplate(); - particleTemplate.setSizeRange(osgParticle::rangef(5.f, 15.f)); - particleTemplate.setAlphaRange(osgParticle::rangef(1.f, 1.f)); - particleTemplate.setLifeTime(1); - - osg::ref_ptr emitter (new osgParticle::ModularEmitter); - emitter->setParticleSystem(mRainParticleSystem); - - osg::ref_ptr placer (new osgParticle::BoxPlacer); - placer->setXRange(-rainRange.x() / 2, rainRange.x() / 2); - placer->setYRange(-rainRange.y() / 2, rainRange.y() / 2); - placer->setZRange(-rainRange.z() / 2, rainRange.z() / 2); - emitter->setPlacer(placer); - mPlacer = placer; - - // FIXME: vanilla engine does not use a particle system to handle rain, it uses a NIF-file with 20 raindrops in it. - // It spawns the (maxRaindrops-getParticleSystem()->numParticles())*dt/rainEntranceSpeed batches every frame (near 1-2). - // Since the rain is a regular geometry, it produces water ripples, also in theory it can be removed if collides with something. - osg::ref_ptr counter (new RainCounter); - counter->setNumberOfParticlesPerSecondToCreate(mRainMaxRaindrops/mRainEntranceSpeed*20); - emitter->setCounter(counter); - mCounter = counter; - - osg::ref_ptr shooter (new RainShooter); - mRainShooter = shooter; - emitter->setShooter(shooter); - - osg::ref_ptr updater (new osgParticle::ParticleSystemUpdater); - updater->addParticleSystem(mRainParticleSystem); - - osg::ref_ptr program (new osgParticle::ModularProgram); - program->addOperator(new WrapAroundOperator(mCamera,rainRange)); - program->addOperator(new WeatherAlphaOperator(mPrecipitationAlpha, true)); - program->setParticleSystem(mRainParticleSystem); - mRainNode->addChild(program); - - mRainNode->addChild(emitter); - mRainNode->addChild(mRainParticleSystem); - mRainNode->addChild(updater); - - // Note: if we ever switch to regular geometry rain, it'll need to use an AlphaFader. - mRainNode->addCullCallback(mUnderwaterSwitch); - mRainNode->setNodeMask(Mask_WeatherParticles); - - mRootNode->addChild(mRainNode); -} - -void SkyManager::destroyRain() -{ - if (!mRainNode) - return; - - mRootNode->removeChild(mRainNode); - mRainNode = nullptr; - mPlacer = nullptr; - mCounter = nullptr; - mRainParticleSystem = nullptr; - mRainShooter = nullptr; -} - -SkyManager::~SkyManager() -{ - if (mRootNode) - { - mRootNode->getParent(0)->removeChild(mRootNode); - mRootNode = nullptr; - } -} - -int SkyManager::getMasserPhase() const -{ - if (!mCreated) return 0; - return mMasser->getPhaseInt(); -} - -int SkyManager::getSecundaPhase() const -{ - if (!mCreated) return 0; - return mSecunda->getPhaseInt(); -} - -bool SkyManager::isEnabled() -{ - return mEnabled; -} - -bool SkyManager::hasRain() const -{ - return mRainNode != nullptr; -} - -float SkyManager::getPrecipitationAlpha() const -{ - if (mEnabled && !mIsStorm && (hasRain() || mParticleNode)) - return mPrecipitationAlpha; - - return 0.f; -} - -void SkyManager::update(float duration) -{ - if (!mEnabled) - return; - - switchUnderwaterRain(); - - if (mIsStorm) - { - osg::Quat quat; - quat.makeRotate(osg::Vec3f(0,1,0), mStormDirection); - - mCloudNode->setAttitude(quat); - if (mParticleNode) - { - // Morrowind deliberately rotates the blizzard mesh, so so should we. - if (mCurrentParticleEffect == Settings::Manager::getString("weatherblizzard", "Models")) - quat.makeRotate(osg::Vec3f(-1,0,0), mStormDirection); - mParticleNode->setAttitude(quat); - } - } - else - mCloudNode->setAttitude(osg::Quat()); - - // UV Scroll the clouds - mCloudAnimationTimer += duration * mCloudSpeed * 0.003; - mCloudUpdater->setAnimationTimer(mCloudAnimationTimer); - mCloudUpdater2->setAnimationTimer(mCloudAnimationTimer); - - // rotate the stars by 360 degrees every 4 days - mAtmosphereNightRoll += MWBase::Environment::get().getWorld()->getTimeScaleFactor()*duration*osg::DegreesToRadians(360.f) / (3600*96.f); - if (mAtmosphereNightNode->getNodeMask() != 0) - mAtmosphereNightNode->setAttitude(osg::Quat(mAtmosphereNightRoll, osg::Vec3f(0,0,1))); -} - -void SkyManager::setEnabled(bool enabled) -{ - if (enabled && !mCreated) - create(); - - mRootNode->setNodeMask(enabled ? Mask_Sky : 0u); - - mEnabled = enabled; -} - -void SkyManager::setMoonColour (bool red) -{ - if (!mCreated) return; - mSecunda->setColor(red ? mMoonScriptColor : osg::Vec4f(1,1,1,1)); -} - -void SkyManager::updateRainParameters() -{ - if (mRainShooter) - { - float angle = -std::atan(mWindSpeed/50.f); - mRainShooter->setVelocity(osg::Vec3f(0, mRainSpeed*std::sin(angle), -mRainSpeed/std::cos(angle))); - mRainShooter->setAngle(angle); + mRainNode = new osg::Group; + mRainParticleSystem = new NifOsg::ParticleSystem; osg::Vec3 rainRange = osg::Vec3(mRainDiameter, mRainDiameter, (mRainMinHeight+mRainMaxHeight)/2.f); - mPlacer->setXRange(-rainRange.x() / 2, rainRange.x() / 2); - mPlacer->setYRange(-rainRange.y() / 2, rainRange.y() / 2); - mPlacer->setZRange(-rainRange.z() / 2, rainRange.z() / 2); + mRainParticleSystem->setParticleAlignment(osgParticle::ParticleSystem::FIXED); + mRainParticleSystem->setAlignVectorX(osg::Vec3f(0.1,0,0)); + mRainParticleSystem->setAlignVectorY(osg::Vec3f(0,0,1)); - mCounter->setNumberOfParticlesPerSecondToCreate(mRainMaxRaindrops/mRainEntranceSpeed*20); + osg::ref_ptr stateset = mRainParticleSystem->getOrCreateStateSet(); + + osg::ref_ptr raindropTex = new osg::Texture2D(mSceneManager->getImageManager()->getImage("textures/tx_raindrop_01.dds")); + raindropTex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); + raindropTex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); + + stateset->setTextureAttributeAndModes(0, raindropTex, osg::StateAttribute::ON); + stateset->setNestRenderBins(false); + stateset->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); + stateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); + stateset->setMode(GL_BLEND, osg::StateAttribute::ON); + + osg::ref_ptr mat = new osg::Material; + mat->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1)); + mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1)); + mat->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE); + stateset->setAttributeAndModes(mat, osg::StateAttribute::ON); + + osgParticle::Particle& particleTemplate = mRainParticleSystem->getDefaultParticleTemplate(); + particleTemplate.setSizeRange(osgParticle::rangef(5.f, 15.f)); + particleTemplate.setAlphaRange(osgParticle::rangef(1.f, 1.f)); + particleTemplate.setLifeTime(1); + + osg::ref_ptr emitter = new osgParticle::ModularEmitter; + emitter->setParticleSystem(mRainParticleSystem); + + osg::ref_ptr placer = new osgParticle::BoxPlacer; + placer->setXRange(-rainRange.x() / 2, rainRange.x() / 2); + placer->setYRange(-rainRange.y() / 2, rainRange.y() / 2); + placer->setZRange(-rainRange.z() / 2, rainRange.z() / 2); + emitter->setPlacer(placer); + mPlacer = placer; + + // FIXME: vanilla engine does not use a particle system to handle rain, it uses a NIF-file with 20 raindrops in it. + // It spawns the (maxRaindrops-getParticleSystem()->numParticles())*dt/rainEntranceSpeed batches every frame (near 1-2). + // Since the rain is a regular geometry, it produces water ripples, also in theory it can be removed if collides with something. + osg::ref_ptr counter = new RainCounter; + counter->setNumberOfParticlesPerSecondToCreate(mRainMaxRaindrops/mRainEntranceSpeed*20); + emitter->setCounter(counter); + mCounter = counter; + + osg::ref_ptr shooter = new RainShooter; + mRainShooter = shooter; + emitter->setShooter(shooter); + + osg::ref_ptr updater = new osgParticle::ParticleSystemUpdater; + updater->addParticleSystem(mRainParticleSystem); + + osg::ref_ptr program = new osgParticle::ModularProgram; + program->addOperator(new WrapAroundOperator(mCamera,rainRange)); + program->addOperator(new WeatherAlphaOperator(mPrecipitationAlpha, true)); + program->setParticleSystem(mRainParticleSystem); + mRainNode->addChild(program); + + mRainNode->addChild(emitter); + mRainNode->addChild(mRainParticleSystem); + mRainNode->addChild(updater); + + // Note: if we ever switch to regular geometry rain, it'll need to use an AlphaFader. + mRainNode->addCullCallback(mUnderwaterSwitch); + mRainNode->setNodeMask(Mask_WeatherParticles); + + mRainParticleSystem->setUserValue("simpleLighting", true); + mSceneManager->recreateShaders(mRainNode); + + mRootNode->addChild(mRainNode); } -} -void SkyManager::switchUnderwaterRain() -{ - if (!mRainParticleSystem) - return; - - bool freeze = mUnderwaterSwitch->isUnderwater(); - mRainParticleSystem->setFrozen(freeze); -} - -void SkyManager::setWeather(const WeatherResult& weather) -{ - if (!mCreated) return; - - mRainEntranceSpeed = weather.mRainEntranceSpeed; - mRainMaxRaindrops = weather.mRainMaxRaindrops; - mRainDiameter = weather.mRainDiameter; - mRainMinHeight = weather.mRainMinHeight; - mRainMaxHeight = weather.mRainMaxHeight; - mRainSpeed = weather.mRainSpeed; - mWindSpeed = weather.mWindSpeed; - mBaseWindSpeed = weather.mBaseWindSpeed; - - if (mRainEffect != weather.mRainEffect) + void SkyManager::destroyRain() { - mRainEffect = weather.mRainEffect; - if (!mRainEffect.empty()) + if (!mRainNode) + return; + + mRootNode->removeChild(mRainNode); + mRainNode = nullptr; + mPlacer = nullptr; + mCounter = nullptr; + mRainParticleSystem = nullptr; + mRainShooter = nullptr; + } + + SkyManager::~SkyManager() + { + if (mRootNode) { - createRain(); - } - else - { - destroyRain(); + mRootNode->getParent(0)->removeChild(mRootNode); + mRootNode = nullptr; } } - updateRainParameters(); - - mIsStorm = weather.mIsStorm; - - if (mCurrentParticleEffect != weather.mParticleEffect) + int SkyManager::getMasserPhase() const { - mCurrentParticleEffect = weather.mParticleEffect; + if (!mCreated) return 0; + return mMasser->getPhaseInt(); + } - // cleanup old particles - if (mParticleEffect) + int SkyManager::getSecundaPhase() const + { + if (!mCreated) return 0; + return mSecunda->getPhaseInt(); + } + + bool SkyManager::isEnabled() + { + return mEnabled; + } + + bool SkyManager::hasRain() const + { + return mRainNode != nullptr; + } + + float SkyManager::getPrecipitationAlpha() const + { + if (mEnabled && !mIsStorm && (hasRain() || mParticleNode)) + return mPrecipitationAlpha; + + return 0.f; + } + + void SkyManager::update(float duration) + { + if (!mEnabled) + return; + + switchUnderwaterRain(); + + if (mIsStorm && mParticleNode) { - mParticleNode->removeChild(mParticleEffect); - mParticleEffect = nullptr; + osg::Quat quat; + quat.makeRotate(MWWorld::Weather::defaultDirection(), mStormParticleDirection); + // Morrowind deliberately rotates the blizzard mesh, so so should we. + if (mCurrentParticleEffect == Settings::Manager::getString("weatherblizzard", "Models")) + quat.makeRotate(osg::Vec3f(-1,0,0), mStormParticleDirection); + mParticleNode->setAttitude(quat); } - if (mCurrentParticleEffect.empty()) + // UV Scroll the clouds + mCloudAnimationTimer += duration * mCloudSpeed * 0.003; + mNextCloudUpdater->setTextureCoord(mCloudAnimationTimer); + mCloudUpdater->setTextureCoord(mCloudAnimationTimer); + + // morrowind rotates each cloud mesh independently + osg::Quat rotation; + rotation.makeRotate(MWWorld::Weather::defaultDirection(), mStormDirection); + mCloudMesh->setAttitude(rotation); + + if (mNextCloudMesh->getNodeMask()) { - if (mParticleNode) + rotation.makeRotate(MWWorld::Weather::defaultDirection(), mNextStormDirection); + mNextCloudMesh->setAttitude(rotation); + } + + // rotate the stars by 360 degrees every 4 days + mAtmosphereNightRoll += MWBase::Environment::get().getWorld()->getTimeScaleFactor()*duration*osg::DegreesToRadians(360.f) / (3600*96.f); + if (mAtmosphereNightNode->getNodeMask() != 0) + mAtmosphereNightNode->setAttitude(osg::Quat(mAtmosphereNightRoll, osg::Vec3f(0,0,1))); + } + + void SkyManager::setEnabled(bool enabled) + { + if (enabled && !mCreated) + create(); + + mRootNode->setNodeMask(enabled ? Mask_Sky : 0u); + + if (!enabled && mParticleNode && mParticleEffect) + mCurrentParticleEffect = {}; + + mEnabled = enabled; + } + + void SkyManager::setMoonColour (bool red) + { + if (!mCreated) return; + mSecunda->setColor(red ? mMoonScriptColor : osg::Vec4f(1,1,1,1)); + } + + void SkyManager::updateRainParameters() + { + if (mRainShooter) + { + float angle = -std::atan(mWindSpeed/50.f); + mRainShooter->setVelocity(osg::Vec3f(0, mRainSpeed*std::sin(angle), -mRainSpeed/std::cos(angle))); + mRainShooter->setAngle(angle); + + osg::Vec3 rainRange = osg::Vec3(mRainDiameter, mRainDiameter, (mRainMinHeight+mRainMaxHeight)/2.f); + + mPlacer->setXRange(-rainRange.x() / 2, rainRange.x() / 2); + mPlacer->setYRange(-rainRange.y() / 2, rainRange.y() / 2); + mPlacer->setZRange(-rainRange.z() / 2, rainRange.z() / 2); + + mCounter->setNumberOfParticlesPerSecondToCreate(mRainMaxRaindrops/mRainEntranceSpeed*20); + } + } + + void SkyManager::switchUnderwaterRain() + { + if (!mRainParticleSystem) + return; + + bool freeze = mUnderwaterSwitch->isUnderwater(); + mRainParticleSystem->setFrozen(freeze); + } + + void SkyManager::setWeather(const WeatherResult& weather) + { + if (!mCreated) return; + + mRainEntranceSpeed = weather.mRainEntranceSpeed; + mRainMaxRaindrops = weather.mRainMaxRaindrops; + mRainDiameter = weather.mRainDiameter; + mRainMinHeight = weather.mRainMinHeight; + mRainMaxHeight = weather.mRainMaxHeight; + mRainSpeed = weather.mRainSpeed; + mWindSpeed = weather.mWindSpeed; + mBaseWindSpeed = weather.mBaseWindSpeed; + + if (mRainEffect != weather.mRainEffect) + { + mRainEffect = weather.mRainEffect; + if (!mRainEffect.empty()) { - mRootNode->removeChild(mParticleNode); - mParticleNode = nullptr; + createRain(); + } + else + { + destroyRain(); } } - else + + updateRainParameters(); + + mIsStorm = weather.mIsStorm; + + if (mIsStorm) + mStormDirection = weather.mStormDirection; + + if (mCurrentParticleEffect != weather.mParticleEffect) { - if (!mParticleNode) + mCurrentParticleEffect = weather.mParticleEffect; + + // cleanup old particles + if (mParticleEffect) { - mParticleNode = new osg::PositionAttitudeTransform; - mParticleNode->addCullCallback(mUnderwaterSwitch); - mParticleNode->setNodeMask(Mask_WeatherParticles); - mRootNode->addChild(mParticleNode); + mParticleNode->removeChild(mParticleEffect); + mParticleEffect = nullptr; } - mParticleEffect = mSceneManager->getInstance(mCurrentParticleEffect, mParticleNode); - - SceneUtil::AssignControllerSourcesVisitor assignVisitor(std::shared_ptr(new SceneUtil::FrameTimeSource)); - mParticleEffect->accept(assignVisitor); - - AlphaFader::SetupVisitor alphaFaderSetupVisitor(mPrecipitationAlpha); - - mParticleEffect->accept(alphaFaderSetupVisitor); - - SceneUtil::FindByClassVisitor findPSVisitor(std::string("ParticleSystem")); - mParticleEffect->accept(findPSVisitor); - - for (unsigned int i = 0; i < findPSVisitor.mFoundNodes.size(); ++i) + if (mCurrentParticleEffect.empty()) { - osgParticle::ParticleSystem *ps = static_cast(findPSVisitor.mFoundNodes[i]); - - osg::ref_ptr program (new osgParticle::ModularProgram); - if (!mIsStorm) - program->addOperator(new WrapAroundOperator(mCamera,osg::Vec3(1024,1024,800))); - program->addOperator(new WeatherAlphaOperator(mPrecipitationAlpha, false)); - program->setParticleSystem(ps); - mParticleNode->addChild(program); + if (mParticleNode) + { + mRootNode->removeChild(mParticleNode); + mParticleNode = nullptr; + } + } + else + { + if (!mParticleNode) + { + mParticleNode = new osg::PositionAttitudeTransform; + mParticleNode->addCullCallback(mUnderwaterSwitch); + mParticleNode->setNodeMask(Mask_WeatherParticles); + mParticleNode->getOrCreateStateSet(); + mRootNode->addChild(mParticleNode); + } + + mParticleEffect = mSceneManager->getInstance(mCurrentParticleEffect, mParticleNode); + + SceneUtil::AssignControllerSourcesVisitor assignVisitor = std::shared_ptr(new SceneUtil::FrameTimeSource); + mParticleEffect->accept(assignVisitor); + + SetupVisitor alphaFaderSetupVisitor(mPrecipitationAlpha); + mParticleEffect->accept(alphaFaderSetupVisitor); + + SceneUtil::FindByClassVisitor findPSVisitor(std::string("ParticleSystem")); + mParticleEffect->accept(findPSVisitor); + + for (unsigned int i = 0; i < findPSVisitor.mFoundNodes.size(); ++i) + { + osgParticle::ParticleSystem *ps = static_cast(findPSVisitor.mFoundNodes[i]); + + osg::ref_ptr program = new osgParticle::ModularProgram; + if (!mIsStorm) + program->addOperator(new WrapAroundOperator(mCamera,osg::Vec3(1024,1024,800))); + program->addOperator(new WeatherAlphaOperator(mPrecipitationAlpha, false)); + program->setParticleSystem(ps); + mParticleNode->addChild(program); + + for (int particleIndex = 0; particleIndex < ps->numParticles(); ++particleIndex) + ps->getParticle(particleIndex)->setAlphaRange(osgParticle::rangef(mPrecipitationAlpha, mPrecipitationAlpha)); + + ps->getOrCreateStateSet(); + ps->setUserValue("simpleLighting", true); + } + + mSceneManager->recreateShaders(mParticleNode); } } - } - if (mClouds != weather.mCloudTexture) - { - mClouds = weather.mCloudTexture; - - std::string texture = Misc::ResourceHelpers::correctTexturePath(mClouds, mSceneManager->getVFS()); - - osg::ref_ptr cloudTex (new osg::Texture2D(mSceneManager->getImageManager()->getImage(texture))); - cloudTex->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT); - cloudTex->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT); - - mCloudUpdater->setTexture(cloudTex); - } - - if (mNextClouds != weather.mNextCloudTexture) - { - mNextClouds = weather.mNextCloudTexture; - - if (!mNextClouds.empty()) + if (mClouds != weather.mCloudTexture) { - std::string texture = Misc::ResourceHelpers::correctTexturePath(mNextClouds, mSceneManager->getVFS()); + mClouds = weather.mCloudTexture; - osg::ref_ptr cloudTex (new osg::Texture2D(mSceneManager->getImageManager()->getImage(texture))); + std::string texture = Misc::ResourceHelpers::correctTexturePath(mClouds, mSceneManager->getVFS()); + + osg::ref_ptr cloudTex = new osg::Texture2D(mSceneManager->getImageManager()->getImage(texture)); cloudTex->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT); cloudTex->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT); - mCloudUpdater2->setTexture(cloudTex); + mCloudUpdater->setTexture(cloudTex); } + + if (mStormDirection != weather.mStormDirection) + mStormDirection = weather.mStormDirection; + + if (mNextStormDirection != weather.mNextStormDirection) + mNextStormDirection = weather.mNextStormDirection; + + if (mNextClouds != weather.mNextCloudTexture) + { + mNextClouds = weather.mNextCloudTexture; + + if (!mNextClouds.empty()) + { + std::string texture = Misc::ResourceHelpers::correctTexturePath(mNextClouds, mSceneManager->getVFS()); + + osg::ref_ptr cloudTex = new osg::Texture2D(mSceneManager->getImageManager()->getImage(texture)); + cloudTex->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT); + cloudTex->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT); + + mNextCloudUpdater->setTexture(cloudTex); + mNextStormDirection = weather.mStormDirection; + } + } + + if (mCloudBlendFactor != weather.mCloudBlendFactor) + { + mCloudBlendFactor = std::clamp(weather.mCloudBlendFactor, 0.f, 1.f); + + mCloudUpdater->setOpacity(1.f - mCloudBlendFactor); + mNextCloudUpdater->setOpacity(mCloudBlendFactor); + mNextCloudMesh->setNodeMask(mCloudBlendFactor > 0.f ? ~0u : 0); + } + + if (mCloudColour != weather.mFogColor) + { + osg::Vec4f clr (weather.mFogColor); + clr += osg::Vec4f(0.13f, 0.13f, 0.13f, 0.f); + + mCloudUpdater->setEmissionColor(clr); + mNextCloudUpdater->setEmissionColor(clr); + + mCloudColour = weather.mFogColor; + } + + if (mSkyColour != weather.mSkyColor) + { + mSkyColour = weather.mSkyColor; + + mAtmosphereUpdater->setEmissionColor(mSkyColour); + mMasser->setAtmosphereColor(mSkyColour); + mSecunda->setAtmosphereColor(mSkyColour); + } + + if (mFogColour != weather.mFogColor) + { + mFogColour = weather.mFogColor; + } + + mCloudSpeed = weather.mCloudSpeed; + + mMasser->adjustTransparency(weather.mGlareView); + mSecunda->adjustTransparency(weather.mGlareView); + + mSun->setColor(weather.mSunDiscColor); + mSun->adjustTransparency(weather.mGlareView * weather.mSunDiscColor.a()); + + float nextStarsOpacity = weather.mNightFade * weather.mGlareView; + + if (weather.mNight && mStarsOpacity != nextStarsOpacity) + { + mStarsOpacity = nextStarsOpacity; + + mAtmosphereNightUpdater->setFade(mStarsOpacity); + } + + mAtmosphereNightNode->setNodeMask(weather.mNight ? ~0u : 0); + mPrecipitationAlpha = weather.mPrecipitationAlpha; } - if (mCloudBlendFactor != weather.mCloudBlendFactor) + float SkyManager::getBaseWindSpeed() const { - mCloudBlendFactor = weather.mCloudBlendFactor; + if (!mCreated) return 0.f; - mCloudUpdater->setOpacity((1.f-mCloudBlendFactor)); - mCloudUpdater2->setOpacity(mCloudBlendFactor); - mCloudMesh2->setNodeMask(mCloudBlendFactor > 0.f ? ~0u : 0); + return mBaseWindSpeed; } - if (mCloudColour != weather.mFogColor) + void SkyManager::sunEnable() { - osg::Vec4f clr (weather.mFogColor); - clr += osg::Vec4f(0.13f, 0.13f, 0.13f, 0.f); + if (!mCreated) return; - mCloudUpdater->setEmissionColor(clr); - mCloudUpdater2->setEmissionColor(clr); - - mCloudColour = weather.mFogColor; + mSun->setVisible(true); } - if (mSkyColour != weather.mSkyColor) + void SkyManager::sunDisable() { - mSkyColour = weather.mSkyColor; + if (!mCreated) return; - mAtmosphereUpdater->setEmissionColor(mSkyColour); - mMasser->setAtmosphereColor(mSkyColour); - mSecunda->setAtmosphereColor(mSkyColour); + mSun->setVisible(false); } - if (mFogColour != weather.mFogColor) + void SkyManager::setStormParticleDirection(const osg::Vec3f &direction) { - mFogColour = weather.mFogColor; + mStormParticleDirection = direction; } - mCloudSpeed = weather.mCloudSpeed; - - mMasser->adjustTransparency(weather.mGlareView); - mSecunda->adjustTransparency(weather.mGlareView); - - mSun->setColor(weather.mSunDiscColor); - mSun->adjustTransparency(weather.mGlareView * weather.mSunDiscColor.a()); - - float nextStarsOpacity = weather.mNightFade * weather.mGlareView; - - if (weather.mNight && mStarsOpacity != nextStarsOpacity) + void SkyManager::setSunDirection(const osg::Vec3f& direction) { - mStarsOpacity = nextStarsOpacity; + if (!mCreated) return; - mAtmosphereNightUpdater->setFade(mStarsOpacity); + mSun->setDirection(direction); } - mAtmosphereNightNode->setNodeMask(weather.mNight ? ~0u : 0); - - mPrecipitationAlpha = weather.mPrecipitationAlpha; -} - -float SkyManager::getBaseWindSpeed() const -{ - if (!mCreated) return 0.f; - - return mBaseWindSpeed; -} - -void SkyManager::sunEnable() -{ - if (!mCreated) return; - - mSun->setVisible(true); -} - -void SkyManager::sunDisable() -{ - if (!mCreated) return; - - mSun->setVisible(false); -} - -void SkyManager::setStormDirection(const osg::Vec3f &direction) -{ - mStormDirection = direction; -} - -void SkyManager::setSunDirection(const osg::Vec3f& direction) -{ - if (!mCreated) return; - - mSun->setDirection(direction); -} - -void SkyManager::setMasserState(const MoonState& state) -{ - if(!mCreated) return; - - mMasser->setState(state); -} - -void SkyManager::setSecundaState(const MoonState& state) -{ - if(!mCreated) return; - - mSecunda->setState(state); -} - -void SkyManager::setDate(int day, int month) -{ - mDay = day; - mMonth = month; -} - -void SkyManager::setGlareTimeOfDayFade(float val) -{ - mSun->setGlareTimeOfDayFade(val); -} - -void SkyManager::setWaterHeight(float height) -{ - mUnderwaterSwitch->setWaterLevel(height); -} - -void SkyManager::listAssetsToPreload(std::vector& models, std::vector& textures) -{ - models.emplace_back(Settings::Manager::getString("skyatmosphere", "Models")); - if (mSceneManager->getVFS()->exists(Settings::Manager::getString("skynight02", "Models"))) - models.emplace_back(Settings::Manager::getString("skynight02", "Models")); - models.emplace_back(Settings::Manager::getString("skynight01", "Models")); - models.emplace_back(Settings::Manager::getString("skyclouds", "Models")); - - models.emplace_back(Settings::Manager::getString("weatherashcloud", "Models")); - models.emplace_back(Settings::Manager::getString("weatherblightcloud", "Models")); - models.emplace_back(Settings::Manager::getString("weathersnow", "Models")); - models.emplace_back(Settings::Manager::getString("weatherblizzard", "Models")); - - textures.emplace_back("textures/tx_mooncircle_full_s.dds"); - textures.emplace_back("textures/tx_mooncircle_full_m.dds"); - - textures.emplace_back("textures/tx_masser_new.dds"); - textures.emplace_back("textures/tx_masser_one_wax.dds"); - textures.emplace_back("textures/tx_masser_half_wax.dds"); - textures.emplace_back("textures/tx_masser_three_wax.dds"); - textures.emplace_back("textures/tx_masser_one_wan.dds"); - textures.emplace_back("textures/tx_masser_half_wan.dds"); - textures.emplace_back("textures/tx_masser_three_wan.dds"); - textures.emplace_back("textures/tx_masser_full.dds"); - - textures.emplace_back("textures/tx_secunda_new.dds"); - textures.emplace_back("textures/tx_secunda_one_wax.dds"); - textures.emplace_back("textures/tx_secunda_half_wax.dds"); - textures.emplace_back("textures/tx_secunda_three_wax.dds"); - textures.emplace_back("textures/tx_secunda_one_wan.dds"); - textures.emplace_back("textures/tx_secunda_half_wan.dds"); - textures.emplace_back("textures/tx_secunda_three_wan.dds"); - textures.emplace_back("textures/tx_secunda_full.dds"); - - textures.emplace_back("textures/tx_sun_05.dds"); - textures.emplace_back("textures/tx_sun_flash_grey_05.dds"); - - textures.emplace_back("textures/tx_raindrop_01.dds"); -} - -void SkyManager::setWaterEnabled(bool enabled) -{ - mUnderwaterSwitch->setEnabled(enabled); -} + void SkyManager::setMasserState(const MoonState& state) + { + if(!mCreated) return; + mMasser->setState(state); + } + + void SkyManager::setSecundaState(const MoonState& state) + { + if(!mCreated) return; + + mSecunda->setState(state); + } + + void SkyManager::setDate(int day, int month) + { + mDay = day; + mMonth = month; + } + + void SkyManager::setGlareTimeOfDayFade(float val) + { + mSun->setGlareTimeOfDayFade(val); + } + + void SkyManager::setWaterHeight(float height) + { + mUnderwaterSwitch->setWaterLevel(height); + } + + void SkyManager::listAssetsToPreload(std::vector& models, std::vector& textures) + { + models.emplace_back(Settings::Manager::getString("skyatmosphere", "Models")); + if (mSceneManager->getVFS()->exists(Settings::Manager::getString("skynight02", "Models"))) + models.emplace_back(Settings::Manager::getString("skynight02", "Models")); + models.emplace_back(Settings::Manager::getString("skynight01", "Models")); + models.emplace_back(Settings::Manager::getString("skyclouds", "Models")); + + models.emplace_back(Settings::Manager::getString("weatherashcloud", "Models")); + models.emplace_back(Settings::Manager::getString("weatherblightcloud", "Models")); + models.emplace_back(Settings::Manager::getString("weathersnow", "Models")); + models.emplace_back(Settings::Manager::getString("weatherblizzard", "Models")); + + textures.emplace_back("textures/tx_mooncircle_full_s.dds"); + textures.emplace_back("textures/tx_mooncircle_full_m.dds"); + + textures.emplace_back("textures/tx_masser_new.dds"); + textures.emplace_back("textures/tx_masser_one_wax.dds"); + textures.emplace_back("textures/tx_masser_half_wax.dds"); + textures.emplace_back("textures/tx_masser_three_wax.dds"); + textures.emplace_back("textures/tx_masser_one_wan.dds"); + textures.emplace_back("textures/tx_masser_half_wan.dds"); + textures.emplace_back("textures/tx_masser_three_wan.dds"); + textures.emplace_back("textures/tx_masser_full.dds"); + + textures.emplace_back("textures/tx_secunda_new.dds"); + textures.emplace_back("textures/tx_secunda_one_wax.dds"); + textures.emplace_back("textures/tx_secunda_half_wax.dds"); + textures.emplace_back("textures/tx_secunda_three_wax.dds"); + textures.emplace_back("textures/tx_secunda_one_wan.dds"); + textures.emplace_back("textures/tx_secunda_half_wan.dds"); + textures.emplace_back("textures/tx_secunda_three_wan.dds"); + textures.emplace_back("textures/tx_secunda_full.dds"); + + textures.emplace_back("textures/tx_sun_05.dds"); + textures.emplace_back("textures/tx_sun_flash_grey_05.dds"); + + textures.emplace_back("textures/tx_raindrop_01.dds"); + } + + void SkyManager::setWaterEnabled(bool enabled) + { + mUnderwaterSwitch->setEnabled(enabled); + } } diff --git a/apps/openmw/mwrender/sky.hpp b/apps/openmw/mwrender/sky.hpp index f8c501dda6..227dee213b 100644 --- a/apps/openmw/mwrender/sky.hpp +++ b/apps/openmw/mwrender/sky.hpp @@ -8,10 +8,7 @@ #include #include -namespace osg -{ - class Camera; -} +#include "skyutil.hpp" namespace osg { @@ -19,6 +16,7 @@ namespace osg class Node; class Material; class PositionAttitudeTransform; + class Camera; } namespace osgParticle @@ -45,80 +43,6 @@ namespace MWRender class AlphaFader; class UnderwaterSwitchCallback; - struct WeatherResult - { - std::string mCloudTexture; - std::string mNextCloudTexture; - float mCloudBlendFactor; - - osg::Vec4f mFogColor; - - osg::Vec4f mAmbientColor; - - osg::Vec4f mSkyColor; - - // sun light color - osg::Vec4f mSunColor; - - // alpha is the sun transparency - osg::Vec4f mSunDiscColor; - - float mFogDepth; - - float mDLFogFactor; - float mDLFogOffset; - - float mWindSpeed; - float mBaseWindSpeed; - float mCurrentWindSpeed; - float mNextWindSpeed; - - float mCloudSpeed; - - float mGlareView; - - bool mNight; // use night skybox - float mNightFade; // fading factor for night skybox - - bool mIsStorm; - - std::string mAmbientLoopSoundID; - float mAmbientSoundVolume; - - std::string mParticleEffect; - std::string mRainEffect; - float mPrecipitationAlpha; - - float mRainDiameter; - float mRainMinHeight; - float mRainMaxHeight; - float mRainSpeed; - float mRainEntranceSpeed; - int mRainMaxRaindrops; - }; - - struct MoonState - { - enum class Phase - { - Full = 0, - WaningGibbous, - ThirdQuarter, - WaningCrescent, - New, - WaxingCrescent, - FirstQuarter, - WaxingGibbous, - Unspecified - }; - - float mRotationFromHorizon; - float mRotationFromNorth; - Phase mPhase; - float mShadowBlend; - float mMoonAlpha; - }; - ///@brief The SkyManager handles rendering of the sky domes, celestial bodies as well as other objects that need to be rendered /// relative to the camera (e.g. weather particle effects) class SkyManager @@ -162,7 +86,7 @@ namespace MWRender void setRainSpeed(float speed); - void setStormDirection(const osg::Vec3f& direction); + void setStormParticleDirection(const osg::Vec3f& direction); void setSunDirection(const osg::Vec3f& direction); @@ -203,12 +127,12 @@ namespace MWRender osg::ref_ptr mParticleEffect; osg::ref_ptr mUnderwaterSwitch; - osg::ref_ptr mCloudNode; + osg::ref_ptr mCloudNode; osg::ref_ptr mCloudUpdater; - osg::ref_ptr mCloudUpdater2; - osg::ref_ptr mCloudMesh; - osg::ref_ptr mCloudMesh2; + osg::ref_ptr mNextCloudUpdater; + osg::ref_ptr mCloudMesh; + osg::ref_ptr mNextCloudMesh; osg::ref_ptr mAtmosphereDay; @@ -239,7 +163,10 @@ namespace MWRender float mRainTimer; + // particle system rotation is independent of cloud rotation internally + osg::Vec3f mStormParticleDirection; osg::Vec3f mStormDirection; + osg::Vec3f mNextStormDirection; // remember some settings so we don't have to apply them again if they didn't change std::string mClouds; @@ -275,4 +202,4 @@ namespace MWRender }; } -#endif // GAME_RENDER_SKY_H +#endif diff --git a/apps/openmw/mwrender/skyutil.cpp b/apps/openmw/mwrender/skyutil.cpp new file mode 100644 index 0000000000..ae689547fd --- /dev/null +++ b/apps/openmw/mwrender/skyutil.cpp @@ -0,0 +1,1142 @@ +#include "skyutil.hpp" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include + +#include +#include + +#include + +#include + +#include + +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" + +#include "../mwworld/weather.hpp" + +#include "vismask.hpp" +#include "renderbin.hpp" + +namespace +{ + enum class Pass + { + Atmosphere, + Atmosphere_Night, + Clouds, + Moon, + Sun, + Sunflash_Query, + Sunglare, + }; + + osg::ref_ptr createTexturedQuad(int numUvSets = 1, float scale = 1.f) + { + osg::ref_ptr geom = new osg::Geometry; + + osg::ref_ptr verts = new osg::Vec3Array; + verts->push_back(osg::Vec3f(-0.5 * scale, -0.5 * scale, 0)); + verts->push_back(osg::Vec3f(-0.5 * scale, 0.5 * scale, 0)); + verts->push_back(osg::Vec3f(0.5 * scale, 0.5 * scale, 0)); + verts->push_back(osg::Vec3f(0.5 * scale, -0.5 * scale, 0)); + + geom->setVertexArray(verts); + + osg::ref_ptr texcoords = new osg::Vec2Array; + texcoords->push_back(osg::Vec2f(0, 1)); + texcoords->push_back(osg::Vec2f(0, 0)); + texcoords->push_back(osg::Vec2f(1, 0)); + texcoords->push_back(osg::Vec2f(1, 1)); + + osg::ref_ptr colors = new osg::Vec4Array; + colors->push_back(osg::Vec4(1.f, 1.f, 1.f, 1.f)); + geom->setColorArray(colors, osg::Array::BIND_OVERALL); + + for (int i=0; isetTexCoordArray(i, texcoords, osg::Array::BIND_PER_VERTEX); + + geom->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::QUADS,0,4)); + + return geom; + } + + struct DummyComputeBoundCallback : osg::Node::ComputeBoundingSphereCallback + { + osg::BoundingSphere computeBound(const osg::Node& node) const override + { + return osg::BoundingSphere(); + } + }; +} + +namespace MWRender +{ + osg::ref_ptr createAlphaTrackingUnlitMaterial() + { + osg::ref_ptr mat = new osg::Material; + mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 1.f)); + mat->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 1.f)); + mat->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(1.f, 1.f, 1.f, 1.f)); + mat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 0.f)); + mat->setColorMode(osg::Material::DIFFUSE); + return mat; + } + + osg::ref_ptr createUnlitMaterial() + { + osg::ref_ptr mat = new osg::Material; + mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 1.f)); + mat->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 1.f)); + mat->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(1.f, 1.f, 1.f, 1.f)); + mat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 0.f)); + mat->setColorMode(osg::Material::OFF); + return mat; + } + + class SunUpdater : public SceneUtil::StateSetUpdater + { + public: + osg::Vec4f mColor; + + SunUpdater() + : mColor(1.f, 1.f, 1.f, 1.f) + { } + + void setDefaults(osg::StateSet* stateset) override + { + stateset->setAttributeAndModes(MWRender::createUnlitMaterial(), osg::StateAttribute::ON); + } + + void apply(osg::StateSet* stateset, osg::NodeVisitor*) override + { + osg::Material* mat = static_cast(stateset->getAttribute(osg::StateAttribute::MATERIAL)); + mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0,0,0,mColor.a())); + mat->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(mColor.r(), mColor.g(), mColor.b(), 1)); + } + }; + + OcclusionCallback::OcclusionCallback(osg::ref_ptr oqnVisible, osg::ref_ptr oqnTotal) + : mOcclusionQueryVisiblePixels(oqnVisible) + , mOcclusionQueryTotalPixels(oqnTotal) + { } + + float OcclusionCallback::getVisibleRatio (osg::Camera* camera) + { + int visible = mOcclusionQueryVisiblePixels->getQueryGeometry()->getNumPixels(camera); + int total = mOcclusionQueryTotalPixels->getQueryGeometry()->getNumPixels(camera); + + float visibleRatio = 0.f; + if (total > 0) + visibleRatio = static_cast(visible) / static_cast(total); + + float dt = MWBase::Environment::get().getFrameDuration(); + + float lastRatio = mLastRatio[osg::observer_ptr(camera)]; + + float change = dt*10; + + if (visibleRatio > lastRatio) + visibleRatio = std::min(visibleRatio, lastRatio + change); + else + visibleRatio = std::max(visibleRatio, lastRatio - change); + + mLastRatio[osg::observer_ptr(camera)] = visibleRatio; + + return visibleRatio; + } + + /// SunFlashCallback handles fading/scaling of a node depending on occlusion query result. Must be attached as a cull callback. + class SunFlashCallback : public OcclusionCallback, public SceneUtil::NodeCallback + { + public: + SunFlashCallback(osg::ref_ptr oqnVisible, osg::ref_ptr oqnTotal) + : OcclusionCallback(oqnVisible, oqnTotal) + , mGlareView(1.f) + { } + + void operator()(osg::Node* node, osgUtil::CullVisitor* cv) + { + float visibleRatio = getVisibleRatio(cv->getCurrentCamera()); + + osg::ref_ptr stateset; + + if (visibleRatio > 0.f) + { + const float fadeThreshold = 0.1; + if (visibleRatio < fadeThreshold) + { + float fade = 1.f - (fadeThreshold - visibleRatio) / fadeThreshold; + osg::ref_ptr mat (MWRender::createUnlitMaterial()); + mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0,0,0,fade*mGlareView)); + stateset = new osg::StateSet; + stateset->setAttributeAndModes(mat, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + } + else if (visibleRatio < 1.f) + { + const float threshold = 0.6; + visibleRatio = visibleRatio * (1.f - threshold) + threshold; + } + } + + float scale = visibleRatio; + + if (scale == 0.f) + { + // no traverse + return; + } + else if (scale == 1.f) + traverse(node, cv); + else + { + osg::Matrix modelView = *cv->getModelViewMatrix(); + + modelView.preMultScale(osg::Vec3f(scale, scale, scale)); + + if (stateset) + cv->pushStateSet(stateset); + + cv->pushModelViewMatrix(new osg::RefMatrix(modelView), osg::Transform::RELATIVE_RF); + + traverse(node, cv); + + cv->popModelViewMatrix(); + + if (stateset) + cv->popStateSet(); + } + } + + void setGlareView(float value) + { + mGlareView = value; + } + + private: + float mGlareView; + }; + + /// SunGlareCallback controls a full-screen glare effect depending on occlusion query result and the angle between sun and camera. + /// Must be attached as a cull callback to the node above the glare node. + class SunGlareCallback : public OcclusionCallback, public SceneUtil::NodeCallback + { + public: + SunGlareCallback(osg::ref_ptr oqnVisible, osg::ref_ptr oqnTotal, + osg::ref_ptr sunTransform) + : OcclusionCallback(oqnVisible, oqnTotal) + , mSunTransform(sunTransform) + , mTimeOfDayFade(1.f) + , mGlareView(1.f) + { + mColor = Fallback::Map::getColour("Weather_Sun_Glare_Fader_Color"); + mSunGlareFaderMax = Fallback::Map::getFloat("Weather_Sun_Glare_Fader_Max"); + mSunGlareFaderAngleMax = Fallback::Map::getFloat("Weather_Sun_Glare_Fader_Angle_Max"); + + // Replicating a design flaw in MW. The color was being set on both ambient and emissive properties, which multiplies the result by two, + // then finally gets clamped by the fixed function pipeline. With the default INI settings, only the red component gets clamped, + // so the resulting color looks more orange than red. + mColor *= 2; + for (int i=0; i<3; ++i) + mColor[i] = std::min(1.f, mColor[i]); + } + + void operator ()(osg::Node* node, osgUtil::CullVisitor* cv) + { + float angleRadians = getAngleToSunInRadians(*cv->getCurrentRenderStage()->getInitialViewMatrix()); + float visibleRatio = getVisibleRatio(cv->getCurrentCamera()); + + const float angleMaxRadians = osg::DegreesToRadians(mSunGlareFaderAngleMax); + + float value = 1.f - std::min(1.f, angleRadians / angleMaxRadians); + float fade = value * mSunGlareFaderMax; + + fade *= mTimeOfDayFade * mGlareView * visibleRatio; + + if (fade == 0.f) + { + // no traverse + return; + + osg::Vec4 v4; + osg::Vec4 v3; + v4 = v3; + } + else + { + osg::ref_ptr stateset = new osg::StateSet; + + osg::ref_ptr mat = MWRender::createUnlitMaterial(); + + mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0,0,0,fade)); + mat->setEmission(osg::Material::FRONT_AND_BACK, mColor); + + stateset->setAttributeAndModes(mat, osg::StateAttribute::ON); + + cv->pushStateSet(stateset); + traverse(node, cv); + cv->popStateSet(); + } + } + + void setTimeOfDayFade(float val) + { + mTimeOfDayFade = val; + } + + void setGlareView(float glareView) + { + mGlareView = glareView; + } + + private: + float getAngleToSunInRadians(const osg::Matrix& viewMatrix) const + { + osg::Vec3d eye, center, up; + viewMatrix.getLookAt(eye, center, up); + + osg::Vec3d forward = center - eye; + osg::Vec3d sun = mSunTransform->getPosition(); + + forward.normalize(); + sun.normalize(); + float angleRadians = std::acos(forward * sun); + return angleRadians; + } + + osg::ref_ptr mSunTransform; + float mTimeOfDayFade; + float mGlareView; + osg::Vec4f mColor; + float mSunGlareFaderMax; + float mSunGlareFaderAngleMax; + }; + + struct MoonUpdater : SceneUtil::StateSetUpdater + { + Resource::ImageManager& mImageManager; + osg::ref_ptr mPhaseTex; + osg::ref_ptr mCircleTex; + float mTransparency; + float mShadowBlend; + osg::Vec4f mAtmosphereColor; + osg::Vec4f mMoonColor; + bool mForceShaders; + + MoonUpdater(Resource::ImageManager& imageManager, bool forceShaders) + : mImageManager(imageManager) + , mPhaseTex() + , mCircleTex() + , mTransparency(1.0f) + , mShadowBlend(1.0f) + , mAtmosphereColor(1.0f, 1.0f, 1.0f, 1.0f) + , mMoonColor(1.0f, 1.0f, 1.0f, 1.0f) + , mForceShaders(forceShaders) + { } + + void setDefaults(osg::StateSet* stateset) override + { + if (mForceShaders) + { + stateset->addUniform(new osg::Uniform("pass", static_cast(Pass::Moon)), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + stateset->setTextureAttributeAndModes(0, mPhaseTex, osg::StateAttribute::ON); + stateset->setTextureAttributeAndModes(1, mCircleTex, osg::StateAttribute::ON); + stateset->setTextureMode(0, GL_TEXTURE_2D, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + stateset->setTextureMode(1, GL_TEXTURE_2D, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + stateset->addUniform(new osg::Uniform("moonBlend", osg::Vec4f{})); + stateset->addUniform(new osg::Uniform("atmosphereFade", osg::Vec4f{})); + stateset->addUniform(new osg::Uniform("diffuseMap", 0)); + stateset->addUniform(new osg::Uniform("maskMap", 1)); + stateset->setAttributeAndModes(MWRender::createUnlitMaterial(), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + } + else + { + stateset->setTextureAttributeAndModes(0, mPhaseTex, osg::StateAttribute::ON); + osg::ref_ptr texEnv = new osg::TexEnvCombine; + texEnv->setCombine_RGB(osg::TexEnvCombine::MODULATE); + texEnv->setSource0_RGB(osg::TexEnvCombine::CONSTANT); + texEnv->setSource1_RGB(osg::TexEnvCombine::TEXTURE); + texEnv->setConstantColor(osg::Vec4f(1.f, 0.f, 0.f, 1.f)); // mShadowBlend * mMoonColor + stateset->setTextureAttributeAndModes(0, texEnv, osg::StateAttribute::ON); + + stateset->setTextureAttributeAndModes(1, mCircleTex, osg::StateAttribute::ON); + osg::ref_ptr texEnv2 = new osg::TexEnvCombine; + texEnv2->setCombine_RGB(osg::TexEnvCombine::ADD); + texEnv2->setCombine_Alpha(osg::TexEnvCombine::MODULATE); + texEnv2->setSource0_Alpha(osg::TexEnvCombine::TEXTURE); + texEnv2->setSource1_Alpha(osg::TexEnvCombine::CONSTANT); + texEnv2->setSource0_RGB(osg::TexEnvCombine::PREVIOUS); + texEnv2->setSource1_RGB(osg::TexEnvCombine::CONSTANT); + texEnv2->setConstantColor(osg::Vec4f(0.f, 0.f, 0.f, 1.f)); // mAtmosphereColor.rgb, mTransparency + stateset->setTextureAttributeAndModes(1, texEnv2, osg::StateAttribute::ON); + stateset->setAttributeAndModes(MWRender::createUnlitMaterial(), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + } + } + + void apply(osg::StateSet* stateset, osg::NodeVisitor*) override + { + if (mForceShaders) + { + stateset->setTextureAttribute(0, mPhaseTex, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + stateset->setTextureAttribute(1, mCircleTex, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + + if (auto* uMoonBlend = stateset->getUniform("moonBlend")) + uMoonBlend->set(mMoonColor * mShadowBlend); + if (auto* uAtmosphereFade = stateset->getUniform("atmosphereFade")) + uAtmosphereFade->set(osg::Vec4f(mAtmosphereColor.x(), mAtmosphereColor.y(), mAtmosphereColor.z(), mTransparency)); + } + else + { + osg::TexEnvCombine* texEnv = static_cast(stateset->getTextureAttribute(0, osg::StateAttribute::TEXENV)); + texEnv->setConstantColor(mMoonColor * mShadowBlend); + + osg::TexEnvCombine* texEnv2 = static_cast(stateset->getTextureAttribute(1, osg::StateAttribute::TEXENV)); + texEnv2->setConstantColor(osg::Vec4f(mAtmosphereColor.x(), mAtmosphereColor.y(), mAtmosphereColor.z(), mTransparency)); + } + } + + void setTextures(const std::string& phaseTex, const std::string& circleTex) + { + mPhaseTex = new osg::Texture2D(mImageManager.getImage(phaseTex)); + mPhaseTex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); + mPhaseTex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); + mCircleTex = new osg::Texture2D(mImageManager.getImage(circleTex)); + mCircleTex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); + mCircleTex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); + + reset(); + } + }; + + class CameraRelativeTransformCullCallback : public SceneUtil::NodeCallback + { + public: + void operator() (osg::Node* node, osgUtil::CullVisitor* cv) + { + // XXX have to remove unwanted culling plane of the water reflection camera + + // Remove all planes that aren't from the standard frustum + unsigned int numPlanes = 4; + if (cv->getCullingMode() & osg::CullSettings::NEAR_PLANE_CULLING) + ++numPlanes; + if (cv->getCullingMode() & osg::CullSettings::FAR_PLANE_CULLING) + ++numPlanes; + + unsigned int mask = 0x1; + unsigned int resultMask = cv->getProjectionCullingStack().back().getFrustum().getResultMask(); + for (unsigned int i=0; igetProjectionCullingStack().back().getFrustum().getPlaneList().size(); ++i) + { + if (i >= numPlanes) + { + // turn off this culling plane + resultMask &= (~mask); + } + + mask <<= 1; + } + + cv->getProjectionCullingStack().back().getFrustum().setResultMask(resultMask); + cv->getCurrentCullingSet().getFrustum().setResultMask(resultMask); + + cv->getProjectionCullingStack().back().pushCurrentMask(); + cv->getCurrentCullingSet().pushCurrentMask(); + + traverse(node, cv); + + cv->getProjectionCullingStack().back().popCurrentMask(); + cv->getCurrentCullingSet().popCurrentMask(); + } + }; + + void AtmosphereUpdater::setEmissionColor(const osg::Vec4f& emissionColor) + { + mEmissionColor = emissionColor; + } + + void AtmosphereUpdater::setDefaults(osg::StateSet* stateset) + { + stateset->setAttributeAndModes(createAlphaTrackingUnlitMaterial(), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + stateset->addUniform(new osg::Uniform("pass", static_cast(Pass::Atmosphere)), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + } + + void AtmosphereUpdater::apply(osg::StateSet* stateset, osg::NodeVisitor* /*nv*/) + { + osg::Material* mat = static_cast(stateset->getAttribute(osg::StateAttribute::MATERIAL)); + mat->setEmission(osg::Material::FRONT_AND_BACK, mEmissionColor); + } + + AtmosphereNightUpdater::AtmosphereNightUpdater(Resource::ImageManager* imageManager, bool forceShaders) + : mColor(osg::Vec4f(0,0,0,0)) + , mTexture(new osg::Texture2D(imageManager->getWarningImage())) + , mForceShaders(forceShaders) + { } + + void AtmosphereNightUpdater::setFade(float fade) + { + mColor.a() = fade; + } + + void AtmosphereNightUpdater::setDefaults(osg::StateSet* stateset) + { + if (mForceShaders) + { + stateset->addUniform(new osg::Uniform("opacity", 0.f), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + stateset->addUniform(new osg::Uniform("pass", static_cast(Pass::Atmosphere_Night)), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + } + else + { + osg::ref_ptr texEnv = new osg::TexEnvCombine; + texEnv->setCombine_Alpha(osg::TexEnvCombine::MODULATE); + texEnv->setSource0_Alpha(osg::TexEnvCombine::PREVIOUS); + texEnv->setSource1_Alpha(osg::TexEnvCombine::CONSTANT); + texEnv->setCombine_RGB(osg::TexEnvCombine::REPLACE); + texEnv->setSource0_RGB(osg::TexEnvCombine::PREVIOUS); + + stateset->setTextureAttributeAndModes(1, mTexture, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + stateset->setTextureAttributeAndModes(1, texEnv, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + } + } + + void AtmosphereNightUpdater::apply(osg::StateSet* stateset, osg::NodeVisitor* /*nv*/) + { + if (mForceShaders) + { + stateset->getUniform("opacity")->set(mColor.a()); + } + else + { + osg::TexEnvCombine* texEnv = static_cast(stateset->getTextureAttribute(1, osg::StateAttribute::TEXENV)); + texEnv->setConstantColor(mColor); + } + } + + CloudUpdater::CloudUpdater(bool forceShaders) + : mOpacity(0.f) + , mForceShaders(forceShaders) + { } + + void CloudUpdater::setTexture(osg::ref_ptr texture) + { + mTexture = texture; + } + + void CloudUpdater::setEmissionColor(const osg::Vec4f& emissionColor) + { + mEmissionColor = emissionColor; + } + + void CloudUpdater::setOpacity(float opacity) + { + mOpacity = opacity; + } + + void CloudUpdater::setTextureCoord(float timer) + { + mTexMat = osg::Matrixf::translate(osg::Vec3f(0.f, -timer, 0.f)); + } + + void CloudUpdater::setDefaults(osg::StateSet *stateset) + { + stateset->setAttribute(createAlphaTrackingUnlitMaterial(), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + + osg::ref_ptr texmat = new osg::TexMat; + stateset->setTextureAttributeAndModes(0, texmat, osg::StateAttribute::ON); + + if (mForceShaders) + { + stateset->setTextureAttribute(0, mTexture, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + + stateset->addUniform(new osg::Uniform("opacity", 1.f), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + stateset->addUniform(new osg::Uniform("pass", static_cast(Pass::Clouds)), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + } + else + { + stateset->setTextureAttributeAndModes(1, texmat, osg::StateAttribute::ON); + // need to set opacity on a separate texture unit, diffuse alpha is used by the vertex colors already + osg::ref_ptr texEnvCombine = new osg::TexEnvCombine; + texEnvCombine->setSource0_RGB(osg::TexEnvCombine::PREVIOUS); + texEnvCombine->setSource0_Alpha(osg::TexEnvCombine::PREVIOUS); + texEnvCombine->setSource1_Alpha(osg::TexEnvCombine::CONSTANT); + texEnvCombine->setConstantColor(osg::Vec4f(1,1,1,1)); + texEnvCombine->setCombine_Alpha(osg::TexEnvCombine::MODULATE); + texEnvCombine->setCombine_RGB(osg::TexEnvCombine::REPLACE); + + stateset->setTextureAttributeAndModes(1, texEnvCombine, osg::StateAttribute::ON); + + stateset->setTextureMode(0, GL_TEXTURE_2D, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + stateset->setTextureMode(1, GL_TEXTURE_2D, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + } + } + + void CloudUpdater::apply(osg::StateSet *stateset, osg::NodeVisitor *nv) + { + stateset->setTextureAttribute(0, mTexture, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + + osg::Material* mat = static_cast(stateset->getAttribute(osg::StateAttribute::MATERIAL)); + mat->setEmission(osg::Material::FRONT_AND_BACK, mEmissionColor); + + osg::TexMat* texMat = static_cast(stateset->getTextureAttribute(0, osg::StateAttribute::TEXMAT)); + texMat->setMatrix(mTexMat); + + if (mForceShaders) + { + stateset->getUniform("opacity")->set(mOpacity); + } + else + { + stateset->setTextureAttribute(1, mTexture, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + + osg::TexEnvCombine* texEnv = static_cast(stateset->getTextureAttribute(1, osg::StateAttribute::TEXENV)); + texEnv->setConstantColor(osg::Vec4f(1,1,1,mOpacity)); + } + } + + CameraRelativeTransform::CameraRelativeTransform() + { + // Culling works in node-local space, not in camera space, so we can't cull this node correctly + // That's not a problem though, children of this node can be culled just fine + // Just make sure you do not place a CameraRelativeTransform deep in the scene graph + setCullingActive(false); + + addCullCallback(new CameraRelativeTransformCullCallback); + } + + CameraRelativeTransform::CameraRelativeTransform(const CameraRelativeTransform& copy, const osg::CopyOp& copyop) + : osg::Transform(copy, copyop) + { } + + const osg::Vec3f& CameraRelativeTransform::getLastViewPoint() const + { + return mViewPoint; + } + + bool CameraRelativeTransform::computeLocalToWorldMatrix(osg::Matrix& matrix, osg::NodeVisitor* nv) const + { + if (nv->getVisitorType() == osg::NodeVisitor::CULL_VISITOR) + { + mViewPoint = static_cast(nv)->getViewPoint(); + } + + if (_referenceFrame==RELATIVE_RF) + { + matrix.setTrans(osg::Vec3f(0.f,0.f,0.f)); + return false; + } + else // absolute + { + matrix.makeIdentity(); + return true; + } + } + + osg::BoundingSphere CameraRelativeTransform::computeBound() const + { + return osg::BoundingSphere(); + } + + UnderwaterSwitchCallback::UnderwaterSwitchCallback(CameraRelativeTransform* cameraRelativeTransform) + : mCameraRelativeTransform(cameraRelativeTransform) + , mEnabled(true) + , mWaterLevel(0.f) + { } + + bool UnderwaterSwitchCallback::isUnderwater() + { + osg::Vec3f viewPoint = mCameraRelativeTransform->getLastViewPoint(); + return mEnabled && viewPoint.z() < mWaterLevel; + } + + void UnderwaterSwitchCallback::operator()(osg::Node* node, osg::NodeVisitor* nv) + { + if (isUnderwater()) + return; + + traverse(node, nv); + } + + void UnderwaterSwitchCallback::setEnabled(bool enabled) + { + mEnabled = enabled; + } + void UnderwaterSwitchCallback::setWaterLevel(float waterLevel) + { + mWaterLevel = waterLevel; + } + + const float CelestialBody::mDistance = 1000.0f; + + CelestialBody::CelestialBody(osg::Group* parentNode, float scaleFactor, int numUvSets, unsigned int visibleMask) + : mVisibleMask(visibleMask) + { + mGeom = createTexturedQuad(numUvSets); + mGeom->getOrCreateStateSet(); + mTransform = new osg::PositionAttitudeTransform; + mTransform->setNodeMask(mVisibleMask); + mTransform->setScale(osg::Vec3f(450,450,450) * scaleFactor); + mTransform->addChild(mGeom); + + parentNode->addChild(mTransform); + } + + void CelestialBody::setVisible(bool visible) + { + mTransform->setNodeMask(visible ? mVisibleMask : 0); + } + + Sun::Sun(osg::Group* parentNode, Resource::ImageManager& imageManager) + : CelestialBody(parentNode, 1.0f, 1, Mask_Sun) + , mUpdater(new SunUpdater) + { + mTransform->addUpdateCallback(mUpdater); + + osg::ref_ptr sunTex = new osg::Texture2D(imageManager.getImage("textures/tx_sun_05.dds")); + sunTex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); + sunTex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); + sunTex->setName("diffuseMap"); + + mGeom->getOrCreateStateSet()->setTextureAttributeAndModes(0, sunTex, osg::StateAttribute::ON); + mGeom->getOrCreateStateSet()->addUniform(new osg::Uniform("pass", static_cast(Pass::Sun)), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + + osg::ref_ptr queryNode = new osg::Group; + // Need to render after the world geometry so we can correctly test for occlusions + osg::StateSet* stateset = queryNode->getOrCreateStateSet(); + stateset->setRenderBinDetails(RenderBin_OcclusionQuery, "RenderBin"); + stateset->setNestRenderBins(false); + // Set up alpha testing on the occlusion testing subgraph, that way we can get the occlusion tested fragments to match the circular shape of the sun + osg::ref_ptr alphaFunc = new osg::AlphaFunc; + alphaFunc->setFunction(osg::AlphaFunc::GREATER, 0.8); + stateset->setAttributeAndModes(alphaFunc, osg::StateAttribute::ON); + stateset->setTextureAttributeAndModes(0, sunTex, osg::StateAttribute::ON); + stateset->setAttributeAndModes(createUnlitMaterial(), osg::StateAttribute::ON); + stateset->addUniform(new osg::Uniform("pass", static_cast(Pass::Sunflash_Query)), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + + // Disable writing to the color buffer. We are using this geometry for visibility tests only. + osg::ref_ptr colormask = new osg::ColorMask(0, 0, 0, 0); + stateset->setAttributeAndModes(colormask, osg::StateAttribute::ON); + + mTransform->addChild(queryNode); + + mOcclusionQueryVisiblePixels = createOcclusionQueryNode(queryNode, true); + mOcclusionQueryTotalPixels = createOcclusionQueryNode(queryNode, false); + + createSunFlash(imageManager); + createSunGlare(); + } + + Sun::~Sun() + { + mTransform->removeUpdateCallback(mUpdater); + destroySunFlash(); + destroySunGlare(); + } + + void Sun::setColor(const osg::Vec4f& color) + { + mUpdater->mColor.r() = color.r(); + mUpdater->mColor.g() = color.g(); + mUpdater->mColor.b() = color.b(); + } + + void Sun::adjustTransparency(const float ratio) + { + mUpdater->mColor.a() = ratio; + if (mSunGlareCallback) + mSunGlareCallback->setGlareView(ratio); + if (mSunFlashCallback) + mSunFlashCallback->setGlareView(ratio); + } + + void Sun::setDirection(const osg::Vec3f& direction) + { + osg::Vec3f normalizedDirection = direction / direction.length(); + mTransform->setPosition(normalizedDirection * mDistance); + + osg::Quat quat; + quat.makeRotate(osg::Vec3f(0.0f, 0.0f, 1.0f), normalizedDirection); + mTransform->setAttitude(quat); + } + + void Sun::setGlareTimeOfDayFade(float val) + { + if (mSunGlareCallback) + mSunGlareCallback->setTimeOfDayFade(val); + } + + osg::ref_ptr Sun::createOcclusionQueryNode(osg::Group* parent, bool queryVisible) + { + osg::ref_ptr oqn = new osg::OcclusionQueryNode; + oqn->setQueriesEnabled(true); + +#if OSG_VERSION_GREATER_OR_EQUAL(3, 6, 5) + // With OSG 3.6.5, the method of providing user defined query geometry has been completely replaced + osg::ref_ptr queryGeom = new osg::QueryGeometry(oqn->getName()); +#else + osg::ref_ptr queryGeom = oqn->getQueryGeometry(); +#endif + + // Make it fast! A DYNAMIC query geometry means we can't break frame until the flare is rendered (which is rendered after all the other geometry, + // so that would be pretty bad). STATIC should be safe, since our node's local bounds are static, thus computeBounds() which modifies the queryGeometry + // is only called once. + // Note the debug geometry setDebugDisplay(true) is always DYNAMIC and that can't be changed, not a big deal. + queryGeom->setDataVariance(osg::Object::STATIC); + + // Set up the query geometry to match the actual sun's rendering shape. osg::OcclusionQueryNode wasn't originally intended to allow this, + // normally it would automatically adjust the query geometry to match the sub graph's bounding box. The below hack is needed to + // circumvent this. + queryGeom->setVertexArray(mGeom->getVertexArray()); + queryGeom->setTexCoordArray(0, mGeom->getTexCoordArray(0), osg::Array::BIND_PER_VERTEX); + queryGeom->removePrimitiveSet(0, queryGeom->getNumPrimitiveSets()); + queryGeom->addPrimitiveSet(mGeom->getPrimitiveSet(0)); + + // Hack to disable unwanted awful code inside OcclusionQueryNode::computeBound. + oqn->setComputeBoundingSphereCallback(new DummyComputeBoundCallback); + // Still need a proper bounding sphere. + oqn->setInitialBound(queryGeom->getBound()); + +#if OSG_VERSION_GREATER_OR_EQUAL(3, 6, 5) + oqn->setQueryGeometry(queryGeom.release()); +#endif + + osg::StateSet* queryStateSet = new osg::StateSet; + if (queryVisible) + { + auto depth = SceneUtil::createDepth(); + // This is a trick to make fragments written by the query always use the maximum depth value, + // without having to retrieve the current far clipping distance. + // We want the sun glare to be "infinitely" far away. + double far = SceneUtil::getReverseZ() ? 0.0 : 1.0; + depth->setZNear(far); + depth->setZFar(far); + depth->setWriteMask(false); + queryStateSet->setAttributeAndModes(depth, osg::StateAttribute::ON); + } + else + { + queryStateSet->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); + } + oqn->setQueryStateSet(queryStateSet); + + parent->addChild(oqn); + + return oqn; + } + + void Sun::createSunFlash(Resource::ImageManager& imageManager) + { + osg::ref_ptr tex = new osg::Texture2D(imageManager.getImage("textures/tx_sun_flash_grey_05.dds")); + tex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); + tex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); + tex->setName("diffuseMap"); + + osg::ref_ptr group (new osg::Group); + + mTransform->addChild(group); + + const float scale = 2.6f; + osg::ref_ptr geom = createTexturedQuad(1, scale); + group->addChild(geom); + + osg::StateSet* stateset = geom->getOrCreateStateSet(); + + stateset->setTextureAttributeAndModes(0, tex, osg::StateAttribute::ON); + stateset->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); + stateset->setRenderBinDetails(RenderBin_SunGlare, "RenderBin"); + stateset->setNestRenderBins(false); + stateset->addUniform(new osg::Uniform("pass", static_cast(Pass::Sun)), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + + mSunFlashNode = group; + + mSunFlashCallback = new SunFlashCallback(mOcclusionQueryVisiblePixels, mOcclusionQueryTotalPixels); + mSunFlashNode->addCullCallback(mSunFlashCallback); + } + + void Sun::destroySunFlash() + { + if (mSunFlashNode) + { + mSunFlashNode->removeCullCallback(mSunFlashCallback); + mSunFlashCallback = nullptr; + } + } + + void Sun::createSunGlare() + { + osg::ref_ptr camera = new osg::Camera; + camera->setProjectionMatrix(osg::Matrix::identity()); + camera->setReferenceFrame(osg::Transform::ABSOLUTE_RF); // add to skyRoot instead? + camera->setViewMatrix(osg::Matrix::identity()); + camera->setClearMask(0); + camera->setRenderOrder(osg::Camera::NESTED_RENDER); + camera->setAllowEventFocus(false); + + osg::ref_ptr geom = osg::createTexturedQuadGeometry(osg::Vec3f(-1,-1,0), osg::Vec3f(2,0,0), osg::Vec3f(0,2,0)); + camera->addChild(geom); + + osg::StateSet* stateset = geom->getOrCreateStateSet(); + + stateset->setRenderBinDetails(RenderBin_SunGlare, "RenderBin"); + stateset->setNestRenderBins(false); + stateset->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); + stateset->addUniform(new osg::Uniform("pass", static_cast(Pass::Sunglare)), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + + // set up additive blending + osg::ref_ptr blendFunc = new osg::BlendFunc; + blendFunc->setSource(osg::BlendFunc::SRC_ALPHA); + blendFunc->setDestination(osg::BlendFunc::ONE); + stateset->setAttributeAndModes(blendFunc, osg::StateAttribute::ON); + + mSunGlareCallback = new SunGlareCallback(mOcclusionQueryVisiblePixels, mOcclusionQueryTotalPixels, mTransform); + mSunGlareNode = camera; + + mSunGlareNode->addCullCallback(mSunGlareCallback); + + mTransform->addChild(camera); + } + + void Sun::destroySunGlare() + { + if (mSunGlareNode) + { + mSunGlareNode->removeCullCallback(mSunGlareCallback); + mSunGlareCallback = nullptr; + } + } + + Moon::Moon(osg::Group* parentNode, Resource::SceneManager& sceneManager, float scaleFactor, Type type) + : CelestialBody(parentNode, scaleFactor, 2) + , mType(type) + , mPhase(MoonState::Phase::Unspecified) + , mUpdater(new MoonUpdater(*sceneManager.getImageManager(), sceneManager.getForceShaders())) + { + setPhase(MoonState::Phase::Full); + setVisible(true); + + mGeom->addUpdateCallback(mUpdater); + } + + Moon::~Moon() + { + mGeom->removeUpdateCallback(mUpdater); + } + + void Moon::adjustTransparency(const float ratio) + { + mUpdater->mTransparency *= ratio; + } + + void Moon::setState(const MoonState state) + { + float radsX = ((state.mRotationFromHorizon) * static_cast(osg::PI)) / 180.0f; + float radsZ = ((state.mRotationFromNorth) * static_cast(osg::PI)) / 180.0f; + + osg::Quat rotX(radsX, osg::Vec3f(1.0f, 0.0f, 0.0f)); + osg::Quat rotZ(radsZ, osg::Vec3f(0.0f, 0.0f, 1.0f)); + + osg::Vec3f direction = rotX * rotZ * osg::Vec3f(0.0f, 1.0f, 0.0f); + mTransform->setPosition(direction * mDistance); + + // The moon quad is initially oriented facing down, so we need to offset its X-axis + // rotation to rotate it to face the camera when sitting at the horizon. + osg::Quat attX((-static_cast(osg::PI) / 2.0f) + radsX, osg::Vec3f(1.0f, 0.0f, 0.0f)); + mTransform->setAttitude(attX * rotZ); + + setPhase(state.mPhase); + mUpdater->mTransparency = state.mMoonAlpha; + mUpdater->mShadowBlend = state.mShadowBlend; + } + + void Moon::setAtmosphereColor(const osg::Vec4f& color) + { + mUpdater->mAtmosphereColor = color; + } + + void Moon::setColor(const osg::Vec4f& color) + { + mUpdater->mMoonColor = color; + } + + unsigned int Moon::getPhaseInt() const + { + switch (mPhase) + { + case MoonState::Phase::New: + return 0; + case MoonState::Phase::WaxingCrescent: + return 1; + case MoonState::Phase::WaningCrescent: + return 1; + case MoonState::Phase::FirstQuarter: + return 2; + case MoonState::Phase::ThirdQuarter: + return 2; + case MoonState::Phase::WaxingGibbous: + return 3; + case MoonState::Phase::WaningGibbous: + return 3; + case MoonState::Phase::Full: + return 4; + default: + return 0; + } + } + + void Moon::setPhase(const MoonState::Phase& phase) + { + if(mPhase == phase) + return; + + mPhase = phase; + + std::string textureName = "textures/tx_"; + + if (mType == Moon::Type_Secunda) + textureName += "secunda_"; + else + textureName += "masser_"; + + switch (mPhase) + { + case MoonState::Phase::New: + textureName += "new"; + break; + case MoonState::Phase::WaxingCrescent: + textureName += "one_wax"; + break; + case MoonState::Phase::FirstQuarter: + textureName += "half_wax"; + break; + case MoonState::Phase::WaxingGibbous: + textureName += "three_wax"; + break; + case MoonState::Phase::WaningCrescent: + textureName += "one_wan"; + break; + case MoonState::Phase::ThirdQuarter: + textureName += "half_wan"; + break; + case MoonState::Phase::WaningGibbous: + textureName += "three_wan"; + break; + case MoonState::Phase::Full: + textureName += "full"; + break; + default: + break; + } + + textureName += ".dds"; + + if (mType == Moon::Type_Secunda) + mUpdater->setTextures(textureName, "textures/tx_mooncircle_full_s.dds"); + else + mUpdater->setTextures(textureName, "textures/tx_mooncircle_full_m.dds"); + } + + int RainCounter::numParticlesToCreate(double dt) const + { + // limit dt to avoid large particle emissions if there are jumps in the simulation time + // 0.2 seconds is the same cap as used in Engine's frame loop + dt = std::min(dt, 0.2); + return ConstantRateCounter::numParticlesToCreate(dt); + } + + RainShooter::RainShooter() + : mAngle(0.f) + { } + + void RainShooter::shoot(osgParticle::Particle* particle) const + { + particle->setVelocity(mVelocity); + particle->setAngle(osg::Vec3f(-mAngle, 0, (Misc::Rng::rollProbability() * 2 - 1) * osg::PI)); + } + + void RainShooter::setVelocity(const osg::Vec3f& velocity) + { + mVelocity = velocity; + } + + void RainShooter::setAngle(float angle) + { + mAngle = angle; + } + + osg::Object* RainShooter::cloneType() const + { + return new RainShooter; + } + + osg::Object* RainShooter::clone(const osg::CopyOp &) const + { + return new RainShooter(*this); + } + + ModVertexAlphaVisitor::ModVertexAlphaVisitor(ModVertexAlphaVisitor::MeshType type) + : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) + , mType(type) + { } + + void ModVertexAlphaVisitor::apply(osg::Geometry& geometry) + { + osg::ref_ptr colors = new osg::Vec4Array(geometry.getVertexArray()->getNumElements()); + for (unsigned int i=0; isize(); ++i) + { + float alpha = 1.f; + + switch (mType) + { + case ModVertexAlphaVisitor::Atmosphere: + { + // this is a cylinder, so every second vertex belongs to the bottom-most row + alpha = (i%2) ? 0.f : 1.f; + break; + } + case ModVertexAlphaVisitor::Clouds: + { + if (i>= 49 && i <= 64) + alpha = 0.f; // bottom-most row + else if (i>= 33 && i <= 48) + alpha = 0.25098; // second row + else + alpha = 1.f; + break; + } + case ModVertexAlphaVisitor::Stars: + { + if (geometry.getColorArray()) + { + osg::Vec4Array* origColors = static_cast(geometry.getColorArray()); + alpha = ((*origColors)[i].x() == 1.f) ? 1.f : 0.f; + } + else + alpha = 1.f; + break; + } + } + + (*colors)[i] = osg::Vec4f(0.f, 0.f, 0.f, alpha); + } + + geometry.setColorArray(colors, osg::Array::BIND_PER_VERTEX); + } +} diff --git a/apps/openmw/mwrender/skyutil.hpp b/apps/openmw/mwrender/skyutil.hpp new file mode 100644 index 0000000000..ae04c88d23 --- /dev/null +++ b/apps/openmw/mwrender/skyutil.hpp @@ -0,0 +1,343 @@ +#ifndef OPENMW_MWRENDER_SKYUTIL_H +#define OPENMW_MWRENDER_SKYUTIL_H + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +namespace Resource +{ + class ImageManager; + class SceneManager; +} + +namespace MWRender +{ + struct MoonUpdater; + class SunUpdater; + class SunFlashCallback; + class SunGlareCallback; + + struct WeatherResult + { + std::string mCloudTexture; + std::string mNextCloudTexture; + float mCloudBlendFactor; + + osg::Vec4f mFogColor; + + osg::Vec4f mAmbientColor; + + osg::Vec4f mSkyColor; + + // sun light color + osg::Vec4f mSunColor; + + // alpha is the sun transparency + osg::Vec4f mSunDiscColor; + + float mFogDepth; + + float mDLFogFactor; + float mDLFogOffset; + + float mWindSpeed; + float mBaseWindSpeed; + float mCurrentWindSpeed; + float mNextWindSpeed; + + float mCloudSpeed; + + float mGlareView; + + bool mNight; // use night skybox + float mNightFade; // fading factor for night skybox + + bool mIsStorm; + + std::string mAmbientLoopSoundID; + float mAmbientSoundVolume; + + std::string mParticleEffect; + std::string mRainEffect; + float mPrecipitationAlpha; + + float mRainDiameter; + float mRainMinHeight; + float mRainMaxHeight; + float mRainSpeed; + float mRainEntranceSpeed; + int mRainMaxRaindrops; + + osg::Vec3f mStormDirection; + osg::Vec3f mNextStormDirection; + }; + + struct MoonState + { + enum class Phase + { + Full, + WaningGibbous, + ThirdQuarter, + WaningCrescent, + New, + WaxingCrescent, + FirstQuarter, + WaxingGibbous, + Unspecified + }; + + float mRotationFromHorizon; + float mRotationFromNorth; + Phase mPhase; + float mShadowBlend; + float mMoonAlpha; + }; + + osg::ref_ptr createAlphaTrackingUnlitMaterial(); + osg::ref_ptr createUnlitMaterial(); + + class OcclusionCallback + { + public: + OcclusionCallback(osg::ref_ptr oqnVisible, osg::ref_ptr oqnTotal); + + protected: + float getVisibleRatio (osg::Camera* camera); + + private: + osg::ref_ptr mOcclusionQueryVisiblePixels; + osg::ref_ptr mOcclusionQueryTotalPixels; + + std::map, float> mLastRatio; + }; + + class AtmosphereUpdater : public SceneUtil::StateSetUpdater + { + public: + void setEmissionColor(const osg::Vec4f& emissionColor); + + protected: + void setDefaults(osg::StateSet* stateset) override; + void apply(osg::StateSet* stateset, osg::NodeVisitor* /*nv*/) override; + + private: + osg::Vec4f mEmissionColor; + }; + + class AtmosphereNightUpdater : public SceneUtil::StateSetUpdater + { + public: + AtmosphereNightUpdater(Resource::ImageManager* imageManager, bool forceShaders); + + void setFade(float fade); + + protected: + void setDefaults(osg::StateSet* stateset) override; + + void apply(osg::StateSet* stateset, osg::NodeVisitor* /*nv*/) override; + + private: + osg::Vec4f mColor; + osg::ref_ptr mTexture; + bool mForceShaders; + }; + + class CloudUpdater : public SceneUtil::StateSetUpdater + { + public: + CloudUpdater(bool forceShaders); + + void setTexture(osg::ref_ptr texture); + + void setEmissionColor(const osg::Vec4f& emissionColor); + void setOpacity(float opacity); + void setTextureCoord(float timer); + + protected: + void setDefaults(osg::StateSet *stateset) override; + void apply(osg::StateSet *stateset, osg::NodeVisitor *nv) override; + + private: + osg::ref_ptr mTexture; + osg::Vec4f mEmissionColor; + float mOpacity; + bool mForceShaders; + osg::Matrixf mTexMat; + }; + + /// Transform that removes the eyepoint of the modelview matrix, + /// i.e. its children are positioned relative to the camera. + class CameraRelativeTransform : public osg::Transform + { + public: + CameraRelativeTransform(); + + CameraRelativeTransform(const CameraRelativeTransform& copy, const osg::CopyOp& copyop); + + META_Node(MWRender, CameraRelativeTransform) + + const osg::Vec3f& getLastViewPoint() const; + + bool computeLocalToWorldMatrix(osg::Matrix& matrix, osg::NodeVisitor* nv) const override; + + osg::BoundingSphere computeBound() const override; + + private: + // viewPoint for the current frame + mutable osg::Vec3f mViewPoint; + }; + + /// @brief Hides the node subgraph if the eye point is below water. + /// @note Must be added as cull callback. + /// @note Meant to be used on a node that is child of a CameraRelativeTransform. + /// The current view point must be retrieved by the CameraRelativeTransform since we can't get it anymore once we are in camera-relative space. + class UnderwaterSwitchCallback : public SceneUtil::NodeCallback + { + public: + UnderwaterSwitchCallback(CameraRelativeTransform* cameraRelativeTransform); + bool isUnderwater(); + + void operator()(osg::Node* node, osg::NodeVisitor* nv); + void setEnabled(bool enabled); + void setWaterLevel(float waterLevel); + + private: + osg::ref_ptr mCameraRelativeTransform; + bool mEnabled; + float mWaterLevel; + }; + + /// A base class for the sun and moons. + class CelestialBody + { + public: + CelestialBody(osg::Group* parentNode, float scaleFactor, int numUvSets, unsigned int visibleMask=~0u); + + virtual ~CelestialBody() = default; + + virtual void adjustTransparency(const float ratio) = 0; + + void setVisible(bool visible); + + protected: + unsigned int mVisibleMask; + static const float mDistance; + osg::ref_ptr mTransform; + osg::ref_ptr mGeom; + }; + + class Sun : public CelestialBody + { + public: + Sun(osg::Group* parentNode, Resource::ImageManager& imageManager); + + ~Sun(); + + void setColor(const osg::Vec4f& color); + void adjustTransparency(const float ratio) override; + + void setDirection(const osg::Vec3f& direction); + void setGlareTimeOfDayFade(float val); + + private: + /// @param queryVisible If true, queries the amount of visible pixels. If false, queries the total amount of pixels. + osg::ref_ptr createOcclusionQueryNode(osg::Group* parent, bool queryVisible); + + void createSunFlash(Resource::ImageManager& imageManager); + void destroySunFlash(); + + void createSunGlare(); + void destroySunGlare(); + + osg::ref_ptr mUpdater; + osg::ref_ptr mSunFlashNode; + osg::ref_ptr mSunGlareNode; + osg::ref_ptr mSunFlashCallback; + osg::ref_ptr mSunGlareCallback; + osg::ref_ptr mOcclusionQueryVisiblePixels; + osg::ref_ptr mOcclusionQueryTotalPixels; + }; + + class Moon : public CelestialBody + { + public: + enum Type + { + Type_Masser = 0, + Type_Secunda + }; + + Moon(osg::Group* parentNode, Resource::SceneManager& sceneManager, float scaleFactor, Type type); + + ~Moon(); + + void adjustTransparency(const float ratio) override; + void setState(const MoonState state); + void setAtmosphereColor(const osg::Vec4f& color); + void setColor(const osg::Vec4f& color); + + unsigned int getPhaseInt() const; + + private: + Type mType; + MoonState::Phase mPhase; + osg::ref_ptr mUpdater; + + void setPhase(const MoonState::Phase& phase); + }; + + class RainCounter : public osgParticle::ConstantRateCounter + { + public: + int numParticlesToCreate(double dt) const override; + }; + + class RainShooter : public osgParticle::Shooter + { + public: + RainShooter(); + + osg::Object* cloneType() const override; + + osg::Object* clone(const osg::CopyOp &) const override; + + void shoot(osgParticle::Particle* particle) const override; + + void setVelocity(const osg::Vec3f& velocity); + void setAngle(float angle); + + private: + osg::Vec3f mVelocity; + float mAngle; + }; + + class ModVertexAlphaVisitor : public osg::NodeVisitor + { + public: + enum MeshType + { + Atmosphere, + Stars, + Clouds + }; + + ModVertexAlphaVisitor(MeshType type); + + void apply(osg::Geometry& geometry) override; + + private: + MeshType mType; + }; +} + +#endif diff --git a/apps/openmw/mwworld/weather.cpp b/apps/openmw/mwworld/weather.cpp index 4bdd784db1..9055b95ee1 100644 --- a/apps/openmw/mwworld/weather.cpp +++ b/apps/openmw/mwworld/weather.cpp @@ -22,8 +22,6 @@ #include -using namespace MWWorld; - namespace { static const int invalidWeatherID = -1; @@ -38,1226 +36,1241 @@ namespace { return x * (1-factor) + y * factor; } -} -template -T TimeOfDayInterpolator::getValue(const float gameHour, const TimeOfDaySettings& timeSettings, const std::string& prefix) const -{ - WeatherSetting setting = timeSettings.getSetting(prefix); - float preSunriseTime = setting.mPreSunriseTime; - float postSunriseTime = setting.mPostSunriseTime; - float preSunsetTime = setting.mPreSunsetTime; - float postSunsetTime = setting.mPostSunsetTime; - - // night - if (gameHour < timeSettings.mNightEnd - preSunriseTime || gameHour > timeSettings.mNightStart + postSunsetTime) - return mNightValue; - // sunrise - else if (gameHour >= timeSettings.mNightEnd - preSunriseTime && gameHour <= timeSettings.mDayStart + postSunriseTime) + osg::Vec3f calculateStormDirection(const std::string& particleEffect) { - float duration = timeSettings.mDayStart + postSunriseTime - timeSettings.mNightEnd + preSunriseTime; - float middle = timeSettings.mNightEnd - preSunriseTime + duration / 2.f; - - if (gameHour <= middle) + osg::Vec3f stormDirection = MWWorld::Weather::defaultDirection(); + if (particleEffect == "meshes\\ashcloud.nif" || particleEffect == "meshes\\blightcloud.nif") { - // fade in - float advance = middle - gameHour; - float factor = 0.f; - if (duration > 0) - factor = advance / duration * 2; - return lerp(mSunriseValue, mNightValue, factor); - } - else - { - // fade out - float advance = gameHour - middle; - float factor = 1.f; - if (duration > 0) - factor = advance / duration * 2; - return lerp(mSunriseValue, mDayValue, factor); + osg::Vec3f playerPos = MWMechanics::getPlayer().getRefData().getPosition().asVec3(); + playerPos.z() = 0; + osg::Vec3f redMountainPos = osg::Vec3f(25000.f, 70000.f, 0.f); + stormDirection = (playerPos - redMountainPos); + stormDirection.normalize(); } + return stormDirection; } - // day - else if (gameHour > timeSettings.mDayStart + postSunriseTime && gameHour < timeSettings.mDayEnd - preSunsetTime) - return mDayValue; - // sunset - else if (gameHour >= timeSettings.mDayEnd - preSunsetTime && gameHour <= timeSettings.mNightStart + postSunsetTime) +} + +namespace MWWorld +{ + template + T TimeOfDayInterpolator::getValue(const float gameHour, const TimeOfDaySettings& timeSettings, const std::string& prefix) const { - float duration = timeSettings.mNightStart + postSunsetTime - timeSettings.mDayEnd + preSunsetTime; - float middle = timeSettings.mDayEnd - preSunsetTime + duration / 2.f; + WeatherSetting setting = timeSettings.getSetting(prefix); + float preSunriseTime = setting.mPreSunriseTime; + float postSunriseTime = setting.mPostSunriseTime; + float preSunsetTime = setting.mPreSunsetTime; + float postSunsetTime = setting.mPostSunsetTime; - if (gameHour <= middle) + // night + if (gameHour < timeSettings.mNightEnd - preSunriseTime || gameHour > timeSettings.mNightStart + postSunsetTime) + return mNightValue; + // sunrise + else if (gameHour >= timeSettings.mNightEnd - preSunriseTime && gameHour <= timeSettings.mDayStart + postSunriseTime) { - // fade in - float advance = middle - gameHour; - float factor = 0.f; - if (duration > 0) - factor = advance / duration * 2; - return lerp(mSunsetValue, mDayValue, factor); - } - else - { - // fade out - float advance = gameHour - middle; - float factor = 1.f; - if (duration > 0) - factor = advance / duration * 2; - return lerp(mSunsetValue, mNightValue, factor); - } - } - // shut up compiler - return T(); -} + float duration = timeSettings.mDayStart + postSunriseTime - timeSettings.mNightEnd + preSunriseTime; + float middle = timeSettings.mNightEnd - preSunriseTime + duration / 2.f; - - -template class MWWorld::TimeOfDayInterpolator; -template class MWWorld::TimeOfDayInterpolator; - -Weather::Weather(const std::string& name, - float stormWindSpeed, - float rainSpeed, - float dlFactor, - float dlOffset, - const std::string& particleEffect) - : mCloudTexture(Fallback::Map::getString("Weather_" + name + "_Cloud_Texture")) - , mSkyColor(Fallback::Map::getColour("Weather_" + name +"_Sky_Sunrise_Color"), - Fallback::Map::getColour("Weather_" + name + "_Sky_Day_Color"), - Fallback::Map::getColour("Weather_" + name + "_Sky_Sunset_Color"), - Fallback::Map::getColour("Weather_" + name + "_Sky_Night_Color")) - , mFogColor(Fallback::Map::getColour("Weather_" + name + "_Fog_Sunrise_Color"), - Fallback::Map::getColour("Weather_" + name + "_Fog_Day_Color"), - Fallback::Map::getColour("Weather_" + name + "_Fog_Sunset_Color"), - Fallback::Map::getColour("Weather_" + name + "_Fog_Night_Color")) - , mAmbientColor(Fallback::Map::getColour("Weather_" + name + "_Ambient_Sunrise_Color"), - Fallback::Map::getColour("Weather_" + name + "_Ambient_Day_Color"), - Fallback::Map::getColour("Weather_" + name + "_Ambient_Sunset_Color"), - Fallback::Map::getColour("Weather_" + name + "_Ambient_Night_Color")) - , mSunColor(Fallback::Map::getColour("Weather_" + name + "_Sun_Sunrise_Color"), - Fallback::Map::getColour("Weather_" + name + "_Sun_Day_Color"), - Fallback::Map::getColour("Weather_" + name + "_Sun_Sunset_Color"), - Fallback::Map::getColour("Weather_" + name + "_Sun_Night_Color")) - , mLandFogDepth(Fallback::Map::getFloat("Weather_" + name + "_Land_Fog_Day_Depth"), - Fallback::Map::getFloat("Weather_" + name + "_Land_Fog_Day_Depth"), - Fallback::Map::getFloat("Weather_" + name + "_Land_Fog_Day_Depth"), - Fallback::Map::getFloat("Weather_" + name + "_Land_Fog_Night_Depth")) - , mSunDiscSunsetColor(Fallback::Map::getColour("Weather_" + name + "_Sun_Disc_Sunset_Color")) - , mWindSpeed(Fallback::Map::getFloat("Weather_" + name + "_Wind_Speed")) - , mCloudSpeed(Fallback::Map::getFloat("Weather_" + name + "_Cloud_Speed")) - , mGlareView(Fallback::Map::getFloat("Weather_" + name + "_Glare_View")) - , mIsStorm(mWindSpeed > stormWindSpeed) - , mRainSpeed(rainSpeed) - , mRainEntranceSpeed(Fallback::Map::getFloat("Weather_" + name + "_Rain_Entrance_Speed")) - , mRainMaxRaindrops(Fallback::Map::getFloat("Weather_" + name + "_Max_Raindrops")) - , mRainDiameter(Fallback::Map::getFloat("Weather_" + name + "_Rain_Diameter")) - , mRainThreshold(Fallback::Map::getFloat("Weather_" + name + "_Rain_Threshold")) - , mRainMinHeight(Fallback::Map::getFloat("Weather_" + name + "_Rain_Height_Min")) - , mRainMaxHeight(Fallback::Map::getFloat("Weather_" + name + "_Rain_Height_Max")) - , mParticleEffect(particleEffect) - , mRainEffect(Fallback::Map::getBool("Weather_" + name + "_Using_Precip") ? "meshes\\raindrop.nif" : "") - , mTransitionDelta(Fallback::Map::getFloat("Weather_" + name + "_Transition_Delta")) - , mCloudsMaximumPercent(Fallback::Map::getFloat("Weather_" + name + "_Clouds_Maximum_Percent")) - , mThunderFrequency(Fallback::Map::getFloat("Weather_" + name + "_Thunder_Frequency")) - , mThunderThreshold(Fallback::Map::getFloat("Weather_" + name + "_Thunder_Threshold")) - , mThunderSoundID() - , mFlashDecrement(Fallback::Map::getFloat("Weather_" + name + "_Flash_Decrement")) - , mFlashBrightness(0.0f) -{ - mDL.FogFactor = dlFactor; - mDL.FogOffset = dlOffset; - mThunderSoundID[0] = Fallback::Map::getString("Weather_" + name + "_Thunder_Sound_ID_0"); - mThunderSoundID[1] = Fallback::Map::getString("Weather_" + name + "_Thunder_Sound_ID_1"); - mThunderSoundID[2] = Fallback::Map::getString("Weather_" + name + "_Thunder_Sound_ID_2"); - mThunderSoundID[3] = Fallback::Map::getString("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::Map::getString("Weather_" + name + "_Rain_Loop_Sound_ID"); - if (mAmbientLoopSoundID.empty()) // default to "rain" if not set - mAmbientLoopSoundID = "rain"; - } - else - mAmbientLoopSoundID = Fallback::Map::getString("Weather_" + name + "_Ambient_Loop_Sound_ID"); - - if (Misc::StringUtils::ciEqual(mAmbientLoopSoundID, "None")) - mAmbientLoopSoundID.clear(); -} - -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)) + if (gameHour <= middle) { - lightningAndThunder(); + // fade in + float advance = middle - gameHour; + float factor = 0.f; + if (duration > 0) + factor = advance / duration * 2; + return lerp(mSunriseValue, mNightValue, factor); + } + else + { + // fade out + float advance = gameHour - middle; + float factor = 1.f; + if (duration > 0) + factor = advance / duration * 2; + return lerp(mSunriseValue, mDayValue, factor); + } + } + // day + else if (gameHour > timeSettings.mDayStart + postSunriseTime && gameHour < timeSettings.mDayEnd - preSunsetTime) + return mDayValue; + // sunset + else if (gameHour >= timeSettings.mDayEnd - preSunsetTime && gameHour <= timeSettings.mNightStart + postSunsetTime) + { + float duration = timeSettings.mNightStart + postSunsetTime - timeSettings.mDayEnd + preSunsetTime; + float middle = timeSettings.mDayEnd - preSunsetTime + duration / 2.f; + + if (gameHour <= middle) + { + // fade in + float advance = middle - gameHour; + float factor = 0.f; + if (duration > 0) + factor = advance / duration * 2; + return lerp(mSunsetValue, mDayValue, factor); + } + else + { + // fade out + float advance = gameHour - middle; + float factor = 1.f; + if (duration > 0) + factor = advance / duration * 2; + return lerp(mSunsetValue, mNightValue, factor); + } + } + // shut up compiler + return T(); + } + + template class MWWorld::TimeOfDayInterpolator; + template class MWWorld::TimeOfDayInterpolator; + + osg::Vec3f Weather::defaultDirection() + { + static const osg::Vec3f direction = osg::Vec3f(0.f, 1.f, 0.f); + return direction; + } + + Weather::Weather(const std::string& name, + float stormWindSpeed, + float rainSpeed, + float dlFactor, + float dlOffset, + const std::string& particleEffect) + : mCloudTexture(Fallback::Map::getString("Weather_" + name + "_Cloud_Texture")) + , mSkyColor(Fallback::Map::getColour("Weather_" + name +"_Sky_Sunrise_Color"), + Fallback::Map::getColour("Weather_" + name + "_Sky_Day_Color"), + Fallback::Map::getColour("Weather_" + name + "_Sky_Sunset_Color"), + Fallback::Map::getColour("Weather_" + name + "_Sky_Night_Color")) + , mFogColor(Fallback::Map::getColour("Weather_" + name + "_Fog_Sunrise_Color"), + Fallback::Map::getColour("Weather_" + name + "_Fog_Day_Color"), + Fallback::Map::getColour("Weather_" + name + "_Fog_Sunset_Color"), + Fallback::Map::getColour("Weather_" + name + "_Fog_Night_Color")) + , mAmbientColor(Fallback::Map::getColour("Weather_" + name + "_Ambient_Sunrise_Color"), + Fallback::Map::getColour("Weather_" + name + "_Ambient_Day_Color"), + Fallback::Map::getColour("Weather_" + name + "_Ambient_Sunset_Color"), + Fallback::Map::getColour("Weather_" + name + "_Ambient_Night_Color")) + , mSunColor(Fallback::Map::getColour("Weather_" + name + "_Sun_Sunrise_Color"), + Fallback::Map::getColour("Weather_" + name + "_Sun_Day_Color"), + Fallback::Map::getColour("Weather_" + name + "_Sun_Sunset_Color"), + Fallback::Map::getColour("Weather_" + name + "_Sun_Night_Color")) + , mLandFogDepth(Fallback::Map::getFloat("Weather_" + name + "_Land_Fog_Day_Depth"), + Fallback::Map::getFloat("Weather_" + name + "_Land_Fog_Day_Depth"), + Fallback::Map::getFloat("Weather_" + name + "_Land_Fog_Day_Depth"), + Fallback::Map::getFloat("Weather_" + name + "_Land_Fog_Night_Depth")) + , mSunDiscSunsetColor(Fallback::Map::getColour("Weather_" + name + "_Sun_Disc_Sunset_Color")) + , mWindSpeed(Fallback::Map::getFloat("Weather_" + name + "_Wind_Speed")) + , mCloudSpeed(Fallback::Map::getFloat("Weather_" + name + "_Cloud_Speed")) + , mGlareView(Fallback::Map::getFloat("Weather_" + name + "_Glare_View")) + , mIsStorm(mWindSpeed > stormWindSpeed) + , mRainSpeed(rainSpeed) + , mRainEntranceSpeed(Fallback::Map::getFloat("Weather_" + name + "_Rain_Entrance_Speed")) + , mRainMaxRaindrops(Fallback::Map::getFloat("Weather_" + name + "_Max_Raindrops")) + , mRainDiameter(Fallback::Map::getFloat("Weather_" + name + "_Rain_Diameter")) + , mRainThreshold(Fallback::Map::getFloat("Weather_" + name + "_Rain_Threshold")) + , mRainMinHeight(Fallback::Map::getFloat("Weather_" + name + "_Rain_Height_Min")) + , mRainMaxHeight(Fallback::Map::getFloat("Weather_" + name + "_Rain_Height_Max")) + , mParticleEffect(particleEffect) + , mRainEffect(Fallback::Map::getBool("Weather_" + name + "_Using_Precip") ? "meshes\\raindrop.nif" : "") + , mStormDirection(Weather::defaultDirection()) + , mTransitionDelta(Fallback::Map::getFloat("Weather_" + name + "_Transition_Delta")) + , mCloudsMaximumPercent(Fallback::Map::getFloat("Weather_" + name + "_Clouds_Maximum_Percent")) + , mThunderFrequency(Fallback::Map::getFloat("Weather_" + name + "_Thunder_Frequency")) + , mThunderThreshold(Fallback::Map::getFloat("Weather_" + name + "_Thunder_Threshold")) + , mThunderSoundID() + , mFlashDecrement(Fallback::Map::getFloat("Weather_" + name + "_Flash_Decrement")) + , mFlashBrightness(0.0f) + { + mDL.FogFactor = dlFactor; + mDL.FogOffset = dlOffset; + mThunderSoundID[0] = Fallback::Map::getString("Weather_" + name + "_Thunder_Sound_ID_0"); + mThunderSoundID[1] = Fallback::Map::getString("Weather_" + name + "_Thunder_Sound_ID_1"); + mThunderSoundID[2] = Fallback::Map::getString("Weather_" + name + "_Thunder_Sound_ID_2"); + mThunderSoundID[3] = Fallback::Map::getString("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::Map::getString("Weather_" + name + "_Rain_Loop_Sound_ID"); + if (mAmbientLoopSoundID.empty()) // default to "rain" if not set + mAmbientLoopSoundID = "rain"; + } + else + mAmbientLoopSoundID = Fallback::Map::getString("Weather_" + name + "_Ambient_Loop_Sound_ID"); + + if (Misc::StringUtils::ciEqual(mAmbientLoopSoundID, "None")) + mAmbientLoopSoundID.clear(); + } + + 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& chances) + { + if(mChances.size() < chances.size()) + { + mChances.reserve(chances.size()); + } + + int i = 0; + for(char chance : chances) + { + mChances[i] = chance; + i++; + } + + // Regional weather no longer supports the current type, select a new weather pattern. + if((static_cast(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(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) + : mFadeInStart(Fallback::Map::getFloat("Moons_" + name + "_Fade_In_Start")) + , mFadeInFinish(Fallback::Map::getFloat("Moons_" + name + "_Fade_In_Finish")) + , mFadeOutStart(Fallback::Map::getFloat("Moons_" + name + "_Fade_Out_Start")) + , mFadeOutFinish(Fallback::Map::getFloat("Moons_" + name + "_Fade_Out_Finish")) + , mAxisOffset(Fallback::Map::getFloat("Moons_" + name + "_Axis_Offset")) + , mSpeed(Fallback::Map::getFloat("Moons_" + name + "_Speed")) + , mDailyIncrement(Fallback::Map::getFloat("Moons_" + name + "_Daily_Increment")) + , mFadeStartAngle(Fallback::Map::getFloat("Moons_" + name + "_Fade_Start_Angle")) + , mFadeEndAngle(Fallback::Map::getFloat("Moons_" + name + "_Fade_End_Angle")) + , mMoonShadowEarlyFadeAngle(Fallback::Map::getFloat("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. + 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 { - mFlashBrightness = 0.0f; + moonRiseAngleToday = rotation(gameTime.getHour() - moonRiseHourToday); } + + if(moonRiseAngleToday >= 180) + { + // The moon set today, reset the angle to the horizon. + moonRiseAngleToday = 0; + } + + return moonRiseAngleToday; } - return mFlashBrightness; -} + 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; -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; -} + // 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 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 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 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); -} + MWRender::MoonState::Phase 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. -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); -} + // If the moon didn't rise yet today, use yesterday's moon phase. + if(gameTime.getHour() < moonRiseHour(gameTime.getDay())) + return static_cast((gameTime.getDay() / 3) % 8); + else + return static_cast(((gameTime.getDay() + 1) / 3) % 8); + } -RegionWeather::RegionWeather(const ESM::RegionWeatherState& state) - : mWeather(state.mWeather) - , mChances(state.mChances) -{ -} + 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; + } -RegionWeather::operator ESM::RegionWeatherState() const -{ - ESM::RegionWeatherState state = - { - mWeather, - mChances + 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, MWWorld::ESMStore& store) + : mStore(store) + , mRendering(rendering) + , mSunriseTime(Fallback::Map::getFloat("Weather_Sunrise_Time")) + , mSunsetTime(Fallback::Map::getFloat("Weather_Sunset_Time")) + , mSunriseDuration(Fallback::Map::getFloat("Weather_Sunrise_Duration")) + , mSunsetDuration(Fallback::Map::getFloat("Weather_Sunset_Duration")) + , mSunPreSunsetTime(Fallback::Map::getFloat("Weather_Sun_Pre-Sunset_Time")) + , mNightFade(0, 0, 0, 1) + , mHoursBetweenWeatherChanges(Fallback::Map::getFloat("Weather_Hours_Between_Weather_Changes")) + , mRainSpeed(Fallback::Map::getFloat("Weather_Precip_Gravity")) + , mUnderwaterFog(Fallback::Map::getFloat("Water_UnderwaterSunriseFog"), + Fallback::Map::getFloat("Water_UnderwaterDayFog"), + Fallback::Map::getFloat("Water_UnderwaterSunsetFog"), + Fallback::Map::getFloat("Water_UnderwaterNightFog")) + , mWeatherSettings() + , mMasser("Masser") + , mSecunda("Secunda") + , mWindSpeed(0.f) + , mCurrentWindSpeed(0.f) + , mNextWindSpeed(0.f) + , mIsStorm(false) + , mPrecipitation(false) + , mStormDirection(Weather::defaultDirection()) + , mCurrentRegion() + , mTimePassed(0) + , mFastForward(false) + , mWeatherUpdateTime(mHoursBetweenWeatherChanges) + , mTransitionFactor(0) + , mNightDayMode(Default) + , mCurrentWeather(0) + , mNextWeather(0) + , mQueuedWeather(0) + , mRegions() + , mResult() + , mAmbientSound(nullptr) + , mPlayingSoundID() + { + mTimeSettings.mNightStart = mSunsetTime + mSunsetDuration; + mTimeSettings.mNightEnd = mSunriseTime; + mTimeSettings.mDayStart = mSunriseTime + mSunriseDuration; + mTimeSettings.mDayEnd = mSunsetTime; + + mTimeSettings.addSetting("Sky"); + mTimeSettings.addSetting("Ambient"); + mTimeSettings.addSetting("Fog"); + mTimeSettings.addSetting("Sun"); + + // Morrowind handles stars settings differently for other ones + mTimeSettings.mStarsPostSunsetStart = Fallback::Map::getFloat("Weather_Stars_Post-Sunset_Start"); + mTimeSettings.mStarsPreSunriseFinish = Fallback::Map::getFloat("Weather_Stars_Pre-Sunrise_Finish"); + mTimeSettings.mStarsFadingDuration = Fallback::Map::getFloat("Weather_Stars_Fading_Duration"); + + WeatherSetting starSetting = { + mTimeSettings.mStarsPreSunriseFinish, + mTimeSettings.mStarsFadingDuration - mTimeSettings.mStarsPreSunriseFinish, + mTimeSettings.mStarsPostSunsetStart, + mTimeSettings.mStarsFadingDuration - mTimeSettings.mStarsPostSunsetStart }; - return state; -} + mTimeSettings.mSunriseTransitions["Stars"] = starSetting; -void RegionWeather::setChances(const std::vector& chances) -{ - if(mChances.size() < chances.size()) - { - mChances.reserve(chances.size()); - } + mWeatherSettings.reserve(10); + // These distant land fog factor and offset values are the defaults MGE XE provides. Should be + // provided by settings somewhere? + addWeather("Clear", 1.0f, 0.0f); // 0 + addWeather("Cloudy", 0.9f, 0.0f); // 1 + addWeather("Foggy", 0.2f, 30.0f); // 2 + addWeather("Overcast", 0.7f, 0.0f); // 3 + addWeather("Rain", 0.5f, 10.0f); // 4 + addWeather("Thunderstorm", 0.5f, 20.0f); // 5 + addWeather("Ashstorm", 0.2f, 50.0f, "meshes\\ashcloud.nif"); // 6 + addWeather("Blight", 0.2f, 60.0f, "meshes\\blightcloud.nif"); // 7 + addWeather("Snow", 0.5f, 40.0f, "meshes\\snow.nif"); // 8 + addWeather("Blizzard", 0.16f, 70.0f, "meshes\\blizzard.nif"); // 9 - int i = 0; - for(char chance : chances) - { - mChances[i] = chance; - i++; - } - - // Regional weather no longer supports the current type, select a new weather pattern. - if((static_cast(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(i) < mChances.size(); ++i) - { - sum += mChances[i]; - if(chance <= sum) + Store::iterator it = store.get().begin(); + for(; it != store.get().end(); ++it) { - mWeather = i; - return; + std::string regionID = Misc::StringUtils::lowerCase(it->mId); + mRegions.insert(std::make_pair(regionID, RegionWeather(*it))); } + + forceWeather(0); } - // 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) - : mFadeInStart(Fallback::Map::getFloat("Moons_" + name + "_Fade_In_Start")) - , mFadeInFinish(Fallback::Map::getFloat("Moons_" + name + "_Fade_In_Finish")) - , mFadeOutStart(Fallback::Map::getFloat("Moons_" + name + "_Fade_Out_Start")) - , mFadeOutFinish(Fallback::Map::getFloat("Moons_" + name + "_Fade_Out_Finish")) - , mAxisOffset(Fallback::Map::getFloat("Moons_" + name + "_Axis_Offset")) - , mSpeed(Fallback::Map::getFloat("Moons_" + name + "_Speed")) - , mDailyIncrement(Fallback::Map::getFloat("Moons_" + name + "_Daily_Increment")) - , mFadeStartAngle(Fallback::Map::getFloat("Moons_" + name + "_Fade_Start_Angle")) - , mFadeEndAngle(Fallback::Map::getFloat("Moons_" + name + "_Fade_End_Angle")) - , mMoonShadowEarlyFadeAngle(Fallback::Map::getFloat("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. - 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) + WeatherManager::~WeatherManager() { - float moonRiseHourYesterday = moonRiseHour(gameTime.getDay() - 1); - if(moonRiseHourYesterday < 24) + 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()) { - float moonRiseAngleYesterday = rotation(24 - moonRiseHourYesterday); - if(moonRiseAngleYesterday < 180) + std::string lowerCaseRegionID = Misc::StringUtils::lowerCase(regionID); + std::map::iterator it = mRegions.find(lowerCaseRegionID); + if(it != mRegions.end()) { - // 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; + it->second.setWeather(weatherID); + regionalWeatherChanged(it->first, it->second); } } } - else + + void WeatherManager::modRegion(const std::string& regionID, const std::vector& chances) { - moonRiseAngleToday = rotation(gameTime.getHour() - moonRiseHourToday); - } + // 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. - 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; -} - -MWRender::MoonState::Phase 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. - - // If the moon didn't rise yet today, use yesterday's moon phase. - if(gameTime.getHour() < moonRiseHour(gameTime.getDay())) - return static_cast((gameTime.getDay() / 3) % 8); - else - return static_cast(((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, MWWorld::ESMStore& store) - : mStore(store) - , mRendering(rendering) - , mSunriseTime(Fallback::Map::getFloat("Weather_Sunrise_Time")) - , mSunsetTime(Fallback::Map::getFloat("Weather_Sunset_Time")) - , mSunriseDuration(Fallback::Map::getFloat("Weather_Sunrise_Duration")) - , mSunsetDuration(Fallback::Map::getFloat("Weather_Sunset_Duration")) - , mSunPreSunsetTime(Fallback::Map::getFloat("Weather_Sun_Pre-Sunset_Time")) - , mNightFade(0, 0, 0, 1) - , mHoursBetweenWeatherChanges(Fallback::Map::getFloat("Weather_Hours_Between_Weather_Changes")) - , mRainSpeed(Fallback::Map::getFloat("Weather_Precip_Gravity")) - , mUnderwaterFog(Fallback::Map::getFloat("Water_UnderwaterSunriseFog"), - Fallback::Map::getFloat("Water_UnderwaterDayFog"), - Fallback::Map::getFloat("Water_UnderwaterSunsetFog"), - Fallback::Map::getFloat("Water_UnderwaterNightFog")) - , mWeatherSettings() - , mMasser("Masser") - , mSecunda("Secunda") - , mWindSpeed(0.f) - , mCurrentWindSpeed(0.f) - , mNextWindSpeed(0.f) - , mIsStorm(false) - , mPrecipitation(false) - , mStormDirection(0,1,0) - , mCurrentRegion() - , mTimePassed(0) - , mFastForward(false) - , mWeatherUpdateTime(mHoursBetweenWeatherChanges) - , mTransitionFactor(0) - , mNightDayMode(Default) - , mCurrentWeather(0) - , mNextWeather(0) - , mQueuedWeather(0) - , mRegions() - , mResult() - , mAmbientSound(nullptr) - , mPlayingSoundID() -{ - mTimeSettings.mNightStart = mSunsetTime + mSunsetDuration; - mTimeSettings.mNightEnd = mSunriseTime; - mTimeSettings.mDayStart = mSunriseTime + mSunriseDuration; - mTimeSettings.mDayEnd = mSunsetTime; - - mTimeSettings.addSetting("Sky"); - mTimeSettings.addSetting("Ambient"); - mTimeSettings.addSetting("Fog"); - mTimeSettings.addSetting("Sun"); - - // Morrowind handles stars settings differently for other ones - mTimeSettings.mStarsPostSunsetStart = Fallback::Map::getFloat("Weather_Stars_Post-Sunset_Start"); - mTimeSettings.mStarsPreSunriseFinish = Fallback::Map::getFloat("Weather_Stars_Pre-Sunrise_Finish"); - mTimeSettings.mStarsFadingDuration = Fallback::Map::getFloat("Weather_Stars_Fading_Duration"); - - WeatherSetting starSetting = { - mTimeSettings.mStarsPreSunriseFinish, - mTimeSettings.mStarsFadingDuration - mTimeSettings.mStarsPreSunriseFinish, - mTimeSettings.mStarsPostSunsetStart, - mTimeSettings.mStarsFadingDuration - mTimeSettings.mStarsPostSunsetStart - }; - - mTimeSettings.mSunriseTransitions["Stars"] = starSetting; - - mWeatherSettings.reserve(10); - // These distant land fog factor and offset values are the defaults MGE XE provides. Should be - // provided by settings somewhere? - addWeather("Clear", 1.0f, 0.0f); // 0 - addWeather("Cloudy", 0.9f, 0.0f); // 1 - addWeather("Foggy", 0.2f, 30.0f); // 2 - addWeather("Overcast", 0.7f, 0.0f); // 3 - addWeather("Rain", 0.5f, 10.0f); // 4 - addWeather("Thunderstorm", 0.5f, 20.0f); // 5 - addWeather("Ashstorm", 0.2f, 50.0f, "meshes\\ashcloud.nif"); // 6 - addWeather("Blight", 0.2f, 60.0f, "meshes\\blightcloud.nif"); // 7 - addWeather("Snow", 0.5f, 40.0f, "meshes\\snow.nif"); // 8 - addWeather("Blizzard", 0.16f, 70.0f, "meshes\\blizzard.nif"); // 9 - - Store::iterator it = store.get().begin(); - for(; it != store.get().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::iterator it = mRegions.find(lowerCaseRegionID); if(it != mRegions.end()) { - it->second.setWeather(weatherID); + it->second.setChances(chances); regionalWeatherChanged(it->first, it->second); } } -} -void WeatherManager::modRegion(const std::string& regionID, const std::vector& 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::iterator it = mRegions.find(lowerCaseRegionID); - if(it != mRegions.end()) + void WeatherManager::playerTeleported(const std::string& playerRegion, bool isExterior) { - it->second.setChances(chances); - regionalWeatherChanged(it->first, it->second); - } -} - -void WeatherManager::playerTeleported(const std::string& playerRegion, bool isExterior) -{ - // 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. - { - std::map::iterator it = mRegions.find(playerRegion); - if(it != mRegions.end() && playerRegion != mCurrentRegion) + // 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. { - mCurrentRegion = playerRegion; - forceWeather(it->second.getWeather()); - } - } -} - -float WeatherManager::calculateWindSpeed(int weatherId, float currentSpeed) -{ - float targetSpeed = std::min(8.0f * mWeatherSettings[weatherId].mWindSpeed, 70.f); - if (currentSpeed == 0.f) - currentSpeed = targetSpeed; - - float multiplier = mWeatherSettings[weatherId].mRainEffect.empty() ? 1.f : 0.5f; - float updatedSpeed = (Misc::Rng::rollClosedProbability() - 0.5f) * multiplier * targetSpeed + currentSpeed; - - if (updatedSpeed > 0.5f * targetSpeed && updatedSpeed < 2.f * targetSpeed) - currentSpeed = updatedSpeed; - - return currentSpeed; -} - -void WeatherManager::update(float duration, bool paused, const TimeStamp& time, bool isExterior) -{ - MWWorld::ConstPtr player = MWMechanics::getPlayer(); - - 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::iterator it = mRegions.find(mCurrentRegion); - if(it != mRegions.end()) + std::map::iterator it = mRegions.find(playerRegion); + if(it != mRegions.end() && playerRegion != mCurrentRegion) { - addWeatherTransition(it->second.getWeather()); + mCurrentRegion = playerRegion; + forceWeather(it->second.getWeather()); } } - - updateWeatherTransitions(duration); } - bool isDay = time.getHour() >= mSunriseTime && time.getHour() <= mTimeSettings.mNightStart; - if (isExterior && !isDay) - mNightDayMode = ExteriorNight; - else if (!isExterior && isDay && mWeatherSettings[mCurrentWeather].mGlareView >= 0.5f) - mNightDayMode = InteriorDay; - else - mNightDayMode = Default; - - if(!isExterior) + float WeatherManager::calculateWindSpeed(int weatherId, float currentSpeed) { - mRendering.setSkyEnabled(false); - stopSounds(); - mWindSpeed = 0.f; - mCurrentWindSpeed = 0.f; - mNextWindSpeed = 0.f; - return; + float targetSpeed = std::min(8.0f * mWeatherSettings[weatherId].mWindSpeed, 70.f); + if (currentSpeed == 0.f) + currentSpeed = targetSpeed; + + float multiplier = mWeatherSettings[weatherId].mRainEffect.empty() ? 1.f : 0.5f; + float updatedSpeed = (Misc::Rng::rollClosedProbability() - 0.5f) * multiplier * targetSpeed + currentSpeed; + + if (updatedSpeed > 0.5f * targetSpeed && updatedSpeed < 2.f * targetSpeed) + currentSpeed = updatedSpeed; + + return currentSpeed; } - calculateWeatherResult(time.getHour(), duration, paused); - - if (!paused) + void WeatherManager::update(float duration, bool paused, const TimeStamp& time, bool isExterior) { - mWindSpeed = mResult.mWindSpeed; - mCurrentWindSpeed = mResult.mCurrentWindSpeed; - mNextWindSpeed = mResult.mNextWindSpeed; - } + MWWorld::ConstPtr player = MWMechanics::getPlayer(); - mIsStorm = mResult.mIsStorm; - - // For some reason Ash Storm is not considered as a precipitation weather in game - mPrecipitation = !(mResult.mParticleEffect.empty() && mResult.mRainEffect.empty()) - && mResult.mParticleEffect != "meshes\\ashcloud.nif"; - - if (mIsStorm) - { - osg::Vec3f stormDirection(0, 1, 0); - if (mResult.mParticleEffect == "meshes\\ashcloud.nif" || mResult.mParticleEffect == "meshes\\blightcloud.nif") + if(!paused || mFastForward) { - osg::Vec3f playerPos (MWMechanics::getPlayer().getRefData().getPosition().asVec3()); - playerPos.z() = 0; - osg::Vec3f redMountainPos (25000, 70000, 0); - stormDirection = (playerPos - redMountainPos); - stormDirection.normalize(); - } - mStormDirection = stormDirection; - 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(osg::PI) * (adjustedHour - mSunriseTime) / dayDuration; - } - else - { - theta = static_cast(osg::PI) - static_cast(osg::PI) * (adjustedHour - adjustedNightStart) / nightDuration; - } - - osg::Vec3f final( - static_cast(cos(theta)), - -0.268f, // approx tan( -15 degrees ) - static_cast(sin(theta))); - mRendering.setSunDirection( final * -1 ); - } - - float underwaterFog = mUnderwaterFog.getValue(time.getHour(), mTimeSettings, "Fog"); - - float peakHour = mSunriseTime + (mTimeSettings.mNightStart - mSunriseTime) / 2; - float glareFade = 1.f; - if (time.getHour() < mSunriseTime || time.getHour() > mTimeSettings.mNightStart) - glareFade = 0.f; - else if (time.getHour() < peakHour) - glareFade = 1.f - (peakHour - time.getHour()) / (peakHour - mSunriseTime); - else - glareFade = 1.f - (time.getHour() - peakHour) / (mTimeSettings.mNightStart - peakHour); - - mRendering.getSkyManager()->setGlareTimeOfDayFade(glareFade); - - mRendering.getSkyManager()->setMasserState(mMasser.calculateState(time)); - mRendering.getSkyManager()->setSecundaState(mSecunda.calculateState(time)); - - mRendering.configureFog(mResult.mFogDepth, underwaterFog, mResult.mDLFogFactor, - mResult.mDLFogOffset/100.0f, mResult.mFogColor); - mRendering.setAmbientColour(mResult.mAmbientColor); - mRendering.setSunColour(mResult.mSunColor, mResult.mSunColor * mResult.mGlareView * glareFade); - - 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; -} - -NightDayMode WeatherManager::getNightDayMode() const -{ - return mNightDayMode; -} - -bool WeatherManager::useTorches(float hour) const -{ - bool isDark = hour < mSunriseTime || hour > mTimeSettings.mNightStart; - - return isDark && !mPrecipitation; -} - -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::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::iterator it = state.mRegions.begin(); it != state.mRegions.end(); ++it) + // 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::iterator found = mRegions.find(it->first); - if (found != mRegions.end()) + std::map::iterator it = mRegions.find(mCurrentRegion); + if(it != mRegions.end()) { - found->second = RegionWeather(it->second); + addWeatherTransition(it->second.getWeather()); } } + + updateWeatherTransitions(duration); } - return true; - } + bool isDay = time.getHour() >= mSunriseTime && time.getHour() <= mTimeSettings.mNightStart; + if (isExterior && !isDay) + mNightDayMode = ExteriorNight; + else if (!isExterior && isDay && mWeatherSettings[mCurrentWeather].mGlareView >= 0.5f) + mNightDayMode = InteriorDay; + else + mNightDayMode = Default; - 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, - float dlFactor, float dlOffset, - const std::string& particleEffect) -{ - static const float fStromWindSpeed = mStore.get().find("fStromWindSpeed")->mValue.getFloat(); - - Weather weather(name, fStromWindSpeed, mRainSpeed, dlFactor, dlOffset, particleEffect); - - mWeatherSettings.push_back(weather); -} - -inline void WeatherManager::importRegions() -{ - for(const ESM::Region& region : mStore.get()) - { - std::string regionID = Misc::StringUtils::lowerCase(region.mId); - mRegions.insert(std::make_pair(regionID, RegionWeather(region))); - } -} - -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)) + if(!isExterior) { - 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::iterator it = mRegions.begin(); - for(; it != mRegions.end(); ++it) - { - it->second.setWeather(invalidWeatherID); + mRendering.setSkyEnabled(false); + stopSounds(); + mWindSpeed = 0.f; + mCurrentWindSpeed = 0.f; + mNextWindSpeed = 0.f; + return; } - mWeatherUpdateTime += mHoursBetweenWeatherChanges; + calculateWeatherResult(time.getHour(), duration, paused); - 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) + if (!paused) { - mCurrentWeather = mNextWeather; - mNextWeather = mQueuedWeather; - mQueuedWeather = invalidWeatherID; + mWindSpeed = mResult.mWindSpeed; + mCurrentWindSpeed = mResult.mCurrentWindSpeed; + mNextWindSpeed = mResult.mNextWindSpeed; + } - // We may have begun processing the queued transition, so we need to apply the remaining time towards it. - if(inTransition()) + mIsStorm = mResult.mIsStorm; + + // For some reason Ash Storm is not considered as a precipitation weather in game + mPrecipitation = !(mResult.mParticleEffect.empty() && mResult.mRainEffect.empty()) + && mResult.mParticleEffect != "meshes\\ashcloud.nif"; + + mStormDirection = calculateStormDirection(mResult.mParticleEffect); + mRendering.getSkyManager()->setStormParticleDirection(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 ) { - const float newDelta = mWeatherSettings[mNextWeather].transitionDelta(); - const float remainingSeconds = -(mTransitionFactor / delta); - mTransitionFactor = 1.0f - (remainingSeconds * newDelta); + theta = static_cast(osg::PI) * (adjustedHour - mSunriseTime) / dayDuration; } else { - mTransitionFactor = 0.0f; + theta = static_cast(osg::PI) - static_cast(osg::PI) * (adjustedHour - adjustedNightStart) / nightDuration; + } + + osg::Vec3f final( + static_cast(cos(theta)), + -0.268f, // approx tan( -15 degrees ) + static_cast(sin(theta))); + mRendering.setSunDirection( final * -1 ); + } + + float underwaterFog = mUnderwaterFog.getValue(time.getHour(), mTimeSettings, "Fog"); + + float peakHour = mSunriseTime + (mTimeSettings.mNightStart - mSunriseTime) / 2; + float glareFade = 1.f; + if (time.getHour() < mSunriseTime || time.getHour() > mTimeSettings.mNightStart) + glareFade = 0.f; + else if (time.getHour() < peakHour) + glareFade = 1.f - (peakHour - time.getHour()) / (peakHour - mSunriseTime); + else + glareFade = 1.f - (time.getHour() - peakHour) / (mTimeSettings.mNightStart - peakHour); + + mRendering.getSkyManager()->setGlareTimeOfDayFade(glareFade); + + mRendering.getSkyManager()->setMasserState(mMasser.calculateState(time)); + mRendering.getSkyManager()->setSecundaState(mSecunda.calculateState(time)); + + mRendering.configureFog(mResult.mFogDepth, underwaterFog, mResult.mDLFogFactor, + mResult.mDLFogOffset/100.0f, mResult.mFogColor); + mRendering.setAmbientColour(mResult.mAmbientColor); + mRendering.setSunColour(mResult.mSunColor, mResult.mSunColor * mResult.mGlareView * glareFade); + + 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; + } + + NightDayMode WeatherManager::getNightDayMode() const + { + return mNightDayMode; + } + + bool WeatherManager::useTorches(float hour) const + { + bool isDark = hour < mSunriseTime || hour > mTimeSettings.mNightStart; + + return isDark && !mPrecipitation; + } + + 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::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::iterator it = state.mRegions.begin(); it != state.mRegions.end(); ++it) + { + std::map::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, + float dlFactor, float dlOffset, + const std::string& particleEffect) + { + static const float fStromWindSpeed = mStore.get().find("fStromWindSpeed")->mValue.getFloat(); + + Weather weather(name, fStromWindSpeed, mRainSpeed, dlFactor, dlOffset, particleEffect); + + mWeatherSettings.push_back(weather); + } + + inline void WeatherManager::importRegions() + { + for(const ESM::Region& region : mStore.get()) + { + std::string regionID = Misc::StringUtils::lowerCase(region.mId); + mRegions.insert(std::make_pair(regionID, RegionWeather(region))); + } + } + + 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()); } } } - else + + inline bool WeatherManager::updateWeatherTime() { - if(mQueuedWeather != invalidWeatherID) + mWeatherUpdateTime -= mTimePassed; + mTimePassed = 0.0f; + if(mWeatherUpdateTime <= 0.0f) { - mCurrentWeather = mQueuedWeather; - } - else if(mNextWeather != invalidWeatherID) - { - mCurrentWeather = mNextWeather; + // Expire all regional weather, so that any call to getWeather() will return a new weather ID. + std::map::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; - 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(weatherID) < mWeatherSettings.size()); - - if(!inTransition() && (weatherID != mCurrentWeather)) + inline bool WeatherManager::inTransition() { - mNextWeather = weatherID; - mTransitionFactor = 1.0f; + return mNextWeather != invalidWeatherID; } - else if(inTransition() && (weatherID != mNextWeather)) + + inline void WeatherManager::addWeatherTransition(const int weatherID) { - mQueuedWeather = 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(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()) + inline void WeatherManager::calculateWeatherResult(const float gameHour, + const float elapsedSeconds, + const bool isPaused) { - calculateResult(mCurrentWeather, gameHour); - flash = mWeatherSettings[mCurrentWeather].calculateThunder(1.0f, elapsedSeconds, 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; } - else + + inline void WeatherManager::calculateResult(const int weatherID, const float gameHour) { - 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); + const Weather& current = mWeatherSettings[weatherID]; - mResult.mFogColor += flashColor; - mResult.mAmbientColor += flashColor; - mResult.mSunColor += flashColor; -} + mResult.mCloudTexture = current.mCloudTexture; + mResult.mCloudBlendFactor = 0; + mResult.mNextWindSpeed = 0; + mResult.mWindSpeed = mResult.mCurrentWindSpeed = calculateWindSpeed(weatherID, mWindSpeed); + mResult.mBaseWindSpeed = mWeatherSettings[weatherID].mWindSpeed; -inline void WeatherManager::calculateResult(const int weatherID, const float gameHour) -{ - const Weather& current = mWeatherSettings[weatherID]; + mResult.mCloudSpeed = current.mCloudSpeed; + mResult.mGlareView = current.mGlareView; + mResult.mAmbientLoopSoundID = current.mAmbientLoopSoundID; + mResult.mAmbientSoundVolume = 1.f; + mResult.mPrecipitationAlpha = 1.f; - mResult.mCloudTexture = current.mCloudTexture; - mResult.mCloudBlendFactor = 0; - mResult.mNextWindSpeed = 0; - mResult.mWindSpeed = mResult.mCurrentWindSpeed = calculateWindSpeed(weatherID, mWindSpeed); - mResult.mBaseWindSpeed = mWeatherSettings[weatherID].mWindSpeed; - - mResult.mCloudSpeed = current.mCloudSpeed; - mResult.mGlareView = current.mGlareView; - mResult.mAmbientLoopSoundID = current.mAmbientLoopSoundID; - mResult.mAmbientSoundVolume = 1.f; - mResult.mPrecipitationAlpha = 1.f; - - mResult.mIsStorm = current.mIsStorm; - - mResult.mRainSpeed = current.mRainSpeed; - mResult.mRainEntranceSpeed = current.mRainEntranceSpeed; - mResult.mRainDiameter = current.mRainDiameter; - mResult.mRainMinHeight = current.mRainMinHeight; - mResult.mRainMaxHeight = current.mRainMaxHeight; - mResult.mRainMaxRaindrops = current.mRainMaxRaindrops; - - mResult.mParticleEffect = current.mParticleEffect; - mResult.mRainEffect = current.mRainEffect; - - mResult.mNight = (gameHour < mSunriseTime || gameHour > mTimeSettings.mNightStart + mTimeSettings.mStarsPostSunsetStart - mTimeSettings.mStarsFadingDuration); - - mResult.mFogDepth = current.mLandFogDepth.getValue(gameHour, mTimeSettings, "Fog"); - mResult.mFogColor = current.mFogColor.getValue(gameHour, mTimeSettings, "Fog"); - mResult.mAmbientColor = current.mAmbientColor.getValue(gameHour, mTimeSettings, "Ambient"); - mResult.mSunColor = current.mSunColor.getValue(gameHour, mTimeSettings, "Sun"); - mResult.mSkyColor = current.mSkyColor.getValue(gameHour, mTimeSettings, "Sky"); - mResult.mNightFade = mNightFade.getValue(gameHour, mTimeSettings, "Stars"); - mResult.mDLFogFactor = current.mDL.FogFactor; - mResult.mDLFogOffset = current.mDL.FogOffset; - - WeatherSetting setting = mTimeSettings.getSetting("Sun"); - float preSunsetTime = setting.mPreSunsetTime; - - if (gameHour >= mTimeSettings.mDayEnd - preSunsetTime) - { - float factor = 1.f; - if (preSunsetTime > 0) - factor = (gameHour - (mTimeSettings.mDayEnd - preSunsetTime)) / preSunsetTime; - 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 >= mTimeSettings.mDayEnd) - { - // sunset - float fade = std::min(1.f, (gameHour - mTimeSettings.mDayEnd) / (mTimeSettings.mNightStart - mTimeSettings.mDayEnd)); - fade = fade*fade; - mResult.mSunDiscColor.a() = 1.f - fade; - } - else if (gameHour >= mTimeSettings.mNightEnd && gameHour <= mTimeSettings.mNightEnd + mSunriseDuration / 2.f) - { - // sunrise - mResult.mSunDiscColor.a() = gameHour - mTimeSettings.mNightEnd; - } - 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.mDLFogFactor = lerp(current.mDLFogFactor, other.mDLFogFactor, factor); - mResult.mDLFogOffset = lerp(current.mDLFogOffset, other.mDLFogOffset, factor); - - mResult.mCurrentWindSpeed = calculateWindSpeed(mCurrentWeather, mCurrentWindSpeed); - mResult.mNextWindSpeed = calculateWindSpeed(mNextWeather, mNextWindSpeed); - mResult.mBaseWindSpeed = lerp(current.mBaseWindSpeed, other.mBaseWindSpeed, factor); - - mResult.mWindSpeed = lerp(mResult.mCurrentWindSpeed, mResult.mNextWindSpeed, 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; - - float threshold = mWeatherSettings[mNextWeather].mRainThreshold; - if (threshold <= 0) - threshold = 0.5f; - - if(factor < threshold) - { mResult.mIsStorm = current.mIsStorm; - mResult.mParticleEffect = current.mParticleEffect; - mResult.mRainEffect = current.mRainEffect; + mResult.mRainSpeed = current.mRainSpeed; mResult.mRainEntranceSpeed = current.mRainEntranceSpeed; - mResult.mAmbientSoundVolume = 1 - factor / threshold; - mResult.mPrecipitationAlpha = mResult.mAmbientSoundVolume; - mResult.mAmbientLoopSoundID = current.mAmbientLoopSoundID; mResult.mRainDiameter = current.mRainDiameter; mResult.mRainMinHeight = current.mRainMinHeight; mResult.mRainMaxHeight = current.mRainMaxHeight; mResult.mRainMaxRaindrops = current.mRainMaxRaindrops; - } - else - { - mResult.mIsStorm = other.mIsStorm; - mResult.mParticleEffect = other.mParticleEffect; - mResult.mRainEffect = other.mRainEffect; - mResult.mRainSpeed = other.mRainSpeed; - mResult.mRainEntranceSpeed = other.mRainEntranceSpeed; - mResult.mAmbientSoundVolume = (factor - threshold) / (1 - threshold); - mResult.mPrecipitationAlpha = mResult.mAmbientSoundVolume; - mResult.mAmbientLoopSoundID = other.mAmbientLoopSoundID; - mResult.mRainDiameter = other.mRainDiameter; - mResult.mRainMinHeight = other.mRainMinHeight; - mResult.mRainMaxHeight = other.mRainMaxHeight; - mResult.mRainMaxRaindrops = other.mRainMaxRaindrops; + mResult.mParticleEffect = current.mParticleEffect; + mResult.mRainEffect = current.mRainEffect; + + mResult.mNight = (gameHour < mSunriseTime || gameHour > mTimeSettings.mNightStart + mTimeSettings.mStarsPostSunsetStart - mTimeSettings.mStarsFadingDuration); + + mResult.mFogDepth = current.mLandFogDepth.getValue(gameHour, mTimeSettings, "Fog"); + mResult.mFogColor = current.mFogColor.getValue(gameHour, mTimeSettings, "Fog"); + mResult.mAmbientColor = current.mAmbientColor.getValue(gameHour, mTimeSettings, "Ambient"); + mResult.mSunColor = current.mSunColor.getValue(gameHour, mTimeSettings, "Sun"); + mResult.mSkyColor = current.mSkyColor.getValue(gameHour, mTimeSettings, "Sky"); + mResult.mNightFade = mNightFade.getValue(gameHour, mTimeSettings, "Stars"); + mResult.mDLFogFactor = current.mDL.FogFactor; + mResult.mDLFogOffset = current.mDL.FogOffset; + + WeatherSetting setting = mTimeSettings.getSetting("Sun"); + float preSunsetTime = setting.mPreSunsetTime; + + if (gameHour >= mTimeSettings.mDayEnd - preSunsetTime) + { + float factor = 1.f; + if (preSunsetTime > 0) + factor = (gameHour - (mTimeSettings.mDayEnd - preSunsetTime)) / preSunsetTime; + 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 >= mTimeSettings.mDayEnd) + { + // sunset + float fade = std::min(1.f, (gameHour - mTimeSettings.mDayEnd) / (mTimeSettings.mNightStart - mTimeSettings.mDayEnd)); + fade = fade*fade; + mResult.mSunDiscColor.a() = 1.f - fade; + } + else if (gameHour >= mTimeSettings.mNightEnd && gameHour <= mTimeSettings.mNightEnd + mSunriseDuration / 2.f) + { + // sunrise + mResult.mSunDiscColor.a() = gameHour - mTimeSettings.mNightEnd; + } + else + mResult.mSunDiscColor.a() = 1; + + mResult.mStormDirection = calculateStormDirection(mResult.mParticleEffect); + } + + 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.mStormDirection = current.mStormDirection; + mResult.mNextStormDirection = other.mStormDirection; + + 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.mDLFogFactor = lerp(current.mDLFogFactor, other.mDLFogFactor, factor); + mResult.mDLFogOffset = lerp(current.mDLFogOffset, other.mDLFogOffset, factor); + + mResult.mCurrentWindSpeed = calculateWindSpeed(mCurrentWeather, mCurrentWindSpeed); + mResult.mNextWindSpeed = calculateWindSpeed(mNextWeather, mNextWindSpeed); + mResult.mBaseWindSpeed = lerp(current.mBaseWindSpeed, other.mBaseWindSpeed, factor); + + mResult.mWindSpeed = lerp(mResult.mCurrentWindSpeed, mResult.mNextWindSpeed, 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; + + float threshold = mWeatherSettings[mNextWeather].mRainThreshold; + if (threshold <= 0.f) + threshold = 0.5f; + + if(factor < threshold) + { + mResult.mIsStorm = current.mIsStorm; + mResult.mParticleEffect = current.mParticleEffect; + mResult.mRainEffect = current.mRainEffect; + mResult.mRainSpeed = current.mRainSpeed; + mResult.mRainEntranceSpeed = current.mRainEntranceSpeed; + mResult.mAmbientSoundVolume = 1.f - factor / threshold; + mResult.mPrecipitationAlpha = mResult.mAmbientSoundVolume; + mResult.mAmbientLoopSoundID = current.mAmbientLoopSoundID; + mResult.mRainDiameter = current.mRainDiameter; + mResult.mRainMinHeight = current.mRainMinHeight; + mResult.mRainMaxHeight = current.mRainMaxHeight; + mResult.mRainMaxRaindrops = current.mRainMaxRaindrops; + } + else + { + mResult.mIsStorm = other.mIsStorm; + mResult.mParticleEffect = other.mParticleEffect; + mResult.mRainEffect = other.mRainEffect; + mResult.mRainSpeed = other.mRainSpeed; + mResult.mRainEntranceSpeed = other.mRainEntranceSpeed; + mResult.mAmbientSoundVolume = (factor - threshold) / (1 - threshold); + mResult.mPrecipitationAlpha = mResult.mAmbientSoundVolume; + mResult.mAmbientLoopSoundID = other.mAmbientLoopSoundID; + + mResult.mRainDiameter = other.mRainDiameter; + mResult.mRainMinHeight = other.mRainMinHeight; + mResult.mRainMaxHeight = other.mRainMaxHeight; + mResult.mRainMaxRaindrops = other.mRainMaxRaindrops; + } } } + diff --git a/apps/openmw/mwworld/weather.hpp b/apps/openmw/mwworld/weather.hpp index a3928465c4..21b2fae9f8 100644 --- a/apps/openmw/mwworld/weather.hpp +++ b/apps/openmw/mwworld/weather.hpp @@ -115,6 +115,8 @@ namespace MWWorld class Weather { public: + static osg::Vec3f defaultDirection(); + Weather(const std::string& name, float stormWindSpeed, float rainSpeed, @@ -189,6 +191,8 @@ namespace MWWorld std::string mRainEffect; + osg::Vec3f mStormDirection; + // Note: For Weather Blight, there is a "Disease Chance" (=0.1) setting. But according to MWSFD this feature // is broken in the vanilla game and was disabled. diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index 6709ee842e..dcd4874e90 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -516,6 +516,10 @@ namespace Shader // We could fall back to a texture size uniform if EXT_gpu_shader4 is missing } + bool simpleLighting = false; + node.getUserValue("simpleLighting", simpleLighting); + defineMap["simpleLighting"] = simpleLighting ? "1" : "0"; + if (writableStateSet->getMode(GL_ALPHA_TEST) != osg::StateAttribute::INHERIT && !previousAddedState->hasMode(GL_ALPHA_TEST)) removedState->setMode(GL_ALPHA_TEST, writableStateSet->getMode(GL_ALPHA_TEST)); // This disables the deprecated fixed-function alpha test diff --git a/files/shaders/CMakeLists.txt b/files/shaders/CMakeLists.txt index 04446d2982..d86719f318 100644 --- a/files/shaders/CMakeLists.txt +++ b/files/shaders/CMakeLists.txt @@ -36,6 +36,9 @@ set(SHADER_FILES gui_fragment.glsl debug_vertex.glsl debug_fragment.glsl + sky_vertex.glsl + sky_fragment.glsl + skypasses.glsl ) copy_all_resource_files(${CMAKE_CURRENT_SOURCE_DIR} ${OPENMW_SHADERS_ROOT} ${DDIRRELATIVE} "${SHADER_FILES}") diff --git a/files/shaders/lighting.glsl b/files/shaders/lighting.glsl index 177017f822..dc9c297a2f 100644 --- a/files/shaders/lighting.glsl +++ b/files/shaders/lighting.glsl @@ -87,6 +87,16 @@ void doLighting(vec3 viewPos, vec3 viewNormal, out vec3 diffuseLight, out vec3 a } } +// Simplest lighting which only takes into account sun and ambient. Currently used for our weather particle systems. +void doSimpleLighting(vec3 viewPos, vec3 viewNormal, out vec3 diffuseLight, out vec3 ambientLight) +{ + vec3 ambientOut, diffuseOut; + + perLightSun(diffuseOut, viewPos, viewNormal); + ambientLight = gl_LightModel.ambient.xyz; + diffuseLight = diffuseOut; +} + vec3 getSpecular(vec3 viewNormal, vec3 viewDirection, float shininess, vec3 matSpec) { vec3 lightDir = normalize(lcalcPosition(0)); diff --git a/files/shaders/objects_fragment.glsl b/files/shaders/objects_fragment.glsl index 6f6cede4e4..26107abb10 100644 --- a/files/shaders/objects_fragment.glsl +++ b/files/shaders/objects_fragment.glsl @@ -63,7 +63,7 @@ uniform bool simpleWater; varying float euclideanDepth; varying float linearDepth; -#define PER_PIXEL_LIGHTING (@normalMap || @forcePPL) +#define PER_PIXEL_LIGHTING ((@normalMap || @forcePPL) && !@simpleLighting) #if !PER_PIXEL_LIGHTING centroid varying vec3 passLighting; @@ -170,6 +170,9 @@ void main() #endif float shadowing = unshadowedLightRatio(linearDepth); +#if @simpleLighting + gl_FragData[0].xyz *= passLighting; +#else vec3 lighting; #if !PER_PIXEL_LIGHTING lighting = passLighting + shadowDiffuseLighting * shadowing; @@ -180,8 +183,8 @@ void main() lighting = diffuseColor.xyz * diffuseLight + getAmbientColor().xyz * ambientLight + emission; clampLightingResult(lighting); #endif - gl_FragData[0].xyz *= lighting; +#endif #if @envMap && !@preLightEnv gl_FragData[0].xyz += texture2D(envMap, envTexCoordGen).xyz * envMapColor.xyz * envLuma; @@ -207,6 +210,7 @@ void main() #endif gl_FragData[0].xyz += getSpecular(normalize(viewNormal), normalize(passViewPos.xyz), shininess, matSpec) * shadowing; } + #if @radialFog float depth; // For the less detailed mesh of simple water we need to recalculate depth on per-pixel basis diff --git a/files/shaders/objects_vertex.glsl b/files/shaders/objects_vertex.glsl index 969fe5903e..5f208ffc25 100644 --- a/files/shaders/objects_vertex.glsl +++ b/files/shaders/objects_vertex.glsl @@ -50,7 +50,7 @@ varying vec2 specularMapUV; varying float euclideanDepth; varying float linearDepth; -#define PER_PIXEL_LIGHTING (@normalMap || @forcePPL) +#define PER_PIXEL_LIGHTING ((@normalMap || @forcePPL) && !@simpleLighting) #if !PER_PIXEL_LIGHTING centroid varying vec3 passLighting; @@ -126,7 +126,11 @@ void main(void) #if !PER_PIXEL_LIGHTING vec3 diffuseLight, ambientLight; - doLighting(viewPos.xyz, viewNormal, diffuseLight, ambientLight, shadowDiffuseLighting); +#if @simpleLighting + doSimpleLighting(passViewPos, viewNormal, diffuseLight, ambientLight); +#else + doLighting(passViewPos, viewNormal, diffuseLight, ambientLight, shadowDiffuseLighting); +#endif vec3 emission = getEmissionColor().xyz * emissiveMult; passLighting = getDiffuseColor().xyz * diffuseLight + getAmbientColor().xyz * ambientLight + emission; clampLightingResult(passLighting); diff --git a/files/shaders/sky_fragment.glsl b/files/shaders/sky_fragment.glsl new file mode 100644 index 0000000000..c064619579 --- /dev/null +++ b/files/shaders/sky_fragment.glsl @@ -0,0 +1,84 @@ +#version 120 + +#include "skypasses.glsl" + +uniform int pass; +uniform sampler2D diffuseMap; +uniform sampler2D maskMap; // PASS_MOON +uniform float opacity; // PASS_CLOUDS, PASS_ATMOSPHERE_NIGHT +uniform vec4 moonBlend; // PASS_MOON +uniform vec4 atmosphereFade; // PASS_MOON + +varying vec2 diffuseMapUV; +varying vec4 passColor; + +void paintAtmosphere(inout vec4 color) +{ + color = gl_FrontMaterial.emission; + color.a *= passColor.a; +} + +void paintAtmosphereNight(inout vec4 color) +{ + color = texture2D(diffuseMap, diffuseMapUV); + color.a *= passColor.a * opacity; +} + +void paintClouds(inout vec4 color) +{ + color = texture2D(diffuseMap, diffuseMapUV); + color.a *= passColor.a * opacity; + color.xyz = clamp(color.xyz * gl_FrontMaterial.emission.xyz, 0.0, 1.0); +} + +void paintMoon(inout vec4 color) +{ + vec4 phase = texture2D(diffuseMap, diffuseMapUV); + vec4 mask = texture2D(maskMap, diffuseMapUV); + + vec4 blendedLayer = phase * moonBlend; + color = vec4(blendedLayer.xyz + atmosphereFade.xyz, atmosphereFade.a * mask.a); +} + +void paintSun(inout vec4 color) +{ + color = texture2D(diffuseMap, diffuseMapUV); + color.a *= gl_FrontMaterial.diffuse.a; +} + +void paintSunflashQuery(inout vec4 color) +{ + const float threshold = 0.8; + + color = texture2D(diffuseMap, diffuseMapUV); + if (color.a <= threshold) + discard; +} + +void paintSunglare(inout vec4 color) +{ + color = gl_FrontMaterial.emission; + color.a = gl_FrontMaterial.diffuse.a; +} + +void main() +{ + vec4 color = vec4(0.0); + + if (pass == PASS_ATMOSPHERE) + paintAtmosphere(color); + else if (pass == PASS_ATMOSPHERE_NIGHT) + paintAtmosphereNight(color); + else if (pass == PASS_CLOUDS) + paintClouds(color); + else if (pass == PASS_MOON) + paintMoon(color); + else if (pass == PASS_SUN) + paintSun(color); + else if (pass == PASS_SUNFLASH_QUERY) + paintSunflashQuery(color); + else if (pass == PASS_SUNGLARE) + paintSunglare(color); + + gl_FragData[0] = color; +} diff --git a/files/shaders/sky_vertex.glsl b/files/shaders/sky_vertex.glsl new file mode 100644 index 0000000000..9c676140ac --- /dev/null +++ b/files/shaders/sky_vertex.glsl @@ -0,0 +1,20 @@ +#version 120 + +#include "skypasses.glsl" + +uniform mat4 projectionMatrix; +uniform int pass; + +varying vec4 passColor; +varying vec2 diffuseMapUV; + +void main() +{ + gl_Position = projectionMatrix * (gl_ModelViewMatrix * gl_Vertex); + passColor = gl_Color; + + if (pass == PASS_CLOUDS) + diffuseMapUV = (gl_TextureMatrix[0] * gl_MultiTexCoord0).xy; + else + diffuseMapUV = gl_MultiTexCoord0.xy; +} diff --git a/files/shaders/skypasses.glsl b/files/shaders/skypasses.glsl new file mode 100644 index 0000000000..e80d4eb259 --- /dev/null +++ b/files/shaders/skypasses.glsl @@ -0,0 +1,7 @@ +#define PASS_ATMOSPHERE 0 +#define PASS_ATMOSPHERE_NIGHT 1 +#define PASS_CLOUDS 2 +#define PASS_MOON 3 +#define PASS_SUN 4 +#define PASS_SUNFLASH_QUERY 5 +#define PASS_SUNGLARE 6 From 9cbbd2fff56a69ea27091abbd687dc9e967f8b9b Mon Sep 17 00:00:00 2001 From: "glassmancody.info" Date: Sun, 24 Oct 2021 17:13:42 -0700 Subject: [PATCH 2/4] better transitions --- apps/openmw/mwrender/sky.cpp | 3 +++ files/shaders/sky_fragment.glsl | 3 +++ 2 files changed, 6 insertions(+) diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 217f10f294..9deb6968e3 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -659,7 +659,10 @@ namespace MWRender mParticleNode->addChild(program); for (int particleIndex = 0; particleIndex < ps->numParticles(); ++particleIndex) + { ps->getParticle(particleIndex)->setAlphaRange(osgParticle::rangef(mPrecipitationAlpha, mPrecipitationAlpha)); + ps->getParticle(particleIndex)->update(0, true); + } ps->getOrCreateStateSet(); ps->setUserValue("simpleLighting", true); diff --git a/files/shaders/sky_fragment.glsl b/files/shaders/sky_fragment.glsl index c064619579..1ef62ab2cf 100644 --- a/files/shaders/sky_fragment.glsl +++ b/files/shaders/sky_fragment.glsl @@ -29,6 +29,9 @@ void paintClouds(inout vec4 color) color = texture2D(diffuseMap, diffuseMapUV); color.a *= passColor.a * opacity; color.xyz = clamp(color.xyz * gl_FrontMaterial.emission.xyz, 0.0, 1.0); + + // ease transition between clear color and atmosphere/clouds + color = mix(vec4(gl_Fog.color.xyz, color.a), color, passColor.a * passColor.a); } void paintMoon(inout vec4 color) From 07e32c0fa6be9fa7129c1002b77deb469803d248 Mon Sep 17 00:00:00 2001 From: "glassmancody.info" Date: Mon, 25 Oct 2021 10:23:16 -0700 Subject: [PATCH 3/4] remove object shader path --- apps/openmw/mwworld/weather.cpp | 1 - components/shader/shadervisitor.cpp | 6 +++++- files/shaders/lighting.glsl | 10 ---------- files/shaders/objects_fragment.glsl | 8 ++------ files/shaders/objects_vertex.glsl | 8 ++------ files/shaders/sky_fragment.glsl | 2 +- 6 files changed, 10 insertions(+), 25 deletions(-) diff --git a/apps/openmw/mwworld/weather.cpp b/apps/openmw/mwworld/weather.cpp index 9055b95ee1..965c690238 100644 --- a/apps/openmw/mwworld/weather.cpp +++ b/apps/openmw/mwworld/weather.cpp @@ -1273,4 +1273,3 @@ namespace MWWorld } } - diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index dcd4874e90..9877ab1863 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -518,7 +518,11 @@ namespace Shader bool simpleLighting = false; node.getUserValue("simpleLighting", simpleLighting); - defineMap["simpleLighting"] = simpleLighting ? "1" : "0"; + if (simpleLighting) + { + defineMap["forcePPL"] = "1"; + defineMap["endLight"] = "0"; + } if (writableStateSet->getMode(GL_ALPHA_TEST) != osg::StateAttribute::INHERIT && !previousAddedState->hasMode(GL_ALPHA_TEST)) removedState->setMode(GL_ALPHA_TEST, writableStateSet->getMode(GL_ALPHA_TEST)); diff --git a/files/shaders/lighting.glsl b/files/shaders/lighting.glsl index dc9c297a2f..177017f822 100644 --- a/files/shaders/lighting.glsl +++ b/files/shaders/lighting.glsl @@ -87,16 +87,6 @@ void doLighting(vec3 viewPos, vec3 viewNormal, out vec3 diffuseLight, out vec3 a } } -// Simplest lighting which only takes into account sun and ambient. Currently used for our weather particle systems. -void doSimpleLighting(vec3 viewPos, vec3 viewNormal, out vec3 diffuseLight, out vec3 ambientLight) -{ - vec3 ambientOut, diffuseOut; - - perLightSun(diffuseOut, viewPos, viewNormal); - ambientLight = gl_LightModel.ambient.xyz; - diffuseLight = diffuseOut; -} - vec3 getSpecular(vec3 viewNormal, vec3 viewDirection, float shininess, vec3 matSpec) { vec3 lightDir = normalize(lcalcPosition(0)); diff --git a/files/shaders/objects_fragment.glsl b/files/shaders/objects_fragment.glsl index 26107abb10..6f6cede4e4 100644 --- a/files/shaders/objects_fragment.glsl +++ b/files/shaders/objects_fragment.glsl @@ -63,7 +63,7 @@ uniform bool simpleWater; varying float euclideanDepth; varying float linearDepth; -#define PER_PIXEL_LIGHTING ((@normalMap || @forcePPL) && !@simpleLighting) +#define PER_PIXEL_LIGHTING (@normalMap || @forcePPL) #if !PER_PIXEL_LIGHTING centroid varying vec3 passLighting; @@ -170,9 +170,6 @@ void main() #endif float shadowing = unshadowedLightRatio(linearDepth); -#if @simpleLighting - gl_FragData[0].xyz *= passLighting; -#else vec3 lighting; #if !PER_PIXEL_LIGHTING lighting = passLighting + shadowDiffuseLighting * shadowing; @@ -183,8 +180,8 @@ void main() lighting = diffuseColor.xyz * diffuseLight + getAmbientColor().xyz * ambientLight + emission; clampLightingResult(lighting); #endif + gl_FragData[0].xyz *= lighting; -#endif #if @envMap && !@preLightEnv gl_FragData[0].xyz += texture2D(envMap, envTexCoordGen).xyz * envMapColor.xyz * envLuma; @@ -210,7 +207,6 @@ void main() #endif gl_FragData[0].xyz += getSpecular(normalize(viewNormal), normalize(passViewPos.xyz), shininess, matSpec) * shadowing; } - #if @radialFog float depth; // For the less detailed mesh of simple water we need to recalculate depth on per-pixel basis diff --git a/files/shaders/objects_vertex.glsl b/files/shaders/objects_vertex.glsl index 5f208ffc25..969fe5903e 100644 --- a/files/shaders/objects_vertex.glsl +++ b/files/shaders/objects_vertex.glsl @@ -50,7 +50,7 @@ varying vec2 specularMapUV; varying float euclideanDepth; varying float linearDepth; -#define PER_PIXEL_LIGHTING ((@normalMap || @forcePPL) && !@simpleLighting) +#define PER_PIXEL_LIGHTING (@normalMap || @forcePPL) #if !PER_PIXEL_LIGHTING centroid varying vec3 passLighting; @@ -126,11 +126,7 @@ void main(void) #if !PER_PIXEL_LIGHTING vec3 diffuseLight, ambientLight; -#if @simpleLighting - doSimpleLighting(passViewPos, viewNormal, diffuseLight, ambientLight); -#else - doLighting(passViewPos, viewNormal, diffuseLight, ambientLight, shadowDiffuseLighting); -#endif + doLighting(viewPos.xyz, viewNormal, diffuseLight, ambientLight, shadowDiffuseLighting); vec3 emission = getEmissionColor().xyz * emissiveMult; passLighting = getDiffuseColor().xyz * diffuseLight + getAmbientColor().xyz * ambientLight + emission; clampLightingResult(passLighting); diff --git a/files/shaders/sky_fragment.glsl b/files/shaders/sky_fragment.glsl index 1ef62ab2cf..cfa3650c02 100644 --- a/files/shaders/sky_fragment.glsl +++ b/files/shaders/sky_fragment.glsl @@ -31,7 +31,7 @@ void paintClouds(inout vec4 color) color.xyz = clamp(color.xyz * gl_FrontMaterial.emission.xyz, 0.0, 1.0); // ease transition between clear color and atmosphere/clouds - color = mix(vec4(gl_Fog.color.xyz, color.a), color, passColor.a * passColor.a); + color = mix(vec4(gl_Fog.color.xyz, color.a), color, passColor.a); } void paintMoon(inout vec4 color) From ba4f1921abf22c0b1af85b52c1eebbfb8c780d47 Mon Sep 17 00:00:00 2001 From: "glassmancody.info" Date: Mon, 25 Oct 2021 15:04:55 -0700 Subject: [PATCH 4/4] overrides --- apps/openmw/mwrender/sky.cpp | 6 ++-- apps/openmw/mwrender/skyutil.cpp | 58 ++++++++++++++++---------------- 2 files changed, 32 insertions(+), 32 deletions(-) diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 9deb6968e3..eb7c4a3882 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -340,7 +340,7 @@ namespace MWRender auto depth = SceneUtil::createDepth(); depth->setWriteMask(false); - mEarlyRenderBinRoot->getOrCreateStateSet()->setAttributeAndModes(depth, osg::StateAttribute::ON); + mEarlyRenderBinRoot->getOrCreateStateSet()->setAttributeAndModes(depth); mEarlyRenderBinRoot->getOrCreateStateSet()->setMode(GL_BLEND, osg::StateAttribute::ON); mEarlyRenderBinRoot->getOrCreateStateSet()->setMode(GL_FOG, osg::StateAttribute::OFF); @@ -374,7 +374,7 @@ namespace MWRender raindropTex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); raindropTex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); - stateset->setTextureAttributeAndModes(0, raindropTex, osg::StateAttribute::ON); + stateset->setTextureAttributeAndModes(0, raindropTex); stateset->setNestRenderBins(false); stateset->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); stateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); @@ -384,7 +384,7 @@ namespace MWRender mat->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1)); mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1)); mat->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE); - stateset->setAttributeAndModes(mat, osg::StateAttribute::ON); + stateset->setAttributeAndModes(mat); osgParticle::Particle& particleTemplate = mRainParticleSystem->getDefaultParticleTemplate(); particleTemplate.setSizeRange(osgParticle::rangef(5.f, 15.f)); diff --git a/apps/openmw/mwrender/skyutil.cpp b/apps/openmw/mwrender/skyutil.cpp index ae689547fd..9932733fa1 100644 --- a/apps/openmw/mwrender/skyutil.cpp +++ b/apps/openmw/mwrender/skyutil.cpp @@ -126,7 +126,7 @@ namespace MWRender void setDefaults(osg::StateSet* stateset) override { - stateset->setAttributeAndModes(MWRender::createUnlitMaterial(), osg::StateAttribute::ON); + stateset->setAttributeAndModes(MWRender::createUnlitMaterial()); } void apply(osg::StateSet* stateset, osg::NodeVisitor*) override @@ -292,7 +292,7 @@ namespace MWRender mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0,0,0,fade)); mat->setEmission(osg::Material::FRONT_AND_BACK, mColor); - stateset->setAttributeAndModes(mat, osg::StateAttribute::ON); + stateset->setAttributeAndModes(mat); cv->pushStateSet(stateset); traverse(node, cv); @@ -359,9 +359,9 @@ namespace MWRender { if (mForceShaders) { - stateset->addUniform(new osg::Uniform("pass", static_cast(Pass::Moon)), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); - stateset->setTextureAttributeAndModes(0, mPhaseTex, osg::StateAttribute::ON); - stateset->setTextureAttributeAndModes(1, mCircleTex, osg::StateAttribute::ON); + stateset->addUniform(new osg::Uniform("pass", static_cast(Pass::Moon))); + stateset->setTextureAttributeAndModes(0, mPhaseTex); + stateset->setTextureAttributeAndModes(1, mCircleTex); stateset->setTextureMode(0, GL_TEXTURE_2D, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); stateset->setTextureMode(1, GL_TEXTURE_2D, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); stateset->addUniform(new osg::Uniform("moonBlend", osg::Vec4f{})); @@ -372,15 +372,15 @@ namespace MWRender } else { - stateset->setTextureAttributeAndModes(0, mPhaseTex, osg::StateAttribute::ON); + stateset->setTextureAttributeAndModes(0, mPhaseTex); osg::ref_ptr texEnv = new osg::TexEnvCombine; texEnv->setCombine_RGB(osg::TexEnvCombine::MODULATE); texEnv->setSource0_RGB(osg::TexEnvCombine::CONSTANT); texEnv->setSource1_RGB(osg::TexEnvCombine::TEXTURE); texEnv->setConstantColor(osg::Vec4f(1.f, 0.f, 0.f, 1.f)); // mShadowBlend * mMoonColor - stateset->setTextureAttributeAndModes(0, texEnv, osg::StateAttribute::ON); + stateset->setTextureAttributeAndModes(0, texEnv); - stateset->setTextureAttributeAndModes(1, mCircleTex, osg::StateAttribute::ON); + stateset->setTextureAttributeAndModes(1, mCircleTex); osg::ref_ptr texEnv2 = new osg::TexEnvCombine; texEnv2->setCombine_RGB(osg::TexEnvCombine::ADD); texEnv2->setCombine_Alpha(osg::TexEnvCombine::MODULATE); @@ -389,7 +389,7 @@ namespace MWRender texEnv2->setSource0_RGB(osg::TexEnvCombine::PREVIOUS); texEnv2->setSource1_RGB(osg::TexEnvCombine::CONSTANT); texEnv2->setConstantColor(osg::Vec4f(0.f, 0.f, 0.f, 1.f)); // mAtmosphereColor.rgb, mTransparency - stateset->setTextureAttributeAndModes(1, texEnv2, osg::StateAttribute::ON); + stateset->setTextureAttributeAndModes(1, texEnv2); stateset->setAttributeAndModes(MWRender::createUnlitMaterial(), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); } } @@ -477,7 +477,7 @@ namespace MWRender void AtmosphereUpdater::setDefaults(osg::StateSet* stateset) { stateset->setAttributeAndModes(createAlphaTrackingUnlitMaterial(), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); - stateset->addUniform(new osg::Uniform("pass", static_cast(Pass::Atmosphere)), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + stateset->addUniform(new osg::Uniform("pass", static_cast(Pass::Atmosphere))); } void AtmosphereUpdater::apply(osg::StateSet* stateset, osg::NodeVisitor* /*nv*/) @@ -501,8 +501,8 @@ namespace MWRender { if (mForceShaders) { - stateset->addUniform(new osg::Uniform("opacity", 0.f), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); - stateset->addUniform(new osg::Uniform("pass", static_cast(Pass::Atmosphere_Night)), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + stateset->addUniform(new osg::Uniform("opacity", 0.f)); + stateset->addUniform(new osg::Uniform("pass", static_cast(Pass::Atmosphere_Night))); } else { @@ -561,18 +561,18 @@ namespace MWRender stateset->setAttribute(createAlphaTrackingUnlitMaterial(), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); osg::ref_ptr texmat = new osg::TexMat; - stateset->setTextureAttributeAndModes(0, texmat, osg::StateAttribute::ON); + stateset->setTextureAttributeAndModes(0, texmat); if (mForceShaders) { stateset->setTextureAttribute(0, mTexture, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); - stateset->addUniform(new osg::Uniform("opacity", 1.f), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); - stateset->addUniform(new osg::Uniform("pass", static_cast(Pass::Clouds)), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + stateset->addUniform(new osg::Uniform("opacity", 1.f)); + stateset->addUniform(new osg::Uniform("pass", static_cast(Pass::Clouds))); } else { - stateset->setTextureAttributeAndModes(1, texmat, osg::StateAttribute::ON); + stateset->setTextureAttributeAndModes(1, texmat); // need to set opacity on a separate texture unit, diffuse alpha is used by the vertex colors already osg::ref_ptr texEnvCombine = new osg::TexEnvCombine; texEnvCombine->setSource0_RGB(osg::TexEnvCombine::PREVIOUS); @@ -582,7 +582,7 @@ namespace MWRender texEnvCombine->setCombine_Alpha(osg::TexEnvCombine::MODULATE); texEnvCombine->setCombine_RGB(osg::TexEnvCombine::REPLACE); - stateset->setTextureAttributeAndModes(1, texEnvCombine, osg::StateAttribute::ON); + stateset->setTextureAttributeAndModes(1, texEnvCombine); stateset->setTextureMode(0, GL_TEXTURE_2D, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); stateset->setTextureMode(1, GL_TEXTURE_2D, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); @@ -715,8 +715,8 @@ namespace MWRender sunTex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); sunTex->setName("diffuseMap"); - mGeom->getOrCreateStateSet()->setTextureAttributeAndModes(0, sunTex, osg::StateAttribute::ON); - mGeom->getOrCreateStateSet()->addUniform(new osg::Uniform("pass", static_cast(Pass::Sun)), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + mGeom->getOrCreateStateSet()->setTextureAttributeAndModes(0, sunTex); + mGeom->getOrCreateStateSet()->addUniform(new osg::Uniform("pass", static_cast(Pass::Sun))); osg::ref_ptr queryNode = new osg::Group; // Need to render after the world geometry so we can correctly test for occlusions @@ -726,14 +726,14 @@ namespace MWRender // Set up alpha testing on the occlusion testing subgraph, that way we can get the occlusion tested fragments to match the circular shape of the sun osg::ref_ptr alphaFunc = new osg::AlphaFunc; alphaFunc->setFunction(osg::AlphaFunc::GREATER, 0.8); - stateset->setAttributeAndModes(alphaFunc, osg::StateAttribute::ON); - stateset->setTextureAttributeAndModes(0, sunTex, osg::StateAttribute::ON); - stateset->setAttributeAndModes(createUnlitMaterial(), osg::StateAttribute::ON); - stateset->addUniform(new osg::Uniform("pass", static_cast(Pass::Sunflash_Query)), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + stateset->setAttributeAndModes(alphaFunc); + stateset->setTextureAttributeAndModes(0, sunTex); + stateset->setAttributeAndModes(createUnlitMaterial()); + stateset->addUniform(new osg::Uniform("pass", static_cast(Pass::Sunflash_Query))); // Disable writing to the color buffer. We are using this geometry for visibility tests only. osg::ref_ptr colormask = new osg::ColorMask(0, 0, 0, 0); - stateset->setAttributeAndModes(colormask, osg::StateAttribute::ON); + stateset->setAttributeAndModes(colormask); mTransform->addChild(queryNode); @@ -829,7 +829,7 @@ namespace MWRender depth->setZNear(far); depth->setZFar(far); depth->setWriteMask(false); - queryStateSet->setAttributeAndModes(depth, osg::StateAttribute::ON); + queryStateSet->setAttributeAndModes(depth); } else { @@ -859,11 +859,11 @@ namespace MWRender osg::StateSet* stateset = geom->getOrCreateStateSet(); - stateset->setTextureAttributeAndModes(0, tex, osg::StateAttribute::ON); + stateset->setTextureAttributeAndModes(0, tex); stateset->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); stateset->setRenderBinDetails(RenderBin_SunGlare, "RenderBin"); stateset->setNestRenderBins(false); - stateset->addUniform(new osg::Uniform("pass", static_cast(Pass::Sun)), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + stateset->addUniform(new osg::Uniform("pass", static_cast(Pass::Sun))); mSunFlashNode = group; @@ -898,13 +898,13 @@ namespace MWRender stateset->setRenderBinDetails(RenderBin_SunGlare, "RenderBin"); stateset->setNestRenderBins(false); stateset->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); - stateset->addUniform(new osg::Uniform("pass", static_cast(Pass::Sunglare)), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + stateset->addUniform(new osg::Uniform("pass", static_cast(Pass::Sunglare))); // set up additive blending osg::ref_ptr blendFunc = new osg::BlendFunc; blendFunc->setSource(osg::BlendFunc::SRC_ALPHA); blendFunc->setDestination(osg::BlendFunc::ONE); - stateset->setAttributeAndModes(blendFunc, osg::StateAttribute::ON); + stateset->setAttributeAndModes(blendFunc); mSunGlareCallback = new SunGlareCallback(mOcclusionQueryVisiblePixels, mOcclusionQueryTotalPixels, mTransform); mSunGlareNode = camera;