1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-03-30 03:36:41 +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 #5416: Junk non-node records before the root node are not handled gracefully
Bug #5422: The player loses all spells when resurrected
Bug #5423: Guar follows actors too closely
Bug #5424: Creatures do not headtrack player
Bug #5425: Poison effect only appears for one frame
Bug #5427: GetDistance unknown ID error is misleading
@ -72,7 +73,9 @@
Bug #5688: Water shader broken indoors with enable indoor shadows = false
Bug #5695: ExplodeSpell for actors doesn't target the ground
Bug #5703: OpenMW-CS menu system crashing on XFCE
Bug #5706: AI sequences stop looping after the saved game is reloaded
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 #1536: Show more information about level on menu
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)
)
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})
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> getActorsFollowing(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
/** 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;
///< @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 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.
virtual bool isLevitationEnabled() const = 0;
virtual bool getGodModeState() = 0;
virtual bool getGodModeState() const = 0;
virtual bool toggleGodMode() = 0;

View file

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

View file

@ -122,7 +122,8 @@ namespace MWGui
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);

View file

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

View file

@ -1,10 +1,12 @@
#include "loadingscreen.hpp"
#include <array>
#include <condition_variable>
#include <osgViewer/Viewer>
#include <osg/Texture2D>
#include <osg/Version>
#include <MyGUI_RenderManager.h>
#include <MyGUI_ScrollBar.h>
@ -43,6 +45,8 @@ namespace MWGui
, mNestedLoadingCount(0)
, mProgress(0)
, mShowWallpaper(true)
, mOldCallback(nullptr)
, mHasCallback(false)
{
mMainWidget->setSize(MyGUI::RenderManager::getInstance().getViewSize());
@ -136,18 +140,53 @@ namespace MWGui
{
public:
CopyFramebufferToTextureCallback(osg::Texture2D* texture)
: mTexture(texture)
: mOneshot(true)
, mTexture(texture)
{
}
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 h = renderInfo.getCurrentCamera()->getViewport()->height();
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:
mutable bool mOneshot;
mutable std::mutex mMutex;
mutable std::condition_variable mSignal;
osg::ref_ptr<osg::Texture2D> mTexture;
};
@ -322,7 +361,15 @@ namespace MWGui
mCopyFramebufferToTextureCallback = new CopyFramebufferToTextureCallback(mTexture);
}
#if OSG_VERSION_GREATER_OR_EQUAL(3, 5, 10)
mViewer->getCamera()->addInitialDrawCallback(mCopyFramebufferToTextureCallback);
#else
// TODO: Remove once we officially end support for OSG versions pre 3.5.10
mOldCallback = mViewer->getCamera()->getInitialDrawCallback();
mViewer->getCamera()->setInitialDrawCallback(mCopyFramebufferToTextureCallback);
#endif
mCopyFramebufferToTextureCallback->reset();
mHasCallback = true;
mBackgroundImage->setBackgroundImage("");
mBackgroundImage->setVisible(false);
@ -365,10 +412,19 @@ namespace MWGui
mViewer->renderingTraversals();
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);
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();

View file

@ -3,6 +3,7 @@
#include <memory>
#include <osg/Camera>
#include <osg/Timer>
#include <osg/ref_ptr>
@ -86,6 +87,8 @@ namespace MWGui
osg::ref_ptr<osg::Texture2D> mTexture;
osg::ref_ptr<CopyFramebufferToTextureCallback> mCopyFramebufferToTextureCallback;
osg::ref_ptr<osg::Camera::DrawCallback> mOldCallback;
bool mHasCallback;
std::unique_ptr<MyGUI::ITexture> mGuiTexture;
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();
}
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
@ -2512,26 +2535,14 @@ namespace MWMechanics
std::list<MWWorld::Ptr> Actors::getActorsFollowing(const MWWorld::Ptr& actor)
{
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 (iteratedActor == getPlayer() || iteratedActor == actor)
continue;
const CreatureStats &stats = iteratedActor.getClass().getCreatureStats(iteratedActor);
if (stats.isDead())
continue;
// An actor counts as following if AiFollow is the current AiPackage,
// or there are only Combat and Wander packages before the AiFollow package
for (const auto& package : stats.getAiSequence())
{
if (package->followTargetThroughDoors() && package->getTarget() == actor)
list.push_back(iteratedActor);
else if (package->getTypeId() != AiPackageTypeId::Combat && package->getTypeId() != AiPackageTypeId::Wander)
break;
}
}
if (package->followTargetThroughDoors() && package->getTarget() == actor)
list.push_back(iter.first);
else if (package->getTypeId() != AiPackageTypeId::Combat && package->getTypeId() != AiPackageTypeId::Wander)
return false;
return true;
});
return list;
}
@ -2575,32 +2586,38 @@ namespace MWMechanics
std::list<int> Actors::getActorsFollowingIndices(const MWWorld::Ptr &actor)
{
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 (iteratedActor == getPlayer() || iteratedActor == actor)
continue;
const CreatureStats &stats = iteratedActor.getClass().getCreatureStats(iteratedActor);
if (stats.isDead())
continue;
// An actor counts as following if AiFollow is the current AiPackage,
// or there are only Combat and Wander packages before the AiFollow package
for (const auto& package : stats.getAiSequence())
if (package->followTargetThroughDoors() && package->getTarget() == actor)
{
if (package->followTargetThroughDoors() && package->getTarget() == actor)
{
list.push_back(static_cast<const AiFollow*>(package.get())->getFollowIndex());
break;
}
else if (package->getTypeId() != AiPackageTypeId::Combat && package->getTypeId() != AiPackageTypeId::Wander)
break;
list.push_back(static_cast<const AiFollow*>(package.get())->getFollowIndex());
return false;
}
}
else if (package->getTypeId() != AiPackageTypeId::Combat && package->getTypeId() != AiPackageTypeId::Wander)
return false;
return true;
});
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> list;
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
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
/**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)
return false;
// The distances below are approximations based on observations of the original engine.
// If only one actor is following the target, it uses 186.
// If there are multiple actors following the same target, they form a group with each group member at 313 + (130 * i) distance to the target.
short followDistance = 186;
std::list<int> followers = MWBase::Environment::get().getMechanicsManager()->getActorsFollowingIndices(target);
if (followers.size() >= 2)
// In the original engine the first follower stays closer to the player than any subsequent followers.
// Followers beyond the first usually attempt to stand inside each other.
osg::Vec3f::value_type floatingDistance = 0;
auto followers = MWBase::Environment::get().getMechanicsManager()->getActorsFollowingByIndex(target);
if (followers.size() >= 2 && followers.cbegin()->first != mFollowIndex)
{
followDistance = 313;
short i = 0;
followers.sort();
for (int followIndex : followers)
osg::Vec3f::value_type maxSize = 0;
for(auto& follower : followers)
{
if (followIndex == mFollowIndex)
followDistance += 130 * i;
++i;
auto halfExtent = MWBase::Environment::get().getWorld()->getHalfExtents(follower.second).y();
if(halfExtent > floatingDistance)
floatingDistance = halfExtent;
}
}
floatingDistance += MWBase::Environment::get().getWorld()->getHalfExtents(target).y();
floatingDistance += MWBase::Environment::get().getWorld()->getHalfExtents(actor).y() * 2;
short followDistance = static_cast<short>(floatingDistance);
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));
smoothTurn(actor, getXAngleToPoint(position, dest), 0);
world->removeActorPath(actor);
return true;
return isDestReached;
}
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)
{
// If there is more than one package in the list, enable repeating
if (!list.mList.empty() && list.mList.begin() != (list.mList.end()-1))
if (list.mList.size() >= 2)
mRepeat = true;
for (const auto& esmPackage : list.mList)
@ -459,8 +459,15 @@ void AiSequence::readState(const ESM::AiSequence::AiSequence &sequence)
int count = 0;
for (auto& container : sequence.mPackages)
{
if (isActualAiPackage(static_cast<AiPackageTypeId>(container.mType)))
count++;
switch (container.mType)
{
case ESM::AiSequence::Ai_Wander:
case ESM::AiSequence::Ai_Travel:
case ESM::AiSequence::Ai_Escort:
case ESM::AiSequence::Ai_Follow:
case ESM::AiSequence::Ai_Activate:
++count;
}
}
if (count > 1)

View file

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

View file

@ -1654,6 +1654,11 @@ namespace MWMechanics
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) {
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> getActorsFollowing(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> getEnemiesNearby(const MWWorld::Ptr& actor) override;

View file

@ -74,8 +74,9 @@ Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, Physic
updateRotation();
updateScale();
resetPosition();
updatePosition();
addCollisionMask(getCollisionMask());
updateCollisionObjectPosition();
}
Actor::~Actor()
@ -118,26 +119,34 @@ int Actor::getCollisionMask() const
return collisionMask;
}
void Actor::updatePositionUnsafe()
{
mWorldPosition = mPtr.getRefData().getPosition().asVec3();
}
void Actor::updatePosition()
{
std::scoped_lock lock(mPositionMutex);
updatePositionUnsafe();
updateWorldPosition();
mPreviousPosition = mWorldPosition;
mPosition = mWorldPosition;
mSimulationPosition = mWorldPosition;
mStandingOnPtr = nullptr;
mSkipSimulation = true;
}
void Actor::updateWorldPosition()
{
if (mWorldPosition != mPtr.getRefData().getPosition().asVec3())
mWorldPositionChanged = true;
mWorldPosition = mPtr.getRefData().getPosition().asVec3();
}
osg::Vec3f Actor::getWorldPosition() const
{
std::scoped_lock lock(mPositionMutex);
return mWorldPosition;
}
void Actor::setSimulationPosition(const osg::Vec3f& position)
{
mSimulationPosition = position;
if (!mSkipSimulation)
mSimulationPosition = position;
mSkipSimulation = false;
}
osg::Vec3f Actor::getSimulationPosition() const
@ -145,20 +154,16 @@ osg::Vec3f Actor::getSimulationPosition() const
return mSimulationPosition;
}
void Actor::updateCollisionObjectPositionUnsafe()
void Actor::updateCollisionObjectPosition()
{
std::scoped_lock lock(mPositionMutex);
mShape->setLocalScaling(Misc::Convert::toBullet(mScale));
osg::Vec3f scaledTranslation = mRotation * osg::componentMultiply(mMeshTranslation, mScale);
osg::Vec3f newPosition = scaledTranslation + mPosition;
mLocalTransform.setOrigin(Misc::Convert::toBullet(newPosition));
mLocalTransform.setRotation(Misc::Convert::toBullet(mRotation));
mCollisionObject->setWorldTransform(mLocalTransform);
}
void Actor::updateCollisionObjectPosition()
{
std::scoped_lock lock(mPositionMutex);
updateCollisionObjectPositionUnsafe();
mWorldPositionChanged = false;
}
osg::Vec3f Actor::getCollisionObjectPosition() const
@ -167,28 +172,20 @@ osg::Vec3f Actor::getCollisionObjectPosition() const
return Misc::Convert::toOsg(mLocalTransform.getOrigin());
}
void Actor::setPosition(const osg::Vec3f& position)
bool Actor::setPosition(const osg::Vec3f& position)
{
std::scoped_lock lock(mPositionMutex);
mPreviousPosition = mPosition;
mPosition = position;
bool hasChanged = mPosition != position || mPositionOffset.length() != 0 || mWorldPositionChanged;
mPreviousPosition = mPosition + mPositionOffset;
mPosition = position + mPositionOffset;
mPositionOffset = osg::Vec3f();
return hasChanged;
}
void Actor::adjustPosition(const osg::Vec3f& offset)
{
std::scoped_lock lock(mPositionMutex);
mPosition += offset;
mPreviousPosition += offset;
}
void Actor::resetPosition()
{
std::scoped_lock lock(mPositionMutex);
updatePositionUnsafe();
mPreviousPosition = mWorldPosition;
mPosition = mWorldPosition;
mSimulationPosition = mWorldPosition;
updateCollisionObjectPositionUnsafe();
mPositionOffset += offset;
}
osg::Vec3f Actor::getPosition() const
@ -204,8 +201,6 @@ osg::Vec3f Actor::getPreviousPosition() const
void Actor::updateRotation ()
{
std::scoped_lock lock(mPositionMutex);
if (mRotation == mPtr.getRefData().getBaseNode()->getAttitude())
return;
mRotation = mPtr.getRefData().getBaseNode()->getAttitude();
}
@ -236,7 +231,6 @@ osg::Vec3f Actor::getHalfExtents() const
osg::Vec3f Actor::getOriginalHalfExtents() const
{
std::scoped_lock lock(mPositionMutex);
return mHalfExtents;
}
@ -273,7 +267,6 @@ void Actor::setWalkingOnWater(bool walkingOnWater)
void Actor::setCanWaterWalk(bool waterWalk)
{
std::scoped_lock lock(mPositionMutex);
if (waterWalk != mCanWaterWalk)
{
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
* when an object is "instantly" moved/teleported as opposed to being moved by the physics simulation.
*/
void updatePosition();
void updateWorldPosition();
osg::Vec3f getWorldPosition() const;
/**
@ -90,9 +90,10 @@ namespace MWPhysics
/**
* Store the current position into mPreviousPosition, then move to this position.
* Returns true if the new position is different.
*/
void setPosition(const osg::Vec3f& position);
void resetPosition();
bool setPosition(const osg::Vec3f& position);
void updatePosition();
void adjustPosition(const osg::Vec3f& offset);
osg::Vec3f getPosition() const;
@ -154,8 +155,6 @@ namespace MWPhysics
void updateCollisionMask();
void addCollisionMask(int collisionMask);
int getCollisionMask() const;
void updateCollisionObjectPositionUnsafe();
void updatePositionUnsafe();
bool mCanWaterWalk;
std::atomic<bool> mWalkingOnWater;
@ -177,6 +176,9 @@ namespace MWPhysics
osg::Vec3f mSimulationPosition;
osg::Vec3f mPosition;
osg::Vec3f mPreviousPosition;
osg::Vec3f mPositionOffset;
bool mWorldPositionChanged;
bool mSkipSimulation;
btTransform mLocalTransform;
mutable std::mutex mPositionMutex;

View file

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

View file

@ -41,15 +41,21 @@ namespace MWPhysics
btCollisionWorld::ClosestRayResultCallback::addSingleResult(rayResult, normalInWorldSpace);
if (mProjectile)
{
auto* holder = static_cast<PtrHolder*>(rayResult.m_collisionObject->getUserPointer());
if (auto* target = dynamic_cast<Actor*>(holder))
switch (rayResult.m_collisionObject->getBroadphaseHandle()->m_collisionFilterGroup)
{
mProjectile->hit(target->getPtr(), m_hitPointWorld, m_hitNormalWorld);
}
else if (auto* target = dynamic_cast<Projectile*>(holder))
{
target->hit(mProjectile->getPtr(), m_hitPointWorld, m_hitNormalWorld);
mProjectile->hit(target->getPtr(), m_hitPointWorld, m_hitNormalWorld);
case CollisionType_Actor:
{
auto* target = static_cast<Actor*>(rayResult.m_collisionObject->getUserPointer());
mProjectile->hit(target->getPtr(), m_hitPointWorld, m_hitNormalWorld);
break;
}
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)
{
auto* physicActor = actor.mActorRaw;
auto ptr = actor.mPtr;
const ESM::Position& refpos = actor.mRefpos;
// Early-out for totally static creatures
// (Not sure if gravity should still apply?)
if (!ptr.getClass().isMobile(ptr))
return;
{
const auto ptr = physicActor->getPtr();
if (!ptr.getClass().isMobile(ptr))
return;
}
// Reset per-frame data
physicActor->setWalkingOnWater(false);
@ -128,8 +130,9 @@ namespace MWPhysics
velocity = velocity + inertia;
}
// dead actors underwater will float to the surface, if the CharacterController tells us to do so
if (actor.mMovement.z() > 0 && actor.mIsDead && actor.mPosition.z() < swimlevel)
// Dead and paralyzed actors underwater will float to the surface,
// if the CharacterController tells us to do so
if (actor.mMovement.z() > 0 && actor.mFloatToSurface && actor.mPosition.z() < swimlevel)
velocity = osg::Vec3f(0,0,1) * 25;
if (actor.mWantJump)
@ -211,6 +214,7 @@ namespace MWPhysics
if (result)
{
// don't let pure water creatures move out of water after stepMove
const auto ptr = physicActor->getPtr();
if (ptr.getClass().isPureWaterCreature(ptr) && newPosition.z() + halfExtents.z() > actor.mWaterlevel)
newPosition = oldPosition;
}

View file

@ -87,12 +87,13 @@ namespace
void updateMechanics(MWPhysics::ActorFrameData& actorData)
{
auto ptr = actorData.mActorRaw->getPtr();
if (actorData.mDidJump)
handleJump(actorData.mPtr);
handleJump(ptr);
MWMechanics::CreatureStats& stats = actorData.mPtr.getClass().getCreatureStats(actorData.mPtr);
MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr);
if (actorData.mNeedLand)
stats.land(actorData.mPtr == MWMechanics::getPlayer() && (actorData.mFlying || actorData.mSwimming));
stats.land(ptr == MWMechanics::getPlayer() && (actorData.mFlying || actorData.mSwimming));
else if (actorData.mFallHeight < 0)
stats.addToFallHeight(-actorData.mFallHeight);
}
@ -100,15 +101,6 @@ namespace
osg::Vec3f interpolateMovements(MWPhysics::ActorFrameData& actorData, float timeAccum, float physicsDt)
{
const float interpolationFactor = timeAccum / physicsDt;
// account for force change of actor's position in the main thread
const auto correction = actorData.mActorRaw->getWorldPosition() - actorData.mOrigin;
if (correction.length() != 0)
{
actorData.mActorRaw->adjustPosition(correction);
actorData.mPosition = actorData.mActorRaw->getPosition();
}
return actorData.mPosition * interpolationFactor + actorData.mActorRaw->getPreviousPosition() * (1.f - interpolationFactor);
}
@ -213,45 +205,43 @@ namespace MWPhysics
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.
// While the mSimulationMutex is held, background physics threads can't run.
std::unique_lock lock(mSimulationMutex);
for (auto& data : actorsData)
data.updatePosition();
mMovedActors.clear();
// start by finishing previous background computation
if (mNumThreads != 0)
{
for (auto& data : mActorsFrameData)
{
// Ignore actors that were deleted while the background thread was running
if (!data.mActor.lock())
continue;
const auto actorActive = [&data](const auto& newFrameData) -> bool
{
const auto actor = data.mActor.lock();
return actor && actor->getPtr() == newFrameData.mActorRaw->getPtr();
};
// Only return actors that are still part of the scene
if (std::any_of(actorsData.begin(), actorsData.end(), actorActive))
{
updateMechanics(data);
updateMechanics(data);
if (mAdvanceSimulation)
data.mActorRaw->setStandingOnPtr(data.mStandingOn);
if (mMovementResults.find(data.mPtr) != mMovementResults.end())
data.mActorRaw->setSimulationPosition(mMovementResults[data.mPtr]);
// these variables are accessed directly from the main thread, update them here to prevent accessing "too new" values
if (mAdvanceSimulation)
data.mActorRaw->setStandingOnPtr(data.mStandingOn);
data.mActorRaw->setSimulationPosition(interpolateMovements(data, mTimeAccum, mPhysicsDt));
mMovedActors.emplace_back(data.mActorRaw->getPtr());
}
}
if (mFrameNumber == frameNumber - 1)
{
stats.setAttribute(mFrameNumber, "physicsworker_time_begin", mTimer->delta_s(mFrameStart, mTimeBegin));
stats.setAttribute(mFrameNumber, "physicsworker_time_taken", mTimer->delta_s(mTimeBegin, mTimeEnd));
stats.setAttribute(mFrameNumber, "physicsworker_time_end", mTimer->delta_s(mFrameStart, mTimeEnd));
}
mFrameStart = frameStart;
mTimeBegin = mTimer->tick();
mFrameNumber = frameNumber;
updateStats(frameStart, frameNumber, stats);
}
// init
for (auto& data : actorsData)
data.updatePosition();
mRemainingSteps = numSteps;
mTimeAccum = timeAccum;
mActorsFrameData = std::move(actorsData);
@ -266,52 +256,28 @@ namespace MWPhysics
if (mNumThreads == 0)
{
mMovementResults.clear();
syncComputation();
for (auto& data : mActorsFrameData)
{
if (mAdvanceSimulation)
data.mActorRaw->setStandingOnPtr(data.mStandingOn);
if (mMovementResults.find(data.mPtr) != mMovementResults.end())
data.mActorRaw->setSimulationPosition(mMovementResults[data.mPtr]);
}
return mMovementResults;
return mMovedActors;
}
// Remove actors that were deleted while the background thread was running
for (auto& data : mActorsFrameData)
{
if (!data.mActor.lock())
mMovementResults.erase(data.mPtr);
}
std::swap(mMovementResults, mPreviousMovementResults);
// mMovementResults is shared between all workers instance
// pre-allocate all nodes so that we don't need synchronization
mMovementResults.clear();
for (const auto& m : mActorsFrameData)
mMovementResults[m.mPtr] = m.mPosition;
lock.unlock();
mHasJob.notify_all();
return mPreviousMovementResults;
return mMovedActors;
}
const PtrPositionList& PhysicsTaskScheduler::resetSimulation(const ActorMap& actors)
const std::vector<MWWorld::Ptr>& PhysicsTaskScheduler::resetSimulation(const ActorMap& actors)
{
std::unique_lock lock(mSimulationMutex);
mMovementResults.clear();
mPreviousMovementResults.clear();
mMovedActors.clear();
mActorsFrameData.clear();
for (const auto& [_, actor] : actors)
{
actor->resetPosition();
actor->setStandingOnPtr(nullptr);
mMovementResults[actor->getPtr()] = actor->getWorldPosition();
actor->updatePosition();
actor->setSimulationPosition(actor->getWorldPosition()); // updatePosition skip next simulation, now we need to "consume" it
actor->updateCollisionObjectPosition();
mMovedActors.emplace_back(actor->getPtr());
}
return mMovementResults;
return mMovedActors;
}
void PhysicsTaskScheduler::rayTest(const btVector3& rayFromWorld, const btVector3& rayToWorld, btCollisionWorld::RayResultCallback& resultCallback) const
@ -379,18 +345,18 @@ namespace MWPhysics
mCollisionWorld->removeCollisionObject(collisionObject);
}
void PhysicsTaskScheduler::updateSingleAabb(std::weak_ptr<PtrHolder> ptr)
void PhysicsTaskScheduler::updateSingleAabb(std::weak_ptr<PtrHolder> ptr, bool immediate)
{
if (mDeferAabbUpdate)
{
std::unique_lock lock(mUpdateAabbMutex);
mUpdateAabb.insert(std::move(ptr));
}
else
if (!mDeferAabbUpdate || immediate)
{
std::unique_lock lock(mCollisionWorldMutex);
updatePtrAabb(ptr);
}
else
{
std::unique_lock lock(mUpdateAabbMutex);
mUpdateAabb.insert(std::move(ptr));
}
}
bool PhysicsTaskScheduler::getLineOfSight(const std::weak_ptr<Actor>& actor1, const std::weak_ptr<Actor>& actor2)
@ -493,7 +459,6 @@ namespace MWPhysics
{
auto& actorData = mActorsFrameData[job];
handleFall(actorData, mAdvanceSimulation);
mMovementResults[actorData.mPtr] = interpolateMovements(actorData, mTimeAccum, mPhysicsDt);
}
}
@ -511,9 +476,7 @@ namespace MWPhysics
{
if(const auto actor = actorData.mActor.lock())
{
bool positionChanged = actorData.mPosition != actorData.mActorRaw->getPosition();
actorData.mActorRaw->setPosition(actorData.mPosition);
if (positionChanged)
if (actor->setPosition(actorData.mPosition))
{
actor->updateCollisionObjectPosition();
mCollisionWorld->updateSingleAabb(actor->getCollisionObject());
@ -550,8 +513,24 @@ namespace MWPhysics
for (auto& actorData : mActorsFrameData)
{
handleFall(actorData, mAdvanceSimulation);
mMovementResults[actorData.mPtr] = interpolateMovements(actorData, mTimeAccum, mPhysicsDt);
actorData.mActorRaw->setSimulationPosition(interpolateMovements(actorData, mTimeAccum, mPhysicsDt));
updateMechanics(actorData);
mMovedActors.emplace_back(actorData.mActorRaw->getPtr());
if (mAdvanceSimulation)
actorData.mActorRaw->setStandingOnPtr(actorData.mStandingOn);
}
}
void PhysicsTaskScheduler::updateStats(osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats)
{
if (mFrameNumber == frameNumber - 1)
{
stats.setAttribute(mFrameNumber, "physicsworker_time_begin", mTimer->delta_s(mFrameStart, mTimeBegin));
stats.setAttribute(mFrameNumber, "physicsworker_time_taken", mTimer->delta_s(mTimeBegin, mTimeEnd));
stats.setAttribute(mFrameNumber, "physicsworker_time_end", mTimer->delta_s(mFrameStart, mTimeEnd));
}
mFrameStart = frameStart;
mTimeBegin = mTimer->tick();
mFrameNumber = frameNumber;
}
}

View file

@ -32,9 +32,9 @@ namespace MWPhysics
/// @param timeAccum accumulated time from previous run to interpolate movements
/// @param actorsData per actor data needed to compute new positions
/// @return new position of each actor
const PtrPositionList& moveActors(int numSteps, float timeAccum, std::vector<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
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 addCollisionObject(btCollisionObject* collisionObject, int collisionFilterGroup, int collisionFilterMask);
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);
private:
@ -57,11 +57,11 @@ namespace MWPhysics
void refreshLOSCache();
void updateAabbs();
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::vector<ActorFrameData> mActorsFrameData;
PtrPositionList mMovementResults;
PtrPositionList mPreviousMovementResults;
std::vector<MWWorld::Ptr> mMovedActors;
const float mPhysicsDt;
float mTimeAccum;
std::shared_ptr<btCollisionWorld> mCollisionWorld;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -32,11 +32,7 @@ namespace MWScript
std::vector<MWWorld::Ptr> actors;
MWBase::Environment::get().getWorld()->getActorsStandingOn (ptr, actors);
for (auto& actor : actors)
{
osg::Vec3f actorPos(actor.getRefData().getPosition().asVec3());
actorPos += diff;
MWBase::Environment::get().getWorld()->moveObject(actor, actorPos.x(), actorPos.y(), actorPos.z());
}
MWBase::Environment::get().getWorld()->moveObjectBy(actor, diff);
}
template<class R>
@ -727,14 +723,12 @@ namespace MWScript
return;
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.
// This approach can be used to create elevators.
moveStandingActors(ptr, diff);
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());
runtime.pop();
const float *objPos = ptr.getRefData().getPosition().pos;
osg::Vec3f diff;
if (axis == "x")
diff.x() += movement;
diff.x() = movement;
else if (axis == "y")
diff.y() += movement;
diff.y() = movement;
else if (axis == "z")
diff.z() += movement;
diff.z() = movement;
else
return;
@ -771,7 +764,7 @@ namespace MWScript
// This approach can be used to create elevators.
moveStandingActors(ptr, diff);
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);
}
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)
{
if (mPhysics->getActor(ptr))
@ -1333,7 +1345,7 @@ namespace MWWorld
}
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.
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());
if (ptr.getClass().isActor())
{
MWPhysics::Actor* actor = mPhysics->getActor(ptr);
if (actor)
actor->resetPosition();
}
}
void World::fixPosition()
@ -1500,16 +1506,26 @@ namespace MWWorld
mProjectileManager->processHits();
mDiscardMovements = false;
for(const auto& [actor, position]: results)
for(const auto& actor : results)
{
// Handle player last, in case a cell transition occurs
if(actor != getPlayerPtr())
{
auto* physactor = mPhysics->getActor(actor);
assert(physactor);
const auto position = physactor->getSimulationPosition();
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())
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()
@ -2286,8 +2302,12 @@ namespace MWWorld
if (stats.isDead())
return false;
const bool isPlayer = ptr == getPlayerConstPtr();
if (!(isPlayer && mGodMode) && stats.isParalyzed())
return false;
if (ptr.getClass().canFly(ptr))
return !stats.isParalyzed();
return true;
if(stats.getMagicEffects().get(ESM::MagicEffect::Levitate).getMagnitude() > 0
&& isLevitationEnabled())
@ -2897,7 +2917,7 @@ namespace MWWorld
mRendering->rebuildPtr(getPlayerPtr());
}
bool World::getGodModeState()
bool World::getGodModeState() const
{
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;
///< @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;
/// World rotates object, uses radians
@ -616,7 +619,7 @@ namespace MWWorld
/// Returns true if levitation spell effect is allowed.
bool isLevitationEnabled() const override;
bool getGodModeState() override;
bool getGodModeState() const override;
bool toggleGodMode() override;

View file

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

View file

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

View file

@ -151,7 +151,13 @@ add_component_dir (fallback
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
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 <memory>
#include <functional>
#include <components/crashcatcher/crashcatcher.hpp>
#ifdef _WIN32
# include <components/crashcatcher/windows_crashcatcher.hpp>
# undef WIN32_LEAN_AND_MEAN
# define WIN32_LEAN_AND_MEAN
# include <windows.h>
@ -163,7 +165,6 @@ int wrapApplication(int (*innerApplication)(int argc, char *argv[]), int argc, c
#endif
const std::string logName = Misc::StringUtils::lowerCase(appName) + ".log";
const std::string crashLogName = Misc::StringUtils::lowerCase(appName) + "-crash.log";
boost::filesystem::ofstream logfile;
int ret = 0;
@ -187,13 +188,18 @@ int wrapApplication(int (*innerApplication)(int argc, char *argv[]), int argc, c
std::cerr.rdbuf (&cerrsb);
#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
// does not depend on config being read.
crashCatcherInstall(argc, argv, (cfgMgr.getLogPath() / crashLogName).string());
#endif
ret = innerApplication(argc, argv);
}
catch (std::exception& e)
catch (const std::exception& e)
{
#if (defined(__APPLE__) || defined(__linux) || defined(__unix) || defined(__posix))
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)
{
assert(isTypeGeometry(nifNode->recType));
osg::ref_ptr<osg::Drawable> drawable;
osg::ref_ptr<osg::Geometry> geom (new osg::Geometry);
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)
{
if (!(ctrl->flags & Nif::NiNode::ControllerFlag_Active))
@ -1292,6 +1295,8 @@ namespace NifOsg
assert(isTypeGeometry(nifNode->recType));
osg::ref_ptr<osg::Geometry> geometry (new osg::Geometry);
handleNiGeometry(nifNode, geometry, parentNode, composite, boundTextures, animflags);
if (geometry->empty())
return;
osg::ref_ptr<SceneUtil::RigGeometry> rig(new SceneUtil::RigGeometry);
rig->setSourceGeometry(geometry);
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.
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]
font size = 16

View file

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

View file

@ -1,85 +1,38 @@
#define MAX_LIGHTS 8
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;
void perLight(out vec3 ambientOut, out vec3 diffuseOut, int lightIndex, vec3 viewPos, vec3 viewNormal, vec4 diffuse, vec3 ambient)
void perLight(out vec3 ambientOut, out vec3 diffuseOut, int lightIndex, vec3 viewPos, vec3 viewNormal)
{
vec3 lightDir;
float lightDistance;
lightDir = gl_LightSource[lightIndex].position.xyz - (viewPos.xyz * gl_LightSource[lightIndex].position.w);
lightDistance = length(lightDir);
vec3 lightDir = gl_LightSource[lightIndex].position.xyz - viewPos * gl_LightSource[lightIndex].position.w;
float lightDistance = length(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);
ambientOut = ambient * gl_LightSource[lightIndex].ambient.xyz * illumination;
diffuseOut = diffuse.xyz * gl_LightSource[lightIndex].diffuse.xyz * max(dot(viewNormal.xyz, lightDir), 0.0) * illumination;
ambientOut = gl_LightSource[lightIndex].ambient.xyz * illumination;
diffuseOut = gl_LightSource[lightIndex].diffuse.xyz * max(dot(viewNormal, lightDir), 0.0) * illumination;
}
#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
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
{
vec4 diffuse;
vec3 ambient;
if (colorMode == ColorMode_AmbientAndDiffuse)
{
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);
vec3 ambientOut, diffuseOut;
// This light gets added a second time in the loop to fix Mesa users' slowdown, so we need to negate its contribution here.
perLight(ambientOut, diffuseOut, 0, viewPos, viewNormal);
#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
shadowDiffuse = diffuseLight;
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.
shadowDiffuse = diffuseOut;
diffuseLight = -diffuseOut;
#endif
ambientLight = gl_LightModel.ambient.xyz;
for (int i=0; i<MAX_LIGHTS; ++i)
{
perLight(ambientLight, diffuseLight, i, viewPos, viewNormal, diffuse, ambient);
lightResult.xyz += ambientLight + diffuseLight;
perLight(ambientOut, diffuseOut, i, viewPos, viewNormal);
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);
float NdotL = dot(viewNormal, lightDir);
if (NdotL <= 0.0)
return vec3(0.,0.,0.);
return vec3(0.0);
vec3 halfVec = normalize(lightDir - viewDirection);
float NdotH = dot(viewNormal, halfVec);
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)
#if !PER_PIXEL_LIGHTING
centroid varying vec4 lighting;
centroid varying vec3 passLighting;
centroid varying vec3 shadowDiffuseLighting;
#endif
centroid varying vec4 passColor;
varying vec3 passViewPos;
varying vec3 passNormal;
#include "vertexcolors.glsl"
#include "shadows_fragment.glsl"
#include "lighting.glsl"
#include "parallax.glsl"
@ -152,21 +152,27 @@ void main()
#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
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
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
gl_FragData[0] *= lighting + vec4(shadowDiffuseLighting * shadowing, 0);
lighting = max(lighting, 0.0);
#endif
#else
gl_FragData[0] *= doLighting(passViewPos, normalize(viewNormal), passColor, shadowing);
#endif
alphaTest();
gl_FragData[0].xyz *= lighting;
#if @envMap && !@preLightEnv
gl_FragData[0].xyz += texture2D(envMap, envTexCoordGen).xyz * envMapColor.xyz * envLuma;
@ -182,11 +188,7 @@ void main()
vec3 matSpec = specTex.xyz;
#else
float shininess = gl_FrontMaterial.shininess;
vec3 matSpec;
if (colorMode == ColorMode_Specular)
matSpec = passColor.xyz;
else
matSpec = gl_FrontMaterial.specular.xyz;
vec3 matSpec = getSpecularColor().xyz;
#endif
if (matSpec != vec3(0.0))

View file

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

View file

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

View file

@ -7,13 +7,13 @@ varying float linearDepth;
#define PER_PIXEL_LIGHTING (@normalMap || @forcePPL)
#if !PER_PIXEL_LIGHTING
centroid varying vec4 lighting;
centroid varying vec3 passLighting;
centroid varying vec3 shadowDiffuseLighting;
#endif
centroid varying vec4 passColor;
varying vec3 passViewPos;
varying vec3 passNormal;
#include "vertexcolors.glsl"
#include "shadows_vertex.glsl"
#include "lighting.glsl"
@ -31,13 +31,17 @@ void main(void)
vec3 viewNormal = normalize((gl_NormalMatrix * gl_Normal).xyz);
#endif
#if !PER_PIXEL_LIGHTING
lighting = doLighting(viewPos.xyz, viewNormal, gl_Color, shadowDiffuseLighting);
#endif
passColor = gl_Color;
passNormal = gl_Normal.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;
#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;
}