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