QGIS API Documentation 3.43.0-Master (32433f7016e)
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#include "moc_qgs3daxis.cpp"
18
19#include <Qt3DCore/QTransform>
20#include <Qt3DExtras/QCylinderMesh>
21#include <Qt3DExtras/QPhongMaterial>
22#include <Qt3DExtras/QConeMesh>
23#include <Qt3DRender/qcameralens.h>
24#include <Qt3DRender/QCameraSelector>
25#include <Qt3DRender/QClearBuffers>
26#include <Qt3DRender/QLayer>
27#include <Qt3DRender/QLayerFilter>
28#include <Qt3DRender/QPointLight>
29#include <Qt3DRender/QSortPolicy>
30#include <QWidget>
31#include <QScreen>
32#include <QShortcut>
33#include <QFontDatabase>
34#include <ctime>
35#include <QApplication>
36#include <QActionGroup>
37
38#include "qgsmapsettings.h"
39#include "qgs3dmapsettings.h"
40#include "qgs3dmapscene.h"
41#include "qgsterrainentity.h"
44#include "qgswindow3dengine.h"
46#include "qgs3dwiredmesh_p.h"
47#include "qgsframegraph.h"
49
50Qgs3DAxis::Qgs3DAxis( Qgs3DMapCanvas *canvas, Qt3DCore::QEntity *parent3DScene, Qgs3DMapScene *mapScene, //
51 QgsCameraController *cameraCtrl, Qgs3DMapSettings *map )
52 : QObject( canvas )
53 , mMapSettings( map )
54 , mCanvas( canvas )
55 , mMapScene( mapScene )
56 , mCameraController( cameraCtrl )
57 , mCrs( map->crs() )
58{
59 mMapScene->engine()->frameGraph()->registerRenderView( std::make_unique<Qgs3DAxisRenderView>( //
61 mCanvas, mCameraController, mMapSettings, //
62 this
63 ),
65
66 mRenderView = dynamic_cast<Qgs3DAxisRenderView *>( mMapScene->engine()->frameGraph()->renderView( QgsFrameGraph::AXIS3D_RENDERVIEW ) );
67 Q_ASSERT( mRenderView );
68 constructAxisScene( parent3DScene );
69 constructLabelsScene( parent3DScene );
70
71 mTwoDLabelSceneEntity->addComponent( mRenderView->labelLayer() );
72
73 connect( cameraCtrl, &QgsCameraController::cameraChanged, this, &Qgs3DAxis::onCameraUpdate );
74
75 createAxisScene();
76 onAxisViewportSizeUpdate();
77
78 init3DObjectPicking();
79
80 createKeyboardShortCut();
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 // we need event filter (see Qgs3DAxis::eventFilter) to handle the mouse click event as this event is not catchable via the Qt3DRender::QObjectPicker
130 mCanvas->installEventFilter( this );
131}
132
133bool Qgs3DAxis::eventFilter( QObject *watched, QEvent *event )
134{
135 if ( watched != mCanvas )
136 return false;
137
138 if ( event->type() == QEvent::MouseButtonPress )
139 {
140 // register mouse click to detect dragging
141 mHasClicked = true;
142 QMouseEvent *mouseEvent = static_cast<QMouseEvent *>( event );
143 mLastClickedPos = mouseEvent->pos();
144 }
145
146 // handle QEvent::MouseButtonRelease as it represents the end of click and QEvent::MouseMove.
147 else if ( event->type() == QEvent::MouseButtonRelease || event->type() == QEvent::MouseMove )
148 {
149 QMouseEvent *mouseEvent = static_cast<QMouseEvent *>( event );
150
151 // user has clicked and move ==> dragging start
152 if ( event->type() == QEvent::MouseMove && ( ( mHasClicked && ( mouseEvent->pos() - mLastClickedPos ).manhattanLength() < QApplication::startDragDistance() ) || mIsDragging ) )
153 {
154 mIsDragging = true;
155 }
156
157 // user has released ==> dragging ends
158 else if ( mIsDragging && event->type() == QEvent::MouseButtonRelease )
159 {
160 mIsDragging = false;
161 mHasClicked = false;
162 }
163
164 // user is moving or has released but not dragging
165 else if ( !mIsDragging )
166 {
167 // limit ray caster usage to the axis viewport
168 QPointF normalizedPos( static_cast<float>( mouseEvent->pos().x() ) / static_cast<float>( mCanvas->width() ), static_cast<float>( mouseEvent->pos().y() ) / static_cast<float>( mCanvas->height() ) );
169
170 if ( 2 <= QgsLogger::debugLevel() && event->type() == QEvent::MouseButtonRelease )
171 {
172 std::ostringstream os;
173 os << "QGS3DAxis: normalized pos: " << normalizedPos << " / viewport: " << mRenderView->viewport()->normalizedRect();
174 QgsDebugMsgLevel( os.str().c_str(), 2 );
175 }
176
177 if ( mRenderView->viewport()->normalizedRect().contains( normalizedPos ) )
178 {
179 mLastClickedButton = mouseEvent->button();
180 mLastClickedPos = mouseEvent->pos();
181
182 // if casted ray from pos matches an entity, call onTouchedByRay
183 mScreenRayCaster->trigger( mLastClickedPos );
184 }
185 // exit the viewport
186 else
187 {
188 // reset the mouse cursor if needed
189 if ( mPreviousCursor != Qt::ArrowCursor && mCanvas->cursor() == Qt::ArrowCursor )
190 {
191 mCanvas->setCursor( mPreviousCursor );
192 mPreviousCursor = Qt::ArrowCursor;
193 }
194
195 // reset the picking settings if needed
196 if ( mMapScene->engine()->renderSettings()->pickingSettings()->pickMethod() == Qt3DRender::QPickingSettings::TrianglePicking
197 && mDefaultPickingMethod != Qt3DRender::QPickingSettings::TrianglePicking )
198 {
199 mMapScene->engine()->renderSettings()->pickingSettings()->setPickMethod( mDefaultPickingMethod );
200 QgsDebugMsgLevel( "Disabling triangle picking", 2 );
201 }
202 }
203
204 mIsDragging = false; // drag ends
205 mHasClicked = false;
206 }
207 }
208
209 return false;
210}
211
212void Qgs3DAxis::onTouchedByRay( const Qt3DRender::QAbstractRayCaster::Hits &hits )
213{
214 int mHitsFound = -1;
215 if ( !hits.empty() )
216 {
217 if ( 2 <= QgsLogger::debugLevel() )
218 {
219 std::ostringstream os;
220 os << "Qgs3DAxis::onTouchedByRay " << hits.length() << " hits at pos " << mLastClickedPos << " with QButton: " << mLastClickedButton;
221 for ( int i = 0; i < hits.length(); ++i )
222 {
223 os << "\n";
224 os << "\tHit Type: " << hits.at( i ).type() << "\n";
225 os << "\tHit triangle id: " << hits.at( i ).primitiveIndex() << "\n";
226 os << "\tHit distance: " << hits.at( i ).distance() << "\n";
227 os << "\tHit entity name: " << hits.at( i ).entity()->objectName().toStdString();
228 }
229 QgsDebugMsgLevel( os.str().c_str(), 2 );
230 }
231
232 for ( int i = 0; i < hits.length() && mHitsFound == -1; ++i )
233 {
234 if ( hits.at( i ).distance() < 500.0f && hits.at( i ).entity() && ( hits.at( i ).entity() == mCubeRoot || hits.at( i ).entity() == mAxisRoot || hits.at( i ).entity()->parent() == mCubeRoot || hits.at( i ).entity()->parent() == mAxisRoot ) )
235 {
236 mHitsFound = i;
237 }
238 }
239 }
240
241 if ( mLastClickedButton == Qt::NoButton ) // hover
242 {
243 if ( mHitsFound != -1 )
244 {
245 if ( mCanvas->cursor() != Qt::ArrowCursor )
246 {
247 mPreviousCursor = mCanvas->cursor();
248 mCanvas->setCursor( Qt::ArrowCursor );
249 QgsDebugMsgLevel( "Enabling arrow cursor", 2 );
250
251 // The cube needs triangle picking to handle click on faces.
252 if ( mMapScene->engine()->renderSettings()->pickingSettings()->pickMethod() != Qt3DRender::QPickingSettings::TrianglePicking && mCubeRoot->isEnabled() )
253 {
254 mMapScene->engine()->renderSettings()->pickingSettings()->setPickMethod( Qt3DRender::QPickingSettings::TrianglePicking );
255 QgsDebugMsgLevel( "Enabling triangle picking", 2 );
256 }
257 }
258 }
259 }
260 else if ( mLastClickedButton == Qt::MouseButton::RightButton && mHitsFound != -1 ) // show menu
261 {
262 displayMenuAt( mLastClickedPos );
263 }
264 else if ( mLastClickedButton == Qt::MouseButton::LeftButton ) // handle cube face clicks
265 {
266 hideMenu();
267
268 if ( mHitsFound != -1 )
269 {
270 if ( hits.at( mHitsFound ).entity() == mCubeRoot || hits.at( mHitsFound ).entity()->parent() == mCubeRoot )
271 {
272 switch ( hits.at( mHitsFound ).primitiveIndex() / 2 )
273 {
274 case 0: // "East face";
275 QgsDebugMsgLevel( "Qgs3DAxis: East face clicked", 2 );
276 onCameraViewChangeEast();
277 break;
278
279 case 1: // "West face ";
280 QgsDebugMsgLevel( "Qgs3DAxis: West face clicked", 2 );
281 onCameraViewChangeWest();
282 break;
283
284 case 2: // "North face ";
285 QgsDebugMsgLevel( "Qgs3DAxis: North face clicked", 2 );
286 onCameraViewChangeNorth();
287 break;
288
289 case 3: // "South face";
290 QgsDebugMsgLevel( "Qgs3DAxis: South face clicked", 2 );
291 onCameraViewChangeSouth();
292 break;
293
294 case 4: // "Top face ";
295 QgsDebugMsgLevel( "Qgs3DAxis: Top face clicked", 2 );
296 onCameraViewChangeTop();
297 break;
298
299 case 5: // "Bottom face ";
300 QgsDebugMsgLevel( "Qgs3DAxis: Bottom face clicked", 2 );
301 onCameraViewChangeBottom();
302 break;
303
304 default:
305 break;
306 }
307 }
308 }
309 }
310}
311
312void Qgs3DAxis::constructAxisScene( Qt3DCore::QEntity *parent3DScene )
313{
314 mAxisSceneEntity = new Qt3DCore::QEntity;
315 mAxisSceneEntity->setParent( parent3DScene );
316 mAxisSceneEntity->setObjectName( "3DAxis_SceneEntity" );
317
318 mAxisCamera = mRenderView->objectCamera();
319 mAxisCamera->setUpVector( QVector3D( 0.0f, 1.0f, 0.0f ) );
320 mAxisCamera->setViewCenter( QVector3D( 0.0f, 0.0f, 0.0f ) );
321 // position will be set later
322}
323
324void Qgs3DAxis::constructLabelsScene( Qt3DCore::QEntity *parent3DScene )
325{
326 mTwoDLabelSceneEntity = new Qt3DCore::QEntity;
327 mTwoDLabelSceneEntity->setParent( parent3DScene );
328 mTwoDLabelSceneEntity->setEnabled( true );
329
330 mTwoDLabelCamera = mRenderView->labelCamera();
331 mTwoDLabelCamera->setUpVector( QVector3D( 0.0f, 0.0f, 1.0f ) );
332 mTwoDLabelCamera->setViewCenter( QVector3D( 0.0f, 0.0f, 0.0f ) );
333 mTwoDLabelCamera->setPosition( QVector3D( 0.0f, 0.0f, 100.0f ) );
334}
335
336QVector3D Qgs3DAxis::from3DTo2DLabelPosition( const QVector3D &sourcePos, Qt3DRender::QCamera *sourceCamera, Qt3DRender::QCamera *destCamera )
337{
338 const int viewportWidth = static_cast<int>( std::round( mTwoDLabelCamera->lens()->right() - mTwoDLabelCamera->lens()->left() ) );
339 const int viewportHeight = static_cast<int>( std::round( mTwoDLabelCamera->lens()->top() - mTwoDLabelCamera->lens()->bottom() ) );
340 QRect viewportRect( static_cast<int>( std::round( mTwoDLabelCamera->lens()->left() ) ), static_cast<int>( std::round( mTwoDLabelCamera->lens()->bottom() ) ), viewportWidth, viewportHeight );
341
342 QVector3D destPos = sourcePos.project( sourceCamera->viewMatrix(), destCamera->projectionMatrix(), viewportRect );
343 destPos.setZ( 0.0f );
344 return destPos;
345}
346
347void Qgs3DAxis::setEnableCube( bool show )
348{
349 mCubeRoot->setEnabled( show );
350 if ( show )
351 {
352 mCubeRoot->setParent( mAxisSceneEntity );
353 }
354 else
355 {
356 mCubeRoot->setParent( static_cast<Qt3DCore::QEntity *>( nullptr ) );
357 }
358}
359
360void Qgs3DAxis::setEnableAxis( bool show )
361{
362 mAxisRoot->setEnabled( show );
363 if ( show )
364 {
365 mAxisRoot->setParent( mAxisSceneEntity );
366 }
367 else
368 {
369 mAxisRoot->setParent( static_cast<Qt3DCore::QEntity *>( nullptr ) );
370 }
371
372 mTextX->setEnabled( show );
373 mTextY->setEnabled( show );
374 mTextZ->setEnabled( show );
375}
376
377void Qgs3DAxis::createAxisScene()
378{
379 if ( !mAxisRoot || !mCubeRoot )
380 {
381 mAxisRoot = new Qt3DCore::QEntity;
382 mAxisRoot->setParent( mAxisSceneEntity );
383 mAxisRoot->setObjectName( "3DAxis_AxisRoot" );
384 mAxisRoot->addComponent( mRenderView->objectLayer() ); // raycaster will filter object containing this layer
385
386 createAxis( Qt::Axis::XAxis );
387 createAxis( Qt::Axis::YAxis );
388 createAxis( Qt::Axis::ZAxis );
389
390 mCubeRoot = new Qt3DCore::QEntity;
391 mCubeRoot->setParent( mAxisSceneEntity );
392 mCubeRoot->setObjectName( "3DAxis_CubeRoot" );
393 mCubeRoot->addComponent( mRenderView->objectLayer() ); // raycaster will filter object containing this layer
394
395 createCube();
396 }
397
398 Qgs3DAxisSettings::Mode mode = mMapSettings->get3DAxisSettings().mode();
399
400 if ( mode == Qgs3DAxisSettings::Mode::Off )
401 {
402 mAxisSceneEntity->setEnabled( false );
403 setEnableAxis( false );
404 setEnableCube( false );
405 mRenderView->setEnabled( false );
406 }
407 else
408 {
409 mRenderView->setEnabled( true );
410 mAxisSceneEntity->setEnabled( true );
411 if ( mode == Qgs3DAxisSettings::Mode::Crs )
412 {
413 setEnableCube( false );
414 setEnableAxis( true );
415
416 const QList<Qgis::CrsAxisDirection> axisDirections = mCrs.axisOrdering();
417
418 if ( axisDirections.length() > 0 )
419 mTextX->setText( QgsCoordinateReferenceSystemUtils::axisDirectionToAbbreviatedString( axisDirections.at( 0 ) ) );
420 else
421 mTextX->setText( "X?" );
422
423 if ( axisDirections.length() > 1 )
424 mTextY->setText( QgsCoordinateReferenceSystemUtils::axisDirectionToAbbreviatedString( axisDirections.at( 1 ) ) );
425 else
426 mTextY->setText( "Y?" );
427
428 if ( axisDirections.length() > 2 )
429 mTextZ->setText( QgsCoordinateReferenceSystemUtils::axisDirectionToAbbreviatedString( axisDirections.at( 2 ) ) );
430 else
431 mTextZ->setText( QStringLiteral( "up" ) );
432 }
433 else if ( mode == Qgs3DAxisSettings::Mode::Cube )
434 {
435 setEnableCube( true );
436 setEnableAxis( false );
437 }
438 else
439 {
440 setEnableCube( false );
441 setEnableAxis( true );
442 mTextX->setText( "X?" );
443 mTextY->setText( "Y?" );
444 mTextZ->setText( "Z?" );
445 }
446
447 updateAxisLabelPosition();
448 }
449}
450
451void Qgs3DAxis::createKeyboardShortCut()
452{
453 QgsWindow3DEngine *eng = dynamic_cast<QgsWindow3DEngine *>( mMapScene->engine() );
454 if ( eng )
455 {
456 QWidget *mapCanvas = dynamic_cast<QWidget *>( eng->parent() );
457 if ( !mapCanvas )
458 {
459 QgsLogger::warning( "Qgs3DAxis: no canvas defined!" );
460 }
461 else
462 {
463 QShortcut *shortcutHome = new QShortcut( QKeySequence( Qt::CTRL + Qt::Key_1 ), mapCanvas );
464 connect( shortcutHome, &QShortcut::activated, this, [this]() { onCameraViewChangeHome(); } );
465
466 QShortcut *shortcutTop = new QShortcut( QKeySequence( Qt::CTRL + Qt::Key_5 ), mapCanvas );
467 connect( shortcutTop, &QShortcut::activated, this, [this]() { onCameraViewChangeTop(); } );
468
469 QShortcut *shortcutNorth = new QShortcut( QKeySequence( Qt::CTRL + Qt::Key_8 ), mapCanvas );
470 connect( shortcutNorth, &QShortcut::activated, this, [this]() { onCameraViewChangeNorth(); } );
471
472 QShortcut *shortcutEast = new QShortcut( QKeySequence( Qt::CTRL + Qt::Key_6 ), mapCanvas );
473 connect( shortcutEast, &QShortcut::activated, this, [this]() { onCameraViewChangeEast(); } );
474
475 QShortcut *shortcutSouth = new QShortcut( QKeySequence( Qt::CTRL + Qt::Key_2 ), mapCanvas );
476 connect( shortcutSouth, &QShortcut::activated, this, [this]() { onCameraViewChangeSouth(); } );
477
478 QShortcut *shortcutWest = new QShortcut( QKeySequence( Qt::CTRL + Qt::Key_4 ), mapCanvas );
479 connect( shortcutWest, &QShortcut::activated, this, [this]() { onCameraViewChangeWest(); } );
480 }
481 }
482}
483
484void Qgs3DAxis::createMenu()
485{
486 mMenu = new QMenu();
487
488 // axis type menu
489 QAction *typeOffAct = new QAction( tr( "&Off" ), mMenu );
490 typeOffAct->setCheckable( true );
491 typeOffAct->setStatusTip( tr( "Disable 3D axis" ) );
492 connect( mMapSettings, &Qgs3DMapSettings::axisSettingsChanged, this, [typeOffAct, this]() {
493 if ( mMapSettings->get3DAxisSettings().mode() == Qgs3DAxisSettings::Mode::Off )
494 typeOffAct->setChecked( true );
495 } );
496
497 QAction *typeCrsAct = new QAction( tr( "Coordinate Reference &System" ), mMenu );
498 typeCrsAct->setCheckable( true );
499 typeCrsAct->setStatusTip( tr( "Coordinate Reference System 3D axis" ) );
500 connect( mMapSettings, &Qgs3DMapSettings::axisSettingsChanged, this, [typeCrsAct, this]() {
501 if ( mMapSettings->get3DAxisSettings().mode() == Qgs3DAxisSettings::Mode::Crs )
502 typeCrsAct->setChecked( true );
503 } );
504
505 QAction *typeCubeAct = new QAction( tr( "&Cube" ), mMenu );
506 typeCubeAct->setCheckable( true );
507 typeCubeAct->setStatusTip( tr( "Cube 3D axis" ) );
508 connect( mMapSettings, &Qgs3DMapSettings::axisSettingsChanged, this, [typeCubeAct, this]() {
509 if ( mMapSettings->get3DAxisSettings().mode() == Qgs3DAxisSettings::Mode::Cube )
510 typeCubeAct->setChecked( true );
511 } );
512
513 QActionGroup *typeGroup = new QActionGroup( mMenu );
514 typeGroup->addAction( typeOffAct );
515 typeGroup->addAction( typeCrsAct );
516 typeGroup->addAction( typeCubeAct );
517
518 connect( typeOffAct, &QAction::triggered, this, [this]( bool ) { onAxisModeChanged( Qgs3DAxisSettings::Mode::Off ); } );
519 connect( typeCrsAct, &QAction::triggered, this, [this]( bool ) { onAxisModeChanged( Qgs3DAxisSettings::Mode::Crs ); } );
520 connect( typeCubeAct, &QAction::triggered, this, [this]( bool ) { onAxisModeChanged( Qgs3DAxisSettings::Mode::Cube ); } );
521
522 QMenu *typeMenu = new QMenu( QStringLiteral( "Axis Type" ), mMenu );
523 Q_ASSERT( typeMenu );
524 typeMenu->addAction( typeOffAct );
525 typeMenu->addAction( typeCrsAct );
526 typeMenu->addAction( typeCubeAct );
527 mMenu->addMenu( typeMenu );
528
529 // horizontal position menu
530 QAction *hPosLeftAct = new QAction( tr( "&Left" ), mMenu );
531 hPosLeftAct->setCheckable( true );
532 connect( mMapSettings, &Qgs3DMapSettings::axisSettingsChanged, this, [hPosLeftAct, this]() {
533 if ( mMapSettings->get3DAxisSettings().horizontalPosition() == Qt::AnchorPoint::AnchorLeft )
534 hPosLeftAct->setChecked( true );
535 } );
536
537 QAction *hPosMiddleAct = new QAction( tr( "&Center" ), mMenu );
538 hPosMiddleAct->setCheckable( true );
539 connect( mMapSettings, &Qgs3DMapSettings::axisSettingsChanged, this, [hPosMiddleAct, this]() {
540 if ( mMapSettings->get3DAxisSettings().horizontalPosition() == Qt::AnchorPoint::AnchorHorizontalCenter )
541 hPosMiddleAct->setChecked( true );
542 } );
543
544 QAction *hPosRightAct = new QAction( tr( "&Right" ), mMenu );
545 hPosRightAct->setCheckable( true );
546 connect( mMapSettings, &Qgs3DMapSettings::axisSettingsChanged, this, [hPosRightAct, this]() {
547 if ( mMapSettings->get3DAxisSettings().horizontalPosition() == Qt::AnchorPoint::AnchorRight )
548 hPosRightAct->setChecked( true );
549 } );
550
551 QActionGroup *hPosGroup = new QActionGroup( mMenu );
552 hPosGroup->addAction( hPosLeftAct );
553 hPosGroup->addAction( hPosMiddleAct );
554 hPosGroup->addAction( hPosRightAct );
555
556 connect( hPosLeftAct, &QAction::triggered, this, [this]( bool ) { mRenderView->onHorizontalPositionChanged( Qt::AnchorPoint::AnchorLeft ); } );
557 connect( hPosMiddleAct, &QAction::triggered, this, [this]( bool ) { mRenderView->onHorizontalPositionChanged( Qt::AnchorPoint::AnchorHorizontalCenter ); } );
558 connect( hPosRightAct, &QAction::triggered, this, [this]( bool ) { mRenderView->onHorizontalPositionChanged( Qt::AnchorPoint::AnchorRight ); } );
559
560 QMenu *horizPosMenu = new QMenu( QStringLiteral( "Horizontal Position" ), mMenu );
561 horizPosMenu->addAction( hPosLeftAct );
562 horizPosMenu->addAction( hPosMiddleAct );
563 horizPosMenu->addAction( hPosRightAct );
564 mMenu->addMenu( horizPosMenu );
565
566 // vertical position menu
567 QAction *vPosTopAct = new QAction( tr( "&Top" ), mMenu );
568 vPosTopAct->setCheckable( true );
569 connect( mMapSettings, &Qgs3DMapSettings::axisSettingsChanged, this, [vPosTopAct, this]() {
570 if ( mMapSettings->get3DAxisSettings().verticalPosition() == Qt::AnchorPoint::AnchorTop )
571 vPosTopAct->setChecked( true );
572 } );
573
574 QAction *vPosMiddleAct = new QAction( tr( "&Middle" ), mMenu );
575 vPosMiddleAct->setCheckable( true );
576 connect( mMapSettings, &Qgs3DMapSettings::axisSettingsChanged, this, [vPosMiddleAct, this]() {
577 if ( mMapSettings->get3DAxisSettings().verticalPosition() == Qt::AnchorPoint::AnchorVerticalCenter )
578 vPosMiddleAct->setChecked( true );
579 } );
580
581 QAction *vPosBottomAct = new QAction( tr( "&Bottom" ), mMenu );
582 vPosBottomAct->setCheckable( true );
583 connect( mMapSettings, &Qgs3DMapSettings::axisSettingsChanged, this, [vPosBottomAct, this]() {
584 if ( mMapSettings->get3DAxisSettings().verticalPosition() == Qt::AnchorPoint::AnchorBottom )
585 vPosBottomAct->setChecked( true );
586 } );
587
588 QActionGroup *vPosGroup = new QActionGroup( mMenu );
589 vPosGroup->addAction( vPosTopAct );
590 vPosGroup->addAction( vPosMiddleAct );
591 vPosGroup->addAction( vPosBottomAct );
592
593 connect( vPosTopAct, &QAction::triggered, this, [this]( bool ) { mRenderView->onVerticalPositionChanged( Qt::AnchorPoint::AnchorTop ); } );
594 connect( vPosMiddleAct, &QAction::triggered, this, [this]( bool ) { mRenderView->onVerticalPositionChanged( Qt::AnchorPoint::AnchorVerticalCenter ); } );
595 connect( vPosBottomAct, &QAction::triggered, this, [this]( bool ) { mRenderView->onVerticalPositionChanged( Qt::AnchorPoint::AnchorBottom ); } );
596
597 QMenu *vertPosMenu = new QMenu( QStringLiteral( "Vertical Position" ), mMenu );
598 vertPosMenu->addAction( vPosTopAct );
599 vertPosMenu->addAction( vPosMiddleAct );
600 vertPosMenu->addAction( vPosBottomAct );
601 mMenu->addMenu( vertPosMenu );
602
603 // axis view menu
604 QAction *viewHomeAct = new QAction( tr( "&Home" ) + "\t Ctrl+1", mMenu );
605 QAction *viewTopAct = new QAction( tr( "&Top" ) + "\t Ctrl+5", mMenu );
606 QAction *viewNorthAct = new QAction( tr( "&North" ) + "\t Ctrl+8", mMenu );
607 QAction *viewEastAct = new QAction( tr( "&East" ) + "\t Ctrl+6", mMenu );
608 QAction *viewSouthAct = new QAction( tr( "&South" ) + "\t Ctrl+2", mMenu );
609 QAction *viewWestAct = new QAction( tr( "&West" ) + "\t Ctrl+4", mMenu );
610 QAction *viewBottomAct = new QAction( tr( "&Bottom" ), mMenu );
611
612 connect( viewHomeAct, &QAction::triggered, this, &Qgs3DAxis::onCameraViewChangeHome );
613 connect( viewTopAct, &QAction::triggered, this, &Qgs3DAxis::onCameraViewChangeTop );
614 connect( viewNorthAct, &QAction::triggered, this, &Qgs3DAxis::onCameraViewChangeNorth );
615 connect( viewEastAct, &QAction::triggered, this, &Qgs3DAxis::onCameraViewChangeEast );
616 connect( viewSouthAct, &QAction::triggered, this, &Qgs3DAxis::onCameraViewChangeSouth );
617 connect( viewWestAct, &QAction::triggered, this, &Qgs3DAxis::onCameraViewChangeWest );
618 connect( viewBottomAct, &QAction::triggered, this, &Qgs3DAxis::onCameraViewChangeBottom );
619
620 QMenu *viewMenu = new QMenu( QStringLiteral( "Camera View" ), mMenu );
621 viewMenu->addAction( viewHomeAct );
622 viewMenu->addAction( viewTopAct );
623 viewMenu->addAction( viewNorthAct );
624 viewMenu->addAction( viewEastAct );
625 viewMenu->addAction( viewSouthAct );
626 viewMenu->addAction( viewWestAct );
627 viewMenu->addAction( viewBottomAct );
628 mMenu->addMenu( viewMenu );
629
630 // update checkable items
631 mMapSettings->set3DAxisSettings( mMapSettings->get3DAxisSettings(), true );
632}
633
634void Qgs3DAxis::hideMenu()
635{
636 if ( mMenu && mMenu->isVisible() )
637 mMenu->hide();
638}
639
640void Qgs3DAxis::displayMenuAt( const QPoint &sourcePos )
641{
642 if ( !mMenu )
643 {
644 createMenu();
645 }
646
647 mMenu->popup( mCanvas->mapToGlobal( sourcePos ) );
648}
649
650void Qgs3DAxis::onAxisModeChanged( Qgs3DAxisSettings::Mode mode )
651{
652 Qgs3DAxisSettings s = mMapSettings->get3DAxisSettings();
653 s.setMode( mode );
654 mMapSettings->set3DAxisSettings( s );
655}
656
657void Qgs3DAxis::onCameraViewChange( float pitch, float yaw )
658{
659 QgsVector3D pos = mCameraController->lookingAtPoint();
660 double elevation = 0.0;
661 if ( mMapSettings->terrainRenderingEnabled() )
662 {
663 QgsDebugMsgLevel( "Checking elevation from terrain...", 2 );
664 QVector3D camPos = mCameraController->camera()->position();
665 QgsRayCastingUtils::Ray3D ray( camPos, pos.toVector3D() - camPos, mCameraController->camera()->farPlane() );
666 const QVector<QgsRayCastingUtils::RayHit> hits = mMapScene->terrainEntity()->rayIntersection( ray, QgsRayCastingUtils::RayCastContext() );
667 if ( !hits.isEmpty() )
668 {
669 elevation = hits.at( 0 ).pos.z();
670 QgsDebugMsgLevel( QString( "Computed elevation from terrain: %1" ).arg( elevation ), 2 );
671 }
672 else
673 {
674 QgsDebugMsgLevel( "Unable to obtain elevation from terrain", 2 );
675 }
676 }
677 pos.set( pos.x(), pos.y(), elevation + mMapSettings->terrainSettings()->elevationOffset() );
678
679 mCameraController->setLookingAtPoint( pos, ( mCameraController->camera()->position() - pos.toVector3D() ).length(), pitch, yaw );
680}
681
682
683void Qgs3DAxis::createCube()
684{
685 QVector3D minPos = QVector3D( -mCylinderLength * 0.5f, -mCylinderLength * 0.5f, -mCylinderLength * 0.5f );
686
687 // cube outlines
688 Qt3DCore::QEntity *cubeLineEntity = new Qt3DCore::QEntity( mCubeRoot );
689 cubeLineEntity->setObjectName( "3DAxis_cubeline" );
690 Qgs3DWiredMesh *cubeLine = new Qgs3DWiredMesh;
691 QgsAABB box = QgsAABB( -mCylinderLength * 0.5f, -mCylinderLength * 0.5f, -mCylinderLength * 0.5f, mCylinderLength * 0.5f, mCylinderLength * 0.5f, mCylinderLength * 0.5f );
692 cubeLine->setVertices( box.verticesForLines() );
693 cubeLineEntity->addComponent( cubeLine );
694
695 Qt3DExtras::QPhongMaterial *cubeLineMaterial = new Qt3DExtras::QPhongMaterial;
696 cubeLineMaterial->setAmbient( Qt::white );
697 cubeLineEntity->addComponent( cubeLineMaterial );
698
699 // cube mesh
700 Qt3DExtras::QCuboidMesh *cubeMesh = new Qt3DExtras::QCuboidMesh;
701 cubeMesh->setObjectName( "3DAxis_cubemesh" );
702 cubeMesh->setXExtent( mCylinderLength );
703 cubeMesh->setYExtent( mCylinderLength );
704 cubeMesh->setZExtent( mCylinderLength );
705 mCubeRoot->addComponent( cubeMesh );
706
707 Qt3DExtras::QPhongMaterial *cubeMaterial = new Qt3DExtras::QPhongMaterial( mCubeRoot );
708 cubeMaterial->setAmbient( QColor( 100, 100, 100, 50 ) );
709 cubeMaterial->setShininess( 100 );
710 mCubeRoot->addComponent( cubeMaterial );
711
712 Qt3DCore::QTransform *cubeTransform = new Qt3DCore::QTransform;
713 QMatrix4x4 transformMatrixcube;
714 //transformMatrixcube.rotate( rotation );
715 transformMatrixcube.translate( minPos + QVector3D( mCylinderLength * 0.5f, mCylinderLength * 0.5f, mCylinderLength * 0.5f ) );
716 cubeTransform->setMatrix( transformMatrixcube );
717 mCubeRoot->addComponent( cubeTransform );
718
719 // text
720 QString text;
721 const int fontSize = static_cast<int>( std::round( 0.75f * static_cast<float>( mFontSize ) ) );
722 const float textHeight = static_cast<float>( fontSize ) * 1.5f;
723 float textWidth;
724 const QFont font = createFont( fontSize );
725
726 {
727 text = QStringLiteral( "top" );
728 textWidth = static_cast<float>( text.length() * fontSize ) * 0.75f;
729 QVector3D translation = minPos + QVector3D( mCylinderLength * 0.5f - textWidth / 2.0f, mCylinderLength * 0.5f - textHeight / 2.0f, mCylinderLength * 1.01f );
730 QMatrix4x4 rotation;
731 mCubeLabels << addCubeText( text, textHeight, textWidth, font, rotation, translation );
732 }
733
734 {
735 text = QStringLiteral( "btm" );
736 textWidth = static_cast<float>( text.length() * fontSize ) * 0.75f;
737 QVector3D translation = minPos + QVector3D( mCylinderLength * 0.5f - textWidth / 2.0f, mCylinderLength * 0.5f + textHeight / 2.0f, -mCylinderLength * 0.01f );
738 QMatrix4x4 rotation;
739 rotation.rotate( 180.0f, QVector3D( 1.0f, 0.0f, 0.0f ).normalized() );
740 mCubeLabels << addCubeText( text, textHeight, textWidth, font, rotation, translation );
741 }
742
743 {
744 text = QStringLiteral( "west" );
745 textWidth = static_cast<float>( text.length() * fontSize ) * 0.75f;
746 QVector3D translation = minPos + QVector3D( -mCylinderLength * 0.01f, mCylinderLength * 0.5f + textWidth / 2.0f, mCylinderLength * 0.5f - textHeight / 2.0f );
747 QMatrix4x4 rotation;
748 rotation.rotate( 90.0f, QVector3D( 0.0f, -1.0f, 0.0f ).normalized() );
749 rotation.rotate( 90.0f, QVector3D( 0.0f, 0.0f, -1.0f ).normalized() );
750 mCubeLabels << addCubeText( text, textHeight, textWidth, font, rotation, translation );
751 }
752
753 {
754 text = QStringLiteral( "east" );
755 textWidth = static_cast<float>( text.length() * fontSize ) * 0.75f;
756 QVector3D translation = minPos + QVector3D( mCylinderLength * 1.01f, mCylinderLength * 0.5f - textWidth / 2.0f, mCylinderLength * 0.5f - textHeight / 2.0f );
757 QMatrix4x4 rotation;
758 rotation.rotate( 90.0f, QVector3D( 0.0f, 1.0f, 0.0f ).normalized() );
759 rotation.rotate( 90.0f, QVector3D( 0.0f, 0.0f, 1.0f ).normalized() );
760 mCubeLabels << addCubeText( text, textHeight, textWidth, font, rotation, translation );
761 }
762
763 {
764 text = QStringLiteral( "south" );
765 textWidth = static_cast<float>( text.length() * fontSize ) * 0.75f;
766 QVector3D translation = minPos + QVector3D( mCylinderLength * 0.5f - textWidth / 2.0f, -mCylinderLength * 0.01f, mCylinderLength * 0.5f - textHeight / 2.0f );
767 QMatrix4x4 rotation;
768 rotation.rotate( 90.0f, QVector3D( 1.0f, 0.0f, 0.0f ).normalized() );
769 mCubeLabels << addCubeText( text, textHeight, textWidth, font, rotation, translation );
770 }
771
772 {
773 text = QStringLiteral( "north" );
774 textWidth = static_cast<float>( text.length() * fontSize ) * 0.75f;
775 QVector3D translation = minPos + QVector3D( mCylinderLength * 0.5f + textWidth / 2.0f, mCylinderLength * 1.01f, mCylinderLength * 0.5f - textHeight / 2.0f );
776 QMatrix4x4 rotation;
777 rotation.rotate( 90.0f, QVector3D( -1.0f, 0.0f, 0.0f ).normalized() );
778 rotation.rotate( 180.0f, QVector3D( 0.0f, 0.0f, 1.0f ).normalized() );
779 mCubeLabels << addCubeText( text, textHeight, textWidth, font, rotation, translation );
780 }
781
782 for ( Qt3DExtras::QText2DEntity *l : std::as_const( mCubeLabels ) )
783 {
784 l->setParent( mCubeRoot );
785 }
786}
787
788Qt3DExtras::QText2DEntity *Qgs3DAxis::addCubeText( const QString &text, float textHeight, float textWidth, const QFont &font, const QMatrix4x4 &rotation, const QVector3D &translation )
789{
790 Qt3DExtras::QText2DEntity *textEntity = new Qt3DExtras::QText2DEntity;
791 textEntity->setObjectName( "3DAxis_cube_label_" + text );
792 textEntity->setFont( font );
793 textEntity->setHeight( textHeight );
794 textEntity->setWidth( textWidth );
795 textEntity->setColor( QColor( 192, 192, 192 ) );
796 textEntity->setText( text );
797
798 Qt3DCore::QTransform *textFrontTransform = new Qt3DCore::QTransform();
799 textFrontTransform->setMatrix( rotation );
800 textFrontTransform->setTranslation( translation );
801 textEntity->addComponent( textFrontTransform );
802
803 return textEntity;
804}
805
806void Qgs3DAxis::createAxis( Qt::Axis axisType )
807{
808 float cylinderRadius = 0.05f * mCylinderLength;
809 float coneLength = 0.3f * mCylinderLength;
810 float coneBottomRadius = 0.1f * mCylinderLength;
811
812 QQuaternion rotation;
813 QColor color;
814
815 Qt3DExtras::QText2DEntity *text = nullptr;
816 Qt3DCore::QTransform *textTransform = nullptr;
817 QString name;
818
819 switch ( axisType )
820 {
821 case Qt::Axis::XAxis:
822 mTextX = new Qt3DExtras::QText2DEntity(); // object initialization in two step:
823 mTextX->setParent( mTwoDLabelSceneEntity ); // see https://bugreports.qt.io/browse/QTBUG-77139
824 connect( mTextX, &Qt3DExtras::QText2DEntity::textChanged, this, [this]( const QString &text ) {
825 updateAxisLabelText( mTextX, text );
826 } );
827 mTextTransformX = new Qt3DCore::QTransform();
828 mTextCoordX = QVector3D( mCylinderLength + coneLength / 2.0f, 0.0f, 0.0f );
829
830 rotation = QQuaternion::fromAxisAndAngle( QVector3D( 0.0f, 0.0f, 1.0f ), -90.0f );
831 color = Qt::red;
832 text = mTextX;
833 textTransform = mTextTransformX;
834 name = "3DAxis_axisX";
835 break;
836
837 case Qt::Axis::YAxis:
838 mTextY = new Qt3DExtras::QText2DEntity(); // object initialization in two step:
839 mTextY->setParent( mTwoDLabelSceneEntity ); // see https://bugreports.qt.io/browse/QTBUG-77139
840 connect( mTextY, &Qt3DExtras::QText2DEntity::textChanged, this, [this]( const QString &text ) {
841 updateAxisLabelText( mTextY, text );
842 } );
843 mTextTransformY = new Qt3DCore::QTransform();
844 mTextCoordY = QVector3D( 0.0f, mCylinderLength + coneLength / 2.0f, 0.0f );
845
846 // no rotation
847
848 color = Qt::green;
849 text = mTextY;
850 textTransform = mTextTransformY;
851 name = "3DAxis_axisY";
852 break;
853
854 case Qt::Axis::ZAxis:
855 mTextZ = new Qt3DExtras::QText2DEntity(); // object initialization in two step:
856 mTextZ->setParent( mTwoDLabelSceneEntity ); // see https://bugreports.qt.io/browse/QTBUG-77139
857 connect( mTextZ, &Qt3DExtras::QText2DEntity::textChanged, this, [this]( const QString &text ) {
858 updateAxisLabelText( mTextZ, text );
859 } );
860 mTextTransformZ = new Qt3DCore::QTransform();
861 mTextCoordZ = QVector3D( 0.0f, 0.0f, mCylinderLength + coneLength / 2.0f );
862
863 rotation = QQuaternion::fromAxisAndAngle( QVector3D( 1.0f, 0.0f, 0.0f ), 90.0f );
864 color = Qt::blue;
865 text = mTextZ;
866 textTransform = mTextTransformZ;
867 name = "3DAxis_axisZ";
868 break;
869
870 default:
871 return;
872 }
873
874 // cylinder
875 Qt3DCore::QEntity *cylinder = new Qt3DCore::QEntity( mAxisRoot );
876 cylinder->setObjectName( name );
877
878 Qt3DExtras::QCylinderMesh *cylinderMesh = new Qt3DExtras::QCylinderMesh;
879 cylinderMesh->setRadius( cylinderRadius );
880 cylinderMesh->setLength( mCylinderLength );
881 cylinderMesh->setRings( 10 );
882 cylinderMesh->setSlices( 4 );
883 cylinder->addComponent( cylinderMesh );
884
885 Qt3DExtras::QPhongMaterial *cylinderMaterial = new Qt3DExtras::QPhongMaterial( cylinder );
886 cylinderMaterial->setAmbient( color );
887 cylinderMaterial->setShininess( 0 );
888 cylinder->addComponent( cylinderMaterial );
889
890 Qt3DCore::QTransform *cylinderTransform = new Qt3DCore::QTransform;
891 QMatrix4x4 transformMatrixCylinder;
892 transformMatrixCylinder.rotate( rotation );
893 transformMatrixCylinder.translate( QVector3D( 0.0f, mCylinderLength / 2.0f, 0.0f ) );
894 cylinderTransform->setMatrix( transformMatrixCylinder );
895 cylinder->addComponent( cylinderTransform );
896
897 // cone
898 Qt3DCore::QEntity *coneEntity = new Qt3DCore::QEntity( mAxisRoot );
899 coneEntity->setObjectName( name );
900 Qt3DExtras::QConeMesh *coneMesh = new Qt3DExtras::QConeMesh;
901 coneMesh->setLength( coneLength );
902 coneMesh->setBottomRadius( coneBottomRadius );
903 coneMesh->setTopRadius( 0.0f );
904 coneMesh->setRings( 10 );
905 coneMesh->setSlices( 4 );
906 coneEntity->addComponent( coneMesh );
907
908 Qt3DExtras::QPhongMaterial *coneMaterial = new Qt3DExtras::QPhongMaterial( coneEntity );
909 coneMaterial->setAmbient( color );
910 coneMaterial->setShininess( 0 );
911 coneEntity->addComponent( coneMaterial );
912
913 Qt3DCore::QTransform *coneTransform = new Qt3DCore::QTransform;
914 QMatrix4x4 transformMatrixCone;
915 transformMatrixCone.rotate( rotation );
916 transformMatrixCone.translate( QVector3D( 0.0f, mCylinderLength, 0.0f ) );
917 coneTransform->setMatrix( transformMatrixCone );
918 coneEntity->addComponent( coneTransform );
919
920 // text font, height and width will be set later in onText?Changed
921 text->setColor( QColor( 192, 192, 192, 192 ) );
922 text->addComponent( textTransform );
923}
924
926{
927 createAxisScene();
928 onAxisViewportSizeUpdate();
929}
930
931void Qgs3DAxis::onAxisViewportSizeUpdate()
932{
933 mRenderView->onViewportSizeUpdate(); // will call onViewportScaleFactorChanged as callback
934
935 // mRenderView->onViewportSizeUpdate() has updated `mTwoDLabelCamera` lens parameters.
936 // The position of the labels needs to be updated.
937 const Qgs3DAxisSettings axisSettings = mMapSettings->get3DAxisSettings();
938 if ( axisSettings.mode() == Qgs3DAxisSettings::Mode::Crs && mAxisRoot->isEnabled() )
939 {
940 updateAxisLabelPosition();
941 }
942}
943
945{
946 // if the axis scene has not been created, don't do anything
947 if ( !mAxisRoot || !mCubeRoot )
948 {
949 return;
950 }
951
952 if ( scaleFactor > 0.0 )
953 {
954 Qgs3DAxisSettings settings = mMapSettings->get3DAxisSettings();
955 if ( settings.mode() == Qgs3DAxisSettings::Mode::Crs )
956 setEnableAxis( true );
957 else if ( settings.mode() == Qgs3DAxisSettings::Mode::Cube )
958 setEnableCube( true );
959
960 mAxisScaleFactor = scaleFactor;
961 QgsDebugMsgLevel( QString( "3DAxis viewport mAxisScaleFactor %1" ).arg( mAxisScaleFactor ), 2 );
962 }
963 else
964 {
965 setEnableCube( false );
966 setEnableAxis( false );
967 }
968}
969
970void Qgs3DAxis::onCameraUpdate()
971{
972 Qt3DRender::QCamera *parentCamera = mCameraController->camera();
973
974 if ( parentCamera->viewVector() != mPreviousVector
975 && !std::isnan( parentCamera->viewVector().x() )
976 && !std::isnan( parentCamera->viewVector().y() )
977 && !std::isnan( parentCamera->viewVector().z() ) )
978 {
979 mPreviousVector = parentCamera->viewVector();
980
981 QQuaternion q = QQuaternion::fromDirection( -parentCamera->viewVector(), parentCamera->upVector() );
982 mAxisCamera->setPosition( q.rotatedVector( QVector3D( 0, 0, mCylinderLength * 9.0f ) ) );
983 mAxisCamera->setUpVector( q.rotatedVector( QVector3D( 0, 1, 0 ) ) );
984
985 if ( mAxisRoot->isEnabled() )
986 {
987 updateAxisLabelPosition();
988 }
989 }
990}
991
992void Qgs3DAxis::updateAxisLabelPosition()
993{
994 if ( mTextTransformX && mTextTransformY && mTextTransformZ )
995 {
996 mTextTransformX->setTranslation( from3DTo2DLabelPosition( mTextCoordX * static_cast<float>( mAxisScaleFactor ), mAxisCamera, mTwoDLabelCamera ) );
997 updateAxisLabelText( mTextX, mTextX->text() );
998
999 mTextTransformY->setTranslation( from3DTo2DLabelPosition( mTextCoordY * static_cast<float>( mAxisScaleFactor ), mAxisCamera, mTwoDLabelCamera ) );
1000 updateAxisLabelText( mTextY, mTextY->text() );
1001
1002 mTextTransformZ->setTranslation( from3DTo2DLabelPosition( mTextCoordZ * static_cast<float>( mAxisScaleFactor ), mAxisCamera, mTwoDLabelCamera ) );
1003 updateAxisLabelText( mTextZ, mTextZ->text() );
1004 }
1005}
1006
1007void Qgs3DAxis::updateAxisLabelText( Qt3DExtras::QText2DEntity *textEntity, const QString &text )
1008{
1009 const float scaledFontSize = static_cast<float>( mAxisScaleFactor ) * static_cast<float>( mFontSize );
1010 const QFont font = createFont( static_cast<int>( std::round( scaledFontSize ) ) );
1011 textEntity->setFont( font );
1012 textEntity->setWidth( scaledFontSize * static_cast<float>( text.length() ) );
1013 textEntity->setHeight( 1.5f * scaledFontSize );
1014}
1015
1016QFont Qgs3DAxis::createFont( int pointSize )
1017{
1018 QFont font = QFontDatabase::systemFont( QFontDatabase::FixedFont );
1019 font.setPointSize( pointSize );
1020 font.setWeight( QFont::Weight::Black );
1021 font.setStyleStrategy( QFont::StyleStrategy::ForceOutline );
1022 return font;
1023}
3D axis render view.
Qt3DRender::QLayer * labelLayer() const
Returns the layer to be used by entities to be included in the label renderpass.
void onVerticalPositionChanged(Qt::AnchorPoint position)
Updates viewport vertical position.
Qt3DRender::QCamera * labelCamera() const
Returns camera used for billboarded labels.
Qt3DRender::QLayer * objectLayer() const
Returns main object layer.
Qt3DRender::QViewport * viewport() const
Returns the viewport associated to this renderview.
void onViewportSizeUpdate(int width=-1, int height=-1)
Updates viewport size. Uses canvas size by default.
Qt3DRender::QCamera * objectCamera() const
Returns main object camera (used for axis or cube)
void onHorizontalPositionChanged(Qt::AnchorPoint position)
Updates viewport horizontal position.
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.
Qt::AnchorPoint verticalPosition() const
Returns the vertical position for the 3d axis.
Qgs3DAxisSettings::Mode mode() const
Returns the type of the 3daxis.
Qt::AnchorPoint horizontalPosition() const
Returns the horizontal position for the 3d axis.
~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:50
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.
QgsTerrainEntity * terrainEntity()
Returns terrain entity (may be nullptr if using globe scene, terrain rendering is disabled or when te...
Definition of the world.
const QgsAbstractTerrainSettings * terrainSettings() const
Returns the terrain settings.
Qgs3DAxisSettings get3DAxisSettings() const
Returns the current configuration of 3d axis.
void set3DAxisSettings(const Qgs3DAxisSettings &axisSettings, bool force=false)
Sets the current configuration of 3d axis.
bool terrainRenderingEnabled() const
Returns whether the 2D terrain surface will be rendered.
void axisSettingsChanged()
Emitted when 3d axis rendering settings are changed.
Axis-aligned bounding box - in world coords.
Definition qgsaabb.h:35
QList< QVector3D > verticesForLines() const
Returns a list of pairs of vertices (useful for display of bounding boxes)
Definition qgsaabb.cpp:59
QgsFrameGraph * frameGraph()
Returns the shadow rendering frame graph object used to render the scene.
virtual Qt3DRender::QRenderSettings * renderSettings()=0
Returns access to the engine's render settings (the frame graph can be accessed from here)
virtual void setEnabled(bool enable)
Enable or disable via enable the render view sub tree.
double elevationOffset() const
Returns the elevation offset of the terrain (used to move the terrain up or down).
Object that controls camera movement based on user input.
Qt3DRender::QCamera * camera() const
Returns camera that is being controlled.
void cameraChanged()
Emitted when camera has been updated.
void setLookingAtPoint(const QgsVector3D &point, float distance, float pitch, float yaw)
Sets the complete camera configuration: the point towards it is looking (in 3D world coordinates),...
QgsVector3D lookingAtPoint() const
Returns the point in the world coordinates towards which the camera is looking.
static QString axisDirectionToAbbreviatedString(Qgis::CrsAxisDirection axis)
Returns a translated abbreviation representing an axis direction.
QList< Qgis::CrsAxisDirection > axisOrdering() const
Returns an ordered list of the axis directions reflecting the native axis order for the CRS.
QgsAbstractRenderView * renderView(const QString &name)
Returns the render view named name, if any.
bool registerRenderView(std::unique_ptr< QgsAbstractRenderView > renderView, const QString &name)
Registers a new the render view renderView with name name.
static const QString AXIS3D_RENDERVIEW
static int debugLevel()
Reads the environment variable QGIS_DEBUG and converts it to int.
Definition qgslogger.h:112
static void warning(const QString &msg)
Goes to qWarning.
A 3D vector (similar to QVector3D) with the difference that it uses double precision instead of singl...
Definition qgsvector3d.h:30
double y() const
Returns Y coordinate.
Definition qgsvector3d.h:49
QVector3D toVector3D() const
Converts the current object to QVector3D.
double x() const
Returns X coordinate.
Definition qgsvector3d.h:47
void set(double x, double y, double z)
Sets vector coordinates.
Definition qgsvector3d.h:72
On-screen 3D engine: it creates an OpenGL window (QWindow) and displays rendered 3D scenes there.
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:41
const QgsCoordinateReferenceSystem & crs
Helper struct to store ray casting parameters.