Add OpenMW commits up to 20 Mar 2021

# Conflicts:
#   apps/openmw/CMakeLists.txt
#   apps/openmw/mwmechanics/aipursue.cpp
#   components/CMakeLists.txt
pull/593/head
David Cernat 4 years ago
commit 59cb31e0a7

10
.gitignore vendored

@ -85,13 +85,3 @@ moc_*.cxx
*.[ao] *.[ao]
*.so *.so
venv/ venv/
## recastnavigation unused files
extern/recastnavigation/.travis.yml
extern/recastnavigation/CONTRIBUTING.md
extern/recastnavigation/Docs/
extern/recastnavigation/Doxyfile
extern/recastnavigation/README.md
extern/recastnavigation/RecastDemo/
extern/recastnavigation/Tests/
extern/recastnavigation/appveyor.yml

@ -1,7 +1,6 @@
0.47.0 0.47.0
------ ------
Bug #832: OpenMW-CS: Handle deleted references
Bug #1662: Qt4 and Windows binaries crash if there's a non-ASCII character in a file path/config path Bug #1662: Qt4 and Windows binaries crash if there's a non-ASCII character in a file path/config path
Bug #1901: Actors colliding behaviour is different from vanilla Bug #1901: Actors colliding behaviour is different from vanilla
Bug #1952: Incorrect particle lighting Bug #1952: Incorrect particle lighting
@ -9,7 +8,7 @@
Bug #2311: Targeted scripts are not properly supported on non-unique RefIDs Bug #2311: Targeted scripts are not properly supported on non-unique RefIDs
Bug #2473: Unable to overstock merchants Bug #2473: Unable to overstock merchants
Bug #2798: Mutable ESM records Bug #2798: Mutable ESM records
Bug #2976 [reopened]: Issues combining settings from the command line and both config files Bug #2976: [reopened]: Issues combining settings from the command line and both config files
Bug #3137: Walking into a wall prevents jumping Bug #3137: Walking into a wall prevents jumping
Bug #3372: Projectiles and magic bolts go through moving targets Bug #3372: Projectiles and magic bolts go through moving targets
Bug #3676: NiParticleColorModifier isn't applied properly Bug #3676: NiParticleColorModifier isn't applied properly
@ -24,7 +23,7 @@
Bug #4201: Projectile-projectile collision Bug #4201: Projectile-projectile collision
Bug #4247: Cannot walk up stairs in Ebonheart docks Bug #4247: Cannot walk up stairs in Ebonheart docks
Bug #4357: OpenMW-CS: TopicInfos index sorting and rearranging isn't fully functional Bug #4357: OpenMW-CS: TopicInfos index sorting and rearranging isn't fully functional
Bug #4363: Editor: Defect in Clone Function for Dialogue Info records Bug #4363: OpenMW-CS: Defect in Clone Function for Dialogue Info records
Bug #4447: Actor collision capsule shape allows looking through some walls Bug #4447: Actor collision capsule shape allows looking through some walls
Bug #4465: Collision shape overlapping causes twitching Bug #4465: Collision shape overlapping causes twitching
Bug #4476: Abot Gondoliers: player hangs in air during scenic travel Bug #4476: Abot Gondoliers: player hangs in air during scenic travel
@ -34,6 +33,7 @@
Bug #4764: Data race in osg ParticleSystem Bug #4764: Data race in osg ParticleSystem
Bug #4765: Data race in ChunkManager -> Array::setBinding Bug #4765: Data race in ChunkManager -> Array::setBinding
Bug #4774: Guards are ignorant of an invisible player that tries to attack them Bug #4774: Guards are ignorant of an invisible player that tries to attack them
Bug #5026: Data races with rain intensity uniform set by sky and used by water
Bug #5101: Hostile followers travel with the player Bug #5101: Hostile followers travel with the player
Bug #5108: Savegame bloating due to inefficient fog textures format Bug #5108: Savegame bloating due to inefficient fog textures format
Bug #5165: Active spells should use real time intead of timestamps Bug #5165: Active spells should use real time intead of timestamps
@ -44,11 +44,11 @@
Bug #5367: Selecting a spell on an enchanted item per hotkey always plays the equip sound Bug #5367: Selecting a spell on an enchanted item per hotkey always plays the equip sound
Bug #5369: Spawnpoint in the Grazelands doesn't produce oversized creatures Bug #5369: Spawnpoint in the Grazelands doesn't produce oversized creatures
Bug #5370: Opening an unlocked but trapped door uses the key Bug #5370: Opening an unlocked but trapped door uses the key
Bug #5384: openmw-cs: deleting an instance requires reload of scene window to show in editor Bug #5384: OpenMW-CS: Deleting an instance requires reload of scene window to show in editor
Bug #5387: Move/MoveWorld don't update the object's cell properly Bug #5387: Move/MoveWorld don't update the object's cell properly
Bug #5391: Races Redone 1.2 bodies don't show on the inventory Bug #5391: Races Redone 1.2 bodies don't show on the inventory
Bug #5397: NPC greeting does not reset if you leave + reenter area Bug #5397: NPC greeting does not reset if you leave + reenter area
Bug #5400: Editor: Verifier checks race of non-skin bodyparts Bug #5400: OpenMW-CS: Verifier checks race of non-skin bodyparts
Bug #5403: Enchantment effect doesn't show on an enemy during death animation Bug #5403: Enchantment effect doesn't show on an enemy during death animation
Bug #5415: Environment maps in ebony cuirass and HiRez Armors Indoril cuirass don't work Bug #5415: Environment maps in ebony cuirass and HiRez Armors Indoril cuirass don't work
Bug #5416: Junk non-node records before the root node are not handled gracefully Bug #5416: Junk non-node records before the root node are not handled gracefully
@ -70,6 +70,7 @@
Bug #5499: Faction advance is available when requirements not met Bug #5499: Faction advance is available when requirements not met
Bug #5502: Dead zone for analogue stick movement is too small Bug #5502: Dead zone for analogue stick movement is too small
Bug #5507: Sound volume is not clamped on ingame settings update Bug #5507: Sound volume is not clamped on ingame settings update
Bug #5525: Case-insensitive search in the inventory window does not work with non-ASCII characters
Bug #5531: Actors flee using current rotation by axis x Bug #5531: Actors flee using current rotation by axis x
Bug #5539: Window resize breaks when going from a lower resolution to full screen resolution Bug #5539: Window resize breaks when going from a lower resolution to full screen resolution
Bug #5548: Certain exhausted topics can be highlighted again even though there's no new dialogue Bug #5548: Certain exhausted topics can be highlighted again even though there's no new dialogue
@ -93,26 +94,35 @@
Bug #5703: OpenMW-CS menu system crashing on XFCE Bug #5703: OpenMW-CS menu system crashing on XFCE
Bug #5706: AI sequences stop looping after the saved game is reloaded Bug #5706: AI sequences stop looping after the saved game is reloaded
Bug #5713: OpenMW-CS: Collada models are corrupted in Qt-based scene view Bug #5713: OpenMW-CS: Collada models are corrupted in Qt-based scene view
Bug #5731: Editor: skirts are invisible on characters Bug #5731: OpenMW-CS: skirts are invisible on characters
Bug #5739: Saving and loading the save a second or two before hitting the ground doesn't count fall damage Bug #5739: Saving and loading the save a second or two before hitting the ground doesn't count fall damage
Bug #5758: Paralyzed actors behavior is inconsistent with vanilla Bug #5758: Paralyzed actors behavior is inconsistent with vanilla
Bug #5762: Movement solver is insufficiently robust Bug #5762: Movement solver is insufficiently robust
Bug #5807: Video decoding crash on ARM
Bug #5821: NPCs from mods getting removed if mod order was changed Bug #5821: NPCs from mods getting removed if mod order was changed
Bug #5835: OpenMW doesn't accept negative values for NPC's hello, alarm, fight, and flee Bug #5835: OpenMW doesn't accept negative values for NPC's hello, alarm, fight, and flee
Bug #5836: OpenMW dialogue/greeting/voice filter doesn't accept negative Ai values for NPC's hello, alarm, fight, and flee Bug #5836: OpenMW dialogue/greeting/voice filter doesn't accept negative Ai values for NPC's hello, alarm, fight, and flee
Bug #5838: Local map and other menus become blank in some locations while playing Wizards' Islands mod. Bug #5838: Local map and other menus become blank in some locations while playing Wizards' Islands mod.
Bug #5840: GetSoundPlaying "Health Damage" doesn't play when NPC hits target with shield effect ( vanilla engine behavior ) Bug #5840: GetSoundPlaying "Health Damage" doesn't play when NPC hits target with shield effect ( vanilla engine behavior )
Bug #5841: Can't Cast Zero Cost Spells When Magicka is < 0 Bug #5841: Can't Cast Zero Cost Spells When Magicka is < 0
Bug #5869: Guards can initiate arrest dialogue behind locked doors
Bug #5871: The console appears if you type the Russian letter "Ё" in the name of the enchantment
Bug #5877: Effects appearing with empty icon
Bug #5899: Visible modal windows and dropdowns crashing game on exit
Bug #5902: NiZBufferProperty is unable to disable the depth test
Bug #5906: Sunglare doesn't work with Mesa drivers and AMD GPUs
Feature #390: 3rd person look "over the shoulder" Feature #390: 3rd person look "over the shoulder"
Feature #832: OpenMW-CS: Handle deleted references
Feature #1536: Show more information about level on menu Feature #1536: Show more information about level on menu
Feature #2386: Distant Statics in the form of Object Paging Feature #2386: Distant Statics in the form of Object Paging
Feature #2404: Levelled List can not be placed into a container Feature #2404: Levelled List can not be placed into a container
Feature #2686: Timestamps in openmw.log Feature #2686: Timestamps in openmw.log
Feature #3171: OpenMW-CS: Instance drag selection Feature #3171: OpenMW-CS: Instance drag selection
Feature #4894: Consider actors as obstacles for pathfinding Feature #4894: Consider actors as obstacles for pathfinding
Feature #4899: Alpha-To-Coverage Anti-Aliasing for alpha testing
Feature #4977: Use the "default icon.tga" when an item's icon is not found Feature #4977: Use the "default icon.tga" when an item's icon is not found
Feature #5043: Head Bobbing Feature #5043: Head Bobbing
Feature #5199: Improve Scene Colors Feature #5199: OpenMW-CS: Improve scene view colors
Feature #5297: Add a search function to the "Datafiles" tab of the OpenMW launcher Feature #5297: Add a search function to the "Datafiles" tab of the OpenMW launcher
Feature #5362: Show the soul gems' trapped soul in count dialog Feature #5362: Show the soul gems' trapped soul in count dialog
Feature #5445: Handle NiLines Feature #5445: Handle NiLines
@ -121,7 +131,6 @@
Feature #5486: Fixes trainers to choose their training skills based on their base skill points Feature #5486: Fixes trainers to choose their training skills based on their base skill points
Feature #5519: Code Patch tab in launcher Feature #5519: Code Patch tab in launcher
Feature #5524: Resume failed script execution after reload Feature #5524: Resume failed script execution after reload
Feature #5525: Search fields tweaks (utf-8)
Feature #5545: Option to allow stealing from an unconscious NPC during combat Feature #5545: Option to allow stealing from an unconscious NPC during combat
Feature #5563: Run physics update in background thread Feature #5563: Run physics update in background thread
Feature #5579: MCP SetAngle enhancement Feature #5579: MCP SetAngle enhancement

@ -1,13 +1,19 @@
#!/bin/sh -e #!/bin/sh -ex
# workaround python issue on travis # workaround python issue on travis
HOMEBREW_NO_AUTO_UPDATE=1 brew uninstall --ignore-dependencies python@3.8 || true HOMEBREW_NO_AUTO_UPDATE=1 brew uninstall --ignore-dependencies python@3.8 || true
HOMEBREW_NO_AUTO_UPDATE=1 brew uninstall --ignore-dependencies python@3.9 || true HOMEBREW_NO_AUTO_UPDATE=1 brew uninstall --ignore-dependencies python@3.9 || true
HOMEBREW_NO_AUTO_UPDATE=1 brew uninstall --ignore-dependencies qt@6 || true
# Some of these tools can come from places other than brew, so check before installing # Some of these tools can come from places other than brew, so check before installing
command -v ccache >/dev/null 2>&1 || brew install ccache command -v ccache >/dev/null 2>&1 || brew install ccache
command -v cmake >/dev/null 2>&1 || brew install cmake command -v cmake >/dev/null 2>&1 || brew install cmake
command -v qmake >/dev/null 2>&1 || brew install qt command -v qmake >/dev/null 2>&1 || brew install qt@5
export PATH="/usr/local/opt/qt@5/bin:$PATH" # needed to use qmake in none default path as qt now points to qt6
ccache --version
cmake --version
qmake --version
curl -fSL -R -J https://downloads.openmw.org/osx/dependencies/openmw-deps-f8918dd.zip -o ~/openmw-deps.zip curl -fSL -R -J https://downloads.openmw.org/osx/dependencies/openmw-deps-f8918dd.zip -o ~/openmw-deps.zip
unzip -o ~/openmw-deps.zip -d /private/tmp/openmw-deps > /dev/null unzip -o ~/openmw-deps.zip -d /private/tmp/openmw-deps > /dev/null

@ -325,6 +325,16 @@ add_qt_platform_dlls() {
QT_PLATFORMS[$CONFIG]="${QT_PLATFORMS[$CONFIG]} $@" QT_PLATFORMS[$CONFIG]="${QT_PLATFORMS[$CONFIG]} $@"
} }
declare -A QT_STYLES
QT_STYLES["Release"]=""
QT_STYLES["Debug"]=""
QT_STYLES["RelWithDebInfo"]=""
add_qt_style_dlls() {
local CONFIG=$1
shift
QT_STYLES[$CONFIG]="${QT_STYLES[$CONFIG]} $@"
}
if [ -z $PLATFORM ]; then if [ -z $PLATFORM ]; then
PLATFORM="$(uname -m)" PLATFORM="$(uname -m)"
fi fi
@ -868,6 +878,7 @@ fi
fi fi
add_runtime_dlls $CONFIGURATION "$(pwd)/bin/Qt5"{Core,Gui,Network,OpenGL,Widgets}${DLLSUFFIX}.dll add_runtime_dlls $CONFIGURATION "$(pwd)/bin/Qt5"{Core,Gui,Network,OpenGL,Widgets}${DLLSUFFIX}.dll
add_qt_platform_dlls $CONFIGURATION "$(pwd)/plugins/platforms/qwindows${DLLSUFFIX}.dll" add_qt_platform_dlls $CONFIGURATION "$(pwd)/plugins/platforms/qwindows${DLLSUFFIX}.dll"
add_qt_style_dlls $CONFIGURATION "$(pwd)/plugins/styles/qwindowsvistastyle${DLLSUFFIX}.dll"
done done
echo Done. echo Done.
else else
@ -883,6 +894,7 @@ fi
DIR=$(windowsPathAsUnix "${QT_SDK}") DIR=$(windowsPathAsUnix "${QT_SDK}")
add_runtime_dlls $CONFIGURATION "${DIR}/bin/Qt5"{Core,Gui,Network,OpenGL,Widgets}${DLLSUFFIX}.dll add_runtime_dlls $CONFIGURATION "${DIR}/bin/Qt5"{Core,Gui,Network,OpenGL,Widgets}${DLLSUFFIX}.dll
add_qt_platform_dlls $CONFIGURATION "${DIR}/plugins/platforms/qwindows${DLLSUFFIX}.dll" add_qt_platform_dlls $CONFIGURATION "${DIR}/plugins/platforms/qwindows${DLLSUFFIX}.dll"
add_qt_style_dlls $CONFIGURATION "${DIR}/plugins/styles/qwindowsvistastyle${DLLSUFFIX}.dll"
done done
echo Done. echo Done.
fi fi
@ -1060,6 +1072,13 @@ fi
cp "$DLL" "${DLL_PREFIX}platforms" cp "$DLL" "${DLL_PREFIX}platforms"
done done
echo echo
echo "- Qt Style DLLs..."
mkdir -p ${DLL_PREFIX}styles
for DLL in ${QT_STYLES[$CONFIGURATION]}; do
echo " $(basename $DLL)"
cp "$DLL" "${DLL_PREFIX}styles"
done
echo
done done
#fi #fi
@ -1067,7 +1086,13 @@ if [ -n "$ACTIVATE_MSVC" ]; then
echo -n "- Activating MSVC in the current shell... " 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; } command -v vswhere >/dev/null 2>&1 || { echo "Error: vswhere is not on the path."; wrappedExit 1; }
MSVC_INSTALLATION_PATH=$(vswhere -products '*' -version "[$MSVC_REAL_VER,$(awk "BEGIN { print $MSVC_REAL_VER + 1; exit }"))" -property installationPath) # There are so many arguments now that I'm going to document them:
# * products: allow Visual Studio or standalone build tools
# * version: obvious. Awk helps make a version range by adding one.
# * property installationPath: only give the installation path.
# * latest: return only one result if several candidates exist. Prefer the last installed/updated
# * requires: make sure it's got the MSVC compiler instead of, for example, just the .NET compiler. The .x86.x64 suffix means it's for either, not that it's the x64 on x86 cross compiler as you always get both
MSVC_INSTALLATION_PATH=$(vswhere -products '*' -version "[$MSVC_REAL_VER,$(awk "BEGIN { print $MSVC_REAL_VER + 1; exit }"))" -property installationPath -latest -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64)
if [ -z "$MSVC_INSTALLATION_PATH" ]; then if [ -z "$MSVC_INSTALLATION_PATH" ]; then
echo "vswhere was unable to find MSVC $MSVC_DISPLAY_YEAR" echo "vswhere was unable to find MSVC $MSVC_DISPLAY_YEAR"
wrappedExit 1 wrappedExit 1

@ -4,7 +4,7 @@ export CXX=clang++
export CC=clang export CC=clang
DEPENDENCIES_ROOT="/private/tmp/openmw-deps/openmw-deps" DEPENDENCIES_ROOT="/private/tmp/openmw-deps/openmw-deps"
QT_PATH=$(brew --prefix qt) QT_PATH=$(brew --prefix qt@5)
CCACHE_EXECUTABLE=$(brew --prefix ccache)/bin/ccache CCACHE_EXECUTABLE=$(brew --prefix ccache)/bin/ccache
mkdir build mkdir build
cd build cd build

