1
0
Fork 1
mirror of https://github.com/TES3MP/openmw-tes3mp.git synced 2025-02-22 22:39:51 +00:00

Merge branch 'master' of https://gitlab.com/madsbuvi/openmw into multiview_test_branch

This commit is contained in:
Mads Buvik Sandvei 2020-10-08 19:28:51 +02:00
commit 70e8e818b6
67 changed files with 1905 additions and 684 deletions

View file

@ -1,33 +1,67 @@
stages:
- build
Debian:
.Debian:
tags:
- docker
- linux
image: debian:bullseye
cache:
key: cache.002
paths:
- apt-cache/
- ccache/
before_script:
- export APT_CACHE_DIR=`pwd`/apt-cache && mkdir -pv $APT_CACHE_DIR
- apt-get update -yq
- apt-get -o dir::cache::archives="$APT_CACHE_DIR" install -y cmake build-essential libboost-filesystem-dev libboost-program-options-dev libboost-system-dev libboost-iostreams-dev libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev libsdl2-dev libqt5opengl5-dev libopenal-dev libopenscenegraph-dev libunshield-dev libtinyxml-dev libmygui-dev libbullet-dev ccache
- apt-get -o dir::cache::archives="$APT_CACHE_DIR" install -y cmake build-essential libboost-filesystem-dev libboost-program-options-dev libboost-system-dev libboost-iostreams-dev libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev libsdl2-dev libqt5opengl5-dev libopenal-dev libopenscenegraph-dev libunshield-dev libtinyxml-dev libmygui-dev libbullet-dev ccache git clang
stage: build
script:
- export CCACHE_BASEDIR="`pwd`"
- export CCACHE_DIR="`pwd`/ccache" && mkdir -pv "$CCACHE_DIR"
- ccache -z -M 250M
- cores_to_use=$((`nproc`-2)); if (( $cores_to_use < 1 )); then cores_to_use=1; fi
- mkdir build; cd build; cmake -DCMAKE_BUILD_TYPE=MinSizeRel ../ -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache
- make -j$cores_to_use
- DESTDIR=artifacts make install
- ccache -z -M 1G
- CI/before_script.linux.sh
- cd build
- cmake --build . -- -j $(nproc)
- cmake --install .
- if [[ "${BUILD_TESTS_ONLY}" ]]; then ./openmw_test_suite; fi
- ccache -s
artifacts:
paths:
- build/artifacts/
- build/install/
Debian_GCC:
extends: .Debian
cache:
key: Debian_GCC.v1
variables:
CC: gcc
CXX: g++
Debian_GCC_tests:
extends: .Debian
cache:
key: Debian_GCC_tests.v1
variables:
CC: gcc
CXX: g++
BUILD_TESTS_ONLY: 1
Debian_Clang:
extends: .Debian
cache:
key: Debian_Clang.v1
variables:
CC: clang
CXX: clang++
Debian_Clang_tests:
extends: .Debian
cache:
key: Debian_Clang_tests.v1
variables:
CC: clang
CXX: clang++
BUILD_TESTS_ONLY: 1
MacOS:
tags:
@ -228,4 +262,4 @@ Windows_MSBuild_CS_RelWithDebInfo:
- .Windows_MSBuild_Base
variables:
<<: *cs-targets
config: "RelWithDebInfo"
config: "RelWithDebInfo"

View file

@ -45,6 +45,12 @@ matrix:
os: linux
dist: focal
if: branch != coverity_scan
- name: OpenMW (tests only) on Ubuntu Focal with GCC
os: linux
dist: focal
if: branch != coverity_scan
env:
- BUILD_TESTS_ONLY: 1
- name: OpenMW (openmw) on Ubuntu Focal with Clang's Static Analysis
os: linux
dist: focal
@ -73,7 +79,7 @@ script:
- if [ "${COVERITY_SCAN_BRANCH}" != 1 ]; then ${ANALYZE} make -j3; fi
- if [ "${COVERITY_SCAN_BRANCH}" != 1 ] && [ "${TRAVIS_OS_NAME}" = "osx" ]; then make package; fi
- if [ "${COVERITY_SCAN_BRANCH}" != 1 ] && [ "${TRAVIS_OS_NAME}" = "osx" ]; then ../CI/check_package.osx.sh; fi
- if [ "${COVERITY_SCAN_BRANCH}" != 1 ] && [ "${TRAVIS_OS_NAME}" = "linux" ]; then ./openmw_test_suite; fi
- if [ "${COVERITY_SCAN_BRANCH}" != 1 ] && [ "${TRAVIS_OS_NAME}" = "linux" ] && [ "${BUILD_TESTS_ONLY}" ]; then ./openmw_test_suite; fi
- if [ "${COVERITY_SCAN_BRANCH}" != 1 ] && [ "${TRAVIS_OS_NAME}" = "linux" ]; then cd .. && ./CI/check_tabs.sh; fi
- cd "${TRAVIS_BUILD_DIR}"
- ccache -s

View file

@ -3,14 +3,17 @@
Bug #1662: Qt4 and Windows binaries crash if there's a non-ASCII character in a file path/config path
Bug #1952: Incorrect particle lighting
Bug #2069: Fireflies in Fireflies invade Morrowind look wrong
Bug #2311: Targeted scripts are not properly supported on non-unique RefIDs
Bug #3676: NiParticleColorModifier isn't applied properly
Bug #3714: Savegame fails to load due to conflict between SpellState and MagicEffects
Bug #4021: Attributes and skills are not stored as floats
Bug #4055: Local scripts don't inherit variables from their base record
Bug #4623: Corprus implementation is incorrect
Bug #4631: Setting MSAA level too high doesn't fall back to highest supported level
Bug #4764: Data race in osg ParticleSystem
Bug #4774: Guards are ignorant of an invisible player that tries to attack them
Bug #5101: Hostile followers travel with the player
Bug #5108: Savegame bloating due to inefficient fog textures format
Bug #5165: Active spells should use real time intead of timestamps
Bug #5358: ForceGreeting always resets the dialogue window completely
@ -45,8 +48,12 @@
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 #5557: Diagonal movement is noticeably slower with analogue stick
Bug #5588: Randomly clicking on the journal's right-side page when it's empty shows random topics
Bug #5603: Setting constant effect cast style doesn't correct effects view
Bug #5611: Usable items with "0 Uses" should be used only once
Feature #390: 3rd person look "over the shoulder"
Feature #2386: Distant Statics in the form of Object Paging
Feature #4894: Consider actors as obstacles for pathfinding
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 #5445: Handle NiLines
@ -56,6 +63,8 @@
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 #5579: MCP SetAngle enhancement
Feature #5610: Actors movement should be smoother
Task #5480: Drop Qt4 support
Task #5520: Improve cell name autocompleter implementation

View file

@ -2,22 +2,43 @@
free -m
env GENERATOR='Unix Makefiles' CONFIGURATION=Release CI/build_googletest.sh
GOOGLETEST_DIR="$(pwd)/googletest/build"
if [[ "${BUILD_TESTS_ONLY}" ]]; then
export GOOGLETEST_DIR="$(pwd)/googletest/build/install"
env GENERATOR='Unix Makefiles' CONFIGURATION=Release CI/build_googletest.sh
fi
mkdir build
cd build
${ANALYZE} cmake \
-DCMAKE_C_COMPILER="${CC}" \
-DCMAKE_CXX_COMPILER="${CXX}" \
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
-DBUILD_UNITTESTS=TRUE \
-DUSE_SYSTEM_TINYXML=TRUE \
-DCMAKE_INSTALL_PREFIX="/usr" \
-DBINDIR="/usr/games" \
-DCMAKE_BUILD_TYPE="DEBUG" \
-DGTEST_ROOT="${GOOGLETEST_DIR}" \
-DGMOCK_ROOT="${GOOGLETEST_DIR}" \
..
if [[ "${BUILD_TESTS_ONLY}" ]]; then
${ANALYZE} cmake \
-D CMAKE_C_COMPILER="${CC}" \
-D CMAKE_CXX_COMPILER="${CXX}" \
-D CMAKE_C_COMPILER_LAUNCHER=ccache \
-D CMAKE_CXX_COMPILER_LAUNCHER=ccache \
-D CMAKE_INSTALL_PREFIX=install \
-D CMAKE_BUILD_TYPE=RelWithDebInfo \
-D USE_SYSTEM_TINYXML=TRUE \
-D BUILD_OPENMW=OFF \
-D BUILD_BSATOOL=OFF \
-D BUILD_ESMTOOL=OFF \
-D BUILD_LAUNCHER=OFF \
-D BUILD_MWINIIMPORTER=OFF \
-D BUILD_ESSIMPORTER=OFF \
-D BUILD_OPENCS=OFF \
-D BUILD_WIZARD=OFF \
-D BUILD_UNITTESTS=ON \
-D GTEST_ROOT="${GOOGLETEST_DIR}" \
-D GMOCK_ROOT="${GOOGLETEST_DIR}" \
..
else
${ANALYZE} cmake \
-D CMAKE_C_COMPILER="${CC}" \
-D CMAKE_CXX_COMPILER="${CXX}" \
-D CMAKE_C_COMPILER_LAUNCHER=ccache \
-D CMAKE_CXX_COMPILER_LAUNCHER=ccache \
-D USE_SYSTEM_TINYXML=TRUE \
-D CMAKE_INSTALL_PREFIX=install \
-D CMAKE_BUILD_TYPE=Debug \
..
fi

View file

@ -523,52 +523,52 @@ if [ -z $SKIP_DOWNLOAD ]; then
# Boost
if [ -z $APPVEYOR ]; then
download "Boost ${BOOST_VER}" \
"https://sourceforge.net/projects/boost/files/boost-binaries/${BOOST_VER}/boost_${BOOST_VER_URL}-msvc-${MSVC_VER}-${BITS}.exe" \
"https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/boost_${BOOST_VER_URL}-msvc-${MSVC_VER}-${BITS}.exe" \
"boost-${BOOST_VER}-msvc${MSVC_VER}-win${BITS}.exe"
fi
# Bullet
download "Bullet 2.89 (${BULLET_DBL_DISPLAY})" \
"https://rgw.ctrl-c.liu.se/openmw/Deps/Bullet-2.89-msvc${MSVC_YEAR}-win${BITS}${BULLET_DBL}.7z" \
"https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/Bullet-2.89-msvc${MSVC_YEAR}-win${BITS}${BULLET_DBL}.7z" \
"Bullet-2.89-msvc${MSVC_YEAR}-win${BITS}${BULLET_DBL}.7z"
# FFmpeg
download "FFmpeg 4.2.2" \
"https://ffmpeg.zeranoe.com/builds/win${BITS}/shared/ffmpeg-4.2.2-win${BITS}-shared.zip" \
"https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/ffmpeg-4.2.2-win${BITS}.zip" \
"ffmpeg-4.2.2-win${BITS}.zip" \
"https://ffmpeg.zeranoe.com/builds/win${BITS}/dev/ffmpeg-4.2.2-win${BITS}-dev.zip" \
"https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/ffmpeg-4.2.2-dev-win${BITS}.zip" \
"ffmpeg-4.2.2-dev-win${BITS}.zip"
# MyGUI
download "MyGUI 3.4.0" \
"https://rgw.ctrl-c.liu.se/openmw/Deps/MyGUI-3.4.0-msvc${MSVC_REAL_YEAR}-win${BITS}.7z" \
"https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/MyGUI-3.4.0-msvc${MSVC_REAL_YEAR}-win${BITS}.7z" \
"MyGUI-3.4.0-msvc${MSVC_REAL_YEAR}-win${BITS}.7z"
if [ -n "$PDBS" ]; then
download "MyGUI symbols" \
"https://rgw.ctrl-c.liu.se/openmw/Deps/MyGUI-3.4.0-msvc${MSVC_REAL_YEAR}-win${BITS}-sym.7z" \
"https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/MyGUI-3.4.0-msvc${MSVC_REAL_YEAR}-win${BITS}-sym.7z" \
"MyGUI-3.4.0-msvc${MSVC_REAL_YEAR}-win${BITS}-sym.7z"
fi
# OpenAL
download "OpenAL-Soft 1.20.1" \
"http://openal-soft.org/openal-binaries/openal-soft-1.20.1-bin.zip" \
"https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/OpenAL-Soft-1.20.1.zip" \
"OpenAL-Soft-1.20.1.zip"
# OSG
download "OpenSceneGraph 3.6.5" \
"https://rgw.ctrl-c.liu.se/openmw/Deps/OSG-3.6.5-msvc${MSVC_REAL_YEAR}-win${BITS}.7z" \
"https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/OSG-3.6.5-msvc${MSVC_REAL_YEAR}-win${BITS}.7z" \
"OSG-3.6.5-msvc${MSVC_REAL_YEAR}-win${BITS}.7z"
if [ -n "$PDBS" ]; then
download "OpenSceneGraph symbols" \
"https://rgw.ctrl-c.liu.se/openmw/Deps/OSG-3.6.5-msvc${MSVC_REAL_YEAR}-win${BITS}-sym.7z" \
"https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/OSG-3.6.5-msvc${MSVC_REAL_YEAR}-win${BITS}-sym.7z" \
"OSG-3.6.5-msvc${MSVC_REAL_YEAR}-win${BITS}-sym.7z"
fi
# SDL2
download "SDL 2.0.12" \
"https://www.libsdl.org/release/SDL2-devel-2.0.12-VC.zip" \
"https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/SDL2-2.0.12.zip" \
"SDL2-2.0.12.zip"
# Google test and mock

View file

@ -1,13 +1,17 @@
#!/bin/sh -e
#!/bin/sh -ex
git clone -b release-1.10.0 https://github.com/google/googletest.git
cd googletest
mkdir build
cd build
cmake \
-D CMAKE_C_COMPILER="${CC}" \
-D CMAKE_CXX_COMPILER="${CXX}" \
-D CMAKE_C_COMPILER_LAUNCHER=ccache \
-D CMAKE_CXX_COMPILER_LAUNCHER=ccache \
-D CMAKE_BUILD_TYPE="${CONFIGURATION}" \
-D CMAKE_INSTALL_PREFIX=. \
-D CMAKE_INSTALL_PREFIX="${GOOGLETEST_DIR}" \
-G "${GENERATOR}" \
..
cmake --build . --config "${CONFIGURATION}"
cmake --build . --target install --config "${CONFIGURATION}"
cmake --build . --config "${CONFIGURATION}" -- -j $(nproc)
cmake --install . --config "${CONFIGURATION}"

View file

