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