Marble

MarblePhysics.cpp
1// SPDX-License-Identifier: LGPL-2.1-or-later
2//
3// SPDX-FileCopyrightText: 2008 Torsten Rahn <rahn@kde.org>
4//
5
6#include "MarblePhysics.h"
7
8#include "GeoDataLineString.h"
9#include "GeoDataLookAt.h"
10#include "MarbleAbstractPresenter.h"
11#include "MarbleDebug.h"
12#include "Quaternion.h"
13#include "ViewportParams.h"
14
15#include <QTimeLine>
16
17namespace Marble
18{
19
20class MarblePhysicsPrivate
21{
22public:
23 MarbleAbstractPresenter *const m_presenter;
24
25 GeoDataLookAt m_source;
26
27 GeoDataLookAt m_target;
28
29 FlyToMode m_mode;
30
31 QTimeLine m_timeline;
32
33 qreal m_planetRadius;
34
35 explicit MarblePhysicsPrivate(MarbleAbstractPresenter *presenter)
36 : m_presenter(presenter)
37 , m_mode(Instant)
38 , m_planetRadius(EARTH_RADIUS)
39 {
40 m_timeline.setDuration(2000);
42 }
43
44 qreal suggestedRange(qreal t) const
45 {
46 Q_ASSERT(m_mode == Linear || m_mode == Jump);
47 Q_ASSERT(0 <= t && t <= 1.0);
48
49 if (m_mode == Linear) {
50 qreal in = m_source.range();
51 qreal out = m_target.range();
52
53 return in + t * (out - in);
54 } else if (m_mode == Jump) {
55 qreal jumpDuration = m_timeline.duration();
56
57 // Purely cinematic approach to calculate the jump path
58 const qreal totalDistance = m_planetRadius * m_source.coordinates().sphericalDistanceTo(m_target.coordinates());
59 qreal g = qMin(m_source.range(), m_target.range()); // Min altitude
60 qreal k = qMax(m_source.range(), m_target.range()); // Base altitude
61 qreal d = t > 0.5 ? m_source.range() - g : m_target.range() - g; // Base difference
62 qreal c = d * 2 * qAbs(t - 0.5); // Correction factor
63 qreal h = qMin(1000 * 3000.0, totalDistance / 2.0); // Jump height
64
65 // Parameters for the parabolic function that has the maximum at
66 // the point H ( 0.5 * m_jumpDuration, g + h )
67 qreal a = -h / ((qreal)(0.25 * jumpDuration * jumpDuration));
68 qreal b = 2.0 * h / (qreal)(0.5 * jumpDuration);
69
70 qreal x = jumpDuration * t;
71 qreal y = (a * x + b) * x + k - c; // Parabolic function
72
73 return y;
74 } else {
75 qWarning("Unhandled FlyTo mode, no camera distance interpolation.");
76 return m_target.range();
77 }
78 }
79};
80
81MarblePhysics::MarblePhysics(MarbleAbstractPresenter *presenter)
82 : QObject(presenter)
83 , d(new MarblePhysicsPrivate(presenter))
84{
85 connect(&d->m_timeline, &QTimeLine::valueChanged, this, &MarblePhysics::updateProgress);
86 connect(&d->m_timeline, &QTimeLine::finished, this, &MarblePhysics::startStillMode);
87}
88
89MarblePhysics::~MarblePhysics()
90{
91 delete d;
92}
93
94void MarblePhysics::flyTo(const GeoDataLookAt &target, FlyToMode mode)
95{
96 d->m_timeline.stop();
97 d->m_source = d->m_presenter->lookAt();
98 d->m_target = target;
99 const ViewportParams *viewport = d->m_presenter->viewport();
100
101 FlyToMode effectiveMode = mode;
102 qreal x(0), y(0);
103 bool globeHidesPoint(false);
104 bool onScreen = viewport->screenCoordinates(target.coordinates(), x, y, globeHidesPoint);
105 bool invisible = globeHidesPoint || !onScreen;
106
107 if (effectiveMode == Automatic) {
108 bool zoom = qAbs(d->m_source.range() - target.range()) > 10;
109
110 if ((invisible || zoom)) {
111 effectiveMode = Jump;
112 } else {
113 effectiveMode = Linear;
114 }
115 }
116
117 d->m_mode = effectiveMode;
118
119 switch (effectiveMode) {
120 case Instant:
121 d->m_presenter->flyTo(target, Instant);
122 return;
123 case Linear:
124 d->m_timeline.setDuration(300);
125 d->m_timeline.setEasingCurve(QEasingCurve::OutCurve);
126 break;
127 case Jump: {
128 qreal duration = invisible ? 2000 : 1000;
129 d->m_timeline.setDuration(duration);
130 d->m_timeline.setEasingCurve(QEasingCurve::InOutSine);
131 } break;
132 case Automatic:
133 Q_ASSERT(false);
134 break;
135 }
136
137 d->m_timeline.start();
138}
139
140void MarblePhysics::updateProgress(qreal progress)
141{
142 Q_ASSERT(d->m_mode != Instant);
143 Q_ASSERT(d->m_mode != Automatic);
144
145 if (progress >= 1.0) {
146 d->m_presenter->flyTo(d->m_target, Instant);
147 d->m_presenter->setViewContext(Marble::Still);
148 return;
149 }
150
151 Q_ASSERT(progress >= 0.0 && progress < 1.0);
152 const GeoDataCoordinates interpolated = d->m_source.coordinates().interpolate(d->m_target.coordinates(), progress);
153 qreal range = d->suggestedRange(progress);
154
155 GeoDataLookAt intermediate;
156 intermediate.setCoordinates(interpolated);
157 intermediate.setRange(range);
158
159 d->m_presenter->setViewContext(Marble::Animation);
160 d->m_presenter->flyTo(intermediate, Instant);
161}
162
163void MarblePhysics::startStillMode()
164{
165 d->m_presenter->setViewContext(Marble::Still);
166}
167
168}
169
170#include "moc_MarblePhysics.cpp"
This file contains the headers for ViewportParams.
QAction * zoom(const QObject *recvr, const char *slot, QObject *parent)
Binds a QML item to a specific geodetic location in screen coordinates.
@ Automatic
A sane value is chosen automatically depending on animation settings and the action.
@ Instant
Change camera position immediately (no interpolation)
@ Linear
Linear interpolation of lon, lat and distance to ground.
@ Jump
Linear interpolation of lon and lat, distance increases towards the middle point, then decreases.
@ Still
still image
@ Animation
animated view (e.g. while rotating the globe)
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
void setDuration(int duration)
void setEasingCurve(const QEasingCurve &curve)
void finished()
void valueChanged(qreal value)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Nov 8 2024 12:02:44 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.