You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
openmw/apps/opencs/view/render/cameracontroller.cpp

803 lines
22 KiB
C++

#include "cameracontroller.hpp"
#include <algorithm>
#include <cmath>
#include <set>
#include <QWidget>
#include <osg/BoundingBox>
#include <osg/Camera>
#include <osg/ComputeBoundsVisitor>
#include <osg/Group>
#include <osg/Math>
#include <osg/Matrixd>
#include <osg/Quat>
#include <osg/Vec3>
#include <osg/ref_ptr>
#include <osgUtil/IntersectionVisitor>
#include <osgUtil/LineSegmentIntersector>
#include "../../model/prefs/shortcut.hpp"
namespace CSVRender
{
/*
Camera Controller
*/
const osg::Vec3d CameraController::WorldUp = osg::Vec3d(0, 0, 1);
const osg::Vec3d CameraController::LocalUp = osg::Vec3d(0, 1, 0);
const osg::Vec3d CameraController::LocalLeft = osg::Vec3d(1, 0, 0);
const osg::Vec3d CameraController::LocalForward = osg::Vec3d(0, 0, 1);
CameraController::CameraController(QObject* parent)
: QObject(parent)
, mActive(false)
, mInverted(false)
, mCameraSensitivity(1 / 650.f)
, mSecondaryMoveMult(50)
, mWheelMoveMult(8)
, mCamera(nullptr)
{
}
bool CameraController::isActive() const
{
return mActive;
}
osg::Camera* CameraController::getCamera() const
{
return mCamera;
}
double CameraController::getCameraSensitivity() const
{
return mCameraSensitivity;
}
bool CameraController::getInverted() const
{
return mInverted;
}
double CameraController::getSecondaryMovementMultiplier() const
{
return mSecondaryMoveMult;
}
double CameraController::getWheelMovementMultiplier() const
{
return mWheelMoveMult;
}
void CameraController::setCamera(osg::Camera* camera)
{
bool wasActive = mActive;
mCamera = camera;
mActive = (mCamera != nullptr);
if (mActive != wasActive)
{
for (std::vector<CSMPrefs::Shortcut*>::iterator it = mShortcuts.begin(); it != mShortcuts.end(); ++it)
{
CSMPrefs::Shortcut* shortcut = *it;
shortcut->enable(mActive);
}
}
}
void CameraController::setCameraSensitivity(double value)
{
mCameraSensitivity = value;
}
void CameraController::setInverted(bool value)
{
mInverted = value;
}
void CameraController::setSecondaryMovementMultiplier(double value)
{
mSecondaryMoveMult = value;
}
void CameraController::setWheelMovementMultiplier(double value)
{
mWheelMoveMult = value;
}
void CameraController::setup(osg::Group* root, unsigned int mask, const osg::Vec3d& up)
{
// Find World bounds
osg::ComputeBoundsVisitor boundsVisitor;
osg::BoundingBox& boundingBox = boundsVisitor.getBoundingBox();
boundsVisitor.setTraversalMask(mask);
root->accept(boundsVisitor);
if (!boundingBox.valid())
{
// Try again without any mask
boundsVisitor.reset();
boundsVisitor.setTraversalMask(~0u);
root->accept(boundsVisitor);
// Last resort, set a default
if (!boundingBox.valid())
{
boundingBox.set(-1, -1, -1, 1, 1, 1);
}
}
// Calculate a good starting position
osg::Vec3d minBounds = boundingBox.corner(0) - boundingBox.center();
osg::Vec3d maxBounds = boundingBox.corner(7) - boundingBox.center();
osg::Vec3d camOffset = up * maxBounds > 0 ? maxBounds : minBounds;
camOffset *= 2;
osg::Vec3d eye = camOffset + boundingBox.center();
osg::Vec3d center = boundingBox.center();
getCamera()->setViewMatrixAsLookAt(eye, center, up);
}
void CameraController::addShortcut(CSMPrefs::Shortcut* shortcut)
{
mShortcuts.push_back(shortcut);
}
/*
Free Camera Controller
*/
FreeCameraController::FreeCameraController(QWidget* widget)
: CameraController(widget)
, mLockUpright(false)
, mModified(false)
, mNaviPrimary(false)
, mNaviSecondary(false)
, mFast(false)
, mFastAlternate(false)
, mLeft(false)
, mRight(false)
, mForward(false)
, mBackward(false)
, mRollLeft(false)
, mRollRight(false)
, mUp(LocalUp)
, mLinSpeed(1000)
, mRotSpeed(osg::PI / 2)
, mSpeedMult(8)
{
CSMPrefs::Shortcut* naviPrimaryShortcut = new CSMPrefs::Shortcut("scene-navi-primary", widget);
naviPrimaryShortcut->enable(false);
connect(naviPrimaryShortcut, qOverload<bool>(&CSMPrefs::Shortcut::activated), this,
&FreeCameraController::naviPrimary);
addShortcut(naviPrimaryShortcut);
CSMPrefs::Shortcut* naviSecondaryShortcut = new CSMPrefs::Shortcut("scene-navi-secondary", widget);
naviSecondaryShortcut->enable(false);
connect(naviSecondaryShortcut, qOverload<bool>(&CSMPrefs::Shortcut::activated), this,
&FreeCameraController::naviSecondary);
addShortcut(naviSecondaryShortcut);
CSMPrefs::Shortcut* forwardShortcut
= new CSMPrefs::Shortcut("free-forward", "scene-speed-modifier", CSMPrefs::Shortcut::SM_Detach, widget);
forwardShortcut->enable(false);
connect(forwardShortcut, qOverload<bool>(&CSMPrefs::Shortcut::activated), this, &FreeCameraController::forward);
connect(forwardShortcut, qOverload<bool>(&CSMPrefs::Shortcut::secondary), this,
&FreeCameraController::alternateFast);
addShortcut(forwardShortcut);
CSMPrefs::Shortcut* leftShortcut = new CSMPrefs::Shortcut("free-left", widget);
leftShortcut->enable(false);
connect(leftShortcut, qOverload<bool>(&CSMPrefs::Shortcut::activated), this, &FreeCameraController::left);
addShortcut(leftShortcut);
CSMPrefs::Shortcut* backShortcut = new CSMPrefs::Shortcut("free-backward", widget);
backShortcut->enable(false);
connect(backShortcut, qOverload<bool>(&CSMPrefs::Shortcut::activated), this, &FreeCameraController::backward);
addShortcut(backShortcut);
CSMPrefs::Shortcut* rightShortcut = new CSMPrefs::Shortcut("free-right", widget);
rightShortcut->enable(false);
connect(rightShortcut, qOverload<bool>(&CSMPrefs::Shortcut::activated), this, &FreeCameraController::right);
addShortcut(rightShortcut);
CSMPrefs::Shortcut* rollLeftShortcut = new CSMPrefs::Shortcut("free-roll-left", widget);
rollLeftShortcut->enable(false);
connect(
rollLeftShortcut, qOverload<bool>(&CSMPrefs::Shortcut::activated), this, &FreeCameraController::rollLeft);
addShortcut(rollLeftShortcut);
CSMPrefs::Shortcut* rollRightShortcut = new CSMPrefs::Shortcut("free-roll-right", widget);
rollRightShortcut->enable(false);
connect(
rollRightShortcut, qOverload<bool>(&CSMPrefs::Shortcut::activated), this, &FreeCameraController::rollRight);
addShortcut(rollRightShortcut);
CSMPrefs::Shortcut* speedModeShortcut = new CSMPrefs::Shortcut("free-speed-mode", widget);
speedModeShortcut->enable(false);
connect(
speedModeShortcut, qOverload<>(&CSMPrefs::Shortcut::activated), this, &FreeCameraController::swapSpeedMode);
addShortcut(speedModeShortcut);
}
double FreeCameraController::getLinearSpeed() const
{
return mLinSpeed;
}
double FreeCameraController::getRotationalSpeed() const
{
return mRotSpeed;
}
double FreeCameraController::getSpeedMultiplier() const
{
return mSpeedMult;
}
void FreeCameraController::setLinearSpeed(double value)
{
mLinSpeed = value;
}
void FreeCameraController::setRotationalSpeed(double value)
{
mRotSpeed = value;
}
void FreeCameraController::setSpeedMultiplier(double value)
{
mSpeedMult = value;
}
void FreeCameraController::fixUpAxis(const osg::Vec3d& up)
{
mLockUpright = true;
mUp = up;
mModified = true;
}
void FreeCameraController::unfixUpAxis()
{
mLockUpright = false;
}
void FreeCameraController::handleMouseMoveEvent(int x, int y)
{
if (!isActive())
return;
if (mNaviPrimary)
{
double scalar = getCameraSensitivity() * (getInverted() ? -1.0 : 1.0);
yaw(x * scalar);
pitch(y * scalar);
}
else if (mNaviSecondary)
{
osg::Vec3d movement;
movement += LocalLeft * -x * getSecondaryMovementMultiplier();
movement += LocalUp * y * getSecondaryMovementMultiplier();
translate(movement);
}
}
void FreeCameraController::handleMouseScrollEvent(int x)
{
if (!isActive())
return;
translate(LocalForward * x * ((mFast ^ mFastAlternate) ? getWheelMovementMultiplier() : 1));
}
void FreeCameraController::update(double dt)
{
if (!isActive())
return;
double linDist = mLinSpeed * dt;
double rotDist = mRotSpeed * dt;
if (mFast ^ mFastAlternate)
linDist *= mSpeedMult;
if (mLeft)
translate(LocalLeft * linDist);
if (mRight)
translate(LocalLeft * -linDist);
if (mForward)
translate(LocalForward * linDist);
if (mBackward)
translate(LocalForward * -linDist);
if (!mLockUpright)
{
if (mRollLeft)
roll(-rotDist);
if (mRollRight)
roll(rotDist);
}
else if (mModified)
{
stabilize();
mModified = false;
}
// Normalize the matrix to counter drift
getCamera()->getViewMatrix().orthoNormal(getCamera()->getViewMatrix());
}
void FreeCameraController::yaw(double value)
{
getCamera()->getViewMatrix() *= osg::Matrixd::rotate(value, LocalUp);
mModified = true;
}
void FreeCameraController::pitch(double value)
{
const double Constraint = osg::PI / 2 - 0.1;
if (mLockUpright)
{
osg::Vec3d eye, center, up;
getCamera()->getViewMatrixAsLookAt(eye, center, up);
osg::Vec3d forward = center - eye;
osg::Vec3d left = up ^ forward;
double pitchAngle = std::acos(up * mUp);
if ((mUp ^ up) * left < 0)
pitchAngle *= -1;
if (std::abs(pitchAngle + value) > Constraint)
value = (pitchAngle > 0 ? 1 : -1) * Constraint - pitchAngle;
}
getCamera()->getViewMatrix() *= osg::Matrixd::rotate(value, LocalLeft);
mModified = true;
}
void FreeCameraController::roll(double value)
{
getCamera()->getViewMatrix() *= osg::Matrixd::rotate(value, LocalForward);
mModified = true;
}
void FreeCameraController::translate(const osg::Vec3d& offset)
{
getCamera()->getViewMatrix() *= osg::Matrixd::translate(offset);
mModified = true;
}
void FreeCameraController::stabilize()
{
osg::Vec3d eye, center, up;
getCamera()->getViewMatrixAsLookAt(eye, center, up);
getCamera()->setViewMatrixAsLookAt(eye, center, mUp);
}
void FreeCameraController::naviPrimary(bool active)
{
mNaviPrimary = active;
}
void FreeCameraController::naviSecondary(bool active)
{
mNaviSecondary = active;
}
void FreeCameraController::forward(bool active)
{
mForward = active;
}
void FreeCameraController::left(bool active)
{
mLeft = active;
}
void FreeCameraController::backward(bool active)
{
mBackward = active;
}
void FreeCameraController::right(bool active)
{
mRight = active;
}
void FreeCameraController::rollLeft(bool active)
{
mRollLeft = active;
}
void FreeCameraController::rollRight(bool active)
{
mRollRight = active;
}
void FreeCameraController::alternateFast(bool active)
{
mFastAlternate = active;
}
void FreeCameraController::swapSpeedMode()
{
mFast = !mFast;
}
/*
Orbit Camera Controller
*/
OrbitCameraController::OrbitCameraController(QWidget* widget)
: CameraController(widget)
, mInitialized(false)
, mNaviPrimary(false)
, mNaviSecondary(false)
, mFast(false)
, mFastAlternate(false)
, mLeft(false)
, mRight(false)
, mUp(false)
, mDown(false)
, mRollLeft(false)
, mRollRight(false)
, mPickingMask(~0u)
, mCenter(0, 0, 0)
, mDistance(0)
, mOrbitSpeed(osg::PI / 4)
, mOrbitSpeedMult(4)
, mConstRoll(false)
{
CSMPrefs::Shortcut* naviPrimaryShortcut = new CSMPrefs::Shortcut("scene-navi-primary", widget);
naviPrimaryShortcut->enable(false);
connect(naviPrimaryShortcut, qOverload<bool>(&CSMPrefs::Shortcut::activated), this,
&OrbitCameraController::naviPrimary);
addShortcut(naviPrimaryShortcut);
CSMPrefs::Shortcut* naviSecondaryShortcut = new CSMPrefs::Shortcut("scene-navi-secondary", widget);
naviSecondaryShortcut->enable(false);
connect(naviSecondaryShortcut, qOverload<bool>(&CSMPrefs::Shortcut::activated), this,
&OrbitCameraController::naviSecondary);
addShortcut(naviSecondaryShortcut);
CSMPrefs::Shortcut* upShortcut
= new CSMPrefs::Shortcut("orbit-up", "scene-speed-modifier", CSMPrefs::Shortcut::SM_Detach, widget);
upShortcut->enable(false);
connect(upShortcut, qOverload<bool>(&CSMPrefs::Shortcut::activated), this, &OrbitCameraController::up);
connect(
upShortcut, qOverload<bool>(&CSMPrefs::Shortcut::secondary), this, &OrbitCameraController::alternateFast);
addShortcut(upShortcut);
CSMPrefs::Shortcut* leftShortcut = new CSMPrefs::Shortcut("orbit-left", widget);
leftShortcut->enable(false);
connect(leftShortcut, qOverload<bool>(&CSMPrefs::Shortcut::activated), this, &OrbitCameraController::left);
addShortcut(leftShortcut);
CSMPrefs::Shortcut* downShortcut = new CSMPrefs::Shortcut("orbit-down", widget);
downShortcut->enable(false);
connect(downShortcut, qOverload<bool>(&CSMPrefs::Shortcut::activated), this, &OrbitCameraController::down);
addShortcut(downShortcut);
CSMPrefs::Shortcut* rightShortcut = new CSMPrefs::Shortcut("orbit-right", widget);
rightShortcut->enable(false);
connect(rightShortcut, qOverload<bool>(&CSMPrefs::Shortcut::activated), this, &OrbitCameraController::right);
addShortcut(rightShortcut);
CSMPrefs::Shortcut* rollLeftShortcut = new CSMPrefs::Shortcut("orbit-roll-left", widget);
rollLeftShortcut->enable(false);
connect(
rollLeftShortcut, qOverload<bool>(&CSMPrefs::Shortcut::activated), this, &OrbitCameraController::rollLeft);
addShortcut(rollLeftShortcut);
CSMPrefs::Shortcut* rollRightShortcut = new CSMPrefs::Shortcut("orbit-roll-right", widget);
rollRightShortcut->enable(false);
connect(rollRightShortcut, qOverload<bool>(&CSMPrefs::Shortcut::activated), this,
&OrbitCameraController::rollRight);
addShortcut(rollRightShortcut);
CSMPrefs::Shortcut* speedModeShortcut = new CSMPrefs::Shortcut("orbit-speed-mode", widget);
speedModeShortcut->enable(false);
connect(speedModeShortcut, qOverload<>(&CSMPrefs::Shortcut::activated), this,
&OrbitCameraController::swapSpeedMode);
addShortcut(speedModeShortcut);
}
osg::Vec3d OrbitCameraController::getCenter() const
{
return mCenter;
}
double OrbitCameraController::getOrbitSpeed() const
{
return mOrbitSpeed;
}
double OrbitCameraController::getOrbitSpeedMultiplier() const
{
return mOrbitSpeedMult;
}
unsigned int OrbitCameraController::getPickingMask() const
{
return mPickingMask;
}
void OrbitCameraController::setCenter(const osg::Vec3d& value)
{
osg::Vec3d eye, center, up;
getCamera()->getViewMatrixAsLookAt(eye, center, up);
mCenter = value;
mDistance = (eye - mCenter).length();
getCamera()->setViewMatrixAsLookAt(eye, mCenter, up);
mInitialized = true;
}
void OrbitCameraController::setOrbitSpeed(double value)
{
mOrbitSpeed = value;
}
void OrbitCameraController::setOrbitSpeedMultiplier(double value)
{
mOrbitSpeedMult = value;
}
void OrbitCameraController::setPickingMask(unsigned int value)
{
mPickingMask = value;
}
void OrbitCameraController::handleMouseMoveEvent(int x, int y)
{
if (!isActive())
return;
if (!mInitialized)
initialize();
if (mNaviPrimary)
{
double scalar = getCameraSensitivity() * (getInverted() ? -1.0 : 1.0);
rotateHorizontal(x * scalar);
rotateVertical(-y * scalar);
}
else if (mNaviSecondary)
{
osg::Vec3d movement;
movement += LocalLeft * x * getSecondaryMovementMultiplier();
movement += LocalUp * -y * getSecondaryMovementMultiplier();
translate(movement);
}
}
void OrbitCameraController::handleMouseScrollEvent(int x)
{
if (!isActive())
return;
zoom(-x * ((mFast ^ mFastAlternate) ? getWheelMovementMultiplier() : 1));
}
void OrbitCameraController::update(double dt)
{
if (!isActive())
return;
if (!mInitialized)
initialize();
double rotDist = mOrbitSpeed * dt;
if (mFast ^ mFastAlternate)
rotDist *= mOrbitSpeedMult;
if (mLeft)
rotateHorizontal(-rotDist);
if (mRight)
rotateHorizontal(rotDist);
if (mUp)
rotateVertical(rotDist);
if (mDown)
rotateVertical(-rotDist);
if (mRollLeft)
roll(-rotDist);
if (mRollRight)
roll(rotDist);
// Normalize the matrix to counter drift
getCamera()->getViewMatrix().orthoNormal(getCamera()->getViewMatrix());
}
void OrbitCameraController::reset()
{
mInitialized = false;
}
void OrbitCameraController::initialize()
{
static const int DefaultStartDistance = 10000.f;
// Try to intelligently pick focus object
osg::ref_ptr<osgUtil::LineSegmentIntersector> intersector(
new osgUtil::LineSegmentIntersector(osgUtil::Intersector::PROJECTION, osg::Vec3d(0, 0, 0), LocalForward));
intersector->setIntersectionLimit(osgUtil::LineSegmentIntersector::LIMIT_NEAREST);
osgUtil::IntersectionVisitor visitor(intersector);
visitor.setTraversalMask(mPickingMask);
getCamera()->accept(visitor);
osg::Vec3d eye, center, up;
getCamera()->getViewMatrixAsLookAt(eye, center, up, DefaultStartDistance);
if (intersector->getIntersections().begin() != intersector->getIntersections().end())
{
mCenter = intersector->getIntersections().begin()->getWorldIntersectPoint();
mDistance = (eye - mCenter).length();
}
else
{
mCenter = center;
mDistance = DefaultStartDistance;
}
mInitialized = true;
}
void OrbitCameraController::setConstRoll(bool enabled)
{
mConstRoll = enabled;
}
void OrbitCameraController::rotateHorizontal(double value)
{
osg::Vec3d eye, center, up;
getCamera()->getViewMatrixAsLookAt(eye, center, up);
osg::Vec3d absoluteUp = osg::Vec3(0, 0, 1);
osg::Quat rotation = osg::Quat(value, mConstRoll ? absoluteUp : up);
osg::Vec3d oldOffset = eye - mCenter;
osg::Vec3d newOffset = rotation * oldOffset;
if (mConstRoll)
up = rotation * up;
getCamera()->setViewMatrixAsLookAt(mCenter + newOffset, mCenter, up);
}
void OrbitCameraController::rotateVertical(double value)
{
osg::Vec3d eye, center, up;
getCamera()->getViewMatrixAsLookAt(eye, center, up);
osg::Vec3d forward = center - eye;
osg::Vec3d axis = up ^ forward;
osg::Quat rotation = osg::Quat(value, axis);
osg::Vec3d oldOffset = eye - mCenter;
osg::Vec3d newOffset = rotation * oldOffset;
if (mConstRoll)
up = rotation * up;
getCamera()->setViewMatrixAsLookAt(mCenter + newOffset, mCenter, up);
}
void OrbitCameraController::roll(double value)
{
getCamera()->getViewMatrix() *= osg::Matrixd::rotate(value, LocalForward);
}
void OrbitCameraController::translate(const osg::Vec3d& offset)
{
osg::Vec3d eye, center, up;
getCamera()->getViewMatrixAsLookAt(eye, center, up);
osg::Vec3d newOffset = getCamera()->getViewMatrix().getRotate().inverse() * offset;
mCenter += newOffset;
eye += newOffset;
getCamera()->setViewMatrixAsLookAt(eye, mCenter, up);
}
void OrbitCameraController::zoom(double value)
{
mDistance = std::max(10., mDistance + value);
osg::Vec3d eye, center, up;
getCamera()->getViewMatrixAsLookAt(eye, center, up, 1.f);
osg::Vec3d offset = (eye - center) * mDistance;
getCamera()->setViewMatrixAsLookAt(mCenter + offset, mCenter, up);
}
void OrbitCameraController::naviPrimary(bool active)
{
mNaviPrimary = active;
}
void OrbitCameraController::naviSecondary(bool active)
{
mNaviSecondary = active;
}
void OrbitCameraController::up(bool active)
{
mUp = active;
}
void OrbitCameraController::left(bool active)
{
mLeft = active;
}
void OrbitCameraController::down(bool active)
{
mDown = active;
}
void OrbitCameraController::right(bool active)
{
mRight = active;
}
void OrbitCameraController::rollLeft(bool active)
{
if (isActive())
mRollLeft = active;
}
void OrbitCameraController::rollRight(bool active)
{
mRollRight = active;
}
void OrbitCameraController::alternateFast(bool active)
{
mFastAlternate = active;
}
void OrbitCameraController::swapSpeedMode()
{
mFast = !mFast;
}
}