keyWord);
void addResponse (const std::string& title, const std::string& text, bool needMargin = true);
diff --git a/apps/openmw/mwgui/formatting.cpp b/apps/openmw/mwgui/formatting.cpp
index 75bf8a342..156dc5b0d 100644
--- a/apps/openmw/mwgui/formatting.cpp
+++ b/apps/openmw/mwgui/formatting.cpp
@@ -30,14 +30,18 @@ namespace MWGui
// vanilla game does not show any text after the last EOL tag.
const std::string lowerText = Misc::StringUtils::lowerCase(mText);
- int brIndex = lowerText.rfind("
");
- int pIndex = lowerText.rfind("");
- if (brIndex == pIndex)
- mText = "";
- else if (brIndex > pIndex)
- mText = mText.substr(0, brIndex+4);
- else
- mText = mText.substr(0, pIndex+3);
+ size_t brIndex = lowerText.rfind("
");
+ size_t pIndex = lowerText.rfind("
");
+ mPlainTextEnd = 0;
+ if (brIndex != pIndex)
+ {
+ if (brIndex != std::string::npos && pIndex != std::string::npos)
+ mPlainTextEnd = std::max(brIndex, pIndex);
+ else if (brIndex != std::string::npos)
+ mPlainTextEnd = brIndex;
+ else
+ mPlainTextEnd = pIndex;
+ }
registerTag("br", Event_BrTag);
registerTag("p", Event_PTag);
@@ -103,7 +107,8 @@ namespace MWGui
{
if (!mIgnoreLineEndings || ch != '\n')
{
- mBuffer.push_back(ch);
+ if (mIndex < mPlainTextEnd)
+ mBuffer.push_back(ch);
mIgnoreLineEndings = false;
mIgnoreNewlineTags = false;
}
diff --git a/apps/openmw/mwgui/formatting.hpp b/apps/openmw/mwgui/formatting.hpp
index d56351414..12a3d46ca 100644
--- a/apps/openmw/mwgui/formatting.hpp
+++ b/apps/openmw/mwgui/formatting.hpp
@@ -73,6 +73,8 @@ namespace MWGui
bool mClosingTag;
std::map mTagTypes;
std::string mBuffer;
+
+ size_t mPlainTextEnd;
};
class Paginator
diff --git a/apps/openmw/mwgui/keyboardnavigation.cpp b/apps/openmw/mwgui/keyboardnavigation.cpp
index 50ab74fb5..f7d54adf6 100644
--- a/apps/openmw/mwgui/keyboardnavigation.cpp
+++ b/apps/openmw/mwgui/keyboardnavigation.cpp
@@ -78,7 +78,7 @@ void KeyboardNavigation::saveFocus(int mode)
{
mKeyFocus[mode] = focus;
}
- else
+ else if(shouldAcceptKeyFocus(mCurrentFocus))
{
mKeyFocus[mode] = mCurrentFocus;
}
@@ -104,6 +104,7 @@ void KeyboardNavigation::_unlinkWidget(MyGUI::Widget *widget)
mCurrentFocus = nullptr;
}
+#if MYGUI_VERSION < MYGUI_DEFINE_VERSION(3,2,3)
void styleFocusedButton(MyGUI::Widget* w)
{
if (w)
@@ -114,6 +115,7 @@ void styleFocusedButton(MyGUI::Widget* w)
}
}
}
+#endif
bool isRootParent(MyGUI::Widget* widget, MyGUI::Widget* root)
{
@@ -145,7 +147,9 @@ void KeyboardNavigation::onFrame()
if (focus == mCurrentFocus)
{
+#if MYGUI_VERSION < MYGUI_DEFINE_VERSION(3,2,3)
styleFocusedButton(mCurrentFocus);
+#endif
return;
}
@@ -156,19 +160,21 @@ void KeyboardNavigation::onFrame()
focus = mCurrentFocus;
}
- // style highlighted button (won't be needed for MyGUI 3.2.3)
if (focus != mCurrentFocus)
{
+#if MYGUI_VERSION < MYGUI_DEFINE_VERSION(3,2,3)
if (mCurrentFocus)
{
if (MyGUI::Button* b = mCurrentFocus->castType(false))
b->_setWidgetState("normal");
}
-
+#endif
mCurrentFocus = focus;
}
+#if MYGUI_VERSION < MYGUI_DEFINE_VERSION(3,2,3)
styleFocusedButton(mCurrentFocus);
+#endif
}
void KeyboardNavigation::setDefaultFocus(MyGUI::Widget *window, MyGUI::Widget *defaultFocus)
diff --git a/apps/openmw/mwgui/loadingscreen.cpp b/apps/openmw/mwgui/loadingscreen.cpp
index cd0384bb0..7ddc8c550 100644
--- a/apps/openmw/mwgui/loadingscreen.cpp
+++ b/apps/openmw/mwgui/loadingscreen.cpp
@@ -1,10 +1,12 @@
#include "loadingscreen.hpp"
#include
+#include
#include
#include
+#include
#include
#include
@@ -43,6 +45,8 @@ namespace MWGui
, mNestedLoadingCount(0)
, mProgress(0)
, mShowWallpaper(true)
+ , mOldCallback(nullptr)
+ , mHasCallback(false)
{
mMainWidget->setSize(MyGUI::RenderManager::getInstance().getViewSize());
@@ -136,24 +140,54 @@ namespace MWGui
{
public:
CopyFramebufferToTextureCallback(osg::Texture2D* texture)
- : mTexture(texture)
- , oneshot(true)
+ : mOneshot(true)
+ , mTexture(texture)
{
}
void operator () (osg::RenderInfo& renderInfo) const override
{
- if (!oneshot)
- return;
- oneshot = false;
+ {
+ std::unique_lock lock(mMutex);
+ mOneshot = false;
+ }
+ mSignal.notify_all();
+
int w = renderInfo.getCurrentCamera()->getViewport()->width();
int h = renderInfo.getCurrentCamera()->getViewport()->height();
mTexture->copyTexImage2D(*renderInfo.getState(), 0, 0, w, h);
+
+ {
+ std::unique_lock lock(mMutex);
+ mOneshot = false;
+ }
+ mSignal.notify_all();
+ }
+
+ void wait()
+ {
+ std::unique_lock lock(mMutex);
+ while (mOneshot)
+ mSignal.wait(lock);
+ }
+
+ void waitUntilInvoked()
+ {
+ std::unique_lock lock(mMutex);
+ while (mOneshot)
+ mSignal.wait(lock);
+ }
+
+ void reset()
+ {
+ mOneshot = true;
}
private:
+ mutable bool mOneshot;
+ mutable std::mutex mMutex;
+ mutable std::condition_variable mSignal;
osg::ref_ptr mTexture;
- mutable bool oneshot;
};
class DontComputeBoundCallback : public osg::Node::ComputeBoundingSphereCallback
@@ -322,9 +356,20 @@ namespace MWGui
mGuiTexture.reset(new osgMyGUI::OSGTexture(mTexture));
}
- // Notice that the next time this is called, the current CopyFramebufferToTextureCallback will be deleted
- // so there's no memory leak as at most one object of type CopyFramebufferToTextureCallback is allocated at a time.
- mViewer->getCamera()->setInitialDrawCallback(new CopyFramebufferToTextureCallback(mTexture));
+ if (!mCopyFramebufferToTextureCallback)
+ {
+ mCopyFramebufferToTextureCallback = new CopyFramebufferToTextureCallback(mTexture);
+ }
+
+#if OSG_VERSION_GREATER_OR_EQUAL(3, 5, 10)
+ mViewer->getCamera()->addInitialDrawCallback(mCopyFramebufferToTextureCallback);
+#else
+ // TODO: Remove once we officially end support for OSG versions pre 3.5.10
+ mOldCallback = mViewer->getCamera()->getInitialDrawCallback();
+ mViewer->getCamera()->setInitialDrawCallback(mCopyFramebufferToTextureCallback);
+#endif
+ mCopyFramebufferToTextureCallback->reset();
+ mHasCallback = true;
mBackgroundImage->setBackgroundImage("");
mBackgroundImage->setVisible(false);
@@ -367,6 +412,21 @@ namespace MWGui
mViewer->renderingTraversals();
mViewer->advance(mViewer->getFrameStamp()->getSimulationTime());
+ if (mHasCallback)
+ {
+ mCopyFramebufferToTextureCallback->waitUntilInvoked();
+
+ // Note that we are removing the callback before the draw thread has returned from it.
+ // This is OK as we are retaining the ref_ptr.
+#if OSG_VERSION_GREATER_OR_EQUAL(3, 5, 10)
+ mViewer->getCamera()->removeInitialDrawCallback(mCopyFramebufferToTextureCallback);
+#else
+ // TODO: Remove once we officially end support for OSG versions pre 3.5.10
+ mViewer->getCamera()->setInitialDrawCallback(mOldCallback);
+#endif
+ mHasCallback = false;
+ }
+
mLastRenderTime = mTimer.time_m();
}
diff --git a/apps/openmw/mwgui/loadingscreen.hpp b/apps/openmw/mwgui/loadingscreen.hpp
index 2577827aa..5d86ed389 100644
--- a/apps/openmw/mwgui/loadingscreen.hpp
+++ b/apps/openmw/mwgui/loadingscreen.hpp
@@ -3,6 +3,7 @@
#include
+#include
#include
#include
@@ -28,6 +29,7 @@ namespace Resource
namespace MWGui
{
class BackgroundImage;
+ class CopyFramebufferToTextureCallback;
class LoadingScreen : public WindowBase, public Loading::Listener
{
@@ -84,6 +86,9 @@ namespace MWGui
std::vector mSplashScreens;
osg::ref_ptr mTexture;
+ osg::ref_ptr mCopyFramebufferToTextureCallback;
+ osg::ref_ptr mOldCallback;
+ bool mHasCallback;
std::unique_ptr mGuiTexture;
void changeWallpaper();
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/mwgui/statswindow.cpp b/apps/openmw/mwgui/statswindow.cpp
index 2a3e2cd85..66a1ea1ef 100644
--- a/apps/openmw/mwgui/statswindow.cpp
+++ b/apps/openmw/mwgui/statswindow.cpp
@@ -5,6 +5,7 @@
#include
#include
#include
+#include
#include
#include
@@ -335,6 +336,17 @@ namespace MWGui
{
int max = MWBase::Environment::get().getWorld()->getStore().get().find("iLevelUpTotal")->mValue.getInteger();
getWidget(levelWidget, i==0 ? "Level_str" : "LevelText");
+
+ std::stringstream detail;
+ for (int i = 0; i < ESM::Attribute::Length; ++i)
+ {
+ if (auto increase = PCstats.getLevelUpAttributeIncrease(i))
+ detail << (detail.str().empty() ? "" : "\n") << "#{"
+ << MyGUI::TextIterator::toTagsString(ESM::Attribute::sGmstAttributeIds[i])
+ << "} x" << MyGUI::utility::toString(increase);
+ }
+ if (!detail.str().empty())
+ levelWidget->setUserString("Caption_LevelDetailText", MyGUI::LanguageManager::getInstance().replaceTags(detail.str()));
levelWidget->setUserString("RangePosition_LevelProgress", MyGUI::utility::toString(PCstats.getLevelProgress()));
levelWidget->setUserString("Range_LevelProgress", MyGUI::utility::toString(max));
levelWidget->setUserString("Caption_LevelProgressText", MyGUI::utility::toString(PCstats.getLevelProgress()) + "/"
diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp
index 3de7947c2..c18755630 100644
--- a/apps/openmw/mwmechanics/actors.cpp
+++ b/apps/openmw/mwmechanics/actors.cpp
@@ -161,6 +161,29 @@ void getRestorationPerHourOfSleep (const MWWorld::Ptr& ptr, float& health, float
magicka = fRestMagicMult * stats.getAttribute(ESM::Attribute::Intelligence).getModified();
}
+template
+void forEachFollowingPackage(MWMechanics::Actors::PtrActorMap& actors, const MWWorld::Ptr& actor, const MWWorld::Ptr& player, T&& func)
+{
+ for(auto& iter : actors)
+ {
+ const MWWorld::Ptr &iteratedActor = iter.first;
+ if (iteratedActor == player || iteratedActor == actor)
+ continue;
+
+ const MWMechanics::CreatureStats &stats = iteratedActor.getClass().getCreatureStats(iteratedActor);
+ if (stats.isDead())
+ continue;
+
+ // An actor counts as following if AiFollow is the current AiPackage,
+ // or there are only Combat and Wander packages before the AiFollow package
+ for (const auto& package : stats.getAiSequence())
+ {
+ if(!func(iter, package))
+ break;
+ }
+ }
+}
+
}
namespace MWMechanics
@@ -1482,6 +1505,13 @@ namespace MWMechanics
if (heldIter != inventoryStore.end() && heldIter->getTypeName() != typeid(ESM::Light).name())
inventoryStore.unequipItem(*heldIter, ptr);
}
+ else if (heldIter == inventoryStore.end() || heldIter->getTypeName() == typeid(ESM::Light).name())
+ {
+ // For hostile NPCs, see if they have anything better to equip first
+ auto shield = inventoryStore.getPreferredShield(ptr);
+ if(shield != inventoryStore.end())
+ inventoryStore.equip(MWWorld::InventoryStore::Slot_CarriedLeft, shield, ptr);
+ }
heldIter = inventoryStore.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft);
@@ -2788,26 +2818,14 @@ namespace MWMechanics
std::list Actors::getActorsFollowing(const MWWorld::Ptr& actor)
{
std::list list;
- for(PtrActorMap::iterator iter(mActors.begin());iter != mActors.end();++iter)
+ forEachFollowingPackage(mActors, actor, getPlayer(), [&] (auto& iter, const std::unique_ptr& package)
{
- const MWWorld::Ptr &iteratedActor = iter->first;
- if (iteratedActor == getPlayer() || iteratedActor == actor)
- continue;
-
- const CreatureStats &stats = iteratedActor.getClass().getCreatureStats(iteratedActor);
- if (stats.isDead())
- continue;
-
- // An actor counts as following if AiFollow is the current AiPackage,
- // or there are only Combat and Wander packages before the AiFollow package
- for (const auto& package : stats.getAiSequence())
- {
- if (package->followTargetThroughDoors() && package->getTarget() == actor)
- list.push_back(iteratedActor);
- else if (package->getTypeId() != AiPackageTypeId::Combat && package->getTypeId() != AiPackageTypeId::Wander)
- break;
- }
- }
+ if (package->followTargetThroughDoors() && package->getTarget() == actor)
+ list.push_back(iter.first);
+ else if (package->getTypeId() != AiPackageTypeId::Combat && package->getTypeId() != AiPackageTypeId::Wander)
+ return false;
+ return true;
+ });
return list;
}
@@ -2851,32 +2869,38 @@ namespace MWMechanics
std::list Actors::getActorsFollowingIndices(const MWWorld::Ptr &actor)
{
std::list list;
- for(PtrActorMap::iterator iter(mActors.begin());iter != mActors.end();++iter)
+ forEachFollowingPackage(mActors, actor, getPlayer(), [&] (auto& iter, const std::unique_ptr& package)
{
- const MWWorld::Ptr &iteratedActor = iter->first;
- if (iteratedActor == getPlayer() || iteratedActor == actor)
- continue;
-
- const CreatureStats &stats = iteratedActor.getClass().getCreatureStats(iteratedActor);
- if (stats.isDead())
- continue;
-
- // An actor counts as following if AiFollow is the current AiPackage,
- // or there are only Combat and Wander packages before the AiFollow package
- for (const auto& package : stats.getAiSequence())
+ if (package->followTargetThroughDoors() && package->getTarget() == actor)
{
- if (package->followTargetThroughDoors() && package->getTarget() == actor)
- {
- list.push_back(static_cast(package.get())->getFollowIndex());
- break;
- }
- else if (package->getTypeId() != AiPackageTypeId::Combat && package->getTypeId() != AiPackageTypeId::Wander)
- break;
+ list.push_back(static_cast(package.get())->getFollowIndex());
+ return false;
}
- }
+ else if (package->getTypeId() != AiPackageTypeId::Combat && package->getTypeId() != AiPackageTypeId::Wander)
+ return false;
+ return true;
+ });
return list;
}
+ std::map Actors::getActorsFollowingByIndex(const MWWorld::Ptr &actor)
+ {
+ std::map map;
+ forEachFollowingPackage(mActors, actor, getPlayer(), [&] (auto& iter, const std::unique_ptr& package)
+ {
+ if (package->followTargetThroughDoors() && package->getTarget() == actor)
+ {
+ int index = static_cast(package.get())->getFollowIndex();
+ map[index] = iter.first;
+ return false;
+ }
+ else if (package->getTypeId() != AiPackageTypeId::Combat && package->getTypeId() != AiPackageTypeId::Wander)
+ return false;
+ return true;
+ });
+ return map;
+ }
+
std::list Actors::getActorsFighting(const MWWorld::Ptr& actor) {
std::list list;
std::vector neighbors;
diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp
index 8543537fe..59890aa16 100644
--- a/apps/openmw/mwmechanics/actors.hpp
+++ b/apps/openmw/mwmechanics/actors.hpp
@@ -191,6 +191,7 @@ namespace MWMechanics
/// Get the list of AiFollow::mFollowIndex for all actors following this target
std::list getActorsFollowingIndices(const MWWorld::Ptr& actor);
+ std::map getActorsFollowingByIndex(const MWWorld::Ptr& actor);
///Returns the list of actors which are fighting the given actor
/**ie AiCombat is active and the target is the actor **/
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/mwmechanics/aifollow.cpp b/apps/openmw/mwmechanics/aifollow.cpp
index 8a1fc54f7..59d5db0a7 100644
--- a/apps/openmw/mwmechanics/aifollow.cpp
+++ b/apps/openmw/mwmechanics/aifollow.cpp
@@ -124,24 +124,22 @@ bool AiFollow::execute (const MWWorld::Ptr& actor, CharacterController& characte
if (!mActive)
return false;
- // The distances below are approximations based on observations of the original engine.
- // If only one actor is following the target, it uses 186.
- // If there are multiple actors following the same target, they form a group with each group member at 313 + (130 * i) distance to the target.
-
- short followDistance = 186;
- std::list followers = MWBase::Environment::get().getMechanicsManager()->getActorsFollowingIndices(target);
- if (followers.size() >= 2)
+ // In the original engine the first follower stays closer to the player than any subsequent followers.
+ // Followers beyond the first usually attempt to stand inside each other.
+ osg::Vec3f::value_type floatingDistance = 0;
+ auto followers = MWBase::Environment::get().getMechanicsManager()->getActorsFollowingByIndex(target);
+ if (followers.size() >= 2 && followers.cbegin()->first != mFollowIndex)
{
- followDistance = 313;
- short i = 0;
- followers.sort();
- for (int followIndex : followers)
+ for(auto& follower : followers)
{
- if (followIndex == mFollowIndex)
- followDistance += 130 * i;
- ++i;
+ auto halfExtent = MWBase::Environment::get().getWorld()->getHalfExtents(follower.second).y();
+ if(halfExtent > floatingDistance)
+ floatingDistance = halfExtent;
}
}
+ floatingDistance += MWBase::Environment::get().getWorld()->getHalfExtents(target).y();
+ floatingDistance += MWBase::Environment::get().getWorld()->getHalfExtents(actor).y() * 2;
+ short followDistance = static_cast(floatingDistance);
if (!mAlwaysFollow) //Update if you only follow for a bit
{
diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp
index 4bffd28ba..8880820dd 100644
--- a/apps/openmw/mwmechanics/aipackage.cpp
+++ b/apps/openmw/mwmechanics/aipackage.cpp
@@ -158,7 +158,7 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f&
zTurn(actor, getZAngleToPoint(position, dest));
smoothTurn(actor, getXAngleToPoint(position, dest), 0);
world->removeActorPath(actor);
- return true;
+ return isDestReached;
}
world->updateActorPath(actor, mPathFinder.getPath(), halfExtents, position, dest);
diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp
index 57d32898c..575a03434 100644
--- a/apps/openmw/mwmechanics/aisequence.cpp
+++ b/apps/openmw/mwmechanics/aisequence.cpp
@@ -403,7 +403,7 @@ const AiPackage& MWMechanics::AiSequence::getActivePackage()
void AiSequence::fill(const ESM::AIPackageList &list)
{
// If there is more than one package in the list, enable repeating
- if (!list.mList.empty() && list.mList.begin() != (list.mList.end()-1))
+ if (list.mList.size() >= 2)
mRepeat = true;
for (const auto& esmPackage : list.mList)
@@ -459,8 +459,15 @@ void AiSequence::readState(const ESM::AiSequence::AiSequence &sequence)
int count = 0;
for (auto& container : sequence.mPackages)
{
- if (isActualAiPackage(static_cast(container.mType)))
- count++;
+ switch (container.mType)
+ {
+ case ESM::AiSequence::Ai_Wander:
+ case ESM::AiSequence::Ai_Travel:
+ case ESM::AiSequence::Ai_Escort:
+ case ESM::AiSequence::Ai_Follow:
+ case ESM::AiSequence::Ai_Activate:
+ ++count;
+ }
}
if (count > 1)
diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp
index 4514549ed..e6ca75455 100644
--- a/apps/openmw/mwmechanics/character.cpp
+++ b/apps/openmw/mwmechanics/character.cpp
@@ -2156,7 +2156,7 @@ void CharacterController::update(float duration, bool animationOnly)
movementSettings.mSpeedFactor *= 2.f;
static const bool smoothMovement = Settings::Manager::getBool("smooth movement", "Game");
- if (smoothMovement && !isFirstPersonPlayer)
+ if (smoothMovement)
{
static const float playerTurningCoef = 1.0 / std::max(0.01f, Settings::Manager::getFloat("smooth movement player turning delay", "Game"));
float angle = mPtr.getRefData().getPosition().rot[2];
@@ -2166,7 +2166,9 @@ void CharacterController::update(float duration, bool animationOnly)
float deltaLen = delta.length();
float maxDelta;
- if (std::abs(speedDelta) < deltaLen / 2)
+ if (isFirstPersonPlayer)
+ maxDelta = 1;
+ else if (std::abs(speedDelta) < deltaLen / 2)
// Turning is smooth for player and less smooth for NPCs (otherwise NPC can miss a path point).
maxDelta = duration * (isPlayer ? playerTurningCoef : 6.f);
else if (isPlayer && speedDelta < -deltaLen / 2)
@@ -2204,7 +2206,10 @@ void CharacterController::update(float duration, bool animationOnly)
bool canMove = cls.getMaxSpeed(mPtr) > 0;
static const bool turnToMovementDirection = Settings::Manager::getBool("turn to movement direction", "Game");
if (!turnToMovementDirection || isFirstPersonPlayer)
+ {
movementSettings.mIsStrafing = std::abs(vec.x()) > std::abs(vec.y()) * 2;
+ stats.setSideMovementAngle(0);
+ }
else if (canMove)
{
float targetMovementAngle = vec.y() >= 0 ? std::atan2(-vec.x(), vec.y()) : std::atan2(vec.x(), -vec.y());
@@ -2456,18 +2461,19 @@ void CharacterController::update(float duration, bool animationOnly)
sndMgr->playSound3D(mPtr, sound, 1.f, 1.f, MWSound::Type::Foot, MWSound::PlayMode::NoPlayerLocal);
}
- if (turnToMovementDirection)
+ if (turnToMovementDirection && !isFirstPersonPlayer &&
+ (movestate == CharState_SwimRunForward || movestate == CharState_SwimWalkForward ||
+ movestate == CharState_SwimRunBack || movestate == CharState_SwimWalkBack))
{
- float targetSwimmingPitch;
- if (inwater && vec.y() != 0 && !isFirstPersonPlayer && !movementSettings.mIsStrafing)
- targetSwimmingPitch = -mPtr.getRefData().getPosition().rot[0];
- else
- targetSwimmingPitch = 0;
- float maxSwimPitchDelta = 3.0f * duration;
float swimmingPitch = mAnimation->getBodyPitchRadians();
+ float targetSwimmingPitch = -mPtr.getRefData().getPosition().rot[0];
+ float maxSwimPitchDelta = 3.0f * duration;
swimmingPitch += osg::clampBetween(targetSwimmingPitch - swimmingPitch, -maxSwimPitchDelta, maxSwimPitchDelta);
mAnimation->setBodyPitchRadians(swimmingPitch);
}
+ else
+ mAnimation->setBodyPitchRadians(0);
+
static const bool swimUpwardCorrection = Settings::Manager::getBool("swim upward correction", "Game");
if (inwater && isPlayer && !isFirstPersonPlayer && swimUpwardCorrection)
{
@@ -2615,7 +2621,7 @@ void CharacterController::update(float duration, bool animationOnly)
moved.y() *= scale;
// Ensure we're moving in generally the right direction...
- if(speed > 0.f)
+ if (speed > 0.f && moved != osg::Vec3f())
{
float l = moved.length();
if (std::abs(movement.x() - moved.x()) > std::abs(moved.x()) / 2 ||
@@ -2631,8 +2637,14 @@ void CharacterController::update(float duration, bool animationOnly)
}
}
- if (mFloatToSurface && cls.isActor() && cls.getCreatureStats(mPtr).isDead() && cls.canSwim(mPtr))
- moved.z() = 1.0;
+ if (mFloatToSurface && cls.isActor() && cls.canSwim(mPtr))
+ {
+ if (cls.getCreatureStats(mPtr).isDead()
+ || (!godmode && cls.getCreatureStats(mPtr).isParalyzed()))
+ {
+ moved.z() = 1.0;
+ }
+ }
// Update movement
if(!animationOnly && mMovementAnimationControlled && mPtr.getClass().isActor())
diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp
index f42c3cf44..fe4a55216 100644
--- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp
+++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp
@@ -1794,6 +1794,11 @@ namespace MWMechanics
return mActors.getActorsFollowingIndices(actor);
}
+ std::map MechanicsManager::getActorsFollowingByIndex(const MWWorld::Ptr& actor)
+ {
+ return mActors.getActorsFollowingByIndex(actor);
+ }
+
std::list MechanicsManager::getActorsFighting(const MWWorld::Ptr& actor) {
return mActors.getActorsFighting(actor);
}
diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp
index c1a59fccd..1087fd6fc 100644
--- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp
+++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp
@@ -160,6 +160,7 @@ namespace MWMechanics
std::list getActorsSidingWith(const MWWorld::Ptr& actor) override;
std::list getActorsFollowing(const MWWorld::Ptr& actor) override;
std::list getActorsFollowingIndices(const MWWorld::Ptr& actor) override;
+ std::map getActorsFollowingByIndex(const MWWorld::Ptr& actor) override;
std::list getActorsFighting(const MWWorld::Ptr& actor) override;
std::list getEnemiesNearby(const MWWorld::Ptr& actor) override;
diff --git a/apps/openmw/mwmechanics/npcstats.cpp b/apps/openmw/mwmechanics/npcstats.cpp
index b3a234a1b..b96bea419 100644
--- a/apps/openmw/mwmechanics/npcstats.cpp
+++ b/apps/openmw/mwmechanics/npcstats.cpp
@@ -383,6 +383,11 @@ void MWMechanics::NpcStats::updateHealth()
setHealth(floor(0.5f * (strength + endurance)));
}
+int MWMechanics::NpcStats::getLevelUpAttributeIncrease(int attribute) const
+{
+ return mSkillIncreases[attribute];
+}
+
int MWMechanics::NpcStats::getLevelupAttributeMultiplier(int attribute) const
{
int num = mSkillIncreases[attribute];
diff --git a/apps/openmw/mwmechanics/npcstats.hpp b/apps/openmw/mwmechanics/npcstats.hpp
index b693cf271..86fc4ef0d 100644
--- a/apps/openmw/mwmechanics/npcstats.hpp
+++ b/apps/openmw/mwmechanics/npcstats.hpp
@@ -131,6 +131,8 @@ namespace MWMechanics
End of tes3mp addition
*/
+ int getLevelUpAttributeIncrease(int attribute) const;
+
int getLevelupAttributeMultiplier(int attribute) const;
int getSkillIncreasesForSpecialization(int spec) const;
diff --git a/apps/openmw/mwphysics/actor.cpp b/apps/openmw/mwphysics/actor.cpp
index 78b8abafb..b84068cf9 100644
--- a/apps/openmw/mwphysics/actor.cpp
+++ b/apps/openmw/mwphysics/actor.cpp
@@ -13,7 +13,6 @@
End of tes3mp addition
*/
-#include
#include
#include
@@ -27,13 +26,15 @@
#include "collisiontype.hpp"
#include "mtphysics.hpp"
+#include
+
namespace MWPhysics
{
Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, PhysicsTaskScheduler* scheduler)
: mStandingOnPtr(nullptr), mCanWaterWalk(false), mWalkingOnWater(false)
- , mCollisionObject(nullptr), mMeshTranslation(shape->mCollisionBoxTranslate), mHalfExtents(shape->mCollisionBoxHalfExtents)
+ , mCollisionObject(nullptr), mMeshTranslation(shape->mCollisionBox.center), mHalfExtents(shape->mCollisionBox.extents)
, mForce(0.f, 0.f, 0.f), mOnGround(true), mOnSlope(false)
, mInternalCollisionMode(true)
, mExternalCollisionMode(true)
@@ -65,17 +66,8 @@ Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, Physic
Log(Debug::Error) << "Error: Failed to calculate bounding box for actor \"" << ptr.getCellRef().getRefId() << "\".";
}
- // Use capsule shape only if base is square (nonuniform scaling apparently doesn't work on it)
- if (std::abs(mHalfExtents.x()-mHalfExtents.y())= mHalfExtents.x())
- {
- mShape.reset(new btCapsuleShapeZ(mHalfExtents.x(), 2*mHalfExtents.z() - 2*mHalfExtents.x()));
- mRotationallyInvariant = true;
- }
- else
- {
- mShape.reset(new btBoxShape(Misc::Convert::toBullet(mHalfExtents)));
- mRotationallyInvariant = false;
- }
+ mShape.reset(new btBoxShape(Misc::Convert::toBullet(mHalfExtents)));
+ mRotationallyInvariant = (mMeshTranslation.x() == 0.0 && mMeshTranslation.y() == 0.0) && std::fabs(mHalfExtents.x() - mHalfExtents.y()) < 2.2;
mConvexShape = static_cast(mShape.get());
@@ -83,11 +75,14 @@ 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();
- resetPosition();
+
+ if(!mRotationallyInvariant)
+ updateRotation();
+
+ updatePosition();
addCollisionMask(getCollisionMask());
/*
@@ -110,6 +105,8 @@ Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, Physic
/*
End of tes3mp addition
*/
+
+ updateCollisionObjectPosition();
}
Actor::~Actor()
@@ -152,26 +149,34 @@ int Actor::getCollisionMask() const
return collisionMask;
}
-void Actor::updatePositionUnsafe()
-{
- mWorldPosition = mPtr.getRefData().getPosition().asVec3();
-}
-
void Actor::updatePosition()
{
std::scoped_lock lock(mPositionMutex);
- updatePositionUnsafe();
+ updateWorldPosition();
+ mPreviousPosition = mWorldPosition;
+ mPosition = mWorldPosition;
+ mSimulationPosition = mWorldPosition;
+ mStandingOnPtr = nullptr;
+ mSkipSimulation = true;
+}
+
+void Actor::updateWorldPosition()
+{
+ if (mWorldPosition != mPtr.getRefData().getPosition().asVec3())
+ mWorldPositionChanged = true;
+ mWorldPosition = mPtr.getRefData().getPosition().asVec3();
}
osg::Vec3f Actor::getWorldPosition() const
{
- std::scoped_lock lock(mPositionMutex);
return mWorldPosition;
}
void Actor::setSimulationPosition(const osg::Vec3f& position)
{
- mSimulationPosition = position;
+ if (!mSkipSimulation)
+ mSimulationPosition = position;
+ mSkipSimulation = false;
}
osg::Vec3f Actor::getSimulationPosition() const
@@ -179,20 +184,21 @@ osg::Vec3f Actor::getSimulationPosition() const
return mSimulationPosition;
}
-void Actor::updateCollisionObjectPositionUnsafe()
+osg::Vec3f Actor::getScaledMeshTranslation() const
{
+ return mRotation * osg::componentMultiply(mMeshTranslation, mScale);
+}
+
+void Actor::updateCollisionObjectPosition()
+{
+ std::scoped_lock lock(mPositionMutex);
mShape->setLocalScaling(Misc::Convert::toBullet(mScale));
osg::Vec3f scaledTranslation = mRotation * osg::componentMultiply(mMeshTranslation, mScale);
osg::Vec3f newPosition = scaledTranslation + mPosition;
mLocalTransform.setOrigin(Misc::Convert::toBullet(newPosition));
mLocalTransform.setRotation(Misc::Convert::toBullet(mRotation));
mCollisionObject->setWorldTransform(mLocalTransform);
-}
-
-void Actor::updateCollisionObjectPosition()
-{
- std::scoped_lock lock(mPositionMutex);
- updateCollisionObjectPositionUnsafe();
+ mWorldPositionChanged = false;
}
osg::Vec3f Actor::getCollisionObjectPosition() const
@@ -201,28 +207,20 @@ osg::Vec3f Actor::getCollisionObjectPosition() const
return Misc::Convert::toOsg(mLocalTransform.getOrigin());
}
-void Actor::setPosition(const osg::Vec3f& position)
+bool Actor::setPosition(const osg::Vec3f& position)
{
std::scoped_lock lock(mPositionMutex);
- mPreviousPosition = mPosition;
- mPosition = position;
+ bool hasChanged = mPosition != position || mPositionOffset.length() != 0 || mWorldPositionChanged;
+ mPreviousPosition = mPosition + mPositionOffset;
+ mPosition = position + mPositionOffset;
+ mPositionOffset = osg::Vec3f();
+ return hasChanged;
}
void Actor::adjustPosition(const osg::Vec3f& offset)
{
std::scoped_lock lock(mPositionMutex);
- mPosition += offset;
- mPreviousPosition += offset;
-}
-
-void Actor::resetPosition()
-{
- std::scoped_lock lock(mPositionMutex);
- updatePositionUnsafe();
- mPreviousPosition = mWorldPosition;
- mPosition = mWorldPosition;
- mSimulationPosition = mWorldPosition;
- updateCollisionObjectPositionUnsafe();
+ mPositionOffset += offset;
}
osg::Vec3f Actor::getPosition() const
@@ -238,8 +236,6 @@ osg::Vec3f Actor::getPreviousPosition() const
void Actor::updateRotation ()
{
std::scoped_lock lock(mPositionMutex);
- if (mRotation == mPtr.getRefData().getBaseNode()->getAttitude())
- return;
mRotation = mPtr.getRefData().getBaseNode()->getAttitude();
}
@@ -270,7 +266,6 @@ osg::Vec3f Actor::getHalfExtents() const
osg::Vec3f Actor::getOriginalHalfExtents() const
{
- std::scoped_lock lock(mPositionMutex);
return mHalfExtents;
}
@@ -307,7 +302,6 @@ void Actor::setWalkingOnWater(bool walkingOnWater)
void Actor::setCanWaterWalk(bool waterWalk)
{
- std::scoped_lock lock(mPositionMutex);
if (waterWalk != mCanWaterWalk)
{
mCanWaterWalk = waterWalk;
diff --git a/apps/openmw/mwphysics/actor.hpp b/apps/openmw/mwphysics/actor.hpp
index 07a9bebd2..9d129f2ba 100644
--- a/apps/openmw/mwphysics/actor.hpp
+++ b/apps/openmw/mwphysics/actor.hpp
@@ -60,7 +60,7 @@ namespace MWPhysics
* Set mWorldPosition to the position in the Ptr's RefData. This is used by the physics simulation to account for
* when an object is "instantly" moved/teleported as opposed to being moved by the physics simulation.
*/
- void updatePosition();
+ void updateWorldPosition();
osg::Vec3f getWorldPosition() const;
/**
@@ -82,6 +82,9 @@ namespace MWPhysics
*/
osg::Vec3f getOriginalHalfExtents() const;
+ /// Returns the mesh translation, scaled and rotated as necessary
+ osg::Vec3f getScaledMeshTranslation() const;
+
/**
* Returns the position of the collision body
* @note The collision shape's origin is in its center, so the position returned can be described as center of the actor collision box in world space.
@@ -90,9 +93,10 @@ namespace MWPhysics
/**
* Store the current position into mPreviousPosition, then move to this position.
+ * Returns true if the new position is different.
*/
- void setPosition(const osg::Vec3f& position);
- void resetPosition();
+ bool setPosition(const osg::Vec3f& position);
+ void updatePosition();
void adjustPosition(const osg::Vec3f& offset);
osg::Vec3f getPosition() const;
@@ -154,8 +158,6 @@ namespace MWPhysics
void updateCollisionMask();
void addCollisionMask(int collisionMask);
int getCollisionMask() const;
- void updateCollisionObjectPositionUnsafe();
- void updatePositionUnsafe();
bool mCanWaterWalk;
std::atomic mWalkingOnWater;
@@ -177,6 +179,9 @@ namespace MWPhysics
osg::Vec3f mSimulationPosition;
osg::Vec3f mPosition;
osg::Vec3f mPreviousPosition;
+ osg::Vec3f mPositionOffset;
+ bool mWorldPositionChanged;
+ bool mSkipSimulation;
btTransform mLocalTransform;
mutable std::mutex mPositionMutex;
diff --git a/apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp b/apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp
index ddfdb8a42..27e7a390c 100644
--- a/apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp
+++ b/apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp
@@ -1,20 +1,92 @@
+#include
+
#include "closestnotmeconvexresultcallback.hpp"
+#include "collisiontype.hpp"
+#include "contacttestwrapper.h"
#include
+#include
+
+#include "collisiontype.hpp"
+#include "projectile.hpp"
namespace MWPhysics
{
- ClosestNotMeConvexResultCallback::ClosestNotMeConvexResultCallback(const btCollisionObject *me, const btVector3 &motion, btScalar minCollisionDot)
+ class ActorOverlapTester : public btCollisionWorld::ContactResultCallback
+ {
+ public:
+ bool overlapping = false;
+
+ btScalar addSingleResult(btManifoldPoint& cp,
+ const btCollisionObjectWrapper* colObj0Wrap,
+ int partId0,
+ int index0,
+ const btCollisionObjectWrapper* colObj1Wrap,
+ int partId1,
+ int index1) override
+ {
+ if(cp.getDistance() <= 0.0f)
+ overlapping = true;
+ return btScalar(1);
+ }
+ };
+
+ ClosestNotMeConvexResultCallback::ClosestNotMeConvexResultCallback(const btCollisionObject *me, const btVector3 &motion, btScalar minCollisionDot, const btCollisionWorld * world)
: btCollisionWorld::ClosestConvexResultCallback(btVector3(0.0, 0.0, 0.0), btVector3(0.0, 0.0, 0.0)),
- mMe(me), mMotion(motion), mMinCollisionDot(minCollisionDot)
+ mMe(me), mMotion(motion), mMinCollisionDot(minCollisionDot), mWorld(world)
{
}
- btScalar ClosestNotMeConvexResultCallback::addSingleResult(btCollisionWorld::LocalConvexResult& convexResult,bool normalInWorldSpace)
+ btScalar ClosestNotMeConvexResultCallback::addSingleResult(btCollisionWorld::LocalConvexResult& convexResult, bool normalInWorldSpace)
{
if (convexResult.m_hitCollisionObject == mMe)
return btScalar(1);
+ // override data for actor-actor collisions
+ // vanilla Morrowind seems to make overlapping actors collide as though they are both cylinders with a diameter of the distance between them
+ // For some reason this doesn't work as well as it should when using capsules, but it still helps a lot.
+ if(convexResult.m_hitCollisionObject->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Actor)
+ {
+ ActorOverlapTester isOverlapping;
+ // FIXME: This is absolutely terrible and bullet should feel terrible for not making contactPairTest const-correct.
+ ContactTestWrapper::contactPairTest(const_cast(mWorld), const_cast(mMe), const_cast(convexResult.m_hitCollisionObject), isOverlapping);
+
+ if(isOverlapping.overlapping)
+ {
+ auto originA = Misc::Convert::toOsg(mMe->getWorldTransform().getOrigin());
+ auto originB = Misc::Convert::toOsg(convexResult.m_hitCollisionObject->getWorldTransform().getOrigin());
+ osg::Vec3f motion = Misc::Convert::toOsg(mMotion);
+ osg::Vec3f normal = (originA-originB);
+ normal.z() = 0;
+ normal.normalize();
+ // only collide if horizontally moving towards the hit actor (note: the motion vector appears to be inverted)
+ // FIXME: This kinda screws with standing on actors that walk up slopes for some reason. Makes you fall through them.
+ // It happens in vanilla Morrowind too, but much less often.
+ // I tried hunting down why but couldn't figure it out. Possibly a stair stepping or ground ejection bug.
+ if(normal * motion > 0.0f)
+ {
+ convexResult.m_hitFraction = 0.0f;
+ convexResult.m_hitNormalLocal = Misc::Convert::toBullet(normal);
+ return ClosestConvexResultCallback::addSingleResult(convexResult, true);
+ }
+ else
+ {
+ 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();
+ if (projectileHolder->isValidTarget(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/closestnotmeconvexresultcallback.hpp b/apps/openmw/mwphysics/closestnotmeconvexresultcallback.hpp
index 97aaa64a1..538721ad8 100644
--- a/apps/openmw/mwphysics/closestnotmeconvexresultcallback.hpp
+++ b/apps/openmw/mwphysics/closestnotmeconvexresultcallback.hpp
@@ -10,7 +10,7 @@ namespace MWPhysics
class ClosestNotMeConvexResultCallback : public btCollisionWorld::ClosestConvexResultCallback
{
public:
- ClosestNotMeConvexResultCallback(const btCollisionObject *me, const btVector3 &motion, btScalar minCollisionDot);
+ ClosestNotMeConvexResultCallback(const btCollisionObject *me, const btVector3 &motion, btScalar minCollisionDot, const btCollisionWorld * world);
btScalar addSingleResult(btCollisionWorld::LocalConvexResult& convexResult,bool normalInWorldSpace) override;
@@ -18,6 +18,7 @@ namespace MWPhysics
const btCollisionObject *mMe;
const btVector3 mMotion;
const btScalar mMinCollisionDot;
+ const btCollisionWorld * mWorld;
};
}
diff --git a/apps/openmw/mwphysics/closestnotmerayresultcallback.cpp b/apps/openmw/mwphysics/closestnotmerayresultcallback.cpp
index 86763a793..c3104f860 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)
{
}
@@ -20,15 +24,41 @@ namespace MWPhysics
{
if (rayResult.m_collisionObject == mMe)
return 1.f;
+
+ if (mProjectile && rayResult.m_collisionObject == mProjectile->getCollisionObject())
+ return 1.f;
+
if (!mTargets.empty())
{
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)
+ {
+ switch (rayResult.m_collisionObject->getBroadphaseHandle()->m_collisionFilterGroup)
+ {
+ case CollisionType_Actor:
+ {
+ auto* target = static_cast(rayResult.m_collisionObject->getUserPointer());
+ mProjectile->hit(target->getPtr(), m_hitPointWorld, m_hitNormalWorld);
+ break;
+ }
+ case CollisionType_Projectile:
+ {
+ auto* target = static_cast(rayResult.m_collisionObject->getUserPointer());
+ target->hit(mProjectile->getPtr(), m_hitPointWorld, m_hitNormalWorld);
+ mProjectile->hit(target->getPtr(), m_hitPointWorld, m_hitNormalWorld);
+ break;
+ }
+ }
+ }
+
+ 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/constants.hpp b/apps/openmw/mwphysics/constants.hpp
index 46367ab34..c6f2f3b53 100644
--- a/apps/openmw/mwphysics/constants.hpp
+++ b/apps/openmw/mwphysics/constants.hpp
@@ -5,12 +5,22 @@ namespace MWPhysics
{
static const float sStepSizeUp = 34.0f;
static const float sStepSizeDown = 62.0f;
- static const float sMinStep = 10.f;
+
+ static const float sMinStep = 10.0f; // hack to skip over tiny unwalkable slopes
+ static const float sMinStep2 = 20.0f; // hack to skip over shorter but longer/wider/further unwalkable slopes
+ // whether to do the above stairstepping logic hacks to work around bad morrowind assets - disabling causes problems but improves performance
+ static const bool sDoExtraStairHacks = true;
+
static const float sGroundOffset = 1.0f;
static const float sMaxSlope = 49.0f;
// Arbitrary number. To prevent infinite loops. They shouldn't happen but it's good to be prepared.
static const int sMaxIterations = 8;
+ // Allows for more precise movement solving without getting stuck or snagging too easily.
+ static const float sCollisionMargin = 0.1;
+ // Allow for a small amount of penetration to prevent numerical precision issues from causing the "unstuck"ing code to run unnecessarily
+ // Currently set to 0 because having the "unstuck"ing code run whenever possible prevents some glitchy snagging issues
+ static const float sAllowedPenetration = 0.0;
}
#endif
diff --git a/apps/openmw/mwphysics/contacttestwrapper.cpp b/apps/openmw/mwphysics/contacttestwrapper.cpp
new file mode 100644
index 000000000..c11a7e292
--- /dev/null
+++ b/apps/openmw/mwphysics/contacttestwrapper.cpp
@@ -0,0 +1,21 @@
+#include
+
+#include "contacttestwrapper.h"
+
+namespace MWPhysics
+{
+ // Concurrent calls to contactPairTest (and by extension contactTest) are forbidden.
+ static std::mutex contactMutex;
+ void ContactTestWrapper::contactTest(btCollisionWorld* collisionWorld, btCollisionObject* colObj, btCollisionWorld::ContactResultCallback& resultCallback)
+ {
+ std::unique_lock lock(contactMutex);
+ collisionWorld->contactTest(colObj, resultCallback);
+ }
+
+ void ContactTestWrapper::contactPairTest(btCollisionWorld* collisionWorld, btCollisionObject* colObjA, btCollisionObject* colObjB, btCollisionWorld::ContactResultCallback& resultCallback)
+ {
+ std::unique_lock lock(contactMutex);
+ collisionWorld->contactPairTest(colObjA, colObjB, resultCallback);
+ }
+
+}
diff --git a/apps/openmw/mwphysics/contacttestwrapper.h b/apps/openmw/mwphysics/contacttestwrapper.h
new file mode 100644
index 000000000..b3b6edc59
--- /dev/null
+++ b/apps/openmw/mwphysics/contacttestwrapper.h
@@ -0,0 +1,14 @@
+#ifndef OPENMW_MWPHYSICS_CONTACTTESTWRAPPER_H
+#define OPENMW_MWPHYSICS_CONTACTTESTWRAPPER_H
+
+#include
+
+namespace MWPhysics
+{
+ struct ContactTestWrapper
+ {
+ static void contactTest(btCollisionWorld* collisionWorld, btCollisionObject* colObj, btCollisionWorld::ContactResultCallback& resultCallback);
+ static void contactPairTest(btCollisionWorld* collisionWorld, btCollisionObject* colObjA, btCollisionObject* colObjB, btCollisionWorld::ContactResultCallback& resultCallback);
+ };
+}
+#endif
diff --git a/apps/openmw/mwphysics/movementsolver.cpp b/apps/openmw/mwphysics/movementsolver.cpp
index 3c78104dc..9d2957409 100644
--- a/apps/openmw/mwphysics/movementsolver.cpp
+++ b/apps/openmw/mwphysics/movementsolver.cpp
@@ -17,10 +17,13 @@
#include "actor.hpp"
#include "collisiontype.hpp"
#include "constants.hpp"
+#include "contacttestwrapper.h"
#include "physicssystem.hpp"
#include "stepper.hpp"
#include "trace.h"
+#include
+
namespace MWPhysics
{
static bool isActor(const btCollisionObject *obj)
@@ -29,12 +32,50 @@ namespace MWPhysics
return obj->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Actor;
}
- template
- static bool isWalkableSlope(const Vec3 &normal)
+ class ContactCollectionCallback : public btCollisionWorld::ContactResultCallback
{
- static const float sMaxSlopeCos = std::cos(osg::DegreesToRadians(sMaxSlope));
- return (normal.z() > sMaxSlopeCos);
- }
+ public:
+ ContactCollectionCallback(const btCollisionObject * me, osg::Vec3f velocity) : mMe(me)
+ {
+ m_collisionFilterGroup = me->getBroadphaseHandle()->m_collisionFilterGroup;
+ m_collisionFilterMask = me->getBroadphaseHandle()->m_collisionFilterMask;
+ mVelocity = Misc::Convert::toBullet(velocity);
+ }
+ btScalar addSingleResult(btManifoldPoint & contact, const btCollisionObjectWrapper * colObj0Wrap, int partId0, int index0, const btCollisionObjectWrapper * colObj1Wrap, int partId1, int index1) override
+ {
+ if (isActor(colObj0Wrap->getCollisionObject()) && isActor(colObj1Wrap->getCollisionObject()))
+ return 0.0;
+ // ignore overlap if we're moving in the same direction as it would push us out (don't change this to >=, that would break detection when not moving)
+ if (contact.m_normalWorldOnB.dot(mVelocity) > 0.0)
+ return 0.0;
+ auto delta = contact.m_normalWorldOnB * -contact.m_distance1;
+ mContactSum += delta;
+ mMaxX = std::max(std::abs(delta.x()), mMaxX);
+ mMaxY = std::max(std::abs(delta.y()), mMaxY);
+ mMaxZ = std::max(std::abs(delta.z()), mMaxZ);
+ if (contact.m_distance1 < mDistance)
+ {
+ mDistance = contact.m_distance1;
+ mNormal = contact.m_normalWorldOnB;
+ mDelta = delta;
+ return mDistance;
+ }
+ else
+ {
+ return 0.0;
+ }
+ }
+ btScalar mMaxX = 0.0;
+ btScalar mMaxY = 0.0;
+ btScalar mMaxZ = 0.0;
+ btVector3 mContactSum{0.0, 0.0, 0.0};
+ btVector3 mNormal{0.0, 0.0, 0.0}; // points towards "me"
+ btVector3 mDelta{0.0, 0.0, 0.0}; // points towards "me"
+ btScalar mDistance = 0.0; // negative or zero
+ protected:
+ btVector3 mVelocity;
+ const btCollisionObject * mMe;
+ };
osg::Vec3f MovementSolver::traceDown(const MWWorld::Ptr &ptr, const osg::Vec3f& position, Actor* actor, btCollisionWorld* collisionWorld, float maxHeight)
{
@@ -78,12 +119,14 @@ namespace MWPhysics
WorldFrameData& worldData)
{
auto* physicActor = actor.mActorRaw;
- auto ptr = actor.mPtr;
const ESM::Position& refpos = actor.mRefpos;
// Early-out for totally static creatures
// (Not sure if gravity should still apply?)
- if (!ptr.getClass().isMobile(ptr))
- return;
+ {
+ const auto ptr = physicActor->getPtr();
+ if (!ptr.getClass().isMobile(ptr))
+ return;
+ }
// Reset per-frame data
physicActor->setWalkingOnWater(false);
@@ -97,13 +140,13 @@ namespace MWPhysics
}
const btCollisionObject *colobj = physicActor->getCollisionObject();
- osg::Vec3f halfExtents = physicActor->getHalfExtents();
- // NOTE: here we don't account for the collision box translation (i.e. physicActor->getPosition() - refpos.pos).
- // That means the collision shape used for moving this actor is in a different spot than the collision shape
- // other actors are using to collide against this actor.
- // While this is strictly speaking wrong, it's needed for MW compatibility.
- actor.mPosition.z() += halfExtents.z();
+ // Adjust for collision mesh offset relative to actor's "location"
+ // (doTrace doesn't take local/interior collision shape translation into account, so we have to do it on our own)
+ // for compatibility with vanilla assets, we have to derive this from the vertical half extent instead of from internal hull translation
+ // if not for this hack, the "correct" collision hull position would be physicActor->getScaledMeshTranslation()
+ osg::Vec3f halfExtents = physicActor->getHalfExtents();
+ actor.mPosition.z() += halfExtents.z(); // vanilla-accurate
static const float fSwimHeightScale = MWBase::Environment::get().getWorld()->getStore().get().find("fSwimHeightScale")->mValue.getFloat();
float swimlevel = actor.mWaterlevel + halfExtents.z() - (physicActor->getRenderingHalfExtents().z() * 2 * fSwimHeightScale);
@@ -128,8 +171,9 @@ namespace MWPhysics
velocity = velocity + inertia;
}
- // dead actors underwater will float to the surface, if the CharacterController tells us to do so
- if (actor.mMovement.z() > 0 && actor.mIsDead && actor.mPosition.z() < swimlevel)
+ // Dead and paralyzed actors underwater will float to the surface,
+ // if the CharacterController tells us to do so
+ if (actor.mMovement.z() > 0 && actor.mFloatToSurface && actor.mPosition.z() < swimlevel)
velocity = osg::Vec3f(0,0,1) * 25;
if (actor.mWantJump)
@@ -153,6 +197,13 @@ namespace MWPhysics
* The initial velocity was set earlier (see above).
*/
float remainingTime = time;
+ bool seenGround = physicActor->getOnGround() && !physicActor->getOnSlope() && !actor.mFlying;
+
+ int numTimesSlid = 0;
+ osg::Vec3f lastSlideNormal(0,0,1);
+ osg::Vec3f lastSlideNormalFallback(0,0,1);
+ bool forceGroundTest = false;
+
for (int iterations = 0; iterations < sMaxIterations && remainingTime > 0.01f; ++iterations)
{
osg::Vec3f nextpos = newPosition + velocity * remainingTime;
@@ -161,7 +212,7 @@ namespace MWPhysics
if(!actor.mFlying && nextpos.z() > swimlevel && newPosition.z() < swimlevel)
{
const osg::Vec3f down(0,0,-1);
- velocity = slide(velocity, down);
+ velocity = reject(velocity, down);
// NOTE: remainingTime is unchanged before the loop continues
continue; // velocity updated, calculate nextpos again
}
@@ -190,91 +241,158 @@ namespace MWPhysics
break;
}
- // We are touching something.
- if (tracer.mFraction < 1E-9f)
- {
- // Try to separate by backing off slighly to unstuck the solver
- osg::Vec3f backOff = (newPosition - tracer.mHitPoint) * 1E-2f;
- newPosition += backOff;
- }
+ if (isWalkableSlope(tracer.mPlaneNormal) && !actor.mFlying && newPosition.z() >= swimlevel)
+ seenGround = true;
// We hit something. Check if we can step up.
float hitHeight = tracer.mHitPoint.z() - tracer.mEndPos.z() + halfExtents.z();
osg::Vec3f oldPosition = newPosition;
- bool result = false;
+ bool usedStepLogic = false;
if (hitHeight < sStepSizeUp && !isActor(tracer.mHitObject))
{
// Try to step up onto it.
- // NOTE: stepMove does not allow stepping over, modifies newPosition if successful
- result = stepper.step(newPosition, velocity*remainingTime, remainingTime);
+ // NOTE: this modifies newPosition and velocity on its own if successful
+ usedStepLogic = stepper.step(newPosition, velocity, remainingTime, seenGround, iterations == 0);
}
- if (result)
+ if (usedStepLogic)
{
// don't let pure water creatures move out of water after stepMove
+ const auto ptr = physicActor->getPtr();
if (ptr.getClass().isPureWaterCreature(ptr) && newPosition.z() + halfExtents.z() > actor.mWaterlevel)
newPosition = oldPosition;
+ else if(!actor.mFlying && actor.mPosition.z() >= swimlevel)
+ forceGroundTest = true;
}
else
{
- // Can't move this way, try to find another spot along the plane
- osg::Vec3f newVelocity = slide(velocity, tracer.mPlaneNormal);
+ // Can't step up, so slide against what we ran into
+ remainingTime *= (1.0f-tracer.mFraction);
- // Do not allow sliding upward if there is gravity.
- // Stepping will have taken care of that.
- if(!(newPosition.z() < swimlevel || actor.mFlying))
- newVelocity.z() = std::min(newVelocity.z(), 0.0f);
+ auto planeNormal = tracer.mPlaneNormal;
- if ((newVelocity-velocity).length2() < 0.01)
+ // If we touched the ground this frame, and whatever we ran into is a wall of some sort,
+ // pretend that its collision normal is pointing horizontally
+ // (fixes snagging on slightly downward-facing walls, and crawling up the bases of very steep walls because of the collision margin)
+ if (seenGround && !isWalkableSlope(planeNormal) && planeNormal.z() != 0)
+ {
+ planeNormal.z() = 0;
+ planeNormal.normalize();
+ }
+
+ // Move up to what we ran into (with a bit of a collision margin)
+ if ((newPosition-tracer.mEndPos).length2() > sCollisionMargin*sCollisionMargin)
+ {
+ auto direction = velocity;
+ direction.normalize();
+ newPosition = tracer.mEndPos;
+ newPosition -= direction*sCollisionMargin;
+ }
+
+ osg::Vec3f newVelocity = (velocity * planeNormal <= 0.0) ? reject(velocity, planeNormal) : velocity;
+ bool usedSeamLogic = false;
+
+ // check for the current and previous collision planes forming an acute angle; slide along the seam if they do
+ if(numTimesSlid > 0)
+ {
+ auto dotA = lastSlideNormal * planeNormal;
+ auto dotB = lastSlideNormalFallback * planeNormal;
+ if(numTimesSlid <= 1) // ignore fallback normal if this is only the first or second slide
+ dotB = 1.0;
+ if(dotA <= 0.0 || dotB <= 0.0)
+ {
+ osg::Vec3f bestNormal = lastSlideNormal;
+ // use previous-to-previous collision plane if it's acute with current plane but actual previous plane isn't
+ if(dotB < dotA)
+ {
+ bestNormal = lastSlideNormalFallback;
+ lastSlideNormal = lastSlideNormalFallback;
+ }
+
+ auto constraintVector = bestNormal ^ planeNormal; // cross product
+ if(constraintVector.length2() > 0) // only if it's not zero length
+ {
+ constraintVector.normalize();
+ newVelocity = project(velocity, constraintVector);
+
+ // version of surface rejection for acute crevices/seams
+ auto averageNormal = bestNormal + planeNormal;
+ averageNormal.normalize();
+ tracer.doTrace(colobj, newPosition, newPosition + averageNormal*(sCollisionMargin*2.0), collisionWorld);
+ newPosition = (newPosition + tracer.mEndPos)/2.0;
+
+ usedSeamLogic = true;
+ }
+ }
+ }
+ // otherwise just keep the normal vector rejection
+
+ // if this isn't the first iteration, or if the first iteration is also the last iteration,
+ // move away from the collision plane slightly, if possible
+ // this reduces getting stuck in some concave geometry, like the gaps above the railings in some ald'ruhn buildings
+ // this is different from the normal collision margin, because the normal collision margin is along the movement path,
+ // but this is along the collision normal
+ if(!usedSeamLogic && (iterations > 0 || remainingTime < 0.01f))
+ {
+ tracer.doTrace(colobj, newPosition, newPosition + planeNormal*(sCollisionMargin*2.0), collisionWorld);
+ newPosition = (newPosition + tracer.mEndPos)/2.0;
+ }
+
+ // Do not allow sliding up steep slopes if there is gravity.
+ if (newPosition.z() >= swimlevel && !actor.mFlying && !isWalkableSlope(planeNormal))
+ newVelocity.z() = std::min(newVelocity.z(), velocity.z());
+
+ if (newVelocity * origVelocity <= 0.0f)
break;
- if ((newVelocity * origVelocity) <= 0.f)
- break; // ^ dot product
+ numTimesSlid += 1;
+ lastSlideNormalFallback = lastSlideNormal;
+ lastSlideNormal = planeNormal;
velocity = newVelocity;
}
}
bool isOnGround = false;
bool isOnSlope = false;
- if (!(inertia.z() > 0.f) && !(newPosition.z() < swimlevel))
+ if (forceGroundTest || (inertia.z() <= 0.f && newPosition.z() >= swimlevel))
{
osg::Vec3f from = newPosition;
- osg::Vec3f to = newPosition - (physicActor->getOnGround() ? osg::Vec3f(0,0,sStepSizeDown + 2*sGroundOffset) : osg::Vec3f(0,0,2*sGroundOffset));
+ auto dropDistance = 2*sGroundOffset + (physicActor->getOnGround() ? sStepSizeDown : 0);
+ osg::Vec3f to = newPosition - osg::Vec3f(0,0,dropDistance);
tracer.doTrace(colobj, from, to, collisionWorld);
- if(tracer.mFraction < 1.0f && !isActor(tracer.mHitObject))
+ if(tracer.mFraction < 1.0f)
{
- const btCollisionObject* standingOn = tracer.mHitObject;
- PtrHolder* ptrHolder = static_cast(standingOn->getUserPointer());
- if (ptrHolder)
- actor.mStandingOn = ptrHolder->getPtr();
-
- if (standingOn->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Water)
- physicActor->setWalkingOnWater(true);
- if (!actor.mFlying)
- newPosition.z() = tracer.mEndPos.z() + sGroundOffset;
-
- isOnGround = true;
-
- isOnSlope = !isWalkableSlope(tracer.mPlaneNormal);
- }
- else
- {
- // standing on actors is not allowed (see above).
- // in addition to that, apply a sliding effect away from the center of the actor,
- // so that we do not stay suspended in air indefinitely.
- if (tracer.mFraction < 1.0f && isActor(tracer.mHitObject))
+ if (!isActor(tracer.mHitObject))
{
- if (osg::Vec3f(velocity.x(), velocity.y(), 0).length2() < 100.f*100.f)
+ isOnGround = true;
+ isOnSlope = !isWalkableSlope(tracer.mPlaneNormal);
+
+ const btCollisionObject* standingOn = tracer.mHitObject;
+ PtrHolder* ptrHolder = static_cast(standingOn->getUserPointer());
+ if (ptrHolder)
+ actor.mStandingOn = ptrHolder->getPtr();
+
+ if (standingOn->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Water)
+ physicActor->setWalkingOnWater(true);
+ if (!actor.mFlying && !isOnSlope)
{
- btVector3 aabbMin, aabbMax;
- tracer.mHitObject->getCollisionShape()->getAabb(tracer.mHitObject->getWorldTransform(), aabbMin, aabbMax);
- btVector3 center = (aabbMin + aabbMax) / 2.f;
- inertia = osg::Vec3f(actor.mPosition.x() - center.x(), actor.mPosition.y() - center.y(), 0);
- inertia.normalize();
- inertia *= 100;
+ if (tracer.mFraction*dropDistance > sGroundOffset)
+ newPosition.z() = tracer.mEndPos.z() + sGroundOffset;
+ else
+ {
+ newPosition.z() = tracer.mEndPos.z();
+ tracer.doTrace(colobj, newPosition, newPosition + osg::Vec3f(0, 0, 2*sGroundOffset), collisionWorld);
+ newPosition = (newPosition+tracer.mEndPos)/2.0;
+ }
}
}
+ else
+ {
+ // Vanilla allows actors to float on top of other actors. Do not push them off.
+ if (!actor.mFlying && isWalkableSlope(tracer.mPlaneNormal) && tracer.mEndPos.z()+sGroundOffset <= newPosition.z())
+ newPosition.z() = tracer.mEndPos.z() + sGroundOffset;
- isOnGround = false;
+ isOnGround = false;
+ }
}
}
@@ -294,7 +412,98 @@ namespace MWPhysics
physicActor->setOnGround(isOnGround);
physicActor->setOnSlope(isOnSlope);
- newPosition.z() -= halfExtents.z(); // remove what was added at the beginning
actor.mPosition = newPosition;
+ // remove what was added earlier in compensating for doTrace not taking interior transformation into account
+ actor.mPosition.z() -= halfExtents.z(); // vanilla-accurate
+ }
+
+ btVector3 addMarginToDelta(btVector3 delta)
+ {
+ if(delta.length2() == 0.0)
+ return delta;
+ return delta + delta.normalized() * sCollisionMargin;
+ }
+
+ void MovementSolver::unstuck(ActorFrameData& actor, const btCollisionWorld* collisionWorld)
+ {
+ const auto& ptr = actor.mActorRaw->getPtr();
+ if (!ptr.getClass().isMobile(ptr))
+ return;
+
+ auto* physicActor = actor.mActorRaw;
+ if(!physicActor->getCollisionMode()) // noclipping/tcl
+ return;
+
+ auto* collisionObject = physicActor->getCollisionObject();
+ auto tempPosition = actor.mPosition;
+
+ // use vanilla-accurate collision hull position hack (do same hitbox offset hack as movement solver)
+ // if vanilla compatibility didn't matter, the "correct" collision hull position would be physicActor->getScaledMeshTranslation()
+ const auto verticalHalfExtent = osg::Vec3f(0.0, 0.0, physicActor->getHalfExtents().z());
+
+ // use a 3d approximation of the movement vector to better judge player intent
+ const ESM::Position& refpos = ptr.getRefData().getPosition();
+ auto velocity = (osg::Quat(refpos.rot[0], osg::Vec3f(-1, 0, 0)) * osg::Quat(refpos.rot[2], osg::Vec3f(0, 0, -1))) * actor.mMovement;
+ // try to pop outside of the world before doing anything else if we're inside of it
+ if (!physicActor->getOnGround() || physicActor->getOnSlope())
+ velocity += physicActor->getInertialForce();
+
+ // because of the internal collision box offset hack, and the fact that we're moving the collision box manually,
+ // we need to replicate part of the collision box's transform process from scratch
+ osg::Vec3f refPosition = tempPosition + verticalHalfExtent;
+ osg::Vec3f goodPosition = refPosition;
+ const btTransform oldTransform = collisionObject->getWorldTransform();
+ btTransform newTransform = oldTransform;
+
+ auto gatherContacts = [&](btVector3 newOffset) -> ContactCollectionCallback
+ {
+ goodPosition = refPosition + Misc::Convert::toOsg(addMarginToDelta(newOffset));
+ newTransform.setOrigin(Misc::Convert::toBullet(goodPosition));
+ collisionObject->setWorldTransform(newTransform);
+
+ ContactCollectionCallback callback{collisionObject, velocity};
+ ContactTestWrapper::contactTest(const_cast(collisionWorld), collisionObject, callback);
+ return callback;
+ };
+
+ // check whether we're inside the world with our collision box with manually-derived offset
+ auto contactCallback = gatherContacts({0.0, 0.0, 0.0});
+ if(contactCallback.mDistance < -sAllowedPenetration)
+ {
+ // we are; try moving it out of the world
+ auto positionDelta = contactCallback.mContactSum;
+ // limit rejection delta to the largest known individual rejections
+ if(std::abs(positionDelta.x()) > contactCallback.mMaxX)
+ positionDelta *= contactCallback.mMaxX / std::abs(positionDelta.x());
+ if(std::abs(positionDelta.y()) > contactCallback.mMaxY)
+ positionDelta *= contactCallback.mMaxY / std::abs(positionDelta.y());
+ if(std::abs(positionDelta.z()) > contactCallback.mMaxZ)
+ positionDelta *= contactCallback.mMaxZ / std::abs(positionDelta.z());
+
+ auto contactCallback2 = gatherContacts(positionDelta);
+ // successfully moved further out from contact (does not have to be in open space, just less inside of things)
+ if(contactCallback2.mDistance > contactCallback.mDistance)
+ tempPosition = goodPosition - verticalHalfExtent;
+ // try again but only upwards (fixes some bad coc floors)
+ else
+ {
+ // upwards-only offset
+ auto contactCallback3 = gatherContacts({0.0, 0.0, std::abs(positionDelta.z())});
+ // success
+ if(contactCallback3.mDistance > contactCallback.mDistance)
+ tempPosition = goodPosition - verticalHalfExtent;
+ else
+ // try again but fixed distance up
+ {
+ auto contactCallback4 = gatherContacts({0.0, 0.0, 10.0});
+ // success
+ if(contactCallback4.mDistance > contactCallback.mDistance)
+ tempPosition = goodPosition - verticalHalfExtent;
+ }
+ }
+ }
+
+ collisionObject->setWorldTransform(oldTransform);
+ actor.mPosition = tempPosition;
}
}
diff --git a/apps/openmw/mwphysics/movementsolver.hpp b/apps/openmw/mwphysics/movementsolver.hpp
index 75fba1cf0..76141ec0e 100644
--- a/apps/openmw/mwphysics/movementsolver.hpp
+++ b/apps/openmw/mwphysics/movementsolver.hpp
@@ -5,6 +5,9 @@
#include
+#include "constants.hpp"
+#include "../mwworld/ptr.hpp"
+
class btCollisionWorld;
namespace MWWorld
@@ -14,29 +17,35 @@ namespace MWWorld
namespace MWPhysics
{
+ /// Vector projection
+ static inline osg::Vec3f project(const osg::Vec3f& u, const osg::Vec3f &v)
+ {
+ return v * (u * v);
+ }
+
+ /// Vector rejection
+ static inline osg::Vec3f reject(const osg::Vec3f& direction, const osg::Vec3f &planeNormal)
+ {
+ return direction - project(direction, planeNormal);
+ }
+
+ template
+ static bool isWalkableSlope(const Vec3 &normal)
+ {
+ static const float sMaxSlopeCos = std::cos(osg::DegreesToRadians(sMaxSlope));
+ return (normal.z() > sMaxSlopeCos);
+ }
+
class Actor;
struct ActorFrameData;
struct WorldFrameData;
class MovementSolver
{
- private:
- ///Project a vector u on another vector v
- static inline osg::Vec3f project(const osg::Vec3f& u, const osg::Vec3f &v)
- {
- return v * (u * v);
- // ^ dot product
- }
-
- ///Helper for computing the character sliding
- static inline osg::Vec3f slide(const osg::Vec3f& direction, const osg::Vec3f &planeNormal)
- {
- return direction - project(direction, planeNormal);
- }
-
public:
static osg::Vec3f traceDown(const MWWorld::Ptr &ptr, const osg::Vec3f& position, Actor* actor, btCollisionWorld* collisionWorld, float maxHeight);
static void move(ActorFrameData& actor, float time, const btCollisionWorld* collisionWorld, WorldFrameData& worldData);
+ static void unstuck(ActorFrameData& actor, const btCollisionWorld* collisionWorld);
};
}
diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp
index 2d4009e7d..0d1e5962a 100644
--- a/apps/openmw/mwphysics/mtphysics.cpp
+++ b/apps/openmw/mwphysics/mtphysics.cpp
@@ -13,10 +13,12 @@
#include "../mwworld/player.hpp"
#include "actor.hpp"
+#include "contacttestwrapper.h"
#include "movementsolver.hpp"
#include "mtphysics.hpp"
#include "object.hpp"
#include "physicssystem.hpp"
+#include "projectile.hpp"
namespace
{
@@ -86,12 +88,13 @@ namespace
void updateMechanics(MWPhysics::ActorFrameData& actorData)
{
+ auto ptr = actorData.mActorRaw->getPtr();
if (actorData.mDidJump)
- handleJump(actorData.mPtr);
+ handleJump(ptr);
- MWMechanics::CreatureStats& stats = actorData.mPtr.getClass().getCreatureStats(actorData.mPtr);
+ MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr);
if (actorData.mNeedLand)
- stats.land(actorData.mPtr == MWMechanics::getPlayer() && (actorData.mFlying || actorData.mSwimming));
+ stats.land(ptr == MWMechanics::getPlayer() && (actorData.mFlying || actorData.mSwimming));
else if (actorData.mFallHeight < 0)
stats.addToFallHeight(-actorData.mFallHeight);
}
@@ -99,15 +102,6 @@ namespace
osg::Vec3f interpolateMovements(MWPhysics::ActorFrameData& actorData, float timeAccum, float physicsDt)
{
const float interpolationFactor = timeAccum / physicsDt;
-
- // account for force change of actor's position in the main thread
- const auto correction = actorData.mActorRaw->getWorldPosition() - actorData.mOrigin;
- if (correction.length() != 0)
- {
- actorData.mActorRaw->adjustPosition(correction);
- actorData.mPosition = actorData.mActorRaw->getPosition();
- }
-
return actorData.mPosition * interpolationFactor + actorData.mActorRaw->getPreviousPosition() * (1.f - interpolationFactor);
}
@@ -174,7 +168,14 @@ namespace MWPhysics
mPreStepBarrier = std::make_unique(mNumThreads, [&]()
{
+ if (mDeferAabbUpdate)
updateAabbs();
+ for (auto& data : mActorsFrameData)
+ if (data.mActor.lock())
+ {
+ std::unique_lock lock(mCollisionWorldMutex);
+ MovementSolver::unstuck(data, mCollisionWorld.get());
+ }
});
mPostStepBarrier = std::make_unique(mNumThreads, [&]()
@@ -212,45 +213,43 @@ namespace MWPhysics
thread.join();
}
- const PtrPositionList& PhysicsTaskScheduler::moveActors(int numSteps, float timeAccum, std::vector&& actorsData, bool skipSimulation, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats)
+ const std::vector& PhysicsTaskScheduler::moveActors(int numSteps, float timeAccum, std::vector&& actorsData, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats)
{
// This function run in the main thread.
// While the mSimulationMutex is held, background physics threads can't run.
std::unique_lock lock(mSimulationMutex);
- for (auto& data : actorsData)
- data.updatePosition();
+ mMovedActors.clear();
// start by finishing previous background computation
if (mNumThreads != 0)
{
for (auto& data : mActorsFrameData)
{
- // Ignore actors that were deleted while the background thread was running
- if (!data.mActor.lock())
- continue;
+ const auto actorActive = [&data](const auto& newFrameData) -> bool
+ {
+ const auto actor = data.mActor.lock();
+ return actor && actor->getPtr() == newFrameData.mActorRaw->getPtr();
+ };
+ // Only return actors that are still part of the scene
+ if (std::any_of(actorsData.begin(), actorsData.end(), actorActive))
+ {
+ updateMechanics(data);
- updateMechanics(data);
- if (mAdvanceSimulation)
- data.mActorRaw->setStandingOnPtr(data.mStandingOn);
-
- if (mMovementResults.find(data.mPtr) != mMovementResults.end())
- data.mActorRaw->setSimulationPosition(mMovementResults[data.mPtr]);
+ // these variables are accessed directly from the main thread, update them here to prevent accessing "too new" values
+ if (mAdvanceSimulation)
+ data.mActorRaw->setStandingOnPtr(data.mStandingOn);
+ data.mActorRaw->setSimulationPosition(interpolateMovements(data, mTimeAccum, mPhysicsDt));
+ mMovedActors.emplace_back(data.mActorRaw->getPtr());
+ }
}
-
- if (mFrameNumber == frameNumber - 1)
- {
- stats.setAttribute(mFrameNumber, "physicsworker_time_begin", mTimer->delta_s(mFrameStart, mTimeBegin));
- stats.setAttribute(mFrameNumber, "physicsworker_time_taken", mTimer->delta_s(mTimeBegin, mTimeEnd));
- stats.setAttribute(mFrameNumber, "physicsworker_time_end", mTimer->delta_s(mFrameStart, mTimeEnd));
- }
- mFrameStart = frameStart;
- mTimeBegin = mTimer->tick();
- mFrameNumber = frameNumber;
+ updateStats(frameStart, frameNumber, stats);
}
// init
+ for (auto& data : actorsData)
+ data.updatePosition();
mRemainingSteps = numSteps;
mTimeAccum = timeAccum;
mActorsFrameData = std::move(actorsData);
@@ -263,52 +262,30 @@ namespace MWPhysics
if (mAdvanceSimulation)
mWorldFrameData = std::make_unique();
- // we are asked to skip the simulation (load a savegame for instance)
- // just return the actors' reference position without applying the movements
- if (skipSimulation)
- {
- mMovementResults.clear();
- for (const auto& m : mActorsFrameData)
- {
- m.mActorRaw->setStandingOnPtr(nullptr);
- m.mActorRaw->resetPosition();
- mMovementResults[m.mPtr] = m.mActorRaw->getWorldPosition();
- }
- return mMovementResults;
- }
-
if (mNumThreads == 0)
{
- mMovementResults.clear();
syncComputation();
-
- for (auto& data : mActorsFrameData)
- {
- if (mAdvanceSimulation)
- data.mActorRaw->setStandingOnPtr(data.mStandingOn);
- if (mMovementResults.find(data.mPtr) != mMovementResults.end())
- data.mActorRaw->setSimulationPosition(mMovementResults[data.mPtr]);
- }
- return mMovementResults;
+ return mMovedActors;
}
- // Remove actors that were deleted while the background thread was running
- for (auto& data : mActorsFrameData)
- {
- if (!data.mActor.lock())
- mMovementResults.erase(data.mPtr);
- }
- std::swap(mMovementResults, mPreviousMovementResults);
-
- // mMovementResults is shared between all workers instance
- // pre-allocate all nodes so that we don't need synchronization
- mMovementResults.clear();
- for (const auto& m : mActorsFrameData)
- mMovementResults[m.mPtr] = m.mPosition;
-
lock.unlock();
mHasJob.notify_all();
- return mPreviousMovementResults;
+ return mMovedActors;
+ }
+
+ const std::vector& PhysicsTaskScheduler::resetSimulation(const ActorMap& actors)
+ {
+ std::unique_lock lock(mSimulationMutex);
+ mMovedActors.clear();
+ mActorsFrameData.clear();
+ for (const auto& [_, actor] : actors)
+ {
+ actor->updatePosition();
+ actor->setSimulationPosition(actor->getWorldPosition()); // updatePosition skip next simulation, now we need to "consume" it
+ actor->updateCollisionObjectPosition();
+ mMovedActors.emplace_back(actor->getPtr());
+ }
+ return mMovedActors;
}
void PhysicsTaskScheduler::rayTest(const btVector3& rayFromWorld, const btVector3& rayToWorld, btCollisionWorld::RayResultCallback& resultCallback) const
@@ -326,7 +303,7 @@ namespace MWPhysics
void PhysicsTaskScheduler::contactTest(btCollisionObject* colObj, btCollisionWorld::ContactResultCallback& resultCallback)
{
std::shared_lock lock(mCollisionWorldMutex);
- mCollisionWorld->contactTest(colObj, resultCallback);
+ ContactTestWrapper::contactTest(mCollisionWorld.get(), colObj, resultCallback);
}
std::optional PhysicsTaskScheduler::getHitPoint(const btTransform& from, btCollisionObject* target)
@@ -376,18 +353,18 @@ namespace MWPhysics
mCollisionWorld->removeCollisionObject(collisionObject);
}
- void PhysicsTaskScheduler::updateSingleAabb(std::weak_ptr ptr)
+ void PhysicsTaskScheduler::updateSingleAabb(std::weak_ptr ptr, bool immediate)
{
- if (mDeferAabbUpdate)
- {
- std::unique_lock lock(mUpdateAabbMutex);
- mUpdateAabb.insert(std::move(ptr));
- }
- else
+ if (!mDeferAabbUpdate || immediate)
{
std::unique_lock lock(mCollisionWorldMutex);
updatePtrAabb(ptr);
}
+ else
+ {
+ std::unique_lock lock(mUpdateAabbMutex);
+ mUpdateAabb.insert(std::move(ptr));
+ }
}
bool PhysicsTaskScheduler::getLineOfSight(const std::weak_ptr& actor1, const std::weak_ptr& actor2)
@@ -453,6 +430,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());
+ }
};
}
@@ -464,8 +446,7 @@ namespace MWPhysics
if (!mNewFrame)
mHasJob.wait(lock, [&]() { return mQuit || mNewFrame; });
- if (mDeferAabbUpdate)
- mPreStepBarrier->wait();
+ mPreStepBarrier->wait();
int job = 0;
while (mRemainingSteps && (job = mNextJob.fetch_add(1, std::memory_order_relaxed)) < mNumJobs)
@@ -485,7 +466,6 @@ namespace MWPhysics
{
auto& actorData = mActorsFrameData[job];
handleFall(actorData, mAdvanceSimulation);
- mMovementResults[actorData.mPtr] = interpolateMovements(actorData, mTimeAccum, mPhysicsDt);
}
}
@@ -503,9 +483,7 @@ namespace MWPhysics
{
if(const auto actor = actorData.mActor.lock())
{
- bool positionChanged = actorData.mPosition != actorData.mActorRaw->getPosition();
- actorData.mActorRaw->setPosition(actorData.mPosition);
- if (positionChanged)
+ if (actor->setPosition(actorData.mPosition))
{
actor->updateCollisionObjectPosition();
mCollisionWorld->updateSingleAabb(actor->getCollisionObject());
@@ -534,7 +512,10 @@ namespace MWPhysics
while (mRemainingSteps--)
{
for (auto& actorData : mActorsFrameData)
+ {
+ MovementSolver::unstuck(actorData, mCollisionWorld.get());
MovementSolver::move(actorData, mPhysicsDt, mCollisionWorld.get(), *mWorldFrameData);
+ }
updateActorsPositions();
}
@@ -542,8 +523,24 @@ namespace MWPhysics
for (auto& actorData : mActorsFrameData)
{
handleFall(actorData, mAdvanceSimulation);
- mMovementResults[actorData.mPtr] = interpolateMovements(actorData, mTimeAccum, mPhysicsDt);
+ actorData.mActorRaw->setSimulationPosition(interpolateMovements(actorData, mTimeAccum, mPhysicsDt));
updateMechanics(actorData);
+ mMovedActors.emplace_back(actorData.mActorRaw->getPtr());
+ if (mAdvanceSimulation)
+ actorData.mActorRaw->setStandingOnPtr(actorData.mStandingOn);
}
}
+
+ void PhysicsTaskScheduler::updateStats(osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats)
+ {
+ if (mFrameNumber == frameNumber - 1)
+ {
+ stats.setAttribute(mFrameNumber, "physicsworker_time_begin", mTimer->delta_s(mFrameStart, mTimeBegin));
+ stats.setAttribute(mFrameNumber, "physicsworker_time_taken", mTimer->delta_s(mTimeBegin, mTimeEnd));
+ stats.setAttribute(mFrameNumber, "physicsworker_time_end", mTimer->delta_s(mFrameStart, mTimeEnd));
+ }
+ mFrameStart = frameStart;
+ mTimeBegin = mTimer->tick();
+ mFrameNumber = frameNumber;
+ }
}
diff --git a/apps/openmw/mwphysics/mtphysics.hpp b/apps/openmw/mwphysics/mtphysics.hpp
index 3ffc851e5..f10b39f0c 100644
--- a/apps/openmw/mwphysics/mtphysics.hpp
+++ b/apps/openmw/mwphysics/mtphysics.hpp
@@ -32,7 +32,9 @@ namespace MWPhysics
/// @param timeAccum accumulated time from previous run to interpolate movements
/// @param actorsData per actor data needed to compute new positions
/// @return new position of each actor
- const PtrPositionList& moveActors(int numSteps, float timeAccum, std::vector&& actorsData, bool skip, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats);
+ const std::vector& moveActors(int numSteps, float timeAccum, std::vector&& actorsData, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats);
+
+ const std::vector& resetSimulation(const ActorMap& actors);
// Thread safe wrappers
void rayTest(const btVector3& rayFromWorld, const btVector3& rayToWorld, btCollisionWorld::RayResultCallback& resultCallback) const;
@@ -44,7 +46,7 @@ namespace MWPhysics
void setCollisionFilterMask(btCollisionObject* collisionObject, int collisionFilterMask);
void addCollisionObject(btCollisionObject* collisionObject, int collisionFilterGroup, int collisionFilterMask);
void removeCollisionObject(btCollisionObject* collisionObject);
- void updateSingleAabb(std::weak_ptr ptr);
+ void updateSingleAabb(std::weak_ptr ptr, bool immediate=false);
bool getLineOfSight(const std::weak_ptr& actor1, const std::weak_ptr& actor2);
private:
@@ -55,11 +57,11 @@ namespace MWPhysics
void refreshLOSCache();
void updateAabbs();
void updatePtrAabb(const std::weak_ptr& ptr);
+ void updateStats(osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats);
std::unique_ptr mWorldFrameData;
std::vector mActorsFrameData;
- PtrPositionList mMovementResults;
- PtrPositionList mPreviousMovementResults;
+ std::vector mMovedActors;
/*
Start of tes3mp change (major)
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 2bf94060f..f347682b2 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)
@@ -272,7 +275,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)
{
@@ -309,7 +312,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;
@@ -458,7 +461,6 @@ namespace MWPhysics
ActorMap::iterator found = mActors.find(ptr);
if (found == mActors.end())
return ptr.getRefData().getPosition().asVec3();
- found->second->resetPosition();
return MovementSolver::traceDown(ptr, position, found->second.get(), mCollisionWorld.get(), maxHeight);
}
@@ -526,6 +528,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);
@@ -551,6 +560,13 @@ namespace MWPhysics
if (actor->getStandingOnPtr() == old)
actor->setStandingOnPtr(updated);
}
+
+ for (auto& [_, projectile] : mProjectiles)
+ {
+ if (projectile->getCaster() == old)
+ projectile->setCaster(updated);
+ }
+
}
Actor *PhysicsSystem::getActor(const MWWorld::Ptr &ptr)
@@ -577,6 +593,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);
@@ -596,6 +620,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);
@@ -630,7 +665,7 @@ namespace MWPhysics
if (foundActor != mActors.end())
{
foundActor->second->updatePosition();
- mTaskScheduler->updateSingleAabb(foundActor->second);
+ mTaskScheduler->updateSingleAabb(foundActor->second, true);
return;
}
}
@@ -640,7 +675,7 @@ namespace MWPhysics
osg::ref_ptr shape = mShapeManager->getShape(mesh);
// Try to get shape from basic model as fallback for creatures
- if (!ptr.getClass().isNpc() && shape && shape->mCollisionBoxHalfExtents.length2() == 0)
+ if (!ptr.getClass().isNpc() && shape && shape->mCollisionBox.extents.length2() == 0)
{
const std::string fallbackModel = ptr.getClass().getModel(ptr);
if (fallbackModel != mesh)
@@ -656,6 +691,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());
@@ -690,7 +734,7 @@ namespace MWPhysics
mMovementQueue.clear();
}
- const PtrPositionList& PhysicsSystem::applyQueuedMovement(float dt, bool skipSimulation, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats)
+ const std::vector& PhysicsSystem::applyQueuedMovement(float dt, bool skipSimulation, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats)
{
mTimeAccum += dt;
@@ -700,7 +744,10 @@ namespace MWPhysics
mTimeAccum -= numSteps * mPhysicsDt;
- return mTaskScheduler->moveActors(numSteps, mTimeAccum, prepareFrameData(numSteps), skipSimulation, frameStart, frameNumber, stats);
+ if (skipSimulation)
+ return mTaskScheduler->resetSimulation(mActors);
+
+ return mTaskScheduler->moveActors(numSteps, mTimeAccum, prepareFrameData(numSteps), frameStart, frameNumber, stats);
}
std::vector PhysicsSystem::prepareFrameData(int numSteps)
@@ -746,7 +793,7 @@ namespace MWPhysics
if (numSteps == 0)
standingOn = physicActor->getStandingOnPtr();
- actorsFrameData.emplace_back(std::move(physicActor), character, standingOn, moveToWaterSurface, movement, slowFall, waterlevel);
+ actorsFrameData.emplace_back(std::move(physicActor), standingOn, moveToWaterSurface, movement, slowFall, waterlevel);
}
mMovementQueue.clear();
return actorsFrameData;
@@ -888,33 +935,34 @@ namespace MWPhysics
mDebugDrawer->addCollision(position, normal);
}
- ActorFrameData::ActorFrameData(const std::shared_ptr& actor, const MWWorld::Ptr character, const MWWorld::Ptr standingOn,
+ ActorFrameData::ActorFrameData(const std::shared_ptr& actor, const MWWorld::Ptr standingOn,
bool moveToWaterSurface, osg::Vec3f movement, float slowFall, float waterlevel)
: mActor(actor), mActorRaw(actor.get()), mStandingOn(standingOn),
mDidJump(false), mNeedLand(false), mMoveToWaterSurface(moveToWaterSurface),
mWaterlevel(waterlevel), mSlowFall(slowFall), mOldHeight(0), mFallHeight(0), mMovement(movement), mPosition(), mRefpos()
{
const MWBase::World *world = MWBase::Environment::get().getWorld();
- mPtr = actor->getPtr();
- mFlying = world->isFlying(character);
- mSwimming = world->isSwimming(character);
- mWantJump = mPtr.getClass().getMovementSettings(mPtr).mPosition[2] != 0;
- mIsDead = mPtr.getClass().getCreatureStats(mPtr).isDead();
+ const auto ptr = actor->getPtr();
+ mFlying = world->isFlying(ptr);
+ mSwimming = world->isSwimming(ptr);
+ mWantJump = ptr.getClass().getMovementSettings(ptr).mPosition[2] != 0;
+ auto& stats = ptr.getClass().getCreatureStats(ptr);
+ const bool godmode = ptr == world->getPlayerConstPtr() && world->getGodModeState();
+ mFloatToSurface = stats.isDead() || (!godmode && stats.isParalyzed());
mWasOnGround = actor->getOnGround();
}
void ActorFrameData::updatePosition()
{
- mActorRaw->updatePosition();
- mOrigin = mActorRaw->getSimulationPosition();
+ mActorRaw->updateWorldPosition();
mPosition = mActorRaw->getPosition();
if (mMoveToWaterSurface)
{
mPosition.z() = mWaterlevel;
- mActorRaw->setPosition(mPosition);
+ MWBase::Environment::get().getWorld()->moveObject(mActorRaw->getPtr(), mPosition.x(), mPosition.y(), mPosition.z());
}
mOldHeight = mPosition.z();
- mRefpos = mPtr.getRefData().getPosition();
+ mRefpos = mActorRaw->getPtr().getRefData().getPosition();
}
WorldFrameData::WorldFrameData()
diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp
index 8e369a254..ead229ec4 100644
--- a/apps/openmw/mwphysics/physicssystem.hpp
+++ b/apps/openmw/mwphysics/physicssystem.hpp
@@ -50,12 +50,13 @@ class btVector3;
namespace MWPhysics
{
- using PtrPositionList = std::map;
-
class HeightField;
class Object;
class Actor;
class PhysicsTaskScheduler;
+ class Projectile;
+
+ using ActorMap = std::map>;
struct ContactPoint
{
@@ -77,18 +78,17 @@ namespace MWPhysics
struct ActorFrameData
{
- ActorFrameData(const std::shared_ptr& actor, const MWWorld::Ptr character, const MWWorld::Ptr standingOn, bool moveToWaterSurface, osg::Vec3f movement, float slowFall, float waterlevel);
+ ActorFrameData(const std::shared_ptr& actor, const MWWorld::Ptr standingOn, bool moveToWaterSurface, osg::Vec3f movement, float slowFall, float waterlevel);
void updatePosition();
std::weak_ptr mActor;
Actor* mActorRaw;
- MWWorld::Ptr mPtr;
MWWorld::Ptr mStandingOn;
bool mFlying;
bool mSwimming;
bool mWasOnGround;
bool mWantJump;
bool mDidJump;
- bool mIsDead;
+ bool mFloatToSurface;
bool mNeedLand;
bool mMoveToWaterSurface;
float mWaterlevel;
@@ -96,7 +96,6 @@ namespace MWPhysics
float mOldHeight;
float mFallHeight;
osg::Vec3f mMovement;
- osg::Vec3f mOrigin;
osg::Vec3f mPosition;
ESM::Position mRefpos;
};
@@ -125,6 +124,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 +135,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 +144,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 +174,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;
@@ -202,7 +206,7 @@ namespace MWPhysics
void queueObjectMovement(const MWWorld::Ptr &ptr, const osg::Vec3f &velocity);
/// Apply all queued movements, then clear the list.
- const PtrPositionList& applyQueuedMovement(float dt, bool skipSimulation, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats);
+ const std::vector& applyQueuedMovement(float dt, bool skipSimulation, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats);
/// Clear the queued movements list without applying.
void clearQueuedMovement();
@@ -275,9 +279,11 @@ namespace MWPhysics
std::set