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 #2798: Mutable ESM records
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 #3714: Savegame fails to load due to conflict between SpellState and MagicEffects
Bug #3789: Crash in visitEffectSources while in battle
@ -67,6 +68,7 @@
Bug #5656: Sneaking characters block hits while standing
Bug #5661: Region sounds don't play at the right interval
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
Feature #390: 3rd person look "over the shoulder"
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
actionequip timestamp actionalchemy cellstore actionapply actioneat
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
)
add_openmw_dir (mwphysics
physicssystem trace collisiontype actor convert object heightfield closestnotmerayresultcallback
contacttestresultcallback deepestnotmecontacttestresultcallback stepper movementsolver
contacttestresultcallback deepestnotmecontacttestresultcallback stepper movementsolver projectile
closestnotmeconvexresultcallback raycasting mtphysics
)

View file

@ -399,8 +399,6 @@ OMW::Engine::Engine(Files::ConfigurationManager& configurationManager)
, mNewGame (false)
, mCfgMgr(configurationManager)
{
MWClass::registerClasses();
SDL_SetHint(SDL_HINT_ACCELEROMETER_AS_JOYSTICK, "0"); // We use only gamepads
Uint32 flags = SDL_INIT_VIDEO|SDL_INIT_NOPARACHUTE|SDL_INIT_GAMECONTROLLER|SDL_INIT_JOYSTICK|SDL_INIT_SENSOR;
@ -771,7 +769,7 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings)
}
// 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
// therefore this part is separate from the rest of stereo setup.
@ -907,6 +905,8 @@ void OMW::Engine::go()
std::string settingspath;
settingspath = loadSettings (settings);
MWClass::registerClasses();
// Create encoder
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);
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>());
// Font encoding settings
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));
// directory settings

View file

@ -2,6 +2,7 @@
#include <components/esm/loadcont.hpp>
#include <components/esm/containerstate.hpp>
#include <components/settings/settings.hpp>
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
@ -59,6 +60,11 @@ namespace MWClass
return *this;
}
Container::Container()
{
mHarvestEnabled = Settings::Manager::getBool("graphic herbalism", "Game");
}
void Container::ensureCustomData (const MWWorld::Ptr& ptr) const
{
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);
if (animation == nullptr)
return false;

View file

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

View file

@ -48,9 +48,9 @@ namespace MWGui
const MWWorld::ESMStore &store =
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)
{
@ -59,14 +59,14 @@ namespace MWGui
std::string effectIDStr = ESM::MagicEffect::effectIdToString(effectId);
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);

View file

@ -46,27 +46,29 @@ bool MWMechanics::AiCast::execute(const MWWorld::Ptr& actor, MWMechanics::Charac
{
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
bool isCasting = MWBase::Environment::get().getMechanicsManager()->isCastingSpell(actor);
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->setActivationState(DISABLE_DEACTIVATION);
mCollisionObject->setCollisionShape(mShape.get());
mCollisionObject->setUserPointer(static_cast<PtrHolder*>(this));
mCollisionObject->setUserPointer(this);
updateRotation();
updateScale();

View file

@ -2,6 +2,14 @@
#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
{
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)
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;
if (normalInWorldSpace)
hitNormalWorld = convexResult.m_hitNormalLocal;

View file

@ -1,18 +1,22 @@
#include "closestnotmerayresultcallback.hpp"
#include <algorithm>
#include <utility>
#include <BulletCollision/CollisionDispatch/btCollisionObject.h>
#include "../mwworld/class.hpp"
#include "actor.hpp"
#include "collisiontype.hpp"
#include "projectile.hpp"
#include "ptrholder.hpp"
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)
, 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()))
{
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())
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
{
class Projectile;
class ClosestNotMeRayResultCallback : public btCollisionWorld::ClosestRayResultCallback
{
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;
private:
const btCollisionObject* mMe;
const std::vector<const btCollisionObject*> mTargets;
Projectile* mProjectile;
};
}

View file

@ -17,6 +17,7 @@
#include "mtphysics.hpp"
#include "object.hpp"
#include "physicssystem.hpp"
#include "projectile.hpp"
namespace
{
@ -297,12 +298,19 @@ namespace MWPhysics
return mPreviousMovementResults;
}
const PtrPositionList& PhysicsTaskScheduler::resetSimulation()
const PtrPositionList& PhysicsTaskScheduler::resetSimulation(const ActorMap& actors)
{
std::unique_lock lock(mSimulationMutex);
mMovementResults.clear();
mPreviousMovementResults.clear();
mActorsFrameData.clear();
for (const auto& [_, actor] : actors)
{
actor->resetPosition();
actor->setStandingOnPtr(nullptr);
mMovementResults[actor->getPtr()] = actor->getWorldPosition();
}
return mMovementResults;
}
@ -448,6 +456,11 @@ namespace MWPhysics
object->commitPositionChange();
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
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
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->setCollisionShape(shapeInstance->getCollisionShape());
mCollisionObject->setUserPointer(static_cast<PtrHolder*>(this));
mCollisionObject->setUserPointer(this);
setScale(ptr.getCellRef().getScale());
setRotation(Misc::Convert::toBullet(ptr.getRefData().getBaseNode()->getAttitude()));

