diff --git a/CHANGELOG.md b/CHANGELOG.md index d0965aea8..de0f33f78 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,9 @@ Bug #2969: Scripted items can stack Bug #2987: Editor: some chance and AI data fields can overflow + Bug #3006: 'else if' operator breaks script compilation + Bug #3109: SetPos/Position handles actors differently + Bug #3282: Unintended behaviour when assigning F3 and Windows keys Bug #3623: Fix HiDPI on Windows Bug #3733: Normal maps are inverted on mirrored UVs Bug #3765: DisableTeleporting makes Mark/Recall/Intervention effects undetectable @@ -18,6 +21,7 @@ Bug #4720: Inventory avatar has shield with two-handed weapon during [un]equipping animation Bug #4723: ResetActors command works incorrectly Bug #4745: Editor: Interior cell lighting field values are not displayed as colors + Bug #4736: LandTexture records overrides do not work Bug #4746: Non-solid player can't run or sneak Bug #4747: Bones are not read from X.NIF file for NPC animation Bug #4750: Sneaking doesn't work in first person view if the player is in attack ready state @@ -32,15 +36,26 @@ Bug #4813: Creatures with known file but no "Sound Gen Creature" assigned use default sounds Bug #4815: "Finished" journal entry with lower index doesn't close journal, SetJournalIndex closes journal Bug #4820: Spell absorption is broken + Bug #4823: Jail progress bar works incorrectly Bug #4827: NiUVController is handled incorrectly Bug #4828: Potion looping effects VFX are not shown for NPCs Bug #4837: CTD when a mesh with NiLODNode root node with particles is loaded + Bug #4841: Russian localization ignores implicit keywords Bug #4860: Actors outside of processing range visible for one frame after spawning + Bug #4867: Arbitrary text after local variable declarations breaks script compilation Bug #4876: AI ratings handling inconsistencies + Bug #4877: Startup script executes only on a new game start Bug #4888: Global variable stray explicit reference calls break script compilation + Bug #4896: Title screen music doesn't loop + Bug #4911: Editor: QOpenGLContext::swapBuffers() warning with Qt5 + Bug #4916: Specular power (shininess) material parameter is ignored when shaders are used. + Bug #4922: Werewolves can not attack if the transformation happens during attack + Bug #4938: Strings from subrecords with actually empty headers can't be empty + Bug #4942: Hand-to-Hand attack type is chosen randomly when "always use best attack" is turned off Feature #2229: Improve pathfinding AI Feature #3442: Default values for fallbacks from ini file Feature #3610: Option to invert X axis + Feature #3893: Implicit target for "set" function in console Feature #3980: In-game option to disable controller Feature #4209: Editor: Faction rank sub-table Feature #4673: Weapon sheathing @@ -52,6 +67,7 @@ Feature #4887: Add openmw command option to set initial random seed Feature #4890: Make Distant Terrain configurable Task #4686: Upgrade media decoder to a more current FFmpeg API + Task #4695: Optimize Distant Terrain memory consumption 0.45.0 ------ diff --git a/CI/before_install.osx.sh b/CI/before_install.osx.sh index 895b07e1e..0a4c7313c 100755 --- a/CI/before_install.osx.sh +++ b/CI/before_install.osx.sh @@ -1,11 +1,9 @@ #!/bin/sh -e brew update -brew unlink cmake || true -brew install https://gist.githubusercontent.com/nikolaykasyanov/f36da224bdef42025e480f99fa21a82d/raw/7dd8b5ed2750198757f81c6bc6456e03541999bd/cmake.rb -brew switch cmake 3.12.4 brew outdated pkgconfig || brew upgrade pkgconfig brew install qt +brew install ccache curl -fSL -R -J https://downloads.openmw.org/osx/dependencies/openmw-deps-110f3d3.zip -o ~/openmw-deps.zip unzip -o ~/openmw-deps.zip -d /private/tmp/openmw-deps > /dev/null diff --git a/CI/before_script.osx.sh b/CI/before_script.osx.sh index d3994a7ae..1c3a71bdd 100755 --- a/CI/before_script.osx.sh +++ b/CI/before_script.osx.sh @@ -4,12 +4,15 @@ export CXX=clang++ export CC=clang DEPENDENCIES_ROOT="/private/tmp/openmw-deps/openmw-deps" -QT_PATH=`brew --prefix qt` +QT_PATH=$(brew --prefix qt) +CCACHE_EXECUTABLE=$(brew --prefix ccache)/bin/ccache mkdir build cd build cmake \ -D CMAKE_PREFIX_PATH="$DEPENDENCIES_ROOT;$QT_PATH" \ +-D CMAKE_C_COMPILER_LAUNCHER="$CCACHE_EXECUTABLE" \ +-D CMAKE_CXX_COMPILER_LAUNCHER="$CCACHE_EXECUTABLE" \ -D CMAKE_OSX_DEPLOYMENT_TARGET="10.9" \ -D CMAKE_OSX_SYSROOT="macosx10.14" \ -D CMAKE_BUILD_TYPE=Release \ diff --git a/CMakeLists.txt b/CMakeLists.txt index b6e646c13..4d750c915 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -267,11 +267,12 @@ endif() IF(BUILD_OPENMW OR BUILD_OPENCS) find_package(OpenSceneGraph 3.3.4 REQUIRED osgDB osgViewer osgText osgGA osgParticle osgUtil osgFX osgShadow) - include_directories(${OPENSCENEGRAPH_INCLUDE_DIRS}) + include_directories(SYSTEM ${OPENSCENEGRAPH_INCLUDE_DIRS}) set(USED_OSG_PLUGINS osgdb_bmp osgdb_dds + osgdb_freetype osgdb_jpeg osgdb_osg osgdb_png @@ -858,8 +859,8 @@ endif() # Apple bundling if (OPENMW_OSX_DEPLOYMENT AND APPLE AND DESIRED_QT_VERSION MATCHES 5) - if (${CMAKE_MAJOR_VERSION} STREQUAL "3" AND ${CMAKE_MINOR_VERSION} STREQUAL "13") - message(FATAL_ERROR "macOS packaging is broken in CMake 3.13.*, see https://gitlab.com/OpenMW/openmw/issues/4767. Please use an older version like 3.12.4") + if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.13 AND CMAKE_VERSION VERSION_LESS 3.13.4) + message(FATAL_ERROR "macOS packaging is broken in early CMake 3.13 releases, see https://gitlab.com/OpenMW/openmw/issues/4767. Please use at least 3.13.4 or an older version like 3.12.4") endif () get_property(QT_COCOA_PLUGIN_PATH TARGET Qt5::QCocoaIntegrationPlugin PROPERTY LOCATION_RELEASE) diff --git a/apps/esmtool/record.cpp b/apps/esmtool/record.cpp index cfd658fc9..c5153dadb 100644 --- a/apps/esmtool/record.cpp +++ b/apps/esmtool/record.cpp @@ -739,7 +739,7 @@ void Record::print() std::cout << " Faction Reaction: " << mData.mData.mRankData[i].mFactReaction << std::endl; } - for (const std::pair &reaction : mData.mReactions) + for (const auto &reaction : mData.mReactions) std::cout << " Reaction: " << reaction.second << " = " << reaction.first << std::endl; std::cout << " Deleted: " << mIsDeleted << std::endl; } diff --git a/apps/niftest/niftest.cpp b/apps/niftest/niftest.cpp index e3f2b89f8..f848ae330 100644 --- a/apps/niftest/niftest.cpp +++ b/apps/niftest/niftest.cpp @@ -102,6 +102,17 @@ bool parseOptions (int argc, char** argv, std::vector& files) bpo::parsed_options valid_opts = bpo::command_line_parser(argc, argv). options(desc).positional(p).run(); bpo::store(valid_opts, variables); + bpo::notify(variables); + if (variables.count ("help")) + { + std::cout << desc << std::endl; + return false; + } + if (variables.count("input-file")) + { + files = variables["input-file"].as< std::vector >(); + return true; + } } catch(std::exception &e) { @@ -110,18 +121,6 @@ bool parseOptions (int argc, char** argv, std::vector& files) return false; } - bpo::notify(variables); - if (variables.count ("help")) - { - std::cout << desc << std::endl; - return false; - } - if (variables.count("input-file")) - { - files = variables["input-file"].as< std::vector >(); - return true; - } - std::cout << "No input files or directories specified!" << std::endl; std::cout << desc << std::endl; return false; diff --git a/apps/opencs/model/prefs/shortcuteventhandler.cpp b/apps/opencs/model/prefs/shortcuteventhandler.cpp index e42cb5da1..07c48fcaa 100644 --- a/apps/opencs/model/prefs/shortcuteventhandler.cpp +++ b/apps/opencs/model/prefs/shortcuteventhandler.cpp @@ -195,7 +195,7 @@ namespace CSMPrefs // Only activate the best match; in exact conflicts, this will favor the first shortcut added. if (!potentials.empty()) { - std::sort(potentials.begin(), potentials.end(), ShortcutEventHandler::sort); + std::stable_sort(potentials.begin(), potentials.end(), ShortcutEventHandler::sort); Shortcut* shortcut = potentials.front().second; if (shortcut->getModifierStatus() && shortcut->getSecondaryMode() == Shortcut::SM_Replace) @@ -325,7 +325,7 @@ namespace CSMPrefs if (left.first == Matches_WithMod && right.first == Matches_NoMod) return true; else - return left.second->getPosition() >= right.second->getPosition(); + return left.second->getPosition() > right.second->getPosition(); } void ShortcutEventHandler::widgetDestroyed() diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index da46ea876..8fa449b8f 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -1006,7 +1006,7 @@ void CSMWorld::Data::loadFallbackEntries() std::make_pair("PrisonMarker", "marker_prison.nif") }; - for (const std::pair &marker : staticMarkers) + for (const auto &marker : staticMarkers) { if (mReferenceables.searchId (marker.first)==-1) { @@ -1020,7 +1020,7 @@ void CSMWorld::Data::loadFallbackEntries() } } - for (const std::pair &marker : doorMarkers) + for (const auto &marker : doorMarkers) { if (mReferenceables.searchId (marker.first)==-1) { diff --git a/apps/opencs/view/render/pagedworldspacewidget.cpp b/apps/opencs/view/render/pagedworldspacewidget.cpp index ff69656a2..7f31373ee 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.cpp +++ b/apps/opencs/view/render/pagedworldspacewidget.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -615,7 +616,39 @@ void CSVRender::PagedWorldspaceWidget::useViewHint (const std::string& hint) } else if (hint[0]=='r') { - /// \todo implement 'r' type hints + // syntax r:ref#number (e.g. r:ref#100) + char ignore; + + std::istringstream stream (hint.c_str()); + if (stream >> ignore) // ignore r + { + char ignore1; // : or ; + + std::string refCode; // ref#number (e.g. ref#100) + + while (stream >> ignore1 >> refCode) {} + + //Find out cell coordinate + CSMWorld::IdTable& references = dynamic_cast ( + *mDocument.getData().getTableModel (CSMWorld::UniversalId::Type_References)); + int cellColumn = references.findColumnIndex(CSMWorld::Columns::ColumnId_Cell); + QVariant cell = references.data(references.getModelIndex(refCode, cellColumn)).value(); + QString cellqs = cell.toString(); + std::istringstream streamCellCoord (cellqs.toStdString().c_str()); + + if (streamCellCoord >> ignore) //ignore # + { + // Current coordinate + int x, y; + + // Loop through all the coordinates to add them to selection + while (streamCellCoord >> x >> y) + selection.add (CSMWorld::CellCoordinates (x, y)); + + // Mark that camera needs setup + mCamPositionSet=false; + } + } } setCellSelection (selection); diff --git a/apps/opencs/view/render/terrainstorage.hpp b/apps/opencs/view/render/terrainstorage.hpp index 949311248..6c3151e8d 100644 --- a/apps/opencs/view/render/terrainstorage.hpp +++ b/apps/opencs/view/render/terrainstorage.hpp @@ -18,10 +18,10 @@ namespace CSVRender private: const CSMWorld::Data& mData; - virtual osg::ref_ptr getLand (int cellX, int cellY); - virtual const ESM::LandTexture* getLandTexture(int index, short plugin); + virtual osg::ref_ptr getLand (int cellX, int cellY) override; + virtual const ESM::LandTexture* getLandTexture(int index, short plugin) override; - virtual void getBounds(float& minX, float& maxX, float& minY, float& maxY); + virtual void getBounds(float& minX, float& maxX, float& minY, float& maxY) override; }; } diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index e7f57eebd..24f7d2cbb 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -44,6 +44,8 @@ End of tes3mp addition */ +#include + #include "mwinput/inputmanagerimp.hpp" #include "mwgui/windowmanagerimp.hpp" @@ -289,6 +291,8 @@ bool OMW::Engine::frame(float frametime) stats->setAttribute(frameNumber, "WorkQueue", mWorkQueue->getNumItems()); stats->setAttribute(frameNumber, "WorkThread", mWorkQueue->getNumActiveThreads()); + + mEnvironment.getWorld()->getNavigator()->reportStats(frameNumber, *stats); } } @@ -313,6 +317,7 @@ OMW::Engine::Engine(Files::ConfigurationManager& configurationManager) , mActivationDistanceOverride(-1) , mGrab(true) , mExportFonts(false) + , mRandomSeed(0) , mScriptContext (0) , mFSStrict (false) , mScriptBlacklistUse (true) @@ -667,7 +672,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, mFallbackMap, - mActivationDistanceOverride, mCellName, mStartupScript, mResDir.string(), mCfgMgr.getUserDataPath().string())); + mActivationDistanceOverride, mCellName, mResDir.string(), mCfgMgr.getUserDataPath().string())); mEnvironment.getWorld()->setupPlayer(); input->setPlayer(&mEnvironment.getWorld()->getPlayer()); @@ -783,6 +788,9 @@ void OMW::Engine::go() */ Log(Debug::Info) << "OSG version: " << osgGetVersion(); + SDL_version sdlVersion; + SDL_GetVersion(&sdlVersion); + Log(Debug::Info) << "SDL version: " << (int)sdlVersion.major << "." << (int)sdlVersion.minor << "." << (int)sdlVersion.patch; Misc::Rng::init(mRandomSeed); @@ -855,22 +863,21 @@ void OMW::Engine::go() { // start in main menu mEnvironment.getWindowManager()->pushGuiMode (MWGui::GM_MainMenu); - try - { - // Is there an ini setting for this filename or something? - mEnvironment.getSoundManager()->streamMusic("Special/morrowind title.mp3"); - - std::string logo = mFallbackMap["Movies_Morrowind_Logo"]; - if (!logo.empty()) - mEnvironment.getWindowManager()->playVideo(logo, true); - } - catch (...) {} + mEnvironment.getSoundManager()->playTitleMusic(); + std::string logo = mFallbackMap["Movies_Morrowind_Logo"]; + if (!logo.empty()) + mEnvironment.getWindowManager()->playVideo(logo, true); } else { mEnvironment.getStateManager()->newGame (!mNewGame); } + if (!mStartupScript.empty() && mEnvironment.getStateManager()->getState() == MWState::StateManager::State_Running) + { + mEnvironment.getWindowManager()->executeInConsole(mStartupScript); + } + // Start the main rendering loop osg::Timer frameTimer; double simulationTime = 0.0; diff --git a/apps/openmw/mwbase/mechanicsmanager.hpp b/apps/openmw/mwbase/mechanicsmanager.hpp index 68459b471..ee80d919b 100644 --- a/apps/openmw/mwbase/mechanicsmanager.hpp +++ b/apps/openmw/mwbase/mechanicsmanager.hpp @@ -90,9 +90,9 @@ namespace MWBase virtual void setPlayerClass (const ESM::Class& class_) = 0; ///< Set player class to custom class. - virtual void restoreDynamicStats(MWWorld::Ptr actor, bool sleep) = 0; + virtual void restoreDynamicStats(MWWorld::Ptr actor, double hours, bool sleep) = 0; - virtual void rest(bool sleep) = 0; + virtual void rest(double hours, bool sleep) = 0; ///< If the player is sleeping or waiting, this should be called every hour. /// @param sleep is the player sleeping or waiting? diff --git a/apps/openmw/mwbase/soundmanager.hpp b/apps/openmw/mwbase/soundmanager.hpp index 2b3cf1f0d..bad80d6bd 100644 --- a/apps/openmw/mwbase/soundmanager.hpp +++ b/apps/openmw/mwbase/soundmanager.hpp @@ -89,6 +89,9 @@ namespace MWBase ///< Start playing music from the selected folder /// \param name of the folder that contains the playlist + virtual void playTitleMusic() = 0; + ///< Start playing title music + virtual void say(const MWWorld::ConstPtr &reference, const std::string& filename) = 0; ///< Make an actor say some text. /// \param filename name of a sound file in "Sound/" in the data directory. diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index ba697e0ec..ed1d660a5 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -406,7 +406,7 @@ namespace MWBase virtual void deleteObject (const MWWorld::Ptr& ptr) = 0; virtual void undeleteObject (const MWWorld::Ptr& ptr) = 0; - virtual MWWorld::Ptr moveObject (const MWWorld::Ptr& ptr, float x, float y, float z) = 0; + virtual MWWorld::Ptr moveObject (const MWWorld::Ptr& ptr, float x, float y, float z, bool moveToActive=false) = 0; ///< @return an updated Ptr in case the Ptr's cell changes virtual MWWorld::Ptr moveObject(const MWWorld::Ptr &ptr, MWWorld::CellStore* newCell, float x, float y, float z, bool movePhysics=true) = 0; @@ -760,7 +760,7 @@ namespace MWBase virtual bool isPlayerInJail() const = 0; - virtual void rest() = 0; + virtual void rest(double hours) = 0; virtual void setPlayerTraveling(bool traveling) = 0; virtual bool isPlayerTraveling() const = 0; diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp index 50647e546..b09560a39 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp @@ -131,9 +131,6 @@ namespace MWDialogue End of tes3mp addition */ - if (tok->isImplicitKeyword() && mTranslationDataStorage.hasTranslation()) - continue; - if (mActorKnownTopics.count( topicId )) mKnownTopics.insert( topicId ); } diff --git a/apps/openmw/mwdialogue/hypertextparser.hpp b/apps/openmw/mwdialogue/hypertextparser.hpp index 13e135f3c..4ae0474c4 100644 --- a/apps/openmw/mwdialogue/hypertextparser.hpp +++ b/apps/openmw/mwdialogue/hypertextparser.hpp @@ -19,7 +19,6 @@ namespace MWDialogue Token(const std::string & text, Type type) : mText(text), mType(type) {} bool isExplicitLink() { return mType == ExplicitLink; } - bool isImplicitKeyword() { return mType == ImplicitKeyword; } std::string mText; Type mType; diff --git a/apps/openmw/mwgui/console.cpp b/apps/openmw/mwgui/console.cpp index 1de24b056..d210d2bfc 100644 --- a/apps/openmw/mwgui/console.cpp +++ b/apps/openmw/mwgui/console.cpp @@ -11,10 +11,12 @@ #include "../mwscript/extensions.hpp" #include "../mwbase/environment.hpp" +#include "../mwbase/scriptmanager.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" #include "../mwworld/esmstore.hpp" +#include "../mwworld/class.hpp" namespace MWGui { @@ -173,6 +175,12 @@ namespace MWGui print("> " + command + "\n"); Compiler::Locals locals; + if (!mPtr.isEmpty()) + { + std::string script = mPtr.getClass().getScript(mPtr); + if (!script.empty()) + locals = MWBase::Environment::get().getScriptManager()->getLocals(script); + } Compiler::Output output (locals); if (compile (command + "\n", output)) diff --git a/apps/openmw/mwgui/jailscreen.cpp b/apps/openmw/mwgui/jailscreen.cpp index d71a8ca6e..26e2bdc75 100644 --- a/apps/openmw/mwgui/jailscreen.cpp +++ b/apps/openmw/mwgui/jailscreen.cpp @@ -122,8 +122,7 @@ namespace MWGui End of tes3mp addition */ - for (int i=0; irest(true); + MWBase::Environment::get().getMechanicsManager()->rest(mDays * 24, true); /* Start of tes3mp change (major) diff --git a/apps/openmw/mwgui/mainmenu.cpp b/apps/openmw/mwgui/mainmenu.cpp index faf388004..c524a5e5c 100644 --- a/apps/openmw/mwgui/mainmenu.cpp +++ b/apps/openmw/mwgui/mainmenu.cpp @@ -304,11 +304,10 @@ namespace MWGui MyGUI::IntSize requested = button->getRequestedSize(); + button->setImageCoord(MyGUI::IntCoord(0, 0, requested.width, requested.height)); // Trim off some of the excessive padding // TODO: perhaps do this within ImageButton? - int trim = 8; - button->setImageCoord(MyGUI::IntCoord(0, trim, requested.width, requested.height-trim)); - int height = requested.height-trim*2; + int height = requested.height-16; button->setImageTile(MyGUI::IntSize(requested.width, height)); button->setCoord((maxwidth-requested.width) / 2, curH, requested.width, height); curH += height; diff --git a/apps/openmw/mwgui/mapwindow.cpp b/apps/openmw/mwgui/mapwindow.cpp index 6407444a9..f49e13ad1 100644 --- a/apps/openmw/mwgui/mapwindow.cpp +++ b/apps/openmw/mwgui/mapwindow.cpp @@ -244,8 +244,7 @@ namespace MWGui map->setNeedMouseFocus(false); fog->setNeedMouseFocus(false); - mMapWidgets.push_back(map); - mFogWidgets.push_back(fog); + mMaps.emplace_back(map, fog); } } } @@ -265,36 +264,37 @@ namespace MWGui void LocalMapBase::applyFogOfWar() { - TextureVector fogTextures; for (int mx=0; mxsetImageTexture(""); + entry.mFogTexture.reset(); continue; } osg::ref_ptr tex = mLocalMapRender->getFogOfWarTexture(x, y); if (tex) { - std::shared_ptr myguitex (new osgMyGUI::OSGTexture(tex)); - fog->setRenderItemTexture(myguitex.get()); + entry.mFogTexture.reset(new osgMyGUI::OSGTexture(tex)); + fog->setRenderItemTexture(entry.mFogTexture.get()); fog->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 1.f, 1.f, 0.f)); - fogTextures.push_back(myguitex); } else + { fog->setImageTexture("black"); + entry.mFogTexture.reset(); + } } } - // Move the textures we just set into mFogTextures, and move the previous textures into fogTextures, for deletion when this function ends. - // Note, above we need to ensure that all widgets are getting a new texture set, lest we delete textures that are still in use. - mFogTextures.swap(fogTextures); redraw(); } @@ -428,7 +428,6 @@ namespace MWGui applyFogOfWar(); // Update the map textures - TextureVector textures; for (int mx=0; mx texture = mLocalMapRender->getMapTexture(mapX, mapY); if (texture) { - std::shared_ptr guiTex (new osgMyGUI::OSGTexture(texture)); - textures.push_back(guiTex); - box->setRenderItemTexture(guiTex.get()); + entry.mMapTexture.reset(new osgMyGUI::OSGTexture(texture)); + box->setRenderItemTexture(entry.mMapTexture.get()); box->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 0.f, 1.f, 1.f)); } else + { box->setRenderItemTexture(nullptr); + entry.mMapTexture.reset(); + } } } - mMapTextures.swap(textures); // Delay the door markers update until scripts have been given a chance to run. // If we don't do this, door markers that should be disabled will still appear on the map. @@ -896,22 +897,13 @@ namespace MWGui void MapWindow::cellExplored(int x, int y) { - mQueuedToExplore.push_back(std::make_pair(x,y)); + mGlobalMapRender->cleanupCameras(); + mGlobalMapRender->exploreCell(x, y, mLocalMapRender->getMapTexture(x, y)); } void MapWindow::onFrame(float dt) { LocalMapBase::onFrame(dt); - - mGlobalMapRender->cleanupCameras(); - - for (CellId& cellId : mQueuedToExplore) - { - mGlobalMapRender->exploreCell(cellId.first, cellId.second, mLocalMapRender->getMapTexture(cellId.first, cellId.second)); - } - - mQueuedToExplore.clear(); - NoDrop::onFrame(dt); } @@ -1128,8 +1120,8 @@ namespace MWGui NoDrop::setAlpha(alpha); // can't allow showing map with partial transparency, as the fog of war will also go transparent // and reveal parts of the map you shouldn't be able to see - for (MyGUI::ImageBox* widget : mMapWidgets) - widget->setVisible(alpha == 1); + for (MapEntry& entry : mMaps) + entry.mMapWidget->setVisible(alpha == 1); } void MapWindow::customMarkerCreated(MyGUI::Widget *marker) diff --git a/apps/openmw/mwgui/mapwindow.hpp b/apps/openmw/mwgui/mapwindow.hpp index 90534bf88..0b6bae26a 100644 --- a/apps/openmw/mwgui/mapwindow.hpp +++ b/apps/openmw/mwgui/mapwindow.hpp @@ -148,12 +148,17 @@ namespace MWGui // Stores markers that were placed by a player. May be shared between multiple map views. CustomMarkerCollection& mCustomMarkers; - std::vector mMapWidgets; - std::vector mFogWidgets; + struct MapEntry + { + MapEntry(MyGUI::ImageBox* mapWidget, MyGUI::ImageBox* fogWidget) + : mMapWidget(mapWidget), mFogWidget(fogWidget) {} - typedef std::vector > TextureVector; - TextureVector mMapTextures; - TextureVector mFogTextures; + MyGUI::ImageBox* mMapWidget; + MyGUI::ImageBox* mFogWidget; + std::shared_ptr mMapTexture; + std::shared_ptr mFogTexture; + }; + std::vector mMaps; // Keep track of created marker widgets, just to easily remove them later. std::vector mDoorMarkerWidgets; @@ -333,10 +338,6 @@ namespace MWGui typedef std::pair CellId; std::set mMarkers; - // Cells that should be explored in the next frame (i.e. their map revealed on the global map) - // We can't do this immediately, because the map update is not immediate either (see mNeedMapUpdate in scene.cpp) - std::vector mQueuedToExplore; - MyGUI::Button* mEventBoxGlobal; MyGUI::Button* mEventBoxLocal; diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index 8615778d5..707192784 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -141,7 +141,8 @@ namespace MWGui MyGUI::ScrollBar* scroll = current->castType(); std::string valueStr; - if (getSettingValueType(current) == "Float") + std::string valueType = getSettingValueType(current); + if (valueType == "Float" || valueType == "Integer") { // TODO: ScrollBar isn't meant for this. should probably use a dedicated FloatSlider widget float min,max; @@ -438,14 +439,18 @@ namespace MWGui if (getSettingType(scroller) == "Slider") { std::string valueStr; - if (getSettingValueType(scroller) == "Float") + std::string valueType = getSettingValueType(scroller); + if (valueType == "Float" || valueType == "Integer") { float value = pos / float(scroller->getScrollRange()-1); float min,max; getSettingMinMax(scroller, min, max); value = min + (max-min) * value; - Settings::Manager::setFloat(getSettingName(scroller), getSettingCategory(scroller), value); + if (valueType == "Float") + Settings::Manager::setFloat(getSettingName(scroller), getSettingCategory(scroller), value); + else + Settings::Manager::setInt(getSettingName(scroller), getSettingCategory(scroller), (int)value); valueStr = MyGUI::utility::toString(int(value)); } else diff --git a/apps/openmw/mwgui/spellicons.cpp b/apps/openmw/mwgui/spellicons.cpp index c1bd421db..51508cd06 100644 --- a/apps/openmw/mwgui/spellicons.cpp +++ b/apps/openmw/mwgui/spellicons.cpp @@ -79,14 +79,11 @@ namespace MWGui static const float fadeTime = MWBase::Environment::get().getWorld()->getStore().get().find("fMagicStartIconBlink")->mValue.getFloat(); std::vector& effectInfos = effectInfoPair.second; - bool addNewLine = true; + bool addNewLine = false; for (const MagicEffectInfo& effectInfo : effectInfos) { if (addNewLine) - { sourcesDescription += "\n"; - addNewLine = false; - } // if at least one of the effect sources is permanent, the effect will never wear off if (effectInfo.mPermanent) @@ -161,6 +158,8 @@ namespace MWGui sourcesDescription += MWGui::ToolTips::toString(duration) + "s"; } } + + addNewLine = true; } if (remainingDuration > 0.f) diff --git a/apps/openmw/mwgui/trainingwindow.cpp b/apps/openmw/mwgui/trainingwindow.cpp index a5143513c..913ea1024 100644 --- a/apps/openmw/mwgui/trainingwindow.cpp +++ b/apps/openmw/mwgui/trainingwindow.cpp @@ -169,8 +169,7 @@ namespace MWGui npcStats.setGoldPool(npcStats.getGoldPool() + price); // advance time - MWBase::Environment::get().getMechanicsManager()->rest(false); - MWBase::Environment::get().getMechanicsManager()->rest(false); + MWBase::Environment::get().getMechanicsManager()->rest(2, false); /* Start of tes3mp change (major) diff --git a/apps/openmw/mwgui/travelwindow.cpp b/apps/openmw/mwgui/travelwindow.cpp index f0ed4c317..680d26bd5 100644 --- a/apps/openmw/mwgui/travelwindow.cpp +++ b/apps/openmw/mwgui/travelwindow.cpp @@ -174,10 +174,7 @@ namespace MWGui ESM::Position playerPos = player.getRefData().getPosition(); float d = (osg::Vec3f(pos.pos[0], pos.pos[1], 0) - osg::Vec3f(playerPos.pos[0], playerPos.pos[1], 0)).length(); int hours = static_cast(d /MWBase::Environment::get().getWorld()->getStore().get().find("fTravelTimeMult")->mValue.getFloat()); - for(int i = 0;i < hours;i++) - { - MWBase::Environment::get().getMechanicsManager ()->rest (true); - } + MWBase::Environment::get().getMechanicsManager()->rest(hours, true); /* Start of tes3mp change (major) diff --git a/apps/openmw/mwgui/waitdialog.cpp b/apps/openmw/mwgui/waitdialog.cpp index 847f19acf..138928905 100644 --- a/apps/openmw/mwgui/waitdialog.cpp +++ b/apps/openmw/mwgui/waitdialog.cpp @@ -271,7 +271,7 @@ namespace MWGui void WaitDialog::onWaitingProgressChanged(int cur, int total) { mProgressBar.setProgress(cur, total); - MWBase::Environment::get().getMechanicsManager()->rest(mSleeping); + MWBase::Environment::get().getMechanicsManager()->rest(1, mSleeping); /* Start of tes3mp change (major) diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index b91379d21..2bd30109e 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -1070,9 +1070,6 @@ namespace MWGui updateMap(); - if (!mMap->isVisible()) - mMap->onFrame(frameDuration); - mHud->onFrame(frameDuration); mDebugWindow->onFrame(frameDuration); diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index e4cfe73a8..882217e1f 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -208,8 +208,6 @@ namespace MWInput void InputManager::handleGuiArrowKey(int action) { - // Temporary shut-down of this function until deemed necessary. - return; if (SDL_IsTextInputActive()) return; @@ -414,7 +412,8 @@ namespace MWInput case A_MoveRight: case A_MoveForward: case A_MoveBackward: - handleGuiArrowKey(action); + // Temporary shut-down of this function until deemed necessary. + //handleGuiArrowKey(action); break; case A_Journal: toggleJournal (); @@ -1905,6 +1904,17 @@ namespace MWInput MWBase::Environment::get().getWindowManager ()->notifyInputActionBound (); return; } + + // Disallow binding reserved keys + if (key == SDL_SCANCODE_F3 || key == SDL_SCANCODE_F4 || key == SDL_SCANCODE_F10 || key == SDL_SCANCODE_F11) + return; + + #ifndef __APPLE__ + // Disallow binding Windows/Meta keys + if (key == SDL_SCANCODE_LGUI || key == SDL_SCANCODE_RGUI) + return; + #endif + if(!mDetectingKeyboard) return; diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index e0f586311..442f2fc98 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -147,23 +147,45 @@ void getRestorationPerHourOfSleep (const MWWorld::Ptr& ptr, float& health, float MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats (ptr); const MWWorld::Store& settings = MWBase::Environment::get().getWorld()->getStore().get(); - bool stunted = stats.getMagicEffects ().get(ESM::MagicEffect::StuntedMagicka).getMagnitude() > 0; int endurance = stats.getAttribute (ESM::Attribute::Endurance).getModified (); - health = 0.1f * endurance; - magicka = 0; - if (!stunted) - { - float fRestMagicMult = settings.find("fRestMagicMult")->mValue.getFloat (); - magicka = fRestMagicMult * stats.getAttribute(ESM::Attribute::Intelligence).getModified(); - } + float fRestMagicMult = settings.find("fRestMagicMult")->mValue.getFloat (); + magicka = fRestMagicMult * stats.getAttribute(ESM::Attribute::Intelligence).getModified(); } } namespace MWMechanics { + class GetStuntedMagickaDuration : public MWMechanics::EffectSourceVisitor + { + public: + float mRemainingTime; + + GetStuntedMagickaDuration(const MWWorld::Ptr& actor) + : mRemainingTime(0.f){} + + virtual void visit (MWMechanics::EffectKey key, + const std::string& sourceName, const std::string& sourceId, int casterActorId, + float magnitude, float remainingTime = -1, float totalTime = -1) + { + if (mRemainingTime == -1) return; + + if (key.mId == ESM::MagicEffect::StuntedMagicka) + { + if (totalTime == -1) + { + mRemainingTime = -1; + return; + } + + if (remainingTime > mRemainingTime) + mRemainingTime = remainingTime; + } + } + }; + class SoulTrap : public MWMechanics::EffectSourceVisitor { MWWorld::Ptr mCreature; @@ -611,7 +633,7 @@ namespace MWMechanics creatureStats.setMagicka(magicka); } - void Actors::restoreDynamicStats (const MWWorld::Ptr& ptr, bool sleep) + void Actors::restoreDynamicStats (const MWWorld::Ptr& ptr, double hours, bool sleep) { MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats (ptr); if (stats.isDead()) @@ -625,12 +647,36 @@ namespace MWMechanics getRestorationPerHourOfSleep(ptr, health, magicka); DynamicStat stat = stats.getHealth(); - stat.setCurrent(stat.getCurrent() + health); + stat.setCurrent(stat.getCurrent() + health * hours); stats.setHealth(stat); - stat = stats.getMagicka(); - stat.setCurrent(stat.getCurrent() + magicka); - stats.setMagicka(stat); + double restoreHours = hours; + bool stunted = stats.getMagicEffects ().get(ESM::MagicEffect::StuntedMagicka).getMagnitude() > 0; + if (stunted) + { + // Stunted Magicka effect should be taken into account. + GetStuntedMagickaDuration visitor(ptr); + stats.getActiveSpells().visitEffectSources(visitor); + stats.getSpells().visitEffectSources(visitor); + if (ptr.getClass().hasInventoryStore(ptr)) + ptr.getClass().getInventoryStore(ptr).visitEffectSources(visitor); + + // Take a maximum remaining duration of Stunted Magicka effects (-1 is a constant one) in game hours. + if (visitor.mRemainingTime > 0) + { + double timeScale = MWBase::Environment::get().getWorld()->getTimeScaleFactor(); + restoreHours = std::max(0.0, hours - visitor.mRemainingTime * timeScale / 3600.f); + } + else if (visitor.mRemainingTime == -1) + restoreHours = 0; + } + + if (restoreHours > 0) + { + stat = stats.getMagicka(); + stat.setCurrent(stat.getCurrent() + magicka * restoreHours); + stats.setMagicka(stat); + } } // Current fatigue can be above base value due to a fortify effect. @@ -653,7 +699,7 @@ namespace MWMechanics float x = fFatigueReturnBase + fFatigueReturnMult * (1 - normalizedEncumbrance); x *= fEndFatigueMult * endurance; - fatigue.setCurrent (fatigue.getCurrent() + 3600 * x); + fatigue.setCurrent (fatigue.getCurrent() + 3600 * x * hours); stats.setFatigue (fatigue); } @@ -1925,9 +1971,9 @@ namespace MWMechanics } } - void Actors::rest(bool sleep) + void Actors::rest(double hours, bool sleep) { - float duration = 3600.f / MWBase::Environment::get().getWorld()->getTimeScaleFactor(); + float duration = hours * 3600.f / MWBase::Environment::get().getWorld()->getTimeScaleFactor(); const MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); const osg::Vec3f playerPos = player.getRefData().getPosition().asVec3(); @@ -1937,7 +1983,7 @@ namespace MWMechanics continue; if (!sleep || iter->first == player) - restoreDynamicStats(iter->first, sleep); + restoreDynamicStats(iter->first, hours, sleep); if ((!iter->first.getRefData().getBaseNode()) || (playerPos - iter->first.getRefData().getPosition().asVec3()).length2() > mActorsProcessingRange*mActorsProcessingRange) @@ -2049,11 +2095,12 @@ namespace MWMechanics getRestorationPerHourOfSleep(ptr, healthPerHour, magickaPerHour); CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); + bool stunted = stats.getMagicEffects ().get(ESM::MagicEffect::StuntedMagicka).getMagnitude() > 0; float healthHours = healthPerHour > 0 ? (stats.getHealth().getModified() - stats.getHealth().getCurrent()) / healthPerHour : 1.0f; - float magickaHours = magickaPerHour > 0 + float magickaHours = magickaPerHour > 0 && !stunted ? (stats.getMagicka().getModified() - stats.getMagicka().getCurrent()) / magickaPerHour : 1.0f; diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index 0cb786e2a..42147b63b 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -121,13 +121,13 @@ namespace MWMechanics void updateHeadTracking(const MWWorld::Ptr& actor, const MWWorld::Ptr& targetActor, MWWorld::Ptr& headTrackTarget, float& sqrHeadTrackDistance); - void rest(bool sleep); - ///< Update actors while the player is waiting or sleeping. This should be called every hour. + void rest(double hours, bool sleep); + ///< Update actors while the player is waiting or sleeping. void updateSneaking(CharacterController* ctrl, float duration); ///< Update the sneaking indicator state according to the given player character controller. - void restoreDynamicStats(const MWWorld::Ptr& actor, bool sleep); + void restoreDynamicStats(const MWWorld::Ptr& actor, double hours, bool sleep); int getHoursToRest(const MWWorld::Ptr& ptr) const; ///< Calculate how many hours the given actor needs to rest in order to be fully healed diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 405f36767..db2057df3 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -13,6 +13,8 @@ #include "../mwworld/esmstore.hpp" #include "../mwworld/cellstore.hpp" +#include "../mwphysics/collisiontype.hpp" + #include "pathgrid.hpp" #include "creaturestats.hpp" #include "steering.hpp" @@ -45,6 +47,16 @@ namespace MWMechanics std::string("idle9"), }; + namespace + { + inline int getCountBeforeReset(const MWWorld::ConstPtr& actor) + { + if (actor.getClass().isPureWaterCreature(actor) || actor.getClass().isPureFlyingCreature(actor)) + return 1; + return COUNT_BEFORE_RESET; + } + } + AiWander::AiWander(int distance, int duration, int timeOfDay, const std::vector& idle, bool repeat): mDistance(distance), mDuration(duration), mRemainingDuration(duration), mTimeOfDay(timeOfDay), mIdle(idle), mRepeat(repeat), mStoredInitialActorPosition(false), mInitialActorPosition(osg::Vec3f(0, 0, 0)), @@ -298,7 +310,8 @@ namespace MWMechanics const auto currentPosition = actor.getRefData().getPosition().asVec3(); std::size_t attempts = 10; // If a unit can't wander out of water, don't want to hang here - bool isWaterCreature = actor.getClass().isPureWaterCreature(actor); + const bool isWaterCreature = actor.getClass().isPureWaterCreature(actor); + const bool isFlyingCreature = actor.getClass().isPureFlyingCreature(actor); do { // Determine a random location within radius of original position const float wanderRadius = (0.2f + Misc::Rng::rollClosedProbability() * 0.8f) * wanderDistance; @@ -309,22 +322,31 @@ namespace MWMechanics mDestination = osg::Vec3f(destinationX, destinationY, destinationZ); // Check if land creature will walk onto water or if water creature will swim onto land - if ((!isWaterCreature && !destinationIsAtWater(actor, mDestination)) || - (isWaterCreature && !destinationThroughGround(currentPosition, mDestination))) + if (!isWaterCreature && destinationIsAtWater(actor, mDestination)) + continue; + + if ((isWaterCreature || isFlyingCreature) && destinationThroughGround(currentPosition, mDestination)) + continue; + + if (isWaterCreature || isFlyingCreature) + { + mPathFinder.buildStraightPath(mDestination); + } + else { const osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getPathfindingHalfExtents(actor); - mPathFinder.buildPath(actor, currentPosition, mDestination, actor.getCell(), - getPathGridGraph(actor.getCell()), halfExtents, getNavigatorFlags(actor)); - mPathFinder.addPointToPath(mDestination); + mPathFinder.buildPathByNavMesh(actor, currentPosition, mDestination, halfExtents, + getNavigatorFlags(actor)); + } - if (mPathFinder.isPathConstructed()) - { - storage.setState(AiWanderStorage::Wander_Walking, true); - mHasDestination = true; - mUsePathgrid = false; - } - return; + if (mPathFinder.isPathConstructed()) + { + storage.setState(AiWanderStorage::Wander_Walking, true); + mHasDestination = true; + mUsePathgrid = false; } + + break; } while (--attempts); } @@ -342,8 +364,10 @@ namespace MWMechanics * Returns true if the start to end point travels through a collision point (land). */ bool AiWander::destinationThroughGround(const osg::Vec3f& startPoint, const osg::Vec3f& destination) { + const int mask = MWPhysics::CollisionType_World | MWPhysics::CollisionType_HeightMap | MWPhysics::CollisionType_Door; return MWBase::Environment::get().getWorld()->castRay(startPoint.x(), startPoint.y(), startPoint.z(), - destination.x(), destination.y(), destination.z()); + destination.x(), destination.y(), destination.z(), + mask); } void AiWander::completeManualWalking(const MWWorld::Ptr &actor, AiWanderStorage &storage) { @@ -479,7 +503,7 @@ namespace MWMechanics } // if stuck for sufficiently long, act like current location was the destination - if (storage.mStuckCount >= COUNT_BEFORE_RESET) // something has gone wrong, reset + if (storage.mStuckCount >= getCountBeforeReset(actor)) // something has gone wrong, reset { mObstacleCheck.clear(); stopWalking(actor, storage); diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 43144d87e..84e160f5e 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1720,20 +1720,22 @@ bool CharacterController::updateWeaponState(CharacterState& idle) End of tes3mp change (major) */ { - if (isWeapon) + if (Settings::Manager::getBool("best attack", "Game")) { - if (Settings::Manager::getBool("best attack", "Game")) + if (isWeapon) { MWWorld::ConstContainerStoreIterator weapon = mPtr.getClass().getInventoryStore(mPtr).getSlot(MWWorld::InventoryStore::Slot_CarriedRight); mAttackType = getBestAttack(weapon->get()->mBase); } else - setAttackTypeBasedOnMovement(); + { + // There is no "best attack" for Hand-to-Hand + setAttackTypeRandomly(mAttackType); + } } else { - // There is no "best attack" for Hand-to-Hand - setAttackTypeRandomly(mAttackType); + setAttackTypeBasedOnMovement(); } } // else if (mPtr != getPlayer()) use mAttackType set by AiCombat @@ -2697,6 +2699,13 @@ void CharacterController::forceStateUpdate() return; clearAnimQueue(); + // Make sure we canceled the current attack or spellcasting, + // because we disabled attack animations anyway. + mCastingManualSpell = false; + mAttackingOrSpell = false; + if (mUpperBodyState != UpperCharState_Nothing) + mUpperBodyState = UpperCharState_WeapEquiped; + refreshCurrentAnims(mIdleState, mMovementState, mJumpState, true); if(mDeathState != CharState_None) diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index db45aca4d..3af98447c 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -303,7 +303,9 @@ namespace MWMechanics void MechanicsManager::advanceTime (float duration) { // Uses ingame time, but scaled to real time - duration /= MWBase::Environment::get().getWorld()->getTimeScaleFactor(); + const float timeScaleFactor = MWBase::Environment::get().getWorld()->getTimeScaleFactor(); + if (timeScaleFactor != 0.0f) + duration /= timeScaleFactor; MWWorld::Ptr player = getPlayer(); player.getClass().getInventoryStore(player).rechargeItems(duration); } @@ -491,17 +493,17 @@ namespace MWMechanics return mActors.isSneaking(ptr); } - void MechanicsManager::rest(bool sleep) + void MechanicsManager::rest(double hours, bool sleep) { if (sleep) - MWBase::Environment::get().getWorld()->rest(); + MWBase::Environment::get().getWorld()->rest(hours); - mActors.rest(sleep); + mActors.rest(hours, sleep); } - void MechanicsManager::restoreDynamicStats(MWWorld::Ptr actor, bool sleep) + void MechanicsManager::restoreDynamicStats(MWWorld::Ptr actor, double hours, bool sleep) { - mActors.restoreDynamicStats(actor, sleep); + mActors.restoreDynamicStats(actor, hours, sleep); } int MechanicsManager::getHoursToRest() const diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp index 47e7c9200..e7d2e1834 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp @@ -95,9 +95,9 @@ namespace MWMechanics virtual void setPlayerClass (const ESM::Class& class_) override; ///< Set player class to custom class. - virtual void restoreDynamicStats(MWWorld::Ptr actor, bool sleep) override; + virtual void restoreDynamicStats(MWWorld::Ptr actor, double hours, bool sleep) override; - virtual void rest(bool sleep) override; + virtual void rest(double hours, bool sleep) override; ///< If the player is sleeping or waiting, this should be called every hour. /// @param sleep is the player sleeping or waiting? diff --git a/apps/openmw/mwmechanics/obstacle.cpp b/apps/openmw/mwmechanics/obstacle.cpp index 3d8fe7919..63167e302 100644 --- a/apps/openmw/mwmechanics/obstacle.cpp +++ b/apps/openmw/mwmechanics/obstacle.cpp @@ -2,6 +2,8 @@ #include +#include "../mwbase/world.hpp" +#include "../mwbase/environment.hpp" #include "../mwworld/class.hpp" #include "../mwworld/cellstore.hpp" @@ -121,17 +123,19 @@ namespace MWMechanics * u = how long to move sideways * */ - void ObstacleCheck::update(const MWWorld::Ptr& actor, float duration, float scaleMinimumDistance) + void ObstacleCheck::update(const MWWorld::Ptr& actor, float duration) { - const MWWorld::Class& cls = actor.getClass(); - ESM::Position pos = actor.getRefData().getPosition(); + const ESM::Position pos = actor.getRefData().getPosition(); - if(mDistSameSpot == -1) - mDistSameSpot = DIST_SAME_SPOT * cls.getSpeed(actor) * scaleMinimumDistance; - - float distSameSpot = mDistSameSpot * duration; + if (mDistSameSpot == -1) + { + const osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getHalfExtents(actor); + mDistSameSpot = DIST_SAME_SPOT * actor.getClass().getSpeed(actor) + 1.2 * std::max(halfExtents.x(), halfExtents.y()); + } - bool samePosition = (osg::Vec2f(pos.pos[0], pos.pos[1]) - osg::Vec2f(mPrevX, mPrevY)).length2() < distSameSpot * distSameSpot; + const float distSameSpot = mDistSameSpot * duration; + const float squaredMovedDistance = (osg::Vec2f(pos.pos[0], pos.pos[1]) - osg::Vec2f(mPrevX, mPrevY)).length2(); + const bool samePosition = squaredMovedDistance < distSameSpot * distSameSpot; // update position mPrevX = pos.pos[0]; diff --git a/apps/openmw/mwmechanics/obstacle.hpp b/apps/openmw/mwmechanics/obstacle.hpp index d7e582f8c..46c1bc83d 100644 --- a/apps/openmw/mwmechanics/obstacle.hpp +++ b/apps/openmw/mwmechanics/obstacle.hpp @@ -30,7 +30,7 @@ namespace MWMechanics bool isEvading() const; // Updates internal state, call each frame for moving actor - void update(const MWWorld::Ptr& actor, float duration, float scaleMinimumDistance = 1.0f); + void update(const MWWorld::Ptr& actor, float duration); // change direction to try to fix "stuck" actor void takeEvasiveAction(MWMechanics::Movement& actorMovement) const; diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index e0285be92..db6b8d686 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -269,6 +269,13 @@ namespace MWMechanics mPath.pop_front(); } + void PathFinder::buildStraightPath(const osg::Vec3f& endPoint) + { + mPath.clear(); + mPath.push_back(endPoint); + mConstructed = true; + } + void PathFinder::buildPathByPathgrid(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph) { @@ -280,6 +287,16 @@ namespace MWMechanics mConstructed = true; } + void PathFinder::buildPathByNavMesh(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, + const osg::Vec3f& endPoint, const osg::Vec3f& halfExtents, const DetourNavigator::Flags flags) + { + mPath.clear(); + + buildPathByNavigatorImpl(actor, startPoint, endPoint, halfExtents, flags, std::back_inserter(mPath)); + + mConstructed = true; + } + void PathFinder::buildPath(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph, const osg::Vec3f& halfExtents, const DetourNavigator::Flags flags) diff --git a/apps/openmw/mwmechanics/pathfinding.hpp b/apps/openmw/mwmechanics/pathfinding.hpp index 8a5b8338a..1dc85c5e5 100644 --- a/apps/openmw/mwmechanics/pathfinding.hpp +++ b/apps/openmw/mwmechanics/pathfinding.hpp @@ -72,9 +72,14 @@ namespace MWMechanics mCell = nullptr; } + void buildStraightPath(const osg::Vec3f& endPoint); + void buildPathByPathgrid(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph); + void buildPathByNavMesh(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, + const osg::Vec3f& endPoint, const osg::Vec3f& halfExtents, const DetourNavigator::Flags flags); + void buildPath(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph, const osg::Vec3f& halfExtents, const DetourNavigator::Flags flags); diff --git a/apps/openmw/mwmechanics/spellpriority.cpp b/apps/openmw/mwmechanics/spellpriority.cpp index 79606fda6..6cda51ac8 100644 --- a/apps/openmw/mwmechanics/spellpriority.cpp +++ b/apps/openmw/mwmechanics/spellpriority.cpp @@ -170,7 +170,7 @@ namespace MWMechanics float rating = rateEffects(enchantment->mEffects, actor, enemy); - rating *= 2; // prefer rechargable magic items over spells + rating *= 1.25f; // prefer rechargable magic items over spells return rating; } diff --git a/apps/openmw/mwmechanics/weaponpriority.cpp b/apps/openmw/mwmechanics/weaponpriority.cpp index 66fbbcdcc..c02729341 100644 --- a/apps/openmw/mwmechanics/weaponpriority.cpp +++ b/apps/openmw/mwmechanics/weaponpriority.cpp @@ -127,7 +127,9 @@ namespace MWMechanics value = ref->mBase->mData.mCombat; } - rating *= getHitChance(actor, enemy, value) / 100.f; + // Take hit chance in account, but do not allow rating become negative. + float chance = getHitChance(actor, enemy, value) / 100.f; + rating *= std::min(1.f, std::max(0.01f, chance)); if (weapon->mData.mType < ESM::Weapon::Arrow) rating *= weapon->mData.mSpeed; diff --git a/apps/openmw/mwrender/actoranimation.cpp b/apps/openmw/mwrender/actoranimation.cpp index 68b4c2ac8..42f108355 100644 --- a/apps/openmw/mwrender/actoranimation.cpp +++ b/apps/openmw/mwrender/actoranimation.cpp @@ -242,7 +242,8 @@ void ActorAnimation::updateHolsteredWeapon(bool showHolsteredWeapons) { osg::Vec4f glowColor = getEnchantmentColor(*weapon); mScabbard = getWeaponPart(mesh, boneName, isEnchanted, &glowColor); - resetControllers(mScabbard->getNode()); + if (mScabbard) + resetControllers(mScabbard->getNode()); } return; diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index c21cab6c9..8ec75b1ce 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1798,17 +1798,13 @@ namespace MWRender material->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,alpha)); material->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1)); stateset->setAttributeAndModes(material, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); - + stateset->addUniform(new osg::Uniform("colorMode", 0), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); mObjectRoot->setStateSet(stateset); - - mResourceSystem->getSceneManager()->recreateShaders(mObjectRoot); } } else { mObjectRoot->setStateSet(nullptr); - - mResourceSystem->getSceneManager()->recreateShaders(mObjectRoot); } setRenderBin(); diff --git a/apps/openmw/mwrender/globalmap.cpp b/apps/openmw/mwrender/globalmap.cpp index ffff45e25..adce81ff8 100644 --- a/apps/openmw/mwrender/globalmap.cpp +++ b/apps/openmw/mwrender/globalmap.cpp @@ -329,7 +329,7 @@ namespace MWRender camera->setViewMatrix(osg::Matrix::identity()); camera->setProjectionMatrix(osg::Matrix::identity()); camera->setProjectionResizePolicy(osg::Camera::FIXED); - camera->setRenderOrder(osg::Camera::PRE_RENDER); + camera->setRenderOrder(osg::Camera::PRE_RENDER, 1); // Make sure the global map is rendered after the local map y = mHeight - y - height; // convert top-left origin to bottom-left camera->setViewport(x, y, width, height); diff --git a/apps/openmw/mwrender/landmanager.cpp b/apps/openmw/mwrender/landmanager.cpp index e3b19ca47..560c1ba72 100644 --- a/apps/openmw/mwrender/landmanager.cpp +++ b/apps/openmw/mwrender/landmanager.cpp @@ -12,16 +12,15 @@ namespace MWRender { LandManager::LandManager(int loadFlags) - : ResourceManager(nullptr) + : GenericResourceManager >(nullptr) , mLoadFlags(loadFlags) { + mCache = new CacheType; } osg::ref_ptr LandManager::getLand(int x, int y) { - std::string idstr = std::to_string(x) + " " + std::to_string(y); - - osg::ref_ptr obj = mCache->getRefFromObjectCache(idstr); + osg::ref_ptr obj = mCache->getRefFromObjectCache(std::make_pair(x,y)); if (obj) return static_cast(obj.get()); else @@ -30,7 +29,7 @@ osg::ref_ptr LandManager::getLand(int x, int y) if (!land) return nullptr; osg::ref_ptr landObj (new ESMTerrain::LandObject(land, mLoadFlags)); - mCache->addEntryToObjectCache(idstr, landObj.get()); + mCache->addEntryToObjectCache(std::make_pair(x,y), landObj.get()); return landObj; } } diff --git a/apps/openmw/mwrender/landmanager.hpp b/apps/openmw/mwrender/landmanager.hpp index 81253bba3..0d37097d8 100644 --- a/apps/openmw/mwrender/landmanager.hpp +++ b/apps/openmw/mwrender/landmanager.hpp @@ -3,6 +3,7 @@ #include +#include #include #include @@ -14,7 +15,7 @@ namespace ESM namespace MWRender { - class LandManager : public Resource::ResourceManager + class LandManager : public Resource::GenericResourceManager > { public: LandManager(int loadFlags); diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index 1650dc92c..6c8dfec60 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -613,7 +613,8 @@ void LocalMap::updatePlayer (const osg::Vec3f& position, const osg::Quat& orient if (!segment.mFogOfWarImage || !segment.mMapTexture) continue; - unsigned char* data = segment.mFogOfWarImage->data(); + uint32_t* data = (uint32_t*)segment.mFogOfWarImage->data(); + bool changed = false; for (int texV = 0; texV> 24); alpha = std::min( alpha, (uint8_t) (std::max(0.f, std::min(1.f, (sqrDist/sqrExploreRadius)))*255) ); - *(uint32_t*)data = (uint32_t) (alpha << 24); + uint32_t val = (uint32_t) (alpha << 24); + if ( *data != val) + { + *data = val; + changed = true; + } - data += 4; + ++data; } } - segment.mHasFogState = true; - segment.mFogOfWarImage->dirty(); + if (changed) + { + segment.mHasFogState = true; + segment.mFogOfWarImage->dirty(); + } } } } diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index f1ebc80df..5c078e824 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -54,10 +54,8 @@ std::string getVampireHead(const std::string& race, bool female) if (sVampireMapping.find(thisCombination) == sVampireMapping.end()) { const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); - const MWWorld::Store &partStore = store.get(); - for(MWWorld::Store::iterator it = partStore.begin(); it != partStore.end(); ++it) + for (const ESM::BodyPart& bodypart : store.get()) { - const ESM::BodyPart& bodypart = *it; if (!bodypart.mData.mVampire) continue; if (bodypart.mData.mType != ESM::BodyPart::MT_Skin) @@ -68,12 +66,11 @@ std::string getVampireHead(const std::string& race, bool female) continue; if (!Misc::StringUtils::ciEqual(bodypart.mRace, race)) continue; - sVampireMapping[thisCombination] = &*it; + sVampireMapping[thisCombination] = &bodypart; } } - if (sVampireMapping.find(thisCombination) == sVampireMapping.end()) - sVampireMapping[thisCombination] = nullptr; + sVampireMapping.emplace(thisCombination, nullptr); const ESM::BodyPart* bodyPart = sVampireMapping[thisCombination]; if (!bodyPart) @@ -238,6 +235,18 @@ void HeadAnimationTime::setBlinkStop(float value) // ---------------------------------------------------- +NpcAnimation::NpcType NpcAnimation::getNpcType() +{ + const MWWorld::Class &cls = mPtr.getClass(); + NpcAnimation::NpcType curType = Type_Normal; + if (cls.getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Vampirism).getMagnitude() > 0) + curType = Type_Vampire; + if (cls.getNpcStats(mPtr).isWerewolf()) + curType = Type_Werewolf; + + return curType; +} + static NpcAnimation::PartBoneMap createPartListMap() { NpcAnimation::PartBoneMap result; @@ -283,7 +292,7 @@ NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, osg::ref_ptr par mViewMode(viewMode), mShowWeapons(false), mShowCarriedLeft(true), - mNpcType(Type_Normal), + mNpcType(getNpcType()), mFirstPersonFieldOfView(firstPersonFieldOfView), mSoundsDisabled(disableSounds), mAccurateAiming(false), @@ -431,41 +440,39 @@ void NpcAnimation::updateNpcBase() const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); const ESM::Race *race = store.get().find(mNpc->mRace); - bool isWerewolf = (mNpcType == Type_Werewolf); - bool isVampire = (mNpcType == Type_Vampire); + NpcType curType = getNpcType(); + bool isWerewolf = (curType == Type_Werewolf); + bool isVampire = (curType == Type_Vampire); bool isFemale = !mNpc->isMale(); - if (isWerewolf) + mHeadModel.clear(); + mHairModel.clear(); + + std::string headName = isWerewolf ? "WerewolfHead" : mNpc->mHead; + std::string hairName = isWerewolf ? "WerewolfHair" : mNpc->mHair; + + if (!headName.empty()) { - mHeadModel = "meshes\\" + store.get().find("WerewolfHead")->mModel; - mHairModel = "meshes\\" + store.get().find("WerewolfHair")->mModel; + const ESM::BodyPart* bp = store.get().search(headName); + if (bp) + mHeadModel = "meshes\\" + bp->mModel; + else + Log(Debug::Warning) << "Warning: Failed to load body part '" << headName << "'"; } - else - { - mHeadModel = ""; - const std::string& vampireHead = getVampireHead(mNpc->mRace, isFemale); - if (isVampire && !vampireHead.empty()) - mHeadModel = vampireHead; - else if (!mNpc->mHead.empty()) - { - const ESM::BodyPart* bp = store.get().search(mNpc->mHead); - if (bp) - mHeadModel = "meshes\\" + bp->mModel; - else - Log(Debug::Warning) << "Warning: Failed to load body part '" << mNpc->mHead << "'"; - } - mHairModel = ""; - if (!mNpc->mHair.empty()) - { - const ESM::BodyPart* bp = store.get().search(mNpc->mHair); - if (bp) - mHairModel = "meshes\\" + bp->mModel; - else - Log(Debug::Warning) << "Warning: Failed to load body part '" << mNpc->mHair << "'"; - } + if (!hairName.empty()) + { + const ESM::BodyPart* bp = store.get().search(hairName); + if (bp) + mHairModel = "meshes\\" + bp->mModel; + else + Log(Debug::Warning) << "Warning: Failed to load body part '" << hairName << "'"; } + const std::string& vampireHead = getVampireHead(mNpc->mRace, isFemale); + if (!isWerewolf && isVampire && !vampireHead.empty()) + mHeadModel = vampireHead; + bool is1stPerson = mViewMode == VM_FirstPerson; bool isBeast = (race->mData.mFlags & ESM::Race::Beast) != 0; @@ -473,7 +480,7 @@ void NpcAnimation::updateNpcBase() defaultSkeleton = Misc::ResourceHelpers::correctActorModelPath(defaultSkeleton, mResourceSystem->getVFS()); std::string smodel = defaultSkeleton; - if (!is1stPerson && !isWerewolf & !mNpc->mModel.empty()) + if (!is1stPerson && !isWerewolf && !mNpc->mModel.empty()) smodel = Misc::ResourceHelpers::correctActorModelPath("meshes\\" + mNpc->mModel, mResourceSystem->getVFS()); setObjectRoot(smodel, true, true, false); @@ -517,14 +524,7 @@ void NpcAnimation::updateParts() if (!mObjectRoot.get()) return; - const MWWorld::Class &cls = mPtr.getClass(); - - NpcType curType = Type_Normal; - if (cls.getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Vampirism).getMagnitude() > 0) - curType = Type_Vampire; - if (cls.getNpcStats(mPtr).isWerewolf()) - curType = Type_Werewolf; - + NpcType curType = getNpcType(); if (curType != mNpcType) { mNpcType = curType; @@ -632,7 +632,7 @@ void NpcAnimation::updateParts() showWeapons(mShowWeapons); showCarriedLeft(mShowCarriedLeft); - bool isWerewolf = (mNpcType == Type_Werewolf); + bool isWerewolf = (getNpcType() == Type_Werewolf); std::string race = (isWerewolf ? "werewolf" : Misc::StringUtils::lowerCase(mNpc->mRace)); const std::vector &parts = getBodyParts(race, !mNpc->isMale(), mViewMode == VM_FirstPerson, isWerewolf); @@ -649,9 +649,6 @@ void NpcAnimation::updateParts() if (wasArrowAttached) attachArrow(); - - if (mAlpha != 1.f) - mResourceSystem->getSceneManager()->recreateShaders(mObjectRoot); } @@ -730,10 +727,7 @@ void NpcAnimation::removePartGroup(int group) bool NpcAnimation::isFirstPersonPart(const ESM::BodyPart* bodypart) { - return (bodypart->mId.size() >= 3) - && bodypart->mId[bodypart->mId.size()-3] == '1' - && bodypart->mId[bodypart->mId.size()-2] == 's' - && bodypart->mId[bodypart->mId.size()-1] == 't'; + return bodypart->mId.size() >= 3 && bodypart->mId.substr(bodypart->mId.size()-3, 3) == "1st"; } bool NpcAnimation::isFemalePart(const ESM::BodyPart* bodypart) @@ -793,16 +787,16 @@ bool NpcAnimation::addOrReplaceIndividualPart(ESM::PartReferenceType type, int g osg::Object* obj = node->getUserDataContainer()->getUserObject(i); if (NifOsg::TextKeyMapHolder* keys = dynamic_cast(obj)) { - for (NifOsg::TextKeyMap::const_iterator it = keys->mTextKeys.begin(); it != keys->mTextKeys.end(); ++it) + for (const auto &key : keys->mTextKeys) { - if (Misc::StringUtils::ciEqual(it->second, "talk: start")) - mHeadAnimationTime->setTalkStart(it->first); - if (Misc::StringUtils::ciEqual(it->second, "talk: stop")) - mHeadAnimationTime->setTalkStop(it->first); - if (Misc::StringUtils::ciEqual(it->second, "blink: start")) - mHeadAnimationTime->setBlinkStart(it->first); - if (Misc::StringUtils::ciEqual(it->second, "blink: stop")) - mHeadAnimationTime->setBlinkStop(it->first); + if (Misc::StringUtils::ciEqual(key.second, "talk: start")) + mHeadAnimationTime->setTalkStart(key.first); + if (Misc::StringUtils::ciEqual(key.second, "talk: stop")) + mHeadAnimationTime->setTalkStop(key.first); + if (Misc::StringUtils::ciEqual(key.second, "blink: start")) + mHeadAnimationTime->setBlinkStart(key.first); + if (Misc::StringUtils::ciEqual(key.second, "blink: stop")) + mHeadAnimationTime->setBlinkStop(key.first); } break; @@ -828,16 +822,15 @@ void NpcAnimation::addPartGroup(int group, int priority, const std::vector &partStore = store.get(); const char *ext = (mViewMode == VM_FirstPerson) ? ".1st" : ""; - std::vector::const_iterator part(parts.begin()); - for(;part != parts.end();++part) + for(const ESM::PartReference& part : parts) { - const ESM::BodyPart *bodypart = 0; - if(!mNpc->isMale() && !part->mFemale.empty()) + const ESM::BodyPart *bodypart = nullptr; + if(!mNpc->isMale() && !part.mFemale.empty()) { - bodypart = partStore.search(part->mFemale+ext); + bodypart = partStore.search(part.mFemale+ext); if(!bodypart && mViewMode == VM_FirstPerson) { - bodypart = partStore.search(part->mFemale); + bodypart = partStore.search(part.mFemale); if(bodypart && !(bodypart->mData.mPart == ESM::BodyPart::MP_Hand || bodypart->mData.mPart == ESM::BodyPart::MP_Wrist || bodypart->mData.mPart == ESM::BodyPart::MP_Forearm || @@ -845,14 +838,14 @@ void NpcAnimation::addPartGroup(int group, int priority, const std::vectormFemale << "'"; + Log(Debug::Warning) << "Warning: Failed to find body part '" << part.mFemale << "'"; } - if(!bodypart && !part->mMale.empty()) + if(!bodypart && !part.mMale.empty()) { - bodypart = partStore.search(part->mMale+ext); + bodypart = partStore.search(part.mMale+ext); if(!bodypart && mViewMode == VM_FirstPerson) { - bodypart = partStore.search(part->mMale); + bodypart = partStore.search(part.mMale); if(bodypart && !(bodypart->mData.mPart == ESM::BodyPart::MP_Hand || bodypart->mData.mPart == ESM::BodyPart::MP_Wrist || bodypart->mData.mPart == ESM::BodyPart::MP_Forearm || @@ -860,13 +853,13 @@ void NpcAnimation::addPartGroup(int group, int priority, const std::vectormMale << "'"; + Log(Debug::Warning) << "Warning: Failed to find body part '" << part.mMale << "'"; } if(bodypart) - addOrReplaceIndividualPart((ESM::PartReferenceType)part->mPart, group, priority, "meshes\\"+bodypart->mModel, enchantedGlow, glowColor); + addOrReplaceIndividualPart((ESM::PartReferenceType)part.mPart, group, priority, "meshes\\"+bodypart->mModel, enchantedGlow, glowColor); else - reserveIndividualPart((ESM::PartReferenceType)part->mPart, group, priority); + reserveIndividualPart((ESM::PartReferenceType)part.mPart, group, priority); } } @@ -885,7 +878,7 @@ void NpcAnimation::addControllers() osg::MatrixTransform* node = found->second.get(); mFirstPersonNeckController = new NeckController(mObjectRoot.get()); node->addUpdateCallback(mFirstPersonNeckController); - mActiveControllers.insert(std::make_pair(node, mFirstPersonNeckController)); + mActiveControllers.emplace(node, mFirstPersonNeckController); } } else if (mViewMode == VM_Normal) @@ -918,9 +911,6 @@ void NpcAnimation::showWeapons(bool showWeapon) attachArrow(); } } - // Note: we will need to recreate shaders later if we use weapon sheathing anyway, so there is no point to update them here - if (mAlpha != 1.f && !mWeaponSheathing) - mResourceSystem->getSceneManager()->recreateShaders(mObjectRoot); } else { @@ -932,10 +922,6 @@ void NpcAnimation::showWeapons(bool showWeapon) updateHolsteredWeapon(!mShowWeapons); updateQuiver(); - - // Recreate shaders for invisible actors, otherwise sheath nodes will be visible - if (mAlpha != 1.f && mWeaponSheathing) - mResourceSystem->getSceneManager()->recreateShaders(mObjectRoot); } void NpcAnimation::showCarriedLeft(bool show) @@ -953,8 +939,6 @@ void NpcAnimation::showCarriedLeft(bool show) if (iter->getTypeName() == typeid(ESM::Light).name() && mObjectParts[ESM::PRT_Shield]) addExtraLight(mObjectParts[ESM::PRT_Shield]->getNode()->asGroup(), iter->get()->mBase); } - if (mAlpha != 1.f) - mResourceSystem->getSceneManager()->recreateShaders(mObjectRoot); } else removeIndividualPart(ESM::PRT_Shield); @@ -1087,40 +1071,39 @@ const std::vector& NpcAnimation::getBodyParts(const std:: std::vector& parts = sRaceMapping[std::make_pair(race, flags)]; typedef std::multimap BodyPartMapType; - static BodyPartMapType sBodyPartMap; - if(sBodyPartMap.empty()) + static const BodyPartMapType sBodyPartMap = { - sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Neck, ESM::PRT_Neck)); - sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Chest, ESM::PRT_Cuirass)); - sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Groin, ESM::PRT_Groin)); - sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Hand, ESM::PRT_RHand)); - sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Hand, ESM::PRT_LHand)); - sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Wrist, ESM::PRT_RWrist)); - sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Wrist, ESM::PRT_LWrist)); - sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Forearm, ESM::PRT_RForearm)); - sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Forearm, ESM::PRT_LForearm)); - sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Upperarm, ESM::PRT_RUpperarm)); - sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Upperarm, ESM::PRT_LUpperarm)); - sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Foot, ESM::PRT_RFoot)); - sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Foot, ESM::PRT_LFoot)); - sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Ankle, ESM::PRT_RAnkle)); - sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Ankle, ESM::PRT_LAnkle)); - sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Knee, ESM::PRT_RKnee)); - sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Knee, ESM::PRT_LKnee)); - sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Upperleg, ESM::PRT_RLeg)); - sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Upperleg, ESM::PRT_LLeg)); - sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Tail, ESM::PRT_Tail)); - } + {ESM::BodyPart::MP_Neck, ESM::PRT_Neck}, + {ESM::BodyPart::MP_Chest, ESM::PRT_Cuirass}, + {ESM::BodyPart::MP_Groin, ESM::PRT_Groin}, + {ESM::BodyPart::MP_Hand, ESM::PRT_RHand}, + {ESM::BodyPart::MP_Hand, ESM::PRT_LHand}, + {ESM::BodyPart::MP_Wrist, ESM::PRT_RWrist}, + {ESM::BodyPart::MP_Wrist, ESM::PRT_LWrist}, + {ESM::BodyPart::MP_Forearm, ESM::PRT_RForearm}, + {ESM::BodyPart::MP_Forearm, ESM::PRT_LForearm}, + {ESM::BodyPart::MP_Upperarm, ESM::PRT_RUpperarm}, + {ESM::BodyPart::MP_Upperarm, ESM::PRT_LUpperarm}, + {ESM::BodyPart::MP_Foot, ESM::PRT_RFoot}, + {ESM::BodyPart::MP_Foot, ESM::PRT_LFoot}, + {ESM::BodyPart::MP_Ankle, ESM::PRT_RAnkle}, + {ESM::BodyPart::MP_Ankle, ESM::PRT_LAnkle}, + {ESM::BodyPart::MP_Knee, ESM::PRT_RKnee}, + {ESM::BodyPart::MP_Knee, ESM::PRT_LKnee}, + {ESM::BodyPart::MP_Upperleg, ESM::PRT_RLeg}, + {ESM::BodyPart::MP_Upperleg, ESM::PRT_LLeg}, + {ESM::BodyPart::MP_Tail, ESM::PRT_Tail} + }; parts.resize(ESM::PRT_Count, nullptr); + if (werewolf) + return parts; + const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); - const MWWorld::Store &partStore = store.get(); - for(MWWorld::Store::iterator it = partStore.begin(); it != partStore.end(); ++it) + + for(const ESM::BodyPart& bodypart : store.get()) { - if(werewolf) - break; - const ESM::BodyPart& bodypart = *it; if (bodypart.mData.mFlags & ESM::BodyPart::BPF_NotPlayable) continue; if (bodypart.mData.mType != ESM::BodyPart::MT_Skin) diff --git a/apps/openmw/mwrender/npcanimation.hpp b/apps/openmw/mwrender/npcanimation.hpp index 1ec0dfa59..bed07dcdc 100644 --- a/apps/openmw/mwrender/npcanimation.hpp +++ b/apps/openmw/mwrender/npcanimation.hpp @@ -74,6 +74,8 @@ private: void updateNpcBase(); + NpcType getNpcType(); + PartHolderPtr insertBoundedPart(const std::string &model, const std::string &bonename, const std::string &bonefilter, bool enchantedGlow, osg::Vec4f* glowColor=nullptr); diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index f54e423f1..bdefe4c5c 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -314,6 +314,7 @@ namespace MWRender mTerrain->setDefaultViewer(mViewer->getCamera()); mTerrain->setTargetFrameRate(Settings::Manager::getFloat("target framerate", "Cells")); + mTerrain->setWorkQueue(mWorkQueue.get()); mCamera.reset(new Camera(mViewer->getCamera())); @@ -371,8 +372,10 @@ namespace MWRender mNearClip = Settings::Manager::getFloat("near clip", "Camera"); mViewDistance = Settings::Manager::getFloat("viewing distance", "Camera"); - mFieldOfView = Settings::Manager::getFloat("field of view", "Camera"); - mFirstPersonFieldOfView = Settings::Manager::getFloat("first person field of view", "Camera"); + float fov = Settings::Manager::getFloat("field of view", "Camera"); + mFieldOfView = std::min(std::max(1.f, fov), 179.f); + float firstPersonFov = Settings::Manager::getFloat("first person field of view", "Camera"); + mFirstPersonFieldOfView = std::min(std::max(1.f, firstPersonFov), 179.f); mStateUpdater->setFogEnd(mViewDistance); mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("near", mNearClip)); @@ -1198,6 +1201,12 @@ namespace MWRender mUniformNear->set(mNearClip); mUniformFar->set(mViewDistance); + + // Since our fog is not radial yet, we should take FOV in account, otherwise terrain near viewing distance may disappear. + // Limit FOV here just for sure, otherwise viewing distance can be too high. + fov = std::min(mFieldOfView, 140.f); + float distanceMult = std::cos(osg::DegreesToRadians(fov)/2.f); + mTerrain->setViewDistance(mViewDistance * (distanceMult ? 1.f/distanceMult : 1.f)); } void RenderingManager::updateTextureFiltering() @@ -1446,8 +1455,8 @@ namespace MWRender { try { - const auto locked = it->second.lockConst(); - mNavMesh->update(locked->getValue(), mNavMeshNumber, locked->getGeneration(), + const auto locked = it->second->lockConst(); + mNavMesh->update(locked->getImpl(), mNavMeshNumber, locked->getGeneration(), locked->getNavMeshRevision(), mNavigator.getSettings()); } catch (const std::exception& e) diff --git a/apps/openmw/mwrender/terrainstorage.cpp b/apps/openmw/mwrender/terrainstorage.cpp index 7894a8393..528ce70ea 100644 --- a/apps/openmw/mwrender/terrainstorage.cpp +++ b/apps/openmw/mwrender/terrainstorage.cpp @@ -22,6 +22,15 @@ namespace MWRender mResourceSystem->removeResourceManager(mLandManager.get()); } + bool TerrainStorage::hasData(int cellX, int cellY) + { + const MWWorld::ESMStore &esmStore = + MWBase::Environment::get().getWorld()->getStore(); + + const ESM::Land* land = esmStore.get().search(cellX, cellY); + return land != nullptr; + } + void TerrainStorage::getBounds(float& minX, float& maxX, float& minY, float& maxY) { minX = 0, minY = 0, maxX = 0, maxY = 0; diff --git a/apps/openmw/mwrender/terrainstorage.hpp b/apps/openmw/mwrender/terrainstorage.hpp index 6f716e752..14bed7b7b 100644 --- a/apps/openmw/mwrender/terrainstorage.hpp +++ b/apps/openmw/mwrender/terrainstorage.hpp @@ -20,11 +20,13 @@ namespace MWRender TerrainStorage(Resource::ResourceSystem* resourceSystem, const std::string& normalMapPattern = "", const std::string& normalHeightMapPatteern = "", bool autoUseNormalMaps = false, const std::string& specularMapPattern = "", bool autoUseSpecularMaps = false); ~TerrainStorage(); - virtual osg::ref_ptr getLand (int cellX, int cellY); - virtual const ESM::LandTexture* getLandTexture(int index, short plugin); + virtual osg::ref_ptr getLand (int cellX, int cellY) override; + virtual const ESM::LandTexture* getLandTexture(int index, short plugin) override; + + virtual bool hasData(int cellX, int cellY) override; /// Get bounds of the whole terrain in cell units - virtual void getBounds(float& minX, float& maxX, float& minY, float& maxY); + virtual void getBounds(float& minX, float& maxX, float& minY, float& maxY) override; LandManager* getLandManager() const; diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index 058b7cf7a..2af31b5c9 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -1070,7 +1070,7 @@ namespace MWScript const std::string script = ptr.getClass().getScript(ptr); if(script.empty()) - str<< ptr.getCellRef().getRefId()<<" does not have a script."; + str<< ptr.getCellRef().getRefId()<<" does not have a script."; else { str<< "Local variables for "<moveObject(ptr,pos,ay,az); + updated = MWBase::Environment::get().getWorld()->moveObject(ptr,pos,ay,az,true); } else if(axis == "y") { - updated = MWBase::Environment::get().getWorld()->moveObject(ptr,ax,pos,az); + updated = MWBase::Environment::get().getWorld()->moveObject(ptr,ax,pos,az,true); } else if(axis == "z") { @@ -304,7 +304,7 @@ namespace MWScript pos = terrainHeight; } - updated = MWBase::Environment::get().getWorld()->moveObject(ptr,ax,ay,pos); + updated = MWBase::Environment::get().getWorld()->moveObject(ptr,ax,ay,pos,true); } else throw std::runtime_error ("invalid axis: " + axis); @@ -447,7 +447,7 @@ namespace MWScript } else { - ptr = MWBase::Environment::get().getWorld()->moveObject(ptr, x, y, z); + ptr = MWBase::Environment::get().getWorld()->moveObject(ptr, x, y, z, true); } dynamic_cast(runtime.getContext()).updatePtr(base,ptr); diff --git a/apps/openmw/mwsound/ffmpeg_decoder.cpp b/apps/openmw/mwsound/ffmpeg_decoder.cpp index c0379184e..6c334978c 100644 --- a/apps/openmw/mwsound/ffmpeg_decoder.cpp +++ b/apps/openmw/mwsound/ffmpeg_decoder.cpp @@ -96,25 +96,25 @@ bool FFmpeg_Decoder::getAVAudioData() return false; do { - if(mPacket.size == 0 && !getNextPacket()) - return false; - /* Decode some data, and check for errors */ - int ret = 0; - ret = avcodec_receive_frame(mCodecCtx, mFrame); - if (ret == 0) - got_frame = true; + int ret = avcodec_receive_frame(mCodecCtx, mFrame); if (ret == AVERROR(EAGAIN)) - ret = 0; - if (ret == 0) + { + if (mPacket.size == 0 && !getNextPacket()) + return false; ret = avcodec_send_packet(mCodecCtx, &mPacket); - if (ret < 0 && ret != AVERROR(EAGAIN)) + av_packet_unref(&mPacket); + if (ret == 0) + continue; + } + if (ret != 0) return false; av_packet_unref(&mPacket); - if (!got_frame || mFrame->nb_samples == 0) + if (mFrame->nb_samples == 0) continue; + got_frame = true; if(mSwr) { @@ -138,7 +138,7 @@ bool FFmpeg_Decoder::getAVAudioData() else mFrameData = &mFrame->data[0]; - } while(!got_frame || mFrame->nb_samples == 0); + } while(!got_frame); mNextPts += (double)mFrame->nb_samples / mCodecCtx->sample_rate; return true; diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index 7b722a835..ae2c25ead 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -471,6 +471,36 @@ namespace MWSound startRandomTitle(); } + void SoundManager::playTitleMusic() + { + if (mCurrentPlaylist == "Title") + return; + + if (mMusicFiles.find("Title") == mMusicFiles.end()) + { + std::vector filelist; + const std::map& index = mVFS->getIndex(); + // Is there an ini setting for this filename or something? + std::string filename = "music/special/morrowind title.mp3"; + auto found = index.find(filename); + if (found != index.end()) + { + filelist.emplace_back(found->first); + mMusicFiles["Title"] = filelist; + } + else + { + Log(Debug::Warning) << "Title music not found"; + return; + } + } + + if (mMusicFiles["Title"].empty()) + return; + + mCurrentPlaylist = "Title"; + startRandomTitle(); + } void SoundManager::say(const MWWorld::ConstPtr &ptr, const std::string &filename) { @@ -1122,10 +1152,10 @@ namespace MWSound if(!mOutput->isInitialized()) return; + updateSounds(duration); if (MWBase::Environment::get().getStateManager()->getState()!= MWBase::StateManager::State_NoGame) { - updateSounds(duration); updateRegionSound(duration); updateWaterSound(duration); } diff --git a/apps/openmw/mwsound/soundmanagerimp.hpp b/apps/openmw/mwsound/soundmanagerimp.hpp index d8a4cfc8c..9878e924a 100644 --- a/apps/openmw/mwsound/soundmanagerimp.hpp +++ b/apps/openmw/mwsound/soundmanagerimp.hpp @@ -168,6 +168,9 @@ namespace MWSound ///< Start playing music from the selected folder /// \param name of the folder that contains the playlist + virtual void playTitleMusic(); + ///< Start playing title music + virtual void say(const MWWorld::ConstPtr &reference, const std::string& filename); ///< Make an actor say some text. /// \param filename name of a sound file in "Sound/" in the data directory. diff --git a/apps/openmw/mwworld/cellpreloader.cpp b/apps/openmw/mwworld/cellpreloader.cpp index 65ba19b4a..98e9c7368 100644 --- a/apps/openmw/mwworld/cellpreloader.cpp +++ b/apps/openmw/mwworld/cellpreloader.cpp @@ -381,7 +381,7 @@ namespace MWWorld { for (unsigned int i=0; ipreload(mTerrainViews[i], mPreloadPositions[i]); + mWorld->preload(mTerrainViews[i], mPreloadPositions[i], mAbort); mTerrainViews[i]->reset(0); } } diff --git a/apps/openmw/mwworld/cells.cpp b/apps/openmw/mwworld/cells.cpp index 4d2583d53..bea5825da 100644 --- a/apps/openmw/mwworld/cells.cpp +++ b/apps/openmw/mwworld/cells.cpp @@ -150,16 +150,16 @@ MWWorld::CellStore *MWWorld::Cells::getInterior (const std::string& name) return &result->second; } -void MWWorld::Cells::rest () +void MWWorld::Cells::rest (double hours) { for (auto &interior : mInteriors) { - interior.second.rest(); + interior.second.rest(hours); } for (auto &exterior : mExteriors) { - exterior.second.rest(); + exterior.second.rest(hours); } } diff --git a/apps/openmw/mwworld/cells.hpp b/apps/openmw/mwworld/cells.hpp index bd730329b..dce42d996 100644 --- a/apps/openmw/mwworld/cells.hpp +++ b/apps/openmw/mwworld/cells.hpp @@ -61,7 +61,7 @@ namespace MWWorld /// @note name must be lower case Ptr getPtr (const std::string& name); - void rest (); + void rest (double hours); /// Get all Ptrs referencing \a name in exterior cells /// @note Due to the current implementation of getPtr this only supports one Ptr per cell. diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index b6e490218..cfb1ccbee 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -1152,7 +1152,7 @@ namespace MWWorld } } - void CellStore::rest() + void CellStore::rest(double hours) { if (mState == State_Loaded) { @@ -1161,7 +1161,7 @@ namespace MWWorld Ptr ptr = getCurrentPtr(&*it); if (!ptr.isEmpty() && ptr.getRefData().getCount() > 0) { - MWBase::Environment::get().getMechanicsManager()->restoreDynamicStats(ptr, true); + MWBase::Environment::get().getMechanicsManager()->restoreDynamicStats(ptr, hours, true); } } for (CellRefList::List::iterator it (mNpcs.mList.begin()); it!=mNpcs.mList.end(); ++it) @@ -1169,7 +1169,7 @@ namespace MWWorld Ptr ptr = getCurrentPtr(&*it); if (!ptr.isEmpty() && ptr.getRefData().getCount() > 0) { - MWBase::Environment::get().getMechanicsManager()->restoreDynamicStats(ptr, true); + MWBase::Environment::get().getMechanicsManager()->restoreDynamicStats(ptr, hours, true); } } } diff --git a/apps/openmw/mwworld/cellstore.hpp b/apps/openmw/mwworld/cellstore.hpp index 6e5f340c8..aea557884 100644 --- a/apps/openmw/mwworld/cellstore.hpp +++ b/apps/openmw/mwworld/cellstore.hpp @@ -183,7 +183,7 @@ namespace MWWorld /// @return updated MWWorld::Ptr with the new CellStore pointer set. MWWorld::Ptr moveTo(const MWWorld::Ptr& object, MWWorld::CellStore* cellToMoveTo); - void rest(); + void rest(double hours); /// Make a copy of the given object and insert it into this cell. /// @note If you get a linker error here, this means the given type can not be inserted into a cell. diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 4ff6842a6..05d5ae1a3 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -360,7 +360,7 @@ namespace MWWorld { if (const auto object = mPhysics->getObject(ptr)) navigator->removeObject(DetourNavigator::ObjectId(object)); - else if (const auto actor = mPhysics->getActor(ptr)) + else if (mPhysics->getActor(ptr)) { navigator->removeAgent(world->getPathfindingHalfExtents(ptr)); mRendering.removeActorPath(ptr); @@ -892,7 +892,7 @@ namespace MWWorld const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr(); navigator->update(player.getRefData().getPosition().asVec3()); } - else if (const auto actor = mPhysics->getActor(ptr)) + else if (mPhysics->getActor(ptr)) { navigator->removeAgent(MWBase::Environment::get().getWorld()->getPathfindingHalfExtents(ptr)); } diff --git a/apps/openmw/mwworld/store.cpp b/apps/openmw/mwworld/store.cpp index 79d9174ed..2a0c39466 100644 --- a/apps/openmw/mwworld/store.cpp +++ b/apps/openmw/mwworld/store.cpp @@ -387,6 +387,21 @@ namespace MWWorld assert(plugin < mStatic.size()); + // Replace texture for records with given ID and index from all plugins. + for (unsigned int i=0; i(search(lt.mIndex, i)); + if (tex) + { + const std::string texId = Misc::StringUtils::lowerCase(tex->mId); + const std::string ltId = Misc::StringUtils::lowerCase(lt.mId); + if (texId == ltId) + { + tex->mTexture = lt.mTexture; + } + } + } + LandTextureList <exl = mStatic[plugin]; if(lt.mIndex + 1 > (int)ltexl.size()) ltexl.resize(lt.mIndex+1); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index b493622a7..89d2400ed 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -178,12 +178,12 @@ namespace MWWorld const Files::Collections& fileCollections, const std::vector& contentFiles, ToUTF8::Utf8Encoder* encoder, const std::map& fallbackMap, - int activationDistanceOverride, const std::string& startCell, const std::string& startupScript, - const std::string& resourcePath, const std::string& userDataPath) + int activationDistanceOverride, const std::string& startCell, + const std::string& resourcePath, const std::string& userDataPath) : mResourceSystem(resourceSystem), mFallback(fallbackMap), mLocalScripts (mStore), mSky (true), mCells (mStore, mEsm), mGodMode(false), mScriptsEnabled(true), mContentFiles (contentFiles), mUserDataPath(userDataPath), - mActivationDistanceOverride (activationDistanceOverride), mStartupScript(startupScript), + mActivationDistanceOverride (activationDistanceOverride), mStartCell (startCell), mDistanceToFacedObject(-1), mTeleportEnabled(true), mLevitationEnabled(true), mGoToJail(false), mDaysInPrison(0), mPlayerTraveling(false), mPlayerInJail(false), mSpellPreloadTimer(0.f) @@ -337,9 +337,6 @@ namespace MWWorld if (!mPhysics->toggleCollisionMode()) mPhysics->toggleCollisionMode(); - if (!mStartupScript.empty()) - MWBase::Environment::get().getWindowManager()->executeInConsole(mStartupScript); - MWBase::Environment::get().getWindowManager()->updatePlayer(); } @@ -503,7 +500,7 @@ namespace MWWorld gmst["iWereWolfBounty"] = ESM::Variant(1000); gmst["fCombatDistanceWerewolfMod"] = ESM::Variant(0.3f); - for (const std::pair ¶ms : gmst) + for (const auto ¶ms : gmst) { if (!mStore.get().search(params.first)) { @@ -533,7 +530,7 @@ namespace MWWorld globals["crimegoldturnin"] = ESM::Variant(0); globals["pchasturnin"] = ESM::Variant(0); - for (const std::pair ¶ms : globals) + for (const auto ¶ms : globals) { if (!mStore.get().search(params.first)) { @@ -552,7 +549,7 @@ namespace MWWorld statics["templemarker"] = "marker_temple.nif"; statics["travelmarker"] = "marker_travel.nif"; - for (const std::pair ¶ms : statics) + for (const auto ¶ms : statics) { if (!mStore.get().search(params.first)) { @@ -566,7 +563,7 @@ namespace MWWorld std::map doors; doors["prisonmarker"] = "marker_prison.nif"; - for (const std::pair ¶ms : doors) + for (const auto ¶ms : doors) { if (!mStore.get().search(params.first)) { @@ -1512,23 +1509,24 @@ namespace MWWorld return newPtr; } - MWWorld::Ptr World::moveObjectImp(const Ptr& ptr, float x, float y, float z, bool movePhysics) + MWWorld::Ptr World::moveObjectImp(const Ptr& ptr, float x, float y, float z, bool movePhysics, bool moveToActive) { - CellStore *cell = ptr.getCell(); + int cellX, cellY; + positionToIndex(x, y, cellX, cellY); - if (cell->isExterior()) { - int cellX, cellY; - positionToIndex(x, y, cellX, cellY); + CellStore* cell = ptr.getCell(); + CellStore* newCell = getExterior(cellX, cellY); + bool isCellActive = getPlayerPtr().getCell()->isExterior() && mWorldScene->isCellActive(*newCell); - cell = getExterior(cellX, cellY); - } + if (cell->isExterior() || (moveToActive && isCellActive && ptr.getClass().isActor())) + cell = newCell; return moveObject(ptr, cell, x, y, z, movePhysics); } - MWWorld::Ptr World::moveObject (const Ptr& ptr, float x, float y, float z) + MWWorld::Ptr World::moveObject (const Ptr& ptr, float x, float y, float z, bool moveToActive) { - return moveObjectImp(ptr, x, y, z); + return moveObjectImp(ptr, x, y, z, true, moveToActive); } void World::scaleObject (const Ptr& ptr, float scale) @@ -3698,9 +3696,9 @@ namespace MWWorld return closestMarker; } - void World::rest() + void World::rest(double hours) { - mCells.rest(); + mCells.rest(hours); } void World::teleportToClosestMarker (const MWWorld::Ptr& ptr, diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index c2480e05f..a4463be62 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -120,8 +120,6 @@ namespace MWWorld int mActivationDistanceOverride; - std::string mStartupScript; - std::map mDoorStates; ///< only holds doors that are currently moving. 1 = opening, 2 = closing @@ -132,7 +130,7 @@ namespace MWWorld void rotateObjectImp (const Ptr& ptr, const osg::Vec3f& rot, bool adjust); - Ptr moveObjectImp (const Ptr& ptr, float x, float y, float z, bool movePhysics=true); + Ptr moveObjectImp (const Ptr& ptr, float x, float y, float z, bool movePhysics=true, bool moveToActive=false); ///< @return an updated Ptr in case the Ptr's cell changes Ptr copyObjectToCell(const ConstPtr &ptr, CellStore* cell, ESM::Position pos, int count, bool adjustPos); @@ -211,7 +209,7 @@ namespace MWWorld const Files::Collections& fileCollections, const std::vector& contentFiles, ToUTF8::Utf8Encoder* encoder, const std::map& fallbackMap, - int activationDistanceOverride, const std::string& startCell, const std::string& startupScript, const std::string& resourcePath, const std::string& userDataPath); + int activationDistanceOverride, const std::string& startCell, const std::string& resourcePath, const std::string& userDataPath); virtual ~World(); @@ -499,7 +497,7 @@ namespace MWWorld void undeleteObject (const Ptr& ptr) override; - MWWorld::Ptr moveObject (const Ptr& ptr, float x, float y, float z) override; + MWWorld::Ptr moveObject (const Ptr& ptr, float x, float y, float z, bool moveToActive=false) override; ///< @return an updated Ptr in case the Ptr's cell changes MWWorld::Ptr moveObject (const Ptr& ptr, CellStore* newCell, float x, float y, float z, bool movePhysics=true) override; @@ -737,7 +735,7 @@ namespace MWWorld RestPermitted canRest() const override; ///< check if the player is allowed to rest - void rest() override; + void rest(double hours) override; /// \todo Probably shouldn't be here MWRender::Animation* getAnimation(const MWWorld::Ptr &ptr) override; diff --git a/apps/openmw_test_suite/detournavigator/navigator.cpp b/apps/openmw_test_suite/detournavigator/navigator.cpp index 2bf7bf643..5f5433b05 100644 --- a/apps/openmw_test_suite/detournavigator/navigator.cpp +++ b/apps/openmw_test_suite/detournavigator/navigator.cpp @@ -80,14 +80,6 @@ namespace EXPECT_THROW(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mOut), NavigatorException); } - TEST_F(DetourNavigatorNavigatorTest, find_path_for_removed_agent_should_return_empty) - { - mNavigator->addAgent(mAgentHalfExtents); - mNavigator->removeAgent(mAgentHalfExtents); - mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mOut); - EXPECT_EQ(mPath, std::deque()); - } - TEST_F(DetourNavigatorNavigatorTest, add_agent_should_count_each_agent) { mNavigator->addAgent(mAgentHalfExtents); diff --git a/components/compiler/controlparser.cpp b/components/compiler/controlparser.cpp index b202467db..ebadcbbc6 100644 --- a/components/compiler/controlparser.cpp +++ b/components/compiler/controlparser.cpp @@ -125,7 +125,7 @@ namespace Compiler if (loop.size()!=loop2.size()) throw std::logic_error ( - "internal compiler error: failed to generate a while loop"); + "Internal compiler error: failed to generate a while loop"); std::copy (loop2.begin(), loop2.end(), std::back_inserter (mCode)); @@ -179,6 +179,14 @@ namespace Compiler scanner.scan (mLineParser); return true; } + else if (mState==IfElseJunkState) + { + getErrorHandler().warning ("Extra text after else", loc); + SkipParser skip (getErrorHandler(), getContext()); + scanner.scan (skip); + mState = IfElseBodyState; + return true; + } return Parser::parseName (name, loc, scanner); } @@ -207,8 +215,7 @@ namespace Compiler return true; } } - else if (mState==IfBodyState || mState==IfElseifBodyState || mState==IfElseBodyState || - mState==IfElseJunkState) + else if (mState==IfBodyState || mState==IfElseifBodyState || mState==IfElseBodyState) { if (parseIfBody (keyword, loc, scanner)) return true; @@ -218,6 +225,14 @@ namespace Compiler if ( parseWhileBody (keyword, loc, scanner)) return true; } + else if (mState==IfElseJunkState) + { + getErrorHandler().warning ("Extra text after else", loc); + SkipParser skip (getErrorHandler(), getContext()); + scanner.scan (skip); + mState = IfElseBodyState; + return true; + } return Parser::parseKeyword (keyword, loc, scanner); } @@ -250,8 +265,9 @@ namespace Compiler default: ; } } - else if (code==Scanner::S_open && mState==IfElseJunkState) + else if (mState==IfElseJunkState) { + getErrorHandler().warning ("Extra text after else", loc); SkipParser skip (getErrorHandler(), getContext()); scanner.scan (skip); mState = IfElseBodyState; diff --git a/components/compiler/declarationparser.cpp b/components/compiler/declarationparser.cpp index e85c8c3ec..1c64aaade 100644 --- a/components/compiler/declarationparser.cpp +++ b/components/compiler/declarationparser.cpp @@ -22,20 +22,20 @@ bool Compiler::DeclarationParser::parseName (const std::string& name, const Toke char type = mLocals.getType (name2); if (type!=' ') - { - /// \todo add option to make re-declared local variables an error - getErrorHandler().warning ("ignoring local variable re-declaration", - loc); - - mState = State_End; - return true; - } - - mLocals.declare (mType, name2); + getErrorHandler().warning ("Local variable re-declaration", loc); + else + mLocals.declare (mType, name2); mState = State_End; return true; } + else if (mState==State_End) + { + getErrorHandler().warning ("Extra text after local variable declaration", loc); + SkipParser skip (getErrorHandler(), getContext()); + scanner.scan (skip); + return false; + } return Parser::parseName (name, loc, scanner); } @@ -61,17 +61,31 @@ bool Compiler::DeclarationParser::parseKeyword (int keyword, const TokenLoc& loc else if (mState==State_Name) { // allow keywords to be used as local variable names. MW script compiler, you suck! - /// \todo option to disable this atrocity. return parseName (loc.mLiteral, loc, scanner); } + else if (mState==State_End) + { + getErrorHandler().warning ("Extra text after local variable declaration", loc); + SkipParser skip (getErrorHandler(), getContext()); + scanner.scan (skip); + return false; + } return Parser::parseKeyword (keyword, loc, scanner); } bool Compiler::DeclarationParser::parseSpecial (int code, const TokenLoc& loc, Scanner& scanner) { - if (code==Scanner::S_newline && mState==State_End) + if (mState==State_End) + { + if (code!=Scanner::S_newline) + { + getErrorHandler().warning ("Extra text after local variable declaration", loc); + SkipParser skip (getErrorHandler(), getContext()); + scanner.scan (skip); + } return false; + } return Parser::parseSpecial (code, loc, scanner); } diff --git a/components/compiler/exception.hpp b/components/compiler/exception.hpp index a28663115..33bad7590 100644 --- a/components/compiler/exception.hpp +++ b/components/compiler/exception.hpp @@ -11,7 +11,7 @@ namespace Compiler { public: - virtual const char *what() const throw() { return "compile error";} + virtual const char *what() const throw() { return "Compile error";} ///< Return error message }; @@ -21,7 +21,7 @@ namespace Compiler { public: - virtual const char *what() const throw() { return "can't read file"; } + virtual const char *what() const throw() { return "Can't read file"; } ///< Return error message }; @@ -31,7 +31,7 @@ namespace Compiler { public: - virtual const char *what() const throw() { return "end of file"; } + virtual const char *what() const throw() { return "End of file"; } ///< Return error message }; } diff --git a/components/compiler/exprparser.cpp b/components/compiler/exprparser.cpp index b3fd1e5ae..e3a306b8f 100644 --- a/components/compiler/exprparser.cpp +++ b/components/compiler/exprparser.cpp @@ -99,7 +99,7 @@ namespace Compiler else if (t1=='f' || t2=='f') mOperands.push_back ('f'); else - throw std::logic_error ("failed to determine result operand type"); + throw std::logic_error ("Failed to determine result operand type"); } void ExprParser::pop() @@ -158,7 +158,7 @@ namespace Compiler default: - throw std::logic_error ("unknown operator"); + throw std::logic_error ("Unknown operator"); } } @@ -287,7 +287,7 @@ namespace Compiler else { mExplicit.clear(); - getErrorHandler().warning ("Ignoring stray explicit reference", loc); + getErrorHandler().warning ("Stray explicit reference", loc); } } @@ -430,7 +430,7 @@ namespace Compiler { if (!hasExplicit) { - getErrorHandler().warning ("ignoring stray explicit reference", loc); + getErrorHandler().warning ("Stray explicit reference", loc); mExplicit.clear(); } @@ -735,13 +735,13 @@ namespace Compiler { if (mOperands.empty() && mOperators.empty()) { - getErrorHandler().error ("missing expression", mTokenLoc); + getErrorHandler().error ("Missing expression", mTokenLoc); return 'l'; } if (mNextOperand || mOperands.empty()) { - getErrorHandler().error ("syntax error in expression", mTokenLoc); + getErrorHandler().error ("Syntax error in expression", mTokenLoc); return 'l'; } @@ -799,7 +799,7 @@ namespace Compiler ++optionalCount; } else - getErrorHandler().warning ("ignoring extra argument", + getErrorHandler().warning ("Extra argument", stringParser.getTokenLoc()); } else if (*iter=='X') @@ -813,7 +813,7 @@ namespace Compiler if (parser.isEmpty()) break; else - getErrorHandler().warning("ignoring extra argument", parser.getTokenLoc()); + getErrorHandler().warning("Extra argument", parser.getTokenLoc()); } else if (*iter=='z') { @@ -825,7 +825,7 @@ namespace Compiler if (discardParser.isEmpty()) break; else - getErrorHandler().warning("ignoring extra argument", discardParser.getTokenLoc()); + getErrorHandler().warning("Extra argument", discardParser.getTokenLoc()); } else if (*iter=='j') { diff --git a/components/compiler/extensions0.cpp b/components/compiler/extensions0.cpp index fdb670748..92b647288 100644 --- a/components/compiler/extensions0.cpp +++ b/components/compiler/extensions0.cpp @@ -356,7 +356,7 @@ namespace Compiler opcodePlaySound3DExplicit); extensions.registerInstruction ("playsound3dvp", "cff", opcodePlaySound3DVP, opcodePlaySound3DVPExplicit); - extensions.registerInstruction ("playloopsound3d", "c", opcodePlayLoopSound3D, + extensions.registerInstruction ("playloopsound3d", "cXX", opcodePlayLoopSound3D, opcodePlayLoopSound3DExplicit); extensions.registerInstruction ("playloopsound3dvp", "cff", opcodePlayLoopSound3DVP, opcodePlayLoopSound3DVPExplicit); diff --git a/components/compiler/fileparser.cpp b/components/compiler/fileparser.cpp index 8b2f1fe08..c7459c2ae 100644 --- a/components/compiler/fileparser.cpp +++ b/components/compiler/fileparser.cpp @@ -98,7 +98,7 @@ namespace Compiler if (mState == BeginState) { if (code != Scanner::S_newline) - reportWarning ("Ignoring stray special character before begin statement", loc); + reportWarning ("Stray special character before begin statement", loc); return true; } diff --git a/components/compiler/junkparser.cpp b/components/compiler/junkparser.cpp index 5910cca43..5df551535 100644 --- a/components/compiler/junkparser.cpp +++ b/components/compiler/junkparser.cpp @@ -29,7 +29,7 @@ bool Compiler::JunkParser::parseName (const std::string& name, const TokenLoc& l bool Compiler::JunkParser::parseKeyword (int keyword, const TokenLoc& loc, Scanner& scanner) { if (keyword==mIgnoreKeyword) - reportWarning ("ignoring found junk", loc); + reportWarning ("Ignoring found junk", loc); else scanner.putbackKeyword (keyword, loc); @@ -39,7 +39,7 @@ bool Compiler::JunkParser::parseKeyword (int keyword, const TokenLoc& loc, Scann bool Compiler::JunkParser::parseSpecial (int code, const TokenLoc& loc, Scanner& scanner) { if (code==Scanner::S_member) - reportWarning ("ignoring found junk", loc); + reportWarning ("Ignoring found junk", loc); else scanner.putbackSpecial (code, loc); diff --git a/components/compiler/lineparser.cpp b/components/compiler/lineparser.cpp index 1cfa70f73..1c3d1a067 100644 --- a/components/compiler/lineparser.cpp +++ b/components/compiler/lineparser.cpp @@ -48,7 +48,7 @@ namespace Compiler default: - throw std::runtime_error ("unknown expression result type"); + throw std::runtime_error ("Unknown expression result type"); } } @@ -88,7 +88,7 @@ namespace Compiler { if (mState==PotentialEndState) { - getErrorHandler().warning ("ignoring stray string argument", loc); + getErrorHandler().warning ("Stray string argument", loc); mState = EndState; return true; } @@ -132,7 +132,7 @@ namespace Compiler return true; } - getErrorHandler().error ("unknown variable", loc); + getErrorHandler().error ("Unknown variable", loc); SkipParser skip (getErrorHandler(), getContext()); scanner.scan (skip); return false; @@ -233,7 +233,7 @@ namespace Compiler if (mState==SetPotentialMemberVarState && keyword==Scanner::K_to) { - getErrorHandler().warning ("unknown variable, ignoring set instruction", loc); + getErrorHandler().warning ("Unknown variable", loc); SkipParser skip (getErrorHandler(), getContext()); scanner.scan (skip); return false; @@ -286,7 +286,7 @@ namespace Compiler { if (!hasExplicit && mState==ExplicitState) { - getErrorHandler().warning ("ignoring stray explicit reference", loc); + getErrorHandler().warning ("Stray explicit reference", loc); mExplicit.clear(); } @@ -344,7 +344,7 @@ namespace Compiler { if (!hasExplicit && !mExplicit.empty()) { - getErrorHandler().warning ("ignoring stray explicit reference", loc); + getErrorHandler().warning ("Stray explicit reference", loc); mExplicit.clear(); } @@ -360,7 +360,7 @@ namespace Compiler if (mState==ExplicitState) { // drop stray explicit reference - getErrorHandler().warning ("ignoring stray explicit reference", loc); + getErrorHandler().warning ("Stray explicit reference", loc); mState = BeginState; mExplicit.clear(); } @@ -375,8 +375,7 @@ namespace Compiler { if (!getContext().canDeclareLocals()) { - getErrorHandler().error ( - "local variables can't be declared in this context", loc); + getErrorHandler().error("Local variables cannot be declared in this context", loc); SkipParser skip (getErrorHandler(), getContext()); scanner.scan (skip); return true; @@ -412,19 +411,19 @@ namespace Compiler case Scanner::K_else: - getErrorHandler().warning ("ignoring stray else", loc); + getErrorHandler().warning ("Stray else", loc); mState = EndState; return true; case Scanner::K_endif: - getErrorHandler().warning ("ignoring stray endif", loc); + getErrorHandler().warning ("Stray endif", loc); mState = EndState; return true; case Scanner::K_begin: - getErrorHandler().warning ("ignoring stray begin", loc); + getErrorHandler().warning ("Stray begin", loc); mState = EndState; return true; } @@ -491,7 +490,7 @@ namespace Compiler { if (mState==EndState && code==Scanner::S_open) { - getErrorHandler().warning ("ignoring stray '[' or '(' at the end of the line", + getErrorHandler().warning ("Stray '[' or '(' at the end of the line", loc); return true; } @@ -508,7 +507,7 @@ namespace Compiler if (code==Scanner::S_ref && mState==SetPotentialMemberVarState) { - getErrorHandler().warning ("Ignoring stray explicit reference", loc); + getErrorHandler().warning ("Stray explicit reference", loc); mState = SetState; return true; } diff --git a/components/compiler/locals.cpp b/components/compiler/locals.cpp index 2b485abec..a7102c38d 100644 --- a/components/compiler/locals.cpp +++ b/components/compiler/locals.cpp @@ -18,7 +18,7 @@ namespace Compiler case 'f': return mFloats; } - throw std::logic_error ("unknown variable type"); + throw std::logic_error ("Unknown variable type"); } int Locals::searchIndex (char type, const std::string& name) const @@ -48,7 +48,7 @@ namespace Compiler case 'f': return mFloats; } - throw std::logic_error ("unknown variable type"); + throw std::logic_error ("Unknown variable type"); } char Locals::getType (const std::string& name) const diff --git a/components/compiler/scanner.cpp b/components/compiler/scanner.cpp index c5ef483f3..65c050ce0 100644 --- a/components/compiler/scanner.cpp +++ b/components/compiler/scanner.cpp @@ -158,7 +158,7 @@ namespace Compiler TokenLoc loc (mLoc); mLoc.mLiteral.clear(); - mErrorHandler.error ("syntax error", loc); + mErrorHandler.error ("Syntax error", loc); throw SourceException(); } @@ -521,7 +521,7 @@ namespace Compiler else if (c == '<' || c == '>') // Treat <> and << as < { special = S_cmpLT; - mErrorHandler.warning (std::string("invalid operator <") + c + ", treating it as <", mLoc); + mErrorHandler.warning ("Invalid operator, treating it as <", mLoc); } else { @@ -549,7 +549,7 @@ namespace Compiler else if (c == '<' || c == '>') // Treat >< and >> as > { special = S_cmpGT; - mErrorHandler.warning (std::string("invalid operator >") + c + ", treating it as >", mLoc); + mErrorHandler.warning ("Invalid operator, treating it as >", mLoc); } else { diff --git a/components/detournavigator/asyncnavmeshupdater.cpp b/components/detournavigator/asyncnavmeshupdater.cpp index ee3b6d77a..6bd4f01c9 100644 --- a/components/detournavigator/asyncnavmeshupdater.cpp +++ b/components/detournavigator/asyncnavmeshupdater.cpp @@ -5,6 +5,8 @@ #include +#include + namespace { using DetourNavigator::ChangeType; @@ -102,6 +104,20 @@ namespace DetourNavigator mDone.wait(lock, [&] { return mJobs.empty(); }); } + void AsyncNavMeshUpdater::reportStats(unsigned int frameNumber, osg::Stats& stats) const + { + std::size_t jobs = 0; + + { + const std::lock_guard lock(mMutex); + jobs = mJobs.size(); + } + + stats.setAttribute(frameNumber, "NavMesh UpdateJobs", jobs); + + mNavMeshTilesCache.reportStats(frameNumber, stats); + } + void AsyncNavMeshUpdater::process() throw() { log("start process jobs"); @@ -129,12 +145,17 @@ namespace DetourNavigator const auto firstStart = setFirstStart(start); + const auto navMeshCacheItem = job.mNavMeshCacheItem.lock(); + + if (!navMeshCacheItem) + return true; + const auto recastMesh = mRecastMeshManager.get().getMesh(job.mChangedTile); const auto playerTile = *mPlayerTile.lockConst(); const auto offMeshConnections = mOffMeshConnectionsManager.get().get(job.mChangedTile); const auto status = updateNavMesh(job.mAgentHalfExtents, recastMesh.get(), job.mChangedTile, playerTile, - offMeshConnections, mSettings, job.mNavMeshCacheItem, mNavMeshTilesCache); + offMeshConnections, mSettings, navMeshCacheItem, mNavMeshTilesCache); const auto finish = std::chrono::steady_clock::now(); @@ -143,7 +164,7 @@ namespace DetourNavigator using FloatMs = std::chrono::duration; { - const auto locked = job.mNavMeshCacheItem.lockConst(); + const auto locked = navMeshCacheItem->lockConst(); log("cache updated for agent=", job.mAgentHalfExtents, " status=", status, " generation=", locked->getGeneration(), " revision=", locked->getNavMeshRevision(), @@ -157,9 +178,7 @@ namespace DetourNavigator boost::optional AsyncNavMeshUpdater::getNextJob() { std::unique_lock lock(mMutex); - if (mJobs.empty()) - mHasJob.wait_for(lock, std::chrono::milliseconds(10)); - if (mJobs.empty()) + if (!mHasJob.wait_for(lock, std::chrono::milliseconds(10), [&] { return !mJobs.empty(); })) { mFirstStart.lock()->reset(); mDone.notify_all(); @@ -194,7 +213,8 @@ namespace DetourNavigator writeToFile(*recastMesh, mSettings.get().mRecastMeshPathPrefix + std::to_string(job.mChangedTile.x()) + "_" + std::to_string(job.mChangedTile.y()) + "_", recastMeshRevision); if (mSettings.get().mEnableWriteNavMeshToFile) - writeToFile(job.mNavMeshCacheItem.lockConst()->getValue(), mSettings.get().mNavMeshPathPrefix, navMeshRevision); + if (const auto shared = job.mNavMeshCacheItem.lock()) + writeToFile(shared->lockConst()->getImpl(), mSettings.get().mNavMeshPathPrefix, navMeshRevision); } std::chrono::steady_clock::time_point AsyncNavMeshUpdater::setFirstStart(const std::chrono::steady_clock::time_point& value) diff --git a/components/detournavigator/asyncnavmeshupdater.hpp b/components/detournavigator/asyncnavmeshupdater.hpp index 98359964d..3493ba02f 100644 --- a/components/detournavigator/asyncnavmeshupdater.hpp +++ b/components/detournavigator/asyncnavmeshupdater.hpp @@ -44,11 +44,13 @@ namespace DetourNavigator void wait(); + void reportStats(unsigned int frameNumber, osg::Stats& stats) const; + private: struct Job { osg::Vec3f mAgentHalfExtents; - SharedNavMeshCacheItem mNavMeshCacheItem; + std::weak_ptr mNavMeshCacheItem; TilePosition mChangedTile; unsigned mTryNumber; ChangeType mChangeType; @@ -72,7 +74,7 @@ namespace DetourNavigator std::reference_wrapper mRecastMeshManager; std::reference_wrapper mOffMeshConnectionsManager; std::atomic_bool mShouldStop; - std::mutex mMutex; + mutable std::mutex mMutex; std::condition_variable mHasJob; std::condition_variable mDone; Jobs mJobs; diff --git a/components/detournavigator/makenavmesh.cpp b/components/detournavigator/makenavmesh.cpp index 5ac28127e..7caad9ec6 100644 --- a/components/detournavigator/makenavmesh.cpp +++ b/components/detournavigator/makenavmesh.cpp @@ -25,8 +25,6 @@ namespace { using namespace DetourNavigator; - static const int doNotTransferOwnership = 0; - void initPolyMeshDetail(rcPolyMeshDetail& value) { value.meshes = nullptr; @@ -441,56 +439,7 @@ namespace return NavMeshData(navMeshData, navMeshDataSize); } - class UpdateNavMeshStatusBuilder - { - public: - UpdateNavMeshStatusBuilder() = default; - - UpdateNavMeshStatusBuilder removed(bool value) - { - if (value) - set(UpdateNavMeshStatus::removed); - else - unset(UpdateNavMeshStatus::removed); - return *this; - } - - UpdateNavMeshStatusBuilder added(bool value) - { - if (value) - set(UpdateNavMeshStatus::added); - else - unset(UpdateNavMeshStatus::added); - return *this; - } - UpdateNavMeshStatusBuilder failed(bool value) - { - if (value) - set(UpdateNavMeshStatus::failed); - else - unset(UpdateNavMeshStatus::failed); - return *this; - } - - UpdateNavMeshStatus getResult() const - { - return mResult; - } - - private: - UpdateNavMeshStatus mResult = UpdateNavMeshStatus::ignored; - - void set(UpdateNavMeshStatus value) - { - mResult = static_cast(static_cast(mResult) | static_cast(value)); - } - - void unset(UpdateNavMeshStatus value) - { - mResult = static_cast(static_cast(mResult) & ~static_cast(value)); - } - }; template unsigned long getMinValuableBitsNumber(const T value) @@ -500,49 +449,6 @@ namespace ++power; return power; } - - dtStatus addTile(dtNavMesh& navMesh, const NavMeshData& navMeshData) - { - const dtTileRef lastRef = 0; - dtTileRef* const result = nullptr; - return navMesh.addTile(navMeshData.mValue.get(), navMeshData.mSize, - doNotTransferOwnership, lastRef, result); - } - - dtStatus addTile(dtNavMesh& navMesh, const NavMeshTilesCache::Value& cachedNavMeshData) - { - const dtTileRef lastRef = 0; - dtTileRef* const result = nullptr; - return navMesh.addTile(cachedNavMeshData.get().mValue, cachedNavMeshData.get().mSize, - doNotTransferOwnership, lastRef, result); - } - - template - UpdateNavMeshStatus replaceTile(const SharedNavMeshCacheItem& navMeshCacheItem, - const TilePosition& changedTile, T&& navMeshData) - { - const auto locked = navMeshCacheItem.lock(); - auto& navMesh = locked->getValue(); - const int layer = 0; - const auto tileRef = navMesh.getTileRefAt(changedTile.x(), changedTile.y(), layer); - unsigned char** const data = nullptr; - int* const dataSize = nullptr; - const auto removed = dtStatusSucceed(navMesh.removeTile(tileRef, data, dataSize)); - const auto addStatus = addTile(navMesh, navMeshData); - - if (dtStatusSucceed(addStatus)) - { - locked->setUsedTile(changedTile, std::forward(navMeshData)); - return UpdateNavMeshStatusBuilder().added(true).removed(removed).getResult(); - } - else - { - if (removed) - locked->removeUsedTile(changedTile); - log("failed to add tile with status=", WriteDtStatus {addStatus}); - return UpdateNavMeshStatusBuilder().removed(removed).failed((addStatus & DT_OUT_OF_MEMORY) != 0).getResult(); - } - } } namespace DetourNavigator @@ -591,26 +497,13 @@ namespace DetourNavigator " playerTile=", playerTile, " changedTileDistance=", getDistance(changedTile, playerTile)); - const auto params = *navMeshCacheItem.lockConst()->getValue().getParams(); + const auto params = *navMeshCacheItem->lockConst()->getImpl().getParams(); const osg::Vec3f origin(params.orig[0], params.orig[1], params.orig[2]); - const auto x = changedTile.x(); - const auto y = changedTile.y(); - - const auto removeTile = [&] { - const auto locked = navMeshCacheItem.lock(); - auto& navMesh = locked->getValue(); - const auto tileRef = navMesh.getTileRefAt(x, y, 0); - const auto removed = dtStatusSucceed(navMesh.removeTile(tileRef, nullptr, nullptr)); - if (removed) - locked->removeUsedTile(changedTile); - return UpdateNavMeshStatusBuilder().removed(removed).getResult(); - }; - if (!recastMesh) { log("ignore add tile: recastMesh is null"); - return removeTile(); + return navMeshCacheItem->lock()->removeTile(changedTile); } auto recastMeshBounds = recastMesh->getBounds(); @@ -625,13 +518,13 @@ namespace DetourNavigator if (isEmpty(recastMeshBounds)) { log("ignore add tile: recastMesh is empty"); - return removeTile(); + return navMeshCacheItem->lock()->removeTile(changedTile); } if (!shouldAddTile(changedTile, playerTile, std::min(settings.mMaxTilesNumber, params.maxTiles))) { log("ignore add tile: too far from player"); - return removeTile(); + return navMeshCacheItem->lock()->removeTile(changedTile); } auto cachedNavMeshData = navMeshTilesCache.get(agentHalfExtents, changedTile, *recastMesh, offMeshConnections); @@ -648,7 +541,7 @@ namespace DetourNavigator if (!navMeshData.mValue) { log("ignore add tile: NavMeshData is null"); - return removeTile(); + return navMeshCacheItem->lock()->removeTile(changedTile); } try @@ -665,10 +558,10 @@ namespace DetourNavigator if (!cachedNavMeshData) { log("cache overflow"); - return replaceTile(navMeshCacheItem, changedTile, std::move(navMeshData)); + return navMeshCacheItem->lock()->updateTile(changedTile, std::move(navMeshData)); } } - return replaceTile(navMeshCacheItem, changedTile, std::move(cachedNavMeshData)); + return navMeshCacheItem->lock()->updateTile(changedTile, std::move(cachedNavMeshData)); } } diff --git a/components/detournavigator/makenavmesh.hpp b/components/detournavigator/makenavmesh.hpp index 1dfa242ee..f9cf68a73 100644 --- a/components/detournavigator/makenavmesh.hpp +++ b/components/detournavigator/makenavmesh.hpp @@ -20,21 +20,6 @@ namespace DetourNavigator class RecastMesh; struct Settings; - enum class UpdateNavMeshStatus : unsigned - { - ignored = 0, - removed = 1 << 0, - added = 1 << 1, - replaced = removed | added, - failed = 1 << 2, - lost = removed | failed, - }; - - inline bool isSuccess(UpdateNavMeshStatus value) - { - return (static_cast(value) & static_cast(UpdateNavMeshStatus::failed)) == 0; - } - inline float getLength(const osg::Vec2i& value) { return std::sqrt(float(osg::square(value.x()) + osg::square(value.y()))); diff --git a/components/detournavigator/navigator.hpp b/components/detournavigator/navigator.hpp index a146fe0fb..561b7f02b 100644 --- a/components/detournavigator/navigator.hpp +++ b/components/detournavigator/navigator.hpp @@ -174,7 +174,7 @@ namespace DetourNavigator if (!navMesh) return out; const auto settings = getSettings(); - return findSmoothPath(navMesh.lock()->getValue(), toNavMeshCoordinates(settings, agentHalfExtents), + return findSmoothPath(navMesh->lockConst()->getImpl(), toNavMeshCoordinates(settings, agentHalfExtents), toNavMeshCoordinates(settings, stepSize), toNavMeshCoordinates(settings, start), toNavMeshCoordinates(settings, end), includeFlags, settings, out); } @@ -191,7 +191,9 @@ namespace DetourNavigator */ virtual std::map getNavMeshes() const = 0; - virtual Settings getSettings() const = 0; + virtual const Settings& getSettings() const = 0; + + virtual void reportStats(unsigned int frameNumber, osg::Stats& stats) const = 0; }; } diff --git a/components/detournavigator/navigatorimpl.cpp b/components/detournavigator/navigatorimpl.cpp index 1b83769f4..b743f26b2 100644 --- a/components/detournavigator/navigatorimpl.cpp +++ b/components/detournavigator/navigatorimpl.cpp @@ -21,10 +21,10 @@ namespace DetourNavigator void NavigatorImpl::removeAgent(const osg::Vec3f& agentHalfExtents) { const auto it = mAgents.find(agentHalfExtents); - if (it == mAgents.end() || --it->second) + if (it == mAgents.end()) return; - mAgents.erase(it); - mNavMeshManager.reset(agentHalfExtents); + if (it->second > 0) + --it->second; } bool NavigatorImpl::addObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform) @@ -113,6 +113,7 @@ namespace DetourNavigator void NavigatorImpl::update(const osg::Vec3f& playerPosition) { + removeUnusedNavMeshes(); for (const auto& v : mAgents) mNavMeshManager.update(playerPosition, v.first); } @@ -132,11 +133,16 @@ namespace DetourNavigator return mNavMeshManager.getNavMeshes(); } - Settings NavigatorImpl::getSettings() const + const Settings& NavigatorImpl::getSettings() const { return mSettings; } + void NavigatorImpl::reportStats(unsigned int frameNumber, osg::Stats& stats) const + { + mNavMeshManager.reportStats(frameNumber, stats); + } + void NavigatorImpl::updateAvoidShapeId(const ObjectId id, const ObjectId avoidId) { updateId(id, avoidId, mWaterIds); @@ -156,4 +162,15 @@ namespace DetourNavigator inserted.first->second = updateId; } } + + void NavigatorImpl::removeUnusedNavMeshes() + { + for (auto it = mAgents.begin(); it != mAgents.end();) + { + if (it->second == 0 && mNavMeshManager.reset(it->first)) + it = mAgents.erase(it); + else + ++it; + } + } } diff --git a/components/detournavigator/navigatorimpl.hpp b/components/detournavigator/navigatorimpl.hpp index 975055984..b6b3b1b7f 100644 --- a/components/detournavigator/navigatorimpl.hpp +++ b/components/detournavigator/navigatorimpl.hpp @@ -46,7 +46,9 @@ namespace DetourNavigator std::map getNavMeshes() const override; - Settings getSettings() const override; + const Settings& getSettings() const override; + + void reportStats(unsigned int frameNumber, osg::Stats& stats) const override; private: Settings mSettings; @@ -58,6 +60,7 @@ namespace DetourNavigator void updateAvoidShapeId(const ObjectId id, const ObjectId avoidId); void updateWaterShapeId(const ObjectId id, const ObjectId waterId); void updateId(const ObjectId id, const ObjectId waterId, std::unordered_map& ids); + void removeUnusedNavMeshes(); }; } diff --git a/components/detournavigator/navigatorstub.hpp b/components/detournavigator/navigatorstub.hpp index 5d82d2f59..885482a45 100644 --- a/components/detournavigator/navigatorstub.hpp +++ b/components/detournavigator/navigatorstub.hpp @@ -5,8 +5,9 @@ namespace DetourNavigator { - struct NavigatorStub final : public Navigator + class NavigatorStub final : public Navigator { + public: NavigatorStub() = default; void addAgent(const osg::Vec3f& /*agentHalfExtents*/) override {} @@ -65,7 +66,7 @@ namespace DetourNavigator SharedNavMeshCacheItem getNavMesh(const osg::Vec3f& /*agentHalfExtents*/) const override { - return SharedNavMeshCacheItem(); + return mEmptyNavMeshCacheItem; } std::map getNavMeshes() const override @@ -73,10 +74,16 @@ namespace DetourNavigator return std::map(); } - Settings getSettings() const override + const Settings& getSettings() const override { - return Settings {}; + return mDefaultSettings; } + + void reportStats(unsigned int /*frameNumber*/, osg::Stats& /*stats*/) const override {} + + private: + Settings mDefaultSettings {}; + SharedNavMeshCacheItem mEmptyNavMeshCacheItem; }; } diff --git a/components/detournavigator/navmeshcacheitem.hpp b/components/detournavigator/navmeshcacheitem.hpp index e64b9d138..f13341397 100644 --- a/components/detournavigator/navmeshcacheitem.hpp +++ b/components/detournavigator/navmeshcacheitem.hpp @@ -4,29 +4,113 @@ #include "sharednavmesh.hpp" #include "tileposition.hpp" #include "navmeshtilescache.hpp" +#include "dtstatus.hpp" #include +#include + #include namespace DetourNavigator { - class NavMeshCacheItem + enum class UpdateNavMeshStatus : unsigned + { + ignored = 0, + removed = 1 << 0, + added = 1 << 1, + replaced = removed | added, + failed = 1 << 2, + lost = removed | failed, + }; + + inline bool isSuccess(UpdateNavMeshStatus value) + { + return (static_cast(value) & static_cast(UpdateNavMeshStatus::failed)) == 0; + } + + class UpdateNavMeshStatusBuilder { public: - NavMeshCacheItem(const NavMeshPtr& value, std::size_t generation) - : mValue(value), mGeneration(generation), mNavMeshRevision(0) + UpdateNavMeshStatusBuilder() = default; + + UpdateNavMeshStatusBuilder removed(bool value) { + if (value) + set(UpdateNavMeshStatus::removed); + else + unset(UpdateNavMeshStatus::removed); + return *this; } - const dtNavMesh& getValue() const + UpdateNavMeshStatusBuilder added(bool value) { - return *mValue; + if (value) + set(UpdateNavMeshStatus::added); + else + unset(UpdateNavMeshStatus::added); + return *this; } - dtNavMesh& getValue() + UpdateNavMeshStatusBuilder failed(bool value) { - return *mValue; + if (value) + set(UpdateNavMeshStatus::failed); + else + unset(UpdateNavMeshStatus::failed); + return *this; + } + + UpdateNavMeshStatus getResult() const + { + return mResult; + } + + private: + UpdateNavMeshStatus mResult = UpdateNavMeshStatus::ignored; + + void set(UpdateNavMeshStatus value) + { + mResult = static_cast(static_cast(mResult) | static_cast(value)); + } + + void unset(UpdateNavMeshStatus value) + { + mResult = static_cast(static_cast(mResult) & ~static_cast(value)); + } + }; + + inline unsigned char* getRawData(NavMeshData& navMeshData) + { + return navMeshData.mValue.get(); + } + + inline unsigned char* getRawData(NavMeshTilesCache::Value& cachedNavMeshData) + { + return cachedNavMeshData.get().mValue; + } + + inline int getSize(const NavMeshData& navMeshData) + { + return navMeshData.mSize; + } + + inline int getSize(const NavMeshTilesCache::Value& cachedNavMeshData) + { + return cachedNavMeshData.get().mSize; + } + + class NavMeshCacheItem + { + public: + NavMeshCacheItem(const NavMeshPtr& impl, std::size_t generation) + : mImpl(impl), mGeneration(generation), mNavMeshRevision(0) + { + } + + const dtNavMesh& getImpl() const + { + return *mImpl; } std::size_t getGeneration() const @@ -39,6 +123,38 @@ namespace DetourNavigator return mNavMeshRevision; } + template + UpdateNavMeshStatus updateTile(const TilePosition& position, T&& navMeshData) + { + const auto removed = removeTileImpl(position); + const auto addStatus = addTileImpl(getRawData(navMeshData), getSize(navMeshData)); + if (dtStatusSucceed(addStatus)) + { + setUsedTile(position, std::forward(navMeshData)); + return UpdateNavMeshStatusBuilder().added(true).removed(removed).getResult(); + } + else + { + if (removed) + removeUsedTile(position); + return UpdateNavMeshStatusBuilder().removed(removed).failed((addStatus & DT_OUT_OF_MEMORY) != 0).getResult(); + } + } + + UpdateNavMeshStatus removeTile(const TilePosition& position) + { + const auto removed = dtStatusSucceed(removeTileImpl(position)); + if (removed) + removeUsedTile(position); + return UpdateNavMeshStatusBuilder().removed(removed).getResult(); + } + + private: + NavMeshPtr mImpl; + std::size_t mGeneration; + std::size_t mNavMeshRevision; + std::map> mUsedTiles; + void setUsedTile(const TilePosition& tilePosition, NavMeshTilesCache::Value value) { mUsedTiles[tilePosition] = std::make_pair(std::move(value), NavMeshData()); @@ -57,14 +173,26 @@ namespace DetourNavigator ++mNavMeshRevision; } - private: - NavMeshPtr mValue; - std::size_t mGeneration; - std::size_t mNavMeshRevision; - std::map> mUsedTiles; + dtStatus addTileImpl(unsigned char* data, int size) + { + const int doNotTransferOwnership = 0; + const dtTileRef lastRef = 0; + dtTileRef* const result = nullptr; + return mImpl->addTile(data, size, doNotTransferOwnership, lastRef, result); + } + + dtStatus removeTileImpl(const TilePosition& position) + { + const int layer = 0; + const auto tileRef = mImpl->getTileRefAt(position.x(), position.y(), layer); + unsigned char** const data = nullptr; + int* const dataSize = nullptr; + return mImpl->removeTile(tileRef, data, dataSize); + } }; - using SharedNavMeshCacheItem = Misc::SharedGuarded; + using GuardedNavMeshCacheItem = Misc::ScopeGuarded; + using SharedNavMeshCacheItem = std::shared_ptr; } #endif diff --git a/components/detournavigator/navmeshmanager.cpp b/components/detournavigator/navmeshmanager.cpp index 217c17e41..a43410e91 100644 --- a/components/detournavigator/navmeshmanager.cpp +++ b/components/detournavigator/navmeshmanager.cpp @@ -16,6 +16,21 @@ namespace { return current == add ? current : ChangeType::mixed; } + + /// Safely reset shared_ptr with definite underlying object destrutor call. + /// Assuming there is another thread holding copy of this shared_ptr or weak_ptr to this shared_ptr. + template + bool resetIfUnique(std::shared_ptr& ptr) + { + const std::weak_ptr weak(ptr); + ptr.reset(); + if (auto shared = weak.lock()) + { + ptr = std::move(shared); + return false; + } + return true; + } } namespace DetourNavigator @@ -79,13 +94,22 @@ namespace DetourNavigator if (cached != mCache.end()) return; mCache.insert(std::make_pair(agentHalfExtents, - std::make_shared(makeEmptyNavMesh(mSettings), ++mGenerationCounter))); + std::make_shared(makeEmptyNavMesh(mSettings), ++mGenerationCounter))); log("cache add for agent=", agentHalfExtents); } - void NavMeshManager::reset(const osg::Vec3f& agentHalfExtents) + bool NavMeshManager::reset(const osg::Vec3f& agentHalfExtents) { + const auto it = mCache.find(agentHalfExtents); + if (it == mCache.end()) + return true; + if (!resetIfUnique(it->second)) + return false; mCache.erase(agentHalfExtents); + mChangedTiles.erase(agentHalfExtents); + mPlayerTile.erase(agentHalfExtents); + mLastRecastMeshManagerRevision.erase(agentHalfExtents); + return true; } void NavMeshManager::addOffMeshConnection(const ObjectId id, const osg::Vec3f& start, const osg::Vec3f& end) @@ -139,8 +163,8 @@ namespace DetourNavigator } const auto changedTiles = mChangedTiles.find(agentHalfExtents); { - const auto locked = cached.lock(); - const auto& navMesh = locked->getValue(); + const auto locked = cached->lockConst(); + const auto& navMesh = locked->getImpl(); if (changedTiles != mChangedTiles.end()) { for (const auto& tile : changedTiles->second) @@ -152,10 +176,6 @@ namespace DetourNavigator else tileToPost->second = addChangeType(tileToPost->second, tile.second); } - for (const auto& tile : tilesToPost) - changedTiles->second.erase(tile.first); - if (changedTiles->second.empty()) - mChangedTiles.erase(changedTiles); } const auto maxTiles = std::min(mSettings.mMaxTilesNumber, navMesh.getParams()->maxTiles); mRecastMeshManager.forEachTilePosition([&] (const TilePosition& tile) @@ -171,6 +191,8 @@ namespace DetourNavigator }); } mAsyncNavMeshUpdater.post(agentHalfExtents, cached, playerTile, tilesToPost); + if (changedTiles != mChangedTiles.end()) + changedTiles->second.clear(); log("cache update posted for agent=", agentHalfExtents, " playerTile=", lastPlayerTile->second, " recastMeshManagerRevision=", lastRevision); @@ -191,6 +213,11 @@ namespace DetourNavigator return mCache; } + void NavMeshManager::reportStats(unsigned int frameNumber, osg::Stats& stats) const + { + mAsyncNavMeshUpdater.reportStats(frameNumber, stats); + } + void NavMeshManager::addChangedTiles(const btCollisionShape& shape, const btTransform& transform, const ChangeType changeType) { diff --git a/components/detournavigator/navmeshmanager.hpp b/components/detournavigator/navmeshmanager.hpp index ae006da73..3ef898b05 100644 --- a/components/detournavigator/navmeshmanager.hpp +++ b/components/detournavigator/navmeshmanager.hpp @@ -36,7 +36,7 @@ namespace DetourNavigator bool removeWater(const osg::Vec2i& cellPosition); - void reset(const osg::Vec3f& agentHalfExtents); + bool reset(const osg::Vec3f& agentHalfExtents); void addOffMeshConnection(const ObjectId id, const osg::Vec3f& start, const osg::Vec3f& end); @@ -50,6 +50,8 @@ namespace DetourNavigator std::map getNavMeshes() const; + void reportStats(unsigned int frameNumber, osg::Stats& stats) const; + private: const Settings& mSettings; TileCachedRecastMeshManager mRecastMeshManager; diff --git a/components/detournavigator/navmeshtilescache.cpp b/components/detournavigator/navmeshtilescache.cpp index 418e69e82..466d2e708 100644 --- a/components/detournavigator/navmeshtilescache.cpp +++ b/components/detournavigator/navmeshtilescache.cpp @@ -1,6 +1,10 @@ #include "navmeshtilescache.hpp" #include "exceptions.hpp" +#include + +#include + namespace DetourNavigator { namespace @@ -61,8 +65,7 @@ namespace DetourNavigator if (tileValues == agentValues->second.end()) return Value(); - // TODO: use different function to make key to avoid unnecessary std::string allocation - const auto tile = tileValues->second.mMap.find(makeNavMeshKey(recastMesh, offMeshConnections)); + const auto tile = tileValues->second.mMap.find(RecastMeshKeyView(recastMesh, offMeshConnections)); if (tile == tileValues->second.mMap.end()) return Value(); @@ -85,7 +88,7 @@ namespace DetourNavigator if (navMeshSize > mFreeNavMeshDataSize + (mMaxNavMeshDataSize - mUsedNavMeshDataSize)) return Value(); - const auto navMeshKey = makeNavMeshKey(recastMesh, offMeshConnections); + auto navMeshKey = makeNavMeshKey(recastMesh, offMeshConnections); const auto itemSize = navMeshSize + 2 * navMeshKey.size(); if (itemSize > mFreeNavMeshDataSize + (mMaxNavMeshDataSize - mUsedNavMeshDataSize)) @@ -94,9 +97,8 @@ namespace DetourNavigator while (!mFreeItems.empty() && mUsedNavMeshDataSize + itemSize > mMaxNavMeshDataSize) removeLeastRecentlyUsed(); - const auto iterator = mFreeItems.emplace(mFreeItems.end(), agentHalfExtents, changedTile, navMeshKey); - // TODO: use std::string_view or some alternative to avoid navMeshKey copy into both mFreeItems and mValues - const auto emplaced = mValues[agentHalfExtents][changedTile].mMap.emplace(navMeshKey, iterator); + const auto iterator = mFreeItems.emplace(mFreeItems.end(), agentHalfExtents, changedTile, std::move(navMeshKey)); + const auto emplaced = mValues[agentHalfExtents][changedTile].mMap.emplace(iterator->mNavMeshKey, iterator); if (!emplaced.second) { @@ -113,6 +115,24 @@ namespace DetourNavigator return Value(*this, iterator); } + void NavMeshTilesCache::reportStats(unsigned int frameNumber, osg::Stats& stats) const + { + std::size_t navMeshCacheSize = 0; + std::size_t usedNavMeshTiles = 0; + std::size_t cachedNavMeshTiles = 0; + + { + const std::lock_guard lock(mMutex); + navMeshCacheSize = mUsedNavMeshDataSize; + usedNavMeshTiles = mBusyItems.size(); + cachedNavMeshTiles = mFreeItems.size(); + } + + stats.setAttribute(frameNumber, "NavMesh CacheSize", navMeshCacheSize); + stats.setAttribute(frameNumber, "NavMesh UsedTiles", usedNavMeshTiles); + stats.setAttribute(frameNumber, "NavMesh CachedTiles", cachedNavMeshTiles); + } + void NavMeshTilesCache::removeLeastRecentlyUsed() { const auto& item = mFreeItems.back(); @@ -131,9 +151,10 @@ namespace DetourNavigator mUsedNavMeshDataSize -= getSize(item); mFreeNavMeshDataSize -= getSize(item); - mFreeItems.pop_back(); tileValues->second.mMap.erase(value); + mFreeItems.pop_back(); + if (!tileValues->second.mMap.empty()) return; @@ -163,4 +184,69 @@ namespace DetourNavigator mFreeItems.splice(mFreeItems.begin(), mBusyItems, iterator); mFreeNavMeshDataSize += getSize(*iterator); } + + namespace + { + struct CompareBytes + { + const char* mRhsIt; + const char* mRhsEnd; + + template + int operator ()(const std::vector& lhs) + { + const auto lhsBegin = reinterpret_cast(lhs.data()); + const auto lhsEnd = reinterpret_cast(lhs.data() + lhs.size()); + const auto lhsSize = static_cast(lhsEnd - lhsBegin); + const auto rhsSize = static_cast(mRhsEnd - mRhsIt); + + if (lhsBegin == nullptr || mRhsIt == nullptr) + { + if (lhsSize < rhsSize) + return -1; + else if (lhsSize > rhsSize) + return 1; + else + return 0; + } + + const auto size = std::min(lhsSize, rhsSize); + + if (const auto result = std::memcmp(lhsBegin, mRhsIt, size)) + return result; + + if (lhsSize > rhsSize) + return 1; + + mRhsIt += size; + + return 0; + } + }; + } + + int NavMeshTilesCache::RecastMeshKeyView::compare(const std::string& other) const + { + CompareBytes compareBytes {other.data(), other.data() + other.size()}; + + if (const auto result = compareBytes(mRecastMesh.get().getIndices())) + return result; + + if (const auto result = compareBytes(mRecastMesh.get().getVertices())) + return result; + + if (const auto result = compareBytes(mRecastMesh.get().getAreaTypes())) + return result; + + if (const auto result = compareBytes(mRecastMesh.get().getWater())) + return result; + + if (const auto result = compareBytes(mOffMeshConnections.get())) + return result; + + if (compareBytes.mRhsIt < compareBytes.mRhsEnd) + return -1; + + return 0; + } } diff --git a/components/detournavigator/navmeshtilescache.hpp b/components/detournavigator/navmeshtilescache.hpp index e244cda9d..5d5db47a8 100644 --- a/components/detournavigator/navmeshtilescache.hpp +++ b/components/detournavigator/navmeshtilescache.hpp @@ -10,6 +10,12 @@ #include #include #include +#include + +namespace osg +{ + class Stats; +} namespace DetourNavigator { @@ -104,14 +110,68 @@ namespace DetourNavigator const RecastMesh& recastMesh, const std::vector& offMeshConnections, NavMeshData&& value); + void reportStats(unsigned int frameNumber, osg::Stats& stats) const; + private: + class KeyView + { + public: + KeyView() = default; + + KeyView(const std::string& value) + : mValue(&value) {} + + const std::string& getValue() const + { + assert(mValue); + return *mValue; + } + + virtual int compare(const std::string& other) const + { + assert(mValue); + return mValue->compare(other); + } + + virtual bool isLess(const KeyView& other) const + { + assert(mValue); + return other.compare(*mValue) > 0; + } + + friend bool operator <(const KeyView& lhs, const KeyView& rhs) + { + return lhs.isLess(rhs); + } + + private: + const std::string* mValue = nullptr; + }; + + class RecastMeshKeyView : public KeyView + { + public: + RecastMeshKeyView(const RecastMesh& recastMesh, const std::vector& offMeshConnections) + : mRecastMesh(recastMesh), mOffMeshConnections(offMeshConnections) {} + + int compare(const std::string& other) const override; + + bool isLess(const KeyView& other) const override + { + return compare(other.getValue()) < 0; + } + + private: + std::reference_wrapper mRecastMesh; + std::reference_wrapper> mOffMeshConnections; + }; struct TileMap { - std::map mMap; + std::map mMap; }; - std::mutex mMutex; + mutable std::mutex mMutex; std::size_t mMaxNavMeshDataSize; std::size_t mUsedNavMeshDataSize; std::size_t mFreeNavMeshDataSize; diff --git a/components/detournavigator/tilecachedrecastmeshmanager.cpp b/components/detournavigator/tilecachedrecastmeshmanager.cpp index b878c2d3e..0cb1cfb80 100644 --- a/components/detournavigator/tilecachedrecastmeshmanager.cpp +++ b/components/detournavigator/tilecachedrecastmeshmanager.cpp @@ -61,8 +61,8 @@ namespace DetourNavigator for (const auto& tile : currentTiles) if (!newTiles.count(tile) && removeTile(id, tile, tiles.get())) changedTiles.push_back(tile); - std::swap(currentTiles, newTiles); } + std::swap(currentTiles, newTiles); if (!changedTiles.empty()) ++mRevision; return changedTiles; diff --git a/components/esm/esmreader.cpp b/components/esm/esmreader.cpp index 2f4f4917c..278a1541a 100644 --- a/components/esm/esmreader.cpp +++ b/components/esm/esmreader.cpp @@ -127,7 +127,7 @@ std::string ESMReader::getHString() // them. For some reason, they break the rules, and contain a byte // (value 0) even if the header says there is no data. If // Morrowind accepts it, so should we. - if (mCtx.leftSub == 0) + if (mCtx.leftSub == 0 && mCtx.leftRec != 0) { // Skip the following zero byte mCtx.leftRec--; diff --git a/components/esm/loadltex.hpp b/components/esm/loadltex.hpp index 1604e9281..e3e258246 100644 --- a/components/esm/loadltex.hpp +++ b/components/esm/loadltex.hpp @@ -11,18 +11,9 @@ class ESMWriter; /* * Texture used for texturing landscape. - * - * They are probably indexed by 'num', not 'id', but I don't know for - * sure. And num is not unique between files, so one option is to keep - * a separate list for each input file (that has LTEX records, of - * course.) We also need to resolve references to already existing - * land textures to save space. - - * I'm not sure if it is even possible to override existing land - * textures, probably not. I'll have to try it, and have to mimic the - * behaviour of morrowind. First, check what you are allowed to do in - * the editor. Then make an esp which changes a commonly used land - * texture, and see if it affects the game. + * They are indexed by 'num', but still use 'id' to override base records. + * Original editor even does not allow to create new records with existing ID's. + * TODO: currently OpenMW-CS does not allow to override LTEX records at all. */ struct LandTexture diff --git a/components/esm/loadscpt.cpp b/components/esm/loadscpt.cpp index 808b416d7..b41dc496f 100644 --- a/components/esm/loadscpt.cpp +++ b/components/esm/loadscpt.cpp @@ -104,7 +104,7 @@ namespace ESM } mScriptData.resize(subSize); - esm.getExact(&mScriptData[0], mScriptData.size()); + esm.getExact(mScriptData.data(), mScriptData.size()); break; } case ESM::FourCC<'S','C','T','X'>::value: @@ -156,7 +156,7 @@ namespace ESM } esm.startSubRecord("SCDT"); - esm.write(reinterpret_cast(&mScriptData[0]), mData.mScriptDataSize); + esm.write(reinterpret_cast(mScriptData.data()), mData.mScriptDataSize); esm.endRecord("SCDT"); esm.writeHNOString("SCTX", mScriptText); diff --git a/components/esmterrain/storage.cpp b/components/esmterrain/storage.cpp index 567d93bbd..1e23569b5 100644 --- a/components/esmterrain/storage.cpp +++ b/components/esmterrain/storage.cpp @@ -46,19 +46,6 @@ namespace ESMTerrain { } - const ESM::Land::LandData *LandObject::getData(int flags) const - { - if ((mData.mDataLoaded & flags) != flags) - return nullptr; - return &mData; - } - - int LandObject::getPlugin() const - { - return mLand->mPlugin; - } - - const float defaultHeight = ESM::Land::DEFAULT_HEIGHT; Storage::Storage(const VFS::Manager *vfs, const std::string& normalMapPattern, const std::string& normalHeightMapPattern, bool autoUseNormalMaps, const std::string& specularMapPattern, bool autoUseSpecularMaps) @@ -158,7 +145,7 @@ namespace ESMTerrain normal.normalize(); } - void Storage::fixColour (osg::Vec4f& color, int cellX, int cellY, int col, int row, LandCache& cache) + void Storage::fixColour (osg::Vec4ub& color, int cellX, int cellY, int col, int row, LandCache& cache) { if (col == ESM::Land::LAND_SIZE-1) { @@ -175,22 +162,22 @@ namespace ESMTerrain const ESM::Land::LandData* data = land ? land->getData(ESM::Land::DATA_VCLR) : 0; if (data) { - color.r() = data->mColours[col*ESM::Land::LAND_SIZE*3+row*3] / 255.f; - color.g() = data->mColours[col*ESM::Land::LAND_SIZE*3+row*3+1] / 255.f; - color.b() = data->mColours[col*ESM::Land::LAND_SIZE*3+row*3+2] / 255.f; + color.r() = data->mColours[col*ESM::Land::LAND_SIZE*3+row*3]; + color.g() = data->mColours[col*ESM::Land::LAND_SIZE*3+row*3+1]; + color.b() = data->mColours[col*ESM::Land::LAND_SIZE*3+row*3+2]; } else { - color.r() = 1; - color.g() = 1; - color.b() = 1; + color.r() = 255; + color.g() = 255; + color.b() = 255; } } void Storage::fillVertexBuffers (int lodLevel, float size, const osg::Vec2f& center, osg::ref_ptr positions, osg::ref_ptr normals, - osg::ref_ptr colours) + osg::ref_ptr colours) { // LOD level n means every 2^n-th vertex is kept size_t increment = static_cast(1) << lodLevel; @@ -207,7 +194,7 @@ namespace ESMTerrain colours->resize(numVerts*numVerts); osg::Vec3f normal; - osg::Vec4f color; + osg::Vec4ub color; float vertY = 0; float vertX = 0; @@ -295,20 +282,20 @@ namespace ESMTerrain if (colourData) { for (int i=0; i<3; ++i) - color[i] = colourData->mColours[srcArrayIndex+i] / 255.f; + color[i] = colourData->mColours[srcArrayIndex+i]; } else { - color.r() = 1; - color.g() = 1; - color.b() = 1; + color.r() = 255; + color.g() = 255; + color.b() = 255; } // Unlike normals, colors mostly connect seamlessly between cells, but not always... if (col == ESM::Land::LAND_SIZE-1 || row == ESM::Land::LAND_SIZE-1) fixColour(color, cellX, cellY, col, row, cache); - color.a() = 1; + color.a() = 255; (*colours)[static_cast(vertX*numVerts + vertY)] = color; @@ -388,8 +375,7 @@ namespace ESMTerrain return texture; } - void Storage::getBlendmaps(float chunkSize, const osg::Vec2f &chunkCenter, - bool pack, ImageVector &blendmaps, std::vector &layerList) + void Storage::getBlendmaps(float chunkSize, const osg::Vec2f &chunkCenter, ImageVector &blendmaps, std::vector &layerList) { osg::Vec2f origin = chunkCenter - osg::Vec2f(chunkSize/2.f, chunkSize/2.f); int cellX = static_cast(std::floor(origin.x())); @@ -429,11 +415,8 @@ namespace ESMTerrain layerList.push_back(getLayerInfo(getTextureName(*it))); } - int numTextures = textureIndices.size(); - // numTextures-1 since the base layer doesn't need blending - int numBlendmaps = pack ? static_cast(std::ceil((numTextures - 1) / 4.f)) : (numTextures - 1); - - int channels = pack ? 4 : 1; + // size-1 since the base layer doesn't need blending + int numBlendmaps = textureIndices.size() - 1; // Second iteration - create and fill in the blend maps const int blendmapSize = (realTextureSize-1) * chunkSize + 1; @@ -443,10 +426,8 @@ namespace ESMTerrain for (int i=0; i image (new osg::Image); - image->allocateImage(blendmapImageSize, blendmapImageSize, 1, format, GL_UNSIGNED_BYTE); + image->allocateImage(blendmapImageSize, blendmapImageSize, 1, GL_ALPHA, GL_UNSIGNED_BYTE); unsigned char* pData = image->data(); for (int y=0; ysecond; - int blendIndex = (pack ? static_cast(std::floor((layerIndex - 1) / 4.f)) : layerIndex - 1); - int channel = pack ? std::max(0, (layerIndex-1) % 4) : 0; - int alpha = (blendIndex == i) ? 255 : 0; + int alpha = (layerIndex == i+1) ? 255 : 0; int realY = (blendmapSize - y - 1)*imageScaleFactor; int realX = x*imageScaleFactor; - pData[((realY+0)*blendmapImageSize + realX + 0)*channels + channel] = alpha; - pData[((realY+1)*blendmapImageSize + realX + 0)*channels + channel] = alpha; - pData[((realY+0)*blendmapImageSize + realX + 1)*channels + channel] = alpha; - pData[((realY+1)*blendmapImageSize + realX + 1)*channels + channel] = alpha; + pData[(realY+0)*blendmapImageSize + realX + 0] = alpha; + pData[(realY+1)*blendmapImageSize + realX + 0] = alpha; + pData[(realY+0)*blendmapImageSize + realX + 1] = alpha; + pData[(realY+1)*blendmapImageSize + realX + 1] = alpha; } } blendmaps.push_back(image); diff --git a/components/esmterrain/storage.hpp b/components/esmterrain/storage.hpp index f3300f748..613d2e218 100644 --- a/components/esmterrain/storage.hpp +++ b/components/esmterrain/storage.hpp @@ -29,8 +29,17 @@ namespace ESMTerrain META_Object(ESMTerrain, LandObject) - const ESM::Land::LandData* getData(int flags) const; - int getPlugin() const; + inline const ESM::Land::LandData* getData(int flags) const + { + if ((mData.mDataLoaded & flags) != flags) + return nullptr; + return &mData; + } + + inline int getPlugin() const + { + return mLand->mPlugin; + } private: const ESM::Land* mLand; @@ -75,7 +84,7 @@ namespace ESMTerrain virtual void fillVertexBuffers (int lodLevel, float size, const osg::Vec2f& center, osg::ref_ptr positions, osg::ref_ptr normals, - osg::ref_ptr colours); + osg::ref_ptr colours); /// Create textures holding layer blend values for a terrain chunk. /// @note The terrain chunk shouldn't be larger than one cell since otherwise we might @@ -83,14 +92,10 @@ namespace ESMTerrain /// @note May be called from background threads. /// @param chunkSize size of the terrain chunk in cell units /// @param chunkCenter center of the chunk in cell units - /// @param pack Whether to pack blend values for up to 4 layers into one texture (one in each channel) - - /// otherwise, each texture contains blend values for one layer only. Shader-based rendering - /// can utilize packing, FFP can't. /// @param blendmaps created blendmaps will be written here /// @param layerList names of the layer textures used will be written here - virtual void getBlendmaps (float chunkSize, const osg::Vec2f& chunkCenter, bool pack, - ImageVector& blendmaps, - std::vector& layerList); + virtual void getBlendmaps (float chunkSize, const osg::Vec2f& chunkCenter, ImageVector& blendmaps, + std::vector& layerList); virtual float getHeightAt (const osg::Vec3f& worldPos); @@ -105,21 +110,20 @@ namespace ESMTerrain private: const VFS::Manager* mVFS; - void fixNormal (osg::Vec3f& normal, int cellX, int cellY, int col, int row, LandCache& cache); - void fixColour (osg::Vec4f& colour, int cellX, int cellY, int col, int row, LandCache& cache); - void averageNormal (osg::Vec3f& normal, int cellX, int cellY, int col, int row, LandCache& cache); + inline void fixNormal (osg::Vec3f& normal, int cellX, int cellY, int col, int row, LandCache& cache); + inline void fixColour (osg::Vec4ub& colour, int cellX, int cellY, int col, int row, LandCache& cache); + inline void averageNormal (osg::Vec3f& normal, int cellX, int cellY, int col, int row, LandCache& cache); - float getVertexHeight (const ESM::Land::LandData* data, int x, int y); + inline float getVertexHeight (const ESM::Land::LandData* data, int x, int y); - const LandObject* getLand(int cellX, int cellY, LandCache& cache); + inline const LandObject* getLand(int cellX, int cellY, LandCache& cache); // Since plugins can define new texture palettes, we need to know the plugin index too // in order to retrieve the correct texture name. // pair typedef std::pair UniqueTextureId; - UniqueTextureId getVtexIndexAt(int cellX, int cellY, - int x, int y, LandCache&); + inline UniqueTextureId getVtexIndexAt(int cellX, int cellY, int x, int y, LandCache&); std::string getTextureName (UniqueTextureId id); std::map mLayerInfoMap; diff --git a/components/misc/guarded.hpp b/components/misc/guarded.hpp index 114a77b10..559476867 100644 --- a/components/misc/guarded.hpp +++ b/components/misc/guarded.hpp @@ -83,38 +83,6 @@ namespace Misc std::mutex mMutex; T mValue; }; - - template - class SharedGuarded - { - public: - SharedGuarded() - : mMutex(std::make_shared()), mValue() - {} - - SharedGuarded(std::shared_ptr value) - : mMutex(std::make_shared()), mValue(std::move(value)) - {} - - Locked lock() const - { - return Locked(*mMutex, *mValue); - } - - Locked lockConst() const - { - return Locked(*mMutex, *mValue); - } - - operator bool() const - { - return static_cast(mValue); - } - - private: - std::shared_ptr mMutex; - std::shared_ptr mValue; - }; } #endif diff --git a/components/nif/data.cpp b/components/nif/data.cpp index d19c8321e..086021930 100644 --- a/components/nif/data.cpp +++ b/components/nif/data.cpp @@ -120,7 +120,7 @@ void NiRotatingParticlesData::read(NIFStream *nif) void NiPosData::read(NIFStream *nif) { - mKeyList.reset(new Vector3KeyMap); + mKeyList = std::make_shared(); mKeyList->read(nif); } @@ -128,14 +128,14 @@ void NiUVData::read(NIFStream *nif) { for(int i = 0;i < 4;i++) { - mKeyList[i].reset(new FloatKeyMap); + mKeyList[i] = std::make_shared(); mKeyList[i]->read(nif); } } void NiFloatData::read(NIFStream *nif) { - mKeyList.reset(new FloatKeyMap); + mKeyList = std::make_shared(); mKeyList->read(nif); } @@ -177,7 +177,7 @@ void NiPixelData::read(NIFStream *nif) void NiColorData::read(NIFStream *nif) { - mKeyMap.reset(new Vector4KeyMap); + mKeyMap = std::make_shared(); mKeyMap->read(nif); } @@ -231,7 +231,7 @@ void NiMorphData::read(NIFStream *nif) mMorphs.resize(morphCount); for(int i = 0;i < morphCount;i++) { - mMorphs[i].mKeyFrames.reset(new FloatKeyMap); + mMorphs[i].mKeyFrames = std::make_shared(); mMorphs[i].mKeyFrames->read(nif, true); nif->getVector3s(mMorphs[i].mVertices, vertCount); } @@ -239,22 +239,22 @@ void NiMorphData::read(NIFStream *nif) void NiKeyframeData::read(NIFStream *nif) { - mRotations.reset(new QuaternionKeyMap); + mRotations = std::make_shared(); mRotations->read(nif); if(mRotations->mInterpolationType == Vector3KeyMap::sXYZInterpolation) { //Chomp unused float nif->getFloat(); - mXRotations.reset(new FloatKeyMap); - mYRotations.reset(new FloatKeyMap); - mZRotations.reset(new FloatKeyMap); + mXRotations = std::make_shared(); + mYRotations = std::make_shared(); + mZRotations = std::make_shared(); mXRotations->read(nif, true); mYRotations->read(nif, true); mZRotations->read(nif, true); } - mTranslations.reset(new Vector3KeyMap); + mTranslations = std::make_shared(); mTranslations->read(nif); - mScales.reset(new FloatKeyMap); + mScales = std::make_shared(); mScales->read(nif); } diff --git a/components/nifosg/controller.cpp b/components/nifosg/controller.cpp index 934b3b914..4029e9b15 100644 --- a/components/nifosg/controller.cpp +++ b/components/nifosg/controller.cpp @@ -311,10 +311,11 @@ void VisController::operator() (osg::Node* node, osg::NodeVisitor* nv) RollController::RollController(const Nif::NiFloatData *data) : mData(data->mKeyList, 1.f) + , mStartingTime(0) { } -RollController::RollController() +RollController::RollController() : mStartingTime(0) { } diff --git a/components/resource/objectcache.cpp b/components/resource/objectcache.cpp deleted file mode 100644 index ffe150c74..000000000 --- a/components/resource/objectcache.cpp +++ /dev/null @@ -1,165 +0,0 @@ -/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield - * - * This library is open source and may be redistributed and/or modified under - * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or - * (at your option) any later version. The full license is in LICENSE file - * included with this distribution, and on the openscenegraph.org website. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * OpenSceneGraph Public License for more details. -*/ - -#include "objectcache.hpp" - -#include -#include - -namespace Resource -{ - -//////////////////////////////////////////////////////////////////////////////////////////// -// -// ObjectCache -// -ObjectCache::ObjectCache(): - osg::Referenced(true) -{ -} - -ObjectCache::~ObjectCache() -{ -} - -void ObjectCache::addEntryToObjectCache(const std::string& filename, osg::Object* object, double timestamp) -{ - if (!object) - { - OSG_ALWAYS << " trying to add NULL object to cache for " << filename << std::endl; - return; - } - OpenThreads::ScopedLock lock(_objectCacheMutex); - _objectCache[filename]=ObjectTimeStampPair(object,timestamp); -} - -osg::ref_ptr ObjectCache::getRefFromObjectCache(const std::string& fileName) -{ - OpenThreads::ScopedLock lock(_objectCacheMutex); - ObjectCacheMap::iterator itr = _objectCache.find(fileName); - if (itr!=_objectCache.end()) - { - return itr->second.first; - } - else return 0; -} - -bool ObjectCache::checkInObjectCache(const std::string &fileName, double timeStamp) -{ - OpenThreads::ScopedLock lock(_objectCacheMutex); - ObjectCacheMap::iterator itr = _objectCache.find(fileName); - if (itr!=_objectCache.end()) - { - itr->second.second = timeStamp; - return true; - } - else return false; -} - -void ObjectCache::updateTimeStampOfObjectsInCacheWithExternalReferences(double referenceTime) -{ - OpenThreads::ScopedLock lock(_objectCacheMutex); - - // look for objects with external references and update their time stamp. - for(ObjectCacheMap::iterator itr=_objectCache.begin(); - itr!=_objectCache.end(); - ++itr) - { - // If ref count is greater than 1, the object has an external reference. - // If the timestamp is yet to be initialized, it needs to be updated too. - if (itr->second.first->referenceCount()>1 || itr->second.second == 0.0) - { - // So update it. - itr->second.second = referenceTime; - } - } -} - -void ObjectCache::removeExpiredObjectsInCache(double expiryTime) -{ - std::vector > objectsToRemove; - - { - OpenThreads::ScopedLock lock(_objectCacheMutex); - - // Remove expired entries from object cache - ObjectCacheMap::iterator oitr = _objectCache.begin(); - while(oitr != _objectCache.end()) - { - if (oitr->second.second<=expiryTime) - { - objectsToRemove.push_back(oitr->second.first); - _objectCache.erase(oitr++); - } - else - { - ++oitr; - } - } - } - - // note, actual unref happens outside of the lock - objectsToRemove.clear(); -} - -void ObjectCache::removeFromObjectCache(const std::string& fileName) -{ - OpenThreads::ScopedLock lock(_objectCacheMutex); - ObjectCacheMap::iterator itr = _objectCache.find(fileName); - if (itr!=_objectCache.end()) _objectCache.erase(itr); -} - -void ObjectCache::clear() -{ - OpenThreads::ScopedLock lock(_objectCacheMutex); - _objectCache.clear(); -} - -void ObjectCache::releaseGLObjects(osg::State* state) -{ - OpenThreads::ScopedLock lock(_objectCacheMutex); - - for(ObjectCacheMap::iterator itr = _objectCache.begin(); - itr != _objectCache.end(); - ++itr) - { - osg::Object* object = itr->second.first.get(); - object->releaseGLObjects(state); - } -} - -void ObjectCache::accept(osg::NodeVisitor &nv) -{ - OpenThreads::ScopedLock lock(_objectCacheMutex); - - for(ObjectCacheMap::iterator itr = _objectCache.begin(); - itr != _objectCache.end(); - ++itr) - { - osg::Object* object = itr->second.first.get(); - if (object) - { - osg::Node* node = dynamic_cast(object); - if (node) - node->accept(nv); - } - } -} - -unsigned int ObjectCache::getCacheSize() const -{ - OpenThreads::ScopedLock lock(_objectCacheMutex); - return _objectCache.size(); -} - -} diff --git a/components/resource/objectcache.hpp b/components/resource/objectcache.hpp index 72db835e8..cfd41f19c 100644 --- a/components/resource/objectcache.hpp +++ b/components/resource/objectcache.hpp @@ -1,5 +1,8 @@ // Resource ObjectCache for OpenMW, forked from osgDB ObjectCache by Robert Osfield, see copyright notice below. -// The main change from the upstream version is that removeExpiredObjectsInCache no longer keeps a lock while the unref happens. +// Changes: +// - removeExpiredObjectsInCache no longer keeps a lock while the unref happens. +// - template allows customized KeyType. +// - objects with uninitialized time stamp are not removed. /* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield * @@ -19,6 +22,7 @@ #include #include +#include #include #include @@ -32,11 +36,13 @@ namespace osg namespace Resource { -class ObjectCache : public osg::Referenced +template +class GenericObjectCache : public osg::Referenced { public: - ObjectCache(); + GenericObjectCache() + : osg::Referenced(true) {} /** For each object in the cache which has an reference count greater than 1 * (and therefore referenced by elsewhere in the application) set the time stamp @@ -44,59 +50,149 @@ class ObjectCache : public osg::Referenced * This would typically be called once per frame by applications which are doing database paging, * and need to prune objects that are no longer required. * The time used should be taken from the FrameStamp::getReferenceTime().*/ - void updateTimeStampOfObjectsInCacheWithExternalReferences(double referenceTime); + void updateTimeStampOfObjectsInCacheWithExternalReferences(double referenceTime) + { + // look for objects with external references and update their time stamp. + OpenThreads::ScopedLock lock(_objectCacheMutex); + for(typename ObjectCacheMap::iterator itr=_objectCache.begin(); itr!=_objectCache.end(); ++itr) + { + // If ref count is greater than 1, the object has an external reference. + // If the timestamp is yet to be initialized, it needs to be updated too. + if (itr->second.first->referenceCount()>1 || itr->second.second == 0.0) + itr->second.second = referenceTime; + } + } /** Removed object in the cache which have a time stamp at or before the specified expiry time. * This would typically be called once per frame by applications which are doing database paging, * and need to prune objects that are no longer required, and called after the a called * after the call to updateTimeStampOfObjectsInCacheWithExternalReferences(expirtyTime).*/ - void removeExpiredObjectsInCache(double expiryTime); + void removeExpiredObjectsInCache(double expiryTime) + { + std::vector > objectsToRemove; + { + OpenThreads::ScopedLock lock(_objectCacheMutex); + // Remove expired entries from object cache + typename ObjectCacheMap::iterator oitr = _objectCache.begin(); + while(oitr != _objectCache.end()) + { + if (oitr->second.second<=expiryTime) + { + objectsToRemove.push_back(oitr->second.first); + _objectCache.erase(oitr++); + } + else + ++oitr; + } + } + // note, actual unref happens outside of the lock + objectsToRemove.clear(); + } /** Remove all objects in the cache regardless of having external references or expiry times.*/ - void clear(); + void clear() + { + OpenThreads::ScopedLock lock(_objectCacheMutex); + _objectCache.clear(); + } - /** Add a filename,object,timestamp triple to the Registry::ObjectCache.*/ - void addEntryToObjectCache(const std::string& filename, osg::Object* object, double timestamp = 0.0); + /** Add a key,object,timestamp triple to the Registry::ObjectCache.*/ + void addEntryToObjectCache(const KeyType& key, osg::Object* object, double timestamp = 0.0) + { + OpenThreads::ScopedLock lock(_objectCacheMutex); + _objectCache[key]=ObjectTimeStampPair(object,timestamp); + } /** Remove Object from cache.*/ - void removeFromObjectCache(const std::string& fileName); + void removeFromObjectCache(const KeyType& key) + { + OpenThreads::ScopedLock lock(_objectCacheMutex); + typename ObjectCacheMap::iterator itr = _objectCache.find(key); + if (itr!=_objectCache.end()) _objectCache.erase(itr); + } /** Get an ref_ptr from the object cache*/ - osg::ref_ptr getRefFromObjectCache(const std::string& fileName); + osg::ref_ptr getRefFromObjectCache(const KeyType& key) + { + OpenThreads::ScopedLock lock(_objectCacheMutex); + typename ObjectCacheMap::iterator itr = _objectCache.find(key); + if (itr!=_objectCache.end()) + return itr->second.first; + else return 0; + } /** Check if an object is in the cache, and if it is, update its usage time stamp. */ - bool checkInObjectCache(const std::string& fileName, double timeStamp); + bool checkInObjectCache(const KeyType& key, double timeStamp) + { + OpenThreads::ScopedLock lock(_objectCacheMutex); + typename ObjectCacheMap::iterator itr = _objectCache.find(key); + if (itr!=_objectCache.end()) + { + itr->second.second = timeStamp; + return true; + } + else return false; + } /** call releaseGLObjects on all objects attached to the object cache.*/ - void releaseGLObjects(osg::State* state); + void releaseGLObjects(osg::State* state) + { + OpenThreads::ScopedLock lock(_objectCacheMutex); + for(typename ObjectCacheMap::iterator itr = _objectCache.begin(); itr != _objectCache.end(); ++itr) + { + osg::Object* object = itr->second.first.get(); + object->releaseGLObjects(state); + } + } /** call node->accept(nv); for all nodes in the objectCache. */ - void accept(osg::NodeVisitor& nv); + void accept(osg::NodeVisitor& nv) + { + OpenThreads::ScopedLock lock(_objectCacheMutex); + for(typename ObjectCacheMap::iterator itr = _objectCache.begin(); itr != _objectCache.end(); ++itr) + { + osg::Object* object = itr->second.first.get(); + if (object) + { + osg::Node* node = dynamic_cast(object); + if (node) + node->accept(nv); + } + } + } /** call operator()(osg::Object*) for each object in the cache. */ template void call(Functor& f) { OpenThreads::ScopedLock lock(_objectCacheMutex); - for (ObjectCacheMap::iterator it = _objectCache.begin(); it != _objectCache.end(); ++it) + for (typename ObjectCacheMap::iterator it = _objectCache.begin(); it != _objectCache.end(); ++it) f(it->second.first.get()); } /** Get the number of objects in the cache. */ - unsigned int getCacheSize() const; + unsigned int getCacheSize() const + { + OpenThreads::ScopedLock lock(_objectCacheMutex); + return _objectCache.size(); + } protected: - virtual ~ObjectCache(); + virtual ~GenericObjectCache() {} typedef std::pair, double > ObjectTimeStampPair; - typedef std::map ObjectCacheMap; + typedef std::map ObjectCacheMap; ObjectCacheMap _objectCache; mutable OpenThreads::Mutex _objectCacheMutex; }; +class ObjectCache : public GenericObjectCache +{ +}; + } #endif diff --git a/components/resource/resourcemanager.cpp b/components/resource/resourcemanager.cpp deleted file mode 100644 index c0e99674e..000000000 --- a/components/resource/resourcemanager.cpp +++ /dev/null @@ -1,46 +0,0 @@ -#include "resourcemanager.hpp" - -#include "objectcache.hpp" - -namespace Resource -{ - - ResourceManager::ResourceManager(const VFS::Manager *vfs) - : mVFS(vfs) - , mCache(new Resource::ObjectCache) - , mExpiryDelay(0.0) - { - - } - - ResourceManager::~ResourceManager() - { - } - - void ResourceManager::updateCache(double referenceTime) - { - mCache->updateTimeStampOfObjectsInCacheWithExternalReferences(referenceTime); - mCache->removeExpiredObjectsInCache(referenceTime - mExpiryDelay); - } - - void ResourceManager::clearCache() - { - mCache->clear(); - } - - void ResourceManager::setExpiryDelay(double expiryDelay) - { - mExpiryDelay = expiryDelay; - } - - const VFS::Manager* ResourceManager::getVFS() const - { - return mVFS; - } - - void ResourceManager::releaseGLObjects(osg::State *state) - { - mCache->releaseGLObjects(state); - } - -} diff --git a/components/resource/resourcemanager.hpp b/components/resource/resourcemanager.hpp index 6031ecc01..a5ae27c6a 100644 --- a/components/resource/resourcemanager.hpp +++ b/components/resource/resourcemanager.hpp @@ -3,6 +3,8 @@ #include +#include "objectcache.hpp" + namespace VFS { class Manager; @@ -16,37 +18,67 @@ namespace osg namespace Resource { - class ObjectCache; + + class BaseResourceManager + { + public: + virtual ~BaseResourceManager() {} + virtual void updateCache(double referenceTime) {} + virtual void clearCache() {} + virtual void setExpiryDelay(double expiryDelay) {} + virtual void reportStats(unsigned int frameNumber, osg::Stats* stats) const {} + virtual void releaseGLObjects(osg::State* state) {} + }; /// @brief Base class for managers that require a virtual file system and object cache. /// @par This base class implements clearing of the cache, but populating it and what it's used for is up to the individual sub classes. - class ResourceManager + template + class GenericResourceManager : public BaseResourceManager { public: - ResourceManager(const VFS::Manager* vfs); - virtual ~ResourceManager(); + typedef GenericObjectCache CacheType; + + GenericResourceManager(const VFS::Manager* vfs) + : mVFS(vfs) + , mCache(new CacheType) + , mExpiryDelay(0.0) + { + } + + virtual ~GenericResourceManager() {} /// Clear cache entries that have not been referenced for longer than expiryDelay. - virtual void updateCache(double referenceTime); + virtual void updateCache(double referenceTime) + { + mCache->updateTimeStampOfObjectsInCacheWithExternalReferences(referenceTime); + mCache->removeExpiredObjectsInCache(referenceTime - mExpiryDelay); + } /// Clear all cache entries. - virtual void clearCache(); + virtual void clearCache() { mCache->clear(); } /// How long to keep objects in cache after no longer being referenced. - void setExpiryDelay (double expiryDelay); + void setExpiryDelay (double expiryDelay) { mExpiryDelay = expiryDelay; } - const VFS::Manager* getVFS() const; + const VFS::Manager* getVFS() const { return mVFS; } virtual void reportStats(unsigned int frameNumber, osg::Stats* stats) const {} - virtual void releaseGLObjects(osg::State* state); + virtual void releaseGLObjects(osg::State* state) { mCache->releaseGLObjects(state); } protected: const VFS::Manager* mVFS; - osg::ref_ptr mCache; + osg::ref_ptr mCache; double mExpiryDelay; }; + + class ResourceManager : public GenericResourceManager + { + public: + ResourceManager(const VFS::Manager* vfs) : GenericResourceManager(vfs) {} + }; + } #endif diff --git a/components/resource/resourcesystem.cpp b/components/resource/resourcesystem.cpp index 4d61dce69..2015ba874 100644 --- a/components/resource/resourcesystem.cpp +++ b/components/resource/resourcesystem.cpp @@ -54,7 +54,7 @@ namespace Resource void ResourceSystem::setExpiryDelay(double expiryDelay) { - for (std::vector::iterator it = mResourceManagers.begin(); it != mResourceManagers.end(); ++it) + for (std::vector::iterator it = mResourceManagers.begin(); it != mResourceManagers.end(); ++it) (*it)->setExpiryDelay(expiryDelay); // NIF files aren't needed any more once the converted objects are cached in SceneManager / BulletShapeManager, @@ -64,24 +64,24 @@ namespace Resource void ResourceSystem::updateCache(double referenceTime) { - for (std::vector::iterator it = mResourceManagers.begin(); it != mResourceManagers.end(); ++it) + for (std::vector::iterator it = mResourceManagers.begin(); it != mResourceManagers.end(); ++it) (*it)->updateCache(referenceTime); } void ResourceSystem::clearCache() { - for (std::vector::iterator it = mResourceManagers.begin(); it != mResourceManagers.end(); ++it) + for (std::vector::iterator it = mResourceManagers.begin(); it != mResourceManagers.end(); ++it) (*it)->clearCache(); } - void ResourceSystem::addResourceManager(ResourceManager *resourceMgr) + void ResourceSystem::addResourceManager(BaseResourceManager *resourceMgr) { mResourceManagers.push_back(resourceMgr); } - void ResourceSystem::removeResourceManager(ResourceManager *resourceMgr) + void ResourceSystem::removeResourceManager(BaseResourceManager *resourceMgr) { - std::vector::iterator found = std::find(mResourceManagers.begin(), mResourceManagers.end(), resourceMgr); + std::vector::iterator found = std::find(mResourceManagers.begin(), mResourceManagers.end(), resourceMgr); if (found != mResourceManagers.end()) mResourceManagers.erase(found); } @@ -93,13 +93,13 @@ namespace Resource void ResourceSystem::reportStats(unsigned int frameNumber, osg::Stats *stats) const { - for (std::vector::const_iterator it = mResourceManagers.begin(); it != mResourceManagers.end(); ++it) + for (std::vector::const_iterator it = mResourceManagers.begin(); it != mResourceManagers.end(); ++it) (*it)->reportStats(frameNumber, stats); } void ResourceSystem::releaseGLObjects(osg::State *state) { - for (std::vector::const_iterator it = mResourceManagers.begin(); it != mResourceManagers.end(); ++it) + for (std::vector::const_iterator it = mResourceManagers.begin(); it != mResourceManagers.end(); ++it) (*it)->releaseGLObjects(state); } diff --git a/components/resource/resourcesystem.hpp b/components/resource/resourcesystem.hpp index 396bdb8fa..db8bbafc9 100644 --- a/components/resource/resourcesystem.hpp +++ b/components/resource/resourcesystem.hpp @@ -22,7 +22,7 @@ namespace Resource class ImageManager; class NifFileManager; class KeyframeManager; - class ResourceManager; + class BaseResourceManager; /// @brief Wrapper class that constructs and provides access to the most commonly used resource subsystems. /// @par Resource subsystems can be used with multiple OpenGL contexts, just like the OSG equivalents, but @@ -48,10 +48,10 @@ namespace Resource /// Add this ResourceManager to be handled by the ResourceSystem. /// @note Does not transfer ownership. - void addResourceManager(ResourceManager* resourceMgr); + void addResourceManager(BaseResourceManager* resourceMgr); /// @note Do nothing if resourceMgr does not exist. /// @note Does not delete resourceMgr. - void removeResourceManager(ResourceManager* resourceMgr); + void removeResourceManager(BaseResourceManager* resourceMgr); /// How long to keep objects in cache after no longer being referenced. void setExpiryDelay(double expiryDelay); @@ -72,7 +72,7 @@ namespace Resource // Store the base classes separately to get convenient access to the common interface // Here users can register their own resourcemanager as well - std::vector mResourceManagers; + std::vector mResourceManagers; const VFS::Manager* mVFS; diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index d371a8ce4..61a40ee4b 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -531,6 +531,8 @@ namespace Resource if (mIncrementalCompileOperation) mIncrementalCompileOperation->add(loaded); + else + loaded->getBound(); mCache->addEntryToObjectCache(normalized, loaded); return loaded; @@ -543,6 +545,12 @@ namespace Resource mVFS->normalizeFilename(normalized); osg::ref_ptr node = createInstance(normalized); + + // Note: osg::clone() does not calculate bound volumes. + // Do it immediately, otherwise we will need to update them for all objects + // during first update traversal, what may lead to stuttering during cell transitions + node->getBound(); + mInstanceCache->addEntryToObjectCache(normalized, node.get()); return node; } diff --git a/components/resource/stats.cpp b/components/resource/stats.cpp index 0a70d75ab..51497cd27 100644 --- a/components/resource/stats.cpp +++ b/components/resource/stats.cpp @@ -2,6 +2,7 @@ #include #include +#include #include @@ -197,14 +198,14 @@ class ResourceStatsTextDrawCallback : public osg::Drawable::DrawCallback { public: ResourceStatsTextDrawCallback(osg::Stats* stats, const std::vector& statNames) - : _stats(stats) - , _statNames(statNames) + : mStats(stats) + , mStatNames(statNames) { } virtual void drawImplementation(osg::RenderInfo& renderInfo,const osg::Drawable* drawable) const { - if (!_stats) return; + if (!mStats) return; osgText::Text* text = (osgText::Text*)(drawable); @@ -218,14 +219,14 @@ public: unsigned int frameNumber = renderInfo.getState()->getFrameStamp()->getFrameNumber()-1; - for (std::vector::const_iterator it = _statNames.begin(); it != _statNames.end(); ++it) + for (const auto& statName : mStatNames.get()) { - if (it->empty()) + if (statName.empty()) viewStr << std::endl; else { double value = 0.0; - if (_stats->getAttribute(frameNumber, *it, value)) + if (mStats->getAttribute(frameNumber, statName, value)) viewStr << std::setw(8) << value << std::endl; else viewStr << std::setw(8) << "." << std::endl; @@ -237,8 +238,8 @@ public: text->drawImplementation(renderInfo); } - osg::ref_ptr _stats; - std::vector _statNames; + osg::ref_ptr mStats; + std::reference_wrapper> mStatNames; }; void StatsHandler::setUpScene(osgViewer::ViewerBase *viewer) @@ -255,7 +256,7 @@ void StatsHandler::setUpScene(osgViewer::ViewerBase *viewer) stateset->setAttribute(new osg::PolygonMode(), osg::StateAttribute::PROTECTED); #endif - osg::Vec3 pos(_statsWidth-300.f, _statsHeight-500.0f,0.0f); + osg::Vec3 pos(_statsWidth-420.f, _statsHeight-500.0f,0.0f); osg::Vec4 backgroundColor(0.0, 0.0, 0.0f, 0.3); osg::Vec4 staticTextColor(1.0, 1.0, 0.0f, 1.0); osg::Vec4 dynamicTextColor(1.0, 1.0, 1.0f, 1.0); @@ -269,12 +270,41 @@ void StatsHandler::setUpScene(osgViewer::ViewerBase *viewer) _resourceStatsChildNum = _switch->getNumChildren(); _switch->addChild(group, false); - const char* statNames[] = {"Compiling", "WorkQueue", "WorkThread", "", "Texture", "StateSet", "Node", "Node Instance", "Shape", "Shape Instance", "Image", "Nif", "Keyframe", "", "Terrain Chunk", "Terrain Texture", "Land", "Composite", "", "UnrefQueue"}; - - int numLines = sizeof(statNames) / sizeof(statNames[0]); + static const std::vector statNames({ + "Compiling", + "WorkQueue", + "WorkThread", + "", + "Texture", + "StateSet", + "Node", + "Node Instance", + "Shape", + "Shape Instance", + "Image", + "Nif", + "Keyframe", + "", + "Terrain Chunk", + "Terrain Texture", + "Land", + "Composite", + "", + "UnrefQueue", + "", + "NavMesh UpdateJobs", + "NavMesh CacheSize", + "NavMesh UsedTiles", + "NavMesh CachedTiles", + }); + + static const auto longest = std::max_element(statNames.begin(), statNames.end(), + [] (const std::string& lhs, const std::string& rhs) { return lhs.size() < rhs.size(); }); + const int numLines = statNames.size(); + const float statNamesWidth = 13 * _characterSize + 2 * backgroundMargin; group->addChild(createBackgroundRectangle(pos + osg::Vec3(-backgroundMargin, _characterSize + backgroundMargin, 0), - 10 * _characterSize + 2 * backgroundMargin, + statNamesWidth, numLines * _characterSize + 2 * backgroundMargin, backgroundColor)); @@ -288,18 +318,18 @@ void StatsHandler::setUpScene(osgViewer::ViewerBase *viewer) std::ostringstream viewStr; viewStr.clear(); viewStr.setf(std::ios::left, std::ios::adjustfield); - viewStr.width(14); - for (size_t i = 0; isize()); + for (const auto& statName : statNames) { - viewStr << statNames[i] << std::endl; + viewStr << statName << std::endl; } staticText->setText(viewStr.str()); - pos.x() += 10 * _characterSize + 2 * backgroundMargin + backgroundSpacing; + pos.x() += statNamesWidth + backgroundSpacing; group->addChild(createBackgroundRectangle(pos + osg::Vec3(-backgroundMargin, _characterSize + backgroundMargin, 0), - 5 * _characterSize + 2 * backgroundMargin, + 7 * _characterSize + 2 * backgroundMargin, numLines * _characterSize + 2 * backgroundMargin, backgroundColor)); @@ -311,7 +341,7 @@ void StatsHandler::setUpScene(osgViewer::ViewerBase *viewer) statsText->setCharacterSize(_characterSize); statsText->setPosition(pos); statsText->setText(""); - statsText->setDrawCallback(new ResourceStatsTextDrawCallback(viewer->getViewerStats(), std::vector(statNames, statNames + numLines))); + statsText->setDrawCallback(new ResourceStatsTextDrawCallback(viewer->getViewerStats(), statNames)); } } diff --git a/components/sceneutil/detourdebugdraw.cpp b/components/sceneutil/detourdebugdraw.cpp index a0ae67dc2..ea8a4c2f6 100644 --- a/components/sceneutil/detourdebugdraw.cpp +++ b/components/sceneutil/detourdebugdraw.cpp @@ -102,7 +102,6 @@ namespace SceneUtil osg::ref_ptr geometry(new osg::Geometry); geometry->setStateSet(stateSet); - geometry->setUseDisplayList(false); geometry->setVertexArray(mVertices); geometry->setColorArray(mColors, osg::Array::BIND_PER_VERTEX); geometry->addPrimitiveSet(new osg::DrawArrays(mMode, 0, static_cast(mVertices->size()))); diff --git a/components/sceneutil/lightmanager.cpp b/components/sceneutil/lightmanager.cpp index f0fd0ef9f..c657e66fd 100644 --- a/components/sceneutil/lightmanager.cpp +++ b/components/sceneutil/lightmanager.cpp @@ -245,7 +245,8 @@ namespace SceneUtil osg::ref_ptr attr = new LightStateAttribute(mStartLight, lights); // don't use setAttributeAndModes, that does not support light indices! stateset->setAttribute(attr, osg::StateAttribute::ON); - stateset->setAssociatedModes(attr, osg::StateAttribute::ON); + for (unsigned int i=0; isetMode(GL_LIGHT0 + mStartLight + i, osg::StateAttribute::ON); // need to push some dummy attributes to ensure proper state tracking // lights need to reset to their default when the StateSet is popped diff --git a/components/sceneutil/mwshadowtechnique.cpp b/components/sceneutil/mwshadowtechnique.cpp index 4125ebe7d..92bf44ec1 100644 --- a/components/sceneutil/mwshadowtechnique.cpp +++ b/components/sceneutil/mwshadowtechnique.cpp @@ -30,6 +30,8 @@ namespace { using namespace osgShadow; using namespace SceneUtil; +#define dbl_max std::numeric_limits::max() + ////////////////////////////////////////////////////////////////// // fragment shader // @@ -775,6 +777,7 @@ MWShadowTechnique::MWShadowTechnique(const MWShadowTechnique& vdsm, const osg::C ShadowTechnique(vdsm,copyop) { _shadowRecievingPlaceholderStateSet = new osg::StateSet; + _enableShadows = vdsm._enableShadows; } MWShadowTechnique::~MWShadowTechnique() @@ -928,7 +931,7 @@ void MWShadowTechnique::cull(osgUtil::CullVisitor& cv) viewProjectionMatrix(2,3)==0.0; double minZNear = 0.0; - double maxZFar = DBL_MAX; + double maxZFar = dbl_max; if (cachedNearFarMode==osg::CullSettings::DO_NOT_COMPUTE_NEAR_FAR) { @@ -1088,8 +1091,8 @@ void MWShadowTechnique::cull(osgUtil::CullVisitor& cv) double yRange = (clsb._bb.yMax()-clsb._bb.yMin()); osg::Matrixd cornerConverter = osg::Matrixd::inverse(projectionMatrix) * osg::Matrixd::inverse(viewMatrix) * *cv.getModelViewMatrix(); - double minZ = DBL_MAX; - double maxZ = -DBL_MAX; + double minZ = dbl_max; + double maxZ = -dbl_max; clsb._bb._max[2] = 1.0; for (unsigned int i = 0; i < 8; i++) { @@ -1777,7 +1780,7 @@ bool MWShadowTechnique::computeShadowCameraSettings(Frustum& frustum, LightData& } else { - double zMax=-DBL_MAX; + double zMax=-dbl_max; OSG_INFO<<"lightDir = "<second.end(); ++dupItr) { - osg::Geometry* geomToPush = *dupItr; + osg::Geometry* geomToPush = dupItr->get(); // try to group geomToPush with another geometry MergeList::iterator eachMergeList=mergeListTmp.begin(); @@ -1216,125 +1210,73 @@ bool Optimizer::MergeGeometryVisitor::mergeGroup(osg::Group& group) // then build merge list using _targetMaximumNumberOfVertices bool needToDoMerge = false; // dequeue each DuplicateList when vertices limit is reached or when all elements has been checked - for(;!mergeListChecked.empty();) + for(MergeList::iterator itr=mergeListChecked.begin(); itr!=mergeListChecked.end(); ++itr) { - MergeList::iterator itr=mergeListChecked.begin(); DuplicateList& duplicateList(*itr); if (duplicateList.size()==0) { - mergeListChecked.erase(itr); continue; } if (duplicateList.size()==1) { mergeList.push_back(duplicateList); - mergeListChecked.erase(itr); continue; } - unsigned int numVertices(duplicateList.front()->getVertexArray() ? duplicateList.front()->getVertexArray()->getNumElements() : 0); - DuplicateList::iterator eachGeom(duplicateList.begin()+1); - // until all geometries have been checked or _targetMaximumNumberOfVertices is reached - for(;eachGeom!=duplicateList.end(); ++eachGeom) + unsigned int totalNumberVertices = 0; + DuplicateList subset; + for(DuplicateList::iterator ditr = duplicateList.begin(); + ditr != duplicateList.end(); + ++ditr) { - unsigned int numAddVertices((*eachGeom)->getVertexArray() ? (*eachGeom)->getVertexArray()->getNumElements() : 0); - if ((numVertices+numAddVertices)>_targetMaximumNumberOfVertices) - { - break; - } - else + osg::Geometry* geometry = ditr->get(); + unsigned int numVertices = (geometry->getVertexArray() ? geometry->getVertexArray()->getNumElements() : 0); + if ((totalNumberVertices+numVertices)>_targetMaximumNumberOfVertices && !subset.empty()) { - numVertices += numAddVertices; + mergeList.push_back(subset); + subset.clear(); + totalNumberVertices = 0; } + totalNumberVertices += numVertices; + subset.push_back(geometry); + if (subset.size()>1) needToDoMerge = true; } - - // push back if bellow the limit - if (eachGeom==duplicateList.end()) - { - if (duplicateList.size()>1) needToDoMerge = true; - mergeList.push_back(duplicateList); - mergeListChecked.erase(itr); - } - // else split the list to store what is below the limit and retry on what is above - else - { - mergeList.push_back(DuplicateList()); - DuplicateList* duplicateListResult = &mergeList.back(); - duplicateListResult->insert(duplicateListResult->end(),duplicateList.begin(),eachGeom); - duplicateList.erase(duplicateList.begin(),eachGeom); - if (duplicateListResult->size()>1) needToDoMerge = true; - } + if (!subset.empty()) mergeList.push_back(subset); } if (needToDoMerge) { + // to avoid performance issues associated with incrementally removing a large number children, we remove them all and add back the ones we need. + group.removeChildren(0, group.getNumChildren()); + + for(Nodes::iterator itr = standardChildren.begin(); + itr != standardChildren.end(); + ++itr) + { + group.addChild(*itr); + } + // now do the merging of geometries for(MergeList::iterator mitr = mergeList.begin(); mitr != mergeList.end(); ++mitr) { DuplicateList& duplicateList = *mitr; - if (duplicateList.size()>1) + if (!duplicateList.empty()) { - osg::Geometry* lhs = duplicateList.front(); - for(DuplicateList::iterator ditr = duplicateList.begin()+1; + DuplicateList::iterator ditr = duplicateList.begin(); + osg::ref_ptr lhs = *ditr++; + group.addChild(lhs.get()); + for(; ditr != duplicateList.end(); ++ditr) { - mergeGeometry(*lhs,**ditr); - - group.removeChild(*ditr); - } - } - } - } - -#else - // don't merge geometry if its above a maximum number of vertices. - for(GeometryDuplicateMap::iterator itr=geometryDuplicateMap.begin(); - itr!=geometryDuplicateMap.end(); - ++itr) - { - if (itr->second.size()>1) - { - std::sort(itr->second.begin(),itr->second.end(),LessGeometryPrimitiveType()); - osg::Geometry* lhs = itr->second[0]; - for(DuplicateList::iterator dupItr=itr->second.begin()+1; - dupItr!=itr->second.end(); - ++dupItr) - { - - osg::Geometry* rhs = *dupItr; - - if (lhs->getVertexArray() && lhs->getVertexArray()->getNumElements()>=_targetMaximumNumberOfVertices) - { - lhs = rhs; - continue; - } - - if (rhs->getVertexArray() && rhs->getVertexArray()->getNumElements()>=_targetMaximumNumberOfVertices) - { - continue; - } - - if (lhs->getVertexArray() && rhs->getVertexArray() && - (lhs->getVertexArray()->getNumElements()+rhs->getVertexArray()->getNumElements())>=_targetMaximumNumberOfVertices) - { - continue; - } - - if (mergeGeometry(*lhs,*rhs)) - { - geode.removeDrawable(rhs); - - static int co = 0; - OSG_INFO<<"merged and removed Geometry "<<++co<getType()!=rhs->getType()) return false; _lhs = lhs; - _offset = offset; rhs->accept(*this); return true; @@ -1607,30 +1546,24 @@ class MergeArrayVisitor : public osg::ArrayVisitor lhs->insert(lhs->end(),rhs.begin(),rhs.end()); } - template - void _mergeAndOffset(T& rhs) - { - T* lhs = static_cast(_lhs); - - typename T::iterator itr; - for(itr = rhs.begin(); - itr != rhs.end(); - ++itr) - { - lhs->push_back(*itr + _offset); - } - } - virtual void apply(osg::Array&) { OSG_WARN << "Warning: Optimizer's MergeArrayVisitor cannot merge Array type." << std::endl; } - virtual void apply(osg::ByteArray& rhs) { if (_offset) _mergeAndOffset(rhs); else _merge(rhs); } - virtual void apply(osg::ShortArray& rhs) { if (_offset) _mergeAndOffset(rhs); else _merge(rhs); } - virtual void apply(osg::IntArray& rhs) { if (_offset) _mergeAndOffset(rhs); else _merge(rhs); } - virtual void apply(osg::UByteArray& rhs) { if (_offset) _mergeAndOffset(rhs); else _merge(rhs); } - virtual void apply(osg::UShortArray& rhs) { if (_offset) _mergeAndOffset(rhs); else _merge(rhs); } - virtual void apply(osg::UIntArray& rhs) { if (_offset) _mergeAndOffset(rhs); else _merge(rhs); } + + virtual void apply(osg::ByteArray& rhs) { _merge(rhs); } + virtual void apply(osg::ShortArray& rhs) { _merge(rhs); } + virtual void apply(osg::IntArray& rhs) { _merge(rhs); } + virtual void apply(osg::UByteArray& rhs) { _merge(rhs); } + virtual void apply(osg::UShortArray& rhs) { _merge(rhs); } + virtual void apply(osg::UIntArray& rhs) { _merge(rhs); } virtual void apply(osg::Vec4ubArray& rhs) { _merge(rhs); } + virtual void apply(osg::Vec3ubArray& rhs) { _merge(rhs); } + virtual void apply(osg::Vec2ubArray& rhs) { _merge(rhs); } + + virtual void apply(osg::Vec4usArray& rhs) { _merge(rhs); } + virtual void apply(osg::Vec3usArray& rhs) { _merge(rhs); } + virtual void apply(osg::Vec2usArray& rhs) { _merge(rhs); } + virtual void apply(osg::FloatArray& rhs) { _merge(rhs); } virtual void apply(osg::Vec2Array& rhs) { _merge(rhs); } virtual void apply(osg::Vec3Array& rhs) { _merge(rhs); } @@ -1644,6 +1577,7 @@ class MergeArrayVisitor : public osg::ArrayVisitor virtual void apply(osg::Vec2bArray& rhs) { _merge(rhs); } virtual void apply(osg::Vec3bArray& rhs) { _merge(rhs); } virtual void apply(osg::Vec4bArray& rhs) { _merge(rhs); } + virtual void apply(osg::Vec2sArray& rhs) { _merge(rhs); } virtual void apply(osg::Vec3sArray& rhs) { _merge(rhs); } virtual void apply(osg::Vec4sArray& rhs) { _merge(rhs); } diff --git a/components/sceneutil/riggeometry.cpp b/components/sceneutil/riggeometry.cpp index 0c8198055..97c8e1d39 100644 --- a/components/sceneutil/riggeometry.cpp +++ b/components/sceneutil/riggeometry.cpp @@ -40,8 +40,8 @@ RigGeometry::RigGeometry() , mLastFrameNumber(0) , mBoundsFirstFrame(true) { - setUpdateCallback(new osg::Callback); // dummy to make sure getNumChildrenRequiringUpdateTraversal() is correct - // update done in accept(NodeVisitor&) + setNumChildrenRequiringUpdateTraversal(1); + // update done in accept(NodeVisitor&) } RigGeometry::RigGeometry(const RigGeometry ©, const osg::CopyOp ©op) @@ -54,6 +54,7 @@ RigGeometry::RigGeometry(const RigGeometry ©, const osg::CopyOp ©op) , mBoundsFirstFrame(true) { setSourceGeometry(copy.mSourceGeometry); + setNumChildrenRequiringUpdateTraversal(1); } void RigGeometry::setSourceGeometry(osg::ref_ptr sourceGeometry) diff --git a/components/sdlutil/sdlgraphicswindow.cpp b/components/sdlutil/sdlgraphicswindow.cpp index dc6129e43..3b76e6887 100644 --- a/components/sdlutil/sdlgraphicswindow.cpp +++ b/components/sdlutil/sdlgraphicswindow.cpp @@ -113,7 +113,7 @@ void GraphicsWindowSDL2::init() return; } - SDL_GL_SetSwapInterval(_traits->vsync ? 1 : 0); + setSwapInterval(_traits->vsync); SDL_GL_MakeCurrent(oldWin, oldCtx); @@ -194,11 +194,31 @@ void GraphicsWindowSDL2::setSyncToVBlank(bool on) SDL_GL_MakeCurrent(mWindow, mContext); - SDL_GL_SetSwapInterval(on ? 1 : 0); + setSwapInterval(on); SDL_GL_MakeCurrent(oldWin, oldCtx); } +void GraphicsWindowSDL2::setSwapInterval(bool enable) +{ + if (enable) + { + if (SDL_GL_SetSwapInterval(-1) == -1) + { + OSG_NOTICE << "Adaptive vsync unsupported" << std::endl; + if (SDL_GL_SetSwapInterval(1) == -1) + { + OSG_NOTICE << "Vertical synchronization unsupported, disabling" << std::endl; + SDL_GL_SetSwapInterval(0); + } + } + } + else + { + SDL_GL_SetSwapInterval(0); + } +} + void GraphicsWindowSDL2::raiseWindow() { SDL_RaiseWindow(mWindow); diff --git a/components/sdlutil/sdlgraphicswindow.hpp b/components/sdlutil/sdlgraphicswindow.hpp index 4b48b4073..dd8076776 100644 --- a/components/sdlutil/sdlgraphicswindow.hpp +++ b/components/sdlutil/sdlgraphicswindow.hpp @@ -80,6 +80,9 @@ public: SDL_Window *mWindow; }; + +private: + void setSwapInterval(bool enable); }; } diff --git a/components/terrain/chunkmanager.cpp b/components/terrain/chunkmanager.cpp index 80f414541..41b1fdbe1 100644 --- a/components/terrain/chunkmanager.cpp +++ b/components/terrain/chunkmanager.cpp @@ -22,7 +22,7 @@ namespace Terrain { ChunkManager::ChunkManager(Storage *storage, Resource::SceneManager *sceneMgr, TextureManager* textureManager, CompositeMapRenderer* renderer) - : ResourceManager(nullptr) + : GenericResourceManager(nullptr) , mStorage(storage) , mSceneManager(sceneMgr) , mTextureManager(textureManager) @@ -35,12 +35,9 @@ ChunkManager::ChunkManager(Storage *storage, Resource::SceneManager *sceneMgr, T } -osg::ref_ptr ChunkManager::getChunk(float size, const osg::Vec2f ¢er, int lod, unsigned int lodFlags) +osg::ref_ptr ChunkManager::getChunk(float size, const osg::Vec2f ¢er, unsigned char lod, unsigned int lodFlags) { - std::ostringstream stream; - stream << size << " " << center.x() << " " << center.y() << " " << lod << " " << lodFlags; - std::string id = stream.str(); - + ChunkId id = std::make_tuple(center, lod, lodFlags); osg::ref_ptr obj = mCache->getRefFromObjectCache(id); if (obj) return obj->asNode(); @@ -59,14 +56,14 @@ void ChunkManager::reportStats(unsigned int frameNumber, osg::Stats *stats) cons void ChunkManager::clearCache() { - ResourceManager::clearCache(); + GenericResourceManager::clearCache(); mBufferCache.clearCache(); } void ChunkManager::releaseGLObjects(osg::State *state) { - ResourceManager::releaseGLObjects(state); + GenericResourceManager::releaseGLObjects(state); mBufferCache.releaseGLObjects(state); } @@ -119,7 +116,7 @@ std::vector > ChunkManager::createPasses(float chunk { std::vector layerList; std::vector > blendmaps; - mStorage->getBlendmaps(chunkSize, chunkCenter, false, blendmaps, layerList); + mStorage->getBlendmaps(chunkSize, chunkCenter, blendmaps, layerList); bool useShaders = mSceneManager->getForceShaders(); if (!mSceneManager->getClampLighting()) @@ -164,7 +161,7 @@ std::vector > ChunkManager::createPasses(float chunk return ::Terrain::createPasses(useShaders, &mSceneManager->getShaderManager(), layers, blendmapTextures, blendmapScale, blendmapScale); } -osg::ref_ptr ChunkManager::createChunk(float chunkSize, const osg::Vec2f &chunkCenter, int lod, unsigned int lodFlags) +osg::ref_ptr ChunkManager::createChunk(float chunkSize, const osg::Vec2f &chunkCenter, unsigned char lod, unsigned int lodFlags) { osg::Vec2f worldCenter = chunkCenter*mStorage->getCellWorldSize(); osg::ref_ptr transform (new SceneUtil::PositionAttitudeTransform); @@ -172,7 +169,8 @@ osg::ref_ptr ChunkManager::createChunk(float chunkSize, const osg::Ve osg::ref_ptr positions (new osg::Vec3Array); osg::ref_ptr normals (new osg::Vec3Array); - osg::ref_ptr colors (new osg::Vec4Array); + osg::ref_ptr colors (new osg::Vec4ubArray); + colors->setNormalize(true); osg::ref_ptr vbo (new osg::VertexBufferObject); positions->setVertexBufferObject(vbo); @@ -188,7 +186,7 @@ osg::ref_ptr ChunkManager::createChunk(float chunkSize, const osg::Ve geometry->setUseDisplayList(false); geometry->setUseVertexBufferObjects(true); - if (chunkSize <= 2.f) + if (chunkSize <= 1.f) geometry->setLightListCallback(new SceneUtil::LightListCallback); unsigned int numVerts = (mStorage->getCellVertices()-1) * chunkSize / (1 << lod) + 1; diff --git a/components/terrain/chunkmanager.hpp b/components/terrain/chunkmanager.hpp index 27201864f..2dea1cf92 100644 --- a/components/terrain/chunkmanager.hpp +++ b/components/terrain/chunkmanager.hpp @@ -1,6 +1,8 @@ #ifndef OPENMW_COMPONENTS_TERRAIN_CHUNKMANAGER_H #define OPENMW_COMPONENTS_TERRAIN_CHUNKMANAGER_H +#include + #include #include "buffercache.hpp" @@ -24,13 +26,15 @@ namespace Terrain class Storage; class CompositeMap; + typedef std::tuple ChunkId; // Center, Lod, Lod Flags + /// @brief Handles loading and caching of terrain chunks - class ChunkManager : public Resource::ResourceManager + class ChunkManager : public Resource::GenericResourceManager { public: ChunkManager(Storage* storage, Resource::SceneManager* sceneMgr, TextureManager* textureManager, CompositeMapRenderer* renderer); - osg::ref_ptr getChunk(float size, const osg::Vec2f& center, int lod, unsigned int lodFlags); + osg::ref_ptr getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags); void setCullingActive(bool active) { mCullingActive = active; } void setCompositeMapSize(unsigned int size) { mCompositeMapSize = size; } @@ -44,7 +48,7 @@ namespace Terrain void releaseGLObjects(osg::State* state) override; private: - osg::ref_ptr createChunk(float size, const osg::Vec2f& center, int lod, unsigned int lodFlags); + osg::ref_ptr createChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags); osg::ref_ptr createCompositeMapRTT(); diff --git a/components/terrain/compositemaprenderer.cpp b/components/terrain/compositemaprenderer.cpp index 0ef649197..ee4a66fcd 100644 --- a/components/terrain/compositemaprenderer.cpp +++ b/components/terrain/compositemaprenderer.cpp @@ -6,6 +6,9 @@ #include #include +#include +#include + #include namespace Terrain @@ -20,9 +23,20 @@ CompositeMapRenderer::CompositeMapRenderer() mFBO = new osg::FrameBufferObject; + mUnrefQueue = new SceneUtil::UnrefQueue; + getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF); } +CompositeMapRenderer::~CompositeMapRenderer() +{ +} + +void CompositeMapRenderer::setWorkQueue(SceneUtil::WorkQueue* workQueue) +{ + mWorkQueue = workQueue; +} + void CompositeMapRenderer::drawImplementation(osg::RenderInfo &renderInfo) const { double dt = mTimer.time_s(); @@ -33,7 +47,8 @@ void CompositeMapRenderer::drawImplementation(osg::RenderInfo &renderInfo) const double availableTime = std::max((targetFrameTime - dt)*conservativeTimeRatio, mMinimumTimeAvailable); - mCompiled.clear(); + if (mWorkQueue) + mUnrefQueue->flush(mWorkQueue.get()); OpenThreads::ScopedLock lock(mMutex); @@ -42,26 +57,30 @@ void CompositeMapRenderer::drawImplementation(osg::RenderInfo &renderInfo) const while (!mImmediateCompileSet.empty()) { - CompositeMap* node = *mImmediateCompileSet.begin(); - mCompiled.insert(node); + osg::ref_ptr node = *mImmediateCompileSet.begin(); + mImmediateCompileSet.erase(node); + mMutex.unlock(); compile(*node, renderInfo, nullptr); - - mImmediateCompileSet.erase(mImmediateCompileSet.begin()); + mMutex.lock(); } double timeLeft = availableTime; while (!mCompileSet.empty() && timeLeft > 0) { - CompositeMap* node = *mCompileSet.begin(); + osg::ref_ptr node = *mCompileSet.begin(); + mCompileSet.erase(node); + mMutex.unlock(); compile(*node, renderInfo, &timeLeft); + mMutex.lock(); - if (node->mCompiled >= node->mDrawables.size()) + if (node->mCompiled < node->mDrawables.size()) { - mCompiled.insert(node); - mCompileSet.erase(mCompileSet.begin()); + // We did not compile the map fully. + // Place it back to queue to continue work in the next time. + mCompileSet.insert(node); } } mTimer.setStartTick(); @@ -122,6 +141,10 @@ void CompositeMapRenderer::compile(CompositeMap &compositeMap, osg::RenderInfo & ++compositeMap.mCompiled; + if (mWorkQueue) + { + mUnrefQueue->push(compositeMap.mDrawables[i]); + } compositeMap.mDrawables[i] = nullptr; if (timeLeft) diff --git a/components/terrain/compositemaprenderer.hpp b/components/terrain/compositemaprenderer.hpp index 54e158ed4..201130e30 100644 --- a/components/terrain/compositemaprenderer.hpp +++ b/components/terrain/compositemaprenderer.hpp @@ -14,6 +14,12 @@ namespace osg class Texture2D; } +namespace SceneUtil +{ + class UnrefQueue; + class WorkQueue; +} + namespace Terrain { @@ -34,11 +40,15 @@ namespace Terrain { public: CompositeMapRenderer(); + ~CompositeMapRenderer(); virtual void drawImplementation(osg::RenderInfo& renderInfo) const; void compile(CompositeMap& compositeMap, osg::RenderInfo& renderInfo, double* timeLeft) const; + /// Set a WorkQueue to delete compiled composite map layers in the background thread + void setWorkQueue(SceneUtil::WorkQueue* workQueue); + /// Set the available time in seconds for compiling (non-immediate) composite maps each frame void setMinimumTimeAvailableForCompile(double time); @@ -58,13 +68,14 @@ namespace Terrain double mMinimumTimeAvailable; mutable osg::Timer mTimer; + osg::ref_ptr mUnrefQueue; + osg::ref_ptr mWorkQueue; + typedef std::set > CompileSet; mutable CompileSet mCompileSet; mutable CompileSet mImmediateCompileSet; - mutable CompileSet mCompiled; - mutable OpenThreads::Mutex mMutex; osg::ref_ptr mFBO; diff --git a/components/terrain/quadtreenode.cpp b/components/terrain/quadtreenode.cpp index f8237306e..0c157cd48 100644 --- a/components/terrain/quadtreenode.cpp +++ b/components/terrain/quadtreenode.cpp @@ -76,6 +76,30 @@ QuadTreeNode *QuadTreeNode::getNeighbour(Direction dir) return mNeighbours[dir]; } +float QuadTreeNode::distance(const osg::Vec3f& v) const +{ + const osg::BoundingBox& box = getBoundingBox(); + if (box.contains(v)) + return 0; + else + { + osg::Vec3f maxDist(0,0,0); + if (v.x() < box.xMin()) + maxDist.x() = box.xMin() - v.x(); + else if (v.x() > box.xMax()) + maxDist.x() = v.x() - box.xMax(); + if (v.y() < box.yMin()) + maxDist.y() = box.yMin() - v.y(); + else if (v.y() > box.yMax()) + maxDist.y() = v.y() - box.yMax(); + if (v.z() < box.zMin()) + maxDist.z() = box.zMin() - v.z(); + else if (v.z() > box.zMax()) + maxDist.z() = v.z() - box.zMax(); + return maxDist.length(); + } +} + void QuadTreeNode::initNeighbours() { for (int i=0; i<4; ++i) @@ -92,7 +116,7 @@ void QuadTreeNode::traverse(osg::NodeVisitor &nv) ViewData* vd = getView(nv); - if ((mLodCallback && mLodCallback->isSufficientDetail(this, vd->getEyePoint())) || !getNumChildren()) + if ((mLodCallback && mLodCallback->isSufficientDetail(this, distance(vd->getEyePoint()))) || !getNumChildren()) vd->add(this, true); else osg::Group::traverse(nv); diff --git a/components/terrain/quadtreenode.hpp b/components/terrain/quadtreenode.hpp index 618429c5c..336a257fb 100644 --- a/components/terrain/quadtreenode.hpp +++ b/components/terrain/quadtreenode.hpp @@ -23,7 +23,7 @@ namespace Terrain public: virtual ~LodCallback() {} - virtual bool isSufficientDetail(QuadTreeNode *node, const osg::Vec3f& eyePoint) = 0; + virtual bool isSufficientDetail(QuadTreeNode *node, float dist) = 0; }; class ViewDataMap; @@ -49,6 +49,8 @@ namespace Terrain child->addParent(this); }; + float distance(const osg::Vec3f& v) const; + /// Returns our direction relative to the parent node, or Root if we are the root node. ChildDirection getDirection() { return mDirection; } diff --git a/components/terrain/quadtreeworld.cpp b/components/terrain/quadtreeworld.cpp index 8d54f62ce..fcb0f51a7 100644 --- a/components/terrain/quadtreeworld.cpp +++ b/components/terrain/quadtreeworld.cpp @@ -40,33 +40,6 @@ namespace return targetlevel; } - float distanceToBox(const osg::BoundingBox& box, const osg::Vec3f& v) - { - if (box.contains(v)) - return 0; - else - { - osg::Vec3f maxDist(0,0,0); - - if (v.x() < box.xMin()) - maxDist.x() = box.xMin() - v.x(); - else if (v.x() > box.xMax()) - maxDist.x() = v.x() - box.xMax(); - - if (v.y() < box.yMin()) - maxDist.y() = box.yMin() - v.y(); - else if (v.y() > box.yMax()) - maxDist.y() = v.y() - box.yMax(); - - if (v.z() < box.zMin()) - maxDist.z() = box.zMin() - v.z(); - else if (v.z() > box.zMax()) - maxDist.z() = v.z() - box.zMax(); - - return maxDist.length(); - } - } - } namespace Terrain @@ -81,9 +54,8 @@ public: { } - virtual bool isSufficientDetail(QuadTreeNode* node, const osg::Vec3f& eyePoint) + virtual bool isSufficientDetail(QuadTreeNode* node, float dist) { - float dist = distanceToBox(node->getBoundingBox(), eyePoint); int nativeLodLevel = Log2(static_cast(node->getSize()/mMinSize)); int lodLevel = Log2(static_cast(dist/(Constants::CellSizeInUnits*mMinSize*mFactor))); @@ -201,22 +173,40 @@ public: node->setLodCallback(parent->getLodCallback()); node->setViewDataMap(mViewDataMap); - if (node->getSize() > mMinSize) + if (center.x() - halfSize > mMaxX + || center.x() + halfSize < mMinX + || center.y() - halfSize > mMaxY + || center.y() + halfSize < mMinY ) + // Out of bounds of the actual terrain - this will happen because + // we rounded the size up to the next power of two { - addChildren(node); + // Still create and return an empty node so as to not break the assumption that each QuadTreeNode has either 4 or 0 children. return node; } - // We arrived at a leaf - float minZ, maxZ; - mStorage->getMinMaxHeights(size, center, minZ, maxZ); - - float cellWorldSize = mStorage->getCellWorldSize(); - osg::BoundingBox boundingBox(osg::Vec3f((center.x()-halfSize)*cellWorldSize, (center.y()-halfSize)*cellWorldSize, minZ), - osg::Vec3f((center.x()+halfSize)*cellWorldSize, (center.y()+halfSize)*cellWorldSize, maxZ)); - node->setBoundingBox(boundingBox); + // Do not add child nodes for default cells without data. + // size = 1 means that the single shape covers the whole cell. + if (node->getSize() == 1 && !mStorage->hasData(center.x()-0.5, center.y()-0.5)) + return node; - return node; + if (node->getSize() <= mMinSize) + { + // We arrived at a leaf + float minZ,maxZ; + if (mStorage->getMinMaxHeights(size, center, minZ, maxZ)) + { + float cellWorldSize = mStorage->getCellWorldSize(); + osg::BoundingBox boundingBox(osg::Vec3f((center.x()-halfSize)*cellWorldSize, (center.y()-halfSize)*cellWorldSize, minZ), + osg::Vec3f((center.x()+halfSize)*cellWorldSize, (center.y()+halfSize)*cellWorldSize, maxZ)); + node->setBoundingBox(boundingBox); + } + return node; + } + else + { + addChildren(node); + return node; + } } osg::ref_ptr getRootNode() @@ -236,11 +226,12 @@ private: }; QuadTreeWorld::QuadTreeWorld(osg::Group *parent, osg::Group *compileRoot, Resource::ResourceSystem *resourceSystem, Storage *storage, int nodeMask, int preCompileMask, int borderMask, int compMapResolution, float compMapLevel, float lodFactor, int vertexLodMod, float maxCompGeometrySize) - : World(parent, compileRoot, resourceSystem, storage, nodeMask, preCompileMask, borderMask) + : TerrainGrid(parent, compileRoot, resourceSystem, storage, nodeMask, preCompileMask, borderMask) , mViewDataMap(new ViewDataMap) , mQuadTreeBuilt(false) , mLodFactor(lodFactor) , mVertexLodMod(vertexLodMod) + , mViewDistance(std::numeric_limits::max()) { // No need for culling on the Drawable / Transform level as the quad tree performs the culling already. mChunkManager->setCullingActive(false); @@ -256,7 +247,7 @@ QuadTreeWorld::~QuadTreeWorld() } -void traverse(QuadTreeNode* node, ViewData* vd, osg::NodeVisitor* nv, LodCallback* lodCallback, const osg::Vec3f& eyePoint, bool visible) +void traverse(QuadTreeNode* node, ViewData* vd, osg::NodeVisitor* nv, LodCallback* lodCallback, const osg::Vec3f& eyePoint, bool visible, float maxDist) { if (!node->hasValidBounds()) return; @@ -264,14 +255,18 @@ void traverse(QuadTreeNode* node, ViewData* vd, osg::NodeVisitor* nv, LodCallbac if (nv && nv->getVisitorType() == osg::NodeVisitor::CULL_VISITOR) visible = visible && !static_cast(nv)->isCulled(node->getBoundingBox()); - bool stopTraversal = (lodCallback && lodCallback->isSufficientDetail(node, eyePoint)) || !node->getNumChildren(); + float dist = node->distance(eyePoint); + if (dist > maxDist) + return; + + bool stopTraversal = (lodCallback && lodCallback->isSufficientDetail(node, dist)) || !node->getNumChildren(); if (stopTraversal) vd->add(node, visible); else { for (unsigned int i=0; igetNumChildren(); ++i) - traverse(node->getChild(i), vd, nv, lodCallback, eyePoint, visible); + traverse(node->getChild(i), vd, nv, lodCallback, eyePoint, visible, maxDist); } } @@ -403,7 +398,7 @@ void QuadTreeWorld::accept(osg::NodeVisitor &nv) traverseToCell(mRootNode.get(), vd, x,y); } else - traverse(mRootNode.get(), vd, cv, mRootNode->getLodCallback(), cv->getViewPoint(), true); + traverse(mRootNode.get(), vd, cv, mRootNode->getLodCallback(), cv->getViewPoint(), true, mViewDistance); } else mRootNode->traverse(nv); @@ -478,14 +473,14 @@ View* QuadTreeWorld::createView() return new ViewData; } -void QuadTreeWorld::preload(View *view, const osg::Vec3f &eyePoint) +void QuadTreeWorld::preload(View *view, const osg::Vec3f &eyePoint, std::atomic &abort) { ensureQuadTreeBuilt(); ViewData* vd = static_cast(view); - traverse(mRootNode.get(), vd, nullptr, mRootNode->getLodCallback(), eyePoint, false); + traverse(mRootNode.get(), vd, nullptr, mRootNode->getLodCallback(), eyePoint, false, mViewDistance); - for (unsigned int i=0; igetNumEntries(); ++i) + for (unsigned int i=0; igetNumEntries() && !abort; ++i) { ViewData::Entry& entry = vd->getEntry(i); loadRenderingNode(entry, vd, mVertexLodMod, mChunkManager.get()); @@ -502,5 +497,25 @@ void QuadTreeWorld::setDefaultViewer(osg::Object *obj) mViewDataMap->setDefaultViewer(obj); } +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)) + TerrainGrid::loadCell(x,y); + else + World::loadCell(x,y); +} + +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)) + TerrainGrid::unloadCell(x,y); + else + World::unloadCell(x,y); +} + } diff --git a/components/terrain/quadtreeworld.hpp b/components/terrain/quadtreeworld.hpp index f724c44b1..0595096e3 100644 --- a/components/terrain/quadtreeworld.hpp +++ b/components/terrain/quadtreeworld.hpp @@ -2,6 +2,7 @@ #define COMPONENTS_TERRAIN_QUADTREEWORLD_H #include "world.hpp" +#include "terraingrid.hpp" #include @@ -15,8 +16,8 @@ namespace Terrain class RootNode; class ViewDataMap; - /// @brief Terrain implementation that loads cells into a Quad Tree, with geometry LOD and texture LOD. The entire world is displayed at all times. - class QuadTreeWorld : public Terrain::World + /// @brief Terrain implementation that loads cells into a Quad Tree, with geometry LOD and texture LOD. + class QuadTreeWorld : public TerrainGrid // note: derived from TerrainGrid is only to render default cells (see loadCell) { 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); @@ -27,10 +28,16 @@ namespace Terrain virtual void enable(bool enabled); + virtual void setViewDistance(float distance) { mViewDistance = distance; } + void cacheCell(View *view, int x, int y); + /// @note Not thread safe. + virtual void loadCell(int x, int y); + /// @note Not thread safe. + virtual void unloadCell(int x, int y); View* createView(); - void preload(View* view, const osg::Vec3f& eyePoint); + void preload(View* view, const osg::Vec3f& eyePoint, std::atomic& abort); void reportStats(unsigned int frameNumber, osg::Stats* stats); @@ -47,6 +54,7 @@ namespace Terrain bool mQuadTreeBuilt; float mLodFactor; int mVertexLodMod; + float mViewDistance; }; } diff --git a/components/terrain/storage.hpp b/components/terrain/storage.hpp index ebac1148c..cdde06e47 100644 --- a/components/terrain/storage.hpp +++ b/components/terrain/storage.hpp @@ -28,6 +28,14 @@ namespace Terrain /// Get bounds of the whole terrain in cell units virtual void getBounds(float& minX, float& maxX, float& minY, float& maxY) = 0; + /// Return true if there is land data for this cell + /// May be overriden for a faster implementation + virtual bool hasData(int cellX, int cellY) + { + float dummy; + return getMinMaxHeights(1, osg::Vec2f(cellX+0.5, cellY+0.5), dummy, dummy); + } + /// Get the minimum and maximum heights of a terrain region. /// @note Will only be called for chunks with size = minBatchSize, i.e. leafs of the quad tree. /// Larger chunks can simply merge AABB of children. @@ -52,7 +60,7 @@ namespace Terrain virtual void fillVertexBuffers (int lodLevel, float size, const osg::Vec2f& center, osg::ref_ptr positions, osg::ref_ptr normals, - osg::ref_ptr colours) = 0; + osg::ref_ptr colours) = 0; typedef std::vector > ImageVector; /// Create textures holding layer blend values for a terrain chunk. @@ -61,14 +69,10 @@ namespace Terrain /// @note May be called from background threads. Make sure to only call thread-safe functions from here! /// @param chunkSize size of the terrain chunk in cell units /// @param chunkCenter center of the chunk in cell units - /// @param pack Whether to pack blend values for up to 4 layers into one texture (one in each channel) - - /// otherwise, each texture contains blend values for one layer only. Shader-based rendering - /// can utilize packing, FFP can't. /// @param blendmaps created blendmaps will be written here /// @param layerList names of the layer textures used will be written here - virtual void getBlendmaps (float chunkSize, const osg::Vec2f& chunkCenter, bool pack, - ImageVector& blendmaps, - std::vector& layerList) = 0; + virtual void getBlendmaps (float chunkSize, const osg::Vec2f& chunkCenter, ImageVector& blendmaps, + std::vector& layerList) = 0; virtual float getHeightAt (const osg::Vec3f& worldPos) = 0; diff --git a/components/terrain/terraingrid.cpp b/components/terrain/terraingrid.cpp index 1888a02b3..a7d43f673 100644 --- a/components/terrain/terraingrid.cpp +++ b/components/terrain/terraingrid.cpp @@ -5,6 +5,7 @@ #include #include "chunkmanager.hpp" +#include "compositemaprenderer.hpp" namespace Terrain { @@ -61,6 +62,10 @@ osg::ref_ptr TerrainGrid::buildTerrain (osg::Group* parent, float chu if (parent) parent->addChild(node); + osg::UserDataContainer* udc = node->getUserDataContainer(); + if (udc && udc->getUserData()) + mCompositeMapRenderer->setImmediate(static_cast(udc->getUserData())); + return node; } } diff --git a/components/terrain/viewdata.cpp b/components/terrain/viewdata.cpp index 2f9cb6641..3d09e7ba0 100644 --- a/components/terrain/viewdata.cpp +++ b/components/terrain/viewdata.cpp @@ -118,7 +118,6 @@ ViewData *ViewDataMap::getViewData(osg::Object *viewer) if (found == mViews.end()) { ViewData* vd = createOrReuseView(); - vd->setViewer(viewer); mViews[viewer] = vd; return vd; } @@ -148,7 +147,6 @@ void ViewDataMap::clearUnusedViews(unsigned int frame) ViewData* vd = it->second; if (vd->getFrameLastUsed() + 2 < frame) { - vd->setViewer(nullptr); vd->clear(); mUnusedViews.push_back(vd); mViews.erase(it++); diff --git a/components/terrain/viewdata.hpp b/components/terrain/viewdata.hpp index aaf5b788f..53bcf42c0 100644 --- a/components/terrain/viewdata.hpp +++ b/components/terrain/viewdata.hpp @@ -44,9 +44,6 @@ namespace Terrain Entry& getEntry(unsigned int i); - osg::Object* getViewer() const { return mViewer.get(); } - void setViewer(osg::Object* viewer) { mViewer = viewer; } - unsigned int getFrameLastUsed() const { return mFrameLastUsed; } /// @return Have any nodes changed since the last frame @@ -62,7 +59,6 @@ namespace Terrain unsigned int mNumEntries; unsigned int mFrameLastUsed; bool mChanged; - osg::ref_ptr mViewer; osg::Vec3f mEyePoint; bool mHasEyePoint; }; @@ -85,7 +81,7 @@ namespace Terrain private: std::list mViewVector; - typedef std::map Map; + typedef std::map, ViewData*> Map; Map mViews; std::deque mUnusedViews; diff --git a/components/terrain/world.cpp b/components/terrain/world.cpp index 6a8322bb5..da3bdb5c2 100644 --- a/components/terrain/world.cpp +++ b/components/terrain/world.cpp @@ -66,6 +66,11 @@ World::~World() delete mStorage; } +void World::setWorkQueue(SceneUtil::WorkQueue* workQueue) +{ + mCompositeMapRenderer->setWorkQueue(workQueue); +} + void World::setBordersVisible(bool visible) { mBorderVisible = visible; diff --git a/components/terrain/world.hpp b/components/terrain/world.hpp index 4fb724ebb..cfb7edda6 100644 --- a/components/terrain/world.hpp +++ b/components/terrain/world.hpp @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -24,6 +25,11 @@ namespace Resource class ResourceSystem; } +namespace SceneUtil +{ + class WorkQueue; +} + namespace Terrain { class Storage; @@ -59,6 +65,9 @@ namespace Terrain World(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem, Storage* storage, int nodeMask, int preCompileMask, int borderMask); virtual ~World(); + /// Set a WorkQueue to delete objects in the background thread. + void setWorkQueue(SceneUtil::WorkQueue* workQueue); + /// See CompositeMapRenderer::setTargetFrameRate void setTargetFrameRate(float rate); @@ -93,13 +102,15 @@ namespace Terrain virtual View* createView() { return nullptr; } /// @note Thread safe, as long as you do not attempt to load into the same view from multiple threads. - virtual void preload(View* view, const osg::Vec3f& eyePoint) {} + virtual void preload(View* view, const osg::Vec3f& eyePoint, std::atomic& abort) {} virtual void reportStats(unsigned int frameNumber, osg::Stats* stats) {} /// Set the default viewer (usually a Camera), used as viewpoint for any viewers that don't use their own viewpoint. virtual void setDefaultViewer(osg::Object* obj) {} + virtual void setViewDistance(float distance) {} + Storage* getStorage() { return mStorage; } protected: diff --git a/docs/source/reference/modding/paths.rst b/docs/source/reference/modding/paths.rst index 23e33190d..085168189 100644 --- a/docs/source/reference/modding/paths.rst +++ b/docs/source/reference/modding/paths.rst @@ -9,20 +9,50 @@ The following describes the locations for the various OpenMW file paths for diff Configuration files and log files --------------------------------- -:Linux: ``$HOME/.config/openmw`` -:Mac: ``$HOME/Library/Preferences/openmw`` -:Windows: ``C:\Users\Username\Documents\my games\openmw`` ++--------------+------------------------------------------------------------------+ +| OS | Location | ++==============+==================================================================+ +| Linux | ``$HOME/.config/openmw`` | ++--------------+------------------------------------------------------------------+ +| Mac | ``$HOME/Library/Preferences/openmw`` | ++--------------+---------------+--------------------------------------------------+ +| Windows | File Explorer | ``%USERPROFILE%\Documents\My Games\OpenMW`` | +| | | | +| | PowerShell | ``"$env:USERPROFILE\Documents\My Games\OpenMW"`` | +| | | | +| | Example | ``C:\Users\Username\Documents\My Games\OpenMW`` | ++--------------+---------------+--------------------------------------------------+ Savegames --------- -:Linux: ``$HOME/.local/share/openmw/saves`` -:Mac: ``$HOME/Library/Application Support/openmw/saves`` -:Windows: ``C:\Users\Username\Documents\my games\openmw\saves`` ++--------------+------------------------------------------------------------------------+ +| OS | Location | ++==============+========================================================================+ +| Linux | ``$HOME/.config/openmw/saves`` | ++--------------+------------------------------------------------------------------------+ +| Mac | ``$HOME/Library/Application\ Support/openmw/saves`` | ++--------------+---------------+--------------------------------------------------------+ +| Windows | File Explorer | ``%USERPROFILE%\Documents\My Games\OpenMW\saves`` | +| | | | +| | PowerShell | ``"$env:USERPROFILE\Documents\My Games\OpenMW\saves"`` | +| | | | +| | Example | ``C:\Users\Username\Documents\My Games\OpenMW\saves`` | ++--------------+---------------+--------------------------------------------------------+ Screenshots ----------- -:Linux: ``$HOME/.local/share/openmw`` -:Mac: ``$HOME/Library/Application Support/openmw`` -:Windows: ``C:\Users\Username\Documents\my games\openmw`` ++--------------+-------------------------------------------------------------------------+ +| OS | Location | ++==============+=========================================================================+ +| Linux | ``$HOME/.local/share/openmw`` | ++--------------+-------------------------------------------------------------------------+ +| Mac | ``$HOME/Library/Application\ Support/openmw`` | ++--------------+---------------+---------------------------------------------------------+ +| Windows | File Explorer | ``%USERPROFILE%\Documents\My Games\OpenMW\OpenMW`` | +| | | | +| | PowerShell | ``"$env:USERPROFILE\Documents\My Games\OpenMW\OpenMW"`` | +| | | | +| | Example | ``C:\Users\Username\Documents\My Games\OpenMW\OpenMW`` | ++--------------+---------------+---------------------------------------------------------+ diff --git a/docs/source/reference/modding/settings/camera.rst b/docs/source/reference/modding/settings/camera.rst index aed68658f..6f13e3305 100644 --- a/docs/source/reference/modding/settings/camera.rst +++ b/docs/source/reference/modding/settings/camera.rst @@ -94,7 +94,7 @@ field of view ------------- :Type: floating point -:Range: 0-360 +:Range: 1-179 :Default: 55.0 Sets the camera field of view in degrees. Recommended values range from 30 degrees to 110 degrees. @@ -109,7 +109,7 @@ first person field of view -------------------------- :Type: floating point -:Range: 0-360 +:Range: 1-179 :Default: 55.0 This setting controls the field of view for first person meshes such as the player's hands and held objects. diff --git a/docs/source/reference/modding/settings/terrain.rst b/docs/source/reference/modding/settings/terrain.rst index e15a0035d..c8ae2a227 100644 --- a/docs/source/reference/modding/settings/terrain.rst +++ b/docs/source/reference/modding/settings/terrain.rst @@ -68,7 +68,7 @@ composite map level :Type: integer :Range: >= -3 -:Default: 0 +:Default: -2 Controls at which minimum size (in 2^value cell units) terrain chunks will start to use a composite map instead of the high-detail textures. With value -3 composite maps are used everywhere. diff --git a/extern/osgQt/GraphicsWindowQt b/extern/osgQt/GraphicsWindowQt index 1e34fc9db..274f0a149 100644 --- a/extern/osgQt/GraphicsWindowQt +++ b/extern/osgQt/GraphicsWindowQt @@ -14,8 +14,6 @@ #ifndef OSGVIEWER_GRAPHICSWINDOWQT #define OSGVIEWER_GRAPHICSWINDOWQT -#include - #include #include diff --git a/extern/osgQt/GraphicsWindowQt.cpp b/extern/osgQt/GraphicsWindowQt.cpp index 9e5f6e55e..af963c04b 100644 --- a/extern/osgQt/GraphicsWindowQt.cpp +++ b/extern/osgQt/GraphicsWindowQt.cpp @@ -18,10 +18,8 @@ #include #include -#if (QT_VERSION>=QT_VERSION_CHECK(4, 6, 0)) -# define USE_GESTURES -# include -# include +#if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)) +#include #endif using namespace osgQt; @@ -128,6 +126,8 @@ bool GLWidget::event( QEvent* event ) void GLWidget::resizeEvent( QResizeEvent* event ) { + if (_gw == nullptr || !_gw->valid()) + return; const QSize& size = event->size(); int scaled_width = static_cast(size.width()*_devicePixelRatio); @@ -139,6 +139,8 @@ void GLWidget::resizeEvent( QResizeEvent* event ) void GLWidget::moveEvent( QMoveEvent* event ) { + if (_gw == nullptr || !_gw->valid()) + return; const QPoint& pos = event->pos(); int scaled_width = static_cast(width()*_devicePixelRatio); int scaled_height = static_cast(height()*_devicePixelRatio); @@ -518,8 +520,11 @@ bool GraphicsWindowQt::releaseContextImplementation() void GraphicsWindowQt::swapBuffersImplementation() { - _widget->swapBuffers(); - +#if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)) + // QOpenGLContext complains if we swap on an non-exposed QWindow + if (!_widget || !_widget->windowHandle()->isExposed()) + return; +#endif // FIXME: the processDeferredEvents should really be executed in a GUI (main) thread context but // I couln't find any reliable way to do this. For now, lets hope non of *GUI thread only operations* will // be executed in a QGLWidget::event handler. On the other hand, calling GUI only operations in the @@ -529,8 +534,8 @@ void GraphicsWindowQt::swapBuffersImplementation() // We need to call makeCurrent here to restore our previously current context // which may be changed by the processDeferredEvents function. - if (QGLContext::currentContext() != _widget->context()) - _widget->makeCurrent(); + _widget->makeCurrent(); + _widget->swapBuffers(); } void GraphicsWindowQt::requestWarpPointer( float x, float y ) diff --git a/files/mygui/openmw_settings_window.layout b/files/mygui/openmw_settings_window.layout index 58be98cca..2341bfa19 100644 --- a/files/mygui/openmw_settings_window.layout +++ b/files/mygui/openmw_settings_window.layout @@ -53,12 +53,12 @@ - - + + - + @@ -83,11 +83,11 @@ - + - + @@ -309,12 +309,12 @@ - - + + - + @@ -353,18 +353,19 @@ - + - + + @@ -372,18 +373,20 @@ - + - + - + + + diff --git a/files/settings-default.cfg b/files/settings-default.cfg index e95646602..353a3ac3d 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -98,7 +98,7 @@ vertex lod mod = 0 # Controls when the distant terrain will flip to composited textures instead of high-detail textures, should be >= -3. # Higher value is more detailed textures. -composite map level = 0 +composite map level = -2 # Controls the resolution of composite maps. composite map resolution = 512 diff --git a/files/shaders/lighting.glsl b/files/shaders/lighting.glsl index 90d849472..782d17d4e 100644 --- a/files/shaders/lighting.glsl +++ b/files/shaders/lighting.glsl @@ -74,9 +74,10 @@ vec4 doLighting(vec3 viewPos, vec3 viewNormal, vec4 vertexColor, out vec3 shadow vec3 getSpecular(vec3 viewNormal, vec3 viewDirection, float shininess, vec3 matSpec) { vec3 lightDir = normalize(gl_LightSource[0].position.xyz); - float NdotL = max(dot(viewNormal, lightDir), 0.0); - if (NdotL < 0.0) + float NdotL = dot(viewNormal, lightDir); + if (NdotL <= 0.0) return vec3(0.,0.,0.); vec3 halfVec = normalize(lightDir - viewDirection); - return pow(max(dot(viewNormal, halfVec), 0.0), 128.) * gl_LightSource[0].specular.xyz * matSpec; + float NdotH = dot(viewNormal, halfVec); + return pow(max(NdotH, 0.0), max(1e-4, shininess)) * gl_LightSource[0].specular.xyz * matSpec; }