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