diff --git a/CHANGELOG.md b/CHANGELOG.md index 96f0460f1..f347fb46e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,7 +44,6 @@ Bug #5367: Selecting a spell on an enchanted item per hotkey always plays the equip sound Bug #5369: Spawnpoint in the Grazelands doesn't produce oversized creatures Bug #5370: Opening an unlocked but trapped door uses the key - Bug #5379: Wandering NPCs falling through cantons Bug #5384: openmw-cs: deleting an instance requires reload of scene window to show in editor Bug #5387: Move/MoveWorld don't update the object's cell properly Bug #5391: Races Redone 1.2 bodies don't show on the inventory @@ -127,6 +126,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 diff --git a/apps/opencs/view/render/instancemode.cpp b/apps/opencs/view/render/instancemode.cpp index 4f6759cdb..99ddce7f7 100644 --- a/apps/opencs/view/render/instancemode.cpp +++ b/apps/opencs/view/render/instancemode.cpp @@ -802,39 +802,15 @@ void CSVRender::InstanceMode::deleteSelectedInstances(bool active) getWorldspaceWidget().clearSelection (Mask_Reference); } -void CSVRender::InstanceMode::dropInstance(DropMode dropMode, CSVRender::Object* object, float objectHeight) +void CSVRender::InstanceMode::dropInstance(CSVRender::Object* object, float dropHeight) { - osg::Vec3d point = object->getPosition().asVec3(); - - osg::Vec3d start = point; - start.z() += objectHeight; - osg::Vec3d end = point; - end.z() = std::numeric_limits::lowest(); - - osg::ref_ptr intersector (new osgUtil::LineSegmentIntersector( - osgUtil::Intersector::MODEL, start, end) ); - intersector->setIntersectionLimit(osgUtil::LineSegmentIntersector::NO_LIMIT); - osgUtil::IntersectionVisitor visitor(intersector); - - if (dropMode == TerrainSep) - visitor.setTraversalMask(Mask_Terrain); - if (dropMode == CollisionSep) - visitor.setTraversalMask(Mask_Terrain | Mask_Reference); - - mParentNode->accept(visitor); - - osgUtil::LineSegmentIntersector::Intersections::iterator it = intersector->getIntersections().begin(); - if (it != intersector->getIntersections().end()) - { - osgUtil::LineSegmentIntersector::Intersection intersection = *it; - ESM::Position position = object->getPosition(); - object->setEdited (Object::Override_Position); - position.pos[2] = intersection.getWorldIntersectPoint().z() + objectHeight; - object->setPosition(position.pos); - } + object->setEdited(Object::Override_Position); + ESM::Position position = object->getPosition(); + position.pos[2] -= dropHeight; + object->setPosition(position.pos); } -float CSVRender::InstanceMode::getDropHeight(DropMode dropMode, CSVRender::Object* object, float objectHeight) +float CSVRender::InstanceMode::calculateDropHeight(DropMode dropMode, CSVRender::Object* object, float objectHeight) { osg::Vec3d point = object->getPosition().asVec3(); @@ -848,9 +824,9 @@ float CSVRender::InstanceMode::getDropHeight(DropMode dropMode, CSVRender::Objec intersector->setIntersectionLimit(osgUtil::LineSegmentIntersector::NO_LIMIT); osgUtil::IntersectionVisitor visitor(intersector); - if (dropMode == Terrain) + if (dropMode & Terrain) visitor.setTraversalMask(Mask_Terrain); - if (dropMode == Collision) + if (dropMode & Collision) visitor.setTraversalMask(Mask_Terrain | Mask_Reference); mParentNode->accept(visitor); @@ -878,12 +854,12 @@ void CSVRender::InstanceMode::dropSelectedInstancesToTerrain() void CSVRender::InstanceMode::dropSelectedInstancesToCollisionSeparately() { - handleDropMethod(TerrainSep, "Drop instances to next collision level separately"); + handleDropMethod(CollisionSep, "Drop instances to next collision level separately"); } void CSVRender::InstanceMode::dropSelectedInstancesToTerrainSeparately() { - handleDropMethod(CollisionSep, "Drop instances to terrain level separately"); + handleDropMethod(TerrainSep, "Drop instances to terrain level separately"); } void CSVRender::InstanceMode::handleDropMethod(DropMode dropMode, QString commandMsg) @@ -897,52 +873,44 @@ void CSVRender::InstanceMode::handleDropMethod(DropMode dropMode, QString comman CSMWorld::CommandMacro macro (undoStack, commandMsg); - DropObjectDataHandler dropObjectDataHandler(&getWorldspaceWidget()); + DropObjectHeightHandler dropObjectDataHandler(&getWorldspaceWidget()); - switch (dropMode) + if(dropMode & Separate) { - case Terrain: - case Collision: - { - float smallestDropHeight = std::numeric_limits::max(); - int counter = 0; - for(osg::ref_ptr tag: selection) - if (CSVRender::ObjectTag *objectTag = dynamic_cast (tag.get())) - { - float thisDrop = getDropHeight(dropMode, objectTag->mObject, dropObjectDataHandler.mObjectHeights[counter]); - if (thisDrop < smallestDropHeight) - smallestDropHeight = thisDrop; - counter++; - } - for(osg::ref_ptr tag: selection) - if (CSVRender::ObjectTag *objectTag = dynamic_cast (tag.get())) - { - objectTag->mObject->setEdited (Object::Override_Position); - ESM::Position position = objectTag->mObject->getPosition(); - position.pos[2] -= smallestDropHeight; - objectTag->mObject->setPosition(position.pos); - objectTag->mObject->apply (macro); - } - } - break; - - case TerrainSep: - case CollisionSep: - { - int counter = 0; - for(osg::ref_ptr tag: selection) - if (CSVRender::ObjectTag *objectTag = dynamic_cast (tag.get())) - { - dropInstance(dropMode, objectTag->mObject, dropObjectDataHandler.mObjectHeights[counter]); - objectTag->mObject->apply (macro); - counter++; - } - } - break; + int counter = 0; + for (osg::ref_ptr tag : selection) + if (CSVRender::ObjectTag* objectTag = dynamic_cast(tag.get())) + { + float objectHeight = dropObjectDataHandler.mObjectHeights[counter]; + float dropHeight = calculateDropHeight(dropMode, objectTag->mObject, objectHeight); + dropInstance(objectTag->mObject, dropHeight); + objectTag->mObject->apply(macro); + counter++; + } + } + else + { + float smallestDropHeight = std::numeric_limits::max(); + int counter = 0; + for (osg::ref_ptr tag : selection) + if (CSVRender::ObjectTag* objectTag = dynamic_cast(tag.get())) + { + float objectHeight = dropObjectDataHandler.mObjectHeights[counter]; + float thisDrop = calculateDropHeight(dropMode, objectTag->mObject, objectHeight); + if (thisDrop < smallestDropHeight) + smallestDropHeight = thisDrop; + counter++; + } + for (osg::ref_ptr tag : selection) + if (CSVRender::ObjectTag* objectTag = dynamic_cast(tag.get())) + { + dropInstance(objectTag->mObject, smallestDropHeight); + objectTag->mObject->apply(macro); + } } } -CSVRender::DropObjectDataHandler::DropObjectDataHandler(WorldspaceWidget* worldspacewidget) +CSVRender::DropObjectHeightHandler::DropObjectHeightHandler(WorldspaceWidget* worldspacewidget) : mWorldspaceWidget(worldspacewidget) { std::vector > selection = mWorldspaceWidget->getSelection (Mask_Reference); @@ -969,7 +937,7 @@ CSVRender::DropObjectDataHandler::DropObjectDataHandler(WorldspaceWidget* worlds } } -CSVRender::DropObjectDataHandler::~DropObjectDataHandler() +CSVRender::DropObjectHeightHandler::~DropObjectHeightHandler() { std::vector > selection = mWorldspaceWidget->getSelection (Mask_Reference); int counter = 0; diff --git a/apps/opencs/view/render/instancemode.hpp b/apps/opencs/view/render/instancemode.hpp index 0a4f2e478..73b7fff12 100644 --- a/apps/opencs/view/render/instancemode.hpp +++ b/apps/opencs/view/render/instancemode.hpp @@ -28,10 +28,13 @@ namespace CSVRender enum DropMode { - Collision, - Terrain, - CollisionSep, - TerrainSep + Separate = 0b1, + + Collision = 0b10, + Terrain = 0b100, + + CollisionSep = Collision | Separate, + TerrainSep = Terrain | Separate, }; CSVWidget::SceneToolMode *mSubMode; @@ -53,8 +56,8 @@ namespace CSVRender osg::Vec3f getProjectionSpaceCoords(const osg::Vec3f& pos); osg::Vec3f getMousePlaneCoords(const QPoint& point, const osg::Vec3d& dragStart); void handleSelectDrag(const QPoint& pos); - void dropInstance(DropMode dropMode, CSVRender::Object* object, float objectHeight); - float getDropHeight(DropMode dropMode, CSVRender::Object* object, float objectHeight); + void dropInstance(CSVRender::Object* object, float dropHeight); + float calculateDropHeight(DropMode dropMode, CSVRender::Object* object, float objectHeight); public: @@ -116,11 +119,11 @@ namespace CSVRender }; /// \brief Helper class to handle object mask data in safe way - class DropObjectDataHandler + class DropObjectHeightHandler { public: - DropObjectDataHandler(WorldspaceWidget* worldspacewidget); - ~DropObjectDataHandler(); + DropObjectHeightHandler(WorldspaceWidget* worldspacewidget); + ~DropObjectHeightHandler(); std::vector mObjectHeights; private: 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..0e4cf762a 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -177,6 +177,8 @@ namespace ~ScopedProfile() { + if (!mStats.collectStats("engine")) + return; const osg::Timer_t end = mTimer.tick(); const UserStats& stats = UserStatsValue::sValue; @@ -460,6 +462,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; @@ -721,7 +728,7 @@ 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, + mFileCollections, mContentFiles, mGroundcoverFiles, mEncoder, mActivationDistanceOverride, mCellName, mStartupScript, mResDir.string(), mCfgMgr.getUserDataPath().string())); mEnvironment.getWorld()->setupPlayer(); @@ -863,16 +870,29 @@ void OMW::Engine::go() prepareEngine (settings); + std::ofstream stats; + if (const auto path = std::getenv("OPENMW_OSG_STATS_FILE")) + { + stats.open(path, std::ios_base::out); + if (stats.is_open()) + Log(Debug::Info) << "Stats will be written to: " << path; + else + Log(Debug::Warning) << "Failed to open file for stats: " << path; + } + // Setup profiler - osg::ref_ptr statshandler = new Resource::Profiler; + osg::ref_ptr statshandler = new Resource::Profiler(stats.is_open()); initStatsHandler(*statshandler); mViewer->addEventHandler(statshandler); - osg::ref_ptr resourceshandler = new Resource::StatsHandler; + osg::ref_ptr resourceshandler = new Resource::StatsHandler(stats.is_open()); mViewer->addEventHandler(resourceshandler); + if (stats.is_open()) + Resource::CollectStatistics(mViewer); + // Start the game if (!mSaveGameFile.empty()) { @@ -897,14 +917,6 @@ void OMW::Engine::go() mEnvironment.getWindowManager()->executeInConsole(mStartupScript); } - std::ofstream stats; - if (const auto path = std::getenv("OPENMW_OSG_STATS_FILE")) - { - stats.open(path, std::ios_base::out); - if (!stats) - Log(Debug::Warning) << "Failed to open file for stats: " << path; - } - // Start the main rendering loop osg::Timer frameTimer; double simulationTime = 0.0; 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(*it); + engine.addContentFile(file); + } + + StringsVector groundcover = variables["groundcover"].as().toStdStringVector(); + for (auto& file : groundcover) + { + engine.addGroundcoverFile(file); } // startup-settings diff --git a/apps/openmw/mwclass/activator.cpp b/apps/openmw/mwclass/activator.cpp index 716e548e1..c54b1c369 100644 --- a/apps/openmw/mwclass/activator.cpp +++ b/apps/openmw/mwclass/activator.cpp @@ -38,10 +38,10 @@ namespace MWClass } } - void Activator::insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const + void Activator::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const { if(!model.empty()) - physics.addObject(ptr, model, rotation); + physics.addObject(ptr, model); } std::string Activator::getModel(const MWWorld::ConstPtr &ptr) const diff --git a/apps/openmw/mwclass/activator.hpp b/apps/openmw/mwclass/activator.hpp index c7b65ef67..10ace6f74 100644 --- a/apps/openmw/mwclass/activator.hpp +++ b/apps/openmw/mwclass/activator.hpp @@ -17,7 +17,7 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const override; + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/actor.cpp b/apps/openmw/mwclass/actor.cpp index 1789f1b19..33aeb26bb 100644 --- a/apps/openmw/mwclass/actor.cpp +++ b/apps/openmw/mwclass/actor.cpp @@ -17,12 +17,16 @@ namespace MWClass { + Actor::Actor() {} + + Actor::~Actor() {} + void Actor::adjustPosition(const MWWorld::Ptr& ptr, bool force) const { MWBase::Environment::get().getWorld()->adjustPosition(ptr, force); } - void Actor::insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const + void Actor::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const { if (!model.empty()) { diff --git a/apps/openmw/mwclass/actor.hpp b/apps/openmw/mwclass/actor.hpp index 98998b4eb..3d509b276 100644 --- a/apps/openmw/mwclass/actor.hpp +++ b/apps/openmw/mwclass/actor.hpp @@ -15,16 +15,16 @@ namespace MWClass { protected: - Actor() = default; + Actor(); public: - ~Actor() override = default; + virtual ~Actor(); void adjustPosition(const MWWorld::Ptr& ptr, bool force) const override; ///< Adjust position to stand on ground. Must be called post model load /// @param force do this even if the ptr is flying - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const override; + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; bool useAnim() const override; @@ -46,8 +46,8 @@ namespace MWClass float getCurrentSpeed(const MWWorld::Ptr& ptr) const override; // not implemented - Actor(const Actor&) = delete; - Actor& operator= (const Actor&) = delete; + Actor(const Actor&); + Actor& operator= (const Actor&); }; } diff --git a/apps/openmw/mwclass/apparatus.cpp b/apps/openmw/mwclass/apparatus.cpp index e09e4804c..518695fab 100644 --- a/apps/openmw/mwclass/apparatus.cpp +++ b/apps/openmw/mwclass/apparatus.cpp @@ -26,6 +26,11 @@ namespace MWClass } } + void Apparatus::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const + { + // TODO: add option somewhere to enable collision for placeable objects + } + std::string Apparatus::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/apparatus.hpp b/apps/openmw/mwclass/apparatus.hpp index 828abef25..8087c57ba 100644 --- a/apps/openmw/mwclass/apparatus.hpp +++ b/apps/openmw/mwclass/apparatus.hpp @@ -17,6 +17,8 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; + std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/armor.cpp b/apps/openmw/mwclass/armor.cpp index 817adbc1f..3f9bfb859 100644 --- a/apps/openmw/mwclass/armor.cpp +++ b/apps/openmw/mwclass/armor.cpp @@ -34,6 +34,11 @@ namespace MWClass } } + void Armor::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const + { + // TODO: add option somewhere to enable collision for placeable objects + } + std::string Armor::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/armor.hpp b/apps/openmw/mwclass/armor.hpp index f64f138a2..4f04e0824 100644 --- a/apps/openmw/mwclass/armor.hpp +++ b/apps/openmw/mwclass/armor.hpp @@ -16,6 +16,8 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; + std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/bodypart.cpp b/apps/openmw/mwclass/bodypart.cpp index 7fe798e27..0315d3ddb 100644 --- a/apps/openmw/mwclass/bodypart.cpp +++ b/apps/openmw/mwclass/bodypart.cpp @@ -22,6 +22,10 @@ namespace MWClass } } + void BodyPart::insertObject(const MWWorld::Ptr &ptr, const std::string &model, MWPhysics::PhysicsSystem &physics) const + { + } + std::string BodyPart::getName(const MWWorld::ConstPtr &ptr) const { return std::string(); diff --git a/apps/openmw/mwclass/bodypart.hpp b/apps/openmw/mwclass/bodypart.hpp index 0e372b884..13d914138 100644 --- a/apps/openmw/mwclass/bodypart.hpp +++ b/apps/openmw/mwclass/bodypart.hpp @@ -15,6 +15,8 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; + std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/book.cpp b/apps/openmw/mwclass/book.cpp index 51b9e39d7..4ea71e3ac 100644 --- a/apps/openmw/mwclass/book.cpp +++ b/apps/openmw/mwclass/book.cpp @@ -31,6 +31,11 @@ namespace MWClass } } + void Book::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const + { + // TODO: add option somewhere to enable collision for placeable objects + } + std::string Book::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/book.hpp b/apps/openmw/mwclass/book.hpp index f3d34c516..c58e68ad8 100644 --- a/apps/openmw/mwclass/book.hpp +++ b/apps/openmw/mwclass/book.hpp @@ -14,6 +14,8 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; + std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/clothing.cpp b/apps/openmw/mwclass/clothing.cpp index 400cd97e4..6d7960aac 100644 --- a/apps/openmw/mwclass/clothing.cpp +++ b/apps/openmw/mwclass/clothing.cpp @@ -29,6 +29,11 @@ namespace MWClass } } + void Clothing::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const + { + // TODO: add option somewhere to enable collision for placeable objects + } + std::string Clothing::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/clothing.hpp b/apps/openmw/mwclass/clothing.hpp index 3d5c162aa..a87e0cbe0 100644 --- a/apps/openmw/mwclass/clothing.hpp +++ b/apps/openmw/mwclass/clothing.hpp @@ -14,6 +14,8 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; + std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp index 3023466f0..28305c394 100644 --- a/apps/openmw/mwclass/container.cpp +++ b/apps/openmw/mwclass/container.cpp @@ -111,10 +111,10 @@ namespace MWClass } } - void Container::insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const + void Container::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const { if(!model.empty()) - physics.addObject(ptr, model, rotation); + physics.addObject(ptr, model); } std::string Container::getModel(const MWWorld::ConstPtr &ptr) const diff --git a/apps/openmw/mwclass/container.hpp b/apps/openmw/mwclass/container.hpp index 74d9ac0da..2dc0c06ca 100644 --- a/apps/openmw/mwclass/container.hpp +++ b/apps/openmw/mwclass/container.hpp @@ -44,7 +44,7 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const override; + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/door.cpp b/apps/openmw/mwclass/door.cpp index 983953c20..ba51d9c2b 100644 --- a/apps/openmw/mwclass/door.cpp +++ b/apps/openmw/mwclass/door.cpp @@ -62,10 +62,10 @@ namespace MWClass } } - void Door::insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const + void Door::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const { if(!model.empty()) - physics.addObject(ptr, model, rotation, MWPhysics::CollisionType_Door); + physics.addObject(ptr, model, MWPhysics::CollisionType_Door); // Resume the door's opening/closing animation if it wasn't finished if (ptr.getRefData().getCustomData()) diff --git a/apps/openmw/mwclass/door.hpp b/apps/openmw/mwclass/door.hpp index 6d40a840b..6c2fa26b8 100644 --- a/apps/openmw/mwclass/door.hpp +++ b/apps/openmw/mwclass/door.hpp @@ -18,7 +18,7 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const override; + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; bool isDoor() const override; diff --git a/apps/openmw/mwclass/ingredient.cpp b/apps/openmw/mwclass/ingredient.cpp index 20f9576df..a007ad115 100644 --- a/apps/openmw/mwclass/ingredient.cpp +++ b/apps/openmw/mwclass/ingredient.cpp @@ -28,6 +28,11 @@ namespace MWClass } } + void Ingredient::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const + { + // TODO: add option somewhere to enable collision for placeable objects + } + std::string Ingredient::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/ingredient.hpp b/apps/openmw/mwclass/ingredient.hpp index 2aa831f86..5219cf39c 100644 --- a/apps/openmw/mwclass/ingredient.hpp +++ b/apps/openmw/mwclass/ingredient.hpp @@ -14,6 +14,8 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; + std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/light.cpp b/apps/openmw/mwclass/light.cpp index d4d196bc4..3bdf10f47 100644 --- a/apps/openmw/mwclass/light.cpp +++ b/apps/openmw/mwclass/light.cpp @@ -33,7 +33,7 @@ namespace MWClass renderingInterface.getObjects().insertModel(ptr, model, true, !(ref->mBase->mData.mFlags & ESM::Light::OffDefault)); } - void Light::insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const + void Light::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const { MWWorld::LiveCellRef *ref = ptr.get(); @@ -41,7 +41,7 @@ namespace MWClass // TODO: add option somewhere to enable collision for placeable objects if (!model.empty() && (ref->mBase->mData.mFlags & ESM::Light::Carry) == 0) - physics.addObject(ptr, model, rotation); + physics.addObject(ptr, model); if (!ref->mBase->mSound.empty() && !(ref->mBase->mData.mFlags & ESM::Light::OffDefault)) MWBase::Environment::get().getSoundManager()->playSound3D(ptr, ref->mBase->mSound, 1.0, 1.0, diff --git a/apps/openmw/mwclass/light.hpp b/apps/openmw/mwclass/light.hpp index 1b1794d4a..e37dddc25 100644 --- a/apps/openmw/mwclass/light.hpp +++ b/apps/openmw/mwclass/light.hpp @@ -14,7 +14,7 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const override; + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; bool useAnim() const override; diff --git a/apps/openmw/mwclass/lockpick.cpp b/apps/openmw/mwclass/lockpick.cpp index 985b08771..9b8abc8f2 100644 --- a/apps/openmw/mwclass/lockpick.cpp +++ b/apps/openmw/mwclass/lockpick.cpp @@ -28,6 +28,11 @@ namespace MWClass } } + void Lockpick::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const + { + // TODO: add option somewhere to enable collision for placeable objects + } + std::string Lockpick::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/lockpick.hpp b/apps/openmw/mwclass/lockpick.hpp index d4b265e39..fabae3343 100644 --- a/apps/openmw/mwclass/lockpick.hpp +++ b/apps/openmw/mwclass/lockpick.hpp @@ -14,6 +14,8 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; + std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/misc.cpp b/apps/openmw/mwclass/misc.cpp index facab9d51..8d3cda6fe 100644 --- a/apps/openmw/mwclass/misc.cpp +++ b/apps/openmw/mwclass/misc.cpp @@ -37,6 +37,11 @@ namespace MWClass } } + void Miscellaneous::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const + { + // TODO: add option somewhere to enable collision for placeable objects + } + std::string Miscellaneous::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/misc.hpp b/apps/openmw/mwclass/misc.hpp index 18788c7ed..9bff85ca5 100644 --- a/apps/openmw/mwclass/misc.hpp +++ b/apps/openmw/mwclass/misc.hpp @@ -14,6 +14,8 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; + std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/potion.cpp b/apps/openmw/mwclass/potion.cpp index 56d9dff27..4af97e634 100644 --- a/apps/openmw/mwclass/potion.cpp +++ b/apps/openmw/mwclass/potion.cpp @@ -30,6 +30,11 @@ namespace MWClass } } + void Potion::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const + { + // TODO: add option somewhere to enable collision for placeable objects + } + std::string Potion::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/potion.hpp b/apps/openmw/mwclass/potion.hpp index 75b962164..75d923f0b 100644 --- a/apps/openmw/mwclass/potion.hpp +++ b/apps/openmw/mwclass/potion.hpp @@ -14,6 +14,8 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; + std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/probe.cpp b/apps/openmw/mwclass/probe.cpp index 51273337a..dba4e8c06 100644 --- a/apps/openmw/mwclass/probe.cpp +++ b/apps/openmw/mwclass/probe.cpp @@ -28,6 +28,11 @@ namespace MWClass } } + void Probe::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const + { + // TODO: add option somewhere to enable collision for placeable objects + } + std::string Probe::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/probe.hpp b/apps/openmw/mwclass/probe.hpp index ef9273a37..a0a41dcfb 100644 --- a/apps/openmw/mwclass/probe.hpp +++ b/apps/openmw/mwclass/probe.hpp @@ -14,6 +14,8 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; + std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/repair.cpp b/apps/openmw/mwclass/repair.cpp index f1b88e422..8907c8212 100644 --- a/apps/openmw/mwclass/repair.cpp +++ b/apps/openmw/mwclass/repair.cpp @@ -25,6 +25,11 @@ namespace MWClass } } + void Repair::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const + { + // TODO: add option somewhere to enable collision for placeable objects + } + std::string Repair::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/repair.hpp b/apps/openmw/mwclass/repair.hpp index c403449e1..b9791e9cf 100644 --- a/apps/openmw/mwclass/repair.hpp +++ b/apps/openmw/mwclass/repair.hpp @@ -14,6 +14,8 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; + std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/static.cpp b/apps/openmw/mwclass/static.cpp index 28156c97f..5551b3d73 100644 --- a/apps/openmw/mwclass/static.cpp +++ b/apps/openmw/mwclass/static.cpp @@ -23,10 +23,10 @@ namespace MWClass } } - void Static::insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const + void Static::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const { if(!model.empty()) - physics.addObject(ptr, model, rotation); + physics.addObject(ptr, model); } std::string Static::getModel(const MWWorld::ConstPtr &ptr) const @@ -63,9 +63,4 @@ namespace MWClass return MWWorld::Ptr(cell.insert(ref), &cell); } - - bool Static::isStatic() const - { - return true; - } } diff --git a/apps/openmw/mwclass/static.hpp b/apps/openmw/mwclass/static.hpp index d0f4913f0..6bc783dad 100644 --- a/apps/openmw/mwclass/static.hpp +++ b/apps/openmw/mwclass/static.hpp @@ -14,7 +14,7 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const override; + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. @@ -25,8 +25,6 @@ namespace MWClass static void registerSelf(); std::string getModel(const MWWorld::ConstPtr &ptr) const override; - - bool isStatic() const override; }; } diff --git a/apps/openmw/mwclass/weapon.cpp b/apps/openmw/mwclass/weapon.cpp index 6246c8fb0..0d6a27cf6 100644 --- a/apps/openmw/mwclass/weapon.cpp +++ b/apps/openmw/mwclass/weapon.cpp @@ -34,6 +34,11 @@ namespace MWClass } } + void Weapon::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const + { + // TODO: add option somewhere to enable collision for placeable objects + } + std::string Weapon::getModel(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/weapon.hpp b/apps/openmw/mwclass/weapon.hpp index db17e6b70..f1824b7d1 100644 --- a/apps/openmw/mwclass/weapon.hpp +++ b/apps/openmw/mwclass/weapon.hpp @@ -15,6 +15,8 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const override; + std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. 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/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index 595f9d629..276321b81 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -309,7 +309,7 @@ namespace MWMechanics if (mPath.size() > 1 && isAlmostStraight(position, mPath[0], mPath[1], pointTolerance)) mPath.pop_front(); - if (mPath.size() == 1 && (mPath.front() - position).length2() < destinationTolerance * destinationTolerance) + if (mPath.size() == 1 && sqrDistanceIgnoreZ(mPath.front(), position) < destinationTolerance * destinationTolerance) mPath.pop_front(); } diff --git a/apps/openmw/mwphysics/actor.cpp b/apps/openmw/mwphysics/actor.cpp index 06600fd6a..3b52ee934 100644 --- a/apps/openmw/mwphysics/actor.cpp +++ b/apps/openmw/mwphysics/actor.cpp @@ -67,7 +67,7 @@ Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, Physic updateScale(); if(!mRotationallyInvariant) - setRotation(mPtr.getRefData().getBaseNode()->getAttitude()); + updateRotation(); updatePosition(); addCollisionMask(getCollisionMask()); @@ -197,10 +197,10 @@ osg::Vec3f Actor::getPreviousPosition() const return mPreviousPosition; } -void Actor::setRotation(osg::Quat quat) +void Actor::updateRotation () { std::scoped_lock lock(mPositionMutex); - mRotation = quat; + mRotation = mPtr.getRefData().getBaseNode()->getAttitude(); } bool Actor::isRotationallyInvariant() const diff --git a/apps/openmw/mwphysics/actor.hpp b/apps/openmw/mwphysics/actor.hpp index 4039f4481..9d129f2ba 100644 --- a/apps/openmw/mwphysics/actor.hpp +++ b/apps/openmw/mwphysics/actor.hpp @@ -49,7 +49,7 @@ namespace MWPhysics void enableCollisionBody(bool collision); void updateScale(); - void setRotation(osg::Quat quat); + void updateRotation(); /** * Return true if the collision shape looks the same no matter how its Z rotated. diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index 6c7c573a4..754bb60af 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -540,6 +540,8 @@ namespace MWPhysics void PhysicsTaskScheduler::updateStats(osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) { + if (!stats.collectStats("engine")) + return; if (mFrameNumber == frameNumber - 1) { stats.setAttribute(mFrameNumber, "physicsworker_time_begin", mTimer->delta_s(mFrameStart, mTimeBegin)); diff --git a/apps/openmw/mwphysics/object.cpp b/apps/openmw/mwphysics/object.cpp index 0a7b9540c..e3615989d 100644 --- a/apps/openmw/mwphysics/object.cpp +++ b/apps/openmw/mwphysics/object.cpp @@ -14,7 +14,7 @@ namespace MWPhysics { - Object::Object(const MWWorld::Ptr& ptr, osg::ref_ptr shapeInstance, osg::Quat rotation, int collisionType, PhysicsTaskScheduler* scheduler) + Object::Object(const MWWorld::Ptr& ptr, osg::ref_ptr shapeInstance, int collisionType, PhysicsTaskScheduler* scheduler) : mShapeInstance(shapeInstance) , mSolid(true) , mTaskScheduler(scheduler) @@ -27,7 +27,7 @@ namespace MWPhysics mCollisionObject->setUserPointer(this); setScale(ptr.getCellRef().getScale()); - setRotation(rotation); + setRotation(Misc::Convert::toBullet(ptr.getRefData().getBaseNode()->getAttitude())); setOrigin(Misc::Convert::toBullet(ptr.getRefData().getPosition().asVec3())); commitPositionChange(); @@ -51,10 +51,10 @@ namespace MWPhysics mScaleUpdatePending = true; } - void Object::setRotation(osg::Quat quat) + void Object::setRotation(const btQuaternion& quat) { std::unique_lock lock(mPositionMutex); - mLocalTransform.setRotation(Misc::Convert::toBullet(quat)); + mLocalTransform.setRotation(quat); mTransformUpdatePending = true; } @@ -116,9 +116,6 @@ namespace MWPhysics if (mShapeInstance->mAnimatedShapes.empty()) return false; - if (mPtr.getRefData().getBaseNode() == nullptr) - return true; - assert (mShapeInstance->getCollisionShape()->isCompound()); btCompoundShape* compound = static_cast(mShapeInstance->getCollisionShape()); diff --git a/apps/openmw/mwphysics/object.hpp b/apps/openmw/mwphysics/object.hpp index c2273831e..cae877180 100644 --- a/apps/openmw/mwphysics/object.hpp +++ b/apps/openmw/mwphysics/object.hpp @@ -26,12 +26,12 @@ namespace MWPhysics class Object final : public PtrHolder { public: - Object(const MWWorld::Ptr& ptr, osg::ref_ptr shapeInstance, osg::Quat rotation, int collisionType, PhysicsTaskScheduler* scheduler); + Object(const MWWorld::Ptr& ptr, osg::ref_ptr shapeInstance, int collisionType, PhysicsTaskScheduler* scheduler); ~Object() override; const Resource::BulletShapeInstance* getShapeInstance() const; void setScale(float scale); - void setRotation(osg::Quat quat); + void setRotation(const btQuaternion& quat); void setOrigin(const btVector3& vec); void commitPositionChange(); btCollisionObject* getCollisionObject(); diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 5a9a0be83..ed0e0c915 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -456,20 +456,20 @@ namespace MWPhysics return heightField->second.get(); } - void PhysicsSystem::addObject (const MWWorld::Ptr& ptr, const std::string& mesh, osg::Quat rotation, int collisionType) + void PhysicsSystem::addObject (const MWWorld::Ptr& ptr, const std::string& mesh, int collisionType) { osg::ref_ptr shapeInstance = mShapeManager->getInstance(mesh); if (!shapeInstance || !shapeInstance->getCollisionShape()) return; - auto obj = std::make_shared(ptr, shapeInstance, rotation, collisionType, mTaskScheduler.get()); + auto obj = std::make_shared(ptr, shapeInstance, collisionType, mTaskScheduler.get()); mObjects.emplace(ptr, obj); if (obj->isAnimated()) mAnimatedObjects.insert(obj.get()); } - void PhysicsSystem::remove(const MWWorld::Ptr &ptr, bool keepObject) + void PhysicsSystem::remove(const MWWorld::Ptr &ptr) { ObjectMap::iterator found = mObjects.find(ptr); if (found != mObjects.end()) @@ -479,8 +479,7 @@ namespace MWPhysics mAnimatedObjects.erase(found->second.get()); - if (!keepObject) - mObjects.erase(found); + mObjects.erase(found); } ActorMap::iterator foundActor = mActors.find(ptr); @@ -622,12 +621,12 @@ namespace MWPhysics mTaskScheduler->updateSingleAabb(foundProjectile->second); } - void PhysicsSystem::updateRotation(const MWWorld::Ptr &ptr, osg::Quat rotate) + void PhysicsSystem::updateRotation(const MWWorld::Ptr &ptr) { ObjectMap::iterator found = mObjects.find(ptr); if (found != mObjects.end()) { - found->second->setRotation(rotate); + found->second->setRotation(Misc::Convert::toBullet(ptr.getRefData().getBaseNode()->getAttitude())); mTaskScheduler->updateSingleAabb(found->second); return; } @@ -636,7 +635,7 @@ namespace MWPhysics { if (!foundActor->second->isRotationallyInvariant()) { - foundActor->second->setRotation(rotate); + foundActor->second->updateRotation(); mTaskScheduler->updateSingleAabb(foundActor->second); } return; diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 715a6cd1a..6f901067a 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -121,7 +121,7 @@ namespace MWPhysics void setWaterHeight(float height); void disableWater(); - void addObject (const MWWorld::Ptr& ptr, const std::string& mesh, osg::Quat rotation, int collisionType = CollisionType_World); + void addObject (const MWWorld::Ptr& ptr, const std::string& mesh, int collisionType = CollisionType_World); void addActor (const MWWorld::Ptr& ptr, const std::string& mesh); int addProjectile(const MWWorld::Ptr& caster, const osg::Vec3f& position, float radius, bool canTraverseWater); @@ -138,10 +138,10 @@ namespace MWPhysics Projectile* getProjectile(int projectileId) const; // Object or Actor - void remove (const MWWorld::Ptr& ptr, bool keepObject = false); + void remove (const MWWorld::Ptr& ptr); void updateScale (const MWWorld::Ptr& ptr); - void updateRotation (const MWWorld::Ptr& ptr, osg::Quat rotate); + void updateRotation (const MWWorld::Ptr& ptr); void updatePosition (const MWWorld::Ptr& ptr); void addHeightField (const float* heights, int x, int y, float triSize, float sqrtVerts, float minH, float maxH, const osg::Object* holdObject); diff --git a/apps/openmw/mwphysics/projectileconvexcallback.cpp b/apps/openmw/mwphysics/projectileconvexcallback.cpp index 0d0ac8720..b803c4400 100644 --- a/apps/openmw/mwphysics/projectileconvexcallback.cpp +++ b/apps/openmw/mwphysics/projectileconvexcallback.cpp @@ -55,7 +55,9 @@ namespace MWPhysics } default: { - mProjectile->hit(MWWorld::Ptr(), m_hitPointWorld, m_hitNormalWorld); + auto* target = static_cast(result.m_hitCollisionObject->getUserPointer()); + auto ptr = target ? target->getPtr() : MWWorld::Ptr(); + mProjectile->hit(ptr, m_hitPointWorld, m_hitNormalWorld); break; } } diff --git a/apps/openmw/mwrender/groundcover.cpp b/apps/openmw/mwrender/groundcover.cpp new file mode 100644 index 000000000..049118c90 --- /dev/null +++ b/apps/openmw/mwrender/groundcover.cpp @@ -0,0 +1,282 @@ +#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 +{ + 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; + } + + 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) + { + ss->removeAttribute(osg::StateAttribute::MATERIAL); + 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(); + } + + // 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); + + osg::ref_ptr ss = geom.getOrCreateStateSet(); + ss->setAttribute(new osg::VertexAttribDivisor(6, 1)); + ss->setAttribute(new osg::VertexAttribDivisor(7, 1)); + + ss->removeAttribute(osg::StateAttribute::MATERIAL); + 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; + 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); + } + } + } + } + } + + 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..7386c0069 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 (ref.mRefNum.fromGroundcoverFile()) continue; refs[ref.mRefNum] = ref; } } diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 6ce431d2e..6ba4baec5 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -68,6 +69,7 @@ #include "fogmanager.hpp" #include "objectpaging.hpp" #include "screenshotmanager.hpp" +#include "groundcover.hpp" namespace MWRender { @@ -243,6 +245,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 * std::max(1, Settings::Manager::getInt("distance", "Groundcover")) - 1024) * 0.93; + 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. mResourceSystem->getSceneManager()->getShaderManager().setGlobalDefines(globalDefines); @@ -269,7 +275,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 +284,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 +298,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 +546,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 +559,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 +573,8 @@ namespace MWRender if (!enable) mWater->setCullCallback(nullptr); mTerrain->enable(enable); + if (mGroundcoverWorld) + mGroundcoverWorld->enable(enable); } void RenderingManager::setSkyEnabled(bool enabled) @@ -612,6 +660,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 +863,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 +1022,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 * std::max(1, Settings::Manager::getInt("distance", "Groundcover")); + mGroundcoverWorld->setViewDistance(groundcoverDistance * (distanceMult ? 1.f/distanceMult : 1.f)); + } } void RenderingManager::updateTextureFiltering() @@ -1158,6 +1222,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 530dfb97e..5f7886856 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -330,12 +330,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; mNodeMask = Mask_Scene | Mask_Sky | Mask_Lighting | extraMask; } diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index b48fe74a6..3f98684ae 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -687,7 +687,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/cellvisitors.hpp b/apps/openmw/mwworld/cellvisitors.hpp index 5985d06fb..e68b383b7 100644 --- a/apps/openmw/mwworld/cellvisitors.hpp +++ b/apps/openmw/mwworld/cellvisitors.hpp @@ -18,23 +18,12 @@ namespace MWWorld if (ptr.getRefData().getBaseNode()) { ptr.getRefData().setBaseNode(nullptr); + mObjects.push_back (ptr); } - mObjects.push_back (ptr); return true; } }; - - struct ListObjectsVisitor - { - std::vector mObjects; - - bool operator() (MWWorld::Ptr ptr) - { - mObjects.push_back (ptr); - return true; - } - }; } #endif diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index becf912ea..950c8a6d4 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -25,12 +25,16 @@ namespace MWWorld { std::map > Class::sClasses; + Class::Class() {} + + Class::~Class() {} + void Class::insertObjectRendering (const Ptr& ptr, const std::string& mesh, MWRender::RenderingInterface& renderingInterface) const { } - void Class::insertObject(const Ptr& ptr, const std::string& mesh, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const + void Class::insertObject(const Ptr& ptr, const std::string& mesh, MWPhysics::PhysicsSystem& physics) const { } diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index 592552d47..1b3d4029e 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -6,7 +6,6 @@ #include #include -#include #include #include "ptr.hpp" @@ -58,9 +57,13 @@ namespace MWWorld std::string mTypeName; + // not implemented + Class (const Class&); + Class& operator= (const Class&); + protected: - Class() = default; + Class(); std::shared_ptr defaultItemActivate(const Ptr &ptr, const Ptr &actor) const; ///< Generate default action for activating inventory items @@ -69,16 +72,14 @@ namespace MWWorld public: - virtual ~Class() = default; - Class (const Class&) = delete; - Class& operator= (const Class&) = delete; + virtual ~Class(); const std::string& getTypeName() const { return mTypeName; } virtual void insertObjectRendering (const Ptr& ptr, const std::string& mesh, MWRender::RenderingInterface& renderingInterface) const; - virtual void insertObject(const Ptr& ptr, const std::string& mesh, osg::Quat rotation, MWPhysics::PhysicsSystem& physics) const; + virtual void insertObject(const Ptr& ptr, const std::string& mesh, MWPhysics::PhysicsSystem& physics) const; ///< Add reference into a cell for rendering (default implementation: don't render anything). virtual std::string getName (const ConstPtr& ptr) const = 0; @@ -318,10 +319,6 @@ namespace MWWorld return false; } - virtual bool isStatic() const { - return false; - } - virtual bool isBipedal(const MWWorld::ConstPtr& ptr) const; virtual bool canFly(const MWWorld::ConstPtr& ptr) const; virtual bool canSwim(const MWWorld::ConstPtr& ptr) const; diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index fcf2c4b38..427ee3aa8 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -75,20 +75,18 @@ namespace * osg::Quat(xr, osg::Vec3(-1, 0, 0)); } - osg::Quat makeNodeRotation(const MWWorld::Ptr& ptr, RotationOrder order) + void setNodeRotation(const MWWorld::Ptr& ptr, MWRender::RenderingManager& rendering, RotationOrder order) { - const auto pos = ptr.getRefData().getPosition(); + if (!ptr.getRefData().getBaseNode()) + return; - const auto rot = ptr.getClass().isActor() ? makeActorOsgQuat(pos) - : (order == RotationOrder::inverse ? makeInversedOrderObjectOsgQuat(pos) : makeObjectOsgQuat(pos)); - - return rot; - } - - void setNodeRotation(const MWWorld::Ptr& ptr, MWRender::RenderingManager& rendering, osg::Quat rotation) - { - if (ptr.getRefData().getBaseNode()) - rendering.rotateObject(ptr, rotation); + rendering.rotateObject(ptr, + ptr.getClass().isActor() + ? makeActorOsgQuat(ptr.getRefData().getPosition()) + : (order == RotationOrder::inverse + ? makeInversedOrderObjectOsgQuat(ptr.getRefData().getPosition()) + : makeObjectOsgQuat(ptr.getRefData().getPosition())) + ); } std::string getModel(const MWWorld::Ptr &ptr, const VFS::Manager *vfs) @@ -105,7 +103,7 @@ namespace } void addObject(const MWWorld::Ptr& ptr, MWPhysics::PhysicsSystem& physics, - MWRender::RenderingManager& rendering, std::set& pagedRefs, bool onlyPhysics) + MWRender::RenderingManager& rendering, std::set& pagedRefs) { if (ptr.getRefData().getBaseNode() || physics.getActor(ptr)) { @@ -113,29 +111,26 @@ namespace return; } + bool useAnim = ptr.getClass().useAnim(); std::string model = getModel(ptr, rendering.getResourceSystem()->getVFS()); - const auto rotation = makeNodeRotation(ptr, RotationOrder::direct); - if (!onlyPhysics) - { - bool useAnim = ptr.getClass().useAnim(); - const ESM::RefNum& refnum = ptr.getCellRef().getRefNum(); - if (!refnum.hasContentFile() || pagedRefs.find(refnum) == pagedRefs.end()) - ptr.getClass().insertObjectRendering(ptr, model, rendering); + const ESM::RefNum& refnum = ptr.getCellRef().getRefNum(); + if (!refnum.hasContentFile() || pagedRefs.find(refnum) == pagedRefs.end()) + ptr.getClass().insertObjectRendering(ptr, model, rendering); + else + ptr.getRefData().setBaseNode(new SceneUtil::PositionAttitudeTransform); // FIXME remove this when physics code is fixed not to depend on basenode + setNodeRotation(ptr, rendering, RotationOrder::direct); - setNodeRotation(ptr, rendering, rotation); + ptr.getClass().insertObject (ptr, model, physics); - if (useAnim) - MWBase::Environment::get().getMechanicsManager()->add(ptr); + if (useAnim) + MWBase::Environment::get().getMechanicsManager()->add(ptr); - if (ptr.getClass().isActor()) - rendering.addWaterRippleEmitter(ptr); + if (ptr.getClass().isActor()) + rendering.addWaterRippleEmitter(ptr); - // Restore effect particles - MWBase::Environment::get().getWorld()->applyLoopingParticles(ptr); - } - if (!physics.getObject(ptr)) - ptr.getClass().insertObject (ptr, model, rotation, physics); + // Restore effect particles + MWBase::Environment::get().getWorld()->applyLoopingParticles(ptr); } void addObject(const MWWorld::Ptr& ptr, const MWPhysics::PhysicsSystem& physics, DetourNavigator::Navigator& navigator) @@ -206,12 +201,11 @@ namespace { MWWorld::CellStore& mCell; Loading::Listener& mLoadingListener; - bool mOnlyStatics; bool mTest; std::vector mToInsert; - InsertVisitor (MWWorld::CellStore& cell, Loading::Listener& loadingListener, bool onlyStatics, bool test); + InsertVisitor (MWWorld::CellStore& cell, Loading::Listener& loadingListener, bool test); bool operator() (const MWWorld::Ptr& ptr); @@ -219,8 +213,8 @@ namespace void insert(AddObject&& addObject); }; - InsertVisitor::InsertVisitor (MWWorld::CellStore& cell, Loading::Listener& loadingListener, bool onlyStatics, bool test) - : mCell (cell), mLoadingListener (loadingListener), mOnlyStatics(onlyStatics), mTest(test) + InsertVisitor::InsertVisitor (MWWorld::CellStore& cell, Loading::Listener& loadingListener, bool test) + : mCell (cell), mLoadingListener (loadingListener), mTest(test) {} bool InsertVisitor::operator() (const MWWorld::Ptr& ptr) @@ -236,7 +230,7 @@ namespace { for (MWWorld::Ptr& ptr : mToInsert) { - if (!ptr.getRefData().isDeleted() && ptr.getRefData().isEnabled() && ((mOnlyStatics && ptr.getClass().isStatic()) || !mOnlyStatics)) + if (!ptr.getRefData().isDeleted() && ptr.getRefData().isEnabled()) { try { @@ -269,16 +263,6 @@ namespace return std::abs(cellPosition.first) + std::abs(cellPosition.second); } - bool isCellInCollection(int x, int y, MWWorld::Scene::CellStoreCollection& collection) - { - for (auto *cell : collection) - { - assert(cell->getCell()->isExterior()); - if (x == cell->getCell()->getGridX() && y == cell->getCell()->getGridY()) - return true; - } - return false; - } } @@ -292,7 +276,7 @@ namespace MWWorld { if (!ptr.getRefData().getBaseNode()) return; ptr.getClass().insertObjectRendering(ptr, getModel(ptr, mRendering.getResourceSystem()->getVFS()), mRendering); - setNodeRotation(ptr, mRendering, makeNodeRotation(ptr, RotationOrder::direct)); + setNodeRotation(ptr, mRendering, RotationOrder::direct); reloadTerrain(); } } @@ -308,9 +292,8 @@ namespace MWWorld void Scene::updateObjectRotation(const Ptr &ptr, RotationOrder order) { - const auto rot = makeNodeRotation(ptr, order); - setNodeRotation(ptr, mRendering, rot); - mPhysics->updateRotation(ptr, rot); + setNodeRotation(ptr, mRendering, order); + mPhysics->updateRotation(ptr); } void Scene::updateObjectScale(const Ptr &ptr) @@ -330,41 +313,15 @@ namespace MWWorld mRendering.update (duration, paused); } - void Scene::unloadInactiveCell (CellStore* cell, bool test) + void Scene::unloadCell (CellStoreCollection::iterator iter, bool test) { - assert(mActiveCells.find(cell) == mActiveCells.end()); - assert(mInactiveCells.find(cell) != mInactiveCells.end()); if (!test) - Log(Debug::Info) << "Unloading cell " << cell->getCell()->getDescription(); - - ListObjectsVisitor visitor; - - cell->forEach(visitor); - for (const auto& ptr : visitor.mObjects) - mPhysics->remove(ptr); - - if (cell->getCell()->isExterior()) - { - const auto cellX = cell->getCell()->getGridX(); - const auto cellY = cell->getCell()->getGridY(); - mPhysics->removeHeightField(cellX, cellY); - } - - mInactiveCells.erase(cell); - } - - void Scene::deactivateCell(CellStore* cell, bool test) - { - assert(mInactiveCells.find(cell) != mInactiveCells.end()); - if (mActiveCells.find(cell) == mActiveCells.end()) - return; - if (!test) - Log(Debug::Info) << "Deactivate cell " << cell->getCell()->getDescription(); + Log(Debug::Info) << "Unloading cell " << (*iter)->getCell()->getDescription(); const auto navigator = MWBase::Environment::get().getWorld()->getNavigator(); ListAndResetObjectsVisitor visitor; - cell->forEach(visitor); + (*iter)->forEach(visitor); const auto world = MWBase::Environment::get().getWorld(); for (const auto& ptr : visitor.mObjects) { @@ -375,157 +332,140 @@ namespace MWWorld navigator->removeAgent(world->getPathfindingHalfExtents(ptr)); mRendering.removeActorPath(ptr); } - mPhysics->remove(ptr, ptr.getClass().isStatic()); + mPhysics->remove(ptr); } - const auto cellX = cell->getCell()->getGridX(); - const auto cellY = cell->getCell()->getGridY(); + const auto cellX = (*iter)->getCell()->getGridX(); + const auto cellY = (*iter)->getCell()->getGridY(); - if (cell->getCell()->isExterior()) + if ((*iter)->getCell()->isExterior()) { if (const auto heightField = mPhysics->getHeightField(cellX, cellY)) navigator->removeObject(DetourNavigator::ObjectId(heightField)); + mPhysics->removeHeightField(cellX, cellY); } - if (cell->getCell()->hasWater()) + if ((*iter)->getCell()->hasWater()) navigator->removeWater(osg::Vec2i(cellX, cellY)); - if (const auto pathgrid = world->getStore().get().search(*cell->getCell())) + if (const auto pathgrid = world->getStore().get().search(*(*iter)->getCell())) navigator->removePathgrid(*pathgrid); const auto player = world->getPlayerPtr(); navigator->update(player.getRefData().getPosition().asVec3()); - MWBase::Environment::get().getMechanicsManager()->drop (cell); + MWBase::Environment::get().getMechanicsManager()->drop (*iter); - mRendering.removeCell(cell); - MWBase::Environment::get().getWindowManager()->removeCell(cell); + mRendering.removeCell(*iter); + MWBase::Environment::get().getWindowManager()->removeCell(*iter); - MWBase::Environment::get().getWorld()->getLocalScripts().clearCell (cell); + MWBase::Environment::get().getWorld()->getLocalScripts().clearCell (*iter); - MWBase::Environment::get().getSoundManager()->stopSound (cell); - mActiveCells.erase(cell); + MWBase::Environment::get().getSoundManager()->stopSound (*iter); + mActiveCells.erase(*iter); } - void Scene::activateCell (CellStore *cell, Loading::Listener* loadingListener, bool respawn, bool test) + void Scene::loadCell (CellStore *cell, Loading::Listener* loadingListener, bool respawn, bool test) { - assert(mActiveCells.find(cell) == mActiveCells.end()); - assert(mInactiveCells.find(cell) != mInactiveCells.end()); - mActiveCells.insert(cell); + std::pair result = mActiveCells.insert(cell); - if (test) - Log(Debug::Info) << "Testing cell " << cell->getCell()->getDescription(); - else - Log(Debug::Info) << "Loading cell " << cell->getCell()->getDescription(); - - const auto world = MWBase::Environment::get().getWorld(); - const auto navigator = world->getNavigator(); - - const int cellX = cell->getCell()->getGridX(); - const int cellY = cell->getCell()->getGridY(); - - if (!test && cell->getCell()->isExterior()) + if(result.second) { - if (const auto heightField = mPhysics->getHeightField(cellX, cellY)) - navigator->addObject(DetourNavigator::ObjectId(heightField), *heightField->getShape(), - heightField->getCollisionObject()->getWorldTransform()); - } + if (test) + Log(Debug::Info) << "Testing cell " << cell->getCell()->getDescription(); + else + Log(Debug::Info) << "Loading cell " << cell->getCell()->getDescription(); - if (const auto pathgrid = world->getStore().get().search(*cell->getCell())) - navigator->addPathgrid(*cell->getCell(), *pathgrid); + float verts = ESM::Land::LAND_SIZE; + float worldsize = ESM::Land::REAL_SIZE; - // register local scripts - // do this before insertCell, to make sure we don't add scripts from levelled creature spawning twice - MWBase::Environment::get().getWorld()->getLocalScripts().addCell (cell); + const auto world = MWBase::Environment::get().getWorld(); + const auto navigator = world->getNavigator(); - if (respawn) - cell->respawn(); + const int cellX = cell->getCell()->getGridX(); + const int cellY = cell->getCell()->getGridY(); - insertCell (*cell, loadingListener, false, test); - - mRendering.addCell(cell); - if (!test) - { - MWBase::Environment::get().getWindowManager()->addCell(cell); - bool waterEnabled = cell->getCell()->hasWater() || cell->isExterior(); - float waterLevel = cell->getWaterLevel(); - mRendering.setWaterEnabled(waterEnabled); - if (waterEnabled) + // Load terrain physics first... + if (!test && cell->getCell()->isExterior()) { - mPhysics->enableWater(waterLevel); - mRendering.setWaterHeight(waterLevel); - - if (cell->getCell()->isExterior()) + osg::ref_ptr land = mRendering.getLandManager()->getLand(cellX, cellY); + const ESM::Land::LandData* data = land ? land->getData(ESM::Land::DATA_VHGT) : nullptr; + if (data) { - if (const auto heightField = mPhysics->getHeightField(cellX, cellY)) - navigator->addWater(osg::Vec2i(cellX, cellY), ESM::Land::REAL_SIZE, - cell->getWaterLevel(), heightField->getCollisionObject()->getWorldTransform()); + mPhysics->addHeightField (data->mHeights, cellX, cellY, worldsize / (verts-1), verts, data->mMinHeight, data->mMaxHeight, land.get()); } else { - navigator->addWater(osg::Vec2i(cellX, cellY), std::numeric_limits::max(), + static std::vector defaultHeight; + defaultHeight.resize(verts*verts, ESM::Land::DEFAULT_HEIGHT); + mPhysics->addHeightField (&defaultHeight[0], cell->getCell()->getGridX(), cell->getCell()->getGridY(), worldsize / (verts-1), verts, ESM::Land::DEFAULT_HEIGHT, ESM::Land::DEFAULT_HEIGHT, land.get()); + } + + if (const auto heightField = mPhysics->getHeightField(cellX, cellY)) + navigator->addObject(DetourNavigator::ObjectId(heightField), *heightField->getShape(), + heightField->getCollisionObject()->getWorldTransform()); + } + + if (const auto pathgrid = world->getStore().get().search(*cell->getCell())) + navigator->addPathgrid(*cell->getCell(), *pathgrid); + + // register local scripts + // do this before insertCell, to make sure we don't add scripts from levelled creature spawning twice + MWBase::Environment::get().getWorld()->getLocalScripts().addCell (cell); + + if (respawn) + cell->respawn(); + + // ... then references. This is important for adjustPosition to work correctly. + insertCell (*cell, loadingListener, test); + + mRendering.addCell(cell); + if (!test) + { + MWBase::Environment::get().getWindowManager()->addCell(cell); + bool waterEnabled = cell->getCell()->hasWater() || cell->isExterior(); + float waterLevel = cell->getWaterLevel(); + mRendering.setWaterEnabled(waterEnabled); + if (waterEnabled) + { + mPhysics->enableWater(waterLevel); + mRendering.setWaterHeight(waterLevel); + + if (cell->getCell()->isExterior()) + { + if (const auto heightField = mPhysics->getHeightField(cellX, cellY)) + navigator->addWater(osg::Vec2i(cellX, cellY), ESM::Land::REAL_SIZE, + cell->getWaterLevel(), heightField->getCollisionObject()->getWorldTransform()); + } + else + { + navigator->addWater(osg::Vec2i(cellX, cellY), std::numeric_limits::max(), cell->getWaterLevel(), btTransform::getIdentity()); + } + } + else + mPhysics->disableWater(); + + const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + navigator->update(player.getRefData().getPosition().asVec3()); + + if (!cell->isExterior() && !(cell->getCell()->mData.mFlags & ESM::Cell::QuasiEx)) + { + + mRendering.configureAmbient(cell->getCell()); } } - else - mPhysics->disableWater(); - - const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - navigator->update(player.getRefData().getPosition().asVec3()); - - if (!cell->isExterior() && !(cell->getCell()->mData.mFlags & ESM::Cell::QuasiEx)) - mRendering.configureAmbient(cell->getCell()); } mPreloader->notifyLoaded(cell); } - void Scene::loadInactiveCell (CellStore *cell, Loading::Listener* loadingListener, bool test) - { - assert(mActiveCells.find(cell) == mActiveCells.end()); - assert(mInactiveCells.find(cell) == mInactiveCells.end()); - mInactiveCells.insert(cell); - - if (test) - Log(Debug::Info) << "Testing inactive cell " << cell->getCell()->getDescription(); - else - Log(Debug::Info) << "Loading inactive cell " << cell->getCell()->getDescription(); - - if (!test && cell->getCell()->isExterior()) - { - float verts = ESM::Land::LAND_SIZE; - float worldsize = ESM::Land::REAL_SIZE; - - const int cellX = cell->getCell()->getGridX(); - const int cellY = cell->getCell()->getGridY(); - - osg::ref_ptr land = mRendering.getLandManager()->getLand(cellX, cellY); - const ESM::Land::LandData* data = land ? land->getData(ESM::Land::DATA_VHGT) : nullptr; - if (data) - { - mPhysics->addHeightField (data->mHeights, cellX, cellY, worldsize / (verts-1), verts, data->mMinHeight, data->mMaxHeight, land.get()); - } - else - { - static std::vector defaultHeight; - defaultHeight.resize(verts*verts, ESM::Land::DEFAULT_HEIGHT); - mPhysics->addHeightField (&defaultHeight[0], cell->getCell()->getGridX(), cell->getCell()->getGridY(), worldsize / (verts-1), verts, ESM::Land::DEFAULT_HEIGHT, ESM::Land::DEFAULT_HEIGHT, land.get()); - } - } - - insertCell (*cell, loadingListener, true, test); - } - void Scene::clear() { - for (auto iter = mInactiveCells.begin(); iter!=mInactiveCells.end(); ) - { - auto* cell = *iter++; - deactivateCell(cell); - unloadInactiveCell (cell); - } + CellStoreCollection::iterator active = mActiveCells.begin(); + while (active!=mActiveCells.end()) + unloadCell (active++); assert(mActiveCells.empty()); - assert(mInactiveCells.empty()); mCurrentCell = nullptr; mPreloader->clear(); @@ -568,24 +508,20 @@ namespace MWWorld void Scene::changeCellGrid (const osg::Vec3f &pos, int playerCellX, int playerCellY, bool changeEvent) { - for (auto iter = mInactiveCells.begin(); iter != mInactiveCells.end(); ) + CellStoreCollection::iterator active = mActiveCells.begin(); + while (active!=mActiveCells.end()) { - auto* cell = *iter++; - if (cell->getCell()->isExterior()) + if ((*active)->getCell()->isExterior()) { - const auto dx = std::abs(playerCellX - cell->getCell()->getGridX()); - const auto dy = std::abs(playerCellY - cell->getCell()->getGridY()); - if (dx > mHalfGridSize || dy > mHalfGridSize) - deactivateCell(cell); - - if (dx > mHalfGridSize+1 || dy > mHalfGridSize+1) - unloadInactiveCell(cell); - } - else - { - deactivateCell(cell); - unloadInactiveCell(cell); + if (std::abs (playerCellX-(*active)->getCell()->getGridX())<=mHalfGridSize && + std::abs (playerCellY-(*active)->getCell()->getGridY())<=mHalfGridSize) + { + // keep cells within the new grid + ++active; + continue; + } } + unloadCell (active++); } mCurrentGridCenter = osg::Vec2i(playerCellX, playerCellY); @@ -597,24 +533,32 @@ namespace MWWorld mRendering.getPagedRefnums(newGrid, mPagedRefs); std::size_t refsToLoad = 0; - const auto cellsToLoad = [&playerCellX,&playerCellY,&refsToLoad](CellStoreCollection& collection, int range) -> std::vector> + std::vector> cellsPositionsToLoad; + // get the number of refs to load + for (int x = playerCellX - mHalfGridSize; x <= playerCellX + mHalfGridSize; ++x) { - std::vector> cellsPositionsToLoad; - for (int x = playerCellX - range; x <= playerCellX + range; ++x) + for (int y = playerCellY - mHalfGridSize; y <= playerCellY + mHalfGridSize; ++y) { - for (int y = playerCellY - range; y <= playerCellY + range; ++y) + CellStoreCollection::iterator iter = mActiveCells.begin(); + + while (iter!=mActiveCells.end()) { - if (!isCellInCollection(x, y, collection)) - { - refsToLoad += MWBase::Environment::get().getWorld()->getExterior(x, y)->count(); - cellsPositionsToLoad.emplace_back(x, y); - } + assert ((*iter)->getCell()->isExterior()); + + if (x==(*iter)->getCell()->getGridX() && + y==(*iter)->getCell()->getGridY()) + break; + + ++iter; + } + + if (iter==mActiveCells.end()) + { + refsToLoad += MWBase::Environment::get().getWorld()->getExterior(x, y)->count(); + cellsPositionsToLoad.emplace_back(x, y); } } - return cellsPositionsToLoad; - }; - auto cellsPositionsToLoad = cellsToLoad(mActiveCells,mHalfGridSize); - auto cellsPositionsToLoadInactive = cellsToLoad(mInactiveCells,mHalfGridSize+1); + } Loading::Listener* loadingListener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); Loading::ScopedLoad load(loadingListener); @@ -638,26 +582,30 @@ namespace MWWorld return getCellPositionPriority(lhs) < getCellPositionPriority(rhs); }); - std::sort(cellsPositionsToLoadInactive.begin(), cellsPositionsToLoadInactive.end(), - [&] (const std::pair& lhs, const std::pair& rhs) { - return getCellPositionPriority(lhs) < getCellPositionPriority(rhs); - }); - // Load cells - for (const auto& [x,y] : cellsPositionsToLoadInactive) + for (const auto& cellPosition : cellsPositionsToLoad) { - if (!isCellInCollection(x, y, mInactiveCells)) + const auto x = cellPosition.first; + const auto y = cellPosition.second; + + CellStoreCollection::iterator iter = mActiveCells.begin(); + + while (iter != mActiveCells.end()) { - CellStore *cell = MWBase::Environment::get().getWorld()->getExterior(x, y); - loadInactiveCell (cell, loadingListener); + assert ((*iter)->getCell()->isExterior()); + + if (x == (*iter)->getCell()->getGridX() && + y == (*iter)->getCell()->getGridY()) + break; + + ++iter; } - } - for (const auto& [x,y] : cellsPositionsToLoad) - { - if (!isCellInCollection(x, y, mActiveCells)) + + if (iter == mActiveCells.end()) { CellStore *cell = MWBase::Environment::get().getWorld()->getExterior(x, y); - activateCell (cell, loadingListener, changeEvent); + + loadCell (cell, loadingListener, changeEvent); } } @@ -690,8 +638,7 @@ namespace MWWorld CellStoreCollection::iterator iter = mActiveCells.begin(); CellStore *cell = MWBase::Environment::get().getWorld()->getExterior(it->mData.mX, it->mData.mY); - loadInactiveCell (cell, loadingListener, true); - activateCell (cell, loadingListener, false, true); + loadCell (cell, loadingListener, false, true); iter = mActiveCells.begin(); while (iter != mActiveCells.end()) @@ -699,8 +646,7 @@ namespace MWWorld if (it->isExterior() && it->mData.mX == (*iter)->getCell()->getGridX() && it->mData.mY == (*iter)->getCell()->getGridY()) { - deactivateCell(*iter, true); - unloadInactiveCell (*iter, true); + unloadCell(iter, true); break; } @@ -738,8 +684,7 @@ namespace MWWorld loadingListener->setLabel("Testing interior cells ("+std::to_string(i)+"/"+std::to_string(cells.getIntSize())+")..."); CellStore *cell = MWBase::Environment::get().getWorld()->getInterior(it->mName); - loadInactiveCell (cell, loadingListener, true); - activateCell (cell, loadingListener, false, true); + loadCell (cell, loadingListener, false, true); CellStoreCollection::iterator iter = mActiveCells.begin(); while (iter != mActiveCells.end()) @@ -748,8 +693,7 @@ namespace MWWorld if (it->mName == (*iter)->getCell()->mName) { - deactivateCell(*iter, true); - unloadInactiveCell (*iter, true); + unloadCell(iter, true); break; } @@ -872,21 +816,15 @@ namespace MWWorld Log(Debug::Info) << "Changing to interior"; // unload - for (auto iter = mInactiveCells.begin(); iter!=mInactiveCells.end(); ) - { - auto* cell = *iter++; - deactivateCell(cell); - unloadInactiveCell(cell); - } - assert(mActiveCells.empty()); - assert(mInactiveCells.empty()); + CellStoreCollection::iterator active = mActiveCells.begin(); + while (active!=mActiveCells.end()) + unloadCell (active++); loadingListener->setProgressRange(cell->count()); // Load cell. mPagedRefs.clear(); - loadInactiveCell (cell, loadingListener); - activateCell (cell, loadingListener, changeEvent); + loadCell (cell, loadingListener, changeEvent); changePlayerCell(cell, position, adjustPlayerPos); @@ -934,26 +872,23 @@ namespace MWWorld mCellChanged = false; } - void Scene::insertCell (CellStore &cell, Loading::Listener* loadingListener, bool onlyStatics, bool test) + void Scene::insertCell (CellStore &cell, Loading::Listener* loadingListener, bool test) { - InsertVisitor insertVisitor (cell, *loadingListener, onlyStatics, test); + InsertVisitor insertVisitor (cell, *loadingListener, test); cell.forEach (insertVisitor); - insertVisitor.insert([&] (const MWWorld::Ptr& ptr) { addObject(ptr, *mPhysics, mRendering, mPagedRefs, onlyStatics); }); - if (!onlyStatics) - { - insertVisitor.insert([&] (const MWWorld::Ptr& ptr) { addObject(ptr, *mPhysics, mNavigator); }); + insertVisitor.insert([&] (const MWWorld::Ptr& ptr) { addObject(ptr, *mPhysics, mRendering, mPagedRefs); }); + insertVisitor.insert([&] (const MWWorld::Ptr& ptr) { addObject(ptr, *mPhysics, mNavigator); }); - // do adjustPosition (snapping actors to ground) after objects are loaded, so we don't depend on the loading order - PositionVisitor posVisitor; - cell.forEach (posVisitor); - } + // do adjustPosition (snapping actors to ground) after objects are loaded, so we don't depend on the loading order + PositionVisitor posVisitor; + cell.forEach (posVisitor); } void Scene::addObjectToScene (const Ptr& ptr) { try { - addObject(ptr, *mPhysics, mRendering, mPagedRefs, false); + addObject(ptr, *mPhysics, mRendering, mPagedRefs); addObject(ptr, *mPhysics, mNavigator); MWBase::Environment::get().getWorld()->scaleObject(ptr, ptr.getCellRef().getScale()); const auto navigator = MWBase::Environment::get().getWorld()->getNavigator(); diff --git a/apps/openmw/mwworld/scene.hpp b/apps/openmw/mwworld/scene.hpp index 33c7b78d0..a70d3ccdd 100644 --- a/apps/openmw/mwworld/scene.hpp +++ b/apps/openmw/mwworld/scene.hpp @@ -65,13 +65,13 @@ namespace MWWorld class Scene { public: - using CellStoreCollection = std::set; + + typedef std::set CellStoreCollection; private: CellStore* mCurrentCell; // the cell the player is in CellStoreCollection mActiveCells; - CellStoreCollection mInactiveCells; bool mCellChanged; MWPhysics::PhysicsSystem *mPhysics; MWRender::RenderingManager& mRendering; @@ -92,7 +92,7 @@ namespace MWWorld std::set mPagedRefs; - void insertCell (CellStore &cell, Loading::Listener* loadingListener, bool onlyStatics, bool test = false); + void insertCell (CellStore &cell, Loading::Listener* loadingListener, bool test = false); osg::Vec2i mCurrentGridCenter; // Load and unload cells as necessary to create a cell grid with "X" and "Y" in the center @@ -108,11 +108,6 @@ namespace MWWorld osg::Vec4i gridCenterToBounds(const osg::Vec2i ¢erCell) const; osg::Vec2i getNewGridCenter(const osg::Vec3f &pos, const osg::Vec2i *currentGridCenter = nullptr) const; - void unloadInactiveCell (CellStore* cell, bool test = false); - void deactivateCell (CellStore* cell, bool test = false); - void activateCell (CellStore *cell, Loading::Listener* loadingListener, bool respawn, bool test = false); - void loadInactiveCell (CellStore *cell, Loading::Listener* loadingListener, bool test = false); - public: Scene (MWRender::RenderingManager& rendering, MWPhysics::PhysicsSystem *physics, @@ -124,6 +119,10 @@ namespace MWWorld void preloadTerrain(const osg::Vec3f& pos, bool sync=false); void reloadTerrain(); + void unloadCell (CellStoreCollection::iterator iter, bool test = false); + + void loadCell (CellStore *cell, Loading::Listener* loadingListener, bool respawn, bool test = false); + void playerMoved (const osg::Vec3f& pos); void changePlayerCell (CellStore* newCell, const ESM::Position& position, bool adjustPlayerPos); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index af9a5e0bb..442672d2b 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -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(); @@ -1336,6 +1337,12 @@ namespace MWWorld void World::adjustPosition(const Ptr &ptr, bool force) { + if (ptr.isEmpty()) + { + Log(Debug::Warning) << "Unable to adjust position for empty object"; + return; + } + osg::Vec3f pos (ptr.getRefData().getPosition().asVec3()); if(!ptr.getRefData().getBaseNode()) @@ -1344,6 +1351,12 @@ namespace MWWorld return; } + if (!ptr.isInCell()) + { + Log(Debug::Warning) << "Unable to adjust position for object '" << ptr.getCellRef().getRefId() << "' - it has no cell"; + return; + } + const float terrainHeight = ptr.getCell()->isExterior() ? getTerrainHeightAt(pos) : -std::numeric_limits::max(); pos.z() = std::max(pos.z(), terrainHeight) + 20; // place slightly above terrain. will snap down to ground with code below @@ -1407,7 +1420,7 @@ namespace MWWorld mWorldScene->removeFromPagedRefs(ptr); mRendering->rotateObject(ptr, rotate); - mPhysics->updateRotation(ptr, rotate); + mPhysics->updateRotation(ptr); if (const auto object = mPhysics->getObject(ptr)) updateNavigatorObject(object); @@ -2941,7 +2954,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) @@ -2959,6 +2972,24 @@ 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); + } + 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::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/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index d937b992b..71f11e382 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -247,10 +247,12 @@ namespace Resource return mForceShaders; } - void SceneManager::recreateShaders(osg::ref_ptr 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); } 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..3649af089 100644 --- a/components/resource/stats.cpp +++ b/components/resource/stats.cpp @@ -18,10 +18,72 @@ namespace Resource { -StatsHandler::StatsHandler(): +static bool collectStatRendering = false; +static bool collectStatCameraObjects = false; +static bool collectStatViewerObjects = false; +static bool collectStatResource = false; +static bool collectStatGPU = false; +static bool collectStatEvent = false; +static bool collectStatFrameRate = false; +static bool collectStatUpdate = false; +static bool collectStatEngine = false; + +static void setupStatCollection() +{ + const char* envList = getenv("OPENMW_OSG_STATS_LIST"); + if (envList == nullptr) + return; + + std::string_view kwList(envList); + + auto kwBegin = kwList.begin(); + + while (kwBegin != kwList.end()) + { + auto kwEnd = std::find(kwBegin, kwList.end(), ';'); + + const auto kw = kwList.substr(std::distance(kwList.begin(), kwBegin), std::distance(kwBegin, kwEnd)); + + if (kw.compare("gpu") == 0) + collectStatGPU = true; + else if (kw.compare("event") == 0) + collectStatEvent = true; + else if (kw.compare("frame_rate") == 0) + collectStatFrameRate = true; + else if (kw.compare("update") == 0) + collectStatUpdate = true; + else if (kw.compare("engine") == 0) + collectStatEngine = true; + else if (kw.compare("rendering") == 0) + collectStatRendering = true; + else if (kw.compare("cameraobjects") == 0) + collectStatCameraObjects = true; + else if (kw.compare("viewerobjects") == 0) + collectStatViewerObjects = true; + else if (kw.compare("resource") == 0) + collectStatResource = true; + else if (kw.compare("times") == 0) + { + collectStatGPU = true; + collectStatEvent = true; + collectStatFrameRate = true; + collectStatUpdate = true; + collectStatEngine = true; + collectStatRendering = true; + } + + if (kwEnd == kwList.end()) + break; + + kwBegin = std::next(kwEnd); + } +} + +StatsHandler::StatsHandler(bool offlineCollect): _key(osgGA::GUIEventAdapter::KEY_F4), _initialized(false), _statsType(false), + _offlineCollect(offlineCollect), _statsWidth(1280.0f), _statsHeight(1024.0f), _font(""), @@ -38,7 +100,8 @@ StatsHandler::StatsHandler(): _font = osgMyGUI::DataManager::getInstance().getDataPath("DejaVuLGCSansMono.ttf"); } -Profiler::Profiler() +Profiler::Profiler(bool offlineCollect): + _offlineCollect(offlineCollect) { if (osgDB::Registry::instance()->getReaderWriterForExtension("ttf")) _font = osgMyGUI::DataManager::getInstance().getDataPath("DejaVuLGCSansMono.ttf"); @@ -48,6 +111,28 @@ Profiler::Profiler() _characterSize = 18; setKeyEventTogglesOnScreenStats(osgGA::GUIEventAdapter::KEY_F3); + setupStatCollection(); +} + +bool Profiler::handle(const osgGA::GUIEventAdapter &ea, osgGA::GUIActionAdapter &aa) +{ + osgViewer::ViewerBase* viewer = nullptr; + + bool handled = StatsHandler::handle(ea, aa); + + auto* view = dynamic_cast(&aa); + if (view) + viewer = view->getViewerBase(); + + if (viewer) + { + // Add/remove openmw stats to the osd as necessary + viewer->getViewerStats()->collectStats("engine", _statsType == StatsHandler::StatsType::VIEWER_STATS); + + if (_offlineCollect) + CollectStatistics(viewer); + } + return handled; } bool StatsHandler::handle(const osgGA::GUIEventAdapter &ea, osgGA::GUIActionAdapter &aa) @@ -67,6 +152,9 @@ bool StatsHandler::handle(const osgGA::GUIEventAdapter &ea, osgGA::GUIActionAdap toggle(viewer); + if (_offlineCollect) + CollectStatistics(viewer); + aa.requestRedraw(); return true; } @@ -281,6 +369,7 @@ void StatsHandler::setUpScene(osgViewer::ViewerBase *viewer) "FrameNumber", "", "Compiling", + "UnrefQueue", "WorkQueue", "WorkThread", "", @@ -294,14 +383,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", @@ -370,6 +458,22 @@ void StatsHandler::getUsage(osg::ApplicationUsage &usage) const usage.addKeyboardMouseBinding(_key, "On screen resource usage stats."); } - +void CollectStatistics(osgViewer::ViewerBase* viewer) +{ + osgViewer::Viewer::Cameras cameras; + viewer->getCameras(cameras); + for (auto* camera : cameras) + { + if (collectStatGPU) camera->getStats()->collectStats("gpu", true); + if (collectStatRendering) camera->getStats()->collectStats("rendering", true); + if (collectStatCameraObjects) camera->getStats()->collectStats("scene", true); + } + if (collectStatEvent) viewer->getViewerStats()->collectStats("event", true); + if (collectStatFrameRate) viewer->getViewerStats()->collectStats("frame_rate", true); + if (collectStatUpdate) viewer->getViewerStats()->collectStats("update", true); + if (collectStatResource) viewer->getViewerStats()->collectStats("resource", true); + if (collectStatViewerObjects) viewer->getViewerStats()->collectStats("scene", true); + if (collectStatEngine) viewer->getViewerStats()->collectStats("engine", true); +} } diff --git a/components/resource/stats.hpp b/components/resource/stats.hpp index 9fa583cca..560275d70 100644 --- a/components/resource/stats.hpp +++ b/components/resource/stats.hpp @@ -18,13 +18,17 @@ namespace Resource class Profiler : public osgViewer::StatsHandler { public: - Profiler(); + Profiler(bool offlineCollect); + bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa) override; + + private: + bool _offlineCollect; }; class StatsHandler : public osgGA::GUIEventHandler { public: - StatsHandler(); + StatsHandler(bool offlineCollect); void setKey(int key) { _key = key; } int getKey() const { return _key; } @@ -47,6 +51,7 @@ namespace Resource osg::ref_ptr _camera; bool _initialized; bool _statsType; + bool _offlineCollect; float _statsWidth; float _statsHeight; @@ -58,6 +63,8 @@ namespace Resource }; + void CollectStatistics(osgViewer::ViewerBase* viewer); + } #endif 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..f0c37b738 --- /dev/null +++ b/docs/source/reference/modding/settings/groundcover.rst @@ -0,0 +1,56 @@ +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. + +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..3660f56f0 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -950,6 +950,22 @@ 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 + +# 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..77fd32e58 --- /dev/null +++ b/files/shaders/groundcover_fragment.glsl @@ -0,0 +1,89 @@ +#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 @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..407599eff --- /dev/null +++ b/files/shaders/groundcover_vertex.glsl @@ -0,0 +1,139 @@ +#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)); + + 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) +{ + 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..5eae89029 100644 --- a/files/shaders/lighting.glsl +++ b/files/shaders/lighting.glsl @@ -8,7 +8,29 @@ 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 + { + 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; } #if PER_PIXEL_LIGHTING