diff --git a/apps/opencs/model/world/scriptcontext.cpp b/apps/opencs/model/world/scriptcontext.cpp index bcbca4b28..f644ad37a 100644 --- a/apps/opencs/model/world/scriptcontext.cpp +++ b/apps/opencs/model/world/scriptcontext.cpp @@ -39,8 +39,6 @@ char CSMWorld::ScriptContext::getGlobalType (const std::string& name) const std::pair CSMWorld::ScriptContext::getMemberType (const std::string& name, const std::string& id) const { - /// \todo invalidate locals cache on change to scripts - std::string id2 = Misc::StringUtils::lowerCase (id); int index = mData.getScripts().searchId (id2); @@ -120,3 +118,18 @@ void CSMWorld::ScriptContext::clear() mIdsUpdated = false; mLocals.clear(); } + +bool CSMWorld::ScriptContext::clearLocals (const std::string& script) +{ + std::map::iterator iter = + mLocals.find (Misc::StringUtils::lowerCase (script)); + + if (iter!=mLocals.end()) + { + mLocals.erase (iter); + mIdsUpdated = false; + return true; + } + + return false; +} diff --git a/apps/opencs/model/world/scriptcontext.hpp b/apps/opencs/model/world/scriptcontext.hpp index 29ee42645..2cd59f070 100644 --- a/apps/opencs/model/world/scriptcontext.hpp +++ b/apps/opencs/model/world/scriptcontext.hpp @@ -46,6 +46,9 @@ namespace CSMWorld void clear(); ///< Remove all cached data. + + /// \return Were there any locals that needed clearing? + bool clearLocals (const std::string& script); }; } diff --git a/apps/opencs/view/render/pagedworldspacewidget.cpp b/apps/opencs/view/render/pagedworldspacewidget.cpp index 274c77620..b0c1e74a9 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.cpp +++ b/apps/opencs/view/render/pagedworldspacewidget.cpp @@ -170,7 +170,10 @@ void CSVRender::PagedWorldspaceWidget::referenceAdded (const QModelIndex& parent std::string CSVRender::PagedWorldspaceWidget::getStartupInstruction() { - osg::Vec3d position = mView->getCamera()->getViewMatrix().getTrans(); + osg::Vec3d eye, center, up; + mView->getCamera()->getViewMatrixAsLookAt(eye, center, up); + osg::Vec3d position = eye; + std::ostringstream stream; stream diff --git a/apps/opencs/view/render/unpagedworldspacewidget.cpp b/apps/opencs/view/render/unpagedworldspacewidget.cpp index a5733ad10..3e1733c81 100644 --- a/apps/opencs/view/render/unpagedworldspacewidget.cpp +++ b/apps/opencs/view/render/unpagedworldspacewidget.cpp @@ -166,7 +166,9 @@ void CSVRender::UnpagedWorldspaceWidget::addVisibilitySelectorButtons ( std::string CSVRender::UnpagedWorldspaceWidget::getStartupInstruction() { - osg::Vec3d position = mView->getCamera()->getViewMatrix().getTrans(); + osg::Vec3d eye, center, up; + mView->getCamera()->getViewMatrixAsLookAt(eye, center, up); + osg::Vec3d position = eye; std::ostringstream stream; diff --git a/apps/opencs/view/world/scripterrortable.cpp b/apps/opencs/view/world/scripterrortable.cpp index b44e1c0bd..a9e315c73 100644 --- a/apps/opencs/view/world/scripterrortable.cpp +++ b/apps/opencs/view/world/scripterrortable.cpp @@ -131,6 +131,11 @@ void CSVWorld::ScriptErrorTable::clear() setRowCount (0); } +bool CSVWorld::ScriptErrorTable::clearLocals (const std::string& script) +{ + return mContext.clearLocals (script); +} + void CSVWorld::ScriptErrorTable::cellClicked (int row, int column) { if (item (row, 1)) diff --git a/apps/opencs/view/world/scripterrortable.hpp b/apps/opencs/view/world/scripterrortable.hpp index 98db425cf..33af7c864 100644 --- a/apps/opencs/view/world/scripterrortable.hpp +++ b/apps/opencs/view/world/scripterrortable.hpp @@ -44,6 +44,11 @@ namespace CSVWorld void clear(); + /// Clear local variable cache for \a script. + /// + /// \return Were there any locals that needed clearing? + bool clearLocals (const std::string& script); + private slots: void cellClicked (int row, int column); diff --git a/apps/opencs/view/world/scriptsubview.cpp b/apps/opencs/view/world/scriptsubview.cpp index d405d1765..d99f789b3 100644 --- a/apps/opencs/view/world/scriptsubview.cpp +++ b/apps/opencs/view/world/scriptsubview.cpp @@ -89,6 +89,7 @@ CSVWorld::ScriptSubView::ScriptSubView (const CSMWorld::UniversalId& id, CSMDoc: *document.getData().getTableModel (CSMWorld::UniversalId::Type_Scripts)); mColumn = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_ScriptText); + mIdColumn = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Id); mStateColumn = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Modification); QString source = mModel->data (mModel->getModelIndex (id.getId(), mColumn)).toString(); @@ -241,6 +242,15 @@ void CSVWorld::ScriptSubView::dataChanged (const QModelIndex& topLeft, const QMo ScriptEdit::ChangeLock lock (*mEditor); + bool updateRequired = false; + + for (int i=topLeft.row(); i<=bottomRight.row(); ++i) + { + std::string id = mModel->data (mModel->index (i, mIdColumn)).toString().toUtf8().constData(); + if (mErrors->clearLocals (id)) + updateRequired = true; + } + QModelIndex index = mModel->getModelIndex (getUniversalId().getId(), mColumn); if (index.row()>=topLeft.row() && index.row()<=bottomRight.row()) @@ -256,13 +266,28 @@ void CSVWorld::ScriptSubView::dataChanged (const QModelIndex& topLeft, const QMo mEditor->setPlainText (source); mEditor->setTextCursor (cursor); - recompile(); + updateRequired = true; } } + + if (updateRequired) + recompile(); } void CSVWorld::ScriptSubView::rowsAboutToBeRemoved (const QModelIndex& parent, int start, int end) { + bool updateRequired = false; + + for (int i=start; i<=end; ++i) + { + std::string id = mModel->data (mModel->index (i, mIdColumn)).toString().toUtf8().constData(); + if (mErrors->clearLocals (id)) + updateRequired = true; + } + + if (updateRequired) + recompile(); + QModelIndex index = mModel->getModelIndex (getUniversalId().getId(), mColumn); if (!parent.isValid() && index.row()>=start && index.row()<=end) diff --git a/apps/opencs/view/world/scriptsubview.hpp b/apps/opencs/view/world/scriptsubview.hpp index 6125dd259..907dc7958 100644 --- a/apps/opencs/view/world/scriptsubview.hpp +++ b/apps/opencs/view/world/scriptsubview.hpp @@ -38,6 +38,7 @@ namespace CSVWorld CSMDoc::Document& mDocument; CSMWorld::IdTable *mModel; int mColumn; + int mIdColumn; int mStateColumn; TableBottomBox *mBottom; RecordButtonBar *mButtons; diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 2dd16a4e6..5a9b52cb2 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -23,6 +23,7 @@ add_openmw_dir (mwrender actors objects renderingmanager animation rotatecontroller sky npcanimation vismask creatureanimation effectmanager util renderinginterface pathgrid rendermode weaponanimation bulletdebugdraw globalmap characterpreview camera localmap water terrainstorage ripplesimulation + renderbin ) add_openmw_dir (mwinput diff --git a/apps/openmw/mwrender/renderbin.hpp b/apps/openmw/mwrender/renderbin.hpp new file mode 100644 index 000000000..63ccdd259 --- /dev/null +++ b/apps/openmw/mwrender/renderbin.hpp @@ -0,0 +1,20 @@ +#ifndef OPENMW_MWRENDER_RENDERBIN_H +#define OPENMW_MWRENDER_RENDERBIN_H + +namespace MWRender +{ + + /// Defines the render bin numbers used in the OpenMW scene graph. The bin with the lowest number is rendered first. + /// Beware of RenderBin nesting, in most cases you will want to use setNestRenderBins(false). + enum RenderBins + { + RenderBin_Sky = -1, + RenderBin_Default = 0, + RenderBin_Water = 9, + RenderBin_OcclusionQuery = 10, + RenderBin_SunGlare = 11 + }; + +} + +#endif diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index dba2e5b0e..317f2229d 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -11,6 +11,11 @@ #include #include #include +#include +#include +#include +#include +#include #include #include @@ -38,6 +43,7 @@ #include "../mwworld/fallback.hpp" #include "vismask.hpp" +#include "renderbin.hpp" namespace { @@ -359,19 +365,57 @@ class Sun : public CelestialBody public: Sun(osg::Group* parentNode, Resource::TextureManager& textureManager) : CelestialBody(parentNode, 1.0f, 1) - , mUpdater(new Updater(textureManager)) + , mUpdater(new Updater) { - mGeode->addUpdateCallback(mUpdater); + mTransform->addUpdateCallback(mUpdater); + + osg::ref_ptr sunTex = textureManager.getTexture2D("textures/tx_sun_05.dds", + osg::Texture::CLAMP, + osg::Texture::CLAMP); + + mGeode->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 + queryNode->getOrCreateStateSet()->setRenderBinDetails(RenderBin_OcclusionQuery, "RenderBin"); + queryNode->getOrCreateStateSet()->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); + queryNode->getOrCreateStateSet()->setAttributeAndModes(alphaFunc, osg::StateAttribute::ON); + queryNode->getOrCreateStateSet()->setTextureAttributeAndModes(0, sunTex, osg::StateAttribute::ON); + queryNode->getOrCreateStateSet()->setAttributeAndModes(createUnlitMaterial(), osg::StateAttribute::ON); + + mTransform->addChild(queryNode); + + mOcclusionQueryVisiblePixels = createOcclusionQueryNode(queryNode, true); + mOcclusionQueryTotalPixels = createOcclusionQueryNode(queryNode, false); + + createSunFlash(textureManager); + createSunGlare(); } ~Sun() { - mGeode->removeUpdateCallback(mUpdater); + 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(); } virtual void adjustTransparency(const float ratio) { mUpdater->mColor.a() = ratio; + if (mSunGlareCallback) + mSunGlareCallback->setGlareView(ratio); + if (mSunFlashCallback) + mSunFlashCallback->setGlareView(ratio); } void setDirection(const osg::Vec3f& direction) @@ -384,37 +428,390 @@ public: mTransform->setAttitude(quat); } + void setGlareTimeOfDayFade(float val) + { + if (mSunGlareCallback) + mSunGlareCallback->setTimeOfDayFade(val); + } + private: - struct Updater : public SceneUtil::StateSetUpdater + /// @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) { - Resource::TextureManager& mTextureManager; + osg::ref_ptr oqn = new osg::OcclusionQueryNode; + oqn->setQueriesEnabled(true); + + // 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. + oqn->getQueryGeometry()->setDataVariance(osg::Object::STATIC); + + osg::ref_ptr queryGeode = osg::clone(mGeode.get(), osg::CopyOp::DEEP_COPY_ALL); + // Disable writing to the color buffer. We are using this geode for visibility tests only. + osg::ref_ptr colormask (new osg::ColorMask(0, 0, 0, 0)); + queryGeode->getOrCreateStateSet()->setAttributeAndModes(colormask, osg::StateAttribute::ON); + + oqn->addChild(queryGeode); + + // Remove the default OFF|PROTECTED setting for texturing. We *want* to enable texturing for alpha testing purposes + oqn->getQueryStateSet()->removeTextureMode(0, GL_TEXTURE_2D); + + // Need to add texture coordinates so that texturing works. A bit ugly, relies on the vertex ordering + // used within OcclusionQueryNode. + osg::ref_ptr texCoordArray (new osg::Vec2Array); + for (int i=0; i<8; ++i) + { + texCoordArray->push_back(osg::Vec2(0,0)); + texCoordArray->push_back(osg::Vec2(1,0)); + texCoordArray->push_back(osg::Vec2(0,0)); + texCoordArray->push_back(osg::Vec2(1,0)); + texCoordArray->push_back(osg::Vec2(1,1)); + texCoordArray->push_back(osg::Vec2(0,1)); + texCoordArray->push_back(osg::Vec2(0,1)); + texCoordArray->push_back(osg::Vec2(1,1)); + } + + oqn->getQueryGeometry()->setTexCoordArray(0, texCoordArray, osg::Array::BIND_PER_VERTEX); + + if (queryVisible) + { + osg::ref_ptr depth (new osg::Depth); + depth->setFunction(osg::Depth::LESS); + // 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. + depth->setZNear(1.0); + depth->setZFar(1.0); + oqn->getQueryStateSet()->setAttributeAndModes(depth, osg::StateAttribute::ON); + } + else + { + oqn->getQueryStateSet()->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); + } + + parent->addChild(oqn); + + return oqn; + } + + void createSunFlash(Resource::TextureManager& textureManager) + { + osg::ref_ptr tex = textureManager.getTexture2D("textures/tx_sun_flash_grey_05.dds", + osg::Texture::CLAMP, + osg::Texture::CLAMP); + + osg::ref_ptr transform (new osg::PositionAttitudeTransform); + const float scale = 2.6f; + transform->setScale(osg::Vec3f(scale,scale,scale)); + + mTransform->addChild(transform); + + osg::ref_ptr geode (new osg::Geode); + transform->addChild(geode); + + geode->addDrawable(createTexturedQuad()); + + osg::StateSet* stateset = geode->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 = transform; + + mSunFlashCallback = new SunFlashCallback(mOcclusionQueryVisiblePixels, mOcclusionQueryTotalPixels); + mSunFlashNode->addCullCallback(mSunFlashCallback); + } + void destroySunFlash() + { + if (mSunFlashNode) + { + mSunFlashNode->removeCullCallback(mSunFlashCallback); + mSunFlashCallback = NULL; + } + } + + 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 geode (new osg::Geode); + osg::ref_ptr geom = osg::createTexturedQuadGeometry(osg::Vec3f(-1,-1,0), osg::Vec3f(2,0,0), osg::Vec3f(0,2,0)); + geode->addDrawable(geom); + + camera->addChild(geode); + + 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 = NULL; + } + } + + class Updater : public SceneUtil::StateSetUpdater + { + public: osg::Vec4f mColor; - Updater(Resource::TextureManager& textureManager) - : mTextureManager(textureManager) - , mColor(0.0f, 0.0f, 0.0f, 1.0f) + Updater() + : mColor(1.f, 1.f, 1.f, 1.f) { } virtual void setDefaults(osg::StateSet* stateset) { - osg::ref_ptr tex = mTextureManager.getTexture2D("textures/tx_sun_05.dds", - osg::Texture::CLAMP, - osg::Texture::CLAMP); - - stateset->setTextureAttributeAndModes(0, tex, osg::StateAttribute::ON); - stateset->setAttributeAndModes(createUnlitMaterial(), - osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); + stateset->setAttributeAndModes(createUnlitMaterial(), osg::StateAttribute::ON); } virtual void apply(osg::StateSet* stateset, osg::NodeVisitor*) { osg::Material* mat = static_cast(stateset->getAttribute(osg::StateAttribute::MATERIAL)); - mat->setDiffuse(osg::Material::FRONT_AND_BACK, mColor); + 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 osg::NodeCallback + { + public: + OcclusionCallback(osg::ref_ptr oqnVisible, osg::ref_ptr oqnTotal) + : mOcclusionQueryVisiblePixels(oqnVisible) + , mOcclusionQueryTotalPixels(oqnTotal) + { + } + + protected: + float 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; + } + + 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: + SunFlashCallback(osg::ref_ptr oqnVisible, osg::ref_ptr oqnTotal) + : OcclusionCallback(oqnVisible, oqnTotal) + , mGlareView(1.f) + { + } + + virtual void operator()(osg::Node* node, osg::NodeVisitor* nv) + { + osgUtil::CullVisitor* cv = static_cast(nv); + + 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); + } + + const float threshold = 0.6; + visibleRatio = visibleRatio * (1.f - threshold) + threshold; + } + + float scale = visibleRatio; + + if (scale == 0.f) + { + // no traverse + return; + } + else + { + osg::Matrix modelView = *cv->getModelViewMatrix(); + + modelView.preMultScale(osg::Vec3f(visibleRatio, visibleRatio, visibleRatio)); + + if (stateset) + cv->pushStateSet(stateset); + + cv->pushModelViewMatrix(new osg::RefMatrix(modelView), osg::Transform::RELATIVE_RF); + + traverse(node, nv); + + 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: + SunGlareCallback(osg::ref_ptr oqnVisible, osg::ref_ptr oqnTotal, + osg::ref_ptr sunTransform) + : OcclusionCallback(oqnVisible, oqnTotal) + , mSunTransform(sunTransform) + , mTimeOfDayFade(1.f) + , mGlareView(1.f) + { + + } + + virtual void operator ()(osg::Node* node, osg::NodeVisitor* nv) + { + osgUtil::CullVisitor* cv = static_cast(nv); + + float angleRadians = getAngleToSunInRadians(cv->getCurrentCamera()); + float visibleRatio = getVisibleRatio(cv->getCurrentCamera()); + + const float angleMaxRadians = osg::DegreesToRadians(30.f); // Sun Glare Fader Angle Max + + float value = 1.f - std::min(1.f, angleRadians / angleMaxRadians); + + const float sunGlareFaderMax = 0.5f; + float fade = value * sunGlareFaderMax; + + fade *= mTimeOfDayFade * mGlareView * visibleRatio; + + if (fade == 0.f) + { + // no traverse + return; + } + else + { + osg::ref_ptr stateset (new osg::StateSet); + + osg::ref_ptr mat (createUnlitMaterial()); + + osg::Vec4f sunGlareFaderColor (222/255.f, 95/255.f, 39/255.f, 1); + + // 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. + sunGlareFaderColor *= 2; + for (int i=0; i<3; ++i) + sunGlareFaderColor[i] = std::min(1.f, sunGlareFaderColor[i]); + + mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0,0,0,fade)); + mat->setEmission(osg::Material::FRONT_AND_BACK, sunGlareFaderColor); + + stateset->setAttributeAndModes(mat, osg::StateAttribute::ON); + + cv->pushStateSet(stateset); + traverse(node, nv); + cv->popStateSet(); + } + } + + void setTimeOfDayFade(float val) + { + mTimeOfDayFade = val; + } + + void setGlareView(float glareView) + { + mGlareView = glareView; + } + + private: + float getAngleToSunInRadians(osg::Camera* camera) const + { + osg::Vec3d eye, center, up; + camera->getViewMatrixAsLookAt(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::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 @@ -608,8 +1005,6 @@ SkyManager::SkyManager(osg::Group* parentNode, Resource::SceneManager* sceneMana , mCloudSpeed(0.0f) , mStarsOpacity(0.0f) , mRemainingTransitionTime(0.0f) - , mGlare(0.0f) - , mGlareFade(0.0f) , mRainEnabled(false) , mRainSpeed(0) , mRainFrequency(1) @@ -624,7 +1019,7 @@ SkyManager::SkyManager(osg::Group* parentNode, Resource::SceneManager* sceneMana mRootNode = skyroot; // By default render before the world is rendered - mRootNode->getOrCreateStateSet()->setRenderBinDetails(-1, "RenderBin"); + mRootNode->getOrCreateStateSet()->setRenderBinDetails(RenderBin_Sky, "RenderBin"); } void SkyManager::create() @@ -926,32 +1321,6 @@ void SkyManager::update(float duration) mCloudUpdater->setAnimationTimer(mCloudAnimationTimer); mCloudUpdater2->setAnimationTimer(mCloudAnimationTimer); - if (mSunEnabled) - { - // take 1/10 sec for fading the glare effect from invisible to full - if (mGlareFade > mGlare) - { - mGlareFade -= duration*10; - if (mGlareFade < mGlare) mGlareFade = mGlare; - } - else if (mGlareFade < mGlare) - { - mGlareFade += duration*10; - if (mGlareFade > mGlare) mGlareFade = mGlare; - } - - // increase the strength of the sun glare effect depending - // on how directly the player is looking at the sun - /* - Vector3 sun = mSunGlare->getPosition(); - Vector3 cam = mCamera->getRealDirection(); - const Degree angle = sun.angleBetween( cam ); - float val = 1- (angle.valueDegrees() / 180.f); - val = (val*val*val*val)*6; - mSunGlare->setSize(val * mGlareFade); - */ - } - // 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) @@ -1102,7 +1471,9 @@ void SkyManager::setWeather(const WeatherResult& weather) mMasser->adjustTransparency(weather.mGlareView); mSecunda->adjustTransparency(weather.mGlareView); - mSun->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) @@ -1114,31 +1485,12 @@ void SkyManager::setWeather(const WeatherResult& weather) mAtmosphereNightNode->setNodeMask(weather.mNight ? ~0 : 0); - - /* - float strength; - float timeofday_angle = std::abs(mSunGlare->getPosition().z/mSunGlare->getPosition().length()); - if (timeofday_angle <= 0.44) - strength = timeofday_angle/0.44f; - else - strength = 1.f; - - mSunGlare->setVisibility(weather.mGlareView * mGlareFade * strength); - - mSun->setVisibility(weather.mGlareView * strength); - */ - if (mRainFader) mRainFader->setAlpha(weather.mEffectFade * 0.6); // * Rain_Threshold? if (mParticleFader) mParticleFader->setAlpha(weather.mEffectFade); } -void SkyManager::setGlare(const float glare) -{ - mGlare = glare; -} - void SkyManager::sunEnable() { if (!mCreated) return; @@ -1163,8 +1515,6 @@ void SkyManager::setSunDirection(const osg::Vec3f& direction) if (!mCreated) return; mSun->setDirection(direction); - - //mSunGlare->setPosition(direction); } void SkyManager::setMasserState(const MoonState& state) @@ -1187,11 +1537,9 @@ void SkyManager::setDate(int day, int month) mMonth = month; } -void SkyManager::setGlareEnabled (bool enabled) +void SkyManager::setGlareTimeOfDayFade(float val) { - if (!mCreated || !mEnabled) - return; - //mSunGlare->setVisible (mSunEnabled && enabled); + mSun->setGlareTimeOfDayFade(val); } } diff --git a/apps/openmw/mwrender/sky.hpp b/apps/openmw/mwrender/sky.hpp index de27cf447..072083d27 100644 --- a/apps/openmw/mwrender/sky.hpp +++ b/apps/openmw/mwrender/sky.hpp @@ -48,8 +48,10 @@ namespace MWRender osg::Vec4f mSkyColor; + // sun light color osg::Vec4f mSunColor; + // alpha is the sun transparency osg::Vec4f mSunDiscColor; float mFogDepth; @@ -140,8 +142,7 @@ namespace MWRender void setMasserState(const MoonState& state); void setSecundaState(const MoonState& state); - void setGlare(const float glare); - void setGlareEnabled(bool enabled); + void setGlareTimeOfDayFade(float val); private: void create(); @@ -210,9 +211,6 @@ namespace MWRender float mRemainingTransitionTime; - float mGlare; // target - float mGlareFade; // actual - bool mRainEnabled; std::string mRainEffect; float mRainSpeed; diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index b5f141963..03ab58e6b 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -21,6 +21,7 @@ #include "vismask.hpp" #include "ripplesimulation.hpp" +#include "renderbin.hpp" namespace { @@ -86,7 +87,7 @@ namespace depth->setWriteMask(false); stateset->setAttributeAndModes(depth, osg::StateAttribute::ON); - stateset->setRenderBinDetails(9, "RenderBin"); + stateset->setRenderBinDetails(MWRender::RenderBin_Water, "RenderBin"); std::vector > textures; for (int i=0; i<32; ++i) diff --git a/apps/openmw/mwworld/weather.cpp b/apps/openmw/mwworld/weather.cpp index cdd0a8d79..78ae6fa0b 100644 --- a/apps/openmw/mwworld/weather.cpp +++ b/apps/openmw/mwworld/weather.cpp @@ -432,6 +432,7 @@ WeatherManager::WeatherManager(MWRender::RenderingManager& rendering, const MWWo , mSunsetTime(fallback.getFallbackFloat("Weather_Sunset_Time")) , mSunriseDuration(fallback.getFallbackFloat("Weather_Sunrise_Duration")) , mSunsetDuration(fallback.getFallbackFloat("Weather_Sunset_Duration")) + , mSunPreSunsetTime(fallback.getFallbackFloat("Weather_Sun_Pre-Sunset_Time")) , mNightStart(mSunsetTime + mSunsetDuration) , mNightEnd(mSunriseTime - 0.5f) , mDayStart(mSunriseTime + mSunriseDuration) @@ -458,16 +459,16 @@ WeatherManager::WeatherManager(MWRender::RenderingManager& rendering, const MWWo , mPlayingSoundID() { mWeatherSettings.reserve(10); - addWeather("Clear", fallback); - addWeather("Cloudy", fallback); - addWeather("Foggy", fallback); - addWeather("Overcast", fallback); - addWeather("Rain", fallback, "rain"); - addWeather("Thunderstorm", fallback, "rain heavy"); - addWeather("Ashstorm", fallback, "ashstorm", "meshes\\ashcloud.nif"); - addWeather("Blight", fallback, "blight", "meshes\\blightcloud.nif"); - addWeather("Snow", fallback, "", "meshes\\snow.nif"); - addWeather("Blizzard", fallback, "BM Blizzard", "meshes\\blizzard.nif"); + addWeather("Clear", fallback); // 0 + addWeather("Cloudy", fallback); // 1 + addWeather("Foggy", fallback); // 2 + addWeather("Overcast", fallback); // 3 + addWeather("Rain", fallback, "rain"); // 4 + addWeather("Thunderstorm", fallback, "rain heavy"); // 5 + addWeather("Ashstorm", fallback, "ashstorm", "meshes\\ashcloud.nif"); // 6 + addWeather("Blight", fallback, "blight", "meshes\\blightcloud.nif"); // 7 + addWeather("Snow", fallback, "", "meshes\\snow.nif"); // 8 + addWeather("Blizzard", fallback, "BM Blizzard", "meshes\\blizzard.nif"); // 9 Store::iterator it = store.get().begin(); for(; it != store.get().end(); ++it) @@ -624,6 +625,14 @@ void WeatherManager::update(float duration, bool paused) mRendering.setSunDirection( final * -1 ); } + float peakHour = mSunriseTime + (mSunsetTime - mSunriseTime) / 2; + if (time.getHour() < mSunriseTime || time.getHour() > mSunsetTime) + mRendering.getSkyManager()->setGlareTimeOfDayFade(0); + else if (time.getHour() < peakHour) + mRendering.getSkyManager()->setGlareTimeOfDayFade(1 - (peakHour - time.getHour()) / (peakHour - mSunriseTime)); + else + mRendering.getSkyManager()->setGlareTimeOfDayFade(1 - (time.getHour() - peakHour) / (mSunsetTime - peakHour)); + mRendering.getSkyManager()->setMasserState(mMasser.calculateState(time)); mRendering.getSkyManager()->setSecundaState(mSecunda.calculateState(time)); @@ -958,7 +967,6 @@ inline void WeatherManager::calculateResult(const int weatherID, const float gam mResult.mAmbientLoopSoundID = current.mAmbientLoopSoundID; mResult.mAmbientSoundVolume = 1.f; mResult.mEffectFade = 1.f; - mResult.mSunColor = current.mSunDiscSunsetColor; mResult.mIsStorm = current.mIsStorm; @@ -972,6 +980,7 @@ inline void WeatherManager::calculateResult(const int weatherID, const float gam mResult.mFogDepth = mResult.mNight ? current.mLandFogNightDepth : current.mLandFogDayDepth; + // TODO: use pre/post sunset/sunrise time values in [Weather] section // night if (gameHour <= mNightEnd || gameHour >= mNightStart + 1) { @@ -1042,6 +1051,36 @@ inline void WeatherManager::calculateResult(const int weatherID, const float gam mResult.mNightFade = factor; } } + + if (gameHour >= mSunsetTime - mSunPreSunsetTime) + { + float factor = (gameHour - (mSunsetTime - mSunPreSunsetTime)) / mSunPreSunsetTime; + factor = std::min(1.f, factor); + mResult.mSunDiscColor = lerp(osg::Vec4f(1,1,1,1), current.mSunDiscSunsetColor, factor); + // The SunDiscSunsetColor in the INI isn't exactly the resulting color on screen, most likely because + // MW applied the color to the ambient term as well. After the ambient and emissive terms are added together, the fixed pipeline + // would then clamp the total lighting to (1,1,1). A noticable change in color tone can be observed when only one of the color components gets clamped. + // Unfortunately that means we can't use the INI color as is, have to replicate the above nonsense. + mResult.mSunDiscColor = mResult.mSunDiscColor + osg::componentMultiply(mResult.mSunDiscColor, mResult.mAmbientColor); + for (int i=0; i<3; ++i) + mResult.mSunDiscColor[i] = std::min(1.f, mResult.mSunDiscColor[i]); + } + else + mResult.mSunDiscColor = osg::Vec4f(1,1,1,1); + + if (gameHour >= mSunsetTime) + { + float fade = std::min(1.f, (gameHour - mSunsetTime) / 2.f); + fade = fade*fade; + mResult.mSunDiscColor.a() = 1.f - fade; + } + else if (gameHour >= mSunriseTime && gameHour <= mSunriseTime + 1) + { + mResult.mSunDiscColor.a() = gameHour - mSunriseTime; + } + else + mResult.mSunDiscColor.a() = 1; + } inline void WeatherManager::calculateTransitionResult(const float factor, const float gameHour) diff --git a/apps/openmw/mwworld/weather.hpp b/apps/openmw/mwworld/weather.hpp index a0c93a460..7ce7c1bf8 100644 --- a/apps/openmw/mwworld/weather.hpp +++ b/apps/openmw/mwworld/weather.hpp @@ -75,7 +75,7 @@ namespace MWWorld float mLandFogDayDepth; float mLandFogNightDepth; - // Color modulation for the sun itself during sunset (not completely sure) + // Color modulation for the sun itself during sunset osg::Vec4f mSunDiscSunsetColor; // Used by scripts to animate signs, etc based on the wind (GetWindSpeed) @@ -242,12 +242,7 @@ namespace MWWorld float mSunsetTime; float mSunriseDuration; float mSunsetDuration; - // Some useful values - /* TODO: Use pre-sunrise_time, pre-sunset_time, - * post-sunrise_time, and post-sunset_time to better - * describe sunrise/sunset time. - * These values are fallbacks attached to weather. - */ + float mSunPreSunsetTime; float mNightStart; float mNightEnd; float mDayStart; diff --git a/components/sceneutil/statesetupdater.hpp b/components/sceneutil/statesetupdater.hpp index a4fcd7866..37d08e025 100644 --- a/components/sceneutil/statesetupdater.hpp +++ b/components/sceneutil/statesetupdater.hpp @@ -6,7 +6,7 @@ namespace SceneUtil { - /// @brief Implements efficient pre-frame updating of StateSets. + /// @brief Implements efficient per-frame updating of StateSets. /// @par With a naive update there would be race conditions when the OSG draw thread of the last frame /// queues up a StateSet that we want to modify for the next frame. To solve this we could set the StateSet to /// DYNAMIC data variance but that would undo all the benefits of the threading model - having the cull and draw