@ -23,6 +23,7 @@ declare -rA GROUPED_DEPS=(
libsdl2-dev libqt5opengl5-dev libopenal-dev libunshield-dev libtinyxml-dev libsdl2-dev libqt5opengl5-dev libopenal-dev libunshield-dev libtinyxml-dev
libbullet-dev liblz4-dev libpng-dev libjpeg-dev libbullet-dev liblz4-dev libpng-dev libjpeg-dev
" "
# TODO: add librecastnavigation-dev when debian is ready
# These dependencies can alternatively be built and linked statically. # These dependencies can alternatively be built and linked statically.
[openmw-deps-dynamic]="libmygui-dev libopenscenegraph-dev" [openmw-deps-dynamic]="libmygui-dev libopenscenegraph-dev"

@ -16,6 +16,11 @@ endif()
# Detect OS # Detect OS
include(cmake/OSIdentity.cmake) include(cmake/OSIdentity.cmake)
option(OPENMW_GL4ES_MANUAL_INIT "Manually initialize gl4es. This is more reliable on platforms without a windowing system. Requires gl4es to be configured with -DNOEGL=ON -DNO_LOADER=ON -DNO_INIT_CONSTRUCTOR=ON." OFF)
if(OPENMW_GL4ES_MANUAL_INIT)
add_definitions(-DOPENMW_GL4ES_MANUAL_INIT)
endif()
# Apps and tools # Apps and tools
option(BUILD_OPENMW "Build OpenMW" ON) option(BUILD_OPENMW "Build OpenMW" ON)
option(BUILD_LAUNCHER "Build Launcher" ON) option(BUILD_LAUNCHER "Build Launcher" ON)
@ -113,6 +118,12 @@ option(SDL2_STATIC "Link static build of SDL into the binaries" FALSE)
option(QT_STATIC "Link static build of QT into the binaries" FALSE) option(QT_STATIC "Link static build of QT into the binaries" FALSE)
option(OPENMW_USE_SYSTEM_BULLET "Use system provided bullet physics library" ON) option(OPENMW_USE_SYSTEM_BULLET "Use system provided bullet physics library" ON)
if(OPENMW_USE_SYSTEM_BULLET)
set(_bullet_static_default OFF)
else()
set(_bullet_static_default ON)
endif()
option(BULLET_STATIC "Link static build of Bullet into the binaries" ${_bullet_static_default})
option(OPENMW_USE_SYSTEM_OSG "Use system provided OpenSceneGraph libraries" ON) option(OPENMW_USE_SYSTEM_OSG "Use system provided OpenSceneGraph libraries" ON)
if(OPENMW_USE_SYSTEM_OSG) if(OPENMW_USE_SYSTEM_OSG)
@ -133,6 +144,7 @@ option(MYGUI_STATIC "Link static build of Mygui into the binaries" ${_mygui_stat
option(OPENMW_USE_SYSTEM_RECASTNAVIGATION "Use system provided recastnavigation library" OFF) option(OPENMW_USE_SYSTEM_RECASTNAVIGATION "Use system provided recastnavigation library" OFF)
if(OPENMW_USE_SYSTEM_RECASTNAVIGATION) if(OPENMW_USE_SYSTEM_RECASTNAVIGATION)
set(_recastnavigation_static_default OFF) set(_recastnavigation_static_default OFF)
find_package(RecastNavigation REQUIRED)
else() else()
set(_recastnavigation_static_default ON) set(_recastnavigation_static_default ON)
endif() endif()
@ -369,7 +381,9 @@ set(BOOST_COMPONENTS system filesystem program_options iostreams)
if(WIN32) if(WIN32)
set(BOOST_COMPONENTS ${BOOST_COMPONENTS} locale) set(BOOST_COMPONENTS ${BOOST_COMPONENTS} locale)
if(MSVC) if(MSVC)
set(BOOST_COMPONENTS ${BOOST_COMPONENTS} zlib) # boost-zlib is not present (nor needed) in vcpkg version of boost.
# there, it is part of boost-iostreams instead.
set(BOOST_OPTIONAL_COMPONENTS zlib)
endif(MSVC) endif(MSVC)
endif(WIN32) endif(WIN32)
@ -379,7 +393,7 @@ endif()
set(Boost_NO_BOOST_CMAKE ON) set(Boost_NO_BOOST_CMAKE ON)
find_package(Boost 1.6.2 REQUIRED COMPONENTS ${BOOST_COMPONENTS}) find_package(Boost 1.6.2 REQUIRED COMPONENTS ${BOOST_COMPONENTS} OPTIONAL_COMPONENTS ${BOOST_OPTIONAL_COMPONENTS})
if(OPENMW_USE_SYSTEM_MYGUI) if(OPENMW_USE_SYSTEM_MYGUI)
find_package(MyGUI 3.2.2 REQUIRED) find_package(MyGUI 3.2.2 REQUIRED)
endif() endif()

@ -215,12 +215,16 @@ if(APPLE)
endif(APPLE) endif(APPLE)
target_link_libraries(openmw-cs target_link_libraries(openmw-cs
${OSG_LIBRARIES} # CMake's built-in OSG finder does not use pkgconfig, so we have to
${OSGTEXT_LIBRARIES} # manually ensure the order is correct for inter-library dependencies.
${OSGUTIL_LIBRARIES} # This only makes a difference with `-DOPENMW_USE_SYSTEM_OSG=ON -DOSG_STATIC=ON`.
# https://gitlab.kitware.com/cmake/cmake/-/issues/21701
${OSGVIEWER_LIBRARIES} ${OSGVIEWER_LIBRARIES}
${OSGGA_LIBRARIES}
${OSGFX_LIBRARIES} ${OSGFX_LIBRARIES}
${OSGGA_LIBRARIES}
${OSGUTIL_LIBRARIES}
${OSGTEXT_LIBRARIES}
${OSG_LIBRARIES}
${EXTERN_OSGQT_LIBRARY} ${EXTERN_OSGQT_LIBRARY}
${Boost_SYSTEM_LIBRARY} ${Boost_SYSTEM_LIBRARY}
${Boost_FILESYSTEM_LIBRARY} ${Boost_FILESYSTEM_LIBRARY}
@ -233,13 +237,24 @@ if(OSG_STATIC)
add_library(openmw_cs_osg_plugins INTERFACE) add_library(openmw_cs_osg_plugins INTERFACE)
foreach(_plugin ${USED_OSG_PLUGINS}) foreach(_plugin ${USED_OSG_PLUGINS})
string(TOUPPER ${_plugin} _plugin_uc) string(TOUPPER ${_plugin} _plugin_uc)
list(APPEND _osg_plugins_static_files $<TARGET_FILE:${${_plugin_uc}_LIBRARY}>) if(OPENMW_USE_SYSTEM_OSG)
target_link_libraries(openmw_cs_osg_plugins INTERFACE ${${_plugin_uc}_LIBRARY}) list(APPEND _osg_plugins_static_files ${${_plugin_uc}_LIBRARY})
else()
list(APPEND _osg_plugins_static_files $<TARGET_FILE:${${_plugin_uc}_LIBRARY}>)
target_link_libraries(openmw_cs_osg_plugins INTERFACE $<TARGET_PROPERTY:${${_plugin_uc}_LIBRARY},LINK_LIBRARIES>)
add_dependencies(openmw_cs_osg_plugins ${${_plugin_uc}_LIBRARY})
endif()
endforeach() endforeach()
# We use --whole-archive because OSG plugins use registration. # We use --whole-archive because OSG plugins use registration.
get_whole_archive_options(_opts ${_osg_plugins_static_files}) get_whole_archive_options(_opts ${_osg_plugins_static_files})
target_link_options(openmw_cs_osg_plugins INTERFACE ${_opts}) target_link_options(openmw_cs_osg_plugins INTERFACE ${_opts})
target_link_libraries(openmw-cs openmw_cs_osg_plugins) target_link_libraries(openmw-cs openmw_cs_osg_plugins)
if(OPENMW_USE_SYSTEM_OSG)
# OSG plugin pkgconfig files are missing these dependencies.
# https://github.com/openscenegraph/OpenSceneGraph/issues/1052
target_link_libraries(openmw freetype jpeg png)
endif()
endif(OSG_STATIC) endif(OSG_STATIC)
target_link_libraries(openmw-cs Qt5::Widgets Qt5::Core Qt5::Network Qt5::OpenGL) target_link_libraries(openmw-cs Qt5::Widgets Qt5::Core Qt5::Network Qt5::OpenGL)

@ -169,13 +169,18 @@ include_directories(
) )
target_link_libraries(tes3mp target_link_libraries(tes3mp
${OSG_LIBRARIES} # CMake's built-in OSG finder does not use pkgconfig, so we have to
# manually ensure the order is correct for inter-library dependencies.
# This only makes a difference with `-DOPENMW_USE_SYSTEM_OSG=ON -DOSG_STATIC=ON`.
# https://gitlab.kitware.com/cmake/cmake/-/issues/21701
${OSGPARTICLE_LIBRARIES} ${OSGPARTICLE_LIBRARIES}
${OSGUTIL_LIBRARIES}
${OSGDB_LIBRARIES}
${OSGVIEWER_LIBRARIES} ${OSGVIEWER_LIBRARIES}
${OSGGA_LIBRARIES} ${OSGGA_LIBRARIES}
${OSGSHADOW_LIBRARIES} ${OSGSHADOW_LIBRARIES}
${OSGDB_LIBRARIES}
${OSGUTIL_LIBRARIES}
${OSG_LIBRARIES}
${Boost_SYSTEM_LIBRARY} ${Boost_SYSTEM_LIBRARY}
${Boost_THREAD_LIBRARY} ${Boost_THREAD_LIBRARY}
${Boost_FILESYSTEM_LIBRARY} ${Boost_FILESYSTEM_LIBRARY}
@ -196,13 +201,24 @@ if(OSG_STATIC)
add_library(openmw_osg_plugins INTERFACE) add_library(openmw_osg_plugins INTERFACE)
foreach(_plugin ${USED_OSG_PLUGINS}) foreach(_plugin ${USED_OSG_PLUGINS})
string(TOUPPER ${_plugin} _plugin_uc) string(TOUPPER ${_plugin} _plugin_uc)
list(APPEND _osg_plugins_static_files $<TARGET_FILE:${${_plugin_uc}_LIBRARY}>) if(OPENMW_USE_SYSTEM_OSG)
target_link_libraries(openmw_osg_plugins INTERFACE ${${_plugin_uc}_LIBRARY}) list(APPEND _osg_plugins_static_files ${${_plugin_uc}_LIBRARY})
else()
list(APPEND _osg_plugins_static_files $<TARGET_FILE:${${_plugin_uc}_LIBRARY}>)
target_link_libraries(openmw_osg_plugins INTERFACE $<TARGET_PROPERTY:${${_plugin_uc}_LIBRARY},LINK_LIBRARIES>)
add_dependencies(openmw_osg_plugins ${${_plugin_uc}_LIBRARY})
endif()
endforeach() endforeach()
# We use --whole-archive because OSG plugins use registration. # We use --whole-archive because OSG plugins use registration.
get_whole_archive_options(_opts ${_osg_plugins_static_files}) get_whole_archive_options(_opts ${_osg_plugins_static_files})
target_link_options(openmw_osg_plugins INTERFACE ${_opts}) target_link_options(openmw_osg_plugins INTERFACE ${_opts})
target_link_libraries(tes3mp openmw_osg_plugins) target_link_libraries(tes3mp openmw_osg_plugins)
if(OPENMW_USE_SYSTEM_OSG)
# OSG plugin pkgconfig files are missing these dependencies.
# https://github.com/openscenegraph/OpenSceneGraph/issues/1052
target_link_libraries(tes3mp freetype jpeg png)
endif()
endif(OSG_STATIC) endif(OSG_STATIC)
if (ANDROID) if (ANDROID)

@ -733,6 +733,7 @@ namespace MWBase
virtual void launchMagicBolt (const std::string& spellId, const MWWorld::Ptr& caster, const osg::Vec3f& fallbackDirection) = 0; virtual void launchMagicBolt (const std::string& spellId, const MWWorld::Ptr& caster, const osg::Vec3f& fallbackDirection) = 0;
virtual void launchProjectile (MWWorld::Ptr& actor, MWWorld::Ptr& projectile, virtual void launchProjectile (MWWorld::Ptr& actor, MWWorld::Ptr& projectile,
const osg::Vec3f& worldPos, const osg::Quat& orient, MWWorld::Ptr& bow, float speed, float attackStrength) = 0; const osg::Vec3f& worldPos, const osg::Quat& orient, MWWorld::Ptr& bow, float speed, float attackStrength) = 0;
virtual void updateProjectilesCasters() = 0;
virtual void applyLoopingParticles(const MWWorld::Ptr& ptr) = 0; virtual void applyLoopingParticles(const MWWorld::Ptr& ptr) = 0;

@ -53,7 +53,7 @@ namespace MWGui
{ {
static const int fontHeight = MWBase::Environment::get().getWindowManager()->getFontHeight(); static const int fontHeight = MWBase::Environment::get().getWindowManager()->getFontHeight();
MyGUI::GlyphInfo* gi = font->getGlyphInfo(ch); const MyGUI::GlyphInfo* gi = font->getGlyphInfo(ch);
if (gi) if (gi)
{ {
const float scale = font->getDefaultHeight() / (float) fontHeight; const float scale = font->getDefaultHeight() / (float) fontHeight;

@ -293,7 +293,7 @@ namespace MWGui
size_t length = mCommandLine->getTextCursor() - max; size_t length = mCommandLine->getTextCursor() - max;
if(length > 0) if(length > 0)
{ {
std::string text = caption; auto text = caption;
text.erase(max, length); text.erase(max, length);
mCommandLine->setCaption(text); mCommandLine->setCaption(text);
mCommandLine->setTextCursor(max); mCommandLine->setTextCursor(max);
@ -303,7 +303,7 @@ namespace MWGui
{ {
if(mCommandLine->getTextCursor() > 0) if(mCommandLine->getTextCursor() > 0)
{ {
std::string text = mCommandLine->getCaption(); auto text = mCommandLine->getCaption();
text.erase(0, mCommandLine->getTextCursor()); text.erase(0, mCommandLine->getTextCursor());
mCommandLine->setCaption(text); mCommandLine->setCaption(text);
mCommandLine->setTextCursor(0); mCommandLine->setTextCursor(0);

@ -35,6 +35,7 @@ namespace MWGui
void Layout::shutdown() void Layout::shutdown()
{ {
setVisible(false);
MyGUI::Gui::getInstance().destroyWidget(mMainWidget); MyGUI::Gui::getInstance().destroyWidget(mMainWidget);
mListWindowRoot.clear(); mListWindowRoot.clear();
} }

@ -181,7 +181,9 @@ namespace MWGui
} }
else if (mWidgetMap.find(effectId) != mWidgetMap.end()) else if (mWidgetMap.find(effectId) != mWidgetMap.end())
{ {
mWidgetMap[effectId]->setVisible(false); MyGUI::ImageBox* image = mWidgetMap[effectId];
image->setVisible(false);
image->setAlpha(1.f);
} }
} }

@ -63,6 +63,8 @@ namespace MWGui
void watchActor(const MWWorld::Ptr& ptr); void watchActor(const MWWorld::Ptr& ptr);
MWWorld::Ptr getWatchedActor() const { return mWatched; } MWWorld::Ptr getWatchedActor() const { return mWatched; }
void forceUpdate() { mWatchedStatsEmpty = true; }
}; };
} }

@ -184,7 +184,7 @@ namespace MWGui
, mWerewolfOverlayEnabled(Settings::Manager::getBool ("werewolf overlay", "GUI")) , mWerewolfOverlayEnabled(Settings::Manager::getBool ("werewolf overlay", "GUI"))
, mHudEnabled(true) , mHudEnabled(true)
, mCursorVisible(true) , mCursorVisible(true)
, mCursorActive(false) , mCursorActive(true)
, mPlayerBounty(-1) , mPlayerBounty(-1)
, mGui(nullptr) , mGui(nullptr)
, mGuiModes() , mGuiModes()
@ -201,7 +201,7 @@ namespace MWGui
{ {
float uiScale = Settings::Manager::getFloat("scaling factor", "GUI"); float uiScale = Settings::Manager::getFloat("scaling factor", "GUI");
mGuiPlatform = new osgMyGUI::Platform(viewer, guiRoot, resourceSystem->getImageManager(), uiScale); mGuiPlatform = new osgMyGUI::Platform(viewer, guiRoot, resourceSystem->getImageManager(), uiScale);
mGuiPlatform->initialise(resourcePath, logpath); mGuiPlatform->initialise(resourcePath, (boost::filesystem::path(logpath) / "MyGUI.log").generic_string());
mGui = new MyGUI::Gui; mGui = new MyGUI::Gui;
mGui->initialise(""); mGui->initialise("");
@ -516,6 +516,8 @@ namespace MWGui
} }
else else
allow(GW_ALL); allow(GW_ALL);
mStatsWatcher->forceUpdate();
} }
WindowManager::~WindowManager() WindowManager::~WindowManager()
@ -524,8 +526,6 @@ namespace MWGui
{ {
mStatsWatcher.reset(); mStatsWatcher.reset();
mKeyboardNavigation.reset();
MyGUI::LanguageManager::getInstance().eventRequestTag.clear(); MyGUI::LanguageManager::getInstance().eventRequestTag.clear();
MyGUI::PointerManager::getInstance().eventChangeMousePointer.clear(); MyGUI::PointerManager::getInstance().eventChangeMousePointer.clear();
MyGUI::InputManager::getInstance().eventChangeKeyFocus.clear(); MyGUI::InputManager::getInstance().eventChangeKeyFocus.clear();
@ -544,6 +544,8 @@ namespace MWGui
delete mCursorManager; delete mCursorManager;
delete mToolTips; delete mToolTips;
mKeyboardNavigation.reset();
cleanupGarbage(); cleanupGarbage();
mFontLoader.reset(); mFontLoader.reset();

@ -39,12 +39,14 @@ namespace MWInput
&& MWBase::Environment::get().getWindowManager()->isConsoleMode()) && MWBase::Environment::get().getWindowManager()->isConsoleMode())
SDL_StopTextInput(); SDL_StopTextInput();
bool consumed = false; bool consumed = SDL_IsTextInputActive() && // Little trick to check if key is printable
(!(SDLK_SCANCODE_MASK & arg.keysym.sym) &&
(std::isprint(arg.keysym.sym) ||
// Don't trust isprint for symbols outside the extended ASCII range
(kc == MyGUI::KeyCode::None && arg.keysym.sym > 0xff)));
if (kc != MyGUI::KeyCode::None && !mBindingsManager->isDetectingBindingState()) if (kc != MyGUI::KeyCode::None && !mBindingsManager->isDetectingBindingState())
{ {
consumed = MWBase::Environment::get().getWindowManager()->injectKeyPress(kc, 0, arg.repeat); if (MWBase::Environment::get().getWindowManager()->injectKeyPress(kc, 0, arg.repeat))
if (SDL_IsTextInputActive() && // Little trick to check if key is printable
(!(SDLK_SCANCODE_MASK & arg.keysym.sym) && std::isprint(arg.keysym.sym)))
consumed = true; consumed = true;
mBindingsManager->setPlayerControlsEnabled(!consumed); mBindingsManager->setPlayerControlsEnabled(!consumed);
} }

@ -8,6 +8,7 @@
#include <components/misc/mathutil.hpp> #include <components/misc/mathutil.hpp>
#include <components/sceneutil/positionattitudetransform.hpp> #include <components/sceneutil/positionattitudetransform.hpp>
#include <components/detournavigator/navigator.hpp>
/* /*
Start of tes3mp addition Start of tes3mp addition
@ -142,10 +143,11 @@ namespace MWMechanics
{ {
//Update every frame. UpdateLOS uses a timer, so the LOS check does not happen every frame. //Update every frame. UpdateLOS uses a timer, so the LOS check does not happen every frame.
updateLOS(actor, target, duration, storage); updateLOS(actor, target, duration, storage);
float targetReachedTolerance = 0.0f; const float targetReachedTolerance = storage.mLOS && !storage.mUseCustomDestination
if (storage.mLOS) ? storage.mAttackRange : 0.0f;
targetReachedTolerance = storage.mAttackRange; const osg::Vec3f destination = storage.mUseCustomDestination
const bool is_target_reached = pathTo(actor, target.getRefData().getPosition().asVec3(), duration, targetReachedTolerance); ? storage.mCustomDestination : target.getRefData().getPosition().asVec3();
const bool is_target_reached = pathTo(actor, destination, duration, targetReachedTolerance);
if (is_target_reached) storage.mReadyToAttack = true; if (is_target_reached) storage.mReadyToAttack = true;
} }
@ -300,8 +302,8 @@ namespace MWMechanics
const ESM::Weapon* weapon = currentAction->getWeapon(); const ESM::Weapon* weapon = currentAction->getWeapon();
ESM::Position pos = actor.getRefData().getPosition(); ESM::Position pos = actor.getRefData().getPosition();
osg::Vec3f vActorPos(pos.asVec3()); const osg::Vec3f vActorPos(pos.asVec3());
osg::Vec3f vTargetPos(target.getRefData().getPosition().asVec3()); const osg::Vec3f vTargetPos(target.getRefData().getPosition().asVec3());
osg::Vec3f vAimDir = MWBase::Environment::get().getWorld()->aimToTarget(actor, target); osg::Vec3f vAimDir = MWBase::Environment::get().getWorld()->aimToTarget(actor, target);
float distToTarget = MWBase::Environment::get().getWorld()->getHitDistance(actor, target); float distToTarget = MWBase::Environment::get().getWorld()->getHitDistance(actor, target);
@ -311,9 +313,7 @@ namespace MWMechanics
if (isRangedCombat) if (isRangedCombat)
{ {
// rotate actor taking into account target movement direction and projectile speed // rotate actor taking into account target movement direction and projectile speed
osg::Vec3f& lastTargetPos = storage.mLastTargetPos; vAimDir = AimDirToMovingTarget(actor, target, storage.mLastTargetPos, AI_REACTION_TIME, (weapon ? weapon->mData.mType : 0), storage.mStrength);
vAimDir = AimDirToMovingTarget(actor, target, lastTargetPos, AI_REACTION_TIME, (weapon ? weapon->mData.mType : 0), storage.mStrength);
lastTargetPos = vTargetPos;
storage.mMovement.mRotation[0] = getXAngleToDir(vAimDir); storage.mMovement.mRotation[0] = getXAngleToDir(vAimDir);
storage.mMovement.mRotation[2] = getZAngleToDir(vAimDir); storage.mMovement.mRotation[2] = getZAngleToDir(vAimDir);
@ -324,12 +324,69 @@ namespace MWMechanics
storage.mMovement.mRotation[2] = getZAngleToDir((vTargetPos-vActorPos)); // using vAimDir results in spastic movements since the head is animated storage.mMovement.mRotation[2] = getZAngleToDir((vTargetPos-vActorPos)); // using vAimDir results in spastic movements since the head is animated
} }
storage.mLastTargetPos = vTargetPos;
if (storage.mReadyToAttack) if (storage.mReadyToAttack)
{ {
storage.startCombatMove(isRangedCombat, distToTarget, rangeAttack, actor, target); storage.startCombatMove(isRangedCombat, distToTarget, rangeAttack, actor, target);
// start new attack // start new attack
storage.startAttackIfReady(actor, characterController, weapon, isRangedCombat); storage.startAttackIfReady(actor, characterController, weapon, isRangedCombat);
} }
// If actor uses custom destination it has to try to rebuild path because environment can change
// (door is opened between actor and target) or target position has changed and current custom destination
// is not good enough to attack target.
if (storage.mCurrentAction->isAttackingOrSpell()
&& ((!storage.mReadyToAttack && !mPathFinder.isPathConstructed())
|| (storage.mUseCustomDestination && (storage.mCustomDestination - vTargetPos).length() > rangeAttack)))
{
// Try to build path to the target.
const auto halfExtents = MWBase::Environment::get().getWorld()->getPathfindingHalfExtents(actor);
const auto navigatorFlags = getNavigatorFlags(actor);
const auto areaCosts = getAreaCosts(actor);
const auto pathGridGraph = getPathGridGraph(actor.getCell());
mPathFinder.buildPath(actor, vActorPos, vTargetPos, actor.getCell(), pathGridGraph, halfExtents, navigatorFlags, areaCosts);
if (!mPathFinder.isPathConstructed())
{
// If there is no path, try to find a point on a line from the actor position to target projected
// on navmesh to attack the target from there.
const MWBase::World* world = MWBase::Environment::get().getWorld();
const auto halfExtents = world->getPathfindingHalfExtents(actor);
const auto navigator = world->getNavigator();
const auto navigatorFlags = getNavigatorFlags(actor);
const auto areaCosts = getAreaCosts(actor);
const auto hit = navigator->raycast(halfExtents, vActorPos, vTargetPos, navigatorFlags);
if (hit.has_value() && (*hit - vTargetPos).length() <= rangeAttack)
{
// If the point is close enough, try to find a path to that point.
mPathFinder.buildPath(actor, vActorPos, *hit, actor.getCell(), pathGridGraph, halfExtents, navigatorFlags, areaCosts);
if (mPathFinder.isPathConstructed())
{
// If path to that point is found use it as custom destination.
storage.mCustomDestination = *hit;
storage.mUseCustomDestination = true;
}
}
if (!mPathFinder.isPathConstructed())
{
storage.mUseCustomDestination = false;
storage.stopAttack();
characterController.setAttackingOrSpell(false);
currentAction.reset(new ActionFlee());
actionCooldown = currentAction->getActionCooldown();
storage.startFleeing();
MWBase::Environment::get().getDialogueManager()->say(actor, "flee");
}
}
else
{
storage.mUseCustomDestination = false;
}
}
return false; return false;
} }

@ -55,6 +55,9 @@ namespace MWMechanics
float mFleeBlindRunTimer; float mFleeBlindRunTimer;
ESM::Pathgrid::Point mFleeDest; ESM::Pathgrid::Point mFleeDest;
bool mUseCustomDestination;
osg::Vec3f mCustomDestination;
AiCombatStorage(): AiCombatStorage():
mAttackCooldown(0.0f), mAttackCooldown(0.0f),
mTimerReact(AI_REACTION_TIME), mTimerReact(AI_REACTION_TIME),
@ -74,7 +77,9 @@ namespace MWMechanics
mFleeState(FleeState_None), mFleeState(FleeState_None),
mLOS(false), mLOS(false),
mUpdateLOSTimer(0.0f), mUpdateLOSTimer(0.0f),
mFleeBlindRunTimer(0.0f) mFleeBlindRunTimer(0.0f),
mUseCustomDestination(false),
mCustomDestination()
{} {}
void startCombatMove(bool isDistantCombat, float distToTarget, float rangeAttack, const MWWorld::Ptr& actor, const MWWorld::Ptr& target); void startCombatMove(bool isDistantCombat, float distToTarget, float rangeAttack, const MWWorld::Ptr& actor, const MWWorld::Ptr& target);

@ -86,9 +86,15 @@ bool AiPursue::execute (const MWWorld::Ptr& actor, CharacterController& characte
const float pathTolerance = 100.f; const float pathTolerance = 100.f;
if (pathTo(actor, dest, duration, pathTolerance) && // check the true distance in case the target is far away in Z-direction
std::abs(dest.z() - actorPos.z()) < pathTolerance) // check the true distance in case the target is far away in Z-direction bool reached = pathTo(actor, dest, duration, pathTolerance) &&
std::abs(dest.z() - actorPos.z()) < pathTolerance;
if (reached)
{ {
if (!MWBase::Environment::get().getWorld()->getLOS(target, actor))
return false;
/* /*
Start of tes3mp addition Start of tes3mp addition
@ -102,6 +108,7 @@ bool AiPursue::execute (const MWWorld::Ptr& actor, CharacterController& characte
/* /*
End of tes3mp addition End of tes3mp addition
*/ */
MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Dialogue, actor); //Arrest player when reached MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Dialogue, actor); //Arrest player when reached
return true; return true;
} }

@ -3154,8 +3154,8 @@ void CharacterController::updateHeadTracking(float duration)
if (!head) if (!head)
return; return;
float zAngleRadians = 0.f; double zAngleRadians = 0.f;
float xAngleRadians = 0.f; double xAngleRadians = 0.f;
if (!mHeadTrackTarget.isEmpty()) if (!mHeadTrackTarget.isEmpty())
{ {
@ -3188,15 +3188,16 @@ void CharacterController::updateHeadTracking(float duration)
const osg::Vec3f actorDirection = mPtr.getRefData().getBaseNode()->getAttitude() * osg::Vec3f(0,1,0); const osg::Vec3f actorDirection = mPtr.getRefData().getBaseNode()->getAttitude() * osg::Vec3f(0,1,0);
zAngleRadians = std::atan2(actorDirection.x(), actorDirection.y()) - std::atan2(direction.x(), direction.y()); zAngleRadians = std::atan2(actorDirection.x(), actorDirection.y()) - std::atan2(direction.x(), direction.y());
zAngleRadians = Misc::normalizeAngle(zAngleRadians - mAnimation->getHeadYaw()) + mAnimation->getHeadYaw();
zAngleRadians *= (1 - direction.z() * direction.z());
xAngleRadians = std::asin(direction.z()); xAngleRadians = std::asin(direction.z());
} }
const double xLimit = osg::DegreesToRadians(40.0); const double xLimit = osg::DegreesToRadians(40.0);
const double zLimit = osg::DegreesToRadians(30.0); const double zLimit = osg::DegreesToRadians(30.0);
double zLimitOffset = mAnimation->getUpperBodyYawRadians(); double zLimitOffset = mAnimation->getUpperBodyYawRadians();
xAngleRadians = osg::clampBetween(Misc::normalizeAngle(xAngleRadians), -xLimit, xLimit); xAngleRadians = osg::clampBetween(xAngleRadians, -xLimit, xLimit);
zAngleRadians = osg::clampBetween(Misc::normalizeAngle(zAngleRadians), zAngleRadians = osg::clampBetween(zAngleRadians, -zLimit + zLimitOffset, zLimit + zLimitOffset);
-zLimit + zLimitOffset, zLimit + zLimitOffset);
float factor = duration*5; float factor = duration*5;
factor = std::min(factor, 1.f); factor = std::min(factor, 1.f);

@ -412,6 +412,10 @@ namespace MWMechanics
void applyElementalShields(const MWWorld::Ptr &attacker, const MWWorld::Ptr &victim) void applyElementalShields(const MWWorld::Ptr &attacker, const MWWorld::Ptr &victim)
{ {
// Don't let elemental shields harm the player in god mode.
bool godmode = attacker == getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState();
if (godmode)
return;
for (int i=0; i<3; ++i) for (int i=0; i<3; ++i)
{ {
float magnitude = victim.getClass().getCreatureStats(victim).getMagicEffects().get(ESM::MagicEffect::FireShield+i).getMagnitude(); float magnitude = victim.getClass().getCreatureStats(victim).getMagicEffects().get(ESM::MagicEffect::FireShield+i).getMagnitude();

@ -35,6 +35,14 @@ namespace MWMechanics
{ {
} }
Spells::Spells(const Spells& spells) : mSpellList(spells.mSpellList), mSpells(spells.mSpells),
mSelectedSpell(spells.mSelectedSpell), mUsedPowers(spells.mUsedPowers),
mSpellsChanged(spells.mSpellsChanged), mEffects(spells.mEffects), mSourcedEffects(spells.mSourcedEffects)
{
if(mSpellList)
mSpellList->addListener(this);
}
std::map<const ESM::Spell*, SpellParams>::const_iterator Spells::begin() const std::map<const ESM::Spell*, SpellParams>::const_iterator Spells::begin() const
{ {
return mSpells.begin(); return mSpells.begin();

@ -57,6 +57,10 @@ namespace MWMechanics
Spells(); Spells();
Spells(const Spells&);
Spells(const Spells&&) = delete;
~Spells(); ~Spells();
static bool hasCorprusEffect(const ESM::Spell *spell); static bool hasCorprusEffect(const ESM::Spell *spell);

@ -209,6 +209,9 @@ osg::Vec3f Actor::getCollisionObjectPosition() const
bool Actor::setPosition(const osg::Vec3f& position) bool Actor::setPosition(const osg::Vec3f& position)
{ {
std::scoped_lock lock(mPositionMutex); std::scoped_lock lock(mPositionMutex);
// position is being forced, ignore simulation results until we sync up
if (mSkipSimulation)
return false;
bool hasChanged = mPosition != position || mPositionOffset.length() != 0 || mWorldPositionChanged; bool hasChanged = mPosition != position || mPositionOffset.length() != 0 || mWorldPositionChanged;
mPreviousPosition = mPosition + mPositionOffset; mPreviousPosition = mPosition + mPositionOffset;
mPosition = position + mPositionOffset; mPosition = position + mPositionOffset;
@ -222,6 +225,17 @@ void Actor::adjustPosition(const osg::Vec3f& offset)
mPositionOffset += offset; mPositionOffset += offset;
} }
void Actor::applyOffsetChange()
{
if (mPositionOffset.length() == 0)
return;
mWorldPosition += mPositionOffset;
mPosition += mPositionOffset;
mPreviousPosition += mPositionOffset;
mPositionOffset = osg::Vec3f();
mWorldPositionChanged = true;
}
osg::Vec3f Actor::getPosition() const osg::Vec3f Actor::getPosition() const
{ {
return mPosition; return mPosition;

@ -96,9 +96,16 @@ namespace MWPhysics
* Returns true if the new position is different. * Returns true if the new position is different.
*/ */
bool setPosition(const osg::Vec3f& position); bool setPosition(const osg::Vec3f& position);
// force set actor position to be as in Ptr::RefData
void updatePosition(); void updatePosition();
// register a position offset that will be applied during simulation.
void adjustPosition(const osg::Vec3f& offset); void adjustPosition(const osg::Vec3f& offset);
// apply position offset. Can't be called during simulation
void applyOffsetChange();
osg::Vec3f getPosition() const; osg::Vec3f getPosition() const;
osg::Vec3f getPreviousPosition() const; osg::Vec3f getPreviousPosition() const;

@ -10,6 +10,12 @@
#include <type_traits> #include <type_traits>
#if BT_BULLET_VERSION < 310
// Older Bullet versions only support `btScalar` heightfields.
// Our heightfield data is `float`.
//
// These functions handle conversion from `float` to `double` when
// `btScalar` is `double` (`BT_USE_DOUBLE_PRECISION`).
namespace namespace
{ {
template <class T> template <class T>
@ -40,14 +46,18 @@ namespace
return btScalarHeights.data(); return btScalarHeights.data();
} }
} }
#endif
namespace MWPhysics namespace MWPhysics
{ {
HeightField::HeightField(const float* heights, int x, int y, float triSize, float sqrtVerts, float minH, float maxH, const osg::Object* holdObject, PhysicsTaskScheduler* scheduler) HeightField::HeightField(const float* heights, int x, int y, float triSize, float sqrtVerts, float minH, float maxH, const osg::Object* holdObject, PhysicsTaskScheduler* scheduler)
: mHoldObject(holdObject) : mHoldObject(holdObject)
#if BT_BULLET_VERSION < 310
, mHeights(makeHeights(heights, sqrtVerts)) , mHeights(makeHeights(heights, sqrtVerts))
#endif
, mTaskScheduler(scheduler) , mTaskScheduler(scheduler)
{ {
#if BT_BULLET_VERSION < 310
mShape = std::make_unique<btHeightfieldTerrainShape>( mShape = std::make_unique<btHeightfieldTerrainShape>(
sqrtVerts, sqrtVerts, sqrtVerts, sqrtVerts,
getHeights(heights, mHeights), getHeights(heights, mHeights),
@ -55,9 +65,22 @@ namespace MWPhysics
minH, maxH, 2, minH, maxH, 2,
PHY_FLOAT, false PHY_FLOAT, false
); );
#else
mShape = std::make_unique<btHeightfieldTerrainShape>(
sqrtVerts, sqrtVerts, heights, minH, maxH, 2, false);
#endif
mShape->setUseDiamondSubdivision(true); mShape->setUseDiamondSubdivision(true);
mShape->setLocalScaling(btVector3(triSize, triSize, 1)); mShape->setLocalScaling(btVector3(triSize, triSize, 1));
#if BT_BULLET_VERSION >= 289
// Accelerates some collision tests.
//
// Note: The accelerator data structure in Bullet is only used
// in some operations. This could be improved, see:
// https://github.com/bulletphysics/bullet3/issues/3276
mShape->buildAccelerator();
#endif
btTransform transform(btQuaternion::getIdentity(), btTransform transform(btQuaternion::getIdentity(),
btVector3((x+0.5f) * triSize * (sqrtVerts-1), btVector3((x+0.5f) * triSize * (sqrtVerts-1),
(y+0.5f) * triSize * (sqrtVerts-1), (y+0.5f) * triSize * (sqrtVerts-1),

@ -34,7 +34,9 @@ namespace MWPhysics
std::unique_ptr<btHeightfieldTerrainShape> mShape; std::unique_ptr<btHeightfieldTerrainShape> mShape;
std::unique_ptr<btCollisionObject> mCollisionObject; std::unique_ptr<btCollisionObject> mCollisionObject;
osg::ref_ptr<const osg::Object> mHoldObject; osg::ref_ptr<const osg::Object> mHoldObject;
#if BT_BULLET_VERSION < 310
std::vector<btScalar> mHeights; std::vector<btScalar> mHeights;
#endif
PhysicsTaskScheduler* mTaskScheduler; PhysicsTaskScheduler* mTaskScheduler;

@ -186,9 +186,11 @@ namespace MWPhysics
mPostStepBarrier = std::make_unique<Misc::Barrier>(mNumThreads, [&]() mPostStepBarrier = std::make_unique<Misc::Barrier>(mNumThreads, [&]()
{ {
if (mRemainingSteps) if (mRemainingSteps)
{
--mRemainingSteps; --mRemainingSteps;
updateActorsPositions();
}
mNextJob.store(0, std::memory_order_release); mNextJob.store(0, std::memory_order_release);
updateActorsPositions();
}); });
mPostSimBarrier = std::make_unique<Misc::Barrier>(mNumThreads, [&]() mPostSimBarrier = std::make_unique<Misc::Barrier>(mNumThreads, [&]()
@ -492,6 +494,7 @@ namespace MWPhysics
if (actor->setPosition(actorData.mPosition)) if (actor->setPosition(actorData.mPosition))
{ {
std::scoped_lock lock(mCollisionWorldMutex); std::scoped_lock lock(mCollisionWorldMutex);
actorData.mPosition = actor->getPosition(); // account for potential position change made by script
actor->updateCollisionObjectPosition(); actor->updateCollisionObjectPosition();
mCollisionWorld->updateSingleAabb(actor->getCollisionObject()); mCollisionWorld->updateSingleAabb(actor->getCollisionObject());
} }

@ -628,7 +628,9 @@ namespace MWPhysics
return object->getCollisionObject(); return object->getCollisionObject();
return nullptr; return nullptr;
}(); }();
assert(caster);
if (caster == nullptr)
Log(Debug::Warning) << "No caster for projectile " << projectileId;
ProjectileConvexCallback resultCallback(caster, btFrom, btTo, projectile); ProjectileConvexCallback resultCallback(caster, btFrom, btTo, projectile);
resultCallback.m_collisionFilterMask = 0xff; resultCallback.m_collisionFilterMask = 0xff;
@ -719,6 +721,15 @@ namespace MWPhysics
return mProjectileId; return mProjectileId;
} }
void PhysicsSystem::setCaster(int projectileId, const MWWorld::Ptr& caster)
{
const auto foundProjectile = mProjectiles.find(projectileId);
assert(foundProjectile != mProjectiles.end());
auto* projectile = foundProjectile->second.get();
projectile->setCaster(caster);
}
bool PhysicsSystem::toggleCollisionMode() bool PhysicsSystem::toggleCollisionMode()
{ {
ActorMap::iterator found = mActors.find(MWMechanics::getPlayer()); ActorMap::iterator found = mActors.find(MWMechanics::getPlayer());
@ -974,6 +985,10 @@ namespace MWPhysics
void ActorFrameData::updatePosition() void ActorFrameData::updatePosition()
{ {
mActorRaw->updateWorldPosition(); mActorRaw->updateWorldPosition();
// If physics runs "fast enough", position are interpolated without simulation
// By calling this here, we are sure that offsets are applied at least once per frame,
// regardless of simulation speed.
mActorRaw->applyOffsetChange();
mPosition = mActorRaw->getPosition(); mPosition = mActorRaw->getPosition();
if (mMoveToWaterSurface) if (mMoveToWaterSurface)
{ {

@ -125,6 +125,7 @@ namespace MWPhysics
void addActor (const MWWorld::Ptr& ptr, const std::string& mesh); void addActor (const MWWorld::Ptr& ptr, const std::string& mesh);
int addProjectile(const MWWorld::Ptr& caster, const osg::Vec3f& position, const std::string& mesh, bool computeRadius, bool canTraverseWater); int addProjectile(const MWWorld::Ptr& caster, const osg::Vec3f& position, const std::string& mesh, bool computeRadius, bool canTraverseWater);
void setCaster(int projectileId, const MWWorld::Ptr& caster);
void updateProjectile(const int projectileId, const osg::Vec3f &position) const; void updateProjectile(const int projectileId, const osg::Vec3f &position) const;
void removeProjectile(const int projectileId); void removeProjectile(const int projectileId);

@ -11,6 +11,7 @@
#include <osg/PositionAttitudeTransform> #include <osg/PositionAttitudeTransform>
#include <osg/LightModel> #include <osg/LightModel>
#include <osg/LightSource> #include <osg/LightSource>
#include <osg/ValueObject>
#include <osgUtil/IntersectionVisitor> #include <osgUtil/IntersectionVisitor>
#include <osgUtil/LineSegmentIntersector> #include <osgUtil/LineSegmentIntersector>
@ -20,6 +21,7 @@
#include <components/resource/resourcesystem.hpp> #include <components/resource/resourcesystem.hpp>
#include <components/sceneutil/lightmanager.hpp> #include <components/sceneutil/lightmanager.hpp>
#include <components/sceneutil/shadow.hpp> #include <components/sceneutil/shadow.hpp>
#include <components/settings/settings.hpp>
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp" #include "../mwbase/world.hpp"
@ -84,7 +86,8 @@ namespace MWRender
}; };
// Set up alpha blending to Additive mode to avoid issues caused by transparent objects writing onto the alpha value of the FBO // Set up alpha blending mode to avoid issues caused by transparent objects writing onto the alpha value of the FBO
// This makes the RTT have premultiplied alpha, though, so the source blend factor must be GL_ONE when it's applied
class SetUpBlendVisitor : public osg::NodeVisitor class SetUpBlendVisitor : public osg::NodeVisitor
{ {
public: public:
@ -94,22 +97,40 @@ namespace MWRender
void apply(osg::Node& node) override void apply(osg::Node& node) override
{ {
if (osg::StateSet* stateset = node.getStateSet()) if (osg::ref_ptr<osg::StateSet> stateset = node.getStateSet())
{ {
osg::ref_ptr<osg::StateSet> newStateSet;
if (stateset->getAttribute(osg::StateAttribute::BLENDFUNC) || stateset->getBinNumber() == osg::StateSet::TRANSPARENT_BIN) if (stateset->getAttribute(osg::StateAttribute::BLENDFUNC) || stateset->getBinNumber() == osg::StateSet::TRANSPARENT_BIN)
{ {
osg::ref_ptr<osg::StateSet> newStateSet = new osg::StateSet(*stateset, osg::CopyOp::SHALLOW_COPY);
osg::BlendFunc* blendFunc = static_cast<osg::BlendFunc*>(stateset->getAttribute(osg::StateAttribute::BLENDFUNC)); osg::BlendFunc* blendFunc = static_cast<osg::BlendFunc*>(stateset->getAttribute(osg::StateAttribute::BLENDFUNC));
osg::ref_ptr<osg::BlendFunc> newBlendFunc = blendFunc ? new osg::BlendFunc(*blendFunc) : new osg::BlendFunc;
newBlendFunc->setDestinationAlpha(osg::BlendFunc::ONE); if (blendFunc)
newStateSet->setAttribute(newBlendFunc, osg::StateAttribute::ON); {
node.setStateSet(newStateSet); newStateSet = new osg::StateSet(*stateset, osg::CopyOp::SHALLOW_COPY);
node.setStateSet(newStateSet);
osg::ref_ptr<osg::BlendFunc> newBlendFunc = new osg::BlendFunc(*blendFunc);
newStateSet->setAttribute(newBlendFunc, osg::StateAttribute::ON);
// I *think* (based on some by-hand maths) that the RGB and dest alpha factors are unchanged, and only dest determines source alpha factor
// This has the benefit of being idempotent if we assume nothing used glBlendFuncSeparate before we touched it
if (blendFunc->getDestination() == osg::BlendFunc::ONE_MINUS_SRC_ALPHA)
newBlendFunc->setSourceAlpha(osg::BlendFunc::ONE);
else if (blendFunc->getDestination() == osg::BlendFunc::ONE)
newBlendFunc->setSourceAlpha(osg::BlendFunc::ZERO);
// Other setups barely exist in the wild and aren't worth supporting as they're not equippable gear
else
Log(Debug::Info) << "Unable to adjust blend mode for character preview. Source factor 0x" << std::hex << blendFunc->getSource() << ", destination factor 0x" << blendFunc->getDestination() << std::dec;
}
} }
if (stateset->getMode(GL_BLEND) & osg::StateAttribute::ON) if (stateset->getMode(GL_BLEND) & osg::StateAttribute::ON)
{ {
if (!newStateSet)
{
newStateSet = new osg::StateSet(*stateset, osg::CopyOp::SHALLOW_COPY);
node.setStateSet(newStateSet);
}
// Disable noBlendAlphaEnv // Disable noBlendAlphaEnv
stateset->setTextureMode(7, GL_TEXTURE_2D, osg::StateAttribute::OFF); newStateSet->setTextureMode(7, GL_TEXTURE_2D, osg::StateAttribute::OFF);
stateset->addUniform(mNoAlphaUniform); newStateSet->addUniform(mNoAlphaUniform);
} }
} }
traverse(node); traverse(node);
@ -134,6 +155,7 @@ namespace MWRender
mTexture->setInternalFormat(GL_RGBA); mTexture->setInternalFormat(GL_RGBA);
mTexture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); mTexture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR);
mTexture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); mTexture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR);
mTexture->setUserValue("premultiplied alpha", true);
mCamera = new osg::Camera; mCamera = new osg::Camera;
// hints that the camera is not relative to the master camera // hints that the camera is not relative to the master camera
@ -145,7 +167,7 @@ namespace MWRender
mCamera->setProjectionMatrixAsPerspective(fovYDegrees, sizeX/static_cast<float>(sizeY), 0.1f, 10000.f); // zNear and zFar are autocomputed mCamera->setProjectionMatrixAsPerspective(fovYDegrees, sizeX/static_cast<float>(sizeY), 0.1f, 10000.f); // zNear and zFar are autocomputed
mCamera->setViewport(0, 0, sizeX, sizeY); mCamera->setViewport(0, 0, sizeX, sizeY);
mCamera->setRenderOrder(osg::Camera::PRE_RENDER); mCamera->setRenderOrder(osg::Camera::PRE_RENDER);
mCamera->attach(osg::Camera::COLOR_BUFFER, mTexture); mCamera->attach(osg::Camera::COLOR_BUFFER, mTexture, 0, 0, false, Settings::Manager::getInt("antialiasing", "Video"));
mCamera->setName("CharacterPreview"); mCamera->setName("CharacterPreview");
mCamera->setComputeNearFarMode(osg::Camera::COMPUTE_NEAR_FAR_USING_BOUNDING_VOLUMES); mCamera->setComputeNearFarMode(osg::Camera::COMPUTE_NEAR_FAR_USING_BOUNDING_VOLUMES);
mCamera->setCullMask(~(Mask_UpdateVisitor)); mCamera->setCullMask(~(Mask_UpdateVisitor));

@ -1,5 +1,6 @@
#include "groundcover.hpp" #include "groundcover.hpp"
#include <osg/AlphaFunc>
#include <osg/Geometry> #include <osg/Geometry>
#include <osg/VertexAttribDivisor> #include <osg/VertexAttribDivisor>
@ -258,11 +259,16 @@ namespace MWRender
// Keep link to original mesh to keep it in cache // Keep link to original mesh to keep it in cache
group->getOrCreateUserDataContainer()->addUserObject(new Resource::TemplateRef(temp)); group->getOrCreateUserDataContainer()->addUserObject(new Resource::TemplateRef(temp));
mSceneManager->reinstateRemovedState(node);
InstancingVisitor visitor(pair.second, worldCenter); InstancingVisitor visitor(pair.second, worldCenter);
node->accept(visitor); node->accept(visitor);
group->addChild(node); group->addChild(node);
} }
// Force a unified alpha handling instead of data from meshes
osg::ref_ptr<osg::AlphaFunc> alpha = new osg::AlphaFunc(osg::AlphaFunc::GEQUAL, 128.f / 255.f);
group->getOrCreateStateSet()->setAttributeAndModes(alpha.get(), osg::StateAttribute::ON);
group->getBound(); group->getBound();
group->setNodeMask(Mask_Groundcover); group->setNodeMask(Mask_Groundcover);
mSceneManager->recreateShaders(group, "groundcover", false, true); mSceneManager->recreateShaders(group, "groundcover", false, true);

@ -18,6 +18,7 @@
#include <components/settings/settings.hpp> #include <components/settings/settings.hpp>
#include <components/sceneutil/visitor.hpp> #include <components/sceneutil/visitor.hpp>
#include <components/sceneutil/shadow.hpp> #include <components/sceneutil/shadow.hpp>
#include <components/sceneutil/util.hpp>
#include <components/files/memorystream.hpp> #include <components/files/memorystream.hpp>
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
@ -237,7 +238,7 @@ void LocalMap::setupRenderToTexture(osg::ref_ptr<osg::Camera> camera, int x, int
texture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); texture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE);
texture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); texture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE);
camera->attach(osg::Camera::COLOR_BUFFER, texture); SceneUtil::attachAlphaToCoverageFriendlyFramebufferToCamera(camera, osg::Camera::COLOR_BUFFER, texture);
camera->addChild(mSceneRoot); camera->addChild(mSceneRoot);
mRoot->addChild(camera); mRoot->addChild(camera);

@ -3,7 +3,6 @@
#include <limits> #include <limits>
#include <cstdlib> #include <cstdlib>
#include <osg/AlphaFunc>
#include <osg/Light> #include <osg/Light>
#include <osg/LightModel> #include <osg/LightModel>
#include <osg/Fog> #include <osg/Fog>
@ -26,6 +25,7 @@
#include <components/resource/scenemanager.hpp> #include <components/resource/scenemanager.hpp>
#include <components/resource/keyframemanager.hpp> #include <components/resource/keyframemanager.hpp>
#include <components/shader/removedalphafunc.hpp>
#include <components/shader/shadermanager.hpp> #include <components/shader/shadermanager.hpp>
#include <components/settings/settings.hpp> #include <components/settings/settings.hpp>
@ -212,6 +212,7 @@ namespace MWRender
resourceSystem->getSceneManager()->setAutoUseSpecularMaps(Settings::Manager::getBool("auto use object specular maps", "Shaders")); resourceSystem->getSceneManager()->setAutoUseSpecularMaps(Settings::Manager::getBool("auto use object specular maps", "Shaders"));
resourceSystem->getSceneManager()->setSpecularMapPattern(Settings::Manager::getString("specular map pattern", "Shaders")); resourceSystem->getSceneManager()->setSpecularMapPattern(Settings::Manager::getString("specular map pattern", "Shaders"));
resourceSystem->getSceneManager()->setApplyLightingToEnvMaps(Settings::Manager::getBool("apply lighting to environment maps", "Shaders")); resourceSystem->getSceneManager()->setApplyLightingToEnvMaps(Settings::Manager::getBool("apply lighting to environment maps", "Shaders"));
resourceSystem->getSceneManager()->setConvertAlphaTestToAlphaToCoverage(Settings::Manager::getBool("antialias alpha test", "Shaders") && Settings::Manager::getInt("antialiasing", "Video") > 1);
osg::ref_ptr<SceneUtil::LightManager> sceneRoot = new SceneUtil::LightManager; osg::ref_ptr<SceneUtil::LightManager> sceneRoot = new SceneUtil::LightManager;
sceneRoot->setLightingMask(Mask_Lighting); sceneRoot->setLightingMask(Mask_Lighting);
@ -244,6 +245,7 @@ namespace MWRender
globalDefines["clamp"] = Settings::Manager::getBool("clamp lighting", "Shaders") ? "1" : "0"; globalDefines["clamp"] = Settings::Manager::getBool("clamp lighting", "Shaders") ? "1" : "0";
globalDefines["preLightEnv"] = Settings::Manager::getBool("apply lighting to environment maps", "Shaders") ? "1" : "0"; globalDefines["preLightEnv"] = Settings::Manager::getBool("apply lighting to environment maps", "Shaders") ? "1" : "0";
globalDefines["radialFog"] = Settings::Manager::getBool("radial fog", "Shaders") ? "1" : "0"; globalDefines["radialFog"] = Settings::Manager::getBool("radial fog", "Shaders") ? "1" : "0";
globalDefines["useGPUShader4"] = "0";
float groundcoverDistance = (Constants::CellSizeInUnits * std::max(1, Settings::Manager::getInt("distance", "Groundcover")) - 1024) * 0.93; float groundcoverDistance = (Constants::CellSizeInUnits * std::max(1, Settings::Manager::getInt("distance", "Groundcover")) - 1024) * 0.93;
globalDefines["groundcoverFadeStart"] = std::to_string(groundcoverDistance * 0.9f); globalDefines["groundcoverFadeStart"] = std::to_string(groundcoverDistance * 0.9f);
@ -310,10 +312,6 @@ namespace MWRender
groundcoverRoot->setName("Groundcover Root"); groundcoverRoot->setName("Groundcover Root");
sceneRoot->addChild(groundcoverRoot); sceneRoot->addChild(groundcoverRoot);
// Force a unified alpha handling instead of data from meshes
osg::ref_ptr<osg::AlphaFunc> alpha = new osg::AlphaFunc(osg::AlphaFunc::GEQUAL, 128.f/255.f);
groundcoverRoot->getOrCreateStateSet()->setAttributeAndModes(alpha.get(), osg::StateAttribute::ON);
mGroundcoverUpdater = new GroundcoverUpdater; mGroundcoverUpdater = new GroundcoverUpdater;
groundcoverRoot->addUpdateCallback(mGroundcoverUpdater); groundcoverRoot->addUpdateCallback(mGroundcoverUpdater);
@ -370,7 +368,6 @@ namespace MWRender
mSky.reset(new SkyManager(sceneRoot, resourceSystem->getSceneManager())); mSky.reset(new SkyManager(sceneRoot, resourceSystem->getSceneManager()));
mSky->setCamera(mViewer->getCamera()); mSky->setCamera(mViewer->getCamera());
mSky->setRainIntensityUniform(mWater->getRainIntensityUniform());
source->setStateSetModes(*mRootNode->getOrCreateStateSet(), osg::StateAttribute::ON); source->setStateSetModes(*mRootNode->getOrCreateStateSet(), osg::StateAttribute::ON);
@ -409,6 +406,11 @@ namespace MWRender
mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("far", mViewDistance)); mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("far", mViewDistance));
mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("simpleWater", false)); mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("simpleWater", false));
// Hopefully, anything genuinely requiring the default alpha func of GL_ALWAYS explicitly sets it
mRootNode->getOrCreateStateSet()->setAttribute(Shader::RemovedAlphaFunc::getInstance(GL_ALWAYS));
// The transparent renderbin sets alpha testing on because that was faster on old GPUs. It's now slower and breaks things.
mRootNode->getOrCreateStateSet()->setMode(GL_ALPHA_TEST, osg::StateAttribute::OFF);
mUniformNear = mRootNode->getOrCreateStateSet()->getUniform("near"); mUniformNear = mRootNode->getOrCreateStateSet()->getUniform("near");
mUniformFar = mRootNode->getOrCreateStateSet()->getUniform("far"); mUniformFar = mRootNode->getOrCreateStateSet()->getUniform("far");
updateProjectionMatrix(); updateProjectionMatrix();
@ -658,6 +660,9 @@ namespace MWRender
mUnrefQueue->flush(mWorkQueue.get()); mUnrefQueue->flush(mWorkQueue.get());
float rainIntensity = mSky->getPrecipitationAlpha();
mWater->setRainIntensity(rainIntensity);
if (!paused) if (!paused)
{ {
mEffectManager->update(dt); mEffectManager->update(dt);

@ -497,8 +497,6 @@ public:
// Disable writing to the color buffer. We are using this geometry for visibility tests only. // Disable writing to the color buffer. We are using this geometry for visibility tests only.
osg::ref_ptr<osg::ColorMask> colormask (new osg::ColorMask(0, 0, 0, 0)); osg::ref_ptr<osg::ColorMask> colormask (new osg::ColorMask(0, 0, 0, 0));
stateset->setAttributeAndModes(colormask, osg::StateAttribute::ON); stateset->setAttributeAndModes(colormask, osg::StateAttribute::ON);
osg::ref_ptr<osg::PolygonOffset> po (new osg::PolygonOffset( -1., -1. ));
stateset->setAttributeAndModes(po, osg::StateAttribute::ON);
mTransform->addChild(queryNode); mTransform->addChild(queryNode);
@ -595,7 +593,7 @@ private:
if (queryVisible) if (queryVisible)
{ {
osg::ref_ptr<osg::Depth> depth (new osg::Depth); osg::ref_ptr<osg::Depth> depth (new osg::Depth);
depth->setFunction(osg::Depth::LESS); depth->setFunction(osg::Depth::LEQUAL);
// This is a trick to make fragments written by the query always use the maximum depth value, // This is a trick to make fragments written by the query always use the maximum depth value,
// without having to retrieve the current far clipping distance. // without having to retrieve the current far clipping distance.
// We want the sun glare to be "infinitely" far away. // We want the sun glare to be "infinitely" far away.
@ -1113,7 +1111,6 @@ private:
SkyManager::SkyManager(osg::Group* parentNode, Resource::SceneManager* sceneManager) SkyManager::SkyManager(osg::Group* parentNode, Resource::SceneManager* sceneManager)
: mSceneManager(sceneManager) : mSceneManager(sceneManager)
, mCamera(nullptr) , mCamera(nullptr)
, mRainIntensityUniform(nullptr)
, mAtmosphereNightRoll(0.f) , mAtmosphereNightRoll(0.f)
, mCreated(false) , mCreated(false)
, mIsStorm(false) , mIsStorm(false)
@ -1139,7 +1136,7 @@ SkyManager::SkyManager(osg::Group* parentNode, Resource::SceneManager* sceneMana
, mBaseWindSpeed(0.f) , mBaseWindSpeed(0.f)
, mEnabled(true) , mEnabled(true)
, mSunEnabled(true) , mSunEnabled(true)
, mEffectFade(0.f) , mPrecipitationAlpha(0.f)
{ {
osg::ref_ptr<CameraRelativeTransform> skyroot (new CameraRelativeTransform); osg::ref_ptr<CameraRelativeTransform> skyroot (new CameraRelativeTransform);
skyroot->setName("Sky Root"); skyroot->setName("Sky Root");
@ -1163,11 +1160,6 @@ SkyManager::SkyManager(osg::Group* parentNode, Resource::SceneManager* sceneMana
mUnderwaterSwitch = new UnderwaterSwitchCallback(skyroot); mUnderwaterSwitch = new UnderwaterSwitchCallback(skyroot);
} }
void SkyManager::setRainIntensityUniform(osg::Uniform *uniform)
{
mRainIntensityUniform = uniform;
}
void SkyManager::create() void SkyManager::create()
{ {
assert(!mCreated); assert(!mCreated);
@ -1522,7 +1514,7 @@ void SkyManager::createRain()
osg::ref_ptr<osgParticle::ModularProgram> program (new osgParticle::ModularProgram); osg::ref_ptr<osgParticle::ModularProgram> program (new osgParticle::ModularProgram);
program->addOperator(new WrapAroundOperator(mCamera,rainRange)); program->addOperator(new WrapAroundOperator(mCamera,rainRange));
program->addOperator(new WeatherAlphaOperator(mEffectFade, true)); program->addOperator(new WeatherAlphaOperator(mPrecipitationAlpha, true));
program->setParticleSystem(mRainParticleSystem); program->setParticleSystem(mRainParticleSystem);
mRainNode->addChild(program); mRainNode->addChild(program);
@ -1576,29 +1568,23 @@ bool SkyManager::isEnabled()
return mEnabled; return mEnabled;
} }
bool SkyManager::hasRain() bool SkyManager::hasRain() const
{ {
return mRainNode != nullptr; return mRainNode != nullptr;
} }
float SkyManager::getPrecipitationAlpha() const
{
if (mEnabled && !mIsStorm && (hasRain() || mParticleNode))
return mPrecipitationAlpha;
return 0.f;
}
void SkyManager::update(float duration) void SkyManager::update(float duration)
{ {
if (!mEnabled) if (!mEnabled)
{
if (mRainIntensityUniform)
mRainIntensityUniform->set(0.f);
return; return;
}
if (mRainIntensityUniform)
{
float rainIntensity = 0.f;
if (!mIsStorm && (hasRain() || mParticleNode))
rainIntensity = mEffectFade;
mRainIntensityUniform->set(rainIntensity);
}
switchUnderwaterRain(); switchUnderwaterRain();
@ -1737,7 +1723,7 @@ void SkyManager::setWeather(const WeatherResult& weather)
SceneUtil::AssignControllerSourcesVisitor assignVisitor(std::shared_ptr<SceneUtil::ControllerSource>(new SceneUtil::FrameTimeSource)); SceneUtil::AssignControllerSourcesVisitor assignVisitor(std::shared_ptr<SceneUtil::ControllerSource>(new SceneUtil::FrameTimeSource));
mParticleEffect->accept(assignVisitor); mParticleEffect->accept(assignVisitor);
AlphaFader::SetupVisitor alphaFaderSetupVisitor(mEffectFade); AlphaFader::SetupVisitor alphaFaderSetupVisitor(mPrecipitationAlpha);
mParticleEffect->accept(alphaFaderSetupVisitor); mParticleEffect->accept(alphaFaderSetupVisitor);
@ -1754,7 +1740,7 @@ void SkyManager::setWeather(const WeatherResult& weather)
osg::ref_ptr<osgParticle::ModularProgram> program (new osgParticle::ModularProgram); osg::ref_ptr<osgParticle::ModularProgram> program (new osgParticle::ModularProgram);
if (!mIsStorm) if (!mIsStorm)
program->addOperator(new WrapAroundOperator(mCamera,osg::Vec3(1024,1024,800))); program->addOperator(new WrapAroundOperator(mCamera,osg::Vec3(1024,1024,800)));
program->addOperator(new WeatherAlphaOperator(mEffectFade, false)); program->addOperator(new WeatherAlphaOperator(mPrecipitationAlpha, false));
program->setParticleSystem(ps); program->setParticleSystem(ps);
mParticleNode->addChild(program); mParticleNode->addChild(program);
} }
@ -1843,7 +1829,7 @@ void SkyManager::setWeather(const WeatherResult& weather)
mAtmosphereNightNode->setNodeMask(weather.mNight ? ~0 : 0); mAtmosphereNightNode->setNodeMask(weather.mNight ? ~0 : 0);
mEffectFade = weather.mEffectFade; mPrecipitationAlpha = weather.mPrecipitationAlpha;
} }
float SkyManager::getBaseWindSpeed() const float SkyManager::getBaseWindSpeed() const

@ -7,7 +7,6 @@
#include <osg/ref_ptr> #include <osg/ref_ptr>
#include <osg/Vec4f> #include <osg/Vec4f>
#include <osg/Uniform>
namespace osg namespace osg
{ {
@ -88,7 +87,7 @@ namespace MWRender
std::string mParticleEffect; std::string mParticleEffect;
std::string mRainEffect; std::string mRainEffect;
float mEffectFade; float mPrecipitationAlpha;
float mRainDiameter; float mRainDiameter;
float mRainMinHeight; float mRainMinHeight;
@ -157,7 +156,9 @@ namespace MWRender
bool isEnabled(); bool isEnabled();
bool hasRain(); bool hasRain() const;
float getPrecipitationAlpha() const;
void setRainSpeed(float speed); void setRainSpeed(float speed);
@ -180,8 +181,6 @@ namespace MWRender
void setCamera(osg::Camera *camera); void setCamera(osg::Camera *camera);
void setRainIntensityUniform(osg::Uniform *uniform);
float getBaseWindSpeed() const; float getBaseWindSpeed() const;
private: private:
@ -196,7 +195,6 @@ namespace MWRender
Resource::SceneManager* mSceneManager; Resource::SceneManager* mSceneManager;
osg::Camera *mCamera; osg::Camera *mCamera;
osg::Uniform *mRainIntensityUniform;
osg::ref_ptr<osg::Group> mRootNode; osg::ref_ptr<osg::Group> mRootNode;
osg::ref_ptr<osg::Group> mEarlyRenderBinRoot; osg::ref_ptr<osg::Group> mEarlyRenderBinRoot;
@ -271,7 +269,7 @@ namespace MWRender
bool mEnabled; bool mEnabled;
bool mSunEnabled; bool mSunEnabled;
float mEffectFade; float mPrecipitationAlpha;
osg::Vec4f mMoonScriptColor; osg::Vec4f mMoonScriptColor;
}; };

@ -26,6 +26,7 @@
#include <components/resource/scenemanager.hpp> #include <components/resource/scenemanager.hpp>
#include <components/sceneutil/shadow.hpp> #include <components/sceneutil/shadow.hpp>
#include <components/sceneutil/util.hpp>
#include <components/sceneutil/waterutil.hpp> #include <components/sceneutil/waterutil.hpp>
#include <components/misc/constants.hpp> #include <components/misc/constants.hpp>
@ -208,6 +209,37 @@ public:
} }
}; };
class RainIntensityUpdater : public SceneUtil::StateSetUpdater
{
public:
RainIntensityUpdater()
: mRainIntensity(0.f)
{
}
void setRainIntensity(float rainIntensity)
{
mRainIntensity = rainIntensity;
}
protected:
void setDefaults(osg::StateSet* stateset) override
{
osg::ref_ptr<osg::Uniform> rainIntensityUniform = new osg::Uniform("rainIntensity", 0.0f);
stateset->addUniform(rainIntensityUniform.get());
}
void apply(osg::StateSet* stateset, osg::NodeVisitor* /*nv*/) override
{
osg::ref_ptr<osg::Uniform> rainIntensityUniform = stateset->getUniform("rainIntensity");
if (rainIntensityUniform != nullptr)
rainIntensityUniform->set(mRainIntensity);
}
private:
float mRainIntensity;
};
osg::ref_ptr<osg::Image> readPngImage (const std::string& file) osg::ref_ptr<osg::Image> readPngImage (const std::string& file)
{ {
// use boost in favor of osgDB::readImage, to handle utf-8 path issues on Windows // use boost in favor of osgDB::readImage, to handle utf-8 path issues on Windows
@ -271,7 +303,7 @@ public:
mRefractionTexture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); mRefractionTexture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR);
mRefractionTexture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); mRefractionTexture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR);
attach(osg::Camera::COLOR_BUFFER, mRefractionTexture); SceneUtil::attachAlphaToCoverageFriendlyFramebufferToCamera(this, osg::Camera::COLOR_BUFFER, mRefractionTexture);
mRefractionDepthTexture = new osg::Texture2D; mRefractionDepthTexture = new osg::Texture2D;
mRefractionDepthTexture->setTextureSize(rttSize, rttSize); mRefractionDepthTexture->setTextureSize(rttSize, rttSize);
@ -356,7 +388,7 @@ public:
mReflectionTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); mReflectionTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE);
mReflectionTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); mReflectionTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE);
attach(osg::Camera::COLOR_BUFFER, mReflectionTexture); SceneUtil::attachAlphaToCoverageFriendlyFramebufferToCamera(this, osg::Camera::COLOR_BUFFER, mReflectionTexture);
// XXX: should really flip the FrontFace on each renderable instead of forcing clockwise. // XXX: should really flip the FrontFace on each renderable instead of forcing clockwise.
osg::ref_ptr<osg::FrontFace> frontFace (new osg::FrontFace); osg::ref_ptr<osg::FrontFace> frontFace (new osg::FrontFace);
@ -431,7 +463,8 @@ public:
Water::Water(osg::Group *parent, osg::Group* sceneRoot, Resource::ResourceSystem *resourceSystem, Water::Water(osg::Group *parent, osg::Group* sceneRoot, Resource::ResourceSystem *resourceSystem,
osgUtil::IncrementalCompileOperation *ico, const std::string& resourcePath) osgUtil::IncrementalCompileOperation *ico, const std::string& resourcePath)
: mParent(parent) : mRainIntensityUpdater(nullptr)
, mParent(parent)
, mSceneRoot(sceneRoot) , mSceneRoot(sceneRoot)
, mResourceSystem(resourceSystem) , mResourceSystem(resourceSystem)
, mResourcePath(resourcePath) , mResourcePath(resourcePath)
@ -463,8 +496,6 @@ Water::Water(osg::Group *parent, osg::Group* sceneRoot, Resource::ResourceSystem
setHeight(mTop); setHeight(mTop);
mRainIntensityUniform = new osg::Uniform("rainIntensity",(float) 0.0);
updateWaterMaterial(); updateWaterMaterial();
if (ico) if (ico)
@ -494,11 +525,6 @@ void Water::setCullCallback(osg::Callback* callback)
} }
} }
osg::Uniform *Water::getRainIntensityUniform()
{
return mRainIntensityUniform.get();
}
void Water::updateWaterMaterial() void Water::updateWaterMaterial()
{ {
if (mReflection) if (mReflection)
@ -556,6 +582,8 @@ void Water::createSimpleWaterStateSet(osg::Node* node, float alpha)
osg::ref_ptr<osg::StateSet> stateset = SceneUtil::createSimpleWaterStateSet(alpha, MWRender::RenderBin_Water); osg::ref_ptr<osg::StateSet> stateset = SceneUtil::createSimpleWaterStateSet(alpha, MWRender::RenderBin_Water);
node->setStateSet(stateset); node->setStateSet(stateset);
node->setUpdateCallback(nullptr);
mRainIntensityUpdater = nullptr;
// Add animated textures // Add animated textures
std::vector<osg::ref_ptr<osg::Texture2D> > textures; std::vector<osg::ref_ptr<osg::Texture2D> > textures;
@ -639,15 +667,15 @@ void Water::createShaderWaterStateSet(osg::Node* node, Reflection* reflection, R
shaderStateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); shaderStateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF);
shaderStateset->addUniform(mRainIntensityUniform.get());
osg::ref_ptr<osg::Program> program (new osg::Program); osg::ref_ptr<osg::Program> program (new osg::Program);
program->addShader(vertexShader); program->addShader(vertexShader);
program->addShader(fragmentShader); program->addShader(fragmentShader);
shaderStateset->setAttributeAndModes(program, osg::StateAttribute::ON); shaderStateset->setAttributeAndModes(program, osg::StateAttribute::ON);
node->setStateSet(shaderStateset); node->setStateSet(shaderStateset);
node->setUpdateCallback(nullptr);
mRainIntensityUpdater = new RainIntensityUpdater();
node->setUpdateCallback(mRainIntensityUpdater);
} }
void Water::processChangedSettings(const Settings::CategorySettingVector& settings) void Water::processChangedSettings(const Settings::CategorySettingVector& settings)
@ -730,6 +758,12 @@ void Water::setHeight(const float height)
mRefraction->setWaterLevel(mTop); mRefraction->setWaterLevel(mTop);
} }
void Water::setRainIntensity(float rainIntensity)
{
if (mRainIntensityUpdater)
mRainIntensityUpdater->setRainIntensity(rainIntensity);
}
void Water::update(float dt) void Water::update(float dt)
{ {
mSimulation->update(dt); mSimulation->update(dt);

@ -6,7 +6,6 @@
#include <osg/ref_ptr> #include <osg/ref_ptr>
#include <osg/Vec3f> #include <osg/Vec3f>
#include <osg/Uniform>
#include <osg/Camera> #include <osg/Camera>
#include <components/settings/settings.hpp> #include <components/settings/settings.hpp>
@ -46,11 +45,12 @@ namespace MWRender
class Refraction; class Refraction;
class Reflection; class Reflection;
class RippleSimulation; class RippleSimulation;
class RainIntensityUpdater;
/// Water rendering /// Water rendering
class Water class Water
{ {
osg::ref_ptr<osg::Uniform> mRainIntensityUniform; osg::ref_ptr<RainIntensityUpdater> mRainIntensityUpdater;
osg::ref_ptr<osg::Group> mParent; osg::ref_ptr<osg::Group> mParent;
osg::ref_ptr<osg::Group> mSceneRoot; osg::ref_ptr<osg::Group> mSceneRoot;
@ -112,6 +112,7 @@ namespace MWRender
void changeCell(const MWWorld::CellStore* store); void changeCell(const MWWorld::CellStore* store);
void setHeight(const float height); void setHeight(const float height);
void setRainIntensity(const float rainIntensity);
void update(float dt); void update(float dt);
@ -119,8 +120,6 @@ namespace MWRender
osg::Camera *getRefractionCamera(); osg::Camera *getRefractionCamera();
void processChangedSettings(const Settings::CategorySettingVector& settings); void processChangedSettings(const Settings::CategorySettingVector& settings);
osg::Uniform *getRainIntensityUniform();
}; };
} }

@ -330,20 +330,17 @@ namespace MWScript
Interpreter::Type_Float pos = runtime[0].mFloat; Interpreter::Type_Float pos = runtime[0].mFloat;
runtime.pop(); runtime.pop();
float ax = ptr.getRefData().getPosition().pos[0];
float ay = ptr.getRefData().getPosition().pos[1];
float az = ptr.getRefData().getPosition().pos[2];
// Note: SetPos does not skip weather transitions in vanilla engine, so we do not call setTeleported(true) here. // Note: SetPos does not skip weather transitions in vanilla engine, so we do not call setTeleported(true) here.
MWWorld::Ptr updated = ptr; const auto curPos = ptr.getRefData().getPosition().asVec3();
auto newPos = curPos;
if(axis == "x") if(axis == "x")
{ {
updated = MWBase::Environment::get().getWorld()->moveObject(ptr,pos,ay,az,true); newPos[0] = pos;
} }
else if(axis == "y") else if(axis == "y")
{ {
updated = MWBase::Environment::get().getWorld()->moveObject(ptr,ax,pos,az,true); newPos[1] = pos;
} }
else if(axis == "z") else if(axis == "z")
{ {
@ -352,20 +349,21 @@ namespace MWScript
{ {
float terrainHeight = -std::numeric_limits<float>::max(); float terrainHeight = -std::numeric_limits<float>::max();
if (ptr.getCell()->isExterior()) if (ptr.getCell()->isExterior())
terrainHeight = MWBase::Environment::get().getWorld()->getTerrainHeightAt(osg::Vec3f(ax, ay, az)); terrainHeight = MWBase::Environment::get().getWorld()->getTerrainHeightAt(curPos);
if (pos < terrainHeight) if (pos < terrainHeight)
pos = terrainHeight; pos = terrainHeight;
} }
updated = MWBase::Environment::get().getWorld()->moveObject(ptr,ax,ay,pos,true); newPos[2] = pos;
} }
else else
{ {
return; return;
} }
dynamic_cast<MWScript::InterpreterContext&>(runtime.getContext()).updatePtr(ptr,updated); dynamic_cast<MWScript::InterpreterContext&>(runtime.getContext()).updatePtr(ptr,
MWBase::Environment::get().getWorld()->moveObjectBy(ptr, newPos - curPos));
} }
}; };

