Dehardcode camera

fix-static-urls
Petr Mikheev 4 years ago
parent e56ee2c735
commit f42badd7be

@ -62,6 +62,7 @@ namespace MWPhysics
namespace MWRender namespace MWRender
{ {
class Animation; class Animation;
class Camera;
} }
namespace MWMechanics namespace MWMechanics
@ -433,14 +434,12 @@ namespace MWBase
virtual osg::Matrixf getActorHeadTransform(const MWWorld::ConstPtr& actor) const = 0; virtual osg::Matrixf getActorHeadTransform(const MWWorld::ConstPtr& actor) const = 0;
virtual MWRender::Camera* getCamera() = 0;
virtual void togglePOV(bool force = false) = 0; virtual void togglePOV(bool force = false) = 0;
virtual bool isFirstPerson() const = 0; virtual bool isFirstPerson() const = 0;
virtual bool isPreviewModeEnabled() const = 0; virtual bool isPreviewModeEnabled() const = 0;
virtual void togglePreviewMode(bool enable) = 0;
virtual bool toggleVanityMode(bool enable) = 0; virtual bool toggleVanityMode(bool enable) = 0;
virtual void allowVanityMode(bool allow) = 0;
virtual bool vanityRotateCamera(float * rot) = 0; virtual bool vanityRotateCamera(float * rot) = 0;
virtual void adjustCameraDistance(float dist) = 0;
virtual void applyDeferredPreviewRotationToPlayer(float dt) = 0; virtual void applyDeferredPreviewRotationToPlayer(float dt) = 0;
virtual void disableDeferredPreviewRotation() = 0; virtual void disableDeferredPreviewRotation() = 0;

@ -27,7 +27,6 @@
namespace MWInput namespace MWInput
{ {
const float ZOOM_SCALE = 10.f; /// Used for scrolling camera in and out
ActionManager::ActionManager(BindingsManager* bindingsManager, ActionManager::ActionManager(BindingsManager* bindingsManager,
osgViewer::ScreenCaptureHandler::CaptureOperation* screenCaptureOperation, osgViewer::ScreenCaptureHandler::CaptureOperation* screenCaptureOperation,
@ -41,7 +40,6 @@ namespace MWInput
, mSneaking(false) , mSneaking(false)
, mAttemptJump(false) , mAttemptJump(false)
, mOverencumberedMessageDelay(0.f) , mOverencumberedMessageDelay(0.f)
, mPreviewPOVDelay(0.f)
, mTimeIdle(0.f) , mTimeIdle(0.f)
{ {
} }
@ -109,27 +107,6 @@ namespace MWInput
} }
} }
if (MWBase::Environment::get().getInputManager()->getControlSwitch("playerviewswitch"))
{
const float switchLimit = 0.25;
MWBase::World* world = MWBase::Environment::get().getWorld();
if (mBindingsManager->actionIsActive(A_TogglePOV))
{
if (world->isFirstPerson() ? mPreviewPOVDelay > switchLimit : mPreviewPOVDelay == 0)
world->togglePreviewMode(true);
mPreviewPOVDelay += dt;
}
else
{
//disable preview mode
if (mPreviewPOVDelay > 0)
world->togglePreviewMode(false);
if (mPreviewPOVDelay > 0.f && mPreviewPOVDelay <= switchLimit)
world->togglePOV();
mPreviewPOVDelay = 0.f;
}
}
if (triedToMove) if (triedToMove)
MWBase::Environment::get().getInputManager()->resetIdleTime(); MWBase::Environment::get().getInputManager()->resetIdleTime();
@ -162,38 +139,16 @@ namespace MWInput
resetIdleTime(); resetIdleTime();
} }
else else
{ mTimeIdle += dt;
updateIdleTime(dt);
}
mAttemptJump = false; mAttemptJump = false;
} }
bool ActionManager::isPreviewModeEnabled()
{
return MWBase::Environment::get().getWorld()->isPreviewModeEnabled();
}
void ActionManager::resetIdleTime() void ActionManager::resetIdleTime()
{ {
if (mTimeIdle < 0)
MWBase::Environment::get().getWorld()->toggleVanityMode(false);
mTimeIdle = 0.f; mTimeIdle = 0.f;
} }
void ActionManager::updateIdleTime(float dt)
{
static const float vanityDelay = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>()
.find("fVanityDelay")->mValue.getFloat();
if (mTimeIdle >= 0.f)
mTimeIdle += dt;
if (mTimeIdle > vanityDelay)
{
MWBase::Environment::get().getWorld()->toggleVanityMode(true);
mTimeIdle = -1.f;
}
}
void ActionManager::executeAction(int action) void ActionManager::executeAction(int action)
{ {
MWBase::Environment::get().getLuaManager()->inputEvent({MWBase::LuaManager::InputEvent::Action, action}); MWBase::Environment::get().getLuaManager()->inputEvent({MWBase::LuaManager::InputEvent::Action, action});
@ -281,14 +236,6 @@ namespace MWInput
case A_ToggleDebug: case A_ToggleDebug:
windowManager->toggleDebugWindow(); windowManager->toggleDebugWindow();
break; break;
case A_ZoomIn:
if (inputManager->getControlSwitch("playerviewswitch") && inputManager->getControlSwitch("playercontrols") && !windowManager->isGuiMode())
MWBase::Environment::get().getWorld()->adjustCameraDistance(-ZOOM_SCALE);
break;
case A_ZoomOut:
if (inputManager->getControlSwitch("playerviewswitch") && inputManager->getControlSwitch("playercontrols") && !windowManager->isGuiMode())
MWBase::Environment::get().getWorld()->adjustCameraDistance(ZOOM_SCALE);
break;
case A_QuickSave: case A_QuickSave:
quickSave(); quickSave();
break; break;

@ -55,13 +55,9 @@ namespace MWInput
void setAttemptJump(bool enabled) { mAttemptJump = enabled; } void setAttemptJump(bool enabled) { mAttemptJump = enabled; }
bool isPreviewModeEnabled();
private: private:
void handleGuiArrowKey(int action); void handleGuiArrowKey(int action);
void updateIdleTime(float dt);
BindingsManager* mBindingsManager; BindingsManager* mBindingsManager;
osg::ref_ptr<osgViewer::Viewer> mViewer; osg::ref_ptr<osgViewer::Viewer> mViewer;
osg::ref_ptr<osgViewer::ScreenCaptureHandler> mScreenCaptureHandler; osg::ref_ptr<osgViewer::ScreenCaptureHandler> mScreenCaptureHandler;
@ -72,7 +68,6 @@ namespace MWInput
bool mAttemptJump; bool mAttemptJump;
float mOverencumberedMessageDelay; float mOverencumberedMessageDelay;
float mPreviewPOVDelay;
float mTimeIdle; float mTimeIdle;
}; };
} }

