18#include <Qt3DCore/QTransform>
19#include <Qt3DExtras/QCylinderMesh>
20#include <Qt3DExtras/QPhongMaterial>
21#include <Qt3DExtras/QConeMesh>
22#include <Qt3DRender/qcameralens.h>
23#include <Qt3DRender/QCameraSelector>
24#include <Qt3DRender/QClearBuffers>
25#include <Qt3DRender/QLayer>
26#include <Qt3DRender/QLayerFilter>
27#include <Qt3DRender/QPointLight>
28#include <Qt3DRender/QSortPolicy>
32#include <QFontDatabase>
34#include <QApplication>
35#include <QActionGroup>
47 Qt3DCore::QEntity *parent3DScene,
54 , mMapScene( mapScene )
55 , mCameraController( cameraCtrl )
58 mViewport = constructAxisScene( parent3DScene );
61 constructLabelsScene( parent3DScene );
64 connect( mCanvas, &Qgs3DMapCanvas::widthChanged,
this, &Qgs3DAxis::onAxisViewportSizeUpdate );
65 connect( mCanvas, &Qgs3DMapCanvas::heightChanged,
this, &Qgs3DAxis::onAxisViewportSizeUpdate );
68 onAxisViewportSizeUpdate();
70 init3DObjectPicking();
72 createKeyboardShortCut();
102void Qgs3DAxis::init3DObjectPicking( )
110 mScreenRayCaster =
new Qt3DRender::QScreenRayCaster( mAxisSceneEntity );
111 mScreenRayCaster->addLayer( mAxisObjectLayer );
112 mScreenRayCaster->setFilterMode( Qt3DRender::QScreenRayCaster::AcceptAllMatchingLayers );
113 mScreenRayCaster->setRunMode( Qt3DRender::QAbstractRayCaster::SingleShot );
115 mAxisSceneEntity->addComponent( mScreenRayCaster );
117 QObject::connect( mScreenRayCaster, &Qt3DRender::QScreenRayCaster::hitsChanged,
this, &Qgs3DAxis::onTouchedByRay );
120 mCanvas->installEventFilter(
this );
123bool Qgs3DAxis::eventFilter( QObject *watched, QEvent *event )
125 if ( watched != mCanvas )
128 if ( event->type() == QEvent::MouseButtonPress )
132 QMouseEvent *mouseEvent =
static_cast<QMouseEvent *
>( event );
133 mLastClickedPos = mouseEvent->pos();
137 else if ( event->type() == QEvent::MouseButtonRelease ||
event->type() == QEvent::MouseMove )
139 QMouseEvent *mouseEvent =
static_cast<QMouseEvent *
>( event );
142 if ( event->type() == QEvent::MouseMove &&
143 ( ( mHasClicked && ( mouseEvent->pos() - mLastClickedPos ).manhattanLength() < QApplication::startDragDistance() ) || mIsDragging ) )
149 else if ( mIsDragging && event->type() == QEvent::MouseButtonRelease )
156 else if ( ! mIsDragging )
159 QPointF normalizedPos(
static_cast<float>( mouseEvent->pos().x() ) /
static_cast<float>( mCanvas->width() ),
160 static_cast<float>( mouseEvent->pos().y() ) /
static_cast<float>( mCanvas->height() ) );
164 std::ostringstream os;
165 os <<
"QGS3DAxis: normalized pos: " << normalizedPos <<
" / viewport: " << mViewport->normalizedRect();
169 if ( mViewport->normalizedRect().contains( normalizedPos ) )
171 mLastClickedButton = mouseEvent->button();
172 mLastClickedPos = mouseEvent->pos();
175 mScreenRayCaster->trigger( mLastClickedPos );
181 if ( mPreviousCursor != Qt::ArrowCursor && mCanvas->cursor() == Qt::ArrowCursor )
183 mCanvas->setCursor( mPreviousCursor );
184 mPreviousCursor = Qt::ArrowCursor;
188 if ( mMapScene->
engine()->
renderSettings()->pickingSettings()->pickMethod() == Qt3DRender::QPickingSettings::TrianglePicking
189 && mDefaultPickingMethod != Qt3DRender::QPickingSettings::TrianglePicking )
191 mMapScene->
engine()->
renderSettings()->pickingSettings()->setPickMethod( mDefaultPickingMethod );
204void Qgs3DAxis::onTouchedByRay(
const Qt3DRender::QAbstractRayCaster::Hits &hits )
211 std::ostringstream os;
212 os <<
"Qgs3DAxis::onTouchedByRay " << hits.length() <<
" hits at pos " << mLastClickedPos <<
" with QButton: " << mLastClickedButton;
213 for (
int i = 0; i < hits.length(); ++i )
216 os <<
"\tHit Type: " << hits.at( i ).type() <<
"\n";
217 os <<
"\tHit triangle id: " << hits.at( i ).primitiveIndex() <<
"\n";
218 os <<
"\tHit distance: " << hits.at( i ).distance() <<
"\n";
219 os <<
"\tHit entity name: " << hits.at( i ).entity()->objectName().toStdString();
224 for (
int i = 0; i < hits.length() && mHitsFound == -1; ++i )
226 if ( hits.at( i ).distance() < 500.0f && hits.at( i ).entity() &&
227 ( hits.at( i ).entity() == mCubeRoot ||
228 hits.at( i ).entity() == mAxisRoot ||
229 hits.at( i ).entity()->parent() == mCubeRoot ||
230 hits.at( i ).entity()->parent() == mAxisRoot ) )
237 if ( mLastClickedButton == Qt::NoButton )
239 if ( mHitsFound != -1 )
241 if ( mCanvas->cursor() != Qt::ArrowCursor )
243 mPreviousCursor = mCanvas->cursor();
244 mCanvas->setCursor( Qt::ArrowCursor );
248 if ( mMapScene->
engine()->
renderSettings()->pickingSettings()->pickMethod() != Qt3DRender::QPickingSettings::TrianglePicking &&
249 mCubeRoot->isEnabled() )
251 mMapScene->
engine()->
renderSettings()->pickingSettings()->setPickMethod( Qt3DRender::QPickingSettings::TrianglePicking );
257 else if ( mLastClickedButton == Qt::MouseButton::RightButton && mHitsFound != -1 )
259 displayMenuAt( mLastClickedPos );
261 else if ( mLastClickedButton == Qt::MouseButton::LeftButton )
265 if ( mHitsFound != -1 )
267 if ( hits.at( mHitsFound ).entity() == mCubeRoot || hits.at( mHitsFound ).entity()->parent() == mCubeRoot )
269 switch ( hits.at( mHitsFound ).primitiveIndex() / 2 )
273 onCameraViewChangeEast();
278 onCameraViewChangeWest();
283 onCameraViewChangeNorth();
288 onCameraViewChangeSouth();
293 onCameraViewChangeTop();
298 onCameraViewChangeBottom();
309Qt3DRender::QViewport *Qgs3DAxis::constructAxisScene( Qt3DCore::QEntity *parent3DScene )
311 Qt3DRender::QViewport *axisViewport =
new Qt3DRender::QViewport;
315 mAxisSceneEntity =
new Qt3DCore::QEntity;
316 mAxisSceneEntity->setParent( parent3DScene );
317 mAxisSceneEntity->setObjectName(
"3DAxis_SceneEntity" );
319 mAxisObjectLayer =
new Qt3DRender::QLayer;
320 mAxisObjectLayer->setObjectName(
"3DAxis_ObjectLayer" );
321 mAxisObjectLayer->setParent( mAxisSceneEntity );
322 mAxisObjectLayer->setRecursive(
true );
324 mAxisCamera =
new Qt3DRender::QCamera;
325 mAxisCamera->setParent( mAxisSceneEntity );
326 mAxisCamera->setProjectionType( mCameraController->
camera()->projectionType() );
327 mAxisCamera->lens()->setFieldOfView( mCameraController->
camera()->lens()->fieldOfView() * 0.5f );
329 mAxisCamera->setUpVector( QVector3D( 0.0f, 0.0f, 1.0f ) );
330 mAxisCamera->setViewCenter( QVector3D( 0.0f, 0.0f, 0.0f ) );
333 Qt3DRender::QLayerFilter *axisLayerFilter =
new Qt3DRender::QLayerFilter( axisViewport );
334 axisLayerFilter->addLayer( mAxisObjectLayer );
336 Qt3DRender::QCameraSelector *axisCameraSelector =
new Qt3DRender::QCameraSelector;
337 axisCameraSelector->setParent( axisLayerFilter );
338 axisCameraSelector->setCamera( mAxisCamera );
342 Qt3DRender::QSortPolicy *sortPolicy =
new Qt3DRender::QSortPolicy( axisCameraSelector );
343 QVector<Qt3DRender::QSortPolicy::SortType> sortTypes = QVector<Qt3DRender::QSortPolicy::SortType>();
344 sortTypes << Qt3DRender::QSortPolicy::BackToFront;
345 sortPolicy->setSortTypes( sortTypes );
347 Qt3DRender::QClearBuffers *clearBuffers =
new Qt3DRender::QClearBuffers( sortPolicy );
348 clearBuffers->setBuffers( Qt3DRender::QClearBuffers::DepthBuffer );
354void Qgs3DAxis::constructLabelsScene( Qt3DCore::QEntity *parent3DScene )
356 mTwoDLabelSceneEntity =
new Qt3DCore::QEntity;
357 mTwoDLabelSceneEntity->setParent( parent3DScene );
358 mTwoDLabelSceneEntity->setEnabled(
true );
360 mTwoDLabelCamera =
new Qt3DRender::QCamera;
361 mTwoDLabelCamera->setParent( mTwoDLabelSceneEntity );
362 mTwoDLabelCamera->setProjectionType( Qt3DRender::QCameraLens::ProjectionType::OrthographicProjection );
365 mTwoDLabelCamera->setUpVector( QVector3D( 0.0f, 0.0f, 1.0f ) );
366 mTwoDLabelCamera->setViewCenter( QVector3D( 0.0f, 0.0f, 0.0f ) );
368 mTwoDLabelCamera->setPosition( QVector3D( 0.0f, 0.0f, 100.0f ) );
370 Qt3DRender::QLayer *twoDLayer =
new Qt3DRender::QLayer;
371 twoDLayer->setObjectName(
"3DAxis_LabelsLayer" );
372 twoDLayer->setRecursive(
true );
373 mTwoDLabelSceneEntity->addComponent( twoDLayer );
375 Qt3DRender::QLayerFilter *twoDLayerFilter =
new Qt3DRender::QLayerFilter;
376 twoDLayerFilter->addLayer( twoDLayer );
378 Qt3DRender::QCameraSelector *twoDCameraSelector =
new Qt3DRender::QCameraSelector;
379 twoDCameraSelector->setParent( twoDLayerFilter );
380 twoDCameraSelector->setCamera( mTwoDLabelCamera );
384 Qt3DRender::QSortPolicy *sortPolicy =
new Qt3DRender::QSortPolicy( twoDCameraSelector );
385 QVector<Qt3DRender::QSortPolicy::SortType> sortTypes = QVector<Qt3DRender::QSortPolicy::SortType>();
386 sortTypes << Qt3DRender::QSortPolicy::BackToFront;
387 sortPolicy->setSortTypes( sortTypes );
389 Qt3DRender::QClearBuffers *clearBuffers =
new Qt3DRender::QClearBuffers( sortPolicy );
390 clearBuffers->setBuffers( Qt3DRender::QClearBuffers::DepthBuffer );
392 twoDLayerFilter->setParent( mViewport );
397 const int viewportWidth =
static_cast<int>( std::round( mTwoDLabelCamera->lens()->right() - mTwoDLabelCamera->lens()->left() ) );
398 const int viewportHeight =
static_cast<int>( std::round( mTwoDLabelCamera->lens()->top() - mTwoDLabelCamera->lens()->bottom() ) );
399 QRect viewportRect(
static_cast<int>( std::round( mTwoDLabelCamera->lens()->left() ) ),
static_cast<int>( std::round( mTwoDLabelCamera->lens()->bottom() ) ),
400 viewportWidth, viewportHeight );
402 QVector3D destPos = sourcePos.project( sourceCamera->viewMatrix(), destCamera->projectionMatrix(), viewportRect );
403 destPos.setZ( 0.0f );
407void Qgs3DAxis::setEnableCube(
bool show )
409 mCubeRoot->setEnabled( show );
412 mCubeRoot->setParent( mAxisSceneEntity );
416 mCubeRoot->setParent(
static_cast<Qt3DCore::QEntity *
>(
nullptr ) );
420void Qgs3DAxis::setEnableAxis(
bool show )
422 mAxisRoot->setEnabled( show );
425 mAxisRoot->setParent( mAxisSceneEntity );
429 mAxisRoot->setParent(
static_cast<Qt3DCore::QEntity *
>(
nullptr ) );
432 mTextX->setEnabled( show );
433 mTextY->setEnabled( show );
434 mTextZ->setEnabled( show );
437void Qgs3DAxis::createAxisScene()
439 if ( !mAxisRoot || !mCubeRoot )
441 mAxisRoot =
new Qt3DCore::QEntity;
442 mAxisRoot->setParent( mAxisSceneEntity );
443 mAxisRoot->setObjectName(
"3DAxis_AxisRoot" );
444 mAxisRoot->addComponent( mAxisObjectLayer );
446 createAxis( Qt::Axis::XAxis );
447 createAxis( Qt::Axis::YAxis );
448 createAxis( Qt::Axis::ZAxis );
450 mCubeRoot =
new Qt3DCore::QEntity;
451 mCubeRoot->setParent( mAxisSceneEntity );
452 mCubeRoot->setObjectName(
"3DAxis_CubeRoot" );
453 mCubeRoot->addComponent( mAxisObjectLayer );
462 mAxisSceneEntity->setEnabled(
false );
463 setEnableAxis(
false );
464 setEnableCube(
false );
468 mAxisSceneEntity->setEnabled(
true );
471 setEnableCube(
false );
472 setEnableAxis(
true );
474 const QList< Qgis::CrsAxisDirection > axisDirections = mCrs.
axisOrdering();
476 if ( axisDirections.length() > 0 )
479 mTextY->setText(
"X?" );
481 if ( axisDirections.length() > 1 )
484 mTextY->setText(
"Y?" );
486 if ( axisDirections.length() > 2 )
489 mTextZ->setText( QStringLiteral(
"up" ) );
493 setEnableCube(
true );
494 setEnableAxis(
false );
498 setEnableCube(
false );
499 setEnableAxis(
true );
500 mTextX->setText(
"X?" );
501 mTextY->setText(
"Y?" );
502 mTextZ->setText(
"Z?" );
505 updateAxisLabelPosition();
509void Qgs3DAxis::createKeyboardShortCut()
514 QWidget *mapCanvas =
dynamic_cast<QWidget *
>( eng->parent() );
521 QShortcut *shortcutHome =
new QShortcut( QKeySequence( Qt::CTRL + Qt::Key_1 ), mapCanvas );
522 connect( shortcutHome, &QShortcut::activated,
this, [
this]( ) {onCameraViewChangeHome();} );
524 QShortcut *shortcutTop =
new QShortcut( QKeySequence( Qt::CTRL + Qt::Key_5 ), mapCanvas );
525 connect( shortcutTop, &QShortcut::activated,
this, [
this]( ) {onCameraViewChangeTop();} );
527 QShortcut *shortcutNorth =
new QShortcut( QKeySequence( Qt::CTRL + Qt::Key_8 ), mapCanvas );
528 connect( shortcutNorth, &QShortcut::activated,
this, [
this]( ) {onCameraViewChangeNorth();} );
530 QShortcut *shortcutEast =
new QShortcut( QKeySequence( Qt::CTRL + Qt::Key_6 ), mapCanvas );
531 connect( shortcutEast, &QShortcut::activated,
this, [
this]( ) {onCameraViewChangeEast();} );
533 QShortcut *shortcutSouth =
new QShortcut( QKeySequence( Qt::CTRL + Qt::Key_2 ), mapCanvas );
534 connect( shortcutSouth, &QShortcut::activated,
this, [
this]( ) {onCameraViewChangeSouth();} );
536 QShortcut *shortcutWest =
new QShortcut( QKeySequence( Qt::CTRL + Qt::Key_4 ), mapCanvas );
537 connect( shortcutWest, &QShortcut::activated,
this, [
this]( ) {onCameraViewChangeWest();} );
542void Qgs3DAxis::createMenu()
547 QAction *typeOffAct =
new QAction( tr(
"&Off" ), mMenu );
548 typeOffAct->setCheckable(
true );
549 typeOffAct->setStatusTip( tr(
"Disable 3D axis" ) );
553 typeOffAct->setChecked(
true );
556 QAction *typeCrsAct =
new QAction( tr(
"Coordinate Reference &System" ), mMenu );
557 typeCrsAct->setCheckable(
true );
558 typeCrsAct->setStatusTip( tr(
"Coordinate Reference System 3D axis" ) );
562 typeCrsAct->setChecked(
true );
565 QAction *typeCubeAct =
new QAction( tr(
"&Cube" ), mMenu );
566 typeCubeAct->setCheckable(
true );
567 typeCubeAct->setStatusTip( tr(
"Cube 3D axis" ) );
571 typeCubeAct->setChecked(
true );
574 QActionGroup *typeGroup =
new QActionGroup( mMenu );
575 typeGroup->addAction( typeOffAct );
576 typeGroup->addAction( typeCrsAct );
577 typeGroup->addAction( typeCubeAct );
583 QMenu *typeMenu =
new QMenu( QStringLiteral(
"Axis Type" ), mMenu );
584 Q_ASSERT( typeMenu );
585 typeMenu->addAction( typeOffAct );
586 typeMenu->addAction( typeCrsAct );
587 typeMenu->addAction( typeCubeAct );
588 mMenu->addMenu( typeMenu );
591 QAction *hPosLeftAct =
new QAction( tr(
"&Left" ), mMenu );
592 hPosLeftAct->setCheckable(
true );
596 hPosLeftAct->setChecked(
true );
599 QAction *hPosMiddleAct =
new QAction( tr(
"&Center" ), mMenu );
600 hPosMiddleAct->setCheckable(
true );
604 hPosMiddleAct->setChecked(
true );
607 QAction *hPosRightAct =
new QAction( tr(
"&Right" ), mMenu );
608 hPosRightAct->setCheckable(
true );
612 hPosRightAct->setChecked(
true );
615 QActionGroup *hPosGroup =
new QActionGroup( mMenu );
616 hPosGroup->addAction( hPosLeftAct );
617 hPosGroup->addAction( hPosMiddleAct );
618 hPosGroup->addAction( hPosRightAct );
620 connect( hPosLeftAct, &QAction::triggered,
this, [
this](
bool ) {onAxisHorizPositionChanged( Qt::AnchorPoint::AnchorLeft );} );
621 connect( hPosMiddleAct, &QAction::triggered,
this, [
this](
bool ) {onAxisHorizPositionChanged( Qt::AnchorPoint::AnchorHorizontalCenter );} );
622 connect( hPosRightAct, &QAction::triggered,
this, [
this](
bool ) {onAxisHorizPositionChanged( Qt::AnchorPoint::AnchorRight );} );
624 QMenu *horizPosMenu =
new QMenu( QStringLiteral(
"Horizontal Position" ), mMenu );
625 horizPosMenu->addAction( hPosLeftAct );
626 horizPosMenu->addAction( hPosMiddleAct );
627 horizPosMenu->addAction( hPosRightAct );
628 mMenu->addMenu( horizPosMenu );
631 QAction *vPosTopAct =
new QAction( tr(
"&Top" ), mMenu );
632 vPosTopAct->setCheckable(
true );
636 vPosTopAct->setChecked(
true );
639 QAction *vPosMiddleAct =
new QAction( tr(
"&Middle" ), mMenu );
640 vPosMiddleAct->setCheckable(
true );
644 vPosMiddleAct->setChecked(
true );
647 QAction *vPosBottomAct =
new QAction( tr(
"&Bottom" ), mMenu );
648 vPosBottomAct->setCheckable(
true );
652 vPosBottomAct->setChecked(
true );
655 QActionGroup *vPosGroup =
new QActionGroup( mMenu );
656 vPosGroup->addAction( vPosTopAct );
657 vPosGroup->addAction( vPosMiddleAct );
658 vPosGroup->addAction( vPosBottomAct );
660 connect( vPosTopAct, &QAction::triggered,
this, [
this](
bool ) {onAxisVertPositionChanged( Qt::AnchorPoint::AnchorTop );} );
661 connect( vPosMiddleAct, &QAction::triggered,
this, [
this](
bool ) {onAxisVertPositionChanged( Qt::AnchorPoint::AnchorVerticalCenter );} );
662 connect( vPosBottomAct, &QAction::triggered,
this, [
this](
bool ) {onAxisVertPositionChanged( Qt::AnchorPoint::AnchorBottom );} );
664 QMenu *vertPosMenu =
new QMenu( QStringLiteral(
"Vertical Position" ), mMenu );
665 vertPosMenu->addAction( vPosTopAct );
666 vertPosMenu->addAction( vPosMiddleAct );
667 vertPosMenu->addAction( vPosBottomAct );
668 mMenu->addMenu( vertPosMenu );
671 QAction *viewHomeAct =
new QAction( tr(
"&Home" ) +
"\t Ctrl+1", mMenu );
672 QAction *viewTopAct =
new QAction( tr(
"&Top" ) +
"\t Ctrl+5", mMenu );
673 QAction *viewNorthAct =
new QAction( tr(
"&North" ) +
"\t Ctrl+8", mMenu );
674 QAction *viewEastAct =
new QAction( tr(
"&East" ) +
"\t Ctrl+6", mMenu );
675 QAction *viewSouthAct =
new QAction( tr(
"&South" ) +
"\t Ctrl+2", mMenu );
676 QAction *viewWestAct =
new QAction( tr(
"&West" ) +
"\t Ctrl+4", mMenu );
677 QAction *viewBottomAct =
new QAction( tr(
"&Bottom" ), mMenu );
679 connect( viewHomeAct, &QAction::triggered,
this, &Qgs3DAxis::onCameraViewChangeHome );
680 connect( viewTopAct, &QAction::triggered,
this, &Qgs3DAxis::onCameraViewChangeTop );
681 connect( viewNorthAct, &QAction::triggered,
this, &Qgs3DAxis::onCameraViewChangeNorth );
682 connect( viewEastAct, &QAction::triggered,
this, &Qgs3DAxis::onCameraViewChangeEast );
683 connect( viewSouthAct, &QAction::triggered,
this, &Qgs3DAxis::onCameraViewChangeSouth );
684 connect( viewWestAct, &QAction::triggered,
this, &Qgs3DAxis::onCameraViewChangeWest );
685 connect( viewBottomAct, &QAction::triggered,
this, &Qgs3DAxis::onCameraViewChangeBottom );
687 QMenu *viewMenu =
new QMenu( QStringLiteral(
"Camera View" ), mMenu );
688 viewMenu->addAction( viewHomeAct );
689 viewMenu->addAction( viewTopAct );
690 viewMenu->addAction( viewNorthAct );
691 viewMenu->addAction( viewEastAct );
692 viewMenu->addAction( viewSouthAct );
693 viewMenu->addAction( viewWestAct );
694 viewMenu->addAction( viewBottomAct );
695 mMenu->addMenu( viewMenu );
701void Qgs3DAxis::hideMenu()
703 if ( mMenu && mMenu->isVisible() )
707void Qgs3DAxis::displayMenuAt(
const QPoint &sourcePos )
714 mMenu->popup( mCanvas->mapToGlobal( sourcePos ) );
724void Qgs3DAxis::onAxisHorizPositionChanged( Qt::AnchorPoint pos )
731void Qgs3DAxis::onAxisVertPositionChanged( Qt::AnchorPoint pos )
738void Qgs3DAxis::onCameraViewChange(
float pitch,
float yaw )
741 double elevation = 0.0;
745 QVector3D camPos = mCameraController->
camera()->position();
746 QgsRayCastingUtils::Ray3D ray( camPos, pos.
toVector3D() - camPos, mCameraController->
camera()->farPlane() );
748 if ( !hits.isEmpty() )
750 elevation = hits.at( 0 ).pos.y();
751 QgsDebugMsgLevel( QString(
"Computed elevation from terrain: %1" ).arg( elevation ), 2 );
765void Qgs3DAxis::createCube( )
767 QVector3D minPos = QVector3D( -mCylinderLength * 0.5f, -mCylinderLength * 0.5f, -mCylinderLength * 0.5f );
770 Qt3DCore::QEntity *cubeLineEntity =
new Qt3DCore::QEntity( mCubeRoot );
771 cubeLineEntity->setObjectName(
"3DAxis_cubeline" );
772 Qgs3DWiredMesh *cubeLine =
new Qgs3DWiredMesh;
773 QgsAABB box =
QgsAABB( -mCylinderLength * 0.5f, -mCylinderLength * 0.5f, -mCylinderLength * 0.5f,
774 mCylinderLength * 0.5f, mCylinderLength * 0.5f, mCylinderLength * 0.5f );
776 cubeLineEntity->addComponent( cubeLine );
778 Qt3DExtras::QPhongMaterial *cubeLineMaterial =
new Qt3DExtras::QPhongMaterial;
779 cubeLineMaterial->setAmbient( Qt::white );
780 cubeLineEntity->addComponent( cubeLineMaterial );
783 Qt3DExtras::QCuboidMesh *cubeMesh =
new Qt3DExtras::QCuboidMesh;
784 cubeMesh->setObjectName(
"3DAxis_cubemesh" );
785 cubeMesh->setXExtent( mCylinderLength );
786 cubeMesh->setYExtent( mCylinderLength );
787 cubeMesh->setZExtent( mCylinderLength );
788 mCubeRoot->addComponent( cubeMesh );
790 Qt3DExtras::QPhongMaterial *cubeMaterial =
new Qt3DExtras::QPhongMaterial( mCubeRoot );
791 cubeMaterial->setAmbient( QColor( 100, 100, 100, 50 ) );
792 cubeMaterial->setShininess( 100 );
793 mCubeRoot->addComponent( cubeMaterial );
795 Qt3DCore::QTransform *cubeTransform =
new Qt3DCore::QTransform;
796 QMatrix4x4 transformMatrixcube;
798 transformMatrixcube.translate( minPos + QVector3D( mCylinderLength * 0.5f, mCylinderLength * 0.5f, mCylinderLength * 0.5f ) );
799 cubeTransform->setMatrix( transformMatrixcube );
800 mCubeRoot->addComponent( cubeTransform );
804 const int fontSize =
static_cast<int>( std::round( 0.75f *
static_cast<float>( mFontSize ) ) );
805 const float textHeight =
static_cast<float>( fontSize ) * 1.5f;
807 const QFont font = createFont( fontSize );
810 text = QStringLiteral(
"top" );
811 textWidth =
static_cast<float>( text.length() * fontSize ) * 0.75f;
812 QVector3D translation = minPos + QVector3D(
813 mCylinderLength * 0.5f - textWidth / 2.0f,
814 mCylinderLength * 0.5f - textHeight / 2.0f,
815 mCylinderLength * 1.01f );
817 mCubeLabels << addCubeText( text, textHeight, textWidth, font, rotation, translation );
821 text = QStringLiteral(
"btm" );
822 textWidth =
static_cast<float>( text.length() * fontSize ) * 0.75f;
823 QVector3D translation = minPos + QVector3D(
824 mCylinderLength * 0.5f - textWidth / 2.0f,
825 mCylinderLength * 0.5f + textHeight / 2.0f,
826 -mCylinderLength * 0.01f );
828 rotation.rotate( 180.0f, QVector3D( 1.0f, 0.0f, 0.0f ).normalized() );
829 mCubeLabels << addCubeText( text, textHeight, textWidth, font, rotation, translation );
833 text = QStringLiteral(
"west" );
834 textWidth =
static_cast<float>( text.length() * fontSize ) * 0.75f;
835 QVector3D translation = minPos + QVector3D(
836 - mCylinderLength * 0.01f,
837 mCylinderLength * 0.5f + textWidth / 2.0f,
838 mCylinderLength * 0.5f - textHeight / 2.0f );
840 rotation.rotate( 90.0f, QVector3D( 0.0f, -1.0f, 0.0f ).normalized() );
841 rotation.rotate( 90.0f, QVector3D( 0.0f, 0.0f, -1.0f ).normalized() );
842 mCubeLabels << addCubeText( text, textHeight, textWidth, font, rotation, translation );
846 text = QStringLiteral(
"east" );
847 textWidth =
static_cast<float>( text.length() * fontSize ) * 0.75f;
848 QVector3D translation = minPos + QVector3D(
849 mCylinderLength * 1.01f,
850 mCylinderLength * 0.5f - textWidth / 2.0f,
851 mCylinderLength * 0.5f - textHeight / 2.0f );
853 rotation.rotate( 90.0f, QVector3D( 0.0f, 1.0f, 0.0f ).normalized() );
854 rotation.rotate( 90.0f, QVector3D( 0.0f, 0.0f, 1.0f ).normalized() );
855 mCubeLabels << addCubeText( text, textHeight, textWidth, font, rotation, translation );
859 text = QStringLiteral(
"south" );
860 textWidth =
static_cast<float>( text.length() * fontSize ) * 0.75f;
861 QVector3D translation = minPos + QVector3D(
862 mCylinderLength * 0.5f - textWidth / 2.0f,
863 - mCylinderLength * 0.01f,
864 mCylinderLength * 0.5f - textHeight / 2.0f );
866 rotation.rotate( 90.0f, QVector3D( 1.0f, 0.0f, 0.0f ).normalized() );
867 mCubeLabels << addCubeText( text, textHeight, textWidth, font, rotation, translation );
871 text = QStringLiteral(
"north" );
872 textWidth =
static_cast<float>( text.length() * fontSize ) * 0.75f;
873 QVector3D translation = minPos + QVector3D(
874 mCylinderLength * 0.5f + textWidth / 2.0f,
875 mCylinderLength * 1.01f,
876 mCylinderLength * 0.5f - textHeight / 2.0f );
878 rotation.rotate( 90.0f, QVector3D( -1.0f, 0.0f, 0.0f ).normalized() );
879 rotation.rotate( 180.0f, QVector3D( 0.0f, 0.0f, 1.0f ).normalized() );
880 mCubeLabels << addCubeText( text, textHeight, textWidth, font, rotation, translation );
883 for ( Qt3DExtras::QText2DEntity *l : std::as_const( mCubeLabels ) )
885 l->setParent( mCubeRoot );
889Qt3DExtras::QText2DEntity *Qgs3DAxis::addCubeText(
const QString &text,
float textHeight,
float textWidth,
const QFont &font,
const QMatrix4x4 &rotation,
const QVector3D &translation )
891 Qt3DExtras::QText2DEntity *textEntity =
new Qt3DExtras::QText2DEntity;
892 textEntity->setObjectName(
"3DAxis_cube_label_" + text );
893 textEntity->setFont( font );
894 textEntity->setHeight( textHeight );
895 textEntity->setWidth( textWidth );
896 textEntity->setColor( QColor( 192, 192, 192 ) );
897 textEntity->setText( text );
899 Qt3DCore::QTransform *textFrontTransform =
new Qt3DCore::QTransform();
900 textFrontTransform->setMatrix( rotation );
901 textFrontTransform->setTranslation( translation );
902 textEntity->addComponent( textFrontTransform );
907void Qgs3DAxis::createAxis( Qt::Axis axisType )
909 float cylinderRadius = 0.05f * mCylinderLength;
910 float coneLength = 0.3f * mCylinderLength;
911 float coneBottomRadius = 0.1f * mCylinderLength;
913 QQuaternion rotation;
916 Qt3DExtras::QText2DEntity *text =
nullptr;
917 Qt3DCore::QTransform *textTransform =
nullptr;
922 case Qt::Axis::XAxis:
923 mTextX =
new Qt3DExtras::QText2DEntity( );
924 mTextX->setParent( mTwoDLabelSceneEntity );
925 connect( mTextX, &Qt3DExtras::QText2DEntity::textChanged,
this, [
this](
const QString & text )
927 updateAxisLabelText( mTextX, text );
929 mTextTransformX =
new Qt3DCore::QTransform();
930 mTextCoordX = QVector3D( mCylinderLength + coneLength / 2.0f, 0.0f, 0.0f );
932 rotation = QQuaternion::fromAxisAndAngle( QVector3D( 0.0f, 0.0f, 1.0f ), -90.0f );
935 textTransform = mTextTransformX;
936 name =
"3DAxis_axisX";
939 case Qt::Axis::YAxis:
940 mTextY =
new Qt3DExtras::QText2DEntity( );
941 mTextY->setParent( mTwoDLabelSceneEntity );
942 connect( mTextY, &Qt3DExtras::QText2DEntity::textChanged,
this, [
this](
const QString & text )
944 updateAxisLabelText( mTextY, text );
946 mTextTransformY =
new Qt3DCore::QTransform();
947 mTextCoordY = QVector3D( 0.0f, mCylinderLength + coneLength / 2.0f, 0.0f );
949 rotation = QQuaternion::fromAxisAndAngle( QVector3D( 0.0f, 0.0f, 0.0f ), 0.0f );
952 textTransform = mTextTransformY;
953 name =
"3DAxis_axisY";
956 case Qt::Axis::ZAxis:
957 mTextZ =
new Qt3DExtras::QText2DEntity( );
958 mTextZ->setParent( mTwoDLabelSceneEntity );
959 connect( mTextZ, &Qt3DExtras::QText2DEntity::textChanged,
this, [
this](
const QString & text )
961 updateAxisLabelText( mTextZ, text );
963 mTextTransformZ =
new Qt3DCore::QTransform();
964 mTextCoordZ = QVector3D( 0.0f, 0.0f, mCylinderLength + coneLength / 2.0f );
966 rotation = QQuaternion::fromAxisAndAngle( QVector3D( 1.0f, 0.0f, 0.0f ), 90.0f );
969 textTransform = mTextTransformZ;
970 name =
"3DAxis_axisZ";
978 Qt3DCore::QEntity *cylinder =
new Qt3DCore::QEntity( mAxisRoot );
979 cylinder->setObjectName( name );
981 Qt3DExtras::QCylinderMesh *cylinderMesh =
new Qt3DExtras::QCylinderMesh;
982 cylinderMesh->setRadius( cylinderRadius );
983 cylinderMesh->setLength( mCylinderLength );
984 cylinderMesh->setRings( 10 );
985 cylinderMesh->setSlices( 4 );
986 cylinder->addComponent( cylinderMesh );
988 Qt3DExtras::QPhongMaterial *cylinderMaterial =
new Qt3DExtras::QPhongMaterial( cylinder );
989 cylinderMaterial->setAmbient( color );
990 cylinderMaterial->setShininess( 0 );
991 cylinder->addComponent( cylinderMaterial );
993 Qt3DCore::QTransform *cylinderTransform =
new Qt3DCore::QTransform;
994 QMatrix4x4 transformMatrixCylinder;
995 transformMatrixCylinder.rotate( rotation );
996 transformMatrixCylinder.translate( QVector3D( 0.0f, mCylinderLength / 2.0f, 0.0f ) );
997 cylinderTransform->setMatrix( transformMatrixCylinder );
998 cylinder->addComponent( cylinderTransform );
1001 Qt3DCore::QEntity *coneEntity =
new Qt3DCore::QEntity( mAxisRoot );
1002 coneEntity->setObjectName( name );
1003 Qt3DExtras::QConeMesh *coneMesh =
new Qt3DExtras::QConeMesh;
1004 coneMesh->setLength( coneLength );
1005 coneMesh->setBottomRadius( coneBottomRadius );
1006 coneMesh->setTopRadius( 0.0f );
1007 coneMesh->setRings( 10 );
1008 coneMesh->setSlices( 4 );
1009 coneEntity->addComponent( coneMesh );
1011 Qt3DExtras::QPhongMaterial *coneMaterial =
new Qt3DExtras::QPhongMaterial( coneEntity );
1012 coneMaterial->setAmbient( color );
1013 coneMaterial->setShininess( 0 );
1014 coneEntity->addComponent( coneMaterial );
1016 Qt3DCore::QTransform *coneTransform =
new Qt3DCore::QTransform;
1017 QMatrix4x4 transformMatrixCone;
1018 transformMatrixCone.rotate( rotation );
1019 transformMatrixCone.translate( QVector3D( 0.0f, mCylinderLength, 0.0f ) );
1020 coneTransform->setMatrix( transformMatrixCone );
1021 coneEntity->addComponent( coneTransform );
1024 text->setColor( QColor( 192, 192, 192, 192 ) );
1025 text->addComponent( textTransform );
1031 onAxisViewportSizeUpdate();
1034void Qgs3DAxis::onAxisViewportSizeUpdate(
int )
1038 double windowWidth = ( double )mCanvas->width();
1039 double windowHeight = ( double )mCanvas->height();
1044 QgsDebugMsgLevel( QString(
"onAxisViewportSizeUpdate window w/h: %1px / %2px" )
1045 .arg( windowWidth ).arg( windowHeight ), 2 );
1046 QgsDebugMsgLevel( QString(
"onAxisViewportSizeUpdate window physicalDpi %1 (%2, %3)" )
1047 .arg( mCanvas->screen()->physicalDotsPerInch() )
1048 .arg( mCanvas->screen()->physicalDotsPerInchX() )
1049 .arg( mCanvas->screen()->physicalDotsPerInchY() ), 2 );
1050 QgsDebugMsgLevel( QString(
"onAxisViewportSizeUpdate window logicalDotsPerInch %1 (%2, %3)" )
1051 .arg( mCanvas->screen()->logicalDotsPerInch() )
1052 .arg( mCanvas->screen()->logicalDotsPerInchX() )
1053 .arg( mCanvas->screen()->logicalDotsPerInchY() ), 2 );
1055 QgsDebugMsgLevel( QString(
"onAxisViewportSizeUpdate window pixel ratio %1" )
1056 .arg( mCanvas->screen()->devicePixelRatio() ), 2 );
1067 double defaultViewportPixelSize = ( ( double )settings.
defaultViewportSize() / 25.4 ) * 92.0;
1071 double viewportPixelSize = defaultViewportPixelSize + ( ( double )settings.
defaultViewportSize() / 25.4 )
1072 * ( mCanvas->screen()->physicalDotsPerInch() - 92.0 ) * 0.7;
1073 QgsDebugMsgLevel( QString(
"onAxisViewportSizeUpdate viewportPixelSize %1" ).arg( viewportPixelSize ), 2 );
1074 double widthRatio = viewportPixelSize / windowWidth;
1075 double heightRatio = widthRatio * windowWidth / windowHeight;
1077 QgsDebugMsgLevel( QString(
"3DAxis viewport ratios width: %1% / height: %2%" ).arg( widthRatio ).arg( heightRatio ), 2 );
1079 if ( heightRatio * windowHeight < viewportPixelSize )
1081 heightRatio = viewportPixelSize / windowHeight;
1082 widthRatio = heightRatio * windowHeight / windowWidth;
1083 QgsDebugMsgLevel( QString(
"3DAxis viewport, height too small, ratios adjusted to width: %1% / height: %2%" ).arg( widthRatio ).arg( heightRatio ), 2 );
1088 QgsDebugMsgLevel(
"viewport takes too much place into the 3d view, disabling it", 2 );
1090 mViewport->setEnabled(
false );
1091 setEnableCube(
false );
1092 setEnableAxis(
false );
1097 mAxisScaleFactor = viewportPixelSize / defaultViewportPixelSize;
1098 QgsDebugMsgLevel( QString(
"3DAxis viewport mAxisScaleFactor %1" ).arg( mAxisScaleFactor ), 2 );
1100 if ( ! mViewport->isEnabled() )
1103 setEnableAxis(
true );
1105 setEnableCube(
true );
1107 mViewport->setEnabled(
true );
1109 float xRatio = 1.0f;
1110 float yRatio = 1.0f;
1114 xRatio = 0.5f -
static_cast<float>( widthRatio ) / 2.0f;
1116 xRatio = 1.0f -
static_cast<float>( widthRatio );
1120 else if ( settings.
verticalPosition() == Qt::AnchorPoint::AnchorVerticalCenter )
1121 yRatio = 0.5f -
static_cast<float>( heightRatio ) / 2.0f;
1123 yRatio = 1.0f -
static_cast<float>( heightRatio );
1125 QgsDebugMsgLevel( QString(
"Qgs3DAxis: update viewport: %1 x %2 x %3 x %4" ).arg( xRatio ).arg( yRatio ).arg( widthRatio ).arg( heightRatio ), 2 );
1126 mViewport->setNormalizedRect( QRectF( xRatio, yRatio, widthRatio, heightRatio ) );
1130 const float halfWidthSize =
static_cast<float>( windowWidth * widthRatio / 2.0 );
1131 const float halfHeightSize =
static_cast<float>( windowWidth * widthRatio / 2.0 );
1132 mTwoDLabelCamera->lens()->setOrthographicProjection(
1133 -halfWidthSize, halfWidthSize,
1134 -halfHeightSize, halfHeightSize,
1135 mTwoDLabelCamera->lens()->nearPlane(), mTwoDLabelCamera->lens()->farPlane() );
1137 updateAxisLabelPosition();
1142void Qgs3DAxis::onCameraUpdate( )
1144 Qt3DRender::QCamera *parentCamera = mCameraController->
camera();
1146 if ( parentCamera->viewVector() != mPreviousVector
1147 && !std::isnan( parentCamera->viewVector().x() )
1148 && !std::isnan( parentCamera->viewVector().y() )
1149 && !std::isnan( parentCamera->viewVector().z() ) )
1151 mPreviousVector = parentCamera->viewVector();
1152 QVector3D mainCameraShift = parentCamera->viewVector().normalized();
1153 float zy_swap = mainCameraShift.y();
1154 mainCameraShift.setY( mainCameraShift.z() );
1155 mainCameraShift.setZ( -zy_swap );
1156 mainCameraShift.setX( -mainCameraShift.x() );
1158 if ( mAxisCamera->projectionType() == Qt3DRender::QCameraLens::ProjectionType::OrthographicProjection )
1160 mAxisCamera->setPosition( mainCameraShift );
1164 mAxisCamera->setPosition( mainCameraShift * mCylinderLength * 9.0 );
1167 if ( mAxisRoot->isEnabled() )
1169 updateAxisLabelPosition();
1174void Qgs3DAxis::updateAxisLabelPosition()
1176 if ( mTextTransformX && mTextTransformY && mTextTransformZ )
1178 mTextTransformX->setTranslation(
from3DTo2DLabelPosition( mTextCoordX *
static_cast<float>( mAxisScaleFactor ), mAxisCamera, mTwoDLabelCamera ) );
1179 updateAxisLabelText( mTextX, mTextX->text() );
1181 mTextTransformY->setTranslation(
from3DTo2DLabelPosition( mTextCoordY *
static_cast<float>( mAxisScaleFactor ), mAxisCamera, mTwoDLabelCamera ) );
1182 updateAxisLabelText( mTextY, mTextY->text() );
1184 mTextTransformZ->setTranslation(
from3DTo2DLabelPosition( mTextCoordZ *
static_cast<float>( mAxisScaleFactor ), mAxisCamera, mTwoDLabelCamera ) );
1185 updateAxisLabelText( mTextZ, mTextZ->text() );
1189void Qgs3DAxis::updateAxisLabelText( Qt3DExtras::QText2DEntity *textEntity,
const QString &text )
1191 const float scaledFontSize =
static_cast<float>( mAxisScaleFactor ) *
static_cast<float>( mFontSize );
1192 const QFont font = createFont(
static_cast<int>( std::round( scaledFontSize ) ) );
1193 textEntity->setFont( font );
1194 textEntity->setWidth( scaledFontSize *
static_cast<float>( text.length() ) );
1195 textEntity->setHeight( 1.5f * scaledFontSize );
1198QFont Qgs3DAxis::createFont(
int pointSize )
1200 QFont font = QFontDatabase::systemFont( QFontDatabase::FixedFont );
1201 font.setPointSize( pointSize );
1202 font.setWeight( QFont::Weight::Black );
1203 font.setStyleStrategy( QFont::StyleStrategy::ForceOutline );
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.
void onAxisSettingsChanged()
Force update of the axis and the viewport when a setting has changed.
QVector3D from3DTo2DLabelPosition(const QVector3D &sourcePos, Qt3DRender::QCamera *sourceCamera, Qt3DRender::QCamera *destCamera)
Project a 3D position from sourceCamera to a 2D position for destCamera.
Qgs3DAxis(Qgs3DMapCanvas *canvas, Qt3DCore::QEntity *parent3DScene, Qgs3DMapScene *mapScene, QgsCameraController *camera, Qgs3DMapSettings *map)
Default Qgs3DAxis constructor.
Qt3DRender::QFrameGraphNode * activeFrameGraph() const
Returns the node of the active frame graph.
QgsAbstract3DEngine * engine() const
Returns the abstract 3D engine.
QgsTerrainEntity * terrainEntity()
Returns terrain entity (may be temporarily nullptr)
Qgs3DAxisSettings get3DAxisSettings() const
Returns the current configuration of 3d axis.
float terrainElevationOffset() const
Returns the elevation offset of the terrain (used to move the terrain up or down)
void set3DAxisSettings(const Qgs3DAxisSettings &axisSettings, bool force=false)
Sets the current configuration of 3d axis.
bool terrainRenderingEnabled() const
Returns whether the 2D terrain surface will be rendered.
void axisSettingsChanged()
Emitted when 3d axis rendering settings are changed.
QList< QVector3D > verticesForLines() const
Returns a list of pairs of vertices (useful for display of bounding boxes)
virtual Qt3DRender::QRenderSettings * renderSettings()=0
Returns access to the engine's render settings (the frame graph can be accessed from here)
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.
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...
double z() const
Returns Z coordinate.
QVector3D toVector3D() const
Converts the current object to QVector3D.
double x() const
Returns X coordinate.
void set(double x, double y, double z)
Sets vector coordinates.
#define QgsDebugMsgLevel(str, level)
const QgsCoordinateReferenceSystem & crs
Helper struct to store ray casting parameters.