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