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