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"