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