Merge branch 'improved_third_person' into 'master'

Improved third person experience

Closes #5457 and #390

See merge request OpenMW/openmw!219
pull/2923/head
psi29a 5 years ago
commit 421ad91a08

@ -155,6 +155,7 @@ Programmers
Paul McElroy (Greendogo)
pchan3
Perry Hugh
Petr Mikheev (ptmikheev)
Phillip Andrews (PhillipAnd)
Pi03k
Pieter van der Kloet (pvdk)

@ -29,9 +29,11 @@
Bug #5441: Enemies can't push a player character when in critical strike stance
Bug #5451: Magic projectiles don't disappear with the caster
Bug #5452: Autowalk is being included in savegames
Feature #390: 3rd person look "over the shoulder"
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
Feature #5457: Realistic diagonal movement
Task #5480: Drop Qt4 support
0.46.0

@ -536,10 +536,11 @@ namespace MWClass
moveSpeed = getSwimSpeed(ptr);
else
moveSpeed = getWalkSpeed(ptr);
if(getMovementSettings(ptr).mPosition[0] != 0 && getMovementSettings(ptr).mPosition[1] == 0)
moveSpeed *= 0.75f;
moveSpeed *= ptr.getClass().getMovementSettings(ptr).mSpeedFactor;
const MWMechanics::Movement& movementSettings = ptr.getClass().getMovementSettings(ptr);
if (movementSettings.mIsStrafing)
moveSpeed *= 0.75f;
moveSpeed *= movementSettings.mSpeedFactor;
return moveSpeed;
}

@ -966,13 +966,14 @@ namespace MWClass
moveSpeed = getRunSpeed(ptr);
else
moveSpeed = getWalkSpeed(ptr);
if(getMovementSettings(ptr).mPosition[0] != 0 && getMovementSettings(ptr).mPosition[1] == 0)
moveSpeed *= 0.75f;
if(npcdata->mNpcStats.isWerewolf() && running && npcdata->mNpcStats.getDrawState() == MWMechanics::DrawState_Nothing)
moveSpeed *= gmst.fWereWolfRunMult->mValue.getFloat();
moveSpeed *= ptr.getClass().getMovementSettings(ptr).mSpeedFactor;
const MWMechanics::Movement& movementSettings = ptr.getClass().getMovementSettings(ptr);
if (movementSettings.mIsStrafing)
moveSpeed *= 0.75f;
moveSpeed *= movementSettings.mSpeedFactor;
return moveSpeed;
}