@ -34,12 +34,10 @@ namespace MWInput
, mJoystickEnabled (Settings::Manager::getBool("enable controller", "Input")) , mJoystickEnabled (Settings::Manager::getBool("enable controller", "Input"))
, mGamepadCursorSpeed(Settings::Manager::getFloat("gamepad cursor speed", "Input")) , mGamepadCursorSpeed(Settings::Manager::getFloat("gamepad cursor speed", "Input"))
, mSneakToggleShortcutTimer(0.f) , mSneakToggleShortcutTimer(0.f)
, mGamepadZoom(0)
, mGamepadGuiCursorEnabled(true) , mGamepadGuiCursorEnabled(true)
, mGuiCursorEnabled(true) , mGuiCursorEnabled(true)
, mJoystickLastUsed(false) , mJoystickLastUsed(false)
, mSneakGamepadShortcut(false) , mSneakGamepadShortcut(false)
, mGamepadPreviewMode(false)
{ {
if (!controllerBindingsFile.empty()) if (!controllerBindingsFile.empty())
{ {
@ -85,8 +83,6 @@ namespace MWInput
bool ControllerManager::update(float dt) bool ControllerManager::update(float dt)
{ {
mGamepadPreviewMode = mActionManager->isPreviewModeEnabled();
if (mGuiCursorEnabled && !(mJoystickLastUsed && !mGamepadGuiCursorEnabled)) if (mGuiCursorEnabled && !(mJoystickLastUsed && !mGamepadGuiCursorEnabled))
{ {
float xAxis = mBindingsManager->getActionValue(A_MoveLeftRight) * 2.0f - 1.0f; float xAxis = mBindingsManager->getActionValue(A_MoveLeftRight) * 2.0f - 1.0f;
@ -115,7 +111,6 @@ namespace MWInput
if (MWBase::Environment::get().getWindowManager()->isGuiMode() if (MWBase::Environment::get().getWindowManager()->isGuiMode()
|| MWBase::Environment::get().getStateManager()->getState() != MWBase::StateManager::State_Running) || MWBase::Environment::get().getStateManager()->getState() != MWBase::StateManager::State_Running)
{ {
mGamepadZoom = 0;
return false; return false;
} }
@ -182,15 +177,6 @@ namespace MWInput
} }
} }
if (MWBase::Environment::get().getInputManager()->getControlSwitch("playerviewswitch"))
{
if (!mBindingsManager->actionIsActive(A_TogglePOV))
mGamepadZoom = 0;
if (mGamepadZoom)
MWBase::Environment::get().getWorld()->adjustCameraDistance(-mGamepadZoom);
}
return triedToMove; return triedToMove;
} }
@ -289,21 +275,11 @@ namespace MWInput
{ {
gamepadToGuiControl(arg); gamepadToGuiControl(arg);
} }
else else if (MWBase::Environment::get().getWorld()->isPreviewModeEnabled() &&
(arg.axis == SDL_CONTROLLER_AXIS_TRIGGERRIGHT || arg.axis == SDL_CONTROLLER_AXIS_TRIGGERLEFT))
{ {
if (mGamepadPreviewMode) // Preview Mode Gamepad Zooming // Preview Mode Gamepad Zooming; do not propagate to mBindingsManager
{ return;
if (arg.axis == SDL_CONTROLLER_AXIS_TRIGGERRIGHT)
{
mGamepadZoom = arg.value * 0.85f / 1000.f / 12.f;
return; // Do not propagate event.
}
else if (arg.axis == SDL_CONTROLLER_AXIS_TRIGGERLEFT)
{
mGamepadZoom = -arg.value * 0.85f / 1000.f / 12.f;
return; // Do not propagate event.
}
}
} }
mBindingsManager->controllerAxisMoved(deviceID, arg); mBindingsManager->controllerAxisMoved(deviceID, arg);
} }

@ -56,12 +56,10 @@ namespace MWInput
bool mJoystickEnabled; bool mJoystickEnabled;
float mGamepadCursorSpeed; float mGamepadCursorSpeed;
float mSneakToggleShortcutTimer; float mSneakToggleShortcutTimer;
float mGamepadZoom;
bool mGamepadGuiCursorEnabled; bool mGamepadGuiCursorEnabled;
bool mGuiCursorEnabled; bool mGuiCursorEnabled;
bool mJoystickLastUsed; bool mJoystickLastUsed;
bool mSneakGamepadShortcut; bool mSneakGamepadShortcut;
bool mGamepadPreviewMode;
}; };
} }
#endif #endif

