Add OpenMW commits up to 1 Jun 2021

# Conflicts:
#   .travis.yml
#   README.md
pull/593/head
David Cernat 4 years ago
commit 1f0cb09933

@ -749,7 +749,7 @@ void CSVDoc::View::infoAbout()
"<tr><td>%4</td><td><a href=\"https://openmw.org\">https://openmw.org</a></td></tr>" "<tr><td>%4</td><td><a href=\"https://openmw.org\">https://openmw.org</a></td></tr>"
"<tr><td>%5</td><td><a href=\"https://forum.openmw.org\">https://forum.openmw.org</a></td></tr>" "<tr><td>%5</td><td><a href=\"https://forum.openmw.org\">https://forum.openmw.org</a></td></tr>"
"<tr><td>%6</td><td><a href=\"https://gitlab.com/OpenMW/openmw/issues\">https://gitlab.com/OpenMW/openmw/issues</a></td></tr>" "<tr><td>%6</td><td><a href=\"https://gitlab.com/OpenMW/openmw/issues\">https://gitlab.com/OpenMW/openmw/issues</a></td></tr>"
"<tr><td>%7</td><td><a href=\"https://webchat.freenode.net/?channels=openmw&uio=OT10cnVlde\">irc://irc.freenode.net/#openmw</a></td></tr>" "<tr><td>%7</td><td><a href=\"https://web.libera.chat/#openmw\">ircs://irc.libera.chat/#openmw</a></td></tr>"
"</table>" "</table>"
"</p>") "</p>")
.arg(versionInfo .arg(versionInfo

@ -1136,6 +1136,8 @@ void OMW::Engine::go()
// Save user settings // Save user settings
settings.saveUser(settingspath); settings.saveUser(settingspath);
mViewer->stopThreading();
Log(Debug::Info) << "Quitting peacefully."; Log(Debug::Info) << "Quitting peacefully.";
} }

@ -393,7 +393,7 @@ namespace MWDialogue
if(!inJournal(topicId, answer->mId)) if(!inJournal(topicId, answer->mId))
{ {
// Does this dialogue contains some actor-specific answer? // Does this dialogue contains some actor-specific answer?
if (answer->mActor == mActor.getCellRef().getRefId()) if (Misc::StringUtils::ciEqual(answer->mActor, mActor.getCellRef().getRefId()))
flag |= MWBase::DialogueManager::TopicType::Specific; flag |= MWBase::DialogueManager::TopicType::Specific;
} }
else else

