diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5dbe10478..95919af31 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -58,6 +58,7 @@
Feature #390: 3rd person look "over the shoulder"
Feature #2386: Distant Statics in the form of Object Paging
Feature #4894: Consider actors as obstacles for pathfinding
+ Feature #5043: Head Bobbing
Feature #5297: Add a search function to the "Datafiles" tab of the OpenMW launcher
Feature #5362: Show the soul gems' trapped soul in count dialog
Feature #5445: Handle NiLines
diff --git a/apps/launcher/advancedpage.cpp b/apps/launcher/advancedpage.cpp
index bd3f6979e..050ab8d21 100644
--- a/apps/launcher/advancedpage.cpp
+++ b/apps/launcher/advancedpage.cpp
@@ -136,6 +136,7 @@ bool Launcher::AdvancedPage::loadSettings()
loadSettingBool(autoSwitchShoulderCheckBox, "auto switch shoulder", "Camera");
loadSettingBool(previewIfStandStillCheckBox, "preview if stand still", "Camera");
loadSettingBool(deferredPreviewRotationCheckBox, "deferred preview rotation", "Camera");
+ loadSettingBool(headBobbingCheckBox, "head bobbing", "Camera");
defaultShoulderComboBox->setCurrentIndex(
mEngineSettings.getVector2("view over shoulder offset", "Camera").x() >= 0 ? 0 : 1);
}
@@ -253,6 +254,7 @@ void Launcher::AdvancedPage::saveSettings()
saveSettingBool(autoSwitchShoulderCheckBox, "auto switch shoulder", "Camera");
saveSettingBool(previewIfStandStillCheckBox, "preview if stand still", "Camera");
saveSettingBool(deferredPreviewRotationCheckBox, "deferred preview rotation", "Camera");
+ saveSettingBool(headBobbingCheckBox, "head bobbing", "Camera");
osg::Vec2f shoulderOffset = mEngineSettings.getVector2("view over shoulder offset", "Camera");
if (defaultShoulderComboBox->currentIndex() != (shoulderOffset.x() >= 0 ? 0 : 1))
diff --git a/apps/openmw/mwrender/camera.cpp b/apps/openmw/mwrender/camera.cpp
index 527dddb9d..5b95b302b 100644
--- a/apps/openmw/mwrender/camera.cpp
+++ b/apps/openmw/mwrender/camera.cpp
@@ -80,6 +80,7 @@ namespace MWRender
mZoomOutWhenMoveCoef(Settings::Manager::getFloat("zoom out when move coef", "Camera")),
mDynamicCameraDistanceEnabled(false),
mShowCrosshairInThirdPersonMode(false),
+ mHeadBobbingEnabled(Settings::Manager::getBool("head bobbing", "Camera")),
mDeferredRotation(osg::Vec3f()),
mDeferredRotationDisabled(false)
{
@@ -104,7 +105,9 @@ namespace MWRender
osg::Matrix worldMat = osg::computeLocalToWorld(nodepaths[0]);
osg::Vec3d position = worldMat.getTrans();
- if (!isFirstPerson())
+ if (isFirstPerson())
+ position.z() += mHeadBobbingOffset;
+ else
{
position.z() += mHeight * mHeightScale;
@@ -143,13 +146,31 @@ namespace MWRender
osg::Vec3d focal, position;
getPosition(focal, position);
- osg::Quat orient = osg::Quat(getPitch(), osg::Vec3d(1,0,0)) * osg::Quat(getYaw(), osg::Vec3d(0,0,1));
+ osg::Quat orient = osg::Quat(mRoll, osg::Vec3d(0, 1, 0)) * osg::Quat(mPitch, osg::Vec3d(1, 0, 0)) * osg::Quat(mYaw, osg::Vec3d(0, 0, 1));
osg::Vec3d forward = orient * osg::Vec3d(0,1,0);
osg::Vec3d up = orient * osg::Vec3d(0,0,1);
cam->setViewMatrixAsLookAt(position, position + forward, up);
}
+ void Camera::updateHeadBobbing(float duration) {
+ static const float doubleStepLength = Settings::Manager::getFloat("head bobbing step", "Camera") * 2;
+ static const float stepHeight = Settings::Manager::getFloat("head bobbing height", "Camera");
+ static const float maxRoll = osg::DegreesToRadians(Settings::Manager::getFloat("head bobbing roll", "Camera"));
+
+ if (MWBase::Environment::get().getWorld()->isOnGround(mTrackingPtr))
+ mHeadBobbingWeight = std::min(mHeadBobbingWeight + duration * 5, 1.f);
+ else
+ mHeadBobbingWeight = std::max(mHeadBobbingWeight - duration * 5, 0.f);
+
+ float doubleStepState = mTotalMovement / doubleStepLength - std::floor(mTotalMovement / doubleStepLength); // from 0 to 1 during 2 steps
+ float stepState = std::abs(doubleStepState * 4 - 2) - 1; // from -1 to 1 on even steps and from 1 to -1 on odd steps
+ float effect = (1 - std::cos(stepState * osg::DegreesToRadians(30.f))) * 7.5f; // range from 0 to 1
+ float coef = std::min(mSmoothedSpeed / 300.f, 1.f) * mHeadBobbingWeight;
+ mHeadBobbingOffset = (0.5f - effect) * coef * stepHeight; // range from -stepHeight/2 to stepHeight/2
+ mRoll = osg::sign(stepState) * effect * coef * maxRoll; // range from -maxRoll to maxRoll
+ }
+
void Camera::reset()
{
togglePreviewMode(false);
@@ -198,10 +219,16 @@ namespace MWRender
if(mMode == Mode::Vanity)
rotateCamera(0.f, osg::DegreesToRadians(3.f * duration), true);
+ if (isFirstPerson() && mHeadBobbingEnabled)
+ updateHeadBobbing(duration);
+ else
+ mRoll = mHeadBobbingOffset = 0;
+
updateFocalPointOffset(duration);
updatePosition();
float speed = mTrackingPtr.getClass().getCurrentSpeed(mTrackingPtr);
+ mTotalMovement += speed * duration;
speed /= (1.f + speed / 500.f);
float maxDelta = 300.f * duration;
mSmoothedSpeed += osg::clampBetween(speed - mSmoothedSpeed, -maxDelta, maxDelta);
diff --git a/apps/openmw/mwrender/camera.hpp b/apps/openmw/mwrender/camera.hpp
index b3f6026eb..6fe392683 100644
--- a/apps/openmw/mwrender/camera.hpp
+++ b/apps/openmw/mwrender/camera.hpp
@@ -46,7 +46,7 @@ namespace MWRender
bool mIsNearest;
float mHeight, mBaseCameraDistance;
- float mPitch, mYaw;
+ float mPitch, mYaw, mRoll;
bool mVanityToggleQueued;
bool mVanityToggleQueuedValue;
@@ -72,6 +72,12 @@ namespace MWRender
bool mDynamicCameraDistanceEnabled;
bool mShowCrosshairInThirdPersonMode;
+ bool mHeadBobbingEnabled;
+ float mHeadBobbingOffset;
+ float mHeadBobbingWeight = 0; // Value from 0 to 1 for smooth enabling/disabling.
+ float mTotalMovement = 0; // Needed for head bobbing.
+ void updateHeadBobbing(float duration);
+
void updateFocalPointOffset(float duration);
void updatePosition();
float getCameraDistanceCorrection() const;
diff --git a/docs/source/reference/modding/settings/camera.rst b/docs/source/reference/modding/settings/camera.rst
index 1025a2fbd..5701947dc 100644
--- a/docs/source/reference/modding/settings/camera.rst
+++ b/docs/source/reference/modding/settings/camera.rst
@@ -199,3 +199,50 @@ If disabled then the camera rotates rather than the character.
This setting can be controlled in Advanced tab of the launcher.
+head bobbing
+------------
+
+:Type: boolean
+:Range: True/False
+:Default: False
+
+Enables head bobbing when move in first person mode.
+
+This setting can be controlled in Advanced tab of the launcher.
+
+head bobbing step
+-----------------
+
+:Type: floating point
+:Range: >0
+:Default: 90.0
+
+Makes diffence only in first person mode if 'head bobbing' is enabled.
+Length of each step.
+
+This setting can only be configured by editing the settings configuration file.
+
+head bobbing height
+-------------------
+
+:Type: floating point
+:Range: Any
+:Default: 3.0
+
+Makes diffence only in first person mode if 'head bobbing' is enabled.
+Amplitude of the head bobbing.
+
+This setting can only be configured by editing the settings configuration file.
+
+head bobbing roll
+-----------------
+
+:Type: floating point
+:Range: 0-90
+:Default: 0.2
+
+Makes diffence only in first person mode if 'head bobbing' is enabled.
+Maximum roll angle in degrees.
+
+This setting can only be configured by editing the settings configuration file.
+
diff --git a/files/settings-default.cfg b/files/settings-default.cfg
index 3c6e736d8..ec8f5fe14 100644
--- a/files/settings-default.cfg
+++ b/files/settings-default.cfg
@@ -54,6 +54,18 @@ preview if stand still = false
# Rotate the character to the view direction after exiting preview mode.
deferred preview rotation = true
+# Enables head bobbing in first person mode
+head bobbing = false
+
+# Length of each step
+head bobbing step = 90.0
+
+# Amplitude of the bobbing effect
+head bobbing height = 3.0
+
+# Maximum camera roll angle (degrees)
+head bobbing roll = 0.2
+
[Cells]
# Preload cells in a background thread. All settings starting with 'preload' have no effect unless this is enabled.
diff --git a/files/ui/advancedpage.ui b/files/ui/advancedpage.ui
index 6cb73e7ef..c678ffbba 100644
--- a/files/ui/advancedpage.ui
+++ b/files/ui/advancedpage.ui
@@ -583,6 +583,16 @@ True: In non-combat mode camera is positioned behind the character's shoulder. C
+ -
+
+
+ <html><head/><body><p>Enables head bobbing when move in first person mode.</p></body></html>
+
+
+ Head bobbing in 1st person mode
+
+
+
-