QGIS API Documentation 3.28.14-Firenze (exported)
Loading...
Searching...
No Matches
qgs3daxis.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgs3daxis.cpp
3 --------------------------------------
4 Date : March 2022
5 Copyright : (C) 2022 by Jean Felder
6 Email : jean dot felder at oslandia dot com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
16#include "qgs3daxis.h"
17
18#include <Qt3DCore/QTransform>
19#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 intersectionPoint;
752 QVector3D camPos = mCameraController->camera()->position();
753 QgsRayCastingUtils::Ray3D r( camPos, pos.toVector3D() - camPos );
754 if ( mMapScene->terrainEntity()->rayIntersection( r, intersectionPoint ) )
755 {
756 elevation = intersectionPoint.y();
757 QgsDebugMsgLevel( QString( "Computed elevation from terrain: %1" ).arg( elevation ), 2 );
758 }
759 else
760 QgsDebugMsgLevel( "Unable to obtain elevation from terrain", 2 );
761
762 }
763 pos.set( pos.x(), elevation + mMapSettings->terrainElevationOffset(), pos.z() );
764
765 mCameraController->setLookingAtPoint( pos, ( mCameraController->camera()->position() - pos.toVector3D() ).length(),
766 pitch, yaw );
767}
768
769
770void Qgs3DAxis::createCube( )
771{
772 QVector3D minPos = QVector3D( -mCylinderLength * 0.5f, -mCylinderLength * 0.5f, -mCylinderLength * 0.5f );
773
774 // cube outlines
775 Qt3DCore::QEntity *cubeLineEntity = new Qt3DCore::QEntity( mCubeRoot );
776 cubeLineEntity->setObjectName( "3DAxis_cubeline" );
777 Qgs3DWiredMesh *cubeLine = new Qgs3DWiredMesh;
778 QgsAABB box = QgsAABB( -mCylinderLength * 0.5f, -mCylinderLength * 0.5f, -mCylinderLength * 0.5f,
779 mCylinderLength * 0.5f, mCylinderLength * 0.5f, mCylinderLength * 0.5f );
780 cubeLine->setVertices( box.verticesForLines() );
781 cubeLineEntity->addComponent( cubeLine );
782
783 Qt3DExtras::QPhongMaterial *cubeLineMaterial = new Qt3DExtras::QPhongMaterial;
784 cubeLineMaterial->setAmbient( Qt::white );
785 cubeLineEntity->addComponent( cubeLineMaterial );
786
787 // cube mesh
788 Qt3DExtras::QCuboidMesh *cubeMesh = new Qt3DExtras::QCuboidMesh;
789 cubeMesh->setObjectName( "3DAxis_cubemesh" );
790 cubeMesh->setXExtent( mCylinderLength );
791 cubeMesh->setYExtent( mCylinderLength );
792 cubeMesh->setZExtent( mCylinderLength );
793 mCubeRoot->addComponent( cubeMesh );
794
795 Qt3DExtras::QPhongMaterial *cubeMaterial = new Qt3DExtras::QPhongMaterial( mCubeRoot );
796 cubeMaterial->setAmbient( QColor( 100, 100, 100, 50 ) );
797 cubeMaterial->setShininess( 100 );
798 mCubeRoot->addComponent( cubeMaterial );
799
800 Qt3DCore::QTransform *cubeTransform = new Qt3DCore::QTransform;
801 QMatrix4x4 transformMatrixcube;
802 //transformMatrixcube.rotate( rotation );
803 transformMatrixcube.translate( minPos + QVector3D( mCylinderLength * 0.5f, mCylinderLength * 0.5f, mCylinderLength * 0.5f ) );
804 cubeTransform->setMatrix( transformMatrixcube );
805 mCubeRoot->addComponent( cubeTransform );
806
807 // text
808 QString text;
809 int fontSize = 0.75 * mFontSize;
810 float textHeight = fontSize * 1.5f;
811 float textWidth;
812 QFont f = QFontDatabase::systemFont( QFontDatabase::FixedFont );
813 f.setPointSize( fontSize );
814 f.setWeight( QFont::Weight::Black );
815
816 {
817 text = QStringLiteral( "top" );
818 textWidth = text.length() * fontSize * 0.75f;
819 QVector3D translation = minPos + QVector3D(
820 mCylinderLength * 0.5f - textWidth / 2.0f,
821 mCylinderLength * 0.5f - textHeight / 2.0f,
822 mCylinderLength * 1.01f );
823 QMatrix4x4 rotation;
824 mCubeLabels << addCubeText( text, textHeight, textWidth, f, rotation, translation );
825 }
826
827 {
828 text = QStringLiteral( "btm" );
829 textWidth = text.length() * fontSize * 0.75f;
830 QVector3D translation = minPos + QVector3D(
831 mCylinderLength * 0.5f - textWidth / 2.0f,
832 mCylinderLength * 0.5f + textHeight / 2.0f,
833 -mCylinderLength * 0.01f );
834 QMatrix4x4 rotation;
835 rotation.rotate( 180.0f, QVector3D( 1.0f, 0.0f, 0.0f ).normalized() );
836 mCubeLabels << addCubeText( text, textHeight, textWidth, f, rotation, translation );
837 }
838
839 {
840 text = QStringLiteral( "west" );
841 textWidth = text.length() * fontSize * 0.75f;
842 QVector3D translation = minPos + QVector3D(
843 - mCylinderLength * 0.01f,
844 mCylinderLength * 0.5f + textWidth / 2.0f,
845 mCylinderLength * 0.5f - textHeight / 2.0f );
846 QMatrix4x4 rotation;
847 rotation.rotate( 90.0f, QVector3D( 0.0f, -1.0f, 0.0f ).normalized() );
848 rotation.rotate( 90.0f, QVector3D( 0.0f, 0.0f, -1.0f ).normalized() );
849 mCubeLabels << addCubeText( text, textHeight, textWidth, f, rotation, translation );
850 }
851
852 {
853 text = QStringLiteral( "east" );
854 textWidth = text.length() * fontSize * 0.75f;
855 QVector3D translation = minPos + QVector3D(
856 mCylinderLength * 1.01f,
857 mCylinderLength * 0.5f - textWidth / 2.0f,
858 mCylinderLength * 0.5f - textHeight / 2.0f );
859 QMatrix4x4 rotation;
860 rotation.rotate( 90.0f, QVector3D( 0.0f, 1.0f, 0.0f ).normalized() );
861 rotation.rotate( 90.0f, QVector3D( 0.0f, 0.0f, 1.0f ).normalized() );
862 mCubeLabels << addCubeText( text, textHeight, textWidth, f, rotation, translation );
863 }
864
865 {
866 text = QStringLiteral( "south" );
867 textWidth = text.length() * fontSize * 0.75f;
868 QVector3D translation = minPos + QVector3D(
869 mCylinderLength * 0.5f - textWidth / 2.0f,
870 - mCylinderLength * 0.01f,
871 mCylinderLength * 0.5f - textHeight / 2.0f );
872 QMatrix4x4 rotation;
873 rotation.rotate( 90.0f, QVector3D( 1.0f, 0.0f, 0.0f ).normalized() );
874 mCubeLabels << addCubeText( text, textHeight, textWidth, f, rotation, translation );
875 }
876
877 {
878 text = QStringLiteral( "north" );
879 textWidth = text.length() * fontSize * 0.75f;
880 QVector3D translation = minPos + QVector3D(
881 mCylinderLength * 0.5f + textWidth / 2.0f,
882 mCylinderLength * 1.01f,
883 mCylinderLength * 0.5f - textHeight / 2.0f );
884 QMatrix4x4 rotation;
885 rotation.rotate( 90.0f, QVector3D( -1.0f, 0.0f, 0.0f ).normalized() );
886 rotation.rotate( 180.0f, QVector3D( 0.0f, 0.0f, 1.0f ).normalized() );
887 mCubeLabels << addCubeText( text, textHeight, textWidth, f, rotation, translation );
888 }
889
890 for ( Qt3DExtras::QText2DEntity *l : std::as_const( mCubeLabels ) )
891 {
892 l->setParent( mCubeRoot );
893 }
894}
895
896Qt3DExtras::QText2DEntity *Qgs3DAxis::addCubeText( const QString &text, float textHeight, float textWidth, const QFont &f, const QMatrix4x4 &rotation, const QVector3D &translation )
897{
898 Qt3DExtras::QText2DEntity *textEntity = new Qt3DExtras::QText2DEntity;
899 textEntity->setObjectName( "3DAxis_cube_label_" + text );
900 textEntity->setFont( f );
901 textEntity->setHeight( textHeight );
902 textEntity->setWidth( textWidth );
903 textEntity->setColor( QColor( 192, 192, 192 ) );
904 textEntity->setText( text );
905
906 Qt3DCore::QTransform *textFrontTransform = new Qt3DCore::QTransform();
907 textFrontTransform->setMatrix( rotation );
908 textFrontTransform->setTranslation( translation );
909 textEntity->addComponent( textFrontTransform );
910
911 return textEntity;
912}
913
914void Qgs3DAxis::createAxis( Qt::Axis axisType )
915{
916 float cylinderRadius = 0.05f * mCylinderLength;
917 float coneLength = 0.3f * mCylinderLength;
918 float coneBottomRadius = 0.1f * mCylinderLength;
919
920 QQuaternion rotation;
921 QColor color;
922
923 Qt3DExtras::QText2DEntity *text = nullptr;
924 Qt3DCore::QTransform *textTransform = nullptr;
925 QString name;
926
927 switch ( axisType )
928 {
929 case Qt::Axis::XAxis:
930 mTextX = new Qt3DExtras::QText2DEntity( ); // object initialization in two step:
931 mTextX->setParent( mTwoDLabelSceneEntity ); // see https://bugreports.qt.io/browse/QTBUG-77139
932 connect( mTextX, &Qt3DExtras::QText2DEntity::textChanged, this, &Qgs3DAxis::onTextXChanged );
933 mTextTransformX = new Qt3DCore::QTransform();
934 mTextCoordX = QVector3D( mCylinderLength + coneLength / 2.0f, 0.0f, 0.0f );
935
936 rotation = QQuaternion::fromAxisAndAngle( QVector3D( 0.0f, 0.0f, 1.0f ), -90.0f );
937 color = Qt::red;
938 text = mTextX;
939 textTransform = mTextTransformX;
940 name = "3DAxis_axisX";
941 break;
942
943 case Qt::Axis::YAxis:
944 mTextY = new Qt3DExtras::QText2DEntity( ); // object initialization in two step:
945 mTextY->setParent( mTwoDLabelSceneEntity ); // see https://bugreports.qt.io/browse/QTBUG-77139
946 connect( mTextY, &Qt3DExtras::QText2DEntity::textChanged, this, &Qgs3DAxis::onTextYChanged );
947 mTextTransformY = new Qt3DCore::QTransform();
948 mTextCoordY = QVector3D( 0.0f, mCylinderLength + coneLength / 2.0f, 0.0f );
949
950 rotation = QQuaternion::fromAxisAndAngle( QVector3D( 0.0f, 0.0f, 0.0f ), 0.0f );
951 color = Qt::green;
952 text = mTextY;
953 textTransform = mTextTransformY;
954 name = "3DAxis_axisY";
955 break;
956
957 case Qt::Axis::ZAxis:
958 mTextZ = new Qt3DExtras::QText2DEntity( ); // object initialization in two step:
959 mTextZ->setParent( mTwoDLabelSceneEntity ); // see https://bugreports.qt.io/browse/QTBUG-77139
960 connect( mTextZ, &Qt3DExtras::QText2DEntity::textChanged, this, &Qgs3DAxis::onTextZChanged );
961 mTextTransformZ = new Qt3DCore::QTransform();
962 mTextCoordZ = QVector3D( 0.0f, 0.0f, mCylinderLength + coneLength / 2.0f );
963
964 rotation = QQuaternion::fromAxisAndAngle( QVector3D( 1.0f, 0.0f, 0.0f ), 90.0f );
965 color = Qt::blue;
966 text = mTextZ;
967 textTransform = mTextTransformZ;
968 name = "3DAxis_axisZ";
969 break;
970
971 default:
972 return;
973 }
974
975 // cylinder
976 Qt3DCore::QEntity *cylinder = new Qt3DCore::QEntity( mAxisRoot );
977 cylinder->setObjectName( name );
978
979 Qt3DExtras::QCylinderMesh *cylinderMesh = new Qt3DExtras::QCylinderMesh;
980 cylinderMesh->setRadius( cylinderRadius );
981 cylinderMesh->setLength( mCylinderLength );
982 cylinderMesh->setRings( 10 );
983 cylinderMesh->setSlices( 4 );
984 cylinder->addComponent( cylinderMesh );
985
986 Qt3DExtras::QPhongMaterial *cylinderMaterial = new Qt3DExtras::QPhongMaterial( cylinder );
987 cylinderMaterial->setAmbient( color );
988 cylinderMaterial->setShininess( 0 );
989 cylinder->addComponent( cylinderMaterial );
990
991 Qt3DCore::QTransform *cylinderTransform = new Qt3DCore::QTransform;
992 QMatrix4x4 transformMatrixCylinder;
993 transformMatrixCylinder.rotate( rotation );
994 transformMatrixCylinder.translate( QVector3D( 0.0f, mCylinderLength / 2.0f, 0.0f ) );
995 cylinderTransform->setMatrix( transformMatrixCylinder );
996 cylinder->addComponent( cylinderTransform );
997
998 // cone
999 Qt3DCore::QEntity *coneEntity = new Qt3DCore::QEntity( mAxisRoot );
1000 coneEntity->setObjectName( name );
1001 Qt3DExtras::QConeMesh *coneMesh = new Qt3DExtras::QConeMesh;
1002 coneMesh->setLength( coneLength );
1003 coneMesh->setBottomRadius( coneBottomRadius );
1004 coneMesh->setTopRadius( 0.0f );
1005 coneMesh->setRings( 10 );
1006 coneMesh->setSlices( 4 );
1007 coneEntity->addComponent( coneMesh );
1008
1009 Qt3DExtras::QPhongMaterial *coneMaterial = new Qt3DExtras::QPhongMaterial( coneEntity );
1010 coneMaterial->setAmbient( color );
1011 coneMaterial->setShininess( 0 );
1012 coneEntity->addComponent( coneMaterial );
1013
1014 Qt3DCore::QTransform *coneTransform = new Qt3DCore::QTransform;
1015 QMatrix4x4 transformMatrixCone;
1016 transformMatrixCone.rotate( rotation );
1017 transformMatrixCone.translate( QVector3D( 0.0f, mCylinderLength, 0.0f ) );
1018 coneTransform->setMatrix( transformMatrixCone );
1019 coneEntity->addComponent( coneTransform );
1020
1021 // text font, height and width will be set later in onText?Changed
1022 text->setColor( QColor( 192, 192, 192, 192 ) );
1023 text->addComponent( textTransform );
1024}
1025
1027{
1028 createAxisScene();
1029 onAxisViewportSizeUpdate();
1030}
1031
1032void Qgs3DAxis::onAxisViewportSizeUpdate( int )
1033{
1034 Qgs3DAxisSettings settings = mMapSettings->get3DAxisSettings();
1035
1036 double windowWidth = ( double )mParentWindow->width();
1037 double windowHeight = ( double )mParentWindow->height();
1038
1039 QgsMapSettings set;
1040 if ( 2 <= QgsLogger::debugLevel() )
1041 {
1042 QgsDebugMsgLevel( QString( "onAxisViewportSizeUpdate window w/h: %1px / %2px" )
1043 .arg( windowWidth ).arg( windowHeight ), 2 );
1044 QgsDebugMsgLevel( QString( "onAxisViewportSizeUpdate window physicalDpi %1 (%2, %3)" )
1045 .arg( mParentWindow->screen()->physicalDotsPerInch() )
1046 .arg( mParentWindow->screen()->physicalDotsPerInchX() )
1047 .arg( mParentWindow->screen()->physicalDotsPerInchY() ), 2 );
1048 QgsDebugMsgLevel( QString( "onAxisViewportSizeUpdate window logicalDotsPerInch %1 (%2, %3)" )
1049 .arg( mParentWindow->screen()->logicalDotsPerInch() )
1050 .arg( mParentWindow->screen()->logicalDotsPerInchX() )
1051 .arg( mParentWindow->screen()->logicalDotsPerInchY() ), 2 );
1052
1053 QgsDebugMsgLevel( QString( "onAxisViewportSizeUpdate window pixel ratio %1" )
1054 .arg( mParentWindow->screen()->devicePixelRatio() ), 2 );
1055
1056 QgsDebugMsgLevel( QString( "onAxisViewportSizeUpdate set pixel ratio %1" )
1057 .arg( set.devicePixelRatio() ), 2 );
1058 QgsDebugMsgLevel( QString( "onAxisViewportSizeUpdate set outputDpi %1" )
1059 .arg( set.outputDpi() ), 2 );
1060 QgsDebugMsgLevel( QString( "onAxisViewportSizeUpdate set dpiTarget %1" )
1061 .arg( set.dpiTarget() ), 2 );
1062 }
1063
1064 // default viewport size in pixel according to 92 dpi
1065 double defaultViewportPixelSize = ( ( double )settings.defaultViewportSize() / 25.4 ) * 92.0;
1066
1067 // computes the viewport size according to screen dpi but as the viewport size growths too fast
1068 // then we limit the growth by using a factor on the dpi difference.
1069 double viewportPixelSize = defaultViewportPixelSize + ( ( double )settings.defaultViewportSize() / 25.4 )
1070 * ( mParentWindow->screen()->physicalDotsPerInch() - 92.0 ) * 0.7;
1071 QgsDebugMsgLevel( QString( "onAxisViewportSizeUpdate viewportPixelSize %1" ).arg( viewportPixelSize ), 2 );
1072 double widthRatio = viewportPixelSize / windowWidth;
1073 double heightRatio = widthRatio * windowWidth / windowHeight;
1074
1075 QgsDebugMsgLevel( QString( "3DAxis viewport ratios width: %1% / height: %2%" ).arg( widthRatio ).arg( heightRatio ), 2 );
1076
1077 if ( heightRatio * windowHeight < viewportPixelSize )
1078 {
1079 heightRatio = viewportPixelSize / windowHeight;
1080 widthRatio = heightRatio * windowHeight / windowWidth;
1081 QgsDebugMsgLevel( QString( "3DAxis viewport, height too small, ratios adjusted to width: %1% / height: %2%" ).arg( widthRatio ).arg( heightRatio ), 2 );
1082 }
1083
1084 if ( heightRatio > settings.maxViewportRatio() || widthRatio > settings.maxViewportRatio() )
1085 {
1086 QgsDebugMsgLevel( "viewport takes too much place into the 3d view, disabling it", 2 );
1087 // take too much place into the 3d view
1088 mAxisViewport->setEnabled( false );
1089 setEnableCube( false );
1090 setEnableAxis( false );
1091 }
1092 else
1093 {
1094 // will be used to adjust the axis label translations/sizes
1095 mAxisScaleFactor = viewportPixelSize / defaultViewportPixelSize;
1096 QgsDebugMsgLevel( QString( "3DAxis viewport mAxisScaleFactor %1" ).arg( mAxisScaleFactor ), 2 );
1097
1098 if ( ! mAxisViewport->isEnabled() )
1099 {
1100 if ( settings.mode() == Qgs3DAxisSettings::Mode::Crs )
1101 setEnableAxis( true );
1102 else if ( settings.mode() == Qgs3DAxisSettings::Mode::Cube )
1103 setEnableCube( true );
1104 }
1105 mAxisViewport->setEnabled( true );
1106
1107 float xRatio = 1.0f;
1108 float yRatio = 1.0f;
1109 if ( settings.horizontalPosition() == Qt::AnchorPoint::AnchorLeft )
1110 xRatio = 0.0f;
1111 else if ( settings.horizontalPosition() == Qt::AnchorPoint::AnchorHorizontalCenter )
1112 xRatio = 0.5f - widthRatio / 2.0f;
1113 else
1114 xRatio = 1.0f - widthRatio;
1115
1116 if ( settings.verticalPosition() == Qt::AnchorPoint::AnchorTop )
1117 yRatio = 0.0f;
1118 else if ( settings.verticalPosition() == Qt::AnchorPoint::AnchorVerticalCenter )
1119 yRatio = 0.5f - heightRatio / 2.0f;
1120 else
1121 yRatio = 1.0f - heightRatio;
1122
1123 QgsDebugMsgLevel( QString( "Qgs3DAxis: update viewport: %1 x %2 x %3 x %4" ).arg( xRatio ).arg( yRatio ).arg( widthRatio ).arg( heightRatio ), 2 );
1124 mAxisViewport->setNormalizedRect( QRectF( xRatio, yRatio, widthRatio, heightRatio ) );
1125
1126 if ( settings.mode() == Qgs3DAxisSettings::Mode::Crs )
1127 {
1128 mTwoDLabelCamera->lens()->setOrthographicProjection(
1129 -windowWidth / 2.0f, windowWidth / 2.0f,
1130 -windowHeight / 2.0f, windowHeight / 2.0f,
1131 mTwoDLabelCamera->lens()->nearPlane(), mTwoDLabelCamera->lens()->farPlane() );
1132
1133 updateAxisLabelPosition();
1134 }
1135 }
1136}
1137
1138void Qgs3DAxis::onCameraUpdate( )
1139{
1140 Qt3DRender::QCamera *parentCamera = mCameraController->camera();
1141
1142 if ( parentCamera->viewVector() != mPreviousVector
1143 && !std::isnan( parentCamera->viewVector().x() )
1144 && !std::isnan( parentCamera->viewVector().y() )
1145 && !std::isnan( parentCamera->viewVector().z() ) )
1146 {
1147 mPreviousVector = parentCamera->viewVector();
1148 QVector3D mainCameraShift = parentCamera->viewVector().normalized();
1149 float zy_swap = mainCameraShift.y();
1150 mainCameraShift.setY( mainCameraShift.z() );
1151 mainCameraShift.setZ( -zy_swap );
1152 mainCameraShift.setX( -mainCameraShift.x() );
1153
1154 if ( mAxisCamera->projectionType() == Qt3DRender::QCameraLens::ProjectionType::OrthographicProjection )
1155 {
1156 mAxisCamera->setPosition( mainCameraShift );
1157 }
1158 else
1159 {
1160 mAxisCamera->setPosition( mainCameraShift * mCylinderLength * 10.0 );
1161 }
1162
1163 if ( mAxisRoot->isEnabled() )
1164 {
1165 updateAxisLabelPosition();
1166 }
1167 }
1168}
1169
1170void Qgs3DAxis::updateAxisLabelPosition()
1171{
1172 if ( mTextTransformX && mTextTransformY && mTextTransformZ )
1173 {
1174 mTextTransformX->setTranslation( from3DTo2DLabelPosition( mTextCoordX * mAxisScaleFactor, mAxisCamera,
1175 mAxisViewport, mTwoDLabelCamera, mTwoDLabelViewport,
1176 mParentWindow->size() ) );
1177 onTextXChanged( mTextX->text() );
1178
1179 mTextTransformY->setTranslation( from3DTo2DLabelPosition( mTextCoordY * mAxisScaleFactor, mAxisCamera,
1180 mAxisViewport, mTwoDLabelCamera, mTwoDLabelViewport,
1181 mParentWindow->size() ) );
1182 onTextYChanged( mTextY->text() );
1183
1184 mTextTransformZ->setTranslation( from3DTo2DLabelPosition( mTextCoordZ * mAxisScaleFactor, mAxisCamera,
1185 mAxisViewport, mTwoDLabelCamera, mTwoDLabelViewport,
1186 mParentWindow->size() ) );
1187 onTextZChanged( mTextZ->text() );
1188 }
1189}
1190
1191void Qgs3DAxis::onTextXChanged( const QString &text )
1192{
1193 QFont f = QFont( "monospace", mAxisScaleFactor * mFontSize ); // TODO: should use outlined font
1194 f.setWeight( QFont::Weight::Black );
1195 f.setStyleStrategy( QFont::StyleStrategy::ForceOutline );
1196 mTextX->setFont( f );
1197 mTextX->setWidth( mAxisScaleFactor * mFontSize * text.length() );
1198 mTextX->setHeight( mAxisScaleFactor * mFontSize * 1.5f );
1199}
1200
1201void Qgs3DAxis::onTextYChanged( const QString &text )
1202{
1203 QFont f = QFont( "monospace", mAxisScaleFactor * mFontSize ); // TODO: should use outlined font
1204 f.setWeight( QFont::Weight::Black );
1205 f.setStyleStrategy( QFont::StyleStrategy::ForceOutline );
1206 mTextY->setFont( f );
1207 mTextY->setWidth( mAxisScaleFactor * mFontSize * text.length() );
1208 mTextY->setHeight( mAxisScaleFactor * mFontSize * 1.5f );
1209}
1210
1211void Qgs3DAxis::onTextZChanged( const QString &text )
1212{
1213 QFont f = QFont( "monospace", mAxisScaleFactor * mFontSize ); // TODO: should use outlined font
1214 f.setWeight( QFont::Weight::Black );
1215 f.setStyleStrategy( QFont::StyleStrategy::ForceOutline );
1216 mTextZ->setFont( f );
1217 mTextZ->setWidth( mAxisScaleFactor * mFontSize * text.length() );
1218 mTextZ->setHeight( mAxisScaleFactor * mFontSize * 1.5f );
1219}
1220
1221//
1222// Qgs3DWiredMesh
1223//
1224
1225Qgs3DWiredMesh::Qgs3DWiredMesh( Qt3DCore::QNode *parent )
1226 : Qt3DRender::QGeometryRenderer( parent )
1227 , mPositionAttribute( new Qt3DQAttribute( this ) )
1228 , mVertexBuffer( new Qt3DQBuffer( this ) )
1229{
1230 mPositionAttribute->setAttributeType( Qt3DQAttribute::VertexAttribute );
1231 mPositionAttribute->setBuffer( mVertexBuffer );
1232 mPositionAttribute->setVertexBaseType( Qt3DQAttribute::Float );
1233 mPositionAttribute->setVertexSize( 3 );
1234 mPositionAttribute->setName( Qt3DQAttribute::defaultPositionAttributeName() );
1235
1236 mGeom = new Qt3DQGeometry( this );
1237 mGeom->addAttribute( mPositionAttribute );
1238
1239 setInstanceCount( 1 );
1240 setIndexOffset( 0 );
1241 setFirstInstance( 0 );
1242 setPrimitiveType( Qt3DRender::QGeometryRenderer::Lines );
1243 setGeometry( mGeom );
1244}
1245
1247
1248void Qgs3DWiredMesh::setVertices( const QList<QVector3D> &vertices )
1249{
1250 QByteArray vertexBufferData;
1251 vertexBufferData.resize( vertices.size() * 3 * sizeof( float ) );
1252 float *rawVertexArray = reinterpret_cast<float *>( vertexBufferData.data() );
1253 int idx = 0;
1254 for ( const QVector3D &v : std::as_const( vertices ) )
1255 {
1256 rawVertexArray[idx++] = v.x();
1257 rawVertexArray[idx++] = v.y();
1258 rawVertexArray[idx++] = v.z();
1259 }
1260
1261 mVertexBuffer->setData( vertexBufferData );
1262 setVertexCount( vertices.count() );
1263}
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: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...
~Qgs3DAxis() override
Definition qgs3daxis.cpp:88
void onAxisSettingsChanged()
Force update of the axis and the viewport when a setting has changed.
QgsAbstract3DEngine * engine()
Returns the abstract 3D engine.
QgsTerrainEntity * terrainEntity()
Returns terrain entity (may be temporarily nullptr)
Qgs3DAxisSettings get3DAxisSettings() const
Returns the current configuration of 3d axis.
float terrainElevationOffset() const
Returns the elevation offset of the terrain (used to move the terrain up or down)
void set3DAxisSettings(const Qgs3DAxisSettings &axisSettings, bool force=false)
Sets the current configuration of 3d axis.
bool terrainRenderingEnabled() const
Returns whether the 2D terrain surface will be rendered.
void axisSettingsChanged()
Emitted when 3d axis rendering settings are changed.
~Qgs3DWiredMesh() override
Qgs3DWiredMesh(Qt3DCore::QNode *parent=nullptr)
Defaul Qgs3DWiredMesh constructor.
void setVertices(const QList< QVector3D > &vertices)
add or replace mesh vertices coordinates
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.
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.
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