QGIS API Documentation 3.99.0-Master (09f76ad7019)
Loading...
Searching...
No Matches
qgs3daxis.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgs3daxis.cpp
3 --------------------------------------
4 Date : March 2022
5 Copyright : (C) 2022 by Jean Felder
6 Email : jean dot felder at oslandia dot com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
16#include "qgs3daxis.h"
17
18#include <ctime>
19
20#include "qgs3dmapscene.h"
21#include "qgs3dmapsettings.h"
22#include "qgs3dwiredmesh_p.h"
25#include "qgsframegraph.h"
26#include "qgsterrainentity.h"
27#include "qgswindow3dengine.h"
28
29#include <QActionGroup>
30#include <QApplication>
31#include <QFontDatabase>
32#include <QScreen>
33#include <QString>
34#include <QWidget>
35#include <Qt3DCore/QTransform>
36#include <Qt3DExtras/QConeMesh>
37#include <Qt3DExtras/QCylinderMesh>
38#include <Qt3DExtras/QPhongMaterial>
39#include <Qt3DRender/QPointLight>
40#include <Qt3DRender/QSortPolicy>
41#include <Qt3DRender/qcameralens.h>
42
43#include "moc_qgs3daxis.cpp"
44
45using namespace Qt::StringLiterals;
46
47Qgs3DAxis::Qgs3DAxis( Qgs3DMapCanvas *canvas, Qt3DCore::QEntity *parent3DScene, Qgs3DMapScene *mapScene, //
48 QgsCameraController *cameraCtrl, Qgs3DMapSettings *map )
49 : QObject( canvas )
50 , mMapSettings( map )
51 , mCanvas( canvas )
52 , mMapScene( mapScene )
53 , mCameraController( cameraCtrl )
54 , mCrs( map->crs() )
55{
56 mMapScene->engine()->frameGraph()->registerRenderView( std::make_unique<Qgs3DAxisRenderView>( //
58 mCanvas, mCameraController, mMapSettings, //
59 this
60 ),
62
63 mRenderView = dynamic_cast<Qgs3DAxisRenderView *>( mMapScene->engine()->frameGraph()->renderView( QgsFrameGraph::AXIS3D_RENDERVIEW ) );
64 Q_ASSERT( mRenderView );
65 constructAxisScene( parent3DScene );
66 constructLabelsScene( parent3DScene );
67
68 mTwoDLabelSceneEntity->addComponent( mRenderView->labelLayer() );
69
70 connect( cameraCtrl, &QgsCameraController::cameraChanged, this, &Qgs3DAxis::onCameraUpdate );
71
72 createAxisScene();
73 onAxisViewportSizeUpdate();
74
75 init3DObjectPicking();
76}
77
79{
80 delete mMenu;
81 mMenu = nullptr;
82
83 // When an object (axis or cube) is not enabled. It is still present but it does not have a parent.
84 // In that case, it will never be automatically deleted. Therefore, it needs to be manually deleted.
85 // See setEnableCube() and setEnableAxis().
86 switch ( mMapSettings->get3DAxisSettings().mode() )
87 {
89 delete mCubeRoot;
90 mCubeRoot = nullptr;
91 break;
93 delete mAxisRoot;
94 mAxisRoot = nullptr;
95 break;
97 delete mAxisRoot;
98 mAxisRoot = nullptr;
99 delete mCubeRoot;
100 mCubeRoot = nullptr;
101 break;
102 }
103
104 // render view unregistration will be done by framegraph destructor!
105}
106
107void Qgs3DAxis::init3DObjectPicking()
108{
109 mDefaultPickingMethod = mMapScene->engine()->renderSettings()->pickingSettings()->pickMethod();
110
111 // Create screencaster to be used by EventFilter:
112 // 1- Perform ray casting tests by specifying "touch" coordinates in screen space
113 // 2- connect screencaster results to onTouchedByRay
114 // 3- screencaster will be triggered by EventFilter
115 mScreenRayCaster = new Qt3DRender::QScreenRayCaster( mAxisSceneEntity );
116 mScreenRayCaster->addLayer( mRenderView->objectLayer() ); // to only filter on axis objects
117 mScreenRayCaster->setFilterMode( Qt3DRender::QScreenRayCaster::AcceptAllMatchingLayers );
118 mScreenRayCaster->setRunMode( Qt3DRender::QAbstractRayCaster::SingleShot );
119
120 mAxisSceneEntity->addComponent( mScreenRayCaster );
121
122 QObject::connect( mScreenRayCaster, &Qt3DRender::QScreenRayCaster::hitsChanged, this, &Qgs3DAxis::onTouchedByRay );
123}
124
125// will be called by Qgs3DMapCanvas::eventFilter
126bool Qgs3DAxis::handleEvent( QEvent *event )
127{
128 if ( event->type() == QEvent::MouseButtonPress )
129 {
130 // register mouse click to detect dragging
131 mHasClicked = true;
132 QMouseEvent *mouseEvent = static_cast<QMouseEvent *>( event );
133 mLastClickedPos = mouseEvent->pos();
134 }
135
136 // handle QEvent::MouseButtonRelease as it represents the end of click and QEvent::MouseMove.
137 else if ( event->type() == QEvent::MouseButtonRelease || event->type() == QEvent::MouseMove )
138 {
139 QMouseEvent *mouseEvent = static_cast<QMouseEvent *>( event );
140
141 // user has clicked and move ==> dragging start
142 if ( event->type() == QEvent::MouseMove && ( ( mHasClicked && ( mouseEvent->pos() - mLastClickedPos ).manhattanLength() < QApplication::startDragDistance() ) || mIsDragging ) )
143 {
144 mIsDragging = true;
145 }
146
147 // user has released ==> dragging ends
148 else if ( mIsDragging && event->type() == QEvent::MouseButtonRelease )
149 {
150 mIsDragging = false;
151 mHasClicked = false;
152 }
153
154 // user is moving or has released but not dragging
155 else if ( !mIsDragging )
156 {
157 // limit ray caster usage to the axis viewport
158 QPointF normalizedPos( static_cast<float>( mouseEvent->pos().x() ) / static_cast<float>( mCanvas->width() ), static_cast<float>( mouseEvent->pos().y() ) / static_cast<float>( mCanvas->height() ) );
159
160 if ( 2 <= QgsLogger::debugLevel() && event->type() == QEvent::MouseButtonRelease )
161 {
162 std::ostringstream os;
163 os << "QGS3DAxis: normalized pos: " << normalizedPos << " / viewport: " << mRenderView->viewport()->normalizedRect();
164 QgsDebugMsgLevel( os.str().c_str(), 2 );
165 }
166
167 if ( mRenderView->viewport()->normalizedRect().contains( normalizedPos ) )
168 {
169 mLastClickedButton = mouseEvent->button();
170 mLastClickedPos = mouseEvent->pos();
171
172 // if casted ray from pos matches an entity, call onTouchedByRay
173 mScreenRayCaster->trigger( mLastClickedPos );
174 }
175 // exit the viewport
176 else
177 {
178 // reset the mouse cursor if needed
179 if ( mPreviousCursor != Qt::ArrowCursor && mCanvas->cursor() == Qt::ArrowCursor )
180 {
181 mCanvas->setCursor( mPreviousCursor );
182 mPreviousCursor = Qt::ArrowCursor;
183 }
184
185 // reset the picking settings if needed
186 if ( mMapScene->engine()->renderSettings()->pickingSettings()->pickMethod() == Qt3DRender::QPickingSettings::TrianglePicking
187 && mDefaultPickingMethod != Qt3DRender::QPickingSettings::TrianglePicking )
188 {
189 mMapScene->engine()->renderSettings()->pickingSettings()->setPickMethod( mDefaultPickingMethod );
190 QgsDebugMsgLevel( "Disabling triangle picking", 2 );
191 }
192 }
193
194 mIsDragging = false; // drag ends
195 mHasClicked = false;
196 }
197 }
198
199 return false;
200}
201
202void Qgs3DAxis::onTouchedByRay( const Qt3DRender::QAbstractRayCaster::Hits &hits )
203{
204 int hitFoundIdx = -1;
205 if ( !hits.empty() )
206 {
207 if ( 2 <= QgsLogger::debugLevel() )
208 {
209 std::ostringstream os;
210 os << "Qgs3DAxis::onTouchedByRay " << hits.length() << " hits at pos " << mLastClickedPos << " with QButton: " << mLastClickedButton;
211 for ( int i = 0; i < hits.length(); ++i )
212 {
213 os << "\n";
214 os << "\tHit Type: " << hits.at( i ).type() << "\n";
215 os << "\tHit triangle id: " << hits.at( i ).primitiveIndex() << "\n";
216 os << "\tHit distance: " << hits.at( i ).distance() << "\n";
217 os << "\tHit entity name: " << hits.at( i ).entity()->objectName().toStdString();
218 }
219 QgsDebugMsgLevel( os.str().c_str(), 2 );
220 }
221
222 for ( int i = 0; i < hits.length() && hitFoundIdx == -1; ++i )
223 {
224 Qt3DCore::QEntity *hitEntity = hits.at( i ).entity();
225 // In Qt6, a Qt3DExtras::Text2DEntity contains a private entity: Qt3DExtras::DistanceFieldTextRenderer
226 // The Text2DEntity needs to be retrieved to handle proper picking
227 if ( hitEntity && qobject_cast<Qt3DExtras::QText2DEntity *>( hitEntity->parentEntity() ) )
228 {
229 hitEntity = hitEntity->parentEntity();
230 }
231 if ( hits.at( i ).distance() < 500.0f && hitEntity && ( hitEntity == mCubeRoot || hitEntity == mAxisRoot || hitEntity->parent() == mCubeRoot || hitEntity->parent() == mAxisRoot ) )
232 {
233 hitFoundIdx = i;
234 }
235 }
236 }
237
238 if ( mLastClickedButton == Qt::NoButton ) // hover
239 {
240 if ( hitFoundIdx != -1 )
241 {
242 if ( mCanvas->cursor() != Qt::ArrowCursor )
243 {
244 mPreviousCursor = mCanvas->cursor();
245 mCanvas->setCursor( Qt::ArrowCursor );
246 QgsDebugMsgLevel( "Enabling arrow cursor", 2 );
247
248 // The cube needs triangle picking to handle click on faces.
249 if ( mMapScene->engine()->renderSettings()->pickingSettings()->pickMethod() != Qt3DRender::QPickingSettings::TrianglePicking && mCubeRoot->isEnabled() )
250 {
251 mMapScene->engine()->renderSettings()->pickingSettings()->setPickMethod( Qt3DRender::QPickingSettings::TrianglePicking );
252 QgsDebugMsgLevel( "Enabling triangle picking", 2 );
253 }
254 }
255 }
256 }
257 else if ( mLastClickedButton == Qt::MouseButton::RightButton && hitFoundIdx != -1 ) // show menu
258 {
259 displayMenuAt( mLastClickedPos );
260 }
261 else if ( mLastClickedButton == Qt::MouseButton::LeftButton ) // handle cube face clicks
262 {
263 hideMenu();
264
265 if ( hitFoundIdx != -1 )
266 {
267 Qt3DCore::QEntity *hitEntity = hits.at( hitFoundIdx ).entity();
268 if ( hitEntity && qobject_cast<Qt3DExtras::QText2DEntity *>( hitEntity->parentEntity() ) )
269 {
270 hitEntity = hitEntity->parentEntity();
271 }
272 if ( hitEntity && ( hitEntity == mCubeRoot || hitEntity->parent() == mCubeRoot ) )
273 {
274 switch ( hits.at( hitFoundIdx ).primitiveIndex() / 2 )
275 {
276 case 0: // "East face";
277 QgsDebugMsgLevel( "Qgs3DAxis: East face clicked", 2 );
278 mCameraController->rotateCameraToEast();
279 break;
280
281 case 1: // "West face ";
282 QgsDebugMsgLevel( "Qgs3DAxis: West face clicked", 2 );
283 mCameraController->rotateCameraToWest();
284 break;
285
286 case 2: // "North face ";
287 QgsDebugMsgLevel( "Qgs3DAxis: North face clicked", 2 );
288 mCameraController->rotateCameraToNorth();
289 break;
290
291 case 3: // "South face";
292 QgsDebugMsgLevel( "Qgs3DAxis: South face clicked", 2 );
293 mCameraController->rotateCameraToSouth();
294 break;
295
296 case 4: // "Top face ";
297 QgsDebugMsgLevel( "Qgs3DAxis: Top face clicked", 2 );
298 mCameraController->rotateCameraToTop();
299 break;
300
301 case 5: // "Bottom face ";
302 QgsDebugMsgLevel( "Qgs3DAxis: Bottom face clicked", 2 );
303 mCameraController->rotateCameraToBottom();
304 break;
305
306 default:
307 break;
308 }
309 }
310 }
311 }
312}
313
314void Qgs3DAxis::constructAxisScene( Qt3DCore::QEntity *parent3DScene )
315{
316 mAxisSceneEntity = new Qt3DCore::QEntity;
317 mAxisSceneEntity->setParent( parent3DScene );
318 mAxisSceneEntity->setObjectName( "3DAxis_SceneEntity" );
319
320 mAxisCamera = mRenderView->objectCamera();
321 mAxisCamera->setUpVector( QVector3D( 0.0f, 1.0f, 0.0f ) );
322 mAxisCamera->setViewCenter( QVector3D( 0.0f, 0.0f, 0.0f ) );
323 // position will be set later
324}
325
326void Qgs3DAxis::constructLabelsScene( Qt3DCore::QEntity *parent3DScene )
327{
328 mTwoDLabelSceneEntity = new Qt3DCore::QEntity;
329 mTwoDLabelSceneEntity->setParent( parent3DScene );
330 mTwoDLabelSceneEntity->setEnabled( true );
331
332 mTwoDLabelCamera = mRenderView->labelCamera();
333 mTwoDLabelCamera->setUpVector( QVector3D( 0.0f, 1.0f, 0.0f ) );
334 mTwoDLabelCamera->setViewCenter( QVector3D( 0.0f, 0.0f, 0.0f ) );
335 mTwoDLabelCamera->setPosition( QVector3D( 0.0f, 0.0f, 100.0f ) );
336}
337
338QVector3D Qgs3DAxis::from3DTo2DLabelPosition( const QVector3D &sourcePos, Qt3DRender::QCamera *sourceCamera, Qt3DRender::QCamera *destCamera )
339{
340 const int viewportWidth = static_cast<int>( std::round( mTwoDLabelCamera->lens()->right() - mTwoDLabelCamera->lens()->left() ) );
341 const int viewportHeight = static_cast<int>( std::round( mTwoDLabelCamera->lens()->top() - mTwoDLabelCamera->lens()->bottom() ) );
342 QRect viewportRect( static_cast<int>( std::round( mTwoDLabelCamera->lens()->left() ) ), static_cast<int>( std::round( mTwoDLabelCamera->lens()->bottom() ) ), viewportWidth, viewportHeight );
343
344 QVector3D destPos = sourcePos.project( sourceCamera->viewMatrix(), destCamera->projectionMatrix(), viewportRect );
345 destPos.setZ( 0.0f );
346 return destPos;
347}
348
349void Qgs3DAxis::setEnableCube( bool show )
350{
351 mCubeRoot->setEnabled( show );
352 if ( show )
353 {
354 mCubeRoot->setParent( mAxisSceneEntity );
355 }
356 else
357 {
358 mCubeRoot->setParent( static_cast<Qt3DCore::QEntity *>( nullptr ) );
359 }
360}
361
362void Qgs3DAxis::setEnableAxis( bool show )
363{
364 mAxisRoot->setEnabled( show );
365 if ( show )
366 {
367 mAxisRoot->setParent( mAxisSceneEntity );
368 }
369 else
370 {
371 mAxisRoot->setParent( static_cast<Qt3DCore::QEntity *>( nullptr ) );
372 }
373
374 mTextX->setEnabled( show );
375 mTextY->setEnabled( show );
376 mTextZ->setEnabled( show );
377}
378
379void Qgs3DAxis::createAxisScene()
380{
381 if ( !mAxisRoot || !mCubeRoot )
382 {
383 mAxisRoot = new Qt3DCore::QEntity;
384 mAxisRoot->setParent( mAxisSceneEntity );
385 mAxisRoot->setObjectName( "3DAxis_AxisRoot" );
386 mAxisRoot->addComponent( mRenderView->objectLayer() ); // raycaster will filter object containing this layer
387
388 createAxis( Qt::Axis::XAxis );
389 createAxis( Qt::Axis::YAxis );
390 createAxis( Qt::Axis::ZAxis );
391
392 mCubeRoot = new Qt3DCore::QEntity;
393 mCubeRoot->setParent( mAxisSceneEntity );
394 mCubeRoot->setObjectName( "3DAxis_CubeRoot" );
395 mCubeRoot->addComponent( mRenderView->objectLayer() ); // raycaster will filter object containing this layer
396
397 createCube();
398 }
399
400 Qgs3DAxisSettings::Mode mode = mMapSettings->get3DAxisSettings().mode();
401
402 if ( mode == Qgs3DAxisSettings::Mode::Off )
403 {
404 mAxisSceneEntity->setEnabled( false );
405 setEnableAxis( false );
406 setEnableCube( false );
407 mRenderView->setEnabled( false );
408 }
409 else
410 {
411 mRenderView->setEnabled( true );
412 mAxisSceneEntity->setEnabled( true );
413 if ( mode == Qgs3DAxisSettings::Mode::Crs )
414 {
415 setEnableCube( false );
416 setEnableAxis( true );
417
418 const QList<Qgis::CrsAxisDirection> axisDirections = mCrs.axisOrdering();
419
420 if ( axisDirections.length() > 0 )
421 mTextX->setText( QgsCoordinateReferenceSystemUtils::axisDirectionToAbbreviatedString( axisDirections.at( 0 ) ) );
422 else
423 mTextX->setText( "X?" );
424
425 if ( axisDirections.length() > 1 )
426 mTextY->setText( QgsCoordinateReferenceSystemUtils::axisDirectionToAbbreviatedString( axisDirections.at( 1 ) ) );
427 else
428 mTextY->setText( "Y?" );
429
430 if ( axisDirections.length() > 2 )
431 mTextZ->setText( QgsCoordinateReferenceSystemUtils::axisDirectionToAbbreviatedString( axisDirections.at( 2 ) ) );
432 else
433 mTextZ->setText( u"up"_s );
434 }
435 else if ( mode == Qgs3DAxisSettings::Mode::Cube )
436 {
437 setEnableCube( true );
438 setEnableAxis( false );
439 }
440 else
441 {
442 setEnableCube( false );
443 setEnableAxis( true );
444 mTextX->setText( "X?" );
445 mTextY->setText( "Y?" );
446 mTextZ->setText( "Z?" );
447 }
448
449 updateAxisLabelPosition();
450 }
451}
452
453void Qgs3DAxis::createMenu()
454{
455 mMenu = new QMenu();
456
457 // axis type menu
458 QAction *typeOffAct = new QAction( tr( "&Off" ), mMenu );
459 typeOffAct->setCheckable( true );
460 typeOffAct->setStatusTip( tr( "Disable 3D axis" ) );
461 connect( mMapSettings, &Qgs3DMapSettings::axisSettingsChanged, this, [typeOffAct, this]() {
462 if ( mMapSettings->get3DAxisSettings().mode() == Qgs3DAxisSettings::Mode::Off )
463 typeOffAct->setChecked( true );
464 } );
465
466 QAction *typeCrsAct = new QAction( tr( "Coordinate Reference &System" ), mMenu );
467 typeCrsAct->setCheckable( true );
468 typeCrsAct->setStatusTip( tr( "Coordinate Reference System 3D axis" ) );
469 connect( mMapSettings, &Qgs3DMapSettings::axisSettingsChanged, this, [typeCrsAct, this]() {
470 if ( mMapSettings->get3DAxisSettings().mode() == Qgs3DAxisSettings::Mode::Crs )
471 typeCrsAct->setChecked( true );
472 } );
473
474 QAction *typeCubeAct = new QAction( tr( "&Cube" ), mMenu );
475 typeCubeAct->setCheckable( true );
476 typeCubeAct->setStatusTip( tr( "Cube 3D axis" ) );
477 connect( mMapSettings, &Qgs3DMapSettings::axisSettingsChanged, this, [typeCubeAct, this]() {
478 if ( mMapSettings->get3DAxisSettings().mode() == Qgs3DAxisSettings::Mode::Cube )
479 typeCubeAct->setChecked( true );
480 } );
481
482 QActionGroup *typeGroup = new QActionGroup( mMenu );
483 typeGroup->addAction( typeOffAct );
484 typeGroup->addAction( typeCrsAct );
485 typeGroup->addAction( typeCubeAct );
486
487 connect( typeOffAct, &QAction::triggered, this, [this]( bool ) { onAxisModeChanged( Qgs3DAxisSettings::Mode::Off ); } );
488 connect( typeCrsAct, &QAction::triggered, this, [this]( bool ) { onAxisModeChanged( Qgs3DAxisSettings::Mode::Crs ); } );
489 connect( typeCubeAct, &QAction::triggered, this, [this]( bool ) { onAxisModeChanged( Qgs3DAxisSettings::Mode::Cube ); } );
490
491 QMenu *typeMenu = new QMenu( u"Axis Type"_s, mMenu );
492 Q_ASSERT( typeMenu );
493 typeMenu->addAction( typeOffAct );
494 typeMenu->addAction( typeCrsAct );
495 typeMenu->addAction( typeCubeAct );
496 mMenu->addMenu( typeMenu );
497
498 // horizontal position menu
499 QAction *hPosLeftAct = new QAction( tr( "&Left" ), mMenu );
500 hPosLeftAct->setCheckable( true );
501 connect( mMapSettings, &Qgs3DMapSettings::axisSettingsChanged, this, [hPosLeftAct, this]() {
502 if ( mMapSettings->get3DAxisSettings().horizontalPosition() == Qt::AnchorPoint::AnchorLeft )
503 hPosLeftAct->setChecked( true );
504 } );
505
506 QAction *hPosMiddleAct = new QAction( tr( "&Center" ), mMenu );
507 hPosMiddleAct->setCheckable( true );
508 connect( mMapSettings, &Qgs3DMapSettings::axisSettingsChanged, this, [hPosMiddleAct, this]() {
509 if ( mMapSettings->get3DAxisSettings().horizontalPosition() == Qt::AnchorPoint::AnchorHorizontalCenter )
510 hPosMiddleAct->setChecked( true );
511 } );
512
513 QAction *hPosRightAct = new QAction( tr( "&Right" ), mMenu );
514 hPosRightAct->setCheckable( true );
515 connect( mMapSettings, &Qgs3DMapSettings::axisSettingsChanged, this, [hPosRightAct, this]() {
516 if ( mMapSettings->get3DAxisSettings().horizontalPosition() == Qt::AnchorPoint::AnchorRight )
517 hPosRightAct->setChecked( true );
518 } );
519
520 QActionGroup *hPosGroup = new QActionGroup( mMenu );
521 hPosGroup->addAction( hPosLeftAct );
522 hPosGroup->addAction( hPosMiddleAct );
523 hPosGroup->addAction( hPosRightAct );
524
525 connect( hPosLeftAct, &QAction::triggered, this, [this]( bool ) { mRenderView->onHorizontalPositionChanged( Qt::AnchorPoint::AnchorLeft ); } );
526 connect( hPosMiddleAct, &QAction::triggered, this, [this]( bool ) { mRenderView->onHorizontalPositionChanged( Qt::AnchorPoint::AnchorHorizontalCenter ); } );
527 connect( hPosRightAct, &QAction::triggered, this, [this]( bool ) { mRenderView->onHorizontalPositionChanged( Qt::AnchorPoint::AnchorRight ); } );
528
529 QMenu *horizPosMenu = new QMenu( u"Horizontal Position"_s, mMenu );
530 horizPosMenu->addAction( hPosLeftAct );
531 horizPosMenu->addAction( hPosMiddleAct );
532 horizPosMenu->addAction( hPosRightAct );
533 mMenu->addMenu( horizPosMenu );
534
535 // vertical position menu
536 QAction *vPosTopAct = new QAction( tr( "&Top" ), mMenu );
537 vPosTopAct->setCheckable( true );
538 connect( mMapSettings, &Qgs3DMapSettings::axisSettingsChanged, this, [vPosTopAct, this]() {
539 if ( mMapSettings->get3DAxisSettings().verticalPosition() == Qt::AnchorPoint::AnchorTop )
540 vPosTopAct->setChecked( true );
541 } );
542
543 QAction *vPosMiddleAct = new QAction( tr( "&Middle" ), mMenu );
544 vPosMiddleAct->setCheckable( true );
545 connect( mMapSettings, &Qgs3DMapSettings::axisSettingsChanged, this, [vPosMiddleAct, this]() {
546 if ( mMapSettings->get3DAxisSettings().verticalPosition() == Qt::AnchorPoint::AnchorVerticalCenter )
547 vPosMiddleAct->setChecked( true );
548 } );
549
550 QAction *vPosBottomAct = new QAction( tr( "&Bottom" ), mMenu );
551 vPosBottomAct->setCheckable( true );
552 connect( mMapSettings, &Qgs3DMapSettings::axisSettingsChanged, this, [vPosBottomAct, this]() {
553 if ( mMapSettings->get3DAxisSettings().verticalPosition() == Qt::AnchorPoint::AnchorBottom )
554 vPosBottomAct->setChecked( true );
555 } );
556
557 QActionGroup *vPosGroup = new QActionGroup( mMenu );
558 vPosGroup->addAction( vPosTopAct );
559 vPosGroup->addAction( vPosMiddleAct );
560 vPosGroup->addAction( vPosBottomAct );
561
562 connect( vPosTopAct, &QAction::triggered, this, [this]( bool ) { mRenderView->onVerticalPositionChanged( Qt::AnchorPoint::AnchorTop ); } );
563 connect( vPosMiddleAct, &QAction::triggered, this, [this]( bool ) { mRenderView->onVerticalPositionChanged( Qt::AnchorPoint::AnchorVerticalCenter ); } );
564 connect( vPosBottomAct, &QAction::triggered, this, [this]( bool ) { mRenderView->onVerticalPositionChanged( Qt::AnchorPoint::AnchorBottom ); } );
565
566 QMenu *vertPosMenu = new QMenu( u"Vertical Position"_s, mMenu );
567 vertPosMenu->addAction( vPosTopAct );
568 vertPosMenu->addAction( vPosMiddleAct );
569 vertPosMenu->addAction( vPosBottomAct );
570 mMenu->addMenu( vertPosMenu );
571
572 // axis view menu
573 // Make sure to sync the key combinations with QgsCameraController::keyboardEventFilter()!
574 QAction *viewHomeAct = new QAction( tr( "&Home" ) + "\t Ctrl+5", mMenu );
575 QAction *viewTopAct = new QAction( tr( "&Top" ) + "\t Ctrl+9", mMenu );
576 QAction *viewNorthAct = new QAction( tr( "&North" ) + "\t Ctrl+8", mMenu );
577 QAction *viewEastAct = new QAction( tr( "&East" ) + "\t Ctrl+6", mMenu );
578 QAction *viewSouthAct = new QAction( tr( "&South" ) + "\t Ctrl+2", mMenu );
579 QAction *viewWestAct = new QAction( tr( "&West" ) + "\t Ctrl+4", mMenu );
580 QAction *viewBottomAct = new QAction( tr( "&Bottom" ) + "\t Ctrl+3", mMenu );
581
582 connect( viewHomeAct, &QAction::triggered, mCameraController, &QgsCameraController::rotateCameraToHome );
583 connect( viewTopAct, &QAction::triggered, mCameraController, &QgsCameraController::rotateCameraToTop );
584 connect( viewNorthAct, &QAction::triggered, mCameraController, &QgsCameraController::rotateCameraToNorth );
585 connect( viewEastAct, &QAction::triggered, mCameraController, &QgsCameraController::rotateCameraToEast );
586 connect( viewSouthAct, &QAction::triggered, mCameraController, &QgsCameraController::rotateCameraToSouth );
587 connect( viewWestAct, &QAction::triggered, mCameraController, &QgsCameraController::rotateCameraToWest );
588 connect( viewBottomAct, &QAction::triggered, mCameraController, &QgsCameraController::rotateCameraToBottom );
589
590 QMenu *viewMenu = new QMenu( u"Camera View"_s, mMenu );
591 viewMenu->addAction( viewHomeAct );
592 viewMenu->addAction( viewTopAct );
593 viewMenu->addAction( viewNorthAct );
594 viewMenu->addAction( viewEastAct );
595 viewMenu->addAction( viewSouthAct );
596 viewMenu->addAction( viewWestAct );
597 viewMenu->addAction( viewBottomAct );
598 mMenu->addMenu( viewMenu );
599
600 // update checkable items
601 mMapSettings->set3DAxisSettings( mMapSettings->get3DAxisSettings(), true );
602}
603
604void Qgs3DAxis::hideMenu()
605{
606 if ( mMenu && mMenu->isVisible() )
607 mMenu->hide();
608}
609
610void Qgs3DAxis::displayMenuAt( const QPoint &sourcePos )
611{
612 if ( !mMenu )
613 {
614 createMenu();
615 }
616
617 mMenu->popup( mCanvas->mapToGlobal( sourcePos ) );
618}
619
620void Qgs3DAxis::onAxisModeChanged( Qgs3DAxisSettings::Mode mode )
621{
622 Qgs3DAxisSettings s = mMapSettings->get3DAxisSettings();
623 s.setMode( mode );
624 mMapSettings->set3DAxisSettings( s );
625}
626
627void Qgs3DAxis::createCube()
628{
629 QVector3D minPos = QVector3D( -mCylinderLength * 0.5f, -mCylinderLength * 0.5f, -mCylinderLength * 0.5f );
630
631 // cube outlines
632 Qt3DCore::QEntity *cubeLineEntity = new Qt3DCore::QEntity( mCubeRoot );
633 cubeLineEntity->setObjectName( "3DAxis_cubeline" );
634 Qgs3DWiredMesh *cubeLine = new Qgs3DWiredMesh;
635 QgsAABB box = QgsAABB( -mCylinderLength * 0.5f, -mCylinderLength * 0.5f, -mCylinderLength * 0.5f, mCylinderLength * 0.5f, mCylinderLength * 0.5f, mCylinderLength * 0.5f );
636 cubeLine->setVertices( box.verticesForLines() );
637 cubeLineEntity->addComponent( cubeLine );
638
639 Qt3DExtras::QPhongMaterial *cubeLineMaterial = new Qt3DExtras::QPhongMaterial;
640 cubeLineMaterial->setAmbient( Qt::white );
641 cubeLineEntity->addComponent( cubeLineMaterial );
642
643 // cube mesh
644 Qt3DExtras::QCuboidMesh *cubeMesh = new Qt3DExtras::QCuboidMesh;
645 cubeMesh->setObjectName( "3DAxis_cubemesh" );
646 cubeMesh->setXExtent( mCylinderLength );
647 cubeMesh->setYExtent( mCylinderLength );
648 cubeMesh->setZExtent( mCylinderLength );
649 mCubeRoot->addComponent( cubeMesh );
650
651 Qt3DExtras::QPhongMaterial *cubeMaterial = new Qt3DExtras::QPhongMaterial( mCubeRoot );
652 cubeMaterial->setAmbient( QColor( 100, 100, 100, 50 ) );
653 cubeMaterial->setShininess( 100 );
654 mCubeRoot->addComponent( cubeMaterial );
655
656 Qt3DCore::QTransform *cubeTransform = new Qt3DCore::QTransform;
657 QMatrix4x4 transformMatrixcube;
658 //transformMatrixcube.rotate( rotation );
659 transformMatrixcube.translate( minPos + QVector3D( mCylinderLength * 0.5f, mCylinderLength * 0.5f, mCylinderLength * 0.5f ) );
660 cubeTransform->setMatrix( transformMatrixcube );
661 mCubeRoot->addComponent( cubeTransform );
662
663 // text
664 QString text;
665 const int fontSize = static_cast<int>( std::round( 0.75f * static_cast<float>( mFontSize ) ) );
666 const float textHeight = static_cast<float>( fontSize ) * 1.5f;
667 float textWidth;
668 const QFont font = createFont( fontSize );
669
670 {
671 text = u"top"_s;
672 textWidth = static_cast<float>( text.length() * fontSize ) * 0.75f;
673 QVector3D translation = minPos + QVector3D( mCylinderLength * 0.5f - textWidth / 2.0f, mCylinderLength * 0.5f - textHeight / 2.0f, mCylinderLength * 1.01f );
674 QMatrix4x4 rotation;
675 mCubeLabels << addCubeText( text, textHeight, textWidth, font, rotation, translation );
676 }
677
678 {
679 text = u"btm"_s;
680 textWidth = static_cast<float>( text.length() * fontSize ) * 0.75f;
681 QVector3D translation = minPos + QVector3D( mCylinderLength * 0.5f - textWidth / 2.0f, mCylinderLength * 0.5f + textHeight / 2.0f, -mCylinderLength * 0.01f );
682 QMatrix4x4 rotation;
683 rotation.rotate( 180.0f, QVector3D( 1.0f, 0.0f, 0.0f ).normalized() );
684 mCubeLabels << addCubeText( text, textHeight, textWidth, font, rotation, translation );
685 }
686
687 {
688 text = u"west"_s;
689 textWidth = static_cast<float>( text.length() * fontSize ) * 0.75f;
690 QVector3D translation = minPos + QVector3D( -mCylinderLength * 0.01f, mCylinderLength * 0.5f + textWidth / 2.0f, mCylinderLength * 0.5f - textHeight / 2.0f );
691 QMatrix4x4 rotation;
692 rotation.rotate( 90.0f, QVector3D( 0.0f, -1.0f, 0.0f ).normalized() );
693 rotation.rotate( 90.0f, QVector3D( 0.0f, 0.0f, -1.0f ).normalized() );
694 mCubeLabels << addCubeText( text, textHeight, textWidth, font, rotation, translation );
695 }
696
697 {
698 text = u"east"_s;
699 textWidth = static_cast<float>( text.length() * fontSize ) * 0.75f;
700 QVector3D translation = minPos + QVector3D( mCylinderLength * 1.01f, mCylinderLength * 0.5f - textWidth / 2.0f, mCylinderLength * 0.5f - textHeight / 2.0f );
701 QMatrix4x4 rotation;
702 rotation.rotate( 90.0f, QVector3D( 0.0f, 1.0f, 0.0f ).normalized() );
703 rotation.rotate( 90.0f, QVector3D( 0.0f, 0.0f, 1.0f ).normalized() );
704 mCubeLabels << addCubeText( text, textHeight, textWidth, font, rotation, translation );
705 }
706
707 {
708 text = u"south"_s;
709 textWidth = static_cast<float>( text.length() * fontSize ) * 0.75f;
710 QVector3D translation = minPos + QVector3D( mCylinderLength * 0.5f - textWidth / 2.0f, -mCylinderLength * 0.01f, mCylinderLength * 0.5f - textHeight / 2.0f );
711 QMatrix4x4 rotation;
712 rotation.rotate( 90.0f, QVector3D( 1.0f, 0.0f, 0.0f ).normalized() );
713 mCubeLabels << addCubeText( text, textHeight, textWidth, font, rotation, translation );
714 }
715
716 {
717 text = u"north"_s;
718 textWidth = static_cast<float>( text.length() * fontSize ) * 0.75f;
719 QVector3D translation = minPos + QVector3D( mCylinderLength * 0.5f + textWidth / 2.0f, mCylinderLength * 1.01f, mCylinderLength * 0.5f - textHeight / 2.0f );
720 QMatrix4x4 rotation;
721 rotation.rotate( 90.0f, QVector3D( -1.0f, 0.0f, 0.0f ).normalized() );
722 rotation.rotate( 180.0f, QVector3D( 0.0f, 0.0f, 1.0f ).normalized() );
723 mCubeLabels << addCubeText( text, textHeight, textWidth, font, rotation, translation );
724 }
725
726 for ( Qt3DExtras::QText2DEntity *l : std::as_const( mCubeLabels ) )
727 {
728 l->setParent( mCubeRoot );
729 }
730}
731
732Qt3DExtras::QText2DEntity *Qgs3DAxis::addCubeText( const QString &text, float textHeight, float textWidth, const QFont &font, const QMatrix4x4 &rotation, const QVector3D &translation )
733{
734 Qt3DExtras::QText2DEntity *textEntity = new Qt3DExtras::QText2DEntity;
735 textEntity->setObjectName( "3DAxis_cube_label_" + text );
736 textEntity->setFont( font );
737 textEntity->setHeight( textHeight );
738 textEntity->setWidth( textWidth );
739 textEntity->setColor( QColor( 192, 192, 192 ) );
740 textEntity->setText( text );
741
742 Qt3DCore::QTransform *textFrontTransform = new Qt3DCore::QTransform();
743 textFrontTransform->setMatrix( rotation );
744 textFrontTransform->setTranslation( translation );
745 textEntity->addComponent( textFrontTransform );
746
747 return textEntity;
748}
749
750void Qgs3DAxis::createAxis( Qt::Axis axisType )
751{
752 float cylinderRadius = 0.05f * mCylinderLength;
753 float coneLength = 0.3f * mCylinderLength;
754 float coneBottomRadius = 0.1f * mCylinderLength;
755
756 QQuaternion rotation;
757 QColor color;
758
759 Qt3DExtras::QText2DEntity *text = nullptr;
760 Qt3DCore::QTransform *textTransform = nullptr;
761 QString name;
762
763 switch ( axisType )
764 {
765 case Qt::Axis::XAxis:
766 mTextX = new Qt3DExtras::QText2DEntity(); // object initialization in two step:
767 mTextX->setParent( mTwoDLabelSceneEntity ); // see https://bugreports.qt.io/browse/QTBUG-77139
768 connect( mTextX, &Qt3DExtras::QText2DEntity::textChanged, this, [this]( const QString &text ) {
769 updateAxisLabelText( mTextX, text );
770 } );
771 mTextTransformX = new Qt3DCore::QTransform();
772 mTextCoordX = QVector3D( mCylinderLength + coneLength / 2.0f, 0.0f, 0.0f );
773
774 rotation = QQuaternion::fromAxisAndAngle( QVector3D( 0.0f, 0.0f, 1.0f ), -90.0f );
775 color = Qt::red;
776 text = mTextX;
777 textTransform = mTextTransformX;
778 name = "3DAxis_axisX";
779 break;
780
781 case Qt::Axis::YAxis:
782 mTextY = new Qt3DExtras::QText2DEntity(); // object initialization in two step:
783 mTextY->setParent( mTwoDLabelSceneEntity ); // see https://bugreports.qt.io/browse/QTBUG-77139
784 connect( mTextY, &Qt3DExtras::QText2DEntity::textChanged, this, [this]( const QString &text ) {
785 updateAxisLabelText( mTextY, text );
786 } );
787 mTextTransformY = new Qt3DCore::QTransform();
788 mTextCoordY = QVector3D( 0.0f, mCylinderLength + coneLength / 2.0f, 0.0f );
789
790 // no rotation
791
792 color = Qt::green;
793 text = mTextY;
794 textTransform = mTextTransformY;
795 name = "3DAxis_axisY";
796 break;
797
798 case Qt::Axis::ZAxis:
799 mTextZ = new Qt3DExtras::QText2DEntity(); // object initialization in two step:
800 mTextZ->setParent( mTwoDLabelSceneEntity ); // see https://bugreports.qt.io/browse/QTBUG-77139
801 connect( mTextZ, &Qt3DExtras::QText2DEntity::textChanged, this, [this]( const QString &text ) {
802 updateAxisLabelText( mTextZ, text );
803 } );
804 mTextTransformZ = new Qt3DCore::QTransform();
805 mTextCoordZ = QVector3D( 0.0f, 0.0f, mCylinderLength + coneLength / 2.0f );
806
807 rotation = QQuaternion::fromAxisAndAngle( QVector3D( 1.0f, 0.0f, 0.0f ), 90.0f );
808 color = Qt::blue;
809 text = mTextZ;
810 textTransform = mTextTransformZ;
811 name = "3DAxis_axisZ";
812 break;
813
814 default:
815 return;
816 }
817
818 // cylinder
819 Qt3DCore::QEntity *cylinder = new Qt3DCore::QEntity( mAxisRoot );
820 cylinder->setObjectName( name );
821
822 Qt3DExtras::QCylinderMesh *cylinderMesh = new Qt3DExtras::QCylinderMesh;
823 cylinderMesh->setRadius( cylinderRadius );
824 cylinderMesh->setLength( mCylinderLength );
825 cylinderMesh->setRings( 10 );
826 cylinderMesh->setSlices( 4 );
827 cylinder->addComponent( cylinderMesh );
828
829 Qt3DExtras::QPhongMaterial *cylinderMaterial = new Qt3DExtras::QPhongMaterial( cylinder );
830 cylinderMaterial->setAmbient( color );
831 cylinderMaterial->setShininess( 0 );
832 cylinder->addComponent( cylinderMaterial );
833
834 Qt3DCore::QTransform *cylinderTransform = new Qt3DCore::QTransform;
835 QMatrix4x4 transformMatrixCylinder;
836 transformMatrixCylinder.rotate( rotation );
837 transformMatrixCylinder.translate( QVector3D( 0.0f, mCylinderLength / 2.0f, 0.0f ) );
838 cylinderTransform->setMatrix( transformMatrixCylinder );
839 cylinder->addComponent( cylinderTransform );
840
841 // cone
842 Qt3DCore::QEntity *coneEntity = new Qt3DCore::QEntity( mAxisRoot );
843 coneEntity->setObjectName( name );
844 Qt3DExtras::QConeMesh *coneMesh = new Qt3DExtras::QConeMesh;
845 coneMesh->setLength( coneLength );
846 coneMesh->setBottomRadius( coneBottomRadius );
847 coneMesh->setTopRadius( 0.0f );
848 coneMesh->setRings( 10 );
849 coneMesh->setSlices( 4 );
850 coneEntity->addComponent( coneMesh );
851
852 Qt3DExtras::QPhongMaterial *coneMaterial = new Qt3DExtras::QPhongMaterial( coneEntity );
853 coneMaterial->setAmbient( color );
854 coneMaterial->setShininess( 0 );
855 coneEntity->addComponent( coneMaterial );
856
857 Qt3DCore::QTransform *coneTransform = new Qt3DCore::QTransform;
858 QMatrix4x4 transformMatrixCone;
859 transformMatrixCone.rotate( rotation );
860 transformMatrixCone.translate( QVector3D( 0.0f, mCylinderLength, 0.0f ) );
861 coneTransform->setMatrix( transformMatrixCone );
862 coneEntity->addComponent( coneTransform );
863
864 // text font, height and width will be set later in onText?Changed
865 text->setColor( QColor( 192, 192, 192, 192 ) );
866 text->addComponent( textTransform );
867}
868
870{
871 createAxisScene();
872 onAxisViewportSizeUpdate();
873}
874
875void Qgs3DAxis::onAxisViewportSizeUpdate()
876{
877 mRenderView->onViewportSizeUpdate(); // will call onViewportScaleFactorChanged as callback
878
879 // mRenderView->onViewportSizeUpdate() has updated `mTwoDLabelCamera` lens parameters.
880 // The position of the labels needs to be updated.
881 const Qgs3DAxisSettings axisSettings = mMapSettings->get3DAxisSettings();
882 if ( axisSettings.mode() == Qgs3DAxisSettings::Mode::Crs && mAxisRoot->isEnabled() )
883 {
884 updateAxisLabelPosition();
885 }
886}
887
889{
890 // if the axis scene has not been created, don't do anything
891 if ( !mAxisRoot || !mCubeRoot )
892 {
893 return;
894 }
895
896 if ( scaleFactor > 0.0 )
897 {
898 Qgs3DAxisSettings settings = mMapSettings->get3DAxisSettings();
899 if ( settings.mode() == Qgs3DAxisSettings::Mode::Crs )
900 setEnableAxis( true );
901 else if ( settings.mode() == Qgs3DAxisSettings::Mode::Cube )
902 setEnableCube( true );
903
904 mAxisScaleFactor = scaleFactor;
905 QgsDebugMsgLevel( QString( "3DAxis viewport mAxisScaleFactor %1" ).arg( mAxisScaleFactor ), 2 );
906 }
907 else
908 {
909 setEnableCube( false );
910 setEnableAxis( false );
911 }
912}
913
914void Qgs3DAxis::onCameraUpdate()
915{
916 Qt3DRender::QCamera *parentCamera = mCameraController->camera();
917
918 if ( parentCamera->viewVector() != mPreviousVector
919 && !std::isnan( parentCamera->viewVector().x() )
920 && !std::isnan( parentCamera->viewVector().y() )
921 && !std::isnan( parentCamera->viewVector().z() ) )
922 {
923 mPreviousVector = parentCamera->viewVector();
924
925 QQuaternion q = QQuaternion::fromDirection( -parentCamera->viewVector(), parentCamera->upVector() );
926 mAxisCamera->setPosition( q.rotatedVector( QVector3D( 0, 0, mCylinderLength * 9.0f ) ) );
927 mAxisCamera->setUpVector( q.rotatedVector( QVector3D( 0, 1, 0 ) ) );
928
929 if ( mAxisRoot->isEnabled() )
930 {
931 updateAxisLabelPosition();
932 }
933 }
934}
935
936void Qgs3DAxis::updateAxisLabelPosition()
937{
938 if ( mTextTransformX && mTextTransformY && mTextTransformZ )
939 {
940 mTextTransformX->setTranslation( from3DTo2DLabelPosition( mTextCoordX * static_cast<float>( mAxisScaleFactor ), mAxisCamera, mTwoDLabelCamera ) );
941 updateAxisLabelText( mTextX, mTextX->text() );
942
943 mTextTransformY->setTranslation( from3DTo2DLabelPosition( mTextCoordY * static_cast<float>( mAxisScaleFactor ), mAxisCamera, mTwoDLabelCamera ) );
944 updateAxisLabelText( mTextY, mTextY->text() );
945
946 mTextTransformZ->setTranslation( from3DTo2DLabelPosition( mTextCoordZ * static_cast<float>( mAxisScaleFactor ), mAxisCamera, mTwoDLabelCamera ) );
947 updateAxisLabelText( mTextZ, mTextZ->text() );
948 }
949}
950
951void Qgs3DAxis::updateAxisLabelText( Qt3DExtras::QText2DEntity *textEntity, const QString &text )
952{
953 const float scaledFontSize = static_cast<float>( mAxisScaleFactor ) * static_cast<float>( mFontSize );
954 const QFont font = createFont( static_cast<int>( std::round( scaledFontSize ) ) );
955 textEntity->setFont( font );
956 textEntity->setWidth( scaledFontSize * static_cast<float>( text.length() ) );
957 textEntity->setHeight( 1.5f * scaledFontSize );
958}
959
960QFont Qgs3DAxis::createFont( int pointSize )
961{
962 QFont font = QFontDatabase::systemFont( QFontDatabase::FixedFont );
963 font.setPointSize( pointSize );
964 font.setWeight( QFont::Weight::Black );
965 font.setStyleStrategy( QFont::StyleStrategy::ForceOutline );
966 return font;
967}
3D axis render view.
Qt3DRender::QLayer * objectLayer() const
Returns main object layer.
void onViewportSizeUpdate(int width=-1, int height=-1)
Updates viewport size. Uses canvas size by default.
Contains the configuration of a 3d axis.
void setMode(Qgs3DAxisSettings::Mode type)
Sets the type of the 3daxis.
Mode
Axis representation enum.
@ Crs
Respect CRS directions.
@ Cube
Abstract cube mode.
Qgs3DAxisSettings::Mode mode() const
Returns the type of the 3daxis.
bool handleEvent(QEvent *event)
Returns if the 3D axis controller will handle the specified event.
~Qgs3DAxis() override
Definition qgs3daxis.cpp:78
void onAxisSettingsChanged()
Force update of the axis and the viewport when a setting has changed.
void onViewportScaleFactorChanged(double scaleFactor)
Used as callback from renderview when viewport scale factor changes.
QVector3D from3DTo2DLabelPosition(const QVector3D &sourcePos, Qt3DRender::QCamera *sourceCamera, Qt3DRender::QCamera *destCamera)
Project a 3D position from sourceCamera to a 2D position for destCamera.
Qgs3DAxis(Qgs3DMapCanvas *canvas, Qt3DCore::QEntity *parent3DScene, Qgs3DMapScene *mapScene, QgsCameraController *camera, Qgs3DMapSettings *map)
Default Qgs3DAxis constructor.
Definition qgs3daxis.cpp:47
Convenience wrapper to simplify the creation of a 3D window ready to be used with QGIS.
Entity that encapsulates our 3D scene - contains all other entities (such as terrain) as children.
QgsAbstract3DEngine * engine() const
Returns the abstract 3D engine.
Definition of the world.
Qgs3DAxisSettings get3DAxisSettings() const
Returns the current configuration of 3d axis.
void axisSettingsChanged()
Emitted when 3d axis rendering settings are changed.
QList< QVector3D > verticesForLines() const
Returns a list of pairs of vertices (useful for display of bounding boxes).
Definition qgsaabb.cpp:63
virtual Qt3DRender::QRenderSettings * renderSettings()=0
Returns access to the engine's render settings (the frame graph can be accessed from here).
Object that controls camera movement based on user input.
void rotateCameraToBottom()
Rotate to bottom-up view.
Qt3DRender::QCamera * camera() const
Returns camera that is being controlled.
void rotateCameraToTop()
Rotate to top-down view.
void rotateCameraToEast()
Rotate to view from the east.
void rotateCameraToHome()
Rotate to diagonal view.
void rotateCameraToNorth()
Rotate to view from the north.
void rotateCameraToWest()
Rotate to view from the west.
void cameraChanged()
Emitted when camera has been updated.
void rotateCameraToSouth()
Rotate to view from the south.
static QString axisDirectionToAbbreviatedString(Qgis::CrsAxisDirection axis)
Returns a translated abbreviation representing an axis direction.
static const QString AXIS3D_RENDERVIEW
static int debugLevel()
Reads the environment variable QGIS_DEBUG and converts it to int.
Definition qgslogger.h:146
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:63