mirror of
https://github.com/TES3MP/openmw-tes3mp.git
synced 2025-01-15 21:19:57 +00:00
f90a049702
Refactoring related to "smooth movement" See merge request OpenMW/openmw!285 (cherry picked from commit 6eaf0a389d5aed3b74ab1a7cf89574612f964bdf) e847b4c8 Split getSpeed() to getMaxSpeed() and getCurrentSpeed() a96c46bc Refactor calculation of movement.mSpeedFactor 03ee9090 Use getMaxSpeed instead of getCurrentSpeed where it makes sense. a178af5c Create helper functions `normalizeAngle` and `rotateVec2f`
541 lines
17 KiB
C++
541 lines
17 KiB
C++
#include "camera.hpp"
|
|
|
|
#include <osg/Camera>
|
|
|
|
#include <components/misc/mathutil.hpp>
|
|
#include <components/sceneutil/positionattitudetransform.hpp>
|
|
#include <components/settings/settings.hpp>
|
|
|
|
#include "../mwbase/environment.hpp"
|
|
#include "../mwbase/windowmanager.hpp"
|
|
#include "../mwbase/world.hpp"
|
|
|
|
#include "../mwworld/class.hpp"
|
|
#include "../mwworld/ptr.hpp"
|
|
#include "../mwworld/refdata.hpp"
|
|
|
|
#include "../mwmechanics/drawstate.hpp"
|
|
#include "../mwmechanics/movement.hpp"
|
|
#include "../mwmechanics/npcstats.hpp"
|
|
|
|
#include "../mwphysics/raycasting.hpp"
|
|
|
|
#include "npcanimation.hpp"
|
|
|
|
namespace
|
|
{
|
|
|
|
class UpdateRenderCameraCallback : public osg::NodeCallback
|
|
{
|
|
public:
|
|
UpdateRenderCameraCallback(MWRender::Camera* cam)
|
|
: mCamera(cam)
|
|
{
|
|
}
|
|
|
|
virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
|
|
{
|
|
osg::Camera* cam = static_cast<osg::Camera*>(node);
|
|
|
|
// traverse first to update animations, in case the camera is attached to an animated node
|
|
traverse(node, nv);
|
|
|
|
mCamera->updateCamera(cam);
|
|
}
|
|
|
|
private:
|
|
MWRender::Camera* mCamera;
|
|
};
|
|
|
|
}
|
|
|
|
namespace MWRender
|
|
{
|
|
|
|
Camera::Camera (osg::Camera* camera)
|
|
: mHeightScale(1.f),
|
|
mCamera(camera),
|
|
mAnimation(nullptr),
|
|
mFirstPersonView(true),
|
|
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),
|
|
mHeight(124.f),
|
|
mBaseCameraDistance(Settings::Manager::getFloat("third person camera distance", "Camera")),
|
|
mVanityToggleQueued(false),
|
|
mVanityToggleQueuedValue(false),
|
|
mViewModeToggleQueued(false),
|
|
mCameraDistance(0.f),
|
|
mMaxNextCameraDistance(800.f),
|
|
mFocalPointCurrentOffset(osg::Vec2d()),
|
|
mFocalPointTargetOffset(osg::Vec2d()),
|
|
mFocalPointTransitionSpeedCoef(1.f),
|
|
mSkipFocalPointTransition(true),
|
|
mPreviousTransitionInfluence(0.f),
|
|
mSmoothedSpeed(0.f),
|
|
mZoomOutWhenMoveCoef(Settings::Manager::getFloat("zoom out when move coef", "Camera")),
|
|
mDynamicCameraDistanceEnabled(false),
|
|
mShowCrosshairInThirdPersonMode(false),
|
|
mDeferredRotation(osg::Vec3f()),
|
|
mDeferredRotationDisabled(false)
|
|
{
|
|
mCameraDistance = mBaseCameraDistance;
|
|
|
|
mUpdateCallback = new UpdateRenderCameraCallback(this);
|
|
mCamera->addUpdateCallback(mUpdateCallback);
|
|
}
|
|
|
|
Camera::~Camera()
|
|
{
|
|
mCamera->removeUpdateCallback(mUpdateCallback);
|
|
}
|
|
|
|
osg::Vec3d Camera::getFocalPoint() const
|
|
{
|
|
if (!mTrackingNode)
|
|
return osg::Vec3d();
|
|
osg::NodePathList nodepaths = mTrackingNode->getParentalNodePaths();
|
|
if (nodepaths.empty())
|
|
return osg::Vec3d();
|
|
osg::Matrix worldMat = osg::computeLocalToWorld(nodepaths[0]);
|
|
|
|
osg::Vec3d position = worldMat.getTrans();
|
|
if (!isFirstPerson())
|
|
{
|
|
position.z() += mHeight * mHeightScale;
|
|
|
|
// We subtract 10.f here and add it within focalPointOffset in order to avoid camera clipping through ceiling.
|
|
// Needed because character's head can be a bit higher than collision area.
|
|
position.z() -= 10.f;
|
|
|
|
position += getFocalPointOffset() + mFocalPointAdjustment;
|
|
}
|
|
return position;
|
|
}
|
|
|
|
osg::Vec3d Camera::getFocalPointOffset() const
|
|
{
|
|
osg::Vec3d offset(0, 0, 10.f);
|
|
offset.x() += mFocalPointCurrentOffset.x() * cos(getYaw());
|
|
offset.y() += mFocalPointCurrentOffset.x() * sin(getYaw());
|
|
offset.z() += mFocalPointCurrentOffset.y();
|
|
return offset;
|
|
}
|
|
|
|
void Camera::getPosition(osg::Vec3d &focal, osg::Vec3d &camera) const
|
|
{
|
|
focal = getFocalPoint();
|
|
osg::Vec3d offset(0,0,0);
|
|
if (!isFirstPerson())
|
|
{
|
|
osg::Quat orient = osg::Quat(getPitch(), osg::Vec3d(1,0,0)) * osg::Quat(getYaw(), osg::Vec3d(0,0,1));
|
|
offset = orient * osg::Vec3d(0.f, -mCameraDistance, 0.f);
|
|
}
|
|
camera = focal + offset;
|
|
}
|
|
|
|
void Camera::updateCamera(osg::Camera *cam)
|
|
{
|
|
osg::Vec3d focal, position;
|
|
getPosition(focal, position);
|
|
|
|
osg::Quat orient = osg::Quat(getPitch(), osg::Vec3d(1,0,0)) * osg::Quat(getYaw(), osg::Vec3d(0,0,1));
|
|
osg::Vec3d forward = orient * osg::Vec3d(0,1,0);
|
|
osg::Vec3d up = orient * osg::Vec3d(0,0,1);
|
|
|
|
cam->setViewMatrixAsLookAt(position, position + forward, up);
|
|
}
|
|
|
|
void Camera::reset()
|
|
{
|
|
togglePreviewMode(false);
|
|
toggleVanityMode(false);
|
|
if (!mFirstPersonView)
|
|
toggleViewMode();
|
|
}
|
|
|
|
void Camera::rotateCamera(float pitch, float yaw, bool adjust)
|
|
{
|
|
if (adjust)
|
|
{
|
|
pitch += getPitch();
|
|
yaw += getYaw();
|
|
}
|
|
setYaw(yaw);
|
|
setPitch(pitch);
|
|
}
|
|
|
|
void Camera::update(float duration, bool paused)
|
|
{
|
|
if (mAnimation->upperBodyReady())
|
|
{
|
|
// Now process the view changes we queued earlier
|
|
if (mVanityToggleQueued)
|
|
{
|
|
toggleVanityMode(mVanityToggleQueuedValue);
|
|
mVanityToggleQueued = false;
|
|
}
|
|
if (mViewModeToggleQueued)
|
|
{
|
|
togglePreviewMode(false);
|
|
toggleViewMode();
|
|
mViewModeToggleQueued = false;
|
|
}
|
|
}
|
|
|
|
if (paused)
|
|
return;
|
|
|
|
// only show the crosshair in game mode
|
|
MWBase::WindowManager *wm = MWBase::Environment::get().getWindowManager();
|
|
wm->showCrosshair(!wm->isGuiMode() && mMode != Mode::Preview && mMode != Mode::Vanity
|
|
&& (mFirstPersonView || mShowCrosshairInThirdPersonMode));
|
|
|
|
if(mMode == Mode::Vanity)
|
|
rotateCamera(0.f, osg::DegreesToRadians(3.f * duration), true);
|
|
|
|
updateFocalPointOffset(duration);
|
|
updatePosition();
|
|
|
|
float speed = mTrackingPtr.getClass().getCurrentSpeed(mTrackingPtr);
|
|
speed /= (1.f + speed / 500.f);
|
|
float maxDelta = 300.f * duration;
|
|
mSmoothedSpeed += osg::clampBetween(speed - mSmoothedSpeed, -maxDelta, maxDelta);
|
|
|
|
mMaxNextCameraDistance = mCameraDistance + duration * (100.f + mBaseCameraDistance);
|
|
updateStandingPreviewMode();
|
|
}
|
|
|
|
void Camera::updatePosition()
|
|
{
|
|
mFocalPointAdjustment = osg::Vec3d();
|
|
if (isFirstPerson())
|
|
return;
|
|
|
|
const float cameraObstacleLimit = 5.0f;
|
|
const float focalObstacleLimit = 10.f;
|
|
|
|
const auto* rayCasting = MWBase::Environment::get().getWorld()->getRayCasting();
|
|
|
|
// Adjust focal point to prevent clipping.
|
|
osg::Vec3d focal = getFocalPoint();
|
|
osg::Vec3d focalOffset = getFocalPointOffset();
|
|
float offsetLen = focalOffset.length();
|
|
if (offsetLen > 0)
|
|
{
|
|
MWPhysics::RayCastingResult result = rayCasting->castSphere(focal - focalOffset, focal, focalObstacleLimit);
|
|
if (result.mHit)
|
|
{
|
|
double adjustmentCoef = -(result.mHitPos + result.mHitNormal * focalObstacleLimit - focal).length() / offsetLen;
|
|
mFocalPointAdjustment = focalOffset * std::max(-1.0, adjustmentCoef);
|
|
}
|
|
}
|
|
|
|
// Calculate camera distance.
|
|
mCameraDistance = mBaseCameraDistance + getCameraDistanceCorrection();
|
|
if (mDynamicCameraDistanceEnabled)
|
|
mCameraDistance = std::min(mCameraDistance, mMaxNextCameraDistance);
|
|
osg::Vec3d cameraPos;
|
|
getPosition(focal, cameraPos);
|
|
MWPhysics::RayCastingResult result = rayCasting->castSphere(focal, cameraPos, cameraObstacleLimit);
|
|
if (result.mHit)
|
|
mCameraDistance = (result.mHitPos + result.mHitNormal * cameraObstacleLimit - focal).length();
|
|
}
|
|
|
|
void Camera::updateStandingPreviewMode()
|
|
{
|
|
if (!mStandingPreviewAllowed)
|
|
return;
|
|
float speed = mTrackingPtr.getClass().getCurrentSpeed(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)
|
|
{
|
|
mFocalPointTargetOffset = v;
|
|
mPreviousTransitionSpeed = mFocalPointTransitionSpeed;
|
|
mPreviousTransitionInfluence = 1.0f;
|
|
}
|
|
|
|
void Camera::updateFocalPointOffset(float duration)
|
|
{
|
|
if (duration <= 0)
|
|
return;
|
|
|
|
if (mSkipFocalPointTransition)
|
|
{
|
|
mSkipFocalPointTransition = false;
|
|
mPreviousExtraOffset = osg::Vec2d();
|
|
mPreviousTransitionInfluence = 0.f;
|
|
mFocalPointCurrentOffset = mFocalPointTargetOffset;
|
|
}
|
|
|
|
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)
|
|
{
|
|
// Changing the view will stop all playing animations, so if we are playing
|
|
// anything important, queue the view change for later
|
|
if (!mAnimation->upperBodyReady() && !force)
|
|
{
|
|
mViewModeToggleQueued = true;
|
|
return;
|
|
}
|
|
else
|
|
mViewModeToggleQueued = false;
|
|
|
|
if (mTrackingPtr.getClass().isActor())
|
|
mTrackingPtr.getClass().getCreatureStats(mTrackingPtr).setSideMovementAngle(0);
|
|
|
|
mFirstPersonView = !mFirstPersonView;
|
|
updateStandingPreviewMode();
|
|
instantTransition();
|
|
processViewChange();
|
|
}
|
|
|
|
void Camera::allowVanityMode(bool allow)
|
|
{
|
|
if (!allow && mMode == Mode::Vanity)
|
|
{
|
|
disableDeferredPreviewRotation();
|
|
toggleVanityMode(false);
|
|
}
|
|
mVanityAllowed = allow;
|
|
}
|
|
|
|
bool Camera::toggleVanityMode(bool enable)
|
|
{
|
|
// Changing the view will stop all playing animations, so if we are playing
|
|
// anything important, queue the view change for later
|
|
if (mFirstPersonView && !mAnimation->upperBodyReady())
|
|
{
|
|
mVanityToggleQueued = true;
|
|
mVanityToggleQueuedValue = enable;
|
|
return false;
|
|
}
|
|
|
|
if (!mVanityAllowed && enable)
|
|
return false;
|
|
|
|
if ((mMode == Mode::Vanity) == enable)
|
|
return true;
|
|
mMode = enable ? Mode::Vanity : Mode::Normal;
|
|
if (!mDeferredRotationAllowed)
|
|
disableDeferredPreviewRotation();
|
|
if (!enable)
|
|
calculateDeferredRotation();
|
|
|
|
processViewChange();
|
|
return true;
|
|
}
|
|
|
|
void Camera::togglePreviewMode(bool enable)
|
|
{
|
|
if (mFirstPersonView && !mAnimation->upperBodyReady())
|
|
return;
|
|
|
|
if((mMode == Mode::Preview) == enable)
|
|
return;
|
|
|
|
mMode = enable ? Mode::Preview : Mode::Normal;
|
|
if (mMode == Mode::Normal)
|
|
updateStandingPreviewMode();
|
|
else if (mFirstPersonView)
|
|
instantTransition();
|
|
if (mMode == Mode::Normal)
|
|
{
|
|
if (!mDeferredRotationAllowed)
|
|
disableDeferredPreviewRotation();
|
|
calculateDeferredRotation();
|
|
}
|
|
processViewChange();
|
|
}
|
|
|
|
void Camera::setSneakOffset(float offset)
|
|
{
|
|
mAnimation->setFirstPersonOffset(osg::Vec3f(0,0,-offset));
|
|
}
|
|
|
|
void Camera::setYaw(float angle)
|
|
{
|
|
mYaw = Misc::normalizeAngle(angle);
|
|
}
|
|
|
|
void Camera::setPitch(float angle)
|
|
{
|
|
const float epsilon = 0.000001f;
|
|
float limit = osg::PI_2 - epsilon;
|
|
mPitch = osg::clampBetween(angle, -limit, limit);
|
|
}
|
|
|
|
float Camera::getCameraDistance() const
|
|
{
|
|
if (isFirstPerson())
|
|
return 0.f;
|
|
return mCameraDistance;
|
|
}
|
|
|
|
void Camera::adjustCameraDistance(float delta)
|
|
{
|
|
if (!isFirstPerson())
|
|
{
|
|
if(isNearest() && delta < 0.f && getMode() != Mode::Preview && getMode() != Mode::Vanity)
|
|
toggleViewMode();
|
|
else
|
|
mBaseCameraDistance = std::min(mCameraDistance - getCameraDistanceCorrection(), mBaseCameraDistance) + delta;
|
|
}
|
|
else if (delta > 0.f)
|
|
{
|
|
toggleViewMode();
|
|
mBaseCameraDistance = 0;
|
|
}
|
|
|
|
mIsNearest = mBaseCameraDistance <= mNearest;
|
|
mBaseCameraDistance = osg::clampBetween(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)
|
|
{
|
|
mAnimation = anim;
|
|
processViewChange();
|
|
}
|
|
|
|
void Camera::processViewChange()
|
|
{
|
|
if(isFirstPerson())
|
|
{
|
|
mAnimation->setViewMode(NpcAnimation::VM_FirstPerson);
|
|
mTrackingNode = mAnimation->getNode("Camera");
|
|
if (!mTrackingNode)
|
|
mTrackingNode = mAnimation->getNode("Head");
|
|
mHeightScale = 1.f;
|
|
}
|
|
else
|
|
{
|
|
mAnimation->setViewMode(NpcAnimation::VM_Normal);
|
|
SceneUtil::PositionAttitudeTransform* transform = mTrackingPtr.getRefData().getBaseNode();
|
|
mTrackingNode = transform;
|
|
if (transform)
|
|
mHeightScale = transform->getScale().z();
|
|
else
|
|
mHeightScale = 1.f;
|
|
}
|
|
rotateCamera(getPitch(), getYaw(), false);
|
|
}
|
|
|
|
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()
|
|
{
|
|
mSkipFocalPointTransition = true;
|
|
mDeferredRotationDisabled = false;
|
|
mDeferredRotation = osg::Vec3f();
|
|
rotateCameraToTrackingPtr();
|
|
}
|
|
|
|
void Camera::calculateDeferredRotation()
|
|
{
|
|
MWWorld::Ptr ptr = mTrackingPtr;
|
|
if (isVanityOrPreviewModeEnabled() || ptr.isEmpty())
|
|
return;
|
|
if (mFirstPersonView)
|
|
{
|
|
instantTransition();
|
|
return;
|
|
}
|
|
|
|
mDeferredRotation.x() = Misc::normalizeAngle(-ptr.getRefData().getPosition().rot[0] - mPitch);
|
|
mDeferredRotation.z() = Misc::normalizeAngle(-ptr.getRefData().getPosition().rot[2] - mYaw);
|
|
}
|
|
|
|
}
|