Skip to content

Commit

Permalink
Merge branch 'figure-skaters' into 'master'
Browse files Browse the repository at this point in the history
Use accumulated movement when possible

Closes #5062

See merge request OpenMW/openmw!3549
  • Loading branch information
Capostrophic committed Nov 7, 2023
2 parents d5dbbfb + acf6178 commit bdc0196
Show file tree
Hide file tree
Showing 9 changed files with 117 additions and 43 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
Bug #4754: Stack of ammunition cannot be equipped partially
Bug #4816: GetWeaponDrawn returns 1 before weapon is attached
Bug #5057: Weapon swing sound plays at same pitch whether it hits or misses
Bug #5062: Root bone rotations for NPC animation don't work the same as for creature animation
Bug #5129: Stuttering animation on Centurion Archer
Bug #5280: Unskinned shapes in skinned equipment are rendered in the wrong place
Bug #5371: Keyframe animation tracks are used for any file that begins with an X
Expand Down
2 changes: 2 additions & 0 deletions apps/launcher/settingspage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ bool Launcher::SettingsPage::loadSettings()
}
loadSettingBool(Settings::game().mTurnToMovementDirection, *turnToMovementDirectionCheckBox);
loadSettingBool(Settings::game().mSmoothMovement, *smoothMovementCheckBox);
loadSettingBool(Settings::game().mPlayerMovementIgnoresAnimation, *playerMovementIgnoresAnimationCheckBox);

distantLandCheckBox->setCheckState(
Settings::terrain().mDistantTerrain && Settings::terrain().mObjectPaging ? Qt::Checked : Qt::Unchecked);
Expand Down Expand Up @@ -338,6 +339,7 @@ void Launcher::SettingsPage::saveSettings()
saveSettingBool(*shieldSheathingCheckBox, Settings::game().mShieldSheathing);
saveSettingBool(*turnToMovementDirectionCheckBox, Settings::game().mTurnToMovementDirection);
saveSettingBool(*smoothMovementCheckBox, Settings::game().mSmoothMovement);
saveSettingBool(*playerMovementIgnoresAnimationCheckBox, Settings::game().mPlayerMovementIgnoresAnimation);

const bool wantDistantLand = distantLandCheckBox->checkState() == Qt::Checked;
if (wantDistantLand != (Settings::terrain().mDistantTerrain && Settings::terrain().mObjectPaging))
Expand Down
115 changes: 77 additions & 38 deletions apps/openmw/mwmechanics/character.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2386,50 +2386,56 @@ namespace MWMechanics
}
}

osg::Vec3f moved = mAnimation->runAnimation(mSkipAnim && !isScriptedAnimPlaying() ? 0.f : duration);
if (duration > 0.0f)
moved /= duration;
else
moved = osg::Vec3f(0.f, 0.f, 0.f);

moved.x() *= scale;
moved.y() *= scale;
osg::Vec3f movementFromAnimation
= mAnimation->runAnimation(mSkipAnim && !isScriptedAnimPlaying() ? 0.f : duration);

// Ensure we're moving in generally the right direction...
if (speed > 0.f && moved != osg::Vec3f())
if (mPtr.getClass().isActor() && isMovementAnimationControlled() && !isScriptedAnimPlaying())
{
float l = moved.length();
if (std::abs(movement.x() - moved.x()) > std::abs(moved.x()) / 2
|| std::abs(movement.y() - moved.y()) > std::abs(moved.y()) / 2
|| std::abs(movement.z() - moved.z()) > std::abs(moved.z()) / 2)
{
moved = movement;
// For some creatures getSpeed doesn't work, so we adjust speed to the animation.
// TODO: Fix Creature::getSpeed.
float newLength = moved.length();
if (newLength > 0 && !cls.isNpc())
moved *= (l / newLength);
if (duration > 0.0f)
movementFromAnimation /= duration;
else
movementFromAnimation = osg::Vec3f(0.f, 0.f, 0.f);

movementFromAnimation.x() *= scale;
movementFromAnimation.y() *= scale;

if (speed > 0.f && movementFromAnimation != osg::Vec3f())
{
// Ensure we're moving in the right general direction. In vanilla, all horizontal movement is taken from
// animations, even when moving diagonally (which doesn't have a corresponding animation). So to acheive
// diagonal movement, we have to rotate the movement taken from the animation to the intended
// direction.
//
// Note that while a complete movement animation cycle will have a well defined direction, no individual
// frame will, and therefore we have to determine the direction based on the currently playing cycle
// instead.
float animMovementAngle = getAnimationMovementDirection();
float targetMovementAngle = std::atan2(-movement.x(), movement.y());
float diff = targetMovementAngle - animMovementAngle;
movementFromAnimation = osg::Quat(diff, osg::Vec3f(0, 0, 1)) * movementFromAnimation;
}

if (!(isPlayer && Settings::game().mPlayerMovementIgnoresAnimation))
movement = movementFromAnimation;

if (mFloatToSurface)
{
if (cls.getCreatureStats(mPtr).isDead()
|| (!godmode
&& cls.getCreatureStats(mPtr)
.getMagicEffects()
.getOrDefault(ESM::MagicEffect::Paralyze)
.getModifier()
> 0))
{
movement.z() = 1.0;
}
}
}

if (mFloatToSurface && cls.isActor())
{
if (cls.getCreatureStats(mPtr).isDead()
|| (!godmode
&& cls.getCreatureStats(mPtr)
.getMagicEffects()
.getOrDefault(ESM::MagicEffect::Paralyze)
.getModifier()
> 0))
{
moved.z() = 1.0;
}
// Update movement
world->queueMovement(mPtr, movement);
}

// Update movement
if (isMovementAnimationControlled() && mPtr.getClass().isActor() && !isScriptedAnimPlaying())
world->queueMovement(mPtr, moved);

mSkipAnim = false;

mAnimation->enableHeadAnimation(cls.isActor() && !cls.getCreatureStats(mPtr).isDead());
Expand Down Expand Up @@ -2909,6 +2915,39 @@ namespace MWMechanics
MWBase::Environment::get().getSoundManager()->playSound3D(mPtr, *soundId, volume, pitch);
}