@ -287,9 +287,9 @@ void FFmpeg_Decoder::close()
mStream = nullptr; mStream = nullptr;
av_packet_unref(&mPacket); av_packet_unref(&mPacket);
av_freep(&mFrame);
swr_free(&mSwr);
av_freep(&mDataBuf); av_freep(&mDataBuf);
av_frame_free(&mFrame);
swr_free(&mSwr);
if(mFormatCtx) if(mFormatCtx)
{ {
@ -302,11 +302,13 @@ void FFmpeg_Decoder::close()
// //
if (mFormatCtx->pb->buffer != nullptr) if (mFormatCtx->pb->buffer != nullptr)
{ {
av_free(mFormatCtx->pb->buffer); av_freep(&mFormatCtx->pb->buffer);
mFormatCtx->pb->buffer = nullptr;
} }
av_free(mFormatCtx->pb); #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 80, 100)
mFormatCtx->pb = nullptr; avio_context_free(&mFormatCtx->pb);
#else
av_freep(&mFormatCtx->pb);
#endif
} }
avformat_close_input(&mFormatCtx); avformat_close_input(&mFormatCtx);
} }

@ -549,6 +549,8 @@ void MWState::StateManager::loadGame (const Character *character, const std::str
MWBase::Environment::get().getWorld()->changeToCell(cell->getCell()->getCellId(), pos, true, false); MWBase::Environment::get().getWorld()->changeToCell(cell->getCell()->getCellId(), pos, true, false);
} }
MWBase::Environment::get().getWorld()->updateProjectilesCasters();
// Vanilla MW will restart startup scripts when a save game is loaded. This is unintuitive, // Vanilla MW will restart startup scripts when a save game is loaded. This is unintuitive,
// but some mods may be using it as a reload detector. // but some mods may be using it as a reload detector.
MWBase::Environment::get().getScriptManager()->getGlobalScripts().addStartup(); MWBase::Environment::get().getScriptManager()->getGlobalScripts().addStartup();

@ -117,6 +117,45 @@ namespace
} }
} }
template<class RecordType, class T>
void fixRestockingImpl(const T* base, RecordType& state)
{
// Workaround for old saves not containing negative quantities
for(const auto& baseItem : base->mInventory.mList)
{
if(baseItem.mCount < 0)
{
for(auto& item : state.mInventory.mItems)
{
if(item.mCount > 0 && Misc::StringUtils::ciEqual(baseItem.mItem, item.mRef.mRefID))
item.mCount = -item.mCount;
}
}
}
}
template<class RecordType, class T>
void fixRestocking(const T* base, RecordType& state)
{}
template<>
void fixRestocking<>(const ESM::Creature* base, ESM::CreatureState& state)
{
fixRestockingImpl(base, state);
}
template<>
void fixRestocking<>(const ESM::NPC* base, ESM::NpcState& state)
{
fixRestockingImpl(base, state);
}
template<>
void fixRestocking<>(const ESM::Container* base, ESM::ContainerState& state)
{
fixRestockingImpl(base, state);
}
template<typename RecordType, typename T> template<typename RecordType, typename T>
void readReferenceCollection (ESM::ESMReader& reader, void readReferenceCollection (ESM::ESMReader& reader,
MWWorld::CellRefList<T>& collection, const ESM::CellRef& cref, const std::map<int, int>& contentFileMap, MWWorld::CellStore* cellstore) MWWorld::CellRefList<T>& collection, const ESM::CellRef& cref, const std::map<int, int>& contentFileMap, MWWorld::CellStore* cellstore)
@ -147,6 +186,9 @@ namespace
if (!record) if (!record)
return; return;
if (state.mVersion < 15)
fixRestocking(record, state);
if (state.mRef.mRefNum.hasContentFile()) if (state.mRef.mRefNum.hasContentFile())
{ {
for (typename MWWorld::CellRefList<T>::List::iterator iter (collection.mList.begin()); for (typename MWWorld::CellRefList<T>::List::iterator iter (collection.mList.begin());

@ -352,6 +352,29 @@ namespace MWWorld
mProjectiles.push_back(state); mProjectiles.push_back(state);
} }
void ProjectileManager::updateCasters()
{
for (auto& state : mProjectiles)
mPhysics->setCaster(state.mProjectileId, state.getCaster());
for (auto& state : mMagicBolts)
{
// casters are identified by actor id in the savegame. objects doesn't have one so they can't be identified back.
// TODO: should object-type caster be restored from savegame?
if (state.mActorId == -1)
continue;
auto caster = state.getCaster();
if (caster.isEmpty())
{
Log(Debug::Error) << "Couldn't find caster with ID " << state.mActorId;
cleanupMagicBolt(state);
continue;
}
mPhysics->setCaster(state.mProjectileId, caster);
}
}
void ProjectileManager::update(float dt) void ProjectileManager::update(float dt)
{ {
periodicCleanup(dt); periodicCleanup(dt);

@ -54,6 +54,8 @@ namespace MWWorld
void launchProjectile (MWWorld::Ptr actor, MWWorld::ConstPtr projectile, void launchProjectile (MWWorld::Ptr actor, MWWorld::ConstPtr projectile,
const osg::Vec3f& pos, const osg::Quat& orient, MWWorld::Ptr bow, float speed, float attackStrength); const osg::Vec3f& pos, const osg::Quat& orient, MWWorld::Ptr bow, float speed, float attackStrength);
void updateCasters();
void update(float dt); void update(float dt);
void processHits(); void processHits();

@ -479,11 +479,17 @@ namespace MWWorld
mPhysics->disableWater(); mPhysics->disableWater();
const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr(); const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr();
// By default the player is grounded, with the scene fully loaded, we validate and correct this.
if (player.mCell == cell) // Only run once, during initial cell load.
{
mPhysics->traceDown(player, player.getRefData().getPosition().asVec3(), 10.f);
}
navigator->update(player.getRefData().getPosition().asVec3()); navigator->update(player.getRefData().getPosition().asVec3());
if (!cell->isExterior() && !(cell->getCell()->mData.mFlags & ESM::Cell::QuasiEx)) if (!cell->isExterior() && !(cell->getCell()->mData.mFlags & ESM::Cell::QuasiEx))
{ {
mRendering.configureAmbient(cell->getCell()); mRendering.configureAmbient(cell->getCell());
} }

@ -1353,7 +1353,7 @@ inline void WeatherManager::calculateResult(const int weatherID, const float gam
mResult.mGlareView = current.mGlareView; mResult.mGlareView = current.mGlareView;
mResult.mAmbientLoopSoundID = current.mAmbientLoopSoundID; mResult.mAmbientLoopSoundID = current.mAmbientLoopSoundID;
mResult.mAmbientSoundVolume = 1.f; mResult.mAmbientSoundVolume = 1.f;
mResult.mEffectFade = 1.f; mResult.mPrecipitationAlpha = 1.f;
mResult.mIsStorm = current.mIsStorm; mResult.mIsStorm = current.mIsStorm;
@ -1460,7 +1460,7 @@ inline void WeatherManager::calculateTransitionResult(const float factor, const
mResult.mRainSpeed = current.mRainSpeed; mResult.mRainSpeed = current.mRainSpeed;
mResult.mRainEntranceSpeed = current.mRainEntranceSpeed; mResult.mRainEntranceSpeed = current.mRainEntranceSpeed;
mResult.mAmbientSoundVolume = 1 - factor / threshold; mResult.mAmbientSoundVolume = 1 - factor / threshold;
mResult.mEffectFade = mResult.mAmbientSoundVolume; mResult.mPrecipitationAlpha = mResult.mAmbientSoundVolume;
mResult.mAmbientLoopSoundID = current.mAmbientLoopSoundID; mResult.mAmbientLoopSoundID = current.mAmbientLoopSoundID;
mResult.mRainDiameter = current.mRainDiameter; mResult.mRainDiameter = current.mRainDiameter;
mResult.mRainMinHeight = current.mRainMinHeight; mResult.mRainMinHeight = current.mRainMinHeight;
@ -1475,7 +1475,7 @@ inline void WeatherManager::calculateTransitionResult(const float factor, const
mResult.mRainSpeed = other.mRainSpeed; mResult.mRainSpeed = other.mRainSpeed;
mResult.mRainEntranceSpeed = other.mRainEntranceSpeed; mResult.mRainEntranceSpeed = other.mRainEntranceSpeed;
mResult.mAmbientSoundVolume = (factor - threshold) / (1 - threshold); mResult.mAmbientSoundVolume = (factor - threshold) / (1 - threshold);
mResult.mEffectFade = mResult.mAmbientSoundVolume; mResult.mPrecipitationAlpha = mResult.mAmbientSoundVolume;
mResult.mAmbientLoopSoundID = other.mAmbientLoopSoundID; mResult.mAmbientLoopSoundID = other.mAmbientLoopSoundID;
mResult.mRainDiameter = other.mRainDiameter; mResult.mRainDiameter = other.mRainDiameter;

@ -3662,6 +3662,11 @@ namespace MWWorld
mProjectileManager->launchMagicBolt(spellId, caster, fallbackDirection); mProjectileManager->launchMagicBolt(spellId, caster, fallbackDirection);
} }
void World::updateProjectilesCasters()
{
mProjectileManager->updateCasters();
}
class ApplyLoopingParticlesVisitor : public MWMechanics::EffectSourceVisitor class ApplyLoopingParticlesVisitor : public MWMechanics::EffectSourceVisitor
{ {
private: private:

@ -817,6 +817,7 @@ namespace MWWorld
void launchMagicBolt (const std::string& spellId, const MWWorld::Ptr& caster, const osg::Vec3f& fallbackDirection) override; void launchMagicBolt (const std::string& spellId, const MWWorld::Ptr& caster, const osg::Vec3f& fallbackDirection) override;
void launchProjectile (MWWorld::Ptr& actor, MWWorld::Ptr& projectile, void launchProjectile (MWWorld::Ptr& actor, MWWorld::Ptr& projectile,
const osg::Vec3f& worldPos, const osg::Quat& orient, MWWorld::Ptr& bow, float speed, float attackStrength) override; const osg::Vec3f& worldPos, const osg::Quat& orient, MWWorld::Ptr& bow, float speed, float attackStrength) override;
void updateProjectilesCasters() override;
void applyLoopingParticles(const MWWorld::Ptr& ptr) override; void applyLoopingParticles(const MWWorld::Ptr& ptr) override;

@ -76,6 +76,17 @@ namespace
} }
}; };
template <std::size_t size>
btHeightfieldTerrainShape makeSquareHeightfieldTerrainShape(const std::array<btScalar, size>& values,
btScalar heightScale = 1, int upAxis = 2, PHY_ScalarType heightDataType = PHY_FLOAT, bool flipQuadEdges = false)
{
const int width = static_cast<int>(std::sqrt(size));
const btScalar min = *std::min_element(values.begin(), values.end());
const btScalar max = *std::max_element(values.begin(), values.end());
const btScalar greater = std::max(std::abs(min), std::abs(max));
return btHeightfieldTerrainShape(width, width, values.data(), heightScale, -greater, greater, upAxis, heightDataType, flipQuadEdges);
}
TEST_F(DetourNavigatorNavigatorTest, find_path_for_empty_should_return_empty) TEST_F(DetourNavigatorNavigatorTest, find_path_for_empty_should_return_empty)
{ {
EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mOut), EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mOut),
@ -108,7 +119,7 @@ namespace
0, -25, -100, -100, -100, 0, -25, -100, -100, -100,
0, -25, -100, -100, -100, 0, -25, -100, -100, -100,
}}; }};
btHeightfieldTerrainShape shape(5, 5, heightfieldData.data(), 1, 0, 0, 2, PHY_FLOAT, false); btHeightfieldTerrainShape shape = makeSquareHeightfieldTerrainShape(heightfieldData);
shape.setLocalScaling(btVector3(128, 128, 1)); shape.setLocalScaling(btVector3(128, 128, 1));
mNavigator->addAgent(mAgentHalfExtents); mNavigator->addAgent(mAgentHalfExtents);
@ -154,7 +165,7 @@ namespace
0, -25, -100, -100, -100, 0, -25, -100, -100, -100,
0, -25, -100, -100, -100, 0, -25, -100, -100, -100,
}}; }};
btHeightfieldTerrainShape heightfieldShape(5, 5, heightfieldData.data(), 1, 0, 0, 2, PHY_FLOAT, false); btHeightfieldTerrainShape heightfieldShape = makeSquareHeightfieldTerrainShape(heightfieldData);
heightfieldShape.setLocalScaling(btVector3(128, 128, 1)); heightfieldShape.setLocalScaling(btVector3(128, 128, 1));
btBoxShape boxShape(btVector3(20, 20, 100)); btBoxShape boxShape(btVector3(20, 20, 100));
@ -238,7 +249,7 @@ namespace
0, -25, -100, -100, -100, 0, -25, -100, -100, -100,
0, -25, -100, -100, -100, 0, -25, -100, -100, -100,
}}; }};
btHeightfieldTerrainShape heightfieldShape(5, 5, heightfieldData.data(), 1, 0, 0, 2, PHY_FLOAT, false); btHeightfieldTerrainShape heightfieldShape = makeSquareHeightfieldTerrainShape(heightfieldData);
heightfieldShape.setLocalScaling(btVector3(128, 128, 1)); heightfieldShape.setLocalScaling(btVector3(128, 128, 1));
btBoxShape boxShape(btVector3(20, 20, 100)); btBoxShape boxShape(btVector3(20, 20, 100));
@ -325,7 +336,7 @@ namespace
0, -25, -100, -100, -100, 0, -25, -100, -100, -100,
0, -25, -100, -100, -100, 0, -25, -100, -100, -100,
}}; }};
btHeightfieldTerrainShape shape(5, 5, heightfieldData.data(), 1, 0, 0, 2, PHY_FLOAT, false); btHeightfieldTerrainShape shape = makeSquareHeightfieldTerrainShape(heightfieldData);
shape.setLocalScaling(btVector3(128, 128, 1)); shape.setLocalScaling(btVector3(128, 128, 1));
const std::array<btScalar, 5 * 5> heightfieldData2 {{ const std::array<btScalar, 5 * 5> heightfieldData2 {{
@ -335,7 +346,7 @@ namespace
-25, -25, -25, -25, -25, -25, -25, -25, -25, -25,
-25, -25, -25, -25, -25, -25, -25, -25, -25, -25,
}}; }};
btHeightfieldTerrainShape shape2(5, 5, heightfieldData2.data(), 1, 0, 0, 2, PHY_FLOAT, false); btHeightfieldTerrainShape shape2 = makeSquareHeightfieldTerrainShape(heightfieldData2);
shape2.setLocalScaling(btVector3(128, 128, 1)); shape2.setLocalScaling(btVector3(128, 128, 1));
mNavigator->addAgent(mAgentHalfExtents); mNavigator->addAgent(mAgentHalfExtents);
@ -382,7 +393,7 @@ namespace
0, -25, -100, -100, -100, 0, -25, -100, -100, -100,
0, -25, -100, -100, -100, 0, -25, -100, -100, -100,
}}; }};
btHeightfieldTerrainShape shape(5, 5, heightfieldData.data(), 1, 0, 0, 2, PHY_FLOAT, false); btHeightfieldTerrainShape shape = makeSquareHeightfieldTerrainShape(heightfieldData);
shape.setLocalScaling(btVector3(128, 128, 1)); shape.setLocalScaling(btVector3(128, 128, 1));
std::array<btScalar, 5 * 5> heightfieldDataAvoid {{ std::array<btScalar, 5 * 5> heightfieldDataAvoid {{
@ -392,7 +403,7 @@ namespace
-25, -25, -25, -25, -25, -25, -25, -25, -25, -25,
-25, -25, -25, -25, -25, -25, -25, -25, -25, -25,
}}; }};
btHeightfieldTerrainShape shapeAvoid(5, 5, heightfieldDataAvoid.data(), 1, 0, 0, 2, PHY_FLOAT, false); btHeightfieldTerrainShape shapeAvoid = makeSquareHeightfieldTerrainShape(heightfieldDataAvoid);
shapeAvoid.setLocalScaling(btVector3(128, 128, 1)); shapeAvoid.setLocalScaling(btVector3(128, 128, 1));
mNavigator->addAgent(mAgentHalfExtents); mNavigator->addAgent(mAgentHalfExtents);
@ -439,7 +450,7 @@ namespace
-50, -100, -150, -100, -100, -50, -100, -150, -100, -100,
0, -50, -100, -100, -100, 0, -50, -100, -100, -100,
}}; }};
btHeightfieldTerrainShape shape(5, 5, heightfieldData.data(), 1, 0, 0, 2, PHY_FLOAT, false); btHeightfieldTerrainShape shape = makeSquareHeightfieldTerrainShape(heightfieldData);
shape.setLocalScaling(btVector3(128, 128, 1)); shape.setLocalScaling(btVector3(128, 128, 1));
mNavigator->addAgent(mAgentHalfExtents); mNavigator->addAgent(mAgentHalfExtents);
@ -487,7 +498,7 @@ namespace
0, -100, -100, -100, -100, -100, 0, 0, -100, -100, -100, -100, -100, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
}}; }};
btHeightfieldTerrainShape shape(7, 7, heightfieldData.data(), 1, 0, 0, 2, PHY_FLOAT, false); btHeightfieldTerrainShape shape = makeSquareHeightfieldTerrainShape(heightfieldData);
shape.setLocalScaling(btVector3(128, 128, 1)); shape.setLocalScaling(btVector3(128, 128, 1));
mNavigator->addAgent(mAgentHalfExtents); mNavigator->addAgent(mAgentHalfExtents);
@ -534,7 +545,7 @@ namespace
0, -100, -100, -100, -100, -100, 0, 0, -100, -100, -100, -100, -100, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
}}; }};
btHeightfieldTerrainShape shape(7, 7, heightfieldData.data(), 1, 0, 0, 2, PHY_FLOAT, false); btHeightfieldTerrainShape shape = makeSquareHeightfieldTerrainShape(heightfieldData);
shape.setLocalScaling(btVector3(128, 128, 1)); shape.setLocalScaling(btVector3(128, 128, 1));
mNavigator->addAgent(mAgentHalfExtents); mNavigator->addAgent(mAgentHalfExtents);
@ -581,7 +592,7 @@ namespace
0, -100, -100, -100, -100, -100, 0, 0, -100, -100, -100, -100, -100, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
}}; }};
btHeightfieldTerrainShape shape(7, 7, heightfieldData.data(), 1, 0, 0, 2, PHY_FLOAT, false); btHeightfieldTerrainShape shape = makeSquareHeightfieldTerrainShape(heightfieldData);
shape.setLocalScaling(btVector3(128, 128, 1)); shape.setLocalScaling(btVector3(128, 128, 1));
mNavigator->addAgent(mAgentHalfExtents); mNavigator->addAgent(mAgentHalfExtents);
@ -626,7 +637,7 @@ namespace
0, -25, -100, -100, -100, 0, -25, -100, -100, -100,
0, -25, -100, -100, -100, 0, -25, -100, -100, -100,
}}; }};
btHeightfieldTerrainShape shape(5, 5, heightfieldData.data(), 1, 0, 0, 2, PHY_FLOAT, false); btHeightfieldTerrainShape shape = makeSquareHeightfieldTerrainShape(heightfieldData);
shape.setLocalScaling(btVector3(128, 128, 1)); shape.setLocalScaling(btVector3(128, 128, 1));
mNavigator->addAgent(mAgentHalfExtents); mNavigator->addAgent(mAgentHalfExtents);
@ -680,7 +691,7 @@ namespace
0, -25, -100, -100, -100, 0, -25, -100, -100, -100,
0, -25, -100, -100, -100, 0, -25, -100, -100, -100,
}}; }};
btHeightfieldTerrainShape shape(5, 5, heightfieldData.data(), 1, 0, 0, 2, PHY_FLOAT, false); btHeightfieldTerrainShape shape = makeSquareHeightfieldTerrainShape(heightfieldData);
shape.setLocalScaling(btVector3(128, 128, 1)); shape.setLocalScaling(btVector3(128, 128, 1));
mNavigator->addAgent(mAgentHalfExtents); mNavigator->addAgent(mAgentHalfExtents);
@ -711,7 +722,7 @@ namespace
0, -25, -100, -100, -100, 0, -25, -100, -100, -100,
0, -25, -100, -100, -100, 0, -25, -100, -100, -100,
}}; }};
btHeightfieldTerrainShape heightfieldShape(5, 5, heightfieldData.data(), 1, 0, 0, 2, PHY_FLOAT, false); btHeightfieldTerrainShape heightfieldShape = makeSquareHeightfieldTerrainShape(heightfieldData);
heightfieldShape.setLocalScaling(btVector3(128, 128, 1)); heightfieldShape.setLocalScaling(btVector3(128, 128, 1));
const std::vector<btBoxShape> boxShapes(100, btVector3(20, 20, 100)); const std::vector<btBoxShape> boxShapes(100, btVector3(20, 20, 100));
@ -802,4 +813,26 @@ namespace
EXPECT_GT(duration, mSettings.mMinUpdateInterval) EXPECT_GT(duration, mSettings.mMinUpdateInterval)
<< std::chrono::duration_cast<std::chrono::duration<float, std::milli>>(duration).count() << " ms"; << std::chrono::duration_cast<std::chrono::duration<float, std::milli>>(duration).count() << " ms";
} }
TEST_F(DetourNavigatorNavigatorTest, update_then_raycast_should_return_position)
{
const std::array<btScalar, 5 * 5> heightfieldData {{
0, 0, 0, 0, 0,
0, -25, -25, -25, -25,
0, -25, -100, -100, -100,
0, -25, -100, -100, -100,
0, -25, -100, -100, -100,
}};
btHeightfieldTerrainShape shape = makeSquareHeightfieldTerrainShape(heightfieldData);
shape.setLocalScaling(btVector3(128, 128, 1));
mNavigator->addAgent(mAgentHalfExtents);
mNavigator->addObject(ObjectId(&shape), shape, btTransform::getIdentity());
mNavigator->update(mPlayerPosition);
mNavigator->wait();
const auto result = mNavigator->raycast(mAgentHalfExtents, mStart, mEnd, Flag_walk);
ASSERT_THAT(result, Optional(Vec3fEq(mEnd.x(), mEnd.y(), 1.87719)));
}
} }

@ -4,10 +4,6 @@
#include <components/detournavigator/settingsutils.hpp> #include <components/detournavigator/settingsutils.hpp>
#include <BulletCollision/CollisionShapes/btBoxShape.h> #include <BulletCollision/CollisionShapes/btBoxShape.h>
#include <BulletCollision/CollisionShapes/btBvhTriangleMeshShape.h>
#include <BulletCollision/CollisionShapes/btTriangleMesh.h>
#include <BulletCollision/CollisionShapes/btHeightfieldTerrainShape.h>
#include <BulletCollision/CollisionShapes/btCompoundShape.h>
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include <gmock/gmock.h> #include <gmock/gmock.h>

@ -0,0 +1,211 @@
# Distributed under the OSI-approved BSD 3-Clause License. See accompanying
# file Copyright.txt or https://cmake.org/licensing for details.
# Copyright 2021 Bret Curtis for OpenMW
#[=======================================================================[.rst:
FindRecastNavigation
-------
Find the RecastNavigation include directory and library.
Use this module by invoking find_package with the form::
.. code-block:: cmake
find_package(RecastNavigation
[version] # Minimum version e.g. 1.8.0
[REQUIRED] # Fail with error if RECAST is not found
)
Imported targets
^^^^^^^^^^^^^^^^
This module defines the following :prop_tgt:`IMPORTED` targets:
.. variable:: RecastNavigation::Recast
Imported target for using the RECAST library, if found.
Result variables
^^^^^^^^^^^^^^^^
.. variable:: RECAST_FOUND
Set to true if RECAST library found, otherwise false or undefined.
.. variable:: RECAST_INCLUDE_DIRS
Paths to include directories listed in one variable for use by RECAST client.
.. variable:: RECAST_LIBRARIES
Paths to libraries to linked against to use RECAST.
.. variable:: RECAST_VERSION
The version string of RECAST found.
Cache variables
^^^^^^^^^^^^^^^
For users who wish to edit and control the module behavior, this module
reads hints about search locations from the following variables::
.. variable:: RECAST_INCLUDE_DIR
Path to RECAST include directory with ``Recast.h`` header.
.. variable:: RECAST_LIBRARY
Path to RECAST library to be linked.
NOTE: The variables above should not usually be used in CMakeLists.txt files!
#]=======================================================================]
### Find libraries ##############################################################
if(NOT RECAST_LIBRARY)
find_library(RECAST_LIBRARY_RELEASE NAMES Recast HINTS ${RECASTNAVIGATION_ROOT} PATH_SUFFIXES lib)
find_library(RECAST_LIBRARY_DEBUG NAMES Recast-d HINTS ${RECASTNAVIGATION_ROOT} PATH_SUFFIXES lib)
include(SelectLibraryConfigurations)
select_library_configurations(RECAST)
mark_as_advanced(RECAST_LIBRARY_RELEASE RECAST_LIBRARY_DEBUG)
else()
file(TO_CMAKE_PATH "${RECAST_LIBRARY}" RECAST_LIBRARY)
endif()
if(NOT DETOUR_LIBRARY)
find_library(DETOUR_LIBRARY_RELEASE NAMES Detour HINTS ${RECASTNAVIGATION_ROOT} PATH_SUFFIXES lib)
find_library(DETOUR_LIBRARY_DEBUG NAMES Detour-d HINTS ${RECASTNAVIGATION_ROOT} PATH_SUFFIXES lib)
include(SelectLibraryConfigurations)
select_library_configurations(DETOUR)
mark_as_advanced(DETOUR_LIBRARY_RELEASE DETOUR_LIBRARY_DEBUG)
else()
file(TO_CMAKE_PATH "${DETOUR_LIBRARY}" DETOUR_LIBRARY)
endif()
if(NOT DEBUGUTILS_LIBRARY)
find_library(DEBUGUTILS_LIBRARY_RELEASE NAMES DebugUtils HINTS ${RECASTNAVIGATION_ROOT} PATH_SUFFIXES lib)
find_library(DEBUGUTILS_LIBRARY_DEBUG NAMES DebugUtils-d HINTS ${RECASTNAVIGATION_ROOT} PATH_SUFFIXES lib)
include(SelectLibraryConfigurations)
select_library_configurations(DEBUGUTILS)
mark_as_advanced(DEBUGUTILS_LIBRARY_RELEASE DEBUGUTILS_LIBRARY_DEBUG)
else()
file(TO_CMAKE_PATH "${DEBUGUTILS_LIBRARY}" DEBUGUTILS_LIBRARY)
endif()
### Find include directory ####################################################
find_path(RECAST_INCLUDE_DIR NAMES Recast.h HINTS ${RECASTNAVIGATION_ROOT} PATH_SUFFIXES include RECAST include/recastnavigation)
mark_as_advanced(RECAST_INCLUDE_DIR)
if(RECAST_INCLUDE_DIR AND EXISTS "${RECAST_INCLUDE_DIR}/Recast.h")
file(STRINGS "${RECAST_INCLUDE_DIR}/Recast.h" _Recast_h_contents
REGEX "#define RECAST_VERSION_[A-Z]+[ ]+[0-9]+")
string(REGEX REPLACE "#define RECAST_VERSION_MAJOR[ ]+([0-9]+).+" "\\1"
RECAST_VERSION_MAJOR "${_Recast_h_contents}")
string(REGEX REPLACE ".+#define RECAST_VERSION_MINOR[ ]+([0-9]+).+" "\\1"
RECAST_VERSION_MINOR "${_Recast_h_contents}")
string(REGEX REPLACE ".+#define RECAST_VERSION_RELEASE[ ]+([0-9]+).*" "\\1"
RECAST_VERSION_RELEASE "${_Recast_h_contents}")
set(RECAST_VERSION "${RECAST_VERSION_MAJOR}.${RECAST_VERSION_MINOR}.${RECAST_VERSION_RELEASE}")
unset(_Recast_h_contents)
endif()
#TODO: they don't include a version yet
set(RECAST_VERSION "1.5.1")
### Set result variables ######################################################
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(RecastNavigation DEFAULT_MSG
RECAST_LIBRARY RECAST_INCLUDE_DIR RECAST_VERSION)
set(RECAST_LIBRARIES ${RECAST_LIBRARY})
set(RECAST_INCLUDE_DIRS ${RECAST_INCLUDE_DIR})
set(DETOUR_LIBRARIES ${DETOUR_LIBRARY})
set(DETOUR_INCLUDE_DIRS ${RECAST_INCLUDE_DIR})
set(DEBUGUTILS_LIBRARIES ${DEBUGUTILS_LIBRARY})
set(DEBUGUTILS_INCLUDE_DIRS ${RECAST_INCLUDE_DIR})
### Import targets ############################################################
if(RecastNavigation_FOUND)
if(NOT TARGET RecastNavigation::Recast)
add_library(RecastNavigation::Recast UNKNOWN IMPORTED)
set_target_properties(RecastNavigation::Recast PROPERTIES
IMPORTED_LINK_INTERFACE_LANGUAGES "C"
INTERFACE_INCLUDE_DIRECTORIES "${RECAST_INCLUDE_DIR}")
if(RECAST_LIBRARY_RELEASE)
set_property(TARGET RecastNavigation::Recast APPEND PROPERTY
IMPORTED_CONFIGURATIONS RELEASE)
set_target_properties(RecastNavigation::Recast PROPERTIES
IMPORTED_LOCATION_RELEASE "${RECAST_LIBRARY_RELEASE}")
endif()
if(RECAST_LIBRARY_DEBUG)
set_property(TARGET RecastNavigation::Recast APPEND PROPERTY
IMPORTED_CONFIGURATIONS DEBUG)
set_target_properties(RecastNavigation::Recast PROPERTIES
IMPORTED_LOCATION_DEBUG "${RECAST_LIBRARY_DEBUG}")
endif()
if(NOT RECAST_LIBRARY_RELEASE AND NOT RECAST_LIBRARY_DEBUG)
set_property(TARGET RecastNavigation::Recast APPEND PROPERTY
IMPORTED_LOCATION "${RECAST_LIBRARY}")
endif()
endif()
if(NOT TARGET RecastNavigation::Detour)
add_library(RecastNavigation::Detour UNKNOWN IMPORTED)
set_target_properties(RecastNavigation::Detour PROPERTIES
IMPORTED_LINK_INTERFACE_LANGUAGES "C"
INTERFACE_INCLUDE_DIRECTORIES "${DETOUR_INCLUDE_DIR}")
if(DETOUR_LIBRARY_RELEASE)
set_property(TARGET RecastNavigation::Detour APPEND PROPERTY
IMPORTED_CONFIGURATIONS RELEASE)
set_target_properties(RecastNavigation::Detour PROPERTIES
IMPORTED_LOCATION_RELEASE "${DETOUR_LIBRARY_RELEASE}")
endif()
if(DETOUR_LIBRARY_DEBUG)
set_property(TARGET RecastNavigation::Detour APPEND PROPERTY
IMPORTED_CONFIGURATIONS DEBUG)
set_target_properties(RecastNavigation::Detour PROPERTIES
IMPORTED_LOCATION_DEBUG "${DETOUR_LIBRARY_DEBUG}")
endif()
if(NOT DETOUR_LIBRARY_RELEASE AND NOT DETOUR_LIBRARY_DEBUG)
set_property(TARGET RecastNavigation::Detour APPEND PROPERTY
IMPORTED_LOCATION "${DETOUR_LIBRARY}")
endif()
endif()
if(NOT TARGET RecastNavigation::DebugUtils)
add_library(RecastNavigation::DebugUtils UNKNOWN IMPORTED)
set_target_properties(RecastNavigation::DebugUtils PROPERTIES
IMPORTED_LINK_INTERFACE_LANGUAGES "C"
INTERFACE_INCLUDE_DIRECTORIES "${DEBUGUTILS_INCLUDE_DIR}")
if(DEBUGUTILS_LIBRARY_RELEASE)
set_property(TARGET RecastNavigation::DebugUtils APPEND PROPERTY
IMPORTED_CONFIGURATIONS RELEASE)
set_target_properties(RecastNavigation::DebugUtils PROPERTIES
IMPORTED_LOCATION_RELEASE "${DEBUGUTILS_LIBRARY_RELEASE}")
endif()
if(DEBUGUTILS_LIBRARY_DEBUG)
set_property(TARGET RecastNavigation::DebugUtils APPEND PROPERTY
IMPORTED_CONFIGURATIONS DEBUG)
set_target_properties(RecastNavigation::DebugUtils PROPERTIES
IMPORTED_LOCATION_DEBUG "${DEBUGUTILS_LIBRARY_DEBUG}")
endif()
if(NOT DEBUGUTILS_LIBRARY_RELEASE AND NOT DEBUGUTILS_LIBRARY_DEBUG)
set_property(TARGET RecastNavigation::DebugUtils APPEND PROPERTY
IMPORTED_LOCATION "${DEBUGUTILS_LIBRARY}")
endif()
endif()
endif()

