1
0
mirror of https://gitlab.com/OpenMW/openmw.git synced 2025-01-30 12:32:36 +00:00
OpenMW/apps/openmw/mwrender/camera.cpp

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

439 lines
14 KiB
C++
Raw Normal View History

2013-04-29 05:50:40 -07:00
#include "camera.hpp"
2015-06-14 23:13:26 +02:00
#include <osg/Camera>
#include <components/misc/mathutil.hpp>
#include <components/sceneutil/nodecallback.hpp>
#include <components/sceneutil/positionattitudetransform.hpp>
#include "../mwbase/environment.hpp"
#include "../mwbase/windowmanager.hpp"
#include "../mwbase/world.hpp"
2020-06-22 01:54:08 +02:00
#include "../mwworld/class.hpp"
#include "../mwworld/ptr.hpp"
#include "../mwworld/refdata.hpp"
#include "../mwmechanics/movement.hpp"
2020-06-22 01:54:08 +02:00
2020-08-03 22:44:16 +02:00
#include "../mwphysics/raycasting.hpp"
2012-08-14 20:33:29 +04:00
#include "npcanimation.hpp"
2015-05-21 23:54:39 +02:00
namespace
{
class UpdateRenderCameraCallback : public SceneUtil::NodeCallback<UpdateRenderCameraCallback, osg::Camera*>
2015-05-21 23:54:39 +02:00
{
2022-09-22 21:26:05 +03:00
public:
2015-09-24 15:21:42 +02:00
UpdateRenderCameraCallback(MWRender::Camera* cam)
2015-05-21 23:54:39 +02:00
: mCamera(cam)
2022-09-22 21:26:05 +03:00
{
2015-05-21 23:54:39 +02:00
}
void operator()(osg::Camera* cam, osg::NodeVisitor* nv)
2015-05-21 23:54:39 +02:00
{
// traverse first to update animations, in case the camera is attached to an animated node
traverse(cam, nv);
2015-05-21 23:54:39 +02:00
mCamera->updateCamera(cam);
}
private:
MWRender::Camera* mCamera;
};
}
namespace MWRender
{
2015-05-21 23:54:39 +02:00
Camera::Camera(osg::Camera* camera)
: mHeightScale(1.f)
, mCollisionType(
(MWPhysics::CollisionType::CollisionType_Default & ~MWPhysics::CollisionType::CollisionType_Actor)
| MWPhysics::CollisionType_CameraOnly)
, mCamera(camera)
2018-10-09 10:21:12 +04:00
, mAnimation(nullptr)
, mFirstPersonView(true)
2021-06-20 00:57:41 +02:00
, mMode(Mode::FirstPerson)
, mVanityAllowed(true)
2021-06-14 19:58:04 +02:00
, mDeferredRotationAllowed(true)
2021-06-20 00:57:41 +02:00
, mProcessViewChange(false)
, mHeight(124.f)
2020-10-28 18:02:31 +04:00
, mPitch(0.f)
, mYaw(0.f)
, mRoll(0.f)
2020-06-22 01:54:08 +02:00
, mCameraDistance(0.f)
2021-06-14 19:58:04 +02:00
, mPreferredCameraDistance(0.f)
, mFocalPointCurrentOffset(osg::Vec2d())
, mFocalPointTargetOffset(osg::Vec2d())
, mFocalPointTransitionSpeedCoef(1.f)
, mSkipFocalPointTransition(true)
, mPreviousTransitionInfluence(0.f)
2021-06-14 19:58:04 +02:00
, mShowCrosshair(false)
, mDeferredRotation(osg::Vec3f())
, mDeferredRotationDisabled(false)
{
2015-09-24 15:21:42 +02:00
mUpdateCallback = new UpdateRenderCameraCallback(this);
2015-05-21 23:54:39 +02:00
mCamera->addUpdateCallback(mUpdateCallback);
2012-08-12 15:50:37 +04:00
}
2012-09-13 19:03:31 +02:00
2013-04-29 05:50:40 -07:00
Camera::~Camera()
2012-09-13 19:03:31 +02:00
{
2015-05-21 23:54:39 +02:00
mCamera->removeUpdateCallback(mUpdateCallback);
2012-09-13 19:03:31 +02:00
}
2021-06-14 19:58:04 +02:00
osg::Vec3d Camera::calculateTrackedPosition() const
{
if (!mTrackingNode)
2015-06-01 15:34:46 +02:00
return osg::Vec3d();
osg::NodePathList nodepaths = mTrackingNode->getParentalNodePaths();
if (nodepaths.empty())
2015-06-01 15:34:46 +02:00
return osg::Vec3d();
osg::Matrix worldMat = osg::computeLocalToWorld(nodepaths[0]);
2021-06-14 19:58:04 +02:00
osg::Vec3d res = worldMat.getTrans();
if (mMode != Mode::FirstPerson)
res.z() += mHeight * mHeightScale;
return res;
2015-06-01 15:34:46 +02:00
}
2020-06-22 01:54:08 +02:00
osg::Vec3d Camera::getFocalPointOffset() const
{
2021-06-14 19:58:04 +02:00
osg::Vec3d offset;
offset.x() = mFocalPointCurrentOffset.x() * cos(mYaw);
offset.y() = mFocalPointCurrentOffset.x() * sin(mYaw);
offset.z() = mFocalPointCurrentOffset.y();
2020-06-22 01:54:08 +02:00
return offset;
}
2015-06-01 15:34:46 +02:00
void Camera::updateCamera(osg::Camera* cam)
{
osg::Quat orient = osg::Quat(mRoll + mExtraRoll, osg::Vec3d(0, 1, 0))
2021-06-14 19:58:04 +02:00
* osg::Quat(mPitch + mExtraPitch, osg::Vec3d(1, 0, 0)) * osg::Quat(mYaw + mExtraYaw, osg::Vec3d(0, 0, 1));
2015-05-31 23:09:37 +02:00
osg::Vec3d forward = orient * osg::Vec3d(0, 1, 0);
osg::Vec3d up = orient * osg::Vec3d(0, 0, 1);
2015-05-21 23:54:39 +02:00
2021-06-14 19:58:04 +02:00
osg::Vec3d pos = mPosition;
if (mMode == Mode::FirstPerson)
{
// It is a hack. Camera position depends on neck animation.
// Animations are updated in OSG cull traversal and in order to avoid 1 frame delay we
// recalculate the position here. Note that it becomes different from mPosition that
// is used in other parts of the code.
// TODO: detach camera from OSG animation and get rid of this hack.
osg::Vec3d recalculatedTrackedPosition = calculateTrackedPosition();
pos = calculateFirstPersonPosition(recalculatedTrackedPosition);
}
cam->setViewMatrixAsLookAt(pos, pos + forward, up);
2022-04-03 20:42:19 +02:00
mViewMatrix = cam->getViewMatrix();
2020-10-04 20:12:45 +02:00
}
void Camera::update(float duration, bool paused)
2012-08-12 15:50:37 +04:00
{
2021-06-14 19:58:04 +02:00
mLockPitch = mLockYaw = false;
2021-06-20 00:57:41 +02:00
if (mQueuedMode && mAnimation->upperBodyReady())
setMode(*mQueuedMode);
if (mProcessViewChange)
processViewChange();
if (paused)
return;
2012-10-01 11:24:44 +04:00
2020-06-22 01:54:08 +02:00
// only show the crosshair in game mode
MWBase::WindowManager* wm = MWBase::Environment::get().getWindowManager();
2021-06-14 19:58:04 +02:00
wm->showCrosshair(!wm->isGuiMode() && mShowCrosshair);
2020-10-04 20:12:45 +02:00
updateFocalPointOffset(duration);
updatePosition();
2021-06-14 19:58:04 +02:00
}
2021-06-14 19:58:04 +02:00
osg::Vec3d Camera::calculateFirstPersonPosition(const osg::Vec3d& trackedPosition) const
{
osg::Vec3d res = trackedPosition;
osg::Vec2f horizontalOffset
= Misc::rotateVec2f(osg::Vec2f(mFirstPersonOffset.x(), mFirstPersonOffset.y()), mYaw);
res.x() += horizontalOffset.x();
res.y() += horizontalOffset.y();
res.z() += mFirstPersonOffset.z();
return res;
}
void Camera::updatePosition()
{
2021-06-14 19:58:04 +02:00
mTrackedPosition = calculateTrackedPosition();
2021-06-20 00:57:41 +02:00
if (mMode == Mode::Static)
return;
2021-06-20 00:57:41 +02:00
if (mMode == Mode::FirstPerson)
{
2021-06-14 19:58:04 +02:00
mPosition = calculateFirstPersonPosition(mTrackedPosition);
mCameraDistance = 0;
2021-06-20 00:57:41 +02:00
return;
}
2021-06-20 00:57:41 +02:00
constexpr float cameraObstacleLimit = 5.0f;
constexpr float focalObstacleLimit = 10.f;
2020-08-03 22:44:16 +02:00
const auto* rayCasting = MWBase::Environment::get().getWorld()->getRayCasting();
// Adjust focal point to prevent clipping.
osg::Vec3d focalOffset = getFocalPointOffset();
2021-06-14 19:58:04 +02:00
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();
if (offsetLen > 0)
{
2022-04-03 20:42:19 +02:00
MWPhysics::RayCastingResult result
= rayCasting->castSphere(focal - focalOffset, focal, focalObstacleLimit, mCollisionType);
if (result.mHit)
{
double adjustmentCoef
= -(result.mHitPos + result.mHitNormal * focalObstacleLimit - focal).length() / offsetLen;
2021-06-20 00:57:41 +02:00
focal += focalOffset * std::max(-1.0, adjustmentCoef);
}
}
2021-06-14 19:58:04 +02:00
// Adjust camera distance.
mCameraDistance = mPreferredCameraDistance;
osg::Quat orient
= osg::Quat(mPitch + mExtraPitch, osg::Vec3d(1, 0, 0)) * osg::Quat(mYaw + mExtraYaw, osg::Vec3d(0, 0, 1));
2021-06-20 00:57:41 +02:00
osg::Vec3d offset = orient * osg::Vec3d(0.f, -mCameraDistance, 0.f);
2022-04-03 20:42:19 +02:00
MWPhysics::RayCastingResult result
= rayCasting->castSphere(focal, focal + offset, cameraObstacleLimit, mCollisionType);
if (result.mHit)
2021-06-20 00:57:41 +02:00
{
mCameraDistance = (result.mHitPos + result.mHitNormal * cameraObstacleLimit - focal).length();
2021-06-20 00:57:41 +02:00
offset = orient * osg::Vec3d(0.f, -mCameraDistance, 0.f);
}
mPosition = focal + offset;
}
2021-06-20 00:57:41 +02:00
void Camera::setMode(Mode newMode, bool force)
{
2021-06-20 00:57:41 +02:00
if (mMode == newMode)
2023-02-15 09:52:26 +01:00
{
mQueuedMode = std::nullopt;
2021-06-20 00:57:41 +02:00
return;
2023-02-15 09:52:26 +01:00
}
2021-06-20 00:57:41 +02:00
Mode oldMode = mMode;
2022-04-03 20:42:19 +02:00
if (!force && (newMode == Mode::FirstPerson || oldMode == Mode::FirstPerson) && mAnimation
&& !mAnimation->upperBodyReady())
2021-06-20 00:57:41 +02:00
{
// Changing the view will stop all playing animations, so if we are playing
// anything important, queue the view change for later
mQueuedMode = newMode;
return;
2021-06-20 00:57:41 +02:00
}
mMode = newMode;
mQueuedMode = std::nullopt;
if (newMode == Mode::FirstPerson)
mFirstPersonView = true;
else if (newMode == Mode::ThirdPerson)
mFirstPersonView = false;
calculateDeferredRotation();
if (oldMode == Mode::FirstPerson || newMode == Mode::FirstPerson)
{
instantTransition();
mProcessViewChange = true;
}
2020-06-22 01:54:08 +02:00
}
2021-06-20 00:57:41 +02:00
void Camera::setFocalPointTargetOffset(const 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;
2012-08-12 15:50:37 +04:00
}
void Camera::toggleViewMode(bool force)
2012-08-12 15:50:37 +04:00
{
2021-06-20 00:57:41 +02:00
setMode(mFirstPersonView ? Mode::ThirdPerson : Mode::FirstPerson, force);
2012-08-12 15:50:37 +04:00
}
2013-04-29 05:50:40 -07:00
bool Camera::toggleVanityMode(bool enable)
2012-08-12 15:50:37 +04:00
{
if (!enable)
2021-06-20 00:57:41 +02:00
setMode(mFirstPersonView ? Mode::FirstPerson : Mode::ThirdPerson, false);
else if (mVanityAllowed)
setMode(Mode::Vanity, false);
return (mMode == Mode::Vanity) == enable;
2012-08-12 15:50:37 +04:00
}
void Camera::setSneakOffset(float offset)
{
mAnimation->setFirstPersonOffset(osg::Vec3f(0, 0, -offset));
}
2021-06-14 19:58:04 +02:00
void Camera::setYaw(float angle, bool force)
2012-08-14 02:36:18 +04:00
{
2021-06-14 19:58:04 +02:00
if (!mLockYaw || force)
mYaw = Misc::normalizeAngle(angle);
if (force)
mLockYaw = true;
2012-08-14 02:36:18 +04:00
}
2012-08-12 18:35:35 +04:00
2021-06-14 19:58:04 +02:00
void Camera::setPitch(float angle, bool force)
2012-08-14 14:37:48 +04:00
{
2013-04-27 01:24:36 -07:00
const float epsilon = 0.000001f;
float limit = static_cast<float>(osg::PI_2) - epsilon;
2021-06-14 19:58:04 +02:00
if (!mLockPitch || force)
mPitch = std::clamp(angle, -limit, limit);
if (force)
mLockPitch = true;
2012-08-12 18:35:35 +04:00
}
2012-08-14 14:37:48 +04:00
2021-06-14 19:58:04 +02:00
void Camera::setStaticPosition(const osg::Vec3d& pos)
{
2021-06-14 19:58:04 +02:00
if (mMode != Mode::Static)
throw std::runtime_error("setStaticPosition can be used only if camera is in Static mode");
mPosition = pos;
2012-08-14 14:37:48 +04:00
}
2012-08-14 20:33:29 +04:00
2013-04-29 05:50:40 -07:00
void Camera::setAnimation(NpcAnimation* anim)
2012-08-15 15:17:35 +04:00
{
mAnimation = anim;
2021-06-20 00:57:41 +02:00
mProcessViewChange = true;
2012-08-15 15:17:35 +04:00
}
void Camera::processViewChange()
2012-08-14 20:33:29 +04:00
{
2021-06-14 19:58:04 +02:00
if (mTrackingPtr.isEmpty())
return;
2021-06-20 00:57:41 +02:00
if (mMode == Mode::FirstPerson)
{
mAnimation->setViewMode(NpcAnimation::VM_FirstPerson);
2015-05-31 01:07:43 +02:00
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)
2015-11-11 17:23:47 +01:00
mHeightScale = transform->getScale().z();
else
mHeightScale = 1.f;
}
2021-06-20 00:57:41 +02:00
mProcessViewChange = false;
2012-08-14 20:33:29 +04:00
}
void Camera::applyDeferredPreviewRotationToPlayer(float dt)
{
2021-06-20 00:57:41 +02:00
if (mMode != Mode::ThirdPerson || 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()
{
2021-06-14 19:58:04 +02:00
if (mMode == Mode::Static || mTrackingPtr.isEmpty())
2021-06-20 00:57:41 +02:00
return;
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()
{
2021-06-20 00:57:41 +02:00
if (mMode == Mode::Static)
{
mDeferredRotation = osg::Vec3f();
return;
}
MWWorld::Ptr ptr = mTrackingPtr;
2021-06-20 00:57:41 +02:00
if (mMode == Mode::Preview || mMode == Mode::Vanity || 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);
2021-06-20 00:57:41 +02:00
if (!mDeferredRotationAllowed)
mDeferredRotationDisabled = true;
}
}