diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b4c6212b9..419b62bc5 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -16,8 +16,6 @@ stages: - apt-cache/ - ccache/ stage: build - rules: - - if: '$CI_PIPELINE_SOURCE != "schedule"' script: - export CCACHE_BASEDIR="`pwd`" - export CCACHE_DIR="`pwd`/ccache" && mkdir -pv "$CCACHE_DIR" @@ -27,6 +25,7 @@ stages: - cmake --build . -- -j $(nproc) - cmake --install . - if [[ "${BUILD_TESTS_ONLY}" ]]; then ./openmw_test_suite; fi + - if [[ "${BUILD_TESTS_ONLY}" ]]; then ./openmw_detournavigator_navmeshtilescache_benchmark; fi - ccache -s artifacts: paths: @@ -149,6 +148,7 @@ Debian_Clang_tests: macOS11_Xcode12: extends: .MacOS image: macos-11-xcode-12 + allow_failure: true cache: key: macOS11_Xcode12.v1 variables: @@ -157,7 +157,6 @@ macOS11_Xcode12: macOS10.15_Xcode11: extends: .MacOS image: macos-10.15-xcode-11 - allow_failure: true cache: key: macOS10.15_Xcode11.v1 variables: @@ -171,6 +170,10 @@ variables: &cs-targets targets: "openmw-cs,bsatool,esmtool,niftest" package: "CS" +variables: &tests-targets + targets: "openmw_test_suite,openmw_detournavigator_navmeshtilescache_benchmark" + package: "Tests" + .Windows_Ninja_Base: tags: - windows @@ -185,13 +188,11 @@ variables: &cs-targets - choco install python -y - refreshenv stage: build - rules: - - if: '$CI_PIPELINE_SOURCE != "schedule"' script: - $time = (Get-Date -Format "HH:mm:ss") - echo ${time} - echo "started by ${GITLAB_USER_NAME}" - - sh CI/before_script.msvc.sh -c $config -p Win64 -v 2019 -k -V -N + - sh CI/before_script.msvc.sh -c $config -p Win64 -v 2019 -k -V -N -b -t - cd MSVC2019_64_Ninja - .\ActivateMSVC.ps1 - cmake --build . --config $config --target ($targets.Split(',')) @@ -203,6 +204,7 @@ variables: &cs-targets Get-ChildItem -Recurse *.pdb | Remove-Item } - 7z a -tzip ..\..\OpenMW_MSVC2019_64_${package}_${config}_${CI_COMMIT_REF_NAME}.zip '*' + - if ($executables) { foreach ($exe in $executables.Split(',')) { & .\$exe } } after_script: - Copy-Item C:\ProgramData\chocolatey\logs\chocolatey.log cache: @@ -266,6 +268,15 @@ Windows_Ninja_CS_RelWithDebInfo: <<: *cs-targets config: "RelWithDebInfo" +Windows_Ninja_Tests_RelWithDebInfo: + extends: .Windows_Ninja_Base + stage: build + variables: + <<: *tests-targets + config: "RelWithDebInfo" + # Gitlab can't successfully execute following binaries due to unknown reason + # executables: "openmw_test_suite.exe,openmw_detournavigator_navmeshtilescache_benchmark.exe" + .Windows_MSBuild_Base: tags: - windows @@ -279,13 +290,11 @@ Windows_Ninja_CS_RelWithDebInfo: - choco install python -y - refreshenv stage: build - rules: - - if: '$CI_PIPELINE_SOURCE != "schedule"' script: - $time = (Get-Date -Format "HH:mm:ss") - echo ${time} - echo "started by ${GITLAB_USER_NAME}" - - sh CI/before_script.msvc.sh -c $config -p Win64 -v 2019 -k -V + - sh CI/before_script.msvc.sh -c $config -p Win64 -v 2019 -k -V -b -t - cd MSVC2019_64 - cmake --build . --config $config --target ($targets.Split(',')) - cd $config @@ -296,6 +305,7 @@ Windows_Ninja_CS_RelWithDebInfo: Get-ChildItem -Recurse *.pdb | Remove-Item } - 7z a -tzip ..\..\OpenMW_MSVC2019_64_${package}_${config}_${CI_COMMIT_REF_NAME}.zip '*' + - if ($executables) { foreach ($exe in $executables.Split(',')) { & .\$exe } } after_script: - Copy-Item C:\ProgramData\chocolatey\logs\chocolatey.log cache: @@ -359,6 +369,15 @@ Windows_MSBuild_CS_RelWithDebInfo: <<: *cs-targets config: "RelWithDebInfo" +Windows_MSBuild_Tests_RelWithDebInfo: + extends: .Windows_MSBuild_Base + stage: build + variables: + <<: *tests-targets + config: "RelWithDebInfo" + # Gitlab can't successfully execute following binaries due to unknown reason + # executables: "openmw_test_suite.exe,openmw_detournavigator_navmeshtilescache_benchmark.exe" + #Debian_AndroidNDK_arm64-v8a: # tags: # - linux diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 000000000..e0b39ec49 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,10 @@ +version: 2 + +sphinx: + configuration: docs/source/conf.py + +python: + version: 3.8 + install: + - requirements: docs/requirements.txt + diff --git a/.travis.yml b/.travis.yml index 8d2ab43f3..1322dfca1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,9 +22,9 @@ addons: ] matrix: include: - - name: OpenMW (all) on MacOS 10.15 with Xcode 10.2 + - name: OpenMW (all) on MacOS 10.15 with Xcode 11.6 os: osx - osx_image: xcode10.2 + osx_image: xcode11.6 - name: OpenMW (all) on Ubuntu Focal with GCC os: linux dist: focal @@ -74,7 +74,7 @@ notifications: irc: if: repository_slug = OpenMW/openmw AND branch = master channels: - - "chat.freenode.net#openmw" + - "irc.libera.chat#openmw" on_success: change on_failure: always use_notice: true diff --git a/AUTHORS.md b/AUTHORS.md index 75302908e..5ade21d41 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -222,6 +222,7 @@ Programmers Yuri Krupenin zelurker Noah Gooder + Andrew Appuhamy (andrew-app) Documentation ------------- diff --git a/CHANGELOG.md b/CHANGELOG.md index dec5e1e7f..8dacef572 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,6 @@ Bug #2069: Fireflies in Fireflies invade Morrowind look wrong Bug #2311: Targeted scripts are not properly supported on non-unique RefIDs Bug #2473: Unable to overstock merchants - Bug #2798: Mutable ESM records Bug #2976: [reopened]: Issues combining settings from the command line and both config files Bug #3137: Walking into a wall prevents jumping Bug #3372: Projectiles and magic bolts go through moving targets @@ -20,7 +19,6 @@ Bug #4039: Multiple followers should have the same following distance Bug #4055: Local scripts don't inherit variables from their base record Bug #4083: Door animation freezes when colliding with actors - Bug #4201: Projectile-projectile collision Bug #4247: Cannot walk up stairs in Ebonheart docks Bug #4357: OpenMW-CS: TopicInfos index sorting and rearranging isn't fully functional Bug #4363: OpenMW-CS: Defect in Clone Function for Dialogue Info records @@ -64,6 +62,7 @@ Bug #5452: Autowalk is being included in savegames Bug #5469: Local map is reset when re-entering certain cells Bug #5472: Mistify mod causes CTD in 0.46 on Mac + Bug #5473: OpenMW-CS: Cell border lines don't update properly on terrain change Bug #5479: NPCs who should be walking around town are standing around without walking Bug #5484: Zero value items shouldn't be able to be bought or sold for 1 gold Bug #5485: Intimidate doesn't increase disposition on marginal wins @@ -80,6 +79,7 @@ Bug #5603: Setting constant effect cast style doesn't correct effects view Bug #5604: Only one valid NIF root node is loaded from a single file Bug #5611: Usable items with "0 Uses" should be used only once + Bug #5619: Input events are queued during save loading Bug #5622: Can't properly interact with the console when in pause menu Bug #5627: Bookart not shown if it isn't followed by
statement Bug #5633: Damage Spells in effect before god mode is enabled continue to hurt the player character and can kill them @@ -87,7 +87,7 @@ Bug #5644: Summon effects running on the player during game initialization cause crashes Bug #5656: Sneaking characters block hits while standing Bug #5661: Region sounds don't play at the right interval - Bug #5675: OpenMW-cs. FRMR subrecords are saved with the wrong MastIdx + Bug #5675: OpenMW-CS: FRMR subrecords are saved with the wrong MastIdx Bug #5680: Bull Netches incorrectly aim over the player character's head and always miss Bug #5681: Player character can clip or pass through bridges instead of colliding against them Bug #5687: Bound items covering the same inventory slot expiring at the same time freezes the game @@ -100,6 +100,7 @@ Bug #5739: Saving and loading the save a second or two before hitting the ground doesn't count fall damage Bug #5758: Paralyzed actors behavior is inconsistent with vanilla Bug #5762: Movement solver is insufficiently robust + Bug #5800: Equipping a CE enchanted ring deselects an already equipped and selected enchanted ring from the spell menu Bug #5807: Video decoding crash on ARM Bug #5821: NPCs from mods getting removed if mod order was changed Bug #5835: OpenMW doesn't accept negative values for NPC's hello, alarm, fight, and flee @@ -118,14 +119,31 @@ Bug #5923: Clicking on empty spaces between journal entries might show random topics Bug #5934: AddItem command doesn't accept negative values Bug #5975: NIF controllers from sheath meshes are used + Bug #5991: Activate should always be allowed for inventory items + Bug #5995: NiUVController doesn't calculate the UV offset properly + Bug #6007: Crash when ending cutscene is playing + Bug #6016: Greeting interrupts Fargoth's sneak-walk + Bug #6022: OpenMW-CS: Terrain selection is not updated when undoing/redoing terrain changes + Bug #6023: OpenMW-CS: Clicking on a reference in "Terrain land editing" mode discards corresponding select/edit action + Bug #6028: Particle system controller values are incorrectly used + Bug #6035: OpenMW-CS: Circle brush in "Terrain land editing" mode sometimes includes vertices outside its radius + Bug #6036: OpenMW-CS: Terrain selection at the border of cells omits certain corner vertices + Bug #6043: Actor can have torch missing when torch animation is played + Bug #6047: Mouse bindings can be triggered during save loading + Bug #6136: Game freezes when NPCs try to open doors that are about to be closed + Bug #6294: Game crashes with empty pathgrid Feature #390: 3rd person look "over the shoulder" Feature #832: OpenMW-CS: Handle deleted references Feature #1536: Show more information about level on menu + Feature #2159: "Graying out" exhausted dialogue topics Feature #2386: Distant Statics in the form of Object Paging Feature #2404: Levelled List can not be placed into a container Feature #2686: Timestamps in openmw.log + Feature #2798: Mutable ESM records Feature #3171: OpenMW-CS: Instance drag selection Feature #3983: Wizard: Add link to buy Morrowind + Feature #4201: Projectile-projectile collision + Feature #4486: Handle crashes on Windows Feature #4894: Consider actors as obstacles for pathfinding Feature #4899: Alpha-To-Coverage Anti-Aliasing for alpha testing Feature #4917: Do not trigger NavMesh update when RecastMesh update should not change NavMesh @@ -138,10 +156,12 @@ Feature #5456: Basic collada animation support Feature #5457: Realistic diagonal movement Feature #5486: Fixes trainers to choose their training skills based on their base skill points + Feature #5500: Prepare enough navmesh tiles before scene loading ends Feature #5511: Add in game option to toggle HRTF support in OpenMW Feature #5519: Code Patch tab in launcher Feature #5524: Resume failed script execution after reload Feature #5545: Option to allow stealing from an unconscious NPC during combat + Feature #5551: Do not reboot PC after OpenMW installation on Windows Feature #5563: Run physics update in background thread Feature #5579: MCP SetAngle enhancement Feature #5580: Service refusal filtering @@ -156,9 +176,12 @@ Feature #5814: Bsatool should be able to create BSA archives, not only to extract it Feature #5828: Support more than 8 lights Feature #5910: Fall back to delta time when physics can't keep up + Feature #5980: Support Bullet with double precision instead of one with single precision + Feature #6024: OpenMW-CS: Selecting terrain in "Terrain land editing" should support "Add to selection" and "Remove from selection" modes + Feature #6033: Include pathgrid to navigation mesh + Feature #6034: Find path based on area cost depending on NPC stats Task #5480: Drop Qt4 support Task #5520: Improve cell name autocompleter implementation - Task #5844: Update 'toggle sneak' documentation 0.46.0 ------ @@ -168,7 +191,7 @@ Bug #2395: Duplicated plugins in the launcher when multiple data directories provide the same plugin Bug #2679: Unable to map mouse wheel under control settings Bug #2969: Scripted items can stack - Bug #2976: Data lines in global openmw.cfg take priority over user openmw.cfg + Bug #2976: [reopened in 0.47] Data lines in global openmw.cfg take priority over user openmw.cfg Bug #2987: Editor: some chance and AI data fields can overflow Bug #3006: 'else if' operator breaks script compilation Bug #3109: SetPos/Position handles actors differently @@ -186,7 +209,6 @@ Bug #4009: Launcher does not show data files on the first run after installing Bug #4077: Enchanted items are not recharged if they are not in the player's inventory Bug #4141: PCSkipEquip isn't set to 1 when reading books/scrolls - Bug #4202: Open .omwaddon files without needing toopen openmw-cs first Bug #4240: Ash storm origin coordinates and hand shielding animation behavior are incorrect Bug #4262: Rain settings are hardcoded Bug #4270: Closing doors while they are obstructed desyncs closing sfx @@ -276,7 +298,6 @@ Bug #4964: Multiple effect spell projectile sounds play louder than vanilla Bug #4965: Global light attenuation settings setup is lacking Bug #4969: "Miss" sound plays for any actor - Bug #4971: OpenMW-CS: Make rotations display as degrees instead of radians Bug #4972: Player is able to use quickkeys while disableplayerfighting is active Bug #4979: AiTravel maximum range depends on "actors processing range" setting Bug #4980: Drowning mechanics is applied for actors indifferently from distance to player @@ -374,7 +395,6 @@ Bug #5350: An attempt to launch magic bolt causes "AL error invalid value" error Bug #5352: Light source items' duration is decremented while they aren't visible Feature #1724: Handle AvoidNode - Feature #2159: "Graying out" exhausted dialogue topics Feature #2229: Improve pathfinding AI Feature #3025: Analogue gamepad movement controls Feature #3442: Default values for fallbacks from ini file @@ -387,6 +407,7 @@ Feature #4001: Toggle sneak controller shortcut Feature #4068: OpenMW-CS: Add a button to reset key bindings to defaults Feature #4129: Beta Comment to File + Feature #4202: Open .omwaddon files without needing to open openmw-cs first Feature #4209: Editor: Faction rank sub-table Feature #4255: Handle broken RepairedOnMe script function Feature #4316: Implement RaiseRank/LowerRank functions properly @@ -409,6 +430,7 @@ Feature #4958: Support eight blood types Feature #4962: Add casting animations for magic items Feature #4968: Scalable UI widget skins + Feature #4971: OpenMW-CS: Make rotations display as degrees instead of radians Feature #4994: Persistent pinnable windows hiding Feature #5000: Compressed BSA format support Feature #5005: Editor: Instance window via Scene window diff --git a/CHANGELOG_PR.md b/CHANGELOG_PR.md index cfe709e97..100bc376f 100644 --- a/CHANGELOG_PR.md +++ b/CHANGELOG_PR.md @@ -38,9 +38,15 @@ Editor Bug Fixes: - Disabled record sorting in Topic and Journal Info tables, implemented drag-move for records (#4357) - Topic and Journal Info records can now be cloned with a different parent Topic/Journal Id (#4363) - Verifier no longer checks for alleged 'race' entries in clothing body parts (#5400) +- Cell borders are now properly redrawn when undoing/redoing terrain changes (#5473) - Loading mods now keeps the master index (#5675) - Flicker and crashing on XFCE4 fixed (#5703) - Collada models render properly in the Editor (#5713) +- Terrain-selection grid is now properly updated when undoing/redoing terrain changes (#6022) +- Tool outline and select/edit actions in "Terrain land editing" mode now ignore references (#6023) +- Primary-select and secondary-select actions in "Terrain land editing" mode now behave like in "Instance editing" mode (#6024) +- Using the circle brush to select terrain in the "Terrain land editing" mode no longer selects vertices outside the circle (#6035) +- Vertices at the NW and SE corners of a cell can now also be selected in "Terrain land editing" mode if the adjacent cells aren't loaded yet (#6036) Miscellaneous: - Prevent save-game bloating by using an appropriate fog texture format (#5108) diff --git a/CI/activate_msvc.sh b/CI/activate_msvc.sh index 47f2c246f..233f01743 100644 --- a/CI/activate_msvc.sh +++ b/CI/activate_msvc.sh @@ -30,55 +30,11 @@ command -v unixPathAsWindows >/dev/null 2>&1 || function unixPathAsWindows { fi } -function windowsSystemPathAsUnix { - if command -v cygpath >/dev/null 2>&1; then - cygpath -u -p $1 - else - IFS=';' read -r -a paths <<< "$1" - declare -a convertedPaths - for entry in paths; do - convertedPaths+=(windowsPathAsUnix $entry) - done - convertedPath=printf ":%s" ${convertedPaths[@]} - echo ${convertedPath:1} - fi -} - -# capture CMD environment so we know what's been changed -declare -A originalCmdEnv -originalIFS="$IFS" -IFS=$'\n\r' -for pair in $(cmd //c "set"); do - IFS='=' read -r -a separatedPair <<< "${pair}" - if [ ${#separatedPair[@]} -ne 2 ]; then - echo "Parsed '$pair' as ${#separatedPair[@]} parts, expected 2." - continue - fi - originalCmdEnv["${separatedPair[0]}"]="${separatedPair[1]}" -done # capture CMD environment in a shell with MSVC activated -cmdEnv="$(cmd //c "$(unixPathAsWindows "$(dirname "${BASH_SOURCE[0]}")")\ActivateMSVC.bat" "&&" set)" - -declare -A cmdEnvChanges -for pair in $cmdEnv; do - if [ -n "$pair" ]; then - IFS='=' read -r -a separatedPair <<< "${pair}" - if [ ${#separatedPair[@]} -ne 2 ]; then - echo "Parsed '$pair' as ${#separatedPair[@]} parts, expected 2." - continue - fi - key="${separatedPair[0]}" - value="${separatedPair[1]}" - if ! [ ${originalCmdEnv[$key]+_} ] || [ "${originalCmdEnv[$key]}" != "$value" ]; then - if [ $key != 'PATH' ] && [ $key != 'path' ] && [ $key != 'Path' ]; then - export "$key=$value" - else - export PATH=$(windowsSystemPathAsUnix $value) - fi - fi - fi -done +cmd //c "$(unixPathAsWindows "$(dirname "${BASH_SOURCE[0]}")")\ActivateMSVC.bat" "&&" "bash" "-c" "declare -px > declared_env.sh" +source ./declared_env.sh +rm declared_env.sh MISSINGTOOLS=0 @@ -93,6 +49,4 @@ if [ $MISSINGTOOLS -ne 0 ]; then return 1 fi -IFS="$originalIFS" - -restoreOldSettings \ No newline at end of file +restoreOldSettings diff --git a/CI/before_install.osx.sh b/CI/before_install.osx.sh index c03341b86..1ca0fc611 100755 --- a/CI/before_install.osx.sh +++ b/CI/before_install.osx.sh @@ -1,9 +1,9 @@ #!/bin/sh -ex # workaround python issue on travis -[-z "${TRAVIS}"] && HOMEBREW_NO_AUTO_UPDATE=1 brew uninstall --ignore-dependencies python@3.8 || true -[-z "${TRAVIS}"] && HOMEBREW_NO_AUTO_UPDATE=1 brew uninstall --ignore-dependencies python@3.9 || true -[-z "${TRAVIS}"] && HOMEBREW_NO_AUTO_UPDATE=1 brew uninstall --ignore-dependencies qt@6 || true +[ -z "${TRAVIS}" ] && HOMEBREW_NO_AUTO_UPDATE=1 brew uninstall --ignore-dependencies python@3.8 || true +[ -z "${TRAVIS}" ] && HOMEBREW_NO_AUTO_UPDATE=1 brew uninstall --ignore-dependencies python@3.9 || true +[ -z "${TRAVIS}" ] && HOMEBREW_NO_AUTO_UPDATE=1 brew uninstall --ignore-dependencies qt@6 || true # Some of these tools can come from places other than brew, so check before installing command -v ccache >/dev/null 2>&1 || brew install ccache @@ -15,5 +15,8 @@ ccache --version cmake --version qmake --version -curl -fSL -R -J https://downloads.openmw.org/osx/dependencies/openmw-deps-f8918dd.zip -o ~/openmw-deps.zip +curl -fSL -R -J https://gitlab.com/OpenMW/openmw-deps/-/raw/main/macos/openmw-deps-20210617.zip -o ~/openmw-deps.zip unzip -o ~/openmw-deps.zip -d /private/tmp/openmw-deps > /dev/null + +# additional libraries +[ -z "${TRAVIS}" ] && HOMEBREW_NO_AUTO_UPDATE=1 brew install fontconfig \ No newline at end of file diff --git a/CI/before_script.linux.sh b/CI/before_script.linux.sh index 5f74b4714..17292e4e9 100755 --- a/CI/before_script.linux.sh +++ b/CI/before_script.linux.sh @@ -4,9 +4,14 @@ set -xeo pipefail free -m +BUILD_UNITTESTS=OFF +BUILD_BENCHMARKS=OFF + if [[ "${BUILD_TESTS_ONLY}" ]]; then export GOOGLETEST_DIR="${PWD}/googletest/build/install" env GENERATOR='Unix Makefiles' CONFIGURATION=Release CI/build_googletest.sh + BUILD_UNITTESTS=ON + BUILD_BENCHMARKS=ON fi declare -a CMAKE_CONF_OPTS=( @@ -43,7 +48,8 @@ if [[ "${BUILD_TESTS_ONLY}" ]]; then -DBUILD_ESSIMPORTER=OFF \ -DBUILD_OPENCS=OFF \ -DBUILD_WIZARD=OFF \ - -DBUILD_UNITTESTS=ON \ + -DBUILD_UNITTESTS=${BUILD_UNITTESTS} \ + -DBUILD_BENCHMARKS=${BUILD_BENCHMARKS} \ -DGTEST_ROOT="${GOOGLETEST_DIR}" \ -DGMOCK_ROOT="${GOOGLETEST_DIR}" \ .. diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh old mode 100644 new mode 100755 index 478c40144..1cf510e34 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -73,10 +73,7 @@ CONFIGURATIONS=() TEST_FRAMEWORK="" GOOGLE_INSTALL_ROOT="" INSTALL_PREFIX="." -BULLET_DOUBLE=true -BULLET_DBL="" -BULLET_DBL_DISPLAY="Single precision" -SKIP_VR="" +BUILD_BENCHMARKS="" ACTIVATE_MSVC="" SINGLE_CONFIG="" @@ -103,9 +100,6 @@ while [ $# -gt 0 ]; do d ) SKIP_DOWNLOAD=true ;; - D ) - BULLET_DOUBLE=true ;; - e ) SKIP_EXTRACT=true ;; @@ -143,6 +137,9 @@ while [ $# -gt 0 ]; do INSTALL_PREFIX=$(echo "$1" | sed 's;\\;/;g' | sed -E 's;/+;/;g') shift ;; + b ) + BUILD_BENCHMARKS=true ;; + h ) cat </dev/null 2>&1 || { echo "Error: vswhere is not on the path."; wrappedExit 1; } diff --git a/CI/before_script.osx.sh b/CI/before_script.osx.sh index f9191eb89..265e05b8e 100755 --- a/CI/before_script.osx.sh +++ b/CI/before_script.osx.sh @@ -25,6 +25,5 @@ cmake \ -D BUILD_BSATOOL=TRUE \ -D BUILD_ESSIMPORTER=TRUE \ -D BUILD_NIFTEST=TRUE \ --D BULLET_USE_DOUBLES=TRUE \ -G"Unix Makefiles" \ .. diff --git a/CI/install_debian_deps.sh b/CI/install_debian_deps.sh index 1f35ccebc..2f905314b 100755 --- a/CI/install_debian_deps.sh +++ b/CI/install_debian_deps.sh @@ -21,14 +21,13 @@ declare -rA GROUPED_DEPS=( libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev libsdl2-dev libqt5opengl5-dev libopenal-dev libunshield-dev libtinyxml-dev - libbullet-dev liblz4-dev libpng-dev libjpeg-dev - libxcb-glx0-dev libx11-dev + libbullet-dev liblz4-dev libpng-dev libjpeg-dev + ca-certificates " # TODO: add librecastnavigation-dev when debian is ready # These dependencies can alternatively be built and linked statically. [openmw-deps-dynamic]="libmygui-dev libopenscenegraph-dev" - [coverity]="curl" # Pre-requisites for building MyGUI and OSG for static linking. @@ -65,4 +64,4 @@ export APT_CACHE_DIR="${PWD}/apt-cache" set -x mkdir -pv "$APT_CACHE_DIR" apt-get update -yq -apt-get -q -o dir::cache::archives="$APT_CACHE_DIR" install -y "${deps[@]}" +apt-get -q -o dir::cache::archives="$APT_CACHE_DIR" install -y --no-install-recommends "${deps[@]}" diff --git a/CMakeLists.txt b/CMakeLists.txt index e322497a6..6e8c5f870 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,9 +13,6 @@ if(POLICY CMP0083) cmake_policy(SET CMP0083 NEW) endif() -# Detect OS -include(cmake/OSIdentity.cmake) - option(OPENMW_GL4ES_MANUAL_INIT "Manually initialize gl4es. This is more reliable on platforms without a windowing system. Requires gl4es to be configured with -DNOEGL=ON -DNO_LOADER=ON -DNO_INIT_CONSTRUCTOR=ON." OFF) if(OPENMW_GL4ES_MANUAL_INIT) add_definitions(-DOPENMW_GL4ES_MANUAL_INIT) @@ -35,7 +32,7 @@ option(BUILD_DOCS "Build documentation." OFF ) option(BUILD_OPENMW_VR "Build VR support using OpenXR" ON) option(BUILD_WITH_CODE_COVERAGE "Enable code coverage with gconv" OFF) option(BUILD_UNITTESTS "Enable Unittests with Google C++ Unittest" OFF) -option(BULLET_USE_DOUBLES "Use double precision for Bullet" ON) +option(BUILD_BENCHMARKS "Build benchmarks with Google Benchmark" OFF) set(OpenGL_GL_PREFERENCE LEGACY) # Use LEGACY as we use GL2; GLNVD is for GL3 and up. @@ -265,16 +262,16 @@ if(FFmpeg_FOUND) set(FFVER_OK FALSE) endif() endif() + + if(NOT FFVER_OK AND NOT APPLE) # unable to detect on version on MacOS < 11.0 + message(FATAL_ERROR "FFmpeg version is too old, 3.2 is required" ) + endif() endif() if(NOT FFmpeg_FOUND) message(FATAL_ERROR "FFmpeg was not found" ) endif() -if(NOT FFVER_OK) - message(FATAL_ERROR "FFmpeg version is too old, 3.2 is required" ) -endif() - if(WIN32) message("Can not detect FFmpeg version, at least the 3.2 is required" ) endif() @@ -313,7 +310,31 @@ if(OPENMW_USE_SYSTEM_BULLET) set(REQUIRED_BULLET_VERSION 283) # but for build testing, 283 is fine endif() - find_package(Bullet ${REQUIRED_BULLET_VERSION} REQUIRED COMPONENTS BulletCollision LinearMath) + # First, try BulletConfig-float64.cmake which comes with Debian derivatives. + # This file does not define the Bullet version in a CMake-friendly way. + find_package(Bullet CONFIGS BulletConfig-float64.cmake QUIET COMPONENTS BulletCollision LinearMath) + if (BULLET_FOUND) + string(REPLACE "." "" _bullet_version_num ${BULLET_VERSION_STRING}) + if (_bullet_version_num VERSION_LESS REQUIRED_BULLET_VERSION) + message(FATAL_ERROR "System bullet version too old, OpenMW requires at least ${REQUIRED_BULLET_VERSION}, got ${_bullet_version_num}") + endif() + # Fix the relative include: + set(BULLET_INCLUDE_DIRS "${BULLET_ROOT_DIR}/${BULLET_INCLUDE_DIRS}") + include(FindPackageMessage) + find_package_message(Bullet "Found Bullet: ${BULLET_LIBRARIES} ${BULLET_VERSION_STRING}" "${BULLET_VERSION_STRING}-float64") + else() + find_package(Bullet ${REQUIRED_BULLET_VERSION} REQUIRED COMPONENTS BulletCollision LinearMath) + endif() + + # Only link the Bullet libraries that we need: + string(REGEX MATCHALL "((optimized|debug);)?[^;]*(BulletCollision|LinearMath)[^;]*" BULLET_LIBRARIES "${BULLET_LIBRARIES}") + + include(cmake/CheckBulletPrecision.cmake) + if (HAS_DOUBLE_PRECISION_BULLET) + message(STATUS "Bullet uses double precision") + else() + message(FATAL_ERROR "Bullet does not uses double precision") + endif() endif() if (NOT WIN32 AND BUILD_WIZARD) # windows users can just run the morrowind installer @@ -342,6 +363,11 @@ endif() if(OPENMW_USE_SYSTEM_OSG) find_package(OpenSceneGraph ${OSG_VERSION_REQUIRED} REQUIRED ${USED_OSG_COMPONENTS}) + + if (${OPENSCENEGRAPH_VERSION} VERSION_GREATER 3.6.2 AND ${OPENSCENEGRAPH_VERSION} VERSION_LESS 3.6.5) + message(FATAL_ERROR "OpenSceneGraph version ${OPENSCENEGRAPH_VERSION} has critical regressions which cause crashes. Please upgrade to 3.6.5 or later. We strongly recommend using the tip of the official 'OpenSceneGraph-3.6' branch or the tip of '3.6' OpenMW/osg (OSGoS).") + endif() + if(OSG_STATIC) find_package(OSGPlugins REQUIRED COMPONENTS ${USED_OSG_PLUGINS}) endif() @@ -424,8 +450,8 @@ endif (APPLE) # Other files -configure_resource_file(${OpenMW_SOURCE_DIR}/files/settings-default.cfg - "${OpenMW_BINARY_DIR}" "settings-default.cfg") +pack_resource_file(${OpenMW_SOURCE_DIR}/files/settings-default.cfg + "${OpenMW_BINARY_DIR}" "defaults.bin") configure_resource_file(${OpenMW_SOURCE_DIR}/files/settings-overrides-vr.cfg "${OpenMW_BINARY_DIR}" "settings-overrides-vr.cfg") @@ -443,8 +469,8 @@ else () "${OpenMW_BINARY_DIR}/openmw.cfg") endif () -configure_resource_file(${OpenMW_SOURCE_DIR}/files/openmw-cs.cfg - "${OpenMW_BINARY_DIR}" "openmw-cs.cfg") +pack_resource_file(${OpenMW_SOURCE_DIR}/files/openmw-cs.cfg + "${OpenMW_BINARY_DIR}" "defaults-cs.bin") # Needs the copy version because the configure version assumes the end of the file has been reached when a null character is reached and there are no CMake expressions to evaluate. copy_resource_file(${OpenMW_SOURCE_DIR}/files/opencs/defaultfilters @@ -499,16 +525,13 @@ if (CMAKE_CXX_COMPILER_ID STREQUAL GNU OR CMAKE_CXX_COMPILER_ID STREQUAL Clang) if (CMAKE_CXX_COMPILER_ID STREQUAL GNU AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 4.6 OR CMAKE_CXX_COMPILER_VERSION VERSION_EQUAL 4.6) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unused-but-set-parameter") endif() - - if (CMAKE_CXX_COMPILER_ID STREQUAL GNU AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 5.0 OR CMAKE_CXX_COMPILER_VERSION VERSION_EQUAL 5.0) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wsuggest-override") - endif() endif (CMAKE_CXX_COMPILER_ID STREQUAL GNU OR CMAKE_CXX_COMPILER_ID STREQUAL Clang) # Extern add_subdirectory (extern/osg-ffmpeg-videoplayer) add_subdirectory (extern/oics) +add_subdirectory (extern/Base64) if (BUILD_OPENCS) add_subdirectory (extern/osgQt) endif() @@ -559,6 +582,10 @@ if (BUILD_UNITTESTS) add_subdirectory( apps/openmw_test_suite ) endif() +if (BUILD_BENCHMARKS) + add_subdirectory(apps/benchmarks) +endif() + if (WIN32) if (MSVC) if (OPENMW_MP_BUILD) @@ -606,63 +633,13 @@ if (WIN32) # Play a bit with the warning levels - set(WARNINGS "/Wall") # Since windows can only disable specific warnings, not enable them + set(WARNINGS "/W4") set(WARNINGS_DISABLE - # Warnings that aren't enabled normally and don't need to be enabled - # They're unneeded and sometimes completely retarded warnings that /Wall enables - # Not going to bother commenting them as they tend to warn on every standard library file - 4061 4263 4264 4266 4350 4371 4435 4514 4548 4571 4582 4583 4610 4619 4623 4625 - 4626 4628 4640 4668 4710 4711 4768 4820 4826 4917 4946 5032 5039 5045 5219 5220 - - # Warnings that are thrown on standard libraries and not OpenMW - 4347 # Non-template function with same name and parameter count as template function - 4365 # Variable signed/unsigned mismatch - 4510 4512 # Unable to generate copy constructor/assignment operator as it's not public in the base - 4706 # Assignment in conditional expression - 4738 # Storing 32-bit float result in memory, possible loss of performance - 4774 # Format string expected in argument is not a string literal - 4986 # Undocumented warning that occurs in the crtdbg.h file - 4987 # nonstandard extension used (triggered by setjmp.h) - 4996 # Function was declared deprecated - - # caused by OSG - 4589 # Constructor of abstract class 'osg::Operation' ignores initializer for virtual base class 'osg::Referenced' (False warning) - - # caused by boost - 4191 # 'type cast' : unsafe conversion (1.56, thread_primitives.hpp, normally off) - 4643 # Forward declaring 'X' in namespace std is not permitted by the C++ Standard. (in *_std_fwd.h files) - 5204 # Class has virtual functions, but its trivial destructor is not virtual - - # caused by MyGUI - 4297 # function assumed not to throw an exception but does - - # OpenMW specific warnings - 4099 # Type mismatch, declared class or struct is defined with other type 4100 # Unreferenced formal parameter (-Wunused-parameter) - 4101 # Unreferenced local variable (-Wunused-variable) 4127 # Conditional expression is constant - 4242 # Storing value in a variable of a smaller type, possible loss of data - 4244 # Storing value of one type in variable of another (size_t in int, for example) - 4245 # Signed/unsigned mismatch - 4267 # Conversion from 'size_t' to 'int', possible loss of data - 4305 # Truncating value (double to float, for example) - 4309 # Variable overflow, trying to store 128 in a signed char for example - 4351 # New behavior: elements of array 'array' will be default initialized (desired behavior) - 4355 # Using 'this' in member initialization list - 4464 # relative include path contains '..' - 4505 # Unreferenced local function has been removed - 4701 # Potentially uninitialized local variable used - 4702 # Unreachable code - 4714 # function 'QString QString::trimmed(void) &&' marked as __forceinline not inlined - 4800 # Boolean optimization warning, e.g. myBool = (myInt != 0) instead of myBool = myInt - ) - - if (MSVC_VERSION GREATER 1800) - set(WARNINGS_DISABLE ${WARNINGS_DISABLE} 5026 5027 - 5031 # #pragma warning(pop): likely mismatch, popping warning state pushed in different file (config_begin.hpp, config_end.hpp) + 4996 # Function was declared deprecated ) - endif() if( "${MyGUI_VERSION}" VERSION_LESS_EQUAL "3.4.0" ) set(WARNINGS_DISABLE ${WARNINGS_DISABLE} @@ -730,6 +707,10 @@ if (WIN32) if (BUILD_WIZARD) set_target_properties(openmw-wizard PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}") endif() + + if (BUILD_BENCHMARKS) + set_target_properties(openmw_detournavigator_navmeshtilescache_benchmark PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}") + endif() endif(MSVC) # TODO: At some point release builds should not use the console but rather write to a log file @@ -869,7 +850,7 @@ elseif(NOT APPLE) INSTALL(FILES "${OpenMW_SOURCE_DIR}/README.md" DESTINATION "." RENAME "README.txt") INSTALL(FILES "${OpenMW_SOURCE_DIR}/LICENSE" DESTINATION "." RENAME "LICENSE.txt") INSTALL(FILES "${OpenMW_SOURCE_DIR}/files/mygui/DejaVuFontLicense.txt" DESTINATION ".") - INSTALL(FILES "${INSTALL_SOURCE}/settings-default.cfg" DESTINATION ".") + INSTALL(FILES "${INSTALL_SOURCE}/defaults.bin" DESTINATION ".") INSTALL(FILES "${INSTALL_SOURCE}/gamecontrollerdb.txt" DESTINATION ".") INSTALL(FILES "${INSTALL_SOURCE}/xrcontrollersuggestions.xml" DESTINATION ".") @@ -912,13 +893,13 @@ elseif(NOT APPLE) SET(VCREDIST32 "${OpenMW_BINARY_DIR}/vcredist_x86.exe") if(EXISTS ${VCREDIST32}) INSTALL(FILES ${VCREDIST32} DESTINATION "redist") - SET(CPACK_NSIS_EXTRA_INSTALL_COMMANDS "ExecWait '\\\"$INSTDIR\\\\redist\\\\vcredist_x86.exe\\\" /q'" ) + SET(CPACK_NSIS_EXTRA_INSTALL_COMMANDS "ExecWait '\\\"$INSTDIR\\\\redist\\\\vcredist_x86.exe\\\" /q /norestart'" ) endif(EXISTS ${VCREDIST32}) SET(VCREDIST64 "${OpenMW_BINARY_DIR}/vcredist_x64.exe") if(EXISTS ${VCREDIST64}) INSTALL(FILES ${VCREDIST64} DESTINATION "redist") - SET(CPACK_NSIS_EXTRA_INSTALL_COMMANDS "ExecWait '\\\"$INSTDIR\\\\redist\\\\vcredist_x64.exe\\\" /q'" ) + SET(CPACK_NSIS_EXTRA_INSTALL_COMMANDS "ExecWait '\\\"$INSTDIR\\\\redist\\\\vcredist_x64.exe\\\" /q /norestart'" ) endif(EXISTS ${VCREDIST64}) SET(OALREDIST "${OpenMW_BINARY_DIR}/oalinst.exe") @@ -978,14 +959,14 @@ elseif(NOT APPLE) ENDIF(BUILD_OPENCS) # Install global configuration files - INSTALL(FILES "${INSTALL_SOURCE}/settings-default.cfg" DESTINATION "${SYSCONFDIR}" COMPONENT "openmw") + INSTALL(FILES "${INSTALL_SOURCE}/defaults.bin" DESTINATION "${SYSCONFDIR}" COMPONENT "openmw") INSTALL(FILES "${INSTALL_SOURCE}/openmw.cfg.install" DESTINATION "${SYSCONFDIR}" RENAME "openmw.cfg" COMPONENT "openmw") INSTALL(FILES "${INSTALL_SOURCE}/resources/version" DESTINATION "${SYSCONFDIR}" COMPONENT "openmw") INSTALL(FILES "${INSTALL_SOURCE}/gamecontrollerdb.txt" DESTINATION "${SYSCONFDIR}" COMPONENT "openmw") INSTALL(FILES "${INSTALL_SOURCE}/xrcontrollersuggestions.xml" DESTINATION "${SYSCONFDIR}" COMPONENT "openmw") IF(BUILD_OPENCS) - INSTALL(FILES "${INSTALL_SOURCE}/openmw-cs.cfg" DESTINATION "${SYSCONFDIR}" COMPONENT "opencs") + INSTALL(FILES "${INSTALL_SOURCE}/defaults-cs.bin" DESTINATION "${SYSCONFDIR}" COMPONENT "opencs") ENDIF(BUILD_OPENCS) # Install resources diff --git a/README.md b/README.md index c49e863a6..4d1ea740f 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ OpenMW also comes with OpenMW-CS, a replacement for Bethesda's Construction Set. * Version: 0.47.0 * License: GPLv3 (see [LICENSE](https://github.com/OpenMW/openmw/blob/master/LICENSE) for more information) * Website: https://www.openmw.org -* IRC: #openmw on irc.freenode.net +* IRC: #openmw on irc.libera.chat Font Licenses: * DejaVuLGCSansMono.ttf: custom (see [files/mygui/DejaVuFontLicense.txt](https://github.com/OpenMW/openmw/blob/master/files/mygui/DejaVuFontLicense.txt) for more information) diff --git a/apps/benchmarks/CMakeLists.txt b/apps/benchmarks/CMakeLists.txt new file mode 100644 index 000000000..f9aa9aad4 --- /dev/null +++ b/apps/benchmarks/CMakeLists.txt @@ -0,0 +1,27 @@ +cmake_minimum_required(VERSION 3.11) + +set(BENCHMARK_ENABLE_TESTING OFF) +set(BENCHMARK_ENABLE_INSTALL OFF) +set(BENCHMARK_ENABLE_GTEST_TESTS OFF) + +set(SAVED_CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") + +string(REPLACE "-Wsuggest-override" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") +string(REPLACE "-Wundef" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") +include(FetchContent) +FetchContent_Declare(benchmark + URL https://github.com/google/benchmark/archive/refs/tags/v1.5.2.zip + URL_HASH MD5=49395b757a7c4656d70f1328d93efd00 + SOURCE_DIR fetched/benchmark +) +FetchContent_MakeAvailableExcludeFromAll(benchmark) + +set(CMAKE_CXX_FLAGS "${SAVED_CMAKE_CXX_FLAGS}") + +openmw_add_executable(openmw_detournavigator_navmeshtilescache_benchmark detournavigator/navmeshtilescache.cpp) +target_compile_features(openmw_detournavigator_navmeshtilescache_benchmark PRIVATE cxx_std_17) +target_link_libraries(openmw_detournavigator_navmeshtilescache_benchmark benchmark::benchmark components) + +if (UNIX AND NOT APPLE) + target_link_libraries(openmw_detournavigator_navmeshtilescache_benchmark ${CMAKE_THREAD_LIBS_INIT}) +endif() diff --git a/apps/benchmarks/detournavigator/navmeshtilescache.cpp b/apps/benchmarks/detournavigator/navmeshtilescache.cpp new file mode 100644 index 000000000..2c7a981ad --- /dev/null +++ b/apps/benchmarks/detournavigator/navmeshtilescache.cpp @@ -0,0 +1,214 @@ +#include + +#include + +#include +#include +#include + +namespace +{ + using namespace DetourNavigator; + + struct Key + { + osg::Vec3f mAgentHalfExtents; + TilePosition mTilePosition; + RecastMesh mRecastMesh; + std::vector mOffMeshConnections; + }; + + struct Item + { + Key mKey; + NavMeshData mValue; + }; + + template + TilePosition generateTilePosition(int max, Random& random) + { + std::uniform_int_distribution distribution(0, max); + return TilePosition(distribution(random), distribution(random)); + } + + template + osg::Vec3f generateAgentHalfExtents(float min, float max, Random& random) + { + std::uniform_int_distribution distribution(min, max); + return osg::Vec3f(distribution(random), distribution(random), distribution(random)); + } + + template + void generateVertices(OutputIterator out, std::size_t number, Random& random) + { + std::uniform_real_distribution distribution(0.0, 1.0); + std::generate_n(out, 3 * (number - number % 3), [&] { return distribution(random); }); + } + + template + void generateIndices(OutputIterator out, int max, std::size_t number, Random& random) + { + std::uniform_int_distribution distribution(0, max); + std::generate_n(out, number - number % 3, [&] { return distribution(random); }); + } + + AreaType toAreaType(int index) + { + switch (index) + { + case 0: return AreaType_null; + case 1: return AreaType_water; + case 2: return AreaType_door; + case 3: return AreaType_pathgrid; + case 4: return AreaType_ground; + } + return AreaType_null; + } + + template + AreaType generateAreaType(Random& random) + { + std::uniform_int_distribution distribution(0, 4); + return toAreaType(distribution(random));; + } + + template + void generateAreaTypes(OutputIterator out, std::size_t triangles, Random& random) + { + std::generate_n(out, triangles, [&] { return generateAreaType(random); }); + } + + template + void generateWater(OutputIterator out, std::size_t count, Random& random) + { + std::uniform_real_distribution distribution(0.0, 1.0); + std::generate_n(out, count, [&] { + const btVector3 shift(distribution(random), distribution(random), distribution(random)); + return RecastMesh::Water {1, btTransform(btMatrix3x3::getIdentity(), shift)}; + }); + } + + template + void generateOffMeshConnection(OutputIterator out, std::size_t count, Random& random) + { + std::uniform_real_distribution distribution(0.0, 1.0); + std::generate_n(out, count, [&] { + const osg::Vec3f start(distribution(random), distribution(random), distribution(random)); + const osg::Vec3f end(distribution(random), distribution(random), distribution(random)); + return OffMeshConnection {start, end, generateAreaType(random)}; + }); + } + + template + Key generateKey(std::size_t triangles, Random& random) + { + const osg::Vec3f agentHalfExtents = generateAgentHalfExtents(0.5, 1.5, random); + const TilePosition tilePosition = generateTilePosition(10000, random); + const std::size_t generation = std::uniform_int_distribution(0, 100)(random); + const std::size_t revision = std::uniform_int_distribution(0, 10000)(random); + std::vector vertices; + generateVertices(std::back_inserter(vertices), triangles * 1.98, random); + std::vector indices; + generateIndices(std::back_inserter(indices), static_cast(vertices.size() / 3) - 1, vertices.size() * 1.53, random); + std::vector areaTypes; + generateAreaTypes(std::back_inserter(areaTypes), indices.size() / 3, random); + std::vector water; + generateWater(std::back_inserter(water), 2, random); + RecastMesh recastMesh(generation, revision, std::move(indices), std::move(vertices), + std::move(areaTypes), std::move(water)); + std::vector offMeshConnections; + generateOffMeshConnection(std::back_inserter(offMeshConnections), 300, random); + return Key {agentHalfExtents, tilePosition, std::move(recastMesh), std::move(offMeshConnections)}; + } + + constexpr std::size_t trianglesPerTile = 310; + + template + void generateKeys(OutputIterator out, std::size_t count, Random& random) + { + std::generate_n(out, count, [&] { return generateKey(trianglesPerTile, random); }); + } + + template + void fillCache(OutputIterator out, Random& random, NavMeshTilesCache& cache) + { + std::size_t size = cache.getStats().mNavMeshCacheSize; + + while (true) + { + Key key = generateKey(trianglesPerTile, random); + cache.set(key.mAgentHalfExtents, key.mTilePosition, key.mRecastMesh, key.mOffMeshConnections, NavMeshData()); + *out++ = std::move(key); + const std::size_t newSize = cache.getStats().mNavMeshCacheSize; + if (size >= newSize) + break; + size = newSize; + } + } + + template + void getFromFilledCache(benchmark::State& state) + { + NavMeshTilesCache cache(maxCacheSize); + std::minstd_rand random; + std::vector keys; + fillCache(std::back_inserter(keys), random, cache); + generateKeys(std::back_inserter(keys), keys.size() * (100 - hitPercentage) / 100, random); + std::size_t n = 0; + + while (state.KeepRunning()) + { + const auto& key = keys[n++ % keys.size()]; + const auto result = cache.get(key.mAgentHalfExtents, key.mTilePosition, key.mRecastMesh, key.mOffMeshConnections); + benchmark::DoNotOptimize(result); + } + } + + constexpr auto getFromFilledCache_1m_100hit = getFromFilledCache<1 * 1024 * 1024, 100>; + constexpr auto getFromFilledCache_4m_100hit = getFromFilledCache<4 * 1024 * 1024, 100>; + constexpr auto getFromFilledCache_16m_100hit = getFromFilledCache<16 * 1024 * 1024, 100>; + constexpr auto getFromFilledCache_64m_100hit = getFromFilledCache<64 * 1024 * 1024, 100>; + constexpr auto getFromFilledCache_1m_70hit = getFromFilledCache<1 * 1024 * 1024, 70>; + constexpr auto getFromFilledCache_4m_70hit = getFromFilledCache<4 * 1024 * 1024, 70>; + constexpr auto getFromFilledCache_16m_70hit = getFromFilledCache<16 * 1024 * 1024, 70>; + constexpr auto getFromFilledCache_64m_70hit = getFromFilledCache<64 * 1024 * 1024, 70>; + + template + void setToBoundedNonEmptyCache(benchmark::State& state) + { + NavMeshTilesCache cache(maxCacheSize); + std::minstd_rand random; + std::vector keys; + fillCache(std::back_inserter(keys), random, cache); + generateKeys(std::back_inserter(keys), keys.size() * 2, random); + std::reverse(keys.begin(), keys.end()); + std::size_t n = 0; + + while (state.KeepRunning()) + { + const auto& key = keys[n++ % keys.size()]; + const auto result = cache.set(key.mAgentHalfExtents, key.mTilePosition, key.mRecastMesh, key.mOffMeshConnections, NavMeshData()); + benchmark::DoNotOptimize(result); + } + } + + constexpr auto setToBoundedNonEmptyCache_1m = setToBoundedNonEmptyCache<1 * 1024 * 1024>; + constexpr auto setToBoundedNonEmptyCache_4m = setToBoundedNonEmptyCache<4 * 1024 * 1024>; + constexpr auto setToBoundedNonEmptyCache_16m = setToBoundedNonEmptyCache<16 * 1024 * 1024>; + constexpr auto setToBoundedNonEmptyCache_64m = setToBoundedNonEmptyCache<64 * 1024 * 1024>; +} // namespace + +BENCHMARK(getFromFilledCache_1m_100hit); +BENCHMARK(getFromFilledCache_4m_100hit); +BENCHMARK(getFromFilledCache_16m_100hit); +BENCHMARK(getFromFilledCache_64m_100hit); +BENCHMARK(getFromFilledCache_1m_70hit); +BENCHMARK(getFromFilledCache_4m_70hit); +BENCHMARK(getFromFilledCache_16m_70hit); +BENCHMARK(getFromFilledCache_64m_70hit); +BENCHMARK(setToBoundedNonEmptyCache_1m); +BENCHMARK(setToBoundedNonEmptyCache_4m); +BENCHMARK(setToBoundedNonEmptyCache_16m); +BENCHMARK(setToBoundedNonEmptyCache_64m); + +BENCHMARK_MAIN(); diff --git a/apps/esmtool/esmtool.cpp b/apps/esmtool/esmtool.cpp index 579f5f67d..60483f981 100644 --- a/apps/esmtool/esmtool.cpp +++ b/apps/esmtool/esmtool.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include @@ -322,7 +323,7 @@ int load(Arguments& info) std::string filename = info.filename; std::cout << "Loading file: " << filename << std::endl; - std::list skipped; + std::unordered_set skipped; try { @@ -364,17 +365,17 @@ int load(Arguments& info) // Loop through all records while(esm.hasMoreRecs()) { - ESM::NAME n = esm.getRecName(); + const ESM::NAME n = esm.getRecName(); uint32_t flags; esm.getRecHeader(flags); EsmTool::RecordBase *record = EsmTool::RecordBase::create(n); if (record == nullptr) { - if (std::find(skipped.begin(), skipped.end(), n.intval) == skipped.end()) + if (skipped.count(n.intval) == 0) { std::cout << "Skipping " << n.toString() << " records." << std::endl; - skipped.push_back(n.intval); + skipped.emplace(n.intval); } esm.skipRecord(); diff --git a/apps/esmtool/record.cpp b/apps/esmtool/record.cpp index 3679184a6..55170e232 100644 --- a/apps/esmtool/record.cpp +++ b/apps/esmtool/record.cpp @@ -172,7 +172,7 @@ void printTransport(const std::vector& transport) namespace EsmTool { RecordBase * -RecordBase::create(ESM::NAME type) +RecordBase::create(const ESM::NAME type) { RecordBase *record = nullptr; diff --git a/apps/launcher/advancedpage.cpp b/apps/launcher/advancedpage.cpp index b4b5f5dda..008b71a0b 100644 --- a/apps/launcher/advancedpage.cpp +++ b/apps/launcher/advancedpage.cpp @@ -3,10 +3,8 @@ #include #include -#include #include #include -#include #include #include #include @@ -15,11 +13,9 @@ #include "utils/openalutil.hpp" -Launcher::AdvancedPage::AdvancedPage(Config::GameSettings &gameSettings, - Settings::Manager &engineSettings, QWidget *parent) +Launcher::AdvancedPage::AdvancedPage(Config::GameSettings &gameSettings, QWidget *parent) : QWidget(parent) , mGameSettings(gameSettings) - , mEngineSettings(engineSettings) { setObjectName ("AdvancedPage"); setupUi(this); @@ -102,12 +98,12 @@ bool Launcher::AdvancedPage::loadSettings() loadSettingBool(normaliseRaceSpeedCheckBox, "normalise race speed", "Game"); loadSettingBool(swimUpwardCorrectionCheckBox, "swim upward correction", "Game"); loadSettingBool(avoidCollisionsCheckBox, "NPCs avoid collisions", "Game"); - int unarmedFactorsStrengthIndex = mEngineSettings.getInt("strength influences hand to hand", "Game"); + int unarmedFactorsStrengthIndex = Settings::Manager::getInt("strength influences hand to hand", "Game"); if (unarmedFactorsStrengthIndex >= 0 && unarmedFactorsStrengthIndex <= 2) unarmedFactorsStrengthComboBox->setCurrentIndex(unarmedFactorsStrengthIndex); loadSettingBool(stealingFromKnockedOutCheckBox, "always allow stealing from knocked out actors", "Game"); loadSettingBool(enableNavigatorCheckBox, "enable", "Navigator"); - int numPhysicsThreads = mEngineSettings.getInt("async num threads", "Physics"); + int numPhysicsThreads = Settings::Manager::getInt("async num threads", "Physics"); if (numPhysicsThreads >= 0) physicsThreadsSpinBox->setValue(numPhysicsThreads); loadSettingBool(allowNPCToFollowOverWaterSurfaceCheckBox, "allow actors to follow over water surface", "Game"); @@ -132,26 +128,20 @@ bool Launcher::AdvancedPage::loadSettings() loadSettingBool(turnToMovementDirectionCheckBox, "turn to movement direction", "Game"); loadSettingBool(smoothMovementCheckBox, "smooth movement", "Game"); - const bool distantTerrain = mEngineSettings.getBool("distant terrain", "Terrain"); - const bool objectPaging = mEngineSettings.getBool("object paging", "Terrain"); + const bool distantTerrain = Settings::Manager::getBool("distant terrain", "Terrain"); + const bool objectPaging = Settings::Manager::getBool("object paging", "Terrain"); if (distantTerrain && objectPaging) { distantLandCheckBox->setCheckState(Qt::Checked); } loadSettingBool(activeGridObjectPagingCheckBox, "object paging active grid", "Terrain"); - viewingDistanceComboBox->setValue(convertToCells(mEngineSettings.getInt("viewing distance", "Camera"))); - - int lightingMethod = 1; - if (mEngineSettings.getString("lighting method", "Shaders") == "legacy") - lightingMethod = 0; - else if (mEngineSettings.getString("lighting method", "Shaders") == "shaders") - lightingMethod = 2; - lightingMethodComboBox->setCurrentIndex(lightingMethod); + viewingDistanceComboBox->setValue(convertToCells(Settings::Manager::getInt("viewing distance", "Camera"))); + objectPagingMinSizeComboBox->setValue(Settings::Manager::getDouble("object paging min size", "Terrain")); } // Audio { - std::string selectedAudioDevice = mEngineSettings.getString("device", "Sound"); + std::string selectedAudioDevice = Settings::Manager::getString("device", "Sound"); if (selectedAudioDevice.empty() == false) { int audioDeviceIndex = audioDeviceSelectorComboBox->findData(QString::fromStdString(selectedAudioDevice)); @@ -160,12 +150,12 @@ bool Launcher::AdvancedPage::loadSettings() audioDeviceSelectorComboBox->setCurrentIndex(audioDeviceIndex); } } - int hrtfEnabledIndex = mEngineSettings.getInt("hrtf enable", "Sound"); + int hrtfEnabledIndex = Settings::Manager::getInt("hrtf enable", "Sound"); if (hrtfEnabledIndex >= -1 && hrtfEnabledIndex <= 1) { enableHRTFComboBox->setCurrentIndex(hrtfEnabledIndex + 1); } - std::string selectedHRTFProfile = mEngineSettings.getString("hrtf", "Sound"); + std::string selectedHRTFProfile = Settings::Manager::getString("hrtf", "Sound"); if (selectedHRTFProfile.empty() == false) { int hrtfProfileIndex = hrtfProfileSelectorComboBox->findData(QString::fromStdString(selectedHRTFProfile)); @@ -187,7 +177,7 @@ bool Launcher::AdvancedPage::loadSettings() loadSettingBool(deferredPreviewRotationCheckBox, "deferred preview rotation", "Camera"); loadSettingBool(headBobbingCheckBox, "head bobbing", "Camera"); defaultShoulderComboBox->setCurrentIndex( - mEngineSettings.getVector2("view over shoulder offset", "Camera").x() >= 0 ? 0 : 1); + Settings::Manager::getVector2("view over shoulder offset", "Camera").x() >= 0 ? 0 : 1); } // Interface Changes @@ -197,13 +187,13 @@ bool Launcher::AdvancedPage::loadSettings() loadSettingBool(showMeleeInfoCheckBox, "show melee info", "Game"); loadSettingBool(showProjectileDamageCheckBox, "show projectile damage", "Game"); loadSettingBool(changeDialogTopicsCheckBox, "color topic enable", "GUI"); - int showOwnedIndex = mEngineSettings.getInt("show owned", "Game"); + int showOwnedIndex = Settings::Manager::getInt("show owned", "Game"); // Match the index with the option (only 0, 1, 2, or 3 are valid). Will default to 0 if invalid. if (showOwnedIndex >= 0 && showOwnedIndex <= 3) showOwnedComboBox->setCurrentIndex(showOwnedIndex); loadSettingBool(stretchBackgroundCheckBox, "stretch menu background", "GUI"); loadSettingBool(graphicHerbalismCheckBox, "graphic herbalism", "Game"); - scalingSpinBox->setValue(mEngineSettings.getFloat("scaling factor", "GUI")); + scalingSpinBox->setValue(Settings::Manager::getFloat("scaling factor", "GUI")); } // Bug fixes @@ -216,10 +206,10 @@ bool Launcher::AdvancedPage::loadSettings() { // Saves loadSettingBool(timePlayedCheckbox, "timeplayed", "Saves"); - maximumQuicksavesComboBox->setValue(mEngineSettings.getInt("max quicksaves", "Saves")); + maximumQuicksavesComboBox->setValue(Settings::Manager::getInt("max quicksaves", "Saves")); // Other Settings - QString screenshotFormatString = QString::fromStdString(mEngineSettings.getString("screenshot format", "General")).toUpper(); + QString screenshotFormatString = QString::fromStdString(Settings::Manager::getString("screenshot format", "General")).toUpper(); if (screenshotFormatComboBox->findText(screenshotFormatString) == -1) screenshotFormatComboBox->addItem(screenshotFormatString); screenshotFormatComboBox->setCurrentIndex(screenshotFormatComboBox->findText(screenshotFormatString)); @@ -280,13 +270,13 @@ void Launcher::AdvancedPage::saveSettings() saveSettingBool(swimUpwardCorrectionCheckBox, "swim upward correction", "Game"); saveSettingBool(avoidCollisionsCheckBox, "NPCs avoid collisions", "Game"); int unarmedFactorsStrengthIndex = unarmedFactorsStrengthComboBox->currentIndex(); - if (unarmedFactorsStrengthIndex != mEngineSettings.getInt("strength influences hand to hand", "Game")) - mEngineSettings.setInt("strength influences hand to hand", "Game", unarmedFactorsStrengthIndex); + if (unarmedFactorsStrengthIndex != Settings::Manager::getInt("strength influences hand to hand", "Game")) + Settings::Manager::setInt("strength influences hand to hand", "Game", unarmedFactorsStrengthIndex); saveSettingBool(stealingFromKnockedOutCheckBox, "always allow stealing from knocked out actors", "Game"); saveSettingBool(enableNavigatorCheckBox, "enable", "Navigator"); int numPhysicsThreads = physicsThreadsSpinBox->value(); - if (numPhysicsThreads != mEngineSettings.getInt("async num threads", "Physics")) - mEngineSettings.setInt("async num threads", "Physics", numPhysicsThreads); + if (numPhysicsThreads != Settings::Manager::getInt("async num threads", "Physics")) + Settings::Manager::setInt("async num threads", "Physics", numPhysicsThreads); } // Visuals @@ -304,23 +294,23 @@ void Launcher::AdvancedPage::saveSettings() saveSettingBool(turnToMovementDirectionCheckBox, "turn to movement direction", "Game"); saveSettingBool(smoothMovementCheckBox, "smooth movement", "Game"); - const bool distantTerrain = mEngineSettings.getBool("distant terrain", "Terrain"); - const bool objectPaging = mEngineSettings.getBool("object paging", "Terrain"); + const bool distantTerrain = Settings::Manager::getBool("distant terrain", "Terrain"); + const bool objectPaging = Settings::Manager::getBool("object paging", "Terrain"); const bool wantDistantLand = distantLandCheckBox->checkState(); if (wantDistantLand != (distantTerrain && objectPaging)) { - mEngineSettings.setBool("distant terrain", "Terrain", wantDistantLand); - mEngineSettings.setBool("object paging", "Terrain", wantDistantLand); + Settings::Manager::setBool("distant terrain", "Terrain", wantDistantLand); + Settings::Manager::setBool("object paging", "Terrain", wantDistantLand); } saveSettingBool(activeGridObjectPagingCheckBox, "object paging active grid", "Terrain"); double viewingDistance = viewingDistanceComboBox->value(); - if (viewingDistance != convertToCells(mEngineSettings.getInt("viewing distance", "Camera"))) + if (viewingDistance != convertToCells(Settings::Manager::getInt("viewing distance", "Camera"))) { - mEngineSettings.setInt("viewing distance", "Camera", convertToUnits(viewingDistance)); + Settings::Manager::setInt("viewing distance", "Camera", convertToUnits(viewingDistance)); } - - static std::array lightingMethodMap = {"legacy", "shaders compatibility", "shaders"}; - mEngineSettings.setString("lighting method", "Shaders", lightingMethodMap[lightingMethodComboBox->currentIndex()]); + double objectPagingMinSize = objectPagingMinSizeComboBox->value(); + if (objectPagingMinSize != Settings::Manager::getDouble("object paging min size", "Terrain")) + Settings::Manager::setDouble("object paging min size", "Terrain", objectPagingMinSize); } // Audio @@ -328,25 +318,25 @@ void Launcher::AdvancedPage::saveSettings() int audioDeviceIndex = audioDeviceSelectorComboBox->currentIndex(); if (audioDeviceIndex != 0) { - mEngineSettings.setString("device", "Sound", audioDeviceSelectorComboBox->currentText().toUtf8().constData()); + Settings::Manager::setString("device", "Sound", audioDeviceSelectorComboBox->currentText().toUtf8().constData()); } else { - mEngineSettings.setString("device", "Sound", ""); + Settings::Manager::setString("device", "Sound", ""); } int hrtfEnabledIndex = enableHRTFComboBox->currentIndex() - 1; - if (hrtfEnabledIndex != mEngineSettings.getInt("hrtf enable", "Sound")) + if (hrtfEnabledIndex != Settings::Manager::getInt("hrtf enable", "Sound")) { - mEngineSettings.setInt("hrtf enable", "Sound", hrtfEnabledIndex); + Settings::Manager::setInt("hrtf enable", "Sound", hrtfEnabledIndex); } int selectedHRTFProfileIndex = hrtfProfileSelectorComboBox->currentIndex(); if (selectedHRTFProfileIndex != 0) { - mEngineSettings.setString("hrtf", "Sound", hrtfProfileSelectorComboBox->currentText().toUtf8().constData()); + Settings::Manager::setString("hrtf", "Sound", hrtfProfileSelectorComboBox->currentText().toUtf8().constData()); } else { - mEngineSettings.setString("hrtf", "Sound", ""); + Settings::Manager::setString("hrtf", "Sound", ""); } } @@ -358,14 +348,14 @@ void Launcher::AdvancedPage::saveSettings() saveSettingBool(deferredPreviewRotationCheckBox, "deferred preview rotation", "Camera"); saveSettingBool(headBobbingCheckBox, "head bobbing", "Camera"); - osg::Vec2f shoulderOffset = mEngineSettings.getVector2("view over shoulder offset", "Camera"); + osg::Vec2f shoulderOffset = Settings::Manager::getVector2("view over shoulder offset", "Camera"); if (defaultShoulderComboBox->currentIndex() != (shoulderOffset.x() >= 0 ? 0 : 1)) { if (defaultShoulderComboBox->currentIndex() == 0) shoulderOffset.x() = std::abs(shoulderOffset.x()); else shoulderOffset.x() = -std::abs(shoulderOffset.x()); - mEngineSettings.setVector2("view over shoulder offset", "Camera", shoulderOffset); + Settings::Manager::setVector2("view over shoulder offset", "Camera", shoulderOffset); } } @@ -377,13 +367,13 @@ void Launcher::AdvancedPage::saveSettings() saveSettingBool(showProjectileDamageCheckBox, "show projectile damage", "Game"); saveSettingBool(changeDialogTopicsCheckBox, "color topic enable", "GUI"); int showOwnedCurrentIndex = showOwnedComboBox->currentIndex(); - if (showOwnedCurrentIndex != mEngineSettings.getInt("show owned", "Game")) - mEngineSettings.setInt("show owned", "Game", showOwnedCurrentIndex); + if (showOwnedCurrentIndex != Settings::Manager::getInt("show owned", "Game")) + Settings::Manager::setInt("show owned", "Game", showOwnedCurrentIndex); saveSettingBool(stretchBackgroundCheckBox, "stretch menu background", "GUI"); saveSettingBool(graphicHerbalismCheckBox, "graphic herbalism", "Game"); float uiScalingFactor = scalingSpinBox->value(); - if (uiScalingFactor != mEngineSettings.getFloat("scaling factor", "GUI")) - mEngineSettings.setFloat("scaling factor", "GUI", uiScalingFactor); + if (uiScalingFactor != Settings::Manager::getFloat("scaling factor", "GUI")) + Settings::Manager::setFloat("scaling factor", "GUI", uiScalingFactor); } // Bug fixes @@ -397,15 +387,15 @@ void Launcher::AdvancedPage::saveSettings() // Saves Settings saveSettingBool(timePlayedCheckbox, "timeplayed", "Saves"); int maximumQuicksaves = maximumQuicksavesComboBox->value(); - if (maximumQuicksaves != mEngineSettings.getInt("max quicksaves", "Saves")) + if (maximumQuicksaves != Settings::Manager::getInt("max quicksaves", "Saves")) { - mEngineSettings.setInt("max quicksaves", "Saves", maximumQuicksaves); + Settings::Manager::setInt("max quicksaves", "Saves", maximumQuicksaves); } // Other Settings std::string screenshotFormatString = screenshotFormatComboBox->currentText().toLower().toStdString(); - if (screenshotFormatString != mEngineSettings.getString("screenshot format", "General")) - mEngineSettings.setString("screenshot format", "General", screenshotFormatString); + if (screenshotFormatString != Settings::Manager::getString("screenshot format", "General")) + Settings::Manager::setString("screenshot format", "General", screenshotFormatString); } // Testing @@ -457,15 +447,15 @@ void Launcher::AdvancedPage::saveSettings() void Launcher::AdvancedPage::loadSettingBool(QCheckBox *checkbox, const std::string &setting, const std::string &group) { - if (mEngineSettings.getBool(setting, group)) + if (Settings::Manager::getBool(setting, group)) checkbox->setCheckState(Qt::Checked); } void Launcher::AdvancedPage::saveSettingBool(QCheckBox *checkbox, const std::string &setting, const std::string &group) { bool cValue = checkbox->checkState(); - if (cValue != mEngineSettings.getBool(setting, group)) - mEngineSettings.setBool(setting, group, cValue); + if (cValue != Settings::Manager::getBool(setting, group)) + Settings::Manager::setBool(setting, group, cValue); } void Launcher::AdvancedPage::slotLoadedCellsChanged(QStringList cellNames) diff --git a/apps/launcher/advancedpage.hpp b/apps/launcher/advancedpage.hpp index a373fae43..9685dcefe 100644 --- a/apps/launcher/advancedpage.hpp +++ b/apps/launcher/advancedpage.hpp @@ -1,7 +1,6 @@ #ifndef ADVANCEDPAGE_H #define ADVANCEDPAGE_H -#include #include #include @@ -18,8 +17,7 @@ namespace Launcher Q_OBJECT public: - AdvancedPage(Config::GameSettings &gameSettings, - Settings::Manager &engineSettings, QWidget *parent = nullptr); + explicit AdvancedPage(Config::GameSettings &gameSettings, QWidget *parent = nullptr); bool loadSettings(); void saveSettings(); @@ -35,7 +33,6 @@ namespace Launcher private: Config::GameSettings &mGameSettings; - Settings::Manager &mEngineSettings; QCompleter mCellNameCompleter; QStringListModel mCellNameCompleterModel; diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index 81544b094..956483a3f 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -4,7 +4,6 @@ #include #include -#include #include #include #include @@ -14,7 +13,6 @@ #include #include -#include #include #include diff --git a/apps/launcher/datafilespage.hpp b/apps/launcher/datafilespage.hpp index 3d200e6d2..5a7a6dc6e 100644 --- a/apps/launcher/datafilespage.hpp +++ b/apps/launcher/datafilespage.hpp @@ -6,7 +6,6 @@ #include -#include #include class QSortFilterProxyModel; diff --git a/apps/launcher/graphicspage.cpp b/apps/launcher/graphicspage.cpp index c6e74573c..ebb031e9e 100644 --- a/apps/launcher/graphicspage.cpp +++ b/apps/launcher/graphicspage.cpp @@ -32,9 +32,8 @@ QString getAspect(int x, int y) return QString(QString::number(xaspect) + ":" + QString::number(yaspect)); } -Launcher::GraphicsPage::GraphicsPage(Settings::Manager &engineSettings, QWidget *parent) +Launcher::GraphicsPage::GraphicsPage(QWidget *parent) : QWidget(parent) - , mEngineSettings(engineSettings) { setObjectName ("GraphicsPage"); setupUi(this); @@ -93,26 +92,27 @@ bool Launcher::GraphicsPage::loadSettings() if (!setupSDL()) return false; - if (mEngineSettings.getBool("vsync", "Video")) + // Visuals + if (Settings::Manager::getBool("vsync", "Video")) vSyncCheckBox->setCheckState(Qt::Checked); - if (mEngineSettings.getBool("fullscreen", "Video")) + if (Settings::Manager::getBool("fullscreen", "Video")) fullScreenCheckBox->setCheckState(Qt::Checked); - if (mEngineSettings.getBool("window border", "Video")) + if (Settings::Manager::getBool("window border", "Video")) windowBorderCheckBox->setCheckState(Qt::Checked); // aaValue is the actual value (0, 1, 2, 4, 8, 16) - int aaValue = mEngineSettings.getInt("antialiasing", "Video"); + int aaValue = Settings::Manager::getInt("antialiasing", "Video"); // aaIndex is the index into the allowed values in the pull down. int aaIndex = antiAliasingComboBox->findText(QString::number(aaValue)); if (aaIndex != -1) antiAliasingComboBox->setCurrentIndex(aaIndex); - int width = mEngineSettings.getInt("resolution x", "Video"); - int height = mEngineSettings.getInt("resolution y", "Video"); + int width = Settings::Manager::getInt("resolution x", "Video"); + int height = Settings::Manager::getInt("resolution y", "Video"); QString resolution = QString::number(width) + QString(" x ") + QString::number(height); - screenComboBox->setCurrentIndex(mEngineSettings.getInt("screen", "Video")); + screenComboBox->setCurrentIndex(Settings::Manager::getInt("screen", "Video")); int resIndex = resolutionComboBox->findText(resolution, Qt::MatchStartsWith); @@ -125,40 +125,49 @@ bool Launcher::GraphicsPage::loadSettings() customHeightSpinBox->setValue(height); } - float fpsLimit = mEngineSettings.getFloat("framerate limit", "Video"); + float fpsLimit = Settings::Manager::getFloat("framerate limit", "Video"); if (fpsLimit != 0) { framerateLimitCheckBox->setCheckState(Qt::Checked); framerateLimitSpinBox->setValue(fpsLimit); } - if (mEngineSettings.getBool("actor shadows", "Shadows")) + // Lighting + int lightingMethod = 1; + if (Settings::Manager::getString("lighting method", "Shaders") == "legacy") + lightingMethod = 0; + else if (Settings::Manager::getString("lighting method", "Shaders") == "shaders") + lightingMethod = 2; + lightingMethodComboBox->setCurrentIndex(lightingMethod); + + // Shadows + if (Settings::Manager::getBool("actor shadows", "Shadows")) actorShadowsCheckBox->setCheckState(Qt::Checked); - if (mEngineSettings.getBool("player shadows", "Shadows")) + if (Settings::Manager::getBool("player shadows", "Shadows")) playerShadowsCheckBox->setCheckState(Qt::Checked); - if (mEngineSettings.getBool("terrain shadows", "Shadows")) + if (Settings::Manager::getBool("terrain shadows", "Shadows")) terrainShadowsCheckBox->setCheckState(Qt::Checked); - if (mEngineSettings.getBool("object shadows", "Shadows")) + if (Settings::Manager::getBool("object shadows", "Shadows")) objectShadowsCheckBox->setCheckState(Qt::Checked); - if (mEngineSettings.getBool("enable indoor shadows", "Shadows")) + if (Settings::Manager::getBool("enable indoor shadows", "Shadows")) indoorShadowsCheckBox->setCheckState(Qt::Checked); shadowComputeSceneBoundsComboBox->setCurrentIndex( shadowComputeSceneBoundsComboBox->findText( - QString(tr(mEngineSettings.getString("compute scene bounds", "Shadows").c_str())))); + QString(tr(Settings::Manager::getString("compute scene bounds", "Shadows").c_str())))); - int shadowDistLimit = mEngineSettings.getInt("maximum shadow map distance", "Shadows"); + int shadowDistLimit = Settings::Manager::getInt("maximum shadow map distance", "Shadows"); if (shadowDistLimit > 0) { shadowDistanceCheckBox->setCheckState(Qt::Checked); shadowDistanceSpinBox->setValue(shadowDistLimit); } - float shadowFadeStart = mEngineSettings.getFloat("shadow fade start", "Shadows"); + float shadowFadeStart = Settings::Manager::getFloat("shadow fade start", "Shadows"); if (shadowFadeStart != 0) fadeStartSpinBox->setValue(shadowFadeStart); - int shadowRes = mEngineSettings.getInt("shadow map resolution", "Shadows"); + int shadowRes = Settings::Manager::getInt("shadow map resolution", "Shadows"); int shadowResIndex = shadowResolutionComboBox->findText(QString::number(shadowRes)); if (shadowResIndex != -1) shadowResolutionComboBox->setCurrentIndex(shadowResIndex); @@ -168,23 +177,25 @@ bool Launcher::GraphicsPage::loadSettings() void Launcher::GraphicsPage::saveSettings() { + // Visuals + // Ensure we only set the new settings if they changed. This is to avoid cluttering the // user settings file (which by definition should only contain settings the user has touched) bool cVSync = vSyncCheckBox->checkState(); - if (cVSync != mEngineSettings.getBool("vsync", "Video")) - mEngineSettings.setBool("vsync", "Video", cVSync); + if (cVSync != Settings::Manager::getBool("vsync", "Video")) + Settings::Manager::setBool("vsync", "Video", cVSync); bool cFullScreen = fullScreenCheckBox->checkState(); - if (cFullScreen != mEngineSettings.getBool("fullscreen", "Video")) - mEngineSettings.setBool("fullscreen", "Video", cFullScreen); + if (cFullScreen != Settings::Manager::getBool("fullscreen", "Video")) + Settings::Manager::setBool("fullscreen", "Video", cFullScreen); bool cWindowBorder = windowBorderCheckBox->checkState(); - if (cWindowBorder != mEngineSettings.getBool("window border", "Video")) - mEngineSettings.setBool("window border", "Video", cWindowBorder); + if (cWindowBorder != Settings::Manager::getBool("window border", "Video")) + Settings::Manager::setBool("window border", "Video", cWindowBorder); int cAAValue = antiAliasingComboBox->currentText().toInt(); - if (cAAValue != mEngineSettings.getInt("antialiasing", "Video")) - mEngineSettings.setInt("antialiasing", "Video", cAAValue); + if (cAAValue != Settings::Manager::getInt("antialiasing", "Video")) + Settings::Manager::setInt("antialiasing", "Video", cAAValue); int cWidth = 0; int cHeight = 0; @@ -199,33 +210,38 @@ void Launcher::GraphicsPage::saveSettings() cHeight = customHeightSpinBox->value(); } - if (cWidth != mEngineSettings.getInt("resolution x", "Video")) - mEngineSettings.setInt("resolution x", "Video", cWidth); + if (cWidth != Settings::Manager::getInt("resolution x", "Video")) + Settings::Manager::setInt("resolution x", "Video", cWidth); - if (cHeight != mEngineSettings.getInt("resolution y", "Video")) - mEngineSettings.setInt("resolution y", "Video", cHeight); + if (cHeight != Settings::Manager::getInt("resolution y", "Video")) + Settings::Manager::setInt("resolution y", "Video", cHeight); int cScreen = screenComboBox->currentIndex(); - if (cScreen != mEngineSettings.getInt("screen", "Video")) - mEngineSettings.setInt("screen", "Video", cScreen); + if (cScreen != Settings::Manager::getInt("screen", "Video")) + Settings::Manager::setInt("screen", "Video", cScreen); if (framerateLimitCheckBox->checkState() != Qt::Unchecked) { float cFpsLimit = framerateLimitSpinBox->value(); - if (cFpsLimit != mEngineSettings.getFloat("framerate limit", "Video")) - mEngineSettings.setFloat("framerate limit", "Video", cFpsLimit); + if (cFpsLimit != Settings::Manager::getFloat("framerate limit", "Video")) + Settings::Manager::setFloat("framerate limit", "Video", cFpsLimit); } - else if (mEngineSettings.getFloat("framerate limit", "Video") != 0) + else if (Settings::Manager::getFloat("framerate limit", "Video") != 0) { - mEngineSettings.setFloat("framerate limit", "Video", 0); + Settings::Manager::setFloat("framerate limit", "Video", 0); } + // Lighting + static std::array lightingMethodMap = {"legacy", "shaders compatibility", "shaders"}; + Settings::Manager::setString("lighting method", "Shaders", lightingMethodMap[lightingMethodComboBox->currentIndex()]); + + // Shadows int cShadowDist = shadowDistanceCheckBox->checkState() != Qt::Unchecked ? shadowDistanceSpinBox->value() : 0; - if (mEngineSettings.getInt("maximum shadow map distance", "Shadows") != cShadowDist) - mEngineSettings.setInt("maximum shadow map distance", "Shadows", cShadowDist); + if (Settings::Manager::getInt("maximum shadow map distance", "Shadows") != cShadowDist) + Settings::Manager::setInt("maximum shadow map distance", "Shadows", cShadowDist); float cFadeStart = fadeStartSpinBox->value(); - if (cShadowDist > 0 && mEngineSettings.getFloat("shadow fade start", "Shadows") != cFadeStart) - mEngineSettings.setFloat("shadow fade start", "Shadows", cFadeStart); + if (cShadowDist > 0 && Settings::Manager::getFloat("shadow fade start", "Shadows") != cFadeStart) + Settings::Manager::setFloat("shadow fade start", "Shadows", cFadeStart); bool cActorShadows = actorShadowsCheckBox->checkState(); bool cObjectShadows = objectShadowsCheckBox->checkState(); @@ -233,42 +249,42 @@ void Launcher::GraphicsPage::saveSettings() bool cPlayerShadows = playerShadowsCheckBox->checkState(); if (cActorShadows || cObjectShadows || cTerrainShadows || cPlayerShadows) { - if (!mEngineSettings.getBool("enable shadows", "Shadows")) - mEngineSettings.setBool("enable shadows", "Shadows", true); - if (mEngineSettings.getBool("actor shadows", "Shadows") != cActorShadows) - mEngineSettings.setBool("actor shadows", "Shadows", cActorShadows); - if (mEngineSettings.getBool("player shadows", "Shadows") != cPlayerShadows) - mEngineSettings.setBool("player shadows", "Shadows", cPlayerShadows); - if (mEngineSettings.getBool("object shadows", "Shadows") != cObjectShadows) - mEngineSettings.setBool("object shadows", "Shadows", cObjectShadows); - if (mEngineSettings.getBool("terrain shadows", "Shadows") != cTerrainShadows) - mEngineSettings.setBool("terrain shadows", "Shadows", cTerrainShadows); + if (!Settings::Manager::getBool("enable shadows", "Shadows")) + Settings::Manager::setBool("enable shadows", "Shadows", true); + if (Settings::Manager::getBool("actor shadows", "Shadows") != cActorShadows) + Settings::Manager::setBool("actor shadows", "Shadows", cActorShadows); + if (Settings::Manager::getBool("player shadows", "Shadows") != cPlayerShadows) + Settings::Manager::setBool("player shadows", "Shadows", cPlayerShadows); + if (Settings::Manager::getBool("object shadows", "Shadows") != cObjectShadows) + Settings::Manager::setBool("object shadows", "Shadows", cObjectShadows); + if (Settings::Manager::getBool("terrain shadows", "Shadows") != cTerrainShadows) + Settings::Manager::setBool("terrain shadows", "Shadows", cTerrainShadows); } else { - if (mEngineSettings.getBool("enable shadows", "Shadows")) - mEngineSettings.setBool("enable shadows", "Shadows", false); - if (mEngineSettings.getBool("actor shadows", "Shadows")) - mEngineSettings.setBool("actor shadows", "Shadows", false); - if (mEngineSettings.getBool("player shadows", "Shadows")) - mEngineSettings.setBool("player shadows", "Shadows", false); - if (mEngineSettings.getBool("object shadows", "Shadows")) - mEngineSettings.setBool("object shadows", "Shadows", false); - if (mEngineSettings.getBool("terrain shadows", "Shadows")) - mEngineSettings.setBool("terrain shadows", "Shadows", false); + if (Settings::Manager::getBool("enable shadows", "Shadows")) + Settings::Manager::setBool("enable shadows", "Shadows", false); + if (Settings::Manager::getBool("actor shadows", "Shadows")) + Settings::Manager::setBool("actor shadows", "Shadows", false); + if (Settings::Manager::getBool("player shadows", "Shadows")) + Settings::Manager::setBool("player shadows", "Shadows", false); + if (Settings::Manager::getBool("object shadows", "Shadows")) + Settings::Manager::setBool("object shadows", "Shadows", false); + if (Settings::Manager::getBool("terrain shadows", "Shadows")) + Settings::Manager::setBool("terrain shadows", "Shadows", false); } bool cIndoorShadows = indoorShadowsCheckBox->checkState(); - if (mEngineSettings.getBool("enable indoor shadows", "Shadows") != cIndoorShadows) - mEngineSettings.setBool("enable indoor shadows", "Shadows", cIndoorShadows); + if (Settings::Manager::getBool("enable indoor shadows", "Shadows") != cIndoorShadows) + Settings::Manager::setBool("enable indoor shadows", "Shadows", cIndoorShadows); int cShadowRes = shadowResolutionComboBox->currentText().toInt(); - if (cShadowRes != mEngineSettings.getInt("shadow map resolution", "Shadows")) - mEngineSettings.setInt("shadow map resolution", "Shadows", cShadowRes); + if (cShadowRes != Settings::Manager::getInt("shadow map resolution", "Shadows")) + Settings::Manager::setInt("shadow map resolution", "Shadows", cShadowRes); auto cComputeSceneBounds = shadowComputeSceneBoundsComboBox->currentText().toStdString(); - if (cComputeSceneBounds != mEngineSettings.getString("compute scene bounds", "Shadows")) - mEngineSettings.setString("compute scene bounds", "Shadows", cComputeSceneBounds); + if (cComputeSceneBounds != Settings::Manager::getString("compute scene bounds", "Shadows")) + Settings::Manager::setString("compute scene bounds", "Shadows", cComputeSceneBounds); } QStringList Launcher::GraphicsPage::getAvailableResolutions(int screen) diff --git a/apps/launcher/graphicspage.hpp b/apps/launcher/graphicspage.hpp index 35f711500..a6754ccb0 100644 --- a/apps/launcher/graphicspage.hpp +++ b/apps/launcher/graphicspage.hpp @@ -1,8 +1,6 @@ #ifndef GRAPHICSPAGE_H #define GRAPHICSPAGE_H -#include - #include "ui_graphicspage.h" #include @@ -20,7 +18,7 @@ namespace Launcher Q_OBJECT public: - GraphicsPage(Settings::Manager &engineSettings, QWidget *parent = nullptr); + explicit GraphicsPage(QWidget *parent = nullptr); void saveSettings(); bool loadSettings(); @@ -35,8 +33,6 @@ namespace Launcher void slotShadowDistLimitToggled(bool checked); private: - Settings::Manager &mEngineSettings; - QVector mResolutionsPerScreen; static QStringList getAvailableResolutions(int screen); diff --git a/apps/launcher/main.cpp b/apps/launcher/main.cpp index f15abafce..9c9acb4a1 100644 --- a/apps/launcher/main.cpp +++ b/apps/launcher/main.cpp @@ -1,10 +1,8 @@ #include -#include #include #include #include -#include #ifdef MAC_OS_X_VERSION_MIN_REQUIRED #undef MAC_OS_X_VERSION_MIN_REQUIRED diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp index 215258bcd..d41cd529d 100644 --- a/apps/launcher/maindialog.cpp +++ b/apps/launcher/maindialog.cpp @@ -5,15 +5,12 @@ #include #include -#include #include #include #include #include #include -#include - #include "playpage.hpp" #include "graphicspage.hpp" #include "datafilespage.hpp" @@ -126,9 +123,9 @@ void Launcher::MainDialog::createPages() mPlayPage = new PlayPage(this); mDataFilesPage = new DataFilesPage(mCfgMgr, mGameSettings, mLauncherSettings, this); - mGraphicsPage = new GraphicsPage(mEngineSettings, this); + mGraphicsPage = new GraphicsPage(this); mSettingsPage = new SettingsPage(mCfgMgr, mGameSettings, mLauncherSettings, this); - mAdvancedPage = new AdvancedPage(mGameSettings, mEngineSettings, this); + mAdvancedPage = new AdvancedPage(mGameSettings, this); // Set the combobox of the play page to imitate the combobox on the datafilespage mPlayPage->setProfilesModel(mDataFilesPage->profilesModel()); @@ -427,11 +424,11 @@ bool Launcher::MainDialog::setupGraphicsSettings() mEngineSettings.clear(); // Create the settings manager and load default settings file - const std::string localDefault = (mCfgMgr.getLocalPath() / "settings-default.cfg").string(); - const std::string globalDefault = (mCfgMgr.getGlobalPath() / "settings-default.cfg").string(); + const std::string localDefault = (mCfgMgr.getLocalPath() / "defaults.bin").string(); + const std::string globalDefault = (mCfgMgr.getGlobalPath() / "defaults.bin").string(); std::string defaultPath; - // Prefer the settings-default.cfg in the current directory. + // Prefer the defaults.bin in the current directory. if (boost::filesystem::exists(localDefault)) defaultPath = localDefault; else if (boost::filesystem::exists(globalDefault)) @@ -439,7 +436,7 @@ bool Launcher::MainDialog::setupGraphicsSettings() // Something's very wrong if we can't find the file at all. else { cfgError(tr("Error reading OpenMW configuration file"), - tr("
Could not find settings-default.cfg

\ + tr("
Could not find defaults.bin

\ The problem may be due to an incomplete installation of OpenMW.
\ Reinstalling OpenMW may resolve the problem.")); return false; @@ -450,7 +447,7 @@ bool Launcher::MainDialog::setupGraphicsSettings() mEngineSettings.loadDefault(defaultPath); } catch (std::exception& e) { - std::string msg = std::string("
Error reading settings-default.cfg

") + e.what(); + std::string msg = std::string("
Error reading defaults.bin

") + e.what(); cfgError(tr("Error reading OpenMW configuration file"), tr(msg.c_str())); return false; } diff --git a/apps/launcher/maindialog.hpp b/apps/launcher/maindialog.hpp index de7423330..80e014e28 100644 --- a/apps/launcher/maindialog.hpp +++ b/apps/launcher/maindialog.hpp @@ -1,8 +1,6 @@ #ifndef MAINDIALOG_H #define MAINDIALOG_H -#include -#include #ifndef Q_MOC_RUN #include diff --git a/apps/launcher/playpage.hpp b/apps/launcher/playpage.hpp index b0bd3ee27..8f414dc6a 100644 --- a/apps/launcher/playpage.hpp +++ b/apps/launcher/playpage.hpp @@ -1,8 +1,6 @@ #ifndef PLAYPAGE_H #define PLAYPAGE_H -#include - #include "ui_playpage.h" class QComboBox; diff --git a/apps/launcher/sdlinit.cpp b/apps/launcher/sdlinit.cpp index 1fe1fd4c2..4717fb281 100644 --- a/apps/launcher/sdlinit.cpp +++ b/apps/launcher/sdlinit.cpp @@ -1,7 +1,6 @@ #include #include -#include bool initSDL() { diff --git a/apps/launcher/settingspage.cpp b/apps/launcher/settingspage.cpp index 59d7cfd25..ca7fd028a 100644 --- a/apps/launcher/settingspage.cpp +++ b/apps/launcher/settingspage.cpp @@ -2,7 +2,6 @@ #include #include -#include #include #include diff --git a/apps/launcher/settingspage.hpp b/apps/launcher/settingspage.hpp index 97b099dd6..df7c0e8eb 100644 --- a/apps/launcher/settingspage.hpp +++ b/apps/launcher/settingspage.hpp @@ -1,9 +1,6 @@ #ifndef SETTINGSPAGE_HPP #define SETTINGSPAGE_HPP -#include -#include - #include #include "ui_settingspage.h" diff --git a/apps/launcher/utils/cellnameloader.hpp b/apps/launcher/utils/cellnameloader.hpp index c58d09226..899ff75ad 100644 --- a/apps/launcher/utils/cellnameloader.hpp +++ b/apps/launcher/utils/cellnameloader.hpp @@ -1,7 +1,6 @@ #ifndef OPENMW_CELLNAMELOADER_H #define OPENMW_CELLNAMELOADER_H -#include #include #include diff --git a/apps/launcher/utils/lineedit.hpp b/apps/launcher/utils/lineedit.hpp index da28e858c..89de39588 100644 --- a/apps/launcher/utils/lineedit.hpp +++ b/apps/launcher/utils/lineedit.hpp @@ -11,7 +11,6 @@ #define LINEEDIT_H #include -#include #include #include diff --git a/apps/launcher/utils/profilescombobox.cpp b/apps/launcher/utils/profilescombobox.cpp index 462c2ebc2..af349ddff 100644 --- a/apps/launcher/utils/profilescombobox.cpp +++ b/apps/launcher/utils/profilescombobox.cpp @@ -1,5 +1,4 @@ #include -#include #include #include #include diff --git a/apps/mwiniimporter/importer.cpp b/apps/mwiniimporter/importer.cpp index 23aea2deb..2763d8ad9 100644 --- a/apps/mwiniimporter/importer.cpp +++ b/apps/mwiniimporter/importer.cpp @@ -676,7 +676,7 @@ MwIniImporter::multistrmap MwIniImporter::loadIniFile(const boost::filesystem::p } if(line[0] == '[') { - int pos = line.find(']'); + int pos = static_cast(line.find(']')); if(pos < 2) { std::cout << "Warning: ini file wrongly formatted (" << line << "). Line ignored." << std::endl; continue; @@ -686,12 +686,12 @@ MwIniImporter::multistrmap MwIniImporter::loadIniFile(const boost::filesystem::p continue; } - int comment_pos = line.find(";"); + int comment_pos = static_cast(line.find(';')); if(comment_pos > 0) { line = line.substr(0,comment_pos); } - int pos = line.find("="); + int pos = static_cast(line.find('=')); if(pos < 1) { continue; } @@ -722,7 +722,7 @@ MwIniImporter::multistrmap MwIniImporter::loadCfgFile(const boost::filesystem::p while (std::getline(file, line)) { // we cant say comment by only looking at first char anymore - int comment_pos = line.find("#"); + int comment_pos = static_cast(line.find('#')); if(comment_pos > 0) { line = line.substr(0,comment_pos); } @@ -731,7 +731,7 @@ MwIniImporter::multistrmap MwIniImporter::loadCfgFile(const boost::filesystem::p continue; } - int pos = line.find("="); + int pos = static_cast(line.find('=')); if(pos < 1) { continue; } diff --git a/apps/niftest/niftest.cpp b/apps/niftest/niftest.cpp index e9484d5f5..e403562d3 100644 --- a/apps/niftest/niftest.cpp +++ b/apps/niftest/niftest.cpp @@ -20,7 +20,7 @@ namespace bfs = boost::filesystem; ///See if the file has the named extension bool hasExtension(std::string filename, std::string extensionToFind) { - std::string extension = filename.substr(filename.find_last_of(".")+1); + std::string extension = filename.substr(filename.find_last_of('.')+1); //Convert strings to lower case for comparison std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower); diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index b73cd37b8..88c4233c9 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -89,7 +89,7 @@ opencs_units (view/render scenewidget worldspacewidget pagedworldspacewidget unpagedworldspacewidget previewwidget editmode instancemode instanceselectionmode instancemovemode orbitcameramode pathgridmode selectionmode pathgridselectionmode cameracontroller - cellwater terraintexturemode actor terrainselection terrainshapemode brushdraw + cellwater terraintexturemode actor terrainselection terrainshapemode brushdraw commands ) opencs_units_noqt (view/render @@ -158,7 +158,7 @@ include_directories(${CMAKE_CURRENT_BINARY_DIR}) if(APPLE) set (OPENCS_MAC_ICON "${CMAKE_SOURCE_DIR}/files/mac/openmw-cs.icns") - set (OPENCS_CFG "${OpenMW_BINARY_DIR}/openmw-cs.cfg") + set (OPENCS_CFG "${OpenMW_BINARY_DIR}/defaults-cs.bin") set (OPENCS_DEFAULT_FILTERS_FILE "${OpenMW_BINARY_DIR}/resources/defaultfilters") set (OPENCS_OPENMW_CFG "${OpenMW_BINARY_DIR}/openmw.cfg") else() @@ -270,7 +270,7 @@ if (WIN32) SET(INSTALL_SOURCE "${OpenMW_BINARY_DIR}") endif () - INSTALL(FILES "${INSTALL_SOURCE}/openmw-cs.cfg" DESTINATION ".") + INSTALL(FILES "${INSTALL_SOURCE}/defaults-cs.bin" DESTINATION ".") endif() if (MSVC) diff --git a/apps/opencs/model/doc/blacklist.cpp b/apps/opencs/model/doc/blacklist.cpp index b1d402c69..690d79983 100644 --- a/apps/opencs/model/doc/blacklist.cpp +++ b/apps/opencs/model/doc/blacklist.cpp @@ -21,7 +21,7 @@ void CSMDoc::Blacklist::add (CSMWorld::UniversalId::Type type, { std::vector& list = mIds[type]; - int size = list.size(); + size_t size = list.size(); list.resize (size+ids.size()); diff --git a/apps/opencs/model/doc/loader.cpp b/apps/opencs/model/doc/loader.cpp index 69c78bd5e..1c5a7348c 100644 --- a/apps/opencs/model/doc/loader.cpp +++ b/apps/opencs/model/doc/loader.cpp @@ -5,7 +5,6 @@ #include "../tools/reportmodel.hpp" #include "document.hpp" -#include "state.hpp" CSMDoc::Loader::Stage::Stage() : mFile (0), mRecordsLoaded (0), mRecordsLeft (false) {} diff --git a/apps/opencs/model/doc/operation.cpp b/apps/opencs/model/doc/operation.cpp index 218e13e38..369c6bb10 100644 --- a/apps/opencs/model/doc/operation.cpp +++ b/apps/opencs/model/doc/operation.cpp @@ -7,7 +7,6 @@ #include "../world/universalid.hpp" -#include "state.hpp" #include "stage.hpp" void CSMDoc::Operation::prepareStages() diff --git a/apps/opencs/model/doc/runner.cpp b/apps/opencs/model/doc/runner.cpp index ccdff1444..927bda080 100644 --- a/apps/opencs/model/doc/runner.cpp +++ b/apps/opencs/model/doc/runner.cpp @@ -83,6 +83,8 @@ void CSMDoc::Runner::start (bool delayed) arguments << QString::fromUtf8 (("--data=\""+mProjectPath.parent_path().string()+"\"").c_str()); + arguments << "--replace=content"; + for (std::vector::const_iterator iter (mContentFiles.begin()); iter!=mContentFiles.end(); ++iter) { diff --git a/apps/opencs/model/filter/narynode.cpp b/apps/opencs/model/filter/narynode.cpp index 2fa9ac6cc..9415f1daf 100644 --- a/apps/opencs/model/filter/narynode.cpp +++ b/apps/opencs/model/filter/narynode.cpp @@ -9,7 +9,7 @@ CSMFilter::NAryNode::NAryNode (const std::vector >& nodes, int CSMFilter::NAryNode::getSize() const { - return mNodes.size(); + return static_cast(mNodes.size()); } const CSMFilter::Node& CSMFilter::NAryNode::operator[] (int index) const diff --git a/apps/opencs/model/prefs/shortcutmanager.cpp b/apps/opencs/model/prefs/shortcutmanager.cpp index 781ad4de3..4c5f77900 100644 --- a/apps/opencs/model/prefs/shortcutmanager.cpp +++ b/apps/opencs/model/prefs/shortcutmanager.cpp @@ -178,7 +178,7 @@ namespace CSMPrefs { const int MaxKeys = 4; // A limitation of QKeySequence - size_t end = data.find(";"); + size_t end = data.find(';'); size_t size = std::min(end, data.size()); std::string value = data.substr(0, size); @@ -191,7 +191,7 @@ namespace CSMPrefs while (start < value.size()) { - end = data.find("+", start); + end = data.find('+', start); end = std::min(end, value.size()); std::string name = value.substr(start, end - start); @@ -243,7 +243,7 @@ namespace CSMPrefs void ShortcutManager::convertFromString(const std::string& data, int& modifier) const { - size_t start = data.find(";") + 1; + size_t start = data.find(';') + 1; start = std::min(start, data.size()); std::string name = data.substr(start); diff --git a/apps/opencs/model/prefs/state.cpp b/apps/opencs/model/prefs/state.cpp index 0958fa8d4..58a0f296e 100644 --- a/apps/opencs/model/prefs/state.cpp +++ b/apps/opencs/model/prefs/state.cpp @@ -17,15 +17,15 @@ CSMPrefs::State *CSMPrefs::State::sThis = nullptr; void CSMPrefs::State::load() { // default settings file - boost::filesystem::path local = mConfigurationManager.getLocalPath() / mConfigFile; - boost::filesystem::path global = mConfigurationManager.getGlobalPath() / mConfigFile; + boost::filesystem::path local = mConfigurationManager.getLocalPath() / mDefaultConfigFile; + boost::filesystem::path global = mConfigurationManager.getGlobalPath() / mDefaultConfigFile; if (boost::filesystem::exists (local)) mSettings.loadDefault (local.string()); else if (boost::filesystem::exists (global)) mSettings.loadDefault (global.string()); else - throw std::runtime_error ("No default settings file found! Make sure the file \"openmw-cs.cfg\" was properly installed."); + throw std::runtime_error ("No default settings file found! Make sure the file \"" + mDefaultConfigFile + "\" was properly installed."); // user settings file boost::filesystem::path user = mConfigurationManager.getUserConfigPath() / mConfigFile; @@ -641,7 +641,7 @@ void CSMPrefs::State::setDefault (const std::string& key, const std::string& def } CSMPrefs::State::State (const Files::ConfigurationManager& configurationManager) -: mConfigFile ("openmw-cs.cfg"), mConfigurationManager (configurationManager), +: mConfigFile ("openmw-cs.cfg"), mDefaultConfigFile("defaults-cs.bin"), mConfigurationManager (configurationManager), mCurrentCategory (mCategories.end()) { if (sThis) diff --git a/apps/opencs/model/prefs/state.hpp b/apps/opencs/model/prefs/state.hpp index aa63de595..7c9fcbecd 100644 --- a/apps/opencs/model/prefs/state.hpp +++ b/apps/opencs/model/prefs/state.hpp @@ -48,6 +48,7 @@ namespace CSMPrefs private: const std::string mConfigFile; + const std::string mDefaultConfigFile; const Files::ConfigurationManager& mConfigurationManager; ShortcutManager mShortcutManager; Settings::Manager mSettings; diff --git a/apps/opencs/model/tools/mandatoryid.cpp b/apps/opencs/model/tools/mandatoryid.cpp index 23adb9d37..d0d9cc0b9 100644 --- a/apps/opencs/model/tools/mandatoryid.cpp +++ b/apps/opencs/model/tools/mandatoryid.cpp @@ -11,7 +11,7 @@ CSMTools::MandatoryIdStage::MandatoryIdStage (const CSMWorld::CollectionBase& id int CSMTools::MandatoryIdStage::setup() { - return mIds.size(); + return static_cast(mIds.size()); } void CSMTools::MandatoryIdStage::perform (int stage, CSMDoc::Messages& messages) diff --git a/apps/opencs/model/tools/reportmodel.cpp b/apps/opencs/model/tools/reportmodel.cpp index f9a1fdb0c..a2901a663 100644 --- a/apps/opencs/model/tools/reportmodel.cpp +++ b/apps/opencs/model/tools/reportmodel.cpp @@ -24,7 +24,7 @@ int CSMTools::ReportModel::rowCount (const QModelIndex & parent) const if (parent.isValid()) return 0; - return mRows.size(); + return static_cast(mRows.size()); } int CSMTools::ReportModel::columnCount (const QModelIndex & parent) const @@ -140,7 +140,7 @@ bool CSMTools::ReportModel::removeRows (int row, int count, const QModelIndex& p void CSMTools::ReportModel::add (const CSMDoc::Message& message) { - beginInsertRows (QModelIndex(), mRows.size(), mRows.size()); + beginInsertRows (QModelIndex(), static_cast(mRows.size()), static_cast(mRows.size())); mRows.push_back (message); @@ -176,7 +176,7 @@ void CSMTools::ReportModel::clear() { if (!mRows.empty()) { - beginRemoveRows (QModelIndex(), 0, mRows.size()-1); + beginRemoveRows (QModelIndex(), 0, static_cast(mRows.size())-1); mRows.clear(); endRemoveRows(); } diff --git a/apps/opencs/model/world/idtable.cpp b/apps/opencs/model/world/idtable.cpp index 27d60ae98..30fe6f639 100644 --- a/apps/opencs/model/world/idtable.cpp +++ b/apps/opencs/model/world/idtable.cpp @@ -270,7 +270,7 @@ void CSMWorld::IdTable::reorderRows (int baseIndex, const std::vector& newO if (!newOrder.empty()) if (mIdCollection->reorderRows (baseIndex, newOrder)) emit dataChanged (index (baseIndex, 0), - index (baseIndex+newOrder.size()-1, mIdCollection->getColumns()-1)); + index (baseIndex+static_cast(newOrder.size())-1, mIdCollection->getColumns()-1)); } std::pair CSMWorld::IdTable::view (int row) const diff --git a/apps/opencs/model/world/refcollection.cpp b/apps/opencs/model/world/refcollection.cpp index 126a0ea78..dfdb8e73b 100644 --- a/apps/opencs/model/world/refcollection.cpp +++ b/apps/opencs/model/world/refcollection.cpp @@ -1,6 +1,5 @@ #include "refcollection.hpp" -#include #include #include "ref.hpp" @@ -109,11 +108,13 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool Record record; record.mState = base ? RecordBase::State_BaseOnly : RecordBase::State_ModifiedOnly; - (base ? record.mBase : record.mModified) = ref; + const ESM::RefNum refNum = ref.mRefNum; + std::string refId = ref.mId; + (base ? record.mBase : record.mModified) = std::move(ref); appendRecord (record); - cache.insert (std::make_pair (ref.mRefNum, ref.mId)); + cache.emplace(refNum, std::move(refId)); } else { @@ -124,7 +125,7 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool Record record = getRecord (index); record.mState = base ? RecordBase::State_BaseOnly : RecordBase::State_Modified; - (base ? record.mBase : record.mModified) = ref; + (base ? record.mBase : record.mModified) = std::move(ref); setRecord (index, record); } diff --git a/apps/opencs/model/world/resources.cpp b/apps/opencs/model/world/resources.cpp index b40ab1389..c3eb9762e 100644 --- a/apps/opencs/model/world/resources.cpp +++ b/apps/opencs/model/world/resources.cpp @@ -20,13 +20,13 @@ void CSMWorld::Resources::recreate(const VFS::Manager* vfs, const char * const * mFiles.clear(); mIndex.clear(); - int baseSize = mBaseDirectory.size(); + size_t baseSize = mBaseDirectory.size(); const std::map& index = vfs->getIndex(); for (std::map::const_iterator it = index.begin(); it != index.end(); ++it) { std::string filepath = it->first; - if (static_cast (filepath.size())(mFiles.size()); } std::string CSMWorld::Resources::getId (int index) const diff --git a/apps/opencs/view/doc/operations.cpp b/apps/opencs/view/doc/operations.cpp index 7ee4b8726..9ed77ff9c 100644 --- a/apps/opencs/view/doc/operations.cpp +++ b/apps/opencs/view/doc/operations.cpp @@ -30,7 +30,7 @@ void CSVDoc::Operations::setProgress (int current, int max, int type, int thread return; } - int oldCount = mOperations.size(); + int oldCount = static_cast(mOperations.size()); int newCount = oldCount + 1; Operation *operation = new Operation (type, this); @@ -51,7 +51,7 @@ void CSVDoc::Operations::quitOperation (int type) for (std::vector::iterator iter (mOperations.begin()); iter!=mOperations.end(); ++iter) if ((*iter)->getType()==type) { - int oldCount = mOperations.size(); + int oldCount = static_cast(mOperations.size()); int newCount = oldCount - 1; mLayout->removeItem ((*iter)->getLayout()); diff --git a/apps/opencs/view/doc/view.cpp b/apps/opencs/view/doc/view.cpp index 6fe01dc27..4514cfb58 100644 --- a/apps/opencs/view/doc/view.cpp +++ b/apps/opencs/view/doc/view.cpp @@ -749,7 +749,7 @@ void CSVDoc::View::infoAbout() "%4https://openmw.org" "%5https://forum.openmw.org" "%6https://gitlab.com/OpenMW/openmw/issues" - "%7irc://irc.freenode.net/#openmw" + "%7ircs://irc.libera.chat/#openmw" "" "

") .arg(versionInfo diff --git a/apps/opencs/view/doc/viewmanager.cpp b/apps/opencs/view/doc/viewmanager.cpp index 8cca3a849..95f9f606d 100644 --- a/apps/opencs/view/doc/viewmanager.cpp +++ b/apps/opencs/view/doc/viewmanager.cpp @@ -11,7 +11,6 @@ #include "../../model/doc/documentmanager.hpp" #include "../../model/doc/document.hpp" #include "../../model/world/columns.hpp" -#include "../../model/world/universalid.hpp" #include "../../model/world/idcompletionmanager.hpp" #include "../../model/prefs/state.hpp" diff --git a/apps/opencs/view/render/cellborder.cpp b/apps/opencs/view/render/cellborder.cpp index 6073807ce..d8ff63801 100644 --- a/apps/opencs/view/render/cellborder.cpp +++ b/apps/opencs/view/render/cellborder.cpp @@ -2,7 +2,6 @@ #include #include -#include #include #include @@ -13,15 +12,23 @@ #include "../../model/world/cellcoordinates.hpp" const int CSVRender::CellBorder::CellSize = ESM::Land::REAL_SIZE; -const int CSVRender::CellBorder::VertexCount = (ESM::Land::LAND_SIZE * 4) - 3; + +/* + The number of vertices per cell border is equal to the number of vertices per edge + minus the duplicated corner vertices. An additional vertex to close the loop is NOT needed. +*/ +const int CSVRender::CellBorder::VertexCount = (ESM::Land::LAND_SIZE * 4) - 4; CSVRender::CellBorder::CellBorder(osg::Group* cellNode, const CSMWorld::CellCoordinates& coords) : mParentNode(cellNode) { + mBorderGeometry = new osg::Geometry(); + mBaseNode = new osg::PositionAttitudeTransform(); mBaseNode->setNodeMask(Mask_CellBorder); mBaseNode->setPosition(osg::Vec3f(coords.getX() * CellSize, coords.getY() * CellSize, 10)); + mBaseNode->addChild(mBorderGeometry); mParentNode->addChild(mBaseNode); } @@ -38,56 +45,59 @@ void CSVRender::CellBorder::buildShape(const ESM::Land& esmLand) if (!landData) return; - osg::ref_ptr geometry = new osg::Geometry(); + mBaseNode->removeChild(mBorderGeometry); + mBorderGeometry = new osg::Geometry(); - // Vertices osg::ref_ptr vertices = new osg::Vec3Array(); - int x = 0, y = 0; - for (; x < ESM::Land::LAND_SIZE; ++x) + int x = 0; + int y = 0; + + /* + Traverse the cell border counter-clockwise starting at the SW corner vertex (0, 0). + Each loop starts at a corner vertex and ends right before the next corner vertex. + */ + for (; x < ESM::Land::LAND_SIZE - 1; ++x) vertices->push_back(osg::Vec3f(scaleToWorld(x), scaleToWorld(y), landData->mHeights[landIndex(x, y)])); x = ESM::Land::LAND_SIZE - 1; - for (; y < ESM::Land::LAND_SIZE; ++y) + for (; y < ESM::Land::LAND_SIZE - 1; ++y) vertices->push_back(osg::Vec3f(scaleToWorld(x), scaleToWorld(y), landData->mHeights[landIndex(x, y)])); y = ESM::Land::LAND_SIZE - 1; - for (; x >= 0; --x) + for (; x > 0; --x) vertices->push_back(osg::Vec3f(scaleToWorld(x), scaleToWorld(y), landData->mHeights[landIndex(x, y)])); x = 0; - for (; y >= 0; --y) + for (; y > 0; --y) vertices->push_back(osg::Vec3f(scaleToWorld(x), scaleToWorld(y), landData->mHeights[landIndex(x, y)])); - geometry->setVertexArray(vertices); + mBorderGeometry->setVertexArray(vertices); - // Color osg::ref_ptr colors = new osg::Vec4Array(); colors->push_back(osg::Vec4f(0.f, 0.5f, 0.f, 1.f)); - geometry->setColorArray(colors, osg::Array::BIND_PER_PRIMITIVE_SET); + mBorderGeometry->setColorArray(colors, osg::Array::BIND_PER_PRIMITIVE_SET); - // Primitive osg::ref_ptr primitives = - new osg::DrawElementsUShort(osg::PrimitiveSet::LINE_STRIP, VertexCount+1); + new osg::DrawElementsUShort(osg::PrimitiveSet::LINE_STRIP, VertexCount + 1); + // Assign one primitive to each vertex. for (size_t i = 0; i < VertexCount; ++i) primitives->setElement(i, i); + // Assign the last primitive to the first vertex to close the loop. primitives->setElement(VertexCount, 0); - geometry->addPrimitiveSet(primitives); - geometry->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF); - + mBorderGeometry->addPrimitiveSet(primitives); + mBorderGeometry->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF); - osg::ref_ptr geode = new osg::Geode(); - geode->addDrawable(geometry); - mBaseNode->addChild(geode); + mBaseNode->addChild(mBorderGeometry); } size_t CSVRender::CellBorder::landIndex(int x, int y) { - return y * ESM::Land::LAND_SIZE + x; + return static_cast(y) * ESM::Land::LAND_SIZE + x; } float CSVRender::CellBorder::scaleToWorld(int value) diff --git a/apps/opencs/view/render/cellborder.hpp b/apps/opencs/view/render/cellborder.hpp index c91aa46c6..be2e18eee 100644 --- a/apps/opencs/view/render/cellborder.hpp +++ b/apps/opencs/view/render/cellborder.hpp @@ -7,6 +7,7 @@ namespace osg { + class Geometry; class Group; class PositionAttitudeTransform; } @@ -47,7 +48,7 @@ namespace CSVRender osg::Group* mParentNode; osg::ref_ptr mBaseNode; - + osg::ref_ptr mBorderGeometry; }; } diff --git a/apps/opencs/view/render/commands.cpp b/apps/opencs/view/render/commands.cpp new file mode 100644 index 000000000..699bf5d01 --- /dev/null +++ b/apps/opencs/view/render/commands.cpp @@ -0,0 +1,44 @@ +#include "commands.hpp" + +#include + +#include +#include + +#include "editmode.hpp" +#include "terrainselection.hpp" +#include "terrainshapemode.hpp" +#include "terraintexturemode.hpp" +#include "worldspacewidget.hpp" + +CSVRender::DrawTerrainSelectionCommand::DrawTerrainSelectionCommand(WorldspaceWidget* worldspaceWidget, QUndoCommand* parent) + : mWorldspaceWidget(worldspaceWidget) +{ } + +void CSVRender::DrawTerrainSelectionCommand::redo() +{ + tryUpdate(); +} + +void CSVRender::DrawTerrainSelectionCommand::undo() +{ + tryUpdate(); +} + +void CSVRender::DrawTerrainSelectionCommand::tryUpdate() +{ + if (!mWorldspaceWidget) + { + Log(Debug::Verbose) << "Can't update terrain selection, no WorldspaceWidget found!"; + return; + } + + auto terrainMode = dynamic_cast(mWorldspaceWidget->getEditMode()); + if (!terrainMode) + { + Log(Debug::Verbose) << "Can't update terrain selection in current EditMode"; + return; + } + + terrainMode->getTerrainSelection()->update(); +} diff --git a/apps/opencs/view/render/commands.hpp b/apps/opencs/view/render/commands.hpp new file mode 100644 index 000000000..62b7fbfdc --- /dev/null +++ b/apps/opencs/view/render/commands.hpp @@ -0,0 +1,42 @@ +#ifndef CSV_RENDER_COMMANDS_HPP +#define CSV_RENDER_COMMANDS_HPP + +#include + +#include + +#include "worldspacewidget.hpp" + +namespace CSVRender +{ + class TerrainSelection; + + /* + Current solution to force a redrawing of the terrain-selection grid + when undoing/redoing changes in the editor. + This only triggers a simple redraw of the grid, so only use it in + conjunction with actual data changes which deform the grid. + + Please note that this command needs to be put onto the QUndoStack twice: + at the start and at the end of the related terrain manipulation. + This makes sure that the grid is always updated after all changes have + been undone or redone -- but it also means that the selection is redrawn + once at the beginning of either action. Future refinement may solve that. + */ + class DrawTerrainSelectionCommand : public QUndoCommand + { + + private: + QPointer mWorldspaceWidget; + + public: + DrawTerrainSelectionCommand(WorldspaceWidget* worldspaceWidget, QUndoCommand* parent = nullptr); + + void redo() override; + void undo() override; + + void tryUpdate(); + }; +} + +#endif diff --git a/apps/opencs/view/render/orbitcameramode.cpp b/apps/opencs/view/render/orbitcameramode.cpp index 79ac0167a..c81402ed1 100644 --- a/apps/opencs/view/render/orbitcameramode.cpp +++ b/apps/opencs/view/render/orbitcameramode.cpp @@ -3,7 +3,6 @@ #include #include "../../model/prefs/shortcut.hpp" -#include "../../model/prefs/shortcuteventhandler.hpp" #include "worldspacewidget.hpp" diff --git a/apps/opencs/view/render/pagedworldspacewidget.cpp b/apps/opencs/view/render/pagedworldspacewidget.cpp index ed3558422..a0b4de979 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.cpp +++ b/apps/opencs/view/render/pagedworldspacewidget.cpp @@ -7,18 +7,14 @@ #include #include -#include - #include #include "../../model/prefs/shortcut.hpp" -#include "../../model/world/tablemimedata.hpp" #include "../../model/world/idtable.hpp" #include "../widget/scenetooltoggle2.hpp" #include "../widget/scenetoolmode.hpp" -#include "../widget/scenetooltoggle2.hpp" #include "editmode.hpp" #include "mask.hpp" diff --git a/apps/opencs/view/render/pathgridmode.cpp b/apps/opencs/view/render/pathgridmode.cpp index 7a3fc8ecf..193cb664d 100644 --- a/apps/opencs/view/render/pathgridmode.cpp +++ b/apps/opencs/view/render/pathgridmode.cpp @@ -9,8 +9,6 @@ #include "../../model/world/commands.hpp" #include "../../model/world/commandmacro.hpp" -#include "../../model/world/idtable.hpp" -#include "../../model/world/idtree.hpp" #include "../widget/scenetoolbar.hpp" diff --git a/apps/opencs/view/render/scenewidget.cpp b/apps/opencs/view/render/scenewidget.cpp index 97f4c6ce2..c444cc8c6 100644 --- a/apps/opencs/view/render/scenewidget.cpp +++ b/apps/opencs/view/render/scenewidget.cpp @@ -26,7 +26,6 @@ #include "../../model/prefs/state.hpp" #include "../../model/prefs/shortcut.hpp" -#include "../../model/prefs/shortcuteventhandler.hpp" #include "lighting.hpp" #include "mask.hpp" diff --git a/apps/opencs/view/render/terrainselection.cpp b/apps/opencs/view/render/terrainselection.cpp index 4e209af57..0593917e0 100644 --- a/apps/opencs/view/render/terrainselection.cpp +++ b/apps/opencs/view/render/terrainselection.cpp @@ -39,64 +39,23 @@ std::vector> CSVRender::TerrainSelection::getTerrainSelectio void CSVRender::TerrainSelection::onlySelect(const std::vector> &localPositions) { mSelection = localPositions; + update(); } -void CSVRender::TerrainSelection::addSelect(const std::pair &localPos) +void CSVRender::TerrainSelection::addSelect(const std::vector>& localPositions, bool toggleInProgress) { - if (std::find(mSelection.begin(), mSelection.end(), localPos) == mSelection.end()) - { - mSelection.emplace_back(localPos); - update(); - } + handleSelection(localPositions, toggleInProgress, SelectionMethod::AddSelect); } -void CSVRender::TerrainSelection::toggleSelect(const std::vector> &localPositions, bool toggleInProgress) +void CSVRender::TerrainSelection::removeSelect(const std::vector>& localPositions, bool toggleInProgress) { - if (toggleInProgress) - { - for(auto const& localPos: localPositions) - { - auto iterTemp = std::find(mTemporarySelection.begin(), mTemporarySelection.end(), localPos); - mDraggedOperationFlag = true; - - if (iterTemp == mTemporarySelection.end()) - { - auto iter = std::find(mSelection.begin(), mSelection.end(), localPos); - if (iter != mSelection.end()) - { - mSelection.erase(iter); - } - else - { - mSelection.emplace_back(localPos); - } - } + handleSelection(localPositions, toggleInProgress, SelectionMethod::RemoveSelect); +} - mTemporarySelection.push_back(localPos); - } - } - else if (mDraggedOperationFlag == false) - { - for(auto const& localPos: localPositions) - { - const auto iter = std::find(mSelection.begin(), mSelection.end(), localPos); - if (iter != mSelection.end()) - { - mSelection.erase(iter); - } - else - { - mSelection.emplace_back(localPos); - } - } - } - else - { - mDraggedOperationFlag = false; - mTemporarySelection.clear(); - } - update(); +void CSVRender::TerrainSelection::toggleSelect(const std::vector>& localPositions, bool toggleInProgress) +{ + handleSelection(localPositions, toggleInProgress, SelectionMethod::ToggleSelect); } void CSVRender::TerrainSelection::activate() @@ -239,6 +198,100 @@ void CSVRender::TerrainSelection::drawTextureSelection(const osg::ref_ptr>& localPositions, bool toggleInProgress, SelectionMethod selectionMethod) +{ + if (toggleInProgress) + { + for (auto const& localPos : localPositions) + { + auto iterTemp = std::find(mTemporarySelection.begin(), mTemporarySelection.end(), localPos); + mDraggedOperationFlag = true; + + if (iterTemp == mTemporarySelection.end()) + { + auto iter = std::find(mSelection.begin(), mSelection.end(), localPos); + + switch (selectionMethod) + { + case SelectionMethod::AddSelect: + if (iter == mSelection.end()) + { + mSelection.emplace_back(localPos); + } + + break; + case SelectionMethod::RemoveSelect: + if (iter != mSelection.end()) + { + mSelection.erase(iter); + } + + break; + case SelectionMethod::ToggleSelect: + if (iter == mSelection.end()) + { + mSelection.emplace_back(localPos); + } + else + { + mSelection.erase(iter); + } + + break; + default: break; + } + } + + mTemporarySelection.push_back(localPos); + } + } + else if (mDraggedOperationFlag == false) + { + for (auto const& localPos : localPositions) + { + const auto iter = std::find(mSelection.begin(), mSelection.end(), localPos); + + switch (selectionMethod) + { + case SelectionMethod::AddSelect: + if (iter == mSelection.end()) + { + mSelection.emplace_back(localPos); + } + + break; + case SelectionMethod::RemoveSelect: + if (iter != mSelection.end()) + { + mSelection.erase(iter); + } + + break; + case SelectionMethod::ToggleSelect: + if (iter == mSelection.end()) + { + mSelection.emplace_back(localPos); + } + else + { + mSelection.erase(iter); + } + + break; + default: break; + } + } + } + else + { + mDraggedOperationFlag = false; + + mTemporarySelection.clear(); + } + + update(); +} + bool CSVRender::TerrainSelection::noCell(const std::string& cellId) { CSMDoc::Document& document = mWorldspaceWidget->getDocument(); diff --git a/apps/opencs/view/render/terrainselection.hpp b/apps/opencs/view/render/terrainselection.hpp index 84ee6f25a..18622ad13 100644 --- a/apps/opencs/view/render/terrainselection.hpp +++ b/apps/opencs/view/render/terrainselection.hpp @@ -27,6 +27,14 @@ namespace CSVRender Shape }; + enum class SelectionMethod + { + OnlySelect, + AddSelect, + RemoveSelect, + ToggleSelect + }; + /// \brief Class handling the terrain selection data and rendering class TerrainSelection { @@ -36,7 +44,8 @@ namespace CSVRender ~TerrainSelection(); void onlySelect(const std::vector> &localPositions); - void addSelect(const std::pair &localPos); + void addSelect(const std::vector>& localPositions, bool toggleInProgress); + void removeSelect(const std::vector>& localPositions, bool toggleInProgress); void toggleSelect(const std::vector> &localPositions, bool toggleInProgress); void activate(); @@ -55,6 +64,8 @@ namespace CSVRender private: + void handleSelection(const std::vector>& localPositions, bool toggleInProgress, SelectionMethod selectionMethod); + bool noCell(const std::string& cellId); bool noLand(const std::string& cellId); diff --git a/apps/opencs/view/render/terrainshapemode.cpp b/apps/opencs/view/render/terrainshapemode.cpp index e495d2355..050494495 100644 --- a/apps/opencs/view/render/terrainshapemode.cpp +++ b/apps/opencs/view/render/terrainshapemode.cpp @@ -25,18 +25,16 @@ #include "../../model/doc/document.hpp" #include "../../model/prefs/state.hpp" -#include "../../model/world/columnbase.hpp" -#include "../../model/world/commandmacro.hpp" #include "../../model/world/commands.hpp" #include "../../model/world/data.hpp" #include "../../model/world/idtable.hpp" #include "../../model/world/idtree.hpp" #include "../../model/world/land.hpp" -#include "../../model/world/resourcetable.hpp" #include "../../model/world/tablemimedata.hpp" #include "../../model/world/universalid.hpp" #include "brushdraw.hpp" +#include "commands.hpp" #include "editmode.hpp" #include "pagedworldspacewidget.hpp" #include "mask.hpp" @@ -45,7 +43,7 @@ #include "worldspacewidget.hpp" CSVRender::TerrainShapeMode::TerrainShapeMode (WorldspaceWidget *worldspaceWidget, osg::Group* parentNode, QWidget *parent) -: EditMode (worldspaceWidget, QIcon {":scenetoolbar/editing-terrain-shape"}, Mask_Terrain | Mask_Reference, "Terrain land editing", parent), +: EditMode (worldspaceWidget, QIcon {":scenetoolbar/editing-terrain-shape"}, Mask_Terrain, "Terrain land editing", parent), mParentNode(parentNode) { } @@ -288,6 +286,9 @@ void CSVRender::TerrainShapeMode::applyTerrainEditChanges() undoStack.beginMacro ("Edit shape and normal records"); + // One command at the beginning of the macro for redrawing the terrain-selection grid when undoing the changes. + undoStack.push(new DrawTerrainSelectionCommand(&getWorldspaceWidget())); + for(CSMWorld::CellCoordinates cellCoordinates: mAlteredCells) { std::string cellId = CSMWorld::CellCoordinates::generateId(cellCoordinates.getX(), cellCoordinates.getY()); @@ -356,6 +357,9 @@ void CSVRender::TerrainShapeMode::applyTerrainEditChanges() } pushNormalsEditToCommand(landNormalsNew, document, landTable, cellId); } + // One command at the end of the macro for redrawing the terrain-selection grid when redoing the changes. + undoStack.push(new DrawTerrainSelectionCommand(&getWorldspaceWidget())); + undoStack.endMacro(); clearTransientEdits(); } @@ -433,7 +437,9 @@ void CSVRender::TerrainShapeMode::editTerrainShapeGrid(const std::pair float smoothedByDistance = 0.0f; if (mShapeEditTool == ShapeEditTool_Drag) smoothedByDistance = calculateBumpShape(distance, r, mTotalDiffY); if (mShapeEditTool == ShapeEditTool_PaintToRaise || mShapeEditTool == ShapeEditTool_PaintToLower) smoothedByDistance = calculateBumpShape(distance, r, r + mShapeEditToolStrength); - if (distance <= r) + + // Using floating-point radius here to prevent selecting too few vertices. + if (distance <= mBrushSize / 2.0f) { if (mShapeEditTool == ShapeEditTool_Drag) alterHeight(cellCoords, x, y, smoothedByDistance); if (mShapeEditTool == ShapeEditTool_PaintToRaise || mShapeEditTool == ShapeEditTool_PaintToLower) @@ -1036,10 +1042,35 @@ void CSVRender::TerrainShapeMode::handleSelection(int globalSelectionX, int glob return; int selectionX = globalSelectionX; int selectionY = globalSelectionY; - if (xIsAtCellBorder) + + /* + The northern and eastern edges don't belong to the current cell. + If the corresponding adjacent cell is not loaded, some special handling is necessary to select border vertices. + */ + if (xIsAtCellBorder && yIsAtCellBorder) + { + /* + Handle the NW, NE, and SE corner vertices. + NW corner: (+1, -1) offset to reach current cell. + NE corner: (-1, -1) offset to reach current cell. + SE corner: (-1, +1) offset to reach current cell. + */ + if (isInCellSelection(globalSelectionX - 1, globalSelectionY - 1) + || isInCellSelection(globalSelectionX + 1, globalSelectionY - 1) + || isInCellSelection(globalSelectionX - 1, globalSelectionY + 1)) + { + selections->emplace_back(globalSelectionX, globalSelectionY); + } + } + else if (xIsAtCellBorder) + { selectionX--; - if (yIsAtCellBorder) + } + else if (yIsAtCellBorder) + { selectionY--; + } + if (isInCellSelection(selectionX, selectionY)) selections->emplace_back(globalSelectionX, globalSelectionY); } @@ -1074,8 +1105,11 @@ void CSVRender::TerrainShapeMode::selectTerrainShapes(const std::pair& { int distanceX = abs(i - vertexCoords.first); int distanceY = abs(j - vertexCoords.second); - int distance = std::round(sqrt(pow(distanceX, 2)+pow(distanceY, 2))); - if (distance <= r) handleSelection(i, j, &selections); + float distance = sqrt(pow(distanceX, 2)+pow(distanceY, 2)); + + // Using floating-point radius here to prevent selecting too few vertices. + if (distance <= mBrushSize / 2.0f) + handleSelection(i, j, &selections); } } } @@ -1092,9 +1126,21 @@ void CSVRender::TerrainShapeMode::selectTerrainShapes(const std::pair& } } - if(selectMode == 0) mTerrainShapeSelection->onlySelect(selections); - if(selectMode == 1) mTerrainShapeSelection->toggleSelect(selections, dragOperation); + std::string selectAction; + if (selectMode == 0) + selectAction = CSMPrefs::get()["3D Scene Editing"]["primary-select-action"].toString(); + else + selectAction = CSMPrefs::get()["3D Scene Editing"]["secondary-select-action"].toString(); + + if (selectAction == "Select only") + mTerrainShapeSelection->onlySelect(selections); + else if (selectAction == "Add to selection") + mTerrainShapeSelection->addSelect(selections, dragOperation); + else if (selectAction == "Remove from selection") + mTerrainShapeSelection->removeSelect(selections, dragOperation); + else if (selectAction == "Invert selection") + mTerrainShapeSelection->toggleSelect(selections, dragOperation); } void CSVRender::TerrainShapeMode::pushEditToCommand(const CSMWorld::LandHeightsColumn::DataType& newLandGrid, CSMDoc::Document& document, @@ -1398,6 +1444,11 @@ void CSVRender::TerrainShapeMode::mouseMoveEvent (QMouseEvent *event) mBrushDraw->hide(); } +std::shared_ptr CSVRender::TerrainShapeMode::getTerrainSelection() +{ + return mTerrainShapeSelection; +} + void CSVRender::TerrainShapeMode::setBrushSize(int brushSize) { mBrushSize = brushSize; diff --git a/apps/opencs/view/render/terrainshapemode.hpp b/apps/opencs/view/render/terrainshapemode.hpp index a88e60c9c..abc4b8aba 100644 --- a/apps/opencs/view/render/terrainshapemode.hpp +++ b/apps/opencs/view/render/terrainshapemode.hpp @@ -92,6 +92,8 @@ namespace CSVRender void dragMoveEvent (QDragMoveEvent *event) override; void mouseMoveEvent (QMouseEvent *event) override; + std::shared_ptr getTerrainSelection(); + private: /// Remove duplicates and sort mAlteredCells, then limitAlteredHeights forward and reverse @@ -176,7 +178,7 @@ namespace CSVRender int mDragMode = InteractionType_None; osg::Group* mParentNode; bool mIsEditing = false; - std::unique_ptr mTerrainShapeSelection; + std::shared_ptr mTerrainShapeSelection; int mTotalDiffY = 0; std::vector mAlteredCells; osg::Vec3d mEditingPos; diff --git a/apps/opencs/view/render/terraintexturemode.cpp b/apps/opencs/view/render/terraintexturemode.cpp index fa46cf673..dfcc29ae0 100644 --- a/apps/opencs/view/render/terraintexturemode.cpp +++ b/apps/opencs/view/render/terraintexturemode.cpp @@ -12,23 +12,17 @@ #include -#include - #include "../widget/modebutton.hpp" #include "../widget/scenetoolbar.hpp" #include "../widget/scenetooltexturebrush.hpp" #include "../../model/doc/document.hpp" #include "../../model/prefs/state.hpp" -#include "../../model/world/columnbase.hpp" -#include "../../model/world/commandmacro.hpp" #include "../../model/world/commands.hpp" #include "../../model/world/data.hpp" #include "../../model/world/idtable.hpp" #include "../../model/world/idtree.hpp" -#include "../../model/world/land.hpp" #include "../../model/world/landtexture.hpp" -#include "../../model/world/resourcetable.hpp" #include "../../model/world/tablemimedata.hpp" #include "../../model/world/universalid.hpp" #include "../widget/brushshapes.hpp" @@ -332,7 +326,7 @@ void CSVRender::TerrainTextureMode::editTerrainTextureGrid(const WorldspaceHitRe int textureColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandTexturesIndex); - std::size_t hashlocation = mBrushTexture.find("#"); + std::size_t hashlocation = mBrushTexture.find('#'); std::string mBrushTextureInt = mBrushTexture.substr (hashlocation+1); int brushInt = stoi(mBrushTexture.substr (hashlocation+1))+1; // All indices are offset by +1 @@ -718,6 +712,11 @@ void CSVRender::TerrainTextureMode::mouseMoveEvent (QMouseEvent *event) mBrushDraw->hide(); } +std::shared_ptr CSVRender::TerrainTextureMode::getTerrainSelection() +{ + return mTerrainTextureSelection; +} + void CSVRender::TerrainTextureMode::setBrushSize(int brushSize) { diff --git a/apps/opencs/view/render/terraintexturemode.hpp b/apps/opencs/view/render/terraintexturemode.hpp index 31932df21..e0c6e4b40 100644 --- a/apps/opencs/view/render/terraintexturemode.hpp +++ b/apps/opencs/view/render/terraintexturemode.hpp @@ -85,6 +85,8 @@ namespace CSVRender void mouseMoveEvent (QMouseEvent *event) override; + std::shared_ptr getTerrainSelection(); + private: /// \brief Handle brush mechanics, maths regarding worldspace hit etc. void editTerrainTextureGrid (const WorldspaceHitResult& hit); @@ -115,7 +117,7 @@ namespace CSVRender int mDragMode; osg::Group* mParentNode; bool mIsEditing; - std::unique_ptr mTerrainTextureSelection; + std::shared_ptr mTerrainTextureSelection; const int cellSize {ESM::Land::REAL_SIZE}; const int landTextureSize {ESM::Land::LAND_TEXTURE_SIZE}; diff --git a/apps/opencs/view/render/unpagedworldspacewidget.cpp b/apps/opencs/view/render/unpagedworldspacewidget.cpp index c4fef45dd..20dc5b8d1 100644 --- a/apps/opencs/view/render/unpagedworldspacewidget.cpp +++ b/apps/opencs/view/render/unpagedworldspacewidget.cpp @@ -12,7 +12,6 @@ #include "../../model/world/idtable.hpp" #include "../../model/world/tablemimedata.hpp" -#include "../widget/scenetooltoggle.hpp" #include "../widget/scenetooltoggle2.hpp" #include "cameracontroller.hpp" diff --git a/apps/opencs/view/render/worldspacewidget.cpp b/apps/opencs/view/render/worldspacewidget.cpp index 6eb437daf..2ebc083f2 100644 --- a/apps/opencs/view/render/worldspacewidget.cpp +++ b/apps/opencs/view/render/worldspacewidget.cpp @@ -17,7 +17,6 @@ #include "../../model/world/idtable.hpp" #include "../../model/prefs/shortcut.hpp" -#include "../../model/prefs/shortcuteventhandler.hpp" #include "../../model/prefs/state.hpp" #include "../render/orbitcameramode.hpp" @@ -453,6 +452,11 @@ CSVRender::WorldspaceHitResult CSVRender::WorldspaceWidget::mousePick (const QPo return hit; } +CSVRender::EditMode *CSVRender::WorldspaceWidget::getEditMode() +{ + return dynamic_cast (mEditMode->getCurrent()); +} + void CSVRender::WorldspaceWidget::abortDrag() { if (mDragging) @@ -698,11 +702,6 @@ void CSVRender::WorldspaceWidget::handleInteractionPress (const WorldspaceHitRes editMode.primaryOpenPressed (hit); } -CSVRender::EditMode *CSVRender::WorldspaceWidget::getEditMode() -{ - return dynamic_cast (mEditMode->getCurrent()); -} - void CSVRender::WorldspaceWidget::primaryOpen(bool activate) { handleInteraction(InteractionType_PrimaryOpen, activate); diff --git a/apps/opencs/view/render/worldspacewidget.hpp b/apps/opencs/view/render/worldspacewidget.hpp index 5e224b380..cf244ce71 100644 --- a/apps/opencs/view/render/worldspacewidget.hpp +++ b/apps/opencs/view/render/worldspacewidget.hpp @@ -189,6 +189,8 @@ namespace CSVRender /// Erase all overrides and restore the visual representation to its true state. virtual void reset (unsigned int elementMask) = 0; + EditMode *getEditMode(); + protected: /// Visual elements in a scene @@ -215,8 +217,6 @@ namespace CSVRender void settingChanged (const CSMPrefs::Setting *setting) override; - EditMode *getEditMode(); - bool getSpeedMode(); private: diff --git a/apps/opencs/view/widget/scenetoolrun.cpp b/apps/opencs/view/widget/scenetoolrun.cpp index b53282036..24bcf3f13 100644 --- a/apps/opencs/view/widget/scenetoolrun.cpp +++ b/apps/opencs/view/widget/scenetoolrun.cpp @@ -30,7 +30,7 @@ void CSVWidget::SceneToolRun::updateIcon() void CSVWidget::SceneToolRun::updatePanel() { - mTable->setRowCount (mProfiles.size()); + mTable->setRowCount (static_cast(mProfiles.size())); int i = 0; diff --git a/apps/opencs/view/widget/scenetoolshapebrush.cpp b/apps/opencs/view/widget/scenetoolshapebrush.cpp index 4b2d20004..d0419a13a 100644 --- a/apps/opencs/view/widget/scenetoolshapebrush.cpp +++ b/apps/opencs/view/widget/scenetoolshapebrush.cpp @@ -27,11 +27,6 @@ #include "../../model/doc/document.hpp" #include "../../model/prefs/state.hpp" #include "../../model/world/commands.hpp" -#include "../../model/world/data.hpp" -#include "../../model/world/idcollection.hpp" -#include "../../model/world/idtable.hpp" -#include "../../model/world/landtexture.hpp" -#include "../../model/world/universalid.hpp" CSVWidget::ShapeBrushSizeControls::ShapeBrushSizeControls(const QString &title, QWidget *parent) diff --git a/apps/opencs/view/widget/scenetooltoggle.cpp b/apps/opencs/view/widget/scenetooltoggle.cpp index fa0be3155..04ac3322b 100644 --- a/apps/opencs/view/widget/scenetooltoggle.cpp +++ b/apps/opencs/view/widget/scenetooltoggle.cpp @@ -80,7 +80,7 @@ QRect CSVWidget::SceneToolToggle::getIconBox (int index) const int y = index / xMax; int x = index % xMax; - int total = mButtons.size(); + int total = static_cast(mButtons.size()); int actualYIcons = total/xMax; @@ -154,7 +154,7 @@ void CSVWidget::SceneToolToggle::addButton (const std::string& icon, unsigned in desc.mMask = mask; desc.mSmallIcon = smallIcon; desc.mName = name; - desc.mIndex = mButtons.size(); + desc.mIndex = static_cast(mButtons.size()); mButtons.insert (std::make_pair (button, desc)); diff --git a/apps/opencs/view/widget/scenetooltoggle2.cpp b/apps/opencs/view/widget/scenetooltoggle2.cpp index cf9bfe349..363cae570 100644 --- a/apps/opencs/view/widget/scenetooltoggle2.cpp +++ b/apps/opencs/view/widget/scenetooltoggle2.cpp @@ -99,7 +99,7 @@ void CSVWidget::SceneToolToggle2::addButton (unsigned int id, unsigned int mask, desc.mButtonId = id; desc.mMask = mask; desc.mName = name; - desc.mIndex = mButtons.size(); + desc.mIndex = static_cast(mButtons.size()); mButtons.insert (std::make_pair (button, desc)); diff --git a/apps/opencs/view/world/colordelegate.cpp b/apps/opencs/view/world/colordelegate.cpp index 15a07b42c..3b5f692fe 100644 --- a/apps/opencs/view/world/colordelegate.cpp +++ b/apps/opencs/view/world/colordelegate.cpp @@ -3,8 +3,6 @@ #include #include -#include "../widget/coloreditor.hpp" - CSVWorld::ColorDelegate::ColorDelegate(CSMWorld::CommandDispatcher *dispatcher, CSMDoc::Document& document, QObject *parent) diff --git a/apps/opencs/view/world/dialoguesubview.cpp b/apps/opencs/view/world/dialoguesubview.cpp index 3d3b3cdbe..f2360b137 100644 --- a/apps/opencs/view/world/dialoguesubview.cpp +++ b/apps/opencs/view/world/dialoguesubview.cpp @@ -28,7 +28,6 @@ #include "../../model/world/columns.hpp" #include "../../model/world/record.hpp" #include "../../model/world/tablemimedata.hpp" -#include "../../model/world/idtree.hpp" #include "../../model/world/commands.hpp" #include "../../model/doc/document.hpp" diff --git a/apps/opencs/view/world/idvalidator.cpp b/apps/opencs/view/world/idvalidator.cpp index 1092d7217..442157ac5 100644 --- a/apps/opencs/view/world/idvalidator.cpp +++ b/apps/opencs/view/world/idvalidator.cpp @@ -42,7 +42,7 @@ QValidator::State CSVWorld::IdValidator::validate (QString& input, int& pos) con if (!mNamespace.empty()) { - std::string namespace_ = input.left (mNamespace.size()).toUtf8().constData(); + std::string namespace_ = input.left (static_cast(mNamespace.size())).toUtf8().constData(); if (Misc::StringUtils::lowerCase (namespace_)!=mNamespace) return QValidator::Invalid; // incorrect namespace diff --git a/apps/opencs/view/world/scenesubview.cpp b/apps/opencs/view/world/scenesubview.cpp index 44542c529..58d159a17 100644 --- a/apps/opencs/view/world/scenesubview.cpp +++ b/apps/opencs/view/world/scenesubview.cpp @@ -15,7 +15,6 @@ #include "../render/pagedworldspacewidget.hpp" #include "../render/unpagedworldspacewidget.hpp" -#include "../render/editmode.hpp" #include "../widget/scenetoolbar.hpp" #include "../widget/scenetoolmode.hpp" diff --git a/apps/opencs/view/world/scriptsubview.cpp b/apps/opencs/view/world/scriptsubview.cpp index 096fc8a9e..6eab7aaa5 100644 --- a/apps/opencs/view/world/scriptsubview.cpp +++ b/apps/opencs/view/world/scriptsubview.cpp @@ -12,7 +12,6 @@ #include "../../model/doc/document.hpp" #include "../../model/world/universalid.hpp" #include "../../model/world/data.hpp" -#include "../../model/world/columnbase.hpp" #include "../../model/world/commands.hpp" #include "../../model/world/idtable.hpp" #include "../../model/prefs/state.hpp" diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index e0d2ea998..02348452f 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -184,7 +184,7 @@ if(APPLE) add_subdirectory(../../files/ ${CMAKE_CURRENT_BINARY_DIR}/files) - configure_file("${OpenMW_BINARY_DIR}/settings-default.cfg" ${BUNDLE_RESOURCES_DIR} COPYONLY) + configure_file("${OpenMW_BINARY_DIR}/defaults.bin" ${BUNDLE_RESOURCES_DIR} COPYONLY) configure_file("${OpenMW_BINARY_DIR}/openmw.cfg" ${BUNDLE_RESOURCES_DIR} COPYONLY) configure_file("${OpenMW_BINARY_DIR}/gamecontrollerdb.txt" ${BUNDLE_RESOURCES_DIR} COPYONLY) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 677174877..35e550582 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -496,8 +496,8 @@ void OMW::Engine::setSkipMenu (bool skipMenu, bool newGame) std::string OMW::Engine::loadSettings (Settings::Manager & settings) { // Create the settings manager and load default settings file - const std::string localdefault = (mCfgMgr.getLocalPath() / "settings-default.cfg").string(); - const std::string globaldefault = (mCfgMgr.getGlobalPath() / "settings-default.cfg").string(); + const std::string localdefault = (mCfgMgr.getLocalPath() / "defaults.bin").string(); + const std::string globaldefault = (mCfgMgr.getGlobalPath() / "defaults.bin").string(); // prefer local if (boost::filesystem::exists(localdefault)) @@ -505,7 +505,7 @@ std::string OMW::Engine::loadSettings (Settings::Manager & settings) else if (boost::filesystem::exists(globaldefault)) settings.loadDefault(globaldefault); else - throw std::runtime_error ("No default settings file found! Make sure the file \"settings-default.cfg\" was properly installed."); + throw std::runtime_error ("No default settings file found! Make sure the file \"defaults.bin\" was properly installed."); // load user settings if they exist const std::string settingspath = (mCfgMgr.getUserConfigPath() / "settings.cfg").string(); @@ -1081,6 +1081,8 @@ void OMW::Engine::go() // Save user settings settings.saveUser(settingspath); + mViewer->stopThreading(); + Log(Debug::Info) << "Quitting peacefully."; } diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index 709ffda2c..8f7e16058 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -44,6 +44,10 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat desc.add_options() ("help", "print help message") ("version", "print version information and quit") + + ("replace", bpo::value()->default_value(Files::EscapeStringVector(), "") + ->multitoken()->composing(), "settings where the values from the current source should replace those from lower-priority sources instead of being appended") + ("data", bpo::value()->default_value(Files::EscapePathContainer(), "data") ->multitoken()->composing(), "set data directories (later directories have higher priority)") @@ -192,6 +196,15 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat Log(Debug::Error) << "No content file given (esm/esp, nor omwgame/omwaddon). Aborting..."; return false; } + std::set contentDedupe; + for (const auto& contentFile : content) + { + if (!contentDedupe.insert(contentFile).second) + { + Log(Debug::Error) << "Content file specified more than once: " << contentFile << ". Aborting..."; + return false; + } + } for (auto& file : content) { diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 570399e91..b0c38f98d 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -301,7 +301,7 @@ namespace MWBase virtual MWWorld::Ptr moveObject(const MWWorld::Ptr &ptr, MWWorld::CellStore* newCell, float x, float y, float z, bool movePhysics=true) = 0; ///< @return an updated Ptr - virtual MWWorld::Ptr moveObjectBy(const MWWorld::Ptr &ptr, osg::Vec3f vec, bool moveToActive) = 0; + virtual MWWorld::Ptr moveObjectBy(const MWWorld::Ptr &ptr, osg::Vec3f vec, bool moveToActive, bool ignoreCollisions) = 0; ///< @return an updated Ptr virtual void scaleObject (const MWWorld::Ptr& ptr, float scale) = 0; diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 692ca72bb..b3c792bde 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -26,7 +26,6 @@ #include "../mwworld/failedaction.hpp" #include "../mwworld/customdata.hpp" #include "../mwworld/containerstore.hpp" -#include "../mwphysics/physicssystem.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/localscripts.hpp" diff --git a/apps/openmw/mwclass/door.cpp b/apps/openmw/mwclass/door.cpp index 3a5ff0d9a..849960a09 100644 --- a/apps/openmw/mwclass/door.cpp +++ b/apps/openmw/mwclass/door.cpp @@ -132,12 +132,14 @@ namespace MWClass MWBase::Environment::get().getWorld()->getMaxActivationDistance()) { MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(ptr); + if(animation) + { + const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); + int index = ESM::MagicEffect::effectStringToId("sEffectTelekinesis"); + const ESM::MagicEffect *effect = store.get().find(index); - const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); - int index = ESM::MagicEffect::effectStringToId("sEffectTelekinesis"); - const ESM::MagicEffect *effect = store.get().find(index); - - animation->addSpellCastGlow(effect, 1); // 1 second glow to match the time taken for a door opening or closing + animation->addSpellCastGlow(effect, 1); // 1 second glow to match the time taken for a door opening or closing + } } const std::string keyId = ptr.getCellRef().getKey(); diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 9d2c45b10..50206b32c 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -36,7 +36,6 @@ #include "../mwworld/failedaction.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwworld/customdata.hpp" -#include "../mwphysics/physicssystem.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/localscripts.hpp" diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp index 34fd5828f..3c6349ad8 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp @@ -343,7 +343,7 @@ namespace MWDialogue if(!inJournal(topicId, answer->mId)) { // Does this dialogue contains some actor-specific answer? - if (answer->mActor == mActor.getCellRef().getRefId()) + if (Misc::StringUtils::ciEqual(answer->mActor, mActor.getCellRef().getRefId())) flag |= MWBase::DialogueManager::TopicType::Specific; } else diff --git a/apps/openmw/mwdialogue/journalimp.hpp b/apps/openmw/mwdialogue/journalimp.hpp index a6501e54e..0c778d2ba 100644 --- a/apps/openmw/mwdialogue/journalimp.hpp +++ b/apps/openmw/mwdialogue/journalimp.hpp @@ -3,7 +3,6 @@ #include "../mwbase/journal.hpp" -#include "journalentry.hpp" #include "quest.hpp" namespace MWDialogue diff --git a/apps/openmw/mwgui/bookpage.hpp b/apps/openmw/mwgui/bookpage.hpp index f9fd93089..512a43992 100644 --- a/apps/openmw/mwgui/bookpage.hpp +++ b/apps/openmw/mwgui/bookpage.hpp @@ -10,7 +10,6 @@ #include #include -#include #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" diff --git a/apps/openmw/mwgui/console.cpp b/apps/openmw/mwgui/console.cpp index d0d3b87a6..b9004fcdb 100644 --- a/apps/openmw/mwgui/console.cpp +++ b/apps/openmw/mwgui/console.cpp @@ -9,6 +9,10 @@ #include #include +#include +#include +#include +#include #include "../mwscript/extensions.hpp" diff --git a/apps/openmw/mwgui/console.hpp b/apps/openmw/mwgui/console.hpp index 52aa42f2a..2b8cecbc0 100644 --- a/apps/openmw/mwgui/console.hpp +++ b/apps/openmw/mwgui/console.hpp @@ -6,12 +6,8 @@ #include #include -#include -#include -#include #include #include -#include #include "../mwscript/compilercontext.hpp" #include "../mwscript/interpretercontext.hpp" diff --git a/apps/openmw/mwgui/container.cpp b/apps/openmw/mwgui/container.cpp index b0913219f..c980ef3ea 100644 --- a/apps/openmw/mwgui/container.cpp +++ b/apps/openmw/mwgui/container.cpp @@ -12,7 +12,9 @@ #include "../mwworld/class.hpp" #include "../mwworld/inventorystore.hpp" +#include "../mwmechanics/aipackage.hpp" #include "../mwmechanics/creaturestats.hpp" +#include "../mwmechanics/summoning.hpp" #include "../mwscript/interpretercontext.hpp" @@ -268,6 +270,23 @@ namespace MWGui for (const auto& creature : creatureMap) MWBase::Environment::get().getMechanicsManager()->cleanupSummonedCreature(ptr, creature.second); creatureMap.clear(); + + // Check if we are a summon and inform our master we've bit the dust + for(const auto& package : creatureStats.getAiSequence()) + { + if(package->followTargetThroughDoors() && !package->getTarget().isEmpty()) + { + const auto& summoner = package->getTarget(); + auto& summons = summoner.getClass().getCreatureStats(summoner).getSummonedCreatureMap(); + auto it = std::find_if(summons.begin(), summons.end(), [&] (const auto& entry) { return entry.second == creatureStats.getActorId(); }); + if(it != summons.end()) + { + MWMechanics::purgeSummonEffect(summoner, *it); + summons.erase(it); + break; + } + } + } } MWBase::Environment::get().getWorld()->deleteObject(ptr); diff --git a/apps/openmw/mwgui/dialogue.cpp b/apps/openmw/mwgui/dialogue.cpp index eb2b36c8a..43c8f4ffe 100644 --- a/apps/openmw/mwgui/dialogue.cpp +++ b/apps/openmw/mwgui/dialogue.cpp @@ -310,7 +310,7 @@ namespace MWGui deleteLater(); for (Link* link : mLinks) delete link; - for (auto link : mTopicLinks) + for (const auto& link : mTopicLinks) delete link.second; for (auto history : mHistoryContents) delete history; diff --git a/apps/openmw/mwgui/itemmodel.cpp b/apps/openmw/mwgui/itemmodel.cpp index cf88efaae..4e4d77da4 100644 --- a/apps/openmw/mwgui/itemmodel.cpp +++ b/apps/openmw/mwgui/itemmodel.cpp @@ -2,7 +2,6 @@ #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" -#include "../mwworld/esmstore.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" diff --git a/apps/openmw/mwgui/itemview.cpp b/apps/openmw/mwgui/itemview.cpp index 94dcc77c5..14f2c1dd9 100644 --- a/apps/openmw/mwgui/itemview.cpp +++ b/apps/openmw/mwgui/itemview.cpp @@ -5,9 +5,7 @@ #include #include #include -#include #include -#include #include "../mwworld/class.hpp" diff --git a/apps/openmw/mwgui/journalbooks.cpp b/apps/openmw/mwgui/journalbooks.cpp index 065a503e6..40053b3c8 100644 --- a/apps/openmw/mwgui/journalbooks.cpp +++ b/apps/openmw/mwgui/journalbooks.cpp @@ -278,7 +278,7 @@ BookTypesetter::Ptr JournalBooks::createCyrillicJournalIndex () mIndexPagesCount = 2; } - unsigned char ch[2] = {0xd0, 0x90}; // CYRILLIC CAPITAL A is a 0xd090 in UTF-8 + unsigned char ch[3] = {0xd0, 0x90, 0x00}; // CYRILLIC CAPITAL A is a 0xd090 in UTF-8 for (int i = 0; i < 32; ++i) { diff --git a/apps/openmw/mwgui/levelupdialog.cpp b/apps/openmw/mwgui/levelupdialog.cpp index 86f92db6f..14de3fd27 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/cellstore.hpp" #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/npcstats.hpp" diff --git a/apps/openmw/mwgui/loadingscreen.cpp b/apps/openmw/mwgui/loadingscreen.cpp index 6501284fd..9fab582b5 100644 --- a/apps/openmw/mwgui/loadingscreen.cpp +++ b/apps/openmw/mwgui/loadingscreen.cpp @@ -26,8 +26,6 @@ #include "../mwbase/windowmanager.hpp" #include "../mwbase/inputmanager.hpp" -#include "../mwrender/vismask.hpp" - #include "backgroundimage.hpp" namespace MWGui @@ -103,7 +101,7 @@ namespace MWGui Log(Debug::Warning) << "Warning: no splash screens found!"; } - void LoadingScreen::setLabel(const std::string &label, bool important, bool center) + void LoadingScreen::setLabel(const std::string &label, bool important) { mImportantLabel = important; @@ -113,7 +111,7 @@ namespace MWGui size.width = std::max(300, size.width); mLoadingBox->setSize(size); - if (center) + if (MWBase::Environment::get().getWindowManager()->getMessagesCount() > 0) mLoadingBox->setPosition(mMainWidget->getWidth()/2 - mLoadingBox->getWidth()/2, mMainWidget->getHeight()/2 - mLoadingBox->getHeight()/2); else mLoadingBox->setPosition(mMainWidget->getWidth()/2 - mLoadingBox->getWidth()/2, mMainWidget->getHeight() - mLoadingBox->getHeight() - 8); diff --git a/apps/openmw/mwgui/loadingscreen.hpp b/apps/openmw/mwgui/loadingscreen.hpp index e1c652386..c64396534 100644 --- a/apps/openmw/mwgui/loadingscreen.hpp +++ b/apps/openmw/mwgui/loadingscreen.hpp @@ -3,7 +3,6 @@ #include -#include #include #include @@ -38,7 +37,7 @@ namespace MWGui virtual ~LoadingScreen(); /// Overridden from Loading::Listener, see the Loading::Listener documentation for usage details - void setLabel (const std::string& label, bool important, bool center) override; + void setLabel (const std::string& label, bool important) override; void loadingOn(bool visible=true) override; void loadingOff() override; void setProgressRange (size_t range) override; diff --git a/apps/openmw/mwgui/pickpocketitemmodel.cpp b/apps/openmw/mwgui/pickpocketitemmodel.cpp index b4de5cb50..5daea8f3f 100644 --- a/apps/openmw/mwgui/pickpocketitemmodel.cpp +++ b/apps/openmw/mwgui/pickpocketitemmodel.cpp @@ -7,7 +7,6 @@ #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/pickpocket.hpp" -#include "../mwworld/containerstore.hpp" #include "../mwworld/class.hpp" #include "../mwbase/environment.hpp" diff --git a/apps/openmw/mwgui/quickkeysmenu.hpp b/apps/openmw/mwgui/quickkeysmenu.hpp index d0e950979..b2742df79 100644 --- a/apps/openmw/mwgui/quickkeysmenu.hpp +++ b/apps/openmw/mwgui/quickkeysmenu.hpp @@ -1,8 +1,6 @@ #ifndef MWGUI_QUICKKEYS_H #define MWGUI_QUICKKEYS_H -#include "../mwworld/ptr.hpp" - #include "windowbase.hpp" #include "spellmodel.hpp" diff --git a/apps/openmw/mwgui/recharge.cpp b/apps/openmw/mwgui/recharge.cpp index f8d89c0cb..df6962c78 100644 --- a/apps/openmw/mwgui/recharge.cpp +++ b/apps/openmw/mwgui/recharge.cpp @@ -1,7 +1,6 @@ #include "recharge.hpp" #include -#include #include @@ -15,10 +14,10 @@ #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" -#include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/actorutil.hpp" #include "../mwmechanics/recharge.hpp" +#include "itemselection.hpp" #include "itemwidget.hpp" #include "itemchargeview.hpp" #include "sortfilteritemmodel.hpp" diff --git a/apps/openmw/mwgui/recharge.hpp b/apps/openmw/mwgui/recharge.hpp index 3d469bac5..c260b1554 100644 --- a/apps/openmw/mwgui/recharge.hpp +++ b/apps/openmw/mwgui/recharge.hpp @@ -3,8 +3,6 @@ #include "windowbase.hpp" -#include "itemselection.hpp" - namespace MWWorld { class Ptr; diff --git a/apps/openmw/mwgui/repair.cpp b/apps/openmw/mwgui/repair.cpp index ea79e0326..351b97603 100644 --- a/apps/openmw/mwgui/repair.cpp +++ b/apps/openmw/mwgui/repair.cpp @@ -3,8 +3,6 @@ #include #include -#include -#include #include @@ -17,6 +15,7 @@ #include "../mwworld/containerstore.hpp" #include "../mwworld/class.hpp" +#include "itemselection.hpp" #include "itemwidget.hpp" #include "itemchargeview.hpp" #include "sortfilteritemmodel.hpp" diff --git a/apps/openmw/mwgui/repair.hpp b/apps/openmw/mwgui/repair.hpp index 594ad2823..701009f54 100644 --- a/apps/openmw/mwgui/repair.hpp +++ b/apps/openmw/mwgui/repair.hpp @@ -3,8 +3,6 @@ #include "windowbase.hpp" -#include "itemselection.hpp" - #include "../mwmechanics/repair.hpp" namespace MWGui diff --git a/apps/openmw/mwgui/spellwindow.cpp b/apps/openmw/mwgui/spellwindow.cpp index dcd94b768..26149e147 100644 --- a/apps/openmw/mwgui/spellwindow.cpp +++ b/apps/openmw/mwgui/spellwindow.cpp @@ -1,9 +1,7 @@ #include "spellwindow.hpp" -#include #include #include -#include #include #include diff --git a/apps/openmw/mwgui/spellwindow.hpp b/apps/openmw/mwgui/spellwindow.hpp index cf5e88f8e..786a7d877 100644 --- a/apps/openmw/mwgui/spellwindow.hpp +++ b/apps/openmw/mwgui/spellwindow.hpp @@ -2,7 +2,6 @@ #define MWGUI_SPELLWINDOW_H #include "windowpinnablebase.hpp" -#include "../mwworld/ptr.hpp" #include "spellmodel.hpp" diff --git a/apps/openmw/mwgui/tooltips.cpp b/apps/openmw/mwgui/tooltips.cpp index df56684f7..356945dd0 100644 --- a/apps/openmw/mwgui/tooltips.cpp +++ b/apps/openmw/mwgui/tooltips.cpp @@ -272,14 +272,14 @@ namespace MWGui std::map userStrings = focus->getUserStrings(); for (auto& userStringPair : userStrings) { - size_t underscorePos = userStringPair.first.find("_"); + size_t underscorePos = userStringPair.first.find('_'); if (underscorePos == std::string::npos) continue; 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("^"); + size_t caretPos = key.find('^'); if (caretPos != std::string::npos) { type = key.substr(0, caretPos); diff --git a/apps/openmw/mwgui/tradewindow.cpp b/apps/openmw/mwgui/tradewindow.cpp index 0ccf1a144..c88f8048c 100644 --- a/apps/openmw/mwgui/tradewindow.cpp +++ b/apps/openmw/mwgui/tradewindow.cpp @@ -26,7 +26,6 @@ #include "containeritemmodel.hpp" #include "tradeitemmodel.hpp" #include "countdialog.hpp" -#include "controllers.hpp" #include "tooltips.hpp" namespace diff --git a/apps/openmw/mwgui/waitdialog.cpp b/apps/openmw/mwgui/waitdialog.cpp index 18cc187c1..42a70dcfa 100644 --- a/apps/openmw/mwgui/waitdialog.cpp +++ b/apps/openmw/mwgui/waitdialog.cpp @@ -23,8 +23,6 @@ #include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/actorutil.hpp" -#include "../mwstate/charactermanager.hpp" - namespace MWGui { diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 21bdb261e..541fcd82b 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -9,7 +9,6 @@ #include #include -#include #include #include #include @@ -65,7 +64,6 @@ #include "../mwworld/cellstore.hpp" #include "../mwworld/esmstore.hpp" -#include "../mwmechanics/stat.hpp" #include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/actorutil.hpp" @@ -1061,8 +1059,9 @@ namespace MWGui if(tag.compare(0, MyGuiPrefixLength, MyGuiPrefix) == 0) { tag = tag.substr(MyGuiPrefixLength, tag.length()); - std::string settingSection = tag.substr(0, tag.find(",")); - std::string settingTag = tag.substr(tag.find(",")+1, tag.length()); + size_t comma_pos = tag.find(','); + std::string settingSection = tag.substr(0, comma_pos); + std::string settingTag = tag.substr(comma_pos+1, tag.length()); _result = Settings::Manager::getString(settingTag, settingSection); } diff --git a/apps/openmw/mwinput/controllermanager.cpp b/apps/openmw/mwinput/controllermanager.cpp index 03d492c9c..abb214710 100644 --- a/apps/openmw/mwinput/controllermanager.cpp +++ b/apps/openmw/mwinput/controllermanager.cpp @@ -98,8 +98,8 @@ namespace MWInput // 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 float uiScale = MWBase::Environment::get().getWindowManager()->getScalingFactor(); - float xMove = xAxis * dt * 1500.0f / uiScale; - float yMove = yAxis * dt * 1500.0f / uiScale; + float xMove = xAxis * dt * 1500.0f / uiScale * mGamepadCursorSpeed; + float yMove = yAxis * dt * 1500.0f / uiScale * mGamepadCursorSpeed; float mouseWheelMove = -zAxis * dt * 1500.0f; if (xMove != 0 || yMove != 0 || mouseWheelMove != 0) diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 690183c57..436eab7ad 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -5,7 +5,6 @@ #include #include #include -#include #include "../mwbase/windowmanager.hpp" #include "../mwbase/environment.hpp" diff --git a/apps/openmw/mwinput/keyboardmanager.hpp b/apps/openmw/mwinput/keyboardmanager.hpp index f97f6b9e6..ca58461a2 100644 --- a/apps/openmw/mwinput/keyboardmanager.hpp +++ b/apps/openmw/mwinput/keyboardmanager.hpp @@ -1,7 +1,6 @@ #ifndef MWINPUT_MWKEYBOARDMANAGER_H #define MWINPUT_MWKEYBOARDMANAGER_H -#include #include namespace MWInput diff --git a/apps/openmw/mwinput/mousemanager.cpp b/apps/openmw/mwinput/mousemanager.cpp index 4843da5f1..7b6d00082 100644 --- a/apps/openmw/mwinput/mousemanager.cpp +++ b/apps/openmw/mwinput/mousemanager.cpp @@ -147,7 +147,8 @@ namespace MWInput void MouseManager::mousePressed(const SDL_MouseButtonEvent &arg, Uint8 id) { - MWBase::Environment::get().getInputManager()->setJoystickLastUsed(false); + MWBase::InputManager* input = MWBase::Environment::get().getInputManager(); + input->setJoystickLastUsed(false); bool guiMode = false; if (id == SDL_BUTTON_LEFT || id == SDL_BUTTON_RIGHT) // MyGUI only uses these mouse events @@ -168,7 +169,8 @@ namespace MWInput mBindingsManager->setPlayerControlsEnabled(!guiMode); // Don't trigger any mouse bindings while in settings menu, otherwise rebinding controls becomes impossible - if (MWBase::Environment::get().getWindowManager()->getMode() != MWGui::GM_Settings) + // Also do not trigger bindings when input controls are disabled, e.g. during save loading + if (MWBase::Environment::get().getWindowManager()->getMode() != MWGui::GM_Settings && !input->controlsDisabled()) mBindingsManager->mousePressed(arg, id); } diff --git a/apps/openmw/mwmechanics/activespells.hpp b/apps/openmw/mwmechanics/activespells.hpp index 4d36c717e..54f662fc2 100644 --- a/apps/openmw/mwmechanics/activespells.hpp +++ b/apps/openmw/mwmechanics/activespells.hpp @@ -5,7 +5,6 @@ #include #include -#include #include #include "../mwworld/timestamp.hpp" diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index e9467a679..c9fa3309d 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -1,5 +1,7 @@ #include "actors.hpp" +#include + #include #include @@ -40,7 +42,6 @@ #include "aiwander.hpp" #include "actor.hpp" #include "summoning.hpp" -#include "combat.hpp" #include "actorutil.hpp" #include "tickableeffects.hpp" @@ -174,6 +175,15 @@ namespace MWMechanics static const int GREETING_COOLDOWN = 40; // how many updates should pass before NPC can continue movement static const float DECELERATE_DISTANCE = 512.f; + namespace + { + float getTimeToDestination(const AiPackage& package, const osg::Vec3f& position, float speed, float duration, const osg::Vec3f& halfExtents) + { + const auto distanceToNextPathPoint = (package.getNextPathPoint(package.getDestination()) - position).length(); + return (distanceToNextPathPoint - package.getNextPathPointTolerance(speed, duration, halfExtents)) / speed; + } + } + class GetStuntedMagickaDuration : public MWMechanics::EffectSourceVisitor { public: @@ -590,7 +600,8 @@ namespace MWMechanics { greetingTimer++; - if (greetingTimer <= GREETING_SHOULD_END || MWBase::Environment::get().getSoundManager()->sayActive(actor)) + if (!stats.getMovementFlag(CreatureStats::Flag_ForceJump) && !stats.getMovementFlag(CreatureStats::Flag_ForceSneak) + && (greetingTimer <= GREETING_SHOULD_END || MWBase::Environment::get().getSoundManager()->sayActive(actor))) turnActorToFacePlayer(actor, actorState, dir); if (greetingTimer >= GREETING_COOLDOWN) @@ -1228,7 +1239,7 @@ namespace MWMechanics // Update bound effects // Note: in vanilla MW multiple bound items of the same type can be created by different spells. // As these extra copies are kinda useless this may or may not be important. - static std::map boundItemsMap; + static std::map boundItemsMap; if (boundItemsMap.empty()) { boundItemsMap[ESM::MagicEffect::BoundBattleAxe] = "sMagicBoundBattleAxeID"; @@ -1244,28 +1255,30 @@ namespace MWMechanics boundItemsMap[ESM::MagicEffect::BoundSpear] = "sMagicBoundSpearID"; } - for (std::map::iterator it = boundItemsMap.begin(); it != boundItemsMap.end(); ++it) + if(ptr.getClass().hasInventoryStore(ptr)) { - bool found = creatureStats.mBoundItems.find(it->first) != creatureStats.mBoundItems.end(); - float magnitude = effects.get(it->first).getMagnitude(); - if (found != (magnitude > 0)) + for (const auto& [effect, itemGmst] : boundItemsMap) { - if (magnitude > 0) - creatureStats.mBoundItems.insert(it->first); - else - creatureStats.mBoundItems.erase(it->first); - - std::string itemGmst = it->second; - std::string item = MWBase::Environment::get().getWorld()->getStore().get().find( - itemGmst)->mValue.getString(); + bool found = creatureStats.mBoundItems.find(effect) != creatureStats.mBoundItems.end(); + float magnitude = effects.get(effect).getMagnitude(); + if (found != (magnitude > 0)) + { + if (magnitude > 0) + creatureStats.mBoundItems.insert(effect); + else + creatureStats.mBoundItems.erase(effect); - magnitude > 0 ? addBoundItem(item, ptr) : removeBoundItem(item, ptr); + std::string item = MWBase::Environment::get().getWorld()->getStore().get().find( + itemGmst)->mValue.getString(); - if (it->first == ESM::MagicEffect::BoundGloves) - { - item = MWBase::Environment::get().getWorld()->getStore().get().find( - "sMagicBoundRightGauntletID")->mValue.getString(); magnitude > 0 ? addBoundItem(item, ptr) : removeBoundItem(item, ptr); + + if (effect == ESM::MagicEffect::BoundGloves) + { + item = MWBase::Environment::get().getWorld()->getStore().get().find( + "sMagicBoundRightGauntletID")->mValue.getString(); + magnitude > 0 ? addBoundItem(item, ptr) : removeBoundItem(item, ptr); + } } } } @@ -1764,7 +1777,7 @@ namespace MWMechanics } - void Actors::predictAndAvoidCollisions() + void Actors::predictAndAvoidCollisions(float duration) { if (!MWBase::Environment::get().getMechanicsManager()->isAIActive()) return; @@ -1797,16 +1810,18 @@ namespace MWMechanics // Standing NPCs give way to moving ones if they are not in combat (or pursue) mode and either // follow player or have a AIWander package with non-empty wander area. bool shouldAvoidCollision = isMoving; + bool shouldGiveWay = false; bool shouldTurnToApproachingActor = !isMoving; MWWorld::Ptr currentTarget; // Combat or pursue target (NPCs should not avoid collision with their targets). - for (const auto& package : ptr.getClass().getCreatureStats(ptr).getAiSequence()) + const auto& aiSequence = ptr.getClass().getCreatureStats(ptr).getAiSequence(); + for (const auto& package : aiSequence) { if (package->getTypeId() == AiPackageTypeId::Follow) shouldAvoidCollision = true; else if (package->getTypeId() == AiPackageTypeId::Wander && giveWayWhenIdle) { if (!static_cast(package.get())->isStationary()) - shouldAvoidCollision = true; + shouldGiveWay = true; } else if (package->getTypeId() == AiPackageTypeId::Combat || package->getTypeId() == AiPackageTypeId::Pursue) { @@ -1816,7 +1831,7 @@ namespace MWMechanics break; } } - if (!shouldAvoidCollision) + if (!shouldAvoidCollision && !shouldGiveWay) continue; osg::Vec2f baseSpeed = origMovement * maxSpeed; @@ -1825,7 +1840,11 @@ namespace MWMechanics osg::Vec3f halfExtents = world->getHalfExtents(ptr); float maxDistToCheck = isMoving ? maxDistForPartialAvoiding : maxDistForStrictAvoiding; - float timeToCollision = maxTimeToCheck; + float timeToCheck = maxTimeToCheck; + if (!shouldGiveWay && !aiSequence.isEmpty()) + timeToCheck = std::min(timeToCheck, getTimeToDestination(**aiSequence.begin(), basePos, maxSpeed, duration, halfExtents)); + + float timeToCollision = timeToCheck; osg::Vec2f movementCorrection(0, 0); float angleToApproachingActor = 0; @@ -1885,7 +1904,7 @@ namespace MWMechanics movementCorrection.y() *= 0.5f; } - if (timeToCollision < maxTimeToCheck) + if (timeToCollision < timeToCheck) { // Try to evade the nearest collision. osg::Vec2f newMovement = origMovement + movementCorrection; @@ -2075,7 +2094,7 @@ namespace MWMechanics static const bool avoidCollisions = Settings::Manager::getBool("NPCs avoid collisions", "Game"); if (avoidCollisions) - predictAndAvoidCollisions(); + predictAndAvoidCollisions(duration); timerUpdateHeadTrack += duration; timerUpdateEquippedLight += duration; diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index 2de0728d5..0ae968757 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -63,7 +63,7 @@ namespace MWMechanics void purgeSpellEffects (int casterActorId); - void predictAndAvoidCollisions(); + void predictAndAvoidCollisions(float duration); public: diff --git a/apps/openmw/mwmechanics/aiavoiddoor.hpp b/apps/openmw/mwmechanics/aiavoiddoor.hpp index 1781c5e4a..183f429f3 100644 --- a/apps/openmw/mwmechanics/aiavoiddoor.hpp +++ b/apps/openmw/mwmechanics/aiavoiddoor.hpp @@ -3,10 +3,6 @@ #include "typedaipackage.hpp" -#include - -#include - #include "../mwworld/class.hpp" #include "pathfinding.hpp" diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index b5e7594f3..1adc2531b 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -346,7 +346,7 @@ namespace MWMechanics bool runFallback = true; - if (pathgrid && !actor.getClass().isPureWaterCreature(actor)) + if (pathgrid != nullptr && !pathgrid->mPoints.empty() && !actor.getClass().isPureWaterCreature(actor)) { ESM::Pathgrid::PointList points; Misc::CoordinateConverter coords(storage.mCell->getCell()); @@ -715,16 +715,18 @@ osg::Vec3f AimDirToMovingTarget(const MWWorld::Ptr& actor, const MWWorld::Ptr& t float t_collision; float projVelDirSquared = projSpeed * projSpeed - velPerp * velPerp; + if (projVelDirSquared > 0) + { + osg::Vec3f vTargetMoveDirNormalized = vTargetMoveDir; + vTargetMoveDirNormalized.normalize(); - osg::Vec3f vTargetMoveDirNormalized = vTargetMoveDir; - vTargetMoveDirNormalized.normalize(); - - float projDistDiff = vDirToTarget * vTargetMoveDirNormalized; // dot product - projDistDiff = std::sqrt(distToTarget * distToTarget - projDistDiff * projDistDiff); + float projDistDiff = vDirToTarget * vTargetMoveDirNormalized; // dot product + projDistDiff = std::sqrt(distToTarget * distToTarget - projDistDiff * projDistDiff); - if (projVelDirSquared > 0) t_collision = projDistDiff / (std::sqrt(projVelDirSquared) - velDir); - else t_collision = 0; // speed of projectile is not enough to reach moving target + } + else + t_collision = 0; // speed of projectile is not enough to reach moving target return vDirToTarget + vTargetMoveDir * t_collision; } diff --git a/apps/openmw/mwmechanics/aicombat.hpp b/apps/openmw/mwmechanics/aicombat.hpp index 0f42c6e2d..5425f1af0 100644 --- a/apps/openmw/mwmechanics/aicombat.hpp +++ b/apps/openmw/mwmechanics/aicombat.hpp @@ -9,7 +9,6 @@ #include "pathfinding.hpp" #include "movement.hpp" -#include "obstacle.hpp" #include "aitimer.hpp" namespace ESM diff --git a/apps/openmw/mwmechanics/aicombataction.hpp b/apps/openmw/mwmechanics/aicombataction.hpp index 77a19f804..d17d5a313 100644 --- a/apps/openmw/mwmechanics/aicombataction.hpp +++ b/apps/openmw/mwmechanics/aicombataction.hpp @@ -3,8 +3,6 @@ #include -#include - #include "../mwworld/ptr.hpp" #include "../mwworld/containerstore.hpp" diff --git a/apps/openmw/mwmechanics/aifollow.hpp b/apps/openmw/mwmechanics/aifollow.hpp index e6aeebb24..c4ac5eb3f 100644 --- a/apps/openmw/mwmechanics/aifollow.hpp +++ b/apps/openmw/mwmechanics/aifollow.hpp @@ -9,8 +9,6 @@ #include "../mwworld/ptr.hpp" -#include "pathfinding.hpp" - namespace ESM { namespace AiSequence diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 204cf2f3f..8ad944751 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -2,7 +2,6 @@ #include #include -#include #include #include #include @@ -15,8 +14,6 @@ #include "../mwworld/cellstore.hpp" #include "../mwworld/inventorystore.hpp" -#include "../mwphysics/collisiontype.hpp" - #include "pathgrid.hpp" #include "creaturestats.hpp" #include "movement.hpp" @@ -31,6 +28,12 @@ namespace { return divisor == 0 ? std::numeric_limits::max() * std::numeric_limits::epsilon() : dividend / divisor; } + + float getPointTolerance(float speed, float duration, const osg::Vec3f& halfExtents) + { + const float actorTolerance = 2 * speed * duration + 1.2 * std::max(halfExtents.x(), halfExtents.y()); + return std::max(MWMechanics::MIN_TOLERANCE, actorTolerance); + } } MWMechanics::AiPackage::AiPackage(AiPackageTypeId typeId, const Options& options) : @@ -52,6 +55,11 @@ MWWorld::Ptr MWMechanics::AiPackage::getTarget() const if (mTargetActorId == -1) { + if (mTargetActorRefId.empty()) + { + mTargetActorId = -2; + return MWWorld::Ptr(); + } MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtr(mTargetActorRefId, false); if (target.isEmpty()) { @@ -101,6 +109,8 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& return false; } + mLastDestinationTolerance = destTolerance; + const float distToTarget = distance(position, dest); const bool isDestReached = (distToTarget <= destTolerance); const bool actorCanMoveByZ = canActorMoveByZAxis(actor); @@ -151,13 +161,12 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& } } - const float actorTolerance = 2 * actor.getClass().getMaxSpeed(actor) * duration - + 1.2 * std::max(halfExtents.x(), halfExtents.y()); - const float pointTolerance = std::max(MIN_TOLERANCE, actorTolerance); + const float pointTolerance = getPointTolerance(actor.getClass().getMaxSpeed(actor), duration, halfExtents); static const bool smoothMovement = Settings::Manager::getBool("smooth movement", "Game"); mPathFinder.update(position, pointTolerance, DEFAULT_TOLERANCE, - /*shortenIfAlmostStraight=*/smoothMovement, actorCanMoveByZ); + /*shortenIfAlmostStraight=*/smoothMovement, actorCanMoveByZ, + halfExtents, getNavigatorFlags(actor)); if (isDestReached || mPathFinder.checkPathCompleted()) // if path is finished { @@ -184,7 +193,7 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& zTurn(actor, zAngleToNext); smoothTurn(actor, mPathFinder.getXAngleToNext(position.x(), position.y(), position.z()), 0); - const auto destination = mPathFinder.getPath().empty() ? dest : mPathFinder.getPath().front(); + const auto destination = getNextPathPoint(dest); mObstacleCheck.update(actor, destination, duration); if (smoothMovement) @@ -464,3 +473,15 @@ DetourNavigator::AreaCosts MWMechanics::AiPackage::getAreaCosts(const MWWorld::P return costs; } + +osg::Vec3f MWMechanics::AiPackage::getNextPathPoint(const osg::Vec3f& destination) const +{ + return mPathFinder.getPath().empty() ? destination : mPathFinder.getPath().front(); +} + +float MWMechanics::AiPackage::getNextPathPointTolerance(float speed, float duration, const osg::Vec3f& halfExtents) const +{ + if (mPathFinder.getPathSize() <= 1) + return std::max(DEFAULT_TOLERANCE, mLastDestinationTolerance); + return getPointTolerance(speed, duration, halfExtents); +} diff --git a/apps/openmw/mwmechanics/aipackage.hpp b/apps/openmw/mwmechanics/aipackage.hpp index 81b09c8b9..6d8af0d92 100644 --- a/apps/openmw/mwmechanics/aipackage.hpp +++ b/apps/openmw/mwmechanics/aipackage.hpp @@ -3,7 +3,6 @@ #include -#include #include #include "pathfinding.hpp" @@ -123,6 +122,10 @@ namespace MWMechanics /// Return if actor's rotation speed is sufficient to rotate to the destination pathpoint on the run. Otherwise actor should rotate while standing. static bool isReachableRotatingOnTheRun(const MWWorld::Ptr& actor, const osg::Vec3f& dest); + osg::Vec3f getNextPathPoint(const osg::Vec3f& destination) const; + + float getNextPathPointTolerance(float speed, float duration, const osg::Vec3f& halfExtents) const; + protected: /// Handles path building and shortcutting with obstacles avoiding /** \return If the actor has arrived at his destination **/ @@ -167,6 +170,7 @@ namespace MWMechanics bool mIsShortcutting; // if shortcutting at the moment bool mShortcutProhibited; // shortcutting may be prohibited after unsuccessful attempt osg::Vec3f mShortcutFailPos; // position of last shortcut fail + float mLastDestinationTolerance = 0; private: bool isNearInactiveCell(osg::Vec3f position); diff --git a/apps/openmw/mwmechanics/aipursue.cpp b/apps/openmw/mwmechanics/aipursue.cpp index 5af73887c..05605529a 100644 --- a/apps/openmw/mwmechanics/aipursue.cpp +++ b/apps/openmw/mwmechanics/aipursue.cpp @@ -8,7 +8,6 @@ #include "../mwbase/world.hpp" #include "../mwworld/class.hpp" -#include "../mwworld/action.hpp" #include "movement.hpp" #include "creaturestats.hpp" diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index 575a03434..fada7761d 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -5,8 +5,6 @@ #include #include -#include "../mwbase/world.hpp" - #include "aipackage.hpp" #include "aistate.hpp" #include "aiwander.hpp" diff --git a/apps/openmw/mwmechanics/aitravel.cpp b/apps/openmw/mwmechanics/aitravel.cpp index b2a506ca6..8e5372c46 100644 --- a/apps/openmw/mwmechanics/aitravel.cpp +++ b/apps/openmw/mwmechanics/aitravel.cpp @@ -52,14 +52,15 @@ namespace MWMechanics bool AiTravel::execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) { MWBase::MechanicsManager* mechMgr = MWBase::Environment::get().getMechanicsManager(); + auto& stats = actor.getClass().getCreatureStats(actor); - if (mechMgr->isTurningToPlayer(actor) || mechMgr->getGreetingState(actor) == Greet_InProgress) + if (!stats.getMovementFlag(CreatureStats::Flag_ForceJump) && !stats.getMovementFlag(CreatureStats::Flag_ForceSneak) + && (mechMgr->isTurningToPlayer(actor) || mechMgr->getGreetingState(actor) == Greet_InProgress)) return false; const osg::Vec3f actorPos(actor.getRefData().getPosition().asVec3()); const osg::Vec3f targetPos(mX, mY, mZ); - auto& stats = actor.getClass().getCreatureStats(actor); stats.setMovementFlag(CreatureStats::Flag_Run, false); stats.setDrawState(DrawState_Nothing); diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 72b8757bf..175836b11 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -11,7 +11,6 @@ #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" -#include "../mwbase/dialoguemanager.hpp" #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" @@ -210,14 +209,17 @@ namespace MWMechanics storage.setState(AiWanderStorage::Wander_Walking); } - GreetingState greetingState = MWBase::Environment::get().getMechanicsManager()->getGreetingState(actor); - if (greetingState == Greet_InProgress) + if(!cStats.getMovementFlag(CreatureStats::Flag_ForceJump) && !cStats.getMovementFlag(CreatureStats::Flag_ForceSneak)) { - if (storage.mState == AiWanderStorage::Wander_Walking) + GreetingState greetingState = MWBase::Environment::get().getMechanicsManager()->getGreetingState(actor); + if (greetingState == Greet_InProgress) { - stopMovement(actor); - mObstacleCheck.clear(); - storage.setState(AiWanderStorage::Wander_IdleNow); + if (storage.mState == AiWanderStorage::Wander_Walking) + { + stopMovement(actor); + mObstacleCheck.clear(); + storage.setState(AiWanderStorage::Wander_IdleNow); + } } } @@ -752,6 +754,9 @@ namespace MWMechanics const ESM::Pathgrid *pathgrid = MWBase::Environment::get().getWorld()->getStore().get().search(*currentCell->getCell()); + if (pathgrid == nullptr || pathgrid->mPoints.empty()) + return; + int index = PathFinder::getClosestPoint(pathgrid, PathFinder::makeOsgVec3(dest)); getPathGridGraph(currentCell).getNeighbouringPoints(index, points); diff --git a/apps/openmw/mwmechanics/aiwander.hpp b/apps/openmw/mwmechanics/aiwander.hpp index 52a926d14..f8506ff59 100644 --- a/apps/openmw/mwmechanics/aiwander.hpp +++ b/apps/openmw/mwmechanics/aiwander.hpp @@ -5,8 +5,6 @@ #include -#include "../mwworld/timestamp.hpp" - #include "pathfinding.hpp" #include "obstacle.hpp" #include "aistate.hpp" @@ -128,7 +126,6 @@ namespace MWMechanics short unsigned getRandomIdle(); void setPathToAnAllowedNode(const MWWorld::Ptr& actor, AiWanderStorage& storage, const ESM::Position& actorPos); void evadeObstacles(const MWWorld::Ptr& actor, AiWanderStorage& storage); - 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); void onWalkingStatePerFrameActions(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage); diff --git a/apps/openmw/mwmechanics/alchemy.cpp b/apps/openmw/mwmechanics/alchemy.cpp index 6d06724f0..61fec3b54 100644 --- a/apps/openmw/mwmechanics/alchemy.cpp +++ b/apps/openmw/mwmechanics/alchemy.cpp @@ -21,7 +21,6 @@ #include "../mwworld/containerstore.hpp" #include "../mwworld/class.hpp" #include "../mwworld/cellstore.hpp" -#include "../mwworld/manualref.hpp" #include "magiceffects.hpp" #include "creaturestats.hpp" diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 342b4ca39..c7c77cf2d 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -973,7 +973,7 @@ void CharacterController::handleTextKey(const std::string &groupname, SceneUtil: // The event can optionally contain volume and pitch modifiers float volume=1.f, pitch=1.f; - if (soundgen.find(" ") != std::string::npos) + if (soundgen.find(' ') != std::string::npos) { std::vector tokens; split(soundgen, ' ', tokens); @@ -1887,8 +1887,10 @@ bool CharacterController::updateWeaponState(CharacterState& idle) MWWorld::ConstContainerStoreIterator torch = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); if(torch != inv.end() && torch->getTypeName() == typeid(ESM::Light).name() && updateCarriedLeftVisible(mWeaponType)) - { + if (mAnimation->isPlaying("shield")) + mAnimation->disable("shield"); + mAnimation->play("torch", Priority_Torch, MWRender::Animation::BlendMask_LeftArm, false, 1.0f, "start", "stop", 0.0f, (~(size_t)0), true); } @@ -2414,9 +2416,6 @@ void CharacterController::update(float duration) if (!mMovementAnimationControlled) world->queueMovement(mPtr, vec); } - else - // We must always queue movement, even if there is none, to apply gravity. - world->queueMovement(mPtr, osg::Vec3f(0.f, 0.f, 0.f)); movement = vec; movementSettings.mPosition[0] = movementSettings.mPosition[1] = 0; @@ -2438,8 +2437,6 @@ void CharacterController::update(float duration) if (cls.isPersistent(mPtr) || cls.getCreatureStats(mPtr).isDeathAnimationFinished()) playDeath(1.f, mDeathState); } - // We must always queue movement, even if there is none, to apply gravity. - world->queueMovement(mPtr, osg::Vec3f(0.f, 0.f, 0.f)); } bool isPersist = isPersistentAnimPlaying(); @@ -2472,7 +2469,7 @@ void CharacterController::update(float duration) if (mFloatToSurface && cls.isActor()) { if (cls.getCreatureStats(mPtr).isDead() - || (!godmode && cls.getCreatureStats(mPtr).isParalyzed())) + || (!godmode && cls.getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Paralyze).getModifier() > 0)) { moved.z() = 1.0; } diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index 0821b3225..3f7fa4e1b 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -3,8 +3,6 @@ #include -#include - #include "../mwworld/ptr.hpp" #include "../mwworld/containerstore.hpp" diff --git a/apps/openmw/mwmechanics/creaturestats.cpp b/apps/openmw/mwmechanics/creaturestats.cpp index 1d5fe8347..c6561af96 100644 --- a/apps/openmw/mwmechanics/creaturestats.cpp +++ b/apps/openmw/mwmechanics/creaturestats.cpp @@ -11,7 +11,6 @@ #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" -#include "../mwbase/mechanicsmanager.hpp" namespace MWMechanics { @@ -602,6 +601,28 @@ namespace MWMechanics mAiSequence.readState(state.mAiSequence); mMagicEffects.readState(state.mMagicEffects); + // Rebuild the bound item cache + for(int effectId = ESM::MagicEffect::BoundDagger; effectId <= ESM::MagicEffect::BoundGloves; effectId++) + { + if(mMagicEffects.get(effectId).getMagnitude() > 0) + mBoundItems.insert(effectId); + else + { + // Check active spell effects + // We can't use mActiveSpells::getMagicEffects here because it doesn't include expired effects + auto spell = std::find_if(mActiveSpells.begin(), mActiveSpells.end(), [&] (const auto& spell) + { + const auto& effects = spell.second.mEffects; + return std::find_if(effects.begin(), effects.end(), [&] (const auto& effect) + { + return effect.mEffectId == effectId; + }) != effects.end(); + }); + if(spell != mActiveSpells.end()) + mBoundItems.insert(effectId); + } + } + mSummonedCreatures = state.mSummonedCreatureMap; mSummonGraveyard = state.mSummonGraveyard; diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index 781b897a7..0e704cd46 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -3,7 +3,6 @@ #include #include -#include #include #include #include @@ -106,6 +105,19 @@ namespace return checkAngle && checkDist; } + + struct IsValidShortcut + { + const DetourNavigator::Navigator* mNavigator; + const osg::Vec3f mHalfExtents; + const DetourNavigator::Flags mFlags; + + bool operator()(const osg::Vec3f& start, const osg::Vec3f& end) const + { + const auto position = mNavigator->raycast(mHalfExtents, start, end, mFlags); + return position.has_value() && std::abs((position.value() - start).length2() - (end - start).length2()) <= 1; + } + }; } namespace MWMechanics @@ -194,9 +206,6 @@ namespace MWMechanics endPointInLocalCoords, startNode); - if (!endNode.second) - return; - // if it's shorter for actor to travel from start to end, than to travel from either // start or end to nearest pathgrid point, just travel from start to end. float startToEndLength2 = (endPointInLocalCoords - startPointInLocalCoords).length2(); @@ -267,7 +276,8 @@ namespace MWMechanics // unreachable pathgrid point. // // The AI routines will have to deal with such situations. - *out++ = endPoint; + if (endNode.second) + *out++ = endPoint; } float PathFinder::getZAngleToNext(float x, float y) const @@ -297,7 +307,8 @@ namespace MWMechanics } void PathFinder::update(const osg::Vec3f& position, float pointTolerance, float destinationTolerance, - bool shortenIfAlmostStraight, bool canMoveByZ) + bool shortenIfAlmostStraight, bool canMoveByZ, const osg::Vec3f& halfExtents, + const DetourNavigator::Flags flags) { if (mPath.empty()) return; @@ -307,9 +318,15 @@ namespace MWMechanics if (shortenIfAlmostStraight) { - while (mPath.size() > 2 && isAlmostStraight(mPath[0], mPath[1], mPath[2], pointTolerance)) + const IsValidShortcut isValidShortcut { + MWBase::Environment::get().getWorld()->getNavigator(), + halfExtents, flags + }; + while (mPath.size() > 2 && isAlmostStraight(mPath[0], mPath[1], mPath[2], pointTolerance) + && isValidShortcut(mPath[0], mPath[2])) mPath.erase(mPath.begin() + 1); - if (mPath.size() > 1 && isAlmostStraight(position, mPath[0], mPath[1], pointTolerance)) + if (mPath.size() > 1 && isAlmostStraight(position, mPath[0], mPath[1], pointTolerance) + && isValidShortcut(position, mPath[1])) mPath.pop_front(); } @@ -350,7 +367,13 @@ namespace MWMechanics mPath.clear(); // If it's not possible to build path over navmesh due to disabled navmesh generation fallback to straight path - if (!buildPathByNavigatorImpl(actor, startPoint, endPoint, halfExtents, flags, areaCosts, std::back_inserter(mPath))) + DetourNavigator::Status status = buildPathByNavigatorImpl(actor, startPoint, endPoint, halfExtents, flags, + areaCosts, std::back_inserter(mPath)); + + if (status != DetourNavigator::Status::Success) + mPath.clear(); + + if (status == DetourNavigator::Status::NavMeshNotFound) mPath.push_back(endPoint); mConstructed = !mPath.empty(); @@ -363,25 +386,33 @@ namespace MWMechanics mPath.clear(); mCell = cell; - bool hasNavMesh = false; + DetourNavigator::Status status = DetourNavigator::Status::NavMeshNotFound; if (!actor.getClass().isPureWaterCreature(actor) && !actor.getClass().isPureFlyingCreature(actor)) - hasNavMesh = buildPathByNavigatorImpl(actor, startPoint, endPoint, halfExtents, flags, areaCosts, std::back_inserter(mPath)); + { + status = buildPathByNavigatorImpl(actor, startPoint, endPoint, halfExtents, flags, areaCosts, std::back_inserter(mPath)); + if (status != DetourNavigator::Status::Success) + mPath.clear(); + } - if (hasNavMesh && mPath.empty()) - buildPathByNavigatorImpl(actor, startPoint, endPoint, halfExtents, + if (status != DetourNavigator::Status::NavMeshNotFound && mPath.empty()) + { + status = buildPathByNavigatorImpl(actor, startPoint, endPoint, halfExtents, flags | DetourNavigator::Flag_usePathgrid, areaCosts, std::back_inserter(mPath)); + if (status != DetourNavigator::Status::Success) + mPath.clear(); + } if (mPath.empty()) buildPathByPathgridImpl(startPoint, endPoint, pathgridGraph, std::back_inserter(mPath)); - if (!hasNavMesh && mPath.empty()) + if (status == DetourNavigator::Status::NavMeshNotFound && mPath.empty()) mPath.push_back(endPoint); mConstructed = !mPath.empty(); } - bool PathFinder::buildPathByNavigatorImpl(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, + DetourNavigator::Status PathFinder::buildPathByNavigatorImpl(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const osg::Vec3f& halfExtents, const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts, std::back_insert_iterator> out) { @@ -390,9 +421,6 @@ namespace MWMechanics const auto navigator = world->getNavigator(); const auto status = navigator->findPath(halfExtents, stepSize, startPoint, endPoint, flags, areaCosts, out); - if (status == DetourNavigator::Status::NavMeshNotFound) - return false; - if (status != DetourNavigator::Status::Success) { Log(Debug::Debug) << "Build path by navigator error: \"" << DetourNavigator::getMessage(status) @@ -401,7 +429,7 @@ namespace MWMechanics << DetourNavigator::WriteFlags {flags} << ")"; } - return true; + return status; } void PathFinder::buildPathByNavMeshToNextPoint(const MWWorld::ConstPtr& actor, const osg::Vec3f& halfExtents, diff --git a/apps/openmw/mwmechanics/pathfinding.hpp b/apps/openmw/mwmechanics/pathfinding.hpp index ed88a57ca..0d281d1e2 100644 --- a/apps/openmw/mwmechanics/pathfinding.hpp +++ b/apps/openmw/mwmechanics/pathfinding.hpp @@ -7,6 +7,7 @@ #include #include +#include #include #include @@ -107,7 +108,8 @@ namespace MWMechanics /// Remove front point if exist and within tolerance void update(const osg::Vec3f& position, float pointTolerance, float destinationTolerance, - bool shortenIfAlmostStraight, bool canMoveByZ); + bool shortenIfAlmostStraight, bool canMoveByZ, const osg::Vec3f& halfExtents, + const DetourNavigator::Flags flags); bool checkPathCompleted() const { @@ -208,9 +210,10 @@ namespace MWMechanics void buildPathByPathgridImpl(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const PathgridGraph& pathgridGraph, std::back_insert_iterator> out); - bool buildPathByNavigatorImpl(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, - const osg::Vec3f& endPoint, const osg::Vec3f& halfExtents, const DetourNavigator::Flags flags, - const DetourNavigator::AreaCosts& areaCosts, std::back_insert_iterator> out); + [[nodiscard]] DetourNavigator::Status buildPathByNavigatorImpl(const MWWorld::ConstPtr& actor, + const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const osg::Vec3f& halfExtents, + const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts, + std::back_insert_iterator> out); }; } diff --git a/apps/openmw/mwmechanics/spellabsorption.cpp b/apps/openmw/mwmechanics/spellabsorption.cpp index bab290fda..1a2dfe65a 100644 --- a/apps/openmw/mwmechanics/spellabsorption.cpp +++ b/apps/openmw/mwmechanics/spellabsorption.cpp @@ -44,14 +44,14 @@ namespace MWMechanics } }; - bool absorbSpell (const std::string& spellId, const MWWorld::Ptr& caster, const MWWorld::Ptr& target) + int getAbsorbChance(const MWWorld::Ptr& caster, const MWWorld::Ptr& target) { - if (spellId.empty() || target.isEmpty() || caster == target || !target.getClass().isActor()) - return false; + if(target.isEmpty() || caster == target || !target.getClass().isActor()) + return 0; CreatureStats& stats = target.getClass().getCreatureStats(target); if (stats.getMagicEffects().get(ESM::MagicEffect::SpellAbsorption).getMagnitude() <= 0.f) - return false; + return 0; GetAbsorptionProbability check; stats.getActiveSpells().visitEffectSources(check); @@ -59,9 +59,12 @@ namespace MWMechanics if (target.getClass().hasInventoryStore(target)) target.getClass().getInventoryStore(target).visitEffectSources(check); - int chance = check.mProbability * 100; - if (Misc::Rng::roll0to99() >= chance) - return false; + return check.mProbability * 100; + } + + void absorbSpell (const std::string& spellId, const MWWorld::Ptr& caster, const MWWorld::Ptr& target) + { + CreatureStats& stats = target.getClass().getCreatureStats(target); const auto& esmStore = MWBase::Environment::get().getWorld()->getStore(); const ESM::Static* absorbStatic = esmStore.get().find("VFX_Absorb"); @@ -85,7 +88,6 @@ namespace MWMechanics DynamicStat magicka = stats.getMagicka(); magicka.setCurrent(magicka.getCurrent() + spellCost); stats.setMagicka(magicka); - return true; } } diff --git a/apps/openmw/mwmechanics/spellabsorption.hpp b/apps/openmw/mwmechanics/spellabsorption.hpp index 0fe501df9..5537483a4 100644 --- a/apps/openmw/mwmechanics/spellabsorption.hpp +++ b/apps/openmw/mwmechanics/spellabsorption.hpp @@ -10,8 +10,9 @@ namespace MWWorld namespace MWMechanics { - // Try to absorb a spell based on the magnitude of every Spell Absorption effect source on the target. - bool absorbSpell(const std::string& spellId, const MWWorld::Ptr& caster, const MWWorld::Ptr& target); + void absorbSpell(const std::string& spellId, const MWWorld::Ptr& caster, const MWWorld::Ptr& target); + // Calculate the chance to absorb a spell based on the magnitude of every Spell Absorption effect source on the target. + int getAbsorbChance(const MWWorld::Ptr& caster, const MWWorld::Ptr& target); } #endif diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 142914c35..0011515ba 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -60,10 +60,8 @@ namespace MWMechanics void CastSpell::inflict(const MWWorld::Ptr &target, const MWWorld::Ptr &caster, const ESM::EffectList &effects, ESM::RangeType range, bool reflected, bool exploded) { - if (target.isEmpty()) - return; - - if (target.getClass().isActor()) + const bool targetIsActor = !target.isEmpty() && target.getClass().isActor(); + if (targetIsActor) { // Early-out for characters that have departed. const auto& stats = target.getClass().getCreatureStats(target); @@ -85,7 +83,7 @@ namespace MWMechanics return; const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search (mId); - if (spell && target.getClass().isActor() && (spell->mData.mType == ESM::Spell::ST_Disease || spell->mData.mType == ESM::Spell::ST_Blight)) + if (spell && targetIsActor && (spell->mData.mType == ESM::Spell::ST_Disease || spell->mData.mType == ESM::Spell::ST_Blight)) { int requiredResistance = (spell->mData.mType == ESM::Spell::ST_Disease) ? ESM::MagicEffect::ResistCommonDisease @@ -108,25 +106,24 @@ namespace MWMechanics // This is required for Weakness effects in a spell to apply to any subsequent effects in the spell. // Otherwise, they'd only apply after the whole spell was added. MagicEffects targetEffects; - if (target.getClass().isActor()) + if (targetIsActor) targetEffects += target.getClass().getCreatureStats(target).getMagicEffects(); bool castByPlayer = (!caster.isEmpty() && caster == getPlayer()); ActiveSpells targetSpells; - if (target.getClass().isActor()) + if (targetIsActor) targetSpells = target.getClass().getCreatureStats(target).getActiveSpells(); bool canCastAnEffect = false; // For bound equipment.If this remains false // throughout the iteration of this spell's // effects, we display a "can't re-cast" message - // Try absorbing the spell. Some handling must still happen for absorbed effects. - bool absorbed = absorbSpell(mId, caster, target); + int absorbChance = getAbsorbChance(caster, target); int currentEffectIndex = 0; for (std::vector::const_iterator effectIt (effects.mList.begin()); - effectIt != effects.mList.end(); ++effectIt, ++currentEffectIndex) + !target.isEmpty() && effectIt != effects.mList.end(); ++effectIt, ++currentEffectIndex) { if (effectIt->mRange != range) continue; @@ -144,6 +141,13 @@ namespace MWMechanics } canCastAnEffect = true; + // Try absorbing the effect + if(absorbChance && Misc::Rng::roll0to99() < absorbChance) + { + absorbSpell(mId, caster, target); + continue; + } + if (!checkEffectTarget(effectIt->mEffectID, target, caster, castByPlayer)) continue; @@ -157,10 +161,6 @@ namespace MWMechanics if (target.getClass().isActor() && target != caster && !caster.isEmpty() && isHarmful) target.getClass().onHit(target, 0.0f, true, MWWorld::Ptr(), caster, osg::Vec3f(), true); - // Avoid proceeding further for absorbed spells. - if (absorbed) - continue; - // Reflect harmful effects if (!reflected && reflectEffect(*effectIt, magicEffect, caster, target, reflectedEffects)) continue; @@ -270,7 +270,7 @@ namespace MWMechanics } // Re-casting a summon effect will remove the creature from previous castings of that effect. - if (isSummoningEffect(effectIt->mEffectID) && target.getClass().isActor()) + if (isSummoningEffect(effectIt->mEffectID) && targetIsActor) { CreatureStats& targetStats = target.getClass().getCreatureStats(target); ESM::SummonKey key(effectIt->mEffectID, mId, currentEffectIndex); @@ -313,16 +313,19 @@ namespace MWMechanics if (!exploded) MWBase::Environment::get().getWorld()->explodeSpell(mHitPosition, effects, caster, target, range, mId, mSourceName, mFromProjectile); - if (!reflectedEffects.mList.empty()) - inflict(caster, target, reflectedEffects, range, true, exploded); - - if (!appliedLastingEffects.empty()) + if (!target.isEmpty()) { - int casterActorId = -1; - if (!caster.isEmpty() && caster.getClass().isActor()) - casterActorId = caster.getClass().getCreatureStats(caster).getActorId(); - target.getClass().getCreatureStats(target).getActiveSpells().addSpell(mId, mStack, appliedLastingEffects, - mSourceName, casterActorId); + if (!reflectedEffects.mList.empty()) + inflict(caster, target, reflectedEffects, range, true, exploded); + + if (!appliedLastingEffects.empty()) + { + int casterActorId = -1; + if (!caster.isEmpty() && caster.getClass().isActor()) + casterActorId = caster.getClass().getCreatureStats(caster).getActorId(); + target.getClass().getCreatureStats(target).getActiveSpells().addSpell(mId, mStack, appliedLastingEffects, + mSourceName, casterActorId); + } } } diff --git a/apps/openmw/mwmechanics/spellcasting.hpp b/apps/openmw/mwmechanics/spellcasting.hpp index 45431bbc6..cc525d2db 100644 --- a/apps/openmw/mwmechanics/spellcasting.hpp +++ b/apps/openmw/mwmechanics/spellcasting.hpp @@ -2,7 +2,6 @@ #define MWMECHANICS_SPELLCASTING_H #include -#include #include "../mwworld/ptr.hpp" diff --git a/apps/openmw/mwmechanics/spelllist.cpp b/apps/openmw/mwmechanics/spelllist.cpp index d8fbcf25a..9328d533e 100644 --- a/apps/openmw/mwmechanics/spelllist.cpp +++ b/apps/openmw/mwmechanics/spelllist.cpp @@ -3,7 +3,6 @@ #include #include -#include #include "spells.hpp" @@ -71,7 +70,7 @@ namespace MWMechanics auto& id = spell->mId; bool changed = withBaseRecord([&] (auto& spells) { - for(auto it : spells) + for(const auto& it : spells) { if(Misc::StringUtils::ciEqual(id, it)) return false; diff --git a/apps/openmw/mwmechanics/spelllist.hpp b/apps/openmw/mwmechanics/spelllist.hpp index c95ee812b..5920949d6 100644 --- a/apps/openmw/mwmechanics/spelllist.hpp +++ b/apps/openmw/mwmechanics/spelllist.hpp @@ -9,8 +9,6 @@ #include -#include "magiceffects.hpp" - namespace ESM { struct SpellState; diff --git a/apps/openmw/mwmechanics/spellpriority.cpp b/apps/openmw/mwmechanics/spellpriority.cpp index 81658193d..bd9c5f7cb 100644 --- a/apps/openmw/mwmechanics/spellpriority.cpp +++ b/apps/openmw/mwmechanics/spellpriority.cpp @@ -17,7 +17,6 @@ #include "creaturestats.hpp" #include "spellresistance.hpp" #include "weapontype.hpp" -#include "combat.hpp" #include "summoning.hpp" #include "spellutil.hpp" diff --git a/apps/openmw/mwmechanics/spells.hpp b/apps/openmw/mwmechanics/spells.hpp index 055339795..9737b72cd 100644 --- a/apps/openmw/mwmechanics/spells.hpp +++ b/apps/openmw/mwmechanics/spells.hpp @@ -4,7 +4,6 @@ #include #include #include -#include #include #include "../mwworld/timestamp.hpp" diff --git a/apps/openmw/mwmechanics/summoning.cpp b/apps/openmw/mwmechanics/summoning.cpp index 9f65f3d6c..d90545fc6 100644 --- a/apps/openmw/mwmechanics/summoning.cpp +++ b/apps/openmw/mwmechanics/summoning.cpp @@ -152,16 +152,10 @@ namespace MWMechanics continue; } MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->searchPtrViaActorId(it->second); - if (ptr.isEmpty() || (ptr.getClass().getCreatureStats(ptr).isDead() && ptr.getClass().getCreatureStats(ptr).isDeathAnimationFinished())) + if (!ptr.isEmpty() && ptr.getClass().getCreatureStats(ptr).isDead() && ptr.getClass().getCreatureStats(ptr).isDeathAnimationFinished()) { // Purge the magic effect so a new creature can be summoned if desired - const ESM::SummonKey& key = it->first; - creatureStats.getActiveSpells().purgeEffect(key.mEffectId, key.mSourceId, key.mEffectIndex); - creatureStats.getSpells().purgeEffect(key.mEffectId, key.mSourceId); - if (mActor.getClass().hasInventoryStore(mActor)) - mActor.getClass().getInventoryStore(mActor).purgeEffect(key.mEffectId, key.mSourceId, false, key.mEffectIndex); - - MWBase::Environment::get().getMechanicsManager()->cleanupSummonedCreature(mActor, it->second); + purgeSummonEffect(mActor, *it); creatureMap.erase(it++); } else @@ -169,4 +163,14 @@ namespace MWMechanics } } + void purgeSummonEffect(const MWWorld::Ptr& summoner, const std::pair& summon) + { + auto& creatureStats = summoner.getClass().getCreatureStats(summoner); + creatureStats.getActiveSpells().purgeEffect(summon.first.mEffectId, summon.first.mSourceId, summon.first.mEffectIndex); + creatureStats.getSpells().purgeEffect(summon.first.mEffectId, summon.first.mSourceId); + if (summoner.getClass().hasInventoryStore(summoner)) + summoner.getClass().getInventoryStore(summoner).purgeEffect(summon.first.mEffectId, summon.first.mSourceId, false, summon.first.mEffectIndex); + + MWBase::Environment::get().getMechanicsManager()->cleanupSummonedCreature(summoner, summon.second); + } } diff --git a/apps/openmw/mwmechanics/summoning.hpp b/apps/openmw/mwmechanics/summoning.hpp index 7e787499e..3c3e18a96 100644 --- a/apps/openmw/mwmechanics/summoning.hpp +++ b/apps/openmw/mwmechanics/summoning.hpp @@ -17,6 +17,8 @@ namespace MWMechanics std::string getSummonedCreature(int effectId); + void purgeSummonEffect(const MWWorld::Ptr& summoner, const std::pair& summon); + struct UpdateSummonedCreatures : public EffectSourceVisitor { UpdateSummonedCreatures(const MWWorld::Ptr& actor); diff --git a/apps/openmw/mwmechanics/weaponpriority.cpp b/apps/openmw/mwmechanics/weaponpriority.cpp index 13ce30927..8480dc208 100644 --- a/apps/openmw/mwmechanics/weaponpriority.cpp +++ b/apps/openmw/mwmechanics/weaponpriority.cpp @@ -9,7 +9,6 @@ #include "../mwworld/esmstore.hpp" #include "../mwworld/inventorystore.hpp" -#include "npcstats.hpp" #include "combat.hpp" #include "aicombataction.hpp" #include "spellpriority.hpp" diff --git a/apps/openmw/mwmechanics/weaponpriority.hpp b/apps/openmw/mwmechanics/weaponpriority.hpp index 67de7b50f..9dcef3e2e 100644 --- a/apps/openmw/mwmechanics/weaponpriority.hpp +++ b/apps/openmw/mwmechanics/weaponpriority.hpp @@ -1,8 +1,6 @@ #ifndef OPENMW_WEAPON_PRIORITY_H #define OPENMW_WEAPON_PRIORITY_H -#include - #include "../mwworld/ptr.hpp" namespace MWMechanics diff --git a/apps/openmw/mwmechanics/weapontype.hpp b/apps/openmw/mwmechanics/weapontype.hpp index 056a1dbfd..09fa73c06 100644 --- a/apps/openmw/mwmechanics/weapontype.hpp +++ b/apps/openmw/mwmechanics/weapontype.hpp @@ -3,8 +3,6 @@ #include "../mwworld/inventorystore.hpp" -#include "creaturestats.hpp" - namespace MWMechanics { static std::map sWeaponTypeList = diff --git a/apps/openmw/mwphysics/actor.cpp b/apps/openmw/mwphysics/actor.cpp index f14805c6f..ef9ac57b9 100644 --- a/apps/openmw/mwphysics/actor.cpp +++ b/apps/openmw/mwphysics/actor.cpp @@ -19,10 +19,10 @@ namespace MWPhysics { -Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, PhysicsTaskScheduler* scheduler) - : mStandingOnPtr(nullptr), mCanWaterWalk(false), mWalkingOnWater(false) +Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, PhysicsTaskScheduler* scheduler, bool canWaterWalk) + : mStandingOnPtr(nullptr), mCanWaterWalk(canWaterWalk), mWalkingOnWater(false) , mCollisionObject(nullptr), mMeshTranslation(shape->mCollisionBox.center), mHalfExtents(shape->mCollisionBox.extents) - , mStuckFrames(0), mLastStuckPosition{0, 0, 0} + , mVelocity(0,0,0), mStuckFrames(0), mLastStuckPosition{0, 0, 0} , mForce(0.f, 0.f, 0.f), mOnGround(true), mOnSlope(false) , mInternalCollisionMode(true) , mExternalCollisionMode(true) @@ -117,32 +117,20 @@ int Actor::getCollisionMask() const void Actor::updatePosition() { std::scoped_lock lock(mPositionMutex); - updateWorldPosition(); - mPreviousPosition = mWorldPosition; - mPosition = mWorldPosition; - mSimulationPosition = mWorldPosition; + const auto worldPosition = mPtr.getRefData().getPosition().asVec3(); + mPreviousPosition = worldPosition; + mPosition = worldPosition; + mSimulationPosition = worldPosition; mPositionOffset = osg::Vec3f(); mStandingOnPtr = nullptr; + mSkipCollisions = true; mSkipSimulation = true; } -void Actor::updateWorldPosition() -{ - if (mWorldPosition != mPtr.getRefData().getPosition().asVec3()) - mWorldPositionChanged = true; - mWorldPosition = mPtr.getRefData().getPosition().asVec3(); -} - -osg::Vec3f Actor::getWorldPosition() const -{ - return mWorldPosition; -} - void Actor::setSimulationPosition(const osg::Vec3f& position) { - if (!mSkipSimulation) + if (!std::exchange(mSkipSimulation, false)) mSimulationPosition = position; - mSkipSimulation = false; } osg::Vec3f Actor::getSimulationPosition() const @@ -159,38 +147,37 @@ void Actor::updateCollisionObjectPosition() { std::scoped_lock lock(mPositionMutex); mShape->setLocalScaling(Misc::Convert::toBullet(mScale)); - osg::Vec3f scaledTranslation = mRotation * osg::componentMultiply(mMeshTranslation, mScale); - osg::Vec3f newPosition = scaledTranslation + mPosition; - mLocalTransform.setOrigin(Misc::Convert::toBullet(newPosition)); - mLocalTransform.setRotation(Misc::Convert::toBullet(mRotation)); - mCollisionObject->setWorldTransform(mLocalTransform); + osg::Vec3f newPosition = getScaledMeshTranslation() + mPosition; + + auto& trans = mCollisionObject->getWorldTransform(); + trans.setOrigin(Misc::Convert::toBullet(newPosition)); + trans.setRotation(Misc::Convert::toBullet(mRotation)); + mCollisionObject->setWorldTransform(trans); + mWorldPositionChanged = false; } osg::Vec3f Actor::getCollisionObjectPosition() const { std::scoped_lock lock(mPositionMutex); - return Misc::Convert::toOsg(mLocalTransform.getOrigin()); + return getScaledMeshTranslation() + mPosition; } bool Actor::setPosition(const osg::Vec3f& position) { std::scoped_lock lock(mPositionMutex); - // position is being forced, ignore simulation results until we sync up - if (mSkipSimulation) - return false; - bool hasChanged = mPosition != position || mPositionOffset.length() != 0 || mWorldPositionChanged; - updateWorldPosition(); applyOffsetChange(); + bool hasChanged = mPosition != position || mWorldPositionChanged; mPreviousPosition = mPosition; mPosition = position; return hasChanged; } -void Actor::adjustPosition(const osg::Vec3f& offset) +void Actor::adjustPosition(const osg::Vec3f& offset, bool ignoreCollisions) { std::scoped_lock lock(mPositionMutex); mPositionOffset += offset; + mSkipCollisions = mSkipCollisions || ignoreCollisions; } void Actor::applyOffsetChange() @@ -302,4 +289,19 @@ void Actor::setStandingOnPtr(const MWWorld::Ptr& ptr) mStandingOnPtr = ptr; } +bool Actor::skipCollisions() +{ + return std::exchange(mSkipCollisions, false); +} + +void Actor::setVelocity(osg::Vec3f velocity) +{ + mVelocity = velocity; +} + +osg::Vec3f Actor::velocity() +{ + return std::exchange(mVelocity, osg::Vec3f()); +} + } diff --git a/apps/openmw/mwphysics/actor.hpp b/apps/openmw/mwphysics/actor.hpp index 472a79bff..99f625394 100644 --- a/apps/openmw/mwphysics/actor.hpp +++ b/apps/openmw/mwphysics/actor.hpp @@ -10,7 +10,6 @@ #include #include #include -#include class btCollisionShape; class btCollisionObject; @@ -28,7 +27,7 @@ namespace MWPhysics class Actor final : public PtrHolder { public: - Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, PhysicsTaskScheduler* scheduler); + Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, PhysicsTaskScheduler* scheduler, bool canWaterWalk); ~Actor() override; /** @@ -56,13 +55,6 @@ namespace MWPhysics */ bool isRotationallyInvariant() const; - /** - * Set mWorldPosition to the position in the Ptr's RefData. This is used by the physics simulation to account for - * when an object is "instantly" moved/teleported as opposed to being moved by the physics simulation. - */ - void updateWorldPosition(); - osg::Vec3f getWorldPosition() const; - /** * Used by the physics simulation to store the simulation result. Used in conjunction with mWorldPosition * to account for e.g. scripted movements @@ -82,9 +74,6 @@ namespace MWPhysics */ osg::Vec3f getOriginalHalfExtents() const; - /// Returns the mesh translation, scaled and rotated as necessary - osg::Vec3f getScaledMeshTranslation() 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. @@ -101,7 +90,7 @@ namespace MWPhysics void updatePosition(); // register a position offset that will be applied during simulation. - void adjustPosition(const osg::Vec3f& offset); + void adjustPosition(const osg::Vec3f& offset, bool ignoreCollisions); // apply position offset. Can't be called during simulation void applyOffsetChange(); @@ -177,6 +166,11 @@ namespace MWPhysics mLastStuckPosition = position; } + bool skipCollisions(); + + void setVelocity(osg::Vec3f velocity); + osg::Vec3f velocity(); + private: MWWorld::Ptr mStandingOnPtr; /// Removes then re-adds the collision object to the dynamics world @@ -184,6 +178,9 @@ namespace MWPhysics void addCollisionMask(int collisionMask); int getCollisionMask() const; + /// Returns the mesh translation, scaled and rotated as necessary + osg::Vec3f getScaledMeshTranslation() const; + bool mCanWaterWalk; std::atomic mWalkingOnWater; @@ -200,14 +197,14 @@ namespace MWPhysics osg::Vec3f mScale; osg::Vec3f mRenderingScale; - osg::Vec3f mWorldPosition; osg::Vec3f mSimulationPosition; osg::Vec3f mPosition; osg::Vec3f mPreviousPosition; osg::Vec3f mPositionOffset; + osg::Vec3f mVelocity; bool mWorldPositionChanged; + bool mSkipCollisions; bool mSkipSimulation; - btTransform mLocalTransform; mutable std::mutex mPositionMutex; unsigned int mStuckFrames; diff --git a/apps/openmw/mwphysics/actorconvexcallback.cpp b/apps/openmw/mwphysics/actorconvexcallback.cpp index f99b706d7..ef52e07c2 100644 --- a/apps/openmw/mwphysics/actorconvexcallback.cpp +++ b/apps/openmw/mwphysics/actorconvexcallback.cpp @@ -1,5 +1,3 @@ -#include - #include "actorconvexcallback.hpp" #include "collisiontype.hpp" #include "contacttestwrapper.h" @@ -7,7 +5,6 @@ #include #include -#include "collisiontype.hpp" #include "projectile.hpp" namespace MWPhysics diff --git a/apps/openmw/mwphysics/contacttestresultcallback.hpp b/apps/openmw/mwphysics/contacttestresultcallback.hpp index fbe12d5dc..3d1b3b8aa 100644 --- a/apps/openmw/mwphysics/contacttestresultcallback.hpp +++ b/apps/openmw/mwphysics/contacttestresultcallback.hpp @@ -5,8 +5,6 @@ #include -#include "../mwworld/ptr.hpp" - #include "physicssystem.hpp" class btCollisionObject; diff --git a/apps/openmw/mwphysics/heightfield.cpp b/apps/openmw/mwphysics/heightfield.cpp index e210bc390..0856d8bb5 100644 --- a/apps/openmw/mwphysics/heightfield.cpp +++ b/apps/openmw/mwphysics/heightfield.cpp @@ -50,6 +50,10 @@ namespace namespace MWPhysics { + HeightField::HeightField() {} + + HeightField::HeightField(const HeightField&, const osg::CopyOp&) {} + HeightField::HeightField(const float* heights, int x, int y, float triSize, float sqrtVerts, float minH, float maxH, const osg::Object* holdObject, PhysicsTaskScheduler* scheduler) : mHoldObject(holdObject) #if BT_BULLET_VERSION < 310 diff --git a/apps/openmw/mwphysics/heightfield.hpp b/apps/openmw/mwphysics/heightfield.hpp index 93b2733f3..007fd12f5 100644 --- a/apps/openmw/mwphysics/heightfield.hpp +++ b/apps/openmw/mwphysics/heightfield.hpp @@ -2,6 +2,7 @@ #define OPENMW_MWPHYSICS_HEIGHTFIELD_H #include +#include #include @@ -20,12 +21,14 @@ namespace MWPhysics { class PhysicsTaskScheduler; - class HeightField + class HeightField : public osg::Object { public: HeightField(const float* heights, int x, int y, float triSize, float sqrtVerts, float minH, float maxH, const osg::Object* holdObject, PhysicsTaskScheduler* scheduler); ~HeightField(); + META_Object(MWPhysics, HeightField) + btCollisionObject* getCollisionObject(); const btCollisionObject* getCollisionObject() const; const btHeightfieldTerrainShape* getShape() const; @@ -40,6 +43,8 @@ namespace MWPhysics PhysicsTaskScheduler* mTaskScheduler; + HeightField(); + HeightField(const HeightField&, const osg::CopyOp&); void operator=(const HeightField&); HeightField(const HeightField&); }; diff --git a/apps/openmw/mwphysics/movementsolver.cpp b/apps/openmw/mwphysics/movementsolver.cpp index 5c3c771f2..07dff6932 100644 --- a/apps/openmw/mwphysics/movementsolver.cpp +++ b/apps/openmw/mwphysics/movementsolver.cpp @@ -171,7 +171,7 @@ namespace MWPhysics // Reset per-frame data physicActor->setWalkingOnWater(false); // Anything to collide with? - if(!physicActor->getCollisionMode()) + if(!physicActor->getCollisionMode() || actor.mSkipCollisionDetection) { actor.mPosition += (osg::Quat(refpos.rot[0], osg::Vec3f(-1, 0, 0)) * osg::Quat(refpos.rot[2], osg::Vec3f(0, 0, -1)) @@ -567,7 +567,7 @@ namespace MWPhysics return; auto* physicActor = actor.mActorRaw; - if(!physicActor->getCollisionMode()) // noclipping/tcl + if(!physicActor->getCollisionMode() || actor.mSkipCollisionDetection) // noclipping/tcl return; auto* collisionObject = physicActor->getCollisionObject(); diff --git a/apps/openmw/mwphysics/movementsolver.hpp b/apps/openmw/mwphysics/movementsolver.hpp index 76141ec0e..90b10c275 100644 --- a/apps/openmw/mwphysics/movementsolver.hpp +++ b/apps/openmw/mwphysics/movementsolver.hpp @@ -1,8 +1,6 @@ #ifndef OPENMW_MWPHYSICS_MOVEMENTSOLVER_H #define OPENMW_MWPHYSICS_MOVEMENTSOLVER_H -#include - #include #include "constants.hpp" diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index 4957ef422..5a7f5f044 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -106,16 +106,6 @@ namespace return actorData.mPosition * interpolationFactor + actorData.mActorRaw->getPreviousPosition() * (1.f - interpolationFactor); } - struct WorldFrameData - { - WorldFrameData() : mIsInStorm(MWBase::Environment::get().getWorld()->isInStorm()) - , mStormDirection(MWBase::Environment::get().getWorld()->getStormDirection()) - {} - - bool mIsInStorm; - osg::Vec3f mStormDirection; - }; - namespace Config { /// @return either the number of thread as configured by the user, or 1 if Bullet doesn't support multithreading @@ -148,7 +138,7 @@ namespace MWPhysics , mRemainingSteps(0) , mLOSCacheExpiry(Settings::Manager::getInt("lineofsight keep inactive cache", "Physics")) , mDeferAabbUpdate(Settings::Manager::getBool("defer aabb update", "Physics")) - , mNewFrame(false) + , mFrameCounter(0) , mAdvanceSimulation(false) , mQuit(false) , mNextJob(0) @@ -177,53 +167,22 @@ namespace MWPhysics mDeferAabbUpdate = false; } - mPreStepBarrier = std::make_unique(mNumThreads, [&]() - { - if (mDeferAabbUpdate) - updateAabbs(); - if (!mRemainingSteps) - return; - for (auto& data : mActorsFrameData) - if (data.mActor.lock()) - { - std::unique_lock lock(mCollisionWorldMutex); - MovementSolver::unstuck(data, mCollisionWorld); - } - }); + mPreStepBarrier = std::make_unique(mNumThreads); - mPostStepBarrier = std::make_unique(mNumThreads, [&]() - { - if (mRemainingSteps) - { - --mRemainingSteps; - updateActorsPositions(); - } - mNextJob.store(0, std::memory_order_release); - }); + mPostStepBarrier = std::make_unique(mNumThreads); - mPostSimBarrier = std::make_unique(mNumThreads, [&]() - { - mNewFrame = false; - if (mLOSCacheExpiry >= 0) - { - std::unique_lock lock(mLOSCacheMutex); - mLOSCache.erase( - std::remove_if(mLOSCache.begin(), mLOSCache.end(), - [](const LOSRequest& req) { return req.mStale; }), - mLOSCache.end()); - } - mTimeEnd = mTimer->tick(); - }); + mPostSimBarrier = std::make_unique(mNumThreads); } PhysicsTaskScheduler::~PhysicsTaskScheduler() { + waitForWorkers(); std::unique_lock lock(mSimulationMutex); mQuit = true; mNumJobs = 0; mRemainingSteps = 0; - lock.unlock(); mHasJob.notify_all(); + lock.unlock(); for (auto& thread : mThreads) thread.join(); } @@ -276,9 +235,10 @@ namespace MWPhysics const std::vector& PhysicsTaskScheduler::moveActors(float & timeAccum, std::vector&& actorsData, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) { + waitForWorkers(); + // This function run in the main thread. // While the mSimulationMutex is held, background physics threads can't run. - std::unique_lock lock(mSimulationMutex); double timeStart = mTimer->tick(); @@ -324,7 +284,7 @@ namespace MWPhysics mPhysicsDt = newDelta; mActorsFrameData = std::move(actorsData); mAdvanceSimulation = (mRemainingSteps != 0); - mNewFrame = true; + ++mFrameCounter; mNumJobs = mActorsFrameData.size(); mNextLOS.store(0, std::memory_order_relaxed); mNextJob.store(0, std::memory_order_release); @@ -344,8 +304,8 @@ namespace MWPhysics } mAsyncStartTime = mTimer->tick(); - lock.unlock(); mHasJob.notify_all(); + lock.unlock(); if (mAdvanceSimulation) mBudget.update(mTimer->delta_s(timeStart, mTimer->tick()), 1, mBudgetCursor); return mMovedActors; @@ -353,6 +313,7 @@ namespace MWPhysics const std::vector& PhysicsTaskScheduler::resetSimulation(const ActorMap& actors) { + waitForWorkers(); std::unique_lock lock(mSimulationMutex); mBudget.reset(mDefaultPhysicsDt); mAsyncBudget.reset(0.0f); @@ -361,7 +322,6 @@ namespace MWPhysics for (const auto& [_, actor] : actors) { actor->updatePosition(); - actor->setSimulationPosition(actor->getWorldPosition()); // updatePosition skip next simulation, now we need to "consume" it actor->updateCollisionObjectPosition(); mMovedActors.emplace_back(actor->getPtr()); } @@ -520,13 +480,15 @@ namespace MWPhysics void PhysicsTaskScheduler::worker() { + std::size_t lastFrame = 0; std::shared_lock lock(mSimulationMutex); while (!mQuit) { - if (!mNewFrame) - mHasJob.wait(lock, [&]() { return mQuit || mNewFrame; }); + if (mRemainingSteps == 0 && lastFrame == mFrameCounter) + mHasJob.wait(lock, [&] { return mQuit || lastFrame != mFrameCounter; }); + lastFrame = mFrameCounter; - mPreStepBarrier->wait(); + mPreStepBarrier->wait([this] { afterPreStep(); }); int job = 0; while (mRemainingSteps && (job = mNextJob.fetch_add(1, std::memory_order_relaxed)) < mNumJobs) @@ -538,7 +500,7 @@ namespace MWPhysics } } - mPostStepBarrier->wait(); + mPostStepBarrier->wait([this] { afterPostStep(); }); if (!mRemainingSteps) { @@ -553,7 +515,7 @@ namespace MWPhysics if (mLOSCacheExpiry >= 0) refreshLOSCache(); - mPostSimBarrier->wait(); + mPostSimBarrier->wait([this] { afterPostSim(); }); } } } @@ -634,4 +596,59 @@ namespace MWPhysics std::shared_lock lock(mCollisionWorldMutex); mDebugDrawer->step(); } + + void PhysicsTaskScheduler::afterPreStep() + { + if (mDeferAabbUpdate) + updateAabbs(); + if (!mRemainingSteps) + return; + for (auto& data : mActorsFrameData) + if (const auto actor = data.mActor.lock()) + { + std::unique_lock lock(mCollisionWorldMutex); + MovementSolver::unstuck(data, mCollisionWorld); + } + } + + void PhysicsTaskScheduler::afterPostStep() + { + if (mRemainingSteps) + { + --mRemainingSteps; + updateActorsPositions(); + } + mNextJob.store(0, std::memory_order_release); + } + + void PhysicsTaskScheduler::afterPostSim() + { + if (mLOSCacheExpiry >= 0) + { + std::unique_lock lock(mLOSCacheMutex); + mLOSCache.erase( + std::remove_if(mLOSCache.begin(), mLOSCache.end(), + [](const LOSRequest& req) { return req.mStale; }), + mLOSCache.end()); + } + mTimeEnd = mTimer->tick(); + std::unique_lock lock(mWorkersDoneMutex); + ++mWorkersFrameCounter; + mWorkersDone.notify_all(); + } + + // Attempt to acquire unique lock on mSimulationMutex while not all worker + // threads are holding shared lock but will have to may lead to a deadlock because + // C++ standard does not guarantee priority for exclusive and shared locks + // for std::shared_mutex. For example microsoft STL implementation points out + // for the absence of such priority: + // https://docs.microsoft.com/en-us/windows/win32/sync/slim-reader-writer--srw--locks + void PhysicsTaskScheduler::waitForWorkers() + { + if (mNumThreads == 0) + return; + std::unique_lock lock(mWorkersDoneMutex); + if (mFrameCounter != mWorkersFrameCounter) + mWorkersDone.wait(lock); + } } diff --git a/apps/openmw/mwphysics/mtphysics.hpp b/apps/openmw/mwphysics/mtphysics.hpp index 6d2c392c0..0c4402579 100644 --- a/apps/openmw/mwphysics/mtphysics.hpp +++ b/apps/openmw/mwphysics/mtphysics.hpp @@ -66,6 +66,10 @@ namespace MWPhysics void updatePtrAabb(const std::weak_ptr& ptr); void updateStats(osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats); std::tuple calculateStepConfig(float timeAccum) const; + void afterPreStep(); + void afterPostStep(); + void afterPostSim(); + void waitForWorkers(); std::unique_ptr mWorldFrameData; std::vector mActorsFrameData; @@ -88,7 +92,7 @@ namespace MWPhysics int mRemainingSteps; int mLOSCacheExpiry; bool mDeferAabbUpdate; - bool mNewFrame; + std::size_t mFrameCounter; bool mAdvanceSimulation; bool mThreadSafeBullet; bool mQuit; @@ -96,6 +100,10 @@ namespace MWPhysics std::atomic mNextLOS; std::vector mThreads; + std::size_t mWorkersFrameCounter = 0; + std::condition_variable mWorkersDone; + std::mutex mWorkersDoneMutex; + mutable std::shared_mutex mSimulationMutex; mutable std::shared_mutex mCollisionWorldMutex; mutable std::shared_mutex mLOSCacheMutex; diff --git a/apps/openmw/mwphysics/object.cpp b/apps/openmw/mwphysics/object.cpp index e3615989d..1e69136ab 100644 --- a/apps/openmw/mwphysics/object.cpp +++ b/apps/openmw/mwphysics/object.cpp @@ -27,8 +27,8 @@ namespace MWPhysics mCollisionObject->setUserPointer(this); setScale(ptr.getCellRef().getScale()); - setRotation(Misc::Convert::toBullet(ptr.getRefData().getBaseNode()->getAttitude())); - setOrigin(Misc::Convert::toBullet(ptr.getRefData().getPosition().asVec3())); + setRotation(ptr.getRefData().getBaseNode()->getAttitude()); + updatePosition(); commitPositionChange(); mTaskScheduler->addCollisionObject(mCollisionObject.get(), collisionType, CollisionType_Actor|CollisionType_HeightMap|CollisionType_Projectile); @@ -51,17 +51,17 @@ namespace MWPhysics mScaleUpdatePending = true; } - void Object::setRotation(const btQuaternion& quat) + void Object::setRotation(const osg::Quat& quat) { std::unique_lock lock(mPositionMutex); - mLocalTransform.setRotation(quat); + mRotation = quat; mTransformUpdatePending = true; } - void Object::setOrigin(const btVector3& vec) + void Object::updatePosition() { std::unique_lock lock(mPositionMutex); - mLocalTransform.setOrigin(vec); + mPosition = mPtr.getRefData().getPosition().asVec3(); mTransformUpdatePending = true; } @@ -75,7 +75,10 @@ namespace MWPhysics } if (mTransformUpdatePending) { - mCollisionObject->setWorldTransform(mLocalTransform); + btTransform trans; + trans.setOrigin(Misc::Convert::toBullet(mPosition)); + trans.setRotation(Misc::Convert::toBullet(mRotation)); + mCollisionObject->setWorldTransform(trans); mTransformUpdatePending = false; } } @@ -93,7 +96,10 @@ namespace MWPhysics btTransform Object::getTransform() const { std::unique_lock lock(mPositionMutex); - return mLocalTransform; + btTransform trans; + trans.setOrigin(Misc::Convert::toBullet(mPosition)); + trans.setRotation(Misc::Convert::toBullet(mRotation)); + return trans; } bool Object::isSolid() const @@ -119,11 +125,8 @@ namespace MWPhysics assert (mShapeInstance->getCollisionShape()->isCompound()); btCompoundShape* compound = static_cast(mShapeInstance->getCollisionShape()); - for (const auto& shape : mShapeInstance->mAnimatedShapes) + for (const auto& [recIndex, shapeIndex] : mShapeInstance->mAnimatedShapes) { - int recIndex = shape.first; - int shapeIndex = shape.second; - auto nodePathFound = mRecIndexToNodePath.find(recIndex); if (nodePathFound == mRecIndexToNodePath.end()) { diff --git a/apps/openmw/mwphysics/object.hpp b/apps/openmw/mwphysics/object.hpp index cae877180..c640318c7 100644 --- a/apps/openmw/mwphysics/object.hpp +++ b/apps/openmw/mwphysics/object.hpp @@ -16,7 +16,6 @@ namespace Resource } class btCollisionObject; -class btQuaternion; class btVector3; namespace MWPhysics @@ -31,8 +30,8 @@ namespace MWPhysics const Resource::BulletShapeInstance* getShapeInstance() const; void setScale(float scale); - void setRotation(const btQuaternion& quat); - void setOrigin(const btVector3& vec); + void setRotation(const osg::Quat& quat); + void updatePosition(); void commitPositionChange(); btCollisionObject* getCollisionObject(); const btCollisionObject* getCollisionObject() const; @@ -51,7 +50,8 @@ namespace MWPhysics std::map mRecIndexToNodePath; bool mSolid; btVector3 mScale; - btTransform mLocalTransform; + osg::Vec3f mPosition; + osg::Quat mRotation; bool mScaleUpdatePending; bool mTransformUpdatePending; mutable std::mutex mPositionMutex; diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index c5ba902fc..a820a5855 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -23,7 +23,6 @@ #include #include #include -#include #include #include #include @@ -56,7 +55,6 @@ #include "closestnotmerayresultcallback.hpp" #include "contacttestresultcallback.hpp" #include "projectileconvexcallback.hpp" -#include "constants.hpp" #include "movementsolver.hpp" #include "mtphysics.hpp" @@ -445,7 +443,7 @@ namespace MWPhysics void PhysicsSystem::addHeightField (const float* heights, int x, int y, float triSize, float sqrtVerts, float minH, float maxH, const osg::Object* holdObject) { - mHeightFields[std::make_pair(x,y)] = std::make_unique(heights, x, y, triSize, sqrtVerts, minH, maxH, holdObject, mTaskScheduler.get()); + mHeightFields[std::make_pair(x,y)] = osg::ref_ptr(new HeightField(heights, x, y, triSize, sqrtVerts, minH, maxH, holdObject, mTaskScheduler.get())); } void PhysicsSystem::removeHeightField (int x, int y) @@ -622,7 +620,7 @@ namespace MWPhysics mTaskScheduler->convexSweepTest(projectile->getConvexShape(), from_, to_, resultCallback); - const auto newpos = projectile->isActive() ? position : Misc::Convert::toOsg(resultCallback.m_hitPointWorld); + const auto newpos = projectile->isActive() ? position : Misc::Convert::toOsg(projectile->getHitPosition()); projectile->setPosition(newpos); mTaskScheduler->updateSingleAabb(foundProjectile->second); } @@ -632,7 +630,7 @@ namespace MWPhysics ObjectMap::iterator found = mObjects.find(ptr); if (found != mObjects.end()) { - found->second->setRotation(Misc::Convert::toBullet(ptr.getRefData().getBaseNode()->getAttitude())); + found->second->setRotation(ptr.getRefData().getBaseNode()->getAttitude()); mTaskScheduler->updateSingleAabb(found->second); return; } @@ -653,7 +651,7 @@ namespace MWPhysics ObjectMap::iterator found = mObjects.find(ptr); if (found != mObjects.end()) { - found->second->setOrigin(Misc::Convert::toBullet(ptr.getRefData().getPosition().asVec3())); + found->second->updatePosition(); mTaskScheduler->updateSingleAabb(found->second); return; } @@ -683,11 +681,19 @@ namespace MWPhysics if (!shape) return; - auto actor = std::make_shared(ptr, shape, mTaskScheduler.get()); + // check if Actor should spawn above water + const MWMechanics::MagicEffects& effects = ptr.getClass().getCreatureStats(ptr).getMagicEffects(); + const bool canWaterWalk = effects.get(ESM::MagicEffect::WaterWalking).getMagnitude() > 0; + + auto actor = std::make_shared(ptr, shape, mTaskScheduler.get(), canWaterWalk); + + // check if Actor is on the ground or in the air + traceDown(ptr, ptr.getRefData().getPosition().asVec3(), 10.f); + mActors.emplace(ptr, std::move(actor)); } - int PhysicsSystem::addProjectile (const MWWorld::Ptr& caster, const osg::Vec3f& position, const std::string& mesh, bool computeRadius, bool canTraverseWater) + int PhysicsSystem::addProjectile (const MWWorld::Ptr& caster, const osg::Vec3f& position, const std::string& mesh, bool computeRadius) { osg::ref_ptr shapeInstance = mShapeManager->getInstance(mesh); assert(shapeInstance); @@ -695,7 +701,7 @@ namespace MWPhysics mProjectileId++; - auto projectile = std::make_shared(caster, position, radius, canTraverseWater, mTaskScheduler.get(), this); + auto projectile = std::make_shared(caster, position, radius, mTaskScheduler.get(), this); mProjectiles.emplace(mProjectileId, std::move(projectile)); return mProjectileId; @@ -727,21 +733,15 @@ namespace MWPhysics void PhysicsSystem::queueObjectMovement(const MWWorld::Ptr &ptr, const osg::Vec3f &velocity) { - for(auto& movementItem : mMovementQueue) - { - if (movementItem.first == ptr) - { - movementItem.second = velocity; - return; - } - } - - mMovementQueue.emplace_back(ptr, velocity); + ActorMap::iterator found = mActors.find(ptr); + if (found != mActors.end()) + found->second->setVelocity(velocity); } void PhysicsSystem::clearQueuedMovement() { - mMovementQueue.clear(); + for (const auto& [_, actor] : mActors) + actor->setVelocity(osg::Vec3f()); } const std::vector& PhysicsSystem::applyQueuedMovement(float dt, bool skipSimulation, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) @@ -758,27 +758,21 @@ namespace MWPhysics std::vector PhysicsSystem::prepareFrameData(bool willSimulate) { std::vector actorsFrameData; - actorsFrameData.reserve(mMovementQueue.size()); + actorsFrameData.reserve(mActors.size()); const MWBase::World *world = MWBase::Environment::get().getWorld(); - for (const auto& [character, movement] : mMovementQueue) + for (const auto& [ptr, physicActor] : mActors) { - const auto foundActor = mActors.find(character); - if (foundActor == mActors.end()) // actor was already removed from the scene - continue; - - auto physicActor = foundActor->second; - float waterlevel = -std::numeric_limits::max(); - const MWWorld::CellStore *cell = character.getCell(); + const MWWorld::CellStore *cell = ptr.getCell(); if(cell->getCell()->hasWater()) waterlevel = cell->getWaterLevel(); - const MWMechanics::MagicEffects& effects = character.getClass().getCreatureStats(character).getMagicEffects(); + const MWMechanics::MagicEffects& effects = ptr.getClass().getCreatureStats(physicActor->getPtr()).getMagicEffects(); bool waterCollision = false; if (cell->getCell()->hasWater() && effects.get(ESM::MagicEffect::WaterWalking).getMagnitude()) { - if (physicActor->getCollisionMode() || !world->isUnderwater(character.getCell(), osg::Vec3f(character.getRefData().getPosition().asVec3()))) + if (physicActor->getCollisionMode() || !world->isUnderwater(ptr.getCell(), osg::Vec3f(ptr.getRefData().getPosition().asVec3()))) waterCollision = true; } @@ -792,9 +786,8 @@ namespace MWPhysics if (!willSimulate) standingOn = physicActor->getStandingOnPtr(); - actorsFrameData.emplace_back(std::move(physicActor), standingOn, waterCollision, movement, slowFall, waterlevel); + actorsFrameData.emplace_back(physicActor, standingOn, waterCollision, slowFall, waterlevel); } - mMovementQueue.clear(); return actorsFrameData; } @@ -935,10 +928,10 @@ namespace MWPhysics } ActorFrameData::ActorFrameData(const std::shared_ptr& actor, const MWWorld::Ptr standingOn, - bool waterCollision, osg::Vec3f movement, float slowFall, float waterlevel) + bool waterCollision, float slowFall, float waterlevel) : mActor(actor), mActorRaw(actor.get()), mStandingOn(standingOn), - mDidJump(false), mNeedLand(false), mWaterCollision(waterCollision), - mWaterlevel(waterlevel), mSlowFall(slowFall), mOldHeight(0), mFallHeight(0), mMovement(movement), mPosition(), mRefpos() + mDidJump(false), mNeedLand(false), mWaterCollision(waterCollision), mSkipCollisionDetection(actor->skipCollisions()), + mWaterlevel(waterlevel), mSlowFall(slowFall), mOldHeight(0), mFallHeight(0), mMovement(actor->velocity()), mPosition(), mRefpos() { const MWBase::World *world = MWBase::Environment::get().getWorld(); const auto ptr = actor->getPtr(); @@ -947,16 +940,12 @@ namespace MWPhysics mWantJump = ptr.getClass().getMovementSettings(ptr).mPosition[2] != 0; auto& stats = ptr.getClass().getCreatureStats(ptr); const bool godmode = ptr == world->getPlayerConstPtr() && world->getGodModeState(); - mFloatToSurface = stats.isDead() || (!godmode && stats.isParalyzed()); + mFloatToSurface = stats.isDead() || (!godmode && stats.getMagicEffects().get(ESM::MagicEffect::Paralyze).getModifier() > 0); mWasOnGround = actor->getOnGround(); } void ActorFrameData::updatePosition(btCollisionWorld* world) { - mActorRaw->updateWorldPosition(); - // If physics runs "fast enough", position are interpolated without simulation - // By calling this here, we are sure that offsets are applied at least once per frame, - // regardless of simulation speed. mActorRaw->applyOffsetChange(); mPosition = mActorRaw->getPosition(); if (mWaterCollision && mPosition.z() < mWaterlevel && canMoveToWaterSurface(mActorRaw, mWaterlevel, world)) diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index ce10b4246..f28d244d4 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -78,7 +78,7 @@ namespace MWPhysics struct ActorFrameData { - ActorFrameData(const std::shared_ptr& actor, const MWWorld::Ptr standingOn, bool moveToWaterSurface, osg::Vec3f movement, float slowFall, float waterlevel); + ActorFrameData(const std::shared_ptr& actor, const MWWorld::Ptr standingOn, bool moveToWaterSurface, float slowFall, float waterlevel); void updatePosition(btCollisionWorld* world); std::weak_ptr mActor; Actor* mActorRaw; @@ -91,6 +91,7 @@ namespace MWPhysics bool mFloatToSurface; bool mNeedLand; bool mWaterCollision; + bool mSkipCollisionDetection; float mWaterlevel; float mSlowFall; float mOldHeight; @@ -124,7 +125,7 @@ namespace MWPhysics void addObject (const MWWorld::Ptr& ptr, const std::string& mesh, int collisionType = CollisionType_World); void addActor (const MWWorld::Ptr& ptr, const std::string& mesh); - int addProjectile(const MWWorld::Ptr& caster, const osg::Vec3f& position, const std::string& mesh, bool computeRadius, bool canTraverseWater); + int addProjectile(const MWWorld::Ptr& caster, const osg::Vec3f& position, const std::string& mesh, bool computeRadius); void setCaster(int projectileId, const MWWorld::Ptr& caster); void updateProjectile(const int projectileId, const osg::Vec3f &position) const; void removeProjectile(const int projectileId); @@ -275,14 +276,11 @@ namespace MWPhysics using ProjectileMap = std::map>; ProjectileMap mProjectiles; - using HeightFieldMap = std::map, std::unique_ptr>; + using HeightFieldMap = std::map, osg::ref_ptr>; HeightFieldMap mHeightFields; bool mDebugDrawEnabled; - using PtrVelocityList = std::vector>; - PtrVelocityList mMovementQueue; - float mTimeAccum; unsigned int mProjectileId; diff --git a/apps/openmw/mwphysics/projectile.cpp b/apps/openmw/mwphysics/projectile.cpp index a93121997..0c7c99db3 100644 --- a/apps/openmw/mwphysics/projectile.cpp +++ b/apps/openmw/mwphysics/projectile.cpp @@ -3,28 +3,20 @@ #include #include -#include - -#include #include -#include -#include #include "../mwworld/class.hpp" #include "collisiontype.hpp" -#include "memory" #include "mtphysics.hpp" #include "projectile.hpp" namespace MWPhysics { -Projectile::Projectile(const MWWorld::Ptr& caster, const osg::Vec3f& position, float radius, bool canCrossWaterSurface, PhysicsTaskScheduler* scheduler, PhysicsSystem* physicssystem) - : mCanCrossWaterSurface(canCrossWaterSurface) - , mCrossedWaterSurface(false) +Projectile::Projectile(const MWWorld::Ptr& caster, const osg::Vec3f& position, float radius, PhysicsTaskScheduler* scheduler, PhysicsSystem* physicssystem) + : mHitWater(false) , mActive(true) , mCaster(caster) - , mWaterHitPosition(std::nullopt) , mPhysics(physicssystem) , mTaskScheduler(scheduler) { @@ -58,7 +50,9 @@ void Projectile::commitPositionChange() std::scoped_lock lock(mMutex); if (mTransformUpdatePending) { - mCollisionObject->setWorldTransform(mLocalTransform); + auto& trans = mCollisionObject->getWorldTransform(); + trans.setOrigin(Misc::Convert::toBullet(mPosition)); + mCollisionObject->setWorldTransform(trans); mTransformUpdatePending = false; } } @@ -66,19 +60,14 @@ void Projectile::commitPositionChange() void Projectile::setPosition(const osg::Vec3f &position) { std::scoped_lock lock(mMutex); - mLocalTransform.setOrigin(Misc::Convert::toBullet(position)); + mPosition = position; mTransformUpdatePending = true; } osg::Vec3f Projectile::getPosition() const { std::scoped_lock lock(mMutex); - return Misc::Convert::toOsg(mLocalTransform.getOrigin()); -} - -bool Projectile::canTraverseWater() const -{ - return mCanCrossWaterSurface; + return mPosition; } void Projectile::hit(MWWorld::Ptr target, btVector3 pos, btVector3 normal) @@ -131,17 +120,4 @@ bool Projectile::isValidTarget(const MWWorld::Ptr& target) const return validTarget; } -std::optional Projectile::getWaterHitPosition() -{ - return std::exchange(mWaterHitPosition, std::nullopt); -} - -void Projectile::setWaterHitPosition(btVector3 pos) -{ - if (mCrossedWaterSurface) - return; - mCrossedWaterSurface = true; - mWaterHitPosition = pos; -} - } diff --git a/apps/openmw/mwphysics/projectile.hpp b/apps/openmw/mwphysics/projectile.hpp index fb50eebde..c14b201d8 100644 --- a/apps/openmw/mwphysics/projectile.hpp +++ b/apps/openmw/mwphysics/projectile.hpp @@ -4,16 +4,14 @@ #include #include #include -#include -#include "components/misc/convert.hpp" +#include #include "ptrholder.hpp" class btCollisionObject; class btCollisionShape; class btConvexShape; -class btVector3; namespace osg { @@ -33,7 +31,7 @@ namespace MWPhysics class Projectile final : public PtrHolder { public: - Projectile(const MWWorld::Ptr& caster, const osg::Vec3f& position, float radius, bool canCrossWaterSurface, PhysicsTaskScheduler* scheduler, PhysicsSystem* physicssystem); + Projectile(const MWWorld::Ptr& caster, const osg::Vec3f& position, float radius, PhysicsTaskScheduler* scheduler, PhysicsSystem* physicssystem); ~Projectile() override; btConvexShape* getConvexShape() const { return mConvexShape; } @@ -62,15 +60,25 @@ namespace MWPhysics MWWorld::Ptr getCaster() const; void setCaster(MWWorld::Ptr caster); - bool canTraverseWater() const; + void setHitWater() + { + mHitWater = true; + } + + bool getHitWater() const + { + return mHitWater; + } void hit(MWWorld::Ptr target, btVector3 pos, btVector3 normal); void setValidTargets(const std::vector& targets); bool isValidTarget(const MWWorld::Ptr& target) const; - std::optional getWaterHitPosition(); - void setWaterHitPosition(btVector3 pos); + btVector3 getHitPosition() const + { + return mHitPosition; + } private: @@ -78,14 +86,12 @@ namespace MWPhysics btConvexShape* mConvexShape; std::unique_ptr mCollisionObject; - btTransform mLocalTransform; bool mTransformUpdatePending; - bool mCanCrossWaterSurface; - bool mCrossedWaterSurface; + bool mHitWater; std::atomic mActive; MWWorld::Ptr mCaster; MWWorld::Ptr mHitTarget; - std::optional mWaterHitPosition; + osg::Vec3f mPosition; btVector3 mHitPosition; btVector3 mHitNormal; diff --git a/apps/openmw/mwphysics/projectileconvexcallback.cpp b/apps/openmw/mwphysics/projectileconvexcallback.cpp index b803c4400..1e19937db 100644 --- a/apps/openmw/mwphysics/projectileconvexcallback.cpp +++ b/apps/openmw/mwphysics/projectileconvexcallback.cpp @@ -47,9 +47,7 @@ namespace MWPhysics } case CollisionType_Water: { - mProjectile->setWaterHitPosition(m_hitPointWorld); - if (mProjectile->canTraverseWater()) - return 1.f; + mProjectile->setHitWater(); mProjectile->hit(MWWorld::Ptr(), m_hitPointWorld, m_hitNormalWorld); break; } diff --git a/apps/openmw/mwphysics/projectileconvexcallback.hpp b/apps/openmw/mwphysics/projectileconvexcallback.hpp index c687de36c..96c84b1fe 100644 --- a/apps/openmw/mwphysics/projectileconvexcallback.hpp +++ b/apps/openmw/mwphysics/projectileconvexcallback.hpp @@ -1,8 +1,6 @@ #ifndef OPENMW_MWPHYSICS_PROJECTILECONVEXCALLBACK_H #define OPENMW_MWPHYSICS_PROJECTILECONVEXCALLBACK_H -#include - #include class btCollisionObject; diff --git a/apps/openmw/mwphysics/stepper.cpp b/apps/openmw/mwphysics/stepper.cpp index 4f5a27e53..1f53c1ac5 100644 --- a/apps/openmw/mwphysics/stepper.cpp +++ b/apps/openmw/mwphysics/stepper.cpp @@ -1,7 +1,5 @@ #include "stepper.hpp" -#include - #include #include diff --git a/apps/openmw/mwrender/actoranimation.cpp b/apps/openmw/mwrender/actoranimation.cpp index 845e917fb..59bc32765 100644 --- a/apps/openmw/mwrender/actoranimation.cpp +++ b/apps/openmw/mwrender/actoranimation.cpp @@ -11,7 +11,6 @@ #include #include -#include #include #include #include diff --git a/apps/openmw/mwrender/actoranimation.hpp b/apps/openmw/mwrender/actoranimation.hpp index 86929a18a..e149e4414 100644 --- a/apps/openmw/mwrender/actoranimation.hpp +++ b/apps/openmw/mwrender/actoranimation.hpp @@ -6,7 +6,6 @@ #include #include "../mwworld/containerstore.hpp" -#include "../mwworld/inventorystore.hpp" #include "animation.hpp" diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index b75e45906..e88c4cee3 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -23,7 +23,6 @@ #include #include -#include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwworld/class.hpp" #include "../mwworld/inventorystore.hpp" diff --git a/apps/openmw/mwrender/globalmap.cpp b/apps/openmw/mwrender/globalmap.cpp index 366da6439..8784eb501 100644 --- a/apps/openmw/mwrender/globalmap.cpp +++ b/apps/openmw/mwrender/globalmap.cpp @@ -9,7 +9,6 @@ #include -#include #include #include diff --git a/apps/openmw/mwrender/groundcover.cpp b/apps/openmw/mwrender/groundcover.cpp index fd2246253..63df3e249 100644 --- a/apps/openmw/mwrender/groundcover.cpp +++ b/apps/openmw/mwrender/groundcover.cpp @@ -241,7 +241,7 @@ namespace MWRender if (model.empty()) continue; model = "meshes/" + model; - instances[model].emplace_back(ref, model); + instances[model].emplace_back(std::move(ref), std::move(model)); } } } diff --git a/apps/openmw/mwrender/landmanager.hpp b/apps/openmw/mwrender/landmanager.hpp index 1694bd243..f3cc86085 100644 --- a/apps/openmw/mwrender/landmanager.hpp +++ b/apps/openmw/mwrender/landmanager.hpp @@ -3,7 +3,6 @@ #include -#include #include #include diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index ec2bf54a7..686078879 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -22,7 +22,6 @@ #include #include #include -#include #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" diff --git a/apps/openmw/mwrender/objectpaging.cpp b/apps/openmw/mwrender/objectpaging.cpp index 064d3aa35..6ab7ac4ce 100644 --- a/apps/openmw/mwrender/objectpaging.cpp +++ b/apps/openmw/mwrender/objectpaging.cpp @@ -105,6 +105,7 @@ namespace MWRender bool mOptimizeBillboards = true; float mSqrDistance = 0.f; osg::Vec3f mViewVector; + osg::Node::NodeMask mCopyMask = ~0u; mutable std::vector mNodePath; void copy(const osg::Node* toCopy, osg::Group* attachTo) @@ -121,6 +122,9 @@ namespace MWRender osg::Node* operator() (const osg::Node* node) const override { + if (!(node->getNodeMask() & mCopyMask)) + return nullptr; + if (const osg::Drawable* d = node->asDrawable()) return operator()(d); @@ -224,6 +228,9 @@ namespace MWRender } osg::Drawable* operator() (const osg::Drawable* drawable) const override { + if (!(drawable->getNodeMask() & mCopyMask)) + return nullptr; + if (dynamic_cast(drawable)) return nullptr; @@ -261,9 +268,11 @@ namespace MWRender class AnalyzeVisitor : public osg::NodeVisitor { public: - AnalyzeVisitor() + AnalyzeVisitor(osg::Node::NodeMask analyzeMask) : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) - , mCurrentStateSet(nullptr) {} + , mCurrentStateSet(nullptr) + , mCurrentDistance(0.f) + , mAnalyzeMask(analyzeMask) {} typedef std::unordered_map StateSetCounter; struct Result @@ -274,12 +283,34 @@ namespace MWRender void apply(osg::Node& node) override { + if (!(node.getNodeMask() & mAnalyzeMask)) + return; + if (node.getStateSet()) mCurrentStateSet = node.getStateSet(); + + if (osg::Switch* sw = node.asSwitch()) + { + for (unsigned int i=0; igetNumChildren(); ++i) + if (sw->getValue(i)) + traverse(*sw->getChild(i)); + return; + } + if (osg::LOD* lod = dynamic_cast(&node)) + { + for (unsigned int i=0; igetNumChildren(); ++i) + if (lod->getMinRange(i) * lod->getMinRange(i) <= mCurrentDistance && mCurrentDistance < lod->getMaxRange(i) * lod->getMaxRange(i)) + traverse(*lod->getChild(i)); + return; + } + traverse(node); } void apply(osg::Geometry& geom) override { + if (!(geom.getNodeMask() & mAnalyzeMask)) + return; + if (osg::Array* array = geom.getVertexArray()) mResult.mNumVerts += array->getNumElements(); @@ -313,6 +344,8 @@ namespace MWRender Result mResult; osg::StateSet* mCurrentStateSet; StateSetCounter mGlobalStateSetCounter; + float mCurrentDistance; + osg::Node::NodeMask mAnalyzeMask; }; class DebugVisitor : public osg::NodeVisitor @@ -331,6 +364,7 @@ namespace MWRender osg::ref_ptr stateset = node.getStateSet() ? osg::clone(node.getStateSet(), osg::CopyOp::SHALLOW_COPY) : new osg::StateSet; stateset->setAttribute(m); stateset->addUniform(new osg::Uniform("colorMode", 0)); + stateset->addUniform(new osg::Uniform("emissiveMult", 1.f)); node.setStateSet(stateset); } }; @@ -384,7 +418,7 @@ namespace MWRender { try { - unsigned int index = cell->mContextList.at(i).index; + unsigned int index = cell->mContextList[i].index; if (esm.size()<=index) esm.resize(index+1); cell->restore(esm[index], i); @@ -393,13 +427,13 @@ namespace MWRender bool deleted = false; while(cell->getNextRef(esm[index], ref, deleted)) { - Misc::StringUtils::lowerCaseInPlace(ref.mRefID); if (std::find(cell->mMovedRefs.begin(), cell->mMovedRefs.end(), ref.mRefNum) != cell->mMovedRefs.end()) continue; + Misc::StringUtils::lowerCaseInPlace(ref.mRefID); int type = store.findStatic(ref.mRefID); if (!typeFilter(type,size>=2)) continue; if (deleted) { refs.erase(ref.mRefNum); continue; } if (ref.mRefNum.fromGroundcoverFile()) continue; - refs[ref.mRefNum] = ref; + refs[ref.mRefNum] = std::move(ref); } } catch (std::exception&) @@ -407,15 +441,13 @@ namespace MWRender continue; } } - for (ESM::CellRefTracker::const_iterator it = cell->mLeasedRefs.begin(); it != cell->mLeasedRefs.end(); ++it) + for (auto [ref, deleted] : cell->mLeasedRefs) { - ESM::CellRef ref = it->first; - Misc::StringUtils::lowerCaseInPlace(ref.mRefID); - bool deleted = it->second; if (deleted) { refs.erase(ref.mRefNum); continue; } + Misc::StringUtils::lowerCaseInPlace(ref.mRefID); int type = store.findStatic(ref.mRefID); if (!typeFilter(type,size>=2)) continue; - refs[ref.mRefNum] = ref; + refs[ref.mRefNum] = std::move(ref); } } } @@ -438,7 +470,15 @@ namespace MWRender typedef std::map, InstanceList> NodeMap; NodeMap nodes; osg::ref_ptr refnumSet = activeGrid ? new RefnumSet : nullptr; - AnalyzeVisitor analyzeVisitor; + + // Mask_UpdateVisitor is used in such cases in NIF loader: + // 1. For collision nodes, which is not supposed to be rendered. + // 2. For nodes masked via Flag_Hidden (VisController can change this flag value at runtime). + // Since ObjectPaging does not handle VisController, we can just ignore both types of nodes. + constexpr auto copyMask = ~Mask_UpdateVisitor; + + AnalyzeVisitor analyzeVisitor(copyMask); + analyzeVisitor.mCurrentDistance = (viewPoint - worldCenter).length2(); float minSize = mMinSize; if (mMinSizeMergeFactor) minSize *= mMinSizeMergeFactor; @@ -525,6 +565,7 @@ namespace MWRender osg::ref_ptr templateRefs = new Resource::TemplateMultiRef; osgUtil::StateToCompile stateToCompile(0, nullptr); CopyOp copyop; + copyop.mCopyMask = copyMask; for (const auto& pair : nodes) { const osg::Node* cnode = pair.first; @@ -611,6 +652,7 @@ namespace MWRender } optimizer.setIsOperationPermissibleForObjectCallback(new CanOptimizeCallback); unsigned int options = SceneUtil::Optimizer::FLATTEN_STATIC_TRANSFORMS|SceneUtil::Optimizer::REMOVE_REDUNDANT_NODES|SceneUtil::Optimizer::MERGE_GEOMETRY; + mSceneManager->shareState(mergeGroup); optimizer.optimize(mergeGroup, options); group->addChild(mergeGroup); @@ -701,7 +743,7 @@ namespace MWRender ccf.mCell = cell; mCache->call(ccf); if (ccf.mToClear.empty()) return false; - for (auto chunk : ccf.mToClear) + for (const auto& chunk : ccf.mToClear) mCache->removeFromObjectCache(chunk); return true; } @@ -723,7 +765,7 @@ namespace MWRender ccf.mActiveGridOnly = true; mCache->call(ccf); if (ccf.mToClear.empty()) return false; - for (auto chunk : ccf.mToClear) + for (const auto& chunk : ccf.mToClear) mCache->removeFromObjectCache(chunk); return true; } diff --git a/apps/openmw/mwrender/recastmesh.cpp b/apps/openmw/mwrender/recastmesh.cpp index d07e7d37b..7c7c6b8eb 100644 --- a/apps/openmw/mwrender/recastmesh.cpp +++ b/apps/openmw/mwrender/recastmesh.cpp @@ -1,7 +1,6 @@ #include "recastmesh.hpp" #include -#include #include diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 8971e8e99..edd9547f3 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -45,14 +45,12 @@ #include #include -#include #include #include "../mwworld/cellstore.hpp" #include "../mwworld/class.hpp" #include "../mwgui/loadingscreen.hpp" -#include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwmechanics/actorutil.hpp" @@ -65,7 +63,6 @@ #include "viewovershoulder.hpp" #include "water.hpp" #include "terrainstorage.hpp" -#include "util.hpp" #include "navmesh.hpp" #include "actorspaths.hpp" #include "recastmesh.hpp" @@ -280,7 +277,7 @@ namespace MWRender globalDefines[itr->first] = itr->second; // Refactor this at some point - most shaders don't care about these defines - float groundcoverDistance = (Constants::CellSizeInUnits * std::max(1, Settings::Manager::getInt("distance", "Groundcover")) - 1024) * 0.93; + float groundcoverDistance = std::max(0.f, Settings::Manager::getFloat("rendering distance", "Groundcover")); globalDefines["groundcoverFadeStart"] = std::to_string(groundcoverDistance * 0.9f); globalDefines["groundcoverFadeEnd"] = std::to_string(groundcoverDistance); globalDefines["groundcoverStompMode"] = std::to_string(std::clamp(Settings::Manager::getInt("stomp mode", "Groundcover"), 0, 2)); @@ -367,6 +364,10 @@ namespace MWRender mGroundcover.reset(new Groundcover(mResourceSystem->getSceneManager(), density)); static_cast(mGroundcoverWorld.get())->addChunkManager(mGroundcover.get()); mResourceSystem->addResourceManager(mGroundcover.get()); + + // Groundcover it is handled in the same way indifferently from if it is from active grid or from distant cell. + // Use a stub grid to avoid splitting between chunks for active grid and chunks for distant cells. + mGroundcoverWorld->setActiveGrid(osg::Vec4i(0, 0, 0, 0)); } // water goes after terrain for correct waterculling order mWater.reset(new Water(sceneRoot->getParent(0), sceneRoot, mResourceSystem, mViewer->getIncrementalCompileOperation(), resourcePath)); @@ -1133,7 +1134,7 @@ namespace MWRender if (mGroundcoverWorld) { - int groundcoverDistance = Constants::CellSizeInUnits * std::max(1, Settings::Manager::getInt("distance", "Groundcover")); + float groundcoverDistance = std::max(0.f, Settings::Manager::getFloat("rendering distance", "Groundcover")); mGroundcoverWorld->setViewDistance(groundcoverDistance * (distanceMult ? 1.f/distanceMult : 1.f)); } } @@ -1374,8 +1375,6 @@ namespace MWRender void RenderingManager::setActiveGrid(const osg::Vec4i &grid) { mTerrain->setActiveGrid(grid); - if (mGroundcoverWorld) - mGroundcoverWorld->setActiveGrid(grid); } bool RenderingManager::pagingEnableObject(int type, const MWWorld::ConstPtr& ptr, bool enabled) { diff --git a/apps/openmw/mwrender/screenshotmanager.cpp b/apps/openmw/mwrender/screenshotmanager.cpp index 241585c14..b769943b6 100644 --- a/apps/openmw/mwrender/screenshotmanager.cpp +++ b/apps/openmw/mwrender/screenshotmanager.cpp @@ -37,15 +37,15 @@ namespace MWRender class NotifyDrawCompletedCallback : public osg::Camera::DrawCallback { public: - NotifyDrawCompletedCallback(unsigned int frame) - : mDone(false), mFrame(frame) + NotifyDrawCompletedCallback() + : mDone(false), mFrame(0) { } void operator () (osg::RenderInfo& renderInfo) const override { std::lock_guard lock(mMutex); - if (renderInfo.getState()->getFrameStamp()->getFrameNumber() >= mFrame) + if (renderInfo.getState()->getFrameStamp()->getFrameNumber() >= mFrame && !mDone) { mDone = true; mCondition.notify_one(); @@ -60,6 +60,13 @@ namespace MWRender mCondition.wait(lock); } + void reset(unsigned int frame) + { + std::lock_guard lock(mMutex); + mDone = false; + mFrame = frame; + } + mutable std::condition_variable mCondition; mutable std::mutex mMutex; mutable bool mDone; @@ -95,11 +102,16 @@ namespace MWRender : mViewer(viewer) , mRootNode(rootNode) , mSceneRoot(sceneRoot) + , mDrawCompleteCallback(new NotifyDrawCompletedCallback) , mResourceSystem(resourceSystem) , mWater(water) { } + ScreenshotManager::~ScreenshotManager() + { + } + void ScreenshotManager::screenshot(osg::Image* image, int w, int h) { osg::Camera * camera = mViewer->getCamera(); @@ -108,11 +120,8 @@ namespace MWRender tempDrw->setCullingActive(false); tempDrw->getOrCreateStateSet()->setRenderBinDetails(100, "RenderBin", osg::StateSet::USE_RENDERBIN_DETAILS); // so its after all scene bins but before POST_RENDER gui camera camera->addChild(tempDrw); - osg::ref_ptr callback(new NotifyDrawCompletedCallback(mViewer->getFrameStamp()->getFrameNumber())); - Misc::CallbackManager::instance().addCallbackOneshot(Misc::CallbackManager::DrawStage::Final, callback); - MWBase::Environment::get().getWindowManager()->viewerTraversals(false); - Misc::CallbackManager::instance().waitCallbackOneshot(Misc::CallbackManager::DrawStage::Final, callback); - // now that we've "used up" the current frame, get a fresh frame number for the next frame() following after the screenshot is completed + traversalsAndWait(mViewer->getFrameStamp()->getFrameNumber()); + // now that we've "used up" the current frame, get a fresh frame number for the next frame() following after the screenshot is completed mViewer->advance(mViewer->getFrameStamp()->getSimulationTime()); camera->removeChild(tempDrw); } @@ -255,6 +264,18 @@ namespace MWRender return true; } + void ScreenshotManager::traversalsAndWait(unsigned int frame) + { + // Ref https://gitlab.com/OpenMW/openmw/-/issues/6013 + mDrawCompleteCallback->reset(frame); + mViewer->getCamera()->setFinalDrawCallback(mDrawCompleteCallback); + + mViewer->eventTraversal(); + mViewer->updateTraversal(); + mViewer->renderingTraversals(); + mDrawCompleteCallback->waitTillDone(); + } + void ScreenshotManager::renderCameraToImage(osg::Camera *camera, osg::Image *image, int w, int h) { camera->setNodeMask(Mask_RenderToTexture); @@ -278,14 +299,10 @@ namespace MWRender mRootNode->addChild(camera); - // The draw needs to complete before we can copy back our image. - osg::ref_ptr callback (new NotifyDrawCompletedCallback(0)); - MWBase::Environment::get().getWindowManager()->getLoadingScreen()->loadingOn(false); - Misc::CallbackManager::instance().addCallbackOneshot(Misc::CallbackManager::DrawStage::Final, callback); - MWBase::Environment::get().getWindowManager()->viewerTraversals(false); - Misc::CallbackManager::instance().waitCallbackOneshot(Misc::CallbackManager::DrawStage::Final, callback); + // The draw needs to complete before we can copy back our image. + traversalsAndWait(0); MWBase::Environment::get().getWindowManager()->getLoadingScreen()->loadingOff(); diff --git a/apps/openmw/mwrender/screenshotmanager.hpp b/apps/openmw/mwrender/screenshotmanager.hpp index 2ac50bdf0..373fe3be8 100644 --- a/apps/openmw/mwrender/screenshotmanager.hpp +++ b/apps/openmw/mwrender/screenshotmanager.hpp @@ -16,11 +16,13 @@ namespace Resource namespace MWRender { class Water; + class NotifyDrawCompletedCallback; class ScreenshotManager { public: ScreenshotManager(osgViewer::Viewer* viewer, osg::ref_ptr rootNode, osg::ref_ptr sceneRoot, Resource::ResourceSystem* resourceSystem, Water* water); + ~ScreenshotManager(); void screenshot(osg::Image* image, int w, int h); bool screenshot360(osg::Image* image); @@ -29,9 +31,11 @@ namespace MWRender osg::ref_ptr mViewer; osg::ref_ptr mRootNode; osg::ref_ptr mSceneRoot; + osg::ref_ptr mDrawCompleteCallback; Resource::ResourceSystem* mResourceSystem; Water* mWater; + void traversalsAndWait(unsigned int frame); void renderCameraToImage(osg::Camera *camera, osg::Image *image, int w, int h); void makeCubemapScreenshot(osg::Image* image, int w, int h, osg::Matrixd cameraTransform=osg::Matrixd()); }; diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index f0e876470..8bac90604 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -46,7 +46,6 @@ #include #include -#include #include @@ -1315,7 +1314,8 @@ public: while (callback) { - if ((composite = dynamic_cast(callback))) + composite = dynamic_cast(callback); + if (composite) break; callback = callback->getNestedCallback(); diff --git a/apps/openmw/mwscript/cellextensions.cpp b/apps/openmw/mwscript/cellextensions.cpp index 356428156..b0977984f 100644 --- a/apps/openmw/mwscript/cellextensions.cpp +++ b/apps/openmw/mwscript/cellextensions.cpp @@ -13,7 +13,6 @@ #include "../mwworld/actionteleport.hpp" #include "../mwworld/cellstore.hpp" #include "../mwbase/environment.hpp" -#include "../mwworld/player.hpp" #include "../mwbase/statemanager.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" diff --git a/apps/openmw/mwscript/controlextensions.cpp b/apps/openmw/mwscript/controlextensions.cpp index 5362759e1..2716630d7 100644 --- a/apps/openmw/mwscript/controlextensions.cpp +++ b/apps/openmw/mwscript/controlextensions.cpp @@ -1,6 +1,5 @@ #include "controlextensions.hpp" -#include #include #include @@ -15,8 +14,6 @@ #include "../mwworld/class.hpp" #include "../mwworld/ptr.hpp" -#include "../mwmechanics/npcstats.hpp" - #include "interpretercontext.hpp" #include "ref.hpp" diff --git a/apps/openmw/mwscript/globalscripts.cpp b/apps/openmw/mwscript/globalscripts.cpp index 0d579abdc..b287d33b3 100644 --- a/apps/openmw/mwscript/globalscripts.cpp +++ b/apps/openmw/mwscript/globalscripts.cpp @@ -12,8 +12,6 @@ #include "../mwbase/world.hpp" #include "../mwbase/scriptmanager.hpp" -#include "../mwmechanics/creaturestats.hpp" - #include "interpretercontext.hpp" namespace diff --git a/apps/openmw/mwscript/interpretercontext.cpp b/apps/openmw/mwscript/interpretercontext.cpp index f3895c8b6..d8fd287d7 100644 --- a/apps/openmw/mwscript/interpretercontext.cpp +++ b/apps/openmw/mwscript/interpretercontext.cpp @@ -5,8 +5,6 @@ #include -#include - #include "../mwworld/esmstore.hpp" #include "../mwbase/environment.hpp" diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index d78337a62..5538e8536 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -278,7 +278,7 @@ namespace MWScript MWWorld::Ptr ptr = R()(runtime); - if (ptr.getRefData().activateByScript()) + if (ptr.getRefData().activateByScript() || ptr.getContainerStore()) context.executeActivation(ptr, MWMechanics::getPlayer()); } }; diff --git a/apps/openmw/mwscript/transformationextensions.cpp b/apps/openmw/mwscript/transformationextensions.cpp index ba211503e..dbd8e905a 100644 --- a/apps/openmw/mwscript/transformationextensions.cpp +++ b/apps/openmw/mwscript/transformationextensions.cpp @@ -32,7 +32,7 @@ namespace MWScript std::vector actors; MWBase::Environment::get().getWorld()->getActorsStandingOn (ptr, actors); for (auto& actor : actors) - MWBase::Environment::get().getWorld()->moveObjectBy(actor, diff, false); + MWBase::Environment::get().getWorld()->moveObjectBy(actor, diff, false, false); } template @@ -303,7 +303,7 @@ namespace MWScript } dynamic_cast(runtime.getContext()).updatePtr(ptr, - MWBase::Environment::get().getWorld()->moveObjectBy(ptr, newPos - curPos, true)); + MWBase::Environment::get().getWorld()->moveObjectBy(ptr, newPos - curPos, true, true)); } }; @@ -726,7 +726,7 @@ namespace MWScript // This approach can be used to create elevators. moveStandingActors(ptr, diff); dynamic_cast(runtime.getContext()).updatePtr(ptr, - MWBase::Environment::get().getWorld()->moveObjectBy(ptr, diff, false)); + MWBase::Environment::get().getWorld()->moveObjectBy(ptr, diff, false, true)); } }; @@ -762,7 +762,7 @@ namespace MWScript // This approach can be used to create elevators. moveStandingActors(ptr, diff); dynamic_cast(runtime.getContext()).updatePtr(ptr, - MWBase::Environment::get().getWorld()->moveObjectBy(ptr, diff, false)); + MWBase::Environment::get().getWorld()->moveObjectBy(ptr, diff, false, true)); } }; diff --git a/apps/openmw/mwsound/ffmpeg_decoder.cpp b/apps/openmw/mwsound/ffmpeg_decoder.cpp index ec36a5cdf..0a9641635 100644 --- a/apps/openmw/mwsound/ffmpeg_decoder.cpp +++ b/apps/openmw/mwsound/ffmpeg_decoder.cpp @@ -221,7 +221,7 @@ void FFmpeg_Decoder::open(const std::string &fname) if(!mStream) throw std::runtime_error("No audio streams in "+fname); - AVCodec *codec = avcodec_find_decoder((*mStream)->codecpar->codec_id); + const AVCodec *codec = avcodec_find_decoder((*mStream)->codecpar->codec_id); if(!codec) { std::string ss = "No codec found for id " + diff --git a/apps/openmw/mwsound/ffmpeg_decoder.hpp b/apps/openmw/mwsound/ffmpeg_decoder.hpp index f099c831c..0a67a4758 100644 --- a/apps/openmw/mwsound/ffmpeg_decoder.hpp +++ b/apps/openmw/mwsound/ffmpeg_decoder.hpp @@ -2,6 +2,12 @@ #define GAME_SOUND_FFMPEG_DECODER_H #include + +#if defined(_MSC_VER) + #pragma warning (push) + #pragma warning (disable : 4244) +#endif + extern "C" { #include @@ -14,6 +20,10 @@ extern "C" #include } +#if defined(_MSC_VER) + #pragma warning (pop) +#endif + #include #include diff --git a/apps/openmw/mwstate/quicksavemanager.hpp b/apps/openmw/mwstate/quicksavemanager.hpp index cdeff42c2..3272b24b5 100644 --- a/apps/openmw/mwstate/quicksavemanager.hpp +++ b/apps/openmw/mwstate/quicksavemanager.hpp @@ -4,7 +4,6 @@ #include #include "character.hpp" -#include "../mwbase/statemanager.hpp" namespace MWState{ class QuickSaveManager{ diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index fb418b94a..ffef9d74a 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -260,10 +260,9 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot writer.save (stream); Loading::Listener& listener = *MWBase::Environment::get().getWindowManager()->getLoadingScreen(); - int messagesCount = MWBase::Environment::get().getWindowManager()->getMessagesCount(); // Using only Cells for progress information, since they typically have the largest records by far listener.setProgressRange(MWBase::Environment::get().getWorld()->countSavedGameCells()); - listener.setLabel("#{sNotifyMessage4}", true, messagesCount > 0); + listener.setLabel("#{sNotifyMessage4}", true); Loading::ScopedLoad load(&listener); @@ -385,10 +384,9 @@ void MWState::StateManager::loadGame (const Character *character, const std::str std::map contentFileMap = buildContentFileIndexMap (reader); Loading::Listener& listener = *MWBase::Environment::get().getWindowManager()->getLoadingScreen(); - int messagesCount = MWBase::Environment::get().getWindowManager()->getMessagesCount(); listener.setProgressRange(100); - listener.setLabel("#{sLoadingMessage14}", false, messagesCount > 0); + listener.setLabel("#{sLoadingMessage14}"); Loading::ScopedLoad load(&listener); diff --git a/apps/openmw/mwworld/actionequip.cpp b/apps/openmw/mwworld/actionequip.cpp index 4cb0dbe51..92cd04438 100644 --- a/apps/openmw/mwworld/actionequip.cpp +++ b/apps/openmw/mwworld/actionequip.cpp @@ -91,13 +91,29 @@ namespace MWWorld // move all slots one towards begin(), then equip the item in the slot that is now free if (slot == slots_.first.end()) { - for (slot=slots_.first.begin();slot!=slots_.first.end(); ++slot) + ContainerStoreIterator enchItem = invStore.getSelectedEnchantItem(); + bool reEquip = false; + for (slot = slots_.first.begin(); slot != slots_.first.end(); ++slot) { invStore.unequipSlot(*slot, actor, false); - if (slot+1 != slots_.first.end()) - invStore.equip(*slot, invStore.getSlot(*(slot+1)), actor); + if (slot + 1 != slots_.first.end()) + { + invStore.equip(*slot, invStore.getSlot(*(slot + 1)), actor); + } else + { invStore.equip(*slot, it, actor); + } + + //Fix for issue of selected enchated item getting remmoved on cycle + if (invStore.getSlot(*slot) == enchItem) + { + reEquip = true; + } + } + if (reEquip) + { + invStore.setSelectedEnchantItem(enchItem); } } } diff --git a/apps/openmw/mwworld/cellpreloader.cpp b/apps/openmw/mwworld/cellpreloader.cpp index 6bc60968c..44afde22a 100644 --- a/apps/openmw/mwworld/cellpreloader.cpp +++ b/apps/openmw/mwworld/cellpreloader.cpp @@ -15,13 +15,9 @@ #include #include -#include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" - #include "../mwrender/landmanager.hpp" #include "cellstore.hpp" -#include "manualref.hpp" #include "class.hpp" namespace MWWorld @@ -448,7 +444,7 @@ namespace MWWorld void CellPreloader::abortTerrainPreloadExcept(const CellPreloader::PositionCellGrid *exceptPos) { const float resetThreshold = ESM::Land::REAL_SIZE; - for (auto pos : mTerrainPreloadPositions) + for (const auto& pos : mTerrainPreloadPositions) if (exceptPos && (pos.first-exceptPos->first).length2() < resetThreshold*resetThreshold && pos.second == exceptPos->second) return; if (mTerrainPreloadItem && !mTerrainPreloadItem->isDone()) @@ -461,10 +457,10 @@ namespace MWWorld bool contains(const std::vector& container, const std::vector& contained) { - for (auto pos : contained) + for (const auto& pos : contained) { bool found = false; - for (auto pos2 : container) + for (const auto& pos2 : container) { if ((pos.first-pos2.first).length2() < 1 && pos.second == pos2.second) { diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index 73f69e220..2e2735e11 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -345,8 +345,8 @@ namespace MWWorld void merge() { - for (std::map::const_iterator it = mMovedHere.begin(); it != mMovedHere.end(); ++it) - mMergeTo.push_back(it->first); + for (const auto & [base, _] : mMovedHere) + mMergeTo.push_back(base); } private: @@ -453,9 +453,9 @@ namespace MWWorld if (Ptr ptr = ::searchViaActorId (mCreatures, id, this, mMovedToAnotherCell)) return ptr; - for (MovedRefTracker::const_iterator it = mMovedHere.begin(); it != mMovedHere.end(); ++it) + for (const auto& [base, _] : mMovedHere) { - MWWorld::Ptr actor (it->first, this); + MWWorld::Ptr actor (base, this); if (!actor.getClass().isActor()) continue; if (actor.getClass().getCreatureStats (actor).matchesActorId (id) && actor.getRefData().getCount() > 0) @@ -547,7 +547,7 @@ namespace MWWorld try { // Reopen the ESM reader and seek to the right position. - int index = mCell->mContextList.at(i).index; + int index = mCell->mContextList[i].index; mCell->restore (esm[index], i); ESM::CellRef ref; @@ -566,7 +566,8 @@ namespace MWWorld continue; } - mIds.push_back (Misc::StringUtils::lowerCase (ref.mRefID)); + Misc::StringUtils::lowerCaseInPlace(ref.mRefID); + mIds.push_back(std::move(ref.mRefID)); } } catch (std::exception& e) @@ -576,11 +577,8 @@ namespace MWWorld } // List moved references, from separately tracked list. - for (ESM::CellRefTracker::const_iterator it = mCell->mLeasedRefs.begin(); it != mCell->mLeasedRefs.end(); ++it) + for (const auto& [ref, deleted]: mCell->mLeasedRefs) { - const ESM::CellRef &ref = it->first; - bool deleted = it->second; - if (!deleted) mIds.push_back(Misc::StringUtils::lowerCase(ref.mRefID)); } @@ -605,7 +603,7 @@ namespace MWWorld try { // Reopen the ESM reader and seek to the right position. - int index = mCell->mContextList.at(i).index; + int index = mCell->mContextList[i].index; mCell->restore (esm[index], i); ESM::CellRef ref; @@ -632,10 +630,10 @@ namespace MWWorld } // Load moved references, from separately tracked list. - for (ESM::CellRefTracker::const_iterator it = mCell->mLeasedRefs.begin(); it != mCell->mLeasedRefs.end(); ++it) + for (const auto& leasedRef : mCell->mLeasedRefs) { - ESM::CellRef &ref = const_cast(it->first); - bool deleted = it->second; + ESM::CellRef &ref = const_cast(leasedRef.first); + bool deleted = leasedRef.second; loadRef (ref, deleted, refNumToID); } @@ -806,11 +804,10 @@ namespace MWWorld writeReferenceCollection (writer, mWeapons); writeReferenceCollection (writer, mBodyParts); - for (MovedRefTracker::const_iterator it = mMovedToAnotherCell.begin(); it != mMovedToAnotherCell.end(); ++it) + for (const auto& [base, store] : mMovedToAnotherCell) { - LiveCellRefBase* base = it->first; ESM::RefNum refNum = base->mRef.getRefNum(); - ESM::CellId movedTo = it->second->getCell()->getCellId(); + ESM::CellId movedTo = store->getCell()->getCellId(); refNum.save(writer, true, "MVRF"); movedTo.save(writer); @@ -1137,9 +1134,9 @@ namespace MWWorld updateRechargingItems(); mRechargingItemsUpToDate = true; } - for (TRechargingItems::iterator it = mRechargingItems.begin(); it != mRechargingItems.end(); ++it) + for (const auto& [item, charge] : mRechargingItems) { - MWMechanics::rechargeItem(it->first, it->second, duration); + MWMechanics::rechargeItem(item, charge, duration); } } @@ -1147,38 +1144,22 @@ namespace MWWorld { mRechargingItems.clear(); - for (CellRefList::List::iterator it (mWeapons.mList.begin()); it!=mWeapons.mList.end(); ++it) - { - Ptr ptr = getCurrentPtr(&*it); - if (!ptr.isEmpty() && ptr.getRefData().getCount() > 0) - { - checkItem(ptr); - } - } - for (CellRefList::List::iterator it (mArmors.mList.begin()); it!=mArmors.mList.end(); ++it) - { - Ptr ptr = getCurrentPtr(&*it); - if (!ptr.isEmpty() && ptr.getRefData().getCount() > 0) - { - checkItem(ptr); - } - } - for (CellRefList::List::iterator it (mClothes.mList.begin()); it!=mClothes.mList.end(); ++it) + const auto update = [this](auto& list) { - Ptr ptr = getCurrentPtr(&*it); - if (!ptr.isEmpty() && ptr.getRefData().getCount() > 0) + for (auto & item : list) { - checkItem(ptr); - } - } - for (CellRefList::List::iterator it (mBooks.mList.begin()); it!=mBooks.mList.end(); ++it) - { - Ptr ptr = getCurrentPtr(&*it); - if (!ptr.isEmpty() && ptr.getRefData().getCount() > 0) - { - checkItem(ptr); + Ptr ptr = getCurrentPtr(&item); + if (!ptr.isEmpty() && ptr.getRefData().getCount() > 0) + { + checkItem(ptr); + } } - } + }; + + update(mWeapons.mList); + update(mArmors.mList); + update(mClothes.mList); + update(mBooks.mList); } void MWWorld::CellStore::checkItem(Ptr ptr) diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index a69d74b05..b1885edaf 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -1,5 +1,6 @@ #include "esmstore.hpp" +#include #include #include @@ -8,12 +9,23 @@ #include #include #include +#include #include "../mwmechanics/spelllist.hpp" namespace { - void readRefs(const ESM::Cell& cell, std::map& refs, std::vector& readers) + struct Ref + { + ESM::RefNum mRefNum; + std::size_t mRefID; + + Ref(ESM::RefNum refNum, std::size_t refID) : mRefNum(refNum), mRefID(refID) {} + }; + + constexpr std::size_t deletedRefID = std::numeric_limits::max(); + + void readRefs(const ESM::Cell& cell, std::vector& refs, std::vector& refIDs, std::vector& readers) { for (size_t i = 0; i < cell.mContextList.size(); i++) { @@ -27,24 +39,22 @@ namespace while(cell.getNextRef(readers[index], ref, deleted)) { if(deleted) - refs.erase(ref.mRefNum); + refs.emplace_back(ref.mRefNum, deletedRefID); else if (std::find(cell.mMovedRefs.begin(), cell.mMovedRefs.end(), ref.mRefNum) == cell.mMovedRefs.end()) { - Misc::StringUtils::lowerCaseInPlace(ref.mRefID); - refs[ref.mRefNum] = ref.mRefID; + refs.emplace_back(ref.mRefNum, refIDs.size()); + refIDs.push_back(std::move(ref.mRefID)); } } } - for(const auto& it : cell.mLeasedRefs) + for(const auto& [value, deleted] : cell.mLeasedRefs) { - bool deleted = it.second; if(deleted) - refs.erase(it.first.mRefNum); + refs.emplace_back(value.mRefNum, deletedRefID); else { - ESM::CellRef ref = it.first; - Misc::StringUtils::lowerCaseInPlace(ref.mRefID); - refs[ref.mRefNum] = ref.mRefID; + refs.emplace_back(value.mRefNum, refIDs.size()); + refIDs.push_back(value.mRefID); } } } @@ -63,7 +73,7 @@ namespace // We will replace invalid entries by fixed ones std::vector npcsToReplace; - for (auto npcIter : npcs) + for (const auto& npcIter : npcs) { ESM::NPC npc = npcIter.second; bool changed = false; @@ -248,14 +258,26 @@ void ESMStore::countRecords() { if(!mRefCount.empty()) return; - std::map refs; + std::vector refs; + std::vector refIDs; std::vector readers; for(auto it = mCells.intBegin(); it != mCells.intEnd(); it++) - readRefs(*it, refs, readers); + readRefs(*it, refs, refIDs, readers); for(auto it = mCells.extBegin(); it != mCells.extEnd(); it++) - readRefs(*it, refs, readers); - for(const auto& pair : refs) - mRefCount[pair.second]++; + readRefs(*it, refs, refIDs, readers); + const auto lessByRefNum = [] (const Ref& l, const Ref& r) { return l.mRefNum < r.mRefNum; }; + std::stable_sort(refs.begin(), refs.end(), lessByRefNum); + const auto equalByRefNum = [] (const Ref& l, const Ref& r) { return l.mRefNum == r.mRefNum; }; + const auto incrementRefCount = [&] (const Ref& value) + { + if (value.mRefID != deletedRefID) + { + std::string& refId = refIDs[value.mRefID]; + Misc::StringUtils::lowerCaseInPlace(refId); + ++mRefCount[std::move(refId)]; + } + }; + Misc::forEachUnique(refs.rbegin(), refs.rend(), equalByRefNum, incrementRefCount); } int ESMStore::getRefCount(const std::string& id) const diff --git a/apps/openmw/mwworld/esmstore.hpp b/apps/openmw/mwworld/esmstore.hpp index 26f497a52..608b5489e 100644 --- a/apps/openmw/mwworld/esmstore.hpp +++ b/apps/openmw/mwworld/esmstore.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include "store.hpp" @@ -76,7 +77,7 @@ namespace MWWorld std::map mIds; std::map mStaticIds; - std::map mRefCount; + std::unordered_map mRefCount; std::map mStores; diff --git a/apps/openmw/mwworld/failedaction.cpp b/apps/openmw/mwworld/failedaction.cpp index 45df75a32..ec8314712 100644 --- a/apps/openmw/mwworld/failedaction.cpp +++ b/apps/openmw/mwworld/failedaction.cpp @@ -1,5 +1,4 @@ #include "failedaction.hpp" -#include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" diff --git a/apps/openmw/mwworld/globals.hpp b/apps/openmw/mwworld/globals.hpp index 3468c2e71..ae5e412c7 100644 --- a/apps/openmw/mwworld/globals.hpp +++ b/apps/openmw/mwworld/globals.hpp @@ -7,7 +7,6 @@ #include -#include #include namespace ESM diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index 384378840..077a46220 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -44,7 +44,6 @@ #include "../mwsound/sound.hpp" -#include "../mwphysics/collisiontype.hpp" #include "../mwphysics/physicssystem.hpp" #include "../mwphysics/projectile.hpp" @@ -335,7 +334,7 @@ namespace MWWorld // in case there are multiple effects, the model is a dummy without geometry. Use the second effect for physics shape if (state.mIdMagic.size() > 1) model = "meshes\\" + MWBase::Environment::get().getWorld()->getStore().get().find(state.mIdMagic.at(1))->mModel; - state.mProjectileId = mPhysics->addProjectile(caster, pos, model, true, false); + state.mProjectileId = mPhysics->addProjectile(caster, pos, model, true); state.mToDelete = false; mMagicBolts.push_back(state); } @@ -360,7 +359,7 @@ namespace MWWorld if (!ptr.getClass().getEnchantment(ptr).empty()) SceneUtil::addEnchantedGlow(state.mNode, mResourceSystem, ptr.getClass().getEnchantmentColor(ptr)); - state.mProjectileId = mPhysics->addProjectile(actor, pos, model, false, true); + state.mProjectileId = mPhysics->addProjectile(actor, pos, model, false); state.mToDelete = false; mProjectiles.push_back(state); } @@ -511,9 +510,6 @@ namespace MWWorld auto* projectile = mPhysics->getProjectile(projectileState.mProjectileId); - if (const auto hitWaterPos = projectile->getWaterHitPosition()) - mRendering->emitWaterRipple(Misc::Convert::toOsg(*hitWaterPos)); - const auto pos = projectile->getPosition(); projectileState.mNode->setPosition(pos); @@ -537,6 +533,8 @@ namespace MWWorld if (invIt != inv.end() && Misc::StringUtils::ciEqual(invIt->getCellRef().getRefId(), projectileState.mBowId)) bow = *invIt; } + if (projectile->getHitWater()) + mRendering->emitWaterRipple(pos); MWMechanics::projectileHit(caster, target, bow, projectileRef.getPtr(), pos, projectileState.mAttackStrength); cleanupProjectile(projectileState); @@ -669,7 +667,7 @@ namespace MWWorld int weaponType = ptr.get()->mBase->mData.mType; state.mThrown = MWMechanics::getWeaponType(weaponType)->mWeaponClass == ESM::WeaponType::Thrown; - state.mProjectileId = mPhysics->addProjectile(state.getCaster(), osg::Vec3f(esm.mPosition), model, false, true); + state.mProjectileId = mPhysics->addProjectile(state.getCaster(), osg::Vec3f(esm.mPosition), model, false); } catch(...) { @@ -722,7 +720,7 @@ namespace MWWorld osg::Vec4 lightDiffuseColor = getMagicBoltLightDiffuseColor(state.mEffects); createModel(state, model, osg::Vec3f(esm.mPosition), osg::Quat(esm.mOrientation), true, true, lightDiffuseColor, texture); - state.mProjectileId = mPhysics->addProjectile(state.getCaster(), osg::Vec3f(esm.mPosition), model, true, false); + state.mProjectileId = mPhysics->addProjectile(state.getCaster(), osg::Vec3f(esm.mPosition), model, true); MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); for (const std::string &soundid : state.mSoundIds) diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index dae703c8c..a1e71f06b 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -26,8 +26,6 @@ #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/windowmanager.hpp" -#include "../mwmechanics/actorutil.hpp" - #include "../mwrender/renderingmanager.hpp" #include "../mwrender/landmanager.hpp" @@ -139,11 +137,9 @@ namespace { if (ptr.getClass().isDoor() && !ptr.getCellRef().getTeleport()) { - const auto shape = object->getShapeInstance()->getCollisionShape(); - btVector3 aabbMin; btVector3 aabbMax; - shape->getAabb(btTransform::getIdentity(), aabbMin, aabbMax); + object->getShapeInstance()->getCollisionShape()->getAabb(btTransform::getIdentity(), aabbMin, aabbMax); const auto center = (aabbMax + aabbMin) * 0.5f; @@ -170,12 +166,7 @@ namespace navigator.addObject( DetourNavigator::ObjectId(object), - DetourNavigator::DoorShapes( - *shape, - object->getShapeInstance()->getAvoidCollisionShape(), - connectionStart, - connectionEnd - ), + DetourNavigator::DoorShapes(object->getShapeInstance(), connectionStart, connectionEnd), transform ); } @@ -183,10 +174,7 @@ namespace { navigator.addObject( DetourNavigator::ObjectId(object), - DetourNavigator::ObjectShapes { - *object->getShapeInstance()->getCollisionShape(), - object->getShapeInstance()->getAvoidCollisionShape() - }, + DetourNavigator::ObjectShapes(object->getShapeInstance()), object->getTransform() ); } @@ -402,7 +390,7 @@ namespace MWWorld } if (const auto heightField = mPhysics->getHeightField(cellX, cellY)) - navigator->addObject(DetourNavigator::ObjectId(heightField), *heightField->getShape(), + navigator->addObject(DetourNavigator::ObjectId(heightField), heightField, *heightField->getShape(), heightField->getCollisionObject()->getWorldTransform()); } @@ -448,12 +436,6 @@ namespace MWWorld const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - // By default the player is grounded, with the scene fully loaded, we validate and correct this. - if (player.mCell == cell) // Only run once, during initial cell load. - { - mPhysics->traceDown(player, player.getRefData().getPosition().asVec3(), 10.f); - } - navigator->update(player.getRefData().getPosition().asVec3()); if (!cell->isExterior() && !(cell->getCell()->mData.mFlags & ESM::Cell::QuasiEx)) @@ -502,7 +484,7 @@ namespace MWWorld { const auto navigator = MWBase::Environment::get().getWorld()->getNavigator(); const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - navigator->update(player.getRefData().getPosition().asVec3()); + navigator->updatePlayerPosition(player.getRefData().getPosition().asVec3()); if (!mCurrentCell || !mCurrentCell->isExterior()) return; @@ -568,9 +550,8 @@ namespace MWWorld Loading::Listener* loadingListener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); Loading::ScopedLoad load(loadingListener); - int messagesCount = MWBase::Environment::get().getWindowManager()->getMessagesCount(); std::string loadingExteriorText = "#{sLoadingMessage3}"; - loadingListener->setLabel(loadingExteriorText, false, messagesCount > 0); + loadingListener->setLabel(loadingExteriorText); loadingListener->setProgressRange(refsToLoad); const auto getDistanceToPlayerCell = [&] (const std::pair& cellPosition) @@ -620,6 +601,8 @@ namespace MWWorld if (changeEvent) mCellChanged = true; + + mNavigator.wait(*loadingListener, DetourNavigator::WaitConditionType::requiredTilesPresent); } void Scene::testExteriorCells() @@ -798,9 +781,8 @@ namespace MWWorld MWBase::Environment::get().getWindowManager()->fadeScreenOut(0.5); Loading::Listener* loadingListener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); - int messagesCount = MWBase::Environment::get().getWindowManager()->getMessagesCount(); std::string loadingInteriorText = "#{sLoadingMessage2}"; - loadingListener->setLabel(loadingInteriorText, false, messagesCount > 0); + loadingListener->setLabel(loadingInteriorText); Loading::ScopedLoad load(loadingListener); if(mCurrentCell != nullptr && *mCurrentCell == *cell) @@ -847,6 +829,8 @@ namespace MWWorld MWBase::Environment::get().getWindowManager()->fadeScreenIn(0.5); MWBase::Environment::get().getWindowManager()->changeCell(mCurrentCell); + + mNavigator.wait(*loadingListener, DetourNavigator::WaitConditionType::requiredTilesPresent); } void Scene::changeToExteriorCell (const ESM::Position& position, bool adjustPlayerPos, bool changeEvent) diff --git a/apps/openmw/mwworld/store.cpp b/apps/openmw/mwworld/store.cpp index 9d6552106..8753a7173 100644 --- a/apps/openmw/mwworld/store.cpp +++ b/apps/openmw/mwworld/store.cpp @@ -8,28 +8,11 @@ #include #include +#include #include namespace { - template - class GetRecords - { - const std::string mFind; - std::vector *mRecords; - - public: - GetRecords(const std::string &str, std::vector *records) - : mFind(Misc::StringUtils::lowerCase(str)), mRecords(records) - { } - - void operator()(const T *item) - { - if(Misc::StringUtils::ciCompareLen(mFind, item->mId, mFind.size()) == 0) - mRecords->push_back(item); - } - }; - struct Compare { bool operator()(const ESM::Land *x, const ESM::Land *y) { @@ -75,10 +58,7 @@ namespace MWWorld record.load(esm, isDeleted); - // Try to overwrite existing record - std::pair ret = mStatic.insert(std::make_pair(record.mIndex, record)); - if (!ret.second) - ret.first->second = record; + mStatic.insert_or_assign(record.mIndex, record); } template int IndexedStore::getSize() const @@ -169,7 +149,11 @@ namespace MWWorld const T *Store::searchRandom(const std::string &id) const { std::vector results; - std::for_each(mShared.begin(), mShared.end(), GetRecords(id, &results)); + std::copy_if(mShared.begin(), mShared.end(), std::back_inserter(results), + [&id](const T* item) + { + return Misc::StringUtils::ciCompareLen(id, item->mId, id.size()) == 0; + }); if(!results.empty()) return results[Misc::Rng::rollDice(results.size())]; return nullptr; @@ -186,17 +170,6 @@ namespace MWWorld return ptr; } template - const T *Store::findRandom(const std::string &id) const - { - const T *ptr = searchRandom(id); - if(ptr == nullptr) - { - const std::string msg = T::getRecordType() + " starting with '" + id + "' not found"; - throw std::runtime_error(msg); - } - return ptr; - } - template RecordId Store::load(ESM::ESMReader &esm) { T record; @@ -205,11 +178,9 @@ namespace MWWorld record.load(esm, isDeleted); Misc::StringUtils::lowerCaseInPlace(record.mId); - std::pair inserted = mStatic.insert(std::make_pair(record.mId, record)); + std::pair inserted = mStatic.insert_or_assign(record.mId, record); if (inserted.second) mShared.push_back(&inserted.first->second); - else - inserted.first->second = record; return RecordId(record.mId, isDeleted); } @@ -259,28 +230,20 @@ namespace MWWorld if(it == mStatic.end()) return nullptr; } - std::pair result = - mDynamic.insert(std::pair(id, item)); + std::pair result = mDynamic.insert_or_assign(id, item); T *ptr = &result.first->second; - if (result.second) { + if (result.second) mShared.push_back(ptr); - } else { - *ptr = item; - } return ptr; } template T *Store::insertStatic(const T &item) { std::string id = Misc::StringUtils::lowerCase(item.mId); - std::pair result = - mStatic.insert(std::pair(id, item)); + std::pair result = mStatic.insert_or_assign(id, item); T *ptr = &result.first->second; - if (result.second) { + if (result.second) mShared.push_back(ptr); - } else { - *ptr = item; - } return ptr; } template @@ -555,9 +518,9 @@ namespace MWWorld // But there may be duplicates here! ESM::CellRefTracker::iterator iter = std::find_if(cellAlt->mLeasedRefs.begin(), cellAlt->mLeasedRefs.end(), ESM::CellRefTrackerPredicate(ref.mRefNum)); if (iter == cellAlt->mLeasedRefs.end()) - cellAlt->mLeasedRefs.push_back(std::make_pair(ref, deleted)); + cellAlt->mLeasedRefs.emplace_back(std::move(ref), deleted); else - *iter = std::make_pair(ref, deleted); + *iter = std::make_pair(std::move(ref), deleted); } } const ESM::Cell *Store::search(const std::string &id) const @@ -658,20 +621,15 @@ namespace MWWorld void Store::setUp() { - typedef DynamicExt::iterator ExtIterator; - typedef std::map::iterator IntIterator; - mSharedInt.clear(); mSharedInt.reserve(mInt.size()); - for (IntIterator it = mInt.begin(); it != mInt.end(); ++it) { - mSharedInt.push_back(&(it->second)); - } + for (auto & [_, cell] : mInt) + mSharedInt.push_back(&cell); mSharedExt.clear(); mSharedExt.reserve(mExt.size()); - for (ExtIterator it = mExt.begin(); it != mExt.end(); ++it) { - mSharedExt.push_back(&(it->second)); - } + for (auto & [_, cell] : mExt) + mSharedExt.push_back(&cell); } RecordId Store::load(ESM::ESMReader &esm) { @@ -1040,7 +998,7 @@ namespace MWWorld if (index >= mStatic.size()) { return nullptr; } - return &mStatic.at(index); + return &mStatic[index]; } const ESM::Attribute *Store::find(size_t index) const @@ -1087,18 +1045,13 @@ namespace MWWorld { // DialInfos marked as deleted are kept during the loading phase, so that the linked list // structure is kept intact for inserting further INFOs. Delete them now that loading is done. - for (Static::iterator it = mStatic.begin(); it != mStatic.end(); ++it) - { - ESM::Dialogue& dial = it->second; + for (auto & [_, dial] : mStatic) dial.clearDeletedInfos(); - } mShared.clear(); mShared.reserve(mStatic.size()); - std::map::iterator it = mStatic.begin(); - for (; it != mStatic.end(); ++it) { - mShared.push_back(&(it->second)); - } + for (auto & [_, dial] : mStatic) + mShared.push_back(&dial); } template <> diff --git a/apps/openmw/mwworld/store.hpp b/apps/openmw/mwworld/store.hpp index 9cb1c7473..e37152431 100644 --- a/apps/openmw/mwworld/store.hpp +++ b/apps/openmw/mwworld/store.hpp @@ -179,10 +179,6 @@ namespace MWWorld const T *find(const std::string &id) const; - /** Returns a random record that starts with the named ID. An exception is thrown if none - * are found. */ - const T *findRandom(const std::string &id) const; - iterator begin() const; iterator end() const; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 99e393e98..488d62a0c 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1261,7 +1261,7 @@ namespace MWWorld if (movePhysics) { if (const auto object = mPhysics->getObject(ptr)) - updateNavigatorObject(object); + updateNavigatorObject(*object); } } @@ -1291,12 +1291,12 @@ namespace MWWorld return moveObject(ptr, cell, x, y, z, movePhysics); } - MWWorld::Ptr World::moveObjectBy(const Ptr& ptr, osg::Vec3f vec, bool moveToActive) + MWWorld::Ptr World::moveObjectBy(const Ptr& ptr, osg::Vec3f vec, bool moveToActive, bool ignoreCollisions) { auto* actor = mPhysics->getActor(ptr); osg::Vec3f newpos = ptr.getRefData().getPosition().asVec3() + vec; if (actor) - actor->adjustPosition(vec); + actor->adjustPosition(vec, ignoreCollisions); if (ptr.getClass().isActor()) return moveObject(ptr, newpos.x(), newpos.y(), newpos.z(), false, moveToActive && ptr != getPlayerPtr()); return moveObject(ptr, newpos.x(), newpos.y(), newpos.z()); @@ -1320,7 +1320,7 @@ namespace MWWorld if (mPhysics->getActor(ptr)) mNavigator->addAgent(getPathfindingHalfExtents(ptr)); else if (const auto object = mPhysics->getObject(ptr)) - mShouldUpdateNavigator = updateNavigatorObject(object) || mShouldUpdateNavigator; + updateNavigatorObject(*object); } void World::rotateObjectImp(const Ptr& ptr, const osg::Vec3f& rot, MWBase::RotationFlags flags) @@ -1369,7 +1369,7 @@ namespace MWWorld mWorldScene->updateObjectRotation(ptr, order); if (const auto object = mPhysics->getObject(ptr)) - updateNavigatorObject(object); + updateNavigatorObject(*object); } } @@ -1461,7 +1461,7 @@ namespace MWWorld mPhysics->updateRotation(ptr); if (const auto object = mPhysics->getObject(ptr)) - updateNavigatorObject(object); + updateNavigatorObject(*object); } } @@ -1581,14 +1581,11 @@ namespace MWWorld void World::updateNavigator() { - mPhysics->forEachAnimatedObject([&] (const MWPhysics::Object* object) - { - mShouldUpdateNavigator = updateNavigatorObject(object) || mShouldUpdateNavigator; - }); + mPhysics->forEachAnimatedObject([&] (const MWPhysics::Object* object) { updateNavigatorObject(*object); }); for (const auto& door : mDoorStates) if (const auto object = mPhysics->getObject(door.first)) - mShouldUpdateNavigator = updateNavigatorObject(object) || mShouldUpdateNavigator; + updateNavigatorObject(*object); if (mShouldUpdateNavigator) { @@ -1597,13 +1594,11 @@ namespace MWWorld } } - bool World::updateNavigatorObject(const MWPhysics::Object* object) + void World::updateNavigatorObject(const MWPhysics::Object& object) { - const DetourNavigator::ObjectShapes shapes { - *object->getShapeInstance()->getCollisionShape(), - object->getShapeInstance()->getAvoidCollisionShape() - }; - return mNavigator->updateObject(DetourNavigator::ObjectId(object), shapes, object->getTransform()); + const DetourNavigator::ObjectShapes shapes(object.getShapeInstance()); + mShouldUpdateNavigator = mNavigator->updateObject(DetourNavigator::ObjectId(&object), shapes, object.getTransform()) + || mShouldUpdateNavigator; } const MWPhysics::RayCastingInterface* World::getRayCasting() const @@ -2361,7 +2356,7 @@ namespace MWWorld return false; const bool isPlayer = ptr == getPlayerConstPtr(); - if (!(isPlayer && mGodMode) && stats.isParalyzed()) + if (!(isPlayer && mGodMode) && stats.getMagicEffects().get(ESM::MagicEffect::Paralyze).getModifier() > 0) return false; if (ptr.getClass().canFly(ptr)) @@ -2589,7 +2584,14 @@ namespace MWWorld MWRender::Animation* World::getAnimation(const MWWorld::Ptr &ptr) { - return mRendering->getAnimation(ptr); + auto* animation = mRendering->getAnimation(ptr); + if(!animation) { + mWorldScene->removeFromPagedRefs(ptr); + animation = mRendering->getAnimation(ptr); + if(animation) + mRendering->pagingBlacklistObject(mStore.find(ptr.getCellRef().getRefId()), ptr); + } + return animation; } const MWRender::Animation* World::getAnimation(const MWWorld::ConstPtr &ptr) const @@ -3218,6 +3220,7 @@ namespace MWWorld bool underwater = MWBase::Environment::get().getWorld()->isUnderwater(MWMechanics::getPlayer().getCell(), worldPos); if (underwater) { + MWMechanics::projectileHit(actor, Ptr(), bow, projectile, worldPos, attackStrength); mRendering->emitWaterRipple(worldPos); return; } diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index be992731e..6ca31d717 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -4,7 +4,6 @@ #include #include -#include #include "../mwbase/world.hpp" @@ -159,7 +158,7 @@ namespace MWWorld void updateNavigator(); - bool updateNavigatorObject(const MWPhysics::Object* object); + void updateNavigatorObject(const MWPhysics::Object& object); void ensureNeededRecords(); @@ -382,7 +381,7 @@ namespace MWWorld MWWorld::Ptr moveObject (const Ptr& ptr, CellStore* newCell, float x, float y, float z, bool movePhysics=true) override; ///< @return an updated Ptr - MWWorld::Ptr moveObjectBy(const Ptr& ptr, osg::Vec3f vec, bool moveToActive) override; + MWWorld::Ptr moveObjectBy(const Ptr& ptr, osg::Vec3f vec, bool moveToActive, bool ignoreCollisions) override; ///< @return an updated Ptr void scaleObject (const Ptr& ptr, float scale) override; diff --git a/apps/openmw_test_suite/detournavigator/navigator.cpp b/apps/openmw_test_suite/detournavigator/navigator.cpp index 3b7dc4915..38f69e948 100644 --- a/apps/openmw_test_suite/detournavigator/navigator.cpp +++ b/apps/openmw_test_suite/detournavigator/navigator.cpp @@ -3,6 +3,10 @@ #include #include #include +#include +#include + +#include #include #include @@ -11,7 +15,9 @@ #include #include +#include #include +#include MATCHER_P3(Vec3fEq, x, y, z, "") { @@ -35,12 +41,13 @@ namespace std::back_insert_iterator> mOut; float mStepSize; AreaCosts mAreaCosts; + Loading::Listener mListener; DetourNavigatorNavigatorTest() : mPlayerPosition(0, 0, 0) , mAgentHalfExtents(29, 29, 66) - , mStart(-215, 215, 1) - , mEnd(215, -215, 1) + , mStart(-204, 204, 1) + , mEnd(204, -204, 1) , mOut(mPath) , mStepSize(28.333332061767578125f) { @@ -64,11 +71,11 @@ namespace mSettings.mRegionMergeSize = 20; mSettings.mRegionMinSize = 8; mSettings.mTileSize = 64; + mSettings.mWaitUntilMinDistanceToPlayer = std::numeric_limits::max(); mSettings.mAsyncNavMeshUpdaterThreads = 1; mSettings.mMaxNavMeshTilesCacheSize = 1024 * 1024; mSettings.mMaxPolygonPathSize = 1024; mSettings.mMaxSmoothPathSize = 1024; - mSettings.mTrianglesPerChunk = 256; mSettings.mMaxPolys = 4096; mSettings.mMaxTilesNumber = 512; mSettings.mMinUpdateInterval = std::chrono::milliseconds(50); @@ -77,16 +84,38 @@ namespace }; template - btHeightfieldTerrainShape makeSquareHeightfieldTerrainShape(const std::array& values, + std::unique_ptr makeSquareHeightfieldTerrainShape(const std::array& values, btScalar heightScale = 1, int upAxis = 2, PHY_ScalarType heightDataType = PHY_FLOAT, bool flipQuadEdges = false) { const int width = static_cast(std::sqrt(size)); const btScalar min = *std::min_element(values.begin(), values.end()); const btScalar max = *std::max_element(values.begin(), values.end()); const btScalar greater = std::max(std::abs(min), std::abs(max)); - return btHeightfieldTerrainShape(width, width, values.data(), heightScale, -greater, greater, upAxis, heightDataType, flipQuadEdges); + return std::make_unique(width, width, values.data(), heightScale, -greater, greater, + upAxis, heightDataType, flipQuadEdges); + } + + template + osg::ref_ptr makeBulletShapeInstance(std::unique_ptr&& shape) + { + osg::ref_ptr bulletShape(new Resource::BulletShape); + bulletShape->mCollisionShape = std::move(shape).release(); + return new Resource::BulletShapeInstance(bulletShape); } + template + class CollisionShapeInstance + { + public: + CollisionShapeInstance(std::unique_ptr&& shape) : mInstance(makeBulletShapeInstance(std::move(shape))) {} + + T& shape() { return static_cast(*mInstance->mCollisionShape); } + const osg::ref_ptr& instance() const { return mInstance; } + + private: + osg::ref_ptr mInstance; + }; + TEST_F(DetourNavigatorNavigatorTest, find_path_for_empty_should_return_empty) { EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mOut), @@ -119,41 +148,41 @@ namespace 0, -25, -100, -100, -100, 0, -25, -100, -100, -100, }}; - btHeightfieldTerrainShape shape = makeSquareHeightfieldTerrainShape(heightfieldData); + const auto shapePtr = makeSquareHeightfieldTerrainShape(heightfieldData); + btHeightfieldTerrainShape& shape = *shapePtr; shape.setLocalScaling(btVector3(128, 128, 1)); mNavigator->addAgent(mAgentHalfExtents); - mNavigator->addObject(ObjectId(&shape), shape, btTransform::getIdentity()); + mNavigator->addObject(ObjectId(&shape), nullptr, shape, btTransform::getIdentity()); mNavigator->update(mPlayerPosition); - mNavigator->wait(); + mNavigator->wait(mListener, WaitConditionType::requiredTilesPresent); EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mOut), Status::Success); EXPECT_THAT(mPath, ElementsAre( - Vec3fEq(-215, 215, 1.85963428020477294921875), - Vec3fEq(-194.9653167724609375, 194.9653167724609375, -6.57602214813232421875), - Vec3fEq(-174.930633544921875, 174.930633544921875, -15.01167774200439453125), - Vec3fEq(-154.8959503173828125, 154.8959503173828125, -23.4473361968994140625), - Vec3fEq(-134.86126708984375, 134.86126708984375, -31.8829936981201171875), - Vec3fEq(-114.82657623291015625, 114.82657623291015625, -40.3186492919921875), - Vec3fEq(-94.7918853759765625, 94.7918853759765625, -47.3990631103515625), - Vec3fEq(-74.75719451904296875, 74.75719451904296875, -53.7258148193359375), - Vec3fEq(-54.722499847412109375, 54.722499847412109375, -60.052555084228515625), - Vec3fEq(-34.68780517578125, 34.68780517578125, -66.37931060791015625), - Vec3fEq(-14.6531162261962890625, 14.6531162261962890625, -72.70604705810546875), - Vec3fEq(5.3815765380859375, -5.3815765380859375, -75.35065460205078125), - Vec3fEq(25.41626739501953125, -25.41626739501953125, -67.9694671630859375), - Vec3fEq(45.450958251953125, -45.450958251953125, -60.5882568359375), - Vec3fEq(65.48564910888671875, -65.48564910888671875, -53.20705413818359375), - Vec3fEq(85.5203399658203125, -85.5203399658203125, -45.8258514404296875), - Vec3fEq(105.55503082275390625, -105.55503082275390625, -38.44464874267578125), - Vec3fEq(125.5897216796875, -125.5897216796875, -31.063449859619140625), - Vec3fEq(145.6244049072265625, -145.6244049072265625, -23.6822509765625), - Vec3fEq(165.659088134765625, -165.659088134765625, -16.3010501861572265625), - Vec3fEq(185.6937713623046875, -185.6937713623046875, -8.91985416412353515625), - Vec3fEq(205.7284698486328125, -205.7284698486328125, -1.5386505126953125), - Vec3fEq(215, -215, 1.87718021869659423828125) - )); + Vec3fEq(-204.0000152587890625, 204, 1.99998295307159423828125), + Vec3fEq(-183.96533203125, 183.9653167724609375, 1.99998819828033447265625), + Vec3fEq(-163.930633544921875, 163.9306182861328125, 1.99999344348907470703125), + Vec3fEq(-143.8959503173828125, 143.89593505859375, -2.720611572265625), + Vec3fEq(-123.86126708984375, 123.86124420166015625, -13.1089687347412109375), + Vec3fEq(-103.82657623291015625, 103.8265533447265625, -23.497333526611328125), + Vec3fEq(-83.7918853759765625, 83.7918548583984375, -33.885692596435546875), + Vec3fEq(-63.757190704345703125, 63.757171630859375, -44.274051666259765625), + Vec3fEq(-43.722503662109375, 43.72248077392578125, -54.66241455078125), + Vec3fEq(-23.687808990478515625, 23.6877918243408203125, -65.05077362060546875), + Vec3fEq(-3.6531188488006591796875, 3.6531002521514892578125, -75.43914031982421875), + Vec3fEq(16.3815746307373046875, -16.381591796875, -69.74927520751953125), + Vec3fEq(36.416263580322265625, -36.416286468505859375, -60.4739532470703125), + Vec3fEq(56.450958251953125, -56.450977325439453125, -51.1986236572265625), + Vec3fEq(76.48564910888671875, -76.4856719970703125, -41.92330169677734375), + Vec3fEq(96.5203399658203125, -96.52036285400390625, -31.46941375732421875), + Vec3fEq(116.55503082275390625, -116.5550537109375, -19.597003936767578125), + Vec3fEq(136.5897216796875, -136.5897369384765625, -7.724592685699462890625), + Vec3fEq(156.624420166015625, -156.624420166015625, 1.99999535083770751953125), + Vec3fEq(176.6591033935546875, -176.65911865234375, 1.99999010562896728515625), + Vec3fEq(196.69378662109375, -196.6938018798828125, 1.99998486042022705078125), + Vec3fEq(204, -204.0000152587890625, 1.99998295307159423828125) + )) << mPath; } TEST_F(DetourNavigatorNavigatorTest, add_object_should_change_navmesh) @@ -165,79 +194,78 @@ namespace 0, -25, -100, -100, -100, 0, -25, -100, -100, -100, }}; - btHeightfieldTerrainShape heightfieldShape = makeSquareHeightfieldTerrainShape(heightfieldData); + const auto heightfieldShapePtr = makeSquareHeightfieldTerrainShape(heightfieldData); + btHeightfieldTerrainShape& heightfieldShape = *heightfieldShapePtr; heightfieldShape.setLocalScaling(btVector3(128, 128, 1)); - btBoxShape boxShape(btVector3(20, 20, 100)); - btCompoundShape compoundShape; - compoundShape.addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(0, 0, 0)), &boxShape); + CollisionShapeInstance compound(std::make_unique()); + compound.shape().addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(0, 0, 0)), new btBoxShape(btVector3(20, 20, 100))); mNavigator->addAgent(mAgentHalfExtents); - mNavigator->addObject(ObjectId(&heightfieldShape), heightfieldShape, btTransform::getIdentity()); + mNavigator->addObject(ObjectId(&heightfieldShape), nullptr, heightfieldShape, btTransform::getIdentity()); mNavigator->update(mPlayerPosition); - mNavigator->wait(); + mNavigator->wait(mListener, WaitConditionType::allJobsDone); EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mOut), Status::Success); EXPECT_THAT(mPath, ElementsAre( - Vec3fEq(-215, 215, 1.85963428020477294921875), - Vec3fEq(-194.9653167724609375, 194.9653167724609375, -6.57602214813232421875), - Vec3fEq(-174.930633544921875, 174.930633544921875, -15.01167774200439453125), - Vec3fEq(-154.8959503173828125, 154.8959503173828125, -23.4473361968994140625), - Vec3fEq(-134.86126708984375, 134.86126708984375, -31.8829936981201171875), - Vec3fEq(-114.82657623291015625, 114.82657623291015625, -40.3186492919921875), - Vec3fEq(-94.7918853759765625, 94.7918853759765625, -47.3990631103515625), - Vec3fEq(-74.75719451904296875, 74.75719451904296875, -53.7258148193359375), - Vec3fEq(-54.722499847412109375, 54.722499847412109375, -60.052555084228515625), - Vec3fEq(-34.68780517578125, 34.68780517578125, -66.37931060791015625), - Vec3fEq(-14.6531162261962890625, 14.6531162261962890625, -72.70604705810546875), - Vec3fEq(5.3815765380859375, -5.3815765380859375, -75.35065460205078125), - Vec3fEq(25.41626739501953125, -25.41626739501953125, -67.9694671630859375), - Vec3fEq(45.450958251953125, -45.450958251953125, -60.5882568359375), - Vec3fEq(65.48564910888671875, -65.48564910888671875, -53.20705413818359375), - Vec3fEq(85.5203399658203125, -85.5203399658203125, -45.8258514404296875), - Vec3fEq(105.55503082275390625, -105.55503082275390625, -38.44464874267578125), - Vec3fEq(125.5897216796875, -125.5897216796875, -31.063449859619140625), - Vec3fEq(145.6244049072265625, -145.6244049072265625, -23.6822509765625), - Vec3fEq(165.659088134765625, -165.659088134765625, -16.3010501861572265625), - Vec3fEq(185.6937713623046875, -185.6937713623046875, -8.91985416412353515625), - Vec3fEq(205.7284698486328125, -205.7284698486328125, -1.5386505126953125), - Vec3fEq(215, -215, 1.87718021869659423828125) - )); - - mNavigator->addObject(ObjectId(&compoundShape), compoundShape, btTransform::getIdentity()); + Vec3fEq(-204, 204, 1.99998295307159423828125), + Vec3fEq(-183.965301513671875, 183.965301513671875, 1.99998819828033447265625), + Vec3fEq(-163.9306182861328125, 163.9306182861328125, 1.99999344348907470703125), + Vec3fEq(-143.89593505859375, 143.89593505859375, -2.7206256389617919921875), + Vec3fEq(-123.86124420166015625, 123.86124420166015625, -13.1089839935302734375), + Vec3fEq(-103.8265533447265625, 103.8265533447265625, -23.4973468780517578125), + Vec3fEq(-83.7918548583984375, 83.7918548583984375, -33.885707855224609375), + Vec3fEq(-63.75716400146484375, 63.75716400146484375, -44.27407073974609375), + Vec3fEq(-43.72247314453125, 43.72247314453125, -54.662433624267578125), + Vec3fEq(-23.6877803802490234375, 23.6877803802490234375, -65.0507965087890625), + Vec3fEq(-3.653090000152587890625, 3.653090000152587890625, -75.43915557861328125), + Vec3fEq(16.3816013336181640625, -16.3816013336181640625, -69.749267578125), + Vec3fEq(36.416290283203125, -36.416290283203125, -60.4739532470703125), + Vec3fEq(56.450984954833984375, -56.450984954833984375, -51.1986236572265625), + Vec3fEq(76.4856719970703125, -76.4856719970703125, -41.92330169677734375), + Vec3fEq(96.52036285400390625, -96.52036285400390625, -31.46941375732421875), + Vec3fEq(116.5550537109375, -116.5550537109375, -19.597003936767578125), + Vec3fEq(136.5897369384765625, -136.5897369384765625, -7.724592685699462890625), + Vec3fEq(156.6244354248046875, -156.6244354248046875, 1.99999535083770751953125), + Vec3fEq(176.6591339111328125, -176.6591339111328125, 1.99999010562896728515625), + Vec3fEq(196.693817138671875, -196.693817138671875, 1.99998486042022705078125), + Vec3fEq(204, -204, 1.99998295307159423828125) + )) << mPath; + + mNavigator->addObject(ObjectId(&compound.shape()), ObjectShapes(compound.instance()), btTransform::getIdentity()); mNavigator->update(mPlayerPosition); - mNavigator->wait(); + mNavigator->wait(mListener, WaitConditionType::allJobsDone); mPath.clear(); mOut = std::back_inserter(mPath); EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mOut), Status::Success); EXPECT_THAT(mPath, ElementsAre( - Vec3fEq(-215, 215, 1.87826788425445556640625), - Vec3fEq(-199.7968292236328125, 191.09100341796875, -3.54876613616943359375), - Vec3fEq(-184.5936431884765625, 167.1819915771484375, -8.97847843170166015625), - Vec3fEq(-169.3904571533203125, 143.2729949951171875, -14.408184051513671875), - Vec3fEq(-154.1872711181640625, 119.36397552490234375, -19.837890625), - Vec3fEq(-138.9840850830078125, 95.45496368408203125, -25.2675991058349609375), - Vec3fEq(-123.78090667724609375, 71.54595184326171875, -30.6973056793212890625), - Vec3fEq(-108.57772064208984375, 47.636936187744140625, -36.12701416015625), - Vec3fEq(-93.3745269775390625, 23.7279262542724609375, -40.754688262939453125), - Vec3fEq(-78.17134857177734375, -0.18108306825160980224609375, -37.128787994384765625), - Vec3fEq(-62.968158721923828125, -24.0900936126708984375, -33.50289154052734375), - Vec3fEq(-47.764972686767578125, -47.999103546142578125, -30.797946929931640625), - Vec3fEq(-23.852447509765625, -63.196765899658203125, -33.97112274169921875), - Vec3fEq(0.0600789971649646759033203125, -78.39443206787109375, -37.14543914794921875), - Vec3fEq(23.97260284423828125, -93.5920867919921875, -40.774089813232421875), - Vec3fEq(47.885128021240234375, -108.78974151611328125, -36.05129241943359375), - Vec3fEq(71.7976531982421875, -123.98740386962890625, -30.6235561370849609375), - Vec3fEq(95.71018218994140625, -139.18505859375, -25.1958255767822265625), - Vec3fEq(119.6226959228515625, -154.382720947265625, -19.7680912017822265625), - Vec3fEq(143.53521728515625, -169.58038330078125, -14.34035205841064453125), - Vec3fEq(167.4477386474609375, -184.778045654296875, -8.9126186370849609375), - Vec3fEq(191.360260009765625, -199.9757080078125, -3.4848802089691162109375), - Vec3fEq(215, -215, 1.87826788425445556640625) - )); + Vec3fEq(-204, 204, 1.99998295307159423828125), + Vec3fEq(-189.9427337646484375, 179.3997802734375, -3.622931003570556640625), + Vec3fEq(-175.8854522705078125, 154.7995452880859375, -9.24583911895751953125), + Vec3fEq(-161.82818603515625, 130.1993255615234375, -14.86874866485595703125), + Vec3fEq(-147.770904541015625, 105.5991058349609375, -20.4916591644287109375), + Vec3fEq(-133.7136383056640625, 80.99887847900390625, -26.1145648956298828125), + Vec3fEq(-119.65636444091796875, 56.39865875244140625, -31.7374725341796875), + Vec3fEq(-105.59909820556640625, 31.798435211181640625, -26.133396148681640625), + Vec3fEq(-91.54183197021484375, 7.1982135772705078125, -31.5624217987060546875), + Vec3fEq(-77.48455810546875, -17.402008056640625, -26.98972320556640625), + Vec3fEq(-63.427295684814453125, -42.00223541259765625, -19.9045581817626953125), + Vec3fEq(-42.193531036376953125, -60.761363983154296875, -20.4544773101806640625), + Vec3fEq(-20.9597682952880859375, -79.5204925537109375, -23.599918365478515625), + Vec3fEq(3.8312885761260986328125, -93.2384033203125, -30.7141361236572265625), + Vec3fEq(28.6223468780517578125, -106.95632171630859375, -24.8243885040283203125), + Vec3fEq(53.413402557373046875, -120.6742401123046875, -31.3303241729736328125), + Vec3fEq(78.20446014404296875, -134.39215087890625, -25.8431549072265625), + Vec3fEq(102.99552154541015625, -148.110076904296875, -20.3559894561767578125), + Vec3fEq(127.7865753173828125, -161.827972412109375, -14.868824005126953125), + Vec3fEq(152.57763671875, -175.5458984375, -9.3816623687744140625), + Vec3fEq(177.3686981201171875, -189.2638092041015625, -3.894496917724609375), + Vec3fEq(202.1597442626953125, -202.9817047119140625, 1.59266507625579833984375), + Vec3fEq(204, -204, 1.99998295307159423828125) + )) << mPath; } TEST_F(DetourNavigatorNavigatorTest, update_changed_object_should_change_navmesh) @@ -249,82 +277,81 @@ namespace 0, -25, -100, -100, -100, 0, -25, -100, -100, -100, }}; - btHeightfieldTerrainShape heightfieldShape = makeSquareHeightfieldTerrainShape(heightfieldData); + const auto heightfieldShapePtr = makeSquareHeightfieldTerrainShape(heightfieldData); + btHeightfieldTerrainShape& heightfieldShape = *heightfieldShapePtr; heightfieldShape.setLocalScaling(btVector3(128, 128, 1)); - btBoxShape boxShape(btVector3(20, 20, 100)); - btCompoundShape compoundShape; - compoundShape.addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(0, 0, 0)), &boxShape); + CollisionShapeInstance compound(std::make_unique()); + compound.shape().addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(0, 0, 0)), new btBoxShape(btVector3(20, 20, 100))); mNavigator->addAgent(mAgentHalfExtents); - mNavigator->addObject(ObjectId(&heightfieldShape), heightfieldShape, btTransform::getIdentity()); - mNavigator->addObject(ObjectId(&compoundShape), compoundShape, btTransform::getIdentity()); + mNavigator->addObject(ObjectId(&heightfieldShape), nullptr, heightfieldShape, btTransform::getIdentity()); + mNavigator->addObject(ObjectId(&compound.shape()), ObjectShapes(compound.instance()), btTransform::getIdentity()); mNavigator->update(mPlayerPosition); - mNavigator->wait(); + mNavigator->wait(mListener, WaitConditionType::allJobsDone); EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mOut), Status::Success); EXPECT_THAT(mPath, ElementsAre( - Vec3fEq(-215, 215, 1.87826788425445556640625), - Vec3fEq(-199.7968292236328125, 191.09100341796875, -3.54876613616943359375), - Vec3fEq(-184.5936431884765625, 167.1819915771484375, -8.97847843170166015625), - Vec3fEq(-169.3904571533203125, 143.2729949951171875, -14.408184051513671875), - Vec3fEq(-154.1872711181640625, 119.36397552490234375, -19.837890625), - Vec3fEq(-138.9840850830078125, 95.45496368408203125, -25.2675991058349609375), - Vec3fEq(-123.78090667724609375, 71.54595184326171875, -30.6973056793212890625), - Vec3fEq(-108.57772064208984375, 47.636936187744140625, -36.12701416015625), - Vec3fEq(-93.3745269775390625, 23.7279262542724609375, -40.754688262939453125), - Vec3fEq(-78.17134857177734375, -0.18108306825160980224609375, -37.128787994384765625), - Vec3fEq(-62.968158721923828125, -24.0900936126708984375, -33.50289154052734375), - Vec3fEq(-47.764972686767578125, -47.999103546142578125, -30.797946929931640625), - Vec3fEq(-23.852447509765625, -63.196765899658203125, -33.97112274169921875), - Vec3fEq(0.0600789971649646759033203125, -78.39443206787109375, -37.14543914794921875), - Vec3fEq(23.97260284423828125, -93.5920867919921875, -40.774089813232421875), - Vec3fEq(47.885128021240234375, -108.78974151611328125, -36.05129241943359375), - Vec3fEq(71.7976531982421875, -123.98740386962890625, -30.6235561370849609375), - Vec3fEq(95.71018218994140625, -139.18505859375, -25.1958255767822265625), - Vec3fEq(119.6226959228515625, -154.382720947265625, -19.7680912017822265625), - Vec3fEq(143.53521728515625, -169.58038330078125, -14.34035205841064453125), - Vec3fEq(167.4477386474609375, -184.778045654296875, -8.9126186370849609375), - Vec3fEq(191.360260009765625, -199.9757080078125, -3.4848802089691162109375), - Vec3fEq(215, -215, 1.87826788425445556640625) - )); - - compoundShape.updateChildTransform(0, btTransform(btMatrix3x3::getIdentity(), btVector3(1000, 0, 0))); - - mNavigator->updateObject(ObjectId(&compoundShape), compoundShape, btTransform::getIdentity()); + Vec3fEq(-204, 204, 1.99998295307159423828125), + Vec3fEq(-189.9427337646484375, 179.3997802734375, -3.622931003570556640625), + Vec3fEq(-175.8854522705078125, 154.7995452880859375, -9.24583911895751953125), + Vec3fEq(-161.82818603515625, 130.1993255615234375, -14.86874866485595703125), + Vec3fEq(-147.770904541015625, 105.5991058349609375, -20.4916591644287109375), + Vec3fEq(-133.7136383056640625, 80.99887847900390625, -26.1145648956298828125), + Vec3fEq(-119.65636444091796875, 56.39865875244140625, -31.7374725341796875), + Vec3fEq(-105.59909820556640625, 31.798435211181640625, -26.133396148681640625), + Vec3fEq(-91.54183197021484375, 7.1982135772705078125, -31.5624217987060546875), + Vec3fEq(-77.48455810546875, -17.402008056640625, -26.98972320556640625), + Vec3fEq(-63.427295684814453125, -42.00223541259765625, -19.9045581817626953125), + Vec3fEq(-42.193531036376953125, -60.761363983154296875, -20.4544773101806640625), + Vec3fEq(-20.9597682952880859375, -79.5204925537109375, -23.599918365478515625), + Vec3fEq(3.8312885761260986328125, -93.2384033203125, -30.7141361236572265625), + Vec3fEq(28.6223468780517578125, -106.95632171630859375, -24.8243885040283203125), + Vec3fEq(53.413402557373046875, -120.6742401123046875, -31.3303241729736328125), + Vec3fEq(78.20446014404296875, -134.39215087890625, -25.8431549072265625), + Vec3fEq(102.99552154541015625, -148.110076904296875, -20.3559894561767578125), + Vec3fEq(127.7865753173828125, -161.827972412109375, -14.868824005126953125), + Vec3fEq(152.57763671875, -175.5458984375, -9.3816623687744140625), + Vec3fEq(177.3686981201171875, -189.2638092041015625, -3.894496917724609375), + Vec3fEq(202.1597442626953125, -202.9817047119140625, 1.59266507625579833984375), + Vec3fEq(204, -204, 1.99998295307159423828125) + )) << mPath; + + compound.shape().updateChildTransform(0, btTransform(btMatrix3x3::getIdentity(), btVector3(1000, 0, 0))); + + mNavigator->updateObject(ObjectId(&compound.shape()), ObjectShapes(compound.instance()), btTransform::getIdentity()); mNavigator->update(mPlayerPosition); - mNavigator->wait(); + mNavigator->wait(mListener, WaitConditionType::allJobsDone); mPath.clear(); mOut = std::back_inserter(mPath); EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mOut), Status::Success); EXPECT_THAT(mPath, ElementsAre( - Vec3fEq(-215, 215, 1.85963428020477294921875), - Vec3fEq(-194.9653167724609375, 194.9653167724609375, -6.57602214813232421875), - Vec3fEq(-174.930633544921875, 174.930633544921875, -15.01167774200439453125), - Vec3fEq(-154.8959503173828125, 154.8959503173828125, -23.4473361968994140625), - Vec3fEq(-134.86126708984375, 134.86126708984375, -31.8829936981201171875), - Vec3fEq(-114.82657623291015625, 114.82657623291015625, -40.3186492919921875), - Vec3fEq(-94.7918853759765625, 94.7918853759765625, -47.3990631103515625), - Vec3fEq(-74.75719451904296875, 74.75719451904296875, -53.7258148193359375), - Vec3fEq(-54.722499847412109375, 54.722499847412109375, -60.052555084228515625), - Vec3fEq(-34.68780517578125, 34.68780517578125, -66.37931060791015625), - Vec3fEq(-14.6531162261962890625, 14.6531162261962890625, -72.70604705810546875), - Vec3fEq(5.3815765380859375, -5.3815765380859375, -75.35065460205078125), - Vec3fEq(25.41626739501953125, -25.41626739501953125, -67.9694671630859375), - Vec3fEq(45.450958251953125, -45.450958251953125, -60.5882568359375), - Vec3fEq(65.48564910888671875, -65.48564910888671875, -53.20705413818359375), - Vec3fEq(85.5203399658203125, -85.5203399658203125, -45.8258514404296875), - Vec3fEq(105.55503082275390625, -105.55503082275390625, -38.44464874267578125), - Vec3fEq(125.5897216796875, -125.5897216796875, -31.063449859619140625), - Vec3fEq(145.6244049072265625, -145.6244049072265625, -23.6822509765625), - Vec3fEq(165.659088134765625, -165.659088134765625, -16.3010501861572265625), - Vec3fEq(185.6937713623046875, -185.6937713623046875, -8.91985416412353515625), - Vec3fEq(205.7284698486328125, -205.7284698486328125, -1.5386505126953125), - Vec3fEq(215, -215, 1.87718021869659423828125) - )); + Vec3fEq(-204, 204, 1.99998295307159423828125), + Vec3fEq(-183.965301513671875, 183.965301513671875, 1.99998819828033447265625), + Vec3fEq(-163.9306182861328125, 163.9306182861328125, 1.99999344348907470703125), + Vec3fEq(-143.89593505859375, 143.89593505859375, -2.7206256389617919921875), + Vec3fEq(-123.86124420166015625, 123.86124420166015625, -13.1089839935302734375), + Vec3fEq(-103.8265533447265625, 103.8265533447265625, -23.4973468780517578125), + Vec3fEq(-83.7918548583984375, 83.7918548583984375, -33.885707855224609375), + Vec3fEq(-63.75716400146484375, 63.75716400146484375, -44.27407073974609375), + Vec3fEq(-43.72247314453125, 43.72247314453125, -54.662433624267578125), + Vec3fEq(-23.6877803802490234375, 23.6877803802490234375, -65.0507965087890625), + Vec3fEq(-3.653090000152587890625, 3.653090000152587890625, -75.43915557861328125), + Vec3fEq(16.3816013336181640625, -16.3816013336181640625, -69.749267578125), + Vec3fEq(36.416290283203125, -36.416290283203125, -60.4739532470703125), + Vec3fEq(56.450984954833984375, -56.450984954833984375, -51.1986236572265625), + Vec3fEq(76.4856719970703125, -76.4856719970703125, -41.92330169677734375), + Vec3fEq(96.52036285400390625, -96.52036285400390625, -31.46941375732421875), + Vec3fEq(116.5550537109375, -116.5550537109375, -19.597003936767578125), + Vec3fEq(136.5897369384765625, -136.5897369384765625, -7.724592685699462890625), + Vec3fEq(156.6244354248046875, -156.6244354248046875, 1.99999535083770751953125), + Vec3fEq(176.6591339111328125, -176.6591339111328125, 1.99999010562896728515625), + Vec3fEq(196.693817138671875, -196.693817138671875, 1.99998486042022705078125), + Vec3fEq(204, -204, 1.99998295307159423828125) + )) << mPath; } TEST_F(DetourNavigatorNavigatorTest, for_overlapping_heightfields_should_use_higher) @@ -336,7 +363,8 @@ namespace 0, -25, -100, -100, -100, 0, -25, -100, -100, -100, }}; - btHeightfieldTerrainShape shape = makeSquareHeightfieldTerrainShape(heightfieldData); + const auto shapePtr = makeSquareHeightfieldTerrainShape(heightfieldData); + btHeightfieldTerrainShape& shape = *shapePtr; shape.setLocalScaling(btVector3(128, 128, 1)); const std::array heightfieldData2 {{ @@ -346,46 +374,48 @@ namespace -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, }}; - btHeightfieldTerrainShape shape2 = makeSquareHeightfieldTerrainShape(heightfieldData2); + const auto shapePtr2 = makeSquareHeightfieldTerrainShape(heightfieldData2); + btHeightfieldTerrainShape& shape2 = *shapePtr2; shape2.setLocalScaling(btVector3(128, 128, 1)); mNavigator->addAgent(mAgentHalfExtents); - mNavigator->addObject(ObjectId(&shape), shape, btTransform::getIdentity()); - mNavigator->addObject(ObjectId(&shape2), shape2, btTransform::getIdentity()); + mNavigator->addObject(ObjectId(&shape), nullptr, shape, btTransform::getIdentity()); + mNavigator->addObject(ObjectId(&shape2), nullptr, shape2, btTransform::getIdentity()); mNavigator->update(mPlayerPosition); - mNavigator->wait(); + mNavigator->wait(mListener, WaitConditionType::allJobsDone); EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mOut), Status::Success); EXPECT_THAT(mPath, ElementsAre( - Vec3fEq(-215, 215, 1.96328866481781005859375), - Vec3fEq(-194.9653167724609375, 194.9653167724609375, -0.242215454578399658203125), - Vec3fEq(-174.930633544921875, 174.930633544921875, -2.447719097137451171875), - Vec3fEq(-154.8959503173828125, 154.8959503173828125, -4.65322399139404296875), - Vec3fEq(-134.86126708984375, 134.86126708984375, -6.858726978302001953125), - Vec3fEq(-114.82657623291015625, 114.82657623291015625, -9.06423282623291015625), - Vec3fEq(-94.7918853759765625, 94.7918853759765625, -11.26973628997802734375), - Vec3fEq(-74.75719451904296875, 74.75719451904296875, -13.26497173309326171875), - Vec3fEq(-54.722499847412109375, 54.722499847412109375, -15.24860477447509765625), - Vec3fEq(-34.68780517578125, 34.68780517578125, -17.23223876953125), - Vec3fEq(-14.6531162261962890625, 14.6531162261962890625, -19.215869903564453125), - Vec3fEq(5.3815765380859375, -5.3815765380859375, -20.1338443756103515625), - Vec3fEq(25.41626739501953125, -25.41626739501953125, -18.1502132415771484375), - Vec3fEq(45.450958251953125, -45.450958251953125, -16.1665802001953125), - Vec3fEq(65.48564910888671875, -65.48564910888671875, -14.18294620513916015625), - Vec3fEq(85.5203399658203125, -85.5203399658203125, -12.199314117431640625), - Vec3fEq(105.55503082275390625, -105.55503082275390625, -10.08488368988037109375), - Vec3fEq(125.5897216796875, -125.5897216796875, -7.87938022613525390625), - Vec3fEq(145.6244049072265625, -145.6244049072265625, -5.673875331878662109375), - Vec3fEq(165.659088134765625, -165.659088134765625, -3.468370914459228515625), - Vec3fEq(185.6937713623046875, -185.6937713623046875, -1.26286637783050537109375), - Vec3fEq(205.7284698486328125, -205.7284698486328125, 0.942641556262969970703125), - Vec3fEq(215, -215, 1.96328866481781005859375) - )); + Vec3fEq(-204, 204, 1.999981403350830078125), + Vec3fEq(-183.965301513671875, 183.965301513671875, -0.428465187549591064453125), + Vec3fEq(-163.9306182861328125, 163.9306182861328125, -2.8569104671478271484375), + Vec3fEq(-143.89593505859375, 143.89593505859375, -5.28535556793212890625), + Vec3fEq(-123.86124420166015625, 123.86124420166015625, -7.7138004302978515625), + Vec3fEq(-103.8265533447265625, 103.8265533447265625, -10.142246246337890625), + Vec3fEq(-83.7918548583984375, 83.7918548583984375, -12.3704509735107421875), + Vec3fEq(-63.75716400146484375, 63.75716400146484375, -14.354084014892578125), + Vec3fEq(-43.72247314453125, 43.72247314453125, -16.3377170562744140625), + Vec3fEq(-23.6877803802490234375, 23.6877803802490234375, -18.32135009765625), + Vec3fEq(-3.653090000152587890625, 3.653090000152587890625, -20.3049831390380859375), + Vec3fEq(16.3816013336181640625, -16.3816013336181640625, -19.044734954833984375), + Vec3fEq(36.416290283203125, -36.416290283203125, -17.061100006103515625), + Vec3fEq(56.450984954833984375, -56.450984954833984375, -15.0774688720703125), + Vec3fEq(76.4856719970703125, -76.4856719970703125, -13.0938358306884765625), + Vec3fEq(96.52036285400390625, -96.52036285400390625, -11.02784252166748046875), + Vec3fEq(116.5550537109375, -116.5550537109375, -8.5993976593017578125), + Vec3fEq(136.5897369384765625, -136.5897369384765625, -6.170953273773193359375), + Vec3fEq(156.6244354248046875, -156.6244354248046875, -3.74250507354736328125), + Vec3fEq(176.6591339111328125, -176.6591339111328125, -1.314060688018798828125), + Vec3fEq(196.693817138671875, -196.693817138671875, 1.1143856048583984375), + Vec3fEq(204, -204, 1.9999811649322509765625) + )) << mPath; } TEST_F(DetourNavigatorNavigatorTest, path_should_be_around_avoid_shape) { + osg::ref_ptr bulletShape(new Resource::BulletShape); + std::array heightfieldData {{ 0, 0, 0, 0, 0, 0, -25, -25, -25, -25, @@ -393,8 +423,9 @@ namespace 0, -25, -100, -100, -100, 0, -25, -100, -100, -100, }}; - btHeightfieldTerrainShape shape = makeSquareHeightfieldTerrainShape(heightfieldData); - shape.setLocalScaling(btVector3(128, 128, 1)); + auto shapePtr = makeSquareHeightfieldTerrainShape(heightfieldData); + shapePtr->setLocalScaling(btVector3(128, 128, 1)); + bulletShape->mCollisionShape = shapePtr.release(); std::array heightfieldDataAvoid {{ -25, -25, -25, -25, -25, @@ -403,42 +434,44 @@ namespace -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, }}; - btHeightfieldTerrainShape shapeAvoid = makeSquareHeightfieldTerrainShape(heightfieldDataAvoid); - shapeAvoid.setLocalScaling(btVector3(128, 128, 1)); + auto shapeAvoidPtr = makeSquareHeightfieldTerrainShape(heightfieldDataAvoid); + shapeAvoidPtr->setLocalScaling(btVector3(128, 128, 1)); + bulletShape->mAvoidCollisionShape = shapeAvoidPtr.release(); + + osg::ref_ptr instance(new Resource::BulletShapeInstance(bulletShape)); mNavigator->addAgent(mAgentHalfExtents); - mNavigator->addObject(ObjectId(&shape), ObjectShapes {shape, &shapeAvoid}, btTransform::getIdentity()); + mNavigator->addObject(ObjectId(instance->getCollisionShape()), ObjectShapes(instance), btTransform::getIdentity()); mNavigator->update(mPlayerPosition); - mNavigator->wait(); + mNavigator->wait(mListener, WaitConditionType::allJobsDone); EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mOut), Status::Success); EXPECT_THAT(mPath, ElementsAre( - Vec3fEq(-215, 215, 1.9393787384033203125), - Vec3fEq(-200.8159637451171875, 190.47265625, -0.639537751674652099609375), - Vec3fEq(-186.6319427490234375, 165.9453125, -3.2184507846832275390625), - Vec3fEq(-172.447906494140625, 141.41796875, -5.797363758087158203125), - Vec3fEq(-158.263885498046875, 116.8906097412109375, -8.37627696990966796875), - Vec3fEq(-144.079864501953125, 92.3632659912109375, -10.9551906585693359375), - Vec3fEq(-129.89581298828125, 67.83591461181640625, -13.53410625457763671875), - Vec3fEq(-115.7117919921875, 43.308563232421875, -16.1130199432373046875), - Vec3fEq(-101.5277557373046875, 18.7812137603759765625, -18.6919345855712890625), - Vec3fEq(-87.34372711181640625, -5.7461376190185546875, -20.4680538177490234375), - Vec3fEq(-67.02922821044921875, -25.4970550537109375, -20.514247894287109375), - Vec3fEq(-46.714717864990234375, -45.2479705810546875, -20.560443878173828125), - Vec3fEq(-26.40021514892578125, -64.99889373779296875, -20.6066417694091796875), - Vec3fEq(-6.085712432861328125, -84.74980926513671875, -20.652835845947265625), - Vec3fEq(14.22879505157470703125, -104.50072479248046875, -18.151397705078125), - Vec3fEq(39.05098724365234375, -118.16222381591796875, -15.66748714447021484375), - Vec3fEq(63.87317657470703125, -131.82373046875, -13.18358135223388671875), - Vec3fEq(88.69537353515625, -145.4852142333984375, -10.699672698974609375), - Vec3fEq(113.51757049560546875, -159.146697998046875, -8.21576786041259765625), - Vec3fEq(138.3397674560546875, -172.808197021484375, -5.731859683990478515625), - Vec3fEq(163.1619720458984375, -186.469696044921875, -3.2479507923126220703125), - Vec3fEq(187.984161376953125, -200.1311798095703125, -0.764044821262359619140625), - Vec3fEq(212.8063507080078125, -213.7926788330078125, 1.719865322113037109375), - Vec3fEq(215, -215, 1.9393787384033203125) - )); + Vec3fEq(-204, 204, 1.99997997283935546875), + Vec3fEq(-191.328948974609375, 178.65789794921875, -0.815807759761810302734375), + Vec3fEq(-178.65789794921875, 153.3157806396484375, -3.6315968036651611328125), + Vec3fEq(-165.986846923828125, 127.9736785888671875, -6.4473857879638671875), + Vec3fEq(-153.3157806396484375, 102.6315765380859375, -9.26317310333251953125), + Vec3fEq(-140.6447296142578125, 77.28946685791015625, -12.07896137237548828125), + Vec3fEq(-127.9736785888671875, 51.947368621826171875, -14.894748687744140625), + Vec3fEq(-115.3026275634765625, 26.6052646636962890625, -17.7105388641357421875), + Vec3fEq(-102.63158416748046875, 1.2631585597991943359375, -20.5263233184814453125), + Vec3fEq(-89.9605712890625, -24.0789661407470703125, -19.591716766357421875), + Vec3fEq(-68.54410552978515625, -42.629238128662109375, -19.847625732421875), + Vec3fEq(-47.127635955810546875, -61.17951202392578125, -20.1035366058349609375), + Vec3fEq(-25.711170196533203125, -79.72978973388671875, -20.359447479248046875), + Vec3fEq(-4.294706821441650390625, -98.280059814453125, -20.6153545379638671875), + Vec3fEq(17.121753692626953125, -116.83034515380859375, -17.3710460662841796875), + Vec3fEq(42.7990570068359375, -128.80755615234375, -14.7094440460205078125), + Vec3fEq(68.4763641357421875, -140.7847747802734375, -12.0478420257568359375), + Vec3fEq(94.15366363525390625, -152.761993408203125, -9.3862361907958984375), + Vec3fEq(119.83097076416015625, -164.7392120361328125, -6.724635601043701171875), + Vec3fEq(145.508270263671875, -176.7164306640625, -4.06303119659423828125), + Vec3fEq(171.185577392578125, -188.69366455078125, -1.40142619609832763671875), + Vec3fEq(196.862884521484375, -200.6708831787109375, 1.2601754665374755859375), + Vec3fEq(204, -204, 1.999979496002197265625) + )) << mPath; } TEST_F(DetourNavigatorNavigatorTest, path_should_be_over_water_ground_lower_than_water_with_only_swim_flag) @@ -450,14 +483,15 @@ namespace -50, -100, -150, -100, -100, 0, -50, -100, -100, -100, }}; - btHeightfieldTerrainShape shape = makeSquareHeightfieldTerrainShape(heightfieldData); + const auto shapePtr = makeSquareHeightfieldTerrainShape(heightfieldData); + btHeightfieldTerrainShape& shape = *shapePtr; shape.setLocalScaling(btVector3(128, 128, 1)); mNavigator->addAgent(mAgentHalfExtents); mNavigator->addWater(osg::Vec2i(0, 0), 128 * 4, 300, btTransform::getIdentity()); - mNavigator->addObject(ObjectId(&shape), shape, btTransform::getIdentity()); + mNavigator->addObject(ObjectId(&shape), nullptr, shape, btTransform::getIdentity()); mNavigator->update(mPlayerPosition); - mNavigator->wait(); + mNavigator->wait(mListener, WaitConditionType::allJobsDone); mStart.x() = 0; mStart.z() = 300; @@ -466,25 +500,24 @@ namespace EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_swim, mAreaCosts, mOut), Status::Success); - EXPECT_EQ(mPath, std::deque({ - osg::Vec3f(0, 215, 185.33331298828125), - osg::Vec3f(0, 186.6666717529296875, 185.33331298828125), - osg::Vec3f(0, 158.333343505859375, 185.33331298828125), - osg::Vec3f(0, 130.0000152587890625, 185.33331298828125), - osg::Vec3f(0, 101.66667938232421875, 185.33331298828125), - osg::Vec3f(0, 73.333343505859375, 185.33331298828125), - osg::Vec3f(0, 45.0000152587890625, 185.33331298828125), - osg::Vec3f(0, 16.6666812896728515625, 185.33331298828125), - osg::Vec3f(0, -11.66664981842041015625, 185.33331298828125), - osg::Vec3f(0, -39.999980926513671875, 185.33331298828125), - osg::Vec3f(0, -68.33331298828125, 185.33331298828125), - osg::Vec3f(0, -96.66664886474609375, 185.33331298828125), - osg::Vec3f(0, -124.99997711181640625, 185.33331298828125), - osg::Vec3f(0, -153.33331298828125, 185.33331298828125), - osg::Vec3f(0, -181.6666412353515625, 185.33331298828125), - osg::Vec3f(0, -209.999969482421875, 185.33331298828125), - osg::Vec3f(0, -215, 185.33331298828125), - })) << mPath; + EXPECT_THAT(mPath, ElementsAre( + Vec3fEq(0, 204, 185.33331298828125), + Vec3fEq(0, 175.6666717529296875, 185.33331298828125), + Vec3fEq(0, 147.3333282470703125, 185.33331298828125), + Vec3fEq(0, 119, 185.33331298828125), + Vec3fEq(0, 90.6666717529296875, 185.33331298828125), + Vec3fEq(0, 62.333339691162109375, 185.33331298828125), + Vec3fEq(0, 34.00000762939453125, 185.33331298828125), + Vec3fEq(0, 5.66667461395263671875, 185.33331298828125), + Vec3fEq(0, -22.6666584014892578125, 185.33331298828125), + Vec3fEq(0, -50.999988555908203125, 185.33331298828125), + Vec3fEq(0, -79.33332061767578125, 185.33331298828125), + Vec3fEq(0, -107.666656494140625, 185.33331298828125), + Vec3fEq(0, -135.9999847412109375, 185.33331298828125), + Vec3fEq(0, -164.33331298828125, 185.33331298828125), + Vec3fEq(0, -192.666656494140625, 185.33331298828125), + Vec3fEq(0, -204, 185.33331298828125) + )) << mPath; } TEST_F(DetourNavigatorNavigatorTest, path_should_be_over_water_when_ground_cross_water_with_swim_and_walk_flags) @@ -498,14 +531,15 @@ namespace 0, -100, -100, -100, -100, -100, 0, 0, 0, 0, 0, 0, 0, 0, }}; - btHeightfieldTerrainShape shape = makeSquareHeightfieldTerrainShape(heightfieldData); + const auto shapePtr = makeSquareHeightfieldTerrainShape(heightfieldData); + btHeightfieldTerrainShape& shape = *shapePtr; shape.setLocalScaling(btVector3(128, 128, 1)); mNavigator->addAgent(mAgentHalfExtents); mNavigator->addWater(osg::Vec2i(0, 0), 128 * 4, -25, btTransform::getIdentity()); - mNavigator->addObject(ObjectId(&shape), shape, btTransform::getIdentity()); + mNavigator->addObject(ObjectId(&shape), nullptr, shape, btTransform::getIdentity()); mNavigator->update(mPlayerPosition); - mNavigator->wait(); + mNavigator->wait(mListener, WaitConditionType::allJobsDone); mStart.x() = 0; mEnd.x() = 0; @@ -513,25 +547,24 @@ namespace EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_swim | Flag_walk, mAreaCosts, mOut), Status::Success); - EXPECT_EQ(mPath, std::deque({ - osg::Vec3f(0, 215, -94.75363922119140625), - osg::Vec3f(0, 186.6666717529296875, -106.0000152587890625), - osg::Vec3f(0, 158.333343505859375, -115.85507965087890625), - osg::Vec3f(0, 130.0000152587890625, -125.71016693115234375), - osg::Vec3f(0, 101.66667938232421875, -135.5652313232421875), - osg::Vec3f(0, 73.333343505859375, -143.3333587646484375), - osg::Vec3f(0, 45.0000152587890625, -143.3333587646484375), - osg::Vec3f(0, 16.6666812896728515625, -143.3333587646484375), - osg::Vec3f(0, -11.66664981842041015625, -143.3333587646484375), - osg::Vec3f(0, -39.999980926513671875, -143.3333587646484375), - 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.59423065185546875), - osg::Vec3f(0, -181.6666412353515625, -107.73915863037109375), - osg::Vec3f(0, -209.999969482421875, -97.7971343994140625), - osg::Vec3f(0, -215, -94.75363922119140625), - })) << mPath; + EXPECT_THAT(mPath, ElementsAre( + Vec3fEq(0, 204, -98.000030517578125), + Vec3fEq(0, 175.6666717529296875, -108.30306243896484375), + Vec3fEq(0, 147.3333282470703125, -118.6060791015625), + Vec3fEq(0, 119, -128.90911865234375), + Vec3fEq(0, 90.6666717529296875, -139.2121429443359375), + Vec3fEq(0, 62.333339691162109375, -143.3333587646484375), + Vec3fEq(0, 34.00000762939453125, -143.3333587646484375), + Vec3fEq(0, 5.66667461395263671875, -143.3333587646484375), + Vec3fEq(0, -22.6666584014892578125, -143.3333587646484375), + Vec3fEq(0, -50.999988555908203125, -143.3333587646484375), + Vec3fEq(0, -79.33332061767578125, -143.3333587646484375), + Vec3fEq(0, -107.666656494140625, -133.0303192138671875), + Vec3fEq(0, -135.9999847412109375, -122.72728729248046875), + Vec3fEq(0, -164.33331298828125, -112.4242706298828125), + Vec3fEq(0, -192.666656494140625, -102.12123870849609375), + Vec3fEq(0, -204, -98.00002288818359375) + )) << mPath; } TEST_F(DetourNavigatorNavigatorTest, path_should_be_over_water_when_ground_cross_water_with_max_int_cells_size_and_swim_and_walk_flags) @@ -545,14 +578,15 @@ namespace 0, -100, -100, -100, -100, -100, 0, 0, 0, 0, 0, 0, 0, 0, }}; - btHeightfieldTerrainShape shape = makeSquareHeightfieldTerrainShape(heightfieldData); + const auto shapePtr = makeSquareHeightfieldTerrainShape(heightfieldData); + btHeightfieldTerrainShape& shape = *shapePtr; shape.setLocalScaling(btVector3(128, 128, 1)); mNavigator->addAgent(mAgentHalfExtents); - mNavigator->addObject(ObjectId(&shape), shape, btTransform::getIdentity()); + mNavigator->addObject(ObjectId(&shape), nullptr, shape, btTransform::getIdentity()); mNavigator->addWater(osg::Vec2i(0, 0), std::numeric_limits::max(), -25, btTransform::getIdentity()); mNavigator->update(mPlayerPosition); - mNavigator->wait(); + mNavigator->wait(mListener, WaitConditionType::allJobsDone); mStart.x() = 0; mEnd.x() = 0; @@ -560,25 +594,24 @@ namespace EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_swim | Flag_walk, mAreaCosts, mOut), Status::Success); - EXPECT_EQ(mPath, std::deque({ - osg::Vec3f(0, 215, -94.75363922119140625), - osg::Vec3f(0, 186.6666717529296875, -106.0000152587890625), - osg::Vec3f(0, 158.333343505859375, -115.85507965087890625), - osg::Vec3f(0, 130.0000152587890625, -125.71016693115234375), - osg::Vec3f(0, 101.66667938232421875, -135.5652313232421875), - osg::Vec3f(0, 73.333343505859375, -143.3333587646484375), - osg::Vec3f(0, 45.0000152587890625, -143.3333587646484375), - osg::Vec3f(0, 16.6666812896728515625, -143.3333587646484375), - osg::Vec3f(0, -11.66664981842041015625, -143.3333587646484375), - osg::Vec3f(0, -39.999980926513671875, -143.3333587646484375), - 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.59423065185546875), - osg::Vec3f(0, -181.6666412353515625, -107.73915863037109375), - osg::Vec3f(0, -209.999969482421875, -97.7971343994140625), - osg::Vec3f(0, -215, -94.75363922119140625), - })) << mPath; + EXPECT_THAT(mPath, ElementsAre( + Vec3fEq(0, 204, -98.000030517578125), + Vec3fEq(0, 175.6666717529296875, -108.30306243896484375), + Vec3fEq(0, 147.3333282470703125, -118.6060791015625), + Vec3fEq(0, 119, -128.90911865234375), + Vec3fEq(0, 90.6666717529296875, -139.2121429443359375), + Vec3fEq(0, 62.333339691162109375, -143.3333587646484375), + Vec3fEq(0, 34.00000762939453125, -143.3333587646484375), + Vec3fEq(0, 5.66667461395263671875, -143.3333587646484375), + Vec3fEq(0, -22.6666584014892578125, -143.3333587646484375), + Vec3fEq(0, -50.999988555908203125, -143.3333587646484375), + Vec3fEq(0, -79.33332061767578125, -143.3333587646484375), + Vec3fEq(0, -107.666656494140625, -133.0303192138671875), + Vec3fEq(0, -135.9999847412109375, -122.72728729248046875), + Vec3fEq(0, -164.33331298828125, -112.4242706298828125), + Vec3fEq(0, -192.666656494140625, -102.12123870849609375), + Vec3fEq(0, -204, -98.00002288818359375) + )) << mPath; } TEST_F(DetourNavigatorNavigatorTest, path_should_be_over_ground_when_ground_cross_water_with_only_walk_flag) @@ -592,14 +625,15 @@ namespace 0, -100, -100, -100, -100, -100, 0, 0, 0, 0, 0, 0, 0, 0, }}; - btHeightfieldTerrainShape shape = makeSquareHeightfieldTerrainShape(heightfieldData); + const auto shapePtr = makeSquareHeightfieldTerrainShape(heightfieldData); + btHeightfieldTerrainShape& shape = *shapePtr; shape.setLocalScaling(btVector3(128, 128, 1)); mNavigator->addAgent(mAgentHalfExtents); mNavigator->addWater(osg::Vec2i(0, 0), 128 * 4, -25, btTransform::getIdentity()); - mNavigator->addObject(ObjectId(&shape), shape, btTransform::getIdentity()); + mNavigator->addObject(ObjectId(&shape), nullptr, shape, btTransform::getIdentity()); mNavigator->update(mPlayerPosition); - mNavigator->wait(); + mNavigator->wait(mListener, WaitConditionType::allJobsDone); mStart.x() = 0; mEnd.x() = 0; @@ -607,25 +641,24 @@ namespace EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mOut), Status::Success); EXPECT_THAT(mPath, ElementsAre( - Vec3fEq(0, 215, -94.75363922119140625), - Vec3fEq(9.8083515167236328125, 188.4185333251953125, -105.199951171875), - Vec3fEq(19.6167049407958984375, 161.837066650390625, -114.25495147705078125), - Vec3fEq(29.42505645751953125, 135.255615234375, -123.309967041015625), - Vec3fEq(39.23340606689453125, 108.674163818359375, -132.3649749755859375), - Vec3fEq(49.04175567626953125, 82.09270477294921875, -137.2874755859375), - Vec3fEq(58.8501129150390625, 55.5112457275390625, -139.2451171875), - Vec3fEq(68.6584625244140625, 28.9297885894775390625, -141.2027740478515625), - Vec3fEq(78.4668121337890625, 2.3483295440673828125, -143.1604156494140625), - Vec3fEq(88.27516937255859375, -24.233127593994140625, -141.3894805908203125), - Vec3fEq(83.73651885986328125, -52.2005767822265625, -142.3761444091796875), - Vec3fEq(79.19786834716796875, -80.16802978515625, -143.114837646484375), - Vec3fEq(64.8477935791015625, -104.598602294921875, -137.840911865234375), - Vec3fEq(50.497714996337890625, -129.0291748046875, -131.45831298828125), - Vec3fEq(36.147632598876953125, -153.459747314453125, -121.42321014404296875), - Vec3fEq(21.7975559234619140625, -177.8903350830078125, -111.38811492919921875), - Vec3fEq(7.44747829437255859375, -202.3209075927734375, -101.19382476806640625), - Vec3fEq(0, -215, -94.75363922119140625) - )); + Vec3fEq(0, 204, -98.000030517578125), + Vec3fEq(10.26930999755859375, 177.59320068359375, -107.4711456298828125), + Vec3fEq(20.5386199951171875, 151.1864166259765625, -116.9422607421875), + Vec3fEq(30.8079280853271484375, 124.77960968017578125, -126.41339111328125), + Vec3fEq(41.077239990234375, 98.37281036376953125, -135.8845062255859375), + Vec3fEq(51.346546173095703125, 71.96601104736328125, -138.2003936767578125), + Vec3fEq(61.615856170654296875, 45.559215545654296875, -140.0838470458984375), + Vec3fEq(71.88516998291015625, 19.1524181365966796875, -141.9673004150390625), + Vec3fEq(82.15447235107421875, -7.254379749298095703125, -142.3074798583984375), + Vec3fEq(81.04636383056640625, -35.56603240966796875, -142.7104339599609375), + Vec3fEq(79.93825531005859375, -63.877685546875, -143.1133880615234375), + Vec3fEq(78.83014678955078125, -92.18933868408203125, -138.7660675048828125), + Vec3fEq(62.50392913818359375, -115.3460235595703125, -130.237823486328125), + Vec3fEq(46.17771148681640625, -138.502716064453125, -121.8172149658203125), + Vec3fEq(29.85149383544921875, -161.6594085693359375, -113.39659881591796875), + Vec3fEq(13.52527523040771484375, -184.81610107421875, -104.97599029541015625), + Vec3fEq(0, -204, -98.00002288818359375) + )) << mPath; } TEST_F(DetourNavigatorNavigatorTest, update_remove_and_update_then_find_path_should_return_path) @@ -637,49 +670,49 @@ namespace 0, -25, -100, -100, -100, 0, -25, -100, -100, -100, }}; - btHeightfieldTerrainShape shape = makeSquareHeightfieldTerrainShape(heightfieldData); + const auto shapePtr = makeSquareHeightfieldTerrainShape(heightfieldData); + btHeightfieldTerrainShape& shape = *shapePtr; shape.setLocalScaling(btVector3(128, 128, 1)); mNavigator->addAgent(mAgentHalfExtents); - mNavigator->addObject(ObjectId(&shape), shape, btTransform::getIdentity()); + mNavigator->addObject(ObjectId(&shape), nullptr, shape, btTransform::getIdentity()); mNavigator->update(mPlayerPosition); - mNavigator->wait(); + mNavigator->wait(mListener, WaitConditionType::allJobsDone); mNavigator->removeObject(ObjectId(&shape)); mNavigator->update(mPlayerPosition); - mNavigator->wait(); + mNavigator->wait(mListener, WaitConditionType::allJobsDone); - mNavigator->addObject(ObjectId(&shape), shape, btTransform::getIdentity()); + mNavigator->addObject(ObjectId(&shape), nullptr, shape, btTransform::getIdentity()); mNavigator->update(mPlayerPosition); - mNavigator->wait(); + mNavigator->wait(mListener, WaitConditionType::allJobsDone); EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mOut), Status::Success); EXPECT_THAT(mPath, ElementsAre( - Vec3fEq(-215, 215, 1.85963428020477294921875), - Vec3fEq(-194.9653167724609375, 194.9653167724609375, -6.57602214813232421875), - Vec3fEq(-174.930633544921875, 174.930633544921875, -15.01167774200439453125), - Vec3fEq(-154.8959503173828125, 154.8959503173828125, -23.4473361968994140625), - Vec3fEq(-134.86126708984375, 134.86126708984375, -31.8829936981201171875), - Vec3fEq(-114.82657623291015625, 114.82657623291015625, -40.3186492919921875), - Vec3fEq(-94.7918853759765625, 94.7918853759765625, -47.3990631103515625), - Vec3fEq(-74.75719451904296875, 74.75719451904296875, -53.7258148193359375), - Vec3fEq(-54.722499847412109375, 54.722499847412109375, -60.052555084228515625), - Vec3fEq(-34.68780517578125, 34.68780517578125, -66.37931060791015625), - Vec3fEq(-14.6531162261962890625, 14.6531162261962890625, -72.70604705810546875), - Vec3fEq(5.3815765380859375, -5.3815765380859375, -75.35065460205078125), - Vec3fEq(25.41626739501953125, -25.41626739501953125, -67.9694671630859375), - Vec3fEq(45.450958251953125, -45.450958251953125, -60.5882568359375), - Vec3fEq(65.48564910888671875, -65.48564910888671875, -53.20705413818359375), - Vec3fEq(85.5203399658203125, -85.5203399658203125, -45.8258514404296875), - Vec3fEq(105.55503082275390625, -105.55503082275390625, -38.44464874267578125), - Vec3fEq(125.5897216796875, -125.5897216796875, -31.063449859619140625), - Vec3fEq(145.6244049072265625, -145.6244049072265625, -23.6822509765625), - Vec3fEq(165.659088134765625, -165.659088134765625, -16.3010501861572265625), - Vec3fEq(185.6937713623046875, -185.6937713623046875, -8.91985416412353515625), - Vec3fEq(205.7284698486328125, -205.7284698486328125, -1.5386505126953125), - Vec3fEq(215, -215, 1.87718021869659423828125) - )); + Vec3fEq(-204, 204, 1.99998295307159423828125), + Vec3fEq(-183.965301513671875, 183.965301513671875, 1.99998819828033447265625), + Vec3fEq(-163.9306182861328125, 163.9306182861328125, 1.99999344348907470703125), + Vec3fEq(-143.89593505859375, 143.89593505859375, -2.7206256389617919921875), + Vec3fEq(-123.86124420166015625, 123.86124420166015625, -13.1089839935302734375), + Vec3fEq(-103.8265533447265625, 103.8265533447265625, -23.4973468780517578125), + Vec3fEq(-83.7918548583984375, 83.7918548583984375, -33.885707855224609375), + Vec3fEq(-63.75716400146484375, 63.75716400146484375, -44.27407073974609375), + Vec3fEq(-43.72247314453125, 43.72247314453125, -54.662433624267578125), + Vec3fEq(-23.6877803802490234375, 23.6877803802490234375, -65.0507965087890625), + Vec3fEq(-3.653090000152587890625, 3.653090000152587890625, -75.43915557861328125), + Vec3fEq(16.3816013336181640625, -16.3816013336181640625, -69.749267578125), + Vec3fEq(36.416290283203125, -36.416290283203125, -60.4739532470703125), + Vec3fEq(56.450984954833984375, -56.450984954833984375, -51.1986236572265625), + Vec3fEq(76.4856719970703125, -76.4856719970703125, -41.92330169677734375), + Vec3fEq(96.52036285400390625, -96.52036285400390625, -31.46941375732421875), + Vec3fEq(116.5550537109375, -116.5550537109375, -19.597003936767578125), + Vec3fEq(136.5897369384765625, -136.5897369384765625, -7.724592685699462890625), + Vec3fEq(156.6244354248046875, -156.6244354248046875, 1.99999535083770751953125), + Vec3fEq(176.6591339111328125, -176.6591339111328125, 1.99999010562896728515625), + Vec3fEq(196.693817138671875, -196.693817138671875, 1.99998486042022705078125), + Vec3fEq(204, -204, 1.99998295307159423828125) + )) << mPath; } TEST_F(DetourNavigatorNavigatorTest, update_then_find_random_point_around_circle_should_return_position) @@ -691,23 +724,25 @@ namespace 0, -25, -100, -100, -100, 0, -25, -100, -100, -100, }}; - btHeightfieldTerrainShape shape = makeSquareHeightfieldTerrainShape(heightfieldData); + const auto shapePtr = makeSquareHeightfieldTerrainShape(heightfieldData); + btHeightfieldTerrainShape& shape = *shapePtr; shape.setLocalScaling(btVector3(128, 128, 1)); mNavigator->addAgent(mAgentHalfExtents); - mNavigator->addObject(ObjectId(&shape), shape, btTransform::getIdentity()); + mNavigator->addObject(ObjectId(&shape), nullptr, shape, btTransform::getIdentity()); mNavigator->update(mPlayerPosition); - mNavigator->wait(); + mNavigator->wait(mListener, WaitConditionType::allJobsDone); Misc::Rng::init(42); const auto result = mNavigator->findRandomPointAroundCircle(mAgentHalfExtents, mStart, 100.0, Flag_walk); - ASSERT_THAT(result, Optional(Vec3fEq(-209.95985412597656, 129.89768981933594, -0.26253718137741089))); + ASSERT_THAT(result, Optional(Vec3fEq(-198.909332275390625, 123.06096649169921875, 1.99998414516448974609375))) + << (result ? *result : osg::Vec3f()); const auto distance = (*result - mStart).length(); - EXPECT_FLOAT_EQ(distance, 85.260780334472656); + EXPECT_FLOAT_EQ(distance, 81.105133056640625) << distance; } TEST_F(DetourNavigatorNavigatorTest, multiple_threads_should_lock_tiles) @@ -722,91 +757,94 @@ namespace 0, -25, -100, -100, -100, 0, -25, -100, -100, -100, }}; - btHeightfieldTerrainShape heightfieldShape = makeSquareHeightfieldTerrainShape(heightfieldData); + const auto heightfieldShapePtr = makeSquareHeightfieldTerrainShape(heightfieldData); + btHeightfieldTerrainShape& heightfieldShape = *heightfieldShapePtr; heightfieldShape.setLocalScaling(btVector3(128, 128, 1)); - const std::vector boxShapes(100, btVector3(20, 20, 100)); + std::vector> boxes; + std::generate_n(std::back_inserter(boxes), 100, [] { return std::make_unique(btVector3(20, 20, 100)); }); mNavigator->addAgent(mAgentHalfExtents); - mNavigator->addObject(ObjectId(&heightfieldShape), heightfieldShape, btTransform::getIdentity()); + mNavigator->addObject(ObjectId(&heightfieldShape), nullptr, heightfieldShape, btTransform::getIdentity()); - for (std::size_t i = 0; i < boxShapes.size(); ++i) + for (std::size_t i = 0; i < boxes.size(); ++i) { const btTransform transform(btMatrix3x3::getIdentity(), btVector3(i * 10, i * 10, i * 10)); - mNavigator->addObject(ObjectId(&boxShapes[i]), boxShapes[i], transform); + mNavigator->addObject(ObjectId(&boxes[i].shape()), ObjectShapes(boxes[i].instance()), transform); } std::this_thread::sleep_for(std::chrono::microseconds(1)); - for (std::size_t i = 0; i < boxShapes.size(); ++i) + for (std::size_t i = 0; i < boxes.size(); ++i) { const btTransform transform(btMatrix3x3::getIdentity(), btVector3(i * 10 + 1, i * 10 + 1, i * 10 + 1)); - mNavigator->updateObject(ObjectId(&boxShapes[i]), boxShapes[i], transform); + mNavigator->updateObject(ObjectId(&boxes[i].shape()), ObjectShapes(boxes[i].instance()), transform); } mNavigator->update(mPlayerPosition); - mNavigator->wait(); + mNavigator->wait(mListener, WaitConditionType::allJobsDone); EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mOut), Status::Success); EXPECT_THAT(mPath, ElementsAre( - Vec3fEq(-215, 215, 1.8782780170440673828125), - Vec3fEq(-199.7968292236328125, 191.09100341796875, -3.54875946044921875), - Vec3fEq(-184.5936431884765625, 167.1819915771484375, -8.97846889495849609375), - Vec3fEq(-169.3904571533203125, 143.2729949951171875, -14.40818119049072265625), - Vec3fEq(-154.1872711181640625, 119.363983154296875, -19.837886810302734375), - Vec3fEq(-138.9840850830078125, 95.4549713134765625, -25.2675952911376953125), - Vec3fEq(-123.78090667724609375, 71.54595947265625, -30.6973056793212890625), - Vec3fEq(-108.57772064208984375, 47.63695526123046875, -36.12701416015625), - Vec3fEq(-93.3745269775390625, 23.72794342041015625, -40.754695892333984375), - Vec3fEq(-78.17134857177734375, -0.18106450140476226806640625, -37.128795623779296875), - Vec3fEq(-62.968158721923828125, -24.0900726318359375, -33.50289154052734375), - Vec3fEq(-47.764972686767578125, -47.99908447265625, -30.797946929931640625), - Vec3fEq(-23.8524494171142578125, -63.196746826171875, -33.97112274169921875), - Vec3fEq(0.0600722394883632659912109375, -78.3944091796875, -37.14543914794921875), - Vec3fEq(23.97259521484375, -93.592071533203125, -40.774089813232421875), - Vec3fEq(47.885120391845703125, -108.78974151611328125, -36.051296234130859375), - Vec3fEq(71.797637939453125, -123.98740386962890625, -30.62355804443359375), - Vec3fEq(95.71016693115234375, -139.18505859375, -25.195819854736328125), - Vec3fEq(119.6226806640625, -154.382720947265625, -19.768085479736328125), - Vec3fEq(143.5352020263671875, -169.5803680419921875, -14.34035015106201171875), - Vec3fEq(167.447723388671875, -184.7780303955078125, -8.912616729736328125), - Vec3fEq(191.3602294921875, -199.9756927490234375, -3.48488140106201171875), - Vec3fEq(215, -215, 1.8782813549041748046875) + Vec3fEq(-204, 204, 1.99998295307159423828125), + Vec3fEq(-189.9427337646484375, 179.3997802734375, 1.9999866485595703125), + Vec3fEq(-175.8854522705078125, 154.7995452880859375, 1.99999034404754638671875), + Vec3fEq(-161.82818603515625, 130.1993255615234375, -3.701923847198486328125), + Vec3fEq(-147.770904541015625, 105.5991058349609375, -15.67664432525634765625), + Vec3fEq(-133.7136383056640625, 80.99887847900390625, -27.6513614654541015625), + Vec3fEq(-119.65636444091796875, 56.39865875244140625, -20.1209163665771484375), + Vec3fEq(-105.59909820556640625, 31.798435211181640625, -25.0669879913330078125), + Vec3fEq(-91.54183197021484375, 7.1982135772705078125, -31.5624217987060546875), + Vec3fEq(-77.48455810546875, -17.402008056640625, -26.98972320556640625), + Vec3fEq(-63.427295684814453125, -42.00223541259765625, -19.9045581817626953125), + Vec3fEq(-42.193531036376953125, -60.761363983154296875, -20.4544773101806640625), + Vec3fEq(-20.9597682952880859375, -79.5204925537109375, -23.599918365478515625), + Vec3fEq(3.8312885761260986328125, -93.2384033203125, -30.7141361236572265625), + Vec3fEq(28.6223468780517578125, -106.95632171630859375, -24.1782474517822265625), + Vec3fEq(53.413402557373046875, -120.6742401123046875, -19.4096889495849609375), + Vec3fEq(78.20446014404296875, -134.39215087890625, -27.6632633209228515625), + Vec3fEq(102.99552154541015625, -148.110076904296875, -15.8613681793212890625), + Vec3fEq(127.7865753173828125, -161.827972412109375, -4.059485912322998046875), + Vec3fEq(152.57763671875, -175.5458984375, 1.9999904632568359375), + Vec3fEq(177.3686981201171875, -189.2638092041015625, 1.9999866485595703125), + Vec3fEq(202.1597442626953125, -202.9817047119140625, 1.9999830722808837890625), + Vec3fEq(204, -204, 1.99998295307159423828125) )) << mPath; } TEST_F(DetourNavigatorNavigatorTest, update_changed_multiple_times_object_should_delay_navmesh_change) { - const std::vector shapes(100, btVector3(64, 64, 64)); + std::vector> shapes; + std::generate_n(std::back_inserter(shapes), 100, [] { return std::make_unique(btVector3(64, 64, 64)); }); mNavigator->addAgent(mAgentHalfExtents); for (std::size_t i = 0; i < shapes.size(); ++i) { const btTransform transform(btMatrix3x3::getIdentity(), btVector3(i * 32, i * 32, i * 32)); - mNavigator->addObject(ObjectId(&shapes[i]), shapes[i], transform); + mNavigator->addObject(ObjectId(&shapes[i].shape()), ObjectShapes(shapes[i].instance()), transform); } mNavigator->update(mPlayerPosition); - mNavigator->wait(); + mNavigator->wait(mListener, WaitConditionType::allJobsDone); const auto start = std::chrono::steady_clock::now(); for (std::size_t i = 0; i < shapes.size(); ++i) { const btTransform transform(btMatrix3x3::getIdentity(), btVector3(i * 32 + 1, i * 32 + 1, i * 32 + 1)); - mNavigator->updateObject(ObjectId(&shapes[i]), shapes[i], transform); + mNavigator->updateObject(ObjectId(&shapes[i].shape()), ObjectShapes(shapes[i].instance()), transform); } mNavigator->update(mPlayerPosition); - mNavigator->wait(); + mNavigator->wait(mListener, WaitConditionType::allJobsDone); for (std::size_t i = 0; i < shapes.size(); ++i) { const btTransform transform(btMatrix3x3::getIdentity(), btVector3(i * 32 + 2, i * 32 + 2, i * 32 + 2)); - mNavigator->updateObject(ObjectId(&shapes[i]), shapes[i], transform); + mNavigator->updateObject(ObjectId(&shapes[i].shape()), ObjectShapes(shapes[i].instance()), transform); } mNavigator->update(mPlayerPosition); - mNavigator->wait(); + mNavigator->wait(mListener, WaitConditionType::allJobsDone); const auto duration = std::chrono::steady_clock::now() - start; @@ -823,17 +861,19 @@ namespace 0, -25, -100, -100, -100, 0, -25, -100, -100, -100, }}; - btHeightfieldTerrainShape shape = makeSquareHeightfieldTerrainShape(heightfieldData); + const auto shapePtr = makeSquareHeightfieldTerrainShape(heightfieldData); + btHeightfieldTerrainShape& shape = *shapePtr; shape.setLocalScaling(btVector3(128, 128, 1)); mNavigator->addAgent(mAgentHalfExtents); - mNavigator->addObject(ObjectId(&shape), shape, btTransform::getIdentity()); + mNavigator->addObject(ObjectId(&shape), nullptr, shape, btTransform::getIdentity()); mNavigator->update(mPlayerPosition); - mNavigator->wait(); + mNavigator->wait(mListener, WaitConditionType::allJobsDone); const auto result = mNavigator->raycast(mAgentHalfExtents, mStart, mEnd, Flag_walk); - ASSERT_THAT(result, Optional(Vec3fEq(mEnd.x(), mEnd.y(), 1.87719))); + ASSERT_THAT(result, Optional(Vec3fEq(mEnd.x(), mEnd.y(), 1.99998295307159423828125))) + << (result ? *result : osg::Vec3f()); } TEST_F(DetourNavigatorNavigatorTest, update_for_oscillating_object_that_does_not_change_navmesh_should_not_trigger_navmesh_update) @@ -845,22 +885,23 @@ namespace 0, -25, -100, -100, -100, 0, -25, -100, -100, -100, }}; - btHeightfieldTerrainShape heightfieldShape = makeSquareHeightfieldTerrainShape(heightfieldData); + const auto heightfieldShapePtr = makeSquareHeightfieldTerrainShape(heightfieldData); + btHeightfieldTerrainShape& heightfieldShape = *heightfieldShapePtr; heightfieldShape.setLocalScaling(btVector3(128, 128, 1)); - const btBoxShape oscillatingBoxShape(btVector3(20, 20, 20)); + CollisionShapeInstance oscillatingBox(std::make_unique(btVector3(20, 20, 20))); const btVector3 oscillatingBoxShapePosition(32, 32, 400); - const btBoxShape boderBoxShape(btVector3(50, 50, 50)); + CollisionShapeInstance boderBox(std::make_unique(btVector3(50, 50, 50))); mNavigator->addAgent(mAgentHalfExtents); - mNavigator->addObject(ObjectId(&heightfieldShape), heightfieldShape, btTransform::getIdentity()); - mNavigator->addObject(ObjectId(&oscillatingBoxShape), oscillatingBoxShape, + mNavigator->addObject(ObjectId(&heightfieldShape), nullptr, heightfieldShape, btTransform::getIdentity()); + mNavigator->addObject(ObjectId(&oscillatingBox.shape()), ObjectShapes(oscillatingBox.instance()), btTransform(btMatrix3x3::getIdentity(), oscillatingBoxShapePosition)); // add this box to make navmesh bound box independent from oscillatingBoxShape rotations - mNavigator->addObject(ObjectId(&boderBoxShape), boderBoxShape, + mNavigator->addObject(ObjectId(&boderBox.shape()), ObjectShapes(boderBox.instance()), btTransform(btMatrix3x3::getIdentity(), oscillatingBoxShapePosition + btVector3(0, 0, 200))); mNavigator->update(mPlayerPosition); - mNavigator->wait(); + mNavigator->wait(mListener, WaitConditionType::allJobsDone); const auto navMeshes = mNavigator->getNavMeshes(); ASSERT_EQ(navMeshes.size(), 1); @@ -874,9 +915,9 @@ namespace { const btTransform transform(btQuaternion(btVector3(0, 0, 1), n * 2 * osg::PI / 10), oscillatingBoxShapePosition); - mNavigator->updateObject(ObjectId(&oscillatingBoxShape), oscillatingBoxShape, transform); + mNavigator->updateObject(ObjectId(&oscillatingBox.shape()), ObjectShapes(oscillatingBox.instance()), transform); mNavigator->update(mPlayerPosition); - mNavigator->wait(); + mNavigator->wait(mListener, WaitConditionType::allJobsDone); } ASSERT_EQ(navMeshes.size(), 1); diff --git a/apps/openmw_test_suite/detournavigator/navmeshtilescache.cpp b/apps/openmw_test_suite/detournavigator/navmeshtilescache.cpp index 5bc7af646..447a5b44e 100644 --- a/apps/openmw_test_suite/detournavigator/navmeshtilescache.cpp +++ b/apps/openmw_test_suite/detournavigator/navmeshtilescache.cpp @@ -31,9 +31,7 @@ namespace const std::vector mVertices {{0, 0, 0, 1, 0, 0, 1, 1, 0}}; const std::vector mAreaTypes {1, AreaType_ground}; const std::vector mWater {}; - const std::size_t mTrianglesPerChunk {1}; - const RecastMesh mRecastMesh {mGeneration, mRevision, mIndices, mVertices, - mAreaTypes, mWater, mTrianglesPerChunk}; + const RecastMesh mRecastMesh {mGeneration, mRevision, mIndices, mVertices, mAreaTypes, mWater}; const std::vector mOffMeshConnections {}; unsigned char* const mData = reinterpret_cast(dtAlloc(1, DT_ALLOC_PERM)); NavMeshData mNavMeshData {mData, 1}; @@ -62,6 +60,7 @@ namespace EXPECT_FALSE(cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, std::move(mNavMeshData))); + EXPECT_NE(mNavMeshData.mValue, nullptr); } TEST_F(DetourNavigatorNavMeshTilesCacheTest, set_should_return_cached_value) @@ -77,7 +76,7 @@ namespace EXPECT_EQ(result.get(), (NavMeshDataRef {mData, 1})); } - TEST_F(DetourNavigatorNavMeshTilesCacheTest, set_existing_element_should_throw_exception) + TEST_F(DetourNavigatorNavMeshTilesCacheTest, set_existing_element_should_return_cached_element) { const std::size_t navMeshDataSize = 1; const std::size_t navMeshKeySize = cRecastMeshKeySize; @@ -87,10 +86,10 @@ namespace NavMeshData anotherNavMeshData {anotherData, 1}; cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, std::move(mNavMeshData)); - EXPECT_THROW( - cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, std::move(anotherNavMeshData)), - InvalidArgument - ); + EXPECT_EQ(mNavMeshData.mValue, nullptr); + const auto result = cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, std::move(anotherNavMeshData)); + ASSERT_TRUE(result); + EXPECT_EQ(result.get(), (NavMeshDataRef {mData, 1})); } TEST_F(DetourNavigatorNavMeshTilesCacheTest, get_should_return_cached_value) @@ -131,8 +130,7 @@ namespace const std::size_t maxSize = 1; NavMeshTilesCache cache(maxSize); const std::vector water {1, RecastMesh::Water {1, btTransform::getIdentity()}}; - const RecastMesh unexistentRecastMesh {mGeneration, mRevision, mIndices, mVertices, - mAreaTypes, water, mTrianglesPerChunk}; + const RecastMesh unexistentRecastMesh {mGeneration, mRevision, mIndices, mVertices, mAreaTypes, water}; cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, std::move(mNavMeshData)); EXPECT_FALSE(cache.get(mAgentHalfExtents, mTilePosition, unexistentRecastMesh, mOffMeshConnections)); @@ -146,8 +144,7 @@ namespace NavMeshTilesCache cache(maxSize); const std::vector water {1, RecastMesh::Water {1, btTransform::getIdentity()}}; - const RecastMesh anotherRecastMesh {mGeneration, mRevision, mIndices, mVertices, - mAreaTypes, water, mTrianglesPerChunk}; + const RecastMesh anotherRecastMesh {mGeneration, mRevision, mIndices, mVertices, mAreaTypes, water}; const auto anotherData = reinterpret_cast(dtAlloc(1, DT_ALLOC_PERM)); NavMeshData anotherNavMeshData {anotherData, 1}; @@ -167,8 +164,7 @@ namespace NavMeshTilesCache cache(maxSize); const std::vector water {1, RecastMesh::Water {1, btTransform::getIdentity()}}; - const RecastMesh anotherRecastMesh {mGeneration, mRevision, mIndices, mVertices, - mAreaTypes, water, mTrianglesPerChunk}; + const RecastMesh anotherRecastMesh {mGeneration, mRevision, mIndices, mVertices, mAreaTypes, water}; const auto anotherData = reinterpret_cast(dtAlloc(1, DT_ALLOC_PERM)); NavMeshData anotherNavMeshData {anotherData, 1}; @@ -187,13 +183,13 @@ namespace const std::vector leastRecentlySetWater {1, RecastMesh::Water {1, btTransform::getIdentity()}}; const RecastMesh leastRecentlySetRecastMesh {mGeneration, mRevision, mIndices, mVertices, - mAreaTypes, leastRecentlySetWater, mTrianglesPerChunk}; + mAreaTypes, leastRecentlySetWater}; const auto leastRecentlySetData = reinterpret_cast(dtAlloc(1, DT_ALLOC_PERM)); NavMeshData leastRecentlySetNavMeshData {leastRecentlySetData, 1}; const std::vector mostRecentlySetWater {1, RecastMesh::Water {2, btTransform::getIdentity()}}; const RecastMesh mostRecentlySetRecastMesh {mGeneration, mRevision, mIndices, mVertices, - mAreaTypes, mostRecentlySetWater, mTrianglesPerChunk}; + mAreaTypes, mostRecentlySetWater}; const auto mostRecentlySetData = reinterpret_cast(dtAlloc(1, DT_ALLOC_PERM)); NavMeshData mostRecentlySetNavMeshData {mostRecentlySetData, 1}; @@ -219,13 +215,13 @@ namespace const std::vector leastRecentlyUsedWater {1, RecastMesh::Water {1, btTransform::getIdentity()}}; const RecastMesh leastRecentlyUsedRecastMesh {mGeneration, mRevision, mIndices, mVertices, - mAreaTypes, leastRecentlyUsedWater, mTrianglesPerChunk}; + mAreaTypes, leastRecentlyUsedWater}; const auto leastRecentlyUsedData = reinterpret_cast(dtAlloc(1, DT_ALLOC_PERM)); NavMeshData leastRecentlyUsedNavMeshData {leastRecentlyUsedData, 1}; const std::vector mostRecentlyUsedWater {1, RecastMesh::Water {2, btTransform::getIdentity()}}; const RecastMesh mostRecentlyUsedRecastMesh {mGeneration, mRevision, mIndices, mVertices, - mAreaTypes, mostRecentlyUsedWater, mTrianglesPerChunk}; + mAreaTypes, mostRecentlyUsedWater}; const auto mostRecentlyUsedData = reinterpret_cast(dtAlloc(1, DT_ALLOC_PERM)); NavMeshData mostRecentlyUsedNavMeshData {mostRecentlyUsedData, 1}; @@ -262,7 +258,7 @@ namespace NavMeshTilesCache cache(maxSize); const std::vector water {1, RecastMesh::Water {1, btTransform::getIdentity()}}; - const RecastMesh tooLargeRecastMesh {mGeneration, mRevision, mIndices, mVertices, mAreaTypes, water, mTrianglesPerChunk}; + const RecastMesh tooLargeRecastMesh {mGeneration, mRevision, mIndices, mVertices, mAreaTypes, water}; const auto tooLargeData = reinterpret_cast(dtAlloc(2, DT_ALLOC_PERM)); NavMeshData tooLargeNavMeshData {tooLargeData, 2}; @@ -281,13 +277,13 @@ namespace NavMeshTilesCache cache(maxSize); const std::vector anotherWater {1, RecastMesh::Water {1, btTransform::getIdentity()}}; - const RecastMesh anotherRecastMesh {mGeneration, mRevision, mIndices, mVertices, mAreaTypes, anotherWater, mTrianglesPerChunk}; + const RecastMesh anotherRecastMesh {mGeneration, mRevision, mIndices, mVertices, mAreaTypes, anotherWater}; const auto anotherData = reinterpret_cast(dtAlloc(1, DT_ALLOC_PERM)); NavMeshData anotherNavMeshData {anotherData, 1}; const std::vector tooLargeWater {1, RecastMesh::Water {2, btTransform::getIdentity()}}; const RecastMesh tooLargeRecastMesh {mGeneration, mRevision, mIndices, mVertices, - mAreaTypes, tooLargeWater, mTrianglesPerChunk}; + mAreaTypes, tooLargeWater}; const auto tooLargeData = reinterpret_cast(dtAlloc(2, DT_ALLOC_PERM)); NavMeshData tooLargeNavMeshData {tooLargeData, 2}; @@ -311,7 +307,7 @@ namespace const std::vector water {1, RecastMesh::Water {1, btTransform::getIdentity()}}; const RecastMesh anotherRecastMesh {mGeneration, mRevision, mIndices, mVertices, - mAreaTypes, water, mTrianglesPerChunk}; + mAreaTypes, water}; const auto anotherData = reinterpret_cast(dtAlloc(1, DT_ALLOC_PERM)); NavMeshData anotherNavMeshData {anotherData, 1}; @@ -334,7 +330,7 @@ namespace NavMeshTilesCache cache(maxSize); const std::vector water {1, RecastMesh::Water {1, btTransform::getIdentity()}}; - const RecastMesh anotherRecastMesh {mGeneration, mRevision, mIndices, mVertices, mAreaTypes, water, mTrianglesPerChunk}; + const RecastMesh anotherRecastMesh {mGeneration, mRevision, mIndices, mVertices, mAreaTypes, water}; const auto anotherData = reinterpret_cast(dtAlloc(1, DT_ALLOC_PERM)); NavMeshData anotherNavMeshData {anotherData, 1}; diff --git a/apps/openmw_test_suite/detournavigator/recastmeshbuilder.cpp b/apps/openmw_test_suite/detournavigator/recastmeshbuilder.cpp index bcbf448ac..2624389b7 100644 --- a/apps/openmw_test_suite/detournavigator/recastmeshbuilder.cpp +++ b/apps/openmw_test_suite/detournavigator/recastmeshbuilder.cpp @@ -14,6 +14,8 @@ #include #include +#include + namespace DetourNavigator { static inline bool operator ==(const RecastMesh::Water& lhs, const RecastMesh::Water& rhs) @@ -37,7 +39,6 @@ namespace DetourNavigatorRecastMeshBuilderTest() { mSettings.mRecastScaleFactor = 1.0f; - mSettings.mTrianglesPerChunk = 256; mBounds.mMin = osg::Vec2f(-std::numeric_limits::max() * std::numeric_limits::epsilon(), -std::numeric_limits::max() * std::numeric_limits::epsilon()); mBounds.mMax = osg::Vec2f(std::numeric_limits::max() * std::numeric_limits::epsilon(), @@ -48,7 +49,7 @@ namespace TEST_F(DetourNavigatorRecastMeshBuilderTest, create_for_empty_should_return_empty) { RecastMeshBuilder builder(mSettings, mBounds); - const auto recastMesh = builder.create(mGeneration, mRevision); + const auto recastMesh = std::move(builder).create(mGeneration, mRevision); EXPECT_EQ(recastMesh->getVertices(), std::vector()); EXPECT_EQ(recastMesh->getIndices(), std::vector()); EXPECT_EQ(recastMesh->getAreaTypes(), std::vector()); @@ -62,7 +63,7 @@ namespace RecastMeshBuilder builder(mSettings, mBounds); builder.addObject(static_cast(shape), btTransform::getIdentity(), AreaType_ground); - const auto recastMesh = builder.create(mGeneration, mRevision); + const auto recastMesh = std::move(builder).create(mGeneration, mRevision); EXPECT_EQ(recastMesh->getVertices(), std::vector({ 1, 0, -1, -1, 0, 1, @@ -83,7 +84,7 @@ namespace btTransform(btMatrix3x3::getIdentity().scaled(btVector3(1, 2, 3)), btVector3(1, 2, 3)), AreaType_ground ); - const auto recastMesh = builder.create(mGeneration, mRevision); + const auto recastMesh = std::move(builder).create(mGeneration, mRevision); EXPECT_EQ(recastMesh->getVertices(), std::vector({ 2, 3, 0, 0, 3, 4, @@ -99,7 +100,7 @@ namespace btHeightfieldTerrainShape shape(2, 2, heightfieldData.data(), 1, 0, 0, 2, PHY_FLOAT, false); RecastMeshBuilder builder(mSettings, mBounds); builder.addObject(static_cast(shape), btTransform::getIdentity(), AreaType_ground); - const auto recastMesh = builder.create(mGeneration, mRevision); + const auto recastMesh = std::move(builder).create(mGeneration, mRevision); EXPECT_EQ(recastMesh->getVertices(), std::vector({ -0.5, 0, -0.5, -0.5, 0, 0.5, @@ -115,7 +116,7 @@ namespace btBoxShape shape(btVector3(1, 1, 2)); RecastMeshBuilder builder(mSettings, mBounds); builder.addObject(static_cast(shape), btTransform::getIdentity(), AreaType_ground); - const auto recastMesh = builder.create(mGeneration, mRevision); + const auto recastMesh = std::move(builder).create(mGeneration, mRevision); EXPECT_EQ(recastMesh->getVertices(), std::vector({ 1, 2, 1, -1, 2, 1, @@ -162,7 +163,7 @@ namespace btTransform::getIdentity(), AreaType_ground ); - const auto recastMesh = builder.create(mGeneration, mRevision); + const auto recastMesh = std::move(builder).create(mGeneration, mRevision); EXPECT_EQ(recastMesh->getVertices(), std::vector({ -1, -2, -1, -1, -2, 1, @@ -209,7 +210,7 @@ namespace btTransform(btMatrix3x3::getIdentity().scaled(btVector3(1, 2, 3)), btVector3(1, 2, 3)), AreaType_ground ); - const auto recastMesh = builder.create(mGeneration, mRevision); + const auto recastMesh = std::move(builder).create(mGeneration, mRevision); EXPECT_EQ(recastMesh->getVertices(), std::vector({ 2, 3, 0, 0, 3, 4, @@ -233,7 +234,7 @@ namespace btTransform(btMatrix3x3::getIdentity().scaled(btVector3(1, 2, 3)), btVector3(1, 2, 3)), AreaType_ground ); - const auto recastMesh = builder.create(mGeneration, mRevision); + const auto recastMesh = std::move(builder).create(mGeneration, mRevision); EXPECT_EQ(recastMesh->getVertices(), std::vector({ 3, 12, 2, 1, 12, 10, @@ -255,7 +256,7 @@ namespace btTransform::getIdentity(), AreaType_ground ); - const auto recastMesh = builder.create(mGeneration, mRevision); + const auto recastMesh = std::move(builder).create(mGeneration, mRevision); EXPECT_EQ(recastMesh->getVertices(), std::vector({ 1, 0, -1, -1, 0, 1, @@ -283,7 +284,7 @@ namespace btTransform::getIdentity(), AreaType_ground ); - const auto recastMesh = builder.create(mGeneration, mRevision); + const auto recastMesh = std::move(builder).create(mGeneration, mRevision); EXPECT_EQ(recastMesh->getVertices(), std::vector({ -0.2f, 0, -0.3f, -0.3f, 0, -0.2f, @@ -308,7 +309,7 @@ namespace static_cast(-osg::PI_4))), AreaType_ground ); - const auto recastMesh = builder.create(mGeneration, mRevision); + const auto recastMesh = std::move(builder).create(mGeneration, mRevision); EXPECT_THAT(recastMesh->getVertices(), Pointwise(FloatNear(1e-5), std::vector({ 0, -0.70710659027099609375, -3.535533905029296875, 0, 0.707107067108154296875, -3.535533905029296875, @@ -333,7 +334,7 @@ namespace static_cast(osg::PI_4))), AreaType_ground ); - const auto recastMesh = builder.create(mGeneration, mRevision); + const auto recastMesh = std::move(builder).create(mGeneration, mRevision); EXPECT_THAT(recastMesh->getVertices(), Pointwise(FloatNear(1e-5), std::vector({ -3.535533905029296875, -0.70710659027099609375, 0, -3.535533905029296875, 0.707107067108154296875, 0, @@ -358,7 +359,7 @@ namespace static_cast(osg::PI_4))), AreaType_ground ); - const auto recastMesh = builder.create(mGeneration, mRevision); + const auto recastMesh = std::move(builder).create(mGeneration, mRevision); EXPECT_THAT(recastMesh->getVertices(), Pointwise(FloatNear(1e-5), std::vector({ 1.41421353816986083984375, 0, 1.1920928955078125e-07, -1.41421353816986083984375, 0, -1.1920928955078125e-07, @@ -387,7 +388,7 @@ namespace btTransform::getIdentity(), AreaType_null ); - const auto recastMesh = builder.create(mGeneration, mRevision); + const auto recastMesh = std::move(builder).create(mGeneration, mRevision); EXPECT_EQ(recastMesh->getVertices(), std::vector({ 1, 0, -1, -1, 0, 1, @@ -404,7 +405,7 @@ namespace { RecastMeshBuilder builder(mSettings, mBounds); builder.addWater(1000, btTransform(btMatrix3x3::getIdentity(), btVector3(100, 200, 300))); - const auto recastMesh = builder.create(mGeneration, mRevision); + const auto recastMesh = std::move(builder).create(mGeneration, mRevision); EXPECT_EQ(recastMesh->getWater(), std::vector({ RecastMesh::Water {1000, btTransform(btMatrix3x3::getIdentity(), btVector3(100, 200, 300))} })); @@ -419,7 +420,7 @@ namespace RecastMeshBuilder builder(mSettings, mBounds); builder.addObject(static_cast(shape), btTransform::getIdentity(), AreaType_ground); - const auto recastMesh = builder.create(mGeneration, mRevision); + const auto recastMesh = std::move(builder).create(mGeneration, mRevision); EXPECT_EQ(recastMesh->getVertices(), std::vector({ -1, 0, -1, -1, 0, 1, diff --git a/apps/openmw_test_suite/detournavigator/recastmeshobject.cpp b/apps/openmw_test_suite/detournavigator/recastmeshobject.cpp index 621db51a8..7751d5220 100644 --- a/apps/openmw_test_suite/detournavigator/recastmeshobject.cpp +++ b/apps/openmw_test_suite/detournavigator/recastmeshobject.cpp @@ -14,20 +14,22 @@ namespace struct DetourNavigatorRecastMeshObjectTest : Test { - btBoxShape mBoxShape {btVector3(1, 2, 3)}; - btCompoundShape mCompoundShape {true}; + btBoxShape mBoxShapeImpl {btVector3(1, 2, 3)}; + CollisionShape mBoxShape {nullptr, mBoxShapeImpl}; + btCompoundShape mCompoundShapeImpl {true}; + CollisionShape mCompoundShape {nullptr, mCompoundShapeImpl}; btTransform mTransform {btQuaternion(btVector3(1, 2, 3), 1), btVector3(1, 2, 3)}; DetourNavigatorRecastMeshObjectTest() { - mCompoundShape.addChildShape(mTransform, std::addressof(mBoxShape)); + mCompoundShapeImpl.addChildShape(mTransform, std::addressof(mBoxShapeImpl)); } }; TEST_F(DetourNavigatorRecastMeshObjectTest, constructed_object_should_have_shape_and_transform) { const RecastMeshObject object(mBoxShape, mTransform, AreaType_ground); - EXPECT_EQ(std::addressof(object.getShape()), std::addressof(mBoxShape)); + EXPECT_EQ(std::addressof(object.getShape()), std::addressof(mBoxShapeImpl)); EXPECT_EQ(object.getTransform(), mTransform); } @@ -58,14 +60,14 @@ namespace TEST_F(DetourNavigatorRecastMeshObjectTest, update_for_compound_shape_with_same_transform_and_changed_child_transform_should_return_true) { RecastMeshObject object(mCompoundShape, mTransform, AreaType_ground); - mCompoundShape.updateChildTransform(0, btTransform::getIdentity()); + mCompoundShapeImpl.updateChildTransform(0, btTransform::getIdentity()); EXPECT_TRUE(object.update(mTransform, AreaType_ground)); } TEST_F(DetourNavigatorRecastMeshObjectTest, repeated_update_for_compound_shape_without_changes_should_return_false) { RecastMeshObject object(mCompoundShape, mTransform, AreaType_ground); - mCompoundShape.updateChildTransform(0, btTransform::getIdentity()); + mCompoundShapeImpl.updateChildTransform(0, btTransform::getIdentity()); object.update(mTransform, AreaType_ground); EXPECT_FALSE(object.update(mTransform, AreaType_ground)); } @@ -73,7 +75,7 @@ namespace TEST_F(DetourNavigatorRecastMeshObjectTest, update_for_changed_local_scaling_should_return_true) { RecastMeshObject object(mBoxShape, mTransform, AreaType_ground); - mBoxShape.setLocalScaling(btVector3(2, 2, 2)); + mBoxShapeImpl.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 index be5209001..038b1c628 100644 --- a/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp +++ b/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp @@ -24,7 +24,6 @@ namespace mSettings.mCellSize = 0.2f; mSettings.mRecastScaleFactor = 0.017647058823529415f; mSettings.mTileSize = 64; - mSettings.mTrianglesPerChunk = 256; } void onChangedTile(const TilePosition& tilePosition) @@ -55,7 +54,7 @@ namespace { TileCachedRecastMeshManager manager(mSettings); std::size_t calls = 0; - manager.forEachTilePosition([&] (const TilePosition&) { ++calls; }); + manager.forEachTile([&] (const TilePosition&, const CachedRecastMeshManager&) { ++calls; }); EXPECT_EQ(calls, 0); } @@ -63,15 +62,28 @@ namespace { TileCachedRecastMeshManager manager(mSettings); const btBoxShape boxShape(btVector3(20, 20, 100)); - EXPECT_TRUE(manager.addObject(ObjectId(&boxShape), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground)); + const CollisionShape shape(nullptr, boxShape); + EXPECT_TRUE(manager.addObject(ObjectId(&boxShape), shape, 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(&boxShape), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground); - EXPECT_FALSE(manager.addObject(ObjectId(&boxShape), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground)); + const CollisionShape shape(nullptr, boxShape); + manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); + EXPECT_FALSE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground)); + } + + TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, add_object_should_add_tiles) + { + TileCachedRecastMeshManager manager(mSettings); + const btBoxShape boxShape(btVector3(20, 20, 100)); + const CollisionShape shape(nullptr, boxShape); + ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground)); + for (int x = -1; x < 1; ++x) + for (int y = -1; y < 1; ++y) + ASSERT_TRUE(manager.hasTile(TilePosition(x, y))); } TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, update_object_for_changed_object_should_return_changed_tiles) @@ -79,8 +91,9 @@ namespace 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(&boxShape), boxShape, transform, AreaType::AreaType_ground); - EXPECT_TRUE(manager.updateObject(ObjectId(&boxShape), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground, + const CollisionShape shape(nullptr, boxShape); + manager.addObject(ObjectId(&boxShape), shape, transform, AreaType::AreaType_ground); + EXPECT_TRUE(manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [&] (const auto& v) { onChangedTile(v); })); EXPECT_THAT( mChangedTiles, @@ -93,8 +106,9 @@ namespace { TileCachedRecastMeshManager manager(mSettings); const btBoxShape boxShape(btVector3(20, 20, 100)); - manager.addObject(ObjectId(&boxShape), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground); - EXPECT_FALSE(manager.updateObject(ObjectId(&boxShape), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground, + const CollisionShape shape(nullptr, boxShape); + manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); + EXPECT_FALSE(manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [&] (const auto& v) { onChangedTile(v); })); EXPECT_EQ(mChangedTiles, std::vector()); } @@ -103,7 +117,8 @@ namespace { TileCachedRecastMeshManager manager(mSettings); const btBoxShape boxShape(btVector3(20, 20, 100)); - manager.addObject(ObjectId(&boxShape), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground); + const CollisionShape shape(nullptr, boxShape); + manager.addObject(ObjectId(&boxShape), shape, 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); @@ -114,7 +129,8 @@ namespace { TileCachedRecastMeshManager manager(mSettings); const btBoxShape boxShape(btVector3(20, 20, 100)); - manager.addObject(ObjectId(&boxShape), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground); + const CollisionShape shape(nullptr, boxShape); + manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); EXPECT_EQ(manager.getMesh(TilePosition(1, 0)), nullptr); } @@ -123,14 +139,15 @@ namespace TileCachedRecastMeshManager manager(mSettings); const btBoxShape boxShape(btVector3(20, 20, 100)); const btTransform transform(btMatrix3x3::getIdentity(), btVector3(getTileSize(mSettings) / mSettings.mRecastScaleFactor, 0, 0)); + const CollisionShape shape(nullptr, boxShape); - manager.addObject(ObjectId(&boxShape), boxShape, transform, AreaType::AreaType_ground); + manager.addObject(ObjectId(&boxShape), shape, 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(&boxShape), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); + manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); EXPECT_NE(manager.getMesh(TilePosition(-1, -1)), nullptr); EXPECT_NE(manager.getMesh(TilePosition(-1, 0)), nullptr); EXPECT_NE(manager.getMesh(TilePosition(0, -1)), nullptr); @@ -142,12 +159,13 @@ namespace TileCachedRecastMeshManager manager(mSettings); const btBoxShape boxShape(btVector3(20, 20, 100)); const btTransform transform(btMatrix3x3::getIdentity(), btVector3(getTileSize(mSettings) / mSettings.mRecastScaleFactor, 0, 0)); + const CollisionShape shape(nullptr, boxShape); - manager.addObject(ObjectId(&boxShape), boxShape, transform, AreaType::AreaType_ground); + manager.addObject(ObjectId(&boxShape), shape, transform, AreaType::AreaType_ground); EXPECT_EQ(manager.getMesh(TilePosition(-1, -1)), nullptr); EXPECT_EQ(manager.getMesh(TilePosition(-1, 0)), nullptr); - manager.updateObject(ObjectId(&boxShape), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); + manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); EXPECT_EQ(manager.getMesh(TilePosition(1, 0)), nullptr); EXPECT_EQ(manager.getMesh(TilePosition(1, -1)), nullptr); } @@ -156,7 +174,8 @@ namespace { TileCachedRecastMeshManager manager(mSettings); const btBoxShape boxShape(btVector3(20, 20, 100)); - manager.addObject(ObjectId(&boxShape), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground); + const CollisionShape shape(nullptr, boxShape); + manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); manager.removeObject(ObjectId(&boxShape)); EXPECT_EQ(manager.getMesh(TilePosition(-1, -1)), nullptr); EXPECT_EQ(manager.getMesh(TilePosition(-1, 0)), nullptr); @@ -168,14 +187,15 @@ namespace { TileCachedRecastMeshManager manager(mSettings); const btBoxShape boxShape(btVector3(20, 20, 100)); + const CollisionShape shape(nullptr, boxShape); - manager.addObject(ObjectId(&boxShape), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground); + manager.addObject(ObjectId(&boxShape), shape, 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(&boxShape), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); + manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); EXPECT_NE(manager.getMesh(TilePosition(-1, -1)), nullptr); EXPECT_NE(manager.getMesh(TilePosition(-1, 0)), nullptr); EXPECT_NE(manager.getMesh(TilePosition(0, -1)), nullptr); @@ -187,7 +207,8 @@ namespace TileCachedRecastMeshManager manager(mSettings); const auto initialRevision = manager.getRevision(); const btBoxShape boxShape(btVector3(20, 20, 100)); - manager.addObject(ObjectId(&boxShape), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground); + const CollisionShape shape(nullptr, boxShape); + manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); EXPECT_EQ(manager.getRevision(), initialRevision + 1); } @@ -195,9 +216,10 @@ namespace { TileCachedRecastMeshManager manager(mSettings); const btBoxShape boxShape(btVector3(20, 20, 100)); - manager.addObject(ObjectId(&boxShape), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground); + const CollisionShape shape(nullptr, boxShape); + manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); const auto beforeAddRevision = manager.getRevision(); - manager.addObject(ObjectId(&boxShape), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground); + manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); EXPECT_EQ(manager.getRevision(), beforeAddRevision); } @@ -206,9 +228,10 @@ namespace 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(&boxShape), boxShape, transform, AreaType::AreaType_ground); + const CollisionShape shape(nullptr, boxShape); + manager.addObject(ObjectId(&boxShape), shape, transform, AreaType::AreaType_ground); const auto beforeUpdateRevision = manager.getRevision(); - manager.updateObject(ObjectId(&boxShape), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); + manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); EXPECT_EQ(manager.getRevision(), beforeUpdateRevision + 1); } @@ -216,9 +239,10 @@ namespace { TileCachedRecastMeshManager manager(mSettings); const btBoxShape boxShape(btVector3(20, 20, 100)); - manager.addObject(ObjectId(&boxShape), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground); + const CollisionShape shape(nullptr, boxShape); + manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); const auto beforeUpdateRevision = manager.getRevision(); - manager.updateObject(ObjectId(&boxShape), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); + manager.updateObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); EXPECT_EQ(manager.getRevision(), beforeUpdateRevision); } @@ -226,7 +250,8 @@ namespace { TileCachedRecastMeshManager manager(mSettings); const btBoxShape boxShape(btVector3(20, 20, 100)); - manager.addObject(ObjectId(&boxShape), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground); + const CollisionShape shape(nullptr, boxShape); + manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground); const auto beforeRemoveRevision = manager.getRevision(); manager.removeObject(ObjectId(&boxShape)); EXPECT_EQ(manager.getRevision(), beforeRemoveRevision + 1); @@ -239,4 +264,81 @@ namespace manager.removeObject(ObjectId(&manager)); EXPECT_EQ(manager.getRevision(), beforeRemoveRevision); } + + TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, add_water_for_new_water_should_return_true) + { + TileCachedRecastMeshManager manager(mSettings); + const osg::Vec2i cellPosition(0, 0); + const int cellSize = 8192; + EXPECT_TRUE(manager.addWater(cellPosition, cellSize, btTransform::getIdentity())); + } + + TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, add_water_for_not_max_int_should_add_new_tiles) + { + TileCachedRecastMeshManager manager(mSettings); + const osg::Vec2i cellPosition(0, 0); + const int cellSize = 8192; + ASSERT_TRUE(manager.addWater(cellPosition, cellSize, btTransform::getIdentity())); + for (int x = -6; x < 6; ++x) + for (int y = -6; y < 6; ++y) + ASSERT_TRUE(manager.hasTile(TilePosition(x, y))); + } + + TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, add_water_for_max_int_should_not_add_new_tiles) + { + TileCachedRecastMeshManager manager(mSettings); + const btBoxShape boxShape(btVector3(20, 20, 100)); + const CollisionShape shape(nullptr, boxShape); + ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground)); + const osg::Vec2i cellPosition(0, 0); + const int cellSize = std::numeric_limits::max(); + ASSERT_TRUE(manager.addWater(cellPosition, cellSize, btTransform::getIdentity())); + for (int x = -6; x < 6; ++x) + for (int y = -6; y < 6; ++y) + ASSERT_EQ(manager.hasTile(TilePosition(x, y)), -1 <= x && x <= 0 && -1 <= y && y <= 0); + } + + TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, remove_water_for_absent_cell_should_return_nullopt) + { + TileCachedRecastMeshManager manager(mSettings); + EXPECT_EQ(manager.removeWater(osg::Vec2i(0, 0)), std::nullopt); + } + + TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, remove_water_for_existing_cell_should_return_removed_water) + { + TileCachedRecastMeshManager manager(mSettings); + const osg::Vec2i cellPosition(0, 0); + const int cellSize = 8192; + ASSERT_TRUE(manager.addWater(cellPosition, cellSize, btTransform::getIdentity())); + const auto result = manager.removeWater(cellPosition); + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result->mCellSize, cellSize); + } + + TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, remove_water_for_existing_cell_should_remove_empty_tiles) + { + TileCachedRecastMeshManager manager(mSettings); + const osg::Vec2i cellPosition(0, 0); + const int cellSize = 8192; + ASSERT_TRUE(manager.addWater(cellPosition, cellSize, btTransform::getIdentity())); + ASSERT_TRUE(manager.removeWater(cellPosition)); + for (int x = -6; x < 6; ++x) + for (int y = -6; y < 6; ++y) + ASSERT_FALSE(manager.hasTile(TilePosition(x, y))); + } + + TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, remove_water_for_existing_cell_should_leave_not_empty_tiles) + { + TileCachedRecastMeshManager manager(mSettings); + const btBoxShape boxShape(btVector3(20, 20, 100)); + const CollisionShape shape(nullptr, boxShape); + ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), shape, btTransform::getIdentity(), AreaType::AreaType_ground)); + const osg::Vec2i cellPosition(0, 0); + const int cellSize = 8192; + ASSERT_TRUE(manager.addWater(cellPosition, cellSize, btTransform::getIdentity())); + ASSERT_TRUE(manager.removeWater(cellPosition)); + for (int x = -6; x < 6; ++x) + for (int y = -6; y < 6; ++y) + ASSERT_EQ(manager.hasTile(TilePosition(x, y)), -1 <= x && x <= 0 && -1 <= y && y <= 0); + } } diff --git a/apps/wizard/componentselectionpage.hpp b/apps/wizard/componentselectionpage.hpp index 2509b9f5e..961669ab5 100644 --- a/apps/wizard/componentselectionpage.hpp +++ b/apps/wizard/componentselectionpage.hpp @@ -1,8 +1,6 @@ #ifndef COMPONENTSELECTIONPAGE_HPP #define COMPONENTSELECTIONPAGE_HPP -#include - #include "ui_componentselectionpage.h" namespace Wizard diff --git a/apps/wizard/conclusionpage.hpp b/apps/wizard/conclusionpage.hpp index f5f27dfca..72af2cceb 100644 --- a/apps/wizard/conclusionpage.hpp +++ b/apps/wizard/conclusionpage.hpp @@ -1,8 +1,6 @@ #ifndef CONCLUSIONPAGE_HPP #define CONCLUSIONPAGE_HPP -#include - #include "ui_conclusionpage.h" namespace Wizard diff --git a/apps/wizard/existinginstallationpage.hpp b/apps/wizard/existinginstallationpage.hpp index bb229e249..fbbbe432e 100644 --- a/apps/wizard/existinginstallationpage.hpp +++ b/apps/wizard/existinginstallationpage.hpp @@ -1,8 +1,6 @@ #ifndef EXISTINGINSTALLATIONPAGE_HPP #define EXISTINGINSTALLATIONPAGE_HPP -#include - #include "ui_existinginstallationpage.h" namespace Wizard diff --git a/apps/wizard/importpage.hpp b/apps/wizard/importpage.hpp index 412d39ac1..b1f26409a 100644 --- a/apps/wizard/importpage.hpp +++ b/apps/wizard/importpage.hpp @@ -1,8 +1,6 @@ #ifndef IMPORTPAGE_HPP #define IMPORTPAGE_HPP -#include - #include "ui_importpage.h" namespace Wizard diff --git a/apps/wizard/inisettings.cpp b/apps/wizard/inisettings.cpp index d4a63c676..e9cec12f5 100644 --- a/apps/wizard/inisettings.cpp +++ b/apps/wizard/inisettings.cpp @@ -1,7 +1,5 @@ #include "inisettings.hpp" -#include - #include #include #include diff --git a/apps/wizard/installationtargetpage.hpp b/apps/wizard/installationtargetpage.hpp index 7cba29573..1f1e95228 100644 --- a/apps/wizard/installationtargetpage.hpp +++ b/apps/wizard/installationtargetpage.hpp @@ -1,8 +1,6 @@ #ifndef INSTALLATIONTARGETPAGE_HPP #define INSTALLATIONTARGETPAGE_HPP -#include - #include "ui_installationtargetpage.h" namespace Files diff --git a/apps/wizard/intropage.hpp b/apps/wizard/intropage.hpp index c8cd69016..3fae6a747 100644 --- a/apps/wizard/intropage.hpp +++ b/apps/wizard/intropage.hpp @@ -1,8 +1,6 @@ #ifndef INTROPAGE_HPP #define INTROPAGE_HPP -#include - #include "ui_intropage.h" namespace Wizard diff --git a/apps/wizard/languageselectionpage.hpp b/apps/wizard/languageselectionpage.hpp index cc86ba9b3..81f8faa48 100644 --- a/apps/wizard/languageselectionpage.hpp +++ b/apps/wizard/languageselectionpage.hpp @@ -1,8 +1,6 @@ #ifndef LANGUAGESELECTIONPAGE_HPP #define LANGUAGESELECTIONPAGE_HPP -#include - #include "ui_languageselectionpage.h" namespace Wizard diff --git a/apps/wizard/main.cpp b/apps/wizard/main.cpp index e3624742a..67410df14 100644 --- a/apps/wizard/main.cpp +++ b/apps/wizard/main.cpp @@ -1,7 +1,5 @@ #include -#include #include -#include #include "mainwizard.hpp" diff --git a/apps/wizard/mainwizard.hpp b/apps/wizard/mainwizard.hpp index 4c221635e..249ae1cf9 100644 --- a/apps/wizard/mainwizard.hpp +++ b/apps/wizard/mainwizard.hpp @@ -1,9 +1,7 @@ #ifndef MAINWIZARD_HPP #define MAINWIZARD_HPP -#include #include -#include #include diff --git a/apps/wizard/methodselectionpage.hpp b/apps/wizard/methodselectionpage.hpp index 57d551d27..e844024b4 100644 --- a/apps/wizard/methodselectionpage.hpp +++ b/apps/wizard/methodselectionpage.hpp @@ -1,8 +1,6 @@ #ifndef METHODSELECTIONPAGE_HPP #define METHODSELECTIONPAGE_HPP -#include - #include "ui_methodselectionpage.h" namespace Wizard diff --git a/apps/wizard/utils/componentlistwidget.cpp b/apps/wizard/utils/componentlistwidget.cpp index 6a5d019b5..ff37e5d8c 100644 --- a/apps/wizard/utils/componentlistwidget.cpp +++ b/apps/wizard/utils/componentlistwidget.cpp @@ -1,7 +1,6 @@ #include "componentlistwidget.hpp" #include -#include ComponentListWidget::ComponentListWidget(QWidget *parent) : QListWidget(parent) diff --git a/appveyor.yml b/appveyor.yml index ed6f727be..e2c13ed94 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -50,7 +50,7 @@ install: before_build: - cmd: git submodule update --init --recursive - - cmd: sh %APPVEYOR_BUILD_FOLDER%\CI\before_script.msvc.sh -c %configuration% -p %PLATFORM% -v %msvc% -V -i %APPVEYOR_BUILD_FOLDER%\install -D + - cmd: sh %APPVEYOR_BUILD_FOLDER%\CI\before_script.msvc.sh -c %configuration% -p %PLATFORM% -v %msvc% -V -i %APPVEYOR_BUILD_FOLDER%\install build_script: - cmd: if %PLATFORM%==Win32 set build=MSVC%msvc%_32 diff --git a/cmake/CheckBulletPrecision.cmake b/cmake/CheckBulletPrecision.cmake new file mode 100644 index 000000000..77409f932 --- /dev/null +++ b/cmake/CheckBulletPrecision.cmake @@ -0,0 +1,26 @@ +set(TMP_ROOT ${CMAKE_BINARY_DIR}/try-compile) +file(MAKE_DIRECTORY ${TMP_ROOT}) + +file(WRITE ${TMP_ROOT}/checkbullet.cpp +" +#include +int main(int argc, char** argv) +{ + btSphereShape shape(1.0); + btScalar mass(1.0); + btVector3 inertia; + shape.calculateLocalInertia(mass, inertia); + return 0; +} +") + +message(STATUS "Checking if Bullet uses double precision") + +try_compile(RESULT_VAR + ${TMP_ROOT}/temp + ${TMP_ROOT}/checkbullet.cpp + COMPILE_DEFINITIONS "-DBT_USE_DOUBLE_PRECISION" + LINK_LIBRARIES ${BULLET_LIBRARIES} + CMAKE_FLAGS "-DINCLUDE_DIRECTORIES=${BULLET_INCLUDE_DIRS}" + ) +set(HAS_DOUBLE_PRECISION_BULLET ${RESULT_VAR}) diff --git a/cmake/OSIdentity.cmake b/cmake/OSIdentity.cmake deleted file mode 100644 index 40b7b2089..000000000 --- a/cmake/OSIdentity.cmake +++ /dev/null @@ -1,67 +0,0 @@ -if (UNIX) - - if (APPLE) - - set(CMAKE_OS_NAME "OSX" CACHE STRING "Operating system name" FORCE) - - else (APPLE) - - ## Check for Debian GNU/Linux ________________ - - find_file(DEBIAN_FOUND debian_version debconf.conf - PATHS /etc - ) - if (DEBIAN_FOUND) - set(CMAKE_OS_NAME "Debian" CACHE STRING "Operating system name" FORCE) - endif (DEBIAN_FOUND) - - ## Check for Fedora _________________________ - - find_file(FEDORA_FOUND fedora-release - PATHS /etc - ) - if (FEDORA_FOUND) - set(CMAKE_OS_NAME "Fedora" CACHE STRING "Operating system name" FORCE) - endif (FEDORA_FOUND) - - ## Check for RedHat _________________________ - - find_file(REDHAT_FOUND redhat-release inittab.RH - PATHS /etc - ) - if (REDHAT_FOUND) - set(CMAKE_OS_NAME "RedHat" CACHE STRING "Operating system name" FORCE) - endif (REDHAT_FOUND) - - ## Extra check for Ubuntu ____________________ - - if (DEBIAN_FOUND) - - ## At its core Ubuntu is a Debian system, with - ## a slightly altered configuration; hence from - ## a first superficial inspection a system will - ## be considered as Debian, which signifies an - ## extra check is required. - - find_file(UBUNTU_EXTRA legal issue - PATHS /etc - ) - - if (UBUNTU_EXTRA) - ## Scan contents of file - file(STRINGS ${UBUNTU_EXTRA} UBUNTU_FOUND - REGEX Ubuntu - ) - ## Check result of string search - if (UBUNTU_FOUND) - set(CMAKE_OS_NAME "Ubuntu" CACHE STRING "Operating system name" FORCE) - set(DEBIAN_FOUND FALSE) - endif (UBUNTU_FOUND) - - endif (UBUNTU_EXTRA) - - endif (DEBIAN_FOUND) - - endif (APPLE) - -endif (UNIX) diff --git a/cmake/OpenMWMacros.cmake b/cmake/OpenMWMacros.cmake index b86c32a8f..01eb571fc 100644 --- a/cmake/OpenMWMacros.cmake +++ b/cmake/OpenMWMacros.cmake @@ -199,6 +199,18 @@ macro (configure_resource_file source_path destination_dir_base dest_path_relati endif (multi_config) endmacro (configure_resource_file) +macro (pack_resource_file source_path destination_dir_base dest_path_relative) + get_generator_is_multi_config(multi_config) + if (multi_config) + foreach(cfgtype ${CMAKE_CONFIGURATION_TYPES}) + execute_process(COMMAND ${CMAKE_COMMAND} "-DINPUT_FILE=${source_path}" "-DOUTPUT_FILE=${destination_dir_base}/${cfgtype}/${dest_path_relative}" -P "${CMAKE_SOURCE_DIR}/cmake/base64.cmake") + endforeach(cfgtype) + else (multi_config) + execute_process(COMMAND ${CMAKE_COMMAND} "-DINPUT_FILE=${source_path}" "-DOUTPUT_FILE=${destination_dir_base}/${dest_path_relative}" -P "${CMAKE_SOURCE_DIR}/cmake/base64.cmake") + endif (multi_config) + set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${source_path}") +endmacro (pack_resource_file) + macro (copy_all_resource_files source_dir destination_dir_base destination_dir_relative files) foreach (f ${files}) get_filename_component(filename ${f} NAME) diff --git a/cmake/base64.cmake b/cmake/base64.cmake new file mode 100644 index 000000000..7931758bb --- /dev/null +++ b/cmake/base64.cmake @@ -0,0 +1,74 @@ +# math(EXPR "...") can't parse hex until 3.13 +cmake_minimum_required(VERSION 3.13) + +if (NOT DEFINED INPUT_FILE) + message(STATUS "Usage: cmake -DINPUT_FILE=\"infile.ext\" -DOUTPUT_FILE=\"out.txt\" -P base64.cmake") + message(FATAL_ERROR "INPUT_FILE not specified") +endif() + +if (NOT DEFINED OUTPUT_FILE) + message(STATUS "Usage: cmake -DINPUT_FILE=\"infile.ext\" -DOUTPUT_FILE=\"out.txt\" -P base64.cmake") + message(FATAL_ERROR "OUTPUT_FILE not specified") +endif() + +if (NOT EXISTS ${INPUT_FILE}) + message(FATAL_ERROR "INPUT_FILE ${INPUT_FILE} does not exist") +endif() + +set(lut "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/") + +file(READ "${INPUT_FILE}" hexContent HEX) + +set(base64Content "") +while(TRUE) + string(LENGTH "${hexContent}" tailLength) + if (tailLength LESS 1) + break() + endif() + + string(SUBSTRING "${hexContent}" 0 6 head) + # base64 works on three-byte chunks. Pad. + string(LENGTH "${head}" headLength) + if (headLength LESS 6) + set(hexContent "") + math(EXPR padSize "6 - ${headLength}") + set(pad "") + foreach(i RANGE 1 ${padSize}) + string(APPEND pad "0") + endforeach() + string(APPEND head "${pad}") + else() + string(SUBSTRING "${hexContent}" 6 -1 hexContent) + set(padSize 0) + endif() + + # get six-bit slices + math(EXPR first "0x${head} >> 18") + math(EXPR second "(0x${head} & 0x3F000) >> 12") + math(EXPR third "(0x${head} & 0xFC0) >> 6") + math(EXPR fourth "0x${head} & 0x3F") + + # first two characters are always needed to represent the first byte + string(SUBSTRING "${lut}" ${first} 1 char) + string(APPEND base64Content "${char}") + string(SUBSTRING "${lut}" ${second} 1 char) + string(APPEND base64Content "${char}") + + # if there's no second byte, pad with = + if (NOT padSize EQUAL 4) + string(SUBSTRING "${lut}" ${third} 1 char) + string(APPEND base64Content "${char}") + else() + string(APPEND base64Content "=") + endif() + + # if there's no third byte, pad with = + if (padSize EQUAL 0) + string(SUBSTRING "${lut}" ${fourth} 1 char) + string(APPEND base64Content "${char}") + else() + string(APPEND base64Content "=") + endif() +endwhile() + +file(WRITE "${OUTPUT_FILE}" "${base64Content}") \ No newline at end of file diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index d5bd9f80f..2d7907ef6 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -173,7 +173,6 @@ add_component_dir(detournavigator navmeshmanager navigatorimpl asyncnavmeshupdater - chunkytrimesh recastmesh tilecachedrecastmeshmanager recastmeshobject @@ -184,6 +183,7 @@ add_component_dir(detournavigator raycast navmeshtileview oscillatingrecastmeshobject + offmeshconnectionsmanager ) set (ESM_UI ${CMAKE_SOURCE_DIR}/files/ui/contentselector.ui @@ -252,13 +252,11 @@ target_link_libraries(components RecastNavigation::DebugUtils RecastNavigation::Detour RecastNavigation::Recast + + Base64 ) -if (BULLET_USE_DOUBLES AND (UBUNTU_FOUND OR DEBIAN_FOUND) AND OPENMW_USE_SYSTEM_BULLET) - target_link_libraries(components BulletCollision-float64 LinearMath-float64) -else() - target_link_libraries(components ${BULLET_LIBRARIES}) -endif() +target_link_libraries(components ${BULLET_LIBRARIES}) if (WIN32) target_link_libraries(components @@ -292,6 +290,4 @@ endif() # Make the variable accessible for other subdirectories set(COMPONENT_FILES ${COMPONENT_FILES} PARENT_SCOPE) -if (BULLET_USE_DOUBLES) - target_compile_definitions(components PUBLIC BT_USE_DOUBLE_PRECISION) -endif() +target_compile_definitions(components PUBLIC BT_USE_DOUBLE_PRECISION) diff --git a/components/bsa/bsa_file.cpp b/components/bsa/bsa_file.cpp index ec455ea1f..129c2bf45 100644 --- a/components/bsa/bsa_file.cpp +++ b/components/bsa/bsa_file.cpp @@ -43,7 +43,7 @@ void BSAFile::fail(const std::string &msg) BSAFile::Hash getHash(const std::string& name) { BSAFile::Hash hash; - unsigned l = (name.size() >> 1); + unsigned l = (static_cast(name.size()) >> 1); unsigned sum, off, temp, i, n; for (sum = off = i = 0; i < l; i++) { @@ -163,7 +163,7 @@ void BSAFile::readHeader() { FileStruct &fs = mFiles[i]; fs.fileSize = offsets[i*2]; - fs.offset = offsets[i*2+1] + fileDataOffset; + fs.offset = static_cast(offsets[i*2+1] + fileDataOffset); auto namesOffset = offsets[2*filenum+i]; fs.setNameInfos(namesOffset, &mStringBuf); fs.hash = hashes[i]; @@ -182,8 +182,6 @@ void BSAFile::readHeader() if(fs.offset + fs.fileSize > fsize) fail("Archive contains offsets outside itself"); - // Add the file name to the lookup - mLookup[fs.name()] = i; } mStringBuf.resize(endOfNameBuffer); @@ -191,6 +189,13 @@ void BSAFile::readHeader() return left.offset < right.offset; }); + for (size_t i = 0; i < filenum; i++) + { + FileStruct& fs = mFiles[i]; + // Add the file name to the lookup + mLookup[fs.name()] = i; + } + mIsLoaded = true; } @@ -203,11 +208,11 @@ void Bsa::BSAFile::writeHeader() uint32_t head[3]; head[0] = 0x100; auto fileDataOffset = mFiles.empty() ? 12 : mFiles.front().offset; - head[1] = fileDataOffset - 12 - 8*mFiles.size(); + head[1] = static_cast(fileDataOffset - 12 - 8*mFiles.size()); output.seekp(0, std::ios_base::end); - head[2] = mFiles.size(); + head[2] = static_cast(mFiles.size()); output.seekp(0); output.write(reinterpret_cast(head), 12); @@ -239,14 +244,17 @@ int BSAFile::getIndex(const char *str) const if(it == mLookup.end()) return -1; - int res = it->second; - assert(res >= 0 && (size_t)res < mFiles.size()); - return res; + size_t res = it->second; + assert(res < mFiles.size()); + return static_cast(res); } /// Open an archive file. void BSAFile::open(const std::string &file) { + if (mIsLoaded) + close(); + mFilename = file; if(boost::filesystem::exists(file)) readHeader(); @@ -254,16 +262,20 @@ void BSAFile::open(const std::string &file) { { boost::filesystem::fstream(mFilename, std::ios::binary | std::ios::out); } writeHeader(); + mIsLoaded = true; } } /// Close the archive, write the updated headers to the file void Bsa::BSAFile::close() { - if (!mHasChanged) - return; + if (mHasChanged) + writeHeader(); - writeHeader(); + mFiles.clear(); + mStringBuf.clear(); + mLookup.clear(); + mIsLoaded = false; } Files::IStreamPtr BSAFile::getFile(const char *file) @@ -285,6 +297,8 @@ Files::IStreamPtr BSAFile::getFile(const FileStruct *file) void Bsa::BSAFile::addFile(const std::string& filename, std::istream& file) { + if (!mIsLoaded) + fail("Unable to add file " + filename + " the archive is not opened"); namespace bfs = boost::filesystem; auto newStartOfDataBuffer = 12 + (12 + 8) * (mFiles.size() + 1) + mStringBuf.size() + filename.size() + 1; @@ -295,12 +309,12 @@ void Bsa::BSAFile::addFile(const std::string& filename, std::istream& file) FileStruct newFile; file.seekg(0, std::ios::end); - newFile.fileSize = file.tellg(); + newFile.fileSize = static_cast(file.tellg()); newFile.setNameInfos(mStringBuf.size(), &mStringBuf); newFile.hash = getHash(filename); if(mFiles.empty()) - newFile.offset = newStartOfDataBuffer; + newFile.offset = static_cast(newStartOfDataBuffer); else { std::vector buffer; @@ -312,7 +326,7 @@ void Bsa::BSAFile::addFile(const std::string& filename, std::istream& file) stream.read(buffer.data(), firstFile.fileSize); stream.seekp(0, std::ios::end); - firstFile.offset = stream.tellp(); + firstFile.offset = static_cast(stream.tellp()); stream.write(buffer.data(), firstFile.fileSize); @@ -320,7 +334,7 @@ void Bsa::BSAFile::addFile(const std::string& filename, std::istream& file) std::rotate(mFiles.begin(), mFiles.begin() + 1, mFiles.end()); } stream.seekp(0, std::ios::end); - newFile.offset = stream.tellp(); + newFile.offset = static_cast(stream.tellp()); } mStringBuf.insert(mStringBuf.end(), filename.begin(), filename.end()); diff --git a/components/bsa/bsa_file.hpp b/components/bsa/bsa_file.hpp index fa6e5fc1c..f9b4d4fa3 100644 --- a/components/bsa/bsa_file.hpp +++ b/components/bsa/bsa_file.hpp @@ -58,7 +58,7 @@ public: void setNameInfos(size_t index, std::vector* stringBuf ) { - namesOffset = index; + namesOffset = static_cast(index); namesBuffer = stringBuf; } @@ -102,7 +102,7 @@ protected: the files[] vector above. The iltstr ensures that file name checks are case insensitive. */ - typedef std::map Lookup; + typedef std::map Lookup; Lookup mLookup; /// Error handling diff --git a/components/bsa/compressedbsafile.cpp b/components/bsa/compressedbsafile.cpp index 0f3a26e15..09a05d2f5 100644 --- a/components/bsa/compressedbsafile.cpp +++ b/components/bsa/compressedbsafile.cpp @@ -35,7 +35,16 @@ #include #include -#include + +#if defined(_MSC_VER) + #pragma warning (push) + #pragma warning (disable : 4706) + #include + #pragma warning (pop) +#else + #include +#endif + #include #include diff --git a/components/compiler/controlparser.cpp b/components/compiler/controlparser.cpp index ec69fffa2..a5a68edbd 100644 --- a/components/compiler/controlparser.cpp +++ b/components/compiler/controlparser.cpp @@ -39,14 +39,14 @@ namespace Compiler Codes block; if (iter!=mIfCode.rbegin()) - Generator::jump (iter->second, codes.size()+1); + Generator::jump (iter->second, static_cast(codes.size()+1)); if (!iter->first.empty()) { // if or elseif std::copy (iter->first.begin(), iter->first.end(), std::back_inserter (block)); - Generator::jumpOnZero (block, iter->second.size()+1); + Generator::jumpOnZero (block, static_cast(iter->second.size()+1)); } std::copy (iter->second.begin(), iter->second.end(), @@ -113,7 +113,7 @@ namespace Compiler Codes skip; - Generator::jumpOnZero (skip, mCodeBlock.size()+loop.size()+1); + Generator::jumpOnZero (skip, static_cast (mCodeBlock.size()+loop.size()+1)); std::copy (skip.begin(), skip.end(), std::back_inserter (mCode)); diff --git a/components/compiler/literals.cpp b/components/compiler/literals.cpp index 774ca4ca7..a40ff2a02 100644 --- a/components/compiler/literals.cpp +++ b/components/compiler/literals.cpp @@ -6,12 +6,12 @@ namespace Compiler { int Literals::getIntegerSize() const { - return mIntegers.size() * sizeof (Interpreter::Type_Integer); + return static_cast(mIntegers.size() * sizeof (Interpreter::Type_Integer)); } int Literals::getFloatSize() const { - return mFloats.size() * sizeof (Interpreter::Type_Float); + return static_cast(mFloats.size() * sizeof (Interpreter::Type_Float)); } int Literals::getStringSize() const @@ -41,11 +41,11 @@ namespace Compiler code.resize (size+stringBlockSize/4); - int offset = 0; + size_t offset = 0; for (const auto & mString : mStrings) { - int stringSize = mString.size()+1; + size_t stringSize = mString.size()+1; std::copy (mString.c_str(), mString.c_str()+stringSize, reinterpret_cast (&code[size]) + offset); diff --git a/components/compiler/locals.cpp b/components/compiler/locals.cpp index 9b233b8f5..f31acb1a8 100644 --- a/components/compiler/locals.cpp +++ b/components/compiler/locals.cpp @@ -30,7 +30,7 @@ namespace Compiler if (iter==collection.end()) return -1; - return iter-collection.begin(); + return static_cast(iter-collection.begin()); } bool Locals::search (char type, const std::string& name) const diff --git a/components/compiler/scanner.hpp b/components/compiler/scanner.hpp index 9c7bd656e..fd7de5b6b 100644 --- a/components/compiler/scanner.hpp +++ b/components/compiler/scanner.hpp @@ -103,7 +103,7 @@ namespace Compiler { blank(); - char ch = in.peek(); + char ch = static_cast(in.peek()); if (!in.good()) return false; @@ -130,7 +130,7 @@ namespace Compiler { std::streampos p_orig = in.tellg(); - char ch = in.peek(); + char ch = static_cast(in.peek()); if (!in.good()) return false; @@ -156,7 +156,7 @@ namespace Compiler void blank() { - std::fill(mData, mData + sizeof(mData), 0); + std::fill(std::begin(mData), std::end(mData), '\0'); mLength = -1; } diff --git a/components/crashcatcher/windows_crashcatcher.cpp b/components/crashcatcher/windows_crashcatcher.cpp index 33ec319aa..39ac86d7b 100644 --- a/components/crashcatcher/windows_crashcatcher.cpp +++ b/components/crashcatcher/windows_crashcatcher.cpp @@ -130,13 +130,13 @@ namespace Crash DWORD copied = 0; do { executablePath.resize(executablePath.size() + MAX_PATH); - copied = GetModuleFileNameW(nullptr, executablePath.data(), executablePath.size()); + copied = GetModuleFileNameW(nullptr, executablePath.data(), static_cast(executablePath.size())); } while (copied >= executablePath.size()); executablePath.resize(copied); memset(mShm->mStartup.mLogFilePath, 0, sizeof(mShm->mStartup.mLogFilePath)); - int length = crashLogPath.length(); - if (length > MAX_LONG_PATH) length = MAX_LONG_PATH; + size_t length = crashLogPath.length(); + if (length >= MAX_LONG_PATH) length = MAX_LONG_PATH - 1; strncpy(mShm->mStartup.mLogFilePath, crashLogPath.c_str(), length); mShm->mStartup.mLogFilePath[length] = '\0'; diff --git a/components/debug/gldebug.cpp b/components/debug/gldebug.cpp index 37829a8c1..ccf49edd4 100644 --- a/components/debug/gldebug.cpp +++ b/components/debug/gldebug.cpp @@ -179,7 +179,7 @@ namespace Debug if (str.length() == 0) return true; - return str.find("OFF") == std::string::npos && str.find("0") == std::string::npos && str.find("NO") == std::string::npos; + return str.find("OFF") == std::string::npos && str.find('0') == std::string::npos && str.find("NO") == std::string::npos; } DebugGroup::DebugGroup(const std::string & message, GLuint id) diff --git a/components/detournavigator/asyncnavmeshupdater.cpp b/components/detournavigator/asyncnavmeshupdater.cpp index 4855cf0ad..11de60745 100644 --- a/components/detournavigator/asyncnavmeshupdater.cpp +++ b/components/detournavigator/asyncnavmeshupdater.cpp @@ -6,10 +6,13 @@ #include #include +#include #include +#include #include +#include namespace { @@ -20,6 +23,18 @@ namespace { return std::abs(lhs.x() - rhs.x()) + std::abs(lhs.y() - rhs.y()); } + + int getMinDistanceTo(const TilePosition& position, int maxDistance, + const std::map>& tilesPerHalfExtents, + const std::set>& presentTiles) + { + int result = maxDistance; + for (const auto& [halfExtents, tiles] : tilesPerHalfExtents) + for (const TilePosition& tile : tiles) + if (presentTiles.find(std::make_tuple(halfExtents, tile)) == presentTiles.end()) + result = std::min(result, getManhattanDistance(position, tile)); + return result; + } } namespace DetourNavigator @@ -77,13 +92,22 @@ namespace DetourNavigator const SharedNavMeshCacheItem& navMeshCacheItem, const TilePosition& playerTile, const std::map& changedTiles) { - *mPlayerTile.lock() = playerTile; + bool playerTileChanged = false; + { + auto locked = mPlayerTile.lock(); + playerTileChanged = *locked != playerTile; + *locked = playerTile; + } - if (changedTiles.empty()) + if (!playerTileChanged && changedTiles.empty()) return; const std::lock_guard lock(mMutex); + if (playerTileChanged) + for (auto& job : mJobs) + job.mDistanceToPlayer = getManhattanDistance(job.mChangedTile, playerTile); + for (const auto& changedTile : changedTiles) { if (mPushed[agentHalfExtents].insert(changedTile.first).second) @@ -101,21 +125,100 @@ namespace DetourNavigator ? mLastUpdates[job.mAgentHalfExtents][job.mChangedTile] + mSettings.get().mMinUpdateInterval : std::chrono::steady_clock::time_point(); - mJobs.push(std::move(job)); + if (playerTileChanged) + { + mJobs.push_back(std::move(job)); + } + else + { + const auto it = std::upper_bound(mJobs.begin(), mJobs.end(), job); + mJobs.insert(it, std::move(job)); + } } } + if (playerTileChanged) + std::sort(mJobs.begin(), mJobs.end()); + Log(Debug::Debug) << "Posted " << mJobs.size() << " navigator jobs"; if (!mJobs.empty()) mHasJob.notify_all(); } - void AsyncNavMeshUpdater::wait() + void AsyncNavMeshUpdater::wait(Loading::Listener& listener, WaitConditionType waitConditionType) + { + if (mSettings.get().mWaitUntilMinDistanceToPlayer == 0) + return; + listener.setLabel("Building navigation mesh"); + const std::size_t initialJobsLeft = getTotalJobs(); + std::size_t maxProgress = initialJobsLeft + mThreads.size(); + listener.setProgressRange(maxProgress); + switch (waitConditionType) + { + case WaitConditionType::requiredTilesPresent: + { + const int minDistanceToPlayer = waitUntilJobsDoneForNotPresentTiles(initialJobsLeft, maxProgress, listener); + if (minDistanceToPlayer < mSettings.get().mWaitUntilMinDistanceToPlayer) + { + mProcessingTiles.wait(mProcessed, [] (const auto& v) { return v.empty(); }); + listener.setProgress(maxProgress); + } + break; + } + case WaitConditionType::allJobsDone: + waitUntilAllJobsDone(); + listener.setProgress(maxProgress); + break; + } + } + + int AsyncNavMeshUpdater::waitUntilJobsDoneForNotPresentTiles(const std::size_t initialJobsLeft, std::size_t& maxProgress, Loading::Listener& listener) + { + std::size_t prevJobsLeft = initialJobsLeft; + std::size_t jobsDone = 0; + std::size_t jobsLeft = 0; + const int maxDistanceToPlayer = mSettings.get().mWaitUntilMinDistanceToPlayer; + const TilePosition playerPosition = *mPlayerTile.lockConst(); + int minDistanceToPlayer = 0; + const auto isDone = [&] + { + jobsLeft = mJobs.size() + getTotalThreadJobsUnsafe(); + if (jobsLeft == 0) + { + minDistanceToPlayer = 0; + return true; + } + minDistanceToPlayer = getMinDistanceTo(playerPosition, maxDistanceToPlayer, mPushed, mPresentTiles); + for (const auto& [threadId, queue] : mThreadsQueues) + minDistanceToPlayer = getMinDistanceTo(playerPosition, minDistanceToPlayer, queue.mPushed, mPresentTiles); + return minDistanceToPlayer >= maxDistanceToPlayer; + }; + std::unique_lock lock(mMutex); + while (!mDone.wait_for(lock, std::chrono::milliseconds(250), isDone)) + { + if (maxProgress < jobsLeft) + { + maxProgress = jobsLeft + mThreads.size(); + listener.setProgressRange(maxProgress); + listener.setProgress(jobsDone); + } + else if (jobsLeft < prevJobsLeft) + { + const std::size_t newJobsDone = prevJobsLeft - jobsLeft; + jobsDone += newJobsDone; + prevJobsLeft = jobsLeft; + listener.increaseProgress(newJobsDone); + } + } + return minDistanceToPlayer; + } + + void AsyncNavMeshUpdater::waitUntilAllJobsDone() { { std::unique_lock lock(mMutex); - mDone.wait(lock, [&] { return mJobs.empty() && getTotalThreadJobsUnsafe() == 0; }); + mDone.wait(lock, [this] { return mJobs.size() + getTotalThreadJobsUnsafe() == 0; }); } mProcessingTiles.wait(mProcessed, [] (const auto& v) { return v.empty(); }); } @@ -194,6 +297,17 @@ namespace DetourNavigator navMeshVersion); } + if (status == UpdateNavMeshStatus::removed || status == UpdateNavMeshStatus::lost) + { + const std::scoped_lock lock(mMutex); + mPresentTiles.erase(std::make_tuple(job.mAgentHalfExtents, job.mChangedTile)); + } + else if (isSuccess(status) && status != UpdateNavMeshStatus::ignored) + { + const std::scoped_lock lock(mMutex); + mPresentTiles.insert(std::make_tuple(job.mAgentHalfExtents, job.mChangedTile)); + } + const auto finish = std::chrono::steady_clock::now(); writeDebugFiles(job, recastMesh.get()); @@ -224,7 +338,7 @@ namespace DetourNavigator while (true) { const auto hasJob = [&] { - return (!mJobs.empty() && mJobs.top().mProcessTime <= std::chrono::steady_clock::now()) + return (!mJobs.empty() && mJobs.front().mProcessTime <= std::chrono::steady_clock::now()) || !threadQueue.mJobs.empty(); }; @@ -259,11 +373,11 @@ namespace DetourNavigator { const auto now = std::chrono::steady_clock::now(); - if (jobs.top().mProcessTime > now) + if (jobs.front().mProcessTime > now) return {}; - Job job = std::move(jobs.top()); - jobs.pop(); + Job job = jobs.front(); + jobs.pop_front(); if (changeLastUpdate && job.mChangeType == ChangeType::update) mLastUpdates[job.mAgentHalfExtents][job.mChangedTile] = now; @@ -273,7 +387,7 @@ namespace DetourNavigator if (it->second.empty()) pushed.erase(it); - return {std::move(job)}; + return job; } void AsyncNavMeshUpdater::writeDebugFiles(const Job& job, const RecastMesh* recastMesh) const @@ -317,7 +431,7 @@ namespace DetourNavigator if (mPushed[job.mAgentHalfExtents].insert(job.mChangedTile).second) { ++job.mTryNumber; - mJobs.push(std::move(job)); + mJobs.push_back(std::move(job)); mHasJob.notify_all(); } } @@ -326,7 +440,7 @@ namespace DetourNavigator { if (queue.mPushed[job.mAgentHalfExtents].insert(job.mChangedTile).second) { - queue.mJobs.push(std::move(job)); + queue.mJobs.push_back(std::move(job)); mHasJob.notify_all(); } } @@ -381,6 +495,12 @@ namespace DetourNavigator mProcessed.notify_all(); } + std::size_t AsyncNavMeshUpdater::getTotalJobs() const + { + const std::scoped_lock lock(mMutex); + return mJobs.size() + getTotalThreadJobsUnsafe(); + } + std::size_t AsyncNavMeshUpdater::getTotalThreadJobsUnsafe() const { return std::accumulate(mThreadsQueues.begin(), mThreadsQueues.end(), std::size_t(0), diff --git a/components/detournavigator/asyncnavmeshupdater.hpp b/components/detournavigator/asyncnavmeshupdater.hpp index 53e7fd7c1..e8b2611e9 100644 --- a/components/detournavigator/asyncnavmeshupdater.hpp +++ b/components/detournavigator/asyncnavmeshupdater.hpp @@ -6,6 +6,7 @@ #include "tilecachedrecastmeshmanager.hpp" #include "tileposition.hpp" #include "navmeshtilescache.hpp" +#include "waitconditiontype.hpp" #include @@ -14,12 +15,18 @@ #include #include #include -#include +#include #include #include +#include class dtNavMesh; +namespace Loading +{ + class Listener; +} + namespace DetourNavigator { enum class ChangeType @@ -55,7 +62,7 @@ namespace DetourNavigator void post(const osg::Vec3f& agentHalfExtents, const SharedNavMeshCacheItem& mNavMeshCacheItem, const TilePosition& playerTile, const std::map& changedTiles); - void wait(); + void wait(Loading::Listener& listener, WaitConditionType waitConditionType); void reportStats(unsigned int frameNumber, osg::Stats& stats) const; @@ -78,11 +85,11 @@ namespace DetourNavigator friend inline bool operator <(const Job& lhs, const Job& rhs) { - return lhs.getPriority() > rhs.getPriority(); + return lhs.getPriority() < rhs.getPriority(); } }; - using Jobs = std::priority_queue>; + using Jobs = std::deque; using Pushed = std::map>; struct Queue @@ -108,6 +115,7 @@ namespace DetourNavigator NavMeshTilesCache mNavMeshTilesCache; Misc::ScopeGuarded>> mProcessingTiles; std::map> mLastUpdates; + std::set> mPresentTiles; std::map mThreadsQueues; std::vector mThreads; @@ -131,9 +139,15 @@ namespace DetourNavigator void unlockTile(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile); + inline std::size_t getTotalJobs() const; + inline std::size_t getTotalThreadJobsUnsafe() const; void cleanupLastUpdates(); + + int waitUntilJobsDoneForNotPresentTiles(const std::size_t initialJobsLeft, std::size_t& maxJobsLeft, Loading::Listener& listener); + + void waitUntilAllJobsDone(); }; } diff --git a/components/detournavigator/cachedrecastmeshmanager.cpp b/components/detournavigator/cachedrecastmeshmanager.cpp index 22b047fbd..b8c1ed72f 100644 --- a/components/detournavigator/cachedrecastmeshmanager.cpp +++ b/components/detournavigator/cachedrecastmeshmanager.cpp @@ -8,12 +8,12 @@ namespace DetourNavigator : mImpl(settings, bounds, generation) {} - bool CachedRecastMeshManager::addObject(const ObjectId id, const btCollisionShape& shape, + bool CachedRecastMeshManager::addObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform, const AreaType areaType) { if (!mImpl.addObject(id, shape, transform, areaType)) return false; - mCached.reset(); + mCached.lock()->reset(); return true; } @@ -21,7 +21,7 @@ namespace DetourNavigator { if (!mImpl.updateObject(id, transform, areaType)) return false; - mCached.reset(); + mCached.lock()->reset(); return true; } @@ -29,7 +29,7 @@ namespace DetourNavigator { const auto object = mImpl.removeObject(id); if (object) - mCached.reset(); + mCached.lock()->reset(); return object; } @@ -38,7 +38,7 @@ namespace DetourNavigator { if (!mImpl.addWater(cellPosition, cellSize, transform)) return false; - mCached.reset(); + mCached.lock()->reset(); return true; } @@ -46,15 +46,18 @@ namespace DetourNavigator { const auto water = mImpl.removeWater(cellPosition); if (water) - mCached.reset(); + mCached.lock()->reset(); return water; } std::shared_ptr CachedRecastMeshManager::getMesh() { - if (!mCached) - mCached = mImpl.getMesh(); - return mCached; + std::shared_ptr cached = *mCached.lock(); + if (cached != nullptr) + return cached; + cached = mImpl.getMesh(); + *mCached.lock() = cached; + return cached; } bool CachedRecastMeshManager::isEmpty() const @@ -66,4 +69,9 @@ namespace DetourNavigator { mImpl.reportNavMeshChange(recastMeshVersion, navMeshVersion); } + + Version CachedRecastMeshManager::getVersion() const + { + return mImpl.getVersion(); + } } diff --git a/components/detournavigator/cachedrecastmeshmanager.hpp b/components/detournavigator/cachedrecastmeshmanager.hpp index a19f017a4..f2cf54c92 100644 --- a/components/detournavigator/cachedrecastmeshmanager.hpp +++ b/components/detournavigator/cachedrecastmeshmanager.hpp @@ -4,6 +4,8 @@ #include "recastmeshmanager.hpp" #include "version.hpp" +#include + namespace DetourNavigator { class CachedRecastMeshManager @@ -11,7 +13,7 @@ namespace DetourNavigator public: CachedRecastMeshManager(const Settings& settings, const TileBounds& bounds, std::size_t generation); - bool addObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform, + bool addObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform, const AreaType areaType); bool updateObject(const ObjectId id, const btTransform& transform, const AreaType areaType); @@ -28,9 +30,11 @@ namespace DetourNavigator void reportNavMeshChange(Version recastMeshVersion, Version navMeshVersion); + Version getVersion() const; + private: RecastMeshManager mImpl; - std::shared_ptr mCached; + Misc::ScopeGuarded> mCached; }; } diff --git a/components/detournavigator/chunkytrimesh.cpp b/components/detournavigator/chunkytrimesh.cpp deleted file mode 100644 index ffd39d0a9..000000000 --- a/components/detournavigator/chunkytrimesh.cpp +++ /dev/null @@ -1,179 +0,0 @@ -#include "chunkytrimesh.hpp" -#include "exceptions.hpp" - -#include - -#include - -namespace DetourNavigator -{ - namespace - { - struct BoundsItem - { - Rect mBounds; - std::ptrdiff_t mOffset; - unsigned char mAreaTypes; - }; - - template - struct LessBoundsItem - { - bool operator ()(const BoundsItem& lhs, const BoundsItem& rhs) const - { - return lhs.mBounds.mMinBound[axis] < rhs.mBounds.mMinBound[axis]; - } - }; - - void calcExtends(const std::vector& items, const std::size_t imin, const std::size_t imax, - Rect& bounds) - { - bounds = items[imin].mBounds; - - std::for_each( - items.begin() + static_cast(imin) + 1, - items.begin() + static_cast(imax), - [&] (const BoundsItem& item) - { - for (int i = 0; i < 2; ++i) - { - bounds.mMinBound[i] = std::min(bounds.mMinBound[i], item.mBounds.mMinBound[i]); - bounds.mMaxBound[i] = std::max(bounds.mMaxBound[i], item.mBounds.mMaxBound[i]); - } - }); - } - - void subdivide(std::vector& items, const std::size_t imin, const std::size_t imax, - const std::size_t trisPerChunk, const std::vector& inIndices, const std::vector& inAreaTypes, - std::size_t& curNode, std::vector& nodes, std::size_t& curTri, - std::vector& outIndices, std::vector& outAreaTypes) - { - const auto inum = imax - imin; - const auto icur = curNode; - - if (curNode >= nodes.size()) - return; - - ChunkyTriMeshNode& node = nodes[curNode++]; - - if (inum <= trisPerChunk) - { - // Leaf - calcExtends(items, imin, imax, node.mBounds); - - // Copy triangles. - node.mOffset = static_cast(curTri); - node.mSize = inum; - - for (std::size_t i = imin; i < imax; ++i) - { - std::copy( - inIndices.begin() + items[i].mOffset * 3, - inIndices.begin() + items[i].mOffset * 3 + 3, - outIndices.begin() + static_cast(curTri) * 3 - ); - outAreaTypes[curTri] = inAreaTypes[static_cast(items[i].mOffset)]; - curTri++; - } - } - else - { - // Split - calcExtends(items, imin, imax, node.mBounds); - - if (node.mBounds.mMaxBound.x() - node.mBounds.mMinBound.x() - >= node.mBounds.mMaxBound.y() - node.mBounds.mMinBound.y()) - { - // Sort along x-axis - std::sort( - items.begin() + static_cast(imin), - items.begin() + static_cast(imax), - LessBoundsItem<0> {} - ); - } - else - { - // Sort along y-axis - std::sort( - items.begin() + static_cast(imin), - items.begin() + static_cast(imax), - LessBoundsItem<1> {} - ); - } - - const auto isplit = imin + inum / 2; - - // Left - subdivide(items, imin, isplit, trisPerChunk, inIndices, inAreaTypes, curNode, nodes, curTri, outIndices, outAreaTypes); - // Right - subdivide(items, isplit, imax, trisPerChunk, inIndices, inAreaTypes, curNode, nodes, curTri, outIndices, outAreaTypes); - - const auto iescape = static_cast(curNode) - static_cast(icur); - // Negative index means escape. - node.mOffset = -iescape; - } - } - } - - ChunkyTriMesh::ChunkyTriMesh(const std::vector& verts, const std::vector& indices, - const std::vector& flags, const std::size_t trisPerChunk) - : mMaxTrisPerChunk(0) - { - const auto trianglesCount = indices.size() / 3; - - if (trianglesCount == 0) - return; - - const auto nchunks = (trianglesCount + trisPerChunk - 1) / trisPerChunk; - - mNodes.resize(nchunks * 4); - mIndices.resize(trianglesCount * 3); - mAreaTypes.resize(trianglesCount); - - // Build tree - std::vector items(trianglesCount); - - for (std::size_t i = 0; i < trianglesCount; i++) - { - auto& item = items[i]; - - item.mOffset = static_cast(i); - item.mAreaTypes = flags[i]; - - // Calc triangle XZ bounds. - const auto baseIndex = static_cast(indices[i * 3]) * 3; - - item.mBounds.mMinBound.x() = item.mBounds.mMaxBound.x() = verts[baseIndex + 0]; - item.mBounds.mMinBound.y() = item.mBounds.mMaxBound.y() = verts[baseIndex + 2]; - - for (std::size_t j = 1; j < 3; ++j) - { - const auto index = static_cast(indices[i * 3 + j]) * 3; - - item.mBounds.mMinBound.x() = std::min(item.mBounds.mMinBound.x(), verts[index + 0]); - item.mBounds.mMinBound.y() = std::min(item.mBounds.mMinBound.y(), verts[index + 2]); - - item.mBounds.mMaxBound.x() = std::max(item.mBounds.mMaxBound.x(), verts[index + 0]); - item.mBounds.mMaxBound.y() = std::max(item.mBounds.mMaxBound.y(), verts[index + 2]); - } - } - - std::size_t curTri = 0; - std::size_t curNode = 0; - subdivide(items, 0, trianglesCount, trisPerChunk, indices, flags, curNode, mNodes, curTri, mIndices, mAreaTypes); - - items.clear(); - - mNodes.resize(curNode); - - // Calc max tris per node. - for (auto& node : mNodes) - { - const bool isLeaf = node.mOffset >= 0; - if (!isLeaf) - continue; - if (node.mSize > mMaxTrisPerChunk) - mMaxTrisPerChunk = node.mSize; - } - } -} diff --git a/components/detournavigator/chunkytrimesh.hpp b/components/detournavigator/chunkytrimesh.hpp deleted file mode 100644 index 9f6275ec8..000000000 --- a/components/detournavigator/chunkytrimesh.hpp +++ /dev/null @@ -1,99 +0,0 @@ -#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_CHUNKYTRIMESH_H -#define OPENMW_COMPONENTS_DETOURNAVIGATOR_CHUNKYTRIMESH_H - -#include "areatype.hpp" - -#include - -#include -#include - -namespace DetourNavigator -{ - struct Rect - { - osg::Vec2f mMinBound; - osg::Vec2f mMaxBound; - }; - - struct ChunkyTriMeshNode - { - Rect mBounds; - std::ptrdiff_t mOffset; - std::size_t mSize; - }; - - struct Chunk - { - const int* const mIndices; - const AreaType* const mAreaTypes; - const std::size_t mSize; - }; - - inline bool checkOverlapRect(const Rect& lhs, const Rect& rhs) - { - bool overlap = true; - overlap = (lhs.mMinBound.x() > rhs.mMaxBound.x() || lhs.mMaxBound.x() < rhs.mMinBound.x()) ? false : overlap; - overlap = (lhs.mMinBound.y() > rhs.mMaxBound.y() || lhs.mMaxBound.y() < rhs.mMinBound.y()) ? false : overlap; - return overlap; - } - - class ChunkyTriMesh - { - public: - /// Creates partitioned triangle mesh (AABB tree), - /// where each node contains at max trisPerChunk triangles. - ChunkyTriMesh(const std::vector& verts, const std::vector& tris, - const std::vector& flags, const std::size_t trisPerChunk); - - ChunkyTriMesh(const ChunkyTriMesh&) = delete; - ChunkyTriMesh& operator=(const ChunkyTriMesh&) = delete; - - /// Returns the chunk indices which overlap the input rectable. - template - void forEachChunksOverlappingRect(const Rect& rect, Function&& function) const - { - // Traverse tree - for (std::size_t i = 0; i < mNodes.size(); ) - { - const ChunkyTriMeshNode* node = &mNodes[i]; - const bool overlap = checkOverlapRect(rect, node->mBounds); - const bool isLeafNode = node->mOffset >= 0; - - if (isLeafNode && overlap) - function(i); - - if (overlap || isLeafNode) - i++; - else - { - const auto escapeIndex = -node->mOffset; - i += static_cast(escapeIndex); - } - } - } - - Chunk getChunk(const std::size_t chunkId) const - { - const auto& node = mNodes[chunkId]; - return Chunk { - mIndices.data() + node.mOffset * 3, - mAreaTypes.data() + node.mOffset, - node.mSize - }; - } - - std::size_t getMaxTrisPerChunk() const - { - return mMaxTrisPerChunk; - } - - private: - std::vector mNodes; - std::vector mIndices; - std::vector mAreaTypes; - std::size_t mMaxTrisPerChunk; - }; -} - -#endif diff --git a/components/detournavigator/findrandompointaroundcircle.cpp b/components/detournavigator/findrandompointaroundcircle.cpp index 29d4e9da4..ed73dca61 100644 --- a/components/detournavigator/findrandompointaroundcircle.cpp +++ b/components/detournavigator/findrandompointaroundcircle.cpp @@ -4,7 +4,6 @@ #include -#include #include #include diff --git a/components/detournavigator/findsmoothpath.cpp b/components/detournavigator/findsmoothpath.cpp index 8974be532..659826339 100644 --- a/components/detournavigator/findsmoothpath.cpp +++ b/components/detournavigator/findsmoothpath.cpp @@ -1,5 +1,7 @@ #include "findsmoothpath.hpp" +#include + #include #include @@ -103,7 +105,7 @@ namespace DetourNavigator return result; } - std::optional getSteerTarget(const dtNavMeshQuery& navQuery, const osg::Vec3f& startPos, + std::optional getSteerTarget(const dtNavMeshQuery& navMeshQuery, const osg::Vec3f& startPos, const osg::Vec3f& endPos, const float minTargetDist, const std::vector& path) { // Find steer target. @@ -113,8 +115,11 @@ namespace DetourNavigator std::array steerPathFlags; std::array steerPathPolys; int nsteerPath = 0; - navQuery.findStraightPath(startPos.ptr(), endPos.ptr(), path.data(), int(path.size()), steerPath.data(), - steerPathFlags.data(), steerPathPolys.data(), &nsteerPath, maxSteerPoints); + const dtStatus status = navMeshQuery.findStraightPath(startPos.ptr(), endPos.ptr(), path.data(), + static_cast(path.size()), steerPath.data(), steerPathFlags.data(), steerPathPolys.data(), + &nsteerPath, maxSteerPoints); + if (dtStatusFailed(status)) + return std::nullopt; assert(nsteerPath >= 0); if (!nsteerPath) return std::nullopt; @@ -125,7 +130,7 @@ namespace DetourNavigator { // Stop at Off-Mesh link or when point is further than slop away. if ((steerPathFlags[ns] & DT_STRAIGHTPATH_OFFMESH_CONNECTION) || - !inRange(Misc::Convert::makeOsgVec3f(&steerPath[ns * 3]), startPos, minTargetDist, 1000.0f)) + !inRange(Misc::Convert::makeOsgVec3f(&steerPath[ns * 3]), startPos, minTargetDist)) break; ns++; } diff --git a/components/detournavigator/findsmoothpath.hpp b/components/detournavigator/findsmoothpath.hpp index 29a3ce805..db2219b2c 100644 --- a/components/detournavigator/findsmoothpath.hpp +++ b/components/detournavigator/findsmoothpath.hpp @@ -14,11 +14,8 @@ #include #include -#include - -#include - #include + #include #include @@ -28,10 +25,9 @@ namespace DetourNavigator { struct Settings; - inline bool inRange(const osg::Vec3f& v1, const osg::Vec3f& v2, const float r, const float h) + inline bool inRange(const osg::Vec3f& v1, const osg::Vec3f& v2, const float r) { - const auto d = v2 - v1; - return (d.x() * d.x() + d.z() * d.z()) < r * r && std::abs(d.y()) < h; + return (osg::Vec2f(v1.x(), v1.z()) - osg::Vec2f(v2.x(), v2.z())).length() < r; } std::vector fixupCorridor(const std::vector& path, const std::vector& visited); @@ -145,15 +141,6 @@ namespace DetourNavigator return {std::move(result)}; } - inline std::optional getPolyHeight(const dtNavMeshQuery& navMeshQuery, const dtPolyRef ref, const osg::Vec3f& pos) - { - float result = 0.0f; - const auto status = navMeshQuery.getPolyHeight(ref, pos.ptr(), &result); - if (!dtStatusSucceed(status)) - return {}; - return result; - } - template Status makeSmoothPath(const dtNavMesh& navMesh, const dtNavMeshQuery& navMeshQuery, const dtQueryFilter& filter, const osg::Vec3f& start, const osg::Vec3f& end, const float stepSize, @@ -203,13 +190,8 @@ namespace DetourNavigator polygonPath = fixupCorridor(polygonPath, result->mVisited); polygonPath = fixupShortcuts(polygonPath, navMeshQuery); - float h = 0; - navMeshQuery.getPolyHeight(polygonPath.front(), result->mResultPos.ptr(), &h); - iterPos = result->mResultPos; - iterPos.y() = h; - // Handle end of path and off-mesh links when close enough. - if (endOfPath && inRange(iterPos, steerTarget->steerPos, slop, 1.0f)) + if (endOfPath && inRange(result->mResultPos, steerTarget->steerPos, slop)) { // Reached end of path. iterPos = targetPos; @@ -217,7 +199,7 @@ namespace DetourNavigator ++smoothPathSize; break; } - else if (offMeshConnection && inRange(iterPos, steerTarget->steerPos, slop, 1.0f)) + else if (offMeshConnection && inRange(result->mResultPos, steerTarget->steerPos, slop)) { // Advance the path up to and over the off-mesh connection. dtPolyRef prevRef = 0; @@ -251,16 +233,18 @@ namespace DetourNavigator } // Move position at the other side of the off-mesh link. - iterPos = endPos; - const auto height = getPolyHeight(navMeshQuery, polygonPath.front(), iterPos); - - if (!height) + if (dtStatusFailed(navMeshQuery.getPolyHeight(polygonPath.front(), endPos.ptr(), &iterPos.y()))) return Status::GetPolyHeightFailed; - - iterPos.y() = *height; + iterPos.x() = endPos.x(); + iterPos.z() = endPos.z(); } } + if (dtStatusFailed(navMeshQuery.getPolyHeight(polygonPath.front(), result->mResultPos.ptr(), &iterPos.y()))) + return Status::GetPolyHeightFailed; + iterPos.x() = result->mResultPos.x(); + iterPos.z() = result->mResultPos.z(); + // Store results. *out++ = iterPos; ++smoothPathSize; diff --git a/components/detournavigator/makenavmesh.cpp b/components/detournavigator/makenavmesh.cpp index 7c7dcf186..422fdffb1 100644 --- a/components/detournavigator/makenavmesh.cpp +++ b/components/detournavigator/makenavmesh.cpp @@ -1,7 +1,5 @@ #include "makenavmesh.hpp" -#include "chunkytrimesh.hpp" #include "debug.hpp" -#include "dtstatus.hpp" #include "exceptions.hpp" #include "recastmesh.hpp" #include "settings.hpp" @@ -22,6 +20,7 @@ #include #include #include +#include namespace { @@ -178,65 +177,30 @@ namespace bool rasterizeSolidObjectsTriangles(rcContext& context, const RecastMesh& recastMesh, const rcConfig& config, rcHeightfield& solid) { - const auto& chunkyMesh = recastMesh.getChunkyTriMesh(); - std::vector areas(chunkyMesh.getMaxTrisPerChunk(), AreaType_null); const osg::Vec2f tileBoundsMin(config.bmin[0], config.bmin[2]); const osg::Vec2f tileBoundsMax(config.bmax[0], config.bmax[2]); - bool result = false; - - chunkyMesh.forEachChunksOverlappingRect(Rect {tileBoundsMin, tileBoundsMax}, - [&] (const std::size_t cid) - { - const auto chunk = chunkyMesh.getChunk(cid); - - std::fill( - areas.begin(), - std::min(areas.begin() + static_cast(chunk.mSize), - areas.end()), - AreaType_null - ); - - rcMarkWalkableTriangles( - &context, - config.walkableSlopeAngle, - recastMesh.getVertices().data(), - static_cast(recastMesh.getVerticesCount()), - chunk.mIndices, - static_cast(chunk.mSize), - areas.data() - ); - - for (std::size_t i = 0; i < chunk.mSize; ++i) - areas[i] = chunk.mAreaTypes[i]; - - rcClearUnwalkableTriangles( - &context, - config.walkableSlopeAngle, - recastMesh.getVertices().data(), - static_cast(recastMesh.getVerticesCount()), - chunk.mIndices, - static_cast(chunk.mSize), - areas.data() - ); - - const auto trianglesRasterized = rcRasterizeTriangles( - &context, - recastMesh.getVertices().data(), - static_cast(recastMesh.getVerticesCount()), - chunk.mIndices, - areas.data(), - static_cast(chunk.mSize), - solid, - config.walkableClimb - ); - - if (!trianglesRasterized) - throw NavigatorException("Failed to create rasterize triangles from recast mesh for navmesh"); - - result = true; - }); - - return result; + std::vector areas(recastMesh.getAreaTypes().begin(), recastMesh.getAreaTypes().end()); + + rcClearUnwalkableTriangles( + &context, + config.walkableSlopeAngle, + recastMesh.getVertices().data(), + static_cast(recastMesh.getVerticesCount()), + recastMesh.getIndices().data(), + static_cast(areas.size()), + areas.data() + ); + + return rcRasterizeTriangles( + &context, + recastMesh.getVertices().data(), + static_cast(recastMesh.getVerticesCount()), + recastMesh.getIndices().data(), + areas.data(), + static_cast(areas.size()), + solid, + config.walkableClimb + ); } void rasterizeWaterTriangles(rcContext& context, const osg::Vec3f& agentHalfExtents, const RecastMesh& recastMesh, @@ -425,7 +389,7 @@ namespace const auto offMeshConVerts = getOffMeshVerts(offMeshConnections); const std::vector offMeshConRad(offMeshConnections.size(), getRadius(settings, agentHalfExtents)); - const std::vector offMeshConDir(offMeshConnections.size(), DT_OFFMESH_CON_BIDIR); + const std::vector offMeshConDir(offMeshConnections.size(), 0); const std::vector offMeshConAreas = getOffMeshConAreas(offMeshConnections); const std::vector offMeshConFlags = getOffMeshFlags(offMeshConnections); @@ -576,17 +540,8 @@ namespace DetourNavigator return navMeshCacheItem->lock()->removeTile(changedTile); } - try - { - cachedNavMeshData = navMeshTilesCache.set(agentHalfExtents, changedTile, *recastMesh, - offMeshConnections, std::move(navMeshData)); - } - catch (const InvalidArgument&) - { - cachedNavMeshData = navMeshTilesCache.get(agentHalfExtents, changedTile, *recastMesh, - offMeshConnections); - cached = static_cast(cachedNavMeshData); - } + cachedNavMeshData = navMeshTilesCache.set(agentHalfExtents, changedTile, *recastMesh, + offMeshConnections, std::move(navMeshData)); if (!cachedNavMeshData) { diff --git a/components/detournavigator/makenavmesh.hpp b/components/detournavigator/makenavmesh.hpp index f9cf68a73..95720634c 100644 --- a/components/detournavigator/makenavmesh.hpp +++ b/components/detournavigator/makenavmesh.hpp @@ -2,10 +2,8 @@ #define OPENMW_COMPONENTS_DETOURNAVIGATOR_MAKENAVMESH_H #include "offmeshconnectionsmanager.hpp" -#include "settings.hpp" #include "navmeshcacheitem.hpp" #include "tileposition.hpp" -#include "tilebounds.hpp" #include "sharednavmesh.hpp" #include "navmeshtilescache.hpp" diff --git a/components/detournavigator/navigator.hpp b/components/detournavigator/navigator.hpp index d08bfa640..d0c747650 100644 --- a/components/detournavigator/navigator.hpp +++ b/components/detournavigator/navigator.hpp @@ -6,8 +6,10 @@ #include "settings.hpp" #include "objectid.hpp" #include "navmeshcacheitem.hpp" -#include "recastmesh.hpp" #include "recastmeshtiles.hpp" +#include "waitconditiontype.hpp" + +#include namespace ESM { @@ -15,15 +17,19 @@ namespace ESM struct Pathgrid; } +namespace Loading +{ + class Listener; +} + namespace DetourNavigator { struct ObjectShapes { - const btCollisionShape& mShape; - const btCollisionShape* mAvoid; + osg::ref_ptr mShapeInstance; - ObjectShapes(const btCollisionShape& shape, const btCollisionShape* avoid) - : mShape(shape), mAvoid(avoid) + ObjectShapes(const osg::ref_ptr& shapeInstance) + : mShapeInstance(shapeInstance) {} }; @@ -32,9 +38,9 @@ namespace DetourNavigator osg::Vec3f mConnectionStart; osg::Vec3f mConnectionEnd; - DoorShapes(const btCollisionShape& shape, const btCollisionShape* avoid, + DoorShapes(const osg::ref_ptr& shapeInstance, const osg::Vec3f& connectionStart,const osg::Vec3f& connectionEnd) - : ObjectShapes(shape, avoid) + : ObjectShapes(shapeInstance) , mConnectionStart(connectionStart) , mConnectionEnd(connectionEnd) {} @@ -65,13 +71,15 @@ namespace DetourNavigator virtual void removeAgent(const osg::Vec3f& agentHalfExtents) = 0; /** - * @brief addObject is used to add object represented by single btCollisionShape and btTransform. + * @brief addObject is used to add object represented by single btHeightfieldTerrainShape and btTransform. * @param id is used to distinguish different objects. - * @param shape must live until object is updated by another shape removed from Navigator. + * @param holder shape wrapper to keep shape lifetime after object is removed. + * @param shape must be wrapped by holder. * @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. */ - virtual bool addObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform) = 0; + virtual bool addObject(const ObjectId id, const osg::ref_ptr& holder, + const btHeightfieldTerrainShape& shape, const btTransform& transform) = 0; /** * @brief addObject is used to add complex object with allowed to walk and avoided to walk shapes @@ -91,15 +99,6 @@ namespace DetourNavigator */ virtual bool addObject(const ObjectId id, const DoorShapes& shapes, const btTransform& transform) = 0; - /** - * @brief updateObject replace object geometry by given data. - * @param id is used to find object. - * @param shape must live until object is updated by another shape removed from Navigator. - * @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. - */ - virtual bool updateObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform) = 0; - /** * @brief updateObject replace object geometry by given data. * @param id is used to find object. @@ -150,20 +149,27 @@ namespace DetourNavigator virtual void removePathgrid(const ESM::Pathgrid& pathgrid) = 0; /** - * @brief update start background navmesh update using current scene state. + * @brief update starts background navmesh update using current scene state. * @param playerPosition setup initial point to order build tiles of navmesh. */ virtual void update(const osg::Vec3f& playerPosition) = 0; + /** + * @brief updatePlayerPosition starts background navmesh update using current scene state only when player position has been changed. + * @param playerPosition setup initial point to order build tiles of navmesh. + */ + virtual void updatePlayerPosition(const osg::Vec3f& playerPosition) = 0; + /** * @brief disable navigator updates */ virtual void setUpdatesEnabled(bool enabled) = 0; /** - * @brief wait locks thread until all tiles are updated from last update call. + * @brief wait locks thread until tiles are updated from last update call based on passed condition type. + * @param waitConditionType defines when waiting will stop */ - virtual void wait() = 0; + virtual void wait(Loading::Listener& listener, WaitConditionType waitConditionType) = 0; /** * @brief findPath fills output iterator with points of scene surfaces to be used for actor to walk through. diff --git a/components/detournavigator/navigatorimpl.cpp b/components/detournavigator/navigatorimpl.cpp index abfb20ba8..70a69405e 100644 --- a/components/detournavigator/navigatorimpl.cpp +++ b/components/detournavigator/navigatorimpl.cpp @@ -5,8 +5,6 @@ #include #include -#include - namespace DetourNavigator { NavigatorImpl::NavigatorImpl(const Settings& settings) @@ -33,18 +31,22 @@ namespace DetourNavigator --it->second; } - bool NavigatorImpl::addObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform) + bool NavigatorImpl::addObject(const ObjectId id, const osg::ref_ptr& holder, + const btHeightfieldTerrainShape& shape, const btTransform& transform) { - return mNavMeshManager.addObject(id, shape, transform, AreaType_ground); + const CollisionShape collisionShape {holder, shape}; + return mNavMeshManager.addObject(id, collisionShape, transform, AreaType_ground); } bool NavigatorImpl::addObject(const ObjectId id, const ObjectShapes& shapes, const btTransform& transform) { - bool result = addObject(id, shapes.mShape, transform); - if (shapes.mAvoid) + const CollisionShape collisionShape {shapes.mShapeInstance, *shapes.mShapeInstance->getCollisionShape()}; + bool result = mNavMeshManager.addObject(id, collisionShape, transform, AreaType_ground); + if (const btCollisionShape* const avoidShape = shapes.mShapeInstance->getAvoidCollisionShape()) { - const ObjectId avoidId(shapes.mAvoid); - if (mNavMeshManager.addObject(avoidId, *shapes.mAvoid, transform, AreaType_null)) + const ObjectId avoidId(avoidShape); + const CollisionShape collisionShape {shapes.mShapeInstance, *avoidShape}; + if (mNavMeshManager.addObject(avoidId, collisionShape, transform, AreaType_null)) { updateAvoidShapeId(id, avoidId); result = true; @@ -57,29 +59,24 @@ namespace DetourNavigator { if (addObject(id, static_cast(shapes), transform)) { - mNavMeshManager.addOffMeshConnection( - id, - toNavMeshCoordinates(mSettings, shapes.mConnectionStart), - toNavMeshCoordinates(mSettings, shapes.mConnectionEnd), - AreaType_door - ); + const osg::Vec3f start = toNavMeshCoordinates(mSettings, shapes.mConnectionStart); + const osg::Vec3f end = toNavMeshCoordinates(mSettings, shapes.mConnectionEnd); + mNavMeshManager.addOffMeshConnection(id, start, end, AreaType_door); + mNavMeshManager.addOffMeshConnection(id, end, start, AreaType_door); return true; } return false; } - bool NavigatorImpl::updateObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform) - { - return mNavMeshManager.updateObject(id, shape, transform, AreaType_ground); - } - bool NavigatorImpl::updateObject(const ObjectId id, const ObjectShapes& shapes, const btTransform& transform) { - bool result = updateObject(id, shapes.mShape, transform); - if (shapes.mAvoid) + const CollisionShape collisionShape {shapes.mShapeInstance, *shapes.mShapeInstance->getCollisionShape()}; + bool result = mNavMeshManager.updateObject(id, collisionShape, transform, AreaType_ground); + if (const btCollisionShape* const avoidShape = shapes.mShapeInstance->getAvoidCollisionShape()) { - const ObjectId avoidId(shapes.mAvoid); - if (mNavMeshManager.updateObject(avoidId, *shapes.mAvoid, transform, AreaType_null)) + const ObjectId avoidId(avoidShape); + const CollisionShape collisionShape {shapes.mShapeInstance, *avoidShape}; + if (mNavMeshManager.updateObject(avoidId, collisionShape, transform, AreaType_null)) { updateAvoidShapeId(id, avoidId); result = true; @@ -148,14 +145,23 @@ namespace DetourNavigator mNavMeshManager.update(playerPosition, v.first); } + void NavigatorImpl::updatePlayerPosition(const osg::Vec3f& playerPosition) + { + const TilePosition tilePosition = getTilePosition(mSettings, toNavMeshCoordinates(mSettings, playerPosition)); + if (mLastPlayerPosition.has_value() && *mLastPlayerPosition == tilePosition) + return; + update(playerPosition); + mLastPlayerPosition = tilePosition; + } + void NavigatorImpl::setUpdatesEnabled(bool enabled) { mUpdatesEnabled = enabled; } - void NavigatorImpl::wait() + void NavigatorImpl::wait(Loading::Listener& listener, WaitConditionType waitConditionType) { - mNavMeshManager.wait(); + mNavMeshManager.wait(listener, waitConditionType); } SharedNavMeshCacheItem NavigatorImpl::getNavMesh(const osg::Vec3f& agentHalfExtents) const diff --git a/components/detournavigator/navigatorimpl.hpp b/components/detournavigator/navigatorimpl.hpp index 74fff0dea..c8f0abc68 100644 --- a/components/detournavigator/navigatorimpl.hpp +++ b/components/detournavigator/navigatorimpl.hpp @@ -21,14 +21,13 @@ namespace DetourNavigator 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 osg::ref_ptr& holder, + const btHeightfieldTerrainShape& 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; @@ -46,9 +45,11 @@ namespace DetourNavigator void update(const osg::Vec3f& playerPosition) override; + void updatePlayerPosition(const osg::Vec3f& playerPosition) override; + void setUpdatesEnabled(bool enabled) override; - void wait() override; + void wait(Loading::Listener& listener, WaitConditionType waitConditionType) override; SharedNavMeshCacheItem getNavMesh(const osg::Vec3f& agentHalfExtents) const override; @@ -66,6 +67,7 @@ namespace DetourNavigator Settings mSettings; NavMeshManager mNavMeshManager; bool mUpdatesEnabled; + std::optional mLastPlayerPosition; std::map mAgents; std::unordered_map mAvoidIds; std::unordered_map mWaterIds; diff --git a/components/detournavigator/navigatorstub.hpp b/components/detournavigator/navigatorstub.hpp index f6892bf1b..a507150ca 100644 --- a/components/detournavigator/navigatorstub.hpp +++ b/components/detournavigator/navigatorstub.hpp @@ -3,6 +3,11 @@ #include "navigator.hpp" +namespace Loading +{ + class Listener; +} + namespace DetourNavigator { class NavigatorStub final : public Navigator @@ -14,7 +19,8 @@ namespace DetourNavigator 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 osg::ref_ptr& /*holder*/, + const btHeightfieldTerrainShape& /*shape*/, const btTransform& /*transform*/) override { return false; } @@ -29,11 +35,6 @@ namespace DetourNavigator 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; @@ -66,9 +67,11 @@ namespace DetourNavigator void update(const osg::Vec3f& /*playerPosition*/) override {} + void updatePlayerPosition(const osg::Vec3f& /*playerPosition*/) override {}; + void setUpdatesEnabled(bool /*enabled*/) override {} - void wait() override {} + void wait(Loading::Listener& /*listener*/, WaitConditionType /*waitConditionType*/) override {} SharedNavMeshCacheItem getNavMesh(const osg::Vec3f& /*agentHalfExtents*/) const override { diff --git a/components/detournavigator/navmeshmanager.cpp b/components/detournavigator/navmeshmanager.cpp index 43d330648..b6524db30 100644 --- a/components/detournavigator/navmeshmanager.cpp +++ b/components/detournavigator/navmeshmanager.cpp @@ -5,11 +5,14 @@ #include "makenavmesh.hpp" #include "navmeshcacheitem.hpp" #include "settings.hpp" +#include "waitconditiontype.hpp" #include #include +#include + namespace { using DetourNavigator::ChangeType; @@ -44,16 +47,17 @@ namespace DetourNavigator , mAsyncNavMeshUpdater(settings, mRecastMeshManager, mOffMeshConnectionsManager) {} - bool NavMeshManager::addObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform, + bool NavMeshManager::addObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform, const AreaType areaType) { + const btCollisionShape& collisionShape = shape.getShape(); if (!mRecastMeshManager.addObject(id, shape, transform, areaType)) return false; - addChangedTiles(shape, transform, ChangeType::add); + addChangedTiles(collisionShape, transform, ChangeType::add); return true; } - bool NavMeshManager::updateObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform, + bool NavMeshManager::updateObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform, const AreaType areaType) { return mRecastMeshManager.updateObject(id, shape, transform, areaType, @@ -168,7 +172,7 @@ namespace DetourNavigator } } const auto maxTiles = std::min(mSettings.mMaxTilesNumber, navMesh.getParams()->maxTiles); - mRecastMeshManager.forEachTilePosition([&] (const TilePosition& tile) + mRecastMeshManager.forEachTile([&] (const TilePosition& tile, CachedRecastMeshManager& recastMeshManager) { if (tilesToPost.count(tile)) return; @@ -178,6 +182,8 @@ namespace DetourNavigator tilesToPost.insert(std::make_pair(tile, ChangeType::add)); else if (!shouldAdd && presentInNavMesh) tilesToPost.insert(std::make_pair(tile, ChangeType::mixed)); + else + recastMeshManager.reportNavMeshChange(recastMeshManager.getVersion(), Version {0, 0}); }); } mAsyncNavMeshUpdater.post(agentHalfExtents, cached, playerTile, tilesToPost); @@ -188,9 +194,9 @@ namespace DetourNavigator " recastMeshManagerRevision=" << lastRevision; } - void NavMeshManager::wait() + void NavMeshManager::wait(Loading::Listener& listener, WaitConditionType waitConditionType) { - mAsyncNavMeshUpdater.wait(); + mAsyncNavMeshUpdater.wait(listener, waitConditionType); } SharedNavMeshCacheItem NavMeshManager::getNavMesh(const osg::Vec3f& agentHalfExtents) const @@ -211,8 +217,8 @@ namespace DetourNavigator RecastMeshTiles NavMeshManager::getRecastMeshTiles() { std::vector tiles; - mRecastMeshManager.forEachTilePosition( - [&tiles] (const TilePosition& tile) { tiles.push_back(tile); }); + mRecastMeshManager.forEachTile( + [&tiles] (const TilePosition& tile, const CachedRecastMeshManager&) { tiles.push_back(tile); }); RecastMeshTiles result; std::transform(tiles.begin(), tiles.end(), std::inserter(result, result.end()), [this] (const TilePosition& tile) { return std::make_pair(tile, mRecastMeshManager.getMesh(tile)); }); diff --git a/components/detournavigator/navmeshmanager.hpp b/components/detournavigator/navmeshmanager.hpp index f3861f8f2..2402dc3b1 100644 --- a/components/detournavigator/navmeshmanager.hpp +++ b/components/detournavigator/navmeshmanager.hpp @@ -4,8 +4,8 @@ #include "asyncnavmeshupdater.hpp" #include "cachedrecastmeshmanager.hpp" #include "offmeshconnectionsmanager.hpp" -#include "sharednavmesh.hpp" #include "recastmeshtiles.hpp" +#include "waitconditiontype.hpp" #include @@ -23,10 +23,10 @@ namespace DetourNavigator public: NavMeshManager(const Settings& settings); - bool addObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform, + bool addObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform, const AreaType areaType); - bool updateObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform, + bool updateObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform, const AreaType areaType); bool removeObject(const ObjectId id); @@ -45,7 +45,7 @@ namespace DetourNavigator void update(osg::Vec3f playerPosition, const osg::Vec3f& agentHalfExtents); - void wait(); + void wait(Loading::Listener& listener, WaitConditionType waitConditionType); SharedNavMeshCacheItem getNavMesh(const osg::Vec3f& agentHalfExtents) const; diff --git a/components/detournavigator/navmeshtilescache.cpp b/components/detournavigator/navmeshtilescache.cpp index b6048da58..ccc41a666 100644 --- a/components/detournavigator/navmeshtilescache.cpp +++ b/components/detournavigator/navmeshtilescache.cpp @@ -1,5 +1,4 @@ #include "navmeshtilescache.hpp" -#include "exceptions.hpp" #include @@ -32,16 +31,8 @@ namespace DetourNavigator ++mGetCount; - const auto agentValues = mValues.find(agentHalfExtents); - if (agentValues == mValues.end()) - return Value(); - - const auto tileValues = agentValues->second.find(changedTile); - if (tileValues == agentValues->second.end()) - return Value(); - - const auto tile = tileValues->second.mMap.find(NavMeshKeyView(recastMesh, offMeshConnections)); - if (tile == tileValues->second.mMap.end()) + const auto tile = mValues.find(std::make_tuple(agentHalfExtents, changedTile, NavMeshKeyView(recastMesh, offMeshConnections))); + if (tile == mValues.end()) return Value(); acquireItemUnsafe(tile->second); @@ -71,76 +62,61 @@ namespace DetourNavigator }; const auto iterator = mFreeItems.emplace(mFreeItems.end(), agentHalfExtents, changedTile, std::move(navMeshKey), itemSize); - const auto emplaced = mValues[agentHalfExtents][changedTile].mMap.emplace(iterator->mNavMeshKey, iterator); + const auto emplaced = mValues.emplace(std::make_tuple(agentHalfExtents, changedTile, NavMeshKeyRef(iterator->mNavMeshKey)), iterator); if (!emplaced.second) { mFreeItems.erase(iterator); - throw InvalidArgument("Set existing cache value"); + acquireItemUnsafe(emplaced.first->second); + ++mGetCount; + ++mHitCount; + return Value(*this, emplaced.first->second); } iterator->mNavMeshData = std::move(value); + ++iterator->mUseCount; mUsedNavMeshDataSize += itemSize; - mFreeNavMeshDataSize += itemSize; - - acquireItemUnsafe(iterator); + mBusyItems.splice(mBusyItems.end(), mFreeItems, iterator); return Value(*this, iterator); } - void NavMeshTilesCache::reportStats(unsigned int frameNumber, osg::Stats& stats) const + NavMeshTilesCache::Stats NavMeshTilesCache::getStats() const { - std::size_t navMeshCacheSize = 0; - std::size_t usedNavMeshTiles = 0; - std::size_t cachedNavMeshTiles = 0; - std::size_t hitCount = 0; - std::size_t getCount = 0; - + Stats result; { const std::lock_guard lock(mMutex); - navMeshCacheSize = mUsedNavMeshDataSize; - usedNavMeshTiles = mBusyItems.size(); - cachedNavMeshTiles = mFreeItems.size(); - hitCount = mHitCount; - getCount = mGetCount; + result.mNavMeshCacheSize = mUsedNavMeshDataSize; + result.mUsedNavMeshTiles = mBusyItems.size(); + result.mCachedNavMeshTiles = mFreeItems.size(); + result.mHitCount = mHitCount; + result.mGetCount = mGetCount; } + return result; + } - stats.setAttribute(frameNumber, "NavMesh CacheSize", navMeshCacheSize); - stats.setAttribute(frameNumber, "NavMesh UsedTiles", usedNavMeshTiles); - stats.setAttribute(frameNumber, "NavMesh CachedTiles", cachedNavMeshTiles); - stats.setAttribute(frameNumber, "NavMesh CacheHitRate", static_cast(hitCount) / getCount * 100.0); + void NavMeshTilesCache::reportStats(unsigned int frameNumber, osg::Stats& out) const + { + const Stats stats = getStats(); + out.setAttribute(frameNumber, "NavMesh CacheSize", stats.mNavMeshCacheSize); + out.setAttribute(frameNumber, "NavMesh UsedTiles", stats.mUsedNavMeshTiles); + out.setAttribute(frameNumber, "NavMesh CachedTiles", stats.mCachedNavMeshTiles); + out.setAttribute(frameNumber, "NavMesh CacheHitRate", static_cast(stats.mHitCount) / stats.mGetCount * 100.0); } void NavMeshTilesCache::removeLeastRecentlyUsed() { const auto& item = mFreeItems.back(); - const auto agentValues = mValues.find(item.mAgentHalfExtents); - if (agentValues == mValues.end()) - return; - - const auto tileValues = agentValues->second.find(item.mChangedTile); - if (tileValues == agentValues->second.end()) - return; - - const auto value = tileValues->second.mMap.find(item.mNavMeshKey); - if (value == tileValues->second.mMap.end()) + const auto value = mValues.find(std::make_tuple(item.mAgentHalfExtents, item.mChangedTile, NavMeshKeyRef(item.mNavMeshKey))); + if (value == mValues.end()) return; mUsedNavMeshDataSize -= item.mSize; mFreeNavMeshDataSize -= item.mSize; - tileValues->second.mMap.erase(value); + mValues.erase(value); mFreeItems.pop_back(); - - if (!tileValues->second.mMap.empty()) - return; - - agentValues->second.erase(tileValues); - if (!agentValues->second.empty()) - return; - - mValues.erase(agentValues); } void NavMeshTilesCache::acquireItemUnsafe(ItemIterator iterator) diff --git a/components/detournavigator/navmeshtilescache.hpp b/components/detournavigator/navmeshtilescache.hpp index 25f4dc187..afa53beba 100644 --- a/components/detournavigator/navmeshtilescache.hpp +++ b/components/detournavigator/navmeshtilescache.hpp @@ -110,6 +110,14 @@ namespace DetourNavigator return lhs < rhs.mRef.get(); } + template + inline bool operator <(const std::tuple& lhs, const std::tuple& rhs) + { + const auto left = std::tie(std::get<0>(lhs), std::get<1>(lhs)); + const auto right = std::tie(std::get<0>(rhs), std::get<1>(rhs)); + return std::tie(left, std::get<2>(lhs)) < std::tie(right, std::get<2>(rhs)); + } + class NavMeshTilesCache { public: @@ -188,6 +196,15 @@ namespace DetourNavigator ItemIterator mIterator; }; + struct Stats + { + std::size_t mNavMeshCacheSize; + std::size_t mUsedNavMeshTiles; + std::size_t mCachedNavMeshTiles; + std::size_t mHitCount; + std::size_t mGetCount; + }; + NavMeshTilesCache(const std::size_t maxNavMeshDataSize); Value get(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile, @@ -197,14 +214,11 @@ namespace DetourNavigator const RecastMesh& recastMesh, const std::vector& offMeshConnections, NavMeshData&& value); + Stats getStats() const; + void reportStats(unsigned int frameNumber, osg::Stats& stats) const; private: - struct TileMap - { - std::map> mMap; - }; - mutable std::mutex mMutex; std::size_t mMaxNavMeshDataSize; std::size_t mUsedNavMeshDataSize; @@ -213,7 +227,7 @@ namespace DetourNavigator std::size_t mGetCount; std::list mBusyItems; std::list mFreeItems; - std::map> mValues; + std::map, ItemIterator, std::less<>> mValues; void removeLeastRecentlyUsed(); diff --git a/components/detournavigator/offmeshconnectionsmanager.cpp b/components/detournavigator/offmeshconnectionsmanager.cpp new file mode 100644 index 000000000..a673ae3e6 --- /dev/null +++ b/components/detournavigator/offmeshconnectionsmanager.cpp @@ -0,0 +1,95 @@ +#include "offmeshconnectionsmanager.hpp" +#include "settings.hpp" +#include "settingsutils.hpp" +#include "tileposition.hpp" +#include "objectid.hpp" +#include "offmeshconnection.hpp" + +#include +#include +#include + +namespace DetourNavigator +{ + OffMeshConnectionsManager::OffMeshConnectionsManager(const Settings& settings) + : mSettings(settings) + {} + + void OffMeshConnectionsManager::add(const ObjectId id, const OffMeshConnection& value) + { + const auto values = mValues.lock(); + + values->mById.insert(std::make_pair(id, value)); + + const auto startTilePosition = getTilePosition(mSettings, value.mStart); + const auto endTilePosition = getTilePosition(mSettings, value.mEnd); + + values->mByTilePosition[startTilePosition].insert(id); + + if (startTilePosition != endTilePosition) + values->mByTilePosition[endTilePosition].insert(id); + } + + std::set OffMeshConnectionsManager::remove(const ObjectId id) + { + const auto values = mValues.lock(); + + const auto byId = values->mById.equal_range(id); + + if (byId.first == byId.second) + return {}; + + std::set removed; + + std::for_each(byId.first, byId.second, [&] (const auto& v) { + const auto startTilePosition = getTilePosition(mSettings, v.second.mStart); + const auto endTilePosition = getTilePosition(mSettings, v.second.mEnd); + + removed.emplace(startTilePosition); + if (startTilePosition != endTilePosition) + removed.emplace(endTilePosition); + }); + + for (const TilePosition& tilePosition : removed) + { + const auto it = values->mByTilePosition.find(tilePosition); + if (it == values->mByTilePosition.end()) + continue; + it->second.erase(id); + if (it->second.empty()) + values->mByTilePosition.erase(it); + } + + values->mById.erase(byId.first, byId.second); + + return removed; + } + + std::vector OffMeshConnectionsManager::get(const TilePosition& tilePosition) + { + std::vector result; + + const auto values = mValues.lock(); + + const auto itByTilePosition = values->mByTilePosition.find(tilePosition); + + if (itByTilePosition == values->mByTilePosition.end()) + return result; + + std::for_each(itByTilePosition->second.begin(), itByTilePosition->second.end(), + [&] (const ObjectId v) + { + const auto byId = values->mById.equal_range(v); + std::for_each(byId.first, byId.second, [&] (const auto& v) + { + if (getTilePosition(mSettings, v.second.mStart) == tilePosition + || getTilePosition(mSettings, v.second.mEnd) == tilePosition) + result.push_back(v.second); + }); + }); + + std::sort(result.begin(), result.end()); + + return result; + } +} diff --git a/components/detournavigator/offmeshconnectionsmanager.hpp b/components/detournavigator/offmeshconnectionsmanager.hpp index 1ad96e3b9..20a6427cd 100644 --- a/components/detournavigator/offmeshconnectionsmanager.hpp +++ b/components/detournavigator/offmeshconnectionsmanager.hpp @@ -2,16 +2,12 @@ #define OPENMW_COMPONENTS_DETOURNAVIGATOR_OFFMESHCONNECTIONSMANAGER_H #include "settings.hpp" -#include "settingsutils.hpp" #include "tileposition.hpp" #include "objectid.hpp" #include "offmeshconnection.hpp" #include -#include - -#include #include #include #include @@ -22,73 +18,13 @@ namespace DetourNavigator class OffMeshConnectionsManager { public: - OffMeshConnectionsManager(const Settings& settings) - : mSettings(settings) - {} - - void add(const ObjectId id, const OffMeshConnection& value) - { - const auto values = mValues.lock(); - - values->mById.insert(std::make_pair(id, value)); - - const auto startTilePosition = getTilePosition(mSettings, value.mStart); - const auto endTilePosition = getTilePosition(mSettings, value.mEnd); - - values->mByTilePosition[startTilePosition].insert(id); - - if (startTilePosition != endTilePosition) - values->mByTilePosition[endTilePosition].insert(id); - } - - std::set remove(const ObjectId id) - { - const auto values = mValues.lock(); - - const auto byId = values->mById.equal_range(id); - - if (byId.first == byId.second) { - return {}; - } - - std::set removed; + OffMeshConnectionsManager(const Settings& settings); - std::for_each(byId.first, byId.second, [&] (const auto& v) { - const auto startTilePosition = getTilePosition(mSettings, v.second.mStart); - const auto endTilePosition = getTilePosition(mSettings, v.second.mEnd); + void add(const ObjectId id, const OffMeshConnection& value); - removed.emplace(startTilePosition); - if (startTilePosition != endTilePosition) - removed.emplace(endTilePosition); - }); + std::set remove(const ObjectId id); - values->mById.erase(byId.first, byId.second); - - return removed; - } - - std::vector get(const TilePosition& tilePosition) - { - std::vector result; - - const auto values = mValues.lock(); - - const auto itByTilePosition = values->mByTilePosition.find(tilePosition); - - if (itByTilePosition == values->mByTilePosition.end()) - return result; - - std::for_each(itByTilePosition->second.begin(), itByTilePosition->second.end(), - [&] (const ObjectId v) - { - const auto byId = values->mById.equal_range(v); - std::for_each(byId.first, byId.second, [&] (const auto& v) { result.push_back(v.second); }); - }); - - std::sort(result.begin(), result.end()); - - return result; - } + std::vector get(const TilePosition& tilePosition); private: struct Values @@ -99,14 +35,6 @@ namespace DetourNavigator const Settings& mSettings; Misc::ScopeGuarded mValues; - - void removeByTilePosition(std::map>& valuesByTilePosition, - const TilePosition& tilePosition, const ObjectId id) - { - const auto it = valuesByTilePosition.find(tilePosition); - if (it != valuesByTilePosition.end()) - it->second.erase(id); - } }; } diff --git a/components/detournavigator/oscillatingrecastmeshobject.cpp b/components/detournavigator/oscillatingrecastmeshobject.cpp index dd0b95600..fbe4b77ff 100644 --- a/components/detournavigator/oscillatingrecastmeshobject.cpp +++ b/components/detournavigator/oscillatingrecastmeshobject.cpp @@ -1,18 +1,39 @@ #include "oscillatingrecastmeshobject.hpp" +#include "tilebounds.hpp" #include +#include + namespace DetourNavigator { - OscillatingRecastMeshObject::OscillatingRecastMeshObject(RecastMeshObject impl, std::size_t lastChangeRevision) + namespace + { + void limitBy(btAABB& aabb, const TileBounds& bounds) + { + aabb.m_min.setX(std::max(aabb.m_min.x(), static_cast(bounds.mMin.x()))); + aabb.m_min.setY(std::max(aabb.m_min.y(), static_cast(bounds.mMin.y()))); + aabb.m_max.setX(std::min(aabb.m_max.x(), static_cast(bounds.mMax.x()))); + aabb.m_max.setY(std::min(aabb.m_max.y(), static_cast(bounds.mMax.y()))); + } + } + + OscillatingRecastMeshObject::OscillatingRecastMeshObject(RecastMeshObject&& impl, std::size_t lastChangeRevision) : mImpl(std::move(impl)) , mLastChangeRevision(lastChangeRevision) , mAabb(BulletHelpers::getAabb(mImpl.getShape(), mImpl.getTransform())) { } + OscillatingRecastMeshObject::OscillatingRecastMeshObject(const RecastMeshObject& impl, std::size_t lastChangeRevision) + : mImpl(impl) + , mLastChangeRevision(lastChangeRevision) + , mAabb(BulletHelpers::getAabb(mImpl.getShape(), mImpl.getTransform())) + { + } + bool OscillatingRecastMeshObject::update(const btTransform& transform, const AreaType areaType, - std::size_t lastChangeRevision) + std::size_t lastChangeRevision, const TileBounds& bounds) { const btTransform oldTransform = mImpl.getTransform(); if (!mImpl.update(transform, areaType)) @@ -30,6 +51,7 @@ namespace DetourNavigator } const btAABB currentAabb = mAabb; mAabb.merge(BulletHelpers::getAabb(mImpl.getShape(), transform)); + limitBy(mAabb, bounds); return currentAabb != mAabb; } } diff --git a/components/detournavigator/oscillatingrecastmeshobject.hpp b/components/detournavigator/oscillatingrecastmeshobject.hpp index 476fd4e0d..f8aabce62 100644 --- a/components/detournavigator/oscillatingrecastmeshobject.hpp +++ b/components/detournavigator/oscillatingrecastmeshobject.hpp @@ -3,6 +3,7 @@ #include "areatype.hpp" #include "recastmeshobject.hpp" +#include "tilebounds.hpp" #include #include @@ -12,9 +13,11 @@ namespace DetourNavigator class OscillatingRecastMeshObject { public: - explicit OscillatingRecastMeshObject(RecastMeshObject impl, std::size_t lastChangeRevision); + explicit OscillatingRecastMeshObject(RecastMeshObject&& impl, std::size_t lastChangeRevision); + explicit OscillatingRecastMeshObject(const RecastMeshObject& impl, std::size_t lastChangeRevision); - bool update(const btTransform& transform, const AreaType areaType, std::size_t lastChangeRevision); + bool update(const btTransform& transform, const AreaType areaType, std::size_t lastChangeRevision, + const TileBounds& bounds); const RecastMeshObject& getImpl() const { return mImpl; } diff --git a/components/detournavigator/raycast.cpp b/components/detournavigator/raycast.cpp index 86fabe9c1..271da2249 100644 --- a/components/detournavigator/raycast.cpp +++ b/components/detournavigator/raycast.cpp @@ -2,7 +2,6 @@ #include "settings.hpp" #include "findsmoothpath.hpp" -#include #include #include diff --git a/components/detournavigator/recastmesh.cpp b/components/detournavigator/recastmesh.cpp index dc56f7b93..00d6ae556 100644 --- a/components/detournavigator/recastmesh.cpp +++ b/components/detournavigator/recastmesh.cpp @@ -6,19 +6,22 @@ namespace DetourNavigator { RecastMesh::RecastMesh(std::size_t generation, std::size_t revision, std::vector indices, std::vector vertices, - std::vector areaTypes, std::vector water, const std::size_t trianglesPerChunk) + std::vector areaTypes, std::vector water) : mGeneration(generation) , mRevision(revision) , mIndices(std::move(indices)) , mVertices(std::move(vertices)) , mAreaTypes(std::move(areaTypes)) , mWater(std::move(water)) - , mChunkyTriMesh(mVertices, mIndices, mAreaTypes, trianglesPerChunk) { if (getTrianglesCount() != mAreaTypes.size()) throw InvalidArgument("Number of flags doesn't match number of triangles: triangles=" + std::to_string(getTrianglesCount()) + ", areaTypes=" + std::to_string(mAreaTypes.size())); if (getVerticesCount()) rcCalcBounds(mVertices.data(), static_cast(getVerticesCount()), mBounds.mMin.ptr(), mBounds.mMax.ptr()); + mIndices.shrink_to_fit(); + mVertices.shrink_to_fit(); + mAreaTypes.shrink_to_fit(); + mWater.shrink_to_fit(); } } diff --git a/components/detournavigator/recastmesh.hpp b/components/detournavigator/recastmesh.hpp index 746422ac8..0e6bc4920 100644 --- a/components/detournavigator/recastmesh.hpp +++ b/components/detournavigator/recastmesh.hpp @@ -2,7 +2,6 @@ #define OPENMW_COMPONENTS_DETOURNAVIGATOR_RECASTMESH_H #include "areatype.hpp" -#include "chunkytrimesh.hpp" #include "bounds.hpp" #include @@ -12,8 +11,6 @@ #include #include -#include - #include namespace DetourNavigator @@ -28,7 +25,7 @@ namespace DetourNavigator }; RecastMesh(std::size_t generation, std::size_t revision, std::vector indices, std::vector vertices, - std::vector areaTypes, std::vector water, const std::size_t trianglesPerChunk); + std::vector areaTypes, std::vector water); std::size_t getGeneration() const { @@ -70,11 +67,6 @@ namespace DetourNavigator return mIndices.size() / 3; } - const ChunkyTriMesh& getChunkyTriMesh() const - { - return mChunkyTriMesh; - } - const Bounds& getBounds() const { return mBounds; @@ -87,7 +79,6 @@ namespace DetourNavigator std::vector mVertices; std::vector mAreaTypes; std::vector mWater; - ChunkyTriMesh mChunkyTriMesh; Bounds mBounds; }; diff --git a/components/detournavigator/recastmeshbuilder.cpp b/components/detournavigator/recastmeshbuilder.cpp index f8456acf0..8ecbf1ec8 100644 --- a/components/detournavigator/recastmeshbuilder.cpp +++ b/components/detournavigator/recastmeshbuilder.cpp @@ -1,5 +1,4 @@ #include "recastmeshbuilder.hpp" -#include "chunkytrimesh.hpp" #include "debug.hpp" #include "settings.hpp" #include "settingsutils.hpp" @@ -19,6 +18,7 @@ #include #include #include +#include namespace DetourNavigator { @@ -152,20 +152,11 @@ namespace DetourNavigator mWater.push_back(RecastMesh::Water {cellSize, transform}); } - std::shared_ptr RecastMeshBuilder::create(std::size_t generation, std::size_t revision) + std::shared_ptr RecastMeshBuilder::create(std::size_t generation, std::size_t revision) && { optimizeRecastMesh(mIndices, mVertices); std::sort(mWater.begin(), mWater.end()); - return std::make_shared(generation, revision, mIndices, mVertices, mAreaTypes, - mWater, mSettings.get().mTrianglesPerChunk); - } - - void RecastMeshBuilder::reset() - { - mIndices.clear(); - mVertices.clear(); - mAreaTypes.clear(); - mWater.clear(); + return std::make_shared(generation, revision, std::move(mIndices), std::move(mVertices), std::move(mAreaTypes), std::move(mWater)); } void RecastMeshBuilder::addObject(const btConcaveShape& shape, const btTransform& transform, diff --git a/components/detournavigator/recastmeshbuilder.hpp b/components/detournavigator/recastmeshbuilder.hpp index fc2bbbc02..cb1b79377 100644 --- a/components/detournavigator/recastmeshbuilder.hpp +++ b/components/detournavigator/recastmeshbuilder.hpp @@ -34,9 +34,7 @@ namespace DetourNavigator void addWater(const int mCellSize, const btTransform& transform); - std::shared_ptr create(std::size_t generation, std::size_t revision); - - void reset(); + std::shared_ptr create(std::size_t generation, std::size_t revision) &&; private: std::reference_wrapper mSettings; diff --git a/components/detournavigator/recastmeshmanager.cpp b/components/detournavigator/recastmeshmanager.cpp index 146038ae6..aa47e45d9 100644 --- a/components/detournavigator/recastmeshmanager.cpp +++ b/components/detournavigator/recastmeshmanager.cpp @@ -1,37 +1,38 @@ #include "recastmeshmanager.hpp" - -#include +#include "recastmeshbuilder.hpp" namespace DetourNavigator { RecastMeshManager::RecastMeshManager(const Settings& settings, const TileBounds& bounds, std::size_t generation) - : mGeneration(generation) - , mMeshBuilder(settings, bounds) + : mSettings(settings) + , mGeneration(generation) + , mTileBounds(bounds) { } - bool RecastMeshManager::addObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform, + bool RecastMeshManager::addObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform, const AreaType areaType) { + const std::lock_guard lock(mMutex); + const auto object = mObjects.lower_bound(id); + if (object != mObjects.end() && object->first == id) + return false; const auto iterator = mObjectsOrder.emplace(mObjectsOrder.end(), OscillatingRecastMeshObject(RecastMeshObject(shape, transform, areaType), mRevision + 1)); - if (!mObjects.emplace(id, iterator).second) - { - mObjectsOrder.erase(iterator); - return false; - } + mObjects.emplace_hint(object, id, iterator); ++mRevision; return true; } bool RecastMeshManager::updateObject(const ObjectId id, const btTransform& transform, const AreaType areaType) { + const std::lock_guard lock(mMutex); const auto object = mObjects.find(id); if (object == mObjects.end()) return false; const std::size_t lastChangeRevision = mLastNavMeshReportedChange.has_value() ? mLastNavMeshReportedChange->mRevision : mRevision; - if (!object->second->update(transform, areaType, lastChangeRevision)) + if (!object->second->update(transform, areaType, lastChangeRevision, mTileBounds)) return false; ++mRevision; return true; @@ -39,6 +40,7 @@ namespace DetourNavigator std::optional RecastMeshManager::removeObject(const ObjectId id) { + const std::lock_guard lock(mMutex); const auto object = mObjects.find(id); if (object == mObjects.end()) return std::nullopt; @@ -52,6 +54,7 @@ namespace DetourNavigator bool RecastMeshManager::addWater(const osg::Vec2i& cellPosition, const int cellSize, const btTransform& transform) { + const std::lock_guard lock(mMutex); const auto iterator = mWaterOrder.emplace(mWaterOrder.end(), Water {cellSize, transform}); if (!mWater.emplace(cellPosition, iterator).second) { @@ -64,6 +67,7 @@ namespace DetourNavigator std::optional RecastMeshManager::removeWater(const osg::Vec2i& cellPosition) { + const std::lock_guard lock(mMutex); const auto water = mWater.find(cellPosition); if (water == mWater.end()) return std::nullopt; @@ -76,12 +80,35 @@ namespace DetourNavigator std::shared_ptr RecastMeshManager::getMesh() { - rebuild(); - return mMeshBuilder.create(mGeneration, mRevision); + RecastMeshBuilder builder(mSettings, mTileBounds); + using Object = std::tuple< + osg::ref_ptr, + std::reference_wrapper, + btTransform, + AreaType + >; + std::vector objects; + std::size_t revision; + { + const std::lock_guard lock(mMutex); + for (const auto& v : mWaterOrder) + builder.addWater(v.mCellSize, v.mTransform); + objects.reserve(mObjectsOrder.size()); + for (const auto& object : mObjectsOrder) + { + const RecastMeshObject& impl = object.getImpl(); + objects.emplace_back(impl.getHolder(), impl.getShape(), impl.getTransform(), impl.getAreaType()); + } + revision = mRevision; + } + for (const auto& [holder, shape, transform, areaType] : objects) + builder.addObject(shape, transform, areaType); + return std::move(builder).create(mGeneration, revision); } bool RecastMeshManager::isEmpty() const { + const std::lock_guard lock(mMutex); return mObjects.empty(); } @@ -89,6 +116,7 @@ namespace DetourNavigator { if (recastMeshVersion.mGeneration != mGeneration) return; + const std::lock_guard lock(mMutex); if (mLastNavMeshReport.has_value() && navMeshVersion < mLastNavMeshReport->mNavMeshVersion) return; mLastNavMeshReport = {recastMeshVersion.mRevision, navMeshVersion}; @@ -97,15 +125,9 @@ namespace DetourNavigator mLastNavMeshReportedChange = mLastNavMeshReport; } - void RecastMeshManager::rebuild() + Version RecastMeshManager::getVersion() const { - mMeshBuilder.reset(); - for (const auto& v : mWaterOrder) - mMeshBuilder.addWater(v.mCellSize, v.mTransform); - for (const auto& object : mObjectsOrder) - { - const RecastMeshObject& v = object.getImpl(); - mMeshBuilder.addObject(v.getShape(), v.getTransform(), v.getAreaType()); - } + const std::lock_guard lock(mMutex); + return Version {mGeneration, mRevision}; } } diff --git a/components/detournavigator/recastmeshmanager.hpp b/components/detournavigator/recastmeshmanager.hpp index daa123fcb..bf60ad8f1 100644 --- a/components/detournavigator/recastmeshmanager.hpp +++ b/components/detournavigator/recastmeshmanager.hpp @@ -1,7 +1,6 @@ #ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_RECASTMESHMANAGER_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_RECASTMESHMANAGER_H -#include "recastmeshbuilder.hpp" #include "oscillatingrecastmeshobject.hpp" #include "objectid.hpp" #include "version.hpp" @@ -13,12 +12,16 @@ #include #include #include -#include +#include +#include class btCollisionShape; namespace DetourNavigator { + struct Settings; + class RecastMesh; + struct RemovedRecastMeshObject { std::reference_wrapper mShape; @@ -36,7 +39,7 @@ namespace DetourNavigator RecastMeshManager(const Settings& settings, const TileBounds& bounds, std::size_t generation); - bool addObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform, + bool addObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform, const AreaType areaType); bool updateObject(const ObjectId id, const btTransform& transform, const AreaType areaType); @@ -53,6 +56,8 @@ namespace DetourNavigator void reportNavMeshChange(Version recastMeshVersion, Version navMeshVersion); + Version getVersion() const; + private: struct Report { @@ -60,17 +65,17 @@ namespace DetourNavigator Version mNavMeshVersion; }; + const Settings& mSettings; + const std::size_t mGeneration; + const TileBounds mTileBounds; + mutable std::mutex mMutex; std::size_t mRevision = 0; - std::size_t mGeneration; - RecastMeshBuilder mMeshBuilder; std::list mObjectsOrder; - std::unordered_map::iterator> mObjects; + std::map::iterator> mObjects; std::list mWaterOrder; std::map::iterator> mWater; std::optional mLastNavMeshReportedChange; std::optional mLastNavMeshReport; - - void rebuild(); }; } diff --git a/components/detournavigator/recastmeshobject.cpp b/components/detournavigator/recastmeshobject.cpp index 3f3546278..69ef5db62 100644 --- a/components/detournavigator/recastmeshobject.cpp +++ b/components/detournavigator/recastmeshobject.cpp @@ -22,15 +22,36 @@ namespace DetourNavigator } return result; } + + std::vector makeChildrenObjects(const osg::ref_ptr& holder, + const btCompoundShape& shape, const AreaType areaType) + { + std::vector result; + for (int i = 0, num = shape.getNumChildShapes(); i < num; ++i) + { + const CollisionShape collisionShape {holder, *shape.getChildShape(i)}; + result.emplace_back(collisionShape, shape.getChildTransform(i), areaType); + } + return result; + } + + std::vector makeChildrenObjects(const osg::ref_ptr& holder, + const btCollisionShape& shape, const AreaType areaType) + { + if (shape.isCompound()) + return makeChildrenObjects(holder, static_cast(shape), areaType); + return std::vector(); + } } - RecastMeshObject::RecastMeshObject(const btCollisionShape& shape, const btTransform& transform, + RecastMeshObject::RecastMeshObject(const CollisionShape& shape, const btTransform& transform, const AreaType areaType) - : mShape(shape) + : mHolder(shape.getHolder()) + , mShape(shape.getShape()) , mTransform(transform) , mAreaType(areaType) - , mLocalScaling(shape.getLocalScaling()) - , mChildren(makeChildrenObjects(shape, mAreaType)) + , mLocalScaling(mShape.get().getLocalScaling()) + , mChildren(makeChildrenObjects(mHolder, mShape.get(), mAreaType)) { } @@ -57,20 +78,4 @@ namespace DetourNavigator || result; return result; } - - std::vector makeChildrenObjects(const btCollisionShape& shape, const AreaType areaType) - { - if (shape.isCompound()) - return makeChildrenObjects(static_cast(shape), areaType); - else - return std::vector(); - } - - std::vector makeChildrenObjects(const btCompoundShape& shape, const AreaType areaType) - { - std::vector result; - for (int i = 0, num = shape.getNumChildShapes(); i < num; ++i) - result.emplace_back(*shape.getChildShape(i), shape.getChildTransform(i), areaType); - return result; - } } diff --git a/components/detournavigator/recastmeshobject.hpp b/components/detournavigator/recastmeshobject.hpp index e659300e6..eefbc5384 100644 --- a/components/detournavigator/recastmeshobject.hpp +++ b/components/detournavigator/recastmeshobject.hpp @@ -5,6 +5,9 @@ #include +#include +#include + #include #include @@ -13,13 +16,34 @@ class btCompoundShape; namespace DetourNavigator { + class CollisionShape + { + public: + CollisionShape(osg::ref_ptr holder, const btCollisionShape& shape) + : mHolder(std::move(holder)) + , mShape(shape) + {} + + const osg::ref_ptr& getHolder() const { return mHolder; } + const btCollisionShape& getShape() const { return mShape; } + + private: + osg::ref_ptr mHolder; + std::reference_wrapper mShape; + }; + class RecastMeshObject { public: - RecastMeshObject(const btCollisionShape& shape, const btTransform& transform, const AreaType areaType); + RecastMeshObject(const CollisionShape& shape, const btTransform& transform, const AreaType areaType); bool update(const btTransform& transform, const AreaType areaType); + const osg::ref_ptr& getHolder() const + { + return mHolder; + } + const btCollisionShape& getShape() const { return mShape; @@ -36,16 +60,13 @@ namespace DetourNavigator } private: + osg::ref_ptr mHolder; std::reference_wrapper mShape; btTransform mTransform; AreaType mAreaType; btVector3 mLocalScaling; std::vector mChildren; }; - - std::vector makeChildrenObjects(const btCollisionShape& shape, const AreaType areaType); - - std::vector makeChildrenObjects(const btCompoundShape& shape, const AreaType areaType); } #endif diff --git a/components/detournavigator/recastmeshtiles.hpp b/components/detournavigator/recastmeshtiles.hpp index 68e30ba63..03059c744 100644 --- a/components/detournavigator/recastmeshtiles.hpp +++ b/components/detournavigator/recastmeshtiles.hpp @@ -2,13 +2,14 @@ #define OPENMW_COMPONENTS_DETOURNAVIGATOR_RECASTMESHTILE_H #include "tileposition.hpp" -#include "recastmesh.hpp" #include #include namespace DetourNavigator { + class RecastMesh; + using RecastMeshTiles = std::map>; } diff --git a/components/detournavigator/settings.cpp b/components/detournavigator/settings.cpp index 977b80cf5..ff99fae20 100644 --- a/components/detournavigator/settings.cpp +++ b/components/detournavigator/settings.cpp @@ -29,11 +29,11 @@ namespace DetourNavigator 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.mWaitUntilMinDistanceToPlayer = ::Settings::Manager::getInt("wait until min distance to player", "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"); diff --git a/components/detournavigator/settings.hpp b/components/detournavigator/settings.hpp index d73087b21..39f5815b8 100644 --- a/components/detournavigator/settings.hpp +++ b/components/detournavigator/settings.hpp @@ -31,11 +31,11 @@ namespace DetourNavigator int mRegionMergeSize = 0; int mRegionMinSize = 0; int mTileSize = 0; + int mWaitUntilMinDistanceToPlayer = 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; std::chrono::milliseconds mMinUpdateInterval; diff --git a/components/detournavigator/settingsutils.hpp b/components/detournavigator/settingsutils.hpp index 8f1c96e28..245e4a7e5 100644 --- a/components/detournavigator/settingsutils.hpp +++ b/components/detournavigator/settingsutils.hpp @@ -12,7 +12,8 @@ #include #include -#include +#include +#include namespace DetourNavigator { @@ -28,7 +29,7 @@ namespace DetourNavigator inline float getRadius(const Settings& settings, const osg::Vec3f& agentHalfExtents) { - return agentHalfExtents.x() * settings.mRecastScaleFactor; + return std::max(agentHalfExtents.x(), agentHalfExtents.y()) * std::sqrt(2) * settings.mRecastScaleFactor; } inline float toNavMeshCoordinates(const Settings& settings, float value) diff --git a/components/detournavigator/tilecachedrecastmeshmanager.cpp b/components/detournavigator/tilecachedrecastmeshmanager.cpp index 20ebc8fea..8e94f0994 100644 --- a/components/detournavigator/tilecachedrecastmeshmanager.cpp +++ b/components/detournavigator/tilecachedrecastmeshmanager.cpp @@ -3,32 +3,34 @@ #include "gettilespositions.hpp" #include "settingsutils.hpp" +#include +#include + namespace DetourNavigator { TileCachedRecastMeshManager::TileCachedRecastMeshManager(const Settings& settings) : mSettings(settings) {} - bool TileCachedRecastMeshManager::addObject(const ObjectId id, const btCollisionShape& shape, + bool TileCachedRecastMeshManager::addObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform, const AreaType areaType) { - bool result = false; - auto& tilesPositions = mObjectsTilesPositions[id]; + std::vector tilesPositions; const auto border = getBorderSize(mSettings); { auto tiles = mTiles.lock(); - getTilesPositions(shape, transform, mSettings, [&] (const TilePosition& tilePosition) + getTilesPositions(shape.getShape(), transform, mSettings, [&] (const TilePosition& tilePosition) { if (addTile(id, shape, transform, areaType, tilePosition, border, tiles.get())) - { - tilesPositions.insert(tilePosition); - result = true; - } + tilesPositions.push_back(tilePosition); }); } - if (result) - ++mRevision; - return result; + if (tilesPositions.empty()) + return false; + std::sort(tilesPositions.begin(), tilesPositions.end()); + mObjectsTilesPositions.insert_or_assign(id, std::move(tilesPositions)); + ++mRevision; + return true; } std::optional TileCachedRecastMeshManager::removeObject(const ObjectId id) @@ -65,7 +67,7 @@ namespace DetourNavigator const auto tiles = mTiles.lock(); for (auto& tile : *tiles) { - if (tile.second.addWater(cellPosition, cellSize, transform)) + if (tile.second->addWater(cellPosition, cellSize, transform)) { tilesPositions.push_back(tile.first); result = true; @@ -84,9 +86,9 @@ namespace DetourNavigator tileBounds.mMin -= osg::Vec2f(border, border); tileBounds.mMax += osg::Vec2f(border, border); tile = tiles->insert(std::make_pair(tilePosition, - CachedRecastMeshManager(mSettings, tileBounds, mTilesGeneration))).first; + std::make_shared(mSettings, tileBounds, mTilesGeneration))).first; } - if (tile->second.addWater(cellPosition, cellSize, transform)) + if (tile->second->addWater(cellPosition, cellSize, transform)) { tilesPositions.push_back(tilePosition); result = true; @@ -112,8 +114,8 @@ namespace DetourNavigator const auto tile = tiles->find(tilePosition); if (tile == tiles->end()) continue; - const auto tileResult = tile->second.removeWater(cellPosition); - if (tile->second.isEmpty()) + const auto tileResult = tile->second->removeWater(cellPosition); + if (tile->second->isEmpty()) { tiles->erase(tile); ++mTilesGeneration; @@ -128,11 +130,17 @@ namespace DetourNavigator std::shared_ptr TileCachedRecastMeshManager::getMesh(const TilePosition& tilePosition) { - const auto tiles = mTiles.lock(); - const auto it = tiles->find(tilePosition); - if (it == tiles->end()) + const auto manager = [&] () -> std::shared_ptr + { + const auto tiles = mTiles.lock(); + const auto it = tiles->find(tilePosition); + if (it == tiles->end()) + return nullptr; + return it->second; + } (); + if (manager == nullptr) return nullptr; - return it->second.getMesh(); + return manager->getMesh(); } bool TileCachedRecastMeshManager::hasTile(const TilePosition& tilePosition) @@ -151,12 +159,12 @@ namespace DetourNavigator const auto it = tiles->find(tilePosition); if (it == tiles->end()) return; - it->second.reportNavMeshChange(recastMeshVersion, navMeshVersion); + it->second->reportNavMeshChange(recastMeshVersion, navMeshVersion); } - bool TileCachedRecastMeshManager::addTile(const ObjectId id, const btCollisionShape& shape, + bool TileCachedRecastMeshManager::addTile(const ObjectId id, const CollisionShape& shape, const btTransform& transform, const AreaType areaType, const TilePosition& tilePosition, float border, - std::map& tiles) + TilesMap& tiles) { auto tile = tiles.find(tilePosition); if (tile == tiles.end()) @@ -165,26 +173,26 @@ namespace DetourNavigator tileBounds.mMin -= osg::Vec2f(border, border); tileBounds.mMax += osg::Vec2f(border, border); tile = tiles.insert(std::make_pair( - tilePosition, CachedRecastMeshManager(mSettings, tileBounds, mTilesGeneration))).first; + tilePosition, std::make_shared(mSettings, tileBounds, mTilesGeneration))).first; } - return tile->second.addObject(id, shape, transform, areaType); + 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 AreaType areaType, const TilePosition& tilePosition, TilesMap& tiles) { const auto tile = tiles.find(tilePosition); - return tile != tiles.end() && tile->second.updateObject(id, transform, areaType); + return tile != tiles.end() && tile->second->updateObject(id, transform, areaType); } std::optional TileCachedRecastMeshManager::removeTile(const ObjectId id, - const TilePosition& tilePosition, std::map& tiles) + const TilePosition& tilePosition, TilesMap& tiles) { const auto tile = tiles.find(tilePosition); if (tile == tiles.end()) return std::optional(); - const auto tileResult = tile->second.removeObject(id); - if (tile->second.isEmpty()) + const auto tileResult = tile->second->removeObject(id); + if (tile->second->isEmpty()) { tiles.erase(tile); ++mTilesGeneration; diff --git a/components/detournavigator/tilecachedrecastmeshmanager.hpp b/components/detournavigator/tilecachedrecastmeshmanager.hpp index 68683f410..23b2b63b7 100644 --- a/components/detournavigator/tilecachedrecastmeshmanager.hpp +++ b/components/detournavigator/tilecachedrecastmeshmanager.hpp @@ -9,9 +9,10 @@ #include +#include #include #include -#include +#include namespace DetourNavigator { @@ -20,11 +21,11 @@ namespace DetourNavigator public: TileCachedRecastMeshManager(const Settings& settings); - bool addObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform, + bool addObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform, const AreaType areaType); template - bool updateObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform, + bool updateObject(const ObjectId id, const CollisionShape& shape, const btTransform& transform, const AreaType areaType, OnChangedTile&& onChangedTile) { const auto object = mObjectsTilesPositions.find(id); @@ -33,14 +34,14 @@ namespace DetourNavigator auto& currentTiles = object->second; const auto border = getBorderSize(mSettings); bool changed = false; - std::set newTiles; + std::vector newTiles; { auto tiles = mTiles.lock(); const auto onTilePosition = [&] (const TilePosition& tilePosition) { - if (currentTiles.count(tilePosition)) + if (std::binary_search(currentTiles.begin(), currentTiles.end(), tilePosition)) { - newTiles.insert(tilePosition); + newTiles.push_back(tilePosition); if (updateTile(id, transform, areaType, tilePosition, tiles.get())) { onChangedTile(tilePosition); @@ -49,24 +50,27 @@ namespace DetourNavigator } else if (addTile(id, shape, transform, areaType, tilePosition, border, tiles.get())) { - newTiles.insert(tilePosition); + newTiles.push_back(tilePosition); onChangedTile(tilePosition); changed = true; } }; - getTilesPositions(shape, transform, mSettings, onTilePosition); + getTilesPositions(shape.getShape(), transform, mSettings, onTilePosition); + std::sort(newTiles.begin(), newTiles.end()); for (const auto& tile : currentTiles) { - if (!newTiles.count(tile) && removeTile(id, tile, tiles.get())) + if (!std::binary_search(newTiles.begin(), newTiles.end(), tile) && removeTile(id, tile, tiles.get())) { onChangedTile(tile); changed = true; } } } - std::swap(currentTiles, newTiles); if (changed) + { + currentTiles = std::move(newTiles); ++mRevision; + } return changed; } @@ -81,10 +85,10 @@ namespace DetourNavigator bool hasTile(const TilePosition& tilePosition); template - void forEachTilePosition(Function&& function) + void forEachTile(Function&& function) { - for (const auto& tile : *mTiles.lock()) - function(tile.first); + for (auto& [tilePosition, recastMeshManager] : *mTiles.lock()) + function(tilePosition, *recastMeshManager); } std::size_t getRevision() const; @@ -92,22 +96,23 @@ namespace DetourNavigator void reportNavMeshChange(const TilePosition& tilePosition, Version recastMeshVersion, Version navMeshVersion); private: + using TilesMap = std::map>; + const Settings& mSettings; - Misc::ScopeGuarded> mTiles; - std::unordered_map> mObjectsTilesPositions; + Misc::ScopeGuarded mTiles; + std::unordered_map> mObjectsTilesPositions; std::map> mWaterTilesPositions; std::size_t mRevision = 0; std::size_t mTilesGeneration = 0; - bool addTile(const ObjectId id, const btCollisionShape& shape, const btTransform& transform, - const AreaType areaType, const TilePosition& tilePosition, float border, - std::map& tiles); + bool addTile(const ObjectId id, const CollisionShape& shape, const btTransform& transform, + const AreaType areaType, const TilePosition& tilePosition, float border, TilesMap& tiles); bool updateTile(const ObjectId id, const btTransform& transform, const AreaType areaType, - const TilePosition& tilePosition, std::map& tiles); + const TilePosition& tilePosition, TilesMap& tiles); std::optional removeTile(const ObjectId id, const TilePosition& tilePosition, - std::map& tiles); + TilesMap& tiles); }; } diff --git a/components/detournavigator/waitconditiontype.hpp b/components/detournavigator/waitconditiontype.hpp new file mode 100644 index 000000000..06a590128 --- /dev/null +++ b/components/detournavigator/waitconditiontype.hpp @@ -0,0 +1,13 @@ +#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_WAITCONDITIONTYPE_H +#define OPENMW_COMPONENTS_DETOURNAVIGATOR_WAITCONDITIONTYPE_H + +namespace DetourNavigator +{ + enum class WaitConditionType + { + requiredTilesPresent, + allJobsDone, + }; +} + +#endif diff --git a/components/esm/aipackage.cpp b/components/esm/aipackage.cpp index abbd2c62c..fa20d271c 100644 --- a/components/esm/aipackage.cpp +++ b/components/esm/aipackage.cpp @@ -15,7 +15,12 @@ namespace ESM { AIPackage pack; if (esm.retSubName() == AI_CNDT) { - mList.back().mCellName = esm.getHString(); + if (mList.empty()) + { + esm.fail("AIPackge with an AI_CNDT applying to no cell."); + } else { + mList.back().mCellName = esm.getHString(); + } } else if (esm.retSubName() == AI_Wander) { pack.mType = AI_Wander; esm.getHExact(&pack.mWander, 14); diff --git a/components/esm/defs.hpp b/components/esm/defs.hpp index 9bf9b01f3..1b623f69f 100644 --- a/components/esm/defs.hpp +++ b/components/esm/defs.hpp @@ -85,7 +85,7 @@ bool inline operator!= (const Position& left, const Position& right) noexcept template struct FourCC { - static const unsigned int value = (((((d << 8) | c) << 8) | b) << 8) | a; + static constexpr unsigned int value = (((((d << 8) | c) << 8) | b) << 8) | a; }; enum RecNameInts diff --git a/components/esm/esmcommon.hpp b/components/esm/esmcommon.hpp index 232a24fcf..749e9a85d 100644 --- a/components/esm/esmcommon.hpp +++ b/components/esm/esmcommon.hpp @@ -1,6 +1,7 @@ #ifndef OPENMW_ESM_COMMON_H #define OPENMW_ESM_COMMON_H +#include #include #include #include @@ -110,15 +111,7 @@ struct FIXED_STRING<4> : public FIXED_STRING_BASE void assign(const std::string& value) { intval = 0; - size_t length = value.size(); - if (length == 0) return; - data[0] = value[0]; - if (length == 1) return; - data[1] = value[1]; - if (length == 2) return; - data[2] = value[2]; - if (length == 3) return; - data[3] = value[3]; + std::memcpy(data, value.data(), std::min(value.size(), sizeof(data))); } char const* ro_data() const { return data; } diff --git a/components/esm/esmreader.cpp b/components/esm/esmreader.cpp index 4e7dce876..b779d7f7f 100644 --- a/components/esm/esmreader.cpp +++ b/components/esm/esmreader.cpp @@ -97,13 +97,6 @@ void ESMReader::open(const std::string &file) open (Files::openConstrainedFileStream (file.c_str ()), file); } -int64_t ESMReader::getHNLong(const char *name) -{ - int64_t val; - getHNT(val, name); - return val; -} - std::string ESMReader::getHNOString(const char* name) { if (isNextSub(name)) @@ -210,21 +203,9 @@ void ESMReader::getSubName() } // reading the subrecord data anyway. - const size_t subNameSize = mCtx.subName.data_size(); + const int subNameSize = static_cast(mCtx.subName.data_size()); getExact(mCtx.subName.rw_data(), subNameSize); - mCtx.leftRec -= subNameSize; -} - -bool ESMReader::isEmptyOrGetName() -{ - if (mCtx.leftRec) - { - const size_t subNameSize = mCtx.subName.data_size(); - getExact(mCtx.subName.rw_data(), subNameSize); - mCtx.leftRec -= subNameSize; - return false; - } - return true; + mCtx.leftRec -= static_cast(subNameSize); } void ESMReader::skipHSub() @@ -343,10 +324,10 @@ std::string ESMReader::getString(int size) mBuffer[s] = 0; // read ESM data - char *ptr = &mBuffer[0]; + char *ptr = mBuffer.data(); getExact(ptr, size); - size = strnlen(ptr, size); + size = static_cast(strnlen(ptr, size)); // Convert to UTF8 and return if (mEncoder) @@ -373,7 +354,7 @@ void ESMReader::setEncoder(ToUTF8::Utf8Encoder* encoder) mEncoder = encoder; } -size_t ESMReader::getFileOffset() +size_t ESMReader::getFileOffset() const { return mEsm->tellg(); } diff --git a/components/esm/esmreader.hpp b/components/esm/esmreader.hpp index 761756e8f..503bb637d 100644 --- a/components/esm/esmreader.hpp +++ b/components/esm/esmreader.hpp @@ -73,7 +73,7 @@ public: void openRaw(const std::string &filename); /// Get the current position in the file. Make sure that the file has been opened! - size_t getFileOffset(); + size_t getFileOffset() const; // This is a quick hack for multiple esm/esp files. Each plugin introduces its own // terrain palette, but ESMReader does not pass a reference to the correct plugin @@ -128,8 +128,6 @@ public: getHT(x); } - int64_t getHNLong(const char *name); - // Get data of a given type/size, including subrecord header template void getHT(X &x) @@ -193,9 +191,6 @@ public: // slightly. void getSubName(); - // This is specially optimized for LoadINFO. - bool isEmptyOrGetName(); - // Skip current sub record, including header (but not including // name.) void skipHSub(); diff --git a/components/esm/esmwriter.cpp b/components/esm/esmwriter.cpp index 09fca4b26..f65340f70 100644 --- a/components/esm/esmwriter.cpp +++ b/components/esm/esmwriter.cpp @@ -217,7 +217,7 @@ namespace ESM if (mCounting && !mRecords.empty()) { for (std::list::iterator it = mRecords.begin(); it != mRecords.end(); ++it) - it->size += size; + it->size += static_cast(size); } mStream->write(data, size); diff --git a/components/esm/globalscript.cpp b/components/esm/globalscript.cpp index 239d162f2..016ea4f0c 100644 --- a/components/esm/globalscript.cpp +++ b/components/esm/globalscript.cpp @@ -13,8 +13,7 @@ void ESM::GlobalScript::load (ESMReader &esm) esm.getHNOT (mRunning, "RUN_"); mTargetRef.unset(); - if (esm.peekNextSub("TARG")) - mTargetId = esm.getHNString ("TARG"); + mTargetId = esm.getHNOString ("TARG"); if (esm.peekNextSub("FRMR")) mTargetRef.load(esm, true, "FRMR"); } diff --git a/components/esm/loadarmo.cpp b/components/esm/loadarmo.cpp index 929c111a9..674370253 100644 --- a/components/esm/loadarmo.cpp +++ b/components/esm/loadarmo.cpp @@ -90,7 +90,7 @@ namespace ESM if (!hasName) esm.fail("Missing NAME subrecord"); if (!hasData && !isDeleted) - esm.fail("Missing CTDT subrecord"); + esm.fail("Missing AODT subrecord"); } void Armor::save(ESMWriter &esm, bool isDeleted) const diff --git a/components/esm/loadland.cpp b/components/esm/loadland.cpp index 2aa8f21db..38596628f 100644 --- a/components/esm/loadland.cpp +++ b/components/esm/loadland.cpp @@ -161,9 +161,9 @@ namespace ESM { // Generate WNAM record signed char wnam[LAND_GLOBAL_MAP_LOD_SIZE]; - float max = std::numeric_limits::max(); - float min = std::numeric_limits::min(); - float vertMult = static_cast(ESM::Land::LAND_SIZE - 1) / LAND_GLOBAL_MAP_LOD_SIZE_SQRT; + constexpr float max = std::numeric_limits::max(); + constexpr float min = std::numeric_limits::min(); + constexpr float vertMult = static_cast(ESM::Land::LAND_SIZE - 1) / LAND_GLOBAL_MAP_LOD_SIZE_SQRT; for (int row = 0; row < LAND_GLOBAL_MAP_LOD_SIZE_SQRT; ++row) { for (int col = 0; col < LAND_GLOBAL_MAP_LOD_SIZE_SQRT; ++col) diff --git a/components/esm/loadpgrd.cpp b/components/esm/loadpgrd.cpp index 708685e72..9abeba260 100644 --- a/components/esm/loadpgrd.cpp +++ b/components/esm/loadpgrd.cpp @@ -100,6 +100,8 @@ namespace ESM for(PointList::const_iterator it = mPoints.begin(); it != mPoints.end(); ++it, ++pointIndex) { unsigned char connectionNum = (*it).mConnectionNum; + if (rawConnections.end() - rawIt < connectionNum) + esm.fail("Not enough connections"); for (int i = 0; i < connectionNum; ++i) { Edge edge; edge.mV0 = pointIndex; diff --git a/components/esm/loadscpt.cpp b/components/esm/loadscpt.cpp index 53b6aedd3..1ad90dbc3 100644 --- a/components/esm/loadscpt.cpp +++ b/components/esm/loadscpt.cpp @@ -41,7 +41,7 @@ namespace ESM // Support '\r' terminated strings like vanilla. See Bug #1324. std::replace(tmp.begin(), tmp.end(), '\r', '\0'); // Avoid heap corruption - if (!tmp.empty() && tmp[tmp.size()-1] != '\0') + if (tmp.back() != '\0') { tmp.emplace_back('\0'); std::stringstream ss; @@ -54,21 +54,25 @@ namespace ESM str = tmp.data(); } + const auto tmpEnd = tmp.data() + tmp.size(); for (size_t i = 0; i < mVarNames.size(); i++) { mVarNames[i] = std::string(str); str += mVarNames[i].size() + 1; - if (static_cast(str - tmp.data()) > tmp.size()) + if (str >= tmpEnd) { - // SCVR subrecord is unused and variable names are determined - // from the script source, so an overflow is not fatal. - std::stringstream ss; - ss << "String table overflow"; - ss << "\n File: " << esm.getName(); - ss << "\n Record: " << esm.getContext().recName.toString(); - ss << "\n Subrecord: " << "SCVR"; - ss << "\n Offset: 0x" << std::hex << esm.getFileOffset(); - Log(Debug::Verbose) << ss.str(); + if(str > tmpEnd) + { + // SCVR subrecord is unused and variable names are determined + // from the script source, so an overflow is not fatal. + std::stringstream ss; + ss << "String table overflow"; + ss << "\n File: " << esm.getName(); + ss << "\n Record: " << esm.getContext().recName.toString(); + ss << "\n Subrecord: " << "SCVR"; + ss << "\n Offset: 0x" << std::hex << esm.getFileOffset(); + Log(Debug::Verbose) << ss.str(); + } // Get rid of empty strings in the list. mVarNames.resize(i+1); break; diff --git a/components/esm/loadtes3.cpp b/components/esm/loadtes3.cpp index d953f1dc2..f5cbcd62f 100644 --- a/components/esm/loadtes3.cpp +++ b/components/esm/loadtes3.cpp @@ -41,7 +41,7 @@ void ESM::Header::load (ESMReader &esm) { MasterData m; m.name = esm.getHString(); - m.size = esm.getHNLong ("DATA"); + esm.getHNT(m.size, "DATA"); mMaster.push_back (m); } @@ -54,14 +54,14 @@ void ESM::Header::load (ESMReader &esm) esm.getSubHeader(); mSCRD.resize(esm.getSubSize()); if (!mSCRD.empty()) - esm.getExact(&mSCRD[0], mSCRD.size()); + esm.getExact(mSCRD.data(), mSCRD.size()); } if (esm.isNextSub("SCRS")) { esm.getSubHeader(); mSCRS.resize(esm.getSubSize()); if (!mSCRS.empty()) - esm.getExact(&mSCRS[0], mSCRS.size()); + esm.getExact(mSCRS.data(), mSCRS.size()); } } @@ -78,10 +78,9 @@ void ESM::Header::save (ESMWriter &esm) esm.writeT(mData.records); esm.endRecord("HEDR"); - for (std::vector::iterator iter = mMaster.begin(); - iter != mMaster.end(); ++iter) + for (const Header::MasterData& data : mMaster) { - esm.writeHNCString ("MAST", iter->name); - esm.writeHNT ("DATA", iter->size); + esm.writeHNCString ("MAST", data.name); + esm.writeHNT ("DATA", data.size); } } diff --git a/components/esm/statstate.cpp b/components/esm/statstate.cpp index b9ddc3efd..30d39e3c6 100644 --- a/components/esm/statstate.cpp +++ b/components/esm/statstate.cpp @@ -16,17 +16,16 @@ namespace ESM { int base = 0; esm.getHNT(base, "STBA"); - mBase = static_cast(base); + mBase = static_cast(base); int mod = 0; esm.getHNOT(mod, "STMO"); - mMod = static_cast(mod); + mMod = static_cast(mod); int current = 0; esm.getHNOT(current, "STCU"); - mCurrent = static_cast(current); + mCurrent = static_cast(current); - // mDamage was changed to a float; ensure backwards compatibility int oldDamage = 0; esm.getHNOT(oldDamage, "STDA"); mDamage = static_cast(oldDamage); diff --git a/components/esm/weatherstate.cpp b/components/esm/weatherstate.cpp index ff2528e58..3d94a445b 100644 --- a/components/esm/weatherstate.cpp +++ b/components/esm/weatherstate.cpp @@ -31,15 +31,15 @@ namespace ESM esm.getHNT(mNextWeather, nextWeatherRecord); esm.getHNT(mQueuedWeather, queuedWeatherRecord); - while(esm.peekNextSub(regionNameRecord)) + while (esm.isNextSub(regionNameRecord)) { - std::string regionID = esm.getHNString(regionNameRecord); + std::string regionID = esm.getHString(); RegionWeatherState region; esm.getHNT(region.mWeather, regionWeatherRecord); - while(esm.peekNextSub(regionChanceRecord)) + while (esm.isNextSub(regionChanceRecord)) { char chance; - esm.getHNT(chance, regionChanceRecord); + esm.getHT(chance); region.mChances.push_back(chance); } diff --git a/components/fallback/validate.cpp b/components/fallback/validate.cpp index 982c709af..a482d40fa 100644 --- a/components/fallback/validate.cpp +++ b/components/fallback/validate.cpp @@ -12,7 +12,7 @@ void Fallback::validate(boost::any& v, std::vector const& tokens, F for (const auto& token : tokens) { std::string temp = Files::EscapeHashString::processString(token); - size_t sep = temp.find(","); + size_t sep = temp.find(','); if (sep < 1 || sep == temp.length() - 1 || sep == std::string::npos) throw boost::program_options::validation_error(boost::program_options::validation_error::invalid_option_value); diff --git a/components/files/configurationmanager.cpp b/components/files/configurationmanager.cpp index 92d35a6b6..35679ef29 100644 --- a/components/files/configurationmanager.cpp +++ b/components/files/configurationmanager.cpp @@ -100,6 +100,17 @@ boost::program_options::variables_map ConfigurationManager::separateComposingVar void ConfigurationManager::mergeComposingVariables(boost::program_options::variables_map & first, boost::program_options::variables_map & second, boost::program_options::options_description& description) { + // There are a few places this assumes all variables are present in second, but it's never crashed in the wild, so it looks like that's guaranteed. + std::set replacedVariables; + if (description.find_nothrow("replace", false)) + { + auto replace = second["replace"]; + if (!replace.defaulted() && !replace.empty()) + { + std::vector replaceVector = replace.as().toStdStringVector(); + replacedVariables.insert(replaceVector.begin(), replaceVector.end()); + } + } for (const auto& option : description.options()) { if (option->semantic()->is_composing()) @@ -113,6 +124,12 @@ void ConfigurationManager::mergeComposingVariables(boost::program_options::varia continue; } + if (replacedVariables.count(name)) + { + firstPosition->second = second[name]; + continue; + } + if (second[name].defaulted() || second[name].empty()) continue; diff --git a/components/files/lowlevelfile.cpp b/components/files/lowlevelfile.cpp index 07915abba..d89ae5838 100644 --- a/components/files/lowlevelfile.cpp +++ b/components/files/lowlevelfile.cpp @@ -326,7 +326,7 @@ void LowLevelFile::seek (size_t position) { assert (mHandle != INVALID_HANDLE_VALUE); - if (SetFilePointer (mHandle, position, nullptr, SEEK_SET) == INVALID_SET_FILE_POINTER) + if (SetFilePointer (mHandle, static_cast(position), nullptr, SEEK_SET) == INVALID_SET_FILE_POINTER) if (GetLastError () != NO_ERROR) throw std::runtime_error ("A seek operation on a file failed."); } @@ -349,7 +349,7 @@ size_t LowLevelFile::read (void * data, size_t size) DWORD read; - if (!ReadFile (mHandle, data, size, &read, nullptr)) + if (!ReadFile (mHandle, data, static_cast(size), &read, nullptr)) throw std::runtime_error ("A read operation on a file failed."); return read; diff --git a/components/interpreter/defines.cpp b/components/interpreter/defines.cpp index 0ceed80d5..d2e7067c6 100644 --- a/components/interpreter/defines.cpp +++ b/components/interpreter/defines.cpp @@ -176,7 +176,8 @@ namespace Interpreter{ transform(temp.begin(), temp.end(), temp.begin(), ::tolower); } - if((found = check(temp, globals[j], &i, &start))){ + found = check(temp, globals[j], &i, &start); + if(found){ char type = context.getGlobalType(globals[j]); switch(type){ diff --git a/components/interpreter/runtime.cpp b/components/interpreter/runtime.cpp index 35ad93a8a..1cb8f558c 100644 --- a/components/interpreter/runtime.cpp +++ b/components/interpreter/runtime.cpp @@ -45,7 +45,7 @@ namespace Interpreter for (; index; --index) { - offset += std::strlen (literalBlock+offset) + 1; + offset += static_cast(std::strlen (literalBlock+offset)) + 1; if (offset / 4 >= static_cast (mCode[3])) throw std::out_of_range("out of range"); } diff --git a/components/loadinglistener/loadinglistener.hpp b/components/loadinglistener/loadinglistener.hpp index 93467c141..14a1b96f9 100644 --- a/components/loadinglistener/loadinglistener.hpp +++ b/components/loadinglistener/loadinglistener.hpp @@ -14,7 +14,7 @@ namespace Loading /// @note "non-important" labels may not show on screen if the loading process went so fast /// that the implementation decided not to show a loading screen at all. "important" labels /// will show in a separate message-box if the loading screen was not shown. - virtual void setLabel (const std::string& label, bool important=false, bool center=false) {} + virtual void setLabel (const std::string& label, bool important=false) {} /// Start a loading sequence. Must call loadingOff() when done. /// @note To get the loading screen to actually update, you must call setProgress / increaseProgress periodically. diff --git a/components/misc/algorithm.hpp b/components/misc/algorithm.hpp new file mode 100644 index 000000000..4d70afa86 --- /dev/null +++ b/components/misc/algorithm.hpp @@ -0,0 +1,36 @@ +#ifndef OPENMW_COMPONENTS_MISC_ALGORITHM_H +#define OPENMW_COMPONENTS_MISC_ALGORITHM_H + +#include +#include + +namespace Misc +{ + template + inline Iterator forEachUnique(Iterator begin, Iterator end, BinaryPredicate predicate, Function function) + { + static_assert( + std::is_base_of_v< + std::forward_iterator_tag, + typename std::iterator_traits::iterator_category + > + ); + if (begin == end) + return begin; + function(*begin); + auto last = begin; + ++begin; + while (begin != end) + { + if (!predicate(*begin, *last)) + { + function(*begin); + last = begin; + } + ++begin; + } + return begin; + } +} + +#endif diff --git a/components/misc/barrier.hpp b/components/misc/barrier.hpp index a5af9f565..b3fe944b0 100644 --- a/components/misc/barrier.hpp +++ b/components/misc/barrier.hpp @@ -2,7 +2,6 @@ #define OPENMW_BARRIER_H #include -#include #include namespace Misc @@ -11,15 +10,14 @@ namespace Misc class Barrier { public: - using BarrierCallback = std::function; /// @param count number of threads to wait on - /// @param func callable to be executed once after all threads have met - Barrier(int count, BarrierCallback&& func) : mThreadCount(count), mRendezvousCount(0), mGeneration(0) - , mFunc(std::forward(func)) + explicit Barrier(int count) : mThreadCount(count), mRendezvousCount(0), mGeneration(0) {} /// @brief stop execution of threads until count distinct threads reach this point - void wait() + /// @param func callable to be executed once after all threads have met + template + void wait(Callback&& func) { std::unique_lock lock(mMutex); @@ -29,7 +27,7 @@ namespace Misc { ++mGeneration; mRendezvousCount = 0; - mFunc(); + func(); mRendezvous.notify_all(); } else @@ -44,7 +42,6 @@ namespace Misc int mGeneration; mutable std::mutex mMutex; std::condition_variable mRendezvous; - BarrierCallback mFunc; }; } diff --git a/components/misc/guarded.hpp b/components/misc/guarded.hpp index 55a2c670c..7f1005fc3 100644 --- a/components/misc/guarded.hpp +++ b/components/misc/guarded.hpp @@ -75,7 +75,7 @@ namespace Misc return Locked(mMutex, mValue); } - Locked lockConst() + Locked lockConst() const { return Locked(mMutex, mValue); } @@ -88,7 +88,7 @@ namespace Misc } private: - std::mutex mMutex; + mutable std::mutex mMutex; T mValue; }; } diff --git a/components/misc/stringops.hpp b/components/misc/stringops.hpp index 5d5acb4d4..665ee003e 100644 --- a/components/misc/stringops.hpp +++ b/components/misc/stringops.hpp @@ -45,19 +45,19 @@ public: { // Russian alphabet if (ch >= 0x0410 && ch < 0x0430) - return ch += 0x20; + return ch + 0x20; // Cyrillic IO character if (ch == 0x0401) - return ch += 0x50; + return ch + 0x50; // Latin alphabet if (ch >= 0x41 && ch < 0x60) - return ch += 0x20; + return ch + 0x20; // Deutch characters if (ch == 0xc4 || ch == 0xd6 || ch == 0xdc) - return ch += 0x20; + return ch + 0x20; if (ch == 0x1e9e) return 0xdf; diff --git a/components/myguiplatform/scalinglayer.cpp b/components/myguiplatform/scalinglayer.cpp index 99ed6d07a..51c148253 100644 --- a/components/myguiplatform/scalinglayer.cpp +++ b/components/myguiplatform/scalinglayer.cpp @@ -74,8 +74,8 @@ namespace osgMyGUI _left -= globalViewSize.width/2; _top -= globalViewSize.height/2; - _left /= scale; - _top /= scale; + _left = static_cast(_left/scale); + _top = static_cast(_top/scale); _left += mViewSize.width/2; _top += mViewSize.height/2; @@ -84,8 +84,8 @@ namespace osgMyGUI float ScalingLayer::getScaleFactor() const { MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize(); - float w = viewSize.width; - float h = viewSize.height; + float w = static_cast(viewSize.width); + float h = static_cast(viewSize.height); float heightScale = (h / mViewSize.height); float widthScale = (w / mViewSize.width); @@ -103,8 +103,8 @@ namespace osgMyGUI MyGUI::IntSize globalViewSize = MyGUI::RenderManager::getInstance().getViewSize(); MyGUI::IntSize viewSize = globalViewSize; float scale = getScaleFactor(); - viewSize.width /= scale; - viewSize.height /= scale; + viewSize.width = static_cast(viewSize.width / scale); + viewSize.height = static_cast(viewSize.height / scale); float hoffset = (globalViewSize.width - mViewSize.width*getScaleFactor())/2.f / static_cast(globalViewSize.width); float voffset = (globalViewSize.height - mViewSize.height*getScaleFactor())/2.f / static_cast(globalViewSize.height); diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index 3e226b35e..08301ce47 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -212,7 +212,7 @@ void NIFFile::parse(Files::IStreamPtr stream) userVer = nif.getUInt(); // Number of records - unsigned int recNum = nif.getUInt(); + const std::size_t recNum = nif.getUInt(); records.resize(recNum); // Bethesda stream header @@ -251,7 +251,7 @@ void NIFFile::parse(Files::IStreamPtr stream) std::vector recSizes; // Currently unused nif.getUInts(recSizes, recNum); } - unsigned int stringNum = nif.getUInt(); + const std::size_t stringNum = nif.getUInt(); nif.getUInt(); // Max string length if (stringNum) nif.getSizedStrings(strings, stringNum); @@ -264,7 +264,7 @@ void NIFFile::parse(Files::IStreamPtr stream) } const bool hasRecordSeparators = ver >= NIFStream::generateVersion(10,0,0,0) && ver < NIFStream::generateVersion(10,2,0,0); - for (unsigned int i = 0; i < recNum; i++) + for (std::size_t i = 0; i < recNum; i++) { Record *r = nullptr; @@ -308,21 +308,21 @@ void NIFFile::parse(Files::IStreamPtr stream) r->read(&nif); } - unsigned int rootNum = nif.getUInt(); + const std::size_t rootNum = nif.getUInt(); roots.resize(rootNum); //Determine which records are roots - for (unsigned int i = 0; i < rootNum; i++) + for (std::size_t i = 0; i < rootNum; i++) { int idx = nif.getInt(); - if (idx >= 0 && idx < int(records.size())) + if (idx >= 0 && static_cast(idx) < records.size()) { roots[i] = records[idx]; } else { roots[i] = nullptr; - warn("Null Root found"); + warn("Root " + std::to_string(i + 1) + " does not point to a record: index " + std::to_string(idx)); } } diff --git a/components/nif/nifstream.cpp b/components/nif/nifstream.cpp index 07c9c917c..6129a1739 100644 --- a/components/nif/nifstream.cpp +++ b/components/nif/nifstream.cpp @@ -7,7 +7,7 @@ namespace Nif osg::Quat NIFStream::getQuaternion() { float f[4]; - readLittleEndianBufferOfType<4, float>(inp, (float*)&f); + readLittleEndianBufferOfType<4, float>(inp, f); osg::Quat quat; quat.w() = f[0]; quat.x() = f[1]; diff --git a/components/nif/nifstream.hpp b/components/nif/nifstream.hpp index 4d221b867..b6bf01ce5 100644 --- a/components/nif/nifstream.hpp +++ b/components/nif/nifstream.hpp @@ -7,6 +7,8 @@ #include #include #include +#include +#include #include #include @@ -22,31 +24,32 @@ namespace Nif class NIFFile; -/* - readLittleEndianBufferOfType: This template should only be used with arithmetic types -*/ -template inline void readLittleEndianBufferOfType(Files::IStreamPtr &pIStream, T* dest) +template inline void readLittleEndianBufferOfType(Files::IStreamPtr &pIStream, T* dest) { + static_assert(std::is_arithmetic_v, "Buffer element type is not arithmetic"); pIStream->read((char*)dest, numInstances * sizeof(T)); + if (pIStream->bad()) + throw std::runtime_error("Failed to read little endian typed (" + std::string(typeid(T).name()) + ") buffer of " + + std::to_string(numInstances) + " instances"); if constexpr (Misc::IS_BIG_ENDIAN) - for (uint32_t i = 0; i < numInstances; i++) + for (std::size_t i = 0; i < numInstances; i++) Misc::swapEndiannessInplace(dest[i]); } -/* - readLittleEndianDynamicBufferOfType: This template should only be used with arithmetic types -*/ -template inline void readLittleEndianDynamicBufferOfType(Files::IStreamPtr &pIStream, T* dest, uint32_t numInstances) +template inline void readLittleEndianDynamicBufferOfType(Files::IStreamPtr &pIStream, T* dest, std::size_t numInstances) { + static_assert(std::is_arithmetic_v, "Buffer element type is not arithmetic"); pIStream->read((char*)dest, numInstances * sizeof(T)); + if (pIStream->bad()) + throw std::runtime_error("Failed to read little endian dynamic buffer of " + std::to_string(numInstances) + " instances"); if constexpr (Misc::IS_BIG_ENDIAN) - for (uint32_t i = 0; i < numInstances; i++) + for (std::size_t i = 0; i < numInstances; i++) Misc::swapEndiannessInplace(dest[i]); } template type inline readLittleEndianType(Files::IStreamPtr &pIStream) { type val; - readLittleEndianBufferOfType<1, type>(pIStream, (type*)&val); + readLittleEndianBufferOfType<1, type>(pIStream, &val); return val; } @@ -96,21 +99,21 @@ public: osg::Vec2f getVector2() { osg::Vec2f vec; - readLittleEndianBufferOfType<2,float>(inp, (float*)&vec._v[0]); + readLittleEndianBufferOfType<2,float>(inp, vec._v); return vec; } osg::Vec3f getVector3() { osg::Vec3f vec; - readLittleEndianBufferOfType<3, float>(inp, (float*)&vec._v[0]); + readLittleEndianBufferOfType<3, float>(inp, vec._v); return vec; } osg::Vec4f getVector4() { osg::Vec4f vec; - readLittleEndianBufferOfType<4, float>(inp, (float*)&vec._v[0]); + readLittleEndianBufferOfType<4, float>(inp, vec._v); return vec; } @@ -142,11 +145,11 @@ public: ///Read in a string of the given length std::string getSizedString(size_t length) { - std::vector str(length + 1, 0); - + std::string str(length, '\0'); inp->read(str.data(), length); - - return str.data(); + if (inp->bad()) + throw std::runtime_error("Failed to read sized string of " + std::to_string(length) + " chars"); + return str; } ///Read in a string of the length specified in the file std::string getSizedString() @@ -167,6 +170,8 @@ public: { std::string result; std::getline(*inp, result); + if (inp->bad()) + throw std::runtime_error("Failed to read version string"); return result; } diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index d72cef194..6ae175939 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -121,13 +121,14 @@ osg::ref_ptr BulletNifLoader::load(const Nif::File& nif) mAvoidStaticMesh.reset(); const size_t numRoots = nif.numRoots(); - std::vector roots; + std::vector roots; for (size_t i = 0; i < numRoots; ++i) { - Nif::Record* r = nif.getRoot(i); - assert(r != nullptr); - Nif::Node* node = nullptr; - if ((node = dynamic_cast(r))) + const Nif::Record* r = nif.getRoot(i); + if (!r) + continue; + const Nif::Node* node = dynamic_cast(r); + if (node) roots.emplace_back(node); } const std::string filename = nif.getFilename(); diff --git a/components/nifosg/controller.cpp b/components/nifosg/controller.cpp index 31fd92b43..d48c55ad7 100644 --- a/components/nifosg/controller.cpp +++ b/components/nifosg/controller.cpp @@ -277,19 +277,18 @@ void UVController::apply(osg::StateSet* stateset, osg::NodeVisitor* nv) if (hasInput()) { float value = getInputValue(nv); - float uTrans = mUTrans.interpKey(value); - float vTrans = mVTrans.interpKey(value); - float uScale = mUScale.interpKey(value); - float vScale = mVScale.interpKey(value); - osg::Matrix flipMat; - flipMat.preMultTranslate(osg::Vec3f(0,1,0)); - flipMat.preMultScale(osg::Vec3f(1,-1,1)); - - osg::Matrixf mat = osg::Matrixf::scale(uScale, vScale, 1); - mat.setTrans(uTrans, vTrans, 0); - - mat = flipMat * mat * flipMat; + // First scale the UV relative to its center, then apply the offset. + // U offset is flipped regardless of the graphics library, + // while V offset is flipped to account for OpenGL Y axis convention. + osg::Vec3f uvOrigin(0.5f, 0.5f, 0.f); + osg::Vec3f uvScale(mUScale.interpKey(value), mVScale.interpKey(value), 1.f); + osg::Vec3f uvTrans(-mUTrans.interpKey(value), -mVTrans.interpKey(value), 0.f); + + osg::Matrixf mat = osg::Matrixf::translate(uvOrigin); + mat.preMultScale(uvScale); + mat.preMultTranslate(-uvOrigin); + mat.setTrans(mat.getTrans() + uvTrans); // setting once is enough because all other texture units share the same TexMat (see setDefaults). if (!mTextureUnits.empty()) diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 5f083f925..702ab3366 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -254,8 +254,7 @@ namespace NifOsg for (size_t i = 0; i < numRoots; ++i) { const Nif::Record *r = nif->getRoot(i); - assert(r != nullptr); - if (r->recType == Nif::RC_NiSequenceStreamHelper) + if (r && r->recType == Nif::RC_NiSequenceStreamHelper) { seq = static_cast(r); break; @@ -312,8 +311,10 @@ namespace NifOsg for (size_t i = 0; i < numRoots; ++i) { const Nif::Record* r = nif->getRoot(i); - const Nif::Node* nifNode = nullptr; - if ((nifNode = dynamic_cast(r))) + if (!r) + continue; + const Nif::Node* nifNode = dynamic_cast(r); + if (nifNode) roots.emplace_back(nifNode); } if (roots.empty()) @@ -609,7 +610,8 @@ namespace NifOsg bool hasVisController = false; for (Nif::ControllerPtr ctrl = nifNode->controller; !ctrl.empty(); ctrl = ctrl->next) { - if ((hasVisController |= (ctrl->recType == Nif::RC_NiVisController))) + hasVisController |= (ctrl->recType == Nif::RC_NiVisController); + if (hasVisController) break; } @@ -952,13 +954,17 @@ namespace NifOsg } // Load the initial state of the particle system, i.e. the initial particles and their positions, velocity and colors. - void handleParticleInitialState(const Nif::Node* nifNode, osgParticle::ParticleSystem* partsys, const Nif::NiParticleSystemController* partctrl) + void handleParticleInitialState(const Nif::Node* nifNode, ParticleSystem* partsys, const Nif::NiParticleSystemController* partctrl) { auto particleNode = static_cast(nifNode); if (particleNode->data.empty() || particleNode->data->recType != Nif::RC_NiParticlesData) + { + partsys->setQuota(partctrl->numParticles); return; + } auto particledata = static_cast(particleNode->data.getPtr()); + partsys->setQuota(particledata->numParticles); osg::BoundingBox box; @@ -1093,8 +1099,6 @@ namespace NifOsg handleParticleInitialState(nifNode, partsys, partctrl); - partsys->setQuota(partctrl->numParticles); - partsys->getDefaultParticleTemplate().setSizeRange(osgParticle::rangef(partctrl->size, partctrl->size)); partsys->getDefaultParticleTemplate().setColorRange(osgParticle::rangev4(osg::Vec4f(1.f,1.f,1.f,1.f), osg::Vec4f(1.f,1.f,1.f,1.f))); partsys->getDefaultParticleTemplate().setAlphaRange(osgParticle::rangef(1.f, 1.f)); @@ -2007,6 +2011,11 @@ namespace NifOsg { osg::ref_ptr blendFunc (new osg::BlendFunc(getBlendMode((alphaprop->flags>>1)&0xf), getBlendMode((alphaprop->flags>>5)&0xf))); + // on AMD hardware, alpha still seems to be stored with an RGBA framebuffer with OpenGL. + // This might be mandated by the OpenGL 2.1 specification section 2.14.9, or might be a bug. + // Either way, D3D8.1 doesn't do that, so adapt the destination factor. + if (blendFunc->getDestination() == GL_DST_ALPHA) + blendFunc->setDestination(GL_ONE); blendFunc = shareAttribute(blendFunc); stateset->setAttributeAndModes(blendFunc, osg::StateAttribute::ON); diff --git a/components/resource/bulletshape.cpp b/components/resource/bulletshape.cpp index fc68c5545..f44e5a126 100644 --- a/components/resource/bulletshape.cpp +++ b/components/resource/bulletshape.cpp @@ -6,6 +6,7 @@ #include #include #include +#include namespace Resource { @@ -75,6 +76,9 @@ btCollisionShape* BulletShape::duplicateCollisionShape(const btCollisionShape *s return new btBoxShape(*boxshape); } + if (shape->getShapeType() == TERRAIN_SHAPE_PROXYTYPE) + return new btHeightfieldTerrainShape(static_cast(*shape)); + throw std::logic_error(std::string("Unhandled Bullet shape duplication: ")+shape->getName()); } diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index a3c751f7a..f6035a47d 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -378,7 +378,7 @@ namespace Resource Resource::ImageManager* mImageManager; }; - osg::ref_ptr load (Files::IStreamPtr file, const std::string& normalizedFilename, Resource::ImageManager* imageManager, Resource::NifFileManager* nifFileManager) + osg::ref_ptr load (const std::string& normalizedFilename, const VFS::Manager* vfs, Resource::ImageManager* imageManager, Resource::NifFileManager* nifFileManager) { std::string ext = Resource::getFileExtension(normalizedFilename); if (ext == "nif") @@ -400,7 +400,7 @@ namespace Resource options->setReadFileCallback(new ImageReadCallback(imageManager)); if (ext == "dae") options->setOptionString("daeUseSequencedTextureUnits"); - osgDB::ReaderWriter::ReadResult result = reader->readNode(*file, options); + osgDB::ReaderWriter::ReadResult result = reader->readNode(*vfs->get(normalizedFilename), options); if (!result.success()) { std::stringstream errormsg; @@ -500,7 +500,7 @@ namespace Resource { std::string str(env); - if(str.find("OFF")!=std::string::npos || str.find("0")!= std::string::npos) options = 0; + if(str.find("OFF")!=std::string::npos || str.find('0')!= std::string::npos) options = 0; if(str.find("~FLATTEN_STATIC_TRANSFORMS")!=std::string::npos) options ^= Optimizer::FLATTEN_STATIC_TRANSFORMS; else if(str.find("FLATTEN_STATIC_TRANSFORMS")!=std::string::npos) options |= Optimizer::FLATTEN_STATIC_TRANSFORMS; @@ -514,6 +514,12 @@ namespace Resource return options; } + void SceneManager::shareState(osg::ref_ptr node) { + mSharedStateMutex.lock(); + mSharedStateManager->share(node.get()); + mSharedStateMutex.unlock(); + } + osg::ref_ptr SceneManager::getTemplate(const std::string &name, bool compile) { std::string normalized = name; @@ -527,9 +533,7 @@ namespace Resource osg::ref_ptr loaded; try { - Files::IStreamPtr file = mVFS->get(normalized); - - loaded = load(file, normalized, mImageManager, mNifFileManager); + loaded = load(normalized, mVFS, mImageManager, mNifFileManager); } catch (std::exception& e) { @@ -541,8 +545,7 @@ namespace Resource if (mVFS->exists(normalized)) { Log(Debug::Error) << "Failed to load '" << name << "': " << e.what() << ", using marker_error." << sMeshTypes[i] << " instead"; - Files::IStreamPtr file = mVFS->get(normalized); - loaded = load(file, normalized, mImageManager, mNifFileManager); + loaded = load(normalized, mVFS, mImageManager, mNifFileManager); break; } } diff --git a/components/resource/scenemanager.hpp b/components/resource/scenemanager.hpp index 7635cd20f..de014165b 100644 --- a/components/resource/scenemanager.hpp +++ b/components/resource/scenemanager.hpp @@ -135,7 +135,7 @@ namespace Resource osg::ref_ptr createInstance(const std::string& name); osg::ref_ptr createInstance(const osg::Node* base); - + void shareState(osg::ref_ptr node); /// Get an instance of the given scene template /// @see getTemplate /// @note Thread safe. diff --git a/components/sceneutil/lightmanager.cpp b/components/sceneutil/lightmanager.cpp index f592460bb..85d8c1638 100644 --- a/components/sceneutil/lightmanager.cpp +++ b/components/sceneutil/lightmanager.cpp @@ -103,26 +103,11 @@ namespace SceneUtil : mData(new osg::FloatArray(3*4*count)) , mEndian(osg::getCpuByteOrder()) , mCount(count) - , mStride(12) , mCachedSunPos(osg::Vec4()) { - mOffsets[Diffuse] = 0; - mOffsets[Ambient] = 1; - mOffsets[Specular] = 2; - mOffsets[DiffuseSign] = 3; - mOffsets[Position] = 4; - mOffsets[AttenuationRadius] = 8; } - LightBuffer(const LightBuffer& copy) - : osg::Referenced() - , mData(copy.mData) - , mEndian(copy.mEndian) - , mCount(copy.mCount) - , mStride(copy.mStride) - , mOffsets(copy.mOffsets) - , mCachedSunPos(copy.mCachedSunPos) - {} + LightBuffer(const LightBuffer&) = delete; void setDiffuse(int index, const osg::Vec4& value) { @@ -173,7 +158,7 @@ namespace SceneUtil static constexpr int queryBlockSize(int sz) { - return 3 * osg::Vec4::num_components * sizeof(GL_FLOAT) * sz; + return 3 * osg::Vec4::num_components * sizeof(GLfloat) * sz; } void setCachedSunPos(const osg::Vec4& pos) @@ -192,42 +177,71 @@ namespace SceneUtil return mEndian == osg::BigEndian ? value.asABGR() : value.asRGBA(); } - int getOffset(int index, LayoutOffset slot) + int getOffset(int index, LayoutOffset slot) const { - return mStride * index + mOffsets[slot]; + return mOffsets.get(index, slot); } void configureLayout(int offsetColors, int offsetPosition, int offsetAttenuationRadius, int size, int stride) { - constexpr auto sizeofFloat = sizeof(GL_FLOAT); - constexpr auto sizeofVec4 = sizeofFloat * osg::Vec4::num_components; - - LightBuffer oldBuffer = LightBuffer(*this); - - mOffsets[Diffuse] = offsetColors / sizeofFloat; - mOffsets[Ambient] = mOffsets[Diffuse] + 1; - mOffsets[Specular] = mOffsets[Diffuse] + 2; - mOffsets[DiffuseSign] = mOffsets[Diffuse] + 3; - mOffsets[Position] = offsetPosition / sizeofFloat; - mOffsets[AttenuationRadius] = offsetAttenuationRadius / sizeofFloat; - mStride = (offsetAttenuationRadius + sizeofVec4 + stride) / 4; - - // Copy over previous buffers light data. Buffers populate before we know the layout. - mData->resize(size / sizeofFloat); - for (int i = 0; i < oldBuffer.mCount; ++i) + const Offsets offsets(offsetColors, offsetPosition, offsetAttenuationRadius, stride); + + // Copy cloned data using current layout into current data using new layout. + // This allows to preserve osg::FloatArray buffer object in mData. + const auto data = mData->asVector(); + mData->resizeArray(static_cast(size)); + for (int i = 0; i < mCount; ++i) { - std::memcpy(&(*mData)[getOffset(i, Diffuse)], &(*oldBuffer.mData)[oldBuffer.getOffset(i, Diffuse)], sizeof(osg::Vec4f)); - std::memcpy(&(*mData)[getOffset(i, Position)], &(*oldBuffer.mData)[oldBuffer.getOffset(i, Position)], sizeof(osg::Vec4f)); - std::memcpy(&(*mData)[getOffset(i, AttenuationRadius)], &(*oldBuffer.mData)[oldBuffer.getOffset(i, AttenuationRadius)], sizeof(osg::Vec4f)); + std::memcpy(&(*mData)[offsets.get(i, Diffuse)], data.data() + getOffset(i, Diffuse), sizeof(osg::Vec4f)); + std::memcpy(&(*mData)[offsets.get(i, Position)], data.data() + getOffset(i, Position), sizeof(osg::Vec4f)); + std::memcpy(&(*mData)[offsets.get(i, AttenuationRadius)], data.data() + getOffset(i, AttenuationRadius), sizeof(osg::Vec4f)); } + mOffsets = offsets; } private: + class Offsets + { + public: + Offsets() + : mStride(12) + { + mValues[Diffuse] = 0; + mValues[Ambient] = 1; + mValues[Specular] = 2; + mValues[DiffuseSign] = 3; + mValues[Position] = 4; + mValues[AttenuationRadius] = 8; + } + + Offsets(int offsetColors, int offsetPosition, int offsetAttenuationRadius, int stride) + : mStride((offsetAttenuationRadius + sizeof(GLfloat) * osg::Vec4::num_components + stride) / 4) + { + constexpr auto sizeofFloat = sizeof(GLfloat); + const auto diffuseOffset = offsetColors / sizeofFloat; + + mValues[Diffuse] = diffuseOffset; + mValues[Ambient] = diffuseOffset + 1; + mValues[Specular] = diffuseOffset + 2; + mValues[DiffuseSign] = diffuseOffset + 3; + mValues[Position] = offsetPosition / sizeofFloat; + mValues[AttenuationRadius] = offsetAttenuationRadius / sizeofFloat; + } + + int get(int index, LayoutOffset slot) const + { + return mStride * index + mValues[slot]; + } + + private: + int mStride; + std::array mValues; + }; + osg::ref_ptr mData; osg::Endian mEndian; int mCount; - int mStride; - std::array mOffsets; + Offsets mOffsets; osg::Vec4 mCachedSunPos; }; @@ -444,7 +458,11 @@ namespace SceneUtil void apply(osg::State &state) const override { - auto* lightUniform = mLightManager->getStateSet()->getUniform("LightBuffer"); + osg::StateSet* stateSet = mLightManager->getStateSet(); + if (!stateSet) + return; + + auto* lightUniform = stateSet->getUniform("LightBuffer"); for (size_t i = 0; i < mLights.size(); ++i) { auto light = mLights[i]; @@ -715,11 +733,10 @@ namespace SceneUtil { static const std::string dummyVertSource = generateDummyShader(mLightManager->getMaxLightsInScene()); - mDummyProgram->addShader(new osg::Shader(osg::Shader::VERTEX, dummyVertSource)); - mDummyProgram->addBindUniformBlock("LightBufferBinding", static_cast(Shader::UBOBinding::LightBuffer)); // Needed to query the layout of the buffer object. The layout specifier needed to use the std140 layout is not reliably // available, regardless of extensions, until GLSL 140. - mLightManager->getOrCreateStateSet()->setAttributeAndModes(mDummyProgram, osg::StateAttribute::ON); + mDummyProgram->addShader(new osg::Shader(osg::Shader::VERTEX, dummyVertSource)); + mDummyProgram->addBindUniformBlock("LightBufferBinding", static_cast(Shader::UBOBinding::LightBuffer)); } LightManagerStateAttribute(const LightManagerStateAttribute& copy, const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY) @@ -753,16 +770,14 @@ namespace SceneUtil ext->glGetActiveUniformsiv(handle, indices.size(), indices.data(), GL_UNIFORM_OFFSET, offsets.data()); for (int i = 0; i < 2; ++i) - { - auto& buf = mLightManager->getLightBuffer(i); - buf->configureLayout(offsets[0], offsets[1], offsets[2], totalBlockSize, stride); - } + mLightManager->getLightBuffer(i)->configureLayout(offsets[0], offsets[1], offsets[2], totalBlockSize, stride); } void apply(osg::State& state) const override { if (!mInitLayout) { + mDummyProgram->apply(state); auto handle = mDummyProgram->getPCP(state)->getHandle(); auto* ext = state.get(); @@ -837,6 +852,11 @@ namespace SceneUtil return ""; } + LightManager::~LightManager() + { + getOrCreateStateSet()->removeAttribute(osg::StateAttribute::LIGHT); + } + LightManager::LightManager(bool ffp) : mStartLight(0) , mLightingMask(~0u) @@ -864,8 +884,6 @@ namespace SceneUtil std::string lightingMethodString = Settings::Manager::getString("lighting method", "Shaders"); auto lightingMethod = LightManager::getLightingMethodFromString(lightingMethodString); - updateSettings(); - static bool hasLoggedWarnings = false; if (lightingMethod == LightingMethod::SingleUBO && !hasLoggedWarnings) @@ -884,6 +902,8 @@ namespace SceneUtil else initSingleUBO(targetLights); + updateSettings(); + getOrCreateStateSet()->addUniform(new osg::Uniform("PointLightCount", 0)); addCullCallback(new LightManagerCullCallback(this)); diff --git a/components/sceneutil/lightmanager.hpp b/components/sceneutil/lightmanager.hpp index 3b9d12634..d9a0dd2e7 100644 --- a/components/sceneutil/lightmanager.hpp +++ b/components/sceneutil/lightmanager.hpp @@ -137,6 +137,8 @@ namespace SceneUtil LightManager(const LightManager& copy, const osg::CopyOp& copyop); + ~LightManager(); + /// @param mask This mask is compared with the current Camera's cull mask to determine if lighting is desired. /// By default, it's ~0u i.e. always on. /// If you have some views that do not require lighting, then set the Camera's cull mask to not include diff --git a/components/sceneutil/mwshadowtechnique.cpp b/components/sceneutil/mwshadowtechnique.cpp index d5e3f0690..efc870885 100644 --- a/components/sceneutil/mwshadowtechnique.cpp +++ b/components/sceneutil/mwshadowtechnique.cpp @@ -568,8 +568,9 @@ MWShadowTechnique::ShadowData::ShadowData(MWShadowTechnique::ViewDependentData* _camera = new osg::Camera; _camera->setName("ShadowCamera"); _camera->setReferenceFrame(osg::Camera::ABSOLUTE_RF_INHERIT_VIEWPOINT); +#ifndef __APPLE__ // workaround shadow issue on macOS, https://gitlab.com/OpenMW/openmw/-/issues/6057 _camera->setImplicitBufferAttachmentMask(0, 0); - +#endif //_camera->setClearColor(osg::Vec4(1.0f,1.0f,1.0f,1.0f)); _camera->setClearColor(osg::Vec4(0.0f,0.0f,0.0f,0.0f)); @@ -908,6 +909,7 @@ void SceneUtil::MWShadowTechnique::setupCastingShader(Shader::ShaderManager & sh program->addShader(castingVertexShader); program->addShader(shaderManager.getShader("shadowcasting_fragment.glsl", { {"alphaFunc", std::to_string(alphaFunc)}, {"alphaToCoverage", "0"}, + {"adjustCoverage", "1"}, {"useGPUShader4", useGPUShader4} }, osg::Shader::FRAGMENT)); } @@ -1380,7 +1382,7 @@ void SceneUtil::MWShadowTechnique::castShadows(osgUtil::CullVisitor& cv, ViewDep if (settings->getMultipleShadowMapHint() == ShadowSettings::CASCADED) { cropShadowCameraToMainFrustum(frustum, camera, cascaseNear, cascadeFar, extraPlanes); - for (auto plane : extraPlanes) + for (const auto& plane : extraPlanes) local_polytope.getPlaneList().push_back(plane); local_polytope.setupMask(); } @@ -2150,19 +2152,19 @@ struct ConvexHull Vertices findInternalEdges(osg::Vec3d mainVertex, Vertices connectedVertices) { Vertices internalEdgeVertices; - for (auto vertex : connectedVertices) + for (const auto& vertex : connectedVertices) { osg::Matrixd matrix; osg::Vec3d dir = vertex - mainVertex; matrix.makeLookAt(mainVertex, vertex, dir.z() == 0 ? osg::Vec3d(0, 0, 1) : osg::Vec3d(1, 0, 0)); Vertices testVertices; - for (auto testVertex : connectedVertices) + for (const auto& testVertex : connectedVertices) { if (vertex != testVertex) testVertices.push_back(testVertex); } std::vector bearings; - for (auto testVertex : testVertices) + for (const auto& testVertex : testVertices) { osg::Vec3d transformedVertex = testVertex * matrix; bearings.push_back(atan2(transformedVertex.y(), transformedVertex.x())); @@ -2191,7 +2193,7 @@ struct ConvexHull // Collect the set of vertices VertexSet vertices; - for (Edge edge : _edges) + for (const Edge& edge : _edges) { vertices.insert(edge.first); vertices.insert(edge.second); @@ -2221,7 +2223,7 @@ struct ConvexHull for (auto vertex : extremeVertices) { Vertices connectedVertices; - for (Edge edge : _edges) + for (const Edge& edge : _edges) { if (edge.first == vertex) connectedVertices.push_back(edge.second); @@ -2259,13 +2261,13 @@ struct ConvexHull osg::Vec3d vertex = *unprocessedConnectedVertices.begin(); unprocessedConnectedVertices.erase(unprocessedConnectedVertices.begin()); connectedVertices.insert(vertex); - for (Edge edge : _edges) + for (const Edge& edge : _edges) { osg::Vec3d otherEnd; if (edge.first == vertex) otherEnd = edge.second; else if (edge.second == vertex) - otherEnd - edge.first; + otherEnd = edge.first; else continue; @@ -2276,7 +2278,7 @@ struct ConvexHull } } - for (Edge edge : _edges) + for (const Edge& edge : _edges) { if (connectedVertices.count(edge.first) || connectedVertices.count(edge.second)) finalEdges.push_back(edge); diff --git a/components/sceneutil/serialize.cpp b/components/sceneutil/serialize.cpp index 7e176be3d..1b5c1feed 100644 --- a/components/sceneutil/serialize.cpp +++ b/components/sceneutil/serialize.cpp @@ -131,9 +131,11 @@ void registerSerializers() "SceneUtil::StateSetUpdater", "SceneUtil::DisableLight", "SceneUtil::MWShadowTechnique", + "SceneUtil::TextKeyMapHolder", + "Shader::RemovedAlphaFunc", + "NifOsg::LightManagerStateAttribute", "NifOsg::FlipController", "NifOsg::KeyframeController", - "NifOsg::TextKeyMapHolder", "NifOsg::Emitter", "NifOsg::ParticleColorAffector", "NifOsg::ParticleSystem", @@ -147,6 +149,7 @@ void registerSerializers() "NifOsg::VisController", "osgMyGUI::Drawable", "osg::DrawCallback", + "osg::UniformBufferObject", "osgOQ::ClearQueriesCallback", "osgOQ::RetrieveQueriesCallback", "osg::DummyObject" diff --git a/components/sceneutil/shadowsbin.cpp b/components/sceneutil/shadowsbin.cpp index 5a4096f5c..abc1fa8b4 100644 --- a/components/sceneutil/shadowsbin.cpp +++ b/components/sceneutil/shadowsbin.cpp @@ -194,8 +194,9 @@ void ShadowsBin::sortImplementation() // noTestRoot is now a stategraph with useDiffuseMapForShadowAlpha disabled but minimal other state bool cullFaceOverridden = false; - while ((root = root->_parent)) + while (root->_parent) { + root = root->_parent; if (!root->getStateSet()) continue; unsigned int cullFaceFlags = root->getStateSet()->getMode(GL_CULL_FACE); diff --git a/components/sdlutil/sdlinputwrapper.cpp b/components/sdlutil/sdlinputwrapper.cpp index 705301714..b55b536c2 100644 --- a/components/sdlutil/sdlinputwrapper.cpp +++ b/components/sdlutil/sdlinputwrapper.cpp @@ -53,9 +53,14 @@ InputWrapper::InputWrapper(SDL_Window* window, osg::ref_ptr v if (windowEventsOnly) { - // During loading, just handle window events, and keep others for later + // During loading, handle window events, discard button presses and keep others for later while (SDL_PeepEvents(&evt, 1, SDL_GETEVENT, SDL_WINDOWEVENT, SDL_WINDOWEVENT)) handleWindowEvent(evt); + + SDL_FlushEvent(SDL_KEYDOWN); + SDL_FlushEvent(SDL_CONTROLLERBUTTONDOWN); + SDL_FlushEvent(SDL_MOUSEBUTTONDOWN); + return; } diff --git a/components/settings/parser.cpp b/components/settings/parser.cpp index 9693bf511..24f359b31 100644 --- a/components/settings/parser.cpp +++ b/components/settings/parser.cpp @@ -7,19 +7,36 @@ #include -void Settings::SettingsFileParser::loadSettingsFile(const std::string& file, CategorySettingValueMap& settings) +#include + +void Settings::SettingsFileParser::loadSettingsFile(const std::string& file, CategorySettingValueMap& settings, bool base64Encoded) { mFile = file; - boost::filesystem::ifstream stream; - stream.open(boost::filesystem::path(file)); + boost::filesystem::ifstream fstream; + fstream.open(boost::filesystem::path(file)); + auto stream = std::ref(fstream); + + std::istringstream decodedStream; + if (base64Encoded) + { + std::string base64String(std::istreambuf_iterator(fstream), {}); + std::string decodedString; + auto result = Base64::Base64::Decode(base64String, decodedString); + if (!result.empty()) + fail("Could not decode Base64 file: " + result); + // Move won't do anything until C++20, but won't hurt to do it anyway. + decodedStream.str(std::move(decodedString)); + stream = std::ref(decodedStream); + } + Log(Debug::Info) << "Loading settings file: " << file; std::string currentCategory; mLine = 0; - while (!stream.eof() && !stream.fail()) + while (!stream.get().eof() && !stream.get().fail()) { ++mLine; std::string line; - std::getline( stream, line ); + std::getline( stream.get(), line ); size_t i = 0; if (!skipWhiteSpace(i, line)) @@ -255,7 +272,7 @@ void Settings::SettingsFileParser::saveSettingsFile(const std::string& file, con ostream << "# This is the OpenMW user 'settings.cfg' file. This file only contains" << std::endl; ostream << "# explicitly changed settings. If you would like to revert a setting" << std::endl; ostream << "# to its default, simply remove it from this file. For available" << std::endl; - ostream << "# settings, see the file 'settings-default.cfg' or the documentation at:" << std::endl; + ostream << "# settings, see the file 'files/settings-default.cfg' in our source repo or the documentation at:" << std::endl; ostream << "#" << std::endl; ostream << "# https://openmw.readthedocs.io/en/master/reference/modding/settings/index.html" << std::endl; } diff --git a/components/settings/parser.hpp b/components/settings/parser.hpp index 449e54223..69e9cdaa4 100644 --- a/components/settings/parser.hpp +++ b/components/settings/parser.hpp @@ -10,7 +10,7 @@ namespace Settings class SettingsFileParser { public: - void loadSettingsFile(const std::string& file, CategorySettingValueMap& settings); + void loadSettingsFile(const std::string& file, CategorySettingValueMap& settings, bool base64encoded = false); void saveSettingsFile(const std::string& file, const CategorySettingValueMap& settings); diff --git a/components/settings/settings.cpp b/components/settings/settings.cpp index 629c954de..7249ec9d3 100644 --- a/components/settings/settings.cpp +++ b/components/settings/settings.cpp @@ -24,7 +24,7 @@ void Manager::clear() void Manager::loadDefault(const std::string &file) { SettingsFileParser parser; - parser.loadSettingsFile(file, mDefaultSettings); + parser.loadSettingsFile(file, mDefaultSettings, true); } void Manager::loadUser(const std::string& file) @@ -61,7 +61,7 @@ std::string Manager::getString(const std::string &setting, const std::string &ca return it->second; throw std::runtime_error(std::string("Trying to retrieve a non-existing setting: ") + setting - + ".\nMake sure the settings-default.cfg file was properly installed."); + + ".\nMake sure the defaults.bin file was properly installed."); } float Manager::getFloat (const std::string& setting, const std::string& category) @@ -73,6 +73,15 @@ float Manager::getFloat (const std::string& setting, const std::string& category return number; } +double Manager::getDouble (const std::string& setting, const std::string& category) +{ + const std::string& value = getString(setting, category); + std::stringstream stream(value); + double number = 0.0; + stream >> number; + return number; +} + int Manager::getInt (const std::string& setting, const std::string& category) { const std::string& value = getString(setting, category); @@ -143,6 +152,13 @@ void Manager::setFloat (const std::string &setting, const std::string &category, setString(setting, category, stream.str()); } +void Manager::setDouble (const std::string &setting, const std::string &category, const double value) +{ + std::ostringstream stream; + stream << value; + setString(setting, category, stream.str()); +} + void Manager::setBool(const std::string &setting, const std::string &category, const bool value) { setString(setting, category, value ? "true" : "false"); diff --git a/components/settings/settings.hpp b/components/settings/settings.hpp index d902cd371..ed31901b2 100644 --- a/components/settings/settings.hpp +++ b/components/settings/settings.hpp @@ -48,6 +48,7 @@ namespace Settings static int getInt (const std::string& setting, const std::string& category); static float getFloat (const std::string& setting, const std::string& category); + static double getDouble (const std::string& setting, const std::string& category); static std::string getString (const std::string& setting, const std::string& category); static bool getBool (const std::string& setting, const std::string& category); static osg::Vec2f getVector2 (const std::string& setting, const std::string& category); @@ -55,6 +56,7 @@ namespace Settings static void setInt (const std::string& setting, const std::string& category, const int value); static void setFloat (const std::string& setting, const std::string& category, const float value); + static void setDouble (const std::string& setting, const std::string& category, const double value); static void setString (const std::string& setting, const std::string& category, const std::string& value); static void setBool (const std::string& setting, const std::string& category, const bool value); static void setVector2 (const std::string& setting, const std::string& category, const osg::Vec2f value); diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index 2e0bec023..8edb51f95 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -22,6 +22,68 @@ namespace Shader { + class AddedState : public osg::Object + { + public: + AddedState() = default; + AddedState(const AddedState& rhs, const osg::CopyOp& copyOp) + : osg::Object(rhs, copyOp) + , mUniforms(rhs.mUniforms) + , mModes(rhs.mModes) + , mAttributes(rhs.mAttributes) + { + } + + void addUniform(const std::string& name) { mUniforms.emplace(name); } + void setMode(osg::StateAttribute::GLMode mode) { mModes.emplace(mode); } + void setAttribute(osg::StateAttribute::TypeMemberPair typeMemberPair) { mAttributes.emplace(typeMemberPair); } + + void setAttribute(const osg::StateAttribute* attribute) + { + mAttributes.emplace(attribute->getTypeMemberPair()); + } + template + void setAttribute(osg::ref_ptr attribute) { setAttribute(attribute.get()); } + + void setAttributeAndModes(const osg::StateAttribute* attribute) + { + setAttribute(attribute); + InterrogateModesHelper helper(this); + attribute->getModeUsage(helper); + } + template + void setAttributeAndModes(osg::ref_ptr attribute) { setAttributeAndModes(attribute.get()); } + + bool hasUniform(const std::string& name) { return mUniforms.count(name); } + bool hasMode(osg::StateAttribute::GLMode mode) { return mModes.count(mode); } + bool hasAttribute(osg::StateAttribute::TypeMemberPair typeMemberPair) { return mAttributes.count(typeMemberPair); } + bool hasAttribute(osg::StateAttribute::Type type, unsigned int member) { return hasAttribute(osg::StateAttribute::TypeMemberPair(type, member)); } + + const std::set& getAttributes() { return mAttributes; } + + bool empty() + { + return mUniforms.empty() && mModes.empty() && mAttributes.empty(); + } + + META_Object(Shader, AddedState) + + private: + class InterrogateModesHelper : public osg::StateAttribute::ModeUsage + { + public: + InterrogateModesHelper(AddedState* tracker) : mTracker(tracker) {} + void usesMode(osg::StateAttribute::GLMode mode) override { mTracker->setMode(mode); } + void usesTextureMode(osg::StateAttribute::GLMode mode) override {} + + private: + AddedState* mTracker; + }; + + std::unordered_set mUniforms; + std::unordered_set mModes; + std::set mAttributes; + }; ShaderVisitor::ShaderRequirements::ShaderRequirements() : mShaderRequired(false) @@ -105,14 +167,32 @@ namespace Shader return static_cast(stateSet.getUserDataContainer()->getUserObject("removedState")); } - void updateRemovedState(osg::UserDataContainer& userData, osg::StateSet* stateSet) + void updateRemovedState(osg::UserDataContainer& userData, osg::StateSet* removedState) { unsigned int index = userData.getUserObjectIndex("removedState"); if (index < userData.getNumUserObjects()) - userData.setUserObject(index, stateSet); + userData.setUserObject(index, removedState); else - userData.addUserObject(stateSet); - stateSet->setName("removedState"); + userData.addUserObject(removedState); + removedState->setName("removedState"); + } + + AddedState* getAddedState(osg::StateSet& stateSet) + { + if (!stateSet.getUserDataContainer()) + return nullptr; + + return static_cast(stateSet.getUserDataContainer()->getUserObject("addedState")); + } + + void updateAddedState(osg::UserDataContainer& userData, AddedState* addedState) + { + unsigned int index = userData.getUserObjectIndex("addedState"); + if (index < userData.getNumUserObjects()) + userData.setUserObject(index, addedState); + else + userData.addUserObject(addedState); + addedState->setName("addedState"); } const char* defaultTextures[] = { "diffuseMap", "normalMap", "emissiveMap", "darkMap", "detailMap", "envMap", "specularMap", "decalMap", "bumpMap" }; @@ -280,10 +360,14 @@ namespace Shader osg::StateSet::AttributeList removedAttributes; if (osg::ref_ptr removedState = getRemovedState(*stateset)) removedAttributes = removedState->getAttributeList(); - for (const auto& attributeMap : { attributes, removedAttributes }) + osg::ref_ptr addedState = getAddedState(*stateset); + + for (const auto* attributeMap : std::initializer_list{ &attributes, &removedAttributes }) { - for (osg::StateSet::AttributeList::const_iterator it = attributeMap.begin(); it != attributeMap.end(); ++it) + for (osg::StateSet::AttributeList::const_iterator it = attributeMap->begin(); it != attributeMap->end(); ++it) { + if (addedState && attributeMap != &removedAttributes && addedState->hasAttribute(it->first)) + continue; if (it->first.first == osg::StateAttribute::MATERIAL) { // This should probably be moved out of ShaderRequirements and be applied directly now it's a uniform instead of a define @@ -294,9 +378,6 @@ namespace Shader const osg::Material* mat = static_cast(it->second.first.get()); - if (!writableStateSet) - writableStateSet = getWritableStateSet(node); - int colorMode; switch (mat->getColorMode()) { @@ -374,6 +455,10 @@ namespace Shader writableStateSet = node.getOrCreateStateSet(); else writableStateSet = getWritableStateSet(node); + osg::ref_ptr addedState = new AddedState; + osg::ref_ptr previousAddedState = getAddedState(*writableStateSet); + if (!previousAddedState) + previousAddedState = new AddedState; ShaderManager::DefineMap defineMap; for (unsigned int i=0; iaddUniform(new osg::Uniform("colorMode", reqs.mColorMode)); + addedState->addUniform("colorMode"); defineMap["alphaFunc"] = std::to_string(reqs.mAlphaFunc); @@ -402,23 +488,35 @@ namespace Shader removedState = new osg::StateSet(); defineMap["alphaToCoverage"] = "0"; + defineMap["adjustCoverage"] = "0"; if (reqs.mAlphaFunc != osg::AlphaFunc::ALWAYS) { writableStateSet->addUniform(new osg::Uniform("alphaRef", reqs.mAlphaRef)); + addedState->addUniform("alphaRef"); - const auto* alphaFunc = writableStateSet->getAttributePair(osg::StateAttribute::ALPHAFUNC); - if (alphaFunc) - removedState->setAttribute(alphaFunc->first, alphaFunc->second); + if (!removedState->getAttributePair(osg::StateAttribute::ALPHAFUNC)) + { + const auto* alphaFunc = writableStateSet->getAttributePair(osg::StateAttribute::ALPHAFUNC); + if (alphaFunc && !previousAddedState->hasAttribute(osg::StateAttribute::ALPHAFUNC, 0)) + removedState->setAttribute(alphaFunc->first, alphaFunc->second); + } // This prevents redundant glAlphaFunc calls while letting the shadows bin still see the test writableStateSet->setAttribute(RemovedAlphaFunc::getInstance(reqs.mAlphaFunc), osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); + addedState->setAttribute(RemovedAlphaFunc::getInstance(reqs.mAlphaFunc)); // Blending won't work with A2C as we use the alpha channel for coverage. gl_SampleCoverage from ARB_sample_shading would save the day, but requires GLSL 130 if (mConvertAlphaTestToAlphaToCoverage && !reqs.mAlphaBlend) { writableStateSet->setMode(GL_SAMPLE_ALPHA_TO_COVERAGE_ARB, osg::StateAttribute::ON); + addedState->setMode(GL_SAMPLE_ALPHA_TO_COVERAGE_ARB); defineMap["alphaToCoverage"] = "1"; } + // Adjusting coverage isn't safe with blending on as blending requires the alpha to be intact. + // Maybe we could also somehow (e.g. userdata) detect when the diffuse map has coverage-preserving mip maps in the future + if (!reqs.mAlphaBlend) + defineMap["adjustCoverage"] = "1"; + // Preventing alpha tested stuff shrinking as lower mip levels are used requires knowing the texture size osg::ref_ptr exts = osg::GLExtensions::Get(0, false); if (exts && exts->isGpuShader4Supported) @@ -426,10 +524,11 @@ namespace Shader // We could fall back to a texture size uniform if EXT_gpu_shader4 is missing } - if (writableStateSet->getMode(GL_ALPHA_TEST) != osg::StateAttribute::INHERIT) + if (writableStateSet->getMode(GL_ALPHA_TEST) != osg::StateAttribute::INHERIT && !previousAddedState->hasMode(GL_ALPHA_TEST)) removedState->setMode(GL_ALPHA_TEST, writableStateSet->getMode(GL_ALPHA_TEST)); // This disables the deprecated fixed-function alpha test writableStateSet->setMode(GL_ALPHA_TEST, osg::StateAttribute::OFF | osg::StateAttribute::PROTECTED); + addedState->setMode(GL_ALPHA_TEST); if (!removedState->getModeList().empty() || !removedState->getAttributeList().empty()) { @@ -443,6 +542,18 @@ namespace Shader updateRemovedState(*writableUserData, removedState); } + if (!addedState->empty()) + { + // user data is normally shallow copied so shared with the original stateset + osg::ref_ptr writableUserData; + if (mAllowedToModifyStateSets) + writableUserData = writableStateSet->getOrCreateUserDataContainer(); + else + writableUserData = getWritableUserDataContainer(*writableStateSet); + + updateAddedState(*writableUserData, addedState); + } + defineMap["translucentFramebuffer"] = mTranslucentFramebuffer ? "1" : "0"; std::string shaderPrefix; @@ -454,11 +565,14 @@ namespace Shader if (vertexShader && fragmentShader) { - writableStateSet->setAttributeAndModes(mShaderManager.getProgram(vertexShader, fragmentShader), osg::StateAttribute::ON); + auto program = mShaderManager.getProgram(vertexShader, fragmentShader); + writableStateSet->setAttributeAndModes(program, osg::StateAttribute::ON); + addedState->setAttributeAndModes(program); for (std::map::const_iterator texIt = reqs.mTextures.begin(); texIt != reqs.mTextures.end(); ++texIt) { writableStateSet->addUniform(new osg::Uniform(texIt->second.c_str(), texIt->first), osg::StateAttribute::ON); + addedState->addUniform(texIt->second); } } } @@ -473,24 +587,57 @@ namespace Shader else writableStateSet = getWritableStateSet(node); - writableStateSet->removeAttribute(osg::StateAttribute::PROGRAM); + // user data is normally shallow copied so shared with the original stateset - we'll need to copy before edits + osg::ref_ptr writableUserData; + + if (osg::ref_ptr addedState = getAddedState(*writableStateSet)) + { + if (mAllowedToModifyStateSets) + writableUserData = writableStateSet->getUserDataContainer(); + else + writableUserData = getWritableUserDataContainer(*writableStateSet); + + unsigned int index = writableUserData->getUserObjectIndex("addedState"); + writableUserData->removeUserObject(index); + + // O(n log n) to use StateSet::removeX, but this is O(n) + for (auto itr = writableStateSet->getUniformList().begin(); itr != writableStateSet->getUniformList().end();) + { + if (addedState->hasUniform(itr->first)) + writableStateSet->getUniformList().erase(itr++); + else + ++itr; + } + + for (auto itr = writableStateSet->getModeList().begin(); itr != writableStateSet->getModeList().end();) + { + if (addedState->hasMode(itr->first)) + writableStateSet->getModeList().erase(itr++); + else + ++itr; + } + + // StateAttributes track the StateSets they're attached to + // We don't have access to the function to do that, and can't call removeAttribute with an iterator + for (const auto& [type, member] : addedState->getAttributes()) + writableStateSet->removeAttribute(type, member); + } + if (osg::ref_ptr removedState = getRemovedState(*writableStateSet)) { - // user data is normally shallow copied so shared with the original stateset - osg::ref_ptr writableUserData; + if (!writableUserData) + { if (mAllowedToModifyStateSets) writableUserData = writableStateSet->getUserDataContainer(); else writableUserData = getWritableUserDataContainer(*writableStateSet); + } + unsigned int index = writableUserData->getUserObjectIndex("removedState"); writableUserData->removeUserObject(index); - for (const auto& [mode, value] : removedState->getModeList()) - writableStateSet->setMode(mode, value); - - for (const auto& attribute : removedState->getAttributeList()) - writableStateSet->setAttribute(attribute.second.first, attribute.second.second); + writableStateSet->merge(*removedState); } } diff --git a/components/terrain/quadtreeworld.cpp b/components/terrain/quadtreeworld.cpp index 70b44935c..e9f580556 100644 --- a/components/terrain/quadtreeworld.cpp +++ b/components/terrain/quadtreeworld.cpp @@ -212,8 +212,8 @@ public: { // We arrived at a leaf. // Since the tree is used for LOD level selection instead of culling, we do not need to load the actual height data here. - float minZ = -std::numeric_limits::max(); - float maxZ = std::numeric_limits::max(); + constexpr float minZ = -std::numeric_limits::max(); + constexpr float maxZ = std::numeric_limits::max(); 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)); diff --git a/components/terrain/viewdata.cpp b/components/terrain/viewdata.cpp index e4d043ffc..996bf909c 100644 --- a/components/terrain/viewdata.cpp +++ b/components/terrain/viewdata.cpp @@ -141,9 +141,9 @@ ViewData *ViewDataMap::getViewData(osg::Object *viewer, const osg::Vec3f& viewPo vd = found->second; needsUpdate = false; - if (!(vd->suitableToUse(activeGrid) && (vd->getViewPoint()-viewPoint).length2() < mReuseDistance*mReuseDistance && vd->getWorldUpdateRevision() >= mWorldUpdateRevision)) + if (!vd->suitableToUse(activeGrid) || (vd->getViewPoint()-viewPoint).length2() >= mReuseDistance*mReuseDistance || vd->getWorldUpdateRevision() < mWorldUpdateRevision) { - float shortestDist = std::numeric_limits::max(); + float shortestDist = viewer ? mReuseDistance*mReuseDistance : std::numeric_limits::max(); const ViewData* mostSuitableView = nullptr; for (const ViewData* other : mUsedViews) { @@ -162,6 +162,11 @@ ViewData *ViewDataMap::getViewData(osg::Object *viewer, const osg::Vec3f& viewPo vd->copyFrom(*mostSuitableView); return vd; } + else if (!mostSuitableView) + { + vd->setViewPoint(viewPoint); + needsUpdate = true; + } } if (!vd->suitableToUse(activeGrid)) { diff --git a/components/vfs/filesystemarchive.cpp b/components/vfs/filesystemarchive.cpp index 17f3891ec..6eef4b93a 100644 --- a/components/vfs/filesystemarchive.cpp +++ b/components/vfs/filesystemarchive.cpp @@ -55,12 +55,7 @@ namespace VFS bool FileSystemArchive::contains(const std::string& file, char (*normalize_function)(char)) const { - for (const auto& it : mIndex) - { - if(it.first == file) - return true; - } - return false; + return mIndex.find(file) != mIndex.end(); } std::string FileSystemArchive::getDescription() const diff --git a/components/widgets/imagebutton.cpp b/components/widgets/imagebutton.cpp index 0d1f798da..bf2b1b24c 100644 --- a/components/widgets/imagebutton.cpp +++ b/components/widgets/imagebutton.cpp @@ -118,7 +118,7 @@ namespace Gui void ImageButton::setImage(const std::string &image) { - size_t extpos = image.find_last_of("."); + size_t extpos = image.find_last_of('.'); std::string imageNoExt = image.substr(0, extpos); std::string ext = image.substr(extpos); diff --git a/components/widgets/list.cpp b/components/widgets/list.cpp index 73ca94929..e961b04cb 100644 --- a/components/widgets/list.cpp +++ b/components/widgets/list.cpp @@ -115,7 +115,7 @@ namespace Gui unsigned int MWList::getItemCount() { - return mItems.size(); + return static_cast(mItems.size()); } std::string MWList::getItemNameAt(unsigned int at) diff --git a/docs/requirements.txt b/docs/requirements.txt index 288d462d0..ac82149f5 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,2 +1,3 @@ parse_cmake -sphinx>=1.7.0 +sphinx==1.8.5 +docutils==0.17.1 diff --git a/docs/source/reference/modding/custom-models/index.rst b/docs/source/reference/modding/custom-models/index.rst index 8f575bf3c..b204dffd9 100644 --- a/docs/source/reference/modding/custom-models/index.rst +++ b/docs/source/reference/modding/custom-models/index.rst @@ -2,7 +2,11 @@ Custom Models ############# -Custom models can be imported into OpenMW using a variety of formats. Below is a quick overview of supported formats, followed by separate articles with further look at the pipelines. +Custom models can be imported into OpenMW using a variety of formats. Models for the majority of in-game objects are assigned through openMW-CS :doc:`/manuals/openmw-cs/index`. + +Some models, however, are essential for OpenMW to even run. These include player and NPC animations, and meshes for the sky. They are assigned in the ``settings.cfg`` file, with more information available in :doc:`/reference/modding/settings/models` . + +Below is a quick overview of supported formats, followed by separate articles with further look at the pipelines. * **COLLADA** has no license restrictions and is suitable for modding as well as standalone games based on the OpenMW engine. It supports static and animated models. While it doesn't yet work in all parts of the engine, work is being done to resolve the remaining limitations. diff --git a/docs/source/reference/modding/index.rst b/docs/source/reference/modding/index.rst index df98137d8..440db96d8 100644 --- a/docs/source/reference/modding/index.rst +++ b/docs/source/reference/modding/index.rst @@ -20,9 +20,11 @@ about creating new content for OpenMW, please refer to foreword differences mod-install + openmw-game-template settings/index texture-modding/index custom-models/index font + sky-system extended paths diff --git a/docs/source/reference/modding/openmw-game-template.rst b/docs/source/reference/modding/openmw-game-template.rst new file mode 100644 index 000000000..68fa66791 --- /dev/null +++ b/docs/source/reference/modding/openmw-game-template.rst @@ -0,0 +1,122 @@ +#################### +OpenMW Game Template +#################### + +OpenMW Game Template, or simply, the Template, is a set of base assets required +for OpenMW to run. These include ``template.omwgame`` along with models, textures, +fonts, and UI content. The Template can be used as a foundation for a standalone +game in OpenMW, without requiring any of the original, copyrighted Morrowind assets. +With the exception of ``Pelagiad.ttf`` font file, the Template is released as +`public domain `_. + + +Installation +************ + +The Template is installed the same way you would install a mod, with general +instructions available at :doc:`mod-install`. It can be downloaded from +`its repository `_ and requires +OpenMW 0.47 or later. + +After getting the Template, extract its ``/data`` folder to somewhere on your disk. + +.. note:: + + It's adviseable to not put the Template files in the same folder as your + Morrowind files. This is especially valid when you don't wish to mix the two games + and use the Template as a foundation for a standalone game. + + +Define paths to .omwgame and data files +======================================= + +OpenMW needs to be told where to look for the Template files. This is done in +``openmw.cfg`` file where ``content=`` tells OpenMW which .omwgame file to use +and ``data=`` tells OpenMW what folders to look for meshes, textures, audio, +and other assets. The required lines would look like this, but with the paths +of course different on your system. + +.. code:: + + content=template.omwgame + data="/home/someuser/example-suite/data" + data="/home/someuser/example-suite" + + +Remove references to Morrowind files +==================================== + +In case you have Morrowind installed and have run OpenMW's installation wizard, +you need to remove or comment out the following lines from ``openmw.cfg``. +Not doing so will either produce errors or load Morrowind content, which you +probably do not want when you are making your own game. + +.. code:: + + fallback-archive=Morrowind.bsa + fallback-archive=Tribunal.bsa + fallback-archive=Bloodmoon.bsa + content=Morrowind.esm + content=Tribunal.esm + content=Bloodmoon.esm + data="/home/someuser/.wine/dosdevices/c:/Morrowind/Data Files" + + +Define paths to essential models +================================ + +Certain models, essential to OpenMW, cannot be assigned through OpenMW-CS but +are instead assigned through ``settings.cfg``. These models are player and NPC +animations, and meshes for the sky. In ``settings.cfg`` used by your OpenMW +install, add the following lines under the ``[Models]`` section. + +.. code:: + + xbaseanim = meshes/BasicPlayer.dae + baseanim = meshes/BasicPlayer.dae + xbaseanim1st = meshes/BasicPlayer.dae + baseanimkna = meshes/BasicPlayer.dae + baseanimkna1st = meshes/BasicPlayer.dae + xbaseanimfemale = meshes/BasicPlayer.dae + baseanimfemale = meshes/BasicPlayer.dae + baseanimfemale1st = meshes/BasicPlayer.dae + xargonianswimkna = meshes/BasicPlayer.dae + xbaseanimkf = meshes/BasicPlayer.dae + xbaseanim1stkf = meshes/BasicPlayer.dae + xbaseanimfemalekf = meshes/BasicPlayer.dae + xargonianswimknakf = meshes/BasicPlayer.dae + skyatmosphere = meshes/sky_atmosphere.dae + skyclouds = meshes/sky_clouds_01.osgt + skynight01 = meshes/sky_night_01.osgt + + +As a convenience the Template repository includes a ``settings.cfg`` containing +these same lines which can be copied and pasted. However, do not use the file +to simply overwrite the ``settings.cfg`` used by your OpenMW installation. + + +Copying the UI files +==================== + +The Template includes a ``resources/mygui`` folder. The contents of this folder +need to be copied to ``resources/mygui`` folder found in your OpenMW installation +folder. Overwrite any files aready in this folder. These files provide the +UI font, its definition, and some minor UI tweaks. + +.. code:: + + openmw_box.skin.xml + openmw_button.skin.xml + openmw_font.xml + openmw_windows.skin.xml + Pelagiad.ttf + + +Run OpenMW Launcher +******************* + +After completing all the steps, run OpenMW Launcher and make sure ``template.omwgame`` +is selected in *Data Files* tab. Then, run the game and enjoy an empty island. It is not +empty though! It is full of potential to start making your very own game on the +OpenMW engine. Good luck! + diff --git a/docs/source/reference/modding/settings/camera.rst b/docs/source/reference/modding/settings/camera.rst index e15b06e74..a880a2b62 100644 --- a/docs/source/reference/modding/settings/camera.rst +++ b/docs/source/reference/modding/settings/camera.rst @@ -6,7 +6,7 @@ near clip :Type: floating point :Range: > 0 -:Default: 1.0 +:Default: 3.0 This setting controls the distance to the near clipping plane. The value must be greater than zero. Values greater than approximately 18.0 will occasionally clip objects in the world in front of the character. diff --git a/docs/source/reference/modding/settings/groundcover.rst b/docs/source/reference/modding/settings/groundcover.rst index ef97caeec..7c5a965e0 100644 --- a/docs/source/reference/modding/settings/groundcover.rst +++ b/docs/source/reference/modding/settings/groundcover.rst @@ -28,15 +28,14 @@ are used in the game. Can affect performance a lot. This setting can only be configured by editing the settings configuration file. -distance --------- +rendering distance +------------------ -:Type: integer -:Range: > 0 -:Default: 1 +:Type: floating point +:Range: >= 0.0 +:Default: 6144.0 -Determines on which distance in cells grass pages are rendered. -Default 1 value means 3x3 cells area (active grid). +Determines on which distance in game units grass pages are rendered. May affect performance a lot. This setting can only be configured by editing the settings configuration file. diff --git a/docs/source/reference/modding/settings/index.rst b/docs/source/reference/modding/settings/index.rst index 220ee88c4..e9607fd9d 100644 --- a/docs/source/reference/modding/settings/index.rst +++ b/docs/source/reference/modding/settings/index.rst @@ -8,6 +8,14 @@ If you are familiar with ``.ini`` tweaks in Morrowind or the other games, this w All settings described in this section are changed in ``settings.cfg``, located in your OpenMW user directory. See :doc:`../paths` for this location. +When creating a new game based on the OpenMW engine, it may be desirable to change the default settings - the defaults are chosen for compatibility with Morrowind. +This can be done by editing ``defaults.bin`` in the OpenMW installation directory without rebuilding OpenMW itself. +If you're using a custom fork of OpenMW, ``files/settings-default.cfg`` in the source repository should be edited instead. +To edit ``defaults.bin``, base64 decode it, make any changes, and then base64 encode it again. + +If you feel a need to edit the default settings for any other reason than when creating a new OpenMW-based game, you should not. +We may be able to accommodate your desired workflow some other way if you make a feature request. + Changing Settings ################# @@ -25,8 +33,10 @@ Changing Settings Then to the line above, type ``[GUI]``, as the tooltip delay setting comes from the "GUI Settings" section. Although this guide attempts to be comprehensive and up to date, -you will always be able to find the full list of settings available and their default values in ``settings-default.cfg`` -in your main OpenMW installation directory. +you will always be able to find the full list of settings available and their default values in ``settings-default.cfg``, +available in the ``files`` directory of our source repo, or by base64 decoding ``defaults.bin`` in your main OpenMW installation directory. +This has changed compared to previous versions of OpenMW as more users were confused by the existence of a file they weren't supposed to edit +than were helped by the existence of a file listing settings they could edit in a different file. The ranges included with each setting are the physically possible ranges, not recommendations. .. warning:: diff --git a/docs/source/reference/modding/settings/models.rst b/docs/source/reference/modding/settings/models.rst index b0da374d6..c0192067a 100644 --- a/docs/source/reference/modding/settings/models.rst +++ b/docs/source/reference/modding/settings/models.rst @@ -29,3 +29,213 @@ If enabled, this setting allows the NIF loader to make use of that functionality To help debug possible issues OpenMW will log its progress in loading every file that uses an unsupported NIF version. + +xbaseanim +--------- + +:Type: string +:Range: +:Default: meshes/xbase_anim.nif + +Path to the file used for 3rd person base animation model that looks also for the corresponding kf-file. + +.. note:: + If you are using the COLLADA format, you don't need to separate the files as they are separated between .nif and .kf files. It works if you plug the same COLLADA file into all animation-related entries, just make sure there is a corresponding textkeys file. You can read more about the textkeys in :doc:`../../modding/custom-models/pipeline-blender-collada`. + +baseanim +-------- + +:Type: string +:Range: +:Default: meshes/base_anim.nif + +Path to the file used for 3rd person base model with textkeys-data. + +xbaseanim1st +------------ + +:Type: string +:Range: +:Default: meshes/xbase_anim.1st.nif + +Path to the file used for 1st person base animation model that looks also for corresponding kf-file. + +baseanimkna +----------- + +:Type: string +:Range: +:Default: meshes/base_animkna.nif + +Path to the file used for 3rd person beast race base model with textkeys-data. + +baseanimkna1st +-------------- + +:Type: string +:Range: +:Default: meshes/base_animkna.1st.nif + +Path to the file used for 1st person beast race base animation model. + +xbaseanimfemale +--------------- + +:Type: string +:Range: +:Default: meshes/xbase_anim_female.nif + +Path to the file used for 3rd person female base animation model. + +baseanimfemale +-------------- + +:Type: string +:Range: +:Default: meshes/base_anim_female.nif + +Path to the file used for 3rd person female base model with textkeys-data. + +baseanimfemale1st +----------------- + +:Type: string +:Range: +:Default: meshes/base_anim_female.1st.nif + +Path to the file used for 1st person female base model with textkeys-data. + +wolfskin +-------- + +:Type: string +:Range: +:Default: meshes/wolf/skin.nif + +Path to the file used for 3rd person werewolf skin. + +wolfskin1st +----------- + +:Type: string +:Range: +:Default: meshes/wolf/skin.1st.nif + +Path to the file used for 1st person werewolf skin. + +xargonianswimkna +---------------- + +:Type: string +:Range: +:Default: meshes/xargonian_swimkna.nif + +Path to the file used for Argonian swimkna. + +xbaseanimkf +----------- + +:Type: string +:Range: +:Default: meshes/xbase_anim.kf + +File to load xbaseanim 3rd person animations. + +xbaseanim1stkf +-------------- + +:Type: string +:Range: +:Default: meshes/xbase_anim.1st.kf + +File to load xbaseanim 3rd person animations. + +xbaseanimfemalekf +----------------- + +:Type: string +:Range: +:Default: meshes/xbase_anim_female.kf + +File to load xbaseanim animations from. + +xargonianswimknakf +------------------ + +:Type: string +:Range: +:Default: meshes/xargonian_swimkna.kf + +File to load xargonianswimkna animations from. + +skyatmosphere +------------- + +:Type: string +:Range: +:Default: meshes/sky_atmosphere.nif + +Path to the file used for the sky atmosphere mesh, which is one of the three meshes needed to render the sky. It's used to make the top half of the sky blue and renders in front of the background color. + +skyclouds +--------- + +:Type: string +:Range: +:Default: meshes/sky_clouds_01.nif. + +Path to the file used for the sky clouds mesh, which is one of the three meshes needed to render the sky. It displays a scrolling texture of clouds in front of the atmosphere mesh and background color + +skynight01 +---------- + +:Type: string +:Range: +:Default: meshes/sky_night_01.nif + +Path to the file used for the sky stars mesh, which is one of the three meshes needed to render the sky. During night, it displays a texture with stars in front of the atmosphere and behind the clouds. If skynight02 is present, skynight01 will not be used. + +skynight02 +---------- + +:Type: string +:Range: +:Default: meshes/sky_night_02.nif + +Path to the file used for the sky stars mesh, which is one of the three meshes needed to render the sky. During night, it displays a texture with stars in front of the atmosphere and behind the clouds. If it's present it will be used instead of skynight01. + +weatherashcloud +--------------- + +:Type: string +:Range: +:Default: meshes/ashcloud.nif + +Path to the file used for the ash clouds weather effect in Morrowind. OpenMW doesn't use this file, instead it renders a similar looking particle effect. Changing this won't have any effect. + +weatherblightcloud +------------------ + +:Type: string +:Range: +:Default: meshes/blightcloud.nif + +Path to the file used for the blight clouds weather effect in Morrowind. OpenMW doesn't use this file, instead it renders a similar looking particle effect. Changing this won't have any effect. + +weathersnow +----------- + +:Type: string +:Range: +:Default: meshes/snow.nif + +Path to the file used for the snow falling weather effect in Morrowind. OpenMW doesn't use this file, instead it renders a similar looking particle effect. Changing this won't have any effect. + +weatherblizzard +--------------- + +:Type: string +:Range: +:Default: meshes/blizzard.nif + +Path to the file used for the blizzard clouds weather effect in Morrowind. OpenMW doesn't use this file, instead it renders a similar looking particle effect. Changing this won't have any effect. diff --git a/docs/source/reference/modding/settings/navigator.rst b/docs/source/reference/modding/settings/navigator.rst index fcef549d0..fee4b2626 100644 --- a/docs/source/reference/modding/settings/navigator.rst +++ b/docs/source/reference/modding/settings/navigator.rst @@ -42,6 +42,18 @@ Increasing this value may decrease performance. This condition is always true: ``max tiles number * max polygons per tile <= 4194304``. It's a limitation of `Recastnavigation `_ library. +wait until min distance to player +------------------------------ + +:Type: integer +:Range: >= 0 +:Default: 5 + +Distance in navmesh tiles around the player to keep loading screen until navigation mesh is generated. +Allows to complete cell loading only when minimal navigation mesh area is generated to correctly find path for actors +nearby the player. Increasing this value will keep loading screen longer but will slightly increase nav mesh generation +speed on systems bound by CPU. Zero means no waiting. + Advanced settings ***************** @@ -231,15 +243,6 @@ max smooth path size 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 **************************************** diff --git a/docs/source/reference/modding/settings/terrain.rst b/docs/source/reference/modding/settings/terrain.rst index 149ef979e..e6018a865 100644 --- a/docs/source/reference/modding/settings/terrain.rst +++ b/docs/source/reference/modding/settings/terrain.rst @@ -129,7 +129,7 @@ object paging active grid ------------------------- :Type: boolean :Range: True/False -:Default: False +:Default: True Controls whether the objects in the active cells use the mentioned paging algorithms. Active grid paging significantly improves the framerate when your setup is CPU-limited. diff --git a/docs/source/reference/modding/sky-system.rst b/docs/source/reference/modding/sky-system.rst new file mode 100644 index 000000000..fa71295e3 --- /dev/null +++ b/docs/source/reference/modding/sky-system.rst @@ -0,0 +1,79 @@ +######################## +Sky System and Structure +######################## + +Overview +******** + +The sky system is made from multiple components that contribute to the final result. + +1. Background colour and fog +2. Atmosphere +3. Clouds +4. Stars +5. Sun +6. Moons +7. Weather effects + +.. image:: https://gitlab.com/OpenMW/openmw-docs/-/raw/master/docs/source/reference/modding/_static/sky-system-overview.png + :align: center + +Background Colour and Fog +************************* + +Even when nothing else is being rendered, OpenMW will always show the basic background colour. The distant fog effect is based off this colour as well. Other elements of the sky are rendered on top of the background to compose the sky. The colour of the background changes depending on the time of day and current weather. + +Atmosphere +********** + +A mesh that contributes the blue colour of the sky. It is a rough cylinder in shape without the bottom face and with the normals pointing inwards. During the day, it is light blue and transitions to a very dark blue, almost black, during the night. + +Towards the bottom edge the mesh gradually becomes transparent and blends with the background colour. Transparency is done per vertex and OpenMW decides which vertices are transparent based on their index. This adds a requirement for a very strict vertex and face order on this mesh to blend properly. + +.. image:: https://gitlab.com/OpenMW/openmw-docs/-/raw/master/docs/source/reference/modding/_static/sky-system-mesh-atmosphere.jpg + :align: center + +Clouds +****** + +A mesh that renders a tiling, scrolling texture of the clouds. It is a flat dome in shape with the normals pointing inwards. Towards the boundary edge the mesh becomes transparent and blends with the background colour. As with the atmosphere, there is a very strict vertex order on this mesh to blend properly. + +.. image:: https://gitlab.com/OpenMW/openmw-docs/-/raw/master/docs/source/reference/modding/_static/sky-system-mesh-clouds.jpg + :align: center + +By default, the UVs of this mesh are scaled so the clouds texture repeats five times across the sky. The weather system blends between different cloud textures depending on the current weather. Speed of the clouds moving across the sky can be set per weather type. + +Stars +***** + +A dome shaped mesh that shows the stars during the night. It starts to become visible during sunset and goes back to transparent during sunrise. At its bottom edge it blends to transparency which is defined with the vertex colour. White is full opacity while black is full transparency. The mesh ends above the horizon to prevent the stars being visible near the horizon when underwater. + +.. image:: https://gitlab.com/OpenMW/openmw-docs/-/raw/master/docs/source/reference/modding/_static/sky-system-mesh-stars.jpg + :align: center + +It can use multiple textures to show different star constellations. In addition, extra meshes can be used to blend nebulae across the sky. + +Sun +*** + +The sun is a billboard that moves across the sky. It is composed of two texures. One shows the regular sun sphere, the other is the sun glare that is added on top of the sun. Glare strength adjusts dynamically depending on how obstructed the view to the sun is. + +Moons +***** + +The moons are two separate billboards moving across the sky and are both rendered the same way. First, a circle texture is used to mask the background. A moon texture is then added on top of the mask. Depending on the current moon phase, a variant of the moon texture is used. The texture on top is additively blended so any transparent area is achieved with black pixels. The following image shows all the separate textures needed for one moon. + +.. image:: https://gitlab.com/OpenMW/openmw-docs/-/raw/master/docs/source/reference/modding/_static/sky-system-moon-textures.jpg + :align: center + + +Weather Effects +*************** + +These are particle emitters used to display weather phenomena such as rain, snow, or dust clouds. Originally, Morrowind used .nif files with a number of planes and baked animations. In OpenMW, these effects are done through code and are currently hardcoded. The particle emmitters emit particles around the player in a big enough area so it looks like the whole world is affected by the weather. + +Settings +******** + +Colour and other settings for each weather type can be edited in ``openmw.cfg`` configuration file. + diff --git a/docs/source/reference/modding/texture-modding/texture-basics.rst b/docs/source/reference/modding/texture-modding/texture-basics.rst index 65e71834c..78ae00770 100644 --- a/docs/source/reference/modding/texture-modding/texture-basics.rst +++ b/docs/source/reference/modding/texture-modding/texture-basics.rst @@ -19,8 +19,10 @@ OpenMW automatically uses shaders for objects with these mapping techniques. Normal Mapping ############## -To plug in a normal map, you name the normal map as the diffuse texture but with a specified suffix after. -OpenMW will then recognise the file and load it as a normal map, provided you have set up your settings file correctly. +To plug in a normal map, you name the normal map as the diffuse texture but with a specified suffix after. OpenMW will then recognise the file and load it as a normal map, provided you have set up your settings file correctly. + +Content creators need to know that OpenMW uses the DX format for normal maps, and not the OpenGL format as one otherwise might expect. See an example of the difference between the two formats `here `_. Make sure your normal maps are created according to the DX style. + See the section `Automatic use`_ further down below for detailed information. Specular Mapping diff --git a/extern/Base64/Base64.h b/extern/Base64/Base64.h new file mode 100644 index 000000000..49b2d29e3 --- /dev/null +++ b/extern/Base64/Base64.h @@ -0,0 +1,130 @@ +#ifndef _MACARON_BASE64_H_ +#define _MACARON_BASE64_H_ + +/** + * The MIT License (MIT) + * Copyright (c) 2016 tomykaira + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include + +namespace Base64 { + +class Base64 { + public: + + static std::string Encode(const std::string data) { + static constexpr char sEncodingTable[] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', + 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', + 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', + '4', '5', '6', '7', '8', '9', '+', '/' + }; + + size_t in_len = data.size(); + size_t out_len = 4 * ((in_len + 2) / 3); + std::string ret(out_len, '\0'); + size_t i; + char *p = ret.data(); + + for (i = 0; i < in_len - 2; i += 3) { + *p++ = sEncodingTable[(data[i] >> 2) & 0x3F]; + *p++ = sEncodingTable[((data[i] & 0x3) << 4) | ((int) (data[i + 1] & 0xF0) >> 4)]; + *p++ = sEncodingTable[((data[i + 1] & 0xF) << 2) | ((int) (data[i + 2] & 0xC0) >> 6)]; + *p++ = sEncodingTable[data[i + 2] & 0x3F]; + } + if (i < in_len) { + *p++ = sEncodingTable[(data[i] >> 2) & 0x3F]; + if (i == (in_len - 1)) { + *p++ = sEncodingTable[((data[i] & 0x3) << 4)]; + *p++ = '='; + } + else { + *p++ = sEncodingTable[((data[i] & 0x3) << 4) | ((int) (data[i + 1] & 0xF0) >> 4)]; + *p++ = sEncodingTable[((data[i + 1] & 0xF) << 2)]; + } + *p++ = '='; + } + + return ret; + } + + static std::string Decode(const std::string& input, std::string& out) { + static constexpr unsigned char kDecodingTable[] = { + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 64, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64, 64, 64, 64, + 64, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, 64, + 64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64 + }; + + size_t in_len = input.size(); + if (in_len % 4 != 0) return "Input data size is not a multiple of 4"; + + if (in_len == 0) + { + out = ""; + return ""; + } + + size_t out_len = in_len / 4 * 3; + if (input[in_len - 1] == '=') out_len--; + if (input[in_len - 2] == '=') out_len--; + + out.resize(out_len); + + for (size_t i = 0, j = 0; i < in_len;) { + uint32_t a = input[i] == '=' ? 0 & i++ : kDecodingTable[static_cast(input[i++])]; + uint32_t b = input[i] == '=' ? 0 & i++ : kDecodingTable[static_cast(input[i++])]; + uint32_t c = input[i] == '=' ? 0 & i++ : kDecodingTable[static_cast(input[i++])]; + uint32_t d = input[i] == '=' ? 0 & i++ : kDecodingTable[static_cast(input[i++])]; + + uint32_t triple = (a << 3 * 6) + (b << 2 * 6) + (c << 1 * 6) + (d << 0 * 6); + + if (j < out_len) out[j++] = (triple >> 2 * 8) & 0xFF; + if (j < out_len) out[j++] = (triple >> 1 * 8) & 0xFF; + if (j < out_len) out[j++] = (triple >> 0 * 8) & 0xFF; + } + + return ""; + } + +}; + +} + +#endif /* _MACARON_BASE64_H_ */ diff --git a/extern/Base64/CMakeLists.txt b/extern/Base64/CMakeLists.txt new file mode 100644 index 000000000..d1adf91bc --- /dev/null +++ b/extern/Base64/CMakeLists.txt @@ -0,0 +1,2 @@ +add_library(Base64 INTERFACE) +target_include_directories(Base64 INTERFACE .) \ No newline at end of file diff --git a/extern/CMakeLists.txt b/extern/CMakeLists.txt index 3842b8357..5dc9642d7 100644 --- a/extern/CMakeLists.txt +++ b/extern/CMakeLists.txt @@ -28,7 +28,7 @@ if(NOT OPENMW_USE_SYSTEM_BULLET) set(BUILD_CPU_DEMOS OFF CACHE BOOL "") set(BUILD_EGL OFF CACHE BOOL "") - set(USE_DOUBLE_PRECISION ${BULLET_USE_DOUBLES} CACHE BOOL "") + set(USE_DOUBLE_PRECISION ON CACHE BOOL "") set(BULLET2_MULTITHREADING ON CACHE BOOL "") if(BULLET_STATIC) @@ -44,11 +44,11 @@ if(NOT OPENMW_USE_SYSTEM_BULLET) set(USE_MSVC_RUNTIME_LIBRARY_DLL ON CACHE BOOL "" FORCE) endif() - # master on 12 Mar 2021 + # May 7, 2021 include(FetchContent) FetchContent_Declare(bullet - URL https://github.com/bulletphysics/bullet3/archive/87e668f6b2a883b4ef63db8a07c8e9283916e9d9.zip - URL_HASH MD5=9f13246439968494c2b595cf412d83c8 + URL https://github.com/bulletphysics/bullet3/archive/refs/tags/3.17.tar.gz + URL_HASH MD5=7711bce9a49c289a08ecda34eaa0f32e SOURCE_DIR fetched/bullet ) FetchContent_MakeAvailableExcludeFromAll(bullet) diff --git a/extern/osg-ffmpeg-videoplayer/audiodecoder.cpp b/extern/osg-ffmpeg-videoplayer/audiodecoder.cpp index c32794d2a..decf57e4d 100644 --- a/extern/osg-ffmpeg-videoplayer/audiodecoder.cpp +++ b/extern/osg-ffmpeg-videoplayer/audiodecoder.cpp @@ -4,13 +4,20 @@ #include #include +#if defined(_MSC_VER) + #pragma warning (push) + #pragma warning (disable : 4244) +#endif + extern "C" { - #include - #include } +#if defined(_MSC_VER) + #pragma warning (pop) +#endif + #include "videostate.hpp" namespace @@ -64,7 +71,7 @@ MovieAudioDecoder::MovieAudioDecoder(VideoState* videoState) { mAudioResampler.reset(new AudioResampler()); - AVCodec *codec = avcodec_find_decoder(mAVStream->codecpar->codec_id); + const AVCodec *codec = avcodec_find_decoder(mAVStream->codecpar->codec_id); if(!codec) { std::string ss = "No codec found for id " + @@ -255,7 +262,7 @@ size_t MovieAudioDecoder::read(char *stream, size_t len) size_t sampleSize = av_get_bytes_per_sample(mOutputSampleFormat); char* data[1]; data[0] = stream; - av_samples_set_silence((uint8_t**)data, 0, len/sampleSize, 1, mOutputSampleFormat); + av_samples_set_silence((uint8_t**)data, 0, static_cast(len/sampleSize), 1, mOutputSampleFormat); return len; } @@ -276,7 +283,7 @@ size_t MovieAudioDecoder::read(char *stream, size_t len) mFramePos = std::min(mFrameSize, sample_skip); if(sample_skip > 0 || mFrameSize > -sample_skip) - sample_skip -= mFramePos; + sample_skip -= static_cast(mFramePos); continue; } diff --git a/extern/osg-ffmpeg-videoplayer/audiodecoder.hpp b/extern/osg-ffmpeg-videoplayer/audiodecoder.hpp index afb088431..bbb5a2783 100644 --- a/extern/osg-ffmpeg-videoplayer/audiodecoder.hpp +++ b/extern/osg-ffmpeg-videoplayer/audiodecoder.hpp @@ -6,6 +6,11 @@ #include #include +#if defined(_MSC_VER) + #pragma warning (push) + #pragma warning (disable : 4244) +#endif + extern "C" { #include @@ -14,6 +19,10 @@ extern "C" #include } +#if defined(_MSC_VER) + #pragma warning (pop) +#endif + #if defined(_WIN32) && !defined(__MINGW32__) #include diff --git a/extern/osg-ffmpeg-videoplayer/videostate.cpp b/extern/osg-ffmpeg-videoplayer/videostate.cpp index c153aa14c..7b4060753 100644 --- a/extern/osg-ffmpeg-videoplayer/videostate.cpp +++ b/extern/osg-ffmpeg-videoplayer/videostate.cpp @@ -9,6 +9,11 @@ #include +#if defined(_MSC_VER) + #pragma warning (push) + #pragma warning (disable : 4244) +#endif + extern "C" { #include @@ -17,6 +22,10 @@ extern "C" #include } +#if defined(_MSC_VER) + #pragma warning (pop) +#endif + static const char* flushString = "FLUSH"; struct FlushPacket : AVPacket { @@ -37,6 +46,31 @@ namespace { const int MAX_AUDIOQ_SIZE = (5 * 16 * 1024); const int MAX_VIDEOQ_SIZE = (5 * 256 * 1024); + + struct AVPacketUnref + { + void operator()(AVPacket* packet) const + { + av_packet_unref(packet); + } + }; + + struct AVFrameFree + { + void operator()(AVFrame* frame) const + { + av_frame_free(&frame); + } + }; + + template + struct AVFree + { + void operator()(T* frame) const + { + av_free(&frame); + } + }; } namespace Video @@ -80,8 +114,7 @@ void VideoState::setAudioFactory(MovieAudioFactory *factory) void PacketQueue::put(AVPacket *pkt) { - AVPacketList *pkt1; - pkt1 = (AVPacketList*)av_malloc(sizeof(AVPacketList)); + std::unique_ptr> pkt1(static_cast(av_malloc(sizeof(AVPacketList)))); if(!pkt1) throw std::bad_alloc(); if(pkt == &flush_pkt) @@ -91,18 +124,16 @@ void PacketQueue::put(AVPacket *pkt) pkt1->next = nullptr; - this->mutex.lock (); - + std::lock_guard lock(this->mutex); + AVPacketList* ptr = pkt1.release(); if(!last_pkt) - this->first_pkt = pkt1; + this->first_pkt = ptr; else - this->last_pkt->next = pkt1; - this->last_pkt = pkt1; + this->last_pkt->next = ptr; + this->last_pkt = ptr; this->nb_packets++; - this->size += pkt1->pkt.size; + this->size += ptr->pkt.size; this->cond.notify_one(); - - this->mutex.unlock(); } int PacketQueue::get(AVPacket *pkt, VideoState *is) @@ -144,7 +175,7 @@ void PacketQueue::clear() { AVPacketList *pkt, *pkt1; - this->mutex.lock(); + std::lock_guard lock(this->mutex); for(pkt = this->first_pkt; pkt != nullptr; pkt = pkt1) { pkt1 = pkt->next; @@ -156,7 +187,6 @@ void PacketQueue::clear() this->first_pkt = nullptr; this->nb_packets = 0; this->size = 0; - this->mutex.unlock (); } int VideoPicture::set_dimensions(int w, int h) { @@ -312,7 +342,7 @@ void VideoState::video_refresh() } -int VideoState::queue_picture(AVFrame *pFrame, double pts) +int VideoState::queue_picture(const AVFrame &pFrame, double pts) { VideoPicture *vp; @@ -325,7 +355,7 @@ int VideoState::queue_picture(AVFrame *pFrame, double pts) if(this->mQuit) return -1; - this->pictq_mutex.lock(); + std::lock_guard lock(this->pictq_mutex); // windex is set to 0 initially vp = &this->pictq[this->pictq_windex]; @@ -333,8 +363,8 @@ int VideoState::queue_picture(AVFrame *pFrame, double pts) // Convert the image into RGBA format // TODO: we could do this in a pixel shader instead, if the source format // matches a commonly used format (ie YUV420P) - const int w = pFrame->width; - const int h = pFrame->height; + const int w = pFrame.width; + const int h = pFrame.height; if(this->sws_context == nullptr || this->sws_context_w != w || this->sws_context_h != h) { if (this->sws_context != nullptr) @@ -350,23 +380,19 @@ int VideoState::queue_picture(AVFrame *pFrame, double pts) vp->pts = pts; if (vp->set_dimensions(w, h) < 0) - { - this->pictq_mutex.unlock(); return -1; - } - sws_scale(this->sws_context, pFrame->data, pFrame->linesize, + sws_scale(this->sws_context, pFrame.data, pFrame.linesize, 0, this->video_ctx->height, vp->rgbaFrame->data, vp->rgbaFrame->linesize); // now we inform our display thread that we have a pic ready this->pictq_windex = (this->pictq_windex+1) % VIDEO_PICTURE_ARRAY_SIZE; this->pictq_size++; - this->pictq_mutex.unlock(); return 0; } -double VideoState::synchronize_video(AVFrame *src_frame, double pts) +double VideoState::synchronize_video(const AVFrame &src_frame, double pts) { double frame_delay; @@ -380,7 +406,7 @@ double VideoState::synchronize_video(AVFrame *src_frame, double pts) frame_delay = av_q2d(this->video_ctx->pkt_timebase); /* if we are repeating a frame, adjust clock accordingly */ - frame_delay += src_frame->repeat_pict * (frame_delay * 0.5); + frame_delay += src_frame.repeat_pict * (frame_delay * 0.5); this->video_clock += frame_delay; return pts; @@ -391,7 +417,17 @@ class VideoThread public: VideoThread(VideoState* self) : mVideoState(self) - , mThread([this] { run(); }) + , mThread([this] + { + try + { + run(); + } + catch(std::exception& e) + { + std::cerr << "An error occurred playing the video: " << e.what () << std::endl; + } + }) { } @@ -403,13 +439,12 @@ public: void run() { VideoState* self = mVideoState; - AVPacket pkt1, *packet = &pkt1; - av_init_packet(packet); - AVFrame *pFrame; + AVPacket packetData; + av_init_packet(&packetData); + std::unique_ptr packet(&packetData); + std::unique_ptr pFrame{av_frame_alloc()}; - pFrame = av_frame_alloc(); - - while(self->videoq.get(packet, self) >= 0) + while(self->videoq.get(packet.get(), self) >= 0) { if(packet->data == flush_pkt.data) { @@ -426,30 +461,26 @@ public: } // Decode video frame - int ret = avcodec_send_packet(self->video_ctx, packet); + int ret = avcodec_send_packet(self->video_ctx, packet.get()); // EAGAIN is not expected if (ret < 0) throw std::runtime_error("Error decoding video frame"); while (!ret) { - ret = avcodec_receive_frame(self->video_ctx, pFrame); + ret = avcodec_receive_frame(self->video_ctx, pFrame.get()); if (!ret) { double pts = pFrame->best_effort_timestamp; pts *= av_q2d((*self->video_st)->time_base); - pts = self->synchronize_video(pFrame, pts); + pts = self->synchronize_video(*pFrame, pts); - if(self->queue_picture(pFrame, pts) < 0) + if(self->queue_picture(*pFrame, pts) < 0) break; } } } - - av_packet_unref(packet); - - av_frame_free(&pFrame); } private: @@ -476,8 +507,9 @@ public: VideoState* self = mVideoState; AVFormatContext *pFormatCtx = self->format_ctx; - AVPacket pkt1, *packet = &pkt1; - av_init_packet(packet); + AVPacket packetData; + av_init_packet(&packetData); + std::unique_ptr packet(&packetData); try { @@ -560,7 +592,7 @@ public: continue; } - if(av_read_frame(pFormatCtx, packet) < 0) + if(av_read_frame(pFormatCtx, packet.get()) < 0) { if (self->audioq.nb_packets == 0 && self->videoq.nb_packets == 0 && self->pictq_size == 0) self->mVideoEnded = true; @@ -571,11 +603,11 @@ public: // Is this a packet from the video stream? if(self->video_st && packet->stream_index == self->video_st-pFormatCtx->streams) - self->videoq.put(packet); + self->videoq.put(packet.get()); else if(self->audio_st && packet->stream_index == self->audio_st-pFormatCtx->streams) - self->audioq.put(packet); + self->audioq.put(packet.get()); else - av_packet_unref(packet); + av_packet_unref(packet.get()); } } catch(std::exception& e) { @@ -600,7 +632,7 @@ bool VideoState::update() int VideoState::stream_open(int stream_index, AVFormatContext *pFormatCtx) { - AVCodec *codec; + const AVCodec *codec; if(stream_index < 0 || stream_index >= static_cast(pFormatCtx->nb_streams)) return -1; diff --git a/extern/osg-ffmpeg-videoplayer/videostate.hpp b/extern/osg-ffmpeg-videoplayer/videostate.hpp index 015656084..32a772f29 100644 --- a/extern/osg-ffmpeg-videoplayer/videostate.hpp +++ b/extern/osg-ffmpeg-videoplayer/videostate.hpp @@ -15,6 +15,11 @@ namespace osg class Texture2D; } +#if defined(_MSC_VER) + #pragma warning (push) + #pragma warning (disable : 4244) +#endif + extern "C" { #include @@ -28,6 +33,10 @@ extern "C" #include } +#if defined(_MSC_VER) + #pragma warning (pop) +#endif + #include "videodefs.hpp" #define VIDEO_PICTURE_QUEUE_SIZE 50 @@ -132,8 +141,8 @@ struct VideoState { void video_display(VideoPicture* vp); void video_refresh(); - int queue_picture(AVFrame *pFrame, double pts); - double synchronize_video(AVFrame *src_frame, double pts); + int queue_picture(const AVFrame &pFrame, double pts); + double synchronize_video(const AVFrame &src_frame, double pts); double get_audio_clock(); double get_video_clock(); diff --git a/files/mygui/openmw_settings_window.layout b/files/mygui/openmw_settings_window.layout index 92c185413..b602f3526 100644 --- a/files/mygui/openmw_settings_window.layout +++ b/files/mygui/openmw_settings_window.layout @@ -498,7 +498,7 @@ - + diff --git a/files/settings-default.cfg b/files/settings-default.cfg index f80b6d098..e33780f9f 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -1,6 +1,7 @@ -# WARNING: If this file is named settings-default.cfg, then editing -# this file might have no effect, as these settings may be overwritten -# by your user settings.cfg file (see documentation for its location). +# WARNING: If this file is named settings-default.cfg or was generated +# from defaults.bin, then editing this file might have no effect, as +# these settings may be overwritten by your user settings.cfg file +# (see documentation for its location). # # This file provides minimal documentation for each setting, and # ranges of recommended values. For detailed explanations of the @@ -14,7 +15,7 @@ [Camera] # Near clipping plane (>0.0, e.g. 0.01 to 18.0). -near clip = 1 +near clip = 3 # Cull objects that occupy less than 'small feature culling pixel size' on the screen. small feature culling = true @@ -875,9 +876,6 @@ max polygon path size = 1024 # Maximum size of smoothed path (value > 0) max smooth path size = 1024 -# Maximum number of triangles in each node of mesh AABB tree (value > 0) -triangles per chunk = 256 - # Write recast mesh to file in .obj format for each use to update nav mesh (true, false) enable write recast mesh to file = false @@ -911,6 +909,10 @@ max tiles number = 512 # Min time duration for the same tile update in milliseconds (value >= 0) min update interval ms = 250 +# Keep loading screen until navmesh is generated around the player for all tiles within manhattan distance (value >= 0). +# Distance is measured in the number of tiles and can be only an integer value. +wait until min distance to player = 5 + [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. @@ -1074,8 +1076,8 @@ enabled = false # 1.0 means 100% density density = 1.0 -# A maximum distance in cells on which groundcover is rendered. -distance = 1 +# A maximum distance in game units on which groundcover is rendered. +rendering distance = 6144.0 # A minimum size of groundcover chunk in cells (0.125, 0.25, 0.5, 1.0) min chunk size = 0.5 diff --git a/files/shaders/alpha.glsl b/files/shaders/alpha.glsl index 05be801e9..46b748236 100644 --- a/files/shaders/alpha.glsl +++ b/files/shaders/alpha.glsl @@ -22,7 +22,7 @@ float mipmapLevel(vec2 scaleduv) float coveragePreservingAlphaScale(sampler2D diffuseMap, vec2 uv) { - #if @alphaFunc != FUNC_ALWAYS && @alphaFunc != FUNC_NEVER + #if @adjustCoverage vec2 textureSize; #if @useGPUShader4 textureSize = textureSize2D(diffuseMap, 0); diff --git a/files/shaders/nv_default_fragment.glsl b/files/shaders/nv_default_fragment.glsl index eadadba39..1bd0f4e31 100644 --- a/files/shaders/nv_default_fragment.glsl +++ b/files/shaders/nv_default_fragment.glsl @@ -56,8 +56,6 @@ void main() #if @normalMap vec4 normalTex = texture2D(normalMap, normalMapUV); - // Must flip Y for DirectX format normal maps - normalTex.y = 1.0 - normalTex.y; vec3 normalizedNormal = normalize(passNormal); vec3 normalizedTangent = normalize(passTangent.xyz); diff --git a/files/ui/advancedpage.ui b/files/ui/advancedpage.ui index 7ac94dfba..62ed7a601 100644 --- a/files/ui/advancedpage.ui +++ b/files/ui/advancedpage.ui @@ -6,7 +6,7 @@ 0 0 - 617 + 732 487 @@ -16,1411 +16,1477 @@ 0 - - - Game Mechanics - - - - - - - - <html><head/><body><p>Make enchanted weaponry without Magical flag bypass normal weapons resistance, like in Morrowind.</p></body></html> - - - Enchanted weapons are magical - - - - - - - <html><head/><body><p>Makes player swim a bit upward from the line of sight. Applies only in third person mode. Intended to make simpler swimming without diving.</p></body></html> - - - Swim upward correction - - - - - - - <html><head/><body><p>If enabled NPCs apply evasion maneuver to avoid collisions with others.</p></body></html> - - - NPCs avoid collisions - - - - - - - <html><head/><body><p>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.</p></body></html> - - - Build nav mesh for world geometry - - - - - - - <html><head/><body><p>This setting causes the behavior of the sneak key (bound to Ctrl by default) to toggle sneaking on and off rather than requiring the key to be held down while sneaking. Players that spend significant time sneaking may find the character easier to control with this option enabled. </p></body></html> - - - Toggle sneak - - - - - - - <html><head/><body><p>Make disposition change of merchants caused by trading permanent.</p></body></html> - - - Permanent barter disposition changes - - - - - - - <html><head/><body><p>Don't use race weight in NPC movement speed calculations.</p></body></html> - - - Racial variation in speed fix - - - - - - - <html><head/><body><p>Make Damage Fatigue magic effect uncapped like Drain Fatigue effect.</p><p>This means that unlike Morrowind you will be able to knock down actors using this effect.</p></body></html> - - - Uncapped Damage Fatigue - - - - - - - <html><head/><body><p>If this setting is true, the player is allowed to loot actors (e.g. summoned creatures) during death animation, if they are not in combat. In this case we have to increment death counter and run disposed actor's script instantly.</p><p>If this setting is false, player has to wait until end of death animation in all cases. Makes using of summoned creatures exploit (looting summoned Dremoras and Golden Saints for expensive weapons) a lot harder. Conflicts with mannequin mods, which use SkipAnim to prevent end of death animation.</p></body></html> - - - Can loot during death animation - - - - - - - <html><head/><body><p>Make the value of filled soul gems dependent only on soul magnitude.</p></body></html> - - - Soulgem values rebalance - - - - - - - <html><head/><body><p>Make player followers and escorters start combat with enemies who have started combat with them or the player. Otherwise they wait for the enemies or the player to do an attack first.</p></body></html> - - - Followers defend immediately - - - - - - - <html><head/><body><p>Effects of reflected Absorb spells are not mirrored -- like in Morrowind.</p></body></html> - - - Classic reflected Absorb spells behavior - - - - - - - <html><head/><body><p>Make stealing items from NPCs that were knocked down possible during combat.</p></body></html> - - - Always allow stealing from knocked out actors - - - - - + + + Game Mechanics + + + + + + - Give NPC an ability to swim over the water surface when they follow other actor independently from their ability to swim. Has effect only when nav mesh building is enabled. + <html><head/><body><p>Make enchanted weaponry without Magical flag bypass normal weapons resistance, like in Morrowind.</p></body></html> - Always allow NPC to follow over water surface + Enchanted weapons are magical - - - - - - <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 - - - - - - - - - Factor strength into hand-to-hand combat: - - - - - - - 0 - - - - Off - - - - - Affect werewolves - - - - - Do not affect werewolves - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - <html><head/><body><p>How many threads will be spawned to compute physics update in the background. A value of 0 means that the update will be performed in the main thread.</p><p>A value greater than 1 requires the Bullet library be compiled with multithreading support.</p></body></html> - - - Background physics threads - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - Visuals - - - - - - - - <html><head/><body><p>Affects side and diagonal movement. Enabling this setting makes movement more realistic.</p><p>If disabled then the whole character's body is pointed to the direction of view. Diagonal movement has no special animation and causes sliding.</p><p>If enabled then the character turns lower body to the direction of movement. Upper body is turned partially. Head is always pointed to the direction of view. In combat mode it works only for diagonal movement. In non-combat mode it changes straight right and straight left movement as well. Also turns the whole body up or down when swimming according to the movement direction.</p></body></html> - - - Turn to movement direction - - - - - - - - - Lighting Method: - - - - - - - - legacy - - - - - shaders compatibility - - - - - shaders - - - - - + + + + <html><head/><body><p>Makes player swim a bit upward from the line of sight. Applies only in third person mode. Intended to make simpler swimming without diving.</p></body></html> + + + Swim upward correction + + - - - - <html><head/><body><p>Makes NPCs and player movement more smooth. Recommended to use with "turn to movement direction" enabled.</p></body></html> - - - Smooth movement - - - - - - - <html><head/><body><p>If true, use paging and LOD algorithms to display the entire terrain. If false, only display terrain of the loaded cells.</p></body></html> - - - Distant land - - - - - - - <html><head/><body><p>See 'auto use object normal maps'. Affects terrain.</p></body></html> - - - Auto use terrain normal maps - - - - - - - <html><head/><body><p>If a file with pattern 'terrain specular map pattern' exists, use that file as a 'diffuse specular' map. The texture must contain the layer colour in the RGB channel (as usual), and a specular multiplier in the alpha channel.</p></body></html> - - - Auto use terrain specular maps - - - - - - - <html><head/><body><p>Use object paging for active cells grid.</p></body></html> - - - Active grid object paging - - - - - - - <html><head/><body><p>Use casting animations for magic items, just as for spells.</p></body></html> - - - Use magic item animation - - - - - - - <html><head/><body><p>Load per-group KF-files and skeleton files from Animations folder</p></body></html> - - - Use additional animation sources - - - - - - - <html><head/><body><p>If this option is enabled, normal maps are automatically recognized and used if they are named appropriately - (see 'normal map pattern', e.g. for a base texture foo.dds, the normal map texture would have to be named foo_n.dds). - If this option is disabled, normal maps are only used if they are explicitly listed within the mesh file (.nif or .osg file). Affects objects.</p></body></html> - - - Auto use object normal maps - - - - - - - <html><head/><body><p>Normally environment map reflections aren't affected by lighting, which makes environment-mapped (and thus bump-mapped objects) glow in the dark. - Morrowind Code Patch includes an option to remedy that by doing environment-mapping before applying lighting, this is the equivalent of that option. - Affected objects will use shaders. - </p></body></html> - - - Bump/reflect map local lighting - - - - - - - - - Viewing distance - - - - - - - Cells - - - 0.000000000000000 - - - 0.500000000000000 - - - - - - - - - <html><head/><body><p>If this option is enabled, specular maps are automatically recognized and used if they are named appropriately - (see 'specular map pattern', e.g. for a base texture foo.dds, - the specular map texture would have to be named foo_spec.dds). - If this option is disabled, normal maps are only used if they are explicitly listed within the mesh file - (.osg file, not supported in .nif files). Affects objects.</p></body></html> - - - Auto use object specular maps - - - - - - - <html><head/><body><p>By default, the fog becomes thicker proportionally to your distance from the clipping plane set at the clipping distance, which causes distortion at the edges of the screen. - This setting makes the fog use the actual eye point distance (or so called Euclidean distance) to calculate the fog, which makes the fog look less artificial, especially if you have a wide FOV.</p></body></html> - - - Radial fog - - - - - - - - - 20 - - - - - false - - - <html><head/><body><p>Render holstered weapons (with quivers and scabbards), requires modded assets.</p></body></html> - - - Weapon sheathing - - - - - - - false - - - <html><head/><body><p>Render holstered shield, requires modded assets.</p></body></html> - - - Shield sheathing - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - Audio - - - - - - + + + + <html><head/><body><p>If enabled NPCs apply evasion maneuver to avoid collisions with others.</p></body></html> + - Audio Device + NPCs avoid collisions + + + + + + + <html><head/><body><p>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.</p></body></html> + + + Build nav mesh for world geometry + + + + - Select your preferred audio device. + <html><head/><body><p>This setting causes the behavior of the sneak key (bound to Ctrl by default) to toggle sneaking on and off rather than requiring the key to be held down while sneaking. Players that spend significant time sneaking may find the character easier to control with this option enabled. </p></body></html> + + + Toggle sneak - - - - - 0 - 0 - + + + + <html><head/><body><p>Make disposition change of merchants caused by trading permanent.</p></body></html> - - - 283 - 0 - + + Permanent barter disposition changes - - 0 + + + + + + <html><head/><body><p>Don't use race weight in NPC movement speed calculations.</p></body></html> + + + Racial variation in speed fix + + + + + + + <html><head/><body><p>Make Damage Fatigue magic effect uncapped like Drain Fatigue effect.</p><p>This means that unlike Morrowind you will be able to knock down actors using this effect.</p></body></html> + + + Uncapped Damage Fatigue + + + + + + + <html><head/><body><p>If this setting is true, the player is allowed to loot actors (e.g. summoned creatures) during death animation, if they are not in combat. In this case we have to increment death counter and run disposed actor's script instantly.</p><p>If this setting is false, player has to wait until end of death animation in all cases. Makes using of summoned creatures exploit (looting summoned Dremoras and Golden Saints for expensive weapons) a lot harder. Conflicts with mannequin mods, which use SkipAnim to prevent end of death animation.</p></body></html> + + + Can loot during death animation + + + + + + + <html><head/><body><p>Make the value of filled soul gems dependent only on soul magnitude.</p></body></html> + + + Soulgem values rebalance + + + + + + + <html><head/><body><p>Make player followers and escorters start combat with enemies who have started combat with them or the player. Otherwise they wait for the enemies or the player to do an attack first.</p></body></html> + + + Followers defend immediately + + + + + + + <html><head/><body><p>Effects of reflected Absorb spells are not mirrored -- like in Morrowind.</p></body></html> + + + Classic reflected Absorb spells behavior + + + + + + + <html><head/><body><p>Make stealing items from NPCs that were knocked down possible during combat.</p></body></html> + + + Always allow stealing from knocked out actors + + + + + + + Give NPC an ability to swim over the water surface when they follow other actor independently from their ability to swim. Has effect only when nav mesh building is enabled. + + + Always allow NPC to follow over water surface - - - Default - - - + + + <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 + + + + + - + - HRTF - - - This setting controls HRTF, which simulates 3D sound on stereo systems. + Factor strength into hand-to-hand combat: - - - - 0 - 0 - - - - - 283 - 0 - - + 0 - Automatic + Off - Off + Affect werewolves - On + Do not affect werewolves + + + + Qt::Horizontal + + + + 40 + 20 + + + + - + - - - HRTF Profile - + - Select your preferred HRTF profile. + <html><head/><body><p>How many threads will be spawned to compute physics update in the background. A value of 0 means that the update will be performed in the main thread.</p><p>A value greater than 1 requires the Bullet library be compiled with multithreading support.</p></body></html> - + + Background physics threads + + - - - - 0 - 0 - + + + + + + Qt::Horizontal - + - 283 - 0 + 40 + 20 - - 0 - - - - Default - - - + - + Qt::Vertical - 0 - 0 + 20 + 40 - - - Camera - - - - - - <html><head/><body><p>This setting controls third person view mode.</p><p>False: View is centered on the character's head. Crosshair is hidden. - True: In non-combat mode camera is positioned behind the character's shoulder. Crosshair is visible in third person mode as well. - </p></body></html> - - - View over the shoulder - - - - - - - <html><head/><body><p>When player is close to an obstacle, automatically switches camera to the shoulder that is farther away from the obstacle.</p></body></html> - - - Auto switch shoulder - - - + + + Visuals + + + + + + true + + + + + 0 + 0 + 645 + 413 + + + - - - 20 - - - 0 - - - 0 - - - 0 + + + Animations - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Default shoulder: - - - - - - - 0 - - + + + + + <html><head/><body><p>Use casting animations for magic items, just as for spells.</p></body></html> + + + Use magic item animation + + + + + + + <html><head/><body><p>Makes NPCs and player movement more smooth. Recommended to use with "turn to movement direction" enabled.</p></body></html> + + + Smooth movement + + + + + + + <html><head/><body><p>Load per-group KF-files and skeleton files from Animations folder</p></body></html> + + + Use additional animation sources + + + + + + + <html><head/><body><p>Affects side and diagonal movement. Enabling this setting makes movement more realistic.</p><p>If disabled then the whole character's body is pointed to the direction of view. Diagonal movement has no special animation and causes sliding.</p><p>If enabled then the character turns lower body to the direction of movement. Upper body is turned partially. Head is always pointed to the direction of view. In combat mode it works only for diagonal movement. In non-combat mode it changes straight right and straight left movement as well. Also turns the whole body up or down when swimming according to the movement direction.</p></body></html> + + + Turn to movement direction + + + + + + + 20 + + + + + false + + + <html><head/><body><p>Render holstered weapons (with quivers and scabbards), requires modded assets.</p></body></html> + - Right + Weapon sheathing + + + + + + + false + + + <html><head/><body><p>Render holstered shield, requires modded assets.</p></body></html> - - - Left + Shield sheathing - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - <html><head/><body><p>If enabled then the character rotation is not synchonized with the camera rotation while the character doesn't move and not in combat mode.</p></body></html> - - - Preview if stand still - + + + + + - - - <html><head/><body><p>If enabled then the character smoothly rotates to the view direction after exiting preview or vanity mode. If disabled then the camera rotates rather than the character.</p></body></html> - - - Deferred preview rotation + + + Shaders + + + + + <html><head/><body><p>If this option is enabled, normal maps are automatically recognized and used if they are named appropriately + (see 'normal map pattern', e.g. for a base texture foo.dds, the normal map texture would have to be named foo_n.dds). + If this option is disabled, normal maps are only used if they are explicitly listed within the mesh file (.nif or .osg file). Affects objects.</p></body></html> + + + Auto use object normal maps + + + + + + + <html><head/><body><p>See 'auto use object normal maps'. Affects terrain.</p></body></html> + + + Auto use terrain normal maps + + + + + + + <html><head/><body><p>If this option is enabled, specular maps are automatically recognized and used if they are named appropriately + (see 'specular map pattern', e.g. for a base texture foo.dds, + the specular map texture would have to be named foo_spec.dds). + If this option is disabled, normal maps are only used if they are explicitly listed within the mesh file + (.osg file, not supported in .nif files). Affects objects.</p></body></html> + + + Auto use object specular maps + + + + + + + <html><head/><body><p>If a file with pattern 'terrain specular map pattern' exists, use that file as a 'diffuse specular' map. The texture must contain the layer colour in the RGB channel (as usual), and a specular multiplier in the alpha channel.</p></body></html> + + + Auto use terrain specular maps + + + + + + + <html><head/><body><p>Normally environment map reflections aren't affected by lighting, which makes environment-mapped (and thus bump-mapped objects) glow in the dark. + Morrowind Code Patch includes an option to remedy that by doing environment-mapping before applying lighting, this is the equivalent of that option. + Affected objects will use shaders. + </p></body></html> + + + Bump/reflect map local lighting + + + + + + + <html><head/><body><p>By default, the fog becomes thicker proportionally to your distance from the clipping plane set at the clipping distance, which causes distortion at the edges of the screen. + This setting makes the fog use the actual eye point distance (or so called Euclidean distance) to calculate the fog, which makes the fog look less artificial, especially if you have a wide FOV.</p></body></html> + + + Radial fog + + + + - - - <html><head/><body><p>Enables head bobbing when move in first person mode.</p></body></html> - - - Head bobbing in 1st person mode + + + Terrain + + + + + + + Viewing distance + + + + + + + Cells + + + 0.000000000000000 + + + 0.500000000000000 + + + + + + + + + + + <html><head/><body><p>Controls how large an object must be to be visible in the scene. The object’s size is divided by its distance to the camera and the result of the division is compared with this value. The smaller this value is, the more objects you will see in the scene.</p></body></html> + + + Object paging min size + + + + + + + 3 + + + 0.000000000000000 + + + 0.250000000000000 + + + 0.005000000000000 + + + + + + + + + <html><head/><body><p>If true, use paging and LOD algorithms to display the entire terrain. If false, only display terrain of the loaded cells.</p></body></html> + + + Distant land + + + + + + + 20 + + + + + <html><head/><body><p>Use object paging for active cells grid.</p></body></html> + + + Active grid object paging + + + + + + - + Qt::Vertical - 0 - 0 + 20 + 40 - - - Interface - - - - - - - - Show owned: - - - - - - - 1 - - - - Off - - - - - Tool Tip Only - - - - - Crosshair Only - - - - - Tool Tip and Crosshair - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - + + + + + + + Audio + + + + - + - <html><head/><body><p>This setting scales GUI windows. A value of 1.0 results in the normal scale.</p></body></html> + Select your preferred audio device. - GUI scaling factor + Audio Device - - - 2 - - - 0.500000000000000 + + + + 0 + 0 + - - 8.000000000000000 + + + 283 + 0 + - - 0.250000000000000 + + 0 - - 1.000000000000000 + + + Default + + + + + + + + + + + + This setting controls HRTF, which simulates 3D sound on stereo systems. + + + HRTF - - - Qt::Horizontal + + + + 0 + 0 + - + - 40 - 20 + 283 + 0 - - - - - - - - <html><head/><body><p>Show the remaining duration of magic effects and lights if this setting is true. The remaining duration is displayed in the tooltip by hovering over the magical effect. </p><p>The default value is false.</p></body></html> - - - Show effect duration - - - + + 0 + - - - <html><head/><body><p>Whether or not the chance of success will be displayed in the enchanting menu.</p><p>The default value is false.</p></body></html> - - - Show enchant chance - - + + Automatic + - - - <html><head/><body><p>If this setting is true, melee weapons reach and speed will be shown on item tooltip.</p><p>The default value is false.</p></body></html> - - - Show melee info - - + + Off + - - - <html><head/><body><p>If this setting is true, damage bonus of arrows and bolts will be shown on item tooltip.</p><p>The default value is false.</p></body></html> - - - Show projectile damage - - + + On + + + + + + + + + + + Select your preferred HRTF profile. + + + HRTF Profile + + + + + + + + 0 + 0 + + + + + 283 + 0 + + + + 0 + - - - <html><head/><body><p>If this setting is true, dialogue topics will have a different color if the topic is specific to the NPC you're talking to or the topic was previously seen. Color can be changed in settings.cfg.</p><p>The default value is false.</p></body></html> - - - Change dialogue topic color - - + + Default + + + + + + + + + Qt::Vertical + + + + 0 + 0 + + + + + + + + + Camera + + + + + + <html><head/><body><p>This setting controls third person view mode.</p><p>False: View is centered on the character's head. Crosshair is hidden. +True: In non-combat mode camera is positioned behind the character's shoulder. Crosshair is visible in third person mode as well. +</p></body></html> + + + View over the shoulder + + + + + + + <html><head/><body><p>When player is close to an obstacle, automatically switches camera to the shoulder that is farther away from the obstacle.</p></body></html> + + + Auto switch shoulder + + + + + + + 20 + + + 0 + + + 0 + + + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + - - - <html><head/><body><p>Stretch menus, load screens, etc. to the window aspect ratio.</p></body></html> - + - Stretch menu background + Default shoulder: - - - <html><head/><body><p>If this setting is true, containers supporting graphic herbalism will do so instead of opening the menu.</p></body></html> - - - Enable graphic herbalism + + + 0 + + + Right + + + + + Left + + - + - Qt::Vertical + Qt::Horizontal - 20 - 40 + 40 + 20 - - - - Bug Fixes - - + + + + + + + <html><head/><body><p>If enabled then the character rotation is not synchonized with the camera rotation while the character doesn't move and not in combat mode.</p></body></html> + + + Preview if stand still + + + + + + + <html><head/><body><p>If enabled then the character smoothly rotates to the view direction after exiting preview or vanity mode. If disabled then the camera rotates rather than the character.</p></body></html> + + + Deferred preview rotation + + + + + + + <html><head/><body><p>Enables head bobbing when move in first person mode.</p></body></html> + + + Head bobbing in 1st person mode + + + + + + + Qt::Vertical + + + + 0 + 0 + + + + + + + + + Interface + + + + + + + + Show owned: + + + + + + + 1 + - - - <html><head/><body><p>Prevents merchants from equipping items that are sold to them.</p></body></html> - - - Merchant equipping fix - - + + Off + - - - <html><head/><body><p>Trainers now only choose which skills to train using their base skill points, allowing mercantile improving effects to be used without making mercantile an offered skill.</p></body></html> - - - Trainers choose their training skills based on their base skill points - - + + Tool Tip Only + - - - Qt::Vertical - - - - 0 - 0 - - - + + Crosshair Only + - - - - - Miscellaneous - - - - - Saves - - - - - - <html><head/><body><p>This setting determines whether the amount of the time the player has spent playing will be displayed for each saved game in the Load menu.</p></body></html> - - - Add "Time Played" to saves - - - - - - - - - Maximum Quicksaves - - - - - - - 1 - - - - - - - + + Tool Tip and Crosshair + - - - - Other - - + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + + <html><head/><body><p>This setting scales GUI windows. A value of 1.0 results in the normal scale.</p></body></html> + + + GUI scaling factor + + + + + + + 2 + + + 0.500000000000000 + + + 8.000000000000000 + + + 0.250000000000000 + + + 1.000000000000000 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + <html><head/><body><p>Show the remaining duration of magic effects and lights if this setting is true. The remaining duration is displayed in the tooltip by hovering over the magical effect. </p><p>The default value is false.</p></body></html> + + + Show effect duration + + + + + + + <html><head/><body><p>Whether or not the chance of success will be displayed in the enchanting menu.</p><p>The default value is false.</p></body></html> + + + Show enchant chance + + + + + + + <html><head/><body><p>If this setting is true, melee weapons reach and speed will be shown on item tooltip.</p><p>The default value is false.</p></body></html> + + + Show melee info + + + + + + + <html><head/><body><p>If this setting is true, damage bonus of arrows and bolts will be shown on item tooltip.</p><p>The default value is false.</p></body></html> + + + Show projectile damage + + + + + + + <html><head/><body><p>If this setting is true, dialogue topics will have a different color if the topic is specific to the NPC you're talking to or the topic was previously seen. Color can be changed in settings.cfg.</p><p>The default value is false.</p></body></html> + + + Change dialogue topic color + + + + + + + <html><head/><body><p>Stretch menus, load screens, etc. to the window aspect ratio.</p></body></html> + + + Stretch menu background + + + + + + + <html><head/><body><p>Enable zooming on local and global maps.</p></body></html> + + + Can zoom on maps + + + + + + + <html><head/><body><p>If this setting is true, containers supporting graphic herbalism will do so instead of opening the menu.</p></body></html> + + + Enable graphic herbalism + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + Bug Fixes + + + + + + <html><head/><body><p>Prevents merchants from equipping items that are sold to them.</p></body></html> + + + Merchant equipping fix + + + + + + + <html><head/><body><p>Trainers now only choose which skills to train using their base skill points, allowing mercantile improving effects to be used without making mercantile an offered skill.</p></body></html> + + + Trainers choose their training skills based on their base skill points + + + + + + + Qt::Vertical + + + + 0 + 0 + + + + + + + + + Miscellaneous + + + + + + Saves + + + + + + <html><head/><body><p>This setting determines whether the amount of the time the player has spent playing will be displayed for each saved game in the Load menu.</p></body></html> + + + Add "Time Played" to saves + + + + + + + + + Maximum Quicksaves + + + + + + + 1 + + + + + + + + + + + + Other + + + + + + + + Screenshot Format + + + + + - - - - - Screenshot Format - - - - - - - - JPG - - - - - PNG - - - - - TGA - - - - - - - - - - - - - Qt::Vertical - - - - 0 - 0 - - - - - - - - - Testing - - - - - - These settings are intended for testing mods and will cause issues if used for normal gameplay. - - - true - - - - - - - Qt::Horizontal - - - - - - - <html><head/><body><p>OpenMW will capture control of the cursor if this setting is true.</p><p>In “look mode”, OpenMW will center the cursor regardless of the value of this setting (since the cursor/crosshair is always centered in the OpenMW window). However, in GUI mode, this setting determines the behavior when the cursor is moved outside the OpenMW window. If true, the cursor movement stops at the edge of the window preventing access to other applications. If false, the cursor is allowed to move freely on the desktop.</p><p>This setting does not apply to the screen where escape has been pressed, where the cursor is never captured. Regardless of this setting “Alt-Tab” or some other operating system dependent key sequence can be used to allow the operating system to regain control of the mouse cursor. This setting interacts with the minimize on focus loss setting by affecting what counts as a focus loss. Specifically on a two-screen configuration it may be more convenient to access the second screen with setting disabled.</p><p>Note for developers: it’s desirable to have this setting disabled when running the game in a debugger, to prevent the mouse cursor from becoming unusable when the game pauses on a breakpoint.</p></body></html> - - - Grab cursor - - - - - - - Skip menu and generate default character - - - - - - - - - Qt::Horizontal - - - QSizePolicy::Fixed - - - - 0 - 0 - - - - - - - Start default character at + JPG - - - - - - default cell + + + + PNG - - - - - - - - Run script after startup: - - - - - - - - - - + + - Browse… + TGA - - - - - - - - Qt::Vertical - - - - 0 - 0 - - - - - - - - - VR - - - - - - - - <html><head/><body><p>The velocity at which the minimum strength of attack is achieved when swinging your motion controllers. In meters/second. Swinging slower than this does not initiate an attack.</p></body></html> - - - Realistic melee combat minimum swing speed - - - - - - - true - - - 2 - - - 0 - - - 100 - - - 0.00 - - - 0.100000000000000 - - - - - - - <html><head/><body><p>The velocity at which max strength of attack is achieved when swinging your motion controllers. In meters/second</p></body></html> - - - Realistic melee combat minimum swing speed - - - - - - - true - - - 2 - - - 0 - - - 100 - - - 0.00 - - - 0.100000000000000 - - - - - - - <html><head/><body><p>Your real height. In meters.</p></body></html> - - - Real height (Meters) - - - - - - - true - - - 2 - - - 0 - - - 100 - - - 0.00 - - - 0.010000000000000 - - - - - - - <html><head/><body><p>Makes OpenMW VR use sRGB format for its swapchain textures if available. Needed for some headsets that don't play nice without sRGB. Enable this if the game seems much brighter in your headset than what's shown on the mirror texture.</p></body></html> - - - Prefer sRGB swapchains. - - + + - - - - <html><head/><body><p>Makes OpenMW VR map shadows for both eyes in one pass, instead of once per eye. This is a significant performance benefit, and should only be disabled for debugging or masochism purposes.</p></body></html> - - - Use shared shadow maps - - - - - - - <html><head/><body><p>Makes OpenMW VR render stereo using geometry shaders instead of two render passes. Players with a CPU bound will benefit from this, but players with a gpu bound will not. E.g. with a high cell view distance (distant lands) this will degrade your performance instead. NOTE: This feature is experimental, broken on AMD GPUs, and untested on Intel.</p></body></html> - - - !!Experimental!!: Use geometry shaders. - - - - - - - <html><head/><body><p>Makes OpenMW VR use DirectX swapchains even if OpenGL swapchains are available. Note that OpenMW will always fall back to DirectX swapchains if OpenGL swapchains are not available. This option exists to debug/work around runtimes with bad OpenGL implementations.</p></body></html> - - - Debug: Prefer DirectX Swapchains - - - - - - - <html><head/><body><p>Makes OpenMW VR enable and use the XR_EXT_debug_utils extension, which may help produce more detailed debug information.</p></body></html> - - - Debug: Enable extension XR_EXT_debug_utils - - - - - - - <html><head/><body><p>Makes OpenMW VR log every single OpenXR call. This degrades performance and creates a massive log. You probably want this disabled.</p></body></html> - - - Debug: Log all OpenXR calls - - - - - - - <html><head/><body><p>Makes OpenMW VR ignore unsuccessful calls to OpenXR. You probably want this enabled.</p></body></html> - - - Debug: Continue on errors - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - + + + + + + Notify on saved screenshot + + + + + + + + + + Qt::Vertical + + + + 0 + 0 + + + + + + + + + Testing + + + + + + These settings are intended for testing mods and will cause issues if used for normal gameplay. + + + true + + + + + + + Qt::Horizontal + + + + + + + <html><head/><body><p>OpenMW will capture control of the cursor if this setting is true.</p><p>In “look mode”, OpenMW will center the cursor regardless of the value of this setting (since the cursor/crosshair is always centered in the OpenMW window). However, in GUI mode, this setting determines the behavior when the cursor is moved outside the OpenMW window. If true, the cursor movement stops at the edge of the window preventing access to other applications. If false, the cursor is allowed to move freely on the desktop.</p><p>This setting does not apply to the screen where escape has been pressed, where the cursor is never captured. Regardless of this setting “Alt-Tab” or some other operating system dependent key sequence can be used to allow the operating system to regain control of the mouse cursor. This setting interacts with the minimize on focus loss setting by affecting what counts as a focus loss. Specifically on a two-screen configuration it may be more convenient to access the second screen with setting disabled.</p><p>Note for developers: it’s desirable to have this setting disabled when running the game in a debugger, to prevent the mouse cursor from becoming unusable when the game pauses on a breakpoint.</p></body></html> + + + Grab cursor + + + + + + + Skip menu and generate default character + + + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 0 + 0 + + + + + + + + Start default character at + + + + + + + default cell + + + + + + + + + Run script after startup: + + + + + + + + + + + + Browse… + + + + + + + + + Qt::Vertical + + + + 0 + 0 + + + + + + + + + VR + + + + + + + + <html><head/><body><p>The velocity at which the minimum strength of attack is achieved when swinging your motion controllers. In meters/second. Swinging slower than this does not initiate an attack.</p></body></html> + + + Realistic melee combat minimum swing speed + + + + + + + true + + + 2 + + + 0 + + + 100 + + + 0.00 + + + 0.100000000000000 + + + + + + + <html><head/><body><p>The velocity at which max strength of attack is achieved when swinging your motion controllers. In meters/second</p></body></html> + + + Realistic melee combat minimum swing speed + + + + + + + true + + + 2 + + + 0 + + + 100 + + + 0.00 + + + 0.100000000000000 + + + + + + + <html><head/><body><p>Your real height. In meters.</p></body></html> + + + Real height (Meters) + + + + + + + true + + + 2 + + + 0 + + + 100 + + + 0.00 + + + 0.010000000000000 + + + + + + + <html><head/><body><p>Makes OpenMW VR use sRGB format for its swapchain textures if available. Needed for some headsets that don't play nice without sRGB. Enable this if the game seems much brighter in your headset than what's shown on the mirror texture.</p></body></html> + + + Prefer sRGB swapchains. + + + + + + + <html><head/><body><p>Makes OpenMW VR map shadows for both eyes in one pass, instead of once per eye. This is a significant performance benefit, and should only be disabled for debugging or masochism purposes.</p></body></html> + + + Use shared shadow maps + + + + + + + <html><head/><body><p>Makes OpenMW VR render stereo using geometry shaders instead of two render passes. Players with a CPU bound will benefit from this, but players with a gpu bound will not. E.g. with a high cell view distance (distant lands) this will degrade your performance instead. NOTE: This feature is experimental, broken on AMD GPUs, and untested on Intel.</p></body></html> + + + !!Experimental!!: Use geometry shaders. + + + + + + + <html><head/><body><p>Makes OpenMW VR use DirectX swapchains even if OpenGL swapchains are available. Note that OpenMW will always fall back to DirectX swapchains if OpenGL swapchains are not available. This option exists to debug/work around runtimes with bad OpenGL implementations.</p></body></html> + + + Debug: Prefer DirectX Swapchains + + + + + + + <html><head/><body><p>Makes OpenMW VR enable and use the XR_EXT_debug_utils extension, which may help produce more detailed debug information.</p></body></html> + + + Debug: Enable extension XR_EXT_debug_utils + + + + + + + <html><head/><body><p>Makes OpenMW VR log every single OpenXR call. This degrades performance and creates a massive log. You probably want this disabled.</p></body></html> + + + Debug: Log all OpenXR calls + + + + + + + <html><head/><body><p>Makes OpenMW VR ignore unsuccessful calls to OpenXR. You probably want this enabled.</p></body></html> + + + Debug: Continue on errors + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + - + \ No newline at end of file diff --git a/files/ui/graphicspage.ui b/files/ui/graphicspage.ui index 5ec72611f..508a955d7 100644 --- a/files/ui/graphicspage.ui +++ b/files/ui/graphicspage.ui @@ -187,6 +187,56 @@ + + + Lighting + + + + + + + + Lighting Method: + + + + + + + + legacy + + + + + shaders compatibility + + + + + shaders + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + Shadows diff --git a/files/ui/mainwindow.ui b/files/ui/mainwindow.ui index 13e8593e0..54a2d16a1 100644 --- a/files/ui/mainwindow.ui +++ b/files/ui/mainwindow.ui @@ -52,27 +52,13 @@ - - - + + + + + + Qt::Horizontal - - - 0 - - - 0 - - - 0 - - - 0 - - - - - diff --git a/readthedocs.yml b/readthedocs.yml deleted file mode 100644 index e53e54b78..000000000 --- a/readthedocs.yml +++ /dev/null @@ -1,2 +0,0 @@ -# Don't build any extra formats -formats: [] \ No newline at end of file