@ -47,7 +47,7 @@ add_component_dir (resource
) )
add_component_dir (shader add_component_dir (shader
shadermanager shadervisitor shadermanager shadervisitor removedalphafunc
) )
add_component_dir (sceneutil add_component_dir (sceneutil
@ -143,7 +143,7 @@ add_component_dir (fontloader
) )
add_component_dir (sdlutil add_component_dir (sdlutil
sdlgraphicswindow imagetosurface sdlinputwrapper sdlvideowrapper events sdlcursormanager gl4es_init sdlgraphicswindow imagetosurface sdlinputwrapper sdlvideowrapper events sdlcursormanager
) )
ENDIF(BUILD_OPENMW OR BUILD_OPENCS) ENDIF(BUILD_OPENMW OR BUILD_OPENCS)
@ -242,6 +242,9 @@ if (BUILD_OPENMW OR BUILD_OPENCS)
endif() endif()
# End of tes3mp change (major) # End of tes3mp change (major)
# Start of tes3mp change (major)
#
# Don't require the DetourNavigator when building the server
if (BUILD_OPENMW OR BUILD_OPENCS) if (BUILD_OPENMW OR BUILD_OPENCS)
add_component_dir(detournavigator add_component_dir(detournavigator
debug debug
@ -261,8 +264,10 @@ if (BUILD_OPENMW OR BUILD_OPENCS)
settings settings
navigator navigator
findrandompointaroundcircle findrandompointaroundcircle
raycast
) )
endif() endif()
# End of tes3mp change (major)
set (ESM_UI ${CMAKE_SOURCE_DIR}/files/ui/contentselector.ui set (ESM_UI ${CMAKE_SOURCE_DIR}/files/ui/contentselector.ui
) )
@ -305,20 +310,26 @@ include_directories(${BULLET_INCLUDE_DIRS} ${CMAKE_CURRENT_BINARY_DIR})
add_library(components STATIC ${COMPONENT_FILES} ${MOC_SRCS} ${ESM_UI_HDR}) add_library(components STATIC ${COMPONENT_FILES} ${MOC_SRCS} ${ESM_UI_HDR})
target_link_libraries(components target_link_libraries(components
${Boost_SYSTEM_LIBRARY} # CMake's built-in OSG finder does not use pkgconfig, so we have to
${Boost_FILESYSTEM_LIBRARY} # manually ensure the order is correct for inter-library dependencies.
${Boost_PROGRAM_OPTIONS_LIBRARY} # This only makes a difference with `-DOPENMW_USE_SYSTEM_OSG=ON -DOSG_STATIC=ON`.
${Boost_IOSTREAMS_LIBRARY} # https://gitlab.kitware.com/cmake/cmake/-/issues/21701
${OSG_LIBRARIES}
${OPENTHREADS_LIBRARIES}
${OSGPARTICLE_LIBRARIES} ${OSGPARTICLE_LIBRARIES}
${OSGUTIL_LIBRARIES}
${OSGDB_LIBRARIES}
${OSGVIEWER_LIBRARIES} ${OSGVIEWER_LIBRARIES}
${OSGTEXT_LIBRARIES}
${OSGGA_LIBRARIES}
${OSGSHADOW_LIBRARIES} ${OSGSHADOW_LIBRARIES}
${OSGANIMATION_LIBRARIES} ${OSGANIMATION_LIBRARIES}
${OSGGA_LIBRARIES}
${OSGTEXT_LIBRARIES}
${OSGDB_LIBRARIES}
${OSGUTIL_LIBRARIES}
${OSG_LIBRARIES}
${OPENTHREADS_LIBRARIES}
${Boost_SYSTEM_LIBRARY}
${Boost_FILESYSTEM_LIBRARY}
${Boost_PROGRAM_OPTIONS_LIBRARY}
${Boost_IOSTREAMS_LIBRARY}
${SDL2_LIBRARIES} ${SDL2_LIBRARIES}
${OPENGL_gl_LIBRARY} ${OPENGL_gl_LIBRARY}
${MyGUI_LIBRARIES} ${MyGUI_LIBRARIES}

@ -28,12 +28,11 @@
#include <boost/filesystem/path.hpp> #include <boost/filesystem/path.hpp>
#include <boost/filesystem/fstream.hpp> #include <boost/filesystem/fstream.hpp>
using namespace std;
using namespace Bsa; using namespace Bsa;
/// Error handling /// Error handling
void BSAFile::fail(const string &msg) void BSAFile::fail(const std::string &msg)
{ {
throw std::runtime_error("BSA Error: " + msg + "\nArchive: " + mFilename); throw std::runtime_error("BSA Error: " + msg + "\nArchive: " + mFilename);
} }
@ -160,7 +159,7 @@ int BSAFile::getIndex(const char *str) const
} }
/// Open an archive file. /// Open an archive file.
void BSAFile::open(const string &file) void BSAFile::open(const std::string &file)
{ {
mFilename = file; mFilename = file;
readHeader(); readHeader();
@ -171,7 +170,7 @@ Files::IStreamPtr BSAFile::getFile(const char *file)
assert(file); assert(file);
int i = getIndex(file); int i = getIndex(file);
if(i == -1) if(i == -1)
fail("File not found: " + string(file)); fail("File not found: " + std::string(file));
const FileStruct &fs = mFiles[i]; const FileStruct &fs = mFiles[i];

@ -4,6 +4,7 @@
#include <iomanip> #include <iomanip>
#include <limits> #include <limits>
#include <ostream> #include <ostream>
#include <tuple>
#include <BulletCollision/CollisionShapes/btHeightfieldTerrainShape.h> #include <BulletCollision/CollisionShapes/btHeightfieldTerrainShape.h>
#include <BulletCollision/CollisionShapes/btCompoundShape.h> #include <BulletCollision/CollisionShapes/btCompoundShape.h>

@ -32,133 +32,246 @@ either expressed or implied, of the FreeBSD Project.
#include "gldebug.hpp" #include "gldebug.hpp"
#include <cstdlib> #include <cstdlib>
#include <memory>
#include <components/debug/debuglog.hpp> #include <components/debug/debuglog.hpp>
// OpenGL constants not provided by OSG: // OpenGL constants not provided by OSG:
#include <SDL_opengl_glext.h> #include <SDL_opengl_glext.h>
void debugCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *message, const void *userParam) namespace Debug
{ {
#ifdef GL_DEBUG_OUTPUT
std::string srcStr; void debugCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *message, const void *userParam)
switch (source)
{ {
case GL_DEBUG_SOURCE_API: #ifdef GL_DEBUG_OUTPUT
srcStr = "API"; std::string srcStr;
break; switch (source)
case GL_DEBUG_SOURCE_WINDOW_SYSTEM: {
srcStr = "WINDOW_SYSTEM"; case GL_DEBUG_SOURCE_API:
break; srcStr = "API";
case GL_DEBUG_SOURCE_SHADER_COMPILER: break;
srcStr = "SHADER_COMPILER"; case GL_DEBUG_SOURCE_WINDOW_SYSTEM:
break; srcStr = "WINDOW_SYSTEM";
case GL_DEBUG_SOURCE_THIRD_PARTY: break;
srcStr = "THIRD_PARTY"; case GL_DEBUG_SOURCE_SHADER_COMPILER:
break; srcStr = "SHADER_COMPILER";
case GL_DEBUG_SOURCE_APPLICATION: break;
srcStr = "APPLICATION"; case GL_DEBUG_SOURCE_THIRD_PARTY:
break; srcStr = "THIRD_PARTY";
case GL_DEBUG_SOURCE_OTHER: break;
srcStr = "OTHER"; case GL_DEBUG_SOURCE_APPLICATION:
break; srcStr = "APPLICATION";
default: break;
srcStr = "UNDEFINED"; case GL_DEBUG_SOURCE_OTHER:
break; srcStr = "OTHER";
break;
default:
srcStr = "UNDEFINED";
break;
}
std::string typeStr;
Level logSeverity = Warning;
switch (type)
{
case GL_DEBUG_TYPE_ERROR:
typeStr = "ERROR";
logSeverity = Error;
break;
case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR:
typeStr = "DEPRECATED_BEHAVIOR";
break;
case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR:
typeStr = "UNDEFINED_BEHAVIOR";
break;
case GL_DEBUG_TYPE_PORTABILITY:
typeStr = "PORTABILITY";
break;
case GL_DEBUG_TYPE_PERFORMANCE:
typeStr = "PERFORMANCE";
break;
case GL_DEBUG_TYPE_OTHER:
typeStr = "OTHER";
break;
default:
typeStr = "UNDEFINED";
break;
}
Log(logSeverity) << "OpenGL " << typeStr << " [" << srcStr << "]: " << message;
#endif
} }
std::string typeStr; class PushDebugGroup
{
public:
static std::unique_ptr<PushDebugGroup> sInstance;
void (GL_APIENTRY * glPushDebugGroup) (GLenum source, GLuint id, GLsizei length, const GLchar * message);
void (GL_APIENTRY * glPopDebugGroup) (void);
Debug::Level logSeverity = Debug::Warning; bool valid()
switch (type) {
return glPushDebugGroup && glPopDebugGroup;
}
};
std::unique_ptr<PushDebugGroup> PushDebugGroup::sInstance{ std::make_unique<PushDebugGroup>() };
EnableGLDebugOperation::EnableGLDebugOperation() : osg::GraphicsOperation("EnableGLDebugOperation", false)
{ {
case GL_DEBUG_TYPE_ERROR:
typeStr = "ERROR";
logSeverity = Debug::Error;
break;
case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR:
typeStr = "DEPRECATED_BEHAVIOR";
break;
case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR:
typeStr = "UNDEFINED_BEHAVIOR";
break;
case GL_DEBUG_TYPE_PORTABILITY:
typeStr = "PORTABILITY";
break;
case GL_DEBUG_TYPE_PERFORMANCE:
typeStr = "PERFORMANCE";
break;
case GL_DEBUG_TYPE_OTHER:
typeStr = "OTHER";
break;
default:
typeStr = "UNDEFINED";
break;
} }
Log(logSeverity) << "OpenGL " << typeStr << " [" << srcStr << "]: " << message; void EnableGLDebugOperation::operator()(osg::GraphicsContext* graphicsContext)
{
#ifdef GL_DEBUG_OUTPUT
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mMutex);
unsigned int contextID = graphicsContext->getState()->getContextID();
typedef void (GL_APIENTRY *DEBUGPROC)(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *message, const void *userParam);
typedef void (GL_APIENTRY *GLDebugMessageControlFunction)(GLenum source, GLenum type, GLenum severity, GLsizei count, const GLuint *ids, GLboolean enabled);
typedef void (GL_APIENTRY *GLDebugMessageCallbackFunction)(DEBUGPROC, const void* userParam);
GLDebugMessageControlFunction glDebugMessageControl = nullptr;
GLDebugMessageCallbackFunction glDebugMessageCallback = nullptr;
if (osg::isGLExtensionSupported(contextID, "GL_KHR_debug"))
{
osg::setGLExtensionFuncPtr(glDebugMessageCallback, "glDebugMessageCallback");
osg::setGLExtensionFuncPtr(glDebugMessageControl, "glDebugMessageControl");
osg::setGLExtensionFuncPtr(PushDebugGroup::sInstance->glPushDebugGroup, "glPushDebugGroup");
osg::setGLExtensionFuncPtr(PushDebugGroup::sInstance->glPopDebugGroup, "glPopDebugGroup");
}
else if (osg::isGLExtensionSupported(contextID, "GL_ARB_debug_output"))
{
osg::setGLExtensionFuncPtr(glDebugMessageCallback, "glDebugMessageCallbackARB");
osg::setGLExtensionFuncPtr(glDebugMessageControl, "glDebugMessageControlARB");
}
else if (osg::isGLExtensionSupported(contextID, "GL_AMD_debug_output"))
{
osg::setGLExtensionFuncPtr(glDebugMessageCallback, "glDebugMessageCallbackAMD");
osg::setGLExtensionFuncPtr(glDebugMessageControl, "glDebugMessageControlAMD");
}
if (glDebugMessageCallback && glDebugMessageControl)
{
glEnable(GL_DEBUG_OUTPUT);
glDebugMessageControl(GL_DONT_CARE, GL_DEBUG_TYPE_ERROR, GL_DEBUG_SEVERITY_MEDIUM, 0, nullptr, true);
glDebugMessageCallback(debugCallback, nullptr);
Log(Info) << "OpenGL debug callback attached.";
}
else
#endif #endif
} Log(Error) << "Unable to attach OpenGL debug callback.";
}
void enableGLDebugExtension(unsigned int contextID) bool shouldDebugOpenGL()
{ {
#ifdef GL_DEBUG_OUTPUT const char* env = std::getenv("OPENMW_DEBUG_OPENGL");
typedef void (GL_APIENTRY *DEBUGPROC)(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *message, const void *userParam); if (!env)
typedef void (GL_APIENTRY *GLDebugMessageControlFunction)(GLenum source, GLenum type, GLenum severity, GLsizei count, const GLuint *ids, GLboolean enabled); return false;
typedef void (GL_APIENTRY *GLDebugMessageCallbackFunction)(DEBUGPROC, const void* userParam); std::string str(env);
if (str.length() == 0)
GLDebugMessageControlFunction glDebugMessageControl = nullptr; return true;
GLDebugMessageCallbackFunction glDebugMessageCallback = nullptr;
return str.find("OFF") == std::string::npos && str.find("0") == std::string::npos && str.find("NO") == std::string::npos;
if (osg::isGLExtensionSupported(contextID, "GL_KHR_debug")) }
DebugGroup::DebugGroup(const std::string & message, GLuint id)
#ifdef GL_DEBUG_OUTPUT
: mSource(GL_DEBUG_SOURCE_APPLICATION)
#else
: mSource(0x824A)
#endif
, mId(id)
, mMessage(message)
{ {
osg::setGLExtensionFuncPtr(glDebugMessageCallback, "glDebugMessageCallback");
osg::setGLExtensionFuncPtr(glDebugMessageControl, "glDebugMessageControl");
} }
else if (osg::isGLExtensionSupported(contextID, "GL_ARB_debug_output"))
DebugGroup::DebugGroup(const DebugGroup & debugGroup, const osg::CopyOp & copyop)
: osg::StateAttribute(debugGroup, copyop)
, mSource(debugGroup.mSource)
, mId(debugGroup.mId)
, mMessage(debugGroup.mMessage)
{ {
osg::setGLExtensionFuncPtr(glDebugMessageCallback, "glDebugMessageCallbackARB");
osg::setGLExtensionFuncPtr(glDebugMessageControl, "glDebugMessageControlARB");
} }
else if (osg::isGLExtensionSupported(contextID, "GL_AMD_debug_output"))
void DebugGroup::apply(osg::State & state) const
{ {
osg::setGLExtensionFuncPtr(glDebugMessageCallback, "glDebugMessageCallbackAMD"); if (!PushDebugGroup::sInstance->valid())
osg::setGLExtensionFuncPtr(glDebugMessageControl, "glDebugMessageControlAMD"); {
Log(Error) << "OpenGL debug groups not supported on this system, or OPENMW_DEBUG_OPENGL environment variable not set.";
return;
}
auto& attributeVec = state.getAttributeVec(this);
auto& lastAppliedStack = sLastAppliedStack[state.getContextID()];
size_t firstNonMatch = 0;
while (firstNonMatch < lastAppliedStack.size()
&& ((firstNonMatch < attributeVec.size() && lastAppliedStack[firstNonMatch] == attributeVec[firstNonMatch].first)
|| lastAppliedStack[firstNonMatch] == this))
firstNonMatch++;
for (size_t i = lastAppliedStack.size(); i > firstNonMatch; --i)
lastAppliedStack[i - 1]->pop(state);
lastAppliedStack.resize(firstNonMatch);
lastAppliedStack.reserve(attributeVec.size());
for (size_t i = firstNonMatch; i < attributeVec.size(); ++i)
{
const DebugGroup* group = static_cast<const DebugGroup*>(attributeVec[i].first);
group->push(state);
lastAppliedStack.push_back(group);
}
if (!(lastAppliedStack.back() == this))
{
push(state);
lastAppliedStack.push_back(this);
}
} }
if (glDebugMessageCallback && glDebugMessageControl) int DebugGroup::compare(const StateAttribute & sa) const
{ {
glEnable(GL_DEBUG_OUTPUT); COMPARE_StateAttribute_Types(DebugGroup, sa);
glDebugMessageControl(GL_DONT_CARE, GL_DEBUG_TYPE_ERROR, GL_DEBUG_SEVERITY_MEDIUM, 0, nullptr, true);
glDebugMessageCallback(debugCallback, nullptr); COMPARE_StateAttribute_Parameter(mSource);
COMPARE_StateAttribute_Parameter(mId);
COMPARE_StateAttribute_Parameter(mMessage);
Log(Debug::Info) << "OpenGL debug callback attached."; return 0;
} }
else
#endif
Log(Debug::Error) << "Unable to attach OpenGL debug callback.";
}
Debug::EnableGLDebugOperation::EnableGLDebugOperation() : osg::GraphicsOperation("EnableGLDebugOperation", false) void DebugGroup::releaseGLObjects(osg::State * state) const
{ {
} if (state)
sLastAppliedStack.erase(state->getContextID());
else
sLastAppliedStack.clear();
}
void Debug::EnableGLDebugOperation::operator()(osg::GraphicsContext* graphicsContext) bool DebugGroup::isValid() const
{ {
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mMutex); return mSource || mId || mMessage.length();
}
unsigned int contextID = graphicsContext->getState()->getContextID(); void DebugGroup::push(osg::State & state) const
enableGLDebugExtension(contextID); {
} if (isValid())
PushDebugGroup::sInstance->glPushDebugGroup(mSource, mId, mMessage.size(), mMessage.c_str());
}
void DebugGroup::pop(osg::State & state) const
{
if (isValid())
PushDebugGroup::sInstance->glPopDebugGroup();
}
std::map<unsigned int, std::vector<const DebugGroup *>> DebugGroup::sLastAppliedStack{};
bool Debug::shouldDebugOpenGL()
{
const char* env = std::getenv("OPENMW_DEBUG_OPENGL");
if (!env)
return false;
std::string str(env);
if (str.length() == 0)
return true;
return str.find("OFF") == std::string::npos && str.find("0") == std::string::npos && str.find("NO") == std::string::npos;
} }

@ -17,5 +17,65 @@ namespace Debug
}; };
bool shouldDebugOpenGL(); bool shouldDebugOpenGL();
/*
Debug groups allow rendering to be annotated, making debugging via APITrace/CodeXL/NSight etc. much clearer.
Because I've not thought of a quick and clean way of doing it without incurring a small performance cost,
there are no uses of this class checked in. For now, add annotations locally when you need them.
To use this class, add it to a StateSet just like any other StateAttribute. Prefer the string-only constructor.
You'll need OPENMW_DEBUG_OPENGL set to true, or shouldDebugOpenGL() redefined to just return true as otherwise
the extension function pointers won't get set up. That can maybe be cleaned up in the future.
Beware that consecutive identical debug groups (i.e. pointers match) won't always get applied due to OSG thinking
it's already applied them. Either avoid nesting the same object, add dummy groups so they're not consecutive, or
ensure the leaf group isn't identical to its parent.
*/
class DebugGroup : public osg::StateAttribute
{
public:
DebugGroup()
: mSource(0)
, mId(0)
, mMessage("")
{}
DebugGroup(GLenum source, GLuint id, const std::string& message)
: mSource(source)
, mId(id)
, mMessage(message)
{}
DebugGroup(const std::string& message, GLuint id = 0);
DebugGroup(const DebugGroup& debugGroup, const osg::CopyOp& copyop = osg::CopyOp::SHALLOW_COPY);
META_StateAttribute(Debug, DebugGroup, osg::StateAttribute::Type(101));
void apply(osg::State& state) const override;
int compare(const StateAttribute& sa) const override;
void releaseGLObjects(osg::State* state = nullptr) const override;
virtual bool isValid() const;
protected:
virtual ~DebugGroup() = default;
virtual void push(osg::State& state) const;
virtual void pop(osg::State& state) const;
GLenum mSource;
GLuint mId;
std::string mMessage;
static std::map<unsigned int, std::vector<const DebugGroup *>> sLastAppliedStack;
friend EnableGLDebugOperation;
};
} }
#endif #endif

@ -1,5 +1,6 @@
#include "findrandompointaroundcircle.hpp" #include "findrandompointaroundcircle.hpp"
#include "navigator.hpp" #include "navigator.hpp"
#include "raycast.hpp"
namespace DetourNavigator namespace DetourNavigator
{ {
@ -17,4 +18,19 @@ namespace DetourNavigator
return std::optional<osg::Vec3f>(); return std::optional<osg::Vec3f>();
return std::optional<osg::Vec3f>(fromNavMeshCoordinates(settings, *result)); return std::optional<osg::Vec3f>(fromNavMeshCoordinates(settings, *result));
} }
std::optional<osg::Vec3f> Navigator::raycast(const osg::Vec3f& agentHalfExtents, const osg::Vec3f& start,
const osg::Vec3f& end, const Flags includeFlags) const
{
const auto navMesh = getNavMesh(agentHalfExtents);
if (navMesh == nullptr)
return {};
const auto settings = getSettings();
const auto result = DetourNavigator::raycast(navMesh->lockConst()->getImpl(),
toNavMeshCoordinates(settings, agentHalfExtents), toNavMeshCoordinates(settings, start),
toNavMeshCoordinates(settings, end), includeFlags, settings);
if (!result)
return {};
return fromNavMeshCoordinates(settings, *result);
}
} }

@ -223,6 +223,17 @@ namespace DetourNavigator
std::optional<osg::Vec3f> findRandomPointAroundCircle(const osg::Vec3f& agentHalfExtents, std::optional<osg::Vec3f> findRandomPointAroundCircle(const osg::Vec3f& agentHalfExtents,
const osg::Vec3f& start, const float maxRadius, const Flags includeFlags) const; const osg::Vec3f& start, const float maxRadius, const Flags includeFlags) const;
/**
* @brief raycast finds farest navmesh point from start on a line from start to end that has path from start.
* @param agentHalfExtents allows to find navmesh for given actor.
* @param start of the line
* @param end of the line
* @param includeFlags setup allowed surfaces for actor to walk.
* @return not empty optional with position if point is found and empty optional if point is not found.
*/
std::optional<osg::Vec3f> raycast(const osg::Vec3f& agentHalfExtents, const osg::Vec3f& start,
const osg::Vec3f& end, const Flags includeFlags) const;
virtual RecastMeshTiles getRecastMeshTiles() = 0; virtual RecastMeshTiles getRecastMeshTiles() = 0;
}; };
} }

@ -18,6 +18,8 @@ namespace DetourNavigator
void NavigatorImpl::addAgent(const osg::Vec3f& agentHalfExtents) void NavigatorImpl::addAgent(const osg::Vec3f& agentHalfExtents)
{ {
if(agentHalfExtents.length2() <= 0)
return;
++mAgents[agentHalfExtents]; ++mAgents[agentHalfExtents];
mNavMeshManager.addAgent(agentHalfExtents); mNavMeshManager.addAgent(agentHalfExtents);
} }

@ -0,0 +1,44 @@
#include "raycast.hpp"
#include "settings.hpp"
#include "findsmoothpath.hpp"
#include <DetourCommon.h>
#include <DetourNavMesh.h>
#include <DetourNavMeshQuery.h>
#include <array>
namespace DetourNavigator
{
std::optional<osg::Vec3f> raycast(const dtNavMesh& navMesh, const osg::Vec3f& halfExtents,
const osg::Vec3f& start, const osg::Vec3f& end, const Flags includeFlags, const Settings& settings)
{
dtNavMeshQuery navMeshQuery;
if (!initNavMeshQuery(navMeshQuery, navMesh, settings.mMaxNavMeshQueryNodes))
return {};
dtQueryFilter queryFilter;
queryFilter.setIncludeFlags(includeFlags);
dtPolyRef ref = 0;
if (dtStatus status = navMeshQuery.findNearestPoly(start.ptr(), halfExtents.ptr(), &queryFilter, &ref, nullptr);
dtStatusFailed(status) || ref == 0)
return {};
const unsigned options = 0;
std::array<dtPolyRef, 16> path;
dtRaycastHit hit;
hit.path = path.data();
hit.maxPath = path.size();
if (dtStatus status = navMeshQuery.raycast(ref, start.ptr(), end.ptr(), &queryFilter, options, &hit);
dtStatusFailed(status) || hit.pathCount == 0)
return {};
osg::Vec3f hitPosition;
if (dtStatus status = navMeshQuery.closestPointOnPoly(path[hit.pathCount - 1], end.ptr(), hitPosition.ptr(), nullptr);
dtStatusFailed(status))
return {};
return hitPosition;
}
}

@ -0,0 +1,19 @@
#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_RAYCAST_H
#define OPENMW_COMPONENTS_DETOURNAVIGATOR_RAYCAST_H
#include "flags.hpp"
#include <optional>
#include <osg/Vec3f>
class dtNavMesh;
namespace DetourNavigator
{
struct Settings;
std::optional<osg::Vec3f> raycast(const dtNavMesh& navMesh, const osg::Vec3f& halfExtents,
const osg::Vec3f& start, const osg::Vec3f& end, const Flags includeFlags, const Settings& settings);
}
#endif

@ -357,16 +357,14 @@ std::string ESMReader::getString(int size)
void ESMReader::fail(const std::string &msg) void ESMReader::fail(const std::string &msg)
{ {
using namespace std; std::stringstream ss;
stringstream ss;
ss << "ESM Error: " << msg; ss << "ESM Error: " << msg;
ss << "\n File: " << mCtx.filename; ss << "\n File: " << mCtx.filename;
ss << "\n Record: " << mCtx.recName.toString(); ss << "\n Record: " << mCtx.recName.toString();
ss << "\n Subrecord: " << mCtx.subName.toString(); ss << "\n Subrecord: " << mCtx.subName.toString();
if (mEsm.get()) if (mEsm.get())
ss << "\n Offset: 0x" << hex << mEsm->tellg(); ss << "\n Offset: 0x" << std::hex << mEsm->tellg();
throw std::runtime_error(ss.str()); throw std::runtime_error(ss.str());
} }

