1
0
Fork 1
mirror of https://github.com/TES3MP/openmw-tes3mp.git synced 2025-10-04 19:56:30 +00:00

Merge remote-tracking branch 'remotes/origin/multiview_test_branch' into openxr_vr_geometryshader_feature_branch

This commit is contained in:
Mads Buvik Sandvei 2020-12-09 21:11:53 +01:00
commit b98b4db4c1
39 changed files with 1065 additions and 577 deletions

View file

@ -8,6 +8,7 @@
Bug #2473: Unable to overstock merchants Bug #2473: Unable to overstock merchants
Bug #2798: Mutable ESM records Bug #2798: Mutable ESM records
Bug #2976 [reopened]: Issues combining settings from the command line and both config files Bug #2976 [reopened]: Issues combining settings from the command line and both config files
Bug #3372: Projectiles and magic bolts go through moving targets
Bug #3676: NiParticleColorModifier isn't applied properly Bug #3676: NiParticleColorModifier isn't applied properly
Bug #3714: Savegame fails to load due to conflict between SpellState and MagicEffects Bug #3714: Savegame fails to load due to conflict between SpellState and MagicEffects
Bug #3789: Crash in visitEffectSources while in battle Bug #3789: Crash in visitEffectSources while in battle
@ -67,6 +68,7 @@
Bug #5656: Sneaking characters block hits while standing Bug #5656: Sneaking characters block hits while standing
Bug #5661: Region sounds don't play at the right interval Bug #5661: Region sounds don't play at the right interval
Bug #5688: Water shader broken indoors with enable indoor shadows = false Bug #5688: Water shader broken indoors with enable indoor shadows = false
Bug #5695: ExplodeSpell for actors doesn't target the ground
Bug #5703: OpenMW-CS menu system crashing on XFCE Bug #5703: OpenMW-CS menu system crashing on XFCE
Feature #390: 3rd person look "over the shoulder" Feature #390: 3rd person look "over the shoulder"
Feature #2386: Distant Statics in the form of Object Paging Feature #2386: Distant Statics in the form of Object Paging

View file

@ -66,13 +66,13 @@ add_openmw_dir (mwworld
cells localscripts customdata inventorystore ptr actionopen actionread actionharvest cells localscripts customdata inventorystore ptr actionopen actionread actionharvest
actionequip timestamp actionalchemy cellstore actionapply actioneat actionequip timestamp actionalchemy cellstore actionapply actioneat
store esmstore recordcmp fallback actionrepair actionsoulgem livecellref actiondoor store esmstore recordcmp fallback actionrepair actionsoulgem livecellref actiondoor
contentloader esmloader actiontrap cellreflist cellref physicssystem weather projectilemanager contentloader esmloader actiontrap cellreflist cellref weather projectilemanager
cellpreloader datetimemanager cellpreloader datetimemanager
) )
add_openmw_dir (mwphysics add_openmw_dir (mwphysics
physicssystem trace collisiontype actor convert object heightfield closestnotmerayresultcallback physicssystem trace collisiontype actor convert object heightfield closestnotmerayresultcallback
contacttestresultcallback deepestnotmecontacttestresultcallback stepper movementsolver contacttestresultcallback deepestnotmecontacttestresultcallback stepper movementsolver projectile
closestnotmeconvexresultcallback raycasting mtphysics closestnotmeconvexresultcallback raycasting mtphysics
) )

View file

@ -399,8 +399,6 @@ OMW::Engine::Engine(Files::ConfigurationManager& configurationManager)
, mNewGame (false) , mNewGame (false)
, mCfgMgr(configurationManager) , mCfgMgr(configurationManager)
{ {
MWClass::registerClasses();
SDL_SetHint(SDL_HINT_ACCELEROMETER_AS_JOYSTICK, "0"); // We use only gamepads SDL_SetHint(SDL_HINT_ACCELEROMETER_AS_JOYSTICK, "0"); // We use only gamepads
Uint32 flags = SDL_INIT_VIDEO|SDL_INIT_NOPARACHUTE|SDL_INIT_GAMECONTROLLER|SDL_INIT_JOYSTICK|SDL_INIT_SENSOR; Uint32 flags = SDL_INIT_VIDEO|SDL_INIT_NOPARACHUTE|SDL_INIT_GAMECONTROLLER|SDL_INIT_JOYSTICK|SDL_INIT_SENSOR;
@ -771,7 +769,7 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings)
} }
// VR mode will override this setting by setting mStereoOverride. // VR mode will override this setting by setting mStereoOverride.
mStereoEnabled = mStereoOverride || Settings::Manager::getBool("stereo enabled", "Stereo"); mStereoEnabled = mEnvironment.getVrMode() || Settings::Manager::getBool("stereo enabled", "Stereo");
// geometry shader must be enabled before the RenderingManager sets up any shaders // geometry shader must be enabled before the RenderingManager sets up any shaders
// therefore this part is separate from the rest of stereo setup. // therefore this part is separate from the rest of stereo setup.
@ -907,6 +905,8 @@ void OMW::Engine::go()
std::string settingspath; std::string settingspath;
settingspath = loadSettings (settings); settingspath = loadSettings (settings);
MWClass::registerClasses();
// Create encoder // Create encoder
mEncoder = new ToUTF8::Utf8Encoder(mEncoding); mEncoder = new ToUTF8::Utf8Encoder(mEncoding);

View file

@ -153,13 +153,13 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat
cfgMgr.mergeComposingVariables(variables, composingVariables, desc); cfgMgr.mergeComposingVariables(variables, composingVariables, desc);
Version::Version v = Version::getOpenmwVersion(variables["resources"].as<Files::EscapePath>().mPath.string()); Version::Version v = Version::getOpenmwVersion(variables["resources"].as<Files::EscapePath>().mPath.string());
std::cout << v.describe() << std::endl; Log(Debug::Info) << v.describe();
engine.setGrabMouse(!variables["no-grab"].as<bool>()); engine.setGrabMouse(!variables["no-grab"].as<bool>());
// Font encoding settings // Font encoding settings
std::string encoding(variables["encoding"].as<Files::EscapeHashString>().toStdString()); std::string encoding(variables["encoding"].as<Files::EscapeHashString>().toStdString());
std::cout << ToUTF8::encodingUsingMessage(encoding) << std::endl; Log(Debug::Info) << ToUTF8::encodingUsingMessage(encoding);
engine.setEncoding(ToUTF8::calculateEncoding(encoding)); engine.setEncoding(ToUTF8::calculateEncoding(encoding));
// directory settings // directory settings

View file

@ -2,6 +2,7 @@
#include <components/esm/loadcont.hpp> #include <components/esm/loadcont.hpp>
#include <components/esm/containerstate.hpp> #include <components/esm/containerstate.hpp>
#include <components/settings/settings.hpp>
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp" #include "../mwbase/world.hpp"
@ -59,6 +60,11 @@ namespace MWClass
return *this; return *this;
} }
Container::Container()
{
mHarvestEnabled = Settings::Manager::getBool("graphic herbalism", "Game");
}
void Container::ensureCustomData (const MWWorld::Ptr& ptr) const void Container::ensureCustomData (const MWWorld::Ptr& ptr) const
{ {
if (!ptr.getRefData().getCustomData()) if (!ptr.getRefData().getCustomData())
@ -72,8 +78,10 @@ namespace MWClass
} }
} }
bool canBeHarvested(const MWWorld::ConstPtr& ptr) bool Container::canBeHarvested(const MWWorld::ConstPtr& ptr) const
{ {
if (!mHarvestEnabled)
return false;
const MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(ptr); const MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(ptr);
if (animation == nullptr) if (animation == nullptr)
return false; return false;

View file

@ -30,11 +30,16 @@ namespace MWClass
class Container : public MWWorld::Class class Container : public MWWorld::Class
{ {
bool mHarvestEnabled;
void ensureCustomData (const MWWorld::Ptr& ptr) const; void ensureCustomData (const MWWorld::Ptr& ptr) const;
MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const override; MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const override;
bool canBeHarvested(const MWWorld::ConstPtr& ptr) const;
public: public:
Container();
void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override;
///< Add reference into a cell for rendering ///< Add reference into a cell for rendering

View file

@ -48,9 +48,9 @@ namespace MWGui
const MWWorld::ESMStore &store = const MWWorld::ESMStore &store =
MWBase::Environment::get().getWorld()->getStore(); MWBase::Environment::get().getWorld()->getStore();
for (unsigned int i = 0; i < effects.mList.size(); ++i) for (const auto& effect : effects.mList)
{ {
short effectId = effects.mList[i].mEffectID; short effectId = effect.mEffectID;
if (effectId != -1) if (effectId != -1)
{ {
@ -59,14 +59,14 @@ namespace MWGui
std::string effectIDStr = ESM::MagicEffect::effectIdToString(effectId); std::string effectIDStr = ESM::MagicEffect::effectIdToString(effectId);
std::string fullEffectName = wm->getGameSettingString(effectIDStr, ""); std::string fullEffectName = wm->getGameSettingString(effectIDStr, "");
if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetSkill && effects.mList[i].mSkill != -1) if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetSkill && effect.mSkill != -1)
{ {
fullEffectName += " " + wm->getGameSettingString(ESM::Skill::sSkillNameIds[effects.mList[i].mSkill], ""); fullEffectName += " " + wm->getGameSettingString(ESM::Skill::sSkillNameIds[effect.mSkill], "");
} }
if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetAttribute && effects.mList[i].mAttribute != -1) if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetAttribute && effect.mAttribute != -1)
{ {
fullEffectName += " " + wm->getGameSettingString(ESM::Attribute::sGmstAttributeIds[effects.mList[i].mAttribute], ""); fullEffectName += " " + wm->getGameSettingString(ESM::Attribute::sGmstAttributeIds[effect.mAttribute], "");
} }
std::string convert = Misc::StringUtils::lowerCaseUtf8(fullEffectName); std::string convert = Misc::StringUtils::lowerCaseUtf8(fullEffectName);

View file

@ -46,27 +46,29 @@ bool MWMechanics::AiCast::execute(const MWWorld::Ptr& actor, MWMechanics::Charac
{ {
return false; return false;
} }
osg::Vec3f targetPos = target.getRefData().getPosition().asVec3();
if (target.getClass().isActor())
{
osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getHalfExtents(target);
targetPos.z() += halfExtents.z() * 2 * 0.75f;
}
osg::Vec3f actorPos = actor.getRefData().getPosition().asVec3();
osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getHalfExtents(actor);
actorPos.z() += halfExtents.z() * 2 * 0.75f;
osg::Vec3f dir = targetPos - actorPos;
bool turned = smoothTurn(actor, getZAngleToDir(dir), 2, osg::DegreesToRadians(3.f));
turned &= smoothTurn(actor, getXAngleToDir(dir), 0, osg::DegreesToRadians(3.f));
if (!turned)
return false;
} }
osg::Vec3f targetPos = target.getRefData().getPosition().asVec3();
// If the target of an on-target spell is an actor that is not the caster
// the target position must be adjusted so that it's not casted at the actor's feet.
if (target != actor && target.getClass().isActor())
{
osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getHalfExtents(target);
targetPos.z() += halfExtents.z() * 2 * 0.75f;
}
osg::Vec3f actorPos = actor.getRefData().getPosition().asVec3();
osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getHalfExtents(actor);
actorPos.z() += halfExtents.z() * 2 * 0.75f;
osg::Vec3f dir = targetPos - actorPos;
bool turned = smoothTurn(actor, getZAngleToDir(dir), 2, osg::DegreesToRadians(3.f));
turned &= smoothTurn(actor, getXAngleToDir(dir), 0, osg::DegreesToRadians(3.f));
if (!turned)
return false;
// Check if the actor is already casting another spell // Check if the actor is already casting another spell
bool isCasting = MWBase::Environment::get().getMechanicsManager()->isCastingSpell(actor); bool isCasting = MWBase::Environment::get().getMechanicsManager()->isCastingSpell(actor);
if (isCasting && !mCasting) if (isCasting && !mCasting)

View file

@ -70,7 +70,7 @@ Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, Physic
mCollisionObject->setCollisionFlags(btCollisionObject::CF_KINEMATIC_OBJECT); mCollisionObject->setCollisionFlags(btCollisionObject::CF_KINEMATIC_OBJECT);
mCollisionObject->setActivationState(DISABLE_DEACTIVATION); mCollisionObject->setActivationState(DISABLE_DEACTIVATION);
mCollisionObject->setCollisionShape(mShape.get()); mCollisionObject->setCollisionShape(mShape.get());
mCollisionObject->setUserPointer(static_cast<PtrHolder*>(this)); mCollisionObject->setUserPointer(this);
updateRotation(); updateRotation();
updateScale(); updateScale();

View file

@ -2,6 +2,14 @@
#include <BulletCollision/CollisionDispatch/btCollisionObject.h> #include <BulletCollision/CollisionDispatch/btCollisionObject.h>
#include <components/misc/convert.hpp>
#include "../mwbase/world.hpp"
#include "../mwbase/environment.hpp"
#include "collisiontype.hpp"
#include "projectile.hpp"
namespace MWPhysics namespace MWPhysics
{ {
ClosestNotMeConvexResultCallback::ClosestNotMeConvexResultCallback(const btCollisionObject *me, const btVector3 &motion, btScalar minCollisionDot) ClosestNotMeConvexResultCallback::ClosestNotMeConvexResultCallback(const btCollisionObject *me, const btVector3 &motion, btScalar minCollisionDot)
@ -10,11 +18,26 @@ namespace MWPhysics
{ {
} }
btScalar ClosestNotMeConvexResultCallback::addSingleResult(btCollisionWorld::LocalConvexResult& convexResult,bool normalInWorldSpace) btScalar ClosestNotMeConvexResultCallback::addSingleResult(btCollisionWorld::LocalConvexResult& convexResult, bool normalInWorldSpace)
{ {
if (convexResult.m_hitCollisionObject == mMe) if (convexResult.m_hitCollisionObject == mMe)
return btScalar(1); return btScalar(1);
if (convexResult.m_hitCollisionObject->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Projectile)
{
auto* projectileHolder = static_cast<Projectile*>(convexResult.m_hitCollisionObject->getUserPointer());
if (!projectileHolder->isActive())
return btScalar(1);
auto* targetHolder = static_cast<PtrHolder*>(mMe->getUserPointer());
const MWWorld::Ptr target = targetHolder->getPtr();
// do nothing if we hit the caster. Sometimes the launching origin is inside of caster collision shape
if (projectileHolder->getCaster() != target)
{
projectileHolder->hit(target, convexResult.m_hitPointLocal, convexResult.m_hitNormalLocal);
return btScalar(1);
}
}
btVector3 hitNormalWorld; btVector3 hitNormalWorld;
if (normalInWorldSpace) if (normalInWorldSpace)
hitNormalWorld = convexResult.m_hitNormalLocal; hitNormalWorld = convexResult.m_hitNormalLocal;

View file

@ -1,18 +1,22 @@
#include "closestnotmerayresultcallback.hpp" #include "closestnotmerayresultcallback.hpp"
#include <algorithm> #include <algorithm>
#include <utility>
#include <BulletCollision/CollisionDispatch/btCollisionObject.h> #include <BulletCollision/CollisionDispatch/btCollisionObject.h>
#include "../mwworld/class.hpp" #include "../mwworld/class.hpp"
#include "actor.hpp"
#include "collisiontype.hpp"
#include "projectile.hpp"
#include "ptrholder.hpp" #include "ptrholder.hpp"
namespace MWPhysics namespace MWPhysics
{ {
ClosestNotMeRayResultCallback::ClosestNotMeRayResultCallback(const btCollisionObject* me, const std::vector<const btCollisionObject*>& targets, const btVector3& from, const btVector3& to) ClosestNotMeRayResultCallback::ClosestNotMeRayResultCallback(const btCollisionObject* me, std::vector<const btCollisionObject*> targets, const btVector3& from, const btVector3& to, Projectile* proj)
: btCollisionWorld::ClosestRayResultCallback(from, to) : btCollisionWorld::ClosestRayResultCallback(from, to)
, mMe(me), mTargets(targets) , mMe(me), mTargets(std::move(targets)), mProjectile(proj)
{ {
} }
@ -24,11 +28,27 @@ namespace MWPhysics
{ {
if ((std::find(mTargets.begin(), mTargets.end(), rayResult.m_collisionObject) == mTargets.end())) if ((std::find(mTargets.begin(), mTargets.end(), rayResult.m_collisionObject) == mTargets.end()))
{ {
PtrHolder* holder = static_cast<PtrHolder*>(rayResult.m_collisionObject->getUserPointer()); auto* holder = static_cast<PtrHolder*>(rayResult.m_collisionObject->getUserPointer());
if (holder && !holder->getPtr().isEmpty() && holder->getPtr().getClass().isActor()) if (holder && !holder->getPtr().isEmpty() && holder->getPtr().getClass().isActor())
return 1.f; return 1.f;
} }
} }
return btCollisionWorld::ClosestRayResultCallback::addSingleResult(rayResult, normalInWorldSpace);
btCollisionWorld::ClosestRayResultCallback::addSingleResult(rayResult, normalInWorldSpace);
if (mProjectile)
{
auto* holder = static_cast<PtrHolder*>(rayResult.m_collisionObject->getUserPointer());
if (auto* target = dynamic_cast<Actor*>(holder))
{
mProjectile->hit(target->getPtr(), m_hitPointWorld, m_hitNormalWorld);
}
else if (auto* target = dynamic_cast<Projectile*>(holder))
{
target->hit(mProjectile->getPtr(), m_hitPointWorld, m_hitNormalWorld);
mProjectile->hit(target->getPtr(), m_hitPointWorld, m_hitNormalWorld);
}
}
return rayResult.m_hitFraction;
} }
} }

View file

@ -9,15 +9,18 @@ class btCollisionObject;
namespace MWPhysics namespace MWPhysics
{ {
class Projectile;
class ClosestNotMeRayResultCallback : public btCollisionWorld::ClosestRayResultCallback class ClosestNotMeRayResultCallback : public btCollisionWorld::ClosestRayResultCallback
{ {
public: public:
ClosestNotMeRayResultCallback(const btCollisionObject* me, const std::vector<const btCollisionObject*>& targets, const btVector3& from, const btVector3& to); ClosestNotMeRayResultCallback(const btCollisionObject* me, std::vector<const btCollisionObject*> targets, const btVector3& from, const btVector3& to, Projectile* proj=nullptr);
btScalar addSingleResult(btCollisionWorld::LocalRayResult& rayResult, bool normalInWorldSpace) override; btScalar addSingleResult(btCollisionWorld::LocalRayResult& rayResult, bool normalInWorldSpace) override;
private: private:
const btCollisionObject* mMe; const btCollisionObject* mMe;
const std::vector<const btCollisionObject*> mTargets; const std::vector<const btCollisionObject*> mTargets;
Projectile* mProjectile;
}; };
} }

View file

@ -17,6 +17,7 @@
#include "mtphysics.hpp" #include "mtphysics.hpp"
#include "object.hpp" #include "object.hpp"
#include "physicssystem.hpp" #include "physicssystem.hpp"
#include "projectile.hpp"
namespace namespace
{ {
@ -297,12 +298,19 @@ namespace MWPhysics
return mPreviousMovementResults; return mPreviousMovementResults;
} }
const PtrPositionList& PhysicsTaskScheduler::resetSimulation() const PtrPositionList& PhysicsTaskScheduler::resetSimulation(const ActorMap& actors)
{ {
std::unique_lock lock(mSimulationMutex); std::unique_lock lock(mSimulationMutex);
mMovementResults.clear(); mMovementResults.clear();
mPreviousMovementResults.clear(); mPreviousMovementResults.clear();
mActorsFrameData.clear(); mActorsFrameData.clear();
for (const auto& [_, actor] : actors)
{
actor->resetPosition();
actor->setStandingOnPtr(nullptr);
mMovementResults[actor->getPtr()] = actor->getWorldPosition();
}
return mMovementResults; return mMovementResults;
} }
@ -448,6 +456,11 @@ namespace MWPhysics
object->commitPositionChange(); object->commitPositionChange();
mCollisionWorld->updateSingleAabb(object->getCollisionObject()); mCollisionWorld->updateSingleAabb(object->getCollisionObject());
} }
else if (const auto projectile = std::dynamic_pointer_cast<Projectile>(p))
{
projectile->commitPositionChange();
mCollisionWorld->updateSingleAabb(projectile->getCollisionObject());
}
}; };
} }