@ -435,152 +435,6 @@ elseif (MSVC)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /FORCE:MULTIPLE")
endif (CMAKE_CXX_COMPILER_ID STREQUAL GNU OR CMAKE_CXX_COMPILER_ID STREQUAL Clang)
IF(NOT WIN32 AND NOT APPLE)
# Linux installation
# Install binaries
IF(BUILD_OPENMW)
INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/openmw" DESTINATION "${BINDIR}" )
ENDIF(BUILD_OPENMW)
IF(BUILD_LAUNCHER)
INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/openmw-launcher" DESTINATION "${BINDIR}" )
ENDIF(BUILD_LAUNCHER)
IF(BUILD_BSATOOL)
INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/bsatool" DESTINATION "${BINDIR}" )
ENDIF(BUILD_BSATOOL)
IF(BUILD_ESMTOOL)
INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/esmtool" DESTINATION "${BINDIR}" )
ENDIF(BUILD_ESMTOOL)
IF(BUILD_NIFTEST)
INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/niftest" DESTINATION "${BINDIR}" )
ENDIF(BUILD_NIFTEST)
IF(BUILD_MWINIIMPORTER)
INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/openmw-iniimporter" DESTINATION "${BINDIR}" )
ENDIF(BUILD_MWINIIMPORTER)
IF(BUILD_ESSIMPORTER)
INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/openmw-essimporter" DESTINATION "${BINDIR}" )
ENDIF(BUILD_ESSIMPORTER)
IF(BUILD_OPENCS)
INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/openmw-cs" DESTINATION "${BINDIR}" )
ENDIF(BUILD_OPENCS)
IF(BUILD_WIZARD)
INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/openmw-wizard" DESTINATION "${BINDIR}" )
ENDIF(BUILD_WIZARD)
# Install licenses
INSTALL(FILES "files/mygui/DejaVuFontLicense.txt" DESTINATION "${LICDIR}" )
# Install icon and desktop file
INSTALL(FILES "${OpenMW_BINARY_DIR}/org.openmw.launcher.desktop" DESTINATION "${DATAROOTDIR}/applications" COMPONENT "openmw")
INSTALL(FILES "${OpenMW_SOURCE_DIR}/files/launcher/images/openmw.png" DESTINATION "${ICONDIR}" COMPONENT "openmw")
INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw.appdata.xml" DESTINATION "${DATAROOTDIR}/metainfo" COMPONENT "openmw")
IF(BUILD_OPENCS)
INSTALL(FILES "${OpenMW_BINARY_DIR}/org.openmw.cs.desktop" DESTINATION "${DATAROOTDIR}/applications" COMPONENT "opencs")
INSTALL(FILES "${OpenMW_SOURCE_DIR}/files/opencs/openmw-cs.png" DESTINATION "${ICONDIR}" COMPONENT "opencs")
ENDIF(BUILD_OPENCS)
# Install global configuration files
INSTALL(FILES "${OpenMW_BINARY_DIR}/settings-default.cfg" DESTINATION "${SYSCONFDIR}" COMPONENT "openmw")
INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw.cfg.install" DESTINATION "${SYSCONFDIR}" RENAME "openmw.cfg" COMPONENT "openmw")
INSTALL(FILES "${OpenMW_BINARY_DIR}/resources/version" DESTINATION "${SYSCONFDIR}" COMPONENT "openmw")
INSTALL(FILES "${OpenMW_BINARY_DIR}/gamecontrollerdb.txt" DESTINATION "${SYSCONFDIR}" COMPONENT "openmw")
IF(BUILD_OPENCS)
INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw-cs.cfg" DESTINATION "${SYSCONFDIR}" COMPONENT "opencs")
ENDIF(BUILD_OPENCS)
# Install resources
INSTALL(DIRECTORY "${OpenMW_BINARY_DIR}/resources" DESTINATION "${DATADIR}" COMPONENT "Resources")
INSTALL(DIRECTORY DESTINATION "${DATADIR}/data" COMPONENT "Resources")
ENDIF(NOT WIN32 AND NOT APPLE)
if(WIN32)
FILE(GLOB dll_files_debug "${OpenMW_BINARY_DIR}/Debug/*.dll")
FILE(GLOB dll_files_release "${OpenMW_BINARY_DIR}/Release/*.dll")
INSTALL(FILES ${dll_files_debug} DESTINATION "." CONFIGURATIONS Debug)
INSTALL(FILES ${dll_files_release} DESTINATION "." CONFIGURATIONS Release;RelWithDebInfo;MinSizeRel)
INSTALL(FILES "${OpenMW_BINARY_DIR}/Debug/openmw.cfg.install" DESTINATION "." RENAME "openmw.cfg" CONFIGURATIONS Debug)
INSTALL(FILES "${OpenMW_BINARY_DIR}/Release/openmw.cfg.install" DESTINATION "." RENAME "openmw.cfg" CONFIGURATIONS Release;RelWithDebInfo;MinSizeRel)
INSTALL(FILES "${OpenMW_SOURCE_DIR}/CHANGELOG.md" DESTINATION "." RENAME "CHANGELOG.txt")
INSTALL(FILES "${OpenMW_SOURCE_DIR}/README.md" DESTINATION "." RENAME "README.txt")
INSTALL(FILES "${OpenMW_SOURCE_DIR}/LICENSE" DESTINATION "." RENAME "LICENSE.txt")
INSTALL(FILES "${OpenMW_SOURCE_DIR}/files/mygui/DejaVuFontLicense.txt" DESTINATION ".")
INSTALL(FILES "${OpenMW_BINARY_DIR}/Debug/settings-default.cfg" DESTINATION "." CONFIGURATIONS Debug)
INSTALL(FILES "${OpenMW_BINARY_DIR}/Release/settings-default.cfg" DESTINATION "." CONFIGURATIONS Release;RelWithDebInfo;MinSizeRel)
INSTALL(FILES "${OpenMW_BINARY_DIR}/Debug/gamecontrollerdb.txt" DESTINATION "." CONFIGURATIONS Debug)
INSTALL(FILES "${OpenMW_BINARY_DIR}/Release/gamecontrollerdb.txt" DESTINATION "." CONFIGURATIONS Release;RelWithDebInfo;MinSizeRel)
INSTALL(DIRECTORY "${OpenMW_BINARY_DIR}/Debug/platforms" DESTINATION "." CONFIGURATIONS Debug)
INSTALL(DIRECTORY "${OpenMW_BINARY_DIR}/Release/platforms" DESTINATION "." CONFIGURATIONS Release;RelWithDebInfo;MinSizeRel)
INSTALL(DIRECTORY "${OpenMW_BINARY_DIR}/Debug/resources" DESTINATION "." CONFIGURATIONS Debug)
INSTALL(DIRECTORY "${OpenMW_BINARY_DIR}/Release/resources" DESTINATION "." CONFIGURATIONS Release;RelWithDebInfo;MinSizeRel)
FILE(GLOB plugin_dir_debug "${OpenMW_BINARY_DIR}/Debug/osgPlugins-*")
FILE(GLOB plugin_dir_release "${OpenMW_BINARY_DIR}/Release/osgPlugins-*")
INSTALL(DIRECTORY ${plugin_dir_debug} DESTINATION "." CONFIGURATIONS Debug)
INSTALL(DIRECTORY ${plugin_dir_release} DESTINATION "." CONFIGURATIONS Release;RelWithDebInfo;MinSizeRel)
SET(CPACK_GENERATOR "NSIS")
SET(CPACK_PACKAGE_NAME "OpenMW")
SET(CPACK_PACKAGE_VENDOR "OpenMW.org")
SET(CPACK_PACKAGE_VERSION ${OPENMW_VERSION})
SET(CPACK_PACKAGE_VERSION_MAJOR ${OPENMW_VERSION_MAJOR})
SET(CPACK_PACKAGE_VERSION_MINOR ${OPENMW_VERSION_MINOR})
SET(CPACK_PACKAGE_VERSION_PATCH ${OPENMW_VERSION_RELEASE})
SET(CPACK_PACKAGE_EXECUTABLES "openmw;OpenMW")
IF(BUILD_LAUNCHER)
SET(CPACK_PACKAGE_EXECUTABLES "${CPACK_PACKAGE_EXECUTABLES};openmw-launcher;OpenMW Launcher")
ENDIF(BUILD_LAUNCHER)
IF(BUILD_OPENCS)
SET(CPACK_PACKAGE_EXECUTABLES "${CPACK_PACKAGE_EXECUTABLES};openmw-cs;OpenMW Construction Set")
ENDIF(BUILD_OPENCS)
IF(BUILD_WIZARD)
SET(CPACK_PACKAGE_EXECUTABLES "${CPACK_PACKAGE_EXECUTABLES};openmw-wizard;OpenMW Wizard")
ENDIF(BUILD_WIZARD)
SET(CPACK_NSIS_CREATE_ICONS_EXTRA "CreateShortCut '\$SMPROGRAMS\\\\$STARTMENU_FOLDER\\\\Readme.lnk' '\$INSTDIR\\\\README.txt'")
SET(CPACK_NSIS_DELETE_ICONS_EXTRA "
!insertmacro MUI_STARTMENU_GETFOLDER Application $MUI_TEMP
Delete \\\"$SMPROGRAMS\\\\$MUI_TEMP\\\\Readme.lnk\\\"
")
SET(CPACK_RESOURCE_FILE_README "${OpenMW_SOURCE_DIR}/README.md")
SET(CPACK_PACKAGE_DESCRIPTION_FILE "${OpenMW_SOURCE_DIR}/README.md")
SET(CPACK_NSIS_EXECUTABLES_DIRECTORY ".")
SET(CPACK_NSIS_DISPLAY_NAME "OpenMW ${OPENMW_VERSION}")
SET(CPACK_NSIS_HELP_LINK "https:\\\\\\\\www.openmw.org")
SET(CPACK_NSIS_URL_INFO_ABOUT "https:\\\\\\\\www.openmw.org")
SET(CPACK_NSIS_INSTALLED_ICON_NAME "openmw-launcher.exe")
SET(CPACK_NSIS_MUI_FINISHPAGE_RUN "openmw-launcher.exe")
SET(CPACK_NSIS_MUI_ICON "${OpenMW_SOURCE_DIR}/files/windows/openmw.ico")
SET(CPACK_NSIS_MUI_UNIICON "${OpenMW_SOURCE_DIR}/files/windows/openmw.ico")
SET(CPACK_PACKAGE_ICON "${OpenMW_SOURCE_DIR}\\\\files\\\\openmw.bmp")
SET(VCREDIST32 "${OpenMW_BINARY_DIR}/vcredist_x86.exe")
if(EXISTS ${VCREDIST32})
INSTALL(FILES ${VCREDIST32} DESTINATION "redist")
SET(CPACK_NSIS_EXTRA_INSTALL_COMMANDS "ExecWait '\\\"$INSTDIR\\\\redist\\\\vcredist_x86.exe\\\" /q'" )
endif(EXISTS ${VCREDIST32})
SET(VCREDIST64 "${OpenMW_BINARY_DIR}/vcredist_x64.exe")
if(EXISTS ${VCREDIST64})
INSTALL(FILES ${VCREDIST64} DESTINATION "redist")
SET(CPACK_NSIS_EXTRA_INSTALL_COMMANDS "ExecWait '\\\"$INSTDIR\\\\redist\\\\vcredist_x64.exe\\\" /q'" )
endif(EXISTS ${VCREDIST64})
SET(OALREDIST "${OpenMW_BINARY_DIR}/oalinst.exe")
if(EXISTS ${OALREDIST})
INSTALL(FILES ${OALREDIST} DESTINATION "redist")
SET(CPACK_NSIS_EXTRA_INSTALL_COMMANDS "${CPACK_NSIS_EXTRA_INSTALL_COMMANDS}
ExecWait '\\\"$INSTDIR\\\\redist\\\\oalinst.exe\\\" /s'" )
endif(EXISTS ${OALREDIST})
if(CMAKE_CL_64)
SET(CPACK_NSIS_INSTALL_ROOT "$PROGRAMFILES64")
endif()
include(CPack)
endif(WIN32)
# Extern
set(RECASTNAVIGATION_STATIC ON CACHE BOOL "Build recastnavigation static libraries")
@ -875,7 +729,160 @@ if (OPENMW_OSX_DEPLOYMENT AND APPLE)
fixup_bundle(\"${INSTALLED_OPENCS_APP}\" \"${OPENCS_PLUGINS}\" \"\")
" COMPONENT Runtime)
include(CPack)
endif ()
elseif(NOT APPLE)
get_generator_is_multi_config(multi_config)
if (multi_config)
SET(INSTALL_SOURCE "${OpenMW_BINARY_DIR}/$<CONFIG>")
else ()
SET(INSTALL_SOURCE "${OpenMW_BINARY_DIR}")
endif ()
if(WIN32)
INSTALL(DIRECTORY "${INSTALL_SOURCE}/" DESTINATION "." FILES_MATCHING PATTERN "*.dll"
PATTERN "deps" EXCLUDE
PATTERN "apps" EXCLUDE
PATTERN "CMakeFiles" EXCLUDE
PATTERN "components" EXCLUDE
PATTERN "docs" EXCLUDE
PATTERN "extern" EXCLUDE
PATTERN "files" EXCLUDE
PATTERN "Testing" EXCLUDE)
INSTALL(DIRECTORY "${INSTALL_SOURCE}/" DESTINATION "." CONFIGURATIONS Debug;RelWithDebInfo FILES_MATCHING PATTERN "*.pdb"
PATTERN "deps" EXCLUDE
PATTERN "apps" EXCLUDE
PATTERN "CMakeFiles" EXCLUDE
PATTERN "components" EXCLUDE
PATTERN "docs" EXCLUDE
PATTERN "extern" EXCLUDE
PATTERN "files" EXCLUDE
PATTERN "Testing" EXCLUDE)
INSTALL(FILES "${INSTALL_SOURCE}/openmw.cfg.install" DESTINATION "." RENAME "openmw.cfg")
INSTALL(FILES "${OpenMW_SOURCE_DIR}/CHANGELOG.md" DESTINATION "." RENAME "CHANGELOG.txt")
INSTALL(FILES "${OpenMW_SOURCE_DIR}/README.md" DESTINATION "." RENAME "README.txt")
INSTALL(FILES "${OpenMW_SOURCE_DIR}/LICENSE" DESTINATION "." RENAME "LICENSE.txt")
INSTALL(FILES "${OpenMW_SOURCE_DIR}/files/mygui/DejaVuFontLicense.txt" DESTINATION ".")
INSTALL(FILES "${INSTALL_SOURCE}/settings-default.cfg" DESTINATION ".")
INSTALL(FILES "${INSTALL_SOURCE}/gamecontrollerdb.txt" DESTINATION ".")
INSTALL(DIRECTORY "${INSTALL_SOURCE}/resources" DESTINATION ".")
SET(CPACK_GENERATOR "NSIS")
SET(CPACK_PACKAGE_NAME "OpenMW")
SET(CPACK_PACKAGE_VENDOR "OpenMW.org")
SET(CPACK_PACKAGE_VERSION ${OPENMW_VERSION})
SET(CPACK_PACKAGE_VERSION_MAJOR ${OPENMW_VERSION_MAJOR})
SET(CPACK_PACKAGE_VERSION_MINOR ${OPENMW_VERSION_MINOR})
SET(CPACK_PACKAGE_VERSION_PATCH ${OPENMW_VERSION_RELEASE})
SET(CPACK_PACKAGE_EXECUTABLES "openmw;OpenMW")
IF(BUILD_LAUNCHER)
SET(CPACK_PACKAGE_EXECUTABLES "${CPACK_PACKAGE_EXECUTABLES};openmw-launcher;OpenMW Launcher")
ENDIF(BUILD_LAUNCHER)
IF(BUILD_OPENCS)
SET(CPACK_PACKAGE_EXECUTABLES "${CPACK_PACKAGE_EXECUTABLES};openmw-cs;OpenMW Construction Set")
ENDIF(BUILD_OPENCS)
IF(BUILD_WIZARD)
SET(CPACK_PACKAGE_EXECUTABLES "${CPACK_PACKAGE_EXECUTABLES};openmw-wizard;OpenMW Wizard")
ENDIF(BUILD_WIZARD)
SET(CPACK_NSIS_CREATE_ICONS_EXTRA "CreateShortCut '\$SMPROGRAMS\\\\$STARTMENU_FOLDER\\\\Readme.lnk' '\$INSTDIR\\\\README.txt'")
SET(CPACK_NSIS_DELETE_ICONS_EXTRA "
!insertmacro MUI_STARTMENU_GETFOLDER Application $MUI_TEMP
Delete \\\"$SMPROGRAMS\\\\$MUI_TEMP\\\\Readme.lnk\\\"
")
SET(CPACK_RESOURCE_FILE_README "${OpenMW_SOURCE_DIR}/README.md")
SET(CPACK_PACKAGE_DESCRIPTION_FILE "${OpenMW_SOURCE_DIR}/README.md")
SET(CPACK_NSIS_EXECUTABLES_DIRECTORY ".")
SET(CPACK_NSIS_DISPLAY_NAME "OpenMW ${OPENMW_VERSION}")
SET(CPACK_NSIS_HELP_LINK "https:\\\\\\\\www.openmw.org")
SET(CPACK_NSIS_URL_INFO_ABOUT "https:\\\\\\\\www.openmw.org")
SET(CPACK_NSIS_INSTALLED_ICON_NAME "openmw-launcher.exe")
SET(CPACK_NSIS_MUI_FINISHPAGE_RUN "openmw-launcher.exe")
SET(CPACK_NSIS_MUI_ICON "${OpenMW_SOURCE_DIR}/files/windows/openmw.ico")
SET(CPACK_NSIS_MUI_UNIICON "${OpenMW_SOURCE_DIR}/files/windows/openmw.ico")
SET(CPACK_PACKAGE_ICON "${OpenMW_SOURCE_DIR}\\\\files\\\\openmw.bmp")
SET(VCREDIST32 "${OpenMW_BINARY_DIR}/vcredist_x86.exe")
if(EXISTS ${VCREDIST32})
INSTALL(FILES ${VCREDIST32} DESTINATION "redist")
SET(CPACK_NSIS_EXTRA_INSTALL_COMMANDS "ExecWait '\\\"$INSTDIR\\\\redist\\\\vcredist_x86.exe\\\" /q'" )
endif(EXISTS ${VCREDIST32})
SET(VCREDIST64 "${OpenMW_BINARY_DIR}/vcredist_x64.exe")
if(EXISTS ${VCREDIST64})
INSTALL(FILES ${VCREDIST64} DESTINATION "redist")
SET(CPACK_NSIS_EXTRA_INSTALL_COMMANDS "ExecWait '\\\"$INSTDIR\\\\redist\\\\vcredist_x64.exe\\\" /q'" )
endif(EXISTS ${VCREDIST64})
SET(OALREDIST "${OpenMW_BINARY_DIR}/oalinst.exe")
if(EXISTS ${OALREDIST})
INSTALL(FILES ${OALREDIST} DESTINATION "redist")
SET(CPACK_NSIS_EXTRA_INSTALL_COMMANDS "${CPACK_NSIS_EXTRA_INSTALL_COMMANDS}
ExecWait '\\\"$INSTDIR\\\\redist\\\\oalinst.exe\\\" /s'" )
endif(EXISTS ${OALREDIST})
if(CMAKE_CL_64)
SET(CPACK_NSIS_INSTALL_ROOT "$PROGRAMFILES64")
endif()
include(CPack)
else(WIN32)
# Linux installation
# Install binaries
IF(BUILD_OPENMW)
INSTALL(PROGRAMS "${INSTALL_SOURCE}/openmw" DESTINATION "${BINDIR}" )
ENDIF(BUILD_OPENMW)
IF(BUILD_LAUNCHER)
INSTALL(PROGRAMS "${INSTALL_SOURCE}/openmw-launcher" DESTINATION "${BINDIR}" )
ENDIF(BUILD_LAUNCHER)
IF(BUILD_BSATOOL)
INSTALL(PROGRAMS "${INSTALL_SOURCE}/bsatool" DESTINATION "${BINDIR}" )
ENDIF(BUILD_BSATOOL)
IF(BUILD_ESMTOOL)
INSTALL(PROGRAMS "${INSTALL_SOURCE}/esmtool" DESTINATION "${BINDIR}" )
ENDIF(BUILD_ESMTOOL)
IF(BUILD_NIFTEST)
INSTALL(PROGRAMS "${INSTALL_SOURCE}/niftest" DESTINATION "${BINDIR}" )
ENDIF(BUILD_NIFTEST)
IF(BUILD_MWINIIMPORTER)
INSTALL(PROGRAMS "${INSTALL_SOURCE}/openmw-iniimporter" DESTINATION "${BINDIR}" )
ENDIF(BUILD_MWINIIMPORTER)
IF(BUILD_ESSIMPORTER)
INSTALL(PROGRAMS "${INSTALL_SOURCE}/openmw-essimporter" DESTINATION "${BINDIR}" )
ENDIF(BUILD_ESSIMPORTER)
IF(BUILD_OPENCS)
INSTALL(PROGRAMS "${INSTALL_SOURCE}/openmw-cs" DESTINATION "${BINDIR}" )
ENDIF(BUILD_OPENCS)
IF(BUILD_WIZARD)
INSTALL(PROGRAMS "${INSTALL_SOURCE}/openmw-wizard" DESTINATION "${BINDIR}" )
ENDIF(BUILD_WIZARD)
# Install licenses
INSTALL(FILES "files/mygui/DejaVuFontLicense.txt" DESTINATION "${LICDIR}" )
# Install icon and desktop file
INSTALL(FILES "${OpenMW_BINARY_DIR}/org.openmw.launcher.desktop" DESTINATION "${DATAROOTDIR}/applications" COMPONENT "openmw")
INSTALL(FILES "${OpenMW_SOURCE_DIR}/files/launcher/images/openmw.png" DESTINATION "${ICONDIR}" COMPONENT "openmw")
INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw.appdata.xml" DESTINATION "${DATAROOTDIR}/metainfo" COMPONENT "openmw")
IF(BUILD_OPENCS)
INSTALL(FILES "${OpenMW_BINARY_DIR}/org.openmw.cs.desktop" DESTINATION "${DATAROOTDIR}/applications" COMPONENT "opencs")
INSTALL(FILES "${OpenMW_SOURCE_DIR}/files/opencs/openmw-cs.png" DESTINATION "${ICONDIR}" COMPONENT "opencs")
ENDIF(BUILD_OPENCS)
# Install global configuration files
INSTALL(FILES "${INSTALL_SOURCE}/settings-default.cfg" DESTINATION "${SYSCONFDIR}" COMPONENT "openmw")
INSTALL(FILES "${INSTALL_SOURCE}/openmw.cfg.install" DESTINATION "${SYSCONFDIR}" RENAME "openmw.cfg" COMPONENT "openmw")
INSTALL(FILES "${INSTALL_SOURCE}/resources/version" DESTINATION "${SYSCONFDIR}" COMPONENT "openmw")
INSTALL(FILES "${INSTALL_SOURCE}/gamecontrollerdb.txt" DESTINATION "${SYSCONFDIR}" COMPONENT "openmw")
IF(BUILD_OPENCS)
INSTALL(FILES "${INSTALL_SOURCE}/openmw-cs.cfg" DESTINATION "${SYSCONFDIR}" COMPONENT "opencs")
ENDIF(BUILD_OPENCS)
# Install resources
INSTALL(DIRECTORY "${INSTALL_SOURCE}/resources" DESTINATION "${DATADIR}" COMPONENT "Resources")
INSTALL(DIRECTORY DESTINATION "${DATADIR}/data" COMPONENT "Resources")
endif(WIN32)
endif(NOT APPLE)
# Doxygen Target -- simply run 'make doc' or 'make doc_pages'
# output directory for 'make doc' is "${OpenMW_BINARY_DIR}/docs/Doxygen"

View file

@ -88,6 +88,7 @@ bool Launcher::AdvancedPage::loadSettings()
loadSettingBool(uncappedDamageFatigueCheckBox, "uncapped damage fatigue", "Game");
loadSettingBool(normaliseRaceSpeedCheckBox, "normalise race speed", "Game");
loadSettingBool(swimUpwardCorrectionCheckBox, "swim upward correction", "Game");
loadSettingBool(avoidCollisionsCheckBox, "NPCs avoid collisions", "Game");
int unarmedFactorsStrengthIndex = mEngineSettings.getInt("strength influences hand to hand", "Game");
if (unarmedFactorsStrengthIndex >= 0 && unarmedFactorsStrengthIndex <= 2)
unarmedFactorsStrengthComboBox->setCurrentIndex(unarmedFactorsStrengthIndex);
@ -112,6 +113,7 @@ bool Launcher::AdvancedPage::loadSettings()
loadSettingBool(shieldSheathingCheckBox, "shield sheathing", "Game");
}
loadSettingBool(turnToMovementDirectionCheckBox, "turn to movement direction", "Game");
loadSettingBool(smoothMovementCheckBox, "smooth movement", "Game");
const bool distantTerrain = mEngineSettings.getBool("distant terrain", "Terrain");
const bool objectPaging = mEngineSettings.getBool("object paging", "Terrain");
@ -200,6 +202,7 @@ void Launcher::AdvancedPage::saveSettings()
saveSettingBool(uncappedDamageFatigueCheckBox, "uncapped damage fatigue", "Game");
saveSettingBool(normaliseRaceSpeedCheckBox, "normalise race speed", "Game");
saveSettingBool(swimUpwardCorrectionCheckBox, "swim upward correction", "Game");
saveSettingBool(avoidCollisionsCheckBox, "NPCs avoid collisions", "Game");
int unarmedFactorsStrengthIndex = unarmedFactorsStrengthComboBox->currentIndex();
if (unarmedFactorsStrengthIndex != mEngineSettings.getInt("strength influences hand to hand", "Game"))
mEngineSettings.setInt("strength influences hand to hand", "Game", unarmedFactorsStrengthIndex);
@ -220,6 +223,7 @@ void Launcher::AdvancedPage::saveSettings()
saveSettingBool(weaponSheathingCheckBox, "weapon sheathing", "Game");
saveSettingBool(shieldSheathingCheckBox, "shield sheathing", "Game");
saveSettingBool(turnToMovementDirectionCheckBox, "turn to movement direction", "Game");
saveSettingBool(smoothMovementCheckBox, "smooth movement", "Game");
const bool distantTerrain = mEngineSettings.getBool("distant terrain", "Terrain");
const bool objectPaging = mEngineSettings.getBool("object paging", "Terrain");

View file

@ -233,8 +233,15 @@ target_link_libraries(openmw-cs Qt5::Widgets Qt5::Core Qt5::Network Qt5::OpenGL)
if (WIN32)
target_link_libraries(openmw-cs ${Boost_LOCALE_LIBRARY})
INSTALL(TARGETS openmw-cs RUNTIME DESTINATION ".")
INSTALL(FILES "${OpenMW_BINARY_DIR}/Debug/openmw-cs.cfg" DESTINATION "." CONFIGURATIONS Debug)
INSTALL(FILES "${OpenMW_BINARY_DIR}/Release/openmw-cs.cfg" DESTINATION "." CONFIGURATIONS Release;RelWithDebInfo;MinSizeRel)
get_generator_is_multi_config(multi_config)
if (multi_config)
SET(INSTALL_SOURCE "${OpenMW_BINARY_DIR}/$<CONFIG>")
else ()
SET(INSTALL_SOURCE "${OpenMW_BINARY_DIR}")
endif ()
INSTALL(FILES "${INSTALL_SOURCE}/openmw-cs.cfg" DESTINATION ".")
endif()
if (MSVC)

View file

@ -14,6 +14,7 @@
#include <SDL.h>
#include <components/debug/debuglog.hpp>
#include <components/debug/gldebug.hpp>
#include <components/misc/rng.hpp>
@ -498,7 +499,7 @@ void OMW::Engine::createWindow(Settings::Manager& settings)
bool fullscreen = settings.getBool("fullscreen", "Video");
bool windowBorder = settings.getBool("window border", "Video");
bool vsync = settings.getBool("vsync", "Video");
int antialiasing = settings.getInt("antialiasing", "Video");
unsigned int antialiasing = std::max(0, settings.getInt("antialiasing", "Video"));
int pos_x = SDL_WINDOWPOS_CENTERED_DISPLAY(screen),
pos_y = SDL_WINDOWPOS_CENTERED_DISPLAY(screen);
@ -524,6 +525,8 @@ void OMW::Engine::createWindow(Settings::Manager& settings)
checkSDLError(SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8));
checkSDLError(SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 0));
checkSDLError(SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24));
if (Debug::shouldDebugOpenGL())
checkSDLError(SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG));
if (antialiasing > 0)
{
@ -531,62 +534,80 @@ void OMW::Engine::createWindow(Settings::Manager& settings)
checkSDLError(SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, antialiasing));
}
while (!mWindow)
osg::ref_ptr<SDLUtil::GraphicsWindowSDL2> graphicsWindow;
while (!graphicsWindow || !graphicsWindow->valid())
{
mWindow = SDL_CreateWindow("OpenMW", pos_x, pos_y, width, height, flags);
if (!mWindow)
while (!mWindow)
{
// Try with a lower AA
if (antialiasing > 0)
mWindow = SDL_CreateWindow("OpenMW", pos_x, pos_y, width, height, flags);
if (!mWindow)
{
Log(Debug::Warning) << "Warning: " << antialiasing << "x antialiasing not supported, trying " << antialiasing/2;
antialiasing /= 2;
Settings::Manager::setInt("antialiasing", "Video", antialiasing);
checkSDLError(SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, antialiasing));
continue;
}
else
{
std::stringstream error;
error << "Failed to create SDL window: " << SDL_GetError();
throw std::runtime_error(error.str());
// Try with a lower AA
if (antialiasing > 0)
{
Log(Debug::Warning) << "Warning: " << antialiasing << "x antialiasing not supported, trying " << antialiasing/2;
antialiasing /= 2;
Settings::Manager::setInt("antialiasing", "Video", antialiasing);
checkSDLError(SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, antialiasing));
continue;
}
else
{
std::stringstream error;
error << "Failed to create SDL window: " << SDL_GetError();
throw std::runtime_error(error.str());
}
}
}
setWindowIcon();
osg::ref_ptr<osg::GraphicsContext::Traits> traits = new osg::GraphicsContext::Traits;
SDL_GetWindowPosition(mWindow, &traits->x, &traits->y);
SDL_GetWindowSize(mWindow, &traits->width, &traits->height);
traits->windowName = SDL_GetWindowTitle(mWindow);
traits->windowDecoration = !(SDL_GetWindowFlags(mWindow)&SDL_WINDOW_BORDERLESS);
traits->screenNum = SDL_GetWindowDisplayIndex(mWindow);
traits->vsync = vsync;
traits->inheritedWindowData = new SDLUtil::GraphicsWindowSDL2::WindowData(mWindow);
graphicsWindow = new SDLUtil::GraphicsWindowSDL2(traits);
if (!graphicsWindow->valid()) throw std::runtime_error("Failed to create GraphicsContext");
if (traits->samples < antialiasing)
{
Log(Debug::Warning) << "Warning: Framebuffer MSAA level is only " << traits->samples << "x instead of " << antialiasing << "x. Trying " << antialiasing / 2 << "x instead.";
graphicsWindow->closeImplementation();
SDL_DestroyWindow(mWindow);
mWindow = nullptr;
antialiasing /= 2;
Settings::Manager::setInt("antialiasing", "Video", antialiasing);
checkSDLError(SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, antialiasing));
continue;
}
if (traits->red < 8)
Log(Debug::Warning) << "Warning: Framebuffer only has a " << traits->red << " bit red channel.";
if (traits->green < 8)
Log(Debug::Warning) << "Warning: Framebuffer only has a " << traits->green << " bit green channel.";
if (traits->blue < 8)
Log(Debug::Warning) << "Warning: Framebuffer only has a " << traits->blue << " bit blue channel.";
if (traits->depth < 8)
Log(Debug::Warning) << "Warning: Framebuffer only has " << traits->red << " bits of depth precision.";
traits->alpha = 0; // set to 0 to stop ScreenCaptureHandler reading the alpha channel
}
setWindowIcon();
osg::ref_ptr<osg::GraphicsContext::Traits> traits = new osg::GraphicsContext::Traits;
SDL_GetWindowPosition(mWindow, &traits->x, &traits->y);
SDL_GetWindowSize(mWindow, &traits->width, &traits->height);
traits->windowName = SDL_GetWindowTitle(mWindow);
traits->windowDecoration = !(SDL_GetWindowFlags(mWindow)&SDL_WINDOW_BORDERLESS);
traits->screenNum = SDL_GetWindowDisplayIndex(mWindow);
// We tried to get rid of the hardcoding but failed: https://github.com/OpenMW/openmw/pull/1771
// Here goes kcat's quote:
// It's ultimately a chicken and egg problem, and the reason why the code is like it was in the first place.
// It needs a context to get the current attributes, but it needs the attributes to set up the context.
// So it just specifies the same values that were given to SDL in the hopes that it's good enough to what the window eventually gets.
traits->red = 8;
traits->green = 8;
traits->blue = 8;
traits->alpha = 0; // set to 0 to stop ScreenCaptureHandler reading the alpha channel
traits->depth = 24;
traits->stencil = 8;
traits->vsync = vsync;
traits->doubleBuffer = true;
traits->inheritedWindowData = new SDLUtil::GraphicsWindowSDL2::WindowData(mWindow);
osg::ref_ptr<SDLUtil::GraphicsWindowSDL2> graphicsWindow = new SDLUtil::GraphicsWindowSDL2(traits);
if(!graphicsWindow->valid()) throw std::runtime_error("Failed to create GraphicsContext");
osg::ref_ptr<osg::Camera> camera = mViewer->getCamera();
camera->setGraphicsContext(graphicsWindow);
camera->setViewport(0, 0, traits->width, traits->height);
camera->setViewport(0, 0, graphicsWindow->getTraits()->width, graphicsWindow->getTraits()->height);
if (Debug::shouldDebugOpenGL())
mViewer->setRealizeOperation(new Debug::EnableGLDebugOperation());
mViewer->realize();
mViewer->getEventQueue()->getCurrentEventState()->setWindowRectangle(0, 0, traits->width, traits->height);
mViewer->getEventQueue()->getCurrentEventState()->setWindowRectangle(0, 0, graphicsWindow->getTraits()->width, graphicsWindow->getTraits()->height);
}
void OMW::Engine::setWindowIcon()

