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