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