View file

@ -941,6 +941,9 @@ public:
if (!mBook)
return;
if (mPage >= mBook->mPages.size())
return;
dirtyFocusItem ();
mFocusItem = 0;
@ -952,6 +955,9 @@ public:
if (!mBook)
return;
if (mPage >= mBook->mPages.size())
return;
left -= mCroppedParent->getAbsoluteLeft ();
top -= mCroppedParent->getAbsoluteTop ();
@ -988,6 +994,9 @@ public:
if (!mBook)
return;
if (mPage >= mBook->mPages.size())
return;
// work around inconsistency in MyGUI where the mouse press coordinates aren't
// transformed by the current Layer (even though mouse *move* events are).
MyGUI::IntPoint pos (left, top);
@ -1013,6 +1022,9 @@ public:
if (!mBook)
return;
if (mPage >= mBook->mPages.size())
return;
// work around inconsistency in MyGUI where the mouse release coordinates aren't
// transformed by the current Layer (even though mouse *move* events are).
MyGUI::IntPoint pos (left, top);

View file

@ -739,5 +739,24 @@ namespace MWGui
{
mAddEffectDialog.setConstantEffect(constant);
mConstantEffect = constant;
if (!constant)
return;
for (auto it = mEffects.begin(); it != mEffects.end();)
{
if (it->mRange != ESM::RT_Self)
{
auto& store = MWBase::Environment::get().getWorld()->getStore();
auto magicEffect = store.get<ESM::MagicEffect>().find(it->mEffectID);
if ((magicEffect->mData.mFlags & ESM::MagicEffect::CastSelf) == 0)
{
it = mEffects.erase(it);
continue;
}
it->mRange = ESM::RT_Self;
}
++it;
}
}
}

View file

@ -74,7 +74,7 @@ namespace MWGui
// Add price for the travelling followers
std::set<MWWorld::Ptr> followers;
MWWorld::ActionTeleport::getFollowersToTeleport(player, followers);
MWWorld::ActionTeleport::getFollowers(player, followers);
// Apply followers cost, unlike vanilla the first follower doesn't travel for free
price *= 1 + static_cast<int>(followers.size());

View file

@ -6,6 +6,7 @@
#include <components/sceneutil/positionattitudetransform.hpp>
#include <components/debug/debuglog.hpp>
#include <components/misc/rng.hpp>
#include <components/misc/mathutil.hpp>
#include <components/settings/settings.hpp>
#include "../mwworld/esmstore.hpp"
@ -36,6 +37,7 @@
#include "aicombataction.hpp"
#include "aifollow.hpp"
#include "aipursue.hpp"
#include "aiwander.hpp"
#include "actor.hpp"
#include "summoning.hpp"
#include "combat.hpp"
@ -424,7 +426,7 @@ namespace MWMechanics
const osg::Vec3f actor2Pos(targetActor.getRefData().getPosition().asVec3());
float sqrDist = (actor1Pos - actor2Pos).length2();
if (sqrDist > maxDistance*maxDistance)
if (sqrDist > std::min(maxDistance * maxDistance, sqrHeadTrackDistance))
return;
// stop tracking when target is behind the actor
@ -432,10 +434,7 @@ namespace MWMechanics
osg::Vec3f targetDirection(actor2Pos - actor1Pos);
actorDirection.z() = 0;
targetDirection.z() = 0;
actorDirection.normalize();
targetDirection.normalize();
if (std::acos(actorDirection * targetDirection) < osg::DegreesToRadians(90.f)
&& sqrDist <= sqrHeadTrackDistance
if (actorDirection * targetDirection > 0
&& MWBase::Environment::get().getWorld()->getLOS(actor, targetActor) // check LOS and awareness last as it's the most expensive function
&& MWBase::Environment::get().getMechanicsManager()->awarenessCheck(targetActor, actor))
{
@ -473,6 +472,9 @@ namespace MWMechanics
void Actors::updateMovementSpeed(const MWWorld::Ptr& actor)
{
if (mSmoothMovement)
return;
CreatureStats &stats = actor.getClass().getCreatureStats(actor);
MWMechanics::AiSequence& seq = stats.getAiSequence();
@ -481,9 +483,10 @@ namespace MWMechanics
osg::Vec3f targetPos = seq.getActivePackage().getDestination();
osg::Vec3f actorPos = actor.getRefData().getPosition().asVec3();
float distance = (targetPos - actorPos).length();
if (distance < DECELERATE_DISTANCE)
{
float speedCoef = std::max(0.7f, 0.1f * (distance/64.f + 2.f));
float speedCoef = std::max(0.7f, 0.2f + 0.8f * distance / DECELERATE_DISTANCE);
auto& movement = actor.getClass().getMovementSettings(actor);
movement.mPosition[0] *= speedCoef;
movement.mPosition[1] *= speedCoef;
@ -587,8 +590,11 @@ namespace MWMechanics
if (!actorState.isTurningToPlayer())
{
actorState.setAngleToPlayer(std::atan2(dir.x(), dir.y()));
actorState.setTurningToPlayer(true);
float angle = std::atan2(dir.x(), dir.y());
actorState.setAngleToPlayer(angle);
float deltaAngle = Misc::normalizeAngle(angle - actor.getRefData().getPosition().rot[2]);
if (!mSmoothMovement || std::abs(deltaAngle) > osg::DegreesToRadians(60.f))
actorState.setTurningToPlayer(true);
}
}
@ -1460,7 +1466,7 @@ namespace MWMechanics
}
}
Actors::Actors()
Actors::Actors() : mSmoothMovement(Settings::Manager::getBool("smooth movement", "Game"))
{
mTimerDisposeSummonsCorpses = 0.2f; // We should add a delay between summoned creature death and its corpse despawning
@ -1659,6 +1665,131 @@ namespace MWMechanics
}
void Actors::predictAndAvoidCollisions()
{
const float minGap = 10.f;
const float maxDistToCheck = 100.f;
const float maxTimeToCheck = 1.f;
static const bool giveWayWhenIdle = Settings::Manager::getBool("NPCs give way", "Game");
MWWorld::Ptr player = getPlayer();
MWBase::World* world = MWBase::Environment::get().getWorld();
for(PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter)
{
const MWWorld::Ptr& ptr = iter->first;
if (ptr == player)
continue; // Don't interfere with player controls.
Movement& movement = ptr.getClass().getMovementSettings(ptr);
osg::Vec2f origMovement(movement.mPosition[0], movement.mPosition[1]);
bool isMoving = origMovement.length2() > 0.01;
// Moving NPCs always should avoid collisions.
// Standing NPCs give way to moving ones if they are not in combat (or pursue) mode and either
// follow player or have a AIWander package with non-empty wander area.
bool shouldAvoidCollision = isMoving;
bool shouldTurnToApproachingActor = !isMoving;
MWWorld::Ptr currentTarget; // Combat or pursue target (NPCs should not avoid collision with their targets).
for (const auto& package : ptr.getClass().getCreatureStats(ptr).getAiSequence())
{
if (package->getTypeId() == AiPackageTypeId::Follow)
shouldAvoidCollision = true;
else if (package->getTypeId() == AiPackageTypeId::Wander && giveWayWhenIdle)
{
if (!dynamic_cast<const AiWander*>(package.get())->isStationary())
shouldAvoidCollision = true;
}
else if (package->getTypeId() == AiPackageTypeId::Combat || package->getTypeId() == AiPackageTypeId::Pursue)
{
currentTarget = package->getTarget();
shouldAvoidCollision = isMoving;
shouldTurnToApproachingActor = false;
break;
}
}
if (!shouldAvoidCollision)
continue;
float maxSpeed = ptr.getClass().getMaxSpeed(ptr);
osg::Vec2f baseSpeed = origMovement * maxSpeed;
osg::Vec3f basePos = ptr.getRefData().getPosition().asVec3();
float baseRotZ = ptr.getRefData().getPosition().rot[2];
osg::Vec3f halfExtents = world->getHalfExtents(ptr);
float timeToCollision = maxTimeToCheck;
osg::Vec2f movementCorrection(0, 0);
float angleToApproachingActor = 0;
// Iterate through all other actors and predict collisions.
for(PtrActorMap::iterator otherIter(mActors.begin()); otherIter != mActors.end(); ++otherIter)
{
const MWWorld::Ptr& otherPtr = otherIter->first;
if (otherPtr == ptr || otherPtr == currentTarget)
continue;
osg::Vec3f otherHalfExtents = world->getHalfExtents(otherPtr);
osg::Vec3f deltaPos = otherPtr.getRefData().getPosition().asVec3() - basePos;
osg::Vec2f relPos = Misc::rotateVec2f(osg::Vec2f(deltaPos.x(), deltaPos.y()), baseRotZ);
// Ignore actors which are not close enough or come from behind.
if (deltaPos.length2() > maxDistToCheck * maxDistToCheck || relPos.y() < 0)
continue;
// Don't check for a collision if vertical distance is greater then the actor's height.
if (deltaPos.z() > halfExtents.z() * 2 || deltaPos.z() < -otherHalfExtents.z() * 2)
continue;
osg::Vec3f speed = otherPtr.getClass().getMovementSettings(otherPtr).asVec3() *
otherPtr.getClass().getMaxSpeed(otherPtr);
float rotZ = otherPtr.getRefData().getPosition().rot[2];
osg::Vec2f relSpeed = Misc::rotateVec2f(osg::Vec2f(speed.x(), speed.y()), baseRotZ - rotZ) - baseSpeed;
float collisionDist = minGap + world->getHalfExtents(ptr).x() + world->getHalfExtents(otherPtr).x();
collisionDist = std::min(collisionDist, relPos.length());
// Find the earliest `t` when |relPos + relSpeed * t| == collisionDist.
float vr = relPos.x() * relSpeed.x() + relPos.y() * relSpeed.y();
float v2 = relSpeed.length2();
float Dh = vr * vr - v2 * (relPos.length2() - collisionDist * collisionDist);
if (Dh <= 0 || v2 == 0)
continue; // No solution; distance is always >= collisionDist.
float t = (-vr - std::sqrt(Dh)) / v2;
if (t < 0 || t > timeToCollision)
continue;
// Check visibility and awareness last as it's expensive.
if (!MWBase::Environment::get().getWorld()->getLOS(otherPtr, ptr))
continue;
if (!MWBase::Environment::get().getMechanicsManager()->awarenessCheck(otherPtr, ptr))
continue;
timeToCollision = t;
angleToApproachingActor = std::atan2(deltaPos.x(), deltaPos.y());
osg::Vec2f posAtT = relPos + relSpeed * t;
float coef = (posAtT.x() * relSpeed.x() + posAtT.y() * relSpeed.y()) / (collisionDist * maxSpeed);
movementCorrection = posAtT * coef;
// Step to the side rather than backward. Otherwise player will be able to push the NPC far away from it's original location.
movementCorrection.y() = std::max(0.f, movementCorrection.y());
}
if (timeToCollision < maxTimeToCheck)
{
// Try to evade the nearest collision.
osg::Vec2f newMovement = origMovement + movementCorrection;
if (isMoving)
{ // Keep the original speed.
newMovement.normalize();
newMovement *= origMovement.length();
}
movement.mPosition[0] = newMovement.x();
movement.mPosition[1] = newMovement.y();
if (shouldTurnToApproachingActor)
zTurn(ptr, angleToApproachingActor);
}
}
}
void Actors::update (float duration, bool paused)
{
if(!paused)
@ -1769,14 +1900,12 @@ namespace MWMechanics
MWMechanics::CreatureStats& stats = iter->first.getClass().getCreatureStats(iter->first);
bool firstPersonPlayer = isPlayer && world->isFirstPerson();
bool inCombatOrPursue = stats.getAiSequence().isInCombat() || stats.getAiSequence().hasPackage(AiPackageTypeId::Pursue);
// 1. Unconsious actor can not track target
// 2. Actors in combat and pursue mode do not bother to headtrack
// 3. Player character does not use headtracking in the 1st-person view
if (!stats.getKnockedDown() &&
!stats.getAiSequence().isInCombat() &&
!stats.getAiSequence().hasPackage(AiPackageTypeId::Pursue) &&
!firstPersonPlayer)
if (!stats.getKnockedDown() && !firstPersonPlayer && !inCombatOrPursue)
{
for(PtrActorMap::iterator it(mActors.begin()); it != mActors.end(); ++it)
{
@ -1786,6 +1915,17 @@ namespace MWMechanics
}
}
if (!stats.getKnockedDown() && !isPlayer && inCombatOrPursue)
{
// Actors in combat and pursue mode always look at their target.
for (const auto& package : stats.getAiSequence())
{
headTrackTarget = package->getTarget();
if (!headTrackTarget.isEmpty())
break;
}
}
ctrl->setHeadTrackTarget(headTrackTarget);
}
@ -1824,6 +1964,10 @@ namespace MWMechanics
}
}
static const bool avoidCollisions = Settings::Manager::getBool("NPCs avoid collisions", "Game");
if (avoidCollisions)
predictAndAvoidCollisions();
timerUpdateAITargets += duration;
timerUpdateHeadTrack += duration;
timerUpdateEquippedLight += duration;
@ -2053,10 +2197,11 @@ namespace MWMechanics
for(PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter)
{
iter->first.getClass().getCreatureStats(iter->first).getActiveSpells().update(duration);
if (iter->first.getClass().getCreatureStats(iter->first).isDead())
{
iter->first.getClass().getCreatureStats(iter->first).getActiveSpells().update(duration);
continue;
}
if (!sleep || iter->first == player)
restoreDynamicStats(iter->first, hours, sleep);
@ -2073,13 +2218,14 @@ namespace MWMechanics
if (iter->first.getClass().isNpc())
calculateNpcStatModifiers(iter->first, duration);
iter->first.getClass().getCreatureStats(iter->first).getActiveSpells().update(duration);
MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(iter->first);
if (animation)
{
animation->removeEffects();
MWBase::Environment::get().getWorld()->applyLoopingParticles(iter->first);
}
}
fastForwardAi();

View file

@ -63,6 +63,8 @@ namespace MWMechanics
void purgeSpellEffects (int casterActorId);
void predictAndAvoidCollisions();
public:
Actors();
@ -209,6 +211,7 @@ namespace MWMechanics
float mTimerDisposeSummonsCorpses;
float mActorsProcessingRange;
bool mSmoothMovement;
};
}

View file

@ -23,7 +23,7 @@ bool MWMechanics::AiBreathe::execute (const MWWorld::Ptr& actor, CharacterContro
actorClass.getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, true);
actorClass.getMovementSettings(actor).mPosition[1] = 1;
smoothTurn(actor, -180, 0);
smoothTurn(actor, -osg::PI / 2, 0);
return false;
}

View file

@ -5,6 +5,8 @@
#include <components/esm/aisequence.hpp>
#include <components/misc/mathutil.hpp>
#include <components/sceneutil/positionattitudetransform.hpp>
#include "../mwphysics/collisiontype.hpp"
@ -240,10 +242,6 @@ namespace MWMechanics
if (storage.mReadyToAttack)
{
storage.startCombatMove(isRangedCombat, distToTarget, rangeAttack, actor, target);
// start new attack
storage.startAttackIfReady(actor, characterController, weapon, isRangedCombat);
if (isRangedCombat)
{
// rotate actor taking into account target movement direction and projectile speed
@ -259,6 +257,10 @@ namespace MWMechanics
storage.mMovement.mRotation[0] = getXAngleToDir(vAimDir);
storage.mMovement.mRotation[2] = getZAngleToDir((vTargetPos-vActorPos)); // using vAimDir results in spastic movements since the head is animated
}
storage.startCombatMove(isRangedCombat, distToTarget, rangeAttack, actor, target);
// start new attack
storage.startAttackIfReady(actor, characterController, weapon, isRangedCombat);
}
return false;
}
@ -372,9 +374,13 @@ namespace MWMechanics
void AiCombat::updateActorsMovement(const MWWorld::Ptr& actor, float duration, AiCombatStorage& storage)
{
// apply combat movement
float deltaAngle = storage.mMovement.mRotation[2] - actor.getRefData().getPosition().rot[2];
osg::Vec2f movement = Misc::rotateVec2f(
osg::Vec2f(storage.mMovement.mPosition[0], storage.mMovement.mPosition[1]), -deltaAngle);
MWMechanics::Movement& actorMovementSettings = actor.getClass().getMovementSettings(actor);
actorMovementSettings.mPosition[0] = storage.mMovement.mPosition[0];
actorMovementSettings.mPosition[1] = storage.mMovement.mPosition[1];
actorMovementSettings.mPosition[0] = movement.x();
actorMovementSettings.mPosition[1] = movement.y();
actorMovementSettings.mPosition[2] = storage.mMovement.mPosition[2];
rotateActorOnAxis(actor, 2, actorMovementSettings, storage);
@ -385,26 +391,11 @@ namespace MWMechanics
MWMechanics::Movement& actorMovementSettings, AiCombatStorage& storage)
{
actorMovementSettings.mRotation[axis] = 0;
float& targetAngleRadians = storage.mMovement.mRotation[axis];
if (targetAngleRadians != 0)
{
// Some attack animations contain small amount of movement.
// Since we use cone shapes for melee, we can use a threshold to avoid jittering
std::shared_ptr<Action>& currentAction = storage.mCurrentAction;
bool isRangedCombat = false;
currentAction->getCombatRange(isRangedCombat);
// Check if the actor now facing desired direction, no need to turn any more
if (isRangedCombat)
{
if (smoothTurn(actor, targetAngleRadians, axis))
targetAngleRadians = 0;
}
else
{
if (smoothTurn(actor, targetAngleRadians, axis, osg::DegreesToRadians(3.f)))
targetAngleRadians = 0;
}
}
bool isRangedCombat = false;
storage.mCurrentAction->getCombatRange(isRangedCombat);
float eps = isRangedCombat ? osg::DegreesToRadians(0.5) : osg::DegreesToRadians(3.f);
float targetAngleRadians = storage.mMovement.mRotation[axis];
smoothTurn(actor, targetAngleRadians, axis, eps);
}
MWWorld::Ptr AiCombat::getTarget() const
@ -489,12 +480,19 @@ namespace MWMechanics
// Note: do not use for ranged combat yet since in couple with back up behaviour can move actor out of cliff
else if (actor.getClass().isBipedal(actor))
{
// apply sideway movement (kind of dodging) with some probability
// if actor is within range of target's weapon
if (distToTarget <= rangeAttackOfTarget && Misc::Rng::rollClosedProbability() < 0.25)
float moveDuration = 0;
float angleToTarget = Misc::normalizeAngle(mMovement.mRotation[2] - actor.getRefData().getPosition().rot[2]);
// Apply a big side step if enemy tries to get around and come from behind.
// Otherwise apply a random side step (kind of dodging) with some probability
// if actor is within range of target's weapon.
if (std::abs(angleToTarget) > osg::PI / 4)
moveDuration = 0.2;
else if (distToTarget <= rangeAttackOfTarget && Misc::Rng::rollClosedProbability() < 0.25)
moveDuration = 0.1f + 0.1f * Misc::Rng::rollClosedProbability();
if (moveDuration > 0)
{
mMovement.mPosition[0] = Misc::Rng::rollProbability() < 0.5 ? 1.0f : -1.0f; // to the left/right
mTimerCombatMove = 0.1f + 0.1f * Misc::Rng::rollClosedProbability();
mTimerCombatMove = moveDuration;
mCombatMove = true;
}
}

