diff --git a/CHANGELOG.md b/CHANGELOG.md index f0c6dd5ee..e54515676 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 93faa009f..c8d8ade4b 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -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 ) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index f7b65183f..343355b8b 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -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); diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index 3a6dc526a..aca050592 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -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().mPath.string()); - std::cout << v.describe() << std::endl; + Log(Debug::Info) << v.describe(); engine.setGrabMouse(!variables["no-grab"].as()); // Font encoding settings std::string encoding(variables["encoding"].as().toStdString()); - std::cout << ToUTF8::encodingUsingMessage(encoding) << std::endl; + Log(Debug::Info) << ToUTF8::encodingUsingMessage(encoding); engine.setEncoding(ToUTF8::calculateEncoding(encoding)); // directory settings diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp index a27e3debd..28305c394 100644 --- a/apps/openmw/mwclass/container.cpp +++ b/apps/openmw/mwclass/container.cpp @@ -2,6 +2,7 @@ #include #include +#include #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; diff --git a/apps/openmw/mwclass/container.hpp b/apps/openmw/mwclass/container.hpp index 57dbf0c76..2dc0c06ca 100644 --- a/apps/openmw/mwclass/container.hpp +++ b/apps/openmw/mwclass/container.hpp @@ -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 diff --git a/apps/openmw/mwgui/spellmodel.cpp b/apps/openmw/mwgui/spellmodel.cpp index 136547c4c..78b9171e5 100644 --- a/apps/openmw/mwgui/spellmodel.cpp +++ b/apps/openmw/mwgui/spellmodel.cpp @@ -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); diff --git a/apps/openmw/mwmechanics/aicast.cpp b/apps/openmw/mwmechanics/aicast.cpp index 9ad7b4c56..af3aac340 100644 --- a/apps/openmw/mwmechanics/aicast.cpp +++ b/apps/openmw/mwmechanics/aicast.cpp @@ -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) diff --git a/apps/openmw/mwphysics/actor.cpp b/apps/openmw/mwphysics/actor.cpp index 0e557378d..77b07cde5 100644 --- a/apps/openmw/mwphysics/actor.cpp +++ b/apps/openmw/mwphysics/actor.cpp @@ -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(this)); + mCollisionObject->setUserPointer(this); updateRotation(); updateScale(); diff --git a/apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp b/apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp index ddfdb8a42..8f7242276 100644 --- a/apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp +++ b/apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp @@ -2,6 +2,14 @@ #include +#include + +#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(convexResult.m_hitCollisionObject->getUserPointer()); + if (!projectileHolder->isActive()) + return btScalar(1); + auto* targetHolder = static_cast(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; diff --git a/apps/openmw/mwphysics/closestnotmerayresultcallback.cpp b/apps/openmw/mwphysics/closestnotmerayresultcallback.cpp index 86763a793..422ca78bd 100644 --- a/apps/openmw/mwphysics/closestnotmerayresultcallback.cpp +++ b/apps/openmw/mwphysics/closestnotmerayresultcallback.cpp @@ -1,18 +1,22 @@ #include "closestnotmerayresultcallback.hpp" #include +#include #include #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& targets, const btVector3& from, const btVector3& to) + ClosestNotMeRayResultCallback::ClosestNotMeRayResultCallback(const btCollisionObject* me, std::vector 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(rayResult.m_collisionObject->getUserPointer()); + auto* holder = static_cast(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(rayResult.m_collisionObject->getUserPointer()); + if (auto* target = dynamic_cast(holder)) + { + mProjectile->hit(target->getPtr(), m_hitPointWorld, m_hitNormalWorld); + } + else if (auto* target = dynamic_cast(holder)) + { + target->hit(mProjectile->getPtr(), m_hitPointWorld, m_hitNormalWorld); + mProjectile->hit(target->getPtr(), m_hitPointWorld, m_hitNormalWorld); + } + } + + return rayResult.m_hitFraction; } } diff --git a/apps/openmw/mwphysics/closestnotmerayresultcallback.hpp b/apps/openmw/mwphysics/closestnotmerayresultcallback.hpp index 23d52998c..b86427165 100644 --- a/apps/openmw/mwphysics/closestnotmerayresultcallback.hpp +++ b/apps/openmw/mwphysics/closestnotmerayresultcallback.hpp @@ -9,15 +9,18 @@ class btCollisionObject; namespace MWPhysics { + class Projectile; + class ClosestNotMeRayResultCallback : public btCollisionWorld::ClosestRayResultCallback { public: - ClosestNotMeRayResultCallback(const btCollisionObject* me, const std::vector& targets, const btVector3& from, const btVector3& to); + ClosestNotMeRayResultCallback(const btCollisionObject* me, std::vector 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 mTargets; + Projectile* mProjectile; }; } diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index bdffcef44..a78a30788 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -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(p)) + { + projectile->commitPositionChange(); + mCollisionWorld->updateSingleAabb(projectile->getCollisionObject()); + } }; } diff --git a/apps/openmw/mwphysics/mtphysics.hpp b/apps/openmw/mwphysics/mtphysics.hpp index f7a131ca2..3a761829b 100644 --- a/apps/openmw/mwphysics/mtphysics.hpp +++ b/apps/openmw/mwphysics/mtphysics.hpp @@ -34,7 +34,7 @@ namespace MWPhysics /// @return new position of each actor const PtrPositionList& moveActors(int numSteps, float timeAccum, std::vector&& 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; diff --git a/apps/openmw/mwphysics/object.cpp b/apps/openmw/mwphysics/object.cpp index c822bbcbe..910f7bf15 100644 --- a/apps/openmw/mwphysics/object.cpp +++ b/apps/openmw/mwphysics/object.cpp @@ -24,7 +24,7 @@ namespace MWPhysics mCollisionObject.reset(new btCollisionObject); mCollisionObject->setCollisionShape(shapeInstance->getCollisionShape()); - mCollisionObject->setUserPointer(static_cast(this)); + mCollisionObject->setUserPointer(this); setScale(ptr.getCellRef().getScale()); setRotation(Misc::Convert::toBullet(ptr.getRefData().getBaseNode()->getAttitude())); diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index cdbad2cdd..324b1db2d 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -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 targets, int mask, int group) const + RayCastingResult PhysicsSystem::castRay(const osg::Vec3f &from, const osg::Vec3f &to, const MWWorld::ConstPtr& ignore, std::vector 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(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); } diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 03ae78993..b33d829a4 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -56,6 +56,9 @@ namespace MWPhysics class Object; class Actor; class PhysicsTaskScheduler; + class Projectile; + + using ActorMap = std::map>; 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 targets = std::vector(), - 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 mAnimatedObjects; // stores pointers to elements in mObjects - using ActorMap = std::map>; ActorMap mActors; + using ProjectileMap = std::map>; + ProjectileMap mProjectiles; + using HeightFieldMap = std::map, HeightField *>; HeightFieldMap mHeightFields; @@ -278,6 +288,8 @@ namespace MWPhysics float mTimeAccum; + unsigned int mProjectileId; + float mWaterHeight; bool mWaterEnabled; diff --git a/apps/openmw/mwphysics/projectile.cpp b/apps/openmw/mwphysics/projectile.cpp new file mode 100644 index 000000000..5f5bc778b --- /dev/null +++ b/apps/openmw/mwphysics/projectile.cpp @@ -0,0 +1,89 @@ +#include + +#include +#include + +#include + +#include +#include +#include +#include + +#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(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 lock(mPositionMutex); + if (mTransformUpdatePending) + { + mCollisionObject->setWorldTransform(mLocalTransform); + mTransformUpdatePending = false; + } +} + +void Projectile::setPosition(const osg::Vec3f &position) +{ + std::unique_lock 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 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); +} +} diff --git a/apps/openmw/mwphysics/projectile.hpp b/apps/openmw/mwphysics/projectile.hpp new file mode 100644 index 000000000..ed0fdce15 --- /dev/null +++ b/apps/openmw/mwphysics/projectile.hpp @@ -0,0 +1,106 @@ +#ifndef OPENMW_MWPHYSICS_PROJECTILE_H +#define OPENMW_MWPHYSICS_PROJECTILE_H + +#include +#include +#include + +#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 mShape; + btConvexShape* mConvexShape; + + std::unique_ptr mCollisionObject; + btTransform mLocalTransform; + bool mTransformUpdatePending; + std::atomic 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 diff --git a/apps/openmw/mwphysics/raycasting.hpp b/apps/openmw/mwphysics/raycasting.hpp index 7afbe9321..8ca6965d8 100644 --- a/apps/openmw/mwphysics/raycasting.hpp +++ b/apps/openmw/mwphysics/raycasting.hpp @@ -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 targets = std::vector(), - 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; diff --git a/apps/openmw/mwphysics/trace.cpp b/apps/openmw/mwphysics/trace.cpp index 58082f4db..4d2fccdb7 100644 --- a/apps/openmw/mwphysics/trace.cpp +++ b/apps/openmw/mwphysics/trace.cpp @@ -5,6 +5,9 @@ #include #include +#include "../mwbase/world.hpp" +#include "../mwbase/environment.hpp" + #include "collisiontype.hpp" #include "actor.hpp" #include "closestnotmeconvexresultcallback.hpp" diff --git a/apps/openmw/mwvr/openxrmanagerimpl.cpp b/apps/openmw/mwvr/openxrmanagerimpl.cpp index f831f7bf7..8f990e249 100644 --- a/apps/openmw/mwvr/openxrmanagerimpl.cpp +++ b/apps/openmw/mwvr/openxrmanagerimpl.cpp @@ -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* 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) { diff --git a/apps/openmw/mwvr/openxrmanagerimpl.hpp b/apps/openmw/mwvr/openxrmanagerimpl.hpp index 12e3a486d..6455f42be 100644 --- a/apps/openmw/mwvr/openxrmanagerimpl.hpp +++ b/apps/openmw/mwvr/openxrmanagerimpl.hpp @@ -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 diff --git a/apps/openmw/mwvr/vrsession.cpp b/apps/openmw/mwvr/vrsession.cpp index 152052087..b974f5148 100644 --- a/apps/openmw/mwvr/vrsession.cpp +++ b/apps/openmw/mwvr/vrsession.cpp @@ -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 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; diff --git a/apps/openmw/mwvr/vrtypes.hpp b/apps/openmw/mwvr/vrtypes.hpp index c716d60c9..4a831d568 100644 --- a/apps/openmw/mwvr/vrtypes.hpp +++ b/apps/openmw/mwvr/vrtypes.hpp @@ -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; }; diff --git a/apps/openmw/mwvr/vrview.cpp b/apps/openmw/mwvr/vrview.cpp index 6d51b36c7..86f48a28c 100644 --- a/apps/openmw/mwvr/vrview.cpp +++ b/apps/openmw/mwvr/vrview.cpp @@ -1,138 +1,138 @@ -#include "vrview.hpp" - -#include "openxrmanager.hpp" -#include "openxrmanagerimpl.hpp" -#include "vrsession.hpp" -#include "vrenvironment.hpp" - -#include - -#include - -namespace MWVR { - - VRView::VRView( - std::string name, - SwapchainConfig config, - osg::ref_ptr 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 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(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 +// +//#include +// +//namespace MWVR { +// +// VRView::VRView( +// std::string name, +// SwapchainConfig config, +// osg::ref_ptr 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 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(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); +// } +//} diff --git a/apps/openmw/mwvr/vrview.hpp b/apps/openmw/mwvr/vrview.hpp index 4a18ffb57..b3799b068 100644 --- a/apps/openmw/mwvr/vrview.hpp +++ b/apps/openmw/mwvr/vrview.hpp @@ -1,65 +1,65 @@ -#ifndef MWVR_VRVIEW_H -#define MWVR_VRVIEW_H - -#include -#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 view) : mView(view) {} - void updateSlave(osg::View& view, osg::View::Slave& slave) override; - - private: - osg::ref_ptr mView; - }; - - public: - VRView(std::string name, SwapchainConfig config, osg::ref_ptr 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 mSwapchain; - std::string mName{}; - osg::Node::NodeMask mCullMask; - bool mRendering{ false }; - }; -} - -#endif +//#ifndef MWVR_VRVIEW_H +//#define MWVR_VRVIEW_H +// +//#include +//#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 view) : mView(view) {} +// void updateSlave(osg::View& view, osg::View::Slave& slave) override; +// +// private: +// osg::ref_ptr mView; +// }; +// +// public: +// VRView(std::string name, SwapchainConfig config, osg::ref_ptr 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 mSwapchain; +// std::string mName{}; +// osg::Node::NodeMask mCullMask; +// bool mRendering{ false }; +// }; +//} +// +//#endif diff --git a/apps/openmw/mwvr/vrviewer.cpp b/apps/openmw/mwvr/vrviewer.cpp index f48eb0ae0..70b0ceac7 100644 --- a/apps/openmw/mwvr/vrviewer.cpp +++ b/apps/openmw/mwvr/vrviewer.cpp @@ -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 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(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 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 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(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. diff --git a/apps/openmw/mwvr/vrviewer.hpp b/apps/openmw/mwvr/vrviewer.hpp index 660879b6b..dd5ce241a 100644 --- a/apps/openmw/mwvr/vrviewer.hpp +++ b/apps/openmw/mwvr/vrviewer.hpp @@ -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 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 >& changed); - void enableMainCamera(void); - void disableMainCamera(void); + SubImage subImage(Side side); private: + std::mutex mMutex{}; + bool mConfigured{ false }; + osg::ref_ptr mViewer = nullptr; - std::map > mViews; - std::map > mCameras{}; osg::ref_ptr mPreDraw{ nullptr }; osg::ref_ptr mPostDraw{ nullptr }; - osg::GraphicsContext* mMainCameraGC{ nullptr }; - std::map< std::string, std::unique_ptr > mMsaaResolveMirrorTexture; + + std::unique_ptr mMsaaResolveMirrorTexture; std::unique_ptr mMirrorTexture; - VrShadow mVrShadow; - - std::mutex mMutex{}; - - bool mConfigured{ false }; std::vector mMirrorTextureViews; bool mMirrorTextureShouldBeCleanedUp{ false }; bool mMirrorTextureEnabled{ false }; bool mFlipMirrorTextureOrder{ false }; MirrorTextureEye mMirrorTextureEye{ MirrorTextureEye::Both }; + + std::unique_ptr mSwapchain; + std::array mSubImages; + SwapchainConfig mSwapchainConfig; }; } diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index 17c35489f..635485dde 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -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::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::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()->mBase->mInventory, "", mStore.mSeed); addScripts(mStore, mStore.mPtr.mCell); @@ -127,15 +123,14 @@ template void MWWorld::ContainerStore::storeStates (const CellRefList& collection, ESM::InventoryState& inventory, int& index, bool equipable) const { - for (typename CellRefList::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()->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()->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::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++; diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index 42287ba05..b69e2eced 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -2,6 +2,7 @@ #include +#include #include #include @@ -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()->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::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::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::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() .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 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::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 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 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::iterator it = mProjectiles.begin(); it != mProjectiles.end(); ++it) - { - cleanupProjectile(*it); - } + for (auto& mProjectile : mProjectiles) + cleanupProjectile(mProjectile); mProjectiles.clear(); - for (std::vector::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()->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(...) { diff --git a/apps/openmw/mwworld/projectilemanager.hpp b/apps/openmw/mwworld/projectilemanager.hpp index c7b1056b7..d589e985e 100644 --- a/apps/openmw/mwworld/projectilemanager.hpp +++ b/apps/openmw/mwworld/projectilemanager.hpp @@ -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 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); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index ad57e0777..e838aca99 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1373,25 +1373,24 @@ namespace MWWorld return; } - float terrainHeight = -std::numeric_limits::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::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) diff --git a/components/misc/stereo.cpp b/components/misc/stereo.cpp index b57c71b01..5816c4d9e 100644 --- a/components/misc/stereo.cpp +++ b/components/misc/stereo.cpp @@ -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 cb) + void StereoView::setUpdateViewCallback(std::shared_ptr cb_) { - this->cb = cb; + cb = cb_; } void disableStereoForCamera(osg::Camera* camera) diff --git a/components/misc/stereo.hpp b/components/misc/stereo.hpp index c721f6a1c..e8e2d5886 100644 --- a/components/misc/stereo.hpp +++ b/components/misc/stereo.hpp @@ -8,6 +8,8 @@ #include +#include + // Some cursed headers like to define these #if defined(near) || defined(far) #undef near @@ -122,6 +124,11 @@ namespace Misc osg::ref_ptr mLeftCamera{ new osg::Camera }; osg::ref_ptr mRightCamera{ new osg::Camera }; + using SharedShadowMapConfig = SceneUtil::MWShadowTechnique::SharedShadowMapConfig; + osg::ref_ptr mMasterConfig; + osg::ref_ptr mSlaveConfig; + bool mSharedShadowMaps; + // Camera viewports bool flipViewOrder{ true }; diff --git a/components/sceneutil/mwshadowtechnique.cpp b/components/sceneutil/mwshadowtechnique.cpp index bf91d5c16..e16c07539 100644 --- a/components/sceneutil/mwshadowtechnique.cpp +++ b/components/sceneutil/mwshadowtechnique.cpp @@ -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()< stateset = vdd.getStateSet(); + osg::ref_ptr 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("<getName()<<")"<addUniform(uniform); diff --git a/components/sceneutil/mwshadowtechnique.hpp b/components/sceneutil/mwshadowtechnique.hpp index d28ff7fb4..fbcfb8bb8 100644 --- a/components/sceneutil/mwshadowtechnique.hpp +++ b/components/sceneutil/mwshadowtechnique.hpp @@ -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(); diff --git a/components/sdlutil/sdlgraphicswindow.cpp b/components/sdlutil/sdlgraphicswindow.cpp index 39b479f29..9e7ab5f9b 100644 --- a/components/sdlutil/sdlgraphicswindow.cpp +++ b/components/sdlutil/sdlgraphicswindow.cpp @@ -1,6 +1,6 @@ #include "sdlgraphicswindow.hpp" -#include +#include #include diff --git a/files/settings-default.cfg b/files/settings-default.cfg index d2f84a792..1bb7090bf 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -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