mirror of
https://gitlab.com/OpenMW/openmw.git
synced 2025-01-30 03:32:36 +00:00
e760a6c7e6
Simple Physics API modification for Lua See merge request OpenMW/openmw!1216 (cherry picked from commit d88494c90b501d0832ae0330a0ca81d8b8e5aa50) 02dd055a Save hitObject in castSphere() just like in castRay() 0793d0bf Allow to override collision mask and group for castSphere() as for castRay()
573 lines
19 KiB
C++
573 lines
19 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)
|
|
{
|
|
}
|
|
|
|
void operator()(osg::Node* node, osg::NodeVisitor* nv) override
|
|
{
|
|
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")),
|
|
mPitch(0.f),
|
|
mYaw(0.f),
|
|
mRoll(0.f),
|
|
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),
|
|
mHeadBobbingEnabled(Settings::Manager::getBool("head bobbing", "Camera")),
|
|
mHeadBobbingOffset(0.f),
|
|
mHeadBobbingWeight(0.f),
|
|
mTotalMovement(0.f),
|
|
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() += mHeadBobbingOffset;
|
|
else
|
|
{
|
|
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(mRoll, osg::Vec3d(0, 1, 0)) * osg::Quat(mPitch, osg::Vec3d(1, 0, 0)) * osg::Quat(mYaw, 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::updateHeadBobbing(float duration) {
|
|
static const float doubleStepLength = Settings::Manager::getFloat("head bobbing step", "Camera") * 2;
|
|
static const float stepHeight = Settings::Manager::getFloat("head bobbing height", "Camera");
|
|
static const float maxRoll = osg::DegreesToRadians(Settings::Manager::getFloat("head bobbing roll", "Camera"));
|
|
|
|
if (MWBase::Environment::get().getWorld()->isOnGround(mTrackingPtr))
|
|
mHeadBobbingWeight = std::min(mHeadBobbingWeight + duration * 5, 1.f);
|
|
else
|
|
mHeadBobbingWeight = std::max(mHeadBobbingWeight - duration * 5, 0.f);
|
|
|
|
float doubleStepState = mTotalMovement / doubleStepLength - std::floor(mTotalMovement / doubleStepLength); // from 0 to 1 during 2 steps
|
|
float stepState = std::abs(doubleStepState * 4 - 2) - 1; // from -1 to 1 on even steps and from 1 to -1 on odd steps
|
|
float effect = (1 - std::cos(stepState * osg::DegreesToRadians(30.f))) * 7.5f; // range from 0 to 1
|
|
float coef = std::min(mSmoothedSpeed / 300.f, 1.f) * mHeadBobbingWeight;
|
|
mHeadBobbingOffset = (0.5f - effect) * coef * stepHeight; // range from -stepHeight/2 to stepHeight/2
|
|
mRoll = osg::sign(stepState) * effect * coef * maxRoll; // range from -maxRoll to maxRoll
|
|
}
|
|
|
|
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);
|
|
|
|
if (isFirstPerson() && mHeadBobbingEnabled)
|
|
updateHeadBobbing(duration);
|
|
else
|
|
mRoll = mHeadBobbingOffset = 0;
|
|
|
|
updateFocalPointOffset(duration);
|
|
updatePosition();
|
|
|
|
float speed = mTrackingPtr.getClass().getCurrentSpeed(mTrackingPtr);
|
|
mTotalMovement += speed * duration;
|
|
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 int collisionType = (MWPhysics::CollisionType::CollisionType_Default & ~MWPhysics::CollisionType::CollisionType_Actor);
|
|
|
|
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, collisionType);
|
|
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, collisionType);
|
|
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;
|
|
|
|
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 = static_cast<float>(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);
|
|
}
|
|
|
|
}
|