View file

@ -5,6 +5,7 @@
#include <components/esm/loadmgef.hpp>
#include <components/detournavigator/navigator.hpp>
#include <components/misc/coordinateconverter.hpp>
#include <components/settings/settings.hpp>
#include "../mwbase/world.hpp"
#include "../mwbase/environment.hpp"
@ -87,6 +88,7 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f&
//... But AI processing distance may increase in the future.
if (isNearInactiveCell(position))
{
actor.getClass().getMovementSettings(actor).mPosition[0] = 0;
actor.getClass().getMovementSettings(actor).mPosition[1] = 0;
world->updateActorPath(actor, mPathFinder.getPath(), halfExtents, position, dest);
return false;
@ -169,12 +171,34 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f&
}
// turn to next path point by X,Z axes
zTurn(actor, mPathFinder.getZAngleToNext(position.x(), position.y()));
float zAngleToNext = mPathFinder.getZAngleToNext(position.x(), position.y());
zTurn(actor, zAngleToNext);
smoothTurn(actor, mPathFinder.getXAngleToNext(position.x(), position.y(), position.z()), 0);
const auto destination = mPathFinder.getPath().empty() ? dest : mPathFinder.getPath().front();
mObstacleCheck.update(actor, destination, duration);
static const bool smoothMovement = Settings::Manager::getBool("smooth movement", "Game");
if (smoothMovement)
{
const float smoothTurnReservedDist = 150;
auto& movement = actor.getClass().getMovementSettings(actor);
float distToNextSqr = osg::Vec2f(destination.x() - position.x(), destination.y() - position.y()).length2();
float diffAngle = zAngleToNext - actor.getRefData().getPosition().rot[2];
if (std::cos(diffAngle) < -0.1)
movement.mPosition[0] = movement.mPosition[1] = 0;
else if (distToNextSqr > smoothTurnReservedDist * smoothTurnReservedDist)
{ // Go forward (and slowly turn towards the next path point)
movement.mPosition[0] = 0;
movement.mPosition[1] = 1;
}
else
{ // Next path point is near, so use diagonal movement to follow the path precisely.
movement.mPosition[0] = std::sin(diffAngle);
movement.mPosition[1] = std::max(std::cos(diffAngle), 0.f);
}
}
// handle obstacles on the way
evadeObstacles(actor);

View file

@ -27,7 +27,7 @@
namespace MWMechanics
{
static const int COUNT_BEFORE_RESET = 10;
static const float DOOR_CHECK_INTERVAL = 1.5f;
static const float IDLE_POSITION_CHECK_INTERVAL = 1.5f;
// to prevent overcrowding
static const int DESTINATION_TOLERANCE = 64;
@ -96,6 +96,7 @@ namespace MWMechanics
void stopMovement(const MWWorld::Ptr& actor)
{
actor.getClass().getMovementSettings(actor).mPosition[0] = 0;
actor.getClass().getMovementSettings(actor).mPosition[1] = 0;
}
@ -424,15 +425,14 @@ namespace MWMechanics
void AiWander::onIdleStatePerFrameActions(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage)
{
// Check if an idle actor is too close to a door - if so start walking
storage.mDoorCheckDuration += duration;
// Check if an idle actor is too far from all allowed nodes or too close to a door - if so start walking.
storage.mCheckIdlePositionTimer += duration;
if (storage.mDoorCheckDuration >= DOOR_CHECK_INTERVAL)
if (storage.mCheckIdlePositionTimer >= IDLE_POSITION_CHECK_INTERVAL && !isStationary())
{
storage.mDoorCheckDuration = 0; // restart timer
static float distance = MWBase::Environment::get().getWorld()->getMaxActivationDistance();
if (mDistance && // actor is not intended to be stationary
proximityToDoor(actor, distance*1.6f))
storage.mCheckIdlePositionTimer = 0; // restart timer
static float distance = MWBase::Environment::get().getWorld()->getMaxActivationDistance() * 1.6f;
if (proximityToDoor(actor, distance) || !isNearAllowedNode(actor, storage, distance))
{
storage.setState(AiWanderStorage::Wander_MoveNow);
storage.mTrimCurrentNode = false; // just in case
@ -451,6 +451,20 @@ namespace MWMechanics
}
}
bool AiWander::isNearAllowedNode(const MWWorld::Ptr& actor, const AiWanderStorage& storage, float distance) const
{
const osg::Vec3f actorPos = actor.getRefData().getPosition().asVec3();
auto cell = actor.getCell()->getCell();
for (const ESM::Pathgrid::Point& node : storage.mAllowedNodes)
{
osg::Vec3f point(node.mX, node.mY, node.mZ);
Misc::CoordinateConverter(cell).toWorld(point);
if ((actorPos - point).length2() < distance * distance)
return true;
}
return false;
}
void AiWander::onWalkingStatePerFrameActions(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage)
{
// Is there no destination or are we there yet?
@ -468,6 +482,9 @@ namespace MWMechanics
void AiWander::onChooseActionStatePerFrameActions(const MWWorld::Ptr& actor, AiWanderStorage& storage)
{
// Wait while fully stop before starting idle animation (important if "smooth movement" is enabled).
if (actor.getClass().getCurrentSpeed(actor) > 0)
return;
unsigned short idleAnimation = getRandomIdle();
storage.mIdleAnimation = idleAnimation;

View file

@ -53,7 +53,7 @@ namespace MWMechanics
ESM::Pathgrid::Point mCurrentNode;
bool mTrimCurrentNode;
float mDoorCheckDuration;
float mCheckIdlePositionTimer;
int mStuckCount;
AiWanderStorage():
@ -66,7 +66,7 @@ namespace MWMechanics
mPopulateAvailableNodes(true),
mAllowedNodes(),
mTrimCurrentNode(false),
mDoorCheckDuration(0), // TODO: maybe no longer needed
mCheckIdlePositionTimer(0),
mStuckCount(0)
{};
@ -117,6 +117,8 @@ namespace MWMechanics
return mDestination;
}
bool isStationary() const { return mDistance == 0; }
private:
void stopWalking(const MWWorld::Ptr& actor);
@ -137,6 +139,7 @@ namespace MWMechanics
void wanderNearStart(const MWWorld::Ptr &actor, AiWanderStorage &storage, int wanderDistance);
bool destinationIsAtWater(const MWWorld::Ptr &actor, const osg::Vec3f& destination);
void completeManualWalking(const MWWorld::Ptr &actor, AiWanderStorage &storage);
bool isNearAllowedNode(const MWWorld::Ptr &actor, const AiWanderStorage& storage, float distance) const;
const int mDistance; // how far the actor can wander from the spawn point
const int mDuration;

View file

@ -1963,6 +1963,50 @@ void CharacterController::update(float duration, bool animationOnly)
if (isPlayer && !isrunning && !sneak && !flying && movementSettings.mSpeedFactor <= 0.5f)
movementSettings.mSpeedFactor *= 2.f;
static const bool smoothMovement = Settings::Manager::getBool("smooth movement", "Game");
if (smoothMovement && !isFirstPersonPlayer)
{
float angle = mPtr.getRefData().getPosition().rot[2];
osg::Vec2f targetSpeed = Misc::rotateVec2f(osg::Vec2f(vec.x(), vec.y()), -angle) * movementSettings.mSpeedFactor;
osg::Vec2f delta = targetSpeed - mSmoothedSpeed;
float speedDelta = movementSettings.mSpeedFactor - mSmoothedSpeed.length();
float deltaLen = delta.length();
float maxDelta;
if (std::abs(speedDelta) < deltaLen / 2)
// Turning is smooth for player and less smooth for NPCs (otherwise NPC can miss a path point).
maxDelta = duration * (isPlayer ? 3.f : 6.f);
else if (isPlayer && speedDelta < -deltaLen / 2)
// As soon as controls are released, mwinput switches player from running to walking.
// So stopping should be instant for player, otherwise it causes a small twitch.
maxDelta = 1;
else // In all other cases speeding up and stopping are smooth.
maxDelta = duration * 3.f;
if (deltaLen > maxDelta)
delta *= maxDelta / deltaLen;
mSmoothedSpeed += delta;
osg::Vec2f newSpeed = Misc::rotateVec2f(mSmoothedSpeed, angle);
movementSettings.mSpeedFactor = newSpeed.normalize();
vec.x() = newSpeed.x();
vec.y() = newSpeed.y();
const float eps = 0.001f;
if (movementSettings.mSpeedFactor < eps)
{
movementSettings.mSpeedFactor = 0;
vec.x() = 0;
vec.y() = 1;
}
else if ((vec.y() < 0) != mIsMovingBackward)
{
if (targetSpeed.length() < eps || (movementSettings.mPosition[1] < 0) == mIsMovingBackward)
vec.y() = mIsMovingBackward ? -eps : eps;
}
vec.normalize();
}
float effectiveRotation = rot.z();
bool canMove = cls.getMaxSpeed(mPtr) > 0;
static const bool turnToMovementDirection = Settings::Manager::getBool("turn to movement direction", "Game");
@ -1994,6 +2038,8 @@ void CharacterController::update(float duration, bool animationOnly)
mAnimation->setUpperBodyYawRadians(stats.getSideMovementAngle() / 2);
else
mAnimation->setUpperBodyYawRadians(stats.getSideMovementAngle() / 4);
if (smoothMovement && !isPlayer && !inwater)
mAnimation->setUpperBodyYawRadians(mAnimation->getUpperBodyYawRadians() + mAnimation->getHeadYaw() / 2);
speed = cls.getCurrentSpeed(mPtr);
vec.x() *= speed;
@ -2185,13 +2231,11 @@ void CharacterController::update(float duration, bool animationOnly)
: (sneak ? CharState_SneakBack
: (isrunning ? CharState_RunBack : CharState_WalkBack)));
}
else if (effectiveRotation != 0.0f)
else
{
// Do not play turning animation for player if rotation speed is very slow.
// Actual threshold should take framerate in account.
float rotationThreshold = 0.f;
if (isPlayer)
rotationThreshold = 0.015 * 60 * duration;
float rotationThreshold = (isPlayer ? 0.015f : 0.001f) * 60 * duration;
// It seems only bipedal actors use turning animations.
// Also do not use turning animations in the first-person view and when sneaking.
@ -2695,10 +2739,9 @@ void CharacterController::setVisibility(float visibility)
void CharacterController::setAttackTypeBasedOnMovement()
{
float *move = mPtr.getClass().getMovementSettings(mPtr).mPosition;
if (move[1] && !move[0]) // forward-backward
if (std::abs(move[1]) > std::abs(move[0]) + 0.2f) // forward-backward
mAttackType = "thrust";
else if (move[0] && !move[1]) //sideway
else if (std::abs(move[0]) > std::abs(move[1]) + 0.2f) // sideway
mAttackType = "slash";
else
mAttackType = "chop";
@ -2893,19 +2936,21 @@ void CharacterController::updateHeadTracking(float duration)
return;
const osg::Vec3f actorDirection = mPtr.getRefData().getBaseNode()->getAttitude() * osg::Vec3f(0,1,0);
zAngleRadians = std::atan2(direction.x(), direction.y()) - std::atan2(actorDirection.x(), actorDirection.y());
xAngleRadians = -std::asin(direction.z());
const double xLimit = osg::DegreesToRadians(40.0);
const double zLimit = osg::DegreesToRadians(30.0);
zAngleRadians = osg::clampBetween(Misc::normalizeAngle(zAngleRadians), -xLimit, xLimit);
xAngleRadians = osg::clampBetween(Misc::normalizeAngle(xAngleRadians), -zLimit, zLimit);
zAngleRadians = std::atan2(actorDirection.x(), actorDirection.y()) - std::atan2(direction.x(), direction.y());
xAngleRadians = std::asin(direction.z());
}
const double xLimit = osg::DegreesToRadians(40.0);
const double zLimit = osg::DegreesToRadians(30.0);
double zLimitOffset = mAnimation->getUpperBodyYawRadians();
xAngleRadians = osg::clampBetween(Misc::normalizeAngle(xAngleRadians), -xLimit, xLimit);
zAngleRadians = osg::clampBetween(Misc::normalizeAngle(zAngleRadians),
-zLimit + zLimitOffset, zLimit + zLimitOffset);
float factor = duration*5;
factor = std::min(factor, 1.f);
xAngleRadians = (1.f-factor) * mAnimation->getHeadPitch() + factor * (-xAngleRadians);
zAngleRadians = (1.f-factor) * mAnimation->getHeadYaw() + factor * (-zAngleRadians);
xAngleRadians = (1.f-factor) * mAnimation->getHeadPitch() + factor * xAngleRadians;
zAngleRadians = (1.f-factor) * mAnimation->getHeadYaw() + factor * zAngleRadians;
mAnimation->setHeadPitch(xAngleRadians);
mAnimation->setHeadYaw(zAngleRadians);

View file

@ -196,6 +196,7 @@ class CharacterController : public MWRender::Animation::TextKeyListener
float mTimeUntilWake;
bool mIsMovingBackward;
osg::Vec2f mSmoothedSpeed;
void setAttackTypeBasedOnMovement();

View file

@ -88,6 +88,24 @@ namespace
const auto halfExtents = world->getHalfExtents(actor);
return 2.0 * halfExtents.z();
}
// Returns true if turn in `p2` is less than 10 degrees and all the 3 points are almost on one line.
bool isAlmostStraight(const osg::Vec3f& p1, const osg::Vec3f& p2, const osg::Vec3f& p3, float pointTolerance) {
osg::Vec3f v1 = p1 - p2;
osg::Vec3f v3 = p3 - p2;
v1.z() = v3.z() = 0;
float dotProduct = v1.x() * v3.x() + v1.y() * v3.y();
float crossProduct = v1.x() * v3.y() - v1.y() * v3.x();
// Check that the angle between v1 and v3 is less or equal than 10 degrees.
static const float cos170 = std::cos(osg::PI / 180 * 170);
bool checkAngle = dotProduct <= cos170 * v1.length() * v3.length();
// Check that distance from p2 to the line (p1, p3) is less or equal than `pointTolerance`.
bool checkDist = std::abs(crossProduct) <= pointTolerance * (p3 - p1).length() * 2;
return checkAngle && checkDist;
}
}
namespace MWMechanics
@ -286,6 +304,11 @@ namespace MWMechanics
while (mPath.size() > 1 && sqrDistanceIgnoreZ(mPath.front(), position) < pointTolerance * pointTolerance)
mPath.pop_front();
while (mPath.size() > 2 && isAlmostStraight(mPath[0], mPath[1], mPath[2], pointTolerance))
mPath.erase(mPath.begin() + 1);
if (mPath.size() > 1 && isAlmostStraight(position, mPath[0], mPath[1], pointTolerance))
mPath.pop_front();
if (mPath.size() == 1 && sqrDistanceIgnoreZ(mPath.front(), position) < destinationTolerance * destinationTolerance)
mPath.pop_front();
}

View file

@ -27,7 +27,8 @@ void Repair::repair(const MWWorld::Ptr &itemToRepair)
// reduce number of uses left
int uses = mTool.getClass().getItemHealth(mTool);
mTool.getCellRef().setCharge(uses-1);
uses -= std::min(uses, 1);
mTool.getCellRef().setCharge(uses);
MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player);

View file

@ -33,6 +33,10 @@ namespace MWMechanics
!lock.getClass().hasToolTip(lock)) //If it's unlocked or can not be unlocked back out immediately
return;
int uses = lockpick.getClass().getItemHealth(lockpick);
if (uses == 0)
return;
int lockStrength = lock.getCellRef().getLockLevel();
float pickQuality = lockpick.get<ESM::Lockpick>()->mBase->mData.mQuality;
@ -61,9 +65,7 @@ namespace MWMechanics
resultMessage = "#{sLockFail}";
}
int uses = lockpick.getClass().getItemHealth(lockpick);
--uses;
lockpick.getCellRef().setCharge(uses);
lockpick.getCellRef().setCharge(uses-1);
if (!uses)
lockpick.getContainerStore()->remove(lockpick, 1, mActor);
}
@ -71,7 +73,11 @@ namespace MWMechanics
void Security::probeTrap(const MWWorld::Ptr &trap, const MWWorld::Ptr &probe,
std::string& resultMessage, std::string& resultSound)
{
if (trap.getCellRef().getTrap() == "")
if (trap.getCellRef().getTrap().empty())
return;
int uses = probe.getClass().getItemHealth(probe);
if (uses == 0)
return;
float probeQuality = probe.get<ESM::Probe>()->mBase->mData.mQuality;
@ -104,9 +110,7 @@ namespace MWMechanics
resultMessage = "#{sTrapFail}";
}
int uses = probe.getClass().getItemHealth(probe);
--uses;
probe.getCellRef().setCharge(uses);
probe.getCellRef().setCharge(uses-1);
if (!uses)
probe.getContainerStore()->remove(probe, 1, mActor);
}

View file

@ -12,6 +12,7 @@
#include "../mwworld/inventorystore.hpp"
#include "creaturestats.hpp"
#include "spellutil.hpp"
namespace MWMechanics
{
@ -43,9 +44,9 @@ namespace MWMechanics
}
};
bool absorbSpell (const ESM::Spell* spell, const MWWorld::Ptr& caster, const MWWorld::Ptr& target)
bool absorbSpell (const std::string& spellId, const MWWorld::Ptr& caster, const MWWorld::Ptr& target)
{
if (!spell || caster == target || !target.getClass().isActor())
if (spellId.empty() || caster == target || !target.getClass().isActor())
return false;
CreatureStats& stats = target.getClass().getCreatureStats(target);
@ -62,13 +63,27 @@ namespace MWMechanics
if (Misc::Rng::roll0to99() >= chance)
return false;
const ESM::Static* absorbStatic = MWBase::Environment::get().getWorld()->getStore().get<ESM::Static>().find("VFX_Absorb");
const auto& esmStore = MWBase::Environment::get().getWorld()->getStore();
const ESM::Static* absorbStatic = esmStore.get<ESM::Static>().find("VFX_Absorb");
MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(target);
if (animation && !absorbStatic->mModel.empty())
animation->addEffect( "meshes\\" + absorbStatic->mModel, ESM::MagicEffect::SpellAbsorption, false, std::string());
const ESM::Spell* spell = esmStore.get<ESM::Spell>().search(spellId);
int spellCost = 0;
if (spell)
{
spellCost = spell->mData.mCost;
}
else
{
const ESM::Enchantment* enchantment = esmStore.get<ESM::Enchantment>().search(spellId);
if (enchantment)
spellCost = getEffectiveEnchantmentCastCost(static_cast<float>(enchantment->mData.mCost), caster);
}
// Magicka is increased by the cost of the spell
DynamicStat<float> magicka = stats.getMagicka();
magicka.setCurrent(magicka.getCurrent() + spell->mData.mCost);
magicka.setCurrent(magicka.getCurrent() + spellCost);
stats.setMagicka(magicka);
return true;
}

View file

@ -1,10 +1,7 @@
#ifndef MWMECHANICS_SPELLABSORPTION_H
#define MWMECHANICS_SPELLABSORPTION_H
namespace ESM
{
struct Spell;
}
#include <string>
namespace MWWorld
{
@ -14,7 +11,7 @@ namespace MWWorld
namespace MWMechanics
{
// Try to absorb a spell based on the magnitude of every Spell Absorption effect source on the target.
bool absorbSpell(const ESM::Spell* spell, const MWWorld::Ptr& caster, const MWWorld::Ptr& target);
bool absorbSpell(const std::string& spellId, const MWWorld::Ptr& caster, const MWWorld::Ptr& target);
}
#endif

View file

@ -119,7 +119,7 @@ namespace MWMechanics
// effects, we display a "can't re-cast" message
// Try absorbing the spell. Some handling must still happen for absorbed effects.
bool absorbed = absorbSpell(spell, caster, target);
bool absorbed = absorbSpell(mId, caster, target);
int currentEffectIndex = 0;
for (std::vector<ESM::ENAMstruct>::const_iterator effectIt (effects.mList.begin());

View file

@ -1,5 +1,8 @@
#include "steering.hpp"
#include <components/misc/mathutil.hpp>
#include <components/settings/settings.hpp>
#include "../mwworld/class.hpp"
#include "../mwworld/ptr.hpp"
@ -12,19 +15,8 @@ namespace MWMechanics
bool smoothTurn(const MWWorld::Ptr& actor, float targetAngleRadians, int axis, float epsilonRadians)
{
float currentAngle (actor.getRefData().getPosition().rot[axis]);
float diff (targetAngleRadians - currentAngle);
if (std::abs(diff) >= osg::DegreesToRadians(180.f))
{
if (diff >= 0)
{
diff = diff - osg::DegreesToRadians(360.f);
}
else
{
diff = osg::DegreesToRadians(360.f) + diff;
}
}
MWMechanics::Movement& movement = actor.getClass().getMovementSettings(actor);
float diff = Misc::normalizeAngle(targetAngleRadians - actor.getRefData().getPosition().rot[axis]);
float absDiff = std::abs(diff);
// The turning animation actually moves you slightly, so the angle will be wrong again.
@ -33,10 +25,14 @@ bool smoothTurn(const MWWorld::Ptr& actor, float targetAngleRadians, int axis, f
return true;
float limit = getAngularVelocity(actor.getClass().getMaxSpeed(actor)) * MWBase::Environment::get().getFrameDuration();
static const bool smoothMovement = Settings::Manager::getBool("smooth movement", "Game");
if (smoothMovement)
limit *= std::min(absDiff / osg::PI + 0.1, 0.5);
if (absDiff > limit)
diff = osg::sign(diff) * limit;
actor.getClass().getMovementSettings(actor).mRotation[axis] = diff;
movement.mRotation[axis] = diff;
return false;
}

View file

@ -264,6 +264,8 @@ namespace MWPhysics
RayCastingResult PhysicsSystem::castRay(const osg::Vec3f &from, const osg::Vec3f &to, const MWWorld::ConstPtr& ignore, std::vector<MWWorld::Ptr> targets, int mask, int group) const
{
if (from == to)
return RayCastingResult { false };
btVector3 btFrom = Misc::Convert::toBullet(from);
btVector3 btTo = Misc::Convert::toBullet(to);

View file

@ -159,12 +159,20 @@ namespace MWScript
float ay = ptr.getRefData().getPosition().rot[1];
float az = ptr.getRefData().getPosition().rot[2];
// XYZ axis use the inverse (XYZ) rotation order like vanilla SetAngle.
// UWV axis use the standard (ZYX) rotation order like TESCS/OpenMW-CS and the rest of the game.
if (axis == "x")
MWBase::Environment::get().getWorld()->rotateObject(ptr,angle,ay,az);
MWBase::Environment::get().getWorld()->rotateObject(ptr,angle,ay,az,MWBase::RotationFlag_inverseOrder);
else if (axis == "y")
MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,angle,az);
MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,angle,az,MWBase::RotationFlag_inverseOrder);
else if (axis == "z")
MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,ay,angle);
MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,ay,angle,MWBase::RotationFlag_inverseOrder);
else if (axis == "u")
MWBase::Environment::get().getWorld()->rotateObject(ptr,angle,ay,az,MWBase::RotationFlag_none);
else if (axis == "w")
MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,angle,az,MWBase::RotationFlag_none);
else if (axis == "v")
MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,ay,angle,MWBase::RotationFlag_none);
}
};

View file

@ -24,7 +24,7 @@ namespace MWWorld
{
// Find any NPCs that are following the actor and teleport them with him
std::set<MWWorld::Ptr> followers;
getFollowersToTeleport(actor, followers);
getFollowers(actor, followers, true);
for (std::set<MWWorld::Ptr>::iterator it = followers.begin(); it != followers.end(); ++it)
teleport(*it);
@ -47,7 +47,9 @@ namespace MWWorld
}
else
{
if (mCellName.empty())
if (actor.getClass().getCreatureStats(actor).getAiSequence().isInCombat(world->getPlayerPtr()))
actor.getClass().getCreatureStats(actor).getAiSequence().stopCombat();
else if (mCellName.empty())
{
int cellX;
int cellY;
@ -60,7 +62,7 @@ namespace MWWorld
}
}
void ActionTeleport::getFollowersToTeleport(const MWWorld::Ptr& actor, std::set<MWWorld::Ptr>& out) {
void ActionTeleport::getFollowers(const MWWorld::Ptr& actor, std::set<MWWorld::Ptr>& out, bool includeHostiles) {
std::set<MWWorld::Ptr> followers;
MWBase::Environment::get().getMechanicsManager()->getActorsFollowing(actor, followers);
@ -69,11 +71,17 @@ namespace MWWorld
MWWorld::Ptr follower = *it;
std::string script = follower.getClass().getScript(follower);
if (!includeHostiles && follower.getClass().getCreatureStats(follower).getAiSequence().isInCombat(actor))
continue;
if (!script.empty() && follower.getRefData().getLocals().getIntVar(script, "stayoutside") == 1)
continue;
if ((follower.getRefData().getPosition().asVec3() - actor.getRefData().getPosition().asVec3()).length2() <= 800*800)
out.insert(follower);
if ((follower.getRefData().getPosition().asVec3() - actor.getRefData().getPosition().asVec3()).length2() > 800 * 800)
continue;
out.emplace(follower);
}
}
}

View file

@ -28,8 +28,9 @@ namespace MWWorld
/// @param teleportFollowers Whether to teleport any following actors of the target actor as well.
ActionTeleport (const std::string& cellName, const ESM::Position& position, bool teleportFollowers);
/// Outputs every actor follower who is in teleport range and wasn't ordered to not enter interiors
static void getFollowersToTeleport(const MWWorld::Ptr& actor, std::set<MWWorld::Ptr>& out);
/// @param includeHostiles If true, include hostile followers (which won't actually be teleported) in the output,
/// e.g. so that the teleport action can calm them.
static void getFollowers(const MWWorld::Ptr& actor, std::set<MWWorld::Ptr>& out, bool includeHostiles = false);
};
}

View file

@ -287,9 +287,9 @@ namespace MWWorld
///< Return a pointer to a liveCellRef with the given name.
/// \param activeOnly do non search inactive cells.
Ptr searchPtr (const std::string& name, bool activeOnly, bool searchInContainers = true) override;
Ptr searchPtr (const std::string& name, bool activeOnly, bool searchInContainers = false) override;
///< Return a pointer to a liveCellRef with the given name.
/// \param activeOnly do non search inactive cells.
/// \param activeOnly do not search inactive cells.
Ptr searchPtrViaActorId (int actorId) override;
///< Search is limited to the active cells.

View file

@ -244,6 +244,7 @@ namespace
void init(Nif::Named& value)
{
value.extra = Nif::ExtraPtr(nullptr);
value.extralist = Nif::ExtraList();
value.controller = Nif::ControllerPtr(nullptr);
}

View file

@ -92,7 +92,7 @@ namespace
"\n"
"void bar() { foo() }\n"
"\n"
"#line 2 0\n"
"#line 1 0\n"
"\n"
"void main() { bar() }\n";
EXPECT_EQ(shader->getShaderSource(), expected);

View file

@ -90,7 +90,7 @@ add_component_dir (misc
)
add_component_dir (debug
debugging debuglog
debugging debuglog gldebug
)
IF(NOT WIN32 AND NOT APPLE)

View file

@ -0,0 +1,164 @@
// This file is based heavily on code from https://github.com/ThermalPixel/osgdemos/blob/master/osgdebug/EnableGLDebugOperation.cpp
// The original licence is included below:
/*
Copyright (c) 2014, Andreas Klein
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
The views and conclusions contained in the software and documentation are those
of the authors and should not be interpreted as representing official policies,
either expressed or implied, of the FreeBSD Project.
*/
#include "gldebug.hpp"
#include <cstdlib>
#include <components/debug/debuglog.hpp>
// OpenGL constants not provided by OSG:
#include <SDL_opengl_glext.h>
void debugCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *message, const void *userParam)
{
#ifdef GL_DEBUG_OUTPUT
std::string srcStr;
switch (source)
{
case GL_DEBUG_SOURCE_API:
srcStr = "API";
break;
case GL_DEBUG_SOURCE_WINDOW_SYSTEM:
srcStr = "WINDOW_SYSTEM";
break;
case GL_DEBUG_SOURCE_SHADER_COMPILER:
srcStr = "SHADER_COMPILER";
break;
case GL_DEBUG_SOURCE_THIRD_PARTY:
srcStr = "THIRD_PARTY";
break;
case GL_DEBUG_SOURCE_APPLICATION:
srcStr = "APPLICATION";
break;
case GL_DEBUG_SOURCE_OTHER:
srcStr = "OTHER";
break;
default:
srcStr = "UNDEFINED";
break;
}
std::string typeStr;
Debug::Level logSeverity = Debug::Warning;
switch (type)
{
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;
#endif
}
void enableGLDebugExtension(unsigned int contextID)
{
#ifdef GL_DEBUG_OUTPUT
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");
}
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(Debug::Info) << "OpenGL debug callback attached.";
}
else
#endif
Log(Debug::Error) << "Unable to attach OpenGL debug callback.";
}
Debug::EnableGLDebugOperation::EnableGLDebugOperation() : osg::GraphicsOperation("EnableGLDebugOperation", false)
{
}
void Debug::EnableGLDebugOperation::operator()(osg::GraphicsContext* graphicsContext)
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mMutex);
unsigned int contextID = graphicsContext->getState()->getContextID();
enableGLDebugExtension(contextID);
}
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;
}

View file

@ -0,0 +1,21 @@
#ifndef OPENMW_COMPONENTS_DEBUG_GLDEBUG_H
#define OPENMW_COMPONENTS_DEBUG_GLDEBUG_H
#include <osgViewer/ViewerEventHandlers>
namespace Debug
{
class EnableGLDebugOperation : public osg::GraphicsOperation
{
public:
EnableGLDebugOperation();
virtual void operator()(osg::GraphicsContext* graphicsContext);
private:
OpenThreads::Mutex mMutex;
};
bool shouldDebugOpenGL();
}
#endif

View file

@ -14,12 +14,18 @@ namespace Nif
class Extra : public Record
{
public:
std::string name;
ExtraPtr next; // Next extra data record in the list
void read(NIFStream *nif)
{
next.read(nif);
nif->getUInt(); // Size of the record
if (nif->getVersion() >= NIFStream::generateVersion(10,0,1,0))
name = nif->getString();
else if (nif->getVersion() <= NIFStream::generateVersion(4,2,2,0))
{
next.read(nif);
nif->getUInt(); // Size of the record
}
}
void post(NIFFile *nif) { next.post(nif); }
@ -44,18 +50,23 @@ class Named : public Record
public:
std::string name;
ExtraPtr extra;
ExtraList extralist;
ControllerPtr controller;
void read(NIFStream *nif)
{
name = nif->getString();
extra.read(nif);
if (nif->getVersion() < NIFStream::generateVersion(10,0,1,0))
extra.read(nif);
else
extralist.read(nif);
controller.read(nif);
}
void post(NIFFile *nif)
{
extra.post(nif);
extralist.post(nif);
controller.post(nif);
}
};

View file

@ -14,16 +14,31 @@ namespace Nif
if (external)
filename = nif->getString();
else
internal = nif->getChar();
if (!external && internal)
{
if (nif->getVersion() <= NIFStream::generateVersion(10,0,1,3))
internal = nif->getChar();
if (nif->getVersion() >= NIFStream::generateVersion(10,1,0,0))
filename = nif->getString(); // Original file path of the internal texture
}
if (nif->getVersion() <= NIFStream::generateVersion(10,0,1,3))
{
if (!external && internal)
data.read(nif);
}
else
{
data.read(nif);
}
pixel = nif->getUInt();
mipmap = nif->getUInt();
alpha = nif->getUInt();
nif->getChar(); // always 1
if (nif->getVersion() >= NIFStream::generateVersion(10,1,0,103))
nif->getBoolean(); // Direct rendering
if (nif->getVersion() >= NIFStream::generateVersion(20,2,0,4))
nif->getBoolean(); // NiPersistentSrcTextureRendererData is used instead of NiPixelData
}
void NiSourceTexture::post(NIFFile *nif)
@ -79,6 +94,12 @@ namespace Nif
NiParticleModifier::read(nif);
mBounceFactor = nif->getFloat();
if (nif->getVersion() >= NIFStream::generateVersion(4,2,2,0))
{
// Unused in NifSkope. Need to figure out what these do.
/*bool spawnOnCollision = */nif->getBoolean();
/*bool dieOnCollision = */nif->getBoolean();
}
}
void NiPlanarCollider::read(NIFStream *nif)

View file

