From e7de6b974aac469adfa9bb23956ac961104133bf Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 21 Sep 2018 16:34:23 +0400 Subject: [PATCH] Optimize actors processing 1. Do not update physics and animations for actors outside processing range (bug #4647) 2. Do not render such actors 3. Add transparency to actors near processing border, so they will not pop up suddenly --- apps/openmw/mwbase/mechanicsmanager.hpp | 4 + apps/openmw/mwbase/world.hpp | 3 + apps/openmw/mwgui/settingswindow.cpp | 2 + apps/openmw/mwmechanics/actors.cpp | 97 ++++++++++++------- apps/openmw/mwmechanics/actors.hpp | 4 + apps/openmw/mwmechanics/aipackage.cpp | 4 +- apps/openmw/mwmechanics/aitravel.cpp | 6 +- apps/openmw/mwmechanics/character.cpp | 52 ++++++---- apps/openmw/mwmechanics/character.hpp | 3 +- .../mwmechanics/mechanicsmanagerimp.cpp | 19 ++++ .../mwmechanics/mechanicsmanagerimp.hpp | 6 ++ apps/openmw/mwphysics/physicssystem.cpp | 27 ++++++ apps/openmw/mwphysics/physicssystem.hpp | 2 + apps/openmw/mwrender/animation.cpp | 32 +++--- apps/openmw/mwworld/worldimp.cpp | 16 +++ apps/openmw/mwworld/worldimp.hpp | 3 + .../reference/modding/settings/game.rst | 15 ++- files/mygui/openmw_settings_window.layout | 33 ++++++- files/settings-default.cfg | 3 + 19 files changed, 258 insertions(+), 73 deletions(-) diff --git a/apps/openmw/mwbase/mechanicsmanager.hpp b/apps/openmw/mwbase/mechanicsmanager.hpp index 8137bad95..056628211 100644 --- a/apps/openmw/mwbase/mechanicsmanager.hpp +++ b/apps/openmw/mwbase/mechanicsmanager.hpp @@ -233,6 +233,10 @@ namespace MWBase virtual void castSpell(const MWWorld::Ptr& ptr, const std::string spellId, bool manualSpell) = 0; + virtual void processChangedSettings (const std::set< std::pair >& settings) = 0; + + virtual float getActorsProcessingRange() const = 0; + /// Check if the target actor was detected by an observer /// If the observer is a non-NPC, check all actors in AI processing distance as observers virtual bool isActorDetected(const MWWorld::Ptr& actor, const MWWorld::Ptr& observer) = 0; diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 027d1fd10..864b81158 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -302,6 +302,9 @@ namespace MWBase virtual bool castRay (float x1, float y1, float z1, float x2, float y2, float z2) = 0; + virtual void setActorCollisionMode(const MWWorld::Ptr& ptr, bool enabled) = 0; + virtual bool isActorCollisionEnabled(const MWWorld::Ptr& ptr) = 0; + virtual bool toggleCollisionMode() = 0; ///< Toggle collision mode for player. If disabled player object should ignore /// collisions and gravity. diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index 80ed9202a..6e6924f28 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -20,6 +20,7 @@ #include "../mwbase/world.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwbase/inputmanager.hpp" +#include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/windowmanager.hpp" #include "confirmationdialog.hpp" @@ -437,6 +438,7 @@ namespace MWGui MWBase::Environment::get().getSoundManager()->processChangedSettings(changed); MWBase::Environment::get().getWindowManager()->processChangedSettings(changed); MWBase::Environment::get().getInputManager()->processChangedSettings(changed); + MWBase::Environment::get().getMechanicsManager()->processChangedSettings(changed); } void SettingsWindow::onKeyboardSwitchClicked(MyGUI::Widget* _sender) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 2a68591a8..d4350e07c 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -147,9 +147,6 @@ void getRestorationPerHourOfSleep (const MWWorld::Ptr& ptr, float& health, float namespace MWMechanics { - const float aiProcessingDistance = 7168; - const float sqrAiProcessingDistance = aiProcessingDistance*aiProcessingDistance; - class SoulTrap : public MWMechanics::EffectSourceVisitor { MWWorld::Ptr mCreature; @@ -364,7 +361,8 @@ namespace MWMechanics const ESM::Position& actor1Pos = actor1.getRefData().getPosition(); const ESM::Position& actor2Pos = actor2.getRefData().getPosition(); float sqrDist = (actor1Pos.asVec3() - actor2Pos.asVec3()).length2(); - if (sqrDist > sqrAiProcessingDistance) + + if (sqrDist > mActorsProcessingRange*mActorsProcessingRange) return; // No combat for totally static creatures @@ -1130,8 +1128,11 @@ namespace MWMechanics } } - Actors::Actors() { + Actors::Actors() + { mTimerDisposeSummonsCorpses = 0.2f; // We should add a delay between summoned creature death and its corpse despawning + + updateProcessingRange(); } Actors::~Actors() @@ -1139,6 +1140,23 @@ namespace MWMechanics clear(); } + float Actors::getProcessingRange() const + { + return mActorsProcessingRange; + } + + void Actors::updateProcessingRange() + { + // We have to cap it since using high values (larger than 7168) will make some quests harder or impossible to complete (bug #1876) + static const float maxProcessingRange = 7168.f; + static const float minProcessingRange = maxProcessingRange / 2.f; + + float actorsProcessingRange = Settings::Manager::getFloat("actors processing range", "Game"); + actorsProcessingRange = std::min(actorsProcessingRange, maxProcessingRange); + actorsProcessingRange = std::max(actorsProcessingRange, minProcessingRange); + mActorsProcessingRange = actorsProcessingRange; + } + void Actors::addActor (const MWWorld::Ptr& ptr, bool updateImmediately) { removeActor(ptr); @@ -1184,7 +1202,7 @@ namespace MWMechanics // Otherwise check if any actor in AI processing range sees the target actor std::vector actors; osg::Vec3f position (actor.getRefData().getPosition().asVec3()); - getObjectsInRange(position, aiProcessingDistance, actors); + getObjectsInRange(position, mActorsProcessingRange, actors); for(std::vector::iterator it = actors.begin(); it != actors.end(); ++it) { if (*it == actor) @@ -1242,7 +1260,7 @@ namespace MWMechanics { if (iter->first == player) continue; - bool inProcessingRange = (playerPos - iter->first.getRefData().getPosition().asVec3()).length2() <= sqrAiProcessingDistance; + bool inProcessingRange = (playerPos - iter->first.getRefData().getPosition().asVec3()).length2() <= mActorsProcessingRange*mActorsProcessingRange; if (inProcessingRange) { MWMechanics::CreatureStats& stats = iter->first.getClass().getCreatureStats(iter->first); @@ -1288,7 +1306,8 @@ namespace MWMechanics if (timerUpdateEquippedLight >= updateEquippedLightInterval) timerUpdateEquippedLight = 0; // show torches only when there are darkness and no precipitations - bool showTorches = MWBase::Environment::get().getWorld()->useTorches(); + MWBase::World* world = MWBase::Environment::get().getWorld(); + bool showTorches = world->useTorches(); MWWorld::Ptr player = getPlayer(); const osg::Vec3f playerPos = player.getRefData().getPosition().asVec3(); @@ -1301,7 +1320,7 @@ namespace MWMechanics int attackedByPlayerId = player.getClass().getCreatureStats(player).getHitAttemptActorId(); if (attackedByPlayerId != -1) { - const MWWorld::Ptr playerHitAttemptActor = MWBase::Environment::get().getWorld()->searchPtrViaActorId(attackedByPlayerId); + const MWWorld::Ptr playerHitAttemptActor = world->searchPtrViaActorId(attackedByPlayerId); if (!playerHitAttemptActor.isInCell()) player.getClass().getCreatureStats(player).setHitAttemptActorId(-1); @@ -1314,14 +1333,11 @@ namespace MWMechanics CharacterController* ctrl = iter->second->getCharacterController(); float distSqr = (playerPos - iter->first.getRefData().getPosition().asVec3()).length2(); - // AI processing is only done within distance of 7168 units to the player. Note the "AI distance" slider doesn't affect this - // (it only does some throttling for targets beyond the "AI distance", so doesn't give any guarantees as to whether AI will be enabled or not) - // This distance could be made configurable later, but the setting must be marked with a big warning: - // using higher values will make a quest in Bloodmoon harder or impossible to complete (bug #1876) - bool inProcessingRange = distSqr <= sqrAiProcessingDistance; + // AI processing is only done within given distance to the player. + bool inProcessingRange = distSqr <= mActorsProcessingRange*mActorsProcessingRange; if (isPlayer) - ctrl->setAttackingOrSpell(MWBase::Environment::get().getWorld()->getPlayer().getAttackingOrSpell()); + ctrl->setAttackingOrSpell(world->getPlayer().getAttackingOrSpell()); // If dead or no longer in combat, no longer store any actors who attempted to hit us. Also remove for the player. if (iter->first != player && (iter->first.getClass().getCreatureStats(iter->first).isDead() @@ -1335,10 +1351,10 @@ namespace MWMechanics if (!iter->first.getClass().getCreatureStats(iter->first).isDead()) { - bool cellChanged = MWBase::Environment::get().getWorld()->hasCellChanged(); + bool cellChanged = world->hasCellChanged(); MWWorld::Ptr actor = iter->first; // make a copy of the map key to avoid it being invalidated when the player teleports updateActor(actor, duration); - if (!cellChanged && MWBase::Environment::get().getWorld()->hasCellChanged()) + if (!cellChanged && world->hasCellChanged()) { return; // for now abort update of the old cell when cell changes by teleportation magic effect // a better solution might be to apply cell changes at the end of the frame @@ -1363,7 +1379,7 @@ namespace MWMechanics MWWorld::Ptr headTrackTarget; MWMechanics::CreatureStats& stats = iter->first.getClass().getCreatureStats(iter->first); - bool firstPersonPlayer = isPlayer && MWBase::Environment::get().getWorld()->isFirstPerson(); + bool firstPersonPlayer = isPlayer && world->isFirstPerson(); // 1. Unconsious actor can not track target // 2. Actors in combat and pursue mode do not bother to headtrack @@ -1423,27 +1439,25 @@ namespace MWMechanics CharacterController* playerCharacter = nullptr; for(PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) { - const float animationDistance = aiProcessingDistance + 400; // Slightly larger than AI distance so there is time to switch back to the idle animation. - const float distSqr = (playerPos - iter->first.getRefData().getPosition().asVec3()).length2(); + const float dist = (playerPos - iter->first.getRefData().getPosition().asVec3()).length(); bool isPlayer = iter->first == player; - bool inAnimationRange = isPlayer || (animationDistance == 0 || distSqr <= animationDistance*animationDistance); + bool inRange = isPlayer || dist <= mActorsProcessingRange; int activeFlag = 1; // Can be changed back to '2' to keep updating bounding boxes off screen (more accurate, but slower) if (isPlayer) activeFlag = 2; - int active = inAnimationRange ? activeFlag : 0; - bool canFly = iter->first.getClass().canFly(iter->first); - if (canFly) - { - // Keep animating flying creatures so they don't just hover in-air - inAnimationRange = true; - active = std::max(1, active); - } + int active = inRange ? activeFlag : 0; CharacterController* ctrl = iter->second->getCharacterController(); ctrl->setActive(active); - if (!inAnimationRange) + if (!inRange) + { + iter->first.getRefData().getBaseNode()->setNodeMask(0); + world->setActorCollisionMode(iter->first, false); continue; + } + else if (!isPlayer) + iter->first.getRefData().getBaseNode()->setNodeMask(1<<3); if (iter->first.getClass().getCreatureStats(iter->first).isParalyzed()) ctrl->skipAnim(); @@ -1455,11 +1469,28 @@ namespace MWMechanics playerCharacter = ctrl; continue; } + + world->setActorCollisionMode(iter->first, true); ctrl->update(duration); + + // Fade away actors on large distance (>90% of actor's processing distance) + float visibilityRatio = 1.0; + float fadeStartDistance = mActorsProcessingRange*0.9f; + float fadeEndDistance = mActorsProcessingRange; + float fadeRatio = (dist - fadeStartDistance)/(fadeEndDistance - fadeStartDistance); + if (fadeRatio > 0) + visibilityRatio -= std::max(0.f, fadeRatio); + + visibilityRatio = std::min(1.f, visibilityRatio); + + ctrl->setVisibility(visibilityRatio); } if (playerCharacter) + { playerCharacter->update(duration); + playerCharacter->setVisibility(1.f); + } for(PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) { @@ -1671,7 +1702,7 @@ namespace MWMechanics restoreDynamicStats(iter->first, sleep); if ((!iter->first.getRefData().getBaseNode()) || - (playerPos - iter->first.getRefData().getPosition().asVec3()).length2() > sqrAiProcessingDistance) + (playerPos - iter->first.getRefData().getPosition().asVec3()).length2() > mActorsProcessingRange*mActorsProcessingRange) continue; adjustMagicEffects (iter->first); @@ -1915,7 +1946,7 @@ namespace MWMechanics std::list list; std::vector neighbors; osg::Vec3f position (actor.getRefData().getPosition().asVec3()); - getObjectsInRange(position, aiProcessingDistance, neighbors); + getObjectsInRange(position, mActorsProcessingRange, neighbors); for(auto neighbor = neighbors.begin(); neighbor != neighbors.end(); ++neighbor) { if (*neighbor == actor) @@ -1936,7 +1967,7 @@ namespace MWMechanics std::list list; std::vector neighbors; osg::Vec3f position (actor.getRefData().getPosition().asVec3()); - getObjectsInRange(position, aiProcessingDistance, neighbors); + getObjectsInRange(position, mActorsProcessingRange, neighbors); std::set followers; getActorsFollowing(actor, followers); diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index a1e0e511d..61879c432 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -65,6 +65,9 @@ namespace MWMechanics /// paused we may want to do it manually (after equipping permanent enchantment) void updateMagicEffects (const MWWorld::Ptr& ptr); + void updateProcessingRange(); + float getProcessingRange() const; + void addActor (const MWWorld::Ptr& ptr, bool updateImmediately=false); ///< Register an actor for stats management /// @@ -168,6 +171,7 @@ namespace MWMechanics private: PtrActorMap mActors; float mTimerDisposeSummonsCorpses; + float mActorsProcessingRange; }; } diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 9a42f191e..7045d28e5 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -103,7 +103,7 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const ESM::Pathgr ESM::Position pos = actor.getRefData().getPosition(); //position of the actor /// Stops the actor when it gets too close to a unloaded cell - //... At current time, this test is unnecessary. AI shuts down when actor is more than 7168 + //... At current time, this test is unnecessary. AI shuts down when actor is more than "actors processing range" setting value //... units from player, and exterior cells are 8192 units long and wide. //... But AI processing distance may increase in the future. if (isNearInactiveCell(pos)) @@ -354,7 +354,7 @@ bool MWMechanics::AiPackage::isNearInactiveCell(const ESM::Position& actorPos) // currently assumes 3 x 3 grid for exterior cells, with player at center cell. // ToDo: (Maybe) use "exterior cell load distance" setting to get count of actual active cells - // While AI Process distance is 7168, AI shuts down actors before they reach edges of 3 x 3 grid. + // AI shuts down actors before they reach edges of 3 x 3 grid. const float distanceFromEdge = 200.0; float minThreshold = (-1.0f * ESM::Land::REAL_SIZE) + distanceFromEdge; float maxThreshold = (2.0f * ESM::Land::REAL_SIZE) - distanceFromEdge; diff --git a/apps/openmw/mwmechanics/aitravel.cpp b/apps/openmw/mwmechanics/aitravel.cpp index 8b52b15a4..90beb9ead 100644 --- a/apps/openmw/mwmechanics/aitravel.cpp +++ b/apps/openmw/mwmechanics/aitravel.cpp @@ -3,8 +3,9 @@ #include #include -#include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" +#include "../mwbase/mechanicsmanager.hpp" +#include "../mwbase/world.hpp" #include "../mwworld/class.hpp" #include "../mwworld/cellstore.hpp" @@ -21,7 +22,8 @@ bool isWithinMaxRange(const osg::Vec3f& pos1, const osg::Vec3f& pos2) // Maximum travel distance for vanilla compatibility. // Was likely meant to prevent NPCs walking into non-loaded exterior cells, but for some reason is used in interior cells as well. // We can make this configurable at some point, but the default *must* be the below value. Anything else will break shoddily-written content (*cough* MW *cough*) in bizarre ways. - return (pos1 - pos2).length2() <= 7168*7168; + bool aiDistance = MWBase::Environment::get().getMechanicsManager()->getActorsProcessingRange(); + return (pos1 - pos2).length2() <= aiDistance*aiDistance; } } diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index ce844674f..d5e22cb07 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -30,6 +30,7 @@ #include "../mwrender/animation.hpp" #include "../mwbase/environment.hpp" +#include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/world.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwbase/windowmanager.hpp" @@ -1849,7 +1850,7 @@ void CharacterController::updateAnimQueue() mAnimation->setLoopingEnabled(mAnimQueue.front().mGroup, mAnimQueue.size() <= 1); } -void CharacterController::update(float duration) +void CharacterController::update(float duration, bool animationOnly) { MWBase::World *world = MWBase::Environment::get().getWorld(); const MWWorld::Class &cls = mPtr.getClass(); @@ -2235,10 +2236,10 @@ void CharacterController::update(float duration) world->rotateObject(mPtr, rot.x(), rot.y(), 0.0f, true); } - if (!mMovementAnimationControlled) + if (!animationOnly && !mMovementAnimationControlled) world->queueMovement(mPtr, vec); } - else + else if (!animationOnly) // We must always queue movement, even if there is none, to apply gravity. world->queueMovement(mPtr, osg::Vec3f(0.f, 0.f, 0.f)); @@ -2261,7 +2262,8 @@ void CharacterController::update(float duration) playDeath(1.f, mDeathState); } // We must always queue movement, even if there is none, to apply gravity. - world->queueMovement(mPtr, osg::Vec3f(0.f, 0.f, 0.f)); + if (!animationOnly) + world->queueMovement(mPtr, osg::Vec3f(0.f, 0.f, 0.f)); } bool isPersist = isPersistentAnimPlaying(); @@ -2295,7 +2297,7 @@ void CharacterController::update(float duration) moved.z() = 1.0; // Update movement - if(mMovementAnimationControlled && mPtr.getClass().isActor()) + if(!animationOnly && mMovementAnimationControlled && mPtr.getClass().isActor()) world->queueMovement(mPtr, moved); mSkipAnim = false; @@ -2544,20 +2546,6 @@ void CharacterController::updateMagicEffects() { if (!mPtr.getClass().isActor()) return; - float alpha = 1.f; - if (mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Invisibility).getModifier()) // Ignore base magnitude (see bug #3555). - { - if (mPtr == getPlayer()) - alpha = 0.4f; - else - alpha = 0.f; - } - float chameleon = mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Chameleon).getMagnitude(); - if (chameleon) - { - alpha *= std::max(0.2f, (100.f - chameleon)/100.f); - } - mAnimation->setAlpha(alpha); bool vampire = mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Vampirism).getMagnitude() > 0.0f; mAnimation->setVampire(vampire); @@ -2566,6 +2554,32 @@ void CharacterController::updateMagicEffects() mAnimation->setLightEffect(light); } +void CharacterController::setVisibility(float visibility) +{ + // We should take actor's invisibility in account + if (mPtr.getClass().isActor()) + { + float alpha = 1.f; + if (mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Invisibility).getModifier()) // Ignore base magnitude (see bug #3555). + { + if (mPtr == getPlayer()) + alpha = 0.4f; + else + alpha = 0.f; + } + float chameleon = mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Chameleon).getMagnitude(); + if (chameleon) + { + alpha *= std::max(0.2f, (100.f - chameleon)/100.f); + } + + visibility = std::min(visibility, alpha); + } + + // TODO: implement a dithering shader rather than just change object transparency. + mAnimation->setAlpha(visibility); +} + void CharacterController::setAttackTypeBasedOnMovement() { float *move = mPtr.getClass().getMovementSettings(mPtr).mPosition; diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index f97614ef4..0f4b3aa90 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -257,7 +257,7 @@ public: void updatePtr(const MWWorld::Ptr &ptr); - void update(float duration); + void update(float duration, bool animationOnly=false); void persistAnimationState(); void unpersistAnimationState(); @@ -292,6 +292,7 @@ public: bool isTurning() const; bool isAttackingOrSpell() const; + void setVisibility(float visibility); void setAttackingOrSpell(bool attackingOrSpell); void castSpell(const std::string spellId, bool manualSpell=false); void setAIAttackType(const std::string& attackType); diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 619ed91b3..1c7576316 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -431,6 +431,25 @@ namespace MWMechanics mObjects.update(duration, paused); } + void MechanicsManager::processChangedSettings(const Settings::CategorySettingVector &changed) + { + for (Settings::CategorySettingVector::const_iterator it = changed.begin(); it != changed.end(); ++it) + { + if (it->first == "Game" && it->second == "actors processing range") + { + mActors.updateProcessingRange(); + + // Update mechanics for new processing range immediately + update(0.f, false); + } + } + } + + float MechanicsManager::getActorsProcessingRange() const + { + return mActors.getProcessingRange(); + } + bool MechanicsManager::isActorDetected(const MWWorld::Ptr& actor, const MWWorld::Ptr& observer) { return mActors.isActorDetected(actor, observer); diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp index 3682e97ce..26eaa968d 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp @@ -1,6 +1,8 @@ #ifndef GAME_MWMECHANICS_MECHANICSMANAGERIMP_H #define GAME_MWMECHANICS_MECHANICSMANAGERIMP_H +#include + #include "../mwbase/mechanicsmanager.hpp" #include "../mwworld/ptr.hpp" @@ -206,6 +208,10 @@ namespace MWMechanics virtual void castSpell(const MWWorld::Ptr& ptr, const std::string spellId, bool manualSpell=false); + void processChangedSettings(const Settings::CategorySettingVector& settings) override; + + virtual float getActorsProcessingRange() const; + /// Check if the target actor was detected by an observer /// If the observer is a non-NPC, check all actors in AI processing distance as observers virtual bool isActorDetected(const MWWorld::Ptr& actor, const MWWorld::Ptr& observer); diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 2ce498f37..9ee47f3f5 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -1366,6 +1366,33 @@ namespace MWPhysics return false; } + void PhysicsSystem::setActorCollisionMode(const MWWorld::Ptr& ptr, bool enabled) + { + ActorMap::iterator found = mActors.find(ptr); + if (found != mActors.end()) + { + bool cmode = found->second->getCollisionMode(); + if (cmode == enabled) + return; + + cmode = enabled; + found->second->enableCollisionMode(cmode); + found->second->enableCollisionBody(cmode); + } + } + + bool PhysicsSystem::isActorCollisionEnabled(const MWWorld::Ptr& ptr) + { + ActorMap::iterator found = mActors.find(ptr); + if (found != mActors.end()) + { + bool cmode = found->second->getCollisionMode(); + return cmode; + } + + return false; + } + void PhysicsSystem::queueObjectMovement(const MWWorld::Ptr &ptr, const osg::Vec3f &movement) { PtrVelocityList::iterator iter = mMovementQueue.begin(); diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 3ef9990f5..2de869395 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -86,6 +86,8 @@ namespace MWPhysics void removeHeightField (int x, int y); bool toggleCollisionMode(); + bool isActorCollisionEnabled(const MWWorld::Ptr& ptr); + void setActorCollisionMode(const MWWorld::Ptr& ptr, bool enabled); void stepSimulation(float dt); void debugDraw(); diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 6e2c76d1d..81701797d 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1748,21 +1748,31 @@ namespace MWRender if (alpha != 1.f) { - osg::StateSet* stateset (new osg::StateSet); + // If we have an existing material for alpha transparency, just override alpha level + osg::StateSet* stateset = mObjectRoot->getOrCreateStateSet(); + osg::Material* material = static_cast(stateset->getAttribute(osg::StateAttribute::MATERIAL)); + if (material) + { + material->setAlpha(osg::Material::FRONT_AND_BACK, alpha); + } + else + { + osg::StateSet* stateset (new osg::StateSet); - osg::BlendFunc* blendfunc (new osg::BlendFunc); - stateset->setAttributeAndModes(blendfunc, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + osg::BlendFunc* blendfunc (new osg::BlendFunc); + stateset->setAttributeAndModes(blendfunc, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); - // FIXME: overriding diffuse/ambient/emissive colors - osg::Material* material (new osg::Material); - material->setColorMode(osg::Material::OFF); - material->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,alpha)); - material->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1)); - stateset->setAttributeAndModes(material, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + // FIXME: overriding diffuse/ambient/emissive colors + material = new osg::Material; + material->setColorMode(osg::Material::OFF); + material->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,alpha)); + material->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1)); + stateset->setAttributeAndModes(material, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); - mObjectRoot->setStateSet(stateset); + mObjectRoot->setStateSet(stateset); - mResourceSystem->getSceneManager()->recreateShaders(mObjectRoot); + mResourceSystem->getSceneManager()->recreateShaders(mObjectRoot); + } } else { diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 0f20fa05a..3b1d84a66 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1582,6 +1582,16 @@ namespace MWWorld } } + void World::setActorCollisionMode(const MWWorld::Ptr& ptr, bool enabled) + { + mPhysics->setActorCollisionMode(ptr, enabled); + } + + bool World::isActorCollisionEnabled(const MWWorld::Ptr& ptr) + { + return mPhysics->isActorCollisionEnabled(ptr); + } + bool World::toggleCollisionMode() { if (mPhysics->toggleCollisionMode()) @@ -3559,7 +3569,13 @@ namespace MWWorld MWBase::Environment::get().getMechanicsManager()->getObjectsInRange( origin, feetToGameUnits(static_cast(effectIt->mArea)), objects); for (std::vector::iterator affected = objects.begin(); affected != objects.end(); ++affected) + { + // Ignore actors without collisions here, otherwise it will be possible to hit actors outside processing range. + if (affected->getClass().isActor() && !isActorCollisionEnabled(*affected)) + continue; + toApply[*affected].push_back(*effectIt); + } } // Now apply the appropriate effects to each actor in range diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 1592453a2..9b027ec5a 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -407,6 +407,9 @@ namespace MWWorld bool castRay (float x1, float y1, float z1, float x2, float y2, float z2) override; + void setActorCollisionMode(const Ptr& ptr, bool enabled) override; + bool isActorCollisionEnabled(const Ptr& ptr) override; + bool toggleCollisionMode() override; ///< Toggle collision mode for player. If disabled player object should ignore /// collisions and gravity. diff --git a/docs/source/reference/modding/settings/game.rst b/docs/source/reference/modding/settings/game.rst index 4c3f4579f..254496d5b 100644 --- a/docs/source/reference/modding/settings/game.rst +++ b/docs/source/reference/modding/settings/game.rst @@ -97,8 +97,21 @@ and values above 500 will result in the player inflicting no damage. This setting can be controlled in game with the Difficulty slider in the Prefs panel of the Options menu. +actors processing range +----------------------- + +:Type: integer +:Range: 3584 to 7168 +:Default: 7168 + +This setting allows to specify a distance from player in game units, in which OpenMW updates actor's state. +Actor state update includes AI, animations, and physics processing. +Actors near that border start softly fade out instead of just appearing/disapperaing. + +This setting can be controlled in game with the "Actors processing range slider" in the Prefs panel of the Options menu. + classic reflected absorb spells behavior ------------------------------------------ +---------------------------------------- :Type: boolean :Range: True/False diff --git a/files/mygui/openmw_settings_window.layout b/files/mygui/openmw_settings_window.layout index 8ff850cae..8e6e98612 100644 --- a/files/mygui/openmw_settings_window.layout +++ b/files/mygui/openmw_settings_window.layout @@ -73,7 +73,32 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -83,7 +108,7 @@ - + @@ -93,7 +118,7 @@ - + @@ -103,7 +128,7 @@ - + diff --git a/files/settings-default.cfg b/files/settings-default.cfg index ea6f6e7b6..66c6c6577 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -200,6 +200,9 @@ best attack = false # Difficulty. Expressed as damage dealt and received. (e.g. -100 to 100). difficulty = 0 +# The maximum range of actor AI, animations and physics updates. +actors processing range = 7168 + # Make reflected Absorb spells have no practical effect, like in Morrowind. classic reflected absorb spells behavior = true