diff --git a/apps/openmw/mwphysics/movementsolver.cpp b/apps/openmw/mwphysics/movementsolver.cpp index 93473c467c..679b4f3498 100644 --- a/apps/openmw/mwphysics/movementsolver.cpp +++ b/apps/openmw/mwphysics/movementsolver.cpp @@ -139,17 +139,43 @@ namespace MWPhysics return; } + // Physics simulation can be potentially skipped if all conditions match: + // - Actor is on ground + // - Actor is not flying + // - mIsAquatic is false + // - mMovement is a zero vector + // - mInertia is a zero vector + // This is important to fix #3803 and others + ActorTracer tracer; + if (!actor.mFlying && actor.mIsOnGround && !actor.mIsAquatic && actor.mMovement.length2() == 0 + && actor.mInertia.length2() == 0) + { + // We think we can skip the simulation, but we must double check to cover edge cases + // trace the actor's collision object down to try and find valid ground to stand on + // the ground could be removed from the actor, so we cant just remember the last ground value + // we must also check if walking on water even if we skip this movement simulation + // the simulation can also only be skipped if it finds solid ground, not an actor or unwalkable slope + const osg::Vec3f from = actor.mPosition + osg::Vec3f(0.0f, 0.0f, actor.mHalfExtentsZ); + const auto dropDistance = 2 * sGroundOffset + (actor.mIsOnGround ? sStepSizeDown : 0); + const osg::Vec3f to = from - osg::Vec3f(0, 0, dropDistance); + tracer.doTrace(actor.mCollisionObject, from, to, collisionWorld, actor.mIsOnGround); + if (tracer.mFraction < 1.0f && !isActor(tracer.mHitObject) && isWalkableSlope(tracer.mPlaneNormal)) + { + actor.mStandingOn = tracer.mHitObject; + actor.mWalkingOnWater + = actor.mStandingOn->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Water; + return; + } + } + // Adjust for collision mesh offset relative to actor's "location" // (doTrace doesn't take local/interior collision shape translation into account, so we have to do it on our // own) for compatibility with vanilla assets, we have to derive this from the vertical half extent instead of // from internal hull translation if not for this hack, the "correct" collision hull position would be // physicActor->getScaledMeshTranslation() actor.mPosition.z() += actor.mHalfExtentsZ; // vanilla-accurate - float swimlevel = actor.mSwimLevel + actor.mHalfExtentsZ; - ActorTracer tracer; - osg::Vec3f velocity; // Dead and paralyzed actors underwater will float to the surface, diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 957ab98f24..2182006e25 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1324,19 +1324,21 @@ namespace MWWorld return; } + const bool isActor = ptr.getClass().isActor(); const float terrainHeight = ptr.getCell()->isExterior() ? getTerrainHeightAt(pos, ptr.getCell()->getCell()->getWorldSpace()) : -std::numeric_limits::max(); - pos.z() = std::max(pos.z(), terrainHeight) - + 20; // place slightly above terrain. will snap down to ground with code below + pos.z() = std::max(pos.z(), terrainHeight); // We still should trace down dead persistent actors - they do not use the "swimdeath" animation. - bool swims = ptr.getClass().isActor() && isSwimming(ptr) + bool swims = isActor && isSwimming(ptr) && !(ptr.getClass().isPersistent(ptr) && ptr.getClass().getCreatureStats(ptr).isDeathAnimationFinished()); - if (force || !ptr.getClass().isActor() || (!isFlying(ptr) && !swims && isActorCollisionEnabled(ptr))) + if (force || !isActor || (!isFlying(ptr) && !swims && isActorCollisionEnabled(ptr))) { - osg::Vec3f traced - = mPhysics->traceDown(ptr, pos, ESM::getCellSize(ptr.getCell()->getCell()->getWorldSpace())); + // place slightly above terrain. will snap down to ground with code below + const osg::Vec3f posAdjusted = pos + osg::Vec3f(0.0f, 0.0f, 20.0f); + const osg::Vec3f traced + = mPhysics->traceDown(ptr, posAdjusted, ESM::getCellSize(ptr.getCell()->getCell()->getWorldSpace())); pos.z() = std::min(pos.z(), traced.z()); }