From 14cf0ce1dc67d561a04a3e5b13fa633af54ca939 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sun, 12 Jan 2020 11:42:47 +0400 Subject: [PATCH 01/13] Implement instanced groundcover --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/engine.cpp | 9 +- apps/openmw/engine.hpp | 2 + apps/openmw/main.cpp | 15 +- apps/openmw/mwgui/settingswindow.cpp | 4 +- apps/openmw/mwrender/groundcover.cpp | 264 ++++++++++++++++++ apps/openmw/mwrender/groundcover.hpp | 69 +++++ apps/openmw/mwrender/objectpaging.cpp | 1 + apps/openmw/mwrender/renderingmanager.cpp | 77 ++++- apps/openmw/mwrender/renderingmanager.hpp | 9 +- apps/openmw/mwrender/vismask.hpp | 4 +- apps/openmw/mwrender/water.cpp | 5 +- apps/openmw/mwworld/cellpreloader.cpp | 7 + apps/openmw/mwworld/cellreflist.hpp | 2 + apps/openmw/mwworld/cellstore.cpp | 13 + apps/openmw/mwworld/contentloader.hpp | 2 +- apps/openmw/mwworld/esmloader.cpp | 18 +- apps/openmw/mwworld/esmloader.hpp | 2 +- apps/openmw/mwworld/esmstore.cpp | 9 + apps/openmw/mwworld/esmstore.hpp | 17 ++ apps/openmw/mwworld/worldimp.cpp | 29 +- apps/openmw/mwworld/worldimp.hpp | 3 +- components/config/gamesettings.cpp | 2 + components/esm/esmreader.cpp | 9 +- components/esm/esmreader.hpp | 6 +- components/esm/loadstat.cpp | 2 + components/esm/loadstat.hpp | 2 + components/resource/scenemanager.cpp | 6 +- components/resource/scenemanager.hpp | 2 +- components/resource/stats.cpp | 4 +- components/shader/shadermanager.cpp | 2 + components/terrain/chunkmanager.cpp | 2 +- components/terrain/chunkmanager.hpp | 2 +- components/terrain/quadtreeworld.cpp | 29 +- components/terrain/quadtreeworld.hpp | 5 +- components/terrain/terraingrid.cpp | 8 + components/terrain/terraingrid.hpp | 1 + components/terrain/world.cpp | 41 ++- components/terrain/world.hpp | 1 + docs/source/reference/modding/extended.rst | 56 +++- .../modding/settings/groundcover.rst | 68 +++++ .../reference/modding/settings/index.rst | 1 + .../reference/modding/settings/shaders.rst | 1 + .../reference/modding/settings/water.rst | 3 +- files/mygui/openmw_settings_window.layout | 1 + files/settings-default.cfg | 19 ++ files/shaders/CMakeLists.txt | 2 + files/shaders/groundcover_fragment.glsl | 93 ++++++ files/shaders/groundcover_vertex.glsl | 143 ++++++++++ files/shaders/lighting.glsl | 15 +- 50 files changed, 1015 insertions(+), 74 deletions(-) create mode 100644 apps/openmw/mwrender/groundcover.cpp create mode 100644 apps/openmw/mwrender/groundcover.hpp create mode 100644 docs/source/reference/modding/settings/groundcover.rst create mode 100644 files/shaders/groundcover_fragment.glsl create mode 100644 files/shaders/groundcover_vertex.glsl diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index c01cbe60c..fdc47e8d7 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -21,7 +21,7 @@ add_openmw_dir (mwrender actors objects renderingmanager animation rotatecontroller sky npcanimation vismask creatureanimation effectmanager util renderinginterface pathgrid rendermode weaponanimation screenshotmanager bulletdebugdraw globalmap characterpreview camera viewovershoulder localmap water terrainstorage ripplesimulation - renderbin actoranimation landmanager navmesh actorspaths recastmesh fogmanager objectpaging + renderbin actoranimation landmanager navmesh actorspaths recastmesh fogmanager objectpaging groundcover ) add_openmw_dir (mwinput diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index ead2726cd..103d06f31 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -460,6 +460,11 @@ void OMW::Engine::addContentFile(const std::string& file) mContentFiles.push_back(file); } +void OMW::Engine::addGroundcoverFile(const std::string& file) +{ + mGroundcoverFiles.emplace_back(file); +} + void OMW::Engine::setSkipMenu (bool skipMenu, bool newGame) { mSkipMenu = skipMenu; @@ -720,8 +725,8 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) } // Create the world - mEnvironment.setWorld( new MWWorld::World (mViewer, rootNode, mResourceSystem.get(), mWorkQueue.get(), - mFileCollections, mContentFiles, mEncoder, mActivationDistanceOverride, mCellName, + mEnvironment.setWorld(new MWWorld::World (mViewer, rootNode, mResourceSystem.get(), mWorkQueue.get(), + mFileCollections, mContentFiles, mGroundcoverFiles, mEncoder, mActivationDistanceOverride, mCellName, mStartupScript, mResDir.string(), mCfgMgr.getUserDataPath().string())); mEnvironment.getWorld()->setupPlayer(); diff --git a/apps/openmw/engine.hpp b/apps/openmw/engine.hpp index 3dd1a69b2..ff362f4b6 100644 --- a/apps/openmw/engine.hpp +++ b/apps/openmw/engine.hpp @@ -85,6 +85,7 @@ namespace OMW osgViewer::ScreenCaptureHandler::CaptureOperation *mScreenCaptureOperation; std::string mCellName; std::vector mContentFiles; + std::vector mGroundcoverFiles; bool mSkipMenu; bool mUseSound; bool mCompileAll; @@ -155,6 +156,7 @@ namespace OMW * @param file - filename (extension is required) */ void addContentFile(const std::string& file); + void addGroundcoverFile(const std::string& file); /// Disable or enable all sounds void setSoundUsage(bool soundUsage); diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index 8eaac36e8..709ffda2c 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -62,6 +62,9 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat ("content", bpo::value()->default_value(Files::EscapeStringVector(), "") ->multitoken()->composing(), "content file(s): esm/esp, or omwgame/omwaddon") + ("groundcover", bpo::value()->default_value(Files::EscapeStringVector(), "") + ->multitoken()->composing(), "groundcover content file(s): esm/esp, or omwgame/omwaddon") + ("no-sound", bpo::value()->implicit_value(true) ->default_value(false), "disable all sounds") @@ -190,11 +193,15 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat return false; } - StringsVector::const_iterator it(content.begin()); - StringsVector::const_iterator end(content.end()); - for (; it != end; ++it) + for (auto& file : content) + { + engine.addContentFile(file); + } + + StringsVector groundcover = variables["groundcover"].as().toStdStringVector(); + for (auto& file : groundcover) { - engine.addContentFile(*it); + engine.addGroundcoverFile(file); } // startup-settings diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index 68dac4a95..538b3db5e 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -269,7 +269,7 @@ namespace MWGui mWaterTextureSize->setIndexSelected(2); int waterReflectionDetail = Settings::Manager::getInt("reflection detail", "Water"); - waterReflectionDetail = std::min(4, std::max(0, waterReflectionDetail)); + waterReflectionDetail = std::min(5, std::max(0, waterReflectionDetail)); mWaterReflectionDetail->setIndexSelected(waterReflectionDetail); mWindowBorderButton->setEnabled(!Settings::Manager::getBool("fullscreen", "Video")); @@ -353,7 +353,7 @@ namespace MWGui void SettingsWindow::onWaterReflectionDetailChanged(MyGUI::ComboBox* _sender, size_t pos) { - unsigned int level = std::min((unsigned int)4, (unsigned int)pos); + unsigned int level = std::min((unsigned int)5, (unsigned int)pos); Settings::Manager::setInt("reflection detail", "Water", level); apply(); } diff --git a/apps/openmw/mwrender/groundcover.cpp b/apps/openmw/mwrender/groundcover.cpp new file mode 100644 index 000000000..b9fdc2e28 --- /dev/null +++ b/apps/openmw/mwrender/groundcover.cpp @@ -0,0 +1,264 @@ +#include "groundcover.hpp" + +#include +#include + +#include + +#include "apps/openmw/mwworld/esmstore.hpp" +#include "apps/openmw/mwbase/environment.hpp" +#include "apps/openmw/mwbase/world.hpp" + +#include "vismask.hpp" + +namespace MWRender +{ + void GroundcoverUpdater::setWindSpeed(float windSpeed) + { + mWindSpeed = windSpeed; + } + + void GroundcoverUpdater::setPlayerPos(osg::Vec3f playerPos) + { + mPlayerPos = playerPos; + } + + void GroundcoverUpdater::setDefaults(osg::StateSet *stateset) + { + osg::ref_ptr windUniform = new osg::Uniform("windSpeed", 0.0f); + stateset->addUniform(windUniform.get()); + + osg::ref_ptr playerPosUniform = new osg::Uniform("playerPos", osg::Vec3f(0.f, 0.f, 0.f)); + stateset->addUniform(playerPosUniform.get()); + } + + void GroundcoverUpdater::apply(osg::StateSet *stateset, osg::NodeVisitor *nv) + { + osg::ref_ptr windUniform = stateset->getUniform("windSpeed"); + if (windUniform != nullptr) + windUniform->set(mWindSpeed); + + osg::ref_ptr playerPosUniform = stateset->getUniform("playerPos"); + if (playerPosUniform != nullptr) + playerPosUniform->set(mPlayerPos); + } + + class InstancingVisitor : public osg::NodeVisitor + { + public: + InstancingVisitor(std::vector& instances, osg::Vec3f& chunkPosition) + : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) + , mInstances(instances) + , mChunkPosition(chunkPosition) + { + } + + void apply(osg::Node& node) override + { + osg::ref_ptr ss = node.getStateSet(); + if (ss != nullptr) + { + removeAlpha(ss); + } + + traverse(node); + } + + void apply(osg::Geometry& geom) override + { + for (unsigned int i = 0; i < geom.getNumPrimitiveSets(); ++i) + { + geom.getPrimitiveSet(i)->setNumInstances(mInstances.size()); + } + + osg::ref_ptr transforms = new osg::Vec4Array(mInstances.size()); + osg::BoundingBox box; + float radius = geom.getBoundingBox().radius(); + for (unsigned int i = 0; i < transforms->getNumElements(); i++) + { + osg::Vec3f pos(mInstances[i].mPos.asVec3()); + osg::Vec3f relativePos = pos - mChunkPosition; + (*transforms)[i] = osg::Vec4f(relativePos, mInstances[i].mScale); + + // Use an additional margin due to groundcover animation + float instanceRadius = radius * mInstances[i].mScale * 1.1f; + osg::BoundingSphere instanceBounds(relativePos, instanceRadius); + box.expandBy(instanceBounds); + } + + geom.setInitialBound(box); + + osg::ref_ptr rotations = new osg::Vec3Array(mInstances.size()); + for (unsigned int i = 0; i < rotations->getNumElements(); i++) + { + (*rotations)[i] = mInstances[i].mPos.asRotationVec3(); + } + + geom.setVertexAttribArray(6, transforms.get(), osg::Array::BIND_PER_VERTEX); + geom.setVertexAttribArray(7, rotations.get(), osg::Array::BIND_PER_VERTEX); + + osg::ref_ptr ss = geom.getOrCreateStateSet(); + ss->setAttribute(new osg::VertexAttribDivisor(6, 1)); + ss->setAttribute(new osg::VertexAttribDivisor(7, 1)); + + removeAlpha(ss); + + traverse(geom); + } + private: + std::vector mInstances; + osg::Vec3f mChunkPosition; + + void removeAlpha(osg::StateSet* stateset) + { + // MGE uses default alpha settings for groundcover, so we can not rely on alpha properties + stateset->removeAttribute(osg::StateAttribute::ALPHAFUNC); + stateset->removeMode(GL_ALPHA_TEST); + stateset->removeAttribute(osg::StateAttribute::BLENDFUNC); + stateset->removeMode(GL_BLEND); + stateset->setRenderBinToInherit(); + } + }; + + class DensityCalculator + { + public: + DensityCalculator(float density) + : mDensity(density) + { + } + + bool isInstanceEnabled() + { + if (mDensity >= 1.f) return true; + + mCurrentGroundcover += mDensity; + if (mCurrentGroundcover < 1.f) return false; + + mCurrentGroundcover -= 1.f; + + return true; + } + void reset() { mCurrentGroundcover = 0.f; } + + private: + float mCurrentGroundcover = 0.f; + float mDensity = 0.f; + }; + + inline bool isInChunkBorders(ESM::CellRef& ref, osg::Vec2f& minBound, osg::Vec2f& maxBound) + { + osg::Vec2f size = maxBound - minBound; + if (size.x() >=1 && size.y() >=1) return true; + + osg::Vec3f pos = ref.mPos.asVec3(); + osg::Vec3f cellPos = pos / ESM::Land::REAL_SIZE; + if ((minBound.x() > std::floor(minBound.x()) && cellPos.x() < minBound.x()) || (minBound.y() > std::floor(minBound.y()) && cellPos.y() < minBound.y()) + || (maxBound.x() < std::ceil(maxBound.x()) && cellPos.x() >= maxBound.x()) || (minBound.y() < std::ceil(maxBound.y()) && cellPos.y() >= maxBound.y())) + return false; + + return true; + } + + osg::ref_ptr Groundcover::getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile) + { + ChunkId id = std::make_tuple(center, size, activeGrid); + + osg::ref_ptr obj = mCache->getRefFromObjectCache(id); + if (obj) + return obj->asNode(); + else + { + InstanceMap instances; + collectInstances(instances, size, center); + osg::ref_ptr node = createChunk(instances, center); + mCache->addEntryToObjectCache(id, node.get()); + return node; + } + } + + Groundcover::Groundcover(Resource::SceneManager* sceneManager, float density) + : GenericResourceManager(nullptr) + , mSceneManager(sceneManager) + , mDensity(density) + { + } + + void Groundcover::collectInstances(InstanceMap& instances, float size, const osg::Vec2f& center) + { + const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); + osg::Vec2f minBound = (center - osg::Vec2f(size/2.f, size/2.f)); + osg::Vec2f maxBound = (center + osg::Vec2f(size/2.f, size/2.f)); + DensityCalculator calculator(mDensity); + std::vector esm; + osg::Vec2i startCell = osg::Vec2i(std::floor(center.x() - size/2.f), std::floor(center.y() - size/2.f)); + for (int cellX = startCell.x(); cellX < startCell.x() + size; ++cellX) + { + for (int cellY = startCell.y(); cellY < startCell.y() + size; ++cellY) + { + const ESM::Cell* cell = store.get().searchStatic(cellX, cellY); + if (!cell) continue; + + calculator.reset(); + for (size_t i=0; imContextList.size(); ++i) + { + unsigned int index = cell->mContextList.at(i).index; + if (esm.size() <= index) + esm.resize(index+1); + cell->restore(esm[index], i); + ESM::CellRef ref; + ref.mRefNum.mContentFile = ESM::RefNum::RefNum_NoContentFile; + bool deleted = false; + while(cell->getNextRef(esm[index], ref, deleted)) + { + if (deleted) continue; + Misc::StringUtils::lowerCaseInPlace(ref.mRefID); + std::string model; + if (!store.isGroundcover(ref.mRefID, model)) continue; + if (model.empty()) continue; + + if (!calculator.isInstanceEnabled()) continue; + if (!isInChunkBorders(ref, minBound, maxBound)) continue; + + model = "meshes/" + model; + instances[model].emplace_back(ref, model); + } + } + } + } + } + + osg::ref_ptr Groundcover::createChunk(InstanceMap& instances, const osg::Vec2f& center) + { + osg::ref_ptr group = new osg::Group; + osg::Vec3f worldCenter = osg::Vec3f(center.x(), center.y(), 0)*ESM::Land::REAL_SIZE; + for (auto& pair : instances) + { + const osg::Node* temp = mSceneManager->getTemplate(pair.first); + osg::ref_ptr node = static_cast(temp->clone(osg::CopyOp::DEEP_COPY_ALL&(~osg::CopyOp::DEEP_COPY_TEXTURES))); + + // Keep link to original mesh to keep it in cache + group->getOrCreateUserDataContainer()->addUserObject(new Resource::TemplateRef(temp)); + + InstancingVisitor visitor(pair.second, worldCenter); + node->accept(visitor); + group->addChild(node); + } + + group->getBound(); + group->setNodeMask(Mask_Groundcover); + mSceneManager->recreateShaders(group, "groundcover", false, true); + + return group; + } + + unsigned int Groundcover::getNodeMask() + { + return Mask_Groundcover; + } + + void Groundcover::reportStats(unsigned int frameNumber, osg::Stats *stats) const + { + stats->setAttribute(frameNumber, "Groundcover Chunk", mCache->getCacheSize()); + } +} diff --git a/apps/openmw/mwrender/groundcover.hpp b/apps/openmw/mwrender/groundcover.hpp new file mode 100644 index 000000000..cd80978be --- /dev/null +++ b/apps/openmw/mwrender/groundcover.hpp @@ -0,0 +1,69 @@ +#ifndef OPENMW_MWRENDER_GROUNDCOVER_H +#define OPENMW_MWRENDER_GROUNDCOVER_H + +#include +#include +#include +#include + +namespace MWRender +{ + class GroundcoverUpdater : public SceneUtil::StateSetUpdater + { + public: + GroundcoverUpdater() + : mWindSpeed(0.f) + , mPlayerPos(osg::Vec3f()) + { + } + + void setWindSpeed(float windSpeed); + void setPlayerPos(osg::Vec3f playerPos); + + protected: + void setDefaults(osg::StateSet *stateset) override; + void apply(osg::StateSet *stateset, osg::NodeVisitor *nv) override; + + private: + float mWindSpeed; + osg::Vec3f mPlayerPos; + }; + + typedef std::tuple ChunkId; // Center, Size, ActiveGrid + class Groundcover : public Resource::GenericResourceManager, public Terrain::QuadTreeWorld::ChunkManager + { + public: + Groundcover(Resource::SceneManager* sceneManager, float density); + ~Groundcover() = default; + + osg::ref_ptr getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile) override; + + unsigned int getNodeMask() override; + + void reportStats(unsigned int frameNumber, osg::Stats* stats) const override; + + struct GroundcoverEntry + { + ESM::Position mPos; + float mScale; + std::string mModel; + + GroundcoverEntry(const ESM::CellRef& ref, const std::string& model) + { + mPos = ref.mPos; + mScale = ref.mScale; + mModel = model; + } + }; + + private: + Resource::SceneManager* mSceneManager; + float mDensity; + + typedef std::map> InstanceMap; + osg::ref_ptr createChunk(InstanceMap& instances, const osg::Vec2f& center); + void collectInstances(InstanceMap& instances, float size, const osg::Vec2f& center); + }; +} + +#endif diff --git a/apps/openmw/mwrender/objectpaging.cpp b/apps/openmw/mwrender/objectpaging.cpp index d8e856e76..478fde0f8 100644 --- a/apps/openmw/mwrender/objectpaging.cpp +++ b/apps/openmw/mwrender/objectpaging.cpp @@ -398,6 +398,7 @@ namespace MWRender int type = store.findStatic(ref.mRefID); if (!typeFilter(type,size>=2)) continue; if (deleted) { refs.erase(ref.mRefNum); continue; } + if (store.isGroundcover(ref.mRefID)) continue; refs[ref.mRefNum] = ref; } } diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 6ce431d2e..c755f46f8 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -68,10 +69,10 @@ #include "fogmanager.hpp" #include "objectpaging.hpp" #include "screenshotmanager.hpp" +#include "groundcover.hpp" namespace MWRender { - class StateUpdater : public SceneUtil::StateSetUpdater { public: @@ -243,6 +244,10 @@ namespace MWRender globalDefines["preLightEnv"] = Settings::Manager::getBool("apply lighting to environment maps", "Shaders") ? "1" : "0"; globalDefines["radialFog"] = Settings::Manager::getBool("radial fog", "Shaders") ? "1" : "0"; + float groundcoverDistance = (Constants::CellSizeInUnits * Settings::Manager::getInt("distance", "Groundcover") - 1024) * 0.93; + globalDefines["groundcoverFadeStart"] = std::to_string(groundcoverDistance * Settings::Manager::getFloat("fade start", "Groundcover")); + globalDefines["groundcoverFadeEnd"] = std::to_string(groundcoverDistance); + // It is unnecessary to stop/start the viewer as no frames are being rendered yet. mResourceSystem->getSceneManager()->getShaderManager().setGlobalDefines(globalDefines); @@ -269,7 +274,8 @@ namespace MWRender const bool useTerrainNormalMaps = Settings::Manager::getBool("auto use terrain normal maps", "Shaders"); const bool useTerrainSpecularMaps = Settings::Manager::getBool("auto use terrain specular maps", "Shaders"); - mTerrainStorage = new TerrainStorage(mResourceSystem, normalMapPattern, heightMapPattern, useTerrainNormalMaps, specularMapPattern, useTerrainSpecularMaps); + mTerrainStorage.reset(new TerrainStorage(mResourceSystem, normalMapPattern, heightMapPattern, useTerrainNormalMaps, specularMapPattern, useTerrainSpecularMaps)); + const float lodFactor = Settings::Manager::getFloat("lod factor", "Terrain"); if (Settings::Manager::getBool("distant terrain", "Terrain")) { @@ -277,12 +283,11 @@ namespace MWRender int compMapPower = Settings::Manager::getInt("composite map level", "Terrain"); compMapPower = std::max(-3, compMapPower); float compMapLevel = pow(2, compMapPower); - const float lodFactor = Settings::Manager::getFloat("lod factor", "Terrain"); const int vertexLodMod = Settings::Manager::getInt("vertex lod mod", "Terrain"); float maxCompGeometrySize = Settings::Manager::getFloat("max composite geometry size", "Terrain"); maxCompGeometrySize = std::max(maxCompGeometrySize, 1.f); mTerrain.reset(new Terrain::QuadTreeWorld( - sceneRoot, mRootNode, mResourceSystem, mTerrainStorage, Mask_Terrain, Mask_PreCompile, Mask_Debug, + sceneRoot, mRootNode, mResourceSystem, mTerrainStorage.get(), Mask_Terrain, Mask_PreCompile, Mask_Debug, compMapResolution, compMapLevel, lodFactor, vertexLodMod, maxCompGeometrySize)); if (Settings::Manager::getBool("object paging", "Terrain")) { @@ -292,11 +297,43 @@ namespace MWRender } } else - mTerrain.reset(new Terrain::TerrainGrid(sceneRoot, mRootNode, mResourceSystem, mTerrainStorage, Mask_Terrain, Mask_PreCompile, Mask_Debug)); + mTerrain.reset(new Terrain::TerrainGrid(sceneRoot, mRootNode, mResourceSystem, mTerrainStorage.get(), Mask_Terrain, Mask_PreCompile, Mask_Debug)); mTerrain->setTargetFrameRate(Settings::Manager::getFloat("target framerate", "Cells")); mTerrain->setWorkQueue(mWorkQueue.get()); + if (Settings::Manager::getBool("enabled", "Groundcover")) + { + osg::ref_ptr groundcoverRoot = new osg::Group; + groundcoverRoot->setNodeMask(Mask_Groundcover); + groundcoverRoot->setName("Groundcover Root"); + sceneRoot->addChild(groundcoverRoot); + + // Force a unified alpha handling instead of data from meshes + osg::ref_ptr alpha = new osg::AlphaFunc(osg::AlphaFunc::GEQUAL, 128.f/255.f); + groundcoverRoot->getOrCreateStateSet()->setAttributeAndModes(alpha.get(), osg::StateAttribute::ON); + + mGroundcoverUpdater = new GroundcoverUpdater; + groundcoverRoot->addUpdateCallback(mGroundcoverUpdater); + + float chunkSize = Settings::Manager::getFloat("min chunk size", "Groundcover"); + if (chunkSize >= 1.0f) + chunkSize = 1.0f; + else if (chunkSize >= 0.5f) + chunkSize = 0.5f; + else if (chunkSize >= 0.25f) + chunkSize = 0.25f; + else if (chunkSize != 0.125f) + chunkSize = 0.125f; + + float density = Settings::Manager::getFloat("density", "Groundcover"); + density = std::clamp(density, 0.f, 1.f); + + mGroundcoverWorld.reset(new Terrain::QuadTreeWorld(groundcoverRoot, mTerrainStorage.get(), Mask_Groundcover, lodFactor, chunkSize)); + mGroundcover.reset(new Groundcover(mResourceSystem->getSceneManager(), density)); + static_cast(mGroundcoverWorld.get())->addChunkManager(mGroundcover.get()); + mResourceSystem->addResourceManager(mGroundcover.get()); + } // water goes after terrain for correct waterculling order mWater.reset(new Water(sceneRoot->getParent(0), sceneRoot, mResourceSystem, mViewer->getIncrementalCompileOperation(), resourcePath)); @@ -508,7 +545,11 @@ namespace MWRender mWater->changeCell(store); if (store->getCell()->isExterior()) + { mTerrain->loadCell(store->getCell()->getGridX(), store->getCell()->getGridY()); + if (mGroundcoverWorld) + mGroundcoverWorld->loadCell(store->getCell()->getGridX(), store->getCell()->getGridY()); + } } void RenderingManager::removeCell(const MWWorld::CellStore *store) { @@ -517,7 +558,11 @@ namespace MWRender mObjects->removeCell(store); if (store->getCell()->isExterior()) + { mTerrain->unloadCell(store->getCell()->getGridX(), store->getCell()->getGridY()); + if (mGroundcoverWorld) + mGroundcoverWorld->unloadCell(store->getCell()->getGridX(), store->getCell()->getGridY()); + } mWater->removeCell(store); } @@ -527,6 +572,8 @@ namespace MWRender if (!enable) mWater->setCullCallback(nullptr); mTerrain->enable(enable); + if (mGroundcoverWorld) + mGroundcoverWorld->enable(enable); } void RenderingManager::setSkyEnabled(bool enabled) @@ -612,6 +659,16 @@ namespace MWRender mEffectManager->update(dt); mSky->update(dt); mWater->update(dt); + + if (mGroundcoverUpdater) + { + const MWWorld::Ptr& player = mPlayerAnimation->getPtr(); + osg::Vec3f playerPos(player.getRefData().getPosition().asVec3()); + + float windSpeed = mSky->getBaseWindSpeed(); + mGroundcoverUpdater->setWindSpeed(windSpeed); + mGroundcoverUpdater->setPlayerPos(playerPos); + } } updateNavMesh(); @@ -805,7 +862,7 @@ namespace MWRender mIntersectionVisitor->setIntersector(intersector); int mask = ~0; - mask &= ~(Mask_RenderToTexture|Mask_Sky|Mask_Debug|Mask_Effect|Mask_Water|Mask_SimpleWater); + mask &= ~(Mask_RenderToTexture|Mask_Sky|Mask_Debug|Mask_Effect|Mask_Water|Mask_SimpleWater|Mask_Groundcover); if (ignorePlayer) mask &= ~(Mask_Player); if (ignoreActors) @@ -964,6 +1021,12 @@ namespace MWRender fov = std::min(mFieldOfView, 140.f); float distanceMult = std::cos(osg::DegreesToRadians(fov)/2.f); mTerrain->setViewDistance(mViewDistance * (distanceMult ? 1.f/distanceMult : 1.f)); + + if (mGroundcoverWorld) + { + int groundcoverDistance = Constants::CellSizeInUnits * Settings::Manager::getInt("distance", "Groundcover"); + mGroundcoverWorld->setViewDistance(groundcoverDistance * (distanceMult ? 1.f/distanceMult : 1.f)); + } } void RenderingManager::updateTextureFiltering() @@ -1158,6 +1221,8 @@ namespace MWRender void RenderingManager::setActiveGrid(const osg::Vec4i &grid) { mTerrain->setActiveGrid(grid); + if (mGroundcoverWorld) + mGroundcoverWorld->setActiveGrid(grid); } bool RenderingManager::pagingEnableObject(int type, const MWWorld::ConstPtr& ptr, bool enabled) { diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index 39d1a0194..a7afa2fa0 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -70,7 +70,7 @@ namespace DetourNavigator namespace MWRender { - + class GroundcoverUpdater; class StateUpdater; class EffectManager; @@ -88,6 +88,7 @@ namespace MWRender class ActorsPaths; class RecastMesh; class ObjectPaging; + class Groundcover; class RenderingManager : public MWRender::RenderingInterface { @@ -261,6 +262,8 @@ namespace MWRender osg::ref_ptr mSceneRoot; Resource::ResourceSystem* mResourceSystem; + osg::ref_ptr mGroundcoverUpdater; + osg::ref_ptr mWorkQueue; osg::ref_ptr mUnrefQueue; @@ -275,8 +278,10 @@ namespace MWRender std::unique_ptr mObjects; std::unique_ptr mWater; std::unique_ptr mTerrain; - TerrainStorage* mTerrainStorage; + std::unique_ptr mGroundcoverWorld; + std::unique_ptr mTerrainStorage; std::unique_ptr mObjectPaging; + std::unique_ptr mGroundcover; std::unique_ptr mSky; std::unique_ptr mFog; std::unique_ptr mScreenshotManager; diff --git a/apps/openmw/mwrender/vismask.hpp b/apps/openmw/mwrender/vismask.hpp index f9f9dc74c..bc3d3f192 100644 --- a/apps/openmw/mwrender/vismask.hpp +++ b/apps/openmw/mwrender/vismask.hpp @@ -53,7 +53,9 @@ namespace MWRender Mask_PreCompile = (1<<18), // Set on a camera's cull mask to enable the LightManager - Mask_Lighting = (1<<19) + Mask_Lighting = (1<<19), + + Mask_Groundcover = (1<<20), }; } diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index e786ce937..1cc5a3cb7 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -244,7 +244,7 @@ public: setCullCallback(new InheritViewPointCallback); setComputeNearFarMode(osg::CullSettings::DO_NOT_COMPUTE_NEAR_FAR); - setCullMask(Mask_Effect|Mask_Scene|Mask_Object|Mask_Static|Mask_Terrain|Mask_Actor|Mask_ParticleSystem|Mask_Sky|Mask_Sun|Mask_Player|Mask_Lighting); + setCullMask(Mask_Effect|Mask_Scene|Mask_Object|Mask_Static|Mask_Terrain|Mask_Actor|Mask_ParticleSystem|Mask_Sky|Mask_Sun|Mask_Player|Mask_Lighting|Mask_Groundcover); setNodeMask(Mask_RenderToTexture); setViewport(0, 0, rttSize, rttSize); @@ -372,12 +372,13 @@ public: void setInterior(bool isInterior) { int reflectionDetail = Settings::Manager::getInt("reflection detail", "Water"); - reflectionDetail = std::min(4, std::max(isInterior ? 2 : 0, reflectionDetail)); + reflectionDetail = std::min(5, std::max(isInterior ? 2 : 0, reflectionDetail)); 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; setCullMask(Mask_Scene|Mask_Sky|Mask_Lighting|extraMask); } diff --git a/apps/openmw/mwworld/cellpreloader.cpp b/apps/openmw/mwworld/cellpreloader.cpp index 31af5b24b..937491f62 100644 --- a/apps/openmw/mwworld/cellpreloader.cpp +++ b/apps/openmw/mwworld/cellpreloader.cpp @@ -36,6 +36,13 @@ namespace MWWorld virtual bool operator()(const MWWorld::Ptr& ptr) { + if (ptr.getTypeName()==typeid (ESM::Static).name()) + { + const MWWorld::LiveCellRef *ref = ptr.get(); + if (ref->mBase->mIsGroundcover) + return true; + } + ptr.getClass().getModelsToPreload(ptr, mOut); return true; diff --git a/apps/openmw/mwworld/cellreflist.hpp b/apps/openmw/mwworld/cellreflist.hpp index 30be4a661..69161c840 100644 --- a/apps/openmw/mwworld/cellreflist.hpp +++ b/apps/openmw/mwworld/cellreflist.hpp @@ -24,6 +24,8 @@ namespace MWWorld /// all methods are known. void load (ESM::CellRef &ref, bool deleted, const MWWorld::ESMStore &esmStore); + inline bool ignoreInstance (const X* ptr); + LiveRef &insert (const LiveRef &item) { mList.push_back(item); diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index b48fe74a6..d8e2eb65f 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -169,6 +169,17 @@ namespace namespace MWWorld { + template + bool CellRefList::ignoreInstance (const X* ptr) + { + return false; + } + + template <> + bool CellRefList::ignoreInstance (const ESM::Static* ptr) + { + return ptr->mIsGroundcover; + } template void CellRefList::load(ESM::CellRef &ref, bool deleted, const MWWorld::ESMStore &esmStore) @@ -177,6 +188,8 @@ namespace MWWorld if (const X *ptr = store.search (ref.mRefID)) { + if (ignoreInstance(ptr)) return; + typename std::list::iterator iter = std::find(mList.begin(), mList.end(), ref.mRefNum); diff --git a/apps/openmw/mwworld/contentloader.hpp b/apps/openmw/mwworld/contentloader.hpp index b529ae9db..b559df083 100644 --- a/apps/openmw/mwworld/contentloader.hpp +++ b/apps/openmw/mwworld/contentloader.hpp @@ -21,7 +21,7 @@ struct ContentLoader { } - virtual void load(const boost::filesystem::path& filepath, int& index) + virtual void load(const boost::filesystem::path& filepath, int& index, bool isGroundcover) { Log(Debug::Info) << "Loading content file " << filepath.string(); mListener.setLabel(MyGUI::TextIterator::toTagsString(filepath.string())); diff --git a/apps/openmw/mwworld/esmloader.cpp b/apps/openmw/mwworld/esmloader.cpp index b12d646e7..c96618215 100644 --- a/apps/openmw/mwworld/esmloader.cpp +++ b/apps/openmw/mwworld/esmloader.cpp @@ -15,17 +15,17 @@ EsmLoader::EsmLoader(MWWorld::ESMStore& store, std::vector& read { } -void EsmLoader::load(const boost::filesystem::path& filepath, int& index) +void EsmLoader::load(const boost::filesystem::path& filepath, int& index, bool isGroundcover) { - ContentLoader::load(filepath.filename(), index); + ContentLoader::load(filepath.filename(), index, isGroundcover); - ESM::ESMReader lEsm; - lEsm.setEncoder(mEncoder); - lEsm.setIndex(index); - lEsm.setGlobalReaderList(&mEsm); - lEsm.open(filepath.string()); - mEsm[index] = lEsm; - mStore.load(mEsm[index], &mListener); + ESM::ESMReader lEsm; + lEsm.setEncoder(mEncoder); + lEsm.setIndex(index); + lEsm.setGlobalReaderList(&mEsm); + lEsm.open(filepath.string(), isGroundcover); + mEsm[index] = lEsm; + mStore.load(mEsm[index], &mListener); } } /* namespace MWWorld */ diff --git a/apps/openmw/mwworld/esmloader.hpp b/apps/openmw/mwworld/esmloader.hpp index 506105beb..cc4c15a15 100644 --- a/apps/openmw/mwworld/esmloader.hpp +++ b/apps/openmw/mwworld/esmloader.hpp @@ -25,7 +25,7 @@ struct EsmLoader : public ContentLoader EsmLoader(MWWorld::ESMStore& store, std::vector& readers, ToUTF8::Utf8Encoder* encoder, Loading::Listener& listener); - void load(const boost::filesystem::path& filepath, int& index) override; + void load(const boost::filesystem::path& filepath, int& index, bool isGroundcover) override; private: std::vector& mEsm; diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index 90bc80b48..2731d7eb1 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -190,6 +190,15 @@ void ESMStore::setUp(bool validateRecords) { validate(); countRecords(); + + if (mGroundcovers.empty()) + { + for (const ESM::Static& record : mStatics) + { + if (record.mIsGroundcover) + mGroundcovers[record.mId] = record.mModel; + } + } } } diff --git a/apps/openmw/mwworld/esmstore.hpp b/apps/openmw/mwworld/esmstore.hpp index d69c56d8c..18bb95580 100644 --- a/apps/openmw/mwworld/esmstore.hpp +++ b/apps/openmw/mwworld/esmstore.hpp @@ -75,6 +75,7 @@ namespace MWWorld // maps the id name to the record type. std::map mIds; std::map mStaticIds; + std::map mGroundcovers; std::map mRefCount; @@ -121,6 +122,22 @@ namespace MWWorld return it->second; } + bool isGroundcover(const std::string &id, std::string &model) const + { + std::map::const_iterator it = mGroundcovers.find(id); + if (it == mGroundcovers.end()) { + return false; + } + model = it->second; + return true; + } + + bool isGroundcover(const std::string &id) const + { + std::map::const_iterator it = mGroundcovers.find(id); + return (it != mGroundcovers.end()); + } + ESMStore() : mDynamicCount(0) { diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index af9a5e0bb..93d1a799c 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -103,12 +103,12 @@ namespace MWWorld return mLoaders.insert(std::make_pair(extension, loader)).second; } - void load(const boost::filesystem::path& filepath, int& index) override + void load(const boost::filesystem::path& filepath, int& index, bool isGroundcover) override { LoadersContainer::iterator it(mLoaders.find(Misc::StringUtils::lowerCase(filepath.extension().string()))); if (it != mLoaders.end()) { - it->second->load(filepath, index); + it->second->load(filepath, index, isGroundcover); } else { @@ -140,6 +140,7 @@ namespace MWWorld Resource::ResourceSystem* resourceSystem, SceneUtil::WorkQueue* workQueue, const Files::Collections& fileCollections, const std::vector& contentFiles, + const std::vector& groundcoverFiles, ToUTF8::Utf8Encoder* encoder, int activationDistanceOverride, const std::string& startCell, const std::string& startupScript, const std::string& resourcePath, const std::string& userDataPath) @@ -152,7 +153,7 @@ namespace MWWorld mLevitationEnabled(true), mGoToJail(false), mDaysInPrison(0), mPlayerTraveling(false), mPlayerInJail(false), mSpellPreloadTimer(0.f) { - mEsm.resize(contentFiles.size()); + mEsm.resize(contentFiles.size() + groundcoverFiles.size()); Loading::Listener* listener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); listener->loadingOn(); @@ -165,7 +166,7 @@ namespace MWWorld gameContentLoader.addLoader(".omwaddon", &esmLoader); gameContentLoader.addLoader(".project", &esmLoader); - loadContentFiles(fileCollections, contentFiles, gameContentLoader); + loadContentFiles(fileCollections, contentFiles, groundcoverFiles, gameContentLoader); listener->loadingOff(); @@ -2941,7 +2942,7 @@ namespace MWWorld } void World::loadContentFiles(const Files::Collections& fileCollections, - const std::vector& content, ContentLoader& contentLoader) + const std::vector& content, const std::vector& groundcover, ContentLoader& contentLoader) { int idx = 0; for (const std::string &file : content) @@ -2950,7 +2951,7 @@ namespace MWWorld const Files::MultiDirCollection& col = fileCollections.getCollection(filename.extension().string()); if (col.doesExist(file)) { - contentLoader.load(col.getPath(file), idx); + contentLoader.load(col.getPath(file), idx, false); } else { @@ -2959,6 +2960,22 @@ namespace MWWorld } idx++; } + + for (const std::string &file : groundcover) + { + boost::filesystem::path filename(file); + const Files::MultiDirCollection& col = fileCollections.getCollection(filename.extension().string()); + if (col.doesExist(file)) + { + contentLoader.load(col.getPath(file), idx, true); + } + else + { + std::string message = "Failed loading " + file + ": the groundcover file does not exist"; + throw std::runtime_error(message); + } + idx++; + } } bool World::startSpellCast(const Ptr &actor) diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 79c8a4980..29d29a160 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -177,7 +177,7 @@ namespace MWWorld * @param contentLoader - */ void loadContentFiles(const Files::Collections& fileCollections, - const std::vector& content, ContentLoader& contentLoader); + const std::vector& content, const std::vector& groundcover, ContentLoader& contentLoader); float feetToGameUnits(float feet); float getActivationDistancePlusTelekinesis(); @@ -196,6 +196,7 @@ namespace MWWorld Resource::ResourceSystem* resourceSystem, SceneUtil::WorkQueue* workQueue, const Files::Collections& fileCollections, const std::vector& contentFiles, + const std::vector& groundcoverFiles, ToUTF8::Utf8Encoder* encoder, int activationDistanceOverride, const std::string& startCell, const std::string& startupScript, const std::string& resourcePath, const std::string& userDataPath); diff --git a/components/config/gamesettings.cpp b/components/config/gamesettings.cpp index d7fe7da94..8717a6839 100644 --- a/components/config/gamesettings.cpp +++ b/components/config/gamesettings.cpp @@ -100,6 +100,7 @@ bool Config::GameSettings::readFile(QTextStream &stream, QMultiMap node, const std::string& shaderPrefix, bool translucentFramebuffer) + void SceneManager::recreateShaders(osg::ref_ptr node, const std::string& shaderPrefix, bool translucentFramebuffer, bool forceShadersForNode) { osg::ref_ptr shaderVisitor(createShaderVisitor(shaderPrefix, translucentFramebuffer)); shaderVisitor->setAllowedToModifyStateSets(false); + if (forceShadersForNode) + shaderVisitor->setForceShaders(true); node->accept(*shaderVisitor); } @@ -512,7 +514,7 @@ namespace Resource SetFilterSettingsControllerVisitor setFilterSettingsControllerVisitor(mMinFilter, mMagFilter, mMaxAnisotropy); loaded->accept(setFilterSettingsControllerVisitor); - osg::ref_ptr shaderVisitor (createShaderVisitor()); + osg::ref_ptr shaderVisitor (createShaderVisitor("objects")); loaded->accept(*shaderVisitor); // share state diff --git a/components/resource/scenemanager.hpp b/components/resource/scenemanager.hpp index a815a324f..9da6bc500 100644 --- a/components/resource/scenemanager.hpp +++ b/components/resource/scenemanager.hpp @@ -76,7 +76,7 @@ namespace Resource Shader::ShaderManager& getShaderManager(); /// Re-create shaders for this node, need to call this if texture stages or vertex color mode have changed. - void recreateShaders(osg::ref_ptr node, const std::string& shaderPrefix = "objects", bool translucentFramebuffer = false); + void recreateShaders(osg::ref_ptr node, const std::string& shaderPrefix = "objects", bool translucentFramebuffer = false, bool forceShadersForNode = false); /// @see ShaderVisitor::setForceShaders void setForceShaders(bool force); diff --git a/components/resource/stats.cpp b/components/resource/stats.cpp index 942bd92d8..05f97b3ed 100644 --- a/components/resource/stats.cpp +++ b/components/resource/stats.cpp @@ -281,6 +281,7 @@ void StatsHandler::setUpScene(osgViewer::ViewerBase *viewer) "FrameNumber", "", "Compiling", + "UnrefQueue", "WorkQueue", "WorkThread", "", @@ -294,14 +295,13 @@ void StatsHandler::setUpScene(osgViewer::ViewerBase *viewer) "Nif", "Keyframe", "", + "Groundcover Chunk", "Object Chunk", "Terrain Chunk", "Terrain Texture", "Land", "Composite", "", - "UnrefQueue", - "", "NavMesh UpdateJobs", "NavMesh CacheSize", "NavMesh UsedTiles", diff --git a/components/shader/shadermanager.cpp b/components/shader/shadermanager.cpp index 788a8720b..4f887e659 100644 --- a/components/shader/shadermanager.cpp +++ b/components/shader/shadermanager.cpp @@ -342,6 +342,8 @@ namespace Shader osg::ref_ptr program (new osg::Program); program->addShader(vertexShader); program->addShader(fragmentShader); + program->addBindAttribLocation("aOffset", 6); + program->addBindAttribLocation("aRotation", 7); found = mPrograms.insert(std::make_pair(std::make_pair(vertexShader, fragmentShader), program)).first; } return found->second; diff --git a/components/terrain/chunkmanager.cpp b/components/terrain/chunkmanager.cpp index 50724628d..a744471de 100644 --- a/components/terrain/chunkmanager.cpp +++ b/components/terrain/chunkmanager.cpp @@ -40,7 +40,7 @@ ChunkManager::ChunkManager(Storage *storage, Resource::SceneManager *sceneMgr, T mMultiPassRoot->setAttributeAndModes(material, osg::StateAttribute::ON); } -osg::ref_ptr ChunkManager::getChunk(float size, const osg::Vec2f ¢er, unsigned char lod, unsigned int lodFlags, bool far, const osg::Vec3f& viewPoint, bool compile) +osg::ref_ptr ChunkManager::getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile) { ChunkId id = std::make_tuple(center, lod, lodFlags); osg::ref_ptr obj = mCache->getRefFromObjectCache(id); diff --git a/components/terrain/chunkmanager.hpp b/components/terrain/chunkmanager.hpp index 118df698f..9b7dbf3ee 100644 --- a/components/terrain/chunkmanager.hpp +++ b/components/terrain/chunkmanager.hpp @@ -35,7 +35,7 @@ namespace Terrain public: ChunkManager(Storage* storage, Resource::SceneManager* sceneMgr, TextureManager* textureManager, CompositeMapRenderer* renderer); - osg::ref_ptr getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool far, const osg::Vec3f& viewPoint, bool compile) override; + osg::ref_ptr getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile) override; void setCompositeMapSize(unsigned int size) { mCompositeMapSize = size; } void setCompositeMapLevel(float level) { mCompositeMapLevel = level; } diff --git a/components/terrain/quadtreeworld.cpp b/components/terrain/quadtreeworld.cpp index 57c09000c..7f184c70e 100644 --- a/components/terrain/quadtreeworld.cpp +++ b/components/terrain/quadtreeworld.cpp @@ -90,8 +90,6 @@ private: osg::Vec4i mActiveGrid; }; -const float MIN_SIZE = 1/8.f; - class RootNode : public QuadTreeNode { public: @@ -250,6 +248,7 @@ QuadTreeWorld::QuadTreeWorld(osg::Group *parent, osg::Group *compileRoot, Resour , mLodFactor(lodFactor) , mVertexLodMod(vertexLodMod) , mViewDistance(std::numeric_limits::max()) + , mMinSize(1/8.f) { mChunkManager->setCompositeMapSize(compMapResolution); mChunkManager->setCompositeMapLevel(compMapLevel); @@ -257,6 +256,17 @@ QuadTreeWorld::QuadTreeWorld(osg::Group *parent, osg::Group *compileRoot, Resour mChunkManagers.push_back(mChunkManager.get()); } +QuadTreeWorld::QuadTreeWorld(osg::Group *parent, Storage *storage, int nodeMask, float lodFactor, float chunkSize) + : TerrainGrid(parent, storage, nodeMask) + , mViewDataMap(new ViewDataMap) + , mQuadTreeBuilt(false) + , mLodFactor(lodFactor) + , mVertexLodMod(0) + , mViewDistance(std::numeric_limits::max()) + , mMinSize(chunkSize) +{ +} + QuadTreeWorld::~QuadTreeWorld() { } @@ -425,7 +435,7 @@ void QuadTreeWorld::accept(osg::NodeVisitor &nv) if (needsUpdate) { vd->reset(); - DefaultLodCallback lodCallback(mLodFactor, MIN_SIZE, mViewDistance, mActiveGrid); + DefaultLodCallback lodCallback(mLodFactor, mMinSize, mViewDistance, mActiveGrid); mRootNode->traverseNodes(vd, nv.getViewPoint(), &lodCallback); } @@ -438,7 +448,7 @@ void QuadTreeWorld::accept(osg::NodeVisitor &nv) entry.mRenderingNode->accept(nv); } - if (isCullVisitor) + if (mHeightCullCallback && isCullVisitor) updateWaterCullingView(mHeightCullCallback, vd, static_cast(&nv), mStorage->getCellWorldSize(), !isGridEmpty()); vd->markUnchanged(); @@ -457,7 +467,7 @@ void QuadTreeWorld::ensureQuadTreeBuilt() if (mQuadTreeBuilt) return; - QuadTreeBuilder builder(mStorage, MIN_SIZE); + QuadTreeBuilder builder(mStorage, mMinSize); builder.build(); mRootNode = builder.getRootNode(); @@ -491,7 +501,7 @@ void QuadTreeWorld::preload(View *view, const osg::Vec3f &viewPoint, const osg:: ViewData* vd = static_cast(view); vd->setViewPoint(viewPoint); vd->setActiveGrid(grid); - DefaultLodCallback lodCallback(mLodFactor, MIN_SIZE, mViewDistance, grid); + DefaultLodCallback lodCallback(mLodFactor, mMinSize, mViewDistance, grid); mRootNode->traverseNodes(vd, viewPoint, &lodCallback); if (!progressTotal) @@ -515,14 +525,15 @@ bool QuadTreeWorld::storeView(const View* view, double referenceTime) void QuadTreeWorld::reportStats(unsigned int frameNumber, osg::Stats *stats) { - stats->setAttribute(frameNumber, "Composite", mCompositeMapRenderer->getCompileSetSize()); + if (mCompositeMapRenderer) + stats->setAttribute(frameNumber, "Composite", mCompositeMapRenderer->getCompileSetSize()); } void QuadTreeWorld::loadCell(int x, int y) { // fallback behavior only for undefined cells (every other is already handled in quadtree) float dummy; - if (!mStorage->getMinMaxHeights(1, osg::Vec2f(x+0.5, y+0.5), dummy, dummy)) + if (mChunkManager && !mStorage->getMinMaxHeights(1, osg::Vec2f(x+0.5, y+0.5), dummy, dummy)) TerrainGrid::loadCell(x,y); else World::loadCell(x,y); @@ -532,7 +543,7 @@ void QuadTreeWorld::unloadCell(int x, int y) { // fallback behavior only for undefined cells (every other is already handled in quadtree) float dummy; - if (!mStorage->getMinMaxHeights(1, osg::Vec2f(x+0.5, y+0.5), dummy, dummy)) + if (mChunkManager && !mStorage->getMinMaxHeights(1, osg::Vec2f(x+0.5, y+0.5), dummy, dummy)) TerrainGrid::unloadCell(x,y); else World::unloadCell(x,y); diff --git a/components/terrain/quadtreeworld.hpp b/components/terrain/quadtreeworld.hpp index 4c05efe64..aba2dccf3 100644 --- a/components/terrain/quadtreeworld.hpp +++ b/components/terrain/quadtreeworld.hpp @@ -22,6 +22,8 @@ namespace Terrain public: QuadTreeWorld(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem, Storage* storage, int nodeMask, int preCompileMask, int borderMask, int compMapResolution, float comMapLevel, float lodFactor, int vertexLodMod, float maxCompGeometrySize); + QuadTreeWorld(osg::Group *parent, Storage *storage, int nodeMask, float lodFactor, float chunkSize); + ~QuadTreeWorld(); void accept(osg::NodeVisitor& nv); @@ -47,7 +49,7 @@ namespace Terrain { public: virtual ~ChunkManager(){} - virtual osg::ref_ptr getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool far, const osg::Vec3f& viewPoint, bool compile) = 0; + virtual osg::ref_ptr getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile) = 0; virtual unsigned int getNodeMask() { return 0; } }; void addChunkManager(ChunkManager*); @@ -66,6 +68,7 @@ namespace Terrain float mLodFactor; int mVertexLodMod; float mViewDistance; + float mMinSize; }; } diff --git a/components/terrain/terraingrid.cpp b/components/terrain/terraingrid.cpp index 679597971..cf8debc69 100644 --- a/components/terrain/terraingrid.cpp +++ b/components/terrain/terraingrid.cpp @@ -26,6 +26,12 @@ TerrainGrid::TerrainGrid(osg::Group* parent, osg::Group* compileRoot, Resource:: { } +TerrainGrid::TerrainGrid(osg::Group* parent, Storage* storage, int nodeMask) + : Terrain::World(parent, storage, nodeMask) + , mNumSplits(4) +{ +} + TerrainGrid::~TerrainGrid() { while (!mGrid.empty()) @@ -107,6 +113,8 @@ void TerrainGrid::unloadCell(int x, int y) void TerrainGrid::updateWaterCulling() { + if (!mHeightCullCallback) return; + osg::ComputeBoundsVisitor computeBoundsVisitor; mTerrainRoot->accept(computeBoundsVisitor); float lowZ = computeBoundsVisitor.getBoundingBox()._min.z(); diff --git a/components/terrain/terraingrid.hpp b/components/terrain/terraingrid.hpp index f8b0fb259..dc9203466 100644 --- a/components/terrain/terraingrid.hpp +++ b/components/terrain/terraingrid.hpp @@ -15,6 +15,7 @@ namespace Terrain { public: TerrainGrid(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem, Storage* storage, int nodeMask, int preCompileMask=~0, int borderMask=0); + TerrainGrid(osg::Group* parent, Storage* storage, int nodeMask=~0); ~TerrainGrid(); void cacheCell(View* view, int x, int y) override; diff --git a/components/terrain/world.cpp b/components/terrain/world.cpp index 5b4807b38..15ec72973 100644 --- a/components/terrain/world.cpp +++ b/components/terrain/world.cpp @@ -49,17 +49,38 @@ World::World(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSyst mResourceSystem->addResourceManager(mTextureManager.get()); } +World::World(osg::Group* parent, Storage* storage, int nodeMask) + : mStorage(storage) + , mParent(parent) + , mCompositeMapCamera(nullptr) + , mCompositeMapRenderer(nullptr) + , mResourceSystem(nullptr) + , mTextureManager(nullptr) + , mChunkManager(nullptr) + , mCellBorder(nullptr) + , mBorderVisible(false) + , mHeightCullCallback(nullptr) +{ + mTerrainRoot = new osg::Group; + mTerrainRoot->setNodeMask(nodeMask); + + mParent->addChild(mTerrainRoot); +} + World::~World() { - mResourceSystem->removeResourceManager(mChunkManager.get()); - mResourceSystem->removeResourceManager(mTextureManager.get()); + if (mResourceSystem && mChunkManager) + mResourceSystem->removeResourceManager(mChunkManager.get()); + if (mResourceSystem && mTextureManager) + mResourceSystem->removeResourceManager(mTextureManager.get()); mParent->removeChild(mTerrainRoot); - mCompositeMapCamera->removeChild(mCompositeMapRenderer); - mCompositeMapCamera->getParent(0)->removeChild(mCompositeMapCamera); - - delete mStorage; + if (mCompositeMapCamera && mCompositeMapRenderer) + { + mCompositeMapCamera->removeChild(mCompositeMapRenderer); + mCompositeMapCamera->getParent(0)->removeChild(mCompositeMapCamera); + } } void World::setWorkQueue(SceneUtil::WorkQueue* workQueue) @@ -108,16 +129,20 @@ float World::getHeightAt(const osg::Vec3f &worldPos) void World::updateTextureFiltering() { - mTextureManager->updateTextureFiltering(); + if (mTextureManager) + mTextureManager->updateTextureFiltering(); } void World::clearAssociatedCaches() { - mChunkManager->clearCache(); + if (mChunkManager) + mChunkManager->clearCache(); } osg::Callback* World::getHeightCullCallback(float highz, unsigned int mask) { + if (!mHeightCullCallback) return nullptr; + mHeightCullCallback->setHighZ(highz); mHeightCullCallback->setCullMask(mask); return mHeightCullCallback; diff --git a/components/terrain/world.hpp b/components/terrain/world.hpp index d94125100..a4be57e8e 100644 --- a/components/terrain/world.hpp +++ b/components/terrain/world.hpp @@ -106,6 +106,7 @@ namespace Terrain /// @param nodeMask mask for the terrain root /// @param preCompileMask mask for pre compiling textures World(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem, Storage* storage, int nodeMask, int preCompileMask, int borderMask); + World(osg::Group* parent, Storage* storage, int nodeMask); virtual ~World(); /// Set a WorkQueue to delete objects in the background thread. diff --git a/docs/source/reference/modding/extended.rst b/docs/source/reference/modding/extended.rst index 9e8db49fd..98b3e7f00 100644 --- a/docs/source/reference/modding/extended.rst +++ b/docs/source/reference/modding/extended.rst @@ -223,10 +223,10 @@ For example, to attach a custom weapon bone, you'll need to follow this NIF reco :: -NiNode "root" - NiNode "Bip01 L Hand" - NiNode "Weapon Bone Left" - NiStringExtraData "BONE" + NiNode "root" + NiNode "Bip01 L Hand" + NiNode "Weapon Bone Left" + NiStringExtraData "BONE" OpenMW will detect ``Weapon Bone Left`` node and attach it to ``Bip01 L Hand`` bone of the target skeleton. @@ -276,6 +276,54 @@ Also it is possible to add a "Bip01 Arrow" bone to actor skeletons. In this case Such approach allows to implement better shooting animations (for example, beast races have tail, so quivers should be attached under different angle and default arrow fetching animation does not look good). +Groundcover support +------------------- + +Groundcover objects is a special kind of objects (e.g. grass), which can be used to improve visual fidelity. +They use these assumptions: + +1. Each object is independent, so part of objects can be removed from scene without causing graphical artifacts. + +2. Groundover should not have collisions. + +3. They are not important for some parts of game scene (e.g. local map). + +4. They can not be moved or disabled on the fly. + +5. They can not be interacted with. + +As result, such objects can be treated in the separate way: + +1. It is possible to tweak groundcover objects density. + +2. It is possible to safely merge such objects even near player. + +3. Such objects can be animated (to simulate wind, for example). + +4. Some parts of processing can be skipped. + +For example, we do not need to have collision or animation objects for groundcover, +do not need to render groundcover on the map, do not need to render it for the whole visible area (which can be very large with Distant Terrain). It allows to increase performance a lot. + +General advices to create assets for this feature: +1. Alpha properties from Nif files are not used, a unified alpha settings are used (alpha testing, "greater of equal" function, 128/255 threshold). +2. Use a single NiTriShape in groundocver mesh, or at least use same properties (texture, alpha, material, etc), so OpenMW can merge them on the fly. Otherwise animations may not work properly. +3. Smooth fading does not work for meshes, which have textures without alpha (e.g. rock). + +Groundcover mods can be registered in the openmw.cfg via "groundcover" entries instead of "content" ones: + +:: + + groundcover=my_grass_mod.esp + +Every static from such mod is treated as a groundcover object. +Also groundcover detection should be enabled via settings.cfg: + +:: + + [Groundcover] + enabled = true + .. _`Graphic Herbalism`: https://www.nexusmods.com/morrowind/mods/46599 .. _`OpenMW Containers Animated`: https://www.nexusmods.com/morrowind/mods/46232 .. _`Glow in the Dahrk`: https://www.nexusmods.com/morrowind/mods/45886 diff --git a/docs/source/reference/modding/settings/groundcover.rst b/docs/source/reference/modding/settings/groundcover.rst new file mode 100644 index 000000000..9b00d85a1 --- /dev/null +++ b/docs/source/reference/modding/settings/groundcover.rst @@ -0,0 +1,68 @@ +Groundcover Settings +#################### + +enabled +------- + +:Type: boolean +:Range: True/False +:Default: False + +Allows the engine to use groundcover. +Groundcover objects are static objects which come from ESP files, registered via +"groundcover" entries from openmw.cfg rather than "content" ones. +We assume that groundcover objects have no collisions, can not be moved or interacted with, +so we can merge them to pages and animate them indifferently from distance from player. + +This setting can only be configured by editing the settings configuration file. + +fade start +---------- + +:Type: floating point +:Range: 0.0 to 1.0 +:Default: 0.85 + +Determines on which distance from player groundcover fading starts. +Does not work for meshes which textures do not have transparency (e.g. rocks). + +This setting can only be configured by editing the settings configuration file. + +density +------- + +:Type: floating point +:Range: 0.0 (0%) to 1.0 (100%) +:Default: 1.0 + +Determines how many groundcover instances from content files +are used in the game. Can affect performance a lot. + +This setting can only be configured by editing the settings configuration file. + +distance +-------- + +:Type: integer +:Range: > 0 +:Default: 1 + +Determines on which distance in cells grass pages are rendered. +Default 1 value means 3x3 cells area (active grid). +May affect performance a lot. + +This setting can only be configured by editing the settings configuration file. + +min chunk size +-------------- + +:Type: floating point +:Range: 0.125, 0.25, 0.5, 1.0 +:Default: 0.5 + +Determines a minimum size of groundcover chunks in cells. For example, with 0.5 value +chunks near player will have size 4096x4096 game units. Larger chunks reduce CPU usage +(Draw and Cull bars), but can increase GPU usage (GPU bar) since culling becomes less efficient. +Smaller values do an opposite. + +This setting can only be configured by editing the settings configuration file. diff --git a/docs/source/reference/modding/settings/index.rst b/docs/source/reference/modding/settings/index.rst index 586a99dbb..220ee88c4 100644 --- a/docs/source/reference/modding/settings/index.rst +++ b/docs/source/reference/modding/settings/index.rst @@ -42,6 +42,7 @@ The ranges included with each setting are the physically possible ranges, not re camera cells fog + groundcover map GUI HUD diff --git a/docs/source/reference/modding/settings/shaders.rst b/docs/source/reference/modding/settings/shaders.rst index e23cc3d54..ed43b19a2 100644 --- a/docs/source/reference/modding/settings/shaders.rst +++ b/docs/source/reference/modding/settings/shaders.rst @@ -26,6 +26,7 @@ Has no effect if the 'force shaders' option is false. Enabling per-pixel lighting results in visual differences to the original MW engine. It is not recommended to enable this option when using vanilla Morrowind files, because certain lights in Morrowind rely on vertex lighting to look as intended. +Note that groundcover shaders ignore this setting. clamp lighting -------------- diff --git a/docs/source/reference/modding/settings/water.rst b/docs/source/reference/modding/settings/water.rst index b79daacb7..b3a6f8c1f 100644 --- a/docs/source/reference/modding/settings/water.rst +++ b/docs/source/reference/modding/settings/water.rst @@ -62,7 +62,7 @@ reflection detail ----------------- :Type: integer -:Range: 0, 1, 2, 3, 4 +:Range: 0, 1, 2, 3, 4, 5 :Default: 2 Controls what kinds of things are rendered in water reflections. @@ -72,6 +72,7 @@ Controls what kinds of things are rendered in water reflections. 2: statics, activators, and doors are also reflected 3: items, containers, and particles are also reflected 4: actors are also reflected +5: groundcover objects are also reflected In interiors the lowest level is 2. This setting can be changed ingame with the "Reflection shader detail" dropdown under the Water tab of the Video panel in the Options menu. diff --git a/files/mygui/openmw_settings_window.layout b/files/mygui/openmw_settings_window.layout index 14ab7c9de..b57d362ed 100644 --- a/files/mygui/openmw_settings_window.layout +++ b/files/mygui/openmw_settings_window.layout @@ -449,6 +449,7 @@ + diff --git a/files/settings-default.cfg b/files/settings-default.cfg index d0793fc81..f6bfff7b1 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -950,6 +950,25 @@ lineofsight keep inactive cache = 0 defer aabb update = true [Models] + # Attempt to load any valid NIF file regardless of its version and track the progress. # Loading arbitrary meshes is not advised and may cause instability. load unsupported nif files = false + +[Groundcover] + +# enable separate groundcover handling +enabled = false + +# configure groundcover fade out threshold +fade start = 0.85 + +# A groundcover density (0.0 <= value <= 1.0) +# 1.0 means 100% density +density = 1.0 + +# A maximum distance in cells on which groundcover is rendered. +distance = 1 + +# A minimum size of groundcover chunk in cells (0.125, 0.25, 0.5, 1.0) +min chunk size = 0.5 diff --git a/files/shaders/CMakeLists.txt b/files/shaders/CMakeLists.txt index 47670e7a0..a4e898e4b 100644 --- a/files/shaders/CMakeLists.txt +++ b/files/shaders/CMakeLists.txt @@ -7,6 +7,8 @@ set(SDIR ${CMAKE_CURRENT_SOURCE_DIR}) set(DDIRRELATIVE resources/shaders) set(SHADER_FILES + groundcover_vertex.glsl + groundcover_fragment.glsl water_vertex.glsl water_fragment.glsl water_nm.png diff --git a/files/shaders/groundcover_fragment.glsl b/files/shaders/groundcover_fragment.glsl new file mode 100644 index 000000000..9c680853a --- /dev/null +++ b/files/shaders/groundcover_fragment.glsl @@ -0,0 +1,93 @@ +#version 120 + +#define GROUNDCOVER + +#if @diffuseMap +uniform sampler2D diffuseMap; +varying vec2 diffuseMapUV; +#endif + +#if @normalMap +uniform sampler2D normalMap; +varying vec2 normalMapUV; +varying vec4 passTangent; +#endif + +// Other shaders respect forcePPL, but legacy groundcover mods were designed to work with vertex lighting. +// They may do not look as intended with per-pixel lighting, so ignore this setting for now. +#define PER_PIXEL_LIGHTING @normalMap + +varying float euclideanDepth; +varying float linearDepth; + +#if PER_PIXEL_LIGHTING +varying vec3 passViewPos; +varying vec3 passNormal; +#else +centroid varying vec3 passLighting; +centroid varying vec3 shadowDiffuseLighting; +#endif + +#include "shadows_fragment.glsl" +#include "lighting.glsl" + +float calc_coverage(float a, float alpha_ref, float falloff_rate) +{ + return clamp(falloff_rate * (a - alpha_ref) + alpha_ref, 0.0, 1.0); +} + +void main() +{ +#if @normalMap + vec4 normalTex = texture2D(normalMap, normalMapUV); + + vec3 normalizedNormal = normalize(passNormal); + vec3 normalizedTangent = normalize(passTangent.xyz); + vec3 binormal = cross(normalizedTangent, normalizedNormal) * passTangent.w; + mat3 tbnTranspose = mat3(normalizedTangent, binormal, normalizedNormal); + + vec3 viewNormal = gl_NormalMatrix * normalize(tbnTranspose * (normalTex.xyz * 2.0 - 1.0)); +#endif + +#if (!@normalMap && @forcePPL && false) + vec3 viewNormal = gl_NormalMatrix * normalize(passNormal); +#endif + +#if @diffuseMap + gl_FragData[0] = texture2D(diffuseMap, diffuseMapUV); +#else + gl_FragData[0] = vec4(1.0); +#endif + + gl_FragData[0].a = calc_coverage(gl_FragData[0].a, 128.0/255.0, 4.0); + + float shadowing = unshadowedLightRatio(linearDepth); + if (euclideanDepth > @groundcoverFadeStart) + gl_FragData[0].a *= 1.0-smoothstep(@groundcoverFadeStart, @groundcoverFadeEnd, euclideanDepth); + + vec3 lighting; +#if !PER_PIXEL_LIGHTING + lighting = passLighting + shadowDiffuseLighting * shadowing; +#else + vec3 diffuseLight, ambientLight; + doLighting(passViewPos, normalize(viewNormal), shadowing, diffuseLight, ambientLight); + lighting = diffuseLight + ambientLight; +#endif + +#if @clamp + lighting = clamp(lighting, vec3(0.0), vec3(1.0)); +#else + lighting = max(lighting, 0.0); +#endif + + gl_FragData[0].xyz *= lighting; + +#if @radialFog + float fogValue = clamp((euclideanDepth - gl_Fog.start) * gl_Fog.scale, 0.0, 1.0); +#else + float fogValue = clamp((linearDepth - gl_Fog.start) * gl_Fog.scale, 0.0, 1.0); +#endif + gl_FragData[0].xyz = mix(gl_FragData[0].xyz, gl_Fog.color.xyz, fogValue); + + applyShadowDebugOverlay(); +} diff --git a/files/shaders/groundcover_vertex.glsl b/files/shaders/groundcover_vertex.glsl new file mode 100644 index 000000000..4f3303b03 --- /dev/null +++ b/files/shaders/groundcover_vertex.glsl @@ -0,0 +1,143 @@ +#version 120 + +#define GROUNDCOVER + +attribute vec4 aOffset; +attribute vec3 aRotation; + +#if @diffuseMap +varying vec2 diffuseMapUV; +#endif + +#if @normalMap +varying vec2 normalMapUV; +varying vec4 passTangent; +#endif + +// Other shaders respect forcePPL, but legacy groundcover mods were designed to work with vertex lighting. +// They may do not look as intended with per-pixel lighting, so ignore this setting for now. +#define PER_PIXEL_LIGHTING @normalMap + +varying float euclideanDepth; +varying float linearDepth; + +#if PER_PIXEL_LIGHTING +varying vec3 passViewPos; +varying vec3 passNormal; +#else +centroid varying vec3 passLighting; +centroid varying vec3 shadowDiffuseLighting; +#endif + +#include "shadows_vertex.glsl" +#include "lighting.glsl" + +uniform float osg_SimulationTime; +uniform mat4 osg_ViewMatrixInverse; +uniform mat4 osg_ViewMatrix; +uniform float windSpeed; +uniform vec3 playerPos; + +vec2 groundcoverDisplacement(in vec3 worldpos, float h) +{ + vec2 windDirection = vec2(1.0); + vec3 footPos = playerPos; + vec3 windVec = vec3(windSpeed * windDirection, 1.0); + + float v = length(windVec); + vec2 displace = vec2(2.0 * windVec + 0.1); + vec2 harmonics = vec2(0.0); + + harmonics += vec2((1.0 - 0.10*v) * sin(1.0*osg_SimulationTime + worldpos.xy / 1100.0)); + harmonics += vec2((1.0 - 0.04*v) * cos(2.0*osg_SimulationTime + worldpos.xy / 750.0)); + harmonics += vec2((1.0 + 0.14*v) * sin(3.0*osg_SimulationTime + worldpos.xy / 500.0)); + harmonics += vec2((1.0 + 0.28*v) * sin(5.0*osg_SimulationTime + worldpos.xy / 200.0)); + + // FIXME: stomping function does not work well in MGE: + // 1. It does not take in account Z coordinate, so it works even when player levitates. + // 2. It works more-or-less well only for grass meshes, but not for other types of plants. + // So disable this function for now, until we find a better one. + vec2 stomp = vec2(0.0); + //float d = length(worldpos.xy - footPos.xy); + //if (d < 150.0 && d > 0.0) + //{ + // stomp = (60.0 / d - 0.4) * (worldpos.xy - footPos.xy); + //} + + return clamp(0.02 * h, 0.0, 1.0) * (harmonics * displace + stomp); +} + +mat4 rotation(in vec3 angle) +{ + float sin_x = sin(angle.x); + float cos_x = cos(angle.x); + float sin_y = sin(angle.y); + float cos_y = cos(angle.y); + float sin_z = sin(angle.z); + float cos_z = cos(angle.z); + + return mat4( + cos_z*cos_y+sin_x*sin_y*sin_z, -sin_z*cos_x, cos_z*sin_y+sin_z*sin_x*cos_y, 0.0, + sin_z*cos_y+cos_z*sin_x*sin_y, cos_z*cos_x, sin_z*sin_y-cos_z*sin_x*cos_y, 0.0, + -sin_y*cos_x, sin_x, cos_x*cos_y, 0.0, + 0.0, 0.0, 0.0, 1.0); +} + +mat3 rotation3(in mat4 rot4) +{ + return mat3( + rot4[0].xyz, + rot4[1].xyz, + rot4[2].xyz); +} + +void main(void) +{ + vec3 position = aOffset.xyz; + float scale = aOffset.w; + + mat4 rotation = rotation(aRotation); + vec4 displacedVertex = rotation * scale * gl_Vertex; + + displacedVertex = vec4(displacedVertex.xyz + position, 1.0); + + vec4 worldPos = osg_ViewMatrixInverse * gl_ModelViewMatrix * displacedVertex; + worldPos.xy += groundcoverDisplacement(worldPos.xyz, gl_Vertex.z); + vec4 viewPos = osg_ViewMatrix * worldPos; + + gl_ClipVertex = viewPos; + euclideanDepth = length(viewPos.xyz); + + if (length(gl_ModelViewMatrix * vec4(position, 1.0)) > @groundcoverFadeEnd) + gl_Position = vec4(0.0, 0.0, 0.0, 1.0); + else + gl_Position = gl_ProjectionMatrix * viewPos; + + linearDepth = gl_Position.z; + +#if (!PER_PIXEL_LIGHTING || @shadows_enabled) + vec3 viewNormal = normalize((gl_NormalMatrix * rotation3(rotation) * gl_Normal).xyz); +#endif + +#if @diffuseMap + diffuseMapUV = (gl_TextureMatrix[@diffuseMapUV] * gl_MultiTexCoord@diffuseMapUV).xy; +#endif + +#if @normalMap + normalMapUV = (gl_TextureMatrix[@normalMapUV] * gl_MultiTexCoord@normalMapUV).xy; + passTangent = gl_MultiTexCoord7.xyzw * rotation; +#endif + +#if PER_PIXEL_LIGHTING + passViewPos = viewPos.xyz; + passNormal = rotation3(rotation) * gl_Normal.xyz; +#else + vec3 diffuseLight, ambientLight; + doLighting(viewPos.xyz, viewNormal, diffuseLight, ambientLight, shadowDiffuseLighting); + passLighting = diffuseLight + ambientLight; +#endif + +#if (@shadows_enabled) + setupShadowCoords(viewPos, viewNormal); +#endif +} diff --git a/files/shaders/lighting.glsl b/files/shaders/lighting.glsl index 930f4de26..1b3ff288a 100644 --- a/files/shaders/lighting.glsl +++ b/files/shaders/lighting.glsl @@ -8,7 +8,20 @@ void perLight(out vec3 ambientOut, out vec3 diffuseOut, int lightIndex, vec3 vie float illumination = clamp(1.0 / (gl_LightSource[lightIndex].constantAttenuation + gl_LightSource[lightIndex].linearAttenuation * lightDistance + gl_LightSource[lightIndex].quadraticAttenuation * lightDistance * lightDistance), 0.0, 1.0); ambientOut = gl_LightSource[lightIndex].ambient.xyz * illumination; - diffuseOut = gl_LightSource[lightIndex].diffuse.xyz * max(dot(viewNormal, lightDir), 0.0) * illumination; + + float lambert = dot(viewNormal.xyz, lightDir) * illumination; +#ifndef GROUNDCOVER + lambert = max(lambert, 0.0); +#else + { + // might need to be < 0 depending on direction of viewPos + if (dot(viewPos, viewNormal.xyz) > 0) + lambert = -lambert; + if (lambert < 0) + lambert *= -0.3; + } +#endif + diffuseOut = gl_LightSource[lightIndex].diffuse.xyz * lambert; } #if PER_PIXEL_LIGHTING From 5124e81348ecdc915dd7248c8b088353f2037ea2 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sun, 22 Nov 2020 10:17:20 +0400 Subject: [PATCH 02/13] Use linear interpolation instead of abrupt transitions for groundcover lighting --- files/shaders/lighting.glsl | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/files/shaders/lighting.glsl b/files/shaders/lighting.glsl index 1b3ff288a..5eae89029 100644 --- a/files/shaders/lighting.glsl +++ b/files/shaders/lighting.glsl @@ -14,11 +14,20 @@ void perLight(out vec3 ambientOut, out vec3 diffuseOut, int lightIndex, vec3 vie lambert = max(lambert, 0.0); #else { - // might need to be < 0 depending on direction of viewPos - if (dot(viewPos, viewNormal.xyz) > 0) - lambert = -lambert; - if (lambert < 0) - lambert *= -0.3; + float cosine = dot(normalize(viewPos), normalize(viewNormal.xyz)); + if (lambert >= 0.0) + cosine = -cosine; + + float mult = 1.0; + float divisor = 8.0; + + if (cosine < 0.0 && cosine >= -1.0/divisor) + mult = mix(1.0, 0.3, -cosine*divisor); + else if (cosine < -1.0/divisor) + mult = 0.3; + + lambert *= mult; + lambert = abs(lambert); } #endif diffuseOut = gl_LightSource[lightIndex].diffuse.xyz * lambert; From 36fc573375dcea73cc412d9452e6684f0de9f9ec Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Thu, 17 Dec 2020 13:55:20 +0400 Subject: [PATCH 03/13] Take in account Z direction for stomping --- files/shaders/groundcover_vertex.glsl | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/files/shaders/groundcover_vertex.glsl b/files/shaders/groundcover_vertex.glsl index 4f3303b03..407599eff 100644 --- a/files/shaders/groundcover_vertex.glsl +++ b/files/shaders/groundcover_vertex.glsl @@ -53,18 +53,14 @@ vec2 groundcoverDisplacement(in vec3 worldpos, float h) harmonics += vec2((1.0 + 0.14*v) * sin(3.0*osg_SimulationTime + worldpos.xy / 500.0)); harmonics += vec2((1.0 + 0.28*v) * sin(5.0*osg_SimulationTime + worldpos.xy / 200.0)); - // FIXME: stomping function does not work well in MGE: - // 1. It does not take in account Z coordinate, so it works even when player levitates. - // 2. It works more-or-less well only for grass meshes, but not for other types of plants. - // So disable this function for now, until we find a better one. - vec2 stomp = vec2(0.0); - //float d = length(worldpos.xy - footPos.xy); - //if (d < 150.0 && d > 0.0) - //{ - // stomp = (60.0 / d - 0.4) * (worldpos.xy - footPos.xy); - //} - - return clamp(0.02 * h, 0.0, 1.0) * (harmonics * displace + stomp); + float d = length(worldpos - footPos.xyz); + vec3 stomp = vec3(0.0); + if (d < 150.0 && d > 0.0) + { + stomp = (60.0 / d - 0.4) * (worldpos - footPos.xyz); + } + + return clamp(0.02 * h, 0.0, 1.0) * (harmonics * displace + stomp.xy); } mat4 rotation(in vec3 angle) From a09f03c85062180d5ae82796a92b5eab4ce27543 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 18 Dec 2020 14:52:30 +0400 Subject: [PATCH 04/13] Drop groundcover materials - they are not used --- apps/openmw/mwrender/groundcover.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/openmw/mwrender/groundcover.cpp b/apps/openmw/mwrender/groundcover.cpp index b9fdc2e28..1a5ccc29f 100644 --- a/apps/openmw/mwrender/groundcover.cpp +++ b/apps/openmw/mwrender/groundcover.cpp @@ -58,6 +58,7 @@ namespace MWRender osg::ref_ptr ss = node.getStateSet(); if (ss != nullptr) { + ss->removeAttribute(osg::StateAttribute::MATERIAL); removeAlpha(ss); } @@ -101,6 +102,7 @@ namespace MWRender ss->setAttribute(new osg::VertexAttribDivisor(6, 1)); ss->setAttribute(new osg::VertexAttribDivisor(7, 1)); + ss->removeAttribute(osg::StateAttribute::MATERIAL); removeAlpha(ss); traverse(geom); From 859cd0fd0c34ef6256b0c70cd4be0bef9797a5a0 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 9 Jan 2021 22:47:19 +0400 Subject: [PATCH 05/13] Do not use display lists for instanced meshes --- apps/openmw/mwrender/groundcover.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/openmw/mwrender/groundcover.cpp b/apps/openmw/mwrender/groundcover.cpp index 1a5ccc29f..11a155f9f 100644 --- a/apps/openmw/mwrender/groundcover.cpp +++ b/apps/openmw/mwrender/groundcover.cpp @@ -95,6 +95,9 @@ namespace MWRender (*rotations)[i] = mInstances[i].mPos.asRotationVec3(); } + // Display lists do not support instancing in OSG 3.4 + geom.setUseDisplayList(false); + geom.setVertexAttribArray(6, transforms.get(), osg::Array::BIND_PER_VERTEX); geom.setVertexAttribArray(7, rotations.get(), osg::Array::BIND_PER_VERTEX); From 1a6c06f7b59c05c60b31693f46f9a25940881332 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sun, 10 Jan 2021 16:36:25 +0400 Subject: [PATCH 06/13] Do not allow to set distance to non-positive values --- apps/openmw/mwrender/renderingmanager.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index c755f46f8..1b4b0cf27 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -244,7 +244,7 @@ namespace MWRender globalDefines["preLightEnv"] = Settings::Manager::getBool("apply lighting to environment maps", "Shaders") ? "1" : "0"; globalDefines["radialFog"] = Settings::Manager::getBool("radial fog", "Shaders") ? "1" : "0"; - float groundcoverDistance = (Constants::CellSizeInUnits * Settings::Manager::getInt("distance", "Groundcover") - 1024) * 0.93; + float groundcoverDistance = (Constants::CellSizeInUnits * std::max(1, Settings::Manager::getInt("distance", "Groundcover")) - 1024) * 0.93; globalDefines["groundcoverFadeStart"] = std::to_string(groundcoverDistance * Settings::Manager::getFloat("fade start", "Groundcover")); globalDefines["groundcoverFadeEnd"] = std::to_string(groundcoverDistance); @@ -1024,7 +1024,7 @@ namespace MWRender if (mGroundcoverWorld) { - int groundcoverDistance = Constants::CellSizeInUnits * Settings::Manager::getInt("distance", "Groundcover"); + int groundcoverDistance = Constants::CellSizeInUnits * std::max(1, Settings::Manager::getInt("distance", "Groundcover")); mGroundcoverWorld->setViewDistance(groundcoverDistance * (distanceMult ? 1.f/distanceMult : 1.f)); } } From 8874e88ff1e0339afdf8365ed793a43bf4a750ea Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Mon, 11 Jan 2021 09:59:57 +0400 Subject: [PATCH 07/13] Drop fading setting --- apps/openmw/mwrender/renderingmanager.cpp | 2 +- .../reference/modding/settings/groundcover.rst | 12 ------------ files/settings-default.cfg | 3 --- 3 files changed, 1 insertion(+), 16 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 1b4b0cf27..68576cf44 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -245,7 +245,7 @@ namespace MWRender globalDefines["radialFog"] = Settings::Manager::getBool("radial fog", "Shaders") ? "1" : "0"; float groundcoverDistance = (Constants::CellSizeInUnits * std::max(1, Settings::Manager::getInt("distance", "Groundcover")) - 1024) * 0.93; - globalDefines["groundcoverFadeStart"] = std::to_string(groundcoverDistance * Settings::Manager::getFloat("fade start", "Groundcover")); + globalDefines["groundcoverFadeStart"] = std::to_string(groundcoverDistance * 0.9f); globalDefines["groundcoverFadeEnd"] = std::to_string(groundcoverDistance); // It is unnecessary to stop/start the viewer as no frames are being rendered yet. diff --git a/docs/source/reference/modding/settings/groundcover.rst b/docs/source/reference/modding/settings/groundcover.rst index 9b00d85a1..f0c37b738 100644 --- a/docs/source/reference/modding/settings/groundcover.rst +++ b/docs/source/reference/modding/settings/groundcover.rst @@ -16,18 +16,6 @@ so we can merge them to pages and animate them indifferently from distance from This setting can only be configured by editing the settings configuration file. -fade start ----------- - -:Type: floating point -:Range: 0.0 to 1.0 -:Default: 0.85 - -Determines on which distance from player groundcover fading starts. -Does not work for meshes which textures do not have transparency (e.g. rocks). - -This setting can only be configured by editing the settings configuration file. - density ------- diff --git a/files/settings-default.cfg b/files/settings-default.cfg index f6bfff7b1..3660f56f0 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -960,9 +960,6 @@ load unsupported nif files = false # enable separate groundcover handling enabled = false -# configure groundcover fade out threshold -fade start = 0.85 - # A groundcover density (0.0 <= value <= 1.0) # 1.0 means 100% density density = 1.0 From d12a0fdcb3dbbe056ae4734f8e931fd3e060274f Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Mon, 11 Jan 2021 10:02:55 +0400 Subject: [PATCH 08/13] Mark only instances from groundcover files as groundcover objects --- apps/openmw/mwrender/groundcover.cpp | 21 +++++++++++++++++---- apps/openmw/mwrender/objectpaging.cpp | 3 +-- apps/openmw/mwworld/cellpreloader.cpp | 8 ++------ apps/openmw/mwworld/cellstore.cpp | 20 +++++--------------- apps/openmw/mwworld/contentloader.hpp | 2 +- apps/openmw/mwworld/esmloader.cpp | 6 +++--- apps/openmw/mwworld/esmloader.hpp | 2 +- apps/openmw/mwworld/esmstore.cpp | 9 --------- apps/openmw/mwworld/esmstore.hpp | 17 ----------------- apps/openmw/mwworld/worldimp.cpp | 10 ++++++---- components/esm/cellref.cpp | 5 +++++ components/esm/cellref.hpp | 5 +++++ components/esm/esmreader.cpp | 9 +++------ components/esm/esmreader.hpp | 7 ++----- components/esm/loadstat.cpp | 2 -- components/esm/loadstat.hpp | 2 -- 16 files changed, 51 insertions(+), 77 deletions(-) diff --git a/apps/openmw/mwrender/groundcover.cpp b/apps/openmw/mwrender/groundcover.cpp index 11a155f9f..049118c90 100644 --- a/apps/openmw/mwrender/groundcover.cpp +++ b/apps/openmw/mwrender/groundcover.cpp @@ -13,6 +13,17 @@ namespace MWRender { + std::string getGroundcoverModel(int type, const std::string& id, const MWWorld::ESMStore& store) + { + switch (type) + { + case ESM::REC_STAT: + return store.get().searchStatic(id)->mModel; + default: + return std::string(); + } + } + void GroundcoverUpdater::setWindSpeed(float windSpeed) { mWindSpeed = windSpeed; @@ -217,15 +228,17 @@ namespace MWRender while(cell->getNextRef(esm[index], ref, deleted)) { if (deleted) continue; - Misc::StringUtils::lowerCaseInPlace(ref.mRefID); - std::string model; - if (!store.isGroundcover(ref.mRefID, model)) continue; - if (model.empty()) continue; + if (!ref.mRefNum.fromGroundcoverFile()) continue; if (!calculator.isInstanceEnabled()) continue; if (!isInChunkBorders(ref, minBound, maxBound)) continue; + Misc::StringUtils::lowerCaseInPlace(ref.mRefID); + int type = store.findStatic(ref.mRefID); + std::string model = getGroundcoverModel(type, ref.mRefID, store); + if (model.empty()) continue; model = "meshes/" + model; + instances[model].emplace_back(ref, model); } } diff --git a/apps/openmw/mwrender/objectpaging.cpp b/apps/openmw/mwrender/objectpaging.cpp index 478fde0f8..b85358c20 100644 --- a/apps/openmw/mwrender/objectpaging.cpp +++ b/apps/openmw/mwrender/objectpaging.cpp @@ -373,7 +373,6 @@ namespace MWRender std::map refs; std::vector esm; const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); - for (int cellX = startCell.x(); cellX < startCell.x() + size; ++cellX) { for (int cellY = startCell.y(); cellY < startCell.y() + size; ++cellY) @@ -398,7 +397,7 @@ namespace MWRender int type = store.findStatic(ref.mRefID); if (!typeFilter(type,size>=2)) continue; if (deleted) { refs.erase(ref.mRefNum); continue; } - if (store.isGroundcover(ref.mRefID)) continue; + if (ref.mRefNum.fromGroundcoverFile()) continue; refs[ref.mRefNum] = ref; } } diff --git a/apps/openmw/mwworld/cellpreloader.cpp b/apps/openmw/mwworld/cellpreloader.cpp index 937491f62..421de4a7d 100644 --- a/apps/openmw/mwworld/cellpreloader.cpp +++ b/apps/openmw/mwworld/cellpreloader.cpp @@ -1,5 +1,6 @@ #include "cellpreloader.hpp" +#include #include #include @@ -36,12 +37,7 @@ namespace MWWorld virtual bool operator()(const MWWorld::Ptr& ptr) { - if (ptr.getTypeName()==typeid (ESM::Static).name()) - { - const MWWorld::LiveCellRef *ref = ptr.get(); - if (ref->mBase->mIsGroundcover) - return true; - } + if (ptr.getCellRef().getRefNum().fromGroundcoverFile()) return true; ptr.getClass().getModelsToPreload(ptr, mOut); diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index d8e2eb65f..7ca35a1df 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -169,18 +169,6 @@ namespace namespace MWWorld { - template - bool CellRefList::ignoreInstance (const X* ptr) - { - return false; - } - - template <> - bool CellRefList::ignoreInstance (const ESM::Static* ptr) - { - return ptr->mIsGroundcover; - } - template void CellRefList::load(ESM::CellRef &ref, bool deleted, const MWWorld::ESMStore &esmStore) { @@ -188,8 +176,6 @@ namespace MWWorld if (const X *ptr = store.search (ref.mRefID)) { - if (ignoreInstance(ptr)) return; - typename std::list::iterator iter = std::find(mList.begin(), mList.end(), ref.mRefNum); @@ -700,7 +686,11 @@ namespace MWWorld case ESM::REC_NPC_: mNpcs.load(ref, deleted, store); break; case ESM::REC_PROB: mProbes.load(ref, deleted, store); break; case ESM::REC_REPA: mRepairs.load(ref, deleted, store); break; - case ESM::REC_STAT: mStatics.load(ref, deleted, store); break; + case ESM::REC_STAT: + { + if (ref.mRefNum.fromGroundcoverFile()) return; + mStatics.load(ref, deleted, store); break; + } case ESM::REC_WEAP: mWeapons.load(ref, deleted, store); break; case ESM::REC_BODY: mBodyParts.load(ref, deleted, store); break; diff --git a/apps/openmw/mwworld/contentloader.hpp b/apps/openmw/mwworld/contentloader.hpp index b559df083..b529ae9db 100644 --- a/apps/openmw/mwworld/contentloader.hpp +++ b/apps/openmw/mwworld/contentloader.hpp @@ -21,7 +21,7 @@ struct ContentLoader { } - virtual void load(const boost::filesystem::path& filepath, int& index, bool isGroundcover) + virtual void load(const boost::filesystem::path& filepath, int& index) { Log(Debug::Info) << "Loading content file " << filepath.string(); mListener.setLabel(MyGUI::TextIterator::toTagsString(filepath.string())); diff --git a/apps/openmw/mwworld/esmloader.cpp b/apps/openmw/mwworld/esmloader.cpp index c96618215..46b806582 100644 --- a/apps/openmw/mwworld/esmloader.cpp +++ b/apps/openmw/mwworld/esmloader.cpp @@ -15,15 +15,15 @@ EsmLoader::EsmLoader(MWWorld::ESMStore& store, std::vector& read { } -void EsmLoader::load(const boost::filesystem::path& filepath, int& index, bool isGroundcover) +void EsmLoader::load(const boost::filesystem::path& filepath, int& index) { - ContentLoader::load(filepath.filename(), index, isGroundcover); + ContentLoader::load(filepath.filename(), index); ESM::ESMReader lEsm; lEsm.setEncoder(mEncoder); lEsm.setIndex(index); lEsm.setGlobalReaderList(&mEsm); - lEsm.open(filepath.string(), isGroundcover); + lEsm.open(filepath.string()); mEsm[index] = lEsm; mStore.load(mEsm[index], &mListener); } diff --git a/apps/openmw/mwworld/esmloader.hpp b/apps/openmw/mwworld/esmloader.hpp index cc4c15a15..506105beb 100644 --- a/apps/openmw/mwworld/esmloader.hpp +++ b/apps/openmw/mwworld/esmloader.hpp @@ -25,7 +25,7 @@ struct EsmLoader : public ContentLoader EsmLoader(MWWorld::ESMStore& store, std::vector& readers, ToUTF8::Utf8Encoder* encoder, Loading::Listener& listener); - void load(const boost::filesystem::path& filepath, int& index, bool isGroundcover) override; + void load(const boost::filesystem::path& filepath, int& index) override; private: std::vector& mEsm; diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index 2731d7eb1..90bc80b48 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -190,15 +190,6 @@ void ESMStore::setUp(bool validateRecords) { validate(); countRecords(); - - if (mGroundcovers.empty()) - { - for (const ESM::Static& record : mStatics) - { - if (record.mIsGroundcover) - mGroundcovers[record.mId] = record.mModel; - } - } } } diff --git a/apps/openmw/mwworld/esmstore.hpp b/apps/openmw/mwworld/esmstore.hpp index 18bb95580..d69c56d8c 100644 --- a/apps/openmw/mwworld/esmstore.hpp +++ b/apps/openmw/mwworld/esmstore.hpp @@ -75,7 +75,6 @@ namespace MWWorld // maps the id name to the record type. std::map mIds; std::map mStaticIds; - std::map mGroundcovers; std::map mRefCount; @@ -122,22 +121,6 @@ namespace MWWorld return it->second; } - bool isGroundcover(const std::string &id, std::string &model) const - { - std::map::const_iterator it = mGroundcovers.find(id); - if (it == mGroundcovers.end()) { - return false; - } - model = it->second; - return true; - } - - bool isGroundcover(const std::string &id) const - { - std::map::const_iterator it = mGroundcovers.find(id); - return (it != mGroundcovers.end()); - } - ESMStore() : mDynamicCount(0) { diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 93d1a799c..98af121a5 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -103,12 +103,12 @@ namespace MWWorld return mLoaders.insert(std::make_pair(extension, loader)).second; } - void load(const boost::filesystem::path& filepath, int& index, bool isGroundcover) override + void load(const boost::filesystem::path& filepath, int& index) override { LoadersContainer::iterator it(mLoaders.find(Misc::StringUtils::lowerCase(filepath.extension().string()))); if (it != mLoaders.end()) { - it->second->load(filepath, index, isGroundcover); + it->second->load(filepath, index); } else { @@ -2951,7 +2951,7 @@ namespace MWWorld const Files::MultiDirCollection& col = fileCollections.getCollection(filename.extension().string()); if (col.doesExist(file)) { - contentLoader.load(col.getPath(file), idx, false); + contentLoader.load(col.getPath(file), idx); } else { @@ -2961,13 +2961,15 @@ namespace MWWorld idx++; } + ESM::GroundcoverIndex = idx; + for (const std::string &file : groundcover) { boost::filesystem::path filename(file); const Files::MultiDirCollection& col = fileCollections.getCollection(filename.extension().string()); if (col.doesExist(file)) { - contentLoader.load(col.getPath(file), idx, true); + contentLoader.load(col.getPath(file), idx); } else { diff --git a/components/esm/cellref.cpp b/components/esm/cellref.cpp index 4b9852d65..b4d6ac7a7 100644 --- a/components/esm/cellref.cpp +++ b/components/esm/cellref.cpp @@ -5,6 +5,11 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +namespace ESM +{ + int GroundcoverIndex = std::numeric_limits::max(); +} + void ESM::RefNum::load (ESMReader& esm, bool wide, const std::string& tag) { if (wide) diff --git a/components/esm/cellref.hpp b/components/esm/cellref.hpp index 5bb7fbc53..c2f7ff6de 100644 --- a/components/esm/cellref.hpp +++ b/components/esm/cellref.hpp @@ -12,6 +12,7 @@ namespace ESM class ESMReader; const int UnbreakableLock = std::numeric_limits::max(); + extern int GroundcoverIndex; struct RefNum { @@ -25,6 +26,10 @@ namespace ESM enum { RefNum_NoContentFile = -1 }; inline bool hasContentFile() const { return mContentFile != RefNum_NoContentFile; } inline void unset() { mIndex = 0; mContentFile = RefNum_NoContentFile; } + + // Note: this method should not be used for objects with invalid RefNum + // (for example, for objects from disabled plugins in savegames). + inline bool fromGroundcoverFile() const { return mContentFile >= GroundcoverIndex; } }; /* Cell reference. This represents ONE object (of many) inside the diff --git a/components/esm/esmreader.cpp b/components/esm/esmreader.cpp index 63d2f4d4f..1b6eca734 100644 --- a/components/esm/esmreader.cpp +++ b/components/esm/esmreader.cpp @@ -25,7 +25,6 @@ ESMReader::ESMReader() , mGlobalReaderList(nullptr) , mEncoder(nullptr) , mFileSize(0) - , mIsGroundcoverFile(false) { clearCtx(); } @@ -81,10 +80,8 @@ void ESMReader::openRaw(const std::string& filename) openRaw(Files::openConstrainedFileStream(filename.c_str()), filename); } -void ESMReader::open(Files::IStreamPtr _esm, const std::string &name, bool isGroundcover) +void ESMReader::open(Files::IStreamPtr _esm, const std::string &name) { - mIsGroundcoverFile = isGroundcover; - openRaw(_esm, name); if (getRecName() != "TES3") @@ -95,9 +92,9 @@ void ESMReader::open(Files::IStreamPtr _esm, const std::string &name, bool isGro mHeader.load (*this); } -void ESMReader::open(const std::string &file, bool isGroundcover) +void ESMReader::open(const std::string &file) { - open (Files::openConstrainedFileStream (file.c_str ()), file, isGroundcover); + open (Files::openConstrainedFileStream (file.c_str ()), file); } int64_t ESMReader::getHNLong(const char *name) diff --git a/components/esm/esmreader.hpp b/components/esm/esmreader.hpp index 600cd497b..c660b0dda 100644 --- a/components/esm/esmreader.hpp +++ b/components/esm/esmreader.hpp @@ -31,7 +31,6 @@ public: int getVer() const { return mHeader.mData.version; } int getRecordCount() const { return mHeader.mData.records; } - bool isGroundcoverFile() const { return mIsGroundcoverFile; } float getFVer() const { return (mHeader.mData.version == VER_12) ? 1.2f : 1.3f; } const std::string getAuthor() const { return mHeader.mData.author; } const std::string getDesc() const { return mHeader.mData.desc; } @@ -67,9 +66,9 @@ public: /// Load ES file from a new stream, parses the header. Closes the /// currently open file first, if any. - void open(Files::IStreamPtr _esm, const std::string &name, bool isGroundcover = false); + void open(Files::IStreamPtr _esm, const std::string &name); - void open(const std::string &file, bool isGroundcover = false); + void open(const std::string &file); void openRaw(const std::string &filename); @@ -290,8 +289,6 @@ private: ToUTF8::Utf8Encoder* mEncoder; size_t mFileSize; - - bool mIsGroundcoverFile; }; } #endif diff --git a/components/esm/loadstat.cpp b/components/esm/loadstat.cpp index 85e366f1c..6c9de22bd 100644 --- a/components/esm/loadstat.cpp +++ b/components/esm/loadstat.cpp @@ -37,8 +37,6 @@ namespace ESM if (!hasName) esm.fail("Missing NAME subrecord"); - - mIsGroundcover = esm.isGroundcoverFile(); } void Static::save(ESMWriter &esm, bool isDeleted) const { diff --git a/components/esm/loadstat.hpp b/components/esm/loadstat.hpp index da94c4b8d..3d9144040 100644 --- a/components/esm/loadstat.hpp +++ b/components/esm/loadstat.hpp @@ -28,8 +28,6 @@ struct Static std::string mId, mModel; - bool mIsGroundcover = false; - void load(ESMReader &esm, bool &isDeleted); void save(ESMWriter &esm, bool isDeleted = false) const; From b975f16e6b9ef5ee8f11dbed504d067a2087b9b1 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Tue, 12 Jan 2021 11:23:36 +0400 Subject: [PATCH 09/13] Remove redundant check - groundcover is not present in the CellStore --- apps/openmw/mwworld/cellpreloader.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/apps/openmw/mwworld/cellpreloader.cpp b/apps/openmw/mwworld/cellpreloader.cpp index 421de4a7d..31af5b24b 100644 --- a/apps/openmw/mwworld/cellpreloader.cpp +++ b/apps/openmw/mwworld/cellpreloader.cpp @@ -1,6 +1,5 @@ #include "cellpreloader.hpp" -#include #include #include @@ -37,8 +36,6 @@ namespace MWWorld virtual bool operator()(const MWWorld::Ptr& ptr) { - if (ptr.getCellRef().getRefNum().fromGroundcoverFile()) return true; - ptr.getClass().getModelsToPreload(ptr, mOut); return true; From f40e22768673eb9a7d8ffe2126f853659111d599 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Tue, 12 Jan 2021 12:39:19 +0400 Subject: [PATCH 10/13] Remove redundant formatting changes --- apps/openmw/engine.cpp | 2 +- apps/openmw/mwrender/objectpaging.cpp | 1 + apps/openmw/mwrender/renderingmanager.cpp | 1 + apps/openmw/mwworld/cellreflist.hpp | 2 -- apps/openmw/mwworld/cellstore.cpp | 1 + apps/openmw/mwworld/esmloader.cpp | 16 ++++++++-------- components/esm/esmreader.hpp | 1 + 7 files changed, 13 insertions(+), 11 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 103d06f31..dfaf09c21 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -725,7 +725,7 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) } // Create the world - mEnvironment.setWorld(new MWWorld::World (mViewer, rootNode, mResourceSystem.get(), mWorkQueue.get(), + mEnvironment.setWorld( new MWWorld::World (mViewer, rootNode, mResourceSystem.get(), mWorkQueue.get(), mFileCollections, mContentFiles, mGroundcoverFiles, mEncoder, mActivationDistanceOverride, mCellName, mStartupScript, mResDir.string(), mCfgMgr.getUserDataPath().string())); mEnvironment.getWorld()->setupPlayer(); diff --git a/apps/openmw/mwrender/objectpaging.cpp b/apps/openmw/mwrender/objectpaging.cpp index b85358c20..7386c0069 100644 --- a/apps/openmw/mwrender/objectpaging.cpp +++ b/apps/openmw/mwrender/objectpaging.cpp @@ -373,6 +373,7 @@ namespace MWRender std::map refs; std::vector esm; const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); + for (int cellX = startCell.x(); cellX < startCell.x() + size; ++cellX) { for (int cellY = startCell.y(); cellY < startCell.y() + size; ++cellY) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 68576cf44..6ba4baec5 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -73,6 +73,7 @@ namespace MWRender { + class StateUpdater : public SceneUtil::StateSetUpdater { public: diff --git a/apps/openmw/mwworld/cellreflist.hpp b/apps/openmw/mwworld/cellreflist.hpp index 69161c840..30be4a661 100644 --- a/apps/openmw/mwworld/cellreflist.hpp +++ b/apps/openmw/mwworld/cellreflist.hpp @@ -24,8 +24,6 @@ namespace MWWorld /// all methods are known. void load (ESM::CellRef &ref, bool deleted, const MWWorld::ESMStore &esmStore); - inline bool ignoreInstance (const X* ptr); - LiveRef &insert (const LiveRef &item) { mList.push_back(item); diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index 7ca35a1df..3f98684ae 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -169,6 +169,7 @@ namespace namespace MWWorld { + template void CellRefList::load(ESM::CellRef &ref, bool deleted, const MWWorld::ESMStore &esmStore) { diff --git a/apps/openmw/mwworld/esmloader.cpp b/apps/openmw/mwworld/esmloader.cpp index 46b806582..b12d646e7 100644 --- a/apps/openmw/mwworld/esmloader.cpp +++ b/apps/openmw/mwworld/esmloader.cpp @@ -17,15 +17,15 @@ EsmLoader::EsmLoader(MWWorld::ESMStore& store, std::vector& read void EsmLoader::load(const boost::filesystem::path& filepath, int& index) { - ContentLoader::load(filepath.filename(), index); + ContentLoader::load(filepath.filename(), index); - ESM::ESMReader lEsm; - lEsm.setEncoder(mEncoder); - lEsm.setIndex(index); - lEsm.setGlobalReaderList(&mEsm); - lEsm.open(filepath.string()); - mEsm[index] = lEsm; - mStore.load(mEsm[index], &mListener); + ESM::ESMReader lEsm; + lEsm.setEncoder(mEncoder); + lEsm.setIndex(index); + lEsm.setGlobalReaderList(&mEsm); + lEsm.open(filepath.string()); + mEsm[index] = lEsm; + mStore.load(mEsm[index], &mListener); } } /* namespace MWWorld */ diff --git a/components/esm/esmreader.hpp b/components/esm/esmreader.hpp index c660b0dda..761756e8f 100644 --- a/components/esm/esmreader.hpp +++ b/components/esm/esmreader.hpp @@ -289,6 +289,7 @@ private: ToUTF8::Utf8Encoder* mEncoder; size_t mFileSize; + }; } #endif From 24e1dfcddc50bd51a019d062cc8ad7f4ea2b1515 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Wed, 13 Jan 2021 14:30:51 +0400 Subject: [PATCH 11/13] Use default argument --- components/resource/scenemanager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 0a4faed01..71f11e382 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -514,7 +514,7 @@ namespace Resource SetFilterSettingsControllerVisitor setFilterSettingsControllerVisitor(mMinFilter, mMagFilter, mMaxAnisotropy); loaded->accept(setFilterSettingsControllerVisitor); - osg::ref_ptr shaderVisitor (createShaderVisitor("objects")); + osg::ref_ptr shaderVisitor (createShaderVisitor()); loaded->accept(*shaderVisitor); // share state From e3490c8606c3c83a981421ddfc5bdd74e25c88ae Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 23 Jan 2021 09:30:57 +0400 Subject: [PATCH 12/13] Remove dead code --- files/shaders/groundcover_fragment.glsl | 4 ---- 1 file changed, 4 deletions(-) diff --git a/files/shaders/groundcover_fragment.glsl b/files/shaders/groundcover_fragment.glsl index 9c680853a..77fd32e58 100644 --- a/files/shaders/groundcover_fragment.glsl +++ b/files/shaders/groundcover_fragment.glsl @@ -49,10 +49,6 @@ void main() vec3 viewNormal = gl_NormalMatrix * normalize(tbnTranspose * (normalTex.xyz * 2.0 - 1.0)); #endif -#if (!@normalMap && @forcePPL && false) - vec3 viewNormal = gl_NormalMatrix * normalize(passNormal); -#endif - #if @diffuseMap gl_FragData[0] = texture2D(diffuseMap, diffuseMapUV); #else From 5225ec9e5045142f7191b6d81a19350f694682b3 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Tue, 26 Jan 2021 22:32:06 +0400 Subject: [PATCH 13/13] Add changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 96f0460f1..c6c7b5757 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -127,6 +127,7 @@ Feature #5692: Improve spell/magic item search to factor in magic effect names Feature #5730: Add graphic herbalism option to the launcher and documents Feature #5771: ori command should report where a mesh is loaded from and whether the x version is used. + Feature #5813: Instanced groundcover support Task #5480: Drop Qt4 support Task #5520: Improve cell name autocompleter implementation