diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 259dd3cd00..1cf1e5e1f2 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -30,6 +30,8 @@ variables: - apt-cache/ - ccache/ stage: build + variables: + CMAKE_EXE_LINKER_FLAGS: -fuse-ld=mold script: - df -h - export CCACHE_BASEDIR="`pwd`" @@ -61,28 +63,45 @@ variables: - build/install/ Coverity: - extends: .Ubuntu_Image + tags: + - docker + - linux + image: ubuntu:20.04 stage: build rules: - if: $CI_PIPELINE_SOURCE == "schedule" + cache: + key: Coverity.ubuntu_20.04.v1 + paths: + - apt-cache/ + - ccache/ + variables: + CCACHE_SIZE: 2G + CC: clang + CXX: clang++ + CMAKE_BUILD_TYPE: Debug + CMAKE_CXX_FLAGS_DEBUG: -O0 before_script: - - CI/install_debian_deps.sh clang openmw-deps openmw-deps-dynamic - - curl -o /tmp/cov-analysis-linux64.tgz https://scan.coverity.com/download/linux64 --form project=$COVERITY_SCAN_PROJECT_NAME --form token=$COVERITY_SCAN_TOKEN + - CI/install_debian_deps.sh clang_ubuntu_20_04 openmw-deps openmw-deps-dynamic + - curl -o /tmp/cov-analysis-linux64.tgz https://scan.coverity.com/download/linux64 + --form project=$COVERITY_SCAN_PROJECT_NAME --form token=$COVERITY_SCAN_TOKEN - tar xfz /tmp/cov-analysis-linux64.tgz script: + - export CCACHE_BASEDIR="$(pwd)" + - export CCACHE_DIR="$(pwd)/ccache" + - mkdir -pv "${CCACHE_DIR}" + - ccache -z -M "${CCACHE_SIZE}" - CI/before_script.linux.sh + - cov-analysis-linux64-*/bin/cov-configure --template --comptype prefix --compiler ccache # Remove the specific targets and build everything once we can do it under 3h - - cov-analysis-linux64-*/bin/cov-build --dir cov-int cmake --build build -- -j $(nproc) openmw esmtool bsatool niftest openmw-wizard openmw-launcher openmw-iniimporter openmw-essimporter openmw-navmeshtool openmw-cs + - cov-analysis-linux64-*/bin/cov-build --dir cov-int cmake --build build -- -j $(nproc) + - ccache -s after_script: - tar cfz cov-int.tar.gz cov-int - curl https://scan.coverity.com/builds?project=$COVERITY_SCAN_PROJECT_NAME --form token=$COVERITY_SCAN_TOKEN --form email=$GITLAB_USER_EMAIL --form file=@cov-int.tar.gz --form version="$CI_COMMIT_REF_NAME:$CI_COMMIT_SHORT_SHA" --form description="CI_COMMIT_SHORT_SHA / $CI_COMMIT_TITLE / $CI_COMMIT_REF_NAME:$CI_PIPELINE_ID" - variables: - CC: clang - CXX: clang++ - CXXFLAGS: -O0 artifacts: paths: - /builds/OpenMW/openmw/cov-int/build-log.txt @@ -154,7 +173,7 @@ Ubuntu_GCC_tests_asan: BUILD_TESTS_ONLY: 1 CMAKE_BUILD_TYPE: Debug CMAKE_CXX_FLAGS_DEBUG: -g -O1 -fno-omit-frame-pointer -fsanitize=address -fsanitize=pointer-compare -fsanitize=pointer-subtract -fsanitize=leak - CMAKE_EXE_LINKER_FLAGS: -fsanitize=address -fsanitize=pointer-compare -fsanitize=pointer-subtract -fsanitize=leak + CMAKE_EXE_LINKER_FLAGS: -fsanitize=address -fsanitize=pointer-compare -fsanitize=pointer-subtract -fsanitize=leak -fuse-ld=mold ASAN_OPTIONS: halt_on_error=1:strict_string_checks=1:detect_stack_use_after_return=1:check_initialization_order=1:strict_init_order=1 artifacts: paths: [] @@ -189,7 +208,7 @@ Ubuntu_GCC_tests_ubsan: BUILD_TESTS_ONLY: 1 CMAKE_BUILD_TYPE: Debug CMAKE_CXX_FLAGS_DEBUG: -g -O2 -fno-omit-frame-pointer -fno-optimize-sibling-calls -fsanitize=thread -fPIE - CMAKE_EXE_LINKER_FLAGS: -pthread -pie -fsanitize=thread + CMAKE_EXE_LINKER_FLAGS: -pthread -pie -fsanitize=thread -fuse-ld=mold TSAN_OPTIONS: second_deadlock_stack=1:halt_on_error=1 artifacts: paths: [] diff --git a/CHANGELOG.md b/CHANGELOG.md index 8fbb3d6d1d..627f5f6887 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,7 +20,7 @@ Bug #4376: Moved actors don't respawn in their original cells Bug #4389: NPC's lips do not move if his head model has the NiBSAnimationNode root node Bug #4602: Robert's Bodies: crash inside createInstance() - Bug #4700: Editor: Incorrect command implementation + Bug #4700: OpenMW-CS: Incorrect command implementation Bug #4744: Invisible particles aren't always processed Bug #4949: Incorrect particle lighting Bug #5054: Non-biped creatures don't use spellcast equip/unequip animations @@ -87,7 +87,7 @@ Bug #6276: Deleted groundcover instances are not deleted in game Bug #6282: Laura craft doesn't follow the player character Bug #6283: Avis Dorsey follows you after her death - Bug #6285: Brush template drawing and terrain selection drawing performance is very bad + Bug #6285: OpenMW-CS: Brush template drawing and terrain selection drawing performance is very bad Bug #6289: Keyword search in dialogues expected the text to be all ASCII characters Bug #6291: Can't pickup the dead mage's journal from the mysterious hunter mod Bug #6302: Teleporting disabled actor breaks its disabled state @@ -114,7 +114,6 @@ Bug #6417: OpenMW doesn't always use the right node to accumulate movement Bug #6429: Wyrmhaven: Can't add AI packages to player Bug #6433: Items bound to Quick Keys sometimes do not appear until the Quick Key menu is opened - Bug #6435: Add support for MSVC 2022 Bug #6451: Weapon summoned from Cast When Used item will have the name "None" Bug #6473: Strings from NIF should be parsed only to first null terminator Bug #6493: Unlocking owned but not locked or unlocked containers is considered a crime @@ -130,7 +129,6 @@ Bug #6655: Constant effect absorb attribute causes the game to break Bug #6667: Pressing the Esc key while resting or waiting causes black screen. Bug #6670: Dialogue order is incorrect - Bug #6672: Garbage object refs in groundcover plugins like Vurt's grass plugins Bug #6680: object.cpp handles nodetree unsafely, memory access with dangling pointer Bug #6682: HitOnMe doesn't fire as intended Bug #6697: Shaders vertex lighting incorrectly clamped @@ -142,7 +140,6 @@ Bug #6753: Info records without a DATA subrecords are loaded incorrectly Bug #6794: Light sources are attached to mesh bounds centers instead of mesh origins when AttachLight NiNode is missing Bug #6799: Game crashes if an NPC has no Class attached - Bug #6818: ess-importer doesn't exactly reproduce camera Bug #6849: ImageButton texture is not scaled properly Bug #6869: Hits queue stagger during swing animation Bug #6890: SDL_PeepEvents errors are not handled @@ -178,6 +175,7 @@ Feature #6017: Separate persistent and temporary cell references when saving Feature #6019: Add antialias alpha test to the launcher or enable by default if possible Feature #6032: Reverse-z depth buffer + Feature #6078: Do not clear depth buffer for first-person meshes Feature #6128: Soft Particles Feature #6171: In-game log viewer Feature #6189: Navigation mesh disk cache @@ -196,23 +194,19 @@ Feature #6557: Add support for controller gyroscope Feature #6592: Support for NiTriShape particle emitters Feature #6600: Support NiSortAdjustNode + Feature #6631: Support FFMPEG 5 Feature #6684: Support NiFltAnimationNode Feature #6699: Support Ignored flag Feature #6700: Support windowed fullscreen Feature #6706: Save the size of the Options window - Feature #6721: [OpenMW-CS] Add option to open records in new window + Feature #6721: OpenMW-CS: Add option to open records in new window Feature #6867: Add a way to localize hardcoded strings in GUI - Feature #6888: Add switch for armor degradation fix. - Task #6078: First person should not clear depth buffer + Feature #6888: Add switch for armor degradation fix + Feature #6925: Allow to use a mouse wheel to rotate a head in the race selection menu Task #6161: Refactor Sky to use shaders and be GLES/GL3 friendly Task #6162: Refactor GUI to use shaders and to be GLES and GL3+ friendly - Task #6201: Remove the "Note: No relevant classes found. No output generated" warnings - Task #6264: Remove the old classes in animation.cpp - Task #6553: Simplify interpreter instruction registration + Task #6435: Add support for MSVC 2022 Task #6564: Remove predefined data paths `data="?global?data"`, `data=./data` - Task #6631: Fix ffmpeg avio API usage causing hangs in ffmpeg version 5 - Task #6709: Move KeyframeController transformation magic to NifOsg::MatrixTransform - Task #6763: gcc 12.1 multiple compiler warnings 0.47.0 ------ @@ -348,6 +342,7 @@ 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 + Bug #6923: Dispose of corpse prevents respawning after load Feature #390: 3rd person look "over the shoulder" Feature #832: OpenMW-CS: Handle deleted references Feature #1536: Show more information about level on menu diff --git a/CI/before_script.linux.sh b/CI/before_script.linux.sh index 980915ec47..e8a786d7c6 100755 --- a/CI/before_script.linux.sh +++ b/CI/before_script.linux.sh @@ -11,8 +11,6 @@ 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 @@ -28,9 +26,14 @@ declare -a CMAKE_CONF_OPTS=( -DUSE_SYSTEM_TINYXML=ON -DOPENMW_USE_SYSTEM_RECASTNAVIGATION=ON -DOPENMW_CXX_FLAGS="-Werror -Werror=implicit-fallthrough" # flags specific to OpenMW project - -DCMAKE_EXE_LINKER_FLAGS="${CMAKE_EXE_LINKER_FLAGS} -fuse-ld=mold" ) +if [[ "${CMAKE_EXE_LINKER_FLAGS}" ]]; then + CMAKE_CONF_OPTS+=( + -DCMAKE_EXE_LINKER_FLAGS="${CMAKE_EXE_LINKER_FLAGS}" + ) +fi + if [[ $CI_OPENMW_USE_STATIC_DEPS ]]; then CMAKE_CONF_OPTS+=( -DOPENMW_USE_SYSTEM_MYGUI=OFF @@ -99,8 +102,6 @@ if [[ "${BUILD_TESTS_ONLY}" ]]; then -DBUILD_NIFTEST=OFF \ -DBUILD_UNITTESTS=${BUILD_UNITTESTS} \ -DBUILD_BENCHMARKS=${BUILD_BENCHMARKS} \ - -DGTEST_ROOT="${GOOGLETEST_DIR}" \ - -DGMOCK_ROOT="${GOOGLETEST_DIR}" \ .. else ${ANALYZE} cmake \ diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index 8cf1157004..c6004f49e9 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -642,16 +642,6 @@ if [ -z $SKIP_DOWNLOAD ]; then "https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/LuaJIT-2.1.0-beta3-msvc${LUA_MSVC_YEAR}-win${BITS}.7z" \ "LuaJIT-2.1.0-beta3-msvc${LUA_MSVC_YEAR}-win${BITS}.7z" - # Google test and mock - if [ -n "$TEST_FRAMEWORK" ]; then - echo "Google test 1.11.0..." - if [ -d googletest ]; then - printf " Google test exists, skipping." - else - git clone -b release-1.11.0 https://github.com/google/googletest.git - fi - fi - # ICU download "ICU ${ICU_VER/_/.}"\ "https://github.com/unicode-org/icu/releases/download/release-${ICU_VER/_/-}/icu4c-${ICU_VER}-Win${BITS}-MSVC2019.zip" \ @@ -1003,61 +993,6 @@ printf "LuaJIT 2.1.0-beta3... " done echo Done. } -cd $DEPS -echo -# Google Test and Google Mock -if [ -n "$TEST_FRAMEWORK" ]; then - printf "Google test 1.11.0 ..." - - cd googletest - mkdir -p build${MSVC_DISPLAY_YEAR} - - cd build${MSVC_DISPLAY_YEAR} - - GOOGLE_INSTALL_ROOT="${DEPS_INSTALL}/GoogleTest" - - for CONFIGURATION in ${CONFIGURATIONS[@]}; do - # FindGMock.cmake mentions Release explicitly, but not RelWithDebInfo. Only one optimised library config can be used, so go for the safer one. - GTEST_CONFIG=$([ $CONFIGURATION == "RelWithDebInfo" ] && echo "Release" || echo "$CONFIGURATION" ) - if [ $GTEST_CONFIG == "Debug" ]; then - DEBUG_SUFFIX="d" - else - DEBUG_SUFFIX="" - fi - - if [ ! -f "$GOOGLE_INSTALL_ROOT/lib/gtest${DEBUG_SUFFIX}.lib" ]; then - # Always use MSBuild solution files as they don't need the environment activating - cmake .. -DCMAKE_USE_WIN32_THREADS_INIT=1 -G "Visual Studio $MSVC_REAL_VER $MSVC_DISPLAY_YEAR" "-A $([ $BITS -eq 64 ] && echo "x64" || echo "Win32")" -DBUILD_SHARED_LIBS=1 - cmake --build . --config "${GTEST_CONFIG}" - cmake --install . --config "${GTEST_CONFIG}" --prefix "${GOOGLE_INSTALL_ROOT}" - fi - - add_runtime_dlls $CONFIGURATION "${GOOGLE_INSTALL_ROOT}\bin\gtest_main${DEBUG_SUFFIX}.dll" - add_runtime_dlls $CONFIGURATION "${GOOGLE_INSTALL_ROOT}\bin\gtest${DEBUG_SUFFIX}.dll" - add_runtime_dlls $CONFIGURATION "${GOOGLE_INSTALL_ROOT}\bin\gmock_main${DEBUG_SUFFIX}.dll" - add_runtime_dlls $CONFIGURATION "${GOOGLE_INSTALL_ROOT}\bin\gmock${DEBUG_SUFFIX}.dll" - done - - add_cmake_opts -DBUILD_UNITTESTS=yes - # FindGTest and FindGMock do not work perfectly on Windows - # but we can help them by telling them everything we know about installation - add_cmake_opts -DGMOCK_ROOT="$GOOGLE_INSTALL_ROOT" - add_cmake_opts -DGTEST_ROOT="$GOOGLE_INSTALL_ROOT" - add_cmake_opts -DGTEST_LIBRARY="$GOOGLE_INSTALL_ROOT/lib/gtest.lib" - add_cmake_opts -DGTEST_MAIN_LIBRARY="$GOOGLE_INSTALL_ROOT/lib/gtest_main.lib" - add_cmake_opts -DGMOCK_LIBRARY="$GOOGLE_INSTALL_ROOT/lib/gmock.lib" - add_cmake_opts -DGMOCK_MAIN_LIBRARY="$GOOGLE_INSTALL_ROOT/lib/gmock_main.lib" - add_cmake_opts -DGTEST_LIBRARY_DEBUG="$GOOGLE_INSTALL_ROOT/lib/gtestd.lib" - add_cmake_opts -DGTEST_MAIN_LIBRARY_DEBUG="$GOOGLE_INSTALL_ROOT/lib/gtest_maind.lib" - add_cmake_opts -DGMOCK_LIBRARY_DEBUG="$GOOGLE_INSTALL_ROOT/lib/gmockd.lib" - add_cmake_opts -DGMOCK_MAIN_LIBRARY_DEBUG="$GOOGLE_INSTALL_ROOT/lib/gmock_maind.lib" - add_cmake_opts -DGTEST_LINKED_AS_SHARED_LIBRARY=True - add_cmake_opts -DGTEST_LIBRARY_TYPE=SHARED - add_cmake_opts -DGTEST_MAIN_LIBRARY_TYPE=SHARED - - echo Done. - -fi cd $DEPS echo @@ -1176,6 +1111,10 @@ if [ "${BUILD_BENCHMARKS}" ]; then add_cmake_opts -DBUILD_BENCHMARKS=ON fi +if [ -n "${TEST_FRAMEWORK}" ]; then + add_cmake_opts -DBUILD_UNITTESTS=ON +fi + if [ -n "$ACTIVATE_MSVC" ]; then echo -n "- Activating MSVC in the current shell... " command -v vswhere >/dev/null 2>&1 || { echo "Error: vswhere is not on the path."; wrappedExit 1; } diff --git a/CI/build_googletest.sh b/CI/build_googletest.sh deleted file mode 100755 index 8da5b44232..0000000000 --- a/CI/build_googletest.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/sh -ex - -git clone -b release-1.11.0 https://github.com/google/googletest.git -cd googletest -mkdir build -cd build -cmake \ - -D CMAKE_C_COMPILER="${CC}" \ - -D CMAKE_CXX_COMPILER="${CXX}" \ - -D CMAKE_C_COMPILER_LAUNCHER=ccache \ - -D CMAKE_CXX_COMPILER_LAUNCHER=ccache \ - -D CMAKE_BUILD_TYPE="${CONFIGURATION}" \ - -D CMAKE_INSTALL_PREFIX="${GOOGLETEST_DIR}" \ - -G "${GENERATOR}" \ - .. -cmake --build . --config "${CONFIGURATION}" -- -j $(nproc) -cmake --install . --config "${CONFIGURATION}" diff --git a/CI/install_debian_deps.sh b/CI/install_debian_deps.sh index ca68326a63..ad4a318e83 100755 --- a/CI/install_debian_deps.sh +++ b/CI/install_debian_deps.sh @@ -11,6 +11,7 @@ print_help() { declare -rA GROUPED_DEPS=( [gcc]="binutils gcc build-essential cmake ccache curl unzip git pkg-config mold" [clang]="binutils clang make cmake ccache curl unzip git pkg-config mold" + [clang_ubuntu_20_04]="binutils clang make cmake ccache curl unzip git pkg-config" # Common dependencies for building OpenMW. [openmw-deps]=" diff --git a/CMakeLists.txt b/CMakeLists.txt index 96092a2f61..549e5d6c34 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -163,6 +163,7 @@ option(RECASTNAVIGATION_STATIC "Build recastnavigation static libraries" ${_reca option(OPENMW_USE_SYSTEM_SQLITE3 "Use system provided SQLite3 library" ON) option(OPENMW_USE_SYSTEM_BENCHMARK "Use system Google Benchmark library." OFF) +option(OPENMW_USE_SYSTEM_GOOGLETEST "Use system Google Test library." OFF) option(OPENMW_UNITY_BUILD "Use fewer compilation units to speed up compile time" FALSE) option(OPENMW_LTO_BUILD "Build OpenMW with Link-Time Optimization (Needs ~2GB of RAM)" OFF) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index af7d22dc90..bb20451474 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -112,7 +112,7 @@ namespace World, Gui, Lua, - + LuaSyncUpdate, Number, }; @@ -152,6 +152,10 @@ namespace template <> const UserStats UserStatsValue::sValue {"Lua", "lua"}; + template <> + const UserStats UserStatsValue::sValue{ " -Sync", "luasyncupdate" }; + + template struct ForEachUserStatsValue { @@ -336,10 +340,13 @@ bool OMW::Engine::frame(float frametime) // Main menu opened? Then scripts are also paused. bool paused = mWindowManager->containsMode(MWGui::GM_MainMenu); - - // Should be called after input manager update and before any change to the game world. - // It applies to the game world queued changes from the previous frame. - mLuaManager->synchronizedUpdate(); + + { + ScopedProfile profile(frameStart, frameNumber, *timer, *stats); + // Should be called after input manager update and before any change to the game world. + // It applies to the game world queued changes from the previous frame. + mLuaManager->synchronizedUpdate(); + } // update game state { diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index c71e277c15..7a2d3e13ee 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -796,13 +796,13 @@ namespace MWClass return; } - if (ptr.getRefData().getCount() <= 0) + const CreatureCustomData& customData = ptr.getRefData().getCustomData()->asCreatureCustomData(); + if (ptr.getRefData().getCount() <= 0 && (!isFlagBitSet(ptr, ESM::Creature::Respawn) || !customData.mCreatureStats.isDead())) { state.mHasCustomState = false; return; } - const CreatureCustomData& customData = ptr.getRefData().getCustomData()->asCreatureCustomData(); ESM::CreatureState& creatureState = state.asCreatureState(); customData.mContainerStore->writeState (creatureState.mInventory); customData.mCreatureStats.writeState (creatureState.mCreatureStats); diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 2969c605ff..b271e29f22 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -1349,13 +1349,13 @@ namespace MWClass return; } - if (ptr.getRefData().getCount() <= 0) + const NpcCustomData& customData = ptr.getRefData().getCustomData()->asNpcCustomData(); + if (ptr.getRefData().getCount() <= 0 && (!(ptr.get()->mBase->mFlags & ESM::NPC::Respawn) || !customData.mNpcStats.isDead())) { state.mHasCustomState = false; return; } - const NpcCustomData& customData = ptr.getRefData().getCustomData()->asNpcCustomData(); ESM::NpcState& npcState = state.asNpcState(); customData.mInventoryStore.writeState (npcState.mInventory); customData.mNpcStats.writeState (npcState.mNpcStats); diff --git a/apps/openmw/mwgui/postprocessorhud.cpp b/apps/openmw/mwgui/postprocessorhud.cpp index 1653d2f1d5..fc77f1e841 100644 --- a/apps/openmw/mwgui/postprocessorhud.cpp +++ b/apps/openmw/mwgui/postprocessorhud.cpp @@ -17,28 +17,6 @@ #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" -namespace -{ - void saveChain() - { - auto* processor = MWBase::Environment::get().getWorld()->getPostProcessor(); - - std::vector chain; - - for (size_t i = 1; i < processor->getTechniques().size(); ++i) - { - auto technique = processor->getTechniques()[i]; - - if (!technique || technique->getDynamic()) - continue; - - chain.push_back(technique->getName()); - } - - Settings::Manager::setStringArray("chain", "Post Processing", chain); - } -} - namespace MWGui { void PostProcessorHud::ListWrapper::onKeyButtonPressed(MyGUI::KeyCode key, MyGUI::Char ch) @@ -145,7 +123,7 @@ namespace MWGui processor->enableTechnique(technique); else processor->disableTechnique(technique); - saveChain(); + processor->saveChain(); } } @@ -178,7 +156,7 @@ namespace MWGui return; if (processor->enableTechnique(technique, index) != MWRender::PostProcessor::Status_Error) - saveChain(); + processor->saveChain(); } } @@ -377,7 +355,7 @@ namespace MWGui { Gui::AutoSizedTextBox* divider = mConfigArea->createWidget("MW_UniformGroup", {0,0,0,34}, MyGUI::Align::Default); divider->setNeedMouseFocus(false); - divider->setCaption(uniform->mHeader); + divider->setCaptionWithReplacing(uniform->mHeader); } fx::Widgets::UniformBase* uwidget = mConfigArea->createWidget("MW_UniformEdit", {0,0,0,22}, MyGUI::Align::Default); diff --git a/apps/openmw/mwgui/race.cpp b/apps/openmw/mwgui/race.cpp index 6477d907b7..6427499283 100644 --- a/apps/openmw/mwgui/race.cpp +++ b/apps/openmw/mwgui/race.cpp @@ -56,6 +56,8 @@ namespace MWGui setText("AppearanceT", MWBase::Environment::get().getWindowManager()->getGameSettingString("sRaceMenu1", "Appearance")); getWidget(mPreviewImage, "PreviewImage"); + mPreviewImage->eventMouseWheel += MyGUI::newDelegate(this, &RaceDialog::onPreviewScroll); + getWidget(mHeadRotate, "HeadRotate"); mHeadRotate->setScrollRange(1000); @@ -211,6 +213,19 @@ namespace MWGui eventBack(); } + void RaceDialog::onPreviewScroll(MyGUI::Widget*, int _delta) + { + size_t oldPos = mHeadRotate->getScrollPosition(); + size_t maxPos = mHeadRotate->getScrollRange() - 1; + size_t scrollPage = mHeadRotate->getScrollWheelPage(); + if (_delta < 0) + mHeadRotate->setScrollPosition(oldPos + std::min(maxPos - oldPos, scrollPage)); + else + mHeadRotate->setScrollPosition(oldPos - std::min(oldPos, scrollPage)); + + onHeadRotate(mHeadRotate, mHeadRotate->getScrollPosition()); + } + void RaceDialog::onHeadRotate(MyGUI::ScrollBar* scroll, size_t _position) { float angle = (float(_position) / (scroll->getScrollRange()-1) - 0.5f) * osg::PI * 2; diff --git a/apps/openmw/mwgui/race.hpp b/apps/openmw/mwgui/race.hpp index 160999213e..e48ca28162 100644 --- a/apps/openmw/mwgui/race.hpp +++ b/apps/openmw/mwgui/race.hpp @@ -66,6 +66,7 @@ namespace MWGui EventHandle_WindowBase eventDone; protected: + void onPreviewScroll(MyGUI::Widget* _sender, int _delta); void onHeadRotate(MyGUI::ScrollBar* _sender, size_t _position); void onSelectPreviousGender(MyGUI::Widget* _sender); diff --git a/apps/openmw/mwgui/spellmodel.cpp b/apps/openmw/mwgui/spellmodel.cpp index 455f167415..b241570cfd 100644 --- a/apps/openmw/mwgui/spellmodel.cpp +++ b/apps/openmw/mwgui/spellmodel.cpp @@ -56,7 +56,7 @@ namespace MWGui if (effectId != -1) { const ESM::MagicEffect *magicEffect = - store.get().search(effectId); + store.get().find(effectId); std::string effectIDStr = ESM::MagicEffect::effectIdToString(effectId); std::string fullEffectName = wm->getGameSettingString(effectIDStr, ""); diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index a2c97d641a..c5e15497ac 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -670,25 +670,26 @@ namespace MWMechanics } } - short unsigned AiWander::getRandomIdle() + int AiWander::getRandomIdle() const { - unsigned short idleRoll = 0; - short unsigned selectedAnimation = 0; + MWBase::World* world = MWBase::Environment::get().getWorld(); + static const float fIdleChanceMultiplier = world->getStore().get().find("fIdleChanceMultiplier")->mValue.getFloat(); + if (Misc::Rng::rollClosedProbability(world->getPrng()) > fIdleChanceMultiplier) + return 0; - for(unsigned int counter = 0; counter < mIdle.size(); counter++) + int newIdle = 0; + float maxRoll = 0.f; + for (size_t i = 0; i < mIdle.size(); i++) { - MWBase::World* world = MWBase::Environment::get().getWorld(); - static float fIdleChanceMultiplier = world->getStore().get().find("fIdleChanceMultiplier")->mValue.getFloat(); - - unsigned short idleChance = static_cast(fIdleChanceMultiplier * mIdle[counter]); - unsigned short randSelect = (int)(Misc::Rng::rollProbability(world->getPrng()) * int(100 / fIdleChanceMultiplier)); - if(randSelect < idleChance && randSelect > idleRoll) + float roll = Misc::Rng::rollClosedProbability(world->getPrng()) * 100.f; + if (roll <= mIdle[i] && roll > maxRoll) { - selectedAnimation = counter + GroupIndex_MinIdle; - idleRoll = randSelect; + newIdle = GroupIndex_MinIdle + i; + maxRoll = roll; } } - return selectedAnimation; + + return newIdle; } void AiWander::fastForward(const MWWorld::Ptr& actor, AiState &state) diff --git a/apps/openmw/mwmechanics/aiwander.hpp b/apps/openmw/mwmechanics/aiwander.hpp index 1adb4ed84e..1151005eb4 100644 --- a/apps/openmw/mwmechanics/aiwander.hpp +++ b/apps/openmw/mwmechanics/aiwander.hpp @@ -112,7 +112,7 @@ namespace MWMechanics /// @return Success or error bool playIdle(const MWWorld::Ptr& actor, unsigned short idleSelect); bool checkIdle(const MWWorld::Ptr& actor, unsigned short idleSelect); - short unsigned getRandomIdle(); + int getRandomIdle() const; void setPathToAnAllowedNode(const MWWorld::Ptr& actor, AiWanderStorage& storage, const ESM::Position& actorPos); void evadeObstacles(const MWWorld::Ptr& actor, AiWanderStorage& storage); void doPerFrameActionsForState(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage); diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index bde2a8ce18..e1c71209ca 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -307,7 +307,7 @@ void CharacterController::resetCurrentHitState() void CharacterController::resetCurrentWeaponState() { clearStateAnimation(mCurrentWeapon); - mUpperBodyState = UpperCharState_Nothing; + mUpperBodyState = UpperBodyState::None; } void CharacterController::resetCurrentDeathState() @@ -401,15 +401,15 @@ void CharacterController::refreshHitRecoilAnims() { if (!mCurrentWeapon.empty()) mAnimation->disable(mCurrentWeapon); - if (mUpperBodyState > UpperCharState_WeapEquiped) + if (mUpperBodyState > UpperBodyState::WeaponEquipped) { - mUpperBodyState = UpperCharState_WeapEquiped; + mUpperBodyState = UpperBodyState::WeaponEquipped; if (mWeaponType > ESM::Weapon::None) mAnimation->showWeapons(true); } - else if (mUpperBodyState < UpperCharState_WeapEquiped) + else if (mUpperBodyState < UpperBodyState::WeaponEquipped) { - mUpperBodyState = UpperCharState_Nothing; + mUpperBodyState = UpperBodyState::None; } } @@ -680,7 +680,7 @@ void CharacterController::refreshIdleAnims(CharacterState idle, bool force) { // FIXME: if one of the below states is close to their last animation frame (i.e. will be disabled in the coming update), // the idle animation should be displayed - if (((mUpperBodyState != UpperCharState_Nothing && mUpperBodyState != UpperCharState_WeapEquiped) + if (((mUpperBodyState != UpperBodyState::None && mUpperBodyState != UpperBodyState::WeaponEquipped) || mMovementState != CharState_None || mHitState != CharState_None) && !mPtr.getClass().isBipedal(mPtr)) { resetCurrentIdleState(); @@ -855,7 +855,7 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim getActiveWeapon(mPtr, &mWeaponType); if (mWeaponType != ESM::Weapon::None) { - mUpperBodyState = UpperCharState_WeapEquiped; + mUpperBodyState = UpperBodyState::WeaponEquipped; mCurrentWeapon = getWeaponAnimation(mWeaponType); } @@ -1071,7 +1071,7 @@ void CharacterController::updatePtr(const MWWorld::Ptr &ptr) void CharacterController::updateIdleStormState(bool inwater) const { - if (!mAnimation->hasAnimation("idlestorm") || mUpperBodyState != UpperCharState_Nothing || inwater) + if (!mAnimation->hasAnimation("idlestorm") || mUpperBodyState != UpperBodyState::None || inwater) { mAnimation->disable("idlestorm"); return; @@ -1113,7 +1113,7 @@ bool CharacterController::updateCarriedLeftVisible(const int weaptype) const return mAnimation->updateCarriedLeftVisible(weaptype); } -bool CharacterController::updateWeaponState(CharacterState idle) +bool CharacterController::updateWeaponState() { const auto world = MWBase::Environment::get().getWorld(); auto& prng = world->getPrng(); @@ -1166,20 +1166,18 @@ bool CharacterController::updateWeaponState(CharacterState idle) bool forcestateupdate = false; // We should not play equipping animation and sound during weapon->weapon transition - const bool isStillWeapon = weaptype != ESM::Weapon::HandToHand && weaptype != ESM::Weapon::Spell && weaptype != ESM::Weapon::None && - mWeaponType != ESM::Weapon::HandToHand && mWeaponType != ESM::Weapon::Spell && mWeaponType != ESM::Weapon::None; + const bool isStillWeapon = isRealWeapon(mWeaponType) && isRealWeapon(weaptype); // If the current weapon type was changed in the middle of attack (e.g. by Equip console command or when bound spell expires), // we should force actor to the "weapon equipped" state, interrupt attack and update animations. - if (isStillWeapon && mWeaponType != weaptype && mUpperBodyState > UpperCharState_WeapEquiped) + if (isStillWeapon && mWeaponType != weaptype && mUpperBodyState > UpperBodyState::WeaponEquipped) { forcestateupdate = true; if (!mCurrentWeapon.empty()) mAnimation->disable(mCurrentWeapon); - mUpperBodyState = UpperCharState_WeapEquiped; + mUpperBodyState = UpperBodyState::WeaponEquipped; setAttackingOrSpell(false); mAnimation->showWeapons(true); - stats.setAttackingOrSpell(false); } if(!isKnockedOut() && !isKnockedDown() && !isRecovery()) @@ -1187,7 +1185,7 @@ bool CharacterController::updateWeaponState(CharacterState idle) std::string weapgroup; if ((!isWerewolf || mWeaponType != ESM::Weapon::Spell) && weaptype != mWeaponType - && mUpperBodyState != UpperCharState_UnEquipingWeap + && mUpperBodyState != UpperBodyState::Unequipping && !isStillWeapon) { // We can not play un-equip animation if weapon changed since last update @@ -1209,7 +1207,7 @@ bool CharacterController::updateWeaponState(CharacterState idle) mAnimation->play(weapgroup, priorityWeapon, unequipMask, false, 1.0f, "unequip start", "unequip stop", 0.0f, 0); - mUpperBodyState = UpperCharState_UnEquipingWeap; + mUpperBodyState = UpperBodyState::Unequipping; mAnimation->detachArrow(); @@ -1247,7 +1245,8 @@ bool CharacterController::updateWeaponState(CharacterState idle) if (!isStillWeapon) { - clearStateAnimation(mCurrentWeapon); + if (animPlaying) + mAnimation->disable(mCurrentWeapon); if (weaptype != ESM::Weapon::None) { mAnimation->showWeapons(false); @@ -1262,15 +1261,18 @@ bool CharacterController::updateWeaponState(CharacterState idle) mAnimation->play(weapgroup, priorityWeapon, equipMask, true, 1.0f, "equip start", "equip stop", 0.0f, 0); - mUpperBodyState = UpperCharState_EquipingWeap; + mUpperBodyState = UpperBodyState::Equipping; // If we do not have the "equip attach" key, show weapon manually. - if (weaptype != ESM::Weapon::Spell) + if (weaptype != ESM::Weapon::Spell && mAnimation->getTextKeyTime(weapgroup+": equip attach") < 0) { - if (mAnimation->getTextKeyTime(weapgroup+": equip attach") < 0) - mAnimation->showWeapons(true); + mAnimation->showWeapons(true); } } + if (!upSoundId.empty()) + { + sndMgr->playSound3D(mPtr, upSoundId, 1.0f, 1.0f); + } } if(isWerewolf) @@ -1286,18 +1288,13 @@ bool CharacterController::updateWeaponState(CharacterState idle) mWeaponType = weaptype; mCurrentWeapon = weapgroup; - if(!upSoundId.empty() && !isStillWeapon) - { - sndMgr->playSound3D(mPtr, upSoundId, 1.0f, 1.0f); - } } // Make sure that we disabled unequipping animation - if (mUpperBodyState == UpperCharState_UnEquipingWeap) + if (mUpperBodyState == UpperBodyState::Unequipping) { resetCurrentWeaponState(); mWeaponType = ESM::Weapon::None; - mCurrentWeapon = getWeaponAnimation(mWeaponType); } } } @@ -1317,29 +1314,43 @@ bool CharacterController::updateWeaponState(CharacterState idle) sndMgr->stopSound3D(mPtr, "WolfRun"); } - // Cancel attack if we no longer have ammunition bool ammunition = true; - bool isWeapon = false; float weapSpeed = 1.f; if (cls.hasInventoryStore(mPtr)) { MWWorld::InventoryStore &inv = cls.getInventoryStore(mPtr); - MWWorld::ConstContainerStoreIterator weapon = getActiveWeapon(mPtr, &weaptype); - isWeapon = (weapon != inv.end() && weapon->getType() == ESM::Weapon::sRecordId); - if (isWeapon) + if (stats.getDrawState() == DrawState::Weapon && !mWeapon.isEmpty() && mWeapon.getType() == ESM::Weapon::sRecordId) { - weapSpeed = weapon->get()->mBase->mData.mSpeed; + weapSpeed = mWeapon.get()->mBase->mData.mSpeed; MWWorld::ConstContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); - int ammotype = getWeaponType(weapon->get()->mBase->mData.mType)->mAmmoType; - if (ammotype != ESM::Weapon::None && (ammo == inv.end() || ammo->get()->mBase->mData.mType != ammotype)) - ammunition = false; + int ammotype = getWeaponType(mWeapon.get()->mBase->mData.mType)->mAmmoType; + if (ammotype != ESM::Weapon::None) + ammunition = ammo != inv.end() && ammo->get()->mBase->mData.mType == ammotype; } - if (!ammunition && mUpperBodyState > UpperCharState_WeapEquiped) + // Cancel attack if we no longer have ammunition + if (!ammunition) { - if (!mCurrentWeapon.empty()) + if (mUpperBodyState == UpperBodyState::AttackPreWindUp || mUpperBodyState == UpperBodyState::AttackWindUp) + { mAnimation->disable(mCurrentWeapon); - mUpperBodyState = UpperCharState_WeapEquiped; + mUpperBodyState = UpperBodyState::WeaponEquipped; + } + setAttackingOrSpell(false); + } + + MWWorld::ConstContainerStoreIterator torch = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); + if (torch != inv.end() && torch->getType() == ESM::Light::sRecordId && 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, std::numeric_limits::max(), true); + } + else if (mAnimation->isPlaying("torch")) + { + mAnimation->disable("torch"); } } @@ -1352,15 +1363,13 @@ bool CharacterController::updateWeaponState(CharacterState idle) ESM::WeaponType::Class weapclass = getWeaponType(mWeaponType)->mWeaponClass; if(getAttackingOrSpell()) { - bool resetIdle = ammunition; - if(mUpperBodyState == UpperCharState_WeapEquiped && (mHitState == CharState_None || mHitState == CharState_Block)) + bool resetIdle = true; + if (mUpperBodyState == UpperBodyState::WeaponEquipped && (mHitState == CharState_None || mHitState == CharState_Block)) { mAttackStrength = 0; // Randomize attacks for non-bipedal creatures - if (cls.getType() == ESM::Creature::sRecordId && - !cls.isBipedal(mPtr) && - (!mAnimation->hasAnimation(mCurrentWeapon) || isRandomAttackAnimation(mCurrentWeapon))) + if (!cls.isBipedal(mPtr) && (!mAnimation->hasAnimation(mCurrentWeapon) || isRandomAttackAnimation(mCurrentWeapon))) { mCurrentWeapon = chooseRandomAttackAnimation(); } @@ -1411,7 +1420,7 @@ bool CharacterController::updateWeaponState(CharacterState idle) resetIdle = false; // Spellcasting animation needs to "play" for at least one frame to reset the aiming factor animPlaying = true; - mUpperBodyState = UpperCharState_CastingSpell; + mUpperBodyState = UpperBodyState::Casting; } // Play the spellcasting animation/VFX if the spellcasting was successful or failed due to insufficient magicka. // Used up powers are exempt from this from some reason. @@ -1485,65 +1494,53 @@ bool CharacterController::updateWeaponState(CharacterState idle) MWRender::Animation::BlendMask_All, true, 1, startKey, stopKey, 0.0f, 0); - mUpperBodyState = UpperCharState_CastingSpell; + mUpperBodyState = UpperBodyState::Casting; } else { resetIdle = false; } } - else if(mWeaponType == ESM::Weapon::PickProbe) + else { - world->breakInvisibility(mPtr); - MWWorld::ContainerStoreIterator weapon = cls.getInventoryStore(mPtr).getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - MWWorld::Ptr item = *weapon; - // TODO: this will only work for the player, and needs to be fixed if NPCs should ever use lockpicks/probes. - MWWorld::Ptr target = world->getFacedObject(); - std::string resultMessage, resultSound; + std::string startKey = "start"; + std::string stopKey = "stop"; + bool autodisable = false; - if(!target.isEmpty()) + if (mWeaponType == ESM::Weapon::PickProbe) { - if(item.getType() == ESM::Lockpick::sRecordId) - Security(mPtr).pickLock(target, item, resultMessage, resultSound); - else if(item.getType() == ESM::Probe::sRecordId) - Security(mPtr).probeTrap(target, item, resultMessage, resultSound); - } - mAnimation->play(mCurrentWeapon, priorityWeapon, - MWRender::Animation::BlendMask_All, true, - 1.0f, "start", "stop", 0.0, 0); - mUpperBodyState = UpperCharState_FollowStartToFollowStop; + autodisable = true; + mUpperBodyState = UpperBodyState::AttackEnd; - if(!resultMessage.empty()) - MWBase::Environment::get().getWindowManager()->messageBox(resultMessage); - if(!resultSound.empty()) - sndMgr->playSound3D(target, resultSound, 1.0f, 1.0f); - } - else if (ammunition) - { - std::string startKey; - std::string stopKey; + world->breakInvisibility(mPtr); + // TODO: this will only work for the player, and needs to be fixed if NPCs should ever use lockpicks/probes. + MWWorld::Ptr target = world->getFacedObject(); + std::string resultMessage, resultSound; - if(weapclass == ESM::WeaponType::Ranged || weapclass == ESM::WeaponType::Thrown) - { - mAttackType = "shoot"; - startKey = mAttackType+" start"; - stopKey = mAttackType+" min attack"; + if(!target.isEmpty()) + { + if (mWeapon.getType() == ESM::Lockpick::sRecordId) + Security(mPtr).pickLock(target, mWeapon, resultMessage, resultSound); + else if (mWeapon.getType() == ESM::Probe::sRecordId) + Security(mPtr).probeTrap(target, mWeapon, resultMessage, resultSound); + } + + if (!resultMessage.empty()) + MWBase::Environment::get().getWindowManager()->messageBox(resultMessage); + if (!resultSound.empty()) + sndMgr->playSound3D(target, resultSound, 1.0f, 1.0f); } - else if (isRandomAttackAnimation(mCurrentWeapon)) + else if (!isRandomAttackAnimation(mCurrentWeapon)) { - startKey = "start"; - stopKey = "stop"; - } - else - { - if(mPtr == getPlayer()) + if (weapclass == ESM::WeaponType::Ranged || weapclass == ESM::WeaponType::Thrown) + mAttackType = "shoot"; + else if (mPtr == getPlayer()) { if (Settings::Manager::getBool("best attack", "Game")) { - if (isWeapon) + if (!mWeapon.isEmpty() && mWeapon.getType() == ESM::Weapon::sRecordId) { - MWWorld::ConstContainerStoreIterator weapon = cls.getInventoryStore(mPtr).getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - mAttackType = getBestAttack(weapon->get()->mBase); + mAttackType = getBestAttack(mWeapon.get()->mBase); } else { @@ -1562,12 +1559,12 @@ bool CharacterController::updateWeaponState(CharacterState idle) } mAnimation->play(mCurrentWeapon, priorityWeapon, - MWRender::Animation::BlendMask_All, false, + MWRender::Animation::BlendMask_All, autodisable, weapSpeed, startKey, stopKey, 0.0f, 0); - if(mAnimation->getCurrentTime(mCurrentWeapon) != -1.f) + if (mWeaponType != ESM::Weapon::PickProbe && mAnimation->getCurrentTime(mCurrentWeapon) != -1.f) { - mUpperBodyState = UpperCharState_StartToMinAttack; + mUpperBodyState = UpperBodyState::AttackPreWindUp; if (isRandomAttackAnimation(mCurrentWeapon)) { mAttackStrength = std::min(1.f, 0.1f + Misc::Rng::rollClosedProbability(prng)); @@ -1578,25 +1575,34 @@ bool CharacterController::updateWeaponState(CharacterState idle) } // We should not break swim and sneak animations - if (resetIdle && - idle != CharState_IdleSneak && idle != CharState_IdleSwim && - mIdleState != CharState_IdleSneak && mIdleState != CharState_IdleSwim) + if (resetIdle && mIdleState != CharState_IdleSneak && mIdleState != CharState_IdleSwim) { resetCurrentIdleState(); } - - if (!animPlaying) - animPlaying = mAnimation->getInfo(mCurrentWeapon, &complete); - if(mUpperBodyState == UpperCharState_MinAttackToMaxAttack && !isKnockedDown()) - mAttackStrength = complete; } - else - { + + if (!animPlaying) animPlaying = mAnimation->getInfo(mCurrentWeapon, &complete); - if(mUpperBodyState == UpperCharState_MinAttackToMaxAttack && !isKnockedDown()) + + if (isKnockedDown()) + { + if (mUpperBodyState > UpperBodyState::WeaponEquipped) + { + mUpperBodyState = UpperBodyState::WeaponEquipped; + if (mWeaponType > ESM::Weapon::None) + mAnimation->showWeapons(true); + } + if (!mCurrentWeapon.empty()) + mAnimation->disable(mCurrentWeapon); + } + + if (mUpperBodyState == UpperBodyState::AttackWindUp) + { + mAttackStrength = complete; + + if (!getAttackingOrSpell()) { world->breakInvisibility(mPtr); - float attackStrength = complete; float minAttackTime = mAnimation->getTextKeyTime(mCurrentWeapon+": "+mAttackType+" "+"min attack"); float maxAttackTime = mAnimation->getTextKeyTime(mCurrentWeapon+": "+mAttackType+" "+"max attack"); if (minAttackTime == maxAttackTime) @@ -1604,44 +1610,20 @@ bool CharacterController::updateWeaponState(CharacterState idle) // most creatures don't actually have an attack wind-up animation, so use a uniform random value // (even some creatures that can use weapons don't have a wind-up animation either, e.g. Rieklings) // Note: vanilla MW uses a random value for *all* non-player actors, but we probably don't need to go that far. - attackStrength = std::min(1.f, 0.1f + Misc::Rng::rollClosedProbability(prng)); + mAttackStrength = std::min(1.f, 0.1f + Misc::Rng::rollClosedProbability(prng)); } - if(weapclass != ESM::WeaponType::Ranged && weapclass != ESM::WeaponType::Thrown) - { - if(isWerewolf) - { - const MWWorld::ESMStore &store = world->getStore(); - const ESM::Sound *sound = store.get().searchRandom("WolfSwing", prng); - if(sound) - sndMgr->playSound3D(mPtr, sound->mId, 1.0f, 1.0f); - } - else - { - playSwishSound(attackStrength); - } - } - mAttackStrength = attackStrength; + playSwishSound(mAttackStrength); - mAnimation->disable(mCurrentWeapon); + if (animPlaying) + mAnimation->disable(mCurrentWeapon); mAnimation->play(mCurrentWeapon, priorityWeapon, MWRender::Animation::BlendMask_All, false, weapSpeed, mAttackType+" max attack", mAttackType+" min hit", 1.0f-complete, 0); complete = 0.f; - mUpperBodyState = UpperCharState_MaxAttackToMinHit; - } - else if (isKnockedDown()) - { - if (mUpperBodyState > UpperCharState_WeapEquiped) - { - mUpperBodyState = UpperCharState_WeapEquiped; - if (mWeaponType > ESM::Weapon::None) - mAnimation->showWeapons(true); - } - if (!mCurrentWeapon.empty()) - mAnimation->disable(mCurrentWeapon); + mUpperBodyState = UpperBodyState::AttackRelease; } } @@ -1650,15 +1632,15 @@ bool CharacterController::updateWeaponState(CharacterState idle) { switch (mUpperBodyState) { - case UpperCharState_StartToMinAttack: + case UpperBodyState::AttackPreWindUp: mAnimation->setPitchFactor(complete); break; - case UpperCharState_MinAttackToMaxAttack: - case UpperCharState_MaxAttackToMinHit: - case UpperCharState_MinHitToHit: + case UpperBodyState::AttackWindUp: + case UpperBodyState::AttackRelease: + case UpperBodyState::AttackHit: mAnimation->setPitchFactor(1.f); break; - case UpperCharState_FollowStartToFollowStop: + case UpperBodyState::AttackEnd: if (animPlaying) { // technically we do not need a pitch for crossbow reload animation, @@ -1676,39 +1658,39 @@ bool CharacterController::updateWeaponState(CharacterState idle) if(!animPlaying) { - if(mUpperBodyState == UpperCharState_EquipingWeap || - mUpperBodyState == UpperCharState_FollowStartToFollowStop || - mUpperBodyState == UpperCharState_CastingSpell) + if (mUpperBodyState == UpperBodyState::Equipping || + mUpperBodyState == UpperBodyState::AttackEnd || + mUpperBodyState == UpperBodyState::Casting) { if (ammunition && mWeaponType == ESM::Weapon::MarksmanCrossbow) mAnimation->attachArrow(); // Cancel stagger animation at the end of an attack to avoid abrupt transitions // in favor of a different abrupt transition, like Morrowind - if (mUpperBodyState != UpperCharState_EquipingWeap && isRecovery()) + if (mUpperBodyState != UpperBodyState::Equipping && isRecovery()) mAnimation->disable(mCurrentHit); - mUpperBodyState = UpperCharState_WeapEquiped; + mUpperBodyState = UpperBodyState::WeaponEquipped; } - else if(mUpperBodyState == UpperCharState_UnEquipingWeap) - mUpperBodyState = UpperCharState_Nothing; + else if (mUpperBodyState == UpperBodyState::Unequipping) + mUpperBodyState = UpperBodyState::None; } else if(complete >= 1.0f && !isRandomAttackAnimation(mCurrentWeapon)) { std::string start, stop; switch(mUpperBodyState) { - case UpperCharState_MinAttackToMaxAttack: + case UpperBodyState::AttackWindUp: //hack to avoid body pos desync when jumping/sneaking in 'max attack' state if(!mAnimation->isPlaying(mCurrentWeapon)) mAnimation->play(mCurrentWeapon, priorityWeapon, MWRender::Animation::BlendMask_All, false, 0, mAttackType+" min attack", mAttackType+" max attack", 0.999f, 0); break; - case UpperCharState_StartToMinAttack: - case UpperCharState_MaxAttackToMinHit: + case UpperBodyState::AttackPreWindUp: + case UpperBodyState::AttackRelease: { - if (mUpperBodyState == UpperCharState_StartToMinAttack) + if (mUpperBodyState == UpperBodyState::AttackPreWindUp) { // If actor is already stopped preparing attack, do not play the "min attack -> max attack" part. // Happens if the player did not hold the attack button. @@ -1719,13 +1701,12 @@ bool CharacterController::updateWeaponState(CharacterState idle) { start = mAttackType+" min attack"; stop = mAttackType+" max attack"; - mUpperBodyState = UpperCharState_MinAttackToMaxAttack; + mUpperBodyState = UpperBodyState::AttackWindUp; break; } world->breakInvisibility(mPtr); - if(weapclass != ESM::WeaponType::Ranged && weapclass != ESM::WeaponType::Thrown) - playSwishSound(0.0f); + playSwishSound(0.0f); } if(mAttackType == "shoot") @@ -1738,10 +1719,10 @@ bool CharacterController::updateWeaponState(CharacterState idle) start = mAttackType+" min hit"; stop = mAttackType+" hit"; } - mUpperBodyState = UpperCharState_MinHitToHit; + mUpperBodyState = UpperBodyState::AttackHit; break; } - case UpperCharState_MinHitToHit: + case UpperBodyState::AttackHit: if(mAttackType == "shoot") { start = mAttackType+" follow start"; @@ -1757,57 +1738,26 @@ bool CharacterController::updateWeaponState(CharacterState idle) : (str < 1.0f) ? " medium follow stop" : " large follow stop"); } - mUpperBodyState = UpperCharState_FollowStartToFollowStop; + mUpperBodyState = UpperBodyState::AttackEnd; break; default: break; } - // Note: apply crossbow reload animation only for upper body - // since blending with movement animations can give weird result. if(!start.empty()) { - int mask = MWRender::Animation::BlendMask_All; - if (mWeaponType == ESM::Weapon::MarksmanCrossbow) - mask = MWRender::Animation::BlendMask_UpperBody; - + bool autodisable = mUpperBodyState == UpperBodyState::AttackEnd; mAnimation->disable(mCurrentWeapon); - if (mUpperBodyState == UpperCharState_FollowStartToFollowStop) - mAnimation->play(mCurrentWeapon, priorityWeapon, - mask, true, - weapSpeed, start, stop, 0.0f, 0); - else - mAnimation->play(mCurrentWeapon, priorityWeapon, - mask, false, - weapSpeed, start, stop, 0.0f, 0); + mAnimation->play(mCurrentWeapon, priorityWeapon, MWRender::Animation::BlendMask_All, autodisable, weapSpeed, start, stop, 0.0f, 0); } } else if(complete >= 1.0f && isRandomAttackAnimation(mCurrentWeapon)) { clearStateAnimation(mCurrentWeapon); - mUpperBodyState = UpperCharState_WeapEquiped; + mUpperBodyState = UpperBodyState::WeaponEquipped; } - if (cls.hasInventoryStore(mPtr)) - { - const MWWorld::InventoryStore& inv = cls.getInventoryStore(mPtr); - MWWorld::ConstContainerStoreIterator torch = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); - if(torch != inv.end() && torch->getType() == ESM::Light::sRecordId - && 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); - } - else if (mAnimation->isPlaying("torch")) - { - mAnimation->disable("torch"); - } - } - - mAnimation->setAccurateAiming(mUpperBodyState > UpperCharState_WeapEquiped); + mAnimation->setAccurateAiming(mUpperBodyState > UpperBodyState::WeaponEquipped); return forcestateupdate; } @@ -2270,7 +2220,7 @@ void CharacterController::update(float duration) if (!mSkipAnim) { - refreshCurrentAnims(idlestate, movestate, jumpstate, updateWeaponState(idlestate)); + refreshCurrentAnims(idlestate, movestate, jumpstate, updateWeaponState()); updateIdleStormState(inwater); } @@ -2567,8 +2517,8 @@ void CharacterController::forceStateUpdate() mCanCast = false; mCastingManualSpell = false; setAttackingOrSpell(false); - if (mUpperBodyState != UpperCharState_Nothing) - mUpperBodyState = UpperCharState_WeapEquiped; + if (mUpperBodyState != UpperBodyState::None) + mUpperBodyState = UpperBodyState::WeaponEquipped; refreshCurrentAnims(mIdleState, mMovementState, mJumpState, true); @@ -2687,13 +2637,13 @@ bool CharacterController::isRandomAttackAnimation(std::string_view group) bool CharacterController::isAttackPreparing() const { - return mUpperBodyState == UpperCharState_StartToMinAttack || - mUpperBodyState == UpperCharState_MinAttackToMaxAttack; + return mUpperBodyState == UpperBodyState::AttackPreWindUp || + mUpperBodyState == UpperBodyState::AttackWindUp; } bool CharacterController::isCastingSpell() const { - return mCastingManualSpell || mUpperBodyState == UpperCharState_CastingSpell; + return mCastingManualSpell || mUpperBodyState == UpperBodyState::Casting; } bool CharacterController::isReadyToBlock() const @@ -2729,8 +2679,7 @@ bool CharacterController::isRecovery() const bool CharacterController::isAttackingOrSpell() const { - return mUpperBodyState != UpperCharState_Nothing && - mUpperBodyState != UpperCharState_WeapEquiped; + return mUpperBodyState != UpperBodyState::None && mUpperBodyState != UpperBodyState::WeaponEquipped; } bool CharacterController::isSneaking() const @@ -2786,7 +2735,7 @@ std::string_view CharacterController::getRandomAttackType() bool CharacterController::readyToPrepareAttack() const { return (mHitState == CharState_None || mHitState == CharState_Block) - && mUpperBodyState <= UpperCharState_WeapEquiped; + && mUpperBodyState <= UpperBodyState::WeaponEquipped; } bool CharacterController::readyToStartAttack() const @@ -2794,7 +2743,7 @@ bool CharacterController::readyToStartAttack() const if (mHitState != CharState_None && mHitState != CharState_Block) return false; - return mUpperBodyState == UpperCharState_WeapEquiped; + return mUpperBodyState == UpperBodyState::WeaponEquipped; } float CharacterController::getAttackStrength() const @@ -2819,15 +2768,33 @@ void CharacterController::setHeadTrackTarget(const MWWorld::ConstPtr &target) void CharacterController::playSwishSound(float attackStrength) const { - MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); + ESM::WeaponType::Class weapclass = getWeaponType(mWeaponType)->mWeaponClass; + if (weapclass == ESM::WeaponType::Ranged || weapclass == ESM::WeaponType::Thrown) + return; - std::string sound = "Weapon Swish"; - if(attackStrength < 0.5f) - sndMgr->playSound3D(mPtr, sound, 1.0f, 0.8f); //Weak attack - else if(attackStrength < 1.0f) - sndMgr->playSound3D(mPtr, sound, 1.0f, 1.0f); //Medium attack + std::string soundId; + float pitch = 1.f; + + const MWWorld::Class &cls = mPtr.getClass(); + if (cls.isNpc() && cls.getNpcStats(mPtr).isWerewolf()) + { + MWBase::World* world = MWBase::Environment::get().getWorld(); + const MWWorld::ESMStore &store = world->getStore(); + const ESM::Sound *sound = store.get().searchRandom("WolfSwing", world->getPrng()); + if (sound) + soundId = sound->mId; + } else - sndMgr->playSound3D(mPtr, sound, 1.0f, 1.2f); //Strong attack + { + soundId = "Weapon Swish"; + if (attackStrength < 0.5f) + pitch = 0.8f; // Weak attack + else if (attackStrength >= 1.f) + pitch = 1.2f; // Strong attack + } + + if (!soundId.empty()) + MWBase::Environment::get().getSoundManager()->playSound3D(mPtr, soundId, 1.0f, pitch); } void CharacterController::updateHeadTracking(float duration) diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index 42c60b5ffb..3832bd9ee1 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -103,17 +103,18 @@ enum CharacterState { CharState_Block }; -enum UpperBodyCharacterState { - UpperCharState_Nothing, - UpperCharState_EquipingWeap, - UpperCharState_UnEquipingWeap, - UpperCharState_WeapEquiped, - UpperCharState_StartToMinAttack, - UpperCharState_MinAttackToMaxAttack, - UpperCharState_MaxAttackToMinHit, - UpperCharState_MinHitToHit, - UpperCharState_FollowStartToFollowStop, - UpperCharState_CastingSpell +enum class UpperBodyState +{ + None, + Equipping, + Unequipping, + WeaponEquipped, + AttackPreWindUp, + AttackWindUp, + AttackRelease, + AttackHit, + AttackEnd, + Casting }; enum JumpingState { @@ -156,7 +157,7 @@ class CharacterController : public MWRender::Animation::TextKeyListener CharacterState mHitState{CharState_None}; std::string mCurrentHit; - UpperBodyCharacterState mUpperBodyState{UpperCharState_Nothing}; + UpperBodyState mUpperBodyState{UpperBodyState::None}; JumpingState mJumpState{JumpState_None}; std::string mCurrentJump; @@ -203,7 +204,7 @@ class CharacterController : public MWRender::Animation::TextKeyListener void clearAnimQueue(bool clearPersistAnims = false); - bool updateWeaponState(CharacterState idle); + bool updateWeaponState(); void updateIdleStormState(bool inwater) const; std::string chooseRandomAttackAnimation() const; diff --git a/apps/openmw/mwrender/actoranimation.cpp b/apps/openmw/mwrender/actoranimation.cpp index c238664ae5..3d5daf74e0 100644 --- a/apps/openmw/mwrender/actoranimation.cpp +++ b/apps/openmw/mwrender/actoranimation.cpp @@ -158,33 +158,16 @@ bool ActorAnimation::updateCarriedLeftVisible(const int weaptype) const { const MWWorld::Class &cls = mPtr.getClass(); MWMechanics::CreatureStats &stats = cls.getCreatureStats(mPtr); - if (cls.hasInventoryStore(mPtr) && weaptype != ESM::Weapon::Spell) + if (cls.hasInventoryStore(mPtr) && stats.getDrawState() == MWMechanics::DrawState::Nothing) { SceneUtil::FindByNameVisitor findVisitor ("Bip01 AttachShield"); mObjectRoot->accept(findVisitor); - if (findVisitor.mFoundNode || (mPtr == MWMechanics::getPlayer() && mPtr.isInCell() && MWBase::Environment::get().getWorld()->isFirstPerson())) + if (findVisitor.mFoundNode) { const MWWorld::InventoryStore& inv = cls.getInventoryStore(mPtr); - const MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); const MWWorld::ConstContainerStoreIterator shield = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); if (shield != inv.end() && shield->getType() == ESM::Armor::sRecordId && !getSheathedShieldMesh(*shield).empty()) - { - if(stats.getDrawState() != MWMechanics::DrawState::Weapon) - return false; - - if (weapon != inv.end()) - { - auto type = weapon->getType(); - if(type == ESM::Weapon::sRecordId) - { - const MWWorld::LiveCellRef *ref = weapon->get(); - ESM::Weapon::Type weaponType = (ESM::Weapon::Type)ref->mBase->mData.mType; - return !(MWMechanics::getWeaponType(weaponType)->mFlags & ESM::WeaponType::TwoHanded); - } - else if (type == ESM::Lockpick::sRecordId || type == ESM::Probe::sRecordId) - return true; - } - } + return false; } } } diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index bd9fe543c4..a7c48864dd 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -295,13 +295,23 @@ void NpcAnimation::setViewMode(NpcAnimation::ViewMode viewMode) assert(viewMode != VM_HeadOnly); if(mViewMode == viewMode) return; - + // FIXME: sheathing state must be consistent if the third person skeleton doesn't have the necessary node, but + // third person skeleton is unavailable in first person view. This is a hack to avoid cosmetic issues. + bool viewChange = mViewMode == VM_FirstPerson || viewMode == VM_FirstPerson; mViewMode = viewMode; MWBase::Environment::get().getWorld()->scaleObject(mPtr, mPtr.getCellRef().getScale(), true); // apply race height after view change mAmmunition.reset(); rebuild(); setRenderBin(); + + static const bool shieldSheathing = Settings::Manager::getBool("shield sheathing", "Game"); + if (viewChange && shieldSheathing) + { + int weaptype = ESM::Weapon::None; + MWMechanics::getActiveWeapon(mPtr, &weaptype); + showCarriedLeft(updateCarriedLeftVisible(weaptype)); + } } /// @brief A RenderBin callback to clear the depth buffer before rendering. @@ -983,6 +993,30 @@ void NpcAnimation::showWeapons(bool showWeapon) updateQuiver(); } +bool NpcAnimation::updateCarriedLeftVisible(const int weaptype) const +{ + static const bool shieldSheathing = Settings::Manager::getBool("shield sheathing", "Game"); + if (shieldSheathing) + { + const MWWorld::Class &cls = mPtr.getClass(); + MWMechanics::CreatureStats &stats = cls.getCreatureStats(mPtr); + if (stats.getDrawState() == MWMechanics::DrawState::Nothing) + { + SceneUtil::FindByNameVisitor findVisitor ("Bip01 AttachShield"); + mObjectRoot->accept(findVisitor); + if (findVisitor.mFoundNode || mViewMode == VM_FirstPerson) + { + const MWWorld::InventoryStore& inv = cls.getInventoryStore(mPtr); + const MWWorld::ConstContainerStoreIterator shield = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); + if (shield != inv.end() && shield->getType() == ESM::Armor::sRecordId && !getSheathedShieldMesh(*shield).empty()) + return false; + } + } + } + + return !(MWMechanics::getWeaponType(weaptype)->mFlags & ESM::WeaponType::TwoHanded); +} + void NpcAnimation::showCarriedLeft(bool show) { mShowCarriedLeft = show; diff --git a/apps/openmw/mwrender/npcanimation.hpp b/apps/openmw/mwrender/npcanimation.hpp index 2dcfac3036..d16b339ec6 100644 --- a/apps/openmw/mwrender/npcanimation.hpp +++ b/apps/openmw/mwrender/npcanimation.hpp @@ -137,6 +137,7 @@ public: void showWeapons(bool showWeapon) override; + bool updateCarriedLeftVisible(const int weaptype) const override; bool getCarriedLeftShown() const override { return mShowCarriedLeft; } void showCarriedLeft(bool show) override; diff --git a/apps/openmw/mwrender/objectpaging.cpp b/apps/openmw/mwrender/objectpaging.cpp index 20d69bcf11..53af609a71 100644 --- a/apps/openmw/mwrender/objectpaging.cpp +++ b/apps/openmw/mwrender/objectpaging.cpp @@ -1,6 +1,7 @@ #include "objectpaging.hpp" #include +#include #include #include @@ -276,7 +277,7 @@ namespace MWRender RefnumSet(){} RefnumSet(const RefnumSet& copy, const osg::CopyOp&) : mRefnums(copy.mRefnums) {} META_Object(MWRender, RefnumSet) - std::set mRefnums; + std::vector mRefnums; }; class AnalyzeVisitor : public osg::NodeVisitor @@ -554,7 +555,7 @@ namespace MWRender if (cnode->getNumChildrenRequiringUpdateTraversal() > 0 || SceneUtil::hasUserDescription(cnode, Constants::NightDayLabel) || SceneUtil::hasUserDescription(cnode, Constants::HerbalismLabel)) continue; else - refnumSet->mRefnums.insert(pair.first); + refnumSet->mRefnums.push_back(pair.first); } { @@ -727,6 +728,8 @@ namespace MWRender osg::UserDataContainer* udc = group->getOrCreateUserDataContainer(); if (activeGrid) { + std::sort(refnumSet->mRefnums.begin(), refnumSet->mRefnums.end()); + refnumSet->mRefnums.erase(std::unique(refnumSet->mRefnums.begin(), refnumSet->mRefnums.end()), refnumSet->mRefnums.end()); udc->addUserObject(refnumSet); group->addCullCallback(new SceneUtil::LightListCallback); } @@ -837,7 +840,7 @@ namespace MWRender struct GetRefnumsFunctor { - GetRefnumsFunctor(std::set& output) : mOutput(output) {} + GetRefnumsFunctor(std::vector& output) : mOutput(output) {} void operator()(MWRender::ChunkId chunkId, osg::Object* obj) { if (!std::get<2>(chunkId)) return; @@ -850,18 +853,20 @@ namespace MWRender { RefnumSet* refnums = dynamic_cast(udc->getUserObject(0)); if (!refnums) return; - mOutput.insert(refnums->mRefnums.begin(), refnums->mRefnums.end()); + mOutput.insert(mOutput.end(), refnums->mRefnums.begin(), refnums->mRefnums.end()); } } osg::Vec4i mActiveGrid; - std::set& mOutput; + std::vector& mOutput; }; - void ObjectPaging::getPagedRefnums(const osg::Vec4i &activeGrid, std::set &out) + void ObjectPaging::getPagedRefnums(const osg::Vec4i &activeGrid, std::vector& out) { GetRefnumsFunctor grf(out); grf.mActiveGrid = activeGrid; mCache->call(grf); + std::sort(out.begin(), out.end()); + out.erase(std::unique(out.begin(), out.end()), out.end()); } void ObjectPaging::reportStats(unsigned int frameNumber, osg::Stats *stats) const diff --git a/apps/openmw/mwrender/objectpaging.hpp b/apps/openmw/mwrender/objectpaging.hpp index 940760ff6c..4820f150ab 100644 --- a/apps/openmw/mwrender/objectpaging.hpp +++ b/apps/openmw/mwrender/objectpaging.hpp @@ -47,7 +47,7 @@ namespace MWRender void reportStats(unsigned int frameNumber, osg::Stats* stats) const override; - void getPagedRefnums(const osg::Vec4i &activeGrid, std::set &out); + void getPagedRefnums(const osg::Vec4i &activeGrid, std::vector& out); private: Resource::SceneManager* mSceneManager; diff --git a/apps/openmw/mwrender/pingpongcanvas.cpp b/apps/openmw/mwrender/pingpongcanvas.cpp index 37c357f3fb..16c59787a2 100644 --- a/apps/openmw/mwrender/pingpongcanvas.cpp +++ b/apps/openmw/mwrender/pingpongcanvas.cpp @@ -100,21 +100,10 @@ namespace MWRender if (filtered.empty() || !bufferData.postprocessing) { - if (bufferData.postprocessing) - { - if (!mLoggedLastError) - { - Log(Debug::Error) << "Critical error, postprocess shaders failed to compile. Using default shader."; - mLoggedLastError = true; - } - } - else - mLoggedLastError = false; - state.pushStateSet(mFallbackStateSet); state.apply(); - if (Stereo::getMultiview() && mMultiviewResolveProgram) + if (Stereo::getMultiview()) { state.pushStateSet(mMultiviewResolveStateSet); state.apply(); @@ -126,7 +115,7 @@ namespace MWRender drawGeometry(renderInfo); state.popStateSet(); - if (Stereo::getMultiview() && mMultiviewResolveProgram) + if (Stereo::getMultiview()) { state.popStateSet(); } @@ -298,7 +287,7 @@ namespace MWRender state.popStateSet(); } - if (Stereo::getMultiview() && mMultiviewResolveProgram) + if (Stereo::getMultiview()) { ext->glBindFramebuffer(GL_DRAW_FRAMEBUFFER_EXT, 0); lastApplied = 0; diff --git a/apps/openmw/mwrender/pingpongcanvas.hpp b/apps/openmw/mwrender/pingpongcanvas.hpp index c3eedb3494..eaaa90b778 100644 --- a/apps/openmw/mwrender/pingpongcanvas.hpp +++ b/apps/openmw/mwrender/pingpongcanvas.hpp @@ -83,8 +83,6 @@ namespace MWRender mutable std::array mBufferData; mutable std::array, 3> mFbos; mutable osg::ref_ptr mRenderViewport; - - mutable bool mLoggedLastError = false; }; } diff --git a/apps/openmw/mwrender/postprocessor.cpp b/apps/openmw/mwrender/postprocessor.cpp index 1946efb145..bf5ade223c 100644 --- a/apps/openmw/mwrender/postprocessor.cpp +++ b/apps/openmw/mwrender/postprocessor.cpp @@ -126,7 +126,6 @@ namespace MWRender , mNormalsSupported(false) , mPassLights(false) , mPrevPassLights(false) - , mMainTemplate(new osg::Texture2D) { mSoftParticles = Settings::Manager::getBool("soft particles", "Shaders"); mUsePostProcessing = Settings::Manager::getBool("enabled", "Post Processing"); @@ -249,14 +248,6 @@ namespace MWRender populateTechniqueFiles(); } - mMainTemplate->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); - mMainTemplate->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); - mMainTemplate->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); - mMainTemplate->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); - mMainTemplate->setInternalFormat(GL_RGBA); - mMainTemplate->setSourceType(GL_UNSIGNED_BYTE); - mMainTemplate->setSourceFormat(GL_RGBA); - createTexturesAndCamera(frame() % 2); removeChild(mHUDCamera); @@ -402,13 +393,8 @@ namespace MWRender mReload = false; - if (!mTechniques.empty()) - reloadMainPass(*mTechniques[0]); - - reloadTechniques(); - - if (!mUsePostProcessing) - resize(); + loadChain(); + resize(); } void PostProcessor::update(size_t frameId) @@ -486,9 +472,6 @@ namespace MWRender auto fpDepthRb = createFrameBufferAttachmentFromTemplate(Usage::RENDER_BUFFER, width, height, textures[Tex_Depth], mSamples); fbos[FBO_FirstPerson]->setAttachment(osg::FrameBufferObject::BufferComponent::PACKED_DEPTH_STENCIL_BUFFER, osg::FrameBufferAttachment(fpDepthRb)); - // When MSAA is enabled we must first render to a render buffer, then - // blit the result to the FBO which is either passed to the main frame - // buffer for display or used as the entry point for a post process chain. if (mSamples > 1) { fbos[FBO_Multisample] = new osg::FrameBufferObject; @@ -497,7 +480,6 @@ namespace MWRender { auto normalRB = createFrameBufferAttachmentFromTemplate(Usage::RENDER_BUFFER, width, height, textures[Tex_Normal], mSamples); fbos[FBO_Multisample]->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER1, normalRB); - fbos[FBO_FirstPerson]->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER1, normalRB); } auto depthRB = createFrameBufferAttachmentFromTemplate(Usage::RENDER_BUFFER, width, height, textures[Tex_Depth], mSamples); fbos[FBO_Multisample]->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0, colorRB); @@ -670,7 +652,7 @@ namespace MWRender return Status_Error; } - if (!technique || technique->getLocked() || (location.has_value() && location.value() <= 0)) + if (!technique || technique->getLocked() || (location.has_value() && location.value() < 0)) return Status_Error; disableTechnique(technique, false); @@ -737,17 +719,6 @@ namespace MWRender textures[Tex_Normal]->setSourceFormat(GL_RGB); textures[Tex_Normal]->setInternalFormat(GL_RGB); - if (mMainTemplate) - { - textures[Tex_Scene]->setSourceFormat(mMainTemplate->getSourceFormat()); - textures[Tex_Scene]->setSourceType(mMainTemplate->getSourceType()); - textures[Tex_Scene]->setInternalFormat(mMainTemplate->getInternalFormat()); - textures[Tex_Scene]->setFilter(osg::Texture2D::MIN_FILTER, mMainTemplate->getFilter(osg::Texture2D::MIN_FILTER)); - textures[Tex_Scene]->setFilter(osg::Texture2D::MAG_FILTER, mMainTemplate->getFilter(osg::Texture2D::MAG_FILTER)); - textures[Tex_Scene]->setWrap(osg::Texture::WRAP_S, mMainTemplate->getWrap(osg::Texture2D::WRAP_S)); - textures[Tex_Scene]->setWrap(osg::Texture::WRAP_T, mMainTemplate->getWrap(osg::Texture2D::WRAP_T)); - } - auto setupDepth = [] (osg::Texture* tex) { tex->setSourceFormat(GL_DEPTH_STENCIL_EXT); tex->setSourceType(SceneUtil::AutoDepth::depthSourceType()); @@ -819,14 +790,12 @@ namespace MWRender return technique; } - reloadMainPass(*technique); - mTemplates.push_back(std::move(technique)); return mTemplates.back(); } - void PostProcessor::reloadTechniques() + void PostProcessor::loadChain() { if (!isEnabled()) return; @@ -835,18 +804,9 @@ namespace MWRender std::vector techniqueStrings = Settings::Manager::getStringArray("chain", "Post Processing"); - const std::string mainIdentifier = "main"; - - auto main = loadTechnique(mainIdentifier); - - if (main) - main->setLocked(true); - - mTechniques.push_back(std::move(main)); - for (auto& techniqueName : techniqueStrings) { - if (techniqueName.empty() || Misc::StringUtils::ciEqual(techniqueName, mainIdentifier)) + if (techniqueName.empty()) continue; mTechniques.push_back(loadTechnique(techniqueName)); @@ -855,14 +815,17 @@ namespace MWRender dirtyTechniques(); } - void PostProcessor::reloadMainPass(fx::Technique& technique) + void PostProcessor::saveChain() { - if (!technique.getMainTemplate()) - return; + std::vector chain; - mMainTemplate = technique.getMainTemplate(); + for (const auto& technique : mTechniques) { + if (!technique || technique->getDynamic()) + continue; + chain.push_back(technique->getName()); + } - resize(); + Settings::Manager::setStringArray("chain", "Post Processing", chain); } void PostProcessor::toggleMode() diff --git a/apps/openmw/mwrender/postprocessor.hpp b/apps/openmw/mwrender/postprocessor.hpp index fc20340a29..23cd0f130b 100644 --- a/apps/openmw/mwrender/postprocessor.hpp +++ b/apps/openmw/mwrender/postprocessor.hpp @@ -86,6 +86,13 @@ namespace MWRender Unit_NextFree }; + enum Status + { + Status_Error, + Status_Toggled, + Status_Unchanged + }; + PostProcessor(RenderingManager& rendering, osgViewer::Viewer* viewer, osg::Group* rootNode, const VFS::Manager* vfs); ~PostProcessor(); @@ -110,13 +117,6 @@ namespace MWRender void resize(); - enum Status - { - Status_Error, - Status_Toggled, - Status_Unchanged - }; - Status enableTechnique(std::shared_ptr technique, std::optional location = std::nullopt); Status disableTechnique(std::shared_ptr technique, bool dirty = true); @@ -184,6 +184,10 @@ namespace MWRender bool mEnableLiveReload; + void loadChain(); + void saveChain(); + + private: void populateTechniqueFiles(); @@ -194,8 +198,6 @@ namespace MWRender void createTexturesAndCamera(size_t frameId); - void reloadTechniques(); - void reloadMainPass(fx::Technique& technique); void dirtyTechniques(); @@ -250,8 +252,6 @@ namespace MWRender bool mUBO; int mGLSLVersion; - osg::ref_ptr mMainTemplate; - osg::ref_ptr mStateUpdater; osg::ref_ptr mPingPongCull; osg::ref_ptr mPingPongCanvas; diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 2aef65029c..80003c2b7e 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -637,7 +637,6 @@ namespace MWRender updateProjectionMatrix(); mViewer->getCamera()->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); - mViewer->getUpdateVisitor()->setTraversalMode(osg::NodeVisitor::TRAVERSE_ACTIVE_CHILDREN); } RenderingManager::~RenderingManager() @@ -1616,7 +1615,7 @@ namespace MWRender } return false; } - void RenderingManager::getPagedRefnums(const osg::Vec4i &activeGrid, std::set &out) + void RenderingManager::getPagedRefnums(const osg::Vec4i &activeGrid, std::vector& out) { if (mObjectPaging) mObjectPaging->getPagedRefnums(activeGrid, out); diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index 10b4872650..83dfd32287 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -250,7 +250,7 @@ namespace MWRender bool pagingEnableObject(int type, const MWWorld::ConstPtr& ptr, bool enabled); void pagingBlacklistObject(int type, const MWWorld::ConstPtr &ptr); bool pagingUnlockCache(); - void getPagedRefnums(const osg::Vec4i &activeGrid, std::set &out); + void getPagedRefnums(const osg::Vec4i &activeGrid, std::vector& out); void updateProjectionMatrix(); diff --git a/apps/openmw/mwrender/transparentpass.cpp b/apps/openmw/mwrender/transparentpass.cpp index facd271643..eee7d72ecd 100644 --- a/apps/openmw/mwrender/transparentpass.cpp +++ b/apps/openmw/mwrender/transparentpass.cpp @@ -109,9 +109,6 @@ namespace MWRender if (rl->_drawable->getNodeMask() == Mask_ParticleSystem || rl->_drawable->getNodeMask() == Mask_Effect) continue; - if (ss->getAttribute(osg::StateAttribute::ALPHAFUNC)) - continue; - if (ss->getAttribute(osg::StateAttribute::MATERIAL)) { const osg::Material* mat = static_cast(ss->getAttribute(osg::StateAttribute::MATERIAL)); if (mat->getDiffuse(osg::Material::FRONT).a() < 0.5) diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 63c21d1a1b..e60be693e7 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -99,8 +99,8 @@ namespace return model; } - void addObject(const MWWorld::Ptr& ptr, const MWWorld::World& world, MWPhysics::PhysicsSystem& physics, - MWRender::RenderingManager& rendering, std::set& pagedRefs) + void addObject(const MWWorld::Ptr& ptr, const MWWorld::World& world, const std::vector& pagedRefs, + MWPhysics::PhysicsSystem& physics, MWRender::RenderingManager& rendering) { if (ptr.getRefData().getBaseNode() || physics.getActor(ptr)) { @@ -112,7 +112,7 @@ namespace const auto rotation = makeDirectNodeRotation(ptr); const ESM::RefNum& refnum = ptr.getCellRef().getRefNum(); - if (!refnum.hasContentFile() || pagedRefs.find(refnum) == pagedRefs.end()) + if (!refnum.hasContentFile() || !std::binary_search(pagedRefs.begin(), pagedRefs.end(), refnum)) ptr.getClass().insertObjectRendering(ptr, model, rendering); else ptr.getRefData().setBaseNode(new SceneUtil::PositionAttitudeTransform); // FIXME remove this when physics code is fixed not to depend on basenode @@ -255,6 +255,15 @@ namespace } return false; } + + bool removeFromSorted(const ESM::RefNum& refNum, std::vector& pagedRefs) + { + const auto it = std::lower_bound(pagedRefs.begin(), pagedRefs.end(), refNum); + if (it == pagedRefs.end() || *it != refNum) + return false; + pagedRefs.erase(it); + return true; + } } @@ -264,7 +273,7 @@ namespace MWWorld void Scene::removeFromPagedRefs(const Ptr &ptr) { const ESM::RefNum& refnum = ptr.getCellRef().getRefNum(); - if (refnum.hasContentFile() && mPagedRefs.erase(refnum)) + if (refnum.hasContentFile() && removeFromSorted(refnum, mPagedRefs)) { if (!ptr.getRefData().getBaseNode()) return; ptr.getClass().insertObjectRendering(ptr, getModel(ptr, mRendering.getResourceSystem()->getVFS()), mRendering); @@ -900,7 +909,7 @@ namespace MWWorld { InsertVisitor insertVisitor(cell, loadingListener); cell.forEach (insertVisitor); - insertVisitor.insert([&] (const MWWorld::Ptr& ptr) { addObject(ptr, mWorld, *mPhysics, mRendering, mPagedRefs); }); + insertVisitor.insert([&] (const MWWorld::Ptr& ptr) { addObject(ptr, mWorld, mPagedRefs, *mPhysics, mRendering); }); insertVisitor.insert([&] (const MWWorld::Ptr& ptr) { addObject(ptr, mWorld, *mPhysics, mNavigator); }); } @@ -908,7 +917,7 @@ namespace MWWorld { try { - addObject(ptr, mWorld, *mPhysics, mRendering, mPagedRefs); + addObject(ptr, mWorld, mPagedRefs, *mPhysics, mRendering); addObject(ptr, mWorld, *mPhysics, mNavigator); mWorld.scaleObject(ptr, ptr.getCellRef().getScale()); if (mCurrentCell != nullptr) diff --git a/apps/openmw/mwworld/scene.hpp b/apps/openmw/mwworld/scene.hpp index c504a2cf73..38e39e9450 100644 --- a/apps/openmw/mwworld/scene.hpp +++ b/apps/openmw/mwworld/scene.hpp @@ -104,7 +104,7 @@ namespace MWWorld osg::Vec3f mLastPlayerPos; - std::set mPagedRefs; + std::vector mPagedRefs; std::vector> mWorkItems; diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index 072514d0cc..113a0b61ec 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -1,130 +1,130 @@ -find_package(GTest 1.10 REQUIRED) -find_package(GMock 1.10 REQUIRED) - -if (GTEST_FOUND AND GMOCK_FOUND) - include_directories(SYSTEM ${GTEST_INCLUDE_DIRS}) - include_directories(SYSTEM ${GMOCK_INCLUDE_DIRS}) - - file(GLOB UNITTEST_SRC_FILES - testing_util.hpp - - ../openmw/mwworld/store.cpp - ../openmw/mwworld/esmstore.cpp - mwworld/test_store.cpp - - mwdialogue/test_keywordsearch.cpp - - mwscript/test_scripts.cpp - - esm/test_fixed_string.cpp - esm/variant.cpp - - lua/test_lua.cpp - lua/test_scriptscontainer.cpp - lua/test_utilpackage.cpp - lua/test_serialization.cpp - lua/test_configuration.cpp - lua/test_l10n.cpp - lua/test_storage.cpp - - lua/test_ui_content.cpp - - misc/test_stringops.cpp - misc/test_endianness.cpp - misc/test_resourcehelpers.cpp - misc/progressreporter.cpp - misc/compression.cpp - - nifloader/testbulletnifloader.cpp - - detournavigator/navigator.cpp - detournavigator/settingsutils.cpp - detournavigator/recastmeshbuilder.cpp - detournavigator/gettilespositions.cpp - detournavigator/recastmeshobject.cpp - detournavigator/navmeshtilescache.cpp - detournavigator/tilecachedrecastmeshmanager.cpp - detournavigator/navmeshdb.cpp - detournavigator/serialization.cpp - detournavigator/asyncnavmeshupdater.cpp - - serialization/binaryreader.cpp - serialization/binarywriter.cpp - serialization/sizeaccumulator.cpp - serialization/integration.cpp - - settings/parser.cpp - settings/shadermanager.cpp - - shader/parsedefines.cpp - shader/parsefors.cpp - shader/parselinks.cpp - shader/shadermanager.cpp - - ../openmw/options.cpp - openmw/options.cpp - - sqlite3/db.cpp - sqlite3/request.cpp - sqlite3/statement.cpp - sqlite3/transaction.cpp - - esmloader/load.cpp - esmloader/esmdata.cpp - esmloader/record.cpp - - files/hash.cpp - - toutf8/toutf8.cpp - - esm4/includes.cpp - - fx/lexer.cpp - fx/technique.cpp - - esm3/readerscache.cpp - ) - - source_group(apps\\openmw_test_suite FILES openmw_test_suite.cpp ${UNITTEST_SRC_FILES}) - - openmw_add_executable(openmw_test_suite openmw_test_suite.cpp ${UNITTEST_SRC_FILES}) - - target_link_libraries(openmw_test_suite ${GMOCK_LIBRARIES} components) - # Fix for not visible pthreads functions for linker with glibc 2.15 - if (UNIX AND NOT APPLE) - target_link_libraries(openmw_test_suite ${CMAKE_THREAD_LIBS_INIT}) - endif() - - if (BUILD_WITH_CODE_COVERAGE) - add_definitions(--coverage) - target_link_libraries(openmw_test_suite gcov) - endif() - - file(DOWNLOAD - https://gitlab.com/OpenMW/example-suite/-/raw/8966dab24692555eec720c854fb0f73d108070cd/data/template.omwgame - ${CMAKE_CURRENT_BINARY_DIR}/data/template.omwgame - EXPECTED_HASH SHA512=6e38642bcf013c5f496a9cb0bf3ec7c9553b6e86b836e7844824c5a05f556c9391167214469b6318401684b702d7569896bf743c85aee4198612b3315ba778d6 - ) - - target_compile_definitions(openmw_test_suite - PRIVATE OPENMW_DATA_DIR="${CMAKE_CURRENT_BINARY_DIR}/data" - OPENMW_TEST_SUITE_SOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}") - - if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.16 AND MSVC) - target_precompile_headers(openmw_test_suite PRIVATE - - - - - - - - - - - - - - ) - endif() +if (OPENMW_USE_SYSTEM_GOOGLETEST) + find_package(GTest 1.10 REQUIRED) + find_package(GMock 1.10 REQUIRED) +endif() + +include_directories(SYSTEM ${GTEST_INCLUDE_DIRS}) +include_directories(SYSTEM ${GMOCK_INCLUDE_DIRS}) + +file(GLOB UNITTEST_SRC_FILES + testing_util.hpp + + ../openmw/mwworld/store.cpp + ../openmw/mwworld/esmstore.cpp + mwworld/test_store.cpp + + mwdialogue/test_keywordsearch.cpp + + mwscript/test_scripts.cpp + + esm/test_fixed_string.cpp + esm/variant.cpp + + lua/test_lua.cpp + lua/test_scriptscontainer.cpp + lua/test_utilpackage.cpp + lua/test_serialization.cpp + lua/test_configuration.cpp + lua/test_l10n.cpp + lua/test_storage.cpp + + lua/test_ui_content.cpp + + misc/test_stringops.cpp + misc/test_endianness.cpp + misc/test_resourcehelpers.cpp + misc/progressreporter.cpp + misc/compression.cpp + + nifloader/testbulletnifloader.cpp + + detournavigator/navigator.cpp + detournavigator/settingsutils.cpp + detournavigator/recastmeshbuilder.cpp + detournavigator/gettilespositions.cpp + detournavigator/recastmeshobject.cpp + detournavigator/navmeshtilescache.cpp + detournavigator/tilecachedrecastmeshmanager.cpp + detournavigator/navmeshdb.cpp + detournavigator/serialization.cpp + detournavigator/asyncnavmeshupdater.cpp + + serialization/binaryreader.cpp + serialization/binarywriter.cpp + serialization/sizeaccumulator.cpp + serialization/integration.cpp + + settings/parser.cpp + settings/shadermanager.cpp + + shader/parsedefines.cpp + shader/parsefors.cpp + shader/parselinks.cpp + shader/shadermanager.cpp + + ../openmw/options.cpp + openmw/options.cpp + + sqlite3/db.cpp + sqlite3/request.cpp + sqlite3/statement.cpp + sqlite3/transaction.cpp + + esmloader/load.cpp + esmloader/esmdata.cpp + esmloader/record.cpp + + files/hash.cpp + + toutf8/toutf8.cpp + + esm4/includes.cpp + + fx/lexer.cpp + fx/technique.cpp + + esm3/readerscache.cpp +) + +source_group(apps\\openmw_test_suite FILES openmw_test_suite.cpp ${UNITTEST_SRC_FILES}) + +openmw_add_executable(openmw_test_suite openmw_test_suite.cpp ${UNITTEST_SRC_FILES}) + +target_link_libraries(openmw_test_suite GTest::GTest GMock::GMock components) +# Fix for not visible pthreads functions for linker with glibc 2.15 +if (UNIX AND NOT APPLE) + target_link_libraries(openmw_test_suite ${CMAKE_THREAD_LIBS_INIT}) +endif() + +if (BUILD_WITH_CODE_COVERAGE) + add_definitions(--coverage) + target_link_libraries(openmw_test_suite gcov) +endif() + +file(DOWNLOAD + https://gitlab.com/OpenMW/example-suite/-/raw/8966dab24692555eec720c854fb0f73d108070cd/data/template.omwgame + ${CMAKE_CURRENT_BINARY_DIR}/data/template.omwgame + EXPECTED_HASH SHA512=6e38642bcf013c5f496a9cb0bf3ec7c9553b6e86b836e7844824c5a05f556c9391167214469b6318401684b702d7569896bf743c85aee4198612b3315ba778d6 +) + +target_compile_definitions(openmw_test_suite + PRIVATE OPENMW_DATA_DIR="${CMAKE_CURRENT_BINARY_DIR}/data" + OPENMW_TEST_SUITE_SOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}") + +if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.16 AND MSVC) + target_precompile_headers(openmw_test_suite PRIVATE + + + + + + + + + + + + + + ) endif() diff --git a/apps/openmw_test_suite/esm/test_fixed_string.cpp b/apps/openmw_test_suite/esm/test_fixed_string.cpp index 1189f667e5..2a3e00d605 100644 --- a/apps/openmw_test_suite/esm/test_fixed_string.cpp +++ b/apps/openmw_test_suite/esm/test_fixed_string.cpp @@ -87,20 +87,6 @@ TEST(EsmFixedString, empty_strings) } } -TEST(EsmFixedString, struct_size) -{ - ASSERT_EQ(4, sizeof(ESM::NAME)); - ASSERT_EQ(32, sizeof(ESM::NAME32)); - ASSERT_EQ(64, sizeof(ESM::NAME64)); -} - -TEST(EsmFixedString, is_pod) -{ - ASSERT_TRUE(std::is_pod::value); - ASSERT_TRUE(std::is_pod::value); - ASSERT_TRUE(std::is_pod::value); -} - TEST(EsmFixedString, assign_should_zero_untouched_bytes_for_4) { ESM::NAME value; diff --git a/apps/openmw_test_suite/fx/lexer.cpp b/apps/openmw_test_suite/fx/lexer.cpp index 5024622a71..f5df35c982 100644 --- a/apps/openmw_test_suite/fx/lexer.cpp +++ b/apps/openmw_test_suite/fx/lexer.cpp @@ -23,7 +23,6 @@ namespace TEST_F(LexerSingleTokenTest, single_token_shared) { test(); } TEST_F(LexerSingleTokenTest, single_token_technique) { test(); } - TEST_F(LexerSingleTokenTest, single_token_main_pass) { test(); } TEST_F(LexerSingleTokenTest, single_token_render_target) { test(); } TEST_F(LexerSingleTokenTest, single_token_vertex) { test(); } TEST_F(LexerSingleTokenTest, single_token_fragment) { test(); } diff --git a/components/esm/esmcommon.hpp b/components/esm/esmcommon.hpp index 2685371ec0..35c659fb7e 100644 --- a/components/esm/esmcommon.hpp +++ b/components/esm/esmcommon.hpp @@ -8,6 +8,7 @@ #include #include #include +#include namespace ESM { @@ -171,6 +172,14 @@ using NAME = FixedString<4>; using NAME32 = FixedString<32>; using NAME64 = FixedString<64>; +static_assert(std::is_standard_layout_v && std::is_trivial_v); +static_assert(std::is_standard_layout_v && std::is_trivial_v); +static_assert(std::is_standard_layout_v && std::is_trivial_v); + +static_assert(sizeof(NAME) == 4); +static_assert(sizeof(NAME32) == 32); +static_assert(sizeof(NAME64) == 64); + /* This struct defines a file 'context' which can be saved and later restored by an ESMReader instance. It will save the position within a file, and when restored will let you read from that position as diff --git a/components/fx/lexer.cpp b/components/fx/lexer.cpp index d416dd692d..3227071d23 100644 --- a/components/fx/lexer.cpp +++ b/components/fx/lexer.cpp @@ -230,7 +230,6 @@ namespace fx if (value == "shared") return Shared{}; if (value == "technique") return Technique{}; - if (value == "main_pass") return Main_Pass{}; if (value == "render_target") return Render_Target{}; if (value == "vertex") return Vertex{}; if (value == "fragment") return Fragment{}; diff --git a/components/fx/lexer_types.hpp b/components/fx/lexer_types.hpp index 0d81c483b7..cacddff3c5 100644 --- a/components/fx/lexer_types.hpp +++ b/components/fx/lexer_types.hpp @@ -18,7 +18,6 @@ namespace fx struct Fragment { inline static constexpr std::string_view repr = "fragment"; }; struct Compute { inline static constexpr std::string_view repr = "compute"; }; struct Technique { inline static constexpr std::string_view repr = "technique"; }; - struct Main_Pass { inline static constexpr std::string_view repr = "main_pass"; }; struct Render_Target { inline static constexpr std::string_view repr = "render_target"; }; struct Sampler_1D { inline static constexpr std::string_view repr = "sampler_1d"; }; struct Sampler_2D { inline static constexpr std::string_view repr = "sampler_2d"; }; @@ -49,7 +48,7 @@ namespace fx using Token = std::variant; + True, False, Vec2, Vec3, Vec4, Eof>; } } diff --git a/components/fx/technique.cpp b/components/fx/technique.cpp index eaa74e3e3b..68f5ff3b67 100644 --- a/components/fx/technique.cpp +++ b/components/fx/technique.cpp @@ -65,7 +65,6 @@ namespace fx mPassKeys.clear(); mDefinedUniforms.clear(); mRenderTargets.clear(); - mMainTemplate = nullptr; mLastAppliedType = Pass::Type::None; mFlags = 0; mShared.clear(); @@ -262,47 +261,6 @@ namespace fx error("pass list in 'technique' block cannot be empty."); } - template<> - void Technique::parseBlockImp() - { - if (mMainTemplate) - error("duplicate 'main_pass' block"); - - if (mName != "main") - error("'main_pass' block can only be defined in the 'main.omwfx' technique file"); - - mMainTemplate = new osg::Texture2D; - - mMainTemplate->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); - mMainTemplate->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); - - while (!isNext() && !isNext()) - { - expect(); - - auto key = std::get(mToken).value; - - expect(); - - if (key == "wrap_s") - mMainTemplate->setWrap(osg::Texture::WRAP_S, parseWrapMode()); - else if (key == "wrap_t") - mMainTemplate->setWrap(osg::Texture::WRAP_T, parseWrapMode()); - // Skip depth attachments for main scene, as some engine settings rely on specific depth formats. - // Allowing this to be overriden will cause confusion. - else if (key == "internal_format") - mMainTemplate->setInternalFormat(parseInternalFormat()); - else if (key == "source_type") - mMainTemplate->setSourceType(parseSourceType()); - else if (key == "source_format") - mMainTemplate->setSourceFormat(parseSourceFormat()); - else - error(Misc::StringUtils::format("unexpected key '%s'", std::string(key))); - - expect(); - } - } - template<> void Technique::parseBlockImp() { @@ -738,8 +696,6 @@ namespace fx parseBlock(false); else if constexpr (std::is_same_v) parseBlock(false); - else if constexpr (std::is_same_v) - parseBlock(false); else if constexpr (std::is_same_v) parseBlock(); else if constexpr (std::is_same_v) diff --git a/components/fx/technique.hpp b/components/fx/technique.hpp index 1cb4e3e743..4b1c9f19bb 100644 --- a/components/fx/technique.hpp +++ b/components/fx/technique.hpp @@ -159,8 +159,6 @@ namespace fx const std::unordered_set& getGLSLExtensions() const { return mGLSLExtensions; } - osg::ref_ptr getMainTemplate() const { return mMainTemplate; } - FlagsType getFlags() const { return mFlags; } bool getHidden() const { return mFlags & Flag_Hidden; } @@ -273,7 +271,6 @@ namespace fx int mWidth; int mHeight; - osg::ref_ptr mMainTemplate; RenderTargetMap mRenderTargets; TexList mTextures; @@ -301,7 +298,6 @@ namespace fx template<> void Technique::parseBlockImp(); template<> void Technique::parseBlockImp(); - template<> void Technique::parseBlockImp(); template<> void Technique::parseBlockImp(); template<> void Technique::parseBlockImp(); template<> void Technique::parseBlockImp(); diff --git a/components/nif/controller.cpp b/components/nif/controller.cpp index 7106335668..d4ba54b840 100644 --- a/components/nif/controller.cpp +++ b/components/nif/controller.cpp @@ -40,7 +40,7 @@ namespace Nif horizontalDir = nif->getFloat(); horizontalAngle = nif->getFloat(); /*normal?*/ nif->getVector3(); - /*color?*/ nif->getVector4(); + color = nif->getVector4(); size = nif->getFloat(); startTime = nif->getFloat(); stopTime = nif->getFloat(); diff --git a/components/nif/controller.hpp b/components/nif/controller.hpp index a3df63f913..e14aba46ad 100644 --- a/components/nif/controller.hpp +++ b/components/nif/controller.hpp @@ -52,6 +52,7 @@ struct NiParticleSystemController : public Controller float horizontalDir; float horizontalAngle; + osg::Vec4f color; float size; float startTime; float stopTime; diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 474fc30c32..fab39049e0 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -1050,9 +1050,8 @@ namespace NifOsg const osg::Vec3f& position = particledata->vertices[particle.vertex]; created->setPosition(position); - osg::Vec4f partcolor (1.f,1.f,1.f,1.f); - if (particle.vertex < particledata->colors.size()) - partcolor = particledata->colors[particle.vertex]; + created->setColorRange(osgParticle::rangev4(partctrl->color, partctrl->color)); + created->setAlphaRange(osgParticle::rangef(1.f, 1.f)); float size = partctrl->size; if (particle.vertex < particledata->sizes.size()) @@ -1171,7 +1170,7 @@ namespace NifOsg handleParticleInitialState(nifNode, partsys, partctrl); 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().setColorRange(osgParticle::rangev4(partctrl->color, partctrl->color)); partsys->getDefaultParticleTemplate().setAlphaRange(osgParticle::rangef(1.f, 1.f)); if (!partctrl->emitter.empty()) diff --git a/components/sceneutil/lightmanager.cpp b/components/sceneutil/lightmanager.cpp index be3fe4679b..e24cdcf6f3 100644 --- a/components/sceneutil/lightmanager.cpp +++ b/components/sceneutil/lightmanager.cpp @@ -476,16 +476,14 @@ namespace SceneUtil { osg::ref_ptr stateset = new osg::StateSet; - osg::ref_ptr indices = new osg::IntArray(mLightManager->getMaxLights()); - osg::ref_ptr indicesUni = new osg::Uniform(osg::Uniform::Type::INT, "PointLightIndex", indices->size()); + osg::ref_ptr indicesUni = new osg::Uniform(osg::Uniform::Type::INT, "PointLightIndex", mLightManager->getMaxLights()); int pointCount = 0; for (size_t i = 0; i < lightList.size(); ++i) { int bufIndex = mLightManager->getLightIndexMap(frameNum)[lightList[i]->mLightSource->getId()]; - indices->at(pointCount++) = bufIndex; + indicesUni->setElement(pointCount++, bufIndex); } - indicesUni->setArray(indices); stateset->addUniform(indicesUni); stateset->addUniform(new osg::Uniform("PointLightCount", pointCount)); @@ -608,20 +606,32 @@ namespace SceneUtil class LightManagerCullCallback : public SceneUtil::NodeCallback { public: - void operator()(LightManager* node, osgUtil::CullVisitor* cv) + LightManagerCullCallback(LightManager* lightManager) { - osg::ref_ptr stateset = new osg::StateSet; + if (!lightManager->getUBOManager()) + return; - if (node->getLightingMethod() == LightingMethod::SingleUBO) + for (size_t i = 0; i < mStateSet.size(); ++i) { - auto buffer = node->getUBOManager()->getLightBuffer(cv->getTraversalNumber()); - + auto& buffer = lightManager->getUBOManager()->getLightBuffer(i); #if OSG_VERSION_GREATER_OR_EQUAL(3,5,7) osg::ref_ptr ubb = new osg::UniformBufferBinding(static_cast(Resource::SceneManager::UBOBinding::LightBuffer), buffer->getData(), 0, buffer->getData()->getTotalDataSize()); #else osg::ref_ptr ubb = new osg::UniformBufferBinding(static_cast(Resource::SceneManager::UBOBinding::LightBuffer), buffer->getData()->getBufferObject(), 0, buffer->getData()->getTotalDataSize()); #endif - stateset->setAttributeAndModes(ubb, osg::StateAttribute::ON); + mStateSet[i]->setAttributeAndModes(ubb, osg::StateAttribute::ON); + } + } + + void operator()(LightManager* node, osgUtil::CullVisitor* cv) + { + const size_t frameId = cv->getTraversalNumber() % 2; + + auto& stateset = mStateSet[frameId]; + + if (node->getLightingMethod() == LightingMethod::SingleUBO) + { + auto& buffer = node->getUBOManager()->getLightBuffer(cv->getTraversalNumber()); if (auto sun = node->getSunlight()) { @@ -652,6 +662,8 @@ namespace SceneUtil if (node->getPPLightsBuffer() && cv->getCurrentCamera()->getName() == Constants::SceneCamera) node->getPPLightsBuffer()->updateCount(cv->getTraversalNumber()); } + + std::array, 2> mStateSet = { new osg::StateSet, new osg::StateSet }; }; UBOManager::UBOManager(int lightCount) @@ -838,7 +850,7 @@ namespace SceneUtil getOrCreateStateSet()->addUniform(new osg::Uniform("PointLightCount", 0)); - addCullCallback(new LightManagerCullCallback); + addCullCallback(new LightManagerCullCallback(this)); } LightManager::LightManager(const LightManager ©, const osg::CopyOp ©op) diff --git a/extern/CMakeLists.txt b/extern/CMakeLists.txt index c183fe455b..0d3b5a70f8 100644 --- a/extern/CMakeLists.txt +++ b/extern/CMakeLists.txt @@ -301,3 +301,21 @@ if (NOT OPENMW_USE_SYSTEM_ICU) endforeach() set(ICU_LIBRARIES ICU::i18n ICU::uc ICU::data PARENT_SCOPE) endif() + +if (BUILD_UNITTESTS AND NOT OPENMW_USE_SYSTEM_GOOGLETEST) + cmake_minimum_required(VERSION 3.11) + + include(FetchContent) + FetchContent_Declare(googletest + URL https://github.com/google/googletest/archive/refs/tags/release-1.12.1.zip + URL_HASH SHA512=1479ea2f3172c622c0ca305f5b2bc45a42941221ec0ac7865e6d6d020ec4d008d952fc64e01a4c5138d7bed4148cf75596f25bb9e9044a98bbbf5662053ea11c + SOURCE_DIR fetched/googletest + ) + if (MSVC) + set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) + endif() + FetchContent_MakeAvailableExcludeFromAll(googletest) + + add_library(GTest::GTest ALIAS gtest) + add_library(GMock::GMock ALIAS gmock) +endif() diff --git a/files/data/CMakeLists.txt b/files/data/CMakeLists.txt index f0a1d9b260..d876918f31 100644 --- a/files/data/CMakeLists.txt +++ b/files/data/CMakeLists.txt @@ -87,8 +87,8 @@ set(BUILTIN_DATA_FILES scripts/omw/mwui/space.lua scripts/omw/mwui/init.lua - shaders/main.omwfx - shaders/displaydepth.omwfx + shaders/adjustments.omwfx + shaders/debug.omwfx mygui/core.skin mygui/core.xml diff --git a/files/data/l10n/BuiltInShaders/de.yaml b/files/data/l10n/BuiltInShaders/de.yaml index e8011a2a17..5886356782 100644 --- a/files/data/l10n/BuiltInShaders/de.yaml +++ b/files/data/l10n/BuiltInShaders/de.yaml @@ -1,3 +1,7 @@ -DisplayDepthDescription: "Visualisiert den Tiefenpuffer." +DisplayDepthName: "Visualisiert den Tiefenpuffer." DisplayDepthFactorDescription: "Bestimmt die Korrelation zwischen dem Pixeltiefenwert und seiner Ausgabefarbe. Hohe Werte führen zu einem helleren Bild." DisplayDepthFactorName: "Farbfaktor" +ContrastLevelDescription: "Kontraststufe" +ContrastLevelName: "Kontrast" +GammaLevelDescription: "Gamma-Level" +GammaLevelName: "Gamma" \ No newline at end of file diff --git a/files/data/l10n/BuiltInShaders/en.yaml b/files/data/l10n/BuiltInShaders/en.yaml index f1c6112a49..4454fdefa5 100644 --- a/files/data/l10n/BuiltInShaders/en.yaml +++ b/files/data/l10n/BuiltInShaders/en.yaml @@ -1,3 +1,12 @@ -DisplayDepthDescription: "Visualizes the depth buffer." -DisplayDepthFactorDescription: "Determines correlation between pixel depth value and its output color. High values lead to brighter image." -DisplayDepthFactorName: "Color factor" +AdjustmentsDescription: "Colour adjustments." +DebugDescription: "Debug shader." +DebugHeaderDepth: "Depth Buffer" +DebugHeaderNormals: "Normals" +DisplayDepthFactorName: "Depth colour factor" +DisplayDepthFactorDescription: "Determines correlation between pixel depth value and its output colour. High values lead to brighter image." +DisplayDepthName: "Visualize depth buffer" +DisplayNormalsName: "Visualize pass normals" +ContrastLevelDescription: "Constrast level" +ContrastLevelName: "Constrast" +GammaLevelDescription: "Gamma level" +GammaLevelName: "Gamma" \ No newline at end of file diff --git a/files/data/l10n/BuiltInShaders/ru.yaml b/files/data/l10n/BuiltInShaders/ru.yaml index 3c552ea301..46fc34b856 100644 --- a/files/data/l10n/BuiltInShaders/ru.yaml +++ b/files/data/l10n/BuiltInShaders/ru.yaml @@ -1,3 +1,12 @@ -DisplayDepthDescription: "Визуализирует буфер глубины." +AdjustmentsDescription: "Коррекция цвета." +DebugDescription: "Отладочный шейдер." +DebugHeaderDepth: "Буфер глубины" +DebugHeaderNormals: "Нормали" +DisplayDepthName: "Визуализация буфера глубины" DisplayDepthFactorDescription: "Определяет соотношение между значением глубины пикселя и его цветом. Чем выше значение, тем ярче будет изображение." DisplayDepthFactorName: "Соотношение цвета" +DisplayNormalsName: "Визуализация нормалей" +ContrastLevelDescription: "Контрастность изображения" +ContrastLevelName: "Контрастность" +GammaLevelDescription: "Яркость изображения" +GammaLevelName: "Яркость" \ No newline at end of file diff --git a/files/data/l10n/BuiltInShaders/sv.yaml b/files/data/l10n/BuiltInShaders/sv.yaml index f73ad5ea3d..b43cb34930 100644 --- a/files/data/l10n/BuiltInShaders/sv.yaml +++ b/files/data/l10n/BuiltInShaders/sv.yaml @@ -1,3 +1,7 @@ -DisplayDepthDescription: "Visualiserar djupbufferten." +DisplayDepthName: "Visualiserar djupbufferten." DisplayDepthFactorDescription: "Avgör korrelation mellan djupvärdet på pixeln och dess producerade färg. Högre värden ger ljusare bild." DisplayDepthFactorName: "Färgfaktor" +ContrastLevelDescription: "Kontrastnivå" +ContrastLevelName: "Kontrast" +GammaLevelDescription: "Gammanivå" +# GammaLevelName: "Gamma" samma som en diff --git a/files/data/l10n/PostProcessing/de.yaml b/files/data/l10n/PostProcessing/de.yaml index 4916aa7965..aef3356aa0 100644 --- a/files/data/l10n/PostProcessing/de.yaml +++ b/files/data/l10n/PostProcessing/de.yaml @@ -1,11 +1,7 @@ Abovewater: "Überwasser" Author: "Autor" Configuration: "Konfiguration" -ContrastLevelDescription: "Kontraststufe" -ContrastLevelName: "Kontrast" Description: "Beschreibung" -GammaLevelDescription: "Gamma-Level" -GammaLevelName: "Gamma" InExteriors: "Außenbereich" InInteriors: "Innenbereich" KeyboardControls: | diff --git a/files/data/l10n/PostProcessing/en.yaml b/files/data/l10n/PostProcessing/en.yaml index f40a318cca..44cb90cacf 100644 --- a/files/data/l10n/PostProcessing/en.yaml +++ b/files/data/l10n/PostProcessing/en.yaml @@ -1,11 +1,7 @@ Abovewater: "Abovewater" Author: "Author" Configuration: "Configuration" -ContrastLevelDescription: "Constrast level" -ContrastLevelName: "Constrast" Description: "Description" -GammaLevelDescription: "Gamma level" -GammaLevelName: "Gamma" InExteriors: "Exteriors" InInteriors: "Interiors" KeyboardControls: | @@ -15,7 +11,6 @@ KeyboardControls: | Shift+Left-Arrow > Deactive shader Shift+Up-Arrow > Move shader up Shift+Down-Arrow > Move shader down -MainPassDescription: "Passes scene data to post processing shaders. Can not be toggled or moved." PostProcessHUD: "Postprocess HUD" ResetShader: "Reset shader to default state" ShaderLocked: "Locked" diff --git a/files/data/l10n/PostProcessing/ru.yaml b/files/data/l10n/PostProcessing/ru.yaml index fdae0947ba..55ca595163 100644 --- a/files/data/l10n/PostProcessing/ru.yaml +++ b/files/data/l10n/PostProcessing/ru.yaml @@ -1,11 +1,7 @@ Abovewater: "Над водой" Author: "Автор" Configuration: "Настройки" -ContrastLevelDescription: "Контрастность изображения" -ContrastLevelName: "Контрастность" Description: "Описание" -GammaLevelDescription: "Яркость изображения" -GammaLevelName: "Яркость" InExteriors: "Вне помещений" InInteriors: "В помещениях" KeyboardControls: | @@ -15,7 +11,6 @@ KeyboardControls: | Shift+Left-Arrow > Выключить шейдер Shift+Up-Arrow > Передвинуть шейдер выше Shift+Down-Arrow > Передвинуть шейдер ниже -MainPassDescription: "Передает данные сцены в шейдеры постобработки. Не может быть выключен или передвинут." PostProcessHUD: "Настройки постобработки" ResetShader: "Обнулить настройки этого шейдера" ShaderLocked: "Заблокирован" diff --git a/files/data/l10n/PostProcessing/sv.yaml b/files/data/l10n/PostProcessing/sv.yaml index ed76ff8549..e3c25b72cb 100644 --- a/files/data/l10n/PostProcessing/sv.yaml +++ b/files/data/l10n/PostProcessing/sv.yaml @@ -1,11 +1,7 @@ Abovewater: "Ovan vatten" Author: "Skapare" # Author = Författare, but författare sounds very book-author-ish. Skapare, meaning "creator", sounds better in Swedish in this case. Ok? Configuration: "Konfiguration" -ContrastLevelDescription: "Kontrastnivå" -ContrastLevelName: "Kontrast" Description: "Beskrivning" -GammaLevelDescription: "Gammanivå" -# GammaLevelName: "Gamma" samma som en InExteriors: "Exteriörer" InInteriors: "Interiörer" KeyboardControls: | @@ -15,7 +11,6 @@ KeyboardControls: | Shift+Vänsterpil > Avaktivera shader Shift+Pil upp > Flytta shader upp Shift+Pil ner > Flytta shader ner -MainPassDescription: "Flyttar scendata till postprocess-shaders. Kan ej slås på/av eller flyttas." PostProcessHUD: "Postprocess HUD" ResetShader: "Återställ shader to ursprungligt läge" ShaderLocked: "Låst" diff --git a/files/data/shaders/main.omwfx b/files/data/shaders/adjustments.omwfx similarity index 53% rename from files/data/shaders/main.omwfx rename to files/data/shaders/adjustments.omwfx index d42fb6c77f..dff182d422 100644 --- a/files/data/shaders/main.omwfx +++ b/files/data/shaders/adjustments.omwfx @@ -1,18 +1,10 @@ -main_pass { - wrap_s = clamp_to_edge; - wrap_t = clamp_to_edge; - internal_format = rgba; - source_type = unsigned_int; - source_format = rgba; -} - uniform_float uGamma { default = 1.0; step = 0.01; min = 0.0; max = 5.0; - display_name = "#{PostProcessing:GammaLevelName}"; - description = "#{PostProcessing:GammaLevelDescription}"; + display_name = "#{BuiltInShaders:GammaLevelName}"; + description = "#{BuiltInShaders:GammaLevelDescription}"; } uniform_float uContrast { @@ -20,8 +12,8 @@ uniform_float uContrast { step = 0.01; min = 0.0; max = 5.0; - display_name = "#{PostProcessing:ContrastLevelName}"; - description = "#{PostProcessing:ContrastLevelDescription}"; + display_name = "#{BuiltInShaders:ContrastLevelName}"; + description = "#{BuiltInShaders:ContrastLevelDescription}"; } fragment main { @@ -39,9 +31,8 @@ fragment main { } technique { - description = "#{PostProcessing:MainPassDescription}"; + description = "#{BuiltInShaders:AdjustmentsDescription}"; version = "1.0"; author = "OpenMW"; passes = main; - hdr = false; } diff --git a/files/data/shaders/debug.omwfx b/files/data/shaders/debug.omwfx new file mode 100644 index 0000000000..addcbf6c38 --- /dev/null +++ b/files/data/shaders/debug.omwfx @@ -0,0 +1,45 @@ +uniform_bool uDisplayDepth { + header = "#{BuiltInShaders:DebugHeaderDepth}"; + default = true; + display_name = "#{BuiltInShaders:DisplayDepthName}"; +} + +uniform_float uDepthFactor { + step = 0.1; + min = 0.01; + max = 20.0; + default = 1.0; + display_name = "#{BuiltInShaders:DisplayDepthFactorName}"; + description = "#{BuiltInShaders:DisplayDepthFactorDescription}"; +} + +uniform_bool uDisplayNormals { + header = "#{BuiltInShaders:DebugHeaderNormals}"; + default = true; + display_name = "#{BuiltInShaders:DisplayNormalsName}"; +} + +fragment main { + + omw_In vec2 omw_TexCoord; + + void main() + { + omw_FragColor = omw_GetLastShader(omw_TexCoord); + + if (uDisplayDepth) + omw_FragColor = vec4(vec3(omw_GetLinearDepth(omw_TexCoord) / omw.far * uDepthFactor), 1.0); +#if OMW_NORMALS + if (uDisplayNormals && (!uDisplayDepth || omw_TexCoord.x < 0.5)) + omw_FragColor.rgb = omw_GetNormals(omw_TexCoord); +#endif + } +} + +technique { + passes = main; + description = "#{BuiltInShaders:DebugDescription}"; + author = "OpenMW"; + version = "1.0"; + pass_normals = true; +} diff --git a/files/data/shaders/displaydepth.omwfx b/files/data/shaders/displaydepth.omwfx deleted file mode 100644 index b20b5208e7..0000000000 --- a/files/data/shaders/displaydepth.omwfx +++ /dev/null @@ -1,25 +0,0 @@ -uniform_float uFactor { - step = 0.1; - min = 0.01; - max = 20.0; - default = 1.0; - display_name = "#{BuiltInShaders:DisplayDepthFactorName}"; - description = "#{BuiltInShaders:DisplayDepthFactorDescription}"; -} - -fragment main { - - omw_In vec2 omw_TexCoord; - - void main() - { - omw_FragColor = vec4(vec3(omw_GetLinearDepth(omw_TexCoord) / omw.far * uFactor), 1.0); - } -} - -technique { - passes = main; - description = "#{BuiltInShaders:DisplayDepthDescription}"; - author = "OpenMW"; - version = "1.0"; -}