@ -100,7 +100,7 @@ namespace MWGui
Log(Debug::Warning) << "Warning: no splash screens found!"; Log(Debug::Warning) << "Warning: no splash screens found!";
} }
void LoadingScreen::setLabel(const std::string &label, bool important, bool center) void LoadingScreen::setLabel(const std::string &label, bool important)
{ {
mImportantLabel = important; mImportantLabel = important;
@ -110,7 +110,7 @@ namespace MWGui
size.width = std::max(300, size.width); size.width = std::max(300, size.width);
mLoadingBox->setSize(size); mLoadingBox->setSize(size);
if (center) if (MWBase::Environment::get().getWindowManager()->getMessagesCount() > 0)
mLoadingBox->setPosition(mMainWidget->getWidth()/2 - mLoadingBox->getWidth()/2, mMainWidget->getHeight()/2 - mLoadingBox->getHeight()/2); mLoadingBox->setPosition(mMainWidget->getWidth()/2 - mLoadingBox->getWidth()/2, mMainWidget->getHeight()/2 - mLoadingBox->getHeight()/2);
else else
mLoadingBox->setPosition(mMainWidget->getWidth()/2 - mLoadingBox->getWidth()/2, mMainWidget->getHeight() - mLoadingBox->getHeight() - 8); mLoadingBox->setPosition(mMainWidget->getWidth()/2 - mLoadingBox->getWidth()/2, mMainWidget->getHeight() - mLoadingBox->getHeight() - 8);

@ -37,7 +37,7 @@ namespace MWGui
virtual ~LoadingScreen(); virtual ~LoadingScreen();
/// Overridden from Loading::Listener, see the Loading::Listener documentation for usage details /// Overridden from Loading::Listener, see the Loading::Listener documentation for usage details
void setLabel (const std::string& label, bool important, bool center) override; void setLabel (const std::string& label, bool important) override;
void loadingOn(bool visible=true) override; void loadingOn(bool visible=true) override;
void loadingOff() override; void loadingOff() override;
void setProgressRange (size_t range) override; void setProgressRange (size_t range) override;

@ -1,5 +1,7 @@
#include "actors.hpp" #include "actors.hpp"
#include <optional>
#include <components/esm/esmreader.hpp> #include <components/esm/esmreader.hpp>
#include <components/esm/esmwriter.hpp> #include <components/esm/esmwriter.hpp>
@ -192,6 +194,15 @@ namespace MWMechanics
static const int GREETING_COOLDOWN = 40; // how many updates should pass before NPC can continue movement static const int GREETING_COOLDOWN = 40; // how many updates should pass before NPC can continue movement
static const float DECELERATE_DISTANCE = 512.f; static const float DECELERATE_DISTANCE = 512.f;
namespace
{
float getTimeToDestination(const AiPackage& package, const osg::Vec3f& position, float speed, float duration, const osg::Vec3f& halfExtents)
{
const auto distanceToNextPathPoint = (package.getNextPathPoint(package.getDestination()) - position).length();
return (distanceToNextPathPoint - package.getNextPathPointTolerance(speed, duration, halfExtents)) / speed;
}
}
class GetStuntedMagickaDuration : public MWMechanics::EffectSourceVisitor class GetStuntedMagickaDuration : public MWMechanics::EffectSourceVisitor
{ {
public: public:
@ -1885,7 +1896,7 @@ namespace MWMechanics
} }
void Actors::predictAndAvoidCollisions() void Actors::predictAndAvoidCollisions(float duration)
{ {
if (!MWBase::Environment::get().getMechanicsManager()->isAIActive()) if (!MWBase::Environment::get().getMechanicsManager()->isAIActive())
return; return;
@ -1920,7 +1931,8 @@ namespace MWMechanics
bool shouldAvoidCollision = isMoving; bool shouldAvoidCollision = isMoving;
bool shouldTurnToApproachingActor = !isMoving; bool shouldTurnToApproachingActor = !isMoving;
MWWorld::Ptr currentTarget; // Combat or pursue target (NPCs should not avoid collision with their targets). MWWorld::Ptr currentTarget; // Combat or pursue target (NPCs should not avoid collision with their targets).
for (const auto& package : ptr.getClass().getCreatureStats(ptr).getAiSequence()) const auto& aiSequence = ptr.getClass().getCreatureStats(ptr).getAiSequence();
for (const auto& package : aiSequence)
{ {
if (package->getTypeId() == AiPackageTypeId::Follow) if (package->getTypeId() == AiPackageTypeId::Follow)
shouldAvoidCollision = true; shouldAvoidCollision = true;
@ -1950,6 +1962,10 @@ namespace MWMechanics
osg::Vec2f movementCorrection(0, 0); osg::Vec2f movementCorrection(0, 0);
float angleToApproachingActor = 0; float angleToApproachingActor = 0;
const float timeToDestination = aiSequence.isEmpty()
? std::numeric_limits<float>::max()
: getTimeToDestination(**aiSequence.begin(), basePos, maxSpeed, duration, halfExtents);
// Iterate through all other actors and predict collisions. // Iterate through all other actors and predict collisions.
for(PtrActorMap::iterator otherIter(mActors.begin()); otherIter != mActors.end(); ++otherIter) for(PtrActorMap::iterator otherIter(mActors.begin()); otherIter != mActors.end(); ++otherIter)
{ {
@ -1986,7 +2002,7 @@ namespace MWMechanics
continue; // No solution; distance is always >= collisionDist. continue; // No solution; distance is always >= collisionDist.
float t = (-vr - std::sqrt(Dh)) / v2; float t = (-vr - std::sqrt(Dh)) / v2;
if (t < 0 || t > timeToCollision) if (t < 0 || t > timeToCollision || t > timeToDestination)
continue; continue;
// Check visibility and awareness last as it's expensive. // Check visibility and awareness last as it's expensive.
@ -2260,7 +2276,7 @@ namespace MWMechanics
static const bool avoidCollisions = Settings::Manager::getBool("NPCs avoid collisions", "Game"); static const bool avoidCollisions = Settings::Manager::getBool("NPCs avoid collisions", "Game");
if (avoidCollisions) if (avoidCollisions)
predictAndAvoidCollisions(); predictAndAvoidCollisions(duration);
timerUpdateHeadTrack += duration; timerUpdateHeadTrack += duration;
timerUpdateEquippedLight += duration; timerUpdateEquippedLight += duration;

@ -63,7 +63,7 @@ namespace MWMechanics
void purgeSpellEffects (int casterActorId); void purgeSpellEffects (int casterActorId);
void predictAndAvoidCollisions(); void predictAndAvoidCollisions(float duration);
public: public:

@ -28,6 +28,12 @@ namespace
{ {
return divisor == 0 ? std::numeric_limits<float>::max() * std::numeric_limits<float>::epsilon() : dividend / divisor; return divisor == 0 ? std::numeric_limits<float>::max() * std::numeric_limits<float>::epsilon() : dividend / divisor;
} }
float getPointTolerance(float speed, float duration, const osg::Vec3f& halfExtents)
{
const float actorTolerance = 2 * speed * duration + 1.2 * std::max(halfExtents.x(), halfExtents.y());
return std::max(MWMechanics::MIN_TOLERANCE, actorTolerance);
}
} }
MWMechanics::AiPackage::AiPackage(AiPackageTypeId typeId, const Options& options) : MWMechanics::AiPackage::AiPackage(AiPackageTypeId typeId, const Options& options) :
@ -98,6 +104,8 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f&
return false; return false;
} }
mLastDestinationTolerance = destTolerance;
const float distToTarget = distance(position, dest); const float distToTarget = distance(position, dest);
const bool isDestReached = (distToTarget <= destTolerance); const bool isDestReached = (distToTarget <= destTolerance);
const bool actorCanMoveByZ = canActorMoveByZAxis(actor); const bool actorCanMoveByZ = canActorMoveByZAxis(actor);
@ -148,9 +156,7 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f&
} }
} }
const float actorTolerance = 2 * actor.getClass().getMaxSpeed(actor) * duration const float pointTolerance = getPointTolerance(actor.getClass().getMaxSpeed(actor), duration, halfExtents);
+ 1.2 * std::max(halfExtents.x(), halfExtents.y());
const float pointTolerance = std::max(MIN_TOLERANCE, actorTolerance);
static const bool smoothMovement = Settings::Manager::getBool("smooth movement", "Game"); static const bool smoothMovement = Settings::Manager::getBool("smooth movement", "Game");
mPathFinder.update(position, pointTolerance, DEFAULT_TOLERANCE, mPathFinder.update(position, pointTolerance, DEFAULT_TOLERANCE,
@ -181,7 +187,7 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f&
zTurn(actor, zAngleToNext); zTurn(actor, zAngleToNext);
smoothTurn(actor, mPathFinder.getXAngleToNext(position.x(), position.y(), position.z()), 0); smoothTurn(actor, mPathFinder.getXAngleToNext(position.x(), position.y(), position.z()), 0);
const auto destination = mPathFinder.getPath().empty() ? dest : mPathFinder.getPath().front(); const auto destination = getNextPathPoint(dest);
mObstacleCheck.update(actor, destination, duration); mObstacleCheck.update(actor, destination, duration);
if (smoothMovement) if (smoothMovement)
@ -461,3 +467,15 @@ DetourNavigator::AreaCosts MWMechanics::AiPackage::getAreaCosts(const MWWorld::P
return costs; return costs;
} }
osg::Vec3f MWMechanics::AiPackage::getNextPathPoint(const osg::Vec3f& destination) const
{
return mPathFinder.getPath().empty() ? destination : mPathFinder.getPath().front();
}
float MWMechanics::AiPackage::getNextPathPointTolerance(float speed, float duration, const osg::Vec3f& halfExtents) const
{
if (mPathFinder.getPathSize() <= 1)
return mLastDestinationTolerance;
return getPointTolerance(speed, duration, halfExtents);
}

@ -122,6 +122,10 @@ namespace MWMechanics
/// Return if actor's rotation speed is sufficient to rotate to the destination pathpoint on the run. Otherwise actor should rotate while standing. /// Return if actor's rotation speed is sufficient to rotate to the destination pathpoint on the run. Otherwise actor should rotate while standing.
static bool isReachableRotatingOnTheRun(const MWWorld::Ptr& actor, const osg::Vec3f& dest); static bool isReachableRotatingOnTheRun(const MWWorld::Ptr& actor, const osg::Vec3f& dest);
osg::Vec3f getNextPathPoint(const osg::Vec3f& destination) const;
float getNextPathPointTolerance(float speed, float duration, const osg::Vec3f& halfExtents) const;
protected: protected:
/// Handles path building and shortcutting with obstacles avoiding /// Handles path building and shortcutting with obstacles avoiding
/** \return If the actor has arrived at his destination **/ /** \return If the actor has arrived at his destination **/
@ -166,6 +170,7 @@ namespace MWMechanics
bool mIsShortcutting; // if shortcutting at the moment bool mIsShortcutting; // if shortcutting at the moment
bool mShortcutProhibited; // shortcutting may be prohibited after unsuccessful attempt bool mShortcutProhibited; // shortcutting may be prohibited after unsuccessful attempt
osg::Vec3f mShortcutFailPos; // position of last shortcut fail osg::Vec3f mShortcutFailPos; // position of last shortcut fail
float mLastDestinationTolerance = 0;
private: private:
bool isNearInactiveCell(osg::Vec3f position); bool isNearInactiveCell(osg::Vec3f position);

@ -2650,7 +2650,7 @@ void CharacterController::update(float duration)
if (mFloatToSurface && cls.isActor()) if (mFloatToSurface && cls.isActor())
{ {
if (cls.getCreatureStats(mPtr).isDead() if (cls.getCreatureStats(mPtr).isDead()
|| (!godmode && cls.getCreatureStats(mPtr).isParalyzed())) || (!godmode && cls.getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Paralyze).getModifier() > 0))
{ {
moved.z() = 1.0; moved.z() = 1.0;
} }

@ -956,7 +956,7 @@ namespace MWPhysics
mWantJump = ptr.getClass().getMovementSettings(ptr).mPosition[2] != 0; mWantJump = ptr.getClass().getMovementSettings(ptr).mPosition[2] != 0;
auto& stats = ptr.getClass().getCreatureStats(ptr); auto& stats = ptr.getClass().getCreatureStats(ptr);
const bool godmode = ptr == world->getPlayerConstPtr() && world->getGodModeState(); const bool godmode = ptr == world->getPlayerConstPtr() && world->getGodModeState();
mFloatToSurface = stats.isDead() || (!godmode && stats.isParalyzed()); mFloatToSurface = stats.isDead() || (!godmode && stats.getMagicEffects().get(ESM::MagicEffect::Paralyze).getModifier() > 0);
mWasOnGround = actor->getOnGround(); mWasOnGround = actor->getOnGround();
} }