@ -1939,64 +1939,76 @@ void CharacterController::update(float duration, bool animationOnly)
bool sneak = cls.getCreatureStats(mPtr).getStance(MWMechanics::CreatureStats::Stance_Sneak) && !flying;
bool isrunning = cls.getCreatureStats(mPtr).getStance(MWMechanics::CreatureStats::Stance_Run) && !flying;
CreatureStats &stats = cls.getCreatureStats(mPtr);
Movement& movementSettings = cls.getMovementSettings(mPtr);
//Force Jump Logic
bool isMoving = (std::abs(cls.getMovementSettings(mPtr).mPosition[0]) > .5 || std::abs(cls.getMovementSettings(mPtr).mPosition[1]) > .5);
bool isMoving = (std::abs(movementSettings.mPosition[0]) > .5 || std::abs(movementSettings.mPosition[1]) > .5);
if(!inwater && !flying && solid)
{
//Force Jump
if(stats.getMovementFlag(MWMechanics::CreatureStats::Flag_ForceJump))
{
if(onground)
{
cls.getMovementSettings(mPtr).mPosition[2] = 1;
}
else
cls.getMovementSettings(mPtr).mPosition[2] = 0;
}
movementSettings.mPosition[2] = onground ? 1 : 0;
//Force Move Jump, only jump if they're otherwise moving
if(stats.getMovementFlag(MWMechanics::CreatureStats::Flag_ForceMoveJump) && isMoving)
{
if(onground)
{
cls.getMovementSettings(mPtr).mPosition[2] = 1;
}
else
cls.getMovementSettings(mPtr).mPosition[2] = 0;
}
movementSettings.mPosition[2] = onground ? 1 : 0;
}
osg::Vec3f vec(cls.getMovementSettings(mPtr).asVec3());
vec.normalize();
if(mHitState != CharState_None && mJumpState == JumpState_None)
vec = osg::Vec3f(0.f, 0.f, 0.f);
osg::Vec3f rot = cls.getRotationVector(mPtr);
osg::Vec3f vec(movementSettings.asVec3());
vec.normalize();
speed = cls.getSpeed(mPtr);
float analogueMult = 1.f;
if(isPlayer)
float analogueMult = 1.0f;
if (isPlayer)
{
// TODO: Move this code to mwinput.
// Joystick analogue movement.
float xAxis = std::abs(cls.getMovementSettings(mPtr).mPosition[0]);
float yAxis = std::abs(cls.getMovementSettings(mPtr).mPosition[1]);
analogueMult = ((xAxis > yAxis) ? xAxis : yAxis);
// If Strafing, our max speed is slower so multiply by X axis instead.
if(std::abs(vec.x()/2.0f) > std::abs(vec.y()))
analogueMult = xAxis;
float xAxis = std::abs(movementSettings.mPosition[0]);
float yAxis = std::abs(movementSettings.mPosition[1]);
analogueMult = std::max(xAxis, yAxis);
// Due to the half way split between walking/running, we multiply speed by 2 while walking, unless a keyboard was used.
if(!isrunning && !sneak && !flying && analogueMult <= 0.5f)
analogueMult *= 2.f;
movementSettings.mSpeedFactor = analogueMult;
}
speed *= analogueMult;
float effectiveRotation = rot.z();
static const bool turnToMovementDirection = Settings::Manager::getBool("turn to movement direction", "Game");
static const float turnToMovementDirectionSpeedCoef = Settings::Manager::getFloat("turn to movement direction speed coef", "Game");
if (turnToMovementDirection && !(isPlayer && MWBase::Environment::get().getWorld()->isFirstPerson()))
{
float targetMovementAngle = vec.y() >= 0 ? std::atan2(-vec.x(), vec.y()) : std::atan2(vec.x(), -vec.y());
movementSettings.mIsStrafing = (stats.getDrawState() != MWMechanics::DrawState_Nothing || inwater)
&& std::abs(targetMovementAngle) > osg::DegreesToRadians(60.0f);
if (movementSettings.mIsStrafing)
targetMovementAngle = 0;
float delta = targetMovementAngle - stats.getSideMovementAngle();
float cosDelta = cosf(delta);
movementSettings.mSpeedFactor *= std::min(std::max(cosDelta, 0.f) + 0.3f, 1.f); // slow down when turn
float maxDelta = turnToMovementDirectionSpeedCoef * osg::PI * duration * (2.5f - cosDelta);
delta = std::min(delta, maxDelta);
delta = std::max(delta, -maxDelta);
stats.setSideMovementAngle(stats.getSideMovementAngle() + delta);
effectiveRotation += delta;
}
else
movementSettings.mIsStrafing = std::abs(vec.x()) > std::abs(vec.y()) * 2;
mAnimation->setLegsYawRadians(stats.getSideMovementAngle());
if (stats.getDrawState() == MWMechanics::DrawState_Nothing || inwater)
mAnimation->setUpperBodyYawRadians(stats.getSideMovementAngle() / 2);
else
mAnimation->setUpperBodyYawRadians(stats.getSideMovementAngle() / 4);
speed = cls.getSpeed(mPtr);
vec.x() *= speed;
vec.y() *= speed;
if(mHitState != CharState_None && mJumpState == JumpState_None)
vec = osg::Vec3f();
CharacterState movestate = CharState_None;
CharacterState idlestate = CharState_SpecialIdle;
JumpingState jumpstate = JumpState_None;
@ -2158,7 +2170,7 @@ void CharacterController::update(float duration, bool animationOnly)
inJump = false;
if(std::abs(vec.x()/2.0f) > std::abs(vec.y()))
if (movementSettings.mIsStrafing)
{
if(vec.x() > 0.0f)
movestate = (inwater ? (isrunning ? CharState_SwimRunRight : CharState_SwimWalkRight)
@ -2169,18 +2181,18 @@ void CharacterController::update(float duration, bool animationOnly)
: (sneak ? CharState_SneakLeft
: (isrunning ? CharState_RunLeft : CharState_WalkLeft)));
}
else if(vec.y() != 0.0f)
else if (vec.length2() > 0.0f)
{
if(vec.y() > 0.0f)
if (vec.y() >= 0.0f)
movestate = (inwater ? (isrunning ? CharState_SwimRunForward : CharState_SwimWalkForward)
: (sneak ? CharState_SneakForward
: (isrunning ? CharState_RunForward : CharState_WalkForward)));
else if(vec.y() < 0.0f)
else
movestate = (inwater ? (isrunning ? CharState_SwimRunBack : CharState_SwimWalkBack)
: (sneak ? CharState_SneakBack
: (isrunning ? CharState_RunBack : CharState_WalkBack)));
}
else if(rot.z() != 0.0f)
else if (effectiveRotation != 0.0f)
{
// Do not play turning animation for player if rotation speed is very slow.
// Actual threshold should take framerate in account.
@ -2193,9 +2205,9 @@ void CharacterController::update(float duration, bool animationOnly)
bool isFirstPlayer = isPlayer && MWBase::Environment::get().getWorld()->isFirstPerson();
if (!sneak && jumpstate == JumpState_None && !isFirstPlayer && mPtr.getClass().isBipedal(mPtr))
{
if(rot.z() > rotationThreshold)
if(effectiveRotation > rotationThreshold)
movestate = inwater ? CharState_SwimTurnRight : CharState_TurnRight;
else if(rot.z() < -rotationThreshold)
else if(effectiveRotation < -rotationThreshold)
movestate = inwater ? CharState_SwimTurnLeft : CharState_TurnLeft;
}
}
@ -2317,9 +2329,9 @@ void CharacterController::update(float duration, bool animationOnly)
world->queueMovement(mPtr, osg::Vec3f(0.f, 0.f, 0.f));
movement = vec;
cls.getMovementSettings(mPtr).mPosition[0] = cls.getMovementSettings(mPtr).mPosition[1] = 0;
movementSettings.mPosition[0] = movementSettings.mPosition[1] = 0;
if (movement.z() == 0.f)
cls.getMovementSettings(mPtr).mPosition[2] = 0;
movementSettings.mPosition[2] = 0;
// Can't reset jump state (mPosition[2]) here in full; we don't know for sure whether the PhysicSystem will actually handle it in this frame
// due to the fixed minimum timestep used for the physics update. It will be reset in PhysicSystem::move once the jump is handled.
@ -2355,15 +2367,11 @@ void CharacterController::update(float duration, bool animationOnly)
if(speed > 0.f)
{
float l = moved.length();
if((movement.x() < 0.0f && movement.x() < moved.x()*2.0f) ||
(movement.x() > 0.0f && movement.x() > moved.x()*2.0f))
if (std::abs(movement.x() - moved.x()) > std::abs(moved.x()) / 2)
moved.x() = movement.x();
if((movement.y() < 0.0f && movement.y() < moved.y()*2.0f) ||
(movement.y() > 0.0f && movement.y() > moved.y()*2.0f))
if (std::abs(movement.y() - moved.y()) > std::abs(moved.y()) / 2)
moved.y() = movement.y();
if((movement.z() < 0.0f && movement.z() < moved.z()*2.0f) ||
(movement.z() > 0.0f && movement.z() > moved.z()*2.0f))
if (std::abs(movement.z() - moved.z()) > std::abs(moved.z()) / 2)
moved.z() = movement.z();
// but keep the original speed
float newLength = moved.length();

@ -23,7 +23,7 @@ namespace MWMechanics
mKnockdown(false), mKnockdownOneFrame(false), mKnockdownOverOneFrame(false),
mHitRecovery(false), mBlock(false), mMovementFlags(0),
mFallHeight(0), mRecalcMagicka(false), mLastRestock(0,0), mGoldPool(0), mActorId(-1), mHitAttemptActorId(-1),
mDeathAnimation(-1), mTimeOfDeath(), mLevel (0)
mDeathAnimation(-1), mTimeOfDeath(), mSideMovementAngle(0), mLevel (0)
{
for (int i=0; i<4; ++i)
mAiSettings[i] = 0;

@ -80,6 +80,9 @@ namespace MWMechanics
MWWorld::TimeStamp mTimeOfDeath;
// The difference between view direction and lower body direction.
float mSideMovementAngle;
public:
typedef std::pair<int, std::string> SummonKey; // <ESM::MagicEffect index, spell ID>
private:
@ -298,6 +301,9 @@ namespace MWMechanics
void addCorprusSpell(const std::string& sourceId, CorprusStats& stats);
void removeCorprusSpell(const std::string& sourceId);
float getSideMovementAngle() const { return mSideMovementAngle; }
void setSideMovementAngle(float angle) { mSideMovementAngle = angle; }
};
}