float CharacterController::getAnimationMovementDirection() const
{
switch (mMovementState)
{
case CharState_RunLeft:
case CharState_SneakLeft:
case CharState_SwimWalkLeft:
case CharState_SwimRunLeft:
case CharState_WalkLeft:
return osg::PI_2f;
case CharState_RunRight:
case CharState_SneakRight:
case CharState_SwimWalkRight:
case CharState_SwimRunRight:
case CharState_WalkRight:
return -osg::PI_2f;
case CharState_RunForward:
case CharState_SneakForward:
case CharState_SwimRunForward:
case CharState_SwimWalkForward:
case CharState_WalkForward:
return mAnimation->getLegsYawRadians();
case CharState_RunBack:
case CharState_SneakBack:
case CharState_SwimWalkBack:
case CharState_SwimRunBack:
case CharState_WalkBack:
return mAnimation->getLegsYawRadians() - osg::PIf;
default:
return 0.0f;
}
}

void CharacterController::updateHeadTracking(float duration)
{
const osg::Node* head = mAnimation->getNode("Bip01 Head");
Expand Down
2 changes: 2 additions & 0 deletions apps/openmw/mwmechanics/character.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,8 @@ namespace MWMechanics

void playSwishSound() const;

float getAnimationMovementDirection() const;

MWWorld::MovementDirectionFlags getSupportedMovementDirections() const;
};
}
Expand Down
6 changes: 4 additions & 2 deletions apps/openmw/mwrender/animation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1235,9 +1235,11 @@ namespace MWRender
mRootController->setEnabled(enable);
if (enable)
{
mRootController->setRotate(osg::Quat(mLegsYawRadians, osg::Vec3f(0, 0, 1))
* osg::Quat(mBodyPitchRadians, osg::Vec3f(1, 0, 0)));
osg::Quat legYaw = osg::Quat(mLegsYawRadians, osg::Vec3f(0, 0, 1));
mRootController->setRotate(legYaw * osg::Quat(mBodyPitchRadians, osg::Vec3f(1, 0, 0)));
yawOffset = mLegsYawRadians;
// When yawing the root, also update the accumulated movement.
movement = legYaw * movement;
}
}
if (mSpineController)
Expand Down
1 change: 1 addition & 0 deletions components/settings/categories/game.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ namespace Settings
"unarmed creature attacks damage armor" };
SettingValue<DetourNavigator::CollisionShapeType> mActorCollisionShapeType{ mIndex, "Game",
"actor collision shape type" };
SettingValue<bool> mPlayerMovementIgnoresAnimation{ mIndex, "Game", "player movement ignores animation" };
};
}

Expand Down
12 changes: 12 additions & 0 deletions docs/source/reference/modding/settings/game.rst
Original file line number Diff line number Diff line change
Expand Up @@ -517,3 +517,15 @@ will not be useful with another.
* 0: Axis-aligned bounding box
* 1: Rotating box
* 2: Cylinder

player movement ignores animation
---------------------------------

:Type: boolean
:Range: True/False
:Default: False

In third person, the camera will sway along with the movement animations of the player.
Enabling this option disables this swaying by having the player character move independently of its animation.

This setting can be controlled in the Settings tab of the launcher, under Visuals.
5 changes: 5 additions & 0 deletions files/settings-default.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,11 @@ unarmed creature attacks damage armor = false
# 2 = Cylinder
actor collision shape type = 0

# When false the player character will base movement on animations. This will sway the camera
# while moving in third person like in vanilla, and reproduce movement bugs caused by glitchy
# vanilla animations.
player movement ignores animation = false

[General]

# Anisotropy reduces distortion in textures at low angles (e.g. 0 to 16).
Expand Down
16 changes: 13 additions & 3 deletions files/ui/settingspage.ui
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
<item>
<widget class="QTabWidget" name="AdvancedTabWidget">
<property name="currentIndex">
<number>0</number>
<number>1</number>
</property>
<widget class="QWidget" name="GameMechanics">
<attribute name="title">
Expand Down Expand Up @@ -293,8 +293,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>680</width>
<height>882</height>
<width>671</width>
<height>774</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
Expand Down Expand Up @@ -377,6 +377,16 @@
</item>
</layout>
</item>
<item row="2" column="1">
<widget class="QCheckBox" name="playerMovementIgnoresAnimationCheckBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;In third person, the camera will sway along with the movement animations of the player. Enabling this option disables this swaying by having the player character move independently of its animation. This was the default behavior of OpenMW 0.48 and earlier.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Player movement ignores animation</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
Expand Down

0 comments on commit bdc0196

Please sign in to comment.