diff --git a/CMakeLists.txt b/CMakeLists.txt index 0b545d920..6f007dbcc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -201,6 +201,7 @@ if(APPLE) "Plugin_ParticleFX") endif(APPLE) +add_subdirectory( files/) add_subdirectory( files/mygui ) # Specify build paths diff --git a/apps/launcher/CMakeLists.txt b/apps/launcher/CMakeLists.txt index fd736e011..ccefee1ee 100644 --- a/apps/launcher/CMakeLists.txt +++ b/apps/launcher/CMakeLists.txt @@ -95,5 +95,5 @@ else() "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/launcher.qss") configure_file(${CMAKE_SOURCE_DIR}/files/launcher.cfg - "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}launcher.cfg") + "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/launcher.cfg") endif() diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 3697b6750..3dabc9ac8 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -15,7 +15,7 @@ source_group(game FILES ${GAME} ${GAME_HEADER}) add_openmw_dir (mwrender renderingmanager debugging sky player animation npcanimation creatureanimation actors objects - renderinginterface localmap occlusionquery terrain terrainmaterial + renderinginterface localmap occlusionquery terrain terrainmaterial water ) add_openmw_dir (mwinput diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 5e49ae2f7..441c22769 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -315,7 +315,11 @@ void OMW::Engine::go() // This has to be added BEFORE MyGUI is initialized, as it needs // to find core.xml here. + + //addResourcesDirectory(mResDir); + addResourcesDirectory(mResDir / "mygui"); + addResourcesDirectory(mResDir / "water"); // Create the window mOgre->createWindow("OpenMW"); diff --git a/apps/openmw/mwgui/layouts.cpp b/apps/openmw/mwgui/layouts.cpp index dbd6154b7..de74214ee 100644 --- a/apps/openmw/mwgui/layouts.cpp +++ b/apps/openmw/mwgui/layouts.cpp @@ -182,7 +182,9 @@ void HUD::setPlayerPos(const float x, const float y) } MapWindow::MapWindow() - : Layout("openmw_map_window_layout.xml"), mGlobal(false) + : Layout("openmw_map_window_layout.xml") + , mGlobal(false) + , mVisible(false) { setCoord(500,0,320,300); setText("WorldButton", "World"); @@ -272,6 +274,17 @@ void MapWindow::onWorldButtonClicked(MyGUI::Widget* _sender) mButton->setCaption( mGlobal ? "Local" : "World" ); } +LocalMapBase::LocalMapBase() + : mCurX(0) + , mCurY(0) + , mInterior(false) + , mLocalMap(NULL) + , mPrefix() + , mChanged(true) + , mLayout(NULL) +{ +} + void LocalMapBase::init(MyGUI::ScrollView* widget, OEngine::GUI::Layout* layout) { mLocalMap = widget; diff --git a/apps/openmw/mwgui/layouts.hpp b/apps/openmw/mwgui/layouts.hpp index 8d9a41a22..614479ccc 100644 --- a/apps/openmw/mwgui/layouts.hpp +++ b/apps/openmw/mwgui/layouts.hpp @@ -34,6 +34,7 @@ namespace MWGui class LocalMapBase { public: + LocalMapBase(); void init(MyGUI::ScrollView* widget, OEngine::GUI::Layout* layout); void setCellPrefix(const std::string& prefix); @@ -85,6 +86,7 @@ namespace MWGui { public: MapWindow(); + virtual ~MapWindow(){} void setVisible(bool b); void setPlayerPos(const float x, const float y); diff --git a/apps/openmw/mwrender/objects.cpp b/apps/openmw/mwrender/objects.cpp index e4e721227..94ccb6e97 100644 --- a/apps/openmw/mwrender/objects.cpp +++ b/apps/openmw/mwrender/objects.cpp @@ -88,38 +88,76 @@ void Objects::insertMesh (const MWWorld::Ptr& ptr, const std::string& mesh) NifOgre::NIFLoader::load(mesh); Ogre::Entity *ent = mRenderer.getScene()->createEntity(mesh); +/* + Ogre::Vector3 extents = ent->getBoundingBox().getSize(); + extents *= insert->getScale(); +// float size = std::max(std::max(extents.x, extents.y), extents.z); + + bool small = (size < 250); /// \todo config value + + // do not fade out doors. that will cause holes and look stupid + if (ptr.getTypeName().find("Door") != std::string::npos) + small = false; +*/ + const bool small = false; + + if (mBounds.find(ptr.getCell()) == mBounds.end()) + mBounds[ptr.getCell()] = Ogre::AxisAlignedBox::BOX_NULL; + + Ogre::AxisAlignedBox bounds = ent->getBoundingBox(); + bounds = Ogre::AxisAlignedBox( + insert->_getDerivedPosition() + bounds.getMinimum(), + insert->_getDerivedPosition() + bounds.getMaximum() + ); + + bounds.scale(insert->getScale()); + mBounds[ptr.getCell()].merge(bounds); + if(!mIsStatic) { insert->attachObject(ent); + + ent->setRenderingDistance(small ? 2500 : 0); /// \todo config value } else { Ogre::StaticGeometry* sg = 0; - if(mStaticGeometry.find(ptr.getCell()) == mStaticGeometry.end()) + + if (small) { - uniqueID = uniqueID +1; - sg = mRenderer.getScene()->createStaticGeometry( "sg" + Ogre::StringConverter::toString(uniqueID)); - //Create the scenenode and put it in the map - mStaticGeometry[ptr.getCell()] = sg; + if( mStaticGeometrySmall.find(ptr.getCell()) == mStaticGeometrySmall.end()) + { + uniqueID = uniqueID +1; + sg = mRenderer.getScene()->createStaticGeometry( "sg" + Ogre::StringConverter::toString(uniqueID)); + mStaticGeometrySmall[ptr.getCell()] = sg; - // This specifies the size of a single batch region. - // If it is set too high: - // - there will be problems choosing the correct lights - // - the culling will be more inefficient - // If it is set too low: - // - there will be too many batches. - sg->setRegionDimensions(Ogre::Vector3(2500,2500,2500)); - - mBounds[ptr.getCell()] = Ogre::AxisAlignedBox::BOX_NULL; - mBounds[ptr.getCell()].merge(ent->getBoundingBox()); + sg->setRenderingDistance(2500); /// \todo config value + } + else + sg = mStaticGeometrySmall[ptr.getCell()]; } else { - sg = mStaticGeometry[ptr.getCell()]; + if( mStaticGeometry.find(ptr.getCell()) == mStaticGeometry.end()) + { + + uniqueID = uniqueID +1; + sg = mRenderer.getScene()->createStaticGeometry( "sg" + Ogre::StringConverter::toString(uniqueID)); + mStaticGeometry[ptr.getCell()] = sg; + } + else + sg = mStaticGeometry[ptr.getCell()]; } + // This specifies the size of a single batch region. + // If it is set too high: + // - there will be problems choosing the correct lights + // - the culling will be more inefficient + // If it is set too low: + // - there will be too many batches. + sg->setRegionDimensions(Ogre::Vector3(2500,2500,2500)); + sg->addEntity(ent,insert->_getDerivedPosition(),insert->_getDerivedOrientation(),insert->_getDerivedScale()); - mBounds[ptr.getCell()].merge(insert->_getDerivedPosition()); mRenderer.getScene()->destroyEntity(ent); } @@ -206,7 +244,14 @@ void Objects::removeCell(MWWorld::Ptr::CellStore* store) mRenderer.getScene()->destroyStaticGeometry (sg); sg = 0; } - + if(mStaticGeometrySmall.find(store) != mStaticGeometrySmall.end()) + { + Ogre::StaticGeometry* sg = mStaticGeometrySmall[store]; + mStaticGeometrySmall.erase(store); + mRenderer.getScene()->destroyStaticGeometry (sg); + sg = 0; + } + if(mBounds.find(store) != mBounds.end()) mBounds.erase(store); } @@ -218,6 +263,11 @@ void Objects::buildStaticGeometry(ESMS::CellStore& cell) Ogre::StaticGeometry* sg = mStaticGeometry[&cell]; sg->build(); } + if(mStaticGeometrySmall.find(&cell) != mStaticGeometrySmall.end()) + { + Ogre::StaticGeometry* sg = mStaticGeometrySmall[&cell]; + sg->build(); + } } Ogre::AxisAlignedBox Objects::getDimensions(MWWorld::Ptr::CellStore* cell) diff --git a/apps/openmw/mwrender/objects.hpp b/apps/openmw/mwrender/objects.hpp index 1ca81331d..265de875b 100644 --- a/apps/openmw/mwrender/objects.hpp +++ b/apps/openmw/mwrender/objects.hpp @@ -14,6 +14,7 @@ class Objects{ OEngine::Render::OgreRenderer &mRenderer; std::map mCellSceneNodes; std::map mStaticGeometry; + std::map mStaticGeometrySmall; std::map mBounds; Ogre::SceneNode* mMwRoot; bool mIsStatic; diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 00b43cf9f..3d715b3d3 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -58,6 +58,8 @@ RenderingManager::RenderingManager (OEngine::Render::OgreRenderer& _rend, const mOcclusionQuery = new OcclusionQuery(&mRendering, mSkyManager->getSunNode()); + mWater = 0; + mPlayer = new MWRender::Player (mRendering.getCamera(), playerNode); mSun = 0; @@ -95,13 +97,28 @@ OEngine::Render::Fader* RenderingManager::getFader() return mRendering.getFader(); } -void RenderingManager::removeCell (MWWorld::Ptr::CellStore *store){ +void RenderingManager::removeCell (MWWorld::Ptr::CellStore *store) +{ mObjects.removeCell(store); mActors.removeCell(store); if (store->cell->isExterior()) mTerrainManager->cellRemoved(store); } +void RenderingManager::removeWater () +{ + if(mWater){ + delete mWater; + mWater = 0; + } +} + +void RenderingManager::toggleWater() +{ + if (mWater) + mWater->toggle(); +} + void RenderingManager::cellAdded (MWWorld::Ptr::CellStore *store) { mObjects.buildStaticGeometry (*store); @@ -157,6 +174,27 @@ void RenderingManager::update (float duration){ mRendering.update(duration); mLocalMap->updatePlayer( mRendering.getCamera()->getRealPosition(), mRendering.getCamera()->getRealDirection() ); + + checkUnderwater(); +} +void RenderingManager::waterAdded (MWWorld::Ptr::CellStore *store){ + if(store->cell->data.flags & store->cell->HasWater){ + if(mWater == 0) + mWater = new MWRender::Water(mRendering.getCamera(), store->cell); + else + mWater->changeCell(store->cell); + //else + + } + else + removeWater(); + +} + +void RenderingManager::setWaterHeight(const float height) +{ + if (mWater) + mWater->setHeight(height); } void RenderingManager::skyEnable () @@ -301,6 +339,11 @@ void RenderingManager::toggleLight() setAmbientMode(); } +void RenderingManager::checkUnderwater(){ + if(mWater){ + mWater->checkUnderwater( mRendering.getCamera()->getRealPosition().y ); + } +} void RenderingManager::playAnimationGroup (const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number) diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index edbc9aff2..d71a71229 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -25,6 +25,7 @@ #include "objects.hpp" #include "actors.hpp" #include "player.hpp" +#include "water.hpp" #include "localmap.hpp" #include "occlusionquery.hpp" @@ -61,6 +62,8 @@ class RenderingManager: private RenderingInterface { RenderingManager(OEngine::Render::OgreRenderer& _rend, const boost::filesystem::path& resDir, OEngine::Physic::PhysicEngine* engine, MWWorld::Environment& environment); virtual ~RenderingManager(); + + virtual MWRender::Player& getPlayer(); /// \todo move this to private again as soon as /// MWWorld::Player has been rewritten to not need access /// to internal details of the rendering system anymore @@ -77,7 +80,10 @@ class RenderingManager: private RenderingInterface { /// \todo this function should be removed later. Instead the rendering subsystems should track /// when rebatching is needed and update automatically at the end of each frame. void cellAdded (MWWorld::Ptr::CellStore *store); + void waterAdded(MWWorld::Ptr::CellStore *store); + void removeWater(); + void preCellChange (MWWorld::Ptr::CellStore* store); ///< this event is fired immediately before changing cell @@ -88,6 +94,10 @@ class RenderingManager: private RenderingInterface { void scaleObject (const MWWorld::Ptr& ptr, const Ogre::Vector3& scale); void rotateObject (const MWWorld::Ptr& ptr, const::Ogre::Quaternion& orientation); + void checkUnderwater(); + void setWaterHeight(const float height); + void toggleWater(); + /// \param store Cell the object was in previously (\a ptr has already been updated to the new cell). void moveObjectToCell (const MWWorld::Ptr& ptr, const Ogre::Vector3& position, MWWorld::Ptr::CellStore *store); @@ -143,6 +153,8 @@ class RenderingManager: private RenderingInterface { TerrainManager* mTerrainManager; + MWRender::Water *mWater; + OEngine::Render::OgreRenderer &mRendering; MWRender::Objects mObjects; diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp new file mode 100644 index 000000000..9de55e3a5 --- /dev/null +++ b/apps/openmw/mwrender/water.cpp @@ -0,0 +1,95 @@ +#include "water.hpp" + +namespace MWRender +{ + +Water::Water (Ogre::Camera *camera, const ESM::Cell* cell) : + mCamera (camera), mViewport (camera->getViewport()), mSceneManager (camera->getSceneManager()), + mIsUnderwater(false) +{ + try + { + Ogre::CompositorManager::getSingleton().addCompositor(mViewport, "Water", -1); + Ogre::CompositorManager::getSingleton().setCompositorEnabled(mViewport, "Water", false); + } catch(...) {} + + mTop = cell->water; + + mIsUnderwater = false; + + mWaterPlane = Ogre::Plane(Ogre::Vector3::UNIT_Y, 0); + + Ogre::MeshManager::getSingleton().createPlane("water", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, mWaterPlane, CELL_SIZE*5, CELL_SIZE * 5, 10, 10, true, 1, 3,5, Ogre::Vector3::UNIT_Z); + + mWater = mSceneManager->createEntity("water"); + + mWater->setMaterialName("Examples/Water0"); + + mWaterNode = mSceneManager->getRootSceneNode()->createChildSceneNode(); + mWaterNode->setPosition(0, mTop, 0); + + if(!(cell->data.flags & cell->Interior)) + { + mWaterNode->setPosition(getSceneNodeCoordinates(cell->data.gridX, cell->data.gridY)); + } + mWaterNode->attachObject(mWater); +} + + +Water::~Water() +{ + Ogre::MeshManager::getSingleton().remove("water"); + + mWaterNode->detachObject(mWater); + mSceneManager->destroyEntity(mWater); + mSceneManager->destroySceneNode(mWaterNode); + + Ogre::CompositorManager::getSingleton().removeCompositorChain(mViewport); +} + +void Water::changeCell(const ESM::Cell* cell) +{ + mTop = cell->water; + + if(!(cell->data.flags & cell->Interior)) + mWaterNode->setPosition(getSceneNodeCoordinates(cell->data.gridX, cell->data.gridY)); + else + setHeight(mTop); +} + +void Water::setHeight(const float height) +{ + mTop = height; + mWaterNode->setPosition(0, height, 0); +} + +void Water::toggle() +{ + mWater->setVisible(!mWater->getVisible()); +} + +void Water::checkUnderwater(float y) +{ + if ((mIsUnderwater && y > mTop) || !mWater->isVisible()) + { + try { + Ogre::CompositorManager::getSingleton().setCompositorEnabled(mViewport, "Water", false); + } catch(...) {} + mIsUnderwater = false; + } + + if (!mIsUnderwater && y < mTop && mWater->isVisible()) + { + try { + Ogre::CompositorManager::getSingleton().setCompositorEnabled(mViewport, "Water", true); + } catch(...) {} + mIsUnderwater = true; + } +} + +Ogre::Vector3 Water::getSceneNodeCoordinates(int gridX, int gridY) +{ + return Ogre::Vector3(gridX * CELL_SIZE + (CELL_SIZE / 2), mTop, -gridY * CELL_SIZE - (CELL_SIZE / 2)); +} + +} // namespace diff --git a/apps/openmw/mwrender/water.hpp b/apps/openmw/mwrender/water.hpp new file mode 100644 index 000000000..5a5d1cca0 --- /dev/null +++ b/apps/openmw/mwrender/water.hpp @@ -0,0 +1,40 @@ +#ifndef GAME_MWRENDER_WATER_H +#define GAME_MWRENDER_WATER_H + +#include +#include + +namespace MWRender { + + /// Water rendering + class Water : Ogre::RenderTargetListener, Ogre::Camera::Listener + { + static const int CELL_SIZE = 8192; + Ogre::Camera *mCamera; + Ogre::SceneManager *mSceneManager; + Ogre::Viewport *mViewport; + + Ogre::Plane mWaterPlane; + Ogre::SceneNode *mWaterNode; + Ogre::Entity *mWater; + + bool mIsUnderwater; + int mTop; + + Ogre::Vector3 getSceneNodeCoordinates(int gridX, int gridY); + + public: + Water (Ogre::Camera *camera, const ESM::Cell* cell); + ~Water(); + + void toggle(); + + void checkUnderwater(float y); + void changeCell(const ESM::Cell* cell); + void setHeight(const float height); + + }; + +} + +#endif diff --git a/apps/openmw/mwscript/cellextensions.cpp b/apps/openmw/mwscript/cellextensions.cpp index bb5263203..d69c42ab3 100644 --- a/apps/openmw/mwscript/cellextensions.cpp +++ b/apps/openmw/mwscript/cellextensions.cpp @@ -133,11 +133,70 @@ namespace MWScript } }; + class OpGetWaterLevel : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + InterpreterContext& context + = static_cast (runtime.getContext()); + + MWWorld::Ptr::CellStore *cell = context.getWorld().getPlayer().getPlayer().getCell(); + runtime.push (cell->mWaterLevel); + } + }; + + class OpSetWaterLevel : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + InterpreterContext& context + = static_cast (runtime.getContext()); + + Interpreter::Type_Float level = runtime[0].mFloat; + + MWWorld::Ptr::CellStore *cell = context.getWorld().getPlayer().getPlayer().getCell(); + + if (!(cell->cell->data.flags & ESM::Cell::Interior)) + throw std::runtime_error("Can't set water level in exterior cell"); + + cell->mWaterLevel = level; + context.getEnvironment().mWorld->setWaterHeight(cell->mWaterLevel); + } + }; + + class OpModWaterLevel : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + InterpreterContext& context + = static_cast (runtime.getContext()); + + Interpreter::Type_Float level = runtime[0].mFloat; + + MWWorld::Ptr::CellStore *cell = context.getWorld().getPlayer().getPlayer().getCell(); + + if (!(cell->cell->data.flags & ESM::Cell::Interior)) + throw std::runtime_error("Can't set water level in exterior cell"); + + cell->mWaterLevel +=level; + context.getEnvironment().mWorld->setWaterHeight(cell->mWaterLevel); + } + }; + const int opcodeCellChanged = 0x2000000; const int opcodeCOC = 0x2000026; const int opcodeCOE = 0x200008e; const int opcodeGetInterior = 0x2000131; const int opcodeGetPCCell = 0x2000136; + const int opcodeGetWaterLevel = 0x2000141; + const int opcodeSetWaterLevel = 0x2000142; + const int opcodeModWaterLevel = 0x2000143; void registerExtensions (Compiler::Extensions& extensions) { @@ -146,8 +205,11 @@ namespace MWScript extensions.registerInstruction ("centeroncell", "S", opcodeCOC); extensions.registerInstruction ("coe", "ll", opcodeCOE); extensions.registerInstruction ("centeronexterior", "ll", opcodeCOE); + extensions.registerInstruction ("setwaterlevel", "f", opcodeSetWaterLevel); + extensions.registerInstruction ("modwaterlevel", "f", opcodeModWaterLevel); extensions.registerFunction ("getinterior", 'l', "", opcodeGetInterior); extensions.registerFunction ("getpccell", 'l', "c", opcodeGetPCCell); + extensions.registerFunction ("getwaterlevel", 'f', "", opcodeGetWaterLevel); } void installOpcodes (Interpreter::Interpreter& interpreter) @@ -157,6 +219,9 @@ namespace MWScript interpreter.installSegment5 (opcodeCOE, new OpCOE); interpreter.installSegment5 (opcodeGetInterior, new OpGetInterior); interpreter.installSegment5 (opcodeGetPCCell, new OpGetPCCell); + interpreter.installSegment5 (opcodeGetWaterLevel, new OpGetWaterLevel); + interpreter.installSegment5 (opcodeSetWaterLevel, new OpSetWaterLevel); + interpreter.installSegment5 (opcodeModWaterLevel, new OpModWaterLevel); } } } diff --git a/apps/openmw/mwscript/docs/vmformat.txt b/apps/openmw/mwscript/docs/vmformat.txt index eab5bf846..7ff60a203 100644 --- a/apps/openmw/mwscript/docs/vmformat.txt +++ b/apps/openmw/mwscript/docs/vmformat.txt @@ -123,4 +123,8 @@ op 0x200013d: FadeOut op 0x200013e: FadeTo op 0x200013f: GetCurrentWeather op 0x2000140: ChangeWeather -opcodes 0x2000141-0x3ffffff unused +op 0x2000141: GetWaterLevel +op 0x2000142: SetWaterLevel +op 0x2000143: ModWaterLevel +op 0x2000144: ToggleWater, twa +opcodes 0x2000145-0x3ffffff unused diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index 5bfffd3a2..5109319ed 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -175,6 +175,19 @@ namespace MWScript } }; + class OpToggleWater : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + InterpreterContext& context = + static_cast (runtime.getContext()); + + context.getWorld().toggleWater(); + } + }; + const int opcodeXBox = 0x200000c; const int opcodeOnActivate = 0x200000d; const int opcodeActivate = 0x2000075; @@ -187,6 +200,7 @@ namespace MWScript const int opcodeFadeIn = 0x200013c; const int opcodeFadeOut = 0x200013d; const int opcodeFadeTo = 0x200013e; + const int opcodeToggleWater = 0x2000144; void registerExtensions (Compiler::Extensions& extensions) { @@ -204,6 +218,8 @@ namespace MWScript extensions.registerInstruction ("fadein", "f", opcodeFadeIn); extensions.registerInstruction ("fadeout", "f", opcodeFadeOut); extensions.registerInstruction ("fadeto", "ff", opcodeFadeTo); + extensions.registerInstruction ("togglewater", "", opcodeToggleWater); + extensions.registerInstruction ("twa", "", opcodeToggleWater); } void installOpcodes (Interpreter::Interpreter& interpreter) @@ -220,6 +236,7 @@ namespace MWScript interpreter.installSegment5 (opcodeFadeIn, new OpFadeIn); interpreter.installSegment5 (opcodeFadeOut, new OpFadeOut); interpreter.installSegment5 (opcodeFadeTo, new OpFadeTo); + interpreter.installSegment5 (opcodeToggleWater, new OpToggleWater); } } } diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index 5122b3a5a..041759683 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -441,12 +441,34 @@ void OpenAL_Output::init(const std::string &devname) try { ALCuint maxtotal = std::min(maxmono+maxstereo, 256); - for(size_t i = 0;i < maxtotal;i++) + if (maxtotal == 0) // workaround for broken implementations { - ALuint src = 0; - alGenSources(1, &src); - throwALerror(); - mFreeSources.push_back(src); + maxtotal = 256; + bool stop = false; + for(size_t i = 0;i < maxtotal && !stop;i++) // generate source until error returned + { + ALuint src = 0; + alGenSources(1, &src); + ALenum err = alGetError(); + if(err != AL_NO_ERROR) + { + stop = true; + } + else + { + mFreeSources.push_back(src); + } + } + } + else // normal case + { + for(size_t i = 0;i < maxtotal;i++) + { + ALuint src = 0; + alGenSources(1, &src); + throwALerror(); + mFreeSources.push_back(src); + } } } catch(std::exception &e) diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 22955bf32..df7d20bb6 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -54,9 +54,11 @@ void insertCellRefList(MWRender::RenderingManager& rendering, MWWorld::Environme namespace MWWorld { + void Scene::update (float duration){ mRendering.update (duration); } + void Scene::unloadCell (CellStoreCollection::iterator iter) { std::cout << "Unloading cell\n"; @@ -79,6 +81,7 @@ namespace MWWorld mPhysics->removeObject (node->getName()); } } + mRendering.removeCell(*iter); //mPhysics->removeObject("Unnamed_43"); @@ -87,6 +90,7 @@ namespace MWWorld mEnvironment.mSoundManager->stopSound (*iter); mActiveCells.erase(*iter); + } @@ -101,7 +105,7 @@ namespace MWWorld mActiveCells.insert(cell); if(result.second){ insertCell(*cell, mEnvironment); - mRendering.cellAdded (cell); + mRendering.cellAdded(cell); mRendering.configureAmbient(*cell); mRendering.requestMap(cell); mRendering.configureAmbient(*cell); @@ -192,6 +196,7 @@ namespace MWWorld mCurrentCell = *iter; + // adjust player playerCellChange (mWorld->getExterior(X, Y), position, adjustPlayerPos); @@ -199,6 +204,7 @@ namespace MWWorld mWorld->adjustSky(); mCellChanged = true; + mRendering.waterAdded(mCurrentCell); } //We need the ogre renderer and a scene node. @@ -238,6 +244,7 @@ namespace MWWorld Ptr::CellStore *cell = mWorld->getInterior(cellName); loadCell (cell); + // adjust player mCurrentCell = cell; @@ -250,6 +257,8 @@ namespace MWWorld mWorld->adjustSky(); mCellChanged = true; + + mRendering.waterAdded(cell); } void Scene::changeToExteriorCell (const ESM::Position& position) diff --git a/apps/openmw/mwworld/world.cpp b/apps/openmw/mwworld/world.cpp index dd3044733..6f03fa37f 100644 --- a/apps/openmw/mwworld/world.cpp +++ b/apps/openmw/mwworld/world.cpp @@ -540,9 +540,10 @@ namespace MWWorld ptr.getRefData().getPosition().pos[0] = x; ptr.getRefData().getPosition().pos[1] = y; ptr.getRefData().getPosition().pos[2] = z; - if (ptr==mPlayer->getPlayer()) { + //std::cout << "X:" << ptr.getRefData().getPosition().pos[0] << " Z: " << ptr.getRefData().getPosition().pos[1] << "\n"; + Ptr::CellStore *currentCell = mWorldScene->getCurrentCell(); if (currentCell) { @@ -832,4 +833,15 @@ namespace MWWorld { return mRendering->getFader(); } + + void World::setWaterHeight(const float height) + { + mRendering->setWaterHeight(height); + } + + void World::toggleWater() + { + mRendering->toggleWater(); + } + } diff --git a/apps/openmw/mwworld/world.hpp b/apps/openmw/mwworld/world.hpp index 7d8a1b9a0..7f8b7e861 100644 --- a/apps/openmw/mwworld/world.hpp +++ b/apps/openmw/mwworld/world.hpp @@ -118,6 +118,9 @@ namespace MWWorld Ptr::CellStore *getExterior (int x, int y); Ptr::CellStore *getInterior (const std::string& name); + + void setWaterHeight(const float height); + void toggleWater(); void adjustSky(); diff --git a/components/esm/loadcell.cpp b/components/esm/loadcell.cpp index 80a0f3e5a..158cc0867 100644 --- a/components/esm/loadcell.cpp +++ b/components/esm/loadcell.cpp @@ -21,8 +21,13 @@ void Cell::load(ESMReader &esm) if (data.flags & Interior) { // Interior cells - - if (esm.isNextSub("INTV") || esm.isNextSub("WHGT")) + if (esm.isNextSub("INTV")) + { + int waterl; + esm.getHT(waterl); + water = (float) waterl; + } + else if (esm.isNextSub("WHGT")) esm.getHT(water); // Quasi-exterior cells have a region (which determines the diff --git a/components/esm/loadcell.hpp b/components/esm/loadcell.hpp index a5d61f930..8070f9c03 100644 --- a/components/esm/loadcell.hpp +++ b/components/esm/loadcell.hpp @@ -114,7 +114,7 @@ struct Cell ESM_Context context; // File position DATAstruct data; AMBIstruct ambi; - int water; // Water level + float water; // Water level int mapColor; void load(ESMReader &esm); diff --git a/components/esm_store/cell_store.hpp b/components/esm_store/cell_store.hpp index c0a84c577..024412291 100644 --- a/components/esm_store/cell_store.hpp +++ b/components/esm_store/cell_store.hpp @@ -96,12 +96,16 @@ namespace ESMS }; CellStore (const ESM::Cell *cell_) : cell (cell_), mState (State_Unloaded) - {} + { + mWaterLevel = cell->water; + } const ESM::Cell *cell; State mState; std::vector mIds; + float mWaterLevel; + // Lists for each individual object type CellRefList activators; CellRefList potions; diff --git a/files/CMakeLists.txt b/files/CMakeLists.txt new file mode 100644 index 000000000..203b40681 --- /dev/null +++ b/files/CMakeLists.txt @@ -0,0 +1,13 @@ +project(resources) + +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/water/caustic_0.png "${OpenMW_BINARY_DIR}/resources/water/caustic_0.png" COPYONLY) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/water/Example_Fresnel.cg "${OpenMW_BINARY_DIR}/resources/water/Example_Fresnel.cg" COPYONLY) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/water/Example_FresnelPS.asm "${OpenMW_BINARY_DIR}/resources/water/Example_FresnelPS.asm" COPYONLY) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/water/GlassFP.cg "${OpenMW_BINARY_DIR}/resources/water/GlassFP.cg" COPYONLY) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/water/GlassVP.cg "${OpenMW_BINARY_DIR}/resources/water/GlassVP.cg" COPYONLY) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/water/perlinvolume.dds "${OpenMW_BINARY_DIR}/resources/water/perlinvolume.dds" COPYONLY) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/water/Water02.jpg "${OpenMW_BINARY_DIR}/resources/water/Water02.jpg" COPYONLY) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/water/water.compositor "${OpenMW_BINARY_DIR}/resources/water/water.compositor" COPYONLY) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/water/waves2.dds "${OpenMW_BINARY_DIR}/resources/water/waves2.dds" COPYONLY) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/water/Examples-Water.material "${OpenMW_BINARY_DIR}/resources/water/Examples-Water.material" COPYONLY) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/water/WaterNormal1.tga "${OpenMW_BINARY_DIR}/resources/water/WaterNormal1.tga" COPYONLY) diff --git a/files/water/Example_Fresnel.cg b/files/water/Example_Fresnel.cg new file mode 100644 index 000000000..e091fc587 --- /dev/null +++ b/files/water/Example_Fresnel.cg @@ -0,0 +1,116 @@ +// Vertex program for fresnel reflections / refractions +void main_vp( + float4 pos : POSITION, + float4 normal : NORMAL, + float2 tex : TEXCOORD0, + + out float4 oPos : POSITION, + out float3 noiseCoord : TEXCOORD0, + out float4 projectionCoord : TEXCOORD1, + out float3 oEyeDir : TEXCOORD2, + out float3 oNormal : TEXCOORD3, + + uniform float4x4 worldViewProjMatrix, + uniform float3 eyePosition, // object space + uniform float timeVal, + uniform float scale, // the amount to scale the noise texture by + uniform float scroll, // the amount by which to scroll the noise + uniform float noise // the noise perturb as a factor of the time + ) +{ + oPos = mul(worldViewProjMatrix, pos); + // Projective texture coordinates, adjust for mapping + float4x4 scalemat = float4x4(0.5, 0, 0, 0.5, + 0,-0.5, 0, 0.5, + 0, 0, 0.5, 0.5, + 0, 0, 0, 1); + projectionCoord = mul(scalemat, oPos); + // Noise map coords + noiseCoord.xy = (tex + (timeVal * scroll)) * scale; + noiseCoord.z = noise * timeVal; + + oEyeDir = normalize(pos.xyz - eyePosition); + oNormal = normal.rgb; + +} + +// Fragment program for distorting a texture using a 3D noise texture +void main_fp( + float3 noiseCoord : TEXCOORD0, + float4 projectionCoord : TEXCOORD1, + float3 eyeDir : TEXCOORD2, + float3 normal : TEXCOORD3, + + out float4 col : COLOR, + + uniform float4 tintColour, + uniform float noiseScale, + uniform float fresnelBias, + uniform float fresnelScale, + uniform float fresnelPower, + uniform sampler2D waterTex : register(s0), + uniform sampler2D noiseMap : register(s1), + uniform sampler2D reflectMap : register(s2), + uniform sampler2D refractMap : register(s3) + ) +{ + // Do the tex projection manually so we can distort _after_ + float2 final = projectionCoord.xy / projectionCoord.w; + + // Noise + float3 noiseNormal = (tex2D(noiseMap, (noiseCoord.xy / 5)).rgb - 0.5).rbg * noiseScale; + final += noiseNormal.xz; + + // Fresnel + //normal = normalize(normal + noiseNormal.xz); + float fresnel = fresnelBias + fresnelScale * pow(1 + dot(eyeDir, normal), fresnelPower); + + // Reflection / refraction + float4 reflectionColour = tex2D(reflectMap, final); + float4 refractionColour = tex2D(refractMap, final) + tintColour; + + // Final colour + col = lerp(refractionColour, reflectionColour, fresnel) * tex2D(waterTex, noiseNormal) / 3 ; + + +} + + +// Old version to match ATI PS 1.3 implementation +void main_vp_old( + float4 pos : POSITION, + float4 normal : NORMAL, + float2 tex : TEXCOORD0, + + out float4 oPos : POSITION, + out float fresnel : COLOR, + out float3 noiseCoord : TEXCOORD0, + out float4 projectionCoord : TEXCOORD1, + + uniform float4x4 worldViewProjMatrix, + uniform float3 eyePosition, // object space + uniform float fresnelBias, + uniform float fresnelScale, + uniform float fresnelPower, + uniform float timeVal, + uniform float scale, // the amount to scale the noise texture by + uniform float scroll, // the amount by which to scroll the noise + uniform float noise // the noise perturb as a factor of the time + ) +{ + oPos = mul(worldViewProjMatrix, pos); + // Projective texture coordinates, adjust for mapping + float4x4 scalemat = float4x4(0.5, 0, 0, 0.5, + 0,-0.5, 0, 0.5, + 0, 0, 0.5, 0.5, + 0, 0, 0, 1); + projectionCoord = mul(scalemat, oPos); + // Noise map coords + noiseCoord.xy = (tex + (timeVal * scroll)) * scale; + noiseCoord.z = noise * timeVal; + + // calc fresnel factor (reflection coefficient) + float3 eyeDir = normalize(pos.xyz - eyePosition); + fresnel = fresnelBias + fresnelScale * pow(1 + dot(eyeDir, normal), fresnelPower); + +} diff --git a/files/water/Example_FresnelPS.asm b/files/water/Example_FresnelPS.asm new file mode 100644 index 000000000..2de078ef5 --- /dev/null +++ b/files/water/Example_FresnelPS.asm @@ -0,0 +1,72 @@ +ps.1.4 + // conversion from Cg generated ARB_fragment_program to ps.1.4 by NFZ + // command line args: -profile arbfp1 -entry main_fp + // program main_fp + // c0 : distortionRange + // c1 : tintColour + // testure 0 : noiseMap + // texture 1 : reflectMap + // texture 2 : refractMap + // v0.x : fresnel + // t0.xyz : noiseCoord + // t1.xyw : projectionCoord + +def c2, 2, 1, 0, 0 + + // Cg: distort.x = tex3D(noiseMap, noiseCoord).x; + // arbfp1: TEX R0.x, fragment.texcoord[0], texture[0], 3D; + // sample noise map using noiseCoord in TEX unit 0 + +texld r0, t0.xyz + + // get projected texture coordinates from TEX coord 1 + // will be used in phase 2 + +texcrd r1.xy, t1_dw.xyw +mov r1.z, c2.y + + // Cg: distort.y = tex3D(noiseMap, noiseCoord + yoffset).x; + // arbfp1: ADD R1.xyz, fragment.texcoord[0], c1; + // arbfp1: TEX R1.x, R1, texture[0], 3D; + // arbfp1: MOV R0.y, R1.x; + + // Cg: distort = (distort * 2 - 1) * distortionRange; + // arbfp1: MAD R0.xy, R0, c0.x, -c0.y; + // arbfp1: MUL R0.xy, R0, u0.x; + // (distort * 2 - 1) same as 2*(distort -.5) so use _bx2 + + + // Cg: final = projectionCoord.xy / projectionCoord.w; + // Cg: final += distort; + // arbfp1: RCP R0.w, fragment.texcoord[1].w; + // arbfp1: MAD R0.xy, fragment.texcoord[1], R0.w, R0; + // final = (distort * projectionCoord.w) + projectionCoord.xy + // for ps.1.4 have to re-arrange things a bit to perturb projected texture coordinates + +mad r0.xyz, r0_bx2, c0.x, r1 + +phase + + // do dependant texture reads + // Cg: reflectionColour = tex2D(reflectMap, final); + // arbfp1: TEX R0, R0, texture[1], 2D; + // sampe reflectMap using dependant read : texunit 1 + +texld r1, r0.xyz + + // Cg: refractionColour = tex2D(refractMap, final) + tintColour; + // arbfp1: TEX R1, R0, texture[2], 2D; + // sample refractMap : texunit 2 + +texld r2, r0.xyz + + // adding tintColour that is in global c1 + // arbfp1: ADD R1, R1, u1; + +add r2, r2, c1 + + // Cg: col = lerp(refractionColour, reflectionColour, fresnel); + // arbfp1: ADD R0, R0, -R1; + // arbfp1: MAD result.color, fragment.color.primary.x, R0, R1; + +lrp r0, v0.x, r1, r2 diff --git a/files/water/Examples-Water.material b/files/water/Examples-Water.material new file mode 100644 index 000000000..2b46d6e08 --- /dev/null +++ b/files/water/Examples-Water.material @@ -0,0 +1,149 @@ + +vertex_program Water/GlassVP cg +{ + source GlassVP.cg + entry_point glass_vp + profiles vs_1_1 arbvp1 + + default_params + { + param_named_auto worldViewProj worldviewproj_matrix + } +} + + +fragment_program Water/GlassFP cg +{ + source GlassFP.cg + entry_point main_ps + profiles ps_2_0 arbfp1 +} + +material Water/Compositor +{ + technique + { + pass + { + depth_check off + vertex_program_ref Water/GlassVP + { + param_named_auto timeVal time 0.25 + param_named scale float 0.1 + } + + fragment_program_ref Water/GlassFP + { + param_named tintColour float4 0 0.35 0.35 1 + } + + texture_unit RT + { + tex_coord_set 0 + tex_address_mode clamp + filtering linear linear linear + } + + texture_unit + { + texture WaterNormal1.tga 2d + tex_coord_set 1 + //tex_address_mode clamp + filtering linear linear linear + } + texture_unit + { + texture caustic_0.png 2d + tex_coord_set 2 + //tex_address_mode clamp + filtering linear linear linear + } + } + } +} +vertex_program Water/RefractReflectVP cg +{ + source Example_Fresnel.cg + entry_point main_vp + profiles vs_1_1 arbvp1 +} +vertex_program Water/RefractReflectVPold cg +{ + source Example_Fresnel.cg + entry_point main_vp_old + profiles vs_1_1 arbvp1 +} + +fragment_program Water/RefractReflectFP cg +{ + source Example_Fresnel.cg + entry_point main_fp + // sorry, ps_1_1 and fp20 can't do this + profiles ps_2_0 arbfp1 +} + +fragment_program Water/RefractReflectPS asm +{ + source Example_FresnelPS.asm + // sorry, only for ps_1_4 :) + syntax ps_1_4 + +} +material Examples/Water0 +{ + + technique + { + pass + { + // + + depth_write off + vertex_program_ref Water/RefractReflectVP + { + param_named_auto worldViewProjMatrix worldviewproj_matrix + param_named_auto eyePosition camera_position_object_space + param_named_auto timeVal time 0.15 + param_named scroll float 1 + param_named scale float 1 + param_named noise float 1 + // scroll and noisePos will need updating per frame + } + fragment_program_ref Water/RefractReflectFP + { + param_named fresnelBias float -0.1 + param_named fresnelScale float 0.8 + param_named fresnelPower float 20 + param_named tintColour float4 1 1 1 1 + param_named noiseScale float 0.05 + } + // Water + scene_blend alpha_blend + texture_unit + { + + // Water texture + texture Water02.jpg + // min / mag filtering, no mip + filtering linear linear none + alpha_op_ex source1 src_manual src_current 0.9 + + } + // Noise + texture_unit + { + alpha_op_ex source1 src_manual src_current 0.9 + // Perlin noise volume + texture waves2.dds + // min / mag filtering, no mip + filtering linear linear none + } + + + } + + + } + +} + diff --git a/files/water/GlassFP.cg b/files/water/GlassFP.cg new file mode 100644 index 000000000..eb18885d2 --- /dev/null +++ b/files/water/GlassFP.cg @@ -0,0 +1,15 @@ +sampler RT : register(s0); +sampler NormalMap : register(s1); +sampler CausticMap : register(s2); + +float4 main_ps(float2 iTexCoord : TEXCOORD0, + float3 noiseCoord : TEXCOORD1, + uniform float4 tintColour) : COLOR +{ + float4 normal = tex2D(NormalMap, noiseCoord); + + + return tex2D(RT, iTexCoord + normal.xy * 0.05) + + (tex2D(CausticMap, noiseCoord) / 5) + + tintColour ; +} diff --git a/files/water/GlassVP.cg b/files/water/GlassVP.cg new file mode 100644 index 000000000..71153769c --- /dev/null +++ b/files/water/GlassVP.cg @@ -0,0 +1,24 @@ +void glass_vp +( + in float4 inPos : POSITION, + + out float4 pos : POSITION, + out float2 uv0 : TEXCOORD0, + out float4 noiseCoord : TEXCOORD1, + + uniform float4x4 worldViewProj, + uniform float timeVal, + uniform float scale +) +{ + // Use standardise transform, so work accord with render system specific (RS depth, requires texture flipping, etc) + pos = mul(worldViewProj, inPos); + + // The input positions adjusted by texel offsets, so clean up inaccuracies + inPos.xy = sign(inPos.xy); + + // Convert to image-space + uv0 = (float2(inPos.x, -inPos.y) + 1.0f) * 0.5f; + noiseCoord = (pos + timeVal) * scale; +} + diff --git a/files/water/Water02.jpg b/files/water/Water02.jpg new file mode 100644 index 000000000..3efda7b6a Binary files /dev/null and b/files/water/Water02.jpg differ diff --git a/files/water/WaterNormal1.tga b/files/water/WaterNormal1.tga new file mode 100644 index 000000000..a9ca11b7e Binary files /dev/null and b/files/water/WaterNormal1.tga differ diff --git a/files/water/caustic_0.png b/files/water/caustic_0.png new file mode 100644 index 000000000..fee464860 Binary files /dev/null and b/files/water/caustic_0.png differ diff --git a/files/water/perlinvolume.dds b/files/water/perlinvolume.dds new file mode 100644 index 000000000..bd8147d49 Binary files /dev/null and b/files/water/perlinvolume.dds differ diff --git a/files/water/water.compositor b/files/water/water.compositor new file mode 100644 index 000000000..67bf90896 --- /dev/null +++ b/files/water/water.compositor @@ -0,0 +1,21 @@ +compositor Water +{ + technique + { + texture rt0 target_width target_height PF_R8G8B8 + + target rt0 { input previous } + + target_output + { + // Start with clear output + input none + + pass render_quad + { + material Water/Compositor + input 0 rt0 + } + } + } +} diff --git a/files/water/waves2.dds b/files/water/waves2.dds new file mode 100644 index 000000000..c379886fa Binary files /dev/null and b/files/water/waves2.dds differ