@ -11,12 +11,14 @@ namespace MWMechanics
float mPosition[3];
float mRotation[3];
float mSpeedFactor;
bool mIsStrafing;
Movement()
{
mPosition[0] = mPosition[1] = mPosition[2] = 0.0f;
mRotation[0] = mRotation[1] = mRotation[2] = 0.0f;
mSpeedFactor = 1.f;
mIsStrafing = false;
}
osg::Vec3f asVec3()

@ -621,6 +621,8 @@ namespace MWRender
, mTextKeyListener(nullptr)
, mHeadYawRadians(0.f)
, mHeadPitchRadians(0.f)
, mUpperBodyYawRadians(0.f)
, mLegsYawRadians(0.f)
, mHasMagicEffects(false)
, mAlpha(1.f)
{
@ -1334,13 +1336,36 @@ namespace MWRender
updateEffects();
const float epsilon = 0.001f;
float yawOffset = 0;
if (mRootController)
{
bool enable = std::abs(mLegsYawRadians) > epsilon;
mRootController->setEnabled(enable);
if (enable)
{
mRootController->setRotate(osg::Quat(mLegsYawRadians, osg::Vec3f(0,0,1)));
yawOffset = mLegsYawRadians;
}
}
if (mSpineController)
{
float yaw = mUpperBodyYawRadians - yawOffset;
bool enable = std::abs(yaw) > epsilon;
mSpineController->setEnabled(enable);
if (enable)
{
mSpineController->setRotate(osg::Quat(yaw, osg::Vec3f(0,0,1)));
yawOffset = mUpperBodyYawRadians;
}
}
if (mHeadController)
{
const float epsilon = 0.001f;
bool enable = (std::abs(mHeadPitchRadians) > epsilon || std::abs(mHeadYawRadians) > epsilon);
float yaw = mHeadYawRadians - yawOffset;
bool enable = (std::abs(mHeadPitchRadians) > epsilon || std::abs(yaw) > epsilon);
mHeadController->setEnabled(enable);
if (enable)
mHeadController->setRotate(osg::Quat(mHeadPitchRadians, osg::Vec3f(1,0,0)) * osg::Quat(mHeadYawRadians, osg::Vec3f(0,0,1)));
mHeadController->setRotate(osg::Quat(mHeadPitchRadians, osg::Vec3f(1,0,0)) * osg::Quat(yaw, osg::Vec3f(0,0,1)));
}
// Scripted animations should not cause movement
@ -1801,13 +1826,17 @@ namespace MWRender
void Animation::addControllers()
{
mHeadController = nullptr;
NodeMap::const_iterator found = getNodeMap().find("bip01 head");
if (found == getNodeMap().end())
return;
mHeadController = addRotateController("bip01 head");
mSpineController = addRotateController("bip01 spine1");
mRootController = addRotateController("bip01");
}
osg::MatrixTransform* node = found->second;
RotateController* Animation::addRotateController(std::string bone)
{
auto iter = getNodeMap().find(bone);
if (iter == getNodeMap().end())
return nullptr;
osg::MatrixTransform* node = iter->second;
bool foundKeyframeCtrl = false;
osg::Callback* cb = node->getUpdateCallback();
@ -1820,13 +1849,15 @@ namespace MWRender
}
cb = cb->getNestedCallback();
}
// Without KeyframeController the orientation will not be reseted each frame, so
// RotateController shouldn't be used for such nodes.
if (!foundKeyframeCtrl)
return;
return nullptr;
mHeadController = new RotateController(mObjectRoot.get());
node->addUpdateCallback(mHeadController);
mActiveControllers.emplace_back(node, mHeadController);
RotateController* controller = new RotateController(mObjectRoot.get());
node->addUpdateCallback(controller);
mActiveControllers.emplace_back(node, controller);
return controller;
}
void Animation::setHeadPitch(float pitchRadians)

