1 #include "camera.hpp"
2 
3 #include <osg/Camera>
4 
5 #include <components/misc/mathutil.hpp>
6 #include <components/sceneutil/positionattitudetransform.hpp>
7 #include <components/settings/settings.hpp>
8 
9 #include "../mwbase/environment.hpp"
10 #include "../mwbase/windowmanager.hpp"
11 #include "../mwbase/world.hpp"
12 
13 #include "../mwworld/class.hpp"
14 #include "../mwworld/ptr.hpp"
15 #include "../mwworld/refdata.hpp"
16 
17 #include "../mwmechanics/drawstate.hpp"
18 #include "../mwmechanics/movement.hpp"
19 #include "../mwmechanics/npcstats.hpp"
20 
21 #include "../mwphysics/raycasting.hpp"
22 
23 #include "npcanimation.hpp"
24 
25 namespace
26 {
27 
28 class UpdateRenderCameraCallback : public osg::NodeCallback
29 {
30 public:
UpdateRenderCameraCallback(MWRender::Camera * cam)31     UpdateRenderCameraCallback(MWRender::Camera* cam)
32         : mCamera(cam)
33     {
34     }
35 
operator ()(osg::Node * node,osg::NodeVisitor * nv)36     void operator()(osg::Node* node, osg::NodeVisitor* nv) override
37     {
38         osg::Camera* cam = static_cast<osg::Camera*>(node);
39 
40         // traverse first to update animations, in case the camera is attached to an animated node
41         traverse(node, nv);
42 
43         mCamera->updateCamera(cam);
44     }
45 
46 private:
47     MWRender::Camera* mCamera;
48 };
49 
50 }
51 
52 namespace MWRender
53 {
54 
Camera(osg::Camera * camera)55     Camera::Camera (osg::Camera* camera)
56     : mHeightScale(1.f),
57       mCamera(camera),
58       mAnimation(nullptr),
59       mFirstPersonView(true),
60       mMode(Mode::Normal),
61       mVanityAllowed(true),
62       mStandingPreviewAllowed(Settings::Manager::getBool("preview if stand still", "Camera")),
63       mDeferredRotationAllowed(Settings::Manager::getBool("deferred preview rotation", "Camera")),
64       mNearest(30.f),
65       mFurthest(800.f),
66       mIsNearest(false),
67       mHeight(124.f),
68       mBaseCameraDistance(Settings::Manager::getFloat("third person camera distance", "Camera")),
69       mPitch(0.f),
70       mYaw(0.f),
71       mRoll(0.f),
72       mVanityToggleQueued(false),
73       mVanityToggleQueuedValue(false),
74       mViewModeToggleQueued(false),
75       mCameraDistance(0.f),
76       mMaxNextCameraDistance(800.f),
77       mFocalPointCurrentOffset(osg::Vec2d()),
78       mFocalPointTargetOffset(osg::Vec2d()),
79       mFocalPointTransitionSpeedCoef(1.f),
80       mSkipFocalPointTransition(true),
81       mPreviousTransitionInfluence(0.f),
82       mSmoothedSpeed(0.f),
83       mZoomOutWhenMoveCoef(Settings::Manager::getFloat("zoom out when move coef", "Camera")),
84       mDynamicCameraDistanceEnabled(false),
85       mShowCrosshairInThirdPersonMode(false),
86       mHeadBobbingEnabled(Settings::Manager::getBool("head bobbing", "Camera")),
87       mHeadBobbingOffset(0.f),
88       mHeadBobbingWeight(0.f),
89       mTotalMovement(0.f),
90       mDeferredRotation(osg::Vec3f()),
91       mDeferredRotationDisabled(false)
92     {
93         mCameraDistance = mBaseCameraDistance;
94 
95         mUpdateCallback = new UpdateRenderCameraCallback(this);
96         mCamera->addUpdateCallback(mUpdateCallback);
97     }
98 
~Camera()99     Camera::~Camera()
100     {
101         mCamera->removeUpdateCallback(mUpdateCallback);
102     }
103 
getFocalPoint() const104     osg::Vec3d Camera::getFocalPoint() const
105     {
106         if (!mTrackingNode)
107             return osg::Vec3d();
108         osg::NodePathList nodepaths = mTrackingNode->getParentalNodePaths();
109         if (nodepaths.empty())
110             return osg::Vec3d();
111         osg::Matrix worldMat = osg::computeLocalToWorld(nodepaths[0]);
112 
113         osg::Vec3d position = worldMat.getTrans();
114         if (isFirstPerson())
115             position.z() += mHeadBobbingOffset;
116         else
117         {
118             position.z() += mHeight * mHeightScale;
119 
120             // We subtract 10.f here and add it within focalPointOffset in order to avoid camera clipping through ceiling.
121             // Needed because character's head can be a bit higher than collision area.
122             position.z() -= 10.f;
123 
124             position += getFocalPointOffset() + mFocalPointAdjustment;
125         }
126         return position;
127     }
128 
getFocalPointOffset() const129     osg::Vec3d Camera::getFocalPointOffset() const
130     {
131         osg::Vec3d offset(0, 0, 10.f);
132         offset.x() += mFocalPointCurrentOffset.x() * cos(getYaw());
133         offset.y() += mFocalPointCurrentOffset.x() * sin(getYaw());
134         offset.z() += mFocalPointCurrentOffset.y();
135         return offset;
136     }
137 
getPosition(osg::Vec3d & focal,osg::Vec3d & camera) const138     void Camera::getPosition(osg::Vec3d &focal, osg::Vec3d &camera) const
139     {
140         focal = getFocalPoint();
141         osg::Vec3d offset(0,0,0);
142         if (!isFirstPerson())
143         {
144             osg::Quat orient =  osg::Quat(getPitch(), osg::Vec3d(1,0,0)) * osg::Quat(getYaw(), osg::Vec3d(0,0,1));
145             offset = orient * osg::Vec3d(0.f, -mCameraDistance, 0.f);
146         }
147         camera = focal + offset;
148     }
149 
updateCamera(osg::Camera * cam)150     void Camera::updateCamera(osg::Camera *cam)
151     {
152         osg::Vec3d focal, position;
153         getPosition(focal, position);
154 
155         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));
156         osg::Vec3d forward = orient * osg::Vec3d(0,1,0);
157         osg::Vec3d up = orient * osg::Vec3d(0,0,1);
158 
159         cam->setViewMatrixAsLookAt(position, position + forward, up);
160     }
161 
updateHeadBobbing(float duration)162     void Camera::updateHeadBobbing(float duration) {
163         static const float doubleStepLength = Settings::Manager::getFloat("head bobbing step", "Camera") * 2;
164         static const float stepHeight = Settings::Manager::getFloat("head bobbing height", "Camera");
165         static const float maxRoll = osg::DegreesToRadians(Settings::Manager::getFloat("head bobbing roll", "Camera"));
166 
167         if (MWBase::Environment::get().getWorld()->isOnGround(mTrackingPtr))
168             mHeadBobbingWeight = std::min(mHeadBobbingWeight + duration * 5, 1.f);
169         else
170             mHeadBobbingWeight = std::max(mHeadBobbingWeight - duration * 5, 0.f);
171 
172         float doubleStepState = mTotalMovement / doubleStepLength - std::floor(mTotalMovement / doubleStepLength); // from 0 to 1 during 2 steps
173         float stepState = std::abs(doubleStepState * 4 - 2) - 1; // from -1 to 1 on even steps and from 1 to -1 on odd steps
174         float effect = (1 - std::cos(stepState * osg::DegreesToRadians(30.f))) * 7.5f; // range from 0 to 1
175         float coef = std::min(mSmoothedSpeed / 300.f, 1.f) * mHeadBobbingWeight;
176         mHeadBobbingOffset = (0.5f - effect) * coef * stepHeight; // range from -stepHeight/2 to stepHeight/2
177         mRoll = osg::sign(stepState) * effect * coef * maxRoll; // range from -maxRoll to maxRoll
178     }
179 
reset()180     void Camera::reset()
181     {
182         togglePreviewMode(false);
183         toggleVanityMode(false);
184         if (!mFirstPersonView)
185             toggleViewMode();
186     }
187 
rotateCamera(float pitch,float yaw,bool adjust)188     void Camera::rotateCamera(float pitch, float yaw, bool adjust)
189     {
190         if (adjust)
191         {
192             pitch += getPitch();
193             yaw += getYaw();
194         }
195         setYaw(yaw);
196         setPitch(pitch);
197     }
198 
update(float duration,bool paused)199     void Camera::update(float duration, bool paused)
200     {
201         if (mAnimation->upperBodyReady())
202         {
203             // Now process the view changes we queued earlier
204             if (mVanityToggleQueued)
205             {
206                 toggleVanityMode(mVanityToggleQueuedValue);
207                 mVanityToggleQueued = false;
208             }
209             if (mViewModeToggleQueued)
210             {
211                 togglePreviewMode(false);
212                 toggleViewMode();
213                 mViewModeToggleQueued = false;
214             }
215         }
216 
217         if (paused)
218             return;
219 
220         // only show the crosshair in game mode
221         MWBase::WindowManager *wm = MWBase::Environment::get().getWindowManager();
222         wm->showCrosshair(!wm->isGuiMode() && mMode != Mode::Preview && mMode != Mode::Vanity
223                           && (mFirstPersonView || mShowCrosshairInThirdPersonMode));
224 
225         if(mMode == Mode::Vanity)
226             rotateCamera(0.f, osg::DegreesToRadians(3.f * duration), true);
227 
228         if (isFirstPerson() && mHeadBobbingEnabled)
229             updateHeadBobbing(duration);
230         else
231             mRoll = mHeadBobbingOffset = 0;
232 
233         updateFocalPointOffset(duration);
234         updatePosition();
235 
236         float speed = mTrackingPtr.getClass().getCurrentSpeed(mTrackingPtr);
237         mTotalMovement += speed * duration;
238         speed /= (1.f + speed / 500.f);
239         float maxDelta = 300.f * duration;
240         mSmoothedSpeed += osg::clampBetween(speed - mSmoothedSpeed, -maxDelta, maxDelta);
241 
242         mMaxNextCameraDistance = mCameraDistance + duration * (100.f + mBaseCameraDistance);
243         updateStandingPreviewMode();
244     }
245 
updatePosition()246     void Camera::updatePosition()
247     {
248         mFocalPointAdjustment = osg::Vec3d();
249         if (isFirstPerson())
250             return;
251 
252         const float cameraObstacleLimit = 5.0f;
253         const float focalObstacleLimit = 10.f;
254 
255         const auto* rayCasting = MWBase::Environment::get().getWorld()->getRayCasting();
256 
257         // Adjust focal point to prevent clipping.
258         osg::Vec3d focal = getFocalPoint();
259         osg::Vec3d focalOffset = getFocalPointOffset();
260         float offsetLen = focalOffset.length();
261         if (offsetLen > 0)
262         {
263             MWPhysics::RayCastingResult result = rayCasting->castSphere(focal - focalOffset, focal, focalObstacleLimit);
264             if (result.mHit)
265             {
266                 double adjustmentCoef = -(result.mHitPos + result.mHitNormal * focalObstacleLimit - focal).length() / offsetLen;
267                 mFocalPointAdjustment = focalOffset * std::max(-1.0, adjustmentCoef);
268             }
269         }
270 
271         // Calculate camera distance.
272         mCameraDistance = mBaseCameraDistance + getCameraDistanceCorrection();
273         if (mDynamicCameraDistanceEnabled)
274             mCameraDistance = std::min(mCameraDistance, mMaxNextCameraDistance);
275         osg::Vec3d cameraPos;
276         getPosition(focal, cameraPos);
277         MWPhysics::RayCastingResult result = rayCasting->castSphere(focal, cameraPos, cameraObstacleLimit);
278         if (result.mHit)
279             mCameraDistance = (result.mHitPos + result.mHitNormal * cameraObstacleLimit - focal).length();
280     }
281 
updateStandingPreviewMode()282     void Camera::updateStandingPreviewMode()
283     {
284         if (!mStandingPreviewAllowed)
285             return;
286         float speed = mTrackingPtr.getClass().getCurrentSpeed(mTrackingPtr);
287         bool combat = mTrackingPtr.getClass().isActor() &&
288                       mTrackingPtr.getClass().getCreatureStats(mTrackingPtr).getDrawState() != MWMechanics::DrawState_Nothing;
289         bool standingStill = speed == 0 && !combat && !mFirstPersonView;
290         if (!standingStill && mMode == Mode::StandingPreview)
291         {
292             mMode = Mode::Normal;
293             calculateDeferredRotation();
294         }
295         else if (standingStill && mMode == Mode::Normal)
296             mMode = Mode::StandingPreview;
297     }
298 
setFocalPointTargetOffset(osg::Vec2d v)299     void Camera::setFocalPointTargetOffset(osg::Vec2d v)
300     {
301         mFocalPointTargetOffset = v;
302         mPreviousTransitionSpeed = mFocalPointTransitionSpeed;
303         mPreviousTransitionInfluence = 1.0f;
304     }
305 
updateFocalPointOffset(float duration)306     void Camera::updateFocalPointOffset(float duration)
307     {
308         if (duration <= 0)
309             return;
310 
311         if (mSkipFocalPointTransition)
312         {
313             mSkipFocalPointTransition = false;
314             mPreviousExtraOffset = osg::Vec2d();
315             mPreviousTransitionInfluence = 0.f;
316             mFocalPointCurrentOffset = mFocalPointTargetOffset;
317         }
318 
319         osg::Vec2d oldOffset = mFocalPointCurrentOffset;
320 
321         if (mPreviousTransitionInfluence > 0)
322         {
323             mFocalPointCurrentOffset -= mPreviousExtraOffset;
324             mPreviousExtraOffset = mPreviousExtraOffset / mPreviousTransitionInfluence + mPreviousTransitionSpeed * duration;
325             mPreviousTransitionInfluence =
326                 std::max(0.f, mPreviousTransitionInfluence - duration * mFocalPointTransitionSpeedCoef);
327             mPreviousExtraOffset *= mPreviousTransitionInfluence;
328             mFocalPointCurrentOffset += mPreviousExtraOffset;
329         }
330 
331         osg::Vec2d delta = mFocalPointTargetOffset - mFocalPointCurrentOffset;
332         if (delta.length2() > 0)
333         {
334             float coef = duration * (1.0 + 5.0 / delta.length()) *
335                          mFocalPointTransitionSpeedCoef * (1.0f - mPreviousTransitionInfluence);
336             mFocalPointCurrentOffset += delta * std::min(coef, 1.0f);
337         }
338         else
339         {
340             mPreviousExtraOffset = osg::Vec2d();
341             mPreviousTransitionInfluence = 0.f;
342         }
343 
344         mFocalPointTransitionSpeed = (mFocalPointCurrentOffset - oldOffset) / duration;
345     }
346 
toggleViewMode(bool force)347     void Camera::toggleViewMode(bool force)
348     {
349         // Changing the view will stop all playing animations, so if we are playing
350         // anything important, queue the view change for later
351         if (!mAnimation->upperBodyReady() && !force)
352         {
353             mViewModeToggleQueued = true;
354             return;
355         }
356         else
357             mViewModeToggleQueued = false;
358 
359         mFirstPersonView = !mFirstPersonView;
360         updateStandingPreviewMode();
361         instantTransition();
362         processViewChange();
363     }
364 
allowVanityMode(bool allow)365     void Camera::allowVanityMode(bool allow)
366     {
367         if (!allow && mMode == Mode::Vanity)
368         {
369             disableDeferredPreviewRotation();
370             toggleVanityMode(false);
371         }
372         mVanityAllowed = allow;
373     }
374 
toggleVanityMode(bool enable)375     bool Camera::toggleVanityMode(bool enable)
376     {
377         // Changing the view will stop all playing animations, so if we are playing
378         // anything important, queue the view change for later
379         if (mFirstPersonView && !mAnimation->upperBodyReady())
380         {
381             mVanityToggleQueued = true;
382             mVanityToggleQueuedValue = enable;
383             return false;
384         }
385 
386         if (!mVanityAllowed && enable)
387             return false;
388 
389         if ((mMode == Mode::Vanity) == enable)
390             return true;
391         mMode = enable ? Mode::Vanity : Mode::Normal;
392         if (!mDeferredRotationAllowed)
393             disableDeferredPreviewRotation();
394         if (!enable)
395             calculateDeferredRotation();
396 
397         processViewChange();
398         return true;
399     }
400 
togglePreviewMode(bool enable)401     void Camera::togglePreviewMode(bool enable)
402     {
403         if (mFirstPersonView && !mAnimation->upperBodyReady())
404             return;
405 
406         if((mMode == Mode::Preview) == enable)
407             return;
408 
409         mMode = enable ? Mode::Preview : Mode::Normal;
410         if (mMode == Mode::Normal)
411             updateStandingPreviewMode();
412         else if (mFirstPersonView)
413             instantTransition();
414         if (mMode == Mode::Normal)
415         {
416             if (!mDeferredRotationAllowed)
417                 disableDeferredPreviewRotation();
418             calculateDeferredRotation();
419         }
420         processViewChange();
421     }
422 
setSneakOffset(float offset)423     void Camera::setSneakOffset(float offset)
424     {
425         mAnimation->setFirstPersonOffset(osg::Vec3f(0,0,-offset));
426     }
427 
setYaw(float angle)428     void Camera::setYaw(float angle)
429     {
430         mYaw = Misc::normalizeAngle(angle);
431     }
432 
setPitch(float angle)433     void Camera::setPitch(float angle)
434     {
435         const float epsilon = 0.000001f;
436         float limit = static_cast<float>(osg::PI_2) - epsilon;
437         mPitch = osg::clampBetween(angle, -limit, limit);
438     }
439 
getCameraDistance() const440     float Camera::getCameraDistance() const
441     {
442         if (isFirstPerson())
443             return 0.f;
444         return mCameraDistance;
445     }
446 
adjustCameraDistance(float delta)447     void Camera::adjustCameraDistance(float delta)
448     {
449         if (!isFirstPerson())
450         {
451             if(isNearest() && delta < 0.f && getMode() != Mode::Preview && getMode() != Mode::Vanity)
452                 toggleViewMode();
453             else
454                 mBaseCameraDistance = std::min(mCameraDistance - getCameraDistanceCorrection(), mBaseCameraDistance) + delta;
455         }
456         else if (delta > 0.f)
457         {
458             toggleViewMode();
459             mBaseCameraDistance = 0;
460         }
461 
462         mIsNearest = mBaseCameraDistance <= mNearest;
463         mBaseCameraDistance = osg::clampBetween(mBaseCameraDistance, mNearest, mFurthest);
464         Settings::Manager::setFloat("third person camera distance", "Camera", mBaseCameraDistance);
465     }
466 
getCameraDistanceCorrection() const467     float Camera::getCameraDistanceCorrection() const
468     {
469         if (!mDynamicCameraDistanceEnabled)
470             return 0;
471 
472         float pitchCorrection = std::max(-getPitch(), 0.f) * 50.f;
473 
474         float smoothedSpeedSqr = mSmoothedSpeed * mSmoothedSpeed;
475         float speedCorrection = smoothedSpeedSqr / (smoothedSpeedSqr + 300.f*300.f) * mZoomOutWhenMoveCoef;
476 
477         return pitchCorrection + speedCorrection;
478     }
479 
setAnimation(NpcAnimation * anim)480     void Camera::setAnimation(NpcAnimation *anim)
481     {
482         mAnimation = anim;
483         processViewChange();
484     }
485 
processViewChange()486     void Camera::processViewChange()
487     {
488         if(isFirstPerson())
489         {
490             mAnimation->setViewMode(NpcAnimation::VM_FirstPerson);
491             mTrackingNode = mAnimation->getNode("Camera");
492             if (!mTrackingNode)
493                 mTrackingNode = mAnimation->getNode("Head");
494             mHeightScale = 1.f;
495         }
496         else
497         {
498             mAnimation->setViewMode(NpcAnimation::VM_Normal);
499             SceneUtil::PositionAttitudeTransform* transform = mTrackingPtr.getRefData().getBaseNode();
500             mTrackingNode = transform;
501             if (transform)
502                 mHeightScale = transform->getScale().z();
503             else
504                 mHeightScale = 1.f;
505         }
506         rotateCamera(getPitch(), getYaw(), false);
507     }
508 
applyDeferredPreviewRotationToPlayer(float dt)509     void Camera::applyDeferredPreviewRotationToPlayer(float dt)
510     {
511         if (isVanityOrPreviewModeEnabled() || mTrackingPtr.isEmpty())
512             return;
513 
514         osg::Vec3f rot = mDeferredRotation;
515         float delta = rot.normalize();
516         delta = std::min(delta, (delta + 1.f) * 3 * dt);
517         rot *= delta;
518         mDeferredRotation -= rot;
519 
520         if (mDeferredRotationDisabled)
521         {
522             mDeferredRotationDisabled = delta > 0.0001;
523             rotateCameraToTrackingPtr();
524             return;
525         }
526 
527         auto& movement = mTrackingPtr.getClass().getMovementSettings(mTrackingPtr);
528         movement.mRotation[0] += rot.x();
529         movement.mRotation[1] += rot.y();
530         movement.mRotation[2] += rot.z();
531         if (std::abs(mDeferredRotation.z()) > 0.0001)
532         {
533             float s = std::sin(mDeferredRotation.z());
534             float c = std::cos(mDeferredRotation.z());
535             float x = movement.mPosition[0];
536             float y = movement.mPosition[1];
537             movement.mPosition[0] = x *  c + y * s;
538             movement.mPosition[1] = x * -s + y * c;
539         }
540     }
541 
rotateCameraToTrackingPtr()542     void Camera::rotateCameraToTrackingPtr()
543     {
544         setPitch(-mTrackingPtr.getRefData().getPosition().rot[0] - mDeferredRotation.x());
545         setYaw(-mTrackingPtr.getRefData().getPosition().rot[2] - mDeferredRotation.z());
546     }
547 
instantTransition()548     void Camera::instantTransition()
549     {
550         mSkipFocalPointTransition = true;
551         mDeferredRotationDisabled = false;
552         mDeferredRotation = osg::Vec3f();
553         rotateCameraToTrackingPtr();
554     }
555 
calculateDeferredRotation()556     void Camera::calculateDeferredRotation()
557     {
558         MWWorld::Ptr ptr = mTrackingPtr;
559         if (isVanityOrPreviewModeEnabled() || ptr.isEmpty())
560             return;
561         if (mFirstPersonView)
562         {
563             instantTransition();
564             return;
565         }
566 
567         mDeferredRotation.x() = Misc::normalizeAngle(-ptr.getRefData().getPosition().rot[0] - mPitch);
568         mDeferredRotation.z() = Misc::normalizeAngle(-ptr.getRefData().getPosition().rot[2] - mYaw);
569     }
570 
571 }
572