@ -54,10 +54,6 @@ namespace MWInput
/// \fixme maybe crouching at this time /// \fixme maybe crouching at this time
player.setUpDown(0); player.setUpDown(0);
} }
else if (key == "vanitymode")
{
MWBase::Environment::get().getWorld()->allowVanityMode(value);
}
else if (key == "playerlooking" && !value) else if (key == "playerlooking" && !value)
{ {
MWBase::Environment::get().getWorld()->rotateObject(player.getPlayer(), osg::Vec3f()); MWBase::Environment::get().getWorld()->rotateObject(player.getPlayer(), osg::Vec3f());

@ -1,12 +1,82 @@
#include "luabindings.hpp" #include "luabindings.hpp"
#include "../mwrender/camera.hpp"
namespace MWLua namespace MWLua
{ {
using CameraMode = MWRender::Camera::Mode;
sol::table initCameraPackage(const Context& context) sol::table initCameraPackage(const Context& context)
{ {
MWRender::Camera* camera = MWBase::Environment::get().getWorld()->getCamera();
sol::table api(context.mLua->sol(), sol::create); sol::table api(context.mLua->sol(), sol::create);
// TODO api["MODE"] = LuaUtil::makeReadOnly(context.mLua->sol().create_table_with(
"Static", CameraMode::Static,
"FirstPerson", CameraMode::FirstPerson,
"ThirdPerson", CameraMode::ThirdPerson,
"Vanity", CameraMode::Vanity,
"Preview", CameraMode::Preview
));
api["getMode"] = [camera]() -> int { return static_cast<int>(camera->getMode()); };
api["getQueuedMode"] = [camera]() -> sol::optional<int>
{
std::optional<CameraMode> mode = camera->getQueuedMode();
if (mode)
return static_cast<int>(*mode);
else
return sol::nullopt;
};
api["setMode"] = [camera](int mode, sol::optional<bool> force)
{
camera->setMode(static_cast<CameraMode>(mode), force ? *force : false);
};
api["allowCharacterDeferredRotation"] = [camera](bool v) { camera->allowCharacterDeferredRotation(v); };
api["showCrosshair"] = [camera](bool v) { camera->showCrosshair(v); };
api["getTrackedPosition"] = [camera]() -> osg::Vec3f { return camera->getTrackedPosition(); };
api["getPosition"] = [camera]() -> osg::Vec3f { return camera->getPosition(); };
// All angles are negated in order to make camera rotation consistent with objects rotation.
// TODO: Fix the inconsistency of rotation direction in camera.cpp.
api["getPitch"] = [camera]() { return -camera->getPitch(); };
api["getYaw"] = [camera]() { return -camera->getYaw(); };
api["getRoll"] = [camera]() { return -camera->getRoll(); };
api["setStaticPosition"] = [camera](const osg::Vec3f& pos) { camera->setStaticPosition(pos); };
api["setPitch"] = [camera](float v)
{
camera->setPitch(-v, true);
if (camera->getMode() == CameraMode::ThirdPerson)
camera->calculateDeferredRotation();
};
api["setYaw"] = [camera](float v)
{
camera->setYaw(-v, true);
if (camera->getMode() == CameraMode::ThirdPerson)
camera->calculateDeferredRotation();
};
api["setRoll"] = [camera](float v) { camera->setRoll(-v); };
api["setExtraPitch"] = [camera](float v) { camera->setExtraPitch(-v); };
api["setExtraYaw"] = [camera](float v) { camera->setExtraYaw(-v); };
api["getExtraPitch"] = [camera]() { return -camera->getExtraPitch(); };
api["getExtraYaw"] = [camera]() { return -camera->getExtraYaw(); };
api["getThirdPersonDistance"] = [camera]() { return camera->getCameraDistance(); };
api["setPreferredThirdPersonDistance"] = [camera](float v) { camera->setPreferredCameraDistance(v); };
api["getFirstPersonOffset"] = [camera]() { return camera->getFirstPersonOffset(); };
api["setFirstPersonOffset"] = [camera](const osg::Vec3f& v) { camera->setFirstPersonOffset(v); };
api["getFocalPreferredOffset"] = [camera]() -> osg::Vec2f { return camera->getFocalPointTargetOffset(); };
api["setFocalPreferredOffset"] = [camera](const osg::Vec2f& v) { camera->setFocalPointTargetOffset(v); };
api["getFocalTransitionSpeed"] = [camera]() { return camera->getFocalPointTransitionSpeed(); };
api["setFocalTransitionSpeed"] = [camera](float v) { camera->setFocalPointTransitionSpeed(v); };
api["instantTransition"] = [camera]() { camera->instantTransition(); };
return LuaUtil::makeReadOnly(api); return LuaUtil::makeReadOnly(api);
} }

@ -25,7 +25,7 @@ namespace MWLua
{ {
auto* lua = context.mLua; auto* lua = context.mLua;
sol::table api(lua->sol(), sol::create); sol::table api(lua->sol(), sol::create);
api["API_REVISION"] = 9; api["API_REVISION"] = 10;
api["quit"] = [lua]() api["quit"] = [lua]()
{ {
Log(Debug::Warning) << "Quit requested by a Lua script.\n" << lua->debugTraceback(); Log(Debug::Warning) << "Quit requested by a Lua script.\n" << lua->debugTraceback();

@ -58,37 +58,23 @@ namespace MWRender
mFirstPersonView(true), mFirstPersonView(true),
mMode(Mode::FirstPerson), mMode(Mode::FirstPerson),
mVanityAllowed(true), mVanityAllowed(true),
mStandingPreviewAllowed(Settings::Manager::getBool("preview if stand still", "Camera")), mDeferredRotationAllowed(true),
mDeferredRotationAllowed(Settings::Manager::getBool("deferred preview rotation", "Camera")),
mNearest(30.f),
mFurthest(800.f),
mIsNearest(false),
mProcessViewChange(false), mProcessViewChange(false),
mHeight(124.f), mHeight(124.f),
mBaseCameraDistance(Settings::Manager::getFloat("third person camera distance", "Camera")),
mPitch(0.f), mPitch(0.f),
mYaw(0.f), mYaw(0.f),
mRoll(0.f), mRoll(0.f),
mCameraDistance(0.f), mCameraDistance(0.f),
mMaxNextCameraDistance(800.f), mPreferredCameraDistance(0.f),
mFocalPointCurrentOffset(osg::Vec2d()), mFocalPointCurrentOffset(osg::Vec2d()),
mFocalPointTargetOffset(osg::Vec2d()), mFocalPointTargetOffset(osg::Vec2d()),
mFocalPointTransitionSpeedCoef(1.f), mFocalPointTransitionSpeedCoef(1.f),
mSkipFocalPointTransition(true), mSkipFocalPointTransition(true),
mPreviousTransitionInfluence(0.f), mPreviousTransitionInfluence(0.f),
mSmoothedSpeed(0.f), mShowCrosshair(false),
mZoomOutWhenMoveCoef(Settings::Manager::getFloat("zoom out when move coef", "Camera")),
mDynamicCameraDistanceEnabled(false),
mShowCrosshairInThirdPersonMode(false),
mHeadBobbingEnabled(Settings::Manager::getBool("head bobbing", "Camera")),
mHeadBobbingOffset(0.f),
mHeadBobbingWeight(0.f),
mTotalMovement(0.f),
mDeferredRotation(osg::Vec3f()), mDeferredRotation(osg::Vec3f()),
mDeferredRotationDisabled(false) mDeferredRotationDisabled(false)
{ {
mCameraDistance = mBaseCameraDistance;
mUpdateCallback = new UpdateRenderCameraCallback(this); mUpdateCallback = new UpdateRenderCameraCallback(this);
mCamera->addUpdateCallback(mUpdateCallback); mCamera->addUpdateCallback(mUpdateCallback);
} }
@ -98,7 +84,7 @@ namespace MWRender
mCamera->removeUpdateCallback(mUpdateCallback); mCamera->removeUpdateCallback(mUpdateCallback);
} }
osg::Vec3d Camera::getTrackingNodePosition() const osg::Vec3d Camera::calculateTrackedPosition() const
{ {
if (!mTrackingNode) if (!mTrackingNode)
return osg::Vec3d(); return osg::Vec3d();
@ -106,59 +92,46 @@ namespace MWRender
if (nodepaths.empty()) if (nodepaths.empty())
return osg::Vec3d(); return osg::Vec3d();
osg::Matrix worldMat = osg::computeLocalToWorld(nodepaths[0]); osg::Matrix worldMat = osg::computeLocalToWorld(nodepaths[0]);
return worldMat.getTrans(); osg::Vec3d res = worldMat.getTrans();
} if (mMode != Mode::FirstPerson)
res.z() += mHeight * mHeightScale;
osg::Vec3d Camera::getThirdPersonBasePosition() const return res;
{
osg::Vec3d position = getTrackingNodePosition();
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;
return position;
} }
osg::Vec3d Camera::getFocalPointOffset() const osg::Vec3d Camera::getFocalPointOffset() const
{ {
osg::Vec3d offset(0, 0, 10.f); osg::Vec3d offset;
offset.x() += mFocalPointCurrentOffset.x() * cos(mYaw); offset.x() = mFocalPointCurrentOffset.x() * cos(mYaw);
offset.y() += mFocalPointCurrentOffset.x() * sin(mYaw); offset.y() = mFocalPointCurrentOffset.x() * sin(mYaw);
offset.z() += mFocalPointCurrentOffset.y(); offset.z() = mFocalPointCurrentOffset.y();
return offset; return offset;
} }
void Camera::updateCamera(osg::Camera *cam) void Camera::updateCamera(osg::Camera *cam)
{ {
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::Quat orient = osg::Quat(mRoll, osg::Vec3d(0, 1, 0)) *
osg::Quat(mPitch + mExtraPitch, osg::Vec3d(1, 0, 0)) *
osg::Quat(mYaw + mExtraYaw, osg::Vec3d(0, 0, 1));
osg::Vec3d forward = orient * osg::Vec3d(0,1,0); osg::Vec3d forward = orient * osg::Vec3d(0,1,0);
osg::Vec3d up = orient * osg::Vec3d(0,0,1); osg::Vec3d up = orient * osg::Vec3d(0,0,1);
cam->setViewMatrixAsLookAt(mPosition, mPosition + forward, up); osg::Vec3d pos = mPosition;
} if (mMode == Mode::FirstPerson)
{
void Camera::updateHeadBobbing(float duration) { // It is a hack. Camera position depends on neck animation.
static const float doubleStepLength = Settings::Manager::getFloat("head bobbing step", "Camera") * 2; // Animations are updated in OSG cull traversal and in order to avoid 1 frame delay we
static const float stepHeight = Settings::Manager::getFloat("head bobbing height", "Camera"); // recalculate the position here. Note that it becomes different from mPosition that
static const float maxRoll = osg::DegreesToRadians(Settings::Manager::getFloat("head bobbing roll", "Camera")); // is used in other parts of the code.
// TODO: detach camera from OSG animation and get rid of this hack.
if (MWBase::Environment::get().getWorld()->isOnGround(mTrackingPtr)) osg::Vec3d recalculatedTrackedPosition = calculateTrackedPosition();
mHeadBobbingWeight = std::min(mHeadBobbingWeight + duration * 5, 1.f); pos = calculateFirstPersonPosition(recalculatedTrackedPosition);
else }
mHeadBobbingWeight = std::max(mHeadBobbingWeight - duration * 5, 0.f); cam->setViewMatrixAsLookAt(pos, pos + forward, up);
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::update(float duration, bool paused) void Camera::update(float duration, bool paused)
{ {
mLockPitch = mLockYaw = false;
if (mQueuedMode && mAnimation->upperBodyReady()) if (mQueuedMode && mAnimation->upperBodyReady())
setMode(*mQueuedMode); setMode(*mQueuedMode);
if (mProcessViewChange) if (mProcessViewChange)
@ -169,38 +142,31 @@ namespace MWRender
// only show the crosshair in game mode // only show the crosshair in game mode
MWBase::WindowManager *wm = MWBase::Environment::get().getWindowManager(); MWBase::WindowManager *wm = MWBase::Environment::get().getWindowManager();
wm->showCrosshair(!wm->isGuiMode() && mMode != Mode::Preview && mMode != Mode::Vanity wm->showCrosshair(!wm->isGuiMode() && mShowCrosshair);
&& (mFirstPersonView || mShowCrosshairInThirdPersonMode));
if(mMode == Mode::Vanity)
setYaw(mYaw + osg::DegreesToRadians(3.f) * duration);
if (mMode == Mode::FirstPerson && mHeadBobbingEnabled)
updateHeadBobbing(duration);
else
mRoll = mHeadBobbingOffset = 0;
updateFocalPointOffset(duration); updateFocalPointOffset(duration);
updatePosition(); updatePosition();
}
float speed = mTrackingPtr.getClass().getCurrentSpeed(mTrackingPtr); osg::Vec3d Camera::calculateFirstPersonPosition(const osg::Vec3d& trackedPosition) const
mTotalMovement += speed * duration; {
speed /= (1.f + speed / 500.f); osg::Vec3d res = trackedPosition;
float maxDelta = 300.f * duration; osg::Vec2f horizontalOffset = Misc::rotateVec2f(osg::Vec2f(mFirstPersonOffset.x(), mFirstPersonOffset.y()), mYaw);
mSmoothedSpeed += std::clamp(speed - mSmoothedSpeed, -maxDelta, maxDelta); res.x() += horizontalOffset.x();
res.y() += horizontalOffset.y();
mMaxNextCameraDistance = mCameraDistance + duration * (100.f + mBaseCameraDistance); res.z() += mFirstPersonOffset.z();
updateStandingPreviewMode(); return res;
} }
void Camera::updatePosition() void Camera::updatePosition()
{ {
mTrackedPosition = calculateTrackedPosition();
if (mMode == Mode::Static) if (mMode == Mode::Static)
return; return;
if (mMode == Mode::FirstPerson) if (mMode == Mode::FirstPerson)
{ {
mPosition = getTrackingNodePosition(); mPosition = calculateFirstPersonPosition(mTrackedPosition);
mPosition.z() += mHeadBobbingOffset; mCameraDistance = 0;
return; return;
} }
@ -212,7 +178,9 @@ namespace MWRender
// Adjust focal point to prevent clipping. // Adjust focal point to prevent clipping.
osg::Vec3d focalOffset = getFocalPointOffset(); osg::Vec3d focalOffset = getFocalPointOffset();
osg::Vec3d focal = getThirdPersonBasePosition() + focalOffset; osg::Vec3d focal = mTrackedPosition + focalOffset;
focalOffset.z() += 10.f; // Needed to avoid camera clipping through the ceiling because
// character's head can be a bit higher than the collision area.
float offsetLen = focalOffset.length(); float offsetLen = focalOffset.length();
if (offsetLen > 0) if (offsetLen > 0)
{ {
@ -224,12 +192,9 @@ namespace MWRender
} }
} }
// Calculate offset from focal point. // Adjust camera distance.
mCameraDistance = mBaseCameraDistance + getCameraDistanceCorrection(); mCameraDistance = mPreferredCameraDistance;
if (mDynamicCameraDistanceEnabled) osg::Quat orient = osg::Quat(mPitch + mExtraPitch, osg::Vec3d(1,0,0)) * osg::Quat(mYaw + mExtraYaw, osg::Vec3d(0,0,1));
mCameraDistance = std::min(mCameraDistance, mMaxNextCameraDistance);
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.f, -mCameraDistance, 0.f); osg::Vec3d offset = orient * osg::Vec3d(0.f, -mCameraDistance, 0.f);
MWPhysics::RayCastingResult result = rayCasting->castSphere(focal, focal + offset, cameraObstacleLimit, collisionType); MWPhysics::RayCastingResult result = rayCasting->castSphere(focal, focal + offset, cameraObstacleLimit, collisionType);
if (result.mHit) if (result.mHit)
@ -243,8 +208,6 @@ namespace MWRender
void Camera::setMode(Mode newMode, bool force) void Camera::setMode(Mode newMode, bool force)
{ {
if (newMode == Mode::StandingPreview)
newMode = Mode::ThirdPerson;
if (mMode == newMode) if (mMode == newMode)
return; return;
Mode oldMode = mMode; Mode oldMode = mMode;
@ -267,23 +230,6 @@ namespace MWRender
instantTransition(); instantTransition();
mProcessViewChange = true; mProcessViewChange = true;
} }
else if (newMode == Mode::ThirdPerson)
updateStandingPreviewMode();
}
void Camera::updateStandingPreviewMode()
{
float speed = mTrackingPtr.getClass().getCurrentSpeed(mTrackingPtr);
bool combat = mTrackingPtr.getClass().isActor() &&
mTrackingPtr.getClass().getCreatureStats(mTrackingPtr).getDrawState() != MWMechanics::DrawState_Nothing;
bool standingStill = speed == 0 && !combat && mStandingPreviewAllowed;
if (!standingStill && mMode == Mode::StandingPreview)
{
mMode = Mode::ThirdPerson;
calculateDeferredRotation();
}
else if (standingStill && mMode == Mode::ThirdPerson)
mMode = Mode::StandingPreview;
} }
void Camera::setFocalPointTargetOffset(const osg::Vec2d& v) void Camera::setFocalPointTargetOffset(const osg::Vec2d& v)
@ -339,13 +285,6 @@ namespace MWRender
setMode(mFirstPersonView ? Mode::ThirdPerson : Mode::FirstPerson, force); setMode(mFirstPersonView ? Mode::ThirdPerson : Mode::FirstPerson, force);
} }
void Camera::allowVanityMode(bool allow)
{
if (!allow && mMode == Mode::Vanity)
toggleVanityMode(false);
mVanityAllowed = allow;
}
bool Camera::toggleVanityMode(bool enable) bool Camera::toggleVanityMode(bool enable)
{ {
if (!enable) if (!enable)
@ -355,73 +294,34 @@ namespace MWRender
return (mMode == Mode::Vanity) == enable; return (mMode == Mode::Vanity) == enable;
} }
void Camera::togglePreviewMode(bool enable)
{
if (mFirstPersonView && !mAnimation->upperBodyReady())
return;
if ((mMode == Mode::Preview) == enable)
return;
if (enable)
setMode(Mode::Preview);
else
setMode(mFirstPersonView ? Mode::FirstPerson : Mode::ThirdPerson);
}
void Camera::setSneakOffset(float offset) void Camera::setSneakOffset(float offset)
{ {
mAnimation->setFirstPersonOffset(osg::Vec3f(0,0,-offset)); mAnimation->setFirstPersonOffset(osg::Vec3f(0,0,-offset));
} }
void Camera::setYaw(float angle) void Camera::setYaw(float angle, bool force)
{ {
mYaw = Misc::normalizeAngle(angle); if (!mLockYaw || force)
mYaw = Misc::normalizeAngle(angle);
if (force)
mLockYaw = true;
} }
void Camera::setPitch(float angle) void Camera::setPitch(float angle, bool force)
{ {
const float epsilon = 0.000001f; const float epsilon = 0.000001f;
float limit = static_cast<float>(osg::PI_2) - epsilon; float limit = static_cast<float>(osg::PI_2) - epsilon;
mPitch = std::clamp(angle, -limit, limit); if (!mLockPitch || force)
mPitch = std::clamp(angle, -limit, limit);
if (force)
mLockPitch = true;
} }
float Camera::getCameraDistance() const void Camera::setStaticPosition(const osg::Vec3d& pos)
{ {
return mMode == Mode::FirstPerson ? 0.f : mCameraDistance; if (mMode != Mode::Static)
} throw std::runtime_error("setStaticPosition can be used only if camera is in Static mode");
mPosition = pos;
void Camera::adjustCameraDistance(float delta)
{
if (mMode == Mode::Static)
return;
if (mMode != Mode::FirstPerson)
{
if (mIsNearest && delta < 0.f && mMode != Mode::Preview && mMode != Mode::Vanity)
toggleViewMode();
else
mBaseCameraDistance = std::min(mCameraDistance - getCameraDistanceCorrection(), mBaseCameraDistance) + delta;
}
else if (delta > 0.f)
{
toggleViewMode();
mBaseCameraDistance = 0;
}
mIsNearest = mBaseCameraDistance <= mNearest;
mBaseCameraDistance = std::clamp(mBaseCameraDistance, mNearest, mFurthest);
Settings::Manager::setFloat("third person camera distance", "Camera", mBaseCameraDistance);
}
float Camera::getCameraDistanceCorrection() const
{
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::setAnimation(NpcAnimation *anim) void Camera::setAnimation(NpcAnimation *anim)
@ -432,6 +332,8 @@ namespace MWRender
void Camera::processViewChange() void Camera::processViewChange()
{ {
if (mTrackingPtr.isEmpty())
return;
if (mMode == Mode::FirstPerson) if (mMode == Mode::FirstPerson)
{ {
mAnimation->setViewMode(NpcAnimation::VM_FirstPerson); mAnimation->setViewMode(NpcAnimation::VM_FirstPerson);
@ -488,7 +390,7 @@ namespace MWRender
void Camera::rotateCameraToTrackingPtr() void Camera::rotateCameraToTrackingPtr()
{ {
if (mMode == Mode::Static) if (mMode == Mode::Static || mTrackingPtr.isEmpty())
return; return;
setPitch(-mTrackingPtr.getRefData().getPosition().rot[0] - mDeferredRotation.x()); setPitch(-mTrackingPtr.getRefData().getPosition().rot[0] - mDeferredRotation.x());
setYaw(-mTrackingPtr.getRefData().getPosition().rot[2] - mDeferredRotation.z()); setYaw(-mTrackingPtr.getRefData().getPosition().rot[2] - mDeferredRotation.z());
@ -524,9 +426,4 @@ namespace MWRender
mDeferredRotationDisabled = true; mDeferredRotationDisabled = true;
} }
bool Camera::isVanityOrPreviewModeEnabled() const
{
return mMode == Mode::Vanity || mMode == Mode::Preview || mMode == Mode::StandingPreview;
}
} }

@ -25,11 +25,80 @@ namespace MWRender
class Camera class Camera
{ {
public: public:
enum class Mode : int {Static = 0, FirstPerson = 1, ThirdPerson = 2, Vanity = 3, Preview = 4, StandingPreview = 5}; enum class Mode : int {Static = 0, FirstPerson = 1, ThirdPerson = 2, Vanity = 3, Preview = 4};
Camera(osg::Camera* camera);
~Camera();
/// Attach camera to object
void attachTo(const MWWorld::Ptr &ptr) { mTrackingPtr = ptr; }
MWWorld::Ptr getTrackingPtr() const { return mTrackingPtr; }
void setFocalPointTransitionSpeed(float v) { mFocalPointTransitionSpeedCoef = v; }
float getFocalPointTransitionSpeed() const { return mFocalPointTransitionSpeedCoef; }
void setFocalPointTargetOffset(const osg::Vec2d& v);
osg::Vec2d getFocalPointTargetOffset() const { return mFocalPointTargetOffset; }
void instantTransition();
void showCrosshair(bool v) { mShowCrosshair = v; }
/// Update the view matrix of \a cam
void updateCamera(osg::Camera* cam);
/// Reset to defaults
void reset() { setMode(Mode::FirstPerson); }
void rotateCameraToTrackingPtr();
float getPitch() const { return mPitch; }
float getYaw() const { return mYaw; }
float getRoll() const { return mRoll; }
void setPitch(float angle, bool force = false);
void setYaw(float angle, bool force = false);
void setRoll(float angle) { mRoll = angle; }
float getExtraPitch() const { return mExtraPitch; }
float getExtraYaw() const { return mExtraYaw; }
void setExtraPitch(float angle) { mExtraPitch = angle; }
void setExtraYaw(float angle) { mExtraYaw = angle; }
/// @param Force view mode switch, even if currently not allowed by the animation.
void toggleViewMode(bool force=false);
bool toggleVanityMode(bool enable);
void applyDeferredPreviewRotationToPlayer(float dt);
void disableDeferredPreviewRotation() { mDeferredRotationDisabled = true; }
/// \brief Lowers the camera for sneak.
void setSneakOffset(float offset);
void processViewChange();
void update(float duration, bool paused=false);
float getCameraDistance() const { return mCameraDistance; }
void setPreferredCameraDistance(float v) { mPreferredCameraDistance = v; }
void setAnimation(NpcAnimation *anim);
osg::Vec3d getTrackedPosition() const { return mTrackedPosition; }
const osg::Vec3d& getPosition() const { return mPosition; }
void setStaticPosition(const osg::Vec3d& pos);
bool isVanityOrPreviewModeEnabled() const { return mMode == Mode::Vanity || mMode == Mode::Preview; }
Mode getMode() const { return mMode; }
std::optional<Mode> getQueuedMode() const { return mQueuedMode; }
void setMode(Mode mode, bool force = true);
void allowCharacterDeferredRotation(bool v) { mDeferredRotationAllowed = v; }
void calculateDeferredRotation();
void setFirstPersonOffset(const osg::Vec3f& v) { mFirstPersonOffset = v; }
osg::Vec3f getFirstPersonOffset() const { return mFirstPersonOffset; }
private: private:
MWWorld::Ptr mTrackingPtr; MWWorld::Ptr mTrackingPtr;
osg::ref_ptr<const osg::Node> mTrackingNode; osg::ref_ptr<const osg::Node> mTrackingNode;
osg::Vec3d mTrackedPosition;
float mHeightScale; float mHeightScale;
osg::ref_ptr<osg::Camera> mCamera; osg::ref_ptr<osg::Camera> mCamera;
@ -43,21 +112,19 @@ namespace MWRender
Mode mMode; Mode mMode;
std::optional<Mode> mQueuedMode; std::optional<Mode> mQueuedMode;
bool mVanityAllowed; bool mVanityAllowed;
bool mStandingPreviewAllowed;
bool mDeferredRotationAllowed; bool mDeferredRotationAllowed;
float mNearest;
float mFurthest;
bool mIsNearest;
bool mProcessViewChange; bool mProcessViewChange;
float mHeight, mBaseCameraDistance; float mHeight;
float mPitch, mYaw, mRoll; float mPitch, mYaw, mRoll;
float mExtraPitch = 0, mExtraYaw = 0;
bool mLockPitch = false, mLockYaw = false;
osg::Vec3d mPosition; osg::Vec3d mPosition;
float mCameraDistance; float mCameraDistance, mPreferredCameraDistance;
float mMaxNextCameraDistance;
osg::Vec3f mFirstPersonOffset{0, 0, 0};
osg::Vec2d mFocalPointCurrentOffset; osg::Vec2d mFocalPointCurrentOffset;
osg::Vec2d mFocalPointTargetOffset; osg::Vec2d mFocalPointTargetOffset;
@ -70,91 +137,19 @@ namespace MWRender
osg::Vec2d mPreviousTransitionSpeed; osg::Vec2d mPreviousTransitionSpeed;
osg::Vec2d mPreviousExtraOffset; osg::Vec2d mPreviousExtraOffset;
float mSmoothedSpeed; bool mShowCrosshair;
float mZoomOutWhenMoveCoef;
bool mDynamicCameraDistanceEnabled;
bool mShowCrosshairInThirdPersonMode;
bool mHeadBobbingEnabled;
float mHeadBobbingOffset;
float mHeadBobbingWeight; // Value from 0 to 1 for smooth enabling/disabling.
float mTotalMovement; // Needed for head bobbing.
void updateHeadBobbing(float duration);
osg::Vec3d getTrackingNodePosition() const; osg::Vec3d calculateTrackedPosition() const;
osg::Vec3d calculateFirstPersonPosition(const osg::Vec3d& trackedPosition) const;
osg::Vec3d getFocalPointOffset() const; osg::Vec3d getFocalPointOffset() const;
void updateFocalPointOffset(float duration); void updateFocalPointOffset(float duration);
void updatePosition(); void updatePosition();
float getCameraDistanceCorrection() const;
osg::ref_ptr<osg::Callback> mUpdateCallback; osg::ref_ptr<osg::Callback> mUpdateCallback;
// Used to rotate player to the direction of view after exiting preview or vanity mode. // Used to rotate player to the direction of view after exiting preview or vanity mode.
osg::Vec3f mDeferredRotation; osg::Vec3f mDeferredRotation;
bool mDeferredRotationDisabled; bool mDeferredRotationDisabled;
void calculateDeferredRotation();
void updateStandingPreviewMode();
public:
Camera(osg::Camera* camera);
~Camera();
/// Attach camera to object
void attachTo(const MWWorld::Ptr &ptr) { mTrackingPtr = ptr; }
MWWorld::Ptr getTrackingPtr() const { return mTrackingPtr; }
void setFocalPointTransitionSpeed(float v) { mFocalPointTransitionSpeedCoef = v; }
void setFocalPointTargetOffset(const osg::Vec2d& v);
void instantTransition();
void enableDynamicCameraDistance(bool v) { mDynamicCameraDistanceEnabled = v; }
void enableCrosshairInThirdPersonMode(bool v) { mShowCrosshairInThirdPersonMode = v; }
/// Update the view matrix of \a cam
void updateCamera(osg::Camera* cam);
/// Reset to defaults
void reset() { setMode(Mode::FirstPerson); }
void rotateCameraToTrackingPtr();
float getYaw() const { return mYaw; }
void setYaw(float angle);
float getPitch() const { return mPitch; }
void setPitch(float angle);
/// @param Force view mode switch, even if currently not allowed by the animation.
void toggleViewMode(bool force=false);
bool toggleVanityMode(bool enable);
void allowVanityMode(bool allow);
/// @note this may be ignored if an important animation is currently playing
void togglePreviewMode(bool enable);
void applyDeferredPreviewRotationToPlayer(float dt);
void disableDeferredPreviewRotation() { mDeferredRotationDisabled = true; }
/// \brief Lowers the camera for sneak.
void setSneakOffset(float offset);
void processViewChange();
void update(float duration, bool paused=false);
/// Adds distDelta to the camera distance. Switches 3rd/1st person view if distance is less than limit.
void adjustCameraDistance(float distDelta);
float getCameraDistance() const;
void setAnimation(NpcAnimation *anim);
osg::Vec3d getThirdPersonBasePosition() const;
const osg::Vec3d& getPosition() const { return mPosition; }
bool isVanityOrPreviewModeEnabled() const;
Mode getMode() const { return mMode; }
void setMode(Mode mode, bool force = true);
}; };
} }

@ -59,7 +59,6 @@
#include "vismask.hpp" #include "vismask.hpp"
#include "pathgrid.hpp" #include "pathgrid.hpp"
#include "camera.hpp" #include "camera.hpp"
#include "viewovershoulder.hpp"
#include "water.hpp" #include "water.hpp"
#include "terrainstorage.hpp" #include "terrainstorage.hpp"
#include "navmesh.hpp" #include "navmesh.hpp"
@ -472,8 +471,6 @@ namespace MWRender
mWater.reset(new Water(sceneRoot->getParent(0), sceneRoot, mResourceSystem, mViewer->getIncrementalCompileOperation(), resourcePath)); mWater.reset(new Water(sceneRoot->getParent(0), sceneRoot, mResourceSystem, mViewer->getIncrementalCompileOperation(), resourcePath));
mCamera.reset(new Camera(mViewer->getCamera())); mCamera.reset(new Camera(mViewer->getCamera()));
if (Settings::Manager::getBool("view over shoulder", "Camera"))
mViewOverShoulderController.reset(new ViewOverShoulderController(mCamera.get()));
mScreenshotManager.reset(new ScreenshotManager(viewer, mRootNode, sceneRoot, mResourceSystem, mWater.get())); mScreenshotManager.reset(new ScreenshotManager(viewer, mRootNode, sceneRoot, mResourceSystem, mWater.get()));
@ -826,8 +823,6 @@ namespace MWRender
updateNavMesh(); updateNavMesh();
updateRecastMesh(); updateRecastMesh();
if (mViewOverShoulderController)
mViewOverShoulderController->update();
mCamera->update(dt, paused); mCamera->update(dt, paused);
bool isUnderwater = mWater->isUnderwater(mCamera->getPosition()); bool isUnderwater = mWater->isUnderwater(mCamera->getPosition());

@ -79,7 +79,6 @@ namespace MWRender
class NpcAnimation; class NpcAnimation;
class Pathgrid; class Pathgrid;
class Camera; class Camera;
class ViewOverShoulderController;
class Water; class Water;
class TerrainStorage; class TerrainStorage;
class LandManager; class LandManager;
@ -283,7 +282,6 @@ namespace MWRender
osg::ref_ptr<NpcAnimation> mPlayerAnimation; osg::ref_ptr<NpcAnimation> mPlayerAnimation;
osg::ref_ptr<SceneUtil::PositionAttitudeTransform> mPlayerNode; osg::ref_ptr<SceneUtil::PositionAttitudeTransform> mPlayerNode;
std::unique_ptr<Camera> mCamera; std::unique_ptr<Camera> mCamera;
std::unique_ptr<ViewOverShoulderController> mViewOverShoulderController;
osg::ref_ptr<StateUpdater> mStateUpdater; osg::ref_ptr<StateUpdater> mStateUpdater;
osg::ref_ptr<SharedUniformStateUpdater> mSharedUniformStateUpdater; osg::ref_ptr<SharedUniformStateUpdater> mSharedUniformStateUpdater;

@ -1,110 +0,0 @@
#include "viewovershoulder.hpp"
#include <osg/Quat>
#include <components/settings/settings.hpp>
#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)
{
osg::Vec2f offset = Settings::Manager::getVector2("view over shoulder offset", "Camera");
mOverShoulderHorizontalOffset = std::abs(offset.x());
mOverShoulderVerticalOffset = offset.y();
mDefaultShoulderIsRight = offset.x() >= 0;
mCamera->enableDynamicCameraDistance(true);
mCamera->enableCrosshairInThirdPersonMode(true);
mCamera->setFocalPointTargetOffset(offset);
}
void ViewOverShoulderController::update()
{
if (mCamera->getMode() == Camera::Mode::FirstPerson || mCamera->getMode() == Camera::Mode::Static)
return;
Mode oldMode = mMode;
auto ptr = mCamera->getTrackingPtr();
bool combat = ptr.getClass().isActor() && ptr.getClass().getCreatureStats(ptr).getDrawState() != MWMechanics::DrawState_Nothing;
if (combat && !mCamera->isVanityOrPreviewModeEnabled())
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 (mCamera->getMode() == Camera::Mode::Vanity)
// Player doesn't touch controls for a long time. Transition should be very slow.
mCamera->setFocalPointTransitionSpeed(0.2f);
else if ((oldMode == Mode::Combat || mMode == Mode::Combat) && mCamera->getMode() == Camera::Mode::ThirdPerson)
// Transition to/from combat mode and we are not it preview mode. Should be fast.
mCamera->setFocalPointTransitionSpeed(5.f);
else
mCamera->setFocalPointTransitionSpeed(1.f); // Default transition speed.
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()
{
if (mCamera->getMode() != Camera::Mode::ThirdPerson)
return;
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->getThirdPersonBasePosition();
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, 0, 0), limitToSwitchBack + 1);
float rayLeft = world->getDistToNearestRayHit(
playerPos - sideOffset, orient * osg::Vec3d(-1, 0, 0), limitToSwitchBack + 1);
float rayRightForward = world->getDistToNearestRayHit(
playerPos + sideOffset, orient * osg::Vec3d(1, 3, 0), limitToSwitchBack + 1);
float rayLeftForward = world->getDistToNearestRayHit(
playerPos - sideOffset, orient * osg::Vec3d(-1, 3, 0), limitToSwitchBack + 1);
float distRight = std::min(rayRight, rayRightForward);
float distLeft = std::min(rayLeft, rayLeftForward);
if (distLeft < limitToSwitch && distRight > limitToSwitchBack)
mMode = Mode::RightShoulder;
else if (distRight < limitToSwitch && distLeft > limitToSwitchBack)
mMode = Mode::LeftShoulder;
else if (distRight > limitToSwitchBack && distLeft > limitToSwitchBack)
mMode = mDefaultShoulderIsRight ? Mode::RightShoulder : Mode::LeftShoulder;
}
}

@ -1,30 +0,0 @@
#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

@ -2398,11 +2398,6 @@ namespace MWWorld
return mRendering->getCamera()->getMode() == MWRender::Camera::Mode::Preview; return mRendering->getCamera()->getMode() == MWRender::Camera::Mode::Preview;
} }
void World::togglePreviewMode(bool enable)
{
mRendering->getCamera()->togglePreviewMode(enable);
}
bool World::toggleVanityMode(bool enable) bool World::toggleVanityMode(bool enable)
{ {
return mRendering->getCamera()->toggleVanityMode(enable); return mRendering->getCamera()->toggleVanityMode(enable);
@ -2418,10 +2413,7 @@ namespace MWWorld
mRendering->getCamera()->applyDeferredPreviewRotationToPlayer(dt); mRendering->getCamera()->applyDeferredPreviewRotationToPlayer(dt);
} }
void World::allowVanityMode(bool allow) MWRender::Camera* World::getCamera() { return mRendering->getCamera(); }
{
mRendering->getCamera()->allowVanityMode(allow);
}
bool World::vanityRotateCamera(float * rot) bool World::vanityRotateCamera(float * rot)
{ {
@ -2434,11 +2426,6 @@ namespace MWWorld
return true; return true;
} }
void World::adjustCameraDistance(float dist)
{
mRendering->getCamera()->adjustCameraDistance(dist);
}
void World::saveLoaded() void World::saveLoaded()
{ {
mStore.validateDynamic(); mStore.validateDynamic();

@ -529,13 +529,10 @@ namespace MWWorld
bool isFirstPerson() const override; bool isFirstPerson() const override;
bool isPreviewModeEnabled() const override; bool isPreviewModeEnabled() const override;
void togglePreviewMode(bool enable) override;
bool toggleVanityMode(bool enable) override; bool toggleVanityMode(bool enable) override;
void allowVanityMode(bool allow) override; MWRender::Camera* getCamera() override;
bool vanityRotateCamera(float * rot) override; bool vanityRotateCamera(float * rot) override;
void adjustCameraDistance(float dist) override;
void applyDeferredPreviewRotationToPlayer(float dt) override; void applyDeferredPreviewRotationToPlayer(float dt) override;
void disableDeferredPreviewRotation() override; void disableDeferredPreviewRotation() override;

@ -0,0 +1 @@
PLAYER: scripts/omw/camera.lua

@ -0,0 +1,221 @@
local camera = require('openmw.camera')
local input = require('openmw.input')
local settings = require('openmw.settings')
local util = require('openmw.util')
local self = require('openmw.self')
local head_bobbing = require('scripts.omw.head_bobbing')
local third_person = require('scripts.omw.third_person')
local MODE = camera.MODE
local previewIfStandSill = settings._getBoolFromSettingsCfg('Camera', 'preview if stand still')
local showCrosshairInThirdPerson = settings._getBoolFromSettingsCfg('Camera', 'view over shoulder')
local primaryMode
local noModeControl = 0
local noStandingPreview = 0
local noHeadBobbing = 0
local noZoom = 0
function init()
camera.allowCharacterDeferredRotation(settings._getBoolFromSettingsCfg('Camera', 'deferred preview rotation'))
if camera.getMode() == MODE.FirstPerson then
primaryMode = MODE.FirstPerson
else
primaryMode = MODE.ThirdPerson
camera.setMode(MODE.ThirdPerson)
end
end
local smoothedSpeed = 0
local previewTimer = 0
local function updatePOV(dt)
local switchLimit = 0.25
if input.isActionPressed(input.ACTION.TogglePOV) and input.getControlSwitch(input.CONTROL_SWITCH.ViewMode) then
previewTimer = previewTimer + dt
if primaryMode == MODE.ThirdPerson or previewTimer >= switchLimit then
third_person.standingPreview = false
camera.setMode(MODE.Preview)
end
elseif previewTimer > 0 then
if previewTimer <= switchLimit then
if primaryMode == MODE.FirstPerson then
primaryMode = MODE.ThirdPerson
else
primaryMode = MODE.FirstPerson
end
end
camera.setMode(primaryMode)
previewTimer = 0
end
end
local idleTimer = 0
local vanityDelay = settings.getGMST('fVanityDelay')
local function updateVanity(dt)
if input.isIdle() then
idleTimer = idleTimer + dt
else
idleTimer = 0
end
local vanityAllowed = input.getControlSwitch(input.CONTROL_SWITCH.VanityMode)
if vanityAllowed and idleTimer > vanityDelay and camera.getMode() ~= MODE.Vanity then
camera.setMode(MODE.Vanity)
end
if camera.getMode() == MODE.Vanity then
if not vanityAllowed or idleTimer == 0 then
camera.setMode(primaryMode)
else
camera.setYaw(camera.getYaw() + math.rad(3) * dt)
end
end
end
local function updateSmoothedSpeed(dt)
local speed = self:getCurrentSpeed()
speed = speed / (1 + speed / 500)
local maxDelta = 300 * dt
smoothedSpeed = smoothedSpeed + util.clamp(speed - smoothedSpeed, -maxDelta, maxDelta)
end
local minDistance = 30
local maxDistance = 800
local function zoom(delta)
if not input.getControlSwitch(input.CONTROL_SWITCH.ViewMode) or
not input.getControlSwitch(input.CONTROL_SWITCH.Controls) or
camera.getMode() == MODE.Static or noZoom > 0 then
return
end
if camera.getMode() ~= MODE.FirstPerson then
local obstacleDelta = third_person.preferredDistance - camera.getThirdPersonDistance()
if delta > 0 and third_person.baseDistance == minDistance and
(camera.getMode() ~= MODE.Preview or third_person.standingPreview) and noModeControl == 0 then
primaryMode = MODE.FirstPerson
camera.setMode(primaryMode)
elseif delta > 0 or obstacleDelta < -delta then
third_person.baseDistance = util.clamp(third_person.baseDistance - delta - obstacleDelta, minDistance, maxDistance)
end
elseif delta < 0 and noModeControl == 0 then
primaryMode = MODE.ThirdPerson
camera.setMode(primaryMode)
third_person.baseDistance = minDistance
end
end
local function applyControllerZoom(dt)
if camera.getMode() == MODE.Preview then
local triggerLeft = input.getAxisValue(input.CONTROLLER_AXIS.TriggerLeft)
local triggerRight = input.getAxisValue(input.CONTROLLER_AXIS.TriggerRight)
local controllerZoom = (triggerRight - triggerLeft) * 100 * dt
if controllerZoom ~= 0 then
zoom(controllerZoom)
end
end
end
local function updateStandingPreview()
local mode = camera.getMode()
if not previewIfStandSill or noStandingPreview > 0
or mode == MODE.FirstPerson or mode == MODE.Static or mode == MODE.Vanity then
third_person.standingPreview = false
return
end
local standingStill = self:getCurrentSpeed() == 0 and not self:isInWeaponStance() and not self:isInMagicStance()
if standingStill and mode == MODE.ThirdPerson then
third_person.standingPreview = true
camera.setMode(MODE.Preview)
elseif not standingStill and third_person.standingPreview then
third_person.standingPreview = false
camera.setMode(primaryMode)
end
end
local function updateCrosshair()
camera.showCrosshair(
camera.getMode() == MODE.FirstPerson or
(showCrosshairInThirdPerson and (camera.getMode() == MODE.ThirdPerson or third_person.standingPreview)))
end
local function onUpdate(dt)
camera.setExtraPitch(0)
camera.setExtraYaw(0)
camera.setRoll(0)
camera.setFirstPersonOffset(util.vector3(0, 0, 0))
updateSmoothedSpeed(dt)
end
local function onInputUpdate(dt)
local mode = camera.getMode()
if mode == MODE.FirstPerson or mode == MODE.ThirdPerson then
primaryMode = mode
end
if mode ~= MODE.Static then
if not camera.getQueuedMode() or camera.getQueuedMode() == MODE.Preview then
if noModeControl == 0 then
updatePOV(dt)
updateVanity(dt)
end
updateStandingPreview()
end
updateCrosshair()
end
applyControllerZoom(dt)
third_person.update(dt, smoothedSpeed)
if noHeadBobbing == 0 then head_bobbing.update(dt, smoothedSpeed) end
end
return {
interfaceName = 'Camera',
interface = {
version = 0,
getPrimaryMode = function() return primaryMode end,
getBaseThirdPersonDistance = function() return third_person.baseDistance end,
setBaseThirdPersonDistance = function(v) third_person.baseDistance = v end,
getTargetThirdPersonDistance = function() return third_person.preferredDistance end,
isModeControlEnabled = function() return noModeControl == 0 end,
disableModeControl = function() noModeControl = noModeControl + 1 end,
enableModeControl = function() noModeControl = math.max(0, noModeControl - 1) end,
isStandingPreviewEnabled = function() return previewIfStandSill and noStandingPreview == 0 end,
disableStandingPreview = function() noStandingPreview = noStandingPreview + 1 end,
enableStandingPreview = function() noStandingPreview = math.max(0, noStandingPreview - 1) end,
isHeadBobbingEnabled = function() return head_bobbing.enabled and noHeadBobbing == 0 end,
disableHeadBobbing = function() noHeadBobbing = noHeadBobbing + 1 end,
enableHeadBobbing = function() noHeadBobbing = math.max(0, noHeadBobbing - 1) end,
isZoomEnabled = function() return noZoom == 0 end,
disableZoom = function() noZoom = noZoom + 1 end,
enableZoom = function() noZoom = math.max(0, noZoom - 1) end,
isThirdPersonOffsetControlEnabled = function() return third_person.noOffsetControl == 0 end,
disableThirdPersonOffsetControl = function() third_person.noOffsetControl = third_person.noOffsetControl + 1 end,
enableThirdPersonOffsetControl = function() third_person.noOffsetControl = math.max(0, third_person.noOffsetControl - 1) end,
},
engineHandlers = {
onUpdate = onUpdate,
onInputUpdate = onInputUpdate,
onInputAction = function(action)
if action == input.ACTION.ZoomIn then
zoom(10)
elseif action == input.ACTION.ZoomOut then
zoom(-10)
end
end,
onActive = init,
onLoad = function(data)
if data and data.distance then third_person.baseDistance = data.distance end
end,
onSave = function()
return {version = 0, distance = third_person.baseDistance}
end,
},
}

@ -0,0 +1,51 @@
local camera = require('openmw.camera')
local self = require('openmw.self')
local settings = require('openmw.settings')
local util = require('openmw.util')
local doubleStepLength = settings._getFloatFromSettingsCfg('Camera', 'head bobbing step') * 2
local stepHeight = settings._getFloatFromSettingsCfg('Camera', 'head bobbing height')
local maxRoll = math.rad(settings._getFloatFromSettingsCfg('Camera', 'head bobbing roll'))
local effectWeight = 0
local totalMovement = 0
local M = {
enabled = settings._getBoolFromSettingsCfg('Camera', 'head bobbing')
}
-- Trajectory of each step is a scaled arc of 60 degrees.
local halfArc = math.rad(30)
local sampleArc = function(x) return 1 - math.cos(x * halfArc) end
local arcHeight = sampleArc(1)
function M.update(dt, smoothedSpeed)
local speed = self:getCurrentSpeed()
speed = speed / (1 + speed / 500) -- limit bobbing frequency if the speed is very high
totalMovement = totalMovement + speed * dt
if not M.enabled or camera.getMode() ~= camera.MODE.FirstPerson then
effectWeight = 0
return
end
if self:isOnGround() then
effectWeight = math.min(1, effectWeight + dt * 5)
else
effectWeight = math.max(0, effectWeight - dt * 5)
end
local doubleStepState = totalMovement / doubleStepLength
doubleStepState = doubleStepState - math.floor(doubleStepState) -- from 0 to 1 during 2 steps
local stepState = math.abs(doubleStepState * 4 - 2) - 1 -- from -1 to 1 on even steps and from 1 to -1 on odd steps
local effect = sampleArc(stepState) / arcHeight -- range from 0 to 1
-- Smoothly reduce the effect to zero when the player stops
local coef = math.min(smoothedSpeed / 300, 1) * effectWeight
local zOffset = (0.5 - effect) * coef * stepHeight -- range from -stepHeight/2 to stepHeight/2
local roll = ((stepState > 0 and 1) or -1) * effect * coef * maxRoll -- range from -maxRoll to maxRoll
camera.setFirstPersonOffset(camera.getFirstPersonOffset() + util.vector3(0, 0, zOffset))
camera.setRoll(camera.getRoll() + roll)
end
return M

@ -0,0 +1,139 @@
local camera = require('openmw.camera')
local settings = require('openmw.settings')
local util = require('openmw.util')
local self = require('openmw.self')
local nearby = require('openmw.nearby')
local MODE = camera.MODE
local STATE = { RightShoulder = 0, LeftShoulder = 1, Combat = 2, Swimming = 3 }
local M = {
baseDistance = settings._getFloatFromSettingsCfg('Camera', 'third person camera distance'),
preferredDistance = 0,
standingPreview = false,
noOffsetControl = 0,
}
local viewOverShoulder = settings._getBoolFromSettingsCfg('Camera', 'view over shoulder')
local autoSwitchShoulder = settings._getBoolFromSettingsCfg('Camera', 'auto switch shoulder')
local shoulderOffset = settings._getVector2FromSettingsCfg('Camera', 'view over shoulder offset')
local zoomOutWhenMoveCoef = settings._getFloatFromSettingsCfg('Camera', 'zoom out when move coef')
local defaultShoulder = (shoulderOffset.x > 0 and STATE.RightShoulder) or STATE.LeftShoulder
local rightShoulderOffset = util.vector2(math.abs(shoulderOffset.x), shoulderOffset.y)
local leftShoulderOffset = util.vector2(-math.abs(shoulderOffset.x), shoulderOffset.y)
local combatOffset = util.vector2(0, 15)
local state = defaultShoulder
local rayOptions = {collisionType = nearby.COLLISION_TYPE.Default - nearby.COLLISION_TYPE.Actor}
local function ray(from, angle, limit)
local to = from + util.transform.rotateZ(angle) * util.vector3(0, limit, 0)
local res = nearby.castRay(from, to, rayOptions)
if res.hit then
return (res.hitPos - from):length()
else
return limit
end
end
local function trySwitchShoulder()
local limitToSwitch = 120 -- switch to other shoulder if wall is closer than this limit
local limitToSwitchBack = 300 -- switch back to default shoulder if there is no walls at this distance
local pos = camera.getTrackedPosition()
local rayRight = ray(pos, camera.getYaw() + math.rad(90), limitToSwitchBack + 1)
local rayLeft = ray(pos, camera.getYaw() - math.rad(90), limitToSwitchBack + 1)
local rayRightForward = ray(pos, camera.getYaw() + math.rad(30), limitToSwitchBack + 1)
local rayLeftForward = ray(pos, camera.getYaw() - math.rad(30), limitToSwitchBack + 1)
local distRight = math.min(rayRight, rayRightForward)
local distLeft = math.min(rayLeft, rayLeftForward)
if distLeft < limitToSwitch and distRight > limitToSwitchBack then
state = STATE.RightShoulder
elseif distRight < limitToSwitch and distLeft > limitToSwitchBack then
state = STATE.LeftShoulder
elseif distRight > limitToSwitchBack and distLeft > limitToSwitchBack then
state = defaultShoulder
end
end
local function calculateDistance(smoothedSpeed)
local smoothedSpeedSqr = smoothedSpeed * smoothedSpeed
return (M.baseDistance + math.max(camera.getPitch(), 0) * 50
+ smoothedSpeedSqr / (smoothedSpeedSqr + 300*300) * zoomOutWhenMoveCoef)
end
local noThirdPersonLastFrame = true
local function updateState()
local mode = camera.getMode()
local oldState = state
if (self:isInWeaponStance() or self:isInMagicStance()) and mode == MODE.ThirdPerson then
state = STATE.Combat
elseif self:isSwimming() then
state = STATE.Swimming
elseif oldState == STATE.Combat or oldState == STATE.Swimming then
state = defaultShoulder
end
if autoSwitchShoulder and (mode == MODE.ThirdPerson or state ~= oldState or noThirdPersonLastFrame)
and (state == STATE.LeftShoulder or state == STATE.RightShoulder) then
trySwitchShoulder()
end
if oldState ~= state or noThirdPersonLastFrame then
-- State was changed, start focal point transition.
if mode == MODE.Vanity then
-- Player doesn't touch controls for a long time. Transition should be very slow.
camera.setFocalTransitionSpeed(0.2)
elseif (oldState == STATE.Combat or state == STATE.Combat) and
(mode ~= MODE.Preview or M.standingPreview) then
-- Transition to/from combat mode and we are not in preview mode. Should be fast.
camera.setFocalTransitionSpeed(5.0)
else
camera.setFocalTransitionSpeed(1.0) -- Default transition speed.
end
if state == STATE.RightShoulder then
camera.setFocalPreferredOffset(rightShoulderOffset)
elseif state == STATE.LeftShoulder then
camera.setFocalPreferredOffset(leftShoulderOffset)
else
camera.setFocalPreferredOffset(combatOffset)
end
end
end
function M.update(dt, smoothedSpeed)
local mode = camera.getMode()
if mode == MODE.FirstPerson or mode == MODE.Static then
noThirdPersonLastFrame = true
return
end
if not viewOverShoulder then
M.preferredDistance = M.baseDistance
camera.setPreferredThirdPersonDistance(M.baseDistance)
noThirdPersonLastFrame = false
return
end
if M.noOffsetControl == 0 then
updateState()
else
state = nil
end
M.preferredDistance = calculateDistance(smoothedSpeed)
if noThirdPersonLastFrame then -- just switched to third person view
camera.setPreferredThirdPersonDistance(M.preferredDistance)
camera.instantTransition()
noThirdPersonLastFrame = false
else
local maxIncrease = dt * (100 + M.baseDistance)
camera.setPreferredThirdPersonDistance(math.min(
M.preferredDistance, camera.getThirdPersonDistance() + maxIncrease))
end
end
return M

@ -2,6 +2,7 @@
# Modifications should be done on the user openmw.cfg file instead # Modifications should be done on the user openmw.cfg file instead
# (see: https://openmw.readthedocs.io/en/master/reference/modding/paths.html) # (see: https://openmw.readthedocs.io/en/master/reference/modding/paths.html)
content=builtin.omwscripts
data=${MORROWIND_DATA_FILES} data=${MORROWIND_DATA_FILES}
data-local="?userdata?data" data-local="?userdata?data"
resources=${OPENMW_RESOURCE_FILES} resources=${OPENMW_RESOURCE_FILES}

@ -2,6 +2,7 @@
# Modifications should be done on the user openmw.cfg file instead # Modifications should be done on the user openmw.cfg file instead
# (see: https://openmw.readthedocs.io/en/master/reference/modding/paths.html) # (see: https://openmw.readthedocs.io/en/master/reference/modding/paths.html)
content=builtin.omwscripts
data="?global?data" data="?global?data"
data=./data data=./data
data-local="?userdata?data" data-local="?userdata?data"

Loading…
Cancel
Save