QGIS API Documentation 3.99.0-Master (18a1e75d814)
Loading...
Searching...
No Matches
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 "qgs3dmapcanvas.h"
17
18#include "qgs3daxis.h"
19#include "qgs3dmapscene.h"
20#include "qgs3dmapsettings.h"
21#include "qgs3dmaptool.h"
22#include "qgs3dutils.h"
23#include "qgscameracontroller.h"
24#include "qgsframegraph.h"
25#include "qgsraycastcontext.h"
27#include "qgswindow3dengine.h"
28
29#include <QString>
30#include <QTimer>
31#include <Qt3DCore/QAspectEngine>
32#include <Qt3DCore/QCoreAspect>
33#include <Qt3DInput/QInputAspect>
34#include <Qt3DInput/QInputSettings>
35#include <Qt3DLogic/QFrameAction>
36#include <Qt3DLogic/QLogicAspect>
37#include <Qt3DRender/QRenderAspect>
38#include <Qt3DRender/QRenderSettings>
39
40#include "moc_qgs3dmapcanvas.cpp"
41
42using namespace Qt::StringLiterals;
43
45 : m_aspectEngine( new Qt3DCore::QAspectEngine )
46 , m_renderAspect( new Qt3DRender::QRenderAspect )
47 , m_inputAspect( new Qt3DInput::QInputAspect )
48 , m_logicAspect( new Qt3DLogic::QLogicAspect )
49 , m_renderSettings( new Qt3DRender::QRenderSettings )
50 , m_defaultCamera( new Qt3DRender::QCamera )
51 , m_inputSettings( new Qt3DInput::QInputSettings )
52 , m_root( new Qt3DCore::QEntity )
53{
54 setSurfaceType( QSurface::OpenGLSurface );
55
56 // register aspects
57 m_aspectEngine->registerAspect( new Qt3DCore::QCoreAspect );
58 m_aspectEngine->registerAspect( m_renderAspect );
59 m_aspectEngine->registerAspect( m_inputAspect );
60 m_aspectEngine->registerAspect( m_logicAspect );
61
62 m_defaultCamera->setParent( m_root );
63 m_inputSettings->setEventSource( this );
64
65 const QgsSettings setting;
66 mEngine = new QgsWindow3DEngine( this );
67
68 connect( mEngine, &QgsAbstract3DEngine::imageCaptured, this, [this]( const QImage &image ) {
69 if ( image.save( mCaptureFileName, mCaptureFileFormat.toLocal8Bit().data() ) )
70 {
71 emit savedAsImage( mCaptureFileName );
72 }
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
89 delete m_aspectEngine;
90}
91
92void Qgs3DMapCanvas::setRootEntity( Qt3DCore::QEntity *root )
93{
94 if ( m_userRoot != root )
95 {
96 if ( m_userRoot )
97 m_userRoot->setParent( static_cast<Qt3DCore::QNode *>( nullptr ) );
98 if ( root )
99 root->setParent( m_root );
100 m_userRoot = root;
101 }
102}
103
104void Qgs3DMapCanvas::setActiveFrameGraph( Qt3DRender::QFrameGraphNode *activeFrameGraph )
105{
106 m_renderSettings->setActiveFrameGraph( activeFrameGraph );
107}
108
109Qt3DRender::QFrameGraphNode *Qgs3DMapCanvas::activeFrameGraph() const
110{
111 return m_renderSettings->activeFrameGraph();
112}
113
114Qt3DRender::QCamera *Qgs3DMapCanvas::camera() const
115{
116 return m_defaultCamera;
117}
118
119Qt3DRender::QRenderSettings *Qgs3DMapCanvas::renderSettings() const
120{
121 return m_renderSettings;
122}
123
124void Qgs3DMapCanvas::showEvent( QShowEvent *e )
125{
126 if ( !m_initialized )
127 {
128 m_root->addComponent( m_renderSettings );
129 m_root->addComponent( m_inputSettings );
130 m_aspectEngine->setRootEntity( Qt3DCore::QEntityPtr( m_root ) );
131
132 m_initialized = true;
133 }
134 QWindow::showEvent( e );
135}
136
137void Qgs3DMapCanvas::resizeEvent( QResizeEvent * )
138{
139 m_defaultCamera->setAspectRatio( float( width() ) / std::max( 1.f, static_cast<float>( height() ) ) );
140
141 mEngine->setSize( size() );
142}
143
145{
146 // TODO: eventually we want to get rid of this
147 Q_ASSERT( !mMapSettings );
148 Q_ASSERT( !mScene );
149
150 Qgs3DMapScene *newScene = new Qgs3DMapScene( *mapSettings, mEngine );
151
152 mEngine->setSize( size() );
153 mEngine->setRootEntity( newScene );
154
155 if ( mScene )
156 {
157 mScene->deleteLater();
158 }
159 mScene = newScene;
163
164 mHighlightsHandler.reset( new Qgs3DHighlightFeatureHandler( mScene ) );
165
166 delete mMapSettings;
167 mMapSettings = mapSettings;
168
169 resetView();
170
171 connect( cameraController(), &QgsCameraController::setCursorPosition, this, [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
189QgsRayCastResult Qgs3DMapCanvas::castRay( const QPoint &screenPoint, QgsRayCastContext context )
190{
191 const QgsRay3D ray = Qgs3DUtils::rayFromScreenPoint( screenPoint, size(), camera() );
192 if ( context.maximumDistance() < 0 )
193 context.setMaximumDistance( camera()->farPlane() );
194 const QgsRayCastResult res = Qgs3DUtils::castRay( mScene, ray, context );
195 return res;
196}
197
199{
200 mCrossSection = crossSection;
201
202 if ( !mScene )
203 return;
204
205 if ( !mCrossSection.isValid() )
206 {
207 mScene->disableClipping();
208 emit crossSectionEnabledChanged( false );
209 return;
210 }
211
212 const QgsPoint startPoint = mCrossSection.startPoint();
213 const QgsPoint endPoint = mCrossSection.endPoint();
214 const double width = mCrossSection.halfWidth();
215
216 const QgsVector3D startVec { startPoint.x(), startPoint.y(), 0 };
217 const QgsVector3D endVec { endPoint.x(), endPoint.y(), 0 };
218 const QList<QVector4D> clippingPlanes = Qgs3DUtils::lineSegmentToClippingPlanes(
219 startVec,
220 endVec,
221 width,
222 mMapSettings->origin()
223 );
224
225 mScene->enableClipping( clippingPlanes );
226 emit crossSectionEnabledChanged( true );
227}
228
229
231{
232 return mScene ? !mScene->clipPlaneEquations().isEmpty() : false;
233}
234
236{
237 if ( !mScene )
238 return;
239
240 mScene->viewZoomFull();
241}
242
243void Qgs3DMapCanvas::setViewFromTop( const QgsPointXY &center, float distance, float rotation )
244{
245 if ( !mScene )
246 return;
247
248 const float worldX = center.x() - mMapSettings->origin().x();
249 const float worldY = center.y() - mMapSettings->origin().y();
250 mScene->cameraController()->setViewFromTop( worldX, worldY, distance, rotation );
251}
252
253void Qgs3DMapCanvas::saveAsImage( const QString &fileName, const QString &fileFormat )
254{
255 if ( !mScene || fileName.isEmpty() )
256 return;
257
258 mCaptureFileName = fileName;
259 mCaptureFileFormat = fileFormat;
260 // Setup a frame action that is used to wait until next frame
261 Qt3DLogic::QFrameAction *screenCaptureFrameAction = new Qt3DLogic::QFrameAction;
262 mScene->addComponent( screenCaptureFrameAction );
263 // Wait to have the render capture enabled in the next frame
264 connect( screenCaptureFrameAction, &Qt3DLogic::QFrameAction::triggered, this, [this, screenCaptureFrameAction]( float ) {
265 mEngine->requestCaptureImage();
266 mScene->removeComponent( screenCaptureFrameAction );
267 screenCaptureFrameAction->deleteLater();
268 } );
269}
270
271void Qgs3DMapCanvas::captureDepthBuffer()
272{
273 if ( !mScene )
274 return;
275
276 // Setup a frame action that is used to wait until next frame
277 Qt3DLogic::QFrameAction *screenCaptureFrameAction = new Qt3DLogic::QFrameAction;
278 mScene->addComponent( screenCaptureFrameAction );
279 // Wait to have the render capture enabled in the next frame
280 connect( screenCaptureFrameAction, &Qt3DLogic::QFrameAction::triggered, this, [this, screenCaptureFrameAction]( float ) {
281 mEngine->requestDepthBufferCapture();
282 mScene->removeComponent( screenCaptureFrameAction );
283 screenCaptureFrameAction->deleteLater();
284 } );
285}
286
288{
289 if ( !mScene )
290 return;
291
292 if ( tool == mMapTool )
293 return;
294
295 // For Camera Control tool
296 if ( mMapTool && !tool )
297 {
298 mScene->cameraController()->setEnabled( true );
299 setCursor( Qt::OpenHandCursor );
300 }
301
302 if ( mMapTool )
303 mMapTool->deactivate();
304
305 mMapTool = tool;
306
307 if ( mMapTool )
308 {
309 mMapTool->activate();
310 setCursor( mMapTool->cursor() );
311 }
312}
313
314bool Qgs3DMapCanvas::eventFilter( QObject *watched, QEvent *event )
315{
316 if ( watched != this )
317 return false;
318
319 if ( mScene && mScene->get3DAxis() && mScene->get3DAxis()->handleEvent( event ) )
320 {
321 event->accept();
322 return true;
323 }
324
325 // ShortcutOverride is sent if the pressed key is "claimed" by a shortcut,
326 // but we are given a chance to handle it anyway. We need to basically treat
327 // it as if it were a KeyPress.
328 if ( event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease || event->type() == QEvent::ShortcutOverride )
329 {
330 // if the camera controller will handle a key event, don't allow it to propagate
331 // outside of the 3d window or it may be grabbed by a parent window level shortcut
332 // and accordingly never be received by the camera controller
333 if ( cameraController() && cameraController()->keyboardEventFilter( static_cast<QKeyEvent *>( event ) ) )
334 {
335 event->accept();
336 return true;
337 }
338 return false;
339 }
340
341 if ( !mMapTool )
342 return false;
343
344 switch ( event->type() )
345 {
346 case QEvent::MouseButtonPress:
347 mMapTool->mousePressEvent( static_cast<QMouseEvent *>( event ) );
348 break;
349 case QEvent::MouseButtonRelease:
350 mMapTool->mouseReleaseEvent( static_cast<QMouseEvent *>( event ) );
351 break;
352 case QEvent::MouseMove:
353 mMapTool->mouseMoveEvent( static_cast<QMouseEvent *>( event ) );
354 break;
355 case QEvent::KeyPress:
356 mMapTool->keyPressEvent( static_cast<QKeyEvent *>( event ) );
357 break;
358 case QEvent::KeyRelease:
359 mMapTool->keyReleaseEvent( static_cast<QKeyEvent *>( event ) );
360 break;
361 case QEvent::Wheel:
362 mMapTool->mouseWheelEvent( static_cast<QWheelEvent *>( event ) );
363 break;
364 default:
365 break;
366 }
367 return false;
368}
369
371{
372 if ( mTemporalController )
373 disconnect( mTemporalController, &QgsTemporalController::updateTemporalRange, this, &Qgs3DMapCanvas::updateTemporalRange );
374
375 mTemporalController = temporalController;
376 connect( mTemporalController, &QgsTemporalController::updateTemporalRange, this, &Qgs3DMapCanvas::updateTemporalRange );
377}
378
379void Qgs3DMapCanvas::updateTemporalRange( const QgsDateTimeRange &temporalrange )
380{
381 if ( !mScene )
382 return;
383
384 mMapSettings->setTemporalRange( temporalrange );
385 mScene->updateTemporal();
386}
387
388void Qgs3DMapCanvas::onNavigationModeChanged( Qgis::NavigationMode mode )
389{
390 mMapSettings->setCameraNavigationMode( mode );
391}
392
394{
395 if ( !mScene )
396 return;
397
398 mScene->setViewFrom2DExtent( extent );
399}
400
402{
403 return mScene ? mScene->viewFrustum2DExtent() : QVector<QgsPointXY>();
404}
405
407{
408 mHighlightsHandler->highlightFeature( feature, layer );
409}
410
412{
413 mHighlightsHandler->clearHighlights();
414}
NavigationMode
The navigation mode used by 3D cameras.
Definition qgis.h:4252
Handles the creation of 3D entities used for highlighting identified features.
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.
bool crossSectionEnabled() const
Returns true if the cross section mode is enabled or the 3d scene has other clipping planes applied.
void mapSettingsChanged()
Emitted when the the map setting is changed.
void crossSectionEnabledChanged(bool enabled)
Emitted when the cross section mode is enabled or disabled.
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.
QgsCrossSection crossSection() const
Returns the current cross section definition for the 3D map canvas.
void cameraNavigationSpeedChanged(double speed)
Emitted when the camera navigation speed is changed.
Qt3DRender::QRenderSettings * renderSettings() const
Returns the render settings of the 3D Window.
~Qgs3DMapCanvas() override
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).
QgsRayCastResult castRay(const QPoint &screenPoint, QgsRayCastContext context)
Casts a ray towards the 3d scene and returns information about the intersected 3d entities.
void resizeEvent(QResizeEvent *) override
Resets the aspect ratio of the 3D window.
void highlightFeature(const QgsFeature &feature, QgsMapLayer *layer)
Add a highlight 3d entity for feature of layer.
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()
Removes all highlight entities.
void setCrossSection(const QgsCrossSection &crossSection)
Sets the cross section definition for the 3D map canvas.
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.
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 updateTemporal()
Updates the temporale entities.
void fpsCounterEnabledChanged(bool fpsCounterEnabled)
Emitted when the FPS counter is activated or deactivated.
Definition of the world.
void setCameraMovementSpeed(double movementSpeed)
Sets the camera movement speed.
Base class for map tools operating on 3D map canvas.
static QList< QVector4D > lineSegmentToClippingPlanes(const QgsVector3D &startPoint, const QgsVector3D &endPoint, double distance, const QgsVector3D &origin)
Returns a list of 4 planes derived from a line extending from startPoint to endPoint.
static QgsRayCastResult castRay(Qgs3DMapScene *scene, const QgsRay3D &ray, const QgsRayCastContext &context)
Casts a ray through the scene and returns information about the intersecting entities (ray uses World...
static QgsRay3D rayFromScreenPoint(const QPoint &point, const QSize &windowSize, Qt3DRender::QCamera *camera)
Convert from clicked point on the screen to a ray in world coordinates.
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.
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 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.
Encapsulates the definition of a cross section in 3D map coordinates.
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition qgsfeature.h:60
Base class for all map layer types.
Definition qgsmaplayer.h:83
Represents a 2D point.
Definition qgspointxy.h:62
double y
Definition qgspointxy.h:66
double x
Definition qgspointxy.h:65
Point geometry type, with support for z-dimension and m-values.
Definition qgspoint.h:53
double x
Definition qgspoint.h:56
double y
Definition qgspoint.h:57
A representation of a ray in 3D.
Definition qgsray3d.h:31
Responsible for defining parameters of the ray casting operations in 3D map canvases.
float maximumDistance() const
The maximum distance from ray origin to look for hits when casting a ray.
void setMaximumDistance(float distance)
Sets the maximum distance from ray origin to look for hits when casting a ray.
Contains the results of ray casting operations in a 3D map canvas.
A rectangle specified with double values.
Stores settings for use within QGIS.
Definition qgssettings.h:68
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.
A 3D vector (similar to QVector3D) with the difference that it uses double precision instead of singl...
Definition qgsvector3d.h:33
On-screen 3D engine: it creates an OpenGL window (QWindow) and displays rendered 3D scenes there.
QgsTemporalRange< QDateTime > QgsDateTimeRange
QgsRange which stores a range of date times.
Definition qgsrange.h:764