@ -62,10 +62,31 @@ ESM::Variant& ESM::Variant::operator= (const Variant& variant)
return *this; return *this;
} }
ESM::Variant& ESM::Variant::operator= (Variant&& variant)
{
if (&variant!=this)
{
delete mData;
mType = variant.mType;
mData = variant.mData;
variant.mData = nullptr;
}
return *this;
}
ESM::Variant::Variant (const Variant& variant) ESM::Variant::Variant (const Variant& variant)
: mType (variant.mType), mData (variant.mData ? variant.mData->clone() : nullptr) : mType (variant.mType), mData (variant.mData ? variant.mData->clone() : nullptr)
{} {}
ESM::Variant::Variant(Variant&& variant)
: mType (variant.mType), mData (variant.mData)
{
variant.mData = nullptr;
}
ESM::VarType ESM::Variant::getType() const ESM::VarType ESM::Variant::getType() const
{ {
return mType; return mType;

@ -46,8 +46,10 @@ namespace ESM
~Variant(); ~Variant();
Variant& operator= (const Variant& variant); Variant& operator= (const Variant& variant);
Variant& operator= (Variant && variant);
Variant (const Variant& variant); Variant (const Variant& variant);
Variant (Variant&& variant);
VarType getType() const; VarType getType() const;

@ -0,0 +1,12 @@
#ifndef OPENMW_COMPONENTS_MYGUIPLATFORM_MYGUICOMPAT_H
#define OPENMW_COMPONENTS_MYGUIPLATFORM_MYGUICOMPAT_H
#include <MyGUI_Prerequest.h>
#if MYGUI_VERSION > MYGUI_DEFINE_VERSION(3, 4, 0)
#define OPENMW_MYGUI_CONST_GETTER_3_4_1 const
#else
#define OPENMW_MYGUI_CONST_GETTER_3_4_1
#endif
#endif // OPENMW_COMPONENTS_MYGUIPLATFORM_MYGUICOMPAT_H

@ -15,7 +15,7 @@ void DataManager::setResourcePath(const std::string &path)
mResourcePath = path; mResourcePath = path;
} }
MyGUI::IDataStream *DataManager::getData(const std::string &name) MyGUI::IDataStream *DataManager::getData(const std::string &name) OPENMW_MYGUI_CONST_GETTER_3_4_1
{ {
std::string fullpath = getDataPath(name); std::string fullpath = getDataPath(name);
std::unique_ptr<boost::filesystem::ifstream> stream; std::unique_ptr<boost::filesystem::ifstream> stream;
@ -34,13 +34,13 @@ void DataManager::freeData(MyGUI::IDataStream *data)
delete data; delete data;
} }
bool DataManager::isDataExist(const std::string &name) bool DataManager::isDataExist(const std::string &name) OPENMW_MYGUI_CONST_GETTER_3_4_1
{ {
std::string fullpath = mResourcePath + "/" + name; std::string fullpath = mResourcePath + "/" + name;
return boost::filesystem::exists(fullpath); return boost::filesystem::exists(fullpath);
} }
const MyGUI::VectorString &DataManager::getDataListNames(const std::string &pattern) const MyGUI::VectorString &DataManager::getDataListNames(const std::string &pattern) OPENMW_MYGUI_CONST_GETTER_3_4_1
{ {
// TODO: pattern matching (unused?) // TODO: pattern matching (unused?)
static MyGUI::VectorString strings; static MyGUI::VectorString strings;
@ -49,7 +49,7 @@ const MyGUI::VectorString &DataManager::getDataListNames(const std::string &patt
return strings; return strings;
} }
const std::string &DataManager::getDataPath(const std::string &name) const std::string &DataManager::getDataPath(const std::string &name) OPENMW_MYGUI_CONST_GETTER_3_4_1
{ {
static std::string result; static std::string result;
result.clear(); result.clear();

@ -3,10 +3,11 @@
#include <MyGUI_DataManager.h> #include <MyGUI_DataManager.h>
#include "myguicompat.h"
namespace osgMyGUI namespace osgMyGUI
{ {
class DataManager : public MyGUI::DataManager class DataManager : public MyGUI::DataManager
{ {
public: public:
@ -18,7 +19,7 @@ public:
/** Get data stream from specified resource name. /** Get data stream from specified resource name.
@param _name Resource name (usually file name). @param _name Resource name (usually file name).
*/ */
MyGUI::IDataStream* getData(const std::string& _name) override; MyGUI::IDataStream* getData(const std::string& _name) OPENMW_MYGUI_CONST_GETTER_3_4_1 override;
/** Free data stream. /** Free data stream.
@param _data Data stream. @param _data Data stream.
@ -28,18 +29,18 @@ public:
/** Is data with specified name exist. /** Is data with specified name exist.
@param _name Resource name. @param _name Resource name.
*/ */
bool isDataExist(const std::string& _name) override; bool isDataExist(const std::string& _name) OPENMW_MYGUI_CONST_GETTER_3_4_1 override;
/** Get all data names with names that matches pattern. /** Get all data names with names that matches pattern.
@param _pattern Pattern to match (for example "*.layout"). @param _pattern Pattern to match (for example "*.layout").
*/ */
const MyGUI::VectorString& getDataListNames(const std::string& _pattern) override; const MyGUI::VectorString& getDataListNames(const std::string& _pattern) OPENMW_MYGUI_CONST_GETTER_3_4_1 override;
/** Get full path to data. /** Get full path to data.
@param _name Resource name. @param _name Resource name.
@return Return full path to specified data. @return Return full path to specified data.
*/ */
const std::string& getDataPath(const std::string& _name) override; const std::string& getDataPath(const std::string& _name) OPENMW_MYGUI_CONST_GETTER_3_4_1 override;
private: private:
std::string mResourcePath; std::string mResourcePath;

@ -2,11 +2,15 @@
#include <iomanip> #include <iomanip>
#include <components/debug/debuglog.hpp>
namespace osgMyGUI namespace osgMyGUI
{ {
void CustomLogListener::open() void CustomLogListener::open()
{ {
mStream.open(boost::filesystem::path(mFileName), std::ios_base::out); mStream.open(boost::filesystem::path(mFileName), std::ios_base::out);
if (!mStream.is_open())
Log(Debug::Error) << "Unable to create MyGUI log with path " << mFileName;
} }
void CustomLogListener::close() void CustomLogListener::close()

@ -7,6 +7,7 @@
#include <osg/BlendFunc> #include <osg/BlendFunc>
#include <osg/Texture2D> #include <osg/Texture2D>
#include <osg/TexMat> #include <osg/TexMat>
#include <osg/ValueObject>
#include <osgViewer/Viewer> #include <osgViewer/Viewer>
@ -14,6 +15,9 @@
#include <components/resource/imagemanager.hpp> #include <components/resource/imagemanager.hpp>
#include <components/debug/debuglog.hpp>
#include "myguicompat.h"
#include "myguitexture.hpp" #include "myguitexture.hpp"
#define MYGUI_PLATFORM_LOG_SECTION "Platform" #define MYGUI_PLATFORM_LOG_SECTION "Platform"
@ -263,7 +267,7 @@ public:
osg::VertexBufferObject* getVertexBuffer(); osg::VertexBufferObject* getVertexBuffer();
void setVertexCount(size_t count) override; void setVertexCount(size_t count) override;
size_t getVertexCount() override; size_t getVertexCount() OPENMW_MYGUI_CONST_GETTER_3_4_1 override;
MyGUI::Vertex *lock() override; MyGUI::Vertex *lock() override;
void unlock() override; void unlock() override;
@ -290,7 +294,7 @@ void OSGVertexBuffer::setVertexCount(size_t count)
mNeedVertexCount = count; mNeedVertexCount = count;
} }
size_t OSGVertexBuffer::getVertexCount() size_t OSGVertexBuffer::getVertexCount() OPENMW_MYGUI_CONST_GETTER_3_4_1
{ {
return mNeedVertexCount; return mNeedVertexCount;
} }
@ -438,14 +442,23 @@ void RenderManager::doRender(MyGUI::IVertexBuffer *buffer, MyGUI::ITexture *text
batch.mVertexBuffer = static_cast<OSGVertexBuffer*>(buffer)->getVertexBuffer(); batch.mVertexBuffer = static_cast<OSGVertexBuffer*>(buffer)->getVertexBuffer();
batch.mArray = static_cast<OSGVertexBuffer*>(buffer)->getVertexArray(); batch.mArray = static_cast<OSGVertexBuffer*>(buffer)->getVertexArray();
static_cast<OSGVertexBuffer*>(buffer)->markUsed(); static_cast<OSGVertexBuffer*>(buffer)->markUsed();
bool premultipliedAlpha = false;
if (texture) if (texture)
{ {
batch.mTexture = static_cast<OSGTexture*>(texture)->getTexture(); batch.mTexture = static_cast<OSGTexture*>(texture)->getTexture();
if (batch.mTexture->getDataVariance() == osg::Object::DYNAMIC) if (batch.mTexture->getDataVariance() == osg::Object::DYNAMIC)
mDrawable->setDataVariance(osg::Object::DYNAMIC); // only for this frame, reset in begin() mDrawable->setDataVariance(osg::Object::DYNAMIC); // only for this frame, reset in begin()
batch.mTexture->getUserValue("premultiplied alpha", premultipliedAlpha);
} }
if (mInjectState) if (mInjectState)
batch.mStateSet = mInjectState; batch.mStateSet = mInjectState;
else if (premultipliedAlpha)
{
// This is hacky, but MyGUI made it impossible to use a custom layer for a nested node, so state couldn't be injected 'properly'
osg::ref_ptr<osg::StateSet> stateSet = new osg::StateSet();
stateSet->setAttribute(new osg::BlendFunc(osg::BlendFunc::ONE, osg::BlendFunc::ONE_MINUS_SRC_ALPHA));
batch.mStateSet = stateSet;
}
mDrawable->addBatch(batch); mDrawable->addBatch(batch);
} }
@ -560,4 +573,14 @@ bool RenderManager::checkTexture(MyGUI::ITexture* _texture)
return true; return true;
} }
#if MYGUI_VERSION > MYGUI_DEFINE_VERSION(3, 4, 0)
void RenderManager::registerShader(
const std::string& _shaderName,
const std::string& _vertexProgramFile,
const std::string& _fragmentProgramFile)
{
MYGUI_PLATFORM_LOG(Warning, "osgMyGUI::RenderManager::registerShader is not implemented");
}
#endif
} }

@ -5,6 +5,8 @@
#include <osg/ref_ptr> #include <osg/ref_ptr>
#include "myguicompat.h"
namespace Resource namespace Resource
{ {
class ImageManager; class ImageManager;
@ -70,7 +72,8 @@ public:
const MyGUI::IntSize& getViewSize() const override { return mViewSize; } const MyGUI::IntSize& getViewSize() const override { return mViewSize; }
/** @see RenderManager::getVertexFormat */ /** @see RenderManager::getVertexFormat */
MyGUI::VertexColourType getVertexFormat() override { return mVertexFormat; } MyGUI::VertexColourType getVertexFormat() OPENMW_MYGUI_CONST_GETTER_3_4_1 override
{ return mVertexFormat; }
/** @see RenderManager::isFormatSupported */ /** @see RenderManager::isFormatSupported */
bool isFormatSupported(MyGUI::PixelFormat format, MyGUI::TextureUsage usage) override; bool isFormatSupported(MyGUI::PixelFormat format, MyGUI::TextureUsage usage) override;
@ -102,17 +105,22 @@ public:
void setInjectState(osg::StateSet* stateSet); void setInjectState(osg::StateSet* stateSet);
/** @see IRenderTarget::getInfo */ /** @see IRenderTarget::getInfo */
const MyGUI::RenderTargetInfo& getInfo() override { return mInfo; } const MyGUI::RenderTargetInfo& getInfo() OPENMW_MYGUI_CONST_GETTER_3_4_1 override { return mInfo; }
bool checkTexture(MyGUI::ITexture* _texture); bool checkTexture(MyGUI::ITexture* _texture);
// setViewSize() is a part of MyGUI::RenderManager interface since 3.4.0 release // setViewSize() is a part of MyGUI::RenderManager interface since 3.4.0 release
#if MYGUI_VERSION < MYGUI_DEFINE_VERSION(3,4,0) #if MYGUI_VERSION < MYGUI_DEFINE_VERSION(3, 4, 0)
void setViewSize(int width, int height); void setViewSize(int width, int height);
#else #else
void setViewSize(int width, int height) override; void setViewSize(int width, int height) override;
#endif #endif
// registerShader() is a part of MyGUI::RenderManager interface since 3.4.1 release
#if MYGUI_VERSION > MYGUI_DEFINE_VERSION(3, 4, 0)
void registerShader(const std::string& _shaderName, const std::string& _vertexProgramFile, const std::string& _fragmentProgramFile) override;
#endif
/*internal:*/ /*internal:*/
void collectDrawCalls(); void collectDrawCalls();

@ -115,16 +115,6 @@ namespace osgMyGUI
Log(Debug::Warning) << "Would save image to file " << fname; Log(Debug::Warning) << "Would save image to file " << fname;
} }
int OSGTexture::getWidth()
{
return mWidth;
}
int OSGTexture::getHeight()
{
return mHeight;
}
void *OSGTexture::lock(MyGUI::TextureUsage /*access*/) void *OSGTexture::lock(MyGUI::TextureUsage /*access*/)
{ {
if (!mTexture.valid()) if (!mTexture.valid())
@ -167,15 +157,14 @@ namespace osgMyGUI
mLockedImage = nullptr; mLockedImage = nullptr;
} }
bool OSGTexture::isLocked()
{
return mLockedImage.valid();
}
// Render-to-texture not currently implemented. // Render-to-texture not currently implemented.
MyGUI::IRenderTarget* OSGTexture::getRenderTarget() MyGUI::IRenderTarget* OSGTexture::getRenderTarget()
{ {
return nullptr; return nullptr;
} }
#if MYGUI_VERSION > MYGUI_DEFINE_VERSION(3, 4, 0)
void OSGTexture::setShader(const std::string& _shaderName)
{ Log(Debug::Warning) << "OSGTexture::setShader is not implemented"; }
#endif
} }

@ -5,6 +5,12 @@
#include <osg/ref_ptr> #include <osg/ref_ptr>
#if MYGUI_VERSION > MYGUI_DEFINE_VERSION(3, 4, 0)
#define OPENMW_MYGUI_CONST_GETTER_3_4_1 const
#else
#define OPENMW_MYGUI_CONST_GETTER_3_4_1
#endif
namespace osg namespace osg
{ {
class Image; class Image;
@ -47,17 +53,22 @@ namespace osgMyGUI
void* lock(MyGUI::TextureUsage access) override; void* lock(MyGUI::TextureUsage access) override;
void unlock() override; void unlock() override;
bool isLocked() override; bool isLocked() OPENMW_MYGUI_CONST_GETTER_3_4_1 override { return mLockedImage.valid(); }
int getWidth() override; int getWidth() OPENMW_MYGUI_CONST_GETTER_3_4_1 override { return mWidth; }
int getHeight() override; int getHeight() OPENMW_MYGUI_CONST_GETTER_3_4_1 override { return mHeight; }
MyGUI::PixelFormat getFormat() override { return mFormat; } MyGUI::PixelFormat getFormat() OPENMW_MYGUI_CONST_GETTER_3_4_1 override { return mFormat; }
MyGUI::TextureUsage getUsage() override { return mUsage; } MyGUI::TextureUsage getUsage() OPENMW_MYGUI_CONST_GETTER_3_4_1 override { return mUsage; }
size_t getNumElemBytes() override { return mNumElemBytes; } size_t getNumElemBytes() OPENMW_MYGUI_CONST_GETTER_3_4_1 override { return mNumElemBytes; }
MyGUI::IRenderTarget *getRenderTarget() override; MyGUI::IRenderTarget *getRenderTarget() override;
// setShader() is a part of MyGUI::RenderManager interface since 3.4.1 release
#if MYGUI_VERSION > MYGUI_DEFINE_VERSION(3, 4, 0)
void setShader(const std::string& _shaderName) override;
#endif
/*internal:*/ /*internal:*/
osg::Texture2D *getTexture() const { return mTexture.get(); } osg::Texture2D *getTexture() const { return mTexture.get(); }
}; };

@ -3,6 +3,8 @@
#include <MyGUI_RenderManager.h> #include <MyGUI_RenderManager.h>
#include <algorithm> #include <algorithm>
#include "myguicompat.h"
namespace osgMyGUI namespace osgMyGUI
{ {
@ -37,7 +39,7 @@ namespace osgMyGUI
mTarget->doRender(_buffer, _texture, _count); mTarget->doRender(_buffer, _texture, _count);
} }
const MyGUI::RenderTargetInfo& getInfo() override const MyGUI::RenderTargetInfo& getInfo() OPENMW_MYGUI_CONST_GETTER_3_4_1 override
{ {
mInfo = mTarget->getInfo(); mInfo = mTarget->getInfo();
mInfo.hOffset = mHOffset; mInfo.hOffset = mHOffset;
@ -51,7 +53,7 @@ namespace osgMyGUI
MyGUI::IRenderTarget* mTarget; MyGUI::IRenderTarget* mTarget;
MyGUI::IntSize mViewSize; MyGUI::IntSize mViewSize;
float mHOffset, mVOffset; float mHOffset, mVOffset;
MyGUI::RenderTargetInfo mInfo; mutable MyGUI::RenderTargetInfo mInfo;
}; };
MyGUI::ILayerItem *ScalingLayer::getLayerItemByPoint(int _left, int _top) const MyGUI::ILayerItem *ScalingLayer::getLayerItemByPoint(int _left, int _top) const

@ -77,7 +77,7 @@ struct KeyMapT {
mInterpolationType = nif->getUInt(); mInterpolationType = nif->getUInt();
KeyT<T> key; KeyType key = {};
NIFStream &nifReference = *nif; NIFStream &nifReference = *nif;
if (mInterpolationType == InterpolationType_Linear if (mInterpolationType == InterpolationType_Linear

@ -1725,11 +1725,16 @@ namespace NifOsg
case Nif::RC_NiZBufferProperty: case Nif::RC_NiZBufferProperty:
{ {
const Nif::NiZBufferProperty* zprop = static_cast<const Nif::NiZBufferProperty*>(property); const Nif::NiZBufferProperty* zprop = static_cast<const Nif::NiZBufferProperty*>(property);
// VER_MW doesn't support a DepthFunction according to NifSkope osg::StateSet* stateset = node->getOrCreateStateSet();
// Depth test flag
stateset->setMode(GL_DEPTH_TEST, zprop->flags&1 ? osg::StateAttribute::ON
: osg::StateAttribute::OFF);
osg::ref_ptr<osg::Depth> depth = new osg::Depth; osg::ref_ptr<osg::Depth> depth = new osg::Depth;
// Depth write flag
depth->setWriteMask((zprop->flags>>1)&1); depth->setWriteMask((zprop->flags>>1)&1);
// Morrowind ignores depth test function
depth = shareAttribute(depth); depth = shareAttribute(depth);
node->getOrCreateStateSet()->setAttributeAndModes(depth, osg::StateAttribute::ON); stateset->setAttributeAndModes(depth, osg::StateAttribute::ON);
break; break;
} }
// OSG groups the material properties that NIFs have separate, so we have to parse them all again when one changed // OSG groups the material properties that NIFs have separate, so we have to parse them all again when one changed

@ -257,6 +257,12 @@ namespace Resource
node->accept(*shaderVisitor); node->accept(*shaderVisitor);
} }
void SceneManager::reinstateRemovedState(osg::ref_ptr<osg::Node> node)
{
osg::ref_ptr<Shader::ReinstateRemovedStateVisitor> reinstateRemovedStateVisitor = new Shader::ReinstateRemovedStateVisitor(false);
node->accept(*reinstateRemovedStateVisitor);
}
void SceneManager::setClampLighting(bool clamp) void SceneManager::setClampLighting(bool clamp)
{ {
mClampLighting = clamp; mClampLighting = clamp;
@ -297,6 +303,11 @@ namespace Resource
mApplyLightingToEnvMaps = apply; mApplyLightingToEnvMaps = apply;
} }
void SceneManager::setConvertAlphaTestToAlphaToCoverage(bool convert)
{
mConvertAlphaTestToAlphaToCoverage = convert;
}
SceneManager::~SceneManager() SceneManager::~SceneManager()
{ {
// this has to be defined in the .cpp file as we can't delete incomplete types // this has to be defined in the .cpp file as we can't delete incomplete types
@ -771,6 +782,7 @@ namespace Resource
shaderVisitor->setAutoUseSpecularMaps(mAutoUseSpecularMaps); shaderVisitor->setAutoUseSpecularMaps(mAutoUseSpecularMaps);
shaderVisitor->setSpecularMapPattern(mSpecularMapPattern); shaderVisitor->setSpecularMapPattern(mSpecularMapPattern);
shaderVisitor->setApplyLightingToEnvMaps(mApplyLightingToEnvMaps); shaderVisitor->setApplyLightingToEnvMaps(mApplyLightingToEnvMaps);
shaderVisitor->setConvertAlphaTestToAlphaToCoverage(mConvertAlphaTestToAlphaToCoverage);
shaderVisitor->setTranslucentFramebuffer(translucentFramebuffer); shaderVisitor->setTranslucentFramebuffer(translucentFramebuffer);
return shaderVisitor; return shaderVisitor;
} }

@ -75,9 +75,14 @@ namespace Resource
Shader::ShaderManager& getShaderManager(); Shader::ShaderManager& getShaderManager();
/// Re-create shaders for this node, need to call this if texture stages or vertex color mode have changed. /// Re-create shaders for this node, need to call this if alpha testing, texture stages or vertex color mode have changed.
void recreateShaders(osg::ref_ptr<osg::Node> node, const std::string& shaderPrefix = "objects", bool translucentFramebuffer = false, bool forceShadersForNode = false); void recreateShaders(osg::ref_ptr<osg::Node> node, const std::string& shaderPrefix = "objects", bool translucentFramebuffer = false, bool forceShadersForNode = false);
/// Applying shaders to a node may replace some fixed-function state.
/// This restores it.
/// When editing such state, it should be reinstated before the edits, and shaders should be recreated afterwards.
void reinstateRemovedState(osg::ref_ptr<osg::Node> node);
/// @see ShaderVisitor::setForceShaders /// @see ShaderVisitor::setForceShaders
void setForceShaders(bool force); void setForceShaders(bool force);
bool getForceShaders() const; bool getForceShaders() const;
@ -100,6 +105,8 @@ namespace Resource
void setApplyLightingToEnvMaps(bool apply); void setApplyLightingToEnvMaps(bool apply);
void setConvertAlphaTestToAlphaToCoverage(bool convert);
void setShaderPath(const std::string& path); void setShaderPath(const std::string& path);
/// Check if a given scene is loaded and if so, update its usage timestamp to prevent it from being unloaded /// Check if a given scene is loaded and if so, update its usage timestamp to prevent it from being unloaded
@ -184,6 +191,7 @@ namespace Resource
bool mAutoUseSpecularMaps; bool mAutoUseSpecularMaps;
std::string mSpecularMapPattern; std::string mSpecularMapPattern;
bool mApplyLightingToEnvMaps; bool mApplyLightingToEnvMaps;
bool mConvertAlphaTestToAlphaToCoverage;
osg::ref_ptr<MultiObjectCache> mInstanceCache; osg::ref_ptr<MultiObjectCache> mInstanceCache;

@ -278,7 +278,7 @@ void VDSMCameraCullCallback::operator()(osg::Node* node, osg::NodeVisitor* nv)
static osg::ref_ptr<osg::StateSet> ss; static osg::ref_ptr<osg::StateSet> ss;
if (!ss) if (!ss)
{ {
ShadowsBinAdder adder("ShadowsBin"); ShadowsBinAdder adder("ShadowsBin", _vdsm->getCastingPrograms());
ss = new osg::StateSet; ss = new osg::StateSet;
ss->setRenderBinDetails(osg::StateSet::OPAQUE_BIN, "ShadowsBin", osg::StateSet::OVERRIDE_PROTECTED_RENDERBIN_DETAILS); ss->setRenderBinDetails(osg::StateSet::OPAQUE_BIN, "ShadowsBin", osg::StateSet::OVERRIDE_PROTECTED_RENDERBIN_DETAILS);
} }
@ -782,7 +782,8 @@ void MWShadowTechnique::ViewDependentData::releaseGLObjects(osg::State* state) c
MWShadowTechnique::MWShadowTechnique(): MWShadowTechnique::MWShadowTechnique():
ShadowTechnique(), ShadowTechnique(),
_enableShadows(false), _enableShadows(false),
_debugHud(nullptr) _debugHud(nullptr),
_castingPrograms{ nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr }
{ {
_shadowRecievingPlaceholderStateSet = new osg::StateSet; _shadowRecievingPlaceholderStateSet = new osg::StateSet;
mSetDummyStateWhenDisabled = false; mSetDummyStateWhenDisabled = false;
@ -790,6 +791,7 @@ MWShadowTechnique::MWShadowTechnique():
MWShadowTechnique::MWShadowTechnique(const MWShadowTechnique& vdsm, const osg::CopyOp& copyop): MWShadowTechnique::MWShadowTechnique(const MWShadowTechnique& vdsm, const osg::CopyOp& copyop):
ShadowTechnique(vdsm,copyop) ShadowTechnique(vdsm,copyop)
, _castingPrograms(vdsm._castingPrograms)
{ {
_shadowRecievingPlaceholderStateSet = new osg::StateSet; _shadowRecievingPlaceholderStateSet = new osg::StateSet;
_enableShadows = vdsm._enableShadows; _enableShadows = vdsm._enableShadows;
@ -870,7 +872,10 @@ void SceneUtil::MWShadowTechnique::enableFrontFaceCulling()
_useFrontFaceCulling = true; _useFrontFaceCulling = true;
if (_shadowCastingStateSet) if (_shadowCastingStateSet)
{
_shadowCastingStateSet->setAttribute(new osg::CullFace(osg::CullFace::FRONT), osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); _shadowCastingStateSet->setAttribute(new osg::CullFace(osg::CullFace::FRONT), osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE);
_shadowCastingStateSet->setMode(GL_CULL_FACE, osg::StateAttribute::OFF);
}
} }
void SceneUtil::MWShadowTechnique::disableFrontFaceCulling() void SceneUtil::MWShadowTechnique::disableFrontFaceCulling()
@ -878,17 +883,29 @@ void SceneUtil::MWShadowTechnique::disableFrontFaceCulling()
_useFrontFaceCulling = false; _useFrontFaceCulling = false;
if (_shadowCastingStateSet) if (_shadowCastingStateSet)
{
_shadowCastingStateSet->removeAttribute(osg::StateAttribute::CULLFACE);
_shadowCastingStateSet->setMode(GL_CULL_FACE, osg::StateAttribute::OFF | osg::StateAttribute::OVERRIDE); _shadowCastingStateSet->setMode(GL_CULL_FACE, osg::StateAttribute::OFF | osg::StateAttribute::OVERRIDE);
}
} }
void SceneUtil::MWShadowTechnique::setupCastingShader(Shader::ShaderManager & shaderManager) void SceneUtil::MWShadowTechnique::setupCastingShader(Shader::ShaderManager & shaderManager)
{ {
// This can't be part of the constructor as OSG mandates that there be a trivial constructor available // This can't be part of the constructor as OSG mandates that there be a trivial constructor available
_castingProgram = new osg::Program();
_castingProgram->addShader(shaderManager.getShader("shadowcasting_vertex.glsl", Shader::ShaderManager::DefineMap(), osg::Shader::VERTEX)); osg::ref_ptr<osg::Shader> castingVertexShader = shaderManager.getShader("shadowcasting_vertex.glsl", {}, osg::Shader::VERTEX);
_castingProgram->addShader(shaderManager.getShader("shadowcasting_fragment.glsl", Shader::ShaderManager::DefineMap(), osg::Shader::FRAGMENT)); osg::ref_ptr<osg::GLExtensions> exts = osg::GLExtensions::Get(0, false);
std::string useGPUShader4 = exts && exts->isGpuShader4Supported ? "1" : "0";
for (int alphaFunc = GL_NEVER; alphaFunc <= GL_ALWAYS; ++alphaFunc)
{
auto& program = _castingPrograms[alphaFunc - GL_NEVER];
program = new osg::Program();
program->addShader(castingVertexShader);
program->addShader(shaderManager.getShader("shadowcasting_fragment.glsl", { {"alphaFunc", std::to_string(alphaFunc)},
{"alphaToCoverage", "0"},
{"useGPUShader4", useGPUShader4}
}, osg::Shader::FRAGMENT));
}
} }
MWShadowTechnique::ViewDependentData* MWShadowTechnique::createViewDependentData(osgUtil::CullVisitor* /*cv*/) MWShadowTechnique::ViewDependentData* MWShadowTechnique::createViewDependentData(osgUtil::CullVisitor* /*cv*/)
@ -1606,10 +1623,11 @@ void MWShadowTechnique::createShaders()
} }
if (!_castingProgram) if (!_castingPrograms[GL_ALWAYS - GL_NEVER])
OSG_NOTICE << "Shadow casting shader has not been set up. Remember to call setupCastingShader(Shader::ShaderManager &)" << std::endl; OSG_NOTICE << "Shadow casting shader has not been set up. Remember to call setupCastingShader(Shader::ShaderManager &)" << std::endl;
_shadowCastingStateSet->setAttributeAndModes(_castingProgram, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); // Always use the GL_ALWAYS shader as the shadows bin will change it if necessary
_shadowCastingStateSet->setAttributeAndModes(_castingPrograms[GL_ALWAYS - GL_NEVER], osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE);
// The casting program uses a sampler, so to avoid undefined behaviour, we must bind a dummy texture in case no other is supplied // The casting program uses a sampler, so to avoid undefined behaviour, we must bind a dummy texture in case no other is supplied
_shadowCastingStateSet->setTextureAttributeAndModes(0, _fallbackBaseTexture.get(), osg::StateAttribute::ON); _shadowCastingStateSet->setTextureAttributeAndModes(0, _fallbackBaseTexture.get(), osg::StateAttribute::ON);
_shadowCastingStateSet->addUniform(new osg::Uniform("useDiffuseMapForShadowAlpha", true)); _shadowCastingStateSet->addUniform(new osg::Uniform("useDiffuseMapForShadowAlpha", true));

@ -215,6 +215,8 @@ namespace SceneUtil {
virtual void createShaders(); virtual void createShaders();
virtual std::array<osg::ref_ptr<osg::Program>, GL_ALWAYS - GL_NEVER + 1> getCastingPrograms() const { return _castingPrograms; }
virtual bool selectActiveLights(osgUtil::CullVisitor* cv, ViewDependentData* vdd) const; virtual bool selectActiveLights(osgUtil::CullVisitor* cv, ViewDependentData* vdd) const;
virtual osg::Polytope computeLightViewFrustumPolytope(Frustum& frustum, LightData& positionedLight); virtual osg::Polytope computeLightViewFrustumPolytope(Frustum& frustum, LightData& positionedLight);
@ -288,7 +290,7 @@ namespace SceneUtil {
}; };
osg::ref_ptr<DebugHUD> _debugHud; osg::ref_ptr<DebugHUD> _debugHud;
osg::ref_ptr<osg::Program> _castingProgram; std::array<osg::ref_ptr<osg::Program>, GL_ALWAYS - GL_NEVER + 1> _castingPrograms;
}; };
} }

