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