#include "water.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 "../mwworld/cellstore.hpp" #include "renderbin.hpp" #include "ripples.hpp" #include "ripplesimulation.hpp" #include "vismask.hpp" namespace MWRender { // -------------------------------------------------------------------------------------------------------------------------------- /// @brief Allows to cull and clip meshes that are below a plane. Useful for reflection & refraction camera effects. /// Also handles flipping of the plane when the eye point goes below it. /// To use, simply create the scene as subgraph of this node, then do setPlane(const osg::Plane& plane); class ClipCullNode : public osg::Group { class PlaneCullCallback : public SceneUtil::NodeCallback { public: /// @param cullPlane The culling plane (in world space). PlaneCullCallback(const osg::Plane* cullPlane) : mCullPlane(cullPlane) { } void operator()(osg::Node* node, osgUtil::CullVisitor* cv) { osg::Polytope::PlaneList origPlaneList = cv->getProjectionCullingStack().back().getFrustum().getPlaneList(); osg::Plane plane = *mCullPlane; plane.transform(*cv->getCurrentRenderStage()->getInitialViewMatrix()); osg::Vec3d eyePoint = cv->getEyePoint(); if (mCullPlane->intersect(osg::BoundingSphere(osg::Vec3d(0, 0, eyePoint.z()), 0)) > 0) plane.flip(); cv->getProjectionCullingStack().back().getFrustum().add(plane); traverse(node, cv); // undo cv->getProjectionCullingStack().back().getFrustum().set(origPlaneList); } private: const osg::Plane* mCullPlane; }; class FlipCallback : public SceneUtil::NodeCallback { public: FlipCallback(const osg::Plane* cullPlane) : mCullPlane(cullPlane) { } void operator()(osg::Node* node, osgUtil::CullVisitor* cv) { osg::Vec3d eyePoint = cv->getEyePoint(); osg::RefMatrix* modelViewMatrix = new osg::RefMatrix(*cv->getModelViewMatrix()); // apply the height of the plane // we can't apply this height in the addClipPlane() since the "flip the below graph" function would // otherwise flip the height as well modelViewMatrix->preMultTranslate(mCullPlane->getNormal() * ((*mCullPlane)[3] * -1)); // flip the below graph if the eye point is above the plane if (mCullPlane->intersect(osg::BoundingSphere(osg::Vec3d(0, 0, eyePoint.z()), 0)) > 0) { modelViewMatrix->preMultScale(osg::Vec3(1, 1, -1)); } // move the plane back along its normal a little bit to prevent bleeding at the water shore const float clipFudge = -5; modelViewMatrix->preMultTranslate(mCullPlane->getNormal() * clipFudge); cv->pushModelViewMatrix(modelViewMatrix, osg::Transform::RELATIVE_RF); traverse(node, cv); cv->popModelViewMatrix(); } private: const osg::Plane* mCullPlane; }; public: ClipCullNode() { addCullCallback(new PlaneCullCallback(&mPlane)); mClipNodeTransform = new osg::Group; mClipNodeTransform->addCullCallback(new FlipCallback(&mPlane)); osg::Group::addChild(mClipNodeTransform); mClipNode = new osg::ClipNode; mClipNodeTransform->addChild(mClipNode); } void setPlane(const osg::Plane& plane) { if (plane == mPlane) return; mPlane = plane; mClipNode->getClipPlaneList().clear(); mClipNode->addClipPlane( new osg::ClipPlane(0, osg::Plane(mPlane.getNormal(), 0))); // mPlane.d() applied in FlipCallback mClipNode->setStateSetModes(*getOrCreateStateSet(), osg::StateAttribute::ON); mClipNode->setCullingActive(false); } private: osg::ref_ptr mClipNodeTransform; osg::ref_ptr mClipNode; osg::Plane mPlane; }; /// This callback on the Camera has the effect of a RELATIVE_RF_INHERIT_VIEWPOINT transform mode (which does not /// exist in OSG). We want to keep the View Point of the parent camera so we will not have to recreate LODs. class InheritViewPointCallback : public SceneUtil::NodeCallback { public: InheritViewPointCallback() {} void operator()(osg::Node* node, osgUtil::CullVisitor* cv) { osg::ref_ptr modelViewMatrix = new osg::RefMatrix(*cv->getModelViewMatrix()); cv->popModelViewMatrix(); cv->pushModelViewMatrix(modelViewMatrix, osg::Transform::ABSOLUTE_RF_INHERIT_VIEWPOINT); traverse(node, cv); } }; /// Moves water mesh away from the camera slightly if the camera gets too close on the Z axis. /// The offset works around graphics artifacts that occurred with the GL_DEPTH_CLAMP when the camera gets extremely /// close to the mesh (seen on NVIDIA at least). Must be added as a Cull callback. class FudgeCallback : public SceneUtil::NodeCallback { public: void operator()(osg::Node* node, osgUtil::CullVisitor* cv) { const float fudge = 0.2; if (std::abs(cv->getEyeLocal().z()) < fudge) { float diff = fudge - cv->getEyeLocal().z(); osg::RefMatrix* modelViewMatrix = new osg::RefMatrix(*cv->getModelViewMatrix()); if (cv->getEyeLocal().z() > 0) modelViewMatrix->preMultTranslate(osg::Vec3f(0, 0, -diff)); else modelViewMatrix->preMultTranslate(osg::Vec3f(0, 0, diff)); cv->pushModelViewMatrix(modelViewMatrix, osg::Transform::RELATIVE_RF); traverse(node, cv); cv->popModelViewMatrix(); } else traverse(node, cv); } }; class RainIntensityUpdater : public SceneUtil::StateSetUpdater { public: RainIntensityUpdater() : mRainIntensity(0.f) { } void setRainIntensity(float rainIntensity) { mRainIntensity = rainIntensity; } protected: void setDefaults(osg::StateSet* stateset) override { osg::ref_ptr rainIntensityUniform = new osg::Uniform("rainIntensity", 0.0f); stateset->addUniform(rainIntensityUniform.get()); } void apply(osg::StateSet* stateset, osg::NodeVisitor* /*nv*/) override { osg::ref_ptr rainIntensityUniform = stateset->getUniform("rainIntensity"); if (rainIntensityUniform != nullptr) rainIntensityUniform->set(mRainIntensity); } private: float mRainIntensity; }; class Refraction : public SceneUtil::RTTNode { public: Refraction(uint32_t rttSize) : RTTNode(rttSize, rttSize, 0, false, 1, StereoAwareness::Aware) , mNodeMask(Refraction::sDefaultCullMask) { setDepthBufferInternalFormat(GL_DEPTH24_STENCIL8); mClipCullNode = new ClipCullNode; } void setDefaults(osg::Camera* camera) override { camera->setReferenceFrame(osg::Camera::RELATIVE_RF); camera->setSmallFeatureCullingPixelSize( Settings::Manager::getInt("small feature culling pixel size", "Water")); camera->setName("RefractionCamera"); camera->addCullCallback(new InheritViewPointCallback); camera->setComputeNearFarMode(osg::CullSettings::DO_NOT_COMPUTE_NEAR_FAR); // No need for fog here, we are already applying fog on the water surface itself as well as underwater fog // assign large value to effectively turn off fog // shaders don't respect glDisable(GL_FOG) osg::ref_ptr fog(new osg::Fog); fog->setStart(10000000); fog->setEnd(10000000); camera->getOrCreateStateSet()->setAttributeAndModes( fog, osg::StateAttribute::OFF | osg::StateAttribute::OVERRIDE); camera->addChild(mClipCullNode); camera->setNodeMask(Mask_RenderToTexture); if (Settings::Manager::getFloat("refraction scale", "Water") != 1) // TODO: to be removed with issue #5709 SceneUtil::ShadowManager::disableShadowsForStateSet(camera->getOrCreateStateSet()); } void apply(osg::Camera* camera) override { camera->setViewMatrix(mViewMatrix); camera->setCullMask(mNodeMask); } void setScene(osg::Node* scene) { if (mScene) mClipCullNode->removeChild(mScene); mScene = scene; mClipCullNode->addChild(scene); } void setWaterLevel(float waterLevel) { const float refractionScale = std::clamp(Settings::Manager::getFloat("refraction scale", "Water"), 0.f, 1.f); mViewMatrix = osg::Matrix::scale(1, 1, refractionScale) * osg::Matrix::translate(0, 0, (1.0 - refractionScale) * waterLevel); mClipCullNode->setPlane(osg::Plane(osg::Vec3d(0, 0, -1), osg::Vec3d(0, 0, waterLevel))); } void showWorld(bool show) { if (show) mNodeMask = Refraction::sDefaultCullMask; else mNodeMask = Refraction::sDefaultCullMask & ~sToggleWorldMask; } private: osg::ref_ptr mClipCullNode; osg::ref_ptr mScene; osg::Matrix mViewMatrix{ osg::Matrix::identity() }; unsigned int mNodeMask; static constexpr unsigned int sDefaultCullMask = Mask_Effect | Mask_Scene | Mask_Object | Mask_Static | Mask_Terrain | Mask_Actor | Mask_ParticleSystem | Mask_Sky | Mask_Sun | Mask_Player | Mask_Lighting | Mask_Groundcover; }; class Reflection : public SceneUtil::RTTNode { public: Reflection(uint32_t rttSize, bool isInterior) : RTTNode(rttSize, rttSize, 0, false, 0, StereoAwareness::Aware) { setInterior(isInterior); setDepthBufferInternalFormat(GL_DEPTH24_STENCIL8); mClipCullNode = new ClipCullNode; } void setDefaults(osg::Camera* camera) override { camera->setReferenceFrame(osg::Camera::RELATIVE_RF); camera->setSmallFeatureCullingPixelSize( Settings::Manager::getInt("small feature culling pixel size", "Water")); camera->setName("ReflectionCamera"); camera->addCullCallback(new InheritViewPointCallback); // Inform the shader that we're in a reflection camera->getOrCreateStateSet()->addUniform(new osg::Uniform("isReflection", true)); // XXX: should really flip the FrontFace on each renderable instead of forcing clockwise. osg::ref_ptr frontFace(new osg::FrontFace); frontFace->setMode(osg::FrontFace::CLOCKWISE); camera->getOrCreateStateSet()->setAttributeAndModes(frontFace, osg::StateAttribute::ON); camera->addChild(mClipCullNode); camera->setNodeMask(Mask_RenderToTexture); SceneUtil::ShadowManager::disableShadowsForStateSet(camera->getOrCreateStateSet()); } void apply(osg::Camera* camera) override { camera->setViewMatrix(mViewMatrix); camera->setCullMask(mNodeMask); } void setInterior(bool isInterior) { mInterior = isInterior; mNodeMask = calcNodeMask(); } void setWaterLevel(float waterLevel) { mViewMatrix = osg::Matrix::scale(1, 1, -1) * osg::Matrix::translate(0, 0, 2 * waterLevel); mClipCullNode->setPlane(osg::Plane(osg::Vec3d(0, 0, 1), osg::Vec3d(0, 0, waterLevel))); } void setScene(osg::Node* scene) { if (mScene) mClipCullNode->removeChild(mScene); mScene = scene; mClipCullNode->addChild(scene); } void showWorld(bool show) { if (show) mNodeMask = calcNodeMask(); else mNodeMask = calcNodeMask() & ~sToggleWorldMask; } private: unsigned int calcNodeMask() { int reflectionDetail = Settings::Manager::getInt("reflection detail", "Water"); reflectionDetail = std::clamp(reflectionDetail, mInterior ? 2 : 0, 5); unsigned int extraMask = 0; if (reflectionDetail >= 1) extraMask |= Mask_Terrain; if (reflectionDetail >= 2) extraMask |= Mask_Static; if (reflectionDetail >= 3) extraMask |= Mask_Effect | Mask_ParticleSystem | Mask_Object; if (reflectionDetail >= 4) extraMask |= Mask_Player | Mask_Actor; if (reflectionDetail >= 5) extraMask |= Mask_Groundcover; return Mask_Scene | Mask_Sky | Mask_Lighting | extraMask; } osg::ref_ptr mClipCullNode; osg::ref_ptr mScene; osg::Node::NodeMask mNodeMask; osg::Matrix mViewMatrix{ osg::Matrix::identity() }; bool mInterior; }; /// DepthClampCallback enables GL_DEPTH_CLAMP for the current draw, if supported. class DepthClampCallback : public osg::Drawable::DrawCallback { public: void drawImplementation(osg::RenderInfo& renderInfo, const osg::Drawable* drawable) const override { static bool supported = osg::isGLExtensionOrVersionSupported( renderInfo.getState()->getContextID(), "GL_ARB_depth_clamp", 3.3); if (!supported) { drawable->drawImplementation(renderInfo); return; } glEnable(GL_DEPTH_CLAMP); drawable->drawImplementation(renderInfo); // restore default glDisable(GL_DEPTH_CLAMP); } }; Water::Water(osg::Group* parent, osg::Group* sceneRoot, Resource::ResourceSystem* resourceSystem, osgUtil::IncrementalCompileOperation* ico) : mRainIntensityUpdater(nullptr) , mParent(parent) , mSceneRoot(sceneRoot) , mResourceSystem(resourceSystem) , mEnabled(true) , mToggled(true) , mTop(0) , mInterior(false) , mShowWorld(true) , mCullCallback(nullptr) , mShaderWaterStateSetUpdater(nullptr) { mSimulation = std::make_unique(mSceneRoot, resourceSystem); mWaterGeom = SceneUtil::createWaterGeometry(Constants::CellSizeInUnits * 150, 40, 900); mWaterGeom->setDrawCallback(new DepthClampCallback); mWaterGeom->setNodeMask(Mask_Water); mWaterGeom->setDataVariance(osg::Object::STATIC); mWaterGeom->setName("Water Geometry"); mWaterNode = new osg::PositionAttitudeTransform; mWaterNode->setName("Water Root"); mWaterNode->addChild(mWaterGeom); mWaterNode->addCullCallback(new FudgeCallback); // simple water fallback for the local map osg::ref_ptr geom2(osg::clone(mWaterGeom.get(), osg::CopyOp::DEEP_COPY_NODES)); createSimpleWaterStateSet(geom2, Fallback::Map::getFloat("Water_Map_Alpha")); geom2->setNodeMask(Mask_SimpleWater); geom2->setName("Simple Water Geometry"); mWaterNode->addChild(geom2); mSceneRoot->addChild(mWaterNode); setHeight(mTop); updateWaterMaterial(); if (ico) ico->add(mWaterNode); } void Water::setCullCallback(osg::Callback* callback) { if (mCullCallback) { mWaterNode->removeCullCallback(mCullCallback); if (mReflection) mReflection->removeCullCallback(mCullCallback); if (mRefraction) mRefraction->removeCullCallback(mCullCallback); } mCullCallback = callback; if (callback) { mWaterNode->addCullCallback(callback); if (mReflection) mReflection->addCullCallback(callback); if (mRefraction) mRefraction->addCullCallback(callback); } } void Water::updateWaterMaterial() { if (mShaderWaterStateSetUpdater) { mWaterNode->removeCullCallback(mShaderWaterStateSetUpdater); mShaderWaterStateSetUpdater = nullptr; } if (mReflection) { mParent->removeChild(mReflection); mReflection = nullptr; } if (mRefraction) { mParent->removeChild(mRefraction); mRefraction = nullptr; } if (mRipples) { mParent->removeChild(mRipples); mRipples = nullptr; mSimulation->setRipples(nullptr); } mWaterNode->setStateSet(nullptr); mWaterGeom->setStateSet(nullptr); mWaterGeom->setUpdateCallback(nullptr); if (Settings::Manager::getBool("shader", "Water")) { unsigned int rttSize = Settings::Manager::getInt("rtt size", "Water"); mReflection = new Reflection(rttSize, mInterior); mReflection->setWaterLevel(mTop); mReflection->setScene(mSceneRoot); if (mCullCallback) mReflection->addCullCallback(mCullCallback); mParent->addChild(mReflection); if (Settings::Manager::getBool("refraction", "Water")) { mRefraction = new Refraction(rttSize); mRefraction->setWaterLevel(mTop); mRefraction->setScene(mSceneRoot); if (mCullCallback) mRefraction->addCullCallback(mCullCallback); mParent->addChild(mRefraction); } mRipples = new Ripples(mResourceSystem); mSimulation->setRipples(mRipples); mParent->addChild(mRipples); showWorld(mShowWorld); createShaderWaterStateSet(mWaterNode); } else createSimpleWaterStateSet(mWaterGeom, Fallback::Map::getFloat("Water_World_Alpha")); updateVisible(); } osg::Node* Water::getReflectionNode() { return mReflection; } osg::Node* Water::getRefractionNode() { return mRefraction; } osg::Vec3d Water::getPosition() const { return mWaterNode->getPosition(); } void Water::createSimpleWaterStateSet(osg::Node* node, float alpha) { osg::ref_ptr stateset = SceneUtil::createSimpleWaterStateSet(alpha, MWRender::RenderBin_Water); node->setStateSet(stateset); node->setUpdateCallback(nullptr); mRainIntensityUpdater = nullptr; // Add animated textures std::vector> textures; const int frameCount = std::clamp(Fallback::Map::getInt("Water_SurfaceFrameCount"), 0, 320); std::string_view texture = Fallback::Map::getString("Water_SurfaceTexture"); for (int i = 0; i < frameCount; ++i) { std::ostringstream texname; texname << "textures/water/" << texture << std::setw(2) << std::setfill('0') << i << ".dds"; osg::ref_ptr tex( new osg::Texture2D(mResourceSystem->getImageManager()->getImage(texname.str()))); tex->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT); tex->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT); mResourceSystem->getSceneManager()->applyFilterSettings(tex); textures.push_back(tex); } if (textures.empty()) return; float fps = Fallback::Map::getFloat("Water_SurfaceFPS"); osg::ref_ptr controller(new NifOsg::FlipController(0, 1.f / fps, textures)); controller->setSource(std::make_shared()); node->setUpdateCallback(controller); stateset->setTextureAttributeAndModes(0, textures[0], osg::StateAttribute::ON); // use a shader to render the simple water, ensuring that fog is applied per pixel as required. // this could be removed if a more detailed water mesh, using some sort of paging solution, is implemented. Resource::SceneManager* sceneManager = mResourceSystem->getSceneManager(); bool oldValue = sceneManager->getForceShaders(); sceneManager->setForceShaders(true); sceneManager->recreateShaders(node); sceneManager->setForceShaders(oldValue); } class ShaderWaterStateSetUpdater : public SceneUtil::StateSetUpdater { public: ShaderWaterStateSetUpdater(Water* water, Reflection* reflection, Refraction* refraction, Ripples* ripples, osg::ref_ptr program, osg::ref_ptr normalMap) : mWater(water) , mReflection(reflection) , mRefraction(refraction) , mRipples(ripples) , mProgram(program) , mNormalMap(normalMap) { } void setDefaults(osg::StateSet* stateset) override { stateset->addUniform(new osg::Uniform("normalMap", 0)); stateset->setTextureAttributeAndModes(0, mNormalMap, osg::StateAttribute::ON); stateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); stateset->setAttributeAndModes(mProgram, osg::StateAttribute::ON); stateset->addUniform(new osg::Uniform("reflectionMap", 1)); if (mRefraction) { stateset->addUniform(new osg::Uniform("refractionMap", 2)); stateset->addUniform(new osg::Uniform("refractionDepthMap", 3)); stateset->setRenderBinDetails(MWRender::RenderBin_Default, "RenderBin"); } else { stateset->setMode(GL_BLEND, osg::StateAttribute::ON); stateset->setRenderBinDetails(MWRender::RenderBin_Water, "RenderBin"); osg::ref_ptr depth = new SceneUtil::AutoDepth; depth->setWriteMask(false); stateset->setAttributeAndModes(depth, osg::StateAttribute::ON); } if (mRipples) { stateset->addUniform(new osg::Uniform("rippleMap", 4)); } stateset->addUniform(new osg::Uniform("nodePosition", osg::Vec3f(mWater->getPosition()))); } void apply(osg::StateSet* stateset, osg::NodeVisitor* nv) override { osgUtil::CullVisitor* cv = static_cast(nv); stateset->setTextureAttributeAndModes(1, mReflection->getColorTexture(cv), osg::StateAttribute::ON); if (mRefraction) { stateset->setTextureAttributeAndModes(2, mRefraction->getColorTexture(cv), osg::StateAttribute::ON); stateset->setTextureAttributeAndModes(3, mRefraction->getDepthTexture(cv), osg::StateAttribute::ON); } if (mRipples) { stateset->setTextureAttributeAndModes(4, mRipples->getColorTexture(), osg::StateAttribute::ON); } stateset->getUniform("nodePosition")->set(osg::Vec3f(mWater->getPosition())); } private: Water* mWater; Reflection* mReflection; Refraction* mRefraction; Ripples* mRipples; osg::ref_ptr mProgram; osg::ref_ptr mNormalMap; }; void Water::createShaderWaterStateSet(osg::Node* node) { // use a define map to conditionally compile the shader std::map defineMap; defineMap["refraction_enabled"] = std::string(mRefraction ? "1" : "0"); const auto rippleDetail = std::clamp(Settings::Manager::getInt("rain ripple detail", "Water"), 0, 2); defineMap["rain_ripple_detail"] = std::to_string(rippleDetail); defineMap["ripple_map_world_scale"] = std::to_string(RipplesSurface::mWorldScaleFactor); defineMap["ripple_map_size"] = std::to_string(RipplesSurface::mRTTSize) + ".0"; Stereo::shaderStereoDefines(defineMap); Shader::ShaderManager& shaderMgr = mResourceSystem->getSceneManager()->getShaderManager(); osg::ref_ptr program = shaderMgr.getProgram("water", defineMap); osg::ref_ptr normalMap( new osg::Texture2D(mResourceSystem->getImageManager()->getImage("textures/omw/water_nm.png"))); if (normalMap->getImage()) normalMap->getImage()->flipVertical(); normalMap->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT); normalMap->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT); normalMap->setMaxAnisotropy(16); normalMap->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR_MIPMAP_LINEAR); normalMap->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); mRainIntensityUpdater = new RainIntensityUpdater(); node->setUpdateCallback(mRainIntensityUpdater); mShaderWaterStateSetUpdater = new ShaderWaterStateSetUpdater(this, mReflection, mRefraction, mRipples, program, normalMap); node->addCullCallback(mShaderWaterStateSetUpdater); } void Water::processChangedSettings(const Settings::CategorySettingVector& settings) { updateWaterMaterial(); } Water::~Water() { mParent->removeChild(mWaterNode); if (mReflection) { mParent->removeChild(mReflection); mReflection = nullptr; } if (mRefraction) { mParent->removeChild(mRefraction); mRefraction = nullptr; } if (mRipples) { mParent->removeChild(mRipples); mRipples = nullptr; mSimulation->setRipples(nullptr); } } void Water::listAssetsToPreload(std::vector& textures) { const int frameCount = std::clamp(Fallback::Map::getInt("Water_SurfaceFrameCount"), 0, 320); std::string_view texture = Fallback::Map::getString("Water_SurfaceTexture"); for (int i = 0; i < frameCount; ++i) { std::ostringstream texname; texname << "textures/water/" << texture << std::setw(2) << std::setfill('0') << i << ".dds"; textures.push_back(texname.str()); } } void Water::setEnabled(bool enabled) { mEnabled = enabled; updateVisible(); } void Water::changeCell(const MWWorld::CellStore* store) { bool isInterior = !store->getCell()->isExterior(); bool wasInterior = mInterior; if (!isInterior) { mWaterNode->setPosition( getSceneNodeCoordinates(store->getCell()->getGridX(), store->getCell()->getGridY())); mInterior = false; } else { mWaterNode->setPosition(osg::Vec3f(0, 0, mTop)); mInterior = true; } if (mInterior != wasInterior && mReflection) mReflection->setInterior(mInterior); } void Water::setHeight(const float height) { mTop = height; mSimulation->setWaterHeight(height); osg::Vec3f pos = mWaterNode->getPosition(); pos.z() = height; mWaterNode->setPosition(pos); if (mReflection) mReflection->setWaterLevel(mTop); if (mRefraction) mRefraction->setWaterLevel(mTop); } void Water::setRainIntensity(float rainIntensity) { if (mRainIntensityUpdater) mRainIntensityUpdater->setRainIntensity(rainIntensity); } void Water::update(float dt, bool paused) { if (!paused) { mSimulation->update(dt); } if (mRipples) { mRipples->setPaused(paused); } } void Water::updateVisible() { bool visible = mEnabled && mToggled; mWaterNode->setNodeMask(visible ? ~0u : 0u); if (mRefraction) mRefraction->setNodeMask(visible ? Mask_RenderToTexture : 0u); if (mReflection) mReflection->setNodeMask(visible ? Mask_RenderToTexture : 0u); if (mRipples) mRipples->setNodeMask(visible ? Mask_RenderToTexture : 0u); } bool Water::toggle() { mToggled = !mToggled; updateVisible(); return mToggled; } bool Water::isUnderwater(const osg::Vec3f& pos) const { return pos.z() < mTop && mToggled && mEnabled; } osg::Vec3f Water::getSceneNodeCoordinates(int gridX, int gridY) { return osg::Vec3f(static_cast(gridX * Constants::CellSizeInUnits + (Constants::CellSizeInUnits / 2)), static_cast(gridY * Constants::CellSizeInUnits + (Constants::CellSizeInUnits / 2)), mTop); } void Water::addEmitter(const MWWorld::Ptr& ptr, float scale, float force) { mSimulation->addEmitter(ptr, scale, force); } void Water::removeEmitter(const MWWorld::Ptr& ptr) { mSimulation->removeEmitter(ptr); } void Water::updateEmitterPtr(const MWWorld::Ptr& old, const MWWorld::Ptr& ptr) { mSimulation->updateEmitterPtr(old, ptr); } void Water::emitRipple(const osg::Vec3f& pos) { mSimulation->emitRipple(pos); } void Water::removeCell(const MWWorld::CellStore* store) { mSimulation->removeCell(store); } void Water::clearRipples() { mSimulation->clear(); } void Water::showWorld(bool show) { if (mReflection) mReflection->showWorld(show); if (mRefraction) mRefraction->showWorld(show); mShowWorld = show; } }