View file

@ -46,6 +46,8 @@
#include "collisiontype.hpp"
#include "actor.hpp"
#include "projectile.hpp"
#include "trace.h"
#include "object.hpp"
#include "heightfield.hpp"
@ -64,6 +66,7 @@ namespace MWPhysics
, mResourceSystem(resourceSystem)
, mDebugDrawEnabled(false)
, mTimeAccum(0.0f)
, mProjectileId(0)
, mWaterHeight(0)
, mWaterEnabled(false)
, mParentNode(parentNode)
@ -112,7 +115,7 @@ namespace MWPhysics
mObjects.clear();
mActors.clear();
mProjectiles.clear();
}
void PhysicsSystem::setUnrefQueue(SceneUtil::UnrefQueue *unrefQueue)
@ -248,7 +251,7 @@ namespace MWPhysics
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)
{
@ -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_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)
{
ObjectMap::iterator found = mObjects.find(old);
@ -553,6 +563,14 @@ namespace MWPhysics
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)
{
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)
{
ObjectMap::iterator found = mObjects.find(ptr);
@ -632,6 +661,15 @@ namespace MWPhysics
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()
{
ActorMap::iterator found = mActors.find(MWMechanics::getPlayer());
@ -677,14 +715,7 @@ namespace MWPhysics
mTimeAccum -= numSteps * mPhysicsDt;
if (skipSimulation)
{
for (auto& [_, actor] : mActors)
{
actor->resetPosition();
actor->setStandingOnPtr(nullptr);
}
return mTaskScheduler->resetSimulation();
}
return mTaskScheduler->resetSimulation(mActors);
return mTaskScheduler->moveActors(numSteps, mTimeAccum, prepareFrameData(numSteps), frameStart, frameNumber, stats);
}

View file

@ -56,6 +56,9 @@ namespace MWPhysics
class Object;
class Actor;
class PhysicsTaskScheduler;
class Projectile;
using ActorMap = std::map<MWWorld::ConstPtr, std::shared_ptr<Actor>>;
struct ContactPoint
{
@ -125,6 +128,10 @@ namespace MWPhysics
void addObject (const MWWorld::Ptr& ptr, const std::string& mesh, int collisionType = CollisionType_World);
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);
Actor* getActor(const MWWorld::Ptr& ptr);
@ -132,6 +139,8 @@ namespace MWPhysics
const Object* getObject(const MWWorld::ConstPtr& ptr) const;
Projectile* getProjectile(int projectileId) const;
// Object or Actor
void remove (const MWWorld::Ptr& ptr);
@ -139,7 +148,6 @@ namespace MWPhysics
void updateRotation (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 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.
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>(),
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;
@ -265,9 +273,11 @@ namespace MWPhysics
std::set<Object*> mAnimatedObjects; // stores pointers to elements in mObjects
using ActorMap = std::map<MWWorld::ConstPtr, std::shared_ptr<Actor>>;
ActorMap mActors;
using ProjectileMap = std::map<int, std::shared_ptr<Projectile>>;
ProjectileMap mProjectiles;
using HeightFieldMap = std::map<std::pair<int, int>, HeightField *>;
HeightFieldMap mHeightFields;
@ -278,6 +288,8 @@ namespace MWPhysics
float mTimeAccum;
unsigned int mProjectileId;
float mWaterHeight;
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.
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>(),
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;

View file

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

View file

@ -521,7 +521,7 @@ namespace MWVR
{
XrCompositionLayerProjectionView xrLayer;
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.fov = toXR(layer.fov);
xrLayer.next = nullptr;
@ -529,6 +529,21 @@ namespace MWVR
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
OpenXRManagerImpl::endFrame(FrameInfo frameInfo, const std::array<CompositionLayerProjectionView, 2>* layerStack)
{
@ -555,8 +570,8 @@ namespace MWVR
compositionLayerDepth = mLayerDepth;
compositionLayerDepth[(int)Side::LEFT_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::RIGHT_SIDE].subImage = (*layerStack)[(int)Side::RIGHT_SIDE].swapchain->impl().xrSubImageDepth();
compositionLayerDepth[(int)Side::LEFT_SIDE].subImage = toXR((*layerStack)[(int)Side::LEFT_SIDE].subImage, true);
compositionLayerDepth[(int)Side::RIGHT_SIDE].subImage = toXR((*layerStack)[(int)Side::RIGHT_SIDE].subImage, true);
if (compositionLayerDepth[(int)Side::LEFT_SIDE].subImage.swapchain != XR_NULL_HANDLE
&& compositionLayerDepth[(int)Side::RIGHT_SIDE].subImage.swapchain != XR_NULL_HANDLE)
{

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,6 +1,7 @@
#include "vrviewer.hpp"
#include "openxrmanagerimpl.hpp"
#include "openxrswapchain.hpp"
#include "vrenvironment.hpp"
#include "vrsession.hpp"
#include "vrframebuffer.hpp"
@ -36,10 +37,8 @@ namespace MWVR
VRViewer::VRViewer(
osg::ref_ptr<osgViewer::Viewer> viewer)
: mViewer(viewer)
, mViews()
, mPreDraw(new PredrawCallback(this))
, mPostDraw(new PostdrawCallback(this))
, mVrShadow()
, mConfigured(false)
, mMsaaResolveMirrorTexture{}
, mMirrorTexture{ nullptr }
@ -91,6 +90,30 @@ namespace MWVR
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)
{
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)
auto mainCamera = mCameras["MainCamera"] = mViewer->getCamera();
auto mainCamera = mViewer->getCamera();
mainCamera->setName("Main");
mainCamera->setInitialDrawCallback(new VRView::InitialDrawCallback());
mainCamera->setInitialDrawCallback(new InitialDrawCallback());
auto* xr = Environment::get().getManager();
xr->realize(context);
@ -112,17 +135,7 @@ namespace MWVR
// For the rest of runtime this is handled by vrsession
xr->handleEvents();
// Small feature culling
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();
// Set up swapchain config
auto config = xr->getRecommendedSwapchainConfig();
std::array<std::string, 2> xConfString;
@ -151,42 +164,41 @@ namespace MWVR
config[i].name = sViewNames[i];
auto view = new VRView(name, config[i], context->getState());
mViews[name] = view;
auto camera = mCameras[name] = view->createCamera(i + 2, clearColor, context);
camera->setPreDrawCallback(mPreDraw);
camera->setFinalDrawCallback(mPostDraw);
camera->setCullMask(~MWRender::Mask_GUI & ~MWRender::Mask_SimpleWater & ~MWRender::Mask_UpdateVisitor);
camera->setName(name);
if (smallFeatureCulling)
camera->setSmallFeatureCullingPixelSize(smallFeatureCullingPixelSize);
camera->setCullingMode(cullingMode);
mViewer->addSlave(camera, true);
auto* slave = mViewer->findSlaveForCamera(camera);
slave->_updateSlaveCallback = new VRView::UpdateSlaveCallback(view);
mVrShadow.configureShadowsForCamera(camera, i == 0);
mSubImages[i].width = config[i].selectedWidth;
mSubImages[i].height = config[i].selectedHeight;
if (i > 0)
{
mSubImages[i].x = mSubImages[i - 1].x + mSubImages[i - 1].width;
mSubImages[i].y = mSubImages[i - 1].y + mSubImages[i - 1].height;
}
else
{
mSubImages[i].x = mSubImages[i].y = 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);
setupMirrorTexture();
mMainCameraGC = mainCamera->getGraphicsContext();
mMainCameraGC->setSwapCallback(new VRViewer::SwapBuffersCallback(this));
mainCamera->setGraphicsContext(nullptr);
mainCamera->getGraphicsContext()->setSwapCallback(new VRViewer::SwapBuffersCallback(this));
mainCamera->setPreDrawCallback(mPreDraw);
mainCamera->setPostDrawCallback(mPostDraw);
mainCamera->setCullCallback(new CullCallback);
mConfigured = true;
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()
{
mMirrorTextureEnabled = Settings::Manager::getBool("mirror texture", "VR");
@ -228,58 +240,55 @@ namespace MWVR
setupMirrorTexture();
}
void VRViewer::enableMainCamera(void)
SubImage VRViewer::subImage(Side side)
{
mCameras["MainCamera"]->setGraphicsContext(mMainCameraGC);
}
void VRViewer::disableMainCamera(void)
{
mCameras["MainCamera"]->setGraphicsContext(nullptr);
return mSubImages[static_cast<int>(side)];
}
void VRViewer::blitEyesToMirrorTexture(osg::GraphicsContext* gc)
{
if (mMirrorTextureShouldBeCleanedUp)
{
mMirrorTexture.reset(nullptr);
mMsaaResolveMirrorTexture.clear();
mMirrorTextureShouldBeCleanedUp = false;
}
if (!mMirrorTextureEnabled)
return;
if (!mMirrorTexture)
{
mMirrorTexture.reset(new VRFramebuffer(gc->getState(), mCameras["MainCamera"]->getViewport()->width(), mCameras["MainCamera"]->getViewport()->height(), 0));
}
//if (mMirrorTextureShouldBeCleanedUp)
//{
// mMirrorTexture.reset(nullptr);
// mMsaaResolveMirrorTexture.clear();
// mMirrorTextureShouldBeCleanedUp = false;
//}
//if (!mMirrorTextureEnabled)
// return;
//if (!mMirrorTexture)
//{
// mMirrorTexture.reset(new VRFramebuffer(gc->getState(), mCameras["MainCamera"]->getViewport()->width(), mCameras["MainCamera"]->getViewport()->height(), 0));
//}
auto* state = gc->getState();
auto* gl = osg::GLExtensions::Get(state->getContextID(), false);
int screenWidth = mCameras["MainCamera"]->getViewport()->width();
int mirrorWidth = screenWidth / mMirrorTextureViews.size();
int screenHeight = mCameras["MainCamera"]->getViewport()->height();
//auto mainCamera = mViewer->getCamera();
//int screenWidth = mainCamera->getViewport()->width();
//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
// which means resolving msaa twice.
for (unsigned i = 0; i < mMirrorTextureViews.size(); i++)
{
auto& view = mViews[mMirrorTextureViews[i]];
if(!mMsaaResolveMirrorTexture[mMirrorTextureViews[i]])
mMsaaResolveMirrorTexture[mMirrorTextureViews[i]].reset(new VRFramebuffer(gc->getState(),
view->swapchain().width(),
view->swapchain().height(),
0));
//// Since OpenXR does not include native support for mirror textures, we have to generate them ourselves
//// which means resolving msaa twice.
//for (unsigned i = 0; i < mMirrorTextureViews.size(); i++)
//{
// if(!mMsaaResolveMirrorTexture)
// mMsaaResolveMirrorTexture.reset(new VRFramebuffer(gc->getState(),
// mSwapchain->width(),
// mSwapchain->height(),
// 0));
auto& resolveTexture = *mMsaaResolveMirrorTexture[mMirrorTextureViews[i]];
resolveTexture.bindFramebuffer(gc, GL_FRAMEBUFFER_EXT);
view->swapchain().renderBuffer()->blit(gc, 0, 0, resolveTexture.width(), resolveTexture.height());
mMirrorTexture->bindFramebuffer(gc, GL_FRAMEBUFFER_EXT);
resolveTexture.blit(gc, i * mirrorWidth, 0, (i + 1) * mirrorWidth, screenHeight);
}
// auto& resolveTexture = *mMsaaResolveMirrorTexture[mMirrorTextureViews[i]];
// resolveTexture.bindFramebuffer(gc, GL_FRAMEBUFFER_EXT);
// view->swapchain().renderBuffer()->blit(gc, 0, 0, resolveTexture.width(), resolveTexture.height());
// mMirrorTexture->bindFramebuffer(gc, GL_FRAMEBUFFER_EXT);
// resolveTexture.blit(gc, i * mirrorWidth, 0, (i + 1) * mirrorWidth, screenHeight);
//}
gl->glBindFramebuffer(GL_FRAMEBUFFER_EXT, 0);
mMirrorTexture->blit(gc, 0, 0, screenWidth, screenHeight);
//mMirrorTexture->blit(gc, 0, 0, screenWidth, screenHeight);
mSwapchain->endFrame(gc);
}
void
@ -305,18 +314,14 @@ namespace MWVR
void VRViewer::preDrawCallback(osg::RenderInfo& info)
{
auto* camera = info.getCurrentCamera();
auto name = camera->getName();
mViews[name]->prerenderCallback(info);
if(Environment::get().getSession()->getFrame(VRSession::FramePhase::Draw)->mShouldRender)
mSwapchain->beginFrame(info.getState()->getGraphicsContext());
}
void VRViewer::postDrawCallback(osg::RenderInfo& info)
{
auto* camera = info.getCurrentCamera();
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
// to see why and how i can stop it.

View file

@ -18,6 +18,7 @@ namespace MWVR
{
class VRFramebuffer;
class VRView;
class OpenXRSwapchain;
/// \brief Manages stereo rendering and mirror texturing.
///
@ -64,6 +65,12 @@ namespace MWVR
VRViewer* mViewer;
};
class InitialDrawCallback : public osg::Camera::DrawCallback
{
public:
virtual void operator()(osg::RenderInfo& renderInfo) const;
};
static const std::array<const char*, 2> sViewNames;
enum class MirrorTextureEye
{
@ -84,33 +91,30 @@ namespace MWVR
void blitEyesToMirrorTexture(osg::GraphicsContext* gc);
void realize(osg::GraphicsContext* gc);
bool realized() { return mConfigured; }
VRView* getView(std::string name);
VrShadow& vrShadow() { return mVrShadow; }
void setupMirrorTexture();
void processChangedSettings(const std::set< std::pair<std::string, std::string> >& changed);
void enableMainCamera(void);
void disableMainCamera(void);
SubImage subImage(Side side);
private:
std::mutex mMutex{};
bool mConfigured{ false };
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<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;
VrShadow mVrShadow;
std::mutex mMutex{};
bool mConfigured{ false };
std::vector<std::string> mMirrorTextureViews;
bool mMirrorTextureShouldBeCleanedUp{ false };
bool mMirrorTextureEnabled{ false };
bool mFlipMirrorTextureOrder{ false };
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)
{
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);
if(!script.empty())
@ -43,13 +43,10 @@ namespace
{
float sum = 0;
for (typename MWWorld::CellRefList<T>::List::const_iterator iter (
cellRefList.mList.begin());
iter!=cellRefList.mList.end();
++iter)
for (const auto& iter : cellRefList.mList)
{
if (iter->mData.getCount()>0)
sum += iter->mData.getCount()*iter->mBase->mData.mWeight;
if (iter.mData.getCount()>0)
sum += iter.mData.getCount()*iter.mBase->mData.mWeight;
}
return sum;
@ -62,12 +59,11 @@ namespace
store->resolve();
std::string id2 = Misc::StringUtils::lowerCase (id);
for (typename MWWorld::CellRefList<T>::List::iterator iter (list.mList.begin());
iter!=list.mList.end(); ++iter)
for (auto& iter : list.mList)
{
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);
return ptr;
}
@ -81,7 +77,7 @@ MWWorld::ResolutionListener::~ResolutionListener()
{
if(!mStore.mModified && mStore.mResolved && !mStore.mPtr.isEmpty())
{
for(const MWWorld::Ptr& ptr : mStore)
for(const auto&& ptr : mStore)
ptr.getRefData().setCount(0);
mStore.fillNonRandom(mStore.mPtr.get<ESM::Container>()->mBase->mInventory, "", mStore.mSeed);
addScripts(mStore, mStore.mPtr.mCell);
@ -127,15 +123,14 @@ template<typename T>
void MWWorld::ContainerStore::storeStates (const CellRefList<T>& collection,
ESM::InventoryState& inventory, int& index, bool equipable) const
{
for (typename CellRefList<T>::List::const_iterator iter (collection.mList.begin());
iter!=collection.mList.end(); ++iter)
for (const auto& iter : collection.mList)
{
if (iter->mData.getCount() == 0)
if (iter.mData.getCount() == 0)
continue;
ESM::ObjectState state;
storeState (*iter, state);
storeState (iter, state);
if (equipable)
storeEquipmentState(*iter, index, inventory);
storeEquipmentState(iter, index, inventory);
inventory.mItems.push_back (state);
++index;
}
@ -188,7 +183,7 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::end()
int MWWorld::ContainerStore::count(const std::string &id) const
{
int total=0;
for (const auto& iter : *this)
for (const auto&& iter : *this)
if (Misc::StringUtils::ciEqual(iter.getCellRef().getRefId(), id))
total += iter.getRefData().getCount();
return total;
@ -430,14 +425,14 @@ void MWWorld::ContainerStore::rechargeItems(float duration)
updateRechargingItems();
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;
// attempt to restack when fully recharged
if (it->first->getCellRef().getEnchantmentCharge() == it->second)
it->first = restack(*it->first);
if (it.first->getCellRef().getEnchantmentCharge() == it.second)
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
{
for (auto iter(begin()); iter != end(); ++iter)
for (const auto&& iter : *this)
{
if (iter->getClass().showsInInventory(*iter))
if (iter.getClass().showsInInventory(iter))
return true;
}
@ -601,8 +596,8 @@ void MWWorld::ContainerStore::addInitialItemImp(const MWWorld::Ptr& ptr, const s
void MWWorld::ContainerStore::clear()
{
for (ContainerStoreIterator iter (begin()); iter!=end(); ++iter)
iter->getRefData().setCount (0);
for (auto&& iter : *this)
iter.getRefData().setCount (0);
flagAsModified();
mModified = true;
@ -623,7 +618,7 @@ void MWWorld::ContainerStore::resolve()
{
if(!mResolved && !mPtr.isEmpty())
{
for(const MWWorld::Ptr& ptr : *this)
for(const auto&& ptr : *this)
ptr.getRefData().setCount(0);
Misc::Rng::Seed seed{mSeed};
fill(mPtr.get<ESM::Container>()->mBase->mInventory, "", seed);
@ -644,7 +639,7 @@ MWWorld::ResolutionHandle MWWorld::ContainerStore::resolveTemporarily()
}
if(!mResolved && !mPtr.isEmpty())
{
for(const MWWorld::Ptr& ptr : *this)
for(const auto&& ptr : *this)
ptr.getRefData().setCount(0);
Misc::Rng::Seed seed{mSeed};
fill(mPtr.get<ESM::Container>()->mBase->mInventory, "", seed);
@ -727,10 +722,10 @@ MWWorld::Ptr MWWorld::ContainerStore::findReplacement(const std::string& id)
{
MWWorld::Ptr item;
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;
if (Misc::StringUtils::ciEqual(iter->getCellRef().getRefId(), id))
int iterHealth = iter.getClass().hasItemHealth(iter) ? iter.getClass().getItemHealth(iter) : 1;
if (Misc::StringUtils::ciEqual(iter.getCellRef().getRefId(), id))
{
// Prefer the stack with the lowest remaining uses
// 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) ||
(itemHealth <= 0 && iterHealth > 0))
{
item = *iter;
item = iter;
itemHealth = iterHealth;
}
}
@ -867,11 +862,8 @@ void MWWorld::ContainerStore::readState (const ESM::InventoryState& inventory)
mResolved = true;
int index = 0;
for (std::vector<ESM::ObjectState>::const_iterator
iter (inventory.mItems.begin()); iter!=inventory.mItems.end(); ++iter)
for (const ESM::ObjectState& state : inventory.mItems)
{
const ESM::ObjectState& state = *iter;
int type = MWBase::Environment::get().getWorld()->getStore().find(state.mRef.mRefID);
int thisIndex = index++;

View file

@ -2,6 +2,7 @@
#include <iomanip>
#include <memory>
#include <osg/PositionAttitudeTransform>
#include <components/debug/debuglog.hpp>
@ -43,6 +44,7 @@
#include "../mwsound/sound.hpp"
#include "../mwphysics/physicssystem.hpp"
#include "../mwphysics/projectile.hpp"
#ifdef USE_OPENXR
#include "../mwvr/vrenvironment.hpp"
@ -298,7 +300,7 @@ namespace MWWorld
else
state.mActorId = -1;
std::string texture = "";
std::string texture;
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();
osg::Vec4 lightDiffuseColor = getMagicBoltLightDiffuseColor(state.mEffects);
createModel(state, ptr.getClass().getModel(ptr), pos, orient, true, true, lightDiffuseColor, texture);
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
@ -326,7 +329,9 @@ namespace MWWorld
if (sound)
state.mSounds.push_back(sound);
}
state.mProjectileId = mPhysics->addProjectile(caster, pos);
state.mToDelete = false;
mMagicBolts.push_back(state);
}
@ -339,7 +344,6 @@ namespace MWWorld
state.mIdArrow = projectile.getCellRef().getRefId();
state.mCasterHandle = actor;
state.mAttackStrength = attackStrength;
int type = projectile.get<ESM::Weapon>()->mBase->mData.mType;
state.mThrown = MWMechanics::getWeaponType(type)->mWeaponClass == ESM::WeaponType::Thrown;
@ -350,6 +354,8 @@ namespace MWWorld
if (!ptr.getClass().getEnchantment(ptr).empty())
SceneUtil::addEnchantedGlow(state.mNode, mResourceSystem, ptr.getClass().getEnchantmentColor(ptr));
state.mProjectileId = mPhysics->addProjectile(actor, pos);
state.mToDelete = false;
mProjectiles.push_back(state);
}
@ -374,63 +380,58 @@ namespace MWWorld
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))
{
cleanupProjectile(*it);
it = mProjectiles.erase(it);
}
else
++it;
if (isCleanable(projectileState))
cleanupProjectile(projectileState);
}
for (std::vector<MagicBoltState>::iterator it = mMagicBolts.begin(); it != mMagicBolts.end();)
for (auto& magicBoltState : mMagicBolts)
{
if (isCleanable(*it))
{
cleanupMagicBolt(*it);
it = mMagicBolts.erase(it);
}
else
++it;
if (isCleanable(magicBoltState))
cleanupMagicBolt(magicBoltState);
}
}
}
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.
MWWorld::Ptr caster = it->getCaster();
MWWorld::Ptr caster = magicBoltState.getCaster();
if (!caster.isEmpty() && caster.getClass().isActor())
{
if (caster.getRefData().getCount() <= 0 || caster.getClass().getCreatureStats(caster).isDead())
{
cleanupMagicBolt(*it);
it = mMagicBolts.erase(it);
cleanupMagicBolt(magicBoltState);
continue;
}
}
osg::Quat orient = it->mNode->getAttitude();
osg::Quat orient = magicBoltState.mNode->getAttitude();
static float fTargetSpellMaxSpeed = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>()
.find("fTargetSpellMaxSpeed")->mValue.getFloat();
float speed = fTargetSpellMaxSpeed * it->mSpeed;
float speed = fTargetSpellMaxSpeed * magicBoltState.mSpeed;
osg::Vec3f direction = orient * osg::Vec3f(0,1,0);
direction.normalize();
osg::Vec3f pos(it->mNode->getPosition());
osg::Vec3f pos(magicBoltState.mNode->getPosition());
osg::Vec3f newPos = pos + direction * duration * speed;
for (size_t soundIter = 0; soundIter != it->mSounds.size(); soundIter++)
{
it->mSounds.at(soundIter)->setPosition(newPos);
}
for (const auto& sound : magicBoltState.mSounds)
sound->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.
std::vector<MWWorld::Ptr> targetActors;
@ -439,7 +440,7 @@ namespace MWWorld
// Check for impact
// 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;
if (result.mHit)
@ -447,16 +448,16 @@ namespace MWWorld
hit = true;
if (result.mHitObject.isEmpty())
{
// terrain
// terrain or projectile
}
else
{
MWMechanics::CastSpell cast(caster, result.mHitObject);
cast.mHitPosition = pos;
cast.mId = it->mSpellId;
cast.mSourceName = it->mSourceName;
cast.mId = magicBoltState.mSpellId;
cast.mSourceName = magicBoltState.mSourceName;
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));
}
}
@ -467,47 +468,46 @@ namespace MWWorld
if (hit)
{
MWBase::Environment::get().getWorld()->explodeSpell(pos, it->mEffects, caster, result.mHitObject,
ESM::RT_Target, it->mSpellId, it->mSourceName);
MWBase::Environment::get().getWorld()->explodeSpell(pos, magicBoltState.mEffects, caster, result.mHitObject,
ESM::RT_Target, magicBoltState.mSpellId, magicBoltState.mSourceName);
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
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;
cleanupMagicBolt(magicBoltState);
}
else
++it;
}
}
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
// 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 newPos = pos + it->mVelocity * duration;
osg::Vec3f pos(projectileState.mNode->getPosition());
osg::Vec3f newPos = pos + projectileState.mVelocity * duration;
// 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;
orient.makeRotate(osg::Vec3f(0,1,0), it->mVelocity);
it->mNode->setAttitude(orient);
orient.makeRotate(osg::Vec3f(0,1,0), projectileState.mVelocity);
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.
std::vector<MWWorld::Ptr> targetActors;
@ -516,50 +516,149 @@ namespace MWWorld
// Check for impact
// 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);
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.
MWWorld::ManualRef projectileRef(MWBase::Environment::get().getWorld()->getStore(), projectileState.mIdArrow);
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::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;
}
if (caster.isEmpty())
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));
if (underwater)
mRendering->emitWaterRipple(newPos);
mParent->removeChild(it->mNode);
it = mProjectiles.erase(it);
cleanupProjectile(projectileState);
}
}
}
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;
}
++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)
{
mParent->removeChild(state.mNode);
mPhysics->removeProjectile(state.mProjectileId);
state.mToDelete = true;
}
void ProjectileManager::cleanupMagicBolt(ProjectileManager::MagicBoltState& state)
{
mParent->removeChild(state.mNode);
mPhysics->removeProjectile(state.mProjectileId);
state.mToDelete = true;
for (size_t soundIter = 0; soundIter != state.mSounds.size(); soundIter++)
{
MWBase::Environment::get().getSoundManager()->stopSound(state.mSounds.at(soundIter));
@ -568,15 +667,12 @@ namespace MWWorld
void ProjectileManager::clear()
{
for (std::vector<ProjectileState>::iterator it = mProjectiles.begin(); it != mProjectiles.end(); ++it)
{
cleanupProjectile(*it);
}
for (auto& mProjectile : mProjectiles)
cleanupProjectile(mProjectile);
mProjectiles.clear();
for (std::vector<MagicBoltState>::iterator it = mMagicBolts.begin(); it != mMagicBolts.end(); ++it)
{
cleanupMagicBolt(*it);
}
for (auto& mMagicBolt : mMagicBolts)
cleanupMagicBolt(mMagicBolt);
mMagicBolts.clear();
}
@ -633,6 +729,7 @@ namespace MWWorld
state.mVelocity = esm.mVelocity;
state.mIdArrow = esm.mId;
state.mAttackStrength = esm.mAttackStrength;
state.mToDelete = false;
std::string model;
try
@ -640,9 +737,10 @@ namespace MWWorld
MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), esm.mId);
MWWorld::Ptr ptr = ref.getPtr();
model = ptr.getClass().getModel(ptr);
int weaponType = ptr.get<ESM::Weapon>()->mBase->mData.mType;
state.mThrown = MWMechanics::getWeaponType(weaponType)->mWeaponClass == ESM::WeaponType::Thrown;
state.mProjectileId = mPhysics->addProjectile(state.getCaster(), osg::Vec3f(esm.mPosition));
}
catch(...)
{
@ -654,7 +752,7 @@ namespace MWWorld
mProjectiles.push_back(state);
return true;
}
else if (type == ESM::REC_MPRJ)
if (type == ESM::REC_MPRJ)
{
ESM::MagicBoltState esm;
esm.load(reader);
@ -663,7 +761,8 @@ namespace MWWorld
state.mIdMagic.push_back(esm.mId);
state.mSpellId = esm.mSpellId;
state.mActorId = esm.mActorId;
std::string texture = "";
state.mToDelete = false;
std::string texture;
try
{
@ -686,6 +785,7 @@ namespace MWWorld
MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), state.mIdMagic.at(0));
MWWorld::Ptr ptr = ref.getPtr();
model = ptr.getClass().getModel(ptr);
state.mProjectileId = mPhysics->addProjectile(state.getCaster(), osg::Vec3f(esm.mPosition));
}
catch(...)
{

View file

@ -56,6 +56,8 @@ namespace MWWorld
void update(float dt);
void processHits();
/// Removes all current projectiles. Should be called when switching to a new worldspace.
void clear();
@ -76,6 +78,9 @@ namespace MWWorld
std::shared_ptr<MWRender::EffectAnimationTime> mEffectAnimationTime;
int mActorId;
int mProjectileId;
osg::Vec3f mHitPosition;
// 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.
@ -88,6 +93,8 @@ namespace MWWorld
// MW-id of an arrow projectile
std::string mIdArrow;
bool mToDelete;
};
struct MagicBoltState : public State
@ -125,6 +132,8 @@ namespace MWWorld
void moveProjectiles(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,
bool rotate, bool createLight, osg::Vec4 lightDiffuseColor, std::string texture = "");
void update (State& state, float duration);