@ -267,8 +267,15 @@ protected:
TextKeyListener* mTextKeyListener;
osg::ref_ptr<RotateController> mHeadController;
osg::ref_ptr<RotateController> mSpineController;
osg::ref_ptr<RotateController> mRootController;
float mHeadYawRadians;
float mHeadPitchRadians;
float mUpperBodyYawRadians;
float mLegsYawRadians;
RotateController* addRotateController(std::string bone);
bool mHasMagicEffects;
osg::ref_ptr<SceneUtil::LightSource> mGlowLight;
@ -477,6 +484,12 @@ public:
virtual void setHeadYaw(float yawRadians);
virtual float getHeadPitch() const;
virtual float getHeadYaw() const;
virtual void setUpperBodyYawRadians(float v) { mUpperBodyYawRadians = v; }
virtual void setLegsYawRadians(float v) { mLegsYawRadians = v; }
virtual float getUpperBodyYawRadians() const { return mUpperBodyYawRadians; }
virtual float getLegsYawRadians() const { return mLegsYawRadians; }
virtual void setAccurateAiming(bool enabled) {}
virtual bool canBeHarvested() const { return false; }

@ -7,9 +7,13 @@
#include "../mwbase/environment.hpp"
#include "../mwbase/windowmanager.hpp"
#include "../mwworld/class.hpp"
#include "../mwworld/ptr.hpp"
#include "../mwworld/refdata.hpp"
#include "../mwmechanics/drawstate.hpp"
#include "../mwmechanics/npcstats.hpp"
#include "npcanimation.hpp"
namespace
@ -52,11 +56,14 @@ namespace MWRender
mFurthest(800.f),
mIsNearest(false),
mHeight(124.f),
mMaxCameraDistance(192.f),
mBaseCameraDistance(192.f),
mVanityToggleQueued(false),
mVanityToggleQueuedValue(false),
mViewModeToggleQueued(false),
mCameraDistance(0.f)
mCameraDistance(0.f),
mThirdPersonMode(ThirdPersonViewMode::Standard),
mOverShoulderHorizontalOffset(30.0f),
mSmoothTransitionToCombatMode(0.f)
{
mVanity.enabled = false;
mVanity.allowed = true;
@ -68,7 +75,7 @@ namespace MWRender
mMainCam.yaw = 0.f;
mMainCam.offset = 400.f;
mCameraDistance = mMaxCameraDistance;
mCameraDistance = mBaseCameraDistance;
mUpdateCallback = new UpdateRenderCameraCallback(this);
mCamera->addUpdateCallback(mUpdateCallback);
@ -84,7 +91,7 @@ namespace MWRender
return mTrackingPtr;
}
osg::Vec3d Camera::getFocalPoint()
osg::Vec3d Camera::getFocalPoint() const
{
const osg::Node* trackNode = mTrackingNode;
if (!trackNode)
@ -96,22 +103,54 @@ namespace MWRender
osg::Vec3d position = worldMat.getTrans();
if (!isFirstPerson())
{
position.z() += mHeight * mHeightScale;
// We subtract 10.f here and add it within focalPointOffset in order to avoid camera clipping through ceiling.
// Needed because character's head can be a bit higher than collision area.
position.z() -= 10.f;
position += getFocalPointOffset() + mFocalPointAdjustment;
}
return position;
}
osg::Vec3d Camera::getFocalPointOffset() const
{
osg::Vec3d offset(0, 0, 10.f);
if (mThirdPersonMode == ThirdPersonViewMode::OverShoulder && !mPreviewMode && !mVanity.enabled)
{
float horizontalOffset = mOverShoulderHorizontalOffset * (1.f - mSmoothTransitionToCombatMode);
float verticalOffset = mSmoothTransitionToCombatMode * 15.f + (1.f - mSmoothTransitionToCombatMode) * -10.f;
offset.x() += horizontalOffset * cos(getYaw());
offset.y() += horizontalOffset * sin(getYaw());
offset.z() += verticalOffset;
}
return offset;
}
void Camera::getPosition(osg::Vec3d &focal, osg::Vec3d &camera) const
{
focal = getFocalPoint();
osg::Vec3d offset(0,0,0);
if (!isFirstPerson())
{
osg::Quat orient = osg::Quat(getPitch(), osg::Vec3d(1,0,0)) * osg::Quat(getYaw(), osg::Vec3d(0,0,1));
offset = orient * osg::Vec3d(0.f, -mCameraDistance, 0.f);
}
camera = focal + offset;
}
void Camera::updateCamera(osg::Camera *cam)
{
if (mTrackingPtr.isEmpty())
return;
osg::Vec3d position = getFocalPoint();
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::Vec3d offset = orient * osg::Vec3d(0, isFirstPerson() ? 0 : -mCameraDistance, 0);
position += offset;
osg::Vec3d forward = orient * osg::Vec3d(0,1,0);
osg::Vec3d up = orient * osg::Vec3d(0,0,1);
@ -164,14 +203,33 @@ namespace MWRender
if (paused)
return;
// only show the crosshair in game mode and in first person mode.
// only show the crosshair in game mode
MWBase::WindowManager *wm = MWBase::Environment::get().getWindowManager();
wm->showCrosshair(!wm->isGuiMode() && (mFirstPersonView && !mVanity.enabled && !mPreviewMode));
wm->showCrosshair(!wm->isGuiMode() && !mVanity.enabled && !mPreviewMode
&& (mFirstPersonView || mThirdPersonMode != ThirdPersonViewMode::Standard));
if(mVanity.enabled)
{
rotateCamera(0.f, osg::DegreesToRadians(3.f * duration), true);
}
updateSmoothTransitionToCombatMode(duration);
}
void Camera::updateSmoothTransitionToCombatMode(float duration)
{
bool combatMode = true;
if (mTrackingPtr.getClass().isActor())
combatMode = mTrackingPtr.getClass().getCreatureStats(mTrackingPtr).getDrawState() != MWMechanics::DrawState_Nothing;
float speed = ((combatMode ? 1.f : 0.f) - mSmoothTransitionToCombatMode) * 5;
if (speed != 0)
speed += speed > 0 ? 1 : -1;
mSmoothTransitionToCombatMode += speed * duration;
if (mSmoothTransitionToCombatMode > 1)
mSmoothTransitionToCombatMode = 1;
if (mSmoothTransitionToCombatMode < 0)
mSmoothTransitionToCombatMode = 0;
}
void Camera::toggleViewMode(bool force)
@ -186,6 +244,9 @@ namespace MWRender
else
mViewModeToggleQueued = false;
if (mTrackingPtr.getClass().isActor())
mTrackingPtr.getClass().getCreatureStats(mTrackingPtr).setSideMovementAngle(0);
mFirstPersonView = !mFirstPersonView;
processViewChange();
}
@ -259,7 +320,7 @@ namespace MWRender
mAnimation->setFirstPersonOffset(osg::Vec3f(0,0,-offset));
}
float Camera::getYaw()
float Camera::getYaw() const
{
if(mVanity.enabled || mPreviewMode)
return mPreviewCam.yaw;
@ -280,7 +341,7 @@ namespace MWRender
}
}
float Camera::getPitch()
float Camera::getPitch() const
{
if (mVanity.enabled || mPreviewMode) {
return mPreviewCam.pitch;
@ -314,7 +375,7 @@ namespace MWRender
return mCameraDistance;
}
void Camera::setCameraDistance(float dist, bool adjust, bool override)
void Camera::setBaseCameraDistance(float dist, bool adjust)
{
if(mFirstPersonView && !mPreviewMode && !mVanity.enabled)
return;
@ -322,34 +383,55 @@ namespace MWRender
mIsNearest = false;
if (adjust)
dist += mCameraDistance;
{
if (mVanity.enabled || mPreviewMode)
dist += mCameraDistance;
else
dist += std::min(mCameraDistance - getCameraDistanceCorrection(), mBaseCameraDistance);
}
if (dist >= mFurthest) {
if (dist >= mFurthest)
dist = mFurthest;
} else if (!override && dist < 10.f) {
dist = 10.f;
} else if (override && dist <= mNearest) {
else if (dist <= mNearest)
{
dist = mNearest;
mIsNearest = true;
}
if (mVanity.enabled || mPreviewMode)
mPreviewCam.offset = dist;
else if (!mFirstPersonView)
mBaseCameraDistance = dist;
setCameraDistance();
}
void Camera::setCameraDistance(float dist, bool adjust)
{
if(mFirstPersonView && !mPreviewMode && !mVanity.enabled)
return;
if (adjust) dist += mCameraDistance;
if (dist >= mFurthest)
dist = mFurthest;
else if (dist < 10.f)
dist = 10.f;
mCameraDistance = dist;
}
if (override) {
if (mVanity.enabled || mPreviewMode) {
mPreviewCam.offset = mCameraDistance;
} else if (!mFirstPersonView) {
mMaxCameraDistance = mCameraDistance;
}
}
float Camera::getCameraDistanceCorrection() const
{
return mThirdPersonMode != ThirdPersonViewMode::Standard ? std::max(-getPitch(), 0.f) * 50.f : 0;
}
void Camera::setCameraDistance()
{
if (mVanity.enabled || mPreviewMode) {
if (mVanity.enabled || mPreviewMode)
mCameraDistance = mPreviewCam.offset;
} else if (!mFirstPersonView) {
mCameraDistance = mMaxCameraDistance;
}
else if (!mFirstPersonView)
mCameraDistance = mBaseCameraDistance + getCameraDistanceCorrection();
mFocalPointAdjustment = osg::Vec3d();
}
void Camera::setAnimation(NpcAnimation *anim)
@ -382,22 +464,12 @@ namespace MWRender
rotateCamera(getPitch(), getYaw(), false);
}
void Camera::getPosition(osg::Vec3f &focal, osg::Vec3f &camera)
{
focal = getFocalPoint();
osg::Quat orient = osg::Quat(getPitch(), osg::Vec3d(1,0,0)) * osg::Quat(getYaw(), osg::Vec3d(0,0,1));
osg::Vec3d offset = orient * osg::Vec3d(0, isFirstPerson() ? 0 : -mCameraDistance, 0);
camera = focal + offset;
}
bool Camera::isVanityOrPreviewModeEnabled()
bool Camera::isVanityOrPreviewModeEnabled() const
{
return mPreviewMode || mVanity.enabled;
}
bool Camera::isNearest()
bool Camera::isNearest() const
{
return mIsNearest;
}

@ -23,6 +23,10 @@ namespace MWRender
/// \brief Camera control
class Camera
{
public:
enum class ThirdPersonViewMode {Standard, OverShoulder};
private:
struct CamData {
float pitch, yaw, offset;
};
@ -45,7 +49,7 @@ namespace MWRender
bool enabled, allowed;
} mVanity;
float mHeight, mMaxCameraDistance;
float mHeight, mBaseCameraDistance;
CamData mMainCam, mPreviewCam;
bool mVanityToggleQueued;
@ -54,6 +58,16 @@ namespace MWRender
float mCameraDistance;
ThirdPersonViewMode mThirdPersonMode;
float mOverShoulderHorizontalOffset;
osg::Vec3d mFocalPointAdjustment;
// Makes sense only if mThirdPersonMode is OverShoulder. Can be in range [0, 1].
// Used for smooth transition from non-combat camera position (0) to combat camera position (1).
float mSmoothTransitionToCombatMode;
void updateSmoothTransitionToCombatMode(float duration);
float getCameraDistanceCorrection() const;
osg::ref_ptr<osg::NodeCallback> mUpdateCallback;
public:
@ -62,6 +76,9 @@ namespace MWRender
MWWorld::Ptr getTrackingPtr() const;
void setThirdPersonViewMode(ThirdPersonViewMode mode) { mThirdPersonMode = mode; }
void setOverShoulderHorizontalOffset(float v) { mOverShoulderHorizontalOffset = v; }
/// Update the view matrix of \a cam
void updateCamera(osg::Camera* cam);
@ -72,10 +89,10 @@ namespace MWRender
/// \param rot Rotation angles in radians
void rotateCamera(float pitch, float yaw, bool adjust);
float getYaw();
float getYaw() const;
void setYaw(float angle);
float getPitch();
float getPitch() const;
void setPitch(float angle);
/// Attach camera to object
@ -100,27 +117,32 @@ namespace MWRender
void update(float duration, bool paused=false);
/// Set base camera distance for current mode. Don't work on 1st person view.
/// \param adjust Indicates should distance be adjusted or set.
void setBaseCameraDistance(float dist, bool adjust = false);
/// Set camera distance for current mode. Don't work on 1st person view.
/// \param adjust Indicates should distance be adjusted or set.
/// \param override If true new distance will be used as default.
/// If false, default distance can be restored with setCameraDistance().
void setCameraDistance(float dist, bool adjust = false, bool override = true);
/// Default distance can be restored with setCameraDistance().
void setCameraDistance(float dist, bool adjust = false);
/// Restore default camera distance for current mode.
/// Restore default camera distance and offset for current mode.
void setCameraDistance();
float getCameraDistance() const;
void setAnimation(NpcAnimation *anim);
osg::Vec3d getFocalPoint();
osg::Vec3d getFocalPoint() const;
osg::Vec3d getFocalPointOffset() const;
void adjustFocalPoint(osg::Vec3d adjustment) { mFocalPointAdjustment = adjustment; }
/// Stores focal and camera world positions in passed arguments
void getPosition(osg::Vec3f &focal, osg::Vec3f &camera);
void getPosition(osg::Vec3d &focal, osg::Vec3d &camera) const;
bool isVanityOrPreviewModeEnabled();
bool isVanityOrPreviewModeEnabled() const;
bool isNearest();
bool isNearest() const;
};
}