@ -1,7 +1,9 @@
#include "shadowsbin.hpp" #include "shadowsbin.hpp"
#include <unordered_set> #include <unordered_set>
#include <osg/StateSet> #include <osg/StateSet>
#include <osg/AlphaFunc>
#include <osg/Material> #include <osg/Material>
#include <osg/Program>
#include <osgUtil/StateGraph> #include <osgUtil/StateGraph>
using namespace osgUtil; using namespace osgUtil;
@ -25,9 +27,9 @@ namespace
osg::StateSet::ModeList::const_iterator mf = l.find(mode); osg::StateSet::ModeList::const_iterator mf = l.find(mode);
if (mf == l.end()) if (mf == l.end())
return; return;
int flags = mf->second; unsigned int flags = mf->second;
bool newValue = flags & osg::StateAttribute::ON; bool newValue = flags & osg::StateAttribute::ON;
accumulateState(currentValue, newValue, isOverride, ss->getMode(mode)); accumulateState(currentValue, newValue, isOverride, flags);
} }
inline bool materialNeedShadows(osg::Material* m) inline bool materialNeedShadows(osg::Material* m)
@ -40,6 +42,10 @@ namespace
namespace SceneUtil namespace SceneUtil
{ {
std::array<osg::ref_ptr<osg::Program>, GL_ALWAYS - GL_NEVER + 1> ShadowsBin::sCastingPrograms = {
nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr
};
ShadowsBin::ShadowsBin() ShadowsBin::ShadowsBin()
{ {
mNoTestStateSet = new osg::StateSet; mNoTestStateSet = new osg::StateSet;
@ -48,10 +54,16 @@ ShadowsBin::ShadowsBin()
mShaderAlphaTestStateSet = new osg::StateSet; mShaderAlphaTestStateSet = new osg::StateSet;
mShaderAlphaTestStateSet->addUniform(new osg::Uniform("alphaTestShadows", true)); mShaderAlphaTestStateSet->addUniform(new osg::Uniform("alphaTestShadows", true));
mShaderAlphaTestStateSet->setMode(GL_BLEND, osg::StateAttribute::OFF | osg::StateAttribute::OVERRIDE); mShaderAlphaTestStateSet->setMode(GL_BLEND, osg::StateAttribute::OFF | osg::StateAttribute::PROTECTED | osg::StateAttribute::OVERRIDE);
for (size_t i = 0; i < sCastingPrograms.size(); ++i)
{
mAlphaFuncShaders[i] = new osg::StateSet;
mAlphaFuncShaders[i]->setAttribute(sCastingPrograms[i], osg::StateAttribute::ON | osg::StateAttribute::PROTECTED | osg::StateAttribute::OVERRIDE);
}
} }
StateGraph* ShadowsBin::cullStateGraph(StateGraph* sg, StateGraph* root, std::unordered_set<StateGraph*>& uninterestingCache) StateGraph* ShadowsBin::cullStateGraph(StateGraph* sg, StateGraph* root, std::unordered_set<StateGraph*>& uninterestingCache, bool cullFaceOverridden)
{ {
std::vector<StateGraph*> return_path; std::vector<StateGraph*> return_path;
State state; State state;
@ -71,7 +83,6 @@ StateGraph* ShadowsBin::cullStateGraph(StateGraph* sg, StateGraph* root, std::un
continue; continue;
accumulateModeState(ss, state.mAlphaBlend, state.mAlphaBlendOverride, GL_BLEND); accumulateModeState(ss, state.mAlphaBlend, state.mAlphaBlendOverride, GL_BLEND);
accumulateModeState(ss, state.mAlphaTest, state.mAlphaTestOverride, GL_ALPHA_TEST);
const osg::StateSet::AttributeList& attributes = ss->getAttributeList(); const osg::StateSet::AttributeList& attributes = ss->getAttributeList();
osg::StateSet::AttributeList::const_iterator found = attributes.find(std::make_pair(osg::StateAttribute::MATERIAL, 0)); osg::StateSet::AttributeList::const_iterator found = attributes.find(std::make_pair(osg::StateAttribute::MATERIAL, 0));
@ -83,10 +94,21 @@ StateGraph* ShadowsBin::cullStateGraph(StateGraph* sg, StateGraph* root, std::un
state.mMaterial = nullptr; state.mMaterial = nullptr;
} }
// osg::FrontFace specifies triangle winding, not front-face culling. We can't safely reparent anything under it. found = attributes.find(std::make_pair(osg::StateAttribute::ALPHAFUNC, 0));
found = attributes.find(std::make_pair(osg::StateAttribute::FRONTFACE, 0));
if (found != attributes.end()) if (found != attributes.end())
state.mImportantState = true; {
// As force shaders is on, we know this is really a RemovedAlphaFunc
const osg::StateSet::RefAttributePair& rap = found->second;
accumulateState(state.mAlphaFunc, static_cast<osg::AlphaFunc*>(rap.first.get()), state.mAlphaFuncOverride, rap.second);
}
if (!cullFaceOverridden)
{
// osg::FrontFace specifies triangle winding, not front-face culling. We can't safely reparent anything under it unless GL_CULL_FACE is off or we flip face culling.
found = attributes.find(std::make_pair(osg::StateAttribute::FRONTFACE, 0));
if (found != attributes.end())
state.mImportantState = true;
}
if ((*itr) != sg && !state.interesting()) if ((*itr) != sg && !state.interesting())
uninterestingCache.insert(*itr); uninterestingCache.insert(*itr);
@ -108,21 +130,45 @@ StateGraph* ShadowsBin::cullStateGraph(StateGraph* sg, StateGraph* root, std::un
if (state.mAlphaBlend) if (state.mAlphaBlend)
{ {
sg_new = sg->find_or_insert(mShaderAlphaTestStateSet); sg_new = sg->find_or_insert(mShaderAlphaTestStateSet);
for (RenderLeaf* leaf : sg->_leaves) sg_new->_leaves = std::move(sg->_leaves);
{ for (RenderLeaf* leaf : sg_new->_leaves)
leaf->_parent = sg_new; leaf->_parent = sg_new;
sg_new->_leaves.push_back(leaf); sg = sg_new;
} }
return sg_new;
// GL_ALWAYS is set by default by mwshadowtechnique
if (state.mAlphaFunc && state.mAlphaFunc->getFunction() != GL_ALWAYS)
{
sg_new = sg->find_or_insert(mAlphaFuncShaders[state.mAlphaFunc->getFunction() - GL_NEVER]);
sg_new->_leaves = std::move(sg->_leaves);
for (RenderLeaf* leaf : sg_new->_leaves)
leaf->_parent = sg_new;
sg = sg_new;
} }
return sg; return sg;
} }
void ShadowsBin::addPrototype(const std::string & name, const std::array<osg::ref_ptr<osg::Program>, GL_ALWAYS - GL_NEVER + 1>& castingPrograms)
{
sCastingPrograms = castingPrograms;
osg::ref_ptr<osgUtil::RenderBin> bin(new ShadowsBin);
osgUtil::RenderBin::addRenderBinPrototype(name, bin);
}
inline bool ShadowsBin::State::needTexture() const
{
return mAlphaBlend || (mAlphaFunc && mAlphaFunc->getFunction() != GL_ALWAYS);
}
bool ShadowsBin::State::needShadows() const bool ShadowsBin::State::needShadows() const
{ {
if (!mMaterial) if (mAlphaFunc && mAlphaFunc->getFunction() == GL_NEVER)
return true; return false;
return materialNeedShadows(mMaterial); // other alpha func + material combinations might be skippable
if (mAlphaBlend && mMaterial)
return materialNeedShadows(mMaterial);
return true;
} }
void ShadowsBin::sortImplementation() void ShadowsBin::sortImplementation()
@ -139,13 +185,27 @@ void ShadowsBin::sortImplementation()
root = root->_parent; root = root->_parent;
const osg::StateSet* ss = root->getStateSet(); const osg::StateSet* ss = root->getStateSet();
if (ss->getMode(GL_NORMALIZE) & osg::StateAttribute::ON // that is root stategraph of renderingmanager cpp if (ss->getMode(GL_NORMALIZE) & osg::StateAttribute::ON // that is root stategraph of renderingmanager cpp
|| ss->getAttribute(osg::StateAttribute::VIEWPORT)) // fallback to rendertargets sg just in case || ss->getAttribute(osg::StateAttribute::VIEWPORT)) // fallback to rendertarget's sg just in case
break; break;
if (!root->_parent) if (!root->_parent)
return; return;
} }
StateGraph* noTestRoot = root->find_or_insert(mNoTestStateSet.get()); StateGraph* noTestRoot = root->find_or_insert(mNoTestStateSet.get());
// root is now a stategraph with useDiffuseMapForShadowAlpha disabled but minimal other state // noTestRoot is now a stategraph with useDiffuseMapForShadowAlpha disabled but minimal other state
bool cullFaceOverridden = false;
while ((root = root->_parent))
{
if (!root->getStateSet())
continue;
unsigned int cullFaceFlags = root->getStateSet()->getMode(GL_CULL_FACE);
if (cullFaceFlags & osg::StateAttribute::OVERRIDE && !(cullFaceFlags & osg::StateAttribute::ON))
{
cullFaceOverridden = true;
break;
}
}
noTestRoot->_leaves.reserve(_stateGraphList.size()); noTestRoot->_leaves.reserve(_stateGraphList.size());
StateGraphList newList; StateGraphList newList;
std::unordered_set<StateGraph*> uninterestingCache; std::unordered_set<StateGraph*> uninterestingCache;
@ -154,7 +214,7 @@ void ShadowsBin::sortImplementation()
// Render leaves which shouldn't use the diffuse map for shadow alpha but do cast shadows become children of root, so graph is now empty. Don't add to newList. // Render leaves which shouldn't use the diffuse map for shadow alpha but do cast shadows become children of root, so graph is now empty. Don't add to newList.
// Graphs containing just render leaves which don't cast shadows are discarded. Don't add to newList. // Graphs containing just render leaves which don't cast shadows are discarded. Don't add to newList.
// Graphs containing other leaves need to be in newList. // Graphs containing other leaves need to be in newList.
StateGraph* graphToAdd = cullStateGraph(graph, noTestRoot, uninterestingCache); StateGraph* graphToAdd = cullStateGraph(graph, noTestRoot, uninterestingCache, cullFaceOverridden);
if (graphToAdd) if (graphToAdd)
newList.push_back(graphToAdd); newList.push_back(graphToAdd);
} }

@ -1,11 +1,13 @@
#ifndef OPENMW_COMPONENTS_SCENEUTIL_SHADOWBIN_H #ifndef OPENMW_COMPONENTS_SCENEUTIL_SHADOWBIN_H
#define OPENMW_COMPONENTS_SCENEUTIL_SHADOWBIN_H #define OPENMW_COMPONENTS_SCENEUTIL_SHADOWBIN_H
#include <array>
#include <unordered_set> #include <unordered_set>
#include <osgUtil/RenderBin> #include <osgUtil/RenderBin>
namespace osg namespace osg
{ {
class Material; class Material;
class AlphaFunc;
} }
namespace SceneUtil namespace SceneUtil
@ -15,8 +17,12 @@ namespace SceneUtil
class ShadowsBin : public osgUtil::RenderBin class ShadowsBin : public osgUtil::RenderBin
{ {
private: private:
static std::array<osg::ref_ptr<osg::Program>, GL_ALWAYS - GL_NEVER + 1> sCastingPrograms;
osg::ref_ptr<osg::StateSet> mNoTestStateSet; osg::ref_ptr<osg::StateSet> mNoTestStateSet;
osg::ref_ptr<osg::StateSet> mShaderAlphaTestStateSet; osg::ref_ptr<osg::StateSet> mShaderAlphaTestStateSet;
std::array<osg::ref_ptr<osg::StateSet>, GL_ALWAYS - GL_NEVER + 1> mAlphaFuncShaders;
public: public:
META_Object(SceneUtil, ShadowsBin) META_Object(SceneUtil, ShadowsBin)
ShadowsBin(); ShadowsBin();
@ -24,6 +30,7 @@ namespace SceneUtil
: osgUtil::RenderBin(rhs, copyop) : osgUtil::RenderBin(rhs, copyop)
, mNoTestStateSet(rhs.mNoTestStateSet) , mNoTestStateSet(rhs.mNoTestStateSet)
, mShaderAlphaTestStateSet(rhs.mShaderAlphaTestStateSet) , mShaderAlphaTestStateSet(rhs.mShaderAlphaTestStateSet)
, mAlphaFuncShaders(rhs.mAlphaFuncShaders)
{} {}
void sortImplementation() override; void sortImplementation() override;
@ -33,8 +40,8 @@ namespace SceneUtil
State() State()
: mAlphaBlend(false) : mAlphaBlend(false)
, mAlphaBlendOverride(false) , mAlphaBlendOverride(false)
, mAlphaTest(false) , mAlphaFunc(nullptr)
, mAlphaTestOverride(false) , mAlphaFuncOverride(false)
, mMaterial(nullptr) , mMaterial(nullptr)
, mMaterialOverride(false) , mMaterialOverride(false)
, mImportantState(false) , mImportantState(false)
@ -42,33 +49,29 @@ namespace SceneUtil
bool mAlphaBlend; bool mAlphaBlend;
bool mAlphaBlendOverride; bool mAlphaBlendOverride;
bool mAlphaTest; osg::AlphaFunc* mAlphaFunc;
bool mAlphaTestOverride; bool mAlphaFuncOverride;
osg::Material* mMaterial; osg::Material* mMaterial;
bool mMaterialOverride; bool mMaterialOverride;
bool mImportantState; bool mImportantState;
bool needTexture() const { return mAlphaBlend || mAlphaTest; } bool needTexture() const;
bool needShadows() const; bool needShadows() const;
// A state is interesting if there's anything about it that might affect whether we can optimise child state // A state is interesting if there's anything about it that might affect whether we can optimise child state
bool interesting() const bool interesting() const
{ {
return !needShadows() || needTexture() || mAlphaBlendOverride || mAlphaTestOverride || mMaterialOverride || mImportantState; return !needShadows() || needTexture() || mAlphaBlendOverride || mAlphaFuncOverride || mMaterialOverride || mImportantState;
} }
}; };
osgUtil::StateGraph* cullStateGraph(osgUtil::StateGraph* sg, osgUtil::StateGraph* root, std::unordered_set<osgUtil::StateGraph*>& uninteresting); osgUtil::StateGraph* cullStateGraph(osgUtil::StateGraph* sg, osgUtil::StateGraph* root, std::unordered_set<osgUtil::StateGraph*>& uninteresting, bool cullFaceOverridden);
static void addPrototype(const std::string& name) static void addPrototype(const std::string& name, const std::array<osg::ref_ptr<osg::Program>, GL_ALWAYS - GL_NEVER + 1>& castingPrograms);
{
osg::ref_ptr<osgUtil::RenderBin> bin (new ShadowsBin);
osgUtil::RenderBin::addRenderBinPrototype(name, bin);
}
}; };
class ShadowsBinAdder class ShadowsBinAdder
{ {
public: public:
ShadowsBinAdder(const std::string& name){ ShadowsBin::addPrototype(name); } ShadowsBinAdder(const std::string& name, const std::array<osg::ref_ptr<osg::Program>, GL_ALWAYS - GL_NEVER + 1>& castingPrograms){ ShadowsBin::addPrototype(name, castingPrograms); }
}; };
} }

@ -11,6 +11,7 @@
#include <components/resource/imagemanager.hpp> #include <components/resource/imagemanager.hpp>
#include <components/resource/scenemanager.hpp> #include <components/resource/scenemanager.hpp>
#include <components/settings/settings.hpp>
namespace SceneUtil namespace SceneUtil
{ {
@ -260,4 +261,21 @@ osg::ref_ptr<GlowUpdater> addEnchantedGlow(osg::ref_ptr<osg::Node> node, Resourc
return glowUpdater; return glowUpdater;
} }
bool attachAlphaToCoverageFriendlyFramebufferToCamera(osg::Camera* camera, osg::Camera::BufferComponent buffer, osg::Texture * texture, unsigned int level, unsigned int face, bool mipMapGeneration)
{
unsigned int samples = 0;
unsigned int colourSamples = 0;
bool addMSAAIntermediateTarget = Settings::Manager::getBool("antialias alpha test", "Shaders") && Settings::Manager::getInt("antialiasing", "Video") > 1;
if (addMSAAIntermediateTarget)
{
// Alpha-to-coverage requires a multisampled framebuffer.
// OSG will set that up automatically and resolve it to the specified single-sample texture for us.
// For some reason, two samples are needed, at least with some drivers.
samples = 2;
colourSamples = 1;
}
camera->attach(buffer, texture, level, face, mipMapGeneration, samples, colourSamples);
return addMSAAIntermediateTarget;
}
} }

@ -3,6 +3,7 @@
#include <osg/Matrix> #include <osg/Matrix>
#include <osg/BoundingSphere> #include <osg/BoundingSphere>
#include <osg/Camera>
#include <osg/NodeCallback> #include <osg/NodeCallback>
#include <osg/Texture2D> #include <osg/Texture2D>
#include <osg/Vec4f> #include <osg/Vec4f>
@ -60,6 +61,9 @@ namespace SceneUtil
bool hasUserDescription(const osg::Node* node, const std::string pattern); bool hasUserDescription(const osg::Node* node, const std::string pattern);
osg::ref_ptr<GlowUpdater> addEnchantedGlow(osg::ref_ptr<osg::Node> node, Resource::ResourceSystem* resourceSystem, osg::Vec4f glowColor, float glowDuration=-1); osg::ref_ptr<GlowUpdater> addEnchantedGlow(osg::ref_ptr<osg::Node> node, Resource::ResourceSystem* resourceSystem, osg::Vec4f glowColor, float glowDuration=-1);
// Alpha-to-coverage requires a multisampled framebuffer, so we need to set that up for RTTs
bool attachAlphaToCoverageFriendlyFramebufferToCamera(osg::Camera* camera, osg::Camera::BufferComponent buffer, osg::Texture* texture, unsigned int level = 0, unsigned int face = 0, bool mipMapGeneration = false);
} }
#endif #endif

@ -0,0 +1,36 @@
// EGL does not work reliably for feature detection.
// Instead, we initialize gl4es manually.
#ifdef OPENMW_GL4ES_MANUAL_INIT
#include "gl4es_init.h"
// For glHint
#include <GL/gl.h>
extern "C" {
#include <gl4es/gl4esinit.h>
#include <gl4es/gl4eshint.h>
static SDL_Window *gWindow;
void openmw_gl4es_GetMainFBSize(int *width, int *height)
{
SDL_GetWindowSize(gWindow, width, height);
}
void openmw_gl4es_init(SDL_Window *window)
{
gWindow = window;
set_getprocaddress(SDL_GL_GetProcAddress);
set_getmainfbsize(openmw_gl4es_GetMainFBSize);
initialize_gl4es();
// merge glBegin/glEnd in beams and console
glHint(GL_BEGINEND_HINT_GL4ES, 1);
// dxt unpacked to 16-bit looks ugly
glHint(GL_AVOID16BITS_HINT_GL4ES, 1);
}
} // extern "C"
#endif // OPENMW_GL4ES_MANUAL_INIT

@ -0,0 +1,13 @@
#ifndef OPENMW_COMPONENTS_SDLUTIL_GL4ES_INIT_H
#define OPENMW_COMPONENTS_SDLUTIL_GL4ES_INIT_H
#ifdef OPENMW_GL4ES_MANUAL_INIT
#include <SDL_video.h>
// Must be called once SDL video mode has been set,
// which creates a context.
//
// GL4ES can then query the context for features and extensions.
extern "C" void openmw_gl4es_init(SDL_Window *window);
#endif // OPENMW_GL4ES_MANUAL_INIT
#endif // OPENMW_COMPONENTS_SDLUTIL_GL4ES_INIT_H

@ -2,6 +2,10 @@
#include <SDL_video.h> #include <SDL_video.h>
#ifdef OPENMW_GL4ES_MANUAL_INIT
#include "gl4es_init.h"
#endif
namespace SDLUtil namespace SDLUtil
{ {
@ -91,7 +95,7 @@ void GraphicsWindowSDL2::init()
SDL_Window *oldWin = SDL_GL_GetCurrentWindow(); SDL_Window *oldWin = SDL_GL_GetCurrentWindow();
SDL_GLContext oldCtx = SDL_GL_GetCurrentContext(); SDL_GLContext oldCtx = SDL_GL_GetCurrentContext();
#if defined(ANDROID) #if defined(ANDROID) || defined(OPENMW_GL4ES_MANUAL_INIT)
int major = 1; int major = 1;
int minor = 1; int minor = 1;
char *ver = getenv("OPENMW_GLES_VERSION"); char *ver = getenv("OPENMW_GLES_VERSION");
@ -116,6 +120,10 @@ void GraphicsWindowSDL2::init()
return; return;
} }
#ifdef OPENMW_GL4ES_MANUAL_INIT
openmw_gl4es_init(mWindow);
#endif
setSwapInterval(_traits->vsync); setSwapInterval(_traits->vsync);
// Update traits with what we've actually been given // Update traits with what we've actually been given

@ -0,0 +1,20 @@
#include "removedalphafunc.hpp"
#include <cassert>
#include <osg/State>
namespace Shader
{
std::array<osg::ref_ptr<RemovedAlphaFunc>, GL_ALWAYS - GL_NEVER + 1> RemovedAlphaFunc::sInstances{
nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr
};
osg::ref_ptr<RemovedAlphaFunc> RemovedAlphaFunc::getInstance(GLenum func)
{
assert(func >= GL_NEVER && func <= GL_ALWAYS);
if (!sInstances[func - GL_NEVER])
sInstances[func - GL_NEVER] = new RemovedAlphaFunc(static_cast<osg::AlphaFunc::ComparisonFunction>(func), 1.0);
return sInstances[func - GL_NEVER];
}
}

@ -0,0 +1,40 @@
#ifndef OPENMW_COMPONENTS_REMOVEDALPHAFUNC_H
#define OPENMW_COMPONENTS_REMOVEDALPHAFUNC_H
#include <array>
#include <osg/AlphaFunc>
namespace Shader
{
// State attribute used when shader visitor replaces the deprecated alpha function with a shader
// Prevents redundant glAlphaFunc calls and lets the shadowsbin know the stateset had alpha testing
class RemovedAlphaFunc : public osg::AlphaFunc
{
public:
// Get a singleton-like instance with the right func (but a default threshold)
static osg::ref_ptr<RemovedAlphaFunc> getInstance(GLenum func);
RemovedAlphaFunc()
: osg::AlphaFunc()
{}
RemovedAlphaFunc(ComparisonFunction func, float ref)
: osg::AlphaFunc(func, ref)
{}
RemovedAlphaFunc(const RemovedAlphaFunc& raf, const osg::CopyOp& copyop = osg::CopyOp::SHALLOW_COPY)
: osg::AlphaFunc(raf, copyop)
{}
META_StateAttribute(Shader, RemovedAlphaFunc, ALPHAFUNC);
void apply(osg::State& state) const override {}
protected:
virtual ~RemovedAlphaFunc() = default;
static std::array<osg::ref_ptr<RemovedAlphaFunc>, GL_ALWAYS - GL_NEVER + 1> sInstances;
};
}
#endif //OPENMW_COMPONENTS_REMOVEDALPHAFUNC_H

@ -1,7 +1,10 @@
#include "shadervisitor.hpp" #include "shadervisitor.hpp"
#include <osg/AlphaFunc>
#include <osg/Geometry> #include <osg/Geometry>
#include <osg/GLExtensions>
#include <osg/Material> #include <osg/Material>
#include <osg/Multisample>
#include <osg/Texture> #include <osg/Texture>
#include <osgUtil/TangentSpaceGenerator> #include <osgUtil/TangentSpaceGenerator>
@ -13,6 +16,7 @@
#include <components/sceneutil/riggeometry.hpp> #include <components/sceneutil/riggeometry.hpp>
#include <components/sceneutil/morphgeometry.hpp> #include <components/sceneutil/morphgeometry.hpp>
#include "removedalphafunc.hpp"
#include "shadermanager.hpp" #include "shadermanager.hpp"
namespace Shader namespace Shader
@ -22,6 +26,11 @@ namespace Shader
: mShaderRequired(false) : mShaderRequired(false)
, mColorMode(0) , mColorMode(0)
, mMaterialOverridden(false) , mMaterialOverridden(false)
, mAlphaTestOverridden(false)
, mAlphaBlendOverridden(false)
, mAlphaFunc(GL_ALWAYS)
, mAlphaRef(1.0)
, mAlphaBlend(false)
, mNormalHeight(false) , mNormalHeight(false)
, mTexStageRequiringTangents(-1) , mTexStageRequiringTangents(-1)
, mNode(nullptr) , mNode(nullptr)
@ -77,6 +86,34 @@ namespace Shader
return newStateSet.get(); return newStateSet.get();
} }
osg::UserDataContainer* getWritableUserDataContainer(osg::Object& object)
{
if (!object.getUserDataContainer())
return object.getOrCreateUserDataContainer();
osg::ref_ptr<osg::UserDataContainer> newUserData = static_cast<osg::UserDataContainer *>(object.getUserDataContainer()->clone(osg::CopyOp::SHALLOW_COPY));
object.setUserDataContainer(newUserData);
return newUserData.get();
}
osg::StateSet* getRemovedState(osg::StateSet& stateSet)
{
if (!stateSet.getUserDataContainer())
return nullptr;
return static_cast<osg::StateSet *>(stateSet.getUserDataContainer()->getUserObject("removedState"));
}
void updateRemovedState(osg::UserDataContainer& userData, osg::StateSet* stateSet)
{
unsigned int index = userData.getUserObjectIndex("removedState");
if (index < userData.getNumUserObjects())
userData.setUserObject(index, stateSet);
else
userData.addUserObject(stateSet);
stateSet->setName("removedState");
}
const char* defaultTextures[] = { "diffuseMap", "normalMap", "emissiveMap", "darkMap", "detailMap", "envMap", "specularMap", "decalMap", "bumpMap" }; const char* defaultTextures[] = { "diffuseMap", "normalMap", "emissiveMap", "darkMap", "detailMap", "envMap", "specularMap", "decalMap", "bumpMap" };
bool isTextureNameRecognized(const std::string& name) bool isTextureNameRecognized(const std::string& name)
{ {
@ -235,49 +272,76 @@ namespace Shader
} }
const osg::StateSet::AttributeList& attributes = stateset->getAttributeList(); const osg::StateSet::AttributeList& attributes = stateset->getAttributeList();
for (osg::StateSet::AttributeList::const_iterator it = attributes.begin(); it != attributes.end(); ++it) osg::StateSet::AttributeList removedAttributes;
osg::ref_ptr<osg::StateSet> removedState;
if (removedState = getRemovedState(*stateset))
removedAttributes = removedState->getAttributeList();
for (const auto& attributeMap : { attributes, removedAttributes })
{ {
if (it->first.first == osg::StateAttribute::MATERIAL) for (osg::StateSet::AttributeList::const_iterator it = attributeMap.begin(); it != attributeMap.end(); ++it)
{ {
// This should probably be moved out of ShaderRequirements and be applied directly now it's a uniform instead of a define if (it->first.first == osg::StateAttribute::MATERIAL)
if (!mRequirements.back().mMaterialOverridden || it->second.second & osg::StateAttribute::PROTECTED)
{ {
if (it->second.second & osg::StateAttribute::OVERRIDE) // This should probably be moved out of ShaderRequirements and be applied directly now it's a uniform instead of a define
mRequirements.back().mMaterialOverridden = true; if (!mRequirements.back().mMaterialOverridden || it->second.second & osg::StateAttribute::PROTECTED)
{
if (it->second.second & osg::StateAttribute::OVERRIDE)
mRequirements.back().mMaterialOverridden = true;
const osg::Material* mat = static_cast<const osg::Material*>(it->second.first.get()); const osg::Material* mat = static_cast<const osg::Material*>(it->second.first.get());
if (!writableStateSet) if (!writableStateSet)
writableStateSet = getWritableStateSet(node); writableStateSet = getWritableStateSet(node);
int colorMode; int colorMode;
switch (mat->getColorMode()) switch (mat->getColorMode())
{ {
case osg::Material::OFF: case osg::Material::OFF:
colorMode = 0; colorMode = 0;
break; break;
case osg::Material::EMISSION: case osg::Material::EMISSION:
colorMode = 1; colorMode = 1;
break; break;
default: default:
case osg::Material::AMBIENT_AND_DIFFUSE: case osg::Material::AMBIENT_AND_DIFFUSE:
colorMode = 2; colorMode = 2;
break; break;
case osg::Material::AMBIENT: case osg::Material::AMBIENT:
colorMode = 3; colorMode = 3;
break; break;
case osg::Material::DIFFUSE: case osg::Material::DIFFUSE:
colorMode = 4; colorMode = 4;
break; break;
case osg::Material::SPECULAR: case osg::Material::SPECULAR:
colorMode = 5; colorMode = 5;
break; break;
}
mRequirements.back().mColorMode = colorMode;
} }
}
else if (it->first.first == osg::StateAttribute::ALPHAFUNC)
{
if (!mRequirements.back().mAlphaTestOverridden || it->second.second & osg::StateAttribute::PROTECTED)
{
if (it->second.second & osg::StateAttribute::OVERRIDE)
mRequirements.back().mAlphaTestOverridden = true;
mRequirements.back().mColorMode = colorMode; const osg::AlphaFunc* alpha = static_cast<const osg::AlphaFunc*>(it->second.first.get());
mRequirements.back().mAlphaFunc = alpha->getFunction();
mRequirements.back().mAlphaRef = alpha->getReferenceValue();
}
} }
} }
// Eventually, move alpha testing to discard in shader adn remove deprecated state here }
unsigned int alphaBlend = stateset->getMode(GL_BLEND);
if (alphaBlend != osg::StateAttribute::INHERIT && (!mRequirements.back().mAlphaBlendOverridden || alphaBlend & osg::StateAttribute::PROTECTED))
{
if (alphaBlend & osg::StateAttribute::OVERRIDE)
mRequirements.back().mAlphaBlendOverridden = true;
mRequirements.back().mAlphaBlend = alphaBlend & osg::StateAttribute::ON;
} }
} }
@ -323,6 +387,57 @@ namespace Shader
writableStateSet->addUniform(new osg::Uniform("colorMode", reqs.mColorMode)); writableStateSet->addUniform(new osg::Uniform("colorMode", reqs.mColorMode));
defineMap["alphaFunc"] = std::to_string(reqs.mAlphaFunc);
// back up removed state in case recreateShaders gets rid of the shader later
osg::ref_ptr<osg::StateSet> removedState;
if ((removedState = getRemovedState(*writableStateSet)) && !mAllowedToModifyStateSets)
removedState = new osg::StateSet(*removedState, osg::CopyOp::SHALLOW_COPY);
if (!removedState)
removedState = new osg::StateSet();
defineMap["alphaToCoverage"] = "0";
if (reqs.mAlphaFunc != osg::AlphaFunc::ALWAYS)
{
writableStateSet->addUniform(new osg::Uniform("alphaRef", reqs.mAlphaRef));
const auto* alphaFunc = writableStateSet->getAttributePair(osg::StateAttribute::ALPHAFUNC);
if (alphaFunc)
removedState->setAttribute(alphaFunc->first, alphaFunc->second);
// This prevents redundant glAlphaFunc calls while letting the shadows bin still see the test
writableStateSet->setAttribute(RemovedAlphaFunc::getInstance(reqs.mAlphaFunc), osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE);
// Blending won't work with A2C as we use the alpha channel for coverage. gl_SampleCoverage from ARB_sample_shading would save the day, but requires GLSL 130
if (mConvertAlphaTestToAlphaToCoverage && !reqs.mAlphaBlend)
{
writableStateSet->setMode(GL_SAMPLE_ALPHA_TO_COVERAGE_ARB, osg::StateAttribute::ON);
defineMap["alphaToCoverage"] = "1";
}
// Preventing alpha tested stuff shrinking as lower mip levels are used requires knowing the texture size
osg::ref_ptr<osg::GLExtensions> exts = osg::GLExtensions::Get(0, false);
if (exts && exts->isGpuShader4Supported)
defineMap["useGPUShader4"] = "1";
// We could fall back to a texture size uniform if EXT_gpu_shader4 is missing
}
if (writableStateSet->getMode(GL_ALPHA_TEST) != osg::StateAttribute::INHERIT)
removedState->setMode(GL_ALPHA_TEST, writableStateSet->getMode(GL_ALPHA_TEST));
// This disables the deprecated fixed-function alpha test
writableStateSet->setMode(GL_ALPHA_TEST, osg::StateAttribute::OFF | osg::StateAttribute::PROTECTED);
if (!removedState->getModeList().empty() || !removedState->getAttributeList().empty())
{
// user data is normally shallow copied so shared with the original stateset
osg::ref_ptr<osg::UserDataContainer> writableUserData;
if (mAllowedToModifyStateSets)
writableUserData = writableStateSet->getOrCreateUserDataContainer();
else
writableUserData = getWritableUserDataContainer(*writableStateSet);
updateRemovedState(*writableUserData, removedState);
}
defineMap["translucentFramebuffer"] = mTranslucentFramebuffer ? "1" : "0"; defineMap["translucentFramebuffer"] = mTranslucentFramebuffer ? "1" : "0";
osg::ref_ptr<osg::Shader> vertexShader (mShaderManager.getShader(mDefaultVsTemplate, defineMap, osg::Shader::VERTEX)); osg::ref_ptr<osg::Shader> vertexShader (mShaderManager.getShader(mDefaultVsTemplate, defineMap, osg::Shader::VERTEX));
@ -350,6 +465,25 @@ namespace Shader
writableStateSet = getWritableStateSet(node); writableStateSet = getWritableStateSet(node);
writableStateSet->removeAttribute(osg::StateAttribute::PROGRAM); writableStateSet->removeAttribute(osg::StateAttribute::PROGRAM);
osg::ref_ptr<osg::StateSet> removedState;
if (removedState = getRemovedState(*writableStateSet))
{
// user data is normally shallow copied so shared with the original stateset
osg::ref_ptr<osg::UserDataContainer> writableUserData;
if (mAllowedToModifyStateSets)
writableUserData = writableStateSet->getUserDataContainer();
else
writableUserData = getWritableUserDataContainer(*writableStateSet);
unsigned int index = writableUserData->getUserObjectIndex("removedState");
writableUserData->removeUserObject(index);
for (const auto& [mode, value] : removedState->getModeList())
writableStateSet->setMode(mode, value);
for (const auto& attribute : removedState->getAttributeList())
writableStateSet->setAttribute(attribute.second.first, attribute.second.second);
}
} }
bool ShaderVisitor::adjustGeometry(osg::Geometry& sourceGeometry, const ShaderRequirements& reqs) bool ShaderVisitor::adjustGeometry(osg::Geometry& sourceGeometry, const ShaderRequirements& reqs)
@ -477,9 +611,53 @@ namespace Shader
mApplyLightingToEnvMaps = apply; mApplyLightingToEnvMaps = apply;
} }
void ShaderVisitor::setConvertAlphaTestToAlphaToCoverage(bool convert)
{
mConvertAlphaTestToAlphaToCoverage = convert;
}
void ShaderVisitor::setTranslucentFramebuffer(bool translucent) void ShaderVisitor::setTranslucentFramebuffer(bool translucent)
{ {
mTranslucentFramebuffer = translucent; mTranslucentFramebuffer = translucent;
} }
ReinstateRemovedStateVisitor::ReinstateRemovedStateVisitor(bool allowedToModifyStateSets)
: osg::NodeVisitor(TRAVERSE_ALL_CHILDREN)
, mAllowedToModifyStateSets(allowedToModifyStateSets)
{
}
void ReinstateRemovedStateVisitor::apply(osg::Node& node)
{
if (node.getStateSet())
{
osg::ref_ptr<osg::StateSet> removedState = getRemovedState(*node.getStateSet());
if (removedState)
{
osg::ref_ptr<osg::StateSet> writableStateSet;
if (mAllowedToModifyStateSets)
writableStateSet = node.getStateSet();
else
writableStateSet = getWritableStateSet(node);
// user data is normally shallow copied so shared with the original stateset
osg::ref_ptr<osg::UserDataContainer> writableUserData;
if (mAllowedToModifyStateSets)
writableUserData = writableStateSet->getUserDataContainer();
else
writableUserData = getWritableUserDataContainer(*writableStateSet);
unsigned int index = writableUserData->getUserObjectIndex("removedState");
writableUserData->removeUserObject(index);
for (const auto&[mode, value] : removedState->getModeList())
writableStateSet->setMode(mode, value);
for (const auto& attribute : removedState->getAttributeList())
writableStateSet->setAttribute(attribute.second.first, attribute.second.second);
}
}
traverse(node);
}
} }

