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
pull/615/head
David Cernat 3 years ago
commit 339a196579

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

@ -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")

@ -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();

@ -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;
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();
std::string resultMessage, resultSound;
#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);
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);
// 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);
mClipCullNode = new ClipCullNode;
}
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);
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));
mClipCullNode->setPlane(osg::Plane(osg::Vec3d(0,0,-1), osg::Vec3d(0,0, waterLevel)));
}
osg::Texture2D* getRefractionTexture() const
{
return mRefractionTexture.get();
}
mViewMatrix = osg::Matrix::scale(1, 1, refractionScale) *
osg::Matrix::translate(0, 0, (1.0 - refractionScale) * waterLevel);
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);
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);
mClipCullNode = new ClipCullNode;
}
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(camera->getOrCreateStateSet());
SceneUtil::ShadowManager::disableShadowsForStateSet(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);

@ -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;
}
}

@ -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

@ -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);
}
}

@ -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

@ -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));
}
}

@ -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

@ -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;
}
}
}
}
}

@ -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

@ -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.
}
}

@ -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

@ -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;
}
}

@ -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

@ -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());
}
}

@ -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

@ -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();
}
}

@ -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

@ -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()
{
}
}

@ -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

@ -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 };
}
}

@ -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

@ -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()
{
}
}

@ -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

@ -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;
}
}

@ -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

@ -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);
}
}
}

@ -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…
Cancel
Save