@ -97,7 +97,10 @@ namespace Nif
// 01: Diffuse
// 10: Specular
// 11: Emissive
targetColor = (flags >> 4) & 3;
if (nif->getVersion() >= NIFStream::generateVersion(10,1,0,0))
targetColor = nif->getUShort() & 3;
else
targetColor = (flags >> 4) & 3;
data.read(nif);
}
@ -110,6 +113,8 @@ namespace Nif
void NiLookAtController::read(NIFStream *nif)
{
Controller::read(nif);
if (nif->getVersion() >= NIFStream::generateVersion(10,1,0,0))
lookAtFlags = nif->getUShort();
target.read(nif);
}
@ -165,25 +170,13 @@ namespace Nif
data.post(nif);
}
void NiAlphaController::read(NIFStream *nif)
void NiFloatInterpController::read(NIFStream *nif)
{
Controller::read(nif);
data.read(nif);
}
void NiAlphaController::post(NIFFile *nif)
{
Controller::post(nif);
data.post(nif);
}
void NiRollController::read(NIFStream *nif)
{
Controller::read(nif);
data.read(nif);
}
void NiRollController::post(NIFFile *nif)
void NiFloatInterpController::post(NIFFile *nif)
{
Controller::post(nif);
data.post(nif);
@ -192,6 +185,8 @@ namespace Nif
void NiGeomMorpherController::read(NIFStream *nif)
{
Controller::read(nif);
if (nif->getVersion() >= NIFFile::NIFVersion::VER_OB_OLD)
/*bool updateNormals = !!*/nif->getUShort();
data.read(nif);
if (nif->getVersion() >= NIFFile::NIFVersion::VER_MW)
/*bool alwaysActive = */nif->getChar(); // Always 0
@ -219,8 +214,11 @@ namespace Nif
{
Controller::read(nif);
mTexSlot = nif->getUInt();
/*unknown=*/nif->getUInt();/*0?*/
mDelta = nif->getFloat();
if (nif->getVersion() <= NIFStream::generateVersion(10,1,0,103))
{
timeStart = nif->getFloat();
mDelta = nif->getFloat();
}
mSources.read(nif);
}

View file

@ -118,6 +118,7 @@ class NiLookAtController : public Controller
{
public:
NodePtr target;
unsigned short lookAtFlags{0};
void read(NIFStream *nif);
void post(NIFFile *nif);
@ -142,23 +143,16 @@ public:
void post(NIFFile *nif);
};
class NiAlphaController : public Controller
struct NiFloatInterpController : public Controller
{
public:
NiFloatDataPtr data;
void read(NIFStream *nif);
void post(NIFFile *nif);
};
class NiRollController : public Controller
{
public:
NiFloatDataPtr data;
void read(NIFStream *nif);
void post(NIFFile *nif);
};
class NiAlphaController : public NiFloatInterpController { };
class NiRollController : public NiFloatInterpController { };
class NiGeomMorpherController : public Controller
{

View file

@ -33,13 +33,33 @@ void NiSkinInstance::post(NIFFile *nif)
void NiGeometryData::read(NIFStream *nif)
{
if (nif->getVersion() >= NIFStream::generateVersion(10,1,0,114))
nif->getInt(); // Group ID. (Almost?) always 0.
int verts = nif->getUShort();
if (nif->getVersion() >= NIFStream::generateVersion(10,1,0,0))
nif->skip(2); // Keep flags and compress flags
if (nif->getBoolean())
nif->getVector3s(vertices, verts);
unsigned int dataFlags = 0;
if (nif->getVersion() >= NIFStream::generateVersion(10,0,1,0))
dataFlags = nif->getUShort();
if (nif->getVersion() == NIFFile::NIFVersion::VER_BGS && nif->getBethVersion() > NIFFile::BethVersion::BETHVER_FO3)
nif->getUInt(); // Material CRC
if (nif->getBoolean())
{
nif->getVector3s(normals, verts);
if (dataFlags & 0x1000)
{
nif->getVector3s(tangents, verts);
nif->getVector3s(bitangents, verts);
}
}
center = nif->getVector3();
radius = nif->getFloat();
@ -47,14 +67,27 @@ void NiGeometryData::read(NIFStream *nif)
if (nif->getBoolean())
nif->getVector4s(colors, verts);
// In Morrowind this field only corresponds to the number of UV sets.
// NifTools research is inaccurate.
int uvs = nif->getUShort();
if(nif->getInt())
// Only the first 6 bits are used as a count. I think the rest are
// flags of some sort.
unsigned int numUVs = dataFlags;
if (nif->getVersion() <= NIFStream::generateVersion(4,2,2,0))
{
uvlist.resize(uvs);
for(int i = 0;i < uvs;i++)
numUVs = nif->getUShort();
// In Morrowind this field only corresponds to the number of UV sets.
// NifTools research is inaccurate.
if (nif->getVersion() > NIFFile::NIFVersion::VER_MW)
numUVs &= 0x3f;
}
if (nif->getVersion() == NIFFile::NIFVersion::VER_BGS && nif->getBethVersion() > 0)
numUVs &= 0x1;
bool hasUVs = true;
if (nif->getVersion() <= NIFFile::NIFVersion::VER_MW)
hasUVs = nif->getBoolean();
if (hasUVs)
{
uvlist.resize(numUVs);
for (unsigned int i = 0; i < numUVs; i++)
{
nif->getVector2s(uvlist[i], verts);
// flip the texture coordinates to convert them to the OpenGL convention of bottom-left image origin
@ -64,6 +97,12 @@ void NiGeometryData::read(NIFStream *nif)
}
}
}
if (nif->getVersion() >= NIFStream::generateVersion(10,0,1,0))
nif->getUShort(); // Consistency flags
if (nif->getVersion() >= NIFStream::generateVersion(20,0,0,4))
nif->skip(4); // Additional data
}
void NiTriShapeData::read(NIFStream *nif)
@ -75,13 +114,17 @@ void NiTriShapeData::read(NIFStream *nif)
// We have three times as many vertices as triangles, so this
// is always equal to tris*3.
int cnt = nif->getInt();
nif->getUShorts(triangles, cnt);
bool hasTriangles = true;
if (nif->getVersion() > NIFFile::NIFVersion::VER_OB_OLD)
hasTriangles = nif->getBoolean();
if (hasTriangles)
nif->getUShorts(triangles, cnt);
// Read the match list, which lists the vertices that are equal to
// vertices. We don't actually need need this for anything, so
// just skip it.
int verts = nif->getUShort();
for(int i=0;i < verts;i++)
unsigned short verts = nif->getUShort();
for (unsigned short i=0; i < verts; i++)
{
// Number of vertices matching vertex 'i'
int num = nif->getUShort();
@ -101,7 +144,11 @@ void NiTriStripsData::read(NIFStream *nif)
std::vector<unsigned short> lengths;
nif->getUShorts(lengths, numStrips);
if (!numStrips)
// "Has Strips" flag. Exceptionally useful.
bool hasStrips = false;
if (nif->getVersion() > NIFFile::NIFVersion::VER_OB_OLD)
hasStrips = nif->getBoolean();
if (!hasStrips || !numStrips)
return;
strips.resize(numStrips);
@ -140,27 +187,37 @@ void NiAutoNormalParticlesData::read(NIFStream *nif)
NiGeometryData::read(nif);
// Should always match the number of vertices
numParticles = nif->getUShort();
if (nif->getVersion() <= NIFFile::NIFVersion::VER_MW)
numParticles = nif->getUShort();
particleRadius = nif->getFloat();
if (nif->getVersion() <= NIFStream::generateVersion(10,0,1,0))
std::fill(particleRadii.begin(), particleRadii.end(), nif->getFloat());
else if (nif->getBoolean())
nif->getFloats(particleRadii, vertices.size());
activeCount = nif->getUShort();
// Particle sizes
if (nif->getBoolean())
{
// Particle sizes
nif->getFloats(sizes, vertices.size());
if (nif->getVersion() >= NIFStream::generateVersion(10,0,1,0) && nif->getBoolean())
nif->getQuaternions(rotations, vertices.size());
if (nif->getVersion() >= NIFStream::generateVersion(20,0,0,4))
{
if (nif->getBoolean())
nif->getFloats(rotationAngles, vertices.size());
if (nif->getBoolean())
nif->getVector3s(rotationAxes, vertices.size());
}
}
void NiRotatingParticlesData::read(NIFStream *nif)
{
NiAutoNormalParticlesData::read(nif);
if (nif->getBoolean())
{
// Rotation quaternions.
if (nif->getVersion() <= NIFStream::generateVersion(4,2,2,0) && nif->getBoolean())
nif->getQuaternions(rotations, vertices.size());
}
}
void NiPosData::read(NIFStream *nif)
@ -188,12 +245,27 @@ void NiPixelData::read(NIFStream *nif)
{
fmt = (Format)nif->getUInt();
for (unsigned int i = 0; i < 4; ++i)
colorMask[i] = nif->getUInt();
bpp = nif->getUInt();
if (nif->getVersion() < NIFStream::generateVersion(10,4,0,2))
{
for (unsigned int i = 0; i < 4; ++i)
colorMask[i] = nif->getUInt();
bpp = nif->getUInt();
nif->skip(8); // "Old Fast Compare". Whatever that means.
if (nif->getVersion() >= NIFStream::generateVersion(10,1,0,0))
pixelTiling = nif->getUInt();
}
else // TODO: see if anything from here needs to be implemented
{
bpp = nif->getChar();
nif->skip(4); // Renderer hint
nif->skip(4); // Extra data
nif->skip(4); // Flags
pixelTiling = nif->getUInt();
if (nif->getVersion() >= NIFStream::generateVersion(20,3,0,4))
sRGB = nif->getBoolean();
nif->skip(4*10); // Channel data
}
// 8 bytes of "Old Fast Compare". Whatever that means.
nif->skip(8);
palette.read(nif);
numberOfMipmaps = nif->getUInt();
@ -213,8 +285,10 @@ void NiPixelData::read(NIFStream *nif)
// Read the data
unsigned int numPixels = nif->getUInt();
if (numPixels)
nif->getUChars(data, numPixels);
bool hasFaces = nif->getVersion() >= NIFStream::generateVersion(10,4,0,2);
unsigned int numFaces = hasFaces ? nif->getUInt() : 1;
if (numPixels && numFaces)
nif->getUChars(data, numPixels * numFaces);
}
void NiPixelData::post(NIFFile *nif)
@ -249,6 +323,10 @@ void NiSkinData::read(NIFStream *nif)
if (nif->getVersion() >= NIFFile::NIFVersion::VER_MW && nif->getVersion() <= NIFStream::generateVersion(10,1,0,0))
nif->skip(4); // NiSkinPartition link
// Has vertex weights flag
if (nif->getVersion() > NIFStream::generateVersion(4,2,1,0) && !nif->getBoolean())
return;
bones.resize(boneNum);
for (BoneInfo &bi : bones)
{
@ -272,7 +350,7 @@ void NiMorphData::read(NIFStream *nif)
{
int morphCount = nif->getInt();
int vertCount = nif->getInt();
/*relative targets?*/nif->getChar();
nif->getChar(); // Relative targets, always 1
mMorphs.resize(morphCount);
for(int i = 0;i < morphCount;i++)
@ -290,7 +368,8 @@ void NiKeyframeData::read(NIFStream *nif)
if(mRotations->mInterpolationType == InterpolationType_XYZ)
{
//Chomp unused float
nif->getFloat();
if (nif->getVersion() <= NIFStream::generateVersion(10,1,0,0))
nif->getFloat();
mXRotations = std::make_shared<FloatKeyMap>();
mYRotations = std::make_shared<FloatKeyMap>();
mZRotations = std::make_shared<FloatKeyMap>();

View file

@ -35,7 +35,7 @@ namespace Nif
class NiGeometryData : public Record
{
public:
std::vector<osg::Vec3f> vertices, normals;
std::vector<osg::Vec3f> vertices, normals, tangents, bitangents;
std::vector<osg::Vec4f> colors;
std::vector< std::vector<osg::Vec2f> > uvlist;
osg::Vec3f center;
@ -73,13 +73,13 @@ struct NiLinesData : public NiGeometryData
class NiAutoNormalParticlesData : public NiGeometryData
{
public:
int numParticles;
float particleRadius;
int numParticles{0};
int activeCount;
std::vector<float> sizes;
std::vector<float> particleRadii, sizes, rotationAngles;
std::vector<osg::Quat> rotations;
std::vector<osg::Vec3f> rotationAxes;
void read(NIFStream *nif);
};
@ -87,8 +87,6 @@ public:
class NiRotatingParticlesData : public NiAutoNormalParticlesData
{
public:
std::vector<osg::Quat> rotations;
void read(NIFStream *nif);
};
@ -133,7 +131,8 @@ public:
Format fmt;
unsigned int colorMask[4];
unsigned int bpp;
unsigned int bpp, pixelTiling{0};
bool sRGB{false};
NiPalettePtr palette;
unsigned int numberOfMipmaps;

View file

@ -28,6 +28,10 @@ void NiTextureEffect::read(NIFStream *nif)
// Texture Filtering
nif->skip(4);
// Max anisotropy samples
if (nif->getVersion() >= NIFStream::generateVersion(20,5,0,4))
nif->skip(2);
clamp = nif->getUInt();
textureType = (TextureType)nif->getUInt();
@ -36,14 +40,12 @@ void NiTextureEffect::read(NIFStream *nif)
texture.read(nif);
/*
byte = 0
vector4 = [1,0,0,0]
short = 0
short = -75
short = 0
*/
nif->skip(23);
nif->skip(1); // Use clipping plane
nif->skip(16); // Clipping plane dimensions vector
if (nif->getVersion() <= NIFStream::generateVersion(10,2,0,0))
nif->skip(4); // PS2-specific shorts
if (nif->getVersion() <= NIFStream::generateVersion(4,1,0,12))
nif->skip(2); // Unknown short
}
void NiTextureEffect::post(NIFFile *nif)

View file

@ -34,6 +34,9 @@ struct NiDynamicEffect : public Node
void read(NIFStream *nif)
{
Node::read(nif);
if (nif->getVersion() >= nif->generateVersion(10,1,0,106)
&& nif->getBethVersion() < NIFFile::BethVersion::BETHVER_FO4)
nif->getBoolean(); // Switch state
unsigned int numAffectedNodes = nif->getUInt();
for (unsigned int i=0; i<numAffectedNodes; ++i)
nif->getUInt(); // ref to another Node

View file

@ -29,6 +29,56 @@ void NiVertWeightsExtraData::read(NIFStream *nif)
nif->skip(nif->getUShort() * sizeof(float)); // vertex weights I guess
}
void NiIntegerExtraData::read(NIFStream *nif)
{
Extra::read(nif);
data = nif->getUInt();
}
void NiIntegersExtraData::read(NIFStream *nif)
{
Extra::read(nif);
unsigned int num = nif->getUInt();
if (num)
nif->getUInts(data, num);
}
void NiBinaryExtraData::read(NIFStream *nif)
{
Extra::read(nif);
unsigned int size = nif->getUInt();
if (size)
nif->getChars(data, size);
}
void NiBooleanExtraData::read(NIFStream *nif)
{
Extra::read(nif);
data = nif->getBoolean();
}
void NiVectorExtraData::read(NIFStream *nif)
{
Extra::read(nif);
data = nif->getVector4();
}
void NiFloatExtraData::read(NIFStream *nif)
{
Extra::read(nif);
data = nif->getFloat();
}
void NiFloatsExtraData::read(NIFStream *nif)
{
Extra::read(nif);
unsigned int num = nif->getUInt();
if (num)
nif->getFloats(data, num);
}
}

View file

@ -60,5 +60,54 @@ public:
void read(NIFStream *nif);
};
struct NiIntegerExtraData : public Extra
{
unsigned int data;
void read(NIFStream *nif);
};
struct NiIntegersExtraData : public Extra
{
std::vector<unsigned int> data;
void read(NIFStream *nif);
};
struct NiBinaryExtraData : public Extra
{
std::vector<char> data;
void read(NIFStream *nif);
};
struct NiBooleanExtraData : public Extra
{
bool data;
void read(NIFStream *nif);
};
struct NiVectorExtraData : public Extra
{
osg::Vec4f data;
void read(NIFStream *nif);
};
struct NiFloatExtraData : public Extra
{
float data;
void read(NIFStream *nif);
};
struct NiFloatsExtraData : public Extra
{
std::vector<float> data;
void read(NIFStream *nif);
};
} // Namespace
#endif

View file

@ -16,108 +16,106 @@ NIFFile::NIFFile(Files::IStreamPtr stream, const std::string &name)
NIFFile::~NIFFile()
{
for (std::vector<Record*>::iterator it = records.begin() ; it != records.end(); ++it)
{
delete *it;
}
for (Record* record : records)
delete record;
}
template <typename NodeType> static Record* construct() { return new NodeType; }
struct RecordFactoryEntry {
typedef Record* (*create_t) ();
using create_t = Record* (*)();
create_t mCreate;
RecordType mType;
};
///Helper function for adding records to the factory map
static std::pair<std::string,RecordFactoryEntry> makeEntry(std::string recName, Record* (*create_t) (), RecordType type)
{
RecordFactoryEntry anEntry = {create_t,type};
return std::make_pair(recName, anEntry);
}
///These are all the record types we know how to read.
static std::map<std::string,RecordFactoryEntry> makeFactory()
{
std::map<std::string,RecordFactoryEntry> newFactory;
newFactory.insert(makeEntry("NiNode", &construct <NiNode> , RC_NiNode ));
newFactory.insert(makeEntry("NiSwitchNode", &construct <NiSwitchNode> , RC_NiSwitchNode ));
newFactory.insert(makeEntry("NiLODNode", &construct <NiLODNode> , RC_NiLODNode ));
newFactory.insert(makeEntry("AvoidNode", &construct <NiNode> , RC_AvoidNode ));
newFactory.insert(makeEntry("NiCollisionSwitch", &construct <NiNode> , RC_NiCollisionSwitch ));
newFactory.insert(makeEntry("NiBSParticleNode", &construct <NiNode> , RC_NiBSParticleNode ));
newFactory.insert(makeEntry("NiBSAnimationNode", &construct <NiNode> , RC_NiBSAnimationNode ));
newFactory.insert(makeEntry("NiBillboardNode", &construct <NiNode> , RC_NiBillboardNode ));
newFactory.insert(makeEntry("NiTriShape", &construct <NiTriShape> , RC_NiTriShape ));
newFactory.insert(makeEntry("NiTriStrips", &construct <NiTriStrips> , RC_NiTriStrips ));
newFactory.insert(makeEntry("NiLines", &construct <NiLines> , RC_NiLines ));
newFactory.insert(makeEntry("NiRotatingParticles", &construct <NiRotatingParticles> , RC_NiRotatingParticles ));
newFactory.insert(makeEntry("NiAutoNormalParticles", &construct <NiAutoNormalParticles> , RC_NiAutoNormalParticles ));
newFactory.insert(makeEntry("NiCamera", &construct <NiCamera> , RC_NiCamera ));
newFactory.insert(makeEntry("RootCollisionNode", &construct <NiNode> , RC_RootCollisionNode ));
newFactory.insert(makeEntry("NiTexturingProperty", &construct <NiTexturingProperty> , RC_NiTexturingProperty ));
newFactory.insert(makeEntry("NiFogProperty", &construct <NiFogProperty> , RC_NiFogProperty ));
newFactory.insert(makeEntry("NiMaterialProperty", &construct <NiMaterialProperty> , RC_NiMaterialProperty ));
newFactory.insert(makeEntry("NiZBufferProperty", &construct <NiZBufferProperty> , RC_NiZBufferProperty ));
newFactory.insert(makeEntry("NiAlphaProperty", &construct <NiAlphaProperty> , RC_NiAlphaProperty ));
newFactory.insert(makeEntry("NiVertexColorProperty", &construct <NiVertexColorProperty> , RC_NiVertexColorProperty ));
newFactory.insert(makeEntry("NiShadeProperty", &construct <NiShadeProperty> , RC_NiShadeProperty ));
newFactory.insert(makeEntry("NiDitherProperty", &construct <NiDitherProperty> , RC_NiDitherProperty ));
newFactory.insert(makeEntry("NiWireframeProperty", &construct <NiWireframeProperty> , RC_NiWireframeProperty ));
newFactory.insert(makeEntry("NiSpecularProperty", &construct <NiSpecularProperty> , RC_NiSpecularProperty ));
newFactory.insert(makeEntry("NiStencilProperty", &construct <NiStencilProperty> , RC_NiStencilProperty ));
newFactory.insert(makeEntry("NiVisController", &construct <NiVisController> , RC_NiVisController ));
newFactory.insert(makeEntry("NiGeomMorpherController", &construct <NiGeomMorpherController> , RC_NiGeomMorpherController ));
newFactory.insert(makeEntry("NiKeyframeController", &construct <NiKeyframeController> , RC_NiKeyframeController ));
newFactory.insert(makeEntry("NiAlphaController", &construct <NiAlphaController> , RC_NiAlphaController ));
newFactory.insert(makeEntry("NiRollController", &construct <NiRollController> , RC_NiRollController ));
newFactory.insert(makeEntry("NiUVController", &construct <NiUVController> , RC_NiUVController ));
newFactory.insert(makeEntry("NiPathController", &construct <NiPathController> , RC_NiPathController ));
newFactory.insert(makeEntry("NiMaterialColorController", &construct <NiMaterialColorController> , RC_NiMaterialColorController ));
newFactory.insert(makeEntry("NiBSPArrayController", &construct <NiBSPArrayController> , RC_NiBSPArrayController ));
newFactory.insert(makeEntry("NiParticleSystemController", &construct <NiParticleSystemController> , RC_NiParticleSystemController ));
newFactory.insert(makeEntry("NiFlipController", &construct <NiFlipController> , RC_NiFlipController ));
newFactory.insert(makeEntry("NiAmbientLight", &construct <NiLight> , RC_NiLight ));
newFactory.insert(makeEntry("NiDirectionalLight", &construct <NiLight> , RC_NiLight ));
newFactory.insert(makeEntry("NiPointLight", &construct <NiPointLight> , RC_NiLight ));
newFactory.insert(makeEntry("NiSpotLight", &construct <NiSpotLight> , RC_NiLight ));
newFactory.insert(makeEntry("NiTextureEffect", &construct <NiTextureEffect> , RC_NiTextureEffect ));
newFactory.insert(makeEntry("NiVertWeightsExtraData", &construct <NiVertWeightsExtraData> , RC_NiVertWeightsExtraData ));
newFactory.insert(makeEntry("NiTextKeyExtraData", &construct <NiTextKeyExtraData> , RC_NiTextKeyExtraData ));
newFactory.insert(makeEntry("NiStringExtraData", &construct <NiStringExtraData> , RC_NiStringExtraData ));
newFactory.insert(makeEntry("NiGravity", &construct <NiGravity> , RC_NiGravity ));
newFactory.insert(makeEntry("NiPlanarCollider", &construct <NiPlanarCollider> , RC_NiPlanarCollider ));
newFactory.insert(makeEntry("NiSphericalCollider", &construct <NiSphericalCollider> , RC_NiSphericalCollider ));
newFactory.insert(makeEntry("NiParticleGrowFade", &construct <NiParticleGrowFade> , RC_NiParticleGrowFade ));
newFactory.insert(makeEntry("NiParticleColorModifier", &construct <NiParticleColorModifier> , RC_NiParticleColorModifier ));
newFactory.insert(makeEntry("NiParticleRotation", &construct <NiParticleRotation> , RC_NiParticleRotation ));
newFactory.insert(makeEntry("NiFloatData", &construct <NiFloatData> , RC_NiFloatData ));
newFactory.insert(makeEntry("NiTriShapeData", &construct <NiTriShapeData> , RC_NiTriShapeData ));
newFactory.insert(makeEntry("NiTriStripsData", &construct <NiTriStripsData> , RC_NiTriStripsData ));
newFactory.insert(makeEntry("NiLinesData", &construct <NiLinesData> , RC_NiLinesData ));
newFactory.insert(makeEntry("NiVisData", &construct <NiVisData> , RC_NiVisData ));
newFactory.insert(makeEntry("NiColorData", &construct <NiColorData> , RC_NiColorData ));
newFactory.insert(makeEntry("NiPixelData", &construct <NiPixelData> , RC_NiPixelData ));
newFactory.insert(makeEntry("NiMorphData", &construct <NiMorphData> , RC_NiMorphData ));
newFactory.insert(makeEntry("NiKeyframeData", &construct <NiKeyframeData> , RC_NiKeyframeData ));
newFactory.insert(makeEntry("NiSkinData", &construct <NiSkinData> , RC_NiSkinData ));
newFactory.insert(makeEntry("NiUVData", &construct <NiUVData> , RC_NiUVData ));
newFactory.insert(makeEntry("NiPosData", &construct <NiPosData> , RC_NiPosData ));
newFactory.insert(makeEntry("NiRotatingParticlesData", &construct <NiRotatingParticlesData> , RC_NiRotatingParticlesData ));
newFactory.insert(makeEntry("NiAutoNormalParticlesData", &construct <NiAutoNormalParticlesData> , RC_NiAutoNormalParticlesData ));
newFactory.insert(makeEntry("NiSequenceStreamHelper", &construct <NiSequenceStreamHelper> , RC_NiSequenceStreamHelper ));
newFactory.insert(makeEntry("NiSourceTexture", &construct <NiSourceTexture> , RC_NiSourceTexture ));
newFactory.insert(makeEntry("NiSkinInstance", &construct <NiSkinInstance> , RC_NiSkinInstance ));
newFactory.insert(makeEntry("NiLookAtController", &construct <NiLookAtController> , RC_NiLookAtController ));
newFactory.insert(makeEntry("NiPalette", &construct <NiPalette> , RC_NiPalette ));
return newFactory;
std::map<std::string,RecordFactoryEntry> factory;
factory["NiNode"] = {&construct <NiNode> , RC_NiNode };
factory["NiSwitchNode"] = {&construct <NiSwitchNode> , RC_NiSwitchNode };
factory["NiLODNode"] = {&construct <NiLODNode> , RC_NiLODNode };
factory["AvoidNode"] = {&construct <NiNode> , RC_AvoidNode };
factory["NiCollisionSwitch"] = {&construct <NiNode> , RC_NiCollisionSwitch };
factory["NiBSParticleNode"] = {&construct <NiNode> , RC_NiBSParticleNode };
factory["NiBSAnimationNode"] = {&construct <NiNode> , RC_NiBSAnimationNode };
factory["NiBillboardNode"] = {&construct <NiNode> , RC_NiBillboardNode };
factory["NiTriShape"] = {&construct <NiTriShape> , RC_NiTriShape };
factory["NiTriStrips"] = {&construct <NiTriStrips> , RC_NiTriStrips };
factory["NiLines"] = {&construct <NiLines> , RC_NiLines };
factory["NiRotatingParticles"] = {&construct <NiRotatingParticles> , RC_NiRotatingParticles };
factory["NiAutoNormalParticles"] = {&construct <NiAutoNormalParticles> , RC_NiAutoNormalParticles };
factory["NiCamera"] = {&construct <NiCamera> , RC_NiCamera };
factory["RootCollisionNode"] = {&construct <NiNode> , RC_RootCollisionNode };
factory["NiTexturingProperty"] = {&construct <NiTexturingProperty> , RC_NiTexturingProperty };
factory["NiFogProperty"] = {&construct <NiFogProperty> , RC_NiFogProperty };
factory["NiMaterialProperty"] = {&construct <NiMaterialProperty> , RC_NiMaterialProperty };
factory["NiZBufferProperty"] = {&construct <NiZBufferProperty> , RC_NiZBufferProperty };
factory["NiAlphaProperty"] = {&construct <NiAlphaProperty> , RC_NiAlphaProperty };
factory["NiVertexColorProperty"] = {&construct <NiVertexColorProperty> , RC_NiVertexColorProperty };
factory["NiShadeProperty"] = {&construct <NiShadeProperty> , RC_NiShadeProperty };
factory["NiDitherProperty"] = {&construct <NiDitherProperty> , RC_NiDitherProperty };
factory["NiWireframeProperty"] = {&construct <NiWireframeProperty> , RC_NiWireframeProperty };
factory["NiSpecularProperty"] = {&construct <NiSpecularProperty> , RC_NiSpecularProperty };
factory["NiStencilProperty"] = {&construct <NiStencilProperty> , RC_NiStencilProperty };
factory["NiVisController"] = {&construct <NiVisController> , RC_NiVisController };
factory["NiGeomMorpherController"] = {&construct <NiGeomMorpherController> , RC_NiGeomMorpherController };
factory["NiKeyframeController"] = {&construct <NiKeyframeController> , RC_NiKeyframeController };
factory["NiAlphaController"] = {&construct <NiAlphaController> , RC_NiAlphaController };
factory["NiRollController"] = {&construct <NiRollController> , RC_NiRollController };
factory["NiUVController"] = {&construct <NiUVController> , RC_NiUVController };
factory["NiPathController"] = {&construct <NiPathController> , RC_NiPathController };
factory["NiMaterialColorController"] = {&construct <NiMaterialColorController> , RC_NiMaterialColorController };
factory["NiBSPArrayController"] = {&construct <NiBSPArrayController> , RC_NiBSPArrayController };
factory["NiParticleSystemController"] = {&construct <NiParticleSystemController> , RC_NiParticleSystemController };
factory["NiFlipController"] = {&construct <NiFlipController> , RC_NiFlipController };
factory["NiAmbientLight"] = {&construct <NiLight> , RC_NiLight };
factory["NiDirectionalLight"] = {&construct <NiLight> , RC_NiLight };
factory["NiPointLight"] = {&construct <NiPointLight> , RC_NiLight };
factory["NiSpotLight"] = {&construct <NiSpotLight> , RC_NiLight };
factory["NiTextureEffect"] = {&construct <NiTextureEffect> , RC_NiTextureEffect };
factory["NiVertWeightsExtraData"] = {&construct <NiVertWeightsExtraData> , RC_NiVertWeightsExtraData };
factory["NiTextKeyExtraData"] = {&construct <NiTextKeyExtraData> , RC_NiTextKeyExtraData };
factory["NiStringExtraData"] = {&construct <NiStringExtraData> , RC_NiStringExtraData };
factory["NiGravity"] = {&construct <NiGravity> , RC_NiGravity };
factory["NiPlanarCollider"] = {&construct <NiPlanarCollider> , RC_NiPlanarCollider };
factory["NiSphericalCollider"] = {&construct <NiSphericalCollider> , RC_NiSphericalCollider };
factory["NiParticleGrowFade"] = {&construct <NiParticleGrowFade> , RC_NiParticleGrowFade };
factory["NiParticleColorModifier"] = {&construct <NiParticleColorModifier> , RC_NiParticleColorModifier };
factory["NiParticleRotation"] = {&construct <NiParticleRotation> , RC_NiParticleRotation };
factory["NiFloatData"] = {&construct <NiFloatData> , RC_NiFloatData };
factory["NiTriShapeData"] = {&construct <NiTriShapeData> , RC_NiTriShapeData };
factory["NiTriStripsData"] = {&construct <NiTriStripsData> , RC_NiTriStripsData };
factory["NiLinesData"] = {&construct <NiLinesData> , RC_NiLinesData };
factory["NiVisData"] = {&construct <NiVisData> , RC_NiVisData };
factory["NiColorData"] = {&construct <NiColorData> , RC_NiColorData };
factory["NiPixelData"] = {&construct <NiPixelData> , RC_NiPixelData };
factory["NiMorphData"] = {&construct <NiMorphData> , RC_NiMorphData };
factory["NiKeyframeData"] = {&construct <NiKeyframeData> , RC_NiKeyframeData };
factory["NiSkinData"] = {&construct <NiSkinData> , RC_NiSkinData };
factory["NiUVData"] = {&construct <NiUVData> , RC_NiUVData };
factory["NiPosData"] = {&construct <NiPosData> , RC_NiPosData };
factory["NiRotatingParticlesData"] = {&construct <NiRotatingParticlesData> , RC_NiRotatingParticlesData };
factory["NiAutoNormalParticlesData"] = {&construct <NiAutoNormalParticlesData> , RC_NiAutoNormalParticlesData };
factory["NiSequenceStreamHelper"] = {&construct <NiSequenceStreamHelper> , RC_NiSequenceStreamHelper };
factory["NiSourceTexture"] = {&construct <NiSourceTexture> , RC_NiSourceTexture };
factory["NiSkinInstance"] = {&construct <NiSkinInstance> , RC_NiSkinInstance };
factory["NiLookAtController"] = {&construct <NiLookAtController> , RC_NiLookAtController };
factory["NiPalette"] = {&construct <NiPalette> , RC_NiPalette };
factory["NiIntegerExtraData"] = {&construct <NiIntegerExtraData> , RC_NiIntegerExtraData };
factory["NiIntegersExtraData"] = {&construct <NiIntegersExtraData> , RC_NiIntegersExtraData };
factory["NiBinaryExtraData"] = {&construct <NiBinaryExtraData> , RC_NiBinaryExtraData };
factory["NiBooleanExtraData"] = {&construct <NiBooleanExtraData> , RC_NiBooleanExtraData };
factory["NiVectorExtraData"] = {&construct <NiVectorExtraData> , RC_NiVectorExtraData };
factory["NiColorExtraData"] = {&construct <NiVectorExtraData> , RC_NiColorExtraData };
factory["NiFloatExtraData"] = {&construct <NiFloatExtraData> , RC_NiFloatExtraData };
factory["NiFloatsExtraData"] = {&construct <NiFloatsExtraData> , RC_NiFloatsExtraData };
return factory;
}
///Make the factory map used for parsing the file
static const std::map<std::string,RecordFactoryEntry> factories = makeFactory();
@ -148,15 +146,77 @@ void NIFFile::parse(Files::IStreamPtr stream)
// It's not used by Morrowind assets but Morrowind supports it.
if(ver != NIFStream::generateVersion(4,0,0,0) && ver != VER_MW)
fail("Unsupported NIF version: " + printVersion(ver));
// NIF data endianness
if (ver >= NIFStream::generateVersion(20,0,0,4))
{
unsigned char endianness = nif.getChar();
if (endianness == 0)
fail("Big endian NIF files are unsupported");
}
// User version
if (ver > NIFStream::generateVersion(10,0,1,8))
userVer = nif.getUInt();
// Number of records
size_t recNum = nif.getInt();
size_t recNum = nif.getUInt();
records.resize(recNum);
// Bethesda stream header
// It contains Bethesda format version and (useless) export information
if (ver == VER_OB_OLD ||
(userVer >= 3 && ((ver == VER_OB || ver == VER_BGS)
|| (ver >= NIFStream::generateVersion(10,1,0,0) && ver <= NIFStream::generateVersion(20,0,0,4) && userVer <= 11))))
{
bethVer = nif.getUInt();
nif.getExportString(); // Author
if (bethVer > BETHVER_FO4)
nif.getUInt(); // Unknown
nif.getExportString(); // Process script
nif.getExportString(); // Export script
if (bethVer == BETHVER_FO4)
nif.getExportString(); // Max file path
}
std::vector<std::string> recTypes;
std::vector<unsigned short> recTypeIndices;
const bool hasRecTypeListings = ver >= NIFStream::generateVersion(5,0,0,1);
if (hasRecTypeListings)
{
unsigned short recTypeNum = nif.getUShort();
if (recTypeNum) // Record type list
nif.getSizedStrings(recTypes, recTypeNum);
if (recNum) // Record type mapping for each record
nif.getUShorts(recTypeIndices, recNum);
if (ver >= NIFStream::generateVersion(5,0,0,6)) // Groups
{
if (ver >= NIFStream::generateVersion(20,1,0,1)) // String table
{
if (ver >= NIFStream::generateVersion(20,2,0,5) && recNum) // Record sizes
{
std::vector<unsigned int> recSizes; // Currently unused
nif.getUInts(recSizes, recNum);
}
unsigned int stringNum = nif.getUInt();
nif.getUInt(); // Max string length
if (stringNum)
nif.getSizedStrings(strings, stringNum);
}
std::vector<unsigned int> groups; // Currently unused
unsigned int groupNum = nif.getUInt();
if (groupNum)
nif.getUInts(groups, groupNum);
}
}
const bool hasRecordSeparators = ver >= NIFStream::generateVersion(10,0,0,0) && ver < NIFStream::generateVersion(10,2,0,0);
for(size_t i = 0;i < recNum;i++)
{
Record *r = nullptr;
std::string rec = nif.getString();
std::string rec = hasRecTypeListings ? recTypes[recTypeIndices[i]] : nif.getString();
if(rec.empty())
{
std::stringstream error;
@ -164,6 +224,17 @@ void NIFFile::parse(Files::IStreamPtr stream)
fail(error.str());
}
// Record separator. Some Havok records in Oblivion do not have it.
if (hasRecordSeparators && rec.compare(0, 3, "bhk"))
{
if (nif.getInt())
{
std::stringstream warning;
warning << "Record number " << i << " out of " << recNum << " is preceded by a non-zero separator.";
warn(warning.str());
}
}
std::map<std::string,RecordFactoryEntry>::const_iterator entry = factories.find(rec);
if (entry != factories.end())
@ -201,8 +272,8 @@ void NIFFile::parse(Files::IStreamPtr stream)
}
// Once parsing is done, do post-processing.
for(size_t i=0; i<recNum; i++)
records[i]->post(this);
for (Record* record : records)
record->post(this);
}
void NIFFile::setUseSkinning(bool skinning)

View file

@ -25,18 +25,19 @@ namespace Nif
return t;
}
///Currently specific for 4.0.0.2 and earlier
///Booleans in 4.0.0.2 (Morrowind format) and earlier are 4 byte, while in 4.1.0.0+ they're 1 byte.
bool NIFStream::getBoolean()
{
return getInt() != 0;
return getVersion() < generateVersion(4,1,0,0) ? getInt() != 0 : getChar() != 0;
}
///Read in a string, either from the string table using the index (currently absent) or from the stream using the specified length
///Read in a string, either from the string table using the index or from the stream using the specified length
std::string NIFStream::getString()
{
return getSizedString();
return getVersion() < generateVersion(20,1,0,1) ? getSizedString() : file->getString(getUInt());
}
// Convenience utility functions: get the versions of the currently read file
unsigned int NIFStream::getVersion() const { return file->getVersion(); }
unsigned int NIFStream::getUserVersion() const { return file->getBethVersion(); }

View file

@ -30,7 +30,7 @@ public:
PropertyList props;
// Bounding box info
bool hasBounds;
bool hasBounds{false};
osg::Vec3f boundPos;
Matrix3 boundRot;
osg::Vec3f boundXYZ; // Box size
@ -39,12 +39,15 @@ public:
{
Named::read(nif);
flags = nif->getUShort();
flags = nif->getBethVersion() <= 26 ? nif->getUShort() : nif->getUInt();
trafo = nif->getTrafo();
velocity = nif->getVector3();
props.read(nif);
if (nif->getVersion() <= NIFStream::generateVersion(4,2,2,0))
velocity = nif->getVector3();
if (nif->getBethVersion() <= NIFFile::BethVersion::BETHVER_FO3)
props.read(nif);
hasBounds = nif->getBoolean();
if (nif->getVersion() <= NIFStream::generateVersion(4,2,2,0))
hasBounds = nif->getBoolean();
if(hasBounds)
{
nif->getInt(); // always 1
@ -52,6 +55,9 @@ public:
boundRot = nif->getMatrix3();
boundXYZ = nif->getVector3();
}
// Reference to the collision object in Gamebryo files.
if (nif->getVersion() >= NIFStream::generateVersion(10,0,1,0))
nif->skip(4);
parent = nullptr;
@ -102,7 +108,8 @@ struct NiNode : Node
{
Node::read(nif);
children.read(nif);
effects.read(nif);
if (nif->getBethVersion() < NIFFile::BethVersion::BETHVER_FO4)
effects.read(nif);
// Discard transformations for the root node, otherwise some meshes
// occasionally get wrong orientation. Only for NiNode-s for now, but
@ -130,7 +137,39 @@ struct NiNode : Node
struct NiGeometry : Node
{
struct MaterialData
{
std::vector<std::string> materialNames;
std::vector<int> materialExtraData;
unsigned int activeMaterial{0};
bool materialNeedsUpdate{false};
void read(NIFStream *nif)
{
if (nif->getVersion() <= NIFStream::generateVersion(10,0,1,0))
return;
unsigned int numMaterials = 0;
if (nif->getVersion() <= NIFStream::generateVersion(20,1,0,3))
numMaterials = nif->getBoolean(); // Has Shader
else if (nif->getVersion() >= NIFStream::generateVersion(20,2,0,5))
numMaterials = nif->getUInt();
if (numMaterials)
{
nif->getStrings(materialNames, numMaterials);
nif->getInts(materialExtraData, numMaterials);
}
if (nif->getVersion() >= NIFStream::generateVersion(20,2,0,5))
activeMaterial = nif->getUInt();
if (nif->getVersion() >= NIFFile::NIFVersion::VER_BGS)
{
materialNeedsUpdate = nif->getBoolean();
if (nif->getVersion() == NIFFile::NIFVersion::VER_BGS && nif->getBethVersion() > NIFFile::BethVersion::BETHVER_FO3)
nif->skip(8);
}
}
};
NiSkinInstancePtr skin;
MaterialData materialData;
};
struct NiTriShape : NiGeometry
@ -149,6 +188,7 @@ struct NiTriShape : NiGeometry
Node::read(nif);
data.read(nif);
skin.read(nif);
materialData.read(nif);
}
void post(NIFFile *nif)
@ -170,6 +210,7 @@ struct NiTriStrips : NiGeometry
Node::read(nif);
data.read(nif);
skin.read(nif);
materialData.read(nif);
}
void post(NIFFile *nif)
@ -207,6 +248,8 @@ struct NiCamera : Node
{
struct Camera
{
unsigned short cameraFlags{0};
// Camera frustrum
float left, right, top, bottom, nearDist, farDist;
@ -216,15 +259,21 @@ struct NiCamera : Node
// Level of detail modifier
float LOD;
// Orthographic projection usage flag
bool orthographic{false};
void read(NIFStream *nif)
{
if (nif->getVersion() >= NIFStream::generateVersion(10,1,0,0))
cameraFlags = nif->getUShort();
left = nif->getFloat();
right = nif->getFloat();
top = nif->getFloat();
bottom = nif->getFloat();
nearDist = nif->getFloat();
farDist = nif->getFloat();
if (nif->getVersion() >= NIFStream::generateVersion(10,1,0,0))
orthographic = nif->getBoolean();
vleft = nif->getFloat();
vright = nif->getFloat();
vtop = nif->getFloat();
@ -243,6 +292,8 @@ struct NiCamera : Node
nif->getInt(); // -1
nif->getInt(); // 0
if (nif->getVersion() >= NIFStream::generateVersion(4,2,1,0))
nif->getInt(); // 0
}
};
@ -285,11 +336,14 @@ struct NiRotatingParticles : Node
// A node used as the base to switch between child nodes, such as for LOD levels.
struct NiSwitchNode : public NiNode
{
unsigned int switchFlags{0};
unsigned int initialIndex;
void read(NIFStream *nif)
{
NiNode::read(nif);
if (nif->getVersion() >= NIFStream::generateVersion(10,1,0,0))
switchFlags = nif->getUShort();
initialIndex = nif->getUInt();
}
};
@ -310,6 +364,12 @@ struct NiLODNode : public NiSwitchNode
NiSwitchNode::read(nif);
if (nif->getVersion() >= NIFFile::NIFVersion::VER_MW && nif->getVersion() <= NIFStream::generateVersion(10,0,1,0))
lodCenter = nif->getVector3();
else if (nif->getVersion() > NIFStream::generateVersion(10,0,1,0))
{
nif->skip(4); // NiLODData, unsupported at the moment
return;
}
unsigned int numLodLevels = nif->getUInt();
for (unsigned int i=0; i<numLodLevels; ++i)
{

View file

@ -6,25 +6,44 @@
namespace Nif
{
void Property::read(NIFStream *nif)
{
Named::read(nif);
flags = nif->getUShort();
}
void NiTexturingProperty::Texture::read(NIFStream *nif)
{
inUse = nif->getBoolean();
if(!inUse) return;
texture.read(nif);
clamp = nif->getUInt();
nif->skip(4); // Filter mode. Ignoring because global filtering settings are more sensible
uvSet = nif->getUInt();
if (nif->getVersion() <= NIFFile::NIFVersion::VER_OB)
{
clamp = nif->getInt();
nif->skip(4); // Filter mode. Ignoring because global filtering settings are more sensible
}
else
{
clamp = nif->getUShort() & 0xF;
}
// Max anisotropy. I assume we'll always only use the global anisotropy setting.
if (nif->getVersion() >= NIFStream::generateVersion(20,5,0,4))
nif->getUShort();
if (nif->getVersion() <= NIFFile::NIFVersion::VER_OB)
uvSet = nif->getUInt();
// Two PS2-specific shorts.
nif->skip(4);
nif->skip(2); // Unknown short
if (nif->getVersion() < NIFStream::generateVersion(10,4,0,2))
nif->skip(4);
if (nif->getVersion() <= NIFStream::generateVersion(4,1,0,18))
nif->skip(2); // Unknown short
else if (nif->getVersion() >= NIFStream::generateVersion(10,1,0,0))
{
if (nif->getBoolean()) // Has texture transform
{
nif->getVector2(); // UV translation
nif->getVector2(); // UV scale
nif->getFloat(); // W axis rotation
nif->getUInt(); // Transform method
nif->getVector2(); // Texture rotation origin
}
}
}
void NiTexturingProperty::Texture::post(NIFFile *nif)
@ -35,7 +54,10 @@ void NiTexturingProperty::Texture::post(NIFFile *nif)
void NiTexturingProperty::read(NIFStream *nif)
{
Property::read(nif);
apply = nif->getUInt();
if (nif->getVersion() <= NIFFile::NIFVersion::VER_OB_OLD || nif->getVersion() >= NIFStream::generateVersion(20,1,0,2))
flags = nif->getUShort();
if (nif->getVersion() <= NIFStream::generateVersion(20,1,0,1))
apply = nif->getUInt();
unsigned int numTextures = nif->getUInt();
@ -51,32 +73,53 @@ void NiTexturingProperty::read(NIFStream *nif)
envMapLumaBias = nif->getVector2();
bumpMapMatrix = nif->getVector4();
}
else if (i == 7 && textures[7].inUse && nif->getVersion() >= NIFStream::generateVersion(20,2,0,5))
/*float parallaxOffset = */nif->getFloat();
}
if (nif->getVersion() >= NIFStream::generateVersion(10,0,1,0))
{
unsigned int numShaderTextures = nif->getUInt();
shaderTextures.resize(numShaderTextures);
for (unsigned int i = 0; i < numShaderTextures; i++)
{
shaderTextures[i].read(nif);
if (shaderTextures[i].inUse)
nif->getUInt(); // Unique identifier
}
}
}
void NiTexturingProperty::post(NIFFile *nif)
{
Property::post(nif);
for(int i = 0;i < 7;i++)
for (size_t i = 0; i < textures.size(); i++)
textures[i].post(nif);
for (size_t i = 0; i < shaderTextures.size(); i++)
shaderTextures[i].post(nif);
}
void NiFogProperty::read(NIFStream *nif)
{
Property::read(nif);
mFlags = nif->getUShort();
mFogDepth = nif->getFloat();
mColour = nif->getVector3();
}
void S_MaterialProperty::read(NIFStream *nif)
{
ambient = nif->getVector3();
diffuse = nif->getVector3();
if (nif->getBethVersion() < 26)
{
ambient = nif->getVector3();
diffuse = nif->getVector3();
}
specular = nif->getVector3();
emissive = nif->getVector3();
glossiness = nif->getFloat();
alpha = nif->getFloat();
if (nif->getBethVersion() > 21)
emissive *= nif->getFloat();
}
void S_VertexColorProperty::read(NIFStream *nif)
@ -92,14 +135,29 @@ void S_AlphaProperty::read(NIFStream *nif)
void S_StencilProperty::read(NIFStream *nif)
{
enabled = nif->getChar();
compareFunc = nif->getInt();
stencilRef = nif->getUInt();
stencilMask = nif->getUInt();
failAction = nif->getInt();
zFailAction = nif->getInt();
zPassAction = nif->getInt();
drawMode = nif->getInt();
if (nif->getVersion() <= NIFFile::NIFVersion::VER_OB)
{
enabled = nif->getChar();
compareFunc = nif->getInt();
stencilRef = nif->getUInt();
stencilMask = nif->getUInt();
failAction = nif->getInt();
zFailAction = nif->getInt();
zPassAction = nif->getInt();
drawMode = nif->getInt();
}
else
{
unsigned short flags = nif->getUShort();
enabled = flags & 0x1;
failAction = (flags >> 1) & 0x7;
zFailAction = (flags >> 4) & 0x7;
zPassAction = (flags >> 7) & 0x7;
drawMode = (flags >> 10) & 0x3;
compareFunc = (flags >> 12) & 0x7;
stencilRef = nif->getUInt();
stencilMask = nif->getUInt();
}
}

View file

@ -29,18 +29,13 @@
namespace Nif
{
class Property : public Named
{
public:
// The meaning of these depends on the actual property type.
unsigned int flags;
void read(NIFStream *nif);
};
class Property : public Named { };
class NiTexturingProperty : public Property
{
public:
unsigned short flags{0u};
// A sub-texture
struct Texture
{
@ -92,6 +87,7 @@ public:
};
std::vector<Texture> textures;
std::vector<Texture> shaderTextures;
osg::Vec2f envMapLumaBias;
osg::Vec4f bumpMapMatrix;
@ -103,28 +99,80 @@ public:
class NiFogProperty : public Property
{
public:
unsigned short mFlags;
float mFogDepth;
osg::Vec3f mColour;
void read(NIFStream *nif);
};
// These contain no other data than the 'flags' field in Property
class NiShadeProperty : public Property { };
class NiDitherProperty : public Property { };
class NiZBufferProperty : public Property { };
class NiSpecularProperty : public Property { };
class NiWireframeProperty : public Property { };
// These contain no other data than the 'flags' field
struct NiShadeProperty : public Property
{
unsigned short flags{0u};
void read(NIFStream *nif)
{
Property::read(nif);
if (nif->getBethVersion() <= NIFFile::BethVersion::BETHVER_FO3)
flags = nif->getUShort();
}
};
struct NiDitherProperty : public Property
{
unsigned short flags;
void read(NIFStream* nif)
{
Property::read(nif);
flags = nif->getUShort();
}
};
struct NiZBufferProperty : public Property
{
unsigned short flags;
unsigned int testFunction;
void read(NIFStream *nif)
{
Property::read(nif);
flags = nif->getUShort();
testFunction = (flags >> 2) & 0x7;
if (nif->getVersion() >= NIFStream::generateVersion(4,1,0,12) && nif->getVersion() <= NIFFile::NIFVersion::VER_OB)
testFunction = nif->getUInt();
}
};
struct NiSpecularProperty : public Property
{
unsigned short flags;
void read(NIFStream* nif)
{
Property::read(nif);
flags = nif->getUShort();
}
};
struct NiWireframeProperty : public Property
{
unsigned short flags;
void read(NIFStream* nif)
{
Property::read(nif);
flags = nif->getUShort();
}
};
// The rest are all struct-based
template <typename T>
struct StructPropT : Property
{
T data;
unsigned short flags;
void read(NIFStream *nif)
{
Property::read(nif);
flags = nif->getUShort();
data.read(nif);
}
};
@ -132,7 +180,8 @@ struct StructPropT : Property
struct S_MaterialProperty
{
// The vector components are R,G,B
osg::Vec3f ambient, diffuse, specular, emissive;
osg::Vec3f ambient{1.f,1.f,1.f}, diffuse{1.f,1.f,1.f};
osg::Vec3f specular, emissive;
float glossiness, alpha;
void read(NIFStream *nif);
@ -246,9 +295,35 @@ struct S_StencilProperty
};
class NiAlphaProperty : public StructPropT<S_AlphaProperty> { };
class NiMaterialProperty : public StructPropT<S_MaterialProperty> { };
class NiVertexColorProperty : public StructPropT<S_VertexColorProperty> { };
class NiStencilProperty : public StructPropT<S_StencilProperty> { };
struct NiStencilProperty : public Property
{
S_StencilProperty data;
unsigned short flags{0u};
void read(NIFStream *nif)
{
Property::read(nif);
if (nif->getVersion() <= NIFFile::NIFVersion::VER_OB_OLD)
flags = nif->getUShort();
data.read(nif);
}
};
struct NiMaterialProperty : public Property
{
S_MaterialProperty data;
unsigned short flags{0u};
void read(NIFStream *nif)
{
Property::read(nif);
if (nif->getVersion() >= NIFStream::generateVersion(3,0,0,0)
&& nif->getVersion() <= NIFFile::NIFVersion::VER_OB_OLD)
flags = nif->getUShort();
data.read(nif);
}
};
} // Namespace
#endif

View file

@ -101,7 +101,15 @@ enum RecordType
RC_RootCollisionNode,
RC_NiSphericalCollider,
RC_NiLookAtController,
RC_NiPalette
RC_NiPalette,
RC_NiIntegerExtraData,
RC_NiIntegersExtraData,
RC_NiBinaryExtraData,
RC_NiBooleanExtraData,
RC_NiVectorExtraData,
RC_NiColorExtraData,
RC_NiFloatExtraData,
RC_NiFloatsExtraData
};
/// Base class for all records

View file

@ -310,11 +310,6 @@ void VisController::operator() (osg::Node* node, osg::NodeVisitor* nv)
RollController::RollController(const Nif::NiFloatData *data)
: mData(data->mKeyList, 1.f)
, mStartingTime(0)
{
}
RollController::RollController() : mStartingTime(0)
{
}
@ -322,7 +317,7 @@ RollController::RollController(const RollController &copy, const osg::CopyOp &co
: osg::NodeCallback(copy, copyop)
, Controller(copy)
, mData(copy.mData)
, mStartingTime(0)
, mStartingTime(copy.mStartingTime)
{
}
@ -449,7 +444,7 @@ void MaterialColorController::apply(osg::StateSet *stateset, osg::NodeVisitor *n
}
FlipController::FlipController(const Nif::NiFlipController *ctrl, const std::vector<osg::ref_ptr<osg::Texture2D> >& textures)
: mTexSlot(ctrl->mTexSlot)
: mTexSlot(0) // always affects diffuse
, mDelta(ctrl->mDelta)
, mTextures(textures)
{
@ -462,12 +457,6 @@ FlipController::FlipController(int texSlot, float delta, const std::vector<osg::
{
}
FlipController::FlipController()
: mTexSlot(0)
, mDelta(0.f)
{
}
FlipController::FlipController(const FlipController &copy, const osg::CopyOp &copyop)
: StateSetUpdater(copy, copyop)
, Controller(copy)

View file

@ -268,11 +268,11 @@ namespace NifOsg
{
private:
FloatInterpolator mData;
double mStartingTime;
double mStartingTime{0};
public:
RollController(const Nif::NiFloatData *data);
RollController();
RollController() = default;
RollController(const RollController& copy, const osg::CopyOp& copyop);
virtual void operator() (osg::Node* node, osg::NodeVisitor* nv);
@ -326,14 +326,14 @@ namespace NifOsg
class FlipController : public SceneUtil::StateSetUpdater, public SceneUtil::Controller
{
private:
int mTexSlot;
float mDelta;
int mTexSlot{0};
float mDelta{0.f};
std::vector<osg::ref_ptr<osg::Texture2D> > mTextures;
public:
FlipController(const Nif::NiFlipController* ctrl, const std::vector<osg::ref_ptr<osg::Texture2D> >& textures);
FlipController(int texSlot, float delta, const std::vector<osg::ref_ptr<osg::Texture2D> >& textures);
FlipController();
FlipController() = default;
FlipController(const FlipController& copy, const osg::CopyOp& copyop);
META_Object(NifOsg, FlipController)

View file

@ -799,22 +799,23 @@ namespace NifOsg
{
const Nif::NiFlipController* flipctrl = static_cast<const Nif::NiFlipController*>(ctrl.getPtr());
std::vector<osg::ref_ptr<osg::Texture2D> > textures;
// inherit wrap settings from the target slot
osg::Texture2D* inherit = dynamic_cast<osg::Texture2D*>(stateset->getTextureAttribute(0, osg::StateAttribute::TEXTURE));
osg::Texture2D::WrapMode wrapS = osg::Texture2D::REPEAT;
osg::Texture2D::WrapMode wrapT = osg::Texture2D::REPEAT;
if (inherit)
{
wrapS = inherit->getWrap(osg::Texture2D::WRAP_S);
wrapT = inherit->getWrap(osg::Texture2D::WRAP_T);
}
for (unsigned int i=0; i<flipctrl->mSources.length(); ++i)
{
Nif::NiSourceTexturePtr st = flipctrl->mSources[i];
if (st.empty())
continue;
// inherit wrap settings from the target slot
osg::Texture2D* inherit = dynamic_cast<osg::Texture2D*>(stateset->getTextureAttribute(flipctrl->mTexSlot, osg::StateAttribute::TEXTURE));
osg::Texture2D::WrapMode wrapS = osg::Texture2D::CLAMP_TO_EDGE;
osg::Texture2D::WrapMode wrapT = osg::Texture2D::CLAMP_TO_EDGE;
if (inherit)
{
wrapS = inherit->getWrap(osg::Texture2D::WRAP_S);
wrapT = inherit->getWrap(osg::Texture2D::WRAP_T);
}
osg::ref_ptr<osg::Image> image (handleSourceTexture(st.getPtr(), imageManager));
osg::ref_ptr<osg::Texture2D> texture (new osg::Texture2D(image));
if (image)
@ -1238,15 +1239,13 @@ namespace NifOsg
std::string boneName = Misc::StringUtils::lowerCase(bones[i].getPtr()->name);
SceneUtil::RigGeometry::BoneInfluence influence;
const std::vector<Nif::NiSkinData::VertWeight> &weights = data->bones[i].weights;
const auto& weights = data->bones[i].weights;
for(size_t j = 0;j < weights.size();j++)
{
influence.mWeights.emplace_back(weights[j].vertex, weights[j].weight);
}
influence.mWeights.push_back({weights[j].vertex, weights[j].weight});
influence.mInvBindMatrix = data->bones[i].trafo.toMatrix();
influence.mBoundSphere = osg::BoundingSpheref(data->bones[i].boundSphereCenter, data->bones[i].boundSphereRadius);
map->mData.emplace_back(boneName, influence);
map->mData.push_back({boneName, influence});
}
rig->setInfluenceMap(map);
@ -1451,7 +1450,7 @@ namespace NifOsg
// If this loop is changed such that the base texture isn't guaranteed to end up in texture unit 0, the shadow casting shader will need to be updated accordingly.
for (size_t i=0; i<texprop->textures.size(); ++i)
{
if (texprop->textures[i].inUse)
if (texprop->textures[i].inUse || (i == Nif::NiTexturingProperty::BaseTexture && !texprop->controller.empty()))
{
switch(i)
{
@ -1477,32 +1476,46 @@ namespace NifOsg
}
}
const Nif::NiTexturingProperty::Texture& tex = texprop->textures[i];
if(tex.texture.empty() && texprop->controller.empty())
{
if (i == 0)
Log(Debug::Warning) << "Base texture is in use but empty on shape \"" << nodeName << "\" in " << mFilename;
continue;
}
unsigned int uvSet = 0;
// create a new texture, will later attempt to share using the SharedStateManager
osg::ref_ptr<osg::Texture2D> texture2d;
if (!tex.texture.empty())
if (texprop->textures[i].inUse)
{
const Nif::NiSourceTexture *st = tex.texture.getPtr();
osg::ref_ptr<osg::Image> image = handleSourceTexture(st, imageManager);
texture2d = new osg::Texture2D(image);
if (image)
texture2d->setTextureSize(image->s(), image->t());
const Nif::NiTexturingProperty::Texture& tex = texprop->textures[i];
if(tex.texture.empty() && texprop->controller.empty())
{
if (i == 0)
Log(Debug::Warning) << "Base texture is in use but empty on shape \"" << nodeName << "\" in " << mFilename;
continue;
}
if (!tex.texture.empty())
{
const Nif::NiSourceTexture *st = tex.texture.getPtr();
osg::ref_ptr<osg::Image> image = handleSourceTexture(st, imageManager);
texture2d = new osg::Texture2D(image);
if (image)
texture2d->setTextureSize(image->s(), image->t());
}
else
texture2d = new osg::Texture2D;
bool wrapT = tex.clamp & 0x1;
bool wrapS = (tex.clamp >> 1) & 0x1;
texture2d->setWrap(osg::Texture::WRAP_S, wrapS ? osg::Texture::REPEAT : osg::Texture::CLAMP_TO_EDGE);
texture2d->setWrap(osg::Texture::WRAP_T, wrapT ? osg::Texture::REPEAT : osg::Texture::CLAMP_TO_EDGE);
uvSet = tex.uvSet;
}
else
{
// Texture only comes from NiFlipController, so tex is ignored, set defaults
texture2d = new osg::Texture2D;
bool wrapT = tex.clamp & 0x1;
bool wrapS = (tex.clamp >> 1) & 0x1;
texture2d->setWrap(osg::Texture::WRAP_S, wrapS ? osg::Texture::REPEAT : osg::Texture::CLAMP_TO_EDGE);
texture2d->setWrap(osg::Texture::WRAP_T, wrapT ? osg::Texture::REPEAT : osg::Texture::CLAMP_TO_EDGE);
texture2d->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT);
texture2d->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT);
uvSet = 0;
}
unsigned int texUnit = boundTextures.size();
@ -1590,7 +1603,7 @@ namespace NifOsg
break;
}
boundTextures.push_back(tex.uvSet);
boundTextures.push_back(uvSet);
}
}
handleTextureControllers(texprop, composite, imageManager, stateset, animflags);
@ -1717,7 +1730,7 @@ namespace NifOsg
osg::StateSet* stateset = node->getOrCreateStateSet();
// Specular lighting is enabled by default, but there's a quirk...
int specFlags = 1;
bool specEnabled = true;
osg::ref_ptr<osg::Material> mat (new osg::Material);
mat->setColorMode(hasVertexColors ? osg::Material::AMBIENT_AND_DIFFUSE : osg::Material::OFF);
@ -1736,7 +1749,8 @@ namespace NifOsg
case Nif::RC_NiSpecularProperty:
{
// Specular property can turn specular lighting off.
specFlags = property->flags;
auto specprop = static_cast<const Nif::NiSpecularProperty*>(property);
specEnabled = specprop->flags & 1;
break;
}
case Nif::RC_NiMaterialProperty:
@ -1820,7 +1834,7 @@ namespace NifOsg
}
// While NetImmerse and Gamebryo support specular lighting, Morrowind has its support disabled.
if (mVersion <= Nif::NIFFile::NIFVersion::VER_MW || specFlags == 0)
if (mVersion <= Nif::NIFFile::NIFVersion::VER_MW || !specEnabled)
mat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f,0.f,0.f,0.f));
if (lightmode == 0)

View file

@ -137,14 +137,13 @@ bool RigGeometry::initFromParentSkeleton(osg::NodeVisitor* nv)
}
mBoneNodesVector.clear();
for (auto& bonePair : mBoneSphereVector->mData)
for (auto& boundPair : mBoneSphereVector->mData)
{
const std::string& boneName = bonePair.first;
Bone* bone = mSkeleton->getBone(boneName);
Bone* bone = mSkeleton->getBone(boundPair.name);
if (!bone)
{
mBoneNodesVector.push_back(nullptr);
Log(Debug::Error) << "Error: RigGeometry did not find bone " << boneName;
Log(Debug::Error) << "Error: RigGeometry did not find bone " << boundPair.name;
continue;
}
@ -155,12 +154,11 @@ bool RigGeometry::initFromParentSkeleton(osg::NodeVisitor* nv)
{
for (auto &weight : pair.first)
{
const std::string& boneName = weight.first.first;
Bone* bone = mSkeleton->getBone(boneName);
Bone* bone = mSkeleton->getBone(weight.boneName);
if (!bone)
{
mBoneNodesVector.push_back(nullptr);
Log(Debug::Error) << "Error: RigGeometry did not find bone " << boneName;
Log(Debug::Error) << "Error: RigGeometry did not find bone " << weight.boneName;
continue;
}
@ -218,7 +216,7 @@ void RigGeometry::cull(osg::NodeVisitor* nv)
if (bone == nullptr)
continue;
accumulateMatrix(weight.first.second, bone->mMatrixInSkeletonSpace, weight.second, resultMat);
accumulateMatrix(weight.bindMatrix, bone->mMatrixInSkeletonSpace, weight.value, resultMat);
index++;
}
@ -281,7 +279,7 @@ void RigGeometry::updateBounds(osg::NodeVisitor *nv)
continue;
index++;
osg::BoundingSpheref bs = boundPair.second;
osg::BoundingSpheref bs = boundPair.sphere;
if (mGeomToSkelMatrix)
transformBoundingSphere(bone->mMatrixInSkeletonSpace * (*mGeomToSkelMatrix), bs);
else
@ -337,30 +335,21 @@ void RigGeometry::setInfluenceMap(osg::ref_ptr<InfluenceMap> influenceMap)
{
mInfluenceMap = influenceMap;
typedef std::map<unsigned short, std::vector<BoneWeight> > Vertex2BoneMap;
using Vertex2BoneMap = std::map<unsigned short, std::vector<BoneWeight>>;
Vertex2BoneMap vertex2BoneMap;
mBoneSphereVector = new BoneSphereVector;
mBoneSphereVector->mData.reserve(mInfluenceMap->mData.size());
mBone2VertexVector = new Bone2VertexVector;
for (auto& influencePair : mInfluenceMap->mData)
for (const BoneData& bone : mInfluenceMap->mData)
{
const std::string& boneName = influencePair.first;
const BoneInfluence& bi = influencePair.second;
mBoneSphereVector->mData.emplace_back(boneName, bi.mBoundSphere);
for (auto& weightPair: bi.mWeights)
{
std::vector<BoneWeight>& vec = vertex2BoneMap[weightPair.first];
vec.emplace_back(std::make_pair(boneName, bi.mInvBindMatrix), weightPair.second);
}
mBoneSphereVector->mData.push_back({bone.name, bone.influence.mBoundSphere});
for (auto& weight : bone.influence.mWeights)
vertex2BoneMap[weight.vertex].push_back({bone.name, bone.influence.mInvBindMatrix, weight.value});
}
Bone2VertexMap bone2VertexMap;
for (auto& vertexPair : vertex2BoneMap)
{
bone2VertexMap[vertexPair.second].emplace_back(vertexPair.first);
}
mBone2VertexVector->mData.reserve(bone2VertexMap.size());
mBone2VertexVector->mData.assign(bone2VertexMap.begin(), bone2VertexMap.end());

View file

@ -25,17 +25,32 @@ namespace SceneUtil
// Currently empty as this is difficult to implement. Technically we would need to compile both internal geometries in separate frames but this method is only called once. Alternatively we could compile just the static parts of the model.
virtual void compileGLObjects(osg::RenderInfo& renderInfo) const {}
struct VertexWeight
{
unsigned short vertex;
float value;
};
struct BoneInfluence
{
osg::Matrixf mInvBindMatrix;
osg::BoundingSpheref mBoundSphere;
// <vertex index, weight>
std::vector<std::pair<unsigned short, float>> mWeights;
std::vector<VertexWeight> mWeights;
};
struct BoneData
{
std::string name;
BoneInfluence influence;
bool operator<(const BoneData& other) const
{
return name < other.name;
}
};
struct InfluenceMap : public osg::Referenced
{
std::vector<std::pair<std::string, BoneInfluence>> mData;
std::vector<BoneData> mData;
};
void setInfluenceMap(osg::ref_ptr<InfluenceMap> influenceMap);
@ -79,23 +94,36 @@ namespace SceneUtil
osg::ref_ptr<InfluenceMap> mInfluenceMap;
typedef std::pair<std::string, osg::Matrixf> BoneBindMatrixPair;
struct BoneWeight
{
std::string boneName;
osg::Matrixf bindMatrix;
float value;
bool operator<(const BoneWeight& other) const
{
return boneName < other.boneName;
}
};
typedef std::pair<BoneBindMatrixPair, float> BoneWeight;
typedef std::vector<unsigned short> VertexList;
typedef std::map<std::vector<BoneWeight>, VertexList> Bone2VertexMap;
using VertexList = std::vector<unsigned short>;
using BoneWeightList = std::vector<BoneWeight>;
using Bone2VertexMap = std::map<BoneWeightList, VertexList>;
struct Bone2VertexVector : public osg::Referenced
{
std::vector<std::pair<std::vector<BoneWeight>, VertexList>> mData;
std::vector<std::pair<BoneWeightList, VertexList>> mData;
};
osg::ref_ptr<Bone2VertexVector> mBone2VertexVector;
struct BoneSphere
{
std::string name;
osg::BoundingSpheref sphere;
};
struct BoneSphereVector : public osg::Referenced
{
std::vector<std::pair<std::string, osg::BoundingSpheref>> mData;
std::vector<BoneSphere> mData;
};
osg::ref_ptr<BoneSphereVector> mBoneSphereVector;
std::vector<Bone*> mBoneNodesVector;

View file

@ -120,6 +120,29 @@ void GraphicsWindowSDL2::init()
setSwapInterval(_traits->vsync);
// Update traits with what we've actually been given
// Use intermediate to avoid signed/unsigned mismatch
int intermediateLocation;
SDL_GL_GetAttribute(SDL_GL_RED_SIZE, &intermediateLocation);
_traits->red = intermediateLocation;
SDL_GL_GetAttribute(SDL_GL_GREEN_SIZE, &intermediateLocation);
_traits->green = intermediateLocation;
SDL_GL_GetAttribute(SDL_GL_BLUE_SIZE, &intermediateLocation);
_traits->blue = intermediateLocation;
SDL_GL_GetAttribute(SDL_GL_ALPHA_SIZE, &intermediateLocation);
_traits->alpha = intermediateLocation;
SDL_GL_GetAttribute(SDL_GL_DEPTH_SIZE, &intermediateLocation);
_traits->depth = intermediateLocation;
SDL_GL_GetAttribute(SDL_GL_STENCIL_SIZE, &intermediateLocation);
_traits->stencil = intermediateLocation;
SDL_GL_GetAttribute(SDL_GL_DOUBLEBUFFER, &intermediateLocation);
_traits->doubleBuffer = intermediateLocation;
SDL_GL_GetAttribute(SDL_GL_MULTISAMPLEBUFFERS, &intermediateLocation);
_traits->sampleBuffers = intermediateLocation;
SDL_GL_GetAttribute(SDL_GL_MULTISAMPLESAMPLES, &intermediateLocation);
_traits->samples = intermediateLocation;
SDL_GL_MakeCurrent(oldWin, oldCtx);
mValid = true;

View file

@ -68,7 +68,7 @@ namespace Shader
static bool parseIncludes(boost::filesystem::path shaderPath, std::string& source, const std::string& fileName, int& fileNumber, std::set<boost::filesystem::path> includingFiles)
{
// An include is cyclic if it is being included by itself
if (includingFiles.insert(shaderPath / fileName).second == false)
if (includingFiles.insert(shaderPath/fileName).second == false)
{
Log(Debug::Error) << "Shader " << fileName << " error: Detected cyclic #includes";
return false;
@ -107,7 +107,7 @@ namespace Shader
else
{
lineDirectivePosition = 0;
lineNumber = 1;
lineNumber = 0;
}
lineNumber += std::count(source.begin() + lineDirectivePosition, source.begin() + foundPos, '\n');

View file

@ -105,6 +105,19 @@ If you are running macOS, you can also download Morrowind through Steam:
#. Launch the Steam client and let it download. You can then find ``Morrowind.esm`` at
``~/Library/Application Support/Steam/steamapps/common/The Elder Scrolls III - Morrowind/Data Files/``
Linux
----
Debian/Ubuntu - using "Steam Proton" & "OpenMW launcher".
----
#. Install Steam from "Ubuntu Software" Center
#. Enable Proton (basically WINE under the hood). This is done in the Steam client menu drop down. Select, "Steam | Settings" then in the "SteamPlay" section check the box next to "enable steam play for all other titles"
#. Now Morrowind should be selectable in your game list (as long as you own it). You can install it like any other game, choose to install it and remember the directory path of the location you pick.
#. Once the game files are installed, we can now install the open OpenMW Engine. I used "OpenMW launcher" from "Ubuntu Software" Center this has a wizard to help with the basic setup of OpenMW.
#. Launch "OpenMW launcher" and follow the setup wizard, when asked, point it at the location you installed Morrowind to, we will be looking for the directory that contains the Morrowing.esm file, for example '/steam library/steamapps/common/Morrowind/Data Files/'.
#. Everything should now be in place, click that big "PLAY" button and fire up OpenMW.
Nb. Bloodmoon.esm needs to be below Tribunal.esm in your datafiles list, if you dont have the right order a red "!" will apear next to the filename in the datafiles section of the OpenMW launcher, just drag bloodmoon below tribunal to fix it.
Wine
----

View file

@ -43,16 +43,16 @@ Savegames
Screenshots
-----------
+--------------+-----------------------------------------------------------------------------------------------+
| OS | Location |
+==============+===============================================================================================+
| Linux | ``$HOME/.local/share/openmw`` |
+--------------+-----------------------------------------------------------------------------------------------+
| Mac | ``$HOME/Library/Application\ Support/openmw`` |
+--------------+---------------+-------------------------------------------------------------------------------+
| Windows | File Explorer | ``Documents\My Games\OpenMW`` |
| | | |
| | PowerShell | ``Join-Path ([environment]::GetFolderPath("mydocuments")) "My Games\OpenMW"`` |
| | | |
| | Example | ``C:\Users\Username\Documents\My Games\OpenMW`` |
+--------------+---------------+-------------------------------------------------------------------------------+
+--------------+-----------------------------------------------------------------------------------------------------------+
| OS | Location |
+==============+===========================================================================================================+
| Linux | ``$HOME/.local/share/openmw/screenshots`` |
+--------------+-----------------------------------------------------------------------------------------------------------+
| Mac | ``$HOME/Library/Application\ Support/openmw/screenshots`` |
+--------------+---------------+-------------------------------------------------------------------------------------------+
| Windows | File Explorer | ``Documents\My Games\OpenMW\screenshots`` |
| | | |
| | PowerShell | ``Join-Path ([environment]::GetFolderPath("mydocuments")) "My Games\OpenMW\screenshots"`` |
| | | |
| | Example | ``C:\Users\Username\Documents\My Games\OpenMW\screenshots`` |
+--------------+---------------+-------------------------------------------------------------------------------------------+

View file

@ -331,8 +331,43 @@ If enabled then the character turns lower body to the direction of movement. Upp
This setting can be controlled in Advanced tab of the launcher.
smooth movement
---------------
:Type: boolean
:Range: True/False
:Default: False
Makes NPCs and player movement more smooth.
Recommended to use with "turn to movement direction" enabled.
This setting can be controlled in Advanced tab of the launcher.
NPCs avoid collisions
---------------------
:Type: boolean
:Range: True/False
:Default: False
If enabled NPCs apply evasion maneuver to avoid collisions with others.
This setting can be controlled in Advanced tab of the launcher.
NPCs give way
-------------
:Type: boolean
:Range: True/False
:Default: True
Standing NPCs give way to moving ones. Works only if 'NPCs avoid collisions' is enabled.
This setting can only be configured by editing the settings configuration file.
swim upward correction
----------------
----------------------
:Type: boolean
:Range: True/False

View file

@ -325,6 +325,15 @@ uncapped damage fatigue = false
# Turn lower body to movement direction. 'true' makes diagonal movement more realistic.
turn to movement direction = false
# Makes all movements of NPCs and player more smooth.
smooth movement = false
# All actors avoid collisions with other actors.
NPCs avoid collisions = false
# Give way to moving actors when idle. Requires 'NPCs avoid collisions' to be enabled.
NPCs give way = true
# Makes player swim a bit upward from the line of sight.
swim upward correction = false

View file

@ -43,6 +43,16 @@
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QCheckBox" name="avoidCollisionsCheckBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If enabled NPCs apply evasion maneuver to avoid collisions with others.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>NPCs avoid collisions</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QCheckBox" name="enableNavigatorCheckBox">
<property name="toolTip">
@ -233,6 +243,16 @@
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QCheckBox" name="smoothMovementCheckBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Makes NPCs and player movement more smooth. Recommended to use with "turn to movement direction" enabled.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Smooth movement</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QCheckBox" name="distantLandCheckBox">
<property name="toolTip">
@ -283,7 +303,7 @@
</property>
</widget>
</item>
<item row="5" column="0">
<item row="6" column="0">
<widget class="QCheckBox" name="animSourcesCheckBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Load per-group KF-files and skeleton files from Animations folder&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>