diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 516b5e0aa..fe58fbfb6 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -435,6 +435,8 @@ namespace MWBase virtual void changeVanityModeScale(float factor) = 0; virtual bool vanityRotateCamera(float * rot) = 0; virtual void setCameraDistance(float dist, bool adjust = false, bool override = true)=0; + virtual void applyDeferredPreviewRotationToPlayer(float dt) = 0; + virtual void disableDeferredPreviewRotation() = 0; virtual void setupPlayer() = 0; virtual void renderPlayer() = 0; diff --git a/apps/openmw/mwinput/actionmanager.cpp b/apps/openmw/mwinput/actionmanager.cpp index f2af3089d..ec6c5cf7f 100644 --- a/apps/openmw/mwinput/actionmanager.cpp +++ b/apps/openmw/mwinput/actionmanager.cpp @@ -110,23 +110,21 @@ 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 (mPreviewPOVDelay <= 0.5 && - (mPreviewPOVDelay += dt) > 0.5) - { - mPreviewPOVDelay = 1.f; - MWBase::Environment::get().getWorld()->togglePreviewMode(true); - } + if (world->isFirstPerson() ? mPreviewPOVDelay > switchLimit : mPreviewPOVDelay == 0) + world->togglePreviewMode(true); + mPreviewPOVDelay += dt; } else { //disable preview mode - MWBase::Environment::get().getWorld()->togglePreviewMode(false); - if (mPreviewPOVDelay > 0.f && mPreviewPOVDelay <= 0.5) - { - MWBase::Environment::get().getWorld()->togglePOV(); - } + if (mPreviewPOVDelay > 0) + world->togglePreviewMode(false); + if (mPreviewPOVDelay > 0.f && mPreviewPOVDelay <= switchLimit) + world->togglePOV(); mPreviewPOVDelay = 0.f; } } diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 79541fbe4..690183c57 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -9,6 +9,7 @@ #include "../mwbase/windowmanager.hpp" #include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" #include "../mwworld/esmstore.hpp" @@ -101,6 +102,8 @@ namespace MWInput mMouseManager->update(dt); mSensorManager->update(dt); mActionManager->update(dt, controllerMove); + + MWBase::Environment::get().getWorld()->applyDeferredPreviewRotationToPlayer(dt); } void InputManager::setDragDrop(bool dragDrop) diff --git a/apps/openmw/mwinput/mousemanager.cpp b/apps/openmw/mwinput/mousemanager.cpp index 01602df68..7b1a3b34d 100644 --- a/apps/openmw/mwinput/mousemanager.cpp +++ b/apps/openmw/mwinput/mousemanager.cpp @@ -108,6 +108,8 @@ namespace MWInput player.yaw(x); player.pitch(y); } + else if (!input->getControlSwitch("playerlooking")) + MWBase::Environment::get().getWorld()->disableDeferredPreviewRotation(); if (arg.zrel && input->getControlSwitch("playerviewswitch") && input->getControlSwitch("playercontrols")) //Check to make sure you are allowed to zoomout and there is a change { @@ -207,17 +209,20 @@ namespace MWInput return; float rot[3]; - rot[0] = yAxis * dt * 1000.0f * mCameraSensitivity * (mInvertY ? -1 : 1) * mCameraYMultiplier / 256.f; + rot[0] = -yAxis * dt * 1000.0f * mCameraSensitivity * (mInvertY ? -1 : 1) * mCameraYMultiplier / 256.f; rot[1] = 0.0f; - rot[2] = xAxis * dt * 1000.0f * mCameraSensitivity * (mInvertX ? -1 : 1) / 256.f; + rot[2] = -xAxis * dt * 1000.0f * mCameraSensitivity * (mInvertX ? -1 : 1) / 256.f; // Only actually turn player when we're not in vanity mode - if (!MWBase::Environment::get().getWorld()->vanityRotateCamera(rot) && MWBase::Environment::get().getInputManager()->getControlSwitch("playercontrols")) + bool controls = MWBase::Environment::get().getInputManager()->getControlSwitch("playercontrols"); + if (!MWBase::Environment::get().getWorld()->vanityRotateCamera(rot) && controls) { MWWorld::Player& player = MWBase::Environment::get().getWorld()->getPlayer(); - player.yaw(rot[2]); - player.pitch(rot[0]); + player.yaw(-rot[2]); + player.pitch(-rot[0]); } + else if (!controls) + MWBase::Environment::get().getWorld()->disableDeferredPreviewRotation(); MWBase::Environment::get().getInputManager()->resetIdleTime(); } diff --git a/apps/openmw/mwinput/sensormanager.cpp b/apps/openmw/mwinput/sensormanager.cpp index f3c2c2305..3e8e70aef 100644 --- a/apps/openmw/mwinput/sensormanager.cpp +++ b/apps/openmw/mwinput/sensormanager.cpp @@ -249,17 +249,20 @@ namespace MWInput if (!mGuiCursorEnabled) { float rot[3]; - rot[0] = mGyroYSpeed * dt * mGyroVSensitivity * 4 * (mInvertY ? -1 : 1); + rot[0] = -mGyroYSpeed * dt * mGyroVSensitivity * 4 * (mInvertY ? -1 : 1); rot[1] = 0.0f; - rot[2] = mGyroXSpeed * dt * mGyroHSensitivity * 4 * (mInvertX ? -1 : 1); + rot[2] = -mGyroXSpeed * dt * mGyroHSensitivity * 4 * (mInvertX ? -1 : 1); // Only actually turn player when we're not in vanity mode - if (!MWBase::Environment::get().getWorld()->vanityRotateCamera(rot) && MWBase::Environment::get().getInputManager()->getControlSwitch("playerlooking")) + bool playerLooking = MWBase::Environment::get().getInputManager()->getControlSwitch("playerlooking"); + if (!MWBase::Environment::get().getWorld()->vanityRotateCamera(rot) && playerLooking) { MWWorld::Player& player = MWBase::Environment::get().getWorld()->getPlayer(); - player.yaw(rot[2]); - player.pitch(rot[0]); + player.yaw(-rot[2]); + player.pitch(-rot[0]); } + else if (!playerLooking) + MWBase::Environment::get().getWorld()->disableDeferredPreviewRotation(); MWBase::Environment::get().getInputManager()->resetIdleTime(); } diff --git a/apps/openmw/mwrender/camera.cpp b/apps/openmw/mwrender/camera.cpp index d7ec2409e..954b7e54a 100644 --- a/apps/openmw/mwrender/camera.cpp +++ b/apps/openmw/mwrender/camera.cpp @@ -19,6 +19,7 @@ #endif #include "../mwmechanics/drawstate.hpp" +#include "../mwmechanics/movement.hpp" #include "../mwmechanics/npcstats.hpp" #include "npcanimation.hpp" @@ -59,7 +60,10 @@ namespace MWRender mCamera(camera), mAnimation(nullptr), mFirstPersonView(true), - mPreviewMode(false), + mMode(Mode::Normal), + mVanityAllowed(true), + mStandingPreviewAllowed(Settings::Manager::getBool("preview if stand still", "Camera")), + mDeferredRotationAllowed(Settings::Manager::getBool("deferred preview rotation", "Camera")), mNearest(30.f), mFurthest(800.f), mIsNearest(false), @@ -78,20 +82,10 @@ namespace MWRender mSmoothedSpeed(0.f), mZoomOutWhenMoveCoef(Settings::Manager::getFloat("zoom out when move coef", "Camera")), mDynamicCameraDistanceEnabled(false), - mShowCrosshairInThirdPersonMode(false) + mShowCrosshairInThirdPersonMode(false), + mDeferredRotation(osg::Vec3f()), + mDeferredRotationDisabled(false) { - mVanity.enabled = false; - mVanity.allowed = true; - - mPreviewCam.roll = 0.f; - mPreviewCam.pitch = 0.f; - mPreviewCam.yaw = 0.f; - mPreviewCam.offset = 400.f; - mMainCam.roll = 0.f; - mMainCam.pitch = 0.f; - mMainCam.yaw = 0.f; - mMainCam.offset = 400.f; - mCameraDistance = mBaseCameraDistance; mUpdateCallback = new UpdateRenderCameraCallback(this); @@ -103,19 +97,14 @@ namespace MWRender mCamera->removeUpdateCallback(mUpdateCallback); } - MWWorld::Ptr Camera::getTrackingPtr() const - { - return mTrackingPtr; - } - osg::Vec3d Camera::getFocalPoint() const { - const osg::Node* trackNode = mTrackingNode; - if (!trackNode) + if (!mTrackingNode) { return osg::Vec3d(); } - osg::NodePathList nodepaths = trackNode->getParentalNodePaths(); + + osg::NodePathList nodepaths = mTrackingNode->getParentalNodePaths(); if (nodepaths.empty()) return osg::Vec3d(); osg::Matrix worldMat = osg::computeLocalToWorld(nodepaths[0]); @@ -147,12 +136,9 @@ namespace MWRender osg::Vec3d Camera::getFocalPointOffset() const { osg::Vec3d offset(0, 0, 10.f); - if (!mPreviewMode && !mVanity.enabled) - { - offset.x() += mFocalPointCurrentOffset.x() * cos(getYaw()); - offset.y() += mFocalPointCurrentOffset.x() * sin(getYaw()); - offset.z() += mFocalPointCurrentOffset.y(); - } + offset.x() += mFocalPointCurrentOffset.x() * cos(getYaw()); + offset.y() += mFocalPointCurrentOffset.x() * sin(getYaw()); + offset.z() += mFocalPointCurrentOffset.y(); return offset; } @@ -170,9 +156,6 @@ namespace MWRender void Camera::updateCamera(osg::Camera *cam) { - if (mTrackingPtr.isEmpty()) - return; - osg::Quat orient = osg::Quat(getPitch(), osg::Vec3d(1,0,0)) * osg::Quat(getRoll(), osg::Vec3d(0, 1, 0)) * osg::Quat(getYaw(), osg::Vec3d(0,0,1)); osg::Vec3d focal, position; getPosition(focal, position); @@ -183,7 +166,6 @@ namespace MWRender { position += inputManager->headOffset(); } -#else #endif osg::Vec3d forward = orient * osg::Vec3d(0,1,0); @@ -213,11 +195,6 @@ namespace MWRender setRoll(roll); } - void Camera::attachTo(const MWWorld::Ptr &ptr) - { - mTrackingPtr = ptr; - } - void Camera::update(float duration, bool paused) { if (mAnimation->upperBodyReady()) @@ -230,7 +207,6 @@ namespace MWRender } if (mViewModeToggleQueued) { - togglePreviewMode(false); toggleViewMode(); mViewModeToggleQueued = false; @@ -242,13 +218,11 @@ namespace MWRender // only show the crosshair in game mode MWBase::WindowManager *wm = MWBase::Environment::get().getWindowManager(); - wm->showCrosshair(!wm->isGuiMode() && !mVanity.enabled && !mPreviewMode + wm->showCrosshair(!wm->isGuiMode() && mMode != Mode::Preview && mMode != Mode::Vanity && (mFirstPersonView || mShowCrosshairInThirdPersonMode)); - if(mVanity.enabled) - { + if(mMode == Mode::Vanity) rotateCamera(0.f, 0.f, osg::DegreesToRadians(3.f * duration), true); - } updateFocalPointOffset(duration); @@ -258,6 +232,24 @@ namespace MWRender mSmoothedSpeed += osg::clampBetween(speed - mSmoothedSpeed, -maxDelta, maxDelta); mMaxNextCameraDistance = mCameraDistance + duration * (100.f + mBaseCameraDistance); + updateStandingPreviewMode(); + } + + void Camera::updateStandingPreviewMode() + { + if (!mStandingPreviewAllowed) + return; + float speed = mTrackingPtr.getClass().getSpeed(mTrackingPtr); + bool combat = mTrackingPtr.getClass().isActor() && + mTrackingPtr.getClass().getCreatureStats(mTrackingPtr).getDrawState() != MWMechanics::DrawState_Nothing; + bool standingStill = speed == 0 && !combat && !mFirstPersonView; + if (!standingStill && mMode == Mode::StandingPreview) + { + mMode = Mode::Normal; + calculateDeferredRotation(); + } + else if (standingStill && mMode == Mode::Normal) + mMode = Mode::StandingPreview; } void Camera::setFocalPointTargetOffset(osg::Vec2d v) @@ -324,14 +316,19 @@ namespace MWRender mTrackingPtr.getClass().getCreatureStats(mTrackingPtr).setSideMovementAngle(0); mFirstPersonView = !mFirstPersonView; + updateStandingPreviewMode(); + instantTransition(); processViewChange(); } - + void Camera::allowVanityMode(bool allow) { - if (!allow && mVanity.enabled) + if (!allow && mMode == Mode::Vanity) + { + disableDeferredPreviewRotation(); toggleVanityMode(false); - mVanity.allowed = allow; + } + mVanityAllowed = allow; } bool Camera::toggleVanityMode(bool enable) @@ -349,26 +346,18 @@ namespace MWRender return false; } - if(!mVanity.allowed && enable) + if (!mVanityAllowed && enable) return false; - if(mVanity.enabled == enable) + if ((mMode == Mode::Vanity) == enable) return true; - mVanity.enabled = enable; + mMode = enable ? Mode::Vanity : Mode::Normal; + if (!mDeferredRotationAllowed) + disableDeferredPreviewRotation(); + if (!enable) + calculateDeferredRotation(); processViewChange(); - - float offset = mPreviewCam.offset; - - if (mVanity.enabled) { - setPitch(osg::DegreesToRadians(-30.f)); - mMainCam.offset = mCameraDistance; - } else { - offset = mMainCam.offset; - } - - mCameraDistance = offset; - return true; } @@ -377,22 +366,21 @@ namespace MWRender if (mFirstPersonView && !mAnimation->upperBodyReady()) return; - if(mPreviewMode == enable) + if((mMode == Mode::Preview) == enable) return; - mPreviewMode = enable; - processViewChange(); - - float offset = mCameraDistance; - if (mPreviewMode) { - mMainCam.offset = offset; - offset = mPreviewCam.offset; - } else { - mPreviewCam.offset = offset; - offset = mMainCam.offset; + mMode = enable ? Mode::Preview : Mode::Normal; + if (mMode == Mode::Normal) + updateStandingPreviewMode(); + else if (mFirstPersonView) + instantTransition(); + if (mMode == Mode::Normal) + { + if (!mDeferredRotationAllowed) + disableDeferredPreviewRotation(); + calculateDeferredRotation(); } - - mCameraDistance = offset; + processViewChange(); } void Camera::setSneakOffset(float offset) @@ -400,13 +388,6 @@ namespace MWRender mAnimation->setFirstPersonOffset(osg::Vec3f(0,0,-offset)); } - float Camera::getYaw() const - { - if(mVanity.enabled || mPreviewMode) - return mPreviewCam.yaw; - return mMainCam.yaw; - } - void Camera::setYaw(float angle) { if (angle > osg::PI) { @@ -414,18 +395,7 @@ namespace MWRender } else if (angle < -osg::PI) { angle += osg::PI*2; } - if (mVanity.enabled || mPreviewMode) { - mPreviewCam.yaw = angle; - } else { - mMainCam.yaw = angle; - } - } - - float Camera::getRoll() - { - if (mVanity.enabled || mPreviewMode) - return mPreviewCam.roll; - return mMainCam.roll; + mYaw = angle; } void Camera::setRoll(float angle) @@ -437,46 +407,19 @@ namespace MWRender else if (angle < -osg::PI) { angle += osg::PI * 2; } - if (mVanity.enabled || mPreviewMode) { - mPreviewCam.roll = angle; - } - else { - mMainCam.roll = angle; - } + mRoll = angle; #else // It seems OpenMW saves roll data, causing the camera to get tilted // when loading a VR save in non-VR. - mMainCam.roll = mPreviewCam.roll = 0.f; + mRoll = 0.f; #endif } - float Camera::getPitch() const - { - if (mVanity.enabled || mPreviewMode) { - return mPreviewCam.pitch; - } - return mMainCam.pitch; - } - void Camera::setPitch(float angle) { const float epsilon = 0.000001f; float limit = osg::PI_2 - epsilon; - if(mPreviewMode) - limit /= 2; - -#ifndef USE_OPENXR - if(angle > limit) - angle = limit; - else if(angle < -limit) - angle = -limit; -#endif - - if (mVanity.enabled || mPreviewMode) { - mPreviewCam.pitch = angle; - } else { - mMainCam.pitch = angle; - } + mPitch = osg::clampBetween(angle, -limit, limit); } float Camera::getCameraDistance() const @@ -488,50 +431,25 @@ namespace MWRender void Camera::updateBaseCameraDistance(float dist, bool adjust) { - if(mFirstPersonView && !mPreviewMode && !mVanity.enabled) + if (isFirstPerson()) return; - mIsNearest = false; - if (adjust) - { - if (mVanity.enabled || mPreviewMode) - dist += mCameraDistance; - else - dist += std::min(mCameraDistance - getCameraDistanceCorrection(), mBaseCameraDistance); - } + dist += std::min(mCameraDistance - getCameraDistanceCorrection(), mBaseCameraDistance); - - if (dist >= mFurthest) - dist = mFurthest; - else if (dist <= mNearest) - { - dist = mNearest; - mIsNearest = true; - } - - if (mVanity.enabled || mPreviewMode) - mPreviewCam.offset = dist; - else if (!mFirstPersonView) - { - mBaseCameraDistance = dist; - Settings::Manager::setFloat("third person camera distance", "Camera", dist); - } + mIsNearest = dist <= mNearest; + mBaseCameraDistance = osg::clampBetween(dist, mNearest, mFurthest); + Settings::Manager::setFloat("third person camera distance", "Camera", mBaseCameraDistance); setCameraDistance(); } void Camera::setCameraDistance(float dist, bool adjust) { - if(mFirstPersonView && !mPreviewMode && !mVanity.enabled) + if (isFirstPerson()) return; - - if (adjust) dist += mCameraDistance; - - if (dist >= mFurthest) - dist = mFurthest; - else if (dist < 10.f) - dist = 10.f; - mCameraDistance = dist; + if (adjust) + dist += mCameraDistance; + mCameraDistance = osg::clampBetween(dist, 10.f, mFurthest); } float Camera::getCameraDistanceCorrection() const @@ -549,21 +467,17 @@ namespace MWRender void Camera::setCameraDistance() { - if (mVanity.enabled || mPreviewMode) - mCameraDistance = mPreviewCam.offset; - else if (!mFirstPersonView) - { - mCameraDistance = mBaseCameraDistance + getCameraDistanceCorrection(); - if (mDynamicCameraDistanceEnabled) - mCameraDistance = std::min(mCameraDistance, mMaxNextCameraDistance); - } mFocalPointAdjustment = osg::Vec3d(); + if (isFirstPerson()) + return; + mCameraDistance = mBaseCameraDistance + getCameraDistanceCorrection(); + if (mDynamicCameraDistanceEnabled) + mCameraDistance = std::min(mCameraDistance, mMaxNextCameraDistance); } void Camera::setAnimation(NpcAnimation *anim) { mAnimation = anim; - processViewChange(); } @@ -602,13 +516,74 @@ namespace MWRender #endif } - bool Camera::isVanityOrPreviewModeEnabled() const + void Camera::applyDeferredPreviewRotationToPlayer(float dt) + { + if (isVanityOrPreviewModeEnabled() || mTrackingPtr.isEmpty()) + return; + + osg::Vec3f rot = mDeferredRotation; + float delta = rot.normalize(); + delta = std::min(delta, (delta + 1.f) * 3 * dt); + rot *= delta; + mDeferredRotation -= rot; + + if (mDeferredRotationDisabled) + { + mDeferredRotationDisabled = delta > 0.0001; + rotateCameraToTrackingPtr(); + return; + } + + auto& movement = mTrackingPtr.getClass().getMovementSettings(mTrackingPtr); + movement.mRotation[0] += rot.x(); + movement.mRotation[1] += rot.y(); + movement.mRotation[2] += rot.z(); + if (std::abs(mDeferredRotation.z()) > 0.0001) + { + float s = std::sin(mDeferredRotation.z()); + float c = std::cos(mDeferredRotation.z()); + float x = movement.mPosition[0]; + float y = movement.mPosition[1]; + movement.mPosition[0] = x * c + y * s; + movement.mPosition[1] = x * -s + y * c; + } + } + + void Camera::rotateCameraToTrackingPtr() + { + setPitch(-mTrackingPtr.getRefData().getPosition().rot[0] - mDeferredRotation.x()); + setYaw(-mTrackingPtr.getRefData().getPosition().rot[2] - mDeferredRotation.z()); + } + + void Camera::instantTransition() { - return mPreviewMode || mVanity.enabled; + mSkipFocalPointTransition = true; + mDeferredRotationDisabled = false; + mDeferredRotation = osg::Vec3f(); + rotateCameraToTrackingPtr(); } - bool Camera::isNearest() const + void Camera::calculateDeferredRotation() { - return mIsNearest; + MWWorld::Ptr ptr = mTrackingPtr; + if (isVanityOrPreviewModeEnabled() || ptr.isEmpty()) + return; + if (mFirstPersonView) + { + instantTransition(); + return; + } + + mDeferredRotation.x() = -ptr.getRefData().getPosition().rot[0] - mPitch; + mDeferredRotation.z() = -ptr.getRefData().getPosition().rot[2] - mYaw; + if (mDeferredRotation.x() > osg::PI) + mDeferredRotation.x() -= 2 * osg::PI; + if (mDeferredRotation.x() < -osg::PI) + mDeferredRotation.x() += 2 * osg::PI; + if (mDeferredRotation.z() > osg::PI) + mDeferredRotation.z() -= 2 * osg::PI; + if (mDeferredRotation.z() < -osg::PI) + mDeferredRotation.z() += 2 * osg::PI; } + } diff --git a/apps/openmw/mwrender/camera.hpp b/apps/openmw/mwrender/camera.hpp index 83ecea7b1..97ac8f3ae 100644 --- a/apps/openmw/mwrender/camera.hpp +++ b/apps/openmw/mwrender/camera.hpp @@ -23,11 +23,10 @@ namespace MWRender /// \brief Camera control class Camera { - private: - struct CamData { - float roll, pitch, yaw, offset; - }; + public: + enum class Mode { Normal, Vanity, Preview, StandingPreview }; + private: MWWorld::Ptr mTrackingPtr; osg::ref_ptr mTrackingNode; float mHeightScale; @@ -37,17 +36,17 @@ namespace MWRender NpcAnimation *mAnimation; bool mFirstPersonView; - bool mPreviewMode; + Mode mMode; + bool mVanityAllowed; + bool mStandingPreviewAllowed; + bool mDeferredRotationAllowed; + float mNearest; float mFurthest; bool mIsNearest; - struct { - bool enabled, allowed; - } mVanity; - float mHeight, mBaseCameraDistance; - CamData mMainCam, mPreviewCam; + float mPitch, mYaw, mRoll; bool mVanityToggleQueued; bool mVanityToggleQueuedValue; @@ -78,15 +77,23 @@ namespace MWRender osg::ref_ptr mUpdateCallback; + // Used to rotate player to the direction of view after exiting preview or vanity mode. + osg::Vec3f mDeferredRotation; + bool mDeferredRotationDisabled; + void calculateDeferredRotation(); + void updateStandingPreviewMode(); + public: Camera(osg::Camera* camera); ~Camera(); - MWWorld::Ptr getTrackingPtr() const; + /// 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(osg::Vec2d v); - void skipFocalPointTransition() { mSkipFocalPointTransition = true; } + void instantTransition(); void enableDynamicCameraDistance(bool v) { mDynamicCameraDistanceEnabled = v; } void enableCrosshairInThirdPersonMode(bool v) { mShowCrosshairInThirdPersonMode = v; } @@ -102,18 +109,16 @@ namespace MWRender /// Set where the camera is looking at. Uses Morrowind (euler) angles /// \param rot Rotation angles in radians void rotateCamera(float pitch, float roll, float yaw, bool adjust); + void rotateCameraToTrackingPtr(); - float getYaw() const; + float getYaw() const { return mYaw; } void setYaw(float angle); - float getRoll(); - void setRoll(float angle); - - float getPitch() const; + float getPitch() const { return mPitch; } void setPitch(float angle); - /// Attach camera to object - void attachTo(const MWWorld::Ptr &); + float getRoll() { return mRoll; } + void setRoll(float angle); /// @param Force view mode switch, even if currently not allowed by the animation. void toggleViewMode(bool force=false); @@ -124,11 +129,13 @@ namespace MWRender /// @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); - bool isFirstPerson() const - { return !(mVanity.enabled || mPreviewMode || !mFirstPersonView); } + bool isFirstPerson() const { return mFirstPersonView && mMode == Mode::Normal; } void processViewChange(); @@ -159,9 +166,10 @@ namespace MWRender /// Stores focal and camera world positions in passed arguments void getPosition(osg::Vec3d &focal, osg::Vec3d &camera) const; - bool isVanityOrPreviewModeEnabled() const; + bool isVanityOrPreviewModeEnabled() const { return mMode != Mode::Normal; } + Mode getMode() const { return mMode; } - bool isNearest() const; + bool isNearest() const { return mIsNearest; } }; } diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index f2a024997..7c868b956 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -660,7 +660,7 @@ namespace MWRender if(ptr == mCamera->getTrackingPtr() && !mCamera->isVanityOrPreviewModeEnabled()) { - mCamera->rotateCamera(-ptr.getRefData().getPosition().rot[0], -ptr.getRefData().getPosition().rot[1], -ptr.getRefData().getPosition().rot[2], false); + mCamera->rotateCameraToTrackingPtr(); } ptr.getRefData().getBaseNode()->setAttitude(rot); diff --git a/apps/openmw/mwrender/viewovershoulder.cpp b/apps/openmw/mwrender/viewovershoulder.cpp index 5d7ec7117..4d708afe0 100644 --- a/apps/openmw/mwrender/viewovershoulder.cpp +++ b/apps/openmw/mwrender/viewovershoulder.cpp @@ -33,12 +33,13 @@ namespace MWRender void ViewOverShoulderController::update() { - if (mCamera->isVanityOrPreviewModeEnabled() || mCamera->isFirstPerson()) + if (mCamera->isFirstPerson()) return; Mode oldMode = mMode; auto ptr = mCamera->getTrackingPtr(); - if (ptr.getClass().isActor() && ptr.getClass().getCreatureStats(ptr).getDrawState() != MWMechanics::DrawState_Nothing) + 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; @@ -46,12 +47,18 @@ namespace MWRender 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) + 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::Normal) + // Transition to/from combat mode and we are not it preview mode. Should be fast. mCamera->setFocalPointTransitionSpeed(5.f); else - mCamera->setFocalPointTransitionSpeed(1.f); + mCamera->setFocalPointTransitionSpeed(1.f); // Default transition speed. switch (mMode) { @@ -70,6 +77,9 @@ namespace MWRender void ViewOverShoulderController::trySwitchShoulder() { + if (mCamera->getMode() != Camera::Mode::Normal) + 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 @@ -93,4 +103,4 @@ namespace MWRender mMode = mDefaultShoulderIsRight ? Mode::RightShoulder : Mode::LeftShoulder; } -} \ No newline at end of file +} diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index e45972bd0..68d1507ce 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -954,7 +954,7 @@ namespace MWWorld removeContainerScripts(getPlayerPtr()); mWorldScene->changeToInteriorCell(cellName, position, adjustPlayerPos, changeEvent); addContainerScripts(getPlayerPtr(), getPlayerPtr().getCell()); - mRendering->getCamera()->skipFocalPointTransition(); + mRendering->getCamera()->instantTransition(); #ifdef USE_OPENXR auto* xrInput = MWVR::Environment::get().getInputManager(); @@ -976,7 +976,7 @@ namespace MWWorld removeContainerScripts(getPlayerPtr()); mWorldScene->changeToExteriorCell(position, adjustPlayerPos, changeEvent); addContainerScripts(getPlayerPtr(), getPlayerPtr().getCell()); - mRendering->getCamera()->skipFocalPointTransition(); + mRendering->getCamera()->instantTransition(); #ifdef USE_OPENXR auto* xrInput = MWVR::Environment::get().getInputManager(); @@ -2446,6 +2446,16 @@ namespace MWWorld return mRendering->toggleVanityMode(enable); } + void World::disableDeferredPreviewRotation() + { + mRendering->getCamera()->disableDeferredPreviewRotation(); + } + + void World::applyDeferredPreviewRotationToPlayer(float dt) + { + mRendering->getCamera()->applyDeferredPreviewRotationToPlayer(dt); + } + void World::allowVanityMode(bool allow) { mRendering->allowVanityMode(allow); diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 4eca8b3e7..7a451fcd8 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -540,6 +540,9 @@ namespace MWWorld bool vanityRotateCamera(float * rot) override; void setCameraDistance(float dist, bool adjust = false, bool override = true) override; + void applyDeferredPreviewRotationToPlayer(float dt) override; + void disableDeferredPreviewRotation() override; + void setupPlayer() override; void renderPlayer() override; diff --git a/docs/source/reference/modding/settings/camera.rst b/docs/source/reference/modding/settings/camera.rst index be636cef4..18b6754a7 100644 --- a/docs/source/reference/modding/settings/camera.rst +++ b/docs/source/reference/modding/settings/camera.rst @@ -174,3 +174,27 @@ Slightly pulls camera away (or closer in case of negative value) when the charac This setting can only be configured by editing the settings configuration file. +preview if stand still +---------------------- + +:Type: boolean +:Range: True/False +:Default: False + +If enabled then the character rotation is not synchonized with the camera rotation while the character doesn't move and not in combat mode. + +This setting can only be configured by editing the settings configuration file. + +deferred preview rotation +------------------------- + +:Type: boolean +:Range: True/False +:Default: True + +Makes difference only in third person mode. +If enabled then the character smoothly rotates to the view direction after exiting preview or vanity mode. +If disabled then the camera rotates rather than the character. + +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 4f05f2192..a4b253130 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -48,6 +48,12 @@ 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 +# Automatically enable preview mode when player doesn't move. +preview if stand still = false + +# Rotate the character to the view direction after exiting preview mode. +deferred preview rotation = true + [Cells] # Preload cells in a background thread. All settings starting with 'preload' have no effect unless this is enabled.