1
0
Fork 1
mirror of https://github.com/TES3MP/openmw-tes3mp.git synced 2025-01-20 06:53:52 +00:00

Realistic combat, first draft

This commit is contained in:
Mads Buvik Sandvei 2020-03-15 15:31:38 +01:00
parent 31f5b76394
commit 893b75d767
34 changed files with 645 additions and 1375 deletions

View file

@ -113,11 +113,7 @@ endif ()
if(BUILD_VR_OPENXR)
set(OPENMW_VR_FILES
engine_vr.cpp
mwvr/openxranimation.hpp
mwvr/openxranimation.cpp
mwvr/openxrenvironment.hpp
mwvr/openxrenvironment.cpp
vrengine.cpp
mwvr/openxrinputmanager.hpp
mwvr/openxrinputmanager.cpp
mwvr/openxrlayer.hpp
@ -140,6 +136,12 @@ if(BUILD_VR_OPENXR)
mwvr/openxrviewer.cpp
mwvr/openxrworldview.hpp
mwvr/openxrworldview.cpp
mwvr/realisticcombat.hpp
mwvr/realisticcombat.cpp
mwvr/vranimation.hpp
mwvr/vranimation.cpp
mwvr/vrenvironment.hpp
mwvr/vrenvironment.cpp
)
openmw_add_executable(openmw_vr

View file

@ -737,7 +737,7 @@ void OMW::Engine::go()
#ifdef USE_OPENXR
auto* root = mViewer->getSceneData();
auto* xrViewer = MWVR::OpenXREnvironment::get().getViewer();
auto* xrViewer = MWVR::Environment::get().getViewer();
xrViewer->addChild(root);
mViewer->setSceneData(xrViewer);
mXrEnvironment.setMenuManager(new MWVR::OpenXRMenuManager(mViewer));

View file

@ -14,7 +14,7 @@
#include "mwworld/ptr.hpp"
#ifdef USE_OPENXR
#include "mwvr/openxrenvironment.hpp"
#include "mwvr/vrenvironment.hpp"
#endif
namespace Resource
@ -212,7 +212,7 @@ namespace OMW
Files::ConfigurationManager& mCfgMgr;
#ifdef USE_OPENXR
MWVR::OpenXREnvironment mXrEnvironment;
MWVR::Environment mXrEnvironment;
void initVr();
#endif

View file

@ -23,6 +23,7 @@ namespace osg
class Quat;
class Image;
class Node;
class Transform;
}
namespace Loading
@ -79,6 +80,11 @@ namespace MWWorld
typedef std::vector<std::pair<MWWorld::Ptr,MWMechanics::Movement> > PtrMovementList;
}
namespace MWPhysics
{
class PhysicsSystem;
}
namespace MWBase
{
/// \brief Interface for the World (implemented in MWWorld)
@ -391,6 +397,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
@ -630,26 +642,12 @@ namespace MWBase
virtual bool hasCollisionWithDoor(const MWWorld::ConstPtr& door, const osg::Vec3f& position, const osg::Vec3f& destination) const = 0;
#ifdef USE_OPENXR
using IntersectedObject = std::tuple<
MWWorld::Ptr,
osg::Node*,
osg::Vec3f
>;
/// @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, osg::Transform* pointer) = 0;
virtual float getTargetObject(MWRender::RayResult& result, osg::Transform* pointer, float maxDistance, bool ignorePlayer) = 0;
virtual float getDistanceToPointedAtObject() = 0;
///< Return the distance to the object and/or node the player is currently pointing at
virtual void getPointedAtObject(MWRender::RayResult& result) = 0;
///< Return pointer to the object and/or node the player is currently pointing at
virtual MWWorld::Ptr placeObject(const MWWorld::ConstPtr& object, int amount) = 0;
///< copy and place an object into the gameworld where the player is currently pointing
/// @param object
/// @param number of objects to place
virtual bool canPlaceObject() = 0;
///< @return true if it is possible to place on object where the player is currently pointing
#endif
virtual MWPhysics::PhysicsSystem* getPhysicsSystem(void) = 0;
};
}

View file