@ -61,6 +61,7 @@ namespace MWRender
void reset(unsigned int frame) void reset(unsigned int frame)
{ {
std::lock_guard<std::mutex> lock(mMutex);
mDone = false; mDone = false;
mFrame = frame; mFrame = frame;
} }
@ -104,11 +105,6 @@ namespace MWRender
, mResourceSystem(resourceSystem) , mResourceSystem(resourceSystem)
, mWater(water) , mWater(water)
{ {
// Note: This assumes no other final draw callbacks are set anywhere and that this callback will remain set until the application exits.
// This works around *DrawCallback manipulation being unsafe in OSG >= 3.5.10 for release 0.47
// If you need to set other final draw callbacks, read the comments of issue 6013 for a suggestion
// Ref https://gitlab.com/OpenMW/openmw/-/issues/6013
mViewer->getCamera()->setFinalDrawCallback(mDrawCompleteCallback);
} }
ScreenshotManager::~ScreenshotManager() ScreenshotManager::~ScreenshotManager()
@ -269,7 +265,10 @@ namespace MWRender
void ScreenshotManager::traversalsAndWait(unsigned int frame) void ScreenshotManager::traversalsAndWait(unsigned int frame)
{ {
// Ref https://gitlab.com/OpenMW/openmw/-/issues/6013
mDrawCompleteCallback->reset(frame); mDrawCompleteCallback->reset(frame);
mViewer->getCamera()->setFinalDrawCallback(mDrawCompleteCallback);
mViewer->eventTraversal(); mViewer->eventTraversal();
mViewer->updateTraversal(); mViewer->updateTraversal();
mViewer->renderingTraversals(); mViewer->renderingTraversals();

@ -260,10 +260,9 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot
writer.save (stream); writer.save (stream);
Loading::Listener& listener = *MWBase::Environment::get().getWindowManager()->getLoadingScreen(); Loading::Listener& listener = *MWBase::Environment::get().getWindowManager()->getLoadingScreen();
int messagesCount = MWBase::Environment::get().getWindowManager()->getMessagesCount();
// Using only Cells for progress information, since they typically have the largest records by far // Using only Cells for progress information, since they typically have the largest records by far
listener.setProgressRange(MWBase::Environment::get().getWorld()->countSavedGameCells()); listener.setProgressRange(MWBase::Environment::get().getWorld()->countSavedGameCells());
listener.setLabel("#{sNotifyMessage4}", true, messagesCount > 0); listener.setLabel("#{sNotifyMessage4}", true);
Loading::ScopedLoad load(&listener); Loading::ScopedLoad load(&listener);
@ -395,10 +394,9 @@ void MWState::StateManager::loadGame (const Character *character, const std::str
std::map<int, int> contentFileMap = buildContentFileIndexMap (reader); std::map<int, int> contentFileMap = buildContentFileIndexMap (reader);
Loading::Listener& listener = *MWBase::Environment::get().getWindowManager()->getLoadingScreen(); Loading::Listener& listener = *MWBase::Environment::get().getWindowManager()->getLoadingScreen();
int messagesCount = MWBase::Environment::get().getWindowManager()->getMessagesCount();
listener.setProgressRange(100); listener.setProgressRange(100);
listener.setLabel("#{sLoadingMessage14}", false, messagesCount > 0); listener.setLabel("#{sLoadingMessage14}");
Loading::ScopedLoad load(&listener); Loading::ScopedLoad load(&listener);

@ -542,7 +542,7 @@ namespace MWWorld
{ {
const auto navigator = MWBase::Environment::get().getWorld()->getNavigator(); const auto navigator = MWBase::Environment::get().getWorld()->getNavigator();
const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr(); const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr();
navigator->update(player.getRefData().getPosition().asVec3()); navigator->updatePlayerPosition(player.getRefData().getPosition().asVec3());
if (!mCurrentCell || !mCurrentCell->isExterior()) if (!mCurrentCell || !mCurrentCell->isExterior())
return; return;
@ -608,9 +608,8 @@ namespace MWWorld
Loading::Listener* loadingListener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); Loading::Listener* loadingListener = MWBase::Environment::get().getWindowManager()->getLoadingScreen();
Loading::ScopedLoad load(loadingListener); Loading::ScopedLoad load(loadingListener);
int messagesCount = MWBase::Environment::get().getWindowManager()->getMessagesCount();
std::string loadingExteriorText = "#{sLoadingMessage3}"; std::string loadingExteriorText = "#{sLoadingMessage3}";
loadingListener->setLabel(loadingExteriorText, false, messagesCount > 0); loadingListener->setLabel(loadingExteriorText);
loadingListener->setProgressRange(refsToLoad); loadingListener->setProgressRange(refsToLoad);
const auto getDistanceToPlayerCell = [&] (const std::pair<int, int>& cellPosition) const auto getDistanceToPlayerCell = [&] (const std::pair<int, int>& cellPosition)
@ -855,9 +854,8 @@ namespace MWWorld
MWBase::Environment::get().getWindowManager()->fadeScreenOut(0.5); MWBase::Environment::get().getWindowManager()->fadeScreenOut(0.5);
Loading::Listener* loadingListener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); Loading::Listener* loadingListener = MWBase::Environment::get().getWindowManager()->getLoadingScreen();
int messagesCount = MWBase::Environment::get().getWindowManager()->getMessagesCount();
std::string loadingInteriorText = "#{sLoadingMessage2}"; std::string loadingInteriorText = "#{sLoadingMessage2}";
loadingListener->setLabel(loadingInteriorText, false, messagesCount > 0); loadingListener->setLabel(loadingInteriorText);
Loading::ScopedLoad load(loadingListener); Loading::ScopedLoad load(loadingListener);
if(mCurrentCell != nullptr && *mCurrentCell == *cell) if(mCurrentCell != nullptr && *mCurrentCell == *cell)

@ -2601,7 +2601,7 @@ namespace MWWorld
return false; return false;
const bool isPlayer = ptr == getPlayerConstPtr(); const bool isPlayer = ptr == getPlayerConstPtr();
if (!(isPlayer && mGodMode) && stats.isParalyzed()) if (!(isPlayer && mGodMode) && stats.getMagicEffects().get(ESM::MagicEffect::Paralyze).getModifier() > 0)
return false; return false;
if (ptr.getClass().canFly(ptr)) if (ptr.getClass().canFly(ptr))

@ -60,6 +60,7 @@ namespace
EXPECT_FALSE(cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, EXPECT_FALSE(cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections,
std::move(mNavMeshData))); std::move(mNavMeshData)));
EXPECT_NE(mNavMeshData.mValue, nullptr);
} }
TEST_F(DetourNavigatorNavMeshTilesCacheTest, set_should_return_cached_value) TEST_F(DetourNavigatorNavMeshTilesCacheTest, set_should_return_cached_value)
@ -85,6 +86,7 @@ namespace
NavMeshData anotherNavMeshData {anotherData, 1}; NavMeshData anotherNavMeshData {anotherData, 1};
cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, std::move(mNavMeshData)); cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, std::move(mNavMeshData));
EXPECT_EQ(mNavMeshData.mValue, nullptr);
const auto result = cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, std::move(anotherNavMeshData)); const auto result = cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, std::move(anotherNavMeshData));
ASSERT_TRUE(result); ASSERT_TRUE(result);
EXPECT_EQ(result.get(), (NavMeshDataRef {mData, 1})); EXPECT_EQ(result.get(), (NavMeshDataRef {mData, 1}));

