1 #include <BulletCollision/BroadphaseCollision/btDbvtBroadphase.h>
2 #include <BulletCollision/CollisionShapes/btCollisionShape.h>
3 
4 #include <osg/Stats>
5 
6 #include "components/debug/debuglog.hpp"
7 #include <components/misc/barrier.hpp>
8 #include "components/misc/convert.hpp"
9 #include "components/settings/settings.hpp"
10 #include "../mwmechanics/actorutil.hpp"
11 #include "../mwmechanics/movement.hpp"
12 #include "../mwrender/bulletdebugdraw.hpp"
13 #include "../mwworld/class.hpp"
14 #include "../mwworld/player.hpp"
15 
16 #include "actor.hpp"
17 #include "contacttestwrapper.h"
18 #include "movementsolver.hpp"
19 #include "mtphysics.hpp"
20 #include "object.hpp"
21 #include "physicssystem.hpp"
22 #include "projectile.hpp"
23 
24 namespace
25 {
26     /// @brief A scoped lock that is either shared or exclusive depending on configuration
27     template<class Mutex>
28     class MaybeSharedLock
29     {
30         public:
31             /// @param mutex a shared mutex
32             /// @param canBeSharedLock decide wether the lock will be shared or exclusive
MaybeSharedLock(Mutex & mutex,bool canBeSharedLock)33             MaybeSharedLock(Mutex& mutex, bool canBeSharedLock) : mMutex(mutex), mCanBeSharedLock(canBeSharedLock)
34             {
35                 if (mCanBeSharedLock)
36                     mMutex.lock_shared();
37                 else
38                     mMutex.lock();
39             }
40 
~MaybeSharedLock()41             ~MaybeSharedLock()
42             {
43                 if (mCanBeSharedLock)
44                     mMutex.unlock_shared();
45                 else
46                     mMutex.unlock();
47             }
48         private:
49             Mutex& mMutex;
50             bool mCanBeSharedLock;
51     };
52 
handleFall(MWPhysics::ActorFrameData & actorData,bool simulationPerformed)53     void handleFall(MWPhysics::ActorFrameData& actorData, bool simulationPerformed)
54     {
55         const float heightDiff = actorData.mPosition.z() - actorData.mOldHeight;
56 
57         const bool isStillOnGround = (simulationPerformed && actorData.mWasOnGround && actorData.mActorRaw->getOnGround());
58 
59         if (isStillOnGround || actorData.mFlying || actorData.mSwimming || actorData.mSlowFall < 1)
60             actorData.mNeedLand = true;
61         else if (heightDiff < 0)
62             actorData.mFallHeight += heightDiff;
63     }
64 
handleJump(const MWWorld::Ptr & ptr)65     void handleJump(const MWWorld::Ptr &ptr)
66     {
67         const bool isPlayer = (ptr == MWMechanics::getPlayer());
68         // Advance acrobatics and set flag for GetPCJumping
69         if (isPlayer)
70         {
71             ptr.getClass().skillUsageSucceeded(ptr, ESM::Skill::Acrobatics, 0);
72             MWBase::Environment::get().getWorld()->getPlayer().setJumping(true);
73         }
74 
75         // Decrease fatigue
76         if (!isPlayer || !MWBase::Environment::get().getWorld()->getGodModeState())
77         {
78             const MWWorld::Store<ESM::GameSetting> &gmst = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
79             const float fFatigueJumpBase = gmst.find("fFatigueJumpBase")->mValue.getFloat();
80             const float fFatigueJumpMult = gmst.find("fFatigueJumpMult")->mValue.getFloat();
81             const float normalizedEncumbrance = std::min(1.f, ptr.getClass().getNormalizedEncumbrance(ptr));
82             const float fatigueDecrease = fFatigueJumpBase + normalizedEncumbrance * fFatigueJumpMult;
83             MWMechanics::DynamicStat<float> fatigue = ptr.getClass().getCreatureStats(ptr).getFatigue();
84             fatigue.setCurrent(fatigue.getCurrent() - fatigueDecrease);
85             ptr.getClass().getCreatureStats(ptr).setFatigue(fatigue);
86         }
87         ptr.getClass().getMovementSettings(ptr).mPosition[2] = 0;
88     }
89 
updateMechanics(MWPhysics::ActorFrameData & actorData)90     void updateMechanics(MWPhysics::ActorFrameData& actorData)
91     {
92         auto ptr = actorData.mActorRaw->getPtr();
93         if (actorData.mDidJump)
94             handleJump(ptr);
95 
96         MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr);
97         if (actorData.mNeedLand)
98             stats.land(ptr == MWMechanics::getPlayer() && (actorData.mFlying || actorData.mSwimming));
99         else if (actorData.mFallHeight < 0)
100             stats.addToFallHeight(-actorData.mFallHeight);
101     }
102 
interpolateMovements(MWPhysics::ActorFrameData & actorData,float timeAccum,float physicsDt)103     osg::Vec3f interpolateMovements(MWPhysics::ActorFrameData& actorData, float timeAccum, float physicsDt)
104     {
105         const float interpolationFactor = std::clamp(timeAccum / physicsDt, 0.0f, 1.0f);
106         return actorData.mPosition * interpolationFactor + actorData.mActorRaw->getPreviousPosition() * (1.f - interpolationFactor);
107     }
108 
109     namespace Config
110     {
111         /// @return either the number of thread as configured by the user, or 1 if Bullet doesn't support multithreading
computeNumThreads(bool & threadSafeBullet)112         int computeNumThreads(bool& threadSafeBullet)
113         {
114             int wantedThread = Settings::Manager::getInt("async num threads", "Physics");
115 
116             auto broad = std::make_unique<btDbvtBroadphase>();
117             auto maxSupportedThreads = broad->m_rayTestStacks.size();
118             threadSafeBullet = (maxSupportedThreads > 1);
119             if (!threadSafeBullet && wantedThread > 1)
120             {
121                 Log(Debug::Warning) << "Bullet was not compiled with multithreading support, 1 async thread will be used";
122                 return 1;
123             }
124             return std::max(0, wantedThread);
125         }
126     }
127 }
128 
129 namespace MWPhysics
130 {
PhysicsTaskScheduler(float physicsDt,btCollisionWorld * collisionWorld,MWRender::DebugDrawer * debugDrawer)131     PhysicsTaskScheduler::PhysicsTaskScheduler(float physicsDt, btCollisionWorld *collisionWorld, MWRender::DebugDrawer* debugDrawer)
132           : mDefaultPhysicsDt(physicsDt)
133           , mPhysicsDt(physicsDt)
134           , mTimeAccum(0.f)
135           , mCollisionWorld(collisionWorld)
136           , mDebugDrawer(debugDrawer)
137           , mNumJobs(0)
138           , mRemainingSteps(0)
139           , mLOSCacheExpiry(Settings::Manager::getInt("lineofsight keep inactive cache", "Physics"))
140           , mDeferAabbUpdate(Settings::Manager::getBool("defer aabb update", "Physics"))
141           , mNewFrame(false)
142           , mAdvanceSimulation(false)
143           , mQuit(false)
144           , mNextJob(0)
145           , mNextLOS(0)
146           , mFrameNumber(0)
147           , mTimer(osg::Timer::instance())
148           , mPrevStepCount(1)
149           , mBudget(physicsDt)
150           , mAsyncBudget(0.0f)
151           , mBudgetCursor(0)
152           , mAsyncStartTime(0)
153           , mTimeBegin(0)
154           , mTimeEnd(0)
155           , mFrameStart(0)
156     {
157         mNumThreads = Config::computeNumThreads(mThreadSafeBullet);
158 
159         if (mNumThreads >= 1)
160         {
161             for (int i = 0; i < mNumThreads; ++i)
162                 mThreads.emplace_back([&] { worker(); } );
163         }
164         else
165         {
166             mLOSCacheExpiry = -1;
167             mDeferAabbUpdate = false;
168         }
169 
170         mPreStepBarrier = std::make_unique<Misc::Barrier>(mNumThreads);
171 
172         mPostStepBarrier = std::make_unique<Misc::Barrier>(mNumThreads);
173 
174         mPostSimBarrier = std::make_unique<Misc::Barrier>(mNumThreads);
175     }
176 
~PhysicsTaskScheduler()177     PhysicsTaskScheduler::~PhysicsTaskScheduler()
178     {
179         std::unique_lock lock(mSimulationMutex);
180         mQuit = true;
181         mNumJobs = 0;
182         mRemainingSteps = 0;
183         lock.unlock();
184         mHasJob.notify_all();
185         for (auto& thread : mThreads)
186             thread.join();
187     }
188 
calculateStepConfig(float timeAccum) const189     std::tuple<int, float> PhysicsTaskScheduler::calculateStepConfig(float timeAccum) const
190     {
191         int maxAllowedSteps = 2;
192         int numSteps = timeAccum / mDefaultPhysicsDt;
193 
194         // adjust maximum step count based on whether we're likely physics bottlenecked or not
195         // if maxAllowedSteps ends up higher than numSteps, we will not invoke delta time
196         // if it ends up lower than numSteps, but greater than 1, we will run a number of true delta time physics steps that we expect to be within budget
197         // if it ends up lower than numSteps and also 1, we will run a single delta time physics step
198         // if we did not do this, and had a fixed step count limit,
199         // we would have an unnecessarily low render framerate if we were only physics bottlenecked,
200         // and we would be unnecessarily invoking true delta time if we were only render bottlenecked
201 
202         // get physics timing stats
203         float budgetMeasurement = std::max(mBudget.get(), mAsyncBudget.get());
204         // time spent per step in terms of the intended physics framerate
205         budgetMeasurement /= mDefaultPhysicsDt;
206         // ensure sane minimum value
207         budgetMeasurement = std::max(0.00001f, budgetMeasurement);
208         // we're spending almost or more than realtime per physics frame; limit to a single step
209         if (budgetMeasurement > 0.95)
210             maxAllowedSteps = 1;
211         // physics is fairly cheap; limit based on expense
212         if (budgetMeasurement < 0.5)
213             maxAllowedSteps = std::ceil(1.0/budgetMeasurement);
214         // limit to a reasonable amount
215         maxAllowedSteps = std::min(10, maxAllowedSteps);
216 
217         // fall back to delta time for this frame if fixed timestep physics would fall behind
218         float actualDelta = mDefaultPhysicsDt;
219         if (numSteps > maxAllowedSteps)
220         {
221             numSteps = maxAllowedSteps;
222             // ensure that we do not simulate a frame ahead when doing delta time; this reduces stutter and latency
223             // this causes interpolation to 100% use the most recent physics result when true delta time is happening
224             // and we deliberately simulate up to exactly the timestamp that we want to render
225             actualDelta = timeAccum/float(numSteps+1);
226             // actually: if this results in a per-step delta less than the target physics steptime, clamp it
227             // this might reintroduce some stutter, but only comes into play in obscure cases
228             // (because numSteps is originally based on mDefaultPhysicsDt, this won't cause us to overrun)
229             actualDelta = std::max(actualDelta, mDefaultPhysicsDt);
230         }
231 
232         return std::make_tuple(numSteps, actualDelta);
233     }
234 
moveActors(float & timeAccum,std::vector<ActorFrameData> && actorsData,osg::Timer_t frameStart,unsigned int frameNumber,osg::Stats & stats)235     const std::vector<MWWorld::Ptr>& PhysicsTaskScheduler::moveActors(float & timeAccum, std::vector<ActorFrameData>&& actorsData, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats)
236     {
237         // This function run in the main thread.
238         // While the mSimulationMutex is held, background physics threads can't run.
239 
240         std::unique_lock lock(mSimulationMutex);
241 
242         double timeStart = mTimer->tick();
243 
244         mMovedActors.clear();
245 
246         // start by finishing previous background computation
247         if (mNumThreads != 0)
248         {
249             for (auto& data : mActorsFrameData)
250             {
251                 const auto actorActive = [&data](const auto& newFrameData) -> bool
252                 {
253                     const auto actor = data.mActor.lock();
254                     return actor && actor->getPtr() == newFrameData.mActorRaw->getPtr();
255                 };
256                 // Only return actors that are still part of the scene
257                 if (std::any_of(actorsData.begin(), actorsData.end(), actorActive))
258                 {
259                     updateMechanics(data);
260 
261                     // these variables are accessed directly from the main thread, update them here to prevent accessing "too new" values
262                     if (mAdvanceSimulation)
263                         data.mActorRaw->setStandingOnPtr(data.mStandingOn);
264                     data.mActorRaw->setSimulationPosition(interpolateMovements(data, mTimeAccum, mPhysicsDt));
265                     mMovedActors.emplace_back(data.mActorRaw->getPtr());
266                 }
267             }
268             if(mAdvanceSimulation)
269                 mAsyncBudget.update(mTimer->delta_s(mAsyncStartTime, mTimeEnd), mPrevStepCount, mBudgetCursor);
270             updateStats(frameStart, frameNumber, stats);
271         }
272 
273         auto [numSteps, newDelta] = calculateStepConfig(timeAccum);
274         timeAccum -= numSteps*newDelta;
275 
276         // init
277         for (auto& data : actorsData)
278             data.updatePosition(mCollisionWorld);
279         mPrevStepCount = numSteps;
280         mRemainingSteps = numSteps;
281         mTimeAccum = timeAccum;
282         mPhysicsDt = newDelta;
283         mActorsFrameData = std::move(actorsData);
284         mAdvanceSimulation = (mRemainingSteps != 0);
285         mNewFrame = true;
286         mNumJobs = mActorsFrameData.size();
287         mNextLOS.store(0, std::memory_order_relaxed);
288         mNextJob.store(0, std::memory_order_release);
289 
290         if (mAdvanceSimulation)
291             mWorldFrameData = std::make_unique<WorldFrameData>();
292 
293         if (mAdvanceSimulation)
294             mBudgetCursor += 1;
295 
296         if (mNumThreads == 0)
297         {
298             syncComputation();
299             if(mAdvanceSimulation)
300                 mBudget.update(mTimer->delta_s(timeStart, mTimer->tick()), numSteps, mBudgetCursor);
301             return mMovedActors;
302         }
303 
304         mAsyncStartTime = mTimer->tick();
305         lock.unlock();
306         mHasJob.notify_all();
307         if (mAdvanceSimulation)
308             mBudget.update(mTimer->delta_s(timeStart, mTimer->tick()), 1, mBudgetCursor);
309         return mMovedActors;
310     }
311 
resetSimulation(const ActorMap & actors)312     const std::vector<MWWorld::Ptr>& PhysicsTaskScheduler::resetSimulation(const ActorMap& actors)
313     {
314         std::unique_lock lock(mSimulationMutex);
315         mBudget.reset(mDefaultPhysicsDt);
316         mAsyncBudget.reset(0.0f);
317         mMovedActors.clear();
318         mActorsFrameData.clear();
319         for (const auto& [_, actor] : actors)
320         {
321             actor->updatePosition();
322             actor->updateCollisionObjectPosition();
323             mMovedActors.emplace_back(actor->getPtr());
324         }
325         return mMovedActors;
326     }
327 
rayTest(const btVector3 & rayFromWorld,const btVector3 & rayToWorld,btCollisionWorld::RayResultCallback & resultCallback) const328     void PhysicsTaskScheduler::rayTest(const btVector3& rayFromWorld, const btVector3& rayToWorld, btCollisionWorld::RayResultCallback& resultCallback) const
329     {
330         MaybeSharedLock lock(mCollisionWorldMutex, mThreadSafeBullet);
331         mCollisionWorld->rayTest(rayFromWorld, rayToWorld, resultCallback);
332     }
333 
convexSweepTest(const btConvexShape * castShape,const btTransform & from,const btTransform & to,btCollisionWorld::ConvexResultCallback & resultCallback) const334     void PhysicsTaskScheduler::convexSweepTest(const btConvexShape* castShape, const btTransform& from, const btTransform& to, btCollisionWorld::ConvexResultCallback& resultCallback) const
335     {
336         MaybeSharedLock lock(mCollisionWorldMutex, mThreadSafeBullet);
337         mCollisionWorld->convexSweepTest(castShape, from, to, resultCallback);
338     }
339 
contactTest(btCollisionObject * colObj,btCollisionWorld::ContactResultCallback & resultCallback)340     void PhysicsTaskScheduler::contactTest(btCollisionObject* colObj, btCollisionWorld::ContactResultCallback& resultCallback)
341     {
342         std::shared_lock lock(mCollisionWorldMutex);
343         ContactTestWrapper::contactTest(mCollisionWorld, colObj, resultCallback);
344     }
345 
getHitPoint(const btTransform & from,btCollisionObject * target)346     std::optional<btVector3> PhysicsTaskScheduler::getHitPoint(const btTransform& from, btCollisionObject* target)
347     {
348         MaybeSharedLock lock(mCollisionWorldMutex, mThreadSafeBullet);
349         // target the collision object's world origin, this should be the center of the collision object
350         btTransform rayTo;
351         rayTo.setIdentity();
352         rayTo.setOrigin(target->getWorldTransform().getOrigin());
353 
354         btCollisionWorld::ClosestRayResultCallback cb(from.getOrigin(), rayTo.getOrigin());
355 
356         mCollisionWorld->rayTestSingle(from, rayTo, target, target->getCollisionShape(), target->getWorldTransform(), cb);
357         if (!cb.hasHit())
358             // didn't hit the target. this could happen if point is already inside the collision box
359             return std::nullopt;
360         return {cb.m_hitPointWorld};
361     }
362 
aabbTest(const btVector3 & aabbMin,const btVector3 & aabbMax,btBroadphaseAabbCallback & callback)363     void PhysicsTaskScheduler::aabbTest(const btVector3& aabbMin, const btVector3& aabbMax, btBroadphaseAabbCallback& callback)
364     {
365         std::shared_lock lock(mCollisionWorldMutex);
366         mCollisionWorld->getBroadphase()->aabbTest(aabbMin, aabbMax, callback);
367     }
368 
getAabb(const btCollisionObject * obj,btVector3 & min,btVector3 & max)369     void PhysicsTaskScheduler::getAabb(const btCollisionObject* obj, btVector3& min, btVector3& max)
370     {
371         std::shared_lock lock(mCollisionWorldMutex);
372         obj->getCollisionShape()->getAabb(obj->getWorldTransform(), min, max);
373     }
374 
setCollisionFilterMask(btCollisionObject * collisionObject,int collisionFilterMask)375     void PhysicsTaskScheduler::setCollisionFilterMask(btCollisionObject* collisionObject, int collisionFilterMask)
376     {
377         std::unique_lock lock(mCollisionWorldMutex);
378         collisionObject->getBroadphaseHandle()->m_collisionFilterMask = collisionFilterMask;
379     }
380 
addCollisionObject(btCollisionObject * collisionObject,int collisionFilterGroup,int collisionFilterMask)381     void PhysicsTaskScheduler::addCollisionObject(btCollisionObject* collisionObject, int collisionFilterGroup, int collisionFilterMask)
382     {
383         std::unique_lock lock(mCollisionWorldMutex);
384         mCollisionWorld->addCollisionObject(collisionObject, collisionFilterGroup, collisionFilterMask);
385     }
386 
removeCollisionObject(btCollisionObject * collisionObject)387     void PhysicsTaskScheduler::removeCollisionObject(btCollisionObject* collisionObject)
388     {
389         std::unique_lock lock(mCollisionWorldMutex);
390         mCollisionWorld->removeCollisionObject(collisionObject);
391     }
392 
updateSingleAabb(std::weak_ptr<PtrHolder> ptr,bool immediate)393     void PhysicsTaskScheduler::updateSingleAabb(std::weak_ptr<PtrHolder> ptr, bool immediate)
394     {
395         if (!mDeferAabbUpdate || immediate)
396         {
397             updatePtrAabb(ptr);
398         }
399         else
400         {
401             std::unique_lock lock(mUpdateAabbMutex);
402             mUpdateAabb.insert(std::move(ptr));
403         }
404     }
405 
getLineOfSight(const std::weak_ptr<Actor> & actor1,const std::weak_ptr<Actor> & actor2)406     bool PhysicsTaskScheduler::getLineOfSight(const std::weak_ptr<Actor>& actor1, const std::weak_ptr<Actor>& actor2)
407     {
408         std::unique_lock lock(mLOSCacheMutex);
409 
410         auto actorPtr1 = actor1.lock();
411         auto actorPtr2 = actor2.lock();
412         if (!actorPtr1 || !actorPtr2)
413             return false;
414 
415         auto req = LOSRequest(actor1, actor2);
416         auto result = std::find(mLOSCache.begin(), mLOSCache.end(), req);
417         if (result == mLOSCache.end())
418         {
419             req.mResult = hasLineOfSight(actorPtr1.get(), actorPtr2.get());
420             if (mLOSCacheExpiry >= 0)
421                 mLOSCache.push_back(req);
422             return req.mResult;
423         }
424         result->mAge = 0;
425         return result->mResult;
426     }
427 
refreshLOSCache()428     void PhysicsTaskScheduler::refreshLOSCache()
429     {
430         std::shared_lock lock(mLOSCacheMutex);
431         int job = 0;
432         int numLOS = mLOSCache.size();
433         while ((job = mNextLOS.fetch_add(1, std::memory_order_relaxed)) < numLOS)
434         {
435             auto& req = mLOSCache[job];
436             auto actorPtr1 = req.mActors[0].lock();
437             auto actorPtr2 = req.mActors[1].lock();
438 
439             if (req.mAge++ > mLOSCacheExpiry || !actorPtr1 || !actorPtr2)
440                 req.mStale = true;
441             else
442                 req.mResult = hasLineOfSight(actorPtr1.get(), actorPtr2.get());
443         }
444 
445     }
446 
updateAabbs()447     void PhysicsTaskScheduler::updateAabbs()
448     {
449         std::scoped_lock lock(mUpdateAabbMutex);
450         std::for_each(mUpdateAabb.begin(), mUpdateAabb.end(),
451             [this](const std::weak_ptr<PtrHolder>& ptr) { updatePtrAabb(ptr); });
452         mUpdateAabb.clear();
453     }
454 
updatePtrAabb(const std::weak_ptr<PtrHolder> & ptr)455     void PhysicsTaskScheduler::updatePtrAabb(const std::weak_ptr<PtrHolder>& ptr)
456     {
457         if (const auto p = ptr.lock())
458         {
459             std::scoped_lock lock(mCollisionWorldMutex);
460             if (const auto actor = std::dynamic_pointer_cast<Actor>(p))
461             {
462                 actor->updateCollisionObjectPosition();
463                 mCollisionWorld->updateSingleAabb(actor->getCollisionObject());
464             }
465             else if (const auto object = std::dynamic_pointer_cast<Object>(p))
466             {
467                 object->commitPositionChange();
468                 mCollisionWorld->updateSingleAabb(object->getCollisionObject());
469             }
470             else if (const auto projectile = std::dynamic_pointer_cast<Projectile>(p))
471             {
472                 projectile->commitPositionChange();
473                 mCollisionWorld->updateSingleAabb(projectile->getCollisionObject());
474             }
475         };
476     }
477 
worker()478     void PhysicsTaskScheduler::worker()
479     {
480         std::shared_lock lock(mSimulationMutex);
481         while (!mQuit)
482         {
483             if (!mNewFrame)
484                 mHasJob.wait(lock, [&]() { return mQuit || mNewFrame; });
485 
486             mPreStepBarrier->wait([this] { afterPreStep(); });
487 
488             int job = 0;
489             while (mRemainingSteps && (job = mNextJob.fetch_add(1, std::memory_order_relaxed)) < mNumJobs)
490             {
491                 if(const auto actor = mActorsFrameData[job].mActor.lock())
492                 {
493                     MaybeSharedLock lockColWorld(mCollisionWorldMutex, mThreadSafeBullet);
494                     MovementSolver::move(mActorsFrameData[job], mPhysicsDt, mCollisionWorld, *mWorldFrameData);
495                 }
496             }
497 
498             mPostStepBarrier->wait([this] { afterPostStep(); });
499 
500             if (!mRemainingSteps)
501             {
502                 while ((job = mNextJob.fetch_add(1, std::memory_order_relaxed)) < mNumJobs)
503                 {
504                     if(const auto actor = mActorsFrameData[job].mActor.lock())
505                     {
506                         auto& actorData = mActorsFrameData[job];
507                         handleFall(actorData, mAdvanceSimulation);
508                     }
509                 }
510 
511                 if (mLOSCacheExpiry >= 0)
512                     refreshLOSCache();
513                 mPostSimBarrier->wait([this] { afterPostSim(); });
514             }
515         }
516     }
517 
updateActorsPositions()518     void PhysicsTaskScheduler::updateActorsPositions()
519     {
520         for (auto& actorData : mActorsFrameData)
521         {
522             if(const auto actor = actorData.mActor.lock())
523             {
524                 if (actor->setPosition(actorData.mPosition))
525                 {
526                     std::scoped_lock lock(mCollisionWorldMutex);
527                     actorData.mPosition = actor->getPosition(); // account for potential position change made by script
528                     actor->updateCollisionObjectPosition();
529                     mCollisionWorld->updateSingleAabb(actor->getCollisionObject());
530                 }
531             }
532         }
533     }
534 
hasLineOfSight(const Actor * actor1,const Actor * actor2)535     bool PhysicsTaskScheduler::hasLineOfSight(const Actor* actor1, const Actor* actor2)
536     {
537         btVector3 pos1  = Misc::Convert::toBullet(actor1->getCollisionObjectPosition() + osg::Vec3f(0,0,actor1->getHalfExtents().z() * 0.9)); // eye level
538         btVector3 pos2  = Misc::Convert::toBullet(actor2->getCollisionObjectPosition() + osg::Vec3f(0,0,actor2->getHalfExtents().z() * 0.9));
539 
540         btCollisionWorld::ClosestRayResultCallback resultCallback(pos1, pos2);
541         resultCallback.m_collisionFilterGroup = 0xFF;
542         resultCallback.m_collisionFilterMask = CollisionType_World|CollisionType_HeightMap|CollisionType_Door;
543 
544         MaybeSharedLock lockColWorld(mCollisionWorldMutex, mThreadSafeBullet);
545         mCollisionWorld->rayTest(pos1, pos2, resultCallback);
546 
547         return !resultCallback.hasHit();
548     }
549 
syncComputation()550     void PhysicsTaskScheduler::syncComputation()
551     {
552         while (mRemainingSteps--)
553         {
554             for (auto& actorData : mActorsFrameData)
555             {
556                 MovementSolver::unstuck(actorData, mCollisionWorld);
557                 MovementSolver::move(actorData, mPhysicsDt, mCollisionWorld, *mWorldFrameData);
558             }
559 
560             updateActorsPositions();
561         }
562 
563         for (auto& actorData : mActorsFrameData)
564         {
565             handleFall(actorData, mAdvanceSimulation);
566             actorData.mActorRaw->setSimulationPosition(interpolateMovements(actorData, mTimeAccum, mPhysicsDt));
567             updateMechanics(actorData);
568             mMovedActors.emplace_back(actorData.mActorRaw->getPtr());
569             if (mAdvanceSimulation)
570                 actorData.mActorRaw->setStandingOnPtr(actorData.mStandingOn);
571         }
572     }
573 
updateStats(osg::Timer_t frameStart,unsigned int frameNumber,osg::Stats & stats)574     void PhysicsTaskScheduler::updateStats(osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats)
575     {
576         if (!stats.collectStats("engine"))
577             return;
578         if (mFrameNumber == frameNumber - 1)
579         {
580             stats.setAttribute(mFrameNumber, "physicsworker_time_begin", mTimer->delta_s(mFrameStart, mTimeBegin));
581             stats.setAttribute(mFrameNumber, "physicsworker_time_taken", mTimer->delta_s(mTimeBegin, mTimeEnd));
582             stats.setAttribute(mFrameNumber, "physicsworker_time_end", mTimer->delta_s(mFrameStart, mTimeEnd));
583         }
584         mFrameStart = frameStart;
585         mTimeBegin = mTimer->tick();
586         mFrameNumber = frameNumber;
587     }
588 
debugDraw()589     void PhysicsTaskScheduler::debugDraw()
590     {
591         std::shared_lock lock(mCollisionWorldMutex);
592         mDebugDrawer->step();
593     }
594 
afterPreStep()595     void PhysicsTaskScheduler::afterPreStep()
596     {
597         if (mDeferAabbUpdate)
598             updateAabbs();
599         if (!mRemainingSteps)
600             return;
601         for (auto& data : mActorsFrameData)
602             if (const auto actor = data.mActor.lock())
603             {
604                 std::unique_lock lock(mCollisionWorldMutex);
605                 MovementSolver::unstuck(data, mCollisionWorld);
606             }
607     }
608 
afterPostStep()609     void PhysicsTaskScheduler::afterPostStep()
610     {
611         if (mRemainingSteps)
612         {
613             --mRemainingSteps;
614             updateActorsPositions();
615         }
616         mNextJob.store(0, std::memory_order_release);
617     }
618 
afterPostSim()619     void PhysicsTaskScheduler::afterPostSim()
620     {
621         mNewFrame = false;
622         if (mLOSCacheExpiry >= 0)
623         {
624             std::unique_lock lock(mLOSCacheMutex);
625             mLOSCache.erase(
626                     std::remove_if(mLOSCache.begin(), mLOSCache.end(),
627                         [](const LOSRequest& req) { return req.mStale; }),
628                     mLOSCache.end());
629         }
630         mTimeEnd = mTimer->tick();
631     }
632 }
633