Advanced third person camera.

pull/2923/head
Petr Mikheev 5 years ago
parent 2fb7c27976
commit d3bd67d747

@ -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)
@ -259,7 +317,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 +338,7 @@ namespace MWRender
}
}
float Camera::getPitch()
float Camera::getPitch() const
{
if (mVanity.enabled || mPreviewMode) {
return mPreviewCam.pitch;
@ -314,7 +372,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 +380,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 +461,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);
}
}

@ -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.

Loading…
Cancel
Save