12#include <QtCore/QtMath>
13#include <QtGui/QQuaternion>
19constexpr qreal kYawFullDragDegrees = 360.0;
20constexpr qreal kTiltFullDragDegrees = 180.0;
21constexpr qreal kZoomSensitivity = 0.001;
24QVector3D centerToCameraDirection(qreal tilt, qreal heading)
26 const qreal tiltRad = qDegreesToRadians(tilt);
27 const qreal headingRad = qDegreesToRadians(heading);
28 return QVector3D(std::sin(tiltRad) * std::sin(headingRad), -std::sin(tiltRad) * std::cos(headingRad),
38 if (center == _orbitCenter) {
41 _orbitCenter = center;
76 if (size == _viewportSize) {
85 if (fov == _fieldOfView) {
108 _panAnchorValid = ground.has_value();
109 if (_panAnchorValid) {
110 _panAnchor = *ground;
116 if (!_panAnchorValid) {
120 if (!ground.has_value()) {
130 if (!_viewportSize.isValid() || _viewportSize.isEmpty()) {
131 _orbitAnchorValid =
false;
136 _orbitAnchor = ground.value_or(_orbitCenter);
137 _orbitAnchorValid =
true;
138 _lastOrbitPos = screenPos;
143 if (!_orbitAnchorValid) {
147 const QPointF delta = screenPos - _lastOrbitPos;
148 _lastOrbitPos = screenPos;
150 const qreal headingDelta = (delta.x() / _viewportSize.width()) * kYawFullDragDegrees;
151 const qreal tiltDelta = -((delta.y() / _viewportSize.height()) * kTiltFullDragDegrees);
153 _rotateAboutAnchor(_orbitAnchor, headingDelta, tiltDelta);
158 if (!_viewportSize.isValid() || _viewportSize.isEmpty()) {
162 const QVector3D anchor =
screenToGround(screenPos).value_or(_orbitCenter);
163 _rotateAboutAnchor(anchor, degrees, 0.0);
168 if (!_viewportSize.isValid() || _viewportSize.isEmpty()) {
171 const QVector3D anchor =
screenToGround(screenPos).value_or(_orbitCenter);
172 _rotateAboutAnchor(anchor, 0.0, degrees);
175void Viewer3DCameraController::_rotateAboutAnchor(
const QVector3D& anchor, qreal headingDelta, qreal tiltDelta)
179 QVector3D center = _orbitCenter;
182 if (headingDelta != 0.0) {
183 const QQuaternion yaw = QQuaternion::fromAxisAndAngle(QVector3D(0, 0, 1),
static_cast<float>(headingDelta));
184 center = anchor + yaw.rotatedVector(center - anchor);
191 if (tiltDelta != 0.0) {
192 const qreal headingRad = qDegreesToRadians(_heading);
193 const QVector3D rightAxis(
static_cast<float>(std::cos(headingRad)),
static_cast<float>(std::sin(headingRad)),
195 const QQuaternion pitch = QQuaternion::fromAxisAndAngle(rightAxis,
static_cast<float>(tiltDelta));
196 center = anchor + pitch.rotatedVector(center - anchor);
205 if (!_viewportSize.isValid() || _viewportSize.isEmpty()) {
212 zoomBy(factor, screenPos);
217 if ((factor <= 0.0) || !_viewportSize.isValid() || _viewportSize.isEmpty()) {
222 factor = newDistance / _distance;
228 if (ground.has_value()) {
230 setOrbitCenter(*ground + ((_orbitCenter - *ground) *
static_cast<float>(factor)));
237 if (!_viewportSize.isValid() || _viewportSize.isEmpty()) {
242 const qreal tanHalfFov = std::tan(qDegreesToRadians(_fieldOfView / 2.0));
243 return (2.0 * _distance * tanHalfFov) / _viewportSize.height();
248 return _orbitCenter + (centerToCameraDirection(_tilt, _heading) *
static_cast<float>(_distance));
253 if (!_viewportSize.isValid() || _viewportSize.isEmpty()) {
257 const qreal tiltRad = qDegreesToRadians(_tilt);
258 const qreal headingRad = qDegreesToRadians(_heading);
259 const qreal sinT = std::sin(tiltRad);
260 const qreal cosT = std::cos(tiltRad);
261 const qreal sinH = std::sin(headingRad);
262 const qreal cosH = std::cos(headingRad);
265 const QVector3D forward(
static_cast<float>(-sinT * sinH),
static_cast<float>(sinT * cosH),
266 static_cast<float>(-cosT));
267 const QVector3D right(
static_cast<float>(cosH),
static_cast<float>(sinH), 0.0f);
268 const QVector3D up(
static_cast<float>(-sinH * cosT),
static_cast<float>(cosH * cosT),
static_cast<float>(sinT));
271 const qreal tanHalfFov = std::tan(qDegreesToRadians(_fieldOfView / 2.0));
272 const qreal aspect = _viewportSize.width() / _viewportSize.height();
273 const qreal ndcX = ((2.0 * screenPos.x()) / _viewportSize.width()) - 1.0;
274 const qreal ndcY = 1.0 - ((2.0 * screenPos.y()) / _viewportSize.height());
275 const QVector3D rayDir = (right *
static_cast<float>(ndcX * tanHalfFov * aspect)) +
276 (up *
static_cast<float>(ndcY * tanHalfFov)) + forward;
278 if (rayDir.z() >= 0.0f) {
283 const float s = -rayOrigin.z() / rayDir.z();
287 return rayOrigin + (rayDir * s);
Q_INVOKABLE void beginOrbit(const QPointF &screenPos)
Start an orbit gesture at the given screen position (pixels)
Q_INVOKABLE void zoomBy(qreal factor, const QPointF &screenPos)
void setOrbitCenter(const QVector3D ¢er)
std::optional< QVector3D > screenToGround(const QPointF &screenPos) const
Q_INVOKABLE void orbitTo(const QPointF &screenPos)
Viewer3DCameraController(QObject *parent=nullptr)
Q_INVOKABLE void lookAt(const QVector3D ¢er, qreal heading, qreal tilt, qreal distance)
Set the full pose in one call. tilt/distance are clamped.
static constexpr qreal kMinTilt
Q_INVOKABLE void rotateBy(qreal degrees, const QPointF &screenPos)
static constexpr qreal kDefaultDistance
Q_INVOKABLE void zoom(qreal amount, const QPointF &screenPos)
void viewportSizeChanged()
static constexpr qreal kMaxDistance
void setDistance(qreal distance)
Q_INVOKABLE void reset()
Restore the default pose (center origin, heading 0, tilt 0, default distance)
void fieldOfViewChanged()
Q_INVOKABLE void beginPan(const QPointF &screenPos)
Start a pan gesture at the given screen position (pixels)
void setHeading(qreal heading)
static constexpr qreal kMinDistance
Q_INVOKABLE void panTo(const QPointF &screenPos)
Continue a pan gesture: the ground point picked at beginPan stays under the cursor.
void orbitCenterChanged()
static constexpr qreal kMaxTilt
void setFieldOfView(qreal fov)
QVector3D cameraPosition() const
Derived camera position in scene space.
void setViewportSize(const QSizeF &size)
Q_INVOKABLE void tiltBy(qreal degrees, const QPointF &screenPos)
Q_INVOKABLE qreal sceneUnitsPerPixel() const