View file

@ -34,7 +34,7 @@ namespace MWPhysics
/// @return new position of each actor /// @return new position of each actor
const PtrPositionList& moveActors(int numSteps, float timeAccum, std::vector<ActorFrameData>&& actorsData, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats); const PtrPositionList& moveActors(int numSteps, float timeAccum, std::vector<ActorFrameData>&& actorsData, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats);
const PtrPositionList& resetSimulation(); const PtrPositionList& resetSimulation(const ActorMap& actors);
// Thread safe wrappers // Thread safe wrappers
void rayTest(const btVector3& rayFromWorld, const btVector3& rayToWorld, btCollisionWorld::RayResultCallback& resultCallback) const; void rayTest(const btVector3& rayFromWorld, const btVector3& rayToWorld, btCollisionWorld::RayResultCallback& resultCallback) const;

View file

@ -24,7 +24,7 @@ namespace MWPhysics
mCollisionObject.reset(new btCollisionObject); mCollisionObject.reset(new btCollisionObject);
mCollisionObject->setCollisionShape(shapeInstance->getCollisionShape()); mCollisionObject->setCollisionShape(shapeInstance->getCollisionShape());
mCollisionObject->setUserPointer(static_cast<PtrHolder*>(this)); mCollisionObject->setUserPointer(this);
setScale(ptr.getCellRef().getScale()); setScale(ptr.getCellRef().getScale());
setRotation(Misc::Convert::toBullet(ptr.getRefData().getBaseNode()->getAttitude())); setRotation(Misc::Convert::toBullet(ptr.getRefData().getBaseNode()->getAttitude()));

View file

@ -46,6 +46,8 @@
#include "collisiontype.hpp" #include "collisiontype.hpp"
#include "actor.hpp" #include "actor.hpp"
#include "projectile.hpp"
#include "trace.h" #include "trace.h"
#include "object.hpp" #include "object.hpp"
#include "heightfield.hpp" #include "heightfield.hpp"
@ -64,6 +66,7 @@ namespace MWPhysics
, mResourceSystem(resourceSystem) , mResourceSystem(resourceSystem)
, mDebugDrawEnabled(false) , mDebugDrawEnabled(false)
, mTimeAccum(0.0f) , mTimeAccum(0.0f)
, mProjectileId(0)
, mWaterHeight(0) , mWaterHeight(0)
, mWaterEnabled(false) , mWaterEnabled(false)
, mParentNode(parentNode) , mParentNode(parentNode)
@ -112,7 +115,7 @@ namespace MWPhysics
mObjects.clear(); mObjects.clear();
mActors.clear(); mActors.clear();
mProjectiles.clear();
} }
void PhysicsSystem::setUnrefQueue(SceneUtil::UnrefQueue *unrefQueue) void PhysicsSystem::setUnrefQueue(SceneUtil::UnrefQueue *unrefQueue)
@ -248,7 +251,7 @@ namespace MWPhysics
return 0.f; return 0.f;
} }
RayCastingResult PhysicsSystem::castRay(const osg::Vec3f &from, const osg::Vec3f &to, const MWWorld::ConstPtr& ignore, std::vector<MWWorld::Ptr> targets, int mask, int group) const RayCastingResult PhysicsSystem::castRay(const osg::Vec3f &from, const osg::Vec3f &to, const MWWorld::ConstPtr& ignore, std::vector<MWWorld::Ptr> targets, int mask, int group, int projId) const
{ {
if (from == to) if (from == to)
{ {
@ -285,7 +288,7 @@ namespace MWPhysics
} }
} }
ClosestNotMeRayResultCallback resultCallback(me, targetCollisionObjects, btFrom, btTo); ClosestNotMeRayResultCallback resultCallback(me, targetCollisionObjects, btFrom, btTo, getProjectile(projId));
resultCallback.m_collisionFilterGroup = group; resultCallback.m_collisionFilterGroup = group;
resultCallback.m_collisionFilterMask = mask; resultCallback.m_collisionFilterMask = mask;
@ -502,6 +505,13 @@ namespace MWPhysics
} }
} }
void PhysicsSystem::removeProjectile(const int projectileId)
{
ProjectileMap::iterator foundProjectile = mProjectiles.find(projectileId);
if (foundProjectile != mProjectiles.end())
mProjectiles.erase(foundProjectile);
}
void PhysicsSystem::updatePtr(const MWWorld::Ptr &old, const MWWorld::Ptr &updated) void PhysicsSystem::updatePtr(const MWWorld::Ptr &old, const MWWorld::Ptr &updated)
{ {
ObjectMap::iterator found = mObjects.find(old); ObjectMap::iterator found = mObjects.find(old);
@ -553,6 +563,14 @@ namespace MWPhysics
return nullptr; return nullptr;
} }
Projectile* PhysicsSystem::getProjectile(int projectileId) const
{
ProjectileMap::const_iterator found = mProjectiles.find(projectileId);
if (found != mProjectiles.end())
return found->second.get();
return nullptr;
}
void PhysicsSystem::updateScale(const MWWorld::Ptr &ptr) void PhysicsSystem::updateScale(const MWWorld::Ptr &ptr)
{ {
ObjectMap::iterator found = mObjects.find(ptr); ObjectMap::iterator found = mObjects.find(ptr);
@ -572,6 +590,17 @@ namespace MWPhysics
} }
} }
void PhysicsSystem::updateProjectile(const int projectileId, const osg::Vec3f &position)
{
ProjectileMap::iterator foundProjectile = mProjectiles.find(projectileId);
if (foundProjectile != mProjectiles.end())
{
foundProjectile->second->setPosition(position);
mTaskScheduler->updateSingleAabb(foundProjectile->second);
return;
}
}
void PhysicsSystem::updateRotation(const MWWorld::Ptr &ptr) void PhysicsSystem::updateRotation(const MWWorld::Ptr &ptr)
{ {
ObjectMap::iterator found = mObjects.find(ptr); ObjectMap::iterator found = mObjects.find(ptr);
@ -632,6 +661,15 @@ namespace MWPhysics
mActors.emplace(ptr, std::move(actor)); mActors.emplace(ptr, std::move(actor));
} }
int PhysicsSystem::addProjectile (const MWWorld::Ptr& caster, const osg::Vec3f& position)
{
mProjectileId++;
auto projectile = std::make_shared<Projectile>(mProjectileId, caster, position, mTaskScheduler.get(), this);
mProjectiles.emplace(mProjectileId, std::move(projectile));
return mProjectileId;
}
bool PhysicsSystem::toggleCollisionMode() bool PhysicsSystem::toggleCollisionMode()
{ {
ActorMap::iterator found = mActors.find(MWMechanics::getPlayer()); ActorMap::iterator found = mActors.find(MWMechanics::getPlayer());
@ -677,14 +715,7 @@ namespace MWPhysics
mTimeAccum -= numSteps * mPhysicsDt; mTimeAccum -= numSteps * mPhysicsDt;
if (skipSimulation) if (skipSimulation)
{ return mTaskScheduler->resetSimulation(mActors);
for (auto& [_, actor] : mActors)
{
actor->resetPosition();
actor->setStandingOnPtr(nullptr);
}
return mTaskScheduler->resetSimulation();
}
return mTaskScheduler->moveActors(numSteps, mTimeAccum, prepareFrameData(numSteps), frameStart, frameNumber, stats); return mTaskScheduler->moveActors(numSteps, mTimeAccum, prepareFrameData(numSteps), frameStart, frameNumber, stats);
} }

View file