@ -364,6 +364,7 @@ namespace MWRender
float firstPersonFov = Settings::Manager::getFloat("first person field of view", "Camera");
mFirstPersonFieldOfView = std::min(std::max(1.f, firstPersonFov), 179.f);
mStateUpdater->setFogEnd(mViewDistance);
updateThirdPersonViewMode();
mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("near", mNearClip));
mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("far", mViewDistance));
@ -379,6 +380,15 @@ namespace MWRender
mWorkQueue = nullptr;
}
void RenderingManager::updateThirdPersonViewMode()
{
if (Settings::Manager::getBool("view over shoulder", "Camera"))
mCamera->setThirdPersonViewMode(Camera::ThirdPersonViewMode::OverShoulder);
else
mCamera->setThirdPersonViewMode(Camera::ThirdPersonViewMode::Standard);
mCamera->setOverShoulderHorizontalOffset(Settings::Manager::getFloat("view over shoulder horizontal offset", "Camera"));
}
osgUtil::IncrementalCompileOperation* RenderingManager::getIncrementalCompileOperation()
{
return mViewer->getIncrementalCompileOperation();
@ -616,7 +626,7 @@ namespace MWRender
mCamera->update(dt, paused);
osg::Vec3f focal, cameraPos;
osg::Vec3d focal, cameraPos;
mCamera->getPosition(focal, cameraPos);
mCurrentCameraPos = cameraPos;
@ -1323,13 +1333,18 @@ namespace MWRender
{
if(mCamera->isNearest() && dist > 0.f)
mCamera->toggleViewMode();
else if (override)
mCamera->setBaseCameraDistance(-dist / 120.f * 10, adjust);
else
mCamera->setCameraDistance(-dist / 120.f * 10, adjust, override);
mCamera->setCameraDistance(-dist / 120.f * 10, adjust);
}
else if(mCamera->isFirstPerson() && dist < 0.f)
{
mCamera->toggleViewMode();
mCamera->setCameraDistance(0.f, false, override);
if (override)
mCamera->setBaseCameraDistance(0.f, false);
else
mCamera->setCameraDistance(0.f, false);
}
}
@ -1376,7 +1391,7 @@ namespace MWRender
void RenderingManager::changeVanityModeScale(float factor)
{
if(mCamera->isVanityOrPreviewModeEnabled())
mCamera->setCameraDistance(-factor/120.f*10, true, true);
mCamera->setBaseCameraDistance(-factor/120.f*10, true);
}
void RenderingManager::overrideFieldOfView(float val)

