Refactor 3D globe panning logic

Instead of raycasting based on a previously-captured depth buffer, which
only works well for featureless scenes, construct a virtual sphere and
intersect the mouse pointer's ray with that.
This commit is contained in:
David Koňařík 2025-04-21 19:43:26 +02:00 committed by Nyall Dawson
parent f5d4c39334
commit 8f33234255

View File

@ -678,17 +678,30 @@ void QgsCameraController::onPositionChangedGlobeTerrainNavigation( Qt3DInput::QM
mDragPointCalculated = true; mDragPointCalculated = true;
} }
const QgsVector3D mapPressPos = QgsVector3D( mDragPoint ) + mOrigin; const QgsVector3D mapStartPos = QgsVector3D( mDragPoint ) + mOrigin;
// Approximate the globe as a sphere with a center in mOrigin and of radius
double newDepth = sampleDepthBuffer( mouse->x(), mouse->y() ); // the same as at mapStartPos.
if ( newDepth == 1 ) const double sphereRadius = mapStartPos.length();
return; // the mouse is somewhere in the void... // Find the intersection of this sphere and the ray from the current clicked point.
const QgsRay3D ray = Qgs3DUtils::rayFromScreenPoint( QPoint( mouse->x(), mouse->y() ), mScene->engine()->size(), mCameraBefore.get() );
const QVector3D newWorldPosition = Qgs3DUtils::screenPointToWorldPos( QPoint( mouse->x(), mouse->y() ), newDepth, mScene->engine()->size(), mDepthBufferCamera.get() ); const QgsVector3D rayOriginShifted = QgsVector3D( ray.origin() ) + mOrigin;
if ( !std::isfinite( newWorldPosition.x() ) || !std::isfinite( newWorldPosition.y() ) || !std::isfinite( newWorldPosition.z() ) ) // From equations of ray and sphere
const double quadA = QVector3D::dotProduct( ray.direction(), ray.direction() );
const double quadB = 2 * QgsVector3D::dotProduct( ray.direction(), rayOriginShifted );
const double quadC = QgsVector3D::dotProduct( rayOriginShifted, rayOriginShifted ) - sphereRadius * sphereRadius;
const double disc = quadB * quadB - 4 * quadA * quadC;
if ( disc < 0 )
// Ray misses sphere
return; return;
// Distance to intersection along ray (take smaller root, closer to camera)
const QgsVector3D newMapPos = QgsVector3D( newWorldPosition ) + mOrigin; const double rayDist = ( -quadB - sqrt( disc ) ) / ( 2 * quadA );
if ( rayDist < 0 )
{
QgsDebugError( QStringLiteral( "Sphere intersection result negative, cancelling move" ) );
return;
}
QVector3D sphereIntersection = ray.origin() + ( float ) rayDist * ray.direction();
const QgsVector3D newMapPos = QgsVector3D( sphereIntersection ) + mOrigin;
// now that we have old and new mouse position in ECEF coordinates, // now that we have old and new mouse position in ECEF coordinates,
// let's figure out the difference in lat/lon angles and update the center point // let's figure out the difference in lat/lon angles and update the center point
@ -696,7 +709,7 @@ void QgsCameraController::onPositionChangedGlobeTerrainNavigation( Qt3DInput::QM
QgsVector3D oldLatLon, newLatLon; QgsVector3D oldLatLon, newLatLon;
try try
{ {
oldLatLon = mGlobeCrsToLatLon.transform( mapPressPos ); oldLatLon = mGlobeCrsToLatLon.transform( mapStartPos );
newLatLon = mGlobeCrsToLatLon.transform( newMapPos ); newLatLon = mGlobeCrsToLatLon.transform( newMapPos );
} }
catch ( const QgsCsException & ) catch ( const QgsCsException & )
@ -710,6 +723,7 @@ void QgsCameraController::onPositionChangedGlobeTerrainNavigation( Qt3DInput::QM
const QgsVector3D newVC = moveGeocentricPoint( mMousePressViewCenter, latDiff, lonDiff ); const QgsVector3D newVC = moveGeocentricPoint( mMousePressViewCenter, latDiff, lonDiff );
const QgsVector3D newVCWorld = newVC - mOrigin; const QgsVector3D newVCWorld = newVC - mOrigin;
mCameraPose.setCenterPoint( newVCWorld ); mCameraPose.setCenterPoint( newVCWorld );
updateCameraFromPose(); updateCameraFromPose();
} }