@ -54,7 +54,7 @@ namespace
{ {
TileCachedRecastMeshManager manager(mSettings); TileCachedRecastMeshManager manager(mSettings);
std::size_t calls = 0; std::size_t calls = 0;
manager.forEachTilePosition([&] (const TilePosition&) { ++calls; }); manager.forEachTile([&] (const TilePosition&, const CachedRecastMeshManager&) { ++calls; });
EXPECT_EQ(calls, 0); EXPECT_EQ(calls, 0);
} }
@ -73,6 +73,16 @@ namespace
EXPECT_FALSE(manager.addObject(ObjectId(&boxShape), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground)); EXPECT_FALSE(manager.addObject(ObjectId(&boxShape), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground));
} }
TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, add_object_should_add_tiles)
{
TileCachedRecastMeshManager manager(mSettings);
const btBoxShape boxShape(btVector3(20, 20, 100));
ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground));
for (int x = -1; x < 1; ++x)
for (int y = -1; y < 1; ++y)
ASSERT_TRUE(manager.hasTile(TilePosition(x, y)));
}
TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, update_object_for_changed_object_should_return_changed_tiles) TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, update_object_for_changed_object_should_return_changed_tiles)
{ {
TileCachedRecastMeshManager manager(mSettings); TileCachedRecastMeshManager manager(mSettings);
@ -238,4 +248,79 @@ namespace
manager.removeObject(ObjectId(&manager)); manager.removeObject(ObjectId(&manager));
EXPECT_EQ(manager.getRevision(), beforeRemoveRevision); EXPECT_EQ(manager.getRevision(), beforeRemoveRevision);
} }
TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, add_water_for_new_water_should_return_true)
{
TileCachedRecastMeshManager manager(mSettings);
const osg::Vec2i cellPosition(0, 0);
const int cellSize = 8192;
EXPECT_TRUE(manager.addWater(cellPosition, cellSize, btTransform::getIdentity()));
}
TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, add_water_for_not_max_int_should_add_new_tiles)
{
TileCachedRecastMeshManager manager(mSettings);
const osg::Vec2i cellPosition(0, 0);
const int cellSize = 8192;
ASSERT_TRUE(manager.addWater(cellPosition, cellSize, btTransform::getIdentity()));
for (int x = -6; x < 6; ++x)
for (int y = -6; y < 6; ++y)
ASSERT_TRUE(manager.hasTile(TilePosition(x, y)));
}
TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, add_water_for_max_int_should_not_add_new_tiles)
{
TileCachedRecastMeshManager manager(mSettings);
const btBoxShape boxShape(btVector3(20, 20, 100));
ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground));
const osg::Vec2i cellPosition(0, 0);
const int cellSize = std::numeric_limits<int>::max();
ASSERT_TRUE(manager.addWater(cellPosition, cellSize, btTransform::getIdentity()));
for (int x = -6; x < 6; ++x)
for (int y = -6; y < 6; ++y)
ASSERT_EQ(manager.hasTile(TilePosition(x, y)), -1 <= x && x <= 0 && -1 <= y && y <= 0);
}
TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, remove_water_for_absent_cell_should_return_nullopt)
{
TileCachedRecastMeshManager manager(mSettings);
EXPECT_EQ(manager.removeWater(osg::Vec2i(0, 0)), std::nullopt);
}
TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, remove_water_for_existing_cell_should_return_removed_water)
{
TileCachedRecastMeshManager manager(mSettings);
const osg::Vec2i cellPosition(0, 0);
const int cellSize = 8192;
ASSERT_TRUE(manager.addWater(cellPosition, cellSize, btTransform::getIdentity()));
const auto result = manager.removeWater(cellPosition);
ASSERT_TRUE(result.has_value());
EXPECT_EQ(result->mCellSize, cellSize);
}
TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, remove_water_for_existing_cell_should_remove_empty_tiles)
{
TileCachedRecastMeshManager manager(mSettings);
const osg::Vec2i cellPosition(0, 0);
const int cellSize = 8192;
ASSERT_TRUE(manager.addWater(cellPosition, cellSize, btTransform::getIdentity()));
ASSERT_TRUE(manager.removeWater(cellPosition));
for (int x = -6; x < 6; ++x)
for (int y = -6; y < 6; ++y)
ASSERT_FALSE(manager.hasTile(TilePosition(x, y)));
}
TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, remove_water_for_existing_cell_should_leave_not_empty_tiles)
{
TileCachedRecastMeshManager manager(mSettings);
const btBoxShape boxShape(btVector3(20, 20, 100));
ASSERT_TRUE(manager.addObject(ObjectId(&boxShape), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground));
const osg::Vec2i cellPosition(0, 0);
const int cellSize = 8192;
ASSERT_TRUE(manager.addWater(cellPosition, cellSize, btTransform::getIdentity()));
ASSERT_TRUE(manager.removeWater(cellPosition));
for (int x = -6; x < 6; ++x)
for (int y = -6; y < 6; ++y)
ASSERT_EQ(manager.hasTile(TilePosition(x, y)), -1 <= x && x <= 0 && -1 <= y && y <= 0);
}
} }

@ -182,8 +182,6 @@ void BSAFile::readHeader()
if(fs.offset + fs.fileSize > fsize) if(fs.offset + fs.fileSize > fsize)
fail("Archive contains offsets outside itself"); fail("Archive contains offsets outside itself");
// Add the file name to the lookup
mLookup[fs.name()] = i;
} }
mStringBuf.resize(endOfNameBuffer); mStringBuf.resize(endOfNameBuffer);
@ -191,6 +189,13 @@ void BSAFile::readHeader()
return left.offset < right.offset; return left.offset < right.offset;
}); });
for (size_t i = 0; i < filenum; i++)
{
FileStruct& fs = mFiles[i];
// Add the file name to the lookup
mLookup[fs.name()] = i;
}
mIsLoaded = true; mIsLoaded = true;
} }
@ -247,6 +252,9 @@ int BSAFile::getIndex(const char *str) const
/// Open an archive file. /// Open an archive file.
void BSAFile::open(const std::string &file) void BSAFile::open(const std::string &file)
{ {
if (mIsLoaded)
close();
mFilename = file; mFilename = file;
if(boost::filesystem::exists(file)) if(boost::filesystem::exists(file))
readHeader(); readHeader();
@ -254,16 +262,20 @@ void BSAFile::open(const std::string &file)
{ {
{ boost::filesystem::fstream(mFilename, std::ios::binary | std::ios::out); } { boost::filesystem::fstream(mFilename, std::ios::binary | std::ios::out); }
writeHeader(); writeHeader();
mIsLoaded = true;
} }
} }
/// Close the archive, write the updated headers to the file /// Close the archive, write the updated headers to the file
void Bsa::BSAFile::close() void Bsa::BSAFile::close()
{ {
if (!mHasChanged) if (mHasChanged)
return; writeHeader();
writeHeader(); mFiles.clear();
mStringBuf.clear();
mLookup.clear();
mIsLoaded = false;
} }
Files::IStreamPtr BSAFile::getFile(const char *file) Files::IStreamPtr BSAFile::getFile(const char *file)
@ -285,6 +297,8 @@ Files::IStreamPtr BSAFile::getFile(const FileStruct *file)
void Bsa::BSAFile::addFile(const std::string& filename, std::istream& file) void Bsa::BSAFile::addFile(const std::string& filename, std::istream& file)
{ {
if (!mIsLoaded)
fail("Unable to add file " + filename + " the archive is not opened");
namespace bfs = boost::filesystem; namespace bfs = boost::filesystem;
auto newStartOfDataBuffer = 12 + (12 + 8) * (mFiles.size() + 1) + mStringBuf.size() + filename.size() + 1; auto newStartOfDataBuffer = 12 + (12 + 8) * (mFiles.size() + 1) + mStringBuf.size() + filename.size() + 1;

@ -66,4 +66,9 @@ namespace DetourNavigator
{ {
mImpl.reportNavMeshChange(recastMeshVersion, navMeshVersion); mImpl.reportNavMeshChange(recastMeshVersion, navMeshVersion);
} }
Version CachedRecastMeshManager::getVersion() const
{
return mImpl.getVersion();
}
} }