@ -252,6 +252,7 @@ namespace MWRender
void updateTextureFiltering();
void updateAmbient();
void setFogColor(const osg::Vec4f& color);
void updateThirdPersonViewMode();
void reportStats() const;

@ -1857,15 +1857,35 @@ namespace MWWorld
int nightEye = static_cast<int>(player.getClass().getCreatureStats(player).getMagicEffects().get(ESM::MagicEffect::NightEye).getMagnitude());
mRendering->setNightEyeFactor(std::min(1.f, (nightEye/100.f)));
mRendering->getCamera()->setCameraDistance();
auto* camera = mRendering->getCamera();
camera->setCameraDistance();
if(!mRendering->getCamera()->isFirstPerson())
{
osg::Vec3f focal, camera;
mRendering->getCamera()->getPosition(focal, camera);
float radius = mRendering->getNearClipDistance()*2.5f;
MWPhysics::PhysicsSystem::RayResult result = mPhysics->castSphere(focal, camera, radius);
float cameraObstacleLimit = mRendering->getNearClipDistance() * 2.5f;
float focalObstacleLimit = std::max(cameraObstacleLimit, 10.0f);
// Adjust focal point.
osg::Vec3d focal = camera->getFocalPoint();
osg::Vec3d focalOffset = camera->getFocalPointOffset();
float offsetLen = focalOffset.length();
if (offsetLen > 0)
{
MWPhysics::PhysicsSystem::RayResult result = mPhysics->castSphere(focal - focalOffset, focal, focalObstacleLimit);
if (result.mHit)
{
double adjustmentCoef = -(result.mHitPos + result.mHitNormal * focalObstacleLimit - focal).length() / offsetLen;
if (adjustmentCoef < -1)
adjustmentCoef = -1;
camera->adjustFocalPoint(focalOffset * adjustmentCoef);
}
}
// Adjust camera position.
osg::Vec3d cameraPos;
camera->getPosition(focal, cameraPos);
MWPhysics::PhysicsSystem::RayResult result = mPhysics->castSphere(focal, cameraPos, cameraObstacleLimit);
if (result.mHit)
mRendering->getCamera()->setCameraDistance((result.mHitPos - focal).length() - radius, false, false);
mRendering->getCamera()->setCameraDistance((result.mHitPos + result.mHitNormal * cameraObstacleLimit - focal).length(), false);
}
}

