diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 265fb43e82..cd1ac31ec5 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -22,7 +22,7 @@ source_group(game FILES ${GAME} ${GAME_HEADER}) 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 + bulletdebugdraw globalmap characterpreview camera localmap water terrainstorage ripplesimulation ) add_openmw_dir (mwinput diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 4db776783a..5cbcaab83b 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -114,7 +114,7 @@ namespace MWRender bool mWireframe; }; - RenderingManager::RenderingManager(osgViewer::Viewer* viewer, osg::ref_ptr rootNode, Resource::ResourceSystem* resourceSystem) + RenderingManager::RenderingManager(osgViewer::Viewer* viewer, osg::ref_ptr rootNode, Resource::ResourceSystem* resourceSystem, const MWWorld::Fallback* fallback) : mViewer(viewer) , mRootNode(rootNode) , mResourceSystem(resourceSystem) @@ -136,7 +136,7 @@ namespace MWRender mEffectManager.reset(new EffectManager(lightRoot, mResourceSystem)); - mWater.reset(new Water(lightRoot, mResourceSystem, mViewer->getIncrementalCompileOperation())); + mWater.reset(new Water(lightRoot, mResourceSystem, mViewer->getIncrementalCompileOperation(), fallback)); mTerrain.reset(new Terrain::TerrainGrid(lightRoot, mResourceSystem, mViewer->getIncrementalCompileOperation(), new TerrainStorage(mResourceSystem->getVFS(), false), Mask_Terrain)); @@ -265,6 +265,8 @@ namespace MWRender if (store->getCell()->isExterior()) mTerrain->unloadCell(store->getCell()->getGridX(), store->getCell()->getGridY()); + + mWater->removeCell(store); } void RenderingManager::setSkyEnabled(bool enabled) @@ -330,6 +332,7 @@ namespace MWRender { mEffectManager->update(dt); mSky->update(dt); + mWater->update(dt); mCamera->update(dt, paused); osg::Vec3f focal, cameraPos; @@ -354,6 +357,11 @@ namespace MWRender mCamera->attachTo(ptr); } + void RenderingManager::removePlayer(const MWWorld::Ptr &player) + { + mWater->removeEmitter(player); + } + void RenderingManager::rotateObject(const MWWorld::Ptr &ptr, const osg::Quat& rot) { if(ptr == mCamera->getTrackingPtr() && @@ -378,6 +386,7 @@ namespace MWRender void RenderingManager::removeObject(const MWWorld::Ptr &ptr) { mObjects->removeObject(ptr); + mWater->removeEmitter(ptr); } void RenderingManager::setWaterEnabled(bool enabled) @@ -579,7 +588,7 @@ namespace MWRender void RenderingManager::notifyWorldSpaceChanged() { mEffectManager->clear(); - //mWater->clearRipples(); + mWater->clearRipples(); } void RenderingManager::clear() @@ -613,6 +622,8 @@ namespace MWRender mPlayerNode->getUserDataContainer()->addUserObject(new PtrHolder(player)); player.getRefData().setBaseNode(mPlayerNode); + + mWater->addEmitter(player); } void RenderingManager::renderPlayer(const MWWorld::Ptr &player) @@ -621,8 +632,6 @@ namespace MWRender mCamera->setAnimation(mPlayerAnimation.get()); mCamera->attachTo(player); - //mWater->removeEmitter(ptr); - //mWater->addEmitter(ptr); } void RenderingManager::rebuildPtr(const MWWorld::Ptr &ptr) @@ -643,6 +652,16 @@ namespace MWRender } } + void RenderingManager::addWaterRippleEmitter(const MWWorld::Ptr &ptr) + { + mWater->addEmitter(ptr); + } + + void RenderingManager::removeWaterRippleEmitter(const MWWorld::Ptr &ptr) + { + mWater->removeEmitter(ptr); + } + void RenderingManager::updateProjectionMatrix() { double aspect = mViewer->getCamera()->getViewport()->aspectRatio(); diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index a37203cc22..302073e4dc 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -37,6 +37,11 @@ namespace Terrain class World; } +namespace MWWorld +{ + class Fallback; +} + namespace MWRender { @@ -52,7 +57,7 @@ namespace MWRender class RenderingManager : public MWRender::RenderingInterface { public: - RenderingManager(osgViewer::Viewer* viewer, osg::ref_ptr rootNode, Resource::ResourceSystem* resourceSystem); + RenderingManager(osgViewer::Viewer* viewer, osg::ref_ptr rootNode, Resource::ResourceSystem* resourceSystem, const MWWorld::Fallback* fallback); ~RenderingManager(); MWRender::Objects& getObjects(); @@ -125,8 +130,12 @@ namespace MWRender Animation* getAnimation(const MWWorld::Ptr& ptr); Animation* getPlayerAnimation(); + void addWaterRippleEmitter(const MWWorld::Ptr& ptr); + void removeWaterRippleEmitter(const MWWorld::Ptr& ptr); + void updatePlayerPtr(const MWWorld::Ptr &ptr); + void removePlayer(const MWWorld::Ptr& player); void setupPlayer(const MWWorld::Ptr& player); void renderPlayer(const MWWorld::Ptr& player); diff --git a/apps/openmw/mwrender/ripplesimulation.cpp b/apps/openmw/mwrender/ripplesimulation.cpp new file mode 100644 index 0000000000..a3e96a5b15 --- /dev/null +++ b/apps/openmw/mwrender/ripplesimulation.cpp @@ -0,0 +1,202 @@ +#include "ripplesimulation.hpp" + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "vismask.hpp" + +#include "../mwbase/world.hpp" +#include "../mwbase/environment.hpp" + +#include "../mwworld/fallback.hpp" + +namespace +{ + void createWaterRippleStateSet(Resource::ResourceSystem* resourceSystem, const MWWorld::Fallback* fallback, osg::Node* node) + { + int rippleFrameCount = fallback->getFallbackInt("Water_RippleFrameCount"); + if (rippleFrameCount <= 0) + return; + + std::string tex = fallback->getFallbackString("Water_RippleTexture"); + + std::vector > textures; + for (int i=0; igetTextureManager()->getTexture2D(texname.str(), osg::Texture::REPEAT, osg::Texture::REPEAT)); + } + + osg::ref_ptr controller (new NifOsg::FlipController(0, 0.3f/rippleFrameCount, textures)); + controller->setSource(boost::shared_ptr(new SceneUtil::FrameTimeSource)); + node->addUpdateCallback(controller); + + osg::ref_ptr stateset (new osg::StateSet); + stateset->setMode(GL_BLEND, osg::StateAttribute::ON); + stateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); + stateset->setTextureAttributeAndModes(0, textures[0], osg::StateAttribute::ON); + + osg::ref_ptr depth (new osg::Depth); + depth->setWriteMask(false); + stateset->setAttributeAndModes(depth, osg::StateAttribute::ON); + + stateset->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); + + 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); + stateset->setAttributeAndModes(mat, osg::StateAttribute::ON); + + node->setStateSet(stateset); + } +} + +namespace MWRender +{ + +RippleSimulation::RippleSimulation(osg::Group *parent, Resource::ResourceSystem* resourceSystem, const MWWorld::Fallback* fallback) + : mParent(parent) +{ + osg::ref_ptr geode (new osg::Geode); + + mParticleSystem = new osgParticle::ParticleSystem; + geode->addDrawable(mParticleSystem); + + mParticleSystem->setParticleAlignment(osgParticle::ParticleSystem::FIXED); + mParticleSystem->setAlignVectorX(osg::Vec3f(1,0,0)); + mParticleSystem->setAlignVectorY(osg::Vec3f(0,1,0)); + + osgParticle::Particle& particleTemplate = mParticleSystem->getDefaultParticleTemplate(); + particleTemplate.setSizeRange(osgParticle::rangef(15, 180)); + particleTemplate.setColorRange(osgParticle::rangev4(osg::Vec4f(1,1,1,0.7), osg::Vec4f(1,1,1,0.7))); + particleTemplate.setAlphaRange(osgParticle::rangef(1.f, 0.f)); + particleTemplate.setAngularVelocity(osg::Vec3f(0,0,fallback->getFallbackFloat("Water_RippleRotSpeed"))); + particleTemplate.setLifeTime(fallback->getFallbackFloat("Water_RippleLifetime")); + + osg::ref_ptr updater (new osgParticle::ParticleSystemUpdater); + updater->addParticleSystem(mParticleSystem); + + mParticleNode = new osg::PositionAttitudeTransform; + mParticleNode->addChild(updater); + mParticleNode->addChild(geode); + mParticleNode->setNodeMask(Mask_Effect); + + createWaterRippleStateSet(resourceSystem, fallback, mParticleNode); + + mParent->addChild(mParticleNode); +} + +RippleSimulation::~RippleSimulation() +{ + mParent->removeChild(mParticleNode); +} + +void RippleSimulation::update(float dt) +{ + for (std::vector::iterator it=mEmitters.begin(); it !=mEmitters.end(); ++it) + { + if (it->mPtr == MWBase::Environment::get().getWorld ()->getPlayerPtr()) + { + // fetch a new ptr (to handle cell change etc) + // for non-player actors this is done in updateObjectCell + it->mPtr = MWBase::Environment::get().getWorld ()->getPlayerPtr(); + } + + osg::Vec3f currentPos (it->mPtr.getRefData().getPosition().asVec3()); + currentPos.z() = 0; // Z is set by the Scene Node + + if ( (currentPos - it->mLastEmitPosition).length() > 10 + // Only emit when close to the water surface, not above it and not too deep in the water + && MWBase::Environment::get().getWorld ()->isUnderwater (it->mPtr.getCell(), it->mPtr.getRefData().getPosition().asVec3()) + && !MWBase::Environment::get().getWorld()->isSubmerged(it->mPtr)) + { + it->mLastEmitPosition = currentPos; + + if (mParticleSystem->numParticles()-mParticleSystem->numDeadParticles() > 500) + continue; // TODO: remove the oldest particle to make room? + + osgParticle::Particle* p = mParticleSystem->createParticle(NULL); + p->setPosition(currentPos); + p->setAngle(osg::Vec3f(0,0, Misc::Rng::rollProbability() * osg::PI * 2 - osg::PI)); + } + } +} + + +void RippleSimulation::addEmitter(const MWWorld::Ptr& ptr, float scale, float force) +{ + Emitter newEmitter; + newEmitter.mPtr = ptr; + newEmitter.mScale = scale; + newEmitter.mForce = force; + newEmitter.mLastEmitPosition = osg::Vec3f(0,0,0); + mEmitters.push_back (newEmitter); +} + +void RippleSimulation::removeEmitter (const MWWorld::Ptr& ptr) +{ + for (std::vector::iterator it = mEmitters.begin(); it != mEmitters.end(); ++it) + { + if (it->mPtr == ptr) + { + mEmitters.erase(it); + return; + } + } +} + +void RippleSimulation::updateEmitterPtr (const MWWorld::Ptr& old, const MWWorld::Ptr& ptr) +{ + for (std::vector::iterator it = mEmitters.begin(); it != mEmitters.end(); ++it) + { + if (it->mPtr == old) + { + it->mPtr = ptr; + return; + } + } +} + +void RippleSimulation::removeCell(const MWWorld::CellStore *store) +{ + for (std::vector::iterator it = mEmitters.begin(); it != mEmitters.end();) + { + if (it->mPtr.getCell() == store && it->mPtr != MWBase::Environment::get().getWorld()->getPlayerPtr()) + { + it = mEmitters.erase(it); + } + else + ++it; + } +} + +void RippleSimulation::setWaterHeight(float height) +{ + mParticleNode->setPosition(osg::Vec3f(0,0,height)); +} + +void RippleSimulation::clear() +{ + for (int i=0; inumParticles(); ++i) + mParticleSystem->destroyParticle(i); +} + + + +} diff --git a/apps/openmw/mwrender/ripplesimulation.hpp b/apps/openmw/mwrender/ripplesimulation.hpp new file mode 100644 index 0000000000..98c8a707dc --- /dev/null +++ b/apps/openmw/mwrender/ripplesimulation.hpp @@ -0,0 +1,76 @@ +#ifndef OPENMW_MWRENDER_RIPPLESIMULATION_H +#define OPENMW_MWRENDER_RIPPLESIMULATION_H + +#include + +#include "../mwworld/ptr.hpp" + +namespace osg +{ + class Group; +} + +namespace osgParticle +{ + class ParticleSystem; +} + +namespace Resource +{ + class ResourceSystem; +} + +namespace MWWorld +{ + class Fallback; +} + +namespace MWRender +{ + + struct Emitter + { + MWWorld::Ptr mPtr; + osg::Vec3f mLastEmitPosition; + float mScale; + float mForce; + }; + + class RippleSimulation + { + public: + RippleSimulation(osg::Group* parent, Resource::ResourceSystem* resourceSystem, const MWWorld::Fallback* fallback); + ~RippleSimulation(); + + /// @param dt Time since the last frame + void update(float dt); + + /// adds an emitter, position will be tracked automatically + void addEmitter (const MWWorld::Ptr& ptr, float scale = 1.f, float force = 1.f); + void removeEmitter (const MWWorld::Ptr& ptr); + void updateEmitterPtr (const MWWorld::Ptr& old, const MWWorld::Ptr& ptr); + void removeCell(const MWWorld::CellStore* store); + + /// Change the height of the water surface, thus moving all ripples with it + void setWaterHeight(float height); + + /// Remove all active ripples + void clear(); + + private: + osg::ref_ptr mParent; + Resource::ResourceSystem* mResourceSystem; + + osg::ref_ptr mParticleSystem; + osg::ref_ptr mParticleNode; + + std::vector mEmitters; + + float mRippleLifeTime; + float mRippleRotSpeed; + + }; + +} + +#endif diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index c2149358bd..3b07f35ba0 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -18,6 +18,7 @@ #include #include "vismask.hpp" +#include "ripplesimulation.hpp" namespace { @@ -107,13 +108,15 @@ namespace MWRender // -------------------------------------------------------------------------------------------------------------------------------- -Water::Water(osg::Group *parent, Resource::ResourceSystem *resourceSystem, osgUtil::IncrementalCompileOperation *ico) +Water::Water(osg::Group *parent, Resource::ResourceSystem *resourceSystem, osgUtil::IncrementalCompileOperation *ico, const MWWorld::Fallback* fallback) : mParent(parent) , mResourceSystem(resourceSystem) , mEnabled(true) , mToggled(true) , mTop(0) { + mSimulation.reset(new RippleSimulation(parent, resourceSystem, fallback)); + osg::ref_ptr waterGeom = createWaterGeometry(CELL_SIZE*150, 40, 900); osg::ref_ptr geode (new osg::Geode); @@ -161,6 +164,11 @@ void Water::setHeight(const float height) mWaterNode->setPosition(pos); } +void Water::update(float dt) +{ + mSimulation->update(dt); +} + void Water::updateVisible() { mWaterNode->setNodeMask(mEnabled && mToggled ? ~0 : 0); @@ -183,7 +191,6 @@ osg::Vec3f Water::getSceneNodeCoordinates(int gridX, int gridY) return osg::Vec3f(static_cast(gridX * CELL_SIZE + (CELL_SIZE / 2)), static_cast(gridY * CELL_SIZE + (CELL_SIZE / 2)), mTop); } -/* void Water::addEmitter (const MWWorld::Ptr& ptr, float scale, float force) { mSimulation->addEmitter (ptr, scale, force); @@ -198,6 +205,15 @@ void Water::updateEmitterPtr (const MWWorld::Ptr& old, const MWWorld::Ptr& ptr) { mSimulation->updateEmitterPtr(old, ptr); } -*/ + +void Water::removeCell(const MWWorld::CellStore *store) +{ + mSimulation->removeCell(store); +} + +void Water::clearRipples() +{ + mSimulation->clear(); +} } diff --git a/apps/openmw/mwrender/water.hpp b/apps/openmw/mwrender/water.hpp index d389392ba8..519cd51819 100644 --- a/apps/openmw/mwrender/water.hpp +++ b/apps/openmw/mwrender/water.hpp @@ -21,9 +21,16 @@ namespace Resource class ResourceSystem; } +namespace MWWorld +{ + class Fallback; +} + namespace MWRender { + class RippleSimulation; + /// Water rendering class Water { @@ -34,6 +41,8 @@ namespace MWRender Resource::ResourceSystem* mResourceSystem; osg::ref_ptr mIncrementalCompileOperation; + std::auto_ptr mSimulation; + bool mEnabled; bool mToggled; float mTop; @@ -42,7 +51,7 @@ namespace MWRender void updateVisible(); public: - Water(osg::Group* parent, Resource::ResourceSystem* resourceSystem, osgUtil::IncrementalCompileOperation* ico); + Water(osg::Group* parent, Resource::ResourceSystem* resourceSystem, osgUtil::IncrementalCompileOperation* ico, const MWWorld::Fallback* fallback); ~Water(); void setEnabled(bool enabled); @@ -51,16 +60,19 @@ namespace MWRender bool isUnderwater(const osg::Vec3f& pos) const; - /* /// adds an emitter, position will be tracked automatically using its scene node void addEmitter (const MWWorld::Ptr& ptr, float scale = 1.f, float force = 1.f); void removeEmitter (const MWWorld::Ptr& ptr); void updateEmitterPtr (const MWWorld::Ptr& old, const MWWorld::Ptr& ptr); - */ + void removeCell(const MWWorld::CellStore* store); ///< remove all emitters in this cell + + void clearRipples(); void changeCell(const MWWorld::CellStore* store); void setHeight(const float height); + void update(float dt); + }; } diff --git a/apps/openmw/mwworld/fallback.cpp b/apps/openmw/mwworld/fallback.cpp index fd60224813..e810f8241b 100644 --- a/apps/openmw/mwworld/fallback.cpp +++ b/apps/openmw/mwworld/fallback.cpp @@ -58,4 +58,5 @@ namespace MWWorld return osg::Vec4f(boost::lexical_cast(ret[0])/255.f,boost::lexical_cast(ret[1])/255.f,boost::lexical_cast(ret[2])/255.f, 1.f); } } + } diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 76c2f6ebac..db26b4f2a4 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -39,6 +39,9 @@ namespace model = ""; // marker objects that have a hardcoded function in the game logic, should be hidden from the player ptr.getClass().insertObjectRendering(ptr, model, rendering); ptr.getClass().insertObject (ptr, model, physics); + + if (ptr.getClass().isActor()) + rendering.addWaterRippleEmitter(ptr); } void updateObjectLocalRotation (const MWWorld::Ptr& ptr, MWPhysics::PhysicsSystem& physics, @@ -570,6 +573,8 @@ namespace MWWorld MWBase::Environment::get().getSoundManager()->stopSound3D (ptr); mPhysics->remove(ptr); mRendering.removeObject (ptr); + if (ptr.getClass().isActor()) + mRendering.removeWaterRippleEmitter(ptr); } bool Scene::isCellActive(const CellStore &cell) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 16dcadb231..4743b3ad9d 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -161,7 +161,7 @@ namespace MWWorld { mPhysics = new MWPhysics::PhysicsSystem(resourceSystem, rootNode); mProjectileManager.reset(new ProjectileManager(rootNode, resourceSystem, mPhysics)); - mRendering = new MWRender::RenderingManager(viewer, rootNode, resourceSystem); + mRendering = new MWRender::RenderingManager(viewer, rootNode, resourceSystem, &mFallback); mWeatherManager = new MWWorld::WeatherManager(mRendering,&mFallback); @@ -2064,6 +2064,7 @@ namespace MWWorld // Remove the old CharacterController MWBase::Environment::get().getMechanicsManager()->remove(getPlayerPtr()); mPhysics->remove(getPlayerPtr()); + mRendering->removePlayer(getPlayerPtr()); mPlayer->set(player); }