mirror of
https://github.com/TES3MP/openmw-tes3mp.git
synced 2025-01-30 03:15:32 +00:00
Add OpenMW VR commits up to 4 Jul 2021
# Conflicts: # CMakeLists.txt # LICENSE # README.md # apps/openmw/CMakeLists.txt # apps/openmw/engine.cpp # apps/openmw/mwclass/creature.cpp # apps/openmw/mwclass/npc.cpp # apps/openmw/mwclass/npc.hpp # apps/openmw/mwinput/bindingsmanager.cpp # apps/openmw/mwmechanics/combat.cpp
This commit is contained in:
commit
339a196579
222 changed files with 19245 additions and 753 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -11,6 +11,7 @@ prebuilt
|
|||
##windows build process
|
||||
/deps
|
||||
/MSVC*
|
||||
.vs
|
||||
|
||||
## doxygen
|
||||
Doxygen
|
||||
|
|
|
@ -163,7 +163,7 @@ macOS10.15_Xcode11:
|
|||
CCACHE_SIZE: 3G
|
||||
|
||||
variables: &engine-targets
|
||||
targets: "openmw,openmw-essimporter,openmw-iniimporter,openmw-launcher,openmw-wizard"
|
||||
targets: "openmw_vr,openmw-essimporter,openmw-iniimporter,openmw-launcher,openmw-wizard"
|
||||
package: "Engine"
|
||||
|
||||
variables: &cs-targets
|
||||
|
@ -378,37 +378,37 @@ Windows_MSBuild_Tests_RelWithDebInfo:
|
|||
# Gitlab can't successfully execute following binaries due to unknown reason
|
||||
# executables: "openmw_test_suite.exe,openmw_detournavigator_navmeshtilescache_benchmark.exe"
|
||||
|
||||
Debian_AndroidNDK_arm64-v8a:
|
||||
tags:
|
||||
- linux
|
||||
image: debian:bullseye
|
||||
variables:
|
||||
CCACHE_SIZE: 3G
|
||||
cache:
|
||||
key: Debian_AndroidNDK_arm64-v8a.v3
|
||||
paths:
|
||||
- apt-cache/
|
||||
- ccache/
|
||||
- build/extern/fetched/
|
||||
before_script:
|
||||
- export APT_CACHE_DIR=`pwd`/apt-cache && mkdir -pv $APT_CACHE_DIR
|
||||
- echo "deb http://deb.debian.org/debian unstable main contrib" > /etc/apt/sources.list
|
||||
- echo "google-android-ndk-installer google-android-installers/mirror select https://dl.google.com" | debconf-set-selections
|
||||
- apt-get update -yq
|
||||
- apt-get -q -o dir::cache::archives="$APT_CACHE_DIR" install -y cmake ccache curl unzip git build-essential google-android-ndk-installer
|
||||
stage: build
|
||||
script:
|
||||
- export CCACHE_BASEDIR="`pwd`"
|
||||
- export CCACHE_DIR="`pwd`/ccache" && mkdir -pv "$CCACHE_DIR"
|
||||
- ccache -z -M "${CCACHE_SIZE}"
|
||||
- CI/before_install.android.sh
|
||||
- CI/before_script.android.sh
|
||||
- cd build
|
||||
- cmake --build . -- -j $(nproc)
|
||||
- cmake --install .
|
||||
- ccache -s
|
||||
artifacts:
|
||||
paths:
|
||||
- build/install/
|
||||
# When CCache doesn't exist (e.g. first build on a fork), build takes more than 1h, which is the default for forks.
|
||||
timeout: 1h30m
|
||||
#Debian_AndroidNDK_arm64-v8a:
|
||||
# tags:
|
||||
# - linux
|
||||
# image: debian:bullseye
|
||||
# variables:
|
||||
# CCACHE_SIZE: 3G
|
||||
# cache:
|
||||
# key: Debian_AndroidNDK_arm64-v8a.v3
|
||||
# paths:
|
||||
# - apt-cache/
|
||||
# - ccache/
|
||||
# - build/extern/fetched/
|
||||
# before_script:
|
||||
# - export APT_CACHE_DIR=`pwd`/apt-cache && mkdir -pv $APT_CACHE_DIR
|
||||
# - echo "deb http://deb.debian.org/debian unstable main contrib" > /etc/apt/sources.list
|
||||
# - echo "google-android-ndk-installer google-android-installers/mirror select https://dl.google.com" | debconf-set-selections
|
||||
# - apt-get update -yq
|
||||
# - apt-get -q -o dir::cache::archives="$APT_CACHE_DIR" install -y cmake ccache curl unzip git build-essential google-android-ndk-installer
|
||||
# stage: build
|
||||
# script:
|
||||
# - export CCACHE_BASEDIR="`pwd`"
|
||||
# - export CCACHE_DIR="`pwd`/ccache" && mkdir -pv "$CCACHE_DIR"
|
||||
# - ccache -z -M "${CCACHE_SIZE}"
|
||||
# - CI/before_install.android.sh
|
||||
# - CI/before_script.android.sh
|
||||
# - cd build
|
||||
# - cmake --build . -- -j $(nproc)
|
||||
# - cmake --install .
|
||||
# - ccache -s
|
||||
# artifacts:
|
||||
# paths:
|
||||
# - build/install/
|
||||
# # When CCache doesn't exist (e.g. first build on a fork), build takes more than 1h, which is the default for forks.
|
||||
# timeout: 1h30m
|
||||
|
|
43
CI/before_script.msvc.sh
Normal file → Executable file
43
CI/before_script.msvc.sh
Normal file → Executable file
|
@ -93,6 +93,9 @@ while [ $# -gt 0 ]; do
|
|||
case $ARG in
|
||||
V )
|
||||
VERBOSE=true ;;
|
||||
|
||||
nVR )
|
||||
SKIP_VR=true ;;
|
||||
|
||||
d )
|
||||
SKIP_DOWNLOAD=true ;;
|
||||
|
@ -555,14 +558,14 @@ if [ -z $SKIP_DOWNLOAD ]; then
|
|||
"OpenAL-Soft-1.20.1.zip"
|
||||
|
||||
# OSG
|
||||
download "OpenSceneGraph 3.6.5" \
|
||||
"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"
|
||||
download "OpenSceneGraph 3.6.x" \
|
||||
"https://gitlab.com/madsbuvi/openmw-deps/-/raw/openmw-vr/windows/OSG-3.6.x-msvc${MSVC_REAL_YEAR}-win${BITS}.7z" \
|
||||
"OSG-3.6.x-msvc${MSVC_REAL_YEAR}-win${BITS}.7z"
|
||||
|
||||
if [ -n "$PDBS" ]; then
|
||||
download "OpenSceneGraph symbols" \
|
||||
"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"
|
||||
"https://gitlab.com/madsbuvi/openmw-deps/-/raw/openmw-vr/windows/OSG-3.6.x-msvc${MSVC_REAL_YEAR}-win${BITS}-sym.7z" \
|
||||
"OSG-3.6.x-msvc${MSVC_REAL_YEAR}-win${BITS}-sym.7z"
|
||||
fi
|
||||
|
||||
# SDL2
|
||||
|
@ -760,7 +763,7 @@ printf "OpenAL-Soft 1.20.1... "
|
|||
cd $DEPS
|
||||
echo
|
||||
# OSG
|
||||
printf "OSG 3.6.5... "
|
||||
printf "OSG 3.6.x... "
|
||||
{
|
||||
cd $DEPS_INSTALL
|
||||
if [ -d OSG ] && \
|
||||
|
@ -771,9 +774,9 @@ printf "OSG 3.6.5... "
|
|||
printf "Exists. "
|
||||
elif [ -z $SKIP_EXTRACT ]; then
|
||||
rm -rf OSG
|
||||
eval 7z x -y "${DEPS}/OSG-3.6.5-msvc${MSVC_REAL_YEAR}-win${BITS}.7z" $STRIP
|
||||
[ -n "$PDBS" ] && eval 7z x -y "${DEPS}/OSG-3.6.5-msvc${MSVC_REAL_YEAR}-win${BITS}-sym.7z" $STRIP
|
||||
mv "OSG-3.6.5-msvc${MSVC_REAL_YEAR}-win${BITS}" OSG
|
||||
eval 7z x -y "${DEPS}/OSG-3.6.x-msvc${MSVC_REAL_YEAR}-win${BITS}.7z" $STRIP
|
||||
[ -n "$PDBS" ] && eval 7z x -y "${DEPS}/OSG-3.6.x-msvc${MSVC_REAL_YEAR}-win${BITS}-sym.7z" $STRIP
|
||||
mv "OSG-3.6.x-msvc${MSVC_REAL_YEAR}-win${BITS}" OSG
|
||||
fi
|
||||
OSG_SDK="$(real_pwd)/OSG"
|
||||
add_cmake_opts -DOSG_DIR="$OSG_SDK"
|
||||
|
@ -783,7 +786,7 @@ printf "OSG 3.6.5... "
|
|||
else
|
||||
SUFFIX=""
|
||||
fi
|
||||
add_runtime_dlls $CONFIGURATION "$(pwd)/OSG/bin/"{OpenThreads,zlib,libpng}${SUFFIX}.dll \
|
||||
add_runtime_dlls $CONFIGURATION "$(pwd)/OSG/bin/"{OpenThreads,zlib,libpng16}${SUFFIX}.dll \
|
||||
"$(pwd)/OSG/bin/osg"{,Animation,DB,FX,GA,Particle,Text,Util,Viewer,Shadow}${SUFFIX}.dll
|
||||
add_osg_dlls $CONFIGURATION "$(pwd)/OSG/bin/osgPlugins-3.6.5/osgdb_"{bmp,dds,freetype,jpeg,osg,png,tga}${SUFFIX}.dll
|
||||
add_osg_dlls $CONFIGURATION "$(pwd)/OSG/bin/osgPlugins-3.6.5/osgdb_serializers_osg"{,animation,fx,ga,particle,text,util,viewer,shadow}${SUFFIX}.dll
|
||||
|
@ -934,6 +937,7 @@ printf "LZ4 1.9.2... "
|
|||
}
|
||||
cd $DEPS
|
||||
echo
|
||||
|
||||
# Google Test and Google Mock
|
||||
if [ ! -z $TEST_FRAMEWORK ]; then
|
||||
printf "Google test 1.10.0 ..."
|
||||
|
@ -988,6 +992,11 @@ if [ ! -z $TEST_FRAMEWORK ]; then
|
|||
|
||||
fi
|
||||
|
||||
# VR build
|
||||
if [ ! -Z $SKIP_VR ]; then
|
||||
add_cmake_opts -DBUILD_VR_OPENXR=no
|
||||
fi
|
||||
|
||||
echo
|
||||
cd $DEPS_INSTALL/..
|
||||
echo
|
||||
|
@ -1003,6 +1012,7 @@ if [ ! -z $CI ]; then
|
|||
-DBUILD_MWINIIMPORTER=no \
|
||||
-DBUILD_OPENCS=no \
|
||||
-DBUILD_OPENMW=no \
|
||||
-DBUILD_OPENMW_VR=no \
|
||||
-DBUILD_WIZARD=no
|
||||
;;
|
||||
openmw )
|
||||
|
@ -1011,20 +1021,31 @@ if [ ! -z $CI ]; then
|
|||
-DBUILD_LAUNCHER=no \
|
||||
-DBUILD_MWINIIMPORTER=no \
|
||||
-DBUILD_OPENCS=no \
|
||||
-DBUILD_OPENMW_VR=no \
|
||||
-DBUILD_WIZARD=no
|
||||
;;
|
||||
vr )
|
||||
echo " Building subproject: OpenMW-VR."
|
||||
add_cmake_opts -DBUILD_ESSIMPORTER=no \
|
||||
-DBUILD_OPENCS=no \
|
||||
-DBUILD_BSATOOL=no \
|
||||
-DBUILD_OPENMW=no \
|
||||
-DBUILD_ESMTOOL=no
|
||||
;;
|
||||
opencs )
|
||||
echo " Building subproject: OpenCS."
|
||||
add_cmake_opts -DBUILD_ESSIMPORTER=no \
|
||||
-DBUILD_LAUNCHER=no \
|
||||
-DBUILD_MWINIIMPORTER=no \
|
||||
-DBUILD_OPENMW=no \
|
||||
-DBUILD_OPENMW_VR=no \
|
||||
-DBUILD_WIZARD=no
|
||||
;;
|
||||
misc )
|
||||
echo " Building subprojects: Misc."
|
||||
add_cmake_opts -DBUILD_OPENCS=no \
|
||||
-DBUILD_OPENMW=no
|
||||
-DBUILD_OPENMW=no \
|
||||
-DBUILD_OPENMW_VR=no
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
|
|
@ -29,6 +29,7 @@ option(BUILD_BSATOOL "Build BSA extractor" ON)
|
|||
option(BUILD_ESMTOOL "Build ESM inspector" ON)
|
||||
option(BUILD_NIFTEST "Build nif file tester" ON)
|
||||
option(BUILD_DOCS "Build documentation." OFF )
|
||||
option(BUILD_OPENMW_VR "Build VR support using OpenXR" ON)
|
||||
option(BUILD_WITH_CODE_COVERAGE "Enable code coverage with gconv" OFF)
|
||||
option(BUILD_UNITTESTS "Enable Unittests with Google C++ Unittest" OFF)
|
||||
option(BUILD_BENCHMARKS "Build benchmarks with Google Benchmark" OFF)
|
||||
|
@ -215,7 +216,7 @@ endif()
|
|||
# Start of tes3mp addition
|
||||
#
|
||||
# Don't require certain dependencies for the server
|
||||
IF(BUILD_OPENMW OR BUILD_OPENCS)
|
||||
IF(BUILD_OPENMW OR BUILD_OPENCS OR BUILD_OPENMW_VR)
|
||||
# End of tes3mp addition
|
||||
set(USED_OSG_COMPONENTS
|
||||
osgDB
|
||||
|
@ -239,7 +240,7 @@ set(USED_OSG_PLUGINS
|
|||
# Start of tes3mp addition
|
||||
#
|
||||
# Don't require certain dependencies for the server
|
||||
ENDIF(BUILD_OPENMW OR BUILD_OPENCS)
|
||||
ENDIF(BUILD_OPENMW OR BUILD_OPENCS OR BUILD_OPENMW_VR)
|
||||
# End of tes3mp addition
|
||||
|
||||
add_subdirectory(extern)
|
||||
|
@ -249,7 +250,7 @@ add_subdirectory(extern)
|
|||
# Start of tes3mp addition
|
||||
#
|
||||
# Don't require certain dependencies for the server
|
||||
IF(BUILD_OPENMW OR BUILD_OPENCS)
|
||||
IF(BUILD_OPENMW OR BUILD_OPENCS OR BUILD_OPENMW_VR)
|
||||
# End of tes3mp addition
|
||||
# Require at least ffmpeg 3.2 for now
|
||||
SET(FFVER_OK FALSE)
|
||||
|
@ -312,7 +313,7 @@ endif()
|
|||
# Start of tes3mp addition
|
||||
#
|
||||
# Don't require certain dependencies for the server
|
||||
ENDIF(BUILD_OPENMW OR BUILD_OPENCS)
|
||||
ENDIF(BUILD_OPENMW OR BUILD_OPENCS OR BUILD_OPENMW_VR)
|
||||
# End of tes3mp addition
|
||||
|
||||
# Platform specific
|
||||
|
@ -332,7 +333,7 @@ endif()
|
|||
# Start of tes3mp addition
|
||||
#
|
||||
# Don't require certain dependencies for the server
|
||||
IF(BUILD_OPENMW OR BUILD_OPENCS)
|
||||
IF(BUILD_OPENMW OR BUILD_OPENCS OR BUILD_OPENMW_VR)
|
||||
# End of tes3mp addition
|
||||
if(OPENMW_USE_SYSTEM_BULLET)
|
||||
set(REQUIRED_BULLET_VERSION 286) # Bullet 286 required due to runtime bugfixes for btCapsuleShape
|
||||
|
@ -369,7 +370,7 @@ endif()
|
|||
# Start of tes3mp addition
|
||||
#
|
||||
# Don't require certain dependencies for the server
|
||||
ENDIF(BUILD_OPENMW OR BUILD_OPENCS)
|
||||
ENDIF(BUILD_OPENMW OR BUILD_OPENCS OR BUILD_OPENMW_VR)
|
||||
# End of tes3mp addition
|
||||
|
||||
if (NOT WIN32 AND BUILD_WIZARD) # windows users can just run the morrowind installer
|
||||
|
@ -394,10 +395,12 @@ endif()
|
|||
#
|
||||
# Don't require certain dependencies for the server
|
||||
# but keep OSG's headers (HACK)
|
||||
IF(BUILD_OPENMW OR BUILD_OPENCS)
|
||||
IF(BUILD_OPENMW OR BUILD_OPENCS OR BUILD_OPENMW_VR)
|
||||
# End of tes3mp addition
|
||||
|
||||
if(OPENMW_USE_SYSTEM_OSG)
|
||||
find_package(OpenSceneGraph 3.4.0 REQUIRED ${USED_OSG_COMPONENTS})
|
||||
find_package(OpenSceneGraph ${OSG_VERSION_REQUIRED} REQUIRED ${USED_OSG_COMPONENTS})
|
||||
|
||||
if (${OPENSCENEGRAPH_VERSION} VERSION_GREATER 3.6.2 AND ${OPENSCENEGRAPH_VERSION} VERSION_LESS 3.6.5)
|
||||
message(FATAL_ERROR "OpenSceneGraph version ${OPENSCENEGRAPH_VERSION} has critical regressions which cause crashes. Please upgrade to 3.6.5 or later. We strongly recommend using the tip of the official 'OpenSceneGraph-3.6' branch or the tip of '3.6' OpenMW/osg (OSGoS).")
|
||||
endif()
|
||||
|
@ -416,9 +419,9 @@ endif()
|
|||
#
|
||||
# Don't require certain dependencies for the server
|
||||
# but keep OSG's headers (HACK)
|
||||
ELSE(BUILD_OPENMW OR BUILD_OPENCS)
|
||||
ELSE(BUILD_OPENMW OR BUILD_OPENCS OR BUILD_OPENMW_VR)
|
||||
include_directories(${OPENSCENEGRAPH_INCLUDE_DIRS})
|
||||
ENDIF(BUILD_OPENMW OR BUILD_OPENCS)
|
||||
ENDIF(BUILD_OPENMW OR BUILD_OPENCS OR BUILD_OPENMW_VR)
|
||||
# End of tes3mp addition
|
||||
|
||||
set(BOOST_COMPONENTS system filesystem program_options iostreams)
|
||||
|
@ -441,7 +444,7 @@ find_package(Boost 1.6.2 REQUIRED COMPONENTS ${BOOST_COMPONENTS} OPTIONAL_COMPON
|
|||
# Start of tes3mp addition
|
||||
#
|
||||
# Don't require certain dependencies for the server
|
||||
IF(BUILD_OPENMW OR BUILD_OPENCS)
|
||||
IF(BUILD_OPENMW OR BUILD_OPENCS OR BUILD_OPENMW_VR)
|
||||
# End of tes3mp addition
|
||||
if(OPENMW_USE_SYSTEM_MYGUI)
|
||||
find_package(MyGUI 3.2.2 REQUIRED)
|
||||
|
@ -449,7 +452,7 @@ endif()
|
|||
# End of tes3mp addition
|
||||
#
|
||||
# Don't require certain dependencies for the server
|
||||
ENDIF(BUILD_OPENMW OR BUILD_OPENCS)
|
||||
ENDIF(BUILD_OPENMW OR BUILD_OPENCS OR BUILD_OPENMW_VR)
|
||||
# End of tes3mp addition
|
||||
find_package(SDL2 2.0.9 REQUIRED)
|
||||
# Start of tes3mp addition
|
||||
|
@ -521,6 +524,9 @@ configure_resource_file(${OpenMW_SOURCE_DIR}/files/tes3mp/tes3mp-server-default.
|
|||
pack_resource_file(${OpenMW_SOURCE_DIR}/files/settings-default.cfg
|
||||
"${OpenMW_BINARY_DIR}" "defaults.bin")
|
||||
|
||||
configure_resource_file(${OpenMW_SOURCE_DIR}/files/settings-overrides-vr.cfg
|
||||
"${OpenMW_BINARY_DIR}" "settings-overrides-vr.cfg")
|
||||
|
||||
configure_resource_file(${OpenMW_SOURCE_DIR}/files/openmw.appdata.xml
|
||||
"${OpenMW_BINARY_DIR}" "openmw.appdata.xml")
|
||||
|
||||
|
@ -598,20 +604,20 @@ endif (CMAKE_CXX_COMPILER_ID STREQUAL GNU OR CMAKE_CXX_COMPILER_ID STREQUAL Clan
|
|||
# Start of tes3mp addition
|
||||
#
|
||||
# Don't require certain dependencies for the server
|
||||
IF(BUILD_OPENMW OR BUILD_OPENCS)
|
||||
IF(BUILD_OPENMW OR BUILD_OPENCS OR BUILD_OPENMW_VR)
|
||||
# End of tes3mp addition
|
||||
add_subdirectory (extern/osg-ffmpeg-videoplayer)
|
||||
add_subdirectory (extern/oics)
|
||||
# Start of tes3mp addition
|
||||
#
|
||||
# Don't require certain dependencies for the server
|
||||
ENDIF(BUILD_OPENMW OR BUILD_OPENCS)
|
||||
ENDIF(BUILD_OPENMW OR BUILD_OPENCS OR BUILD_OPENMW_VR)
|
||||
# End of tes3mp addition
|
||||
add_subdirectory (extern/Base64)
|
||||
# Start of tes3mp addition
|
||||
#
|
||||
# Don't require certain dependencies for the server
|
||||
IF(BUILD_OPENMW OR BUILD_OPENCS)
|
||||
IF(BUILD_OPENMW OR BUILD_OPENCS OR BUILD_OPENMW_VR)
|
||||
# End of tes3mp addition
|
||||
if (BUILD_OPENCS)
|
||||
add_subdirectory (extern/osgQt)
|
||||
|
@ -619,7 +625,7 @@ endif()
|
|||
# Start of tes3mp addition
|
||||
#
|
||||
# Don't require certain dependencies for the server
|
||||
ENDIF(BUILD_OPENMW OR BUILD_OPENCS)
|
||||
ENDIF(BUILD_OPENMW OR BUILD_OPENCS OR BUILD_OPENMW_VR)
|
||||
# End of tes3mp addition
|
||||
|
||||
# Components
|
||||
|
@ -635,7 +641,7 @@ if (BUILD_MASTER)
|
|||
add_subdirectory( apps/master )
|
||||
endif()
|
||||
|
||||
if (BUILD_OPENMW)
|
||||
if (BUILD_OPENMW OR BUILD_OPENMW_VR)
|
||||
add_subdirectory( apps/openmw )
|
||||
endif()
|
||||
|
||||
|
@ -712,6 +718,23 @@ if (WIN32)
|
|||
set_target_properties(tes3mp PROPERTIES LINK_FLAGS_MINSIZEREL "/SUBSYSTEM:WINDOWS")
|
||||
endif()
|
||||
|
||||
# TODO: properties and link targets should be copied from openmw to openmw_vr instead of duplicating every line
|
||||
if (USE_DEBUG_CONSOLE AND BUILD_OPENMW_VR)
|
||||
set_target_properties(openmw_vr PROPERTIES LINK_FLAGS_DEBUG "/SUBSYSTEM:CONSOLE")
|
||||
set_target_properties(openmw_vr PROPERTIES LINK_FLAGS_RELWITHDEBINFO "/SUBSYSTEM:CONSOLE")
|
||||
set_target_properties(openmw_vr PROPERTIES COMPILE_DEFINITIONS $<$<CONFIG:Debug>:_CONSOLE>)
|
||||
elseif (BUILD_OPENMW_VR)
|
||||
# Turn off debug console, debug output will be written to visual studio output instead
|
||||
set_target_properties(openmw_vr PROPERTIES LINK_FLAGS_DEBUG "/SUBSYSTEM:WINDOWS")
|
||||
set_target_properties(openmw_vr PROPERTIES LINK_FLAGS_RELWITHDEBINFO "/SUBSYSTEM:WINDOWS")
|
||||
endif()
|
||||
|
||||
if (BUILD_OPENMW_VR)
|
||||
# Release builds don't use the debug console
|
||||
set_target_properties(openmw_vr PROPERTIES LINK_FLAGS_RELEASE "/SUBSYSTEM:WINDOWS")
|
||||
set_target_properties(openmw_vr PROPERTIES LINK_FLAGS_MINSIZEREL "/SUBSYSTEM:WINDOWS")
|
||||
endif()
|
||||
|
||||
# Play a bit with the warning levels
|
||||
|
||||
set(WARNINGS "/W4")
|
||||
|
@ -784,6 +807,14 @@ if (WIN32)
|
|||
endif()
|
||||
endif()
|
||||
|
||||
if (BUILD_OPENMW_VR)
|
||||
if (OPENMW_UNITY_BUILD)
|
||||
set_target_properties(openmw_vr PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD} /bigobj")
|
||||
else()
|
||||
set_target_properties(openmw_vr PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (BUILD_WIZARD)
|
||||
set_target_properties(openmw-wizard PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}")
|
||||
endif()
|
||||
|
@ -936,6 +967,7 @@ elseif(NOT APPLE)
|
|||
INSTALL(FILES "${INSTALL_SOURCE}/tes3mp-server-default.cfg" DESTINATION ".")
|
||||
# End of tes3mp addition
|
||||
INSTALL(FILES "${INSTALL_SOURCE}/gamecontrollerdb.txt" DESTINATION ".")
|
||||
INSTALL(FILES "${INSTALL_SOURCE}/xrcontrollersuggestions.xml" DESTINATION ".")
|
||||
|
||||
INSTALL(DIRECTORY "${INSTALL_SOURCE}/resources" DESTINATION ".")
|
||||
|
||||
|
@ -1076,6 +1108,7 @@ elseif(NOT APPLE)
|
|||
INSTALL(FILES "${INSTALL_SOURCE}/openmw.cfg.install" DESTINATION "${SYSCONFDIR}" RENAME "openmw.cfg" COMPONENT "openmw")
|
||||
INSTALL(FILES "${INSTALL_SOURCE}/resources/version" DESTINATION "${SYSCONFDIR}" COMPONENT "openmw")
|
||||
INSTALL(FILES "${INSTALL_SOURCE}/gamecontrollerdb.txt" DESTINATION "${SYSCONFDIR}" COMPONENT "openmw")
|
||||
INSTALL(FILES "${INSTALL_SOURCE}/xrcontrollersuggestions.xml" DESTINATION "${SYSCONFDIR}" COMPONENT "openmw")
|
||||
|
||||
# Start of tes3mp addition
|
||||
INSTALL(FILES "${INSTALL_SOURCE}/tes3mp-client-default.cfg" DESTINATION "${SYSCONFDIR}" COMPONENT "openmw")
|
||||
|
|
6
LICENSE
6
LICENSE
|
@ -679,3 +679,9 @@ AND AUTHORS OF THAT COVERED WORK FOR ANY DAMAEGS, DEMANDS, CLAIMS, LOSSES,
|
|||
CAUSES OF ACTION, LAWSUITS, JUDGMENTS EXPENSES (INCLUDING WITHOUT LIMITATION
|
||||
REASONABLE ATTORNEYS' FEES AND EXPENSES) OR ANY OTHER LIABLITY ARISING FROM,
|
||||
RELATED TO OR IN CONNECTION WITH YOUR ASSUMPTIONS OF LIABILITY.
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-----------------------------------------------------------------------------
|
||||
On Windows releases, this project bundles the OpenXR loader binary, which is
|
||||
licensed under the Apache 2.0 license. For license details, see:
|
||||
https://github.com/KhronosGroup/OpenXR-SDK/blob/master/LICENSE
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
TES3MP
|
||||
======
|
||||
TES3MP VR
|
||||
=========
|
||||
|
||||
Copyright (c) 2008-2015, OpenMW Team
|
||||
Copyright (c) 2016-2022, David Cernat & Stanislav Zhukov
|
||||
|
||||
TES3MP is a project adding multiplayer functionality to [OpenMW](https://github.com/OpenMW/openmw), an open-source game engine that supports playing "The Elder Scrolls III: Morrowind" by Bethesda Softworks.
|
||||
|
||||
When it's combined with Mads Buvik Sandvei's [fork adding VR support to OpenMW](https://gitlab.com/madsbuvi/openmw), the result is TES3MP VR.
|
||||
|
||||
* TES3MP version: 0.8.0
|
||||
* OpenMW version: 0.47.0
|
||||
* License: GPLv3 (see [LICENSE](https://github.com/TES3MP/TES3MP/blob/master/LICENSE) for more information)
|
||||
|
|
|
@ -230,6 +230,26 @@ bool Launcher::AdvancedPage::loadSettings()
|
|||
startDefaultCharacterAtField->setText(mGameSettings.value("start"));
|
||||
runScriptAfterStartupField->setText(mGameSettings.value("script-run"));
|
||||
}
|
||||
|
||||
// VR
|
||||
{
|
||||
std::string stereoMethod = Settings::Manager::getString("stereo method", "Stereo");
|
||||
useGeometryShaders->setChecked(stereoMethod == "GeometryShader");
|
||||
|
||||
loadSettingBool(useSharedShadowMaps, "shared shadow maps", "Stereo");
|
||||
loadSettingBool(preferDirectXSwapchains, "Prefer DirectX swapchains", "VR");
|
||||
loadSettingBool(preferSRGBSwapchains, "Prefer sRGB swapchains", "VR");
|
||||
loadSettingBool(useXrDebug, "enable XR_EXT_debug_utils", "VR Debug");
|
||||
loadSettingBool(logAllXrCalls, "log all openxr calls", "VR Debug");
|
||||
loadSettingBool(ignoreXrErrors, "continue on errors", "VR Debug");
|
||||
|
||||
double minimumSwingSpeed = Settings::Manager::getDouble("realistic combat minimum swing velocity", "VR");
|
||||
realisticCombatMinimumSwingSpeedSpinBox->setValue(minimumSwingSpeed);
|
||||
double maximumSwingSpeed = Settings::Manager::getDouble("realistic combat maximum swing velocity", "VR");
|
||||
realisticCombatMaximumSwingSpeedSpinBox->setValue(maximumSwingSpeed);
|
||||
double realHeightValue = Settings::Manager::getDouble("real height", "VR");
|
||||
realHeightSpinBox->setValue(realHeightValue);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -395,6 +415,34 @@ void Launcher::AdvancedPage::saveSettings()
|
|||
if (scriptRun != mGameSettings.value("script-run"))
|
||||
mGameSettings.setValue("script-run", scriptRun);
|
||||
}
|
||||
|
||||
// VR
|
||||
{
|
||||
std::string stereoMethod = "BruteForce";
|
||||
if (useGeometryShaders->isChecked())
|
||||
stereoMethod = "GeometryShader";
|
||||
if (stereoMethod != Settings::Manager::getString("stereo method", "Stereo"))
|
||||
Settings::Manager::setString("stereo method", "Stereo", stereoMethod);
|
||||
|
||||
saveSettingBool(useSharedShadowMaps, "shared shadow maps", "Stereo");
|
||||
saveSettingBool(preferDirectXSwapchains, "Prefer sRGB swapchains", "VR");
|
||||
saveSettingBool(preferSRGBSwapchains, "Prefer DirectX swapchains", "VR");
|
||||
saveSettingBool(useXrDebug, "enable XR_EXT_debug_utils", "VR Debug");
|
||||
saveSettingBool(logAllXrCalls, "log all openxr calls", "VR Debug");
|
||||
saveSettingBool(ignoreXrErrors, "continue on errors", "VR Debug");
|
||||
|
||||
double minimumSwingSpeed = realisticCombatMinimumSwingSpeedSpinBox->value();
|
||||
if (minimumSwingSpeed != Settings::Manager::getFloat("realistic combat minimum swing velocity", "VR"))
|
||||
Settings::Manager::setFloat("realistic combat minimum swing velocity", "VR", minimumSwingSpeed);
|
||||
|
||||
double maximumSwingSpeed = realisticCombatMaximumSwingSpeedSpinBox->value();
|
||||
if (maximumSwingSpeed != Settings::Manager::getFloat("realistic combat maximum swing velocity", "VR"))
|
||||
Settings::Manager::setFloat("realistic combat maximum swing velocity", "VR", maximumSwingSpeed);
|
||||
|
||||
double realHeightValue = realHeightSpinBox->value();
|
||||
if (realHeightValue != Settings::Manager::getFloat("real height", "VR"))
|
||||
Settings::Manager::setFloat("real height", "VR", realHeightValue);
|
||||
}
|
||||
}
|
||||
|
||||
void Launcher::AdvancedPage::loadSettingBool(QCheckBox *checkbox, const std::string &setting, const std::string &group)
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
#include <components/resource/scenemanager.hpp>
|
||||
#include <components/resource/resourcesystem.hpp>
|
||||
#include <components/sceneutil/lightmanager.hpp>
|
||||
#include <components/sdlutil/sdlgraphicswindow.hpp>
|
||||
|
||||
#include "../widget/scenetoolmode.hpp"
|
||||
|
||||
|
@ -104,7 +105,7 @@ RenderWidget::~RenderWidget()
|
|||
// before OSG 3.6.4, the default font was a static object, and if it wasn't attached to the scene when a graphics context was destroyed, it's program wouldn't be released.
|
||||
// 3.6.4 moved it into the object cache, which meant it usually got released, but not here.
|
||||
// 3.6.5 improved cleanup with osgViewer::CompositeViewer::removeView so it more reliably released associated state for objects in the object cache.
|
||||
osg::ref_ptr<osg::GraphicsContext> graphicsContext = mView->getCamera()->getGraphicsContext();
|
||||
osg::ref_ptr<osg::GraphicsContext> graphicsContext = SDLUtil::GraphicsWindowSDL2::findContext(*mView);
|
||||
osgText::Font::getDefaultFont()->releaseGLObjects(graphicsContext->getState());
|
||||
#endif
|
||||
}
|
||||
|
@ -132,7 +133,7 @@ osg::Camera *RenderWidget::getCamera()
|
|||
void RenderWidget::toggleRenderStats()
|
||||
{
|
||||
osgViewer::GraphicsWindow* window =
|
||||
static_cast<osgViewer::GraphicsWindow*>(mView->getCamera()->getGraphicsContext());
|
||||
static_cast<osgViewer::GraphicsWindow*>(SDLUtil::GraphicsWindowSDL2::findContext(*mView));
|
||||
|
||||
window->getEventQueue()->keyPress(osgGA::GUIEventAdapter::KEY_S);
|
||||
window->getEventQueue()->keyRelease(osgGA::GUIEventAdapter::KEY_S);
|
||||
|
@ -263,7 +264,7 @@ SceneWidget::SceneWidget(std::shared_ptr<Resource::ResourceSystem> resourceSyste
|
|||
SceneWidget::~SceneWidget()
|
||||
{
|
||||
// Since we're holding on to the resources past the existence of this graphics context, we'll need to manually release the created objects
|
||||
mResourceSystem->releaseGLObjects(mView->getCamera()->getGraphicsContext()->getState());
|
||||
mResourceSystem->releaseGLObjects(SDLUtil::GraphicsWindowSDL2::findContext(*mView)->getState());
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -170,7 +170,7 @@ include_directories(
|
|||
${FFmpeg_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
target_link_libraries(tes3mp
|
||||
set(OPENMW_LINK_TARGETS
|
||||
# CMake's built-in OSG finder does not use pkgconfig, so we have to
|
||||
# manually ensure the order is correct for inter-library dependencies.
|
||||
# This only makes a difference with `-DOPENMW_USE_SYSTEM_OSG=ON -DOSG_STATIC=ON`.
|
||||
|
@ -198,6 +198,16 @@ target_link_libraries(tes3mp
|
|||
${RakNet_LIBRARY}
|
||||
)
|
||||
|
||||
if (USE_SYSTEM_TINYXML)
|
||||
set(OPENMW_LINK_TARGETS ${OPENMW_LINK_TARGETS}
|
||||
${TinyXML_LIBRARIES})
|
||||
endif()
|
||||
|
||||
if (NOT UNIX)
|
||||
set(OPENMW_LINK_TARGETS ${OPENMW_LINK_TARGETS}
|
||||
${SDL2MAIN_LIBRARY})
|
||||
endif()
|
||||
|
||||
if(OSG_STATIC)
|
||||
unset(_osg_plugins_static_files)
|
||||
add_library(openmw_osg_plugins INTERFACE)
|
||||
|
@ -214,30 +224,36 @@ if(OSG_STATIC)
|
|||
# We use --whole-archive because OSG plugins use registration.
|
||||
get_whole_archive_options(_opts ${_osg_plugins_static_files})
|
||||
target_link_options(openmw_osg_plugins INTERFACE ${_opts})
|
||||
target_link_libraries(tes3mp openmw_osg_plugins)
|
||||
set(OPENMW_LINK_TARGETS ${OPENMW_LINK_TARGETS}
|
||||
openmw_osg_plugins)
|
||||
|
||||
if(OPENMW_USE_SYSTEM_OSG)
|
||||
# OSG plugin pkgconfig files are missing these dependencies.
|
||||
# https://github.com/openscenegraph/OpenSceneGraph/issues/1052
|
||||
target_link_libraries(tes3mp freetype jpeg png)
|
||||
set(OPENMW_LINK_TARGETS ${OPENMW_LINK_TARGETS}
|
||||
freetype jpeg png)
|
||||
endif()
|
||||
endif(OSG_STATIC)
|
||||
|
||||
if (ANDROID)
|
||||
target_link_libraries(tes3mp EGL android log z)
|
||||
set(OPENMW_LINK_TARGETS ${OPENMW_LINK_TARGETS}
|
||||
EGL android log z)
|
||||
endif (ANDROID)
|
||||
|
||||
if (USE_SYSTEM_TINYXML)
|
||||
target_link_libraries(tes3mp ${TinyXML_LIBRARIES})
|
||||
set(OPENMW_LINK_TARGETS ${OPENMW_LINK_TARGETS}
|
||||
${TinyXML_LIBRARIES})
|
||||
endif()
|
||||
|
||||
if (NOT UNIX)
|
||||
target_link_libraries(tes3mp ${SDL2MAIN_LIBRARY})
|
||||
set(OPENMW_LINK_TARGETS ${OPENMW_LINK_TARGETS}
|
||||
${SDL2MAIN_LIBRARY})
|
||||
endif()
|
||||
|
||||
# Fix for not visible pthreads functions for linker with glibc 2.15
|
||||
if (UNIX AND NOT APPLE)
|
||||
target_link_libraries(tes3mp ${CMAKE_THREAD_LIBS_INIT})
|
||||
set(OPENMW_LINK_TARGETS ${OPENMW_LINK_TARGETS}
|
||||
${CMAKE_THREAD_LIBS_INIT})
|
||||
endif()
|
||||
|
||||
if(APPLE)
|
||||
|
@ -252,24 +268,26 @@ if(APPLE)
|
|||
configure_file("${OpenMW_BINARY_DIR}/openmw.cfg" ${BUNDLE_RESOURCES_DIR} COPYONLY)
|
||||
configure_file("${OpenMW_BINARY_DIR}/gamecontrollerdb.txt" ${BUNDLE_RESOURCES_DIR} COPYONLY)
|
||||
|
||||
add_custom_command(TARGET openmw
|
||||
POST_BUILD
|
||||
COMMAND cp "${OpenMW_BINARY_DIR}/resources/version" "${BUNDLE_RESOURCES_DIR}/resources")
|
||||
|
||||
find_library(COCOA_FRAMEWORK Cocoa)
|
||||
find_library(IOKIT_FRAMEWORK IOKit)
|
||||
target_link_libraries(tes3mp ${COCOA_FRAMEWORK} ${IOKIT_FRAMEWORK})
|
||||
set(OPENMW_LINK_TARGETS ${OPENMW_LINK_TARGETS}
|
||||
${COCOA_FRAMEWORK}
|
||||
${IOKIT_FRAMEWORK})
|
||||
|
||||
if (FFmpeg_FOUND)
|
||||
find_library(COREVIDEO_FRAMEWORK CoreVideo)
|
||||
find_library(VDA_FRAMEWORK VideoDecodeAcceleration)
|
||||
target_link_libraries(tes3mp z ${COREVIDEO_FRAMEWORK} ${VDA_FRAMEWORK})
|
||||
set(OPENMW_LINK_TARGETS ${OPENMW_LINK_TARGETS}
|
||||
z
|
||||
${COREVIDEO_FRAMEWORK}
|
||||
${VDA_FRAMEWORK})
|
||||
endif()
|
||||
endif(APPLE)
|
||||
|
||||
if (BUILD_WITH_CODE_COVERAGE)
|
||||
add_definitions (--coverage)
|
||||
target_link_libraries(tes3mp gcov)
|
||||
set(OPENMW_LINK_TARGETS ${OPENMW_LINK_TARGETS}
|
||||
gcov)
|
||||
endif()
|
||||
|
||||
if (MSVC)
|
||||
|
@ -278,6 +296,95 @@ if (MSVC)
|
|||
set (CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /bigobj")
|
||||
endif (CMAKE_CL_64)
|
||||
endif (MSVC)
|
||||
|
||||
if(BUILD_OPENMW)
|
||||
if (NOT ANDROID)
|
||||
openmw_add_executable(openmw
|
||||
${OPENMW_FILES}
|
||||
${GAME} ${GAME_HEADER}
|
||||
${APPLE_BUNDLE_RESOURCES}
|
||||
)
|
||||
else ()
|
||||
add_library(openmw
|
||||
SHARED
|
||||
${OPENMW_FILES}
|
||||
${GAME} ${GAME_HEADER}
|
||||
)
|
||||
endif ()
|
||||
|
||||
target_link_libraries(tes3mp ${OPENMW_LINK_TARGETS})
|
||||
|
||||
if(APPLE)
|
||||
add_custom_command(TARGET tes3mp
|
||||
POST_BUILD
|
||||
COMMAND cp "${OpenMW_BINARY_DIR}/resources/version" "${BUNDLE_RESOURCES_DIR}/resources")
|
||||
endif(APPLE)
|
||||
|
||||
if (WIN32)
|
||||
INSTALL(TARGETS openmw RUNTIME DESTINATION ".")
|
||||
endif (WIN32)
|
||||
endif()
|
||||
|
||||
if(BUILD_OPENMW_VR)
|
||||
# Use of FetchContent to include the OpenXR SDK requires CMake 3.11
|
||||
if(${CMAKE_VERSION} VERSION_LESS "3.11")
|
||||
message(FATAL_ERROR "Building openmw_vr requires CMake version 3.11 or later.")
|
||||
endif()
|
||||
|
||||
# TODO: Openmw and openmw_vr should preferrably share game code as a static or shared library
|
||||
# instead of being compiled separately, though for now that's not possible as i depend on
|
||||
# USE_OPENXR preprocessor switches.
|
||||
set(OPENMW_VR_FILES
|
||||
vrengine.cpp
|
||||
)
|
||||
add_openmw_dir (mwvr
|
||||
openxraction openxractionset openxrdebug openxrinput openxrmanager openxrmanagerimpl openxrplatform openxrswapchain openxrswapchainimage openxrswapchainimpl openxrtracker openxrtypeconversions
|
||||
realisticcombat
|
||||
vranimation vrcamera vrenvironment vrframebuffer vrgui vrinputmanager vrinput vrlistbox vrmetamenu vrpointer vrsession vrtracking vrtypes vrutil vrviewer vrvirtualkeyboard
|
||||
)
|
||||
|
||||
openmw_add_executable(openmw_vr
|
||||
${OPENMW_FILES}
|
||||
${OPENMW_VR_FILES}
|
||||
${GAME} ${GAME_HEADER}
|
||||
${APPLE_BUNDLE_RESOURCES}
|
||||
)
|
||||
|
||||
configure_resource_file(${OpenMW_SOURCE_DIR}/files/xrcontrollersuggestions.xml
|
||||
"${OpenMW_BINARY_DIR}" "xrcontrollersuggestions.xml")
|
||||
|
||||
########### Import the OpenXR SDK
|
||||
# Force the openxr-sdk to use its bundled jsoncpp to avoid problems from system jsoncpp if present
|
||||
set(BUILD_WITH_SYSTEM_JSONCPP off)
|
||||
include(FetchContent)
|
||||
FetchContent_Declare(
|
||||
OpenXR
|
||||
GIT_REPOSITORY https://github.com/KhronosGroup/OpenXR-SDK.git
|
||||
GIT_TAG release-1.0.15
|
||||
)
|
||||
FetchContent_MakeAvailable(OpenXR)
|
||||
|
||||
target_link_libraries(openmw_vr openxr_loader)
|
||||
|
||||
# Preprocessor variable used to control code paths to vr code
|
||||
if (WIN32)
|
||||
target_compile_options(openmw_vr PUBLIC -DUSE_OPENXR -DXR_USE_GRAPHICS_API_OPENGL -DXR_USE_GRAPHICS_API_D3D11 -DXR_USE_PLATFORM_WIN32)
|
||||
elseif(UNIX)
|
||||
target_compile_options(openmw_vr PUBLIC -DUSE_OPENXR -DXR_USE_GRAPHICS_API_OPENGL -DXR_USE_PLATFORM_XLIB)
|
||||
find_package(X11 REQUIRED)
|
||||
target_link_libraries(openmw_vr ${X11_LIBRARIES})
|
||||
endif()
|
||||
target_link_libraries(openmw_vr ${OPENMW_LINK_TARGETS})
|
||||
if(APPLE)
|
||||
add_custom_command(TARGET openmw_vr
|
||||
POST_BUILD
|
||||
COMMAND cp "${OpenMW_BINARY_DIR}/resources/version" "${BUNDLE_RESOURCES_DIR}/resources")
|
||||
endif(APPLE)
|
||||
|
||||
if (WIN32)
|
||||
INSTALL(TARGETS openmw_vr RUNTIME DESTINATION ".")
|
||||
endif (WIN32)
|
||||
endif()
|
||||
|
||||
if (WIN32)
|
||||
INSTALL(TARGETS tes3mp RUNTIME DESTINATION ".")
|
||||
|
|
|
@ -24,12 +24,17 @@
|
|||
#include <components/sdlutil/sdlgraphicswindow.hpp>
|
||||
#include <components/sdlutil/imagetosurface.hpp>
|
||||
|
||||
#include <components/shader/shadermanager.hpp>
|
||||
|
||||
#include <components/resource/resourcesystem.hpp>
|
||||
#include <components/resource/scenemanager.hpp>
|
||||
#include <components/resource/stats.hpp>
|
||||
|
||||
#include <components/compiler/extensions0.hpp>
|
||||
|
||||
#include <components/misc/stereo.hpp>
|
||||
#include <components/misc/callbackmanager.hpp>
|
||||
|
||||
#include <components/sceneutil/workqueue.hpp>
|
||||
|
||||
#include <components/files/configurationmanager.hpp>
|
||||
|
@ -66,6 +71,7 @@
|
|||
#include "mwworld/worldimp.hpp"
|
||||
|
||||
#include "mwrender/vismask.hpp"
|
||||
#include "mwrender/camera.hpp"
|
||||
|
||||
#include "mwclass/classes.hpp"
|
||||
|
||||
|
@ -77,6 +83,13 @@
|
|||
|
||||
#include "mwstate/statemanagerimp.hpp"
|
||||
|
||||
#ifdef USE_OPENXR
|
||||
#include "mwvr/vrinputmanager.hpp"
|
||||
#include "mwvr/vrviewer.hpp"
|
||||
#include "mwvr/vrgui.hpp"
|
||||
#include "mwvr/vrcamera.hpp"
|
||||
#endif
|
||||
|
||||
namespace
|
||||
{
|
||||
void checkSDLError(int ret)
|
||||
|
@ -465,6 +478,9 @@ OMW::Engine::Engine(Files::ConfigurationManager& configurationManager)
|
|||
, mEncoding(ToUTF8::WINDOWS_1252)
|
||||
, mEncoder(nullptr)
|
||||
, mScreenCaptureOperation(nullptr)
|
||||
, mStereoEnabled(false)
|
||||
, mStereoOverride(false)
|
||||
, mStereoView(nullptr)
|
||||
, mSkipMenu (false)
|
||||
, mUseSound (true)
|
||||
, mCompileAll (false)
|
||||
|
@ -508,6 +524,8 @@ OMW::Engine::~Engine()
|
|||
End of tes3mp addition
|
||||
*/
|
||||
|
||||
mStereoView = nullptr;
|
||||
|
||||
mEnvironment.cleanup();
|
||||
|
||||
/*
|
||||
|
@ -617,6 +635,18 @@ std::string OMW::Engine::loadSettings (Settings::Manager & settings)
|
|||
if (boost::filesystem::exists(settingspath))
|
||||
settings.loadUser(settingspath);
|
||||
|
||||
|
||||
#ifdef USE_OPENXR
|
||||
const std::string localoverrides = (mCfgMgr.getLocalPath() / "settings-overrides-vr.cfg").string();
|
||||
const std::string globaloverrides = (mCfgMgr.getGlobalPath() / "settings-overrides-vr.cfg").string();
|
||||
if (boost::filesystem::exists(localoverrides))
|
||||
settings.loadOverrides(localoverrides);
|
||||
else if (boost::filesystem::exists(globaloverrides))
|
||||
settings.loadOverrides(globaloverrides);
|
||||
else
|
||||
throw std::runtime_error("No settings overrides file found! Make sure the file \"settings-overrides-vr.cfg\" was properly installed.");
|
||||
#endif
|
||||
|
||||
return settingspath;
|
||||
}
|
||||
|
||||
|
@ -734,6 +764,10 @@ void OMW::Engine::createWindow(Settings::Manager& settings)
|
|||
if (Debug::shouldDebugOpenGL())
|
||||
mViewer->setRealizeOperation(new Debug::EnableGLDebugOperation());
|
||||
|
||||
#ifdef USE_OPENXR
|
||||
initVr();
|
||||
#endif
|
||||
|
||||
mViewer->realize();
|
||||
|
||||
mViewer->getEventQueue()->getCurrentEventState()->setWindowRectangle(0, 0, graphicsWindow->getTraits()->width, graphicsWindow->getTraits()->height);
|
||||
|
@ -773,6 +807,8 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings)
|
|||
osg::ref_ptr<osg::Group> rootNode (new osg::Group);
|
||||
mViewer->setSceneData(rootNode);
|
||||
|
||||
mCallbackManager.reset(new Misc::CallbackManager(mViewer));
|
||||
|
||||
mVFS.reset(new VFS::Manager(mFSStrict));
|
||||
|
||||
VFS::registerArchives(mVFS.get(), mFileCollections, mArchives, true);
|
||||
|
@ -785,6 +821,7 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings)
|
|||
Settings::Manager::getString("texture mipmap", "General"),
|
||||
Settings::Manager::getInt("anisotropy", "General")
|
||||
);
|
||||
mEnvironment.setResourceSystem(mResourceSystem.get());
|
||||
|
||||
int numThreads = Settings::Manager::getInt("preload num threads", "Cells");
|
||||
if (numThreads <= 0)
|
||||
|
@ -838,25 +875,89 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings)
|
|||
Version::getOpenmwVersionDescription(mResDir.string()), mCfgMgr.getUserConfigPath().string());
|
||||
mEnvironment.setWindowManager (window);
|
||||
|
||||
MWInput::InputManager* input = new MWInput::InputManager (mWindow, mViewer, mScreenCaptureHandler, mScreenCaptureOperation, keybinderUser, keybinderUserExists, userGameControllerdb, gameControllerdb, mGrab);
|
||||
#ifdef USE_OPENXR
|
||||
const std::string xrinputuserdefault = mCfgMgr.getUserConfigPath().string() + "/xrcontrollersuggestions.xml";
|
||||
const std::string xrinputlocaldefault = mCfgMgr.getLocalPath().string() + "/xrcontrollersuggestions.xml";
|
||||
const std::string xrinputglobaldefault = mCfgMgr.getGlobalPath().string() + "/xrcontrollersuggestions.xml";
|
||||
|
||||
std::string xrControllerSuggestions;
|
||||
if (boost::filesystem::exists(xrinputuserdefault))
|
||||
xrControllerSuggestions = xrinputuserdefault;
|
||||
else if (boost::filesystem::exists(xrinputlocaldefault))
|
||||
xrControllerSuggestions = xrinputlocaldefault;
|
||||
else if (boost::filesystem::exists(xrinputglobaldefault))
|
||||
xrControllerSuggestions = xrinputglobaldefault;
|
||||
else
|
||||
xrControllerSuggestions = ""; //if it doesn't exist, pass in an empty string
|
||||
|
||||
Log(Debug::Verbose) << "xrinputuserdefault: " << xrinputuserdefault;
|
||||
Log(Debug::Verbose) << "xrinputlocaldefault: " << xrinputlocaldefault;
|
||||
Log(Debug::Verbose) << "xrinputglobaldefault: " << xrinputglobaldefault;
|
||||
|
||||
MWInput::InputManager* input =
|
||||
new MWVR::VRInputManager(mWindow, mViewer, mScreenCaptureHandler, mScreenCaptureOperation, keybinderUser, keybinderUserExists, userGameControllerdb, gameControllerdb, mGrab, xrControllerSuggestions);
|
||||
#else
|
||||
MWInput::InputManager* input =
|
||||
new MWInput::InputManager (mWindow, mViewer, mScreenCaptureHandler, mScreenCaptureOperation, keybinderUser, keybinderUserExists, userGameControllerdb, gameControllerdb, mGrab);
|
||||
#endif
|
||||
mEnvironment.setInputManager (input);
|
||||
|
||||
// Create sound system
|
||||
mEnvironment.setSoundManager (new MWSound::SoundManager(mVFS.get(), mUseSound));
|
||||
|
||||
|
||||
if (mStereoEnabled)
|
||||
{
|
||||
// Set up stereo
|
||||
// Stereo setup is split in two because the GeometryShader approach cannot be used before the RenderingManager has been created.
|
||||
// To be able to see the logo and initial loading screen the BruteForce technique must be set up here.
|
||||
mStereoView->initializeStereo(mViewer, Misc::StereoView::Technique::BruteForce);
|
||||
mResourceSystem->getSceneManager()->getShaderManager().setStereoGeometryShaderEnabled(Misc::getStereoTechnique() == Misc::StereoView::Technique::GeometryShader_IndexedViewports);
|
||||
}
|
||||
|
||||
#ifdef USE_OPENXR
|
||||
mXrEnvironment.setGUIManager(new MWVR::VRGUIManager(mViewer, mResourceSystem.get(), rootNode));
|
||||
mXrEnvironment.getViewer()->configureCallbacks();
|
||||
mStereoView->setCullMask(mStereoView->getCullMask() & ~MWRender::VisMask::Mask_GUI);
|
||||
#endif
|
||||
|
||||
|
||||
#ifdef USE_OPENXR
|
||||
MWVR::VRCamera* camera = new MWVR::VRCamera(mViewer->getCamera());
|
||||
#else
|
||||
MWRender::Camera* camera = new MWRender::Camera(mViewer->getCamera());
|
||||
#endif
|
||||
|
||||
|
||||
if (!mSkipMenu)
|
||||
{
|
||||
const std::string& logo = Fallback::Map::getString("Movies_Company_Logo");
|
||||
if (!logo.empty())
|
||||
window->playVideo(logo, true);
|
||||
mEnvironment.getWindowManager()->playVideo(logo, true);
|
||||
}
|
||||
|
||||
// Create the world
|
||||
mEnvironment.setWorld( new MWWorld::World (mViewer, rootNode, mResourceSystem.get(), mWorkQueue.get(),
|
||||
mEnvironment.setWorld( new MWWorld::World (mViewer, rootNode, std::unique_ptr<MWRender::Camera>(camera), mResourceSystem.get(), mWorkQueue.get(),
|
||||
mFileCollections, mContentFiles, mGroundcoverFiles, mEncoder, mActivationDistanceOverride, mCellName,
|
||||
mStartupScript, mResDir.string(), mCfgMgr.getUserDataPath().string()));
|
||||
mEnvironment.getWorld()->setupPlayer();
|
||||
|
||||
#ifdef USE_OPENXR
|
||||
// TODO: Workaround. Needed to stop camera from querying the world object before it is created.
|
||||
// This will be prettier when i clean up the tracking logic.
|
||||
camera->setShouldTrackPlayerCharacter(true);
|
||||
#endif
|
||||
|
||||
if (mStereoEnabled)
|
||||
{
|
||||
// Stereo shader technique can be set up now.
|
||||
mStereoView->setStereoTechnique(Misc::getStereoTechnique());
|
||||
mStereoView->initializeScene();
|
||||
|
||||
if (mEnvironment.getVrMode())
|
||||
mStereoView->setCullMask(mStereoView->getCullMask() & ~MWRender::VisMask::Mask_GUI);
|
||||
}
|
||||
|
||||
window->setStore(mEnvironment.getWorld()->getStore());
|
||||
window->initUI();
|
||||
|
||||
|
@ -881,7 +982,6 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings)
|
|||
// Create dialog system
|
||||
mEnvironment.setJournal (new MWDialogue::Journal);
|
||||
mEnvironment.setDialogueManager (new MWDialogue::DialogueManager (mExtensions, mTranslationDataStorage));
|
||||
mEnvironment.setResourceSystem(mResourceSystem.get());
|
||||
|
||||
// scripts
|
||||
if (mCompileAll)
|
||||
|
@ -985,6 +1085,22 @@ void OMW::Engine::go()
|
|||
// Create encoder
|
||||
mEncoder = new ToUTF8::Utf8Encoder(mEncoding);
|
||||
|
||||
#ifdef USE_OPENXR
|
||||
mEnvironment.setVrMode(true);
|
||||
#endif
|
||||
|
||||
// geometry shader must be enabled before the RenderingManager sets up any shaders
|
||||
// therefore this part is separate from the rest of stereo setup.
|
||||
mStereoEnabled = mEnvironment.getVrMode() || Settings::Manager::getBool("stereo enabled", "Stereo");
|
||||
if (mStereoEnabled)
|
||||
{
|
||||
// Mask in everything that does not currently use shaders.
|
||||
// Remove that altogether when the sky finally uses them.
|
||||
auto noShaderMask = MWRender::VisMask::Mask_Sky | MWRender::VisMask::Mask_Sun | MWRender::VisMask::Mask_WeatherParticles;
|
||||
// Since shaders are not yet created, we need to use the brute force technique initially
|
||||
mStereoView.reset(new Misc::StereoView(noShaderMask, MWRender::VisMask::Mask_Scene));
|
||||
}
|
||||
|
||||
// Setup viewer
|
||||
mViewer = new osgViewer::Viewer;
|
||||
mViewer->setReleaseContextAtEndOfFrameHint(false);
|
||||
|
@ -1093,12 +1209,7 @@ void OMW::Engine::go()
|
|||
}
|
||||
else
|
||||
{
|
||||
mViewer->eventTraversal();
|
||||
mViewer->updateTraversal();
|
||||
|
||||
mEnvironment.getWorld()->updateWindowManager();
|
||||
|
||||
mViewer->renderingTraversals();
|
||||
mEnvironment.getWindowManager()->viewerTraversals(true);
|
||||
|
||||
bool guiActive = mEnvironment.getWindowManager()->isGuiMode();
|
||||
|
||||
|
|
883
apps/openmw/engine.cpp.orig
Normal file
883
apps/openmw/engine.cpp.orig
Normal file
|
@ -0,0 +1,883 @@
|
|||
#include "engine.hpp"
|
||||
|
||||
#include <iomanip>
|
||||
|
||||
#include <boost/filesystem/fstream.hpp>
|
||||
|
||||
#include <osgViewer/ViewerEventHandlers>
|
||||
#include <osgDB/ReadFile>
|
||||
#include <osgDB/WriteFile>
|
||||
|
||||
#include <SDL.h>
|
||||
|
||||
#include <components/debug/debuglog.hpp>
|
||||
|
||||
#include <components/misc/rng.hpp>
|
||||
|
||||
#include <components/vfs/manager.hpp>
|
||||
#include <components/vfs/registerarchives.hpp>
|
||||
|
||||
#include <components/sdlutil/sdlgraphicswindow.hpp>
|
||||
#include <components/sdlutil/imagetosurface.hpp>
|
||||
|
||||
#include <components/resource/resourcesystem.hpp>
|
||||
#include <components/resource/scenemanager.hpp>
|
||||
#include <components/resource/stats.hpp>
|
||||
|
||||
#include <components/compiler/extensions0.hpp>
|
||||
|
||||
#include <components/sceneutil/workqueue.hpp>
|
||||
|
||||
#include <components/files/configurationmanager.hpp>
|
||||
|
||||
#include <components/version/version.hpp>
|
||||
|
||||
#include <components/detournavigator/navigator.hpp>
|
||||
|
||||
#include "mwinput/inputmanagerimp.hpp"
|
||||
|
||||
#include "mwgui/windowmanagerimp.hpp"
|
||||
|
||||
#include "mwscript/scriptmanagerimp.hpp"
|
||||
#include "mwscript/interpretercontext.hpp"
|
||||
|
||||
#include "mwsound/soundmanagerimp.hpp"
|
||||
|
||||
#include "mwworld/class.hpp"
|
||||
#include "mwworld/player.hpp"
|
||||
#include "mwworld/worldimp.hpp"
|
||||
|
||||
#include "mwrender/vismask.hpp"
|
||||
|
||||
#include "mwclass/classes.hpp"
|
||||
|
||||
#include "mwdialogue/dialoguemanagerimp.hpp"
|
||||
#include "mwdialogue/journalimp.hpp"
|
||||
#include "mwdialogue/scripttest.hpp"
|
||||
|
||||
#include "mwmechanics/mechanicsmanagerimp.hpp"
|
||||
|
||||
#include "mwstate/statemanagerimp.hpp"
|
||||
|
||||
#ifdef USE_OPENXR
|
||||
#include "mwvr/openxrinputmanager.hpp"
|
||||
#include "mwvr/openxrviewer.hpp"
|
||||
#endif
|
||||
|
||||
namespace
|
||||
{
|
||||
void checkSDLError(int ret)
|
||||
{
|
||||
if (ret != 0)
|
||||
Log(Debug::Error) << "SDL error: " << SDL_GetError();
|
||||
}
|
||||
}
|
||||
|
||||
void OMW::Engine::executeLocalScripts()
|
||||
{
|
||||
MWWorld::LocalScripts& localScripts = mEnvironment.getWorld()->getLocalScripts();
|
||||
|
||||
localScripts.startIteration();
|
||||
std::pair<std::string, MWWorld::Ptr> script;
|
||||
while (localScripts.getNext(script))
|
||||
{
|
||||
MWScript::InterpreterContext interpreterContext (
|
||||
&script.second.getRefData().getLocals(), script.second);
|
||||
mEnvironment.getScriptManager()->run (script.first, interpreterContext);
|
||||
}
|
||||
}
|
||||
|
||||
bool OMW::Engine::frame(float frametime)
|
||||
{
|
||||
try
|
||||
{
|
||||
mStartTick = mViewer->getStartTick();
|
||||
|
||||
mEnvironment.setFrameDuration(frametime);
|
||||
|
||||
// update input
|
||||
mEnvironment.getInputManager()->update(frametime, false);
|
||||
|
||||
// When the window is minimized, pause the game. Currently this *has* to be here to work around a MyGUI bug.
|
||||
// If we are not currently rendering, then RenderItems will not be reused resulting in a memory leak upon changing widget textures (fixed in MyGUI 3.3.2),
|
||||
// and destroyed widgets will not be deleted (not fixed yet, https://github.com/MyGUI/mygui/issues/21)
|
||||
if (!mEnvironment.getWindowManager()->isWindowVisible())
|
||||
{
|
||||
mEnvironment.getSoundManager()->pausePlayback();
|
||||
return false;
|
||||
}
|
||||
else
|
||||
mEnvironment.getSoundManager()->resumePlayback();
|
||||
|
||||
// sound
|
||||
if (mUseSound)
|
||||
mEnvironment.getSoundManager()->update(frametime);
|
||||
|
||||
// Main menu opened? Then scripts are also paused.
|
||||
bool paused = mEnvironment.getWindowManager()->containsMode(MWGui::GM_MainMenu);
|
||||
|
||||
// update game state
|
||||
mEnvironment.getStateManager()->update (frametime);
|
||||
|
||||
bool guiActive = mEnvironment.getWindowManager()->isGuiMode();
|
||||
|
||||
osg::Timer_t beforeScriptTick = osg::Timer::instance()->tick();
|
||||
if (mEnvironment.getStateManager()->getState()!=
|
||||
MWBase::StateManager::State_NoGame)
|
||||
{
|
||||
if (!paused)
|
||||
{
|
||||
if (mEnvironment.getWorld()->getScriptsEnabled())
|
||||
{
|
||||
// local scripts
|
||||
executeLocalScripts();
|
||||
|
||||
// global scripts
|
||||
mEnvironment.getScriptManager()->getGlobalScripts().run();
|
||||
}
|
||||
|
||||
mEnvironment.getWorld()->markCellAsUnchanged();
|
||||
}
|
||||
|
||||
if (!guiActive)
|
||||
{
|
||||
double hours = (frametime * mEnvironment.getWorld()->getTimeScaleFactor()) / 3600.0;
|
||||
mEnvironment.getWorld()->advanceTime(hours, true);
|
||||
mEnvironment.getWorld()->rechargeItems(frametime, true);
|
||||
}
|
||||
}
|
||||
osg::Timer_t afterScriptTick = osg::Timer::instance()->tick();
|
||||
|
||||
// update actors
|
||||
osg::Timer_t beforeMechanicsTick = osg::Timer::instance()->tick();
|
||||
if (mEnvironment.getStateManager()->getState()!=
|
||||
MWBase::StateManager::State_NoGame)
|
||||
{
|
||||
mEnvironment.getMechanicsManager()->update(frametime,
|
||||
guiActive);
|
||||
}
|
||||
osg::Timer_t afterMechanicsTick = osg::Timer::instance()->tick();
|
||||
|
||||
if (mEnvironment.getStateManager()->getState()==
|
||||
MWBase::StateManager::State_Running)
|
||||
{
|
||||
MWWorld::Ptr player = mEnvironment.getWorld()->getPlayerPtr();
|
||||
if(!guiActive && player.getClass().getCreatureStats(player).isDead())
|
||||
mEnvironment.getStateManager()->endGame();
|
||||
}
|
||||
|
||||
// update physics
|
||||
osg::Timer_t beforePhysicsTick = osg::Timer::instance()->tick();
|
||||
if (mEnvironment.getStateManager()->getState()!=
|
||||
MWBase::StateManager::State_NoGame)
|
||||
{
|
||||
mEnvironment.getWorld()->updatePhysics(frametime, guiActive);
|
||||
}
|
||||
osg::Timer_t afterPhysicsTick = osg::Timer::instance()->tick();
|
||||
|
||||
// update world
|
||||
osg::Timer_t beforeWorldTick = osg::Timer::instance()->tick();
|
||||
if (mEnvironment.getStateManager()->getState()!=
|
||||
MWBase::StateManager::State_NoGame)
|
||||
{
|
||||
mEnvironment.getWorld()->update(frametime, guiActive);
|
||||
}
|
||||
osg::Timer_t afterWorldTick = osg::Timer::instance()->tick();
|
||||
|
||||
// update GUI
|
||||
mEnvironment.getWindowManager()->onFrame(frametime);
|
||||
|
||||
unsigned int frameNumber = mViewer->getFrameStamp()->getFrameNumber();
|
||||
osg::Stats* stats = mViewer->getViewerStats();
|
||||
stats->setAttribute(frameNumber, "script_time_begin", osg::Timer::instance()->delta_s(mStartTick, beforeScriptTick));
|
||||
stats->setAttribute(frameNumber, "script_time_taken", osg::Timer::instance()->delta_s(beforeScriptTick, afterScriptTick));
|
||||
stats->setAttribute(frameNumber, "script_time_end", osg::Timer::instance()->delta_s(mStartTick, afterScriptTick));
|
||||
|
||||
stats->setAttribute(frameNumber, "mechanics_time_begin", osg::Timer::instance()->delta_s(mStartTick, beforeMechanicsTick));
|
||||
stats->setAttribute(frameNumber, "mechanics_time_taken", osg::Timer::instance()->delta_s(beforeMechanicsTick, afterMechanicsTick));
|
||||
stats->setAttribute(frameNumber, "mechanics_time_end", osg::Timer::instance()->delta_s(mStartTick, afterMechanicsTick));
|
||||
|
||||
stats->setAttribute(frameNumber, "physics_time_begin", osg::Timer::instance()->delta_s(mStartTick, beforePhysicsTick));
|
||||
stats->setAttribute(frameNumber, "physics_time_taken", osg::Timer::instance()->delta_s(beforePhysicsTick, afterPhysicsTick));
|
||||
stats->setAttribute(frameNumber, "physics_time_end", osg::Timer::instance()->delta_s(mStartTick, afterPhysicsTick));
|
||||
|
||||
stats->setAttribute(frameNumber, "world_time_begin", osg::Timer::instance()->delta_s(mStartTick, beforeWorldTick));
|
||||
stats->setAttribute(frameNumber, "world_time_taken", osg::Timer::instance()->delta_s(beforeWorldTick, afterWorldTick));
|
||||
stats->setAttribute(frameNumber, "world_time_end", osg::Timer::instance()->delta_s(mStartTick, afterWorldTick));
|
||||
|
||||
if (stats->collectStats("resource"))
|
||||
{
|
||||
mResourceSystem->reportStats(frameNumber, stats);
|
||||
|
||||
stats->setAttribute(frameNumber, "WorkQueue", mWorkQueue->getNumItems());
|
||||
stats->setAttribute(frameNumber, "WorkThread", mWorkQueue->getNumActiveThreads());
|
||||
|
||||
mEnvironment.getWorld()->getNavigator()->reportStats(frameNumber, *stats);
|
||||
}
|
||||
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
Log(Debug::Error) << "Error in frame: " << e.what();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
OMW::Engine::Engine(Files::ConfigurationManager& configurationManager)
|
||||
: mWindow(nullptr)
|
||||
, mEncoding(ToUTF8::WINDOWS_1252)
|
||||
, mEncoder(nullptr)
|
||||
, mScreenCaptureOperation(nullptr)
|
||||
, mSkipMenu (false)
|
||||
, mUseSound (true)
|
||||
, mCompileAll (false)
|
||||
, mCompileAllDialogue (false)
|
||||
, mWarningsMode (1)
|
||||
, mScriptConsoleMode (false)
|
||||
, mActivationDistanceOverride(-1)
|
||||
, mGrab(true)
|
||||
, mExportFonts(false)
|
||||
, mRandomSeed(0)
|
||||
, mScriptContext (0)
|
||||
, mFSStrict (false)
|
||||
, mScriptBlacklistUse (true)
|
||||
, mNewGame (false)
|
||||
, mCfgMgr(configurationManager)
|
||||
{
|
||||
MWClass::registerClasses();
|
||||
|
||||
SDL_SetHint(SDL_HINT_ACCELEROMETER_AS_JOYSTICK, "0"); // We use only gamepads
|
||||
|
||||
Uint32 flags = SDL_INIT_VIDEO|SDL_INIT_NOPARACHUTE|SDL_INIT_GAMECONTROLLER|SDL_INIT_JOYSTICK|SDL_INIT_SENSOR;
|
||||
if(SDL_WasInit(flags) == 0)
|
||||
{
|
||||
SDL_SetMainReady();
|
||||
if(SDL_Init(flags) != 0)
|
||||
{
|
||||
throw std::runtime_error("Could not initialize SDL! " + std::string(SDL_GetError()));
|
||||
}
|
||||
}
|
||||
|
||||
mStartTick = osg::Timer::instance()->tick();
|
||||
}
|
||||
|
||||
OMW::Engine::~Engine()
|
||||
{
|
||||
mEnvironment.cleanup();
|
||||
|
||||
delete mScriptContext;
|
||||
mScriptContext = nullptr;
|
||||
|
||||
mWorkQueue = nullptr;
|
||||
|
||||
mViewer = nullptr;
|
||||
|
||||
mResourceSystem.reset();
|
||||
|
||||
delete mEncoder;
|
||||
mEncoder = nullptr;
|
||||
|
||||
if (mWindow)
|
||||
{
|
||||
SDL_DestroyWindow(mWindow);
|
||||
mWindow = nullptr;
|
||||
}
|
||||
|
||||
SDL_Quit();
|
||||
}
|
||||
|
||||
void OMW::Engine::enableFSStrict(bool fsStrict)
|
||||
{
|
||||
mFSStrict = fsStrict;
|
||||
}
|
||||
|
||||
// Set data dir
|
||||
|
||||
void OMW::Engine::setDataDirs (const Files::PathContainer& dataDirs)
|
||||
{
|
||||
mDataDirs = dataDirs;
|
||||
mDataDirs.insert(mDataDirs.begin(), (mResDir / "vfs"));
|
||||
mFileCollections = Files::Collections (mDataDirs, !mFSStrict);
|
||||
}
|
||||
|
||||
// Add BSA archive
|
||||
void OMW::Engine::addArchive (const std::string& archive) {
|
||||
mArchives.push_back(archive);
|
||||
}
|
||||
|
||||
// Set resource dir
|
||||
void OMW::Engine::setResourceDir (const boost::filesystem::path& parResDir)
|
||||
{
|
||||
mResDir = parResDir;
|
||||
}
|
||||
|
||||
// Set start cell name
|
||||
void OMW::Engine::setCell (const std::string& cellName)
|
||||
{
|
||||
mCellName = cellName;
|
||||
}
|
||||
|
||||
void OMW::Engine::addContentFile(const std::string& file)
|
||||
{
|
||||
mContentFiles.push_back(file);
|
||||
}
|
||||
|
||||
void OMW::Engine::setSkipMenu (bool skipMenu, bool newGame)
|
||||
{
|
||||
mSkipMenu = skipMenu;
|
||||
mNewGame = newGame;
|
||||
}
|
||||
|
||||
std::string OMW::Engine::loadSettings (Settings::Manager & settings)
|
||||
{
|
||||
// Create the settings manager and load default settings file
|
||||
const std::string localdefault = (mCfgMgr.getLocalPath() / "settings-default.cfg").string();
|
||||
const std::string globaldefault = (mCfgMgr.getGlobalPath() / "settings-default.cfg").string();
|
||||
|
||||
// prefer local
|
||||
if (boost::filesystem::exists(localdefault))
|
||||
settings.loadDefault(localdefault);
|
||||
else if (boost::filesystem::exists(globaldefault))
|
||||
settings.loadDefault(globaldefault);
|
||||
else
|
||||
throw std::runtime_error ("No default settings file found! Make sure the file \"settings-default.cfg\" was properly installed.");
|
||||
|
||||
// load user settings if they exist
|
||||
const std::string settingspath = (mCfgMgr.getUserConfigPath() / "settings.cfg").string();
|
||||
if (boost::filesystem::exists(settingspath))
|
||||
settings.loadUser(settingspath);
|
||||
|
||||
return settingspath;
|
||||
}
|
||||
|
||||
void OMW::Engine::createWindow(Settings::Manager& settings)
|
||||
{
|
||||
int screen = settings.getInt("screen", "Video");
|
||||
int width = settings.getInt("resolution x", "Video");
|
||||
int height = settings.getInt("resolution y", "Video");
|
||||
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");
|
||||
|
||||
int pos_x = SDL_WINDOWPOS_CENTERED_DISPLAY(screen),
|
||||
pos_y = SDL_WINDOWPOS_CENTERED_DISPLAY(screen);
|
||||
|
||||
if(fullscreen)
|
||||
{
|
||||
pos_x = SDL_WINDOWPOS_UNDEFINED_DISPLAY(screen);
|
||||
pos_y = SDL_WINDOWPOS_UNDEFINED_DISPLAY(screen);
|
||||
}
|
||||
|
||||
Uint32 flags = SDL_WINDOW_OPENGL|SDL_WINDOW_SHOWN|SDL_WINDOW_RESIZABLE;
|
||||
if(fullscreen)
|
||||
flags |= SDL_WINDOW_FULLSCREEN;
|
||||
|
||||
if (!windowBorder)
|
||||
flags |= SDL_WINDOW_BORDERLESS;
|
||||
|
||||
SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS,
|
||||
settings.getBool("minimize on focus loss", "Video") ? "1" : "0");
|
||||
|
||||
checkSDLError(SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8));
|
||||
checkSDLError(SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8));
|
||||
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 (antialiasing > 0)
|
||||
{
|
||||
checkSDLError(SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1));
|
||||
checkSDLError(SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, antialiasing));
|
||||
}
|
||||
|
||||
while (!mWindow)
|
||||
{
|
||||
mWindow = SDL_CreateWindow("OpenMW", pos_x, pos_y, width, height, flags);
|
||||
if (!mWindow)
|
||||
{
|
||||
// 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);
|
||||
// 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);
|
||||
|
||||
#ifdef USE_OPENXR
|
||||
initVr();
|
||||
#endif
|
||||
|
||||
mViewer->realize();
|
||||
|
||||
mViewer->getEventQueue()->getCurrentEventState()->setWindowRectangle(0, 0, traits->width, traits->height);
|
||||
}
|
||||
|
||||
void OMW::Engine::setWindowIcon()
|
||||
{
|
||||
boost::filesystem::ifstream windowIconStream;
|
||||
std::string windowIcon = (mResDir / "mygui" / "openmw.png").string();
|
||||
windowIconStream.open(windowIcon, std::ios_base::in | std::ios_base::binary);
|
||||
if (windowIconStream.fail())
|
||||
Log(Debug::Error) << "Error: Failed to open " << windowIcon;
|
||||
osgDB::ReaderWriter* reader = osgDB::Registry::instance()->getReaderWriterForExtension("png");
|
||||
if (!reader)
|
||||
{
|
||||
Log(Debug::Error) << "Error: Failed to read window icon, no png readerwriter found";
|
||||
return;
|
||||
}
|
||||
osgDB::ReaderWriter::ReadResult result = reader->readImage(windowIconStream);
|
||||
if (!result.success())
|
||||
Log(Debug::Error) << "Error: Failed to read " << windowIcon << ": " << result.message() << " code " << result.status();
|
||||
else
|
||||
{
|
||||
osg::ref_ptr<osg::Image> image = result.getImage();
|
||||
auto surface = SDLUtil::imageToSurface(image, true);
|
||||
SDL_SetWindowIcon(mWindow, surface.get());
|
||||
}
|
||||
}
|
||||
|
||||
void OMW::Engine::prepareEngine (Settings::Manager & settings)
|
||||
{
|
||||
mEnvironment.setStateManager (
|
||||
new MWState::StateManager (mCfgMgr.getUserDataPath() / "saves", mContentFiles.at (0)));
|
||||
|
||||
createWindow(settings);
|
||||
|
||||
osg::ref_ptr<osg::Group> rootNode (new osg::Group);
|
||||
|
||||
mViewer->setSceneData(rootNode);
|
||||
|
||||
mVFS.reset(new VFS::Manager(mFSStrict));
|
||||
|
||||
VFS::registerArchives(mVFS.get(), mFileCollections, mArchives, true);
|
||||
|
||||
mResourceSystem.reset(new Resource::ResourceSystem(mVFS.get()));
|
||||
mResourceSystem->getSceneManager()->setUnRefImageDataAfterApply(false); // keep to Off for now to allow better state sharing
|
||||
mResourceSystem->getSceneManager()->setFilterSettings(
|
||||
Settings::Manager::getString("texture mag filter", "General"),
|
||||
Settings::Manager::getString("texture min filter", "General"),
|
||||
Settings::Manager::getString("texture mipmap", "General"),
|
||||
Settings::Manager::getInt("anisotropy", "General")
|
||||
);
|
||||
|
||||
int numThreads = Settings::Manager::getInt("preload num threads", "Cells");
|
||||
if (numThreads <= 0)
|
||||
throw std::runtime_error("Invalid setting: 'preload num threads' must be >0");
|
||||
mWorkQueue = new SceneUtil::WorkQueue(numThreads);
|
||||
|
||||
// Create input and UI first to set up a bootstrapping environment for
|
||||
// showing a loading screen and keeping the window responsive while doing so
|
||||
|
||||
std::string keybinderUser = (mCfgMgr.getUserConfigPath() / "input_v3.xml").string();
|
||||
bool keybinderUserExists = boost::filesystem::exists(keybinderUser);
|
||||
if(!keybinderUserExists)
|
||||
{
|
||||
std::string input2 = (mCfgMgr.getUserConfigPath() / "input_v2.xml").string();
|
||||
if(boost::filesystem::exists(input2)) {
|
||||
boost::filesystem::copy_file(input2, keybinderUser);
|
||||
keybinderUserExists = boost::filesystem::exists(keybinderUser);
|
||||
Log(Debug::Info) << "Loading keybindings file: " << keybinderUser;
|
||||
}
|
||||
}
|
||||
else
|
||||
Log(Debug::Info) << "Loading keybindings file: " << keybinderUser;
|
||||
|
||||
const std::string userdefault = mCfgMgr.getUserConfigPath().string() + "/gamecontrollerdb.txt";
|
||||
const std::string localdefault = mCfgMgr.getLocalPath().string() + "/gamecontrollerdb.txt";
|
||||
const std::string globaldefault = mCfgMgr.getGlobalPath().string() + "/gamecontrollerdb.txt";
|
||||
|
||||
std::string userGameControllerdb;
|
||||
if (boost::filesystem::exists(userdefault)){
|
||||
userGameControllerdb = userdefault;
|
||||
}
|
||||
else
|
||||
userGameControllerdb = "";
|
||||
|
||||
std::string gameControllerdb;
|
||||
if (boost::filesystem::exists(localdefault))
|
||||
gameControllerdb = localdefault;
|
||||
else if (boost::filesystem::exists(globaldefault))
|
||||
gameControllerdb = globaldefault;
|
||||
else
|
||||
gameControllerdb = ""; //if it doesn't exist, pass in an empty string
|
||||
<<<<<<< HEAD
|
||||
MWInput::InputManager* input =
|
||||
#ifdef USE_OPENXR
|
||||
new MWVR::OpenXRInputManager(mWindow, mViewer, mScreenCaptureHandler, mScreenCaptureOperation, keybinderUser, keybinderUserExists, userGameControllerdb, gameControllerdb, mGrab);
|
||||
#else
|
||||
new MWInput::InputManager (mWindow, mViewer, mScreenCaptureHandler, mScreenCaptureOperation, keybinderUser, keybinderUserExists, userGameControllerdb, gameControllerdb, mGrab);
|
||||
#endif
|
||||
mEnvironment.setInputManager (input);
|
||||
=======
|
||||
>>>>>>> 6b44b7f245e12566c26b6bdd92448aeb6dd90a85
|
||||
|
||||
std::string myguiResources = (mResDir / "mygui").string();
|
||||
osg::ref_ptr<osg::Group> guiRoot = new osg::Group;
|
||||
guiRoot->setName("GUI Root");
|
||||
guiRoot->setNodeMask(MWRender::Mask_GUI);
|
||||
rootNode->addChild(guiRoot);
|
||||
MWGui::WindowManager* window = new MWGui::WindowManager(mWindow, mViewer, guiRoot, mResourceSystem.get(), mWorkQueue.get(),
|
||||
mCfgMgr.getLogPath().string() + std::string("/"), myguiResources,
|
||||
mScriptConsoleMode, mTranslationDataStorage, mEncoding, mExportFonts,
|
||||
Version::getOpenmwVersionDescription(mResDir.string()), mCfgMgr.getUserConfigPath().string());
|
||||
mEnvironment.setWindowManager (window);
|
||||
|
||||
MWInput::InputManager* input = new MWInput::InputManager (mWindow, mViewer, mScreenCaptureHandler, mScreenCaptureOperation, keybinderUser, keybinderUserExists, userGameControllerdb, gameControllerdb, mGrab);
|
||||
mEnvironment.setInputManager (input);
|
||||
|
||||
// Create sound system
|
||||
mEnvironment.setSoundManager (new MWSound::SoundManager(mVFS.get(), mUseSound));
|
||||
|
||||
#ifdef USE_OPENXR
|
||||
mXrEnvironment.setGUIManager(new MWVR::VRGUIManager(mViewer));
|
||||
#endif
|
||||
|
||||
if (!mSkipMenu)
|
||||
{
|
||||
const std::string& logo = Fallback::Map::getString("Movies_Company_Logo");
|
||||
if (!logo.empty())
|
||||
window->playVideo(logo, true);
|
||||
}
|
||||
|
||||
// Create the world
|
||||
mEnvironment.setWorld( new MWWorld::World (mViewer, rootNode, mResourceSystem.get(), mWorkQueue.get(),
|
||||
mFileCollections, mContentFiles, mEncoder, mActivationDistanceOverride, mCellName,
|
||||
mStartupScript, mResDir.string(), mCfgMgr.getUserDataPath().string()));
|
||||
mEnvironment.getWorld()->setupPlayer();
|
||||
|
||||
window->setStore(mEnvironment.getWorld()->getStore());
|
||||
window->initUI();
|
||||
|
||||
//Load translation data
|
||||
mTranslationDataStorage.setEncoder(mEncoder);
|
||||
for (size_t i = 0; i < mContentFiles.size(); i++)
|
||||
mTranslationDataStorage.loadTranslationData(mFileCollections, mContentFiles[i]);
|
||||
|
||||
Compiler::registerExtensions (mExtensions);
|
||||
|
||||
// Create script system
|
||||
mScriptContext = new MWScript::CompilerContext (MWScript::CompilerContext::Type_Full);
|
||||
mScriptContext->setExtensions (&mExtensions);
|
||||
|
||||
mEnvironment.setScriptManager (new MWScript::ScriptManager (mEnvironment.getWorld()->getStore(), *mScriptContext, mWarningsMode,
|
||||
mScriptBlacklistUse ? mScriptBlacklist : std::vector<std::string>()));
|
||||
|
||||
// Create game mechanics system
|
||||
MWMechanics::MechanicsManager* mechanics = new MWMechanics::MechanicsManager;
|
||||
mEnvironment.setMechanicsManager (mechanics);
|
||||
|
||||
// Create dialog system
|
||||
mEnvironment.setJournal (new MWDialogue::Journal);
|
||||
mEnvironment.setDialogueManager (new MWDialogue::DialogueManager (mExtensions, mTranslationDataStorage));
|
||||
|
||||
// scripts
|
||||
if (mCompileAll)
|
||||
{
|
||||
std::pair<int, int> result = mEnvironment.getScriptManager()->compileAll();
|
||||
if (result.first)
|
||||
Log(Debug::Info)
|
||||
<< "compiled " << result.second << " of " << result.first << " scripts ("
|
||||
<< 100*static_cast<double> (result.second)/result.first
|
||||
<< "%)";
|
||||
}
|
||||
if (mCompileAllDialogue)
|
||||
{
|
||||
std::pair<int, int> result = MWDialogue::ScriptTest::compileAll(&mExtensions, mWarningsMode);
|
||||
if (result.first)
|
||||
Log(Debug::Info)
|
||||
<< "compiled " << result.second << " of " << result.first << " dialogue script/actor combinations a("
|
||||
<< 100*static_cast<double> (result.second)/result.first
|
||||
<< "%)";
|
||||
}
|
||||
}
|
||||
|
||||
class WriteScreenshotToFileOperation : public osgViewer::ScreenCaptureHandler::CaptureOperation
|
||||
{
|
||||
public:
|
||||
WriteScreenshotToFileOperation(const std::string& screenshotPath, const std::string& screenshotFormat)
|
||||
: mScreenshotPath(screenshotPath)
|
||||
, mScreenshotFormat(screenshotFormat)
|
||||
{
|
||||
}
|
||||
|
||||
virtual void operator()(const osg::Image& image, const unsigned int context_id)
|
||||
{
|
||||
// Count screenshots.
|
||||
int shotCount = 0;
|
||||
|
||||
// Find the first unused filename with a do-while
|
||||
std::ostringstream stream;
|
||||
do
|
||||
{
|
||||
// Reset the stream
|
||||
stream.str("");
|
||||
stream.clear();
|
||||
|
||||
stream << mScreenshotPath << "/screenshot" << std::setw(3) << std::setfill('0') << shotCount++ << "." << mScreenshotFormat;
|
||||
|
||||
} while (boost::filesystem::exists(stream.str()));
|
||||
|
||||
boost::filesystem::ofstream outStream;
|
||||
outStream.open(boost::filesystem::path(stream.str()), std::ios::binary);
|
||||
|
||||
osgDB::ReaderWriter* readerwriter = osgDB::Registry::instance()->getReaderWriterForExtension(mScreenshotFormat);
|
||||
if (!readerwriter)
|
||||
{
|
||||
Log(Debug::Error) << "Error: Can't write screenshot, no '" << mScreenshotFormat << "' readerwriter found";
|
||||
return;
|
||||
}
|
||||
|
||||
osgDB::ReaderWriter::WriteResult result = readerwriter->writeImage(image, outStream);
|
||||
if (!result.success())
|
||||
{
|
||||
Log(Debug::Error) << "Error: Can't write screenshot: " << result.message() << " code " << result.status();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::string mScreenshotPath;
|
||||
std::string mScreenshotFormat;
|
||||
};
|
||||
|
||||
// Initialise and enter main loop.
|
||||
void OMW::Engine::go()
|
||||
{
|
||||
assert (!mContentFiles.empty());
|
||||
|
||||
Log(Debug::Info) << "OSG version: " << osgGetVersion();
|
||||
SDL_version sdlVersion;
|
||||
SDL_GetVersion(&sdlVersion);
|
||||
Log(Debug::Info) << "SDL version: " << (int)sdlVersion.major << "." << (int)sdlVersion.minor << "." << (int)sdlVersion.patch;
|
||||
|
||||
Misc::Rng::init(mRandomSeed);
|
||||
|
||||
// Load settings
|
||||
Settings::Manager settings;
|
||||
std::string settingspath;
|
||||
settingspath = loadSettings (settings);
|
||||
|
||||
// Create encoder
|
||||
mEncoder = new ToUTF8::Utf8Encoder(mEncoding);
|
||||
|
||||
// Setup viewer
|
||||
mViewer = new osgViewer::Viewer;
|
||||
mViewer->setReleaseContextAtEndOfFrameHint(false);
|
||||
|
||||
#if OSG_VERSION_GREATER_OR_EQUAL(3,5,5)
|
||||
// Do not try to outsmart the OS thread scheduler (see bug #4785).
|
||||
mViewer->setUseConfigureAffinity(false);
|
||||
#endif
|
||||
|
||||
mScreenCaptureOperation = new WriteScreenshotToFileOperation(
|
||||
mCfgMgr.getScreenshotPath().string(),
|
||||
Settings::Manager::getString("screenshot format", "General"));
|
||||
|
||||
mScreenCaptureHandler = new osgViewer::ScreenCaptureHandler(mScreenCaptureOperation);
|
||||
|
||||
mViewer->addEventHandler(mScreenCaptureHandler);
|
||||
|
||||
mEnvironment.setFrameRateLimit(Settings::Manager::getFloat("framerate limit", "Video"));
|
||||
|
||||
prepareEngine (settings);
|
||||
|
||||
// Setup profiler
|
||||
osg::ref_ptr<Resource::Profiler> statshandler = new Resource::Profiler;
|
||||
|
||||
statshandler->addUserStatsLine("Script", osg::Vec4f(1.f, 1.f, 1.f, 1.f), osg::Vec4f(1.f, 1.f, 1.f, 1.f),
|
||||
"script_time_taken", 1000.0, true, false, "script_time_begin", "script_time_end", 10000);
|
||||
statshandler->addUserStatsLine("Mech", osg::Vec4f(1.f, 1.f, 1.f, 1.f), osg::Vec4f(1.f, 1.f, 1.f, 1.f),
|
||||
"mechanics_time_taken", 1000.0, true, false, "mechanics_time_begin", "mechanics_time_end", 10000);
|
||||
statshandler->addUserStatsLine("Phys", osg::Vec4f(1.f, 1.f, 1.f, 1.f), osg::Vec4f(1.f, 1.f, 1.f, 1.f),
|
||||
"physics_time_taken", 1000.0, true, false, "physics_time_begin", "physics_time_end", 10000);
|
||||
statshandler->addUserStatsLine("World", osg::Vec4f(1.f, 1.f, 1.f, 1.f), osg::Vec4f(1.f, 1.f, 1.f, 1.f),
|
||||
"world_time_taken", 1000.0, true, false, "world_time_begin", "world_time_end", 10000);
|
||||
|
||||
mViewer->addEventHandler(statshandler);
|
||||
|
||||
osg::ref_ptr<Resource::StatsHandler> resourceshandler = new Resource::StatsHandler;
|
||||
mViewer->addEventHandler(resourceshandler);
|
||||
|
||||
#ifdef USE_OPENXR
|
||||
auto* root = mViewer->getSceneData();
|
||||
auto* xrViewer = MWVR::Environment::get().getViewer();
|
||||
xrViewer->addChild(root);
|
||||
mViewer->setSceneData(xrViewer);
|
||||
mXrEnvironment.setGUIManager(new MWVR::VRGUIManager(mViewer));
|
||||
#endif
|
||||
|
||||
// Start the game
|
||||
if (!mSaveGameFile.empty())
|
||||
{
|
||||
mEnvironment.getStateManager()->loadGame(mSaveGameFile);
|
||||
}
|
||||
else if (!mSkipMenu)
|
||||
{
|
||||
// start in main menu
|
||||
mEnvironment.getWindowManager()->pushGuiMode (MWGui::GM_MainMenu);
|
||||
mEnvironment.getSoundManager()->playTitleMusic();
|
||||
const std::string& logo = Fallback::Map::getString("Movies_Morrowind_Logo");
|
||||
if (!logo.empty())
|
||||
mEnvironment.getWindowManager()->playVideo(logo, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
mEnvironment.getStateManager()->newGame (!mNewGame);
|
||||
}
|
||||
|
||||
if (!mStartupScript.empty() && mEnvironment.getStateManager()->getState() == MWState::StateManager::State_Running)
|
||||
{
|
||||
mEnvironment.getWindowManager()->executeInConsole(mStartupScript);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Start the main rendering loop
|
||||
osg::Timer frameTimer;
|
||||
double simulationTime = 0.0;
|
||||
while (!mViewer->done() && !mEnvironment.getStateManager()->hasQuitRequest())
|
||||
{
|
||||
double dt = frameTimer.time_s();
|
||||
frameTimer.setStartTick();
|
||||
dt = std::min(dt, 0.2);
|
||||
|
||||
mViewer->advance(simulationTime);
|
||||
|
||||
if (!frame(dt))
|
||||
{
|
||||
OpenThreads::Thread::microSleep(5000);
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
mViewer->eventTraversal();
|
||||
|
||||
mEnvironment.getWorld()->updateWindowManager();
|
||||
|
||||
#ifdef USE_OPENXR
|
||||
xrViewer->traversals();
|
||||
#else
|
||||
mViewer->updateTraversal();
|
||||
|
||||
mViewer->renderingTraversals();
|
||||
#endif
|
||||
|
||||
bool guiActive = mEnvironment.getWindowManager()->isGuiMode();
|
||||
if (!guiActive)
|
||||
simulationTime += dt;
|
||||
}
|
||||
|
||||
mEnvironment.limitFrameRate(frameTimer.time_s());
|
||||
}
|
||||
|
||||
// Save user settings
|
||||
settings.saveUser(settingspath);
|
||||
|
||||
Log(Debug::Info) << "Quitting peacefully.";
|
||||
}
|
||||
|
||||
void OMW::Engine::setCompileAll (bool all)
|
||||
{
|
||||
mCompileAll = all;
|
||||
}
|
||||
|
||||
void OMW::Engine::setCompileAllDialogue (bool all)
|
||||
{
|
||||
mCompileAllDialogue = all;
|
||||
}
|
||||
|
||||
void OMW::Engine::setSoundUsage(bool soundUsage)
|
||||
{
|
||||
mUseSound = soundUsage;
|
||||
}
|
||||
|
||||
void OMW::Engine::setEncoding(const ToUTF8::FromType& encoding)
|
||||
{
|
||||
mEncoding = encoding;
|
||||
}
|
||||
|
||||
void OMW::Engine::setScriptConsoleMode (bool enabled)
|
||||
{
|
||||
mScriptConsoleMode = enabled;
|
||||
}
|
||||
|
||||
void OMW::Engine::setStartupScript (const std::string& path)
|
||||
{
|
||||
mStartupScript = path;
|
||||
}
|
||||
|
||||
void OMW::Engine::setActivationDistanceOverride (int distance)
|
||||
{
|
||||
mActivationDistanceOverride = distance;
|
||||
}
|
||||
|
||||
void OMW::Engine::setWarningsMode (int mode)
|
||||
{
|
||||
mWarningsMode = mode;
|
||||
}
|
||||
|
||||
void OMW::Engine::setScriptBlacklist (const std::vector<std::string>& list)
|
||||
{
|
||||
mScriptBlacklist = list;
|
||||
}
|
||||
|
||||
void OMW::Engine::setScriptBlacklistUse (bool use)
|
||||
{
|
||||
mScriptBlacklistUse = use;
|
||||
}
|
||||
|
||||
void OMW::Engine::enableFontExport(bool exportFonts)
|
||||
{
|
||||
mExportFonts = exportFonts;
|
||||
}
|
||||
|
||||
void OMW::Engine::setSaveGameFile(const std::string &savegame)
|
||||
{
|
||||
mSaveGameFile = savegame;
|
||||
}
|
||||
|
||||
void OMW::Engine::setRandomSeed(unsigned int seed)
|
||||
{
|
||||
mRandomSeed = seed;
|
||||
}
|
|
@ -13,6 +13,10 @@
|
|||
|
||||
#include "mwworld/ptr.hpp"
|
||||
|
||||
#ifdef USE_OPENXR
|
||||
#include "mwvr/vrenvironment.hpp"
|
||||
#endif
|
||||
|
||||
namespace Resource
|
||||
{
|
||||
class ResourceSystem;
|
||||
|
@ -33,6 +37,12 @@ namespace Compiler
|
|||
class Context;
|
||||
}
|
||||
|
||||
namespace Misc
|
||||
{
|
||||
class StereoView;
|
||||
class CallbackManager;
|
||||
}
|
||||
|
||||
namespace Files
|
||||
{
|
||||
struct ConfigurationManager;
|
||||
|
@ -66,6 +76,13 @@ namespace OMW
|
|||
std::string mCellName;
|
||||
std::vector<std::string> mContentFiles;
|
||||
std::vector<std::string> mGroundcoverFiles;
|
||||
|
||||
bool mStereoEnabled;
|
||||
bool mStereoOverride;
|
||||
std::unique_ptr<Misc::StereoView> mStereoView;
|
||||
|
||||
std::unique_ptr<Misc::CallbackManager> mCallbackManager;
|
||||
|
||||
bool mSkipMenu;
|
||||
bool mUseSound;
|
||||
bool mCompileAll;
|
||||
|
@ -185,6 +202,12 @@ namespace OMW
|
|||
|
||||
private:
|
||||
Files::ConfigurationManager& mCfgMgr;
|
||||
|
||||
#ifdef USE_OPENXR
|
||||
MWVR::Environment mXrEnvironment;
|
||||
|
||||
void initVr();
|
||||
#endif
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ MWBase::Environment *MWBase::Environment::sThis = nullptr;
|
|||
MWBase::Environment::Environment()
|
||||
: mWorld (nullptr), mSoundManager (nullptr), mScriptManager (nullptr), mWindowManager (nullptr),
|
||||
mMechanicsManager (nullptr), mDialogueManager (nullptr), mJournal (nullptr), mInputManager (nullptr),
|
||||
mStateManager (nullptr), mResourceSystem (nullptr), mFrameDuration (0), mFrameRateLimit(0.f)
|
||||
mStateManager (nullptr), mResourceSystem (nullptr), mFrameDuration (0), mFrameRateLimit(0.f), mVrMode(false)
|
||||
{
|
||||
assert (!sThis);
|
||||
sThis = this;
|
||||
|
@ -96,6 +96,16 @@ float MWBase::Environment::getFrameRateLimit() const
|
|||
return mFrameRateLimit;
|
||||
}
|
||||
|
||||
void MWBase::Environment::setVrMode(bool vrMode)
|
||||
{
|
||||
mVrMode = vrMode;
|
||||
}
|
||||
|
||||
bool MWBase::Environment::getVrMode(void) const
|
||||
{
|
||||
return mVrMode;
|
||||
}
|
||||
|
||||
MWBase::World *MWBase::Environment::getWorld() const
|
||||
{
|
||||
assert (mWorld);
|
||||
|
|
|
@ -45,6 +45,7 @@ namespace MWBase
|
|||
Resource::ResourceSystem *mResourceSystem;
|
||||
float mFrameDuration;
|
||||
float mFrameRateLimit;
|
||||
bool mVrMode;
|
||||
|
||||
Environment (const Environment&);
|
||||
///< not implemented
|
||||
|
@ -84,6 +85,9 @@ namespace MWBase
|
|||
void setFrameRateLimit(float frameRateLimit);
|
||||
float getFrameRateLimit() const;
|
||||
|
||||
void setVrMode(bool vrMode);
|
||||
bool getVrMode(void) const;
|
||||
|
||||
World *getWorld() const;
|
||||
|
||||
SoundManager *getSoundManager() const;
|
||||
|
|
|
@ -63,6 +63,8 @@ namespace MWBase
|
|||
virtual void enableDetectingBindingMode (int action, bool keyboard) = 0;
|
||||
virtual void resetToDefaultKeyBindings() = 0;
|
||||
virtual void resetToDefaultControllerBindings() = 0;
|
||||
virtual void applyHapticsLeftHand(float intensity) = 0;
|
||||
virtual void applyHapticsRightHand(float intensity) = 0;
|
||||
|
||||
/// Returns if the last used input device was a joystick or a keyboard
|
||||
/// @return true if joystick, false otherwise
|
||||
|
|
|
@ -54,7 +54,7 @@ namespace MWWorld
|
|||
namespace MWGui
|
||||
{
|
||||
class Layout;
|
||||
|
||||
class DragAndDrop;
|
||||
class Console;
|
||||
class SpellWindow;
|
||||
class TradeWindow;
|
||||
|
@ -106,6 +106,7 @@ namespace MWBase
|
|||
/// @note This method will block until the video finishes playing
|
||||
/// (and will continually update the window while doing so)
|
||||
virtual void playVideo(const std::string& name, bool allowSkipping) = 0;
|
||||
virtual bool isPlayingVideo(void) const = 0;
|
||||
|
||||
virtual void setNewGame(bool newgame) = 0;
|
||||
|
||||
|
@ -289,6 +290,7 @@ namespace MWBase
|
|||
virtual void showCrosshair(bool show) = 0;
|
||||
virtual bool getSubtitlesEnabled() = 0;
|
||||
virtual bool toggleHud() = 0;
|
||||
virtual MWGui::DragAndDrop& getDragAndDrop(void) = 0;
|
||||
|
||||
virtual void disallowMouse() = 0;
|
||||
virtual void allowMouse() = 0;
|
||||
|
@ -444,6 +446,8 @@ namespace MWBase
|
|||
|
||||
virtual void watchActor(const MWWorld::Ptr& ptr) = 0;
|
||||
virtual MWWorld::Ptr getWatchedActor() const = 0;
|
||||
|
||||
virtual void viewerTraversals(bool updateWindowManager) = 0;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#include <map>
|
||||
#include <set>
|
||||
#include <deque>
|
||||
#include <tuple>
|
||||
|
||||
#include <components/esm/cellid.hpp>
|
||||
|
||||
|
@ -33,7 +34,9 @@ namespace osg
|
|||
class Matrixf;
|
||||
class Quat;
|
||||
class Image;
|
||||
class Node;
|
||||
class Stats;
|
||||
class Transform;
|
||||
}
|
||||
|
||||
namespace Loading
|
||||
|
@ -72,6 +75,8 @@ namespace MWPhysics
|
|||
namespace MWRender
|
||||
{
|
||||
class Animation;
|
||||
class RenderingManager;
|
||||
struct RayResult;
|
||||
}
|
||||
|
||||
namespace MWMechanics
|
||||
|
@ -96,6 +101,11 @@ namespace MWWorld
|
|||
typedef std::vector<std::pair<MWWorld::Ptr,MWMechanics::Movement> > PtrMovementList;
|
||||
}
|
||||
|
||||
namespace MWVR
|
||||
{
|
||||
class UserPointer;
|
||||
}
|
||||
|
||||
namespace MWBase
|
||||
{
|
||||
/// \brief Interface for the World (implemented in MWWorld)
|
||||
|
@ -156,6 +166,8 @@ namespace MWBase
|
|||
virtual MWWorld::Ptr getPlayerPtr() = 0;
|
||||
virtual MWWorld::ConstPtr getPlayerConstPtr() const = 0;
|
||||
|
||||
virtual MWRender::RenderingManager& getRenderingManager() = 0;
|
||||
|
||||
virtual const MWWorld::ESMStore& getStore() const = 0;
|
||||
|
||||
/*
|
||||
|
@ -385,6 +397,8 @@ namespace MWBase
|
|||
|
||||
virtual float getMaxActivationDistance() = 0;
|
||||
|
||||
virtual float getActivationDistancePlusTelekinesis() = 0;
|
||||
|
||||
/// Returns a pointer to the object the provided object would hit (if within the
|
||||
/// specified distance), and the point where the hit occurs. This will attempt to
|
||||
/// use the "Head" node, or alternatively the "Bip01 Head" node as a basis.
|
||||
|
@ -563,6 +577,12 @@ namespace MWBase
|
|||
/// @param cursor Y (relative 0-1)
|
||||
/// @param number of objects to place
|
||||
|
||||
virtual MWWorld::Ptr placeObject (const MWWorld::ConstPtr& object, const MWRender::RayResult& ray, int amount) = 0;
|
||||
///< copy and place an object into the gameworld based on the given intersection
|
||||
/// @param object
|
||||
/// @param world position to place object
|
||||
/// @param number of objects to place
|
||||
|
||||
virtual MWWorld::Ptr dropObjectOnGround (const MWWorld::Ptr& actor, const MWWorld::ConstPtr& object, int amount) = 0;
|
||||
///< copy and place an object into the gameworld at the given actor's position
|
||||
/// @param actor giving the dropped object position
|
||||
|
@ -857,6 +877,18 @@ namespace MWBase
|
|||
|
||||
virtual bool hasCollisionWithDoor(const MWWorld::ConstPtr& door, const osg::Vec3f& position, const osg::Vec3f& destination) const = 0;
|
||||
|
||||
/// @result pointer to the object and/or node the given node is currently pointing at
|
||||
/// @Return distance to the target object, or -1 if no object was targeted / in range
|
||||
virtual float getTargetObject(MWRender::RayResult& result, const osg::Vec3f& origin, const osg::Quat& orientation, float maxDistance, bool ignorePlayer) = 0;
|
||||
|
||||
#ifdef USE_OPENXR
|
||||
virtual MWVR::UserPointer& getUserPointer() = 0;
|
||||
virtual MWWorld::Ptr getPointerTarget() = 0;
|
||||
#endif
|
||||
|
||||
/// @Return ESM::Weapon::Type enum describing the type of weapon currently drawn by the player.
|
||||
virtual int getActiveWeaponType(void) = 0;
|
||||
|
||||
virtual bool isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius, const MWWorld::ConstPtr& ignore) const = 0;
|
||||
|
||||
virtual void reportStats(unsigned int frameNumber, osg::Stats& stats) const = 0;
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
#include "../mwmechanics/difficultyscaling.hpp"
|
||||
|
||||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwbase/inputmanager.hpp"
|
||||
#include "../mwbase/mechanicsmanager.hpp"
|
||||
#include "../mwbase/windowmanager.hpp"
|
||||
#include "../mwbase/world.hpp"
|
||||
|
@ -237,7 +238,7 @@ namespace MWClass
|
|||
}
|
||||
|
||||
|
||||
void Creature::hit(const MWWorld::Ptr& ptr, float attackStrength, int type) const
|
||||
bool Creature::hit(const MWWorld::Ptr& ptr, float attackStrength, int type, bool) const
|
||||
{
|
||||
/*
|
||||
Start of tes3mp addition
|
||||
|
@ -246,7 +247,7 @@ namespace MWClass
|
|||
*/
|
||||
if (mwmp::PlayerList::isDedicatedPlayer(ptr) || mwmp::Main::get().getCellController()->isDedicatedActor(ptr))
|
||||
{
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
/*
|
||||
End of tes3mp addition
|
||||
|
@ -258,7 +259,7 @@ namespace MWClass
|
|||
MWMechanics::CreatureStats &stats = getCreatureStats(ptr);
|
||||
|
||||
if (stats.getDrawState() != MWMechanics::DrawState_Weapon)
|
||||
return;
|
||||
return false;
|
||||
|
||||
// Get the weapon used (if hand-to-hand, weapon = inv.end())
|
||||
MWWorld::Ptr weapon;
|
||||
|
@ -282,7 +283,7 @@ namespace MWClass
|
|||
|
||||
std::pair<MWWorld::Ptr, osg::Vec3f> result = MWBase::Environment::get().getWorld()->getHitContact(ptr, dist, targetActors);
|
||||
if (result.first.isEmpty())
|
||||
return; // Didn't hit anything
|
||||
return false; // Didn't hit anything
|
||||
|
||||
MWWorld::Ptr victim = result.first;
|
||||
|
||||
|
@ -299,6 +300,7 @@ namespace MWClass
|
|||
objectList->packetOrigin = mwmp::CLIENT_GAMEPLAY;
|
||||
objectList->addObjectHit(victim, ptr);
|
||||
objectList->sendObjectHit();
|
||||
return false;
|
||||
}
|
||||
/*
|
||||
End of tes3mp change (major)
|
||||
|
@ -355,7 +357,7 @@ namespace MWClass
|
|||
|
||||
victim.getClass().onHit(victim, 0.0f, false, MWWorld::Ptr(), ptr, osg::Vec3f(), false);
|
||||
MWMechanics::reduceWeaponCondition(0.f, false, weapon, ptr);
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
int min,max;
|
||||
|
@ -423,10 +425,11 @@ namespace MWClass
|
|||
|
||||
MWMechanics::diseaseContact(victim, ptr);
|
||||
|
||||
victim.getClass().onHit(victim, damage, healthdmg, weapon, ptr, hitPosition, true);
|
||||
victim.getClass().onHit(victim, damage, healthdmg, weapon, ptr, hitPosition, true, attackStrength);
|
||||
return true;
|
||||
}
|
||||
|
||||
void Creature::onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, const osg::Vec3f &hitPosition, bool successful) const
|
||||
void Creature::onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, const osg::Vec3f &hitPosition, bool successful, float hitStrength) const
|
||||
{
|
||||
MWMechanics::CreatureStats& stats = getCreatureStats(ptr);
|
||||
|
||||
|
@ -441,6 +444,8 @@ namespace MWClass
|
|||
if (isMobile(ptr) && !attacker.isEmpty())
|
||||
setOnPcHitMe = MWBase::Environment::get().getMechanicsManager()->actorAttacked(ptr, attacker);
|
||||
|
||||
bool attackerIsPlayer = attacker == MWMechanics::getPlayer();
|
||||
|
||||
// Attacker and target store each other as hitattemptactor if they have no one stored yet
|
||||
if (!attacker.isEmpty() && attacker.getClass().isActor())
|
||||
{
|
||||
|
@ -480,7 +485,7 @@ namespace MWClass
|
|||
if (!object.isEmpty())
|
||||
stats.setLastHitAttemptObject(object.getCellRef().getRefId());
|
||||
|
||||
if (setOnPcHitMe && !attacker.isEmpty() && attacker == MWMechanics::getPlayer())
|
||||
if (setOnPcHitMe && !attacker.isEmpty() && attackerIsPlayer)
|
||||
{
|
||||
const std::string &script = ptr.get<ESM::Creature>()->mBase->mScript;
|
||||
/* Set the OnPCHitMe script variable. The script is responsible for clearing it. */
|
||||
|
@ -491,7 +496,7 @@ namespace MWClass
|
|||
if (!successful)
|
||||
{
|
||||
// Missed
|
||||
if (!attacker.isEmpty() && attacker == MWMechanics::getPlayer())
|
||||
if (!attacker.isEmpty() && attackerIsPlayer)
|
||||
MWBase::Environment::get().getSoundManager()->playSound3D(ptr, "miss", 1.0f, 1.0f);
|
||||
return;
|
||||
}
|
||||
|
@ -607,6 +612,16 @@ namespace MWClass
|
|||
/*
|
||||
End of tes3mp addition
|
||||
*/
|
||||
|
||||
if(successful)
|
||||
{
|
||||
auto* inputManager = MWBase::Environment::get().getInputManager();
|
||||
if (attackerIsPlayer && hitStrength > 0.f)
|
||||
{
|
||||
float hapticIntensity = std::max(0.25f, std::min(1.f, hitStrength));
|
||||
inputManager->applyHapticsRightHand(hapticIntensity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<MWWorld::Action> Creature::activate (const MWWorld::Ptr& ptr,
|
||||
|
|
|
@ -55,9 +55,9 @@ namespace MWClass
|
|||
MWMechanics::CreatureStats& getCreatureStats (const MWWorld::Ptr& ptr) const override;
|
||||
///< Return creature stats
|
||||
|
||||
void hit(const MWWorld::Ptr& ptr, float attackStrength, int type) const override;
|
||||
bool hit(const MWWorld::Ptr& ptr, float attackStrength, int type, bool simulated) const override;
|
||||
|
||||
void onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, const osg::Vec3f &hitPosition, bool successful) const override;
|
||||
void onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, const osg::Vec3f &hitPosition, bool successful, float hitStrength) const override;
|
||||
|
||||
std::shared_ptr<MWWorld::Action> activate (const MWWorld::Ptr& ptr,
|
||||
const MWWorld::Ptr& actor) const override;
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
*/
|
||||
|
||||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwbase/inputmanager.hpp"
|
||||
#include "../mwbase/world.hpp"
|
||||
#include "../mwbase/mechanicsmanager.hpp"
|
||||
#include "../mwbase/windowmanager.hpp"
|
||||
|
@ -548,7 +549,7 @@ namespace MWClass
|
|||
}
|
||||
|
||||
|
||||
void Npc::hit(const MWWorld::Ptr& ptr, float attackStrength, int type) const
|
||||
bool Npc::hit(const MWWorld::Ptr& ptr, float attackStrength, int type, bool simulated) const
|
||||
{
|
||||
/*
|
||||
Start of tes3mp addition
|
||||
|
@ -557,7 +558,7 @@ namespace MWClass
|
|||
*/
|
||||
if (mwmp::PlayerList::isDedicatedPlayer(ptr) || mwmp::Main::get().getCellController()->isDedicatedActor(ptr))
|
||||
{
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
/*
|
||||
End of tes3mp addition
|
||||
|
@ -574,7 +575,11 @@ namespace MWClass
|
|||
if(!weapon.isEmpty() && weapon.getTypeName() != typeid(ESM::Weapon).name())
|
||||
weapon = MWWorld::Ptr();
|
||||
|
||||
MWMechanics::applyFatigueLoss(ptr, weapon, attackStrength);
|
||||
if (getCreatureStats(ptr).getDrawState() != MWMechanics::DrawState_Weapon)
|
||||
return false;
|
||||
|
||||
if (!simulated)
|
||||
MWMechanics::applyFatigueLoss(ptr, weapon, attackStrength);
|
||||
|
||||
const float fCombatDistance = store.find("fCombatDistance")->mValue.getFloat();
|
||||
float dist = fCombatDistance * (!weapon.isEmpty() ?
|
||||
|
@ -591,9 +596,10 @@ namespace MWClass
|
|||
MWWorld::Ptr victim = result.first;
|
||||
osg::Vec3f hitPosition (result.second);
|
||||
if(victim.isEmpty()) // Didn't hit anything
|
||||
return;
|
||||
return false;
|
||||
|
||||
const MWWorld::Class &othercls = victim.getClass();
|
||||
|
||||
/*
|
||||
Start of tes3mp change (major)
|
||||
|
||||
|
@ -607,13 +613,18 @@ namespace MWClass
|
|||
objectList->packetOrigin = mwmp::CLIENT_GAMEPLAY;
|
||||
objectList->addObjectHit(victim, ptr);
|
||||
objectList->sendObjectHit();
|
||||
return false;
|
||||
}
|
||||
/*
|
||||
End of tes3mp change (major)
|
||||
*/
|
||||
|
||||
MWMechanics::CreatureStats &otherstats = othercls.getCreatureStats(victim);
|
||||
if(otherstats.isDead()) // Can't hit dead actors
|
||||
return;
|
||||
return false;
|
||||
|
||||
if (simulated)
|
||||
return true;
|
||||
|
||||
if(ptr == MWMechanics::getPlayer())
|
||||
MWBase::Environment::get().getWindowManager()->setEnemy(victim);
|
||||
|
@ -671,7 +682,7 @@ namespace MWClass
|
|||
|
||||
othercls.onHit(victim, 0.0f, false, weapon, ptr, osg::Vec3f(), false);
|
||||
MWMechanics::reduceWeaponCondition(0.f, false, weapon, ptr);
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool healthdmg;
|
||||
|
@ -744,14 +755,16 @@ namespace MWClass
|
|||
|
||||
MWMechanics::diseaseContact(victim, ptr);
|
||||
|
||||
othercls.onHit(victim, damage, healthdmg, weapon, ptr, hitPosition, true);
|
||||
othercls.onHit(victim, damage, healthdmg, weapon, ptr, hitPosition, true, attackStrength);
|
||||
return true;
|
||||
}
|
||||
|
||||
void Npc::onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, const osg::Vec3f &hitPosition, bool successful) const
|
||||
void Npc::onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, const osg::Vec3f &hitPosition, bool successful, float hitStrength) const
|
||||
{
|
||||
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
|
||||
MWMechanics::CreatureStats& stats = getCreatureStats(ptr);
|
||||
bool wasDead = stats.isDead();
|
||||
float rawDamage = damage;
|
||||
|
||||
// Note OnPcHitMe is not set for friendly hits.
|
||||
bool setOnPcHitMe = true;
|
||||
|
@ -762,6 +775,8 @@ namespace MWClass
|
|||
stats.setAttacked(true);
|
||||
setOnPcHitMe = MWBase::Environment::get().getMechanicsManager()->actorAttacked(ptr, attacker);
|
||||
}
|
||||
bool attackerIsPlayer = attacker == MWMechanics::getPlayer();
|
||||
bool victimIsPlayer = ptr == MWMechanics::getPlayer();
|
||||
|
||||
// Attacker and target store each other as hitattemptactor if they have no one stored yet
|
||||
if (!attacker.isEmpty() && attacker.getClass().isActor())
|
||||
|
@ -802,7 +817,7 @@ namespace MWClass
|
|||
if (!object.isEmpty())
|
||||
stats.setLastHitAttemptObject(object.getCellRef().getRefId());
|
||||
|
||||
if (setOnPcHitMe && !attacker.isEmpty() && attacker == MWMechanics::getPlayer())
|
||||
if (setOnPcHitMe && !attacker.isEmpty() && attackerIsPlayer)
|
||||
{
|
||||
const std::string &script = getScript(ptr);
|
||||
/* Set the OnPCHitMe script variable. The script is responsible for clearing it. */
|
||||
|
@ -813,7 +828,7 @@ namespace MWClass
|
|||
if (!successful)
|
||||
{
|
||||
// Missed
|
||||
if (!attacker.isEmpty() && attacker == MWMechanics::getPlayer())
|
||||
if (!attacker.isEmpty() && attackerIsPlayer)
|
||||
sndMgr->playSound3D(ptr, "miss", 1.0f, 1.0f);
|
||||
return;
|
||||
}
|
||||
|
@ -828,7 +843,7 @@ namespace MWClass
|
|||
if (damage < 0.001f)
|
||||
damage = 0;
|
||||
|
||||
bool godmode = ptr == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState();
|
||||
bool godmode = victimIsPlayer && MWBase::Environment::get().getWorld()->getGodModeState();
|
||||
|
||||
if (godmode)
|
||||
damage = 0;
|
||||
|
@ -1046,6 +1061,23 @@ namespace MWClass
|
|||
/*
|
||||
End of tes3mp addition
|
||||
*/
|
||||
|
||||
// Apply haptics
|
||||
if (successful)
|
||||
{
|
||||
auto* inputManager = MWBase::Environment::get().getInputManager();
|
||||
if (victimIsPlayer)
|
||||
{
|
||||
float maxHealth = getCreatureStats(ptr).getHealth().getModified();
|
||||
float hapticIntensity = std::max(0.25f, std::min(1.f, rawDamage / ( maxHealth / 4.f)));
|
||||
inputManager->applyHapticsLeftHand(hapticIntensity);
|
||||
}
|
||||
else if (attackerIsPlayer && hitStrength > 0.f)
|
||||
{
|
||||
float hapticIntensity = std::max(0.25f, std::min(1.f, hitStrength));
|
||||
inputManager->applyHapticsRightHand(hapticIntensity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<MWWorld::Action> Npc::activate (const MWWorld::Ptr& ptr,
|
||||
|
|
|
@ -80,9 +80,9 @@ namespace MWClass
|
|||
End of tes3mp addition
|
||||
*/
|
||||
|
||||
void hit(const MWWorld::Ptr& ptr, float attackStrength, int type) const override;
|
||||
bool hit(const MWWorld::Ptr& ptr, float attackStrength, int type, bool simulated) const override;
|
||||
|
||||
void onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, const osg::Vec3f &hitPosition, bool successful) const override;
|
||||
void onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, const osg::Vec3f &hitPosition, bool successful, float hitStrength) const override;
|
||||
|
||||
void getModelsToPreload(const MWWorld::Ptr& ptr, std::vector<std::string>& models) const override;
|
||||
///< Get a list of models to preload that this object may use (directly or indirectly). default implementation: list getModel().
|
||||
|
|
|
@ -39,6 +39,10 @@
|
|||
#include "itemwidget.hpp"
|
||||
#include "widgets.hpp"
|
||||
|
||||
#ifdef USE_OPENXR
|
||||
#include "../mwvr/vrlistbox.hpp"
|
||||
#endif
|
||||
|
||||
namespace MWGui
|
||||
{
|
||||
AlchemyWindow::AlchemyWindow()
|
||||
|
@ -46,6 +50,9 @@ namespace MWGui
|
|||
, mCurrentFilter(FilterType::ByName)
|
||||
, mModel(nullptr)
|
||||
, mSortModel(nullptr)
|
||||
, mFilterCombo(nullptr)
|
||||
, mFilterEdit(nullptr)
|
||||
, mFilterButton(nullptr)
|
||||
, mAlchemy(new MWMechanics::Alchemy())
|
||||
, mApparatus (4)
|
||||
, mIngredients (4)
|
||||
|
@ -66,8 +73,31 @@ namespace MWGui
|
|||
getWidget(mDecreaseButton, "DecreaseButton");
|
||||
getWidget(mNameEdit, "NameEdit");
|
||||
getWidget(mItemView, "ItemView");
|
||||
getWidget(mFilterValue, "FilterValue");
|
||||
getWidget(mFilterCombo, "FilterValue");
|
||||
getWidget(mFilterType, "FilterType");
|
||||
getWidget(mFilterEdit, "FilterEdit");
|
||||
getWidget(mFilterButton, "FilterButton");
|
||||
|
||||
if (MWBase::Environment::get().getVrMode())
|
||||
{
|
||||
#ifdef USE_OPENXR
|
||||
mFilterListBox = new MWVR::VrListBox();
|
||||
#endif
|
||||
mFilterCombo->setVisible(false);
|
||||
mFilterCombo->setUserString("Hidden", "true");
|
||||
}
|
||||
else
|
||||
{
|
||||
mFilterButton->setVisible(false);
|
||||
mFilterButton->setUserString("Hidden", "true");
|
||||
mFilterEdit->setVisible(false);
|
||||
mFilterEdit->setUserString("Hidden", "true");
|
||||
}
|
||||
|
||||
mFilterButton->eventMouseButtonClick += MyGUI::newDelegate(this, &AlchemyWindow::onFilterButtonClicked);
|
||||
mFilterEdit->eventEditTextChange += MyGUI::newDelegate(this, &AlchemyWindow::onFilterEdited);
|
||||
mFilterCombo->eventComboChangePosition += MyGUI::newDelegate(this, &AlchemyWindow::onFilterChanged);
|
||||
mFilterCombo->eventEditTextChange += MyGUI::newDelegate(this, &AlchemyWindow::onFilterEdited);
|
||||
|
||||
mBrewCountEdit->eventValueChanged += MyGUI::newDelegate(this, &AlchemyWindow::onCountValueChanged);
|
||||
mBrewCountEdit->eventEditSelectAccept += MyGUI::newDelegate(this, &AlchemyWindow::onAccept);
|
||||
|
@ -90,8 +120,7 @@ namespace MWGui
|
|||
mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &AlchemyWindow::onCancelButtonClicked);
|
||||
|
||||
mNameEdit->eventEditSelectAccept += MyGUI::newDelegate(this, &AlchemyWindow::onAccept);
|
||||
mFilterValue->eventComboChangePosition += MyGUI::newDelegate(this, &AlchemyWindow::onFilterChanged);
|
||||
mFilterValue->eventEditTextChange += MyGUI::newDelegate(this, &AlchemyWindow::onFilterEdited);
|
||||
|
||||
mFilterType->eventMouseButtonClick += MyGUI::newDelegate(this, &AlchemyWindow::switchFilterType);
|
||||
|
||||
center();
|
||||
|
@ -210,7 +239,8 @@ namespace MWGui
|
|||
else
|
||||
mCurrentFilter = FilterType::ByEffect;
|
||||
updateFilters();
|
||||
mFilterValue->clearIndexSelected();
|
||||
mFilterCombo->clearIndexSelected();
|
||||
mFilterEdit->setCaption("");
|
||||
updateFilters();
|
||||
}
|
||||
|
||||
|
@ -233,39 +263,44 @@ namespace MWGui
|
|||
}
|
||||
mSortModel->setNameFilter({});
|
||||
mSortModel->setEffectFilter({});
|
||||
mFilterValue->clearIndexSelected();
|
||||
mFilterCombo->clearIndexSelected();
|
||||
mFilterEdit->setCaption("");
|
||||
updateFilters();
|
||||
mItemView->update();
|
||||
}
|
||||
|
||||
void AlchemyWindow::updateFilters()
|
||||
{
|
||||
std::set<std::string> itemNames, itemEffects;
|
||||
mItemEffects.clear();
|
||||
mItemNames.clear();
|
||||
for (size_t i = 0; i < mModel->getItemCount(); ++i)
|
||||
{
|
||||
MWWorld::Ptr item = mModel->getItem(i).mBase;
|
||||
if (item.getTypeName() != typeid(ESM::Ingredient).name())
|
||||
continue;
|
||||
|
||||
itemNames.insert(item.getClass().getName(item));
|
||||
mItemNames.insert(item.getClass().getName(item));
|
||||
|
||||
MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr();
|
||||
auto const alchemySkill = player.getClass().getSkill(player, ESM::Skill::Alchemy);
|
||||
|
||||
auto const effects = MWMechanics::Alchemy::effectsDescription(item, alchemySkill);
|
||||
itemEffects.insert(effects.begin(), effects.end());
|
||||
mItemEffects.insert(effects.begin(), effects.end());
|
||||
}
|
||||
|
||||
mFilterValue->removeAllItems();
|
||||
auto const addItems = [&](auto const& container)
|
||||
mFilterCombo->removeAllItems();
|
||||
for (auto const& item : items())
|
||||
{
|
||||
for (auto const& item : container)
|
||||
mFilterValue->addItem(item);
|
||||
};
|
||||
mFilterCombo->addItem(item);
|
||||
}
|
||||
}
|
||||
|
||||
const std::set<std::string>& AlchemyWindow::items()
|
||||
{
|
||||
switch (mCurrentFilter)
|
||||
{
|
||||
case FilterType::ByName: addItems(itemNames); break;
|
||||
case FilterType::ByEffect: addItems(itemEffects); break;
|
||||
case FilterType::ByName: return mItemNames; break;
|
||||
case FilterType::ByEffect: return mItemEffects; break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -292,6 +327,20 @@ namespace MWGui
|
|||
applyFilter(_sender->getCaption());
|
||||
}
|
||||
|
||||
void AlchemyWindow::onFilterButtonClicked(MyGUI::Widget* _sender)
|
||||
{
|
||||
#ifdef USE_OPENXR
|
||||
mFilterListBox->open(mFilterCombo, [this](std::size_t index) {
|
||||
if (index != MyGUI::ITEM_NONE)
|
||||
{
|
||||
auto filter = mFilterCombo->getItemNameAt(index);
|
||||
mFilterEdit->setCaption(filter);
|
||||
applyFilter(filter);
|
||||
}
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
void AlchemyWindow::onOpen()
|
||||
{
|
||||
mAlchemy->clear();
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
#include <MyGUI_ControllerItem.h>
|
||||
#include <MyGUI_ComboBox.h>
|
||||
#include <MyGUI_ListBox.h>
|
||||
|
||||
#include <components/widgets/box.hpp>
|
||||
#include <components/widgets/numericeditbox.hpp>
|
||||
|
@ -17,6 +18,11 @@ namespace MWMechanics
|
|||
class Alchemy;
|
||||
}
|
||||
|
||||
namespace MWVR
|
||||
{
|
||||
class VrListBox;
|
||||
}
|
||||
|
||||
namespace MWGui
|
||||
{
|
||||
class ItemView;
|
||||
|
@ -54,10 +60,16 @@ namespace MWGui
|
|||
MyGUI::Button* mIncreaseButton;
|
||||
MyGUI::Button* mDecreaseButton;
|
||||
Gui::AutoSizedButton* mFilterType;
|
||||
MyGUI::ComboBox* mFilterValue;
|
||||
MyGUI::ComboBox* mFilterCombo;
|
||||
MyGUI::EditBox* mFilterEdit;
|
||||
MyGUI::Button* mFilterButton;
|
||||
MWVR::VrListBox* mFilterListBox;
|
||||
MyGUI::EditBox* mNameEdit;
|
||||
Gui::NumericEditBox* mBrewCountEdit;
|
||||
|
||||
std::set<std::string> mItemNames;
|
||||
std::set<std::string> mItemEffects;
|
||||
|
||||
void onCancelButtonClicked(MyGUI::Widget* _sender);
|
||||
void onCreateButtonClicked(MyGUI::Widget* _sender);
|
||||
void onIngredientSelected(MyGUI::Widget* _sender);
|
||||
|
@ -72,8 +84,10 @@ namespace MWGui
|
|||
void initFilter();
|
||||
void onFilterChanged(MyGUI::ComboBox* _sender, size_t _index);
|
||||
void onFilterEdited(MyGUI::EditBox* _sender);
|
||||
void onFilterButtonClicked(MyGUI::Widget* _sender);
|
||||
void switchFilterType(MyGUI::Widget* _sender);
|
||||
void updateFilters();
|
||||
const std::set<std::string>& items();
|
||||
|
||||
void addRepeatController(MyGUI::Widget* widget);
|
||||
|
||||
|
|
|
@ -48,7 +48,11 @@ namespace MWGui
|
|||
{
|
||||
|
||||
ContainerWindow::ContainerWindow(DragAndDrop* dragAndDrop)
|
||||
#ifdef USE_OPENXR
|
||||
: WindowBase("openmw_container_window_vr.layout")
|
||||
#else
|
||||
: WindowBase("openmw_container_window.layout")
|
||||
#endif
|
||||
, mDragAndDrop(dragAndDrop)
|
||||
, mSortModel(nullptr)
|
||||
, mModel(nullptr)
|
||||
|
|
|
@ -279,7 +279,11 @@ namespace MWGui
|
|||
// --------------------------------------------------------------------------------------------------
|
||||
|
||||
DialogueWindow::DialogueWindow()
|
||||
#ifdef USE_OPENXR
|
||||
: WindowBase("openmw_dialogue_window_vr.layout")
|
||||
#else
|
||||
: WindowBase("openmw_dialogue_window.layout")
|
||||
#endif
|
||||
, mIsCompanion(false)
|
||||
, mGoodbye(false)
|
||||
, mPersuasionDialog(new ResponseCallback(this))
|
||||
|
|
|
@ -106,7 +106,11 @@ namespace MWGui
|
|||
|
||||
|
||||
HUD::HUD(CustomMarkerCollection &customMarkers, DragAndDrop* dragAndDrop, MWRender::LocalMap* localMapRender)
|
||||
#ifdef USE_OPENXR
|
||||
: WindowBase("openmw_hud_vr.layout")
|
||||
#else
|
||||
: WindowBase("openmw_hud.layout")
|
||||
#endif
|
||||
, LocalMapBase(customMarkers, localMapRender, Settings::Manager::getBool("local map hud fog of war", "Map"))
|
||||
, mHealth(nullptr)
|
||||
, mMagicka(nullptr)
|
||||
|
@ -139,7 +143,13 @@ namespace MWGui
|
|||
, mIsDrowning(false)
|
||||
, mDrowningFlashTheta(0.f)
|
||||
{
|
||||
#ifdef USE_OPENXR
|
||||
mMainWidgetBaseSize = mMainWidget->getSize();
|
||||
mMainWidget->setSize(mMainWidgetBaseSize);
|
||||
#else
|
||||
mMainWidget->setSize(MyGUI::RenderManager::getInstance().getViewSize());
|
||||
mMainWidgetBaseSize = mMainWidget->getSize();
|
||||
#endif
|
||||
|
||||
// Energy bars
|
||||
getWidget(mHealthFrame, "HealthFrame");
|
||||
|
@ -327,6 +337,7 @@ namespace MWGui
|
|||
}
|
||||
}
|
||||
|
||||
// XR-TODO: Implement equivalent
|
||||
void HUD::onWorldMouseOver(MyGUI::Widget* _sender, int x, int y)
|
||||
{
|
||||
if (mDragAndDrop->mIsOnDragAndDrop)
|
||||
|
@ -641,8 +652,8 @@ namespace MWGui
|
|||
mSpellBox->setPosition(mSpellBoxBaseLeft - spellDx, mSpellBox->getTop());
|
||||
mSneakBox->setPosition(mSneakBoxBaseLeft - sneakDx, mSneakBox->getTop());
|
||||
|
||||
#ifndef USE_OPENXR
|
||||
const MyGUI::IntSize& viewSize = MyGUI::RenderManager::getInstance().getViewSize();
|
||||
|
||||
// effect box can have variable width -> variable left coordinate
|
||||
int effectsDx = 0;
|
||||
if (!mMinimapBox->getVisible ())
|
||||
|
@ -653,6 +664,11 @@ namespace MWGui
|
|||
mCellNameBox->setVisible(false);
|
||||
|
||||
mEffectBox->setPosition((viewSize.width - mEffectBoxBaseRight) - mEffectBox->getWidth() + effectsDx, mEffectBox->getTop());
|
||||
#else
|
||||
// in VR mode, the effect box grows to the right and does not need repositioning
|
||||
int width = std::max(mMainWidgetBaseSize.width, mEffectBox->getSize().width);
|
||||
mMainWidget->setSize(width, mMainWidget->getHeight());
|
||||
#endif
|
||||
}
|
||||
|
||||
void HUD::updateEnemyHealthBar()
|
||||
|
|
|
@ -79,6 +79,8 @@ namespace MWGui
|
|||
int mHealthManaStaminaBaseLeft, mWeapBoxBaseLeft, mSpellBoxBaseLeft, mSneakBoxBaseLeft;
|
||||
// bottom right elements
|
||||
int mMinimapBoxBaseRight, mEffectBoxBaseRight;
|
||||
// initial size
|
||||
MyGUI::IntSize mMainWidgetBaseSize;
|
||||
|
||||
DragAndDrop* mDragAndDrop;
|
||||
|
||||
|
|
|
@ -71,7 +71,11 @@ namespace MWGui
|
|||
{
|
||||
|
||||
InventoryWindow::InventoryWindow(DragAndDrop* dragAndDrop, osg::Group* parent, Resource::ResourceSystem* resourceSystem)
|
||||
#ifdef USE_OPENXR
|
||||
: WindowPinnableBase("openmw_inventory_window_vr.layout")
|
||||
#else
|
||||
: WindowPinnableBase("openmw_inventory_window.layout")
|
||||
#endif
|
||||
, mDragAndDrop(dragAndDrop)
|
||||
, mSelectedItem(-1)
|
||||
, mSortModel(nullptr)
|
||||
|
|
|
@ -118,7 +118,7 @@ namespace MWGui
|
|||
void sellItem(MyGUI::Widget* sender, int count);
|
||||
void dragItem(MyGUI::Widget* sender, int count);
|
||||
|
||||
void onWindowResize(MyGUI::Window* _sender);
|
||||
void onWindowResize(MyGUI::Window* _sender) override;
|
||||
void onFilterChanged(MyGUI::Widget* _sender);
|
||||
void onNameFilterChanged(MyGUI::EditBox* _sender);
|
||||
void onAvatarClicked(MyGUI::Widget* _sender);
|
||||
|
|
|
@ -22,6 +22,11 @@
|
|||
#include "../mwbase/windowmanager.hpp"
|
||||
#include "../mwbase/environment.hpp"
|
||||
|
||||
#ifdef USE_OPENXR
|
||||
#include "../mwvr/vrenvironment.hpp"
|
||||
#include "../mwvr/vrgui.hpp"
|
||||
#endif
|
||||
|
||||
namespace MWGui
|
||||
{
|
||||
|
||||
|
@ -102,6 +107,11 @@ void KeyboardNavigation::_unlinkWidget(MyGUI::Widget *widget)
|
|||
w.second = nullptr;
|
||||
if (widget == mCurrentFocus)
|
||||
mCurrentFocus = nullptr;
|
||||
|
||||
#ifdef USE_OPENXR
|
||||
if (MWBase::Environment::get().getVrMode())
|
||||
MWVR::Environment::get().getGUIManager()->notifyWidgetUnlinked(widget);
|
||||
#endif
|
||||
}
|
||||
|
||||
#if MYGUI_VERSION < MYGUI_DEFINE_VERSION(3,2,3)
|
||||
|
|
|
@ -5,6 +5,13 @@
|
|||
#include <MyGUI_Gui.h>
|
||||
#include <MyGUI_TextBox.h>
|
||||
#include <MyGUI_Window.h>
|
||||
#include <MyGUI_OverlappedLayer.h>
|
||||
#include <MyGUI_SharedLayer.h>
|
||||
|
||||
#ifdef USE_OPENXR
|
||||
#include "../mwvr/vrgui.hpp"
|
||||
#include "../mwvr/vrenvironment.hpp"
|
||||
#endif
|
||||
|
||||
namespace MWGui
|
||||
{
|
||||
|
@ -45,9 +52,21 @@ namespace MWGui
|
|||
mMainWidget->setCoord(x,y,w,h);
|
||||
}
|
||||
|
||||
void Layout::setCoordf(float x, float y, float w, float h)
|
||||
{
|
||||
mMainWidget->setRealCoord(x, y, w, h);
|
||||
}
|
||||
|
||||
void Layout::setVisible(bool b)
|
||||
{
|
||||
mMainWidget->setVisible(b);
|
||||
#ifdef USE_OPENXR
|
||||
auto* vrGUIManager = MWVR::Environment::get().getGUIManager();
|
||||
if (!vrGUIManager)
|
||||
// May end up here before before rendering has been fully set up
|
||||
return;
|
||||
vrGUIManager->setVisible(this, b);
|
||||
#endif
|
||||
}
|
||||
|
||||
void Layout::setText(const std::string &name, const std::string &caption)
|
||||
|
|
|
@ -55,6 +55,7 @@ namespace MWGui
|
|||
|
||||
public:
|
||||
void setCoord(int x, int y, int w, int h);
|
||||
void setCoordf(float x, float y, float w, float h);
|
||||
|
||||
virtual void setVisible(bool b);
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
#include <MyGUI_TextBox.h>
|
||||
|
||||
#include <components/misc/rng.hpp>
|
||||
#include <components/misc/callbackmanager.hpp>
|
||||
#include <components/debug/debuglog.hpp>
|
||||
#include <components/myguiplatform/myguitexture.hpp>
|
||||
#include <components/settings/settings.hpp>
|
||||
|
@ -331,12 +332,7 @@ namespace MWGui
|
|||
mCopyFramebufferToTextureCallback = new CopyFramebufferToTextureCallback(mTexture);
|
||||
}
|
||||
|
||||
#if OSG_VERSION_GREATER_OR_EQUAL(3, 5, 10)
|
||||
mViewer->getCamera()->removeInitialDrawCallback(mCopyFramebufferToTextureCallback);
|
||||
mViewer->getCamera()->addInitialDrawCallback(mCopyFramebufferToTextureCallback);
|
||||
#else
|
||||
mViewer->getCamera()->setInitialDrawCallback(mCopyFramebufferToTextureCallback);
|
||||
#endif
|
||||
Misc::CallbackManager::instance().addCallbackOneshot(Misc::CallbackManager::DrawStage::Initial, mCopyFramebufferToTextureCallback);
|
||||
mCopyFramebufferToTextureCallback->reset();
|
||||
|
||||
mBackgroundImage->setBackgroundImage("");
|
||||
|
@ -375,9 +371,7 @@ namespace MWGui
|
|||
// at the time this function is called we are in the middle of a frame,
|
||||
// so out of order calls are necessary to get a correct frameNumber for the next frame.
|
||||
// refer to the advance() and frame() order in Engine::go()
|
||||
mViewer->eventTraversal();
|
||||
mViewer->updateTraversal();
|
||||
mViewer->renderingTraversals();
|
||||
MWBase::Environment::get().getWindowManager()->viewerTraversals(false);
|
||||
mViewer->advance(mViewer->getFrameStamp()->getSimulationTime());
|
||||
|
||||
mLastRenderTime = mTimer.time_m();
|
||||
|
|
|
@ -700,7 +700,11 @@ namespace MWGui
|
|||
// ------------------------------------------------------------------------------------------
|
||||
|
||||
MapWindow::MapWindow(CustomMarkerCollection &customMarkers, DragAndDrop* drag, MWRender::LocalMap* localMapRender, SceneUtil::WorkQueue* workQueue)
|
||||
#ifdef USE_OPENXR
|
||||
: WindowPinnableBase("openmw_map_window_vr.layout")
|
||||
#else
|
||||
: WindowPinnableBase("openmw_map_window.layout")
|
||||
#endif
|
||||
, LocalMapBase(customMarkers, localMapRender)
|
||||
, NoDrop(drag, mMainWidget)
|
||||
, mGlobalMap(nullptr)
|
||||
|
|
|
@ -138,6 +138,8 @@ namespace MWGui
|
|||
messageBox->update(height);
|
||||
height += messageBox->getHeight();
|
||||
}
|
||||
|
||||
box->setVisible(true);
|
||||
}
|
||||
|
||||
void MessageBoxManager::removeStaticMessageBox ()
|
||||
|
@ -228,6 +230,11 @@ namespace MWGui
|
|||
mMessageWidget->setCaptionWithReplacing(mMessage);
|
||||
}
|
||||
|
||||
MessageBox::~MessageBox()
|
||||
{
|
||||
setVisible(false);
|
||||
}
|
||||
|
||||
void MessageBox::update (int height)
|
||||
{
|
||||
MyGUI::IntSize gameWindowSize = MyGUI::RenderManager::getInstance().getViewSize();
|
||||
|
|
|
@ -68,6 +68,7 @@ namespace MWGui
|
|||
{
|
||||
public:
|
||||
MessageBox (MessageBoxManager& parMessageBoxManager, const std::string& message);
|
||||
~MessageBox();
|
||||
void setMessage (const std::string& message);
|
||||
int getHeight ();
|
||||
void update (int height);
|
||||
|
|
|
@ -46,7 +46,8 @@ namespace MWGui
|
|||
GM_LoadingWallpaper,
|
||||
GM_Jail,
|
||||
|
||||
GM_QuickKeysMenu
|
||||
GM_QuickKeysMenu,
|
||||
GM_VrMetaMenu
|
||||
};
|
||||
|
||||
// Windows shown in inventory mode
|
||||
|
|
|
@ -30,6 +30,10 @@
|
|||
|
||||
#include "../mwstate/character.hpp"
|
||||
|
||||
#ifdef USE_OPENXR
|
||||
#include "../mwvr/vrlistbox.hpp"
|
||||
#endif
|
||||
|
||||
#include "confirmationdialog.hpp"
|
||||
|
||||
namespace MWGui
|
||||
|
@ -41,7 +45,6 @@ namespace MWGui
|
|||
, mCurrentSlot(nullptr)
|
||||
{
|
||||
getWidget(mScreenshot, "Screenshot");
|
||||
getWidget(mCharacterSelection, "SelectCharacter");
|
||||
getWidget(mInfoText, "InfoText");
|
||||
getWidget(mOkButton, "OkButton");
|
||||
getWidget(mCancelButton, "CancelButton");
|
||||
|
@ -49,11 +52,28 @@ namespace MWGui
|
|||
getWidget(mSaveList, "SaveList");
|
||||
getWidget(mSaveNameEdit, "SaveNameEdit");
|
||||
getWidget(mSpacer, "Spacer");
|
||||
getWidget(mCharacterSelection, "SelectCharacter");
|
||||
getWidget(mCharacterSelectionButton, "SelectCharacterButton");
|
||||
|
||||
if (MWBase::Environment::get().getVrMode())
|
||||
{
|
||||
#ifdef USE_OPENXR
|
||||
mCharacterSelectionListBox = new MWVR::VrListBox();
|
||||
#endif
|
||||
mCharacterSelection->setVisible(false);
|
||||
mCharacterSelection->setUserString("Hidden", "true");
|
||||
}
|
||||
else
|
||||
{
|
||||
mCharacterSelectionButton->setVisible(false);
|
||||
mCharacterSelectionButton->setUserString("Hidden", "true");
|
||||
}
|
||||
mCharacterSelectionButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SaveGameDialog::onCharacterSelectionButtonClicked);
|
||||
mCharacterSelection->eventComboChangePosition += MyGUI::newDelegate(this, &SaveGameDialog::onCharacterSelected);
|
||||
mCharacterSelection->eventComboAccept += MyGUI::newDelegate(this, &SaveGameDialog::onCharacterAccept);
|
||||
mOkButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SaveGameDialog::onOkButtonClicked);
|
||||
mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SaveGameDialog::onCancelButtonClicked);
|
||||
mDeleteButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SaveGameDialog::onDeleteButtonClicked);
|
||||
mCharacterSelection->eventComboChangePosition += MyGUI::newDelegate(this, &SaveGameDialog::onCharacterSelected);
|
||||
mCharacterSelection->eventComboAccept += MyGUI::newDelegate(this, &SaveGameDialog::onCharacterAccept);
|
||||
mSaveList->eventListChangePosition += MyGUI::newDelegate(this, &SaveGameDialog::onSlotSelected);
|
||||
mSaveList->eventListMouseItemActivate += MyGUI::newDelegate(this, &SaveGameDialog::onSlotMouseClick);
|
||||
mSaveList->eventListSelectAccept += MyGUI::newDelegate(this, &SaveGameDialog::onSlotActivated);
|
||||
|
@ -147,11 +167,15 @@ namespace MWGui
|
|||
if (mSaving)
|
||||
MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mSaveNameEdit);
|
||||
else
|
||||
MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mSaveList);
|
||||
if (MWBase::Environment::get().getVrMode())
|
||||
MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mCharacterSelectionButton);
|
||||
else
|
||||
MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mSaveList);
|
||||
|
||||
center();
|
||||
|
||||
mCharacterSelection->setCaption("");
|
||||
mCharacterSelectionButton->setCaption("");
|
||||
mCharacterSelection->removeAllItems();
|
||||
mCurrentCharacter = nullptr;
|
||||
mCurrentSlot = nullptr;
|
||||
|
@ -209,6 +233,8 @@ namespace MWGui
|
|||
mCharacterSelection->setIndexSelected(selectedIndex);
|
||||
if (selectedIndex == MyGUI::ITEM_NONE)
|
||||
mCharacterSelection->setCaption("Select Character ...");
|
||||
else
|
||||
mCharacterSelectionButton->setCaption(mCharacterSelection->getCaption());
|
||||
|
||||
fillSaveList();
|
||||
|
||||
|
@ -218,8 +244,17 @@ namespace MWGui
|
|||
{
|
||||
mSaving = !load;
|
||||
mSaveNameEdit->setVisible(!load);
|
||||
mCharacterSelection->setUserString("Hidden", load ? "false" : "true");
|
||||
mCharacterSelection->setVisible(load);
|
||||
|
||||
if (MWBase::Environment::get().getVrMode())
|
||||
{
|
||||
mCharacterSelectionButton->setUserString("Hidden", load ? "false" : "true");
|
||||
mCharacterSelectionButton->setVisible(load);
|
||||
}
|
||||
else
|
||||
{
|
||||
mCharacterSelection->setUserString("Hidden", load ? "false" : "true");
|
||||
mCharacterSelection->setVisible(load);
|
||||
}
|
||||
mSpacer->setUserString("Hidden", load ? "false" : "true");
|
||||
|
||||
mDeleteButton->setUserString("Hidden", load ? "false" : "true");
|
||||
|
@ -244,6 +279,21 @@ namespace MWGui
|
|||
confirmDeleteSave();
|
||||
}
|
||||
|
||||
void SaveGameDialog::onCharacterSelectionButtonClicked(MyGUI::Widget* sender)
|
||||
{
|
||||
#ifdef USE_OPENXR
|
||||
mCharacterSelectionListBox->open(mCharacterSelection, [this](std::size_t index) {
|
||||
if (index != MyGUI::ITEM_NONE)
|
||||
{
|
||||
MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mSaveList);
|
||||
auto caption = mCharacterSelection->getItemNameAt(index);
|
||||
mCharacterSelectionButton->setCaption(caption);
|
||||
onCharacterSelected(mCharacterSelection, index);
|
||||
}
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
void SaveGameDialog::onConfirmationGiven()
|
||||
{
|
||||
accept(true);
|
||||
|
|
|
@ -11,6 +11,11 @@ namespace MWState
|
|||
struct Slot;
|
||||
}
|
||||
|
||||
namespace MWVR
|
||||
{
|
||||
class VrListBox;
|
||||
}
|
||||
|
||||
namespace MWGui
|
||||
{
|
||||
|
||||
|
@ -31,6 +36,7 @@ namespace MWGui
|
|||
void onCancelButtonClicked (MyGUI::Widget* sender);
|
||||
void onOkButtonClicked (MyGUI::Widget* sender);
|
||||
void onDeleteButtonClicked (MyGUI::Widget* sender);
|
||||
void onCharacterSelectionButtonClicked(MyGUI::Widget* sender);
|
||||
void onCharacterSelected (MyGUI::ComboBox* sender, size_t pos);
|
||||
void onCharacterAccept(MyGUI::ComboBox* sender, size_t pos);
|
||||
// Slot selected (mouse click or arrow keys)
|
||||
|
@ -57,6 +63,8 @@ namespace MWGui
|
|||
bool mSaving;
|
||||
|
||||
MyGUI::ComboBox* mCharacterSelection;
|
||||
MWVR::VrListBox* mCharacterSelectionListBox;
|
||||
MyGUI::Button* mCharacterSelectionButton;
|
||||
MyGUI::EditBox* mInfoText;
|
||||
MyGUI::Button* mOkButton;
|
||||
MyGUI::Button* mCancelButton;
|
||||
|
|
|
@ -31,6 +31,14 @@
|
|||
|
||||
#include "confirmationdialog.hpp"
|
||||
|
||||
#ifdef USE_OPENXR
|
||||
#include "../mwvr/vrenvironment.hpp"
|
||||
#include "../mwvr/vrsession.hpp"
|
||||
#include "../mwvr/vrviewer.hpp"
|
||||
#include "../mwvr/vrgui.hpp"
|
||||
#include "../mwvr/vrinputmanager.hpp"
|
||||
#endif
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
|
@ -221,7 +229,11 @@ namespace MWGui
|
|||
}
|
||||
|
||||
SettingsWindow::SettingsWindow() :
|
||||
#ifdef USE_OPENXR
|
||||
WindowBase("openmw_settings_window_vr.layout"),
|
||||
#else
|
||||
WindowBase("openmw_settings_window.layout"),
|
||||
#endif
|
||||
mKeyboardMode(true)
|
||||
{
|
||||
bool terrain = Settings::Manager::getBool("distant terrain", "Terrain");
|
||||
|
@ -251,6 +263,12 @@ namespace MWGui
|
|||
getWidget(mLightsResetButton, "LightsResetButton");
|
||||
getWidget(mMaxLights, "MaxLights");
|
||||
|
||||
if (MWBase::Environment::get().getVrMode())
|
||||
{
|
||||
getWidget(mVRMirrorTextureEye, "VRMirrorTextureEye");
|
||||
getWidget(mVRLeftHudPosition, "VRLeftHudPosition");
|
||||
}
|
||||
|
||||
#ifndef WIN32
|
||||
// hide gamma controls since it currently does not work under Linux
|
||||
MyGUI::ScrollBar *gammaSlider;
|
||||
|
@ -282,6 +300,12 @@ namespace MWGui
|
|||
mKeyboardSwitch->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onKeyboardSwitchClicked);
|
||||
mControllerSwitch->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onControllerSwitchClicked);
|
||||
|
||||
if (MWBase::Environment::get().getVrMode())
|
||||
{
|
||||
mVRMirrorTextureEye->eventComboChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onVRMirrorTextureEyeChanged);
|
||||
mVRLeftHudPosition->eventComboChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onVRLeftHudPositionChanged);
|
||||
}
|
||||
|
||||
center();
|
||||
|
||||
mResetControlsButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onResetDefaultBindings);
|
||||
|
@ -312,6 +336,19 @@ namespace MWGui
|
|||
std::string tmip = Settings::Manager::getString("texture mipmap", "General");
|
||||
mTextureFilteringButton->setCaption(textureMipmappingToStr(tmip));
|
||||
|
||||
if (MWBase::Environment::get().getVrMode())
|
||||
{
|
||||
std::string mirrorTextureEye = Settings::Manager::getString("mirror texture eye", "VR");
|
||||
for (unsigned i = 0; i < mVRMirrorTextureEye->getItemCount(); i++)
|
||||
if (Misc::StringUtils::ciEqual(mirrorTextureEye, mVRMirrorTextureEye->getItem(i)))
|
||||
mVRMirrorTextureEye->setIndexSelected(i);
|
||||
|
||||
std::string leftHandHudPosition = Settings::Manager::getString("left hand hud position", "VR");
|
||||
for (unsigned i = 0; i < mVRLeftHudPosition->getItemCount(); i++)
|
||||
if (Misc::StringUtils::ciEqual(leftHandHudPosition, mVRLeftHudPosition->getItem(i)))
|
||||
mVRLeftHudPosition->setIndexSelected(i);
|
||||
}
|
||||
|
||||
int waterTextureSize = Settings::Manager::getInt("rtt size", "Water");
|
||||
if (waterTextureSize >= 512)
|
||||
mWaterTextureSize->setIndexSelected(0);
|
||||
|
@ -392,6 +429,20 @@ namespace MWGui
|
|||
}
|
||||
}
|
||||
|
||||
void SettingsWindow::onVRMirrorTextureEyeChanged(MyGUI::ComboBox* _sender, size_t pos)
|
||||
{
|
||||
auto setting = Misc::StringUtils::lowerCase(_sender->getItem(pos));
|
||||
Settings::Manager::setString("mirror texture eye", "VR", setting);
|
||||
apply();
|
||||
}
|
||||
|
||||
void SettingsWindow::onVRLeftHudPositionChanged(MyGUI::ComboBox* _sender, size_t pos)
|
||||
{
|
||||
auto setting = Misc::StringUtils::lowerCase(_sender->getItem(pos));
|
||||
Settings::Manager::setString("left hand hud position", "VR", setting);
|
||||
apply();
|
||||
}
|
||||
|
||||
void SettingsWindow::onWaterTextureSizeChanged(MyGUI::ComboBox* _sender, size_t pos)
|
||||
{
|
||||
int size = 0;
|
||||
|
@ -590,6 +641,15 @@ namespace MWGui
|
|||
MWBase::Environment::get().getWindowManager()->processChangedSettings(changed);
|
||||
MWBase::Environment::get().getInputManager()->processChangedSettings(changed);
|
||||
MWBase::Environment::get().getMechanicsManager()->processChangedSettings(changed);
|
||||
#ifdef USE_OPENXR
|
||||
if (MWBase::Environment::get().getVrMode())
|
||||
{
|
||||
MWVR::Environment::get().getSession()->processChangedSettings(changed);
|
||||
MWVR::Environment::get().getTrackingManager()->processChangedSettings(changed);
|
||||
MWVR::Environment::get().getViewer()->processChangedSettings(changed);
|
||||
MWVR::Environment::get().getGUIManager()->processChangedSettings(changed);
|
||||
}
|
||||
#endif
|
||||
Settings::Manager::resetPendingChanges();
|
||||
}
|
||||
|
||||
|
|
|
@ -22,6 +22,10 @@ namespace MWGui
|
|||
MyGUI::TabControl* mSettingsTab;
|
||||
MyGUI::Button* mOkButton;
|
||||
|
||||
// VR
|
||||
MyGUI::ComboBox* mVRLeftHudPosition;
|
||||
MyGUI::ComboBox* mVRMirrorTextureEye;
|
||||
|
||||
// graphics
|
||||
MyGUI::ListBox* mResolutionList;
|
||||
MyGUI::Button* mFullscreenButton;
|
||||
|
@ -53,6 +57,9 @@ namespace MWGui
|
|||
void onResolutionCancel();
|
||||
void highlightCurrentResolution();
|
||||
|
||||
void onVRMirrorTextureEyeChanged(MyGUI::ComboBox* _sender, size_t pos);
|
||||
void onVRLeftHudPositionChanged(MyGUI::ComboBox* _sender, size_t pos);
|
||||
|
||||
void onWaterTextureSizeChanged(MyGUI::ComboBox* _sender, size_t pos);
|
||||
void onWaterReflectionDetailChanged(MyGUI::ComboBox* _sender, size_t pos);
|
||||
|
||||
|
|
|
@ -65,8 +65,14 @@ namespace MWGui
|
|||
|
||||
int w=2;
|
||||
|
||||
for(auto rit = effects.rbegin(); rit != effects.rend(); rit++)
|
||||
{
|
||||
auto& effectInfoPair = *rit;
|
||||
#if 0
|
||||
// in VR mode, the effect box grows to the right so we want to invert the order to avoid reordering effects.
|
||||
for (auto& effectInfoPair : effects)
|
||||
{
|
||||
#endif
|
||||
const int effectId = effectInfoPair.first;
|
||||
const ESM::MagicEffect* effect =
|
||||
MWBase::Environment::get().getWorld ()->getStore ().get<ESM::MagicEffect>().find(effectId);
|
||||
|
@ -194,7 +200,10 @@ namespace MWGui
|
|||
s = 0;
|
||||
int diff = parent->getWidth() - s;
|
||||
parent->setSize(s, parent->getHeight());
|
||||
#ifndef USE_OPENXR
|
||||
// in VR mode, the effect box grows to the right and does not need repositioning
|
||||
parent->setPosition(parent->getLeft()+diff, parent->getTop());
|
||||
#endif
|
||||
}
|
||||
|
||||
// hide inactive effects
|
||||
|
|
|
@ -40,7 +40,11 @@ namespace MWGui
|
|||
{
|
||||
|
||||
SpellWindow::SpellWindow(DragAndDrop* drag)
|
||||
#ifdef USE_OPENXR
|
||||
: WindowPinnableBase("openmw_spell_window_vr.layout")
|
||||
#else
|
||||
: WindowPinnableBase("openmw_spell_window.layout")
|
||||
#endif
|
||||
, NoDrop(drag, mMainWidget)
|
||||
, mSpellView(nullptr)
|
||||
, mUpdateTimer(0.0f)
|
||||
|
|
|
@ -26,7 +26,11 @@
|
|||
namespace MWGui
|
||||
{
|
||||
StatsWindow::StatsWindow (DragAndDrop* drag)
|
||||
#ifdef USE_OPENXR
|
||||
: WindowPinnableBase("openmw_stats_window_vr.layout")
|
||||
#else
|
||||
: WindowPinnableBase("openmw_stats_window.layout")
|
||||
#endif
|
||||
, NoDrop(drag, mMainWidget)
|
||||
, mSkillView(nullptr)
|
||||
, mMajorSkills()
|
||||
|
|
|
@ -46,7 +46,7 @@ namespace MWGui
|
|||
void setExpelled (const std::set<std::string>& expelled);
|
||||
void setBirthSign (const std::string &signId);
|
||||
|
||||
void onWindowResize(MyGUI::Window* window);
|
||||
void onWindowResize(MyGUI::Window* window) override;
|
||||
void onMouseWheel(MyGUI::Widget* _sender, int _rel);
|
||||
|
||||
MyGUI::Widget* mLeftPane;
|
||||
|
|
|
@ -30,7 +30,11 @@ namespace MWGui
|
|||
std::string ToolTips::sSchoolNames[] = {"#{sSchoolAlteration}", "#{sSchoolConjuration}", "#{sSchoolDestruction}", "#{sSchoolIllusion}", "#{sSchoolMysticism}", "#{sSchoolRestoration}"};
|
||||
|
||||
ToolTips::ToolTips() :
|
||||
#ifdef USE_OPENXR
|
||||
Layout("openmw_tooltips_vr.layout")
|
||||
#else
|
||||
Layout("openmw_tooltips.layout")
|
||||
#endif
|
||||
, mFocusToolTipX(0.0)
|
||||
, mFocusToolTipY(0.0)
|
||||
, mHorizontalScrollIndex(0)
|
||||
|
@ -52,7 +56,9 @@ namespace MWGui
|
|||
mDynamicToolTipBox->setNeedMouseFocus(false);
|
||||
mMainWidget->setNeedMouseFocus(false);
|
||||
|
||||
mDelay = Settings::Manager::getFloat("tooltip delay", "GUI");
|
||||
// Tooltip delay is not useful in vr as a player cannot be perfectly still.
|
||||
if (!MWBase::Environment::get().getVrMode())
|
||||
mDelay = Settings::Manager::getFloat("tooltip delay", "GUI");
|
||||
mRemainingDelay = mDelay;
|
||||
|
||||
for (unsigned int i=0; i < mMainWidget->getChildCount(); ++i)
|
||||
|
|
|
@ -58,7 +58,11 @@ namespace
|
|||
namespace MWGui
|
||||
{
|
||||
TradeWindow::TradeWindow()
|
||||
#ifdef USE_OPENXR
|
||||
: WindowBase("openmw_trade_window_vr.layout")
|
||||
#else
|
||||
: WindowBase("openmw_trade_window.layout")
|
||||
#endif
|
||||
, mSortModel(nullptr)
|
||||
, mTradeModel(nullptr)
|
||||
, mItemToSell(-1)
|
||||
|
|
|
@ -12,6 +12,11 @@
|
|||
#include "draganddrop.hpp"
|
||||
#include "exposedwindow.hpp"
|
||||
|
||||
#ifdef USE_OPENXR
|
||||
#include "../mwvr/vrgui.hpp"
|
||||
#include "../mwvr/vrenvironment.hpp"
|
||||
#endif
|
||||
|
||||
using namespace MWGui;
|
||||
|
||||
WindowBase::WindowBase(const std::string& parLayout)
|
||||
|
@ -55,6 +60,19 @@ void WindowBase::setVisible(bool visible)
|
|||
onOpen();
|
||||
else if (wasVisible)
|
||||
onClose();
|
||||
|
||||
#ifdef USE_OPENXR
|
||||
// Check that onOpen/onClose didn't reverse the change before forwarding it
|
||||
// to the VR GUI manager.
|
||||
if (this->isVisible() == visible)
|
||||
{
|
||||
auto* vrGUIManager = MWVR::Environment::get().getGUIManager();
|
||||
if (!vrGUIManager)
|
||||
// May end up here before before rendering has been fully set up
|
||||
return;
|
||||
vrGUIManager->setVisible(this, visible);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
bool WindowBase::isVisible()
|
||||
|
@ -109,26 +127,42 @@ void NoDrop::onFrame(float dt)
|
|||
|
||||
MyGUI::IntPoint mousePos = MyGUI::InputManager::getInstance().getMousePosition();
|
||||
|
||||
#ifdef USE_OPENXR
|
||||
// Since VR mode stretches some windows to full screen, the usual outside condition
|
||||
// won't work
|
||||
mTransparent = false;
|
||||
#endif
|
||||
if (mDrag->mIsOnDragAndDrop)
|
||||
{
|
||||
MyGUI::Widget* focus = MyGUI::InputManager::getInstance().getMouseFocusWidget();
|
||||
while (focus && focus != mWidget)
|
||||
{
|
||||
focus = focus->getParent();
|
||||
}
|
||||
|
||||
if (focus == mWidget)
|
||||
{
|
||||
mTransparent = true;
|
||||
}
|
||||
}
|
||||
if (!mWidget->getAbsoluteCoord().inside(mousePos))
|
||||
mTransparent = false;
|
||||
|
||||
if (mTransparent)
|
||||
{
|
||||
#ifndef USE_OPENXR
|
||||
// These makes focus null, which messes up the logic for VR
|
||||
// since i reset mTransparent to false every update.
|
||||
// TODO: Is there a cleaner way?
|
||||
mWidget->setNeedMouseFocus(false); // Allow click-through
|
||||
#endif
|
||||
setAlpha(std::max(0.13f, mWidget->getAlpha() - dt*5));
|
||||
}
|
||||
else
|
||||
{
|
||||
mWidget->setNeedMouseFocus(true);
|
||||
#ifndef USE_OPENXR
|
||||
mWidget->setNeedMouseFocus(true); // Allow click-through
|
||||
#endif
|
||||
setAlpha(std::min(1.0f, mWidget->getAlpha() + dt*5));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,6 +47,9 @@ namespace MWGui
|
|||
/// Called when GUI viewport changes size
|
||||
virtual void onResChange(int width, int height) {}
|
||||
|
||||
/// Called when Window widget changes in size
|
||||
virtual void onWindowResize(MyGUI::Window* window) {}
|
||||
|
||||
protected:
|
||||
virtual void onTitleDoubleClicked();
|
||||
|
||||
|
|
|
@ -131,6 +131,16 @@
|
|||
#include "keyboardnavigation.hpp"
|
||||
#include "resourceskin.hpp"
|
||||
|
||||
#ifdef USE_OPENXR
|
||||
#include "../mwvr/vrmetamenu.hpp"
|
||||
#include "../mwvr/vrenvironment.hpp"
|
||||
#include "../mwvr/vrgui.hpp"
|
||||
#include "../mwvr/vrvirtualkeyboard.hpp"
|
||||
#include "../mwvr/vrviewer.hpp"
|
||||
#include "../mwvr/vrsession.hpp"
|
||||
#include "../mwvr/vrtracking.hpp"
|
||||
#endif
|
||||
|
||||
namespace MWGui
|
||||
{
|
||||
WindowManager::WindowManager(
|
||||
|
@ -174,6 +184,8 @@ namespace MWGui
|
|||
, mScreenFader(nullptr)
|
||||
, mDebugWindow(nullptr)
|
||||
, mJailScreen(nullptr)
|
||||
, mVrMetaMenu(nullptr)
|
||||
, mVirtualKeyboardManager(nullptr)
|
||||
, mTranslationDataStorage (translationDataStorage)
|
||||
, mCharGen(nullptr)
|
||||
, mInputBlocker(nullptr)
|
||||
|
@ -184,6 +196,7 @@ namespace MWGui
|
|||
, mHudEnabled(true)
|
||||
, mCursorVisible(true)
|
||||
, mCursorActive(true)
|
||||
, mVideoEnabled(false)
|
||||
, mPlayerBounty(-1)
|
||||
, mGui(nullptr)
|
||||
, mGuiModes()
|
||||
|
@ -202,6 +215,11 @@ namespace MWGui
|
|||
mGuiPlatform = new osgMyGUI::Platform(viewer, guiRoot, resourceSystem->getImageManager(), mScalingFactor);
|
||||
mGuiPlatform->initialise(resourcePath, (boost::filesystem::path(logpath) / "MyGUI.log").generic_string());
|
||||
|
||||
|
||||
#ifdef USE_OPENXR
|
||||
mGuiPlatform->getRenderManagerPtr()->setViewSize(1024, 1024);
|
||||
#endif
|
||||
|
||||
mGui = new MyGUI::Gui;
|
||||
mGui->initialise("");
|
||||
|
||||
|
@ -236,7 +254,17 @@ namespace MWGui
|
|||
|
||||
MyGUI::FactoryManager::getInstance().registerFactory<ResourceImageSetPointerFix>("Resource", "ResourceImageSetPointer");
|
||||
MyGUI::FactoryManager::getInstance().registerFactory<AutoSizedResourceSkin>("Resource", "AutoSizedResourceSkin");
|
||||
|
||||
#ifdef USE_OPENXR
|
||||
if (MWBase::Environment::get().getVrMode())
|
||||
MWVR::VRGUIManager::registerMyGUIFactories();
|
||||
#endif
|
||||
|
||||
#ifdef USE_OPENXR
|
||||
MyGUI::ResourceManager::getInstance().load("core_vr.xml");
|
||||
#else
|
||||
MyGUI::ResourceManager::getInstance().load("core.xml");
|
||||
#endif
|
||||
WindowManager::loadUserFonts();
|
||||
|
||||
bool keyboardNav = Settings::Manager::getBool("keyboard navigation", "GUI");
|
||||
|
@ -269,7 +297,7 @@ namespace MWGui
|
|||
mVideoBackground->setNeedMouseFocus(true);
|
||||
mVideoBackground->setNeedKeyFocus(true);
|
||||
|
||||
mVideoWidget = mVideoBackground->createWidgetReal<VideoWidget>("ImageBox", 0,0,1,1, MyGUI::Align::Default);
|
||||
mVideoWidget = mVideoBackground->createWidgetReal<VideoWidget>("ImageBox", 0,0,1,1, MyGUI::Align::Default, "InputBlocker");
|
||||
mVideoWidget->setNeedMouseFocus(true);
|
||||
mVideoWidget->setNeedKeyFocus(true);
|
||||
mVideoWidget->setVFS(resourceSystem->getVFS());
|
||||
|
@ -283,7 +311,7 @@ namespace MWGui
|
|||
|
||||
mShowOwned = Settings::Manager::getInt("show owned", "Game");
|
||||
|
||||
mVideoWrapper = new SDLUtil::VideoWrapper(window, viewer);
|
||||
mVideoWrapper = new SDLUtil::VideoWrapper(window, viewer, MWBase::Environment::get().getVrMode() != true);
|
||||
mVideoWrapper->setGammaContrast(Settings::Manager::getFloat("gamma", "Video"),
|
||||
Settings::Manager::getFloat("contrast", "Video"));
|
||||
|
||||
|
@ -305,6 +333,14 @@ namespace MWGui
|
|||
|
||||
mDragAndDrop = new DragAndDrop();
|
||||
|
||||
#ifdef USE_OPENXR
|
||||
mVrMetaMenu = new MWVR::VrMetaMenu(w, h);
|
||||
mWindows.push_back(mVrMetaMenu);
|
||||
mGuiModeStates[GM_VrMetaMenu] = GuiModeState(mVrMetaMenu);
|
||||
|
||||
mVirtualKeyboardManager = new MWVR::VirtualKeyboardManager;
|
||||
#endif
|
||||
|
||||
Recharge* recharge = new Recharge();
|
||||
mGuiModeStates[GM_Recharge] = GuiModeState(recharge);
|
||||
mWindows.push_back(recharge);
|
||||
|
@ -582,18 +618,28 @@ namespace MWGui
|
|||
|
||||
void WindowManager::enableScene(bool enable)
|
||||
{
|
||||
|
||||
unsigned int disablemask = MWRender::Mask_GUI|MWRender::Mask_PreCompile;
|
||||
|
||||
// VR mode needs to render the 3D gui
|
||||
if (MWBase::Environment::get().getVrMode())
|
||||
disablemask = MWRender::Mask_Pointer | MWRender::Mask_3DGUI | MWRender::Mask_PreCompile | MWRender::Mask_RenderToTexture;
|
||||
|
||||
if (!enable && mViewer->getCamera()->getCullMask() != disablemask)
|
||||
{
|
||||
mOldUpdateMask = mViewer->getUpdateVisitor()->getTraversalMask();
|
||||
mOldCullMask = mViewer->getCamera()->getCullMask();
|
||||
mViewer->getUpdateVisitor()->setTraversalMask(disablemask);
|
||||
mViewer->getCamera()->setCullMask(disablemask);
|
||||
mViewer->getCamera()->setCullMaskLeft(disablemask);
|
||||
mViewer->getCamera()->setCullMaskRight(disablemask);
|
||||
}
|
||||
else if (enable && mViewer->getCamera()->getCullMask() == disablemask)
|
||||
{
|
||||
mViewer->getUpdateVisitor()->setTraversalMask(mOldUpdateMask);
|
||||
mViewer->getCamera()->setCullMask(mOldCullMask);
|
||||
mViewer->getCamera()->setCullMaskLeft(mOldCullMask);
|
||||
mViewer->getCamera()->setCullMaskRight(mOldCullMask);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -732,6 +778,8 @@ namespace MWGui
|
|||
}
|
||||
}
|
||||
|
||||
mVideoEnabled = false;
|
||||
|
||||
popGuiMode();
|
||||
}
|
||||
|
||||
|
@ -766,11 +814,7 @@ namespace MWGui
|
|||
if (!mWindowVisible)
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
||||
else
|
||||
{
|
||||
mViewer->eventTraversal();
|
||||
mViewer->updateTraversal();
|
||||
mViewer->renderingTraversals();
|
||||
}
|
||||
viewerTraversals(false);
|
||||
// at the time this function is called we are in the middle of a frame,
|
||||
// so out of order calls are necessary to get a correct frameNumber for the next frame.
|
||||
// refer to the advance() and frame() order in Engine::go()
|
||||
|
@ -1173,6 +1217,9 @@ namespace MWGui
|
|||
|
||||
void WindowManager::windowResized(int x, int y)
|
||||
{
|
||||
#ifdef USE_OPENXR
|
||||
return;
|
||||
#endif
|
||||
// Note: this is a side effect of resolution change or window resize.
|
||||
// There is no need to track these changes.
|
||||
Settings::Manager::setInt("resolution x", "Video", x);
|
||||
|
@ -1534,6 +1581,11 @@ namespace MWGui
|
|||
updateVisible();
|
||||
}
|
||||
|
||||
DragAndDrop& WindowManager::getDragAndDrop(void)
|
||||
{
|
||||
return *mDragAndDrop;
|
||||
}
|
||||
|
||||
void WindowManager::forceHide(GuiWindow wnd)
|
||||
{
|
||||
mForceHidden = (GuiWindow)(mForceHidden | wnd);
|
||||
|
@ -1886,6 +1938,7 @@ namespace MWGui
|
|||
|
||||
void WindowManager::playVideo(const std::string &name, bool allowSkipping)
|
||||
{
|
||||
mVideoEnabled = true;
|
||||
mVideoWidget->playVideo("video\\" + name);
|
||||
|
||||
mVideoWidget->eventKeyButtonPressed.clear();
|
||||
|
@ -1906,6 +1959,11 @@ namespace MWGui
|
|||
|
||||
mVideoBackground->setVisible(true);
|
||||
|
||||
#ifdef USE_OPENXR
|
||||
auto* vrGuiManager = MWVR::Environment::get().getGUIManager();
|
||||
vrGuiManager->insertLayer(mVideoBackground->getLayer()->getName());
|
||||
#endif
|
||||
|
||||
bool cursorWasVisible = mCursorVisible;
|
||||
setCursorVisible(false);
|
||||
|
||||
|
@ -1915,7 +1973,7 @@ namespace MWGui
|
|||
);
|
||||
|
||||
Misc::FrameRateLimiter frameRateLimiter = Misc::makeFrameRateLimiter(MWBase::Environment::get().getFrameRateLimit());
|
||||
while (mVideoWidget->update() && !MWBase::Environment::get().getStateManager()->hasQuitRequest())
|
||||
while (mVideoEnabled && mVideoWidget->update() && !MWBase::Environment::get().getStateManager()->hasQuitRequest())
|
||||
{
|
||||
const double dt = std::chrono::duration_cast<std::chrono::duration<double>>(frameRateLimiter.getLastFrameDuration()).count();
|
||||
|
||||
|
@ -1931,9 +1989,7 @@ namespace MWGui
|
|||
if (mVideoWidget->isPaused())
|
||||
mVideoWidget->resume();
|
||||
|
||||
mViewer->eventTraversal();
|
||||
mViewer->updateTraversal();
|
||||
mViewer->renderingTraversals();
|
||||
viewerTraversals(false);
|
||||
}
|
||||
// at the time this function is called we are in the middle of a frame,
|
||||
// so out of order calls are necessary to get a correct frameNumber for the next frame.
|
||||
|
@ -1953,7 +2009,16 @@ namespace MWGui
|
|||
// Restore normal rendering
|
||||
updateVisible();
|
||||
|
||||
#ifdef USE_OPENXR
|
||||
vrGuiManager->removeLayer(mVideoBackground->getLayer()->getName());
|
||||
#endif
|
||||
mVideoBackground->setVisible(false);
|
||||
mVideoEnabled = false;
|
||||
}
|
||||
|
||||
bool WindowManager::isPlayingVideo(void) const
|
||||
{
|
||||
return mVideoEnabled;
|
||||
}
|
||||
|
||||
void WindowManager::sizeVideo(int screenWidth, int screenHeight)
|
||||
|
@ -2370,6 +2435,15 @@ namespace MWGui
|
|||
return MyGUI::InputManager::getInstance().injectKeyRelease(key);
|
||||
}
|
||||
|
||||
void WindowManager::viewerTraversals(bool updateWindowManager)
|
||||
{
|
||||
mViewer->eventTraversal();
|
||||
mViewer->updateTraversal();
|
||||
if (updateWindowManager)
|
||||
MWBase::Environment::get().getWorld()->updateWindowManager();
|
||||
mViewer->renderingTraversals();
|
||||
}
|
||||
|
||||
void WindowManager::GuiModeState::update(bool visible)
|
||||
{
|
||||
for (unsigned int i=0; i<mWindows.size(); ++i)
|
||||
|
|
|
@ -81,6 +81,7 @@ namespace osgMyGUI
|
|||
namespace Gui
|
||||
{
|
||||
class FontLoader;
|
||||
class VirtualKeyboardManager;
|
||||
}
|
||||
|
||||
namespace MWRender
|
||||
|
@ -88,6 +89,11 @@ namespace MWRender
|
|||
class LocalMap;
|
||||
}
|
||||
|
||||
namespace MWVR
|
||||
{
|
||||
class VrMetaMenu;
|
||||
}
|
||||
|
||||
namespace MWGui
|
||||
{
|
||||
class WindowBase;
|
||||
|
@ -147,6 +153,7 @@ namespace MWGui
|
|||
/// @note This method will block until the video finishes playing
|
||||
/// (and will continually update the window while doing so)
|
||||
void playVideo(const std::string& name, bool allowSkipping) override;
|
||||
bool isPlayingVideo(void) const override;
|
||||
|
||||
/// Warning: do not use MyGUI::InputManager::setKeyFocusWidget directly. Instead use this.
|
||||
void setKeyFocusWidget (MyGUI::Widget* widget) override;
|
||||
|
@ -171,7 +178,7 @@ namespace MWGui
|
|||
|
||||
void forceHide(MWGui::GuiWindow wnd) override;
|
||||
void unsetForceHide(MWGui::GuiWindow wnd) override;
|
||||
|
||||
DragAndDrop& getDragAndDrop(void) override;
|
||||
/// Disallow all inventory mode windows
|
||||
void disallowAll() override;
|
||||
|
||||
|
@ -478,10 +485,12 @@ namespace MWGui
|
|||
bool injectKeyPress(MyGUI::KeyCode key, unsigned int text, bool repeat=false) override;
|
||||
bool injectKeyRelease(MyGUI::KeyCode key) override;
|
||||
|
||||
void viewerTraversals(bool updateWindowManager) override;
|
||||
private:
|
||||
unsigned int mOldUpdateMask; unsigned int mOldCullMask;
|
||||
|
||||
const MWWorld::ESMStore* mStore;
|
||||
bool mVRMode;
|
||||
Resource::ResourceSystem* mResourceSystem;
|
||||
osg::ref_ptr<SceneUtil::WorkQueue> mWorkQueue;
|
||||
|
||||
|
@ -535,6 +544,9 @@ namespace MWGui
|
|||
ScreenFader* mScreenFader;
|
||||
DebugWindow* mDebugWindow;
|
||||
JailScreen* mJailScreen;
|
||||
MWVR::VrMetaMenu* mVrMetaMenu;
|
||||
|
||||
Gui::VirtualKeyboardManager* mVirtualKeyboardManager;
|
||||
|
||||
/*
|
||||
Start of tes3mp addition
|
||||
|
@ -562,6 +574,7 @@ namespace MWGui
|
|||
bool mHudEnabled;
|
||||
bool mCursorVisible;
|
||||
bool mCursorActive;
|
||||
bool mVideoEnabled;
|
||||
|
||||
int mPlayerBounty;
|
||||
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
End of tes3mp addition
|
||||
*/
|
||||
|
||||
#include <components/debug/debuglog.hpp>
|
||||
|
||||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwbase/inputmanager.hpp"
|
||||
#include "../mwbase/windowmanager.hpp"
|
||||
|
@ -59,6 +61,12 @@ namespace MWInput
|
|||
}
|
||||
};
|
||||
|
||||
ICS::InputControlSystem&
|
||||
BindingsManager::ics()
|
||||
{
|
||||
return *mInputBinder;
|
||||
}
|
||||
|
||||
class BindingsListener :
|
||||
public ICS::ChannelListener,
|
||||
public ICS::DetectingBindingListener
|
||||
|
|
|
@ -7,6 +7,11 @@
|
|||
|
||||
#include <components/sdlutil/events.hpp>
|
||||
|
||||
namespace ICS
|
||||
{
|
||||
class InputControlSystem;
|
||||
}
|
||||
|
||||
namespace MWInput
|
||||
{
|
||||
class BindingsListener;
|
||||
|
@ -62,6 +67,8 @@ namespace MWInput
|
|||
|
||||
void actionValueChanged(int action, float currentValue, float previousValue);
|
||||
|
||||
ICS::InputControlSystem& ics();
|
||||
|
||||
private:
|
||||
void setupSDLKeyMappings();
|
||||
|
||||
|
|
|
@ -95,14 +95,16 @@ namespace MWInput
|
|||
void executeAction(int action) override;
|
||||
|
||||
bool controlsDisabled() override { return mControlsDisabled; }
|
||||
void applyHapticsLeftHand(float intensity) override {};
|
||||
void applyHapticsRightHand(float intensity) override {};
|
||||
|
||||
private:
|
||||
protected:
|
||||
void convertMousePosForMyGUI(int& x, int& y);
|
||||
|
||||
void handleGuiArrowKey(int action);
|
||||
|
||||
void quickKey(int index);
|
||||
void showQuickKeysMenu();
|
||||
//void quickKey(int index);
|
||||
//void showQuickKeysMenu();
|
||||
|
||||
void loadKeyDefaults(bool force = false);
|
||||
void loadControllerDefaults(bool force = false);
|
||||
|
|
|
@ -63,6 +63,9 @@ namespace MWInput
|
|||
|
||||
void MouseManager::mouseMoved(const SDLUtil::MouseMotionEvent &arg)
|
||||
{
|
||||
if (MWBase::Environment::get().getVrMode())
|
||||
return;
|
||||
|
||||
mBindingsManager->mouseMoved(arg);
|
||||
|
||||
MWBase::InputManager* input = MWBase::Environment::get().getInputManager();
|
||||
|
@ -235,6 +238,8 @@ namespace MWInput
|
|||
|
||||
void MouseManager::injectMouseMove(float xMove, float yMove, float mouseWheelMove)
|
||||
{
|
||||
if (MWBase::Environment::get().getVrMode())
|
||||
return;
|
||||
mGuiCursorX += xMove;
|
||||
mGuiCursorY += yMove;
|
||||
mMouseWheel += mouseWheelMove;
|
||||
|
@ -251,4 +256,11 @@ namespace MWInput
|
|||
float uiScale = MWBase::Environment::get().getWindowManager()->getScalingFactor();
|
||||
mInputWrapper->warpMouse(static_cast<int>(mGuiCursorX*uiScale), static_cast<int>(mGuiCursorY*uiScale));
|
||||
}
|
||||
|
||||
void MouseManager::setMousePosition(int x, int y)
|
||||
{
|
||||
float uiScale = MWBase::Environment::get().getWindowManager()->getScalingFactor();
|
||||
mGuiCursorX = x / uiScale;
|
||||
mGuiCursorY = y / uiScale;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,6 +38,9 @@ namespace MWInput
|
|||
void setMouseLookEnabled(bool enabled) { mMouseLookEnabled = enabled; }
|
||||
void setGuiCursorEnabled(bool enabled) { mGuiCursorEnabled = enabled; }
|
||||
|
||||
// Used to override mouse position when using controllers not through SDL, such as OpenXR.
|
||||
void setMousePosition(int x, int y);
|
||||
|
||||
private:
|
||||
bool mInvertX;
|
||||
bool mInvertY;
|
||||
|
|
|
@ -57,6 +57,7 @@
|
|||
#include "../mwworld/inventorystore.hpp"
|
||||
#include "../mwworld/esmstore.hpp"
|
||||
#include "../mwworld/player.hpp"
|
||||
#include "../mwrender/npcanimation.hpp"
|
||||
|
||||
#include "aicombataction.hpp"
|
||||
#include "movement.hpp"
|
||||
|
@ -66,6 +67,12 @@
|
|||
#include "actorutil.hpp"
|
||||
#include "spellcasting.hpp"
|
||||
|
||||
#ifdef USE_OPENXR
|
||||
#include "../mwvr/vrenvironment.hpp"
|
||||
#include "../mwvr/vranimation.hpp"
|
||||
#include "../mwvr/vrutil.hpp"
|
||||
#endif
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
|
@ -1106,7 +1113,7 @@ void CharacterController::handleTextKey(const std::string &groupname, SceneUtil:
|
|||
else if (groupname == "attack3" || groupname == "swimattack3")
|
||||
mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Thrust);
|
||||
else
|
||||
mPtr.getClass().hit(mPtr, mAttackStrength);
|
||||
mPtr.getClass().hit(mPtr, mAttackStrength, -1);
|
||||
}
|
||||
else if (!groupname.empty()
|
||||
&& (groupname.compare(0, groupname.size()-1, "attack") == 0 || groupname.compare(0, groupname.size()-1, "swimattack") == 0)
|
||||
|
@ -1720,9 +1727,13 @@ bool CharacterController::updateWeaponState(CharacterState& idle)
|
|||
{
|
||||
MWWorld::ContainerStoreIterator weapon = mPtr.getClass().getInventoryStore(mPtr).getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
|
||||
MWWorld::Ptr item = *weapon;
|
||||
// TODO: this will only work for the player, and needs to be fixed if NPCs should ever use lockpicks/probes.
|
||||
MWWorld::Ptr target = MWBase::Environment::get().getWorld()->getFacedObject();
|
||||
std::string resultMessage, resultSound;
|
||||
// TODO: this will only work for the player, and needs to be fixed if NPCs should ever use lockpicks/probes.
|
||||
#ifdef USE_OPENXR
|
||||
MWWorld::Ptr target = MWVR::Util::getWeaponTarget().first;
|
||||
#else
|
||||
MWWorld::Ptr target = MWBase::Environment::get().getWorld()->getFacedObject();
|
||||
#endif
|
||||
|
||||
if(!target.isEmpty())
|
||||
{
|
||||
|
@ -2663,6 +2674,18 @@ void CharacterController::update(float duration)
|
|||
mSkipAnim = false;
|
||||
|
||||
mAnimation->enableHeadAnimation(cls.isActor() && !cls.getCreatureStats(mPtr).isDead());
|
||||
|
||||
#ifdef USE_OPENXR
|
||||
if (isPlayer)
|
||||
{
|
||||
auto disabled = MWBase::Environment::get().getWorld()->getPlayer().isDisabled();
|
||||
auto animation = static_cast<MWRender::NpcAnimation*>(mAnimation);
|
||||
if (disabled)
|
||||
animation->setViewMode(MWRender::NpcAnimation::VM_VRNormal);
|
||||
else
|
||||
animation->setViewMode(MWRender::NpcAnimation::VM_VRFirstPerson);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void CharacterController::persistAnimationState()
|
||||
|
|
|
@ -294,7 +294,7 @@ namespace MWMechanics
|
|||
End of tes3mp addition
|
||||
*/
|
||||
|
||||
victim.getClass().onHit(victim, damage, false, projectile, attacker, osg::Vec3f(), false);
|
||||
victim.getClass().onHit(victim, damage, false, projectile, attacker, osg::Vec3f(), false, attackStrength);
|
||||
MWMechanics::reduceWeaponCondition(damage, false, weapon, attacker);
|
||||
return;
|
||||
}
|
||||
|
@ -354,7 +354,7 @@ namespace MWMechanics
|
|||
victim.getClass().getContainerStore(victim).add(projectile, 1, victim);
|
||||
}
|
||||
|
||||
victim.getClass().onHit(victim, damage, true, projectile, attacker, hitPosition, true);
|
||||
victim.getClass().onHit(victim, damage, true, projectile, attacker, hitPosition, true, attackStrength);
|
||||
}
|
||||
/*
|
||||
Start of tes3mp addition
|
||||
|
|
|
@ -14,6 +14,15 @@
|
|||
#include "../mwworld/esmstore.hpp"
|
||||
#include "../mwworld/refdata.hpp"
|
||||
|
||||
#ifdef USE_OPENXR
|
||||
#include "../mwvr/vrsession.hpp"
|
||||
#include "../mwvr/vrcamera.hpp"
|
||||
#include "../mwvr/vrenvironment.hpp"
|
||||
#include "../mwrender/renderingmanager.hpp"
|
||||
#include "../mwworld/player.hpp"
|
||||
#endif
|
||||
#include "../mwmechanics/actorutil.hpp"
|
||||
|
||||
#include "actor.hpp"
|
||||
#include "collisiontype.hpp"
|
||||
#include "constants.hpp"
|
||||
|
@ -119,7 +128,8 @@ namespace MWPhysics
|
|||
WorldFrameData& worldData)
|
||||
{
|
||||
auto* physicActor = actor.mActorRaw;
|
||||
const ESM::Position& refpos = actor.mRefpos;
|
||||
ESM::Position refpos = actor.mRefpos;
|
||||
|
||||
// Early-out for totally static creatures
|
||||
// (Not sure if gravity should still apply?)
|
||||
{
|
||||
|
@ -128,6 +138,36 @@ namespace MWPhysics
|
|||
return;
|
||||
}
|
||||
|
||||
const bool isPlayer = (physicActor->getPtr() == MWMechanics::getPlayer());
|
||||
auto* world = MWBase::Environment::get().getWorld();
|
||||
|
||||
// In VR, player should move according to current direction of
|
||||
// a selected limb, rather than current orientation of camera.
|
||||
#ifdef USE_OPENXR
|
||||
// Regarding this and the duplicate movement solver later in this method:
|
||||
// As my two edits in this code are obviously hacks, I could use feedback on how i could implement
|
||||
// VR movement mechanics as not-a-hack. This hack, for instance, does not trigger movement animations
|
||||
// and will obviously be a poor fit for a future merge with tes3mp.
|
||||
|
||||
// The exact mechanics are:
|
||||
// 1. When moving with the controller, the player moves in the direction he is currently pointing his left controller.
|
||||
// 2. The game should seek to eliminate all distance between the player character and the player's position within VR,
|
||||
// without teleporting the player or ignoring collisions.
|
||||
|
||||
// I assume (1.) is easily solved, i just haven't taken the effort to study openmw's code enough.
|
||||
// But 2. is not so obvious. I guess it's doable if i compute the direction between current position and the player's
|
||||
// position in the VR stage, and just let it catch up at the character's own move speed, but it still needs to reach the position as exactly as possible.
|
||||
if (isPlayer)
|
||||
{
|
||||
auto tm = MWVR::Environment::get().getTrackingManager();
|
||||
float pitch = 0.f;
|
||||
float yaw = 0.f;
|
||||
tm->movementAngles(yaw, pitch);
|
||||
refpos.rot[0] += pitch;
|
||||
refpos.rot[2] += yaw;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Reset per-frame data
|
||||
physicActor->setWalkingOnWater(false);
|
||||
// Anything to collide with?
|
||||
|
@ -148,7 +188,7 @@ namespace MWPhysics
|
|||
osg::Vec3f halfExtents = physicActor->getHalfExtents();
|
||||
actor.mPosition.z() += halfExtents.z(); // vanilla-accurate
|
||||
|
||||
static const float fSwimHeightScale = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fSwimHeightScale")->mValue.getFloat();
|
||||
static const float fSwimHeightScale = world->getStore().get<ESM::GameSetting>().find("fSwimHeightScale")->mValue.getFloat();
|
||||
float swimlevel = actor.mWaterlevel + halfExtents.z() - (physicActor->getRenderingHalfExtents().z() * 2 * fSwimHeightScale);
|
||||
|
||||
ActorTracer tracer;
|
||||
|
@ -184,13 +224,103 @@ namespace MWPhysics
|
|||
{
|
||||
osg::Vec3f stormDirection = worldData.mStormDirection;
|
||||
float angleDegrees = osg::RadiansToDegrees(std::acos(stormDirection * velocity / (stormDirection.length() * velocity.length())));
|
||||
static const float fStromWalkMult = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fStromWalkMult")->mValue.getFloat();
|
||||
static const float fStromWalkMult = world->getStore().get<ESM::GameSetting>().find("fStromWalkMult")->mValue.getFloat();
|
||||
velocity *= 1.f-(fStromWalkMult * (angleDegrees/180.f));
|
||||
}
|
||||
|
||||
Stepper stepper(collisionWorld, colobj);
|
||||
osg::Vec3f origVelocity = velocity;
|
||||
osg::Vec3f newPosition = actor.mPosition;
|
||||
|
||||
//#ifdef USE_OPENXR
|
||||
// // Catch the player character up to the real world position of the player.
|
||||
// // But only if play is not seated.
|
||||
// // TODO: This solution is a hack.
|
||||
// if (isPlayer)
|
||||
// {
|
||||
// bool shouldMove = true;
|
||||
// if (session && session->seatedPlay())
|
||||
// shouldMove = false;
|
||||
// if (world->getPlayer().isDisabled())
|
||||
// shouldMove = false;
|
||||
//
|
||||
// if (shouldMove)
|
||||
// {
|
||||
// auto* inputManager = reinterpret_cast<MWVR::VRCamera*>(MWBase::Environment::get().getWorld()->getRenderingManager().getCamera());
|
||||
//
|
||||
// osg::Vec3 headOffset = inputManager->headOffset();
|
||||
// osg::Vec3 trackingOffset = headOffset;
|
||||
// // Player's tracking height should not affect character position
|
||||
// trackingOffset.z() = 0;
|
||||
//
|
||||
// float remainingTime = time;
|
||||
// bool seenGround = physicActor->getOnGround() && !physicActor->getOnSlope() && !actor.mFlying;
|
||||
// float remainder = 1.f;
|
||||
//
|
||||
// for (int iterations = 0; iterations < sMaxIterations && remainingTime > 0.01f && remainder > 0.01; ++iterations)
|
||||
// {
|
||||
// osg::Vec3 toMove = trackingOffset * remainder;
|
||||
// osg::Vec3 nextpos = newPosition + toMove;
|
||||
//
|
||||
// if ((newPosition - nextpos).length2() > 0.0001)
|
||||
// {
|
||||
// // trace to where character would go if there were no obstructions
|
||||
// tracer.doTrace(colobj, newPosition, nextpos, collisionWorld);
|
||||
//
|
||||
// // check for obstructions
|
||||
// if (tracer.mFraction >= 1.0f)
|
||||
// {
|
||||
// newPosition = tracer.mEndPos; // ok to move, so set newPosition
|
||||
// remainder = 0.f;
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// // The current position and next position are nearly the same, so just exit.
|
||||
// // Note: Bullet can trigger an assert in debug modes if the positions
|
||||
// // are the same, since that causes it to attempt to normalize a zero
|
||||
// // length vector (which can also happen with nearly identical vectors, since
|
||||
// // precision can be lost due to any math Bullet does internally). Since we
|
||||
// // aren't performing any collision detection, we want to reject the next
|
||||
// // position, so that we don't slowly move inside another object.
|
||||
// remainder = 0.f;
|
||||
// break;
|
||||
// }
|
||||
//
|
||||
// if (isWalkableSlope(tracer.mPlaneNormal) && !actor.mFlying && newPosition.z() >= swimlevel)
|
||||
// seenGround = true;
|
||||
//
|
||||
// // We are touching something.
|
||||
// if (tracer.mFraction < 1E-9f)
|
||||
// {
|
||||
// // Try to separate by backing off slighly to unstuck the solver
|
||||
// osg::Vec3f backOff = (newPosition - tracer.mHitPoint) * 1E-2f;
|
||||
// newPosition += backOff;
|
||||
// }
|
||||
//
|
||||
// // We hit something. Check if we can step up.
|
||||
// float hitHeight = tracer.mHitPoint.z() - tracer.mEndPos.z() + halfExtents.z();
|
||||
// osg::Vec3f oldPosition = newPosition;
|
||||
// bool result = false;
|
||||
// if (hitHeight < sStepSizeUp && !isActor(tracer.mHitObject))
|
||||
// {
|
||||
// // Try to step up onto it.
|
||||
// // NOTE: stepMove does not allow stepping over, modifies newPosition if successful
|
||||
// result = stepper.step(newPosition, toMove, remainingTime, seenGround, iterations == 0);
|
||||
// remainder = remainingTime / time;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // Try not to lose any tracking
|
||||
// osg::Vec3 moved = newPosition - actor.mPosition;
|
||||
// headOffset.x() -= moved.x();
|
||||
// headOffset.y() -= moved.y();
|
||||
// inputManager->setHeadOffset(headOffset);
|
||||
// }
|
||||
// }
|
||||
//#endif
|
||||
|
||||
/*
|
||||
* A loop to find newPosition using tracer, if successful different from the starting position.
|
||||
* nextpos is the local variable used to find potential newPosition, using velocity and remainingTime
|
||||
|
|
|
@ -42,6 +42,7 @@
|
|||
#include "../mwworld/cellstore.hpp"
|
||||
|
||||
#include "../mwmechanics/character.hpp" // FIXME: for MWMechanics::Priority
|
||||
#include "../mwmechanics/actorutil.hpp"
|
||||
|
||||
#include "vismask.hpp"
|
||||
#include "util.hpp"
|
||||
|
@ -1052,8 +1053,56 @@ namespace MWRender
|
|||
return mNodeMap;
|
||||
}
|
||||
|
||||
static bool vrOverride(const std::string& groupname, const std::string& bone)
|
||||
{
|
||||
#ifdef USE_OPENXR
|
||||
// TODO: It's difficult to design a good override system when
|
||||
// I don't have a good understanding of the animation code. So for
|
||||
// now i just hardcode blocking of updaters for nodes that should not be animated in VR.
|
||||
// Add any bone+groupname pair that is messing with Vr comfort here.
|
||||
using Overrides = std::set<std::string>;
|
||||
using GroupOverrides = std::map<std::string, Overrides>;
|
||||
static GroupOverrides sVrOverrides =
|
||||
{
|
||||
{
|
||||
"crossbow",
|
||||
{
|
||||
"weapon bone"
|
||||
}
|
||||
},
|
||||
{
|
||||
"throwweapon",
|
||||
{
|
||||
"weapon bone"
|
||||
}
|
||||
},
|
||||
{
|
||||
"bowandarrow",
|
||||
{
|
||||
"weapon bone"
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
bool override = false;
|
||||
auto find = sVrOverrides.find(groupname);
|
||||
if (find != sVrOverrides.end())
|
||||
{
|
||||
override = !!find->second.count(bone);
|
||||
}
|
||||
|
||||
return override;
|
||||
#else
|
||||
(void)bone;
|
||||
(void)groupname;
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
void Animation::resetActiveGroups()
|
||||
{
|
||||
const bool isPlayer = (mPtr == MWMechanics::getPlayer());
|
||||
|
||||
// remove all previous external controllers from the scene graph
|
||||
for (auto it = mActiveControllers.begin(); it != mActiveControllers.end(); ++it)
|
||||
{
|
||||
|
@ -1092,11 +1141,16 @@ namespace MWRender
|
|||
for (AnimSource::ControllerMap::iterator it = animsrc->mControllerMap[blendMask].begin(); it != animsrc->mControllerMap[blendMask].end(); ++it)
|
||||
{
|
||||
osg::ref_ptr<osg::Node> node = getNodeMap().at(it->first); // this should not throw, we already checked for the node existing in addAnimSource
|
||||
|
||||
node->addUpdateCallback(it->second);
|
||||
if(!isPlayer || !vrOverride(active->first, it->first))
|
||||
node->addUpdateCallback(it->second);
|
||||
mActiveControllers.emplace_back(node, it->second);
|
||||
|
||||
if (blendMask == 0 && node == mAccumRoot)
|
||||
if (blendMask == 0 && node == mAccumRoot
|
||||
#ifdef USE_OPENXR
|
||||
// TODO: Little hack to keep certain animations from wobbling the camera in VR
|
||||
&& (!isPlayer)
|
||||
#endif
|
||||
)
|
||||
{
|
||||
mAccumCtrl = it->second;
|
||||
|
||||
|
|
|
@ -448,7 +448,7 @@ public:
|
|||
void disable(const std::string &groupname);
|
||||
|
||||
/** Retrieves the velocity (in units per second) that the animation will move. */
|
||||
float getVelocity(const std::string &groupname) const;
|
||||
virtual float getVelocity(const std::string &groupname) const;
|
||||
|
||||
virtual osg::Vec3f runAnimation(float duration);
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include <components/misc/mathutil.hpp>
|
||||
#include <components/sceneutil/positionattitudetransform.hpp>
|
||||
#include <components/settings/settings.hpp>
|
||||
#include <components/debug/debuglog.hpp>
|
||||
|
||||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwbase/windowmanager.hpp"
|
||||
|
@ -126,6 +127,16 @@ namespace MWRender
|
|||
return position;
|
||||
}
|
||||
|
||||
osg::Camera* Camera::getOsgCamera()
|
||||
{
|
||||
return mCamera;
|
||||
}
|
||||
|
||||
void Camera::updateCamera()
|
||||
{
|
||||
updateCamera(mCamera);
|
||||
}
|
||||
|
||||
osg::Vec3d Camera::getFocalPointOffset() const
|
||||
{
|
||||
osg::Vec3d offset(0, 0, 10.f);
|
||||
|
@ -147,12 +158,19 @@ namespace MWRender
|
|||
camera = focal + offset;
|
||||
}
|
||||
|
||||
void Camera::getOrientation(osg::Quat& orientation) const
|
||||
{
|
||||
orientation = osg::Quat(mRoll, osg::Vec3d(0, 1, 0)) * osg::Quat(mPitch, osg::Vec3d(1, 0, 0)) * osg::Quat(mYaw, osg::Vec3d(0, 0, 1));
|
||||
}
|
||||
|
||||
void Camera::updateCamera(osg::Camera *cam)
|
||||
{
|
||||
osg::Vec3d focal, position;
|
||||
getPosition(focal, position);
|
||||
|
||||
osg::Quat orient = osg::Quat(mRoll, osg::Vec3d(0, 1, 0)) * osg::Quat(mPitch, osg::Vec3d(1, 0, 0)) * osg::Quat(mYaw, osg::Vec3d(0, 0, 1));
|
||||
osg::Quat orient;
|
||||
getOrientation(orient);
|
||||
|
||||
osg::Vec3d forward = orient * osg::Vec3d(0,1,0);
|
||||
osg::Vec3d up = orient * osg::Vec3d(0,0,1);
|
||||
|
||||
|
@ -185,8 +203,10 @@ namespace MWRender
|
|||
toggleViewMode();
|
||||
}
|
||||
|
||||
void Camera::rotateCamera(float pitch, float yaw, bool adjust)
|
||||
void Camera::rotateCamera(float pitch, float roll, float yaw, bool adjust)
|
||||
{
|
||||
(void)roll;
|
||||
|
||||
if (adjust)
|
||||
{
|
||||
pitch += getPitch();
|
||||
|
@ -223,7 +243,7 @@ namespace MWRender
|
|||
&& (mFirstPersonView || mShowCrosshairInThirdPersonMode));
|
||||
|
||||
if(mMode == Mode::Vanity)
|
||||
rotateCamera(0.f, osg::DegreesToRadians(3.f * duration), true);
|
||||
rotateCamera(0.f, 0.f, osg::DegreesToRadians(3.f * duration), true);
|
||||
|
||||
if (isFirstPerson() && mHeadBobbingEnabled)
|
||||
updateHeadBobbing(duration);
|
||||
|
@ -503,7 +523,7 @@ namespace MWRender
|
|||
else
|
||||
mHeightScale = 1.f;
|
||||
}
|
||||
rotateCamera(getPitch(), getYaw(), false);
|
||||
rotateCamera(getPitch(), 0.f, getYaw(), false);
|
||||
}
|
||||
|
||||
void Camera::applyDeferredPreviewRotationToPlayer(float dt)
|
||||
|
|
|
@ -14,6 +14,7 @@ namespace osg
|
|||
class Camera;
|
||||
class NodeCallback;
|
||||
class Node;
|
||||
class Quat;
|
||||
}
|
||||
|
||||
namespace MWRender
|
||||
|
@ -26,7 +27,7 @@ namespace MWRender
|
|||
public:
|
||||
enum class Mode { Normal, Vanity, Preview, StandingPreview };
|
||||
|
||||
private:
|
||||
protected:
|
||||
MWWorld::Ptr mTrackingPtr;
|
||||
osg::ref_ptr<const osg::Node> mTrackingNode;
|
||||
float mHeightScale;
|
||||
|
@ -92,7 +93,7 @@ namespace MWRender
|
|||
|
||||
public:
|
||||
Camera(osg::Camera* camera);
|
||||
~Camera();
|
||||
virtual ~Camera();
|
||||
|
||||
/// Attach camera to object
|
||||
void attachTo(const MWWorld::Ptr &ptr) { mTrackingPtr = ptr; }
|
||||
|
@ -100,20 +101,23 @@ namespace MWRender
|
|||
|
||||
void setFocalPointTransitionSpeed(float v) { mFocalPointTransitionSpeedCoef = v; }
|
||||
void setFocalPointTargetOffset(osg::Vec2d v);
|
||||
void instantTransition();
|
||||
virtual void instantTransition();
|
||||
void enableDynamicCameraDistance(bool v) { mDynamicCameraDistanceEnabled = v; }
|
||||
void enableCrosshairInThirdPersonMode(bool v) { mShowCrosshairInThirdPersonMode = v; }
|
||||
|
||||
/// Update the view matrix of \a cam
|
||||
void updateCamera(osg::Camera* cam);
|
||||
virtual void updateCamera(osg::Camera* cam);
|
||||
|
||||
/// Update the view matrix of the current camera
|
||||
virtual void updateCamera();
|
||||
|
||||
/// Reset to defaults
|
||||
void reset();
|
||||
virtual void reset();
|
||||
|
||||
/// Set where the camera is looking at. Uses Morrowind (euler) angles
|
||||
/// \param rot Rotation angles in radians
|
||||
void rotateCamera(float pitch, float yaw, bool adjust);
|
||||
void rotateCameraToTrackingPtr();
|
||||
virtual void rotateCamera(float pitch, float roll, float yaw, bool adjust);
|
||||
virtual void rotateCameraToTrackingPtr();
|
||||
|
||||
float getYaw() const { return mYaw; }
|
||||
void setYaw(float angle);
|
||||
|
@ -122,10 +126,10 @@ namespace MWRender
|
|||
void setPitch(float angle);
|
||||
|
||||
/// @param Force view mode switch, even if currently not allowed by the animation.
|
||||
void toggleViewMode(bool force=false);
|
||||
virtual void toggleViewMode(bool force=false);
|
||||
|
||||
bool toggleVanityMode(bool enable);
|
||||
void allowVanityMode(bool allow);
|
||||
virtual bool toggleVanityMode(bool enable);
|
||||
virtual void allowVanityMode(bool allow);
|
||||
|
||||
/// @note this may be ignored if an important animation is currently playing
|
||||
void togglePreviewMode(bool enable);
|
||||
|
@ -138,7 +142,7 @@ namespace MWRender
|
|||
|
||||
bool isFirstPerson() const { return mFirstPersonView && mMode == Mode::Normal; }
|
||||
|
||||
void processViewChange();
|
||||
virtual void processViewChange();
|
||||
|
||||
void update(float duration, bool paused=false);
|
||||
|
||||
|
@ -149,11 +153,16 @@ namespace MWRender
|
|||
|
||||
void setAnimation(NpcAnimation *anim);
|
||||
|
||||
osg::Camera* getOsgCamera();
|
||||
|
||||
osg::Vec3d getFocalPoint() const;
|
||||
osg::Vec3d getFocalPointOffset() const;
|
||||
|
||||
/// Stores focal and camera world positions in passed arguments
|
||||
void getPosition(osg::Vec3d &focal, osg::Vec3d &camera) const;
|
||||
virtual void getPosition(osg::Vec3d &focal, osg::Vec3d &camera) const;
|
||||
|
||||
/// Store camera orientation in passed arguments
|
||||
virtual void getOrientation(osg::Quat& orientation) const;
|
||||
|
||||
bool isVanityOrPreviewModeEnabled() const { return mMode != Mode::Normal; }
|
||||
Mode getMode() const { return mMode; }
|
||||
|
|
|
@ -959,7 +959,7 @@ void NpcAnimation::addControllers()
|
|||
mActiveControllers.emplace_back(node, mFirstPersonNeckController);
|
||||
}
|
||||
}
|
||||
else if (mViewMode == VM_Normal)
|
||||
else if (mViewMode != VM_HeadOnly)
|
||||
{
|
||||
WeaponAnimation::addControllers(mNodeMap, mActiveControllers, mObjectRoot.get());
|
||||
}
|
||||
|
|
|
@ -27,7 +27,10 @@ namespace MWRender
|
|||
class NeckController;
|
||||
class HeadAnimationTime;
|
||||
|
||||
class NpcAnimation : public ActorAnimation, public WeaponAnimation, public MWWorld::InventoryStoreListener
|
||||
class NpcAnimation :
|
||||
public ActorAnimation
|
||||
, public WeaponAnimation
|
||||
, public MWWorld::InventoryStoreListener
|
||||
{
|
||||
public:
|
||||
void equipmentChanged() override;
|
||||
|
@ -39,10 +42,12 @@ public:
|
|||
enum ViewMode {
|
||||
VM_Normal,
|
||||
VM_FirstPerson,
|
||||
VM_HeadOnly
|
||||
VM_HeadOnly,
|
||||
VM_VRNormal,
|
||||
VM_VRFirstPerson,
|
||||
};
|
||||
|
||||
private:
|
||||
protected:
|
||||
static const PartBoneMap sPartList;
|
||||
|
||||
// Bounded Parts
|
||||
|
@ -152,9 +157,9 @@ public:
|
|||
// WeaponAnimation
|
||||
void showWeapon(bool show) override { showWeapons(show); }
|
||||
|
||||
void setViewMode(ViewMode viewMode);
|
||||
virtual void setViewMode(ViewMode viewMode);
|
||||
|
||||
void updateParts();
|
||||
virtual void updateParts();
|
||||
|
||||
/// Rebuilds the NPC, updating their root model, animation sources, and equipment.
|
||||
void rebuild();
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#include <osg/Group>
|
||||
#include <osg/UserDataContainer>
|
||||
#include <osg/ComputeBoundsVisitor>
|
||||
#include <osg/ViewportIndexed>
|
||||
|
||||
#include <osgUtil/LineSegmentIntersector>
|
||||
|
||||
|
@ -31,6 +32,7 @@
|
|||
#include <components/settings/settings.hpp>
|
||||
|
||||
#include <components/sceneutil/util.hpp>
|
||||
#include <components/sceneutil/visitor.hpp>
|
||||
#include <components/sceneutil/lightmanager.hpp>
|
||||
#include <components/sceneutil/statesetupdater.hpp>
|
||||
#include <components/sceneutil/positionattitudetransform.hpp>
|
||||
|
@ -69,6 +71,15 @@
|
|||
#include "screenshotmanager.hpp"
|
||||
#include "groundcover.hpp"
|
||||
|
||||
#ifdef USE_OPENXR
|
||||
#include "../mwvr/vranimation.hpp"
|
||||
#include "../mwvr/vrpointer.hpp"
|
||||
#include "../mwvr/vrviewer.hpp"
|
||||
#include "../mwvr/vrenvironment.hpp"
|
||||
#include "../mwvr/vrcamera.hpp"
|
||||
#endif
|
||||
|
||||
|
||||
namespace MWRender
|
||||
{
|
||||
|
||||
|
@ -184,7 +195,7 @@ namespace MWRender
|
|||
Resource::ResourceSystem* mResourceSystem;
|
||||
};
|
||||
|
||||
RenderingManager::RenderingManager(osgViewer::Viewer* viewer, osg::ref_ptr<osg::Group> rootNode,
|
||||
RenderingManager::RenderingManager(osgViewer::Viewer* viewer, osg::ref_ptr<osg::Group> rootNode, std::unique_ptr<Camera> camera,
|
||||
Resource::ResourceSystem* resourceSystem, SceneUtil::WorkQueue* workQueue,
|
||||
const std::string& resourcePath, DetourNavigator::Navigator& navigator)
|
||||
: mViewer(viewer)
|
||||
|
@ -193,6 +204,9 @@ namespace MWRender
|
|||
, mWorkQueue(workQueue)
|
||||
, mUnrefQueue(new SceneUtil::UnrefQueue)
|
||||
, mNavigator(navigator)
|
||||
#ifdef USE_OPENXR
|
||||
, mUserPointer(new MWVR::UserPointer(rootNode))
|
||||
#endif
|
||||
, mMinimumAmbientLuminance(0.f)
|
||||
, mNightEyeFactor(0.f)
|
||||
, mFieldOfViewOverridden(false)
|
||||
|
@ -207,7 +221,8 @@ namespace MWRender
|
|||
|| Settings::Manager::getBool("force shaders", "Shaders")
|
||||
|| Settings::Manager::getBool("enable shadows", "Shadows")
|
||||
|| lightingMethod != SceneUtil::LightingMethod::FFP;
|
||||
resourceSystem->getSceneManager()->setForceShaders(forceShaders);
|
||||
//resourceSystem->getSceneManager()->setForceShaders(forceShaders);
|
||||
resourceSystem->getSceneManager()->setForceShaders(true);
|
||||
// FIXME: calling dummy method because terrain needs to know whether lighting is clamped
|
||||
resourceSystem->getSceneManager()->setClampLighting(Settings::Manager::getBool("clamp lighting", "Shaders"));
|
||||
resourceSystem->getSceneManager()->setAutoUseNormalMaps(Settings::Manager::getBool("auto use object normal maps", "Shaders"));
|
||||
|
@ -356,8 +371,8 @@ namespace MWRender
|
|||
}
|
||||
// water goes after terrain for correct waterculling order
|
||||
mWater.reset(new Water(sceneRoot->getParent(0), sceneRoot, mResourceSystem, mViewer->getIncrementalCompileOperation(), resourcePath));
|
||||
mCamera = std::move(camera);
|
||||
|
||||
mCamera.reset(new Camera(mViewer->getCamera()));
|
||||
if (Settings::Manager::getBool("view over shoulder", "Camera"))
|
||||
mViewOverShoulderController.reset(new ViewOverShoulderController(mCamera.get()));
|
||||
|
||||
|
@ -411,7 +426,10 @@ namespace MWRender
|
|||
mViewer->getCamera()->setComputeNearFarMode(osg::Camera::DO_NOT_COMPUTE_NEAR_FAR);
|
||||
mViewer->getCamera()->setCullingMode(cullingMode);
|
||||
|
||||
mViewer->getCamera()->setCullMask(~(Mask_UpdateVisitor|Mask_SimpleWater));
|
||||
auto mask = ~(Mask_UpdateVisitor | Mask_SimpleWater);
|
||||
mViewer->getCamera()->setCullMask(mask);
|
||||
mViewer->getCamera()->setCullMaskLeft(mask);
|
||||
mViewer->getCamera()->setCullMaskRight(mask);
|
||||
NifOsg::Loader::setHiddenNodeMask(Mask_UpdateVisitor);
|
||||
NifOsg::Loader::setIntersectionDisabledNodeMask(Mask_Effect);
|
||||
Nif::NIFFile::setLoadUnsupportedFiles(Settings::Manager::getBool("load unsupported nif files", "Models"));
|
||||
|
@ -424,6 +442,7 @@ namespace MWRender
|
|||
mFirstPersonFieldOfView = std::min(std::max(1.f, firstPersonFov), 179.f);
|
||||
mStateUpdater->setFogEnd(mViewDistance);
|
||||
|
||||
////// Near far uniforms
|
||||
mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("near", mNearClip));
|
||||
mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("far", mViewDistance));
|
||||
mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("simpleWater", false));
|
||||
|
@ -435,6 +454,7 @@ namespace MWRender
|
|||
|
||||
mUniformNear = mRootNode->getOrCreateStateSet()->getUniform("near");
|
||||
mUniformFar = mRootNode->getOrCreateStateSet()->getUniform("far");
|
||||
|
||||
updateProjectionMatrix();
|
||||
}
|
||||
|
||||
|
@ -669,6 +689,8 @@ namespace MWRender
|
|||
else
|
||||
mask &= ~Mask_Scene;
|
||||
mViewer->getCamera()->setCullMask(mask);
|
||||
mViewer->getCamera()->setCullMaskLeft(mask);
|
||||
mViewer->getCamera()->setCullMaskRight(mask);
|
||||
return enabled;
|
||||
}
|
||||
else if (mode == Render_NavMesh)
|
||||
|
@ -857,21 +879,26 @@ namespace MWRender
|
|||
return osg::Vec4f(min_x, min_y, max_x, max_y);
|
||||
}
|
||||
|
||||
RenderingManager::RayResult getIntersectionResult (osgUtil::LineSegmentIntersector* intersector)
|
||||
RayResult getIntersectionResult (osgUtil::LineSegmentIntersector* intersector)
|
||||
{
|
||||
RenderingManager::RayResult result;
|
||||
RayResult result;
|
||||
result.mHit = false;
|
||||
result.mHitRefnum.unset();
|
||||
result.mRatio = 0;
|
||||
result.mHitNode = nullptr;
|
||||
if (intersector->containsIntersections())
|
||||
{
|
||||
result.mHit = true;
|
||||
osgUtil::LineSegmentIntersector::Intersection intersection = intersector->getFirstIntersection();
|
||||
|
||||
result.mHitPointLocal = intersection.getLocalIntersectPoint();
|
||||
result.mHitPointWorld = intersection.getWorldIntersectPoint();
|
||||
result.mHitNormalWorld = intersection.getWorldIntersectNormal();
|
||||
result.mRatio = intersection.ratio;
|
||||
|
||||
if(!intersection.nodePath.empty())
|
||||
result.mHitNode = intersection.nodePath.back();
|
||||
|
||||
PtrHolder* ptrHolder = nullptr;
|
||||
std::vector<RefnumMarker*> refnumMarkers;
|
||||
for (osg::NodePath::const_iterator it = intersection.nodePath.begin(); it != intersection.nodePath.end(); ++it)
|
||||
|
@ -920,15 +947,15 @@ namespace MWRender
|
|||
unsigned int mask = ~0u;
|
||||
mask &= ~(Mask_RenderToTexture|Mask_Sky|Mask_Debug|Mask_Effect|Mask_Water|Mask_SimpleWater|Mask_Groundcover);
|
||||
if (ignorePlayer)
|
||||
mask &= ~(Mask_Player);
|
||||
mask &= ~(Mask_Player|Mask_Pointer);
|
||||
if (ignoreActors)
|
||||
mask &= ~(Mask_Actor|Mask_Player);
|
||||
mask &= ~(Mask_Actor|Mask_Player|Mask_Pointer);
|
||||
|
||||
mIntersectionVisitor->setTraversalMask(mask);
|
||||
return mIntersectionVisitor;
|
||||
}
|
||||
|
||||
RenderingManager::RayResult RenderingManager::castRay(const osg::Vec3f& origin, const osg::Vec3f& dest, bool ignorePlayer, bool ignoreActors)
|
||||
RayResult RenderingManager::castRay(const osg::Vec3f& origin, const osg::Vec3f& dest, bool ignorePlayer, bool ignoreActors)
|
||||
{
|
||||
osg::ref_ptr<osgUtil::LineSegmentIntersector> intersector (new osgUtil::LineSegmentIntersector(osgUtil::LineSegmentIntersector::MODEL,
|
||||
origin, dest));
|
||||
|
@ -939,7 +966,25 @@ namespace MWRender
|
|||
return getIntersectionResult(intersector);
|
||||
}
|
||||
|
||||
RenderingManager::RayResult RenderingManager::castCameraToViewportRay(const float nX, const float nY, float maxDistance, bool ignorePlayer, bool ignoreActors)
|
||||
RayResult RenderingManager::castRay(const osg::Transform* source, float maxDistance, bool ignorePlayer, bool ignoreActors)
|
||||
{
|
||||
|
||||
if (source)
|
||||
{
|
||||
osg::Matrix worldMatrix = osg::computeLocalToWorld(source->getParentalNodePaths()[0]);
|
||||
|
||||
osg::Vec3f direction = worldMatrix.getRotate() * osg::Vec3f(0, 1, 0);
|
||||
direction.normalize();
|
||||
|
||||
osg::Vec3f raySource = worldMatrix.getTrans();
|
||||
osg::Vec3f rayTarget = worldMatrix.getTrans() + direction * maxDistance;
|
||||
|
||||
return castRay(raySource, rayTarget, ignorePlayer, ignoreActors);
|
||||
}
|
||||
return RayResult();
|
||||
}
|
||||
|
||||
RayResult RenderingManager::castCameraToViewportRay(const float nX, const float nY, float maxDistance, bool ignorePlayer, bool ignoreActors)
|
||||
{
|
||||
osg::ref_ptr<osgUtil::LineSegmentIntersector> intersector (new osgUtil::LineSegmentIntersector(osgUtil::LineSegmentIntersector::PROJECTION,
|
||||
nX * 2.f - 1.f, nY * (-2.f) + 1.f));
|
||||
|
@ -982,6 +1027,10 @@ namespace MWRender
|
|||
notifyWorldSpaceChanged();
|
||||
if (mObjectPaging)
|
||||
mObjectPaging->clear();
|
||||
|
||||
#ifdef USE_OPENXR
|
||||
mUserPointer->setParent(nullptr);
|
||||
#endif
|
||||
}
|
||||
|
||||
MWRender::Animation* RenderingManager::getAnimation(const MWWorld::Ptr &ptr)
|
||||
|
@ -1021,8 +1070,13 @@ namespace MWRender
|
|||
|
||||
void RenderingManager::renderPlayer(const MWWorld::Ptr &player)
|
||||
{
|
||||
#ifdef USE_OPENXR
|
||||
MWVR::Environment::get().setPlayerAnimation(new MWVR::VRAnimation(player, player.getRefData().getBaseNode(), mResourceSystem, false, mUserPointer));
|
||||
mPlayerAnimation = MWVR::Environment::get().getPlayerAnimation();
|
||||
#else
|
||||
mPlayerAnimation = new NpcAnimation(player, player.getRefData().getBaseNode(), mResourceSystem, 0, NpcAnimation::VM_Normal,
|
||||
mFirstPersonFieldOfView);
|
||||
#endif
|
||||
|
||||
mCamera->setAnimation(mPlayerAnimation.get());
|
||||
mCamera->attachTo(player);
|
||||
|
@ -1114,6 +1168,12 @@ namespace MWRender
|
|||
void RenderingManager::setFogColor(const osg::Vec4f &color)
|
||||
{
|
||||
mViewer->getCamera()->setClearColor(color);
|
||||
for (unsigned int i = 0; i < mViewer->getNumSlaves(); i++)
|
||||
{
|
||||
const auto& slave = mViewer->getSlave(i);
|
||||
if (slave._camera)
|
||||
slave._camera->setClearColor(color);
|
||||
}
|
||||
|
||||
mStateUpdater->setFogColor(color);
|
||||
}
|
||||
|
@ -1350,4 +1410,11 @@ namespace MWRender
|
|||
if (mObjectPaging)
|
||||
mObjectPaging->getPagedRefnums(activeGrid, out);
|
||||
}
|
||||
|
||||
#ifdef USE_OPENXR
|
||||
MWVR::UserPointer& RenderingManager::userPointer()
|
||||
{
|
||||
return *mUserPointer;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
|
|
@ -68,6 +68,11 @@ namespace DetourNavigator
|
|||
struct Settings;
|
||||
}
|
||||
|
||||
namespace MWVR
|
||||
{
|
||||
class UserPointer;
|
||||
}
|
||||
|
||||
namespace MWRender
|
||||
{
|
||||
class GroundcoverUpdater;
|
||||
|
@ -90,10 +95,25 @@ namespace MWRender
|
|||
class ObjectPaging;
|
||||
class Groundcover;
|
||||
|
||||
// Result data of ray cast methods.
|
||||
// Needs to be declared outside the RenderingManager class to be forward declarable
|
||||
struct RayResult
|
||||
{
|
||||
bool mHit;
|
||||
osg::Vec3f mHitNormalWorld;
|
||||
osg::Vec3f mHitPointWorld;
|
||||
osg::Vec3f mHitPointLocal;
|
||||
MWWorld::Ptr mHitObject;
|
||||
osg::Node* mHitNode;
|
||||
/// Cast a ray between two points
|
||||
ESM::RefNum mHitRefnum;
|
||||
float mRatio;
|
||||
};
|
||||
|
||||
class RenderingManager : public MWRender::RenderingInterface
|
||||
{
|
||||
public:
|
||||
RenderingManager(osgViewer::Viewer* viewer, osg::ref_ptr<osg::Group> rootNode,
|
||||
RenderingManager(osgViewer::Viewer* viewer, osg::ref_ptr<osg::Group> rootNode, std::unique_ptr<Camera> camera,
|
||||
Resource::ResourceSystem* resourceSystem, SceneUtil::WorkQueue* workQueue,
|
||||
const std::string& resourcePath, DetourNavigator::Navigator& navigator);
|
||||
~RenderingManager();
|
||||
|
@ -110,6 +130,8 @@ namespace MWRender
|
|||
|
||||
osg::Uniform* mUniformNear;
|
||||
osg::Uniform* mUniformFar;
|
||||
osg::Uniform* mUniformStereoViewOffsets;
|
||||
osg::Uniform* mUniformStereoProjections;
|
||||
|
||||
void preloadCommonAssets();
|
||||
|
||||
|
@ -153,18 +175,11 @@ namespace MWRender
|
|||
void screenshot(osg::Image* image, int w, int h);
|
||||
bool screenshot360(osg::Image* image);
|
||||
|
||||
struct RayResult
|
||||
{
|
||||
bool mHit;
|
||||
osg::Vec3f mHitNormalWorld;
|
||||
osg::Vec3f mHitPointWorld;
|
||||
MWWorld::Ptr mHitObject;
|
||||
ESM::RefNum mHitRefnum;
|
||||
float mRatio;
|
||||
};
|
||||
|
||||
RayResult castRay(const osg::Vec3f& origin, const osg::Vec3f& dest, bool ignorePlayer, bool ignoreActors=false);
|
||||
|
||||
/// Cast a ray from a node in the scene graph
|
||||
RayResult castRay(const osg::Transform* source, float maxDistance, bool ignorePlayer, bool ignoreActors=false);
|
||||
|
||||
/// Return the object under the mouse cursor / crosshair position, given by nX and nY normalized screen coordinates,
|
||||
/// where (0,0) is the top left corner.
|
||||
RayResult castCameraToViewportRay(const float nX, const float nY, float maxDistance, bool ignorePlayer, bool ignoreActors=false);
|
||||
|
@ -240,6 +255,10 @@ namespace MWRender
|
|||
bool pagingUnlockCache();
|
||||
void getPagedRefnums(const osg::Vec4i &activeGrid, std::set<ESM::RefNum> &out);
|
||||
|
||||
#ifdef USE_OPENXR
|
||||
MWVR::UserPointer& userPointer();
|
||||
#endif
|
||||
|
||||
private:
|
||||
void updateProjectionMatrix();
|
||||
void updateTextureFiltering();
|
||||
|
@ -293,6 +312,10 @@ namespace MWRender
|
|||
std::unique_ptr<ViewOverShoulderController> mViewOverShoulderController;
|
||||
osg::Vec3f mCurrentCameraPos;
|
||||
|
||||
#ifdef USE_OPENXR
|
||||
std::shared_ptr<MWVR::UserPointer> mUserPointer;
|
||||
#endif
|
||||
|
||||
osg::ref_ptr<StateUpdater> mStateUpdater;
|
||||
|
||||
osg::Vec4f mAmbientColor;
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include <osg/TextureCubeMap>
|
||||
|
||||
#include <components/misc/stringops.hpp>
|
||||
#include <components/misc/callbackmanager.hpp>
|
||||
#include <components/resource/resourcesystem.hpp>
|
||||
#include <components/resource/scenemanager.hpp>
|
||||
#include <components/shader/shadermanager.hpp>
|
||||
|
@ -113,7 +114,7 @@ namespace MWRender
|
|||
|
||||
void ScreenshotManager::screenshot(osg::Image* image, int w, int h)
|
||||
{
|
||||
osg::Camera* camera = mViewer->getCamera();
|
||||
osg::Camera * camera = mViewer->getCamera();
|
||||
osg::ref_ptr<osg::Drawable> tempDrw = new osg::Drawable;
|
||||
tempDrw->setDrawCallback(new ReadImageFromFramebufferCallback(image, w, h));
|
||||
tempDrw->setCullingActive(false);
|
||||
|
@ -324,8 +325,8 @@ namespace MWRender
|
|||
rttCamera->setUpdateCallback(new NoTraverseCallback);
|
||||
rttCamera->addChild(mSceneRoot);
|
||||
|
||||
rttCamera->addChild(mWater->getReflectionCamera());
|
||||
rttCamera->addChild(mWater->getRefractionCamera());
|
||||
rttCamera->addChild(mWater->getReflectionNode());
|
||||
rttCamera->addChild(mWater->getRefractionNode());
|
||||
|
||||
rttCamera->setCullMask(mViewer->getCamera()->getCullMask() & (~Mask_GUI));
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include <osg/Node>
|
||||
#include <osg/ValueObject>
|
||||
#include <osg/RenderInfo>
|
||||
|
||||
#include <components/resource/resourcesystem.hpp>
|
||||
#include <components/resource/imagemanager.hpp>
|
||||
|
@ -64,4 +65,20 @@ void overrideTexture(const std::string &texture, Resource::ResourceSystem *resou
|
|||
node->setStateSet(stateset);
|
||||
}
|
||||
|
||||
MipmapCallback::~MipmapCallback()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void MipmapCallback::operator()(osg::RenderInfo& renderInfo) const
|
||||
{
|
||||
auto* gl = renderInfo.getState()->get<osg::GLExtensions>();
|
||||
auto* tex = mTexture->getTextureObject(renderInfo.getContextID());
|
||||
if (tex)
|
||||
{
|
||||
tex->bind();
|
||||
gl->glGenerateMipmap(tex->target());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -2,12 +2,14 @@
|
|||
#define OPENMW_MWRENDER_UTIL_H
|
||||
|
||||
#include <osg/NodeCallback>
|
||||
#include <osg/Camera>
|
||||
#include <osg/ref_ptr>
|
||||
#include <string>
|
||||
|
||||
namespace osg
|
||||
{
|
||||
class Node;
|
||||
class Texture2D;
|
||||
}
|
||||
|
||||
namespace Resource
|
||||
|
@ -32,6 +34,24 @@ namespace MWRender
|
|||
// no traverse()
|
||||
}
|
||||
};
|
||||
|
||||
/// Draw callback for RTT that can be used to regenerate mipmaps
|
||||
/// either as a predraw before use or a postdraw after RTT.
|
||||
class MipmapCallback : public osg::Camera::DrawCallback
|
||||
{
|
||||
public:
|
||||
MipmapCallback(osg::Texture2D* texture)
|
||||
: mTexture(texture)
|
||||
{}
|
||||
|
||||
~MipmapCallback();
|
||||
|
||||
void operator()(osg::RenderInfo& info) const override;
|
||||
|
||||
private:
|
||||
|
||||
osg::ref_ptr<osg::Texture2D> mTexture;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -56,6 +56,10 @@ namespace MWRender
|
|||
Mask_Lighting = (1<<19),
|
||||
|
||||
Mask_Groundcover = (1<<20),
|
||||
|
||||
// Vr masks
|
||||
Mask_3DGUI = (1 << 21),
|
||||
Mask_Pointer = (1 << 22)
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include <osg/PositionAttitudeTransform>
|
||||
#include <osg/ClipNode>
|
||||
#include <osg/FrontFace>
|
||||
#include <osg/ViewportIndexed>
|
||||
|
||||
#include <osgDB/ReadFile>
|
||||
|
||||
|
@ -25,12 +26,14 @@
|
|||
#include <components/resource/imagemanager.hpp>
|
||||
#include <components/resource/scenemanager.hpp>
|
||||
|
||||
#include <components/sceneutil/rtt.hpp>
|
||||
#include <components/sceneutil/shadow.hpp>
|
||||
#include <components/sceneutil/util.hpp>
|
||||
#include <components/sceneutil/waterutil.hpp>
|
||||
#include <components/sceneutil/lightmanager.hpp>
|
||||
|
||||
#include <components/misc/constants.hpp>
|
||||
#include <components/misc/stereo.hpp>
|
||||
|
||||
#include <components/nifosg/controller.hpp>
|
||||
|
||||
|
@ -42,6 +45,8 @@
|
|||
|
||||
#include "../mwworld/cellstore.hpp"
|
||||
|
||||
#include "../mwbase/environment.hpp"
|
||||
|
||||
#include "vismask.hpp"
|
||||
#include "ripplesimulation.hpp"
|
||||
#include "renderbin.hpp"
|
||||
|
@ -261,65 +266,46 @@ osg::ref_ptr<osg::Image> readPngImage (const std::string& file)
|
|||
return result.getImage();
|
||||
}
|
||||
|
||||
|
||||
class Refraction : public osg::Camera
|
||||
class Refraction : public SceneUtil::RTTNode
|
||||
{
|
||||
public:
|
||||
Refraction()
|
||||
Refraction(uint32_t rttSize)
|
||||
: RTTNode(rttSize, rttSize, 1, false)
|
||||
{
|
||||
unsigned int rttSize = Settings::Manager::getInt("rtt size", "Water");
|
||||
setRenderOrder(osg::Camera::PRE_RENDER, 1);
|
||||
setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT);
|
||||
setReferenceFrame(osg::Camera::RELATIVE_RF);
|
||||
setSmallFeatureCullingPixelSize(Settings::Manager::getInt("small feature culling pixel size", "Water"));
|
||||
osg::Camera::setName("RefractionCamera");
|
||||
setCullCallback(new InheritViewPointCallback);
|
||||
setComputeNearFarMode(osg::CullSettings::DO_NOT_COMPUTE_NEAR_FAR);
|
||||
mClipCullNode = new ClipCullNode;
|
||||
}
|
||||
|
||||
setCullMask(Mask_Effect|Mask_Scene|Mask_Object|Mask_Static|Mask_Terrain|Mask_Actor|Mask_ParticleSystem|Mask_Sky|Mask_Sun|Mask_Player|Mask_Lighting|Mask_Groundcover);
|
||||
setNodeMask(Mask_RenderToTexture);
|
||||
setViewport(0, 0, rttSize, rttSize);
|
||||
void setDefaults(osg::Camera* camera) override
|
||||
{
|
||||
camera->setReferenceFrame(osg::Camera::RELATIVE_RF);
|
||||
camera->setSmallFeatureCullingPixelSize(Settings::Manager::getInt("small feature culling pixel size", "Water"));
|
||||
camera->setName("RefractionCamera");
|
||||
camera->addCullCallback(new InheritViewPointCallback);
|
||||
camera->setComputeNearFarMode(osg::CullSettings::DO_NOT_COMPUTE_NEAR_FAR);
|
||||
|
||||
// No need for Update traversal since the scene is already updated as part of the main scene graph
|
||||
// A double update would mess with the light collection (in addition to being plain redundant)
|
||||
setUpdateCallback(new NoTraverseCallback);
|
||||
camera->setCullMask(Mask_Effect | Mask_Scene | Mask_Object | Mask_Static | Mask_Terrain | Mask_Actor | Mask_ParticleSystem | Mask_Sky | Mask_Sun | Mask_Player | Mask_Lighting);
|
||||
|
||||
// No need for fog here, we are already applying fog on the water surface itself as well as underwater fog
|
||||
// assign large value to effectively turn off fog
|
||||
// shaders don't respect glDisable(GL_FOG)
|
||||
osg::ref_ptr<osg::Fog> fog (new osg::Fog);
|
||||
osg::ref_ptr<osg::Fog> fog(new osg::Fog);
|
||||
fog->setStart(10000000);
|
||||
fog->setEnd(10000000);
|
||||
getOrCreateStateSet()->setAttributeAndModes(fog, osg::StateAttribute::OFF|osg::StateAttribute::OVERRIDE);
|
||||
camera->getOrCreateStateSet()->setAttributeAndModes(fog, osg::StateAttribute::OFF | osg::StateAttribute::OVERRIDE);
|
||||
|
||||
mClipCullNode = new ClipCullNode;
|
||||
osg::Camera::addChild(mClipCullNode);
|
||||
|
||||
mRefractionTexture = new osg::Texture2D;
|
||||
mRefractionTexture->setTextureSize(rttSize, rttSize);
|
||||
mRefractionTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE);
|
||||
mRefractionTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE);
|
||||
mRefractionTexture->setInternalFormat(GL_RGB);
|
||||
mRefractionTexture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR);
|
||||
mRefractionTexture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR);
|
||||
|
||||
SceneUtil::attachAlphaToCoverageFriendlyFramebufferToCamera(this, osg::Camera::COLOR_BUFFER, mRefractionTexture);
|
||||
|
||||
mRefractionDepthTexture = new osg::Texture2D;
|
||||
mRefractionDepthTexture->setTextureSize(rttSize, rttSize);
|
||||
mRefractionDepthTexture->setSourceFormat(GL_DEPTH_COMPONENT);
|
||||
mRefractionDepthTexture->setInternalFormat(GL_DEPTH_COMPONENT24);
|
||||
mRefractionDepthTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE);
|
||||
mRefractionDepthTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE);
|
||||
mRefractionDepthTexture->setSourceType(GL_UNSIGNED_INT);
|
||||
mRefractionDepthTexture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR);
|
||||
mRefractionDepthTexture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR);
|
||||
|
||||
attach(osg::Camera::DEPTH_BUFFER, mRefractionDepthTexture);
|
||||
camera->addChild(mClipCullNode);
|
||||
camera->setNodeMask(Mask_RenderToTexture);
|
||||
|
||||
if (Settings::Manager::getFloat("refraction scale", "Water") != 1) // TODO: to be removed with issue #5709
|
||||
SceneUtil::ShadowManager::disableShadowsForStateSet(getOrCreateStateSet());
|
||||
SceneUtil::ShadowManager::disableShadowsForStateSet(camera->getOrCreateStateSet());
|
||||
|
||||
if (MWBase::Environment::get().getResourceSystem()->getSceneManager()->getShaderManager().stereoGeometryShaderEnabled())
|
||||
Misc::enableStereoForCamera(camera, true);
|
||||
}
|
||||
|
||||
void apply(osg::Camera* camera) override
|
||||
{
|
||||
camera->setViewMatrix(mViewMatrix);
|
||||
}
|
||||
|
||||
void setScene(osg::Node* scene)
|
||||
|
@ -332,74 +318,57 @@ public:
|
|||
|
||||
void setWaterLevel(float waterLevel)
|
||||
{
|
||||
const float refractionScale = std::min(1.0f,std::max(0.0f,
|
||||
const float refractionScale = std::min(1.0f, std::max(0.0f,
|
||||
Settings::Manager::getFloat("refraction scale", "Water")));
|
||||
|
||||
setViewMatrix(osg::Matrix::scale(1,1,refractionScale) *
|
||||
osg::Matrix::translate(0,0,(1.0 - refractionScale) * waterLevel));
|
||||
mViewMatrix = osg::Matrix::scale(1, 1, refractionScale) *
|
||||
osg::Matrix::translate(0, 0, (1.0 - refractionScale) * waterLevel);
|
||||
|
||||
mClipCullNode->setPlane(osg::Plane(osg::Vec3d(0,0,-1), osg::Vec3d(0,0, waterLevel)));
|
||||
}
|
||||
|
||||
osg::Texture2D* getRefractionTexture() const
|
||||
{
|
||||
return mRefractionTexture.get();
|
||||
}
|
||||
|
||||
osg::Texture2D* getRefractionDepthTexture() const
|
||||
{
|
||||
return mRefractionDepthTexture.get();
|
||||
mClipCullNode->setPlane(osg::Plane(osg::Vec3d(0, 0, -1), osg::Vec3d(0, 0, waterLevel)));
|
||||
}
|
||||
|
||||
private:
|
||||
osg::ref_ptr<ClipCullNode> mClipCullNode;
|
||||
osg::ref_ptr<osg::Texture2D> mRefractionTexture;
|
||||
osg::ref_ptr<osg::Texture2D> mRefractionDepthTexture;
|
||||
osg::ref_ptr<osg::Node> mScene;
|
||||
osg::Matrix mViewMatrix{ osg::Matrix::identity() };
|
||||
};
|
||||
|
||||
class Reflection : public osg::Camera
|
||||
class Reflection : public SceneUtil::RTTNode
|
||||
{
|
||||
public:
|
||||
Reflection(bool isInterior)
|
||||
Reflection(uint32_t rttSize, bool isInterior)
|
||||
: RTTNode(rttSize, rttSize, 0, false)
|
||||
{
|
||||
setRenderOrder(osg::Camera::PRE_RENDER);
|
||||
setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT);
|
||||
setReferenceFrame(osg::Camera::RELATIVE_RF);
|
||||
setSmallFeatureCullingPixelSize(Settings::Manager::getInt("small feature culling pixel size", "Water"));
|
||||
osg::Camera::setName("ReflectionCamera");
|
||||
setCullCallback(new InheritViewPointCallback);
|
||||
|
||||
setInterior(isInterior);
|
||||
setNodeMask(Mask_RenderToTexture);
|
||||
mClipCullNode = new ClipCullNode;
|
||||
}
|
||||
|
||||
unsigned int rttSize = Settings::Manager::getInt("rtt size", "Water");
|
||||
setViewport(0, 0, rttSize, rttSize);
|
||||
|
||||
// No need for Update traversal since the mSceneRoot is already updated as part of the main scene graph
|
||||
// A double update would mess with the light collection (in addition to being plain redundant)
|
||||
setUpdateCallback(new NoTraverseCallback);
|
||||
|
||||
mReflectionTexture = new osg::Texture2D;
|
||||
mReflectionTexture->setTextureSize(rttSize, rttSize);
|
||||
mReflectionTexture->setInternalFormat(GL_RGB);
|
||||
mReflectionTexture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR);
|
||||
mReflectionTexture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR);
|
||||
mReflectionTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE);
|
||||
mReflectionTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE);
|
||||
|
||||
SceneUtil::attachAlphaToCoverageFriendlyFramebufferToCamera(this, osg::Camera::COLOR_BUFFER, mReflectionTexture);
|
||||
void setDefaults(osg::Camera* camera) override
|
||||
{
|
||||
camera->setReferenceFrame(osg::Camera::RELATIVE_RF);
|
||||
camera->setSmallFeatureCullingPixelSize(Settings::Manager::getInt("small feature culling pixel size", "Water"));
|
||||
camera->setName("ReflectionCamera");
|
||||
camera->addCullCallback(new InheritViewPointCallback);
|
||||
|
||||
|
||||
// XXX: should really flip the FrontFace on each renderable instead of forcing clockwise.
|
||||
osg::ref_ptr<osg::FrontFace> frontFace (new osg::FrontFace);
|
||||
osg::ref_ptr<osg::FrontFace> frontFace(new osg::FrontFace);
|
||||
frontFace->setMode(osg::FrontFace::CLOCKWISE);
|
||||
getOrCreateStateSet()->setAttributeAndModes(frontFace, osg::StateAttribute::ON);
|
||||
camera->getOrCreateStateSet()->setAttributeAndModes(frontFace, osg::StateAttribute::ON);
|
||||
|
||||
mClipCullNode = new ClipCullNode;
|
||||
osg::Camera::addChild(mClipCullNode);
|
||||
camera->addChild(mClipCullNode);
|
||||
camera->setNodeMask(Mask_RenderToTexture);
|
||||
|
||||
SceneUtil::ShadowManager::disableShadowsForStateSet(getOrCreateStateSet());
|
||||
SceneUtil::ShadowManager::disableShadowsForStateSet(camera->getOrCreateStateSet());
|
||||
|
||||
if (MWBase::Environment::get().getResourceSystem()->getSceneManager()->getShaderManager().stereoGeometryShaderEnabled())
|
||||
Misc::enableStereoForCamera(camera, true);
|
||||
}
|
||||
|
||||
void apply(osg::Camera* camera) override
|
||||
{
|
||||
camera->setViewMatrix(mViewMatrix);
|
||||
camera->setCullMask(mNodeMask);
|
||||
}
|
||||
|
||||
void setInterior(bool isInterior)
|
||||
|
@ -409,16 +378,16 @@ public:
|
|||
unsigned int extraMask = 0;
|
||||
if(reflectionDetail >= 1) extraMask |= Mask_Terrain;
|
||||
if(reflectionDetail >= 2) extraMask |= Mask_Static;
|
||||
if(reflectionDetail >= 3) extraMask |= Mask_Effect|Mask_ParticleSystem|Mask_Object;
|
||||
if(reflectionDetail >= 4) extraMask |= Mask_Player|Mask_Actor;
|
||||
if(reflectionDetail >= 3) extraMask |= Mask_Effect | Mask_ParticleSystem | Mask_Object;
|
||||
if(reflectionDetail >= 4) extraMask |= Mask_Player | Mask_Actor;
|
||||
if(reflectionDetail >= 5) extraMask |= Mask_Groundcover;
|
||||
setCullMask(Mask_Scene|Mask_Sky|Mask_Lighting|extraMask);
|
||||
mNodeMask = Mask_Scene | Mask_Sky | Mask_Lighting | extraMask;
|
||||
}
|
||||
|
||||
void setWaterLevel(float waterLevel)
|
||||
{
|
||||
setViewMatrix(osg::Matrix::scale(1,1,-1) * osg::Matrix::translate(0,0,2 * waterLevel));
|
||||
mClipCullNode->setPlane(osg::Plane(osg::Vec3d(0,0,1), osg::Vec3d(0,0,waterLevel)));
|
||||
mViewMatrix = osg::Matrix::scale(1, 1, -1) * osg::Matrix::translate(0, 0, 2 * waterLevel);
|
||||
mClipCullNode->setPlane(osg::Plane(osg::Vec3d(0, 0, 1), osg::Vec3d(0, 0, waterLevel)));
|
||||
}
|
||||
|
||||
void setScene(osg::Node* scene)
|
||||
|
@ -429,15 +398,11 @@ public:
|
|||
mClipCullNode->addChild(scene);
|
||||
}
|
||||
|
||||
osg::Texture2D* getReflectionTexture() const
|
||||
{
|
||||
return mReflectionTexture.get();
|
||||
}
|
||||
|
||||
private:
|
||||
osg::ref_ptr<osg::Texture2D> mReflectionTexture;
|
||||
osg::ref_ptr<ClipCullNode> mClipCullNode;
|
||||
osg::ref_ptr<osg::Node> mScene;
|
||||
osg::Node::NodeMask mNodeMask;
|
||||
osg::Matrix mViewMatrix{ osg::Matrix::identity() };
|
||||
};
|
||||
|
||||
/// DepthClampCallback enables GL_DEPTH_CLAMP for the current draw, if supported.
|
||||
|
@ -474,6 +439,7 @@ Water::Water(osg::Group *parent, osg::Group* sceneRoot, Resource::ResourceSystem
|
|||
, mTop(0)
|
||||
, mInterior(false)
|
||||
, mCullCallback(nullptr)
|
||||
, mShaderWaterStateSetUpdater(nullptr)
|
||||
{
|
||||
mSimulation.reset(new RippleSimulation(mSceneRoot, resourceSystem));
|
||||
|
||||
|
@ -528,22 +494,31 @@ void Water::setCullCallback(osg::Callback* callback)
|
|||
|
||||
void Water::updateWaterMaterial()
|
||||
{
|
||||
if (mShaderWaterStateSetUpdater)
|
||||
{
|
||||
mWaterNode->removeCullCallback(mShaderWaterStateSetUpdater);
|
||||
mShaderWaterStateSetUpdater = nullptr;
|
||||
}
|
||||
if (mReflection)
|
||||
{
|
||||
mReflection->removeChildren(0, mReflection->getNumChildren());
|
||||
mParent->removeChild(mReflection);
|
||||
mReflection = nullptr;
|
||||
}
|
||||
if (mRefraction)
|
||||
{
|
||||
mRefraction->removeChildren(0, mRefraction->getNumChildren());
|
||||
mParent->removeChild(mRefraction);
|
||||
mRefraction = nullptr;
|
||||
}
|
||||
|
||||
mWaterNode->setStateSet(nullptr);
|
||||
mWaterGeom->setStateSet(nullptr);
|
||||
mWaterGeom->setUpdateCallback(nullptr);
|
||||
|
||||
if (Settings::Manager::getBool("shader", "Water"))
|
||||
{
|
||||
mReflection = new Reflection(mInterior);
|
||||
unsigned int rttSize = Settings::Manager::getInt("rtt size", "Water");
|
||||
|
||||
mReflection = new Reflection(rttSize, mInterior);
|
||||
mReflection->setWaterLevel(mTop);
|
||||
mReflection->setScene(mSceneRoot);
|
||||
if (mCullCallback)
|
||||
|
@ -552,7 +527,7 @@ void Water::updateWaterMaterial()
|
|||
|
||||
if (Settings::Manager::getBool("refraction", "Water"))
|
||||
{
|
||||
mRefraction = new Refraction;
|
||||
mRefraction = new Refraction(rttSize);
|
||||
mRefraction->setWaterLevel(mTop);
|
||||
mRefraction->setScene(mSceneRoot);
|
||||
if (mCullCallback)
|
||||
|
@ -560,7 +535,7 @@ void Water::updateWaterMaterial()
|
|||
mParent->addChild(mRefraction);
|
||||
}
|
||||
|
||||
createShaderWaterStateSet(mWaterGeom, mReflection, mRefraction);
|
||||
createShaderWaterStateSet(mWaterNode, mReflection, mRefraction);
|
||||
}
|
||||
else
|
||||
createSimpleWaterStateSet(mWaterGeom, Fallback::Map::getFloat("Water_World_Alpha"));
|
||||
|
@ -568,12 +543,12 @@ void Water::updateWaterMaterial()
|
|||
updateVisible();
|
||||
}
|
||||
|
||||
osg::Camera *Water::getReflectionCamera()
|
||||
osg::Node *Water::getReflectionNode()
|
||||
{
|
||||
return mReflection;
|
||||
}
|
||||
|
||||
osg::Camera *Water::getRefractionCamera()
|
||||
osg::Node* Water::getRefractionNode()
|
||||
{
|
||||
return mRefraction;
|
||||
}
|
||||
|
@ -620,17 +595,79 @@ void Water::createSimpleWaterStateSet(osg::Node* node, float alpha)
|
|||
sceneManager->setForceShaders(oldValue);
|
||||
}
|
||||
|
||||
class ShaderWaterStateSetUpdater : public SceneUtil::StateSetUpdater
|
||||
{
|
||||
public:
|
||||
ShaderWaterStateSetUpdater(Water* water, Reflection* reflection, Refraction* refraction, osg::ref_ptr<osg::Program> program, osg::ref_ptr<osg::Texture2D> normalMap)
|
||||
: mWater(water)
|
||||
, mReflection(reflection)
|
||||
, mRefraction(refraction)
|
||||
, mProgram(program)
|
||||
, mNormalMap(normalMap)
|
||||
{
|
||||
}
|
||||
|
||||
void setDefaults(osg::StateSet* stateset) override
|
||||
{
|
||||
stateset->addUniform(new osg::Uniform("normalMap", 0));
|
||||
stateset->setTextureAttributeAndModes(0, mNormalMap, osg::StateAttribute::ON);
|
||||
stateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF);
|
||||
stateset->setAttributeAndModes(mProgram, osg::StateAttribute::ON);
|
||||
|
||||
stateset->addUniform(new osg::Uniform("reflectionMap", 1));
|
||||
if (mRefraction)
|
||||
{
|
||||
stateset->addUniform(new osg::Uniform("refractionMap", 2));
|
||||
stateset->addUniform(new osg::Uniform("refractionDepthMap", 3));
|
||||
stateset->setRenderBinDetails(MWRender::RenderBin_Default, "RenderBin");
|
||||
}
|
||||
else
|
||||
{
|
||||
stateset->setMode(GL_BLEND, osg::StateAttribute::ON);
|
||||
stateset->setRenderBinDetails(MWRender::RenderBin_Water, "RenderBin");
|
||||
osg::ref_ptr<osg::Depth> depth(new osg::Depth);
|
||||
depth->setWriteMask(false);
|
||||
stateset->setAttributeAndModes(depth, osg::StateAttribute::ON);
|
||||
}
|
||||
}
|
||||
|
||||
void apply(osg::StateSet* stateset, osg::NodeVisitor* nv) override
|
||||
{
|
||||
osgUtil::CullVisitor* cv = static_cast<osgUtil::CullVisitor*>(nv);
|
||||
stateset->setTextureAttributeAndModes(1, mReflection->getColorTexture(cv), osg::StateAttribute::ON);
|
||||
|
||||
if (mRefraction)
|
||||
{
|
||||
stateset->setTextureAttributeAndModes(2, mRefraction->getColorTexture(cv), osg::StateAttribute::ON);
|
||||
stateset->setTextureAttributeAndModes(3, mRefraction->getDepthTexture(cv), osg::StateAttribute::ON);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
Water* mWater;
|
||||
Reflection* mReflection;
|
||||
Refraction* mRefraction;
|
||||
osg::ref_ptr<osg::Program> mProgram;
|
||||
osg::ref_ptr<osg::Texture2D> mNormalMap;
|
||||
};
|
||||
|
||||
void Water::createShaderWaterStateSet(osg::Node* node, Reflection* reflection, Refraction* refraction)
|
||||
{
|
||||
// use a define map to conditionally compile the shader
|
||||
std::map<std::string, std::string> defineMap;
|
||||
defineMap.insert(std::make_pair(std::string("refraction_enabled"), std::string(refraction ? "1" : "0")));
|
||||
defineMap.insert(std::make_pair(std::string("refraction_enabled"), std::string(mRefraction ? "1" : "0")));
|
||||
|
||||
if (mResourceSystem->getSceneManager()->getShaderManager().stereoGeometryShaderEnabled())
|
||||
{
|
||||
defineMap["geometryShader"] = "1";
|
||||
}
|
||||
|
||||
Shader::ShaderManager& shaderMgr = mResourceSystem->getSceneManager()->getShaderManager();
|
||||
osg::ref_ptr<osg::Shader> vertexShader (shaderMgr.getShader("water_vertex.glsl", defineMap, osg::Shader::VERTEX));
|
||||
osg::ref_ptr<osg::Shader> fragmentShader (shaderMgr.getShader("water_fragment.glsl", defineMap, osg::Shader::FRAGMENT));
|
||||
osg::ref_ptr<osg::Shader> vertexShader(shaderMgr.getShader("water_vertex.glsl", defineMap, osg::Shader::VERTEX));
|
||||
osg::ref_ptr<osg::Shader> fragmentShader(shaderMgr.getShader("water_fragment.glsl", defineMap, osg::Shader::FRAGMENT));
|
||||
osg::ref_ptr<osg::Program> program = shaderMgr.getProgram(vertexShader, fragmentShader);
|
||||
|
||||
osg::ref_ptr<osg::Texture2D> normalMap (new osg::Texture2D(readPngImage(mResourcePath + "/shaders/water_nm.png")));
|
||||
osg::ref_ptr<osg::Texture2D> normalMap(new osg::Texture2D(readPngImage(mResourcePath + "/shaders/water_nm.png")));
|
||||
|
||||
if (normalMap->getImage())
|
||||
normalMap->getImage()->flipVertical();
|
||||
|
@ -639,47 +676,16 @@ void Water::createShaderWaterStateSet(osg::Node* node, Reflection* reflection, R
|
|||
normalMap->setMaxAnisotropy(16);
|
||||
normalMap->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR_MIPMAP_LINEAR);
|
||||
normalMap->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR);
|
||||
|
||||
|
||||
osg::ref_ptr<osg::StateSet> shaderStateset = new osg::StateSet;
|
||||
shaderStateset->addUniform(new osg::Uniform("normalMap", 0));
|
||||
shaderStateset->addUniform(new osg::Uniform("reflectionMap", 1));
|
||||
|
||||
shaderStateset->setTextureAttributeAndModes(0, normalMap, osg::StateAttribute::ON);
|
||||
shaderStateset->setTextureAttributeAndModes(1, reflection->getReflectionTexture(), osg::StateAttribute::ON);
|
||||
|
||||
if (refraction)
|
||||
{
|
||||
shaderStateset->setTextureAttributeAndModes(2, refraction->getRefractionTexture(), osg::StateAttribute::ON);
|
||||
shaderStateset->setTextureAttributeAndModes(3, refraction->getRefractionDepthTexture(), osg::StateAttribute::ON);
|
||||
shaderStateset->addUniform(new osg::Uniform("refractionMap", 2));
|
||||
shaderStateset->addUniform(new osg::Uniform("refractionDepthMap", 3));
|
||||
shaderStateset->setRenderBinDetails(MWRender::RenderBin_Default, "RenderBin");
|
||||
}
|
||||
else
|
||||
{
|
||||
shaderStateset->setMode(GL_BLEND, osg::StateAttribute::ON);
|
||||
|
||||
shaderStateset->setRenderBinDetails(MWRender::RenderBin_Water, "RenderBin");
|
||||
|
||||
osg::ref_ptr<osg::Depth> depth (new osg::Depth);
|
||||
depth->setWriteMask(false);
|
||||
shaderStateset->setAttributeAndModes(depth, osg::StateAttribute::ON);
|
||||
}
|
||||
|
||||
shaderStateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF);
|
||||
|
||||
osg::ref_ptr<osg::Program> program (new osg::Program);
|
||||
program->addShader(vertexShader);
|
||||
program->addShader(fragmentShader);
|
||||
auto method = mResourceSystem->getSceneManager()->getLightingMethod();
|
||||
if (method == SceneUtil::LightingMethod::SingleUBO)
|
||||
program->addBindUniformBlock("LightBufferBinding", static_cast<int>(Shader::UBOBinding::LightBuffer));
|
||||
shaderStateset->setAttributeAndModes(program, osg::StateAttribute::ON);
|
||||
|
||||
node->setStateSet(shaderStateset);
|
||||
|
||||
mRainIntensityUpdater = new RainIntensityUpdater();
|
||||
node->setUpdateCallback(mRainIntensityUpdater);
|
||||
|
||||
mShaderWaterStateSetUpdater = new ShaderWaterStateSetUpdater(this, mReflection, mRefraction, program, normalMap);
|
||||
node->addCullCallback(mShaderWaterStateSetUpdater);
|
||||
}
|
||||
|
||||
void Water::processChangedSettings(const Settings::CategorySettingVector& settings)
|
||||
|
@ -693,13 +699,11 @@ Water::~Water()
|
|||
|
||||
if (mReflection)
|
||||
{
|
||||
mReflection->removeChildren(0, mReflection->getNumChildren());
|
||||
mParent->removeChild(mReflection);
|
||||
mReflection = nullptr;
|
||||
}
|
||||
if (mRefraction)
|
||||
{
|
||||
mRefraction->removeChildren(0, mRefraction->getNumChildren());
|
||||
mParent->removeChild(mRefraction);
|
||||
mRefraction = nullptr;
|
||||
}
|
||||
|
|
|
@ -72,6 +72,7 @@ namespace MWRender
|
|||
bool mInterior;
|
||||
|
||||
osg::Callback* mCullCallback;
|
||||
osg::ref_ptr<osg::NodeCallback> mShaderWaterStateSetUpdater;
|
||||
|
||||
osg::Vec3f getSceneNodeCoordinates(int gridX, int gridY);
|
||||
void updateVisible();
|
||||
|
@ -116,8 +117,8 @@ namespace MWRender
|
|||
|
||||
void update(float dt);
|
||||
|
||||
osg::Camera *getReflectionCamera();
|
||||
osg::Camera *getRefractionCamera();
|
||||
osg::Node* getReflectionNode();
|
||||
osg::Node* getRefractionNode();
|
||||
|
||||
void processChangedSettings(const Settings::CategorySettingVector& settings);
|
||||
};
|
||||
|
|
|
@ -26,6 +26,12 @@
|
|||
#include "../mwmechanics/creaturestats.hpp"
|
||||
#include "../mwmechanics/combat.hpp"
|
||||
#include "../mwmechanics/weapontype.hpp"
|
||||
#include "../mwmechanics/actorutil.hpp"
|
||||
|
||||
#ifdef USE_OPENXR
|
||||
#include "../mwvr/vrenvironment.hpp"
|
||||
#include "../mwvr/vranimation.hpp"
|
||||
#endif
|
||||
|
||||
#include "animation.hpp"
|
||||
#include "rotatecontroller.hpp"
|
||||
|
@ -154,8 +160,15 @@ void WeaponAnimation::releaseArrow(MWWorld::Ptr actor, float attackStrength)
|
|||
*/
|
||||
|
||||
// The orientation of the launched projectile. Always the same as the actor orientation, even if the ArrowBone's orientation dictates otherwise.
|
||||
osg::Quat orient = osg::Quat(actor.getRefData().getPosition().rot[0], osg::Vec3f(-1,0,0))
|
||||
* osg::Quat(actor.getRefData().getPosition().rot[2], osg::Vec3f(0,0,-1));
|
||||
osg::Quat orient = osg::Quat(actor.getRefData().getPosition().rot[0], osg::Vec3f(-1, 0, 0))
|
||||
* osg::Quat(actor.getRefData().getPosition().rot[2], osg::Vec3f(0, 0, -1));
|
||||
|
||||
#ifdef USE_OPENXR
|
||||
bool isPlayer = actor == MWMechanics::getPlayer();
|
||||
// In VR weapon aim is taken from the real orientation of the weapon.
|
||||
if(isPlayer)
|
||||
orient = MWVR::Environment::get().getPlayerAnimation()->getWeaponTransformMatrix().getRotate();
|
||||
#endif
|
||||
|
||||
const MWWorld::Store<ESM::GameSetting> &gmst =
|
||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
|
||||
|
@ -228,6 +241,9 @@ void WeaponAnimation::releaseArrow(MWWorld::Ptr actor, float attackStrength)
|
|||
|
||||
MWWorld::Ptr weaponPtr = *weapon;
|
||||
MWWorld::Ptr ammoPtr = *ammo;
|
||||
#ifdef USE_OPENXR
|
||||
orient = osg::computeLocalToWorld(nodepaths[0]).getRotate();
|
||||
#endif
|
||||
MWBase::Environment::get().getWorld()->launchProjectile(actor, ammoPtr, launchPos, orient, weaponPtr, speed, attackStrength);
|
||||
|
||||
inv.remove(ammoPtr, 1, actor);
|
||||
|
|
89
apps/openmw/mwvr/openxraction.cpp
Normal file
89
apps/openmw/mwvr/openxraction.cpp
Normal file
|
@ -0,0 +1,89 @@
|
|||
#include "openxraction.hpp"
|
||||
#include "openxrdebug.hpp"
|
||||
#include "vrenvironment.hpp"
|
||||
#include "openxrmanagerimpl.hpp"
|
||||
|
||||
namespace MWVR
|
||||
{
|
||||
|
||||
OpenXRAction::OpenXRAction(
|
||||
XrAction action,
|
||||
XrActionType actionType,
|
||||
const std::string& actionName,
|
||||
const std::string& localName)
|
||||
: mAction(action)
|
||||
, mType(actionType)
|
||||
, mName(actionName)
|
||||
, mLocalName(localName)
|
||||
{
|
||||
VrDebug::setName(action, "OpenMW XR Action " + actionName);
|
||||
};
|
||||
|
||||
OpenXRAction::~OpenXRAction() {
|
||||
if (mAction)
|
||||
{
|
||||
xrDestroyAction(mAction);
|
||||
}
|
||||
}
|
||||
|
||||
bool OpenXRAction::getFloat(XrPath subactionPath, float& value)
|
||||
{
|
||||
auto* xr = Environment::get().getManager();
|
||||
XrActionStateGetInfo getInfo{ XR_TYPE_ACTION_STATE_GET_INFO };
|
||||
getInfo.action = mAction;
|
||||
getInfo.subactionPath = subactionPath;
|
||||
|
||||
XrActionStateFloat xrValue{ XR_TYPE_ACTION_STATE_FLOAT };
|
||||
CHECK_XRCMD(xrGetActionStateFloat(xr->impl().xrSession(), &getInfo, &xrValue));
|
||||
|
||||
if (xrValue.isActive)
|
||||
value = xrValue.currentState;
|
||||
return xrValue.isActive;
|
||||
}
|
||||
|
||||
bool OpenXRAction::getBool(XrPath subactionPath, bool& value)
|
||||
{
|
||||
auto* xr = Environment::get().getManager();
|
||||
XrActionStateGetInfo getInfo{ XR_TYPE_ACTION_STATE_GET_INFO };
|
||||
getInfo.action = mAction;
|
||||
getInfo.subactionPath = subactionPath;
|
||||
|
||||
XrActionStateBoolean xrValue{ XR_TYPE_ACTION_STATE_BOOLEAN };
|
||||
CHECK_XRCMD(xrGetActionStateBoolean(xr->impl().xrSession(), &getInfo, &xrValue));
|
||||
|
||||
if (xrValue.isActive)
|
||||
value = xrValue.currentState;
|
||||
return xrValue.isActive;
|
||||
}
|
||||
|
||||
// Pose action only checks if the pose is active or not
|
||||
bool OpenXRAction::getPoseIsActive(XrPath subactionPath)
|
||||
{
|
||||
auto* xr = Environment::get().getManager();
|
||||
XrActionStateGetInfo getInfo{ XR_TYPE_ACTION_STATE_GET_INFO };
|
||||
getInfo.action = mAction;
|
||||
getInfo.subactionPath = subactionPath;
|
||||
|
||||
XrActionStatePose xrValue{ XR_TYPE_ACTION_STATE_POSE };
|
||||
CHECK_XRCMD(xrGetActionStatePose(xr->impl().xrSession(), &getInfo, &xrValue));
|
||||
|
||||
return xrValue.isActive;
|
||||
}
|
||||
|
||||
bool OpenXRAction::applyHaptics(XrPath subactionPath, float amplitude)
|
||||
{
|
||||
amplitude = std::max(0.f, std::min(1.f, amplitude));
|
||||
|
||||
auto* xr = Environment::get().getManager();
|
||||
XrHapticVibration vibration{ XR_TYPE_HAPTIC_VIBRATION };
|
||||
vibration.amplitude = amplitude;
|
||||
vibration.duration = XR_MIN_HAPTIC_DURATION;
|
||||
vibration.frequency = XR_FREQUENCY_UNSPECIFIED;
|
||||
|
||||
XrHapticActionInfo hapticActionInfo{ XR_TYPE_HAPTIC_ACTION_INFO };
|
||||
hapticActionInfo.action = mAction;
|
||||
hapticActionInfo.subactionPath = subactionPath;
|
||||
CHECK_XRCMD(xrApplyHapticFeedback(xr->impl().xrSession(), &hapticActionInfo, (XrHapticBaseHeader*)&vibration));
|
||||
return true;
|
||||
}
|
||||
}
|
36
apps/openmw/mwvr/openxraction.hpp
Normal file
36
apps/openmw/mwvr/openxraction.hpp
Normal file
|
@ -0,0 +1,36 @@
|
|||
#ifndef OPENXR_ACTION_HPP
|
||||
#define OPENXR_ACTION_HPP
|
||||
|
||||
#include <openxr/openxr.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
|
||||
namespace MWVR
|
||||
{
|
||||
/// \brief C++ wrapper for the XrAction type
|
||||
struct OpenXRAction
|
||||
{
|
||||
private:
|
||||
OpenXRAction(const OpenXRAction&) = default;
|
||||
OpenXRAction& operator=(const OpenXRAction&) = default;
|
||||
public:
|
||||
OpenXRAction(XrAction action, XrActionType actionType, const std::string& actionName, const std::string& localName);
|
||||
~OpenXRAction();
|
||||
|
||||
//! Convenience
|
||||
operator XrAction() { return mAction; }
|
||||
|
||||
bool getFloat(XrPath subactionPath, float& value);
|
||||
bool getBool(XrPath subactionPath, bool& value);
|
||||
bool getPoseIsActive(XrPath subactionPath);
|
||||
bool applyHaptics(XrPath subactionPath, float amplitude);
|
||||
|
||||
XrAction mAction = XR_NULL_HANDLE;
|
||||
XrActionType mType;
|
||||
std::string mName;
|
||||
std::string mLocalName;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
232
apps/openmw/mwvr/openxractionset.cpp
Normal file
232
apps/openmw/mwvr/openxractionset.cpp
Normal file
|
@ -0,0 +1,232 @@
|
|||
#include "openxractionset.hpp"
|
||||
#include "openxrdebug.hpp"
|
||||
|
||||
#include "vrenvironment.hpp"
|
||||
#include "openxrmanager.hpp"
|
||||
#include "openxrmanagerimpl.hpp"
|
||||
#include "openxraction.hpp"
|
||||
|
||||
#include <openxr/openxr.h>
|
||||
|
||||
#include <components/misc/stringops.hpp>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
// TODO: should implement actual safe strcpy
|
||||
#ifdef __linux__
|
||||
#define strcpy_s(dst, src) int(strcpy(dst, src) != nullptr)
|
||||
#endif
|
||||
|
||||
namespace MWVR
|
||||
{
|
||||
|
||||
OpenXRActionSet::OpenXRActionSet(const std::string& actionSetName, std::shared_ptr<AxisAction::Deadzone> deadzone)
|
||||
: mActionSet(nullptr)
|
||||
, mLocalizedName(actionSetName)
|
||||
, mInternalName(Misc::StringUtils::lowerCase(actionSetName))
|
||||
, mDeadzone(deadzone)
|
||||
{
|
||||
mActionSet = createActionSet(actionSetName);
|
||||
// When starting to account for more devices than oculus touch, this section may need some expansion/redesign.
|
||||
};
|
||||
|
||||
void
|
||||
OpenXRActionSet::createPoseAction(
|
||||
TrackedLimb limb,
|
||||
const std::string& actionName,
|
||||
const std::string& localName)
|
||||
{
|
||||
mTrackerMap.emplace(limb, new PoseAction(std::move(createXRAction(XR_ACTION_TYPE_POSE_INPUT, actionName, localName))));
|
||||
}
|
||||
|
||||
void
|
||||
OpenXRActionSet::createHapticsAction(
|
||||
TrackedLimb limb,
|
||||
const std::string& actionName,
|
||||
const std::string& localName)
|
||||
{
|
||||
mHapticsMap.emplace(limb, new HapticsAction(std::move(createXRAction(XR_ACTION_TYPE_VIBRATION_OUTPUT, actionName, localName))));
|
||||
}
|
||||
|
||||
template<>
|
||||
void
|
||||
OpenXRActionSet::createMWAction<AxisAction>(
|
||||
int openMWAction,
|
||||
const std::string& actionName,
|
||||
const std::string& localName)
|
||||
{
|
||||
auto xrAction = createXRAction(AxisAction::ActionType, mInternalName + "_" + actionName, mLocalizedName + " " + localName);
|
||||
mActionMap.emplace(actionName, new AxisAction(openMWAction, std::move(xrAction), mDeadzone));
|
||||
}
|
||||
|
||||
template<typename A>
|
||||
void
|
||||
OpenXRActionSet::createMWAction(
|
||||
int openMWAction,
|
||||
const std::string& actionName,
|
||||
const std::string& localName)
|
||||
{
|
||||
auto xrAction = createXRAction(A::ActionType, mInternalName + "_" + actionName, mLocalizedName + " " + localName);
|
||||
mActionMap.emplace(actionName, new A(openMWAction, std::move(xrAction)));
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
OpenXRActionSet::createMWAction(
|
||||
VrControlType controlType,
|
||||
int openMWAction,
|
||||
const std::string& actionName,
|
||||
const std::string& localName)
|
||||
{
|
||||
switch (controlType)
|
||||
{
|
||||
case VrControlType::Press:
|
||||
return createMWAction<ButtonPressAction>(openMWAction, actionName, localName);
|
||||
case VrControlType::LongPress:
|
||||
return createMWAction<ButtonLongPressAction>(openMWAction, actionName, localName);
|
||||
case VrControlType::Hold:
|
||||
return createMWAction<ButtonHoldAction>(openMWAction, actionName, localName);
|
||||
case VrControlType::Axis:
|
||||
return createMWAction<AxisAction>(openMWAction, actionName, localName);
|
||||
//case VrControlType::Pose:
|
||||
// return createMWAction<PoseAction>(openMWAction, actionName, localName);
|
||||
//case VrControlType::Haptic:
|
||||
// return createMWAction<HapticsAction>(openMWAction, actionName, localName);
|
||||
default:
|
||||
Log(Debug::Warning) << "createMWAction: pose/haptics Not implemented here";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
XrActionSet
|
||||
OpenXRActionSet::createActionSet(const std::string& name)
|
||||
{
|
||||
std::string localized_name = name;
|
||||
std::string internal_name = Misc::StringUtils::lowerCase(name);
|
||||
auto* xr = Environment::get().getManager();
|
||||
XrActionSet actionSet = XR_NULL_HANDLE;
|
||||
XrActionSetCreateInfo createInfo{ XR_TYPE_ACTION_SET_CREATE_INFO };
|
||||
strcpy_s(createInfo.actionSetName, internal_name.c_str());
|
||||
strcpy_s(createInfo.localizedActionSetName, localized_name.c_str());
|
||||
createInfo.priority = 0;
|
||||
CHECK_XRCMD(xrCreateActionSet(xr->impl().xrInstance(), &createInfo, &actionSet));
|
||||
VrDebug::setName(actionSet, "OpenMW XR Action Set " + name);
|
||||
return actionSet;
|
||||
}
|
||||
|
||||
void OpenXRActionSet::suggestBindings(std::vector<XrActionSuggestedBinding>& xrSuggestedBindings, const SuggestedBindings& mwSuggestedBindings)
|
||||
{
|
||||
std::vector<XrActionSuggestedBinding> suggestedBindings;
|
||||
if (!mTrackerMap.empty())
|
||||
{
|
||||
suggestedBindings.emplace_back(XrActionSuggestedBinding{ *mTrackerMap[TrackedLimb::LEFT_HAND], getXrPath("/user/hand/left/input/aim/pose") });
|
||||
suggestedBindings.emplace_back(XrActionSuggestedBinding{ *mTrackerMap[TrackedLimb::RIGHT_HAND], getXrPath("/user/hand/right/input/aim/pose") });
|
||||
}
|
||||
if(!mHapticsMap.empty())
|
||||
{
|
||||
suggestedBindings.emplace_back(XrActionSuggestedBinding{ *mHapticsMap[TrackedLimb::LEFT_HAND], getXrPath("/user/hand/left/output/haptic") });
|
||||
suggestedBindings.emplace_back(XrActionSuggestedBinding{ *mHapticsMap[TrackedLimb::RIGHT_HAND], getXrPath("/user/hand/right/output/haptic") });
|
||||
};
|
||||
|
||||
for (auto& mwSuggestedBinding : mwSuggestedBindings)
|
||||
{
|
||||
auto xrAction = mActionMap.find(mwSuggestedBinding.action);
|
||||
if (xrAction == mActionMap.end())
|
||||
{
|
||||
Log(Debug::Error) << "OpenXRActionSet: Unknown action " << mwSuggestedBinding.action;
|
||||
continue;
|
||||
}
|
||||
suggestedBindings.push_back({ *xrAction->second, getXrPath(mwSuggestedBinding.path) });
|
||||
}
|
||||
|
||||
xrSuggestedBindings.insert(xrSuggestedBindings.end(), suggestedBindings.begin(), suggestedBindings.end());
|
||||
}
|
||||
|
||||
XrSpace OpenXRActionSet::xrActionSpace(TrackedLimb limb)
|
||||
{
|
||||
return mTrackerMap[limb]->xrSpace();
|
||||
}
|
||||
|
||||
|
||||
std::unique_ptr<OpenXRAction>
|
||||
OpenXRActionSet::createXRAction(
|
||||
XrActionType actionType,
|
||||
const std::string& actionName,
|
||||
const std::string& localName)
|
||||
{
|
||||
std::vector<XrPath> subactionPaths;
|
||||
XrActionCreateInfo createInfo{ XR_TYPE_ACTION_CREATE_INFO };
|
||||
createInfo.actionType = actionType;
|
||||
strcpy_s(createInfo.actionName, actionName.c_str());
|
||||
strcpy_s(createInfo.localizedActionName, localName.c_str());
|
||||
|
||||
XrAction action = XR_NULL_HANDLE;
|
||||
CHECK_XRCMD(xrCreateAction(mActionSet, &createInfo, &action));
|
||||
return std::unique_ptr<OpenXRAction>{new OpenXRAction{ action, actionType, actionName, localName }};
|
||||
}
|
||||
|
||||
void
|
||||
OpenXRActionSet::updateControls()
|
||||
{
|
||||
auto* xr = Environment::get().getManager();
|
||||
if (!xr->impl().appShouldReadInput())
|
||||
return;
|
||||
|
||||
const XrActiveActionSet activeActionSet{ mActionSet, XR_NULL_PATH };
|
||||
XrActionsSyncInfo syncInfo{ XR_TYPE_ACTIONS_SYNC_INFO };
|
||||
syncInfo.countActiveActionSets = 1;
|
||||
syncInfo.activeActionSets = &activeActionSet;
|
||||
CHECK_XRCMD(xrSyncActions(xr->impl().xrSession(), &syncInfo));
|
||||
|
||||
mActionQueue.clear();
|
||||
for (auto& action : mActionMap)
|
||||
action.second->updateAndQueue(mActionQueue);
|
||||
}
|
||||
|
||||
XrPath OpenXRActionSet::getXrPath(const std::string& path)
|
||||
{
|
||||
auto* xr = Environment::get().getManager();
|
||||
XrPath xrpath = 0;
|
||||
CHECK_XRCMD(xrStringToPath(xr->impl().xrInstance(), path.c_str(), &xrpath));
|
||||
return xrpath;
|
||||
}
|
||||
|
||||
const Action* OpenXRActionSet::nextAction()
|
||||
{
|
||||
if (mActionQueue.empty())
|
||||
return nullptr;
|
||||
|
||||
const auto* action = mActionQueue.front();
|
||||
mActionQueue.pop_front();
|
||||
return action;
|
||||
|
||||
}
|
||||
|
||||
Pose
|
||||
OpenXRActionSet::getLimbPose(
|
||||
int64_t time,
|
||||
TrackedLimb limb)
|
||||
{
|
||||
auto it = mTrackerMap.find(limb);
|
||||
if (it == mTrackerMap.end())
|
||||
{
|
||||
Log(Debug::Error) << "OpenXRActionSet: No such tracker: " << limb;
|
||||
return Pose{};
|
||||
}
|
||||
|
||||
it->second->update(time);
|
||||
return it->second->value();
|
||||
}
|
||||
|
||||
void OpenXRActionSet::applyHaptics(TrackedLimb limb, float intensity)
|
||||
{
|
||||
auto it = mHapticsMap.find(limb);
|
||||
if (it == mHapticsMap.end())
|
||||
{
|
||||
Log(Debug::Error) << "OpenXRActionSet: No such tracker: " << limb;
|
||||
return;
|
||||
}
|
||||
|
||||
it->second->apply(intensity);
|
||||
}
|
||||
}
|
58
apps/openmw/mwvr/openxractionset.hpp
Normal file
58
apps/openmw/mwvr/openxractionset.hpp
Normal file
|
@ -0,0 +1,58 @@
|
|||
#ifndef OPENXR_ACTIONSET_HPP
|
||||
#define OPENXR_ACTIONSET_HPP
|
||||
|
||||
#include "vrinput.hpp"
|
||||
|
||||
#include <vector>
|
||||
#include <array>
|
||||
|
||||
namespace MWVR
|
||||
{
|
||||
/// \brief Generates and manages an OpenXR ActionSet and associated actions.
|
||||
class OpenXRActionSet
|
||||
{
|
||||
public:
|
||||
using Actions = MWInput::Actions;
|
||||
|
||||
OpenXRActionSet(const std::string& actionSetName, std::shared_ptr<AxisAction::Deadzone> deadzone);
|
||||
|
||||
//! Update all controls and queue any actions
|
||||
void updateControls();
|
||||
|
||||
//! Get next action from queue (repeat until null is returned)
|
||||
const Action* nextAction();
|
||||
|
||||
//! Get current pose of limb in space.
|
||||
Pose getLimbPose(int64_t time, TrackedLimb limb);
|
||||
|
||||
//! Apply haptics of the given intensity to the given limb
|
||||
void applyHaptics(TrackedLimb limb, float intensity);
|
||||
|
||||
XrActionSet xrActionSet() { return mActionSet; };
|
||||
void suggestBindings(std::vector<XrActionSuggestedBinding>& xrSuggestedBindings, const SuggestedBindings& mwSuggestedBindings);
|
||||
|
||||
XrSpace xrActionSpace(TrackedLimb limb);
|
||||
|
||||
void createMWAction(VrControlType controlType, int openMWAction, const std::string& actionName, const std::string& localName);
|
||||
void createPoseAction(TrackedLimb limb, const std::string& actionName, const std::string& localName);
|
||||
void createHapticsAction(TrackedLimb limb, const std::string& actionName, const std::string& localName);
|
||||
|
||||
protected:
|
||||
template<typename A>
|
||||
void createMWAction(int openMWAction, const std::string& actionName, const std::string& localName);
|
||||
std::unique_ptr<OpenXRAction> createXRAction(XrActionType actionType, const std::string& actionName, const std::string& localName);
|
||||
XrPath getXrPath(const std::string& path);
|
||||
XrActionSet createActionSet(const std::string& name);
|
||||
|
||||
XrActionSet mActionSet{ nullptr };
|
||||
std::string mLocalizedName{};
|
||||
std::string mInternalName{};
|
||||
std::map<std::string, std::unique_ptr<Action>> mActionMap;
|
||||
std::map<TrackedLimb, std::unique_ptr<PoseAction>> mTrackerMap;
|
||||
std::map<TrackedLimb, std::unique_ptr<HapticsAction>> mHapticsMap;
|
||||
std::deque<const Action*> mActionQueue{};
|
||||
std::shared_ptr<AxisAction::Deadzone> mDeadzone;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
36
apps/openmw/mwvr/openxrdebug.cpp
Normal file
36
apps/openmw/mwvr/openxrdebug.cpp
Normal file
|
@ -0,0 +1,36 @@
|
|||
#include "openxrdebug.hpp"
|
||||
#include "openxrmanagerimpl.hpp"
|
||||
#include "vrenvironment.hpp"
|
||||
|
||||
// The OpenXR SDK's platform headers assume we've included these windows headers
|
||||
#ifdef _WIN32
|
||||
#include <Windows.h>
|
||||
#include <objbase.h>
|
||||
|
||||
#elif __linux__
|
||||
#include <X11/Xlib.h>
|
||||
#include <GL/glx.h>
|
||||
#undef None
|
||||
|
||||
#else
|
||||
#error Unsupported platform
|
||||
#endif
|
||||
|
||||
#include <openxr/openxr_platform_defines.h>
|
||||
#include <openxr/openxr_reflection.h>
|
||||
|
||||
void MWVR::VrDebug::setName(uint64_t handle, XrObjectType type, const std::string& name)
|
||||
{
|
||||
auto& xrManager = Environment::get().getManager()->impl();
|
||||
if (xrManager.xrExtensionIsEnabled(XR_EXT_DEBUG_UTILS_EXTENSION_NAME))
|
||||
{
|
||||
XrDebugUtilsObjectNameInfoEXT nameInfo{ XR_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT, nullptr };
|
||||
nameInfo.objectHandle = handle;
|
||||
nameInfo.objectType = type;
|
||||
nameInfo.objectName = name.c_str();
|
||||
|
||||
static PFN_xrSetDebugUtilsObjectNameEXT setDebugUtilsObjectNameEXT
|
||||
= reinterpret_cast<PFN_xrSetDebugUtilsObjectNameEXT>(xrManager.xrGetFunction("xrSetDebugUtilsObjectNameEXT"));
|
||||
CHECK_XRCMD(setDebugUtilsObjectNameEXT(xrManager.xrInstance(), &nameInfo));
|
||||
}
|
||||
}
|
73
apps/openmw/mwvr/openxrdebug.hpp
Normal file
73
apps/openmw/mwvr/openxrdebug.hpp
Normal file
|
@ -0,0 +1,73 @@
|
|||
#ifndef OPENXR_DEBUG_HPP
|
||||
#define OPENXR_DEBUG_HPP
|
||||
|
||||
#include <openxr/openxr.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace MWVR
|
||||
{
|
||||
namespace VrDebug
|
||||
{
|
||||
//! Translates an OpenXR object to the associated XrObjectType enum value
|
||||
template<typename T> XrObjectType getObjectType(T t);
|
||||
|
||||
//! Associates a name with an OpenXR symbol if XR_EXT_debug_utils is enabled
|
||||
template<typename T> void setName(T t, const std::string& name);
|
||||
|
||||
//! Associates a name with an OpenXR symbol if XR_EXT_debug_utils is enabled
|
||||
void setName(uint64_t handle, XrObjectType type, const std::string& name);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T> inline void MWVR::VrDebug::setName(T t, const std::string& name)
|
||||
{
|
||||
setName(reinterpret_cast<uint64_t>(t), getObjectType(t), name);
|
||||
}
|
||||
|
||||
template<> inline XrObjectType MWVR::VrDebug::getObjectType<XrInstance>(XrInstance)
|
||||
{
|
||||
return XR_OBJECT_TYPE_INSTANCE;
|
||||
}
|
||||
|
||||
template<> inline XrObjectType MWVR::VrDebug::getObjectType<XrSession>(XrSession)
|
||||
{
|
||||
return XR_OBJECT_TYPE_SESSION;
|
||||
}
|
||||
|
||||
template<> inline XrObjectType MWVR::VrDebug::getObjectType<XrSpace>(XrSpace)
|
||||
{
|
||||
return XR_OBJECT_TYPE_SPACE;
|
||||
}
|
||||
|
||||
template<> inline XrObjectType MWVR::VrDebug::getObjectType<XrActionSet>(XrActionSet)
|
||||
{
|
||||
return XR_OBJECT_TYPE_ACTION_SET;
|
||||
}
|
||||
|
||||
template<> inline XrObjectType MWVR::VrDebug::getObjectType<XrAction>(XrAction)
|
||||
{
|
||||
return XR_OBJECT_TYPE_ACTION;
|
||||
}
|
||||
|
||||
template<> inline XrObjectType MWVR::VrDebug::getObjectType<XrDebugUtilsMessengerEXT>(XrDebugUtilsMessengerEXT)
|
||||
{
|
||||
return XR_OBJECT_TYPE_DEBUG_UTILS_MESSENGER_EXT;
|
||||
}
|
||||
|
||||
template<> inline XrObjectType MWVR::VrDebug::getObjectType<XrSpatialAnchorMSFT>(XrSpatialAnchorMSFT)
|
||||
{
|
||||
return XR_OBJECT_TYPE_SPATIAL_ANCHOR_MSFT;
|
||||
}
|
||||
|
||||
template<> inline XrObjectType MWVR::VrDebug::getObjectType<XrHandTrackerEXT>(XrHandTrackerEXT)
|
||||
{
|
||||
return XR_OBJECT_TYPE_HAND_TRACKER_EXT;
|
||||
}
|
||||
|
||||
template<typename T> inline XrObjectType MWVR::VrDebug::getObjectType(T t)
|
||||
{
|
||||
return XR_OBJECT_TYPE_UNKNOWN;
|
||||
}
|
||||
|
||||
#endif
|
188
apps/openmw/mwvr/openxrinput.cpp
Normal file
188
apps/openmw/mwvr/openxrinput.cpp
Normal file
|
@ -0,0 +1,188 @@
|
|||
#include "openxrinput.hpp"
|
||||
|
||||
#include "vrenvironment.hpp"
|
||||
#include "openxrmanager.hpp"
|
||||
#include "openxrmanagerimpl.hpp"
|
||||
#include "openxraction.hpp"
|
||||
|
||||
#include <openxr/openxr.h>
|
||||
|
||||
#include <components/misc/stringops.hpp>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
namespace MWVR
|
||||
{
|
||||
|
||||
OpenXRInput::OpenXRInput(std::shared_ptr<AxisAction::Deadzone> deadzone)
|
||||
{
|
||||
mActionSets.emplace(ActionSet::Gameplay, OpenXRActionSet("Gameplay", deadzone));
|
||||
mActionSets.emplace(ActionSet::GUI, OpenXRActionSet("GUI", deadzone));
|
||||
mActionSets.emplace(ActionSet::Tracking, OpenXRActionSet("Tracking", deadzone));
|
||||
mActionSets.emplace(ActionSet::Haptics, OpenXRActionSet("Haptics", deadzone));
|
||||
|
||||
|
||||
/*
|
||||
// Applicable actions not (yet) included
|
||||
A_QuickKey1,
|
||||
A_QuickKey2,
|
||||
A_QuickKey3,
|
||||
A_QuickKey4,
|
||||
A_QuickKey5,
|
||||
A_QuickKey6,
|
||||
A_QuickKey7,
|
||||
A_QuickKey8,
|
||||
A_QuickKey9,
|
||||
A_QuickKey10,
|
||||
A_QuickKeysMenu,
|
||||
A_QuickLoad,
|
||||
A_CycleSpellLeft,
|
||||
A_CycleSpellRight,
|
||||
A_CycleWeaponLeft,
|
||||
A_CycleWeaponRight,
|
||||
A_Screenshot, // Generate a VR screenshot?
|
||||
A_Console, // Currently awkward due to a lack of virtual keyboard, but should be included when that's in place
|
||||
*/
|
||||
|
||||
getActionSet(ActionSet::Gameplay).createMWAction(VrControlType::Press, MWInput::A_GameMenu, "game_menu", "Game Menu");
|
||||
getActionSet(ActionSet::Gameplay).createMWAction(VrControlType::Press, A_VrMetaMenu, "meta_menu", "Meta Menu");
|
||||
getActionSet(ActionSet::Gameplay).createMWAction(VrControlType::LongPress, A_Recenter, "reposition_menu", "Reposition Menu");
|
||||
getActionSet(ActionSet::Gameplay).createMWAction(VrControlType::Press, MWInput::A_Inventory, "inventory", "Inventory");
|
||||
getActionSet(ActionSet::Gameplay).createMWAction(VrControlType::Press, MWInput::A_Activate, "activate", "Activate");
|
||||
getActionSet(ActionSet::Gameplay).createMWAction(VrControlType::Hold, MWInput::A_Use, "use", "Use");
|
||||
getActionSet(ActionSet::Gameplay).createMWAction(VrControlType::Hold, MWInput::A_Jump, "jump", "Jump");
|
||||
getActionSet(ActionSet::Gameplay).createMWAction(VrControlType::Press, MWInput::A_ToggleWeapon, "weapon", "Weapon");
|
||||
getActionSet(ActionSet::Gameplay).createMWAction(VrControlType::Press, MWInput::A_ToggleSpell, "spell", "Spell");
|
||||
getActionSet(ActionSet::Gameplay).createMWAction(VrControlType::Press, MWInput::A_CycleSpellLeft, "cycle_spell_left", "Cycle Spell Left");
|
||||
getActionSet(ActionSet::Gameplay).createMWAction(VrControlType::Press, MWInput::A_CycleSpellRight, "cycle_spell_right", "Cycle Spell Right");
|
||||
getActionSet(ActionSet::Gameplay).createMWAction(VrControlType::Press, MWInput::A_CycleWeaponLeft, "cycle_weapon_left", "Cycle Weapon Left");
|
||||
getActionSet(ActionSet::Gameplay).createMWAction(VrControlType::Press, MWInput::A_CycleWeaponRight, "cycle_weapon_right", "Cycle Weapon Right");
|
||||
getActionSet(ActionSet::Gameplay).createMWAction(VrControlType::Hold, MWInput::A_Sneak, "sneak", "Sneak");
|
||||
getActionSet(ActionSet::Gameplay).createMWAction(VrControlType::Press, MWInput::A_QuickKeysMenu, "quick_menu", "Quick Menu");
|
||||
getActionSet(ActionSet::Gameplay).createMWAction(VrControlType::Axis, MWInput::A_LookLeftRight, "look_left_right", "Look Left Right");
|
||||
getActionSet(ActionSet::Gameplay).createMWAction(VrControlType::Axis, MWInput::A_MoveForwardBackward, "move_forward_backward", "Move Forward Backward");
|
||||
getActionSet(ActionSet::Gameplay).createMWAction(VrControlType::Axis, MWInput::A_MoveLeftRight, "move_left_right", "Move Left Right");
|
||||
getActionSet(ActionSet::Gameplay).createMWAction(VrControlType::Press, MWInput::A_Journal, "journal_book", "Journal Book");
|
||||
getActionSet(ActionSet::Gameplay).createMWAction(VrControlType::Press, MWInput::A_QuickSave, "quick_save", "Quick Save");
|
||||
getActionSet(ActionSet::Gameplay).createMWAction(VrControlType::Press, MWInput::A_Rest, "rest", "Rest");
|
||||
getActionSet(ActionSet::Gameplay).createMWAction(VrControlType::Axis, A_ActivateTouch, "activate_touched", "Activate Touch");
|
||||
getActionSet(ActionSet::Gameplay).createMWAction(VrControlType::Press, MWInput::A_AlwaysRun, "always_run", "Always Run");
|
||||
getActionSet(ActionSet::Gameplay).createMWAction(VrControlType::Press, MWInput::A_AutoMove, "auto_move", "Auto Move");
|
||||
getActionSet(ActionSet::Gameplay).createMWAction(VrControlType::Press, MWInput::A_ToggleHUD, "toggle_hud", "Toggle HUD");
|
||||
getActionSet(ActionSet::Gameplay).createMWAction(VrControlType::Press, MWInput::A_ToggleDebug, "toggle_debug", "Toggle the debug hud");
|
||||
|
||||
getActionSet(ActionSet::GUI).createMWAction(VrControlType::Press, MWInput::A_GameMenu, "game_menu", "Game Menu");
|
||||
getActionSet(ActionSet::GUI).createMWAction(VrControlType::LongPress, A_Recenter, "reposition_menu", "Reposition Menu");
|
||||
getActionSet(ActionSet::GUI).createMWAction(VrControlType::Axis, A_MenuUpDown, "menu_up_down", "Menu Up Down");
|
||||
getActionSet(ActionSet::GUI).createMWAction(VrControlType::Axis, A_MenuLeftRight, "menu_left_right", "Menu Left Right");
|
||||
getActionSet(ActionSet::GUI).createMWAction(VrControlType::Press, A_MenuSelect, "menu_select", "Menu Select");
|
||||
getActionSet(ActionSet::GUI).createMWAction(VrControlType::Press, A_MenuBack, "menu_back", "Menu Back");
|
||||
getActionSet(ActionSet::GUI).createMWAction(VrControlType::Hold, MWInput::A_Use, "use", "Use");
|
||||
|
||||
getActionSet(ActionSet::Tracking).createPoseAction(TrackedLimb::LEFT_HAND, "left_hand_pose", "Left Hand Pose");
|
||||
getActionSet(ActionSet::Tracking).createPoseAction(TrackedLimb::RIGHT_HAND, "right_hand_pose", "Right Hand Pose");
|
||||
|
||||
getActionSet(ActionSet::Haptics).createHapticsAction(TrackedLimb::RIGHT_HAND, "right_hand_haptics", "Right Hand Haptics");
|
||||
getActionSet(ActionSet::Haptics).createHapticsAction(TrackedLimb::LEFT_HAND, "left_hand_haptics", "Left Hand Haptics");
|
||||
|
||||
|
||||
auto* xr = Environment::get().getManager();
|
||||
auto* trackingManager = Environment::get().getTrackingManager();
|
||||
|
||||
auto leftHandPath = trackingManager->stringToVRPath("/user/hand/left/input/aim/pose");
|
||||
auto rightHandPath = trackingManager->stringToVRPath("/user/hand/right/input/aim/pose");
|
||||
|
||||
xr->impl().tracker().addTrackingSpace(leftHandPath, getActionSet(ActionSet::Tracking).xrActionSpace(TrackedLimb::LEFT_HAND));
|
||||
xr->impl().tracker().addTrackingSpace(rightHandPath, getActionSet(ActionSet::Tracking).xrActionSpace(TrackedLimb::RIGHT_HAND));
|
||||
};
|
||||
|
||||
OpenXRActionSet& OpenXRInput::getActionSet(ActionSet actionSet)
|
||||
{
|
||||
auto it = mActionSets.find(actionSet);
|
||||
if (it == mActionSets.end())
|
||||
throw std::logic_error("No such action set");
|
||||
return it->second;
|
||||
}
|
||||
|
||||
void OpenXRInput::suggestBindings(ActionSet actionSet, std::string profilePath, const SuggestedBindings& mwSuggestedBindings)
|
||||
{
|
||||
getActionSet(actionSet).suggestBindings(mSuggestedBindings[profilePath], mwSuggestedBindings);
|
||||
}
|
||||
|
||||
void OpenXRInput::attachActionSets()
|
||||
{
|
||||
auto* xr = Environment::get().getManager();
|
||||
|
||||
// Suggest bindings before attaching
|
||||
for (auto& profile : mSuggestedBindings)
|
||||
{
|
||||
XrPath profilePath = 0;
|
||||
CHECK_XRCMD(
|
||||
xrStringToPath(xr->impl().xrInstance(), profile.first.c_str(), &profilePath));
|
||||
XrInteractionProfileSuggestedBinding xrProfileSuggestedBindings{ XR_TYPE_INTERACTION_PROFILE_SUGGESTED_BINDING };
|
||||
xrProfileSuggestedBindings.interactionProfile = profilePath;
|
||||
xrProfileSuggestedBindings.suggestedBindings = profile.second.data();
|
||||
xrProfileSuggestedBindings.countSuggestedBindings = (uint32_t)profile.second.size();
|
||||
CHECK_XRCMD(xrSuggestInteractionProfileBindings(xr->impl().xrInstance(), &xrProfileSuggestedBindings));
|
||||
mInteractionProfileNames[profilePath] = profile.first;
|
||||
mInteractionProfilePaths[profile.first] = profilePath;
|
||||
}
|
||||
|
||||
// OpenXR requires that xrAttachSessionActionSets be called at most once per session.
|
||||
// So collect all action sets
|
||||
std::vector<XrActionSet> actionSets;
|
||||
for (auto& actionSet : mActionSets)
|
||||
actionSets.push_back(actionSet.second.xrActionSet());
|
||||
|
||||
// Attach
|
||||
XrSessionActionSetsAttachInfo attachInfo{ XR_TYPE_SESSION_ACTION_SETS_ATTACH_INFO };
|
||||
attachInfo.countActionSets = actionSets.size();
|
||||
attachInfo.actionSets = actionSets.data();
|
||||
CHECK_XRCMD(xrAttachSessionActionSets(xr->impl().xrSession(), &attachInfo));
|
||||
}
|
||||
|
||||
void OpenXRInput::notifyInteractionProfileChanged()
|
||||
{
|
||||
auto xr = MWVR::Environment::get().getManager();
|
||||
xr->impl().xrSession();
|
||||
|
||||
// Unfortunately, openxr does not tell us WHICH profile has changed.
|
||||
std::array<std::string, 5> topLevelUserPaths =
|
||||
{
|
||||
"/user/hand/left",
|
||||
"/user/hand/right",
|
||||
"/user/head",
|
||||
"/user/gamepad",
|
||||
"/user/treadmill"
|
||||
};
|
||||
|
||||
for (auto& userPath : topLevelUserPaths)
|
||||
{
|
||||
auto pathIt = mInteractionProfilePaths.find(userPath);
|
||||
if (pathIt == mInteractionProfilePaths.end())
|
||||
{
|
||||
XrPath xrUserPath = XR_NULL_PATH;
|
||||
CHECK_XRCMD(
|
||||
xrStringToPath(xr->impl().xrInstance(), userPath.c_str(), &xrUserPath));
|
||||
mInteractionProfilePaths[userPath] = xrUserPath;
|
||||
pathIt = mInteractionProfilePaths.find(userPath);
|
||||
}
|
||||
|
||||
XrInteractionProfileState interactionProfileState{
|
||||
XR_TYPE_INTERACTION_PROFILE_STATE
|
||||
};
|
||||
|
||||
xrGetCurrentInteractionProfile(xr->impl().xrSession(), pathIt->second, &interactionProfileState);
|
||||
if (interactionProfileState.interactionProfile)
|
||||
{
|
||||
auto activeProfileIt = mActiveInteractionProfiles.find(pathIt->second);
|
||||
if (activeProfileIt == mActiveInteractionProfiles.end() || interactionProfileState.interactionProfile != activeProfileIt->second)
|
||||
{
|
||||
auto activeProfileNameIt = mInteractionProfileNames.find(interactionProfileState.interactionProfile);
|
||||
Log(Debug::Verbose) << userPath << ": Interaction profile changed to '" << activeProfileNameIt->second << "'";
|
||||
mActiveInteractionProfiles[pathIt->second] = interactionProfileState.interactionProfile;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
44
apps/openmw/mwvr/openxrinput.hpp
Normal file
44
apps/openmw/mwvr/openxrinput.hpp
Normal file
|
@ -0,0 +1,44 @@
|
|||
#ifndef OPENXR_INPUT_HPP
|
||||
#define OPENXR_INPUT_HPP
|
||||
|
||||
#include "vrinput.hpp"
|
||||
#include "openxractionset.hpp"
|
||||
|
||||
#include <vector>
|
||||
#include <array>
|
||||
|
||||
namespace MWVR
|
||||
{
|
||||
/// \brief Generates and manages OpenXR Actions and ActionSets by generating openxr bindings from a list of SuggestedBindings structs.
|
||||
class OpenXRInput
|
||||
{
|
||||
public:
|
||||
using XrSuggestedBindings = std::vector<XrActionSuggestedBinding>;
|
||||
using XrProfileSuggestedBindings = std::map<std::string, XrSuggestedBindings>;
|
||||
|
||||
//! Default constructor, creates two ActionSets: Gameplay and GUI
|
||||
OpenXRInput(std::shared_ptr<AxisAction::Deadzone> deadzone);
|
||||
|
||||
//! Get the specified actionSet.
|
||||
OpenXRActionSet& getActionSet(ActionSet actionSet);
|
||||
|
||||
//! Suggest bindings for the specific actionSet and profile pair. Call things after calling attachActionSets is an error.
|
||||
void suggestBindings(ActionSet actionSet, std::string profile, const SuggestedBindings& mwSuggestedBindings);
|
||||
|
||||
//! Set bindings and attach actionSets to the session.
|
||||
void attachActionSets();
|
||||
|
||||
//! Notify that active interaction profile has changed
|
||||
void notifyInteractionProfileChanged();
|
||||
|
||||
protected:
|
||||
std::map<ActionSet, OpenXRActionSet> mActionSets{};
|
||||
std::map<XrPath, std::string> mInteractionProfileNames{};
|
||||
std::map<std::string, XrPath> mInteractionProfilePaths{};
|
||||
std::map<XrPath, XrPath> mActiveInteractionProfiles;
|
||||
XrProfileSuggestedBindings mSuggestedBindings{};
|
||||
bool mAttached = false;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
155
apps/openmw/mwvr/openxrmanager.cpp
Normal file
155
apps/openmw/mwvr/openxrmanager.cpp
Normal file
|
@ -0,0 +1,155 @@
|
|||
#include "openxrmanager.hpp"
|
||||
#include "openxrdebug.hpp"
|
||||
#include "vrenvironment.hpp"
|
||||
#include "openxrmanagerimpl.hpp"
|
||||
#include "../mwinput/inputmanagerimp.hpp"
|
||||
|
||||
#include <components/debug/debuglog.hpp>
|
||||
|
||||
namespace MWVR
|
||||
{
|
||||
OpenXRManager::OpenXRManager()
|
||||
: mPrivate(nullptr)
|
||||
, mMutex()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
OpenXRManager::~OpenXRManager()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
bool
|
||||
OpenXRManager::realized() const
|
||||
{
|
||||
return !!mPrivate;
|
||||
}
|
||||
|
||||
void OpenXRManager::handleEvents()
|
||||
{
|
||||
if (realized())
|
||||
return impl().handleEvents();
|
||||
}
|
||||
|
||||
FrameInfo OpenXRManager::waitFrame()
|
||||
{
|
||||
return impl().waitFrame();
|
||||
}
|
||||
|
||||
void OpenXRManager::beginFrame()
|
||||
{
|
||||
return impl().beginFrame();
|
||||
}
|
||||
|
||||
void OpenXRManager::endFrame(FrameInfo frameInfo, const std::array<CompositionLayerProjectionView, 2>* layerStack)
|
||||
{
|
||||
return impl().endFrame(frameInfo, layerStack);
|
||||
}
|
||||
|
||||
bool OpenXRManager::appShouldSyncFrameLoop() const
|
||||
{
|
||||
if (realized())
|
||||
return impl().appShouldSyncFrameLoop();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool OpenXRManager::appShouldRender() const
|
||||
{
|
||||
if (realized())
|
||||
return impl().appShouldRender();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool OpenXRManager::appShouldReadInput() const
|
||||
{
|
||||
if (realized())
|
||||
return impl().appShouldReadInput();
|
||||
return false;
|
||||
}
|
||||
|
||||
void
|
||||
OpenXRManager::realize(
|
||||
osg::GraphicsContext* gc)
|
||||
{
|
||||
lock_guard lock(mMutex);
|
||||
if (!realized())
|
||||
{
|
||||
gc->makeCurrent();
|
||||
mPrivate = std::make_shared<OpenXRManagerImpl>(gc);
|
||||
}
|
||||
}
|
||||
|
||||
void OpenXRManager::enablePredictions()
|
||||
{
|
||||
return impl().enablePredictions();
|
||||
}
|
||||
|
||||
void OpenXRManager::disablePredictions()
|
||||
{
|
||||
return impl().disablePredictions();
|
||||
}
|
||||
|
||||
void OpenXRManager::xrResourceAcquired()
|
||||
{
|
||||
return impl().xrResourceAcquired();
|
||||
}
|
||||
|
||||
void OpenXRManager::xrResourceReleased()
|
||||
{
|
||||
return impl().xrResourceReleased();
|
||||
}
|
||||
|
||||
std::array<View, 2> OpenXRManager::getPredictedViews(int64_t predictedDisplayTime, ReferenceSpace space)
|
||||
{
|
||||
return impl().getPredictedViews(predictedDisplayTime, space);
|
||||
}
|
||||
|
||||
MWVR::Pose OpenXRManager::getPredictedHeadPose(int64_t predictedDisplayTime, ReferenceSpace space)
|
||||
{
|
||||
return impl().getPredictedHeadPose(predictedDisplayTime, space);
|
||||
}
|
||||
|
||||
long long OpenXRManager::getLastPredictedDisplayTime()
|
||||
{
|
||||
return impl().getLastPredictedDisplayTime();
|
||||
}
|
||||
|
||||
long long OpenXRManager::getLastPredictedDisplayPeriod()
|
||||
{
|
||||
return impl().getLastPredictedDisplayPeriod();
|
||||
}
|
||||
|
||||
std::array<SwapchainConfig, 2> OpenXRManager::getRecommendedSwapchainConfig() const
|
||||
{
|
||||
return impl().getRecommendedSwapchainConfig();
|
||||
}
|
||||
|
||||
bool OpenXRManager::xrExtensionIsEnabled(const char* extensionName) const
|
||||
{
|
||||
return impl().xrExtensionIsEnabled(extensionName);
|
||||
}
|
||||
|
||||
int64_t OpenXRManager::selectColorFormat()
|
||||
{
|
||||
return impl().selectColorFormat();
|
||||
}
|
||||
|
||||
int64_t OpenXRManager::selectDepthFormat()
|
||||
{
|
||||
return impl().selectDepthFormat();
|
||||
}
|
||||
|
||||
void OpenXRManager::eraseFormat(int64_t format)
|
||||
{
|
||||
return impl().eraseFormat(format);
|
||||
}
|
||||
|
||||
void
|
||||
OpenXRManager::CleanupOperation::operator()(
|
||||
osg::GraphicsContext* gc)
|
||||
{
|
||||
// TODO: Use this to make proper cleanup such as cleaning up VRFramebuffers.
|
||||
}
|
||||
}
|
||||
|
122
apps/openmw/mwvr/openxrmanager.hpp
Normal file
122
apps/openmw/mwvr/openxrmanager.hpp
Normal file
|
@ -0,0 +1,122 @@
|
|||
#ifndef MWVR_OPENRXMANAGER_H
|
||||
#define MWVR_OPENRXMANAGER_H
|
||||
#ifndef USE_OPENXR
|
||||
#error "openxrmanager.hpp included without USE_OPENXR defined"
|
||||
#endif
|
||||
|
||||
#include <memory>
|
||||
#include <array>
|
||||
#include <mutex>
|
||||
#include <components/debug/debuglog.hpp>
|
||||
#include <components/sdlutil/sdlgraphicswindow.hpp>
|
||||
#include <components/settings/settings.hpp>
|
||||
#include <osg/Camera>
|
||||
#include <osgViewer/Viewer>
|
||||
#include "vrtypes.hpp"
|
||||
|
||||
|
||||
struct XrSwapchainSubImage;
|
||||
struct XrCompositionLayerBaseHeader;
|
||||
|
||||
namespace MWVR
|
||||
{
|
||||
class OpenXRManagerImpl;
|
||||
|
||||
/// \brief Manage the openxr runtime and session
|
||||
class OpenXRManager : public osg::Referenced
|
||||
{
|
||||
public:
|
||||
class CleanupOperation : public osg::GraphicsOperation
|
||||
{
|
||||
public:
|
||||
CleanupOperation() : osg::GraphicsOperation("OpenXRCleanupOperation", false) {};
|
||||
void operator()(osg::GraphicsContext* gc) override;
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
|
||||
public:
|
||||
OpenXRManager();
|
||||
|
||||
~OpenXRManager();
|
||||
|
||||
/// Manager has been initialized.
|
||||
bool realized() const;
|
||||
|
||||
//! Forward call to xrWaitFrame()
|
||||
FrameInfo waitFrame();
|
||||
|
||||
//! Forward call to xrBeginFrame()
|
||||
void beginFrame();
|
||||
|
||||
//! Forward call to xrEndFrame()
|
||||
void endFrame(FrameInfo frameInfo, const std::array<CompositionLayerProjectionView, 2>* layerStack);
|
||||
|
||||
//! Whether the app should call the openxr frame sync functions ( xr*Frame() )
|
||||
bool appShouldSyncFrameLoop() const;
|
||||
|
||||
//! Whether the app should render anything.
|
||||
bool appShouldRender() const;
|
||||
|
||||
//! Whether the session is focused and can read input
|
||||
bool appShouldReadInput() const;
|
||||
|
||||
//! Process all openxr events
|
||||
void handleEvents();
|
||||
|
||||
//! Instantiate implementation
|
||||
void realize(osg::GraphicsContext* gc);
|
||||
|
||||
//! Enable pose predictions. Exist to police that predictions are never made out of turn.
|
||||
void enablePredictions();
|
||||
|
||||
//! Disable pose predictions.
|
||||
void disablePredictions();
|
||||
|
||||
//! Must be called every time an openxr resource is acquired to keep track
|
||||
void xrResourceAcquired();
|
||||
|
||||
//! Must be called every time an openxr resource is released to keep track
|
||||
void xrResourceReleased();
|
||||
|
||||
//! Get poses and fov of both eyes at the predicted time, relative to the given reference space. \note Will throw if predictions are disabled.
|
||||
std::array<View, 2> getPredictedViews(int64_t predictedDisplayTime, ReferenceSpace space);
|
||||
|
||||
//! Get the pose of the player's head at the predicted time, relative to the given reference space. \note Will throw if predictions are disabled.
|
||||
MWVR::Pose getPredictedHeadPose(int64_t predictedDisplayTime, ReferenceSpace space);
|
||||
|
||||
//! Last predicted display time returned from xrWaitFrame();
|
||||
long long getLastPredictedDisplayTime();
|
||||
|
||||
//! Last predicted display period returned from xrWaitFrame();
|
||||
long long getLastPredictedDisplayPeriod();
|
||||
|
||||
//! Configuration hints for instantiating swapchains, queried from openxr.
|
||||
std::array<SwapchainConfig, 2> getRecommendedSwapchainConfig() const;
|
||||
|
||||
//! Check whether a given openxr extension is enabled or not
|
||||
bool xrExtensionIsEnabled(const char* extensionName) const;
|
||||
|
||||
//! Selects a color format from among formats offered by the runtime
|
||||
//! Returns 0 if no format is supported.
|
||||
int64_t selectColorFormat();
|
||||
|
||||
//! Selects a depth format from among formats offered by the runtime
|
||||
//! Returns 0 if no format is supported.
|
||||
int64_t selectDepthFormat();
|
||||
|
||||
//! Erase format from list of format candidates
|
||||
void eraseFormat(int64_t format);
|
||||
|
||||
OpenXRManagerImpl& impl() { return *mPrivate; }
|
||||
const OpenXRManagerImpl& impl() const { return *mPrivate; }
|
||||
|
||||
private:
|
||||
std::shared_ptr<OpenXRManagerImpl> mPrivate;
|
||||
std::mutex mMutex;
|
||||
using lock_guard = std::lock_guard<std::mutex>;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
686
apps/openmw/mwvr/openxrmanagerimpl.cpp
Normal file
686
apps/openmw/mwvr/openxrmanagerimpl.cpp
Normal file
|
@ -0,0 +1,686 @@
|
|||
#include "openxrmanagerimpl.hpp"
|
||||
#include "openxrdebug.hpp"
|
||||
#include "openxrplatform.hpp"
|
||||
#include "openxrswapchain.hpp"
|
||||
#include "openxrswapchainimpl.hpp"
|
||||
#include "openxrtypeconversions.hpp"
|
||||
#include "vrenvironment.hpp"
|
||||
#include "vrinputmanager.hpp"
|
||||
|
||||
#include <components/debug/debuglog.hpp>
|
||||
#include <components/sdlutil/sdlgraphicswindow.hpp>
|
||||
#include <components/esm/loadrace.hpp>
|
||||
|
||||
#include "../mwmechanics/actorutil.hpp"
|
||||
|
||||
#include "../mwbase/world.hpp"
|
||||
#include "../mwbase/environment.hpp"
|
||||
|
||||
#include "../mwworld/class.hpp"
|
||||
#include "../mwworld/player.hpp"
|
||||
#include "../mwworld/esmstore.hpp"
|
||||
|
||||
#include <openxr/openxr_reflection.h>
|
||||
|
||||
#include <osg/Camera>
|
||||
|
||||
#include <vector>
|
||||
#include <array>
|
||||
#include <iostream>
|
||||
|
||||
#define ENUM_CASE_STR(name, val) case name: return #name;
|
||||
#define MAKE_TO_STRING_FUNC(enumType) \
|
||||
inline const char* to_string(enumType e) { \
|
||||
switch (e) { \
|
||||
XR_LIST_ENUM_##enumType(ENUM_CASE_STR) \
|
||||
default: return "Unknown " #enumType; \
|
||||
} \
|
||||
}
|
||||
|
||||
MAKE_TO_STRING_FUNC(XrReferenceSpaceType);
|
||||
MAKE_TO_STRING_FUNC(XrViewConfigurationType);
|
||||
MAKE_TO_STRING_FUNC(XrEnvironmentBlendMode);
|
||||
MAKE_TO_STRING_FUNC(XrSessionState);
|
||||
MAKE_TO_STRING_FUNC(XrResult);
|
||||
MAKE_TO_STRING_FUNC(XrFormFactor);
|
||||
MAKE_TO_STRING_FUNC(XrStructureType);
|
||||
|
||||
namespace MWVR
|
||||
{
|
||||
OpenXRManagerImpl::OpenXRManagerImpl(osg::GraphicsContext* gc)
|
||||
: mPlatform(gc)
|
||||
{
|
||||
mInstance = mPlatform.createXrInstance("openmw_vr");
|
||||
|
||||
LogInstanceInfo();
|
||||
|
||||
setupDebugMessenger();
|
||||
|
||||
setupLayerDepth();
|
||||
|
||||
getSystem();
|
||||
|
||||
enumerateViews();
|
||||
|
||||
// TODO: Blend mode
|
||||
// setupBlendMode();
|
||||
|
||||
mSession = mPlatform.createXrSession(mInstance, mSystemId);
|
||||
|
||||
LogReferenceSpaces();
|
||||
|
||||
createReferenceSpaces();
|
||||
|
||||
initTracker();
|
||||
|
||||
getSystemProperties();
|
||||
}
|
||||
|
||||
void OpenXRManagerImpl::createReferenceSpaces()
|
||||
{
|
||||
XrReferenceSpaceCreateInfo createInfo{ XR_TYPE_REFERENCE_SPACE_CREATE_INFO };
|
||||
createInfo.poseInReferenceSpace.orientation.w = 1.f; // Identity pose
|
||||
createInfo.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_VIEW;
|
||||
CHECK_XRCMD(xrCreateReferenceSpace(mSession, &createInfo, &mReferenceSpaceView));
|
||||
createInfo.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_STAGE;
|
||||
CHECK_XRCMD(xrCreateReferenceSpace(mSession, &createInfo, &mReferenceSpaceStage));
|
||||
createInfo.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_LOCAL;
|
||||
CHECK_XRCMD(xrCreateReferenceSpace(mSession, &createInfo, &mReferenceSpaceLocal));
|
||||
}
|
||||
|
||||
void OpenXRManagerImpl::getSystem()
|
||||
{
|
||||
XrSystemGetInfo systemInfo{ XR_TYPE_SYSTEM_GET_INFO };
|
||||
systemInfo.formFactor = mFormFactor = XR_FORM_FACTOR_HEAD_MOUNTED_DISPLAY;
|
||||
auto res = CHECK_XRCMD(xrGetSystem(mInstance, &systemInfo, &mSystemId));
|
||||
if (!XR_SUCCEEDED(res))
|
||||
mPlatform.initFailure(res, mInstance);
|
||||
}
|
||||
|
||||
void OpenXRManagerImpl::getSystemProperties()
|
||||
{// Read and log graphics properties for the swapchain
|
||||
CHECK_XRCMD(xrGetSystemProperties(mInstance, mSystemId, &mSystemProperties));
|
||||
|
||||
// Log system properties.
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "System Properties: Name=" << mSystemProperties.systemName << " VendorId=" << mSystemProperties.vendorId << std::endl;
|
||||
ss << "System Graphics Properties: MaxWidth=" << mSystemProperties.graphicsProperties.maxSwapchainImageWidth;
|
||||
ss << " MaxHeight=" << mSystemProperties.graphicsProperties.maxSwapchainImageHeight;
|
||||
ss << " MaxLayers=" << mSystemProperties.graphicsProperties.maxLayerCount << std::endl;
|
||||
ss << "System Tracking Properties: OrientationTracking=" << mSystemProperties.trackingProperties.orientationTracking ? "True" : "False";
|
||||
ss << " PositionTracking=" << mSystemProperties.trackingProperties.positionTracking ? "True" : "False";
|
||||
Log(Debug::Verbose) << ss.str();
|
||||
}
|
||||
}
|
||||
|
||||
void OpenXRManagerImpl::enumerateViews()
|
||||
{
|
||||
uint32_t viewCount = 0;
|
||||
CHECK_XRCMD(xrEnumerateViewConfigurationViews(mInstance, mSystemId, mViewConfigType, 2, &viewCount, mConfigViews.data()));
|
||||
|
||||
if (viewCount != 2)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "xrEnumerateViewConfigurationViews returned " << viewCount << " views";
|
||||
Log(Debug::Verbose) << ss.str();
|
||||
}
|
||||
}
|
||||
|
||||
void OpenXRManagerImpl::setupLayerDepth()
|
||||
{
|
||||
// Layer depth is enabled, cache the invariant values
|
||||
if (xrExtensionIsEnabled(XR_KHR_COMPOSITION_LAYER_DEPTH_EXTENSION_NAME))
|
||||
{
|
||||
GLfloat depthRange[2] = { 0.f, 1.f };
|
||||
glGetFloatv(GL_DEPTH_RANGE, depthRange);
|
||||
auto nearClip = Settings::Manager::getFloat("near clip", "Camera");
|
||||
|
||||
for (auto& layer : mLayerDepth)
|
||||
{
|
||||
layer.type = XR_TYPE_COMPOSITION_LAYER_DEPTH_INFO_KHR;
|
||||
layer.next = nullptr;
|
||||
layer.minDepth = depthRange[0];
|
||||
layer.maxDepth = depthRange[1];
|
||||
layer.nearZ = nearClip;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string XrResultString(XrResult res)
|
||||
{
|
||||
return to_string(res);
|
||||
}
|
||||
|
||||
OpenXRManagerImpl::~OpenXRManagerImpl()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void OpenXRManagerImpl::setupExtensionsAndLayers()
|
||||
{
|
||||
|
||||
}
|
||||
static XrBool32 xrDebugCallback(
|
||||
XrDebugUtilsMessageSeverityFlagsEXT messageSeverity,
|
||||
XrDebugUtilsMessageTypeFlagsEXT messageType,
|
||||
const XrDebugUtilsMessengerCallbackDataEXT* callbackData,
|
||||
void* userData)
|
||||
{
|
||||
OpenXRManagerImpl* manager = reinterpret_cast<OpenXRManagerImpl*>(userData);
|
||||
(void)manager;
|
||||
std::string severityStr = "";
|
||||
std::string typeStr = "";
|
||||
|
||||
switch (messageSeverity)
|
||||
{
|
||||
case XR_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT:
|
||||
severityStr = "Verbose"; break;
|
||||
case XR_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT:
|
||||
severityStr = "Info"; break;
|
||||
case XR_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT:
|
||||
severityStr = "Warning"; break;
|
||||
case XR_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT:
|
||||
severityStr = "Error"; break;
|
||||
default:
|
||||
severityStr = "Unknown"; break;
|
||||
}
|
||||
|
||||
switch (messageType)
|
||||
{
|
||||
case XR_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT:
|
||||
typeStr = "General"; break;
|
||||
case XR_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT:
|
||||
typeStr = "Validation"; break;
|
||||
case XR_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT:
|
||||
typeStr = "Performance"; break;
|
||||
case XR_DEBUG_UTILS_MESSAGE_TYPE_CONFORMANCE_BIT_EXT:
|
||||
typeStr = "Conformance"; break;
|
||||
default:
|
||||
typeStr = "Unknown"; break;
|
||||
}
|
||||
|
||||
Log(Debug::Verbose) << "XrCallback: [" << severityStr << "][" << typeStr << "][ID=" << (callbackData->messageId ? callbackData->messageId : "null") << "]: " << callbackData->message;
|
||||
|
||||
return XR_FALSE;
|
||||
}
|
||||
|
||||
void OpenXRManagerImpl::setupDebugMessenger(void)
|
||||
{
|
||||
if (xrExtensionIsEnabled(XR_EXT_DEBUG_UTILS_EXTENSION_NAME))
|
||||
{
|
||||
XrDebugUtilsMessengerCreateInfoEXT createInfo{ XR_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT, nullptr };
|
||||
|
||||
// Debug message severity levels
|
||||
if (Settings::Manager::getBool("XR_EXT_debug_utils message level verbose", "VR Debug"))
|
||||
createInfo.messageSeverities |= XR_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT;
|
||||
if (Settings::Manager::getBool("XR_EXT_debug_utils message level info", "VR Debug"))
|
||||
createInfo.messageSeverities |= XR_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT;
|
||||
if (Settings::Manager::getBool("XR_EXT_debug_utils message level warning", "VR Debug"))
|
||||
createInfo.messageSeverities |= XR_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT;
|
||||
if (Settings::Manager::getBool("XR_EXT_debug_utils message level error", "VR Debug"))
|
||||
createInfo.messageSeverities |= XR_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT;
|
||||
|
||||
// Debug message types
|
||||
if (Settings::Manager::getBool("XR_EXT_debug_utils message type general", "VR Debug"))
|
||||
createInfo.messageTypes |= XR_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT;
|
||||
if (Settings::Manager::getBool("XR_EXT_debug_utils message type validation", "VR Debug"))
|
||||
createInfo.messageTypes |= XR_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT;
|
||||
if (Settings::Manager::getBool("XR_EXT_debug_utils message type performance", "VR Debug"))
|
||||
createInfo.messageTypes |= XR_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT;
|
||||
if (Settings::Manager::getBool("XR_EXT_debug_utils message type conformance", "VR Debug"))
|
||||
createInfo.messageTypes |= XR_DEBUG_UTILS_MESSAGE_TYPE_CONFORMANCE_BIT_EXT;
|
||||
|
||||
createInfo.userCallback = &xrDebugCallback;
|
||||
createInfo.userData = this;
|
||||
|
||||
PFN_xrCreateDebugUtilsMessengerEXT createDebugUtilsMessenger = reinterpret_cast<PFN_xrCreateDebugUtilsMessengerEXT>(xrGetFunction("xrCreateDebugUtilsMessengerEXT"));
|
||||
assert(createDebugUtilsMessenger);
|
||||
CHECK_XRCMD(createDebugUtilsMessenger(mInstance, &createInfo, &mDebugMessenger));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
OpenXRManagerImpl::LogInstanceInfo() {
|
||||
|
||||
XrInstanceProperties instanceProperties{ XR_TYPE_INSTANCE_PROPERTIES };
|
||||
CHECK_XRCMD(xrGetInstanceProperties(mInstance, &instanceProperties));
|
||||
Log(Debug::Verbose) << "Instance RuntimeName=" << instanceProperties.runtimeName << " RuntimeVersion=" << instanceProperties.runtimeVersion;
|
||||
}
|
||||
|
||||
void
|
||||
OpenXRManagerImpl::LogReferenceSpaces() {
|
||||
|
||||
uint32_t spaceCount = 0;
|
||||
CHECK_XRCMD(xrEnumerateReferenceSpaces(mSession, 0, &spaceCount, nullptr));
|
||||
std::vector<XrReferenceSpaceType> spaces(spaceCount);
|
||||
CHECK_XRCMD(xrEnumerateReferenceSpaces(mSession, spaceCount, &spaceCount, spaces.data()));
|
||||
|
||||
std::stringstream ss;
|
||||
ss << "Available reference spaces=" << spaceCount << std::endl;
|
||||
|
||||
for (XrReferenceSpaceType space : spaces)
|
||||
ss << " Name: " << to_string(space) << std::endl;
|
||||
Log(Debug::Verbose) << ss.str();
|
||||
}
|
||||
|
||||
FrameInfo
|
||||
OpenXRManagerImpl::waitFrame()
|
||||
{
|
||||
XrFrameWaitInfo frameWaitInfo{ XR_TYPE_FRAME_WAIT_INFO };
|
||||
XrFrameState frameState{ XR_TYPE_FRAME_STATE };
|
||||
|
||||
CHECK_XRCMD(xrWaitFrame(mSession, &frameWaitInfo, &frameState));
|
||||
mFrameState = frameState;
|
||||
|
||||
FrameInfo frameInfo;
|
||||
|
||||
frameInfo.runtimePredictedDisplayTime = mFrameState.predictedDisplayTime;
|
||||
frameInfo.runtimePredictedDisplayPeriod = mFrameState.predictedDisplayPeriod;
|
||||
frameInfo.runtimeRequestsRender = !!mFrameState.shouldRender;
|
||||
|
||||
return frameInfo;
|
||||
}
|
||||
|
||||
void
|
||||
OpenXRManagerImpl::beginFrame()
|
||||
{
|
||||
XrFrameBeginInfo frameBeginInfo{ XR_TYPE_FRAME_BEGIN_INFO };
|
||||
CHECK_XRCMD(xrBeginFrame(mSession, &frameBeginInfo));
|
||||
}
|
||||
|
||||
void
|
||||
OpenXRManagerImpl::endFrame(FrameInfo frameInfo, const std::array<CompositionLayerProjectionView, 2>* layerStack)
|
||||
{
|
||||
std::array<XrCompositionLayerProjectionView, 2> compositionLayerProjectionViews{};
|
||||
XrCompositionLayerProjection layer{};
|
||||
std::array<XrCompositionLayerDepthInfoKHR, 2> compositionLayerDepth{};
|
||||
XrFrameEndInfo frameEndInfo{ XR_TYPE_FRAME_END_INFO };
|
||||
frameEndInfo.displayTime = frameInfo.runtimePredictedDisplayTime;
|
||||
frameEndInfo.environmentBlendMode = mEnvironmentBlendMode;
|
||||
if (layerStack && frameInfo.runtimeRequestsRender)
|
||||
{
|
||||
compositionLayerProjectionViews[(int)Side::LEFT_SIDE] = toXR((*layerStack)[(int)Side::LEFT_SIDE]);
|
||||
compositionLayerProjectionViews[(int)Side::RIGHT_SIDE] = toXR((*layerStack)[(int)Side::RIGHT_SIDE]);
|
||||
layer.type = XR_TYPE_COMPOSITION_LAYER_PROJECTION;
|
||||
layer.space = mReferenceSpaceStage;
|
||||
layer.viewCount = 2;
|
||||
layer.views = compositionLayerProjectionViews.data();
|
||||
auto* xrLayerStack = reinterpret_cast<XrCompositionLayerBaseHeader*>(&layer);
|
||||
|
||||
if (xrExtensionIsEnabled(XR_KHR_COMPOSITION_LAYER_DEPTH_EXTENSION_NAME))
|
||||
{
|
||||
auto farClip = Settings::Manager::getFloat("viewing distance", "Camera");
|
||||
// All values not set here are set previously as they are constant
|
||||
compositionLayerDepth = mLayerDepth;
|
||||
compositionLayerDepth[(int)Side::LEFT_SIDE].farZ = farClip;
|
||||
compositionLayerDepth[(int)Side::RIGHT_SIDE].farZ = farClip;
|
||||
compositionLayerDepth[(int)Side::LEFT_SIDE].subImage = toXR((*layerStack)[(int)Side::LEFT_SIDE].subImage, true);
|
||||
compositionLayerDepth[(int)Side::RIGHT_SIDE].subImage = toXR((*layerStack)[(int)Side::RIGHT_SIDE].subImage, true);
|
||||
if (compositionLayerDepth[(int)Side::LEFT_SIDE].subImage.swapchain != XR_NULL_HANDLE
|
||||
&& compositionLayerDepth[(int)Side::RIGHT_SIDE].subImage.swapchain != XR_NULL_HANDLE)
|
||||
{
|
||||
compositionLayerProjectionViews[(int)Side::LEFT_SIDE].next = &compositionLayerDepth[(int)Side::LEFT_SIDE];
|
||||
compositionLayerProjectionViews[(int)Side::RIGHT_SIDE].next = &compositionLayerDepth[(int)Side::RIGHT_SIDE];
|
||||
}
|
||||
}
|
||||
frameEndInfo.layerCount = 1;
|
||||
frameEndInfo.layers = &xrLayerStack;
|
||||
}
|
||||
else
|
||||
{
|
||||
frameEndInfo.layerCount = 0;
|
||||
frameEndInfo.layers = nullptr;
|
||||
}
|
||||
CHECK_XRCMD(xrEndFrame(mSession, &frameEndInfo));
|
||||
}
|
||||
|
||||
std::array<View, 2>
|
||||
OpenXRManagerImpl::getPredictedViews(
|
||||
int64_t predictedDisplayTime,
|
||||
ReferenceSpace space)
|
||||
{
|
||||
//if (!mPredictionsEnabled)
|
||||
//{
|
||||
// Log(Debug::Error) << "Prediction out of order";
|
||||
// throw std::logic_error("Prediction out of order");
|
||||
//}
|
||||
std::array<XrView, 2> xrViews{ {{XR_TYPE_VIEW}, {XR_TYPE_VIEW}} };
|
||||
XrViewState viewState{ XR_TYPE_VIEW_STATE };
|
||||
uint32_t viewCount = 2;
|
||||
|
||||
XrViewLocateInfo viewLocateInfo{ XR_TYPE_VIEW_LOCATE_INFO };
|
||||
viewLocateInfo.viewConfigurationType = mViewConfigType;
|
||||
viewLocateInfo.displayTime = predictedDisplayTime;
|
||||
switch (space)
|
||||
{
|
||||
case ReferenceSpace::STAGE:
|
||||
viewLocateInfo.space = mReferenceSpaceStage;
|
||||
break;
|
||||
case ReferenceSpace::VIEW:
|
||||
viewLocateInfo.space = mReferenceSpaceView;
|
||||
break;
|
||||
}
|
||||
CHECK_XRCMD(xrLocateViews(mSession, &viewLocateInfo, &viewState, viewCount, &viewCount, xrViews.data()));
|
||||
|
||||
std::array<View, 2> vrViews{};
|
||||
vrViews[(int)Side::LEFT_SIDE].pose = fromXR(xrViews[(int)Side::LEFT_SIDE].pose);
|
||||
vrViews[(int)Side::RIGHT_SIDE].pose = fromXR(xrViews[(int)Side::RIGHT_SIDE].pose);
|
||||
vrViews[(int)Side::LEFT_SIDE].fov = fromXR(xrViews[(int)Side::LEFT_SIDE].fov);
|
||||
vrViews[(int)Side::RIGHT_SIDE].fov = fromXR(xrViews[(int)Side::RIGHT_SIDE].fov);
|
||||
return vrViews;
|
||||
}
|
||||
|
||||
MWVR::Pose OpenXRManagerImpl::getPredictedHeadPose(
|
||||
int64_t predictedDisplayTime,
|
||||
ReferenceSpace space)
|
||||
{
|
||||
if (!mPredictionsEnabled)
|
||||
{
|
||||
Log(Debug::Error) << "Prediction out of order";
|
||||
throw std::logic_error("Prediction out of order");
|
||||
}
|
||||
XrSpaceLocation location{ XR_TYPE_SPACE_LOCATION };
|
||||
XrSpace limbSpace = mReferenceSpaceView;
|
||||
XrSpace referenceSpace = XR_NULL_HANDLE;
|
||||
|
||||
switch (space)
|
||||
{
|
||||
case ReferenceSpace::STAGE:
|
||||
referenceSpace = mReferenceSpaceStage;
|
||||
break;
|
||||
case ReferenceSpace::VIEW:
|
||||
referenceSpace = mReferenceSpaceView;
|
||||
break;
|
||||
}
|
||||
CHECK_XRCMD(xrLocateSpace(limbSpace, referenceSpace, predictedDisplayTime, &location));
|
||||
|
||||
if (!location.locationFlags & XR_SPACE_LOCATION_ORIENTATION_VALID_BIT)
|
||||
{
|
||||
// Quat must have a magnitude of 1 but openxr sets it to 0 when tracking is unavailable.
|
||||
// I want a no-track pose to still be valid
|
||||
location.pose.orientation.w = 1;
|
||||
}
|
||||
return MWVR::Pose{
|
||||
fromXR(location.pose.position),
|
||||
fromXR(location.pose.orientation)
|
||||
};
|
||||
}
|
||||
|
||||
void OpenXRManagerImpl::handleEvents()
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mMutex);
|
||||
|
||||
xrQueueEvents();
|
||||
|
||||
while (auto* event = nextEvent())
|
||||
{
|
||||
if (!processEvent(event))
|
||||
{
|
||||
// Do not consider processing an event optional.
|
||||
// Retry once per frame until every event has been successfully processed
|
||||
return;
|
||||
}
|
||||
popEvent();
|
||||
}
|
||||
|
||||
if (mXrSessionShouldStop)
|
||||
{
|
||||
if (checkStopCondition())
|
||||
{
|
||||
CHECK_XRCMD(xrEndSession(mSession));
|
||||
mXrSessionShouldStop = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const XrEventDataBaseHeader* OpenXRManagerImpl::nextEvent()
|
||||
{
|
||||
if (mEventQueue.size() > 0)
|
||||
return reinterpret_cast<XrEventDataBaseHeader*> (&mEventQueue.front());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool OpenXRManagerImpl::processEvent(const XrEventDataBaseHeader* header)
|
||||
{
|
||||
Log(Debug::Verbose) << "OpenXR: Event received: " << to_string(header->type);
|
||||
switch (header->type)
|
||||
{
|
||||
case XR_TYPE_EVENT_DATA_SESSION_STATE_CHANGED:
|
||||
{
|
||||
const auto* stateChangeEvent = reinterpret_cast<const XrEventDataSessionStateChanged*>(header);
|
||||
return handleSessionStateChanged(*stateChangeEvent);
|
||||
break;
|
||||
}
|
||||
case XR_TYPE_EVENT_DATA_INTERACTION_PROFILE_CHANGED:
|
||||
MWVR::Environment::get().getInputManager()->notifyInteractionProfileChanged();
|
||||
break;
|
||||
case XR_TYPE_EVENT_DATA_INSTANCE_LOSS_PENDING:
|
||||
case XR_TYPE_EVENT_DATA_REFERENCE_SPACE_CHANGE_PENDING:
|
||||
default:
|
||||
{
|
||||
Log(Debug::Verbose) << "OpenXR: Event ignored";
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
OpenXRManagerImpl::handleSessionStateChanged(
|
||||
const XrEventDataSessionStateChanged& stateChangedEvent)
|
||||
{
|
||||
Log(Debug::Verbose) << "XrEventDataSessionStateChanged: state " << to_string(mSessionState) << "->" << to_string(stateChangedEvent.state);
|
||||
mSessionState = stateChangedEvent.state;
|
||||
|
||||
// Ref: https://www.khronos.org/registry/OpenXR/specs/1.0/html/xrspec.html#session-states
|
||||
switch (mSessionState)
|
||||
{
|
||||
case XR_SESSION_STATE_IDLE:
|
||||
{
|
||||
mAppShouldSyncFrameLoop = false;
|
||||
mAppShouldRender = false;
|
||||
mAppShouldReadInput = false;
|
||||
mXrSessionShouldStop = false;
|
||||
break;
|
||||
}
|
||||
case XR_SESSION_STATE_READY:
|
||||
{
|
||||
mAppShouldSyncFrameLoop = true;
|
||||
mAppShouldRender = false;
|
||||
mAppShouldReadInput = false;
|
||||
mXrSessionShouldStop = false;
|
||||
|
||||
XrSessionBeginInfo beginInfo{ XR_TYPE_SESSION_BEGIN_INFO };
|
||||
beginInfo.primaryViewConfigurationType = mViewConfigType;
|
||||
CHECK_XRCMD(xrBeginSession(mSession, &beginInfo));
|
||||
|
||||
break;
|
||||
}
|
||||
case XR_SESSION_STATE_STOPPING:
|
||||
{
|
||||
mAppShouldSyncFrameLoop = false;
|
||||
mAppShouldRender = false;
|
||||
mAppShouldReadInput = false;
|
||||
mXrSessionShouldStop = true;
|
||||
break;
|
||||
}
|
||||
case XR_SESSION_STATE_SYNCHRONIZED:
|
||||
{
|
||||
mAppShouldSyncFrameLoop = true;
|
||||
mAppShouldRender = false;
|
||||
mAppShouldReadInput = false;
|
||||
mXrSessionShouldStop = false;
|
||||
break;
|
||||
}
|
||||
case XR_SESSION_STATE_VISIBLE:
|
||||
{
|
||||
mAppShouldSyncFrameLoop = true;
|
||||
mAppShouldRender = true;
|
||||
mAppShouldReadInput = false;
|
||||
mXrSessionShouldStop = false;
|
||||
break;
|
||||
}
|
||||
case XR_SESSION_STATE_FOCUSED:
|
||||
{
|
||||
mAppShouldSyncFrameLoop = true;
|
||||
mAppShouldRender = true;
|
||||
mAppShouldReadInput = true;
|
||||
mXrSessionShouldStop = false;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
Log(Debug::Warning) << "XrEventDataSessionStateChanged: Ignoring new state " << to_string(mSessionState);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OpenXRManagerImpl::checkStopCondition()
|
||||
{
|
||||
return mAcquiredResources == 0;
|
||||
}
|
||||
|
||||
bool OpenXRManagerImpl::xrNextEvent(XrEventDataBuffer& eventBuffer)
|
||||
{
|
||||
XrEventDataBaseHeader* baseHeader = reinterpret_cast<XrEventDataBaseHeader*>(&eventBuffer);
|
||||
*baseHeader = { XR_TYPE_EVENT_DATA_BUFFER };
|
||||
const XrResult result = xrPollEvent(mInstance, &eventBuffer);
|
||||
if (result == XR_SUCCESS)
|
||||
{
|
||||
if (baseHeader->type == XR_TYPE_EVENT_DATA_EVENTS_LOST) {
|
||||
const XrEventDataEventsLost* const eventsLost = reinterpret_cast<const XrEventDataEventsLost*>(baseHeader);
|
||||
Log(Debug::Warning) << "OpenXRManagerImpl: Lost " << eventsLost->lostEventCount << " events";
|
||||
}
|
||||
|
||||
return baseHeader;
|
||||
}
|
||||
|
||||
if (result != XR_EVENT_UNAVAILABLE)
|
||||
CHECK_XRRESULT(result, "xrPollEvent");
|
||||
return false;
|
||||
}
|
||||
|
||||
void OpenXRManagerImpl::popEvent()
|
||||
{
|
||||
if (mEventQueue.size() > 0)
|
||||
mEventQueue.pop();
|
||||
}
|
||||
|
||||
void
|
||||
OpenXRManagerImpl::xrQueueEvents()
|
||||
{
|
||||
XrEventDataBuffer eventBuffer;
|
||||
while (xrNextEvent(eventBuffer))
|
||||
{
|
||||
mEventQueue.push(eventBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
bool OpenXRManagerImpl::xrExtensionIsEnabled(const char* extensionName) const
|
||||
{
|
||||
return mPlatform.extensionEnabled(extensionName);
|
||||
}
|
||||
|
||||
void OpenXRManagerImpl::xrResourceAcquired()
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mMutex);
|
||||
mAcquiredResources++;
|
||||
}
|
||||
|
||||
void OpenXRManagerImpl::xrResourceReleased()
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mMutex);
|
||||
if (mAcquiredResources == 0)
|
||||
throw std::logic_error("Releasing a nonexistent resource");
|
||||
mAcquiredResources--;
|
||||
}
|
||||
|
||||
void OpenXRManagerImpl::xrUpdateNames()
|
||||
{
|
||||
VrDebug::setName(mInstance, "OpenMW XR Instance");
|
||||
VrDebug::setName(mSession, "OpenMW XR Session");
|
||||
VrDebug::setName(mReferenceSpaceStage, "OpenMW XR Reference Space Stage");
|
||||
VrDebug::setName(mReferenceSpaceView, "OpenMW XR Reference Space Stage");
|
||||
}
|
||||
|
||||
PFN_xrVoidFunction OpenXRManagerImpl::xrGetFunction(const std::string& name)
|
||||
{
|
||||
PFN_xrVoidFunction function = nullptr;
|
||||
xrGetInstanceProcAddr(mInstance, name.c_str(), &function);
|
||||
return function;
|
||||
}
|
||||
|
||||
int64_t OpenXRManagerImpl::selectColorFormat()
|
||||
{
|
||||
// Find supported color swapchain format.
|
||||
return mPlatform.selectColorFormat();
|
||||
}
|
||||
|
||||
int64_t OpenXRManagerImpl::selectDepthFormat()
|
||||
{
|
||||
// Find supported depth swapchain format.
|
||||
return mPlatform.selectDepthFormat();
|
||||
}
|
||||
|
||||
void OpenXRManagerImpl::eraseFormat(int64_t format)
|
||||
{
|
||||
mPlatform.eraseFormat(format);
|
||||
}
|
||||
|
||||
void OpenXRManagerImpl::initTracker()
|
||||
{
|
||||
auto* trackingManager = Environment::get().getTrackingManager();
|
||||
auto headPath = trackingManager->stringToVRPath("/user/head/input/pose");
|
||||
|
||||
mTracker.reset(new OpenXRTracker("pcstage", mReferenceSpaceStage));
|
||||
mTracker->addTrackingSpace(headPath, mReferenceSpaceView);
|
||||
mTrackerToWorldBinding.reset(new VRTrackingToWorldBinding("pcworld", mTracker.get(), headPath));
|
||||
}
|
||||
|
||||
void OpenXRManagerImpl::enablePredictions()
|
||||
{
|
||||
mPredictionsEnabled = true;
|
||||
}
|
||||
|
||||
void OpenXRManagerImpl::disablePredictions()
|
||||
{
|
||||
mPredictionsEnabled = false;
|
||||
}
|
||||
|
||||
long long OpenXRManagerImpl::getLastPredictedDisplayTime()
|
||||
{
|
||||
return mFrameState.predictedDisplayTime;
|
||||
}
|
||||
|
||||
long long OpenXRManagerImpl::getLastPredictedDisplayPeriod()
|
||||
{
|
||||
return mFrameState.predictedDisplayPeriod;
|
||||
}
|
||||
std::array<SwapchainConfig, 2> OpenXRManagerImpl::getRecommendedSwapchainConfig() const
|
||||
{
|
||||
std::array<SwapchainConfig, 2> config{};
|
||||
for (uint32_t i = 0; i < 2; i++)
|
||||
config[i] = SwapchainConfig{
|
||||
(int)mConfigViews[i].recommendedImageRectWidth,
|
||||
(int)mConfigViews[i].recommendedImageRectHeight,
|
||||
(int)mConfigViews[i].recommendedSwapchainSampleCount,
|
||||
(int)mConfigViews[i].maxImageRectWidth,
|
||||
(int)mConfigViews[i].maxImageRectHeight,
|
||||
(int)mConfigViews[i].maxSwapchainSampleCount,
|
||||
};
|
||||
return config;
|
||||
}
|
||||
XrSpace OpenXRManagerImpl::getReferenceSpace(ReferenceSpace space)
|
||||
{
|
||||
switch (space)
|
||||
{
|
||||
case ReferenceSpace::STAGE:
|
||||
return mReferenceSpaceStage;
|
||||
case ReferenceSpace::VIEW:
|
||||
return mReferenceSpaceView;
|
||||
}
|
||||
return XR_NULL_HANDLE;
|
||||
}
|
||||
}
|
||||
|
115
apps/openmw/mwvr/openxrmanagerimpl.hpp
Normal file
115
apps/openmw/mwvr/openxrmanagerimpl.hpp
Normal file
|
@ -0,0 +1,115 @@
|
|||
#ifndef OPENXR_MANAGER_IMPL_HPP
|
||||
#define OPENXR_MANAGER_IMPL_HPP
|
||||
|
||||
#include "openxrmanager.hpp"
|
||||
#include "openxrplatform.hpp"
|
||||
#include "openxrtracker.hpp"
|
||||
#include "../mwinput/inputmanagerimp.hpp"
|
||||
|
||||
#include <components/debug/debuglog.hpp>
|
||||
#include <components/sdlutil/sdlgraphicswindow.hpp>
|
||||
|
||||
#include <openxr/openxr.h>
|
||||
|
||||
#include <vector>
|
||||
#include <array>
|
||||
#include <map>
|
||||
#include <iostream>
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
#include <queue>
|
||||
|
||||
namespace MWVR
|
||||
{
|
||||
/// \brief Implementation of OpenXRManager
|
||||
class OpenXRManagerImpl
|
||||
{
|
||||
public:
|
||||
OpenXRManagerImpl(osg::GraphicsContext* gc);
|
||||
~OpenXRManagerImpl(void);
|
||||
|
||||
FrameInfo waitFrame();
|
||||
void beginFrame();
|
||||
void endFrame(FrameInfo frameInfo, const std::array<CompositionLayerProjectionView, 2>* layerStack);
|
||||
bool appShouldSyncFrameLoop() const { return mAppShouldSyncFrameLoop; }
|
||||
bool appShouldRender() const { return mAppShouldRender; }
|
||||
bool appShouldReadInput() const { return mAppShouldReadInput; }
|
||||
std::array<View, 2> getPredictedViews(int64_t predictedDisplayTime, ReferenceSpace space);
|
||||
MWVR::Pose getPredictedHeadPose(int64_t predictedDisplayTime, ReferenceSpace space);
|
||||
void handleEvents();
|
||||
void enablePredictions();
|
||||
void disablePredictions();
|
||||
long long getLastPredictedDisplayTime();
|
||||
long long getLastPredictedDisplayPeriod();
|
||||
std::array<SwapchainConfig, 2> getRecommendedSwapchainConfig() const;
|
||||
XrSpace getReferenceSpace(ReferenceSpace space);
|
||||
XrSession xrSession() const { return mSession; };
|
||||
XrInstance xrInstance() const { return mInstance; };
|
||||
bool xrExtensionIsEnabled(const char* extensionName) const;
|
||||
void xrResourceAcquired();
|
||||
void xrResourceReleased();
|
||||
void xrUpdateNames();
|
||||
PFN_xrVoidFunction xrGetFunction(const std::string& name);
|
||||
int64_t selectColorFormat();
|
||||
int64_t selectDepthFormat();
|
||||
void eraseFormat(int64_t format);
|
||||
OpenXRPlatform& platform() { return mPlatform; }
|
||||
OpenXRTracker& tracker() { return *mTracker; }
|
||||
void initTracker();
|
||||
|
||||
protected:
|
||||
void setupExtensionsAndLayers();
|
||||
void setupDebugMessenger(void);
|
||||
void LogInstanceInfo();
|
||||
void LogReferenceSpaces();
|
||||
bool xrNextEvent(XrEventDataBuffer& eventBuffer);
|
||||
void xrQueueEvents();
|
||||
const XrEventDataBaseHeader* nextEvent();
|
||||
bool processEvent(const XrEventDataBaseHeader* header);
|
||||
void popEvent();
|
||||
bool handleSessionStateChanged(const XrEventDataSessionStateChanged& stateChangedEvent);
|
||||
bool checkStopCondition();
|
||||
void createReferenceSpaces();
|
||||
void getSystem();
|
||||
void getSystemProperties();
|
||||
void enumerateViews();
|
||||
void setupLayerDepth();
|
||||
|
||||
private:
|
||||
|
||||
bool initialized = false;
|
||||
bool mPredictionsEnabled = false;
|
||||
XrInstance mInstance = XR_NULL_HANDLE;
|
||||
XrSession mSession = XR_NULL_HANDLE;
|
||||
XrSpace mSpace = XR_NULL_HANDLE;
|
||||
XrFormFactor mFormFactor = XR_FORM_FACTOR_HEAD_MOUNTED_DISPLAY;
|
||||
XrViewConfigurationType mViewConfigType = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO;
|
||||
XrEnvironmentBlendMode mEnvironmentBlendMode = XR_ENVIRONMENT_BLEND_MODE_OPAQUE;
|
||||
XrSystemId mSystemId = XR_NULL_SYSTEM_ID;
|
||||
XrSystemProperties mSystemProperties{ XR_TYPE_SYSTEM_PROPERTIES };
|
||||
std::array<XrViewConfigurationView, 2> mConfigViews{ { {XR_TYPE_VIEW_CONFIGURATION_VIEW}, {XR_TYPE_VIEW_CONFIGURATION_VIEW} } };
|
||||
XrSpace mReferenceSpaceView = XR_NULL_HANDLE;
|
||||
XrSpace mReferenceSpaceStage = XR_NULL_HANDLE;
|
||||
XrSpace mReferenceSpaceLocal = XR_NULL_HANDLE;
|
||||
XrFrameState mFrameState{};
|
||||
XrSessionState mSessionState = XR_SESSION_STATE_UNKNOWN;
|
||||
XrDebugUtilsMessengerEXT mDebugMessenger{ nullptr };
|
||||
OpenXRPlatform mPlatform;
|
||||
|
||||
std::unique_ptr<OpenXRTracker> mTracker{ nullptr };
|
||||
std::unique_ptr<VRTrackingToWorldBinding> mTrackerToWorldBinding{ nullptr };
|
||||
|
||||
bool mXrSessionShouldStop = false;
|
||||
bool mAppShouldSyncFrameLoop = false;
|
||||
bool mAppShouldRender = false;
|
||||
bool mAppShouldReadInput = false;
|
||||
|
||||
uint32_t mAcquiredResources = 0;
|
||||
std::mutex mMutex{};
|
||||
std::queue<XrEventDataBuffer> mEventQueue;
|
||||
|
||||
std::array<XrCompositionLayerDepthInfoKHR, 2> mLayerDepth;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
746
apps/openmw/mwvr/openxrplatform.cpp
Normal file
746
apps/openmw/mwvr/openxrplatform.cpp
Normal file
|
@ -0,0 +1,746 @@
|
|||
#include "openxrswapchainimage.hpp"
|
||||
#include "openxrmanagerimpl.hpp"
|
||||
#include "openxrplatform.hpp"
|
||||
#include "vrenvironment.hpp"
|
||||
|
||||
// The OpenXR SDK's platform headers assume we've included platform headers
|
||||
#ifdef _WIN32
|
||||
#include <Windows.h>
|
||||
#include <objbase.h>
|
||||
|
||||
#ifdef XR_USE_GRAPHICS_API_D3D11
|
||||
#include <d3d11_1.h>
|
||||
#endif
|
||||
|
||||
#elif __linux__
|
||||
#include <X11/Xlib.h>
|
||||
#include <GL/glx.h>
|
||||
#undef None
|
||||
|
||||
#else
|
||||
#error Unsupported platform
|
||||
#endif
|
||||
|
||||
#include <openxr/openxr.h>
|
||||
#include <openxr/openxr_platform.h>
|
||||
#include <openxr/openxr_platform_defines.h>
|
||||
#include <openxr/openxr_reflection.h>
|
||||
|
||||
#include <stdexcept>
|
||||
#include <deque>
|
||||
#include <cassert>
|
||||
#include <optional>
|
||||
|
||||
namespace MWVR
|
||||
{
|
||||
|
||||
XrResult CheckXrResult(XrResult res, const char* originator, const char* sourceLocation) {
|
||||
static bool initialized = false;
|
||||
static bool sLogAllXrCalls = false;
|
||||
static bool sContinueOnErrors = false;
|
||||
if (!initialized)
|
||||
{
|
||||
initialized = true;
|
||||
sLogAllXrCalls = Settings::Manager::getBool("log all openxr calls", "VR Debug");
|
||||
sContinueOnErrors = Settings::Manager::getBool("continue on errors", "VR Debug");
|
||||
}
|
||||
|
||||
auto resultString = XrResultString(res);
|
||||
|
||||
if (XR_FAILED(res)) {
|
||||
std::stringstream ss;
|
||||
#ifdef _WIN32
|
||||
ss << sourceLocation << ": OpenXR[Error: " << resultString << "][Thread: " << std::this_thread::get_id() << "]: " << originator;
|
||||
#elif __linux__
|
||||
ss << sourceLocation << ": OpenXR[Error: " << resultString << "][Thread: " << std::this_thread::get_id() << "]: " << originator;
|
||||
#endif
|
||||
Log(Debug::Error) << ss.str();
|
||||
if (!sContinueOnErrors)
|
||||
throw std::runtime_error(ss.str().c_str());
|
||||
}
|
||||
else if (res != XR_SUCCESS || sLogAllXrCalls)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
Log(Debug::Verbose) << sourceLocation << ": OpenXR[" << resultString << "][" << std::this_thread::get_id() << "]: " << originator;
|
||||
#elif __linux__
|
||||
Log(Debug::Verbose) << sourceLocation << ": OpenXR[" << resultString << "][" << std::this_thread::get_id() << "]: " << originator;
|
||||
#endif
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
void OpenXRPlatform::enumerateExtensions(const char* layerName, int logIndent)
|
||||
{
|
||||
uint32_t extensionCount = 0;
|
||||
std::vector<XrExtensionProperties> availableExtensions;
|
||||
CHECK_XRCMD(xrEnumerateInstanceExtensionProperties(layerName, 0, &extensionCount, nullptr));
|
||||
availableExtensions.resize(extensionCount, XrExtensionProperties{ XR_TYPE_EXTENSION_PROPERTIES });
|
||||
CHECK_XRCMD(xrEnumerateInstanceExtensionProperties(layerName, availableExtensions.size(), &extensionCount, availableExtensions.data()));
|
||||
|
||||
std::vector<std::string> extensionNames;
|
||||
const std::string indentStr(logIndent, ' ');
|
||||
for (auto& extension : availableExtensions)
|
||||
{
|
||||
if (layerName)
|
||||
mAvailableLayerExtensions[layerName][extension.extensionName] = extension;
|
||||
else
|
||||
mAvailableExtensions[extension.extensionName] = extension;
|
||||
|
||||
Log(Debug::Verbose) << indentStr << "Name=" << extension.extensionName << " SpecVersion=" << extension.extensionVersion;
|
||||
}
|
||||
}
|
||||
|
||||
struct OpenXRPlatformPrivate
|
||||
{
|
||||
OpenXRPlatformPrivate(osg::GraphicsContext* gc);
|
||||
~OpenXRPlatformPrivate();
|
||||
|
||||
#ifdef XR_USE_GRAPHICS_API_D3D11
|
||||
typedef BOOL(WINAPI* P_wglDXSetResourceShareHandleNV)(void* dxObject, HANDLE shareHandle);
|
||||
typedef HANDLE(WINAPI* P_wglDXOpenDeviceNV)(void* dxDevice);
|
||||
typedef BOOL(WINAPI* P_wglDXCloseDeviceNV)(HANDLE hDevice);
|
||||
typedef HANDLE(WINAPI* P_wglDXRegisterObjectNV)(HANDLE hDevice, void* dxObject,
|
||||
GLuint name, GLenum type, GLenum access);
|
||||
typedef BOOL(WINAPI* P_wglDXUnregisterObjectNV)(HANDLE hDevice, HANDLE hObject);
|
||||
typedef BOOL(WINAPI* P_wglDXObjectAccessNV)(HANDLE hObject, GLenum access);
|
||||
typedef BOOL(WINAPI* P_wglDXLockObjectsNV)(HANDLE hDevice, GLint count, HANDLE* hObjects);
|
||||
typedef BOOL(WINAPI* P_wglDXUnlockObjectsNV)(HANDLE hDevice, GLint count, HANDLE* hObjects);
|
||||
|
||||
void initializeD3D11(XrGraphicsRequirementsD3D11KHR requirements)
|
||||
{
|
||||
mD3D11Dll = LoadLibrary("D3D11.dll");
|
||||
|
||||
if (!mD3D11Dll)
|
||||
throw std::runtime_error("Current OpenXR runtime requires DirectX >= 11.0 but D3D11.dll was not found.");
|
||||
|
||||
pD3D11CreateDevice = reinterpret_cast<PFN_D3D11_CREATE_DEVICE>(GetProcAddress(mD3D11Dll, "D3D11CreateDevice"));
|
||||
|
||||
if (!pD3D11CreateDevice)
|
||||
throw std::runtime_error("Symbol 'D3D11CreateDevice' not found in D3D11.dll");
|
||||
|
||||
// Create the device and device context objects
|
||||
pD3D11CreateDevice(
|
||||
nullptr,
|
||||
D3D_DRIVER_TYPE_HARDWARE,
|
||||
nullptr,
|
||||
0,
|
||||
nullptr,
|
||||
0,
|
||||
D3D11_SDK_VERSION,
|
||||
&mD3D11Device,
|
||||
nullptr,
|
||||
&mD3D11ImmediateContext);
|
||||
|
||||
mD3D11bindings.device = mD3D11Device;
|
||||
|
||||
//typedef HANDLE (WINAPI* P_wglDXOpenDeviceNV)(void* dxDevice);
|
||||
//P_wglDXOpenDeviceNV wglDXOpenDeviceNV = reinterpret_cast<P_wglDXOpenDeviceNV>(wglGetProcAddress("wglDXOpenDeviceNV"));
|
||||
//P_wglDXOpenDeviceNV wglDXOpenDeviceNV = reinterpret_cast<P_wglDXOpenDeviceNV>(wglGetProcAddress("wglDXOpenDeviceNV"));
|
||||
|
||||
#define LOAD_WGL(a) a = reinterpret_cast<decltype(a)>(wglGetProcAddress(#a)); if(!a) throw std::runtime_error("Extension WGL_NV_DX_interop2 required to run OpenMW VR via DirectX missing expected symbol '" #a "'.")
|
||||
LOAD_WGL(wglDXSetResourceShareHandleNV);
|
||||
LOAD_WGL(wglDXOpenDeviceNV);
|
||||
LOAD_WGL(wglDXCloseDeviceNV);
|
||||
LOAD_WGL(wglDXRegisterObjectNV);
|
||||
LOAD_WGL(wglDXUnregisterObjectNV);
|
||||
LOAD_WGL(wglDXObjectAccessNV);
|
||||
LOAD_WGL(wglDXLockObjectsNV);
|
||||
LOAD_WGL(wglDXUnlockObjectsNV);
|
||||
#undef LOAD_WGL
|
||||
|
||||
wglDXDevice = wglDXOpenDeviceNV(mD3D11Device);
|
||||
}
|
||||
|
||||
bool mWGL_NV_DX_interop2 = false;
|
||||
XrGraphicsBindingD3D11KHR mD3D11bindings{ XR_TYPE_GRAPHICS_BINDING_D3D11_KHR };
|
||||
ID3D11Device* mD3D11Device = nullptr;
|
||||
ID3D11DeviceContext* mD3D11ImmediateContext = nullptr;
|
||||
HMODULE mD3D11Dll = nullptr;
|
||||
PFN_D3D11_CREATE_DEVICE pD3D11CreateDevice = nullptr;
|
||||
|
||||
P_wglDXSetResourceShareHandleNV wglDXSetResourceShareHandleNV = nullptr;
|
||||
P_wglDXOpenDeviceNV wglDXOpenDeviceNV = nullptr;
|
||||
P_wglDXCloseDeviceNV wglDXCloseDeviceNV = nullptr;
|
||||
P_wglDXRegisterObjectNV wglDXRegisterObjectNV = nullptr;
|
||||
P_wglDXUnregisterObjectNV wglDXUnregisterObjectNV = nullptr;
|
||||
P_wglDXObjectAccessNV wglDXObjectAccessNV = nullptr;
|
||||
P_wglDXLockObjectsNV wglDXLockObjectsNV = nullptr;
|
||||
P_wglDXUnlockObjectsNV wglDXUnlockObjectsNV = nullptr;
|
||||
|
||||
HANDLE wglDXDevice = nullptr;
|
||||
#endif
|
||||
};
|
||||
|
||||
OpenXRPlatformPrivate::OpenXRPlatformPrivate(osg::GraphicsContext* gc)
|
||||
{
|
||||
#ifdef XR_USE_GRAPHICS_API_D3D11
|
||||
typedef const char* (WINAPI* PFNWGLGETEXTENSIONSSTRINGARBPROC) (HDC hdc);
|
||||
PFNWGLGETEXTENSIONSSTRINGARBPROC wglGetExtensionsStringARB = 0;
|
||||
wglGetExtensionsStringARB = reinterpret_cast<PFNWGLGETEXTENSIONSSTRINGARBPROC>(wglGetProcAddress("wglGetExtensionsStringARB"));
|
||||
if (wglGetExtensionsStringARB)
|
||||
{
|
||||
std::string wglExtensions = wglGetExtensionsStringARB(wglGetCurrentDC());
|
||||
Log(Debug::Verbose) << "WGL Extensions: " << wglExtensions;
|
||||
mWGL_NV_DX_interop2 = wglExtensions.find("WGL_NV_DX_interop2") != std::string::npos;
|
||||
}
|
||||
else
|
||||
Log(Debug::Verbose) << "Unable to query WGL extensions";
|
||||
#endif
|
||||
}
|
||||
|
||||
OpenXRPlatformPrivate::~OpenXRPlatformPrivate()
|
||||
{
|
||||
#ifdef XR_USE_GRAPHICS_API_D3D11
|
||||
if (wglDXDevice)
|
||||
wglDXCloseDeviceNV(wglDXDevice);
|
||||
if (mD3D11ImmediateContext)
|
||||
mD3D11ImmediateContext->Release();
|
||||
if (mD3D11Device)
|
||||
mD3D11Device->Release();
|
||||
if (mD3D11Dll)
|
||||
FreeLibrary(mD3D11Dll);
|
||||
#endif
|
||||
}
|
||||
|
||||
OpenXRPlatform::OpenXRPlatform(osg::GraphicsContext* gc)
|
||||
: mPrivate(new OpenXRPlatformPrivate(gc))
|
||||
{
|
||||
// Enumerate layers and their extensions.
|
||||
uint32_t layerCount;
|
||||
CHECK_XRCMD(xrEnumerateApiLayerProperties(0, &layerCount, nullptr));
|
||||
std::vector<XrApiLayerProperties> layers(layerCount, XrApiLayerProperties{ XR_TYPE_API_LAYER_PROPERTIES });
|
||||
CHECK_XRCMD(xrEnumerateApiLayerProperties((uint32_t)layers.size(), &layerCount, layers.data()));
|
||||
|
||||
Log(Debug::Verbose) << "Available Extensions: ";
|
||||
enumerateExtensions(nullptr, 2);
|
||||
Log(Debug::Verbose) << "Available Layers: ";
|
||||
|
||||
if (layers.size() == 0)
|
||||
{
|
||||
Log(Debug::Verbose) << " No layers available";
|
||||
}
|
||||
for (const XrApiLayerProperties& layer : layers) {
|
||||
Log(Debug::Verbose) << " Name=" << layer.layerName << " SpecVersion=" << layer.layerVersion;
|
||||
mAvailableLayers[layer.layerName] = layer;
|
||||
enumerateExtensions(layer.layerName, 4);
|
||||
}
|
||||
|
||||
setupExtensions();
|
||||
}
|
||||
|
||||
OpenXRPlatform::~OpenXRPlatform()
|
||||
{
|
||||
}
|
||||
|
||||
#if !XR_KHR_composition_layer_depth \
|
||||
|| !XR_EXT_hp_mixed_reality_controller \
|
||||
|| !XR_EXT_debug_utils \
|
||||
|| !XR_HTC_vive_cosmos_controller_interaction \
|
||||
|| !XR_HUAWEI_controller_interaction
|
||||
|
||||
#error "OpenXR extensions missing. Please upgrade your copy of the OpenXR SDK to 1.0.13 minimum"
|
||||
#endif
|
||||
void OpenXRPlatform::setupExtensions()
|
||||
{
|
||||
std::vector<const char*> optionalExtensions = {
|
||||
XR_EXT_HP_MIXED_REALITY_CONTROLLER_EXTENSION_NAME,
|
||||
XR_HTC_VIVE_COSMOS_CONTROLLER_INTERACTION_EXTENSION_NAME,
|
||||
XR_HUAWEI_CONTROLLER_INTERACTION_EXTENSION_NAME
|
||||
};
|
||||
|
||||
if (Settings::Manager::getBool("enable XR_KHR_composition_layer_depth", "VR Debug"))
|
||||
optionalExtensions.emplace_back(XR_KHR_COMPOSITION_LAYER_DEPTH_EXTENSION_NAME);
|
||||
|
||||
if (Settings::Manager::getBool("enable XR_EXT_debug_utils", "VR Debug"))
|
||||
optionalExtensions.emplace_back(XR_EXT_DEBUG_UTILS_EXTENSION_NAME);
|
||||
|
||||
selectGraphicsAPIExtension();
|
||||
|
||||
Log(Debug::Verbose) << "Using extensions:";
|
||||
|
||||
auto* graphicsAPIExtension = graphicsAPIExtensionName();
|
||||
if (!graphicsAPIExtension || !enableExtension(graphicsAPIExtension, true))
|
||||
{
|
||||
throw std::runtime_error("No graphics APIs supported by openmw are supported by the OpenXR runtime.");
|
||||
}
|
||||
|
||||
for (auto optionalExtension : optionalExtensions)
|
||||
enableExtension(optionalExtension, true);
|
||||
}
|
||||
|
||||
bool OpenXRPlatform::selectDirectX()
|
||||
{
|
||||
#ifdef XR_USE_GRAPHICS_API_D3D11
|
||||
if (mPrivate->mWGL_NV_DX_interop2)
|
||||
{
|
||||
if (mAvailableExtensions.count(XR_KHR_D3D11_ENABLE_EXTENSION_NAME))
|
||||
{
|
||||
mGraphicsAPIExtension = XR_KHR_D3D11_ENABLE_EXTENSION_NAME;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
Log(Debug::Warning) << "Warning: Failed to select DirectX swapchains: OpenXR runtime does not support essential extension '" << XR_KHR_D3D11_ENABLE_EXTENSION_NAME << "'";
|
||||
}
|
||||
else
|
||||
Log(Debug::Warning) << "Warning: Failed to select DirectX swapchains: Essential WGL extension 'WGL_NV_DX_interop2' not supported by the graphics driver.";
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
bool OpenXRPlatform::selectOpenGL()
|
||||
{
|
||||
#ifdef XR_USE_GRAPHICS_API_OPENGL
|
||||
if (mAvailableExtensions.count(XR_KHR_OPENGL_ENABLE_EXTENSION_NAME))
|
||||
{
|
||||
mGraphicsAPIExtension = XR_KHR_OPENGL_ENABLE_EXTENSION_NAME;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
Log(Debug::Warning) << "Warning: Failed to select OpenGL swapchains: OpenXR runtime does not support essential extension '" << XR_KHR_OPENGL_ENABLE_EXTENSION_NAME << "'";
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef XR_USE_GRAPHICS_API_OPENGL
|
||||
#if !XR_KHR_opengl_enable
|
||||
#error "OpenXR extensions missing. Please upgrade your copy of the OpenXR SDK to 1.0.13 minimum"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef XR_USE_GRAPHICS_API_D3D11
|
||||
#if !XR_KHR_D3D11_enable
|
||||
#error "OpenXR extensions missing. Please upgrade your copy of the OpenXR SDK to 1.0.13 minimum"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
const char* OpenXRPlatform::graphicsAPIExtensionName()
|
||||
{
|
||||
return mGraphicsAPIExtension;
|
||||
}
|
||||
|
||||
void OpenXRPlatform::selectGraphicsAPIExtension()
|
||||
{
|
||||
bool preferDirectX = Settings::Manager::getBool("Prefer DirectX swapchains", "VR");
|
||||
|
||||
if (preferDirectX)
|
||||
if (selectDirectX() || selectOpenGL())
|
||||
return;
|
||||
if (selectOpenGL() || selectDirectX())
|
||||
return;
|
||||
|
||||
Log(Debug::Verbose) << "Error: No graphics API supported by OpenMW VR is supported by the OpenXR runtime.";
|
||||
throw std::runtime_error("Error: No graphics API supported by OpenMW VR is supported by the OpenXR runtime.");
|
||||
}
|
||||
|
||||
bool OpenXRPlatform::supportsExtension(const std::string& extensionName) const
|
||||
{
|
||||
return mAvailableExtensions.count(extensionName) > 0;
|
||||
}
|
||||
|
||||
bool OpenXRPlatform::supportsExtension(const std::string& extensionName, uint32_t minimumVersion) const
|
||||
{
|
||||
auto it = mAvailableExtensions.find(extensionName);
|
||||
return it != mAvailableExtensions.end() && it->second.extensionVersion > minimumVersion;
|
||||
}
|
||||
|
||||
bool OpenXRPlatform::supportsLayer(const std::string& layerName) const
|
||||
{
|
||||
return mAvailableLayers.count(layerName) > 0;
|
||||
}
|
||||
|
||||
bool OpenXRPlatform::supportsLayer(const std::string& layerName, uint32_t minimumVersion) const
|
||||
{
|
||||
auto it = mAvailableLayers.find(layerName);
|
||||
return it != mAvailableLayers.end() && it->second.layerVersion > minimumVersion;
|
||||
}
|
||||
|
||||
bool OpenXRPlatform::enableExtension(const std::string& extensionName, bool optional)
|
||||
{
|
||||
auto it = mAvailableExtensions.find(extensionName);
|
||||
if (it != mAvailableExtensions.end())
|
||||
{
|
||||
Log(Debug::Verbose) << " " << extensionName << ": enabled";
|
||||
mEnabledExtensions.push_back(it->second.extensionName);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log(Debug::Verbose) << " " << extensionName << ": disabled (not supported)";
|
||||
if (!optional)
|
||||
{
|
||||
throw std::runtime_error(std::string("Required OpenXR extension ") + extensionName + " not supported by the runtime");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool OpenXRPlatform::enableExtension(const std::string& extensionName, bool optional, uint32_t minimumVersion)
|
||||
{
|
||||
auto it = mAvailableExtensions.find(extensionName);
|
||||
if (it != mAvailableExtensions.end() && it->second.extensionVersion > minimumVersion)
|
||||
{
|
||||
Log(Debug::Verbose) << " " << extensionName << ": enabled";
|
||||
mEnabledExtensions.push_back(it->second.extensionName);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log(Debug::Verbose) << " " << extensionName << ": disabled (not supported)";
|
||||
if (!optional)
|
||||
{
|
||||
throw std::runtime_error(std::string("Required OpenXR extension ") + extensionName + " not supported by the runtime");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool OpenXRPlatform::extensionEnabled(const std::string& extensionName) const
|
||||
{
|
||||
for (auto* extension : mEnabledExtensions)
|
||||
if (extension == extensionName)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
XrInstance OpenXRPlatform::createXrInstance(const std::string& name)
|
||||
{
|
||||
XrInstance instance = XR_NULL_HANDLE;
|
||||
XrInstanceCreateInfo createInfo{ XR_TYPE_INSTANCE_CREATE_INFO };
|
||||
createInfo.next = nullptr;
|
||||
createInfo.enabledExtensionCount = mEnabledExtensions.size();
|
||||
createInfo.enabledExtensionNames = mEnabledExtensions.data();
|
||||
strcpy(createInfo.applicationInfo.applicationName, "openmw_vr");
|
||||
createInfo.applicationInfo.apiVersion = XR_CURRENT_API_VERSION;
|
||||
|
||||
auto res = CHECK_XRCMD(xrCreateInstance(&createInfo, &instance));
|
||||
if (!XR_SUCCEEDED(res))
|
||||
initFailure(res, instance);
|
||||
return instance;
|
||||
}
|
||||
|
||||
XrSession OpenXRPlatform::createXrSession(XrInstance instance, XrSystemId systemId)
|
||||
{
|
||||
XrSession session = XR_NULL_HANDLE;
|
||||
XrResult res = XR_SUCCESS;
|
||||
#ifdef _WIN32
|
||||
std::string graphicsAPIExtension = graphicsAPIExtensionName();
|
||||
if(graphicsAPIExtension == XR_KHR_OPENGL_ENABLE_EXTENSION_NAME)
|
||||
{
|
||||
// Get system requirements
|
||||
PFN_xrGetOpenGLGraphicsRequirementsKHR p_getRequirements = nullptr;
|
||||
CHECK_XRCMD(xrGetInstanceProcAddr(instance, "xrGetOpenGLGraphicsRequirementsKHR", reinterpret_cast<PFN_xrVoidFunction*>(&p_getRequirements)));
|
||||
XrGraphicsRequirementsOpenGLKHR requirements{ XR_TYPE_GRAPHICS_REQUIREMENTS_OPENGL_KHR };
|
||||
CHECK_XRCMD(p_getRequirements(instance, systemId, &requirements));
|
||||
|
||||
// TODO: Actually get system version
|
||||
const XrVersion systemApiVersion = XR_MAKE_VERSION(4, 6, 0);
|
||||
if (requirements.minApiVersionSupported > systemApiVersion) {
|
||||
std::cout << "Runtime does not support desired Graphics API and/or version" << std::endl;
|
||||
}
|
||||
|
||||
// Create Session
|
||||
auto DC = wglGetCurrentDC();
|
||||
auto GLRC = wglGetCurrentContext();
|
||||
|
||||
XrGraphicsBindingOpenGLWin32KHR graphicsBindings;
|
||||
graphicsBindings.type = XR_TYPE_GRAPHICS_BINDING_OPENGL_WIN32_KHR;
|
||||
graphicsBindings.next = nullptr;
|
||||
graphicsBindings.hDC = DC;
|
||||
graphicsBindings.hGLRC = GLRC;
|
||||
|
||||
if (!graphicsBindings.hDC)
|
||||
Log(Debug::Warning) << "Missing DC";
|
||||
|
||||
XrSessionCreateInfo createInfo{ XR_TYPE_SESSION_CREATE_INFO };
|
||||
createInfo.next = &graphicsBindings;
|
||||
createInfo.systemId = systemId;
|
||||
res = CHECK_XRCMD(xrCreateSession(instance, &createInfo, &session));
|
||||
}
|
||||
#ifdef XR_USE_GRAPHICS_API_D3D11
|
||||
else if(graphicsAPIExtension == XR_KHR_D3D11_ENABLE_EXTENSION_NAME)
|
||||
{
|
||||
PFN_xrGetD3D11GraphicsRequirementsKHR p_getRequirements = nullptr;
|
||||
CHECK_XRCMD(xrGetInstanceProcAddr(instance, "xrGetD3D11GraphicsRequirementsKHR", reinterpret_cast<PFN_xrVoidFunction*>(&p_getRequirements)));
|
||||
XrGraphicsRequirementsD3D11KHR requirements{ XR_TYPE_GRAPHICS_REQUIREMENTS_D3D11_KHR };
|
||||
CHECK_XRCMD(p_getRequirements(instance, systemId, &requirements));
|
||||
mPrivate->initializeD3D11(requirements);
|
||||
|
||||
XrSessionCreateInfo createInfo{ XR_TYPE_SESSION_CREATE_INFO };
|
||||
createInfo.next = &mPrivate->mD3D11bindings;
|
||||
createInfo.systemId = systemId;
|
||||
res = CHECK_XRCMD(xrCreateSession(instance, &createInfo, &session));
|
||||
}
|
||||
#endif
|
||||
else
|
||||
{
|
||||
throw std::logic_error("Enum value not implemented");
|
||||
}
|
||||
#elif __linux__
|
||||
{
|
||||
// Get system requirements
|
||||
PFN_xrGetOpenGLGraphicsRequirementsKHR p_getRequirements = nullptr;
|
||||
xrGetInstanceProcAddr(instance, "xrGetOpenGLGraphicsRequirementsKHR", reinterpret_cast<PFN_xrVoidFunction*>(&p_getRequirements));
|
||||
XrGraphicsRequirementsOpenGLKHR requirements{ XR_TYPE_GRAPHICS_REQUIREMENTS_OPENGL_KHR };
|
||||
CHECK_XRCMD(p_getRequirements(instance, systemId, &requirements));
|
||||
|
||||
// TODO: Actually get system version
|
||||
const XrVersion systemApiVersion = XR_MAKE_VERSION(4, 6, 0);
|
||||
if (requirements.minApiVersionSupported > systemApiVersion) {
|
||||
std::cout << "Runtime does not support desired Graphics API and/or version" << std::endl;
|
||||
}
|
||||
|
||||
// Create Session
|
||||
Display* xDisplay = XOpenDisplay(NULL);
|
||||
GLXContext glxContext = glXGetCurrentContext();
|
||||
GLXDrawable glxDrawable = glXGetCurrentDrawable();
|
||||
|
||||
// TODO: runtimes don't actually care (yet)
|
||||
GLXFBConfig glxFBConfig = 0;
|
||||
uint32_t visualid = 0;
|
||||
|
||||
XrGraphicsBindingOpenGLXlibKHR graphicsBindings;
|
||||
graphicsBindings.type = XR_TYPE_GRAPHICS_BINDING_OPENGL_XLIB_KHR;
|
||||
graphicsBindings.next = nullptr;
|
||||
graphicsBindings.xDisplay = xDisplay;
|
||||
graphicsBindings.glxContext = glxContext;
|
||||
graphicsBindings.glxDrawable = glxDrawable;
|
||||
graphicsBindings.glxFBConfig = glxFBConfig;
|
||||
graphicsBindings.visualid = visualid;
|
||||
|
||||
if (!graphicsBindings.glxContext)
|
||||
Log(Debug::Warning) << "Missing glxContext";
|
||||
|
||||
if (!graphicsBindings.glxDrawable)
|
||||
Log(Debug::Warning) << "Missing glxDrawable";
|
||||
|
||||
XrSessionCreateInfo createInfo{ XR_TYPE_SESSION_CREATE_INFO };
|
||||
createInfo.next = &graphicsBindings;
|
||||
createInfo.systemId = systemId;
|
||||
res = CHECK_XRCMD(xrCreateSession(instance, &createInfo, &session));
|
||||
}
|
||||
#endif
|
||||
if (!XR_SUCCEEDED(res))
|
||||
initFailure(res, instance);
|
||||
|
||||
uint32_t swapchainFormatCount;
|
||||
CHECK_XRCMD(xrEnumerateSwapchainFormats(session, 0, &swapchainFormatCount, nullptr));
|
||||
mSwapchainFormats.resize(swapchainFormatCount);
|
||||
CHECK_XRCMD(xrEnumerateSwapchainFormats(session, (uint32_t)mSwapchainFormats.size(), &swapchainFormatCount, mSwapchainFormats.data()));
|
||||
|
||||
std::stringstream ss;
|
||||
ss << "Available Swapchain formats: (" << swapchainFormatCount << ")" << std::endl;
|
||||
|
||||
for (auto format : mSwapchainFormats)
|
||||
{
|
||||
ss << " Enum=" << std::dec << format << " (0x=" << std::hex << format << ")" << std::dec << std::endl;
|
||||
}
|
||||
|
||||
Log(Debug::Verbose) << ss.str();
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
/*
|
||||
* For reference: These are the DXGI formats offered by WMR when using D3D11:
|
||||
Enum=29 //(0x=1d) DXGI_FORMAT_R8G8B8A8_UNORM_SRGB
|
||||
Enum=91 //(0x=5b) DXGI_FORMAT_B8G8R8A8_UNORM_SRGB
|
||||
Enum=28 //(0x=1c) DXGI_FORMAT_R8G8B8A8_UNORM
|
||||
Enum=87 //(0x=57) DXGI_FORMAT_B8G8R8A8_UNORM
|
||||
Enum=40 //(0x=28) DXGI_FORMAT_D32_FLOAT
|
||||
Enum=20 //(0x=14) DXGI_FORMAT_D32_FLOAT_S8X24_UINT
|
||||
Enum=45 //(0x=2d) DXGI_FORMAT_D24_UNORM_S8_UINT
|
||||
Enum=55 //(0x=37) DXGI_FORMAT_D16_UNORM
|
||||
* And these extra formats are offered by SteamVR:
|
||||
0xa , // DXGI_FORMAT_R16G16B16A16_FLOAT
|
||||
0x18 , // DXGI_FORMAT_R10G10B10A2_UNORM
|
||||
*/
|
||||
|
||||
int64_t OpenXRPlatform::selectColorFormat()
|
||||
{
|
||||
std::string graphicsAPIExtension = graphicsAPIExtensionName();
|
||||
if (graphicsAPIExtension == XR_KHR_OPENGL_ENABLE_EXTENSION_NAME)
|
||||
{
|
||||
std::vector<int64_t> requestedColorSwapchainFormats;
|
||||
|
||||
if (Settings::Manager::getBool("Prefer sRGB swapchains", "VR"))
|
||||
{
|
||||
requestedColorSwapchainFormats.push_back(0x8C43); // GL_SRGB8_ALPHA8
|
||||
requestedColorSwapchainFormats.push_back(0x8C41); // GL_SRGB8
|
||||
}
|
||||
|
||||
requestedColorSwapchainFormats.push_back(0x8058); // GL_RGBA8
|
||||
requestedColorSwapchainFormats.push_back(0x8F97); // GL_RGBA8_SNORM
|
||||
requestedColorSwapchainFormats.push_back(0x881A); // GL_RGBA16F
|
||||
requestedColorSwapchainFormats.push_back(0x881B); // GL_RGB16F
|
||||
requestedColorSwapchainFormats.push_back(0x8C3A); // GL_R11F_G11F_B10F
|
||||
|
||||
return selectFormat(requestedColorSwapchainFormats);
|
||||
}
|
||||
#ifdef XR_USE_GRAPHICS_API_D3D11
|
||||
else if (graphicsAPIExtension == XR_KHR_D3D11_ENABLE_EXTENSION_NAME)
|
||||
{
|
||||
std::vector<int64_t> requestedColorSwapchainFormats;
|
||||
|
||||
if (Settings::Manager::getBool("Prefer sRGB swapchains", "VR"))
|
||||
{
|
||||
requestedColorSwapchainFormats.push_back(0x1d); // DXGI_FORMAT_R8G8B8A8_UNORM_SRGB
|
||||
requestedColorSwapchainFormats.push_back(0x5b); // DXGI_FORMAT_B8G8R8A8_UNORM_SRGB
|
||||
}
|
||||
|
||||
requestedColorSwapchainFormats.push_back(0x1c); // DXGI_FORMAT_R8G8B8A8_UNORM
|
||||
requestedColorSwapchainFormats.push_back(0x57); // DXGI_FORMAT_B8G8R8A8_UNORM
|
||||
requestedColorSwapchainFormats.push_back(0xa); // DXGI_FORMAT_R16G16B16A16_FLOAT
|
||||
requestedColorSwapchainFormats.push_back(0x18); // DXGI_FORMAT_R10G10B10A2_UNORM
|
||||
|
||||
return selectFormat(requestedColorSwapchainFormats);
|
||||
}
|
||||
#endif
|
||||
else
|
||||
{
|
||||
throw std::logic_error("Enum value not implemented");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int64_t OpenXRPlatform::selectDepthFormat()
|
||||
{
|
||||
std::string graphicsAPIExtension = graphicsAPIExtensionName();
|
||||
if (graphicsAPIExtension == XR_KHR_OPENGL_ENABLE_EXTENSION_NAME)
|
||||
{
|
||||
// Find supported depth swapchain format.
|
||||
std::vector<int64_t> requestedDepthSwapchainFormats = {
|
||||
0x88F0, // GL_DEPTH24_STENCIL8
|
||||
0x8CAC, // GL_DEPTH_COMPONENT32F
|
||||
0x81A7, // GL_DEPTH_COMPONENT32
|
||||
0x8DAB, // GL_DEPTH_COMPONENT32F_NV
|
||||
0x8CAD, // GL_DEPTH32_STENCIL8
|
||||
0x81A6, // GL_DEPTH_COMPONENT24
|
||||
// Need 32bit minimum: // 0x81A5, // GL_DEPTH_COMPONENT16
|
||||
};
|
||||
|
||||
return selectFormat(requestedDepthSwapchainFormats);
|
||||
}
|
||||
#ifdef XR_USE_GRAPHICS_API_D3D11
|
||||
else if (graphicsAPIExtension == XR_KHR_D3D11_ENABLE_EXTENSION_NAME)
|
||||
{
|
||||
// Find supported color swapchain format.
|
||||
std::vector<int64_t> requestedDepthSwapchainFormats = {
|
||||
0x2d, // DXGI_FORMAT_D24_UNORM_S8_UINT
|
||||
0x14, // DXGI_FORMAT_D32_FLOAT_S8X24_UINT
|
||||
0x28, // DXGI_FORMAT_D32_FLOAT
|
||||
// Need 32bit minimum: 0x37, // DXGI_FORMAT_D16_UNORM
|
||||
};
|
||||
return selectFormat(requestedDepthSwapchainFormats);
|
||||
}
|
||||
#endif
|
||||
else
|
||||
{
|
||||
throw std::logic_error("Enum value not implemented");
|
||||
}
|
||||
}
|
||||
|
||||
void OpenXRPlatform::eraseFormat(int64_t format)
|
||||
{
|
||||
for (auto it = mSwapchainFormats.begin(); it != mSwapchainFormats.end(); it++)
|
||||
{
|
||||
if (*it == format)
|
||||
{
|
||||
mSwapchainFormats.erase(it);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int64_t OpenXRPlatform::selectFormat(const std::vector<int64_t>& requestedFormats)
|
||||
{
|
||||
auto it =
|
||||
std::find_first_of(std::begin(requestedFormats), std::end(requestedFormats),
|
||||
mSwapchainFormats.begin(), mSwapchainFormats.end());
|
||||
if (it == std::end(requestedFormats))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
return *it;
|
||||
}
|
||||
void* OpenXRPlatform::DXRegisterObject(void* dxResource, uint32_t glName, uint32_t glType, bool discard, void* ntShareHandle)
|
||||
{
|
||||
#ifdef XR_USE_GRAPHICS_API_D3D11
|
||||
if (ntShareHandle)
|
||||
{
|
||||
mPrivate->wglDXSetResourceShareHandleNV(dxResource, ntShareHandle);
|
||||
}
|
||||
return mPrivate->wglDXRegisterObjectNV(mPrivate->wglDXDevice, dxResource, glName, glType, 1);
|
||||
#else
|
||||
return nullptr;
|
||||
#endif
|
||||
}
|
||||
void OpenXRPlatform::DXUnregisterObject(void* dxResourceShareHandle)
|
||||
{
|
||||
#ifdef XR_USE_GRAPHICS_API_D3D11
|
||||
mPrivate->wglDXUnregisterObjectNV(mPrivate->wglDXDevice, dxResourceShareHandle);
|
||||
#endif
|
||||
}
|
||||
void OpenXRPlatform::DXLockObject(void* dxResourceShareHandle)
|
||||
{
|
||||
#ifdef XR_USE_GRAPHICS_API_D3D11
|
||||
mPrivate->wglDXLockObjectsNV(mPrivate->wglDXDevice, 1, &dxResourceShareHandle);
|
||||
#endif
|
||||
}
|
||||
void OpenXRPlatform::DXUnlockObject(void* dxResourceShareHandle)
|
||||
{
|
||||
#ifdef XR_USE_GRAPHICS_API_D3D11
|
||||
mPrivate->wglDXUnlockObjectsNV(mPrivate->wglDXDevice, 1, &dxResourceShareHandle);
|
||||
#endif
|
||||
}
|
||||
|
||||
static XrInstanceProperties
|
||||
getInstanceProperties(XrInstance instance)
|
||||
{
|
||||
XrInstanceProperties properties{ XR_TYPE_INSTANCE_PROPERTIES };
|
||||
if (instance)
|
||||
xrGetInstanceProperties(instance, &properties);
|
||||
return properties;
|
||||
}
|
||||
|
||||
std::string OpenXRPlatform::getInstanceName(XrInstance instance)
|
||||
{
|
||||
if (instance)
|
||||
return getInstanceProperties(instance).runtimeName;
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
XrVersion OpenXRPlatform::getInstanceVersion(XrInstance instance)
|
||||
{
|
||||
if (instance)
|
||||
return getInstanceProperties(instance).runtimeVersion;
|
||||
return 0;
|
||||
}
|
||||
void OpenXRPlatform::initFailure(XrResult res, XrInstance instance)
|
||||
{
|
||||
std::stringstream ss;
|
||||
std::string runtimeName = getInstanceName(instance);
|
||||
XrVersion runtimeVersion = getInstanceVersion(instance);
|
||||
ss << "Error caught while initializing VR device: " << XrResultString(res) << std::endl;
|
||||
ss << "Device: " << runtimeName << std::endl;
|
||||
ss << "Version: " << runtimeVersion << std::endl;
|
||||
if (res == XR_ERROR_FORM_FACTOR_UNAVAILABLE)
|
||||
{
|
||||
ss << "Cause: Unable to open VR device. Make sure your device is plugged in and the VR driver is running." << std::endl;
|
||||
ss << std::endl;
|
||||
if (runtimeName == "Oculus" || runtimeName == "Quest")
|
||||
{
|
||||
ss << "Your device has been identified as an Oculus device." << std::endl;
|
||||
ss << "The most common cause for this error when using an oculus device, is quest users attempting to run the game via Virtual Desktop." << std::endl;
|
||||
ss << "Unfortunately this is currently broken, and quest users will need to play via a link cable." << std::endl;
|
||||
}
|
||||
}
|
||||
else if (res == XR_ERROR_LIMIT_REACHED)
|
||||
{
|
||||
ss << "Cause: Device resources exhausted. Close other VR applications if you have any open. If you have none, you may need to reboot to reset the driver." << std::endl;
|
||||
}
|
||||
else
|
||||
{
|
||||
ss << "Cause: Unknown. Make sure your device is plugged in and ready." << std::endl;
|
||||
}
|
||||
throw std::runtime_error(ss.str());
|
||||
}
|
||||
}
|
86
apps/openmw/mwvr/openxrplatform.hpp
Normal file
86
apps/openmw/mwvr/openxrplatform.hpp
Normal file
|
@ -0,0 +1,86 @@
|
|||
#ifndef OPENXR_PLATFORM_HPP
|
||||
#define OPENXR_PLATFORM_HPP
|
||||
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <string>
|
||||
|
||||
#include <openxr/openxr.h>
|
||||
|
||||
namespace MWVR
|
||||
{
|
||||
// Error management macros and functions. Should be used on every openxr call.
|
||||
#define CHK_STRINGIFY(x) #x
|
||||
#define TOSTRING(x) CHK_STRINGIFY(x)
|
||||
#define FILE_AND_LINE __FILE__ ":" TOSTRING(__LINE__)
|
||||
#define CHECK_XRCMD(cmd) CheckXrResult(cmd, #cmd, FILE_AND_LINE)
|
||||
#define CHECK_XRRESULT(res, cmdStr) CheckXrResult(res, cmdStr, FILE_AND_LINE)
|
||||
XrResult CheckXrResult(XrResult res, const char* originator = nullptr, const char* sourceLocation = nullptr);
|
||||
std::string XrResultString(XrResult res);
|
||||
|
||||
struct OpenXRPlatformPrivate;
|
||||
|
||||
class OpenXRPlatform
|
||||
{
|
||||
public:
|
||||
using ExtensionMap = std::map<std::string, XrExtensionProperties>;
|
||||
using LayerMap = std::map<std::string, XrApiLayerProperties>;
|
||||
using LayerExtensionMap = std::map<std::string, ExtensionMap>;
|
||||
|
||||
public:
|
||||
OpenXRPlatform(osg::GraphicsContext* gc);
|
||||
~OpenXRPlatform();
|
||||
|
||||
const char* graphicsAPIExtensionName();
|
||||
bool supportsExtension(const std::string& extensionName) const;
|
||||
bool supportsExtension(const std::string& extensionName, uint32_t minimumVersion) const;
|
||||
bool supportsLayer(const std::string& layerName) const;
|
||||
bool supportsLayer(const std::string& layerName, uint32_t minimumVersion) const;
|
||||
|
||||
bool enableExtension(const std::string& extensionName, bool optional);
|
||||
bool enableExtension(const std::string& extensionName, bool optional, uint32_t minimumVersion);
|
||||
|
||||
bool extensionEnabled(const std::string& extensionName) const;
|
||||
|
||||
XrInstance createXrInstance(const std::string& name);
|
||||
XrSession createXrSession(XrInstance instance, XrSystemId systemId);
|
||||
|
||||
int64_t selectColorFormat();
|
||||
int64_t selectDepthFormat();
|
||||
int64_t selectFormat(const std::vector<int64_t>& requestedFormats);
|
||||
void eraseFormat(int64_t format);
|
||||
std::vector<int64_t> mSwapchainFormats{};
|
||||
|
||||
/// Registers an object for sharing as if calling wglDXRegisterObjectNV requesting write access.
|
||||
/// If ntShareHandle is not null, wglDXSetResourceShareHandleNV is called first to register the share handle
|
||||
void* DXRegisterObject(void* dxResource, uint32_t glName, uint32_t glType, bool discard, void* ntShareHandle);
|
||||
/// Unregisters an object from sharing as if calling wglDXUnregisterObjectNV
|
||||
void DXUnregisterObject(void* dxResourceShareHandle);
|
||||
/// Locks a DX object for use by OpenGL as if calling wglDXLockObjectsNV
|
||||
void DXLockObject(void* dxResourceShareHandle);
|
||||
/// Unlocks a DX object for use by DirectX as if calling wglDXUnlockObjectsNV
|
||||
void DXUnlockObject(void* dxResourceShareHandle);
|
||||
|
||||
std::string getInstanceName(XrInstance instance);
|
||||
XrVersion getInstanceVersion(XrInstance instance);
|
||||
void initFailure(XrResult, XrInstance);
|
||||
|
||||
private:
|
||||
void enumerateExtensions(const char* layerName, int logIndent);
|
||||
void setupExtensions();
|
||||
void selectGraphicsAPIExtension();
|
||||
bool selectDirectX();
|
||||
bool selectOpenGL();
|
||||
|
||||
ExtensionMap mAvailableExtensions;
|
||||
LayerMap mAvailableLayers;
|
||||
LayerExtensionMap mAvailableLayerExtensions;
|
||||
std::vector<const char*> mEnabledExtensions;
|
||||
const char* mGraphicsAPIExtension = nullptr;
|
||||
|
||||
std::unique_ptr< OpenXRPlatformPrivate > mPrivate;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
48
apps/openmw/mwvr/openxrswapchain.cpp
Normal file
48
apps/openmw/mwvr/openxrswapchain.cpp
Normal file
|
@ -0,0 +1,48 @@
|
|||
#include "openxrswapchain.hpp"
|
||||
#include "openxrswapchainimpl.hpp"
|
||||
#include "openxrmanager.hpp"
|
||||
#include "openxrmanagerimpl.hpp"
|
||||
#include "vrenvironment.hpp"
|
||||
|
||||
#include <components/debug/debuglog.hpp>
|
||||
|
||||
namespace MWVR {
|
||||
OpenXRSwapchain::OpenXRSwapchain(osg::ref_ptr<osg::State> state, SwapchainConfig config)
|
||||
: mPrivate(new OpenXRSwapchainImpl(state, config))
|
||||
{
|
||||
}
|
||||
|
||||
OpenXRSwapchain::~OpenXRSwapchain()
|
||||
{
|
||||
}
|
||||
|
||||
void OpenXRSwapchain::beginFrame(osg::GraphicsContext* gc)
|
||||
{
|
||||
return impl().beginFrame(gc);
|
||||
}
|
||||
|
||||
void OpenXRSwapchain::endFrame(osg::GraphicsContext* gc, VRFramebuffer& readBuffer)
|
||||
{
|
||||
return impl().endFrame(gc, readBuffer);
|
||||
}
|
||||
|
||||
int OpenXRSwapchain::width() const
|
||||
{
|
||||
return impl().width();
|
||||
}
|
||||
|
||||
int OpenXRSwapchain::height() const
|
||||
{
|
||||
return impl().height();
|
||||
}
|
||||
|
||||
int OpenXRSwapchain::samples() const
|
||||
{
|
||||
return impl().samples();
|
||||
}
|
||||
|
||||
bool OpenXRSwapchain::isAcquired() const
|
||||
{
|
||||
return impl().isAcquired();
|
||||
}
|
||||
}
|
53
apps/openmw/mwvr/openxrswapchain.hpp
Normal file
53
apps/openmw/mwvr/openxrswapchain.hpp
Normal file
|
@ -0,0 +1,53 @@
|
|||
#ifndef OPENXR_SWAPCHAIN_HPP
|
||||
#define OPENXR_SWAPCHAIN_HPP
|
||||
|
||||
#include "openxrmanager.hpp"
|
||||
|
||||
struct XrSwapchainSubImage;
|
||||
|
||||
namespace MWVR
|
||||
{
|
||||
class OpenXRSwapchainImpl;
|
||||
class VRFramebuffer;
|
||||
|
||||
/// \brief Creation and management of openxr swapchains
|
||||
class OpenXRSwapchain
|
||||
{
|
||||
public:
|
||||
OpenXRSwapchain(osg::ref_ptr<osg::State> state, SwapchainConfig config);
|
||||
~OpenXRSwapchain();
|
||||
|
||||
public:
|
||||
//! Prepare for render (set FBO)
|
||||
void beginFrame(osg::GraphicsContext* gc);
|
||||
|
||||
//! Finalize render
|
||||
void endFrame(osg::GraphicsContext* gc, VRFramebuffer& readBuffer);
|
||||
|
||||
//! Whether subchain is currently acquired (true) or released (false)
|
||||
bool isAcquired() const;
|
||||
|
||||
//! Width of the view surface
|
||||
int width() const;
|
||||
|
||||
//! Height of the view surface
|
||||
int height() const;
|
||||
|
||||
//! Samples of the view surface
|
||||
int samples() const;
|
||||
|
||||
//! Get the private implementation
|
||||
OpenXRSwapchainImpl& impl() { return *mPrivate; }
|
||||
|
||||
//! Get the private implementation
|
||||
const OpenXRSwapchainImpl& impl() const { return *mPrivate; }
|
||||
|
||||
protected:
|
||||
OpenXRSwapchain(const OpenXRSwapchain&) = delete;
|
||||
void operator=(const OpenXRSwapchain&) = delete;
|
||||
private:
|
||||
std::unique_ptr<OpenXRSwapchainImpl> mPrivate;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
221
apps/openmw/mwvr/openxrswapchainimage.cpp
Normal file
221
apps/openmw/mwvr/openxrswapchainimage.cpp
Normal file
|
@ -0,0 +1,221 @@
|
|||
#include "openxrswapchainimage.hpp"
|
||||
#include "openxrmanagerimpl.hpp"
|
||||
#include "vrenvironment.hpp"
|
||||
#include "vrframebuffer.hpp"
|
||||
|
||||
// The OpenXR SDK's platform headers assume we've included platform headers
|
||||
#ifdef _WIN32
|
||||
#include <Windows.h>
|
||||
#include <objbase.h>
|
||||
|
||||
#ifdef XR_USE_GRAPHICS_API_D3D11
|
||||
#include <d3d11.h>
|
||||
#include <dxgi1_2.h>
|
||||
#endif
|
||||
|
||||
#elif __linux__
|
||||
#include <X11/Xlib.h>
|
||||
#include <GL/glx.h>
|
||||
#undef None
|
||||
|
||||
#else
|
||||
#error Unsupported platform
|
||||
#endif
|
||||
|
||||
#include <openxr/openxr.h>
|
||||
#include <openxr/openxr_platform.h>
|
||||
#include <openxr/openxr_platform_defines.h>
|
||||
#include <openxr/openxr_reflection.h>
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
#define GLERR if(auto err = glGetError() != GL_NO_ERROR) Log(Debug::Verbose) << __FILE__ << "." << __LINE__ << ": " << glGetError()
|
||||
|
||||
namespace MWVR {
|
||||
|
||||
template<typename Image>
|
||||
class OpenXRSwapchainImageTemplate;
|
||||
|
||||
template<>
|
||||
class OpenXRSwapchainImageTemplate< XrSwapchainImageOpenGLKHR > : public OpenXRSwapchainImage
|
||||
{
|
||||
public:
|
||||
static constexpr XrStructureType XrType = XR_TYPE_SWAPCHAIN_IMAGE_OPENGL_KHR;
|
||||
|
||||
public:
|
||||
OpenXRSwapchainImageTemplate(osg::GraphicsContext* gc, XrSwapchainCreateInfo swapchainCreateInfo, const XrSwapchainImageOpenGLKHR& xrImage)
|
||||
: OpenXRSwapchainImage()
|
||||
, mXrImage(xrImage)
|
||||
, mBufferBits(0)
|
||||
, mFramebuffer(nullptr)
|
||||
{
|
||||
mFramebuffer.reset(new VRFramebuffer(gc->getState(), swapchainCreateInfo.width, swapchainCreateInfo.height, swapchainCreateInfo.sampleCount));
|
||||
if (swapchainCreateInfo.usageFlags & XR_SWAPCHAIN_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT)
|
||||
{
|
||||
mFramebuffer->setDepthBuffer(gc, mXrImage.image, false);
|
||||
mBufferBits = GL_DEPTH_BUFFER_BIT;
|
||||
}
|
||||
else
|
||||
{
|
||||
mFramebuffer->setColorBuffer(gc, mXrImage.image, false);
|
||||
mBufferBits = GL_COLOR_BUFFER_BIT;
|
||||
}
|
||||
}
|
||||
|
||||
void blit(osg::GraphicsContext* gc, VRFramebuffer& readBuffer, int offset_x, int offset_y) override
|
||||
{
|
||||
mFramebuffer->bindFramebuffer(gc, GL_FRAMEBUFFER_EXT);
|
||||
readBuffer.blit(gc, offset_x, offset_y, offset_x + mFramebuffer->width(), offset_y + mFramebuffer->height(), 0, 0, mFramebuffer->width(), mFramebuffer->height(), mBufferBits, GL_NEAREST);
|
||||
}
|
||||
|
||||
XrSwapchainImageOpenGLKHR mXrImage;
|
||||
uint32_t mBufferBits;
|
||||
std::unique_ptr<VRFramebuffer> mFramebuffer;
|
||||
};
|
||||
|
||||
#ifdef XR_USE_GRAPHICS_API_D3D11
|
||||
template<>
|
||||
class OpenXRSwapchainImageTemplate< XrSwapchainImageD3D11KHR > : public OpenXRSwapchainImage
|
||||
{
|
||||
public:
|
||||
static constexpr XrStructureType XrType = XR_TYPE_SWAPCHAIN_IMAGE_D3D11_KHR;
|
||||
public:
|
||||
OpenXRSwapchainImageTemplate(osg::GraphicsContext* gc, XrSwapchainCreateInfo swapchainCreateInfo, const XrSwapchainImageD3D11KHR& xrImage)
|
||||
: OpenXRSwapchainImage()
|
||||
, mXrImage(xrImage)
|
||||
, mBufferBits(0)
|
||||
, mFramebuffer(nullptr)
|
||||
{
|
||||
mXrImage.texture->GetDevice(&mDevice);
|
||||
mDevice->GetImmediateContext(&mDeviceContext);
|
||||
|
||||
mXrImage.texture->GetDesc(&mDesc);
|
||||
|
||||
glGenTextures(1, &mGlTextureName);
|
||||
|
||||
auto* xr = Environment::get().getManager();
|
||||
//mDxResourceShareHandle = xr->impl().platform().DXRegisterObject(mXrImage.texture, mGlTextureName, GL_TEXTURE_2D, true, nullptr);
|
||||
|
||||
if (!mDxResourceShareHandle)
|
||||
{
|
||||
// Some OpenXR runtimes return textures that cannot be directly shared.
|
||||
// So we need to make a redundant texture to use as an intermediary...
|
||||
mSharedTextureDesc.Width = mDesc.Width;
|
||||
mSharedTextureDesc.Height = mDesc.Height;
|
||||
mSharedTextureDesc.MipLevels = mDesc.MipLevels;
|
||||
mSharedTextureDesc.ArraySize = mDesc.ArraySize;
|
||||
mSharedTextureDesc.Format = static_cast<DXGI_FORMAT>(swapchainCreateInfo.format);
|
||||
mSharedTextureDesc.SampleDesc = mDesc.SampleDesc;
|
||||
mSharedTextureDesc.Usage = D3D11_USAGE_DEFAULT;
|
||||
mSharedTextureDesc.BindFlags = 0;
|
||||
mSharedTextureDesc.CPUAccessFlags = 0;
|
||||
mSharedTextureDesc.MiscFlags = 0;;
|
||||
|
||||
mDevice->CreateTexture2D(&mSharedTextureDesc, nullptr, &mSharedTexture);
|
||||
mXrImage.texture->GetDesc(&mSharedTextureDesc);
|
||||
mDxResourceShareHandle = xr->impl().platform().DXRegisterObject(mSharedTexture, mGlTextureName, GL_TEXTURE_2D, true, nullptr);
|
||||
}
|
||||
|
||||
// Set up shared texture as blit target
|
||||
mFramebuffer.reset(new VRFramebuffer(gc->getState(), swapchainCreateInfo.width, swapchainCreateInfo.height, swapchainCreateInfo.sampleCount));
|
||||
|
||||
if (swapchainCreateInfo.usageFlags & XR_SWAPCHAIN_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT)
|
||||
{
|
||||
mFramebuffer->setDepthBuffer(gc, mGlTextureName, false);
|
||||
mBufferBits = GL_DEPTH_BUFFER_BIT;
|
||||
}
|
||||
else
|
||||
{
|
||||
mFramebuffer->setColorBuffer(gc, mGlTextureName, false);
|
||||
mBufferBits = GL_COLOR_BUFFER_BIT;
|
||||
}
|
||||
}
|
||||
|
||||
~OpenXRSwapchainImageTemplate()
|
||||
{
|
||||
auto* xr = Environment::get().getManager();
|
||||
if (mDxResourceShareHandle)
|
||||
xr->impl().platform().DXUnregisterObject(mDxResourceShareHandle);
|
||||
glDeleteTextures(1, &mGlTextureName);
|
||||
}
|
||||
|
||||
void blit(osg::GraphicsContext* gc, VRFramebuffer& readBuffer, int offset_x, int offset_y) override
|
||||
{
|
||||
// Blit readBuffer into directx texture, while flipping the Y axis.
|
||||
auto* xr = Environment::get().getManager();
|
||||
xr->impl().platform().DXLockObject(mDxResourceShareHandle);
|
||||
mFramebuffer->bindFramebuffer(gc, GL_FRAMEBUFFER_EXT);
|
||||
readBuffer.blit(gc, offset_x, offset_y, offset_x + mFramebuffer->width(), offset_y + mFramebuffer->height(), 0, mFramebuffer->height(), mFramebuffer->width(), 0, mBufferBits, GL_NEAREST);
|
||||
xr->impl().platform().DXUnlockObject(mDxResourceShareHandle);
|
||||
|
||||
|
||||
// If the d3d11 texture couldn't be shared directly, blit it again.
|
||||
if (mSharedTexture)
|
||||
{
|
||||
mDeviceContext->CopyResource(mXrImage.texture, mSharedTexture);
|
||||
}
|
||||
}
|
||||
|
||||
ID3D11Device* mDevice = nullptr;
|
||||
ID3D11DeviceContext* mDeviceContext = nullptr;
|
||||
D3D11_TEXTURE2D_DESC mDesc;
|
||||
D3D11_TEXTURE2D_DESC mSharedTextureDesc;
|
||||
ID3D11Texture2D* mSharedTexture = nullptr;
|
||||
uint32_t mGlTextureName = 0;
|
||||
void* mDxResourceShareHandle = nullptr;
|
||||
|
||||
XrSwapchainImageD3D11KHR mXrImage;
|
||||
uint32_t mBufferBits;
|
||||
std::unique_ptr<VRFramebuffer> mFramebuffer;
|
||||
};
|
||||
#endif
|
||||
|
||||
|
||||
template< typename Image > static inline
|
||||
std::vector<std::unique_ptr<OpenXRSwapchainImage> >
|
||||
enumerateSwapchainImagesImpl(osg::GraphicsContext* gc, XrSwapchain swapchain, XrSwapchainCreateInfo swapchainCreateInfo)
|
||||
{
|
||||
using SwapchainImage = OpenXRSwapchainImageTemplate<Image>;
|
||||
|
||||
uint32_t imageCount = 0;
|
||||
std::vector< Image > images;
|
||||
CHECK_XRCMD(xrEnumerateSwapchainImages(swapchain, 0, &imageCount, nullptr));
|
||||
images.resize(imageCount, { SwapchainImage::XrType });
|
||||
CHECK_XRCMD(xrEnumerateSwapchainImages(swapchain, imageCount, &imageCount, reinterpret_cast<XrSwapchainImageBaseHeader*>(images.data())));
|
||||
|
||||
std::vector<std::unique_ptr<OpenXRSwapchainImage> > swapchainImages;
|
||||
for(auto& image: images)
|
||||
{
|
||||
swapchainImages.emplace_back(new OpenXRSwapchainImageTemplate<Image>(gc, swapchainCreateInfo, image));
|
||||
}
|
||||
|
||||
return swapchainImages;
|
||||
}
|
||||
|
||||
std::vector<std::unique_ptr<OpenXRSwapchainImage> >
|
||||
OpenXRSwapchainImage::enumerateSwapchainImages(osg::GraphicsContext* gc, XrSwapchain swapchain, XrSwapchainCreateInfo swapchainCreateInfo)
|
||||
{
|
||||
auto* xr = Environment::get().getManager();
|
||||
|
||||
if (xr->xrExtensionIsEnabled(XR_KHR_OPENGL_ENABLE_EXTENSION_NAME))
|
||||
{
|
||||
return enumerateSwapchainImagesImpl<XrSwapchainImageOpenGLKHR>(gc, swapchain, swapchainCreateInfo);
|
||||
}
|
||||
#ifdef XR_USE_GRAPHICS_API_D3D11
|
||||
else if (xr->xrExtensionIsEnabled(XR_KHR_D3D11_ENABLE_EXTENSION_NAME))
|
||||
{
|
||||
return enumerateSwapchainImagesImpl<XrSwapchainImageD3D11KHR>(gc, swapchain, swapchainCreateInfo);
|
||||
}
|
||||
#endif
|
||||
else
|
||||
{
|
||||
throw std::logic_error("Implementation missing for selected graphics API");
|
||||
}
|
||||
|
||||
return std::vector<std::unique_ptr<OpenXRSwapchainImage>>();
|
||||
}
|
||||
|
||||
OpenXRSwapchainImage::OpenXRSwapchainImage()
|
||||
{
|
||||
}
|
||||
}
|
27
apps/openmw/mwvr/openxrswapchainimage.hpp
Normal file
27
apps/openmw/mwvr/openxrswapchainimage.hpp
Normal file
|
@ -0,0 +1,27 @@
|
|||
#ifndef OPENXR_SWAPCHAINIMAGE_HPP
|
||||
#define OPENXR_SWAPCHAINIMAGE_HPP
|
||||
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
#include <openxr/openxr.h>
|
||||
#include <osg/GraphicsContext>
|
||||
|
||||
#include "vrframebuffer.hpp"
|
||||
|
||||
namespace MWVR
|
||||
{
|
||||
class OpenXRSwapchainImage
|
||||
{
|
||||
public:
|
||||
static std::vector< std::unique_ptr<OpenXRSwapchainImage> >
|
||||
enumerateSwapchainImages(osg::GraphicsContext* gc, XrSwapchain swapchain, XrSwapchainCreateInfo swapchainCreateInfo);
|
||||
|
||||
OpenXRSwapchainImage();
|
||||
virtual ~OpenXRSwapchainImage() {};
|
||||
|
||||
virtual void blit(osg::GraphicsContext* gc, VRFramebuffer& readBuffer, int offset_x, int offset_y) = 0;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
263
apps/openmw/mwvr/openxrswapchainimpl.cpp
Normal file
263
apps/openmw/mwvr/openxrswapchainimpl.cpp
Normal file
|
@ -0,0 +1,263 @@
|
|||
#include "openxrswapchainimpl.hpp"
|
||||
#include "openxrdebug.hpp"
|
||||
#include "vrenvironment.hpp"
|
||||
#include "vrframebuffer.hpp"
|
||||
|
||||
#include <components/debug/debuglog.hpp>
|
||||
|
||||
namespace MWVR {
|
||||
OpenXRSwapchainImpl::OpenXRSwapchainImpl(osg::ref_ptr<osg::State> state, SwapchainConfig config)
|
||||
: mConfig(config)
|
||||
{
|
||||
if (mConfig.selectedWidth <= 0)
|
||||
throw std::invalid_argument("Width must be a positive integer");
|
||||
if (mConfig.selectedHeight <= 0)
|
||||
throw std::invalid_argument("Height must be a positive integer");
|
||||
if (mConfig.selectedSamples <= 0)
|
||||
throw std::invalid_argument("Samples must be a positive integer");
|
||||
|
||||
mSwapchain.reset(new SwapchainPrivate(state, mConfig, SwapchainPrivate::Use::COLOR));
|
||||
mConfig.selectedSamples = mSwapchain->samples();
|
||||
|
||||
auto* xr = Environment::get().getManager();
|
||||
|
||||
if (xr->xrExtensionIsEnabled(XR_KHR_COMPOSITION_LAYER_DEPTH_EXTENSION_NAME))
|
||||
{
|
||||
try
|
||||
{
|
||||
mSwapchainDepth.reset(new SwapchainPrivate(state, mConfig, SwapchainPrivate::Use::DEPTH));
|
||||
}
|
||||
catch (std::exception& e)
|
||||
{
|
||||
Log(Debug::Warning) << "XR_KHR_composition_layer_depth was enabled but creating depth swapchain failed: " << e.what();
|
||||
mSwapchainDepth = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
OpenXRSwapchainImpl::~OpenXRSwapchainImpl()
|
||||
{
|
||||
}
|
||||
|
||||
bool OpenXRSwapchainImpl::isAcquired() const
|
||||
{
|
||||
return mFormallyAcquired;
|
||||
}
|
||||
|
||||
void OpenXRSwapchainImpl::beginFrame(osg::GraphicsContext* gc)
|
||||
{
|
||||
acquire(gc);
|
||||
}
|
||||
|
||||
int swapCount = 0;
|
||||
|
||||
void OpenXRSwapchainImpl::endFrame(osg::GraphicsContext* gc, VRFramebuffer& readBuffer)
|
||||
{
|
||||
checkAcquired();
|
||||
release(gc, readBuffer);
|
||||
}
|
||||
|
||||
void OpenXRSwapchainImpl::acquire(osg::GraphicsContext* gc)
|
||||
{
|
||||
if (isAcquired())
|
||||
throw std::logic_error("Trying to acquire already acquired swapchain");
|
||||
|
||||
// The openxr runtime may fail to acquire/release.
|
||||
// Do not re-acquire a swapchain before having successfully released it.
|
||||
// Lest the swapchain fall out of sync.
|
||||
if (!mShouldRelease)
|
||||
{
|
||||
mSwapchain->acquire(gc);
|
||||
mShouldRelease = mSwapchain->isAcquired();
|
||||
if (mSwapchainDepth && mSwapchain->isAcquired())
|
||||
{
|
||||
mSwapchainDepth->acquire(gc);
|
||||
mShouldRelease = mSwapchainDepth->isAcquired();
|
||||
}
|
||||
}
|
||||
|
||||
mFormallyAcquired = true;
|
||||
}
|
||||
|
||||
void OpenXRSwapchainImpl::release(osg::GraphicsContext* gc, VRFramebuffer& readBuffer)
|
||||
{
|
||||
// The openxr runtime may fail to acquire/release.
|
||||
// Do not release a swapchain before having successfully acquire it.
|
||||
if (mShouldRelease)
|
||||
{
|
||||
mSwapchain->blitAndRelease(gc, readBuffer);
|
||||
mShouldRelease = mSwapchain->isAcquired();
|
||||
if (mSwapchainDepth)
|
||||
{
|
||||
mSwapchainDepth->blitAndRelease(gc, readBuffer);
|
||||
mShouldRelease = mSwapchainDepth->isAcquired();
|
||||
}
|
||||
}
|
||||
|
||||
mFormallyAcquired = false;
|
||||
}
|
||||
|
||||
void OpenXRSwapchainImpl::checkAcquired() const
|
||||
{
|
||||
if (!isAcquired())
|
||||
throw std::logic_error("Swapchain must be acquired before use. Call between OpenXRSwapchain::beginFrame() and OpenXRSwapchain::endFrame()");
|
||||
}
|
||||
|
||||
OpenXRSwapchainImpl::SwapchainPrivate::SwapchainPrivate(osg::ref_ptr<osg::State> state, SwapchainConfig config, Use use)
|
||||
: mConfig(config)
|
||||
, mImages()
|
||||
, mWidth(config.selectedWidth)
|
||||
, mHeight(config.selectedHeight)
|
||||
, mSamples(1)
|
||||
, mUsage(use)
|
||||
{
|
||||
auto* xr = Environment::get().getManager();
|
||||
|
||||
XrSwapchainCreateInfo swapchainCreateInfo{ XR_TYPE_SWAPCHAIN_CREATE_INFO };
|
||||
swapchainCreateInfo.arraySize = 1;
|
||||
swapchainCreateInfo.width = mWidth;
|
||||
swapchainCreateInfo.height = mHeight;
|
||||
swapchainCreateInfo.mipCount = 1;
|
||||
swapchainCreateInfo.faceCount = 1;
|
||||
|
||||
while (mSamples > 0 && mSwapchain == XR_NULL_HANDLE && mFormat == 0)
|
||||
{
|
||||
// Select a swapchain format.
|
||||
if (use == Use::COLOR)
|
||||
mFormat = xr->selectColorFormat();
|
||||
else
|
||||
mFormat = xr->selectDepthFormat();
|
||||
std::string typeString = use == Use::COLOR ? "color" : "depth";
|
||||
if (mFormat == 0) {
|
||||
throw std::runtime_error(std::string("Swapchain ") + typeString + " format not supported");
|
||||
}
|
||||
Log(Debug::Verbose) << "Selected " << typeString << " format: " << std::dec << mFormat << " (" << std::hex << mFormat << ")" << std::dec;
|
||||
|
||||
// Now create the swapchain
|
||||
Log(Debug::Verbose) << "Creating swapchain with dimensions Width=" << mWidth << " Heigh=" << mHeight << " SampleCount=" << mSamples;
|
||||
swapchainCreateInfo.format = mFormat;
|
||||
swapchainCreateInfo.sampleCount = mSamples;
|
||||
if(mUsage == Use::COLOR)
|
||||
swapchainCreateInfo.usageFlags = XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT;
|
||||
else
|
||||
swapchainCreateInfo.usageFlags = XR_SWAPCHAIN_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
|
||||
auto res = xrCreateSwapchain(xr->impl().xrSession(), &swapchainCreateInfo, &mSwapchain);
|
||||
|
||||
// Check errors and try again if possible
|
||||
if (res == XR_ERROR_SWAPCHAIN_FORMAT_UNSUPPORTED)
|
||||
{
|
||||
// We only try swapchain formats enumerated by the runtime itself.
|
||||
// This does not guarantee that that swapchain format is going to be supported for this specific usage.
|
||||
Log(Debug::Verbose) << "Failed to create swapchain with Format=" << mFormat<< ": " << XrResultString(res);
|
||||
xr->eraseFormat(mFormat);
|
||||
mFormat = 0;
|
||||
continue;
|
||||
}
|
||||
else if (!XR_SUCCEEDED(res))
|
||||
{
|
||||
Log(Debug::Verbose) << "Failed to create swapchain with SampleCount=" << mSamples << ": " << XrResultString(res);
|
||||
mSamples /= 2;
|
||||
if (mSamples == 0)
|
||||
{
|
||||
CHECK_XRRESULT(res, "xrCreateSwapchain");
|
||||
throw std::runtime_error(XrResultString(res));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
CHECK_XRRESULT(res, "xrCreateSwapchain");
|
||||
VrDebug::setName(mSwapchain, "OpenMW XR Color Swapchain " + config.name);
|
||||
}
|
||||
|
||||
// TODO: here
|
||||
mImages = OpenXRSwapchainImage::enumerateSwapchainImages(state->getGraphicsContext(), mSwapchain, swapchainCreateInfo);
|
||||
mSubImage.swapchain = mSwapchain;
|
||||
mSubImage.imageRect.offset = { 0, 0 };
|
||||
mSubImage.imageRect.extent = { mWidth, mHeight };
|
||||
}
|
||||
|
||||
OpenXRSwapchainImpl::SwapchainPrivate::~SwapchainPrivate()
|
||||
{
|
||||
if (mSwapchain)
|
||||
CHECK_XRCMD(xrDestroySwapchain(mSwapchain));
|
||||
}
|
||||
|
||||
uint32_t OpenXRSwapchainImpl::SwapchainPrivate::count() const
|
||||
{
|
||||
return mImages.size();
|
||||
}
|
||||
|
||||
bool OpenXRSwapchainImpl::SwapchainPrivate::isAcquired() const
|
||||
{
|
||||
return mIsReady;
|
||||
}
|
||||
|
||||
void OpenXRSwapchainImpl::SwapchainPrivate::acquire(osg::GraphicsContext* gc)
|
||||
{
|
||||
auto xr = Environment::get().getManager();
|
||||
XrSwapchainImageAcquireInfo acquireInfo{ XR_TYPE_SWAPCHAIN_IMAGE_ACQUIRE_INFO };
|
||||
XrSwapchainImageWaitInfo waitInfo{ XR_TYPE_SWAPCHAIN_IMAGE_WAIT_INFO };
|
||||
waitInfo.timeout = XR_INFINITE_DURATION;
|
||||
if (!mIsIndexAcquired)
|
||||
{
|
||||
mIsIndexAcquired = XR_SUCCEEDED(CHECK_XRCMD(xrAcquireSwapchainImage(mSwapchain, &acquireInfo, &mAcquiredIndex)));
|
||||
if (mIsIndexAcquired)
|
||||
xr->xrResourceAcquired();
|
||||
}
|
||||
if (mIsIndexAcquired && !mIsReady)
|
||||
{
|
||||
mIsReady = XR_SUCCEEDED(CHECK_XRCMD(xrWaitSwapchainImage(mSwapchain, &waitInfo)));
|
||||
}
|
||||
}
|
||||
void OpenXRSwapchainImpl::SwapchainPrivate::blitAndRelease(osg::GraphicsContext* gc, VRFramebuffer& readBuffer)
|
||||
{
|
||||
auto xr = Environment::get().getManager();
|
||||
|
||||
XrSwapchainImageReleaseInfo releaseInfo{ XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO };
|
||||
if (mIsReady)
|
||||
{
|
||||
mImages[mAcquiredIndex]->blit(gc, readBuffer, mConfig.offsetWidth, mConfig.offsetHeight);
|
||||
|
||||
mIsReady = !XR_SUCCEEDED(CHECK_XRCMD(xrReleaseSwapchainImage(mSwapchain, &releaseInfo)));
|
||||
if (!mIsReady)
|
||||
{
|
||||
mIsIndexAcquired = false;
|
||||
xr->xrResourceReleased();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OpenXRSwapchainImpl::SwapchainPrivate::checkAcquired() const
|
||||
{
|
||||
if (!isAcquired())
|
||||
throw std::logic_error("Swapchain must be acquired before use. Call between OpenXRSwapchain::beginFrame() and OpenXRSwapchain::endFrame()");
|
||||
}
|
||||
|
||||
XrSwapchain OpenXRSwapchainImpl::xrSwapchain(void) const
|
||||
{
|
||||
if(mSwapchain)
|
||||
return mSwapchain->xrSwapchain();
|
||||
return XR_NULL_HANDLE;
|
||||
}
|
||||
|
||||
XrSwapchain OpenXRSwapchainImpl::xrSwapchainDepth(void) const
|
||||
{
|
||||
if (mSwapchainDepth)
|
||||
return mSwapchainDepth->xrSwapchain();
|
||||
return XR_NULL_HANDLE;
|
||||
}
|
||||
|
||||
XrSwapchainSubImage OpenXRSwapchainImpl::xrSubImage(void) const
|
||||
{
|
||||
if (mSwapchain)
|
||||
return mSwapchain->xrSubImage();
|
||||
return XrSwapchainSubImage{ XR_NULL_HANDLE };
|
||||
}
|
||||
|
||||
XrSwapchainSubImage OpenXRSwapchainImpl::xrSubImageDepth(void) const
|
||||
{
|
||||
if (mSwapchainDepth)
|
||||
return mSwapchainDepth->xrSubImage();
|
||||
return XrSwapchainSubImage{ XR_NULL_HANDLE };
|
||||
}
|
||||
}
|
90
apps/openmw/mwvr/openxrswapchainimpl.hpp
Normal file
90
apps/openmw/mwvr/openxrswapchainimpl.hpp
Normal file
|
@ -0,0 +1,90 @@
|
|||
#ifndef OPENXR_SWAPCHAINIMPL_HPP
|
||||
#define OPENXR_SWAPCHAINIMPL_HPP
|
||||
|
||||
#include "openxrswapchainimage.hpp"
|
||||
#include "openxrmanagerimpl.hpp"
|
||||
|
||||
struct XrSwapchainSubImage;
|
||||
|
||||
namespace MWVR
|
||||
{
|
||||
class VRFramebuffer;
|
||||
|
||||
/// \brief Implementation of OpenXRSwapchain
|
||||
class OpenXRSwapchainImpl
|
||||
{
|
||||
private:
|
||||
struct SwapchainPrivate
|
||||
{
|
||||
enum class Use {
|
||||
COLOR = 0,
|
||||
DEPTH = 1
|
||||
};
|
||||
|
||||
SwapchainPrivate(osg::ref_ptr<osg::State> state, SwapchainConfig config, Use use);
|
||||
~SwapchainPrivate();
|
||||
|
||||
uint32_t count() const;
|
||||
bool isAcquired() const;
|
||||
uint32_t acuiredIndex() const { return mAcquiredIndex; };
|
||||
XrSwapchain xrSwapchain(void) const { return mSwapchain; };
|
||||
XrSwapchainSubImage xrSubImage(void) const { return mSubImage; };
|
||||
int width() const { return mWidth; };
|
||||
int height() const { return mHeight; };
|
||||
int samples() const { return mSamples; };
|
||||
|
||||
void acquire(osg::GraphicsContext* gc);
|
||||
void blitAndRelease(osg::GraphicsContext* gc, VRFramebuffer& readBuffer);
|
||||
void checkAcquired() const;
|
||||
|
||||
protected:
|
||||
|
||||
private:
|
||||
SwapchainConfig mConfig;
|
||||
XrSwapchain mSwapchain = XR_NULL_HANDLE;
|
||||
XrSwapchainSubImage mSubImage{};
|
||||
std::vector< std::unique_ptr<OpenXRSwapchainImage> > mImages;
|
||||
int32_t mWidth = -1;
|
||||
int32_t mHeight = -1;
|
||||
int32_t mSamples = -1;
|
||||
int64_t mFormat = 0;
|
||||
uint32_t mAcquiredIndex{ 0 };
|
||||
Use mUsage;
|
||||
bool mIsIndexAcquired{ false };
|
||||
bool mIsReady{ false };
|
||||
};
|
||||
|
||||
public:
|
||||
OpenXRSwapchainImpl(osg::ref_ptr<osg::State> state, SwapchainConfig config);
|
||||
~OpenXRSwapchainImpl();
|
||||
|
||||
void beginFrame(osg::GraphicsContext* gc);
|
||||
void endFrame(osg::GraphicsContext* gc, VRFramebuffer& readBuffer);
|
||||
|
||||
bool isAcquired() const;
|
||||
XrSwapchain xrSwapchain(void) const;
|
||||
XrSwapchain xrSwapchainDepth(void) const;
|
||||
XrSwapchainSubImage xrSubImage(void) const;
|
||||
XrSwapchainSubImage xrSubImageDepth(void) const;
|
||||
int width() const { return mConfig.selectedWidth; };
|
||||
int height() const { return mConfig.selectedHeight; };
|
||||
int samples() const { return mConfig.selectedSamples; };
|
||||
|
||||
protected:
|
||||
OpenXRSwapchainImpl(const OpenXRSwapchainImpl&) = delete;
|
||||
void operator=(const OpenXRSwapchainImpl&) = delete;
|
||||
|
||||
void acquire(osg::GraphicsContext* gc);
|
||||
void release(osg::GraphicsContext* gc, VRFramebuffer& readBuffer);
|
||||
void checkAcquired() const;
|
||||
|
||||
private:
|
||||
SwapchainConfig mConfig;
|
||||
std::unique_ptr<SwapchainPrivate> mSwapchain{ nullptr };
|
||||
std::unique_ptr<SwapchainPrivate> mSwapchainDepth{ nullptr };
|
||||
bool mFormallyAcquired{ false };
|
||||
bool mShouldRelease{ false };
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
141
apps/openmw/mwvr/openxrtracker.cpp
Normal file
141
apps/openmw/mwvr/openxrtracker.cpp
Normal file
|
@ -0,0 +1,141 @@
|
|||
#include "openxrinput.hpp"
|
||||
#include "openxrmanagerimpl.hpp"
|
||||
#include "openxrplatform.hpp"
|
||||
#include "openxrtracker.hpp"
|
||||
#include "openxrtypeconversions.hpp"
|
||||
#include "vrenvironment.hpp"
|
||||
#include "vrinputmanager.hpp"
|
||||
#include "vrsession.hpp"
|
||||
|
||||
#include <components/misc/constants.hpp>
|
||||
|
||||
namespace MWVR
|
||||
{
|
||||
OpenXRTracker::OpenXRTracker(const std::string& name, XrSpace referenceSpace)
|
||||
: VRTrackingSource(name)
|
||||
, mReferenceSpace(referenceSpace)
|
||||
, mTrackingSpaces()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
OpenXRTracker::~OpenXRTracker()
|
||||
{
|
||||
}
|
||||
|
||||
void OpenXRTracker::addTrackingSpace(VRPath path, XrSpace space)
|
||||
{
|
||||
mTrackingSpaces[path] = space;
|
||||
notifyAvailablePosesChanged();
|
||||
}
|
||||
|
||||
void OpenXRTracker::deleteTrackingSpace(VRPath path)
|
||||
{
|
||||
mTrackingSpaces.erase(path);
|
||||
notifyAvailablePosesChanged();
|
||||
}
|
||||
|
||||
void OpenXRTracker::setReferenceSpace(XrSpace referenceSpace)
|
||||
{
|
||||
mReferenceSpace = referenceSpace;
|
||||
}
|
||||
|
||||
std::vector<VRPath> OpenXRTracker::listSupportedTrackingPosePaths() const
|
||||
{
|
||||
std::vector<VRPath> path;
|
||||
for (auto& e : mTrackingSpaces)
|
||||
path.push_back(e.first);
|
||||
return path;
|
||||
}
|
||||
|
||||
void OpenXRTracker::updateTracking(DisplayTime predictedDisplayTime)
|
||||
{
|
||||
Environment::get().getInputManager()->xrInput().getActionSet(ActionSet::Tracking).updateControls();
|
||||
auto* xr = Environment::get().getManager();
|
||||
auto* session = Environment::get().getSession();
|
||||
|
||||
auto& frame = session->getFrame(VRSession::FramePhase::Update);
|
||||
frame->mViews[(int)ReferenceSpace::STAGE] = locateViews(predictedDisplayTime, xr->impl().getReferenceSpace(ReferenceSpace::STAGE));
|
||||
frame->mViews[(int)ReferenceSpace::VIEW] = locateViews(predictedDisplayTime, xr->impl().getReferenceSpace(ReferenceSpace::VIEW));
|
||||
}
|
||||
|
||||
XrSpace OpenXRTracker::getSpace(VRPath path)
|
||||
{
|
||||
auto it = mTrackingSpaces.find(path);
|
||||
if (it != mTrackingSpaces.end())
|
||||
return it->second;
|
||||
return 0;
|
||||
}
|
||||
|
||||
VRTrackingPose OpenXRTracker::getTrackingPoseImpl(DisplayTime predictedDisplayTime, VRPath path, VRPath reference)
|
||||
{
|
||||
VRTrackingPose pose;
|
||||
pose.status = TrackingStatus::Good;
|
||||
XrSpace space = getSpace(path);
|
||||
XrSpace ref = reference == 0 ? mReferenceSpace : getSpace(reference);
|
||||
if (space == 0 || ref == 0)
|
||||
pose.status = TrackingStatus::NotTracked;
|
||||
if (!!pose.status)
|
||||
locate(pose, space, ref, predictedDisplayTime);
|
||||
return pose;
|
||||
}
|
||||
|
||||
void OpenXRTracker::locate(VRTrackingPose& pose, XrSpace space, XrSpace reference, DisplayTime predictedDisplayTime)
|
||||
{
|
||||
XrSpaceLocation location{ XR_TYPE_SPACE_LOCATION };
|
||||
auto res = xrLocateSpace(space, mReferenceSpace, predictedDisplayTime, &location);
|
||||
|
||||
if (XR_FAILED(res))
|
||||
{
|
||||
// Call failed, exit.
|
||||
CHECK_XRRESULT(res, "xrLocateSpace");
|
||||
pose.status = TrackingStatus::RuntimeFailure;
|
||||
return;
|
||||
}
|
||||
|
||||
// Check that everything is being tracked
|
||||
if (!(location.locationFlags & (XR_SPACE_LOCATION_ORIENTATION_TRACKED_BIT | XR_SPACE_LOCATION_POSITION_TRACKED_BIT)))
|
||||
{
|
||||
// It's not, data is stale
|
||||
pose.status = TrackingStatus::Stale;
|
||||
}
|
||||
|
||||
// Check that data is valid
|
||||
if (!(location.locationFlags & (XR_SPACE_LOCATION_ORIENTATION_VALID_BIT | XR_SPACE_LOCATION_POSITION_VALID_BIT)))
|
||||
{
|
||||
// It's not, we've lost tracking
|
||||
pose.status = TrackingStatus::Lost;
|
||||
}
|
||||
|
||||
pose.pose = MWVR::Pose{
|
||||
fromXR(location.pose.position),
|
||||
fromXR(location.pose.orientation)
|
||||
};
|
||||
}
|
||||
|
||||
std::array<View, 2> OpenXRTracker::locateViews(DisplayTime predictedDisplayTime, XrSpace reference)
|
||||
{
|
||||
std::array<XrView, 2> xrViews{ {{XR_TYPE_VIEW}, {XR_TYPE_VIEW}} };
|
||||
XrViewState viewState{ XR_TYPE_VIEW_STATE };
|
||||
uint32_t viewCount = 2;
|
||||
|
||||
XrViewLocateInfo viewLocateInfo{ XR_TYPE_VIEW_LOCATE_INFO };
|
||||
viewLocateInfo.viewConfigurationType = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO;
|
||||
viewLocateInfo.displayTime = predictedDisplayTime;
|
||||
viewLocateInfo.space = reference;
|
||||
|
||||
auto* xr = Environment::get().getManager();
|
||||
CHECK_XRCMD(xrLocateViews(xr->impl().xrSession(), &viewLocateInfo, &viewState, viewCount, &viewCount, xrViews.data()));
|
||||
|
||||
std::array<View, 2> vrViews{};
|
||||
vrViews[(int)Side::LEFT_SIDE].pose = fromXR(xrViews[(int)Side::LEFT_SIDE].pose);
|
||||
vrViews[(int)Side::RIGHT_SIDE].pose = fromXR(xrViews[(int)Side::RIGHT_SIDE].pose);
|
||||
vrViews[(int)Side::LEFT_SIDE].fov = fromXR(xrViews[(int)Side::LEFT_SIDE].fov);
|
||||
vrViews[(int)Side::RIGHT_SIDE].fov = fromXR(xrViews[(int)Side::RIGHT_SIDE].fov);
|
||||
return vrViews;
|
||||
}
|
||||
|
||||
OpenXRTrackingToWorldBinding::OpenXRTrackingToWorldBinding()
|
||||
{
|
||||
}
|
||||
}
|
86
apps/openmw/mwvr/openxrtracker.hpp
Normal file
86
apps/openmw/mwvr/openxrtracker.hpp
Normal file
|
@ -0,0 +1,86 @@
|
|||
#ifndef OPENXR_TRACKER_HPP
|
||||
#define OPENXR_TRACKER_HPP
|
||||
|
||||
#include <openxr/openxr.h>
|
||||
#include "vrtracking.hpp"
|
||||
|
||||
#include <map>
|
||||
|
||||
namespace MWVR
|
||||
{
|
||||
//! Serves as a C++ wrapper of openxr spaces, but also bridges stage coordinates and game coordinates.
|
||||
//! Supports the compulsory sets of paths.
|
||||
class OpenXRTracker : public VRTrackingSource
|
||||
{
|
||||
public:
|
||||
OpenXRTracker(const std::string& name, XrSpace referenceSpace);
|
||||
~OpenXRTracker();
|
||||
|
||||
void addTrackingSpace(VRPath path, XrSpace space);
|
||||
void deleteTrackingSpace(VRPath path);
|
||||
|
||||
//! The base space used to reference everything else.
|
||||
void setReferenceSpace(XrSpace referenceSpace);
|
||||
|
||||
std::vector<VRPath> listSupportedTrackingPosePaths() const override;
|
||||
void updateTracking(DisplayTime predictedDisplayTime) override;
|
||||
|
||||
protected:
|
||||
VRTrackingPose getTrackingPoseImpl(DisplayTime predictedDisplayTime, VRPath path, VRPath reference = 0) override;
|
||||
|
||||
private:
|
||||
std::array<View, 2> locateViews(DisplayTime predictedDisplayTime, XrSpace reference);
|
||||
void locate(VRTrackingPose& pose, XrSpace space, XrSpace reference, DisplayTime predictedDisplayTime);
|
||||
XrSpace getSpace(VRPath);
|
||||
|
||||
XrSpace mReferenceSpace;
|
||||
std::map<VRPath, XrSpace> mTrackingSpaces;
|
||||
};
|
||||
|
||||
//! Ties a tracked pose to the game world.
|
||||
//! A movement tracking pose is selected by passing its path to the constructor.
|
||||
//! All poses are transformed in the horizontal plane by moving the x,y origin to the position of the movement tracking pose, and then reoriented using the current orientation.
|
||||
//! The movement tracking pose is effectively always at the x,y origin
|
||||
//! The movement of the movement tracking pose is accumulated and can be read using the movement() call.
|
||||
//! If this movement is ever consumed (such as by moving the character to follow the player) the consumed movement must be reported using consumeMovement().
|
||||
class OpenXRTrackingToWorldBinding
|
||||
{
|
||||
public:
|
||||
OpenXRTrackingToWorldBinding();
|
||||
|
||||
//! Re-orient the stage.
|
||||
void setOrientation(float yaw, bool adjust);
|
||||
osg::Quat getOrientation() const { return mOrientation; }
|
||||
|
||||
void setEyeLevel(float eyeLevel) { mEyeLevel = eyeLevel; }
|
||||
float getEyeLevel() const { return mEyeLevel; }
|
||||
|
||||
void setSeatedPlay(bool seatedPlay) { mSeatedPlay = seatedPlay; }
|
||||
bool getSeatedPlay() const { return mSeatedPlay; }
|
||||
|
||||
//! The player's movement within the VR stage. This accumulates until the movement has been consumed by calling consumeMovement()
|
||||
osg::Vec3 movement() const;
|
||||
|
||||
//! Consume movement
|
||||
void consumeMovement(const osg::Vec3& movement);
|
||||
|
||||
//! Recenter tracking by consuming all movement.
|
||||
void recenter(bool resetZ);
|
||||
|
||||
void update(Pose movementTrackingPose);
|
||||
|
||||
//! Transforms a stage-referenced pose to be world-aligned.
|
||||
//! \note Unlike VRTrackingSource::getTrackingPose() this does not take a reference path, as re-alignment is only needed when fetching a stage-referenced pose.
|
||||
void alignPose(Pose& pose);
|
||||
|
||||
private:
|
||||
bool mSeatedPlay = false;
|
||||
bool mHasTrackingData = false;
|
||||
float mEyeLevel = 0;
|
||||
Pose mLastPose = Pose();
|
||||
osg::Vec3 mMovement = osg::Vec3(0,0,0);
|
||||
osg::Quat mOrientation = osg::Quat(0,0,0,1);
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
76
apps/openmw/mwvr/openxrtypeconversions.cpp
Normal file
76
apps/openmw/mwvr/openxrtypeconversions.cpp
Normal file
|
@ -0,0 +1,76 @@
|
|||
#include "openxrtypeconversions.hpp"
|
||||
#include "openxrswapchain.hpp"
|
||||
#include "openxrswapchainimpl.hpp"
|
||||
#include <iostream>
|
||||
|
||||
namespace MWVR
|
||||
{
|
||||
osg::Vec3 fromXR(XrVector3f v)
|
||||
{
|
||||
return osg::Vec3{ v.x, -v.z, v.y };
|
||||
}
|
||||
|
||||
osg::Quat fromXR(XrQuaternionf quat)
|
||||
{
|
||||
return osg::Quat{ quat.x, -quat.z, quat.y, quat.w };
|
||||
}
|
||||
|
||||
XrVector3f toXR(osg::Vec3 v)
|
||||
{
|
||||
return XrVector3f{ v.x(), v.z(), -v.y() };
|
||||
}
|
||||
|
||||
XrQuaternionf toXR(osg::Quat quat)
|
||||
{
|
||||
return XrQuaternionf{ static_cast<float>(quat.x()), static_cast<float>(quat.z()), static_cast<float>(-quat.y()), static_cast<float>(quat.w()) };
|
||||
}
|
||||
|
||||
MWVR::Pose fromXR(XrPosef pose)
|
||||
{
|
||||
return MWVR::Pose{ fromXR(pose.position), fromXR(pose.orientation) };
|
||||
}
|
||||
|
||||
XrPosef toXR(MWVR::Pose pose)
|
||||
{
|
||||
return XrPosef{ toXR(pose.orientation), toXR(pose.position) };
|
||||
}
|
||||
|
||||
MWVR::FieldOfView fromXR(XrFovf fov)
|
||||
{
|
||||
return MWVR::FieldOfView{ fov.angleLeft, fov.angleRight, fov.angleUp, fov.angleDown };
|
||||
}
|
||||
|
||||
XrFovf toXR(MWVR::FieldOfView fov)
|
||||
{
|
||||
return XrFovf{ fov.angleLeft, fov.angleRight, fov.angleUp, fov.angleDown };
|
||||
}
|
||||
|
||||
XrCompositionLayerProjectionView toXR(MWVR::CompositionLayerProjectionView layer)
|
||||
{
|
||||
XrCompositionLayerProjectionView xrLayer;
|
||||
xrLayer.type = XR_TYPE_COMPOSITION_LAYER_PROJECTION_VIEW;
|
||||
xrLayer.subImage = toXR(layer.subImage, false);
|
||||
xrLayer.pose = toXR(layer.pose);
|
||||
xrLayer.fov = toXR(layer.fov);
|
||||
xrLayer.next = nullptr;
|
||||
|
||||
return xrLayer;
|
||||
}
|
||||
|
||||
XrSwapchainSubImage toXR(MWVR::SubImage subImage, bool depthImage)
|
||||
{
|
||||
XrSwapchainSubImage xrSubImage{};
|
||||
if (depthImage)
|
||||
xrSubImage.swapchain = subImage.swapchain->impl().xrSwapchainDepth();
|
||||
else
|
||||
xrSubImage.swapchain = subImage.swapchain->impl().xrSwapchain();
|
||||
xrSubImage.imageRect.extent.width = subImage.width;
|
||||
xrSubImage.imageRect.extent.height = subImage.height;
|
||||
xrSubImage.imageRect.offset.x = subImage.x;
|
||||
xrSubImage.imageRect.offset.y = subImage.y;
|
||||
xrSubImage.imageArrayIndex = 0;
|
||||
return xrSubImage;
|
||||
}
|
||||
}
|
||||
|
||||
|
25
apps/openmw/mwvr/openxrtypeconversions.hpp
Normal file
25
apps/openmw/mwvr/openxrtypeconversions.hpp
Normal file
|
@ -0,0 +1,25 @@
|
|||
#ifndef MWVR_OPENXRTYPECONVERSIONS_H
|
||||
#define MWVR_OPENXRTYPECONVERSIONS_H
|
||||
|
||||
#include <openxr/openxr.h>
|
||||
#include "vrtypes.hpp"
|
||||
#include <osg/Vec3>
|
||||
#include <osg/Quat>
|
||||
|
||||
namespace MWVR
|
||||
{
|
||||
/// Conversion methods between openxr types to osg/mwvr types. Includes managing the differing conventions.
|
||||
Pose fromXR(XrPosef pose);
|
||||
FieldOfView fromXR(XrFovf fov);
|
||||
osg::Vec3 fromXR(XrVector3f);
|
||||
osg::Quat fromXR(XrQuaternionf quat);
|
||||
XrPosef toXR(Pose pose);
|
||||
XrFovf toXR(FieldOfView fov);
|
||||
XrVector3f toXR(osg::Vec3 v);
|
||||
XrQuaternionf toXR(osg::Quat quat);
|
||||
|
||||
XrCompositionLayerProjectionView toXR(CompositionLayerProjectionView layer);
|
||||
XrSwapchainSubImage toXR(SubImage, bool depthImage);
|
||||
}
|
||||
|
||||
#endif
|
347
apps/openmw/mwvr/realisticcombat.cpp
Normal file
347
apps/openmw/mwvr/realisticcombat.cpp
Normal file
|
@ -0,0 +1,347 @@
|
|||
#include "realisticcombat.hpp"
|
||||
|
||||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwbase/soundmanager.hpp"
|
||||
|
||||
#include "../mwmechanics/weapontype.hpp"
|
||||
|
||||
#include <components/debug/debuglog.hpp>
|
||||
|
||||
#include <iomanip>
|
||||
|
||||
namespace MWVR {
|
||||
namespace RealisticCombat {
|
||||
|
||||
static const char* stateToString(SwingState florida)
|
||||
{
|
||||
switch (florida)
|
||||
{
|
||||
case SwingState_Cooldown:
|
||||
return "Cooldown";
|
||||
case SwingState_Impact:
|
||||
return "Impact";
|
||||
case SwingState_Ready:
|
||||
return "Ready";
|
||||
case SwingState_Swing:
|
||||
return "Swing";
|
||||
case SwingState_Launch:
|
||||
return "Launch";
|
||||
}
|
||||
return "Error, invalid enum";
|
||||
}
|
||||
static const char* swingTypeToString(int type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case ESM::Weapon::AT_Chop:
|
||||
return "Chop";
|
||||
case ESM::Weapon::AT_Slash:
|
||||
return "Slash";
|
||||
case ESM::Weapon::AT_Thrust:
|
||||
return "Thrust";
|
||||
case -1:
|
||||
return "Fail";
|
||||
default:
|
||||
return "Invalid";
|
||||
}
|
||||
}
|
||||
|
||||
StateMachine::StateMachine(MWWorld::Ptr ptr, VRPath trackingPath)
|
||||
: mPtr(ptr)
|
||||
, mMinVelocity(Settings::Manager::getFloat("realistic combat minimum swing velocity", "VR"))
|
||||
, mMaxVelocity(Settings::Manager::getFloat("realistic combat maximum swing velocity", "VR"))
|
||||
, mTrackingPath(trackingPath)
|
||||
{
|
||||
Log(Debug::Verbose) << "realistic combat minimum swing velocity: " << mMinVelocity;
|
||||
Log(Debug::Verbose) << "realistic combat maximum swing velocity: " << mMaxVelocity;
|
||||
Environment::get().getTrackingManager()->bind(this, "pcstage");
|
||||
}
|
||||
|
||||
void StateMachine::onTrackingUpdated(VRTrackingSource& source, DisplayTime predictedDisplayTime)
|
||||
{
|
||||
mTrackingInput = source.getTrackingPose(predictedDisplayTime, mTrackingPath);
|
||||
}
|
||||
|
||||
bool StateMachine::canSwing()
|
||||
{
|
||||
if (mSwingType >= 0)
|
||||
if (mVelocity >= mMinVelocity)
|
||||
if (mSwingType != ESM::Weapon::AT_Thrust || mThrustVelocity >= 0.f)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Actions common to all transitions
|
||||
void StateMachine::transition(
|
||||
SwingState newState)
|
||||
{
|
||||
Log(Debug::Verbose) << "Transition:" << stateToString(mState) << "->" << stateToString(newState);
|
||||
mMaxSwingVelocity = 0.f;
|
||||
mTimeSinceEnteredState = 0.f;
|
||||
mMovementSinceEnteredState = 0.f;
|
||||
mState = newState;
|
||||
}
|
||||
|
||||
void StateMachine::reset()
|
||||
{
|
||||
mMaxSwingVelocity = 0.f;
|
||||
mTimeSinceEnteredState = 0.f;
|
||||
mVelocity = 0.f;
|
||||
mPreviousPosition = osg::Vec3(0.f, 0.f, 0.f);
|
||||
mState = SwingState_Ready;
|
||||
}
|
||||
|
||||
static bool isMeleeWeapon(int type)
|
||||
{
|
||||
if (MWMechanics::getWeaponType(type)->mWeaponClass != ESM::WeaponType::Melee)
|
||||
return false;
|
||||
if (type == ESM::Weapon::HandToHand)
|
||||
return true;
|
||||
if (type >= 0)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool isSideSwingValidForWeapon(int type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case ESM::Weapon::HandToHand:
|
||||
case ESM::Weapon::BluntOneHand:
|
||||
case ESM::Weapon::BluntTwoClose:
|
||||
case ESM::Weapon::BluntTwoWide:
|
||||
case ESM::Weapon::SpearTwoWide:
|
||||
return true;
|
||||
case ESM::Weapon::ShortBladeOneHand:
|
||||
case ESM::Weapon::LongBladeOneHand:
|
||||
case ESM::Weapon::LongBladeTwoHand:
|
||||
case ESM::Weapon::AxeOneHand:
|
||||
case ESM::Weapon::AxeTwoHand:
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void StateMachine::update(float dt, bool enabled)
|
||||
{
|
||||
auto* world = MWBase::Environment::get().getWorld();
|
||||
auto& handPose = mTrackingInput.pose;
|
||||
auto weaponType = world->getActiveWeaponType();
|
||||
|
||||
enabled = enabled && isMeleeWeapon(weaponType);
|
||||
enabled = enabled && !!mTrackingInput.status;
|
||||
|
||||
if (mEnabled != enabled)
|
||||
{
|
||||
reset();
|
||||
mEnabled = enabled;
|
||||
}
|
||||
if (!enabled)
|
||||
return;
|
||||
|
||||
mTimeSinceEnteredState += dt;
|
||||
|
||||
|
||||
// First determine direction of different swing types
|
||||
|
||||
// Discover orientation of weapon
|
||||
osg::Quat weaponDir = handPose.orientation;
|
||||
|
||||
// Morrowind models do not hold weapons at a natural angle, so i rotate the hand forward
|
||||
// to get a more natural angle on the weapon to allow more comfortable combat.
|
||||
if (weaponType != ESM::Weapon::HandToHand)
|
||||
weaponDir = osg::Quat(osg::PI_4, osg::Vec3{ 1,0,0 }) * weaponDir;
|
||||
|
||||
// Thrust means stabbing in the direction of the weapon
|
||||
osg::Vec3 thrustDirection = weaponDir * osg::Vec3{ 0,1,0 };
|
||||
|
||||
// Slash and Chop are vertical, relative to the orientation of the weapon (direction of the sharp edge / hammer)
|
||||
osg::Vec3 slashChopDirection = weaponDir * osg::Vec3{ 0,0,1 };
|
||||
|
||||
// Side direction of the weapon (i.e. The blunt side of the sword)
|
||||
osg::Vec3 sideDirection = weaponDir * osg::Vec3{ 1,0,0 };
|
||||
|
||||
|
||||
// Next determine current hand movement
|
||||
|
||||
// If tracking is lost, openxr will return a position of 0
|
||||
// So i reset position when tracking is re-acquired to avoid a superspeed strike.
|
||||
// Theoretically, the player's hand really could be at 0,0,0
|
||||
// but that's a super rare case so whatever.
|
||||
if (mPreviousPosition == osg::Vec3(0.f, 0.f, 0.f))
|
||||
mPreviousPosition = handPose.position;
|
||||
|
||||
osg::Vec3 movement = handPose.position - mPreviousPosition;
|
||||
mMovementSinceEnteredState += movement.length();
|
||||
mPreviousPosition = handPose.position;
|
||||
osg::Vec3 swingVector = movement / dt;
|
||||
osg::Vec3 swingDirection = swingVector;
|
||||
swingDirection.normalize();
|
||||
|
||||
// Compute swing velocities
|
||||
|
||||
// Thrust follows the orientation of the weapon. Negative thrust = no attack.
|
||||
mThrustVelocity = swingVector * thrustDirection;
|
||||
mVelocity = swingVector.length();
|
||||
|
||||
|
||||
if (isSideSwingValidForWeapon(weaponType))
|
||||
{
|
||||
// Compute velocity in the plane normal to the thrust direction.
|
||||
float thrustComponent = std::abs(mThrustVelocity / mVelocity);
|
||||
float planeComponent = std::sqrt(1 - thrustComponent * thrustComponent);
|
||||
mSlashChopVelocity = mVelocity * planeComponent;
|
||||
mSideVelocity = -1000.f;
|
||||
}
|
||||
else
|
||||
{
|
||||
// If side swing is not valid for the weapon, count slash/chop only along in
|
||||
// the direction of the weapon's edge.
|
||||
mSlashChopVelocity = std::abs(swingVector * slashChopDirection);
|
||||
mSideVelocity = std::abs(swingVector * sideDirection);
|
||||
}
|
||||
|
||||
|
||||
float orientationVerticality = std::abs(thrustDirection * osg::Vec3{ 0,0,1 });
|
||||
float swingVerticality = std::abs(swingDirection * osg::Vec3{ 0,0,1 });
|
||||
|
||||
// Pick swing type based on greatest current velocity
|
||||
// Note i use abs() of thrust velocity to prevent accidentally triggering
|
||||
// chop/slash when player is withdrawing the weapon.
|
||||
if (mSideVelocity > std::abs(mThrustVelocity) && mSideVelocity > mSlashChopVelocity)
|
||||
{
|
||||
// Player is swinging with the "blunt" side of a weapon that
|
||||
// cannot be used that way.
|
||||
mSwingType = -1;
|
||||
}
|
||||
else if (std::abs(mThrustVelocity) > mSlashChopVelocity)
|
||||
{
|
||||
mSwingType = ESM::Weapon::AT_Thrust;
|
||||
}
|
||||
else
|
||||
{
|
||||
// First check if the weapon is pointing upwards. In which case slash is not
|
||||
// applicable, and the attack must be a chop.
|
||||
if (orientationVerticality > 0.707)
|
||||
mSwingType = ESM::Weapon::AT_Chop;
|
||||
else
|
||||
{
|
||||
// Next check if the swing is more horizontal or vertical. A slash
|
||||
// would be more horizontal.
|
||||
if (swingVerticality > 0.707)
|
||||
mSwingType = ESM::Weapon::AT_Chop;
|
||||
else
|
||||
mSwingType = ESM::Weapon::AT_Slash;
|
||||
}
|
||||
}
|
||||
|
||||
switch (mState)
|
||||
{
|
||||
case SwingState_Cooldown:
|
||||
return update_cooldownState();
|
||||
case SwingState_Ready:
|
||||
return update_readyState();
|
||||
case SwingState_Swing:
|
||||
return update_swingState();
|
||||
case SwingState_Impact:
|
||||
return update_impactState();
|
||||
case SwingState_Launch:
|
||||
return update_launchState();
|
||||
default:
|
||||
throw std::logic_error(std::string("You forgot to implement state ") + stateToString(mState) + " ya dingus");
|
||||
}
|
||||
}
|
||||
|
||||
void StateMachine::update_cooldownState()
|
||||
{
|
||||
if (mTimeSinceEnteredState >= mMinimumPeriod)
|
||||
transition_cooldownToReady();
|
||||
}
|
||||
|
||||
void StateMachine::transition_cooldownToReady()
|
||||
{
|
||||
transition(SwingState_Ready);
|
||||
}
|
||||
|
||||
void StateMachine::update_readyState()
|
||||
{
|
||||
if (canSwing())
|
||||
return transition_readyToLaunch();
|
||||
}
|
||||
|
||||
void StateMachine::transition_readyToLaunch()
|
||||
{
|
||||
transition(SwingState_Launch);
|
||||
}
|
||||
|
||||
void StateMachine::playSwish()
|
||||
{
|
||||
MWBase::SoundManager* sndMgr = MWBase::Environment::get().getSoundManager();
|
||||
|
||||
std::string sound = "Weapon Swish";
|
||||
if (mStrength < 0.5f)
|
||||
sndMgr->playSound3D(mPtr, sound, 1.0f, 0.8f); //Weak attack
|
||||
if (mStrength < 1.0f)
|
||||
sndMgr->playSound3D(mPtr, sound, 1.0f, 1.0f); //Medium attack
|
||||
else
|
||||
sndMgr->playSound3D(mPtr, sound, 1.0f, 1.2f); //Strong attack
|
||||
|
||||
Log(Debug::Verbose) << "Swing: " << swingTypeToString(mSwingType);
|
||||
}
|
||||
|
||||
void StateMachine::update_launchState()
|
||||
{
|
||||
if (mMovementSinceEnteredState > mMinimumPeriod)
|
||||
transition_launchToSwing();
|
||||
if (!canSwing())
|
||||
return transition_launchToReady();
|
||||
}
|
||||
|
||||
void StateMachine::transition_launchToReady()
|
||||
{
|
||||
transition(SwingState_Ready);
|
||||
}
|
||||
|
||||
void StateMachine::transition_launchToSwing()
|
||||
{
|
||||
playSwish();
|
||||
transition(SwingState_Swing);
|
||||
|
||||
// As a special case, update the new state immediately to allow
|
||||
// same-frame impacts.
|
||||
update_swingState();
|
||||
}
|
||||
|
||||
void StateMachine::update_swingState()
|
||||
{
|
||||
mMaxSwingVelocity = std::max(mVelocity, mMaxSwingVelocity);
|
||||
mStrength = std::min(1.f, (mMaxSwingVelocity - mMinVelocity) / mMaxVelocity);
|
||||
|
||||
// When velocity falls below minimum, transition to register the miss
|
||||
if (!canSwing())
|
||||
return transition_swingingToImpact();
|
||||
// Call hit with simulated=true to check for hit without actually causing an impact
|
||||
if (mPtr.getClass().hit(mPtr, mStrength, mSwingType, true))
|
||||
return transition_swingingToImpact();
|
||||
}
|
||||
|
||||
void StateMachine::transition_swingingToImpact()
|
||||
{
|
||||
mPtr.getClass().hit(mPtr, mStrength, mSwingType, false);
|
||||
transition(SwingState_Impact);
|
||||
}
|
||||
|
||||
void StateMachine::update_impactState()
|
||||
{
|
||||
if (mVelocity < mMinVelocity)
|
||||
return transition_impactToCooldown();
|
||||
}
|
||||
|
||||
void StateMachine::transition_impactToCooldown()
|
||||
{
|
||||
transition(SwingState_Cooldown);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
115
apps/openmw/mwvr/realisticcombat.hpp
Normal file
115
apps/openmw/mwvr/realisticcombat.hpp
Normal file
|
@ -0,0 +1,115 @@
|
|||
#ifndef MWVR_REALISTICCOMBAT_H
|
||||
#define MWVR_REALISTICCOMBAT_H
|
||||
|
||||
#include <components/esm/loadweap.hpp>
|
||||
|
||||
#include "../mwbase/world.hpp"
|
||||
#include "../mwworld/ptr.hpp"
|
||||
#include "../mwworld/class.hpp"
|
||||
|
||||
#include "vrenvironment.hpp"
|
||||
#include "vrsession.hpp"
|
||||
#include "vrtracking.hpp"
|
||||
|
||||
namespace MWVR {
|
||||
namespace RealisticCombat {
|
||||
|
||||
/// Enum describing the current state of the MWVR::RealisticCombat::StateMachine
|
||||
enum SwingState
|
||||
{
|
||||
SwingState_Ready,
|
||||
SwingState_Launch,
|
||||
SwingState_Swing,
|
||||
SwingState_Impact,
|
||||
SwingState_Cooldown,
|
||||
};
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
/// \brief State machine for "realistic" combat in openmw vr
|
||||
///
|
||||
/// \sa SwingState
|
||||
///
|
||||
/// Initial state: Ready.
|
||||
///
|
||||
/// State Ready: Ready to initiate a new attack.
|
||||
/// State Launch: Player has begun swinging his weapon.
|
||||
/// State Swing: Currently swinging weapon.
|
||||
/// State Impact: Contact made, weapon still swinging.
|
||||
/// State Cooldown: Swing completed, wait a minimum period before next.
|
||||
///
|
||||
/// Transition rules:
|
||||
/// Ready -> Launch: When the minimum velocity of swing is achieved.
|
||||
/// Launch -> Ready: When the minimum velocity of swing is lost before minimum distance was swung.
|
||||
/// Launch -> Swing: When minimum distance is swung.
|
||||
/// - Play Swish sound
|
||||
/// Swing -> Impact: When minimum velocity is lost, or when a hit is detected.
|
||||
/// - Register hit based on max velocity observed in swing state
|
||||
/// Impact -> Cooldown: When velocity returns below minimum.
|
||||
/// Cooldown -> Ready: When the minimum period has passed since entering Cooldown state
|
||||
///
|
||||
///
|
||||
struct StateMachine : public VRTrackingListener
|
||||
{
|
||||
public:
|
||||
StateMachine(MWWorld::Ptr ptr, VRPath trackingPath);
|
||||
void update(float dt, bool enabled);
|
||||
MWWorld::Ptr ptr() { return mPtr; }
|
||||
|
||||
protected:
|
||||
void onTrackingUpdated(VRTrackingSource& source, DisplayTime predictedDisplayTime) override;
|
||||
|
||||
bool canSwing();
|
||||
|
||||
void playSwish();
|
||||
void reset();
|
||||
|
||||
void transition(SwingState newState);
|
||||
|
||||
void update_cooldownState();
|
||||
void transition_cooldownToReady();
|
||||
|
||||
void update_readyState();
|
||||
void transition_readyToLaunch();
|
||||
|
||||
void update_launchState();
|
||||
void transition_launchToReady();
|
||||
void transition_launchToSwing();
|
||||
|
||||
void update_swingState();
|
||||
void transition_swingingToImpact();
|
||||
|
||||
void update_impactState();
|
||||
void transition_impactToCooldown();
|
||||
|
||||
private:
|
||||
MWWorld::Ptr mPtr;
|
||||
const float mMinVelocity;
|
||||
const float mMaxVelocity;
|
||||
|
||||
float mVelocity = 0.f;
|
||||
float mMaxSwingVelocity = 0.f;
|
||||
|
||||
SwingState mState = SwingState_Ready;
|
||||
int mSwingType = -1;
|
||||
float mStrength = 0.f;
|
||||
|
||||
float mThrustVelocity{ 0.f };
|
||||
float mSlashChopVelocity{ 0.f };
|
||||
float mSideVelocity{ 0.f };
|
||||
|
||||
float mMinimumPeriod{ .25f };
|
||||
|
||||
float mTimeSinceEnteredState = { 0.f };
|
||||
float mMovementSinceEnteredState = { 0.f };
|
||||
|
||||
bool mEnabled = false;
|
||||
|
||||
osg::Vec3 mPreviousPosition{ 0.f,0.f,0.f };
|
||||
VRTrackingPose mTrackingInput = VRTrackingPose();
|
||||
VRPath mTrackingPath = 0;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue