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