QGIS API Documentation 3.43.0-Master (c4a2e9c6d2f)
qgs3dmapcanvas.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgs3dmapcanvas.cpp
3 --------------------------------------
4 Date : July 2017
5 Copyright : (C) 2017 by Martin Dobias
6 Email : wonder dot sk at gmail 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 <Qt3DCore/QAspectEngine>
17#if QT_VERSION >= QT_VERSION_CHECK( 6, 0, 0 )
18#include <Qt3DCore/QCoreAspect>
19#endif
20#include <Qt3DRender/QRenderSettings>
21#include <Qt3DRender/QRenderAspect>
22#include <Qt3DInput/QInputAspect>
23#include <Qt3DInput/QInputSettings>
24#include <Qt3DLogic/QLogicAspect>
25#include <Qt3DLogic/QFrameAction>
26
27#include "qgs3daxis.h"
28#include "qgs3dmapcanvas.h"
29#include "qgs3dmapscene.h"
30#include "qgswindow3dengine.h"
31#include "qgs3dmapsettings.h"
32#include "qgs3dmaptool.h"
34#include "qgsframegraph.h"
36#include "qgsrubberband3d.h"
37
38#include "moc_qgs3dmapcanvas.cpp"
39
40
42 : m_aspectEngine( new Qt3DCore::QAspectEngine )
43 , m_renderAspect( new Qt3DRender::QRenderAspect )
44 , m_inputAspect( new Qt3DInput::QInputAspect )
45 , m_logicAspect( new Qt3DLogic::QLogicAspect )
46 , m_renderSettings( new Qt3DRender::QRenderSettings )
47 , m_defaultCamera( new Qt3DRender::QCamera )
48 , m_inputSettings( new Qt3DInput::QInputSettings )
49 , m_root( new Qt3DCore::QEntity )
50 , m_userRoot( nullptr )
51 , m_initialized( false )
52{
53 setSurfaceType( QSurface::OpenGLSurface );
54
55 // register aspects
56#if QT_VERSION >= QT_VERSION_CHECK( 6, 0, 0 )
57 m_aspectEngine->registerAspect( new Qt3DCore::QCoreAspect );
58#endif
59 m_aspectEngine->registerAspect( m_renderAspect );
60 m_aspectEngine->registerAspect( m_inputAspect );
61 m_aspectEngine->registerAspect( m_logicAspect );
62
63 m_defaultCamera->setParent( m_root );
64 m_inputSettings->setEventSource( this );
65
66 const QgsSettings setting;
67 mEngine = new QgsWindow3DEngine( this );
68
69 connect( mEngine, &QgsAbstract3DEngine::imageCaptured, this, [=]( const QImage &image ) {
70 image.save( mCaptureFileName, mCaptureFileFormat.toLocal8Bit().data() );
71 mEngine->setRenderCaptureEnabled( false );
72 emit savedAsImage( mCaptureFileName );
73 } );
74
75 setCursor( Qt::OpenHandCursor );
76 installEventFilter( this );
77}
78
80{
81 if ( mMapTool )
82 delete mMapTool;
83 // make sure the scene is deleted while map settings object is still alive
84 mScene->deleteLater();
85 mScene = nullptr;
86 mMapSettings->deleteLater();
87 mMapSettings = nullptr;
88 qDeleteAll( mHighlights );
89 mHighlights.clear();
90
91 delete m_aspectEngine;
92}
93
94void Qgs3DMapCanvas::setRootEntity( Qt3DCore::QEntity *root )
95{
96 if ( m_userRoot != root )
97 {
98 if ( m_userRoot )
99 m_userRoot->setParent( static_cast<Qt3DCore::QNode *>( nullptr ) );
100 if ( root )
101 root->setParent( m_root );
102 m_userRoot = root;
103 }
104}
105
106void Qgs3DMapCanvas::setActiveFrameGraph( Qt3DRender::QFrameGraphNode *activeFrameGraph )
107{
108 m_renderSettings->setActiveFrameGraph( activeFrameGraph );
109}
110
111Qt3DRender::QFrameGraphNode *Qgs3DMapCanvas::activeFrameGraph() const
112{
113 return m_renderSettings->activeFrameGraph();
114}
115
116Qt3DRender::QCamera *Qgs3DMapCanvas::camera() const
117{
118 return m_defaultCamera;
119}
120
121Qt3DRender::QRenderSettings *Qgs3DMapCanvas::renderSettings() const
122{
123 return m_renderSettings;
124}
125
126void Qgs3DMapCanvas::showEvent( QShowEvent *e )
127{
128 if ( !m_initialized )
129 {
130 m_root->addComponent( m_renderSettings );
131 m_root->addComponent( m_inputSettings );
132 m_aspectEngine->setRootEntity( Qt3DCore::QEntityPtr( m_root ) );
133
134 m_initialized = true;
135 }
136 QWindow::showEvent( e );
137}
138
139void Qgs3DMapCanvas::resizeEvent( QResizeEvent * )
140{
141 m_defaultCamera->setAspectRatio( float( width() ) / std::max( 1.f, static_cast<float>( height() ) ) );
142
143 mEngine->setSize( size() );
144}
145
147{
148 // TODO: eventually we want to get rid of this
149 Q_ASSERT( !mMapSettings );
150 Q_ASSERT( !mScene );
151
152 Qgs3DMapScene *newScene = new Qgs3DMapScene( *mapSettings, mEngine );
153
154 mEngine->setSize( size() );
155 mEngine->setRootEntity( newScene );
156
157 if ( mScene )
158 {
159 mScene->deleteLater();
160 }
161 mScene = newScene;
165
166 delete mMapSettings;
167 mMapSettings = mapSettings;
168
169 resetView();
170
171 connect( cameraController(), &QgsCameraController::setCursorPosition, this, [=]( QPoint point ) {
172 QCursor::setPos( mapToGlobal( point ) );
173 } );
176 connect( cameraController(), &QgsCameraController::navigationModeChanged, this, &Qgs3DMapCanvas::onNavigationModeChanged );
177 connect( cameraController(), &QgsCameraController::requestDepthBufferCapture, this, &Qgs3DMapCanvas::captureDepthBuffer );
178
180
181 emit mapSettingsChanged();
182}
183
185{
186 return mScene ? mScene->cameraController() : nullptr;
187}
188
190{
191 if ( !mScene )
192 return;
193
194 mScene->viewZoomFull();
195}
196
197void Qgs3DMapCanvas::setViewFromTop( const QgsPointXY &center, float distance, float rotation )
198{
199 if ( !mScene )
200 return;
201
202 const float worldX = center.x() - mMapSettings->origin().x();
203 const float worldY = center.y() - mMapSettings->origin().y();
204 mScene->cameraController()->setViewFromTop( worldX, worldY, distance, rotation );
205}
206
207void Qgs3DMapCanvas::saveAsImage( const QString &fileName, const QString &fileFormat )
208{
209 if ( !mScene || fileName.isEmpty() )
210 return;
211
212 mCaptureFileName = fileName;
213 mCaptureFileFormat = fileFormat;
214 mEngine->setRenderCaptureEnabled( true );
215 // Setup a frame action that is used to wait until next frame
216 Qt3DLogic::QFrameAction *screenCaptureFrameAction = new Qt3DLogic::QFrameAction;
217 mScene->addComponent( screenCaptureFrameAction );
218 // Wait to have the render capture enabled in the next frame
219 connect( screenCaptureFrameAction, &Qt3DLogic::QFrameAction::triggered, this, [=]( float ) {
220 mEngine->requestCaptureImage();
221 mScene->removeComponent( screenCaptureFrameAction );
222 screenCaptureFrameAction->deleteLater();
223 } );
224}
225
226void Qgs3DMapCanvas::captureDepthBuffer()
227{
228 if ( !mScene )
229 return;
230
231 // Setup a frame action that is used to wait until next frame
232 Qt3DLogic::QFrameAction *screenCaptureFrameAction = new Qt3DLogic::QFrameAction;
233 mScene->addComponent( screenCaptureFrameAction );
234 // Wait to have the render capture enabled in the next frame
235 connect( screenCaptureFrameAction, &Qt3DLogic::QFrameAction::triggered, this, [=]( float ) {
236 mEngine->requestDepthBufferCapture();
237 mScene->removeComponent( screenCaptureFrameAction );
238 screenCaptureFrameAction->deleteLater();
239 } );
240}
241
243{
244 if ( !mScene )
245 return;
246
247 if ( tool == mMapTool )
248 return;
249
250 // For Camera Control tool
251 if ( mMapTool && !tool )
252 {
253 mScene->cameraController()->setEnabled( true );
254 setCursor( Qt::OpenHandCursor );
255 }
256
257 if ( mMapTool )
258 mMapTool->deactivate();
259
260 mMapTool = tool;
261
262 if ( mMapTool )
263 {
264 mMapTool->activate();
265 setCursor( mMapTool->cursor() );
266 }
267}
268
269bool Qgs3DMapCanvas::eventFilter( QObject *watched, QEvent *event )
270{
271 if ( watched != this )
272 return false;
273
274 if ( mScene && mScene->get3DAxis() && mScene->get3DAxis()->handleEvent( event ) )
275 {
276 event->accept();
277 return true;
278 }
279
280 if ( event->type() == QEvent::ShortcutOverride )
281 {
282 // if the camera controller will handle a key event, don't allow it to propagate
283 // outside of the 3d window or it may be grabbed by a parent window level shortcut
284 // and accordingly never be received by the camera controller
285 if ( cameraController() && cameraController()->willHandleKeyEvent( static_cast<QKeyEvent *>( event ) ) )
286 {
287 event->accept();
288 return true;
289 }
290 return false;
291 }
292
293 if ( !mMapTool )
294 return false;
295
296 switch ( event->type() )
297 {
298 case QEvent::MouseButtonPress:
299 mMapTool->mousePressEvent( static_cast<QMouseEvent *>( event ) );
300 break;
301 case QEvent::MouseButtonRelease:
302 mMapTool->mouseReleaseEvent( static_cast<QMouseEvent *>( event ) );
303 break;
304 case QEvent::MouseMove:
305 mMapTool->mouseMoveEvent( static_cast<QMouseEvent *>( event ) );
306 break;
307 case QEvent::KeyPress:
308 mMapTool->keyPressEvent( static_cast<QKeyEvent *>( event ) );
309 break;
310 case QEvent::KeyRelease:
311 mMapTool->keyReleaseEvent( static_cast<QKeyEvent *>( event ) );
312 break;
313 case QEvent::Wheel:
314 mMapTool->mouseWheelEvent( static_cast<QWheelEvent *>( event ) );
315 break;
316 default:
317 break;
318 }
319 return false;
320}
321
323{
324 if ( mTemporalController )
325 disconnect( mTemporalController, &QgsTemporalController::updateTemporalRange, this, &Qgs3DMapCanvas::updateTemporalRange );
326
327 mTemporalController = temporalController;
328 connect( mTemporalController, &QgsTemporalController::updateTemporalRange, this, &Qgs3DMapCanvas::updateTemporalRange );
329}
330
331void Qgs3DMapCanvas::updateTemporalRange( const QgsDateTimeRange &temporalrange )
332{
333 if ( !mScene )
334 return;
335
336 mMapSettings->setTemporalRange( temporalrange );
337 mScene->updateTemporal();
338}
339
340void Qgs3DMapCanvas::onNavigationModeChanged( Qgis::NavigationMode mode )
341{
342 mMapSettings->setCameraNavigationMode( mode );
343}
344
346{
347 if ( !mScene )
348 return;
349
350 mScene->setViewFrom2DExtent( extent );
351}
352
354{
355 return mScene ? mScene->viewFrustum2DExtent() : QVector<QgsPointXY>();
356}
357
359{
360 // we only support point clouds for now
361 if ( layer->type() != Qgis::LayerType::PointCloud )
362 return;
363
364 const QgsGeometry geom = feature.geometry();
365 const QgsPoint pt( geom.vertexAt( 0 ) );
366
367 if ( !mHighlights.contains( layer ) )
368 {
369 QgsRubberBand3D *band = new QgsRubberBand3D( *mMapSettings, mEngine, mEngine->frameGraph()->rubberBandsRootEntity(), Qgis::GeometryType::Point );
370
371 const QgsSettings settings;
372 const QColor color = QColor( settings.value( QStringLiteral( "Map/highlight/color" ), Qgis::DEFAULT_HIGHLIGHT_COLOR.name() ).toString() );
373 band->setColor( color );
374 band->setMarkerType( QgsRubberBand3D::MarkerType::Square );
375 if ( QgsPointCloudLayer3DRenderer *pcRenderer = dynamic_cast<QgsPointCloudLayer3DRenderer *>( layer->renderer3D() ) )
376 {
377 band->setWidth( pcRenderer->symbol()->pointSize() + 1 );
378 }
379 mHighlights.insert( layer, band );
380
381 connect( layer, &QgsMapLayer::renderer3DChanged, this, &Qgs3DMapCanvas::updateHighlightSizes );
382 }
383 mHighlights[layer]->addPoint( pt );
384}
385
386void Qgs3DMapCanvas::updateHighlightSizes()
387{
388 if ( QgsMapLayer *layer = qobject_cast<QgsMapLayer *>( sender() ) )
389 {
390 if ( QgsPointCloudLayer3DRenderer *rnd = dynamic_cast<QgsPointCloudLayer3DRenderer *>( layer->renderer3D() ) )
391 {
392 if ( mHighlights.contains( layer ) )
393 {
394 mHighlights[layer]->setWidth( rnd->symbol()->pointSize() + 1 );
395 }
396 }
397 }
398}
399
401{
402 for ( auto it = mHighlights.keyBegin(); it != mHighlights.keyEnd(); it++ )
403 {
404 disconnect( it.base().key(), &QgsMapLayer::renderer3DChanged, this, &Qgs3DMapCanvas::updateHighlightSizes );
405 }
406
407 qDeleteAll( mHighlights );
408 mHighlights.clear();
409}
static const QColor DEFAULT_HIGHLIGHT_COLOR
Default highlight color.
Definition qgis.h:5895
NavigationMode
The navigation mode used by 3D cameras.
Definition qgis.h:4006
@ PointCloud
Point cloud layer. Added in QGIS 3.18.
bool handleEvent(QEvent *event)
Returns if the 3D axis controller will handle the specified event.
void saveAsImage(const QString &fileName, const QString &fileFormat)
Saves the current scene as an image.
QVector< QgsPointXY > viewFrustum2DExtent()
Calculates the 2D extent viewed by the 3D camera as the vertices of the viewed trapezoid.
Qgs3DMapSettings * mapSettings()
Returns access to the 3D scene configuration.
void setTemporalController(QgsTemporalController *temporalController)
Sets the temporal controller.
void mapSettingsChanged()
Emitted when the the map setting is changed.
Qt3DRender::QCamera * camera() const
Returns the default camera of the 3D Window.
void viewed2DExtentFrom3DChanged(QVector< QgsPointXY > extent)
Emitted when the viewed 2D extent seen by the 3D camera has changed.
void fpsCountChanged(float fpsCount)
Emitted when the FPS count changes (at most every frame)
void setRootEntity(Qt3DCore::QEntity *root)
Sets the specified root entity of the scene.
void setViewFromTop(const QgsPointXY &center, float distance, float rotation=0)
Sets camera position to look down at the given point (in map coordinates) in given distance from plan...
void setActiveFrameGraph(Qt3DRender::QFrameGraphNode *activeFrameGraph)
Activates the specified activeFrameGraph.
void setMapSettings(Qgs3DMapSettings *mapSettings)
Configure map scene being displayed. Takes ownership.
void showEvent(QShowEvent *e) override
Manages the display events specified in e.
void cameraNavigationSpeedChanged(double speed)
Emitted when the camera navigation speed is changed.
Qt3DRender::QRenderSettings * renderSettings() const
Returns the render settings of the 3D Window.
Qt3DRender::QFrameGraphNode * activeFrameGraph() const
Returns the node of the active frame graph.
void setMapTool(Qgs3DMapTool *tool)
Sets the active map tool that will receive events from the 3D canvas.
void setViewFrom2DExtent(const QgsRectangle &extent)
Resets camera view to show the extent (top view)
void resizeEvent(QResizeEvent *) override
Resets the aspect ratio of the 3D window.
void highlightFeature(const QgsFeature &feature, QgsMapLayer *layer)
Highlights a feature from layer using a QgsRubberBand3D.
void resetView()
Resets camera position to the default: looking down at the origin of world coordinates.
void savedAsImage(const QString &fileName)
Emitted when the 3D map canvas was successfully saved as image.
void clearHighlights()
Clears all QgsRubberBand3D highlights.
void fpsCounterEnabledChanged(bool enabled)
Emitted when the FPS counter is enabled or disabeld.
QgsCameraController * cameraController()
Returns access to the view's camera controller. Returns nullptr if the scene has not been initialized...
bool eventFilter(QObject *watched, QEvent *event) override
Entity that encapsulates our 3D scene - contains all other entities (such as terrain) as children.
Qgs3DAxis * get3DAxis() const
Returns the 3D axis object.
void viewed2DExtentFrom3DChanged(QVector< QgsPointXY > extent)
Emitted when the viewed 2D extent seen by the 3D camera has changed.
void fpsCountChanged(float fpsCount)
Emitted when the FPS count changes.
void setViewFrom2DExtent(const QgsRectangle &extent)
Resets camera view to show the extent extent (top view)
QgsCameraController * cameraController() const
Returns camera controller.
void updateTemporal()
Updates the temporale entities.
void fpsCounterEnabledChanged(bool fpsCounterEnabled)
Emitted when the FPS counter is activated or deactivated.
QVector< QgsPointXY > viewFrustum2DExtent() const
Calculates the 2D extent viewed by the 3D camera as the vertices of the viewed trapezoid.
void viewZoomFull()
Resets camera view to show the whole scene (top view)
Definition of the world.
void setCameraNavigationMode(Qgis::NavigationMode navigationMode)
Sets the navigation mode for the camera.
void setCameraMovementSpeed(double movementSpeed)
Sets the camera movement speed.
QgsVector3D origin() const
Returns coordinates in map CRS at which 3D scene has origin (0,0,0).
Base class for map tools operating on 3D map canvas.
virtual void mousePressEvent(QMouseEvent *event)
Reimplement to handle mouse event forwarded by the parent Qgs3DMapCanvas.
virtual QCursor cursor() const
Mouse cursor to be used when the tool is active.
virtual void mouseMoveEvent(QMouseEvent *event)
Reimplement to handle mouse move event forwarded by the parent Qgs3DMapCanvas.
virtual void keyPressEvent(QKeyEvent *event)
Reimplement to handle key press event forwarded by the parent Qgs3DMapCanvas.
virtual void deactivate()
Called when map tool is being deactivated.
virtual void activate()
Called when set as currently active map tool.
virtual void keyReleaseEvent(QKeyEvent *event)
Reimplement to handle key release event forwarded by the parent Qgs3DMapCanvas.
virtual void mouseReleaseEvent(QMouseEvent *event)
Reimplement to handle mouse release event forwarded by the parent Qgs3DMapCanvas.
virtual void mouseWheelEvent(QWheelEvent *event)
Reimplement to handle mouse wheel event forwarded by the parent Qgs3DMapCanvas.
void requestCaptureImage()
Starts a request for an image rendered by the engine.
void requestDepthBufferCapture()
Starts a request for an image containing the depth buffer data of the engine.
void imageCaptured(const QImage &image)
Emitted after a call to requestCaptureImage() to return the captured image.
void depthBufferCaptured(const QImage &image)
Emitted after a call to requestDepthBufferCapture() to return the captured depth buffer.
void setRenderCaptureEnabled(bool enabled)
Sets whether it will be possible to render to an image.
QgsFrameGraph * frameGraph()
Returns the shadow rendering frame graph object used to render the scene.
Object that controls camera movement based on user input.
void navigationModeChanged(Qgis::NavigationMode mode)
Emitted when the navigation mode is changed using the hotkey ctrl + ~.
void requestDepthBufferCapture()
Emitted to ask for the depth buffer image.
void cameraMovementSpeedChanged(double speed)
Emitted whenever the camera movement speed is changed by the controller.
void setViewFromTop(float worldX, float worldY, float distance, float yaw=0)
Sets camera to look down towards given point in world coordinate, in given distance from plane with z...
void depthBufferCaptured(const QImage &depthImage)
Sets the depth buffer image used by the camera controller to calculate world position from a pixel's ...
void setCursorPosition(QPoint point)
Emitted when the mouse cursor position should be moved to the specified point on the map viewport.
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition qgsfeature.h:58
QgsGeometry geometry
Definition qgsfeature.h:69
Qt3DCore::QEntity * rubberBandsRootEntity()
Returns entity for all rubber bands (to show them always on top)
A geometry is the spatial representation of a feature.
QgsPoint vertexAt(int atVertex) const
Returns coordinates of a vertex.
Base class for all map layer types.
Definition qgsmaplayer.h:77
QgsAbstract3DRenderer * renderer3D() const
Returns 3D renderer associated with the layer.
void renderer3DChanged()
Signal emitted when 3D renderer associated with the layer has changed.
Qgis::LayerType type
Definition qgsmaplayer.h:87
3D renderer that renders all points from a point cloud layer.
Represents a 2D point.
Definition qgspointxy.h:60
double y
Definition qgspointxy.h:64
double x
Definition qgspointxy.h:63
Point geometry type, with support for z-dimension and m-values.
Definition qgspoint.h:49
A rectangle specified with double values.
Stores settings for use within QGIS.
Definition qgssettings.h:65
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
A controller base class for temporal objects, contains a signal for notifying updates of the objects ...
void updateTemporalRange(const QgsDateTimeRange &range)
Signals that a temporal range has changed and needs to be updated in all connected objects.
void setTemporalRange(const QgsDateTimeRange &range)
Sets the temporal range for the object.
double y() const
Returns Y coordinate.
Definition qgsvector3d.h:49
double x() const
Returns X coordinate.
Definition qgsvector3d.h:47
On-screen 3D engine: it creates an OpenGL window (QWindow) and displays rendered 3D scenes there.
void setRootEntity(Qt3DCore::QEntity *root) override
Sets root entity of the 3D scene.
void setSize(QSize s) override
Sets the size of the rendering area (in pixels)