diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index fddfa276f9..01d270f829 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -20,7 +20,7 @@ source_group(game FILES ${GAME} ${GAME_HEADER}) add_openmw_dir (mwrender actors objects renderingmanager animation rotatecontroller sky npcanimation vismask creatureanimation effectmanager util renderinginterface pathgrid rendermode weaponanimation - bulletdebugdraw globalmap characterpreview camera localmap water terrainstorage ripplesimulation + bulletdebugdraw globalmap characterpreview camera viewovershoulder localmap water terrainstorage ripplesimulation renderbin actoranimation landmanager navmesh actorspaths recastmesh fogmanager objectpaging ) diff --git a/apps/openmw/mwrender/camera.cpp b/apps/openmw/mwrender/camera.cpp index 09ad404287..7531e712b5 100644 --- a/apps/openmw/mwrender/camera.cpp +++ b/apps/openmw/mwrender/camera.cpp @@ -62,9 +62,14 @@ namespace MWRender mVanityToggleQueuedValue(false), mViewModeToggleQueued(false), mCameraDistance(0.f), - mThirdPersonMode(ThirdPersonViewMode::Standard), - mOverShoulderOffset(osg::Vec2f(30.0f, -10.0f)), - mSmoothTransitionToCombatMode(0.f) + mFocalPointCurrentOffset(osg::Vec2d()), + mFocalPointTargetOffset(osg::Vec2d()), + mFocalPointTransitionSpeedCoef(1.f), + mPreviousTransitionInfluence(0.f), + mSmoothedSpeed(0.f), + mZoomOutWhenMoveCoef(Settings::Manager::getFloat("zoom out when move coef", "Camera")), + mDynamicCameraDistanceEnabled(false), + mShowCrosshairInThirdPersonMode(false) { mVanity.enabled = false; mVanity.allowed = true; @@ -119,14 +124,11 @@ namespace MWRender osg::Vec3d Camera::getFocalPointOffset() const { osg::Vec3d offset(0, 0, 10.f); - if (mThirdPersonMode == ThirdPersonViewMode::OverShoulder && !mPreviewMode && !mVanity.enabled) + if (!mPreviewMode && !mVanity.enabled) { - float horizontalOffset = mOverShoulderOffset.x() * (1.f - mSmoothTransitionToCombatMode); - float verticalOffset = mSmoothTransitionToCombatMode * 15.f + (1.f - mSmoothTransitionToCombatMode) * mOverShoulderOffset.y(); - - offset.x() += horizontalOffset * cos(getYaw()); - offset.y() += horizontalOffset * sin(getYaw()); - offset.z() += verticalOffset; + offset.x() += mFocalPointCurrentOffset.x() * cos(getYaw()); + offset.y() += mFocalPointCurrentOffset.x() * sin(getYaw()); + offset.z() += mFocalPointCurrentOffset.y(); } return offset; } @@ -207,35 +209,58 @@ namespace MWRender // only show the crosshair in game mode MWBase::WindowManager *wm = MWBase::Environment::get().getWindowManager(); wm->showCrosshair(!wm->isGuiMode() && !mVanity.enabled && !mPreviewMode - && (mFirstPersonView || mThirdPersonMode != ThirdPersonViewMode::Standard)); + && (mFirstPersonView || mShowCrosshairInThirdPersonMode)); if(mVanity.enabled) { rotateCamera(0.f, osg::DegreesToRadians(3.f * duration), true); } - updateSmoothTransitionToCombatMode(duration); + updateFocalPointOffset(duration); + + float speed = mTrackingPtr.getClass().getSpeed(mTrackingPtr); + float maxDelta = 300.f * duration; + mSmoothedSpeed += osg::clampBetween(speed - mSmoothedSpeed, -maxDelta, maxDelta); } - void Camera::setOverShoulderOffset(float horizontal, float vertical) + void Camera::setFocalPointTargetOffset(osg::Vec2d v) { - mOverShoulderOffset = osg::Vec2f(horizontal, vertical); + mFocalPointTargetOffset = v; + mPreviousTransitionSpeed = mFocalPointTransitionSpeed; + mPreviousTransitionInfluence = 1.0f; } - void Camera::updateSmoothTransitionToCombatMode(float duration) + void Camera::updateFocalPointOffset(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; + if (duration <= 0) + return; - mSmoothTransitionToCombatMode += speed * duration; - if (mSmoothTransitionToCombatMode > 1) - mSmoothTransitionToCombatMode = 1; - if (mSmoothTransitionToCombatMode < 0) - mSmoothTransitionToCombatMode = 0; + osg::Vec2d oldOffset = mFocalPointCurrentOffset; + + if (mPreviousTransitionInfluence > 0) + { + mFocalPointCurrentOffset -= mPreviousExtraOffset; + mPreviousExtraOffset = mPreviousExtraOffset / mPreviousTransitionInfluence + mPreviousTransitionSpeed * duration; + mPreviousTransitionInfluence = + std::max(0.f, mPreviousTransitionInfluence - duration * mFocalPointTransitionSpeedCoef); + mPreviousExtraOffset *= mPreviousTransitionInfluence; + mFocalPointCurrentOffset += mPreviousExtraOffset; + } + + osg::Vec2d delta = mFocalPointTargetOffset - mFocalPointCurrentOffset; + if (delta.length2() > 0) + { + float coef = duration * (1.0 + 5.0 / delta.length()) * + mFocalPointTransitionSpeedCoef * (1.0f - mPreviousTransitionInfluence); + mFocalPointCurrentOffset += delta * std::min(coef, 1.0f); + } + else + { + mPreviousExtraOffset = osg::Vec2d(); + mPreviousTransitionInfluence = 0.f; + } + + mFocalPointTransitionSpeed = (mFocalPointCurrentOffset - oldOffset) / duration; } void Camera::toggleViewMode(bool force) @@ -431,7 +456,15 @@ namespace MWRender float Camera::getCameraDistanceCorrection() const { - return mThirdPersonMode != ThirdPersonViewMode::Standard ? std::max(-getPitch(), 0.f) * 50.f : 0; + if (!mDynamicCameraDistanceEnabled) + return 0; + + float pitchCorrection = std::max(-getPitch(), 0.f) * 50.f; + + float smoothedSpeedSqr = mSmoothedSpeed * mSmoothedSpeed; + float speedCorrection = smoothedSpeedSqr / (smoothedSpeedSqr + 300.f*300.f) * mZoomOutWhenMoveCoef; + + return pitchCorrection + speedCorrection; } void Camera::setCameraDistance() diff --git a/apps/openmw/mwrender/camera.hpp b/apps/openmw/mwrender/camera.hpp index 6398f48252..59fa53047a 100644 --- a/apps/openmw/mwrender/camera.hpp +++ b/apps/openmw/mwrender/camera.hpp @@ -23,9 +23,6 @@ namespace MWRender /// \brief Camera control class Camera { - public: - enum class ThirdPersonViewMode {Standard, OverShoulder}; - private: struct CamData { float pitch, yaw, offset; @@ -58,14 +55,23 @@ namespace MWRender float mCameraDistance; - ThirdPersonViewMode mThirdPersonMode; - osg::Vec2f mOverShoulderOffset; osg::Vec3d mFocalPointAdjustment; + osg::Vec2d mFocalPointCurrentOffset; + osg::Vec2d mFocalPointTargetOffset; + float mFocalPointTransitionSpeedCoef; - // 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); + // This fields are used to make focal point transition smooth if previous transition was not finished. + float mPreviousTransitionInfluence; + osg::Vec2d mFocalPointTransitionSpeed; + osg::Vec2d mPreviousTransitionSpeed; + osg::Vec2d mPreviousExtraOffset; + + float mSmoothedSpeed; + float mZoomOutWhenMoveCoef; + bool mDynamicCameraDistanceEnabled; + bool mShowCrosshairInThirdPersonMode; + + void updateFocalPointOffset(float duration); float getCameraDistanceCorrection() const; osg::ref_ptr mUpdateCallback; @@ -76,8 +82,10 @@ namespace MWRender MWWorld::Ptr getTrackingPtr() const; - void setThirdPersonViewMode(ThirdPersonViewMode mode) { mThirdPersonMode = mode; } - void setOverShoulderOffset(float horizontal, float vertical); + void setFocalPointTransitionSpeed(float v) { mFocalPointTransitionSpeedCoef = v; } + void setFocalPointTargetOffset(osg::Vec2d v); + void enableDynamicCameraDistance(bool v) { mDynamicCameraDistanceEnabled = v; } + void enableCrosshairInThirdPersonMode(bool v) { mShowCrosshairInThirdPersonMode = v; } /// Update the view matrix of \a cam void updateCamera(osg::Camera* cam); diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index c7dd5ca637..d91da5a082 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -65,6 +65,7 @@ #include "vismask.hpp" #include "pathgrid.hpp" #include "camera.hpp" +#include "viewovershoulder.hpp" #include "water.hpp" #include "terrainstorage.hpp" #include "util.hpp" @@ -306,6 +307,8 @@ namespace MWRender mWater.reset(new Water(mRootNode, sceneRoot, mResourceSystem, mViewer->getIncrementalCompileOperation(), resourcePath)); mCamera.reset(new Camera(mViewer->getCamera())); + if (Settings::Manager::getBool("view over shoulder", "Camera")) + mViewOverShoulderController.reset(new ViewOverShoulderController(mCamera.get())); mViewer->setLightingMode(osgViewer::View::NO_LIGHT); @@ -366,7 +369,6 @@ 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)); @@ -382,19 +384,6 @@ 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); - - std::stringstream offset(Settings::Manager::getString("view over shoulder offset", "Camera")); - float horizontal = 30.f, vertical = -10.f; - offset >> horizontal >> vertical; - mCamera->setOverShoulderOffset(horizontal, vertical); - } - osgUtil::IncrementalCompileOperation* RenderingManager::getIncrementalCompileOperation() { return mViewer->getIncrementalCompileOperation(); @@ -630,6 +619,8 @@ namespace MWRender updateNavMesh(); updateRecastMesh(); + if (mViewOverShoulderController) + mViewOverShoulderController->update(); mCamera->update(dt, paused); osg::Vec3d focal, cameraPos; diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index 6700f5ce6c..d6a0f89c31 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -79,6 +79,7 @@ namespace MWRender class NpcAnimation; class Pathgrid; class Camera; + class ViewOverShoulderController; class Water; class TerrainStorage; class LandManager; @@ -294,6 +295,7 @@ namespace MWRender osg::ref_ptr mPlayerAnimation; osg::ref_ptr mPlayerNode; std::unique_ptr mCamera; + std::unique_ptr mViewOverShoulderController; osg::Vec3f mCurrentCameraPos; osg::ref_ptr mStateUpdater; diff --git a/apps/openmw/mwrender/viewovershoulder.cpp b/apps/openmw/mwrender/viewovershoulder.cpp new file mode 100644 index 0000000000..5d7ec7117a --- /dev/null +++ b/apps/openmw/mwrender/viewovershoulder.cpp @@ -0,0 +1,96 @@ +#include "viewovershoulder.hpp" + +#include + +#include + +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" + +#include "../mwworld/class.hpp" +#include "../mwworld/ptr.hpp" +#include "../mwworld/refdata.hpp" + +#include "../mwmechanics/drawstate.hpp" + +namespace MWRender +{ + + ViewOverShoulderController::ViewOverShoulderController(Camera* camera) : + mCamera(camera), mMode(Mode::RightShoulder), + mAutoSwitchShoulder(Settings::Manager::getBool("auto switch shoulder", "Camera")), + mOverShoulderHorizontalOffset(30.f), mOverShoulderVerticalOffset(-10.f) + { + std::stringstream offset(Settings::Manager::getString("view over shoulder offset", "Camera")); + offset >> mOverShoulderHorizontalOffset >> mOverShoulderVerticalOffset; + mDefaultShoulderIsRight = mOverShoulderHorizontalOffset >= 0; + mOverShoulderHorizontalOffset = std::abs(mOverShoulderHorizontalOffset); + + mCamera->enableDynamicCameraDistance(true); + mCamera->enableCrosshairInThirdPersonMode(true); + mCamera->setFocalPointTargetOffset({mOverShoulderHorizontalOffset, mOverShoulderVerticalOffset}); + } + + void ViewOverShoulderController::update() + { + if (mCamera->isVanityOrPreviewModeEnabled() || mCamera->isFirstPerson()) + return; + + Mode oldMode = mMode; + auto ptr = mCamera->getTrackingPtr(); + if (ptr.getClass().isActor() && ptr.getClass().getCreatureStats(ptr).getDrawState() != MWMechanics::DrawState_Nothing) + mMode = Mode::Combat; + else if (MWBase::Environment::get().getWorld()->isSwimming(ptr)) + mMode = Mode::Swimming; + else if (oldMode == Mode::Combat || oldMode == Mode::Swimming) + mMode = mDefaultShoulderIsRight ? Mode::RightShoulder : Mode::LeftShoulder; + if (mAutoSwitchShoulder && (mMode == Mode::LeftShoulder || mMode == Mode::RightShoulder)) + trySwitchShoulder(); + if (oldMode == mMode) return; + + if (oldMode == Mode::Combat || mMode == Mode::Combat) + mCamera->setFocalPointTransitionSpeed(5.f); + else + mCamera->setFocalPointTransitionSpeed(1.f); + + switch (mMode) + { + case Mode::RightShoulder: + mCamera->setFocalPointTargetOffset({mOverShoulderHorizontalOffset, mOverShoulderVerticalOffset}); + break; + case Mode::LeftShoulder: + mCamera->setFocalPointTargetOffset({-mOverShoulderHorizontalOffset, mOverShoulderVerticalOffset}); + break; + case Mode::Combat: + case Mode::Swimming: + default: + mCamera->setFocalPointTargetOffset({0, 15}); + } + } + + void ViewOverShoulderController::trySwitchShoulder() + { + const float limitToSwitch = 120; // switch to other shoulder if wall is closer than this limit + const float limitToSwitchBack = 300; // switch back to default shoulder if there is no walls at this distance + + auto orient = osg::Quat(mCamera->getYaw(), osg::Vec3d(0,0,1)); + osg::Vec3d playerPos = mCamera->getFocalPoint() - mCamera->getFocalPointOffset(); + + MWBase::World* world = MWBase::Environment::get().getWorld(); + osg::Vec3d sideOffset = orient * osg::Vec3d(world->getHalfExtents(mCamera->getTrackingPtr()).x() - 1, 0, 0); + float rayRight = world->getDistToNearestRayHit( + playerPos + sideOffset, orient * osg::Vec3d(1, 1, 0), limitToSwitchBack + 1); + float rayLeft = world->getDistToNearestRayHit( + playerPos - sideOffset, orient * osg::Vec3d(-1, 1, 0), limitToSwitchBack + 1); + float rayForward = world->getDistToNearestRayHit( + playerPos, orient * osg::Vec3d(0, 1, 0), limitToSwitchBack + 1); + + if (rayLeft < limitToSwitch && rayRight > limitToSwitchBack) + mMode = Mode::RightShoulder; + else if (rayRight < limitToSwitch && rayLeft > limitToSwitchBack) + mMode = Mode::LeftShoulder; + else if (rayLeft > limitToSwitchBack && rayRight > limitToSwitchBack && rayForward > limitToSwitchBack) + mMode = mDefaultShoulderIsRight ? Mode::RightShoulder : Mode::LeftShoulder; + } + +} \ No newline at end of file diff --git a/apps/openmw/mwrender/viewovershoulder.hpp b/apps/openmw/mwrender/viewovershoulder.hpp new file mode 100644 index 0000000000..80ac308656 --- /dev/null +++ b/apps/openmw/mwrender/viewovershoulder.hpp @@ -0,0 +1,30 @@ +#ifndef VIEWOVERSHOULDER_H +#define VIEWOVERSHOULDER_H + +#include "camera.hpp" + +namespace MWRender +{ + + class ViewOverShoulderController + { + public: + ViewOverShoulderController(Camera* camera); + + void update(); + + private: + void trySwitchShoulder(); + enum class Mode { RightShoulder, LeftShoulder, Combat, Swimming }; + + Camera* mCamera; + Mode mMode; + bool mAutoSwitchShoulder; + float mOverShoulderHorizontalOffset; + float mOverShoulderVerticalOffset; + bool mDefaultShoulderIsRight; + }; + +} + +#endif // VIEWOVERSHOULDER_H diff --git a/docs/source/reference/modding/settings/camera.rst b/docs/source/reference/modding/settings/camera.rst index 20349e97fd..be636cef4f 100644 --- a/docs/source/reference/modding/settings/camera.rst +++ b/docs/source/reference/modding/settings/camera.rst @@ -150,3 +150,27 @@ Recommened values: 30 -10 for the right shoulder, -30 -10 for the left shoulder. This setting can only be configured by editing the settings configuration file. +auto switch shoulder +-------------------- + +:Type: boolean +:Range: True/False +:Default: True + +This setting makes difference only in third person mode if 'view over shoulder' is enabled. +When player is close to an obstacle, automatically switches camera to the shoulder that is farther away from the obstacle. + +This setting can only be configured by editing the settings configuration file. + +zoom out when move coef +----------------------- + +:Type: floating point +:Range: Any +:Default: 20 + +This setting makes difference only in third person mode if 'view over shoulder' is enabled. +Slightly pulls camera away (or closer in case of negative value) when the character moves. To disable set it to zero. + +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 1e62ff5bff..35be128a89 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -42,6 +42,12 @@ view over shoulder = false # Makes sense only if 'view over shoulder' is true. First number is horizontal offset (negative value means offset to the left), second number is vertical offset. view over shoulder offset = 30 -10 +# Switch shoulder automatically when player is close to an obstacle. +auto switch shoulder = true + +# Slightly pulls camera away when the character moves. Works only in 'view over shoulder' mode. Set to 0 to disable. +zoom out when move coef = 20 + [Cells] # Preload cells in a background thread. All settings starting with 'preload' have no effect unless this is enabled.