QGIS API Documentation 3.41.0-Master (3440c17df1d)
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#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 "qgs3dmapscene.h"
40#include "qgsterrainentity.h"
43#include "qgswindow3dengine.h"
45#include "qgs3dwiredmesh_p.h"
46
48 Qt3DCore::QEntity *parent3DScene,
49 Qgs3DMapScene *mapScene,
50 QgsCameraController *cameraCtrl,
51 Qgs3DMapSettings *map )
52 : QObject( canvas )
53 , mMapSettings( map )
54 , mCanvas( canvas )
55 , mMapScene( mapScene )
56 , mCameraController( cameraCtrl )
57 , mCrs( map->crs() )
58{
59 mViewport = constructAxisScene( parent3DScene );
60 mViewport->setParent( mCanvas->activeFrameGraph() );
61
62 constructLabelsScene( parent3DScene );
63
64 connect( cameraCtrl, &QgsCameraController::cameraChanged, this, &Qgs3DAxis::onCameraUpdate );
65 connect( mCanvas, &Qgs3DMapCanvas::widthChanged, this, &Qgs3DAxis::onAxisViewportSizeUpdate );
66 connect( mCanvas, &Qgs3DMapCanvas::heightChanged, this, &Qgs3DAxis::onAxisViewportSizeUpdate );
67
68 createAxisScene();
69 onAxisViewportSizeUpdate();
70
71 init3DObjectPicking();
72
73 createKeyboardShortCut();
74}
75
77{
78 delete mMenu;
79 mMenu = nullptr;
80
81 // When an object (axis or cube) is not enabled. It is still present but it does not have a parent.
82 // In that case, it will never be automatically deleted. Therefore, it needs to be manually deleted.
83 // See setEnableCube() and setEnableAxis().
84 switch ( mMapSettings->get3DAxisSettings().mode() )
85 {
87 delete mCubeRoot;
88 mCubeRoot = nullptr;
89 break;
91 delete mAxisRoot;
92 mAxisRoot = nullptr;
93 break;
95 delete mAxisRoot;
96 mAxisRoot = nullptr;
97 delete mCubeRoot;
98 mCubeRoot = nullptr;
99 break;
100 }
101}
102
103void Qgs3DAxis::init3DObjectPicking( )
104{
105 mDefaultPickingMethod = mMapScene->engine()->renderSettings()->pickingSettings()->pickMethod();
106
107 // Create screencaster to be used by EventFilter:
108 // 1- Perform ray casting tests by specifying "touch" coordinates in screen space
109 // 2- connect screencaster results to onTouchedByRay
110 // 3- screencaster will be triggered by EventFilter
111 mScreenRayCaster = new Qt3DRender::QScreenRayCaster( mAxisSceneEntity );
112 mScreenRayCaster->addLayer( mAxisObjectLayer ); // to only filter on axis objects
113 mScreenRayCaster->setFilterMode( Qt3DRender::QScreenRayCaster::AcceptAllMatchingLayers );
114 mScreenRayCaster->setRunMode( Qt3DRender::QAbstractRayCaster::SingleShot );
115
116 mAxisSceneEntity->addComponent( mScreenRayCaster );
117
118 QObject::connect( mScreenRayCaster, &Qt3DRender::QScreenRayCaster::hitsChanged, this, &Qgs3DAxis::onTouchedByRay );
119
120 // we need event filter (see Qgs3DAxis::eventFilter) to handle the mouse click event as this event is not catchable via the Qt3DRender::QObjectPicker
121 mCanvas->installEventFilter( this );
122}
123
124bool Qgs3DAxis::eventFilter( QObject *watched, QEvent *event )
125{
126 if ( watched != mCanvas )
127 return false;
128
129 if ( event->type() == QEvent::MouseButtonPress )
130 {
131 // register mouse click to detect dragging
132 mHasClicked = true;
133 QMouseEvent *mouseEvent = static_cast<QMouseEvent *>( event );
134 mLastClickedPos = mouseEvent->pos();
135 }
136
137 // handle QEvent::MouseButtonRelease as it represents the end of click and QEvent::MouseMove.
138 else if ( event->type() == QEvent::MouseButtonRelease || event->type() == QEvent::MouseMove )
139 {
140 QMouseEvent *mouseEvent = static_cast<QMouseEvent *>( event );
141
142 // user has clicked and move ==> dragging start
143 if ( event->type() == QEvent::MouseMove &&
144 ( ( mHasClicked && ( mouseEvent->pos() - mLastClickedPos ).manhattanLength() < QApplication::startDragDistance() ) || mIsDragging ) )
145 {
146 mIsDragging = true;
147 }
148
149 // user has released ==> dragging ends
150 else if ( mIsDragging && event->type() == QEvent::MouseButtonRelease )
151 {
152 mIsDragging = false;
153 mHasClicked = false;
154 }
155
156 // user is moving or has released but not dragging
157 else if ( ! mIsDragging )
158 {
159 // limit ray caster usage to the axis viewport
160 QPointF normalizedPos( static_cast<float>( mouseEvent->pos().x() ) / static_cast<float>( mCanvas->width() ),
161 static_cast<float>( mouseEvent->pos().y() ) / static_cast<float>( mCanvas->height() ) );
162
163 if ( 2 <= QgsLogger::debugLevel() && event->type() == QEvent::MouseButtonRelease )
164 {
165 std::ostringstream os;
166 os << "QGS3DAxis: normalized pos: " << normalizedPos << " / viewport: " << mViewport->normalizedRect();
167 QgsDebugMsgLevel( os.str().c_str(), 2 );
168 }
169
170 if ( mViewport->normalizedRect().contains( normalizedPos ) )
171 {
172 mLastClickedButton = mouseEvent->button();
173 mLastClickedPos = mouseEvent->pos();
174
175 // if casted ray from pos matches an entity, call onTouchedByRay
176 mScreenRayCaster->trigger( mLastClickedPos );
177 }
178 // exit the viewport
179 else
180 {
181 // reset the mouse cursor if needed
182 if ( mPreviousCursor != Qt::ArrowCursor && mCanvas->cursor() == Qt::ArrowCursor )
183 {
184 mCanvas->setCursor( mPreviousCursor );
185 mPreviousCursor = Qt::ArrowCursor;
186 }
187
188 // reset the picking settings if needed
189 if ( mMapScene->engine()->renderSettings()->pickingSettings()->pickMethod() == Qt3DRender::QPickingSettings::TrianglePicking
190 && mDefaultPickingMethod != Qt3DRender::QPickingSettings::TrianglePicking )
191 {
192 mMapScene->engine()->renderSettings()->pickingSettings()->setPickMethod( mDefaultPickingMethod );
193 QgsDebugMsgLevel( "Disabling triangle picking", 2 );
194 }
195 }
196
197 mIsDragging = false; // drag ends
198 mHasClicked = false;
199 }
200 }
201
202 return false;
203}
204
205void Qgs3DAxis::onTouchedByRay( const Qt3DRender::QAbstractRayCaster::Hits &hits )
206{
207 int mHitsFound = -1;
208 if ( !hits.empty() )
209 {
210 if ( 2 <= QgsLogger::debugLevel() )
211 {
212 std::ostringstream os;
213 os << "Qgs3DAxis::onTouchedByRay " << hits.length() << " hits at pos " << mLastClickedPos << " with QButton: " << mLastClickedButton;
214 for ( int i = 0; i < hits.length(); ++i )
215 {
216 os << "\n";
217 os << "\tHit Type: " << hits.at( i ).type() << "\n";
218 os << "\tHit triangle id: " << hits.at( i ).primitiveIndex() << "\n";
219 os << "\tHit distance: " << hits.at( i ).distance() << "\n";
220 os << "\tHit entity name: " << hits.at( i ).entity()->objectName().toStdString();
221 }
222 QgsDebugMsgLevel( os.str().c_str(), 2 );
223 }
224
225 for ( int i = 0; i < hits.length() && mHitsFound == -1; ++i )
226 {
227 if ( hits.at( i ).distance() < 500.0f && hits.at( i ).entity() &&
228 ( hits.at( i ).entity() == mCubeRoot ||
229 hits.at( i ).entity() == mAxisRoot ||
230 hits.at( i ).entity()->parent() == mCubeRoot ||
231 hits.at( i ).entity()->parent() == mAxisRoot ) )
232 {
233 mHitsFound = i;
234 }
235 }
236 }
237
238 if ( mLastClickedButton == Qt::NoButton ) // hover
239 {
240 if ( mHitsFound != -1 )
241 {
242 if ( mCanvas->cursor() != Qt::ArrowCursor )
243 {
244 mPreviousCursor = mCanvas->cursor();
245 mCanvas->setCursor( Qt::ArrowCursor );
246 QgsDebugMsgLevel( "Enabling arrow cursor", 2 );
247
248 // The cube needs triangle picking to handle click on faces.
249 if ( mMapScene->engine()->renderSettings()->pickingSettings()->pickMethod() != Qt3DRender::QPickingSettings::TrianglePicking &&
250 mCubeRoot->isEnabled() )
251 {
252 mMapScene->engine()->renderSettings()->pickingSettings()->setPickMethod( Qt3DRender::QPickingSettings::TrianglePicking );
253 QgsDebugMsgLevel( "Enabling triangle picking", 2 );
254 }
255 }
256 }
257 }
258 else if ( mLastClickedButton == Qt::MouseButton::RightButton && mHitsFound != -1 ) // show menu
259 {
260 displayMenuAt( mLastClickedPos );
261 }
262 else if ( mLastClickedButton == Qt::MouseButton::LeftButton ) // handle cube face clicks
263 {
264 hideMenu();
265
266 if ( mHitsFound != -1 )
267 {
268 if ( hits.at( mHitsFound ).entity() == mCubeRoot || hits.at( mHitsFound ).entity()->parent() == mCubeRoot )
269 {
270 switch ( hits.at( mHitsFound ).primitiveIndex() / 2 )
271 {
272 case 0: // "East face";
273 QgsDebugMsgLevel( "Qgs3DAxis: East face clicked", 2 );
274 onCameraViewChangeEast();
275 break;
276
277 case 1: // "West face ";
278 QgsDebugMsgLevel( "Qgs3DAxis: West face clicked", 2 );
279 onCameraViewChangeWest();
280 break;
281
282 case 2: // "North face ";
283 QgsDebugMsgLevel( "Qgs3DAxis: North face clicked", 2 );
284 onCameraViewChangeNorth();
285 break;
286
287 case 3: // "South face";
288 QgsDebugMsgLevel( "Qgs3DAxis: South face clicked", 2 );
289 onCameraViewChangeSouth();
290 break;
291
292 case 4: // "Top face ";
293 QgsDebugMsgLevel( "Qgs3DAxis: Top face clicked", 2 );
294 onCameraViewChangeTop();
295 break;
296
297 case 5: // "Bottom face ";
298 QgsDebugMsgLevel( "Qgs3DAxis: Bottom face clicked", 2 );
299 onCameraViewChangeBottom();
300 break;
301
302 default:
303 break;
304 }
305 }
306 }
307 }
308}
309
310Qt3DRender::QViewport *Qgs3DAxis::constructAxisScene( Qt3DCore::QEntity *parent3DScene )
311{
312 Qt3DRender::QViewport *axisViewport = new Qt3DRender::QViewport;
313 // parent will be set later
314 // size will be set later
315
316 mAxisSceneEntity = new Qt3DCore::QEntity;
317 mAxisSceneEntity->setParent( parent3DScene );
318 mAxisSceneEntity->setObjectName( "3DAxis_SceneEntity" );
319
320 mAxisObjectLayer = new Qt3DRender::QLayer;
321 mAxisObjectLayer->setObjectName( "3DAxis_ObjectLayer" );
322 mAxisObjectLayer->setParent( mAxisSceneEntity );
323 mAxisObjectLayer->setRecursive( true );
324
325 mAxisCamera = new Qt3DRender::QCamera;
326 mAxisCamera->setParent( mAxisSceneEntity );
327 mAxisCamera->setProjectionType( mCameraController->camera()->projectionType() );
328 mAxisCamera->lens()->setFieldOfView( mCameraController->camera()->lens()->fieldOfView() * 0.5f );
329
330 mAxisCamera->setUpVector( QVector3D( 0.0f, 1.0f, 0.0f ) );
331 mAxisCamera->setViewCenter( QVector3D( 0.0f, 0.0f, 0.0f ) );
332 // position will be set later
333
334 Qt3DRender::QLayerFilter *axisLayerFilter = new Qt3DRender::QLayerFilter( axisViewport );
335 axisLayerFilter->addLayer( mAxisObjectLayer );
336
337 Qt3DRender::QCameraSelector *axisCameraSelector = new Qt3DRender::QCameraSelector;
338 axisCameraSelector->setParent( axisLayerFilter );
339 axisCameraSelector->setCamera( mAxisCamera );
340
341 // This ensures to have the labels (Text2DEntity) rendered after the other objects and therefore
342 // avoid any transparency issue on the labels.
343 Qt3DRender::QSortPolicy *sortPolicy = new Qt3DRender::QSortPolicy( axisCameraSelector );
344 QVector<Qt3DRender::QSortPolicy::SortType> sortTypes = QVector<Qt3DRender::QSortPolicy::SortType>();
345 sortTypes << Qt3DRender::QSortPolicy::BackToFront;
346 sortPolicy->setSortTypes( sortTypes );
347
348 Qt3DRender::QClearBuffers *clearBuffers = new Qt3DRender::QClearBuffers( sortPolicy );
349 clearBuffers->setBuffers( Qt3DRender::QClearBuffers::DepthBuffer );
350
351 // cppcheck-suppress memleak
352 return axisViewport;
353}
354
355void Qgs3DAxis::constructLabelsScene( Qt3DCore::QEntity *parent3DScene )
356{
357 mTwoDLabelSceneEntity = new Qt3DCore::QEntity;
358 mTwoDLabelSceneEntity->setParent( parent3DScene );
359 mTwoDLabelSceneEntity->setEnabled( true );
360
361 mTwoDLabelCamera = new Qt3DRender::QCamera;
362 mTwoDLabelCamera->setParent( mTwoDLabelSceneEntity );
363 mTwoDLabelCamera->setProjectionType( Qt3DRender::QCameraLens::ProjectionType::OrthographicProjection );
364 // the camera lens parameters are defined by onAxisViewportSizeUpdate()
365
366 mTwoDLabelCamera->setUpVector( QVector3D( 0.0f, 0.0f, 1.0f ) );
367 mTwoDLabelCamera->setViewCenter( QVector3D( 0.0f, 0.0f, 0.0f ) );
368
369 mTwoDLabelCamera->setPosition( QVector3D( 0.0f, 0.0f, 100.0f ) );
370
371 Qt3DRender::QLayer *twoDLayer = new Qt3DRender::QLayer;
372 twoDLayer->setObjectName( "3DAxis_LabelsLayer" );
373 twoDLayer->setRecursive( true );
374 mTwoDLabelSceneEntity->addComponent( twoDLayer );
375
376 Qt3DRender::QLayerFilter *twoDLayerFilter = new Qt3DRender::QLayerFilter;
377 twoDLayerFilter->addLayer( twoDLayer );
378
379 Qt3DRender::QCameraSelector *twoDCameraSelector = new Qt3DRender::QCameraSelector;
380 twoDCameraSelector->setParent( twoDLayerFilter );
381 twoDCameraSelector->setCamera( mTwoDLabelCamera );
382
383 // this ensures to have the labels (Text2DEntity) rendered after the other objects and therefore
384 // avoid any transparency issue on the labels.
385 Qt3DRender::QSortPolicy *sortPolicy = new Qt3DRender::QSortPolicy( twoDCameraSelector );
386 QVector<Qt3DRender::QSortPolicy::SortType> sortTypes = QVector<Qt3DRender::QSortPolicy::SortType>();
387 sortTypes << Qt3DRender::QSortPolicy::BackToFront;
388 sortPolicy->setSortTypes( sortTypes );
389
390 Qt3DRender::QClearBuffers *clearBuffers = new Qt3DRender::QClearBuffers( sortPolicy );
391 clearBuffers->setBuffers( Qt3DRender::QClearBuffers::DepthBuffer );
392
393 twoDLayerFilter->setParent( mViewport );
394}
395
396QVector3D Qgs3DAxis::from3DTo2DLabelPosition( const QVector3D &sourcePos, Qt3DRender::QCamera *sourceCamera, Qt3DRender::QCamera *destCamera )
397{
398 const int viewportWidth = static_cast<int>( std::round( mTwoDLabelCamera->lens()->right() - mTwoDLabelCamera->lens()->left() ) );
399 const int viewportHeight = static_cast<int>( std::round( mTwoDLabelCamera->lens()->top() - mTwoDLabelCamera->lens()->bottom() ) );
400 QRect viewportRect( static_cast<int>( std::round( mTwoDLabelCamera->lens()->left() ) ), static_cast<int>( std::round( mTwoDLabelCamera->lens()->bottom() ) ),
401 viewportWidth, viewportHeight );
402
403 QVector3D destPos = sourcePos.project( sourceCamera->viewMatrix(), destCamera->projectionMatrix(), viewportRect );
404 destPos.setZ( 0.0f );
405 return destPos;
406}
407
408void Qgs3DAxis::setEnableCube( bool show )
409{
410 mCubeRoot->setEnabled( show );
411 if ( show )
412 {
413 mCubeRoot->setParent( mAxisSceneEntity );
414 }
415 else
416 {
417 mCubeRoot->setParent( static_cast<Qt3DCore::QEntity *>( nullptr ) );
418 }
419}
420
421void Qgs3DAxis::setEnableAxis( bool show )
422{
423 mAxisRoot->setEnabled( show );
424 if ( show )
425 {
426 mAxisRoot->setParent( mAxisSceneEntity );
427 }
428 else
429 {
430 mAxisRoot->setParent( static_cast<Qt3DCore::QEntity *>( nullptr ) );
431 }
432
433 mTextX->setEnabled( show );
434 mTextY->setEnabled( show );
435 mTextZ->setEnabled( show );
436}
437
438void Qgs3DAxis::createAxisScene()
439{
440 if ( !mAxisRoot || !mCubeRoot )
441 {
442 mAxisRoot = new Qt3DCore::QEntity;
443 mAxisRoot->setParent( mAxisSceneEntity );
444 mAxisRoot->setObjectName( "3DAxis_AxisRoot" );
445 mAxisRoot->addComponent( mAxisObjectLayer ); // raycaster will filter object containing this layer
446
447 createAxis( Qt::Axis::XAxis );
448 createAxis( Qt::Axis::YAxis );
449 createAxis( Qt::Axis::ZAxis );
450
451 mCubeRoot = new Qt3DCore::QEntity;
452 mCubeRoot->setParent( mAxisSceneEntity );
453 mCubeRoot->setObjectName( "3DAxis_CubeRoot" );
454 mCubeRoot->addComponent( mAxisObjectLayer ); // raycaster will filter object containing this layer
455
456 createCube( );
457 }
458
459 Qgs3DAxisSettings::Mode mode = mMapSettings->get3DAxisSettings().mode();
460
461 if ( mode == Qgs3DAxisSettings::Mode::Off )
462 {
463 mAxisSceneEntity->setEnabled( false );
464 setEnableAxis( false );
465 setEnableCube( false );
466 }
467 else
468 {
469 mAxisSceneEntity->setEnabled( true );
470 if ( mode == Qgs3DAxisSettings::Mode::Crs )
471 {
472 setEnableCube( false );
473 setEnableAxis( true );
474
475 const QList< Qgis::CrsAxisDirection > axisDirections = mCrs.axisOrdering();
476
477 if ( axisDirections.length() > 0 )
478 mTextX->setText( QgsCoordinateReferenceSystemUtils::axisDirectionToAbbreviatedString( axisDirections.at( 0 ) ) );
479 else
480 mTextX->setText( "X?" );
481
482 if ( axisDirections.length() > 1 )
483 mTextY->setText( QgsCoordinateReferenceSystemUtils::axisDirectionToAbbreviatedString( axisDirections.at( 1 ) ) );
484 else
485 mTextY->setText( "Y?" );
486
487 if ( axisDirections.length() > 2 )
488 mTextZ->setText( QgsCoordinateReferenceSystemUtils::axisDirectionToAbbreviatedString( axisDirections.at( 2 ) ) );
489 else
490 mTextZ->setText( QStringLiteral( "up" ) );
491 }
492 else if ( mode == Qgs3DAxisSettings::Mode::Cube )
493 {
494 setEnableCube( true );
495 setEnableAxis( false );
496 }
497 else
498 {
499 setEnableCube( false );
500 setEnableAxis( true );
501 mTextX->setText( "X?" );
502 mTextY->setText( "Y?" );
503 mTextZ->setText( "Z?" );
504 }
505
506 updateAxisLabelPosition();
507 }
508}
509
510void Qgs3DAxis::createKeyboardShortCut()
511{
512 QgsWindow3DEngine *eng = dynamic_cast<QgsWindow3DEngine *>( mMapScene->engine() );
513 if ( eng )
514 {
515 QWidget *mapCanvas = dynamic_cast<QWidget *>( eng->parent() );
516 if ( !mapCanvas )
517 {
518 QgsLogger::warning( "Qgs3DAxis: no canvas defined!" );
519 }
520 else
521 {
522 QShortcut *shortcutHome = new QShortcut( QKeySequence( Qt::CTRL + Qt::Key_1 ), mapCanvas );
523 connect( shortcutHome, &QShortcut::activated, this, [this]( ) {onCameraViewChangeHome();} );
524
525 QShortcut *shortcutTop = new QShortcut( QKeySequence( Qt::CTRL + Qt::Key_5 ), mapCanvas );
526 connect( shortcutTop, &QShortcut::activated, this, [this]( ) {onCameraViewChangeTop();} );
527
528 QShortcut *shortcutNorth = new QShortcut( QKeySequence( Qt::CTRL + Qt::Key_8 ), mapCanvas );
529 connect( shortcutNorth, &QShortcut::activated, this, [this]( ) {onCameraViewChangeNorth();} );
530
531 QShortcut *shortcutEast = new QShortcut( QKeySequence( Qt::CTRL + Qt::Key_6 ), mapCanvas );
532 connect( shortcutEast, &QShortcut::activated, this, [this]( ) {onCameraViewChangeEast();} );
533
534 QShortcut *shortcutSouth = new QShortcut( QKeySequence( Qt::CTRL + Qt::Key_2 ), mapCanvas );
535 connect( shortcutSouth, &QShortcut::activated, this, [this]( ) {onCameraViewChangeSouth();} );
536
537 QShortcut *shortcutWest = new QShortcut( QKeySequence( Qt::CTRL + Qt::Key_4 ), mapCanvas );
538 connect( shortcutWest, &QShortcut::activated, this, [this]( ) {onCameraViewChangeWest();} );
539 }
540 }
541}
542
543void Qgs3DAxis::createMenu()
544{
545 mMenu = new QMenu();
546
547 // axis type menu
548 QAction *typeOffAct = new QAction( tr( "&Off" ), mMenu );
549 typeOffAct->setCheckable( true );
550 typeOffAct->setStatusTip( tr( "Disable 3D axis" ) );
551 connect( mMapSettings, &Qgs3DMapSettings::axisSettingsChanged, this, [typeOffAct, this]()
552 {
553 if ( mMapSettings->get3DAxisSettings().mode() == Qgs3DAxisSettings::Mode::Off )
554 typeOffAct->setChecked( true );
555 } );
556
557 QAction *typeCrsAct = new QAction( tr( "Coordinate Reference &System" ), mMenu );
558 typeCrsAct->setCheckable( true );
559 typeCrsAct->setStatusTip( tr( "Coordinate Reference System 3D axis" ) );
560 connect( mMapSettings, &Qgs3DMapSettings::axisSettingsChanged, this, [typeCrsAct, this]()
561 {
562 if ( mMapSettings->get3DAxisSettings().mode() == Qgs3DAxisSettings::Mode::Crs )
563 typeCrsAct->setChecked( true );
564 } );
565
566 QAction *typeCubeAct = new QAction( tr( "&Cube" ), mMenu );
567 typeCubeAct->setCheckable( true );
568 typeCubeAct->setStatusTip( tr( "Cube 3D axis" ) );
569 connect( mMapSettings, &Qgs3DMapSettings::axisSettingsChanged, this, [typeCubeAct, this]()
570 {
571 if ( mMapSettings->get3DAxisSettings().mode() == Qgs3DAxisSettings::Mode::Cube )
572 typeCubeAct->setChecked( true );
573 } );
574
575 QActionGroup *typeGroup = new QActionGroup( mMenu );
576 typeGroup->addAction( typeOffAct );
577 typeGroup->addAction( typeCrsAct );
578 typeGroup->addAction( typeCubeAct );
579
580 connect( typeOffAct, &QAction::triggered, this, [this]( bool ) {onAxisModeChanged( Qgs3DAxisSettings::Mode::Off );} );
581 connect( typeCrsAct, &QAction::triggered, this, [this]( bool ) {onAxisModeChanged( Qgs3DAxisSettings::Mode::Crs );} );
582 connect( typeCubeAct, &QAction::triggered, this, [this]( bool ) {onAxisModeChanged( Qgs3DAxisSettings::Mode::Cube );} );
583
584 QMenu *typeMenu = new QMenu( QStringLiteral( "Axis Type" ), mMenu );
585 Q_ASSERT( typeMenu );
586 typeMenu->addAction( typeOffAct );
587 typeMenu->addAction( typeCrsAct );
588 typeMenu->addAction( typeCubeAct );
589 mMenu->addMenu( typeMenu );
590
591 // horizontal position menu
592 QAction *hPosLeftAct = new QAction( tr( "&Left" ), mMenu );
593 hPosLeftAct->setCheckable( true );
594 connect( mMapSettings, &Qgs3DMapSettings::axisSettingsChanged, this, [hPosLeftAct, this]()
595 {
596 if ( mMapSettings->get3DAxisSettings().horizontalPosition() == Qt::AnchorPoint::AnchorLeft )
597 hPosLeftAct->setChecked( true );
598 } );
599
600 QAction *hPosMiddleAct = new QAction( tr( "&Center" ), mMenu );
601 hPosMiddleAct->setCheckable( true );
602 connect( mMapSettings, &Qgs3DMapSettings::axisSettingsChanged, this, [hPosMiddleAct, this]()
603 {
604 if ( mMapSettings->get3DAxisSettings().horizontalPosition() == Qt::AnchorPoint::AnchorHorizontalCenter )
605 hPosMiddleAct->setChecked( true );
606 } );
607
608 QAction *hPosRightAct = new QAction( tr( "&Right" ), mMenu );
609 hPosRightAct->setCheckable( true );
610 connect( mMapSettings, &Qgs3DMapSettings::axisSettingsChanged, this, [hPosRightAct, this]()
611 {
612 if ( mMapSettings->get3DAxisSettings().horizontalPosition() == Qt::AnchorPoint::AnchorRight )
613 hPosRightAct->setChecked( true );
614 } );
615
616 QActionGroup *hPosGroup = new QActionGroup( mMenu );
617 hPosGroup->addAction( hPosLeftAct );
618 hPosGroup->addAction( hPosMiddleAct );
619 hPosGroup->addAction( hPosRightAct );
620
621 connect( hPosLeftAct, &QAction::triggered, this, [this]( bool ) {onAxisHorizPositionChanged( Qt::AnchorPoint::AnchorLeft );} );
622 connect( hPosMiddleAct, &QAction::triggered, this, [this]( bool ) {onAxisHorizPositionChanged( Qt::AnchorPoint::AnchorHorizontalCenter );} );
623 connect( hPosRightAct, &QAction::triggered, this, [this]( bool ) {onAxisHorizPositionChanged( Qt::AnchorPoint::AnchorRight );} );
624
625 QMenu *horizPosMenu = new QMenu( QStringLiteral( "Horizontal Position" ), mMenu );
626 horizPosMenu->addAction( hPosLeftAct );
627 horizPosMenu->addAction( hPosMiddleAct );
628 horizPosMenu->addAction( hPosRightAct );
629 mMenu->addMenu( horizPosMenu );
630
631 // vertical position menu
632 QAction *vPosTopAct = new QAction( tr( "&Top" ), mMenu );
633 vPosTopAct->setCheckable( true );
634 connect( mMapSettings, &Qgs3DMapSettings::axisSettingsChanged, this, [vPosTopAct, this]()
635 {
636 if ( mMapSettings->get3DAxisSettings().verticalPosition() == Qt::AnchorPoint::AnchorTop )
637 vPosTopAct->setChecked( true );
638 } );
639
640 QAction *vPosMiddleAct = new QAction( tr( "&Middle" ), mMenu );
641 vPosMiddleAct->setCheckable( true );
642 connect( mMapSettings, &Qgs3DMapSettings::axisSettingsChanged, this, [vPosMiddleAct, this]()
643 {
644 if ( mMapSettings->get3DAxisSettings().verticalPosition() == Qt::AnchorPoint::AnchorVerticalCenter )
645 vPosMiddleAct->setChecked( true );
646 } );
647
648 QAction *vPosBottomAct = new QAction( tr( "&Bottom" ), mMenu );
649 vPosBottomAct->setCheckable( true );
650 connect( mMapSettings, &Qgs3DMapSettings::axisSettingsChanged, this, [vPosBottomAct, this]()
651 {
652 if ( mMapSettings->get3DAxisSettings().verticalPosition() == Qt::AnchorPoint::AnchorBottom )
653 vPosBottomAct->setChecked( true );
654 } );
655
656 QActionGroup *vPosGroup = new QActionGroup( mMenu );
657 vPosGroup->addAction( vPosTopAct );
658 vPosGroup->addAction( vPosMiddleAct );
659 vPosGroup->addAction( vPosBottomAct );
660
661 connect( vPosTopAct, &QAction::triggered, this, [this]( bool ) {onAxisVertPositionChanged( Qt::AnchorPoint::AnchorTop );} );
662 connect( vPosMiddleAct, &QAction::triggered, this, [this]( bool ) {onAxisVertPositionChanged( Qt::AnchorPoint::AnchorVerticalCenter );} );
663 connect( vPosBottomAct, &QAction::triggered, this, [this]( bool ) {onAxisVertPositionChanged( Qt::AnchorPoint::AnchorBottom );} );
664
665 QMenu *vertPosMenu = new QMenu( QStringLiteral( "Vertical Position" ), mMenu );
666 vertPosMenu->addAction( vPosTopAct );
667 vertPosMenu->addAction( vPosMiddleAct );
668 vertPosMenu->addAction( vPosBottomAct );
669 mMenu->addMenu( vertPosMenu );
670
671 // axis view menu
672 QAction *viewHomeAct = new QAction( tr( "&Home" ) + "\t Ctrl+1", mMenu );
673 QAction *viewTopAct = new QAction( tr( "&Top" ) + "\t Ctrl+5", mMenu );
674 QAction *viewNorthAct = new QAction( tr( "&North" ) + "\t Ctrl+8", mMenu );
675 QAction *viewEastAct = new QAction( tr( "&East" ) + "\t Ctrl+6", mMenu );
676 QAction *viewSouthAct = new QAction( tr( "&South" ) + "\t Ctrl+2", mMenu );
677 QAction *viewWestAct = new QAction( tr( "&West" ) + "\t Ctrl+4", mMenu );
678 QAction *viewBottomAct = new QAction( tr( "&Bottom" ), mMenu );
679
680 connect( viewHomeAct, &QAction::triggered, this, &Qgs3DAxis::onCameraViewChangeHome );
681 connect( viewTopAct, &QAction::triggered, this, &Qgs3DAxis::onCameraViewChangeTop );
682 connect( viewNorthAct, &QAction::triggered, this, &Qgs3DAxis::onCameraViewChangeNorth );
683 connect( viewEastAct, &QAction::triggered, this, &Qgs3DAxis::onCameraViewChangeEast );
684 connect( viewSouthAct, &QAction::triggered, this, &Qgs3DAxis::onCameraViewChangeSouth );
685 connect( viewWestAct, &QAction::triggered, this, &Qgs3DAxis::onCameraViewChangeWest );
686 connect( viewBottomAct, &QAction::triggered, this, &Qgs3DAxis::onCameraViewChangeBottom );
687
688 QMenu *viewMenu = new QMenu( QStringLiteral( "Camera View" ), mMenu );
689 viewMenu->addAction( viewHomeAct );
690 viewMenu->addAction( viewTopAct );
691 viewMenu->addAction( viewNorthAct );
692 viewMenu->addAction( viewEastAct );
693 viewMenu->addAction( viewSouthAct );
694 viewMenu->addAction( viewWestAct );
695 viewMenu->addAction( viewBottomAct );
696 mMenu->addMenu( viewMenu );
697
698 // update checkable items
699 mMapSettings->set3DAxisSettings( mMapSettings->get3DAxisSettings(), true );
700}
701
702void Qgs3DAxis::hideMenu()
703{
704 if ( mMenu && mMenu->isVisible() )
705 mMenu->hide();
706}
707
708void Qgs3DAxis::displayMenuAt( const QPoint &sourcePos )
709{
710 if ( !mMenu )
711 {
712 createMenu();
713 }
714
715 mMenu->popup( mCanvas->mapToGlobal( sourcePos ) );
716}
717
718void Qgs3DAxis::onAxisModeChanged( Qgs3DAxisSettings::Mode mode )
719{
720 Qgs3DAxisSettings s = mMapSettings->get3DAxisSettings();
721 s.setMode( mode );
722 mMapSettings->set3DAxisSettings( s );
723}
724
725void Qgs3DAxis::onAxisHorizPositionChanged( Qt::AnchorPoint pos )
726{
727 Qgs3DAxisSettings s = mMapSettings->get3DAxisSettings();
728 s.setHorizontalPosition( pos );
729 mMapSettings->set3DAxisSettings( s );
730}
731
732void Qgs3DAxis::onAxisVertPositionChanged( Qt::AnchorPoint pos )
733{
734 Qgs3DAxisSettings s = mMapSettings->get3DAxisSettings();
735 s.setVerticalPosition( pos );
736 mMapSettings->set3DAxisSettings( s );
737}
738
739void Qgs3DAxis::onCameraViewChange( float pitch, float yaw )
740{
741 QgsVector3D pos = mCameraController->lookingAtPoint();
742 double elevation = 0.0;
743 if ( mMapSettings->terrainRenderingEnabled() )
744 {
745 QgsDebugMsgLevel( "Checking elevation from terrain...", 2 );
746 QVector3D camPos = mCameraController->camera()->position();
747 QgsRayCastingUtils::Ray3D ray( camPos, pos.toVector3D() - camPos, mCameraController->camera()->farPlane() );
748 const QVector<QgsRayCastingUtils::RayHit> hits = mMapScene->terrainEntity()->rayIntersection( ray, QgsRayCastingUtils::RayCastContext() );
749 if ( !hits.isEmpty() )
750 {
751 elevation = hits.at( 0 ).pos.z();
752 QgsDebugMsgLevel( QString( "Computed elevation from terrain: %1" ).arg( elevation ), 2 );
753 }
754 else
755 {
756 QgsDebugMsgLevel( "Unable to obtain elevation from terrain", 2 );
757 }
758 }
759 pos.set( pos.x(), pos.y(), elevation + mMapSettings->terrainElevationOffset() );
760
761 mCameraController->setLookingAtPoint( pos, ( mCameraController->camera()->position() - pos.toVector3D() ).length(),
762 pitch, yaw );
763}
764
765
766void Qgs3DAxis::createCube( )
767{
768 QVector3D minPos = QVector3D( -mCylinderLength * 0.5f, -mCylinderLength * 0.5f, -mCylinderLength * 0.5f );
769
770 // cube outlines
771 Qt3DCore::QEntity *cubeLineEntity = new Qt3DCore::QEntity( mCubeRoot );
772 cubeLineEntity->setObjectName( "3DAxis_cubeline" );
773 Qgs3DWiredMesh *cubeLine = new Qgs3DWiredMesh;
774 QgsAABB box = QgsAABB( -mCylinderLength * 0.5f, -mCylinderLength * 0.5f, -mCylinderLength * 0.5f,
775 mCylinderLength * 0.5f, mCylinderLength * 0.5f, mCylinderLength * 0.5f );
776 cubeLine->setVertices( box.verticesForLines() );
777 cubeLineEntity->addComponent( cubeLine );
778
779 Qt3DExtras::QPhongMaterial *cubeLineMaterial = new Qt3DExtras::QPhongMaterial;
780 cubeLineMaterial->setAmbient( Qt::white );
781 cubeLineEntity->addComponent( cubeLineMaterial );
782
783 // cube mesh
784 Qt3DExtras::QCuboidMesh *cubeMesh = new Qt3DExtras::QCuboidMesh;
785 cubeMesh->setObjectName( "3DAxis_cubemesh" );
786 cubeMesh->setXExtent( mCylinderLength );
787 cubeMesh->setYExtent( mCylinderLength );
788 cubeMesh->setZExtent( mCylinderLength );
789 mCubeRoot->addComponent( cubeMesh );
790
791 Qt3DExtras::QPhongMaterial *cubeMaterial = new Qt3DExtras::QPhongMaterial( mCubeRoot );
792 cubeMaterial->setAmbient( QColor( 100, 100, 100, 50 ) );
793 cubeMaterial->setShininess( 100 );
794 mCubeRoot->addComponent( cubeMaterial );
795
796 Qt3DCore::QTransform *cubeTransform = new Qt3DCore::QTransform;
797 QMatrix4x4 transformMatrixcube;
798 //transformMatrixcube.rotate( rotation );
799 transformMatrixcube.translate( minPos + QVector3D( mCylinderLength * 0.5f, mCylinderLength * 0.5f, mCylinderLength * 0.5f ) );
800 cubeTransform->setMatrix( transformMatrixcube );
801 mCubeRoot->addComponent( cubeTransform );
802
803 // text
804 QString text;
805 const int fontSize = static_cast<int>( std::round( 0.75f * static_cast<float>( mFontSize ) ) );
806 const float textHeight = static_cast<float>( fontSize ) * 1.5f;
807 float textWidth;
808 const QFont font = createFont( fontSize );
809
810 {
811 text = QStringLiteral( "top" );
812 textWidth = static_cast<float>( text.length() * fontSize ) * 0.75f;
813 QVector3D translation = minPos + QVector3D(
814 mCylinderLength * 0.5f - textWidth / 2.0f,
815 mCylinderLength * 0.5f - textHeight / 2.0f,
816 mCylinderLength * 1.01f );
817 QMatrix4x4 rotation;
818 mCubeLabels << addCubeText( text, textHeight, textWidth, font, rotation, translation );
819 }
820
821 {
822 text = QStringLiteral( "btm" );
823 textWidth = static_cast<float>( text.length() * fontSize ) * 0.75f;
824 QVector3D translation = minPos + QVector3D(
825 mCylinderLength * 0.5f - textWidth / 2.0f,
826 mCylinderLength * 0.5f + textHeight / 2.0f,
827 -mCylinderLength * 0.01f );
828 QMatrix4x4 rotation;
829 rotation.rotate( 180.0f, QVector3D( 1.0f, 0.0f, 0.0f ).normalized() );
830 mCubeLabels << addCubeText( text, textHeight, textWidth, font, rotation, translation );
831 }
832
833 {
834 text = QStringLiteral( "west" );
835 textWidth = static_cast<float>( text.length() * fontSize ) * 0.75f;
836 QVector3D translation = minPos + QVector3D(
837 - mCylinderLength * 0.01f,
838 mCylinderLength * 0.5f + textWidth / 2.0f,
839 mCylinderLength * 0.5f - textHeight / 2.0f );
840 QMatrix4x4 rotation;
841 rotation.rotate( 90.0f, QVector3D( 0.0f, -1.0f, 0.0f ).normalized() );
842 rotation.rotate( 90.0f, QVector3D( 0.0f, 0.0f, -1.0f ).normalized() );
843 mCubeLabels << addCubeText( text, textHeight, textWidth, font, rotation, translation );
844 }
845
846 {
847 text = QStringLiteral( "east" );
848 textWidth = static_cast<float>( text.length() * fontSize ) * 0.75f;
849 QVector3D translation = minPos + QVector3D(
850 mCylinderLength * 1.01f,
851 mCylinderLength * 0.5f - textWidth / 2.0f,
852 mCylinderLength * 0.5f - textHeight / 2.0f );
853 QMatrix4x4 rotation;
854 rotation.rotate( 90.0f, QVector3D( 0.0f, 1.0f, 0.0f ).normalized() );
855 rotation.rotate( 90.0f, QVector3D( 0.0f, 0.0f, 1.0f ).normalized() );
856 mCubeLabels << addCubeText( text, textHeight, textWidth, font, rotation, translation );
857 }
858
859 {
860 text = QStringLiteral( "south" );
861 textWidth = static_cast<float>( text.length() * fontSize ) * 0.75f;
862 QVector3D translation = minPos + QVector3D(
863 mCylinderLength * 0.5f - textWidth / 2.0f,
864 - mCylinderLength * 0.01f,
865 mCylinderLength * 0.5f - textHeight / 2.0f );
866 QMatrix4x4 rotation;
867 rotation.rotate( 90.0f, QVector3D( 1.0f, 0.0f, 0.0f ).normalized() );
868 mCubeLabels << addCubeText( text, textHeight, textWidth, font, rotation, translation );
869 }
870
871 {
872 text = QStringLiteral( "north" );
873 textWidth = static_cast<float>( text.length() * fontSize ) * 0.75f;
874 QVector3D translation = minPos + QVector3D(
875 mCylinderLength * 0.5f + textWidth / 2.0f,
876 mCylinderLength * 1.01f,
877 mCylinderLength * 0.5f - textHeight / 2.0f );
878 QMatrix4x4 rotation;
879 rotation.rotate( 90.0f, QVector3D( -1.0f, 0.0f, 0.0f ).normalized() );
880 rotation.rotate( 180.0f, QVector3D( 0.0f, 0.0f, 1.0f ).normalized() );
881 mCubeLabels << addCubeText( text, textHeight, textWidth, font, rotation, translation );
882 }
883
884 for ( Qt3DExtras::QText2DEntity *l : std::as_const( mCubeLabels ) )
885 {
886 l->setParent( mCubeRoot );
887 }
888}
889
890Qt3DExtras::QText2DEntity *Qgs3DAxis::addCubeText( const QString &text, float textHeight, float textWidth, const QFont &font, const QMatrix4x4 &rotation, const QVector3D &translation )
891{
892 Qt3DExtras::QText2DEntity *textEntity = new Qt3DExtras::QText2DEntity;
893 textEntity->setObjectName( "3DAxis_cube_label_" + text );
894 textEntity->setFont( font );
895 textEntity->setHeight( textHeight );
896 textEntity->setWidth( textWidth );
897 textEntity->setColor( QColor( 192, 192, 192 ) );
898 textEntity->setText( text );
899
900 Qt3DCore::QTransform *textFrontTransform = new Qt3DCore::QTransform();
901 textFrontTransform->setMatrix( rotation );
902 textFrontTransform->setTranslation( translation );
903 textEntity->addComponent( textFrontTransform );
904
905 return textEntity;
906}
907
908void Qgs3DAxis::createAxis( Qt::Axis axisType )
909{
910 float cylinderRadius = 0.05f * mCylinderLength;
911 float coneLength = 0.3f * mCylinderLength;
912 float coneBottomRadius = 0.1f * mCylinderLength;
913
914 QQuaternion rotation;
915 QColor color;
916
917 Qt3DExtras::QText2DEntity *text = nullptr;
918 Qt3DCore::QTransform *textTransform = nullptr;
919 QString name;
920
921 switch ( axisType )
922 {
923 case Qt::Axis::XAxis:
924 mTextX = new Qt3DExtras::QText2DEntity( ); // object initialization in two step:
925 mTextX->setParent( mTwoDLabelSceneEntity ); // see https://bugreports.qt.io/browse/QTBUG-77139
926 connect( mTextX, &Qt3DExtras::QText2DEntity::textChanged, this, [this]( const QString & text )
927 {
928 updateAxisLabelText( mTextX, text );
929 } );
930 mTextTransformX = new Qt3DCore::QTransform();
931 mTextCoordX = QVector3D( mCylinderLength + coneLength / 2.0f, 0.0f, 0.0f );
932
933 rotation = QQuaternion::fromAxisAndAngle( QVector3D( 0.0f, 0.0f, 1.0f ), -90.0f );
934 color = Qt::red;
935 text = mTextX;
936 textTransform = mTextTransformX;
937 name = "3DAxis_axisX";
938 break;
939
940 case Qt::Axis::YAxis:
941 mTextY = new Qt3DExtras::QText2DEntity( ); // object initialization in two step:
942 mTextY->setParent( mTwoDLabelSceneEntity ); // see https://bugreports.qt.io/browse/QTBUG-77139
943 connect( mTextY, &Qt3DExtras::QText2DEntity::textChanged, this, [this]( const QString & text )
944 {
945 updateAxisLabelText( mTextY, text );
946 } );
947 mTextTransformY = new Qt3DCore::QTransform();
948 mTextCoordY = QVector3D( 0.0f, mCylinderLength + coneLength / 2.0f, 0.0f );
949
950 // no rotation
951
952 color = Qt::green;
953 text = mTextY;
954 textTransform = mTextTransformY;
955 name = "3DAxis_axisY";
956 break;
957
958 case Qt::Axis::ZAxis:
959 mTextZ = new Qt3DExtras::QText2DEntity( ); // object initialization in two step:
960 mTextZ->setParent( mTwoDLabelSceneEntity ); // see https://bugreports.qt.io/browse/QTBUG-77139
961 connect( mTextZ, &Qt3DExtras::QText2DEntity::textChanged, this, [this]( const QString & text )
962 {
963 updateAxisLabelText( mTextZ, text );
964 } );
965 mTextTransformZ = new Qt3DCore::QTransform();
966 mTextCoordZ = QVector3D( 0.0f, 0.0f, mCylinderLength + coneLength / 2.0f );
967
968 rotation = QQuaternion::fromAxisAndAngle( QVector3D( 1.0f, 0.0f, 0.0f ), 90.0f );
969 color = Qt::blue;
970 text = mTextZ;
971 textTransform = mTextTransformZ;
972 name = "3DAxis_axisZ";
973 break;
974
975 default:
976 return;
977 }
978
979 // cylinder
980 Qt3DCore::QEntity *cylinder = new Qt3DCore::QEntity( mAxisRoot );
981 cylinder->setObjectName( name );
982
983 Qt3DExtras::QCylinderMesh *cylinderMesh = new Qt3DExtras::QCylinderMesh;
984 cylinderMesh->setRadius( cylinderRadius );
985 cylinderMesh->setLength( mCylinderLength );
986 cylinderMesh->setRings( 10 );
987 cylinderMesh->setSlices( 4 );
988 cylinder->addComponent( cylinderMesh );
989
990 Qt3DExtras::QPhongMaterial *cylinderMaterial = new Qt3DExtras::QPhongMaterial( cylinder );
991 cylinderMaterial->setAmbient( color );
992 cylinderMaterial->setShininess( 0 );
993 cylinder->addComponent( cylinderMaterial );
994
995 Qt3DCore::QTransform *cylinderTransform = new Qt3DCore::QTransform;
996 QMatrix4x4 transformMatrixCylinder;
997 transformMatrixCylinder.rotate( rotation );
998 transformMatrixCylinder.translate( QVector3D( 0.0f, mCylinderLength / 2.0f, 0.0f ) );
999 cylinderTransform->setMatrix( transformMatrixCylinder );
1000 cylinder->addComponent( cylinderTransform );
1001
1002 // cone
1003 Qt3DCore::QEntity *coneEntity = new Qt3DCore::QEntity( mAxisRoot );
1004 coneEntity->setObjectName( name );
1005 Qt3DExtras::QConeMesh *coneMesh = new Qt3DExtras::QConeMesh;
1006 coneMesh->setLength( coneLength );
1007 coneMesh->setBottomRadius( coneBottomRadius );
1008 coneMesh->setTopRadius( 0.0f );
1009 coneMesh->setRings( 10 );
1010 coneMesh->setSlices( 4 );
1011 coneEntity->addComponent( coneMesh );
1012
1013 Qt3DExtras::QPhongMaterial *coneMaterial = new Qt3DExtras::QPhongMaterial( coneEntity );
1014 coneMaterial->setAmbient( color );
1015 coneMaterial->setShininess( 0 );
1016 coneEntity->addComponent( coneMaterial );
1017
1018 Qt3DCore::QTransform *coneTransform = new Qt3DCore::QTransform;
1019 QMatrix4x4 transformMatrixCone;
1020 transformMatrixCone.rotate( rotation );
1021 transformMatrixCone.translate( QVector3D( 0.0f, mCylinderLength, 0.0f ) );
1022 coneTransform->setMatrix( transformMatrixCone );
1023 coneEntity->addComponent( coneTransform );
1024
1025 // text font, height and width will be set later in onText?Changed
1026 text->setColor( QColor( 192, 192, 192, 192 ) );
1027 text->addComponent( textTransform );
1028}
1029
1031{
1032 createAxisScene();
1033 onAxisViewportSizeUpdate();
1034}
1035
1036void Qgs3DAxis::onAxisViewportSizeUpdate( int )
1037{
1038 Qgs3DAxisSettings settings = mMapSettings->get3DAxisSettings();
1039
1040 double windowWidth = ( double )mCanvas->width();
1041 double windowHeight = ( double )mCanvas->height();
1042
1043 QgsMapSettings set;
1044 if ( 2 <= QgsLogger::debugLevel() )
1045 {
1046 QgsDebugMsgLevel( QString( "onAxisViewportSizeUpdate window w/h: %1px / %2px" )
1047 .arg( windowWidth ).arg( windowHeight ), 2 );
1048 QgsDebugMsgLevel( QString( "onAxisViewportSizeUpdate window physicalDpi %1 (%2, %3)" )
1049 .arg( mCanvas->screen()->physicalDotsPerInch() )
1050 .arg( mCanvas->screen()->physicalDotsPerInchX() )
1051 .arg( mCanvas->screen()->physicalDotsPerInchY() ), 2 );
1052 QgsDebugMsgLevel( QString( "onAxisViewportSizeUpdate window logicalDotsPerInch %1 (%2, %3)" )
1053 .arg( mCanvas->screen()->logicalDotsPerInch() )
1054 .arg( mCanvas->screen()->logicalDotsPerInchX() )
1055 .arg( mCanvas->screen()->logicalDotsPerInchY() ), 2 );
1056
1057 QgsDebugMsgLevel( QString( "onAxisViewportSizeUpdate window pixel ratio %1" )
1058 .arg( mCanvas->screen()->devicePixelRatio() ), 2 );
1059
1060 QgsDebugMsgLevel( QString( "onAxisViewportSizeUpdate set pixel ratio %1" )
1061 .arg( set.devicePixelRatio() ), 2 );
1062 QgsDebugMsgLevel( QString( "onAxisViewportSizeUpdate set outputDpi %1" )
1063 .arg( set.outputDpi() ), 2 );
1064 QgsDebugMsgLevel( QString( "onAxisViewportSizeUpdate set dpiTarget %1" )
1065 .arg( set.dpiTarget() ), 2 );
1066 }
1067
1068 // default viewport size in pixel according to 92 dpi
1069 double defaultViewportPixelSize = ( ( double )settings.defaultViewportSize() / 25.4 ) * 92.0;
1070
1071 // computes the viewport size according to screen dpi but as the viewport size growths too fast
1072 // then we limit the growth by using a factor on the dpi difference.
1073 double viewportPixelSize = defaultViewportPixelSize + ( ( double )settings.defaultViewportSize() / 25.4 )
1074 * ( mCanvas->screen()->physicalDotsPerInch() - 92.0 ) * 0.7;
1075 QgsDebugMsgLevel( QString( "onAxisViewportSizeUpdate viewportPixelSize %1" ).arg( viewportPixelSize ), 2 );
1076 double widthRatio = viewportPixelSize / windowWidth;
1077 double heightRatio = widthRatio * windowWidth / windowHeight;
1078
1079 QgsDebugMsgLevel( QString( "3DAxis viewport ratios width: %1% / height: %2%" ).arg( widthRatio ).arg( heightRatio ), 2 );
1080
1081 if ( heightRatio * windowHeight < viewportPixelSize )
1082 {
1083 heightRatio = viewportPixelSize / windowHeight;
1084 widthRatio = heightRatio * windowHeight / windowWidth;
1085 QgsDebugMsgLevel( QString( "3DAxis viewport, height too small, ratios adjusted to width: %1% / height: %2%" ).arg( widthRatio ).arg( heightRatio ), 2 );
1086 }
1087
1088 if ( heightRatio > settings.maxViewportRatio() || widthRatio > settings.maxViewportRatio() )
1089 {
1090 QgsDebugMsgLevel( "viewport takes too much place into the 3d view, disabling it", 2 );
1091 // take too much place into the 3d view
1092 mViewport->setEnabled( false );
1093 setEnableCube( false );
1094 setEnableAxis( false );
1095 }
1096 else
1097 {
1098 // will be used to adjust the axis label translations/sizes
1099 mAxisScaleFactor = viewportPixelSize / defaultViewportPixelSize;
1100 QgsDebugMsgLevel( QString( "3DAxis viewport mAxisScaleFactor %1" ).arg( mAxisScaleFactor ), 2 );
1101
1102 if ( ! mViewport->isEnabled() )
1103 {
1104 if ( settings.mode() == Qgs3DAxisSettings::Mode::Crs )
1105 setEnableAxis( true );
1106 else if ( settings.mode() == Qgs3DAxisSettings::Mode::Cube )
1107 setEnableCube( true );
1108 }
1109 mViewport->setEnabled( true );
1110
1111 float xRatio = 1.0f;
1112 float yRatio = 1.0f;
1113 if ( settings.horizontalPosition() == Qt::AnchorPoint::AnchorLeft )
1114 xRatio = 0.0f;
1115 else if ( settings.horizontalPosition() == Qt::AnchorPoint::AnchorHorizontalCenter )
1116 xRatio = 0.5f - static_cast<float>( widthRatio ) / 2.0f;
1117 else
1118 xRatio = 1.0f - static_cast<float>( widthRatio );
1119
1120 if ( settings.verticalPosition() == Qt::AnchorPoint::AnchorTop )
1121 yRatio = 0.0f;
1122 else if ( settings.verticalPosition() == Qt::AnchorPoint::AnchorVerticalCenter )
1123 yRatio = 0.5f - static_cast<float>( heightRatio ) / 2.0f;
1124 else
1125 yRatio = 1.0f - static_cast<float>( heightRatio );
1126
1127 QgsDebugMsgLevel( QString( "Qgs3DAxis: update viewport: %1 x %2 x %3 x %4" ).arg( xRatio ).arg( yRatio ).arg( widthRatio ).arg( heightRatio ), 2 );
1128 mViewport->setNormalizedRect( QRectF( xRatio, yRatio, widthRatio, heightRatio ) );
1129
1130 if ( settings.mode() == Qgs3DAxisSettings::Mode::Crs )
1131 {
1132 const float halfWidthSize = static_cast<float>( windowWidth * widthRatio / 2.0 );
1133 const float halfHeightSize = static_cast<float>( windowWidth * widthRatio / 2.0 );
1134 mTwoDLabelCamera->lens()->setOrthographicProjection(
1135 -halfWidthSize, halfWidthSize,
1136 -halfHeightSize, halfHeightSize,
1137 mTwoDLabelCamera->lens()->nearPlane(), mTwoDLabelCamera->lens()->farPlane() );
1138
1139 updateAxisLabelPosition();
1140 }
1141 }
1142}
1143
1144void Qgs3DAxis::onCameraUpdate( )
1145{
1146 Qt3DRender::QCamera *parentCamera = mCameraController->camera();
1147
1148 if ( parentCamera->viewVector() != mPreviousVector
1149 && !std::isnan( parentCamera->viewVector().x() )
1150 && !std::isnan( parentCamera->viewVector().y() )
1151 && !std::isnan( parentCamera->viewVector().z() ) )
1152 {
1153 mPreviousVector = parentCamera->viewVector();
1154
1155 QQuaternion q = QQuaternion::fromDirection( -parentCamera->viewVector(), parentCamera->upVector() );
1156 mAxisCamera->setPosition( q.rotatedVector( QVector3D( 0, 0, mCylinderLength * 9.0f ) ) );
1157 mAxisCamera->setUpVector( q.rotatedVector( QVector3D( 0, 1, 0 ) ) );
1158
1159 if ( mAxisRoot->isEnabled() )
1160 {
1161 updateAxisLabelPosition();
1162 }
1163 }
1164}
1165
1166void Qgs3DAxis::updateAxisLabelPosition()
1167{
1168 if ( mTextTransformX && mTextTransformY && mTextTransformZ )
1169 {
1170 mTextTransformX->setTranslation( from3DTo2DLabelPosition( mTextCoordX * static_cast<float>( mAxisScaleFactor ), mAxisCamera, mTwoDLabelCamera ) );
1171 updateAxisLabelText( mTextX, mTextX->text() );
1172
1173 mTextTransformY->setTranslation( from3DTo2DLabelPosition( mTextCoordY * static_cast<float>( mAxisScaleFactor ), mAxisCamera, mTwoDLabelCamera ) );
1174 updateAxisLabelText( mTextY, mTextY->text() );
1175
1176 mTextTransformZ->setTranslation( from3DTo2DLabelPosition( mTextCoordZ * static_cast<float>( mAxisScaleFactor ), mAxisCamera, mTwoDLabelCamera ) );
1177 updateAxisLabelText( mTextZ, mTextZ->text() );
1178 }
1179}
1180
1181void Qgs3DAxis::updateAxisLabelText( Qt3DExtras::QText2DEntity *textEntity, const QString &text )
1182{
1183 const float scaledFontSize = static_cast<float>( mAxisScaleFactor ) * static_cast<float>( mFontSize );
1184 const QFont font = createFont( static_cast<int>( std::round( scaledFontSize ) ) );
1185 textEntity->setFont( font );
1186 textEntity->setWidth( scaledFontSize * static_cast<float>( text.length() ) );
1187 textEntity->setHeight( 1.5f * scaledFontSize );
1188}
1189
1190QFont Qgs3DAxis::createFont( int pointSize )
1191{
1192 QFont font = QFontDatabase::systemFont( QFontDatabase::FixedFont );
1193 font.setPointSize( pointSize );
1194 font.setWeight( QFont::Weight::Black );
1195 font.setStyleStrategy( QFont::StyleStrategy::ForceOutline );
1196 return font;
1197}
Contains the configuration of a 3d axis.
void setMode(Qgs3DAxisSettings::Mode type)
Sets the type of the 3daxis.
double maxViewportRatio() const
Returns the maximal axis viewport ratio (see Qt3DRender::QViewport::normalizedRect())
Mode
Axis representation enum.
@ Crs
Respect CRS directions.
@ Cube
Abstract cube mode.
Qt::AnchorPoint verticalPosition() const
Returns the vertical position for the 3d axis.
void setHorizontalPosition(Qt::AnchorPoint position)
Sets the horizontal position for the 3d axis.
int defaultViewportSize() const
Returns the default axis viewport size in millimeters.
Qgs3DAxisSettings::Mode mode() const
Returns the type of the 3daxis.
Qt::AnchorPoint horizontalPosition() const
Returns the horizontal position for the 3d axis.
void setVerticalPosition(Qt::AnchorPoint position)
Sets the vertical position for the 3d axis.
~Qgs3DAxis() override
Definition qgs3daxis.cpp:76
void onAxisSettingsChanged()
Force update of the axis and the viewport when a setting has changed.
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:47
Qt3DRender::QFrameGraphNode * activeFrameGraph() const
Returns the node of the active frame graph.
QgsAbstract3DEngine * engine() const
Returns the abstract 3D engine.
QgsTerrainEntity * terrainEntity()
Returns terrain entity (may be temporarily nullptr)
Qgs3DAxisSettings get3DAxisSettings() const
Returns the current configuration of 3d axis.
float terrainElevationOffset() const
Returns the elevation offset of the terrain (used to move the terrain up or down)
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.
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)
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.
static int debugLevel()
Reads the environment variable QGIS_DEBUG and converts it to int.
Definition qgslogger.h:108
static void warning(const QString &msg)
Goes to qWarning.
The QgsMapSettings class contains configuration for rendering of the map.
double dpiTarget() const
Returns the target DPI (dots per inch) to be taken into consideration when rendering.
float devicePixelRatio() const
Returns the device pixel ratio.
double outputDpi() const
Returns the DPI (dots per inch) used for conversion between real world units (e.g.
Class for storage of 3D vectors similar to QVector3D, with the difference that it uses double precisi...
Definition qgsvector3d.h:31
double y() const
Returns Y coordinate.
Definition qgsvector3d.h:50
QVector3D toVector3D() const
Converts the current object to QVector3D.
double x() const
Returns X coordinate.
Definition qgsvector3d.h:48
void set(double x, double y, double z)
Sets vector coordinates.
Definition qgsvector3d.h:73
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:39
const QgsCoordinateReferenceSystem & crs
Helper struct to store ray casting parameters.