File indexing completed on 2024-04-21 03:49:44

0001 // SPDX-License-Identifier: LGPL-2.1-or-later
0002 //
0003 // SPDX-FileCopyrightText: 2008 Torsten Rahn <rahn@kde.org>
0004 //
0005 
0006 #include "MarblePhysics.h"
0007 
0008 #include "Quaternion.h"
0009 #include "MarbleAbstractPresenter.h"
0010 #include "GeoDataLookAt.h"
0011 #include "MarbleDebug.h"
0012 #include "GeoDataLineString.h"
0013 #include "ViewportParams.h"
0014 
0015 #include <QTimeLine>
0016 
0017 namespace Marble
0018 {
0019 
0020 class MarblePhysicsPrivate {
0021 public:
0022     MarbleAbstractPresenter *const m_presenter;
0023 
0024     GeoDataLookAt m_source;
0025 
0026     GeoDataLookAt m_target;
0027 
0028     FlyToMode m_mode;
0029 
0030     QTimeLine m_timeline;
0031 
0032     qreal m_planetRadius;
0033 
0034     explicit MarblePhysicsPrivate( MarbleAbstractPresenter *presenter )
0035         : m_presenter( presenter ),
0036           m_mode( Instant ),
0037           m_planetRadius( EARTH_RADIUS )
0038     {
0039         m_timeline.setDuration(2000);
0040         m_timeline.setEasingCurve(QEasingCurve::InOutSine);
0041     }
0042 
0043     qreal suggestedRange(qreal t) const
0044     {
0045         Q_ASSERT( m_mode == Linear || m_mode == Jump);
0046         Q_ASSERT( 0 <= t && t <= 1.0 );
0047 
0048         if (m_mode == Linear) {
0049             qreal in = m_source.range();
0050             qreal out = m_target.range();
0051 
0052             return in + t * (out-in);
0053         }
0054         else if (m_mode == Jump) {
0055             qreal jumpDuration = m_timeline.duration();
0056 
0057             // Purely cinematic approach to calculate the jump path        
0058             const qreal totalDistance = m_planetRadius * m_source.coordinates().sphericalDistanceTo(m_target.coordinates());
0059             qreal g = qMin(m_source.range(), m_target.range()); // Min altitude
0060             qreal k = qMax(m_source.range(), m_target.range()); // Base altitude
0061             qreal d = t > 0.5 ? m_source.range() - g : m_target.range() - g; // Base difference
0062             qreal c = d * 2 * qAbs(t - 0.5); // Correction factor
0063             qreal h = qMin(1000*3000.0, totalDistance / 2.0); // Jump height
0064 
0065             // Parameters for the parabolic function that has the maximum at
0066             // the point H ( 0.5 * m_jumpDuration, g + h )
0067             qreal a = - h / ( (qreal)( 0.25 * jumpDuration * jumpDuration ) );
0068             qreal b = 2.0 * h / (qreal)( 0.5 * jumpDuration );
0069 
0070             qreal x = jumpDuration * t;
0071             qreal y = ( a * x + b ) * x + k - c;       // Parabolic function
0072 
0073             return y;
0074         }
0075         else {
0076             qWarning("Unhandled FlyTo mode, no camera distance interpolation.");
0077             return m_target.range();
0078         }
0079     }
0080 };
0081 
0082 
0083 MarblePhysics::MarblePhysics( MarbleAbstractPresenter *presenter )
0084     : QObject( presenter ),
0085       d( new MarblePhysicsPrivate( presenter ) )
0086 {
0087     connect( &d->m_timeline, SIGNAL(valueChanged(qreal)),
0088              this, SLOT(updateProgress(qreal)) );
0089     connect( &d->m_timeline, SIGNAL(finished()),
0090              this, SLOT(startStillMode()) );
0091 }
0092 
0093 MarblePhysics::~MarblePhysics()
0094 {
0095     delete d;
0096 }
0097 
0098 void MarblePhysics::flyTo( const GeoDataLookAt &target, FlyToMode mode )
0099 {
0100     d->m_timeline.stop();
0101     d->m_source = d->m_presenter->lookAt();
0102     d->m_target = target;
0103     const ViewportParams *viewport = d->m_presenter->viewport();
0104 
0105     FlyToMode effectiveMode = mode;
0106     qreal x(0), y(0);
0107     bool globeHidesPoint(false);
0108     bool onScreen = viewport->screenCoordinates( target.coordinates(), x, y,
0109                                                                       globeHidesPoint );
0110     bool invisible = globeHidesPoint || !onScreen;
0111 
0112     if (effectiveMode == Automatic)
0113     {
0114         bool zoom = qAbs(d->m_source.range()-target.range()) > 10;
0115 
0116         if ( (invisible || zoom) ) {
0117             effectiveMode = Jump;
0118         } else {
0119             effectiveMode = Linear;
0120         }
0121     }
0122 
0123     d->m_mode = effectiveMode;
0124 
0125     switch(effectiveMode)
0126     {
0127     case Instant:
0128         d->m_presenter->flyTo( target, Instant );
0129         return;
0130     case Linear:
0131         d->m_timeline.setDuration(300);
0132         d->m_timeline.setEasingCurve(QEasingCurve::OutCurve);
0133         break;
0134     case Jump:
0135         {
0136             qreal duration = invisible ? 2000 : 1000;
0137             d->m_timeline.setDuration(duration);
0138             d->m_timeline.setEasingCurve(QEasingCurve::InOutSine);
0139         }
0140         break;
0141     case Automatic:
0142         Q_ASSERT(false);
0143         break;
0144     }
0145 
0146     d->m_timeline.start();
0147 }
0148 
0149 void MarblePhysics::updateProgress(qreal progress)
0150 {
0151     Q_ASSERT(d->m_mode != Instant);
0152     Q_ASSERT(d->m_mode != Automatic);
0153 
0154     if (progress >= 1.0)
0155     {
0156         d->m_presenter->flyTo( d->m_target, Instant );
0157         d->m_presenter->setViewContext( Marble::Still );
0158         return;
0159     }
0160 
0161     Q_ASSERT(progress >= 0.0 && progress < 1.0);
0162     const GeoDataCoordinates interpolated = d->m_source.coordinates().interpolate(d->m_target.coordinates(), progress);
0163     qreal range = d->suggestedRange(progress);
0164 
0165     GeoDataLookAt intermediate;
0166     intermediate.setCoordinates(interpolated);
0167     intermediate.setRange(range);
0168 
0169     d->m_presenter->setViewContext( Marble::Animation );
0170     d->m_presenter->flyTo( intermediate, Instant );
0171 }
0172 
0173 void MarblePhysics::startStillMode()
0174 {
0175     d->m_presenter->setViewContext( Marble::Still );
0176 }
0177 
0178 }
0179 
0180 #include "moc_MarblePhysics.cpp"