diff --git a/.travis.yml b/.travis.yml index 51b1720c4..d87b0f01c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,6 +9,10 @@ env: # The next declaration is the encrypted COVERITY_SCAN_TOKEN, created # via the "travis encrypt" command using the project repo's public key - secure: "jybGzAdUbqt9vWR/GEnRd96BgAi/7Zd1+2HK68j/i/8+/1YH2XxLOy4Jv/DUBhBlJIkxs/Xv8dRcUlFOclZDHX1d/9Qnsqd3oUVkD7k1y7cTOWy9TBQaE/v/kZo3LpzA3xPwwthrb0BvqIbOfIELi5fS5s8ba85WFRg3AX70wWE=" +cache: + ccache: true + directories: + - ${HOME}/.ccache addons: apt: sources: @@ -17,7 +21,7 @@ addons: - llvm-toolchain-xenial-7 packages: [ # Dev - cmake, clang-7, clang-tools-7, gcc-8, g++-8, + cmake, clang-7, clang-tools-7, gcc-8, g++-8, ccache, # Boost libboost-filesystem-dev, libboost-iostreams-dev, libboost-program-options-dev, libboost-system-dev, # FFmpeg diff --git a/AUTHORS.md b/AUTHORS.md index 074081876..c3a27dea2 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -26,17 +26,21 @@ Programmers Allofich Andrei Kortunov (akortunov) AnyOldName3 - Aussiemon - Austin Salgat (Salgat) + Ardekantur + Armin Preiml Artem Kotsynyak (greye) artemutin Arthur Moore (EmperorArthur) Assumeru athile + Aussiemon + Austin Salgat (Salgat) Ben Shealy (bentsherman) + Berulacks Bret Curtis (psi29a) Britt Mathis (galdor557) Capostrophic + Carl Maxwell cc9cii Cédric Mocquillon Chris Boyce (slothlife) @@ -47,6 +51,7 @@ Programmers DanielVukelich darkf David Cernat (davidcernat) + Declan Millar (declan-millar) devnexen Dieho Dmitry Shkurskiy (endorph) @@ -54,10 +59,11 @@ Programmers Douglas Mencken (dougmencken) dreamer-dead David Teviotdale (dteviot) + Diggory Hardy + Dmitry Marakasov (AMDmi3) Edmondo Tommasina (edmondo) Eduard Cot (trombonecot) Eli2 - elsid Emanuel Guével (potatoesmaster) eroen escondida @@ -67,13 +73,19 @@ Programmers Finbar Crago (finbar-crago) Florian Weber (Florianjw) Gašper Sedej + Gohan1989 gugus/gus + guidoj Hallfaer Tuilinn Haoda Wang (h313) hristoast Internecine - Jacob Essex (Yacoby) + Jackerty + Jacob Essex (Yacoby) Jake Westrip (16bitint) + James Carty (MrTopCat) + Jan-Peter Nilsson (peppe) + Jan Borsodi (am0s) Jason Hooks (jhooks) jeaye Jeffrey Haines (Jyby) @@ -84,6 +96,7 @@ Programmers John Blomberg (fstp) Jordan Ayers Jordan Milne + Josua Grawitter Jules Blok (Armada651) julianko Julien Voisin (jvoisin/ap0) @@ -95,9 +108,10 @@ Programmers lazydev Leon Krieg (lkrieg) Leon Saunders (emoose) - Łukasz Gołębiewski (lukago) logzero lohikaarme + Lordrea + Łukasz Gołębiewski (lukago) Lukasz Gromanowski (lgro) Manuel Edelmann (vorenon) Marc Bouvier (CramitDeFrog) @@ -112,6 +126,7 @@ Programmers Michael Hogan (Xethik) Michael Mc Donnell Michael Papageorgiou (werdanith) + Michał Ściubidło (mike-sc) Michał Bień (Glorf) Michał Moroz (dragonee) Miloslav Číž (drummyfish) @@ -123,15 +138,20 @@ Programmers Nathan Jeffords (blunted2night) NeveHanter Nialsy + Nicolay Korslund Nikolay Kasyanov (corristo) nobrakal Nolan Poe (nopoe) Oleg Chkan (mrcheko) Paul Cercueil (pcercuei) Paul McElroy (Greendogo) + pchan3 + Perry Hugh + Phillip Andrews (PhillipAnd) Pi03k Pieter van der Kloet (pvdk) pkubik + PLkolek PlutonicOverkill Radu-Marius Popovici (rpopovici) Rafael Moura (dhustkoder) @@ -145,11 +165,12 @@ Programmers Roman Proskuryakov (kpp) Roman Siromakha (elsid) Sandy Carter (bwrsandman) - Scott Howard + Scott Howard (maqifrnswa) scrawl Sebastian Wick (swick) Sergey Fukanchik - Sergey Shambir + Sergey Shambir (sergey-shambir) + sergoz ShadowRadiance Siimacore sir_herrbatka @@ -158,24 +179,33 @@ Programmers spycrab Stefan Galowicz (bogglez) Stanislav Bobrov (Jiub) + Stanislaw Halik (sthalik) + Star-Demon stil-t + Stomy svaante Sylvain Thesnieres (Garvek) t6 terrorfisch thegriglat Thomas Luppi (Digmaster) + tlmullis tri4ng1e - unelsson - Will Herrmann (Thunderforge) + Thoronador Tom Mason (wheybags) Torben Leif Carrington (TorbenC) + unelsson + uramer viadanna Vincent Heuken + Vladimir Panteleev (CyberShadow) + Wang Ryu (bzzt) + Will Herrmann (Thunderforge) vocollapse + xyzz Yohaulticetl + Yuri Krupenin zelurker - James Carty (MrTopCat) Documentation ------------- @@ -184,11 +214,12 @@ Documentation Alejandro Sanchez (HiPhish) Bodillium Bret Curtis (psi29a) - David Walley (Loriel) Cramal + David Walley (Loriel) + Diego Crespo + Joakim Berg (lysol90) Ryan Tucker (Ravenwing) sir_herrbatka - Diego Crespo Packagers --------- @@ -207,13 +238,19 @@ Public Relations and Translations Artem Kotsynyak (greye) - Russian News Writer Dawid Lakomy (Vedyimyn) - Polish News Writer + ElderTroll - Release Manager Jim Clauwaert (Zedd) - Public Outreach + juanmnzsk8 - Spanish News Writer Julien Voisin (jvoisin/ap0) - French News Writer + Kingpix - Italian News Writer Lukasz Gromanowski (lgro) - English News Writer Martin Otto (Atahualpa) - Podcaster, Public Outreach, German Translator Mickey Lyle (raevol) - Release Manager + Nekochan - English News Writer + penguinroad - Indonesian News Writer Pithorn - Chinese News Writer sir_herrbatka - Polish News Writer + spyboot - German Translator Tom Koenderink (Okulo) - English News Writer Website @@ -243,34 +280,6 @@ Artwork Mickey Lyle (raevol) - Wordpress Theme Tom Koenderink (Okulo), SirHerrbatka, crysthala, Shnatsel, Lamoot - OpenMW Editor Icons -Inactive Contributors ---------------------- - - Ardekantur - Armin Preiml - Berulacks - Carl Maxwell - Diggory Hardy - Dmitry Marakasov (AMDmi3) - ElderTroll - guidoj - Jan-Peter Nilsson (peppe) - Jan Borsodi - Josua Grawitter - juanmnzsk8 - Kingpix - Lordrea - Michal Sciubidlo - Nicolay Korslund - Nekochan - pchan3 - penguinroad - sergoz - spyboot - Star-Demon - Thoronador - Yuri Krupenin - Additional Credits ------------------ In this section we would like to thank people not part of OpenMW for their work. diff --git a/CHANGELOG.md b/CHANGELOG.md index eaa44cc67..27e2ae157 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,17 @@ 0.46.0 ------ + 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 #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 + Bug #3778: [Mod] Improved Thrown Weapon Projectiles - weapons have wrong transformation during throw animation Bug #4329: Removed birthsign abilities are restored after reloading the save Bug #4383: Bow model obscures crosshair when arrow is drawn + Bug #4384: Resist Normal Weapons only checks ammunition for ranged weapons Bug #4411: Reloading a saved game while falling prevents damage in some cases Bug #4540: Rain delay when exiting water Bug #4701: PrisonMarker record is not hardcoded like other markers @@ -14,28 +20,51 @@ 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 Bug #4768: Fallback numerical value recovery chokes on invalid arguments Bug #4775: Slowfall effect resets player jumping flag Bug #4778: Interiors of Illusion puzzle in Sotha Sil Expanded mod is broken + Bug #4797: Player sneaking and running stances are not accounted for when in air Bug #4800: Standing collisions are not updated immediately when an object is teleported without a cell change Bug #4803: Stray special characters before begin statement break script compilation Bug #4804: Particle system with the "Has Sizes = false" causes an exception + Bug #4810: Raki creature broken in OpenMW 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 #4841: Russian localization ignores implicit keywords + 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 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 + Feature #4675: Support for NiRollController Feature #4730: Native animated containers support Feature #4812: Support NiSwitchNode Feature #4836: Daytime node switch + Feature #4859: Make water reflections more configurable + 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_script.linux.sh b/CI/before_script.linux.sh index 3ce005874..250955e49 100755 --- a/CI/before_script.linux.sh +++ b/CI/before_script.linux.sh @@ -14,6 +14,10 @@ if [[ -z "${BUILD_OPENMW}" ]]; then export BUILD_OPENMW=ON; fi if [[ -z "${BUILD_OPENMW_CS}" ]]; then export BUILD_OPENMW_CS=ON; fi ${ANALYZE} cmake \ + -DCMAKE_C_COMPILER="${CC}" \ + -DCMAKE_CXX_COMPILER="${CXX}" \ + -DCMAKE_C_COMPILER_LAUNCHER=ccache \ + -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \ -DBUILD_OPENMW=${BUILD_OPENMW} \ -DBUILD_OPENCS=${BUILD_OPENMW_CS} \ -DBUILD_LAUNCHER=${BUILD_OPENMW_CS} \ diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index b5bf0b632..5ed7283fd 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -82,7 +82,7 @@ while [ $# -gt 0 ]; do t ) TEST_FRAMEWORK=true ;; - + h ) cat < Set the build platform, can also be set with environment variable PLATFORM. - -t - Build unit tests / Google test + -t + Build unit tests / Google test -u Configure for unity builds. -v <2013/2015/2017> @@ -402,7 +402,7 @@ if [ -z $SKIP_DOWNLOAD ]; then download "SDL 2.0.7" \ "https://www.libsdl.org/release/SDL2-devel-2.0.7-VC.zip" \ "SDL2-2.0.7.zip" - + # Google test and mock if [ ! -z $TEST_FRAMEWORK ]; then echo "Google test 1.8.1..." @@ -446,7 +446,7 @@ echo if [ -z $APPVEYOR ]; then printf "Boost 1.67.0... " else - if [ $MSVC_VER -eq 12.0 ]; then + if [ "${MSVC_VER}" -eq 12.0 ]; then printf "Boost 1.58.0 AppVeyor... " else printf "Boost 1.67.0 AppVeyor... " @@ -608,9 +608,9 @@ printf "OSG 3.4.1-scrawl... " SUFFIX="" fi add_runtime_dlls "$(pwd)/OSG/bin/"{OpenThreads,zlib,libpng*}${SUFFIX}.dll \ - "$(pwd)/OSG/bin/osg"{,Animation,DB,FX,GA,Particle,Text,Util,Viewer}${SUFFIX}.dll - add_osg_dlls "$(pwd)/OSG/bin/osgPlugins-3.4.1/osgdb_"{bmp,dds,jpeg,osg,png,tga}${SUFFIX}.dll - add_osg_dlls "$(pwd)/OSG/bin/osgPlugins-3.4.1/osgdb_serializers_osg"{,animation,fx,ga,particle,text,util,viewer}${SUFFIX}.dll + "$(pwd)/OSG/bin/osg"{,Animation,DB,FX,GA,Particle,Text,Util,Viewer,Shadow}${SUFFIX}.dll + add_osg_dlls "$(pwd)/OSG/bin/osgPlugins-3.4.1/osgdb_"{bmp,dds,freetype,jpeg,osg,png,tga}${SUFFIX}.dll + add_osg_dlls "$(pwd)/OSG/bin/osgPlugins-3.4.1/osgdb_serializers_osg"{,animation,fx,ga,particle,text,util,viewer,shadow}${SUFFIX}.dll echo Done. } cd $DEPS @@ -692,33 +692,33 @@ echo # Google Test and Google Mock if [ ! -z $TEST_FRAMEWORK ]; then printf "Google test 1.8.1 ..." - + cd googletest if [ ! -d build ]; then mkdir build fi - + cd build - + GOOGLE_INSTALL_ROOT="${DEPS_INSTALL}/GoogleTest" if [ $CONFIGURATION == "Debug" ]; then DEBUG_SUFFIX="d" else DEBUG_SUFFIX="" fi - + if [ ! -d $GOOGLE_INSTALL_ROOT ]; then - + cmake .. -DCMAKE_BUILD_TYPE="${CONFIGURATION}" -DCMAKE_INSTALL_PREFIX="${GOOGLE_INSTALL_ROOT}" -DCMAKE_USE_WIN32_THREADS_INIT=1 -G "${GENERATOR}" -DBUILD_SHARED_LIBS=1 cmake --build . --config "${CONFIGURATION}" cmake --build . --target install --config "${CONFIGURATION}" - + add_runtime_dlls "${GOOGLE_INSTALL_ROOT}\bin\gtest_main${DEBUG_SUFFIX}.dll" add_runtime_dlls "${GOOGLE_INSTALL_ROOT}\bin\gtest${DEBUG_SUFFIX}.dll" add_runtime_dlls "${GOOGLE_INSTALL_ROOT}\bin\gmock_main${DEBUG_SUFFIX}.dll" add_runtime_dlls "${GOOGLE_INSTALL_ROOT}\bin\gmock${DEBUG_SUFFIX}.dll" fi - + add_cmake_opts -DBUILD_UNITTESTS=yes # FindGTest and FindGMock do not work perfectly on Windows # but we can help them by telling them everything we know about installation @@ -730,7 +730,7 @@ if [ ! -z $TEST_FRAMEWORK ]; then add_cmake_opts -DGMOCK_MAIN_LIBRARY="$GOOGLE_INSTALL_ROOT/lib/gmock_main${DEBUG_SUFFIX}.lib" add_cmake_opts -DGTEST_LINKED_AS_SHARED_LIBRARY=True echo Done. - + fi echo diff --git a/CMakeLists.txt b/CMakeLists.txt index aaf269eef..bf489d99c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -256,12 +256,13 @@ if(NOT HAVE_STDINT_H) endif() -find_package(OpenSceneGraph 3.3.4 REQUIRED osgDB osgViewer osgText osgGA osgParticle osgUtil osgFX) -include_directories(${OPENSCENEGRAPH_INCLUDE_DIRS}) +find_package(OpenSceneGraph 3.3.4 REQUIRED osgDB osgViewer osgText osgGA osgParticle osgUtil osgFX osgShadow) +include_directories(SYSTEM ${OPENSCENEGRAPH_INCLUDE_DIRS}) set(USED_OSG_PLUGINS osgdb_bmp osgdb_dds + osgdb_freetype osgdb_jpeg osgdb_osg osgdb_png @@ -795,8 +796,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/README.md b/README.md index 9fbfc0178..6f453a741 100644 --- a/README.md +++ b/README.md @@ -105,3 +105,4 @@ Command line options --export-fonts [=arg(=1)] (=0) Export Morrowind .fnt fonts to PNG image and XML file in current directory --activate-dist arg (=-1) activation distance override + --random-seed arg (=) seed value for random number generator diff --git a/apps/esmtool/record.cpp b/apps/esmtool/record.cpp index 6cd32077c..c5153dadb 100644 --- a/apps/esmtool/record.cpp +++ b/apps/esmtool/record.cpp @@ -660,7 +660,7 @@ void Record::print() printTransport(mData.getTransport()); - std::cout << " Artifical Intelligence: " << mData.mHasAI << std::endl; + std::cout << " Artificial Intelligence: " << std::endl; std::cout << " AI Hello:" << (int)mData.mAiData.mHello << std::endl; std::cout << " AI Fight:" << (int)mData.mAiData.mFight << std::endl; std::cout << " AI Flee:" << (int)mData.mAiData.mFlee << std::endl; @@ -668,7 +668,6 @@ void Record::print() std::cout << " AI U1:" << (int)mData.mAiData.mU1 << std::endl; std::cout << " AI U2:" << (int)mData.mAiData.mU2 << std::endl; std::cout << " AI U3:" << (int)mData.mAiData.mU3 << std::endl; - std::cout << " AI U4:" << (int)mData.mAiData.mU4 << std::endl; std::cout << " AI Services:" << boost::format("0x%08X") % mData.mAiData.mServices << std::endl; for (const ESM::AIPackage &package : mData.mAiPackage.mList) @@ -740,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; } @@ -1079,7 +1078,7 @@ void Record::print() printTransport(mData.getTransport()); - std::cout << " Artifical Intelligence: " << mData.mHasAI << std::endl; + std::cout << " Artificial Intelligence: " << std::endl; std::cout << " AI Hello:" << (int)mData.mAiData.mHello << std::endl; std::cout << " AI Fight:" << (int)mData.mAiData.mFight << std::endl; std::cout << " AI Flee:" << (int)mData.mAiData.mFlee << std::endl; @@ -1087,7 +1086,6 @@ void Record::print() std::cout << " AI U1:" << (int)mData.mAiData.mU1 << std::endl; std::cout << " AI U2:" << (int)mData.mAiData.mU2 << std::endl; std::cout << " AI U3:" << (int)mData.mAiData.mU3 << std::endl; - std::cout << " AI U4:" << (int)mData.mAiData.mU4 << std::endl; std::cout << " AI Services:" << boost::format("0x%08X") % mData.mAiData.mServices << std::endl; for (const ESM::AIPackage &package : mData.mAiPackage.mList) diff --git a/apps/essimporter/importercontext.hpp b/apps/essimporter/importercontext.hpp index 86501b3cb..1a91b7cea 100644 --- a/apps/essimporter/importercontext.hpp +++ b/apps/essimporter/importercontext.hpp @@ -84,6 +84,8 @@ namespace ESSImport mGlobalMapState.mBounds.mMaxX = 0; mGlobalMapState.mBounds.mMinY = 0; mGlobalMapState.mBounds.mMaxY = 0; + + mPlayerBase.blank(); } int generateActorId() diff --git a/apps/launcher/advancedpage.cpp b/apps/launcher/advancedpage.cpp index 41f546af4..68fe235ce 100644 --- a/apps/launcher/advancedpage.cpp +++ b/apps/launcher/advancedpage.cpp @@ -80,6 +80,7 @@ bool Launcher::AdvancedPage::loadSettings() int unarmedFactorsStrengthIndex = mEngineSettings.getInt("strength influences hand to hand", "Game"); if (unarmedFactorsStrengthIndex >= 0 && unarmedFactorsStrengthIndex <= 2) unarmedFactorsStrengthComboBox->setCurrentIndex(unarmedFactorsStrengthIndex); + loadSettingBool(requireAppropriateAmmunitionCheckBox, "only appropriate ammunition bypasses resistance", "Game"); // Input Settings loadSettingBool(allowThirdPersonZoomCheckBox, "allow third person zoom", "Input"); @@ -139,6 +140,7 @@ void Launcher::AdvancedPage::saveSettings() int unarmedFactorsStrengthIndex = unarmedFactorsStrengthComboBox->currentIndex(); if (unarmedFactorsStrengthIndex != mEngineSettings.getInt("strength influences hand to hand", "Game")) mEngineSettings.setInt("strength influences hand to hand", "Game", unarmedFactorsStrengthIndex); + saveSettingBool(requireAppropriateAmmunitionCheckBox, "only appropriate ammunition bypasses resistance", "Game"); // Input Settings saveSettingBool(allowThirdPersonZoomCheckBox, "allow third person zoom", "Input"); diff --git a/apps/launcher/graphicspage.cpp b/apps/launcher/graphicspage.cpp index 072f1f36f..56cd08680 100644 --- a/apps/launcher/graphicspage.cpp +++ b/apps/launcher/graphicspage.cpp @@ -1,6 +1,5 @@ #include "graphicspage.hpp" -#include #include #include #include @@ -15,10 +14,11 @@ #include #include +#include QString getAspect(int x, int y) { - int gcd = boost::math::gcd (x, y); + int gcd = Misc::gcd (x, y); int xaspect = x / gcd; int yaspect = y / gcd; // special case: 8 : 5 is usually referred to as 16:10 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/doc/document.cpp b/apps/opencs/model/doc/document.cpp index 233b3e439..76511f535 100644 --- a/apps/opencs/model/doc/document.cpp +++ b/apps/opencs/model/doc/document.cpp @@ -364,6 +364,11 @@ int CSMDoc::Document::getState() const return state; } +const boost::filesystem::path& CSMDoc::Document::getResourceDir() const +{ + return mResDir; +} + const boost::filesystem::path& CSMDoc::Document::getSavePath() const { return mSavePath; diff --git a/apps/opencs/model/doc/document.hpp b/apps/opencs/model/doc/document.hpp index 4c442428e..a2579af4c 100644 --- a/apps/opencs/model/doc/document.hpp +++ b/apps/opencs/model/doc/document.hpp @@ -115,6 +115,8 @@ namespace CSMDoc int getState() const; + const boost::filesystem::path& getResourceDir() const; + const boost::filesystem::path& getSavePath() const; const boost::filesystem::path& getProjectPath() const; 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/tools/scriptcheck.cpp b/apps/opencs/model/tools/scriptcheck.cpp index d3d9d1503..952127edf 100644 --- a/apps/opencs/model/tools/scriptcheck.cpp +++ b/apps/opencs/model/tools/scriptcheck.cpp @@ -30,11 +30,11 @@ void CSMTools::ScriptCheckStage::report (const std::string& message, const Compi CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Script, mId); - stream << "line " << loc.mLine << ", column " << loc.mColumn << ": " << message << " (" << loc.mLiteral << ")"; + stream << message << " (" << loc.mLiteral << ")" << " @ line " << loc.mLine+1 << ", column " << loc.mColumn; std::ostringstream hintStream; - hintStream << "l:" << loc.mLine << " " << loc.mColumn; + hintStream << "l:" << loc.mLine+1 << " " << loc.mColumn; mMessages->add (id, stream.str(), hintStream.str(), getSeverity (type)); } diff --git a/apps/opencs/model/world/columnbase.hpp b/apps/opencs/model/world/columnbase.hpp index 94ebbef55..b4eee8630 100644 --- a/apps/opencs/model/world/columnbase.hpp +++ b/apps/opencs/model/world/columnbase.hpp @@ -87,6 +87,7 @@ namespace CSMWorld //CONCRETE TYPES ENDS HERE Display_UnsignedInteger8, + Display_UnsignedInteger16, Display_Integer, Display_Float, Display_Double, 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/model/world/refidadapterimp.hpp b/apps/opencs/model/world/refidadapterimp.hpp index c05a20d07..16b7739f7 100644 --- a/apps/opencs/model/world/refidadapterimp.hpp +++ b/apps/opencs/model/world/refidadapterimp.hpp @@ -627,12 +627,12 @@ namespace CSMWorld RecordT record2 = record.get(); if (column==mActors.mHello) record2.mAiData.mHello = value.toInt(); - else if (column==mActors.mFlee) - record2.mAiData.mFlee = value.toInt(); + else if (column==mActors.mFlee) // Flee, Fight and Alarm ratings are probabilities. + record2.mAiData.mFlee = std::min(100, value.toInt()); else if (column==mActors.mFight) - record2.mAiData.mFight = value.toInt(); + record2.mAiData.mFight = std::min(100, value.toInt()); else if (column==mActors.mAlarm) - record2.mAiData.mAlarm = value.toInt(); + record2.mAiData.mAlarm = std::min(100, value.toInt()); else { typename std::map::const_iterator iter = diff --git a/apps/opencs/model/world/refidcollection.cpp b/apps/opencs/model/world/refidcollection.cpp index 89d346204..bd6849492 100644 --- a/apps/opencs/model/world/refidcollection.cpp +++ b/apps/opencs/model/world/refidcollection.cpp @@ -128,7 +128,7 @@ CSMWorld::RefIdCollection::RefIdCollection() ActorColumns actorsColumns (nameColumns); - mColumns.push_back (RefIdColumn (Columns::ColumnId_AiHello, ColumnBase::Display_UnsignedInteger8)); + mColumns.push_back (RefIdColumn (Columns::ColumnId_AiHello, ColumnBase::Display_UnsignedInteger16)); actorsColumns.mHello = &mColumns.back(); mColumns.push_back (RefIdColumn (Columns::ColumnId_AiFlee, ColumnBase::Display_UnsignedInteger8)); actorsColumns.mFlee = &mColumns.back(); diff --git a/apps/opencs/view/doc/adjusterwidget.cpp b/apps/opencs/view/doc/adjusterwidget.cpp index 06da68ed8..509e656c3 100644 --- a/apps/opencs/view/doc/adjusterwidget.cpp +++ b/apps/opencs/view/doc/adjusterwidget.cpp @@ -1,7 +1,8 @@ #include "adjusterwidget.hpp" +#include + #include -#include #include #include @@ -70,8 +71,7 @@ void CSVDoc::AdjusterWidget::setName (const QString& name, bool addon) { boost::filesystem::path path (name.toUtf8().data()); - std::string extension = path.extension().string(); - boost::algorithm::to_lower(extension); + std::string extension = Misc::StringUtils::lowerCase(path.extension().string()); bool isLegacyPath = (extension == ".esm" || extension == ".esp"); diff --git a/apps/opencs/view/doc/view.cpp b/apps/opencs/view/doc/view.cpp index ed4dcff76..42dbe51ac 100644 --- a/apps/opencs/view/doc/view.cpp +++ b/apps/opencs/view/doc/view.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -25,6 +26,8 @@ #include "../tools/subviews.hpp" +#include + #include "viewmanager.hpp" #include "operations.hpp" #include "subview.hpp" @@ -303,6 +306,17 @@ void CSVDoc::View::setupDebugMenu() connect (runLog, SIGNAL (triggered()), this, SLOT (addRunLogSubView())); } +void CSVDoc::View::setupHelpMenu() +{ + QMenu *help = menuBar()->addMenu (tr ("Help")); + + QAction* about = createMenuEntry("About OpenMW-CS", ":./info.png", help, "document-help-about"); + connect (about, SIGNAL (triggered()), this, SLOT (infoAbout())); + + QAction* aboutQt = createMenuEntry("About Qt", ":./qt.png", help, "document-help-qt"); + connect (aboutQt, SIGNAL (triggered()), this, SLOT (infoAboutQt())); +} + QAction* CSVDoc::View::createMenuEntry(CSMWorld::UniversalId::Type type, QMenu* menu, const char* shortcutName) { const std::string title = CSMWorld::UniversalId (type).getTypeName(); @@ -339,6 +353,7 @@ void CSVDoc::View::setupUi() setupCharacterMenu(); setupAssetsMenu(); setupDebugMenu(); + setupHelpMenu(); } void CSVDoc::View::setupShortcut(const char* name, QAction* action) @@ -674,6 +689,52 @@ void CSVDoc::View::save() mDocument->save(); } +void CSVDoc::View::infoAbout() +{ + // Get current OpenMW version + QString versionInfo = (Version::getOpenmwVersionDescription(mDocument->getResourceDir().string())+ +#if defined(__x86_64__) || defined(_M_X64) + " (64-bit)").c_str(); +#else + " (32-bit)").c_str(); +#endif + + // Get current year + time_t now = time(NULL); + struct tm tstruct; + char copyrightInfo[40]; + tstruct = *localtime(&now); + strftime(copyrightInfo, sizeof(copyrightInfo), "Copyright © 2008-%Y OpenMW Team", &tstruct); + + QString aboutText = QString( + "

" + "

OpenMW Construction Set

" + "%1\n\n" + "%2\n\n" + "%3\n\n" + "" + "" + "" + "" + "" + "
%4https://openmw.org
%5https://forum.openmw.org
%6https://gitlab.com/OpenMW/openmw/issues
%7irc://irc.freenode.net/#openmw
" + "

") + .arg(versionInfo + , tr("OpenMW-CS is a content file editor for OpenMW, a modern, free and open source game engine.") + , tr(copyrightInfo) + , tr("Home Page:") + , tr("Forum:") + , tr("Bug Tracker:") + , tr("IRC:")); + + QMessageBox::about(this, "About OpenMW-CS", aboutText); +} + +void CSVDoc::View::infoAboutQt() +{ + QMessageBox::aboutQt(this); +} + void CSVDoc::View::verify() { addSubView (mDocument->verify()); diff --git a/apps/opencs/view/doc/view.hpp b/apps/opencs/view/doc/view.hpp index 7a9a48b0f..c4046a7a1 100644 --- a/apps/opencs/view/doc/view.hpp +++ b/apps/opencs/view/doc/view.hpp @@ -85,6 +85,8 @@ namespace CSVDoc void setupDebugMenu(); + void setupHelpMenu(); + void setupUi(); void setupShortcut(const char* name, QAction* action); @@ -165,6 +167,10 @@ namespace CSVDoc void exit(); + void infoAbout(); + + void infoAboutQt(); + void verify(); void addGlobalsSubView(); 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/opencs/view/world/table.cpp b/apps/opencs/view/world/table.cpp index 14df4658d..eb7b8e334 100644 --- a/apps/opencs/view/world/table.cpp +++ b/apps/opencs/view/world/table.cpp @@ -141,13 +141,16 @@ void CSVWorld::Table::contextMenuEvent (QContextMenuEvent *event) if (mModel->getFeatures() & CSMWorld::IdTableBase::Feature_Preview) { + const CSMWorld::UniversalId id = getUniversalId(currentRow); + const CSMWorld::UniversalId::Type type = id.getType(); + QModelIndex index = mModel->index (row, mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Modification)); CSMWorld::RecordBase::State state = static_cast ( mModel->data (index).toInt()); - if (state!=CSMWorld::RecordBase::State_Deleted) + if (state!=CSMWorld::RecordBase::State_Deleted && type != CSMWorld::UniversalId::Type_ItemLevelledList) menu.addAction (mPreviewAction); } } diff --git a/apps/opencs/view/world/util.cpp b/apps/opencs/view/world/util.cpp index fef805d56..3aee51e98 100644 --- a/apps/opencs/view/world/util.cpp +++ b/apps/opencs/view/world/util.cpp @@ -219,6 +219,13 @@ QWidget *CSVWorld::CommandDelegate::createEditor (QWidget *parent, const QStyleO return sb; } + case CSMWorld::ColumnBase::Display_UnsignedInteger16: + { + DialogueSpinBox *sb = new DialogueSpinBox(parent); + sb->setRange(0, std::numeric_limits::max()); + return sb; + } + case CSMWorld::ColumnBase::Display_Var: return new QLineEdit(parent); diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 0bb6e900d..554a3ac86 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -126,6 +126,7 @@ target_link_libraries(openmw ${OSGDB_LIBRARIES} ${OSGVIEWER_LIBRARIES} ${OSGGA_LIBRARIES} + ${OSGSHADOW_LIBRARIES} ${Boost_SYSTEM_LIBRARY} ${Boost_THREAD_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 15a8f0f32..181ebd822 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -32,12 +32,13 @@ #include +#include + #include "mwinput/inputmanagerimp.hpp" #include "mwgui/windowmanagerimp.hpp" #include "mwscript/scriptmanagerimp.hpp" -#include "mwscript/extensions.hpp" #include "mwscript/interpretercontext.hpp" #include "mwsound/soundmanagerimp.hpp" @@ -199,6 +200,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); } } @@ -223,13 +226,13 @@ OMW::Engine::Engine(Files::ConfigurationManager& configurationManager) , mActivationDistanceOverride(-1) , mGrab(true) , mExportFonts(false) + , mRandomSeed(0) , mScriptContext (0) , mFSStrict (false) , mScriptBlacklistUse (true) , mNewGame (false) , mCfgMgr(configurationManager) { - Misc::Rng::init(); MWClass::registerClasses(); Uint32 flags = SDL_INIT_VIDEO|SDL_INIT_NOPARACHUTE|SDL_INIT_GAMECONTROLLER|SDL_INIT_JOYSTICK; @@ -258,6 +261,9 @@ OMW::Engine::~Engine() mViewer = nullptr; + delete mEncoder; + mEncoder = nullptr; + if (mWindow) { SDL_DestroyWindow(mWindow); @@ -545,7 +551,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()); @@ -650,6 +656,11 @@ void OMW::Engine::go() assert (!mContentFiles.empty()); 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); // Load settings Settings::Manager settings; @@ -657,8 +668,7 @@ void OMW::Engine::go() settingspath = loadSettings (settings); // Create encoder - ToUTF8::Utf8Encoder encoder (mEncoding); - mEncoder = &encoder; + mEncoder = new ToUTF8::Utf8Encoder(mEncoding); // Setup viewer mViewer = new osgViewer::Viewer; @@ -701,22 +711,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; @@ -820,3 +829,8 @@ void OMW::Engine::setSaveGameFile(const std::string &savegame) { mSaveGameFile = savegame; } + +void OMW::Engine::setRandomSeed(unsigned int seed) +{ + mRandomSeed = seed; +} diff --git a/apps/openmw/engine.hpp b/apps/openmw/engine.hpp index e42a5c94f..bfe9759cd 100644 --- a/apps/openmw/engine.hpp +++ b/apps/openmw/engine.hpp @@ -100,6 +100,7 @@ namespace OMW bool mGrab; bool mExportFonts; + unsigned int mRandomSeed; Compiler::Extensions mExtensions; Compiler::Context *mScriptContext; @@ -203,6 +204,8 @@ namespace OMW /// Set the save game file to load after initialising the engine. void setSaveGameFile(const std::string& savegame); + void setRandomSeed(unsigned int seed); + private: Files::ConfigurationManager& mCfgMgr; }; diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index 7c39a785e..a56cbfe8a 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include "engine.hpp" @@ -131,7 +132,12 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat ("export-fonts", bpo::value()->implicit_value(true) ->default_value(false), "Export Morrowind .fnt fonts to PNG image and XML file in current directory") - ("activate-dist", bpo::value ()->default_value (-1), "activation distance override"); + ("activate-dist", bpo::value ()->default_value (-1), "activation distance override") + + ("random-seed", bpo::value () + ->default_value(Misc::Rng::generateDefaultSeed()), + "seed value for random number generator") + ; bpo::parsed_options valid_opts = bpo::command_line_parser(argc, argv) .options(desc).allow_unregistered().run(); @@ -231,6 +237,7 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat engine.setFallbackValues(variables["fallback"].as().mMap); engine.setActivationDistanceOverride (variables["activate-dist"].as()); engine.enableFontExport(variables["export-fonts"].as()); + engine.setRandomSeed(variables["random-seed"].as()); return true; } diff --git a/apps/openmw/mwbase/mechanicsmanager.hpp b/apps/openmw/mwbase/mechanicsmanager.hpp index 1bdd8f8b5..5c5821bca 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 1728af659..d3bdb419e 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -57,7 +57,7 @@ namespace MWMechanics namespace DetourNavigator { - class Navigator; + struct Navigator; } namespace MWWorld @@ -134,6 +134,7 @@ namespace MWBase virtual MWWorld::Player& getPlayer() = 0; virtual MWWorld::Ptr getPlayerPtr() = 0; + virtual MWWorld::ConstPtr getPlayerConstPtr() const = 0; virtual const MWWorld::ESMStore& getStore() const = 0; @@ -587,7 +588,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; @@ -615,6 +616,9 @@ namespace MWBase virtual void removeActorPath(const MWWorld::ConstPtr& actor) const = 0; virtual void setNavMeshNumberToRender(const std::size_t value) = 0; + + /// Return physical half extents of the given actor to be used in pathfinding + virtual osg::Vec3f getPathfindingHalfExtents(const MWWorld::ConstPtr& actor) const = 0; }; } diff --git a/apps/openmw/mwclass/activator.cpp b/apps/openmw/mwclass/activator.cpp index 42ea99b00..eabac1644 100644 --- a/apps/openmw/mwclass/activator.cpp +++ b/apps/openmw/mwclass/activator.cpp @@ -2,10 +2,10 @@ #include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" -#include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/world.hpp" #include "../mwworld/cellstore.hpp" @@ -19,6 +19,7 @@ #include "../mwrender/objects.hpp" #include "../mwrender/renderinginterface.hpp" +#include "../mwrender/vismask.hpp" #include "../mwgui/tooltips.hpp" @@ -30,8 +31,10 @@ namespace MWClass void Activator::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { - if (!model.empty()) { + if (!model.empty()) + { renderingInterface.getObjects().insertModel(ptr, model, true); + ptr.getRefData().getBaseNode()->setNodeMask(MWRender::Mask_Static); } } diff --git a/apps/openmw/mwclass/actor.cpp b/apps/openmw/mwclass/actor.cpp index 73a4d37d7..aa356f62d 100644 --- a/apps/openmw/mwclass/actor.cpp +++ b/apps/openmw/mwclass/actor.cpp @@ -4,7 +4,6 @@ #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" -#include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwmechanics/creaturestats.hpp" diff --git a/apps/openmw/mwclass/apparatus.cpp b/apps/openmw/mwclass/apparatus.cpp index e151e39c5..5e7c1b933 100644 --- a/apps/openmw/mwclass/apparatus.cpp +++ b/apps/openmw/mwclass/apparatus.cpp @@ -3,11 +3,9 @@ #include #include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwworld/ptr.hpp" -#include "../mwworld/actiontake.hpp" #include "../mwworld/actionalchemy.hpp" #include "../mwworld/cellstore.hpp" #include "../mwphysics/physicssystem.hpp" diff --git a/apps/openmw/mwclass/armor.cpp b/apps/openmw/mwclass/armor.cpp index 0a4958eb9..7ab1d49fe 100644 --- a/apps/openmw/mwclass/armor.cpp +++ b/apps/openmw/mwclass/armor.cpp @@ -9,7 +9,6 @@ #include "../mwbase/windowmanager.hpp" #include "../mwworld/ptr.hpp" -#include "../mwworld/actiontake.hpp" #include "../mwworld/actionequip.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwworld/cellstore.hpp" diff --git a/apps/openmw/mwclass/clothing.cpp b/apps/openmw/mwclass/clothing.cpp index 21c0d89c2..7e3e77d58 100644 --- a/apps/openmw/mwclass/clothing.cpp +++ b/apps/openmw/mwclass/clothing.cpp @@ -7,7 +7,6 @@ #include "../mwbase/windowmanager.hpp" #include "../mwworld/ptr.hpp" -#include "../mwworld/actiontake.hpp" #include "../mwworld/actionequip.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwworld/cellstore.hpp" diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp index e1784d593..3a501a93f 100644 --- a/apps/openmw/mwclass/container.cpp +++ b/apps/openmw/mwclass/container.cpp @@ -6,7 +6,6 @@ #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" -#include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwworld/ptr.hpp" @@ -241,8 +240,8 @@ namespace MWClass std::string text; int lockLevel = ptr.getCellRef().getLockLevel(); if (lockLevel > 0 && lockLevel != ESM::UnbreakableLock) - text += "\n#{sLockLevel}: " + MWGui::ToolTips::toString(ptr.getCellRef().getLockLevel()); - else if (ptr.getCellRef().getLockLevel() < 0) + text += "\n#{sLockLevel}: " + MWGui::ToolTips::toString(lockLevel); + else if (lockLevel < 0) text += "\n#{sUnlocked}"; if (ptr.getCellRef().getTrap() != "") text += "\n#{sTrapped}"; diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 962a5bab5..8f17a3038 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -308,6 +308,7 @@ namespace MWClass { damage = attack[0] + ((attack[1]-attack[0])*attackStrength); MWMechanics::adjustWeaponDamage(damage, weapon, ptr); + MWMechanics::resistNormalWeapon(victim, ptr, weapon, damage); MWMechanics::reduceWeaponCondition(damage, true, weapon, ptr); } @@ -382,9 +383,6 @@ namespace MWClass if (!object.isEmpty()) stats.setLastHitObject(object.getCellRef().getRefId()); - if (damage > 0.0f && !object.isEmpty()) - MWMechanics::resistNormalWeapon(ptr, attacker, object, damage); - if (damage < 0.001f) damage = 0; @@ -610,11 +608,7 @@ namespace MWClass int Creature::getServices(const MWWorld::ConstPtr &actor) const { - const MWWorld::LiveCellRef* ref = actor.get(); - if (ref->mBase->mHasAI) - return ref->mBase->mAiData.mServices; - else - return 0; + return actor.get()->mBase->mAiData.mServices; } bool Creature::isPersistent(const MWWorld::ConstPtr &actor) const diff --git a/apps/openmw/mwclass/door.cpp b/apps/openmw/mwclass/door.cpp index a26118029..c47399fe7 100644 --- a/apps/openmw/mwclass/door.cpp +++ b/apps/openmw/mwclass/door.cpp @@ -2,15 +2,14 @@ #include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/soundmanager.hpp" -#include "../mwbase/mechanicsmanager.hpp" #include "../mwworld/ptr.hpp" -#include "../mwworld/nullaction.hpp" #include "../mwworld/failedaction.hpp" #include "../mwworld/actionteleport.hpp" #include "../mwworld/actiondoor.hpp" @@ -26,6 +25,7 @@ #include "../mwrender/objects.hpp" #include "../mwrender/renderinginterface.hpp" #include "../mwrender/animation.hpp" +#include "../mwrender/vismask.hpp" #include "../mwmechanics/actorutil.hpp" @@ -55,8 +55,10 @@ namespace MWClass void Door::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { - if (!model.empty()) { + if (!model.empty()) + { renderingInterface.getObjects().insertModel(ptr, model, true); + ptr.getRefData().getBaseNode()->setNodeMask(MWRender::Mask_Static); } } diff --git a/apps/openmw/mwclass/ingredient.cpp b/apps/openmw/mwclass/ingredient.cpp index 70339564c..84117f59d 100644 --- a/apps/openmw/mwclass/ingredient.cpp +++ b/apps/openmw/mwclass/ingredient.cpp @@ -7,7 +7,6 @@ #include "../mwbase/windowmanager.hpp" #include "../mwworld/ptr.hpp" -#include "../mwworld/actiontake.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/esmstore.hpp" #include "../mwphysics/physicssystem.hpp" diff --git a/apps/openmw/mwclass/light.cpp b/apps/openmw/mwclass/light.cpp index ea0abd6f6..38b7137f3 100644 --- a/apps/openmw/mwclass/light.cpp +++ b/apps/openmw/mwclass/light.cpp @@ -5,20 +5,16 @@ #include #include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwbase/windowmanager.hpp" -#include "../mwbase/mechanicsmanager.hpp" #include "../mwworld/ptr.hpp" -#include "../mwworld/actiontake.hpp" #include "../mwworld/actionequip.hpp" #include "../mwworld/nullaction.hpp" #include "../mwworld/failedaction.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwworld/cellstore.hpp" #include "../mwphysics/physicssystem.hpp" -#include "../mwworld/customdata.hpp" #include "../mwgui/tooltips.hpp" diff --git a/apps/openmw/mwclass/lockpick.cpp b/apps/openmw/mwclass/lockpick.cpp index 5673465a0..013808bb2 100644 --- a/apps/openmw/mwclass/lockpick.cpp +++ b/apps/openmw/mwclass/lockpick.cpp @@ -4,11 +4,9 @@ #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" -#include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwworld/ptr.hpp" -#include "../mwworld/actiontake.hpp" #include "../mwworld/actionequip.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwworld/cellstore.hpp" diff --git a/apps/openmw/mwclass/misc.cpp b/apps/openmw/mwclass/misc.cpp index 40ec2b34b..51b641d4d 100644 --- a/apps/openmw/mwclass/misc.cpp +++ b/apps/openmw/mwclass/misc.cpp @@ -7,7 +7,6 @@ #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" -#include "../mwworld/actiontake.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/esmstore.hpp" #include "../mwphysics/physicssystem.hpp" diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 317764e0c..5e03477c4 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -619,6 +619,8 @@ namespace MWClass damage = attack[0] + ((attack[1]-attack[0])*attackStrength); } MWMechanics::adjustWeaponDamage(damage, weapon, ptr); + MWMechanics::resistNormalWeapon(victim, ptr, weapon, damage); + MWMechanics::applyWerewolfDamageMult(victim, weapon, damage); MWMechanics::reduceWeaponCondition(damage, true, weapon, ptr); healthdmg = true; } @@ -780,7 +782,7 @@ namespace MWClass MWWorld::Ptr armor = ((armorslot != inv.end()) ? *armorslot : MWWorld::Ptr()); if(!armor.isEmpty() && armor.getTypeName() == typeid(ESM::Armor).name()) { - if (attacker.isEmpty() || (!attacker.isEmpty() && !(object.isEmpty() && !attacker.getClass().isNpc()))) // Unarmed creature attacks don't affect armor condition + if (!object.isEmpty() || attacker.isEmpty() || attacker.getClass().isNpc()) // Unarmed creature attacks don't affect armor condition { int armorhealth = armor.getClass().getItemHealth(armor); armorhealth -= std::min(damageDiff, armorhealth); @@ -935,8 +937,12 @@ namespace MWClass const float normalizedEncumbrance = getNormalizedEncumbrance(ptr); - bool sneaking = MWBase::Environment::get().getMechanicsManager()->isSneaking(ptr) && stats.getStance(MWMechanics::CreatureStats::Stance_Sneak); - bool running = MWBase::Environment::get().getMechanicsManager()->isRunning(ptr) && stats.getStance(MWMechanics::CreatureStats::Stance_Run); + bool swimming = world->isSwimming(ptr); + bool inair = !world->isOnGround(ptr) && !swimming && !world->isFlying(ptr); + bool sneaking = stats.getStance(MWMechanics::CreatureStats::Stance_Sneak); + sneaking = sneaking && (inair || MWBase::Environment::get().getMechanicsManager()->isSneaking(ptr)); + bool running = stats.getStance(MWMechanics::CreatureStats::Stance_Run); + running = running && (inair || MWBase::Environment::get().getMechanicsManager()->isRunning(ptr)); float walkSpeed = gmst.fMinWalkSpeed->mValue.getFloat() + 0.01f*npcdata->mNpcStats.getAttribute(ESM::Attribute::Speed).getModified()* (gmst.fMaxWalkSpeed->mValue.getFloat() - gmst.fMinWalkSpeed->mValue.getFloat()); @@ -961,7 +967,7 @@ namespace MWClass flySpeed = std::max(0.0f, flySpeed); moveSpeed = flySpeed; } - else if (world->isSwimming(ptr)) + else if (swimming) { float swimSpeed = walkSpeed; if(running) @@ -971,7 +977,7 @@ namespace MWClass gmst.fSwimRunAthleticsMult->mValue.getFloat(); moveSpeed = swimSpeed; } - else if (running) + else if (running && !sneaking) moveSpeed = runSpeed; else moveSpeed = walkSpeed; @@ -1199,11 +1205,7 @@ namespace MWClass int Npc::getServices(const MWWorld::ConstPtr &actor) const { - const MWWorld::LiveCellRef* ref = actor.get(); - if (ref->mBase->mHasAI) - return ref->mBase->mAiData.mServices; - else - return 0; + return actor.get()->mBase->mAiData.mServices; } diff --git a/apps/openmw/mwclass/potion.cpp b/apps/openmw/mwclass/potion.cpp index 39b1cc6c2..4e95c6294 100644 --- a/apps/openmw/mwclass/potion.cpp +++ b/apps/openmw/mwclass/potion.cpp @@ -7,11 +7,9 @@ #include "../mwbase/windowmanager.hpp" #include "../mwworld/ptr.hpp" -#include "../mwworld/actiontake.hpp" #include "../mwworld/actionapply.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/esmstore.hpp" -#include "../mwworld/containerstore.hpp" #include "../mwphysics/physicssystem.hpp" #include "../mwworld/nullaction.hpp" diff --git a/apps/openmw/mwclass/probe.cpp b/apps/openmw/mwclass/probe.cpp index 734b6b1fd..b53e2418a 100644 --- a/apps/openmw/mwclass/probe.cpp +++ b/apps/openmw/mwclass/probe.cpp @@ -4,11 +4,9 @@ #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" -#include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwworld/ptr.hpp" -#include "../mwworld/actiontake.hpp" #include "../mwworld/actionequip.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwworld/cellstore.hpp" diff --git a/apps/openmw/mwclass/repair.cpp b/apps/openmw/mwclass/repair.cpp index 7cd7ef10d..922858623 100644 --- a/apps/openmw/mwclass/repair.cpp +++ b/apps/openmw/mwclass/repair.cpp @@ -3,14 +3,11 @@ #include #include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwworld/ptr.hpp" -#include "../mwworld/actiontake.hpp" #include "../mwworld/cellstore.hpp" #include "../mwphysics/physicssystem.hpp" -#include "../mwworld/nullaction.hpp" #include "../mwworld/actionrepair.hpp" #include "../mwgui/tooltips.hpp" diff --git a/apps/openmw/mwclass/static.cpp b/apps/openmw/mwclass/static.cpp index 40a6b998c..a6e4fe5e7 100644 --- a/apps/openmw/mwclass/static.cpp +++ b/apps/openmw/mwclass/static.cpp @@ -1,6 +1,7 @@ #include "static.hpp" #include +#include #include "../mwworld/ptr.hpp" #include "../mwphysics/physicssystem.hpp" @@ -8,14 +9,17 @@ #include "../mwrender/objects.hpp" #include "../mwrender/renderinginterface.hpp" +#include "../mwrender/vismask.hpp" namespace MWClass { void Static::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { - if (!model.empty()) { + if (!model.empty()) + { renderingInterface.getObjects().insertModel(ptr, model); + ptr.getRefData().getBaseNode()->setNodeMask(MWRender::Mask_Static); } } diff --git a/apps/openmw/mwclass/weapon.cpp b/apps/openmw/mwclass/weapon.cpp index 508cf47ae..dd6d63166 100644 --- a/apps/openmw/mwclass/weapon.cpp +++ b/apps/openmw/mwclass/weapon.cpp @@ -10,7 +10,6 @@ #include "../mwbase/windowmanager.hpp" #include "../mwworld/ptr.hpp" -#include "../mwworld/actiontake.hpp" #include "../mwworld/actionequip.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwworld/cellstore.hpp" diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp index 9d2e3e288..ef9a3708a 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp @@ -1,9 +1,6 @@ #include "dialoguemanagerimp.hpp" -#include -#include #include -#include #include #include diff --git a/apps/openmw/mwdialogue/quest.cpp b/apps/openmw/mwdialogue/quest.cpp index 846597886..5f20a8abb 100644 --- a/apps/openmw/mwdialogue/quest.cpp +++ b/apps/openmw/mwdialogue/quest.cpp @@ -41,19 +41,6 @@ namespace MWDialogue void Quest::setIndex (int index) { - const ESM::Dialogue *dialogue = - MWBase::Environment::get().getWorld()->getStore().get().find (mTopic); - - for (ESM::Dialogue::InfoContainer::const_iterator iter (dialogue->mInfo.begin()); - iter!=dialogue->mInfo.end(); ++iter) - if (iter->mData.mDisposition==index && iter->mQuestStatus!=ESM::DialInfo::QS_Name) - { - if (iter->mQuestStatus==ESM::DialInfo::QS_Finished) - mFinished = true; - else if (iter->mQuestStatus==ESM::DialInfo::QS_Restart) - mFinished = false; - } - // The index must be set even if no related journal entry was found mIndex = index; } @@ -81,8 +68,18 @@ namespace MWDialogue if (index==-1) throw std::runtime_error ("unknown journal entry for topic " + mTopic); + for (auto &info : dialogue->mInfo) + { + if (info.mData.mJournalIndex == index + && (info.mQuestStatus == ESM::DialInfo::QS_Finished || info.mQuestStatus == ESM::DialInfo::QS_Restart)) + { + mFinished = (info.mQuestStatus == ESM::DialInfo::QS_Finished); + break; + } + } + if (index > mIndex) - setIndex (index); + mIndex = index; for (TEntryIter iter (mEntries.begin()); iter!=mEntries.end(); ++iter) if (iter->mInfoId==entry.mInfoId) diff --git a/apps/openmw/mwdialogue/scripttest.cpp b/apps/openmw/mwdialogue/scripttest.cpp index 4f0b7422c..791a4da7e 100644 --- a/apps/openmw/mwdialogue/scripttest.cpp +++ b/apps/openmw/mwdialogue/scripttest.cpp @@ -15,7 +15,6 @@ #include #include #include -#include #include #include "filter.hpp" diff --git a/apps/openmw/mwdialogue/selectwrapper.cpp b/apps/openmw/mwdialogue/selectwrapper.cpp index 793963e48..28dbd214b 100644 --- a/apps/openmw/mwdialogue/selectwrapper.cpp +++ b/apps/openmw/mwdialogue/selectwrapper.cpp @@ -1,9 +1,6 @@ #include "selectwrapper.hpp" -#include - #include -#include #include #include diff --git a/apps/openmw/mwgui/alchemywindow.cpp b/apps/openmw/mwgui/alchemywindow.cpp index fa8a96185..9db7a055b 100644 --- a/apps/openmw/mwgui/alchemywindow.cpp +++ b/apps/openmw/mwgui/alchemywindow.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -237,15 +238,15 @@ namespace MWGui std::set effectIds = mAlchemy->listEffects(); Widgets::SpellEffectList list; unsigned int effectIndex=0; - for (std::set::iterator it2 = effectIds.begin(); it2 != effectIds.end(); ++it2) + for (const MWMechanics::EffectKey& effectKey : effectIds) { Widgets::SpellEffectParams params; - params.mEffectID = it2->mId; - const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find(it2->mId); + params.mEffectID = effectKey.mId; + const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find(effectKey.mId); if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetSkill) - params.mSkill = it2->mArg; + params.mSkill = effectKey.mArg; else if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetAttribute) - params.mAttribute = it2->mArg; + params.mAttribute = effectKey.mArg; params.mIsConstant = true; params.mNoTarget = true; diff --git a/apps/openmw/mwgui/alchemywindow.hpp b/apps/openmw/mwgui/alchemywindow.hpp index aa23fbaa5..c6eb00792 100644 --- a/apps/openmw/mwgui/alchemywindow.hpp +++ b/apps/openmw/mwgui/alchemywindow.hpp @@ -1,12 +1,9 @@ #ifndef MWGUI_ALCHEMY_H #define MWGUI_ALCHEMY_H +#include #include -#include - -#include "../mwmechanics/alchemy.hpp" - #include #include "controllers.hpp" diff --git a/apps/openmw/mwgui/birth.cpp b/apps/openmw/mwgui/birth.cpp index c1867541b..6f6a621ad 100644 --- a/apps/openmw/mwgui/birth.cpp +++ b/apps/openmw/mwgui/birth.cpp @@ -145,35 +145,35 @@ namespace MWGui // sort by name std::vector < std::pair > birthSigns; - MWWorld::Store::iterator it = signs.begin(); - for (; it != signs.end(); ++it) + for (const ESM::BirthSign& sign : signs) { - birthSigns.push_back(std::make_pair(it->mId, &(*it))); + birthSigns.push_back(std::make_pair(sign.mId, &sign)); } std::sort(birthSigns.begin(), birthSigns.end(), sortBirthSigns); int index = 0; - for (std::vector >::const_iterator it2 = birthSigns.begin(); - it2 != birthSigns.end(); ++it2, ++index) + for (auto& birthsignPair : birthSigns) { - mBirthList->addItem(it2->second->mName, it2->first); + mBirthList->addItem(birthsignPair.second->mName, birthsignPair.first); if (mCurrentBirthId.empty()) { mBirthList->setIndexSelected(index); - mCurrentBirthId = it2->first; + mCurrentBirthId = birthsignPair.first; } - else if (Misc::StringUtils::ciEqual(it2->first, mCurrentBirthId)) + else if (Misc::StringUtils::ciEqual(birthsignPair.first, mCurrentBirthId)) { mBirthList->setIndexSelected(index); } + + index++; } } void BirthDialog::updateSpells() { - for (std::vector::iterator it = mSpellItems.begin(); it != mSpellItems.end(); ++it) + for (MyGUI::Widget* widget : mSpellItems) { - MyGUI::Gui::getInstance().destroyWidget(*it); + MyGUI::Gui::getInstance().destroyWidget(widget); } mSpellItems.clear(); diff --git a/apps/openmw/mwgui/bookpage.hpp b/apps/openmw/mwgui/bookpage.hpp index 4ea59414d..7f7dfd20a 100644 --- a/apps/openmw/mwgui/bookpage.hpp +++ b/apps/openmw/mwgui/bookpage.hpp @@ -6,6 +6,7 @@ #include "MyGUI_FontManager.h" #include +#include #include #include diff --git a/apps/openmw/mwgui/charactercreation.cpp b/apps/openmw/mwgui/charactercreation.cpp index e3effa995..64b8cb7c6 100644 --- a/apps/openmw/mwgui/charactercreation.cpp +++ b/apps/openmw/mwgui/charactercreation.cpp @@ -257,19 +257,17 @@ namespace MWGui { std::map attributes = MWBase::Environment::get().getWindowManager()->getPlayerAttributeValues(); - for (std::map::iterator it = attributes.begin(); - it != attributes.end(); ++it) + for (auto& attributePair : attributes) { - mReviewDialog->setAttribute(static_cast (it->first), it->second); + mReviewDialog->setAttribute(static_cast (attributePair.first), attributePair.second); } } { std::map skills = MWBase::Environment::get().getWindowManager()->getPlayerSkillValues(); - for (std::map::iterator it = skills.begin(); - it != skills.end(); ++it) + for (auto& skillPair : skills) { - mReviewDialog->setSkillValue(static_cast (it->first), it->second); + mReviewDialog->setSkillValue(static_cast (skillPair.first), skillPair.second); } mReviewDialog->configureSkills(MWBase::Environment::get().getWindowManager()->getPlayerMajorSkills(), MWBase::Environment::get().getWindowManager()->getPlayerMinorSkills()); } @@ -554,7 +552,7 @@ namespace MWGui { if (mGenerateClassStep == 10) { - static boost::array classes = { { + static std::array classes = { { {"Acrobat", {6, 2, 2}}, {"Agent", {6, 1, 3}}, {"Archer", {3, 5, 2}}, diff --git a/apps/openmw/mwgui/class.cpp b/apps/openmw/mwgui/class.cpp index 45abe889e..a92ad934c 100644 --- a/apps/openmw/mwgui/class.cpp +++ b/apps/openmw/mwgui/class.cpp @@ -207,25 +207,25 @@ namespace MWGui const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); - std::vector > items; // class id, class name - for (MWWorld::Store::iterator it = store.get().begin(); it != store.get().end(); ++it) + std::vector > items; // class id, class name + for (const ESM::Class& classInfo : store.get()) { - bool playable = (it->mData.mIsPlayable != 0); + bool playable = (classInfo.mData.mIsPlayable != 0); if (!playable) // Only display playable classes continue; - if (store.get().isDynamic(it->mId)) + if (store.get().isDynamic(classInfo.mId)) continue; // custom-made class not relevant for this dialog - items.push_back(std::make_pair(it->mId, it->mName)); + items.push_back(std::make_pair(classInfo.mId, classInfo.mName)); } std::sort(items.begin(), items.end(), sortClasses); int index = 0; - for (std::vector >::const_iterator it = items.begin(); it != items.end(); ++it) + for (auto& itemPair : items) { - const std::string &id = it->first; - mClassList->addItem(it->second, id); + const std::string &id = itemPair.first; + mClassList->addItem(itemPair.second, id); if (mCurrentClassId.empty()) { mCurrentClassId = id; @@ -332,19 +332,17 @@ namespace MWGui void InfoBoxDialog::setButtons(ButtonList &buttons) { - for (std::vector::iterator it = this->mButtons.begin(); it != this->mButtons.end(); ++it) + for (MyGUI::Button* button : this->mButtons) { - MyGUI::Gui::getInstance().destroyWidget(*it); + MyGUI::Gui::getInstance().destroyWidget(button); } this->mButtons.clear(); // TODO: The buttons should be generated from a template in the layout file, ie. cloning an existing widget MyGUI::Button* button; MyGUI::IntCoord coord = MyGUI::IntCoord(0, 0, mButtonBar->getWidth(), 10); - ButtonList::const_iterator end = buttons.end(); - for (ButtonList::const_iterator it = buttons.begin(); it != end; ++it) + for (const std::string &text : buttons) { - const std::string &text = *it; button = mButtonBar->createWidget("MW_Button", coord, MyGUI::Align::Top | MyGUI::Align::HCenter, ""); button->getSubWidgetText()->setWordWrap(true); button->setCaption(text); @@ -368,11 +366,10 @@ namespace MWGui void InfoBoxDialog::onButtonClicked(MyGUI::Widget* _sender) { - std::vector::const_iterator end = mButtons.end(); int i = 0; - for (std::vector::const_iterator it = mButtons.begin(); it != end; ++it) + for (MyGUI::Button* button : mButtons) { - if (*it == _sender) + if (button == _sender) { eventButtonSelected(i); return; @@ -430,10 +427,9 @@ namespace MWGui mSkills.push_back(mMinorSkill[i]); } - std::vector::const_iterator end = mSkills.end(); - for (std::vector::const_iterator it = mSkills.begin(); it != end; ++it) + for (Widgets::MWSkillPtr& skill : mSkills) { - (*it)->eventClicked += MyGUI::newDelegate(this, &CreateClassDialog::onSkillClicked); + skill->eventClicked += MyGUI::newDelegate(this, &CreateClassDialog::onSkillClicked); } setText("LabelT", MWBase::Environment::get().getWindowManager()->getGameSettingString("sName", "")); @@ -642,14 +638,13 @@ namespace MWGui ESM::Skill::SkillEnum id = mSkillDialog->getSkillId(); // Avoid duplicate skills by swapping any skill field that matches the selected one - std::vector::const_iterator end = mSkills.end(); - for (std::vector::const_iterator it = mSkills.begin(); it != end; ++it) + for (Widgets::MWSkillPtr& skill : mSkills) { - if (*it == mAffectedSkill) + if (skill == mAffectedSkill) continue; - if ((*it)->getSkillId() == id) + if (skill->getSkillId() == id) { - (*it)->setSkillId(mAffectedSkill->getSkillId()); + skill->setSkillId(mAffectedSkill->getSkillId()); break; } } diff --git a/apps/openmw/mwgui/console.cpp b/apps/openmw/mwgui/console.cpp index dc22e4193..899bc0d90 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)) @@ -231,11 +239,13 @@ namespace MWGui { int i = 0; printOK(""); - for(std::vector::iterator it=matches.begin(); it < matches.end(); ++it,++i ) + for(std::string& match : matches) { - printOK( *it ); - if( i == 50 ) + if(i == 50) break; + + printOK(match); + i++; } } } @@ -352,15 +362,16 @@ namespace MWGui } /* Iterate through the vector. */ - for(std::vector::iterator it=mNames.begin(); it < mNames.end();++it) { + for(std::string& name : mNames) + { bool string_different=false; /* Is the string shorter than the input string? If yes skip it. */ - if( (*it).length() < tmp.length() ) + if(name.length() < tmp.length()) continue; /* Is the beginning of the string different from the input string? If yes skip it. */ - for( std::string::iterator iter=tmp.begin(), iter2=(*it).begin(); iter < tmp.end();++iter, ++iter2) { + for( std::string::iterator iter=tmp.begin(), iter2=name.begin(); iter < tmp.end();++iter, ++iter2) { if( Misc::StringUtils::toLower(*iter) != Misc::StringUtils::toLower(*iter2) ) { string_different=true; break; @@ -371,7 +382,7 @@ namespace MWGui continue; /* The beginning of the string matches the input string, save it for the next test. */ - matches.push_back(*it); + matches.push_back(name); } /* There are no matches. Return the unchanged input. */ @@ -399,11 +410,14 @@ namespace MWGui /* Check if all matching strings match further than input. If yes complete to this match. */ int i = tmp.length(); - for(std::string::iterator iter=matches.front().begin()+tmp.length(); iter < matches.front().end(); ++iter, ++i) { - for(std::vector::iterator it=matches.begin(); it < matches.end();++it) { - if( Misc::StringUtils::toLower((*it)[i]) != Misc::StringUtils::toLower(*iter) ) { + for(std::string::iterator iter=matches.front().begin()+tmp.length(); iter < matches.front().end(); ++iter, ++i) + { + for(std::string& match : matches) + { + if(Misc::StringUtils::toLower(match[i]) != Misc::StringUtils::toLower(*iter)) + { /* Append the longest match to the end of the output string*/ - output.append(matches.front().substr( 0, i)); + output.append(matches.front().substr(0, i)); return output; } } diff --git a/apps/openmw/mwgui/container.cpp b/apps/openmw/mwgui/container.cpp index 51c0603ca..f89cd4ff3 100644 --- a/apps/openmw/mwgui/container.cpp +++ b/apps/openmw/mwgui/container.cpp @@ -6,7 +6,6 @@ #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" -#include "../mwbase/dialoguemanager.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwworld/class.hpp" @@ -18,7 +17,6 @@ #include "inventorywindow.hpp" #include "itemview.hpp" -#include "itemwidget.hpp" #include "inventoryitemmodel.hpp" #include "containeritemmodel.hpp" #include "sortfilteritemmodel.hpp" diff --git a/apps/openmw/mwgui/containeritemmodel.cpp b/apps/openmw/mwgui/containeritemmodel.cpp index 24e7f8d00..5ba5bb0eb 100644 --- a/apps/openmw/mwgui/containeritemmodel.cpp +++ b/apps/openmw/mwgui/containeritemmodel.cpp @@ -13,8 +13,6 @@ #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" -#include "../mwmechanics/actorutil.hpp" - namespace { @@ -84,9 +82,9 @@ size_t ContainerItemModel::getItemCount() ItemModel::ModelIndex ContainerItemModel::getIndex (ItemStack item) { size_t i = 0; - for (std::vector::iterator it = mItems.begin(); it != mItems.end(); ++it) + for (ItemStack& itemStack : mItems) { - if (*it == item) + if (itemStack == item) return i; ++i; } @@ -105,29 +103,29 @@ void ContainerItemModel::removeItem (const ItemStack& item, size_t count) { int toRemove = count; - for (std::vector::iterator source = mItemSources.begin(); source != mItemSources.end(); ++source) + for (MWWorld::Ptr& source : mItemSources) { - MWWorld::ContainerStore& store = source->getClass().getContainerStore(*source); + MWWorld::ContainerStore& store = source.getClass().getContainerStore(source); for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) { if (stacks(*it, item.mBase)) { - toRemove -= store.remove(*it, toRemove, *source); + toRemove -= store.remove(*it, toRemove, source); if (toRemove <= 0) return; } } } - for (std::vector::iterator source = mWorldItems.begin(); source != mWorldItems.end(); ++source) + for (MWWorld::Ptr& source : mWorldItems) { - if (stacks(*source, item.mBase)) + if (stacks(source, item.mBase)) { - int refCount = source->getRefData().getCount(); + int refCount = source.getRefData().getCount(); if (refCount - toRemove <= 0) - MWBase::Environment::get().getWorld()->deleteObject(*source); + MWBase::Environment::get().getWorld()->deleteObject(source); else - source->getRefData().setCount(std::max(0, refCount - toRemove)); + source.getRefData().setCount(std::max(0, refCount - toRemove)); toRemove -= refCount; if (toRemove <= 0) return; @@ -140,27 +138,28 @@ void ContainerItemModel::removeItem (const ItemStack& item, size_t count) void ContainerItemModel::update() { mItems.clear(); - for (std::vector::iterator source = mItemSources.begin(); source != mItemSources.end(); ++source) + for (MWWorld::Ptr& source : mItemSources) { - MWWorld::ContainerStore& store = source->getClass().getContainerStore(*source); + MWWorld::ContainerStore& store = source.getClass().getContainerStore(source); for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) { if (!(*it).getClass().showsInInventory(*it)) continue; - std::vector::iterator itemStack = mItems.begin(); - for (; itemStack != mItems.end(); ++itemStack) + bool found = false; + for (ItemStack& itemStack : mItems) { - if (stacks(*it, itemStack->mBase)) + if (stacks(*it, itemStack.mBase)) { // we already have an item stack of this kind, add to it - itemStack->mCount += it->getRefData().getCount(); + itemStack.mCount += it->getRefData().getCount(); + found = true; break; } } - if (itemStack == mItems.end()) + if (!found) { // no stack yet, create one ItemStack newItem (*it, this, it->getRefData().getCount()); @@ -168,23 +167,24 @@ void ContainerItemModel::update() } } } - for (std::vector::iterator source = mWorldItems.begin(); source != mWorldItems.end(); ++source) + for (MWWorld::Ptr& source : mWorldItems) { - std::vector::iterator itemStack = mItems.begin(); - for (; itemStack != mItems.end(); ++itemStack) + bool found = false; + for (ItemStack& itemStack : mItems) { - if (stacks(*source, itemStack->mBase)) + if (stacks(source, itemStack.mBase)) { // we already have an item stack of this kind, add to it - itemStack->mCount += source->getRefData().getCount(); + itemStack.mCount += source.getRefData().getCount(); + found = true; break; } } - if (itemStack == mItems.end()) + if (!found) { // no stack yet, create one - ItemStack newItem (*source, this, source->getRefData().getCount()); + ItemStack newItem (source, this, source.getRefData().getCount()); mItems.push_back(newItem); } } diff --git a/apps/openmw/mwgui/dialogue.cpp b/apps/openmw/mwgui/dialogue.cpp index dfb2b15b9..6b400c172 100644 --- a/apps/openmw/mwgui/dialogue.cpp +++ b/apps/openmw/mwgui/dialogue.cpp @@ -184,16 +184,16 @@ namespace MWGui BookTypesetter::Style* style = typesetter->createStyle("", textColours.normal, false); size_t formatted = 0; // points to the first character that is not laid out yet - for (std::map::iterator it = hyperLinks.begin(); it != hyperLinks.end(); ++it) + for (auto& hyperLink : hyperLinks) { - intptr_t topicId = it->second; + intptr_t topicId = hyperLink.second; BookTypesetter::Style* hotStyle = typesetter->createHotStyle (style, textColours.link, textColours.linkOver, textColours.linkPressed, topicId); - if (formatted < it->first.first) - typesetter->write(style, formatted, it->first.first); - typesetter->write(hotStyle, it->first.first, it->first.second); - formatted = it->first.second; + if (formatted < hyperLink.first.first) + typesetter->write(style, formatted, hyperLink.first.first); + typesetter->write(hotStyle, hyperLink.first.first, hyperLink.first.second); + formatted = hyperLink.first.second; } if (formatted < text.size()) typesetter->write(style, formatted, text.size()); @@ -204,9 +204,8 @@ namespace MWGui keywordSearch->highlightKeywords(text.begin(), text.end(), matches); std::string::const_iterator i = text.begin (); - for (std::vector::iterator it = matches.begin(); it != matches.end(); ++it) + for (KeywordSearchT::Match& match : matches) { - KeywordSearchT::Match match = *it; if (i != match.mBeg) addTopicLink (typesetter, 0, i - text.begin (), match.mBeg - text.begin ()); @@ -419,13 +418,13 @@ namespace MWGui bool sameActor = (mPtr == actor); if (!sameActor) { - for (std::vector::iterator it = mHistoryContents.begin(); it != mHistoryContents.end(); ++it) - delete (*it); + for (DialogueText* text : mHistoryContents) + delete text; mHistoryContents.clear(); mKeywords.clear(); mTopicsList->clear(); - for (std::vector::iterator it = mLinks.begin(); it != mLinks.end(); ++it) - mDeleteLater.push_back(*it); // Links are not deleted right away to prevent issues with event handlers + for (Link* link : mLinks) + mDeleteLater.push_back(link); // Links are not deleted right away to prevent issues with event handlers mLinks.clear(); } @@ -489,8 +488,8 @@ namespace MWGui void DialogueWindow::updateTopicsPane() { mTopicsList->clear(); - for (std::map::iterator it = mTopicLinks.begin(); it != mTopicLinks.end(); ++it) - mDeleteLater.push_back(it->second); + for (auto& linkPair : mTopicLinks) + mDeleteLater.push_back(linkPair.second); mTopicLinks.clear(); mKeywordSearch.clear(); @@ -533,15 +532,15 @@ namespace MWGui mTopicsList->addSeparator(); - for(std::list::iterator it = mKeywords.begin(); it != mKeywords.end(); ++it) + for(std::string& keyword : mKeywords) { - mTopicsList->addItem(*it); + mTopicsList->addItem(keyword); - Topic* t = new Topic(*it); + Topic* t = new Topic(keyword); t->eventTopicActivated += MyGUI::newDelegate(this, &DialogueWindow::onTopicActivated); - mTopicLinks[Misc::StringUtils::lowerCase(*it)] = t; + mTopicLinks[Misc::StringUtils::lowerCase(keyword)] = t; - mKeywordSearch.seed(Misc::StringUtils::lowerCase(*it), intptr_t(t)); + mKeywordSearch.seed(Misc::StringUtils::lowerCase(keyword), intptr_t(t)); } mTopicsList->adjustSize(); @@ -563,9 +562,8 @@ namespace MWGui BookTypesetter::Ptr typesetter = BookTypesetter::create (mHistory->getWidth(), std::numeric_limits::max()); - for (std::vector::iterator it = mHistoryContents.begin(); it != mHistoryContents.end(); ++it) - (*it)->write(typesetter, &mKeywordSearch, mTopicLinks); - + for (DialogueText* text : mHistoryContents) + text->write(typesetter, &mKeywordSearch, mTopicLinks); BookTypesetter::Style* body = typesetter->createStyle("", MyGUI::Colour::White, false); @@ -573,9 +571,9 @@ namespace MWGui // choices const TextColours& textColours = MWBase::Environment::get().getWindowManager()->getTextColours(); mChoices = MWBase::Environment::get().getDialogueManager()->getChoices(); - for (std::vector >::const_iterator it = mChoices.begin(); it != mChoices.end(); ++it) + for (std::pair& choice : mChoices) { - Choice* link = new Choice(it->second); + Choice* link = new Choice(choice.second); link->eventChoiceActivated += MyGUI::newDelegate(this, &DialogueWindow::onChoiceActivated); mLinks.push_back(link); @@ -583,7 +581,7 @@ namespace MWGui BookTypesetter::Style* questionStyle = typesetter->createHotStyle(body, textColours.answer, textColours.answerOver, textColours.answerPressed, TypesetBook::InteractiveId(link)); - typesetter->write(questionStyle, to_utf8_span(it->first.c_str())); + typesetter->write(questionStyle, to_utf8_span(choice.first.c_str())); } mGoodbye = MWBase::Environment::get().getDialogueManager()->isGoodbye(); diff --git a/apps/openmw/mwgui/enchantingdialog.cpp b/apps/openmw/mwgui/enchantingdialog.cpp index 98980e339..fbdc1bf80 100644 --- a/apps/openmw/mwgui/enchantingdialog.cpp +++ b/apps/openmw/mwgui/enchantingdialog.cpp @@ -9,7 +9,10 @@ #include #include +#include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" +#include "../mwbase/world.hpp" + #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" #include "../mwworld/esmstore.hpp" @@ -345,8 +348,7 @@ namespace MWGui if (MWBase::Environment::get().getMechanicsManager()->isItemStolenFrom(item.getCellRef().getRefId(), mPtr)) { std::string msg = MWBase::Environment::get().getWorld()->getStore().get().find("sNotifyMessage49")->mValue.getString(); - if (msg.find("%s") != std::string::npos) - msg.replace(msg.find("%s"), 2, item.getClass().getName(item)); + Misc::StringUtils::replace(msg, "%s", item.getClass().getName(item).c_str(), 2); MWBase::Environment::get().getWindowManager()->messageBox(msg); MWBase::Environment::get().getMechanicsManager()->confiscateStolenItemToOwner(player, item, mPtr, 1); diff --git a/apps/openmw/mwgui/formatting.cpp b/apps/openmw/mwgui/formatting.cpp index 78359608e..fca05b05c 100644 --- a/apps/openmw/mwgui/formatting.cpp +++ b/apps/openmw/mwgui/formatting.cpp @@ -4,15 +4,11 @@ #include #include #include -#include // correctBookartPath #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" -#include -#include - #include #include #include @@ -30,7 +26,7 @@ namespace MWGui MWScript::InterpreterContext interpreterContext(nullptr, MWWorld::Ptr()); // empty arguments, because there is no locals or actor mText = Interpreter::fixDefinesBook(mText, interpreterContext); - boost::algorithm::replace_all(mText, "\r", ""); + Misc::StringUtils::replaceAll(mText, "\r", ""); // vanilla game does not show any text after the last EOL tag. const std::string lowerText = Misc::StringUtils::lowerCase(mText); diff --git a/apps/openmw/mwgui/hud.cpp b/apps/openmw/mwgui/hud.cpp index de85f296d..de6629515 100644 --- a/apps/openmw/mwgui/hud.cpp +++ b/apps/openmw/mwgui/hud.cpp @@ -25,7 +25,6 @@ #include "itemmodel.hpp" #include "draganddrop.hpp" -#include "itemmodel.hpp" #include "itemwidget.hpp" namespace MWGui @@ -262,7 +261,7 @@ namespace MWGui if (mode == GM_Console) MWBase::Environment::get().getWindowManager()->setConsoleSelectedObject(object); - else if ((mode == GM_Container) || (mode == GM_Inventory)) + else //if ((mode == GM_Container) || (mode == GM_Inventory)) { // pick up object if (!object.isEmpty()) diff --git a/apps/openmw/mwgui/inventoryitemmodel.cpp b/apps/openmw/mwgui/inventoryitemmodel.cpp index c9f55d352..d74819a89 100644 --- a/apps/openmw/mwgui/inventoryitemmodel.cpp +++ b/apps/openmw/mwgui/inventoryitemmodel.cpp @@ -37,9 +37,9 @@ size_t InventoryItemModel::getItemCount() ItemModel::ModelIndex InventoryItemModel::getIndex (ItemStack item) { size_t i = 0; - for (std::vector::iterator it = mItems.begin(); it != mItems.end(); ++it) + for (ItemStack& itemStack : mItems) { - if (*it == item) + if (itemStack == item) return i; ++i; } diff --git a/apps/openmw/mwgui/itemchargeview.cpp b/apps/openmw/mwgui/itemchargeview.cpp index 10c36c73f..44fa94f3a 100644 --- a/apps/openmw/mwgui/itemchargeview.cpp +++ b/apps/openmw/mwgui/itemchargeview.cpp @@ -130,13 +130,13 @@ namespace MWGui { int currentY = 0; - for (Lines::const_iterator iter = mLines.begin(); iter != mLines.end(); ++iter) + for (Line& line : mLines) { - iter->mText->setCoord(8, currentY, mScrollView->getWidth()-8, 18); + line.mText->setCoord(8, currentY, mScrollView->getWidth()-8, 18); currentY += 19; - iter->mIcon->setCoord(16, currentY, 32, 32); - iter->mCharge->setCoord(72, currentY+2, std::max(199, mScrollView->getWidth()-72-38), 20); + line.mIcon->setCoord(16, currentY, 32, 32); + line.mCharge->setCoord(72, currentY+2, std::max(199, mScrollView->getWidth()-72-38), 20); currentY += 32 + 4; } diff --git a/apps/openmw/mwgui/itemmodel.cpp b/apps/openmw/mwgui/itemmodel.cpp index c7c10e8e4..08c5bff35 100644 --- a/apps/openmw/mwgui/itemmodel.cpp +++ b/apps/openmw/mwgui/itemmodel.cpp @@ -1,13 +1,9 @@ #include "itemmodel.hpp" -#include - #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" -#include "../mwworld/store.hpp" #include "../mwworld/esmstore.hpp" -#include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" diff --git a/apps/openmw/mwgui/itemwidget.cpp b/apps/openmw/mwgui/itemwidget.cpp index ba3039a16..5ab69e722 100644 --- a/apps/openmw/mwgui/itemwidget.cpp +++ b/apps/openmw/mwgui/itemwidget.cpp @@ -156,12 +156,19 @@ namespace MWGui void SpellWidget::setSpellIcon(const std::string& icon) { - if (mFrame) + if (mFrame && !mCurrentFrame.empty()) + { + mCurrentFrame.clear(); mFrame->setImageTexture(""); - if (mItemShadow) - mItemShadow->setImageTexture(icon); - if (mItem) - mItem->setImageTexture(icon); + } + if (mCurrentIcon != icon) + { + mCurrentIcon = icon; + if (mItemShadow) + mItemShadow->setImageTexture(icon); + if (mItem) + mItem->setImageTexture(icon); + } } } diff --git a/apps/openmw/mwgui/jailscreen.cpp b/apps/openmw/mwgui/jailscreen.cpp index 1761e1346..e04ceac7a 100644 --- a/apps/openmw/mwgui/jailscreen.cpp +++ b/apps/openmw/mwgui/jailscreen.cpp @@ -78,8 +78,7 @@ namespace MWGui MWWorld::Ptr player = MWMechanics::getPlayer(); - for (int i=0; irest(true); + MWBase::Environment::get().getMechanicsManager()->rest(mDays * 24, true); MWBase::Environment::get().getWorld()->advanceTime(mDays * 24); std::set skills; @@ -103,24 +102,18 @@ namespace MWGui else message = gmst.find("sNotifyMessage43")->mValue.getString(); - std::stringstream dayStr; - dayStr << mDays; - if (message.find("%d") != std::string::npos) - message.replace(message.find("%d"), 2, dayStr.str()); + Misc::StringUtils::replace(message, "%d", std::to_string(mDays).c_str(), 2); - for (std::set::iterator it = skills.begin(); it != skills.end(); ++it) + for (const int& skill : skills) { - std::string skillName = gmst.find(ESM::Skill::sSkillNameIds[*it])->mValue.getString(); - std::stringstream skillValue; - skillValue << player.getClass().getNpcStats(player).getSkill(*it).getBase(); + std::string skillName = gmst.find(ESM::Skill::sSkillNameIds[skill])->mValue.getString(); + int skillValue = player.getClass().getNpcStats(player).getSkill(skill).getBase(); std::string skillMsg = gmst.find("sNotifyMessage44")->mValue.getString(); - if (*it == ESM::Skill::Sneak || *it == ESM::Skill::Security) + if (skill == ESM::Skill::Sneak || skill == ESM::Skill::Security) skillMsg = gmst.find("sNotifyMessage39")->mValue.getString(); - if (skillMsg.find("%s") != std::string::npos) - skillMsg.replace(skillMsg.find("%s"), 2, skillName); - if (skillMsg.find("%d") != std::string::npos) - skillMsg.replace(skillMsg.find("%d"), 2, skillValue.str()); + Misc::StringUtils::replace(skillMsg, "%s", skillName.c_str(), 2); + Misc::StringUtils::replace(skillMsg, "%d", std::to_string(skillValue).c_str(), 2); message += "\n" + skillMsg; } diff --git a/apps/openmw/mwgui/journalbooks.cpp b/apps/openmw/mwgui/journalbooks.cpp index 4302740f6..065a503e6 100644 --- a/apps/openmw/mwgui/journalbooks.cpp +++ b/apps/openmw/mwgui/journalbooks.cpp @@ -1,7 +1,5 @@ #include "journalbooks.hpp" -#include - #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" diff --git a/apps/openmw/mwgui/journalwindow.cpp b/apps/openmw/mwgui/journalwindow.cpp index a2f6ea142..095d74e8a 100644 --- a/apps/openmw/mwgui/journalwindow.cpp +++ b/apps/openmw/mwgui/journalwindow.cpp @@ -1,6 +1,5 @@ #include "journalwindow.hpp" -#include #include #include #include @@ -554,7 +553,7 @@ namespace mQuestMode = true; setVisible (LeftTopicIndex, false); - setVisible (CenterTopicIndex, true); + setVisible (CenterTopicIndex, false); setVisible (RightTopicIndex, false); setVisible (TopicsList, false); setVisible (QuestsList, true); @@ -631,7 +630,7 @@ namespace if (page+2 < book->pageCount()) { - MWBase::Environment::get().getWindowManager()->playSound("book page", true); + MWBase::Environment::get().getWindowManager()->playSound("book page"); page += 2; updateShowingPages (); @@ -649,7 +648,7 @@ namespace if(page >= 2) { - MWBase::Environment::get().getWindowManager()->playSound("book page", true); + MWBase::Environment::get().getWindowManager()->playSound("book page"); page -= 2; updateShowingPages (); diff --git a/apps/openmw/mwgui/layout.cpp b/apps/openmw/mwgui/layout.cpp index 3035adebb..ae1c09659 100644 --- a/apps/openmw/mwgui/layout.cpp +++ b/apps/openmw/mwgui/layout.cpp @@ -21,11 +21,11 @@ namespace MWGui mListWindowRoot = MyGUI::LayoutManager::getInstance().loadLayout(mLayoutName, mPrefix, _parent); const std::string main_name = mPrefix + MAIN_WINDOW; - for (MyGUI::VectorWidgetPtr::iterator iter=mListWindowRoot.begin(); iter!=mListWindowRoot.end(); ++iter) + for (MyGUI::Widget* widget : mListWindowRoot) { - if ((*iter)->getName() == main_name) + if (widget->getName() == main_name) { - mMainWidget = (*iter); + mMainWidget = widget; break; } } @@ -66,10 +66,9 @@ namespace MWGui MyGUI::Widget* Layout::getWidget(const std::string &_name) { - for (MyGUI::VectorWidgetPtr::iterator iter=mListWindowRoot.begin(); - iter!=mListWindowRoot.end(); ++iter) + for (MyGUI::Widget* widget : mListWindowRoot) { - MyGUI::Widget* find = (*iter)->findWidget(mPrefix + _name); + MyGUI::Widget* find = widget->findWidget(mPrefix + _name); if (nullptr != find) { return find; diff --git a/apps/openmw/mwgui/levelupdialog.cpp b/apps/openmw/mwgui/levelupdialog.cpp index 286405f79..415c8e31c 100644 --- a/apps/openmw/mwgui/levelupdialog.cpp +++ b/apps/openmw/mwgui/levelupdialog.cpp @@ -12,7 +12,6 @@ #include "../mwbase/soundmanager.hpp" #include "../mwworld/class.hpp" -#include "../mwworld/esmstore.hpp" #include "../mwworld/cellstore.hpp" #include "../mwmechanics/creaturestats.hpp" diff --git a/apps/openmw/mwgui/loadingscreen.hpp b/apps/openmw/mwgui/loadingscreen.hpp index e74a60206..c054f3bbd 100644 --- a/apps/openmw/mwgui/loadingscreen.hpp +++ b/apps/openmw/mwgui/loadingscreen.hpp @@ -1,6 +1,8 @@ #ifndef MWGUI_LOADINGSCREEN_H #define MWGUI_LOADINGSCREEN_H +#include + #include #include diff --git a/apps/openmw/mwgui/mainmenu.cpp b/apps/openmw/mwgui/mainmenu.cpp index 1b9e024ba..2c2bd84df 100644 --- a/apps/openmw/mwgui/mainmenu.cpp +++ b/apps/openmw/mwgui/mainmenu.cpp @@ -253,45 +253,44 @@ namespace MWGui // Create new buttons if needed std::vector allButtons { "return", "newgame", "savegame", "loadgame", "options", "credits", "exitgame"}; - for (std::vector::iterator it = allButtons.begin(); it != allButtons.end(); ++it) + for (std::string& buttonId : allButtons) { - if (mButtons.find(*it) == mButtons.end()) + if (mButtons.find(buttonId) == mButtons.end()) { Gui::ImageButton* button = mButtonBox->createWidget ("ImageBox", MyGUI::IntCoord(0, curH, 0, 0), MyGUI::Align::Default); - button->setProperty("ImageHighlighted", "textures\\menu_" + *it + "_over.dds"); - button->setProperty("ImageNormal", "textures\\menu_" + *it + ".dds"); - button->setProperty("ImagePushed", "textures\\menu_" + *it + "_pressed.dds"); + button->setProperty("ImageHighlighted", "textures\\menu_" + buttonId + "_over.dds"); + button->setProperty("ImageNormal", "textures\\menu_" + buttonId + ".dds"); + button->setProperty("ImagePushed", "textures\\menu_" + buttonId + "_pressed.dds"); button->eventMouseButtonClick += MyGUI::newDelegate(this, &MainMenu::onButtonClicked); - button->setUserData(std::string(*it)); - mButtons[*it] = button; + button->setUserData(std::string(buttonId)); + mButtons[buttonId] = button; } } // Start by hiding all buttons int maxwidth = 0; - for (std::map::iterator it = mButtons.begin(); it != mButtons.end(); ++it) + for (auto& buttonPair : mButtons) { - it->second->setVisible(false); - MyGUI::IntSize requested = it->second->getRequestedSize(); + buttonPair.second->setVisible(false); + MyGUI::IntSize requested = buttonPair.second->getRequestedSize(); if (requested.width > maxwidth) maxwidth = requested.width; } // Now show and position the ones we want - for (std::vector::iterator it = buttons.begin(); it != buttons.end(); ++it) + for (std::string& buttonId : buttons) { - assert(mButtons.find(*it) != mButtons.end()); - Gui::ImageButton* button = mButtons[*it]; + assert(mButtons.find(buttonId) != mButtons.end()); + Gui::ImageButton* button = mButtons[buttonId]; button->setVisible(true); 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 b9b59965d..5f4c9971c 100644 --- a/apps/openmw/mwgui/mapwindow.cpp +++ b/apps/openmw/mwgui/mapwindow.cpp @@ -213,8 +213,7 @@ namespace MWGui map->setNeedMouseFocus(false); fog->setNeedMouseFocus(false); - mMapWidgets.push_back(map); - mFogWidgets.push_back(fog); + mMaps.emplace_back(map, fog); } } } @@ -234,36 +233,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(); } @@ -312,8 +312,8 @@ namespace MWGui void LocalMapBase::updateCustomMarkers() { - for (std::vector::iterator it = mCustomMarkerWidgets.begin(); it != mCustomMarkerWidgets.end(); ++it) - MyGUI::Gui::getInstance().destroyWidget(*it); + for (MyGUI::Widget* widget : mCustomMarkerWidgets) + MyGUI::Gui::getInstance().destroyWidget(widget); mCustomMarkerWidgets.clear(); for (int dX = -mCellDistance; dX <= mCellDistance; ++dX) @@ -369,7 +369,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. @@ -487,9 +488,9 @@ namespace MWGui } int counter = 0; - for (std::vector::iterator it = markers.begin(); it != markers.end(); ++it) + for (const MWWorld::Ptr& ptr : markers) { - const ESM::Position& worldPos = it->getRefData().getPosition(); + const ESM::Position& worldPos = ptr.getRefData().getPosition(); MarkerUserData markerPos (mLocalMapRender); MyGUI::IntPoint widgetPos = getMarkerPosition(worldPos.pos[0], worldPos.pos[1], markerPos); MyGUI::IntCoord widgetCoord(widgetPos.left - 4, @@ -526,8 +527,8 @@ namespace MWGui void LocalMapBase::updateDoorMarkers() { // clear all previous door markers - for (std::vector::iterator it = mDoorMarkerWidgets.begin(); it != mDoorMarkerWidgets.end(); ++it) - MyGUI::Gui::getInstance().destroyWidget(*it); + for (MyGUI::Widget* widget : mDoorMarkerWidgets) + MyGUI::Gui::getInstance().destroyWidget(widget); mDoorMarkerWidgets.clear(); MWBase::World* world = MWBase::Environment::get().getWorld(); @@ -553,10 +554,8 @@ namespace MWGui // Create a widget for each marker int counter = 0; - for (std::vector::iterator it = doors.begin(); it != doors.end(); ++it) + for (MWBase::World::DoorMarker& marker : doors) { - MWBase::World::DoorMarker marker = *it; - std::vector destNotes; CustomMarkerCollection::RangeType markers = mCustomMarkers.getMarkers(marker.dest); for (CustomMarkerCollection::ContainerType::const_iterator iter = markers.first; iter != markers.second; ++iter) @@ -589,8 +588,8 @@ namespace MWGui void LocalMapBase::updateMagicMarkers() { // clear all previous markers - for (std::vector::iterator it = mMagicMarkerWidgets.begin(); it != mMagicMarkerWidgets.end(); ++it) - MyGUI::Gui::getInstance().destroyWidget(*it); + for (MyGUI::Widget* widget : mMagicMarkerWidgets) + MyGUI::Gui::getInstance().destroyWidget(widget); mMagicMarkerWidgets.clear(); addDetectionMarkers(MWBase::World::Detect_Creature); @@ -839,22 +838,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 (std::vector::iterator it = mQueuedToExplore.begin(); it != mQueuedToExplore.end(); ++it) - { - mGlobalMapRender->exploreCell(it->first, it->second, mLocalMapRender->getMapTexture(it->first, it->second)); - } - - mQueuedToExplore.clear(); - NoDrop::onFrame(dt); } @@ -888,11 +878,11 @@ namespace MWGui { LocalMapBase::updateCustomMarkers(); - for (std::map, MyGUI::Widget*>::iterator widgetIt = mGlobalMapMarkers.begin(); widgetIt != mGlobalMapMarkers.end(); ++widgetIt) + for (auto& widgetPair : mGlobalMapMarkers) { - int x = widgetIt->first.first; - int y = widgetIt->first.second; - MyGUI::Widget* markerWidget = widgetIt->second; + int x = widgetPair.first.first; + int y = widgetPair.first.second; + MyGUI::Widget* markerWidget = widgetPair.second; setGlobalMapMarkerTooltip(markerWidget, x, y); } } @@ -1017,8 +1007,8 @@ namespace MWGui mGlobalMapRender->clear(); mChanged = true; - for (std::map, MyGUI::Widget*>::iterator it = mGlobalMapMarkers.begin(); it != mGlobalMapMarkers.end(); ++it) - MyGUI::Gui::getInstance().destroyWidget(it->second); + for (auto& widgetPair : mGlobalMapMarkers) + MyGUI::Gui::getInstance().destroyWidget(widgetPair.second); mGlobalMapMarkers.clear(); } @@ -1043,11 +1033,11 @@ namespace MWGui mGlobalMapRender->read(map); - for (std::set::iterator it = map.mMarkers.begin(); it != map.mMarkers.end(); ++it) + for (const ESM::GlobalMap::CellId& cellId : map.mMarkers) { - const ESM::Cell* cell = MWBase::Environment::get().getWorld()->getStore().get().search(it->first, it->second); + const ESM::Cell* cell = MWBase::Environment::get().getWorld()->getStore().get().search(cellId.first, cellId.second); if (cell && !cell->mName.empty()) - addVisitedLocation(cell->mName, it->first, it->second); + addVisitedLocation(cell->mName, cellId.first, cellId.second); } } } @@ -1057,8 +1047,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 (std::vector::iterator it = mMapWidgets.begin(); it != mMapWidgets.end(); ++it) - (*it)->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 ed6e4874f..4104a7824 100644 --- a/apps/openmw/mwgui/mapwindow.hpp +++ b/apps/openmw/mwgui/mapwindow.hpp @@ -2,6 +2,7 @@ #define MWGUI_MAPWINDOW_H #include +#include #include "windowpinnablebase.hpp" @@ -125,12 +126,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; @@ -260,10 +266,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/messagebox.cpp b/apps/openmw/mwgui/messagebox.cpp index 6c2d85dd9..d64ec9c37 100644 --- a/apps/openmw/mwgui/messagebox.cpp +++ b/apps/openmw/mwgui/messagebox.cpp @@ -28,10 +28,9 @@ namespace MWGui MessageBoxManager::~MessageBoxManager () { - std::vector::iterator it(mMessageBoxes.begin()); - for (; it != mMessageBoxes.end(); ++it) + for (MessageBox* messageBox : mMessageBoxes) { - delete *it; + delete messageBox; } } @@ -50,12 +49,11 @@ namespace MWGui mInterMessageBoxe = nullptr; } - std::vector::iterator it(mMessageBoxes.begin()); - for (; it != mMessageBoxes.end(); ++it) + for (MessageBox* messageBox : mMessageBoxes) { - if (*it == mStaticMessageBox) + if (messageBox == mStaticMessageBox) mStaticMessageBox = nullptr; - delete *it; + delete messageBox; } mMessageBoxes.clear(); @@ -81,9 +79,9 @@ namespace MWGui it = mMessageBoxes.begin(); while(it != mMessageBoxes.end()) { - (*it)->update(static_cast(height)); - height += (*it)->getHeight(); - ++it; + (*it)->update(static_cast(height)); + height += (*it)->getHeight(); + ++it; } if(mInterMessageBoxe != nullptr && mInterMessageBoxe->mMarkedToDelete) { @@ -114,10 +112,10 @@ namespace MWGui } int height = 0; - for(std::vector::iterator it = mMessageBoxes.begin(); it != mMessageBoxes.end(); ++it) + for (MessageBox* messageBox : mMessageBoxes) { - (*it)->update(height); - height += (*it)->getHeight(); + messageBox->update(height); + height += messageBox->getHeight(); } } @@ -240,14 +238,14 @@ namespace MWGui int buttonHeight = 0; MyGUI::IntCoord dummyCoord(0, 0, 0, 0); - for(std::vector::const_iterator it = buttons.begin(); it != buttons.end(); ++it) + for(const std::string& buttonId : buttons) { MyGUI::Button* button = mButtonsWidget->createWidget( MyGUI::WidgetStyle::Child, std::string("MW_Button"), dummyCoord, MyGUI::Align::Default); - button->setCaptionWithReplacing(*it); + button->setCaptionWithReplacing(buttonId); button->eventMouseButtonClick += MyGUI::newDelegate(this, &InteractiveMessageBox::mousePressed); @@ -300,16 +298,16 @@ namespace MWGui MyGUI::IntSize buttonSize(0, buttonHeight); int left = (mainWidgetSize.width - buttonsWidth)/2; - for(std::vector::const_iterator button = mButtons.begin(); button != mButtons.end(); ++button) + for(MyGUI::Button* button : mButtons) { buttonCord.left = left; buttonCord.top = messageWidgetCoord.top + textSize.height + textButtonPadding; - buttonSize.width = (*button)->getTextSize().width + 2*buttonLabelLeftPadding; - buttonSize.height = (*button)->getTextSize().height + 2*buttonLabelTopPadding; + buttonSize.width = button->getTextSize().width + 2*buttonLabelLeftPadding; + buttonSize.height = button->getTextSize().height + 2*buttonLabelTopPadding; - (*button)->setCoord(buttonCord); - (*button)->setSize(buttonSize); + button->setCoord(buttonCord); + button->setSize(buttonSize); left += buttonSize.width + buttonLeftPadding; } @@ -329,16 +327,16 @@ namespace MWGui int top = textPadding + textSize.height + textButtonPadding; - for(std::vector::const_iterator button = mButtons.begin(); button != mButtons.end(); ++button) + for(MyGUI::Button* button : mButtons) { - buttonSize.width = (*button)->getTextSize().width + buttonLabelLeftPadding*2; - buttonSize.height = (*button)->getTextSize().height + buttonLabelTopPadding*2; + buttonSize.width = button->getTextSize().width + buttonLabelLeftPadding*2; + buttonSize.height = button->getTextSize().height + buttonLabelTopPadding*2; buttonCord.top = top; buttonCord.left = (mainWidgetSize.width - buttonSize.width)/2; - (*button)->setCoord(buttonCord); - (*button)->setSize(buttonSize); + button->setCoord(buttonCord); + button->setSize(buttonSize); top += buttonSize.height + buttonTopPadding; } @@ -368,13 +366,13 @@ namespace MWGui MyGUI::Widget* InteractiveMessageBox::getDefaultKeyFocus() { std::vector keywords { "sOk", "sYes" }; - for(std::vector::const_iterator button = mButtons.begin(); button != mButtons.end(); ++button) + for(MyGUI::Button* button : mButtons) { for (const std::string& keyword : keywords) { - if(Misc::StringUtils::ciEqual(MyGUI::LanguageManager::getInstance().replaceTags("#{" + keyword + "}"), (*button)->getCaption())) + if(Misc::StringUtils::ciEqual(MyGUI::LanguageManager::getInstance().replaceTags("#{" + keyword + "}"), button->getCaption())) { - return *button; + return button; } } } @@ -390,10 +388,9 @@ namespace MWGui { mMarkedToDelete = true; int index = 0; - std::vector::const_iterator button; - for(button = mButtons.begin(); button != mButtons.end(); ++button) + for(const MyGUI::Button* button : mButtons) { - if(*button == pressed) + if(button == pressed) { mButtonPressed = index; mMessageBoxManager.onButtonPressed(mButtonPressed); diff --git a/apps/openmw/mwgui/quickkeysmenu.cpp b/apps/openmw/mwgui/quickkeysmenu.cpp index 23ad87fd6..6ac1f2b63 100644 --- a/apps/openmw/mwgui/quickkeysmenu.cpp +++ b/apps/openmw/mwgui/quickkeysmenu.cpp @@ -540,32 +540,32 @@ namespace MWGui MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player); int i=0; - for (std::vector::const_iterator it = keys.mKeys.begin(); it != keys.mKeys.end(); ++it) + for (ESM::QuickKeys::QuickKey& quickKey : keys.mKeys) { if (i >= 10) return; mSelected = &mKey[i]; - switch (it->mType) + switch (quickKey.mType) { case Type_Magic: - if (MWBase::Environment::get().getWorld()->getStore().get().search(it->mId)) - onAssignMagic(it->mId); + if (MWBase::Environment::get().getWorld()->getStore().get().search(quickKey.mId)) + onAssignMagic(quickKey.mId); break; case Type_Item: case Type_MagicItem: { // Find the item by id - MWWorld::Ptr item = store.findReplacement(it->mId); + MWWorld::Ptr item = store.findReplacement(quickKey.mId); if (item.isEmpty()) unassign(mSelected); else { - if (it->mType == Type_Item) + if (quickKey.mType == Type_Item) onAssignItem(item); - else // if (it->mType == Type_MagicItem) + else // if (quickKey.mType == Type_MagicItem) onAssignMagicItem(item); } diff --git a/apps/openmw/mwgui/race.cpp b/apps/openmw/mwgui/race.cpp index be0dff660..cf69ecca3 100644 --- a/apps/openmw/mwgui/race.cpp +++ b/apps/openmw/mwgui/race.cpp @@ -290,9 +290,8 @@ namespace MWGui const MWWorld::Store &store = MWBase::Environment::get().getWorld()->getStore().get(); - for (MWWorld::Store::iterator it = store.begin(); it != store.end(); ++it) + for (const ESM::BodyPart& bodypart : store) { - const ESM::BodyPart& bodypart = *it; if (bodypart.mData.mFlags & ESM::BodyPart::BPF_NotPlayable) continue; if (bodypart.mData.mType != ESM::BodyPart::MT_Skin) @@ -353,22 +352,21 @@ namespace MWGui MWBase::Environment::get().getWorld()->getStore().get(); std::vector > items; // ID, name - MWWorld::Store::iterator it = races.begin(); - for (; it != races.end(); ++it) + for (const ESM::Race& race : races) { - bool playable = it->mData.mFlags & ESM::Race::Playable; + bool playable = race.mData.mFlags & ESM::Race::Playable; if (!playable) // Only display playable races continue; - items.push_back(std::make_pair(it->mId, it->mName)); + items.push_back(std::make_pair(race.mId, race.mName)); } std::sort(items.begin(), items.end(), sortRaces); int index = 0; - for (std::vector >::const_iterator iter = items.begin(); iter != items.end(); ++iter) + for (auto& item : items) { - mRaceList->addItem(iter->second, iter->first); - if (Misc::StringUtils::ciEqual(iter->first, mCurrentRaceId)) + mRaceList->addItem(item.second, item.first); + if (Misc::StringUtils::ciEqual(item.first, mCurrentRaceId)) mRaceList->setIndexSelected(index); ++index; } @@ -376,9 +374,9 @@ namespace MWGui void RaceDialog::updateSkills() { - for (std::vector::iterator it = mSkillItems.begin(); it != mSkillItems.end(); ++it) + for (MyGUI::Widget* widget : mSkillItems) { - MyGUI::Gui::getInstance().destroyWidget(*it); + MyGUI::Gui::getInstance().destroyWidget(widget); } mSkillItems.clear(); @@ -413,9 +411,9 @@ namespace MWGui void RaceDialog::updateSpellPowers() { - for (std::vector::iterator it = mSpellPowerItems.begin(); it != mSpellPowerItems.end(); ++it) + for (MyGUI::Widget* widget : mSpellPowerItems) { - MyGUI::Gui::getInstance().destroyWidget(*it); + MyGUI::Gui::getInstance().destroyWidget(widget); } mSpellPowerItems.clear(); @@ -428,11 +426,9 @@ namespace MWGui const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); const ESM::Race *race = store.get().find(mCurrentRaceId); - std::vector::const_iterator it = race->mPowers.mList.begin(); - std::vector::const_iterator end = race->mPowers.mList.end(); - for (int i = 0; it != end; ++it) + int i = 0; + for (const std::string& spellpower : race->mPowers.mList) { - const std::string &spellpower = *it; Widgets::MWSpellPtr spellPowerWidget = mSpellPowerList->createWidget("MW_StatName", coord, MyGUI::Align::Default, std::string("SpellPower") + MyGUI::utility::toString(i)); spellPowerWidget->setSpellId(spellpower); spellPowerWidget->setUserString("ToolTipType", "Spell"); diff --git a/apps/openmw/mwgui/race.hpp b/apps/openmw/mwgui/race.hpp index c9e31d42d..0fa4fdec5 100644 --- a/apps/openmw/mwgui/race.hpp +++ b/apps/openmw/mwgui/race.hpp @@ -1,6 +1,8 @@ #ifndef MWGUI_RACE_H #define MWGUI_RACE_H +#include + #include "windowbase.hpp" #include diff --git a/apps/openmw/mwgui/recharge.cpp b/apps/openmw/mwgui/recharge.cpp index 56bd97d5e..e42db715b 100644 --- a/apps/openmw/mwgui/recharge.cpp +++ b/apps/openmw/mwgui/recharge.cpp @@ -1,7 +1,5 @@ #include "recharge.hpp" -#include - #include #include @@ -179,7 +177,8 @@ void Recharge::onItemClicked(MyGUI::Widget *sender, const MWWorld::Ptr& item) if (gem.getRefData().getCount() == 0) { std::string message = MWBase::Environment::get().getWorld()->getStore().get().find("sNotifyMessage51")->mValue.getString(); - message = boost::str(boost::format(message) % gem.getClass().getName(gem)); + Misc::StringUtils::replace(message, "%s", gem.getClass().getName(gem).c_str(), 2); + MWBase::Environment::get().getWindowManager()->messageBox(message); // special case: readd Azura's Star diff --git a/apps/openmw/mwgui/review.cpp b/apps/openmw/mwgui/review.cpp index f2f1cf892..e76cbe770 100644 --- a/apps/openmw/mwgui/review.cpp +++ b/apps/openmw/mwgui/review.cpp @@ -228,11 +228,9 @@ namespace MWGui std::set skillSet; std::copy(major.begin(), major.end(), std::inserter(skillSet, skillSet.begin())); std::copy(minor.begin(), minor.end(), std::inserter(skillSet, skillSet.begin())); - boost::array::const_iterator end = ESM::Skill::sSkillIds.end(); mMiscSkills.clear(); - for (boost::array::const_iterator it = ESM::Skill::sSkillIds.begin(); it != end; ++it) + for (const int skill : ESM::Skill::sSkillIds) { - int skill = *it; if (skillSet.find(skill) == skillSet.end()) mMiscSkills.push_back(skill); } @@ -327,11 +325,9 @@ namespace MWGui addGroup(MWBase::Environment::get().getWindowManager()->getGameSettingString(titleId, titleDefault), coord1, coord2); - SkillList::const_iterator end = skills.end(); - for (SkillList::const_iterator it = skills.begin(); it != end; ++it) + for (const int& skillId : skills) { - int skillId = *it; - if (skillId < 0 || skillId > ESM::Skill::Length) // Skip unknown skill indexes + if (skillId < 0 || skillId >= ESM::Skill::Length) // Skip unknown skill indexes continue; assert(skillId >= 0 && skillId < ESM::Skill::Length); const std::string &skillNameId = ESM::Skill::sSkillNameIds[skillId]; @@ -357,9 +353,9 @@ namespace MWGui void ReviewDialog::updateSkillArea() { - for (std::vector::iterator it = mSkillWidgets.begin(); it != mSkillWidgets.end(); ++it) + for (MyGUI::Widget* skillWidget : mSkillWidgets) { - MyGUI::Gui::getInstance().destroyWidget(*it); + MyGUI::Gui::getInstance().destroyWidget(skillWidget); } mSkillWidgets.clear(); @@ -392,19 +388,18 @@ namespace MWGui attributes[i] = mAttributeWidgets[i]->getAttributeValue().getBase(); std::vector selectedSpells = MWMechanics::autoCalcPlayerSpells(skills, attributes, race); - for (std::vector::iterator iter = selectedSpells.begin(); iter != selectedSpells.end(); ++iter) + for (std::string& spellId : selectedSpells) { - std::string lower = Misc::StringUtils::lowerCase(*iter); + std::string lower = Misc::StringUtils::lowerCase(spellId); if (std::find(spells.begin(), spells.end(), lower) == spells.end()) spells.push_back(lower); } if (race) { - for (std::vector::const_iterator iter = race->mPowers.mList.begin(); - iter != race->mPowers.mList.end(); ++iter) + for (const std::string& spellId : race->mPowers.mList) { - std::string lower = Misc::StringUtils::lowerCase(*iter); + std::string lower = Misc::StringUtils::lowerCase(spellId); if (std::find(spells.begin(), spells.end(), lower) == spells.end()) spells.push_back(lower); } @@ -413,10 +408,9 @@ namespace MWGui if (!mBirthSignId.empty()) { const ESM::BirthSign* sign = MWBase::Environment::get().getWorld()->getStore().get().find(mBirthSignId); - for (std::vector::const_iterator iter = sign->mPowers.mList.begin(); - iter != sign->mPowers.mList.end(); ++iter) + for (const std::string& spellId : sign->mPowers.mList) { - std::string lower = Misc::StringUtils::lowerCase(*iter); + std::string lower = Misc::StringUtils::lowerCase(spellId); if (std::find(spells.begin(), spells.end(), lower) == spells.end()) spells.push_back(lower); } @@ -425,27 +419,27 @@ namespace MWGui if (!mSkillWidgets.empty()) addSeparator(coord1, coord2); addGroup(MWBase::Environment::get().getWindowManager()->getGameSettingString("sTypeAbility", "Abilities"), coord1, coord2); - for (std::vector::const_iterator iter = spells.begin(); iter != spells.end(); ++iter) + for (std::string& spellId : spells) { - const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().find(*iter); + const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().find(spellId); if (spell->mData.mType == ESM::Spell::ST_Ability) addItem(spell, coord1, coord2); } addSeparator(coord1, coord2); addGroup(MWBase::Environment::get().getWindowManager()->getGameSettingString("sTypePower", "Powers"), coord1, coord2); - for (std::vector::const_iterator iter = spells.begin(); iter != spells.end(); ++iter) + for (std::string& spellId : spells) { - const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().find(*iter); + const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().find(spellId); if (spell->mData.mType == ESM::Spell::ST_Power) addItem(spell, coord1, coord2); } addSeparator(coord1, coord2); addGroup(MWBase::Environment::get().getWindowManager()->getGameSettingString("sTypeSpell", "Spells"), coord1, coord2); - for (std::vector::const_iterator iter = spells.begin(); iter != spells.end(); ++iter) + for (std::string& spellId : spells) { - const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().find(*iter); + const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().find(spellId); if (spell->mData.mType == ESM::Spell::ST_Spell) addItem(spell, coord1, coord2); } diff --git a/apps/openmw/mwgui/savegamedialog.hpp b/apps/openmw/mwgui/savegamedialog.hpp index 0a87b6600..a9915ee9d 100644 --- a/apps/openmw/mwgui/savegamedialog.hpp +++ b/apps/openmw/mwgui/savegamedialog.hpp @@ -1,6 +1,8 @@ #ifndef OPENMW_MWGUI_SAVEGAMEDIALOG_H #define OPENMW_MWGUI_SAVEGAMEDIALOG_H +#include + #include "windowbase.hpp" namespace MWState diff --git a/apps/openmw/mwgui/screenfader.hpp b/apps/openmw/mwgui/screenfader.hpp index 79bea30e5..aa17ed4e8 100644 --- a/apps/openmw/mwgui/screenfader.hpp +++ b/apps/openmw/mwgui/screenfader.hpp @@ -2,6 +2,7 @@ #define OPENMW_MWGUI_SCREENFADER_H #include +#include #include "windowbase.hpp" diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index 6e6924f28..deb1116d0 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -8,11 +8,12 @@ #include #include -#include #include #include +#include +#include #include #include @@ -58,7 +59,7 @@ namespace std::string getAspect (int x, int y) { - int gcd = boost::math::gcd (x, y); + int gcd = Misc::gcd (x, y); int xaspect = x / gcd; int yaspect = y / gcd; // special case: 8 : 5 is usually referred to as 16:10 @@ -125,7 +126,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; @@ -159,7 +161,7 @@ namespace MWGui MyGUI::TextBox* textBox; getWidget(textBox, labelWidgetName); std::string labelCaption = scroller->getUserString("SettingLabelCaption"); - boost::algorithm::replace_all(labelCaption, "%s", value); + Misc::StringUtils::replaceAll(labelCaption, "%s", value.c_str(), 2); textBox->setCaptionWithReplacing(labelCaption); } } @@ -184,6 +186,7 @@ namespace MWGui getWidget(mKeyboardSwitch, "KeyboardButton"); getWidget(mControllerSwitch, "ControllerButton"); getWidget(mWaterTextureSize, "WaterTextureSize"); + getWidget(mWaterReflectionDetail, "WaterReflectionDetail"); #ifndef WIN32 // hide gamma controls since it currently does not work under Linux @@ -207,6 +210,7 @@ namespace MWGui mResolutionList->eventListChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onResolutionSelected); mWaterTextureSize->eventComboChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onWaterTextureSizeChanged); + mWaterReflectionDetail->eventComboChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onWaterReflectionDetailChanged); mKeyboardSwitch->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onKeyboardSwitchClicked); mControllerSwitch->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onControllerSwitchClicked); @@ -226,11 +230,10 @@ namespace MWGui resolutions.push_back(std::make_pair(mode.w, mode.h)); } std::sort(resolutions.begin(), resolutions.end(), sortResolutions); - for (std::vector < std::pair >::const_iterator it=resolutions.begin(); - it!=resolutions.end(); ++it) + for (std::pair& resolution : resolutions) { - std::string str = MyGUI::utility::toString(it->first) + " x " + MyGUI::utility::toString(it->second) - + " (" + getAspect(it->first,it->second) + ")"; + std::string str = MyGUI::utility::toString(resolution.first) + " x " + MyGUI::utility::toString(resolution.second) + + " (" + getAspect(resolution.first, resolution.second) + ")"; if (mResolutionList->findItemIndexWith(str) == MyGUI::ITEM_NONE) mResolutionList->addItem(str); @@ -240,7 +243,7 @@ namespace MWGui std::string tmip = Settings::Manager::getString("texture mipmap", "General"); mTextureFilteringButton->setCaption(textureMipmappingToStr(tmip)); - int waterTextureSize = Settings::Manager::getInt ("rtt size", "Water"); + int waterTextureSize = Settings::Manager::getInt("rtt size", "Water"); if (waterTextureSize >= 512) mWaterTextureSize->setIndexSelected(0); if (waterTextureSize >= 1024) @@ -248,6 +251,10 @@ namespace MWGui if (waterTextureSize >= 2048) mWaterTextureSize->setIndexSelected(2); + int waterReflectionDetail = Settings::Manager::getInt("reflection detail", "Water"); + waterReflectionDetail = std::min(4, std::max(0, waterReflectionDetail)); + mWaterReflectionDetail->setIndexSelected(waterReflectionDetail); + mWindowBorderButton->setEnabled(!Settings::Manager::getBool("fullscreen", "Video")); mKeyboardSwitch->setStateSelected(true); @@ -327,6 +334,13 @@ namespace MWGui apply(); } + void SettingsWindow::onWaterReflectionDetailChanged(MyGUI::ComboBox* _sender, size_t pos) + { + unsigned int level = std::min((unsigned int)4, (unsigned int)pos); + Settings::Manager::setInt("reflection detail", "Water", level); + apply(); + } + void SettingsWindow::onButtonToggled(MyGUI::Widget* _sender) { std::string on = MWBase::Environment::get().getWindowManager()->getGameSettingString("sOn", "On"); @@ -410,14 +424,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 @@ -475,17 +493,17 @@ namespace MWGui else actions = MWBase::Environment::get().getInputManager()->getActionControllerSorting(); - for (std::vector::const_iterator it = actions.begin(); it != actions.end(); ++it) + for (const int& action : actions) { - std::string desc = MWBase::Environment::get().getInputManager()->getActionDescription (*it); + std::string desc = MWBase::Environment::get().getInputManager()->getActionDescription (action); if (desc == "") continue; std::string binding; if(mKeyboardMode) - binding = MWBase::Environment::get().getInputManager()->getActionKeyBindingName(*it); + binding = MWBase::Environment::get().getInputManager()->getActionKeyBindingName(action); else - binding = MWBase::Environment::get().getInputManager()->getActionControllerBindingName(*it); + binding = MWBase::Environment::get().getInputManager()->getActionControllerBindingName(action); Gui::SharedStateButton* leftText = mControlsBox->createWidget("SandTextButton", MyGUI::IntCoord(), MyGUI::Align::Default); leftText->setCaptionWithReplacing(desc); @@ -493,7 +511,7 @@ namespace MWGui Gui::SharedStateButton* rightText = mControlsBox->createWidget("SandTextButton", MyGUI::IntCoord(), MyGUI::Align::Default); rightText->setCaptionWithReplacing(binding); rightText->setTextAlign (MyGUI::Align::Right); - rightText->setUserData(*it); // save the action id for callbacks + rightText->setUserData(action); // save the action id for callbacks rightText->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onRebindAction); rightText->eventMouseWheel += MyGUI::newDelegate(this, &SettingsWindow::onInputTabMouseWheel); diff --git a/apps/openmw/mwgui/settingswindow.hpp b/apps/openmw/mwgui/settingswindow.hpp index c6e48819c..37d671a5a 100644 --- a/apps/openmw/mwgui/settingswindow.hpp +++ b/apps/openmw/mwgui/settingswindow.hpp @@ -33,6 +33,7 @@ namespace MWGui MyGUI::Widget* mAnisotropyBox; MyGUI::ComboBox* mWaterTextureSize; + MyGUI::ComboBox* mWaterReflectionDetail; // controls MyGUI::ScrollView* mControlsBox; @@ -52,6 +53,7 @@ namespace MWGui void highlightCurrentResolution(); void onWaterTextureSizeChanged(MyGUI::ComboBox* _sender, size_t pos); + void onWaterReflectionDetailChanged(MyGUI::ComboBox* _sender, size_t pos); void onRebindAction(MyGUI::Widget* _sender); void onInputTabMouseWheel(MyGUI::Widget* _sender, int _rel); diff --git a/apps/openmw/mwgui/spellbuyingwindow.cpp b/apps/openmw/mwgui/spellbuyingwindow.cpp index bc5d161d8..5fb361579 100644 --- a/apps/openmw/mwgui/spellbuyingwindow.cpp +++ b/apps/openmw/mwgui/spellbuyingwindow.cpp @@ -122,9 +122,9 @@ namespace MWGui std::stable_sort(spellsToSort.begin(), spellsToSort.end(), sortSpells); - for (std::vector::iterator it = spellsToSort.begin() ; it != spellsToSort.end(); ++it) + for (const ESM::Spell* spell : spellsToSort) { - addSpell(**it); + addSpell(*spell); } spellsToSort.clear(); diff --git a/apps/openmw/mwgui/spellbuyingwindow.hpp b/apps/openmw/mwgui/spellbuyingwindow.hpp index 30727a545..c68ec2483 100644 --- a/apps/openmw/mwgui/spellbuyingwindow.hpp +++ b/apps/openmw/mwgui/spellbuyingwindow.hpp @@ -4,7 +4,10 @@ #include "windowbase.hpp" #include "referenceinterface.hpp" -#include "../mwworld/esmstore.hpp" +namespace ESM +{ + struct Spell; +} namespace MyGUI { @@ -17,7 +20,6 @@ namespace MWGui class WindowManager; } - namespace MWGui { class SpellBuyingWindow : public ReferenceInterface, public WindowBase diff --git a/apps/openmw/mwgui/spellcreationdialog.cpp b/apps/openmw/mwgui/spellcreationdialog.cpp index 46d8b38ec..23f24e321 100644 --- a/apps/openmw/mwgui/spellcreationdialog.cpp +++ b/apps/openmw/mwgui/spellcreationdialog.cpp @@ -455,10 +455,8 @@ namespace MWGui const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); - for (std::vector::const_iterator it = mEffects.begin(); it != mEffects.end(); ++it) + for (const ESM::ENAMstruct& effect : mEffects) { - const ESM::ENAMstruct& effect = *it; - y += std::max(1.f, MWMechanics::calcEffectCost(effect)); if (effect.mRange == ESM::RT_Target) @@ -530,18 +528,17 @@ namespace MWGui if (spell->mData.mType != ESM::Spell::ST_Spell) continue; - const std::vector& list = spell->mEffects.mList; - for (std::vector::const_iterator it2 = list.begin(); it2 != list.end(); ++it2) + for (const ESM::ENAMstruct& effectInfo : spell->mEffects.mList) { - const ESM::MagicEffect * effect = MWBase::Environment::get().getWorld()->getStore().get().find(it2->mEffectID); + const ESM::MagicEffect * effect = MWBase::Environment::get().getWorld()->getStore().get().find(effectInfo.mEffectID); // skip effects that do not allow spellmaking/enchanting int requiredFlags = (mType == Spellmaking) ? ESM::MagicEffect::AllowSpellmaking : ESM::MagicEffect::AllowEnchanting; if (!(effect->mData.mFlags & requiredFlags)) continue; - if (std::find(knownEffects.begin(), knownEffects.end(), it2->mEffectID) == knownEffects.end()) - knownEffects.push_back(it2->mEffectID); + if (std::find(knownEffects.begin(), knownEffects.end(), effectInfo.mEffectID) == knownEffects.end()) + knownEffects.push_back(effectInfo.mEffectID); } } @@ -550,23 +547,23 @@ namespace MWGui mAvailableEffectsList->clear (); int i=0; - for (std::vector::const_iterator it = knownEffects.begin(); it != knownEffects.end(); ++it) + for (const short effectId : knownEffects) { mAvailableEffectsList->addItem(MWBase::Environment::get().getWorld ()->getStore ().get().find( - ESM::MagicEffect::effectIdToString (*it))->mValue.getString()); - mButtonMapping[i] = *it; + ESM::MagicEffect::effectIdToString(effectId))->mValue.getString()); + mButtonMapping[i] = effectId; ++i; } mAvailableEffectsList->adjustSize (); mAvailableEffectsList->scrollToTop(); - for (std::vector::const_iterator it = knownEffects.begin(); it != knownEffects.end(); ++it) + for (const short effectId : knownEffects) { std::string name = MWBase::Environment::get().getWorld ()->getStore ().get().find( - ESM::MagicEffect::effectIdToString (*it))->mValue.getString(); + ESM::MagicEffect::effectIdToString(effectId))->mValue.getString(); MyGUI::Widget* w = mAvailableEffectsList->getItemWidget(name); - ToolTips::createMagicEffectToolTip (w, *it); + ToolTips::createMagicEffectToolTip (w, effectId); } mEffects.clear(); @@ -646,9 +643,9 @@ namespace MWGui } else { - for (std::vector::const_iterator it = mEffects.begin(); it != mEffects.end(); ++it) + for (const ESM::ENAMstruct& effectInfo : mEffects) { - if (it->mEffectID == mSelectedKnownEffectId) + if (effectInfo.mEffectID == mSelectedKnownEffectId) { MWBase::Environment::get().getWindowManager()->messageBox ("#{sOnetypeEffectMessage}"); return; @@ -680,17 +677,17 @@ namespace MWGui MyGUI::IntSize size(0,0); int i = 0; - for (std::vector::const_iterator it = mEffects.begin(); it != mEffects.end(); ++it) + for (const ESM::ENAMstruct& effectInfo : mEffects) { Widgets::SpellEffectParams params; - params.mEffectID = it->mEffectID; - params.mSkill = it->mSkill; - params.mAttribute = it->mAttribute; - params.mDuration = it->mDuration; - params.mMagnMin = it->mMagnMin; - params.mMagnMax = it->mMagnMax; - params.mRange = it->mRange; - params.mArea = it->mArea; + params.mEffectID = effectInfo.mEffectID; + params.mSkill = effectInfo.mSkill; + params.mAttribute = effectInfo.mAttribute; + params.mDuration = effectInfo.mDuration; + params.mMagnMin = effectInfo.mMagnMin; + params.mMagnMax = effectInfo.mMagnMax; + params.mRange = effectInfo.mRange; + params.mArea = effectInfo.mArea; params.mIsConstant = mConstantEffect; MyGUI::Button* button = mUsedEffectsView->createWidget("", MyGUI::IntCoord(0, size.height, 0, 24), MyGUI::Align::Default); diff --git a/apps/openmw/mwgui/spellicons.cpp b/apps/openmw/mwgui/spellicons.cpp index c0f56e654..51508cd06 100644 --- a/apps/openmw/mwgui/spellicons.cpp +++ b/apps/openmw/mwgui/spellicons.cpp @@ -65,10 +65,11 @@ namespace MWGui int w=2; - for (std::map >::const_iterator it = effects.begin(); it != effects.end(); ++it) + for (auto& effectInfoPair : effects) { + const int effectId = effectInfoPair.first; const ESM::MagicEffect* effect = - MWBase::Environment::get().getWorld ()->getStore ().get().find(it->first); + MWBase::Environment::get().getWorld ()->getStore ().get().find(effectId); float remainingDuration = 0; float totalDuration = 0; @@ -77,46 +78,47 @@ namespace MWGui static const float fadeTime = MWBase::Environment::get().getWorld()->getStore().get().find("fMagicStartIconBlink")->mValue.getFloat(); - for (std::vector::const_iterator effectIt = it->second.begin(); - effectIt != it->second.end(); ++effectIt) + std::vector& effectInfos = effectInfoPair.second; + bool addNewLine = false; + for (const MagicEffectInfo& effectInfo : effectInfos) { - if (effectIt != it->second.begin()) + if (addNewLine) sourcesDescription += "\n"; // if at least one of the effect sources is permanent, the effect will never wear off - if (effectIt->mPermanent) + if (effectInfo.mPermanent) { remainingDuration = fadeTime; totalDuration = fadeTime; } else { - remainingDuration = std::max(remainingDuration, effectIt->mRemainingTime); - totalDuration = std::max(totalDuration, effectIt->mTotalTime); + remainingDuration = std::max(remainingDuration, effectInfo.mRemainingTime); + totalDuration = std::max(totalDuration, effectInfo.mTotalTime); } - sourcesDescription += effectIt->mSource; + sourcesDescription += effectInfo.mSource; if (effect->mData.mFlags & ESM::MagicEffect::TargetSkill) sourcesDescription += " (" + MWBase::Environment::get().getWindowManager()->getGameSettingString( - ESM::Skill::sSkillNameIds[effectIt->mKey.mArg], "") + ")"; + ESM::Skill::sSkillNameIds[effectInfo.mKey.mArg], "") + ")"; if (effect->mData.mFlags & ESM::MagicEffect::TargetAttribute) sourcesDescription += " (" + MWBase::Environment::get().getWindowManager()->getGameSettingString( - ESM::Attribute::sGmstAttributeIds[effectIt->mKey.mArg], "") + ")"; + ESM::Attribute::sGmstAttributeIds[effectInfo.mKey.mArg], "") + ")"; ESM::MagicEffect::MagnitudeDisplayType displayType = effect->getMagnitudeDisplayType(); if (displayType == ESM::MagicEffect::MDT_TimesInt) { std::string timesInt = MWBase::Environment::get().getWindowManager()->getGameSettingString("sXTimesINT", ""); std::stringstream formatter; - formatter << std::fixed << std::setprecision(1) << " " << (effectIt->mMagnitude / 10.0f) << timesInt; + formatter << std::fixed << std::setprecision(1) << " " << (effectInfo.mMagnitude / 10.0f) << timesInt; sourcesDescription += formatter.str(); } else if ( displayType != ESM::MagicEffect::MDT_None ) { - sourcesDescription += ": " + MyGUI::utility::toString(effectIt->mMagnitude); + sourcesDescription += ": " + MyGUI::utility::toString(effectInfo.mMagnitude); if ( displayType == ESM::MagicEffect::MDT_Percentage ) sourcesDescription += MWBase::Environment::get().getWindowManager()->getGameSettingString("spercent", ""); @@ -124,49 +126,54 @@ namespace MWGui sourcesDescription += " " + MWBase::Environment::get().getWindowManager()->getGameSettingString("sfeet", ""); else if ( displayType == ESM::MagicEffect::MDT_Level ) { - sourcesDescription += " " + ((effectIt->mMagnitude > 1) ? + sourcesDescription += " " + ((effectInfo.mMagnitude > 1) ? MWBase::Environment::get().getWindowManager()->getGameSettingString("sLevels", "") : MWBase::Environment::get().getWindowManager()->getGameSettingString("sLevel", "") ); } else // ESM::MagicEffect::MDT_Points { - sourcesDescription += " " + ((effectIt->mMagnitude > 1) ? + sourcesDescription += " " + ((effectInfo.mMagnitude > 1) ? MWBase::Environment::get().getWindowManager()->getGameSettingString("spoints", "") : MWBase::Environment::get().getWindowManager()->getGameSettingString("spoint", "") ); } } - if (effectIt->mRemainingTime > -1 && + if (effectInfo.mRemainingTime > -1 && Settings::Manager::getBool("show effect duration","Game")) { sourcesDescription += " #{sDuration}: "; - float duration = effectIt->mRemainingTime; - if (duration > 3600) { + float duration = effectInfo.mRemainingTime; + if (duration > 3600) + { int hour = duration / 3600; duration -= hour*3600; sourcesDescription += MWGui::ToolTips::toString(hour) + "h"; } - if (duration > 60) { + if (duration > 60) + { int minute = duration / 60; duration -= minute*60; sourcesDescription += MWGui::ToolTips::toString(minute) + "m"; } - if (duration > 0.1) { + if (duration > 0.1) + { sourcesDescription += MWGui::ToolTips::toString(duration) + "s"; } } + + addNewLine = true; } if (remainingDuration > 0.f) { MyGUI::ImageBox* image; - if (mWidgetMap.find(it->first) == mWidgetMap.end()) + if (mWidgetMap.find(effectId) == mWidgetMap.end()) { image = parent->createWidget ("ImageBox", MyGUI::IntCoord(w,2,16,16), MyGUI::Align::Default); - mWidgetMap[it->first] = image; + mWidgetMap[effectId] = image; image->setImageTexture(MWBase::Environment::get().getWindowManager()->correctIconPath(effect->mIcon)); - std::string name = ESM::MagicEffect::effectIdToString (it->first); + std::string name = ESM::MagicEffect::effectIdToString (effectId); ToolTipInfo tooltipInfo; tooltipInfo.caption = "#{" + name + "}"; @@ -178,7 +185,7 @@ namespace MWGui image->setUserString("ToolTipType", "ToolTipInfo"); } else - image = mWidgetMap[it->first]; + image = mWidgetMap[effectId]; image->setPosition(w,2); image->setVisible(true); @@ -191,9 +198,9 @@ namespace MWGui if (totalDuration >= fadeTime && fadeTime > 0.f) image->setAlpha(std::min(remainingDuration/fadeTime, 1.f)); } - else if (mWidgetMap.find(it->first) != mWidgetMap.end()) + else if (mWidgetMap.find(effectId) != mWidgetMap.end()) { - mWidgetMap[it->first]->setVisible(false); + mWidgetMap[effectId]->setVisible(false); } } @@ -208,10 +215,10 @@ namespace MWGui } // hide inactive effects - for (std::map::iterator it = mWidgetMap.begin(); it != mWidgetMap.end(); ++it) + for (auto& widgetPair : mWidgetMap) { - if (effects.find(it->first) == effects.end()) - it->second->setVisible(false); + if (effects.find(widgetPair.first) == effects.end()) + widgetPair.second->setVisible(false); } } diff --git a/apps/openmw/mwgui/spellview.cpp b/apps/openmw/mwgui/spellview.cpp index 879ce471f..f03c1cedb 100644 --- a/apps/openmw/mwgui/spellview.cpp +++ b/apps/openmw/mwgui/spellview.cpp @@ -149,13 +149,13 @@ namespace MWGui mModel->update(); bool fullUpdateRequired = false; SpellModel::ModelIndex maxSpellIndexFound = -1; - for (std::vector< LineInfo >::iterator it = mLines.begin(); it != mLines.end(); ++it) + for (LineInfo& line : mLines) { // only update the lines that are "updateable" - SpellModel::ModelIndex spellIndex(it->mSpellIndex); + SpellModel::ModelIndex spellIndex(line.mSpellIndex); if (spellIndex != NoSpellIndex) { - Gui::SharedStateButton* nameButton = reinterpret_cast(it->mLeftWidget); + Gui::SharedStateButton* nameButton = reinterpret_cast(line.mLeftWidget); // match model against line // if don't match, then major change has happened, so do a full update @@ -176,7 +176,7 @@ namespace MWGui else { maxSpellIndexFound = spellIndex; - Gui::SharedStateButton* costButton = reinterpret_cast(it->mRightWidget); + Gui::SharedStateButton* costButton = reinterpret_cast(line.mRightWidget); if ((costButton != nullptr) && (costButton->getCaption() != spell.mCostColumn)) { costButton->setCaption(spell.mCostColumn); @@ -198,27 +198,25 @@ namespace MWGui void SpellView::layoutWidgets() { int height = 0; - for (std::vector< LineInfo >::iterator it = mLines.begin(); - it != mLines.end(); ++it) + for (LineInfo& line : mLines) { - height += (it->mLeftWidget)->getHeight(); + height += line.mLeftWidget->getHeight(); } bool scrollVisible = height > mScrollView->getHeight(); int width = mScrollView->getWidth() - (scrollVisible ? 18 : 0); height = 0; - for (std::vector< LineInfo >::iterator it = mLines.begin(); - it != mLines.end(); ++it) + for (LineInfo& line : mLines) { - int lineHeight = (it->mLeftWidget)->getHeight(); - (it->mLeftWidget)->setCoord(4, height, width - 8, lineHeight); - if (it->mRightWidget) + int lineHeight = line.mLeftWidget->getHeight(); + line.mLeftWidget->setCoord(4, height, width - 8, lineHeight); + if (line.mRightWidget) { - (it->mRightWidget)->setCoord(4, height, width - 8, lineHeight); - MyGUI::TextBox* second = (it->mRightWidget)->castType(false); + line.mRightWidget->setCoord(4, height, width - 8, lineHeight); + MyGUI::TextBox* second = line.mRightWidget->castType(false); if (second) - (it->mLeftWidget)->setSize(width - 8 - second->getTextSize().width, lineHeight); + line.mLeftWidget->setSize(width - 8 - second->getTextSize().width, lineHeight); } height += lineHeight; diff --git a/apps/openmw/mwgui/spellview.hpp b/apps/openmw/mwgui/spellview.hpp index 0eb69f6ba..66113d869 100644 --- a/apps/openmw/mwgui/spellview.hpp +++ b/apps/openmw/mwgui/spellview.hpp @@ -1,6 +1,7 @@ #ifndef OPENMW_GUI_SPELLVIEW_H #define OPENMW_GUI_SPELLVIEW_H +#include #include #include diff --git a/apps/openmw/mwgui/spellwindow.cpp b/apps/openmw/mwgui/spellwindow.cpp index 2177e9e2a..254e731d0 100644 --- a/apps/openmw/mwgui/spellwindow.cpp +++ b/apps/openmw/mwgui/spellwindow.cpp @@ -1,10 +1,9 @@ #include "spellwindow.hpp" -#include - #include #include +#include #include #include "../mwbase/windowmanager.hpp" @@ -157,7 +156,7 @@ namespace MWGui mSpellToDelete = spellId; ConfirmationDialog* dialog = MWBase::Environment::get().getWindowManager()->getConfirmationDialog(); std::string question = MWBase::Environment::get().getWindowManager()->getGameSettingString("sQuestionDeleteSpell", "Delete %s?"); - question = boost::str(boost::format(question) % spell->mName); + Misc::StringUtils::replace(question, "%s", spell->mName.c_str(), 2); dialog->askForConfirmation(question); dialog->eventOkClicked.clear(); dialog->eventOkClicked += MyGUI::newDelegate(this, &SpellWindow::onDeleteSpellAccept); diff --git a/apps/openmw/mwgui/statswindow.cpp b/apps/openmw/mwgui/statswindow.cpp index df292cfaa..c3f852c5b 100644 --- a/apps/openmw/mwgui/statswindow.cpp +++ b/apps/openmw/mwgui/statswindow.cpp @@ -278,11 +278,9 @@ namespace MWGui std::set skillSet; std::copy(major.begin(), major.end(), std::inserter(skillSet, skillSet.begin())); std::copy(minor.begin(), minor.end(), std::inserter(skillSet, skillSet.begin())); - boost::array::const_iterator end = ESM::Skill::sSkillIds.end(); mMiscSkills.clear(); - for (boost::array::const_iterator it = ESM::Skill::sSkillIds.begin(); it != end; ++it) + for (const int skill : ESM::Skill::sSkillIds) { - int skill = *it; if (skillSet.find(skill) == skillSet.end()) mMiscSkills.push_back(skill); } @@ -435,16 +433,11 @@ namespace MWGui addGroup(MWBase::Environment::get().getWindowManager()->getGameSettingString(titleId, titleDefault), coord1, coord2); - SkillList::const_iterator end = skills.end(); - for (SkillList::const_iterator it = skills.begin(); it != end; ++it) + for (const int skillId : skills) { - int skillId = *it; if (skillId < 0 || skillId >= ESM::Skill::Length) // Skip unknown skill indexes continue; const std::string &skillNameId = ESM::Skill::sSkillNameIds[skillId]; - const MWMechanics::SkillValue &stat = mSkillValues.find(skillId)->second; - int base = stat.getBase(); - int modified = stat.getModified(); const MWWorld::ESMStore &esmStore = MWBase::Environment::get().getWorld()->getStore(); @@ -456,13 +449,9 @@ namespace MWGui const ESM::Attribute* attr = esmStore.get().find(skill->mData.mAttribute); - std::string state = "normal"; - if (modified > base) - state = "increased"; - else if (modified < base) - state = "decreased"; std::pair widgets = addValueItem(MWBase::Environment::get().getWindowManager()->getGameSettingString(skillNameId, skillNameId), - MyGUI::utility::toString(static_cast(modified)), state, coord1, coord2); + "", "normal", coord1, coord2); + mSkillWidgetMap[skillId] = widgets; for (int i=0; i<2; ++i) { @@ -473,27 +462,9 @@ namespace MWGui mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("Caption_SkillAttribute", "#{sGoverningAttribute}: #{" + attr->mName + "}"); mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("ImageTexture_SkillImage", icon); mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("Range_SkillProgress", "100"); - if (base < 100) - { - mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("Visible_SkillMaxed", "false"); - mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("UserData^Hidden_SkillMaxed", "true"); - - mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("Visible_SkillProgressVBox", "true"); - mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("UserData^Hidden_SkillProgressVBox", "false"); - - setSkillProgress(mSkillWidgets[mSkillWidgets.size()-1-i], stat.getProgress(), skillId); - } - else - { - mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("Visible_SkillMaxed", "true"); - mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("UserData^Hidden_SkillMaxed", "false"); - - mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("Visible_SkillProgressVBox", "false"); - mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("UserData^Hidden_SkillProgressVBox", "true"); - } } - mSkillWidgetMap[skillId] = widgets; + setValue(static_cast(skillId), mSkillValues.find(skillId)->second); } } @@ -501,9 +472,9 @@ namespace MWGui { mChanged = false; - for (std::vector::iterator it = mSkillWidgets.begin(); it != mSkillWidgets.end(); ++it) + for (MyGUI::Widget* widget : mSkillWidgets) { - MyGUI::Gui::getInstance().destroyWidget(*it); + MyGUI::Gui::getInstance().destroyWidget(widget); } mSkillWidgets.clear(); @@ -552,11 +523,11 @@ namespace MWGui const std::set &expelled = PCstats.getExpelled(); bool firstFaction=true; - FactionList::const_iterator end = mFactions.end(); - for (FactionList::const_iterator it = mFactions.begin(); it != end; ++it) + for (auto& factionPair : mFactions) { + const std::string& factionId = factionPair.first; const ESM::Faction *faction = - store.get().find(it->first); + store.get().find(factionId); if (faction->mData.mIsHidden == 1) continue; @@ -577,11 +548,11 @@ namespace MWGui text += std::string("#{fontcolourhtml=header}") + faction->mName; - if (expelled.find(it->first) != expelled.end()) + if (expelled.find(factionId) != expelled.end()) text += "\n#{fontcolourhtml=normal}#{sExpelled}"; else { - int rank = it->second; + int rank = factionPair.second; rank = std::max(0, std::min(9, rank)); text += std::string("\n#{fontcolourhtml=normal}") + faction->mRanks[rank]; diff --git a/apps/openmw/mwgui/timeadvancer.cpp b/apps/openmw/mwgui/timeadvancer.cpp index 43504ce87..a07da1682 100644 --- a/apps/openmw/mwgui/timeadvancer.cpp +++ b/apps/openmw/mwgui/timeadvancer.cpp @@ -1,8 +1,5 @@ #include "timeadvancer.hpp" -#include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" - namespace MWGui { TimeAdvancer::TimeAdvancer(float delay) diff --git a/apps/openmw/mwgui/tooltips.cpp b/apps/openmw/mwgui/tooltips.cpp index a1879c2f9..f9775253a 100644 --- a/apps/openmw/mwgui/tooltips.cpp +++ b/apps/openmw/mwgui/tooltips.cpp @@ -226,18 +226,17 @@ namespace MWGui MWBase::Environment::get().getWorld()->getStore().get().find(focus->getUserString("Spell")); info.caption = spell->mName; Widgets::SpellEffectList effects; - std::vector::const_iterator end = spell->mEffects.mList.end(); - for (std::vector::const_iterator it = spell->mEffects.mList.begin(); it != end; ++it) + for (const ESM::ENAMstruct& spellEffect : spell->mEffects.mList) { Widgets::SpellEffectParams params; - params.mEffectID = it->mEffectID; - params.mSkill = it->mSkill; - params.mAttribute = it->mAttribute; - params.mDuration = it->mDuration; - params.mMagnMin = it->mMagnMin; - params.mMagnMax = it->mMagnMax; - params.mRange = it->mRange; - params.mArea = it->mArea; + params.mEffectID = spellEffect.mEffectID; + params.mSkill = spellEffect.mSkill; + params.mAttribute = spellEffect.mAttribute; + params.mDuration = spellEffect.mDuration; + params.mMagnMin = spellEffect.mMagnMin; + params.mMagnMax = spellEffect.mMagnMax; + params.mRange = spellEffect.mRange; + params.mArea = spellEffect.mArea; params.mIsConstant = (spell->mData.mType == ESM::Spell::ST_Ability); params.mNoTarget = false; effects.push_back(params); @@ -260,14 +259,13 @@ namespace MWGui tooltip->setVisible(true); std::map userStrings = focus->getUserStrings(); - for (std::map::iterator it = userStrings.begin(); - it != userStrings.end(); ++it) + for (auto& userStringPair : userStrings) { - size_t underscorePos = it->first.find("_"); + size_t underscorePos = userStringPair.first.find("_"); if (underscorePos == std::string::npos) continue; - std::string key = it->first.substr(0, underscorePos); - std::string widgetName = it->first.substr(underscorePos+1, it->first.size()-(underscorePos+1)); + std::string key = userStringPair.first.substr(0, underscorePos); + std::string widgetName = userStringPair.first.substr(underscorePos+1, userStringPair.first.size()-(underscorePos+1)); type = "Property"; size_t caretPos = key.find("^"); @@ -280,9 +278,9 @@ namespace MWGui MyGUI::Widget* w; getWidget(w, widgetName); if (type == "Property") - w->setProperty(key, it->second); + w->setProperty(key, userStringPair.second); else if (type == "UserData") - w->setUserString(key, it->second); + w->setUserString(key, userStringPair.second); } tooltipSize = tooltip->getSize(); @@ -458,7 +456,7 @@ namespace MWGui MyGUI::IntSize totalSize = MyGUI::IntSize( std::min(std::max(textSize.width,captionSize.width + ((image != "") ? imageCaptionHPadding : 0)),maximumWidth), ((text != "") ? textSize.height + imageCaptionVPadding : 0) + captionHeight ); - for (std::vector::const_iterator it = info.notes.begin(); it != info.notes.end(); ++it) + for (const std::string& note : info.notes) { MyGUI::ImageBox* icon = mDynamicToolTipBox->createWidget("MarkerButton", MyGUI::IntCoord(padding.left, totalSize.height+padding.top, 8, 8), MyGUI::Align::Default); @@ -468,7 +466,7 @@ namespace MWGui MyGUI::Align::Default); edit->setEditMultiLine(true); edit->setEditWordWrap(true); - edit->setCaption(*it); + edit->setCaption(note); edit->setSize(edit->getWidth(), edit->getTextSize().height); icon->setPosition(icon->getLeft(),(edit->getTop()+edit->getBottom())/2-icon->getHeight()/2); totalSize.height += std::max(edit->getHeight(), icon->getHeight()); @@ -653,12 +651,12 @@ namespace MWGui std::vector > itemOwners = MWBase::Environment::get().getMechanicsManager()->getStolenItemOwners(cellref.getRefId()); - for (std::vector >::const_iterator it = itemOwners.begin(); it != itemOwners.end(); ++it) + for (std::pair& owner : itemOwners) { - if (it->second == std::numeric_limits::max()) - ret += std::string("\nStolen from ") + it->first; // for legacy (ESS) savegames + if (owner.second == std::numeric_limits::max()) + ret += std::string("\nStolen from ") + owner.first; // for legacy (ESS) savegames else - ret += std::string("\nStolen ") + MyGUI::utility::toString(it->second) + " from " + it->first; + ret += std::string("\nStolen ") + MyGUI::utility::toString(owner.second) + " from " + owner.first; } ret += getMiscString(cellref.getGlobalVariable(), "Global"); @@ -732,17 +730,16 @@ namespace MWGui MWBase::Environment::get().getWorld()->getStore().get(); bool isFirst = true; - MWWorld::Store::iterator it = skills.begin(); - for (; it != skills.end(); ++it) + for (auto& skillPair : skills) { - if (it->second.mData.mSpecialization == specId) + if (skillPair.second.mData.mSpecialization == specId) { if (isFirst) isFirst = false; else specText += "\n"; - specText += std::string("#{") + ESM::Skill::sSkillNameIds[it->first] + "}"; + specText += std::string("#{") + ESM::Skill::sSkillNameIds[skillPair.first] + "}"; } } widget->setUserString("Caption_ColumnText", specText); @@ -767,9 +764,8 @@ namespace MWGui std::vector abilities, powers, spells; - for (std::vector::const_iterator it = sign->mPowers.mList.begin(); it != sign->mPowers.mList.end(); ++it) + for (const std::string& spellId : sign->mPowers.mList) { - const std::string &spellId = *it; const ESM::Spell *spell = store.get().search(spellId); if (!spell) continue; // Skip spells which cannot be found @@ -797,15 +793,15 @@ namespace MWGui for (int category = 0; category < 3; ++category) { - for (std::vector::const_iterator it = categories[category].spells.begin(); it != categories[category].spells.end(); ++it) + bool addHeader = true; + for (const std::string& spellId : categories[category].spells) { - if (it == categories[category].spells.begin()) + if (addHeader) { text += std::string("\n\n#{fontcolourhtml=header}") + std::string("#{") + categories[category].label + "}"; + addHeader = false; } - const std::string &spellId = *it; - const ESM::Spell *spell = store.get().find(spellId); text += "\n#{fontcolourhtml=normal}" + spell->mName; } diff --git a/apps/openmw/mwgui/tradeitemmodel.cpp b/apps/openmw/mwgui/tradeitemmodel.cpp index a26294a0e..84b23ee2b 100644 --- a/apps/openmw/mwgui/tradeitemmodel.cpp +++ b/apps/openmw/mwgui/tradeitemmodel.cpp @@ -36,13 +36,12 @@ namespace MWGui void TradeItemModel::borrowImpl(const ItemStack &item, std::vector &out) { - std::vector::iterator it = out.begin(); bool found = false; - for (; it != out.end(); ++it) + for (ItemStack& itemStack : out) { - if (it->mBase == item.mBase) + if (itemStack.mBase == item.mBase) { - it->mCount += item.mCount; + itemStack.mCount += item.mCount; found = true; break; } @@ -100,15 +99,15 @@ namespace MWGui void TradeItemModel::adjustEncumbrance(float &encumbrance) { - for (std::vector::iterator it = mBorrowedToUs.begin(); it != mBorrowedToUs.end(); ++it) + for (ItemStack& itemStack : mBorrowedToUs) { - MWWorld::Ptr item = it->mBase; - encumbrance += item.getClass().getWeight(item) * it->mCount; + MWWorld::Ptr& item = itemStack.mBase; + encumbrance += item.getClass().getWeight(item) * itemStack.mCount; } - for (std::vector::iterator it = mBorrowedFromUs.begin(); it != mBorrowedFromUs.end(); ++it) + for (ItemStack& itemStack : mBorrowedFromUs) { - MWWorld::Ptr item = it->mBase; - encumbrance -= item.getClass().getWeight(item) * it->mCount; + MWWorld::Ptr& item = itemStack.mBase; + encumbrance -= item.getClass().getWeight(item) * itemStack.mCount; } encumbrance = std::max(0.f, encumbrance); } @@ -126,15 +125,14 @@ namespace MWGui void TradeItemModel::transferItems() { - std::vector::iterator it = mBorrowedToUs.begin(); - for (; it != mBorrowedToUs.end(); ++it) + for (ItemStack& itemStack : mBorrowedToUs) { // get index in the source model - ItemModel* sourceModel = it->mCreator; + ItemModel* sourceModel = itemStack.mCreator; size_t i=0; for (; igetItemCount(); ++i) { - if (it->mBase == sourceModel->getItem(i).mBase) + if (itemStack.mBase == sourceModel->getItem(i).mBase) break; } if (i == sourceModel->getItemCount()) @@ -142,9 +140,9 @@ namespace MWGui const ItemStack& item = sourceModel->getItem(i); // copy the borrowed items to our model - copyItem(item, it->mCount); + copyItem(item, itemStack.mCount); // then remove them from the source model - sourceModel->removeItem(item, it->mCount); + sourceModel->removeItem(item, itemStack.mCount); } mBorrowedToUs.clear(); mBorrowedFromUs.clear(); @@ -189,14 +187,13 @@ namespace MWGui } // don't show items that we borrowed to someone else - std::vector::iterator it = mBorrowedFromUs.begin(); - for (; it != mBorrowedFromUs.end(); ++it) + for (ItemStack& itemStack : mBorrowedFromUs) { - if (it->mBase == item.mBase) + if (itemStack.mBase == item.mBase) { - if (item.mCount < it->mCount) + if (item.mCount < itemStack.mCount) throw std::runtime_error("Lent more items than present"); - item.mCount -= it->mCount; + item.mCount -= itemStack.mCount; } } @@ -205,12 +202,10 @@ namespace MWGui } // add items borrowed to us - std::vector::iterator it = mBorrowedToUs.begin(); - for (; it != mBorrowedToUs.end(); ++it) + for (ItemStack& itemStack : mBorrowedToUs) { - ItemStack item = *it; - item.mType = ItemStack::Type_Barter; - mItems.push_back(item); + itemStack.mType = ItemStack::Type_Barter; + mItems.push_back(itemStack); } } diff --git a/apps/openmw/mwgui/tradewindow.cpp b/apps/openmw/mwgui/tradewindow.cpp index 90698dfc4..b197ecef6 100644 --- a/apps/openmw/mwgui/tradewindow.cpp +++ b/apps/openmw/mwgui/tradewindow.cpp @@ -106,9 +106,9 @@ namespace MWGui // Also restock any containers owned by this merchant, which are also available to buy in the trade window std::vector itemSources; MWBase::Environment::get().getWorld()->getContainersOwnedBy(mPtr, itemSources); - for (std::vector::iterator it = itemSources.begin(); it != itemSources.end(); ++it) + for (MWWorld::Ptr& source : itemSources) { - it->getClass().restock(*it); + source.getClass().restock(source); } } @@ -306,16 +306,15 @@ namespace MWGui } // check if the player is attempting to sell back an item stolen from this actor - for (std::vector::iterator it = merchantBought.begin(); it != merchantBought.end(); ++it) + for (ItemStack& itemStack : merchantBought) { - if (MWBase::Environment::get().getMechanicsManager()->isItemStolenFrom(it->mBase.getCellRef().getRefId(), mPtr)) + if (MWBase::Environment::get().getMechanicsManager()->isItemStolenFrom(itemStack.mBase.getCellRef().getRefId(), mPtr)) { std::string msg = gmst.find("sNotifyMessage49")->mValue.getString(); - if (msg.find("%s") != std::string::npos) - msg.replace(msg.find("%s"), 2, it->mBase.getClass().getName(it->mBase)); + Misc::StringUtils::replace(msg, "%s", itemStack.mBase.getClass().getName(itemStack.mBase).c_str(), 2); MWBase::Environment::get().getWindowManager()->messageBox(msg); - MWBase::Environment::get().getMechanicsManager()->confiscateStolenItemToOwner(player, it->mBase, mPtr, it->mCount); + MWBase::Environment::get().getMechanicsManager()->confiscateStolenItemToOwner(player, itemStack.mBase, mPtr, itemStack.mCount); onCancelButtonClicked(mCancelButton); MWBase::Environment::get().getWindowManager()->exitCurrentGuiMode(); @@ -470,15 +469,15 @@ namespace MWGui int merchantOffer = 0; std::vector playerBorrowed = playerTradeModel->getItemsBorrowedToUs(); - for (std::vector::const_iterator it = playerBorrowed.begin(); it != playerBorrowed.end(); ++it) + for (const ItemStack& itemStack : playerBorrowed) { - merchantOffer -= MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr, getEffectiveValue(it->mBase, it->mCount), true); + merchantOffer -= MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr, getEffectiveValue(itemStack.mBase, itemStack.mCount), true); } std::vector merchantBorrowed = mTradeModel->getItemsBorrowedToUs(); - for (std::vector::const_iterator it = merchantBorrowed.begin(); it != merchantBorrowed.end(); ++it) + for (const ItemStack& itemStack : merchantBorrowed) { - merchantOffer += MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr, getEffectiveValue(it->mBase, it->mCount), false); + merchantOffer += MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr, getEffectiveValue(itemStack.mBase, itemStack.mCount), false); } int diff = merchantOffer - mCurrentMerchantOffer; diff --git a/apps/openmw/mwgui/trainingwindow.cpp b/apps/openmw/mwgui/trainingwindow.cpp index 60e6584c7..acc2ef72a 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); MWBase::Environment::get().getWorld ()->advanceTime (2); setVisible(false); diff --git a/apps/openmw/mwgui/travelwindow.cpp b/apps/openmw/mwgui/travelwindow.cpp index 1bcbc2d12..0ce864556 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); MWBase::Environment::get().getWorld()->advanceTime(hours); } diff --git a/apps/openmw/mwgui/videowidget.hpp b/apps/openmw/mwgui/videowidget.hpp index 20af579a2..dadd1471a 100644 --- a/apps/openmw/mwgui/videowidget.hpp +++ b/apps/openmw/mwgui/videowidget.hpp @@ -1,6 +1,8 @@ #ifndef OPENMW_MWGUI_VIDEOWIDGET_H #define OPENMW_MWGUI_VIDEOWIDGET_H +#include + #include namespace Video diff --git a/apps/openmw/mwgui/waitdialog.cpp b/apps/openmw/mwgui/waitdialog.cpp index 037fcb19b..48384d4b5 100644 --- a/apps/openmw/mwgui/waitdialog.cpp +++ b/apps/openmw/mwgui/waitdialog.cpp @@ -232,7 +232,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); MWBase::Environment::get().getWorld()->advanceTime(1); MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); diff --git a/apps/openmw/mwgui/widgets.cpp b/apps/openmw/mwgui/widgets.cpp index c3bc1ec19..093824d4b 100644 --- a/apps/openmw/mwgui/widgets.cpp +++ b/apps/openmw/mwgui/widgets.cpp @@ -41,7 +41,7 @@ namespace MWGui else if (skill < ESM::Skill::Length) setSkillId(static_cast(skill)); else - throw new std::runtime_error("Skill number out of range"); + throw std::runtime_error("Skill number out of range"); } void MWSkill::setSkillValue(const SkillValue& value) @@ -222,18 +222,17 @@ namespace MWGui const ESM::Spell *spell = store.get().search(mId); MYGUI_ASSERT(spell, "spell with id '" << mId << "' not found"); - std::vector::const_iterator end = spell->mEffects.mList.end(); - for (std::vector::const_iterator it = spell->mEffects.mList.begin(); it != end; ++it) + for (const ESM::ENAMstruct& effectInfo : spell->mEffects.mList) { MWSpellEffectPtr effect = creator->createWidget("MW_EffectImage", coord, MyGUI::Align::Default); SpellEffectParams params; - params.mEffectID = it->mEffectID; - params.mSkill = it->mSkill; - params.mAttribute = it->mAttribute; - params.mDuration = it->mDuration; - params.mMagnMin = it->mMagnMin; - params.mMagnMax = it->mMagnMax; - params.mRange = it->mRange; + params.mEffectID = effectInfo.mEffectID; + params.mSkill = effectInfo.mSkill; + params.mAttribute = effectInfo.mAttribute; + params.mDuration = effectInfo.mDuration; + params.mMagnMin = effectInfo.mMagnMin; + params.mMagnMax = effectInfo.mMagnMax; + params.mRange = effectInfo.mRange; params.mIsConstant = (flags & MWEffectList::EF_Constant) != 0; params.mNoTarget = (flags & MWEffectList::EF_NoTarget); effect->setSpellEffect(params); @@ -289,13 +288,12 @@ namespace MWGui MWSpellEffectPtr effect = nullptr; int maxwidth = coord.width; - for (SpellEffectList::iterator it=mEffectList.begin(); - it != mEffectList.end(); ++it) + for (auto& effectInfo : mEffectList) { effect = creator->createWidget("MW_EffectImage", coord, MyGUI::Align::Default); - it->mIsConstant = (flags & EF_Constant) || it->mIsConstant; - it->mNoTarget = (flags & EF_NoTarget) || it->mNoTarget; - effect->setSpellEffect(*it); + effectInfo.mIsConstant = (flags & EF_Constant) || effectInfo.mIsConstant; + effectInfo.mNoTarget = (flags & EF_NoTarget) || effectInfo.mNoTarget; + effect->setSpellEffect(effectInfo); effects.push_back(effect); if (effect->getRequestedWidth() > maxwidth) maxwidth = effect->getRequestedWidth(); @@ -304,9 +302,9 @@ namespace MWGui } // ... then adjust the size for all widgets - for (std::vector::iterator it = effects.begin(); it != effects.end(); ++it) + for (MyGUI::Widget* effectWidget : effects) { - effect = (*it)->castType(); + effect = effectWidget->castType(); bool needcenter = center && (maxwidth > effect->getRequestedWidth()); int diff = maxwidth - effect->getRequestedWidth(); if (needcenter) @@ -339,18 +337,17 @@ namespace MWGui SpellEffectList MWEffectList::effectListFromESM(const ESM::EffectList* effects) { SpellEffectList result; - std::vector::const_iterator end = effects->mList.end(); - for (std::vector::const_iterator it = effects->mList.begin(); it != end; ++it) + for (const ESM::ENAMstruct& effectInfo : effects->mList) { SpellEffectParams params; - params.mEffectID = it->mEffectID; - params.mSkill = it->mSkill; - params.mAttribute = it->mAttribute; - params.mDuration = it->mDuration; - params.mMagnMin = it->mMagnMin; - params.mMagnMax = it->mMagnMax; - params.mRange = it->mRange; - params.mArea = it->mArea; + params.mEffectID = effectInfo.mEffectID; + params.mSkill = effectInfo.mSkill; + params.mAttribute = effectInfo.mAttribute; + params.mDuration = effectInfo.mDuration; + params.mMagnMin = effectInfo.mMagnMin; + params.mMagnMax = effectInfo.mMagnMax; + params.mRange = effectInfo.mRange; + params.mArea = effectInfo.mArea; result.push_back(params); } return result; diff --git a/apps/openmw/mwgui/windowbase.cpp b/apps/openmw/mwgui/windowbase.cpp index 86c780325..727493892 100644 --- a/apps/openmw/mwgui/windowbase.cpp +++ b/apps/openmw/mwgui/windowbase.cpp @@ -3,8 +3,6 @@ #include #include -#include - #include "../mwbase/windowmanager.hpp" #include "../mwbase/environment.hpp" diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index dc82cad9c..abd483a72 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -1,13 +1,11 @@ #include "windowmanagerimp.hpp" #include -#include #include #include #include -#include #include #include #include @@ -38,7 +36,6 @@ #include #include -#include #include #include #include @@ -47,13 +44,12 @@ #include -#include - #include #include "../mwbase/inputmanager.hpp" #include "../mwbase/statemanager.hpp" #include "../mwbase/soundmanager.hpp" +#include "../mwbase/world.hpp" #include "../mwrender/vismask.hpp" @@ -674,9 +670,9 @@ namespace MWGui // Delete any dialogs which are no longer in use if (!mGarbageDialogs.empty()) { - for (std::vector::iterator it = mGarbageDialogs.begin(); it != mGarbageDialogs.end(); ++it) + for (Layout* widget : mGarbageDialogs) { - delete *it; + delete widget; } mGarbageDialogs.clear(); } @@ -1031,9 +1027,6 @@ namespace MWGui updateMap(); - if (!mMap->isVisible()) - mMap->onFrame(frameDuration); - mHud->onFrame(frameDuration); mDebugWindow->onFrame(frameDuration); @@ -1215,14 +1208,13 @@ namespace MWGui { mToolTips->setDelay(Settings::Manager::getFloat("tooltip delay", "GUI")); - for (Settings::CategorySettingVector::const_iterator it = changed.begin(); - it != changed.end(); ++it) + for (const auto& setting : changed) { - if (it->first == "HUD" && it->second == "crosshair") + if (setting.first == "HUD" && setting.second == "crosshair") mCrosshairEnabled = Settings::Manager::getBool ("crosshair", "HUD"); - else if (it->first == "GUI" && it->second == "subtitles") + else if (setting.first == "GUI" && setting.second == "subtitles") mSubtitlesEnabled = Settings::Manager::getBool ("subtitles", "GUI"); - else if (it->first == "GUI" && it->second == "menu transparency") + else if (setting.first == "GUI" && setting.second == "menu transparency") setMenuTransparency(Settings::Manager::getFloat("menu transparency", "GUI")); } } diff --git a/apps/openmw/mwgui/windowpinnablebase.cpp b/apps/openmw/mwgui/windowpinnablebase.cpp index 271d4e3a8..88e88b494 100644 --- a/apps/openmw/mwgui/windowpinnablebase.cpp +++ b/apps/openmw/mwgui/windowpinnablebase.cpp @@ -16,10 +16,10 @@ namespace MWGui MyGUI::Button* button = nullptr; MyGUI::VectorWidgetPtr widgets = window->getSkinWidgetsByName("Action"); - for (MyGUI::VectorWidgetPtr::iterator it = widgets.begin(); it != widgets.end(); ++it) + for (MyGUI::Widget* widget : widgets) { - if ((*it)->isUserString("HideWindowOnDoubleClick")) - button = (*it)->castType(); + if (widget->isUserString("HideWindowOnDoubleClick")) + button = widget->castType(); } if (button) diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index be1301aac..a5cb66220 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -55,20 +55,25 @@ namespace MWInput , mInvertX (Settings::Manager::getBool("invert x axis", "Input")) , mInvertY (Settings::Manager::getBool("invert y axis", "Input")) , mControlsDisabled(false) + , mJoystickEnabled (Settings::Manager::getBool("enable controller", "Input")) , mCameraSensitivity (Settings::Manager::getFloat("camera sensitivity", "Input")) , mCameraYMultiplier (Settings::Manager::getFloat("camera y multiplier", "Input")) , mPreviewPOVDelay(0.f) , mTimeIdle(0.f) , mMouseLookEnabled(false) , mGuiCursorEnabled(true) + , mGamepadGuiCursorEnabled(true) , mDetectingKeyboard(false) , mOverencumberedMessageDelay(0.f) , mGuiCursorX(0) , mGuiCursorY(0) , mMouseWheel(0) + , mGamepadZoom(0) , mUserFileExists(userFileExists) , mAlwaysRunActive(Settings::Manager::getBool("always run", "Input")) , mSneakToggles(Settings::Manager::getBool("toggle sneak", "Input")) + , mSneakToggleShortcutTimer(0.f) + , mSneakGamepadShortcut(false) , mSneaking(false) , mAttemptJump(false) , mInvUiScalingFactor(1.f) @@ -218,6 +223,96 @@ namespace MWInput MWBase::Environment::get().getWindowManager()->injectKeyPress(key, 0, false); } + bool InputManager::gamepadToGuiControl(const SDL_ControllerButtonEvent &arg, bool release=false) + { + // Presumption of GUI mode will be removed in the future. + // MyGUI KeyCodes *may* change. + // Currently button releases are ignored. + if (release) + return false; + + MyGUI::KeyCode key = MyGUI::KeyCode::None; + switch (arg.button) + { + case SDL_CONTROLLER_BUTTON_DPAD_UP: + key = MyGUI::KeyCode::ArrowUp; + break; + case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: + key = MyGUI::KeyCode::ArrowRight; + break; + case SDL_CONTROLLER_BUTTON_DPAD_DOWN: + key = MyGUI::KeyCode::ArrowDown; + break; + case SDL_CONTROLLER_BUTTON_DPAD_LEFT: + key = MyGUI::KeyCode::ArrowLeft; + break; + case SDL_CONTROLLER_BUTTON_A: + // If we are using the joystick as a GUI mouse, A must be handled via mouse. + if (mGamepadGuiCursorEnabled) + return false; + key = MyGUI::KeyCode::Space; + break; + case SDL_CONTROLLER_BUTTON_B: + if (MyGUI::InputManager::getInstance().isModalAny()) + MWBase::Environment::get().getWindowManager()->exitCurrentModal(); + else + MWBase::Environment::get().getWindowManager()->exitCurrentGuiMode(); + return true; + case SDL_CONTROLLER_BUTTON_X: + key = MyGUI::KeyCode::Semicolon; + break; + case SDL_CONTROLLER_BUTTON_Y: + key = MyGUI::KeyCode::Apostrophe; + break; + case SDL_CONTROLLER_BUTTON_LEFTSHOULDER: + key = MyGUI::KeyCode::Period; + break; + case SDL_CONTROLLER_BUTTON_RIGHTSHOULDER: + key = MyGUI::KeyCode::Slash; + break; + case SDL_CONTROLLER_BUTTON_LEFTSTICK: + mGamepadGuiCursorEnabled = !mGamepadGuiCursorEnabled; + MWBase::Environment::get().getWindowManager()->setCursorActive(mGamepadGuiCursorEnabled); + return true; + default: + return false; + } + + // Some keys will work even when Text Input windows/modals are in focus. + if (SDL_IsTextInputActive()) + return false; + + MWBase::Environment::get().getWindowManager()->injectKeyPress(key, 0, false); + return true; + } + + bool InputManager::gamepadToGuiControl(const SDL_ControllerAxisEvent &arg) + { + switch (arg.axis) + { + case SDL_CONTROLLER_AXIS_TRIGGERRIGHT: + if (arg.value == 32767) // Treat like a button. + MWBase::Environment::get().getWindowManager()->injectKeyPress(MyGUI::KeyCode::Minus, 0, false); + break; + case SDL_CONTROLLER_AXIS_TRIGGERLEFT: + if (arg.value == 32767) // Treat like a button. + MWBase::Environment::get().getWindowManager()->injectKeyPress(MyGUI::KeyCode::Equals, 0, false); + break; + case SDL_CONTROLLER_AXIS_LEFTX: + case SDL_CONTROLLER_AXIS_LEFTY: + case SDL_CONTROLLER_AXIS_RIGHTX: + case SDL_CONTROLLER_AXIS_RIGHTY: + // If we are using the joystick as a GUI mouse, process mouse movement elsewhere. + if (mGamepadGuiCursorEnabled) + return false; + break; + default: + return false; + } + + return true; + } + void InputManager::channelChanged(ICS::Channel* channel, float currentValue, float previousValue) { resetIdleTime (); @@ -252,11 +347,29 @@ namespace MWInput { if (action == A_Use) { - MWMechanics::DrawState_ state = MWBase::Environment::get().getWorld()->getPlayer().getDrawState(); - mPlayer->setAttackingOrSpell(currentValue != 0 && state != MWMechanics::DrawState_Nothing); + if(mJoystickLastUsed && currentValue == 1.0 && actionIsActive(A_ToggleWeapon)) + action = A_CycleWeaponRight; + + else if (mJoystickLastUsed && currentValue == 1.0 && actionIsActive(A_ToggleSpell)) + action = A_CycleSpellRight; + + else + { + MWMechanics::DrawState_ state = MWBase::Environment::get().getWorld()->getPlayer().getDrawState(); + mPlayer->setAttackingOrSpell(currentValue != 0 && state != MWMechanics::DrawState_Nothing); + } } else if (action == A_Jump) - mAttemptJump = (currentValue == 1.0 && previousValue == 0.0); + { + if(mJoystickLastUsed && currentValue == 1.0 && actionIsActive(A_ToggleWeapon)) + action = A_CycleWeaponLeft; + + else if (mJoystickLastUsed && currentValue == 1.0 && actionIsActive(A_ToggleSpell)) + action = A_CycleSpellLeft; + + else + mAttemptJump = (currentValue == 1.0 && previousValue == 0.0); + } } if (currentValue == 1) @@ -267,6 +380,9 @@ namespace MWInput case A_GameMenu: toggleMainMenu (); break; + case A_OptionsMenu: + toggleOptionsMenu(); + break; case A_Screenshot: screenshot(); break; @@ -284,7 +400,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 (); @@ -429,7 +546,7 @@ namespace MWInput updateCursorMode(); - if (mGuiCursorEnabled) + if (mGuiCursorEnabled && !(mJoystickLastUsed && !mGamepadGuiCursorEnabled)) { float xAxis = mInputBinder->getChannel(A_MoveLeftRight)->getValue()*2.0f-1.0f; float yAxis = mInputBinder->getChannel(A_MoveForwardBackward)->getValue()*2.0f-1.0f; @@ -493,28 +610,17 @@ namespace MWInput // joystick movement float xAxis = mInputBinder->getChannel(A_MoveLeftRight)->getValue(); float yAxis = mInputBinder->getChannel(A_MoveForwardBackward)->getValue(); - if (xAxis < .5) + if (xAxis != .5) { triedToMove = true; - mPlayer->setLeftRight (-1); - } - else if (xAxis > .5) - { - triedToMove = true; - mPlayer->setLeftRight (1); + mPlayer->setLeftRight((xAxis - 0.5f) * 2); } - if (yAxis < .5) + if (yAxis != .5) { triedToMove = true; mPlayer->setAutoMove (false); - mPlayer->setForwardBackward (1); - } - else if (yAxis > .5) - { - triedToMove = true; - mPlayer->setAutoMove (false); - mPlayer->setForwardBackward (-1); + mPlayer->setForwardBackward((yAxis - 0.5f) * 2 * -1); } else if(mPlayer->getAutoMove()) { @@ -559,7 +665,35 @@ namespace MWInput if (!mSneakToggles) { - mPlayer->setSneak(actionIsActive(A_Sneak)); + if(mJoystickLastUsed) + { + if(actionIsActive(A_Sneak)) + { + if(mSneakToggleShortcutTimer) // New Sneak Button Press + { + if(mSneakToggleShortcutTimer <= 0.3f) + { + mSneakGamepadShortcut = true; + toggleSneaking(); + } + else + mSneakGamepadShortcut = false; + } + + if(!mSneaking) + toggleSneaking(); + mSneakToggleShortcutTimer = 0.f; + } + else + { + if(!mSneakGamepadShortcut && mSneaking) + toggleSneaking(); + if(mSneakToggleShortcutTimer <= 0.3f) + mSneakToggleShortcutTimer += dt; + } + } + else + mPlayer->setSneak(actionIsActive(A_Sneak)); } if (mAttemptJump && mControlSwitch["playerjumping"]) @@ -606,6 +740,13 @@ namespace MWInput MWBase::Environment::get().getWorld()->togglePOV(); } mPreviewPOVDelay = 0.f; + mGamepadZoom = 0; + } + + if(mGamepadZoom) + { + MWBase::Environment::get().getWorld()->changeVanityModeScale(mGamepadZoom); + MWBase::Environment::get().getWorld()->setCameraDistance(mGamepadZoom, true, true); } } } @@ -622,6 +763,8 @@ namespace MWInput updateIdleTime(dt); } } + else + mGamepadZoom = 0; mAttemptJump = false; // Can only jump on first frame input is on } @@ -636,7 +779,7 @@ namespace MWInput mMouseLookEnabled = !guiMode; if (guiMode) MWBase::Environment::get().getWindowManager()->showCrosshair(false); - MWBase::Environment::get().getWindowManager()->setCursorVisible(guiMode); + MWBase::Environment::get().getWindowManager()->setCursorVisible(guiMode && (mJoystickLastUsed && !mGamepadGuiCursorEnabled)); // if not in gui mode, the camera decides whether to show crosshair or not. } @@ -659,6 +802,9 @@ namespace MWInput if (it->first == "Input" && it->second == "grab cursor") mGrabCursor = Settings::Manager::getBool("grab cursor", "Input"); + if (it->first == "Input" && it->second == "enable controller") + mJoystickEnabled = Settings::Manager::getBool("enable controller", "Input"); + if (it->first == "Video" && ( it->second == "resolution x" || it->second == "resolution y" @@ -813,6 +959,8 @@ namespace MWInput if (mGuiCursorEnabled) { + if (!mGamepadGuiCursorEnabled) + mGamepadGuiCursorEnabled = true; // We keep track of our own mouse position, so that moving the mouse while in // game mode does not move the position of the GUI cursor mGuiCursorX = static_cast(arg.x) * mInvUiScalingFactor; @@ -858,33 +1006,37 @@ namespace MWInput void InputManager::buttonPressed(int deviceID, const SDL_ControllerButtonEvent &arg ) { - mJoystickLastUsed = true; - bool guiMode = false; + if (!mJoystickEnabled || mInputBinder->detectingBindingState()) + return; - if (arg.button == SDL_CONTROLLER_BUTTON_A || arg.button == SDL_CONTROLLER_BUTTON_B) // We'll pretend that A is left click and B is right click + mJoystickLastUsed = true; + if (MWBase::Environment::get().getWindowManager()->isGuiMode()) { - guiMode = MWBase::Environment::get().getWindowManager()->isGuiMode(); - if(!mInputBinder->detectingBindingState()) + if (gamepadToGuiControl(arg, false)) + return; + else if (mGamepadGuiCursorEnabled) { - guiMode = MyGUI::InputManager::getInstance().injectMousePress(static_cast(mGuiCursorX), static_cast(mGuiCursorY), - sdlButtonToMyGUI((arg.button == SDL_CONTROLLER_BUTTON_B) ? SDL_BUTTON_RIGHT : SDL_BUTTON_LEFT)) && guiMode; - if (MyGUI::InputManager::getInstance ().getMouseFocusWidget () != 0) + // Temporary mouse binding until keyboard controls are available: + if (arg.button == SDL_CONTROLLER_BUTTON_A) // We'll pretend that A is left click. { - MyGUI::Button* b = MyGUI::InputManager::getInstance ().getMouseFocusWidget ()->castType(false); - if (b && b->getEnabled()) + bool mousePressSuccess = MyGUI::InputManager::getInstance().injectMousePress(static_cast(mGuiCursorX), static_cast(mGuiCursorY), sdlButtonToMyGUI(SDL_BUTTON_LEFT)); + if (MyGUI::InputManager::getInstance().getMouseFocusWidget()) { - MWBase::Environment::get().getWindowManager()->playSound("Menu Click"); + MyGUI::Button* b = MyGUI::InputManager::getInstance().getMouseFocusWidget()->castType(false); + if (b && b->getEnabled()) + MWBase::Environment::get().getWindowManager()->playSound("Menu Click"); } + + setPlayerControlsEnabled(!mousePressSuccess); } } } - - setPlayerControlsEnabled(!guiMode); + else + setPlayerControlsEnabled(true); //esc, to leave initial movie screen OIS::KeyCode kc = mInputManager->sdl2OISKeyCode(SDLK_ESCAPE); - bool guiFocus = MyGUI::InputManager::getInstance().injectKeyPress(MyGUI::KeyCode::Enum(kc), 0); - setPlayerControlsEnabled(!guiFocus); + setPlayerControlsEnabled(!MyGUI::InputManager::getInstance().injectKeyPress(MyGUI::KeyCode::Enum(kc), 0)); if (!mControlsDisabled) mInputBinder->buttonPressed(deviceID, arg); @@ -892,31 +1044,69 @@ namespace MWInput void InputManager::buttonReleased(int deviceID, const SDL_ControllerButtonEvent &arg ) { - mJoystickLastUsed = true; if(mInputBinder->detectingBindingState()) - mInputBinder->buttonReleased(deviceID, arg); - else if(arg.button == SDL_CONTROLLER_BUTTON_A || arg.button == SDL_CONTROLLER_BUTTON_B) { - bool guiMode = MWBase::Environment::get().getWindowManager()->isGuiMode(); - guiMode = MyGUI::InputManager::getInstance().injectMouseRelease(static_cast(mGuiCursorX), static_cast(mGuiCursorY), sdlButtonToMyGUI((arg.button == SDL_CONTROLLER_BUTTON_B) ? SDL_BUTTON_RIGHT : SDL_BUTTON_LEFT)) && guiMode; - - if(mInputBinder->detectingBindingState()) return; // don't allow same mouseup to bind as initiated bind - - setPlayerControlsEnabled(!guiMode); mInputBinder->buttonReleased(deviceID, arg); + return; + } + if (!mJoystickEnabled || mControlsDisabled) + return; + + mJoystickLastUsed = true; + if (MWBase::Environment::get().getWindowManager()->isGuiMode()) + { + if (gamepadToGuiControl(arg, true)) + return; + else if (mGamepadGuiCursorEnabled) + { + // Temporary mouse binding until keyboard controls are available: + if (arg.button == SDL_CONTROLLER_BUTTON_A) // We'll pretend that A is left click. + { + bool mousePressSuccess = MyGUI::InputManager::getInstance().injectMouseRelease(static_cast(mGuiCursorX), static_cast(mGuiCursorY), sdlButtonToMyGUI(SDL_BUTTON_LEFT)); + if (mInputBinder->detectingBindingState()) // If the player just triggered binding, don't let button release bind. + return; + + setPlayerControlsEnabled(!mousePressSuccess); + } + } } else - mInputBinder->buttonReleased(deviceID, arg); + setPlayerControlsEnabled(true); - ///to escape initial movie + //esc, to leave initial movie screen OIS::KeyCode kc = mInputManager->sdl2OISKeyCode(SDLK_ESCAPE); setPlayerControlsEnabled(!MyGUI::InputManager::getInstance().injectKeyRelease(MyGUI::KeyCode::Enum(kc))); + + mInputBinder->buttonReleased(deviceID, arg); } void InputManager::axisMoved(int deviceID, const SDL_ControllerAxisEvent &arg ) { - if (!mControlsDisabled) - mInputBinder->axisMoved(deviceID, arg); + if(!mJoystickEnabled || mControlsDisabled) + return; + + mJoystickLastUsed = true; + if (MWBase::Environment::get().getWindowManager()->isGuiMode()) + { + gamepadToGuiControl(arg); + } + else + { + if(mPreviewPOVDelay == 1.f && arg.value) // Preview Mode Gamepad Zooming + { + if(arg.axis == SDL_CONTROLLER_AXIS_TRIGGERRIGHT) + { + mGamepadZoom = static_cast(arg.value / 10000 * 8.5f); + return; // Do not propogate event. + } + else if(arg.axis == SDL_CONTROLLER_AXIS_TRIGGERLEFT) + { + mGamepadZoom = static_cast(-(arg.value / 10000 * 8.5f)); + return; // Do not propogate event. + } + } + } + mInputBinder->axisMoved(deviceID, arg); } void InputManager::controllerAdded(int deviceID, const SDL_ControllerDeviceEvent &arg) @@ -952,19 +1142,38 @@ namespace MWInput void InputManager::toggleMainMenu() { - if (MyGUI::InputManager::getInstance().isModalAny()) { + if (MyGUI::InputManager::getInstance().isModalAny()) + { MWBase::Environment::get().getWindowManager()->exitCurrentModal(); return; } - if(!MWBase::Environment::get().getWindowManager()->isGuiMode()) //No open GUIs, open up the MainMenu + bool inGame = MWBase::Environment::get().getStateManager()->getState() != MWBase::StateManager::State_NoGame; + MWGui::GuiMode mode = MWBase::Environment::get().getWindowManager()->getMode(); + + if ((inGame && mode == MWGui::GM_MainMenu) || mode == MWGui::GM_Settings) + MWBase::Environment::get().getWindowManager()->popGuiMode(); + + if (inGame && mode != MWGui::GM_MainMenu) + MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_MainMenu); + } + + void InputManager::toggleOptionsMenu() + { + if (MyGUI::InputManager::getInstance().isModalAny()) { - MWBase::Environment::get().getWindowManager()->pushGuiMode (MWGui::GM_MainMenu); - } - else //Close current GUI - { - MWBase::Environment::get().getWindowManager()->exitCurrentGuiMode(); + MWBase::Environment::get().getWindowManager()->exitCurrentModal(); + return; } + + MWGui::GuiMode mode = MWBase::Environment::get().getWindowManager()->getMode(); + bool inGame = MWBase::Environment::get().getStateManager()->getState() != MWBase::StateManager::State_NoGame; + + if ((inGame && mode == MWGui::GM_MainMenu) || mode == MWGui::GM_Settings) + MWBase::Environment::get().getWindowManager()->popGuiMode(); + + if (inGame && mode != MWGui::GM_Settings) + MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Settings); } void InputManager::quickLoad() { @@ -1317,20 +1526,20 @@ namespace MWInput defaultButtonBindings[A_Activate] = SDL_CONTROLLER_BUTTON_A; defaultButtonBindings[A_ToggleWeapon] = SDL_CONTROLLER_BUTTON_X; - defaultButtonBindings[A_ToggleSpell] = SDL_CONTROLLER_BUTTON_LEFTSHOULDER; + defaultButtonBindings[A_ToggleSpell] = SDL_CONTROLLER_BUTTON_Y; //defaultButtonBindings[A_QuickButtonsMenu] = SDL_GetButtonFromScancode(SDL_SCANCODE_F1); // Need to implement, should be ToggleSpell(5) AND Wait(9) - defaultButtonBindings[A_Sneak] = SDL_CONTROLLER_BUTTON_RIGHTSTICK; - defaultButtonBindings[A_Jump] = SDL_CONTROLLER_BUTTON_Y; - defaultButtonBindings[A_Journal] = SDL_CONTROLLER_BUTTON_RIGHTSHOULDER; - defaultButtonBindings[A_Rest] = SDL_CONTROLLER_BUTTON_BACK; - defaultButtonBindings[A_TogglePOV] = SDL_CONTROLLER_BUTTON_LEFTSTICK; + defaultButtonBindings[A_Sneak] = SDL_CONTROLLER_BUTTON_LEFTSTICK; + defaultButtonBindings[A_Journal] = SDL_CONTROLLER_BUTTON_LEFTSHOULDER; + defaultButtonBindings[A_Rest] = SDL_CONTROLLER_BUTTON_RIGHTSHOULDER; + defaultButtonBindings[A_TogglePOV] = SDL_CONTROLLER_BUTTON_RIGHTSTICK; defaultButtonBindings[A_Inventory] = SDL_CONTROLLER_BUTTON_B; defaultButtonBindings[A_GameMenu] = SDL_CONTROLLER_BUTTON_START; + defaultButtonBindings[A_OptionsMenu] = SDL_CONTROLLER_BUTTON_BACK; defaultButtonBindings[A_QuickSave] = SDL_CONTROLLER_BUTTON_GUIDE; - defaultButtonBindings[A_QuickKey1] = SDL_CONTROLLER_BUTTON_DPAD_UP; - defaultButtonBindings[A_QuickKey2] = SDL_CONTROLLER_BUTTON_DPAD_LEFT; - defaultButtonBindings[A_QuickKey3] = SDL_CONTROLLER_BUTTON_DPAD_DOWN; - defaultButtonBindings[A_QuickKey4] = SDL_CONTROLLER_BUTTON_DPAD_RIGHT; + defaultButtonBindings[A_MoveForward] = SDL_CONTROLLER_BUTTON_DPAD_UP; + defaultButtonBindings[A_MoveLeft] = SDL_CONTROLLER_BUTTON_DPAD_LEFT; + defaultButtonBindings[A_MoveBackward] = SDL_CONTROLLER_BUTTON_DPAD_DOWN; + defaultButtonBindings[A_MoveRight] = SDL_CONTROLLER_BUTTON_DPAD_RIGHT; std::map defaultAxisBindings; defaultAxisBindings[A_MoveForwardBackward] = SDL_CONTROLLER_AXIS_LEFTY; @@ -1338,6 +1547,7 @@ namespace MWInput defaultAxisBindings[A_LookUpDown] = SDL_CONTROLLER_AXIS_RIGHTY; defaultAxisBindings[A_LookLeftRight] = SDL_CONTROLLER_AXIS_RIGHTX; defaultAxisBindings[A_Use] = SDL_CONTROLLER_AXIS_TRIGGERRIGHT; + defaultAxisBindings[A_Jump] = SDL_CONTROLLER_AXIS_TRIGGERLEFT; for (int i = 0; i < A_Last; i++) { @@ -1405,6 +1615,7 @@ namespace MWInput descriptions[A_Journal] = "sJournal"; descriptions[A_Rest] = "sRestKey"; descriptions[A_Inventory] = "sInventory"; + descriptions[A_OptionsMenu] = "sPreferences"; descriptions[A_TogglePOV] = "sTogglePOVCmd"; descriptions[A_QuickKeysMenu] = "sQuickMenu"; descriptions[A_QuickKey1] = "sQuick1Cmd"; @@ -1542,6 +1753,7 @@ namespace MWInput ret.push_back(A_Inventory); ret.push_back(A_Journal); ret.push_back(A_Rest); + ret.push_back(A_OptionsMenu); ret.push_back(A_Console); ret.push_back(A_QuickSave); ret.push_back(A_QuickLoad); @@ -1574,6 +1786,7 @@ namespace MWInput ret.push_back(A_Inventory); ret.push_back(A_Journal); ret.push_back(A_Rest); + ret.push_back(A_OptionsMenu); ret.push_back(A_QuickSave); ret.push_back(A_QuickLoad); ret.push_back(A_Screenshot); @@ -1621,6 +1834,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/mwinput/inputmanagerimp.hpp b/apps/openmw/mwinput/inputmanagerimp.hpp index 361babec4..8b3253dcd 100644 --- a/apps/openmw/mwinput/inputmanagerimp.hpp +++ b/apps/openmw/mwinput/inputmanagerimp.hpp @@ -179,6 +179,7 @@ namespace MWInput bool mInvertY; bool mControlsDisabled; + bool mJoystickEnabled; float mCameraSensitivity; float mCameraYMultiplier; @@ -187,6 +188,7 @@ namespace MWInput bool mMouseLookEnabled; bool mGuiCursorEnabled; + bool mGamepadGuiCursorEnabled; bool mDetectingKeyboard; @@ -195,9 +197,12 @@ namespace MWInput float mGuiCursorX; float mGuiCursorY; int mMouseWheel; + float mGamepadZoom; bool mUserFileExists; bool mAlwaysRunActive; bool mSneakToggles; + float mSneakToggleShortcutTimer; + bool mSneakGamepadShortcut; bool mSneaking; bool mAttemptJump; @@ -218,6 +223,9 @@ namespace MWInput void setPlayerControlsEnabled(bool enabled); void handleGuiArrowKey(int action); + // Return true if GUI consumes input. + bool gamepadToGuiControl(const SDL_ControllerButtonEvent &arg, bool release); + bool gamepadToGuiControl(const SDL_ControllerAxisEvent &arg); void updateCursorMode(); @@ -225,6 +233,7 @@ namespace MWInput private: void toggleMainMenu(); + void toggleOptionsMenu(); void toggleSpell(); void toggleWeapon(); void toggleInventory(); @@ -317,6 +326,8 @@ namespace MWInput A_MoveForwardBackward, A_MoveLeftRight, + A_OptionsMenu, + A_Last // Marker for the last item }; }; diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 769425053..b627bdafa 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -1,13 +1,11 @@ #include "actors.hpp" -#include -#include #include #include -#include #include #include +#include #include #include "../mwworld/esmstore.hpp" @@ -26,6 +24,8 @@ #include "../mwmechanics/aibreathe.hpp" +#include "../mwrender/vismask.hpp" + #include "spellcasting.hpp" #include "npcstats.hpp" #include "creaturestats.hpp" @@ -130,23 +130,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; @@ -348,6 +370,33 @@ namespace MWMechanics } } + void Actors::playIdleDialogue(const MWWorld::Ptr& actor) + { + if (!actor.getClass().isActor() || actor == getPlayer() || !MWBase::Environment::get().getSoundManager()->sayDone(actor)) + return; + + const CreatureStats &stats = actor.getClass().getCreatureStats(actor); + if (stats.getAiSetting(CreatureStats::AI_Hello).getModified() == 0) + return; + + const MWMechanics::AiSequence& seq = stats.getAiSequence(); + if (seq.isInCombat() || seq.hasPackage(AiPackage::TypeIdFollow) || seq.hasPackage(AiPackage::TypeIdEscort)) + return; + + const osg::Vec3f playerPos(getPlayer().getRefData().getPosition().asVec3()); + const osg::Vec3f actorPos(actor.getRefData().getPosition().asVec3()); + MWBase::World* world = MWBase::Environment::get().getWorld(); + if (world->isSwimming(actor) || (playerPos - actorPos).length2() >= 3000 * 3000) + return; + + // Our implementation is not FPS-dependent unlike Morrowind's so it needs to be recalibrated. + // We chose to use the chance MW would have when run at 60 FPS with the default value of the GMST. + const float delta = MWBase::Environment::get().getFrameDuration() * 6.f; + static const float fVoiceIdleOdds = world->getStore().get().find("fVoiceIdleOdds")->mValue.getFloat(); + if (Misc::Rng::rollProbability() * 10000.f < fVoiceIdleOdds * delta && world->getLOS(getPlayer(), actor)) + MWBase::Environment::get().getDialogueManager()->say(actor, "idle"); + } + void Actors::engageCombat (const MWWorld::Ptr& actor1, const MWWorld::Ptr& actor2, std::map >& cachedAllies, bool againstPlayer) { // No combat for totally static creatures @@ -507,7 +556,7 @@ namespace MWMechanics MagicEffects now = creatureStats.getSpells().getMagicEffects(); - if (creature.getTypeName()==typeid (ESM::NPC).name()) + if (creature.getClass().isNpc()) { MWWorld::InventoryStore& store = creature.getClass().getInventoryStore (creature); now += store.getMagicEffects(); @@ -541,7 +590,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()) @@ -555,12 +604,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. @@ -583,7 +656,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); } @@ -1167,8 +1240,46 @@ namespace MWMechanics if (!anim) return; mActors.insert(std::make_pair(ptr, new Actor(ptr, anim))); + + CharacterController* ctrl = mActors[ptr]->getCharacterController(); if (updateImmediately) - mActors[ptr]->getCharacterController()->update(0); + ctrl->update(0); + + // We should initially hide actors outside of processing range. + // Note: since we update player after other actors, distance will be incorrect during teleportation. + // Do not update visibility if player was teleported, so actors will be visible during teleportation frame. + if (MWBase::Environment::get().getWorld()->getPlayer().wasTeleported()) + return; + + updateVisibility(ptr, ctrl); + } + + void Actors::updateVisibility (const MWWorld::Ptr& ptr, CharacterController* ctrl) + { + MWWorld::Ptr player = MWMechanics::getPlayer(); + if (ptr == player) + return; + + const float dist = (player.getRefData().getPosition().asVec3() - ptr.getRefData().getPosition().asVec3()).length(); + if (dist > mActorsProcessingRange) + { + ptr.getRefData().getBaseNode()->setNodeMask(0); + return; + } + else + ptr.getRefData().getBaseNode()->setNodeMask(MWRender::Mask_Actor); + + // Fade away actors on large distance (>90% of actor's processing distance) + float visibilityRatio = 1.0; + float fadeStartDistance = mActorsProcessingRange*0.9f; + float fadeEndDistance = mActorsProcessingRange; + float fadeRatio = (dist - fadeStartDistance)/(fadeEndDistance - fadeStartDistance); + if (fadeRatio > 0) + visibilityRatio -= std::max(0.f, fadeRatio); + + visibilityRatio = std::min(1.f, visibilityRatio); + + ctrl->setVisibility(visibilityRatio); } void Actors::removeActor (const MWWorld::Ptr& ptr) @@ -1419,11 +1530,14 @@ namespace MWMechanics { CreatureStats &stats = iter->first.getClass().getCreatureStats(iter->first); if (isConscious(iter->first)) + { stats.getAiSequence().execute(iter->first, *ctrl, duration); + playIdleDialogue(iter->first); + } } } - if(iter->first.getTypeName() == typeid(ESM::NPC).name()) + if(iter->first.getClass().isNpc()) { updateDrowning(iter->first, duration, ctrl->isKnockedOut(), isPlayer); calculateNpcStatModifiers(iter->first, duration); @@ -1461,7 +1575,7 @@ namespace MWMechanics continue; } else if (!isPlayer) - iter->first.getRefData().getBaseNode()->setNodeMask(1<<3); + iter->first.getRefData().getBaseNode()->setNodeMask(MWRender::Mask_Actor); if (iter->first.getClass().getCreatureStats(iter->first).isParalyzed()) ctrl->skipAnim(); @@ -1477,17 +1591,7 @@ namespace MWMechanics world->setActorCollisionMode(iter->first, true, !iter->first.getClass().getCreatureStats(iter->first).isDeathAnimationFinished()); ctrl->update(duration); - // Fade away actors on large distance (>90% of actor's processing distance) - float visibilityRatio = 1.0; - float fadeStartDistance = mActorsProcessingRange*0.9f; - float fadeEndDistance = mActorsProcessingRange; - float fadeRatio = (dist - fadeStartDistance)/(fadeEndDistance - fadeStartDistance); - if (fadeRatio > 0) - visibilityRatio -= std::max(0.f, fadeRatio); - - visibilityRatio = std::min(1.f, visibilityRatio); - - ctrl->setVisibility(visibilityRatio); + updateVisibility(iter->first, ctrl); } if (playerCharacter) @@ -1516,72 +1620,7 @@ namespace MWMechanics } killDeadActors(); - - static float sneakTimer = 0.f; // times update of sneak icon - - // if player is in sneak state see if anyone detects him - if (playerCharacter && playerCharacter->isSneaking()) - { - static float sneakSkillTimer = 0.f; // times sneak skill progress from "avoid notice" - - const MWWorld::ESMStore& esmStore = MWBase::Environment::get().getWorld()->getStore(); - const int radius = esmStore.get().find("fSneakUseDist")->mValue.getInteger(); - - static float fSneakUseDelay = esmStore.get().find("fSneakUseDelay")->mValue.getFloat(); - - if (sneakTimer >= fSneakUseDelay) - sneakTimer = 0.f; - - if (sneakTimer == 0.f) - { - // Set when an NPC is within line of sight and distance, but is still unaware. Used for skill progress. - bool avoidedNotice = false; - - bool detected = false; - - for (PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) - { - MWWorld::Ptr observer = iter->first; - - if (iter->first == player) // not the player - continue; - - if (observer.getClass().getCreatureStats(observer).isDead()) - continue; - - // is the player in range and can they be detected - if ((observer.getRefData().getPosition().asVec3() - playerPos).length2() <= radius*radius - && MWBase::Environment::get().getWorld()->getLOS(player, observer)) - { - if (MWBase::Environment::get().getMechanicsManager()->awarenessCheck(player, observer)) - { - detected = true; - avoidedNotice = false; - MWBase::Environment::get().getWindowManager()->setSneakVisibility(false); - break; - } - else - avoidedNotice = true; - } - } - - if (sneakSkillTimer >= fSneakUseDelay) - sneakSkillTimer = 0.f; - - if (avoidedNotice && sneakSkillTimer == 0.f) - player.getClass().skillUsageSucceeded(player, ESM::Skill::Sneak, 0); - - if (!detected) - MWBase::Environment::get().getWindowManager()->setSneakVisibility(true); - } - sneakTimer += duration; - sneakSkillTimer += duration; - } - else - { - sneakTimer = 0.f; - MWBase::Environment::get().getWindowManager()->setSneakVisibility(false); - } + updateSneaking(playerCharacter, duration); } updateCombatMusic(); @@ -1692,9 +1731,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(); @@ -1704,7 +1743,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) @@ -1730,17 +1769,98 @@ namespace MWMechanics fastForwardAi(); } + void Actors::updateSneaking(CharacterController* ctrl, float duration) + { + static float sneakTimer = 0.f; // Times update of sneak icon + + if (!ctrl) + { + MWBase::Environment::get().getWindowManager()->setSneakVisibility(false); + return; + } + + MWWorld::Ptr player = getPlayer(); + + CreatureStats& stats = player.getClass().getCreatureStats(player); + MWBase::World* world = MWBase::Environment::get().getWorld(); + + bool sneaking = stats.getStance(MWMechanics::CreatureStats::Stance_Sneak); + bool inair = !world->isOnGround(player) && !world->isSwimming(player) && !world->isFlying(player); + sneaking = sneaking && (ctrl->isSneaking() || inair); + + if (!sneaking) + { + MWBase::Environment::get().getWindowManager()->setSneakVisibility(false); + return; + } + + static float sneakSkillTimer = 0.f; // Times sneak skill progress from "avoid notice" + + const MWWorld::Store& gmst = world->getStore().get(); + static const float fSneakUseDist = gmst.find("fSneakUseDist")->mValue.getFloat(); + static const float fSneakUseDelay = gmst.find("fSneakUseDelay")->mValue.getFloat(); + + if (sneakTimer >= fSneakUseDelay) + sneakTimer = 0.f; + + if (sneakTimer == 0.f) + { + // Set when an NPC is within line of sight and distance, but is still unaware. Used for skill progress. + bool avoidedNotice = false; + bool detected = false; + + std::vector observers; + osg::Vec3f position(player.getRefData().getPosition().asVec3()); + float radius = std::min(fSneakUseDist, mActorsProcessingRange); + getObjectsInRange(position, radius, observers); + + for (const MWWorld::Ptr &observer : observers) + { + if (observer == player || observer.getClass().getCreatureStats(observer).isDead()) + continue; + + if (world->getLOS(player, observer)) + { + if (MWBase::Environment::get().getMechanicsManager()->awarenessCheck(player, observer)) + { + detected = true; + avoidedNotice = false; + MWBase::Environment::get().getWindowManager()->setSneakVisibility(false); + break; + } + else + { + avoidedNotice = true; + } + } + } + + if (sneakSkillTimer >= fSneakUseDelay) + sneakSkillTimer = 0.f; + + if (avoidedNotice && sneakSkillTimer == 0.f) + player.getClass().skillUsageSucceeded(player, ESM::Skill::Sneak, 0); + + if (!detected) + MWBase::Environment::get().getWindowManager()->setSneakVisibility(true); + } + + sneakTimer += duration; + sneakSkillTimer += duration; + } + int Actors::getHoursToRest(const MWWorld::Ptr &ptr) const { float healthPerHour, magickaPerHour; 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 61879c432..07e60e1d1 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -5,10 +5,23 @@ #include #include #include +#include -#include "../mwbase/world.hpp" +namespace ESM +{ + class ESMReader; + class ESMWriter; +} -#include "movement.hpp" +namespace osg +{ + class Vec3f; +} + +namespace Loading +{ + class Listener; +} namespace MWWorld { @@ -19,6 +32,7 @@ namespace MWWorld namespace MWMechanics { class Actor; + class CharacterController; class CreatureStats; class Actors @@ -102,13 +116,18 @@ namespace MWMechanics */ void engageCombat(const MWWorld::Ptr& actor1, const MWWorld::Ptr& actor2, std::map >& cachedAllies, bool againstPlayer); + void playIdleDialogue(const MWWorld::Ptr& actor); + 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 restoreDynamicStats(const MWWorld::Ptr& actor, bool sleep); + void updateSneaking(CharacterController* ctrl, float duration); + ///< Update the sneaking indicator state according to the given player character controller. + + 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 @@ -169,6 +188,8 @@ namespace MWMechanics bool isAttackingOrSpell(const MWWorld::Ptr& ptr) const; private: + void updateVisibility (const MWWorld::Ptr& ptr, CharacterController* ctrl); + PtrActorMap mActors; float mTimerDisposeSummonsCorpses; float mActorsProcessingRange; diff --git a/apps/openmw/mwmechanics/actorutil.hpp b/apps/openmw/mwmechanics/actorutil.hpp index 510e41db3..3c6dc940c 100644 --- a/apps/openmw/mwmechanics/actorutil.hpp +++ b/apps/openmw/mwmechanics/actorutil.hpp @@ -1,7 +1,10 @@ #ifndef OPENMW_MWMECHANICS_ACTORUTIL_H #define OPENMW_MWMECHANICS_ACTORUTIL_H -#include "../mwworld/ptr.hpp" +namespace MWWorld +{ + class Ptr; +} namespace MWMechanics { diff --git a/apps/openmw/mwmechanics/aiactivate.cpp b/apps/openmw/mwmechanics/aiactivate.cpp index c4a8417db..e222c82bc 100644 --- a/apps/openmw/mwmechanics/aiactivate.cpp +++ b/apps/openmw/mwmechanics/aiactivate.cpp @@ -8,7 +8,6 @@ #include "../mwworld/class.hpp" #include "creaturestats.hpp" -#include "steering.hpp" #include "movement.hpp" namespace MWMechanics diff --git a/apps/openmw/mwmechanics/aiavoiddoor.cpp b/apps/openmw/mwmechanics/aiavoiddoor.cpp index e7d1ecee1..8fc35de49 100644 --- a/apps/openmw/mwmechanics/aiavoiddoor.cpp +++ b/apps/openmw/mwmechanics/aiavoiddoor.cpp @@ -12,7 +12,7 @@ #include "steering.hpp" MWMechanics::AiAvoidDoor::AiAvoidDoor(const MWWorld::ConstPtr& doorPtr) -: AiPackage(), mDuration(1), mDoorPtr(doorPtr), mAdjAngle(0) +: AiPackage(), mDuration(1), mDoorPtr(doorPtr), mLastPos(ESM::Position()), mAdjAngle(0) { } diff --git a/apps/openmw/mwmechanics/aicast.cpp b/apps/openmw/mwmechanics/aicast.cpp index 38dc2fa73..b7c9bac3b 100644 --- a/apps/openmw/mwmechanics/aicast.cpp +++ b/apps/openmw/mwmechanics/aicast.cpp @@ -2,12 +2,12 @@ #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" +#include "../mwbase/world.hpp" #include "../mwworld/class.hpp" #include "aicombataction.hpp" #include "creaturestats.hpp" -#include "spellcasting.hpp" #include "steering.hpp" MWMechanics::AiCast::AiCast(const std::string& targetId, const std::string& spellId, bool manualSpell) diff --git a/apps/openmw/mwmechanics/aicast.hpp b/apps/openmw/mwmechanics/aicast.hpp index 70a8a6bbd..7128fe7a2 100644 --- a/apps/openmw/mwmechanics/aicast.hpp +++ b/apps/openmw/mwmechanics/aicast.hpp @@ -1,10 +1,13 @@ #ifndef GAME_MWMECHANICS_AICAST_H #define GAME_MWMECHANICS_AICAST_H -#include "../mwbase/world.hpp" - #include "aipackage.hpp" +namespace MWWorld +{ + class Ptr; +} + namespace MWMechanics { /// AiPackage which makes an actor to cast given spell. diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index ee7b4369e..32b3f9f2b 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -15,15 +15,12 @@ #include "../mwbase/dialoguemanager.hpp" #include "../mwbase/mechanicsmanager.hpp" -#include "../mwrender/animation.hpp" - #include "pathgrid.hpp" #include "creaturestats.hpp" #include "steering.hpp" #include "movement.hpp" #include "character.hpp" #include "aicombataction.hpp" -#include "combat.hpp" #include "coordinateconverter.hpp" #include "actorutil.hpp" @@ -555,13 +552,13 @@ namespace MWMechanics if (actor.getClass().isNpc()) { baseDelay = store.get().find("fCombatDelayNPC")->mValue.getFloat(); + } - //say a provoking combat phrase - int chance = store.get().find("iVoiceAttackOdds")->mValue.getInteger(); - if (Misc::Rng::roll0to99() < chance) - { - MWBase::Environment::get().getDialogueManager()->say(actor, "attack"); - } + // Say a provoking combat phrase + const int iVoiceAttackOdds = store.get().find("iVoiceAttackOdds")->mValue.getInteger(); + if (Misc::Rng::roll0to99() < iVoiceAttackOdds) + { + MWBase::Environment::get().getDialogueManager()->say(actor, "attack"); } mAttackCooldown = std::min(baseDelay + 0.01 * Misc::Rng::roll0to99(), baseDelay + 0.9); } diff --git a/apps/openmw/mwmechanics/aiescort.cpp b/apps/openmw/mwmechanics/aiescort.cpp index 3d018d780..03951d279 100644 --- a/apps/openmw/mwmechanics/aiescort.cpp +++ b/apps/openmw/mwmechanics/aiescort.cpp @@ -11,7 +11,6 @@ #include "../mwworld/cellstore.hpp" #include "creaturestats.hpp" -#include "steering.hpp" #include "movement.hpp" /* diff --git a/apps/openmw/mwmechanics/aiescort.hpp b/apps/openmw/mwmechanics/aiescort.hpp index 82dba960e..56c9b1c85 100644 --- a/apps/openmw/mwmechanics/aiescort.hpp +++ b/apps/openmw/mwmechanics/aiescort.hpp @@ -5,8 +5,6 @@ #include -#include "pathfinding.hpp" - namespace ESM { namespace AiSequence diff --git a/apps/openmw/mwmechanics/aiface.cpp b/apps/openmw/mwmechanics/aiface.cpp index 7d15cabf2..b99a8c1f4 100644 --- a/apps/openmw/mwmechanics/aiface.cpp +++ b/apps/openmw/mwmechanics/aiface.cpp @@ -1,6 +1,6 @@ #include "aiface.hpp" -#include "../mwbase/world.hpp" +#include "../mwworld/ptr.hpp" #include "steering.hpp" diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 4646875fd..90da9d160 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -1,7 +1,5 @@ #include "aipackage.hpp" -#include - #include #include #include @@ -137,9 +135,9 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& { if (wasShortcutting || doesPathNeedRecalc(dest, actor.getCell())) // if need to rebuild path { - const osg::Vec3f playerHalfExtents = world->getHalfExtents(getPlayer()); // Using player half extents for better performance + const auto pathfindingHalfExtents = world->getPathfindingHalfExtents(actor); mPathFinder.buildPath(actor, position, dest, actor.getCell(), getPathGridGraph(actor.getCell()), - playerHalfExtents, getNavigatorFlags(actor)); + pathfindingHalfExtents, getNavigatorFlags(actor)); mRotateOnTheRunChecks = 3; // give priority to go directly on target if there is minimal opportunity @@ -169,7 +167,9 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& mTimer = 0; } - const float pointTolerance = std::min(actor.getClass().getSpeed(actor), DEFAULT_TOLERANCE); + const float actorTolerance = 2 * actor.getClass().getSpeed(actor) * duration + + 1.2 * std::max(halfExtents.x(), halfExtents.y()); + const float pointTolerance = std::max(MIN_TOLERANCE, actorTolerance); mPathFinder.update(position, pointTolerance, DEFAULT_TOLERANCE); diff --git a/apps/openmw/mwmechanics/aipackage.hpp b/apps/openmw/mwmechanics/aipackage.hpp index 5be858b0b..d7287cd2f 100644 --- a/apps/openmw/mwmechanics/aipackage.hpp +++ b/apps/openmw/mwmechanics/aipackage.hpp @@ -7,8 +7,6 @@ #include "obstacle.hpp" #include "aistate.hpp" -#include - namespace MWWorld { class Ptr; diff --git a/apps/openmw/mwmechanics/aipursue.cpp b/apps/openmw/mwmechanics/aipursue.cpp index 4bec050d7..925f3adaa 100644 --- a/apps/openmw/mwmechanics/aipursue.cpp +++ b/apps/openmw/mwmechanics/aipursue.cpp @@ -1,9 +1,9 @@ #include "aipursue.hpp" #include -#include #include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" #include "../mwworld/class.hpp" #include "../mwworld/action.hpp" diff --git a/apps/openmw/mwmechanics/aipursue.hpp b/apps/openmw/mwmechanics/aipursue.hpp index 455b0a2fd..ea83a10e5 100644 --- a/apps/openmw/mwmechanics/aipursue.hpp +++ b/apps/openmw/mwmechanics/aipursue.hpp @@ -3,10 +3,6 @@ #include "aipackage.hpp" -#include "../mwbase/world.hpp" - -#include "pathfinding.hpp" - namespace ESM { namespace AiSequence diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index cf572abc0..13f34058c 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -5,7 +5,6 @@ #include #include -#include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "aipackage.hpp" @@ -470,7 +469,7 @@ void AiSequence::readState(const ESM::AiSequence::AiSequence &sequence) for (std::vector::const_iterator it = sequence.mPackages.begin(); it != sequence.mPackages.end(); ++it) { - std::unique_ptr package (nullptr); + std::unique_ptr package; switch (it->mType) { case ESM::AiSequence::Ai_Wander: diff --git a/apps/openmw/mwmechanics/aistate.hpp b/apps/openmw/mwmechanics/aistate.hpp index 40f4ab1d4..949fb74dd 100644 --- a/apps/openmw/mwmechanics/aistate.hpp +++ b/apps/openmw/mwmechanics/aistate.hpp @@ -2,7 +2,6 @@ #define AISTATE_H #include -#include namespace MWMechanics { diff --git a/apps/openmw/mwmechanics/aitravel.cpp b/apps/openmw/mwmechanics/aitravel.cpp index d87029809..a2876da4f 100644 --- a/apps/openmw/mwmechanics/aitravel.cpp +++ b/apps/openmw/mwmechanics/aitravel.cpp @@ -1,7 +1,6 @@ #include "aitravel.hpp" #include -#include #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" @@ -10,7 +9,6 @@ #include "../mwworld/class.hpp" #include "../mwworld/cellstore.hpp" -#include "steering.hpp" #include "movement.hpp" #include "creaturestats.hpp" diff --git a/apps/openmw/mwmechanics/aitravel.hpp b/apps/openmw/mwmechanics/aitravel.hpp index c297771d0..f9f9f43fc 100644 --- a/apps/openmw/mwmechanics/aitravel.hpp +++ b/apps/openmw/mwmechanics/aitravel.hpp @@ -3,8 +3,6 @@ #include "aipackage.hpp" -#include "pathfinding.hpp" - namespace ESM { namespace AiSequence diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index d2a57c354..db2057df3 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -8,12 +8,13 @@ #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/dialoguemanager.hpp" -#include "../mwbase/soundmanager.hpp" #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/cellstore.hpp" +#include "../mwphysics/collisiontype.hpp" + #include "pathgrid.hpp" #include "creaturestats.hpp" #include "steering.hpp" @@ -46,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)), @@ -156,9 +167,9 @@ namespace MWMechanics } else { - const osg::Vec3f playerHalfExtents = MWBase::Environment::get().getWorld()->getHalfExtents(getPlayer()); // Using player half extents for better performance + const osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getPathfindingHalfExtents(actor); mPathFinder.buildPath(actor, pos.asVec3(), mDestination, actor.getCell(), - getPathGridGraph(actor.getCell()), playerHalfExtents, getNavigatorFlags(actor)); + getPathGridGraph(actor.getCell()), halfExtents, getNavigatorFlags(actor)); } if (mPathFinder.isPathConstructed()) @@ -167,8 +178,6 @@ namespace MWMechanics doPerFrameActionsForState(actor, duration, storage); - playIdleDialogueRandomly(actor); - float& lastReaction = storage.mReaction; lastReaction += duration; if (AI_REACTION_TIME <= lastReaction) @@ -301,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; @@ -312,23 +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))) - { - // Using player half extents for better performance - const osg::Vec3f playerHalfExtents = MWBase::Environment::get().getWorld()->getHalfExtents(getPlayer()); - mPathFinder.buildPath(actor, currentPosition, mDestination, actor.getCell(), - getPathGridGraph(actor.getCell()), playerHalfExtents, getNavigatorFlags(actor)); - mPathFinder.addPointToPath(mDestination); + if (!isWaterCreature && destinationIsAtWater(actor, mDestination)) + continue; - if (mPathFinder.isPathConstructed()) - { - storage.setState(AiWanderStorage::Wander_Walking, true); - mHasDestination = true; - mUsePathgrid = false; - } - return; + 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.buildPathByNavMesh(actor, currentPosition, mDestination, halfExtents, + getNavigatorFlags(actor)); + } + + if (mPathFinder.isPathConstructed()) + { + storage.setState(AiWanderStorage::Wander_Walking, true); + mHasDestination = true; + mUsePathgrid = false; + } + + break; } while (--attempts); } @@ -346,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) { @@ -483,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); @@ -492,36 +512,6 @@ namespace MWMechanics } } - void AiWander::playIdleDialogueRandomly(const MWWorld::Ptr& actor) - { - int hello = actor.getClass().getCreatureStats(actor).getAiSetting(CreatureStats::AI_Hello).getModified(); - if (hello > 0 && !MWBase::Environment::get().getWorld()->isSwimming(actor) - && MWBase::Environment::get().getSoundManager()->sayDone(actor)) - { - MWWorld::Ptr player = getPlayer(); - - static float fVoiceIdleOdds = MWBase::Environment::get().getWorld()->getStore() - .get().find("fVoiceIdleOdds")->mValue.getFloat(); - - float roll = Misc::Rng::rollProbability() * 10000.0f; - - // In vanilla MW the chance was FPS dependent, and did not allow proper changing of fVoiceIdleOdds - // due to the roll being an integer. - // Our implementation does not have these issues, so needs to be recalibrated. We chose to - // use the chance MW would have when run at 60 FPS with the default value of the GMST for calibration. - float x = fVoiceIdleOdds * 0.6f * (MWBase::Environment::get().getFrameDuration() / 0.1f); - - // Only say Idle voices when player is in LOS - // A bit counterintuitive, likely vanilla did this to reduce the appearance of - // voices going through walls? - const osg::Vec3f playerPos(player.getRefData().getPosition().asVec3()); - const osg::Vec3f actorPos(actor.getRefData().getPosition().asVec3()); - if (roll < x && (playerPos - actorPos).length2() < 3000 * 3000 // maybe should be fAudioVoiceDefaultMaxDistance*fAudioMaxDistanceMult instead - && MWBase::Environment::get().getWorld()->getLOS(player, actor)) - MWBase::Environment::get().getDialogueManager()->say(actor, "idle"); - } - } - void AiWander::playGreetingIfPlayerGetsTooClose(const MWWorld::Ptr& actor, AiWanderStorage& storage) { // Play a random voice greeting if the player gets too close diff --git a/apps/openmw/mwmechanics/aiwander.hpp b/apps/openmw/mwmechanics/aiwander.hpp index bba6f7113..d586bb0bc 100644 --- a/apps/openmw/mwmechanics/aiwander.hpp +++ b/apps/openmw/mwmechanics/aiwander.hpp @@ -138,7 +138,6 @@ namespace MWMechanics void setPathToAnAllowedNode(const MWWorld::Ptr& actor, AiWanderStorage& storage, const ESM::Position& actorPos); void playGreetingIfPlayerGetsTooClose(const MWWorld::Ptr& actor, AiWanderStorage& storage); void evadeObstacles(const MWWorld::Ptr& actor, AiWanderStorage& storage); - void playIdleDialogueRandomly(const MWWorld::Ptr& actor); void turnActorToFacePlayer(const osg::Vec3f& actorPosition, const osg::Vec3f& playerPosition, AiWanderStorage& storage); void doPerFrameActionsForState(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage); void onIdleStatePerFrameActions(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage); diff --git a/apps/openmw/mwmechanics/alchemy.cpp b/apps/openmw/mwmechanics/alchemy.cpp index facac4291..87ee47b49 100644 --- a/apps/openmw/mwmechanics/alchemy.cpp +++ b/apps/openmw/mwmechanics/alchemy.cpp @@ -1,7 +1,6 @@ #include "alchemy.hpp" #include -#include #include #include diff --git a/apps/openmw/mwmechanics/autocalcspell.hpp b/apps/openmw/mwmechanics/autocalcspell.hpp index 50be8a5d9..460713ae3 100644 --- a/apps/openmw/mwmechanics/autocalcspell.hpp +++ b/apps/openmw/mwmechanics/autocalcspell.hpp @@ -1,9 +1,14 @@ #ifndef OPENMW_AUTOCALCSPELL_H #define OPENMW_AUTOCALCSPELL_H -#include -#include -#include +#include +#include + +namespace ESM +{ + struct Spell; + struct Race; +} namespace MWMechanics { diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index cdeeece94..ffbf5dd09 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -917,7 +917,10 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim if(mWeaponType != WeapType_None && mWeaponType != WeapType_Spell && mWeaponType != WeapType_HandToHand) { mAnimation->showWeapons(true); - mAnimation->setWeaponGroup(mCurrentWeapon); + // Note: controllers for ranged weapon should use time for beginning of animation to play shooting properly, + // for other weapons they should use absolute time. Some mods rely on this behaviour (to rotate throwing projectiles, for example) + bool useRelativeDuration = mWeaponType == WeapType_BowAndArrow || mWeaponType == WeapType_Crossbow; + mAnimation->setWeaponGroup(mCurrentWeapon, useRelativeDuration); } mAnimation->showCarriedLeft(updateCarriedLeftVisible(mWeaponType)); @@ -1375,7 +1378,10 @@ bool CharacterController::updateWeaponState(CharacterState& idle) mAnimation->showCarriedLeft(updateCarriedLeftVisible(weaptype)); getWeaponGroup(weaptype, weapgroup); - mAnimation->setWeaponGroup(weapgroup); + // Note: controllers for ranged weapon should use time for beginning of animation to play shooting properly, + // for other weapons they should use absolute time. Some mods rely on this behaviour (to rotate throwing projectiles, for example) + bool useRelativeDuration = weaptype == WeapType_BowAndArrow || weaptype == WeapType_Crossbow; + mAnimation->setWeaponGroup(weapgroup, useRelativeDuration); if (!isStillWeapon) { @@ -1956,6 +1962,23 @@ void CharacterController::update(float duration, bool animationOnly) osg::Vec3f rot = cls.getRotationVector(mPtr); speed = cls.getSpeed(mPtr); + if(isPlayer) + { + // Joystick anologue movement. + float xAxis = std::abs(cls.getMovementSettings(mPtr).mPosition[0]); + float yAxis = std::abs(cls.getMovementSettings(mPtr).mPosition[1]); + float analogueMovement = ((xAxis > yAxis) ? xAxis : yAxis); + + // If Strafing, our max speed is slower so multiply by X axis instead. + if(std::abs(vec.x()/2.0f) > std::abs(vec.y())) + analogueMovement = xAxis; + + // Due to the half way split between walking/running, we multiply speed by 2 while walking, unless a keyboard was used. + if(!isrunning && !sneak && !flying && analogueMovement <= 0.5f) + speed *= 2; + + speed *= (analogueMovement); + } vec.x() *= speed; vec.y() *= speed; @@ -2531,6 +2554,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/combat.cpp b/apps/openmw/mwmechanics/combat.cpp index 41e2485ce..3f87363a3 100644 --- a/apps/openmw/mwmechanics/combat.cpp +++ b/apps/openmw/mwmechanics/combat.cpp @@ -148,30 +148,49 @@ namespace MWMechanics return false; } + bool isNormalWeapon(const MWWorld::Ptr &weapon) + { + if (weapon.isEmpty()) + return false; + + const int flags = weapon.get()->mBase->mData.mFlags; + bool isSilver = flags & ESM::Weapon::Silver; + bool isMagical = flags & ESM::Weapon::Magical; + bool isEnchanted = !weapon.getClass().getEnchantment(weapon).empty(); + + return !isSilver && !isMagical && (!isEnchanted || !Settings::Manager::getBool("enchanted weapons are magical", "Game")); + } + void resistNormalWeapon(const MWWorld::Ptr &actor, const MWWorld::Ptr& attacker, const MWWorld::Ptr &weapon, float &damage) { + if (damage == 0 || weapon.isEmpty() || !isNormalWeapon(weapon)) + return; + const MWMechanics::MagicEffects& effects = actor.getClass().getCreatureStats(actor).getMagicEffects(); - float resistance = std::min(100.f, effects.get(ESM::MagicEffect::ResistNormalWeapons).getMagnitude() - - effects.get(ESM::MagicEffect::WeaknessToNormalWeapons).getMagnitude()); + const float resistance = effects.get(ESM::MagicEffect::ResistNormalWeapons).getMagnitude() / 100.f; + const float weakness = effects.get(ESM::MagicEffect::WeaknessToNormalWeapons).getMagnitude() / 100.f; - float multiplier = 1.f - resistance / 100.f; - - if (!(weapon.get()->mBase->mData.mFlags & ESM::Weapon::Silver - || weapon.get()->mBase->mData.mFlags & ESM::Weapon::Magical)) - { - if (weapon.getClass().getEnchantment(weapon).empty() - || !Settings::Manager::getBool("enchanted weapons are magical", "Game")) - damage *= multiplier; - } - - if ((weapon.get()->mBase->mData.mFlags & ESM::Weapon::Silver) - && actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf()) - damage *= MWBase::Environment::get().getWorld()->getStore().get().find("fWereWolfSilverWeaponDamageMult")->mValue.getFloat(); + damage *= 1.f - std::min(1.f, resistance-weakness); if (damage == 0 && attacker == getPlayer()) MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicTargetResistsWeapons}"); } + void applyWerewolfDamageMult(const MWWorld::Ptr &actor, const MWWorld::Ptr &weapon, float &damage) + { + if (damage == 0 || weapon.isEmpty() || !actor.getClass().isNpc()) + return; + + const int flags = weapon.get()->mBase->mData.mFlags; + bool isSilver = flags & ESM::Weapon::Silver; + + if (isSilver && actor.getClass().getNpcStats(actor).isWerewolf()) + { + const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); + damage *= store.get().find("fWereWolfSilverWeaponDamageMult")->mValue.getFloat(); + } + } + void projectileHit(const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim, MWWorld::Ptr weapon, const MWWorld::Ptr& projectile, const osg::Vec3f& hitPosition, float attackStrength) { @@ -208,6 +227,9 @@ namespace MWMechanics damage += attack[0] + ((attack[1] - attack[0]) * attackStrength); adjustWeaponDamage(damage, weapon, attacker); + if (weapon == projectile || Settings::Manager::getBool("only appropriate ammunition bypasses resistance", "Game") || isNormalWeapon(weapon)) + resistNormalWeapon(victim, attacker, projectile, damage); + applyWerewolfDamageMult(victim, projectile, damage); if (attacker == getPlayer()) { diff --git a/apps/openmw/mwmechanics/combat.hpp b/apps/openmw/mwmechanics/combat.hpp index 8b93ca204..6be783219 100644 --- a/apps/openmw/mwmechanics/combat.hpp +++ b/apps/openmw/mwmechanics/combat.hpp @@ -1,7 +1,15 @@ #ifndef OPENMW_MECHANICS_COMBAT_H #define OPENMW_MECHANICS_COMBAT_H -#include "../mwworld/ptr.hpp" +namespace osg +{ + class Vec3f; +} + +namespace MWWorld +{ + class Ptr; +} namespace MWMechanics { @@ -12,8 +20,13 @@ bool applyOnStrikeEnchantment(const MWWorld::Ptr& attacker, const MWWorld::Ptr& /// @return can we block the attack? bool blockMeleeAttack (const MWWorld::Ptr& attacker, const MWWorld::Ptr& blocker, const MWWorld::Ptr& weapon, float damage, float attackStrength); +/// @return does normal weapon resistance and weakness apply to the weapon? +bool isNormalWeapon (const MWWorld::Ptr& weapon); + void resistNormalWeapon (const MWWorld::Ptr& actor, const MWWorld::Ptr& attacker, const MWWorld::Ptr& weapon, float& damage); +void applyWerewolfDamageMult (const MWWorld::Ptr& actor, const MWWorld::Ptr& weapon, float &damage); + /// @note for a thrown weapon, \a weapon == \a projectile, for bows/crossbows, \a projectile is the arrow/bolt /// @note \a victim may be empty (e.g. for a hit on terrain), a non-actor (environment objects) or an actor void projectileHit (const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim, MWWorld::Ptr weapon, const MWWorld::Ptr& projectile, diff --git a/apps/openmw/mwmechanics/disease.hpp b/apps/openmw/mwmechanics/disease.hpp index fec3bdd37..8bca9f9b5 100644 --- a/apps/openmw/mwmechanics/disease.hpp +++ b/apps/openmw/mwmechanics/disease.hpp @@ -60,8 +60,7 @@ namespace MWMechanics std::string msg = "sMagicContractDisease"; msg = MWBase::Environment::get().getWorld()->getStore().get().find(msg)->mValue.getString(); - if (msg.find("%s") != std::string::npos) - msg.replace(msg.find("%s"), 2, spell->mName); + Misc::StringUtils::replace(msg, "%s", spell->mName.c_str(), 2); MWBase::Environment::get().getWindowManager()->messageBox(msg); } } diff --git a/apps/openmw/mwmechanics/enchanting.cpp b/apps/openmw/mwmechanics/enchanting.cpp index e4bb0cf3f..20121c2d9 100644 --- a/apps/openmw/mwmechanics/enchanting.cpp +++ b/apps/openmw/mwmechanics/enchanting.cpp @@ -7,6 +7,8 @@ #include "../mwworld/containerstore.hpp" #include "../mwworld/esmstore.hpp" +#include "../mwbase/world.hpp" +#include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "creaturestats.hpp" diff --git a/apps/openmw/mwmechanics/enchanting.hpp b/apps/openmw/mwmechanics/enchanting.hpp index 8e3b00e5a..275147671 100644 --- a/apps/openmw/mwmechanics/enchanting.hpp +++ b/apps/openmw/mwmechanics/enchanting.hpp @@ -7,9 +7,6 @@ #include "../mwworld/ptr.hpp" -#include "../mwbase/world.hpp" -#include "../mwbase/environment.hpp" - namespace MWMechanics { class Enchanting diff --git a/apps/openmw/mwmechanics/magiceffects.cpp b/apps/openmw/mwmechanics/magiceffects.cpp index 021691d6a..1a1a44f63 100644 --- a/apps/openmw/mwmechanics/magiceffects.cpp +++ b/apps/openmw/mwmechanics/magiceffects.cpp @@ -1,7 +1,5 @@ #include "magiceffects.hpp" -#include - #include #include diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index b909d7df1..42a604118 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -21,7 +21,6 @@ #include "aicombat.hpp" #include "aipursue.hpp" -#include "aitravel.hpp" #include "spellcasting.hpp" #include "autocalcspell.hpp" #include "npcstats.hpp" @@ -291,7 +290,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); } @@ -479,17 +480,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 2dda376f5..a81a6bd75 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/npcstats.cpp b/apps/openmw/mwmechanics/npcstats.cpp index bdd4e6968..df61ee93b 100644 --- a/apps/openmw/mwmechanics/npcstats.cpp +++ b/apps/openmw/mwmechanics/npcstats.cpp @@ -2,14 +2,11 @@ #include -#include - #include #include #include #include -#include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" #include "../mwbase/environment.hpp" @@ -252,16 +249,14 @@ void MWMechanics::NpcStats::increaseSkill(int skillIndex, const ESM::Class &clas /// \todo check if character is the player, if levelling is ever implemented for NPCs MWBase::Environment::get().getWindowManager()->playSound("skillraise"); - std::stringstream message; + std::string message = MWBase::Environment::get().getWindowManager ()->getGameSettingString ("sNotifyMessage39", ""); + Misc::StringUtils::replace(message, "%s", ("#{" + ESM::Skill::sSkillNameIds[skillIndex] + "}").c_str(), 2); + Misc::StringUtils::replace(message, "%d", std::to_string(base).c_str(), 2); if (readBook) - message << std::string("#{sBookSkillMessage}\n"); - - message << boost::format(MWBase::Environment::get().getWindowManager ()->getGameSettingString ("sNotifyMessage39", "")) - % std::string("#{" + ESM::Skill::sSkillNameIds[skillIndex] + "}") - % static_cast (base); + message = "#{sBookSkillMessage}\n" + message; - MWBase::Environment::get().getWindowManager ()->messageBox(message.str(), MWGui::ShowInDialogueMode_Never); + MWBase::Environment::get().getWindowManager ()->messageBox(message, MWGui::ShowInDialogueMode_Never); if (mLevelProgress >= gmst.find("iLevelUpTotal")->mValue.getInteger()) { diff --git a/apps/openmw/mwmechanics/objects.cpp b/apps/openmw/mwmechanics/objects.cpp index 726508161..d4a393c72 100644 --- a/apps/openmw/mwmechanics/objects.cpp +++ b/apps/openmw/mwmechanics/objects.cpp @@ -7,6 +7,7 @@ #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" +#include "character.hpp" #include "movement.hpp" namespace MWMechanics diff --git a/apps/openmw/mwmechanics/objects.hpp b/apps/openmw/mwmechanics/objects.hpp index 81b95baf8..1bcf646a4 100644 --- a/apps/openmw/mwmechanics/objects.hpp +++ b/apps/openmw/mwmechanics/objects.hpp @@ -3,8 +3,12 @@ #include #include +#include -#include "character.hpp" +namespace osg +{ + class Vec3f; +} namespace MWWorld { @@ -14,6 +18,8 @@ namespace MWWorld namespace MWMechanics { + class CharacterController; + class Objects { typedef std::map PtrControllerMap; diff --git a/apps/openmw/mwmechanics/obstacle.cpp b/apps/openmw/mwmechanics/obstacle.cpp index 9562c74df..63167e302 100644 --- a/apps/openmw/mwmechanics/obstacle.cpp +++ b/apps/openmw/mwmechanics/obstacle.cpp @@ -1,9 +1,9 @@ #include "obstacle.hpp" -#include #include #include "../mwbase/world.hpp" +#include "../mwbase/environment.hpp" #include "../mwworld/class.hpp" #include "../mwworld/cellstore.hpp" @@ -123,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; + 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()); + } - float distSameSpot = mDistSameSpot * duration; - - 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 8c7d6fce9..db6b8d686 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -262,10 +262,18 @@ namespace MWMechanics if (mPath.empty()) return; - const auto tolerance = mPath.size() > 1 ? pointTolerance : destinationTolerance; - - if (sqrDistanceIgnoreZ(mPath.front(), position) < tolerance * tolerance) + while (mPath.size() > 1 && sqrDistanceIgnoreZ(mPath.front(), position) < pointTolerance * pointTolerance) mPath.pop_front(); + + if (mPath.size() == 1 && sqrDistanceIgnoreZ(mPath.front(), position) < destinationTolerance * destinationTolerance) + 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, @@ -279,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) @@ -286,7 +304,8 @@ namespace MWMechanics mPath.clear(); mCell = cell; - buildPathByNavigatorImpl(actor, startPoint, endPoint, halfExtents, flags, std::back_inserter(mPath)); + if (!actor.getClass().isPureWaterCreature(actor) && !actor.getClass().isPureFlyingCreature(actor)) + buildPathByNavigatorImpl(actor, startPoint, endPoint, halfExtents, flags, std::back_inserter(mPath)); if (mPath.empty()) buildPathByPathgridImpl(startPoint, endPoint, pathgridGraph, std::back_inserter(mPath)); @@ -300,8 +319,11 @@ namespace MWMechanics { try { - const auto navigator = MWBase::Environment::get().getWorld()->getNavigator(); - navigator->findPath(halfExtents, startPoint, endPoint, flags, out); + const auto world = MWBase::Environment::get().getWorld(); + const auto realHalfExtents = world->getHalfExtents(actor); // This may differ from halfExtents argument + const auto stepSize = 2 * std::max(realHalfExtents.x(), realHalfExtents.y()); + const auto navigator = world->getNavigator(); + navigator->findPath(halfExtents, stepSize, startPoint, endPoint, flags, out); } catch (const DetourNavigator::NavigatorException& exception) { diff --git a/apps/openmw/mwmechanics/pathfinding.hpp b/apps/openmw/mwmechanics/pathfinding.hpp index 567056fa5..1dc85c5e5 100644 --- a/apps/openmw/mwmechanics/pathfinding.hpp +++ b/apps/openmw/mwmechanics/pathfinding.hpp @@ -46,10 +46,10 @@ namespace MWMechanics } const float PATHFIND_Z_REACH = 50.0f; - //static const float sMaxSlope = 49.0f; // duplicate as in physicssystem // distance after which actor (failed previously to shortcut) will try again const float PATHFIND_SHORTCUT_RETRY_DIST = 300.0f; + const float MIN_TOLERANCE = 1.0f; const float DEFAULT_TOLERANCE = 32.0f; // cast up-down ray with some offset from actor position to check for pits/obstacles on the way to target; @@ -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/repair.cpp b/apps/openmw/mwmechanics/repair.cpp index 8c5e69896..28dfab0cd 100644 --- a/apps/openmw/mwmechanics/repair.cpp +++ b/apps/openmw/mwmechanics/repair.cpp @@ -1,12 +1,9 @@ #include "repair.hpp" -#include - #include #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" -#include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwworld/containerstore.hpp" @@ -86,8 +83,9 @@ void Repair::repair(const MWWorld::Ptr &itemToRepair) std::string message = MWBase::Environment::get().getWorld()->getStore().get() .find("sNotifyMessage51")->mValue.getString(); + Misc::StringUtils::replace(message, "%s", mTool.getClass().getName(mTool).c_str(), 2); - MWBase::Environment::get().getWindowManager()->messageBox((boost::format(message) % mTool.getClass().getName(mTool)).str()); + MWBase::Environment::get().getWindowManager()->messageBox(message); // try to find a new tool of the same ID for (MWWorld::ContainerStoreIterator iter (store.begin()); diff --git a/apps/openmw/mwmechanics/security.cpp b/apps/openmw/mwmechanics/security.cpp index 18d4ec2e5..f1aa6bf8e 100644 --- a/apps/openmw/mwmechanics/security.cpp +++ b/apps/openmw/mwmechanics/security.cpp @@ -8,7 +8,6 @@ #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" -#include "../mwbase/windowmanager.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "creaturestats.hpp" diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 9f272e1f3..7b406cf9a 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -3,8 +3,6 @@ #include #include -#include - #include #include @@ -23,7 +21,6 @@ #include "../mwworld/inventorystore.hpp" #include "../mwrender/animation.hpp" -#include "../mwrender/vismask.hpp" #include "npcstats.hpp" #include "actorutil.hpp" @@ -295,17 +292,6 @@ namespace MWMechanics return true; // must still apply to get visual effect and have target regard it as attack } break; - case ESM::MagicEffect::AlmsiviIntervention: - case ESM::MagicEffect::DivineIntervention: - case ESM::MagicEffect::Mark: - case ESM::MagicEffect::Recall: - if (!MWBase::Environment::get().getWorld()->isTeleportingEnabled()) - { - if (castByPlayer) - MWBase::Environment::get().getWindowManager()->messageBox("#{sTeleportDisabled}"); - return false; - } - break; case ESM::MagicEffect::WaterWalking: if (target.getClass().isPureWaterCreature(target) && MWBase::Environment::get().getWorld()->isSwimming(target)) return false; @@ -747,21 +733,19 @@ namespace MWMechanics else if (target.getClass().isActor() && target == getPlayer()) { MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(mCaster); + bool teleportingEnabled = MWBase::Environment::get().getWorld()->isTeleportingEnabled(); - if (effectId == ESM::MagicEffect::DivineIntervention) + if (effectId == ESM::MagicEffect::DivineIntervention || effectId == ESM::MagicEffect::AlmsiviIntervention) { - MWBase::Environment::get().getWorld()->teleportToClosestMarker(target, "divinemarker"); - anim->removeEffect(ESM::MagicEffect::DivineIntervention); - const ESM::Static* fx = MWBase::Environment::get().getWorld()->getStore().get() - .search("VFX_Summon_end"); - if (fx) - anim->addEffect("meshes\\" + fx->mModel, -1); - return true; - } - else if (effectId == ESM::MagicEffect::AlmsiviIntervention) - { - MWBase::Environment::get().getWorld()->teleportToClosestMarker(target, "templemarker"); - anim->removeEffect(ESM::MagicEffect::AlmsiviIntervention); + if (!teleportingEnabled) + { + if (caster == getPlayer()) + MWBase::Environment::get().getWindowManager()->messageBox("#{sTeleportDisabled}"); + return true; + } + std::string marker = (effectId == ESM::MagicEffect::DivineIntervention) ? "divinemarker" : "templemarker"; + MWBase::Environment::get().getWorld()->teleportToClosestMarker(target, marker); + anim->removeEffect(effectId); const ESM::Static* fx = MWBase::Environment::get().getWorld()->getStore().get() .search("VFX_Summon_end"); if (fx) @@ -770,12 +754,26 @@ namespace MWMechanics } else if (effectId == ESM::MagicEffect::Mark) { - MWBase::Environment::get().getWorld()->getPlayer().markPosition( - target.getCell(), target.getRefData().getPosition()); + if (teleportingEnabled) + { + MWBase::Environment::get().getWorld()->getPlayer().markPosition( + target.getCell(), target.getRefData().getPosition()); + } + else if (caster == getPlayer()) + { + MWBase::Environment::get().getWindowManager()->messageBox("#{sTeleportDisabled}"); + } return true; } else if (effectId == ESM::MagicEffect::Recall) { + if (!teleportingEnabled) + { + if (caster == getPlayer()) + MWBase::Environment::get().getWindowManager()->messageBox("#{sTeleportDisabled}"); + return true; + } + MWWorld::CellStore* markedCell = nullptr; ESM::Position markedPosition; @@ -785,7 +783,7 @@ namespace MWMechanics MWWorld::ActionTeleport action(markedCell->isExterior() ? "" : markedCell->getCell()->mName, markedPosition, false); action.execute(target); - anim->removeEffect(ESM::MagicEffect::Recall); + anim->removeEffect(effectId); } return true; } @@ -1015,7 +1013,7 @@ namespace MWMechanics { // "X has no effect on you" std::string message = MWBase::Environment::get().getWorld()->getStore().get().find("sNotifyMessage50")->mValue.getString(); - message = boost::str(boost::format(message) % ingredient->mName); + Misc::StringUtils::replace(message, "%s", ingredient->mName.c_str(), 2); MWBase::Environment::get().getWindowManager()->messageBox(message); return false; } diff --git a/apps/openmw/mwmechanics/spellpriority.cpp b/apps/openmw/mwmechanics/spellpriority.cpp index 3b242266c..6cda51ac8 100644 --- a/apps/openmw/mwmechanics/spellpriority.cpp +++ b/apps/openmw/mwmechanics/spellpriority.cpp @@ -3,6 +3,7 @@ #include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -11,12 +12,10 @@ #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/inventorystore.hpp" -#include "../mwworld/actionequip.hpp" #include "../mwworld/cellstore.hpp" #include "creaturestats.hpp" #include "spellcasting.hpp" -#include "combat.hpp" namespace { @@ -171,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/spellpriority.hpp b/apps/openmw/mwmechanics/spellpriority.hpp index 4e5ff5c71..0305f24b5 100644 --- a/apps/openmw/mwmechanics/spellpriority.hpp +++ b/apps/openmw/mwmechanics/spellpriority.hpp @@ -1,9 +1,17 @@ #ifndef OPENMW_SPELL_PRIORITY_H #define OPENMW_SPELL_PRIORITY_H -#include +namespace ESM +{ + struct Spell; + struct EffectList; + struct ENAMstruct; +} -#include "../mwworld/ptr.hpp" +namespace MWWorld +{ + class Ptr; +} namespace MWMechanics { diff --git a/apps/openmw/mwmechanics/spells.cpp b/apps/openmw/mwmechanics/spells.cpp index 0a11ed641..3214b3fc9 100644 --- a/apps/openmw/mwmechanics/spells.cpp +++ b/apps/openmw/mwmechanics/spells.cpp @@ -1,7 +1,5 @@ #include "spells.hpp" -#include - #include #include #include diff --git a/apps/openmw/mwmechanics/trading.hpp b/apps/openmw/mwmechanics/trading.hpp index e006370dd..e30b82f5e 100644 --- a/apps/openmw/mwmechanics/trading.hpp +++ b/apps/openmw/mwmechanics/trading.hpp @@ -1,7 +1,10 @@ #ifndef OPENMW_MECHANICS_TRADING_H #define OPENMW_MECHANICS_TRADING_H -#include "../mwworld/ptr.hpp" +namespace MWWorld +{ + class Ptr; +} namespace MWMechanics { diff --git a/apps/openmw/mwmechanics/weaponpriority.cpp b/apps/openmw/mwmechanics/weaponpriority.cpp index 55fb2eaf0..c02729341 100644 --- a/apps/openmw/mwmechanics/weaponpriority.cpp +++ b/apps/openmw/mwmechanics/weaponpriority.cpp @@ -1,7 +1,6 @@ #include "weaponpriority.hpp" #include -#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -78,7 +77,10 @@ namespace MWMechanics adjustWeaponDamage(rating, item, actor); if (weapon->mData.mType != ESM::Weapon::MarksmanBow && weapon->mData.mType != ESM::Weapon::MarksmanCrossbow) + { resistNormalWeapon(enemy, actor, item, rating); + applyWerewolfDamageMult(enemy, item, rating); + } else if (weapon->mData.mType == ESM::Weapon::MarksmanBow) { if (arrowRating <= 0.f) @@ -125,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/mwphysics/actor.cpp b/apps/openmw/mwphysics/actor.cpp index b55c20455..0f8814aca 100644 --- a/apps/openmw/mwphysics/actor.cpp +++ b/apps/openmw/mwphysics/actor.cpp @@ -7,10 +7,10 @@ #include #include #include +#include #include "../mwworld/class.hpp" -#include "convert.hpp" #include "collisiontype.hpp" namespace MWPhysics @@ -62,7 +62,7 @@ Actor::Actor(const MWWorld::Ptr& ptr, osg::ref_ptr } else { - mShape.reset(new btBoxShape(toBullet(mHalfExtents))); + mShape.reset(new btBoxShape(Misc::Convert::toBullet(mHalfExtents))); mRotationallyInvariant = false; } @@ -138,13 +138,13 @@ void Actor::updateCollisionObjectPosition() btTransform tr = mCollisionObject->getWorldTransform(); osg::Vec3f scaledTranslation = mRotation * osg::componentMultiply(mMeshTranslation, mScale); osg::Vec3f newPosition = scaledTranslation + mPosition; - tr.setOrigin(toBullet(newPosition)); + tr.setOrigin(Misc::Convert::toBullet(newPosition)); mCollisionObject->setWorldTransform(tr); } osg::Vec3f Actor::getCollisionObjectPosition() const { - return toOsg(mCollisionObject->getWorldTransform().getOrigin()); + return Misc::Convert::toOsg(mCollisionObject->getWorldTransform().getOrigin()); } void Actor::setPosition(const osg::Vec3f &position) @@ -169,7 +169,7 @@ void Actor::updateRotation () { btTransform tr = mCollisionObject->getWorldTransform(); mRotation = mPtr.getRefData().getBaseNode()->getAttitude(); - tr.setRotation(toBullet(mRotation)); + tr.setRotation(Misc::Convert::toBullet(mRotation)); mCollisionObject->setWorldTransform(tr); updateCollisionObjectPosition(); @@ -187,7 +187,7 @@ void Actor::updateScale() mPtr.getClass().adjustScale(mPtr, scaleVec, false); mScale = scaleVec; - mShape->setLocalScaling(toBullet(mScale)); + mShape->setLocalScaling(Misc::Convert::toBullet(mScale)); scaleVec = osg::Vec3f(scale,scale,scale); mPtr.getClass().adjustScale(mPtr, scaleVec, true); @@ -201,6 +201,11 @@ osg::Vec3f Actor::getHalfExtents() const return osg::componentMultiply(mHalfExtents, mScale); } +osg::Vec3f Actor::getOriginalHalfExtents() const +{ + return mHalfExtents; +} + osg::Vec3f Actor::getRenderingHalfExtents() const { return osg::componentMultiply(mHalfExtents, mRenderingScale); diff --git a/apps/openmw/mwphysics/actor.hpp b/apps/openmw/mwphysics/actor.hpp index 31dd22a22..8752f7fee 100644 --- a/apps/openmw/mwphysics/actor.hpp +++ b/apps/openmw/mwphysics/actor.hpp @@ -66,6 +66,11 @@ namespace MWPhysics */ osg::Vec3f getHalfExtents() const; + /** + * Returns the half extents of the collision body (not scaled) + */ + osg::Vec3f getOriginalHalfExtents() const; + /** * Returns the position of the collision body * @note The collision shape's origin is in its center, so the position returned can be described as center of the actor collision box in world space. diff --git a/apps/openmw/mwphysics/object.cpp b/apps/openmw/mwphysics/object.cpp index 2f2866821..950630141 100644 --- a/apps/openmw/mwphysics/object.cpp +++ b/apps/openmw/mwphysics/object.cpp @@ -1,12 +1,10 @@ #include "object.hpp" -#include "convert.hpp" #include #include #include #include - -#include +#include #include #include @@ -28,7 +26,7 @@ namespace MWPhysics mCollisionObject->setUserPointer(static_cast(this)); setScale(ptr.getCellRef().getScale()); - setRotation(toBullet(ptr.getRefData().getBaseNode()->getAttitude())); + setRotation(Misc::Convert::toBullet(ptr.getRefData().getBaseNode()->getAttitude())); const float* pos = ptr.getRefData().getPosition().pos; setOrigin(btVector3(pos[0], pos[1], pos[2])); } @@ -114,7 +112,7 @@ namespace MWPhysics matrix.orthoNormalize(matrix); btTransform transform; - transform.setOrigin(toBullet(matrix.getTrans()) * compound->getLocalScaling()); + transform.setOrigin(Misc::Convert::toBullet(matrix.getTrans()) * compound->getLocalScaling()); for (int i=0; i<3; ++i) for (int j=0; j<3; ++j) transform.getBasis()[i][j] = matrix(j,i); // NB column/row major difference diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 18fc06481..df6d299d0 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -1,15 +1,7 @@ #include "physicssystem.hpp" -#include -#include -#include -#include - -#include - #include -#include #include #include #include @@ -21,12 +13,6 @@ #include -#include -#include -#include -#include -#include - #include #include #include @@ -35,6 +21,7 @@ #include #include #include +#include #include // FindRecIndexVisitor @@ -50,14 +37,10 @@ #include "../mwrender/bulletdebugdraw.hpp" -#include "../mwbase/world.hpp" -#include "../mwbase/environment.hpp" - #include "../mwworld/class.hpp" #include "collisiontype.hpp" #include "actor.hpp" -#include "convert.hpp" #include "trace.h" #include "object.hpp" #include "heightfield.hpp" @@ -260,7 +243,7 @@ namespace MWPhysics // Check if we actually found a valid spawn point (use an infinitely thin ray this time). // Required for some broken door destinations in Morrowind.esm, where the spawn point // intersects with other geometry if the actor's base is taken into account - btVector3 from = toBullet(position); + btVector3 from = Misc::Convert::toBullet(position); btVector3 to = from - btVector3(0,0,maxHeight); btCollisionWorld::ClosestRayResultCallback resultCallback1(from, to); @@ -270,11 +253,11 @@ namespace MWPhysics collisionWorld->rayTest(from, to, resultCallback1); if (resultCallback1.hasHit() && - ( (toOsg(resultCallback1.m_hitPointWorld) - (tracer.mEndPos-offset)).length2() > 35*35 + ( (Misc::Convert::toOsg(resultCallback1.m_hitPointWorld) - (tracer.mEndPos-offset)).length2() > 35*35 || !isWalkableSlope(tracer.mPlaneNormal))) { actor->setOnSlope(!isWalkableSlope(resultCallback1.m_hitNormalWorld)); - return toOsg(resultCallback1.m_hitPointWorld) + osg::Vec3f(0.f, 0.f, sGroundOffset); + return Misc::Convert::toOsg(resultCallback1.m_hitPointWorld) + osg::Vec3f(0.f, 0.f, sGroundOffset); } else { @@ -713,7 +696,7 @@ namespace MWPhysics btCollisionObject object; object.setCollisionShape(&shape); - object.setWorldTransform(btTransform(toBullet(orient), toBullet(center))); + object.setWorldTransform(btTransform(Misc::Convert::toBullet(orient), Misc::Convert::toBullet(center))); const btCollisionObject* me = nullptr; std::vector targetCollisionObjects; @@ -724,15 +707,15 @@ namespace MWPhysics if (!targets.empty()) { - for (std::vector::const_iterator it = targets.begin(); it != targets.end(); ++it) + for (MWWorld::Ptr& target : targets) { - const Actor* physactor2 = getActor(*it); - if (physactor2) - targetCollisionObjects.push_back(physactor2->getCollisionObject()); + const Actor* targetActor = getActor(target); + if (targetActor) + targetCollisionObjects.push_back(targetActor->getCollisionObject()); } } - DeepestNotMeContactTestResultCallback resultCallback(me, targetCollisionObjects, toBullet(origin)); + DeepestNotMeContactTestResultCallback resultCallback(me, targetCollisionObjects, Misc::Convert::toBullet(origin)); resultCallback.m_collisionFilterGroup = CollisionType_Actor; resultCallback.m_collisionFilterMask = CollisionType_World | CollisionType_Door | CollisionType_HeightMap | CollisionType_Actor; mCollisionWorld->contactTest(&object, resultCallback); @@ -741,7 +724,7 @@ namespace MWPhysics { PtrHolder* holder = static_cast(resultCallback.mObject->getUserPointer()); if (holder) - return std::make_pair(holder->getPtr(), toOsg(resultCallback.mContactPoint)); + return std::make_pair(holder->getPtr(), Misc::Convert::toOsg(resultCallback.mContactPoint)); } return std::make_pair(MWWorld::Ptr(), osg::Vec3f()); } @@ -757,7 +740,7 @@ namespace MWPhysics btTransform rayFrom; rayFrom.setIdentity(); - rayFrom.setOrigin(toBullet(point)); + rayFrom.setOrigin(Misc::Convert::toBullet(point)); // target the collision object's world origin, this should be the center of the collision object btTransform rayTo; @@ -773,7 +756,7 @@ namespace MWPhysics return 0.f; } else - return (point - toOsg(cb.m_hitPointWorld)).length(); + return (point - Misc::Convert::toOsg(cb.m_hitPointWorld)).length(); } class ClosestNotMeRayResultCallback : public btCollisionWorld::ClosestRayResultCallback @@ -807,8 +790,8 @@ namespace MWPhysics PhysicsSystem::RayResult PhysicsSystem::castRay(const osg::Vec3f &from, const osg::Vec3f &to, const MWWorld::ConstPtr& ignore, std::vector targets, int mask, int group) const { - btVector3 btFrom = toBullet(from); - btVector3 btTo = toBullet(to); + btVector3 btFrom = Misc::Convert::toBullet(from); + btVector3 btTo = Misc::Convert::toBullet(to); const btCollisionObject* me = nullptr; std::vector targetCollisionObjects; @@ -828,9 +811,9 @@ namespace MWPhysics if (!targets.empty()) { - for (std::vector::const_iterator it = targets.begin(); it != targets.end(); ++it) + for (MWWorld::Ptr& target : targets) { - const Actor* actor = getActor(*it); + const Actor* actor = getActor(target); if (actor) targetCollisionObjects.push_back(actor->getCollisionObject()); } @@ -846,8 +829,8 @@ namespace MWPhysics result.mHit = resultCallback.hasHit(); if (resultCallback.hasHit()) { - result.mHitPos = toOsg(resultCallback.m_hitPointWorld); - result.mHitNormal = toOsg(resultCallback.m_hitNormalWorld); + result.mHitPos = Misc::Convert::toOsg(resultCallback.m_hitPointWorld); + result.mHitNormal = Misc::Convert::toOsg(resultCallback.m_hitNormalWorld); if (PtrHolder* ptrHolder = static_cast(resultCallback.m_collisionObject->getUserPointer())) result.mHitObject = ptrHolder->getPtr(); } @@ -856,15 +839,15 @@ namespace MWPhysics PhysicsSystem::RayResult PhysicsSystem::castSphere(const osg::Vec3f &from, const osg::Vec3f &to, float radius) { - btCollisionWorld::ClosestConvexResultCallback callback(toBullet(from), toBullet(to)); + btCollisionWorld::ClosestConvexResultCallback callback(Misc::Convert::toBullet(from), Misc::Convert::toBullet(to)); callback.m_collisionFilterGroup = 0xff; callback.m_collisionFilterMask = CollisionType_World|CollisionType_HeightMap|CollisionType_Door; btSphereShape shape(radius); const btQuaternion btrot = btQuaternion::getIdentity(); - btTransform from_ (btrot, toBullet(from)); - btTransform to_ (btrot, toBullet(to)); + btTransform from_ (btrot, Misc::Convert::toBullet(from)); + btTransform to_ (btrot, Misc::Convert::toBullet(to)); mCollisionWorld->convexSweepTest(&shape, from_, to_, callback); @@ -872,8 +855,8 @@ namespace MWPhysics result.mHit = callback.hasHit(); if (result.mHit) { - result.mHitPos = toOsg(callback.m_hitPointWorld); - result.mHitNormal = toOsg(callback.m_hitNormalWorld); + result.mHitPos = Misc::Convert::toOsg(callback.m_hitPointWorld); + result.mHitNormal = Misc::Convert::toOsg(callback.m_hitNormalWorld); } return result; } @@ -923,6 +906,14 @@ namespace MWPhysics return osg::Vec3f(); } + osg::Vec3f PhysicsSystem::getOriginalHalfExtents(const MWWorld::ConstPtr &actor) const + { + if (const Actor* physactor = getActor(actor)) + return physactor->getOriginalHalfExtents(); + else + return osg::Vec3f(); + } + osg::Vec3f PhysicsSystem::getRenderingHalfExtents(const MWWorld::ConstPtr &actor) const { const Actor* physactor = getActor(actor); @@ -1148,7 +1139,7 @@ namespace MWPhysics ObjectMap::iterator found = mObjects.find(ptr); if (found != mObjects.end()) { - found->second->setRotation(toBullet(ptr.getRefData().getBaseNode()->getAttitude())); + found->second->setRotation(Misc::Convert::toBullet(ptr.getRefData().getBaseNode()->getAttitude())); mCollisionWorld->updateSingleAabb(found->second->getCollisionObject()); return; } @@ -1169,7 +1160,7 @@ namespace MWPhysics ObjectMap::iterator found = mObjects.find(ptr); if (found != mObjects.end()) { - found->second->setOrigin(toBullet(ptr.getRefData().getPosition().asVec3())); + found->second->setOrigin(Misc::Convert::toBullet(ptr.getRefData().getPosition().asVec3())); mCollisionWorld->updateSingleAabb(found->second->getCollisionObject()); return; } @@ -1329,8 +1320,8 @@ namespace MWPhysics void PhysicsSystem::stepSimulation(float dt) { - for (std::set::iterator it = mAnimatedObjects.begin(); it != mAnimatedObjects.end(); ++it) - (*it)->animateCollisionShapes(mCollisionWorld); + for (Object* animatedObject : mAnimatedObjects) + animatedObject->animateCollisionShapes(mCollisionWorld); #ifndef BT_NO_PROFILE CProfileManager::Reset(); diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 76fbcf8c6..364a59ab1 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -136,6 +136,9 @@ namespace MWPhysics /// Get physical half extents (scaled) of the given actor. osg::Vec3f getHalfExtents(const MWWorld::ConstPtr& actor) const; + /// Get physical half extents (not scaled) of the given actor. + osg::Vec3f getOriginalHalfExtents(const MWWorld::ConstPtr& actor) const; + /// @see MWPhysics::Actor::getRenderingHalfExtents osg::Vec3f getRenderingHalfExtents(const MWWorld::ConstPtr& actor) const; diff --git a/apps/openmw/mwphysics/trace.cpp b/apps/openmw/mwphysics/trace.cpp index 204ec1467..57b0a8368 100644 --- a/apps/openmw/mwphysics/trace.cpp +++ b/apps/openmw/mwphysics/trace.cpp @@ -1,13 +1,12 @@ #include "trace.h" -#include +#include #include #include #include "collisiontype.hpp" #include "actor.hpp" -#include "convert.hpp" namespace MWPhysics { @@ -51,8 +50,8 @@ protected: void ActorTracer::doTrace(const btCollisionObject *actor, const osg::Vec3f& start, const osg::Vec3f& end, const btCollisionWorld* world) { - const btVector3 btstart = toBullet(start); - const btVector3 btend = toBullet(end); + const btVector3 btstart = Misc::Convert::toBullet(start); + const btVector3 btend = Misc::Convert::toBullet(end); const btTransform &trans = actor->getWorldTransform(); btTransform from(trans); @@ -77,7 +76,7 @@ void ActorTracer::doTrace(const btCollisionObject *actor, const osg::Vec3f& star mFraction = newTraceCallback.m_closestHitFraction; mPlaneNormal = osg::Vec3f(tracehitnormal.x(), tracehitnormal.y(), tracehitnormal.z()); mEndPos = (end-start)*mFraction + start; - mHitPoint = toOsg(newTraceCallback.m_hitPointWorld); + mHitPoint = Misc::Convert::toOsg(newTraceCallback.m_hitPointWorld); mHitObject = newTraceCallback.m_hitCollisionObject; } else diff --git a/apps/openmw/mwrender/actoranimation.cpp b/apps/openmw/mwrender/actoranimation.cpp index 9f2f961ec..42f108355 100644 --- a/apps/openmw/mwrender/actoranimation.cpp +++ b/apps/openmw/mwrender/actoranimation.cpp @@ -194,15 +194,16 @@ void ActorAnimation::injectWeaponBones() } } -// To make sure we do not run morph controllers for weapons, i.e. bows -class EmptyCallback : public osg::NodeCallback +void ActorAnimation::resetControllers(osg::Node* node) { - public: + if (node == nullptr) + return; - virtual void operator()(osg::Node* node, osg::NodeVisitor* nv) - { - } -}; + std::shared_ptr src; + src.reset(new NullAnimationTime); + SceneUtil::AssignControllerSourcesVisitor removeVisitor(src); + node->accept(removeVisitor); +} void ActorAnimation::updateHolsteredWeapon(bool showHolsteredWeapons) { @@ -230,7 +231,9 @@ void ActorAnimation::updateHolsteredWeapon(bool showHolsteredWeapons) if (mesh.empty() || boneName.empty()) return; - // If the scabbard is not found, use a weapon mesh as fallback + // If the scabbard is not found, use a weapon mesh as fallback. + // Note: it is unclear how to handle time for controllers attached to bodyparts, so disable them for now. + // We use the similar approach for other bodyparts. scabbardName = scabbardName.replace(scabbardName.size()-4, 4, "_sh.nif"); bool isEnchanted = !weapon->getClass().getEnchantment(*weapon).empty(); if(!mResourceSystem->getVFS()->exists(scabbardName)) @@ -240,7 +243,7 @@ void ActorAnimation::updateHolsteredWeapon(bool showHolsteredWeapons) osg::Vec4f glowColor = getEnchantmentColor(*weapon); mScabbard = getWeaponPart(mesh, boneName, isEnchanted, &glowColor); if (mScabbard) - mScabbard->getNode()->setUpdateCallback(new EmptyCallback); + resetControllers(mScabbard->getNode()); } return; @@ -265,7 +268,7 @@ void ActorAnimation::updateHolsteredWeapon(bool showHolsteredWeapons) if (!weaponNode->getNumChildren()) { osg::ref_ptr fallbackNode = mResourceSystem->getSceneManager()->getInstance(mesh, weaponNode); - fallbackNode->setUpdateCallback(new EmptyCallback); + resetControllers(fallbackNode); } if (isEnchanted) @@ -328,32 +331,32 @@ void ActorAnimation::updateQuiver() suitableAmmo = ammo->get()->mBase->mData.mType == ESM::Weapon::Arrow; } - if (ammoNode && suitableAmmo) + if (!suitableAmmo) + return; + + // We should not show more ammo than equipped and more than quiver mesh has + ammoCount = std::min(ammoCount, ammoNode->getNumChildren()); + + // Remove existing ammo nodes + for (unsigned int i=0; igetNumChildren(); ++i) { - // We should not show more ammo than equipped and more than quiver mesh has - ammoCount = std::min(ammoCount, ammoNode->getNumChildren()); + osg::ref_ptr arrowNode = ammoNode->getChild(i)->asGroup(); + if (!arrowNode->getNumChildren()) + continue; - // Remove existing ammo nodes - for (unsigned int i=0; igetNumChildren(); ++i) - { - osg::ref_ptr arrowNode = ammoNode->getChild(i)->asGroup(); - if (!arrowNode->getNumChildren()) - continue; + osg::ref_ptr arrowChildNode = arrowNode->getChild(0); + arrowNode->removeChild(arrowChildNode); + } - osg::ref_ptr arrowChildNode = arrowNode->getChild(0); - arrowNode->removeChild(arrowChildNode); - } - - // Add new ones - osg::Vec4f glowColor = getEnchantmentColor(*ammo); - std::string model = ammo->getClass().getModel(*ammo); - for (unsigned int i=0; i arrowNode = ammoNode->getChild(i)->asGroup(); - osg::ref_ptr arrow = mResourceSystem->getSceneManager()->getInstance(model, arrowNode); - if (!ammo->getClass().getEnchantment(*ammo).empty()) - addGlow(arrow, glowColor); - } + // Add new ones + osg::Vec4f glowColor = getEnchantmentColor(*ammo); + std::string model = ammo->getClass().getModel(*ammo); + for (unsigned int i=0; i arrowNode = ammoNode->getChild(i)->asGroup(); + osg::ref_ptr arrow = mResourceSystem->getSceneManager()->getInstance(model, arrowNode); + if (!ammo->getClass().getEnchantment(*ammo).empty()) + addGlow(arrow, glowColor); } } diff --git a/apps/openmw/mwrender/actoranimation.hpp b/apps/openmw/mwrender/actoranimation.hpp index 7a8acd3a8..2146a0260 100644 --- a/apps/openmw/mwrender/actoranimation.hpp +++ b/apps/openmw/mwrender/actoranimation.hpp @@ -59,6 +59,7 @@ class ActorAnimation : public Animation, public MWWorld::ContainerStoreListener private: void addHiddenItemLight(const MWWorld::ConstPtr& item, const ESM::Light* esmLight); void removeHiddenItemLight(const MWWorld::ConstPtr& item); + void resetControllers(osg::Node* node); typedef std::map > ItemLightMap; ItemLightMap mItemLights; diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 4b0eec7b9..8ec75b1ce 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -79,10 +79,9 @@ namespace void remove() { - for (std::vector >::iterator it = mToRemove.begin(); it != mToRemove.end(); ++it) + for (osg::Node* node : mToRemove) { // FIXME: a Drawable might have more than one parent - osg::Node* node = *it; if (node->getNumParents()) node->getParent(0)->removeChild(node); } @@ -843,9 +842,9 @@ namespace MWRender if (!mAccumRoot) { - NodeMap::const_iterator found = nodeMap.find("root bone"); + NodeMap::const_iterator found = nodeMap.find("bip01"); if (found == nodeMap.end()) - found = nodeMap.find("bip01"); + found = nodeMap.find("root bone"); if (found != nodeMap.end()) mAccumRoot = found->second; @@ -1436,7 +1435,7 @@ namespace MWRender return sceneMgr->createInstance(found->second); } else - return sceneMgr->createInstance(model); + return sceneMgr->getInstance(model); } void Animation::setObjectRoot(const std::string &model, bool forceskeleton, bool baseonly, bool isCreature) @@ -1799,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/animation.hpp b/apps/openmw/mwrender/animation.hpp index 153f2ead9..f638a4db7 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -457,7 +457,7 @@ public: virtual void showWeapons(bool showWeapon) {} virtual void showCarriedLeft(bool show) {} - virtual void setWeaponGroup(const std::string& group) {} + virtual void setWeaponGroup(const std::string& group, bool relativeDuration) {} virtual void setVampire(bool vampire) {} /// A value < 1 makes the animation translucent, 1.f = fully opaque void setAlpha(float alpha); diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index ce6a27389..a3679e844 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -136,6 +137,7 @@ namespace MWRender mCamera->attach(osg::Camera::COLOR_BUFFER, mTexture); mCamera->setName("CharacterPreview"); mCamera->setComputeNearFarMode(osg::Camera::COMPUTE_NEAR_FAR_USING_BOUNDING_VOLUMES); + mCamera->setCullMask(~(Mask_UpdateVisitor)); mCamera->setNodeMask(Mask_RenderToTexture); @@ -152,6 +154,8 @@ namespace MWRender defaultMat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 0.f)); stateset->setAttribute(defaultMat); + SceneUtil::ShadowManager::disableShadowsForStateSet(stateset); + // assign large value to effectively turn off fog // shaders don't respect glDisable(GL_FOG) osg::ref_ptr fog (new osg::Fog); diff --git a/apps/openmw/mwrender/creatureanimation.hpp b/apps/openmw/mwrender/creatureanimation.hpp index d0fd5bdb4..524272584 100644 --- a/apps/openmw/mwrender/creatureanimation.hpp +++ b/apps/openmw/mwrender/creatureanimation.hpp @@ -44,7 +44,7 @@ namespace MWRender virtual osg::Node* getWeaponNode(); virtual Resource::ResourceSystem* getResourceSystem(); virtual void showWeapon(bool show) { showWeapons(show); } - virtual void setWeaponGroup(const std::string& group) { mWeaponAnimationTime->setGroup(group); } + virtual void setWeaponGroup(const std::string& group, bool relativeDuration) { mWeaponAnimationTime->setGroup(group, relativeDuration); } virtual void addControllers(); diff --git a/apps/openmw/mwrender/globalmap.cpp b/apps/openmw/mwrender/globalmap.cpp index d1b6cd239..57137d415 100644 --- a/apps/openmw/mwrender/globalmap.cpp +++ b/apps/openmw/mwrender/globalmap.cpp @@ -234,10 +234,10 @@ namespace MWRender GlobalMap::~GlobalMap() { - for (CameraVector::iterator it = mCamerasPendingRemoval.begin(); it != mCamerasPendingRemoval.end(); ++it) - removeCamera(*it); - for (CameraVector::iterator it = mActiveCameras.begin(); it != mActiveCameras.end(); ++it) - removeCamera(*it); + for (auto& camera : mCamerasPendingRemoval) + removeCamera(camera); + for (auto& camera : mActiveCameras) + removeCamera(camera); if (mWorkItem) mWorkItem->waitTillDone(); @@ -293,7 +293,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); @@ -581,8 +581,8 @@ namespace MWRender void GlobalMap::cleanupCameras() { - for (CameraVector::iterator it = mCamerasPendingRemoval.begin(); it != mCamerasPendingRemoval.end(); ++it) - removeCamera(*it); + for (auto& camera : mCamerasPendingRemoval) + removeCamera(camera); mCamerasPendingRemoval.clear(); 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 8ea4e3991..6c8dfec60 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include "../mwbase/environment.hpp" @@ -91,10 +92,10 @@ LocalMap::LocalMap(osg::Group* root) LocalMap::~LocalMap() { - for (CameraVector::iterator it = mActiveCameras.begin(); it != mActiveCameras.end(); ++it) - removeCamera(*it); - for (CameraVector::iterator it = mCamerasPendingRemoval.begin(); it != mCamerasPendingRemoval.end(); ++it) - removeCamera(*it); + for (auto& camera : mActiveCameras) + removeCamera(camera); + for (auto& camera : mCamerasPendingRemoval) + removeCamera(camera); } const osg::Vec2f LocalMap::rotatePoint(const osg::Vec2f& point, const osg::Vec2f& center, const float angle) @@ -177,7 +178,7 @@ osg::ref_ptr LocalMap::createOrthographicCamera(float x, float y, f camera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); camera->setRenderOrder(osg::Camera::PRE_RENDER); - camera->setCullMask(Mask_Scene|Mask_SimpleWater|Mask_Terrain); + camera->setCullMask(Mask_Scene | Mask_SimpleWater | Mask_Terrain | Mask_Object | Mask_Static); camera->setNodeMask(Mask_RenderToTexture); osg::ref_ptr stateset = new osg::StateSet; @@ -209,6 +210,8 @@ osg::ref_ptr LocalMap::createOrthographicCamera(float x, float y, f lightSource->setStateSetModes(*stateset, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + SceneUtil::ShadowManager::disableShadowsForStateSet(stateset); + camera->addChild(lightSource); camera->setStateSet(stateset); camera->setViewport(0, 0, mMapResolution, mMapResolution); @@ -256,16 +259,14 @@ bool needUpdate(std::set >& renderedGrid, std::set cells) { std::set > grid; - for (std::set::iterator it = cells.begin(); it != cells.end(); ++it) + for (const MWWorld::CellStore* cell : cells) { - const MWWorld::CellStore* cell = *it; if (cell->isExterior()) grid.insert(std::make_pair(cell->getCell()->getGridX(), cell->getCell()->getGridY())); } - for (std::set::iterator it = cells.begin(); it != cells.end(); ++it) + for (const MWWorld::CellStore* cell : cells) { - const MWWorld::CellStore* cell = *it; if (cell->isExterior()) { int cellX = cell->getCell()->getGridX(); @@ -338,8 +339,8 @@ void LocalMap::cleanupCameras() if (mCamerasPendingRemoval.empty()) return; - for (CameraVector::iterator it = mCamerasPendingRemoval.begin(); it != mCamerasPendingRemoval.end(); ++it) - removeCamera(*it); + for (auto& camera : mCamerasPendingRemoval) + removeCamera(camera); mCamerasPendingRemoval.clear(); } @@ -377,7 +378,7 @@ void LocalMap::requestExteriorMap(const MWWorld::CellStore* cell) void LocalMap::requestInteriorMap(const MWWorld::CellStore* cell) { osg::ComputeBoundsVisitor computeBoundsVisitor; - computeBoundsVisitor.setTraversalMask(Mask_Scene|Mask_Terrain); + computeBoundsVisitor.setTraversalMask(Mask_Scene | Mask_Terrain | Mask_Object | Mask_Static); mSceneRoot->accept(computeBoundsVisitor); osg::BoundingBox bounds = computeBoundsVisitor.getBoundingBox(); @@ -612,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/navmesh.cpp b/apps/openmw/mwrender/navmesh.cpp index 331f506ab..bfc80914a 100644 --- a/apps/openmw/mwrender/navmesh.cpp +++ b/apps/openmw/mwrender/navmesh.cpp @@ -34,7 +34,7 @@ namespace MWRender void NavMesh::update(const dtNavMesh& navMesh, const std::size_t id, const std::size_t generation, const std::size_t revision, const DetourNavigator::Settings& settings) { - if (!mEnabled || (mId == id && mGeneration >= generation && mRevision >= revision)) + if (!mEnabled || (mGroup && mId == id && mGeneration >= generation && mRevision >= revision)) return; mId = id; @@ -53,7 +53,10 @@ namespace MWRender void NavMesh::reset() { if (mGroup) + { mRootNode->removeChild(mGroup); + mGroup = nullptr; + } } void NavMesh::enable() @@ -65,7 +68,8 @@ namespace MWRender void NavMesh::disable() { - reset(); + if (mGroup) + mRootNode->removeChild(mGroup); mEnabled = false; } } diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 920e09a69..5c078e824 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -19,7 +19,6 @@ #include #include #include -#include #include @@ -55,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) @@ -69,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) @@ -239,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; @@ -284,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), @@ -362,11 +370,16 @@ public: if (cv->getProjectionMatrix()->getPerspective(fov, aspect, zNear, zFar)) { fov = mFov; - osg::RefMatrix* newProjectionMatrix = new osg::RefMatrix(*cv->getProjectionMatrix()); + osg::ref_ptr newProjectionMatrix = new osg::RefMatrix(); newProjectionMatrix->makePerspective(fov, aspect, zNear, zFar); - cv->pushProjectionMatrix(newProjectionMatrix); + osg::ref_ptr invertedOldMatrix = cv->getProjectionMatrix(); + invertedOldMatrix = new osg::RefMatrix(osg::RefMatrix::inverse(*invertedOldMatrix)); + osg::ref_ptr viewMatrix = new osg::RefMatrix(*cv->getModelViewMatrix()); + viewMatrix->postMult(*newProjectionMatrix); + viewMatrix->postMult(*invertedOldMatrix); + cv->pushModelViewMatrix(viewMatrix, osg::Transform::ReferenceFrame::ABSOLUTE_RF); traverse(node, nv); - cv->popProjectionMatrix(); + cv->popModelViewMatrix(); } else traverse(node, nv); @@ -427,46 +440,48 @@ 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 = "meshes\\" + store.get().find("WerewolfHead")->mModel; - mHairModel = "meshes\\" + store.get().find("WerewolfHair")->mModel; - } - 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 << "'"; - } + mHeadModel.clear(); + mHairModel.clear(); - 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 << "'"; - } + std::string headName = isWerewolf ? "WerewolfHead" : mNpc->mHead; + std::string hairName = isWerewolf ? "WerewolfHair" : mNpc->mHair; + + if (!headName.empty()) + { + 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 << "'"; } + 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; - std::string smodel = SceneUtil::getActorSkeleton(is1stPerson, isFemale, isBeast, isWerewolf); - smodel = Misc::ResourceHelpers::correctActorModelPath(smodel, mResourceSystem->getVFS()); + std::string defaultSkeleton = SceneUtil::getActorSkeleton(is1stPerson, isFemale, isBeast, isWerewolf); + defaultSkeleton = Misc::ResourceHelpers::correctActorModelPath(defaultSkeleton, mResourceSystem->getVFS()); + + std::string smodel = defaultSkeleton; + if (!is1stPerson && !isWerewolf && !mNpc->mModel.empty()) + smodel = Misc::ResourceHelpers::correctActorModelPath("meshes\\" + mNpc->mModel, mResourceSystem->getVFS()); setObjectRoot(smodel, true, true, false); @@ -481,15 +496,13 @@ void NpcAnimation::updateNpcBase() if (smodel != base) addAnimSource(base, smodel); + if (smodel != defaultSkeleton && base != defaultSkeleton) + addAnimSource(defaultSkeleton, smodel); + addAnimSource(smodel, smodel); - if(!isWerewolf) - { - if(mNpc->mModel.length() > 0) - addAnimSource(Misc::ResourceHelpers::correctActorModelPath("meshes\\" + mNpc->mModel, mResourceSystem->getVFS()), smodel); - if(Misc::StringUtils::lowerCase(mNpc->mRace).find("argonian") != std::string::npos) - addAnimSource("meshes\\xargonian_swimkna.nif", smodel); - } + if(!isWerewolf && Misc::StringUtils::lowerCase(mNpc->mRace).find("argonian") != std::string::npos) + addAnimSource("meshes\\xargonian_swimkna.nif", smodel); } else { @@ -511,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; @@ -626,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); @@ -643,9 +649,6 @@ void NpcAnimation::updateParts() if (wasArrowAttached) attachArrow(); - - if (mAlpha != 1.f) - mResourceSystem->getSceneManager()->recreateShaders(mObjectRoot); } @@ -724,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) @@ -787,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; @@ -822,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 || @@ -839,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 || @@ -854,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); } } @@ -879,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) @@ -912,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 { @@ -926,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) @@ -947,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); @@ -1024,9 +1014,9 @@ void NpcAnimation::enableHeadAnimation(bool enable) mHeadAnimationTime->setEnabled(enable); } -void NpcAnimation::setWeaponGroup(const std::string &group) +void NpcAnimation::setWeaponGroup(const std::string &group, bool relativeDuration) { - mWeaponAnimationTime->setGroup(group); + mWeaponAnimationTime->setGroup(group, relativeDuration); } void NpcAnimation::equipmentChanged() @@ -1081,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 1fbdd863c..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); @@ -117,7 +119,7 @@ public: /// 0: the first person meshes follow the camera with a reduced factor, so you can look down at your own hands virtual void setAccurateAiming(bool enabled); - virtual void setWeaponGroup(const std::string& group); + virtual void setWeaponGroup(const std::string& group, bool relativeDuration); virtual osg::Vec3f runAnimation(float timepassed); diff --git a/apps/openmw/mwrender/objects.cpp b/apps/openmw/mwrender/objects.cpp index 882c9856f..ec1c4397b 100644 --- a/apps/openmw/mwrender/objects.cpp +++ b/apps/openmw/mwrender/objects.cpp @@ -71,6 +71,7 @@ void Objects::insertBegin(const MWWorld::Ptr& ptr) void Objects::insertModel(const MWWorld::Ptr &ptr, const std::string &mesh, bool animated, bool allowLight) { insertBegin(ptr); + ptr.getRefData().getBaseNode()->setNodeMask(Mask_Object); osg::ref_ptr anim (new ObjectAnimation(ptr, mesh, mResourceSystem, animated, allowLight)); diff --git a/apps/openmw/mwrender/objects.hpp b/apps/openmw/mwrender/objects.hpp index 659853763..98ebb95d9 100644 --- a/apps/openmw/mwrender/objects.hpp +++ b/apps/openmw/mwrender/objects.hpp @@ -2,7 +2,6 @@ #define GAME_RENDER_OBJECTS_H #include -#include #include #include diff --git a/apps/openmw/mwrender/pathgrid.cpp b/apps/openmw/mwrender/pathgrid.cpp index ee4120d6f..797794457 100644 --- a/apps/openmw/mwrender/pathgrid.cpp +++ b/apps/openmw/mwrender/pathgrid.cpp @@ -6,14 +6,12 @@ #include #include -#include #include #include #include "../mwbase/world.hpp" // these includes can be removed once the static-hack is gone #include "../mwbase/environment.hpp" -#include "../mwworld/ptr.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/esmstore.hpp" #include "../mwmechanics/pathfinding.hpp" @@ -78,17 +76,17 @@ void Pathgrid::togglePathgrid() mPathGridRoot->setNodeMask(Mask_Debug); mRootNode->addChild(mPathGridRoot); - for(CellList::iterator it = mActiveCells.begin(); it != mActiveCells.end(); ++it) + for(const MWWorld::CellStore* cell : mActiveCells) { - enableCellPathgrid(*it); + enableCellPathgrid(cell); } } else { // remove path grid meshes from already loaded cells - for(CellList::iterator it = mActiveCells.begin(); it != mActiveCells.end(); ++it) + for(const MWWorld::CellStore* cell : mActiveCells) { - disableCellPathgrid(*it); + disableCellPathgrid(cell); } if (mPathGridRoot) diff --git a/apps/openmw/mwrender/pathgrid.hpp b/apps/openmw/mwrender/pathgrid.hpp index 22bfa8e73..77f1a7f33 100644 --- a/apps/openmw/mwrender/pathgrid.hpp +++ b/apps/openmw/mwrender/pathgrid.hpp @@ -4,7 +4,6 @@ #include #include -#include #include #include diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 322c310a2..bdefe4c5c 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -1,6 +1,5 @@ #include "renderingmanager.hpp" -#include #include #include @@ -39,6 +38,7 @@ #include #include #include +#include #include #include @@ -219,9 +219,9 @@ namespace MWRender { resourceSystem->getSceneManager()->setParticleSystemMask(MWRender::Mask_ParticleSystem); resourceSystem->getSceneManager()->setShaderPath(resourcePath + "/shaders"); - resourceSystem->getSceneManager()->setForceShaders(Settings::Manager::getBool("force shaders", "Shaders")); + resourceSystem->getSceneManager()->setForceShaders(Settings::Manager::getBool("force shaders", "Shaders") || Settings::Manager::getBool("enable shadows", "Shadows")); // Shadows have problems with fixed-function mode + // FIXME: calling dummy method because terrain needs to know whether lighting is clamped resourceSystem->getSceneManager()->setClampLighting(Settings::Manager::getBool("clamp lighting", "Shaders")); - resourceSystem->getSceneManager()->setForcePerPixelLighting(Settings::Manager::getBool("force per pixel lighting", "Shaders")); resourceSystem->getSceneManager()->setAutoUseNormalMaps(Settings::Manager::getBool("auto use object normal maps", "Shaders")); resourceSystem->getSceneManager()->setNormalMapPattern(Settings::Manager::getString("normal map pattern", "Shaders")); resourceSystem->getSceneManager()->setNormalHeightMapPattern(Settings::Manager::getString("normal height map pattern", "Shaders")); @@ -233,7 +233,31 @@ namespace MWRender mSceneRoot = sceneRoot; sceneRoot->setStartLight(1); - mRootNode->addChild(mSceneRoot); + int shadowCastingTraversalMask = Mask_Scene; + if (Settings::Manager::getBool("actor shadows", "Shadows")) + shadowCastingTraversalMask |= Mask_Actor; + if (Settings::Manager::getBool("player shadows", "Shadows")) + shadowCastingTraversalMask |= Mask_Player; + if (Settings::Manager::getBool("terrain shadows", "Shadows")) + shadowCastingTraversalMask |= Mask_Terrain; + + int indoorShadowCastingTraversalMask = shadowCastingTraversalMask; + if (Settings::Manager::getBool("object shadows", "Shadows")) + shadowCastingTraversalMask |= (Mask_Object|Mask_Static); + + mShadowManager.reset(new SceneUtil::ShadowManager(sceneRoot, mRootNode, shadowCastingTraversalMask, indoorShadowCastingTraversalMask, mResourceSystem->getSceneManager()->getShaderManager())); + + Shader::ShaderManager::DefineMap shadowDefines = mShadowManager->getShadowDefines(); + Shader::ShaderManager::DefineMap globalDefines = mResourceSystem->getSceneManager()->getShaderManager().getGlobalDefines(); + + for (auto itr = shadowDefines.begin(); itr != shadowDefines.end(); itr++) + globalDefines[itr->first] = itr->second; + + globalDefines["forcePPL"] = Settings::Manager::getBool("force per pixel lighting", "Shaders") ? "1" : "0"; + globalDefines["clamp"] = Settings::Manager::getBool("clamp lighting", "Shaders") ? "1" : "0"; + + // It is unnecessary to stop/start the viewer as no frames are being rendered yet. + mResourceSystem->getSceneManager()->getShaderManager().setGlobalDefines(globalDefines); mNavMesh.reset(new NavMesh(mRootNode, Settings::Manager::getBool("enable nav mesh render", "Navigator"))); mActorsPaths.reset(new ActorsPaths(mRootNode, Settings::Manager::getBool("enable agents paths render", "Navigator"))); @@ -262,17 +286,35 @@ namespace MWRender mDistantFog = Settings::Manager::getBool("use distant fog", "Fog"); mDistantTerrain = Settings::Manager::getBool("distant terrain", "Terrain"); - mTerrainStorage = new TerrainStorage(mResourceSystem, Settings::Manager::getString("normal map pattern", "Shaders"), Settings::Manager::getString("normal height map pattern", "Shaders"), - Settings::Manager::getBool("auto use terrain normal maps", "Shaders"), Settings::Manager::getString("terrain specular map pattern", "Shaders"), - Settings::Manager::getBool("auto use terrain specular maps", "Shaders")); + + const std::string normalMapPattern = Settings::Manager::getString("normal map pattern", "Shaders"); + const std::string heightMapPattern = Settings::Manager::getString("normal height map pattern", "Shaders"); + const std::string specularMapPattern = Settings::Manager::getString("terrain specular map pattern", "Shaders"); + const bool useTerrainNormalMaps = Settings::Manager::getBool("auto use terrain normal maps", "Shaders"); + const bool useTerrainSpecularMaps = Settings::Manager::getBool("auto use terrain specular maps", "Shaders"); + + mTerrainStorage = new TerrainStorage(mResourceSystem, normalMapPattern, heightMapPattern, useTerrainNormalMaps, specularMapPattern, useTerrainSpecularMaps); if (mDistantTerrain) - mTerrain.reset(new Terrain::QuadTreeWorld(sceneRoot, mRootNode, mResourceSystem, mTerrainStorage, Mask_Terrain, Mask_PreCompile, Mask_Debug)); + { + const int compMapResolution = Settings::Manager::getInt("composite map resolution", "Terrain"); + int compMapPower = Settings::Manager::getInt("composite map level", "Terrain"); + compMapPower = std::max(-3, compMapPower); + float compMapLevel = pow(2, compMapPower); + const float lodFactor = Settings::Manager::getFloat("lod factor", "Terrain"); + const int vertexLodMod = Settings::Manager::getInt("vertex lod mod", "Terrain"); + float maxCompGeometrySize = Settings::Manager::getFloat("max composite geometry size", "Terrain"); + maxCompGeometrySize = std::max(maxCompGeometrySize, 1.f); + mTerrain.reset(new Terrain::QuadTreeWorld( + sceneRoot, mRootNode, mResourceSystem, mTerrainStorage, Mask_Terrain, Mask_PreCompile, Mask_Debug, + compMapResolution, compMapLevel, lodFactor, vertexLodMod, maxCompGeometrySize)); + } else mTerrain.reset(new Terrain::TerrainGrid(sceneRoot, mRootNode, mResourceSystem, mTerrainStorage, Mask_Terrain, Mask_PreCompile, Mask_Debug)); mTerrain->setDefaultViewer(mViewer->getCamera()); mTerrain->setTargetFrameRate(Settings::Manager::getFloat("target framerate", "Cells")); + mTerrain->setWorkQueue(mWorkQueue.get()); mCamera.reset(new Camera(mViewer->getCamera())); @@ -330,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)); @@ -443,7 +487,7 @@ namespace MWRender osg::Vec4f diffuse = SceneUtil::colourFromRGB(cell->mAmbi.mSunlight); mSunLight->setDiffuse(diffuse); mSunLight->setSpecular(diffuse); - mSunLight->setDirection(osg::Vec3f(1.f,-1.f,-1.f)); + mSunLight->setPosition(osg::Vec4f(-0.15f, 0.15f, 1.f, 0.f)); } void RenderingManager::setSunColour(const osg::Vec4f& diffuse, const osg::Vec4f& specular) @@ -491,6 +535,10 @@ namespace MWRender void RenderingManager::setSkyEnabled(bool enabled) { mSky->setEnabled(enabled); + if (enabled) + mShadowManager->enableOutdoorMode(); + else + mShadowManager->enableIndoorMode(); } bool RenderingManager::toggleBorders() @@ -1153,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() @@ -1401,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/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index 2a48097bd..8cede92d0 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -13,6 +13,7 @@ #include "rendermode.hpp" #include +#include namespace osg { @@ -53,13 +54,14 @@ namespace Fallback namespace SceneUtil { + class ShadowManager; class WorkQueue; class UnrefQueue; } namespace DetourNavigator { - class Navigator; + struct Navigator; struct Settings; } @@ -267,6 +269,7 @@ namespace MWRender TerrainStorage* mTerrainStorage; std::unique_ptr mSky; std::unique_ptr mEffectManager; + std::unique_ptr mShadowManager; osg::ref_ptr mPlayerAnimation; osg::ref_ptr mPlayerNode; std::unique_ptr mCamera; diff --git a/apps/openmw/mwrender/ripplesimulation.cpp b/apps/openmw/mwrender/ripplesimulation.cpp index 21bd48d9a..a12487060 100644 --- a/apps/openmw/mwrender/ripplesimulation.cpp +++ b/apps/openmw/mwrender/ripplesimulation.cpp @@ -118,21 +118,22 @@ RippleSimulation::~RippleSimulation() void RippleSimulation::update(float dt) { const MWBase::World* world = MWBase::Environment::get().getWorld(); - for (std::vector::iterator it=mEmitters.begin(); it !=mEmitters.end(); ++it) + for (Emitter& emitter : mEmitters) { - if (it->mPtr == MWBase::Environment::get().getWorld ()->getPlayerPtr()) + MWWorld::ConstPtr& ptr = emitter.mPtr; + if (ptr == MWBase::Environment::get().getWorld ()->getPlayerPtr()) { // fetch a new ptr (to handle cell change etc) // for non-player actors this is done in updateObjectCell - it->mPtr = MWBase::Environment::get().getWorld ()->getPlayerPtr(); + ptr = MWBase::Environment::get().getWorld ()->getPlayerPtr(); } - osg::Vec3f currentPos (it->mPtr.getRefData().getPosition().asVec3()); + osg::Vec3f currentPos (ptr.getRefData().getPosition().asVec3()); - bool shouldEmit = ( world->isUnderwater (it->mPtr.getCell(), it->mPtr.getRefData().getPosition().asVec3()) && !world->isSubmerged(it->mPtr) ) || world->isWalkingOnWater(it->mPtr); - if ( shouldEmit && (currentPos - it->mLastEmitPosition).length() > 10 ) + bool shouldEmit = (world->isUnderwater(ptr.getCell(), currentPos) && !world->isSubmerged(ptr)) || world->isWalkingOnWater(ptr); + if (shouldEmit && (currentPos - emitter.mLastEmitPosition).length() > 10) { - it->mLastEmitPosition = currentPos; + emitter.mLastEmitPosition = currentPos; currentPos.z() = mParticleNode->getPosition().z(); diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 0ea0905a6..3b5f0a4fa 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -42,6 +42,7 @@ #include #include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -1127,7 +1128,8 @@ SkyManager::SkyManager(osg::Group* parentNode, Resource::SceneManager* sceneMana skyroot->setName("Sky Root"); // Assign empty program to specify we don't want shaders // The shaders generated by the SceneManager can't handle everything we need - skyroot->getOrCreateStateSet()->setAttributeAndModes(new osg::Program(), osg::StateAttribute::OVERRIDE); + skyroot->getOrCreateStateSet()->setAttributeAndModes(new osg::Program(), osg::StateAttribute::OVERRIDE|osg::StateAttribute::PROTECTED|osg::StateAttribute::ON); + SceneUtil::ShadowManager::disableShadowsForStateSet(skyroot->getOrCreateStateSet()); skyroot->setNodeMask(Mask_Sky); parentNode->addChild(skyroot); @@ -1811,8 +1813,8 @@ void SkyManager::setWeather(const WeatherResult& weather) if (mRainFader) mRainFader->setAlpha(weather.mEffectFade * 0.6); // * Rain_Threshold? - for (std::vector >::const_iterator it = mParticleFaders.begin(); it != mParticleFaders.end(); ++it) - (*it)->setAlpha(weather.mEffectFade); + for (AlphaFader* fader : mParticleFaders) + fader->setAlpha(weather.mEffectFade); } void SkyManager::sunEnable() diff --git a/apps/openmw/mwrender/sky.hpp b/apps/openmw/mwrender/sky.hpp index e70f16521..f6472fbde 100644 --- a/apps/openmw/mwrender/sky.hpp +++ b/apps/openmw/mwrender/sky.hpp @@ -4,7 +4,6 @@ #include #include #include -#include #include #include diff --git a/apps/openmw/mwrender/terrainstorage.cpp b/apps/openmw/mwrender/terrainstorage.cpp index ff2a8bfc7..528ce70ea 100644 --- a/apps/openmw/mwrender/terrainstorage.cpp +++ b/apps/openmw/mwrender/terrainstorage.cpp @@ -1,7 +1,5 @@ #include "terrainstorage.hpp" -#include - #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwworld/esmstore.hpp" @@ -24,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/mwrender/vismask.hpp b/apps/openmw/mwrender/vismask.hpp index d52c7c232..f9f9dc74c 100644 --- a/apps/openmw/mwrender/vismask.hpp +++ b/apps/openmw/mwrender/vismask.hpp @@ -33,25 +33,27 @@ namespace MWRender Mask_SimpleWater = (1<<7), Mask_Terrain = (1<<8), Mask_FirstPerson = (1<<9), + Mask_Object = (1<<10), + Mask_Static = (1<<11), // child of Sky - Mask_Sun = (1<<10), - Mask_WeatherParticles = (1<<11), + Mask_Sun = (1<<12), + Mask_WeatherParticles = (1<<13), // top level masks - Mask_Scene = (1<<12), - Mask_GUI = (1<<13), + Mask_Scene = (1<<14), + Mask_GUI = (1<<15), // Set on a ParticleSystem Drawable - Mask_ParticleSystem = (1<<14), + Mask_ParticleSystem = (1<<16), // Set on cameras within the main scene graph - Mask_RenderToTexture = (1<<15), + Mask_RenderToTexture = (1<<17), - Mask_PreCompile = (1<<16), + Mask_PreCompile = (1<<18), // Set on a camera's cull mask to enable the LightManager - Mask_Lighting = (1<<17) + Mask_Lighting = (1<<19) }; } diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index f668b0820..d047130d4 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -25,6 +25,7 @@ #include #include +#include #include #include @@ -224,7 +225,7 @@ public: setSmallFeatureCullingPixelSize(Settings::Manager::getInt("small feature culling pixel size", "Water")); setName("RefractionCamera"); - setCullMask(Mask_Effect|Mask_Scene|Mask_Terrain|Mask_Actor|Mask_ParticleSystem|Mask_Sky|Mask_Sun|Mask_Player|Mask_Lighting); + setCullMask(Mask_Effect|Mask_Scene|Mask_Object|Mask_Static|Mask_Terrain|Mask_Actor|Mask_ParticleSystem|Mask_Sky|Mask_Sun|Mask_Player|Mask_Lighting); setNodeMask(Mask_RenderToTexture); setViewport(0, 0, rttSize, rttSize); @@ -263,6 +264,8 @@ public: mRefractionDepthTexture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); attach(osg::Camera::DEPTH_BUFFER, mRefractionDepthTexture); + + SceneUtil::ShadowManager::disableShadowsForStateSet(getOrCreateStateSet()); } void setScene(osg::Node* scene) @@ -304,7 +307,7 @@ private: class Reflection : public osg::Camera { public: - Reflection() + Reflection(bool isInterior) { setRenderOrder(osg::Camera::PRE_RENDER); setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); @@ -313,9 +316,14 @@ public: setSmallFeatureCullingPixelSize(Settings::Manager::getInt("small feature culling pixel size", "Water")); setName("ReflectionCamera"); - bool reflectActors = Settings::Manager::getBool("reflect actors", "Water"); - - setCullMask(Mask_Effect|Mask_Scene|Mask_Terrain|Mask_ParticleSystem|Mask_Sky|Mask_Player|Mask_Lighting|(reflectActors ? Mask_Actor : 0)); + int reflectionDetail = Settings::Manager::getInt("reflection detail", "Water"); + reflectionDetail = std::min(4, std::max(isInterior ? 2 : 0, reflectionDetail)); + unsigned int extraMask = 0; + if(reflectionDetail >= 1) extraMask |= Mask_Terrain; + if(reflectionDetail >= 2) extraMask |= Mask_Static; + if(reflectionDetail >= 3) extraMask |= Mask_Effect|Mask_ParticleSystem|Mask_Object; + if(reflectionDetail >= 4) extraMask |= Mask_Player|Mask_Actor; + setCullMask(Mask_Scene|Mask_Sky|Mask_Lighting|extraMask); setNodeMask(Mask_RenderToTexture); unsigned int rttSize = Settings::Manager::getInt("rtt size", "Water"); @@ -341,6 +349,8 @@ public: mClipCullNode = new ClipCullNode; addChild(mClipCullNode); + + SceneUtil::ShadowManager::disableShadowsForStateSet(getOrCreateStateSet()); } void setWaterLevel(float waterLevel) @@ -400,6 +410,7 @@ Water::Water(osg::Group *parent, osg::Group* sceneRoot, Resource::ResourceSystem , mEnabled(true) , mToggled(true) , mTop(0) + , mInterior(false) { mSimulation.reset(new RippleSimulation(parent, resourceSystem, fallback)); @@ -452,7 +463,7 @@ void Water::updateWaterMaterial() if (Settings::Manager::getBool("shader", "Water")) { - mReflection = new Reflection; + mReflection = new Reflection(mInterior); mReflection->setWaterLevel(mTop); mReflection->setScene(mSceneRoot); mParent->addChild(mReflection); @@ -625,10 +636,20 @@ void Water::setEnabled(bool enabled) void Water::changeCell(const MWWorld::CellStore* store) { - if (store->getCell()->isExterior()) + bool isInterior = !store->getCell()->isExterior(); + bool wasInterior = mInterior; + if (!isInterior) + { mWaterNode->setPosition(getSceneNodeCoordinates(store->getCell()->mData.mX, store->getCell()->mData.mY)); + mInterior = false; + } else + { mWaterNode->setPosition(osg::Vec3f(0,0,mTop)); + mInterior = true; + } + if(mInterior != wasInterior) + updateWaterMaterial(); // create a new StateSet to prevent threading issues osg::ref_ptr nodeStateSet (new osg::StateSet); diff --git a/apps/openmw/mwrender/water.hpp b/apps/openmw/mwrender/water.hpp index 32a7977d2..1ad16ca24 100644 --- a/apps/openmw/mwrender/water.hpp +++ b/apps/openmw/mwrender/water.hpp @@ -70,6 +70,7 @@ namespace MWRender bool mEnabled; bool mToggled; float mTop; + bool mInterior; osg::Vec3f getSceneNodeCoordinates(int gridX, int gridY); void updateVisible(); diff --git a/apps/openmw/mwrender/weaponanimation.cpp b/apps/openmw/mwrender/weaponanimation.cpp index d5a8cb10d..1b51f0462 100644 --- a/apps/openmw/mwrender/weaponanimation.cpp +++ b/apps/openmw/mwrender/weaponanimation.cpp @@ -33,15 +33,20 @@ float WeaponAnimationTime::getValue(osg::NodeVisitor*) return current - mStartTime; } -void WeaponAnimationTime::setGroup(const std::string &group) +void WeaponAnimationTime::setGroup(const std::string &group, bool relativeTime) { mWeaponGroup = group; - mStartTime = mAnimation->getStartTime(mWeaponGroup); + mRelativeTime = relativeTime; + + if (mRelativeTime) + mStartTime = mAnimation->getStartTime(mWeaponGroup); + else + mStartTime = 0; } void WeaponAnimationTime::updateStartTime() { - setGroup(mWeaponGroup); + setGroup(mWeaponGroup, mRelativeTime); } WeaponAnimation::WeaponAnimation() diff --git a/apps/openmw/mwrender/weaponanimation.hpp b/apps/openmw/mwrender/weaponanimation.hpp index d50729c62..ece0beaa6 100644 --- a/apps/openmw/mwrender/weaponanimation.hpp +++ b/apps/openmw/mwrender/weaponanimation.hpp @@ -17,9 +17,10 @@ namespace MWRender Animation* mAnimation; std::string mWeaponGroup; float mStartTime; + bool mRelativeTime; public: - WeaponAnimationTime(Animation* animation) : mAnimation(animation), mStartTime(0) {} - void setGroup(const std::string& group); + WeaponAnimationTime(Animation* animation) : mAnimation(animation), mStartTime(0), mRelativeTime(false) {} + void setGroup(const std::string& group, bool relativeTime); void updateStartTime(); virtual float getValue(osg::NodeVisitor* nv); diff --git a/apps/openmw/mwscript/animationextensions.cpp b/apps/openmw/mwscript/animationextensions.cpp index e737d8ea1..6e959c0c0 100644 --- a/apps/openmw/mwscript/animationextensions.cpp +++ b/apps/openmw/mwscript/animationextensions.cpp @@ -3,7 +3,6 @@ #include #include -#include #include #include diff --git a/apps/openmw/mwscript/cellextensions.cpp b/apps/openmw/mwscript/cellextensions.cpp index f01fed2ce..80d4f7eb8 100644 --- a/apps/openmw/mwscript/cellextensions.cpp +++ b/apps/openmw/mwscript/cellextensions.cpp @@ -4,7 +4,6 @@ #include "../mwworld/esmstore.hpp" -#include #include #include diff --git a/apps/openmw/mwscript/consoleextensions.cpp b/apps/openmw/mwscript/consoleextensions.cpp index d2e07d3e1..749ec8096 100644 --- a/apps/openmw/mwscript/consoleextensions.cpp +++ b/apps/openmw/mwscript/consoleextensions.cpp @@ -1,11 +1,6 @@ #include "consoleextensions.hpp" -#include -#include - #include -#include -#include namespace MWScript { diff --git a/apps/openmw/mwscript/containerextensions.cpp b/apps/openmw/mwscript/containerextensions.cpp index cf12d12c3..db50ff621 100644 --- a/apps/openmw/mwscript/containerextensions.cpp +++ b/apps/openmw/mwscript/containerextensions.cpp @@ -2,13 +2,10 @@ #include -#include - #include #include -#include #include #include @@ -22,14 +19,13 @@ #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" -#include "../mwworld/actionequip.hpp" +#include "../mwworld/action.hpp" #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwmechanics/actorutil.hpp" -#include "interpretercontext.hpp" #include "ref.hpp" namespace MWScript @@ -64,7 +60,19 @@ namespace MWScript || ::Misc::StringUtils::ciEqual(item, "gold_100")) item = "gold_001"; - MWWorld::Ptr itemPtr = *ptr.getClass().getContainerStore (ptr).add (item, count, ptr); + MWWorld::ContainerStore& store = ptr.getClass().getContainerStore (ptr); + // Create a Ptr for the first added item to recover the item name later + MWWorld::Ptr itemPtr = *store.add (item, 1, ptr); + if (itemPtr.getClass().getScript(itemPtr).empty()) + { + store.add (item, count-1, ptr); + } + else + { + // Adding just one item per time to make sure there isn't a stack of scripted items + for (int i = 1; i < count; i++) + store.add (item, 1, ptr); + } // Spawn a messagebox (only for items added to player's inventory and if player is talking to someone) if (ptr == MWBase::Environment::get().getWorld ()->getPlayerPtr() ) @@ -75,13 +83,13 @@ namespace MWScript if (count == 1) { msgBox = MyGUI::LanguageManager::getInstance().replaceTags("#{sNotifyMessage60}"); - msgBox = boost::str(boost::format(msgBox) % itemName); } else { msgBox = MyGUI::LanguageManager::getInstance().replaceTags("#{sNotifyMessage61}"); - msgBox = boost::str(boost::format(msgBox) % count % itemName); + ::Misc::StringUtils::replace(msgBox, "%d", std::to_string(count).c_str(), 2); } + ::Misc::StringUtils::replace(msgBox, "%s", itemName.c_str(), 2); MWBase::Environment::get().getWindowManager()->messageBox(msgBox, MWGui::ShowInDialogueMode_Only); } } @@ -143,8 +151,13 @@ namespace MWScript std::string itemName; for (MWWorld::ConstContainerStoreIterator iter(store.cbegin()); iter != store.cend(); ++iter) + { if (::Misc::StringUtils::ciEqual(iter->getCellRef().getRefId(), item)) + { itemName = iter->getClass().getName(*iter); + break; + } + } int numRemoved = store.remove(item, count, ptr); @@ -155,16 +168,16 @@ namespace MWScript // The two GMST entries below expand to strings informing the player of what, and how many of it has been removed from their inventory std::string msgBox; - if(numRemoved > 1) + if (numRemoved > 1) { msgBox = MyGUI::LanguageManager::getInstance().replaceTags("#{sNotifyMessage63}"); - msgBox = boost::str (boost::format(msgBox) % numRemoved % itemName); + ::Misc::StringUtils::replace(msgBox, "%d", std::to_string(numRemoved).c_str(), 2); } else { msgBox = MyGUI::LanguageManager::getInstance().replaceTags("#{sNotifyMessage62}"); - msgBox = boost::str (boost::format(msgBox) % itemName); } + ::Misc::StringUtils::replace(msgBox, "%s", itemName.c_str(), 2); MWBase::Environment::get().getWindowManager()->messageBox(msgBox, MWGui::ShowInDialogueMode_Only); } } diff --git a/apps/openmw/mwscript/controlextensions.cpp b/apps/openmw/mwscript/controlextensions.cpp index 21b3b5587..0d5b1bf3b 100644 --- a/apps/openmw/mwscript/controlextensions.cpp +++ b/apps/openmw/mwscript/controlextensions.cpp @@ -16,7 +16,6 @@ #include "../mwworld/ptr.hpp" #include "../mwmechanics/npcstats.hpp" -#include "../mwmechanics/movement.hpp" #include "interpretercontext.hpp" #include "ref.hpp" @@ -168,12 +167,15 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime) { - MWWorld::Ptr ptr = MWBase::Environment::get().getWorld ()->getPlayerPtr(); - const MWWorld::Class &cls = ptr.getClass(); + MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); + MWBase::World* world = MWBase::Environment::get().getWorld(); - bool isRunning = MWBase::Environment::get().getMechanicsManager()->isRunning(ptr); + bool stanceOn = stats.getStance(MWMechanics::CreatureStats::Stance_Run); + bool running = MWBase::Environment::get().getMechanicsManager()->isRunning(ptr); + bool inair = !world->isOnGround(ptr) && !world->isSwimming(ptr) && !world->isFlying(ptr); - runtime.push (isRunning && cls.getCreatureStats(ptr).getStance(MWMechanics::CreatureStats::Stance_Run)); + runtime.push(stanceOn && (running || inair)); } }; @@ -184,11 +186,14 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime) { MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->getPlayerPtr(); - const MWWorld::Class &cls = ptr.getClass(); + MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); + MWBase::World* world = MWBase::Environment::get().getWorld(); - bool isSneaking = MWBase::Environment::get().getMechanicsManager()->isSneaking(ptr); + bool stanceOn = stats.getStance(MWMechanics::CreatureStats::Stance_Sneak); + bool sneaking = MWBase::Environment::get().getMechanicsManager()->isSneaking(ptr); + bool inair = !world->isOnGround(ptr) && !world->isSwimming(ptr) && !world->isFlying(ptr); - runtime.push (isSneaking && cls.getCreatureStats(ptr).getStance(MWMechanics::CreatureStats::Stance_Sneak)); + runtime.push(stanceOn && (sneaking || inair)); } }; diff --git a/apps/openmw/mwscript/dialogueextensions.cpp b/apps/openmw/mwscript/dialogueextensions.cpp index 135cb787e..200ddbb74 100644 --- a/apps/openmw/mwscript/dialogueextensions.cpp +++ b/apps/openmw/mwscript/dialogueextensions.cpp @@ -285,6 +285,7 @@ namespace MWScript void installOpcodes (Interpreter::Interpreter& interpreter) { interpreter.installSegment5 (Compiler::Dialogue::opcodeJournal, new OpJournal); + interpreter.installSegment5 (Compiler::Dialogue::opcodeJournalExplicit, new OpJournal); interpreter.installSegment5 (Compiler::Dialogue::opcodeSetJournalIndex, new OpSetJournalIndex); interpreter.installSegment5 (Compiler::Dialogue::opcodeGetJournalIndex, new OpGetJournalIndex); interpreter.installSegment5 (Compiler::Dialogue::opcodeAddTopic, new OpAddTopic); diff --git a/apps/openmw/mwscript/docs/vmformat.txt b/apps/openmw/mwscript/docs/vmformat.txt index d346ab6e4..d97f2a134 100644 --- a/apps/openmw/mwscript/docs/vmformat.txt +++ b/apps/openmw/mwscript/docs/vmformat.txt @@ -458,5 +458,6 @@ op 0x2000307: ToggleBorders, tb op 0x2000308: ToggleNavMesh op 0x2000309: ToggleActorsPaths op 0x200030a: SetNavMeshNumber +op 0x200030b: Journal, explicit -opcodes 0x200030b-0x3ffffff unused +opcodes 0x200030c-0x3ffffff unused diff --git a/apps/openmw/mwscript/globalscripts.cpp b/apps/openmw/mwscript/globalscripts.cpp index a52a4938a..63b0236c8 100644 --- a/apps/openmw/mwscript/globalscripts.cpp +++ b/apps/openmw/mwscript/globalscripts.cpp @@ -1,7 +1,5 @@ #include "globalscripts.hpp" -#include - #include #include #include diff --git a/apps/openmw/mwscript/guiextensions.cpp b/apps/openmw/mwscript/guiextensions.cpp index cea176ff6..64f45b4c0 100644 --- a/apps/openmw/mwscript/guiextensions.cpp +++ b/apps/openmw/mwscript/guiextensions.cpp @@ -1,6 +1,5 @@ #include "guiextensions.hpp" -#include #include #include diff --git a/apps/openmw/mwscript/interpretercontext.cpp b/apps/openmw/mwscript/interpretercontext.cpp index 07b7a3bff..30c56406e 100644 --- a/apps/openmw/mwscript/interpretercontext.cpp +++ b/apps/openmw/mwscript/interpretercontext.cpp @@ -4,8 +4,6 @@ #include #include -#include - #include #include @@ -18,6 +16,7 @@ #include "../mwbase/windowmanager.hpp" #include "../mwbase/inputmanager.hpp" +#include "../mwworld/action.hpp" #include "../mwworld/class.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/containerstore.hpp" diff --git a/apps/openmw/mwscript/interpretercontext.hpp b/apps/openmw/mwscript/interpretercontext.hpp index 5f1f4f7ab..0e001d692 100644 --- a/apps/openmw/mwscript/interpretercontext.hpp +++ b/apps/openmw/mwscript/interpretercontext.hpp @@ -4,7 +4,6 @@ #include #include "../mwworld/ptr.hpp" -#include "../mwworld/action.hpp" namespace MWSound { diff --git a/apps/openmw/mwscript/locals.cpp b/apps/openmw/mwscript/locals.cpp index 4c5573168..699200590 100644 --- a/apps/openmw/mwscript/locals.cpp +++ b/apps/openmw/mwscript/locals.cpp @@ -13,8 +13,6 @@ #include "../mwworld/esmstore.hpp" -#include - namespace MWScript { void Locals::ensure (const std::string& scriptName) diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index d3b49239c..a10dc00d2 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -2,7 +2,6 @@ #include -#include #include #include @@ -952,7 +951,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 "< -#include #include #include diff --git a/apps/openmw/mwscript/soundextensions.cpp b/apps/openmw/mwscript/soundextensions.cpp index 4d199c299..fe6f7bac3 100644 --- a/apps/openmw/mwscript/soundextensions.cpp +++ b/apps/openmw/mwscript/soundextensions.cpp @@ -1,6 +1,5 @@ -#include "extensions.hpp" +#include "soundextensions.hpp" -#include #include #include diff --git a/apps/openmw/mwscript/statsextensions.cpp b/apps/openmw/mwscript/statsextensions.cpp index 10d1252b9..092faa926 100644 --- a/apps/openmw/mwscript/statsextensions.cpp +++ b/apps/openmw/mwscript/statsextensions.cpp @@ -14,7 +14,6 @@ #include #include "../mwbase/environment.hpp" -#include "../mwbase/dialoguemanager.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/statemanager.hpp" #include "../mwbase/windowmanager.hpp" @@ -27,7 +26,6 @@ #include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/actorutil.hpp" -#include "interpretercontext.hpp" #include "ref.hpp" namespace diff --git a/apps/openmw/mwscript/transformationextensions.cpp b/apps/openmw/mwscript/transformationextensions.cpp index 9f0784d6c..0d0d6f29e 100644 --- a/apps/openmw/mwscript/transformationextensions.cpp +++ b/apps/openmw/mwscript/transformationextensions.cpp @@ -4,7 +4,6 @@ #include -#include #include #include @@ -18,7 +17,6 @@ #include "../mwworld/class.hpp" #include "../mwworld/manualref.hpp" #include "../mwworld/player.hpp" -#include "../mwworld/esmstore.hpp" #include "../mwmechanics/actorutil.hpp" 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/movieaudiofactory.cpp b/apps/openmw/mwsound/movieaudiofactory.cpp index 146023bc1..b0bfd52e1 100644 --- a/apps/openmw/mwsound/movieaudiofactory.cpp +++ b/apps/openmw/mwsound/movieaudiofactory.cpp @@ -1,7 +1,5 @@ #include "movieaudiofactory.hpp" -#include - #include #include diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index ccb3f22b5..ba75534b6 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -1,5 +1,4 @@ #include -#include #include #include #include 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/mwstate/character.cpp b/apps/openmw/mwstate/character.cpp index a8fffaec6..3c5c4f8b2 100644 --- a/apps/openmw/mwstate/character.cpp +++ b/apps/openmw/mwstate/character.cpp @@ -1,5 +1,6 @@ #include "character.hpp" +#include #include #include diff --git a/apps/openmw/mwstate/charactermanager.cpp b/apps/openmw/mwstate/charactermanager.cpp index d440ba869..856264d03 100644 --- a/apps/openmw/mwstate/charactermanager.cpp +++ b/apps/openmw/mwstate/charactermanager.cpp @@ -1,5 +1,6 @@ #include "charactermanager.hpp" +#include #include #include @@ -58,7 +59,7 @@ MWState::Character* MWState::CharacterManager::createCharacter(const std::string // The character name is user-supplied, so we need to escape the path for (std::string::const_iterator it = name.begin(); it != name.end(); ++it) { - if (isalnum(*it)) // Ignores multibyte characters and non alphanumeric characters + if (std::isalnum(*it)) // Ignores multibyte characters and non alphanumeric characters stream << *it; else stream << "_"; diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index 946c5eef7..01da76e90 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -32,7 +32,6 @@ #include "../mwworld/class.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/esmstore.hpp" -#include "../mwworld/inventorystore.hpp" #include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/actorutil.hpp" diff --git a/apps/openmw/mwworld/actionalchemy.cpp b/apps/openmw/mwworld/actionalchemy.cpp index 37d811eb6..62b673dd2 100644 --- a/apps/openmw/mwworld/actionalchemy.cpp +++ b/apps/openmw/mwworld/actionalchemy.cpp @@ -2,9 +2,7 @@ #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" -#include "../mwbase/world.hpp" -#include "../mwworld/player.hpp" #include "../mwmechanics/actorutil.hpp" namespace MWWorld diff --git a/apps/openmw/mwworld/actionapply.hpp b/apps/openmw/mwworld/actionapply.hpp index 5294745a6..4a1d2aefa 100644 --- a/apps/openmw/mwworld/actionapply.hpp +++ b/apps/openmw/mwworld/actionapply.hpp @@ -4,7 +4,6 @@ #include #include "action.hpp" -#include "ptr.hpp" namespace MWWorld { diff --git a/apps/openmw/mwworld/actioneat.cpp b/apps/openmw/mwworld/actioneat.cpp index 84805c70e..ef435cca9 100644 --- a/apps/openmw/mwworld/actioneat.cpp +++ b/apps/openmw/mwworld/actioneat.cpp @@ -2,9 +2,6 @@ #include -#include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" - #include "../mwworld/containerstore.hpp" #include "../mwmechanics/actorutil.hpp" diff --git a/apps/openmw/mwworld/actioneat.hpp b/apps/openmw/mwworld/actioneat.hpp index ce5330db7..db21ffa17 100644 --- a/apps/openmw/mwworld/actioneat.hpp +++ b/apps/openmw/mwworld/actioneat.hpp @@ -2,7 +2,6 @@ #define GAME_MWWORLD_ACTIONEAT_H #include "action.hpp" -#include "ptr.hpp" namespace MWWorld { diff --git a/apps/openmw/mwworld/actionequip.cpp b/apps/openmw/mwworld/actionequip.cpp index da794bcd2..30f969193 100644 --- a/apps/openmw/mwworld/actionequip.cpp +++ b/apps/openmw/mwworld/actionequip.cpp @@ -1,7 +1,6 @@ #include "actionequip.hpp" #include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwmechanics/actorutil.hpp" diff --git a/apps/openmw/mwworld/actionopen.cpp b/apps/openmw/mwworld/actionopen.cpp index f678037ce..f9866cd41 100644 --- a/apps/openmw/mwworld/actionopen.cpp +++ b/apps/openmw/mwworld/actionopen.cpp @@ -6,9 +6,6 @@ #include "../mwmechanics/disease.hpp" -#include "class.hpp" -#include "containerstore.hpp" - namespace MWWorld { ActionOpen::ActionOpen (const MWWorld::Ptr& container) diff --git a/apps/openmw/mwworld/actionopen.hpp b/apps/openmw/mwworld/actionopen.hpp index 3bce25c6b..e36b971a2 100644 --- a/apps/openmw/mwworld/actionopen.hpp +++ b/apps/openmw/mwworld/actionopen.hpp @@ -2,8 +2,6 @@ #define GAME_MWWORLD_ACTIONOPEN_H #include "action.hpp" -#include "ptr.hpp" - namespace MWWorld { diff --git a/apps/openmw/mwworld/actionread.hpp b/apps/openmw/mwworld/actionread.hpp index c23bf2900..7c4d7d2f4 100644 --- a/apps/openmw/mwworld/actionread.hpp +++ b/apps/openmw/mwworld/actionread.hpp @@ -2,7 +2,6 @@ #define GAME_MWWORLD_ACTIONREAD_H #include "action.hpp" -#include "ptr.hpp" namespace MWWorld { diff --git a/apps/openmw/mwworld/actionrepair.cpp b/apps/openmw/mwworld/actionrepair.cpp index 4fb7f7ada..4fe48b4b4 100644 --- a/apps/openmw/mwworld/actionrepair.cpp +++ b/apps/openmw/mwworld/actionrepair.cpp @@ -3,7 +3,6 @@ #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" -#include "../mwworld/player.hpp" #include "../mwmechanics/actorutil.hpp" namespace MWWorld diff --git a/apps/openmw/mwworld/actionsoulgem.cpp b/apps/openmw/mwworld/actionsoulgem.cpp index 98fe8ee34..f7823daba 100644 --- a/apps/openmw/mwworld/actionsoulgem.cpp +++ b/apps/openmw/mwworld/actionsoulgem.cpp @@ -2,8 +2,7 @@ #include "../mwbase/windowmanager.hpp" #include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" -#include "../mwworld/player.hpp" + #include "../mwmechanics/actorutil.hpp" namespace MWWorld diff --git a/apps/openmw/mwworld/actionsoulgem.hpp b/apps/openmw/mwworld/actionsoulgem.hpp index 0dd526657..6a8f220bc 100644 --- a/apps/openmw/mwworld/actionsoulgem.hpp +++ b/apps/openmw/mwworld/actionsoulgem.hpp @@ -2,7 +2,6 @@ #define GAME_MWWORLD_ACTIONSOULGEM_H #include "action.hpp" -#include "ptr.hpp" namespace MWWorld { diff --git a/apps/openmw/mwworld/actiontake.hpp b/apps/openmw/mwworld/actiontake.hpp index b0a9b8247..fb8d2f4ce 100644 --- a/apps/openmw/mwworld/actiontake.hpp +++ b/apps/openmw/mwworld/actiontake.hpp @@ -2,7 +2,6 @@ #define GAME_MWWORLD_ACTIONTAKE_H #include "action.hpp" -#include "ptr.hpp" namespace MWWorld { diff --git a/apps/openmw/mwworld/actiontalk.hpp b/apps/openmw/mwworld/actiontalk.hpp index b88b168d8..01738a0bb 100644 --- a/apps/openmw/mwworld/actiontalk.hpp +++ b/apps/openmw/mwworld/actiontalk.hpp @@ -1,7 +1,6 @@ #ifndef GAME_MWWORLD_ACTIONTALK_H #define GAME_MWWORLD_ACTIONTALK_H -#include "ptr.hpp" #include "action.hpp" namespace MWWorld diff --git a/apps/openmw/mwworld/actiontrap.hpp b/apps/openmw/mwworld/actiontrap.hpp index 5ff91613f..5b0d429c4 100644 --- a/apps/openmw/mwworld/actiontrap.hpp +++ b/apps/openmw/mwworld/actiontrap.hpp @@ -4,7 +4,6 @@ #include #include "action.hpp" -#include "ptr.hpp" namespace MWWorld { diff --git a/apps/openmw/mwworld/cellpreloader.cpp b/apps/openmw/mwworld/cellpreloader.cpp index 89e5357f0..98e9c7368 100644 --- a/apps/openmw/mwworld/cellpreloader.cpp +++ b/apps/openmw/mwworld/cellpreloader.cpp @@ -10,9 +10,7 @@ #include #include #include -#include #include -#include #include #include @@ -74,9 +72,9 @@ namespace MWWorld const std::vector& objectIds = cell->getPreloadedIds(); // could possibly build the model list in the worker thread if we manage to make the Store thread safe - for (std::vector::const_iterator it = objectIds.begin(); it != objectIds.end(); ++it) + for (const std::string& id : objectIds) { - MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), *it); + MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), id); std::string model = ref.getPtr().getClass().getModel(ref.getPtr()); if (!model.empty()) mMeshes.push_back(model); @@ -104,14 +102,13 @@ namespace MWWorld } } - for (MeshList::const_iterator it = mMeshes.begin(); it != mMeshes.end(); ++it) + for (std::string& mesh: mMeshes) { if (mAbort) break; try { - std::string mesh = *it; mesh = Misc::ResourceHelpers::correctActorModelPath(mesh, mSceneManager->getVFS()); if (mPreloadInstances) @@ -384,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 2b032e1d7..bea5825da 100644 --- a/apps/openmw/mwworld/cells.cpp +++ b/apps/openmw/mwworld/cells.cpp @@ -11,7 +11,6 @@ #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" -#include "class.hpp" #include "esmstore.hpp" #include "containerstore.hpp" #include "cellstore.hpp" @@ -151,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 e291951ae..12430ebde 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -966,7 +966,7 @@ namespace MWWorld } } - void CellStore::rest() + void CellStore::rest(double hours) { if (mState == State_Loaded) { @@ -975,7 +975,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) @@ -983,7 +983,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 ce8bb3da4..baecfb5ea 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/class.cpp b/apps/openmw/mwworld/class.cpp index 70fde33cf..472db15c5 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -20,7 +20,6 @@ #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/npcstats.hpp" -#include "../mwmechanics/magiceffects.hpp" namespace MWWorld { @@ -408,7 +407,7 @@ namespace MWWorld return false; } - bool Class::isPureWaterCreature(const MWWorld::Ptr& ptr) const + bool Class::isPureWaterCreature(const ConstPtr& ptr) const { return canSwim(ptr) && !isBipedal(ptr) @@ -416,7 +415,7 @@ namespace MWWorld && !canWalk(ptr); } - bool Class::isPureFlyingCreature(const Ptr& ptr) const + bool Class::isPureFlyingCreature(const ConstPtr& ptr) const { return canFly(ptr) && !isBipedal(ptr) diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index af88b0dcc..2be54e7ba 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -324,8 +324,8 @@ namespace MWWorld virtual bool canFly(const MWWorld::ConstPtr& ptr) const; virtual bool canSwim(const MWWorld::ConstPtr& ptr) const; virtual bool canWalk(const MWWorld::ConstPtr& ptr) const; - bool isPureWaterCreature(const MWWorld::Ptr& ptr) const; - bool isPureFlyingCreature(const MWWorld::Ptr& ptr) const; + bool isPureWaterCreature(const MWWorld::ConstPtr& ptr) const; + bool isPureFlyingCreature(const MWWorld::ConstPtr& ptr) const; bool isPureLandCreature(const MWWorld::Ptr& ptr) const; bool isMobile(const MWWorld::Ptr& ptr) const; diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index ee3089901..6725fb935 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -246,7 +246,9 @@ bool MWWorld::ContainerStore::stacks(const ConstPtr& ptr1, const ConstPtr& ptr2) && ptr1.getClass().getRemainingUsageTime(ptr1) == ptr2.getClass().getRemainingUsageTime(ptr2) - && cls1.getScript(ptr1) == cls2.getScript(ptr2) + // Items with scripts never stack + && cls1.getScript(ptr1).empty() + && cls2.getScript(ptr2).empty() // item that is already partly used up never stacks && (!cls1.hasItemHealth(ptr1) || ( @@ -306,7 +308,7 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add (const Ptr& itemPtr item.getCellRef().unsetRefNum(); // destroy link to content file std::string script = item.getClass().getScript(item); - if(script != "") + if (!script.empty()) { if (actorPtr == player) { diff --git a/apps/openmw/mwworld/contentloader.hpp b/apps/openmw/mwworld/contentloader.hpp index 2069a78fc..b529ae9db 100644 --- a/apps/openmw/mwworld/contentloader.hpp +++ b/apps/openmw/mwworld/contentloader.hpp @@ -1,7 +1,6 @@ #ifndef CONTENTLOADER_HPP #define CONTENTLOADER_HPP -#include #include #include diff --git a/apps/openmw/mwworld/globals.cpp b/apps/openmw/mwworld/globals.cpp index acc06b28a..69ec5563b 100644 --- a/apps/openmw/mwworld/globals.cpp +++ b/apps/openmw/mwworld/globals.cpp @@ -37,10 +37,9 @@ namespace MWWorld const MWWorld::Store& globals = store.get(); - for (MWWorld::Store::iterator iter = globals.begin(); iter!=globals.end(); - ++iter) + for (const ESM::Global& esmGlobal : globals) { - mVariables.insert (std::make_pair (Misc::StringUtils::lowerCase (iter->mId), *iter)); + mVariables.insert (std::make_pair (Misc::StringUtils::lowerCase (esmGlobal.mId), esmGlobal)); } } diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index 8b35d8dcc..a8fa91680 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -12,9 +12,6 @@ #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/mechanicsmanager.hpp" -#include "../mwbase/windowmanager.hpp" - -#include "../mwgui/inventorywindow.hpp" #include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/spellcasting.hpp" @@ -465,14 +462,13 @@ void MWWorld::InventoryStore::autoEquipArmor (const MWWorld::Ptr& actor, TSlots& iter->getClass().getEquipmentSlots (*iter); // checking if current item pointed by iter can be equipped - for (std::vector::const_iterator iter2 (itemsSlots.first.begin()); - iter2!=itemsSlots.first.end(); ++iter2) + for (int slot : itemsSlots.first) { // if true then it means slot is equipped already // check if slot may require swapping if current item is more valuable - if (slots_.at (*iter2)!=end()) + if (slots_.at (slot)!=end()) { - Ptr old = *slots_.at (*iter2); + Ptr old = *slots_.at (slot); if (iter.getType() == ContainerStore::Type_Armor) { @@ -493,7 +489,7 @@ void MWWorld::InventoryStore::autoEquipArmor (const MWWorld::Ptr& actor, TSlots& else if (iter.getType() == ContainerStore::Type_Clothing) { // if left ring is equipped - if (*iter2 == Slot_LeftRing) + if (slot == Slot_LeftRing) { // if there is a place for right ring dont swap it if (slots_.at(Slot_RightRing) == end()) @@ -533,7 +529,7 @@ void MWWorld::InventoryStore::autoEquipArmor (const MWWorld::Ptr& actor, TSlots& } // if we are here it means item can be equipped or swapped - slots_[*iter2] = iter; + slots_[slot] = iter; break; } } @@ -648,10 +644,9 @@ void MWWorld::InventoryStore::updateMagicEffects(const Ptr& actor) // Try resisting each effect int i=0; - for (std::vector::const_iterator effectIt (enchantment.mEffects.mList.begin()); - effectIt!=enchantment.mEffects.mList.end(); ++effectIt) + for (const ESM::ENAMstruct& effect : enchantment.mEffects.mList) { - params[i].mMultiplier = MWMechanics::getEffectMultiplier(effectIt->mEffectID, actor, actor); + params[i].mMultiplier = MWMechanics::getEffectMultiplier(effect.mEffectID, actor, actor); ++i; } @@ -665,18 +660,20 @@ void MWWorld::InventoryStore::updateMagicEffects(const Ptr& actor) params = mPermanentMagicEffectMagnitudes[(**iter).getCellRef().getRefId()]; int i=0; - for (std::vector::const_iterator effectIt (enchantment.mEffects.mList.begin()); - effectIt!=enchantment.mEffects.mList.end(); ++effectIt, ++i) + for (const ESM::ENAMstruct& effect : enchantment.mEffects.mList) { const ESM::MagicEffect *magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find ( - effectIt->mEffectID); + effect.mEffectID); // Fully resisted or can't be applied to target? - if (params[i].mMultiplier == 0 || !MWMechanics::checkEffectTarget(effectIt->mEffectID, actor, actor, actor == MWMechanics::getPlayer())) + if (params[i].mMultiplier == 0 || !MWMechanics::checkEffectTarget(effect.mEffectID, actor, actor, actor == MWMechanics::getPlayer())) + { + i++; continue; + } - float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * params[i].mRandom; + float magnitude = effect.mMagnMin + (effect.mMagnMax - effect.mMagnMin) * params[i].mRandom; magnitude *= params[i].mMultiplier; if (!existed) @@ -688,7 +685,9 @@ void MWWorld::InventoryStore::updateMagicEffects(const Ptr& actor) } if (magnitude) - mMagicEffects.add (*effectIt, magnitude); + mMagicEffects.add (effect, magnitude); + + i++; } } } @@ -952,17 +951,17 @@ void MWWorld::InventoryStore::visitEffectSources(MWMechanics::EffectSourceVisito continue; int i=0; - for (std::vector::const_iterator effectIt (enchantment.mEffects.mList.begin()); - effectIt!=enchantment.mEffects.mList.end(); ++effectIt, ++i) + for (const ESM::ENAMstruct& effect : enchantment.mEffects.mList) { + i++; // Don't get spell icon display information for enchantments that weren't actually applied - if (mMagicEffects.get(MWMechanics::EffectKey(*effectIt)).getMagnitude() == 0) + if (mMagicEffects.get(MWMechanics::EffectKey(effect)).getMagnitude() == 0) continue; - const EffectParams& params = mPermanentMagicEffectMagnitudes[(**iter).getCellRef().getRefId()][i]; - float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * params.mRandom; + const EffectParams& params = mPermanentMagicEffectMagnitudes[(**iter).getCellRef().getRefId()][i-1]; + float magnitude = effect.mMagnMin + (effect.mMagnMax - effect.mMagnMin) * params.mRandom; magnitude *= params.mMultiplier; if (magnitude > 0) - visitor.visit(MWMechanics::EffectKey(*effectIt), (**iter).getClass().getName(**iter), (**iter).getCellRef().getRefId(), -1, magnitude); + visitor.visit(MWMechanics::EffectKey(effect), (**iter).getClass().getName(**iter), (**iter).getCellRef().getRefId(), -1, magnitude); } } } diff --git a/apps/openmw/mwworld/player.cpp b/apps/openmw/mwworld/player.cpp index faf15215a..9e2d9d6ff 100644 --- a/apps/openmw/mwworld/player.cpp +++ b/apps/openmw/mwworld/player.cpp @@ -15,7 +15,6 @@ #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" -#include "../mwbase/soundmanager.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwmechanics/movement.hpp" @@ -30,6 +29,7 @@ namespace MWWorld Player::Player (const ESM::NPC *player) : mCellStore(0), mLastKnownExteriorPosition(0,0,0), + mMarkedPosition(ESM::Position()), mMarkedCell(nullptr), mAutoMove(false), mForwardBackward(0), @@ -122,6 +122,12 @@ namespace MWWorld return ptr; } + MWWorld::ConstPtr Player::getConstPlayer() const + { + MWWorld::ConstPtr ptr (&mPlayer, mCellStore); + return ptr; + } + void Player::setBirthSign (const std::string &sign) { mSign = sign; @@ -154,16 +160,16 @@ namespace MWWorld if (mAutoMove) value = 1; - ptr.getClass().getMovementSettings(ptr).mPosition[1] = static_cast(value); + ptr.getClass().getMovementSettings(ptr).mPosition[1] = value; } - void Player::setLeftRight (int value) + void Player::setLeftRight (float value) { MWWorld::Ptr ptr = getPlayer(); - ptr.getClass().getMovementSettings(ptr).mPosition[0] = static_cast(value); + ptr.getClass().getMovementSettings(ptr).mPosition[0] = value; } - void Player::setForwardBackward (int value) + void Player::setForwardBackward (float value) { MWWorld::Ptr ptr = getPlayer(); @@ -172,7 +178,7 @@ namespace MWWorld if (mAutoMove) value = 1; - ptr.getClass().getMovementSettings(ptr).mPosition[1] = static_cast(value); + ptr.getClass().getMovementSettings(ptr).mPosition[1] = value; } void Player::setUpDown(int value) diff --git a/apps/openmw/mwworld/player.hpp b/apps/openmw/mwworld/player.hpp index 7f37de010..96ed20adf 100644 --- a/apps/openmw/mwworld/player.hpp +++ b/apps/openmw/mwworld/player.hpp @@ -27,6 +27,7 @@ namespace Loading namespace MWWorld { class CellStore; + class ConstPtr; /// \brief NPC object representing the player and additional player data class Player @@ -42,7 +43,7 @@ namespace MWWorld CellStore* mMarkedCell; bool mAutoMove; - int mForwardBackward; + float mForwardBackward; bool mTeleported; int mCurrentCrimeId; // the id assigned witnesses @@ -81,6 +82,7 @@ namespace MWWorld void setCell (MWWorld::CellStore *cellStore); MWWorld::Ptr getPlayer(); + MWWorld::ConstPtr getConstPlayer() const; void setBirthSign(const std::string &sign); const std::string &getBirthSign() const; @@ -94,9 +96,9 @@ namespace MWWorld bool getAutoMove() const; void setAutoMove (bool enable); - void setLeftRight (int value); + void setLeftRight (float value); - void setForwardBackward (int value); + void setForwardBackward (float value); void setUpDown(int value); void setRunState(bool run); diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index ff449acf5..b13db3a3e 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -3,7 +3,6 @@ #include #include -#include #include @@ -34,7 +33,6 @@ #include "../mwmechanics/actorutil.hpp" #include "../mwmechanics/aipackage.hpp" -#include "../mwrender/effectmanager.hpp" #include "../mwrender/animation.hpp" #include "../mwrender/vismask.hpp" #include "../mwrender/renderingmanager.hpp" @@ -205,12 +203,6 @@ namespace MWWorld osg::ref_ptr projectile = mResourceSystem->getSceneManager()->getInstance(model, attachTo); - osg::ref_ptr boundVisitor = new osg::ComputeBoundsVisitor(); - projectile->accept(*boundVisitor.get()); - osg::BoundingBox bb = boundVisitor->getBoundingBox(); - - state.mNode->setPivotPoint(bb.center()); - if (state.mIdMagic.size() > 1) for (size_t iter = 1; iter != state.mIdMagic.size(); ++iter) { @@ -466,24 +458,14 @@ namespace MWWorld osg::Vec3f pos(it->mNode->getPosition()); osg::Vec3f newPos = pos + it->mVelocity * duration; - osg::Quat orient; - - if (it->mThrown) - orient.set( - osg::Matrixd::rotate(it->mEffectAnimationTime->getTime() * -10.0,osg::Vec3f(0,0,1)) * - osg::Matrixd::rotate(osg::PI / 2.0,osg::Vec3f(0,1,0)) * - osg::Matrixd::rotate(-1 * osg::PI / 2.0,osg::Vec3f(1,0,0)) * - osg::Matrixd::inverse( - osg::Matrixd::lookAt( - osg::Vec3f(0,0,0), - it->mVelocity, - osg::Vec3f(0,0,1)) - ) - ); - else + // rotation does not work well for throwing projectiles - their roll angle will depend on shooting direction. + if (!it->mThrown) + { + osg::Quat orient; orient.makeRotate(osg::Vec3f(0,1,0), it->mVelocity); + it->mNode->setAttitude(orient); + } - it->mNode->setAttitude(orient); it->mNode->setPosition(newPos); update(*it, duration); diff --git a/apps/openmw/mwworld/recordcmp.hpp b/apps/openmw/mwworld/recordcmp.hpp index 500f86b1e..f749351ce 100644 --- a/apps/openmw/mwworld/recordcmp.hpp +++ b/apps/openmw/mwworld/recordcmp.hpp @@ -1,8 +1,6 @@ #ifndef OPENMW_MWWORLD_RECORDCMP_H #define OPENMW_MWWORLD_RECORDCMP_H -#include - #include #include diff --git a/apps/openmw/mwworld/refdata.hpp b/apps/openmw/mwworld/refdata.hpp index 75eec6742..6a59d9797 100644 --- a/apps/openmw/mwworld/refdata.hpp +++ b/apps/openmw/mwworld/refdata.hpp @@ -7,7 +7,6 @@ #include "../mwscript/locals.hpp" #include -#include namespace SceneUtil { diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 1d5293b7c..8cccb2fc5 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -4,7 +4,6 @@ #include #include -#include #include #include @@ -15,6 +14,7 @@ #include #include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -29,7 +29,6 @@ #include "../mwphysics/actor.hpp" #include "../mwphysics/object.hpp" #include "../mwphysics/heightfield.hpp" -#include "../mwphysics/convert.hpp" #include "player.hpp" #include "localscripts.hpp" @@ -136,16 +135,16 @@ namespace const auto& transform = object->getCollisionObject()->getWorldTransform(); const btTransform closedDoorTransform( - MWPhysics::toBullet(makeObjectOsgQuat(ptr.getCellRef().getPosition())), + Misc::Convert::toBullet(makeObjectOsgQuat(ptr.getCellRef().getPosition())), transform.getOrigin() ); - const auto start = DetourNavigator::makeOsgVec3f(closedDoorTransform(center + toPoint)); + const auto start = Misc::Convert::makeOsgVec3f(closedDoorTransform(center + toPoint)); const auto startPoint = physics.castRay(start, start - osg::Vec3f(0, 0, 1000), ptr, {}, MWPhysics::CollisionType_World | MWPhysics::CollisionType_HeightMap | MWPhysics::CollisionType_Water); const auto connectionStart = startPoint.mHit ? startPoint.mHitPos : start; - const auto end = DetourNavigator::makeOsgVec3f(closedDoorTransform(center - toPoint)); + const auto end = Misc::Convert::makeOsgVec3f(closedDoorTransform(center - toPoint)); const auto endPoint = physics.castRay(end, end - osg::Vec3f(0, 0, 1000), ptr, {}, MWPhysics::CollisionType_World | MWPhysics::CollisionType_HeightMap | MWPhysics::CollisionType_Water); const auto connectionEnd = endPoint.mHit ? endPoint.mHitPos : end; @@ -173,12 +172,9 @@ namespace ); } } - else if (const auto actor = physics.getActor(ptr)) + else if (physics.getActor(ptr)) { - const auto halfExtents = ptr.getCell()->isExterior() - ? physics.getHalfExtents(MWBase::Environment::get().getWorld()->getPlayerPtr()) - : actor->getHalfExtents(); - navigator.addAgent(halfExtents); + navigator.addAgent(MWBase::Environment::get().getWorld()->getPathfindingHalfExtents(ptr)); } } @@ -234,9 +230,8 @@ namespace template void InsertVisitor::insert(AddObject&& addObject) { - for (std::vector::iterator it = mToInsert.begin(); it != mToInsert.end(); ++it) + for (MWWorld::Ptr& ptr : mToInsert) { - MWWorld::Ptr ptr = *it; if (mRescale) { if (ptr.getCellRef().getScale()<0.5) @@ -338,15 +333,14 @@ namespace MWWorld ListAndResetObjectsVisitor visitor; (*iter)->forEach(visitor); - const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - const auto playerHalfExtents = mPhysics->getHalfExtents(player); + const auto world = MWBase::Environment::get().getWorld(); for (const auto& ptr : visitor.mObjects) { 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(ptr.getCell()->isExterior() ? playerHalfExtents : actor->getHalfExtents()); + navigator->removeAgent(world->getPathfindingHalfExtents(ptr)); mRendering.removeActorPath(ptr); } mPhysics->remove(ptr); @@ -373,6 +367,7 @@ namespace MWWorld if ((*iter)->getCell()->hasWater()) navigator->removeWater(osg::Vec2i(cellX, cellY)); + const auto player = world->getPlayerPtr(); navigator->update(player.getRefData().getPosition().asVec3()); MWBase::Environment::get().getMechanicsManager()->drop (*iter); @@ -814,12 +809,9 @@ 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)) { - const auto& halfExtents = ptr.getCell()->isExterior() - ? mPhysics->getHalfExtents(MWBase::Environment::get().getWorld()->getPlayerPtr()) - : actor->getHalfExtents(); - navigator->removeAgent(halfExtents); + navigator->removeAgent(MWBase::Environment::get().getWorld()->getPathfindingHalfExtents(ptr)); } mPhysics->remove(ptr); mRendering.removeObject (ptr); @@ -912,24 +904,22 @@ namespace MWWorld void Scene::preloadTeleportDoorDestinations(const osg::Vec3f& playerPos, const osg::Vec3f& predictedPos, std::vector& exteriorPositions) { std::vector teleportDoors; - for (CellStoreCollection::const_iterator iter (mActiveCells.begin()); - iter!=mActiveCells.end(); ++iter) + for (const MWWorld::CellStore* cellStore : mActiveCells) { - const MWWorld::CellStore* cellStore = *iter; typedef MWWorld::CellRefList::List DoorList; const DoorList &doors = cellStore->getReadOnlyDoors().mList; - for (DoorList::const_iterator doorIt = doors.begin(); doorIt != doors.end(); ++doorIt) + for (auto& door : doors) { - if (!doorIt->mRef.getTeleport()) { + if (!door.mRef.getTeleport()) + { continue; } - teleportDoors.push_back(MWWorld::ConstPtr(&*doorIt, cellStore)); + teleportDoors.push_back(MWWorld::ConstPtr(&door, cellStore)); } } - for (std::vector::iterator it = teleportDoors.begin(); it != teleportDoors.end(); ++it) + for (const MWWorld::ConstPtr& door : teleportDoors) { - const MWWorld::ConstPtr& door = *it; float sqrDistToPlayer = (playerPos - door.getRefData().getPosition().asVec3()).length2(); sqrDistToPlayer = std::min(sqrDistToPlayer, (predictedPos - door.getRefData().getPosition().asVec3()).length2()); @@ -1053,20 +1043,19 @@ namespace MWWorld const MWWorld::ConstPtr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); ListFastTravelDestinationsVisitor listVisitor(mPreloadDistance, player.getRefData().getPosition().asVec3()); - for (CellStoreCollection::const_iterator iter (mActiveCells.begin()); iter!=mActiveCells.end(); ++iter) + for (MWWorld::CellStore* cellStore : mActiveCells) { - MWWorld::CellStore* cellStore = *iter; cellStore->forEachType(listVisitor); cellStore->forEachType(listVisitor); } - for (std::vector::const_iterator it = listVisitor.mList.begin(); it != listVisitor.mList.end(); ++it) + for (ESM::Transport::Dest& dest : listVisitor.mList) { - if (!it->mCellName.empty()) - preloadCell(MWBase::Environment::get().getWorld()->getInterior(it->mCellName)); + if (!dest.mCellName.empty()) + preloadCell(MWBase::Environment::get().getWorld()->getInterior(dest.mCellName)); else { - osg::Vec3f pos = it->mPos.asVec3(); + osg::Vec3f pos = dest.mPos.asVec3(); int x,y; MWBase::Environment::get().getWorld()->positionToIndex( pos.x(), pos.y(), x, y); preloadCell(MWBase::Environment::get().getWorld()->getExterior(x,y), true); diff --git a/apps/openmw/mwworld/scene.hpp b/apps/openmw/mwworld/scene.hpp index 00f5f98b8..0f1ae7298 100644 --- a/apps/openmw/mwworld/scene.hpp +++ b/apps/openmw/mwworld/scene.hpp @@ -30,7 +30,7 @@ namespace Loading namespace DetourNavigator { - class Navigator; + struct Navigator; class Water; } diff --git a/apps/openmw/mwworld/store.cpp b/apps/openmw/mwworld/store.cpp index 631b6e081..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); @@ -420,10 +435,9 @@ namespace MWWorld //========================================================================= Store::~Store() { - for (std::vector::const_iterator it = - mStatic.begin(); it != mStatic.end(); ++it) + for (const ESM::Land* staticLand : mStatic) { - delete *it; + delete staticLand; } } @@ -737,15 +751,16 @@ namespace MWWorld } const ESM::Cell *Store::searchExtByName(const std::string &id) const { - ESM::Cell *cell = 0; - std::vector::const_iterator it = mSharedExt.begin(); - for (; it != mSharedExt.end(); ++it) { - if (Misc::StringUtils::ciEqual((*it)->mName, id)) { - if ( cell == 0 || - ( (*it)->mData.mX > cell->mData.mX ) || - ( (*it)->mData.mX == cell->mData.mX && (*it)->mData.mY > cell->mData.mY ) ) + const ESM::Cell *cell = nullptr; + for (const ESM::Cell *sharedCell : mSharedExt) + { + if (Misc::StringUtils::ciEqual(sharedCell->mName, id)) + { + if (cell == 0 || + (sharedCell->mData.mX > cell->mData.mX) || + (sharedCell->mData.mX == cell->mData.mX && sharedCell->mData.mY > cell->mData.mY)) { - cell = *it; + cell = sharedCell; } } } @@ -753,15 +768,16 @@ namespace MWWorld } const ESM::Cell *Store::searchExtByRegion(const std::string &id) const { - ESM::Cell *cell = 0; - std::vector::const_iterator it = mSharedExt.begin(); - for (; it != mSharedExt.end(); ++it) { - if (Misc::StringUtils::ciEqual((*it)->mRegion, id)) { - if ( cell == 0 || - ( (*it)->mData.mX > cell->mData.mX ) || - ( (*it)->mData.mX == cell->mData.mX && (*it)->mData.mY > cell->mData.mY ) ) + const ESM::Cell *cell = nullptr; + for (const ESM::Cell *sharedCell : mSharedExt) + { + if (Misc::StringUtils::ciEqual(sharedCell->mRegion, id)) + { + if (cell == nullptr || + (sharedCell->mData.mX > cell->mData.mX) || + (sharedCell->mData.mX == cell->mData.mX && sharedCell->mData.mY > cell->mData.mY)) { - cell = *it; + cell = sharedCell; } } } @@ -775,9 +791,9 @@ namespace MWWorld { list.reserve(list.size() + mSharedInt.size()); - std::vector::const_iterator it = mSharedInt.begin(); - for (; it != mSharedInt.end(); ++it) { - list.push_back((*it)->mName); + for (const ESM::Cell *sharedCell : mSharedInt) + { + list.push_back(sharedCell->mName); } } ESM::Cell *Store::insert(const ESM::Cell &cell) diff --git a/apps/openmw/mwworld/weather.cpp b/apps/openmw/mwworld/weather.cpp index 14d88a752..c181b95cf 100644 --- a/apps/openmw/mwworld/weather.cpp +++ b/apps/openmw/mwworld/weather.cpp @@ -4,7 +4,6 @@ #include #include -#include #include #include "../mwbase/environment.hpp" @@ -299,10 +298,11 @@ void RegionWeather::setChances(const std::vector& chances) mChances.reserve(chances.size()); } - std::vector::const_iterator it = chances.begin(); - for(size_t i = 0; it != chances.end(); ++it, ++i) + int i = 0; + for(char chance : chances) { - mChances[i] = *it; + mChances[i] = chance; + i++; } // Regional weather no longer supports the current type, select a new weather pattern. @@ -748,7 +748,7 @@ void WeatherManager::update(float duration, bool paused, const TimeStamp& time, } else { - theta = static_cast(osg::PI) + static_cast(osg::PI) * (adjustedHour - adjustedNightStart) / nightDuration; + theta = static_cast(osg::PI) - static_cast(osg::PI) * (adjustedHour - adjustedNightStart) / nightDuration; } osg::Vec3f final( @@ -937,11 +937,10 @@ inline void WeatherManager::addWeather(const std::string& name, inline void WeatherManager::importRegions() { - Store::iterator it = mStore.get().begin(); - for(; it != mStore.get().end(); ++it) + for(const ESM::Region& region : mStore.get()) { - std::string regionID = Misc::StringUtils::lowerCase(it->mId); - mRegions.insert(std::make_pair(regionID, RegionWeather(*it))); + std::string regionID = Misc::StringUtils::lowerCase(region.mId); + mRegions.insert(std::make_pair(regionID, RegionWeather(region))); } } diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 672e6db24..1d33d039e 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -24,7 +24,8 @@ #include #include -#include +#include +#include #include #include "../mwbase/environment.hpp" @@ -34,7 +35,6 @@ #include "../mwbase/scriptmanager.hpp" #include "../mwmechanics/creaturestats.hpp" -#include "../mwmechanics/movement.hpp" #include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/spellcasting.hpp" #include "../mwmechanics/levelledlist.hpp" @@ -47,7 +47,6 @@ #include "../mwrender/camera.hpp" #include "../mwrender/vismask.hpp" -#include "../mwscript/interpretercontext.hpp" #include "../mwscript/globalscripts.hpp" #include "../mwclass/door.hpp" @@ -158,12 +157,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) @@ -198,40 +197,21 @@ namespace MWWorld mPhysics.reset(new MWPhysics::PhysicsSystem(resourceSystem, rootNode)); - DetourNavigator::Settings navigatorSettings; - navigatorSettings.mBorderSize = Settings::Manager::getInt("border size", "Navigator"); - navigatorSettings.mCellHeight = Settings::Manager::getFloat("cell height", "Navigator"); - navigatorSettings.mCellSize = Settings::Manager::getFloat("cell size", "Navigator"); - navigatorSettings.mDetailSampleDist = Settings::Manager::getFloat("detail sample dist", "Navigator"); - navigatorSettings.mDetailSampleMaxError = Settings::Manager::getFloat("detail sample max error", "Navigator"); - navigatorSettings.mMaxClimb = MWPhysics::sStepSizeUp; - navigatorSettings.mMaxSimplificationError = Settings::Manager::getFloat("max simplification error", "Navigator"); - navigatorSettings.mMaxSlope = MWPhysics::sMaxSlope; - navigatorSettings.mRecastScaleFactor = Settings::Manager::getFloat("recast scale factor", "Navigator"); - navigatorSettings.mSwimHeightScale = mSwimHeightScale; - navigatorSettings.mMaxEdgeLen = Settings::Manager::getInt("max edge len", "Navigator"); - navigatorSettings.mMaxNavMeshQueryNodes = Settings::Manager::getInt("max nav mesh query nodes", "Navigator"); - navigatorSettings.mMaxPolys = Settings::Manager::getInt("max polygons per tile", "Navigator"); - navigatorSettings.mMaxVertsPerPoly = Settings::Manager::getInt("max verts per poly", "Navigator"); - navigatorSettings.mRegionMergeSize = Settings::Manager::getInt("region merge size", "Navigator"); - navigatorSettings.mRegionMinSize = Settings::Manager::getInt("region min size", "Navigator"); - navigatorSettings.mTileSize = Settings::Manager::getInt("tile size", "Navigator"); - navigatorSettings.mAsyncNavMeshUpdaterThreads = static_cast(Settings::Manager::getInt("async nav mesh updater threads", "Navigator")); - navigatorSettings.mMaxNavMeshTilesCacheSize = static_cast(Settings::Manager::getInt("max nav mesh tiles cache size", "Navigator")); - navigatorSettings.mMaxPolygonPathSize = static_cast(Settings::Manager::getInt("max polygon path size", "Navigator")); - navigatorSettings.mMaxSmoothPathSize = static_cast(Settings::Manager::getInt("max smooth path size", "Navigator")); - navigatorSettings.mTrianglesPerChunk = static_cast(Settings::Manager::getInt("triangles per chunk", "Navigator")); - navigatorSettings.mEnableWriteRecastMeshToFile = Settings::Manager::getBool("enable write recast mesh to file", "Navigator"); - navigatorSettings.mEnableWriteNavMeshToFile = Settings::Manager::getBool("enable write nav mesh to file", "Navigator"); - navigatorSettings.mRecastMeshPathPrefix = Settings::Manager::getString("recast mesh path prefix", "Navigator"); - navigatorSettings.mNavMeshPathPrefix = Settings::Manager::getString("nav mesh path prefix", "Navigator"); - navigatorSettings.mEnableRecastMeshFileNameRevision = Settings::Manager::getBool("enable recast mesh file name revision", "Navigator"); - navigatorSettings.mEnableNavMeshFileNameRevision = Settings::Manager::getBool("enable nav mesh file name revision", "Navigator"); - if (Settings::Manager::getBool("enable log", "Navigator")) - DetourNavigator::Log::instance().setSink(std::unique_ptr( - new DetourNavigator::FileSink(Settings::Manager::getString("log path", "Navigator")))); - DetourNavigator::RecastGlobalAllocator::init(); - mNavigator.reset(new DetourNavigator::Navigator(navigatorSettings)); + if (auto navigatorSettings = DetourNavigator::makeSettingsFromSettingsManager()) + { + navigatorSettings->mMaxClimb = MWPhysics::sStepSizeUp; + navigatorSettings->mMaxSlope = MWPhysics::sMaxSlope; + navigatorSettings->mSwimHeightScale = mSwimHeightScale; + if (Settings::Manager::getBool("enable log", "Navigator")) + DetourNavigator::Log::instance().setSink(std::unique_ptr( + new DetourNavigator::FileSink(Settings::Manager::getString("log path", "Navigator")))); + DetourNavigator::RecastGlobalAllocator::init(); + mNavigator.reset(new DetourNavigator::NavigatorImpl(*navigatorSettings)); + } + else + { + mNavigator.reset(new DetourNavigator::NavigatorStub()); + } mRendering.reset(new MWRender::RenderingManager(viewer, rootNode, resourceSystem, workQueue, &mFallback, resourcePath, *mNavigator)); mProjectileManager.reset(new ProjectileManager(mRendering->getLightRoot(), resourceSystem, mRendering.get(), mPhysics.get())); @@ -327,9 +307,6 @@ namespace MWWorld if (!mPhysics->toggleCollisionMode()) mPhysics->toggleCollisionMode(); - if (!mStartupScript.empty()) - MWBase::Environment::get().getWindowManager()->executeInConsole(mStartupScript); - MWBase::Environment::get().getWindowManager()->updatePlayer(); } @@ -429,7 +406,8 @@ namespace MWWorld if (getPlayerPtr().isInCell()) { mWorldScene->preloadCell(getPlayerPtr().getCell(), true); - mWorldScene->preloadTerrain(getPlayerPtr().getRefData().getPosition().asVec3()); + if (getPlayerPtr().getCell()->isExterior()) + mWorldScene->preloadTerrain(getPlayerPtr().getRefData().getPosition().asVec3()); } break; default: @@ -492,7 +470,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)) { @@ -522,7 +500,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)) { @@ -541,7 +519,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)) { @@ -555,7 +533,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)) { @@ -1317,6 +1295,9 @@ namespace MWWorld { mPhysics->updatePosition(newPtr); mPhysics->updatePtr(ptr, newPtr); + + if (const auto object = mPhysics->getObject(newPtr)) + updateNavigatorObject(object); } } if (isPlayer) @@ -1347,9 +1328,17 @@ namespace MWWorld void World::scaleObject (const Ptr& ptr, float scale) { + if (mPhysics->getActor(ptr)) + mNavigator->removeAgent(getPathfindingHalfExtents(ptr)); + ptr.getCellRef().setScale(scale); mWorldScene->updateObjectScale(ptr); + + if (mPhysics->getActor(ptr)) + mNavigator->addAgent(getPathfindingHalfExtents(ptr)); + else if (const auto object = mPhysics->getObject(ptr)) + mShouldUpdateNavigator = updateNavigatorObject(object) || mShouldUpdateNavigator; } void World::rotateObjectImp (const Ptr& ptr, const osg::Vec3f& rot, bool adjust) @@ -1389,7 +1378,12 @@ namespace MWWorld ptr.getRefData().setPosition(pos); if(ptr.getRefData().getBaseNode() != 0) + { mWorldScene->updateObjectRotation(ptr, true); + + if (const auto object = mPhysics->getObject(ptr)) + updateNavigatorObject(object); + } } void World::adjustPosition(const Ptr &ptr, bool force) @@ -1583,19 +1577,20 @@ namespace MWWorld void World::updateNavigator() { - bool updated = false; - mPhysics->forEachAnimatedObject([&] (const MWPhysics::Object* object) { - updated = updateNavigatorObject(object) || updated; + mShouldUpdateNavigator = updateNavigatorObject(object) || mShouldUpdateNavigator; }); for (const auto& door : mDoorStates) if (const auto object = mPhysics->getObject(door.first)) - updated = updateNavigatorObject(object) || updated; + mShouldUpdateNavigator = updateNavigatorObject(object) || mShouldUpdateNavigator; - if (updated) + if (mShouldUpdateNavigator) + { mNavigator->update(getPlayerPtr().getRefData().getPosition().asVec3()); + mShouldUpdateNavigator = false; + } } bool World::updateNavigatorObject(const MWPhysics::Object* object) @@ -1651,13 +1646,13 @@ namespace MWWorld /// \todo should use convexSweepTest here std::vector collisions = mPhysics->getCollisions(it->first, MWPhysics::CollisionType_Door, MWPhysics::CollisionType_Actor); - for (std::vector::iterator cit = collisions.begin(); cit != collisions.end(); ++cit) + for (MWWorld::Ptr& ptr : collisions) { - MWWorld::Ptr ptr = *cit; if (ptr.getClass().isActor()) { // Collided with actor, ask actor to try to avoid door - if(ptr != getPlayerPtr() ) { + if(ptr != getPlayerPtr() ) + { MWMechanics::AiSequence& seq = ptr.getClass().getCreatureStats(ptr).getAiSequence(); if(seq.getTypeId() != MWMechanics::AiPackage::TypeIdAvoidDoor) //Only add it once seq.stack(MWMechanics::AiAvoidDoor(it->first),ptr); @@ -2427,7 +2422,7 @@ namespace MWWorld { // Remove the old CharacterController MWBase::Environment::get().getMechanicsManager()->remove(getPlayerPtr()); - mNavigator->removeAgent(mPhysics->getHalfExtents(getPlayerPtr())); + mNavigator->removeAgent(getPathfindingHalfExtents(getPlayerConstPtr())); mPhysics->remove(getPlayerPtr()); mRendering->removePlayer(getPlayerPtr()); @@ -2462,7 +2457,8 @@ namespace MWWorld applyLoopingParticles(player); - mNavigator->addAgent(mPhysics->getHalfExtents(getPlayerPtr())); + mDefaultHalfExtents = mPhysics->getOriginalHalfExtents(getPlayerPtr()); + mNavigator->addAgent(getPathfindingHalfExtents(getPlayerConstPtr())); } World::RestPermitted World::canRest () const @@ -3284,9 +3280,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, @@ -3437,6 +3433,11 @@ namespace MWWorld return mPlayer->getPlayer(); } + MWWorld::ConstPtr World::getPlayerConstPtr() const + { + return mPlayer->getConstPlayer(); + } + void World::updateDialogueGlobals() { MWWorld::Ptr player = getPlayerPtr(); @@ -3602,18 +3603,17 @@ namespace MWWorld const std::string& id, const std::string& sourceName, const bool fromProjectile) { std::map > toApply; - for (std::vector::const_iterator effectIt = effects.mList.begin(); - effectIt != effects.mList.end(); ++effectIt) + for (const ESM::ENAMstruct& effectInfo : effects.mList) { - const ESM::MagicEffect* effect = mStore.get().find(effectIt->mEffectID); + const ESM::MagicEffect* effect = mStore.get().find(effectInfo.mEffectID); - if (effectIt->mRange != rangeType || (effectIt->mArea <= 0 && !ignore.isEmpty() && ignore.getClass().isActor())) + if (effectInfo.mRange != rangeType || (effectInfo.mArea <= 0 && !ignore.isEmpty() && ignore.getClass().isActor())) continue; // Not right range type, or not area effect and hit an actor - if (fromProjectile && effectIt->mArea <= 0) + if (fromProjectile && effectInfo.mArea <= 0) continue; // Don't play explosion for projectiles with 0-area effects - if (!fromProjectile && effectIt->mRange == ESM::RT_Touch && (!ignore.isEmpty()) && (!ignore.getClass().isActor() && !ignore.getClass().canBeActivated(ignore))) + if (!fromProjectile && effectInfo.mRange == ESM::RT_Touch && (!ignore.isEmpty()) && (!ignore.getClass().isActor() && !ignore.getClass().canBeActivated(ignore))) continue; // Don't play explosion for touch spells on non-activatable objects except when spell is from the projectile enchantment // Spawn the explosion orb effect @@ -3625,14 +3625,14 @@ namespace MWWorld std::string texture = effect->mParticle; - if (effectIt->mArea <= 0) + if (effectInfo.mArea <= 0) { - if (effectIt->mRange == ESM::RT_Target) + if (effectInfo.mRange == ESM::RT_Target) mRendering->spawnEffect("meshes\\" + areaStatic->mModel, texture, origin, 1.0f); continue; } else - mRendering->spawnEffect("meshes\\" + areaStatic->mModel, texture, origin, static_cast(effectIt->mArea * 2)); + mRendering->spawnEffect("meshes\\" + areaStatic->mModel, texture, origin, static_cast(effectInfo.mArea * 2)); // Play explosion sound (make sure to use NoTrack, since we will delete the projectile now) static const std::string schools[] = { @@ -3648,40 +3648,40 @@ namespace MWWorld // Get the actors in range of the effect std::vector objects; MWBase::Environment::get().getMechanicsManager()->getObjectsInRange( - origin, feetToGameUnits(static_cast(effectIt->mArea)), objects); + origin, feetToGameUnits(static_cast(effectInfo.mArea)), objects); for (const Ptr& affected : objects) { // Ignore actors without collisions here, otherwise it will be possible to hit actors outside processing range. if (affected.getClass().isActor() && !isActorCollisionEnabled(affected)) continue; - toApply[affected].push_back(*effectIt); + toApply[affected].push_back(effectInfo); } } // Now apply the appropriate effects to each actor in range - for (std::map >::iterator apply = toApply.begin(); apply != toApply.end(); ++apply) + for (auto& applyPair : toApply) { MWWorld::Ptr source = caster; // Vanilla-compatible behaviour of never applying the spell to the caster // (could be changed by mods later) - if (apply->first == caster) + if (applyPair.first == caster) continue; - if (apply->first == ignore) + if (applyPair.first == ignore) continue; if (source.isEmpty()) - source = apply->first; + source = applyPair.first; - MWMechanics::CastSpell cast(source, apply->first); + MWMechanics::CastSpell cast(source, applyPair.first); cast.mHitPosition = origin; cast.mId = id; cast.mSourceName = sourceName; cast.mStack = false; ESM::EffectList effectsToApply; - effectsToApply.mList = apply->second; - cast.inflict(apply->first, caster, effectsToApply, rangeType, false, true); + effectsToApply.mList = applyPair.second; + cast.inflict(applyPair.first, caster, effectsToApply, rangeType, false, true); } } @@ -3765,22 +3765,22 @@ namespace MWWorld void World::preloadEffects(const ESM::EffectList *effectList) { - for (std::vector::const_iterator it = effectList->mList.begin(); it != effectList->mList.end(); ++it) + for (const ESM::ENAMstruct& effectInfo : effectList->mList) { - const ESM::MagicEffect *effect = mStore.get().find(it->mEffectID); + const ESM::MagicEffect *effect = mStore.get().find(effectInfo.mEffectID); - if (MWMechanics::isSummoningEffect(it->mEffectID)) + if (MWMechanics::isSummoningEffect(effectInfo.mEffectID)) { preload(mWorldScene.get(), mStore, "VFX_Summon_Start"); - preload(mWorldScene.get(), mStore, MWMechanics::getSummonedCreature(it->mEffectID)); + preload(mWorldScene.get(), mStore, MWMechanics::getSummonedCreature(effectInfo.mEffectID)); } preload(mWorldScene.get(), mStore, effect->mCasting); preload(mWorldScene.get(), mStore, effect->mHit); - if (it->mArea > 0) + if (effectInfo.mArea > 0) preload(mWorldScene.get(), mStore, effect->mArea); - if (it->mRange == ESM::RT_Target) + if (effectInfo.mRange == ESM::RT_Target) preload(mWorldScene.get(), mStore, effect->mBolt); } } @@ -3806,4 +3806,12 @@ namespace MWWorld mRendering->setNavMeshNumber(value); } + osg::Vec3f World::getPathfindingHalfExtents(const MWWorld::ConstPtr& actor) const + { + if (actor.isInCell() && actor.getCell()->isExterior()) + return mDefaultHalfExtents; // Using default half extents for better performance + else + return getHalfExtents(actor); + } + } diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 98e82bb86..d3e445cff 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -111,14 +111,15 @@ namespace MWWorld std::string mUserDataPath; + osg::Vec3f mDefaultHalfExtents; + bool mShouldUpdateNavigator = false; + // not implemented World (const World&); World& operator= (const World&); int mActivationDistanceOverride; - std::string mStartupScript; - std::map mDoorStates; ///< only holds doors that are currently moving. 1 = opening, 2 = closing @@ -198,7 +199,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(); @@ -238,6 +239,7 @@ namespace MWWorld Player& getPlayer() override; MWWorld::Ptr getPlayerPtr() override; + MWWorld::ConstPtr getPlayerConstPtr() const override; const MWWorld::ESMStore& getStore() const override; @@ -574,7 +576,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; @@ -717,6 +719,9 @@ namespace MWWorld void removeActorPath(const MWWorld::ConstPtr& actor) const override; void setNavMeshNumberToRender(const std::size_t value) override; + + /// Return physical half extents of the given actor to be used in pathfinding + osg::Vec3f getPathfindingHalfExtents(const MWWorld::ConstPtr& actor) const override; }; } diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index 907052882..12775035b 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -24,6 +24,7 @@ if (GTEST_FOUND AND GMOCK_FOUND) detournavigator/gettilespositions.cpp detournavigator/recastmeshobject.cpp detournavigator/navmeshtilescache.cpp + detournavigator/tilecachedrecastmeshmanager.cpp ) source_group(apps\\openmw_test_suite FILES openmw_test_suite.cpp ${UNITTEST_SRC_FILES}) diff --git a/apps/openmw_test_suite/detournavigator/navigator.cpp b/apps/openmw_test_suite/detournavigator/navigator.cpp index febbc0387..5f5433b05 100644 --- a/apps/openmw_test_suite/detournavigator/navigator.cpp +++ b/apps/openmw_test_suite/detournavigator/navigator.cpp @@ -1,6 +1,6 @@ #include "operators.hpp" -#include +#include #include #include @@ -27,6 +27,7 @@ namespace osg::Vec3f mEnd; std::deque mPath; std::back_insert_iterator> mOut; + float mStepSize; DetourNavigatorNavigatorTest() : mPlayerPosition(0, 0, 0) @@ -34,6 +35,7 @@ namespace , mStart(-215, 215, 1) , mEnd(215, -215, 1) , mOut(mPath) + , mStepSize(28.333332061767578125f) { mSettings.mEnableWriteRecastMeshToFile = false; mSettings.mEnableWriteNavMeshToFile = false; @@ -61,26 +63,21 @@ namespace mSettings.mMaxSmoothPathSize = 1024; mSettings.mTrianglesPerChunk = 256; mSettings.mMaxPolys = 4096; - mNavigator.reset(new Navigator(mSettings)); + mSettings.mMaxTilesNumber = 512; + mNavigator.reset(new NavigatorImpl(mSettings)); } }; - TEST_F(DetourNavigatorNavigatorTest, find_path_for_empty_should_throw_exception) + TEST_F(DetourNavigatorNavigatorTest, find_path_for_empty_should_return_empty) { - EXPECT_THROW(mNavigator->findPath(mAgentHalfExtents, mStart, mEnd, Flag_walk, mOut), InvalidArgument); + mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mOut); + EXPECT_EQ(mPath, std::deque()); } TEST_F(DetourNavigatorNavigatorTest, find_path_for_existing_agent_with_no_navmesh_should_throw_exception) { mNavigator->addAgent(mAgentHalfExtents); - EXPECT_THROW(mNavigator->findPath(mAgentHalfExtents, mStart, mEnd, Flag_walk, mOut), NavigatorException); - } - - TEST_F(DetourNavigatorNavigatorTest, find_path_for_removed_agent_should_throw_exception) - { - mNavigator->addAgent(mAgentHalfExtents); - mNavigator->removeAgent(mAgentHalfExtents); - EXPECT_THROW(mNavigator->findPath(mAgentHalfExtents, mStart, mEnd, Flag_walk, mOut), InvalidArgument); + EXPECT_THROW(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mOut), NavigatorException); } TEST_F(DetourNavigatorNavigatorTest, add_agent_should_count_each_agent) @@ -88,7 +85,7 @@ namespace mNavigator->addAgent(mAgentHalfExtents); mNavigator->addAgent(mAgentHalfExtents); mNavigator->removeAgent(mAgentHalfExtents); - EXPECT_THROW(mNavigator->findPath(mAgentHalfExtents, mStart, mEnd, Flag_walk, mOut), NavigatorException); + EXPECT_THROW(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mOut), NavigatorException); } TEST_F(DetourNavigatorNavigatorTest, update_then_find_path_should_return_path) @@ -108,31 +105,31 @@ namespace mNavigator->update(mPlayerPosition); mNavigator->wait(); - mNavigator->findPath(mAgentHalfExtents, mStart, mEnd, Flag_walk, mOut); + mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mOut); EXPECT_EQ(mPath, std::deque({ osg::Vec3f(-215, 215, 1.85963428020477294921875), - osg::Vec3f(-194.9653167724609375, 194.9653167724609375, -6.5760211944580078125), + osg::Vec3f(-194.9653167724609375, 194.9653167724609375, -6.57602214813232421875), osg::Vec3f(-174.930633544921875, 174.930633544921875, -15.01167774200439453125), - osg::Vec3f(-154.8959503173828125, 154.8959503173828125, -23.4473323822021484375), - osg::Vec3f(-134.86126708984375, 134.86126708984375, -31.8829898834228515625), + osg::Vec3f(-154.8959503173828125, 154.8959503173828125, -23.4473361968994140625), + osg::Vec3f(-134.86126708984375, 134.86126708984375, -31.8829936981201171875), osg::Vec3f(-114.82657623291015625, 114.82657623291015625, -40.3186492919921875), - osg::Vec3f(-94.7918853759765625, 94.7918853759765625, -47.39907073974609375), + osg::Vec3f(-94.7918853759765625, 94.7918853759765625, -47.3990631103515625), osg::Vec3f(-74.75719451904296875, 74.75719451904296875, -53.7258148193359375), osg::Vec3f(-54.722499847412109375, 54.722499847412109375, -60.052555084228515625), - osg::Vec3f(-34.68780517578125, 34.68780517578125, -66.37929534912109375), + osg::Vec3f(-34.68780517578125, 34.68780517578125, -66.37931060791015625), osg::Vec3f(-14.6531162261962890625, 14.6531162261962890625, -72.70604705810546875), osg::Vec3f(5.3815765380859375, -5.3815765380859375, -75.35065460205078125), - osg::Vec3f(25.41626739501953125, -25.41626739501953125, -67.96945953369140625), - osg::Vec3f(45.450958251953125, -45.450958251953125, -60.58824920654296875), + osg::Vec3f(25.41626739501953125, -25.41626739501953125, -67.9694671630859375), + osg::Vec3f(45.450958251953125, -45.450958251953125, -60.5882568359375), osg::Vec3f(65.48564910888671875, -65.48564910888671875, -53.20705413818359375), - osg::Vec3f(85.5203399658203125, -85.5203399658203125, -45.825855255126953125), + osg::Vec3f(85.5203399658203125, -85.5203399658203125, -45.82585906982421875), osg::Vec3f(105.55503082275390625, -105.55503082275390625, -38.44464874267578125), osg::Vec3f(125.5897216796875, -125.5897216796875, -31.063449859619140625), osg::Vec3f(145.6244049072265625, -145.6244049072265625, -23.6822509765625), osg::Vec3f(165.659088134765625, -165.659088134765625, -16.3010540008544921875), - osg::Vec3f(185.6937713623046875, -185.6937713623046875, -8.91985416412353515625), - osg::Vec3f(205.7284698486328125, -205.7284698486328125, -1.53864824771881103515625), + osg::Vec3f(185.6937713623046875, -185.6937713623046875, -8.91985321044921875), + osg::Vec3f(205.7284698486328125, -205.7284698486328125, -1.5386486053466796875), osg::Vec3f(215, -215, 1.877177715301513671875), })) << mPath; } @@ -158,31 +155,31 @@ namespace mNavigator->update(mPlayerPosition); mNavigator->wait(); - mNavigator->findPath(mAgentHalfExtents, mStart, mEnd, Flag_walk, std::back_inserter(mPath)); + mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, std::back_inserter(mPath)); EXPECT_EQ(mPath, std::deque({ osg::Vec3f(-215, 215, 1.85963428020477294921875), - osg::Vec3f(-194.9653167724609375, 194.9653167724609375, -6.5760211944580078125), + osg::Vec3f(-194.9653167724609375, 194.9653167724609375, -6.57602214813232421875), osg::Vec3f(-174.930633544921875, 174.930633544921875, -15.01167774200439453125), - osg::Vec3f(-154.8959503173828125, 154.8959503173828125, -23.4473323822021484375), - osg::Vec3f(-134.86126708984375, 134.86126708984375, -31.8829898834228515625), + osg::Vec3f(-154.8959503173828125, 154.8959503173828125, -23.4473361968994140625), + osg::Vec3f(-134.86126708984375, 134.86126708984375, -31.8829936981201171875), osg::Vec3f(-114.82657623291015625, 114.82657623291015625, -40.3186492919921875), - osg::Vec3f(-94.7918853759765625, 94.7918853759765625, -47.39907073974609375), + osg::Vec3f(-94.7918853759765625, 94.7918853759765625, -47.3990631103515625), osg::Vec3f(-74.75719451904296875, 74.75719451904296875, -53.7258148193359375), osg::Vec3f(-54.722499847412109375, 54.722499847412109375, -60.052555084228515625), - osg::Vec3f(-34.68780517578125, 34.68780517578125, -66.37929534912109375), + osg::Vec3f(-34.68780517578125, 34.68780517578125, -66.37931060791015625), osg::Vec3f(-14.6531162261962890625, 14.6531162261962890625, -72.70604705810546875), osg::Vec3f(5.3815765380859375, -5.3815765380859375, -75.35065460205078125), - osg::Vec3f(25.41626739501953125, -25.41626739501953125, -67.96945953369140625), - osg::Vec3f(45.450958251953125, -45.450958251953125, -60.58824920654296875), + osg::Vec3f(25.41626739501953125, -25.41626739501953125, -67.9694671630859375), + osg::Vec3f(45.450958251953125, -45.450958251953125, -60.5882568359375), osg::Vec3f(65.48564910888671875, -65.48564910888671875, -53.20705413818359375), - osg::Vec3f(85.5203399658203125, -85.5203399658203125, -45.825855255126953125), + osg::Vec3f(85.5203399658203125, -85.5203399658203125, -45.82585906982421875), osg::Vec3f(105.55503082275390625, -105.55503082275390625, -38.44464874267578125), osg::Vec3f(125.5897216796875, -125.5897216796875, -31.063449859619140625), osg::Vec3f(145.6244049072265625, -145.6244049072265625, -23.6822509765625), osg::Vec3f(165.659088134765625, -165.659088134765625, -16.3010540008544921875), - osg::Vec3f(185.6937713623046875, -185.6937713623046875, -8.91985416412353515625), - osg::Vec3f(205.7284698486328125, -205.7284698486328125, -1.53864824771881103515625), + osg::Vec3f(185.6937713623046875, -185.6937713623046875, -8.91985321044921875), + osg::Vec3f(205.7284698486328125, -205.7284698486328125, -1.5386486053466796875), osg::Vec3f(215, -215, 1.877177715301513671875), })) << mPath; @@ -191,16 +188,16 @@ namespace mNavigator->wait(); mPath.clear(); - mNavigator->findPath(mAgentHalfExtents, mStart, mEnd, Flag_walk, std::back_inserter(mPath)); + mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, std::back_inserter(mPath)); EXPECT_EQ(mPath, std::deque({ - osg::Vec3f(-215, 215, 1.87827122211456298828125), + osg::Vec3f(-215, 215, 1.87826788425445556640625), osg::Vec3f(-199.7968292236328125, 191.09100341796875, -3.54876613616943359375), - osg::Vec3f(-184.5936431884765625, 167.1819915771484375, -8.97847270965576171875), - osg::Vec3f(-169.3904571533203125, 143.2729949951171875, -14.40817737579345703125), + osg::Vec3f(-184.5936431884765625, 167.1819915771484375, -8.97847843170166015625), + osg::Vec3f(-169.3904571533203125, 143.2729949951171875, -14.408184051513671875), osg::Vec3f(-154.1872711181640625, 119.36397552490234375, -19.837890625), - osg::Vec3f(-138.9840850830078125, 95.45496368408203125, -25.2675952911376953125), - osg::Vec3f(-123.78090667724609375, 71.54595184326171875, -30.6972980499267578125), + osg::Vec3f(-138.9840850830078125, 95.45496368408203125, -25.2675991058349609375), + osg::Vec3f(-123.78090667724609375, 71.54595184326171875, -30.6973056793212890625), osg::Vec3f(-108.57772064208984375, 47.636936187744140625, -36.12701416015625), osg::Vec3f(-93.3745269775390625, 23.7279262542724609375, -40.754688262939453125), osg::Vec3f(-78.17134857177734375, -0.18108306825160980224609375, -37.128787994384765625), @@ -208,15 +205,15 @@ namespace osg::Vec3f(-47.764972686767578125, -47.999103546142578125, -30.797946929931640625), osg::Vec3f(-23.852447509765625, -63.196765899658203125, -33.97112274169921875), osg::Vec3f(0.0600789971649646759033203125, -78.39443206787109375, -37.14543914794921875), - osg::Vec3f(23.97260284423828125, -93.5920867919921875, -40.7740936279296875), - osg::Vec3f(47.885128021240234375, -108.78974151611328125, -36.051288604736328125), - osg::Vec3f(71.7976531982421875, -123.98740386962890625, -30.62355804443359375), - osg::Vec3f(95.71018218994140625, -139.18505859375, -25.1958160400390625), + osg::Vec3f(23.97260284423828125, -93.5920867919921875, -40.774089813232421875), + osg::Vec3f(47.885128021240234375, -108.78974151611328125, -36.05129241943359375), + osg::Vec3f(71.7976531982421875, -123.98740386962890625, -30.6235561370849609375), + osg::Vec3f(95.71018218994140625, -139.18505859375, -25.1958255767822265625), osg::Vec3f(119.6226959228515625, -154.382720947265625, -19.7680912017822265625), - osg::Vec3f(143.53521728515625, -169.58038330078125, -14.3403491973876953125), - osg::Vec3f(167.4477386474609375, -184.778045654296875, -8.91261768341064453125), - osg::Vec3f(191.360260009765625, -199.9757080078125, -3.484879016876220703125), - osg::Vec3f(215, -215, 1.87827455997467041015625), + osg::Vec3f(143.53521728515625, -169.58038330078125, -14.34035205841064453125), + osg::Vec3f(167.4477386474609375, -184.778045654296875, -8.9126186370849609375), + osg::Vec3f(191.360260009765625, -199.9757080078125, -3.4848802089691162109375), + osg::Vec3f(215, -215, 1.87826788425445556640625), })) << mPath; } @@ -242,16 +239,16 @@ namespace mNavigator->update(mPlayerPosition); mNavigator->wait(); - mNavigator->findPath(mAgentHalfExtents, mStart, mEnd, Flag_walk, std::back_inserter(mPath)); + mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, std::back_inserter(mPath)); EXPECT_EQ(mPath, std::deque({ - osg::Vec3f(-215, 215, 1.87827122211456298828125), + osg::Vec3f(-215, 215, 1.87826788425445556640625), osg::Vec3f(-199.7968292236328125, 191.09100341796875, -3.54876613616943359375), - osg::Vec3f(-184.5936431884765625, 167.1819915771484375, -8.97847270965576171875), - osg::Vec3f(-169.3904571533203125, 143.2729949951171875, -14.40817737579345703125), + osg::Vec3f(-184.5936431884765625, 167.1819915771484375, -8.97847843170166015625), + osg::Vec3f(-169.3904571533203125, 143.2729949951171875, -14.408184051513671875), osg::Vec3f(-154.1872711181640625, 119.36397552490234375, -19.837890625), - osg::Vec3f(-138.9840850830078125, 95.45496368408203125, -25.2675952911376953125), - osg::Vec3f(-123.78090667724609375, 71.54595184326171875, -30.6972980499267578125), + osg::Vec3f(-138.9840850830078125, 95.45496368408203125, -25.2675991058349609375), + osg::Vec3f(-123.78090667724609375, 71.54595184326171875, -30.6973056793212890625), osg::Vec3f(-108.57772064208984375, 47.636936187744140625, -36.12701416015625), osg::Vec3f(-93.3745269775390625, 23.7279262542724609375, -40.754688262939453125), osg::Vec3f(-78.17134857177734375, -0.18108306825160980224609375, -37.128787994384765625), @@ -259,15 +256,15 @@ namespace osg::Vec3f(-47.764972686767578125, -47.999103546142578125, -30.797946929931640625), osg::Vec3f(-23.852447509765625, -63.196765899658203125, -33.97112274169921875), osg::Vec3f(0.0600789971649646759033203125, -78.39443206787109375, -37.14543914794921875), - osg::Vec3f(23.97260284423828125, -93.5920867919921875, -40.7740936279296875), - osg::Vec3f(47.885128021240234375, -108.78974151611328125, -36.051288604736328125), - osg::Vec3f(71.7976531982421875, -123.98740386962890625, -30.62355804443359375), - osg::Vec3f(95.71018218994140625, -139.18505859375, -25.1958160400390625), + osg::Vec3f(23.97260284423828125, -93.5920867919921875, -40.774089813232421875), + osg::Vec3f(47.885128021240234375, -108.78974151611328125, -36.05129241943359375), + osg::Vec3f(71.7976531982421875, -123.98740386962890625, -30.6235561370849609375), + osg::Vec3f(95.71018218994140625, -139.18505859375, -25.1958255767822265625), osg::Vec3f(119.6226959228515625, -154.382720947265625, -19.7680912017822265625), - osg::Vec3f(143.53521728515625, -169.58038330078125, -14.3403491973876953125), - osg::Vec3f(167.4477386474609375, -184.778045654296875, -8.91261768341064453125), - osg::Vec3f(191.360260009765625, -199.9757080078125, -3.484879016876220703125), - osg::Vec3f(215, -215, 1.87827455997467041015625), + osg::Vec3f(143.53521728515625, -169.58038330078125, -14.34035205841064453125), + osg::Vec3f(167.4477386474609375, -184.778045654296875, -8.9126186370849609375), + osg::Vec3f(191.360260009765625, -199.9757080078125, -3.4848802089691162109375), + osg::Vec3f(215, -215, 1.87826788425445556640625), })) << mPath; compoundShape.updateChildTransform(0, btTransform(btMatrix3x3::getIdentity(), btVector3(1000, 0, 0))); @@ -277,31 +274,31 @@ namespace mNavigator->wait(); mPath.clear(); - mNavigator->findPath(mAgentHalfExtents, mStart, mEnd, Flag_walk, mOut); + mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mOut); EXPECT_EQ(mPath, std::deque({ osg::Vec3f(-215, 215, 1.85963428020477294921875), - osg::Vec3f(-194.9653167724609375, 194.9653167724609375, -6.5760211944580078125), + osg::Vec3f(-194.9653167724609375, 194.9653167724609375, -6.57602214813232421875), osg::Vec3f(-174.930633544921875, 174.930633544921875, -15.01167774200439453125), - osg::Vec3f(-154.8959503173828125, 154.8959503173828125, -23.4473323822021484375), - osg::Vec3f(-134.86126708984375, 134.86126708984375, -31.8829898834228515625), + osg::Vec3f(-154.8959503173828125, 154.8959503173828125, -23.4473361968994140625), + osg::Vec3f(-134.86126708984375, 134.86126708984375, -31.8829936981201171875), osg::Vec3f(-114.82657623291015625, 114.82657623291015625, -40.3186492919921875), - osg::Vec3f(-94.7918853759765625, 94.7918853759765625, -47.39907073974609375), + osg::Vec3f(-94.7918853759765625, 94.7918853759765625, -47.3990631103515625), osg::Vec3f(-74.75719451904296875, 74.75719451904296875, -53.7258148193359375), osg::Vec3f(-54.722499847412109375, 54.722499847412109375, -60.052555084228515625), - osg::Vec3f(-34.68780517578125, 34.68780517578125, -66.37929534912109375), + osg::Vec3f(-34.68780517578125, 34.68780517578125, -66.37931060791015625), osg::Vec3f(-14.6531162261962890625, 14.6531162261962890625, -72.70604705810546875), osg::Vec3f(5.3815765380859375, -5.3815765380859375, -75.35065460205078125), - osg::Vec3f(25.41626739501953125, -25.41626739501953125, -67.96945953369140625), - osg::Vec3f(45.450958251953125, -45.450958251953125, -60.58824920654296875), + osg::Vec3f(25.41626739501953125, -25.41626739501953125, -67.9694671630859375), + osg::Vec3f(45.450958251953125, -45.450958251953125, -60.5882568359375), osg::Vec3f(65.48564910888671875, -65.48564910888671875, -53.20705413818359375), - osg::Vec3f(85.5203399658203125, -85.5203399658203125, -45.825855255126953125), + osg::Vec3f(85.5203399658203125, -85.5203399658203125, -45.82585906982421875), osg::Vec3f(105.55503082275390625, -105.55503082275390625, -38.44464874267578125), osg::Vec3f(125.5897216796875, -125.5897216796875, -31.063449859619140625), osg::Vec3f(145.6244049072265625, -145.6244049072265625, -23.6822509765625), osg::Vec3f(165.659088134765625, -165.659088134765625, -16.3010540008544921875), - osg::Vec3f(185.6937713623046875, -185.6937713623046875, -8.91985416412353515625), - osg::Vec3f(205.7284698486328125, -205.7284698486328125, -1.53864824771881103515625), + osg::Vec3f(185.6937713623046875, -185.6937713623046875, -8.91985321044921875), + osg::Vec3f(205.7284698486328125, -205.7284698486328125, -1.5386486053466796875), osg::Vec3f(215, -215, 1.877177715301513671875), })) << mPath; } @@ -334,31 +331,31 @@ namespace mNavigator->update(mPlayerPosition); mNavigator->wait(); - mNavigator->findPath(mAgentHalfExtents, mStart, mEnd, Flag_walk, mOut); + mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mOut); EXPECT_EQ(mPath, std::deque({ osg::Vec3f(-215, 215, 1.96328866481781005859375), - osg::Vec3f(-194.9653167724609375, 194.9653167724609375, -0.2422157227993011474609375), - osg::Vec3f(-174.930633544921875, 174.930633544921875, -2.44772052764892578125), - osg::Vec3f(-154.8959503173828125, 154.8959503173828125, -4.653223514556884765625), - osg::Vec3f(-134.86126708984375, 134.86126708984375, -6.858728885650634765625), - osg::Vec3f(-114.82657623291015625, 114.82657623291015625, -9.0642337799072265625), - osg::Vec3f(-94.7918853759765625, 94.7918853759765625, -11.26973724365234375), + osg::Vec3f(-194.9653167724609375, 194.9653167724609375, -0.242215454578399658203125), + osg::Vec3f(-174.930633544921875, 174.930633544921875, -2.447719097137451171875), + osg::Vec3f(-154.8959503173828125, 154.8959503173828125, -4.65322399139404296875), + osg::Vec3f(-134.86126708984375, 134.86126708984375, -6.858726978302001953125), + osg::Vec3f(-114.82657623291015625, 114.82657623291015625, -9.06423282623291015625), + osg::Vec3f(-94.7918853759765625, 94.7918853759765625, -11.26973628997802734375), osg::Vec3f(-74.75719451904296875, 74.75719451904296875, -13.26497173309326171875), - osg::Vec3f(-54.722499847412109375, 54.722499847412109375, -15.24860286712646484375), - osg::Vec3f(-34.68780517578125, 34.68780517578125, -17.2322368621826171875), - osg::Vec3f(-14.6531162261962890625, 14.6531162261962890625, -19.2158660888671875), + osg::Vec3f(-54.722499847412109375, 54.722499847412109375, -15.24860477447509765625), + osg::Vec3f(-34.68780517578125, 34.68780517578125, -17.23223876953125), + osg::Vec3f(-14.6531162261962890625, 14.6531162261962890625, -19.215869903564453125), osg::Vec3f(5.3815765380859375, -5.3815765380859375, -20.1338443756103515625), - osg::Vec3f(25.41626739501953125, -25.41626739501953125, -18.150211334228515625), + osg::Vec3f(25.41626739501953125, -25.41626739501953125, -18.1502132415771484375), osg::Vec3f(45.450958251953125, -45.450958251953125, -16.1665802001953125), - osg::Vec3f(65.48564910888671875, -65.48564910888671875, -14.18294811248779296875), - osg::Vec3f(85.5203399658203125, -85.5203399658203125, -12.19931507110595703125), - osg::Vec3f(105.55503082275390625, -105.55503082275390625, -10.08488559722900390625), - osg::Vec3f(125.5897216796875, -125.5897216796875, -7.879383563995361328125), - osg::Vec3f(145.6244049072265625, -145.6244049072265625, -5.673877239227294921875), - osg::Vec3f(165.659088134765625, -165.659088134765625, -3.4683735370635986328125), - osg::Vec3f(185.6937713623046875, -185.6937713623046875, -1.2628715038299560546875), - osg::Vec3f(205.7284698486328125, -205.7284698486328125, 0.9426348209381103515625), + osg::Vec3f(65.48564910888671875, -65.48564910888671875, -14.18294620513916015625), + osg::Vec3f(85.5203399658203125, -85.5203399658203125, -12.199314117431640625), + osg::Vec3f(105.55503082275390625, -105.55503082275390625, -10.08488368988037109375), + osg::Vec3f(125.5897216796875, -125.5897216796875, -7.87938022613525390625), + osg::Vec3f(145.6244049072265625, -145.6244049072265625, -5.673875331878662109375), + osg::Vec3f(165.659088134765625, -165.659088134765625, -3.468370914459228515625), + osg::Vec3f(185.6937713623046875, -185.6937713623046875, -1.26286637783050537109375), + osg::Vec3f(205.7284698486328125, -205.7284698486328125, 0.942641556262969970703125), osg::Vec3f(215, -215, 1.96328866481781005859375), })) << mPath; } @@ -390,33 +387,33 @@ namespace mNavigator->update(mPlayerPosition); mNavigator->wait(); - mNavigator->findPath(mAgentHalfExtents, mStart, mEnd, Flag_walk, mOut); + mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mOut); EXPECT_EQ(mPath, std::deque({ osg::Vec3f(-215, 215, 1.9393787384033203125), osg::Vec3f(-200.8159637451171875, 190.47265625, -0.639537751674652099609375), osg::Vec3f(-186.6319427490234375, 165.9453125, -3.2184507846832275390625), osg::Vec3f(-172.447906494140625, 141.41796875, -5.797363758087158203125), - osg::Vec3f(-158.263885498046875, 116.8906097412109375, -8.37627887725830078125), - osg::Vec3f(-144.079864501953125, 92.3632659912109375, -10.95519161224365234375), - osg::Vec3f(-129.89581298828125, 67.83591461181640625, -13.534107208251953125), + osg::Vec3f(-158.263885498046875, 116.8906097412109375, -8.37627696990966796875), + osg::Vec3f(-144.079864501953125, 92.3632659912109375, -10.9551906585693359375), + osg::Vec3f(-129.89581298828125, 67.83591461181640625, -13.53410625457763671875), osg::Vec3f(-115.7117919921875, 43.308563232421875, -16.1130199432373046875), osg::Vec3f(-101.5277557373046875, 18.7812137603759765625, -18.6919345855712890625), osg::Vec3f(-87.34372711181640625, -5.7461376190185546875, -20.4680538177490234375), osg::Vec3f(-67.02922821044921875, -25.4970550537109375, -20.514247894287109375), - osg::Vec3f(-46.714717864990234375, -45.2479705810546875, -20.5604457855224609375), + osg::Vec3f(-46.714717864990234375, -45.2479705810546875, -20.560443878173828125), osg::Vec3f(-26.40021514892578125, -64.99889373779296875, -20.6066417694091796875), osg::Vec3f(-6.085712432861328125, -84.74980926513671875, -20.652835845947265625), - osg::Vec3f(14.22879505157470703125, -104.50072479248046875, -18.151393890380859375), - osg::Vec3f(39.05098724365234375, -118.16222381591796875, -15.6674861907958984375), - osg::Vec3f(63.87317657470703125, -131.82373046875, -13.18357944488525390625), - osg::Vec3f(88.69537353515625, -145.4852142333984375, -10.69967365264892578125), - osg::Vec3f(113.51757049560546875, -159.146697998046875, -8.21576690673828125), - osg::Vec3f(138.3397674560546875, -172.808197021484375, -5.731858730316162109375), - osg::Vec3f(163.1619720458984375, -186.469696044921875, -3.2479503154754638671875), - osg::Vec3f(187.984161376953125, -200.1311798095703125, -0.764044582843780517578125), - osg::Vec3f(212.8063507080078125, -213.7926788330078125, 1.7198636531829833984375), - osg::Vec3f(215, -215, 1.93937528133392333984375), + osg::Vec3f(14.22879505157470703125, -104.50072479248046875, -18.151397705078125), + osg::Vec3f(39.05098724365234375, -118.16222381591796875, -15.66748714447021484375), + osg::Vec3f(63.87317657470703125, -131.82373046875, -13.18358135223388671875), + osg::Vec3f(88.69537353515625, -145.4852142333984375, -10.699672698974609375), + osg::Vec3f(113.51757049560546875, -159.146697998046875, -8.21576786041259765625), + osg::Vec3f(138.3397674560546875, -172.808197021484375, -5.731859683990478515625), + osg::Vec3f(163.1619720458984375, -186.469696044921875, -3.2479507923126220703125), + osg::Vec3f(187.984161376953125, -200.1311798095703125, -0.764044821262359619140625), + osg::Vec3f(212.8063507080078125, -213.7926788330078125, 1.719865322113037109375), + osg::Vec3f(215, -215, 1.9393787384033203125), })) << mPath; } @@ -443,7 +440,7 @@ namespace mEnd.x() = 0; mEnd.z() = 300; - mNavigator->findPath(mAgentHalfExtents, mStart, mEnd, Flag_swim, mOut); + mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_swim, mOut); EXPECT_EQ(mPath, std::deque({ osg::Vec3f(0, 215, 185.33331298828125), @@ -489,7 +486,7 @@ namespace mStart.x() = 0; mEnd.x() = 0; - mNavigator->findPath(mAgentHalfExtents, mStart, mEnd, Flag_swim | Flag_walk, mOut); + mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_swim | Flag_walk, mOut); EXPECT_EQ(mPath, std::deque({ osg::Vec3f(0, 215, -94.75363922119140625), @@ -505,10 +502,10 @@ namespace osg::Vec3f(0, -68.33331298828125, -143.3333587646484375), osg::Vec3f(0, -96.66664886474609375, -137.3043670654296875), osg::Vec3f(0, -124.99997711181640625, -127.44930267333984375), - osg::Vec3f(0, -153.33331298828125, -117.5942230224609375), - osg::Vec3f(0, -181.6666412353515625, -107.7391510009765625), - osg::Vec3f(0, -209.999969482421875, -97.79712677001953125), - osg::Vec3f(0, -215, -94.753631591796875), + osg::Vec3f(0, -153.33331298828125, -117.59423065185546875), + osg::Vec3f(0, -181.6666412353515625, -107.73915863037109375), + osg::Vec3f(0, -209.999969482421875, -97.7971343994140625), + osg::Vec3f(0, -215, -94.75363922119140625), })) << mPath; } @@ -535,7 +532,7 @@ namespace mStart.x() = 0; mEnd.x() = 0; - mNavigator->findPath(mAgentHalfExtents, mStart, mEnd, Flag_swim | Flag_walk, mOut); + mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_swim | Flag_walk, mOut); EXPECT_EQ(mPath, std::deque({ osg::Vec3f(0, 215, -94.75363922119140625), @@ -551,10 +548,10 @@ namespace osg::Vec3f(0, -68.33331298828125, -143.3333587646484375), osg::Vec3f(0, -96.66664886474609375, -137.3043670654296875), osg::Vec3f(0, -124.99997711181640625, -127.44930267333984375), - osg::Vec3f(0, -153.33331298828125, -117.5942230224609375), - osg::Vec3f(0, -181.6666412353515625, -107.7391510009765625), - osg::Vec3f(0, -209.999969482421875, -97.79712677001953125), - osg::Vec3f(0, -215, -94.753631591796875), + osg::Vec3f(0, -153.33331298828125, -117.59423065185546875), + osg::Vec3f(0, -181.6666412353515625, -107.73915863037109375), + osg::Vec3f(0, -209.999969482421875, -97.7971343994140625), + osg::Vec3f(0, -215, -94.75363922119140625), })) << mPath; } @@ -581,12 +578,12 @@ namespace mStart.x() = 0; mEnd.x() = 0; - mNavigator->findPath(mAgentHalfExtents, mStart, mEnd, Flag_walk, mOut); + mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mOut); EXPECT_EQ(mPath, std::deque({ osg::Vec3f(0, 215, -94.75363922119140625), - osg::Vec3f(9.8083515167236328125, 188.4185333251953125, -105.19994354248046875), - osg::Vec3f(19.6167049407958984375, 161.837066650390625, -114.25496673583984375), + osg::Vec3f(9.8083515167236328125, 188.4185333251953125, -105.199951171875), + osg::Vec3f(19.6167049407958984375, 161.837066650390625, -114.25495147705078125), osg::Vec3f(29.42505645751953125, 135.255615234375, -123.309967041015625), osg::Vec3f(39.23340606689453125, 108.674163818359375, -132.3649749755859375), osg::Vec3f(49.04175567626953125, 82.09270477294921875, -137.2874755859375), @@ -599,9 +596,9 @@ namespace osg::Vec3f(64.8477935791015625, -104.598602294921875, -137.840911865234375), osg::Vec3f(50.497714996337890625, -129.0291748046875, -131.45831298828125), osg::Vec3f(36.147632598876953125, -153.459747314453125, -121.42321014404296875), - osg::Vec3f(21.7975559234619140625, -177.8903350830078125, -111.38809967041015625), - osg::Vec3f(7.44747829437255859375, -202.3209075927734375, -101.1938323974609375), - osg::Vec3f(0, -215, -94.753631591796875), + osg::Vec3f(21.7975559234619140625, -177.8903350830078125, -111.38811492919921875), + osg::Vec3f(7.44747829437255859375, -202.3209075927734375, -101.19382476806640625), + osg::Vec3f(0, -215, -94.75363922119140625), })) << mPath; } @@ -630,31 +627,31 @@ namespace mNavigator->update(mPlayerPosition); mNavigator->wait(); - mNavigator->findPath(mAgentHalfExtents, mStart, mEnd, Flag_walk, mOut); + mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mOut); EXPECT_EQ(mPath, std::deque({ osg::Vec3f(-215, 215, 1.85963428020477294921875), - osg::Vec3f(-194.9653167724609375, 194.9653167724609375, -6.5760211944580078125), + osg::Vec3f(-194.9653167724609375, 194.9653167724609375, -6.57602214813232421875), osg::Vec3f(-174.930633544921875, 174.930633544921875, -15.01167774200439453125), - osg::Vec3f(-154.8959503173828125, 154.8959503173828125, -23.4473323822021484375), - osg::Vec3f(-134.86126708984375, 134.86126708984375, -31.8829898834228515625), + osg::Vec3f(-154.8959503173828125, 154.8959503173828125, -23.4473361968994140625), + osg::Vec3f(-134.86126708984375, 134.86126708984375, -31.8829936981201171875), osg::Vec3f(-114.82657623291015625, 114.82657623291015625, -40.3186492919921875), - osg::Vec3f(-94.7918853759765625, 94.7918853759765625, -47.39907073974609375), + osg::Vec3f(-94.7918853759765625, 94.7918853759765625, -47.3990631103515625), osg::Vec3f(-74.75719451904296875, 74.75719451904296875, -53.7258148193359375), osg::Vec3f(-54.722499847412109375, 54.722499847412109375, -60.052555084228515625), - osg::Vec3f(-34.68780517578125, 34.68780517578125, -66.37929534912109375), + osg::Vec3f(-34.68780517578125, 34.68780517578125, -66.37931060791015625), osg::Vec3f(-14.6531162261962890625, 14.6531162261962890625, -72.70604705810546875), osg::Vec3f(5.3815765380859375, -5.3815765380859375, -75.35065460205078125), - osg::Vec3f(25.41626739501953125, -25.41626739501953125, -67.96945953369140625), - osg::Vec3f(45.450958251953125, -45.450958251953125, -60.58824920654296875), + osg::Vec3f(25.41626739501953125, -25.41626739501953125, -67.9694671630859375), + osg::Vec3f(45.450958251953125, -45.450958251953125, -60.5882568359375), osg::Vec3f(65.48564910888671875, -65.48564910888671875, -53.20705413818359375), - osg::Vec3f(85.5203399658203125, -85.5203399658203125, -45.825855255126953125), + osg::Vec3f(85.5203399658203125, -85.5203399658203125, -45.82585906982421875), osg::Vec3f(105.55503082275390625, -105.55503082275390625, -38.44464874267578125), osg::Vec3f(125.5897216796875, -125.5897216796875, -31.063449859619140625), osg::Vec3f(145.6244049072265625, -145.6244049072265625, -23.6822509765625), osg::Vec3f(165.659088134765625, -165.659088134765625, -16.3010540008544921875), - osg::Vec3f(185.6937713623046875, -185.6937713623046875, -8.91985416412353515625), - osg::Vec3f(205.7284698486328125, -205.7284698486328125, -1.53864824771881103515625), + osg::Vec3f(185.6937713623046875, -185.6937713623046875, -8.91985321044921875), + osg::Vec3f(205.7284698486328125, -205.7284698486328125, -1.5386486053466796875), osg::Vec3f(215, -215, 1.877177715301513671875), })) << mPath; } diff --git a/apps/openmw_test_suite/detournavigator/navmeshtilescache.cpp b/apps/openmw_test_suite/detournavigator/navmeshtilescache.cpp index 17b17b97c..a3a1816ad 100644 --- a/apps/openmw_test_suite/detournavigator/navmeshtilescache.cpp +++ b/apps/openmw_test_suite/detournavigator/navmeshtilescache.cpp @@ -34,6 +34,14 @@ namespace const std::vector mOffMeshConnections {}; unsigned char* const mData = reinterpret_cast(dtAlloc(1, DT_ALLOC_PERM)); NavMeshData mNavMeshData {mData, 1}; + + const size_t cRecastMeshKeySize = mRecastMesh.getIndices().size() * sizeof(int) + + mRecastMesh.getVertices().size() * sizeof(float) + + mRecastMesh.getAreaTypes().size() * sizeof(AreaType) + + mRecastMesh.getWater().size() * sizeof(RecastMesh::Water) + + mOffMeshConnections.size() * sizeof(OffMeshConnection); + + const size_t cRecastMeshWithWaterKeySize = cRecastMeshKeySize + sizeof(RecastMesh::Water); }; TEST_F(DetourNavigatorNavMeshTilesCacheTest, get_for_empty_cache_should_return_empty_value) @@ -56,7 +64,7 @@ namespace TEST_F(DetourNavigatorNavMeshTilesCacheTest, set_should_return_cached_value) { const std::size_t navMeshDataSize = 1; - const std::size_t navMeshKeySize = 49; + const std::size_t navMeshKeySize = cRecastMeshKeySize; const std::size_t maxSize = navMeshDataSize + 2 * navMeshKeySize; NavMeshTilesCache cache(maxSize); @@ -69,7 +77,7 @@ namespace TEST_F(DetourNavigatorNavMeshTilesCacheTest, set_existing_element_should_throw_exception) { const std::size_t navMeshDataSize = 1; - const std::size_t navMeshKeySize = 49; + const std::size_t navMeshKeySize = cRecastMeshKeySize; const std::size_t maxSize = 2 * (navMeshDataSize + 2 * navMeshKeySize); NavMeshTilesCache cache(maxSize); const auto anotherData = reinterpret_cast(dtAlloc(1, DT_ALLOC_PERM)); @@ -85,7 +93,7 @@ namespace TEST_F(DetourNavigatorNavMeshTilesCacheTest, get_should_return_cached_value) { const std::size_t navMeshDataSize = 1; - const std::size_t navMeshKeySize = 49; + const std::size_t navMeshKeySize = cRecastMeshKeySize; const std::size_t maxSize = navMeshDataSize + 2 * navMeshKeySize; NavMeshTilesCache cache(maxSize); @@ -129,7 +137,7 @@ namespace TEST_F(DetourNavigatorNavMeshTilesCacheTest, set_should_replace_unused_value) { const std::size_t navMeshDataSize = 1; - const std::size_t navMeshKeySize = 117; + const std::size_t navMeshKeySize = cRecastMeshWithWaterKeySize; const std::size_t maxSize = navMeshDataSize + 2 * navMeshKeySize; NavMeshTilesCache cache(maxSize); @@ -149,7 +157,7 @@ namespace TEST_F(DetourNavigatorNavMeshTilesCacheTest, set_should_not_replace_used_value) { const std::size_t navMeshDataSize = 1; - const std::size_t navMeshKeySize = 49; + const std::size_t navMeshKeySize = cRecastMeshKeySize; const std::size_t maxSize = navMeshDataSize + 2 * navMeshKeySize; NavMeshTilesCache cache(maxSize); @@ -167,7 +175,7 @@ namespace TEST_F(DetourNavigatorNavMeshTilesCacheTest, set_should_replace_unused_least_recently_set_value) { const std::size_t navMeshDataSize = 1; - const std::size_t navMeshKeySize = 117; + const std::size_t navMeshKeySize = cRecastMeshWithWaterKeySize; const std::size_t maxSize = 2 * (navMeshDataSize + 2 * navMeshKeySize); NavMeshTilesCache cache(maxSize); @@ -199,7 +207,7 @@ namespace TEST_F(DetourNavigatorNavMeshTilesCacheTest, set_should_replace_unused_least_recently_used_value) { const std::size_t navMeshDataSize = 1; - const std::size_t navMeshKeySize = 117; + const std::size_t navMeshKeySize = cRecastMeshWithWaterKeySize; const std::size_t maxSize = 2 * (navMeshDataSize + 2 * navMeshKeySize); NavMeshTilesCache cache(maxSize); @@ -243,7 +251,7 @@ namespace TEST_F(DetourNavigatorNavMeshTilesCacheTest, set_should_not_replace_unused_least_recently_used_value_when_item_does_not_not_fit_cache_max_size) { const std::size_t navMeshDataSize = 1; - const std::size_t navMeshKeySize = 49; + const std::size_t navMeshKeySize = cRecastMeshKeySize; const std::size_t maxSize = 2 * (navMeshDataSize + 2 * navMeshKeySize); NavMeshTilesCache cache(maxSize); @@ -261,8 +269,8 @@ namespace TEST_F(DetourNavigatorNavMeshTilesCacheTest, set_should_not_replace_unused_least_recently_used_value_when_item_does_not_not_fit_size_of_unused_items) { const std::size_t navMeshDataSize = 1; - const std::size_t navMeshKeySize1 = 49; - const std::size_t navMeshKeySize2 = 117; + const std::size_t navMeshKeySize1 = cRecastMeshKeySize; + const std::size_t navMeshKeySize2 = cRecastMeshWithWaterKeySize; const std::size_t maxSize = 2 * navMeshDataSize + 2 * navMeshKeySize1 + 2 * navMeshKeySize2; NavMeshTilesCache cache(maxSize); @@ -290,7 +298,7 @@ namespace TEST_F(DetourNavigatorNavMeshTilesCacheTest, release_used_after_set_then_used_by_get_item_should_left_this_item_available) { const std::size_t navMeshDataSize = 1; - const std::size_t navMeshKeySize = 49; + const std::size_t navMeshKeySize = cRecastMeshKeySize; const std::size_t maxSize = navMeshDataSize + 2 * navMeshKeySize; NavMeshTilesCache cache(maxSize); @@ -313,7 +321,7 @@ namespace TEST_F(DetourNavigatorNavMeshTilesCacheTest, release_twice_used_item_should_left_this_item_available) { const std::size_t navMeshDataSize = 1; - const std::size_t navMeshKeySize = 49; + const std::size_t navMeshKeySize = cRecastMeshKeySize; const std::size_t maxSize = navMeshDataSize + 2 * navMeshKeySize; NavMeshTilesCache cache(maxSize); diff --git a/apps/openmw_test_suite/detournavigator/operators.hpp b/apps/openmw_test_suite/detournavigator/operators.hpp index 16d8f38f5..a473632ba 100644 --- a/apps/openmw_test_suite/detournavigator/operators.hpp +++ b/apps/openmw_test_suite/detournavigator/operators.hpp @@ -29,7 +29,10 @@ namespace testing for (const auto& v : value) { std::ostringstream stream; - stream << v; + stream << "osg::Vec3f(" + << std::setprecision(std::numeric_limits::max_exponent10) << v.x() << ", " + << std::setprecision(std::numeric_limits::max_exponent10) << v.y() << ", " + << std::setprecision(std::numeric_limits::max_exponent10) << v.z() << ")"; (*this) << stream.str() << ",\n"; } return (*this) << "}"; diff --git a/apps/openmw_test_suite/detournavigator/recastmeshobject.cpp b/apps/openmw_test_suite/detournavigator/recastmeshobject.cpp index 9b30cadd7..a3606f827 100644 --- a/apps/openmw_test_suite/detournavigator/recastmeshobject.cpp +++ b/apps/openmw_test_suite/detournavigator/recastmeshobject.cpp @@ -69,4 +69,11 @@ namespace object.update(mTransform, AreaType_ground); EXPECT_FALSE(object.update(mTransform, AreaType_ground)); } + + TEST_F(DetourNavigatorRecastMeshObjectTest, update_for_changed_local_scaling_should_return_true) + { + RecastMeshObject object(mBoxShape, mTransform, AreaType_ground); + mBoxShape.setLocalScaling(btVector3(2, 2, 2)); + EXPECT_TRUE(object.update(mTransform, AreaType_ground)); + } } diff --git a/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp b/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp new file mode 100644 index 000000000..5275d9119 --- /dev/null +++ b/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp @@ -0,0 +1,239 @@ +#include "operators.hpp" + +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +namespace +{ + using namespace testing; + using namespace DetourNavigator; + + struct DetourNavigatorTileCachedRecastMeshManagerTest : Test + { + Settings mSettings; + + DetourNavigatorTileCachedRecastMeshManagerTest() + { + mSettings.mBorderSize = 16; + mSettings.mCellSize = 0.2f; + mSettings.mRecastScaleFactor = 0.017647058823529415f; + mSettings.mTileSize = 64; + mSettings.mTrianglesPerChunk = 256; + } + }; + + TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_for_empty_should_return_nullptr) + { + TileCachedRecastMeshManager manager(mSettings); + EXPECT_EQ(manager.getMesh(TilePosition(0, 0)), nullptr); + } + + TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, has_tile_for_empty_should_return_false) + { + TileCachedRecastMeshManager manager(mSettings); + EXPECT_FALSE(manager.hasTile(TilePosition(0, 0))); + } + + TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_revision_for_empty_should_return_zero) + { + const TileCachedRecastMeshManager manager(mSettings); + EXPECT_EQ(manager.getRevision(), 0); + } + + TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, for_each_tile_position_for_empty_should_call_none) + { + TileCachedRecastMeshManager manager(mSettings); + std::size_t calls = 0; + manager.forEachTilePosition([&] (const TilePosition&) { ++calls; }); + EXPECT_EQ(calls, 0); + } + + TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, add_object_for_new_object_should_return_true) + { + TileCachedRecastMeshManager manager(mSettings); + const btBoxShape boxShape(btVector3(20, 20, 100)); + EXPECT_TRUE(manager.addObject(ObjectId(1ul), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground)); + } + + TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, add_object_for_existing_object_should_return_false) + { + TileCachedRecastMeshManager manager(mSettings); + const btBoxShape boxShape(btVector3(20, 20, 100)); + manager.addObject(ObjectId(1ul), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground); + EXPECT_FALSE(manager.addObject(ObjectId(1ul), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground)); + } + + TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, update_object_for_changed_object_should_return_changed_tiles) + { + TileCachedRecastMeshManager manager(mSettings); + const btBoxShape boxShape(btVector3(20, 20, 100)); + const btTransform transform(btMatrix3x3::getIdentity(), btVector3(getTileSize(mSettings) / mSettings.mRecastScaleFactor, 0, 0)); + manager.addObject(ObjectId(1ul), boxShape, transform, AreaType::AreaType_ground); + EXPECT_THAT( + manager.updateObject(ObjectId(1ul), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground), + ElementsAre(TilePosition(-1, -1), TilePosition(-1, 0), TilePosition(0, -1), TilePosition(0, 0), + TilePosition(1, -1), TilePosition(1, 0)) + ); + } + + TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, update_object_for_not_changed_object_should_return_empty) + { + TileCachedRecastMeshManager manager(mSettings); + const btBoxShape boxShape(btVector3(20, 20, 100)); + manager.addObject(ObjectId(1ul), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground); + EXPECT_EQ( + manager.updateObject(ObjectId(1ul), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground), + std::vector() + ); + } + + TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_after_add_object_should_return_recast_mesh_for_each_used_tile) + { + TileCachedRecastMeshManager manager(mSettings); + const btBoxShape boxShape(btVector3(20, 20, 100)); + manager.addObject(ObjectId(1ul), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground); + EXPECT_NE(manager.getMesh(TilePosition(-1, -1)), nullptr); + EXPECT_NE(manager.getMesh(TilePosition(-1, 0)), nullptr); + EXPECT_NE(manager.getMesh(TilePosition(0, -1)), nullptr); + EXPECT_NE(manager.getMesh(TilePosition(0, 0)), nullptr); + } + + TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_after_add_object_should_return_nullptr_for_unused_tile) + { + TileCachedRecastMeshManager manager(mSettings); + const btBoxShape boxShape(btVector3(20, 20, 100)); + manager.addObject(ObjectId(1ul), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground); + EXPECT_EQ(manager.getMesh(TilePosition(1, 0)), nullptr); + } + + TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_for_moved_object_should_return_recast_mesh_for_each_used_tile) + { + TileCachedRecastMeshManager manager(mSettings); + const btBoxShape boxShape(btVector3(20, 20, 100)); + const btTransform transform(btMatrix3x3::getIdentity(), btVector3(getTileSize(mSettings) / mSettings.mRecastScaleFactor, 0, 0)); + + manager.addObject(ObjectId(1ul), boxShape, transform, AreaType::AreaType_ground); + EXPECT_NE(manager.getMesh(TilePosition(0, -1)), nullptr); + EXPECT_NE(manager.getMesh(TilePosition(0, 0)), nullptr); + EXPECT_NE(manager.getMesh(TilePosition(1, 0)), nullptr); + EXPECT_NE(manager.getMesh(TilePosition(1, -1)), nullptr); + + manager.updateObject(ObjectId(1ul), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground); + EXPECT_NE(manager.getMesh(TilePosition(-1, -1)), nullptr); + EXPECT_NE(manager.getMesh(TilePosition(-1, 0)), nullptr); + EXPECT_NE(manager.getMesh(TilePosition(0, -1)), nullptr); + EXPECT_NE(manager.getMesh(TilePosition(0, 0)), nullptr); + } + + TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_for_moved_object_should_return_nullptr_for_unused_tile) + { + TileCachedRecastMeshManager manager(mSettings); + const btBoxShape boxShape(btVector3(20, 20, 100)); + const btTransform transform(btMatrix3x3::getIdentity(), btVector3(getTileSize(mSettings) / mSettings.mRecastScaleFactor, 0, 0)); + + manager.addObject(ObjectId(1ul), boxShape, transform, AreaType::AreaType_ground); + EXPECT_EQ(manager.getMesh(TilePosition(-1, -1)), nullptr); + EXPECT_EQ(manager.getMesh(TilePosition(-1, 0)), nullptr); + + manager.updateObject(ObjectId(1ul), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground); + EXPECT_EQ(manager.getMesh(TilePosition(1, 0)), nullptr); + EXPECT_EQ(manager.getMesh(TilePosition(1, -1)), nullptr); + } + + TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_for_removed_object_should_return_nullptr_for_all_previously_used_tiles) + { + TileCachedRecastMeshManager manager(mSettings); + const btBoxShape boxShape(btVector3(20, 20, 100)); + manager.addObject(ObjectId(1ul), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground); + manager.removeObject(ObjectId(1ul)); + EXPECT_EQ(manager.getMesh(TilePosition(-1, -1)), nullptr); + EXPECT_EQ(manager.getMesh(TilePosition(-1, 0)), nullptr); + EXPECT_EQ(manager.getMesh(TilePosition(0, -1)), nullptr); + EXPECT_EQ(manager.getMesh(TilePosition(0, 0)), nullptr); + } + + TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_for_not_changed_object_after_update_should_return_recast_mesh_for_same_tiles) + { + TileCachedRecastMeshManager manager(mSettings); + const btBoxShape boxShape(btVector3(20, 20, 100)); + + manager.addObject(ObjectId(1ul), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground); + EXPECT_NE(manager.getMesh(TilePosition(-1, -1)), nullptr); + EXPECT_NE(manager.getMesh(TilePosition(-1, 0)), nullptr); + EXPECT_NE(manager.getMesh(TilePosition(0, -1)), nullptr); + EXPECT_NE(manager.getMesh(TilePosition(0, 0)), nullptr); + + manager.updateObject(ObjectId(1ul), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground); + EXPECT_NE(manager.getMesh(TilePosition(-1, -1)), nullptr); + EXPECT_NE(manager.getMesh(TilePosition(-1, 0)), nullptr); + EXPECT_NE(manager.getMesh(TilePosition(0, -1)), nullptr); + EXPECT_NE(manager.getMesh(TilePosition(0, 0)), nullptr); + } + + TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_revision_after_add_object_new_should_return_incremented_value) + { + TileCachedRecastMeshManager manager(mSettings); + const auto initialRevision = manager.getRevision(); + const btBoxShape boxShape(btVector3(20, 20, 100)); + manager.addObject(ObjectId(1ul), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground); + EXPECT_EQ(manager.getRevision(), initialRevision + 1); + } + + TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_revision_after_add_object_existing_should_return_same_value) + { + TileCachedRecastMeshManager manager(mSettings); + const btBoxShape boxShape(btVector3(20, 20, 100)); + manager.addObject(ObjectId(1ul), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground); + const auto beforeAddRevision = manager.getRevision(); + manager.addObject(ObjectId(1ul), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground); + EXPECT_EQ(manager.getRevision(), beforeAddRevision); + } + + TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_revision_after_update_moved_object_should_return_incremented_value) + { + TileCachedRecastMeshManager manager(mSettings); + const btBoxShape boxShape(btVector3(20, 20, 100)); + const btTransform transform(btMatrix3x3::getIdentity(), btVector3(getTileSize(mSettings) / mSettings.mRecastScaleFactor, 0, 0)); + manager.addObject(ObjectId(1ul), boxShape, transform, AreaType::AreaType_ground); + const auto beforeUpdateRevision = manager.getRevision(); + manager.updateObject(ObjectId(1ul), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground); + EXPECT_EQ(manager.getRevision(), beforeUpdateRevision + 1); + } + + TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_revision_after_update_not_changed_object_should_return_same_value) + { + TileCachedRecastMeshManager manager(mSettings); + const btBoxShape boxShape(btVector3(20, 20, 100)); + manager.addObject(ObjectId(1ul), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground); + const auto beforeUpdateRevision = manager.getRevision(); + manager.updateObject(ObjectId(1ul), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground); + EXPECT_EQ(manager.getRevision(), beforeUpdateRevision); + } + + TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_revision_after_remove_existing_object_should_return_incremented_value) + { + TileCachedRecastMeshManager manager(mSettings); + const btBoxShape boxShape(btVector3(20, 20, 100)); + manager.addObject(ObjectId(1ul), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground); + const auto beforeRemoveRevision = manager.getRevision(); + manager.removeObject(ObjectId(1ul)); + EXPECT_EQ(manager.getRevision(), beforeRemoveRevision + 1); + } + + TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_revision_after_remove_absent_object_should_return_same_value) + { + TileCachedRecastMeshManager manager(mSettings); + const auto beforeRemoveRevision = manager.getRevision(); + manager.removeObject(ObjectId(1ul)); + EXPECT_EQ(manager.getRevision(), beforeRemoveRevision); + } +} diff --git a/apps/openmw_test_suite/esm/test_fixed_string.cpp b/apps/openmw_test_suite/esm/test_fixed_string.cpp index 89a390ff6..dc88a5f63 100644 --- a/apps/openmw_test_suite/esm/test_fixed_string.cpp +++ b/apps/openmw_test_suite/esm/test_fixed_string.cpp @@ -37,6 +37,42 @@ TEST(EsmFixedString, operator__eq_ne) EXPECT_TRUE(name == ss); } } +TEST(EsmFixedString, operator__eq_ne_const) +{ + { + SCOPED_TRACE("asdc == asdc (const)"); + ESM::NAME name; + name.assign("asdc"); + const char s[4] = { 'a', 's', 'd', 'c' }; + std::string ss(s, 4); + + EXPECT_TRUE(name == s); + EXPECT_TRUE(name == ss.c_str()); + EXPECT_TRUE(name == ss); + } + { + SCOPED_TRACE("asdc == asdcx (const)"); + ESM::NAME name; + name.assign("asdc"); + const char s[5] = { 'a', 's', 'd', 'c', 'x' }; + std::string ss(s, 5); + + EXPECT_TRUE(name != s); + EXPECT_TRUE(name != ss.c_str()); + EXPECT_TRUE(name != ss); + } + { + SCOPED_TRACE("asdc == asdc[NULL] (const)"); + ESM::NAME name; + name.assign("asdc"); + const char s[5] = { 'a', 's', 'd', 'c', '\0' }; + std::string ss(s, 5); + + EXPECT_TRUE(name == s); + EXPECT_TRUE(name == ss.c_str()); + EXPECT_TRUE(name == ss); + } +} TEST(EsmFixedString, empty_strings) { diff --git a/apps/wizard/unshield/unshieldworker.cpp b/apps/wizard/unshield/unshieldworker.cpp index 7aa84d3b1..a997c9324 100644 --- a/apps/wizard/unshield/unshieldworker.cpp +++ b/apps/wizard/unshield/unshieldworker.cpp @@ -876,7 +876,7 @@ QStringList Wizard::UnshieldWorker::findFiles(const QString &fileName, const QSt result.append(info.absoluteFilePath()); break; case Qt::MatchEndsWith: - if (info.fileName().endsWith(fileName), Qt::CaseInsensitive) + if (info.fileName().endsWith(fileName, Qt::CaseInsensitive)) result.append(info.absoluteFilePath()); break; } diff --git a/appveyor.yml b/appveyor.yml index 5f5657285..90a9cb1bc 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -33,18 +33,18 @@ configuration: clone_depth: 1 cache: - - C:\projects\openmw\deps\Bullet-2.83.7-msvc2015-win32.7z - - C:\projects\openmw\deps\Bullet-2.83.7-msvc2015-win64.7z - - C:\projects\openmw\deps\MyGUI-3.2.3-git-msvc2015-win64.7z - - C:\projects\openmw\deps\MyGUI-3.2.3-git-msvc2015-win64.7z - - C:\projects\openmw\deps\OSG-3.4.0-scrawl-msvc2015-win64.7z - - C:\projects\openmw\deps\OSG-3.4.0-scrawl-msvc2015-win64.7z - - C:\projects\openmw\deps\ffmpeg-3.0.1-dev-win32.7z - - C:\projects\openmw\deps\ffmpeg-3.0.1-dev-win64.7z - - C:\projects\openmw\deps\ffmpeg-3.0.1-win32.7z - - C:\projects\openmw\deps\ffmpeg-3.0.1-win64.7z - - C:\projects\openmw\deps\OpenAL-Soft-1.17.2.zip - - C:\projects\openmw\deps\SDL2-2.0.4.zip + - C:\projects\openmw\deps\Bullet-2.86-msvc2015-win32.7z + - C:\projects\openmw\deps\Bullet-2.86-msvc2015-win64.7z + - C:\projects\openmw\deps\MyGUI-3.2.2-msvc2015-win32.7z + - C:\projects\openmw\deps\MyGUI-3.2.2-msvc2015-win64.7z + - C:\projects\openmw\deps\OSG-3.4.1-scrawl-msvc2015-win32.7z + - C:\projects\openmw\deps\OSG-3.4.1-scrawl-msvc2015-win64.7z + - C:\projects\openmw\deps\ffmpeg-3.2.4-dev-win32.zip + - C:\projects\openmw\deps\ffmpeg-3.2.4-dev-win64.zip + - C:\projects\openmw\deps\ffmpeg-3.2.4-win32.zip + - C:\projects\openmw\deps\ffmpeg-3.2.4-win64.zip + - C:\projects\openmw\deps\OpenAL-Soft-1.19.1.zip + - C:\projects\openmw\deps\SDL2-2.0.7.zip clone_folder: C:\projects\openmw diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 76641899d..ed52c8111 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -51,7 +51,7 @@ add_component_dir (shader add_component_dir (sceneutil clone attach visitor util statesetupdater controller skeleton riggeometry morphgeometry lightcontroller lightmanager lightutil positionattitudetransform workqueue unrefqueue pathgridutil waterutil writescene serialize optimizer - actorutil detourdebugdraw navmesh agentpath + actorutil detourdebugdraw navmesh agentpath shadow mwshadowtechnique ) add_component_dir (nif @@ -86,7 +86,7 @@ add_component_dir (esmterrain ) add_component_dir (misc - constants utf8stream stringops resourcehelpers rng messageformatparser weakcache + gcd constants utf8stream stringops resourcehelpers rng messageformatparser weakcache ) add_component_dir (debug @@ -164,13 +164,14 @@ add_component_dir(detournavigator recastmeshmanager cachedrecastmeshmanager navmeshmanager - navigator + navigatorimpl asyncnavmeshupdater chunkytrimesh recastmesh tilecachedrecastmeshmanager recastmeshobject navmeshtilescache + settings ) set (ESM_UI ${CMAKE_SOURCE_DIR}/files/ui/contentselector.ui @@ -227,6 +228,7 @@ target_link_libraries(components ${OSGVIEWER_LIBRARIES} ${OSGTEXT_LIBRARIES} ${OSGGA_LIBRARIES} + ${OSGSHADOW_LIBRARIES} ${OSGANIMATION_LIBRARIES} ${Bullet_LIBRARIES} ${SDL2_LIBRARIES} diff --git a/components/compiler/controlparser.cpp b/components/compiler/controlparser.cpp index b202467db..6f9fad35f 100644 --- a/components/compiler/controlparser.cpp +++ b/components/compiler/controlparser.cpp @@ -179,6 +179,14 @@ namespace Compiler scanner.scan (mLineParser); return true; } + else if (mState==IfElseJunkState) + { + getErrorHandler().warning ("Ignoring 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 ("Ignoring 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 ("Ignoring 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..1e3bc0585 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 ("ignoring local variable re-declaration", loc); + else + mLocals.declare (mType, name2); mState = State_End; return true; } + else if (mState==State_End) + { + getErrorHandler().warning ("Ignoring 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 ("Ignoring 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 ("Ignoring 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/exprparser.cpp b/components/compiler/exprparser.cpp index 6b849ec3a..b3fd1e5ae 100644 --- a/components/compiler/exprparser.cpp +++ b/components/compiler/exprparser.cpp @@ -277,10 +277,18 @@ namespace Compiler { if (!mExplicit.empty()) { - if (mMemberOp && handleMemberAccess (name)) - return true; + if (!mRefOp) + { + if (mMemberOp && handleMemberAccess (name)) + return true; - return Parser::parseName (name, loc, scanner); + return Parser::parseName (name, loc, scanner); + } + else + { + mExplicit.clear(); + getErrorHandler().warning ("Ignoring stray explicit reference", loc); + } } mFirst = false; diff --git a/components/compiler/extensions0.cpp b/components/compiler/extensions0.cpp index 1b5558a3a..fdb670748 100644 --- a/components/compiler/extensions0.cpp +++ b/components/compiler/extensions0.cpp @@ -175,7 +175,7 @@ namespace Compiler { void registerExtensions (Extensions& extensions) { - extensions.registerInstruction ("journal", "cl", opcodeJournal); + extensions.registerInstruction ("journal", "cl", opcodeJournal, opcodeJournalExplicit); extensions.registerInstruction ("setjournalindex", "cl", opcodeSetJournalIndex); extensions.registerFunction ("getjournalindex", 'l', "c", opcodeGetJournalIndex); extensions.registerInstruction ("addtopic", "S" , opcodeAddTopic); diff --git a/components/compiler/lineparser.cpp b/components/compiler/lineparser.cpp index 3f9d2e790..1cfa70f73 100644 --- a/components/compiler/lineparser.cpp +++ b/components/compiler/lineparser.cpp @@ -506,6 +506,13 @@ namespace Compiler return true; } + if (code==Scanner::S_ref && mState==SetPotentialMemberVarState) + { + getErrorHandler().warning ("Ignoring stray explicit reference", loc); + mState = SetState; + return true; + } + if (code==Scanner::S_ref && mState==PotentialExplicitState) { mState = ExplicitState; diff --git a/components/compiler/locals.cpp b/components/compiler/locals.cpp index 768fc077c..2b485abec 100644 --- a/components/compiler/locals.cpp +++ b/components/compiler/locals.cpp @@ -1,6 +1,5 @@ #include "locals.hpp" -#include #include #include #include diff --git a/components/compiler/opcodes.hpp b/components/compiler/opcodes.hpp index a2d8a9467..4aabe3f78 100644 --- a/components/compiler/opcodes.hpp +++ b/components/compiler/opcodes.hpp @@ -150,6 +150,7 @@ namespace Compiler namespace Dialogue { const int opcodeJournal = 0x2000133; + const int opcodeJournalExplicit = 0x200030b; const int opcodeSetJournalIndex = 0x2000134; const int opcodeGetJournalIndex = 0x2000135; const int opcodeAddTopic = 0x200013a; diff --git a/components/compiler/parser.cpp b/components/compiler/parser.cpp index fe019718a..62265d8a1 100644 --- a/components/compiler/parser.cpp +++ b/components/compiler/parser.cpp @@ -1,9 +1,5 @@ #include "parser.hpp" -#include -#include -#include - #include "errorhandler.hpp" #include "exception.hpp" #include "scanner.hpp" diff --git a/components/compiler/scanner.cpp b/components/compiler/scanner.cpp index b56dbb95b..c5ef483f3 100644 --- a/components/compiler/scanner.cpp +++ b/components/compiler/scanner.cpp @@ -3,7 +3,6 @@ #include #include #include -#include #include #include "exception.hpp" diff --git a/components/contentselector/view/combobox.cpp b/components/contentselector/view/combobox.cpp index 18cafc2dc..959eca289 100644 --- a/components/contentselector/view/combobox.cpp +++ b/components/contentselector/view/combobox.cpp @@ -1,7 +1,4 @@ -#include -#include #include -#include #include #include "combobox.hpp" diff --git a/components/contentselector/view/contentselector.cpp b/components/contentselector/view/contentselector.cpp index 4f95b7fe4..89c389556 100644 --- a/components/contentselector/view/contentselector.cpp +++ b/components/contentselector/view/contentselector.cpp @@ -8,10 +8,7 @@ #include #include -#include -#include #include -#include ContentSelectorView::ContentSelector::ContentSelector(QWidget *parent) : QObject(parent) diff --git a/components/detournavigator/asyncnavmeshupdater.cpp b/components/detournavigator/asyncnavmeshupdater.cpp index f4efc744b..6bd4f01c9 100644 --- a/components/detournavigator/asyncnavmeshupdater.cpp +++ b/components/detournavigator/asyncnavmeshupdater.cpp @@ -1,11 +1,11 @@ -#include "asyncnavmeshupdater.hpp" +#include "asyncnavmeshupdater.hpp" #include "debug.hpp" #include "makenavmesh.hpp" #include "settings.hpp" #include -#include +#include namespace { @@ -16,16 +16,6 @@ namespace { return std::abs(lhs.x() - rhs.x()) + std::abs(lhs.y() - rhs.y()); } - - std::tuple makePriority(const TilePosition& position, const ChangeType changeType, - const TilePosition& playerTile) - { - return std::make_tuple( - changeType, - getManhattanDistance(position, playerTile), - getManhattanDistance(position, TilePosition {0, 0}) - ); - } } namespace DetourNavigator @@ -34,14 +24,18 @@ namespace DetourNavigator { switch (value) { - case UpdateNavMeshStatus::ignore: + case UpdateNavMeshStatus::ignored: return stream << "ignore"; case UpdateNavMeshStatus::removed: return stream << "removed"; - case UpdateNavMeshStatus::add: + case UpdateNavMeshStatus::added: return stream << "add"; case UpdateNavMeshStatus::replaced: return stream << "replaced"; + case UpdateNavMeshStatus::failed: + return stream << "failed"; + case UpdateNavMeshStatus::lost: + return stream << "lost"; } return stream << "unknown"; } @@ -83,13 +77,25 @@ namespace DetourNavigator for (const auto& changedTile : changedTiles) { if (mPushed[agentHalfExtents].insert(changedTile.first).second) - mJobs.push(Job {agentHalfExtents, navMeshCacheItem, changedTile.first, - makePriority(changedTile.first, changedTile.second, playerTile)}); + { + Job job; + + job.mAgentHalfExtents = agentHalfExtents; + job.mNavMeshCacheItem = navMeshCacheItem; + job.mChangedTile = changedTile.first; + job.mTryNumber = 0; + job.mChangeType = changedTile.second; + job.mDistanceToPlayer = getManhattanDistance(changedTile.first, playerTile); + job.mDistanceToOrigin = getManhattanDistance(changedTile.first, TilePosition {0, 0}); + + mJobs.push(std::move(job)); + } } log("posted ", mJobs.size(), " jobs"); - mHasJob.notify_all(); + if (!mJobs.empty()) + mHasJob.notify_all(); } void AsyncNavMeshUpdater::wait() @@ -98,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"); @@ -105,8 +125,9 @@ namespace DetourNavigator { try { - if (const auto job = getNextJob()) - processJob(*job); + if (auto job = getNextJob()) + if (!processJob(*job)) + repost(std::move(*job)); } catch (const std::exception& e) { @@ -116,7 +137,7 @@ namespace DetourNavigator log("stop process jobs"); } - void AsyncNavMeshUpdater::processJob(const Job& job) + bool AsyncNavMeshUpdater::processJob(const Job& job) { log("process job for agent=", job.mAgentHalfExtents); @@ -124,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(); @@ -137,20 +163,22 @@ namespace DetourNavigator using FloatMs = std::chrono::duration; - const auto locked = job.mNavMeshCacheItem.lockConst(); - log("cache updated for agent=", job.mAgentHalfExtents, " status=", status, - " generation=", locked->getGeneration(), - " revision=", locked->getNavMeshRevision(), - " time=", std::chrono::duration_cast(finish - start).count(), "ms", - " total_time=", std::chrono::duration_cast(finish - firstStart).count(), "ms"); + { + const auto locked = navMeshCacheItem->lockConst(); + log("cache updated for agent=", job.mAgentHalfExtents, " status=", status, + " generation=", locked->getGeneration(), + " revision=", locked->getNavMeshRevision(), + " time=", std::chrono::duration_cast(finish - start).count(), "ms", + " total_time=", std::chrono::duration_cast(finish - firstStart).count(), "ms"); + } + + return isSuccess(status); } 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(); @@ -185,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) @@ -195,4 +224,19 @@ namespace DetourNavigator *locked = value; return *locked.get(); } + + void AsyncNavMeshUpdater::repost(Job&& job) + { + if (mShouldStop || job.mTryNumber > 2) + return; + + const std::lock_guard lock(mMutex); + + if (mPushed[job.mAgentHalfExtents].insert(job.mChangedTile).second) + { + ++job.mTryNumber; + mJobs.push(std::move(job)); + mHasJob.notify_all(); + } + } } diff --git a/components/detournavigator/asyncnavmeshupdater.hpp b/components/detournavigator/asyncnavmeshupdater.hpp index 39898e48e..3493ba02f 100644 --- a/components/detournavigator/asyncnavmeshupdater.hpp +++ b/components/detournavigator/asyncnavmeshupdater.hpp @@ -44,17 +44,27 @@ 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; - std::tuple mPriority; + unsigned mTryNumber; + ChangeType mChangeType; + int mDistanceToPlayer; + int mDistanceToOrigin; + + std::tuple getPriority() const + { + return std::make_tuple(mTryNumber, mChangeType, mDistanceToPlayer, mDistanceToOrigin); + } friend inline bool operator <(const Job& lhs, const Job& rhs) { - return lhs.mPriority > rhs.mPriority; + return lhs.getPriority() > rhs.getPriority(); } }; @@ -64,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; @@ -76,13 +86,15 @@ namespace DetourNavigator void process() throw(); - void processJob(const Job& job); + bool processJob(const Job& job); boost::optional getNextJob(); void writeDebugFiles(const Job& job, const RecastMesh* recastMesh) const; std::chrono::steady_clock::time_point setFirstStart(const std::chrono::steady_clock::time_point& value); + + void repost(Job&& job); }; } diff --git a/components/detournavigator/findsmoothpath.cpp b/components/detournavigator/findsmoothpath.cpp index e59b80114..ef0341d72 100644 --- a/components/detournavigator/findsmoothpath.cpp +++ b/components/detournavigator/findsmoothpath.cpp @@ -125,7 +125,7 @@ namespace DetourNavigator { // Stop at Off-Mesh link or when point is further than slop away. if ((steerPathFlags[ns] & DT_STRAIGHTPATH_OFFMESH_CONNECTION) || - !inRange(makeOsgVec3f(&steerPath[ns * 3]), startPos, minTargetDist, 1000.0f)) + !inRange(Misc::Convert::makeOsgVec3f(&steerPath[ns * 3]), startPos, minTargetDist, 1000.0f)) break; ns++; } diff --git a/components/detournavigator/findsmoothpath.hpp b/components/detournavigator/findsmoothpath.hpp index 81b732b74..e988dedb7 100644 --- a/components/detournavigator/findsmoothpath.hpp +++ b/components/detournavigator/findsmoothpath.hpp @@ -14,6 +14,8 @@ #include +#include + #include #include @@ -26,16 +28,6 @@ namespace DetourNavigator { struct Settings; - inline osg::Vec3f makeOsgVec3f(const float* values) - { - return osg::Vec3f(values[0], values[1], values[2]); - } - - inline osg::Vec3f makeOsgVec3f(const btVector3& value) - { - return osg::Vec3f(value.x(), value.y(), value.z()); - } - inline bool inRange(const osg::Vec3f& v1, const osg::Vec3f& v2, const float r, const float h) { const auto d = v2 - v1; @@ -81,21 +73,28 @@ namespace DetourNavigator return *this; } - OutputTransformIterator& operator ++(int) + OutputTransformIterator& operator ++() { - mImpl++; + ++mImpl.get(); return *this; } + OutputTransformIterator operator ++(int) + { + const auto copy = *this; + ++(*this); + return copy; + } + OutputTransformIterator& operator =(const osg::Vec3f& value) { - *mImpl = fromNavMeshCoordinates(mSettings, value); + *mImpl.get() = fromNavMeshCoordinates(mSettings, value); return *this; } private: - OutputIterator& mImpl; - const Settings& mSettings; + std::reference_wrapper mImpl; + std::reference_wrapper mSettings; }; inline void initNavMeshQuery(dtNavMeshQuery& value, const dtNavMesh& navMesh, const int maxNodes) @@ -167,7 +166,7 @@ namespace DetourNavigator template OutputIterator makeSmoothPath(const dtNavMesh& navMesh, const dtNavMeshQuery& navMeshQuery, - const dtQueryFilter& filter, const osg::Vec3f& start, const osg::Vec3f& end, + const dtQueryFilter& filter, const osg::Vec3f& start, const osg::Vec3f& end, const float stepSize, std::vector polygonPath, std::size_t maxSmoothPathSize, OutputIterator out) { // Iterate over the path to find smooth path on the detail mesh surface. @@ -177,7 +176,6 @@ namespace DetourNavigator osg::Vec3f targetPos; navMeshQuery.closestPointOnPoly(polygonPath.back(), end.ptr(), targetPos.ptr(), 0); - const float STEP_SIZE = 0.5f; const float SLOP = 0.01f; *out++ = iterPos; @@ -201,10 +199,10 @@ namespace DetourNavigator const osg::Vec3f delta = steerTarget->steerPos - iterPos; float len = delta.length(); // If the steer target is end of path or off-mesh link, do not move past the location. - if ((endOfPath || offMeshConnection) && len < STEP_SIZE) + if ((endOfPath || offMeshConnection) && len < stepSize) len = 1; else - len = STEP_SIZE / len; + len = stepSize / len; const osg::Vec3f moveTgt = iterPos + delta * len; const auto result = moveAlongSurface(navMeshQuery, polygonPath.front(), iterPos, moveTgt, filter, 16); @@ -274,7 +272,7 @@ namespace DetourNavigator } template - OutputIterator findSmoothPath(const dtNavMesh& navMesh, const osg::Vec3f& halfExtents, + OutputIterator findSmoothPath(const dtNavMesh& navMesh, const osg::Vec3f& halfExtents, const float stepSize, const osg::Vec3f& start, const osg::Vec3f& end, const Flags includeFlags, const Settings& settings, OutputIterator out) { @@ -316,7 +314,7 @@ namespace DetourNavigator if (polygonPath.empty() || polygonPath.back() != endRef) return out; - makeSmoothPath(navMesh, navMeshQuery, queryFilter, start, end, std::move(polygonPath), + makeSmoothPath(navMesh, navMeshQuery, queryFilter, start, end, stepSize, std::move(polygonPath), settings.mMaxSmoothPathSize, OutputTransformIterator(out, settings)); return out; diff --git a/components/detournavigator/gettilespositions.hpp b/components/detournavigator/gettilespositions.hpp index 86ce77402..e233795e6 100644 --- a/components/detournavigator/gettilespositions.hpp +++ b/components/detournavigator/gettilespositions.hpp @@ -5,17 +5,14 @@ #include "settingsutils.hpp" #include "tileposition.hpp" +#include + #include #include namespace DetourNavigator { - inline osg::Vec3f makeOsgVec3f(const btVector3& value) - { - return osg::Vec3f(value.x(), value.y(), value.z()); - } - template void getTilesPositions(const osg::Vec3f& aabbMin, const osg::Vec3f& aabbMax, const Settings& settings, Callback&& callback) @@ -49,7 +46,7 @@ namespace DetourNavigator btVector3 aabbMax; shape.getAabb(transform, aabbMin, aabbMax); - getTilesPositions(makeOsgVec3f(aabbMin), makeOsgVec3f(aabbMax), settings, std::forward(callback)); + getTilesPositions(Misc::Convert::makeOsgVec3f(aabbMin), Misc::Convert::makeOsgVec3f(aabbMax), settings, std::forward(callback)); } template @@ -66,7 +63,7 @@ namespace DetourNavigator aabbMax.setX(std::max(aabbMin.x(), aabbMax.x())); aabbMax.setY(std::max(aabbMin.y(), aabbMax.y())); - getTilesPositions(makeOsgVec3f(aabbMin), makeOsgVec3f(aabbMax), settings, std::forward(callback)); + getTilesPositions(Misc::Convert::makeOsgVec3f(aabbMin), Misc::Convert::makeOsgVec3f(aabbMax), settings, std::forward(callback)); } } diff --git a/components/detournavigator/makenavmesh.cpp b/components/detournavigator/makenavmesh.cpp index 26f86da32..7caad9ec6 100644 --- a/components/detournavigator/makenavmesh.cpp +++ b/components/detournavigator/makenavmesh.cpp @@ -7,10 +7,11 @@ #include "settings.hpp" #include "settingsutils.hpp" #include "sharednavmesh.hpp" -#include "settingsutils.hpp" #include "flags.hpp" #include "navmeshtilescache.hpp" +#include + #include #include #include @@ -24,8 +25,6 @@ namespace { using namespace DetourNavigator; - static const int doNotTransferOwnership = 0; - void initPolyMeshDetail(rcPolyMeshDetail& value) { value.meshes = nullptr; @@ -45,11 +44,6 @@ namespace using PolyMeshDetailStackPtr = std::unique_ptr; - osg::Vec3f makeOsgVec3f(const btVector3& value) - { - return osg::Vec3f(value.x(), value.y(), value.z()); - } - struct WaterBounds { osg::Vec3f mMin; @@ -62,8 +56,8 @@ namespace if (water.mCellSize == std::numeric_limits::max()) { const auto transform = getSwimLevelTransform(settings, water.mTransform, agentHalfExtents.z()); - const auto min = toNavMeshCoordinates(settings, makeOsgVec3f(transform(btVector3(-1, -1, 0)))); - const auto max = toNavMeshCoordinates(settings, makeOsgVec3f(transform(btVector3(1, 1, 0)))); + const auto min = toNavMeshCoordinates(settings, Misc::Convert::makeOsgVec3f(transform(btVector3(-1, -1, 0)))); + const auto max = toNavMeshCoordinates(settings, Misc::Convert::makeOsgVec3f(transform(btVector3(1, 1, 0)))); return WaterBounds { osg::Vec3f(-std::numeric_limits::max(), min.y(), -std::numeric_limits::max()), osg::Vec3f(std::numeric_limits::max(), max.y(), std::numeric_limits::max()) @@ -74,8 +68,8 @@ namespace const auto transform = getSwimLevelTransform(settings, water.mTransform, agentHalfExtents.z()); const auto halfCellSize = water.mCellSize / 2.0f; return WaterBounds { - toNavMeshCoordinates(settings, makeOsgVec3f(transform(btVector3(-halfCellSize, -halfCellSize, 0)))), - toNavMeshCoordinates(settings, makeOsgVec3f(transform(btVector3(halfCellSize, halfCellSize, 0)))) + toNavMeshCoordinates(settings, Misc::Convert::makeOsgVec3f(transform(btVector3(-halfCellSize, -halfCellSize, 0)))), + toNavMeshCoordinates(settings, Misc::Convert::makeOsgVec3f(transform(btVector3(halfCellSize, halfCellSize, 0)))) }; } } @@ -445,17 +439,7 @@ namespace return NavMeshData(navMeshData, navMeshDataSize); } - UpdateNavMeshStatus makeUpdateNavMeshStatus(bool removed, bool add) - { - if (removed && add) - return UpdateNavMeshStatus::replaced; - else if (removed) - return UpdateNavMeshStatus::removed; - else if (add) - return UpdateNavMeshStatus::add; - else - return UpdateNavMeshStatus::ignore; - } + template unsigned long getMinValuableBitsNumber(const T value) @@ -513,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 makeUpdateNavMeshStatus(removed, false); - }; - if (!recastMesh) { log("ignore add tile: recastMesh is null"); - return removeTile(); + return navMeshCacheItem->lock()->removeTile(changedTile); } auto recastMeshBounds = recastMesh->getBounds(); @@ -547,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, params.maxTiles)) + 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); @@ -570,7 +541,7 @@ namespace DetourNavigator if (!navMeshData.mValue) { log("ignore add tile: NavMeshData is null"); - return removeTile(); + return navMeshCacheItem->lock()->removeTile(changedTile); } try @@ -587,47 +558,10 @@ namespace DetourNavigator if (!cachedNavMeshData) { log("cache overflow"); - - 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)); - const auto addStatus = navMesh.addTile(navMeshData.mValue.get(), navMeshData.mSize, - doNotTransferOwnership, 0, 0); - - if (dtStatusSucceed(addStatus)) - { - locked->setUsedTile(changedTile, std::move(navMeshData)); - return makeUpdateNavMeshStatus(removed, true); - } - else - { - if (removed) - locked->removeUsedTile(changedTile); - log("failed to add tile with status=", WriteDtStatus {addStatus}); - return makeUpdateNavMeshStatus(removed, false); - } + return navMeshCacheItem->lock()->updateTile(changedTile, std::move(navMeshData)); } } - 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)); - const auto addStatus = navMesh.addTile(cachedNavMeshData.get().mValue, cachedNavMeshData.get().mSize, - doNotTransferOwnership, 0, 0); - - if (dtStatusSucceed(addStatus)) - { - locked->setUsedTile(changedTile, std::move(cachedNavMeshData)); - return makeUpdateNavMeshStatus(removed, true); - } - else - { - if (removed) - locked->removeUsedTile(changedTile); - log("failed to add tile with status=", WriteDtStatus {addStatus}); - return makeUpdateNavMeshStatus(removed, false); - } + return navMeshCacheItem->lock()->updateTile(changedTile, std::move(cachedNavMeshData)); } } diff --git a/components/detournavigator/makenavmesh.hpp b/components/detournavigator/makenavmesh.hpp index 55d3e261c..f9cf68a73 100644 --- a/components/detournavigator/makenavmesh.hpp +++ b/components/detournavigator/makenavmesh.hpp @@ -20,14 +20,6 @@ namespace DetourNavigator class RecastMesh; struct Settings; - enum class UpdateNavMeshStatus - { - ignore, - removed, - add, - replaced - }; - inline float getLength(const osg::Vec2i& value) { return std::sqrt(float(osg::square(value.x()) + osg::square(value.y()))); @@ -41,7 +33,7 @@ namespace DetourNavigator inline bool shouldAddTile(const TilePosition& changedTile, const TilePosition& playerTile, int maxTiles) { const auto expectedTilesCount = std::ceil(osg::PI * osg::square(getDistance(changedTile, playerTile))); - return expectedTilesCount * 3 <= maxTiles; + return expectedTilesCount <= maxTiles; } NavMeshPtr makeEmptyNavMesh(const Settings& settings); diff --git a/components/detournavigator/navigator.hpp b/components/detournavigator/navigator.hpp index 351e0f9f8..561b7f02b 100644 --- a/components/detournavigator/navigator.hpp +++ b/components/detournavigator/navigator.hpp @@ -3,9 +3,9 @@ #include "findsmoothpath.hpp" #include "flags.hpp" -#include "navmeshmanager.hpp" #include "settings.hpp" -#include "settingsutils.hpp" +#include "objectid.hpp" +#include "navmeshcacheitem.hpp" namespace DetourNavigator { @@ -33,33 +33,28 @@ namespace DetourNavigator }; /** - * @brief Top level class of detournavigator componenet. Navigator allows to build a scene with navmesh and find + * @brief Top level interface of detournavigator component. Navigator allows to build a scene with navmesh and find * a path for an agent there. Scene contains agents, geometry objects and water. Agent are distinguished only by * half extents. Each object has unique identifier and could be added, updated or removed. Water could be added once * for each world cell at given level of height. Navmesh builds asynchronously in separate threads. To start build * navmesh call update method. */ - class Navigator + struct Navigator { - public: - /** - * @brief Navigator constructor initializes all internal data. Constructed object is ready to build a scene. - * @param settings allows to customize navigator work. Constructor is only place to set navigator settings. - */ - Navigator(const Settings& settings); + virtual ~Navigator() = default; /** * @brief addAgent should be called for each agent even if all of them has same half extents. * @param agentHalfExtents allows to setup bounding cylinder for each agent, for each different half extents * there is different navmesh. */ - void addAgent(const osg::Vec3f& agentHalfExtents); + virtual void addAgent(const osg::Vec3f& agentHalfExtents) = 0; /** * @brief removeAgent should be called for each agent even if all of them has same half extents * @param agentHalfExtents allows determine which agent to remove */ - void removeAgent(const osg::Vec3f& agentHalfExtents); + virtual void removeAgent(const osg::Vec3f& agentHalfExtents) = 0; /** * @brief addObject is used to add object represented by single btCollisionShape and btTransform. @@ -68,7 +63,7 @@ namespace DetourNavigator * @param transform allows to setup object geometry according to its world state. * @return true if object is added, false if there is already object with given id. */ - bool addObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform); + virtual bool addObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform) = 0; /** * @brief addObject is used to add complex object with allowed to walk and avoided to walk shapes @@ -77,7 +72,7 @@ namespace DetourNavigator * @param transform allows to setup objects geometry according to its world state * @return true if object is added, false if there is already object with given id */ - bool addObject(const ObjectId id, const ObjectShapes& shapes, const btTransform& transform); + virtual bool addObject(const ObjectId id, const ObjectShapes& shapes, const btTransform& transform) = 0; /** * @brief addObject is used to add doors. @@ -86,7 +81,7 @@ namespace DetourNavigator * @param transform allows to setup objects geometry according to its world state. * @return true if object is added, false if there is already object with given id. */ - bool addObject(const ObjectId id, const DoorShapes& shapes, const btTransform& transform); + virtual bool addObject(const ObjectId id, const DoorShapes& shapes, const btTransform& transform) = 0; /** * @brief updateObject replace object geometry by given data. @@ -95,7 +90,7 @@ namespace DetourNavigator * @param transform allows to setup objects geometry according to its world state. * @return true if object is updated, false if there is no object with given id. */ - bool updateObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform); + virtual bool updateObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform) = 0; /** * @brief updateObject replace object geometry by given data. @@ -104,7 +99,7 @@ namespace DetourNavigator * @param transform allows to setup objects geometry according to its world state. * @return true if object is updated, false if there is no object with given id. */ - bool updateObject(const ObjectId id, const ObjectShapes& shapes, const btTransform& transform); + virtual bool updateObject(const ObjectId id, const ObjectShapes& shapes, const btTransform& transform) = 0; /** * @brief updateObject replace object geometry by given data. @@ -113,14 +108,14 @@ namespace DetourNavigator * @param transform allows to setup objects geometry according to its world state. * @return true if object is updated, false if there is no object with given id. */ - bool updateObject(const ObjectId id, const DoorShapes& shapes, const btTransform& transform); + virtual bool updateObject(const ObjectId id, const DoorShapes& shapes, const btTransform& transform) = 0; /** * @brief removeObject to make it no more available at the scene. * @param id is used to find object. * @return true if object is removed, false if there is no object with given id. */ - bool removeObject(const ObjectId id); + virtual bool removeObject(const ObjectId id) = 0; /** * @brief addWater is used to set water level at given world cell. @@ -132,26 +127,26 @@ namespace DetourNavigator * at least single object is added to the scene, false if there is already water for given cell or there is no * any other objects. */ - bool addWater(const osg::Vec2i& cellPosition, const int cellSize, const btScalar level, - const btTransform& transform); + virtual bool addWater(const osg::Vec2i& cellPosition, const int cellSize, const btScalar level, + const btTransform& transform) = 0; /** * @brief removeWater to make it no more available at the scene. * @param cellPosition allows to find cell. * @return true if there was water at given cell. */ - bool removeWater(const osg::Vec2i& cellPosition); + virtual bool removeWater(const osg::Vec2i& cellPosition) = 0; /** * @brief update start background navmesh update using current scene state. * @param playerPosition setup initial point to order build tiles of navmesh. */ - void update(const osg::Vec3f& playerPosition); + virtual void update(const osg::Vec3f& playerPosition) = 0; /** * @brief wait locks thread until all tiles are updated from last update call. */ - void wait(); + virtual void wait() = 0; /** * @brief findPath fills output iterator with points of scene surfaces to be used for actor to walk through. @@ -165,7 +160,7 @@ namespace DetourNavigator * @throws InvalidArgument if there is no navmesh for given agentHalfExtents. */ template - OutputIterator findPath(const osg::Vec3f& agentHalfExtents, const osg::Vec3f& start, + OutputIterator findPath(const osg::Vec3f& agentHalfExtents, const float stepSize, const osg::Vec3f& start, const osg::Vec3f& end, const Flags includeFlags, OutputIterator out) const { static_assert( @@ -175,30 +170,30 @@ namespace DetourNavigator >::value, "out is not an OutputIterator" ); - const auto navMesh = mNavMeshManager.getNavMesh(agentHalfExtents); - return findSmoothPath(navMesh.lock()->getValue(), toNavMeshCoordinates(mSettings, agentHalfExtents), - toNavMeshCoordinates(mSettings, start), toNavMeshCoordinates(mSettings, end), includeFlags, - mSettings, out); + const auto navMesh = getNavMesh(agentHalfExtents); + if (!navMesh) + return out; + const auto settings = getSettings(); + return findSmoothPath(navMesh->lockConst()->getImpl(), toNavMeshCoordinates(settings, agentHalfExtents), + toNavMeshCoordinates(settings, stepSize), toNavMeshCoordinates(settings, start), + toNavMeshCoordinates(settings, end), includeFlags, settings, out); } + /** + * @brief getNavMesh returns navmesh for specific agent half extents + * @return navmesh + */ + virtual SharedNavMeshCacheItem getNavMesh(const osg::Vec3f& agentHalfExtents) const = 0; + /** * @brief getNavMeshes returns all current navmeshes * @return map of agent half extents to navmesh */ - std::map getNavMeshes() const; + virtual std::map getNavMeshes() const = 0; - const Settings& getSettings() const; + virtual const Settings& getSettings() const = 0; - private: - Settings mSettings; - NavMeshManager mNavMeshManager; - std::map mAgents; - std::unordered_map mAvoidIds; - std::unordered_map mWaterIds; - - 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); + virtual void reportStats(unsigned int frameNumber, osg::Stats& stats) const = 0; }; } diff --git a/components/detournavigator/navigator.cpp b/components/detournavigator/navigatorimpl.cpp similarity index 57% rename from components/detournavigator/navigator.cpp rename to components/detournavigator/navigatorimpl.cpp index 73537ff6f..b743f26b2 100644 --- a/components/detournavigator/navigator.cpp +++ b/components/detournavigator/navigatorimpl.cpp @@ -1,4 +1,4 @@ -#include "navigator.hpp" +#include "navigatorimpl.hpp" #include "debug.hpp" #include "settingsutils.hpp" @@ -6,33 +6,33 @@ namespace DetourNavigator { - Navigator::Navigator(const Settings& settings) + NavigatorImpl::NavigatorImpl(const Settings& settings) : mSettings(settings) , mNavMeshManager(mSettings) { } - void Navigator::addAgent(const osg::Vec3f& agentHalfExtents) + void NavigatorImpl::addAgent(const osg::Vec3f& agentHalfExtents) { ++mAgents[agentHalfExtents]; mNavMeshManager.addAgent(agentHalfExtents); } - void Navigator::removeAgent(const osg::Vec3f& agentHalfExtents) + 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 Navigator::addObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform) + bool NavigatorImpl::addObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform) { return mNavMeshManager.addObject(id, shape, transform, AreaType_ground); } - bool Navigator::addObject(const ObjectId id, const ObjectShapes& shapes, const btTransform& transform) + bool NavigatorImpl::addObject(const ObjectId id, const ObjectShapes& shapes, const btTransform& transform) { bool result = addObject(id, shapes.mShape, transform); if (shapes.mAvoid) @@ -47,7 +47,7 @@ namespace DetourNavigator return result; } - bool Navigator::addObject(const ObjectId id, const DoorShapes& shapes, const btTransform& transform) + bool NavigatorImpl::addObject(const ObjectId id, const DoorShapes& shapes, const btTransform& transform) { if (addObject(id, static_cast(shapes), transform)) { @@ -61,12 +61,12 @@ namespace DetourNavigator return false; } - bool Navigator::updateObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform) + bool NavigatorImpl::updateObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform) { return mNavMeshManager.updateObject(id, shape, transform, AreaType_ground); } - bool Navigator::updateObject(const ObjectId id, const ObjectShapes& shapes, const btTransform& transform) + bool NavigatorImpl::updateObject(const ObjectId id, const ObjectShapes& shapes, const btTransform& transform) { bool result = updateObject(id, shapes.mShape, transform); if (shapes.mAvoid) @@ -81,12 +81,12 @@ namespace DetourNavigator return result; } - bool Navigator::updateObject(const ObjectId id, const DoorShapes& shapes, const btTransform& transform) + bool NavigatorImpl::updateObject(const ObjectId id, const DoorShapes& shapes, const btTransform& transform) { return updateObject(id, static_cast(shapes), transform); } - bool Navigator::removeObject(const ObjectId id) + bool NavigatorImpl::removeObject(const ObjectId id) { bool result = mNavMeshManager.removeObject(id); const auto avoid = mAvoidIds.find(id); @@ -99,50 +99,61 @@ namespace DetourNavigator return result; } - bool Navigator::addWater(const osg::Vec2i& cellPosition, const int cellSize, const btScalar level, + bool NavigatorImpl::addWater(const osg::Vec2i& cellPosition, const int cellSize, const btScalar level, const btTransform& transform) { return mNavMeshManager.addWater(cellPosition, cellSize, btTransform(transform.getBasis(), btVector3(transform.getOrigin().x(), transform.getOrigin().y(), level))); } - bool Navigator::removeWater(const osg::Vec2i& cellPosition) + bool NavigatorImpl::removeWater(const osg::Vec2i& cellPosition) { return mNavMeshManager.removeWater(cellPosition); } - void Navigator::update(const osg::Vec3f& playerPosition) + void NavigatorImpl::update(const osg::Vec3f& playerPosition) { + removeUnusedNavMeshes(); for (const auto& v : mAgents) mNavMeshManager.update(playerPosition, v.first); } - void Navigator::wait() + void NavigatorImpl::wait() { mNavMeshManager.wait(); } - std::map Navigator::getNavMeshes() const + SharedNavMeshCacheItem NavigatorImpl::getNavMesh(const osg::Vec3f& agentHalfExtents) const + { + return mNavMeshManager.getNavMesh(agentHalfExtents); + } + + std::map NavigatorImpl::getNavMeshes() const { return mNavMeshManager.getNavMeshes(); } - const Settings& Navigator::getSettings() const + const Settings& NavigatorImpl::getSettings() const { return mSettings; } - void Navigator::updateAvoidShapeId(const ObjectId id, const ObjectId avoidId) + 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); } - void Navigator::updateWaterShapeId(const ObjectId id, const ObjectId waterId) + void NavigatorImpl::updateWaterShapeId(const ObjectId id, const ObjectId waterId) { updateId(id, waterId, mWaterIds); } - void Navigator::updateId(const ObjectId id, const ObjectId updateId, std::unordered_map& ids) + void NavigatorImpl::updateId(const ObjectId id, const ObjectId updateId, std::unordered_map& ids) { auto inserted = ids.insert(std::make_pair(id, updateId)); if (!inserted.second) @@ -151,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 new file mode 100644 index 000000000..b6b3b1b7f --- /dev/null +++ b/components/detournavigator/navigatorimpl.hpp @@ -0,0 +1,67 @@ +#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVIGATORIMPL_H +#define OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVIGATORIMPL_H + +#include "navigator.hpp" +#include "navmeshmanager.hpp" + +namespace DetourNavigator +{ + class NavigatorImpl final : public Navigator + { + public: + /** + * @brief Navigator constructor initializes all internal data. Constructed object is ready to build a scene. + * @param settings allows to customize navigator work. Constructor is only place to set navigator settings. + */ + NavigatorImpl(const Settings& settings); + + void addAgent(const osg::Vec3f& agentHalfExtents) override; + + void removeAgent(const osg::Vec3f& agentHalfExtents) override; + + bool addObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform) override; + + bool addObject(const ObjectId id, const ObjectShapes& shapes, const btTransform& transform) override; + + bool addObject(const ObjectId id, const DoorShapes& shapes, const btTransform& transform) override; + + bool updateObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform) override; + + bool updateObject(const ObjectId id, const ObjectShapes& shapes, const btTransform& transform) override; + + bool updateObject(const ObjectId id, const DoorShapes& shapes, const btTransform& transform) override; + + bool removeObject(const ObjectId id) override; + + bool addWater(const osg::Vec2i& cellPosition, const int cellSize, const btScalar level, + const btTransform& transform) override; + + bool removeWater(const osg::Vec2i& cellPosition) override; + + void update(const osg::Vec3f& playerPosition) override; + + void wait() override; + + SharedNavMeshCacheItem getNavMesh(const osg::Vec3f& agentHalfExtents) const override; + + std::map getNavMeshes() const override; + + const Settings& getSettings() const override; + + void reportStats(unsigned int frameNumber, osg::Stats& stats) const override; + + private: + Settings mSettings; + NavMeshManager mNavMeshManager; + std::map mAgents; + std::unordered_map mAvoidIds; + std::unordered_map mWaterIds; + + 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(); + }; +} + +#endif diff --git a/components/detournavigator/navigatorstub.hpp b/components/detournavigator/navigatorstub.hpp new file mode 100644 index 000000000..885482a45 --- /dev/null +++ b/components/detournavigator/navigatorstub.hpp @@ -0,0 +1,90 @@ +#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVIGATORSTUB_H +#define OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVIGATORSTUB_H + +#include "navigator.hpp" + +namespace DetourNavigator +{ + class NavigatorStub final : public Navigator + { + public: + NavigatorStub() = default; + + void addAgent(const osg::Vec3f& /*agentHalfExtents*/) override {} + + void removeAgent(const osg::Vec3f& /*agentHalfExtents*/) override {} + + bool addObject(const ObjectId /*id*/, const btCollisionShape& /*shape*/, const btTransform& /*transform*/) override + { + return false; + } + + bool addObject(const ObjectId /*id*/, const ObjectShapes& /*shapes*/, const btTransform& /*transform*/) override + { + return false; + } + + bool addObject(const ObjectId /*id*/, const DoorShapes& /*shapes*/, const btTransform& /*transform*/) override + { + return false; + } + + bool updateObject(const ObjectId /*id*/, const btCollisionShape& /*shape*/, const btTransform& /*transform*/) override + { + return false; + } + + bool updateObject(const ObjectId /*id*/, const ObjectShapes& /*shapes*/, const btTransform& /*transform*/) override + { + return false; + } + + bool updateObject(const ObjectId /*id*/, const DoorShapes& /*shapes*/, const btTransform& /*transform*/) override + { + return false; + } + + bool removeObject(const ObjectId /*id*/) override + { + return false; + } + + bool addWater(const osg::Vec2i& /*cellPosition*/, const int /*cellSize*/, const btScalar /*level*/, + const btTransform& /*transform*/) override + { + return false; + } + + bool removeWater(const osg::Vec2i& /*cellPosition*/) override + { + return false; + } + + void update(const osg::Vec3f& /*playerPosition*/) override {} + + void wait() override {} + + SharedNavMeshCacheItem getNavMesh(const osg::Vec3f& /*agentHalfExtents*/) const override + { + return mEmptyNavMeshCacheItem; + } + + std::map getNavMeshes() const override + { + return std::map(); + } + + const Settings& getSettings() const override + { + return mDefaultSettings; + } + + void reportStats(unsigned int /*frameNumber*/, osg::Stats& /*stats*/) const override {} + + private: + Settings mDefaultSettings {}; + SharedNavMeshCacheItem mEmptyNavMeshCacheItem; + }; +} + +#endif 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 { + 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: + 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)); + } + }; + + 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& value, std::size_t generation) - : mValue(value), mGeneration(generation), mNavMeshRevision(0) + NavMeshCacheItem(const NavMeshPtr& impl, std::size_t generation) + : mImpl(impl), mGeneration(generation), mNavMeshRevision(0) { } - const dtNavMesh& getValue() const + const dtNavMesh& getImpl() const { - return *mValue; - } - - dtNavMesh& getValue() - { - return *mValue; + 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 66bf39aaf..a43410e91 100644 --- a/components/detournavigator/navmeshmanager.cpp +++ b/components/detournavigator/navmeshmanager.cpp @@ -5,14 +5,9 @@ #include "makenavmesh.hpp" #include "navmeshcacheitem.hpp" #include "settings.hpp" -#include "sharednavmesh.hpp" #include -#include - -#include - namespace { using DetourNavigator::ChangeType; @@ -21,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 @@ -44,9 +54,11 @@ namespace DetourNavigator bool NavMeshManager::updateObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform, const AreaType areaType) { - if (!mRecastMeshManager.updateObject(id, transform, areaType)) + const auto changedTiles = mRecastMeshManager.updateObject(id, shape, transform, areaType); + if (changedTiles.empty()) return false; - addChangedTiles(shape, transform, ChangeType::update); + for (const auto& tile : changedTiles) + addChangedTile(tile, ChangeType::update); return true; } @@ -82,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) @@ -133,11 +154,17 @@ namespace DetourNavigator else lastPlayerTile->second = playerTile; std::map tilesToPost; - const auto& cached = getCached(agentHalfExtents); + const auto cached = getCached(agentHalfExtents); + if (!cached) + { + std::ostringstream stream; + stream << "Agent with half extents is not found: " << agentHalfExtents; + throw InvalidArgument(stream.str()); + } 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) @@ -149,12 +176,8 @@ 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 = navMesh.getParams()->maxTiles; + const auto maxTiles = std::min(mSettings.mMaxTilesNumber, navMesh.getParams()->maxTiles); mRecastMeshManager.forEachTilePosition([&] (const TilePosition& tile) { if (tilesToPost.count(tile)) @@ -168,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); @@ -188,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) { @@ -218,13 +248,11 @@ namespace DetourNavigator } } - const SharedNavMeshCacheItem& NavMeshManager::getCached(const osg::Vec3f& agentHalfExtents) const + SharedNavMeshCacheItem NavMeshManager::getCached(const osg::Vec3f& agentHalfExtents) const { const auto cached = mCache.find(agentHalfExtents); if (cached != mCache.end()) return cached->second; - std::ostringstream stream; - stream << "Agent with half extents is not found: " << agentHalfExtents; - throw InvalidArgument(stream.str()); + return SharedNavMeshCacheItem(); } } diff --git a/components/detournavigator/navmeshmanager.hpp b/components/detournavigator/navmeshmanager.hpp index f44cdd251..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; @@ -67,7 +69,7 @@ namespace DetourNavigator void addChangedTile(const TilePosition& tilePosition, const ChangeType changeType); - const SharedNavMeshCacheItem& getCached(const osg::Vec3f& agentHalfExtents) const; + SharedNavMeshCacheItem getCached(const osg::Vec3f& agentHalfExtents) const; }; } diff --git a/components/detournavigator/navmeshtilescache.cpp b/components/detournavigator/navmeshtilescache.cpp index 6bfbfb395..466d2e708 100644 --- a/components/detournavigator/navmeshtilescache.cpp +++ b/components/detournavigator/navmeshtilescache.cpp @@ -1,7 +1,9 @@ #include "navmeshtilescache.hpp" #include "exceptions.hpp" -#include +#include + +#include namespace DetourNavigator { @@ -63,9 +65,8 @@ 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.Map.find(makeNavMeshKey(recastMesh, offMeshConnections)); - if (tile == tileValues->second.Map.end()) + const auto tile = tileValues->second.mMap.find(RecastMeshKeyView(recastMesh, offMeshConnections)); + if (tile == tileValues->second.mMap.end()) return Value(); acquireItemUnsafe(tile->second); @@ -87,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)) @@ -96,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].Map.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) { @@ -115,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(); @@ -127,16 +145,17 @@ namespace DetourNavigator if (tileValues == agentValues->second.end()) return; - const auto value = tileValues->second.Map.find(item.mNavMeshKey); - if (value == tileValues->second.Map.end()) + const auto value = tileValues->second.mMap.find(item.mNavMeshKey); + if (value == tileValues->second.mMap.end()) return; mUsedNavMeshDataSize -= getSize(item); mFreeNavMeshDataSize -= getSize(item); + + tileValues->second.mMap.erase(value); mFreeItems.pop_back(); - tileValues->second.Map.erase(value); - if (!tileValues->second.Map.empty()) + if (!tileValues->second.mMap.empty()) return; agentValues->second.erase(tileValues); @@ -165,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 7418c4d3a..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 Map; + 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/recastmeshbuilder.cpp b/components/detournavigator/recastmeshbuilder.cpp index e325b7eaf..71c4f0405 100644 --- a/components/detournavigator/recastmeshbuilder.cpp +++ b/components/detournavigator/recastmeshbuilder.cpp @@ -6,6 +6,7 @@ #include "exceptions.hpp" #include +#include #include #include @@ -15,14 +16,6 @@ #include -namespace -{ - osg::Vec3f makeOsgVec3f(const btVector3& value) - { - return osg::Vec3f(value.x(), value.y(), value.z()); - } -} - namespace DetourNavigator { using BulletHelpers::makeProcessTriangleCallback; @@ -175,7 +168,7 @@ namespace DetourNavigator void RecastMeshBuilder::addVertex(const btVector3& worldPosition) { - const auto navMeshPosition = toNavMeshCoordinates(mSettings, makeOsgVec3f(worldPosition)); + const auto navMeshPosition = toNavMeshCoordinates(mSettings, Misc::Convert::makeOsgVec3f(worldPosition)); mVertices.push_back(navMeshPosition.x()); mVertices.push_back(navMeshPosition.y()); mVertices.push_back(navMeshPosition.z()); diff --git a/components/detournavigator/recastmeshmanager.cpp b/components/detournavigator/recastmeshmanager.cpp index 1c3a72b59..39afdf56e 100644 --- a/components/detournavigator/recastmeshmanager.cpp +++ b/components/detournavigator/recastmeshmanager.cpp @@ -1,6 +1,5 @@ #include "recastmeshmanager.hpp" -#include #include namespace DetourNavigator diff --git a/components/detournavigator/recastmeshobject.cpp b/components/detournavigator/recastmeshobject.cpp index acaf398c1..aac0b4c3c 100644 --- a/components/detournavigator/recastmeshobject.cpp +++ b/components/detournavigator/recastmeshobject.cpp @@ -5,7 +5,6 @@ #include #include -#include namespace DetourNavigator { @@ -14,6 +13,7 @@ namespace DetourNavigator : mShape(shape) , mTransform(transform) , mAreaType(areaType) + , mLocalScaling(shape.getLocalScaling()) , mChildren(makeChildrenObjects(shape, mAreaType)) { } @@ -31,6 +31,11 @@ namespace DetourNavigator mAreaType = areaType; result = true; } + if (!(mLocalScaling == mShape.get().getLocalScaling())) + { + mLocalScaling = mShape.get().getLocalScaling(); + result = true; + } if (mShape.get().isCompound()) result = updateCompoundObject(static_cast(mShape.get()), mAreaType, mChildren) || result; diff --git a/components/detournavigator/recastmeshobject.hpp b/components/detournavigator/recastmeshobject.hpp index aff468122..f25647ae5 100644 --- a/components/detournavigator/recastmeshobject.hpp +++ b/components/detournavigator/recastmeshobject.hpp @@ -39,6 +39,7 @@ namespace DetourNavigator std::reference_wrapper mShape; btTransform mTransform; AreaType mAreaType; + btVector3 mLocalScaling; std::vector mChildren; static bool updateCompoundObject(const btCompoundShape& shape, const AreaType areaType, diff --git a/components/detournavigator/settings.cpp b/components/detournavigator/settings.cpp new file mode 100644 index 000000000..735194dba --- /dev/null +++ b/components/detournavigator/settings.cpp @@ -0,0 +1,46 @@ +#include "settings.hpp" + +#include + +namespace DetourNavigator +{ + boost::optional makeSettingsFromSettingsManager() + { + if (!::Settings::Manager::getBool("enable", "Navigator")) + return boost::optional(); + + Settings navigatorSettings; + + navigatorSettings.mBorderSize = ::Settings::Manager::getInt("border size", "Navigator"); + navigatorSettings.mCellHeight = ::Settings::Manager::getFloat("cell height", "Navigator"); + navigatorSettings.mCellSize = ::Settings::Manager::getFloat("cell size", "Navigator"); + navigatorSettings.mDetailSampleDist = ::Settings::Manager::getFloat("detail sample dist", "Navigator"); + navigatorSettings.mDetailSampleMaxError = ::Settings::Manager::getFloat("detail sample max error", "Navigator"); + navigatorSettings.mMaxClimb = 0; + navigatorSettings.mMaxSimplificationError = ::Settings::Manager::getFloat("max simplification error", "Navigator"); + navigatorSettings.mMaxSlope = 0; + navigatorSettings.mRecastScaleFactor = ::Settings::Manager::getFloat("recast scale factor", "Navigator"); + navigatorSettings.mSwimHeightScale = 0; + navigatorSettings.mMaxEdgeLen = ::Settings::Manager::getInt("max edge len", "Navigator"); + navigatorSettings.mMaxNavMeshQueryNodes = ::Settings::Manager::getInt("max nav mesh query nodes", "Navigator"); + navigatorSettings.mMaxPolys = ::Settings::Manager::getInt("max polygons per tile", "Navigator"); + navigatorSettings.mMaxTilesNumber = ::Settings::Manager::getInt("max tiles number", "Navigator"); + navigatorSettings.mMaxVertsPerPoly = ::Settings::Manager::getInt("max verts per poly", "Navigator"); + navigatorSettings.mRegionMergeSize = ::Settings::Manager::getInt("region merge size", "Navigator"); + navigatorSettings.mRegionMinSize = ::Settings::Manager::getInt("region min size", "Navigator"); + navigatorSettings.mTileSize = ::Settings::Manager::getInt("tile size", "Navigator"); + navigatorSettings.mAsyncNavMeshUpdaterThreads = static_cast(::Settings::Manager::getInt("async nav mesh updater threads", "Navigator")); + navigatorSettings.mMaxNavMeshTilesCacheSize = static_cast(::Settings::Manager::getInt("max nav mesh tiles cache size", "Navigator")); + navigatorSettings.mMaxPolygonPathSize = static_cast(::Settings::Manager::getInt("max polygon path size", "Navigator")); + navigatorSettings.mMaxSmoothPathSize = static_cast(::Settings::Manager::getInt("max smooth path size", "Navigator")); + navigatorSettings.mTrianglesPerChunk = static_cast(::Settings::Manager::getInt("triangles per chunk", "Navigator")); + navigatorSettings.mEnableWriteRecastMeshToFile = ::Settings::Manager::getBool("enable write recast mesh to file", "Navigator"); + navigatorSettings.mEnableWriteNavMeshToFile = ::Settings::Manager::getBool("enable write nav mesh to file", "Navigator"); + navigatorSettings.mRecastMeshPathPrefix = ::Settings::Manager::getString("recast mesh path prefix", "Navigator"); + navigatorSettings.mNavMeshPathPrefix = ::Settings::Manager::getString("nav mesh path prefix", "Navigator"); + navigatorSettings.mEnableRecastMeshFileNameRevision = ::Settings::Manager::getBool("enable recast mesh file name revision", "Navigator"); + navigatorSettings.mEnableNavMeshFileNameRevision = ::Settings::Manager::getBool("enable nav mesh file name revision", "Navigator"); + + return navigatorSettings; + } +} diff --git a/components/detournavigator/settings.hpp b/components/detournavigator/settings.hpp index 3e537c2fb..dc0e5dc5a 100644 --- a/components/detournavigator/settings.hpp +++ b/components/detournavigator/settings.hpp @@ -1,41 +1,46 @@ #ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_SETTINGS_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_SETTINGS_H +#include + #include namespace DetourNavigator { struct Settings { - bool mEnableWriteRecastMeshToFile; - bool mEnableWriteNavMeshToFile; - bool mEnableRecastMeshFileNameRevision; - bool mEnableNavMeshFileNameRevision; - float mCellHeight; - float mCellSize; - float mDetailSampleDist; - float mDetailSampleMaxError; - float mMaxClimb; - float mMaxSimplificationError; - float mMaxSlope; - float mRecastScaleFactor; - float mSwimHeightScale; - int mBorderSize; - int mMaxEdgeLen; - int mMaxNavMeshQueryNodes; - int mMaxPolys; - int mMaxVertsPerPoly; - int mRegionMergeSize; - int mRegionMinSize; - int mTileSize; - std::size_t mAsyncNavMeshUpdaterThreads; - std::size_t mMaxNavMeshTilesCacheSize; - std::size_t mMaxPolygonPathSize; - std::size_t mMaxSmoothPathSize; - std::size_t mTrianglesPerChunk; + bool mEnableWriteRecastMeshToFile = false; + bool mEnableWriteNavMeshToFile = false; + bool mEnableRecastMeshFileNameRevision = false; + bool mEnableNavMeshFileNameRevision = false; + float mCellHeight = 0; + float mCellSize = 0; + float mDetailSampleDist = 0; + float mDetailSampleMaxError = 0; + float mMaxClimb = 0; + float mMaxSimplificationError = 0; + float mMaxSlope = 0; + float mRecastScaleFactor = 0; + float mSwimHeightScale = 0; + int mBorderSize = 0; + int mMaxEdgeLen = 0; + int mMaxNavMeshQueryNodes = 0; + int mMaxPolys = 0; + int mMaxTilesNumber = 0; + int mMaxVertsPerPoly = 0; + int mRegionMergeSize = 0; + int mRegionMinSize = 0; + int mTileSize = 0; + std::size_t mAsyncNavMeshUpdaterThreads = 0; + std::size_t mMaxNavMeshTilesCacheSize = 0; + std::size_t mMaxPolygonPathSize = 0; + std::size_t mMaxSmoothPathSize = 0; + std::size_t mTrianglesPerChunk = 0; std::string mRecastMeshPathPrefix; std::string mNavMeshPathPrefix; }; + + boost::optional makeSettingsFromSettingsManager(); } #endif diff --git a/components/detournavigator/settingsutils.hpp b/components/detournavigator/settingsutils.hpp index d96ea53cc..a22205b2a 100644 --- a/components/detournavigator/settingsutils.hpp +++ b/components/detournavigator/settingsutils.hpp @@ -31,6 +31,11 @@ namespace DetourNavigator return agentHalfExtents.x() * settings.mRecastScaleFactor; } + inline float toNavMeshCoordinates(const Settings& settings, float value) + { + return value * settings.mRecastScaleFactor; + } + inline osg::Vec3f toNavMeshCoordinates(const Settings& settings, osg::Vec3f position) { std::swap(position.y(), position.z()); diff --git a/components/detournavigator/tilecachedrecastmeshmanager.cpp b/components/detournavigator/tilecachedrecastmeshmanager.cpp index d624800e9..0cb1cfb80 100644 --- a/components/detournavigator/tilecachedrecastmeshmanager.cpp +++ b/components/detournavigator/tilecachedrecastmeshmanager.cpp @@ -15,48 +15,57 @@ namespace DetourNavigator bool result = false; auto& tilesPositions = mObjectsTilesPositions[id]; const auto border = getBorderSize(mSettings); - getTilesPositions(shape, transform, mSettings, [&] (const TilePosition& tilePosition) - { - const auto tiles = mTiles.lock(); - auto tile = tiles->find(tilePosition); - if (tile == tiles->end()) + { + auto tiles = mTiles.lock(); + getTilesPositions(shape, transform, mSettings, [&] (const TilePosition& tilePosition) { - auto tileBounds = makeTileBounds(mSettings, tilePosition); - tileBounds.mMin -= osg::Vec2f(border, border); - tileBounds.mMax += osg::Vec2f(border, border); - tile = tiles->insert(std::make_pair(tilePosition, - CachedRecastMeshManager(mSettings, tileBounds))).first; - } - if (tile->second.addObject(id, shape, transform, areaType)) - { - tilesPositions.push_back(tilePosition); - result = true; - } - }); + if (addTile(id, shape, transform, areaType, tilePosition, border, tiles.get())) + { + tilesPositions.insert(tilePosition); + result = true; + } + }); + } if (result) ++mRevision; return result; } - bool TileCachedRecastMeshManager::updateObject(const ObjectId id, const btTransform& transform, - const AreaType areaType) + std::vector TileCachedRecastMeshManager::updateObject(const ObjectId id, const btCollisionShape& shape, + const btTransform& transform, const AreaType areaType) { const auto object = mObjectsTilesPositions.find(id); if (object == mObjectsTilesPositions.end()) - return false; - bool result = false; + return std::vector(); + auto& currentTiles = object->second; + const auto border = getBorderSize(mSettings); + std::vector changedTiles; + std::set newTiles; { - const auto tiles = mTiles.lock(); - for (const auto& tilePosition : object->second) + auto tiles = mTiles.lock(); + const auto onTilePosition = [&] (const TilePosition& tilePosition) { - const auto tile = tiles->find(tilePosition); - if (tile != tiles->end()) - result = tile->second.updateObject(id, transform, areaType) || result; - } + if (currentTiles.count(tilePosition)) + { + newTiles.insert(tilePosition); + if (updateTile(id, transform, areaType, tilePosition, tiles.get())) + changedTiles.push_back(tilePosition); + } + else if (addTile(id, shape, transform, areaType, tilePosition, border, tiles.get())) + { + newTiles.insert(tilePosition); + changedTiles.push_back(tilePosition); + } + }; + getTilesPositions(shape, transform, mSettings, onTilePosition); + for (const auto& tile : currentTiles) + if (!newTiles.count(tile) && removeTile(id, tile, tiles.get())) + changedTiles.push_back(tile); } - if (result) + std::swap(currentTiles, newTiles); + if (!changedTiles.empty()) ++mRevision; - return result; + return changedTiles; } boost::optional TileCachedRecastMeshManager::removeObject(const ObjectId id) @@ -65,17 +74,14 @@ namespace DetourNavigator if (object == mObjectsTilesPositions.end()) return boost::none; boost::optional result; - for (const auto& tilePosition : object->second) { - const auto tiles = mTiles.lock(); - const auto tile = tiles->find(tilePosition); - if (tile == tiles->end()) - continue; - const auto tileResult = tile->second.removeObject(id); - if (tile->second.isEmpty()) - tiles->erase(tile); - if (tileResult && !result) - result = tileResult; + auto tiles = mTiles.lock(); + for (const auto& tilePosition : object->second) + { + const auto removed = removeTile(id, tilePosition, tiles.get()); + if (removed && !result) + result = removed; + } } if (result) ++mRevision; @@ -172,4 +178,38 @@ namespace DetourNavigator { return mRevision; } + + bool TileCachedRecastMeshManager::addTile(const ObjectId id, const btCollisionShape& shape, + const btTransform& transform, const AreaType areaType, const TilePosition& tilePosition, float border, + std::map& tiles) + { + auto tile = tiles.find(tilePosition); + if (tile == tiles.end()) + { + auto tileBounds = makeTileBounds(mSettings, tilePosition); + tileBounds.mMin -= osg::Vec2f(border, border); + tileBounds.mMax += osg::Vec2f(border, border); + tile = tiles.insert(std::make_pair(tilePosition, CachedRecastMeshManager(mSettings, tileBounds))).first; + } + return tile->second.addObject(id, shape, transform, areaType); + } + + bool TileCachedRecastMeshManager::updateTile(const ObjectId id, const btTransform& transform, + const AreaType areaType, const TilePosition& tilePosition, std::map& tiles) + { + const auto tile = tiles.find(tilePosition); + return tile != tiles.end() && tile->second.updateObject(id, transform, areaType); + } + + boost::optional TileCachedRecastMeshManager::removeTile(const ObjectId id, + const TilePosition& tilePosition, std::map& tiles) + { + const auto tile = tiles.find(tilePosition); + if (tile == tiles.end()) + return boost::optional(); + const auto tileResult = tile->second.removeObject(id); + if (tile->second.isEmpty()) + tiles.erase(tile); + return tileResult; + } } diff --git a/components/detournavigator/tilecachedrecastmeshmanager.hpp b/components/detournavigator/tilecachedrecastmeshmanager.hpp index f82ef85c5..a3d0ae1e5 100644 --- a/components/detournavigator/tilecachedrecastmeshmanager.hpp +++ b/components/detournavigator/tilecachedrecastmeshmanager.hpp @@ -8,6 +8,7 @@ #include #include +#include namespace DetourNavigator { @@ -19,7 +20,8 @@ namespace DetourNavigator bool addObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform, const AreaType areaType); - bool updateObject(const ObjectId id, const btTransform& transform, const AreaType areaType); + std::vector updateObject(const ObjectId id, const btCollisionShape& shape, + const btTransform& transform, const AreaType areaType); boost::optional removeObject(const ObjectId id); @@ -43,9 +45,19 @@ namespace DetourNavigator private: const Settings& mSettings; Misc::ScopeGuarded> mTiles; - std::unordered_map> mObjectsTilesPositions; + std::unordered_map> mObjectsTilesPositions; std::map> mWaterTilesPositions; std::size_t mRevision = 0; + + bool addTile(const ObjectId id, const btCollisionShape& shape, const btTransform& transform, + const AreaType areaType, const TilePosition& tilePosition, float border, + std::map& tiles); + + bool updateTile(const ObjectId id, const btTransform& transform, const AreaType areaType, + const TilePosition& tilePosition, std::map& tiles); + + boost::optional removeTile(const ObjectId id, const TilePosition& tilePosition, + std::map& tiles); }; } diff --git a/components/esm/aipackage.cpp b/components/esm/aipackage.cpp index efcd6651e..abbd2c62c 100644 --- a/components/esm/aipackage.cpp +++ b/components/esm/aipackage.cpp @@ -7,7 +7,7 @@ namespace ESM { void AIData::blank() { - mHello = mU1 = mFight = mFlee = mAlarm = mU2 = mU3 = mU4 = 0; + mHello = mFight = mFlee = mAlarm = mU1 = mU2 = mU3 = 0; mServices = 0; } diff --git a/components/esm/aipackage.hpp b/components/esm/aipackage.hpp index 5e08806c8..026e65dd8 100644 --- a/components/esm/aipackage.hpp +++ b/components/esm/aipackage.hpp @@ -16,10 +16,9 @@ namespace ESM struct AIData { - unsigned char mHello; - char mU1; + unsigned short mHello; // This is the base value for greeting distance [0, 65535] unsigned char mFight, mFlee, mAlarm; // These are probabilities [0, 100] - char mU2, mU3, mU4; // Unknown values + char mU1, mU2, mU3; // Unknown values int mServices; // See the Services enum void blank(); diff --git a/components/esm/aisequence.cpp b/components/esm/aisequence.cpp index 6e41ba302..7891299e3 100644 --- a/components/esm/aisequence.cpp +++ b/components/esm/aisequence.cpp @@ -3,8 +3,6 @@ #include "esmreader.hpp" #include "esmwriter.hpp" -#include "defs.hpp" - #include namespace ESM diff --git a/components/esm/esmcommon.hpp b/components/esm/esmcommon.hpp index d635ba6df..97ce88556 100644 --- a/components/esm/esmcommon.hpp +++ b/components/esm/esmcommon.hpp @@ -35,7 +35,10 @@ public: return false; return std::strncmp(self()->ro_data(), str, size) == 0; } - bool operator==(const char* const str) const + + //this operator will not be used for char[N], only for char* + template::value>::type> + bool operator==(const T* const& str) const { char const* const data = self()->ro_data(); for(size_t i = 0; i < size; ++i) diff --git a/components/esm/esmreader.cpp b/components/esm/esmreader.cpp index a6b947dd3..2f4f4917c 100644 --- a/components/esm/esmreader.cpp +++ b/components/esm/esmreader.cpp @@ -27,6 +27,7 @@ ESMReader::ESMReader() , mEncoder(nullptr) , mFileSize(0) { + clearCtx(); } int ESMReader::getFormat() const @@ -50,16 +51,21 @@ void ESMReader::restoreContext(const ESM_Context &rc) void ESMReader::close() { mEsm.reset(); - mCtx.filename.clear(); - mCtx.leftFile = 0; - mCtx.leftRec = 0; - mCtx.leftSub = 0; - mCtx.subCached = false; - mCtx.recName.clear(); - mCtx.subName.clear(); + clearCtx(); mHeader.blank(); } +void ESMReader::clearCtx() +{ + mCtx.filename.clear(); + mCtx.leftFile = 0; + mCtx.leftRec = 0; + mCtx.leftSub = 0; + mCtx.subCached = false; + mCtx.recName.clear(); + mCtx.subName.clear(); +} + void ESMReader::openRaw(Files::IStreamPtr _esm, const std::string& name) { close(); diff --git a/components/esm/esmreader.hpp b/components/esm/esmreader.hpp index 6e84fa7d4..72a7b4790 100644 --- a/components/esm/esmreader.hpp +++ b/components/esm/esmreader.hpp @@ -1,8 +1,7 @@ #ifndef OPENMW_ESM_READER_H #define OPENMW_ESM_READER_H -#include -#include +#include #include #include #include @@ -269,6 +268,8 @@ public: size_t getFileSize() const { return mFileSize; } private: + void clearCtx(); + Files::IStreamPtr mEsm; ESM_Context mCtx; diff --git a/components/esm/loadcrea.cpp b/components/esm/loadcrea.cpp index f04439041..17f03c69b 100644 --- a/components/esm/loadcrea.cpp +++ b/components/esm/loadcrea.cpp @@ -22,8 +22,9 @@ namespace ESM { mTransport.mList.clear(); mScale = 1.f; - mHasAI = false; mAiData.blank(); + mAiData.mFight = 90; + mAiData.mFlee = 20; bool hasName = false; bool hasNpdt = false; @@ -68,7 +69,6 @@ namespace ESM { break; case ESM::FourCC<'A','I','D','T'>::value: esm.getHExact(&mAiData, sizeof(mAiData)); - mHasAI = true; break; case ESM::FourCC<'D','O','D','T'>::value: case ESM::FourCC<'D','N','A','M'>::value: @@ -128,14 +128,7 @@ namespace ESM { mInventory.save(esm); mSpells.save(esm); - if (mAiData.mHello != 0 - || mAiData.mFight != 0 - || mAiData.mFlee != 0 - || mAiData.mAlarm != 0 - || mAiData.mServices != 0) - { - esm.writeHNT("AIDT", mAiData, sizeof(mAiData)); - } + esm.writeHNT("AIDT", mAiData, sizeof(mAiData)); mTransport.save(esm); mAiPackage.save(esm); } @@ -159,8 +152,9 @@ namespace ESM { mOriginal.clear(); mInventory.mList.clear(); mSpells.mList.clear(); - mHasAI = false; mAiData.blank(); + mAiData.mFight = 90; + mAiData.mFlee = 20; mAiPackage.mList.clear(); mTransport.mList.clear(); } diff --git a/components/esm/loadcrea.hpp b/components/esm/loadcrea.hpp index a5147619c..be6a72b8d 100644 --- a/components/esm/loadcrea.hpp +++ b/components/esm/loadcrea.hpp @@ -91,7 +91,6 @@ struct Creature InventoryList mInventory; SpellList mSpells; - bool mHasAI; AIData mAiData; AIPackageList mAiPackage; Transport mTransport; 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/loadnpc.cpp b/components/esm/loadnpc.cpp index 6e7ba66c5..db6b6d31b 100644 --- a/components/esm/loadnpc.cpp +++ b/components/esm/loadnpc.cpp @@ -19,7 +19,7 @@ namespace ESM mTransport.mList.clear(); mAiPackage.mList.clear(); mAiData.blank(); - mHasAI = false; + mAiData.mHello = mAiData.mFight = mAiData.mFlee = 30; bool hasName = false; bool hasNpdt = false; @@ -96,7 +96,6 @@ namespace ESM break; case ESM::FourCC<'A','I','D','T'>::value: esm.getHExact(&mAiData, sizeof(mAiData)); - mHasAI= true; break; case ESM::FourCC<'D','O','D','T'>::value: case ESM::FourCC<'D','N','A','M'>::value: @@ -165,14 +164,7 @@ namespace ESM mInventory.save(esm); mSpells.save(esm); - if (mAiData.mHello != 0 - || mAiData.mFight != 0 - || mAiData.mFlee != 0 - || mAiData.mAlarm != 0 - || mAiData.mServices != 0) - { - esm.writeHNT("AIDT", mAiData, sizeof(mAiData)); - } + esm.writeHNT("AIDT", mAiData, sizeof(mAiData)); mTransport.save(esm); @@ -198,7 +190,7 @@ namespace ESM mInventory.mList.clear(); mSpells.mList.clear(); mAiData.blank(); - mHasAI = false; + mAiData.mHello = mAiData.mFight = mAiData.mFlee = 30; mTransport.mList.clear(); mAiPackage.mList.clear(); mName.clear(); diff --git a/components/esm/loadnpc.hpp b/components/esm/loadnpc.hpp index fbe1dca1f..746913008 100644 --- a/components/esm/loadnpc.hpp +++ b/components/esm/loadnpc.hpp @@ -122,7 +122,6 @@ struct NPC SpellList mSpells; AIData mAiData; - bool mHasAI; Transport mTransport; 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/esm/loadskil.cpp b/components/esm/loadskil.cpp index 36204c940..61cca7d0d 100644 --- a/components/esm/loadskil.cpp +++ b/components/esm/loadskil.cpp @@ -2,8 +2,6 @@ #include -#include - #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" @@ -97,7 +95,7 @@ namespace ESM "stealth_speechcraft.dds", "stealth_handtohand.dds", }; - const boost::array Skill::sSkillIds = {{ + const std::array Skill::sSkillIds = {{ Block, Armorer, MediumArmor, diff --git a/components/esm/loadskil.hpp b/components/esm/loadskil.hpp index 5430b422d..099264fab 100644 --- a/components/esm/loadskil.hpp +++ b/components/esm/loadskil.hpp @@ -1,10 +1,9 @@ #ifndef OPENMW_ESM_SKIL_H #define OPENMW_ESM_SKIL_H +#include #include -#include - #include "defs.hpp" namespace ESM { @@ -76,7 +75,7 @@ struct Skill static const std::string sSkillNames[Length]; static const std::string sSkillNameIds[Length]; static const std::string sIconNames[Length]; - static const boost::array sSkillIds; + static const std::array sSkillIds; void load(ESMReader &esm, bool &isDeleted); void save(ESMWriter &esm, bool isDeleted = false) const; diff --git a/components/esmterrain/storage.cpp b/components/esmterrain/storage.cpp index dfdc9718d..1e23569b5 100644 --- a/components/esmterrain/storage.cpp +++ b/components/esmterrain/storage.cpp @@ -1,7 +1,6 @@ #include "storage.hpp" #include -#include #include @@ -47,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) @@ -159,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) { @@ -176,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; @@ -208,7 +194,7 @@ namespace ESMTerrain colours->resize(numVerts*numVerts); osg::Vec3f normal; - osg::Vec4f color; + osg::Vec4ub color; float vertY = 0; float vertX = 0; @@ -296,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; @@ -389,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())); @@ -430,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; @@ -444,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/files/configurationmanager.cpp b/components/files/configurationmanager.cpp index c58130f96..3df6faf62 100644 --- a/components/files/configurationmanager.cpp +++ b/components/files/configurationmanager.cpp @@ -3,8 +3,6 @@ #include #include -#include -#include #include /** * \namespace Files diff --git a/components/fontloader/fontloader.cpp b/components/fontloader/fontloader.cpp index 0378e294e..dae83a1f9 100644 --- a/components/fontloader/fontloader.cpp +++ b/components/fontloader/fontloader.cpp @@ -3,7 +3,6 @@ #include #include -#include #include diff --git a/components/interpreter/miscopcodes.hpp b/components/interpreter/miscopcodes.hpp index 77e5a0079..29d1c063b 100644 --- a/components/interpreter/miscopcodes.hpp +++ b/components/interpreter/miscopcodes.hpp @@ -1,7 +1,6 @@ #ifndef INTERPRETER_MISCOPCODES_H_INCLUDED #define INTERPRETER_MISCOPCODES_H_INCLUDED -#include #include #include #include diff --git a/apps/openmw/mwphysics/convert.hpp b/components/misc/convert.hpp similarity index 59% rename from apps/openmw/mwphysics/convert.hpp rename to components/misc/convert.hpp index c5075a2c3..c5671f016 100644 --- a/apps/openmw/mwphysics/convert.hpp +++ b/components/misc/convert.hpp @@ -1,14 +1,25 @@ -#ifndef OPENMW_MWPHYSICS_CONVERT_H -#define OPENMW_MWPHYSICS_CONVERT_H +#ifndef OPENMW_COMPONENTS_MISC_CONVERT_H +#define OPENMW_COMPONENTS_MISC_CONVERT_H +#include #include #include - #include #include -namespace MWPhysics +namespace Misc { +namespace Convert +{ + inline osg::Vec3f makeOsgVec3f(const float* values) + { + return osg::Vec3f(values[0], values[1], values[2]); + } + + inline osg::Vec3f makeOsgVec3f(const btVector3& value) + { + return osg::Vec3f(value.x(), value.y(), value.z()); + } inline btVector3 toBullet(const osg::Vec3f& vec) { @@ -29,7 +40,7 @@ namespace MWPhysics { return osg::Quat(quat.x(), quat.y(), quat.z(), quat.w()); } - +} } -#endif +#endif \ No newline at end of file diff --git a/components/misc/gcd.hpp b/components/misc/gcd.hpp new file mode 100644 index 000000000..fd9e972e7 --- /dev/null +++ b/components/misc/gcd.hpp @@ -0,0 +1,13 @@ +#ifndef MISC_GCD_H +#define MISC_GCD_H + +namespace Misc +{ + // TODO: replace to the std::gcd() when the C++17 will be available. + int gcd(int a, int b) + { + return b == 0 ? a : gcd(b, a % b); + } +} + +#endif diff --git a/components/misc/guarded.hpp b/components/misc/guarded.hpp index 4cb0564b1..559476867 100644 --- a/components/misc/guarded.hpp +++ b/components/misc/guarded.hpp @@ -83,33 +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); - } - - private: - std::shared_ptr mMutex; - std::shared_ptr mValue; - }; } #endif diff --git a/components/misc/rng.cpp b/components/misc/rng.cpp index e402f0b79..09279e85e 100644 --- a/components/misc/rng.cpp +++ b/components/misc/rng.cpp @@ -8,9 +8,9 @@ namespace Misc std::mt19937 Rng::generator = std::mt19937(); - void Rng::init() + void Rng::init(unsigned int seed) { - generator.seed(static_cast(std::chrono::high_resolution_clock::now().time_since_epoch().count())); + generator.seed(seed); } float Rng::rollProbability() @@ -28,4 +28,8 @@ namespace Misc return max > 0 ? std::uniform_int_distribution(0, max - 1)(generator) : 0; } + unsigned int Rng::generateDefaultSeed() + { + return static_cast(std::chrono::high_resolution_clock::now().time_since_epoch().count()); + } } diff --git a/components/misc/rng.hpp b/components/misc/rng.hpp index ff56906d9..65a554cf2 100644 --- a/components/misc/rng.hpp +++ b/components/misc/rng.hpp @@ -18,7 +18,7 @@ public: static std::mt19937 generator; /// seed the RNG - static void init(); + static void init(unsigned int seed = generateDefaultSeed()); /// return value in range [0.0f, 1.0f) <- note open upper range. static float rollProbability(); @@ -31,6 +31,9 @@ public: /// return value in range [0, 99] static int roll0to99() { return rollDice(100); } + + /// returns default seed for RNG + static unsigned int generateDefaultSeed(); }; } diff --git a/components/misc/stringops.hpp b/components/misc/stringops.hpp index 79fa36d1e..f0b488f28 100644 --- a/components/misc/stringops.hpp +++ b/components/misc/stringops.hpp @@ -1,7 +1,6 @@ #ifndef MISC_STRINGOPS_H #define MISC_STRINGOPS_H -#include #include #include #include @@ -26,36 +25,7 @@ public: /// Don't use tolower(int) because that depends on global locale. static char toLower(char c) { - switch(c) - { - case 'A':return 'a'; - case 'B':return 'b'; - case 'C':return 'c'; - case 'D':return 'd'; - case 'E':return 'e'; - case 'F':return 'f'; - case 'G':return 'g'; - case 'H':return 'h'; - case 'I':return 'i'; - case 'J':return 'j'; - case 'K':return 'k'; - case 'L':return 'l'; - case 'M':return 'm'; - case 'N':return 'n'; - case 'O':return 'o'; - case 'P':return 'p'; - case 'Q':return 'q'; - case 'R':return 'r'; - case 'S':return 's'; - case 'T':return 't'; - case 'U':return 'u'; - case 'V':return 'v'; - case 'W':return 'w'; - case 'X':return 'x'; - case 'Y':return 'y'; - case 'Z':return 'z'; - default:return c; - }; + return (c >= 'A' && c <= 'Z') ? c + 'a' - 'A' : c; } static Utf8Stream::UnicodeChar toLowerUtf8(Utf8Stream::UnicodeChar ch) @@ -241,6 +211,32 @@ public: } return str; } + + /** @brief Replaces the first occurrence of a string in another string. + * + * @param str The string to operate on. + * @param what The string to replace. + * @param with The replacement string. + * @param whatLen The length of the string to replace. + * @param withLen The length of the replacement string. + * + * @return A reference to the string passed in @p str. + */ + static std::string &replace(std::string &str, const char *what, const char *with, + std::size_t whatLen=std::string::npos, std::size_t withLen=std::string::npos) + { + if (whatLen == std::string::npos) + whatLen = strlen(what); + + if (withLen == std::string::npos) + withLen = strlen(with); + + std::size_t found; + if ((found = str.find(what)) != std::string::npos) + str.replace(found, whatLen, with, withLen); + + return str; + } }; } diff --git a/components/myguiplatform/myguirendermanager.cpp b/components/myguiplatform/myguirendermanager.cpp index a3f7f76a2..33cc31e2e 100644 --- a/components/myguiplatform/myguirendermanager.cpp +++ b/components/myguiplatform/myguirendermanager.cpp @@ -1,7 +1,5 @@ #include "myguirendermanager.hpp" -#include - #include #include diff --git a/components/myguiplatform/myguitexture.cpp b/components/myguiplatform/myguitexture.cpp index 756893974..598f5a14e 100644 --- a/components/myguiplatform/myguitexture.cpp +++ b/components/myguiplatform/myguitexture.cpp @@ -1,7 +1,6 @@ #include "myguitexture.hpp" #include -#include #include diff --git a/components/nif/controller.cpp b/components/nif/controller.cpp index 26c3f43f0..6de720b52 100644 --- a/components/nif/controller.cpp +++ b/components/nif/controller.cpp @@ -173,6 +173,18 @@ namespace Nif data.post(nif); } + void NiRollController::read(NIFStream *nif) + { + Controller::read(nif); + data.read(nif); + } + + void NiRollController::post(NIFFile *nif) + { + Controller::post(nif); + data.post(nif); + } + void NiGeomMorpherController::read(NIFStream *nif) { Controller::read(nif); diff --git a/components/nif/controller.hpp b/components/nif/controller.hpp index 7a05b0715..113a7becd 100644 --- a/components/nif/controller.hpp +++ b/components/nif/controller.hpp @@ -136,6 +136,15 @@ public: void post(NIFFile *nif); }; +class NiRollController : public Controller +{ +public: + NiFloatDataPtr data; + + void read(NIFStream *nif); + void post(NIFFile *nif); +}; + class NiGeomMorpherController : public Controller { public: 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/nif/niffile.cpp b/components/nif/niffile.cpp index 66bbfdb65..0519e0cc6 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -73,6 +73,7 @@ static std::map makeFactory() newFactory.insert(makeEntry("NiGeomMorpherController", &construct , RC_NiGeomMorpherController )); newFactory.insert(makeEntry("NiKeyframeController", &construct , RC_NiKeyframeController )); newFactory.insert(makeEntry("NiAlphaController", &construct , RC_NiAlphaController )); + newFactory.insert(makeEntry("NiRollController", &construct , RC_NiRollController )); newFactory.insert(makeEntry("NiUVController", &construct , RC_NiUVController )); newFactory.insert(makeEntry("NiPathController", &construct , RC_NiPathController )); newFactory.insert(makeEntry("NiMaterialColorController", &construct , RC_NiMaterialColorController )); diff --git a/components/nif/record.hpp b/components/nif/record.hpp index ee4d508ab..4a044ac47 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -60,6 +60,7 @@ enum RecordType RC_NiGeomMorpherController, RC_NiKeyframeController, RC_NiAlphaController, + RC_NiRollController, RC_NiUVController, RC_NiPathController, RC_NiMaterialColorController, diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index 1a526c63b..72933fc32 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -1,9 +1,6 @@ #include "bulletnifloader.hpp" -#include #include -#include -#include #include #include @@ -15,8 +12,6 @@ #include #include -#include -#include #include namespace diff --git a/components/nifosg/controller.cpp b/components/nifosg/controller.cpp index 83841e0e5..4029e9b15 100644 --- a/components/nifosg/controller.cpp +++ b/components/nifosg/controller.cpp @@ -309,6 +309,47 @@ void VisController::operator() (osg::Node* node, osg::NodeVisitor* nv) traverse(node, nv); } +RollController::RollController(const Nif::NiFloatData *data) + : mData(data->mKeyList, 1.f) + , mStartingTime(0) +{ +} + +RollController::RollController() : mStartingTime(0) +{ +} + +RollController::RollController(const RollController ©, const osg::CopyOp ©op) + : osg::NodeCallback(copy, copyop) + , Controller(copy) + , mData(copy.mData) + , mStartingTime(0) +{ +} + +void RollController::operator() (osg::Node* node, osg::NodeVisitor* nv) +{ + traverse(node, nv); + + if (hasInput()) + { + double newTime = nv->getFrameStamp()->getSimulationTime(); + double duration = newTime - mStartingTime; + mStartingTime = newTime; + + float value = mData.interpKey(getInputValue(nv)); + osg::MatrixTransform* transform = static_cast(node); + osg::Matrix matrix = transform->getMatrix(); + + // Rotate around "roll" axis. + // Note: in original game rotation speed is the framerate-dependent in a very tricky way. + // Do not replicate this behaviour until we will really need it. + // For now consider controller's current value as an angular speed in radians per 1/60 seconds. + matrix = osg::Matrix::rotate(value * duration * 60.f, 0, 0, 1) * matrix; + transform->setMatrix(matrix); + } +} + AlphaController::AlphaController(const Nif::NiFloatData *data) : mData(data->mKeyList, 1.f) { diff --git a/components/nifosg/controller.hpp b/components/nifosg/controller.hpp index 0e87af44f..36217f31a 100644 --- a/components/nifosg/controller.hpp +++ b/components/nifosg/controller.hpp @@ -246,6 +246,22 @@ namespace NifOsg virtual void operator() (osg::Node* node, osg::NodeVisitor* nv); }; + class RollController : public osg::NodeCallback, public SceneUtil::Controller + { + private: + FloatInterpolator mData; + double mStartingTime; + + public: + RollController(const Nif::NiFloatData *data); + RollController(); + RollController(const RollController& copy, const osg::CopyOp& copyop); + + virtual void operator() (osg::Node* node, osg::NodeVisitor* nv); + + META_Object(NifOsg, RollController) + }; + class AlphaController : public SceneUtil::StateSetUpdater, public SceneUtil::Controller { private: diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 52e548a69..a1aa74cab 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -21,7 +21,6 @@ #include #include #include -#include #include #include @@ -245,10 +244,8 @@ namespace NifOsg osg::ref_ptr callback(new NifOsg::KeyframeController(key->data.getPtr())); callback->setFunction(std::shared_ptr(new NifOsg::ControllerFunction(key))); - if (target.mKeyframeControllers.find(strdata->string) != target.mKeyframeControllers.end()) + if (!target.mKeyframeControllers.emplace(strdata->string, callback).second) Log(Debug::Verbose) << "Controller " << strdata->string << " present more than once in " << nif->getFilename() << ", ignoring later version"; - else - target.mKeyframeControllers[strdata->string] = callback; } } @@ -434,8 +431,23 @@ namespace NifOsg osg::ref_ptr node; osg::Object::DataVariance dataVariance = osg::Object::UNSPECIFIED; + // TODO: it is unclear how to handle transformations of LOD and Switch nodes and controllers for them. switch (nifNode->recType) { + case Nif::RC_NiLODNode: + { + const Nif::NiLODNode* niLodNode = static_cast(nifNode); + node = handleLodNode(niLodNode); + dataVariance = osg::Object::STATIC; + break; + } + case Nif::RC_NiSwitchNode: + { + const Nif::NiSwitchNode* niSwitchNode = static_cast(nifNode); + node = handleSwitchNode(niSwitchNode); + dataVariance = osg::Object::STATIC; + break; + } case Nif::RC_NiTriShape: case Nif::RC_NiAutoNormalParticles: case Nif::RC_NiRotatingParticles: @@ -597,35 +609,6 @@ namespace NifOsg if (nifNode->recType != Nif::RC_NiTriShape && !nifNode->controller.empty() && node->getDataVariance() == osg::Object::DYNAMIC) handleNodeControllers(nifNode, static_cast(node.get()), animflags); - if (nifNode->recType == Nif::RC_NiLODNode) - { - const Nif::NiLODNode* niLodNode = static_cast(nifNode); - osg::ref_ptr lod = handleLodNode(niLodNode); - node->addChild(lod); // unsure if LOD should be above or below this node's transform - node = lod; - } - - if (nifNode->recType == Nif::RC_NiSwitchNode) - { - const Nif::NiSwitchNode* niSwitchNode = static_cast(nifNode); - osg::ref_ptr switchNode = handleSwitchNode(niSwitchNode); - node->addChild(switchNode); - - if (niSwitchNode->name == Constants::NightDayLabel && !SceneUtil::hasUserDescription(rootNode, Constants::NightDayLabel)) - rootNode->getOrCreateUserDataContainer()->addDescription(Constants::NightDayLabel); - - const Nif::NodeList &children = niSwitchNode->children; - for(size_t i = 0;i < children.length();++i) - { - if(!children[i].empty()) - handleNode(children[i].getPtr(), switchNode, imageManager, boundTextures, animflags, skipMeshes, hasMarkers, isAnimated, textKeys, rootNode); - } - - // show only first child by default - switchNode->setSingleChildOn(0); - return switchNode; - } - const Nif::NiNode *ninode = dynamic_cast(nifNode); if(ninode) { @@ -644,6 +627,16 @@ namespace NifOsg } } + if (nifNode->recType == Nif::RC_NiSwitchNode) + { + // show only first child by default + node->asSwitch()->setSingleChildOn(0); + + const Nif::NiSwitchNode* niSwitchNode = static_cast(nifNode); + if (niSwitchNode->name == Constants::NightDayLabel && !SceneUtil::hasUserDescription(rootNode, Constants::NightDayLabel)) + rootNode->getOrCreateUserDataContainer()->addDescription(Constants::NightDayLabel); + } + return node; } @@ -701,6 +694,10 @@ namespace NifOsg { handleVisController(static_cast(ctrl.getPtr()), transformNode, animflags); } + else if (ctrl->recType == Nif::RC_NiRollController) + { + handleRollController(static_cast(ctrl.getPtr()), transformNode, animflags); + } else Log(Debug::Info) << "Unhandled controller " << ctrl->recName << " on node " << nifNode->recIndex << " in " << mFilename; } @@ -713,6 +710,13 @@ namespace NifOsg node->addUpdateCallback(callback); } + void handleRollController(const Nif::NiRollController* rollctrl, osg::Node* node, int animflags) + { + osg::ref_ptr callback(new RollController(rollctrl->data.getPtr())); + setupController(rollctrl, callback, animflags); + node->addUpdateCallback(callback); + } + void handleMaterialControllers(const Nif::Property *materialProperty, SceneUtil::CompositeStateSetUpdater* composite, int animflags) { for (Nif::ControllerPtr ctrl = materialProperty->controller; !ctrl.empty(); ctrl = ctrl->next) @@ -1288,6 +1292,7 @@ namespace NifOsg boundTextures.clear(); } + // If this loop is changed such that the base texture isn't guaranteed to end up in texture unit 0, the shadow casting shader will need to be updated accordingly. for (int i=0; itextures[i].inUse) diff --git a/components/nifosg/particle.cpp b/components/nifosg/particle.cpp index c1f6a2819..eeb6777ce 100644 --- a/components/nifosg/particle.cpp +++ b/components/nifosg/particle.cpp @@ -8,7 +8,6 @@ #include #include #include -#include #include #include "userdata.hpp" diff --git a/components/process/processinvoker.cpp b/components/process/processinvoker.cpp index cc842fd61..78cf70038 100644 --- a/components/process/processinvoker.cpp +++ b/components/process/processinvoker.cpp @@ -3,8 +3,6 @@ #include #include #include -#include -#include #include #include #include diff --git a/components/resource/bulletshape.cpp b/components/resource/bulletshape.cpp index e3f6b22b4..7dd0964e8 100644 --- a/components/resource/bulletshape.cpp +++ b/components/resource/bulletshape.cpp @@ -4,7 +4,6 @@ #include #include -#include #include #include diff --git a/components/resource/objectcache.cpp b/components/resource/objectcache.cpp deleted file mode 100644 index e8c082f91..000000000 --- a/components/resource/objectcache.cpp +++ /dev/null @@ -1,164 +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 the 1 the object has an external reference. - if (itr->second.first->referenceCount()>1) - { - // so update it time stamp. - 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 328a10cd1..61a40ee4b 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -218,7 +218,6 @@ namespace Resource , mShaderManager(new Shader::ShaderManager) , mForceShaders(false) , mClampLighting(true) - , mForcePerPixelLighting(false) , mAutoUseNormalMaps(false) , mAutoUseSpecularMaps(false) , mInstanceCache(new MultiObjectCache) @@ -260,16 +259,6 @@ namespace Resource return mClampLighting; } - void SceneManager::setForcePerPixelLighting(bool force) - { - mForcePerPixelLighting = force; - } - - bool SceneManager::getForcePerPixelLighting() const - { - return mForcePerPixelLighting; - } - void SceneManager::setAutoUseNormalMaps(bool use) { mAutoUseNormalMaps = use; @@ -542,6 +531,8 @@ namespace Resource if (mIncrementalCompileOperation) mIncrementalCompileOperation->add(loaded); + else + loaded->getBound(); mCache->addEntryToObjectCache(normalized, loaded); return loaded; @@ -554,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; } @@ -729,6 +726,7 @@ namespace Resource void SceneManager::reportStats(unsigned int frameNumber, osg::Stats *stats) const { + if (mIncrementalCompileOperation) { OpenThreads::ScopedLock lock(*mIncrementalCompileOperation->getToCompiledMutex()); stats->setAttribute(frameNumber, "Compiling", mIncrementalCompileOperation->getToCompile().size()); @@ -748,8 +746,6 @@ namespace Resource { Shader::ShaderVisitor* shaderVisitor = new Shader::ShaderVisitor(*mShaderManager.get(), *mImageManager, "objects_vertex.glsl", "objects_fragment.glsl"); shaderVisitor->setForceShaders(mForceShaders); - shaderVisitor->setClampLighting(mClampLighting); - shaderVisitor->setForcePerPixelLighting(mForcePerPixelLighting); shaderVisitor->setAutoUseNormalMaps(mAutoUseNormalMaps); shaderVisitor->setNormalMapPattern(mNormalMapPattern); shaderVisitor->setNormalHeightMapPattern(mNormalHeightMapPattern); diff --git a/components/resource/scenemanager.hpp b/components/resource/scenemanager.hpp index 4f1523ece..1c1c60a58 100644 --- a/components/resource/scenemanager.hpp +++ b/components/resource/scenemanager.hpp @@ -56,14 +56,9 @@ namespace Resource void setForceShaders(bool force); bool getForceShaders() const; - /// @see ShaderVisitor::setClampLighting void setClampLighting(bool clamp); bool getClampLighting() const; - /// @see ShaderVisitor::setForcePerPixelLighting - void setForcePerPixelLighting(bool force); - bool getForcePerPixelLighting() const; - /// @see ShaderVisitor::setAutoUseNormalMaps void setAutoUseNormalMaps(bool use); @@ -155,7 +150,6 @@ namespace Resource std::unique_ptr mShaderManager; bool mForceShaders; bool mClampLighting; - bool mForcePerPixelLighting; bool mAutoUseNormalMaps; std::string mNormalMapPattern; std::string mNormalHeightMapPattern; 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"}; + 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", + }); - int numLines = sizeof(statNames) / sizeof(statNames[0]); + 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/agentpath.cpp b/components/sceneutil/agentpath.cpp index aaee4dd1e..abe332f75 100644 --- a/components/sceneutil/agentpath.cpp +++ b/components/sceneutil/agentpath.cpp @@ -2,7 +2,6 @@ #include "detourdebugdraw.hpp" #include -#include #include diff --git a/components/sceneutil/attach.cpp b/components/sceneutil/attach.cpp index f143d19a8..5126a3776 100644 --- a/components/sceneutil/attach.cpp +++ b/components/sceneutil/attach.cpp @@ -1,7 +1,6 @@ #include "attach.hpp" #include -#include #include #include diff --git a/components/sceneutil/clone.cpp b/components/sceneutil/clone.cpp index 99dd7bad3..d1cca5b31 100644 --- a/components/sceneutil/clone.cpp +++ b/components/sceneutil/clone.cpp @@ -1,12 +1,10 @@ #include "clone.hpp" #include -#include #include #include #include -#include #include 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/detourdebugdraw.hpp b/components/sceneutil/detourdebugdraw.hpp index bb170e7ba..9b6a28ace 100644 --- a/components/sceneutil/detourdebugdraw.hpp +++ b/components/sceneutil/detourdebugdraw.hpp @@ -1,3 +1,6 @@ +#ifndef OPENMW_COMPONENTS_SCENEUTIL_DETOURDEBUGDRAW_H +#define OPENMW_COMPONENTS_SCENEUTIL_DETOURDEBUGDRAW_H + #include #include @@ -48,3 +51,5 @@ namespace SceneUtil void addColor(osg::Vec4f&& value); }; } + +#endif \ No newline at end of file diff --git a/components/sceneutil/lightmanager.cpp b/components/sceneutil/lightmanager.cpp index e102653b9..c657e66fd 100644 --- a/components/sceneutil/lightmanager.cpp +++ b/components/sceneutil/lightmanager.cpp @@ -234,6 +234,7 @@ namespace SceneUtil { osg::ref_ptr stateset = new osg::StateSet; std::vector > lights; + lights.reserve(lightList.size()); for (unsigned int i=0; imLightSource->getLight(frameNum)); @@ -244,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 @@ -399,7 +401,7 @@ namespace SceneUtil return false; } - if (!(cv->getCurrentCamera()->getCullMask() & mLightManager->getLightingMask())) + if (!(cv->getTraversalMask() & mLightManager->getLightingMask())) return false; // Possible optimizations: diff --git a/components/sceneutil/mwshadowtechnique.cpp b/components/sceneutil/mwshadowtechnique.cpp new file mode 100644 index 000000000..92bf44ec1 --- /dev/null +++ b/components/sceneutil/mwshadowtechnique.cpp @@ -0,0 +1,3189 @@ +/* This file is based on OpenSceneGraph's src/osgShadow/ViewDependentShadowMap.cpp. + * Where applicable, any changes made are covered by OpenMW's GPL 3 license, not the OSGPL. + * The original copyright notice is listed below. + */ + +/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2011 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 "mwshadowtechnique.hpp" + +#include +#include +#include +#include + +#include + +namespace { + +using namespace osgShadow; +using namespace SceneUtil; + +#define dbl_max std::numeric_limits::max() + +////////////////////////////////////////////////////////////////// +// fragment shader +// +#if 0 +static const char fragmentShaderSource_withBaseTexture[] = + "uniform sampler2D baseTexture; \n" + "uniform sampler2DShadow shadowTexture; \n" + " \n" + "void main(void) \n" + "{ \n" + " vec4 colorAmbientEmissive = gl_FrontLightModelProduct.sceneColor; \n" + " vec4 color = texture2D( baseTexture, gl_TexCoord[0].xy ); \n" + " color *= mix( colorAmbientEmissive, gl_Color, shadow2DProj( shadowTexture, gl_TexCoord[1] ).r ); \n" + " gl_FragColor = color; \n" + "} \n"; +#else +static const char fragmentShaderSource_withBaseTexture[] = + "uniform sampler2D baseTexture; \n" + "uniform int baseTextureUnit; \n" + "uniform sampler2DShadow shadowTexture0; \n" + "uniform int shadowTextureUnit0; \n" + " \n" + "void main(void) \n" + "{ \n" + " vec4 colorAmbientEmissive = gl_FrontLightModelProduct.sceneColor; \n" + " vec4 color = texture2D( baseTexture, gl_TexCoord[baseTextureUnit].xy ); \n" + " color *= mix( colorAmbientEmissive, gl_Color, shadow2DProj( shadowTexture0, gl_TexCoord[shadowTextureUnit0] ).r ); \n" + " gl_FragColor = color; \n" + "} \n"; + +static const char fragmentShaderSource_withBaseTexture_twoShadowMaps[] = + "uniform sampler2D baseTexture; \n" + "uniform int baseTextureUnit; \n" + "uniform sampler2DShadow shadowTexture0; \n" + "uniform int shadowTextureUnit0; \n" + "uniform sampler2DShadow shadowTexture1; \n" + "uniform int shadowTextureUnit1; \n" + " \n" + "void main(void) \n" + "{ \n" + " vec4 colorAmbientEmissive = gl_FrontLightModelProduct.sceneColor; \n" + " vec4 color = texture2D( baseTexture, gl_TexCoord[baseTextureUnit].xy ); \n" + " float shadow0 = shadow2DProj( shadowTexture0, gl_TexCoord[shadowTextureUnit0] ).r; \n" + " float shadow1 = shadow2DProj( shadowTexture1, gl_TexCoord[shadowTextureUnit1] ).r; \n" + " color *= mix( colorAmbientEmissive, gl_Color, shadow0*shadow1 ); \n" + " gl_FragColor = color; \n" + "} \n"; +#endif + +std::string debugVertexShaderSource = "void main(void){gl_Position = gl_Vertex; gl_TexCoord[0]=gl_MultiTexCoord0;}"; +std::string debugFragmentShaderSource = + "uniform sampler2D texture; \n" + " \n" + "void main(void) \n" + "{ \n" +#if 1 + " float f = texture2D(texture, gl_TexCoord[0].xy).r; \n" + " \n" + " f = 256.0 * f; \n" + " float fC = floor( f ) / 256.0; \n" + " \n" + " f = 256.0 * fract( f ); \n" + " float fS = floor( f ) / 256.0; \n" + " \n" + " f = 256.0 * fract( f ); \n" + " float fH = floor( f ) / 256.0; \n" + " \n" + " fS *= 0.5; \n" + " fH = ( fH * 0.34 + 0.66 ) * ( 1.0 - fS ); \n" + " \n" + " vec3 rgb = vec3( ( fC > 0.5 ? ( 1.0 - fC ) : fC ), \n" + " abs( fC - 0.333333 ), \n" + " abs( fC - 0.666667 ) ); \n" + " \n" + " rgb = min( vec3( 1.0, 1.0, 1.0 ), 3.0 * rgb ); \n" + " \n" + " float fMax = max( max( rgb.r, rgb.g ), rgb.b ); \n" + " fMax = 1.0 / fMax; \n" + " \n" + " vec3 color = fMax * rgb; \n" + " \n" + " gl_FragColor = vec4( fS + fH * color, 1 ); \n" +#else + " gl_FragColor = texture2D(texture, gl_TexCoord[0].xy); \n" +#endif + "} \n"; + +std::string debugFrustumVertexShaderSource = "varying float depth; uniform mat4 transform; void main(void){gl_Position = transform * gl_Vertex; depth = gl_Position.z / gl_Position.w;}"; +std::string debugFrustumFragmentShaderSource = + "varying float depth; \n" + " \n" + "void main(void) \n" + "{ \n" +#if 1 + " float f = depth; \n" + " \n" + " f = 256.0 * f; \n" + " float fC = floor( f ) / 256.0; \n" + " \n" + " f = 256.0 * fract( f ); \n" + " float fS = floor( f ) / 256.0; \n" + " \n" + " f = 256.0 * fract( f ); \n" + " float fH = floor( f ) / 256.0; \n" + " \n" + " fS *= 0.5; \n" + " fH = ( fH * 0.34 + 0.66 ) * ( 1.0 - fS ); \n" + " \n" + " vec3 rgb = vec3( ( fC > 0.5 ? ( 1.0 - fC ) : fC ), \n" + " abs( fC - 0.333333 ), \n" + " abs( fC - 0.666667 ) ); \n" + " \n" + " rgb = min( vec3( 1.0, 1.0, 1.0 ), 3.0 * rgb ); \n" + " \n" + " float fMax = max( max( rgb.r, rgb.g ), rgb.b ); \n" + " fMax = 1.0 / fMax; \n" + " \n" + " vec3 color = fMax * rgb; \n" + " \n" + " gl_FragColor = vec4( fS + fH * color, 1 ); \n" +#else + " gl_FragColor = vec4(0.0, 0.0, 1.0, 0.0); \n" +#endif + "} \n"; + + +template +class RenderLeafTraverser : public T +{ +public: + + RenderLeafTraverser() + { + } + + void traverse(const osgUtil::RenderStage* rs) + { + traverse(static_cast(rs)); + } + + void traverse(const osgUtil::RenderBin* renderBin) + { + const osgUtil::RenderBin::RenderBinList& rbl = renderBin->getRenderBinList(); + for(osgUtil::RenderBin::RenderBinList::const_iterator itr = rbl.begin(); + itr != rbl.end(); + ++itr) + { + traverse(itr->second.get()); + } + + const osgUtil::RenderBin::RenderLeafList& rll = renderBin->getRenderLeafList(); + for(osgUtil::RenderBin::RenderLeafList::const_iterator itr = rll.begin(); + itr != rll.end(); + ++itr) + { + handle(*itr); + } + + const osgUtil::RenderBin::StateGraphList& rgl = renderBin->getStateGraphList(); + for(osgUtil::RenderBin::StateGraphList::const_iterator itr = rgl.begin(); + itr != rgl.end(); + ++itr) + { + traverse(*itr); + } + + } + + void traverse(const osgUtil::StateGraph* stateGraph) + { + const osgUtil::StateGraph::ChildList& cl = stateGraph->_children; + for(osgUtil::StateGraph::ChildList::const_iterator itr = cl.begin(); + itr != cl.end(); + ++itr) + { + traverse(itr->second.get()); + } + + const osgUtil::StateGraph::LeafList& ll = stateGraph->_leaves; + for(osgUtil::StateGraph::LeafList::const_iterator itr = ll.begin(); + itr != ll.end(); + ++itr) + { + handle(itr->get()); + } + } + + inline void handle(const osgUtil::RenderLeaf* renderLeaf) + { + this->operator()(renderLeaf); + } +}; + +/////////////////////////////////////////////////////////////////////////////////////////////// +// +// VDSMCameraCullCallback +// +class VDSMCameraCullCallback : public osg::NodeCallback +{ + public: + + VDSMCameraCullCallback(MWShadowTechnique* vdsm, osg::Polytope& polytope); + + virtual void operator()(osg::Node*, osg::NodeVisitor* nv); + + osg::RefMatrix* getProjectionMatrix() { return _projectionMatrix.get(); } + osgUtil::RenderStage* getRenderStage() { return _renderStage.get(); } + + protected: + + MWShadowTechnique* _vdsm; + osg::ref_ptr _projectionMatrix; + osg::ref_ptr _renderStage; + osg::Polytope _polytope; +}; + +VDSMCameraCullCallback::VDSMCameraCullCallback(MWShadowTechnique* vdsm, osg::Polytope& polytope): + _vdsm(vdsm), + _polytope(polytope) +{ +} + +void VDSMCameraCullCallback::operator()(osg::Node* node, osg::NodeVisitor* nv) +{ + osgUtil::CullVisitor* cv = static_cast(nv); + osg::Camera* camera = node->asCamera(); + OSG_INFO<<"VDSMCameraCullCallback::operator()(osg::Node* "<getProjectionCullingStack().back(); + + cs.setFrustum(_polytope); + + cv->pushCullingSet(); + } +#endif + if (_vdsm->getShadowedScene()) + { + _vdsm->getShadowedScene()->osg::Group::traverse(*nv); + } +#if 1 + if (!_polytope.empty()) + { + OSG_INFO<<"Popping custom Polytope"<popCullingSet(); + } +#endif + + _renderStage = cv->getCurrentRenderBin()->getStage(); + + OSG_INFO<<"VDSM second : _renderStage = "<<_renderStage<getComputeNearFarMode() != osg::CullSettings::DO_NOT_COMPUTE_NEAR_FAR) + { + // make sure that the near plane is computed correctly. + cv->computeNearPlane(); + + osg::Matrixd projection = *(cv->getProjectionMatrix()); + + OSG_INFO<<"RTT Projection matrix "<setProjectionMatrix(projection); + } + + _projectionMatrix = cv->getProjectionMatrix(); +} + +} // namespace + +MWShadowTechnique::ComputeLightSpaceBounds::ComputeLightSpaceBounds(osg::Viewport* viewport, const osg::Matrixd& projectionMatrix, osg::Matrixd& viewMatrix) : + osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ACTIVE_CHILDREN) +{ + setCullingMode(osg::CullSettings::VIEW_FRUSTUM_CULLING); + + pushViewport(viewport); + pushProjectionMatrix(new osg::RefMatrix(projectionMatrix)); + pushModelViewMatrix(new osg::RefMatrix(viewMatrix), osg::Transform::ABSOLUTE_RF); + + setName("SceneUtil::MWShadowTechnique::ComputeLightSpaceBounds,AcceptedByComponentsTerrainQuadTreeWorld"); +} + +void MWShadowTechnique::ComputeLightSpaceBounds::apply(osg::Node& node) +{ + if (isCulled(node)) return; + + // push the culling mode. + pushCurrentMask(); + + traverse(node); + + // pop the culling mode. + popCurrentMask(); +} + +void MWShadowTechnique::ComputeLightSpaceBounds::apply(osg::Drawable& drawable) +{ + if (isCulled(drawable)) return; + + // push the culling mode. + pushCurrentMask(); + + updateBound(drawable.getBoundingBox()); + + // pop the culling mode. + popCurrentMask(); +} + +void MWShadowTechnique::ComputeLightSpaceBounds::apply(Terrain::QuadTreeWorld & quadTreeWorld) +{ + // For now, just expand the bounds fully as terrain will fill them up and possible ways to detect which terrain definitely won't cast shadows aren't implemented. + + update(osg::Vec3(-1.0, -1.0, 0.0)); + update(osg::Vec3(1.0, 1.0, 0.0)); +} + +void MWShadowTechnique::ComputeLightSpaceBounds::apply(osg::Billboard&) +{ + OSG_INFO << "Warning Billboards not yet supported" << std::endl; + return; +} + +void MWShadowTechnique::ComputeLightSpaceBounds::apply(osg::Projection&) +{ + // projection nodes won't affect a shadow map so their subgraphs should be ignored + return; +} + +void MWShadowTechnique::ComputeLightSpaceBounds::apply(osg::Transform& transform) +{ + if (isCulled(transform)) return; + + // push the culling mode. + pushCurrentMask(); + + // absolute transforms won't affect a shadow map so their subgraphs should be ignored. + if (transform.getReferenceFrame() == osg::Transform::RELATIVE_RF) + { + osg::ref_ptr matrix = new osg::RefMatrix(*getModelViewMatrix()); + transform.computeLocalToWorldMatrix(*matrix, this); + pushModelViewMatrix(matrix.get(), transform.getReferenceFrame()); + + traverse(transform); + + popModelViewMatrix(); + } + + // pop the culling mode. + popCurrentMask(); + +} + +void MWShadowTechnique::ComputeLightSpaceBounds::apply(osg::Camera&) +{ + // camera nodes won't affect a shadow map so their subgraphs should be ignored + return; +} + +void MWShadowTechnique::ComputeLightSpaceBounds::updateBound(const osg::BoundingBox& bb) +{ + if (!bb.valid()) return; + + const osg::Matrix& matrix = *getModelViewMatrix() * *getProjectionMatrix(); + + update(bb.corner(0) * matrix); + update(bb.corner(1) * matrix); + update(bb.corner(2) * matrix); + update(bb.corner(3) * matrix); + update(bb.corner(4) * matrix); + update(bb.corner(5) * matrix); + update(bb.corner(6) * matrix); + update(bb.corner(7) * matrix); +} + +void MWShadowTechnique::ComputeLightSpaceBounds::update(const osg::Vec3& v) +{ + if (v.z()<-1.0f) + { + //OSG_NOTICE<<"discarding("<1.0f) x = 1.0f; + float y = v.y(); + if (y<-1.0f) y = -1.0f; + if (y>1.0f) y = 1.0f; + _bb.expandBy(osg::Vec3(x, y, v.z())); +} + +/////////////////////////////////////////////////////////////////////////////////////////////// +// +// LightData +// +MWShadowTechnique::LightData::LightData(MWShadowTechnique::ViewDependentData* vdd): + _viewDependentData(vdd), + directionalLight(false) +{ +} + +void MWShadowTechnique::LightData::setLightData(osg::RefMatrix* lm, const osg::Light* l, const osg::Matrixd& modelViewMatrix) +{ + lightMatrix = lm; + light = l; + + lightPos = light->getPosition(); + directionalLight = (light->getPosition().w()== 0.0); + if (directionalLight) + { + lightPos3.set(0.0, 0.0, 0.0); // directional light has no destinct position + lightDir.set(-lightPos.x(), -lightPos.y(), -lightPos.z()); + lightDir.normalize(); + OSG_INFO<<" Directional light, lightPos="<setReferenceFrame(osg::Camera::ABSOLUTE_RF_INHERIT_VIEWPOINT); + + //_camera->setClearColor(osg::Vec4(1.0f,1.0f,1.0f,1.0f)); + _camera->setClearColor(osg::Vec4(0.0f,0.0f,0.0f,0.0f)); + + //_camera->setComputeNearFarMode(osg::Camera::COMPUTE_NEAR_FAR_USING_BOUNDING_VOLUMES); + //_camera->setComputeNearFarMode(osg::Camera::COMPUTE_NEAR_FAR_USING_PRIMITIVES); + + // Now we are using Depth Clamping, we want to not cull things on the wrong side of the near plane. + // When the near and far planes are computed, OSG always culls anything on the wrong side of the near plane, even if it's told not to. + // Even if that weren't an issue, the near plane can't go past any shadow receivers or the depth-clamped fragments which ended up on the near plane can't cast shadows on those receivers. + // Unfortunately, this change will make shadows have less depth precision when there are no casters outside the view frustum. + // TODO: Find a better solution. E.g. detect when there are no casters outside the view frustum, write a new cull visitor that does all the wacky things we'd need it to. + _camera->setComputeNearFarMode(osg::Camera::DO_NOT_COMPUTE_NEAR_FAR); + + // switch off small feature culling as this can cull out geometry that will still be large enough once perspective correction takes effect. + _camera->setCullingMode(_camera->getCullingMode() & ~osg::CullSettings::SMALL_FEATURE_CULLING); + + // set viewport + _camera->setViewport(0,0,textureSize.x(),textureSize.y()); + + + if (debug) + { + // clear just the depth buffer + _camera->setClearMask(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT); + + // render after the main camera + _camera->setRenderOrder(osg::Camera::POST_RENDER); + + // attach the texture and use it as the color buffer. + //_camera->attach(osg::Camera::DEPTH_BUFFER, _texture.get()); + _camera->attach(osg::Camera::COLOR_BUFFER, _texture.get()); + } + else + { + // clear the depth and colour bufferson each clear. + _camera->setClearMask(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT); + + // set the camera to render before the main camera. + _camera->setRenderOrder(osg::Camera::PRE_RENDER); + + // tell the camera to use OpenGL frame buffer object where supported. + _camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT); + + // attach the texture and use it as the color buffer. + _camera->attach(osg::Camera::DEPTH_BUFFER, _texture.get()); + //_camera->attach(osg::Camera::COLOR_BUFFER, _texture.get()); + } +} + +void MWShadowTechnique::ShadowData::releaseGLObjects(osg::State* state) const +{ + OSG_INFO<<"MWShadowTechnique::ShadowData::releaseGLObjects"<releaseGLObjects(state); + _camera->releaseGLObjects(state); +} + +/////////////////////////////////////////////////////////////////////////////////////////////// +// +// Frustum +// +MWShadowTechnique::Frustum::Frustum(osgUtil::CullVisitor* cv, double minZNear, double maxZFar): + corners(8), + faces(6), + edges(12) +{ + projectionMatrix = *(cv->getProjectionMatrix()); + modelViewMatrix = *(cv->getModelViewMatrix()); + + OSG_INFO<<"Projection matrix "<getComputeNearFarMode()!=osg::CullSettings::DO_NOT_COMPUTE_NEAR_FAR) + { + osg::Matrix::value_type zNear = osg::maximum(cv->getCalculatedNearPlane(),minZNear); + osg::Matrix::value_type zFar = osg::minimum(cv->getCalculatedFarPlane(),maxZFar); + + cv->clampProjectionMatrix(projectionMatrix, zNear, zFar); + + OSG_INFO<<"zNear = "<releaseGLObjects(state); + } +} + +/////////////////////////////////////////////////////////////////////////////////////////////// +// +// MWShadowTechnique +// +MWShadowTechnique::MWShadowTechnique(): + ShadowTechnique(), + _enableShadows(false), + _debugHud(nullptr) +{ + _shadowRecievingPlaceholderStateSet = new osg::StateSet; +} + +MWShadowTechnique::MWShadowTechnique(const MWShadowTechnique& vdsm, const osg::CopyOp& copyop): + ShadowTechnique(vdsm,copyop) +{ + _shadowRecievingPlaceholderStateSet = new osg::StateSet; + _enableShadows = vdsm._enableShadows; +} + +MWShadowTechnique::~MWShadowTechnique() +{ +} + + +void MWShadowTechnique::init() +{ + if (!_shadowedScene) return; + + OSG_INFO<<"MWShadowTechnique::init()"<getShadowSettings()->getNumShadowMapsPerLight()); +} + +void SceneUtil::MWShadowTechnique::disableDebugHUD() +{ + _debugHud = nullptr; +} + +void SceneUtil::MWShadowTechnique::setSplitPointUniformLogarithmicRatio(double ratio) +{ + _splitPointUniformLogRatio = ratio; +} + +void SceneUtil::MWShadowTechnique::setSplitPointDeltaBias(double bias) +{ + _splitPointDeltaBias = bias; +} + +void SceneUtil::MWShadowTechnique::setPolygonOffset(float factor, float units) +{ + _polygonOffsetFactor = factor; + _polygonOffsetUnits = units; + + if (_polygonOffset) + { + _polygonOffset->setFactor(factor); + _polygonOffset->setUnits(units); + } +} + +void SceneUtil::MWShadowTechnique::enableFrontFaceCulling() +{ + _useFrontFaceCulling = true; + + if (_shadowCastingStateSet) + _shadowCastingStateSet->setAttribute(new osg::CullFace(osg::CullFace::FRONT), osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); +} + +void SceneUtil::MWShadowTechnique::disableFrontFaceCulling() +{ + _useFrontFaceCulling = false; + + if (_shadowCastingStateSet) + _shadowCastingStateSet->setMode(GL_CULL_FACE, osg::StateAttribute::OFF | osg::StateAttribute::OVERRIDE); +} + +void SceneUtil::MWShadowTechnique::setupCastingShader(Shader::ShaderManager & shaderManager) +{ + // This can't be part of the constructor as OSG mandates that there be a trivial constructor available + + _castingProgram = new osg::Program(); + + _castingProgram->addShader(shaderManager.getShader("shadowcasting_vertex.glsl", Shader::ShaderManager::DefineMap(), osg::Shader::VERTEX)); + _castingProgram->addShader(shaderManager.getShader("shadowcasting_fragment.glsl", Shader::ShaderManager::DefineMap(), osg::Shader::FRAGMENT)); +} + +MWShadowTechnique::ViewDependentData* MWShadowTechnique::createViewDependentData(osgUtil::CullVisitor* /*cv*/) +{ + return new ViewDependentData(this); +} + +MWShadowTechnique::ViewDependentData* MWShadowTechnique::getViewDependentData(osgUtil::CullVisitor* cv) +{ + OpenThreads::ScopedLock lock(_viewDependentDataMapMutex); + ViewDependentDataMap::iterator itr = _viewDependentDataMap.find(cv); + if (itr!=_viewDependentDataMap.end()) return itr->second.get(); + + osg::ref_ptr vdd = createViewDependentData(cv); + _viewDependentDataMap[cv] = vdd; + return vdd.release(); +} + +void MWShadowTechnique::update(osg::NodeVisitor& nv) +{ + OSG_INFO<<"MWShadowTechnique::update(osg::NodeVisitor& "<<&nv<<")"<osg::Group::traverse(nv); +} + +void MWShadowTechnique::cull(osgUtil::CullVisitor& cv) +{ + if (!_enableShadows) + { + _shadowedScene->osg::Group::traverse(cv); + return; + } + + OSG_INFO<osg::Group::traverse(cv); + return; + } + + ViewDependentData* vdd = getViewDependentData(&cv); + + if (!vdd) + { + OSG_INFO<<"Warning, now ViewDependentData created, unable to create shadows."<osg::Group::traverse(cv); + return; + } + + ShadowSettings* settings = getShadowedScene()->getShadowSettings(); + + OSG_INFO<<"cv->getProjectionMatrix()="<<*cv.getProjectionMatrix()<getMaximumShadowMapDistance(),maxZFar); + if (minZNear>maxZFar) minZNear = maxZFar*settings->getMinimumShadowMapNearFarRatio(); + + //OSG_NOTICE<<"maxZFar "< vertexArray = new osg::Vec3Array(); + for (osg::Vec3d &vertex : frustum.corners) + vertexArray->push_back((osg::Vec3)vertex); + _debugHud->setFrustumVertices(vertexArray, cv.getTraversalNumber()); + } + + double reducedNear, reducedFar; + if (cv.getComputeNearFarMode() != osg::CullSettings::DO_NOT_COMPUTE_NEAR_FAR) + { + reducedNear = osg::maximum(cv.getCalculatedNearPlane(), minZNear); + reducedFar = osg::minimum(cv.getCalculatedFarPlane(), maxZFar); + } + else + { + reducedNear = minZNear; + reducedFar = maxZFar; + } + + // return compute near far mode back to it's original settings + cv.setComputeNearFarMode(cachedNearFarMode); + + OSG_INFO<<"frustum.eye="<1 &&*/ _shadowedScene->getCastsShadowTraversalMask()!=0xffffffff) + { + // osg::ElapsedTime timer; + + osg::ref_ptr viewport = new osg::Viewport(0,0,2048,2048); + ComputeLightSpaceBounds clsb(viewport.get(), projectionMatrix, viewMatrix); + clsb.setTraversalMask(_shadowedScene->getCastsShadowTraversalMask()); + + osg::Matrixd invertModelView; + invertModelView.invert(viewMatrix); + osg::Polytope local_polytope(polytope); + local_polytope.transformProvidingInverse(invertModelView); + + osg::CullingSet& cs = clsb.getProjectionCullingStack().back(); + cs.setFrustum(local_polytope); + clsb.pushCullingSet(); + + _shadowedScene->accept(clsb); + + // OSG_NOTICE<<"Extents of LightSpace "<(maxZ, -corner.z()); + minZ = osg::minimum(minZ, -corner.z()); + } + reducedNear = osg::maximum(reducedNear, minZ); + reducedFar = osg::minimum(reducedFar, maxZ); + + // OSG_NOTICE<<" xMid="< camera = sd->_camera; + + camera->setProjectionMatrix(projectionMatrix); + camera->setViewMatrix(viewMatrix); + + if (settings->getDebugDraw()) + { + camera->getViewport()->x() = pos_x; + pos_x += static_cast(camera->getViewport()->width()) + 40; + } + + // transform polytope in model coords into light spaces eye coords. + osg::Matrixd invertModelView; + invertModelView.invert(camera->getViewMatrix()); + + osg::Polytope local_polytope(polytope); + local_polytope.transformProvidingInverse(invertModelView); + + double cascaseNear = reducedNear; + double cascadeFar = reducedFar; + if (numShadowMapsPerLight>1) + { + // compute the start and end range in non-dimensional coords +#if 0 + double r_start = (sm_i==0) ? -1.0 : (double(sm_i)/double(numShadowMapsPerLight)*2.0-1.0); + double r_end = (sm_i+1==numShadowMapsPerLight) ? 1.0 : (double(sm_i+1)/double(numShadowMapsPerLight)*2.0-1.0); +#elif 0 + + // hardwired for 2 splits + double r_start = (sm_i==0) ? -1.0 : splitPoint; + double r_end = (sm_i+1==numShadowMapsPerLight) ? 1.0 : splitPoint; +#else + double r_start, r_end; + + // split system based on the original Parallel Split Shadow Maps paper. + double n = reducedNear; + double f = reducedFar; + double i = double(sm_i); + double m = double(numShadowMapsPerLight); + if (sm_i == 0) + r_start = -1.0; + else + { + // compute the split point in main camera view + double ciLog = n * pow(f / n, i / m); + double ciUniform = n + (f - n) * i / m; + double ci = _splitPointUniformLogRatio * ciLog + (1.0 - _splitPointUniformLogRatio) * ciUniform + _splitPointDeltaBias; + cascaseNear = ci; + + // work out where this is in light space + osg::Vec3d worldSpacePos = frustum.eye + frustum.frustumCenterLine * ci; + osg::Vec3d lightSpacePos = worldSpacePos * viewMatrix * projectionMatrix; + r_start = lightSpacePos.y(); + } + + if (sm_i + 1 == numShadowMapsPerLight) + r_end = 1.0; + else + { + // compute the split point in main camera view + double ciLog = n * pow(f / n, (i + 1) / m); + double ciUniform = n + (f - n) * (i + 1) / m; + double ci = _splitPointUniformLogRatio * ciLog + (1.0 - _splitPointUniformLogRatio) * ciUniform + _splitPointDeltaBias; + cascadeFar = ci; + + // work out where this is in light space + osg::Vec3d worldSpacePos = frustum.eye + frustum.frustumCenterLine * ci; + osg::Vec3d lightSpacePos = worldSpacePos * viewMatrix * projectionMatrix; + r_end = lightSpacePos.y(); + } +#endif + // for all by the last shadowmap shift the r_end so that it overlaps slightly with the next shadowmap + // to prevent a seam showing through between the shadowmaps + if (sm_i+1getMultipleShadowMapHint() == ShadowSettings::PARALLEL_SPLIT && sm_i>0) + { + // not the first shadowmap so insert a polytope to clip the scene from before r_start + + // plane in clip space coords + osg::Plane plane(0.0,1.0,0.0,-r_start); + + // transform into eye coords + plane.transformProvidingInverse(projectionMatrix); + local_polytope.getPlaneList().push_back(plane); + + //OSG_NOTICE<<"Adding r_start plane "<getMultipleShadowMapHint() == ShadowSettings::PARALLEL_SPLIT && sm_i+1getMultipleShadowMapHint() == ShadowSettings::PARALLEL_SPLIT) + { + // OSG_NOTICE<<"Need to adjust RTT camera projection and view matrix here, r_start="< validRegionUniform; + + OpenThreads::ScopedLock lock(_accessUniformsAndProgramMutex); + + for (auto uniform : _uniforms) + { + if (uniform->getName() == validRegionUniformName) + validRegionUniform = uniform; + } + + if (!validRegionUniform) + { + validRegionUniform = new osg::Uniform(osg::Uniform::FLOAT_MAT4, validRegionUniformName); + _uniforms.push_back(validRegionUniform); + } + + validRegionUniform->set(validRegionMatrix); + } + + if (settings->getMultipleShadowMapHint() == ShadowSettings::CASCADED) + adjustPerspectiveShadowMapCameraSettings(vdsmCallback->getRenderStage(), frustum, pl, camera.get(), cascaseNear, cascadeFar); + else + adjustPerspectiveShadowMapCameraSettings(vdsmCallback->getRenderStage(), frustum, pl, camera.get(), reducedNear, reducedFar); + if (vdsmCallback->getProjectionMatrix()) + { + vdsmCallback->getProjectionMatrix()->set(camera->getProjectionMatrix()); + } + } + + // 4.4 compute main scene graph TexGen + uniform settings + setup state + // + assignTexGenSettings(&cv, camera.get(), textureUnit, sd->_texgen.get()); + + // mark the light as one that has active shadows and requires shaders + pl.textureUnits.push_back(textureUnit); + + // pass on shadow data to ShadowDataList + sd->_textureUnit = textureUnit; + + if (textureUnit >= 8) + { + OSG_NOTICE<<"Shadow texture unit is invalid for texgen, will not be used."<draw(sd->_texture, sm_i, camera->getViewMatrix() * camera->getProjectionMatrix(), cv); + } + } + + if (numValidShadows>0) + { + decoratorStateGraph->setStateSet(selectStateSetForRenderingShadow(*vdd)); + } + + // OSG_NOTICE<<"End of shadow setup Projection matrix "<<*cv.getProjectionMatrix()<getLightDataList(); + + LightDataList previous_ldl; + previous_ldl.swap(pll); + + //MR testing giving a specific light + osgUtil::RenderStage * rs = cv->getCurrentRenderBin()->getStage(); + + OSG_INFO<<"selectActiveLights osgUtil::RenderStage="<getModelViewMatrix()); + + osgUtil::PositionalStateContainer::AttrMatrixList& aml = + rs->getPositionalStateContainer()->getAttrMatrixList(); + + + const ShadowSettings* settings = getShadowedScene()->getShadowSettings(); + + for(osgUtil::PositionalStateContainer::AttrMatrixList::reverse_iterator itr = aml.rbegin(); + itr != aml.rend(); + ++itr) + { + const osg::Light* light = dynamic_cast(itr->first.get()); + if (light && light->getLightNum() >= 0) + { + // is LightNum matched to that defined in settings + if (settings && settings->getLightNum()>=0 && light->getLightNum()!=settings->getLightNum()) continue; + + LightDataList::iterator pll_itr = pll.begin(); + for(; pll_itr != pll.end(); ++pll_itr) + { + if ((*pll_itr)->light->getLightNum()==light->getLightNum()) break; + } + + if (pll_itr==pll.end()) + { + OSG_INFO<<"Light num "<getLightNum()<setLightData(itr->second.get(), light, modelViewMatrix); + pll.push_back(ld); + } + else + { + OSG_INFO<<"Light num "<getLightNum()<<" already used, ignore light"< lock(_accessUniformsAndProgramMutex); + + _shadowCastingStateSet = new osg::StateSet; + + ShadowSettings* settings = getShadowedScene()->getShadowSettings(); + + if (!settings->getDebugDraw()) + { + // note soft (attribute only no mode override) setting. When this works ? + // 1. for objects prepared for backface culling + // because they usually also set CullFace and CullMode on in their state + // For them we override CullFace but CullMode remains set by them + // 2. For one faced, trees, and similar objects which cannot use + // backface nor front face so they usually use CullMode off set here. + // In this case we will draw them in their entirety. + + if (_useFrontFaceCulling) + { + _shadowCastingStateSet->setAttribute(new osg::CullFace(osg::CullFace::FRONT), osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); + + // make sure GL_CULL_FACE is off by default + // we assume that if object has cull face attribute set to back + // it will also set cull face mode ON so no need for override + _shadowCastingStateSet->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); + } + else + _shadowCastingStateSet->setMode(GL_CULL_FACE, osg::StateAttribute::OFF | osg::StateAttribute::OVERRIDE); + } + + _polygonOffset = new osg::PolygonOffset(_polygonOffsetFactor, _polygonOffsetUnits); + _shadowCastingStateSet->setAttribute(_polygonOffset.get(), osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); + _shadowCastingStateSet->setMode(GL_POLYGON_OFFSET_FILL, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); + + + _uniforms.clear(); + osg::ref_ptr baseTextureSampler = new osg::Uniform("baseTexture",(int)_baseTextureUnit); + _uniforms.push_back(baseTextureSampler.get()); + + osg::ref_ptr baseTextureUnit = new osg::Uniform("baseTextureUnit",(int)_baseTextureUnit); + _uniforms.push_back(baseTextureUnit.get()); + + for(unsigned int sm_i=0; sm_igetNumShadowMapsPerLight(); ++sm_i) + { + { + std::stringstream sstr; + sstr<<"shadowTexture"< shadowTextureSampler = new osg::Uniform(sstr.str().c_str(),(int)(settings->getBaseShadowTextureUnit()+sm_i)); + _uniforms.push_back(shadowTextureSampler.get()); + } + + { + std::stringstream sstr; + sstr<<"shadowTextureUnit"< shadowTextureUnit = new osg::Uniform(sstr.str().c_str(),(int)(settings->getBaseShadowTextureUnit()+sm_i)); + _uniforms.push_back(shadowTextureUnit.get()); + } + } + + switch(settings->getShaderHint()) + { + case(ShadowSettings::NO_SHADERS): + { + OSG_INFO<<"No shaders provided by, user must supply own shaders"< fragment_shader = new osg::Shader(osg::Shader::FRAGMENT, fragmentShaderSource_noBaseTexture); + if (settings->getNumShadowMapsPerLight()==2) + { + _program->addShader(new osg::Shader(osg::Shader::FRAGMENT, fragmentShaderSource_withBaseTexture_twoShadowMaps)); + } + else + { + _program->addShader(new osg::Shader(osg::Shader::FRAGMENT, fragmentShaderSource_withBaseTexture)); + } + + break; + } + } + + { + osg::ref_ptr image = new osg::Image; + image->allocateImage( 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE ); + *(osg::Vec4ub*)image->data() = osg::Vec4ub( 0xFF, 0xFF, 0xFF, 0xFF ); + + _fallbackBaseTexture = new osg::Texture2D(image.get()); + _fallbackBaseTexture->setWrap(osg::Texture2D::WRAP_S,osg::Texture2D::REPEAT); + _fallbackBaseTexture->setWrap(osg::Texture2D::WRAP_T,osg::Texture2D::REPEAT); + _fallbackBaseTexture->setFilter(osg::Texture2D::MIN_FILTER,osg::Texture2D::NEAREST); + _fallbackBaseTexture->setFilter(osg::Texture2D::MAG_FILTER,osg::Texture2D::NEAREST); + + _fallbackShadowMapTexture = new osg::Texture2D(image.get()); + _fallbackShadowMapTexture->setWrap(osg::Texture2D::WRAP_S,osg::Texture2D::REPEAT); + _fallbackShadowMapTexture->setWrap(osg::Texture2D::WRAP_T,osg::Texture2D::REPEAT); + _fallbackShadowMapTexture->setFilter(osg::Texture2D::MIN_FILTER,osg::Texture2D::NEAREST); + _fallbackShadowMapTexture->setFilter(osg::Texture2D::MAG_FILTER,osg::Texture2D::NEAREST); + + } + + if (!_castingProgram) + OSG_NOTICE << "Shadow casting shader has not been set up. Remember to call setupCastingShader(Shader::ShaderManager &)" << std::endl; + + _shadowCastingStateSet->setAttributeAndModes(_castingProgram, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); + // The casting program uses a sampler, so to avoid undefined behaviour, we must bind a dummy texture in case no other is supplied + _shadowCastingStateSet->setTextureAttributeAndModes(0, _fallbackBaseTexture.get(), osg::StateAttribute::ON); + _shadowCastingStateSet->addUniform(new osg::Uniform("useDiffuseMapForShadowAlpha", false)); + + _shadowCastingStateSet->setMode(GL_DEPTH_CLAMP, osg::StateAttribute::ON); + + _shadowCastingStateSet->setRenderBinDetails(osg::StateSet::OPAQUE_BIN, "RenderBin", osg::StateSet::OVERRIDE_PROTECTED_RENDERBIN_DETAILS); + + // TODO: compare performance when alpha testing is handled here versus using a discard in the fragment shader + // TODO: compare performance when we set a bunch of GL state to the default here with OVERRIDE set so that there are fewer pointless state switches +} + +osg::Polytope MWShadowTechnique::computeLightViewFrustumPolytope(Frustum& frustum, LightData& positionedLight) +{ + OSG_INFO<<"computeLightViewFrustumPolytope()"<getShadowSettings(); + + double dotProduct_v = positionedLight.lightDir * frustum.frustumCenterLine; + double gamma_v = acos(dotProduct_v); + if (gamma_vgetPerspectiveShadowMapCutOffAngle()) || gamma_v>osg::DegreesToRadians(180.0-settings->getPerspectiveShadowMapCutOffAngle())) + { + OSG_INFO<<"View direction and Light direction below tolerance"<=0.0 && d1>=0.0) + { + // OSG_NOTICE<<" Edge completely inside"<first; + osg::Vec3d& v1 = itr->second; + osg::Vec3d intersection = v0 - (v1-v0)*(d0/(d1-d0)); + intersections.push_back(intersection); + // OSG_NOTICE<<" Edge across clip plane, v0="<=side_y.length2()) ? side_x : side_y; + side.normalize(); + + osg::Vec3d up = side ^ normal; + up.normalize(); + + osg::Vec3d center; + for(Vertices::iterator itr = intersections.begin(); + itr != intersections.end(); + ++itr) + { + center += *itr; + + center.x() = osg::maximum(center.x(), -dbl_max); + center.y() = osg::maximum(center.y(), -dbl_max); + center.z() = osg::maximum(center.z(), -dbl_max); + + center.x() = osg::minimum(center.x(), dbl_max); + center.y() = osg::minimum(center.y(), dbl_max); + center.z() = osg::minimum(center.z(), dbl_max); + } + + center /= double(intersections.size()); + + typedef std::map>> VertexMap; + VertexMap vertexMap; + for(Vertices::iterator itr = intersections.begin(); + itr != intersections.end(); + ++itr) + { + osg::Vec3d dv = (*itr-center); + double h = dv * side; + double v = dv * up; + double angle = atan2(h,v); + // OSG_NOTICE<<"angle = "<_modelview.get()!=previous_modelview) + { + previous_modelview = renderLeaf->_modelview.get(); + if (previous_modelview) + { + light_mvp.mult(*renderLeaf->_modelview, light_p); + } + else + { + // no modelview matrix (such as when LightPointNode is in the scene graph) so assume + // that modelview matrix is indentity. + light_mvp = light_p; + } + // OSG_INFO<<"Computing new light_mvp "<_drawable->getBoundingBox(); + if (bb.valid()) + { + // OSG_NOTICE<<"checked extents of "<_drawable->getName()<max_z) { max_z=ls.z(); /* OSG_NOTICE<<" + ";*/ } + + // OSG_NOTICE<<" bb.z() in ls = "<& planeList) +{ + osg::Matrixd light_p = camera->getProjectionMatrix(); + osg::Matrixd light_v = camera->getViewMatrix(); + osg::Matrixd light_vp = light_v * light_p; + osg::Matrixd oldLightP = light_p; + + ConvexHull convexHull; + convexHull.setToFrustum(frustum); + + osg::Vec3d nearPoint = frustum.eye + frustum.frustumCenterLine * viewNear; + osg::Vec3d farPoint = frustum.eye + frustum.frustumCenterLine * viewFar; + + double nearDist = -frustum.frustumCenterLine * nearPoint; + double farDist = frustum.frustumCenterLine * farPoint; + + convexHull.clip(osg::Plane(frustum.frustumCenterLine, nearDist)); + convexHull.clip(osg::Plane(-frustum.frustumCenterLine, farDist)); + + convexHull.transform(light_vp); + + double xMin = -1.0, xMax = 1.0; + double yMin = -1.0, yMax = 1.0; + double zMin = -1.0, zMax = 1.0; + + if (convexHull.valid()) + { + xMin = osg::maximum(-1.0, convexHull.min(0)); + xMax = osg::minimum(1.0, convexHull.max(0)); + yMin = osg::maximum(-1.0, convexHull.min(1)); + yMax = osg::minimum(1.0, convexHull.max(1)); + zMin = osg::maximum(-1.0, convexHull.min(2)); + zMax = osg::minimum(1.0, convexHull.max(2)); + } + else + return false; + + if (xMin != -1.0 || yMin != -1.0 || zMin != -1.0 || + xMax != 1.0 || yMax != 1.0 || zMax != 1.0) + { + osg::Matrix m; + m.makeTranslate(osg::Vec3d(-0.5*(xMax + xMin), + -0.5*(yMax + yMin), + -0.5*(zMax + zMin))); + + m.postMultScale(osg::Vec3d(2.0 / (xMax - xMin), + 2.0 / (yMax - yMin), + 2.0 / (zMax - zMin))); + + light_p.postMult(m); + camera->setProjectionMatrix(light_p); + + convexHull.transform(osg::Matrixd::inverse(oldLightP)); + + xMin = convexHull.min(0); + xMax = convexHull.max(0); + yMin = convexHull.min(1); + yMax = convexHull.max(1); + zMin = convexHull.min(2); + + planeList.push_back(osg::Plane(0.0, -1.0, 0.0, yMax)); + planeList.push_back(osg::Plane(0.0, 1.0, 0.0, -yMin)); + planeList.push_back(osg::Plane(-1.0, 0.0, 0.0, xMax)); + planeList.push_back(osg::Plane(1.0, 0.0, 0.0, -xMin)); + // In view space, the light is at the most positive value, and we want to cull stuff beyond the minimum value. + planeList.push_back(osg::Plane(0.0, 0.0, 1.0, -zMin)); + // Don't add a zMax culling plane - we still want those objects, but don't care about their depth buffer value. + } + + return true; +} + +bool MWShadowTechnique::adjustPerspectiveShadowMapCameraSettings(osgUtil::RenderStage* renderStage, Frustum& frustum, LightData& /*positionedLight*/, osg::Camera* camera, double viewNear, double viewFar) +{ + const ShadowSettings* settings = getShadowedScene()->getShadowSettings(); + + //frustum.projectionMatrix; + //frustum.modelViewMatrix; + + osg::Matrixd light_p = camera->getProjectionMatrix(); + osg::Matrixd light_v = camera->getViewMatrix(); + osg::Matrixd light_vp = light_v * light_p; + osg::Vec3d lightdir(0.0,0.0,-1.0); + + // check whether this light space projection is perspective or orthographic. + bool orthographicLightSpaceProjection = light_p(0,3)==0.0 && light_p(1,3)==0.0 && light_p(2,3)==0.0; + + if (!orthographicLightSpaceProjection) + { + OSG_INFO<<"perspective light space projection not yet supported."<setProjectionMatrix(light_p); + } + +#endif + + osg::Vec3d eye_v = frustum.eye * light_v; + //osg::Vec3d centerNearPlane_v = frustum.centerNearPlane * light_v; + osg::Vec3d center_v = frustum.center * light_v; + osg::Vec3d viewdir_v = center_v-eye_v; viewdir_v.normalize(); + + double dotProduct_v = lightdir * viewdir_v; + double gamma_v = acos(dotProduct_v); + if (gamma_vgetPerspectiveShadowMapCutOffAngle()) || gamma_v>osg::DegreesToRadians(180-settings->getPerspectiveShadowMapCutOffAngle())) + { + // OSG_NOTICE<<"Light and view vectors near parallel - use standard shadow map."<getTraversalMask(); + + cv->setTraversalMask( traversalMask & _shadowedScene->getShadowSettings()->getReceivesShadowTraversalMask() ); + + _shadowedScene->osg::Group::traverse(*cv); + + cv->setTraversalMask( traversalMask ); + + return; +} + +void MWShadowTechnique::cullShadowCastingScene(osgUtil::CullVisitor* cv, osg::Camera* camera) const +{ + OSG_INFO<<"cullShadowCastingScene()"<getTraversalMask(); + + cv->setTraversalMask( traversalMask & _shadowedScene->getShadowSettings()->getCastsShadowTraversalMask() ); + + if (camera) camera->accept(*cv); + + cv->setTraversalMask( traversalMask ); + + return; +} + +osg::StateSet* MWShadowTechnique::selectStateSetForRenderingShadow(ViewDependentData& vdd) const +{ + OSG_INFO<<" selectStateSetForRenderingShadow() "< stateset = vdd.getStateSet(); + + OpenThreads::ScopedLock lock(_accessUniformsAndProgramMutex); + + vdd.getStateSet()->clear(); + + vdd.getStateSet()->setTextureAttributeAndModes(0, _fallbackBaseTexture.get(), osg::StateAttribute::ON); + + for(Uniforms::const_iterator itr=_uniforms.begin(); + itr!=_uniforms.end(); + ++itr) + { + OSG_INFO<<"addUniform("<<(*itr)->getName()<<")"<addUniform(itr->get()); + } + + if (_program.valid()) + { + stateset->setAttribute(_program.get()); + } + + LightDataList& pll = vdd.getLightDataList(); + for(LightDataList::iterator itr = pll.begin(); + itr != pll.end(); + ++itr) + { + // 3. create per light/per shadow map division of lightspace/frustum + // create a list of light/shadow map data structures + + LightData& pl = (**itr); + + // if no texture units have been activated for this light then no shadow state required. + if (pl.textureUnits.empty()) continue; + + for(LightData::ActiveTextureUnits::iterator atu_itr = pl.textureUnits.begin(); + atu_itr != pl.textureUnits.end(); + ++atu_itr) + { + OSG_INFO<<" Need to assign state for "<<*atu_itr<getShadowSettings(); + unsigned int shadowMapModeValue = settings->getUseOverrideForShadowMapTexture() ? + osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE : + osg::StateAttribute::ON; + + + ShadowDataList& sdl = vdd.getShadowDataList(); + for(ShadowDataList::iterator itr = sdl.begin(); + itr != sdl.end(); + ++itr) + { + // 3. create per light/per shadow map division of lightspace/frustum + // create a list of light/shadow map data structures + + ShadowData& sd = (**itr); + + OSG_INFO<<" ShadowData for "<setTextureAttributeAndModes(sd._textureUnit, sd._texture.get(), shadowMapModeValue); + + stateset->setTextureMode(sd._textureUnit,GL_TEXTURE_GEN_S,osg::StateAttribute::ON); + stateset->setTextureMode(sd._textureUnit,GL_TEXTURE_GEN_T,osg::StateAttribute::ON); + stateset->setTextureMode(sd._textureUnit,GL_TEXTURE_GEN_R,osg::StateAttribute::ON); + stateset->setTextureMode(sd._textureUnit,GL_TEXTURE_GEN_Q,osg::StateAttribute::ON); + } + + return vdd.getStateSet(); +} + +void MWShadowTechnique::resizeGLObjectBuffers(unsigned int /*maxSize*/) +{ + // the way that ViewDependentData is mapped shouldn't +} + +void MWShadowTechnique::releaseGLObjects(osg::State* state) const +{ + OpenThreads::ScopedLock lock(_viewDependentDataMapMutex); + for(ViewDependentDataMap::const_iterator itr = _viewDependentDataMap.begin(); + itr != _viewDependentDataMap.end(); + ++itr) + { + ViewDependentData* vdd = itr->second.get(); + if (vdd) + { + vdd->releaseGLObjects(state); + } + } + if (_debugHud) + _debugHud->releaseGLObjects(state); +} + +class DoubleBufferCallback : public osg::Callback +{ +public: + DoubleBufferCallback(osg::NodeList &children) : mChildren(children) {} + + virtual bool run(osg::Object* node, osg::Object* visitor) override + { + // We can't use a static cast as NodeVisitor virtually inherits from Object + osg::ref_ptr nodeVisitor = visitor->asNodeVisitor(); + unsigned int traversalNumber = nodeVisitor->getTraversalNumber(); + mChildren[traversalNumber % 2]->accept(*nodeVisitor); + + return true; + } + +protected: + osg::NodeList mChildren; +}; + +SceneUtil::MWShadowTechnique::DebugHUD::DebugHUD(int numberOfShadowMapsPerLight) : mDebugProgram(new osg::Program) +{ + osg::ref_ptr vertexShader = new osg::Shader(osg::Shader::VERTEX, debugVertexShaderSource); + mDebugProgram->addShader(vertexShader); + osg::ref_ptr fragmentShader = new osg::Shader(osg::Shader::FRAGMENT, debugFragmentShaderSource); + mDebugProgram->addShader(fragmentShader); + + osg::ref_ptr frustumProgram = new osg::Program; + vertexShader = new osg::Shader(osg::Shader::VERTEX, debugFrustumVertexShaderSource); + frustumProgram->addShader(vertexShader); + fragmentShader = new osg::Shader(osg::Shader::FRAGMENT, debugFrustumFragmentShaderSource); + frustumProgram->addShader(fragmentShader); + + for (int i = 0; i < 2; ++i) + { + mFrustumGeometries.emplace_back(new osg::Geometry()); + mFrustumGeometries[i]->setCullingActive(false); + + mFrustumGeometries[i]->getOrCreateStateSet()->setAttributeAndModes(frustumProgram, osg::StateAttribute::ON); + } + + osg::ref_ptr frustumDrawElements = new osg::DrawElementsUShort(osg::PrimitiveSet::LINE_STRIP); + for (auto & geom : mFrustumGeometries) + geom->addPrimitiveSet(frustumDrawElements); + frustumDrawElements->push_back(0); + frustumDrawElements->push_back(1); + frustumDrawElements->push_back(2); + frustumDrawElements->push_back(3); + frustumDrawElements->push_back(0); + frustumDrawElements->push_back(4); + frustumDrawElements->push_back(5); + frustumDrawElements->push_back(6); + frustumDrawElements->push_back(7); + frustumDrawElements->push_back(4); + + frustumDrawElements = new osg::DrawElementsUShort(osg::PrimitiveSet::LINES); + for (auto & geom : mFrustumGeometries) + geom->addPrimitiveSet(frustumDrawElements); + frustumDrawElements->push_back(1); + frustumDrawElements->push_back(5); + frustumDrawElements->push_back(2); + frustumDrawElements->push_back(6); + frustumDrawElements->push_back(3); + frustumDrawElements->push_back(7); + + for (int i = 0; i < numberOfShadowMapsPerLight; ++i) + addAnotherShadowMap(); +} + +void SceneUtil::MWShadowTechnique::DebugHUD::draw(osg::ref_ptr texture, unsigned int shadowMapNumber, const osg::Matrixd &matrix, osgUtil::CullVisitor& cv) +{ + // It might be possible to change shadow settings at runtime + if (shadowMapNumber > mDebugCameras.size()) + addAnotherShadowMap(); + + mFrustumUniforms[shadowMapNumber]->set(matrix); + + osg::ref_ptr stateSet = mDebugGeometry[shadowMapNumber]->getOrCreateStateSet(); + stateSet->setTextureAttributeAndModes(sDebugTextureUnit, texture, osg::StateAttribute::ON); + + // Some of these calls may be superfluous. + unsigned int traversalMask = cv.getTraversalMask(); + cv.setTraversalMask(mDebugGeometry[shadowMapNumber]->getNodeMask()); + cv.pushStateSet(stateSet); + mDebugCameras[shadowMapNumber]->accept(cv); + cv.popStateSet(); + cv.setTraversalMask(traversalMask); + + // cv.getState()->setCheckForGLErrors(osg::State::ONCE_PER_ATTRIBUTE); +} + +void SceneUtil::MWShadowTechnique::DebugHUD::releaseGLObjects(osg::State* state) const +{ + for (auto const& camera : mDebugCameras) + camera->releaseGLObjects(state); + mDebugProgram->releaseGLObjects(state); + for (auto const& node : mDebugGeometry) + node->releaseGLObjects(state); + for (auto const& node : mFrustumTransforms) + node->releaseGLObjects(state); + for (auto const& node : mFrustumGeometries) + node->releaseGLObjects(state); +} + +void SceneUtil::MWShadowTechnique::DebugHUD::setFrustumVertices(osg::ref_ptr vertices, unsigned int traversalNumber) +{ + mFrustumGeometries[traversalNumber % 2]->setVertexArray(vertices); +} + +void SceneUtil::MWShadowTechnique::DebugHUD::addAnotherShadowMap() +{ + unsigned int shadowMapNumber = mDebugCameras.size(); + + mDebugCameras.push_back(new osg::Camera); + mDebugCameras[shadowMapNumber]->setViewport(200 * shadowMapNumber, 0, 200, 200); + mDebugCameras[shadowMapNumber]->setRenderOrder(osg::Camera::POST_RENDER); + mDebugCameras[shadowMapNumber]->setClearColor(osg::Vec4(1.0, 1.0, 0.0, 1.0)); + mDebugCameras[shadowMapNumber]->getOrCreateStateSet()->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); + + mDebugGeometry.push_back(osg::createTexturedQuadGeometry(osg::Vec3(-1, -1, 0), osg::Vec3(2, 0, 0), osg::Vec3(0, 2, 0))); + mDebugGeometry[shadowMapNumber]->setCullingActive(false); + mDebugCameras[shadowMapNumber]->addChild(mDebugGeometry[shadowMapNumber]); + osg::ref_ptr stateSet = mDebugGeometry[shadowMapNumber]->getOrCreateStateSet(); + stateSet->setAttributeAndModes(mDebugProgram, osg::StateAttribute::ON); + osg::ref_ptr textureUniform = new osg::Uniform("texture", sDebugTextureUnit); + //textureUniform->setType(osg::Uniform::SAMPLER_2D); + stateSet->addUniform(textureUniform.get()); + + mFrustumTransforms.push_back(new osg::Group); + osg::NodeList frustumGeometryNodeList(mFrustumGeometries.cbegin(), mFrustumGeometries.cend()); + mFrustumTransforms[shadowMapNumber]->setCullCallback(new DoubleBufferCallback(frustumGeometryNodeList)); + mFrustumTransforms[shadowMapNumber]->setCullingActive(false); + mDebugCameras[shadowMapNumber]->addChild(mFrustumTransforms[shadowMapNumber]); + + mFrustumUniforms.push_back(new osg::Uniform(osg::Uniform::FLOAT_MAT4, "transform")); + mFrustumTransforms[shadowMapNumber]->getOrCreateStateSet()->addUniform(mFrustumUniforms[shadowMapNumber]); +} diff --git a/components/sceneutil/mwshadowtechnique.hpp b/components/sceneutil/mwshadowtechnique.hpp new file mode 100644 index 000000000..74c8661b9 --- /dev/null +++ b/components/sceneutil/mwshadowtechnique.hpp @@ -0,0 +1,289 @@ +/* This file is based on OpenSceneGraph's include/osgShadow/ViewDependentShadowMap. + * Where applicable, any changes made are covered by OpenMW's GPL 3 license, not the OSGPL. + * The original copyright notice is listed below. + */ + +/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2011 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. +*/ + +#ifndef COMPONENTS_SCENEUTIL_MWSHADOWTECHNIQUE_H +#define COMPONENTS_SCENEUTIL_MWSHADOWTECHNIQUE_H 1 + +#include +#include +#include +#include +#include + +#include + +#include +#include + +namespace SceneUtil { + + /** ViewDependentShadowMap provides an base implementation of view dependent shadow mapping techniques.*/ + class MWShadowTechnique : public osgShadow::ShadowTechnique + { + public: + MWShadowTechnique(); + + MWShadowTechnique(const MWShadowTechnique& vdsm, const osg::CopyOp& copyop = osg::CopyOp::SHALLOW_COPY); + + META_Object(SceneUtil, MWShadowTechnique); + + /** initialize the ShadowedScene and local cached data structures.*/ + virtual void init(); + + /** run the update traversal of the ShadowedScene and update any loca chached data structures.*/ + virtual void update(osg::NodeVisitor& nv); + + /** run the cull traversal of the ShadowedScene and set up the rendering for this ShadowTechnique.*/ + virtual void cull(osgUtil::CullVisitor& cv); + + /** Resize any per context GLObject buffers to specified size. */ + virtual void resizeGLObjectBuffers(unsigned int maxSize); + + /** If State is non-zero, this function releases any associated OpenGL objects for + * the specified graphics context. Otherwise, releases OpenGL objects + * for all graphics contexts. */ + virtual void releaseGLObjects(osg::State* = 0) const; + + /** Clean scene graph from any shadow technique specific nodes, state and drawables.*/ + virtual void cleanSceneGraph(); + + virtual void enableShadows(); + + virtual void disableShadows(); + + virtual void enableDebugHUD(); + + virtual void disableDebugHUD(); + + virtual void setSplitPointUniformLogarithmicRatio(double ratio); + + virtual void setSplitPointDeltaBias(double bias); + + virtual void setPolygonOffset(float factor, float units); + + virtual void enableFrontFaceCulling(); + + virtual void disableFrontFaceCulling(); + + virtual void setupCastingShader(Shader::ShaderManager &shaderManager); + + class ComputeLightSpaceBounds : public osg::NodeVisitor, public osg::CullStack + { + public: + ComputeLightSpaceBounds(osg::Viewport* viewport, const osg::Matrixd& projectionMatrix, osg::Matrixd& viewMatrix); + + void apply(osg::Node& node); + + void apply(osg::Drawable& drawable); + + void apply(Terrain::QuadTreeWorld& quadTreeWorld); + + void apply(osg::Billboard&); + + void apply(osg::Projection&); + + void apply(osg::Transform& transform); + + void apply(osg::Camera&); + + using osg::NodeVisitor::apply; + + void updateBound(const osg::BoundingBox& bb); + + void update(const osg::Vec3& v); + + osg::BoundingBox _bb; + }; + + struct Frustum + { + Frustum(osgUtil::CullVisitor* cv, double minZNear, double maxZFar); + + osg::Matrixd projectionMatrix; + osg::Matrixd modelViewMatrix; + + typedef std::vector Vertices; + Vertices corners; + + typedef std::vector Indices; + typedef std::vector Faces; + Faces faces; + + typedef std::vector Edges; + Edges edges; + + osg::Vec3d eye; + osg::Vec3d centerNearPlane; + osg::Vec3d centerFarPlane; + osg::Vec3d center; + osg::Vec3d frustumCenterLine; + }; + + // forward declare + class ViewDependentData; + + struct LightData : public osg::Referenced + { + LightData(ViewDependentData* vdd); + + virtual void setLightData(osg::RefMatrix* lm, const osg::Light* l, const osg::Matrixd& modelViewMatrix); + + ViewDependentData* _viewDependentData; + + osg::ref_ptr lightMatrix; + osg::ref_ptr light; + + osg::Vec4d lightPos; + osg::Vec3d lightPos3; + osg::Vec3d lightDir; + bool directionalLight; + + typedef std::vector ActiveTextureUnits; + ActiveTextureUnits textureUnits; + }; + + typedef std::list< osg::ref_ptr > LightDataList; + + struct ShadowData : public osg::Referenced + { + ShadowData(ViewDependentData* vdd); + + virtual void releaseGLObjects(osg::State* = 0) const; + + ViewDependentData* _viewDependentData; + + unsigned int _textureUnit; + osg::ref_ptr _texture; + osg::ref_ptr _texgen; + osg::ref_ptr _camera; + }; + + typedef std::list< osg::ref_ptr > ShadowDataList; + + + class ViewDependentData : public osg::Referenced + { + public: + ViewDependentData(MWShadowTechnique* vdsm); + + const MWShadowTechnique* getViewDependentShadowMap() const { return _viewDependentShadowMap; } + + LightDataList& getLightDataList() { return _lightDataList; } + + ShadowDataList& getShadowDataList() { return _shadowDataList; } + + osg::StateSet* getStateSet() { return _stateset.get(); } + + virtual void releaseGLObjects(osg::State* = 0) const; + + protected: + virtual ~ViewDependentData() {} + + MWShadowTechnique* _viewDependentShadowMap; + + osg::ref_ptr _stateset; + + LightDataList _lightDataList; + ShadowDataList _shadowDataList; + }; + + virtual ViewDependentData* createViewDependentData(osgUtil::CullVisitor* cv); + + ViewDependentData* getViewDependentData(osgUtil::CullVisitor* cv); + + + + virtual void createShaders(); + + virtual bool selectActiveLights(osgUtil::CullVisitor* cv, ViewDependentData* vdd) const; + + virtual osg::Polytope computeLightViewFrustumPolytope(Frustum& frustum, LightData& positionedLight); + + virtual bool computeShadowCameraSettings(Frustum& frustum, LightData& positionedLight, osg::Matrixd& projectionMatrix, osg::Matrixd& viewMatrix); + + virtual bool cropShadowCameraToMainFrustum(Frustum& frustum, osg::Camera* camera, double viewNear, double viewFar, std::vector& planeList); + + virtual bool adjustPerspectiveShadowMapCameraSettings(osgUtil::RenderStage* renderStage, Frustum& frustum, LightData& positionedLight, osg::Camera* camera, double viewNear, double viewFar); + + virtual bool assignTexGenSettings(osgUtil::CullVisitor* cv, osg::Camera* camera, unsigned int textureUnit, osg::TexGen* texgen); + + virtual void cullShadowReceivingScene(osgUtil::CullVisitor* cv) const; + + virtual void cullShadowCastingScene(osgUtil::CullVisitor* cv, osg::Camera* camera) const; + + virtual osg::StateSet* selectStateSetForRenderingShadow(ViewDependentData& vdd) const; + + protected: + virtual ~MWShadowTechnique(); + + typedef std::map< osgUtil::CullVisitor*, osg::ref_ptr > ViewDependentDataMap; + mutable OpenThreads::Mutex _viewDependentDataMapMutex; + ViewDependentDataMap _viewDependentDataMap; + + osg::ref_ptr _shadowRecievingPlaceholderStateSet; + + osg::ref_ptr _shadowCastingStateSet; + osg::ref_ptr _polygonOffset; + osg::ref_ptr _fallbackBaseTexture; + osg::ref_ptr _fallbackShadowMapTexture; + + typedef std::vector< osg::ref_ptr > Uniforms; + mutable OpenThreads::Mutex _accessUniformsAndProgramMutex; + Uniforms _uniforms; + osg::ref_ptr _program; + + bool _enableShadows; + + double _splitPointUniformLogRatio = 0.5; + double _splitPointDeltaBias = 0.0; + + float _polygonOffsetFactor = 1.1; + float _polygonOffsetUnits = 4.0; + + bool _useFrontFaceCulling = true; + + class DebugHUD : public osg::Referenced + { + public: + DebugHUD(int numberOfShadowMapsPerLight); + + virtual void draw(osg::ref_ptr texture, unsigned int shadowMapNumber, const osg::Matrixd &matrix, osgUtil::CullVisitor& cv); + + virtual void releaseGLObjects(osg::State* state = 0) const; + + virtual void setFrustumVertices(osg::ref_ptr vertices, unsigned int traversalNumber); + protected: + virtual void addAnotherShadowMap(); + + static const int sDebugTextureUnit = 0; + + std::vector> mDebugCameras; + osg::ref_ptr mDebugProgram; + std::vector> mDebugGeometry; + std::vector> mFrustumTransforms; + std::vector> mFrustumUniforms; + std::vector> mFrustumGeometries; + }; + + osg::ref_ptr _debugHud; + osg::ref_ptr _castingProgram; + }; + +} + +#endif diff --git a/components/sceneutil/optimizer.cpp b/components/sceneutil/optimizer.cpp index 25ad03fba..7c568ff9d 100644 --- a/components/sceneutil/optimizer.cpp +++ b/components/sceneutil/optimizer.cpp @@ -25,7 +25,6 @@ #include #include #include -#include #include #include @@ -36,7 +35,6 @@ #include #include #include -#include #include @@ -424,7 +422,7 @@ void CollectLowestTransformsVisitor::disableObject(ObjectMap::iterator itr) if (itr->second._canBeApplied) { - // we havn't been disabled yet so we need to disable, + // we haven't been disabled yet so we need to disable, itr->second._canBeApplied = false; // and then inform everybody we have been disabled. @@ -448,7 +446,7 @@ void CollectLowestTransformsVisitor::disableTransform(osg::Transform* transform) if (itr->second._canBeApplied) { - // we havn't been disabled yet so we need to disable, + // we haven't been disabled yet so we need to disable, itr->second._canBeApplied = false; // and then inform everybody we have been disabled. for(TransformStruct::ObjectSet::iterator oitr = itr->second._objectSet.begin(); @@ -907,7 +905,7 @@ void Optimizer::RemoveRedundantNodesVisitor::removeRedundantNodes() struct LessGeometry { - bool operator() (const osg::Geometry* lhs,const osg::Geometry* rhs) const + bool operator() (const osg::ref_ptr& lhs,const osg::ref_ptr& rhs) const { if (lhs->getStateSet()getStateSet()) return true; if (rhs->getStateSet()getStateSet()) return false; @@ -1003,7 +1001,7 @@ struct LessGeometry struct LessGeometryPrimitiveType { - bool operator() (const osg::Geometry* lhs,const osg::Geometry* rhs) const + bool operator() (const osg::ref_ptr& lhs,const osg::ref_ptr& rhs) const { for(unsigned int i=0; igetNumPrimitiveSets() && igetNumPrimitiveSets(); @@ -1117,45 +1115,39 @@ bool Optimizer::MergeGeometryVisitor::mergeGroup(osg::Group& group) if (group.getNumChildren()>=2) { - typedef std::vector DuplicateList; - typedef std::vector< osg::ref_ptr > DrawableList; - typedef std::map GeometryDuplicateMap; + typedef std::vector< osg::ref_ptr > DuplicateList; + typedef std::vector< osg::ref_ptr > Nodes; + typedef std::map< osg::ref_ptr ,DuplicateList,LessGeometry> GeometryDuplicateMap; typedef std::vector MergeList; GeometryDuplicateMap geometryDuplicateMap; - DrawableList standardDrawables; + Nodes standardChildren; unsigned int i; for(i=0;iasDrawable(); - if (drawable) + osg::Node* child = group.getChild(i); + osg::Geometry* geom = child->asGeometry(); + if (geom) { - osg::Geometry* geom = drawable->asGeometry(); - if (geom) + if (!geometryContainsSharedArrays(*geom) && + geom->getDataVariance()!=osg::Object::DYNAMIC && + isOperationPermissibleForObject(geom)) { - //geom->computeCorrectBindingsAndArraySizes(); - - if (!geometryContainsSharedArrays(*geom) && - geom->getDataVariance()!=osg::Object::DYNAMIC && - isOperationPermissibleForObject(geom)) - { - geometryDuplicateMap[geom].push_back(geom); - } - else - { - standardDrawables.push_back(drawable); - } + geometryDuplicateMap[geom].push_back(geom); } else { - standardDrawables.push_back(drawable); + standardChildren.push_back(geom); } } + else + { + standardChildren.push_back(child); + } } -#if 1 // first try to group geometries with the same properties // (i.e. array types) to avoid loss of data during merging MergeList mergeListChecked; // List of drawables just before merging, grouped by "compatibility" and vertex limit @@ -1185,7 +1177,7 @@ bool Optimizer::MergeGeometryVisitor::mergeGroup(osg::Group& group) dupItr!=itr->second.end(); ++dupItr) { - osg::Geometry* geomToPush = *dupItr; + osg::Geometry* geomToPush = dupItr->get(); // try to group geomToPush with another geometry MergeList::iterator eachMergeList=mergeListTmp.begin(); @@ -1218,126 +1210,74 @@ 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) + osg::Geometry* geometry = ditr->get(); + unsigned int numVertices = (geometry->getVertexArray() ? geometry->getVertexArray()->getNumElements() : 0); + if ((totalNumberVertices+numVertices)>_targetMaximumNumberOfVertices && !subset.empty()) { - break; - } - else - { - 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); + mergeGeometry(*lhs, **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; @@ -1609,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); } @@ -1646,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/pathgridutil.cpp b/components/sceneutil/pathgridutil.cpp index 1497beb2b..58c5d8ad2 100644 --- a/components/sceneutil/pathgridutil.cpp +++ b/components/sceneutil/pathgridutil.cpp @@ -2,7 +2,6 @@ #include -#include #include namespace SceneUtil diff --git a/components/sceneutil/positionattitudetransform.cpp b/components/sceneutil/positionattitudetransform.cpp index 5f6b57e97..6129c33b4 100644 --- a/components/sceneutil/positionattitudetransform.cpp +++ b/components/sceneutil/positionattitudetransform.cpp @@ -1,7 +1,5 @@ #include "positionattitudetransform.hpp" -#include - namespace SceneUtil { diff --git a/components/sceneutil/riggeometry.cpp b/components/sceneutil/riggeometry.cpp index f32f215e7..97c8e1d39 100644 --- a/components/sceneutil/riggeometry.cpp +++ b/components/sceneutil/riggeometry.cpp @@ -1,8 +1,5 @@ #include "riggeometry.hpp" -#include -#include - #include #include @@ -43,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) @@ -57,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) @@ -71,6 +69,8 @@ void RigGeometry::setSourceGeometry(osg::ref_ptr sourceGeometry) to.setSupportsDisplayList(false); to.setUseVertexBufferObjects(true); to.setCullingActive(false); // make sure to disable culling since that's handled by this class + to.setComputeBoundingBoxCallback(new CopyBoundingBoxCallback()); + to.setComputeBoundingSphereCallback(new CopyBoundingSphereCallback()); // vertices and normals are modified every frame, so we need to deep copy them. // assign a dedicated VBO to make sure that modifications don't interfere with source geometry's VBO. @@ -296,6 +296,14 @@ void RigGeometry::updateBounds(osg::NodeVisitor *nv) _boundingSphereComputed = true; for (unsigned int i=0; idirtyBound(); + + for (unsigned int i = 0; i < 2; ++i) + { + osg::Geometry& geom = *mGeometry[i]; + static_cast(geom.getComputeBoundingBoxCallback())->boundingBox = _boundingBox; + static_cast(geom.getComputeBoundingSphereCallback())->boundingSphere = _boundingSphere; + geom.dirtyBound(); + } } } diff --git a/components/sceneutil/riggeometry.hpp b/components/sceneutil/riggeometry.hpp index 267c709f9..a393530ec 100644 --- a/components/sceneutil/riggeometry.hpp +++ b/components/sceneutil/riggeometry.hpp @@ -50,6 +50,20 @@ namespace SceneUtil virtual bool supports(const osg::PrimitiveFunctor&) const { return true; } virtual void accept(osg::PrimitiveFunctor&) const; + struct CopyBoundingBoxCallback : osg::Drawable::ComputeBoundingBoxCallback + { + osg::BoundingBox boundingBox; + + virtual osg::BoundingBox computeBound(const osg::Drawable&) const override { return boundingBox; } + }; + + struct CopyBoundingSphereCallback : osg::Node::ComputeBoundingSphereCallback + { + osg::BoundingSphere boundingSphere; + + virtual osg::BoundingSphere computeBound(const osg::Node&) const override { return boundingSphere; } + }; + private: void cull(osg::NodeVisitor* nv); void updateBounds(osg::NodeVisitor* nv); diff --git a/components/sceneutil/shadow.cpp b/components/sceneutil/shadow.cpp new file mode 100644 index 000000000..a69fd8090 --- /dev/null +++ b/components/sceneutil/shadow.cpp @@ -0,0 +1,158 @@ +#include "shadow.hpp" + +#include + +#include + +namespace SceneUtil +{ + using namespace osgShadow; + + void ShadowManager::setupShadowSettings() + { + mEnableShadows = Settings::Manager::getBool("enable shadows", "Shadows"); + + if (!mEnableShadows) + { + mShadowTechnique->disableShadows(); + return; + } + + mShadowTechnique->enableShadows(); + + mShadowSettings->setLightNum(0); + mShadowSettings->setReceivesShadowTraversalMask(~0u); + + int numberOfShadowMapsPerLight = Settings::Manager::getInt("number of shadow maps", "Shadows"); + mShadowSettings->setNumShadowMapsPerLight(numberOfShadowMapsPerLight); + mShadowSettings->setBaseShadowTextureUnit(8 - numberOfShadowMapsPerLight); + + mShadowSettings->setMinimumShadowMapNearFarRatio(Settings::Manager::getFloat("minimum lispsm near far ratio", "Shadows")); + if (Settings::Manager::getBool("compute tight scene bounds", "Shadows")) + mShadowSettings->setComputeNearFarModeOverride(osg::CullSettings::COMPUTE_NEAR_FAR_USING_PRIMITIVES); + + int mapres = Settings::Manager::getInt("shadow map resolution", "Shadows"); + mShadowSettings->setTextureSize(osg::Vec2s(mapres, mapres)); + + mShadowTechnique->setSplitPointUniformLogarithmicRatio(Settings::Manager::getFloat("split point uniform logarithmic ratio", "Shadows")); + mShadowTechnique->setSplitPointDeltaBias(Settings::Manager::getFloat("split point bias", "Shadows")); + + mShadowTechnique->setPolygonOffset(Settings::Manager::getFloat("polygon offset factor", "Shadows"), Settings::Manager::getFloat("polygon offset units", "Shadows")); + + if (Settings::Manager::getBool("use front face culling", "Shadows")) + mShadowTechnique->enableFrontFaceCulling(); + else + mShadowTechnique->disableFrontFaceCulling(); + + if (Settings::Manager::getBool("allow shadow map overlap", "Shadows")) + mShadowSettings->setMultipleShadowMapHint(osgShadow::ShadowSettings::CASCADED); + else + mShadowSettings->setMultipleShadowMapHint(osgShadow::ShadowSettings::PARALLEL_SPLIT); + + if (Settings::Manager::getBool("enable debug hud", "Shadows")) + mShadowTechnique->enableDebugHUD(); + else + mShadowTechnique->disableDebugHUD(); + } + + void ShadowManager::disableShadowsForStateSet(osg::ref_ptr stateset) + { + int numberOfShadowMapsPerLight = Settings::Manager::getInt("number of shadow maps", "Shadows"); + int baseShadowTextureUnit = 8 - numberOfShadowMapsPerLight; + + osg::ref_ptr fakeShadowMapImage = new osg::Image(); + fakeShadowMapImage->allocateImage(1, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT); + *(float*)fakeShadowMapImage->data() = std::numeric_limits::infinity(); + osg::ref_ptr fakeShadowMapTexture = new osg::Texture2D(fakeShadowMapImage); + fakeShadowMapTexture->setShadowComparison(true); + fakeShadowMapTexture->setShadowCompareFunc(osg::Texture::ShadowCompareFunc::ALWAYS); + for (int i = baseShadowTextureUnit; i < baseShadowTextureUnit + numberOfShadowMapsPerLight; ++i) + { + stateset->setTextureAttributeAndModes(i, fakeShadowMapTexture, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE | osg::StateAttribute::PROTECTED); + stateset->addUniform(new osg::Uniform(("shadowTexture" + std::to_string(i - baseShadowTextureUnit)).c_str(), i)); + stateset->addUniform(new osg::Uniform(("shadowTextureUnit" + std::to_string(i - baseShadowTextureUnit)).c_str(), i)); + } + } + + ShadowManager::ShadowManager(osg::ref_ptr sceneRoot, osg::ref_ptr rootNode, unsigned int outdoorShadowCastingMask, unsigned int indoorShadowCastingMask, Shader::ShaderManager &shaderManager) : mShadowedScene(new osgShadow::ShadowedScene), + mShadowTechnique(new MWShadowTechnique), + mOutdoorShadowCastingMask(outdoorShadowCastingMask), + mIndoorShadowCastingMask(indoorShadowCastingMask) + { + mShadowedScene->setShadowTechnique(mShadowTechnique); + + mShadowedScene->addChild(sceneRoot); + rootNode->addChild(mShadowedScene); + + mShadowSettings = mShadowedScene->getShadowSettings(); + setupShadowSettings(); + + mShadowTechnique->setupCastingShader(shaderManager); + + enableOutdoorMode(); + } + + Shader::ShaderManager::DefineMap ShadowManager::getShadowDefines() + { + if (!mEnableShadows) + return getShadowsDisabledDefines(); + + Shader::ShaderManager::DefineMap definesWithShadows; + + definesWithShadows["shadows_enabled"] = "1"; + + for (unsigned int i = 0; i < mShadowSettings->getNumShadowMapsPerLight(); ++i) + definesWithShadows["shadow_texture_unit_list"] += std::to_string(i) + ","; + // remove extra comma + definesWithShadows["shadow_texture_unit_list"] = definesWithShadows["shadow_texture_unit_list"].substr(0, definesWithShadows["shadow_texture_unit_list"].length() - 1); + + definesWithShadows["shadowMapsOverlap"] = Settings::Manager::getBool("allow shadow map overlap", "Shadows") ? "1" : "0"; + + definesWithShadows["useShadowDebugOverlay"] = Settings::Manager::getBool("enable debug overlay", "Shadows") ? "1" : "0"; + + // switch this to reading settings if it's ever exposed to the user + definesWithShadows["perspectiveShadowMaps"] = mShadowSettings->getShadowMapProjectionHint() == ShadowSettings::PERSPECTIVE_SHADOW_MAP ? "1" : "0"; + + definesWithShadows["disableNormalOffsetShadows"] = Settings::Manager::getFloat("normal offset distance", "Shadows") == 0.0 ? "1" : "0"; + + definesWithShadows["shadowNormalOffset"] = std::to_string(Settings::Manager::getFloat("normal offset distance", "Shadows")); + + return definesWithShadows; + } + + Shader::ShaderManager::DefineMap ShadowManager::getShadowsDisabledDefines() + { + Shader::ShaderManager::DefineMap definesWithoutShadows; + + definesWithoutShadows["shadows_enabled"] = "0"; + + definesWithoutShadows["shadow_texture_unit_list"] = ""; + + definesWithoutShadows["shadowMapsOverlap"] = "0"; + + definesWithoutShadows["useShadowDebugOverlay"] = "0"; + + definesWithoutShadows["perspectiveShadowMaps"] = "0"; + + definesWithoutShadows["disableNormalOffsetShadows"] = "0"; + + definesWithoutShadows["shadowNormalOffset"] = "0.0"; + + return definesWithoutShadows; + } + + void ShadowManager::enableIndoorMode() + { + if (Settings::Manager::getBool("enable indoor shadows", "Shadows")) + mShadowSettings->setCastsShadowTraversalMask(mIndoorShadowCastingMask); + else + mShadowTechnique->disableShadows(); + } + + void ShadowManager::enableOutdoorMode() + { + if (mEnableShadows) + mShadowTechnique->enableShadows(); + mShadowSettings->setCastsShadowTraversalMask(mOutdoorShadowCastingMask); + } +} diff --git a/components/sceneutil/shadow.hpp b/components/sceneutil/shadow.hpp new file mode 100644 index 000000000..24deff253 --- /dev/null +++ b/components/sceneutil/shadow.hpp @@ -0,0 +1,43 @@ +#ifndef COMPONENTS_SCENEUTIL_SHADOW_H +#define COMPONENTS_SCENEUTIL_SHADOW_H + +#include +#include + +#include + +#include "mwshadowtechnique.hpp" + +namespace SceneUtil +{ + class ShadowManager + { + public: + static void disableShadowsForStateSet(osg::ref_ptr stateSet); + + ShadowManager(osg::ref_ptr sceneRoot, osg::ref_ptr rootNode, unsigned int outdoorShadowCastingMask, unsigned int indoorShadowCastingMask, Shader::ShaderManager &shaderManager); + + virtual ~ShadowManager() = default; + + virtual void setupShadowSettings(); + + virtual Shader::ShaderManager::DefineMap getShadowDefines(); + + virtual Shader::ShaderManager::DefineMap getShadowsDisabledDefines(); + + virtual void enableIndoorMode(); + + virtual void enableOutdoorMode(); + protected: + bool mEnableShadows; + + osg::ref_ptr mShadowedScene; + osg::ref_ptr mShadowSettings; + osg::ref_ptr mShadowTechnique; + + unsigned int mOutdoorShadowCastingMask; + unsigned int mIndoorShadowCastingMask; + }; +} + +#endif //COMPONENTS_SCENEUTIL_SHADOW_H diff --git a/components/sceneutil/visitor.cpp b/components/sceneutil/visitor.cpp index 3c3559a08..876ea920f 100644 --- a/components/sceneutil/visitor.cpp +++ b/components/sceneutil/visitor.cpp @@ -61,10 +61,7 @@ namespace SceneUtil { // Take transformation for first found node in file const std::string nodeName = Misc::StringUtils::lowerCase(trans.getName()); - if (mMap.find(nodeName) == mMap.end()) - { - mMap[nodeName] = &trans; - } + mMap.emplace(nodeName, &trans); traverse(trans); } diff --git a/components/sdlutil/sdlcursormanager.cpp b/components/sdlutil/sdlcursormanager.cpp index 6d2117575..82854cb2f 100644 --- a/components/sdlutil/sdlcursormanager.cpp +++ b/components/sdlutil/sdlcursormanager.cpp @@ -2,7 +2,6 @@ #include #include -#include #include #include 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/sdlutil/sdlinputwrapper.cpp b/components/sdlutil/sdlinputwrapper.cpp index f7111fdf8..c67edfbf3 100644 --- a/components/sdlutil/sdlinputwrapper.cpp +++ b/components/sdlutil/sdlinputwrapper.cpp @@ -368,7 +368,7 @@ InputWrapper::InputWrapper(SDL_Window* window, osg::ref_ptr v } else { - throw new std::runtime_error("Tried to package non-motion event!"); + throw std::runtime_error("Tried to package non-motion event!"); } return pack_evt; diff --git a/components/shader/shadermanager.cpp b/components/shader/shadermanager.cpp index f3d52f5f0..97fc79562 100644 --- a/components/shader/shadermanager.cpp +++ b/components/shader/shadermanager.cpp @@ -1,7 +1,6 @@ #include "shadermanager.hpp" #include -#include #include #include @@ -21,6 +20,44 @@ namespace Shader mPath = path; } + bool addLineDirectivesAfterConditionalBlocks(std::string& source) + { + for (size_t position = 0; position < source.length(); ) + { + size_t foundPos = source.find("#endif", position); + foundPos = std::min(foundPos, source.find("#elif", position)); + foundPos = std::min(foundPos, source.find("#else", position)); + + if (foundPos == std::string::npos) + break; + + foundPos = source.find_first_of("\n\r", foundPos); + foundPos = source.find_first_not_of("\n\r", foundPos); + + size_t lineDirectivePosition = source.rfind("#line", foundPos); + int lineNumber; + if (lineDirectivePosition != std::string::npos) + { + size_t lineNumberStart = lineDirectivePosition + std::string("#line ").length(); + size_t lineNumberEnd = source.find_first_not_of("0123456789", lineNumberStart); + std::string lineNumberString = source.substr(lineNumberStart, lineNumberEnd - lineNumberStart); + lineNumber = std::stoi(lineNumberString) - 1; + } + else + { + lineDirectivePosition = 0; + lineNumber = 1; + } + lineNumber += std::count(source.begin() + lineDirectivePosition, source.begin() + foundPos, '\n'); + + source.replace(foundPos, 0, "#line " + std::to_string(lineNumber) + "\n"); + + position = foundPos; + } + + return true; + } + bool parseIncludes(boost::filesystem::path shaderPath, std::string& source) { boost::replace_all(source, "\r\n", "\n"); @@ -54,14 +91,30 @@ namespace Shader std::stringstream buffer; buffer << includeFstream.rdbuf(); + std::string stringRepresentation = buffer.str(); + addLineDirectivesAfterConditionalBlocks(stringRepresentation); // insert #line directives so we get correct line numbers in compiler errors int includedFileNumber = fileNumber++; - int lineNumber = std::count(source.begin(), source.begin() + foundPos, '\n'); + size_t lineDirectivePosition = source.rfind("#line", foundPos); + int lineNumber; + if (lineDirectivePosition != std::string::npos) + { + size_t lineNumberStart = lineDirectivePosition + std::string("#line ").length(); + size_t lineNumberEnd = source.find_first_not_of("0123456789", lineNumberStart); + std::string lineNumberString = source.substr(lineNumberStart, lineNumberEnd - lineNumberStart); + lineNumber = std::stoi(lineNumberString) - 1; + } + else + { + lineDirectivePosition = 0; + lineNumber = 1; + } + lineNumber += std::count(source.begin() + lineDirectivePosition, source.begin() + foundPos, '\n'); std::stringstream toInsert; - toInsert << "#line 0 " << includedFileNumber << "\n" << buffer.str() << "\n#line " << lineNumber << " 0\n"; + toInsert << "#line 0 " << includedFileNumber << "\n" << stringRepresentation << "\n#line " << lineNumber << " 0\n"; source.replace(foundPos, (end-foundPos+1), toInsert.str()); @@ -74,13 +127,97 @@ namespace Shader return true; } - bool parseDefines(std::string& source, const ShaderManager::DefineMap& defines) + bool parseFors(std::string& source) { - const char escapeCharacter = '@'; + const char escapeCharacter = '$'; size_t foundPos = 0; while ((foundPos = source.find(escapeCharacter)) != std::string::npos) { - size_t endPos = source.find_first_of(" \n\r()[].;", foundPos); + size_t endPos = source.find_first_of(" \n\r()[].;,", foundPos); + if (endPos == std::string::npos) + { + Log(Debug::Error) << "Unexpected EOF"; + return false; + } + std::string command = source.substr(foundPos + 1, endPos - (foundPos + 1)); + if (command != "foreach") + { + Log(Debug::Error) << "Unknown shader directive: $" << command; + return false; + } + + size_t iterNameStart = endPos + 1; + size_t iterNameEnd = source.find_first_of(" \n\r()[].;,", iterNameStart); + if (iterNameEnd == std::string::npos) + { + Log(Debug::Error) << "Unexpected EOF"; + return false; + } + std::string iteratorName = "$" + source.substr(iterNameStart, iterNameEnd - iterNameStart); + + size_t listStart = iterNameEnd + 1; + size_t listEnd = source.find_first_of("\n\r", listStart); + if (listEnd == std::string::npos) + { + Log(Debug::Error) << "Unexpected EOF"; + return false; + } + std::string list = source.substr(listStart, listEnd - listStart); + std::vector listElements; + if (list != "") + boost::split(listElements, list, boost::is_any_of(",")); + + size_t contentStart = source.find_first_not_of("\n\r", listEnd); + size_t contentEnd = source.find("$endforeach", contentStart); + if (contentEnd == std::string::npos) + { + Log(Debug::Error) << "Unexpected EOF"; + return false; + } + std::string content = source.substr(contentStart, contentEnd - contentStart); + + size_t overallEnd = contentEnd + std::string("$endforeach").length(); + + size_t lineDirectivePosition = source.rfind("#line", overallEnd); + int lineNumber; + if (lineDirectivePosition != std::string::npos) + { + size_t lineNumberStart = lineDirectivePosition + std::string("#line ").length(); + size_t lineNumberEnd = source.find_first_not_of("0123456789", lineNumberStart); + std::string lineNumberString = source.substr(lineNumberStart, lineNumberEnd - lineNumberStart); + lineNumber = std::stoi(lineNumberString); + } + else + { + lineDirectivePosition = 0; + lineNumber = 2; + } + lineNumber += std::count(source.begin() + lineDirectivePosition, source.begin() + overallEnd, '\n'); + + std::string replacement = ""; + for (std::vector::const_iterator element = listElements.cbegin(); element != listElements.cend(); element++) + { + std::string contentInstance = content; + size_t foundIterator; + while ((foundIterator = contentInstance.find(iteratorName)) != std::string::npos) + contentInstance.replace(foundIterator, iteratorName.length(), *element); + replacement += contentInstance; + } + replacement += "\n#line " + std::to_string(lineNumber); + source.replace(foundPos, overallEnd - foundPos, replacement); + } + + return true; + } + + bool parseDefines(std::string& source, const ShaderManager::DefineMap& defines, const ShaderManager::DefineMap& globalDefines) + { + const char escapeCharacter = '@'; + size_t foundPos = 0; + std::vector forIterators; + while ((foundPos = source.find(escapeCharacter)) != std::string::npos) + { + size_t endPos = source.find_first_of(" \n\r()[].;,", foundPos); if (endPos == std::string::npos) { Log(Debug::Error) << "Unexpected EOF"; @@ -88,14 +225,46 @@ namespace Shader } std::string define = source.substr(foundPos+1, endPos - (foundPos+1)); ShaderManager::DefineMap::const_iterator defineFound = defines.find(define); - if (defineFound == defines.end()) + ShaderManager::DefineMap::const_iterator globalDefineFound = globalDefines.find(define); + if (define == "foreach") { - Log(Debug::Error) << "Undefined " << define; - return false; + source.replace(foundPos, 1, "$"); + size_t iterNameStart = endPos + 1; + size_t iterNameEnd = source.find_first_of(" \n\r()[].;,", iterNameStart); + if (iterNameEnd == std::string::npos) + { + Log(Debug::Error) << "Unexpected EOF"; + return false; + } + forIterators.push_back(source.substr(iterNameStart, iterNameEnd - iterNameStart)); + } + else if (define == "endforeach") + { + source.replace(foundPos, 1, "$"); + if (forIterators.empty()) + { + Log(Debug::Error) << "endforeach without foreach"; + return false; + } + else + forIterators.pop_back(); + } + else if (std::find(forIterators.begin(), forIterators.end(), define) != forIterators.end()) + { + source.replace(foundPos, 1, "$"); + } + else if (defineFound != defines.end()) + { + source.replace(foundPos, endPos - foundPos, defineFound->second); + } + else if (globalDefineFound != globalDefines.end()) + { + source.replace(foundPos, endPos - foundPos, globalDefineFound->second); } else { - source.replace(foundPos, endPos-foundPos, defineFound->second); + Log(Debug::Error) << "Undefined " << define; + return false; } } return true; @@ -122,7 +291,7 @@ namespace Shader // parse includes std::string source = buffer.str(); - if (!parseIncludes(boost::filesystem::path(mPath), source)) + if (!addLineDirectivesAfterConditionalBlocks(source) || !parseIncludes(boost::filesystem::path(mPath), source)) return nullptr; templateIt = mShaderTemplates.insert(std::make_pair(shaderTemplate, source)).first; @@ -132,7 +301,7 @@ namespace Shader if (shaderIt == mShaders.end()) { std::string shaderSource = templateIt->second; - if (!parseDefines(shaderSource, defines)) + if (!parseDefines(shaderSource, defines, mGlobalDefines) || !parseFors(shaderSource)) { // Add to the cache anyway to avoid logging the same error over and over. mShaders.insert(std::make_pair(std::make_pair(shaderTemplate, defines), nullptr)); @@ -164,11 +333,39 @@ namespace Shader return found->second; } + ShaderManager::DefineMap ShaderManager::getGlobalDefines() + { + return DefineMap(mGlobalDefines); + } + + void ShaderManager::setGlobalDefines(DefineMap & globalDefines) + { + mGlobalDefines = globalDefines; + for (auto shaderMapElement: mShaders) + { + std::string templateId = shaderMapElement.first.first; + ShaderManager::DefineMap defines = shaderMapElement.first.second; + osg::ref_ptr shader = shaderMapElement.second; + if (shader == nullptr) + // I'm not sure how to handle a shader that was already broken as there's no way to get a potential replacement to the nodes that need it. + continue; + std::string shaderSource = mShaderTemplates[templateId]; + if (!parseDefines(shaderSource, defines, mGlobalDefines) || !parseFors(shaderSource)) + // We just broke the shader and there's no way to force existing objects back to fixed-function mode as we would when creating the shader. + // If we put a nullptr in the shader map, we just lose the ability to put a working one in later. + continue; + shader->setShaderSource(shaderSource); + } + } + void ShaderManager::releaseGLObjects(osg::State *state) { OpenThreads::ScopedLock lock(mMutex); for (auto shader : mShaders) - shader.second->releaseGLObjects(state); + { + if (shader.second != nullptr) + shader.second->releaseGLObjects(state); + } for (auto program : mPrograms) program.second->releaseGLObjects(state); } diff --git a/components/shader/shadermanager.hpp b/components/shader/shadermanager.hpp index 6b2e02124..05775edb6 100644 --- a/components/shader/shadermanager.hpp +++ b/components/shader/shadermanager.hpp @@ -8,6 +8,8 @@ #include +#include + #include namespace Shader @@ -32,11 +34,21 @@ namespace Shader osg::ref_ptr getProgram(osg::ref_ptr vertexShader, osg::ref_ptr fragmentShader); + /// Get (a copy of) the DefineMap used to construct all shaders + DefineMap getGlobalDefines(); + + /// Set the DefineMap used to construct all shaders + /// @param defines The DefineMap to use + /// @note This will change the source code for any shaders already created, potentially causing problems if they're being used to render a frame. It is recommended that any associated Viewers have their threading stopped while this function is running if any shaders are in use. + void setGlobalDefines(DefineMap & globalDefines); + void releaseGLObjects(osg::State* state); private: std::string mPath; + DefineMap mGlobalDefines; + // typedef std::map TemplateMap; TemplateMap mShaderTemplates; diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index a0a6832d0..63c676118 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -1,7 +1,5 @@ #include "shadervisitor.hpp" -#include - #include #include #include @@ -23,8 +21,7 @@ namespace Shader ShaderVisitor::ShaderRequirements::ShaderRequirements() : mShaderRequired(false) - , mColorMaterial(false) - , mVertexColorMode(GL_AMBIENT_AND_DIFFUSE) + , mColorMode(0) , mMaterialOverridden(false) , mNormalHeight(false) , mTexStageRequiringTangents(-1) @@ -40,8 +37,6 @@ namespace Shader ShaderVisitor::ShaderVisitor(ShaderManager& shaderManager, Resource::ImageManager& imageManager, const std::string &defaultVsTemplate, const std::string &defaultFsTemplate) : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) , mForceShaders(false) - , mClampLighting(true) - , mForcePerPixelLighting(false) , mAllowedToModifyStateSets(true) , mAutoUseNormalMaps(false) , mAutoUseSpecularMaps(false) @@ -58,16 +53,6 @@ namespace Shader mForceShaders = force; } - void ShaderVisitor::setClampLighting(bool clamp) - { - mClampLighting = clamp; - } - - void ShaderVisitor::setForcePerPixelLighting(bool force) - { - mForcePerPixelLighting = force; - } - void ShaderVisitor::apply(osg::Node& node) { if (node.getStateSet()) @@ -217,6 +202,13 @@ namespace Shader mRequirements.back().mShaderRequired = true; } } + + if (diffuseMap) + { + if (!writableStateSet) + writableStateSet = getWritableStateSet(node); + writableStateSet->addUniform(new osg::Uniform("useDiffuseMapForShadowAlpha", true)); + } } const osg::StateSet::AttributeList& attributes = stateset->getAttributeList(); @@ -230,8 +222,29 @@ namespace Shader mRequirements.back().mMaterialOverridden = true; const osg::Material* mat = static_cast(it->second.first.get()); - mRequirements.back().mColorMaterial = (mat->getColorMode() != osg::Material::OFF); - mRequirements.back().mVertexColorMode = mat->getColorMode(); + + if (!writableStateSet) + writableStateSet = getWritableStateSet(node); + + int colorMode; + switch (mat->getColorMode()) + { + case osg::Material::OFF: + colorMode = 0; + break; + case GL_AMBIENT: + colorMode = 3; + break; + default: + case GL_AMBIENT_AND_DIFFUSE: + colorMode = 2; + break; + case GL_EMISSION: + colorMode = 1; + break; + } + + mRequirements.back().mColorMode = colorMode; } } } @@ -272,30 +285,10 @@ namespace Shader defineMap[texIt->second + std::string("UV")] = std::to_string(texIt->first); } - if (!reqs.mColorMaterial) - defineMap["colorMode"] = "0"; - else - { - switch (reqs.mVertexColorMode) - { - case GL_AMBIENT: - defineMap["colorMode"] = "3"; - break; - default: - case GL_AMBIENT_AND_DIFFUSE: - defineMap["colorMode"] = "2"; - break; - case GL_EMISSION: - defineMap["colorMode"] = "1"; - break; - } - } - - defineMap["forcePPL"] = mForcePerPixelLighting ? "1" : "0"; - defineMap["clamp"] = mClampLighting ? "1" : "0"; - defineMap["parallax"] = reqs.mNormalHeight ? "1" : "0"; + writableStateSet->addUniform(new osg::Uniform("colorMode", reqs.mColorMode)); + osg::ref_ptr vertexShader (mShaderManager.getShader(mDefaultVsTemplate, defineMap, osg::Shader::VERTEX)); osg::ref_ptr fragmentShader (mShaderManager.getShader(mDefaultFsTemplate, defineMap, osg::Shader::FRAGMENT)); diff --git a/components/shader/shadervisitor.hpp b/components/shader/shadervisitor.hpp index cb0538d9d..ac0ecc699 100644 --- a/components/shader/shadervisitor.hpp +++ b/components/shader/shadervisitor.hpp @@ -23,13 +23,6 @@ namespace Shader /// Setting force = true will cause all objects to render using shaders, regardless of having a bump map. void setForceShaders(bool force); - /// Set whether lighting is clamped for visual compatibility with the fixed function pipeline. - void setClampLighting(bool clamp); - - /// By default, only bump mapped objects use per-pixel lighting. - /// Setting force = true will cause all shaders to use per-pixel lighting, regardless of having a bump map. - void setForcePerPixelLighting(bool force); - /// Set if we are allowed to modify StateSets encountered in the graph (default true). /// @par If set to false, then instead of modifying, the StateSet will be cloned and this new StateSet will be assigned to the node. /// @par This option is useful when the ShaderVisitor is run on a "live" subgraph that may have already been submitted for rendering. @@ -57,8 +50,6 @@ namespace Shader private: bool mForceShaders; - bool mClampLighting; - bool mForcePerPixelLighting; bool mAllowedToModifyStateSets; bool mAutoUseNormalMaps; @@ -81,9 +72,8 @@ namespace Shader bool mShaderRequired; - bool mColorMaterial; - // osg::Material::ColorMode - int mVertexColorMode; + int mColorMode; + bool mMaterialOverridden; bool mNormalHeight; // true if normal map has height info in alpha channel diff --git a/components/terrain/cellborder.cpp b/components/terrain/cellborder.cpp index d9e6d52fc..64434727e 100644 --- a/components/terrain/cellborder.cpp +++ b/components/terrain/cellborder.cpp @@ -7,7 +7,7 @@ #include "world.hpp" #include "../esm/loadland.hpp" -namespace MWRender +namespace Terrain { CellBorder::CellBorder(Terrain::World *world, osg::Group *root, int borderMask): diff --git a/components/terrain/cellborder.hpp b/components/terrain/cellborder.hpp index 530ea31ca..908cdea09 100644 --- a/components/terrain/cellborder.hpp +++ b/components/terrain/cellborder.hpp @@ -7,10 +7,7 @@ namespace Terrain { class World; -} -namespace MWRender -{ /** * @Brief Handles the debug cell borders. */ diff --git a/components/terrain/chunkmanager.cpp b/components/terrain/chunkmanager.cpp index b23b0b76c..41b1fdbe1 100644 --- a/components/terrain/chunkmanager.cpp +++ b/components/terrain/chunkmanager.cpp @@ -22,23 +22,22 @@ namespace Terrain { ChunkManager::ChunkManager(Storage *storage, Resource::SceneManager *sceneMgr, TextureManager* textureManager, CompositeMapRenderer* renderer) - : ResourceManager(nullptr) + : GenericResourceManager(nullptr) , mStorage(storage) , mSceneManager(sceneMgr) , mTextureManager(textureManager) , mCompositeMapRenderer(renderer) , mCompositeMapSize(512) + , mCompositeMapLevel(1.f) + , mMaxCompGeometrySize(1.f) , mCullingActive(true) { } -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(); @@ -57,22 +56,17 @@ 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); } -void ChunkManager::setCullingActive(bool active) -{ - mCullingActive = active; -} - osg::ref_ptr ChunkManager::createCompositeMapRTT() { osg::ref_ptr texture = new osg::Texture2D; @@ -89,7 +83,7 @@ osg::ref_ptr ChunkManager::createCompositeMapRTT() void ChunkManager::createCompositeMapGeometry(float chunkSize, const osg::Vec2f& chunkCenter, const osg::Vec4f& texCoords, CompositeMap& compositeMap) { - if (chunkSize > 1.f) + if (chunkSize > mMaxCompGeometrySize) { createCompositeMapGeometry(chunkSize/2.f, chunkCenter + osg::Vec2f(chunkSize/4.f, chunkSize/4.f), osg::Vec4f(texCoords.x() + texCoords.z()/2.f, texCoords.y(), texCoords.z()/2.f, texCoords.w()/2.f), compositeMap); createCompositeMapGeometry(chunkSize/2.f, chunkCenter + osg::Vec2f(-chunkSize/4.f, chunkSize/4.f), osg::Vec4f(texCoords.x(), texCoords.y(), texCoords.z()/2.f, texCoords.w()/2.f), compositeMap); @@ -122,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,11 +158,10 @@ std::vector > ChunkManager::createPasses(float chunk float blendmapScale = mStorage->getBlendmapScale(chunkSize); - return ::Terrain::createPasses(useShaders, mSceneManager->getForcePerPixelLighting(), - mSceneManager->getClampLighting(), &mSceneManager->getShaderManager(), layers, blendmapTextures, blendmapScale, blendmapScale); + 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); @@ -176,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); @@ -192,14 +186,14 @@ 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; geometry->addPrimitiveSet(mBufferCache.getIndexBuffer(numVerts, lodFlags)); - bool useCompositeMap = chunkSize >= 1.f; + bool useCompositeMap = chunkSize >= mCompositeMapLevel; unsigned int numUvSets = useCompositeMap ? 1 : 2; for (unsigned int i=0; i ChunkManager::createChunk(float chunkSize, const osg::Ve layer.mDiffuseMap = compositeMap->mTexture; layer.mParallax = false; layer.mSpecular = false; - geometry->setPasses(::Terrain::createPasses(mSceneManager->getForceShaders() || !mSceneManager->getClampLighting(), mSceneManager->getForcePerPixelLighting(), - mSceneManager->getClampLighting(), &mSceneManager->getShaderManager(), std::vector(1, layer), std::vector >(), 1.f, 1.f)); + geometry->setPasses(::Terrain::createPasses(mSceneManager->getForceShaders() || !mSceneManager->getClampLighting(), &mSceneManager->getShaderManager(), std::vector(1, layer), std::vector >(), 1.f, 1.f)); } else { diff --git a/components/terrain/chunkmanager.hpp b/components/terrain/chunkmanager.hpp index d8c4fd084..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,20 @@ 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; } + void setCompositeMapLevel(float level) { mCompositeMapLevel = level; } + void setMaxCompositeGeometrySize(float maxCompGeometrySize) { mMaxCompGeometrySize = maxCompGeometrySize; } void reportStats(unsigned int frameNumber, osg::Stats* stats) const override; @@ -38,10 +47,8 @@ namespace Terrain void releaseGLObjects(osg::State* state) override; - void setCullingActive(bool active); - 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(); @@ -56,6 +63,8 @@ namespace Terrain BufferCache mBufferCache; unsigned int mCompositeMapSize; + float mCompositeMapLevel; + float mMaxCompGeometrySize; bool mCullingActive; }; diff --git a/components/terrain/compositemaprenderer.cpp b/components/terrain/compositemaprenderer.cpp index 3dc0aa41c..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,12 @@ void CompositeMapRenderer::compile(CompositeMap &compositeMap, osg::RenderInfo & ++compositeMap.mCompiled; + if (mWorkQueue) + { + mUnrefQueue->push(compositeMap.mDrawables[i]); + } + compositeMap.mDrawables[i] = nullptr; + if (timeLeft) { *timeLeft -= timer.time_s(); @@ -131,6 +156,8 @@ void CompositeMapRenderer::compile(CompositeMap &compositeMap, osg::RenderInfo & break; } } + if (compositeMap.mCompiled == compositeMap.mDrawables.size()) + compositeMap.mDrawables = std::vector>(); state.haveAppliedAttribute(osg::StateAttribute::VIEWPORT); 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/material.cpp b/components/terrain/material.cpp index a0f051524..1c989cc2a 100644 --- a/components/terrain/material.cpp +++ b/components/terrain/material.cpp @@ -1,13 +1,10 @@ #include "material.hpp" -#include - #include #include #include #include #include -#include #include #include @@ -156,7 +153,7 @@ namespace namespace Terrain { - std::vector > createPasses(bool useShaders, bool forcePerPixelLighting, bool clampLighting, Shader::ShaderManager* shaderManager, const std::vector &layers, + std::vector > createPasses(bool useShaders, Shader::ShaderManager* shaderManager, const std::vector &layers, const std::vector > &blendmaps, int blendmapScale, float layerTileSize) { std::vector > passes; @@ -214,11 +211,8 @@ namespace Terrain } Shader::ShaderManager::DefineMap defineMap; - defineMap["forcePPL"] = forcePerPixelLighting ? "1" : "0"; - defineMap["clamp"] = clampLighting ? "1" : "0"; defineMap["normalMap"] = (it->mNormalMap) ? "1" : "0"; defineMap["blendMap"] = !firstLayer ? "1" : "0"; - defineMap["colorMode"] = "2"; defineMap["specularMap"] = it->mSpecular ? "1" : "0"; defineMap["parallax"] = (it->mNormalMap && it->mParallax) ? "1" : "0"; @@ -227,10 +221,11 @@ namespace Terrain if (!vertexShader || !fragmentShader) { // Try again without shader. Error already logged by above - return createPasses(false, forcePerPixelLighting, clampLighting, shaderManager, layers, blendmaps, blendmapScale, layerTileSize); + return createPasses(false, shaderManager, layers, blendmaps, blendmapScale, layerTileSize); } stateset->setAttributeAndModes(shaderManager->getProgram(vertexShader, fragmentShader)); + stateset->addUniform(new osg::Uniform("colorMode", 2)); } else { diff --git a/components/terrain/material.hpp b/components/terrain/material.hpp index 25aa69540..5f78af6a0 100644 --- a/components/terrain/material.hpp +++ b/components/terrain/material.hpp @@ -26,7 +26,7 @@ namespace Terrain bool mSpecular; }; - std::vector > createPasses(bool useShaders, bool forcePerPixelLighting, bool clampLighting, Shader::ShaderManager* shaderManager, + std::vector > createPasses(bool useShaders, Shader::ShaderManager* shaderManager, const std::vector& layers, const std::vector >& blendmaps, int blendmapScale, float layerTileSize); diff --git a/components/terrain/quadtreenode.cpp b/components/terrain/quadtreenode.cpp index 2222cbb84..0c157cd48 100644 --- a/components/terrain/quadtreenode.cpp +++ b/components/terrain/quadtreenode.cpp @@ -71,21 +71,35 @@ QuadTreeNode::~QuadTreeNode() { } -QuadTreeNode* QuadTreeNode::getParent() -{ - return mParent; -} - -QuadTreeNode *QuadTreeNode::getChild(unsigned int i) -{ - return static_cast(Group::getChild(i)); -} - 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) @@ -102,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); @@ -134,7 +148,7 @@ ViewData* QuadTreeNode::getView(osg::NodeVisitor &nv) { osgUtil::CullVisitor* cv = static_cast(&nv); ViewData* vd = mViewDataMap->getViewData(cv->getCurrentCamera()); - vd->setEyePoint(nv.getEyePoint()); + vd->setEyePoint(nv.getViewPoint()); return vd; } else // INTERSECTION_VISITOR diff --git a/components/terrain/quadtreenode.hpp b/components/terrain/quadtreenode.hpp index 30e998119..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; @@ -35,10 +35,21 @@ namespace Terrain QuadTreeNode(QuadTreeNode* parent, ChildDirection dir, float size, const osg::Vec2f& center); virtual ~QuadTreeNode(); - QuadTreeNode* getParent(); + inline QuadTreeNode* getParent() { return mParent; } + inline QuadTreeNode* getChild(unsigned int i) { return static_cast(Group::getChild(i)); } + inline unsigned int getNumChildren() const { return _children.size(); } - QuadTreeNode* getChild(unsigned int i); - using osg::Group::getNumChildren; + // osg::Group::addChild() does a lot of unrelated stuff, but we just really want to add a child node. + void addChildNode(QuadTreeNode* child) + { + // QuadTree node should not contain more than 4 child nodes. + // Reserve enough space if this node is supposed to have child nodes. + _children.reserve(4); + _children.push_back(child); + 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 40f7a6fb0..fcb0f51a7 100644 --- a/components/terrain/quadtreeworld.cpp +++ b/components/terrain/quadtreeworld.cpp @@ -5,6 +5,7 @@ #include #include +#include #include "quadtreenode.hpp" #include "storage.hpp" @@ -39,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 @@ -74,21 +48,22 @@ namespace Terrain class DefaultLodCallback : public LodCallback { public: - DefaultLodCallback(float minSize) - : mMinSize(minSize) + DefaultLodCallback(float factor, float minSize) + : mFactor(factor) + , mMinSize(minSize) { } - 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))); + int lodLevel = Log2(static_cast(dist/(Constants::CellSizeInUnits*mMinSize*mFactor))); return nativeLodLevel <= lodLevel; } private: + float mFactor; float mMinSize; }; @@ -122,8 +97,9 @@ private: class QuadTreeBuilder { public: - QuadTreeBuilder(Terrain::Storage* storage, ViewDataMap* viewDataMap, float minSize) + QuadTreeBuilder(Terrain::Storage* storage, ViewDataMap* viewDataMap, float lodFactor, float minSize) : mStorage(storage) + , mLodFactor(lodFactor) , mMinX(0.f), mMaxX(0.f), mMinY(0.f), mMaxY(0.f) , mMinSize(minSize) , mViewDataMap(viewDataMap) @@ -145,7 +121,7 @@ public: mRootNode = new RootNode(size, osg::Vec2f(centerX, centerY)); mRootNode->setViewDataMap(mViewDataMap); - mRootNode->setLodCallback(new DefaultLodCallback(mMinSize)); + mRootNode->setLodCallback(new DefaultLodCallback(mLodFactor, mMinSize)); addChildren(mRootNode); mRootNode->initNeighbours(); @@ -157,30 +133,37 @@ public: osg::BoundingBox boundingBox; for (unsigned int i=0; i<4; ++i) { - QuadTreeNode* child = addChild(parent, static_cast(i), halfSize); + osg::ref_ptr child = addChild(parent, static_cast(i), halfSize); if (child) + { boundingBox.expandBy(child->getBoundingBox()); + parent->addChildNode(child); + } } - parent->setBoundingBox(boundingBox); + if (!boundingBox.valid()) + parent->removeChildren(0, 4); + else + parent->setBoundingBox(boundingBox); } - QuadTreeNode* addChild(QuadTreeNode* parent, ChildDirection direction, float size) + osg::ref_ptr addChild(QuadTreeNode* parent, ChildDirection direction, float size) { + float halfSize = size/2.f; osg::Vec2f center; switch (direction) { case SW: - center = parent->getCenter() + osg::Vec2f(-size/2.f,-size/2.f); + center = parent->getCenter() + osg::Vec2f(-halfSize,-halfSize); break; case SE: - center = parent->getCenter() + osg::Vec2f(size/2.f, -size/2.f); + center = parent->getCenter() + osg::Vec2f(halfSize, -halfSize); break; case NW: - center = parent->getCenter() + osg::Vec2f(-size/2.f, size/2.f); + center = parent->getCenter() + osg::Vec2f(-halfSize, halfSize); break; case NE: - center = parent->getCenter() + osg::Vec2f(size/2.f, size/2.f); + center = parent->getCenter() + osg::Vec2f(halfSize, halfSize); break; default: break; @@ -189,24 +172,41 @@ public: osg::ref_ptr node = new QuadTreeNode(parent, direction, size, center); node->setLodCallback(parent->getLodCallback()); node->setViewDataMap(mViewDataMap); - parent->addChild(node); - 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 + { + // 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; + } + + // 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; + + 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; } - - // 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()-size)*cellWorldSize, (center.y()-size)*cellWorldSize, minZ), - osg::Vec3f((center.x()+size)*cellWorldSize, (center.y()+size)*cellWorldSize, maxZ)); - node->setBoundingBox(boundingBox); - - return node; } osg::ref_ptr getRootNode() @@ -217,6 +217,7 @@ public: private: Terrain::Storage* mStorage; + float mLodFactor; float mMinX, mMaxX, mMinY, mMaxY; float mMinSize; ViewDataMap* mViewDataMap; @@ -224,13 +225,20 @@ private: osg::ref_ptr mRootNode; }; -QuadTreeWorld::QuadTreeWorld(osg::Group *parent, osg::Group *compileRoot, Resource::ResourceSystem *resourceSystem, Storage *storage, int nodeMask, int preCompileMask, int borderMask) - : World(parent, compileRoot, resourceSystem, storage, nodeMask, preCompileMask, borderMask) +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) + : 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); + + mChunkManager->setCompositeMapSize(compMapResolution); + mChunkManager->setCompositeMapLevel(compMapLevel); + mChunkManager->setMaxCompositeGeometrySize(maxCompGeometrySize); } QuadTreeWorld::~QuadTreeWorld() @@ -239,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; @@ -247,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); } } @@ -280,7 +292,30 @@ void traverseToCell(QuadTreeNode* node, ViewData* vd, int cellX, int cellY) } } -unsigned int getLodFlags(QuadTreeNode* node, int ourLod, ViewData* vd) +/// get the level of vertex detail to render this node at, expressed relative to the native resolution of the data set. +unsigned int getVertexLod(QuadTreeNode* node, int vertexLodMod) +{ + int lod = Log2(int(node->getSize())); + if (vertexLodMod > 0) + { + lod = std::max(0, lod-vertexLodMod); + } + else if (vertexLodMod < 0) + { + float size = node->getSize(); + // Stop to simplify at this level since with size = 1 the node already covers the whole cell and has getCellVertices() vertices. + while (size < 1) + { + size *= 2; + vertexLodMod = std::min(0, vertexLodMod+1); + } + lod += std::abs(vertexLodMod); + } + return lod; +} + +/// get the flags to use for stitching in the index buffer so that chunks of different LOD connect seamlessly +unsigned int getLodFlags(QuadTreeNode* node, int ourLod, int vertexLodMod, ViewData* vd) { unsigned int lodFlags = 0; for (unsigned int i=0; i<4; ++i) @@ -295,7 +330,7 @@ unsigned int getLodFlags(QuadTreeNode* node, int ourLod, ViewData* vd) neighbour = neighbour->getParent(); int lod = 0; if (neighbour) - lod = Log2(int(neighbour->getSize())); + lod = getVertexLod(neighbour, vertexLodMod); if (lod <= ourLod) // We only need to worry about neighbours less detailed than we are - lod = 0; // neighbours with more detail will do the stitching themselves @@ -308,13 +343,17 @@ unsigned int getLodFlags(QuadTreeNode* node, int ourLod, ViewData* vd) return lodFlags; } -void loadRenderingNode(ViewData::Entry& entry, ViewData* vd, ChunkManager* chunkManager) +void loadRenderingNode(ViewData::Entry& entry, ViewData* vd, int vertexLodMod, ChunkManager* chunkManager) { + if (!vd->hasChanged() && entry.mRenderingNode) + return; + + int ourLod = getVertexLod(entry.mNode, vertexLodMod); + if (vd->hasChanged()) { // have to recompute the lodFlags in case a neighbour has changed LOD. - int ourLod = Log2(int(entry.mNode->getSize())); - unsigned int lodFlags = getLodFlags(entry.mNode, ourLod, vd); + unsigned int lodFlags = getLodFlags(entry.mNode, ourLod, vertexLodMod, vd); if (lodFlags != entry.mLodFlags) { entry.mRenderingNode = nullptr; @@ -323,16 +362,25 @@ void loadRenderingNode(ViewData::Entry& entry, ViewData* vd, ChunkManager* chunk } if (!entry.mRenderingNode) - { - int ourLod = Log2(int(entry.mNode->getSize())); entry.mRenderingNode = chunkManager->getChunk(entry.mNode->getSize(), entry.mNode->getCenter(), ourLod, entry.mLodFlags); - } } void QuadTreeWorld::accept(osg::NodeVisitor &nv) { if (nv.getVisitorType() != osg::NodeVisitor::CULL_VISITOR && nv.getVisitorType() != osg::NodeVisitor::INTERSECTION_VISITOR) + { + if (nv.getName().find("AcceptedByComponentsTerrainQuadTreeWorld") != std::string::npos) + { + if (nv.getName().find("SceneUtil::MWShadowTechnique::ComputeLightSpaceBounds") != std::string::npos) + { + SceneUtil::MWShadowTechnique::ComputeLightSpaceBounds* clsb = static_cast(&nv); + clsb->apply(*this); + } + else + nv.apply(*mRootNode); + } return; + } ViewData* vd = mRootNode->getView(nv); @@ -350,7 +398,7 @@ void QuadTreeWorld::accept(osg::NodeVisitor &nv) traverseToCell(mRootNode.get(), vd, x,y); } else - traverse(mRootNode.get(), vd, cv, mRootNode->getLodCallback(), cv->getEyePoint(), true); + traverse(mRootNode.get(), vd, cv, mRootNode->getLodCallback(), cv->getViewPoint(), true, mViewDistance); } else mRootNode->traverse(nv); @@ -359,7 +407,7 @@ void QuadTreeWorld::accept(osg::NodeVisitor &nv) { ViewData::Entry& entry = vd->getEntry(i); - loadRenderingNode(entry, vd, mChunkManager.get()); + loadRenderingNode(entry, vd, mVertexLodMod, mChunkManager.get()); if (entry.mVisible) { @@ -385,7 +433,7 @@ void QuadTreeWorld::ensureQuadTreeBuilt() return; const float minSize = 1/8.f; - QuadTreeBuilder builder(mStorage, mViewDataMap.get(), minSize); + QuadTreeBuilder builder(mStorage, mViewDataMap.get(), mLodFactor, minSize); builder.build(); mRootNode = builder.getRootNode(); @@ -416,7 +464,7 @@ void QuadTreeWorld::cacheCell(View *view, int x, int y) for (unsigned int i=0; igetNumEntries(); ++i) { ViewData::Entry& entry = vd->getEntry(i); - loadRenderingNode(entry, vd, mChunkManager.get()); + loadRenderingNode(entry, vd, mVertexLodMod, mChunkManager.get()); } } @@ -425,17 +473,17 @@ 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, mChunkManager.get()); + loadRenderingNode(entry, vd, mVertexLodMod, mChunkManager.get()); } } @@ -449,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 c166a9cb1..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,21 +16,28 @@ 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=~0, int borderMask=0); + QuadTreeWorld(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem, Storage* storage, int nodeMask, int preCompileMask, int borderMask, int compMapResolution, float comMapLevel, float lodFactor, int vertexLodMod, float maxCompGeometrySize); + ~QuadTreeWorld(); void accept(osg::NodeVisitor& nv); 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); @@ -44,6 +52,9 @@ namespace Terrain OpenThreads::Mutex mQuadTreeMutex; 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/terraindrawable.cpp b/components/terrain/terraindrawable.cpp index f3080e31c..f216bb33b 100644 --- a/components/terrain/terraindrawable.cpp +++ b/components/terrain/terraindrawable.cpp @@ -47,10 +47,22 @@ void TerrainDrawable::cull(osgUtil::CullVisitor *cv) osg::RefMatrix& matrix = *cv->getModelViewMatrix(); + if (cv->getComputeNearFarMode() && bb.valid()) + { + if (!cv->updateCalculatedNearFar(matrix, *this, false)) + return; + } + float depth = bb.valid() ? distance(bb.center(),matrix) : 0.0f; if (osg::isNaN(depth)) return; + if (cv->getCurrentCamera()->getName() == "ShadowCamera") + { + cv->addDrawableAndDepth(this, &matrix, depth); + return; + } + bool pushedLight = mLightListCallback && mLightListCallback->pushLightState(this, cv); for (PassVector::const_iterator it = mPasses.begin(); it != mPasses.end(); ++it) diff --git a/components/terrain/terraingrid.cpp b/components/terrain/terraingrid.cpp index dbc1429da..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; } } @@ -84,7 +89,7 @@ void TerrainGrid::loadCell(int x, int y) void TerrainGrid::unloadCell(int x, int y) { - MWRender::CellBorder::CellGrid::iterator it = mGrid.find(std::make_pair(x,y)); + CellBorder::CellGrid::iterator it = mGrid.find(std::make_pair(x,y)); if (it == mGrid.end()) return; diff --git a/components/terrain/terraingrid.hpp b/components/terrain/terraingrid.hpp index 87e3b432c..eb30fb97d 100644 --- a/components/terrain/terraingrid.hpp +++ b/components/terrain/terraingrid.hpp @@ -33,7 +33,7 @@ namespace Terrain // split each ESM::Cell into mNumSplits*mNumSplits terrain chunks unsigned int mNumSplits; - MWRender::CellBorder::CellGrid mGrid; + CellBorder::CellGrid mGrid; }; } diff --git a/components/terrain/world.cpp b/components/terrain/world.cpp index cc81dbef8..da3bdb5c2 100644 --- a/components/terrain/world.cpp +++ b/components/terrain/world.cpp @@ -47,7 +47,7 @@ World::World(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSyst mTextureManager.reset(new TextureManager(mResourceSystem->getSceneManager())); mChunkManager.reset(new ChunkManager(mStorage, mResourceSystem->getSceneManager(), mTextureManager.get(), mCompositeMapRenderer)); - mCellBorder.reset(new MWRender::CellBorder(this,mTerrainRoot.get(),borderMask)); + mCellBorder.reset(new CellBorder(this,mTerrainRoot.get(),borderMask)); mResourceSystem->addResourceManager(mChunkManager.get()); mResourceSystem->addResourceManager(mTextureManager.get()); @@ -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 da1004783..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: @@ -116,7 +127,7 @@ namespace Terrain std::unique_ptr mTextureManager; std::unique_ptr mChunkManager; - std::unique_ptr mCellBorder; + std::unique_ptr mCellBorder; bool mBorderVisible; diff --git a/components/to_utf8/to_utf8.cpp b/components/to_utf8/to_utf8.cpp index d4ab00381..bcb174b7b 100644 --- a/components/to_utf8/to_utf8.cpp +++ b/components/to_utf8/to_utf8.cpp @@ -2,7 +2,6 @@ #include #include -#include #include #include diff --git a/components/vfs/manager.cpp b/components/vfs/manager.cpp index 4f3994bac..c19882381 100644 --- a/components/vfs/manager.cpp +++ b/components/vfs/manager.cpp @@ -1,6 +1,5 @@ #include "manager.hpp" -#include #include #include diff --git a/components/widgets/list.cpp b/components/widgets/list.cpp index 9318e32ed..73e01675a 100644 --- a/components/widgets/list.cpp +++ b/components/widgets/list.cpp @@ -3,7 +3,6 @@ #include #include #include -#include namespace Gui { 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/game.rst b/docs/source/reference/modding/settings/game.rst index 6af0118de..2497b9e2f 100644 --- a/docs/source/reference/modding/settings/game.rst +++ b/docs/source/reference/modding/settings/game.rst @@ -211,3 +211,16 @@ disposition change of merchants caused by trading will be permanent and won't be This imitates the option that Morrowind Code Patch offers. This setting can be toggled in Advanced tab of the launcher. + +only appropriate ammunition bypasses resistance +----------------------------------------------- + +:Type: boolean +:Range: True/False +:Default: False + +If this setting is true, you will have to use the appropriate ammunition to bypass normal weapon resistance (or weakness). +An enchanted bow with chitin arrows will no longer be enough for the purpose, while a steel longbow with glass arrows will still work. +This was previously the default engine behavior that diverged from Morrowind design. + +This setting can be toggled in Advanced tab of the launcher. diff --git a/docs/source/reference/modding/settings/index.rst b/docs/source/reference/modding/settings/index.rst index f7c86b567..53b097a54 100644 --- a/docs/source/reference/modding/settings/index.rst +++ b/docs/source/reference/modding/settings/index.rst @@ -47,6 +47,7 @@ The ranges included with each setting are the physically possible ranges, not re game general shaders + shadows input saves sound @@ -54,3 +55,4 @@ The ranges included with each setting are the physically possible ranges, not re video water windows + navigator diff --git a/docs/source/reference/modding/settings/input.rst b/docs/source/reference/modding/settings/input.rst index 321c28afd..51c72e15d 100644 --- a/docs/source/reference/modding/settings/input.rst +++ b/docs/source/reference/modding/settings/input.rst @@ -120,3 +120,16 @@ If this setting is true, moving the mouse away from the player will look down, while moving it towards the player will look up. This setting does not affect cursor movement in GUI mode. This setting can be toggled in game with the Invert Y Axis button in the Controls panel of the Options menu. + +enable controller +----------------- + +:Type: boolean +:Range: True/False +:Default: True + +Enable support of controller input — or rather not ignore controller events, +which are always sent if a controller is present and detected. +Disabling this setting can be useful for working around controller-related issues or for setting up split-screen gameplay configurations. + +This setting can be toggled in game with the Enable Joystick button in the Controls panel of the Options menu. diff --git a/docs/source/reference/modding/settings/navigator.rst b/docs/source/reference/modding/settings/navigator.rst new file mode 100644 index 000000000..d9ddf5381 --- /dev/null +++ b/docs/source/reference/modding/settings/navigator.rst @@ -0,0 +1,374 @@ +Navigator Settings +################ + +Main settings +************* + +This section is for players. + +enable +------ + +:Type: boolean +:Range: True/False +:Default: True + +Enable navigator. +When enabled background threads are started to build nav mesh for world geometry. +Pathfinding system uses nav mesh to build paths. +When disabled only pathgrid is used to build paths. +Single-core CPU systems may have big performance impact on exiting interior location and moving across exterior world. +May slightly affect performance on multi-core CPU systems. +Multi-core CPU systems may have different latency for nav mesh update depending on other settings and system performance. +Moving across external world, entering/exiting location produce nav mesh update. +NPC and creatures may not be able to find path before nav mesh is built around them. +Try to disable this if you want to have old fashioned AI which doesn't know where to go when you stand behind that stone and casting a firebolt. + +max tiles number +---------------- + +:Type: integer +:Range: >= 0 +:Default: 512 + +Number of tiles at nav mesh. +Nav mesh covers circle area around player. +This option allows to set an explicit limit for nav mesh size, how many tiles should fit into circle. +If actor is inside this area it able to find path over nav mesh. +Increasing this value may decrease performance. + +.. note:: + Don't expect infinite nav mesh size increasing. + This condition is always true: ``max tiles number * max polygons per tile <= 4194304``. + It's a limitation of `Recastnavigation `_ library. + +Advanced settings +***************** + +This section is for advanced PC uses who understands concepts of OS thread and memory. + +async nav mesh updater threads +------------------------------ + +:Type: integer +:Range: >= 1 +:Default: 1 + +Number of background threads to update nav mesh. +Increasing this value may decrease performance, but also may decrease or increase nav mesh update latency depending on number of CPU cores. +On systems with not less than 4 CPU cores latency dependens approximately like 1/log(n) from number of threads. +Don't expect twice better latency by doubling this value. + +max nav mesh tiles cache size +----------------------------- + +:Type: integer +:Range: >= 0 +:Default: 268435456 + +Maximum total cached size of all nav mesh tiles in bytes. +Setting greater than zero value will reduce nav mesh update latency for previously visited locations. +Increasing this value may increase total memory consumption, but potentially will reduce latency for recently visited locations. +Limit this value by total available physical memory minus base game memory consumption and other applications. +Game will not eat all memory at once. +Memory will be consumed in approximately linear dependency from number of nav mesh updates. +But only for new locations or already dropped from cache. + +Developer's settings +******************** + +This section is for developers or anyone who wants to investigate how nav mesh system works in OpenMW. + +enable log +---------- + +:Type: boolean +:Range: True/False +:Default: False + +Enable debug log. +Detournavigator module will write own debug log into separate file. +Potentially decreases performance. + +log path +-------- + +:Type: string +:Range: file system path +:Default: detournavigator.log + +Write debug log to this file. +Try use tmpfs or any other in-memory file system to reduce performance impact. + +enable write recast mesh to file +-------------------------------- + +:Type: boolean +:Range: True/False +:Default: False + +Write recast mesh to file in .obj format for each use to update nav mesh. +Option is used to find out what world geometry is used to build nav mesh. +Potentially decreases performance. + +enable write nav mesh to file +----------------------------- + +:Type: boolean +:Range: True/False +:Default: False + +Write nav mesh to file to be able to open by RecastDemo application. +Usually it is more usefull to have both enable write recast mesh to file and this options enabled. +RecastDemo supports .obj files. +Potentially decreases performance. + +enable recast mesh file name revision +------------------------------------- + +:Type: boolean +:Range: True/False +:Default: False + +Write each recast mesh file with revision in name. +Otherwise will rewrite same file. +If it is unclear when geometry is changed use this option to dump multiple files for each state. + +enable nav mesh file name revision +---------------------------------- + +:Type: boolean +:Range: True/False +:Default: False + +Write each nav mesh file with revision in name. +Otherwise will rewrite same file. +If it is unclear when nav mesh is changed use this option to dump multiple files for each state. + +recast mesh path prefix +----------------------- + +:Type: string +:Range: file system path +:Default: "" + +Write recast mesh file at path with this prefix. + +nav mesh path prefix +-------------------- + +:Type: string +:Range: file system path +:Default: "" + +Write nav mesh file at path with this prefix. + +enable nav mesh render +---------------------- + +:Type: boolean +:Range: True/False +:Default: False + +Render nav mesh. +Allows to do in-game debug. +Every nav mesh is visible and every update is noticable. +Potentially decreases performance. + +enable agents paths render +------------------- + +:Type: boolean +:Range: True/False +:Default: False + +Render agents paths. +Make visible all NPC's and creaure's plans where they are going. +Works even if Navigator is disabled. +Potentially decreases performance. + +Expert settings +*************** + +This section is for developers who wants to go deeper into Detournavigator component logic. + +recast scale factor +------------------- + +:Type: floating point +:Range: > 0.0 +:Default: 0.017647058823529415 + +Scale of nav mesh coordinates to world coordinates. Recastnavigation builds voxels for world geometry. +Basically voxel size is 1 / "cell size". To reduce amount of voxels we apply scale factor, to make voxel size +"recast scale factor" / "cell size". Default value calculates by this equation: +sStepSizeUp * "recast scale factor" / "cell size" = 3 (max climb height should be equal to 3 voxels). +Changing this value will change generated nav mesh. Some locations may become unavailable for NPC and creatures. +Pay attention to slopes and roofs when change it. Increasing this value will reduce nav mesh update latency. + +max polygon path size +--------------------- + +:Type: integer +:Range: > 0 +:Default: 1024 + +Maximum size of path over polygons. + +max smooth path size +-------------------- + +:Type: integer +:Range: > 0 +:Default: 1024 + +Maximum size of smoothed path. + +triangles per chunk +------------------- + +:Type: integer +:Range: > 0 +:Default: 256 + +Maximum number of triangles in each node of mesh AABB tree. + +Expert Recastnavigation related settings +**************************************** + +This section is for OpenMW developers who knows about `Recastnavigation `_ library and understands how it works. + +cell height +----------- + +:Type: floating point +:Range: > 0.0 +:Default: 0.2 + +The z-axis cell size to use for fields. +Defines voxel/grid/cell size. So their values have significant +side effects on all parameters defined in voxel units. +The minimum value for this parameter depends on the platform's floating point +accuracy, with the practical minimum usually around 0.05. +Same default value is used in RecastDemo. + +cell size +--------- + +:Type: floating point +:Range: > 0.0 +:Default: 0.2 + +The xy-plane cell size to use for fields. +Defines voxel/grid/cell size. So their values have significant +side effects on all parameters defined in voxel units. +The minimum value for this parameter depends on the platform's floating point +accuracy, with the practical minimum usually around 0.05. +Same default value is used in RecastDemo. + +detail sample dist +------------------ + +:Type: floating point +:Range: = 0.0 or >= 0.9 +:Default: 6.0 + +Sets the sampling distance to use when generating the detail mesh. + +detail sample max error +----------------------- + +:Type: floating point +:Range: >= 0.0 +:Default: 1.0 + +The maximum distance the detail mesh surface should deviate from heightfield data. + +max simplification error +------------------------ + +:Type: floating point +:Range: >= 0.0 +:Default: 1.3 + +The maximum distance a simplfied contour's border edges should deviate the original raw contour. + +tile size +--------- + +:Type: integer +:Range: > 0 +:Default: 64 + +The width and height of each tile. + +border size +----------- + +:Type: integer +:Range: >= 0 +:Default: 16 + +The size of the non-navigable border around the heightfield. + +max edge len +------------ + +:Type: integer +:Range: >= 0 +:Default: 12 + +The maximum allowed length for contour edges along the border of the mesh. + +max nav mesh query nodes +------------------------ + +:Type: integer +:Range: 0 < value <= 65535 +:Default: 2048 + +Maximum number of search nodes. + +max polygons per tile +--------------------- + +:Type: integer +:Range: 2^n, 0 < n < 22 +:Default: 4096 + +Maximum number of polygons per nav mesh tile. Maximum number of nav mesh tiles depends on +this value. 22 bits is a limit to store both tile identifier and polygon identifier (tiles = 2^(22 - log2(polygons))). +See `recastnavigation `_ for more details. + +.. Warning:: + Lower value may lead to ignored world geometry on nav mesh. + Greater value will reduce number of nav mesh tiles. + This condition is always true: ``max tiles number * max polygons per tile <= 4194304``. + It's a limitation of `Recastnavigation `_ library. + +max verts per poly +------------------ + +:Type: integer +:Range: >= 3 +:Default: 6 + +The maximum number of vertices allowed for polygons generated during the contour to polygon conversion process. + +region merge size +----------------- + +:Type: integer +:Range: >= 0 +:Default: 20 + +Any regions with a span count smaller than this value will, if possible, be merged with larger regions. + +region min size +--------------- + +:Type: integer +:Range: >= 0 +:Default: 8 + +The minimum number of cells allowed to form isolated island areas. diff --git a/docs/source/reference/modding/settings/shadows.rst b/docs/source/reference/modding/settings/shadows.rst new file mode 100644 index 000000000..9b207c448 --- /dev/null +++ b/docs/source/reference/modding/settings/shadows.rst @@ -0,0 +1,216 @@ +Shadow Settings +############### + +Main settings +************* + +enable shadows +-------------- + +:Type: boolean +:Range: True/False +:Default: False + +Enable or disable the rendering of shadows. +Unlike in the original Morrowind engine, 'Shadow Mapping' is used, which can have a performance impact, but has more realistic results. +Bear in mind that this will force OpenMW to use shaders as if :ref:`force shaders` was enabled. +A keen developer may be able to implement compatibility with fixed-function mode using the advice of `this post `_, but it may be more difficult than it seems. + + +number of shadow maps +--------------------- + +:Type: integer +:Range: 1 to 8, but higher values may conflict with other texture effects +:Default: 3 + +Control how many shadow maps to use - more of these means each shadow map texel covers less area, producing better-looking shadows, but may decrease performance. +Using too many shadow maps will lead to them overriding texture slots used for other effects, producing unpleasant artefacts. +A value of three is recommended in most cases, but other values may produce better results or performance. + +allow shadow map overlap +------------------------ + +:Type: boolean +:Range: True/False +:Default: True + +If true, allow shadow maps to overlap. +Counter-intuitively, will produce much better results when the light is behind the camera. +When enabled, OpenMW uses Cascaded Shadow Maps and when disabled, it uses Parallel Split Shadow Maps. + +enable debug hud +---------------- + +:Type: boolean +:Range: True/False +:Default: False + +Enable or disable the debug hud to see what the shadow map(s) contain. +This setting is only recommended for developers, bug reporting and advanced users performing fine-tuning of shadow settings. + +enable debug overlay +---------------- + +:Type: boolean +:Range: True/False +:Default: False + +Enable or disable the debug overlay to see the area covered by each shadow map. +This setting is only recommended for developers, bug reporting and advanced users performing fine-tuning of shadow settings. + +compute tight scene bounds +-------------------------- + +:Type: boolean +:Range: True/False +:Default: False + +With this setting enabled, attempt to better use the shadow map(s) by making them cover a smaller area. +This can be especially helpful when looking downwards with a high viewing distance but will be less useful with the default value. +The performance impact of this may be very large. + +shadow map resolution +--------------------- + +:Type: integer +:Range: Dependent on GPU/driver combination +:Default: 1024 + +Control How large to make the shadow map(s). +Higher values increase GPU load but can produce better-looking results. +Power-of-two values may turn out to be faster than smaller values which are not powers of two on some GPU/driver combinations. + +actor shadows +------------- + +:Type: boolean +:Range: True/False +:Default: False + +Allow actors to cast shadows. +Potentially decreases performance. + +player shadows +-------------- + +:Type: boolean +:Range: True/False +:Default: False + +Allow the player to cast shadows. +Potentially decreases performance. + +terrain shadows +--------------- + +:Type: boolean +:Range: True/False +:Default: False + +Allow terrain to cast shadows. +Potentially decreases performance. + +object shadows +-------------- + +:Type: boolean +:Range: True/False +:Default: False + +Allow static objects to cast shadows. +Potentially decreases performance. + +enable indoor shadows +--------------------- + +:Type: boolean +:Range: True/False +:Default: False + +Allow shadows indoors. +Due to limitations with Morrowind's data, only actors can cast shadows indoors without the ceiling casting a shadow everywhere. +Some might feel this is distracting as shadows can be cast through other objects, so indoor shadows can be disabled completely. + +Expert settings +*************** + +These settings are probably too complicated for regular users to judge what might be good values to set them to. +If you've got a good understanding of how shadow mapping works, or you've got enough time to try a large set of values, you may get better results tuning these yourself. +Copying values from another user who's done careful tuning is the recommended way of arriving at an optimal value for these settings. + +Understanding what some of these do might be easier for people who've read `this paper on Parallel Split Shadow Maps `_ and understood how they interact with the transformation used with Light Space Perspective Shadow Maps. + +polygon offset factor +--------------------- + +:Type: float +:Range: Theoretically the whole range of 32-bit floating point, but values just above 1.0 are most sensible. +:Default: 1.1 + +Used as the factor parameter for the polygon offset used for shadow map rendering. +Higher values reduce shadow flicker, but risk increasing Peter Panning. +See `the OpenGL documentation for glPolygonOffset `_ for details. + +polygon offset units +--------------------- + +:Type: float +:Range: Theoretically the whole range of 32-bit floating point, but values between 1 and 10 are most sensible. +:Default: 4.0 + +Used as the units parameter for the polygon offset used for shadow map rendering. +Higher values reduce shadow flicker, but risk increasing Peter Panning. +See `the OpenGL documentation for glPolygonOffset `_ for details. + +normal offset distance +---------------------- + +:Type: float +:Range: Theoretically the whole range of 32-bit floating point, but values between 0 and 2 are most sensible. +:Default: 1.0 + +How far along the surface normal to project shadow coordinates. +Higher values significantly reduce shadow flicker, usually with a lower increase of Peter Panning than the Polygon Offset settings. +This value is in in-game units, so 1.0 is roughly 1.4 cm. + +use front face culling +---------------------- + +:Type: boolean +:Range: True/False +:Default: True + +Excludes theoretically unnecessary faces from shadow maps, slightly increasing performance. +In practice, Peter Panning can be much less visible with these faces included, so if you have high polygon offset values, disabling this may help minimise the side effects. + +split point uniform logarithmic ratio +------------------------------------- + +:Type: float +:Range: 0.0-1.0 for sensible results. Other values may 'work' but could behave bizarrely. +:Default: 0.5 + +Controls the ratio of :math:`C_i^{log}` versus :math:`C_i^{uniform}` used to form the Practical Split Scheme as described in the linked paper. +When using a larger-than-default viewing distance and distant terrain, and you have `allow shadow map overlap`_ enabled, larger values will prevent nearby shadows losing quality. +It is therefore recommended that this isn't left at the default when the viewing distance is changed. + +split point bias +---------------- + +:Type: float +:Range: Any value supported by C++ floats on your platform, although undesirable behaviour is more likely to appear the further the value is from zero. +:Default: 0.0 + +The :math:`\delta_{bias}` parameter used to form the Practical Split Scheme as described in the linked paper. + +minimum lispsm near far ratio +----------------------------- + +:Type: float +:Range: Must be greater than zero. +:Default: 0.25 + +Controls the minimum near/far ratio for the Light Space Perspective Shadow Map transformation. +Helps prevent too much detail being brought towards the camera at the expense of detail further from the camera. +Increasing this pushes detail further away by moving the frustum apex further from the near plane. \ No newline at end of file diff --git a/docs/source/reference/modding/settings/terrain.rst b/docs/source/reference/modding/settings/terrain.rst index 687f55e5e..c8ae2a227 100644 --- a/docs/source/reference/modding/settings/terrain.rst +++ b/docs/source/reference/modding/settings/terrain.rst @@ -12,7 +12,7 @@ Controls whether the engine will use paging and LOD algorithms to load the terra Otherwise, only the terrain of the surrounding cells is loaded. .. note:: - When enabling distant terrain, make sure the 'viewing distance' in the camera section is set to a larger value so + When enabling distant terrain, make sure the 'viewing distance' in the camera section is set to a larger value so that you can actually see the additional terrain. To avoid frame drops as the player moves around, nearby terrain pages are always preloaded in the background, @@ -23,3 +23,81 @@ will still be controlled by cell preloading settings. The distant terrain engine is currently considered experimental and may receive updates and/or further configuration options in the future. The glaring omission of non-terrain objects in the distance somewhat limits this setting's usefulness. + +vertex lod mod +-------------- + +:Type: integer +:Range: any +:Default: 0 + +Controls only the Vertex LOD of the terrain. The amount of terrain chunks and the detail of composite maps is left unchanged. + +Must be changed in increments of 1. Each increment will double (for positive values) or halve (for negative values) the number of vertices rendered. +For example: -2 means 4x reduced detail, +3 means 8x increased detail. + +Note this setting will typically not affect near terrain. When set to increase detail, the detail of near terrain can not be increased +because the detail is simply not there in the data files, and when set to reduce detail, +the detail of near terrain will not be reduced because it was already less detailed than the far terrain (in view relative terms) to begin with. + +lod factor +---------- + +:Type: float +:Range: >0 +:Default: 1.0 + +Controls the level of detail if distant terrain is enabled. +Higher values increase detail at the cost of performance, lower values reduce detail but increase performance. + +Note: it also changes how the Quad Tree is split. +Increasing detail with this setting results in the visible terrain being divided into more chunks, +where as reducing detail with this setting would reduce the number of chunks. + +Fewer terrain chunks is faster for rendering, but on the other hand a larger proportion of the entire terrain +must be rebuilt when LOD levels change as the camera moves. +This could result in frame drops if moving across the map at high speed. + +For this reason, it is not recommended to change this setting if you want to change the LOD. +If you want to do that, first try using the 'vertex lod mod' setting to configure the detail of the terrain outlines +to your liking and then use 'composite map resolution' to configure the texture detail to your liking. +But these settings can only be changed in multiples of two, so you may want to adjust 'lod factor' afterwards for even more fine-tuning. + +composite map level +------------------- + +:Type: integer +:Range: >= -3 +: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. + +A composite map is a pre-rendered texture that contains all the texture layers combined. +Note that resolution of composite maps is currently always fixed at 'composite map resolution', +regardless of the resolution of the underlying terrain textures. +If high-detail texture replacers are used, probably it is worth to increase 'composite map resolution' setting value. + +composite map resolution +------------------------ + +:Type: integer +:Range: >0 +:Default: 512 + +Controls the resolution of composite maps. Larger values result in increased detail, +but may take longer to prepare and thus could result in longer loading times and an increased chance of frame drops during play. +As with most other texture resolution settings, it's most efficient to use values that are powers of two. + +An easy way to observe changes to loading time is to load a save in an interior next to an exterior door +(so it will start preloding terrain) and watch how long it takes for the 'Composite' counter on the F4 panel to fall to zero. + +max composite geometry size +--------------------------- + +:Type: float +:Range: >=1.0 +:Default: 4.0 + +Controls the maximum size of simple composite geometry chunk in cell units. With small values there will more draw calls and small textures, +but higher values create more overdraw (not every texture layer is used everywhere). diff --git a/docs/source/reference/modding/settings/water.rst b/docs/source/reference/modding/settings/water.rst index bd0741da9..3581efb99 100644 --- a/docs/source/reference/modding/settings/water.rst +++ b/docs/source/reference/modding/settings/water.rst @@ -58,17 +58,23 @@ This setting has no effect if the shader setting is false. This setting can be toggled with the 'Refraction' button in the Water tab of the Video panel of the Options menu. -reflect actors +reflection detail -------------- -:Type: boolean -:Range: True/False -:Default: False +:Type: integer +:Range: 0, 1, 2, 3, 4 +:Default: 2 -This setting controls whether or not NPCs and creatures are drawn in water reflections. -Setting this to true will enable actors in reflections and increase realism with a likely decrease in performance. +Controls what kinds of things are rendered in water reflections. -This setting can be toggled with the 'Reflect actors' button in the Water tab of the Video panel of the Options menu. +0: only sky is reflected +1: terrain is also reflected +2: statics, activators, and doors are also reflected +3: items, containers, and particles are also reflected +4: actors are also reflected + +In interiors the lowest level is 2. +This setting can be changed ingame with the "Reflection shader detail" dropdown under the Water tab of the Video panel in the Options menu. small feature culling pixel size -------------------------------- 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/extern/recastnavigation/.travis.yml b/extern/recastnavigation/.travis.yml index 6044cd6b5..0e63abad1 100644 --- a/extern/recastnavigation/.travis.yml +++ b/extern/recastnavigation/.travis.yml @@ -1,36 +1,72 @@ -sudo: false - language: cpp +branches: + only: + - master + - coverity_scan + - /recast-.*$/ -# Build with gcc and clang. -compiler: - - gcc - - clang - -# Build both debug and release configurations, through use of an environment variable in the build matrix. -env: - - BUILD_TYPE=debug CMAKE_BUILD_TYPE=Debug - - BUILD_TYPE=release CMAKE_BUILD_TYPE=Release +sudo: false addons: apt: - packages: - - libsdl2-dev + sources: + - ubuntu-toolchain-r-test + - llvm-toolchain-xenial-7 + packages: [ cmake, clang-7, clang-tools-7, gcc-8, g++-8, libsdl2-dev ] -install: - - wget https://github.com/premake/premake-core/releases/download/v5.0.0-alpha12/premake-5.0.0-alpha12-linux.tar.gz -O premake.tar.gz - - tar -xf premake.tar.gz - - rm premake.tar.gz +matrix: + include: + - name: Recastnavigation (all) on MacOS xcode9.4 + os: osx + osx_image: xcode9.4 + before_install: + - brew update + - brew install sdl2 + if: branch != coverity_scan + - name: Recastnavigation on Ubuntu Xenial GCC-5 + os: linux + dist: xenial + sudo: required + if: branch != coverity_scan + - name: Recastnavigation on Ubuntu Xenial GCC-8 + os: linux + dist: xenial + sudo: required + env: + - MATRIX_EVAL="CC=gcc-8 && CXX=g++-8" + if: branch != coverity_scan + - name: Recastnavigation on Ubuntu Xenial GCC-5 using Premake5 + os: linux + dist: xenial + sudo: required + if: branch != coverity_scan + before_install: + - wget https://github.com/premake/premake-core/releases/download/v5.0.0-alpha12/premake-5.0.0-alpha12-linux.tar.gz -O premake.tar.gz + - tar -xf premake.tar.gz + env: + - PREMAKE=1 + - name: Recastnavigation on Ubuntu Xenial Clang-7 with Static Analysis + os: linux + dist: xenial + sudo: required + env: + - MATRIX_EVAL="CC=clang-7 && CXX=clang++-7" + - ANALYZE="scan-build-7 --force-analyze-debug-code --use-cc clang-7 --use-c++ clang++-7" + if: branch != coverity_scan + compiler: clang + - name: Recastnavigation Coverity Scan + os: linux + dist: xenial + sudo: required + if: branch = coverity_scan -# Run premake to generate makefiles. -# Have to cd into directory and back out since premake5 doesn't appear to accept a directory argument. before_script: - - cd RecastDemo && ../premake5 gmake && cd .. - - mkdir build && cd build && cmake -D CMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} .. && cd .. + - if [ "${TRAVIS_OS_NAME}" = "linux" ]; then eval "${MATRIX_EVAL}"; fi + - if [ "${PREMAKE}" = "1" ]; then cd RecastDemo && ../premake5 gmake && cd ..; fi + - if [ "${PREMAKE}" != "1" ]; then mkdir -p build && cd build && ${ANALYZE} cmake ../ && cd ..; fi -# Run make in the directory containing generated makefiles, on the configuration specified by the environment variable. -script: - - make -C RecastDemo/Build/gmake -j$(nproc) config=${BUILD_TYPE} - - RecastDemo/Bin/Tests - - make -C build -j$(nproc) - - cd build && ctest +script: # 2 CPUs on Travis-CI + 1 extra for IO bound process + - if [ "${PREMAKE}" = "1" ]; then make -C RecastDemo/Build/gmake -j3; fi + - if [ "${PREMAKE}" != "1" ]; then make -C build -j3; fi + - if [ "${PREMAKE}" = "1" ]; then RecastDemo/Bin/Tests; fi + - if [ "${PREMAKE}" != "1" ]; then cd build && ctest; fi diff --git a/extern/recastnavigation/Detour/Include/DetourCommon.h b/extern/recastnavigation/Detour/Include/DetourCommon.h index 739858cd9..113e8c336 100644 --- a/extern/recastnavigation/Detour/Include/DetourCommon.h +++ b/extern/recastnavigation/Detour/Include/DetourCommon.h @@ -283,6 +283,28 @@ inline bool dtVequal(const float* p0, const float* p1) return d < thr; } +/// Checks that the specified vector's components are all finite. +/// @param[in] v A point. [(x, y, z)] +/// @return True if all of the point's components are finite, i.e. not NaN +/// or any of the infinities. +inline bool dtVisfinite(const float* v) +{ + bool result = + dtMathIsfinite(v[0]) && + dtMathIsfinite(v[1]) && + dtMathIsfinite(v[2]); + + return result; +} + +/// Checks that the specified vector's 2D components are finite. +/// @param[in] v A point. [(x, y, z)] +inline bool dtVisfinite2D(const float* v) +{ + bool result = dtMathIsfinite(v[0]) && dtMathIsfinite(v[2]); + return result; +} + /// Derives the dot product of two vectors on the xz-plane. (@p u . @p v) /// @param[in] u A vector [(x, y, z)] /// @param[in] v A vector [(x, y, z)] diff --git a/extern/recastnavigation/Detour/Include/DetourMath.h b/extern/recastnavigation/Detour/Include/DetourMath.h index 95e14f884..54af8af09 100644 --- a/extern/recastnavigation/Detour/Include/DetourMath.h +++ b/extern/recastnavigation/Detour/Include/DetourMath.h @@ -8,6 +8,9 @@ Members in this module are wrappers around the standard math library #define DETOURMATH_H #include +// This include is required because libstdc++ has problems with isfinite +// if cmath is included before math.h. +#include inline float dtMathFabsf(float x) { return fabsf(x); } inline float dtMathSqrtf(float x) { return sqrtf(x); } @@ -16,5 +19,6 @@ inline float dtMathCeilf(float x) { return ceilf(x); } inline float dtMathCosf(float x) { return cosf(x); } inline float dtMathSinf(float x) { return sinf(x); } inline float dtMathAtan2f(float y, float x) { return atan2f(y, x); } +inline bool dtMathIsfinite(float x) { return std::isfinite(x); } #endif diff --git a/extern/recastnavigation/Detour/Source/DetourCommon.cpp b/extern/recastnavigation/Detour/Source/DetourCommon.cpp index 41d0d7bd3..3886f14b0 100644 --- a/extern/recastnavigation/Detour/Source/DetourCommon.cpp +++ b/extern/recastnavigation/Detour/Source/DetourCommon.cpp @@ -204,32 +204,31 @@ void dtCalcPolyCenter(float* tc, const unsigned short* idx, int nidx, const floa bool dtClosestHeightPointTriangle(const float* p, const float* a, const float* b, const float* c, float& h) { float v0[3], v1[3], v2[3]; + dtVsub(v0, c,a); dtVsub(v1, b,a); dtVsub(v2, p,a); - - const float dot00 = dtVdot2D(v0, v0); - const float dot01 = dtVdot2D(v0, v1); - const float dot02 = dtVdot2D(v0, v2); - const float dot11 = dtVdot2D(v1, v1); - const float dot12 = dtVdot2D(v1, v2); - - // Compute barycentric coordinates - const float invDenom = 1.0f / (dot00 * dot11 - dot01 * dot01); - const float u = (dot11 * dot02 - dot01 * dot12) * invDenom; - const float v = (dot00 * dot12 - dot01 * dot02) * invDenom; + + // Compute scaled barycentric coordinates + float denom = v0[0] * v1[2] - v0[2] * v1[0]; + float u = v1[2] * v2[0] - v1[0] * v2[2]; + float v = v0[0] * v2[2] - v0[2] * v2[0]; + + if (denom < 0) { + denom = -denom; + u = -u; + v = -v; + } // The (sloppy) epsilon is needed to allow to get height of points which // are interpolated along the edges of the triangles. - static const float EPS = 1e-4f; - + float epsilon = - 1e-4f * denom; + // If point lies inside the triangle, return interpolated ycoord. - if (u >= -EPS && v >= -EPS && (u+v) <= 1+EPS) - { - h = a[1] + v0[1]*u + v1[1]*v; + if (u >= epsilon && v >= epsilon && (u+v) <= denom - epsilon) { + h = a[1] + (v0[1]*u + v1[1]*v) / denom; return true; } - return false; } diff --git a/extern/recastnavigation/Detour/Source/DetourNavMeshQuery.cpp b/extern/recastnavigation/Detour/Source/DetourNavMeshQuery.cpp index 90999f2f6..c5ef385f9 100644 --- a/extern/recastnavigation/Detour/Source/DetourNavMeshQuery.cpp +++ b/extern/recastnavigation/Detour/Source/DetourNavMeshQuery.cpp @@ -222,7 +222,10 @@ dtStatus dtNavMeshQuery::findRandomPoint(const dtQueryFilter* filter, float (*fr dtPolyRef* randomRef, float* randomPt) const { dtAssert(m_nav); - + + if (!filter || !frand || !randomRef || !randomPt) + return DT_FAILURE | DT_INVALID_PARAM; + // Randomly pick one tile. Assume that all tiles cover roughly the same area. const dtMeshTile* tile = 0; float tsum = 0.0f; @@ -319,8 +322,13 @@ dtStatus dtNavMeshQuery::findRandomPointAroundCircle(dtPolyRef startRef, const f dtAssert(m_openList); // Validate input - if (!startRef || !m_nav->isValidPolyRef(startRef)) + if (!m_nav->isValidPolyRef(startRef) || + !centerPos || !dtVisfinite(centerPos) || + maxRadius < 0 || !dtMathIsfinite(maxRadius) || + !filter || !frand || !randomRef || !randomPt) + { return DT_FAILURE | DT_INVALID_PARAM; + } const dtMeshTile* startTile = 0; const dtPoly* startPoly = 0; @@ -512,6 +520,9 @@ dtStatus dtNavMeshQuery::closestPointOnPoly(dtPolyRef ref, const float* pos, flo return DT_FAILURE | DT_INVALID_PARAM; if (!tile) return DT_FAILURE | DT_INVALID_PARAM; + + if (!pos || !dtVisfinite(pos) || !closest) + return DT_FAILURE | DT_INVALID_PARAM; // Off-mesh connections don't have detail polygons. if (poly->getType() == DT_POLYTYPE_OFFMESH_CONNECTION) @@ -607,6 +618,9 @@ dtStatus dtNavMeshQuery::closestPointOnPolyBoundary(dtPolyRef ref, const float* const dtPoly* poly = 0; if (dtStatusFailed(m_nav->getTileAndPolyByRef(ref, &tile, &poly))) return DT_FAILURE | DT_INVALID_PARAM; + + if (!pos || !dtVisfinite(pos) || !closest) + return DT_FAILURE | DT_INVALID_PARAM; // Collect vertices. float verts[DT_VERTS_PER_POLYGON*3]; @@ -659,6 +673,9 @@ dtStatus dtNavMeshQuery::getPolyHeight(dtPolyRef ref, const float* pos, float* h const dtPoly* poly = 0; if (dtStatusFailed(m_nav->getTileAndPolyByRef(ref, &tile, &poly))) return DT_FAILURE | DT_INVALID_PARAM; + + if (!pos || !dtVisfinite2D(pos)) + return DT_FAILURE | DT_INVALID_PARAM; if (poly->getType() == DT_POLYTYPE_OFFMESH_CONNECTION) { @@ -767,6 +784,8 @@ dtStatus dtNavMeshQuery::findNearestPoly(const float* center, const float* halfE if (!nearestRef) return DT_FAILURE | DT_INVALID_PARAM; + + // queryPolygons below will check rest of params dtFindNearestPolyQuery query(this, center); @@ -972,8 +991,12 @@ dtStatus dtNavMeshQuery::queryPolygons(const float* center, const float* halfExt { dtAssert(m_nav); - if (!center || !halfExtents || !filter || !query) + if (!center || !dtVisfinite(center) || + !halfExtents || !dtVisfinite(halfExtents) || + !filter || !query) + { return DT_FAILURE | DT_INVALID_PARAM; + } float bmin[3], bmax[3]; dtVsub(bmin, center, halfExtents); @@ -1021,14 +1044,20 @@ dtStatus dtNavMeshQuery::findPath(dtPolyRef startRef, dtPolyRef endRef, dtAssert(m_nav); dtAssert(m_nodePool); dtAssert(m_openList); - - if (pathCount) - *pathCount = 0; + + if (!pathCount) + return DT_FAILURE | DT_INVALID_PARAM; + + *pathCount = 0; // Validate input if (!m_nav->isValidPolyRef(startRef) || !m_nav->isValidPolyRef(endRef) || - !startPos || !endPos || !filter || maxPath <= 0 || !path || !pathCount) + !startPos || !dtVisfinite(startPos) || + !endPos || !dtVisfinite(endPos) || + !filter || !path || maxPath <= 0) + { return DT_FAILURE | DT_INVALID_PARAM; + } if (startRef == endRef) { @@ -1263,18 +1292,21 @@ dtStatus dtNavMeshQuery::initSlicedFindPath(dtPolyRef startRef, dtPolyRef endRef m_query.status = DT_FAILURE; m_query.startRef = startRef; m_query.endRef = endRef; - dtVcopy(m_query.startPos, startPos); - dtVcopy(m_query.endPos, endPos); + if (startPos) + dtVcopy(m_query.startPos, startPos); + if (endPos) + dtVcopy(m_query.endPos, endPos); m_query.filter = filter; m_query.options = options; m_query.raycastLimitSqr = FLT_MAX; - if (!startRef || !endRef) - return DT_FAILURE | DT_INVALID_PARAM; - // Validate input - if (!m_nav->isValidPolyRef(startRef) || !m_nav->isValidPolyRef(endRef)) + if (!m_nav->isValidPolyRef(startRef) || !m_nav->isValidPolyRef(endRef) || + !startPos || !dtVisfinite(startPos) || + !endPos || !dtVisfinite(endPos) || !filter) + { return DT_FAILURE | DT_INVALID_PARAM; + } // trade quality with performance? if (options & DT_FINDPATH_ANY_ANGLE) @@ -1530,7 +1562,13 @@ dtStatus dtNavMeshQuery::updateSlicedFindPath(const int maxIter, int* doneIters) dtStatus dtNavMeshQuery::finalizeSlicedFindPath(dtPolyRef* path, int* pathCount, const int maxPath) { + if (!pathCount) + return DT_FAILURE | DT_INVALID_PARAM; + *pathCount = 0; + + if (!path || maxPath <= 0) + return DT_FAILURE | DT_INVALID_PARAM; if (dtStatusFailed(m_query.status)) { @@ -1615,12 +1653,13 @@ dtStatus dtNavMeshQuery::finalizeSlicedFindPath(dtPolyRef* path, int* pathCount, dtStatus dtNavMeshQuery::finalizeSlicedFindPathPartial(const dtPolyRef* existing, const int existingSize, dtPolyRef* path, int* pathCount, const int maxPath) { + if (!pathCount) + return DT_FAILURE | DT_INVALID_PARAM; + *pathCount = 0; - - if (existingSize == 0) - { - return DT_FAILURE; - } + + if (!existing || existingSize <= 0 || !path || !pathCount || maxPath <= 0) + return DT_FAILURE | DT_INVALID_PARAM; if (dtStatusFailed(m_query.status)) { @@ -1823,14 +1862,19 @@ dtStatus dtNavMeshQuery::findStraightPath(const float* startPos, const float* en int* straightPathCount, const int maxStraightPath, const int options) const { dtAssert(m_nav); - + + if (!straightPathCount) + return DT_FAILURE | DT_INVALID_PARAM; + *straightPathCount = 0; - - if (!maxStraightPath) - return DT_FAILURE | DT_INVALID_PARAM; - - if (!path[0]) + + if (!startPos || !dtVisfinite(startPos) || + !endPos || !dtVisfinite(endPos) || + !path || pathSize <= 0 || !path[0] || + maxStraightPath <= 0) + { return DT_FAILURE | DT_INVALID_PARAM; + } dtStatus stat = 0; @@ -2070,13 +2114,19 @@ dtStatus dtNavMeshQuery::moveAlongSurface(dtPolyRef startRef, const float* start dtAssert(m_nav); dtAssert(m_tinyNodePool); + if (!visitedCount) + return DT_FAILURE | DT_INVALID_PARAM; + *visitedCount = 0; - - // Validate input - if (!startRef) - return DT_FAILURE | DT_INVALID_PARAM; - if (!m_nav->isValidPolyRef(startRef)) + + if (!m_nav->isValidPolyRef(startRef) || + !startPos || !dtVisfinite(startPos) || + !endPos || !dtVisfinite(endPos) || + !filter || !resultPos || !visited || + maxVisitedSize <= 0) + { return DT_FAILURE | DT_INVALID_PARAM; + } dtStatus status = DT_SUCCESS; @@ -2484,16 +2534,23 @@ dtStatus dtNavMeshQuery::raycast(dtPolyRef startRef, const float* startPos, cons dtRaycastHit* hit, dtPolyRef prevRef) const { dtAssert(m_nav); - + + if (!hit) + return DT_FAILURE | DT_INVALID_PARAM; + hit->t = 0; hit->pathCount = 0; hit->pathCost = 0; // Validate input - if (!startRef || !m_nav->isValidPolyRef(startRef)) - return DT_FAILURE | DT_INVALID_PARAM; - if (prevRef && !m_nav->isValidPolyRef(prevRef)) + if (!m_nav->isValidPolyRef(startRef) || + !startPos || !dtVisfinite(startPos) || + !endPos || !dtVisfinite(endPos) || + !filter || + (prevRef && !m_nav->isValidPolyRef(prevRef))) + { return DT_FAILURE | DT_INVALID_PARAM; + } float dir[3], curPos[3], lastPos[3]; float verts[DT_VERTS_PER_POLYGON*3+3]; @@ -2735,11 +2792,18 @@ dtStatus dtNavMeshQuery::findPolysAroundCircle(dtPolyRef startRef, const float* dtAssert(m_nodePool); dtAssert(m_openList); - *resultCount = 0; - - // Validate input - if (!startRef || !m_nav->isValidPolyRef(startRef)) + if (!resultCount) return DT_FAILURE | DT_INVALID_PARAM; + + *resultCount = 0; + + if (!m_nav->isValidPolyRef(startRef) || + !centerPos || !dtVisfinite(centerPos) || + radius < 0 || !dtMathIsfinite(radius) || + !filter || maxResult < 0) + { + return DT_FAILURE | DT_INVALID_PARAM; + } m_nodePool->clear(); m_openList->clear(); @@ -2901,8 +2965,18 @@ dtStatus dtNavMeshQuery::findPolysAroundShape(dtPolyRef startRef, const float* v dtAssert(m_nav); dtAssert(m_nodePool); dtAssert(m_openList); - + + if (!resultCount) + return DT_FAILURE | DT_INVALID_PARAM; + *resultCount = 0; + + if (!m_nav->isValidPolyRef(startRef) || + !verts || nverts < 3 || + !filter || maxResult < 0) + { + return DT_FAILURE | DT_INVALID_PARAM; + } // Validate input if (!startRef || !m_nav->isValidPolyRef(startRef)) @@ -3088,13 +3162,20 @@ dtStatus dtNavMeshQuery::findLocalNeighbourhood(dtPolyRef startRef, const float* { dtAssert(m_nav); dtAssert(m_tinyNodePool); - + + if (!resultCount) + return DT_FAILURE | DT_INVALID_PARAM; + *resultCount = 0; - // Validate input - if (!startRef || !m_nav->isValidPolyRef(startRef)) + if (!m_nav->isValidPolyRef(startRef) || + !centerPos || !dtVisfinite(centerPos) || + radius < 0 || !dtMathIsfinite(radius) || + !filter || maxResult < 0) + { return DT_FAILURE | DT_INVALID_PARAM; - + } + static const int MAX_STACK = 48; dtNode* stack[MAX_STACK]; int nstack = 0; @@ -3301,13 +3382,19 @@ dtStatus dtNavMeshQuery::getPolyWallSegments(dtPolyRef ref, const dtQueryFilter* const int maxSegments) const { dtAssert(m_nav); + + if (!segmentCount) + return DT_FAILURE | DT_INVALID_PARAM; *segmentCount = 0; - + const dtMeshTile* tile = 0; const dtPoly* poly = 0; if (dtStatusFailed(m_nav->getTileAndPolyByRef(ref, &tile, &poly))) return DT_FAILURE | DT_INVALID_PARAM; + + if (!filter || !segmentVerts || maxSegments < 0) + return DT_FAILURE | DT_INVALID_PARAM; int n = 0; static const int MAX_INTERVAL = 16; @@ -3455,8 +3542,13 @@ dtStatus dtNavMeshQuery::findDistanceToWall(dtPolyRef startRef, const float* cen dtAssert(m_openList); // Validate input - if (!startRef || !m_nav->isValidPolyRef(startRef)) + if (!m_nav->isValidPolyRef(startRef) || + !centerPos || !dtVisfinite(centerPos) || + maxRadius < 0 || !dtMathIsfinite(maxRadius) || + !filter || !hitDist || !hitPos || !hitNormal) + { return DT_FAILURE | DT_INVALID_PARAM; + } m_nodePool->clear(); m_openList->clear(); diff --git a/extern/recastnavigation/RecastDemo/CMakeLists.txt b/extern/recastnavigation/RecastDemo/CMakeLists.txt index ffeac6a4b..a6a1a7333 100644 --- a/extern/recastnavigation/RecastDemo/CMakeLists.txt +++ b/extern/recastnavigation/RecastDemo/CMakeLists.txt @@ -38,11 +38,13 @@ endif() add_dependencies(RecastDemo DebugUtils Detour DetourCrowd DetourTileCache Recast) target_link_libraries(RecastDemo ${OPENGL_LIBRARIES} SDL2::SDL2main DebugUtils Detour DetourCrowd DetourTileCache Recast) -install(TARGETS RecastDemo RUNTIME DESTINATION bin) +install(TARGETS RecastDemo + RUNTIME DESTINATION bin + BUNDLE DESTINATION bin) install(DIRECTORY Bin/Meshes DESTINATION bin) install(DIRECTORY Bin/TestCases DESTINATION bin) install(FILES Bin/DroidSans.ttf DESTINATION bin) if (WIN32) install(FILES "${SDL2_RUNTIME_LIBRARY}" DESTINATION bin) -endif() +endif() \ No newline at end of file diff --git a/files/mygui/openmw_settings_window.layout b/files/mygui/openmw_settings_window.layout index 8062980ff..2341bfa19 100644 --- a/files/mygui/openmw_settings_window.layout +++ b/files/mygui/openmw_settings_window.layout @@ -53,12 +53,12 @@ - - + + - + @@ -83,11 +83,11 @@ - + - + @@ -213,7 +213,7 @@ - + @@ -222,21 +222,19 @@ - - - + - + - + - + @@ -246,14 +244,24 @@ - + - + + + + + + + + + + + @@ -301,12 +309,12 @@ - - + + - + @@ -345,18 +353,19 @@ - + - + + @@ -364,18 +373,20 @@ - + - + - + + + @@ -420,13 +431,15 @@ - - - - + + + + + + - - + + diff --git a/files/opencs/qt.png b/files/opencs/qt.png new file mode 100644 index 000000000..381cb2251 Binary files /dev/null and b/files/opencs/qt.png differ diff --git a/files/opencs/resources.qrc b/files/opencs/resources.qrc index 5fd9860e1..6987544d7 100644 --- a/files/opencs/resources.qrc +++ b/files/opencs/resources.qrc @@ -68,6 +68,7 @@ object.png pathgrid.png potion.png + qt.png race.png random-item.png random.png diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 71a93d1a2..353a3ac3d 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -90,6 +90,22 @@ pointers cache size = 40 # If true, use paging and LOD algorithms to display the entire terrain. If false, only display terrain of the loaded cells distant terrain = false +# Controls how the Quad Tree is split. This affects Vertex LOD, Texture LOD and load times. Values > 1 increase detail, values < 1 reduce detail. +lod factor = 1.0 + +# Controls only the Vertex LOD. Change in increments of 1, each change doubles (or halves) the number of vertices. Values > 0 increase detail, values < 0 reduce detail. +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 = -2 + +# Controls the resolution of composite maps. +composite map resolution = 512 + +# Controls the maximum size of composite geometry, should be >= 1.0. With low values there will be many small chunks, with high values - lesser count of bigger chunks. +max composite geometry size = 4.0 + [Fog] # If true, use extended fog parameters for distant terrain not controlled by @@ -236,12 +252,15 @@ barter disposition change is permanent = false # Uses the MCP formula (damage * (strength / 40)) to factor Strength into hand-to-hand combat. # (0 means it does not factor it in, 1 means it factors into werewolves damage calculation and -# 2 means werewolves are ignored) +# 2 means werewolves are ignored) strength influences hand to hand = 0 # Render holstered weapons (with quivers and scabbards), requires modded assets weapon sheathing = false +# Allow non-standard ammunition solely to bypass normal weapon resistance or weakness +only appropriate ammunition bypasses resistance = false + [General] # Anisotropy reduces distortion in textures at low angles (e.g. 0 to 16). @@ -339,6 +358,9 @@ invert x axis = false # Invert the vertical axis while not in GUI mode. invert y axis = false +# Enable controller support. +enable controller = true + [Saves] # Name of last character played, and default for loading save files. @@ -440,8 +462,8 @@ rtt size = 512 # Enable refraction which affects visibility through water plane. refraction = false -# Draw NPCs and creatures on water reflections. -reflect actors = false +# Draw objects on water reflections. +reflection detail = 2 # Overrides the value in '[Camera] small feature culling pixel size' specifically for water reflection/refraction textures. small feature culling pixel size = 20.0 @@ -542,6 +564,10 @@ companion h = 0.63 [Navigator] +# Enable navigator (true, false). When enabled background threads are started to build navmesh for world geometry. +# Pathfinding system uses navmesh to build paths. When disabled only pathgrid is used to build paths. +enable = true + # Scale of NavMesh coordinates to world coordinates (value > 0.0). Recastnavigation builds voxels for world geometry. # Basically voxel size is 1 / "cell size". To reduce amount of voxels we apply scale factor, to make voxel size # "recast scale factor" / "cell size". Default value calculates by this equation: @@ -643,3 +669,65 @@ enable nav mesh render = false # Render agents paths (true, false) enable agents paths render = false + +# Max number of navmesh tiles (value >= 0) +max tiles number = 512 + +[Shadows] + +# Enable or disable shadows. Bear in mind that this will force OpenMW to use shaders as if "[Shaders]/force shaders" was set to true. +enable shadows = false + +# How many shadow maps to use - more of these means each shadow map texel covers less area, producing better looking shadows, but may decrease performance. +number of shadow maps = 3 + +# If true, allow shadow maps to overlap. Counter-intuitively, will produce better results when the light is behind the camera. When enabled, OpenMW uses Cascaded Shadow Maps and when disabled, it uses Parallel Split Shadow Maps. +allow shadow map overlap = true + +# Indirectly controls where to split the shadow map(s). Values closer to 1.0 bring more detail closer to the camera (potentially excessively so), and values closer to 0.0 spread it more evenly across the whole viewing distance. 0.5 is recommended for most viewing distances by the original Parallel Split Shadow Maps paper, but this does not take into account use of a Light Space Perspective transformation, so other values may be preferable. If some of the terms used here go over your head, you might not want to change this, especially not without reading the associated papers first. When "allow shadow map overlap" is combined with a higher-than-default viewing distance, values closer to 1.0 will prevent nearby shadows losing a lot of quality. +split point uniform logarithmic ratio = 0.5 + +# Indirectly controls where to split the shadow map(s). Positive values move split points away from the camera and negative values move them towards the camera. Intended to be used in conjunction with changes to 'split point uniform logarithmic ratio' to counteract side effects, but may cause additional, more serious side effects. Read the Parallel Split Shadow Maps paper by F Zhang et al before changing. +split point bias = 0.0 + +# Enable the debug hud to see what the shadow map(s) contain. +enable debug hud = false + +# Enable the debug overlay to see where each shadow map affects. +enable debug overlay = false + +# Attempt to better use the shadow map by making them cover a smaller area. Especially helpful when looking downwards with a high viewing distance. The performance impact of this may be very large. +compute tight scene bounds = false + +# How large to make the shadow map(s). Higher values increase GPU load, but can produce better-looking results. Power-of-two values may turn out to be faster on some GPU/driver combinations. +shadow map resolution = 1024 + +# Controls the minimum near/far ratio for the Light Space Perspective Shadow Map transformation. Helps prevent too much detail being brought towards the camera at the expense of detail further from the camera. Increasing this pushes detail further away. +minimum lispsm near far ratio = 0.25 + +# Used as the factor parameter for the polygon offset used for shadow map rendering. Higher values reduce shadow flicker, but risk increasing Peter Panning. See https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glPolygonOffset.xhtml for details. +polygon offset factor = 1.1 + +# Used as the units parameter for the polygon offset used for shadow map rendering. Higher values reduce shadow flicker, but risk increasing Peter Panning. See https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glPolygonOffset.xhtml for details. +polygon offset units = 4.0 + +# How far along the surface normal to project shadow coordinates. Higher values significantly reduce shadow flicker, usually with a lower increase of Peter Panning than the Polygon Offset settings. This value is in in-game units, so 1.0 is roughly 1.4 cm. +normal offset distance = 1.0 + +# Excludes theoretically unnecessary faces from shadow maps, slightly increasing performance. In practice, Peter Panning can be much less visible with these faces included, so if you have high polygon offset values, disabling this may help minimise the side effects. +use front face culling = true + +# Allow actors to cast shadows. Potentially decreases performance. +actor shadows = false + +# Allow the player to cast shadows. Potentially decreases performance. +player shadows = false + +# Allow terrain to cast shadows. Potentially decreases performance. +terrain shadows = false + +# Allow world objects to cast shadows. Potentially decreases performance. +object shadows = false + +# Allow shadows indoors. Due to limitations with Morrowind's data, only actors can cast shadows indoors, which some might feel is distracting. +enable indoor shadows = true diff --git a/files/shaders/CMakeLists.txt b/files/shaders/CMakeLists.txt index 7baca78ef..8012c2bc1 100644 --- a/files/shaders/CMakeLists.txt +++ b/files/shaders/CMakeLists.txt @@ -18,6 +18,10 @@ set(SHADER_FILES parallax.glsl s360_fragment.glsl s360_vertex.glsl + shadows_vertex.glsl + shadows_fragment.glsl + shadowcasting_vertex.glsl + shadowcasting_fragment.glsl ) copy_all_resource_files(${CMAKE_CURRENT_SOURCE_DIR} ${OPENMW_SHADERS_ROOT} ${DDIRRELATIVE} "${SHADER_FILES}") diff --git a/files/shaders/lighting.glsl b/files/shaders/lighting.glsl index 06eebea68..782d17d4e 100644 --- a/files/shaders/lighting.glsl +++ b/files/shaders/lighting.glsl @@ -1,38 +1,66 @@ #define MAX_LIGHTS 8 -vec4 doLighting(vec3 viewPos, vec3 viewNormal, vec4 vertexColor) +uniform int colorMode; + +void perLight(out vec3 ambientOut, out vec3 diffuseOut, int lightIndex, vec3 viewPos, vec3 viewNormal, vec4 diffuse, vec3 ambient) { vec3 lightDir; - float d; + float lightDistance; -#if @colorMode == 3 - vec4 diffuse = gl_FrontMaterial.diffuse; - vec3 ambient = vertexColor.xyz; -#elif @colorMode == 2 - vec4 diffuse = vertexColor; - vec3 ambient = vertexColor.xyz; + lightDir = gl_LightSource[lightIndex].position.xyz - (viewPos.xyz * gl_LightSource[lightIndex].position.w); + lightDistance = length(lightDir); + lightDir = normalize(lightDir); + float illumination = clamp(1.0 / (gl_LightSource[lightIndex].constantAttenuation + gl_LightSource[lightIndex].linearAttenuation * lightDistance + gl_LightSource[lightIndex].quadraticAttenuation * lightDistance * lightDistance), 0.0, 1.0); + + ambientOut = ambient * gl_LightSource[lightIndex].ambient.xyz * illumination; + diffuseOut = diffuse.xyz * gl_LightSource[lightIndex].diffuse.xyz * max(dot(viewNormal.xyz, lightDir), 0.0) * illumination; +} + +#if PER_PIXEL_LIGHTING +vec4 doLighting(vec3 viewPos, vec3 viewNormal, vec4 vertexColor, float shadowing) #else - vec4 diffuse = gl_FrontMaterial.diffuse; - vec3 ambient = gl_FrontMaterial.ambient.xyz; +vec4 doLighting(vec3 viewPos, vec3 viewNormal, vec4 vertexColor, out vec3 shadowDiffuse) #endif +{ + vec4 diffuse; + vec3 ambient; + if (colorMode == 3) + { + diffuse = gl_FrontMaterial.diffuse; + ambient = vertexColor.xyz; + } + else if (colorMode == 2) + { + diffuse = vertexColor; + ambient = vertexColor.xyz; + } + else + { + diffuse = gl_FrontMaterial.diffuse; + ambient = gl_FrontMaterial.ambient.xyz; + } vec4 lightResult = vec4(0.0, 0.0, 0.0, diffuse.a); + vec3 diffuseLight, ambientLight; + perLight(ambientLight, diffuseLight, 0, viewPos, viewNormal, diffuse, ambient); +#if PER_PIXEL_LIGHTING + lightResult.xyz += diffuseLight * shadowing - diffuseLight; // This light gets added a second time in the loop to fix Mesa users' slowdown, so we need to negate its contribution here. +#else + shadowDiffuse = diffuseLight; + lightResult.xyz -= shadowDiffuse; // This light gets added a second time in the loop to fix Mesa users' slowdown, so we need to negate its contribution here. +#endif for (int i=0; i non-linear depth value and returns <0,1> linearized value @@ -163,7 +165,7 @@ void main(void) vec2 UV = worldPos.xy / (8192.0*5.0) * 3.0; UV.y *= -1.0; - float shadow = 1.0; + float shadow = unshadowedLightRatio(); vec2 screenCoords = screenCoordsPassthrough.xy / screenCoordsPassthrough.z; screenCoords.y = (1.0-screenCoords.y); @@ -288,4 +290,6 @@ void main(void) #else gl_FragData[0].w = clamp(fresnel*6.0 + specular * gl_LightSource[0].specular.w, 0.0, 1.0); //clamp(fresnel*2.0 + specular * gl_LightSource[0].specular.w, 0.0, 1.0); #endif + + applyShadowDebugOverlay(); } diff --git a/files/shaders/water_vertex.glsl b/files/shaders/water_vertex.glsl index 7d7b7b18a..575f8f3c2 100644 --- a/files/shaders/water_vertex.glsl +++ b/files/shaders/water_vertex.glsl @@ -4,14 +4,16 @@ varying vec3 screenCoordsPassthrough; varying vec4 position; varying float depthPassthrough; +#include "shadows_vertex.glsl" + void main(void) { gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; mat4 scalemat = mat4(0.5, 0.0, 0.0, 0.0, - 0.0, -0.5, 0.0, 0.0, - 0.0, 0.0, 0.5, 0.0, - 0.5, 0.5, 0.5, 1.0); + 0.0, -0.5, 0.0, 0.0, + 0.0, 0.0, 0.5, 0.0, + 0.5, 0.5, 0.5, 1.0); vec4 texcoordProj = ((scalemat) * ( gl_Position)); screenCoordsPassthrough = vec3(texcoordProj.x, texcoordProj.y, texcoordProj.w); @@ -19,4 +21,6 @@ void main(void) position = gl_Vertex; depthPassthrough = gl_Position.z; + + setupShadowCoords(gl_ModelViewMatrix * gl_Vertex, normalize((gl_NormalMatrix * gl_Normal).xyz)); } diff --git a/files/ui/advancedpage.ui b/files/ui/advancedpage.ui index 0793345c8..23c860f89 100644 --- a/files/ui/advancedpage.ui +++ b/files/ui/advancedpage.ui @@ -96,6 +96,16 @@ + + + + <html><head/><body><p>Allow non-standard ammunition solely to bypass normal weapon resistance or weakness.</p></body></html> + + + Only appropriate ammunition bypasses normal weapon resistance + + +