1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-04-01 08:36:40 +00:00

Merge remote-tracking branch 'upstream/master' into alpha-meddling

This commit is contained in:
AnyOldName3 2020-12-27 02:48:42 +00:00
commit 5e004356a2
50 changed files with 1105 additions and 398 deletions

View file

@ -39,6 +39,7 @@
Bug #5415: Environment maps in ebony cuirass and HiRez Armors Indoril cuirass don't work Bug #5415: Environment maps in ebony cuirass and HiRez Armors Indoril cuirass don't work
Bug #5416: Junk non-node records before the root node are not handled gracefully Bug #5416: Junk non-node records before the root node are not handled gracefully
Bug #5422: The player loses all spells when resurrected Bug #5422: The player loses all spells when resurrected
Bug #5423: Guar follows actors too closely
Bug #5424: Creatures do not headtrack player Bug #5424: Creatures do not headtrack player
Bug #5425: Poison effect only appears for one frame Bug #5425: Poison effect only appears for one frame
Bug #5427: GetDistance unknown ID error is misleading Bug #5427: GetDistance unknown ID error is misleading
@ -72,7 +73,9 @@
Bug #5688: Water shader broken indoors with enable indoor shadows = false Bug #5688: Water shader broken indoors with enable indoor shadows = false
Bug #5695: ExplodeSpell for actors doesn't target the ground Bug #5695: ExplodeSpell for actors doesn't target the ground
Bug #5703: OpenMW-CS menu system crashing on XFCE Bug #5703: OpenMW-CS menu system crashing on XFCE
Bug #5706: AI sequences stop looping after the saved game is reloaded
Bug #5731: Editor: skirts are invisible on characters Bug #5731: Editor: skirts are invisible on characters
Bug #5758: Paralyzed actors behavior is inconsistent with vanilla
Feature #390: 3rd person look "over the shoulder" Feature #390: 3rd person look "over the shoulder"
Feature #1536: Show more information about level on menu Feature #1536: Show more information about level on menu
Feature #2386: Distant Statics in the form of Object Paging Feature #2386: Distant Statics in the form of Object Paging

View file

@ -584,6 +584,12 @@ if (WIN32)
5031 # #pragma warning(pop): likely mismatch, popping warning state pushed in different file (config_begin.hpp, config_end.hpp) 5031 # #pragma warning(pop): likely mismatch, popping warning state pushed in different file (config_begin.hpp, config_end.hpp)
) )
endif() endif()
if( "${MyGUI_VERSION}" VERSION_LESS_EQUAL "3.4.0" )
set(WARNINGS_DISABLE ${WARNINGS_DISABLE}
4866 # compiler may not enforce left-to-right evaluation order for call
)
endif()
foreach(d ${WARNINGS_DISABLE}) foreach(d ${WARNINGS_DISABLE})
set(WARNINGS "${WARNINGS} /wd${d}") set(WARNINGS "${WARNINGS} /wd${d}")

View file

@ -199,6 +199,7 @@ namespace MWBase
virtual std::list<MWWorld::Ptr> getActorsSidingWith(const MWWorld::Ptr& actor) = 0; virtual std::list<MWWorld::Ptr> getActorsSidingWith(const MWWorld::Ptr& actor) = 0;
virtual std::list<MWWorld::Ptr> getActorsFollowing(const MWWorld::Ptr& actor) = 0; virtual std::list<MWWorld::Ptr> getActorsFollowing(const MWWorld::Ptr& actor) = 0;
virtual std::list<int> getActorsFollowingIndices(const MWWorld::Ptr& actor) = 0; virtual std::list<int> getActorsFollowingIndices(const MWWorld::Ptr& actor) = 0;
virtual std::map<int, MWWorld::Ptr> getActorsFollowingByIndex(const MWWorld::Ptr& actor) = 0;
///Returns a list of actors who are fighting the given actor within the fAlarmDistance ///Returns a list of actors who are fighting the given actor within the fAlarmDistance
/** ie AiCombat is active and the target is the actor **/ /** ie AiCombat is active and the target is the actor **/

View file

@ -286,6 +286,9 @@ namespace MWBase
virtual MWWorld::Ptr moveObject(const MWWorld::Ptr &ptr, MWWorld::CellStore* newCell, float x, float y, float z, bool movePhysics=true) = 0; virtual MWWorld::Ptr moveObject(const MWWorld::Ptr &ptr, MWWorld::CellStore* newCell, float x, float y, float z, bool movePhysics=true) = 0;
///< @return an updated Ptr ///< @return an updated Ptr
virtual MWWorld::Ptr moveObjectBy(const MWWorld::Ptr &ptr, osg::Vec3f vec) = 0;
///< @return an updated Ptr
virtual void scaleObject (const MWWorld::Ptr& ptr, float scale) = 0; virtual void scaleObject (const MWWorld::Ptr& ptr, float scale) = 0;
virtual void rotateObject(const MWWorld::Ptr& ptr, float x, float y, float z, virtual void rotateObject(const MWWorld::Ptr& ptr, float x, float y, float z,
@ -516,7 +519,7 @@ namespace MWBase
/// Returns true if levitation spell effect is allowed. /// Returns true if levitation spell effect is allowed.
virtual bool isLevitationEnabled() const = 0; virtual bool isLevitationEnabled() const = 0;
virtual bool getGodModeState() = 0; virtual bool getGodModeState() const = 0;
virtual bool toggleGodMode() = 0; virtual bool toggleGodMode() = 0;

View file

@ -451,6 +451,7 @@ namespace MWGui
setTitle(mPtr.getClass().getName(mPtr)); setTitle(mPtr.getClass().getName(mPtr));
updateTopics(); updateTopics();
updateTopicsPane(); // force update for new services
updateDisposition(); updateDisposition();
restock(); restock();
@ -487,12 +488,14 @@ namespace MWGui
mHistoryContents.clear(); mHistoryContents.clear();
} }
void DialogueWindow::setKeywords(std::list<std::string> keyWords) bool DialogueWindow::setKeywords(std::list<std::string> keyWords)
{ {
if (mKeywords == keyWords && isCompanion() == mIsCompanion) if (mKeywords == keyWords && isCompanion() == mIsCompanion)
return; return false;
mIsCompanion = isCompanion(); mIsCompanion = isCompanion();
mKeywords = keyWords; mKeywords = keyWords;
updateTopicsPane();
return true;
} }
void DialogueWindow::updateTopicsPane() void DialogueWindow::updateTopicsPane()
@ -556,6 +559,8 @@ namespace MWGui
mTopicsList->adjustSize(); mTopicsList->adjustSize();
updateHistory(); updateHistory();
// The topics list has been regenerated so topic formatting needs to be updated
updateTopicFormat();
} }
void DialogueWindow::updateHistory(bool scrollbar) void DialogueWindow::updateHistory(bool scrollbar)
@ -758,9 +763,9 @@ namespace MWGui
void DialogueWindow::updateTopics() void DialogueWindow::updateTopics()
{ {
setKeywords(MWBase::Environment::get().getDialogueManager()->getAvailableTopics()); // Topic formatting needs to be updated regardless of whether the topic list has changed
updateTopicsPane(); if (!setKeywords(MWBase::Environment::get().getDialogueManager()->getAvailableTopics()))
updateTopicFormat(); updateTopicFormat();
} }
bool DialogueWindow::isCompanion() bool DialogueWindow::isCompanion()

View file

@ -122,7 +122,8 @@ namespace MWGui
void setPtr(const MWWorld::Ptr& actor) override; void setPtr(const MWWorld::Ptr& actor) override;
void setKeywords(std::list<std::string> keyWord); /// @return true if stale keywords were updated successfully
bool setKeywords(std::list<std::string> keyWord);
void addResponse (const std::string& title, const std::string& text, bool needMargin = true); void addResponse (const std::string& title, const std::string& text, bool needMargin = true);

View file

@ -67,7 +67,7 @@ void KeyboardNavigation::saveFocus(int mode)
{ {
mKeyFocus[mode] = focus; mKeyFocus[mode] = focus;
} }
else else if(shouldAcceptKeyFocus(mCurrentFocus))
{ {
mKeyFocus[mode] = mCurrentFocus; mKeyFocus[mode] = mCurrentFocus;
} }
@ -93,6 +93,7 @@ void KeyboardNavigation::_unlinkWidget(MyGUI::Widget *widget)
mCurrentFocus = nullptr; mCurrentFocus = nullptr;
} }
#if MYGUI_VERSION < MYGUI_DEFINE_VERSION(3,2,3)
void styleFocusedButton(MyGUI::Widget* w) void styleFocusedButton(MyGUI::Widget* w)
{ {
if (w) if (w)
@ -103,6 +104,7 @@ void styleFocusedButton(MyGUI::Widget* w)
} }
} }
} }
#endif
bool isRootParent(MyGUI::Widget* widget, MyGUI::Widget* root) bool isRootParent(MyGUI::Widget* widget, MyGUI::Widget* root)
{ {
@ -126,7 +128,9 @@ void KeyboardNavigation::onFrame()
if (focus == mCurrentFocus) if (focus == mCurrentFocus)
{ {
#if MYGUI_VERSION < MYGUI_DEFINE_VERSION(3,2,3)
styleFocusedButton(mCurrentFocus); styleFocusedButton(mCurrentFocus);
#endif
return; return;
} }
@ -137,19 +141,21 @@ void KeyboardNavigation::onFrame()
focus = mCurrentFocus; focus = mCurrentFocus;
} }
// style highlighted button (won't be needed for MyGUI 3.2.3)
if (focus != mCurrentFocus) if (focus != mCurrentFocus)
{ {
#if MYGUI_VERSION < MYGUI_DEFINE_VERSION(3,2,3)
if (mCurrentFocus) if (mCurrentFocus)
{ {
if (MyGUI::Button* b = mCurrentFocus->castType<MyGUI::Button>(false)) if (MyGUI::Button* b = mCurrentFocus->castType<MyGUI::Button>(false))
b->_setWidgetState("normal"); b->_setWidgetState("normal");
} }
#endif
mCurrentFocus = focus; mCurrentFocus = focus;
} }
#if MYGUI_VERSION < MYGUI_DEFINE_VERSION(3,2,3)
styleFocusedButton(mCurrentFocus); styleFocusedButton(mCurrentFocus);
#endif
} }
void KeyboardNavigation::setDefaultFocus(MyGUI::Widget *window, MyGUI::Widget *defaultFocus) void KeyboardNavigation::setDefaultFocus(MyGUI::Widget *window, MyGUI::Widget *defaultFocus)

View file

@ -1,10 +1,12 @@
#include "loadingscreen.hpp" #include "loadingscreen.hpp"
#include <array> #include <array>
#include <condition_variable>
#include <osgViewer/Viewer> #include <osgViewer/Viewer>
#include <osg/Texture2D> #include <osg/Texture2D>
#include <osg/Version>
#include <MyGUI_RenderManager.h> #include <MyGUI_RenderManager.h>
#include <MyGUI_ScrollBar.h> #include <MyGUI_ScrollBar.h>
@ -43,6 +45,8 @@ namespace MWGui
, mNestedLoadingCount(0) , mNestedLoadingCount(0)
, mProgress(0) , mProgress(0)
, mShowWallpaper(true) , mShowWallpaper(true)
, mOldCallback(nullptr)
, mHasCallback(false)
{ {
mMainWidget->setSize(MyGUI::RenderManager::getInstance().getViewSize()); mMainWidget->setSize(MyGUI::RenderManager::getInstance().getViewSize());
@ -136,18 +140,53 @@ namespace MWGui
{ {
public: public:
CopyFramebufferToTextureCallback(osg::Texture2D* texture) CopyFramebufferToTextureCallback(osg::Texture2D* texture)
: mTexture(texture) : mOneshot(true)
, mTexture(texture)
{ {
} }
void operator () (osg::RenderInfo& renderInfo) const override void operator () (osg::RenderInfo& renderInfo) const override
{ {
{
std::unique_lock<std::mutex> lock(mMutex);
mOneshot = false;
}
mSignal.notify_all();
int w = renderInfo.getCurrentCamera()->getViewport()->width(); int w = renderInfo.getCurrentCamera()->getViewport()->width();
int h = renderInfo.getCurrentCamera()->getViewport()->height(); int h = renderInfo.getCurrentCamera()->getViewport()->height();
mTexture->copyTexImage2D(*renderInfo.getState(), 0, 0, w, h); mTexture->copyTexImage2D(*renderInfo.getState(), 0, 0, w, h);
{
std::unique_lock<std::mutex> lock(mMutex);
mOneshot = false;
}
mSignal.notify_all();
}
void wait()
{
std::unique_lock<std::mutex> lock(mMutex);
while (mOneshot)
mSignal.wait(lock);
}
void waitUntilInvoked()
{
std::unique_lock<std::mutex> lock(mMutex);
while (mOneshot)
mSignal.wait(lock);
}
void reset()
{
mOneshot = true;
} }
private: private:
mutable bool mOneshot;
mutable std::mutex mMutex;
mutable std::condition_variable mSignal;
osg::ref_ptr<osg::Texture2D> mTexture; osg::ref_ptr<osg::Texture2D> mTexture;
}; };
@ -322,7 +361,15 @@ namespace MWGui
mCopyFramebufferToTextureCallback = new CopyFramebufferToTextureCallback(mTexture); mCopyFramebufferToTextureCallback = new CopyFramebufferToTextureCallback(mTexture);
} }
#if OSG_VERSION_GREATER_OR_EQUAL(3, 5, 10)
mViewer->getCamera()->addInitialDrawCallback(mCopyFramebufferToTextureCallback); 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->setBackgroundImage("");
mBackgroundImage->setVisible(false); mBackgroundImage->setVisible(false);
@ -365,10 +412,19 @@ namespace MWGui
mViewer->renderingTraversals(); mViewer->renderingTraversals();
mViewer->advance(mViewer->getFrameStamp()->getSimulationTime()); mViewer->advance(mViewer->getFrameStamp()->getSimulationTime());
if (mCopyFramebufferToTextureCallback) 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); mViewer->getCamera()->removeInitialDrawCallback(mCopyFramebufferToTextureCallback);
mCopyFramebufferToTextureCallback = nullptr; #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(); mLastRenderTime = mTimer.time_m();

View file

@ -3,6 +3,7 @@
#include <memory> #include <memory>
#include <osg/Camera>
#include <osg/Timer> #include <osg/Timer>
#include <osg/ref_ptr> #include <osg/ref_ptr>
@ -86,6 +87,8 @@ namespace MWGui
osg::ref_ptr<osg::Texture2D> mTexture; osg::ref_ptr<osg::Texture2D> mTexture;
osg::ref_ptr<CopyFramebufferToTextureCallback> mCopyFramebufferToTextureCallback; osg::ref_ptr<CopyFramebufferToTextureCallback> mCopyFramebufferToTextureCallback;
osg::ref_ptr<osg::Camera::DrawCallback> mOldCallback;
bool mHasCallback;
std::unique_ptr<MyGUI::ITexture> mGuiTexture; std::unique_ptr<MyGUI::ITexture> mGuiTexture;
void changeWallpaper(); void changeWallpaper();

View file

@ -142,6 +142,29 @@ void getRestorationPerHourOfSleep (const MWWorld::Ptr& ptr, float& health, float
magicka = fRestMagicMult * stats.getAttribute(ESM::Attribute::Intelligence).getModified(); magicka = fRestMagicMult * stats.getAttribute(ESM::Attribute::Intelligence).getModified();
} }
template<class T>
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 namespace MWMechanics
@ -2512,26 +2535,14 @@ namespace MWMechanics
std::list<MWWorld::Ptr> Actors::getActorsFollowing(const MWWorld::Ptr& actor) std::list<MWWorld::Ptr> Actors::getActorsFollowing(const MWWorld::Ptr& actor)
{ {
std::list<MWWorld::Ptr> list; std::list<MWWorld::Ptr> list;
for(PtrActorMap::iterator iter(mActors.begin());iter != mActors.end();++iter) forEachFollowingPackage(mActors, actor, getPlayer(), [&] (auto& iter, const std::unique_ptr<AiPackage>& package)
{ {
const MWWorld::Ptr &iteratedActor = iter->first; if (package->followTargetThroughDoors() && package->getTarget() == actor)
if (iteratedActor == getPlayer() || iteratedActor == actor) list.push_back(iter.first);
continue; else if (package->getTypeId() != AiPackageTypeId::Combat && package->getTypeId() != AiPackageTypeId::Wander)
return false;
const CreatureStats &stats = iteratedActor.getClass().getCreatureStats(iteratedActor); return true;
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;
}
}
return list; return list;
} }
@ -2575,32 +2586,38 @@ namespace MWMechanics
std::list<int> Actors::getActorsFollowingIndices(const MWWorld::Ptr &actor) std::list<int> Actors::getActorsFollowingIndices(const MWWorld::Ptr &actor)
{ {
std::list<int> list; std::list<int> list;
for(PtrActorMap::iterator iter(mActors.begin());iter != mActors.end();++iter) forEachFollowingPackage(mActors, actor, getPlayer(), [&] (auto& iter, const std::unique_ptr<AiPackage>& package)
{ {
const MWWorld::Ptr &iteratedActor = iter->first; if (package->followTargetThroughDoors() && package->getTarget() == actor)
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(static_cast<const AiFollow*>(package.get())->getFollowIndex());
{ return false;
list.push_back(static_cast<const AiFollow*>(package.get())->getFollowIndex());
break;
}
else if (package->getTypeId() != AiPackageTypeId::Combat && package->getTypeId() != AiPackageTypeId::Wander)
break;
} }
} else if (package->getTypeId() != AiPackageTypeId::Combat && package->getTypeId() != AiPackageTypeId::Wander)
return false;
return true;
});
return list; return list;
} }
std::map<int, MWWorld::Ptr> Actors::getActorsFollowingByIndex(const MWWorld::Ptr &actor)
{
std::map<int, MWWorld::Ptr> map;
forEachFollowingPackage(mActors, actor, getPlayer(), [&] (auto& iter, const std::unique_ptr<AiPackage>& package)
{
if (package->followTargetThroughDoors() && package->getTarget() == actor)
{
int index = static_cast<const AiFollow*>(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<MWWorld::Ptr> Actors::getActorsFighting(const MWWorld::Ptr& actor) { std::list<MWWorld::Ptr> Actors::getActorsFighting(const MWWorld::Ptr& actor) {
std::list<MWWorld::Ptr> list; std::list<MWWorld::Ptr> list;
std::vector<MWWorld::Ptr> neighbors; std::vector<MWWorld::Ptr> neighbors;

View file

@ -181,6 +181,7 @@ namespace MWMechanics
/// Get the list of AiFollow::mFollowIndex for all actors following this target /// Get the list of AiFollow::mFollowIndex for all actors following this target
std::list<int> getActorsFollowingIndices(const MWWorld::Ptr& actor); std::list<int> getActorsFollowingIndices(const MWWorld::Ptr& actor);
std::map<int, MWWorld::Ptr> getActorsFollowingByIndex(const MWWorld::Ptr& actor);
///Returns the list of actors which are fighting the given actor ///Returns the list of actors which are fighting the given actor
/**ie AiCombat is active and the target is the actor **/ /**ie AiCombat is active and the target is the actor **/

View file

@ -113,24 +113,23 @@ bool AiFollow::execute (const MWWorld::Ptr& actor, CharacterController& characte
if (!mActive) if (!mActive)
return false; return false;
// The distances below are approximations based on observations of the original engine. // In the original engine the first follower stays closer to the player than any subsequent followers.
// If only one actor is following the target, it uses 186. // Followers beyond the first usually attempt to stand inside each other.
// 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. osg::Vec3f::value_type floatingDistance = 0;
auto followers = MWBase::Environment::get().getMechanicsManager()->getActorsFollowingByIndex(target);
short followDistance = 186; if (followers.size() >= 2 && followers.cbegin()->first != mFollowIndex)
std::list<int> followers = MWBase::Environment::get().getMechanicsManager()->getActorsFollowingIndices(target);
if (followers.size() >= 2)
{ {
followDistance = 313; osg::Vec3f::value_type maxSize = 0;
short i = 0; for(auto& follower : followers)
followers.sort();
for (int followIndex : followers)
{ {
if (followIndex == mFollowIndex) auto halfExtent = MWBase::Environment::get().getWorld()->getHalfExtents(follower.second).y();
followDistance += 130 * i; if(halfExtent > floatingDistance)
++i; floatingDistance = halfExtent;
} }
} }
floatingDistance += MWBase::Environment::get().getWorld()->getHalfExtents(target).y();
floatingDistance += MWBase::Environment::get().getWorld()->getHalfExtents(actor).y() * 2;
short followDistance = static_cast<short>(floatingDistance);
if (!mAlwaysFollow) //Update if you only follow for a bit if (!mAlwaysFollow) //Update if you only follow for a bit
{ {

View file

@ -158,7 +158,7 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f&
zTurn(actor, getZAngleToPoint(position, dest)); zTurn(actor, getZAngleToPoint(position, dest));
smoothTurn(actor, getXAngleToPoint(position, dest), 0); smoothTurn(actor, getXAngleToPoint(position, dest), 0);
world->removeActorPath(actor); world->removeActorPath(actor);
return true; return isDestReached;
} }
world->updateActorPath(actor, mPathFinder.getPath(), halfExtents, position, dest); world->updateActorPath(actor, mPathFinder.getPath(), halfExtents, position, dest);

View file

@ -403,7 +403,7 @@ const AiPackage& MWMechanics::AiSequence::getActivePackage()
void AiSequence::fill(const ESM::AIPackageList &list) void AiSequence::fill(const ESM::AIPackageList &list)
{ {
// If there is more than one package in the list, enable repeating // 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; mRepeat = true;
for (const auto& esmPackage : list.mList) for (const auto& esmPackage : list.mList)
@ -459,8 +459,15 @@ void AiSequence::readState(const ESM::AiSequence::AiSequence &sequence)
int count = 0; int count = 0;
for (auto& container : sequence.mPackages) for (auto& container : sequence.mPackages)
{ {
if (isActualAiPackage(static_cast<AiPackageTypeId>(container.mType))) switch (container.mType)
count++; {
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) if (count > 1)

View file

@ -1965,7 +1965,7 @@ void CharacterController::update(float duration, bool animationOnly)
movementSettings.mSpeedFactor *= 2.f; movementSettings.mSpeedFactor *= 2.f;
static const bool smoothMovement = Settings::Manager::getBool("smooth movement", "Game"); 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")); 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]; float angle = mPtr.getRefData().getPosition().rot[2];
@ -1975,7 +1975,9 @@ void CharacterController::update(float duration, bool animationOnly)
float deltaLen = delta.length(); float deltaLen = delta.length();
float maxDelta; 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). // Turning is smooth for player and less smooth for NPCs (otherwise NPC can miss a path point).
maxDelta = duration * (isPlayer ? playerTurningCoef : 6.f); maxDelta = duration * (isPlayer ? playerTurningCoef : 6.f);
else if (isPlayer && speedDelta < -deltaLen / 2) else if (isPlayer && speedDelta < -deltaLen / 2)
@ -2013,7 +2015,10 @@ void CharacterController::update(float duration, bool animationOnly)
bool canMove = cls.getMaxSpeed(mPtr) > 0; bool canMove = cls.getMaxSpeed(mPtr) > 0;
static const bool turnToMovementDirection = Settings::Manager::getBool("turn to movement direction", "Game"); static const bool turnToMovementDirection = Settings::Manager::getBool("turn to movement direction", "Game");
if (!turnToMovementDirection || isFirstPersonPlayer) if (!turnToMovementDirection || isFirstPersonPlayer)
{
movementSettings.mIsStrafing = std::abs(vec.x()) > std::abs(vec.y()) * 2; movementSettings.mIsStrafing = std::abs(vec.x()) > std::abs(vec.y()) * 2;
stats.setSideMovementAngle(0);
}
else if (canMove) else if (canMove)
{ {
float targetMovementAngle = vec.y() >= 0 ? std::atan2(-vec.x(), vec.y()) : std::atan2(vec.x(), -vec.y()); float targetMovementAngle = vec.y() >= 0 ? std::atan2(-vec.x(), vec.y()) : std::atan2(vec.x(), -vec.y());
@ -2265,18 +2270,19 @@ void CharacterController::update(float duration, bool animationOnly)
sndMgr->playSound3D(mPtr, sound, 1.f, 1.f, MWSound::Type::Foot, MWSound::PlayMode::NoPlayerLocal); 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 swimmingPitch = mAnimation->getBodyPitchRadians();
float targetSwimmingPitch = -mPtr.getRefData().getPosition().rot[0];
float maxSwimPitchDelta = 3.0f * duration;
swimmingPitch += osg::clampBetween(targetSwimmingPitch - swimmingPitch, -maxSwimPitchDelta, maxSwimPitchDelta); swimmingPitch += osg::clampBetween(targetSwimmingPitch - swimmingPitch, -maxSwimPitchDelta, maxSwimPitchDelta);
mAnimation->setBodyPitchRadians(swimmingPitch); mAnimation->setBodyPitchRadians(swimmingPitch);
} }
else
mAnimation->setBodyPitchRadians(0);
static const bool swimUpwardCorrection = Settings::Manager::getBool("swim upward correction", "Game"); static const bool swimUpwardCorrection = Settings::Manager::getBool("swim upward correction", "Game");
if (inwater && isPlayer && !isFirstPersonPlayer && swimUpwardCorrection) if (inwater && isPlayer && !isFirstPersonPlayer && swimUpwardCorrection)
{ {
@ -2439,8 +2445,14 @@ void CharacterController::update(float duration, bool animationOnly)
} }
} }
if (mFloatToSurface && cls.isActor() && cls.getCreatureStats(mPtr).isDead() && cls.canSwim(mPtr)) if (mFloatToSurface && cls.isActor() && cls.canSwim(mPtr))
moved.z() = 1.0; {
if (cls.getCreatureStats(mPtr).isDead()
|| (!godmode && cls.getCreatureStats(mPtr).isParalyzed()))
{
moved.z() = 1.0;
}
}
// Update movement // Update movement
if(!animationOnly && mMovementAnimationControlled && mPtr.getClass().isActor()) if(!animationOnly && mMovementAnimationControlled && mPtr.getClass().isActor())

View file

@ -1654,6 +1654,11 @@ namespace MWMechanics
return mActors.getActorsFollowingIndices(actor); return mActors.getActorsFollowingIndices(actor);
} }
std::map<int, MWWorld::Ptr> MechanicsManager::getActorsFollowingByIndex(const MWWorld::Ptr& actor)
{
return mActors.getActorsFollowingByIndex(actor);
}
std::list<MWWorld::Ptr> MechanicsManager::getActorsFighting(const MWWorld::Ptr& actor) { std::list<MWWorld::Ptr> MechanicsManager::getActorsFighting(const MWWorld::Ptr& actor) {
return mActors.getActorsFighting(actor); return mActors.getActorsFighting(actor);
} }

View file

@ -150,6 +150,7 @@ namespace MWMechanics
std::list<MWWorld::Ptr> getActorsSidingWith(const MWWorld::Ptr& actor) override; std::list<MWWorld::Ptr> getActorsSidingWith(const MWWorld::Ptr& actor) override;
std::list<MWWorld::Ptr> getActorsFollowing(const MWWorld::Ptr& actor) override; std::list<MWWorld::Ptr> getActorsFollowing(const MWWorld::Ptr& actor) override;
std::list<int> getActorsFollowingIndices(const MWWorld::Ptr& actor) override; std::list<int> getActorsFollowingIndices(const MWWorld::Ptr& actor) override;
std::map<int, MWWorld::Ptr> getActorsFollowingByIndex(const MWWorld::Ptr& actor) override;
std::list<MWWorld::Ptr> getActorsFighting(const MWWorld::Ptr& actor) override; std::list<MWWorld::Ptr> getActorsFighting(const MWWorld::Ptr& actor) override;
std::list<MWWorld::Ptr> getEnemiesNearby(const MWWorld::Ptr& actor) override; std::list<MWWorld::Ptr> getEnemiesNearby(const MWWorld::Ptr& actor) override;

View file

@ -74,8 +74,9 @@ Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, Physic
updateRotation(); updateRotation();
updateScale(); updateScale();
resetPosition(); updatePosition();
addCollisionMask(getCollisionMask()); addCollisionMask(getCollisionMask());
updateCollisionObjectPosition();
} }
Actor::~Actor() Actor::~Actor()
@ -118,26 +119,34 @@ int Actor::getCollisionMask() const
return collisionMask; return collisionMask;
} }
void Actor::updatePositionUnsafe()
{
mWorldPosition = mPtr.getRefData().getPosition().asVec3();
}
void Actor::updatePosition() void Actor::updatePosition()
{ {
std::scoped_lock lock(mPositionMutex); 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 osg::Vec3f Actor::getWorldPosition() const
{ {
std::scoped_lock lock(mPositionMutex);
return mWorldPosition; return mWorldPosition;
} }
void Actor::setSimulationPosition(const osg::Vec3f& position) void Actor::setSimulationPosition(const osg::Vec3f& position)
{ {
mSimulationPosition = position; if (!mSkipSimulation)
mSimulationPosition = position;
mSkipSimulation = false;
} }
osg::Vec3f Actor::getSimulationPosition() const osg::Vec3f Actor::getSimulationPosition() const
@ -145,20 +154,16 @@ osg::Vec3f Actor::getSimulationPosition() const
return mSimulationPosition; return mSimulationPosition;
} }
void Actor::updateCollisionObjectPositionUnsafe() void Actor::updateCollisionObjectPosition()
{ {
std::scoped_lock lock(mPositionMutex);
mShape->setLocalScaling(Misc::Convert::toBullet(mScale)); mShape->setLocalScaling(Misc::Convert::toBullet(mScale));
osg::Vec3f scaledTranslation = mRotation * osg::componentMultiply(mMeshTranslation, mScale); osg::Vec3f scaledTranslation = mRotation * osg::componentMultiply(mMeshTranslation, mScale);
osg::Vec3f newPosition = scaledTranslation + mPosition; osg::Vec3f newPosition = scaledTranslation + mPosition;
mLocalTransform.setOrigin(Misc::Convert::toBullet(newPosition)); mLocalTransform.setOrigin(Misc::Convert::toBullet(newPosition));
mLocalTransform.setRotation(Misc::Convert::toBullet(mRotation)); mLocalTransform.setRotation(Misc::Convert::toBullet(mRotation));
mCollisionObject->setWorldTransform(mLocalTransform); mCollisionObject->setWorldTransform(mLocalTransform);
} mWorldPositionChanged = false;
void Actor::updateCollisionObjectPosition()
{
std::scoped_lock lock(mPositionMutex);
updateCollisionObjectPositionUnsafe();
} }
osg::Vec3f Actor::getCollisionObjectPosition() const osg::Vec3f Actor::getCollisionObjectPosition() const
@ -167,28 +172,20 @@ osg::Vec3f Actor::getCollisionObjectPosition() const
return Misc::Convert::toOsg(mLocalTransform.getOrigin()); 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); std::scoped_lock lock(mPositionMutex);
mPreviousPosition = mPosition; bool hasChanged = mPosition != position || mPositionOffset.length() != 0 || mWorldPositionChanged;
mPosition = position; mPreviousPosition = mPosition + mPositionOffset;
mPosition = position + mPositionOffset;
mPositionOffset = osg::Vec3f();
return hasChanged;
} }
void Actor::adjustPosition(const osg::Vec3f& offset) void Actor::adjustPosition(const osg::Vec3f& offset)
{ {
std::scoped_lock lock(mPositionMutex); std::scoped_lock lock(mPositionMutex);
mPosition += offset; mPositionOffset += offset;
mPreviousPosition += offset;
}
void Actor::resetPosition()
{
std::scoped_lock lock(mPositionMutex);
updatePositionUnsafe();
mPreviousPosition = mWorldPosition;
mPosition = mWorldPosition;
mSimulationPosition = mWorldPosition;
updateCollisionObjectPositionUnsafe();
} }
osg::Vec3f Actor::getPosition() const osg::Vec3f Actor::getPosition() const
@ -204,8 +201,6 @@ osg::Vec3f Actor::getPreviousPosition() const
void Actor::updateRotation () void Actor::updateRotation ()
{ {
std::scoped_lock lock(mPositionMutex); std::scoped_lock lock(mPositionMutex);
if (mRotation == mPtr.getRefData().getBaseNode()->getAttitude())
return;
mRotation = mPtr.getRefData().getBaseNode()->getAttitude(); mRotation = mPtr.getRefData().getBaseNode()->getAttitude();
} }
@ -236,7 +231,6 @@ osg::Vec3f Actor::getHalfExtents() const
osg::Vec3f Actor::getOriginalHalfExtents() const osg::Vec3f Actor::getOriginalHalfExtents() const
{ {
std::scoped_lock lock(mPositionMutex);
return mHalfExtents; return mHalfExtents;
} }
@ -273,7 +267,6 @@ void Actor::setWalkingOnWater(bool walkingOnWater)
void Actor::setCanWaterWalk(bool waterWalk) void Actor::setCanWaterWalk(bool waterWalk)
{ {
std::scoped_lock lock(mPositionMutex);
if (waterWalk != mCanWaterWalk) if (waterWalk != mCanWaterWalk)
{ {
mCanWaterWalk = waterWalk; mCanWaterWalk = waterWalk;

View file

@ -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 * 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. * when an object is "instantly" moved/teleported as opposed to being moved by the physics simulation.
*/ */
void updatePosition(); void updateWorldPosition();
osg::Vec3f getWorldPosition() const; osg::Vec3f getWorldPosition() const;
/** /**
@ -90,9 +90,10 @@ namespace MWPhysics
/** /**
* Store the current position into mPreviousPosition, then move to this position. * 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); bool setPosition(const osg::Vec3f& position);
void resetPosition(); void updatePosition();
void adjustPosition(const osg::Vec3f& offset); void adjustPosition(const osg::Vec3f& offset);
osg::Vec3f getPosition() const; osg::Vec3f getPosition() const;
@ -154,8 +155,6 @@ namespace MWPhysics
void updateCollisionMask(); void updateCollisionMask();
void addCollisionMask(int collisionMask); void addCollisionMask(int collisionMask);
int getCollisionMask() const; int getCollisionMask() const;
void updateCollisionObjectPositionUnsafe();
void updatePositionUnsafe();
bool mCanWaterWalk; bool mCanWaterWalk;
std::atomic<bool> mWalkingOnWater; std::atomic<bool> mWalkingOnWater;
@ -177,6 +176,9 @@ namespace MWPhysics
osg::Vec3f mSimulationPosition; osg::Vec3f mSimulationPosition;
osg::Vec3f mPosition; osg::Vec3f mPosition;
osg::Vec3f mPreviousPosition; osg::Vec3f mPreviousPosition;
osg::Vec3f mPositionOffset;
bool mWorldPositionChanged;
bool mSkipSimulation;
btTransform mLocalTransform; btTransform mLocalTransform;
mutable std::mutex mPositionMutex; mutable std::mutex mPositionMutex;

View file

@ -2,11 +2,6 @@
#include <BulletCollision/CollisionDispatch/btCollisionObject.h> #include <BulletCollision/CollisionDispatch/btCollisionObject.h>
#include <components/misc/convert.hpp>
#include "../mwbase/world.hpp"
#include "../mwbase/environment.hpp"
#include "collisiontype.hpp" #include "collisiontype.hpp"
#include "projectile.hpp" #include "projectile.hpp"

View file

@ -41,15 +41,21 @@ namespace MWPhysics
btCollisionWorld::ClosestRayResultCallback::addSingleResult(rayResult, normalInWorldSpace); btCollisionWorld::ClosestRayResultCallback::addSingleResult(rayResult, normalInWorldSpace);
if (mProjectile) if (mProjectile)
{ {
auto* holder = static_cast<PtrHolder*>(rayResult.m_collisionObject->getUserPointer()); switch (rayResult.m_collisionObject->getBroadphaseHandle()->m_collisionFilterGroup)
if (auto* target = dynamic_cast<Actor*>(holder))
{ {
mProjectile->hit(target->getPtr(), m_hitPointWorld, m_hitNormalWorld); case CollisionType_Actor:
} {
else if (auto* target = dynamic_cast<Projectile*>(holder)) auto* target = static_cast<Actor*>(rayResult.m_collisionObject->getUserPointer());
{ mProjectile->hit(target->getPtr(), m_hitPointWorld, m_hitNormalWorld);
target->hit(mProjectile->getPtr(), m_hitPointWorld, m_hitNormalWorld); break;
mProjectile->hit(target->getPtr(), m_hitPointWorld, m_hitNormalWorld); }
case CollisionType_Projectile:
{
auto* target = static_cast<Projectile*>(rayResult.m_collisionObject->getUserPointer());
target->hit(mProjectile->getPtr(), m_hitPointWorld, m_hitNormalWorld);
mProjectile->hit(target->getPtr(), m_hitPointWorld, m_hitNormalWorld);
break;
}
} }
} }

View file

@ -78,12 +78,14 @@ namespace MWPhysics
WorldFrameData& worldData) WorldFrameData& worldData)
{ {
auto* physicActor = actor.mActorRaw; auto* physicActor = actor.mActorRaw;
auto ptr = actor.mPtr;
const ESM::Position& refpos = actor.mRefpos; const ESM::Position& refpos = actor.mRefpos;
// Early-out for totally static creatures // Early-out for totally static creatures
// (Not sure if gravity should still apply?) // (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 // Reset per-frame data
physicActor->setWalkingOnWater(false); physicActor->setWalkingOnWater(false);
@ -128,8 +130,9 @@ namespace MWPhysics
velocity = velocity + inertia; velocity = velocity + inertia;
} }
// dead actors underwater will float to the surface, if the CharacterController tells us to do so // Dead and paralyzed actors underwater will float to the surface,
if (actor.mMovement.z() > 0 && actor.mIsDead && actor.mPosition.z() < swimlevel) // 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; velocity = osg::Vec3f(0,0,1) * 25;
if (actor.mWantJump) if (actor.mWantJump)
@ -211,6 +214,7 @@ namespace MWPhysics
if (result) if (result)
{ {
// don't let pure water creatures move out of water after stepMove // 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) if (ptr.getClass().isPureWaterCreature(ptr) && newPosition.z() + halfExtents.z() > actor.mWaterlevel)
newPosition = oldPosition; newPosition = oldPosition;
} }

View file

@ -87,12 +87,13 @@ namespace
void updateMechanics(MWPhysics::ActorFrameData& actorData) void updateMechanics(MWPhysics::ActorFrameData& actorData)
{ {
auto ptr = actorData.mActorRaw->getPtr();
if (actorData.mDidJump) 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) 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) else if (actorData.mFallHeight < 0)
stats.addToFallHeight(-actorData.mFallHeight); stats.addToFallHeight(-actorData.mFallHeight);
} }
@ -100,15 +101,6 @@ namespace
osg::Vec3f interpolateMovements(MWPhysics::ActorFrameData& actorData, float timeAccum, float physicsDt) osg::Vec3f interpolateMovements(MWPhysics::ActorFrameData& actorData, float timeAccum, float physicsDt)
{ {
const float interpolationFactor = timeAccum / 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); return actorData.mPosition * interpolationFactor + actorData.mActorRaw->getPreviousPosition() * (1.f - interpolationFactor);
} }
@ -213,45 +205,43 @@ namespace MWPhysics
thread.join(); thread.join();
} }
const PtrPositionList& PhysicsTaskScheduler::moveActors(int numSteps, float timeAccum, std::vector<ActorFrameData>&& actorsData, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) const std::vector<MWWorld::Ptr>& PhysicsTaskScheduler::moveActors(int numSteps, float timeAccum, std::vector<ActorFrameData>&& actorsData, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats)
{ {
// This function run in the main thread. // This function run in the main thread.
// While the mSimulationMutex is held, background physics threads can't run. // While the mSimulationMutex is held, background physics threads can't run.
std::unique_lock lock(mSimulationMutex); std::unique_lock lock(mSimulationMutex);
for (auto& data : actorsData) mMovedActors.clear();
data.updatePosition();
// start by finishing previous background computation // start by finishing previous background computation
if (mNumThreads != 0) if (mNumThreads != 0)
{ {
for (auto& data : mActorsFrameData) for (auto& data : mActorsFrameData)
{ {
// Ignore actors that were deleted while the background thread was running const auto actorActive = [&data](const auto& newFrameData) -> bool
if (!data.mActor.lock()) {
continue; 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); // these variables are accessed directly from the main thread, update them here to prevent accessing "too new" values
if (mAdvanceSimulation) if (mAdvanceSimulation)
data.mActorRaw->setStandingOnPtr(data.mStandingOn); data.mActorRaw->setStandingOnPtr(data.mStandingOn);
data.mActorRaw->setSimulationPosition(interpolateMovements(data, mTimeAccum, mPhysicsDt));
if (mMovementResults.find(data.mPtr) != mMovementResults.end()) mMovedActors.emplace_back(data.mActorRaw->getPtr());
data.mActorRaw->setSimulationPosition(mMovementResults[data.mPtr]); }
} }
updateStats(frameStart, frameNumber, 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;
} }
// init // init
for (auto& data : actorsData)
data.updatePosition();
mRemainingSteps = numSteps; mRemainingSteps = numSteps;
mTimeAccum = timeAccum; mTimeAccum = timeAccum;
mActorsFrameData = std::move(actorsData); mActorsFrameData = std::move(actorsData);
@ -266,52 +256,28 @@ namespace MWPhysics
if (mNumThreads == 0) if (mNumThreads == 0)
{ {
mMovementResults.clear();
syncComputation(); syncComputation();
return mMovedActors;
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;
} }
// 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(); lock.unlock();
mHasJob.notify_all(); mHasJob.notify_all();
return mPreviousMovementResults; return mMovedActors;
} }
const PtrPositionList& PhysicsTaskScheduler::resetSimulation(const ActorMap& actors) const std::vector<MWWorld::Ptr>& PhysicsTaskScheduler::resetSimulation(const ActorMap& actors)
{ {
std::unique_lock lock(mSimulationMutex); std::unique_lock lock(mSimulationMutex);
mMovementResults.clear(); mMovedActors.clear();
mPreviousMovementResults.clear();
mActorsFrameData.clear(); mActorsFrameData.clear();
for (const auto& [_, actor] : actors) for (const auto& [_, actor] : actors)
{ {
actor->resetPosition(); actor->updatePosition();
actor->setStandingOnPtr(nullptr); actor->setSimulationPosition(actor->getWorldPosition()); // updatePosition skip next simulation, now we need to "consume" it
mMovementResults[actor->getPtr()] = actor->getWorldPosition(); actor->updateCollisionObjectPosition();
mMovedActors.emplace_back(actor->getPtr());
} }
return mMovementResults; return mMovedActors;
} }
void PhysicsTaskScheduler::rayTest(const btVector3& rayFromWorld, const btVector3& rayToWorld, btCollisionWorld::RayResultCallback& resultCallback) const void PhysicsTaskScheduler::rayTest(const btVector3& rayFromWorld, const btVector3& rayToWorld, btCollisionWorld::RayResultCallback& resultCallback) const
@ -379,18 +345,18 @@ namespace MWPhysics
mCollisionWorld->removeCollisionObject(collisionObject); mCollisionWorld->removeCollisionObject(collisionObject);
} }
void PhysicsTaskScheduler::updateSingleAabb(std::weak_ptr<PtrHolder> ptr) void PhysicsTaskScheduler::updateSingleAabb(std::weak_ptr<PtrHolder> ptr, bool immediate)
{ {
if (mDeferAabbUpdate) if (!mDeferAabbUpdate || immediate)
{
std::unique_lock lock(mUpdateAabbMutex);
mUpdateAabb.insert(std::move(ptr));
}
else
{ {
std::unique_lock lock(mCollisionWorldMutex); std::unique_lock lock(mCollisionWorldMutex);
updatePtrAabb(ptr); updatePtrAabb(ptr);
} }
else
{
std::unique_lock lock(mUpdateAabbMutex);
mUpdateAabb.insert(std::move(ptr));
}
} }
bool PhysicsTaskScheduler::getLineOfSight(const std::weak_ptr<Actor>& actor1, const std::weak_ptr<Actor>& actor2) bool PhysicsTaskScheduler::getLineOfSight(const std::weak_ptr<Actor>& actor1, const std::weak_ptr<Actor>& actor2)
@ -493,7 +459,6 @@ namespace MWPhysics
{ {
auto& actorData = mActorsFrameData[job]; auto& actorData = mActorsFrameData[job];
handleFall(actorData, mAdvanceSimulation); handleFall(actorData, mAdvanceSimulation);
mMovementResults[actorData.mPtr] = interpolateMovements(actorData, mTimeAccum, mPhysicsDt);
} }
} }
@ -511,9 +476,7 @@ namespace MWPhysics
{ {
if(const auto actor = actorData.mActor.lock()) if(const auto actor = actorData.mActor.lock())
{ {
bool positionChanged = actorData.mPosition != actorData.mActorRaw->getPosition(); if (actor->setPosition(actorData.mPosition))
actorData.mActorRaw->setPosition(actorData.mPosition);
if (positionChanged)
{ {
actor->updateCollisionObjectPosition(); actor->updateCollisionObjectPosition();
mCollisionWorld->updateSingleAabb(actor->getCollisionObject()); mCollisionWorld->updateSingleAabb(actor->getCollisionObject());
@ -550,8 +513,24 @@ namespace MWPhysics
for (auto& actorData : mActorsFrameData) for (auto& actorData : mActorsFrameData)
{ {
handleFall(actorData, mAdvanceSimulation); handleFall(actorData, mAdvanceSimulation);
mMovementResults[actorData.mPtr] = interpolateMovements(actorData, mTimeAccum, mPhysicsDt); actorData.mActorRaw->setSimulationPosition(interpolateMovements(actorData, mTimeAccum, mPhysicsDt));
updateMechanics(actorData); 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;
}
} }

View file

@ -32,9 +32,9 @@ namespace MWPhysics
/// @param timeAccum accumulated time from previous run to interpolate movements /// @param timeAccum accumulated time from previous run to interpolate movements
/// @param actorsData per actor data needed to compute new positions /// @param actorsData per actor data needed to compute new positions
/// @return new position of each actor /// @return new position of each actor
const PtrPositionList& moveActors(int numSteps, float timeAccum, std::vector<ActorFrameData>&& actorsData, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats); const std::vector<MWWorld::Ptr>& moveActors(int numSteps, float timeAccum, std::vector<ActorFrameData>&& actorsData, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats);
const PtrPositionList& resetSimulation(const ActorMap& actors); const std::vector<MWWorld::Ptr>& resetSimulation(const ActorMap& actors);
// Thread safe wrappers // Thread safe wrappers
void rayTest(const btVector3& rayFromWorld, const btVector3& rayToWorld, btCollisionWorld::RayResultCallback& resultCallback) const; void rayTest(const btVector3& rayFromWorld, const btVector3& rayToWorld, btCollisionWorld::RayResultCallback& resultCallback) const;
@ -46,7 +46,7 @@ namespace MWPhysics
void setCollisionFilterMask(btCollisionObject* collisionObject, int collisionFilterMask); void setCollisionFilterMask(btCollisionObject* collisionObject, int collisionFilterMask);
void addCollisionObject(btCollisionObject* collisionObject, int collisionFilterGroup, int collisionFilterMask); void addCollisionObject(btCollisionObject* collisionObject, int collisionFilterGroup, int collisionFilterMask);
void removeCollisionObject(btCollisionObject* collisionObject); void removeCollisionObject(btCollisionObject* collisionObject);
void updateSingleAabb(std::weak_ptr<PtrHolder> ptr); void updateSingleAabb(std::weak_ptr<PtrHolder> ptr, bool immediate=false);
bool getLineOfSight(const std::weak_ptr<Actor>& actor1, const std::weak_ptr<Actor>& actor2); bool getLineOfSight(const std::weak_ptr<Actor>& actor1, const std::weak_ptr<Actor>& actor2);
private: private:
@ -57,11 +57,11 @@ namespace MWPhysics
void refreshLOSCache(); void refreshLOSCache();
void updateAabbs(); void updateAabbs();
void updatePtrAabb(const std::weak_ptr<PtrHolder>& ptr); void updatePtrAabb(const std::weak_ptr<PtrHolder>& ptr);
void updateStats(osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats);
std::unique_ptr<WorldFrameData> mWorldFrameData; std::unique_ptr<WorldFrameData> mWorldFrameData;
std::vector<ActorFrameData> mActorsFrameData; std::vector<ActorFrameData> mActorsFrameData;
PtrPositionList mMovementResults; std::vector<MWWorld::Ptr> mMovedActors;
PtrPositionList mPreviousMovementResults;
const float mPhysicsDt; const float mPhysicsDt;
float mTimeAccum; float mTimeAccum;
std::shared_ptr<btCollisionWorld> mCollisionWorld; std::shared_ptr<btCollisionWorld> mCollisionWorld;

View file

@ -437,7 +437,6 @@ namespace MWPhysics
ActorMap::iterator found = mActors.find(ptr); ActorMap::iterator found = mActors.find(ptr);
if (found == mActors.end()) if (found == mActors.end())
return ptr.getRefData().getPosition().asVec3(); return ptr.getRefData().getPosition().asVec3();
found->second->resetPosition();
return MovementSolver::traceDown(ptr, position, found->second.get(), mCollisionWorld.get(), maxHeight); return MovementSolver::traceDown(ptr, position, found->second.get(), mCollisionWorld.get(), maxHeight);
} }
@ -642,7 +641,7 @@ namespace MWPhysics
if (foundActor != mActors.end()) if (foundActor != mActors.end())
{ {
foundActor->second->updatePosition(); foundActor->second->updatePosition();
mTaskScheduler->updateSingleAabb(foundActor->second); mTaskScheduler->updateSingleAabb(foundActor->second, true);
return; return;
} }
} }
@ -711,7 +710,7 @@ namespace MWPhysics
mMovementQueue.clear(); mMovementQueue.clear();
} }
const PtrPositionList& PhysicsSystem::applyQueuedMovement(float dt, bool skipSimulation, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) const std::vector<MWWorld::Ptr>& PhysicsSystem::applyQueuedMovement(float dt, bool skipSimulation, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats)
{ {
mTimeAccum += dt; mTimeAccum += dt;
@ -770,7 +769,7 @@ namespace MWPhysics
if (numSteps == 0) if (numSteps == 0)
standingOn = physicActor->getStandingOnPtr(); 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(); mMovementQueue.clear();
return actorsFrameData; return actorsFrameData;
@ -912,25 +911,26 @@ namespace MWPhysics
mDebugDrawer->addCollision(position, normal); mDebugDrawer->addCollision(position, normal);
} }
ActorFrameData::ActorFrameData(const std::shared_ptr<Actor>& actor, const MWWorld::Ptr character, const MWWorld::Ptr standingOn, ActorFrameData::ActorFrameData(const std::shared_ptr<Actor>& actor, const MWWorld::Ptr standingOn,
bool moveToWaterSurface, osg::Vec3f movement, float slowFall, float waterlevel) bool moveToWaterSurface, osg::Vec3f movement, float slowFall, float waterlevel)
: mActor(actor), mActorRaw(actor.get()), mStandingOn(standingOn), : mActor(actor), mActorRaw(actor.get()), mStandingOn(standingOn),
mDidJump(false), mNeedLand(false), mMoveToWaterSurface(moveToWaterSurface), mDidJump(false), mNeedLand(false), mMoveToWaterSurface(moveToWaterSurface),
mWaterlevel(waterlevel), mSlowFall(slowFall), mOldHeight(0), mFallHeight(0), mMovement(movement), mPosition(), mRefpos() mWaterlevel(waterlevel), mSlowFall(slowFall), mOldHeight(0), mFallHeight(0), mMovement(movement), mPosition(), mRefpos()
{ {
const MWBase::World *world = MWBase::Environment::get().getWorld(); const MWBase::World *world = MWBase::Environment::get().getWorld();
mPtr = actor->getPtr(); const auto ptr = actor->getPtr();
mFlying = world->isFlying(character); mFlying = world->isFlying(ptr);
mSwimming = world->isSwimming(character); mSwimming = world->isSwimming(ptr);
mWantJump = mPtr.getClass().getMovementSettings(mPtr).mPosition[2] != 0; mWantJump = ptr.getClass().getMovementSettings(ptr).mPosition[2] != 0;
mIsDead = mPtr.getClass().getCreatureStats(mPtr).isDead(); auto& stats = ptr.getClass().getCreatureStats(ptr);
const bool godmode = ptr == world->getPlayerConstPtr() && world->getGodModeState();
mFloatToSurface = stats.isDead() || (!godmode && stats.isParalyzed());
mWasOnGround = actor->getOnGround(); mWasOnGround = actor->getOnGround();
} }
void ActorFrameData::updatePosition() void ActorFrameData::updatePosition()
{ {
mActorRaw->updatePosition(); mActorRaw->updateWorldPosition();
mOrigin = mActorRaw->getSimulationPosition();
mPosition = mActorRaw->getPosition(); mPosition = mActorRaw->getPosition();
if (mMoveToWaterSurface) if (mMoveToWaterSurface)
{ {
@ -938,7 +938,7 @@ namespace MWPhysics
mActorRaw->setPosition(mPosition); mActorRaw->setPosition(mPosition);
} }
mOldHeight = mPosition.z(); mOldHeight = mPosition.z();
mRefpos = mPtr.getRefData().getPosition(); mRefpos = mActorRaw->getPtr().getRefData().getPosition();
} }
WorldFrameData::WorldFrameData() WorldFrameData::WorldFrameData()

View file

@ -50,8 +50,6 @@ class btVector3;
namespace MWPhysics namespace MWPhysics
{ {
using PtrPositionList = std::map<MWWorld::Ptr, osg::Vec3f>;
class HeightField; class HeightField;
class Object; class Object;
class Actor; class Actor;
@ -80,18 +78,17 @@ namespace MWPhysics
struct ActorFrameData struct ActorFrameData
{ {
ActorFrameData(const std::shared_ptr<Actor>& actor, const MWWorld::Ptr character, const MWWorld::Ptr standingOn, bool moveToWaterSurface, osg::Vec3f movement, float slowFall, float waterlevel); ActorFrameData(const std::shared_ptr<Actor>& actor, const MWWorld::Ptr standingOn, bool moveToWaterSurface, osg::Vec3f movement, float slowFall, float waterlevel);
void updatePosition(); void updatePosition();
std::weak_ptr<Actor> mActor; std::weak_ptr<Actor> mActor;
Actor* mActorRaw; Actor* mActorRaw;
MWWorld::Ptr mPtr;
MWWorld::Ptr mStandingOn; MWWorld::Ptr mStandingOn;
bool mFlying; bool mFlying;
bool mSwimming; bool mSwimming;
bool mWasOnGround; bool mWasOnGround;
bool mWantJump; bool mWantJump;
bool mDidJump; bool mDidJump;
bool mIsDead; bool mFloatToSurface;
bool mNeedLand; bool mNeedLand;
bool mMoveToWaterSurface; bool mMoveToWaterSurface;
float mWaterlevel; float mWaterlevel;
@ -99,7 +96,6 @@ namespace MWPhysics
float mOldHeight; float mOldHeight;
float mFallHeight; float mFallHeight;
osg::Vec3f mMovement; osg::Vec3f mMovement;
osg::Vec3f mOrigin;
osg::Vec3f mPosition; osg::Vec3f mPosition;
ESM::Position mRefpos; ESM::Position mRefpos;
}; };
@ -210,7 +206,7 @@ namespace MWPhysics
void queueObjectMovement(const MWWorld::Ptr &ptr, const osg::Vec3f &velocity); void queueObjectMovement(const MWWorld::Ptr &ptr, const osg::Vec3f &velocity);
/// Apply all queued movements, then clear the list. /// 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<MWWorld::Ptr>& applyQueuedMovement(float dt, bool skipSimulation, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats);
/// Clear the queued movements list without applying. /// Clear the queued movements list without applying.
void clearQueuedMovement(); void clearQueuedMovement();

View file

@ -1,6 +1,8 @@
#ifndef OPENMW_MWPHYSICS_PTRHOLDER_H #ifndef OPENMW_MWPHYSICS_PTRHOLDER_H
#define OPENMW_MWPHYSICS_PTRHOLDER_H #define OPENMW_MWPHYSICS_PTRHOLDER_H
#include <mutex>
#include "../mwworld/ptr.hpp" #include "../mwworld/ptr.hpp"
namespace MWPhysics namespace MWPhysics
@ -12,21 +14,27 @@ namespace MWPhysics
void updatePtr(const MWWorld::Ptr& updated) void updatePtr(const MWWorld::Ptr& updated)
{ {
std::scoped_lock lock(mMutex);
mPtr = updated; mPtr = updated;
} }
MWWorld::Ptr getPtr() MWWorld::Ptr getPtr()
{ {
std::scoped_lock lock(mMutex);
return mPtr; return mPtr;
} }
MWWorld::ConstPtr getPtr() const MWWorld::ConstPtr getPtr() const
{ {
std::scoped_lock lock(mMutex);
return mPtr; return mPtr;
} }
protected: protected:
MWWorld::Ptr mPtr; MWWorld::Ptr mPtr;
private:
mutable std::mutex mMutex;
}; };
} }

View file

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

View file

@ -356,9 +356,6 @@ namespace MWRender
else else
mViewModeToggleQueued = false; mViewModeToggleQueued = false;
if (mTrackingPtr.getClass().isActor())
mTrackingPtr.getClass().getCreatureStats(mTrackingPtr).setSideMovementAngle(0);
mFirstPersonView = !mFirstPersonView; mFirstPersonView = !mFirstPersonView;
updateStandingPreviewMode(); updateStandingPreviewMode();
instantTransition(); instantTransition();

View file

@ -32,11 +32,7 @@ namespace MWScript
std::vector<MWWorld::Ptr> actors; std::vector<MWWorld::Ptr> actors;
MWBase::Environment::get().getWorld()->getActorsStandingOn (ptr, actors); MWBase::Environment::get().getWorld()->getActorsStandingOn (ptr, actors);
for (auto& actor : actors) for (auto& actor : actors)
{ MWBase::Environment::get().getWorld()->moveObjectBy(actor, diff);
osg::Vec3f actorPos(actor.getRefData().getPosition().asVec3());
actorPos += diff;
MWBase::Environment::get().getWorld()->moveObject(actor, actorPos.x(), actorPos.y(), actorPos.z());
}
} }
template<class R> template<class R>
@ -727,14 +723,12 @@ namespace MWScript
return; return;
osg::Vec3f diff = ptr.getRefData().getBaseNode()->getAttitude() * posChange; osg::Vec3f diff = ptr.getRefData().getBaseNode()->getAttitude() * posChange;
osg::Vec3f worldPos(ptr.getRefData().getPosition().asVec3());
worldPos += diff;
// We should move actors, standing on moving object, too. // We should move actors, standing on moving object, too.
// This approach can be used to create elevators. // This approach can be used to create elevators.
moveStandingActors(ptr, diff); moveStandingActors(ptr, diff);
dynamic_cast<MWScript::InterpreterContext&>(runtime.getContext()).updatePtr(ptr, dynamic_cast<MWScript::InterpreterContext&>(runtime.getContext()).updatePtr(ptr,
MWBase::Environment::get().getWorld()->moveObject(ptr, worldPos.x(), worldPos.y(), worldPos.z())); MWBase::Environment::get().getWorld()->moveObjectBy(ptr, diff));
} }
}; };
@ -755,15 +749,14 @@ namespace MWScript
Interpreter::Type_Float movement = (runtime[0].mFloat*MWBase::Environment::get().getFrameDuration()); Interpreter::Type_Float movement = (runtime[0].mFloat*MWBase::Environment::get().getFrameDuration());
runtime.pop(); runtime.pop();
const float *objPos = ptr.getRefData().getPosition().pos;
osg::Vec3f diff; osg::Vec3f diff;
if (axis == "x") if (axis == "x")
diff.x() += movement; diff.x() = movement;
else if (axis == "y") else if (axis == "y")
diff.y() += movement; diff.y() = movement;
else if (axis == "z") else if (axis == "z")
diff.z() += movement; diff.z() = movement;
else else
return; return;
@ -771,7 +764,7 @@ namespace MWScript
// This approach can be used to create elevators. // This approach can be used to create elevators.
moveStandingActors(ptr, diff); moveStandingActors(ptr, diff);
dynamic_cast<MWScript::InterpreterContext&>(runtime.getContext()).updatePtr(ptr, dynamic_cast<MWScript::InterpreterContext&>(runtime.getContext()).updatePtr(ptr,
MWBase::Environment::get().getWorld()->moveObject(ptr, objPos[0]+diff.x(), objPos[1]+diff.y(), objPos[2]+diff.z())); MWBase::Environment::get().getWorld()->moveObjectBy(ptr, diff));
} }
}; };

View file

@ -1251,6 +1251,18 @@ namespace MWWorld
return moveObjectImp(ptr, x, y, z, true, moveToActive); return moveObjectImp(ptr, x, y, z, true, moveToActive);
} }
MWWorld::Ptr World::moveObjectBy(const Ptr& ptr, osg::Vec3f vec)
{
auto* actor = mPhysics->getActor(ptr);
if (actor)
{
actor->adjustPosition(vec);
return ptr;
}
osg::Vec3f newpos = ptr.getRefData().getPosition().asVec3() + vec;
return moveObject(ptr, newpos.x(), newpos.y(), newpos.z());
}
void World::scaleObject (const Ptr& ptr, float scale) void World::scaleObject (const Ptr& ptr, float scale)
{ {
if (mPhysics->getActor(ptr)) if (mPhysics->getActor(ptr))
@ -1333,7 +1345,7 @@ namespace MWWorld
} }
const float terrainHeight = ptr.getCell()->isExterior() ? getTerrainHeightAt(pos) : -std::numeric_limits<float>::max(); const float terrainHeight = ptr.getCell()->isExterior() ? getTerrainHeightAt(pos) : -std::numeric_limits<float>::max();
pos.z() = std::max(pos.z(), terrainHeight + 20); // place slightly above terrain. will snap down to ground with code below pos.z() = std::max(pos.z(), terrainHeight) + 20; // place slightly above terrain. will snap down to ground with code below
// We still should trace down dead persistent actors - they do not use the "swimdeath" animation. // We still should trace down dead persistent actors - they do not use the "swimdeath" animation.
bool swims = ptr.getClass().isActor() && isSwimming(ptr) && !(ptr.getClass().isPersistent(ptr) && ptr.getClass().getCreatureStats(ptr).isDeathAnimationFinished()); bool swims = ptr.getClass().isActor() && isSwimming(ptr) && !(ptr.getClass().isPersistent(ptr) && ptr.getClass().getCreatureStats(ptr).isDeathAnimationFinished());
@ -1344,12 +1356,6 @@ namespace MWWorld
} }
moveObject(ptr, ptr.getCell(), pos.x(), pos.y(), pos.z()); moveObject(ptr, ptr.getCell(), pos.x(), pos.y(), pos.z());
if (ptr.getClass().isActor())
{
MWPhysics::Actor* actor = mPhysics->getActor(ptr);
if (actor)
actor->resetPosition();
}
} }
void World::fixPosition() void World::fixPosition()
@ -1500,16 +1506,26 @@ namespace MWWorld
mProjectileManager->processHits(); mProjectileManager->processHits();
mDiscardMovements = false; mDiscardMovements = false;
for(const auto& [actor, position]: results) for(const auto& actor : results)
{ {
// Handle player last, in case a cell transition occurs // Handle player last, in case a cell transition occurs
if(actor != getPlayerPtr()) if(actor != getPlayerPtr())
{
auto* physactor = mPhysics->getActor(actor);
assert(physactor);
const auto position = physactor->getSimulationPosition();
moveObjectImp(actor, position.x(), position.y(), position.z(), false); moveObjectImp(actor, position.x(), position.y(), position.z(), false);
}
} }
const auto player = results.find(getPlayerPtr()); const auto player = std::find(results.begin(), results.end(), getPlayerPtr());
if (player != results.end()) if (player != results.end())
moveObjectImp(player->first, player->second.x(), player->second.y(), player->second.z(), false); {
auto* physactor = mPhysics->getActor(*player);
assert(physactor);
const auto position = physactor->getSimulationPosition();
moveObjectImp(*player, position.x(), position.y(), position.z(), false);
}
} }
void World::updateNavigator() void World::updateNavigator()
@ -2286,8 +2302,12 @@ namespace MWWorld
if (stats.isDead()) if (stats.isDead())
return false; return false;
const bool isPlayer = ptr == getPlayerConstPtr();
if (!(isPlayer && mGodMode) && stats.isParalyzed())
return false;
if (ptr.getClass().canFly(ptr)) if (ptr.getClass().canFly(ptr))
return !stats.isParalyzed(); return true;
if(stats.getMagicEffects().get(ESM::MagicEffect::Levitate).getMagnitude() > 0 if(stats.getMagicEffects().get(ESM::MagicEffect::Levitate).getMagnitude() > 0
&& isLevitationEnabled()) && isLevitationEnabled())
@ -2897,7 +2917,7 @@ namespace MWWorld
mRendering->rebuildPtr(getPlayerPtr()); mRendering->rebuildPtr(getPlayerPtr());
} }
bool World::getGodModeState() bool World::getGodModeState() const
{ {
return mGodMode; return mGodMode;
} }

View file

@ -380,6 +380,9 @@ namespace MWWorld
MWWorld::Ptr moveObject (const Ptr& ptr, CellStore* newCell, float x, float y, float z, bool movePhysics=true) override; MWWorld::Ptr moveObject (const Ptr& ptr, CellStore* newCell, float x, float y, float z, bool movePhysics=true) override;
///< @return an updated Ptr ///< @return an updated Ptr
MWWorld::Ptr moveObjectBy(const Ptr& ptr, osg::Vec3f vec) override;
///< @return an updated Ptr
void scaleObject (const Ptr& ptr, float scale) override; void scaleObject (const Ptr& ptr, float scale) override;
/// World rotates object, uses radians /// World rotates object, uses radians
@ -616,7 +619,7 @@ namespace MWWorld
/// Returns true if levitation spell effect is allowed. /// Returns true if levitation spell effect is allowed.
bool isLevitationEnabled() const override; bool isLevitationEnabled() const override;
bool getGodModeState() override; bool getGodModeState() const override;
bool toggleGodMode() override; bool toggleGodMode() override;

View file

@ -1,5 +1,5 @@
find_package(GTest REQUIRED) find_package(GTest 1.10 REQUIRED)
find_package(GMock REQUIRED) find_package(GMock 1.10 REQUIRED)
if (GTEST_FOUND AND GMOCK_FOUND) if (GTEST_FOUND AND GMOCK_FOUND)
include_directories(SYSTEM ${GTEST_INCLUDE_DIRS}) include_directories(SYSTEM ${GTEST_INCLUDE_DIRS})

View file

@ -299,22 +299,22 @@ namespace
struct NifFileMock : Nif::File struct NifFileMock : Nif::File
{ {
MOCK_CONST_METHOD1(getRecord, Nif::Record* (std::size_t)); MOCK_METHOD(Nif::Record*, getRecord, (std::size_t), (const, override));
MOCK_CONST_METHOD0(numRecords, std::size_t ()); MOCK_METHOD(std::size_t, numRecords, (), (const, override));
MOCK_CONST_METHOD1(getRoot, Nif::Record* (std::size_t)); MOCK_METHOD(Nif::Record*, getRoot, (std::size_t), (const, override));
MOCK_CONST_METHOD0(numRoots, std::size_t ()); MOCK_METHOD(std::size_t, numRoots, (), (const, override));
MOCK_CONST_METHOD1(getString, std::string (uint32_t)); MOCK_METHOD(std::string, getString, (uint32_t), (const, override));
MOCK_METHOD1(setUseSkinning, void (bool)); MOCK_METHOD(void, setUseSkinning, (bool), (override));
MOCK_CONST_METHOD0(getUseSkinning, bool ()); MOCK_METHOD(bool, getUseSkinning, (), (const, override));
MOCK_CONST_METHOD0(getFilename, std::string ()); MOCK_METHOD(std::string, getFilename, (), (const, override));
MOCK_CONST_METHOD0(getVersion, unsigned int ()); MOCK_METHOD(unsigned int, getVersion, (), (const, override));
MOCK_CONST_METHOD0(getUserVersion, unsigned int ()); MOCK_METHOD(unsigned int, getUserVersion, (), (const, override));
MOCK_CONST_METHOD0(getBethVersion, unsigned int ()); MOCK_METHOD(unsigned int, getBethVersion, (), (const, override));
}; };
struct RecordMock : Nif::Record struct RecordMock : Nif::Record
{ {
MOCK_METHOD1(read, void (Nif::NIFStream *nif)); MOCK_METHOD(void, read, (Nif::NIFStream *nif), (override));
}; };
struct TestBulletNifLoader : Test struct TestBulletNifLoader : Test

View file

@ -151,7 +151,13 @@ add_component_dir (fallback
fallback validate fallback validate
) )
if(NOT WIN32 AND NOT ANDROID) if(WIN32)
add_component_dir (crashcatcher
windows_crashcatcher
windows_crashmonitor
windows_crashshm
)
elseif(NOT ANDROID)
add_component_dir (crashcatcher add_component_dir (crashcatcher
crashcatcher crashcatcher
) )

View file

@ -0,0 +1,205 @@
#include <cassert>
#include <cwchar>
#include <iostream>
#include <sstream>
#include <thread>
#include "windows_crashcatcher.hpp"
#include "windows_crashmonitor.hpp"
#include "windows_crashshm.hpp"
#include <SDL_messagebox.h>
namespace Crash
{
HANDLE duplicateHandle(HANDLE handle)
{
HANDLE duplicate;
if (!DuplicateHandle(GetCurrentProcess(), handle,
GetCurrentProcess(), &duplicate,
0, TRUE, DUPLICATE_SAME_ACCESS))
{
throw std::runtime_error("Crash monitor could not duplicate handle");
}
return duplicate;
}
CrashCatcher* CrashCatcher::sInstance = nullptr;
CrashCatcher::CrashCatcher(int argc, char **argv, const std::string& crashLogPath)
{
assert(sInstance == nullptr); // don't allow two instances
sInstance = this;
HANDLE shmHandle = nullptr;
for (int i=0; i<argc; ++i)
{
if (strcmp(argv[i], "--crash-monitor"))
continue;
if (i >= argc - 1)
throw std::runtime_error("Crash monitor is missing the SHM handle argument");
sscanf(argv[i + 1], "%p", &shmHandle);
break;
}
if (!shmHandle)
{
setupIpc();
startMonitorProcess(crashLogPath);
installHandler();
}
else
{
CrashMonitor(shmHandle).run();
exit(0);
}
}
CrashCatcher::~CrashCatcher()
{
sInstance = nullptr;
if (mShm && mSignalMonitorEvent)
{
shmLock();
mShm->mEvent = CrashSHM::Event::Shutdown;
shmUnlock();
SetEvent(mSignalMonitorEvent);
}
if (mShmHandle)
CloseHandle(mShmHandle);
}
void CrashCatcher::setupIpc()
{
SECURITY_ATTRIBUTES attributes;
ZeroMemory(&attributes, sizeof(attributes));
attributes.bInheritHandle = TRUE;
mSignalAppEvent = CreateEventW(&attributes, FALSE, FALSE, NULL);
mSignalMonitorEvent = CreateEventW(&attributes, FALSE, FALSE, NULL);
mShmHandle = CreateFileMappingW(INVALID_HANDLE_VALUE, &attributes, PAGE_READWRITE, HIWORD(sizeof(CrashSHM)), LOWORD(sizeof(CrashSHM)), NULL);
if (mShmHandle == nullptr)
throw std::runtime_error("Failed to allocate crash catcher shared memory");
mShm = reinterpret_cast<CrashSHM*>(MapViewOfFile(mShmHandle, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(CrashSHM)));
if (mShm == nullptr)
throw std::runtime_error("Failed to map crash catcher shared memory");
mShmMutex = CreateMutexW(&attributes, FALSE, NULL);
if (mShmMutex == nullptr)
throw std::runtime_error("Failed to create crash catcher shared memory mutex");
}
void CrashCatcher::shmLock()
{
if (WaitForSingleObject(mShmMutex, CrashCatcherTimeout) != WAIT_OBJECT_0)
throw std::runtime_error("SHM lock timed out");
}
void CrashCatcher::shmUnlock()
{
ReleaseMutex(mShmMutex);
}
void CrashCatcher::waitMonitor()
{
if (WaitForSingleObject(mSignalAppEvent, CrashCatcherTimeout) != WAIT_OBJECT_0)
throw std::runtime_error("Waiting for monitor failed");
}
void CrashCatcher::signalMonitor()
{
SetEvent(mSignalMonitorEvent);
}
void CrashCatcher::installHandler()
{
SetUnhandledExceptionFilter(vectoredExceptionHandler);
}
void CrashCatcher::startMonitorProcess(const std::string& crashLogPath)
{
std::wstring executablePath;
DWORD copied = 0;
do {
executablePath.resize(executablePath.size() + MAX_PATH);
copied = GetModuleFileNameW(nullptr, executablePath.data(), executablePath.size());
} while (copied >= executablePath.size());
executablePath.resize(copied);
memset(mShm->mStartup.mLogFilePath, 0, sizeof(mShm->mStartup.mLogFilePath));
int length = crashLogPath.length();
if (length > MAX_LONG_PATH) length = MAX_LONG_PATH;
strncpy(mShm->mStartup.mLogFilePath, crashLogPath.c_str(), length);
mShm->mStartup.mLogFilePath[length] = '\0';
// note that we don't need to lock the SHM here, the other process has not started yet
mShm->mEvent = CrashSHM::Event::Startup;
mShm->mStartup.mShmMutex = duplicateHandle(mShmMutex);
mShm->mStartup.mAppProcessHandle = duplicateHandle(GetCurrentProcess());
mShm->mStartup.mSignalApp = duplicateHandle(mSignalAppEvent);
mShm->mStartup.mSignalMonitor = duplicateHandle(mSignalMonitorEvent);
std::wstringstream ss;
ss << "--crash-monitor " << std::hex << duplicateHandle(mShmHandle);
std::wstring arguments(ss.str());
STARTUPINFOW si;
ZeroMemory(&si, sizeof(si));
PROCESS_INFORMATION pi;
ZeroMemory(&pi, sizeof(pi));
if (!CreateProcessW(executablePath.data(), arguments.data(), NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi))
throw std::runtime_error("Could not start crash monitor process");
waitMonitor();
}
LONG CrashCatcher::vectoredExceptionHandler(PEXCEPTION_POINTERS info)
{
switch (info->ExceptionRecord->ExceptionCode)
{
case EXCEPTION_SINGLE_STEP:
case EXCEPTION_BREAKPOINT:
case DBG_PRINTEXCEPTION_C:
return EXCEPTION_EXECUTE_HANDLER;
}
if (!sInstance)
return EXCEPTION_EXECUTE_HANDLER;
sInstance->handleVectoredException(info);
_Exit(1);
return EXCEPTION_CONTINUE_SEARCH;
}
void CrashCatcher::handleVectoredException(PEXCEPTION_POINTERS info)
{
shmLock();
mShm->mEvent = CrashSHM::Event::Crashed;
mShm->mCrashed.mThreadId = GetCurrentThreadId();
mShm->mCrashed.mContext = *info->ContextRecord;
mShm->mCrashed.mExceptionRecord = *info->ExceptionRecord;
shmUnlock();
signalMonitor();
// must remain until monitor has finished
waitMonitor();
std::string message = "OpenMW has encountered a fatal error.\nCrash log saved to '" + std::string(mShm->mStartup.mLogFilePath) + "'.\n Please report this to https://gitlab.com/OpenMW/openmw/issues !";
SDL_ShowSimpleMessageBox(0, "Fatal Error", message.c_str(), nullptr);
}
} // namespace Crash

View file

@ -0,0 +1,79 @@
#ifndef WINDOWS_CRASHCATCHER_HPP
#define WINDOWS_CRASHCATCHER_HPP
#include <string>
#undef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <components/crashcatcher/crashcatcher.hpp>
namespace Crash
{
// The implementation spawns the current executable as a monitor process which waits
// for a global synchronization event which is sent when the parent process crashes.
// The monitor process then extracts crash information from the parent process while
// the parent process waits for the monitor process to finish. The crashed process
// quits and the monitor writes the crash information to a file.
//
// To detect unexpected shutdowns of the application which are not handled by the
// crash handler, the monitor periodically checks the exit code of the parent
// process and exits if it does not return STILL_ACTIVE. You can test this by closing
// the main openmw process in task manager.
static constexpr const int CrashCatcherTimeout = 2500;
struct CrashSHM;
class CrashCatcher final
{
public:
CrashCatcher(int argc, char **argv, const std::string& crashLogPath);
~CrashCatcher();
private:
static CrashCatcher* sInstance;
// mapped SHM area
CrashSHM* mShm = nullptr;
// the handle is allocated by the catcher and passed to the monitor
// process via the command line which maps the SHM and sends / receives
// events through it
HANDLE mShmHandle = nullptr;
// mutex which guards SHM area
HANDLE mShmMutex = nullptr;
// triggered when the monitor signals the application
HANDLE mSignalAppEvent = INVALID_HANDLE_VALUE;
// triggered when the application wants to wake the monitor process
HANDLE mSignalMonitorEvent = INVALID_HANDLE_VALUE;
void setupIpc();
void shmLock();
void shmUnlock();
void startMonitorProcess(const std::string& crashLogPath);
void waitMonitor();
void signalMonitor();
void installHandler();
void handleVectoredException(PEXCEPTION_POINTERS info);
public:
static LONG WINAPI vectoredExceptionHandler(PEXCEPTION_POINTERS info);
};
} // namespace Crash
#endif // WINDOWS_CRASHCATCHER_HPP

View file

@ -0,0 +1,188 @@
#undef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <Psapi.h>
#include <DbgHelp.h>
#include <iostream>
#include <memory>
#include <sstream>
#include "windows_crashcatcher.hpp"
#include "windows_crashmonitor.hpp"
#include "windows_crashshm.hpp"
#include <components/debug/debuglog.hpp>
namespace Crash
{
CrashMonitor::CrashMonitor(HANDLE shmHandle)
: mShmHandle(shmHandle)
{
mShm = reinterpret_cast<CrashSHM*>(MapViewOfFile(mShmHandle, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(CrashSHM)));
if (mShm == nullptr)
throw std::runtime_error("Failed to map crash monitor shared memory");
// accessing SHM without lock is OK here, the parent waits for a signal before continuing
mShmMutex = mShm->mStartup.mShmMutex;
mAppProcessHandle = mShm->mStartup.mAppProcessHandle;
mSignalAppEvent = mShm->mStartup.mSignalApp;
mSignalMonitorEvent = mShm->mStartup.mSignalMonitor;
}
CrashMonitor::~CrashMonitor()
{
if (mShm)
UnmapViewOfFile(mShm);
// the handles received from the app are duplicates, we must close them
if (mShmHandle)
CloseHandle(mShmHandle);
if (mShmMutex)
CloseHandle(mShmMutex);
if (mSignalAppEvent)
CloseHandle(mSignalAppEvent);
if (mSignalMonitorEvent)
CloseHandle(mSignalMonitorEvent);
}
void CrashMonitor::shmLock()
{
if (WaitForSingleObject(mShmMutex, CrashCatcherTimeout) != WAIT_OBJECT_0)
throw std::runtime_error("SHM monitor lock timed out");
}
void CrashMonitor::shmUnlock()
{
ReleaseMutex(mShmMutex);
}
void CrashMonitor::signalApp() const
{
SetEvent(mSignalAppEvent);
}
bool CrashMonitor::waitApp() const
{
return WaitForSingleObject(mSignalMonitorEvent, CrashCatcherTimeout) == WAIT_OBJECT_0;
}
bool CrashMonitor::isAppAlive() const
{
DWORD code = 0;
GetExitCodeProcess(mAppProcessHandle, &code);
return code == STILL_ACTIVE;
}
void CrashMonitor::run()
{
try
{
// app waits for monitor start up, let it continue
signalApp();
bool running = true;
while (isAppAlive() && running)
{
if (waitApp())
{
shmLock();
switch (mShm->mEvent)
{
case CrashSHM::Event::None:
break;
case CrashSHM::Event::Crashed:
handleCrash();
running = false;
break;
case CrashSHM::Event::Shutdown:
running = false;
break;
case CrashSHM::Event::Startup:
break;
}
shmUnlock();
}
}
}
catch (...)
{
Log(Debug::Error) << "Exception in crash monitor, exiting";
}
signalApp();
}
std::wstring utf8ToUtf16(const std::string& utf8)
{
const int nLenWide = MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), utf8.size(), nullptr, 0);
std::wstring utf16;
utf16.resize(nLenWide);
if (MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), utf8.size(), utf16.data(), nLenWide) != nLenWide)
return {};
return utf16;
}
void CrashMonitor::handleCrash()
{
DWORD processId = GetProcessId(mAppProcessHandle);
try
{
HMODULE dbghelp = LoadLibraryA("dbghelp.dll");
if (dbghelp == NULL)
return;
using MiniDumpWirteDumpFn = BOOL (WINAPI*)(
HANDLE hProcess, DWORD ProcessId, HANDLE hFile, MINIDUMP_TYPE DumpType, PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam,
PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam, PMINIDUMP_CALLBACK_INFORMATION CallbackParam
);
MiniDumpWirteDumpFn miniDumpWriteDump = (MiniDumpWirteDumpFn)GetProcAddress(dbghelp, "MiniDumpWriteDump");
if (miniDumpWriteDump == NULL)
return;
std::wstring utf16Path = utf8ToUtf16(mShm->mStartup.mLogFilePath);
if (utf16Path.empty())
return;
if (utf16Path.length() > MAX_PATH)
utf16Path = LR"(\\?\)" + utf16Path;
HANDLE hCrashLog = CreateFileW(utf16Path.c_str(), GENERIC_READ | GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
if (hCrashLog == NULL || hCrashLog == INVALID_HANDLE_VALUE)
return;
if (auto err = GetLastError(); err != ERROR_ALREADY_EXISTS && err != 0)
return;
EXCEPTION_POINTERS exp;
exp.ContextRecord = &mShm->mCrashed.mContext;
exp.ExceptionRecord = &mShm->mCrashed.mExceptionRecord;
MINIDUMP_EXCEPTION_INFORMATION infos = {};
infos.ThreadId = mShm->mCrashed.mThreadId;
infos.ExceptionPointers = &exp;
infos.ClientPointers = FALSE;
MINIDUMP_TYPE type = (MINIDUMP_TYPE)(MiniDumpWithDataSegs | MiniDumpWithHandleData);
miniDumpWriteDump(mAppProcessHandle, processId, hCrashLog, type, &infos, 0, 0);
}
catch (const std::exception&e)
{
Log(Debug::Error) << "CrashMonitor: " << e.what();
}
catch (...)
{
Log(Debug::Error) << "CrashMonitor: unknown exception";
}
}
} // namespace Crash

View file

@ -0,0 +1,49 @@
#ifndef WINDOWS_CRASHMONITOR_HPP
#define WINDOWS_CRASHMONITOR_HPP
#include <windef.h>
namespace Crash
{
struct CrashSHM;
class CrashMonitor final
{
public:
CrashMonitor(HANDLE shmHandle);
~CrashMonitor();
void run();
private:
HANDLE mAppProcessHandle = nullptr;
// triggered when the monitor process wants to wake the parent process (received via SHM)
HANDLE mSignalAppEvent = nullptr;
// triggered when the application wants to wake the monitor process (received via SHM)
HANDLE mSignalMonitorEvent = nullptr;
CrashSHM* mShm = nullptr;
HANDLE mShmHandle = nullptr;
HANDLE mShmMutex = nullptr;
void signalApp() const;
bool waitApp() const;
bool isAppAlive() const;
void shmLock();
void shmUnlock();
void handleCrash();
};
} // namespace Crash
#endif // WINDOWS_CRASHMONITOR_HPP

View file

@ -0,0 +1,45 @@
#ifndef WINDOWS_CRASHSHM_HPP
#define WINDOWS_CRASHSHM_HPP
#undef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
namespace Crash
{
// Used to communicate between the app and the monitor, fields are is overwritten with each event.
static constexpr const int MAX_LONG_PATH = 0x7fff;
struct CrashSHM
{
enum class Event
{
None,
Startup,
Crashed,
Shutdown
};
Event mEvent;
struct Startup
{
HANDLE mAppProcessHandle;
HANDLE mSignalApp;
HANDLE mSignalMonitor;
HANDLE mShmMutex;
char mLogFilePath[MAX_LONG_PATH];
} mStartup;
struct Crashed
{
DWORD mThreadId;
CONTEXT mContext;
EXCEPTION_RECORD mExceptionRecord;
} mCrashed;
};
} // namespace Crash
#endif // WINDOWS_CRASHSHM_HPP

View file

@ -2,10 +2,12 @@
#include <chrono> #include <chrono>
#include <memory> #include <memory>
#include <functional>
#include <components/crashcatcher/crashcatcher.hpp> #include <components/crashcatcher/crashcatcher.hpp>
#ifdef _WIN32 #ifdef _WIN32
# include <components/crashcatcher/windows_crashcatcher.hpp>
# undef WIN32_LEAN_AND_MEAN # undef WIN32_LEAN_AND_MEAN
# define WIN32_LEAN_AND_MEAN # define WIN32_LEAN_AND_MEAN
# include <windows.h> # include <windows.h>
@ -163,7 +165,6 @@ int wrapApplication(int (*innerApplication)(int argc, char *argv[]), int argc, c
#endif #endif
const std::string logName = Misc::StringUtils::lowerCase(appName) + ".log"; const std::string logName = Misc::StringUtils::lowerCase(appName) + ".log";
const std::string crashLogName = Misc::StringUtils::lowerCase(appName) + "-crash.log";
boost::filesystem::ofstream logfile; boost::filesystem::ofstream logfile;
int ret = 0; int ret = 0;
@ -187,13 +188,18 @@ int wrapApplication(int (*innerApplication)(int argc, char *argv[]), int argc, c
std::cerr.rdbuf (&cerrsb); std::cerr.rdbuf (&cerrsb);
#endif #endif
#if defined(_WIN32)
const std::string crashLogName = Misc::StringUtils::lowerCase(appName) + "-crash.dmp";
Crash::CrashCatcher crashy(argc, argv, (cfgMgr.getLogPath() / crashLogName).make_preferred().string());
#else
const std::string crashLogName = Misc::StringUtils::lowerCase(appName) + "-crash.log";
// install the crash handler as soon as possible. note that the log path // install the crash handler as soon as possible. note that the log path
// does not depend on config being read. // does not depend on config being read.
crashCatcherInstall(argc, argv, (cfgMgr.getLogPath() / crashLogName).string()); crashCatcherInstall(argc, argv, (cfgMgr.getLogPath() / crashLogName).string());
#endif
ret = innerApplication(argc, argv); ret = innerApplication(argc, argv);
} }
catch (std::exception& e) catch (const std::exception& e)
{ {
#if (defined(__APPLE__) || defined(__linux) || defined(__unix) || defined(__posix)) #if (defined(__APPLE__) || defined(__linux) || defined(__unix) || defined(__posix))
if (!isatty(fileno(stdin))) if (!isatty(fileno(stdin)))

View file

@ -1245,9 +1245,12 @@ namespace NifOsg
void handleGeometry(const Nif::Node* nifNode, osg::Group* parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector<unsigned int>& boundTextures, int animflags) void handleGeometry(const Nif::Node* nifNode, osg::Group* parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector<unsigned int>& boundTextures, int animflags)
{ {
assert(isTypeGeometry(nifNode->recType)); assert(isTypeGeometry(nifNode->recType));
osg::ref_ptr<osg::Drawable> drawable;
osg::ref_ptr<osg::Geometry> geom (new osg::Geometry); osg::ref_ptr<osg::Geometry> geom (new osg::Geometry);
handleNiGeometry(nifNode, geom, parentNode, composite, boundTextures, animflags); handleNiGeometry(nifNode, geom, parentNode, composite, boundTextures, animflags);
// If the record had no valid geometry data in it, early-out
if (geom->empty())
return;
osg::ref_ptr<osg::Drawable> drawable;
for (Nif::ControllerPtr ctrl = nifNode->controller; !ctrl.empty(); ctrl = ctrl->next) for (Nif::ControllerPtr ctrl = nifNode->controller; !ctrl.empty(); ctrl = ctrl->next)
{ {
if (!(ctrl->flags & Nif::NiNode::ControllerFlag_Active)) if (!(ctrl->flags & Nif::NiNode::ControllerFlag_Active))
@ -1292,6 +1295,8 @@ namespace NifOsg
assert(isTypeGeometry(nifNode->recType)); assert(isTypeGeometry(nifNode->recType));
osg::ref_ptr<osg::Geometry> geometry (new osg::Geometry); osg::ref_ptr<osg::Geometry> geometry (new osg::Geometry);
handleNiGeometry(nifNode, geometry, parentNode, composite, boundTextures, animflags); handleNiGeometry(nifNode, geometry, parentNode, composite, boundTextures, animflags);
if (geometry->empty())
return;
osg::ref_ptr<SceneUtil::RigGeometry> rig(new SceneUtil::RigGeometry); osg::ref_ptr<SceneUtil::RigGeometry> rig(new SceneUtil::RigGeometry);
rig->setSourceGeometry(geometry); rig->setSourceGeometry(geometry);
rig->setName(nifNode->name); rig->setName(nifNode->name);

View file

@ -26,7 +26,7 @@ Now Fonts folder should include ``openmw_font.xml`` file and three ``.ttf`` file
If desired, you can now delete the ``Data Files/Fonts`` directory. If desired, you can now delete the ``Data Files/Fonts`` directory.
It is also possible to adjust the font size and resolution:: It is also possible to adjust the font size and resolution via ``settings.cfg`` file::
[GUI] [GUI]
font size = 16 font size = 16

View file

@ -23,6 +23,7 @@ set(SHADER_FILES
shadows_fragment.glsl shadows_fragment.glsl
shadowcasting_vertex.glsl shadowcasting_vertex.glsl
shadowcasting_fragment.glsl shadowcasting_fragment.glsl
vertexcolors.glsl
) )
copy_all_resource_files(${CMAKE_CURRENT_SOURCE_DIR} ${OPENMW_SHADERS_ROOT} ${DDIRRELATIVE} "${SHADER_FILES}") copy_all_resource_files(${CMAKE_CURRENT_SOURCE_DIR} ${OPENMW_SHADERS_ROOT} ${DDIRRELATIVE} "${SHADER_FILES}")

View file

@ -1,85 +1,38 @@
#define MAX_LIGHTS 8 #define MAX_LIGHTS 8
uniform int colorMode; void perLight(out vec3 ambientOut, out vec3 diffuseOut, int lightIndex, vec3 viewPos, vec3 viewNormal)
const int ColorMode_None = 0;
const int ColorMode_Emission = 1;
const int ColorMode_AmbientAndDiffuse = 2;
const int ColorMode_Ambient = 3;
const int ColorMode_Diffuse = 4;
const int ColorMode_Specular = 5;
void perLight(out vec3 ambientOut, out vec3 diffuseOut, int lightIndex, vec3 viewPos, vec3 viewNormal, vec4 diffuse, vec3 ambient)
{ {
vec3 lightDir; vec3 lightDir = gl_LightSource[lightIndex].position.xyz - viewPos * gl_LightSource[lightIndex].position.w;
float lightDistance; float lightDistance = length(lightDir);
lightDir = gl_LightSource[lightIndex].position.xyz - (viewPos.xyz * gl_LightSource[lightIndex].position.w);
lightDistance = length(lightDir);
lightDir = normalize(lightDir); lightDir = normalize(lightDir);
float illumination = clamp(1.0 / (gl_LightSource[lightIndex].constantAttenuation + gl_LightSource[lightIndex].linearAttenuation * lightDistance + gl_LightSource[lightIndex].quadraticAttenuation * lightDistance * lightDistance), 0.0, 1.0); float illumination = clamp(1.0 / (gl_LightSource[lightIndex].constantAttenuation + gl_LightSource[lightIndex].linearAttenuation * lightDistance + gl_LightSource[lightIndex].quadraticAttenuation * lightDistance * lightDistance), 0.0, 1.0);
ambientOut = ambient * gl_LightSource[lightIndex].ambient.xyz * illumination; ambientOut = gl_LightSource[lightIndex].ambient.xyz * illumination;
diffuseOut = diffuse.xyz * gl_LightSource[lightIndex].diffuse.xyz * max(dot(viewNormal.xyz, lightDir), 0.0) * illumination; diffuseOut = gl_LightSource[lightIndex].diffuse.xyz * max(dot(viewNormal, lightDir), 0.0) * illumination;
} }
#if PER_PIXEL_LIGHTING #if PER_PIXEL_LIGHTING
vec4 doLighting(vec3 viewPos, vec3 viewNormal, vec4 vertexColor, float shadowing) void doLighting(vec3 viewPos, vec3 viewNormal, float shadowing, out vec3 diffuseLight, out vec3 ambientLight)
#else #else
vec4 doLighting(vec3 viewPos, vec3 viewNormal, vec4 vertexColor, out vec3 shadowDiffuse) void doLighting(vec3 viewPos, vec3 viewNormal, out vec3 diffuseLight, out vec3 ambientLight, out vec3 shadowDiffuse)
#endif #endif
{ {
vec4 diffuse; vec3 ambientOut, diffuseOut;
vec3 ambient; // This light gets added a second time in the loop to fix Mesa users' slowdown, so we need to negate its contribution here.
if (colorMode == ColorMode_AmbientAndDiffuse) perLight(ambientOut, diffuseOut, 0, viewPos, viewNormal);
{
diffuse = vertexColor;
ambient = vertexColor.xyz;
}
else if (colorMode == ColorMode_Diffuse)
{
diffuse = vertexColor;
ambient = gl_FrontMaterial.ambient.xyz;
}
else if (colorMode == ColorMode_Ambient)
{
diffuse = gl_FrontMaterial.diffuse;
ambient = vertexColor.xyz;
}
else
{
diffuse = gl_FrontMaterial.diffuse;
ambient = gl_FrontMaterial.ambient.xyz;
}
vec4 lightResult = vec4(0.0, 0.0, 0.0, diffuse.a);
vec3 diffuseLight, ambientLight;
perLight(ambientLight, diffuseLight, 0, viewPos, viewNormal, diffuse, ambient);
#if PER_PIXEL_LIGHTING #if PER_PIXEL_LIGHTING
lightResult.xyz += diffuseLight * shadowing - diffuseLight; // This light gets added a second time in the loop to fix Mesa users' slowdown, so we need to negate its contribution here. diffuseLight = diffuseOut * shadowing - diffuseOut;
#else #else
shadowDiffuse = diffuseLight; shadowDiffuse = diffuseOut;
lightResult.xyz -= shadowDiffuse; // This light gets added a second time in the loop to fix Mesa users' slowdown, so we need to negate its contribution here. diffuseLight = -diffuseOut;
#endif #endif
ambientLight = gl_LightModel.ambient.xyz;
for (int i=0; i<MAX_LIGHTS; ++i) for (int i=0; i<MAX_LIGHTS; ++i)
{ {
perLight(ambientLight, diffuseLight, i, viewPos, viewNormal, diffuse, ambient); perLight(ambientOut, diffuseOut, i, viewPos, viewNormal);
lightResult.xyz += ambientLight + diffuseLight; ambientLight += ambientOut;
diffuseLight += diffuseOut;
} }
lightResult.xyz += gl_LightModel.ambient.xyz * ambient;
if (colorMode == ColorMode_Emission)
lightResult.xyz += vertexColor.xyz;
else
lightResult.xyz += gl_FrontMaterial.emission.xyz;
#if @clamp
lightResult = clamp(lightResult, vec4(0.0), vec4(1.0));
#else
lightResult = max(lightResult, 0.0);
#endif
return lightResult;
} }
@ -88,7 +41,7 @@ vec3 getSpecular(vec3 viewNormal, vec3 viewDirection, float shininess, vec3 matS
vec3 lightDir = normalize(gl_LightSource[0].position.xyz); vec3 lightDir = normalize(gl_LightSource[0].position.xyz);
float NdotL = dot(viewNormal, lightDir); float NdotL = dot(viewNormal, lightDir);
if (NdotL <= 0.0) if (NdotL <= 0.0)
return vec3(0.,0.,0.); return vec3(0.0);
vec3 halfVec = normalize(lightDir - viewDirection); vec3 halfVec = normalize(lightDir - viewDirection);
float NdotH = dot(viewNormal, halfVec); float NdotH = dot(viewNormal, halfVec);
return pow(max(NdotH, 0.0), max(1e-4, shininess)) * gl_LightSource[0].specular.xyz * matSpec; return pow(max(NdotH, 0.0), max(1e-4, shininess)) * gl_LightSource[0].specular.xyz * matSpec;

View file

@ -57,13 +57,13 @@ varying float linearDepth;
#define PER_PIXEL_LIGHTING (@normalMap || @forcePPL) #define PER_PIXEL_LIGHTING (@normalMap || @forcePPL)
#if !PER_PIXEL_LIGHTING #if !PER_PIXEL_LIGHTING
centroid varying vec4 lighting; centroid varying vec3 passLighting;
centroid varying vec3 shadowDiffuseLighting; centroid varying vec3 shadowDiffuseLighting;
#endif #endif
centroid varying vec4 passColor;
varying vec3 passViewPos; varying vec3 passViewPos;
varying vec3 passNormal; varying vec3 passNormal;
#include "vertexcolors.glsl"
#include "shadows_fragment.glsl" #include "shadows_fragment.glsl"
#include "lighting.glsl" #include "lighting.glsl"
#include "parallax.glsl" #include "parallax.glsl"
@ -152,21 +152,27 @@ void main()
#endif #endif
float shadowing = unshadowedLightRatio(linearDepth); vec4 diffuseColor = getDiffuseColor();
gl_FragData[0].a *= diffuseColor.a;
alphaTest();
float shadowing = unshadowedLightRatio(linearDepth);
vec3 lighting;
#if !PER_PIXEL_LIGHTING #if !PER_PIXEL_LIGHTING
lighting = passLighting + shadowDiffuseLighting * shadowing;
#else
vec3 diffuseLight, ambientLight;
doLighting(passViewPos, normalize(viewNormal), shadowing, diffuseLight, ambientLight);
lighting = diffuseColor.xyz * diffuseLight + getAmbientColor().xyz * ambientLight + getEmissionColor().xyz;
#endif
#if @clamp #if @clamp
gl_FragData[0] *= clamp(lighting + vec4(shadowDiffuseLighting * shadowing, 0), vec4(0.0), vec4(1.0)); lighting = clamp(lighting, vec3(0.0), vec3(1.0));
#else #else
gl_FragData[0] *= lighting + vec4(shadowDiffuseLighting * shadowing, 0); lighting = max(lighting, 0.0);
#endif #endif
#else gl_FragData[0].xyz *= lighting;
gl_FragData[0] *= doLighting(passViewPos, normalize(viewNormal), passColor, shadowing);
#endif
alphaTest();
#if @envMap && !@preLightEnv #if @envMap && !@preLightEnv
gl_FragData[0].xyz += texture2D(envMap, envTexCoordGen).xyz * envMapColor.xyz * envLuma; gl_FragData[0].xyz += texture2D(envMap, envTexCoordGen).xyz * envMapColor.xyz * envLuma;
@ -182,11 +188,7 @@ void main()
vec3 matSpec = specTex.xyz; vec3 matSpec = specTex.xyz;
#else #else
float shininess = gl_FrontMaterial.shininess; float shininess = gl_FrontMaterial.shininess;
vec3 matSpec; vec3 matSpec = getSpecularColor().xyz;
if (colorMode == ColorMode_Specular)
matSpec = passColor.xyz;
else
matSpec = gl_FrontMaterial.specular.xyz;
#endif #endif
if (matSpec != vec3(0.0)) if (matSpec != vec3(0.0))

View file

@ -43,13 +43,13 @@ varying float linearDepth;
#define PER_PIXEL_LIGHTING (@normalMap || @forcePPL) #define PER_PIXEL_LIGHTING (@normalMap || @forcePPL)
#if !PER_PIXEL_LIGHTING #if !PER_PIXEL_LIGHTING
centroid varying vec4 lighting; centroid varying vec3 passLighting;
centroid varying vec3 shadowDiffuseLighting; centroid varying vec3 shadowDiffuseLighting;
#endif #endif
centroid varying vec4 passColor;
varying vec3 passViewPos; varying vec3 passViewPos;
varying vec3 passNormal; varying vec3 passNormal;
#include "vertexcolors.glsl"
#include "shadows_vertex.glsl" #include "shadows_vertex.glsl"
#include "lighting.glsl" #include "lighting.glsl"
@ -107,13 +107,17 @@ void main(void)
specularMapUV = (gl_TextureMatrix[@specularMapUV] * gl_MultiTexCoord@specularMapUV).xy; specularMapUV = (gl_TextureMatrix[@specularMapUV] * gl_MultiTexCoord@specularMapUV).xy;
#endif #endif
#if !PER_PIXEL_LIGHTING
lighting = doLighting(viewPos.xyz, viewNormal, gl_Color, shadowDiffuseLighting);
#endif
passColor = gl_Color; passColor = gl_Color;
passViewPos = viewPos.xyz; passViewPos = viewPos.xyz;
passNormal = gl_Normal.xyz; passNormal = gl_Normal.xyz;
#if !PER_PIXEL_LIGHTING
vec3 diffuseLight, ambientLight;
doLighting(viewPos.xyz, viewNormal, diffuseLight, ambientLight, shadowDiffuseLighting);
passLighting = getDiffuseColor().xyz * diffuseLight + getAmbientColor().xyz * ambientLight + getEmissionColor().xyz;
shadowDiffuseLighting *= getDiffuseColor().xyz;
#endif
#if (@shadows_enabled) #if (@shadows_enabled)
setupShadowCoords(viewPos, viewNormal); setupShadowCoords(viewPos, viewNormal);
#endif #endif

View file

@ -18,13 +18,13 @@ varying float linearDepth;
#define PER_PIXEL_LIGHTING (@normalMap || @forcePPL) #define PER_PIXEL_LIGHTING (@normalMap || @forcePPL)
#if !PER_PIXEL_LIGHTING #if !PER_PIXEL_LIGHTING
centroid varying vec4 lighting; centroid varying vec3 passLighting;
centroid varying vec3 shadowDiffuseLighting; centroid varying vec3 shadowDiffuseLighting;
#endif #endif
centroid varying vec4 passColor;
varying vec3 passViewPos; varying vec3 passViewPos;
varying vec3 passNormal; varying vec3 passNormal;
#include "vertexcolors.glsl"
#include "shadows_fragment.glsl" #include "shadows_fragment.glsl"
#include "lighting.glsl" #include "lighting.glsl"
#include "parallax.glsl" #include "parallax.glsl"
@ -68,30 +68,33 @@ void main()
gl_FragData[0].a *= texture2D(blendMap, blendMapUV).a; gl_FragData[0].a *= texture2D(blendMap, blendMapUV).a;
#endif #endif
float shadowing = unshadowedLightRatio(linearDepth); vec4 diffuseColor = getDiffuseColor();
gl_FragData[0].a *= diffuseColor.a;
float shadowing = unshadowedLightRatio(linearDepth);
vec3 lighting;
#if !PER_PIXEL_LIGHTING #if !PER_PIXEL_LIGHTING
lighting = passLighting + shadowDiffuseLighting * shadowing;
#else
vec3 diffuseLight, ambientLight;
doLighting(passViewPos, normalize(viewNormal), shadowing, diffuseLight, ambientLight);
lighting = diffuseColor.xyz * diffuseLight + getAmbientColor().xyz * ambientLight + getEmissionColor().xyz;
#endif
#if @clamp #if @clamp
gl_FragData[0] *= clamp(lighting + vec4(shadowDiffuseLighting * shadowing, 0), vec4(0.0), vec4(1.0)); lighting = clamp(lighting, vec3(0.0), vec3(1.0));
#else #else
gl_FragData[0] *= lighting + vec4(shadowDiffuseLighting * shadowing, 0); lighting = max(lighting, 0.0);
#endif #endif
#else gl_FragData[0].xyz *= lighting;
gl_FragData[0] *= doLighting(passViewPos, normalize(viewNormal), passColor, shadowing);
#endif
#if @specularMap #if @specularMap
float shininess = 128.0; // TODO: make configurable float shininess = 128.0; // TODO: make configurable
vec3 matSpec = vec3(diffuseTex.a); vec3 matSpec = vec3(diffuseTex.a);
#else #else
float shininess = gl_FrontMaterial.shininess; float shininess = gl_FrontMaterial.shininess;
vec3 matSpec; vec3 matSpec = getSpecularColor().xyz;
if (colorMode == ColorMode_Specular)
matSpec = passColor.xyz;
else
matSpec = gl_FrontMaterial.specular.xyz;
#endif #endif
if (matSpec != vec3(0.0)) if (matSpec != vec3(0.0))

View file

@ -7,13 +7,13 @@ varying float linearDepth;
#define PER_PIXEL_LIGHTING (@normalMap || @forcePPL) #define PER_PIXEL_LIGHTING (@normalMap || @forcePPL)
#if !PER_PIXEL_LIGHTING #if !PER_PIXEL_LIGHTING
centroid varying vec4 lighting; centroid varying vec3 passLighting;
centroid varying vec3 shadowDiffuseLighting; centroid varying vec3 shadowDiffuseLighting;
#endif #endif
centroid varying vec4 passColor;
varying vec3 passViewPos; varying vec3 passViewPos;
varying vec3 passNormal; varying vec3 passNormal;
#include "vertexcolors.glsl"
#include "shadows_vertex.glsl" #include "shadows_vertex.glsl"
#include "lighting.glsl" #include "lighting.glsl"
@ -31,13 +31,17 @@ void main(void)
vec3 viewNormal = normalize((gl_NormalMatrix * gl_Normal).xyz); vec3 viewNormal = normalize((gl_NormalMatrix * gl_Normal).xyz);
#endif #endif
#if !PER_PIXEL_LIGHTING
lighting = doLighting(viewPos.xyz, viewNormal, gl_Color, shadowDiffuseLighting);
#endif
passColor = gl_Color; passColor = gl_Color;
passNormal = gl_Normal.xyz; passNormal = gl_Normal.xyz;
passViewPos = viewPos.xyz; passViewPos = viewPos.xyz;
#if !PER_PIXEL_LIGHTING
vec3 diffuseLight, ambientLight;
doLighting(viewPos.xyz, viewNormal, diffuseLight, ambientLight, shadowDiffuseLighting);
passLighting = getDiffuseColor().xyz * diffuseLight + getAmbientColor().xyz * ambientLight + getEmissionColor().xyz;
shadowDiffuseLighting *= getDiffuseColor().xyz;
#endif
uv = gl_MultiTexCoord0.xy; uv = gl_MultiTexCoord0.xy;
#if (@shadows_enabled) #if (@shadows_enabled)

View file

@ -0,0 +1,38 @@
centroid varying vec4 passColor;
uniform int colorMode;
const int ColorMode_None = 0;
const int ColorMode_Emission = 1;
const int ColorMode_AmbientAndDiffuse = 2;
const int ColorMode_Ambient = 3;
const int ColorMode_Diffuse = 4;
const int ColorMode_Specular = 5;
vec4 getEmissionColor()
{
if (colorMode == ColorMode_Emission)
return passColor;
return gl_FrontMaterial.emission;
}
vec4 getAmbientColor()
{
if (colorMode == ColorMode_AmbientAndDiffuse || colorMode == ColorMode_Ambient)
return passColor;
return gl_FrontMaterial.ambient;
}
vec4 getDiffuseColor()
{
if (colorMode == ColorMode_AmbientAndDiffuse || colorMode == ColorMode_Diffuse)
return passColor;
return gl_FrontMaterial.diffuse;
}
vec4 getSpecularColor()
{
if (colorMode == ColorMode_Specular)
return passColor;
return gl_FrontMaterial.specular;
}