@ -113,3 +113,29 @@ because the Bethesda provided Morrowind assets do not adapt well to large values
while small values can result in the hands not being visible.
This setting can only be configured by editing the settings configuration file.
view over shoulder
------------------
:Type: boolean
:Range: True/False
:Default: False
This setting controls third person view mode.
False: View is centered on the character's head. Crosshair is hidden.
True: In non-combat mode camera is positioned behind the character's shoulder. Crosshair is visible in third person mode as well.
This setting can only be configured by editing the settings configuration file.
view over shoulder horizontal offset
------------------------------------
:Type: floating point
:Range: Any
:Default: 30
This setting makes sense only if 'view over shoulder' is enabled. Controls horizontal offset of the camera in third person mode. Negative value means offset to the left, positive - to the right.
Recommened values: 30 for the right shoulder, -30 for the left shoulder.
This setting can only be configured by editing the settings configuration file.

@ -315,3 +315,30 @@ Setting the value of this setting to true will remove the 0 lower cap from the v
allowing Damage Fatigue to reduce Fatigue to a value below zero.
This setting can be controlled in Advanced tab of the launcher.
turn to movement direction
--------------------------
:Type: boolean
:Range: True/False
:Default: False
Affects side and diagonal movement. Enabling this setting makes movement more realistic.
If disabled then the whole character's body is pointed to the direction of view. Diagonal movement has no special animation and causes sliding.
If enabled then the character turns lower body to the direction of movement. Upper body is turned partially. Head is always pointed to the direction of view. In combat mode it works only for diagonal movement. In non-combat mode it also changes straight right and straight left movement.
This setting can only be configured by editing the settings configuration file.
turn to movement direction speed coef
-------------------------------------
:Type: floating point
:Range: >0
:Default: 1.0
Makes difference only if 'turn to movement direction' is enabled. Modifies turning speed.
This setting can only be configured by editing the settings configuration file.

@ -33,6 +33,12 @@ field of view = 60.0
# Best to leave this at the default since vanilla assets are not complete enough to adapt to high FoV's. Too low FoV would clip the hands off screen.
first person field of view = 60.0
# If enabled then third person camera is positioned above character's shoulder and crosshair is visible.
view over shoulder = false
# Makes sense only if 'view over shoulder' is true. Negative value means offset to the left.
view over shoulder horizontal offset = 30
[Cells]
# Preload cells in a background thread. All settings starting with 'preload' have no effect unless this is enabled.
@ -296,6 +302,12 @@ projectiles enchant multiplier = 0
# This means that unlike Morrowind you will be able to knock down actors using this effect.
uncapped damage fatigue = false
# Turn lower body to movement direction. 'true' makes diagonal movement more realistic.
turn to movement direction = false
# Turning speed multiplier. Makes difference only if 'turn to movement direction' is enabled.
turn to movement direction speed coef = 1.0
[General]
# Anisotropy reduces distortion in textures at low angles (e.g. 0 to 16).

Loading…
Cancel
Save