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