@ -227,7 +227,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
{
MWWorld::LiveCellRef<ESM::Creature> *ref =
ptr.get<ESM::Creature>();
@ -235,7 +235,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;
@ -259,12 +259,12 @@ 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;
if (!victim.getClass().isActor())
return; // Can't hit non-actors
return false; // Can't hit non-actors
osg::Vec3f hitPosition (result.second);
@ -274,7 +274,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;
@ -330,6 +330,7 @@ namespace MWClass
MWMechanics::diseaseContact(victim, ptr);
victim.getClass().onHit(victim, damage, healthdmg, weapon, ptr, hitPosition, true);
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

View file

@ -55,7 +55,7 @@ namespace MWClass
virtual MWMechanics::CreatureStats& getCreatureStats (const MWWorld::Ptr& ptr) const;
///< Return creature stats
virtual void hit(const MWWorld::Ptr& ptr, float attackStrength, int type) const;
virtual bool hit(const MWWorld::Ptr& ptr, float attackStrength, int type, bool simulated) const;
virtual 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;

View file

@ -551,7 +551,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
{
MWBase::World *world = MWBase::Environment::get().getWorld();
@ -564,7 +564,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() ?
@ -581,14 +585,17 @@ 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();
if(!othercls.isActor()) // Can't hit non-actors
return;
return false;
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);
@ -603,7 +610,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;
@ -664,6 +671,7 @@ namespace MWClass
MWMechanics::diseaseContact(victim, ptr);
othercls.onHit(victim, damage, healthdmg, weapon, ptr, hitPosition, true);
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

View file

@ -70,7 +70,7 @@ namespace MWClass
virtual bool hasInventoryStore(const MWWorld::Ptr &ptr) const { return true; }
virtual void hit(const MWWorld::Ptr& ptr, float attackStrength, int type) const;
virtual bool hit(const MWWorld::Ptr& ptr, float attackStrength, int type, bool simulated) const;
virtual 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;

View file

@ -114,7 +114,7 @@
#include "resourceskin.hpp"
#ifdef USE_OPENXR
#include "../mwvr/openxrenvironment.hpp"
#include "../mwvr/vrenvironment.hpp"
#include "../mwvr/openxrmenu.hpp"
#endif
@ -1890,7 +1890,7 @@ namespace MWGui
#ifdef USE_OPENXR
// Temporary hack to force update of menu placement
// (Menu gets recreated next tick)
auto* xrMenuManager = MWVR::OpenXREnvironment::get().getMenuManager();
auto* xrMenuManager = MWVR::Environment::get().getMenuManager();
#endif
mVideoWidget->eventKeyButtonPressed.clear();

View file

@ -49,7 +49,7 @@
#include "spellcasting.hpp"
#ifdef USE_OPENXR
#include "../mwvr/openxranimation.hpp"
#include "../mwvr/vranimation.hpp"
#endif
namespace
@ -1029,7 +1029,7 @@ void CharacterController::handleTextKey(const std::string &groupname, const std:
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)

View file

@ -43,7 +43,7 @@
#ifdef USE_OPENXR
#include "../mwvr/openxrsession.hpp"
#include "../mwvr/openxrinputmanager.hpp"
#include "../mwvr/openxrenvironment.hpp"
#include "../mwvr/vrenvironment.hpp"
#endif
#include "collisiontype.hpp"
@ -277,7 +277,7 @@ namespace MWPhysics
static osg::Vec3f move(osg::Vec3f position, const MWWorld::Ptr &ptr, Actor* physicActor, const osg::Vec3f &movement, float time,
bool isFlying, float waterlevel, float slowFall, const btCollisionWorld* collisionWorld,
std::map<MWWorld::Ptr, MWWorld::Ptr>& standingCollisionTracker, bool isPlayer)
std::map<MWWorld::Ptr, MWWorld::Ptr>& standingCollisionTracker)
{
ESM::Position refpos = ptr.getRefData().getPosition();
// Early-out for totally static creatures
@ -285,12 +285,14 @@ namespace MWPhysics
if (!ptr.getClass().isMobile(ptr))
return position;
const bool isPlayer = (ptr == MWMechanics::getPlayer());
// In VR, player should move according to current direction of
// a selected limb, rather than current orientation of camera.
#ifdef USE_OPENXR
if (isPlayer)
{
auto* session = MWVR::OpenXREnvironment::get().getSession();
auto* session = MWVR::Environment::get().getSession();
if (session)
{
float yaw = session->movementYaw();
@ -349,7 +351,6 @@ namespace MWPhysics
if (ptr.getClass().getMovementSettings(ptr).mPosition[2])
{
const bool isPlayer = (ptr == MWMechanics::getPlayer());
// Advance acrobatics and set flag for GetPCJumping
if (isPlayer)
{
@ -389,7 +390,7 @@ namespace MWPhysics
#ifdef USE_OPENXR
if (isPlayer)
{
auto* inputManager = MWVR::OpenXREnvironment::get().getInputManager();
auto* inputManager = MWVR::Environment::get().getInputManager();
osg::Vec3 trackingOffset = inputManager->mHeadOffset;
// Player's tracking height should not affect character position
@ -449,7 +450,7 @@ namespace MWPhysics
}
}
// Best effort attempt at not losing any tracking
// Try not to lose any tracking
osg::Vec3 moved = newPosition - position;
inputManager->mHeadOffset.x() -= moved.x();
inputManager->mHeadOffset.y() -= moved.y();
@ -1368,7 +1369,6 @@ namespace MWPhysics
PtrVelocityList::iterator iter = mMovementQueue.begin();
for(;iter != mMovementQueue.end();++iter)
{
bool isPlayer = iter->first == MWMechanics::getPlayer();
ActorMap::iterator foundActor = mActors.find(iter->first);
if (foundActor == mActors.end()) // actor was already removed from the scene
continue;
@ -1408,7 +1408,7 @@ namespace MWPhysics
for (int i=0; i<numSteps; ++i)
{
position = MovementSolver::move(position, physicActor->getPtr(), physicActor, iter->second, mPhysicsDt,
flying, waterlevel, slowFall, mCollisionWorld, mStandingCollisions, isPlayer);
flying, waterlevel, slowFall, mCollisionWorld, mStandingCollisions);
if (position != physicActor->getPosition())
positionChanged = true;
physicActor->setPosition(position); // always set even if unchanged to make sure interpolation is correct

View file

@ -13,7 +13,7 @@
#ifdef USE_OPENXR
#include "../mwvr/openxrinputmanager.hpp"
#include "../mwvr/openxrenvironment.hpp"
#include "../mwvr/vrenvironment.hpp"
#endif
#include "npcanimation.hpp"
@ -127,7 +127,7 @@ namespace MWRender
osg::Vec3d position = getFocalPoint();
#ifdef USE_OPENXR
auto* inputManager = MWVR::OpenXREnvironment::get().getInputManager();
auto* inputManager = MWVR::Environment::get().getInputManager();
if (inputManager)
{
position += inputManager->mHeadOffset;

View file

@ -33,6 +33,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>
@ -68,8 +69,8 @@
#include "actorspaths.hpp"
#ifdef USE_OPENXR
#include "../mwvr/openxranimation.hpp"
#include "../mwvr/openxrenvironment.hpp"
#include "../mwvr/vranimation.hpp"
#include "../mwvr/vrenvironment.hpp"
#endif
namespace
@ -1085,6 +1086,24 @@ namespace MWRender
return getIntersectionResult(intersector);
}
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(1, 0, 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,
@ -1165,8 +1184,8 @@ namespace MWRender
void RenderingManager::renderPlayer(const MWWorld::Ptr &player)
{
#ifdef USE_OPENXR
MWVR::OpenXREnvironment::get().setPlayerAnimation(new MWVR::OpenXRAnimation(player, player.getRefData().getBaseNode(), mResourceSystem, false, nullptr));
mPlayerAnimation = MWVR::OpenXREnvironment::get().getPlayerAnimation();
MWVR::Environment::get().setPlayerAnimation(new MWVR::VRAnimation(player, player.getRefData().getBaseNode(), mResourceSystem, false, nullptr));
mPlayerAnimation = MWVR::Environment::get().getPlayerAnimation();
#else
mPlayerAnimation = new NpcAnimation(player, player.getRefData().getBaseNode(), mResourceSystem, 0, NpcAnimation::VM_Normal,
mFirstPersonFieldOfView);

View file

@ -159,7 +159,11 @@ namespace MWRender
void screenshot(osg::Image* image, int w, int h, osg::Matrixd cameraTransform=osg::Matrixd());
bool screenshot360(osg::Image* image, std::string settingStr);
/// Cast a ray between two points
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.

View file

@ -1,854 +0,0 @@
#include "openxrengine.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"
namespace
{
void checkSDLError(int ret)
{
if (ret != 0)
Log(Debug::Error) << "SDL error: " << SDL_GetError();
}
}
void MWVR::OpenXREngine::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 MWVR::OpenXREngine::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.getInputManager()->isWindowVisible())
return false;
// 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;
}
MWVR::OpenXREngine::OpenXREngine(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();
Uint32 flags = SDL_INIT_VIDEO|SDL_INIT_NOPARACHUTE|SDL_INIT_GAMECONTROLLER|SDL_INIT_JOYSTICK;
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();
}
MWVR::OpenXREngine::~OpenXREngine()
{
mEnvironment.cleanup();
delete mScriptContext;
mScriptContext = nullptr;
mWorkQueue = nullptr;
mViewer = nullptr;
mResourceSystem.reset();
delete mEncoder;
mEncoder = nullptr;
mOpenXRManager = nullptr;
if (mWindow)
{
SDL_DestroyWindow(mWindow);
mWindow = nullptr;
}
SDL_Quit();
}
void MWVR::OpenXREngine::enableFSStrict(bool fsStrict)
{
mFSStrict = fsStrict;
}
// Set data dir
void MWVR::OpenXREngine::setDataDirs (const Files::PathContainer& dataDirs)
{
mDataDirs = dataDirs;
mDataDirs.insert(mDataDirs.begin(), (mResDir / "vfs"));
mFileCollections = Files::Collections (mDataDirs, !mFSStrict);
}
// Add BSA archive
void MWVR::OpenXREngine::addArchive (const std::string& archive) {
mArchives.push_back(archive);
}
// Set resource dir
void MWVR::OpenXREngine::setResourceDir (const boost::filesystem::path& parResDir)
{
mResDir = parResDir;
}
// Set start cell name
void MWVR::OpenXREngine::setCell (const std::string& cellName)
{
mCellName = cellName;
}
void MWVR::OpenXREngine::addContentFile(const std::string& file)
{
mContentFiles.push_back(file);
}
void MWVR::OpenXREngine::setSkipMenu (bool skipMenu, bool newGame)
{
mSkipMenu = skipMenu;
mNewGame = newGame;
}
std::string MWVR::OpenXREngine::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 MWVR::OpenXREngine::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");
mOpenXRManager = std::make_unique<OpenXRManager>(graphicsWindow);
osg::ref_ptr<osg::Camera> camera = mViewer->getCamera();
camera->setGraphicsContext(graphicsWindow);
camera->setViewport(0, 0, traits->width, traits->height);
mViewer->realize();
mViewer->getEventQueue()->getCurrentEventState()->setWindowRectangle(0, 0, traits->width, traits->height);
}
void MWVR::OpenXREngine::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 MWVR::OpenXREngine::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;
// find correct path to the game controller bindings
// File format for controller mappings is different for SDL <= 2.0.4, 2.0.5, and >= 2.0.6
SDL_version linkedSdlVersion;
SDL_GetVersion(&linkedSdlVersion);
std::string controllerFileName;
if (linkedSdlVersion.major == 2 && linkedSdlVersion.minor == 0 && linkedSdlVersion.patch <= 4) {
controllerFileName = "gamecontrollerdb_204.txt";
} else if (linkedSdlVersion.major == 2 && linkedSdlVersion.minor == 0 && linkedSdlVersion.patch == 5) {
controllerFileName = "gamecontrollerdb_205.txt";
} else {
controllerFileName = "gamecontrollerdb.txt";
}
const std::string userdefault = mCfgMgr.getUserConfigPath().string() + "/" + controllerFileName;
const std::string localdefault = mCfgMgr.getLocalPath().string() + "/" + controllerFileName;
const std::string globaldefault = mCfgMgr.getGlobalPath().string() + "/" + controllerFileName;
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
MWInput::InputManager* input = new MWInput::InputManager (mWindow, mViewer, mScreenCaptureHandler, mScreenCaptureOperation, keybinderUser, keybinderUserExists, userGameControllerdb, gameControllerdb, mGrab);
mEnvironment.setInputManager (input);
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(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);
// Create sound system
mEnvironment.setSoundManager (new MWSound::SoundManager(mVFS.get(), mUseSound));
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();
input->setPlayer(&mEnvironment.getWorld()->getPlayer());
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 MWVR::OpenXREngine::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.getUserDataPath().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);
// 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();
mViewer->updateTraversal();
mEnvironment.getWorld()->updateWindowManager();
mViewer->renderingTraversals();
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 MWVR::OpenXREngine::setCompileAll (bool all)
{
mCompileAll = all;
}
void MWVR::OpenXREngine::setCompileAllDialogue (bool all)
{
mCompileAllDialogue = all;
}
void MWVR::OpenXREngine::setSoundUsage(bool soundUsage)
{
mUseSound = soundUsage;
}
void MWVR::OpenXREngine::setEncoding(const ToUTF8::FromType& encoding)
{
mEncoding = encoding;
}
void MWVR::OpenXREngine::setScriptConsoleMode (bool enabled)
{
mScriptConsoleMode = enabled;
}
void MWVR::OpenXREngine::setStartupScript (const std::string& path)
{
mStartupScript = path;
}
void MWVR::OpenXREngine::setActivationDistanceOverride (int distance)
{
mActivationDistanceOverride = distance;
}
void MWVR::OpenXREngine::setWarningsMode (int mode)
{
mWarningsMode = mode;
}
void MWVR::OpenXREngine::setScriptBlacklist (const std::vector<std::string>& list)
{
mScriptBlacklist = list;
}
void MWVR::OpenXREngine::setScriptBlacklistUse (bool use)
{
mScriptBlacklistUse = use;
}
void MWVR::OpenXREngine::enableFontExport(bool exportFonts)
{
mExportFonts = exportFonts;
}
void MWVR::OpenXREngine::setSaveGameFile(const std::string &savegame)
{
mSaveGameFile = savegame;
}
void MWVR::OpenXREngine::setRandomSeed(unsigned int seed)
{
mRandomSeed = seed;
}

View file

@ -1,217 +0,0 @@
#ifndef ENGINE_H
#define ENGINE_H
#include <components/compiler/extensions.hpp>
#include <components/files/collections.hpp>
#include <components/translation/translation.hpp>
#include <components/settings/settings.hpp>
#include <osgViewer/Viewer>
#include <osgViewer/ViewerEventHandlers>
#include "../mwbase/environment.hpp"
#include "../mwworld/ptr.hpp"
#include "openxrmanager.hpp"
namespace Resource
{
class ResourceSystem;
}
namespace SceneUtil
{
class WorkQueue;
}
namespace VFS
{
class Manager;
}
namespace Compiler
{
class Context;
}
namespace MWScript
{
class ScriptManager;
}
namespace MWSound
{
class SoundManager;
}
namespace MWWorld
{
class World;
}
namespace MWGui
{
class WindowManager;
}
namespace Files
{
struct ConfigurationManager;
}
namespace osgViewer
{
class ScreenCaptureHandler;
}
struct SDL_Window;
namespace MWVR
{
/// \brief Main VR engine class, that brings together all the components of OpenMW
class OpenXREngine
{
private:
SDL_Window* mWindow;
std::unique_ptr<OpenXRManager> mOpenXRManager;
std::unique_ptr<VFS::Manager> mVFS;
std::unique_ptr<Resource::ResourceSystem> mResourceSystem;
osg::ref_ptr<SceneUtil::WorkQueue> mWorkQueue;
MWBase::Environment mEnvironment;
ToUTF8::FromType mEncoding;
ToUTF8::Utf8Encoder* mEncoder;
Files::PathContainer mDataDirs;
std::vector<std::string> mArchives;
boost::filesystem::path mResDir;
osg::ref_ptr<osgViewer::Viewer> mViewer;
osg::ref_ptr<osgViewer::ScreenCaptureHandler> mScreenCaptureHandler;
osgViewer::ScreenCaptureHandler::CaptureOperation *mScreenCaptureOperation;
std::string mCellName;
std::vector<std::string> mContentFiles;
bool mSkipMenu;
bool mUseSound;
bool mCompileAll;
bool mCompileAllDialogue;
int mWarningsMode;
std::string mFocusName;
bool mScriptConsoleMode;
std::string mStartupScript;
int mActivationDistanceOverride;
std::string mSaveGameFile;
// Grab mouse?
bool mGrab;
bool mExportFonts;
unsigned int mRandomSeed;
Compiler::Extensions mExtensions;
Compiler::Context *mScriptContext;
Files::Collections mFileCollections;
bool mFSStrict;
Translation::Storage mTranslationDataStorage;
std::vector<std::string> mScriptBlacklist;
bool mScriptBlacklistUse;
bool mNewGame;
osg::Timer_t mStartTick;
private:
// not implemented
OpenXREngine(const OpenXREngine&) = delete;
OpenXREngine& operator= (const OpenXREngine&) = delete;
void executeLocalScripts();
bool frame (float dt);
/// Load settings from various files, returns the path to the user settings file
std::string loadSettings (Settings::Manager & settings);
/// Prepare engine for game play
void prepareEngine (Settings::Manager & settings);
void createWindow(Settings::Manager& settings);
void setWindowIcon();
public:
OpenXREngine(Files::ConfigurationManager& configurationManager);
virtual ~OpenXREngine();
/// Enable strict filesystem mode (do not fold case)
///
/// \attention The strict mode must be specified before any path-related settings
/// are given to the engine.
void enableFSStrict(bool fsStrict);
/// Set data dirs
void setDataDirs(const Files::PathContainer& dataDirs);
/// Add BSA archive
void addArchive(const std::string& archive);
/// Set resource dir
void setResourceDir(const boost::filesystem::path& parResDir);
/// Set start cell name
void setCell(const std::string& cellName);
/**
* @brief addContentFile - Adds content file (ie. esm/esp, or omwgame/omwaddon) to the content files container.
* @param file - filename (extension is required)
*/
void addContentFile(const std::string& file);
/// Disable or enable all sounds
void setSoundUsage(bool soundUsage);
/// Skip main menu and go directly into the game
///
/// \param newGame Start a new game instead off dumping the player into the game
/// (ignored if !skipMenu).
void setSkipMenu (bool skipMenu, bool newGame);
void setGrabMouse(bool grab) { mGrab = grab; }
/// Initialise and enter main loop.
void go();
/// Compile all scripts (excludign dialogue scripts) at startup?
void setCompileAll (bool all);
/// Compile all dialogue scripts at startup?
void setCompileAllDialogue (bool all);
/// Font encoding
void setEncoding(const ToUTF8::FromType& encoding);
/// Enable console-only script functionality
void setScriptConsoleMode (bool enabled);
/// Set path for a script that is run on startup in the console.
void setStartupScript (const std::string& path);
/// Override the game setting specified activation distance.
void setActivationDistanceOverride (int distance);
void setWarningsMode (int mode);
void setScriptBlacklist (const std::vector<std::string>& list);
void setScriptBlacklistUse (bool use);
void enableFontExport(bool exportFonts);
/// Set the save game file to load after initialising the engine.
void setSaveGameFile(const std::string& savegame);
void setRandomSeed(unsigned int seed);
private:
Files::ConfigurationManager& mCfgMgr;
};
}
#endif /* ENGINE_H */

View file

@ -1,6 +1,6 @@
#include "openxranimation.hpp"
#include "vranimation.hpp"
#include "openxrinputmanager.hpp"
#include "openxrenvironment.hpp"
#include "vrenvironment.hpp"
#include "openxrmanager.hpp"
#include "openxrmanagerimpl.hpp"
@ -265,7 +265,7 @@ struct OpenXRInput
XrActionSet
OpenXRInput::createActionSet()
{
auto* xr = OpenXREnvironment::get().getManager();
auto* xr = Environment::get().getManager();
XrActionSet actionSet = XR_NULL_HANDLE;
XrActionSetCreateInfo createInfo{ XR_TYPE_ACTION_SET_CREATE_INFO };
strcpy_s(createInfo.actionSetName, "gameplay");
@ -308,7 +308,7 @@ OpenXRAction::~OpenXRAction() {
bool OpenXRAction::getFloat(XrPath subactionPath, float& value)
{
auto* xr = OpenXREnvironment::get().getManager();
auto* xr = Environment::get().getManager();
XrActionStateGetInfo getInfo{ XR_TYPE_ACTION_STATE_GET_INFO };
getInfo.action = mAction;
getInfo.subactionPath = subactionPath;
@ -323,7 +323,7 @@ bool OpenXRAction::getFloat(XrPath subactionPath, float& value)
bool OpenXRAction::getBool(XrPath subactionPath, bool& value)
{
auto* xr = OpenXREnvironment::get().getManager();
auto* xr = Environment::get().getManager();
XrActionStateGetInfo getInfo{ XR_TYPE_ACTION_STATE_GET_INFO };
getInfo.action = mAction;
getInfo.subactionPath = subactionPath;
@ -339,7 +339,7 @@ bool OpenXRAction::getBool(XrPath subactionPath, bool& value)
// Pose action only checks if the pose is active or not
bool OpenXRAction::getPose(XrPath subactionPath)
{
auto* xr = OpenXREnvironment::get().getManager();
auto* xr = Environment::get().getManager();
XrActionStateGetInfo getInfo{ XR_TYPE_ACTION_STATE_GET_INFO };
getInfo.action = mAction;
getInfo.subactionPath = subactionPath;
@ -352,7 +352,7 @@ bool OpenXRAction::getPose(XrPath subactionPath)
bool OpenXRAction::applyHaptics(XrPath subactionPath, float amplitude)
{
auto* xr = OpenXREnvironment::get().getManager();
auto* xr = Environment::get().getManager();
XrHapticVibration vibration{ XR_TYPE_HAPTIC_VIBRATION };
vibration.amplitude = amplitude;
vibration.duration = XR_MIN_HAPTIC_DURATION;
@ -414,7 +414,7 @@ OpenXRInput::ControllerActionPaths
OpenXRInput::generateControllerActionPaths(
const std::string& controllerAction)
{
auto* xr = OpenXREnvironment::get().getManager();
auto* xr = Environment::get().getManager();
ControllerActionPaths actionPaths;
std::string left = std::string("/user/hand/left") + controllerAction;
@ -473,7 +473,7 @@ OpenXRInput::OpenXRInput()
, mHandPoseAction(std::move(createAction(XR_ACTION_TYPE_POSE_INPUT, "hand_pose", "Hand Pose", { LEFT_HAND, RIGHT_HAND })))
, mHapticsAction(std::move(createAction(XR_ACTION_TYPE_VIBRATION_OUTPUT, "vibrate_hand", "Vibrate Hand", { LEFT_HAND, RIGHT_HAND })))
{
auto* xr = OpenXREnvironment::get().getManager();
auto* xr = Environment::get().getManager();
{ // Set up default bindings for the oculus
XrPath oculusTouchInteractionProfilePath;
CHECK_XRCMD(
@ -585,7 +585,7 @@ OpenXRInput::subactionPath(
void
OpenXRInput::updateControls()
{
auto* xr = OpenXREnvironment::get().getManager();
auto* xr = Environment::get().getManager();
if (!xr->impl().mSessionRunning)
return;
@ -696,7 +696,7 @@ OpenXRInput::updateControls()
XrPath OpenXRInput::generateXrPath(const std::string& path)
{
auto* xr = OpenXREnvironment::get().getManager();
auto* xr = Environment::get().getManager();
XrPath xrpath = 0;
CHECK_XRCMD(xrStringToPath(xr->impl().mInstance, path.c_str(), &xrpath));
return xrpath;
@ -720,7 +720,7 @@ OpenXRInput::getHandPoses(
int64_t time,
TrackedSpace space)
{
auto* xr = OpenXREnvironment::get().getManager();
auto* xr = Environment::get().getManager();
PoseSet handPoses{};
XrSpace referenceSpace = XR_NULL_HANDLE;
if (space == TrackedSpace::STAGE)
@ -762,7 +762,7 @@ void OpenXRInputManager::updateActivationIndication(void)
if (mPlayer)
mPlayer->setPointing(show);
auto* playerAnimation = OpenXREnvironment::get().getPlayerAnimation();
auto* playerAnimation = Environment::get().getPlayerAnimation();
if (playerAnimation)
playerAnimation->setPointForward(show);
}
@ -779,10 +779,11 @@ public:
virtual MWWorld::Ptr copyItem(const MWGui::ItemStack& item, size_t count, bool /*allowAutoEquip*/)
{
MWBase::World* world = MWBase::Environment::get().getWorld();
MWVR::VRAnimation* anim = MWVR::Environment::get().getPlayerAnimation();
MWWorld::Ptr dropped;
if (world->canPlaceObject())
dropped = world->placeObject(item.mBase, count);
if (anim->canPlaceObject())
dropped = world->placeObject(item.mBase, anim->getPointerTarget(), count);
else
dropped = world->dropObjectOnGround(world->getPlayerPtr(), item.mBase, count);
dropped.getCellRef().setOwner("");
@ -804,12 +805,11 @@ private:
void OpenXRInputManager::pointActivation(bool onPress)
{
auto* world = MWBase::Environment::get().getWorld();
if (world)
auto* anim = MWVR::Environment::get().getPlayerAnimation();
if (world && anim && anim->mPointerTarget.mHit)
{
MWRender::RayResult pointedAt;
world->getPointedAtObject(pointedAt);
auto* node = pointedAt.mHitNode;
MWWorld::Ptr ptr = pointedAt.mHitObject;
auto* node = anim->mPointerTarget.mHitNode;
MWWorld::Ptr ptr = anim->mPointerTarget.mHitObject;
auto& dnd = MWBase::Environment::get().getWindowManager()->getDragAndDrop();
if (node && node->getName() == "XR Menu Geometry")
@ -894,17 +894,16 @@ private:
mXRInput->updateControls();
auto* world = MWBase::Environment::get().getWorld();
if (world)
auto* anim = MWVR::Environment::get().getPlayerAnimation();
if (world && anim && anim->mPointerTarget.mHit)
{
MWRender::RayResult pointedAt;
world->getPointedAtObject(pointedAt);
auto* node = pointedAt.mHitNode;
auto* node = anim->mPointerTarget.mHitNode;
if (node)
{
int w, h;
SDL_GetWindowSize(mWindow, &w, &h);
osg::Vec3 local = pointedAt.mHitPointLocal;
osg::Vec3 local = anim->mPointerTarget.mHitPointLocal;
local.x() = (local.x() + 1.f) / 2.f;
local.y() = 1.f - (local.y() + 1.f) / 2.f;
@ -915,6 +914,8 @@ private:
}
}
OpenXRActionEvent event{};
while (mXRInput->nextActionEvent(event))
{
@ -926,17 +927,25 @@ private:
MWInput::InputManager::update(dt, disableControls, disableEvents);
bool guiMode = MWBase::Environment::get().getWindowManager()->isGuiMode();
auto* xrMenuManager = OpenXREnvironment::get().getMenuManager();
auto* xrMenuManager = Environment::get().getMenuManager();
if(xrMenuManager)
xrMenuManager->showMenus(guiMode);
setPlayerControlsEnabled(!guiMode);
if (mPlayer)
{
auto player = mPlayer->getPlayer();
if (!mRealisticCombat || mRealisticCombat->ptr != player)
mRealisticCombat.reset(new RealisticCombat::StateMachine(player));
mRealisticCombat->update(dt, !guiMode);
}
}
void OpenXRInputManager::processEvent(const OpenXRActionEvent& event)
{
//auto* session = OpenXREnvironment::get().getSession();
auto* xrMenuManager = OpenXREnvironment::get().getMenuManager();
//auto* session = Environment::get().getSession();
auto* xrMenuManager = Environment::get().getMenuManager();
switch (event.action)
{
case A_GameMenu:
@ -1101,11 +1110,11 @@ private:
MWBase::World* world = MWBase::Environment::get().getWorld();
auto player = mPlayer->getPlayer();
auto* xr = OpenXREnvironment::get().getManager();
auto* session = OpenXREnvironment::get().getSession();
auto* xr = Environment::get().getManager();
auto* session = Environment::get().getSession();
auto currentHeadPose = session->predictedPoses().head[(int)TrackedSpace::STAGE];
xr->playerScale(currentHeadPose);
currentHeadPose.position *= OpenXREnvironment::get().unitsPerMeter();
currentHeadPose.position *= Environment::get().unitsPerMeter();
osg::Vec3 vrMovement = currentHeadPose.position - mPreviousHeadPose.position;
mPreviousHeadPose = currentHeadPose;

View file

@ -2,6 +2,7 @@
#define OPENXR_INPUT_MANAGER_HPP
#include "openxrviewer.hpp"
#include "realisticcombat.hpp"
#include "../mwinput/inputmanagerimp.hpp"
#include <vector>
@ -47,6 +48,7 @@ namespace MWVR
void pointActivation(bool onPress);
std::unique_ptr<OpenXRInput> mXRInput;
std::unique_ptr<RealisticCombat::StateMachine> mRealisticCombat;
Pose mPreviousHeadPose{};
osg::Vec3 mHeadOffset{ 0,0,0 };
bool mRecenter{ true };

View file

@ -1,4 +1,4 @@
#include "openxrenvironment.hpp"
#include "vrenvironment.hpp"
#include "openxrmanager.hpp"
#include "openxrmanagerimpl.hpp"
#include "../mwinput/inputmanagerimp.hpp"
@ -199,14 +199,14 @@ namespace MWVR
OpenXRManager::RealizeOperation::operator()(
osg::GraphicsContext* gc)
{
auto* xr = OpenXREnvironment::get().getManager();
auto* xr = Environment::get().getManager();
xr->realize(gc);
}
bool
OpenXRManager::RealizeOperation::realized()
{
auto* xr = OpenXREnvironment::get().getManager();
auto* xr = Environment::get().getManager();
return xr->realized();
}

View file

@ -1,8 +1,8 @@
#include "openxrmenu.hpp"
#include "openxrenvironment.hpp"
#include "vrenvironment.hpp"
#include "openxrsession.hpp"
#include "openxrmanagerimpl.hpp"
#include "openxranimation.hpp"
#include "vranimation.hpp"
#include <openxr/openxr.h>
#include <osg/Texture2D>
#include <osg/ClipNode>
@ -122,7 +122,7 @@ private:
osg::ref_ptr<osg::Vec3Array> normals{ new osg::Vec3Array(1) };
// Units are divided by 2 because geometry has an extent of 2 (-1 to 1)
auto extent_units = extent_meters * OpenXREnvironment::get().unitsPerMeter() / 2.f;
auto extent_units = extent_meters * Environment::get().unitsPerMeter() / 2.f;
// Define the menu quad
osg::Vec3 top_left (-1, 1, 1);
@ -164,7 +164,7 @@ private:
mTransform->setAttitude(pose.orientation);
mTransform->setPosition(pose.position);
mTransform->addChild(mGeometry);
//mTransform->addChild(OpenXRAnimation::createPointerGeometry());
//mTransform->addChild(VRAnimation::createPointerGeometry());
// Add to scene graph
mGeometryRoot->addChild(mTransform);
@ -295,7 +295,7 @@ private:
// Position the menu about two thirds of a meter in front of the player
osg::Vec3 dir = center - eye;
dir.normalize();
mPose.position = eye + dir * OpenXREnvironment::get().unitsPerMeter() * 2.f / 3.f;
mPose.position = eye + dir * Environment::get().unitsPerMeter() * 2.f / 3.f;
mPose.orientation = camera->getViewMatrix().getRotate().inverse();

View file

@ -1,4 +1,4 @@
#include "openxrenvironment.hpp"
#include "vrenvironment.hpp"
#include "openxrmanager.hpp"
#include "openxrmanagerimpl.hpp"
#include "openxrinputmanager.hpp"
@ -46,7 +46,7 @@ namespace MWVR
{
Timer timer("OpenXRSession::SwapBuffers");
auto* xr = OpenXREnvironment::get().getManager();
auto* xr = Environment::get().getManager();
if (!xr->sessionRunning())
return;
@ -64,7 +64,7 @@ namespace MWVR
void OpenXRSession::waitFrame()
{
auto* xr = OpenXREnvironment::get().getManager();
auto* xr = Environment::get().getManager();
xr->handleEvents();
if (!xr->sessionRunning())
return;
@ -128,8 +128,8 @@ namespace MWVR
void OpenXRSession::predictNext(int extraPeriods)
{
auto* xr = OpenXREnvironment::get().getManager();
auto* input = OpenXREnvironment::get().getInputManager();
auto* xr = Environment::get().getManager();
auto* input = Environment::get().getInputManager();
auto mPredictedDisplayTime = xr->impl().frameState().predictedDisplayTime;
auto previousHeadPose = mPredictedPoses.head[(int)TrackedSpace::STAGE];

View file

@ -2,7 +2,7 @@
#include "openxrsession.hpp"
#include "openxrmanagerimpl.hpp"
#include "openxrinputmanager.hpp"
#include "openxrenvironment.hpp"
#include "vrenvironment.hpp"
#include "Windows.h"
#include "../mwrender/vismask.hpp"
#include "../mwmechanics/actorutil.hpp"
@ -64,7 +64,7 @@ namespace MWVR
void OpenXRViewer::traversals()
{
auto* xr = OpenXREnvironment::get().getManager();
auto* xr = Environment::get().getManager();
xr->handleEvents();
mViewer->updateTraversal();
mViewer->renderingTraversals();
@ -96,11 +96,11 @@ namespace MWVR
osg::Vec4 clearColor = mainCamera->getClearColor();
auto* xr = OpenXREnvironment::get().getManager();
auto* xr = Environment::get().getManager();
if (!xr->realized())
xr->realize(context);
auto* session = OpenXREnvironment::get().getSession();
auto* session = Environment::get().getSession();
OpenXRSwapchain::Config leftConfig;
leftConfig.width = xr->impl().mConfigViews[(int)Side::LEFT_HAND].recommendedImageRectWidth;
@ -186,7 +186,7 @@ namespace MWVR
if (includeMenu)
{
auto menuManager = OpenXREnvironment::get().getMenuManager();
auto menuManager = Environment::get().getMenuManager();
if (menuManager)
{
auto menu = menuManager->getMenu();
@ -234,7 +234,7 @@ namespace MWVR
OpenXRViewer::SwapBuffersCallback::swapBuffersImplementation(
osg::GraphicsContext* gc)
{
auto* session = OpenXREnvironment::get().getSession();
auto* session = Environment::get().getSession();
session->swapBuffers(gc);
}
@ -244,8 +244,8 @@ namespace MWVR
if (!mConfigured)
return;
auto* session = OpenXREnvironment::get().getSession();
auto* xr = OpenXREnvironment::get().getManager();
auto* session = Environment::get().getSession();
auto* xr = Environment::get().getManager();
Timer timer("OpenXRViewer::SwapBuffers");
@ -288,13 +288,13 @@ namespace MWVR
{
OpenXRManager::RealizeOperation::operator()(gc);
OpenXREnvironment::get().getViewer()->realize(gc);
Environment::get().getViewer()->realize(gc);
}
bool
OpenXRViewer::RealizeOperation::realized()
{
return OpenXREnvironment::get().getViewer()->realized();
return Environment::get().getViewer()->realized();
}
void OpenXRViewer::preDrawCallback(osg::RenderInfo& info)
@ -305,8 +305,7 @@ namespace MWVR
if (name == "LeftEye")
{
auto* xr = OpenXREnvironment::get().getManager();
auto* session = OpenXREnvironment::get().getSession();
auto* xr = Environment::get().getManager();
if (xr->sessionRunning())
{
xr->beginFrame();
@ -341,8 +340,8 @@ namespace MWVR
return;
}
auto* xr = OpenXREnvironment::get().getManager();
auto* session = OpenXREnvironment::get().getSession();
auto* xr = Environment::get().getManager();
auto* session = Environment::get().getSession();
auto& poses = session->predictedPoses();
auto handPosesStage = poses.hands[(int)TrackedSpace::STAGE];
int side = (int)Side::LEFT_HAND;
@ -357,14 +356,14 @@ namespace MWVR
xr->playerScale(headStage);
auto orientation = handStage.orientation;
auto position = handStage.position - headStage.position;
position = position * OpenXREnvironment::get().unitsPerMeter();
position = position * Environment::get().unitsPerMeter();
auto camera = mViewer->getCamera();
auto viewMatrix = camera->getViewMatrix();
// Align orientation with the game world
auto* inputManager = OpenXREnvironment::get().getInputManager();
auto* inputManager = Environment::get().getInputManager();
if (inputManager)
{
auto playerYaw = osg::Quat(-inputManager->mYaw, osg::Vec3d(0, 0, 1));
@ -399,7 +398,7 @@ namespace MWVR
if (hand_transform->getName() == "tracker r hand")
offcenter.z() *= -1.;
osg::Vec3 recenter = orientation * offcenter;
position = position + recenter * OpenXREnvironment::get().unitsPerMeter();
position = position + recenter * Environment::get().unitsPerMeter();
hand_transform->setAttitude(orientation);
hand_transform->setPosition(position);

View file

@ -1,5 +1,5 @@
#include "openxrworldview.hpp"
#include "openxrenvironment.hpp"
#include "vrenvironment.hpp"
#include "openxrmanager.hpp"
#include "openxrmanagerimpl.hpp"
#include "../mwinput/inputmanagerimp.hpp"
@ -100,7 +100,7 @@ namespace MWVR
{
MWVR::Pose pose = predictedPose();
mXR->playerScale(pose);
osg::Vec3 position = pose.position * OpenXREnvironment::get().unitsPerMeter();
osg::Vec3 position = pose.position * Environment::get().unitsPerMeter();
osg::Quat orientation = pose.orientation;
float y = position.y();
@ -166,7 +166,7 @@ namespace MWVR
// Updating the head pose needs to happen after waitFrame(),
// But i can't call waitFrame from the input manager since it might
// not always be active.
auto* inputManager = OpenXREnvironment::get().getInputManager();
auto* inputManager = Environment::get().getInputManager();
if (inputManager)
inputManager->updateHead();

View file

@ -0,0 +1,197 @@
#include "realisticcombat.hpp"
#include "../mwbase/environment.hpp"
#include "../mwbase/soundmanager.hpp"
namespace MWVR { namespace RealisticCombat {
StateMachine::StateMachine(MWWorld::Ptr ptr) : ptr(ptr) {}
// Actions common to all transitions
void StateMachine::transition(
SwingState newState)
{
if (newState == state)
throw std::logic_error("Cannot transition to current state");
maxSwingVelocity = 0.f;
timeSinceEnteredState = 0.f;
movementSinceEnteredState = 0.f;
state = newState;
}
void StateMachine::reset()
{
maxSwingVelocity = 0.f;
timeSinceEnteredState = 0.f;
velocity = 0.f;
previousPosition = osg::Vec3(0.f, 0.f, 0.f);
state = SwingState_Ready;
}
void StateMachine::update(float dt, bool enabled)
{
auto* session = Environment::get().getSession();
auto& handPose = session->predictedPoses().hands[(int)MWVR::TrackedSpace::STAGE][(int)MWVR::Side::RIGHT_HAND];
auto& headPose = session->predictedPoses().head[(int)MWVR::TrackedSpace::STAGE];
if (mEnabled != enabled)
{
reset();
mEnabled = enabled;
}
if (!enabled)
return;
timeSinceEnteredState += dt;
// First determine direction of different swing types
// Thrust is radially out from the head. Which is the same as the position of the hand relative to the head, ignoring height component
osg::Vec3 thrustDirection = handPose.position - headPose.position;
thrustDirection.z() = 0;
thrustDirection.normalize();
// Chop is straight down
osg::Vec3 chopDirection = osg::Vec3(0.f, 0.f, -1.f);
// Swing is normal to the plane created by Chop x Thrust
osg::Vec3 slashDirection = chopDirection ^ thrustDirection;
slashDirection.normalize();
// Next determine current hand movement
// If tracking is lost, openxr will return a position of 0
// Reset position when tracking is re-acquired
// Theoretically, the player's hand really could be at 0,0,0
// but that's a super rare case so whatever.
if (previousPosition == osg::Vec3(0.f, 0.f, 0.f))
previousPosition = handPose.position;
osg::Vec3 movement = handPose.position - previousPosition;
movementSinceEnteredState += movement.length();
previousPosition = handPose.position;
osg::Vec3 swingDirection = movement / dt;
// Compute swing velocity
// Unidirectional
thrustVelocity = swingDirection * thrustDirection;
// Bidirectional
slashVelocity = std::abs(swingDirection * slashDirection);
chopVelocity = std::abs(swingDirection * chopDirection);
// Pick swing type based on greatest current velocity
// Note i use abs() of thrust velocity to prevent accidentally triggering
// chop or slash when player is withdrawing his limb.
if (std::abs(thrustVelocity) > slashVelocity && std::abs(thrustVelocity) > chopVelocity)
{
velocity = thrustVelocity;
swingType = ESM::Weapon::AT_Thrust;
}
else if (slashVelocity > chopVelocity)
{
velocity = slashVelocity;
swingType = ESM::Weapon::AT_Slash;
}
else
{
velocity = chopVelocity;
swingType = ESM::Weapon::AT_Chop;
}
switch (state)
{
case SwingState_Ready:
return update_readyState();
case SwingState_Swinging:
return update_swingingState();
case SwingState_Impact:
return update_impactState();
}
}
void StateMachine::update_readyState()
{
if (velocity >= minVelocity)
if(timeSinceEnteredState >= minimumPeriod)
return transition_readyToSwinging();
}
void StateMachine::transition_readyToSwinging()
{
shouldSwish = true;
transition(SwingState_Swinging);
// As an exception, update the new state immediately to allow
// same-frame impacts.
update_swingingState();
}
void StateMachine::playSwish()
{
if (shouldSwish)
{
MWBase::SoundManager* sndMgr = MWBase::Environment::get().getSoundManager();
std::string sound = "Weapon Swish";
if (strength < 0.5f)
sndMgr->playSound3D(ptr, sound, 1.0f, 0.8f); //Weak attack
if (strength < 1.0f)
sndMgr->playSound3D(ptr, sound, 1.0f, 1.0f); //Medium attack
else
sndMgr->playSound3D(ptr, sound, 1.0f, 1.2f); //Strong attack
shouldSwish = false;
}
}
void StateMachine::update_swingingState()
{
maxSwingVelocity = std::max(velocity, maxSwingVelocity);
strength = std::min(1.f, (maxSwingVelocity - minVelocity) / maxVelocity);
if (velocity < minVelocity)
return transition_swingingToReady();
// Require a minimum period of swinging before a hit can be made
// This is to prevent annoying little microswings
if (movementSinceEnteredState > minimumPeriod)
{
playSwish();
// Note: calling hit with simulated=true to avoid side effects
if (ptr.getClass().hit(ptr, strength, swingType, true))
return transition_swingingToImpact();
}
}
void StateMachine::transition_swingingToReady()
{
if (movementSinceEnteredState > minimumPeriod)
{
playSwish();
ptr.getClass().hit(ptr, strength, swingType, false);
}
transition(SwingState_Ready);
}
void StateMachine::transition_swingingToImpact()
{
playSwish();
ptr.getClass().hit(ptr, strength, swingType, false);
transition(SwingState_Impact);
}
void StateMachine::update_impactState()
{
if (velocity < minVelocity)
return transition_impactToReady();
}
void StateMachine::transition_impactToReady()
{
transition(SwingState_Ready);
}
}}

View file

@ -0,0 +1,70 @@
#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 "openxrsession.hpp"
namespace MWVR { namespace RealisticCombat {
enum SwingState
{
SwingState_Ready = 0,
SwingState_Swinging = 1,
SwingState_Impact = 2
};
struct StateMachine
{
// TODO: These should be configurable
const float minVelocity = 1.f;
const float maxVelocity = 4.f;
float velocity = 0.f;
float maxSwingVelocity = 0.f;
SwingState state = SwingState_Ready;
MWWorld::Ptr ptr = MWWorld::Ptr();
int swingType = -1;
float strength = 0.f;
float thrustVelocity{ 0.f };
float slashVelocity{ 0.f };
float chopVelocity{ 0.f };
float minimumPeriod{ .5f };
float timeSinceEnteredState = { 0.f };
float movementSinceEnteredState = { 0.f };
bool shouldSwish = false;
bool mEnabled = false;
osg::Vec3 previousPosition{ 0.f,0.f,0.f };
StateMachine(MWWorld::Ptr ptr);
void playSwish();
void reset();
void transition(SwingState newState);
void update(float dt, bool enabled);
void update_readyState();
void transition_readyToSwinging();
void update_swingingState();
void transition_swingingToReady();
void transition_swingingToImpact();
void update_impactState();
void transition_impactToReady();
};
}}
#endif

View file

@ -1,4 +1,4 @@
#include "openxranimation.hpp"
#include "vranimation.hpp"
#include <osg/UserDataContainer>
#include <osg/MatrixTransform>
@ -47,6 +47,7 @@
#include "../mwbase/world.hpp"
#include "../mwbase/mechanicsmanager.hpp"
#include "../mwbase/soundmanager.hpp"
#include "../mwbase/windowmanager.hpp"
#include "../mwrender/camera.hpp"
#include "../mwrender/rotatecontroller.hpp"
@ -58,7 +59,7 @@
#include "../mwphysics/collisiontype.hpp"
#include "../mwphysics/physicssystem.hpp"
#include "openxrenvironment.hpp"
#include "vrenvironment.hpp"
#include "openxrviewer.hpp"
#include "openxrinputmanager.hpp"
@ -177,30 +178,13 @@ void FingerController::operator()(osg::Node* node, osg::NodeVisitor* nv)
// So that we can display a beam to visualize where the player is pointing.
// Dig up the pointer transform
SceneUtil::FindByNameVisitor findPointerVisitor("Pointer Transform", osg::NodeVisitor::TRAVERSE_ALL_CHILDREN);
tip->accept(findPointerVisitor);
auto pointerTransform = findPointerVisitor.mFoundNode;
if (pointerTransform)
{
// Get distance to pointer intersection
auto pat = pointerTransform->asTransform()->asPositionAttitudeTransform();
auto* world = MWBase::Environment::get().getWorld();
if (world)
{
// TODO: Using the cached value from the input manager makes this off by one frame
// So do one otherwise redundant intersection here.
MWRender::RayResult result;
world->getPointedAtObject(result);
float intersected_distance = world->getDistanceToPointedAtObject();
// Stretch beam to point of intersection.
pat->setScale(osg::Vec3(.25f, .25f, intersected_distance));
}
}
auto* anim = MWVR::Environment::get().getPlayerAnimation();
if (anim)
anim->updatePointerTarget();
}
OpenXRAnimation::OpenXRAnimation(
VRAnimation::VRAnimation(
const MWWorld::Ptr& ptr, osg::ref_ptr<osg::Group> parentNode, Resource::ResourceSystem* resourceSystem,
bool disableSounds, std::shared_ptr<OpenXRSession> xrSession)
// Note that i let it construct as 3rd person and then later update it to VM_VRHeadless
@ -218,17 +202,17 @@ OpenXRAnimation::OpenXRAnimation(
createPointer();
}
OpenXRAnimation::~OpenXRAnimation() {};
VRAnimation::~VRAnimation() {};
void OpenXRAnimation::setViewMode(NpcAnimation::ViewMode viewMode)
void VRAnimation::setViewMode(NpcAnimation::ViewMode viewMode)
{
if (viewMode != VM_VRHeadless)
Log(Debug::Warning) << "View mode of OpenXRAnimation may only be VM_VRHeadless";
Log(Debug::Warning) << "View mode of VRAnimation may only be VM_VRHeadless";
NpcAnimation::setViewMode(VM_VRHeadless);
return;
}
void OpenXRAnimation::updateParts()
void VRAnimation::updateParts()
{
NpcAnimation::updateParts();
@ -252,9 +236,10 @@ void OpenXRAnimation::updateParts()
removeIndividualPart(ESM::PartReferenceType::PRT_LAnkle);
removeIndividualPart(ESM::PartReferenceType::PRT_RAnkle);
}
void OpenXRAnimation::setPointForward(bool enabled)
void VRAnimation::setPointForward(bool enabled)
{
auto found00 = mNodeMap.find("bip01 r finger1");
//auto weapon = mNodeMap.find("weapon");
if (found00 != mNodeMap.end())
{
auto base_joint = found00->second;
@ -262,27 +247,50 @@ void OpenXRAnimation::setPointForward(bool enabled)
assert(second_joint);
second_joint->removeChild(mPointerTransform);
//weapon->second->removeChild(mWeaponDirectionTransform);
mWeaponDirectionTransform->removeChild(mPointerGeometry);
base_joint->removeUpdateCallback(mIndexFingerControllers[0]);
if (enabled)
{
second_joint->addChild(mPointerTransform);
//weapon->second->addChild(mWeaponDirectionTransform);
mWeaponDirectionTransform->addChild(mPointerGeometry);
base_joint->addUpdateCallback(mIndexFingerControllers[0]);
}
}
}
void OpenXRAnimation::createPointer(void)
void VRAnimation::createPointer(void)
{
mPointerGeometry = createPointerGeometry();
mPointerTransform = new osg::PositionAttitudeTransform();
mPointerTransform = new osg::MatrixTransform();
mPointerTransform->addChild(mPointerGeometry);
mPointerTransform->asPositionAttitudeTransform()->setAttitude(osg::Quat(osg::DegreesToRadians(90.f), osg::Vec3(0.f, 1.f, 0.f)));
mPointerTransform->asPositionAttitudeTransform()->setPosition(osg::Vec3(0.f, 0.f, 0.f));
mPointerTransform->asPositionAttitudeTransform()->setScale(osg::Vec3(1.f, 1.f, 1.f));
mPointerTransform->setName("Pointer Transform");
mWeaponPointerTransform = new osg::MatrixTransform();
mWeaponPointerTransform->addChild(mPointerGeometry);
mWeaponPointerTransform->setMatrix(
osg::Matrix::scale(64.f, 1.f, 1.f)
);
mWeaponPointerTransform->setName("Weapon Pointer");
mWeaponDirectionTransform = new osg::MatrixTransform();
mWeaponDirectionTransform->addChild(mWeaponPointerTransform);
mWeaponDirectionTransform->setMatrix(
osg::Matrix::rotate(osg::DegreesToRadians(-90.f), osg::Y_AXIS)
);
mWeaponDirectionTransform->setName("Weapon Direction");
mWeaponAdjustment = new osg::MatrixTransform();
//mWeaponAdjustment->addChild(mWeaponPointerTransform);
mWeaponAdjustment->setMatrix(
osg::Matrix::rotate(osg::DegreesToRadians(90.f), osg::Y_AXIS)
);
mWeaponAdjustment->setName("Weapon Adjustment");
}
osg::ref_ptr<osg::Geometry> OpenXRAnimation::createPointerGeometry(void)
osg::ref_ptr<osg::Geometry> VRAnimation::createPointerGeometry(void)
{
osg::ref_ptr<osg::Geometry> geometry = new osg::Geometry();
@ -291,8 +299,8 @@ osg::ref_ptr<osg::Geometry> OpenXRAnimation::createPointerGeometry(void)
osg::Vec3 vertices[]{
{0, 0, 0}, // origin
{-1, 1, 1}, // top_left
{-1, -1, 1}, // bottom_left
{1, 1, -1}, // top_left
{1, -1, -1}, // bottom_left
{1, -1, 1}, // bottom_right
{1, 1, 1}, // top_right
};
@ -350,12 +358,12 @@ osg::ref_ptr<osg::Geometry> OpenXRAnimation::createPointerGeometry(void)
return geometry;
}
osg::Vec3f OpenXRAnimation::runAnimation(float timepassed)
osg::Vec3f VRAnimation::runAnimation(float timepassed)
{
return NpcAnimation::runAnimation(timepassed);
}
void OpenXRAnimation::addControllers()
void VRAnimation::addControllers()
{
NpcAnimation::addControllers();
@ -402,14 +410,58 @@ void OpenXRAnimation::addControllers()
group->removeChildren(0, parent->getNumChildren());
group->addChild(mModelOffset);
mModelOffset->addChild(mObjectRoot);
auto weapon = mNodeMap.find("weapon");
weapon->second->addChild(mWeaponDirectionTransform);
}
}
void OpenXRAnimation::enableHeadAnimation(bool)
void VRAnimation::enableHeadAnimation(bool)
{
NpcAnimation::enableHeadAnimation(false);
}
void OpenXRAnimation::setAccurateAiming(bool)
void VRAnimation::setAccurateAiming(bool)
{
NpcAnimation::setAccurateAiming(false);
}
bool VRAnimation::canPlaceObject()
{
const float maxDist = 200.f;
if (mPointerTarget.mHit)
{
// check if the wanted position is on a flat surface, and not e.g. against a vertical wall
if (std::acos((mPointerTarget.mHitNormalWorld / mPointerTarget.mHitNormalWorld.length()) * osg::Vec3f(0, 0, 1)) >= osg::DegreesToRadians(30.f))
return false;
return true;
}
return false;
}
const MWRender::RayResult& VRAnimation::getPointerTarget() const
{
return mPointerTarget;
}
void VRAnimation::updatePointerTarget()
{
auto* world = MWBase::Environment::get().getWorld();
if (world)
{
mPointerTransform->setMatrix(osg::Matrix::scale(1, 1, 1));
mDistanceToPointerTarget = world->getTargetObject(mPointerTarget, mPointerTransform);
if(mDistanceToPointerTarget >= 0)
mPointerTransform->setMatrix(osg::Matrix::scale(mDistanceToPointerTarget, 0.25f, 0.25f));
else
mPointerTransform->setMatrix(osg::Matrix::scale(10000.f, 0.25f, 0.25f));
}
}
}

View file

@ -1,7 +1,8 @@
#ifndef MWVR_OPENRXANIMATION_H
#define MWVR_OPENRXANIMATION_H
#ifndef MWVR_VRANIMATION_H
#define MWVR_VRANIMATION_H
#include "../mwrender/npcanimation.hpp"
#include "../mwrender/renderingmanager.hpp"
#include "openxrmanager.hpp"
#include "openxrsession.hpp"
@ -13,7 +14,7 @@ class FingerController;
class ForearmController;
/// Subclassing NpcAnimation to override behaviours not compatible with VR
class OpenXRAnimation : public MWRender::NpcAnimation
class VRAnimation : public MWRender::NpcAnimation
{
protected:
virtual void addControllers();
@ -28,9 +29,9 @@ public:
* @param disableSounds Same as \a disableListener but for playing items sounds
* @param xrSession The XR session that shall be used to track limbs
*/
OpenXRAnimation(const MWWorld::Ptr& ptr, osg::ref_ptr<osg::Group> parentNode, Resource::ResourceSystem* resourceSystem,
VRAnimation(const MWWorld::Ptr& ptr, osg::ref_ptr<osg::Group> parentNode, Resource::ResourceSystem* resourceSystem,
bool disableSounds, std::shared_ptr<OpenXRSession> xrSession );
virtual ~OpenXRAnimation();
virtual ~VRAnimation();
/// Overridden to always be false
virtual void enableHeadAnimation(bool enable);
@ -55,19 +56,31 @@ public:
/// (Used to visualize direction of activation action)
void setPointForward(bool enabled);
bool canPlaceObject();
///< @return true if it is possible to place on object where the player is currently pointing
const MWRender::RayResult& getPointerTarget() const;
///< @return pointer to the object the player's melee weapon is currently intersecting.
void updatePointerTarget();
public:
void createPointer(void);
static osg::ref_ptr<osg::Geometry> createPointerGeometry(void);
private:
public:
std::shared_ptr<OpenXRSession> mSession;
ForearmController* mForearmControllers[2]{};
HandController* mHandControllers[2]{};
osg::ref_ptr<FingerController> mIndexFingerControllers[2];
osg::ref_ptr<osg::MatrixTransform> mModelOffset;
osg::ref_ptr<osg::Geometry> mPointerGeometry{ nullptr };
osg::ref_ptr<osg::Transform> mPointerTransform{ nullptr };
osg::ref_ptr<osg::MatrixTransform> mPointerTransform{ nullptr };
osg::ref_ptr<osg::MatrixTransform> mWeaponAdjustment{ nullptr };
osg::ref_ptr<osg::MatrixTransform> mWeaponDirectionTransform{ nullptr };
osg::ref_ptr<osg::MatrixTransform> mWeaponPointerTransform{ nullptr };
MWRender::RayResult mPointerTarget{};
float mDistanceToPointerTarget{ -1.f };
};
}

View file

@ -1,30 +1,30 @@
#include "openxrenvironment.hpp"
#include "vrenvironment.hpp"
#include <cassert>
#include "openxranimation.hpp"
#include "vranimation.hpp"
#include "openxrinputmanager.hpp"
#include "openxrsession.hpp"
#include "openxrmenu.hpp"
#include "../mwbase/environment.hpp"
MWVR::OpenXREnvironment *MWVR::OpenXREnvironment::sThis = 0;
MWVR::Environment *MWVR::Environment::sThis = 0;
MWVR::OpenXREnvironment::OpenXREnvironment()
MWVR::Environment::Environment()
: mSession(nullptr)
{
assert (!sThis);
sThis = this;
}
MWVR::OpenXREnvironment::~OpenXREnvironment()
MWVR::Environment::~Environment()
{
cleanup();
sThis = 0;
}
void MWVR::OpenXREnvironment::cleanup()
void MWVR::Environment::cleanup()
{
if (mSession)
delete mSession;
@ -43,13 +43,13 @@ void MWVR::OpenXREnvironment::cleanup()
mOpenXRManager = nullptr;
}
MWVR::OpenXREnvironment& MWVR::OpenXREnvironment::get()
MWVR::Environment& MWVR::Environment::get()
{
assert (sThis);
return *sThis;
}
MWVR::OpenXRInputManager* MWVR::OpenXREnvironment::getInputManager() const
MWVR::OpenXRInputManager* MWVR::Environment::getInputManager() const
{
auto* inputManager = MWBase::Environment::get().getInputManager();
assert(inputManager);
@ -58,63 +58,63 @@ MWVR::OpenXRInputManager* MWVR::OpenXREnvironment::getInputManager() const
return xrInputManager;
}
MWVR::OpenXRSession* MWVR::OpenXREnvironment::getSession() const
MWVR::OpenXRSession* MWVR::Environment::getSession() const
{
return mSession;
}
void MWVR::OpenXREnvironment::setSession(MWVR::OpenXRSession* xrSession)
void MWVR::Environment::setSession(MWVR::OpenXRSession* xrSession)
{
mSession = xrSession;
}
MWVR::OpenXRMenuManager* MWVR::OpenXREnvironment::getMenuManager() const
MWVR::OpenXRMenuManager* MWVR::Environment::getMenuManager() const
{
return mMenuManager;
}
void MWVR::OpenXREnvironment::setMenuManager(MWVR::OpenXRMenuManager* menuManager)
void MWVR::Environment::setMenuManager(MWVR::OpenXRMenuManager* menuManager)
{
mMenuManager = menuManager;
}
MWVR::OpenXRAnimation* MWVR::OpenXREnvironment::getPlayerAnimation() const
MWVR::VRAnimation* MWVR::Environment::getPlayerAnimation() const
{
return mPlayerAnimation;
}
void MWVR::OpenXREnvironment::setPlayerAnimation(MWVR::OpenXRAnimation* xrAnimation)
void MWVR::Environment::setPlayerAnimation(MWVR::VRAnimation* xrAnimation)
{
mPlayerAnimation = xrAnimation;
}
MWVR::OpenXRViewer* MWVR::OpenXREnvironment::getViewer() const
MWVR::OpenXRViewer* MWVR::Environment::getViewer() const
{
return mViewer;
}
void MWVR::OpenXREnvironment::setViewer(MWVR::OpenXRViewer* xrViewer)
void MWVR::Environment::setViewer(MWVR::OpenXRViewer* xrViewer)
{
mViewer = xrViewer;
}
MWVR::OpenXRManager* MWVR::OpenXREnvironment::getManager() const
MWVR::OpenXRManager* MWVR::Environment::getManager() const
{
return mOpenXRManager;
}
void MWVR::OpenXREnvironment::setManager(MWVR::OpenXRManager* xrManager)
void MWVR::Environment::setManager(MWVR::OpenXRManager* xrManager)
{
mOpenXRManager = xrManager;
}
float MWVR::OpenXREnvironment::unitsPerMeter() const
float MWVR::Environment::unitsPerMeter() const
{
return mUnitsPerMeter;
}
void MWVR::OpenXREnvironment::setUnitsPerMeter(float unitsPerMeter)
void MWVR::Environment::setUnitsPerMeter(float unitsPerMeter)
{
mUnitsPerMeter = unitsPerMeter;
}

View file

@ -1,9 +1,9 @@
#ifndef OPENXR_ENVIRONMENT_H
#define OPENXR_ENVIRONMENT_H
#ifndef MWVR_ENVIRONMENT_H
#define MWVR_ENVIRONMENT_H
namespace MWVR
{
class OpenXRAnimation;
class VRAnimation;
class OpenXRInputManager;
class OpenXRSession;
class OpenXRMenuManager;
@ -14,29 +14,29 @@ namespace MWVR
///
/// This class allows each mw openxr subsystem to access any others subsystem's top-level manager class.
///
/// \attention OpenXREnvironment takes ownership of the manager class instances it is handed over in
/// \attention Environment takes ownership of the manager class instances it is handed over in
/// the set* functions.
class OpenXREnvironment
class Environment
{
static OpenXREnvironment*sThis;
static Environment* sThis;
OpenXREnvironment(const OpenXREnvironment&) = delete;
Environment(const Environment&) = delete;
///< not implemented
OpenXREnvironment& operator= (const OpenXREnvironment&) = delete;
Environment& operator= (const Environment&) = delete;
///< not implemented
public:
OpenXREnvironment();
Environment();
~OpenXREnvironment();
~Environment();
void cleanup();
///< Delete all mwvr-subsystems.
static OpenXREnvironment& get();
static Environment& get();
///< Return instance of this class.
MWVR::OpenXRInputManager* getInputManager() const;
@ -48,8 +48,8 @@ namespace MWVR
MWVR::OpenXRMenuManager* getMenuManager() const;
void setMenuManager(MWVR::OpenXRMenuManager* xrMenuManager);
MWVR::OpenXRAnimation* getPlayerAnimation() const;
void setPlayerAnimation(MWVR::OpenXRAnimation* xrAnimation);
MWVR::VRAnimation* getPlayerAnimation() const;
void setPlayerAnimation(MWVR::VRAnimation* xrAnimation);
MWVR::OpenXRSession* getSession() const;
void setSession(MWVR::OpenXRSession* xrSession);
@ -66,7 +66,7 @@ namespace MWVR
private:
MWVR::OpenXRSession* mSession{ nullptr };
MWVR::OpenXRMenuManager* mMenuManager{ nullptr };
MWVR::OpenXRAnimation* mPlayerAnimation{ nullptr };
MWVR::VRAnimation* mPlayerAnimation{ nullptr };
MWVR::OpenXRViewer* mViewer{ nullptr };
MWVR::OpenXRManager* mOpenXRManager{ nullptr };
float mUnitsPerMeter{ 1.f };

View file

@ -99,7 +99,7 @@ namespace MWWorld
throw std::runtime_error ("class does not have item health");
}
void Class::hit(const Ptr& ptr, float attackStrength, int type) const
bool Class::hit(const Ptr& ptr, float attackStrength, int type, bool simulated) const
{
throw std::runtime_error("class cannot hit");
}

View file

@ -121,12 +121,14 @@ namespace MWWorld
///< Return item max health or throw an exception, if class does not have item health
/// (default implementation: throw an exception)
virtual void hit(const Ptr& ptr, float attackStrength, int type=-1) const;
virtual bool hit(const Ptr& ptr, float attackStrength, int type, bool simulated = false ) const;
///< Execute a melee hit, using the current weapon. This will check the relevant skills
/// of the given attacker, and whoever is hit.
/// \param attackStrength how long the attack was charged for, a value in 0-1 range.
/// \param type - type of attack, one of the MWMechanics::CreatureStats::AttackType
/// enums. ignored for creature attacks.
/// \param simulated - If true, this function will only check if a hit would be made, and have no side effects. This parameter has no effect for Creature classes.
/// @return True if the attack had a victim, regardless if hit was successful or not.
/// (default implementation: throw an exception)
virtual 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;

View file

@ -70,6 +70,11 @@
#include "contentloader.hpp"
#include "esmloader.hpp"
#ifdef USE_OPENXR
#include "../mwvr/vrenvironment.hpp"
#include "../mwvr/vranimation.hpp"
#endif
namespace
{
@ -1146,15 +1151,29 @@ namespace MWWorld
// the origin of hitbox is an actor's front, not center
distance += halfExtents.y();
// special cased for better aiming with the camera
// if we do not hit anything, will use the default approach as fallback
if (ptr == getPlayerPtr())
{
#ifdef USE_OPENXR
// TODO: Configurable realistic fighting.
// Use current aim of weapon to impact
// TODO: Use bounding box of weapon instead ?
auto* anim = MWVR::Environment::get().getPlayerAnimation();
osg::Matrix worldMatrix = osg::computeLocalToWorld(anim->mWeaponDirectionTransform->getParentalNodePaths()[0]);
auto result = mPhysics->getHitContact(ptr, worldMatrix.getTrans(), worldMatrix.getRotate(), distance, targets);
if (!result.first.isEmpty())
Log(Debug::Verbose) << "Hit: " << result.first.getTypeName();
return result;
#else
// special cased for better aiming with the camera
// if we do not hit anything, will use the default approach as fallback
osg::Vec3f pos = getActorHeadTransform(ptr).getTrans();
std::pair<MWWorld::Ptr,osg::Vec3f> result = mPhysics->getHitContact(ptr, pos, rot, distance, targets);
if(!result.first.isEmpty())
return std::make_pair(result.first, result.second);
#endif
}
osg::Vec3f pos = ptr.getRefData().getPosition().asVec3();
@ -3916,121 +3935,69 @@ namespace MWWorld
return btRayAabb(localFrom, localTo, aabbMin, aabbMax, hitDistance, hitNormal);
}
#ifdef USE_OPENXR
void World::getPointedAtObject(MWRender::RayResult& result)
float World::getTargetObject(MWRender::RayResult& result, osg::Transform* pointer)
{
result = {};
result.mHit = false;
if (MWBase::Environment::get().getWindowManager()->isGuiMode() &&
MWBase::Environment::get().getWindowManager()->isConsoleMode())
auto* windowManager = MWBase::Environment::get().getWindowManager();
if (windowManager->isGuiMode() && windowManager->isConsoleMode())
{
return getPointedAtObject(result, getMaxActivationDistance() * 50, false);
return getTargetObject(result, pointer, getMaxActivationDistance() * 50, false);
}
else
{
MWRender::RayResult pointedAtObject;
getPointedAtObject(pointedAtObject, getActivationDistancePlusTelekinesis(), true);
auto ptr = pointedAtObject.mHitObject;
float maxDistance = getActivationDistancePlusTelekinesis();
MWRender::RayResult rayToObject{};
float distance = getTargetObject(rayToObject, pointer, maxDistance, true);
auto ptr = rayToObject.mHitObject;
if (!ptr.isEmpty() && !ptr.getClass().allowTelekinesis(ptr)
&& mDistanceToFacedObject > getMaxActivationDistance() && !MWBase::Environment::get().getWindowManager()->isGuiMode())
return;
result = pointedAtObject;
}
}
float World::getDistanceToPointedAtObject()
{
return mDistanceToPointedAtObject;
}
void World::getPointedAtObject(MWRender::RayResult& result, float maxDistance, bool ignorePlayer)
{
auto sceneRoot = mRendering->getLightRoot();
result = {};
result.mHit = false;
// Find the transform giving the finger's pointing direction.
SceneUtil::FindByNameVisitor findPointerVisitor("Pointer Transform", osg::NodeVisitor::TRAVERSE_ALL_CHILDREN);
sceneRoot->accept(findPointerVisitor);
auto pointerTransform = findPointerVisitor.mFoundNode;
if (pointerTransform)
{
auto pat = pointerTransform->asTransform()->asPositionAttitudeTransform();
// Discover the world position of the finger
// (This is actually the base of the last joint bone but so long as it is pointing forward it serves the same purpose).
osg::Matrix worldMatrix = osg::computeLocalToWorld(pointerTransform->getParentalNodePaths()[0]);
pat->computeLocalToWorldMatrix(worldMatrix, nullptr);
osg::Vec3 translate;
osg::Quat rotation;
osg::Vec3 scale;
osg::Quat scaleRotation;
worldMatrix.decompose(translate, rotation, scale, scaleRotation);
osg::Vec3f direction = rotation * osg::Vec3f(-1, 0, 0);
direction.normalize();
osg::Vec3f raySource = translate;
osg::Vec3f rayTarget = translate + direction * maxDistance;
auto rayToObject = mRendering->castRay(raySource, rayTarget, ignorePlayer, false);
if (rayToObject.mHit)
mDistanceToPointedAtObject = (rayToObject.mHitPointWorld - raySource).length();
else
// Leave a very large but not too large number to permit visualizing a beam going into "infinity"
mDistanceToPointedAtObject = 10000.f;
&& distance > getMaxActivationDistance() && !MWBase::Environment::get().getWindowManager()->isGuiMode())
return -1.f;
result = rayToObject;
return distance;
}
return -1.f;
}
MWWorld::Ptr World::placeObject(const MWWorld::ConstPtr& object, int amount)
float World::getTargetObject(MWRender::RayResult& result, osg::Transform* pointer, float maxDistance, bool ignorePlayer)
{
result = mRendering->castRay(pointer, maxDistance, ignorePlayer, false);
if(result.mHit)
return result.mRatio * maxDistance;
return -1.f;
}
MWWorld::Ptr World::placeObject(const MWWorld::ConstPtr& object, const MWRender::RayResult& ray, int amount)
{
const float maxDist = 200.f;
MWRender::RayResult pointedAt;
getPointedAtObject(pointedAt);
CellStore* cell = getPlayerPtr().getCell();
ESM::Position pos = getPlayerPtr().getRefData().getPosition();
if (pointedAt.mHit)
if (ray.mHit && !ray.mHitObject.isEmpty())
{
pos.pos[0] = pointedAt.mHitPointWorld.x();
pos.pos[1] = pointedAt.mHitPointWorld.y();
pos.pos[2] = pointedAt.mHitPointWorld.z();
pos.pos[0] = ray.mHitPointWorld.x();
pos.pos[1] = ray.mHitPointWorld.y();
pos.pos[2] = ray.mHitPointWorld.z();
}
// We want only the Z part of the player's rotation
// TODO: Use the hand's orientation?
// TODO: Use the hand to orient in VR?
pos.rot[0] = 0;
pos.rot[1] = 0;
// copy the object and set its count
Ptr dropped = copyObjectToCell(object, cell, pos, amount, true);
MWWorld::Ptr dropped = copyObjectToCell(object, cell, pos, amount, true);
// only the player place items in the world, so no need to check actor
PCDropped(dropped);
return dropped;
}
bool World::canPlaceObject()
MWPhysics::PhysicsSystem* World::getPhysicsSystem(void)
{
const float maxDist = 200.f;
MWRender::RayResult pointedAt;
getPointedAtObject(pointedAt);
if (pointedAt.mHit)
{
// check if the wanted position is on a flat surface, and not e.g. against a vertical wall
if (std::acos((pointedAt.mHitNormalWorld / pointedAt.mHitNormalWorld.length()) * osg::Vec3f(0, 0, 1)) >= osg::DegreesToRadians(30.f))
return false;
return true;
}
else
return false;
return mPhysics.get();
}
#endif
}

View file

@ -8,6 +8,8 @@
#include "../mwbase/world.hpp"
#include "../mwrender/renderingmanager.hpp"
#include "ptr.hpp"
#include "scene.hpp"
#include "esmstore.hpp"
@ -729,24 +731,20 @@ namespace MWWorld
bool hasCollisionWithDoor(const MWWorld::ConstPtr& door, const osg::Vec3f& position, const osg::Vec3f& destination) const override;
#ifdef USE_OPENXR
private:
float mDistanceToPointedAtObject;
void getPointedAtObject(MWRender::RayResult& result, float maxDistance, bool ignorePlayer = true);
public:
void getPointedAtObject(MWRender::RayResult& result) override;
///< Return pointer to the object and/or node the player is currently pointing at
float getDistanceToPointedAtObject() override;
///< Return the distance to the object and/or node the player is currently pointing at
/// @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
float getTargetObject(MWRender::RayResult& result, osg::Transform* pointer) override;
float getTargetObject(MWRender::RayResult& result, osg::Transform* pointer, float maxDistance, bool ignorePlayer) override;
MWWorld::Ptr placeObject(const MWWorld::ConstPtr& object, int amount) override;
///< copy and place an object into the gameworld where the player is currently pointing
MWWorld::Ptr placeObject(const MWWorld::ConstPtr& object, const MWRender::RayResult& ray, int amount) override;
///< 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
bool canPlaceObject() override;
///< @return true if it is possible to place on object where the player is currently pointing
#endif
MWPhysics::PhysicsSystem* getPhysicsSystem(void) override;
};
}