View file

@ -1373,25 +1373,24 @@ namespace MWWorld
return;
}
float terrainHeight = -std::numeric_limits<float>::max();
if (ptr.getCell()->isExterior())
terrainHeight = getTerrainHeightAt(pos);
if (pos.z() < terrainHeight)
pos.z() = terrainHeight;
pos.z() += 20; // place slightly above. will snap down to ground with code below
const float terrainHeight = ptr.getCell()->isExterior() ? getTerrainHeightAt(pos) : -std::numeric_limits<float>::max();
pos.z() = std::max(pos.z(), terrainHeight + 20); // place slightly above terrain. will snap down to ground with code below
// 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());
if (force || !ptr.getClass().isActor() || (!isFlying(ptr) && !swims && isActorCollisionEnabled(ptr)))
{
osg::Vec3f traced = mPhysics->traceDown(ptr, pos, Constants::CellSizeInUnits);
if (traced.z() < pos.z())
pos.z() = traced.z();
pos.z() = std::min(pos.z(), traced.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()
@ -1490,17 +1489,11 @@ namespace MWWorld
ipos.pos[1] = spawnPoint.y();
ipos.pos[2] = spawnPoint.z();
if (!referenceObject.getClass().isActor())
{
ipos.rot[0] = referenceObject.getRefData().getPosition().rot[0];
ipos.rot[1] = referenceObject.getRefData().getPosition().rot[1];
}
else
if (referenceObject.getClass().isActor())
{
ipos.rot[0] = 0;
ipos.rot[1] = 0;
}
ipos.rot[2] = referenceObject.getRefData().getPosition().rot[2];
MWWorld::Ptr placed = copyObjectToCell(ptr, referenceCell, ipos, ptr.getRefData().getCount(), false);
adjustPosition(placed, true); // snap to ground
@ -1545,6 +1538,7 @@ namespace MWWorld
mProjectileManager->update(duration);
const auto results = mPhysics->applyQueuedMovement(duration, mDiscardMovements, frameStart, frameNumber, stats);
mProjectileManager->processHits();
mDiscardMovements = false;
for(const auto& [actor, position]: results)

View file

@ -233,11 +233,19 @@ namespace Misc
, mTechnique(technique)
, mGeometryShaderMask(geometryShaderMask)
, mNoShaderMask(noShaderMask)
, mMasterConfig(new SharedShadowMapConfig)
, mSlaveConfig(new SharedShadowMapConfig)
, mSharedShadowMaps(Settings::Manager::getBool("shared shadow maps", "Stereo"))
{
if (technique == Technique::None)
// Do nothing
return;
mMasterConfig->_id = "STEREO";
mMasterConfig->_master = true;
mSlaveConfig->_id = "STEREO";
mSlaveConfig->_master = false;
SceneUtil::FindByNameVisitor findScene("Scene Root");
mRoot->accept(findScene);
mScene = findScene.mFoundNode;
@ -292,6 +300,12 @@ namespace Misc
mRightCamera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
mRightCamera->setCullMask(mMainCamera->getCullMask());
if (mSharedShadowMaps)
{
mLeftCamera->setUserData(mMasterConfig);
mRightCamera->setUserData(mSlaveConfig);
}
// Slave cameras must have their viewports defined immediately
auto width = mMainCamera->getViewport()->width();
auto height = mMainCamera->getViewport()->height();
@ -366,65 +380,66 @@ namespace Misc
auto width = mMainCamera->getViewport()->width();
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)
{
// 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
mMainCamera->setViewMatrix(frustumViewMatrix);
@ -439,6 +454,18 @@ namespace Misc
mLeftCamera->setViewport(0, 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());
}
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)

View file

@ -8,6 +8,8 @@
#include <memory>
#include <components/sceneutil/mwshadowtechnique.hpp>
// Some cursed headers like to define these
#if defined(near) || defined(far)
#undef near
@ -122,6 +124,11 @@ namespace Misc
osg::ref_ptr<osg::Camera> mLeftCamera{ 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
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
// (the stateset and the texgens).
lhs->_traversalNumber = rhs->_traversalNumber;
lhs->_viewDependentShadowMap = rhs->_viewDependentShadowMap;
lhs->getStateSet()->clear();
auto* stateset = lhs->getStateSet(cv.getTraversalNumber());
stateset->clear();
lhs->_lightDataList = rhs->_lightDataList;
lhs->_numValidShadows = rhs->_numValidShadows;
@ -1480,6 +1480,7 @@ void MWShadowTechnique::update(osg::NodeVisitor& nv)
void MWShadowTechnique::cull(osgUtil::CullVisitor& cv)
{
if (!_enableShadows)
{
if (mSetDummyStateWhenDisabled)
@ -1554,7 +1555,6 @@ void MWShadowTechnique::cull(osgUtil::CullVisitor& cv)
if (doCastShadow)
{
vdd->setTraversalNumber(vdd->getTraversalNumber() + 1);
castShadows(cv, vdd);
}
@ -1566,7 +1566,7 @@ void MWShadowTechnique::cull(osgUtil::CullVisitor& cv)
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;
@ -3148,17 +3148,17 @@ void MWShadowTechnique::cullShadowCastingScene(osgUtil::CullVisitor* cv, osg::Ca
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->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;
stateset->addUniform(uniform);

View file

@ -214,7 +214,7 @@ namespace SceneUtil {
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;
@ -222,10 +222,6 @@ namespace SceneUtil {
void setNumValidShadows(unsigned int numValidShadows) { _numValidShadows = numValidShadows; }
void setTraversalNumber(unsigned int traversalNumber) { _traversalNumber = traversalNumber; }
unsigned int getTraversalNumber() { return _traversalNumber; }
protected:
friend class MWShadowTechnique;
virtual ~ViewDependentData() {}
@ -238,7 +234,6 @@ namespace SceneUtil {
ShadowDataList _shadowDataList;
unsigned int _numValidShadows;
unsigned int _traversalNumber;
};
virtual ViewDependentData* createViewDependentData(osgUtil::CullVisitor* cv);
@ -279,7 +274,7 @@ namespace SceneUtil {
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:
virtual ~MWShadowTechnique();

View file

@ -1,6 +1,6 @@
#include "sdlgraphicswindow.hpp"
#include <osgViewer/viewer>
#include <osgViewer/Viewer>
#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.
always allow stealing from knocked out actors = false
# Enables visually harvesting plants for models that support it.
graphic herbalism = true
[General]
# 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.
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]
# 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