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