@ -28,6 +28,8 @@ namespace DetourNavigator
void reportNavMeshChange(Version recastMeshVersion, Version navMeshVersion); void reportNavMeshChange(Version recastMeshVersion, Version navMeshVersion);
Version getVersion() const;
private: private:
RecastMeshManager mImpl; RecastMeshManager mImpl;
std::shared_ptr<RecastMesh> mCached; std::shared_ptr<RecastMesh> mCached;

@ -155,11 +155,17 @@ namespace DetourNavigator
virtual void removePathgrid(const ESM::Pathgrid& pathgrid) = 0; virtual void removePathgrid(const ESM::Pathgrid& pathgrid) = 0;
/** /**
* @brief update start background navmesh update using current scene state. * @brief update starts background navmesh update using current scene state.
* @param playerPosition setup initial point to order build tiles of navmesh. * @param playerPosition setup initial point to order build tiles of navmesh.
*/ */
virtual void update(const osg::Vec3f& playerPosition) = 0; virtual void update(const osg::Vec3f& playerPosition) = 0;
/**
* @brief updatePlayerPosition starts background navmesh update using current scene state only when player position has been changed.
* @param playerPosition setup initial point to order build tiles of navmesh.
*/
virtual void updatePlayerPosition(const osg::Vec3f& playerPosition) = 0;
/** /**
* @brief disable navigator updates * @brief disable navigator updates
*/ */

@ -146,6 +146,15 @@ namespace DetourNavigator
mNavMeshManager.update(playerPosition, v.first); mNavMeshManager.update(playerPosition, v.first);
} }
void NavigatorImpl::updatePlayerPosition(const osg::Vec3f& playerPosition)
{
const TilePosition tilePosition = getTilePosition(mSettings, toNavMeshCoordinates(mSettings, playerPosition));
if (mLastPlayerPosition.has_value() && *mLastPlayerPosition == tilePosition)
return;
update(playerPosition);
mLastPlayerPosition = tilePosition;
}
void NavigatorImpl::setUpdatesEnabled(bool enabled) void NavigatorImpl::setUpdatesEnabled(bool enabled)
{ {
mUpdatesEnabled = enabled; mUpdatesEnabled = enabled;

@ -46,6 +46,8 @@ namespace DetourNavigator
void update(const osg::Vec3f& playerPosition) override; void update(const osg::Vec3f& playerPosition) override;
void updatePlayerPosition(const osg::Vec3f& playerPosition) override;
void setUpdatesEnabled(bool enabled) override; void setUpdatesEnabled(bool enabled) override;
void wait(Loading::Listener& listener, WaitConditionType waitConditionType) override; void wait(Loading::Listener& listener, WaitConditionType waitConditionType) override;
@ -66,6 +68,7 @@ namespace DetourNavigator
Settings mSettings; Settings mSettings;
NavMeshManager mNavMeshManager; NavMeshManager mNavMeshManager;
bool mUpdatesEnabled; bool mUpdatesEnabled;
std::optional<TilePosition> mLastPlayerPosition;
std::map<osg::Vec3f, std::size_t> mAgents; std::map<osg::Vec3f, std::size_t> mAgents;
std::unordered_map<ObjectId, ObjectId> mAvoidIds; std::unordered_map<ObjectId, ObjectId> mAvoidIds;
std::unordered_map<ObjectId, ObjectId> mWaterIds; std::unordered_map<ObjectId, ObjectId> mWaterIds;

@ -71,6 +71,8 @@ namespace DetourNavigator
void update(const osg::Vec3f& /*playerPosition*/) override {} void update(const osg::Vec3f& /*playerPosition*/) override {}
void updatePlayerPosition(const osg::Vec3f& /*playerPosition*/) override {};
void setUpdatesEnabled(bool /*enabled*/) override {} void setUpdatesEnabled(bool /*enabled*/) override {}
void wait(Loading::Listener& /*listener*/, WaitConditionType /*waitConditionType*/) override {} void wait(Loading::Listener& /*listener*/, WaitConditionType /*waitConditionType*/) override {}

@ -171,7 +171,7 @@ namespace DetourNavigator
} }
} }
const auto maxTiles = std::min(mSettings.mMaxTilesNumber, navMesh.getParams()->maxTiles); const auto maxTiles = std::min(mSettings.mMaxTilesNumber, navMesh.getParams()->maxTiles);
mRecastMeshManager.forEachTilePosition([&] (const TilePosition& tile) mRecastMeshManager.forEachTile([&] (const TilePosition& tile, CachedRecastMeshManager& recastMeshManager)
{ {
if (tilesToPost.count(tile)) if (tilesToPost.count(tile))
return; return;
@ -181,6 +181,8 @@ namespace DetourNavigator
tilesToPost.insert(std::make_pair(tile, ChangeType::add)); tilesToPost.insert(std::make_pair(tile, ChangeType::add));
else if (!shouldAdd && presentInNavMesh) else if (!shouldAdd && presentInNavMesh)
tilesToPost.insert(std::make_pair(tile, ChangeType::mixed)); tilesToPost.insert(std::make_pair(tile, ChangeType::mixed));
else
recastMeshManager.reportNavMeshChange(recastMeshManager.getVersion(), Version {0, 0});
}); });
} }
mAsyncNavMeshUpdater.post(agentHalfExtents, cached, playerTile, tilesToPost); mAsyncNavMeshUpdater.post(agentHalfExtents, cached, playerTile, tilesToPost);
@ -214,8 +216,8 @@ namespace DetourNavigator
RecastMeshTiles NavMeshManager::getRecastMeshTiles() RecastMeshTiles NavMeshManager::getRecastMeshTiles()
{ {
std::vector<TilePosition> tiles; std::vector<TilePosition> tiles;
mRecastMeshManager.forEachTilePosition( mRecastMeshManager.forEachTile(
[&tiles] (const TilePosition& tile) { tiles.push_back(tile); }); [&tiles] (const TilePosition& tile, const CachedRecastMeshManager&) { tiles.push_back(tile); });
RecastMeshTiles result; RecastMeshTiles result;
std::transform(tiles.begin(), tiles.end(), std::inserter(result, result.end()), std::transform(tiles.begin(), tiles.end(), std::inserter(result, result.end()),
[this] (const TilePosition& tile) { return std::make_pair(tile, mRecastMeshManager.getMesh(tile)); }); [this] (const TilePosition& tile) { return std::make_pair(tile, mRecastMeshManager.getMesh(tile)); });

@ -1,9 +1,23 @@
#include "oscillatingrecastmeshobject.hpp" #include "oscillatingrecastmeshobject.hpp"
#include "tilebounds.hpp"
#include <components/bullethelpers/aabb.hpp> #include <components/bullethelpers/aabb.hpp>
#include <algorithm>
namespace DetourNavigator namespace DetourNavigator
{ {
namespace
{
void limitBy(btAABB& aabb, const TileBounds& bounds)
{
aabb.m_min.setX(std::max(aabb.m_min.x(), static_cast<btScalar>(bounds.mMin.x())));
aabb.m_min.setY(std::max(aabb.m_min.y(), static_cast<btScalar>(bounds.mMin.y())));
aabb.m_max.setX(std::min(aabb.m_max.x(), static_cast<btScalar>(bounds.mMax.x())));
aabb.m_max.setY(std::min(aabb.m_max.y(), static_cast<btScalar>(bounds.mMax.y())));
}
}
OscillatingRecastMeshObject::OscillatingRecastMeshObject(RecastMeshObject&& impl, std::size_t lastChangeRevision) OscillatingRecastMeshObject::OscillatingRecastMeshObject(RecastMeshObject&& impl, std::size_t lastChangeRevision)
: mImpl(std::move(impl)) : mImpl(std::move(impl))
, mLastChangeRevision(lastChangeRevision) , mLastChangeRevision(lastChangeRevision)
@ -19,7 +33,7 @@ namespace DetourNavigator
} }
bool OscillatingRecastMeshObject::update(const btTransform& transform, const AreaType areaType, bool OscillatingRecastMeshObject::update(const btTransform& transform, const AreaType areaType,
std::size_t lastChangeRevision) std::size_t lastChangeRevision, const TileBounds& bounds)
{ {
const btTransform oldTransform = mImpl.getTransform(); const btTransform oldTransform = mImpl.getTransform();
if (!mImpl.update(transform, areaType)) if (!mImpl.update(transform, areaType))
@ -37,6 +51,7 @@ namespace DetourNavigator
} }
const btAABB currentAabb = mAabb; const btAABB currentAabb = mAabb;
mAabb.merge(BulletHelpers::getAabb(mImpl.getShape(), transform)); mAabb.merge(BulletHelpers::getAabb(mImpl.getShape(), transform));
limitBy(mAabb, bounds);
return currentAabb != mAabb; return currentAabb != mAabb;
} }
} }