@ -40,6 +40,8 @@ namespace Shader
void setApplyLightingToEnvMaps(bool apply); void setApplyLightingToEnvMaps(bool apply);
void setConvertAlphaTestToAlphaToCoverage(bool convert);
void setTranslucentFramebuffer(bool translucent); void setTranslucentFramebuffer(bool translucent);
void apply(osg::Node& node) override; void apply(osg::Node& node) override;
@ -65,6 +67,8 @@ namespace Shader
bool mApplyLightingToEnvMaps; bool mApplyLightingToEnvMaps;
bool mConvertAlphaTestToAlphaToCoverage;
bool mTranslucentFramebuffer; bool mTranslucentFramebuffer;
ShaderManager& mShaderManager; ShaderManager& mShaderManager;
@ -83,6 +87,12 @@ namespace Shader
int mColorMode; int mColorMode;
bool mMaterialOverridden; bool mMaterialOverridden;
bool mAlphaTestOverridden;
bool mAlphaBlendOverridden;
GLenum mAlphaFunc;
float mAlphaRef;
bool mAlphaBlend;
bool mNormalHeight; // true if normal map has height info in alpha channel bool mNormalHeight; // true if normal map has height info in alpha channel
@ -102,6 +112,17 @@ namespace Shader
bool adjustGeometry(osg::Geometry& sourceGeometry, const ShaderRequirements& reqs); bool adjustGeometry(osg::Geometry& sourceGeometry, const ShaderRequirements& reqs);
}; };
class ReinstateRemovedStateVisitor : public osg::NodeVisitor
{
public:
ReinstateRemovedStateVisitor(bool allowedToModifyStateSets);
void apply(osg::Node& node) override;
private:
bool mAllowedToModifyStateSets;
};
} }
#endif #endif

@ -1,20 +1,19 @@
// This program generates the file tables_gen.hpp // This program generates the file tables_gen.hpp
#include <iostream> #include <iostream>
using namespace std;
#include <iconv.h> #include <iconv.h>
#include <cassert> #include <cassert>
void tab() { cout << " "; } void tab() { std::cout << " "; }
// write one number with a space in front of it and a comma after it // write one number with a space in front of it and a comma after it
void num(char i, bool last) void num(char i, bool last)
{ {
// Convert i to its integer value, i.e. -128 to 127. Printing it directly // Convert i to its integer value, i.e. -128 to 127. Printing it directly
// would result in non-printable characters in the source code, which is bad. // would result in non-printable characters in the source code, which is bad.
cout << " " << static_cast<int>(i); std::cout << " " << static_cast<int>(i);
if(!last) cout << ","; if(!last) std::cout << ",";
} }
// Write one table entry (UTF8 value), 1-5 bytes // Write one table entry (UTF8 value), 1-5 bytes
@ -27,9 +26,9 @@ void writeChar(char *value, int length, bool last, const std::string &comment=""
num(value[i], last && i==4); num(value[i], last && i==4);
if(comment != "") if(comment != "")
cout << " // " << comment; std::cout << " // " << comment;
cout << endl; std::cout << std::endl;
} }
// What to write on missing characters // What to write on missing characters
@ -46,7 +45,7 @@ void writeMissing(bool last)
int write_table(const std::string &charset, const std::string &tableName) int write_table(const std::string &charset, const std::string &tableName)
{ {
// Write table header // Write table header
cout << "static signed char " << tableName << "[] =\n{\n"; std::cout << "static signed char " << tableName << "[] =\n{\n";
// Open conversion system // Open conversion system
iconv_t cd = iconv_open ("UTF-8", charset.c_str()); iconv_t cd = iconv_open ("UTF-8", charset.c_str());
@ -74,7 +73,7 @@ int write_table(const std::string &charset, const std::string &tableName)
iconv_close (cd); iconv_close (cd);
// Finish table // Finish table
cout << "};\n"; std::cout << "};\n";
return 0; return 0;
} }
@ -82,37 +81,37 @@ int write_table(const std::string &charset, const std::string &tableName)
int main() int main()
{ {
// Write header guard // Write header guard
cout << "#ifndef COMPONENTS_TOUTF8_TABLE_GEN_H\n#define COMPONENTS_TOUTF8_TABLE_GEN_H\n\n"; std::cout << "#ifndef COMPONENTS_TOUTF8_TABLE_GEN_H\n#define COMPONENTS_TOUTF8_TABLE_GEN_H\n\n";
// Write namespace // Write namespace
cout << "namespace ToUTF8\n{\n\n"; std::cout << "namespace ToUTF8\n{\n\n";
// Central European and Eastern European languages that use Latin script, such as // Central European and Eastern European languages that use Latin script, such as
// Polish, Czech, Slovak, Hungarian, Slovene, Bosnian, Croatian, Serbian (Latin script), Romanian and Albanian. // Polish, Czech, Slovak, Hungarian, Slovene, Bosnian, Croatian, Serbian (Latin script), Romanian and Albanian.
cout << "\n/// Central European and Eastern European languages that use Latin script," std::cout << "\n/// Central European and Eastern European languages that use Latin script,"
"\n/// such as Polish, Czech, Slovak, Hungarian, Slovene, Bosnian, Croatian," "\n/// such as Polish, Czech, Slovak, Hungarian, Slovene, Bosnian, Croatian,"
"\n/// Serbian (Latin script), Romanian and Albanian." "\n/// Serbian (Latin script), Romanian and Albanian."
"\n"; "\n";
write_table("WINDOWS-1250", "windows_1250"); write_table("WINDOWS-1250", "windows_1250");
// Cyrillic alphabet such as Russian, Bulgarian, Serbian Cyrillic and other languages // Cyrillic alphabet such as Russian, Bulgarian, Serbian Cyrillic and other languages
cout << "\n/// Cyrillic alphabet such as Russian, Bulgarian, Serbian Cyrillic" std::cout << "\n/// Cyrillic alphabet such as Russian, Bulgarian, Serbian Cyrillic"
"\n/// and other languages" "\n/// and other languages"
"\n"; "\n";
write_table("WINDOWS-1251", "windows_1251"); write_table("WINDOWS-1251", "windows_1251");
// English // English
cout << "\n/// Latin alphabet used by English and some other Western languages" std::cout << "\n/// Latin alphabet used by English and some other Western languages"
"\n"; "\n";
write_table("WINDOWS-1252", "windows_1252"); write_table("WINDOWS-1252", "windows_1252");
write_table("CP437", "cp437"); write_table("CP437", "cp437");
// Close namespace // Close namespace
cout << "\n}\n\n"; std::cout << "\n}\n\n";
// Close header guard // Close header guard
cout << "#endif\n\n"; std::cout << "#endif\n\n";
return 0; return 0;
} }

@ -306,8 +306,11 @@ For example, we do not need to have collision or animation objects for groundcov
do not need to render groundcover on the map, do not need to render it for the whole visible area (which can be very large with Distant Terrain). It allows to increase performance a lot. do not need to render groundcover on the map, do not need to render it for the whole visible area (which can be very large with Distant Terrain). It allows to increase performance a lot.
General advices to create assets for this feature: General advices to create assets for this feature:
1. Alpha properties from Nif files are not used, a unified alpha settings are used (alpha testing, "greater of equal" function, 128/255 threshold). 1. Alpha properties from Nif files are not used, a unified alpha settings are used (alpha testing, "greater of equal" function, 128/255 threshold).
2. Use a single NiTriShape in groundocver mesh, or at least use same properties (texture, alpha, material, etc), so OpenMW can merge them on the fly. Otherwise animations may not work properly. 2. Use a single NiTriShape in groundocver mesh, or at least use same properties (texture, alpha, material, etc), so OpenMW can merge them on the fly. Otherwise animations may not work properly.
3. Smooth fading does not work for meshes, which have textures without alpha (e.g. rock). 3. Smooth fading does not work for meshes, which have textures without alpha (e.g. rock).
Groundcover mods can be registered in the openmw.cfg via "groundcover" entries instead of "content" ones: Groundcover mods can be registered in the openmw.cfg via "groundcover" entries instead of "content" ones:

@ -147,3 +147,14 @@ radial fog
By default, the fog becomes thicker proportionally to your distance from the clipping plane set at the clipping distance, which causes distortion at the edges of the screen. By default, the fog becomes thicker proportionally to your distance from the clipping plane set at the clipping distance, which causes distortion at the edges of the screen.
This setting makes the fog use the actual eye point distance (or so called Euclidean distance) to calculate the fog, which makes the fog look less artificial, especially if you have a wide FOV. This setting makes the fog use the actual eye point distance (or so called Euclidean distance) to calculate the fog, which makes the fog look less artificial, especially if you have a wide FOV.
Note that the rendering will act as if you have 'force shaders' option enabled with this on, which means that shaders will be used to render all objects and the terrain. Note that the rendering will act as if you have 'force shaders' option enabled with this on, which means that shaders will be used to render all objects and the terrain.
antialias alpha test
---------------------------------------
:Type: boolean
:Range: True/False
:Default: False
Convert the alpha test (cutout/punchthrough alpha) to alpha-to-coverage when :ref:`antialiasing` is on.
This allows MSAA to work with alpha-tested meshes, producing better-looking edges without pixelation.
When MSAA is off, this setting will have no visible effect, but might have a performance cost.

@ -31,15 +31,20 @@ if(NOT OPENMW_USE_SYSTEM_BULLET)
set(USE_DOUBLE_PRECISION ${BULLET_USE_DOUBLES} CACHE BOOL "") set(USE_DOUBLE_PRECISION ${BULLET_USE_DOUBLES} CACHE BOOL "")
set(BULLET2_MULTITHREADING ON CACHE BOOL "") set(BULLET2_MULTITHREADING ON CACHE BOOL "")
# Version 3.08 with the following changes: if(BULLET_STATIC)
# 1. Fixes the linking of Threads: set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE)
# https://github.com/bulletphysics/bullet3/pull/3237 else()
# 2. Removes ~300 MiB of files not used here: set(BUILD_SHARED_LIBS ON CACHE BOOL "" FORCE)
# rm -rf build3 data docs examples test Doxyfile if(MSVC)
set(USE_MSVC_RUNTIME_LIBRARY_DLL ON CACHE BOOL "" FORCE)
endif()
endif()
# master on 12 Mar 2021
include(FetchContent) include(FetchContent)
FetchContent_Declare(bullet FetchContent_Declare(bullet
URL https://github.com/glebm/bullet3/archive/ed5256454f4f84bd2c1728c88ddb0405d614e7d2.zip URL https://github.com/bulletphysics/bullet3/archive/87e668f6b2a883b4ef63db8a07c8e9283916e9d9.zip
URL_HASH MD5=e3c94fac35a7be885ad8843f828a0f96 URL_HASH MD5=9f13246439968494c2b595cf412d83c8
SOURCE_DIR fetched/bullet SOURCE_DIR fetched/bullet
) )
FetchContent_MakeAvailableExcludeFromAll(bullet) FetchContent_MakeAvailableExcludeFromAll(bullet)
@ -60,16 +65,20 @@ if(NOT OPENMW_USE_SYSTEM_MYGUI)
set(MYGUI_BUILD_PLUGINS OFF CACHE BOOL "") set(MYGUI_BUILD_PLUGINS OFF CACHE BOOL "")
set(MYGUI_BUILD_TOOLS OFF CACHE BOOL "") set(MYGUI_BUILD_TOOLS OFF CACHE BOOL "")
# We appear to be using some obsolete properties in the XML.
# See https://gitlab.com/OpenMW/openmw/-/issues/5896
set(MYGUI_DONT_USE_OBSOLETE OFF CACHE BOOL "")
if(MYGUI_STATIC) if(MYGUI_STATIC)
set(BUILD_SHARED_LIBS OFF) set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE)
else() else()
set(BUILD_SHARED_LIBS ON) set(BUILD_SHARED_LIBS ON CACHE BOOL "" FORCE)
endif() endif()
include(FetchContent) include(FetchContent)
FetchContent_Declare(mygui FetchContent_Declare(mygui
URL https://github.com/MyGUI/mygui/archive/MyGUI3.4.0.zip URL https://github.com/MyGUI/mygui/archive/MyGUI3.4.1.zip
URL_HASH MD5=9e990a4240430cbf567bfe73488a274e URL_HASH MD5=952d4033854612c99a5d9bf4b8550c26
SOURCE_DIR fetched/mygui SOURCE_DIR fetched/mygui
) )
FetchContent_MakeAvailableExcludeFromAll(mygui) FetchContent_MakeAvailableExcludeFromAll(mygui)
@ -81,8 +90,6 @@ endif()
if(NOT OPENMW_USE_SYSTEM_OSG) if(NOT OPENMW_USE_SYSTEM_OSG)
cmake_minimum_required(VERSION 3.11) # for FetchContent cmake_minimum_required(VERSION 3.11) # for FetchContent
set(DYNAMIC_OPENTHREADS OFF CACHE BOOL "")
set(DYNAMIC_OPENSCENEGRAPH OFF CACHE BOOL "")
set(BUILD_OSG_APPLICATIONS OFF CACHE BOOL "") set(BUILD_OSG_APPLICATIONS OFF CACHE BOOL "")
set(BUILD_OSG_DEPRECATED_SERIALIZERS OFF CACHE BOOL "") set(BUILD_OSG_DEPRECATED_SERIALIZERS OFF CACHE BOOL "")
set(OSG_FIND_3RD_PARTY_DEPS OFF CACHE BOOL "") set(OSG_FIND_3RD_PARTY_DEPS OFF CACHE BOOL "")
@ -104,9 +111,33 @@ if(NOT OPENMW_USE_SYSTEM_OSG)
set(OPENGL_PROFILE "GL2" CACHE STRING "") set(OPENGL_PROFILE "GL2" CACHE STRING "")
if(OSG_STATIC) if(OSG_STATIC)
set(BUILD_SHARED_LIBS OFF) set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE)
set(DYNAMIC_OPENTHREADS OFF CACHE BOOL "" FORCE)
set(DYNAMIC_OPENSCENEGRAPH OFF CACHE BOOL "" FORCE)
else() else()
set(BUILD_SHARED_LIBS ON) set(BUILD_SHARED_LIBS ON CACHE BOOL "" FORCE)
set(DYNAMIC_OPENTHREADS ON CACHE BOOL "" FORCE)
set(DYNAMIC_OPENSCENEGRAPH ON CACHE BOOL "" FORCE)
endif()
mark_as_advanced(DYNAMIC_OPENTHREADS DYNAMIC_OPENSCENEGRAPH)
if(WIN32)
# OSG here inherits C++17 language level because it doesn't specify its own.
#
# OSG's `using namespace std` interferes with Windows header files.
#
# See https://developercommunity.visualstudio.com/t/error-c2872-byte-ambiguous-symbol/93889
#
# An alternative way to work around this without changing the language level is:
#
# add_compile_definitions(_HAS_STD_BYTE=0)
#
# TODO: Put OSG into its own scope so that this does not leak into Recast below.
set(CMAKE_CXX_STANDARD 11)
if(MSVC)
set(OSG_MSVC_VERSIONED_DLL OFF CACHE BOOL "")
endif()
endif() endif()
# branch OpenSceneGraph-3.6 on 23 Jan 2021. # branch OpenSceneGraph-3.6 on 23 Jan 2021.

@ -91,7 +91,7 @@ MovieAudioDecoder::~MovieAudioDecoder()
if(mAudioContext) if(mAudioContext)
avcodec_free_context(&mAudioContext); avcodec_free_context(&mAudioContext);
av_freep(&mFrame); av_frame_free(&mFrame);
av_freep(&mDataBuf); av_freep(&mDataBuf);
} }
@ -222,7 +222,7 @@ int MovieAudioDecoder::audio_decode_frame(AVFrame *frame, int &sample_skip)
return result; return result;
} }
av_packet_unref(&mPacket); av_packet_unref(pkt);
mGetNextPacket = true; mGetNextPacket = true;
/* next packet */ /* next packet */

@ -2,6 +2,7 @@
#include <algorithm> #include <algorithm>
#include <cassert> #include <cassert>
#include <cstddef>
#include <iostream> #include <iostream>
#include <thread> #include <thread>
#include <chrono> #include <chrono>
@ -49,7 +50,7 @@ VideoState::VideoState()
, av_sync_type(AV_SYNC_DEFAULT) , av_sync_type(AV_SYNC_DEFAULT)
, audio_st(nullptr) , audio_st(nullptr)
, video_st(nullptr), frame_last_pts(0.0) , video_st(nullptr), frame_last_pts(0.0)
, video_clock(0.0), sws_context(nullptr), rgbaFrame(nullptr), pictq_size(0) , video_clock(0.0), sws_context(nullptr), pictq_size(0)
, pictq_rindex(0), pictq_windex(0) , pictq_rindex(0), pictq_windex(0)
, mSeekRequested(false) , mSeekRequested(false)
, mSeekPos(0) , mSeekPos(0)
@ -82,10 +83,11 @@ void PacketQueue::put(AVPacket *pkt)
pkt1 = (AVPacketList*)av_malloc(sizeof(AVPacketList)); pkt1 = (AVPacketList*)av_malloc(sizeof(AVPacketList));
if(!pkt1) throw std::bad_alloc(); if(!pkt1) throw std::bad_alloc();
if(pkt != &flush_pkt && !pkt->buf && av_packet_ref(&pkt1->pkt, pkt) < 0) if(pkt == &flush_pkt)
throw std::runtime_error("Failed to duplicate packet"); pkt1->pkt = *pkt;
else
av_packet_move_ref(&pkt1->pkt, pkt);
pkt1->pkt = *pkt;
pkt1->next = nullptr; pkt1->next = nullptr;
this->mutex.lock (); this->mutex.lock ();
@ -116,7 +118,8 @@ int PacketQueue::get(AVPacket *pkt, VideoState *is)
this->nb_packets--; this->nb_packets--;
this->size -= pkt1->pkt.size; this->size -= pkt1->pkt.size;
*pkt = pkt1->pkt; av_packet_unref(pkt);
av_packet_move_ref(pkt, &pkt1->pkt);
av_free(pkt1); av_free(pkt1);
return 1; return 1;
@ -155,6 +158,39 @@ void PacketQueue::clear()
this->mutex.unlock (); this->mutex.unlock ();
} }
int VideoPicture::set_dimensions(int w, int h) {
if (this->rgbaFrame != nullptr && this->rgbaFrame->width == w &&
this->rgbaFrame->height == h) {
return 0;
}
std::unique_ptr<AVFrame, VideoPicture::AVFrameDeleter> frame{
av_frame_alloc()};
if (frame == nullptr) {
std::cerr << "av_frame_alloc failed" << std::endl;
return -1;
}
constexpr AVPixelFormat kPixFmt = AV_PIX_FMT_RGBA;
frame->format = kPixFmt;
frame->width = w;
frame->height = h;
if (av_image_alloc(frame->data, frame->linesize, frame->width, frame->height,
kPixFmt, 1) < 0) {
std::cerr << "av_image_alloc failed" << std::endl;
return -1;
}
this->rgbaFrame = std::move(frame);
return 0;
}
void VideoPicture::AVFrameDeleter::operator()(AVFrame* frame) const
{
av_freep(frame->data);
av_frame_free(&frame);
}
int VideoState::istream_read(void *user_data, uint8_t *buf, int buf_size) int VideoState::istream_read(void *user_data, uint8_t *buf, int buf_size)
{ {
try try
@ -220,7 +256,7 @@ void VideoState::video_display(VideoPicture *vp)
osg::ref_ptr<osg::Image> image = new osg::Image; osg::ref_ptr<osg::Image> image = new osg::Image;
image->setImage(this->video_ctx->width, this->video_ctx->height, image->setImage(this->video_ctx->width, this->video_ctx->height,
1, GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, &vp->data[0], osg::Image::NO_DELETE); 1, GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, vp->rgbaFrame->data[0], osg::Image::NO_DELETE);
mTexture->setImage(image); mTexture->setImage(image);
} }
@ -296,23 +332,27 @@ int VideoState::queue_picture(AVFrame *pFrame, double pts)
// Convert the image into RGBA format // Convert the image into RGBA format
// TODO: we could do this in a pixel shader instead, if the source format // TODO: we could do this in a pixel shader instead, if the source format
// matches a commonly used format (ie YUV420P) // matches a commonly used format (ie YUV420P)
if(this->sws_context == nullptr) const int w = pFrame->width;
const int h = pFrame->height;
if(this->sws_context == nullptr || this->sws_context_w != w || this->sws_context_h != h)
{ {
int w = this->video_ctx->width; if (this->sws_context != nullptr)
int h = this->video_ctx->height; sws_freeContext(this->sws_context);
this->sws_context = sws_getContext(w, h, this->video_ctx->pix_fmt, this->sws_context = sws_getContext(w, h, this->video_ctx->pix_fmt,
w, h, AV_PIX_FMT_RGBA, SWS_BICUBIC, w, h, AV_PIX_FMT_RGBA, SWS_BICUBIC,
nullptr, nullptr, nullptr); nullptr, nullptr, nullptr);
if(this->sws_context == nullptr) if(this->sws_context == nullptr)
throw std::runtime_error("Cannot initialize the conversion context!\n"); throw std::runtime_error("Cannot initialize the conversion context!\n");
this->sws_context_w = w;
this->sws_context_h = h;
} }
vp->pts = pts; vp->pts = pts;
vp->data.resize(this->video_ctx->width * this->video_ctx->height * 4); if (vp->set_dimensions(w, h) < 0)
return -1;
uint8_t *dst[4] = { &vp->data[0], nullptr, nullptr, nullptr };
sws_scale(this->sws_context, pFrame->data, pFrame->linesize, sws_scale(this->sws_context, pFrame->data, pFrame->linesize,
0, this->video_ctx->height, dst, this->rgbaFrame->linesize); 0, this->video_ctx->height, vp->rgbaFrame->data, vp->rgbaFrame->linesize);
// now we inform our display thread that we have a pic ready // now we inform our display thread that we have a pic ready
this->pictq_windex = (this->pictq_windex+1) % VIDEO_PICTURE_ARRAY_SIZE; this->pictq_windex = (this->pictq_windex+1) % VIDEO_PICTURE_ARRAY_SIZE;
@ -360,13 +400,11 @@ public:
{ {
VideoState* self = mVideoState; VideoState* self = mVideoState;
AVPacket pkt1, *packet = &pkt1; AVPacket pkt1, *packet = &pkt1;
av_init_packet(packet);
AVFrame *pFrame; AVFrame *pFrame;
pFrame = av_frame_alloc(); pFrame = av_frame_alloc();
self->rgbaFrame = av_frame_alloc();
av_image_alloc(self->rgbaFrame->data, self->rgbaFrame->linesize, self->video_ctx->width, self->video_ctx->height, AV_PIX_FMT_RGBA, 1);
while(self->videoq.get(packet, self) >= 0) while(self->videoq.get(packet, self) >= 0)
{ {
if(packet->data == flush_pkt.data) if(packet->data == flush_pkt.data)
@ -407,10 +445,7 @@ public:
av_packet_unref(packet); av_packet_unref(packet);
av_free(pFrame); av_frame_free(&pFrame);
av_freep(&self->rgbaFrame->data[0]);
av_free(self->rgbaFrame);
} }
private: private:
@ -438,6 +473,7 @@ public:
AVFormatContext *pFormatCtx = self->format_ctx; AVFormatContext *pFormatCtx = self->format_ctx;
AVPacket pkt1, *packet = &pkt1; AVPacket pkt1, *packet = &pkt1;
av_init_packet(packet);
try try
{ {
@ -673,16 +709,21 @@ void VideoState::init(std::shared_ptr<std::istream> inputstream, const std::stri
{ {
if (this->format_ctx->pb != nullptr) if (this->format_ctx->pb != nullptr)
{ {
av_free(this->format_ctx->pb->buffer); av_freep(&this->format_ctx->pb->buffer);
this->format_ctx->pb->buffer = nullptr; #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 80, 100)
avio_context_free(&this->format_ctx->pb);
av_free(this->format_ctx->pb); #else
this->format_ctx->pb = nullptr; av_freep(&this->format_ctx->pb);
#endif
} }
} }
// "Note that a user-supplied AVFormatContext will be freed on failure." // "Note that a user-supplied AVFormatContext will be freed on failure."
this->format_ctx = nullptr; this->format_ctx = nullptr;
av_free(ioCtx); #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 80, 100)
avio_context_free(&ioCtx);
#else
av_freep(&ioCtx);
#endif
throw std::runtime_error("Failed to open video input"); throw std::runtime_error("Failed to open video input");
} }
@ -756,11 +797,12 @@ void VideoState::deinit()
/// ///
if (this->format_ctx->pb != nullptr) if (this->format_ctx->pb != nullptr)
{ {
av_free(this->format_ctx->pb->buffer); av_freep(&this->format_ctx->pb->buffer);
this->format_ctx->pb->buffer = nullptr; #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 80, 100)
avio_context_free(&this->format_ctx->pb);
av_free(this->format_ctx->pb); #else
this->format_ctx->pb = nullptr; av_freep(&this->format_ctx->pb);
#endif
} }
avformat_close_input(&this->format_ctx); avformat_close_input(&this->format_ctx);
} }
@ -771,6 +813,11 @@ void VideoState::deinit()
mTexture->setImage(nullptr); mTexture->setImage(nullptr);
mTexture = nullptr; mTexture = nullptr;
} }
// Dellocate RGBA frame queue.
for (std::size_t i = 0; i < VIDEO_PICTURE_ARRAY_SIZE; ++i)
this->pictq[i].rgbaFrame = nullptr;
} }
double VideoState::get_external_clock() double VideoState::get_external_clock()

@ -95,7 +95,16 @@ struct VideoPicture {
VideoPicture() : pts(0.0) VideoPicture() : pts(0.0)
{ } { }
std::vector<uint8_t> data; struct AVFrameDeleter {
void operator()(AVFrame* frame) const;
};
// Sets frame dimensions.
// Must be called before writing to `rgbaFrame`.
// Return -1 on error.
int set_dimensions(int w, int h);
std::unique_ptr<AVFrame, AVFrameDeleter> rgbaFrame;
double pts; double pts;
}; };
@ -159,8 +168,8 @@ struct VideoState {
double video_clock; ///<pts of last decoded frame / predicted pts of next decoded frame double video_clock; ///<pts of last decoded frame / predicted pts of next decoded frame
PacketQueue videoq; PacketQueue videoq;
SwsContext* sws_context; SwsContext* sws_context;
int sws_context_w, sws_context_h;
VideoPicture pictq[VIDEO_PICTURE_ARRAY_SIZE]; VideoPicture pictq[VIDEO_PICTURE_ARRAY_SIZE];
AVFrame* rgbaFrame; // used as buffer for the frame converted from its native format to RGBA
int pictq_size, pictq_rindex, pictq_windex; int pictq_size, pictq_rindex, pictq_windex;
std::mutex pictq_mutex; std::mutex pictq_mutex;
std::condition_variable pictq_cond; std::condition_variable pictq_cond;

@ -302,7 +302,6 @@
</Widget> </Widget>
<Widget type="TextBox" skin="SandText" position="182 94 300 32" align="Left Top"> <Widget type="TextBox" skin="SandText" position="182 94 300 32" align="Left Top">
<Property key="MultiLine" value="true"/>
<Property key="Caption" value="Hint: press F3 to show \nthe current frame rate."/> <Property key="Caption" value="Hint: press F3 to show \nthe current frame rate."/>
</Widget> </Widget>

@ -193,7 +193,6 @@
<Property key="Shrink" value="true"/> <Property key="Shrink" value="true"/>
<Property key="WordWrap" value="true"/> <Property key="WordWrap" value="true"/>
<Property key="TextAlign" value="Left Top"/> <Property key="TextAlign" value="Left Top"/>
<Property key="Spacing" value="28"/>
<UserString key="HStretch" value="true"/> <UserString key="HStretch" value="true"/>
</Widget> </Widget>
@ -248,7 +247,6 @@
<Property key="MultiLine" value="true"/> <Property key="MultiLine" value="true"/>
<Property key="WordWrap" value="true"/> <Property key="WordWrap" value="true"/>
<Property key="TextAlign" value="Left Top"/> <Property key="TextAlign" value="Left Top"/>
<Property key="Spacing" value="28"/>
</Widget> </Widget>
</Widget> </Widget>
@ -269,9 +267,6 @@
</Widget> </Widget>
<Widget type="AutoSizedTextBox" skin="SandText" position="0 0 0 0" align="Left Top" name="LevelDetailText"> <Widget type="AutoSizedTextBox" skin="SandText" position="0 0 0 0" align="Left Top" name="LevelDetailText">
<Property key="AutoResize" value="true"/>
<Property key="MultiLine" value="true"/>
<Property key="Shrink" value="true"/>
<Property key="TextAlign" value="HCenter Top"/> <Property key="TextAlign" value="HCenter Top"/>
</Widget> </Widget>
</Widget> </Widget>

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save