@ -56,6 +56,9 @@ namespace MWPhysics
class Object; class Object;
class Actor; class Actor;
class PhysicsTaskScheduler; class PhysicsTaskScheduler;
class Projectile;
using ActorMap = std::map<MWWorld::ConstPtr, std::shared_ptr<Actor>>;
struct ContactPoint struct ContactPoint
{ {
@ -125,6 +128,10 @@ namespace MWPhysics
void addObject (const MWWorld::Ptr& ptr, const std::string& mesh, int collisionType = CollisionType_World); void addObject (const MWWorld::Ptr& ptr, const std::string& mesh, int collisionType = CollisionType_World);
void addActor (const MWWorld::Ptr& ptr, const std::string& mesh); void addActor (const MWWorld::Ptr& ptr, const std::string& mesh);
int addProjectile(const MWWorld::Ptr& caster, const osg::Vec3f& position);
void updateProjectile(const int projectileId, const osg::Vec3f &position);
void removeProjectile(const int projectileId);
void updatePtr (const MWWorld::Ptr& old, const MWWorld::Ptr& updated); void updatePtr (const MWWorld::Ptr& old, const MWWorld::Ptr& updated);
Actor* getActor(const MWWorld::Ptr& ptr); Actor* getActor(const MWWorld::Ptr& ptr);
@ -132,6 +139,8 @@ namespace MWPhysics
const Object* getObject(const MWWorld::ConstPtr& ptr) const; const Object* getObject(const MWWorld::ConstPtr& ptr) const;
Projectile* getProjectile(int projectileId) const;
// Object or Actor // Object or Actor
void remove (const MWWorld::Ptr& ptr); void remove (const MWWorld::Ptr& ptr);
@ -139,7 +148,6 @@ namespace MWPhysics
void updateRotation (const MWWorld::Ptr& ptr); void updateRotation (const MWWorld::Ptr& ptr);
void updatePosition (const MWWorld::Ptr& ptr); void updatePosition (const MWWorld::Ptr& ptr);
void addHeightField (const float* heights, int x, int y, float triSize, float sqrtVerts, float minH, float maxH, const osg::Object* holdObject); void addHeightField (const float* heights, int x, int y, float triSize, float sqrtVerts, float minH, float maxH, const osg::Object* holdObject);
void removeHeightField (int x, int y); void removeHeightField (int x, int y);
@ -170,7 +178,7 @@ namespace MWPhysics
/// @param me Optional, a Ptr to ignore in the list of results. targets are actors to filter for, ignoring all other actors. /// @param me Optional, a Ptr to ignore in the list of results. targets are actors to filter for, ignoring all other actors.
RayCastingResult castRay(const osg::Vec3f &from, const osg::Vec3f &to, const MWWorld::ConstPtr& ignore = MWWorld::ConstPtr(), RayCastingResult castRay(const osg::Vec3f &from, const osg::Vec3f &to, const MWWorld::ConstPtr& ignore = MWWorld::ConstPtr(),
std::vector<MWWorld::Ptr> targets = std::vector<MWWorld::Ptr>(), std::vector<MWWorld::Ptr> targets = std::vector<MWWorld::Ptr>(),
int mask = CollisionType_World|CollisionType_HeightMap|CollisionType_Actor|CollisionType_Door, int group=0xff) const override; int mask = CollisionType_World|CollisionType_HeightMap|CollisionType_Actor|CollisionType_Door, int group=0xff, int projId=-1) const override;
RayCastingResult castSphere(const osg::Vec3f& from, const osg::Vec3f& to, float radius) const override; RayCastingResult castSphere(const osg::Vec3f& from, const osg::Vec3f& to, float radius) const override;
@ -265,9 +273,11 @@ namespace MWPhysics
std::set<Object*> mAnimatedObjects; // stores pointers to elements in mObjects std::set<Object*> mAnimatedObjects; // stores pointers to elements in mObjects
using ActorMap = std::map<MWWorld::ConstPtr, std::shared_ptr<Actor>>;
ActorMap mActors; ActorMap mActors;
using ProjectileMap = std::map<int, std::shared_ptr<Projectile>>;
ProjectileMap mProjectiles;
using HeightFieldMap = std::map<std::pair<int, int>, HeightField *>; using HeightFieldMap = std::map<std::pair<int, int>, HeightField *>;
HeightFieldMap mHeightFields; HeightFieldMap mHeightFields;
@ -278,6 +288,8 @@ namespace MWPhysics
float mTimeAccum; float mTimeAccum;
unsigned int mProjectileId;
float mWaterHeight; float mWaterHeight;
bool mWaterEnabled; bool mWaterEnabled;

View file

@ -0,0 +1,89 @@
#include <memory>
#include <BulletCollision/CollisionShapes/btSphereShape.h>
#include <BulletCollision/CollisionDispatch/btCollisionWorld.h>
#include <LinearMath/btVector3.h>
#include <components/debug/debuglog.hpp>
#include <components/misc/convert.hpp>
#include <components/resource/bulletshape.hpp>
#include <components/sceneutil/positionattitudetransform.hpp>
#include "../mwworld/class.hpp"
#include "collisiontype.hpp"
#include "mtphysics.hpp"
#include "projectile.hpp"
namespace MWPhysics
{
Projectile::Projectile(int projectileId, const MWWorld::Ptr& caster, const osg::Vec3f& position, PhysicsTaskScheduler* scheduler, PhysicsSystem* physicssystem)
: mActive(true)
, mCaster(caster)
, mPhysics(physicssystem)
, mTaskScheduler(scheduler)
, mProjectileId(projectileId)
{
mShape.reset(new btSphereShape(1.f));
mConvexShape = static_cast<btConvexShape*>(mShape.get());
mCollisionObject.reset(new btCollisionObject);
mCollisionObject->setCollisionFlags(btCollisionObject::CF_KINEMATIC_OBJECT);
mCollisionObject->setActivationState(DISABLE_DEACTIVATION);
mCollisionObject->setCollisionShape(mShape.get());
mCollisionObject->setUserPointer(this);
setPosition(position);
const int collisionMask = CollisionType_World | CollisionType_HeightMap |
CollisionType_Actor | CollisionType_Door | CollisionType_Water | CollisionType_Projectile;
mTaskScheduler->addCollisionObject(mCollisionObject.get(), CollisionType_Projectile, collisionMask);
commitPositionChange();
}
Projectile::~Projectile()
{
if (mCollisionObject)
{
if (!mActive)
mPhysics->reportCollision(mHitPosition, mHitNormal);
mTaskScheduler->removeCollisionObject(mCollisionObject.get());
}
}
void Projectile::commitPositionChange()
{
std::unique_lock<std::mutex> lock(mPositionMutex);
if (mTransformUpdatePending)
{
mCollisionObject->setWorldTransform(mLocalTransform);
mTransformUpdatePending = false;
}
}
void Projectile::setPosition(const osg::Vec3f &position)
{
std::unique_lock<std::mutex> lock(mPositionMutex);
mLocalTransform.setOrigin(Misc::Convert::toBullet(position));
mTransformUpdatePending = true;
}
void Projectile::hit(MWWorld::Ptr target, btVector3 pos, btVector3 normal)
{
if (!mActive.load(std::memory_order_acquire))
return;
std::unique_lock<std::mutex> lock(mPositionMutex);
mHitTarget = target;
mHitPosition = pos;
mHitNormal = normal;
mActive.store(false, std::memory_order_release);
}
void Projectile::activate()
{
assert(!mActive);
mActive.store(true, std::memory_order_release);
}
}

View file

@ -0,0 +1,106 @@
#ifndef OPENMW_MWPHYSICS_PROJECTILE_H
#define OPENMW_MWPHYSICS_PROJECTILE_H
#include <atomic>
#include <memory>
#include <mutex>
#include "components/misc/convert.hpp"
#include "ptrholder.hpp"
class btCollisionObject;
class btCollisionShape;
class btConvexShape;
class btVector3;
namespace osg
{
class Vec3f;
}
namespace Resource
{
class BulletShape;
}
namespace MWPhysics
{
class PhysicsTaskScheduler;
class PhysicsSystem;
class Projectile final : public PtrHolder
{
public:
Projectile(const int projectileId, const MWWorld::Ptr& caster, const osg::Vec3f& position, PhysicsTaskScheduler* scheduler, PhysicsSystem* physicssystem);
~Projectile() override;
btConvexShape* getConvexShape() const { return mConvexShape; }
void commitPositionChange();
void setPosition(const osg::Vec3f& position);
btCollisionObject* getCollisionObject() const
{
return mCollisionObject.get();
}
int getProjectileId() const
{
return mProjectileId;
}
bool isActive() const
{
return mActive.load(std::memory_order_acquire);
}
MWWorld::Ptr getTarget() const
{
assert(!mActive);
return mHitTarget;
}
MWWorld::Ptr getCaster() const { return mCaster; }
osg::Vec3f getHitPos() const
{
assert(!mActive);
return Misc::Convert::toOsg(mHitPosition);
}
void hit(MWWorld::Ptr target, btVector3 pos, btVector3 normal);
void activate();
private:
std::unique_ptr<btCollisionShape> mShape;
btConvexShape* mConvexShape;
std::unique_ptr<btCollisionObject> mCollisionObject;
btTransform mLocalTransform;
bool mTransformUpdatePending;
std::atomic<bool> mActive;
MWWorld::Ptr mCaster;
MWWorld::Ptr mHitTarget;
btVector3 mHitPosition;
btVector3 mHitNormal;
mutable std::mutex mPositionMutex;
osg::Vec3f mPosition;
PhysicsSystem *mPhysics;
PhysicsTaskScheduler *mTaskScheduler;
Projectile(const Projectile&);
Projectile& operator=(const Projectile&);
int mProjectileId;
};
}
#endif

View file

@ -29,7 +29,7 @@ namespace MWPhysics
/// @param me Optional, a Ptr to ignore in the list of results. targets are actors to filter for, ignoring all other actors. /// @param me Optional, a Ptr to ignore in the list of results. targets are actors to filter for, ignoring all other actors.
virtual RayCastingResult castRay(const osg::Vec3f &from, const osg::Vec3f &to, const MWWorld::ConstPtr& ignore = MWWorld::ConstPtr(), virtual RayCastingResult castRay(const osg::Vec3f &from, const osg::Vec3f &to, const MWWorld::ConstPtr& ignore = MWWorld::ConstPtr(),
std::vector<MWWorld::Ptr> targets = std::vector<MWWorld::Ptr>(), std::vector<MWWorld::Ptr> targets = std::vector<MWWorld::Ptr>(),
int mask = CollisionType_World|CollisionType_HeightMap|CollisionType_Actor|CollisionType_Door, int group=0xff) const = 0; int mask = CollisionType_World|CollisionType_HeightMap|CollisionType_Actor|CollisionType_Door, int group=0xff, int projId=-1) const = 0;
virtual RayCastingResult castSphere(const osg::Vec3f& from, const osg::Vec3f& to, float radius) const = 0; virtual RayCastingResult castSphere(const osg::Vec3f& from, const osg::Vec3f& to, float radius) const = 0;

View file

@ -5,6 +5,9 @@
#include <BulletCollision/CollisionDispatch/btCollisionWorld.h> #include <BulletCollision/CollisionDispatch/btCollisionWorld.h>
#include <BulletCollision/CollisionShapes/btConvexShape.h> #include <BulletCollision/CollisionShapes/btConvexShape.h>
#include "../mwbase/world.hpp"
#include "../mwbase/environment.hpp"
#include "collisiontype.hpp" #include "collisiontype.hpp"
#include "actor.hpp" #include "actor.hpp"
#include "closestnotmeconvexresultcallback.hpp" #include "closestnotmeconvexresultcallback.hpp"

View file

@ -521,7 +521,7 @@ namespace MWVR
{ {
XrCompositionLayerProjectionView xrLayer; XrCompositionLayerProjectionView xrLayer;
xrLayer.type = XR_TYPE_COMPOSITION_LAYER_PROJECTION_VIEW; xrLayer.type = XR_TYPE_COMPOSITION_LAYER_PROJECTION_VIEW;
xrLayer.subImage = layer.swapchain->impl().xrSubImage(); xrLayer.subImage = toXR(layer.subImage, false);
xrLayer.pose = toXR(layer.pose); xrLayer.pose = toXR(layer.pose);
xrLayer.fov = toXR(layer.fov); xrLayer.fov = toXR(layer.fov);
xrLayer.next = nullptr; xrLayer.next = nullptr;
@ -529,6 +529,21 @@ namespace MWVR
return xrLayer; return xrLayer;
} }
XrSwapchainSubImage toXR(MWVR::SubImage subImage, bool depthImage)
{
XrSwapchainSubImage xrSubImage{};
if(depthImage)
xrSubImage.swapchain = subImage.swapchain->impl().xrSwapchainDepth();
else
xrSubImage.swapchain = subImage.swapchain->impl().xrSwapchain();
xrSubImage.imageRect.extent.width = subImage.width;
xrSubImage.imageRect.extent.height = subImage.height;
xrSubImage.imageRect.offset.x = subImage.x;
xrSubImage.imageRect.offset.y = subImage.y;
xrSubImage.imageArrayIndex = 0;
return xrSubImage;
}
void void
OpenXRManagerImpl::endFrame(FrameInfo frameInfo, const std::array<CompositionLayerProjectionView, 2>* layerStack) OpenXRManagerImpl::endFrame(FrameInfo frameInfo, const std::array<CompositionLayerProjectionView, 2>* layerStack)
{ {
@ -555,8 +570,8 @@ namespace MWVR
compositionLayerDepth = mLayerDepth; compositionLayerDepth = mLayerDepth;
compositionLayerDepth[(int)Side::LEFT_SIDE].farZ = farClip; compositionLayerDepth[(int)Side::LEFT_SIDE].farZ = farClip;
compositionLayerDepth[(int)Side::RIGHT_SIDE].farZ = farClip; compositionLayerDepth[(int)Side::RIGHT_SIDE].farZ = farClip;
compositionLayerDepth[(int)Side::LEFT_SIDE].subImage = (*layerStack)[(int)Side::LEFT_SIDE].swapchain->impl().xrSubImageDepth(); compositionLayerDepth[(int)Side::LEFT_SIDE].subImage = toXR((*layerStack)[(int)Side::LEFT_SIDE].subImage, true);
compositionLayerDepth[(int)Side::RIGHT_SIDE].subImage = (*layerStack)[(int)Side::RIGHT_SIDE].swapchain->impl().xrSubImageDepth(); compositionLayerDepth[(int)Side::RIGHT_SIDE].subImage = toXR((*layerStack)[(int)Side::RIGHT_SIDE].subImage, true);
if (compositionLayerDepth[(int)Side::LEFT_SIDE].subImage.swapchain != XR_NULL_HANDLE if (compositionLayerDepth[(int)Side::LEFT_SIDE].subImage.swapchain != XR_NULL_HANDLE
&& compositionLayerDepth[(int)Side::RIGHT_SIDE].subImage.swapchain != XR_NULL_HANDLE) && compositionLayerDepth[(int)Side::RIGHT_SIDE].subImage.swapchain != XR_NULL_HANDLE)
{ {

View file

@ -42,6 +42,7 @@ namespace MWVR
XrQuaternionf toXR(osg::Quat quat); XrQuaternionf toXR(osg::Quat quat);
XrCompositionLayerProjectionView toXR(MWVR::CompositionLayerProjectionView layer); XrCompositionLayerProjectionView toXR(MWVR::CompositionLayerProjectionView layer);
XrSwapchainSubImage toXR(MWVR::SubImage, bool depthImage);
/// \brief Implementation of OpenXRManager /// \brief Implementation of OpenXRManager
struct OpenXRManagerImpl struct OpenXRManagerImpl

View file

@ -156,8 +156,6 @@ namespace MWVR
beginPhase(FramePhase::Swap); beginPhase(FramePhase::Swap);
auto* frameMeta = getFrame(FramePhase::Swap).get(); auto* frameMeta = getFrame(FramePhase::Swap).get();
auto leftView = viewer.getView("LeftEye");
auto rightView = viewer.getView("RightEye");
if (frameMeta->mShouldSyncFrameLoop) if (frameMeta->mShouldSyncFrameLoop)
{ {
@ -165,11 +163,9 @@ namespace MWVR
{ {
viewer.blitEyesToMirrorTexture(gc); viewer.blitEyesToMirrorTexture(gc);
gc->swapBuffersImplementation(); gc->swapBuffersImplementation();
leftView->swapBuffers(gc);
rightView->swapBuffers(gc);
std::array<CompositionLayerProjectionView, 2> layerStack{}; std::array<CompositionLayerProjectionView, 2> layerStack{};
layerStack[(int)Side::LEFT_SIDE].swapchain = &leftView->swapchain(); layerStack[(int)Side::LEFT_SIDE].subImage = viewer.subImage(Side::LEFT_SIDE);
layerStack[(int)Side::RIGHT_SIDE].swapchain = &rightView->swapchain(); layerStack[(int)Side::RIGHT_SIDE].subImage = viewer.subImage(Side::RIGHT_SIDE);
layerStack[(int)Side::LEFT_SIDE].pose = frameMeta->mPredictedPoses.eye[(int)Side::LEFT_SIDE] / mPlayerScale; layerStack[(int)Side::LEFT_SIDE].pose = frameMeta->mPredictedPoses.eye[(int)Side::LEFT_SIDE] / mPlayerScale;
layerStack[(int)Side::RIGHT_SIDE].pose = frameMeta->mPredictedPoses.eye[(int)Side::RIGHT_SIDE] / mPlayerScale; layerStack[(int)Side::RIGHT_SIDE].pose = frameMeta->mPredictedPoses.eye[(int)Side::RIGHT_SIDE] / mPlayerScale;
layerStack[(int)Side::LEFT_SIDE].fov = frameMeta->mPredictedPoses.view[(int)Side::LEFT_SIDE].fov; layerStack[(int)Side::LEFT_SIDE].fov = frameMeta->mPredictedPoses.view[(int)Side::LEFT_SIDE].fov;

View file

@ -90,9 +90,18 @@ namespace MWVR
bool operator==(const PoseSet& rhs) const; bool operator==(const PoseSet& rhs) const;
}; };
struct CompositionLayerProjectionView struct SubImage
{ {
class OpenXRSwapchain* swapchain; class OpenXRSwapchain* swapchain;
int32_t x;
int32_t y;
int32_t width;
int32_t height;
};
struct CompositionLayerProjectionView
{
SubImage subImage;
Pose pose; Pose pose;
FieldOfView fov; FieldOfView fov;
}; };

View file

@ -1,138 +1,138 @@
#include "vrview.hpp" //#include "vrview.hpp"
//
#include "openxrmanager.hpp" //#include "openxrmanager.hpp"
#include "openxrmanagerimpl.hpp" //#include "openxrmanagerimpl.hpp"
#include "vrsession.hpp" //#include "vrsession.hpp"
#include "vrenvironment.hpp" //#include "vrenvironment.hpp"
//
#include <components/debug/debuglog.hpp> //#include <components/debug/debuglog.hpp>
//
#include <osgViewer/Renderer> //#include <osgViewer/Renderer>
//
namespace MWVR { //namespace MWVR {
//
VRView::VRView( // VRView::VRView(
std::string name, // std::string name,
SwapchainConfig config, // SwapchainConfig config,
osg::ref_ptr<osg::State> state) // osg::ref_ptr<osg::State> state)
: mSwapchainConfig{ config } // : mSwapchainConfig{ config }
, mSwapchain(new OpenXRSwapchain(state, mSwapchainConfig)) // , mSwapchain(new OpenXRSwapchain(state, mSwapchainConfig))
, mName(name) // , mName(name)
{ // {
} // }
//
VRView::~VRView() // VRView::~VRView()
{ // {
} // }
//
class CullCallback : public osg::NodeCallback // class CullCallback : public osg::NodeCallback
{ // {
void operator()(osg::Node* node, osg::NodeVisitor* nv) // void operator()(osg::Node* node, osg::NodeVisitor* nv)
{ // {
const auto& name = node->getName(); // const auto& name = node->getName();
if (name == "LeftEye") // if (name == "LeftEye")
Environment::get().getSession()->beginPhase(VRSession::FramePhase::Cull); // Environment::get().getSession()->beginPhase(VRSession::FramePhase::Cull);
traverse(node, nv); // traverse(node, nv);
} // }
}; // };
//
osg::Camera* VRView::createCamera(int order, const osg::Vec4& clearColor, osg::GraphicsContext* gc) // osg::Camera* VRView::createCamera(int order, const osg::Vec4& clearColor, osg::GraphicsContext* gc)
{ // {
osg::ref_ptr<osg::Camera> camera = new osg::Camera(); // osg::ref_ptr<osg::Camera> camera = new osg::Camera();
camera->setClearColor(clearColor); // camera->setClearColor(clearColor);
camera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // camera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT); // camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT);
camera->setRenderOrder(osg::Camera::PRE_RENDER, order); // camera->setRenderOrder(osg::Camera::PRE_RENDER, order);
camera->setComputeNearFarMode(osg::CullSettings::DO_NOT_COMPUTE_NEAR_FAR); // camera->setComputeNearFarMode(osg::CullSettings::DO_NOT_COMPUTE_NEAR_FAR);
camera->setAllowEventFocus(false); // camera->setAllowEventFocus(false);
camera->setReferenceFrame(osg::Transform::ABSOLUTE_RF); // camera->setReferenceFrame(osg::Transform::ABSOLUTE_RF);
camera->setViewport(0, 0, mSwapchain->width(), mSwapchain->height()); // camera->setViewport(0, 0, mSwapchain->width(), mSwapchain->height());
camera->setGraphicsContext(gc); // camera->setGraphicsContext(gc);
//
camera->setInitialDrawCallback(new VRView::InitialDrawCallback()); // camera->setInitialDrawCallback(new VRView::InitialDrawCallback());
camera->setCullCallback(new CullCallback); // camera->setCullCallback(new CullCallback);
//
return camera.release(); // return camera.release();
} // }
//
void VRView::prerenderCallback(osg::RenderInfo& renderInfo) // void VRView::prerenderCallback(osg::RenderInfo& renderInfo)
{ // {
if(Environment::get().getSession()->getFrame(VRSession::FramePhase::Draw)->mShouldRender) // if(Environment::get().getSession()->getFrame(VRSession::FramePhase::Draw)->mShouldRender)
mSwapchain->beginFrame(renderInfo.getState()->getGraphicsContext()); // mSwapchain->beginFrame(renderInfo.getState()->getGraphicsContext());
} // }
//
void VRView::InitialDrawCallback::operator()(osg::RenderInfo& renderInfo) const // void VRView::InitialDrawCallback::operator()(osg::RenderInfo& renderInfo) const
{ // {
const auto& name = renderInfo.getCurrentCamera()->getName(); // const auto& name = renderInfo.getCurrentCamera()->getName();
if (name == "LeftEye") // if (name == "LeftEye")
Environment::get().getSession()->beginPhase(VRSession::FramePhase::Draw); // Environment::get().getSession()->beginPhase(VRSession::FramePhase::Draw);
//
osg::GraphicsOperation* graphicsOperation = renderInfo.getCurrentCamera()->getRenderer(); // osg::GraphicsOperation* graphicsOperation = renderInfo.getCurrentCamera()->getRenderer();
osgViewer::Renderer* renderer = dynamic_cast<osgViewer::Renderer*>(graphicsOperation); // osgViewer::Renderer* renderer = dynamic_cast<osgViewer::Renderer*>(graphicsOperation);
if (renderer != nullptr) // if (renderer != nullptr)
{ // {
// Disable normal OSG FBO camera setup // // Disable normal OSG FBO camera setup
renderer->setCameraRequiresSetUp(false); // renderer->setCameraRequiresSetUp(false);
} // }
} // }
void VRView::UpdateSlaveCallback::updateSlave( // void VRView::UpdateSlaveCallback::updateSlave(
osg::View& view, // osg::View& view,
osg::View::Slave& slave) // osg::View::Slave& slave)
{ // {
mView->updateSlave(view, slave); // mView->updateSlave(view, slave);
} // }
//
void VRView::postrenderCallback(osg::RenderInfo& renderInfo) // void VRView::postrenderCallback(osg::RenderInfo& renderInfo)
{ // {
auto name = renderInfo.getCurrentCamera()->getName(); // auto name = renderInfo.getCurrentCamera()->getName();
} // }
//
void VRView::swapBuffers(osg::GraphicsContext* gc) // void VRView::swapBuffers(osg::GraphicsContext* gc)
{ // {
mSwapchain->endFrame(gc); // mSwapchain->endFrame(gc);
} // }
void VRView::updateSlave(osg::View& view, osg::View::Slave& slave) // void VRView::updateSlave(osg::View& view, osg::View::Slave& slave)
{ // {
auto* camera = slave._camera.get(); // auto* camera = slave._camera.get();
//
// Update current cached cull mask of camera if it is active // // Update current cached cull mask of camera if it is active
auto mask = camera->getCullMask(); // auto mask = camera->getCullMask();
if (mask == 0) // if (mask == 0)
camera->setCullMask(mCullMask); // camera->setCullMask(mCullMask);
else // else
mCullMask = mask; // mCullMask = mask;
//
// If the session is not active, we do not want to waste resources rendering frames. // // If the session is not active, we do not want to waste resources rendering frames.
if (Environment::get().getSession()->getFrame(VRSession::FramePhase::Update)->mShouldRender) // if (Environment::get().getSession()->getFrame(VRSession::FramePhase::Update)->mShouldRender)
{ // {
Side side = Side::RIGHT_SIDE; // Side side = Side::RIGHT_SIDE;
if (mName == "LeftEye") // if (mName == "LeftEye")
{ // {
//
Environment::get().getViewer()->vrShadow().updateShadowConfig(view); // Environment::get().getViewer()->vrShadow().updateShadowConfig(view);
side = Side::LEFT_SIDE; // side = Side::LEFT_SIDE;
} // }
//
auto* session = Environment::get().getSession(); // auto* session = Environment::get().getSession();
auto viewMatrix = view.getCamera()->getViewMatrix(); // auto viewMatrix = view.getCamera()->getViewMatrix();
//
// If the camera does not have a view, use the VR stage directly // // If the camera does not have a view, use the VR stage directly
bool useStage = !(viewMatrix.getTrans().length() > 0.01); // bool useStage = !(viewMatrix.getTrans().length() > 0.01);
//
// If the view matrix is still the identity matrix, conventions have to be swapped around. // // If the view matrix is still the identity matrix, conventions have to be swapped around.
bool swapConventions = viewMatrix.isIdentity(); // bool swapConventions = viewMatrix.isIdentity();
//
viewMatrix = viewMatrix * session->viewMatrix(VRSession::FramePhase::Update, side, !useStage, !swapConventions); // viewMatrix = viewMatrix * session->viewMatrix(VRSession::FramePhase::Update, side, !useStage, !swapConventions);
//
camera->setViewMatrix(viewMatrix); // camera->setViewMatrix(viewMatrix);
//
auto projectionMatrix = session->projectionMatrix(VRSession::FramePhase::Update, side); // auto projectionMatrix = session->projectionMatrix(VRSession::FramePhase::Update, side);
camera->setProjectionMatrix(projectionMatrix); // camera->setProjectionMatrix(projectionMatrix);
} // }
else // else
{ // {
camera->setCullMask(0); // camera->setCullMask(0);
} // }
slave.updateSlaveImplementation(view); // slave.updateSlaveImplementation(view);
} // }
} //}

View file

@ -1,65 +1,65 @@
#ifndef MWVR_VRVIEW_H //#ifndef MWVR_VRVIEW_H
#define MWVR_VRVIEW_H //#define MWVR_VRVIEW_H
//
#include <cassert> //#include <cassert>
#include "openxrmanager.hpp" //#include "openxrmanager.hpp"
#include "openxrswapchain.hpp" //#include "openxrswapchain.hpp"
//
struct XrSwapchainSubImage; //struct XrSwapchainSubImage;
//
namespace MWVR //namespace MWVR
{ //{
class VRViewer; // class VRViewer;
//
/// \brief Manipulates a slave camera by replacing its framebuffer with one destined for openxr // /// \brief Manipulates a slave camera by replacing its framebuffer with one destined for openxr
class VRView : public osg::Referenced // class VRView : public osg::Referenced
{ // {
public: // public:
//
class InitialDrawCallback : public osg::Camera::DrawCallback // class InitialDrawCallback : public osg::Camera::DrawCallback
{ // {
public: // public:
virtual void operator()(osg::RenderInfo& renderInfo) const; // virtual void operator()(osg::RenderInfo& renderInfo) const;
}; // };
//
class UpdateSlaveCallback : public osg::View::Slave::UpdateSlaveCallback // class UpdateSlaveCallback : public osg::View::Slave::UpdateSlaveCallback
{ // {
public: // public:
UpdateSlaveCallback(osg::ref_ptr<VRView> view) : mView(view) {} // UpdateSlaveCallback(osg::ref_ptr<VRView> view) : mView(view) {}
void updateSlave(osg::View& view, osg::View::Slave& slave) override; // void updateSlave(osg::View& view, osg::View::Slave& slave) override;
//
private: // private:
osg::ref_ptr<VRView> mView; // osg::ref_ptr<VRView> mView;
}; // };
//
public: // public:
VRView(std::string name, SwapchainConfig config, osg::ref_ptr<osg::State> state); // VRView(std::string name, SwapchainConfig config, osg::ref_ptr<osg::State> state);
virtual ~VRView(); // virtual ~VRView();
//
public: // public:
//! Prepare for render (set FBO) // //! Prepare for render (set FBO)
virtual void prerenderCallback(osg::RenderInfo& renderInfo); // virtual void prerenderCallback(osg::RenderInfo& renderInfo);
//
//! Finalize render // //! Finalize render
virtual void postrenderCallback(osg::RenderInfo& renderInfo); // virtual void postrenderCallback(osg::RenderInfo& renderInfo);
//
//! Create camera for this view // //! Create camera for this view
osg::Camera* createCamera(int order, const osg::Vec4& clearColor, osg::GraphicsContext* gc); // osg::Camera* createCamera(int order, const osg::Vec4& clearColor, osg::GraphicsContext* gc);
//
//! Get the view surface // //! Get the view surface
OpenXRSwapchain& swapchain(void) { return *mSwapchain; } // OpenXRSwapchain& swapchain(void) { return *mSwapchain; }
//
//! Present to the openxr swapchain // //! Present to the openxr swapchain
void swapBuffers(osg::GraphicsContext* gc); // void swapBuffers(osg::GraphicsContext* gc);
//
void updateSlave(osg::View& view, osg::View::Slave& slave); // void updateSlave(osg::View& view, osg::View::Slave& slave);
public: // public:
SwapchainConfig mSwapchainConfig; // SwapchainConfig mSwapchainConfig;
std::unique_ptr<OpenXRSwapchain> mSwapchain; // std::unique_ptr<OpenXRSwapchain> mSwapchain;
std::string mName{}; // std::string mName{};
osg::Node::NodeMask mCullMask; // osg::Node::NodeMask mCullMask;
bool mRendering{ false }; // bool mRendering{ false };
}; // };
} //}
//
#endif //#endif

View file

@ -1,6 +1,7 @@
#include "vrviewer.hpp" #include "vrviewer.hpp"
#include "openxrmanagerimpl.hpp" #include "openxrmanagerimpl.hpp"
#include "openxrswapchain.hpp"
#include "vrenvironment.hpp" #include "vrenvironment.hpp"
#include "vrsession.hpp" #include "vrsession.hpp"
#include "vrframebuffer.hpp" #include "vrframebuffer.hpp"
@ -36,10 +37,8 @@ namespace MWVR
VRViewer::VRViewer( VRViewer::VRViewer(
osg::ref_ptr<osgViewer::Viewer> viewer) osg::ref_ptr<osgViewer::Viewer> viewer)
: mViewer(viewer) : mViewer(viewer)
, mViews()
, mPreDraw(new PredrawCallback(this)) , mPreDraw(new PredrawCallback(this))
, mPostDraw(new PostdrawCallback(this)) , mPostDraw(new PostdrawCallback(this))
, mVrShadow()
, mConfigured(false) , mConfigured(false)
, mMsaaResolveMirrorTexture{} , mMsaaResolveMirrorTexture{}
, mMirrorTexture{ nullptr } , mMirrorTexture{ nullptr }
@ -91,6 +90,30 @@ namespace MWVR
return VRViewer::MirrorTextureEye::Both; return VRViewer::MirrorTextureEye::Both;
} }
void VRViewer::InitialDrawCallback::operator()(osg::RenderInfo& renderInfo) const
{
const auto& name = renderInfo.getCurrentCamera()->getName();
if (name == "LeftEye")
Environment::get().getSession()->beginPhase(VRSession::FramePhase::Draw);
osg::GraphicsOperation* graphicsOperation = renderInfo.getCurrentCamera()->getRenderer();
osgViewer::Renderer* renderer = dynamic_cast<osgViewer::Renderer*>(graphicsOperation);
if (renderer != nullptr)
{
// Disable normal OSG FBO camera setup
renderer->setCameraRequiresSetUp(false);
}
}
class CullCallback : public osg::NodeCallback
{
void operator()(osg::Node* node, osg::NodeVisitor* nv)
{
Environment::get().getSession()->beginPhase(VRSession::FramePhase::Cull);
traverse(node, nv);
}
};
void VRViewer::realize(osg::GraphicsContext* context) void VRViewer::realize(osg::GraphicsContext* context)
{ {
std::unique_lock<std::mutex> lock(mMutex); std::unique_lock<std::mutex> lock(mMutex);
@ -101,9 +124,9 @@ namespace MWVR
} }
// Give the main camera an initial draw callback that disables camera setup (we don't want it) // Give the main camera an initial draw callback that disables camera setup (we don't want it)
auto mainCamera = mCameras["MainCamera"] = mViewer->getCamera(); auto mainCamera = mViewer->getCamera();
mainCamera->setName("Main"); mainCamera->setName("Main");
mainCamera->setInitialDrawCallback(new VRView::InitialDrawCallback()); mainCamera->setInitialDrawCallback(new InitialDrawCallback());
auto* xr = Environment::get().getManager(); auto* xr = Environment::get().getManager();
xr->realize(context); xr->realize(context);
@ -112,17 +135,7 @@ namespace MWVR
// For the rest of runtime this is handled by vrsession // For the rest of runtime this is handled by vrsession
xr->handleEvents(); xr->handleEvents();
// Small feature culling // Set up swapchain config
bool smallFeatureCulling = Settings::Manager::getBool("small feature culling", "Camera");
auto smallFeatureCullingPixelSize = Settings::Manager::getFloat("small feature culling pixel size", "Camera");
osg::Camera::CullingMode cullingMode = osg::Camera::DEFAULT_CULLING | osg::Camera::FAR_PLANE_CULLING;
if (!smallFeatureCulling)
cullingMode &= ~osg::CullStack::SMALL_FEATURE_CULLING;
else
cullingMode |= osg::CullStack::SMALL_FEATURE_CULLING;
// Configure eyes, their cameras, and their enslavement.
osg::Vec4 clearColor = mainCamera->getClearColor();
auto config = xr->getRecommendedSwapchainConfig(); auto config = xr->getRecommendedSwapchainConfig();
std::array<std::string, 2> xConfString; std::array<std::string, 2> xConfString;
@ -151,42 +164,41 @@ namespace MWVR
config[i].name = sViewNames[i]; config[i].name = sViewNames[i];
auto view = new VRView(name, config[i], context->getState()); mSubImages[i].width = config[i].selectedWidth;
mViews[name] = view; mSubImages[i].height = config[i].selectedHeight;
auto camera = mCameras[name] = view->createCamera(i + 2, clearColor, context); if (i > 0)
camera->setPreDrawCallback(mPreDraw); {
camera->setFinalDrawCallback(mPostDraw); mSubImages[i].x = mSubImages[i - 1].x + mSubImages[i - 1].width;
camera->setCullMask(~MWRender::Mask_GUI & ~MWRender::Mask_SimpleWater & ~MWRender::Mask_UpdateVisitor); mSubImages[i].y = mSubImages[i - 1].y + mSubImages[i - 1].height;
camera->setName(name); }
if (smallFeatureCulling) else
camera->setSmallFeatureCullingPixelSize(smallFeatureCullingPixelSize); {
camera->setCullingMode(cullingMode); mSubImages[i].x = mSubImages[i].y = 0;
mViewer->addSlave(camera, true); }
auto* slave = mViewer->findSlaveForCamera(camera);
slave->_updateSlaveCallback = new VRView::UpdateSlaveCallback(view);
mVrShadow.configureShadowsForCamera(camera, i == 0);
} }
mSwapchainConfig.name = "Main";
mSwapchainConfig.selectedWidth = config[0].selectedWidth + config[1].selectedWidth;
mSwapchainConfig.selectedHeight = std::max(config[0].selectedHeight, config[1].selectedHeight);
mSwapchainConfig.selectedSamples = std::max(config[0].selectedSamples, config[1].selectedSamples);
mSwapchain.reset(new OpenXRSwapchain(context->getState(), mSwapchainConfig));
mSubImages[0].swapchain = mSubImages[1].swapchain = mSwapchain.get();
mViewer->setReleaseContextAtEndOfFrameHint(false); mViewer->setReleaseContextAtEndOfFrameHint(false);
setupMirrorTexture(); setupMirrorTexture();
mMainCameraGC = mainCamera->getGraphicsContext(); mainCamera->getGraphicsContext()->setSwapCallback(new VRViewer::SwapBuffersCallback(this));
mMainCameraGC->setSwapCallback(new VRViewer::SwapBuffersCallback(this)); mainCamera->setPreDrawCallback(mPreDraw);
mainCamera->setGraphicsContext(nullptr); mainCamera->setPostDrawCallback(mPostDraw);
mainCamera->setCullCallback(new CullCallback);
mConfigured = true; mConfigured = true;
Log(Debug::Verbose) << "Realized"; Log(Debug::Verbose) << "Realized";
} }
VRView* VRViewer::getView(std::string name)
{
auto it = mViews.find(name);
if (it != mViews.end())
return it->second.get();
return nullptr;
}
void VRViewer::setupMirrorTexture() void VRViewer::setupMirrorTexture()
{ {
mMirrorTextureEnabled = Settings::Manager::getBool("mirror texture", "VR"); mMirrorTextureEnabled = Settings::Manager::getBool("mirror texture", "VR");
@ -228,58 +240,55 @@ namespace MWVR
setupMirrorTexture(); setupMirrorTexture();
} }
void VRViewer::enableMainCamera(void) SubImage VRViewer::subImage(Side side)
{ {
mCameras["MainCamera"]->setGraphicsContext(mMainCameraGC); return mSubImages[static_cast<int>(side)];
}
void VRViewer::disableMainCamera(void)
{
mCameras["MainCamera"]->setGraphicsContext(nullptr);
} }
void VRViewer::blitEyesToMirrorTexture(osg::GraphicsContext* gc) void VRViewer::blitEyesToMirrorTexture(osg::GraphicsContext* gc)
{ {
if (mMirrorTextureShouldBeCleanedUp) //if (mMirrorTextureShouldBeCleanedUp)
{ //{
mMirrorTexture.reset(nullptr); // mMirrorTexture.reset(nullptr);
mMsaaResolveMirrorTexture.clear(); // mMsaaResolveMirrorTexture.clear();
mMirrorTextureShouldBeCleanedUp = false; // mMirrorTextureShouldBeCleanedUp = false;
} //}
if (!mMirrorTextureEnabled) //if (!mMirrorTextureEnabled)
return; // return;
if (!mMirrorTexture) //if (!mMirrorTexture)
{ //{
mMirrorTexture.reset(new VRFramebuffer(gc->getState(), mCameras["MainCamera"]->getViewport()->width(), mCameras["MainCamera"]->getViewport()->height(), 0)); // mMirrorTexture.reset(new VRFramebuffer(gc->getState(), mCameras["MainCamera"]->getViewport()->width(), mCameras["MainCamera"]->getViewport()->height(), 0));
} //}
auto* state = gc->getState(); auto* state = gc->getState();
auto* gl = osg::GLExtensions::Get(state->getContextID(), false); auto* gl = osg::GLExtensions::Get(state->getContextID(), false);
int screenWidth = mCameras["MainCamera"]->getViewport()->width(); //auto mainCamera = mViewer->getCamera();
int mirrorWidth = screenWidth / mMirrorTextureViews.size(); //int screenWidth = mainCamera->getViewport()->width();
int screenHeight = mCameras["MainCamera"]->getViewport()->height(); //int mirrorWidth = screenWidth / mMirrorTextureViews.size();
//int screenHeight = mainCamera->getViewport()->height();
// Since OpenXR does not include native support for mirror textures, we have to generate them ourselves //// Since OpenXR does not include native support for mirror textures, we have to generate them ourselves
// which means resolving msaa twice. //// which means resolving msaa twice.
for (unsigned i = 0; i < mMirrorTextureViews.size(); i++) //for (unsigned i = 0; i < mMirrorTextureViews.size(); i++)
{ //{
auto& view = mViews[mMirrorTextureViews[i]]; // if(!mMsaaResolveMirrorTexture)
if(!mMsaaResolveMirrorTexture[mMirrorTextureViews[i]]) // mMsaaResolveMirrorTexture.reset(new VRFramebuffer(gc->getState(),
mMsaaResolveMirrorTexture[mMirrorTextureViews[i]].reset(new VRFramebuffer(gc->getState(), // mSwapchain->width(),
view->swapchain().width(), // mSwapchain->height(),
view->swapchain().height(), // 0));
0));
auto& resolveTexture = *mMsaaResolveMirrorTexture[mMirrorTextureViews[i]]; // auto& resolveTexture = *mMsaaResolveMirrorTexture[mMirrorTextureViews[i]];
resolveTexture.bindFramebuffer(gc, GL_FRAMEBUFFER_EXT); // resolveTexture.bindFramebuffer(gc, GL_FRAMEBUFFER_EXT);
view->swapchain().renderBuffer()->blit(gc, 0, 0, resolveTexture.width(), resolveTexture.height()); // view->swapchain().renderBuffer()->blit(gc, 0, 0, resolveTexture.width(), resolveTexture.height());
mMirrorTexture->bindFramebuffer(gc, GL_FRAMEBUFFER_EXT); // mMirrorTexture->bindFramebuffer(gc, GL_FRAMEBUFFER_EXT);
resolveTexture.blit(gc, i * mirrorWidth, 0, (i + 1) * mirrorWidth, screenHeight); // resolveTexture.blit(gc, i * mirrorWidth, 0, (i + 1) * mirrorWidth, screenHeight);
} //}
gl->glBindFramebuffer(GL_FRAMEBUFFER_EXT, 0); gl->glBindFramebuffer(GL_FRAMEBUFFER_EXT, 0);
mMirrorTexture->blit(gc, 0, 0, screenWidth, screenHeight); //mMirrorTexture->blit(gc, 0, 0, screenWidth, screenHeight);
mSwapchain->endFrame(gc);
} }
void void
@ -305,18 +314,14 @@ namespace MWVR
void VRViewer::preDrawCallback(osg::RenderInfo& info) void VRViewer::preDrawCallback(osg::RenderInfo& info)
{ {
auto* camera = info.getCurrentCamera(); if(Environment::get().getSession()->getFrame(VRSession::FramePhase::Draw)->mShouldRender)
auto name = camera->getName(); mSwapchain->beginFrame(info.getState()->getGraphicsContext());
mViews[name]->prerenderCallback(info);
} }
void VRViewer::postDrawCallback(osg::RenderInfo& info) void VRViewer::postDrawCallback(osg::RenderInfo& info)
{ {
auto* camera = info.getCurrentCamera(); auto* camera = info.getCurrentCamera();
auto name = camera->getName(); auto name = camera->getName();
auto& view = mViews[name];
view->postrenderCallback(info);
// This happens sometimes, i've not been able to catch it when as happens // This happens sometimes, i've not been able to catch it when as happens
// to see why and how i can stop it. // to see why and how i can stop it.

View file

@ -18,6 +18,7 @@ namespace MWVR
{ {
class VRFramebuffer; class VRFramebuffer;
class VRView; class VRView;
class OpenXRSwapchain;
/// \brief Manages stereo rendering and mirror texturing. /// \brief Manages stereo rendering and mirror texturing.
/// ///
@ -64,6 +65,12 @@ namespace MWVR
VRViewer* mViewer; VRViewer* mViewer;
}; };
class InitialDrawCallback : public osg::Camera::DrawCallback
{
public:
virtual void operator()(osg::RenderInfo& renderInfo) const;
};
static const std::array<const char*, 2> sViewNames; static const std::array<const char*, 2> sViewNames;
enum class MirrorTextureEye enum class MirrorTextureEye
{ {
@ -84,33 +91,30 @@ namespace MWVR
void blitEyesToMirrorTexture(osg::GraphicsContext* gc); void blitEyesToMirrorTexture(osg::GraphicsContext* gc);
void realize(osg::GraphicsContext* gc); void realize(osg::GraphicsContext* gc);
bool realized() { return mConfigured; } bool realized() { return mConfigured; }
VRView* getView(std::string name);
VrShadow& vrShadow() { return mVrShadow; }
void setupMirrorTexture(); void setupMirrorTexture();
void processChangedSettings(const std::set< std::pair<std::string, std::string> >& changed); void processChangedSettings(const std::set< std::pair<std::string, std::string> >& changed);
void enableMainCamera(void); SubImage subImage(Side side);
void disableMainCamera(void);
private: private:
std::mutex mMutex{};
bool mConfigured{ false };
osg::ref_ptr<osgViewer::Viewer> mViewer = nullptr; osg::ref_ptr<osgViewer::Viewer> mViewer = nullptr;
std::map<std::string, osg::ref_ptr<VRView> > mViews;
std::map<std::string, osg::ref_ptr<osg::Camera> > mCameras{};
osg::ref_ptr<PredrawCallback> mPreDraw{ nullptr }; osg::ref_ptr<PredrawCallback> mPreDraw{ nullptr };
osg::ref_ptr<PostdrawCallback> mPostDraw{ nullptr }; osg::ref_ptr<PostdrawCallback> mPostDraw{ nullptr };
osg::GraphicsContext* mMainCameraGC{ nullptr };
std::map< std::string, std::unique_ptr<VRFramebuffer> > mMsaaResolveMirrorTexture; std::unique_ptr<VRFramebuffer> mMsaaResolveMirrorTexture;
std::unique_ptr<VRFramebuffer> mMirrorTexture; std::unique_ptr<VRFramebuffer> mMirrorTexture;
VrShadow mVrShadow;
std::mutex mMutex{};
bool mConfigured{ false };
std::vector<std::string> mMirrorTextureViews; std::vector<std::string> mMirrorTextureViews;
bool mMirrorTextureShouldBeCleanedUp{ false }; bool mMirrorTextureShouldBeCleanedUp{ false };
bool mMirrorTextureEnabled{ false }; bool mMirrorTextureEnabled{ false };
bool mFlipMirrorTextureOrder{ false }; bool mFlipMirrorTextureOrder{ false };
MirrorTextureEye mMirrorTextureEye{ MirrorTextureEye::Both }; MirrorTextureEye mMirrorTextureEye{ MirrorTextureEye::Both };
std::unique_ptr<OpenXRSwapchain> mSwapchain;
std::array<SubImage, 2> mSubImages;
SwapchainConfig mSwapchainConfig;
}; };
} }

View file

@ -26,7 +26,7 @@ namespace
void addScripts(MWWorld::ContainerStore& store, MWWorld::CellStore* cell) void addScripts(MWWorld::ContainerStore& store, MWWorld::CellStore* cell)
{ {
auto& scripts = MWBase::Environment::get().getWorld()->getLocalScripts(); auto& scripts = MWBase::Environment::get().getWorld()->getLocalScripts();
for(const MWWorld::Ptr& ptr : store) for(const auto&& ptr : store)
{ {
const std::string& script = ptr.getClass().getScript(ptr); const std::string& script = ptr.getClass().getScript(ptr);
if(!script.empty()) if(!script.empty())
@ -43,13 +43,10 @@ namespace
{ {
float sum = 0; float sum = 0;
for (typename MWWorld::CellRefList<T>::List::const_iterator iter ( for (const auto& iter : cellRefList.mList)
cellRefList.mList.begin());
iter!=cellRefList.mList.end();
++iter)
{ {
if (iter->mData.getCount()>0) if (iter.mData.getCount()>0)
sum += iter->mData.getCount()*iter->mBase->mData.mWeight; sum += iter.mData.getCount()*iter.mBase->mData.mWeight;
} }
return sum; return sum;
@ -62,12 +59,11 @@ namespace
store->resolve(); store->resolve();
std::string id2 = Misc::StringUtils::lowerCase (id); std::string id2 = Misc::StringUtils::lowerCase (id);
for (typename MWWorld::CellRefList<T>::List::iterator iter (list.mList.begin()); for (auto& iter : list.mList)
iter!=list.mList.end(); ++iter)
{ {
if (Misc::StringUtils::ciEqual(iter->mBase->mId, id2) && iter->mData.getCount()) if (Misc::StringUtils::ciEqual(iter.mBase->mId, id2) && iter.mData.getCount())
{ {
MWWorld::Ptr ptr (&*iter, nullptr); MWWorld::Ptr ptr (&iter, nullptr);
ptr.setContainerStore (store); ptr.setContainerStore (store);
return ptr; return ptr;
} }
@ -81,7 +77,7 @@ MWWorld::ResolutionListener::~ResolutionListener()
{ {
if(!mStore.mModified && mStore.mResolved && !mStore.mPtr.isEmpty()) if(!mStore.mModified && mStore.mResolved && !mStore.mPtr.isEmpty())
{ {
for(const MWWorld::Ptr& ptr : mStore) for(const auto&& ptr : mStore)
ptr.getRefData().setCount(0); ptr.getRefData().setCount(0);
mStore.fillNonRandom(mStore.mPtr.get<ESM::Container>()->mBase->mInventory, "", mStore.mSeed); mStore.fillNonRandom(mStore.mPtr.get<ESM::Container>()->mBase->mInventory, "", mStore.mSeed);
addScripts(mStore, mStore.mPtr.mCell); addScripts(mStore, mStore.mPtr.mCell);
@ -127,15 +123,14 @@ template<typename T>
void MWWorld::ContainerStore::storeStates (const CellRefList<T>& collection, void MWWorld::ContainerStore::storeStates (const CellRefList<T>& collection,
ESM::InventoryState& inventory, int& index, bool equipable) const ESM::InventoryState& inventory, int& index, bool equipable) const
{ {
for (typename CellRefList<T>::List::const_iterator iter (collection.mList.begin()); for (const auto& iter : collection.mList)
iter!=collection.mList.end(); ++iter)
{ {
if (iter->mData.getCount() == 0) if (iter.mData.getCount() == 0)
continue; continue;
ESM::ObjectState state; ESM::ObjectState state;
storeState (*iter, state); storeState (iter, state);
if (equipable) if (equipable)
storeEquipmentState(*iter, index, inventory); storeEquipmentState(iter, index, inventory);
inventory.mItems.push_back (state); inventory.mItems.push_back (state);
++index; ++index;
} }
@ -188,7 +183,7 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::end()
int MWWorld::ContainerStore::count(const std::string &id) const int MWWorld::ContainerStore::count(const std::string &id) const
{ {
int total=0; int total=0;
for (const auto& iter : *this) for (const auto&& iter : *this)
if (Misc::StringUtils::ciEqual(iter.getCellRef().getRefId(), id)) if (Misc::StringUtils::ciEqual(iter.getCellRef().getRefId(), id))
total += iter.getRefData().getCount(); total += iter.getRefData().getCount();
return total; return total;
@ -430,14 +425,14 @@ void MWWorld::ContainerStore::rechargeItems(float duration)
updateRechargingItems(); updateRechargingItems();
mRechargingItemsUpToDate = true; mRechargingItemsUpToDate = true;
} }
for (TRechargingItems::iterator it = mRechargingItems.begin(); it != mRechargingItems.end(); ++it) for (auto& it : mRechargingItems)
{ {
if (!MWMechanics::rechargeItem(*it->first, it->second, duration)) if (!MWMechanics::rechargeItem(*it.first, it.second, duration))
continue; continue;
// attempt to restack when fully recharged // attempt to restack when fully recharged
if (it->first->getCellRef().getEnchantmentCharge() == it->second) if (it.first->getCellRef().getEnchantmentCharge() == it.second)
it->first = restack(*it->first); it.first = restack(*it.first);
} }
} }
@ -481,9 +476,9 @@ int MWWorld::ContainerStore::remove(const std::string& itemId, int count, const
bool MWWorld::ContainerStore::hasVisibleItems() const bool MWWorld::ContainerStore::hasVisibleItems() const
{ {
for (auto iter(begin()); iter != end(); ++iter) for (const auto&& iter : *this)
{ {
if (iter->getClass().showsInInventory(*iter)) if (iter.getClass().showsInInventory(iter))
return true; return true;
} }
@ -601,8 +596,8 @@ void MWWorld::ContainerStore::addInitialItemImp(const MWWorld::Ptr& ptr, const s
void MWWorld::ContainerStore::clear() void MWWorld::ContainerStore::clear()
{ {
for (ContainerStoreIterator iter (begin()); iter!=end(); ++iter) for (auto&& iter : *this)
iter->getRefData().setCount (0); iter.getRefData().setCount (0);
flagAsModified(); flagAsModified();
mModified = true; mModified = true;
@ -623,7 +618,7 @@ void MWWorld::ContainerStore::resolve()
{ {
if(!mResolved && !mPtr.isEmpty()) if(!mResolved && !mPtr.isEmpty())
{ {
for(const MWWorld::Ptr& ptr : *this) for(const auto&& ptr : *this)
ptr.getRefData().setCount(0); ptr.getRefData().setCount(0);
Misc::Rng::Seed seed{mSeed}; Misc::Rng::Seed seed{mSeed};
fill(mPtr.get<ESM::Container>()->mBase->mInventory, "", seed); fill(mPtr.get<ESM::Container>()->mBase->mInventory, "", seed);
@ -644,7 +639,7 @@ MWWorld::ResolutionHandle MWWorld::ContainerStore::resolveTemporarily()
} }
if(!mResolved && !mPtr.isEmpty()) if(!mResolved && !mPtr.isEmpty())
{ {
for(const MWWorld::Ptr& ptr : *this) for(const auto&& ptr : *this)
ptr.getRefData().setCount(0); ptr.getRefData().setCount(0);
Misc::Rng::Seed seed{mSeed}; Misc::Rng::Seed seed{mSeed};
fill(mPtr.get<ESM::Container>()->mBase->mInventory, "", seed); fill(mPtr.get<ESM::Container>()->mBase->mInventory, "", seed);
@ -727,10 +722,10 @@ MWWorld::Ptr MWWorld::ContainerStore::findReplacement(const std::string& id)
{ {
MWWorld::Ptr item; MWWorld::Ptr item;
int itemHealth = 1; int itemHealth = 1;
for (MWWorld::ContainerStoreIterator iter = begin(); iter != end(); ++iter) for (auto&& iter : *this)
{ {
int iterHealth = iter->getClass().hasItemHealth(*iter) ? iter->getClass().getItemHealth(*iter) : 1; int iterHealth = iter.getClass().hasItemHealth(iter) ? iter.getClass().getItemHealth(iter) : 1;
if (Misc::StringUtils::ciEqual(iter->getCellRef().getRefId(), id)) if (Misc::StringUtils::ciEqual(iter.getCellRef().getRefId(), id))
{ {
// Prefer the stack with the lowest remaining uses // Prefer the stack with the lowest remaining uses
// Try to get item with zero durability only if there are no other items found // Try to get item with zero durability only if there are no other items found
@ -738,7 +733,7 @@ MWWorld::Ptr MWWorld::ContainerStore::findReplacement(const std::string& id)
(iterHealth > 0 && iterHealth < itemHealth) || (iterHealth > 0 && iterHealth < itemHealth) ||
(itemHealth <= 0 && iterHealth > 0)) (itemHealth <= 0 && iterHealth > 0))
{ {
item = *iter; item = iter;
itemHealth = iterHealth; itemHealth = iterHealth;
} }
} }
@ -867,11 +862,8 @@ void MWWorld::ContainerStore::readState (const ESM::InventoryState& inventory)
mResolved = true; mResolved = true;
int index = 0; int index = 0;
for (std::vector<ESM::ObjectState>::const_iterator for (const ESM::ObjectState& state : inventory.mItems)
iter (inventory.mItems.begin()); iter!=inventory.mItems.end(); ++iter)
{ {
const ESM::ObjectState& state = *iter;
int type = MWBase::Environment::get().getWorld()->getStore().find(state.mRef.mRefID); int type = MWBase::Environment::get().getWorld()->getStore().find(state.mRef.mRefID);
int thisIndex = index++; int thisIndex = index++;

View file

@ -2,6 +2,7 @@
#include <iomanip> #include <iomanip>
#include <memory>
#include <osg/PositionAttitudeTransform> #include <osg/PositionAttitudeTransform>
#include <components/debug/debuglog.hpp> #include <components/debug/debuglog.hpp>
@ -43,6 +44,7 @@
#include "../mwsound/sound.hpp" #include "../mwsound/sound.hpp"
#include "../mwphysics/physicssystem.hpp" #include "../mwphysics/physicssystem.hpp"
#include "../mwphysics/projectile.hpp"
#ifdef USE_OPENXR #ifdef USE_OPENXR
#include "../mwvr/vrenvironment.hpp" #include "../mwvr/vrenvironment.hpp"
@ -298,7 +300,7 @@ namespace MWWorld
else else
state.mActorId = -1; state.mActorId = -1;
std::string texture = ""; std::string texture;
state.mEffects = getMagicBoltData(state.mIdMagic, state.mSoundIds, state.mSpeed, texture, state.mSourceName, state.mSpellId); state.mEffects = getMagicBoltData(state.mIdMagic, state.mSoundIds, state.mSpeed, texture, state.mSourceName, state.mSpellId);
@ -316,6 +318,7 @@ namespace MWWorld
MWWorld::Ptr ptr = ref.getPtr(); MWWorld::Ptr ptr = ref.getPtr();
osg::Vec4 lightDiffuseColor = getMagicBoltLightDiffuseColor(state.mEffects); osg::Vec4 lightDiffuseColor = getMagicBoltLightDiffuseColor(state.mEffects);
createModel(state, ptr.getClass().getModel(ptr), pos, orient, true, true, lightDiffuseColor, texture); createModel(state, ptr.getClass().getModel(ptr), pos, orient, true, true, lightDiffuseColor, texture);
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
@ -327,6 +330,8 @@ namespace MWWorld
state.mSounds.push_back(sound); state.mSounds.push_back(sound);
} }
state.mProjectileId = mPhysics->addProjectile(caster, pos);
state.mToDelete = false;
mMagicBolts.push_back(state); mMagicBolts.push_back(state);
} }
@ -339,7 +344,6 @@ namespace MWWorld
state.mIdArrow = projectile.getCellRef().getRefId(); state.mIdArrow = projectile.getCellRef().getRefId();
state.mCasterHandle = actor; state.mCasterHandle = actor;
state.mAttackStrength = attackStrength; state.mAttackStrength = attackStrength;
int type = projectile.get<ESM::Weapon>()->mBase->mData.mType; int type = projectile.get<ESM::Weapon>()->mBase->mData.mType;
state.mThrown = MWMechanics::getWeaponType(type)->mWeaponClass == ESM::WeaponType::Thrown; state.mThrown = MWMechanics::getWeaponType(type)->mWeaponClass == ESM::WeaponType::Thrown;
@ -350,6 +354,8 @@ namespace MWWorld
if (!ptr.getClass().getEnchantment(ptr).empty()) if (!ptr.getClass().getEnchantment(ptr).empty())
SceneUtil::addEnchantedGlow(state.mNode, mResourceSystem, ptr.getClass().getEnchantmentColor(ptr)); SceneUtil::addEnchantedGlow(state.mNode, mResourceSystem, ptr.getClass().getEnchantmentColor(ptr));
state.mProjectileId = mPhysics->addProjectile(actor, pos);
state.mToDelete = false;
mProjectiles.push_back(state); mProjectiles.push_back(state);
} }
@ -374,63 +380,58 @@ namespace MWWorld
return (state.mNode->getPosition() - playerPos).length2() >= farawayThreshold*farawayThreshold; return (state.mNode->getPosition() - playerPos).length2() >= farawayThreshold*farawayThreshold;
}; };
for (std::vector<ProjectileState>::iterator it = mProjectiles.begin(); it != mProjectiles.end();) for (auto& projectileState : mProjectiles)
{ {
if (isCleanable(*it)) if (isCleanable(projectileState))
{ cleanupProjectile(projectileState);
cleanupProjectile(*it);
it = mProjectiles.erase(it);
}
else
++it;
} }
for (std::vector<MagicBoltState>::iterator it = mMagicBolts.begin(); it != mMagicBolts.end();) for (auto& magicBoltState : mMagicBolts)
{ {
if (isCleanable(*it)) if (isCleanable(magicBoltState))
{ cleanupMagicBolt(magicBoltState);
cleanupMagicBolt(*it);
it = mMagicBolts.erase(it);
}
else
++it;
} }
} }
} }
void ProjectileManager::moveMagicBolts(float duration) void ProjectileManager::moveMagicBolts(float duration)
{ {
for (std::vector<MagicBoltState>::iterator it = mMagicBolts.begin(); it != mMagicBolts.end();) for (auto& magicBoltState : mMagicBolts)
{ {
if (magicBoltState.mToDelete)
continue;
const auto* projectile = mPhysics->getProjectile(magicBoltState.mProjectileId);
if (!projectile->isActive())
continue;
// If the actor caster is gone, the magic bolt needs to be removed from the scene during the next frame. // If the actor caster is gone, the magic bolt needs to be removed from the scene during the next frame.
MWWorld::Ptr caster = it->getCaster(); MWWorld::Ptr caster = magicBoltState.getCaster();
if (!caster.isEmpty() && caster.getClass().isActor()) if (!caster.isEmpty() && caster.getClass().isActor())
{ {
if (caster.getRefData().getCount() <= 0 || caster.getClass().getCreatureStats(caster).isDead()) if (caster.getRefData().getCount() <= 0 || caster.getClass().getCreatureStats(caster).isDead())
{ {
cleanupMagicBolt(*it); cleanupMagicBolt(magicBoltState);
it = mMagicBolts.erase(it);
continue; continue;
} }
} }
osg::Quat orient = it->mNode->getAttitude(); osg::Quat orient = magicBoltState.mNode->getAttitude();
static float fTargetSpellMaxSpeed = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>() static float fTargetSpellMaxSpeed = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>()
.find("fTargetSpellMaxSpeed")->mValue.getFloat(); .find("fTargetSpellMaxSpeed")->mValue.getFloat();
float speed = fTargetSpellMaxSpeed * it->mSpeed; float speed = fTargetSpellMaxSpeed * magicBoltState.mSpeed;
osg::Vec3f direction = orient * osg::Vec3f(0,1,0); osg::Vec3f direction = orient * osg::Vec3f(0,1,0);
direction.normalize(); direction.normalize();
osg::Vec3f pos(it->mNode->getPosition()); osg::Vec3f pos(magicBoltState.mNode->getPosition());
osg::Vec3f newPos = pos + direction * duration * speed; osg::Vec3f newPos = pos + direction * duration * speed;
for (size_t soundIter = 0; soundIter != it->mSounds.size(); soundIter++) for (const auto& sound : magicBoltState.mSounds)
{ sound->setPosition(newPos);
it->mSounds.at(soundIter)->setPosition(newPos);
}
it->mNode->setPosition(newPos); magicBoltState.mNode->setPosition(newPos);
update(*it, duration); mPhysics->updateProjectile(magicBoltState.mProjectileId, newPos);
update(magicBoltState, duration);
// For AI actors, get combat targets to use in the ray cast. Only those targets will return a positive hit result. // For AI actors, get combat targets to use in the ray cast. Only those targets will return a positive hit result.
std::vector<MWWorld::Ptr> targetActors; std::vector<MWWorld::Ptr> targetActors;
@ -439,7 +440,7 @@ namespace MWWorld
// Check for impact // Check for impact
// TODO: use a proper btRigidBody / btGhostObject? // TODO: use a proper btRigidBody / btGhostObject?
MWPhysics::RayCastingResult result = mPhysics->castRay(pos, newPos, caster, targetActors, 0xff, MWPhysics::CollisionType_Projectile); const auto result = mPhysics->castRay(pos, newPos, caster, targetActors, 0xff, MWPhysics::CollisionType_Projectile, magicBoltState.mProjectileId);
bool hit = false; bool hit = false;
if (result.mHit) if (result.mHit)
@ -447,16 +448,16 @@ namespace MWWorld
hit = true; hit = true;
if (result.mHitObject.isEmpty()) if (result.mHitObject.isEmpty())
{ {
// terrain // terrain or projectile
} }
else else
{ {
MWMechanics::CastSpell cast(caster, result.mHitObject); MWMechanics::CastSpell cast(caster, result.mHitObject);
cast.mHitPosition = pos; cast.mHitPosition = pos;
cast.mId = it->mSpellId; cast.mId = magicBoltState.mSpellId;
cast.mSourceName = it->mSourceName; cast.mSourceName = magicBoltState.mSourceName;
cast.mStack = false; cast.mStack = false;
cast.inflict(result.mHitObject, caster, it->mEffects, ESM::RT_Target, false, true); cast.inflict(result.mHitObject, caster, magicBoltState.mEffects, ESM::RT_Target, false, true);
mPhysics->reportCollision(Misc::Convert::toBullet(result.mHitPos), Misc::Convert::toBullet(result.mHitNormal)); mPhysics->reportCollision(Misc::Convert::toBullet(result.mHitPos), Misc::Convert::toBullet(result.mHitNormal));
} }
} }
@ -467,47 +468,46 @@ namespace MWWorld
if (hit) if (hit)
{ {
MWBase::Environment::get().getWorld()->explodeSpell(pos, it->mEffects, caster, result.mHitObject, MWBase::Environment::get().getWorld()->explodeSpell(pos, magicBoltState.mEffects, caster, result.mHitObject,
ESM::RT_Target, it->mSpellId, it->mSourceName); ESM::RT_Target, magicBoltState.mSpellId, magicBoltState.mSourceName);
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); cleanupMagicBolt(magicBoltState);
for (size_t soundIter = 0; soundIter != it->mSounds.size(); soundIter++)
sndMgr->stopSound(it->mSounds.at(soundIter));
mParent->removeChild(it->mNode);
it = mMagicBolts.erase(it);
continue;
} }
else
++it;
} }
} }
void ProjectileManager::moveProjectiles(float duration) void ProjectileManager::moveProjectiles(float duration)
{ {
for (std::vector<ProjectileState>::iterator it = mProjectiles.begin(); it != mProjectiles.end();) for (auto& projectileState : mProjectiles)
{ {
if (projectileState.mToDelete)
continue;
const auto* projectile = mPhysics->getProjectile(projectileState.mProjectileId);
if (!projectile->isActive())
continue;
// gravity constant - must be way lower than the gravity affecting actors, since we're not // gravity constant - must be way lower than the gravity affecting actors, since we're not
// simulating aerodynamics at all // simulating aerodynamics at all
it->mVelocity -= osg::Vec3f(0, 0, Constants::GravityConst * Constants::UnitsPerMeter * 0.1f) * duration; projectileState.mVelocity -= osg::Vec3f(0, 0, Constants::GravityConst * Constants::UnitsPerMeter * 0.1f) * duration;
osg::Vec3f pos(it->mNode->getPosition()); osg::Vec3f pos(projectileState.mNode->getPosition());
osg::Vec3f newPos = pos + it->mVelocity * duration; osg::Vec3f newPos = pos + projectileState.mVelocity * duration;
// rotation does not work well for throwing projectiles - their roll angle will depend on shooting direction. // rotation does not work well for throwing projectiles - their roll angle will depend on shooting direction.
if (!it->mThrown) if (!projectileState.mThrown)
{ {
osg::Quat orient; osg::Quat orient;
orient.makeRotate(osg::Vec3f(0,1,0), it->mVelocity); orient.makeRotate(osg::Vec3f(0,1,0), projectileState.mVelocity);
it->mNode->setAttitude(orient); projectileState.mNode->setAttitude(orient);
} }
it->mNode->setPosition(newPos); projectileState.mNode->setPosition(newPos);
update(*it, duration); mPhysics->updateProjectile(projectileState.mProjectileId, newPos);
MWWorld::Ptr caster = it->getCaster(); update(projectileState, duration);
MWWorld::Ptr caster = projectileState.getCaster();
// For AI actors, get combat targets to use in the ray cast. Only those targets will return a positive hit result. // For AI actors, get combat targets to use in the ray cast. Only those targets will return a positive hit result.
std::vector<MWWorld::Ptr> targetActors; std::vector<MWWorld::Ptr> targetActors;
@ -516,50 +516,149 @@ namespace MWWorld
// Check for impact // Check for impact
// TODO: use a proper btRigidBody / btGhostObject? // TODO: use a proper btRigidBody / btGhostObject?
MWPhysics::RayCastingResult result = mPhysics->castRay(pos, newPos, caster, targetActors, 0xff, MWPhysics::CollisionType_Projectile); const auto result = mPhysics->castRay(pos, newPos, caster, targetActors, 0xff, MWPhysics::CollisionType_Projectile, projectileState.mProjectileId);
bool underwater = MWBase::Environment::get().getWorld()->isUnderwater(MWMechanics::getPlayer().getCell(), newPos); bool underwater = MWBase::Environment::get().getWorld()->isUnderwater(MWMechanics::getPlayer().getCell(), newPos);
if (result.mHit || underwater) if (result.mHit || underwater)
{ {
MWWorld::ManualRef projectileRef(MWBase::Environment::get().getWorld()->getStore(), it->mIdArrow);
// Try to get a Ptr to the bow that was used. It might no longer exist. // Try to get a Ptr to the bow that was used. It might no longer exist.
MWWorld::ManualRef projectileRef(MWBase::Environment::get().getWorld()->getStore(), projectileState.mIdArrow);
MWWorld::Ptr bow = projectileRef.getPtr(); MWWorld::Ptr bow = projectileRef.getPtr();
if (!caster.isEmpty() && it->mIdArrow != it->mBowId) if (!caster.isEmpty() && projectileState.mIdArrow != projectileState.mBowId)
{ {
MWWorld::InventoryStore& inv = caster.getClass().getInventoryStore(caster); MWWorld::InventoryStore& inv = caster.getClass().getInventoryStore(caster);
MWWorld::ContainerStoreIterator invIt = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); MWWorld::ContainerStoreIterator invIt = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
if (invIt != inv.end() && Misc::StringUtils::ciEqual(invIt->getCellRef().getRefId(), it->mBowId)) if (invIt != inv.end() && Misc::StringUtils::ciEqual(invIt->getCellRef().getRefId(), projectileState.mBowId))
bow = *invIt; bow = *invIt;
} }
if (caster.isEmpty()) if (caster.isEmpty())
caster = result.mHitObject; caster = result.mHitObject;
MWMechanics::projectileHit(caster, result.mHitObject, bow, projectileRef.getPtr(), result.mHit ? result.mHitPos : newPos, it->mAttackStrength); MWMechanics::projectileHit(caster, result.mHitObject, bow, projectileRef.getPtr(), result.mHit ? result.mHitPos : newPos, projectileState.mAttackStrength);
mPhysics->reportCollision(Misc::Convert::toBullet(result.mHitPos), Misc::Convert::toBullet(result.mHitNormal)); mPhysics->reportCollision(Misc::Convert::toBullet(result.mHitPos), Misc::Convert::toBullet(result.mHitNormal));
if (underwater) if (underwater)
mRendering->emitWaterRipple(newPos); mRendering->emitWaterRipple(newPos);
mParent->removeChild(it->mNode); cleanupProjectile(projectileState);
it = mProjectiles.erase(it); }
}
}
void ProjectileManager::processHits()
{
for (auto& projectileState : mProjectiles)
{
if (projectileState.mToDelete)
continue;
auto* projectile = mPhysics->getProjectile(projectileState.mProjectileId);
if (projectile->isActive())
continue;
const auto target = projectile->getTarget();
const auto pos = projectile->getHitPos();
MWWorld::Ptr caster = projectileState.getCaster();
assert(target != caster);
if (!isValidTarget(caster, target))
{
projectile->activate();
continue; continue;
} }
++it; if (caster.isEmpty())
caster = target;
// Try to get a Ptr to the bow that was used. It might no longer exist.
MWWorld::ManualRef projectileRef(MWBase::Environment::get().getWorld()->getStore(), projectileState.mIdArrow);
MWWorld::Ptr bow = projectileRef.getPtr();
if (!caster.isEmpty() && projectileState.mIdArrow != projectileState.mBowId)
{
MWWorld::InventoryStore& inv = caster.getClass().getInventoryStore(caster);
MWWorld::ContainerStoreIterator invIt = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
if (invIt != inv.end() && Misc::StringUtils::ciEqual(invIt->getCellRef().getRefId(), projectileState.mBowId))
bow = *invIt;
}
projectileState.mHitPosition = pos;
cleanupProjectile(projectileState);
MWMechanics::projectileHit(caster, target, bow, projectileRef.getPtr(), pos, projectileState.mAttackStrength);
} }
for (auto& magicBoltState : mMagicBolts)
{
if (magicBoltState.mToDelete)
continue;
auto* projectile = mPhysics->getProjectile(magicBoltState.mProjectileId);
if (projectile->isActive())
continue;
const auto target = projectile->getTarget();
const auto pos = projectile->getHitPos();
MWWorld::Ptr caster = magicBoltState.getCaster();
assert(target != caster);
if (!isValidTarget(caster, target))
{
projectile->activate();
continue;
}
magicBoltState.mHitPosition = pos;
cleanupMagicBolt(magicBoltState);
MWMechanics::CastSpell cast(caster, target);
cast.mHitPosition = pos;
cast.mId = magicBoltState.mSpellId;
cast.mSourceName = magicBoltState.mSourceName;
cast.mStack = false;
cast.inflict(target, caster, magicBoltState.mEffects, ESM::RT_Target, false, true);
MWBase::Environment::get().getWorld()->explodeSpell(pos, magicBoltState.mEffects, caster, target, ESM::RT_Target, magicBoltState.mSpellId, magicBoltState.mSourceName);
}
mProjectiles.erase(std::remove_if(mProjectiles.begin(), mProjectiles.end(), [](const State& state) { return state.mToDelete; }),
mProjectiles.end());
mMagicBolts.erase(std::remove_if(mMagicBolts.begin(), mMagicBolts.end(), [](const State& state) { return state.mToDelete; }),
mMagicBolts.end());
}
bool ProjectileManager::isValidTarget(const MWWorld::Ptr& caster, const MWWorld::Ptr& target)
{
// For AI actors, get combat targets to use in the ray cast. Only those targets will return a positive hit result.
std::vector<MWWorld::Ptr> targetActors;
if (!caster.isEmpty() && caster.getClass().isActor() && caster != MWMechanics::getPlayer())
{
caster.getClass().getCreatureStats(caster).getAiSequence().getCombatTargets(targetActors);
if (!targetActors.empty())
{
bool validTarget = false;
for (MWWorld::Ptr& targetActor : targetActors)
{
if (targetActor == target)
{
validTarget = true;
break;
}
}
return validTarget;
}
}
return true;
} }
void ProjectileManager::cleanupProjectile(ProjectileManager::ProjectileState& state) void ProjectileManager::cleanupProjectile(ProjectileManager::ProjectileState& state)
{ {
mParent->removeChild(state.mNode); mParent->removeChild(state.mNode);
mPhysics->removeProjectile(state.mProjectileId);
state.mToDelete = true;
} }
void ProjectileManager::cleanupMagicBolt(ProjectileManager::MagicBoltState& state) void ProjectileManager::cleanupMagicBolt(ProjectileManager::MagicBoltState& state)
{ {
mParent->removeChild(state.mNode); mParent->removeChild(state.mNode);
mPhysics->removeProjectile(state.mProjectileId);
state.mToDelete = true;
for (size_t soundIter = 0; soundIter != state.mSounds.size(); soundIter++) for (size_t soundIter = 0; soundIter != state.mSounds.size(); soundIter++)
{ {
MWBase::Environment::get().getSoundManager()->stopSound(state.mSounds.at(soundIter)); MWBase::Environment::get().getSoundManager()->stopSound(state.mSounds.at(soundIter));
@ -568,15 +667,12 @@ namespace MWWorld
void ProjectileManager::clear() void ProjectileManager::clear()
{ {
for (std::vector<ProjectileState>::iterator it = mProjectiles.begin(); it != mProjectiles.end(); ++it) for (auto& mProjectile : mProjectiles)
{ cleanupProjectile(mProjectile);
cleanupProjectile(*it);
}
mProjectiles.clear(); mProjectiles.clear();
for (std::vector<MagicBoltState>::iterator it = mMagicBolts.begin(); it != mMagicBolts.end(); ++it)
{ for (auto& mMagicBolt : mMagicBolts)
cleanupMagicBolt(*it); cleanupMagicBolt(mMagicBolt);
}
mMagicBolts.clear(); mMagicBolts.clear();
} }
@ -633,6 +729,7 @@ namespace MWWorld
state.mVelocity = esm.mVelocity; state.mVelocity = esm.mVelocity;
state.mIdArrow = esm.mId; state.mIdArrow = esm.mId;
state.mAttackStrength = esm.mAttackStrength; state.mAttackStrength = esm.mAttackStrength;
state.mToDelete = false;
std::string model; std::string model;
try try
@ -640,9 +737,10 @@ namespace MWWorld
MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), esm.mId); MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), esm.mId);
MWWorld::Ptr ptr = ref.getPtr(); MWWorld::Ptr ptr = ref.getPtr();
model = ptr.getClass().getModel(ptr); model = ptr.getClass().getModel(ptr);
int weaponType = ptr.get<ESM::Weapon>()->mBase->mData.mType; int weaponType = ptr.get<ESM::Weapon>()->mBase->mData.mType;
state.mThrown = MWMechanics::getWeaponType(weaponType)->mWeaponClass == ESM::WeaponType::Thrown; state.mThrown = MWMechanics::getWeaponType(weaponType)->mWeaponClass == ESM::WeaponType::Thrown;
state.mProjectileId = mPhysics->addProjectile(state.getCaster(), osg::Vec3f(esm.mPosition));
} }
catch(...) catch(...)
{ {
@ -654,7 +752,7 @@ namespace MWWorld
mProjectiles.push_back(state); mProjectiles.push_back(state);
return true; return true;
} }
else if (type == ESM::REC_MPRJ) if (type == ESM::REC_MPRJ)
{ {
ESM::MagicBoltState esm; ESM::MagicBoltState esm;
esm.load(reader); esm.load(reader);
@ -663,7 +761,8 @@ namespace MWWorld
state.mIdMagic.push_back(esm.mId); state.mIdMagic.push_back(esm.mId);
state.mSpellId = esm.mSpellId; state.mSpellId = esm.mSpellId;
state.mActorId = esm.mActorId; state.mActorId = esm.mActorId;
std::string texture = ""; state.mToDelete = false;
std::string texture;
try try
{ {
@ -686,6 +785,7 @@ namespace MWWorld
MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), state.mIdMagic.at(0)); MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), state.mIdMagic.at(0));
MWWorld::Ptr ptr = ref.getPtr(); MWWorld::Ptr ptr = ref.getPtr();
model = ptr.getClass().getModel(ptr); model = ptr.getClass().getModel(ptr);
state.mProjectileId = mPhysics->addProjectile(state.getCaster(), osg::Vec3f(esm.mPosition));
} }
catch(...) catch(...)
{ {

View file

@ -56,6 +56,8 @@ namespace MWWorld
void update(float dt); void update(float dt);
void processHits();
/// Removes all current projectiles. Should be called when switching to a new worldspace. /// Removes all current projectiles. Should be called when switching to a new worldspace.
void clear(); void clear();
@ -76,6 +78,9 @@ namespace MWWorld
std::shared_ptr<MWRender::EffectAnimationTime> mEffectAnimationTime; std::shared_ptr<MWRender::EffectAnimationTime> mEffectAnimationTime;
int mActorId; int mActorId;
int mProjectileId;
osg::Vec3f mHitPosition;
// TODO: this will break when the game is saved and reloaded, since there is currently // TODO: this will break when the game is saved and reloaded, since there is currently
// no way to write identifiers for non-actors to a savegame. // no way to write identifiers for non-actors to a savegame.
@ -88,6 +93,8 @@ namespace MWWorld
// MW-id of an arrow projectile // MW-id of an arrow projectile
std::string mIdArrow; std::string mIdArrow;
bool mToDelete;
}; };
struct MagicBoltState : public State struct MagicBoltState : public State
@ -125,6 +132,8 @@ namespace MWWorld
void moveProjectiles(float dt); void moveProjectiles(float dt);
void moveMagicBolts(float dt); void moveMagicBolts(float dt);
bool isValidTarget(const MWWorld::Ptr& caster, const MWWorld::Ptr& target);
void createModel (State& state, const std::string& model, const osg::Vec3f& pos, const osg::Quat& orient, void createModel (State& state, const std::string& model, const osg::Vec3f& pos, const osg::Quat& orient,
bool rotate, bool createLight, osg::Vec4 lightDiffuseColor, std::string texture = ""); bool rotate, bool createLight, osg::Vec4 lightDiffuseColor, std::string texture = "");
void update (State& state, float duration); void update (State& state, float duration);

View file

@ -1373,25 +1373,24 @@ namespace MWWorld
return; return;
} }
float terrainHeight = -std::numeric_limits<float>::max(); const float terrainHeight = ptr.getCell()->isExterior() ? getTerrainHeightAt(pos) : -std::numeric_limits<float>::max();
if (ptr.getCell()->isExterior()) pos.z() = std::max(pos.z(), terrainHeight + 20); // place slightly above terrain. will snap down to ground with code below
terrainHeight = getTerrainHeightAt(pos);
if (pos.z() < terrainHeight)
pos.z() = terrainHeight;
pos.z() += 20; // place slightly above. will snap down to ground with code below
// We still should trace down dead persistent actors - they do not use the "swimdeath" animation. // We still should trace down dead persistent actors - they do not use the "swimdeath" animation.
bool swims = ptr.getClass().isActor() && isSwimming(ptr) && !(ptr.getClass().isPersistent(ptr) && ptr.getClass().getCreatureStats(ptr).isDeathAnimationFinished()); bool swims = ptr.getClass().isActor() && isSwimming(ptr) && !(ptr.getClass().isPersistent(ptr) && ptr.getClass().getCreatureStats(ptr).isDeathAnimationFinished());
if (force || !ptr.getClass().isActor() || (!isFlying(ptr) && !swims && isActorCollisionEnabled(ptr))) if (force || !ptr.getClass().isActor() || (!isFlying(ptr) && !swims && isActorCollisionEnabled(ptr)))
{ {
osg::Vec3f traced = mPhysics->traceDown(ptr, pos, Constants::CellSizeInUnits); osg::Vec3f traced = mPhysics->traceDown(ptr, pos, Constants::CellSizeInUnits);
if (traced.z() < pos.z()) pos.z() = std::min(pos.z(), traced.z());
pos.z() = traced.z();
} }
moveObject(ptr, ptr.getCell(), pos.x(), pos.y(), pos.z()); moveObject(ptr, ptr.getCell(), pos.x(), pos.y(), pos.z());
if (force) // force physics to use the new position
{
auto actor = mPhysics->getActor(ptr);
if(actor)
actor->resetPosition();
}
} }
void World::fixPosition() void World::fixPosition()
@ -1490,17 +1489,11 @@ namespace MWWorld
ipos.pos[1] = spawnPoint.y(); ipos.pos[1] = spawnPoint.y();
ipos.pos[2] = spawnPoint.z(); ipos.pos[2] = spawnPoint.z();
if (!referenceObject.getClass().isActor()) if (referenceObject.getClass().isActor())
{
ipos.rot[0] = referenceObject.getRefData().getPosition().rot[0];
ipos.rot[1] = referenceObject.getRefData().getPosition().rot[1];
}
else
{ {
ipos.rot[0] = 0; ipos.rot[0] = 0;
ipos.rot[1] = 0; ipos.rot[1] = 0;
} }
ipos.rot[2] = referenceObject.getRefData().getPosition().rot[2];
MWWorld::Ptr placed = copyObjectToCell(ptr, referenceCell, ipos, ptr.getRefData().getCount(), false); MWWorld::Ptr placed = copyObjectToCell(ptr, referenceCell, ipos, ptr.getRefData().getCount(), false);
adjustPosition(placed, true); // snap to ground adjustPosition(placed, true); // snap to ground
@ -1545,6 +1538,7 @@ namespace MWWorld
mProjectileManager->update(duration); mProjectileManager->update(duration);
const auto results = mPhysics->applyQueuedMovement(duration, mDiscardMovements, frameStart, frameNumber, stats); const auto results = mPhysics->applyQueuedMovement(duration, mDiscardMovements, frameStart, frameNumber, stats);
mProjectileManager->processHits();
mDiscardMovements = false; mDiscardMovements = false;
for(const auto& [actor, position]: results) for(const auto& [actor, position]: results)

View file

@ -233,11 +233,19 @@ namespace Misc
, mTechnique(technique) , mTechnique(technique)
, mGeometryShaderMask(geometryShaderMask) , mGeometryShaderMask(geometryShaderMask)
, mNoShaderMask(noShaderMask) , mNoShaderMask(noShaderMask)
, mMasterConfig(new SharedShadowMapConfig)
, mSlaveConfig(new SharedShadowMapConfig)
, mSharedShadowMaps(Settings::Manager::getBool("shared shadow maps", "Stereo"))
{ {
if (technique == Technique::None) if (technique == Technique::None)
// Do nothing // Do nothing
return; return;
mMasterConfig->_id = "STEREO";
mMasterConfig->_master = true;
mSlaveConfig->_id = "STEREO";
mSlaveConfig->_master = false;
SceneUtil::FindByNameVisitor findScene("Scene Root"); SceneUtil::FindByNameVisitor findScene("Scene Root");
mRoot->accept(findScene); mRoot->accept(findScene);
mScene = findScene.mFoundNode; mScene = findScene.mFoundNode;
@ -292,6 +300,12 @@ namespace Misc
mRightCamera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); mRightCamera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
mRightCamera->setCullMask(mMainCamera->getCullMask()); mRightCamera->setCullMask(mMainCamera->getCullMask());
if (mSharedShadowMaps)
{
mLeftCamera->setUserData(mMasterConfig);
mRightCamera->setUserData(mSlaveConfig);
}
// Slave cameras must have their viewports defined immediately // Slave cameras must have their viewports defined immediately
auto width = mMainCamera->getViewport()->width(); auto width = mMainCamera->getViewport()->width();
auto height = mMainCamera->getViewport()->height(); auto height = mMainCamera->getViewport()->height();
@ -366,65 +380,66 @@ namespace Misc
auto width = mMainCamera->getViewport()->width(); auto width = mMainCamera->getViewport()->width();
auto height = mMainCamera->getViewport()->height(); auto height = mMainCamera->getViewport()->height();
// To correctly cull when drawing stereo using the geometry shader, the main camera must
// draw a fake view+perspective that includes the full frustums of both the left and right eyes.
// This frustum will be computed as a perspective frustum from a position P slightly behind the eyes L and R
// where it creates the minimum frustum encompassing both eyes' frustums.
// NOTE: I make an assumption that the eyes lie in a horizontal plane relative to the base view,
// and lie mirrored around the Y axis (straight ahead).
// Re-think this if that turns out to be a bad assumption.
View frustumView;
// Compute Frustum angles. A simple min/max.
/* Example values for reference:
Left:
angleLeft -0.767549932 float
angleRight 0.620896876 float
angleDown -0.837898076 float
angleUp 0.726982594 float
Right:
angleLeft -0.620896876 float
angleRight 0.767549932 float
angleDown -0.837898076 float
angleUp 0.726982594 float
*/
frustumView.fov.angleLeft = std::min(left.fov.angleLeft, right.fov.angleLeft);
frustumView.fov.angleRight = std::max(left.fov.angleRight, right.fov.angleRight);
frustumView.fov.angleDown = std::min(left.fov.angleDown, right.fov.angleDown);
frustumView.fov.angleUp = std::max(left.fov.angleUp, right.fov.angleUp);
// Check that the case works for this approach
auto maxAngle = std::max(frustumView.fov.angleRight - frustumView.fov.angleLeft, frustumView.fov.angleUp - frustumView.fov.angleDown);
if (maxAngle > osg::PI)
{
Log(Debug::Error) << "Total FOV exceeds 180 degrees. Case cannot be culled in single-pass VR. Disabling culling to cope. Consider switching to dual-pass VR.";
mMainCamera->setCullingActive(false);
return;
// TODO: An explicit frustum projection could cope, so implement that later. Guarantee you there will be VR headsets with total horizontal fov > 180 in the future. Maybe already.
}
// Use the law of sines on the triangle spanning PLR to determine P
double angleLeft = std::abs(frustumView.fov.angleLeft);
double angleRight = std::abs(frustumView.fov.angleRight);
double lengthRL = (rightEye - leftEye).length();
double ratioRL = lengthRL / std::sin(osg::PI - angleLeft - angleRight);
double lengthLP = ratioRL * std::sin(angleRight);
osg::Vec3d directionLP = osg::Vec3(std::cos(-angleLeft), std::sin(-angleLeft), 0);
osg::Vec3d LP = directionLP * lengthLP;
frustumView.pose.position = leftEye + LP;
//frustumView.pose.position.x() += 1000;
// Base view position is 0.0, by definition.
// The length of the vector P is therefore the required offset to near/far.
auto nearFarOffset = frustumView.pose.position.length();
// Generate the frustum matrices
auto frustumViewMatrix = viewMatrix * frustumView.pose.viewMatrix(true);
auto frustumProjectionMatrix = frustumView.fov.perspectiveMatrix(near + nearFarOffset, far + nearFarOffset);
if (mTechnique == Technique::GeometryShader_IndexedViewports) if (mTechnique == Technique::GeometryShader_IndexedViewports)
{ {
// To correctly cull when drawing stereo using the geometry shader, the main camera must
// draw a fake view+perspective that includes the full frustums of both the left and right eyes.
// This frustum will be computed as a perspective frustum from a position P slightly behind the eyes L and R
// where it creates the minimum frustum encompassing both eyes' frustums.
// NOTE: I make an assumption that the eyes lie in a horizontal plane relative to the base view,
// and lie mirrored around the Y axis (straight ahead).
// Re-think this if that turns out to be a bad assumption.
View frustumView;
// Compute Frustum angles. A simple min/max.
/* Example values for reference:
Left:
angleLeft -0.767549932 float
angleRight 0.620896876 float
angleDown -0.837898076 float
angleUp 0.726982594 float
Right:
angleLeft -0.620896876 float
angleRight 0.767549932 float
angleDown -0.837898076 float
angleUp 0.726982594 float
*/
frustumView.fov.angleLeft = std::min(left.fov.angleLeft, right.fov.angleLeft);
frustumView.fov.angleRight = std::max(left.fov.angleRight, right.fov.angleRight);
frustumView.fov.angleDown = std::min(left.fov.angleDown, right.fov.angleDown);
frustumView.fov.angleUp = std::max(left.fov.angleUp, right.fov.angleUp);
// Check that the case works for this approach
auto maxAngle = std::max(frustumView.fov.angleRight - frustumView.fov.angleLeft, frustumView.fov.angleUp - frustumView.fov.angleDown);
if (maxAngle > osg::PI)
{
Log(Debug::Error) << "Total FOV exceeds 180 degrees. Case cannot be culled in single-pass VR. Disabling culling to cope. Consider switching to dual-pass VR.";
mMainCamera->setCullingActive(false);
return;
// TODO: An explicit frustum projection could cope, so implement that later. Guarantee you there will be VR headsets with total fov > 180 in the future. Maybe already.
}
// Use the law of sines on the triangle spanning PLR to determine P
double angleLeft = std::abs(frustumView.fov.angleLeft);
double angleRight = std::abs(frustumView.fov.angleRight);
double lengthRL = (rightEye - leftEye).length();
double ratioRL = lengthRL / std::sin(osg::PI - angleLeft - angleRight);
double lengthLP = ratioRL * std::sin(angleRight);
osg::Vec3d directionLP = osg::Vec3(std::cos(-angleLeft), std::sin(-angleLeft), 0);
osg::Vec3d LP = directionLP * lengthLP;
frustumView.pose.position = leftEye + LP;
//frustumView.pose.position.x() += 1000;
// Base view position is 0.0, by definition.
// The length of the vector P is therefore the required offset to near/far.
auto nearFarOffset = frustumView.pose.position.length();
// Generate the frustum matrices
auto frustumViewMatrix = viewMatrix * frustumView.pose.viewMatrix(true);
auto frustumProjectionMatrix = frustumView.fov.perspectiveMatrix(near + nearFarOffset, far + nearFarOffset);
// Update camera with frustum matrices // Update camera with frustum matrices
mMainCamera->setViewMatrix(frustumViewMatrix); mMainCamera->setViewMatrix(frustumViewMatrix);
@ -439,6 +454,18 @@ namespace Misc
mLeftCamera->setViewport(0, 0, width / 2, height); mLeftCamera->setViewport(0, 0, width / 2, height);
mRightCamera->setViewport(width / 2, 0, width / 2, height); mRightCamera->setViewport(width / 2, 0, width / 2, height);
if (mMasterConfig->_projection == nullptr)
mMasterConfig->_projection = new osg::RefMatrix;
if (mMasterConfig->_modelView == nullptr)
mMasterConfig->_modelView = new osg::RefMatrix;
if (mSharedShadowMaps)
{
mMasterConfig->_referenceFrame = mMainCamera->getReferenceFrame();
mMasterConfig->_modelView->set(frustumViewMatrix);
mMasterConfig->_projection->set(projectionMatrix);
}
} }
} }
@ -462,9 +489,9 @@ namespace Misc
stereoViewProjectionsUniform->setElement(1, frustumViewMatrixInverse * mRightCamera->getViewMatrix() * mRightCamera->getProjectionMatrix()); stereoViewProjectionsUniform->setElement(1, frustumViewMatrixInverse * mRightCamera->getViewMatrix() * mRightCamera->getProjectionMatrix());
} }
void StereoView::setUpdateViewCallback(std::shared_ptr<UpdateViewCallback> cb) void StereoView::setUpdateViewCallback(std::shared_ptr<UpdateViewCallback> cb_)
{ {
this->cb = cb; cb = cb_;
} }
void disableStereoForCamera(osg::Camera* camera) void disableStereoForCamera(osg::Camera* camera)

View file

@ -8,6 +8,8 @@
#include <memory> #include <memory>
#include <components/sceneutil/mwshadowtechnique.hpp>
// Some cursed headers like to define these // Some cursed headers like to define these
#if defined(near) || defined(far) #if defined(near) || defined(far)
#undef near #undef near
@ -122,6 +124,11 @@ namespace Misc
osg::ref_ptr<osg::Camera> mLeftCamera{ new osg::Camera }; osg::ref_ptr<osg::Camera> mLeftCamera{ new osg::Camera };
osg::ref_ptr<osg::Camera> mRightCamera{ new osg::Camera }; osg::ref_ptr<osg::Camera> mRightCamera{ new osg::Camera };
using SharedShadowMapConfig = SceneUtil::MWShadowTechnique::SharedShadowMapConfig;
osg::ref_ptr<SharedShadowMapConfig> mMasterConfig;
osg::ref_ptr<SharedShadowMapConfig> mSlaveConfig;
bool mSharedShadowMaps;
// Camera viewports // Camera viewports
bool flipViewOrder{ true }; bool flipViewOrder{ true };

View file

@ -934,9 +934,9 @@ void SceneUtil::MWShadowTechnique::shareShadowMap(osgUtil::CullVisitor& cv, View
// Then initialize new copies of the data that will be written with view-specific data // Then initialize new copies of the data that will be written with view-specific data
// (the stateset and the texgens). // (the stateset and the texgens).
lhs->_traversalNumber = rhs->_traversalNumber;
lhs->_viewDependentShadowMap = rhs->_viewDependentShadowMap; lhs->_viewDependentShadowMap = rhs->_viewDependentShadowMap;
lhs->getStateSet()->clear(); auto* stateset = lhs->getStateSet(cv.getTraversalNumber());
stateset->clear();
lhs->_lightDataList = rhs->_lightDataList; lhs->_lightDataList = rhs->_lightDataList;
lhs->_numValidShadows = rhs->_numValidShadows; lhs->_numValidShadows = rhs->_numValidShadows;
@ -1480,6 +1480,7 @@ void MWShadowTechnique::update(osg::NodeVisitor& nv)
void MWShadowTechnique::cull(osgUtil::CullVisitor& cv) void MWShadowTechnique::cull(osgUtil::CullVisitor& cv)
{ {
if (!_enableShadows) if (!_enableShadows)
{ {
if (mSetDummyStateWhenDisabled) if (mSetDummyStateWhenDisabled)
@ -1554,7 +1555,6 @@ void MWShadowTechnique::cull(osgUtil::CullVisitor& cv)
if (doCastShadow) if (doCastShadow)
{ {
vdd->setTraversalNumber(vdd->getTraversalNumber() + 1);
castShadows(cv, vdd); castShadows(cv, vdd);
} }
@ -1566,7 +1566,7 @@ void MWShadowTechnique::cull(osgUtil::CullVisitor& cv)
if (vdd->_numValidShadows>0) if (vdd->_numValidShadows>0)
{ {
decoratorStateGraph->setStateSet(selectStateSetForRenderingShadow(*vdd)); decoratorStateGraph->setStateSet(selectStateSetForRenderingShadow(*vdd, cv.getTraversalNumber()));
} }
// OSG_NOTICE<<"End of shadow setup Projection matrix "<<*cv.getProjectionMatrix()<<std::endl; // OSG_NOTICE<<"End of shadow setup Projection matrix "<<*cv.getProjectionMatrix()<<std::endl;
@ -3148,17 +3148,17 @@ void MWShadowTechnique::cullShadowCastingScene(osgUtil::CullVisitor* cv, osg::Ca
return; return;
} }
osg::StateSet* MWShadowTechnique::selectStateSetForRenderingShadow(ViewDependentData& vdd) const osg::StateSet* MWShadowTechnique::selectStateSetForRenderingShadow(ViewDependentData& vdd, unsigned int traversalNumber) const
{ {
OSG_INFO<<" selectStateSetForRenderingShadow() "<<vdd.getStateSet()<<std::endl; OSG_INFO<<" selectStateSetForRenderingShadow() "<<vdd.getStateSet(traversalNumber)<<std::endl;
osg::ref_ptr<osg::StateSet> stateset = vdd.getStateSet(); osg::ref_ptr<osg::StateSet> stateset = vdd.getStateSet(traversalNumber);
stateset->clear(); stateset->clear();
stateset->setTextureAttributeAndModes(0, _fallbackBaseTexture.get(), osg::StateAttribute::ON); stateset->setTextureAttributeAndModes(0, _fallbackBaseTexture.get(), osg::StateAttribute::ON);
for(const auto& uniform : _uniforms[vdd.getTraversalNumber() % 2]) for(const auto& uniform : _uniforms[traversalNumber % 2])
{ {
OSG_INFO<<"addUniform("<<uniform->getName()<<")"<<std::endl; OSG_INFO<<"addUniform("<<uniform->getName()<<")"<<std::endl;
stateset->addUniform(uniform); stateset->addUniform(uniform);

View file

@ -214,7 +214,7 @@ namespace SceneUtil {
ShadowDataList& getShadowDataList() { return _shadowDataList; } ShadowDataList& getShadowDataList() { return _shadowDataList; }
osg::StateSet* getStateSet() { return _stateset[_traversalNumber % 2].get(); } osg::StateSet* getStateSet(unsigned int traversalNumber) { return _stateset[traversalNumber % 2].get(); }
virtual void releaseGLObjects(osg::State* = 0) const; virtual void releaseGLObjects(osg::State* = 0) const;
@ -222,10 +222,6 @@ namespace SceneUtil {
void setNumValidShadows(unsigned int numValidShadows) { _numValidShadows = numValidShadows; } void setNumValidShadows(unsigned int numValidShadows) { _numValidShadows = numValidShadows; }
void setTraversalNumber(unsigned int traversalNumber) { _traversalNumber = traversalNumber; }
unsigned int getTraversalNumber() { return _traversalNumber; }
protected: protected:
friend class MWShadowTechnique; friend class MWShadowTechnique;
virtual ~ViewDependentData() {} virtual ~ViewDependentData() {}
@ -238,7 +234,6 @@ namespace SceneUtil {
ShadowDataList _shadowDataList; ShadowDataList _shadowDataList;
unsigned int _numValidShadows; unsigned int _numValidShadows;
unsigned int _traversalNumber;
}; };
virtual ViewDependentData* createViewDependentData(osgUtil::CullVisitor* cv); virtual ViewDependentData* createViewDependentData(osgUtil::CullVisitor* cv);
@ -279,7 +274,7 @@ namespace SceneUtil {
virtual void cullShadowCastingScene(osgUtil::CullVisitor* cv, osg::Camera* camera) const; virtual void cullShadowCastingScene(osgUtil::CullVisitor* cv, osg::Camera* camera) const;
virtual osg::StateSet* selectStateSetForRenderingShadow(ViewDependentData& vdd) const; virtual osg::StateSet* selectStateSetForRenderingShadow(ViewDependentData& vdd, unsigned int traversalNumber) const;
protected: protected:
virtual ~MWShadowTechnique(); virtual ~MWShadowTechnique();

View file

@ -1,6 +1,6 @@
#include "sdlgraphicswindow.hpp" #include "sdlgraphicswindow.hpp"
#include <osgViewer/viewer> #include <osgViewer/Viewer>
#include <SDL_video.h> #include <SDL_video.h>

View file

@ -361,6 +361,9 @@ trainers training skills based on base skill = false
# Make stealing items from NPCs that were knocked down possible during combat. # Make stealing items from NPCs that were knocked down possible during combat.
always allow stealing from knocked out actors = false always allow stealing from knocked out actors = false
# Enables visually harvesting plants for models that support it.
graphic herbalism = true
[General] [General]
# Anisotropy reduces distortion in textures at low angles (e.g. 0 to 16). # Anisotropy reduces distortion in textures at low angles (e.g. 0 to 16).
@ -962,6 +965,20 @@ defer aabb update = true
# Loading arbitrary meshes is not advised and may cause instability. # Loading arbitrary meshes is not advised and may cause instability.
load unsupported nif files = false load unsupported nif files = false
[Stereo]
# Enable/disable stereo view. This setting is ignored in VR.
stereo enabled = false
# Method used to render stereo if enabled
# Must be one of the following: BruteForce, GeometryShader
# BruteForce: Generates stereo using two cameras and two cull/render passes. Choose this if your game is GPU-bound.
# GeometryShader: Generates stereo in a single pass using automatically generated geometry shaders. May break custom shaders. Choose this if your game is CPU-bound.
stereo method = GeometryShader
# May accelerate the BruteForce method when shadows are enabled
shared shadow maps = true
[VR] [VR]
# Should match your real height in the format meters.centimeters. This is used to scale your position within the vr stage to better match your character. # Should match your real height in the format meters.centimeters. This is used to scale your position within the vr stage to better match your character.
real height = 1.85 real height = 1.85