@ -3,6 +3,7 @@
#include "areatype.hpp" #include "areatype.hpp"
#include "recastmeshobject.hpp" #include "recastmeshobject.hpp"
#include "tilebounds.hpp"
#include <LinearMath/btTransform.h> #include <LinearMath/btTransform.h>
#include <BulletCollision/Gimpact/btBoxCollision.h> #include <BulletCollision/Gimpact/btBoxCollision.h>
@ -15,7 +16,8 @@ namespace DetourNavigator
explicit OscillatingRecastMeshObject(RecastMeshObject&& impl, std::size_t lastChangeRevision); explicit OscillatingRecastMeshObject(RecastMeshObject&& impl, std::size_t lastChangeRevision);
explicit OscillatingRecastMeshObject(const RecastMeshObject& impl, std::size_t lastChangeRevision); explicit OscillatingRecastMeshObject(const RecastMeshObject& impl, std::size_t lastChangeRevision);
bool update(const btTransform& transform, const AreaType areaType, std::size_t lastChangeRevision); bool update(const btTransform& transform, const AreaType areaType, std::size_t lastChangeRevision,
const TileBounds& bounds);
const RecastMeshObject& getImpl() const { return mImpl; } const RecastMeshObject& getImpl() const { return mImpl; }

@ -5,19 +5,19 @@ namespace DetourNavigator
RecastMeshManager::RecastMeshManager(const Settings& settings, const TileBounds& bounds, std::size_t generation) RecastMeshManager::RecastMeshManager(const Settings& settings, const TileBounds& bounds, std::size_t generation)
: mGeneration(generation) : mGeneration(generation)
, mMeshBuilder(settings, bounds) , mMeshBuilder(settings, bounds)
, mTileBounds(bounds)
{ {
} }
bool RecastMeshManager::addObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform, bool RecastMeshManager::addObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform,
const AreaType areaType) const AreaType areaType)
{ {
const auto object = mObjects.lower_bound(id);
if (object != mObjects.end() && object->first == id)
return false;
const auto iterator = mObjectsOrder.emplace(mObjectsOrder.end(), const auto iterator = mObjectsOrder.emplace(mObjectsOrder.end(),
OscillatingRecastMeshObject(RecastMeshObject(shape, transform, areaType), mRevision + 1)); OscillatingRecastMeshObject(RecastMeshObject(shape, transform, areaType), mRevision + 1));
if (!mObjects.emplace(id, iterator).second) mObjects.emplace_hint(object, id, iterator);
{
mObjectsOrder.erase(iterator);
return false;
}
++mRevision; ++mRevision;
return true; return true;
} }
@ -29,7 +29,7 @@ namespace DetourNavigator
return false; return false;
const std::size_t lastChangeRevision = mLastNavMeshReportedChange.has_value() const std::size_t lastChangeRevision = mLastNavMeshReportedChange.has_value()
? mLastNavMeshReportedChange->mRevision : mRevision; ? mLastNavMeshReportedChange->mRevision : mRevision;
if (!object->second->update(transform, areaType, lastChangeRevision)) if (!object->second->update(transform, areaType, lastChangeRevision, mTileBounds))
return false; return false;
++mRevision; ++mRevision;
return true; return true;
@ -95,6 +95,11 @@ namespace DetourNavigator
mLastNavMeshReportedChange = mLastNavMeshReport; mLastNavMeshReportedChange = mLastNavMeshReport;
} }
Version RecastMeshManager::getVersion() const
{
return Version {mGeneration, mRevision};
}
void RecastMeshManager::rebuild() void RecastMeshManager::rebuild()
{ {
mMeshBuilder.reset(); mMeshBuilder.reset();

@ -13,7 +13,6 @@
#include <list> #include <list>
#include <map> #include <map>
#include <optional> #include <optional>
#include <unordered_map>
class btCollisionShape; class btCollisionShape;
@ -53,6 +52,8 @@ namespace DetourNavigator
void reportNavMeshChange(Version recastMeshVersion, Version navMeshVersion); void reportNavMeshChange(Version recastMeshVersion, Version navMeshVersion);
Version getVersion() const;
private: private:
struct Report struct Report
{ {
@ -63,8 +64,9 @@ namespace DetourNavigator
std::size_t mRevision = 0; std::size_t mRevision = 0;
std::size_t mGeneration; std::size_t mGeneration;
RecastMeshBuilder mMeshBuilder; RecastMeshBuilder mMeshBuilder;
TileBounds mTileBounds;
std::list<OscillatingRecastMeshObject> mObjectsOrder; std::list<OscillatingRecastMeshObject> mObjectsOrder;
std::unordered_map<ObjectId, std::list<OscillatingRecastMeshObject>::iterator> mObjects; std::map<ObjectId, std::list<OscillatingRecastMeshObject>::iterator> mObjects;
std::list<Water> mWaterOrder; std::list<Water> mWaterOrder;
std::map<osg::Vec2i, std::list<Water>::iterator> mWater; std::map<osg::Vec2i, std::list<Water>::iterator> mWater;
std::optional<Report> mLastNavMeshReportedChange; std::optional<Report> mLastNavMeshReportedChange;

@ -3,6 +3,9 @@
#include "gettilespositions.hpp" #include "gettilespositions.hpp"
#include "settingsutils.hpp" #include "settingsutils.hpp"
#include <algorithm>
#include <vector>
namespace DetourNavigator namespace DetourNavigator
{ {
TileCachedRecastMeshManager::TileCachedRecastMeshManager(const Settings& settings) TileCachedRecastMeshManager::TileCachedRecastMeshManager(const Settings& settings)
@ -12,23 +15,22 @@ namespace DetourNavigator
bool TileCachedRecastMeshManager::addObject(const ObjectId id, const btCollisionShape& shape, bool TileCachedRecastMeshManager::addObject(const ObjectId id, const btCollisionShape& shape,
const btTransform& transform, const AreaType areaType) const btTransform& transform, const AreaType areaType)
{ {
bool result = false; std::vector<TilePosition> tilesPositions;
auto& tilesPositions = mObjectsTilesPositions[id];
const auto border = getBorderSize(mSettings); const auto border = getBorderSize(mSettings);
{ {
auto tiles = mTiles.lock(); auto tiles = mTiles.lock();
getTilesPositions(shape, transform, mSettings, [&] (const TilePosition& tilePosition) getTilesPositions(shape, transform, mSettings, [&] (const TilePosition& tilePosition)
{ {
if (addTile(id, shape, transform, areaType, tilePosition, border, tiles.get())) if (addTile(id, shape, transform, areaType, tilePosition, border, tiles.get()))
{ tilesPositions.push_back(tilePosition);
tilesPositions.insert(tilePosition);
result = true;
}
}); });
} }
if (result) if (tilesPositions.empty())
++mRevision; return false;
return result; std::sort(tilesPositions.begin(), tilesPositions.end());
mObjectsTilesPositions.insert_or_assign(id, std::move(tilesPositions));
++mRevision;
return true;
} }
std::optional<RemovedRecastMeshObject> TileCachedRecastMeshManager::removeObject(const ObjectId id) std::optional<RemovedRecastMeshObject> TileCachedRecastMeshManager::removeObject(const ObjectId id)

@ -9,9 +9,10 @@
#include <components/misc/guarded.hpp> #include <components/misc/guarded.hpp>
#include <algorithm>
#include <map> #include <map>
#include <mutex> #include <mutex>
#include <set> #include <vector>
namespace DetourNavigator namespace DetourNavigator
{ {
@ -33,14 +34,14 @@ namespace DetourNavigator
auto& currentTiles = object->second; auto& currentTiles = object->second;
const auto border = getBorderSize(mSettings); const auto border = getBorderSize(mSettings);
bool changed = false; bool changed = false;
std::set<TilePosition> newTiles; std::vector<TilePosition> newTiles;
{ {
auto tiles = mTiles.lock(); auto tiles = mTiles.lock();
const auto onTilePosition = [&] (const TilePosition& tilePosition) const auto onTilePosition = [&] (const TilePosition& tilePosition)
{ {
if (currentTiles.count(tilePosition)) if (std::binary_search(currentTiles.begin(), currentTiles.end(), tilePosition))
{ {
newTiles.insert(tilePosition); newTiles.push_back(tilePosition);
if (updateTile(id, transform, areaType, tilePosition, tiles.get())) if (updateTile(id, transform, areaType, tilePosition, tiles.get()))
{ {
onChangedTile(tilePosition); onChangedTile(tilePosition);
@ -49,24 +50,27 @@ namespace DetourNavigator
} }
else if (addTile(id, shape, transform, areaType, tilePosition, border, tiles.get())) else if (addTile(id, shape, transform, areaType, tilePosition, border, tiles.get()))
{ {
newTiles.insert(tilePosition); newTiles.push_back(tilePosition);
onChangedTile(tilePosition); onChangedTile(tilePosition);
changed = true; changed = true;
} }
}; };
getTilesPositions(shape, transform, mSettings, onTilePosition); getTilesPositions(shape, transform, mSettings, onTilePosition);
std::sort(newTiles.begin(), newTiles.end());
for (const auto& tile : currentTiles) for (const auto& tile : currentTiles)
{ {
if (!newTiles.count(tile) && removeTile(id, tile, tiles.get())) if (!std::binary_search(newTiles.begin(), newTiles.end(), tile) && removeTile(id, tile, tiles.get()))
{ {
onChangedTile(tile); onChangedTile(tile);
changed = true; changed = true;
} }
} }
} }
std::swap(currentTiles, newTiles);
if (changed) if (changed)
{
currentTiles = std::move(newTiles);
++mRevision; ++mRevision;
}
return changed; return changed;
} }
@ -81,10 +85,10 @@ namespace DetourNavigator
bool hasTile(const TilePosition& tilePosition); bool hasTile(const TilePosition& tilePosition);
template <class Function> template <class Function>
void forEachTilePosition(Function&& function) void forEachTile(Function&& function)
{ {
for (const auto& tile : *mTiles.lock()) for (auto& [tilePosition, recastMeshManager] : *mTiles.lock())
function(tile.first); function(tilePosition, recastMeshManager);
} }
std::size_t getRevision() const; std::size_t getRevision() const;
@ -94,7 +98,7 @@ namespace DetourNavigator
private: private:
const Settings& mSettings; const Settings& mSettings;
Misc::ScopeGuarded<std::map<TilePosition, CachedRecastMeshManager>> mTiles; Misc::ScopeGuarded<std::map<TilePosition, CachedRecastMeshManager>> mTiles;
std::unordered_map<ObjectId, std::set<TilePosition>> mObjectsTilesPositions; std::unordered_map<ObjectId, std::vector<TilePosition>> mObjectsTilesPositions;
std::map<osg::Vec2i, std::vector<TilePosition>> mWaterTilesPositions; std::map<osg::Vec2i, std::vector<TilePosition>> mWaterTilesPositions;
std::size_t mRevision = 0; std::size_t mRevision = 0;
std::size_t mTilesGeneration = 0; std::size_t mTilesGeneration = 0;

@ -14,7 +14,7 @@ namespace Loading
/// @note "non-important" labels may not show on screen if the loading process went so fast /// @note "non-important" labels may not show on screen if the loading process went so fast
/// that the implementation decided not to show a loading screen at all. "important" labels /// that the implementation decided not to show a loading screen at all. "important" labels
/// will show in a separate message-box if the loading screen was not shown. /// will show in a separate message-box if the loading screen was not shown.
virtual void setLabel (const std::string& label, bool important=false, bool center=false) {} virtual void setLabel (const std::string& label, bool important=false) {}
/// Start a loading sequence. Must call loadingOff() when done. /// Start a loading sequence. Must call loadingOff() when done.
/// @note To get the loading screen to actually update, you must call setProgress / increaseProgress periodically. /// @note To get the loading screen to actually update, you must call setProgress / increaseProgress periodically.

@ -45,19 +45,19 @@ public:
{ {
// Russian alphabet // Russian alphabet
if (ch >= 0x0410 && ch < 0x0430) if (ch >= 0x0410 && ch < 0x0430)
return ch += 0x20; return ch + 0x20;
// Cyrillic IO character // Cyrillic IO character
if (ch == 0x0401) if (ch == 0x0401)
return ch += 0x50; return ch + 0x50;
// Latin alphabet // Latin alphabet
if (ch >= 0x41 && ch < 0x60) if (ch >= 0x41 && ch < 0x60)
return ch += 0x20; return ch + 0x20;
// Deutch characters // Deutch characters
if (ch == 0xc4 || ch == 0xd6 || ch == 0xdc) if (ch == 0xc4 || ch == 0xd6 || ch == 0xdc)
return ch += 0x20; return ch + 0x20;
if (ch == 0x1e9e) if (ch == 0x1e9e)
return 0xdf; return 0xdf;

@ -103,47 +103,12 @@ namespace SceneUtil
: mData(new osg::FloatArray(3*4*count)) : mData(new osg::FloatArray(3*4*count))
, mEndian(osg::getCpuByteOrder()) , mEndian(osg::getCpuByteOrder())
, mCount(count) , mCount(count)
, mStride(12)
, mCachedSunPos(osg::Vec4()) , mCachedSunPos(osg::Vec4())
{ {
mOffsets[Diffuse] = 0;
mOffsets[Ambient] = 1;
mOffsets[Specular] = 2;
mOffsets[DiffuseSign] = 3;
mOffsets[Position] = 4;
mOffsets[AttenuationRadius] = 8;
} }
LightBuffer(const LightBuffer&) = delete; LightBuffer(const LightBuffer&) = delete;
LightBuffer(const LightBuffer& other, int offsetColors, int offsetPosition, int offsetAttenuationRadius, int size, int stride)
: mData(new osg::FloatArray(size / sizeof(GL_FLOAT)))
, mEndian(other.mEndian)
, mCount(other.mCount)
, mStride((offsetAttenuationRadius + sizeof(GL_FLOAT) * osg::Vec4::num_components + stride) / 4)
, mCachedSunPos(other.mCachedSunPos)
{
mData->setBufferObject(other.mData->getBufferObject());
constexpr auto sizeofFloat = sizeof(GL_FLOAT);
const auto diffuseOffset = offsetColors / sizeofFloat;
mOffsets[Diffuse] = diffuseOffset;
mOffsets[Ambient] = diffuseOffset + 1;
mOffsets[Specular] = diffuseOffset + 2;
mOffsets[DiffuseSign] = diffuseOffset + 3;
mOffsets[Position] = offsetPosition / sizeofFloat;
mOffsets[AttenuationRadius] = offsetAttenuationRadius / sizeofFloat;
// Copy over previous buffers light data. Buffers populate before we know the layout.
for (int i = 0; i < other.mCount; ++i)
{
std::memcpy(&(*mData)[getOffset(i, Diffuse)], &(*other.mData)[other.getOffset(i, Diffuse)], sizeof(osg::Vec4f));
std::memcpy(&(*mData)[getOffset(i, Position)], &(*other.mData)[other.getOffset(i, Position)], sizeof(osg::Vec4f));
std::memcpy(&(*mData)[getOffset(i, AttenuationRadius)], &(*other.mData)[other.getOffset(i, AttenuationRadius)], sizeof(osg::Vec4f));
}
}
void setDiffuse(int index, const osg::Vec4& value) void setDiffuse(int index, const osg::Vec4& value)
{ {
// Deal with negative lights (negative diffuse) by passing a sign bit in the unused alpha component // Deal with negative lights (negative diffuse) by passing a sign bit in the unused alpha component
@ -214,15 +179,69 @@ namespace SceneUtil
int getOffset(int index, LayoutOffset slot) const int getOffset(int index, LayoutOffset slot) const
{ {
return mStride * index + mOffsets[slot]; return mOffsets.get(index, slot);
}
void configureLayout(int offsetColors, int offsetPosition, int offsetAttenuationRadius, int size, int stride)
{
const Offsets offsets(offsetColors, offsetPosition, offsetAttenuationRadius, stride);
// Copy cloned data using current layout into current data using new layout.
// This allows to preserve osg::FloatArray buffer object in mData.
const auto data = mData->asVector();
mData->resizeArray(static_cast<unsigned>(size));
for (int i = 0; i < mCount; ++i)
{
std::memcpy(&(*mData)[offsets.get(i, Diffuse)], data.data() + getOffset(i, Diffuse), sizeof(osg::Vec4f));
std::memcpy(&(*mData)[offsets.get(i, Position)], data.data() + getOffset(i, Position), sizeof(osg::Vec4f));
std::memcpy(&(*mData)[offsets.get(i, AttenuationRadius)], data.data() + getOffset(i, AttenuationRadius), sizeof(osg::Vec4f));
}
mOffsets = offsets;
} }
private: private:
class Offsets
{
public:
Offsets()
: mStride(12)
{
mValues[Diffuse] = 0;
mValues[Ambient] = 1;
mValues[Specular] = 2;
mValues[DiffuseSign] = 3;
mValues[Position] = 4;
mValues[AttenuationRadius] = 8;
}
Offsets(int offsetColors, int offsetPosition, int offsetAttenuationRadius, int stride)
: mStride((offsetAttenuationRadius + sizeof(GL_FLOAT) * osg::Vec4::num_components + stride) / 4)
{
constexpr auto sizeofFloat = sizeof(GL_FLOAT);
const auto diffuseOffset = offsetColors / sizeofFloat;
mValues[Diffuse] = diffuseOffset;
mValues[Ambient] = diffuseOffset + 1;
mValues[Specular] = diffuseOffset + 2;
mValues[DiffuseSign] = diffuseOffset + 3;
mValues[Position] = offsetPosition / sizeofFloat;
mValues[AttenuationRadius] = offsetAttenuationRadius / sizeofFloat;
}
int get(int index, LayoutOffset slot) const
{
return mStride * index + mValues[slot];
}
private:
int mStride;
std::array<int, 6> mValues;
};
osg::ref_ptr<osg::FloatArray> mData; osg::ref_ptr<osg::FloatArray> mData;
osg::Endian mEndian; osg::Endian mEndian;
int mCount; int mCount;
int mStride; Offsets mOffsets;
std::array<std::size_t, 6> mOffsets;
osg::Vec4 mCachedSunPos; osg::Vec4 mCachedSunPos;
}; };
@ -751,10 +770,7 @@ namespace SceneUtil
ext->glGetActiveUniformsiv(handle, indices.size(), indices.data(), GL_UNIFORM_OFFSET, offsets.data()); ext->glGetActiveUniformsiv(handle, indices.size(), indices.data(), GL_UNIFORM_OFFSET, offsets.data());
for (int i = 0; i < 2; ++i) for (int i = 0; i < 2; ++i)
{ mLightManager->getLightBuffer(i)->configureLayout(offsets[0], offsets[1], offsets[2], totalBlockSize, stride);
auto& buf = mLightManager->getLightBuffer(i);
buf = new LightBuffer(*buf, offsets[0], offsets[1], offsets[2], totalBlockSize, stride);
}
} }
void apply(osg::State& state) const override void apply(osg::State& state) const override

@ -498,7 +498,7 @@
</Widget> </Widget>
<Widget type="ScrollBar" skin="MW_HScroll" position="0 104 352 18" align="HStretch Top"> <Widget type="ScrollBar" skin="MW_HScroll" position="0 104 352 18" align="HStretch Top">
<Property key="Range" value="8192"/> <Property key="Range" value="8192"/>
<Property key="Page" value="1"/> <Property key="Page" value="128"/>
<UserString key="SettingType" value="Slider"/> <UserString key="SettingType" value="Slider"/>
<UserString key="SettingMin" value="0"/> <UserString key="SettingMin" value="0"/>
<UserString key="SettingMax" value="8192"/> <UserString key="SettingMax" value="8192"/>

@ -507,6 +507,19 @@
</layout> </layout>
</widget> </widget>
</item> </item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout> </layout>
</widget> </widget>
</widget> </widget>

Loading…
Cancel
Save