QGIS API Documentation 4.3.0-Master (bf28115e945)
Loading...
Searching...
No Matches
qgs3dmapscene.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgs3dmapscene.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 "qgs3dmapscene.h"
17
18#include <limits>
19
20#include "qgs3daxis.h"
22#include "qgs3dmapsettings.h"
23#include "qgs3dsceneexporter.h"
24#include "qgs3dutils.h"
25#include "qgsaabb.h"
26#include "qgsabstract3dengine.h"
30#include "qgsannotationlayer.h"
32#include "qgsapplication.h"
33#include "qgscameracontroller.h"
35#include "qgschunkedentity.h"
36#include "qgschunknode.h"
37#include "qgsenvironmentlight.h"
38#include "qgseventtracing.h"
41#include "qgsframegraph.h"
42#include "qgsgeotransform.h"
46#include "qgslightsource.h"
47#include "qgslinematerial_p.h"
48#include "qgslogger.h"
51#include "qgsmaterial.h"
52#include "qgsmeshlayer.h"
54#include "qgsmessageoutput.h"
58#include "qgspoint3dsymbol.h"
59#include "qgspointcloudlayer.h"
63#include "qgsshadowrenderview.h"
64#include "qgsskyboxentity.h"
65#include "qgsskyboxsettings.h"
66#include "qgssourcecache.h"
67#include "qgsterrainentity.h"
68#include "qgsterraingenerator.h"
69#include "qgstiledscenelayer.h"
71#include "qgsunlitmaterial.h"
72#include "qgsvectorlayer.h"
74#include "qgswindow3dengine.h"
75
76#include <QOpenGLContext>
77#include <QOpenGLFunctions>
78#include <QString>
79#include <QSurface>
80#include <QTimer>
81#include <QUrl>
82#include <Qt3DExtras/QDiffuseSpecularMaterial>
83#include <Qt3DExtras/QForwardRenderer>
84#include <Qt3DExtras/QSphereMesh>
85#include <Qt3DLogic/QFrameAction>
86#include <Qt3DRender/QCamera>
87#include <Qt3DRender/QCullFace>
88#include <Qt3DRender/QDepthTest>
89#include <Qt3DRender/QEffect>
90#include <Qt3DRender/QMaterial>
91#include <Qt3DRender/QMesh>
92#include <Qt3DRender/QRenderPass>
93#include <Qt3DRender/QRenderSettings>
94#include <Qt3DRender/QRenderState>
95#include <Qt3DRender/QSceneLoader>
96#include <Qt3DRender/QTechnique>
97#include <QtMath>
98
99#ifdef HAVE_TRACY
100#include "tracy/Tracy.hpp"
101#endif
102
103#include "moc_qgs3dmapscene.cpp"
104
105using namespace Qt::StringLiterals;
106
107std::function<QMap<QString, Qgs3DMapScene *>()> Qgs3DMapScene::sOpenScenesFunction = [] { return QMap<QString, Qgs3DMapScene *>(); };
108
110 : mMap( map )
111 , mEngine( engine )
112{
113 connect( &map, &Qgs3DMapSettings::backgroundColorChanged, this, &Qgs3DMapScene::onBackgroundColorChanged );
114 onBackgroundColorChanged();
115
116 // The default render policy in Qt3D is "Always" - i.e. the 3D map scene gets refreshed up to 60 fps
117 // even if there's no change. Switching to "on demand" should only re-render when something has changed
118 // and we save quite a lot of resources
119 mEngine->renderSettings()->setRenderPolicy( Qt3DRender::QRenderSettings::OnDemand );
120
121 QRect viewportRect( QPoint( 0, 0 ), mEngine->size() );
122
123 // Get the maximum of clip planes available
124 mMaxClipPlanes = Qgs3DUtils::openGlMaxClipPlanes( mEngine->surface() );
125
126 // Camera
127 float aspectRatio = ( float ) viewportRect.width() / viewportRect.height();
128 mEngine->camera()->lens()->setPerspectiveProjection( static_cast< float >( mMap.fieldOfView() ), aspectRatio, 10.f, 10000.0f );
129
130 mFrameAction = new Qt3DLogic::QFrameAction();
131 connect( mFrameAction, &Qt3DLogic::QFrameAction::triggered, this, &Qgs3DMapScene::onFrameTriggered );
132 addComponent( mFrameAction ); // takes ownership
133
134 // Camera controlling
135 mCameraController = new QgsCameraController( this ); // attaches to the scene
136
137 if ( mMap.sceneMode() == Qgis::SceneMode::Globe )
138 mCameraController->resetGlobe( 10'000'000 );
139 else
140 mCameraController->resetView( 1000 );
141
142 addCameraViewCenterEntity( mEngine->camera() );
143 addCameraRotationCenterEntity( mCameraController );
144 updateLights();
145
146 // create terrain entity
147
148 createTerrainDeferred();
149 connect( &map, &Qgs3DMapSettings::extentChanged, this, &Qgs3DMapScene::createTerrain );
150 connect( &map, &Qgs3DMapSettings::terrainGeneratorChanged, this, &Qgs3DMapScene::createTerrain );
151
152 connect( &map, &Qgs3DMapSettings::terrainSettingsChanged, this, &Qgs3DMapScene::createTerrain );
153
154 connect( &map, &Qgs3DMapSettings::terrainShadingChanged, this, &Qgs3DMapScene::createTerrain );
155 connect( &map, &Qgs3DMapSettings::lightSourcesChanged, this, &Qgs3DMapScene::updateLights );
156 connect( &map, &Qgs3DMapSettings::showLightSourceOriginsChanged, this, &Qgs3DMapScene::updateLights );
157 connect( &map, &Qgs3DMapSettings::fieldOfViewChanged, this, &Qgs3DMapScene::updateCameraLens );
158 connect( &map, &Qgs3DMapSettings::projectionTypeChanged, this, &Qgs3DMapScene::updateCameraLens );
159 connect( &map, &Qgs3DMapSettings::shadowSettingsChanged, this, &Qgs3DMapScene::onShadowSettingsChanged );
160 connect( &map, &Qgs3DMapSettings::ambientOcclusionSettingsChanged, this, &Qgs3DMapScene::onAmbientOcclusionSettingsChanged );
161 connect( &map, &Qgs3DMapSettings::bloomSettingsChanged, this, &Qgs3DMapScene::onBloomSettingsChanged );
162 connect( &map, &Qgs3DMapSettings::colorGradingSettingsChanged, this, &Qgs3DMapScene::onColorGradingSettingsChanged );
163 connect( &map, &Qgs3DMapSettings::eyeDomeLightingEnabledChanged, this, &Qgs3DMapScene::onEyeDomeShadingSettingsChanged );
164 connect( &map, &Qgs3DMapSettings::eyeDomeLightingStrengthChanged, this, &Qgs3DMapScene::onEyeDomeShadingSettingsChanged );
165 connect( &map, &Qgs3DMapSettings::eyeDomeLightingDistanceChanged, this, &Qgs3DMapScene::onEyeDomeShadingSettingsChanged );
166 connect( &map, &Qgs3DMapSettings::msaaEnabledChanged, this, &Qgs3DMapScene::onMsaaEnabledChanged );
167 connect( &map, &Qgs3DMapSettings::debugDepthMapSettingsChanged, this, &Qgs3DMapScene::onDebugDepthMapSettingsChanged );
169 connect( &map, &Qgs3DMapSettings::cameraMovementSpeedChanged, this, &Qgs3DMapScene::onCameraMovementSpeedChanged );
170 connect( &map, &Qgs3DMapSettings::cameraNavigationModeChanged, this, &Qgs3DMapScene::onCameraNavigationModeChanged );
171 connect( &map, &Qgs3DMapSettings::debugOverlayEnabledChanged, this, &Qgs3DMapScene::onDebugOverlayEnabledChanged );
172 connect( &map, &Qgs3DMapSettings::stopUpdatesChanged, this, &Qgs3DMapScene::onStopUpdatesChanged );
173 connect( &map, &Qgs3DMapSettings::show2DMapOverlayChanged, this, &Qgs3DMapScene::onShowMapOverlayChanged );
174 connect( &map, &Qgs3DMapSettings::viewFrustumVisualizationEnabledChanged, this, &Qgs3DMapScene::onShowMapOverlayChanged );
175
176 connect( &map, &Qgs3DMapSettings::axisSettingsChanged, this, &Qgs3DMapScene::on3DAxisSettingsChanged );
177
178 connect( &map, &Qgs3DMapSettings::originChanged, this, &Qgs3DMapScene::onOriginChanged );
179
180 connect( QgsApplication::sourceCache(), &QgsSourceCache::remoteSourceFetched, this, [this]( const QString &url ) {
181 const QList<QgsMapLayer *> modelVectorLayers = mModelVectorLayers;
182 for ( QgsMapLayer *layer : modelVectorLayers )
183 {
184 QgsAbstract3DRenderer *renderer = layer->renderer3D();
185 if ( renderer )
186 {
187 if ( renderer->type() == "vector"_L1 )
188 {
189 const QgsPoint3DSymbol *pointSymbol = static_cast<const QgsPoint3DSymbol *>( static_cast<QgsVectorLayer3DRenderer *>( renderer )->symbol() );
190 if ( pointSymbol && pointSymbol->shapeProperty( u"model"_s ).toString() == url )
191 {
192 removeLayerEntity( layer );
193 addLayerEntity( layer );
194 }
195 }
196 else if ( renderer->type() == "rulebased"_L1 )
197 {
198 const QgsRuleBased3DRenderer::RuleList rules = static_cast<QgsRuleBased3DRenderer *>( renderer )->rootRule()->descendants();
199 for ( const QgsRuleBased3DRenderer::Rule *rule : rules )
200 {
201 const QgsPoint3DSymbol *pointSymbol = dynamic_cast<const QgsPoint3DSymbol *>( rule->symbol() );
202 if ( pointSymbol && pointSymbol->shapeProperty( u"model"_s ).toString() == url )
203 {
204 removeLayerEntity( layer );
205 addLayerEntity( layer );
206 break;
207 }
208 }
209 }
210 }
211 }
212 } );
213
214 // listen to changes of layers in order to add/remove 3D renderer entities
215 connect( &map, &Qgs3DMapSettings::layersChanged, this, &Qgs3DMapScene::onLayersChanged );
216
217 connect( mCameraController, &QgsCameraController::cameraChanged, this, &Qgs3DMapScene::onCameraChanged );
218 connect( mEngine, &QgsAbstract3DEngine::sizeChanged, this, &Qgs3DMapScene::onCameraChanged );
219 connect( mCameraController, &QgsCameraController::depthBufferReady, this, &Qgs3DMapScene::onViewed2DExtentFrom3DChanged );
220
221 mEnvironmentLight = new QgsEnvironmentLight( mEngine->frameGraph(), this );
222
223 connect( &map, &Qgs3DMapSettings::backgroundSettingsChanged, this, &Qgs3DMapScene::onBackgroundSettingsChanged );
224 onBackgroundSettingsChanged();
225
226 // force initial update of chunked entities
227 onCameraChanged();
228 // force initial update of eye dome shading
229 onEyeDomeShadingSettingsChanged();
230 // force initial update of debugging setting of preview quads
231 onDebugDepthMapSettingsChanged();
232 // force initial update of ambient occlusion settings
233 onAmbientOcclusionSettingsChanged();
234 // force initial update of MSAA setting
235 onMsaaEnabledChanged();
236 // initial state of bloom setting
237 onBloomSettingsChanged();
238 // initial state of color grading
239 onColorGradingSettingsChanged();
240
241 // timer used to refresh the map overlay every 250 ms while the camera is moving.
242 // schedule2DMapOverlayUpdate() is called to schedule the update.
243 // At the end of the delay, applyPendingOverlayUpdate() performs the update.
244 mOverlayUpdateTimer = new QTimer( this );
245 mOverlayUpdateTimer->setSingleShot( true );
246 mOverlayUpdateTimer->setInterval( 250 );
247 connect( mOverlayUpdateTimer, &QTimer::timeout, this, &Qgs3DMapScene::applyPendingOverlayUpdate );
248
249 // force initial update of map overlay entity
250 onShowMapOverlayChanged();
251
252 onCameraMovementSpeedChanged();
253
254 on3DAxisSettingsChanged();
255
256 mDepthBufferRefreshTimer = new QTimer( this );
257 mDepthBufferRefreshTimer->setSingleShot( true );
258 mDepthBufferRefreshTimer->setInterval( 100 );
259 connect( mDepthBufferRefreshTimer, &QTimer::timeout, mCameraController, &QgsCameraController::requestDepthBufferCapture );
260}
261
263{
264 if ( mMap.sceneMode() == Qgis::SceneMode::Globe )
265 {
266 mCameraController->resetGlobe( 10'000'000 );
267 return;
268 }
269
270 const QgsDoubleRange zRange = elevationRange();
271 const QgsRectangle extent = sceneExtent();
272 const double side = std::max( extent.width(), extent.height() );
273 double d = side / 2 / std::tan( cameraController()->camera()->fieldOfView() / 2 * M_PI / 180 );
274 d += zRange.isInfinite() ? 0. : zRange.upper();
275 mCameraController->resetView( static_cast<float>( d ) );
276}
277
279{
280 QgsPointXY center = extent.center();
281 const QgsVector3D origin = mMap.origin();
282
283 const QgsVector3D p1 = mMap.mapToWorldCoordinates( QgsVector3D( extent.xMinimum(), extent.yMinimum(), 0 ) );
284 const QgsVector3D p2 = mMap.mapToWorldCoordinates( QgsVector3D( extent.xMaximum(), extent.yMaximum(), 0 ) );
285
286 const double xSide = std::abs( p1.x() - p2.x() );
287 const double ySide = std::abs( p1.y() - p2.y() );
288 const double side = std::max( xSide, ySide );
289
290 const double fov = qDegreesToRadians( cameraController()->camera()->fieldOfView() );
291 double distance = side / 2.0f / std::tan( fov / 2.0f );
292
293 // adjust by elevation
294 const QgsDoubleRange zRange = elevationRange();
295 if ( !zRange.isInfinite() )
296 distance += zRange.upper();
297
298 // subtract map origin so coordinates are relative to it
299 // clang-format off
300 mCameraController->setViewFromTop(
301 static_cast<float>( center.x() - origin.x() ),
302 static_cast<float>( center.y() - origin.y() ),
303 static_cast<float>( distance )
304 );
305 // clang-format on
306}
307
308QVector<QgsPointXY> Qgs3DMapScene::viewFrustum2DExtent() const
309{
310 Qt3DRender::QCamera *camera = mCameraController->camera();
311 QVector<QgsPointXY> extent;
312
313 const QSize size = mEngine->size();
314
315 const QPoint center( size.width() / 2, size.height() / 2 );
316
317 const QVector3D centerWorldPos = Qgs3DUtils::screenPointToWorldPos( center, static_cast<float>( mLastCenterDepth ), size, camera );
318 const float centerZ = centerWorldPos.z();
319 const QVector3D cameraPosition = camera->position();
320
321 QVector<int> pointsOrder = { 0, 1, 3, 2 };
322 for ( int i : pointsOrder )
323 {
324 const QPoint p( ( ( i >> 0 ) & 1 ) ? 0 : size.width(), ( ( i >> 1 ) & 1 ) ? 0 : size.height() );
325
326 const QVector3D edgePoint = Qgs3DUtils::screenPointToWorldPos( p, static_cast<float>( mLastCenterDepth ), size, camera );
327 const QVector3D rayDir = ( edgePoint - cameraPosition ).normalized();
328
329 QVector3D worldPos;
330 if ( std::abs( rayDir.z() ) > 0 )
331 {
332 float t = ( centerZ - cameraPosition.z() ) / rayDir.z();
333 if ( t < 0 )
334 t = camera->farPlane();
335 else
336 t = std::min<float>( t, camera->farPlane() );
337 worldPos = cameraPosition + t * rayDir;
338 }
339 else
340 {
341 worldPos = cameraPosition + camera->farPlane() * rayDir;
342 worldPos.setZ( centerZ );
343 }
344
345 QgsVector3D mapPos = mMap.worldToMapCoordinates( worldPos );
346 extent.push_back( QgsPointXY( mapPos.x(), mapPos.y() ) );
347 }
348 return extent;
349}
350
352{
353 int count = 0;
354 for ( Qgs3DMapSceneEntity *entity : std::as_const( mSceneEntities ) )
355 count += entity->pendingJobsCount();
356 return count;
357}
358
359double Qgs3DMapScene::worldSpaceError( double epsilon, double distance ) const
360{
361 Qt3DRender::QCamera *camera = mCameraController->camera();
362 const double fov = camera->fieldOfView();
363 const QSize size = mEngine->size();
364 const int screenSizePx = std::max( size.width(), size.height() ); // TODO: is this correct?
365
366 // see Qgs3DUtils::screenSpaceError() for the inverse calculation (world space error to screen space error)
367 // with explanation of the math.
368 const double frustumWidthAtDistance = 2 * distance * tan( fov / 2 );
369 const double err = frustumWidthAtDistance * epsilon / screenSizePx;
370 return err;
371}
372
373void Qgs3DMapScene::onCameraChanged()
374{
375 if ( mDepthBufferRefreshTimer )
376 mDepthBufferRefreshTimer->start();
377
378 updateScene( true );
379 updateCameraNearFarPlanes();
380
381 onShadowSettingsChanged();
382
383 const QVector<QgsPointXY> extent2D = viewFrustum2DExtent();
384 emit viewed2DExtentFrom3DChanged( extent2D );
385 schedule2DMapOverlayUpdate();
386
387 // The magic to make things work better in large scenes (e.g. more than 50km across)
388 // is here: we will simply move the origin of the scene, and update transforms
389 // of the camera and all other entities. That should ensure we will not need to deal
390 // with large coordinates in 32-bit floats (and if we do have large coordinates,
391 // because the scene is far from the camera, we don't care, because those errors
392 // end up being tiny when viewed from far away).
393 constexpr float ORIGIN_SHIFT_THRESHOLD = 10'000;
394 if ( mSceneOriginShiftEnabled && mEngine->camera()->position().length() > ORIGIN_SHIFT_THRESHOLD )
395 {
396 const QgsVector3D newOrigin = mMap.origin() + QgsVector3D( mEngine->camera()->position() );
397 QgsDebugMsgLevel( u"Rebasing scene origin from %1 to %2"_s.arg( mMap.origin().toString( 1 ), newOrigin.toString( 1 ) ), 2 );
398 mMap.setOrigin( newOrigin );
399 }
400}
401
402void Qgs3DMapScene::onViewed2DExtentFrom3DChanged()
403{
404 const QSize size = mEngine->size();
405 const QPoint center( size.width() / 2, size.height() / 2 );
406
407 const double sampledDepth = mCameraController->sampleDepthBuffer( center.x(), center.y() );
408
409 if ( sampledDepth != 1.0 )
410 mLastCenterDepth = sampledDepth;
411
412 const QVector<QgsPointXY> extent2D = viewFrustum2DExtent();
413 emit viewed2DExtentFrom3DChanged( extent2D );
414}
415
416bool Qgs3DMapScene::updateScene( bool forceUpdate )
417{
418 if ( !mSceneUpdatesEnabled )
419 {
420 QgsDebugMsgLevel( "Scene update skipped", 2 );
421 return false;
422 }
423
424 QgsScopedEvent traceEvent( u"3D"_s, forceUpdate ? u"Force update scene"_s : u"Update scene"_s );
425
426 Qgs3DMapSceneEntity::SceneContext sceneContext;
427 Qt3DRender::QCamera *camera = mEngine->camera();
428 sceneContext.cameraFov = camera->fieldOfView();
429 sceneContext.cameraPos = camera->position();
430 const QSize size = mEngine->size();
431 sceneContext.screenSizePx = std::max( size.width(), size.height() ); // TODO: is this correct?
432
433 // Make our own projection matrix so that frustum culling done by the
434 // entities isn't dependent on the current near/far planes, which would then
435 // require multiple steps to stabilize.
436 // The matrix is constructed just like in QMatrix4x4::perspective() /
437 // ortho(), but for all elements involving the near and far plane, the limit
438 // of the expression with the far plane going to infinity is taken.
439 QMatrix4x4 projMatrix;
440 switch ( mMap.projectionType() )
441 {
443 {
444 float fovRadians = ( camera->fieldOfView() / 2.0f ) * static_cast<float>( M_PI ) / 180.0f;
445 float fovCotan = std::cos( fovRadians ) / std::sin( fovRadians );
446 // clang-format off
447 projMatrix = {
448 fovCotan / camera->aspectRatio(), 0, 0, 0,
449 0, fovCotan, 0, 0,
450 0, 0, -1, -2,
451 0, 0, -1, 0
452 };
453 // clang-format on
454 break;
455 }
457 {
458 Qt3DRender::QCameraLens *lens = camera->lens();
459 // clang-format off
460 projMatrix = {
461 2.0f / ( lens->right() - lens->left() ), 0, 0, 0,
462 0, 2.0f / ( lens->top() - lens->bottom() ), 0, 0,
463 0, 0, 1, 0,
464 -( lens->left() + lens->right() ) / ( lens->right() - lens->left() ), -( lens->top() + lens->bottom() ) / ( lens->top() - lens->bottom() ), -1.0f, 1.0f
465 };
466 // clang-format on
467 break;
468 }
469 default:
470 QgsDebugError( "Unhandled 3D projection type" );
471 projMatrix = camera->projectionMatrix(); // Give up and use the current matrix
472 }
473 sceneContext.viewProjectionMatrix = projMatrix * camera->viewMatrix();
474
475
476 bool anyUpdated = false;
477 for ( Qgs3DMapSceneEntity *entity : std::as_const( mSceneEntities ) )
478 {
479 if ( forceUpdate || ( entity->isEnabled() && entity->needsUpdate() ) )
480 {
481 anyUpdated = true;
482 entity->handleSceneUpdate( sceneContext );
483 if ( entity->hasReachedGpuMemoryLimit() )
485 }
486 }
487
488 updateSceneState();
489
490 return anyUpdated;
491}
492
493bool Qgs3DMapScene::updateCameraNearFarPlanes()
494{
495 // Update near and far plane from the terrain.
496 // this needs to be done with great care as we have kind of circular dependency here:
497 // active nodes are culled based on the current frustum (which involves near + far plane)
498 // and then based on active nodes we set near and far plane.
499 //
500 // All of this is just heuristics assuming that all other stuff is being rendered somewhere
501 // around the area where the terrain is.
502 //
503 // Near/far plane is setup in order to make best use of the depth buffer to avoid:
504 // 1. precision errors - if the range is too great
505 // 2. unwanted clipping of scene - if the range is too small
506
507 Qt3DRender::QCamera *camera = cameraController()->camera();
508 QMatrix4x4 viewMatrix = camera->viewMatrix();
509 float fnear = 1e9;
510 float ffar = 0;
511
512 // Iterate all scene entities to make sure that they will not get
513 // clipped by the near or far plane
514 for ( Qgs3DMapSceneEntity *se : std::as_const( mSceneEntities ) )
515 {
516 const QgsRange<float> depthRange = se->getNearFarPlaneRange( viewMatrix );
517
518 fnear = std::min( fnear, depthRange.lower() );
519 ffar = std::max( ffar, depthRange.upper() );
520 }
521
522 if ( fnear < 1 )
523 fnear = 1; // does not really make sense to use negative far plane (behind camera)
524
525 // the update didn't work out... this can happen if the scene does not contain
526 // any Qgs3DMapSceneEntity. Use the scene extent to compute near and far planes
527 // as a fallback.
528 if ( fnear == 1e9 && ffar == 0 )
529 {
530 QgsDoubleRange sceneZRange = elevationRange();
531 sceneZRange = sceneZRange.isInfinite() ? QgsDoubleRange( 0.0, 0.0 ) : sceneZRange;
532 const QgsAABB sceneBbox = Qgs3DUtils::mapToWorldExtent( mMap.extent(), sceneZRange.lower(), sceneZRange.upper(), mMap.origin() );
533 Qgs3DUtils::computeBoundingBoxNearFarPlanes( sceneBbox, viewMatrix, fnear, ffar );
534 }
535
536 // when zooming in a lot, fnear can become smaller than ffar. This should not happen
537 if ( fnear > ffar )
538 std::swap( fnear, ffar );
539
540 // set near/far plane - with some tolerance in front/behind expected near/far planes
541 float newFar = ffar * 2;
542 float newNear = fnear / 2;
543 if ( !qgsFloatNear( newFar, camera->farPlane() ) || !qgsFloatNear( newNear, camera->nearPlane() ) )
544 {
545 camera->setFarPlane( newFar );
546 camera->setNearPlane( newNear );
547 return true;
548 }
549
550 return false;
551}
552
553void Qgs3DMapScene::onFrameTriggered( float dt )
554{
555#ifdef HAVE_TRACY
556 FrameMark;
557#endif
558 QgsEventTracing::addEventToQgisTrace( QgsEventTracing::EventType::Instant, u"3D"_s, u"Frame begins"_s );
559
560 mCameraController->frameTriggered( dt );
561
562 if ( updateScene() )
563 // If the scene was changed, node bboxes might have changed, so we need to
564 // update near/far planes.
565 updateCameraNearFarPlanes();
566
567 // lock changing the FPS counter to 5 fps
568 static int frameCount = 0;
569 static float accumulatedTime = 0.0f;
570
571 if ( !mMap.debugFlags().testFlag( Qgis::Map3DDebugFlag::ShowFPS ) )
572 {
573 frameCount = 0;
574 accumulatedTime = 0;
575 return;
576 }
577
578 frameCount++;
579 accumulatedTime += dt;
580 if ( accumulatedTime >= 0.2f )
581 {
582 float fps = ( float ) frameCount / accumulatedTime;
583 frameCount = 0;
584 accumulatedTime = 0.0f;
585 emit fpsCountChanged( fps );
586 }
587}
588
589void Qgs3DMapScene::update2DMapOverlay( const QVector<QgsPointXY> &extent2DAsPoints )
590{
591 QgsFrameGraph *frameGraph = mEngine->frameGraph();
592 QgsOverlayTextureRenderView &overlayRenderView = frameGraph->overlayTextureRenderView();
593
594 if ( !mMap.is2DMapOverlayEnabled() )
595 {
596 if ( mMapOverlayEntity )
597 {
598 mMapOverlayEntity.reset();
599 }
600 overlayRenderView.setEnabled( mMap.debugDepthMapEnabled() );
601 return;
602 }
603
604 if ( !mMapOverlayEntity )
605 {
606 QgsWindow3DEngine *engine = qobject_cast<QgsWindow3DEngine *>( mEngine );
607 mMapOverlayEntity = make_qobject_unique<QgsMapOverlayEntity>( engine, &overlayRenderView, &mMap, this );
608 mMapOverlayEntity->setEnabled( true );
609 overlayRenderView.setEnabled( true );
610 }
611
612
613 Qt3DRender::QCamera *camera = mEngine->camera();
614 const QgsVector3D extentCenter3D = mMap.worldToMapCoordinates( camera->position() );
615 const QgsPointXY extentCenter2D( extentCenter3D.x(), extentCenter3D.y() );
616
617 // Compute an extent that provides an overview around the camera position.
618 // When the view is from above (pitch near 0°), the scene's extent is reduced.
619 // As the pitch increases (up to 90°), a larger portion of the scene becomes visible.
620 // The smoothing factor allows reproducing the behavior of the maximum extent
621 // as the pitch changes. The calculated extent is bounded to prevent covering too large a scene.
622 double minHalfExtent = std::numeric_limits<double>::max();
623 double maxHalfExtent = 0.0;
624 for ( const QgsPointXY &extentPoint : extent2DAsPoints )
625 {
626 const double distance = extentCenter2D.distance( extentPoint );
627 minHalfExtent = std::min( minHalfExtent, distance );
628 maxHalfExtent = std::max( maxHalfExtent, distance );
629 }
630
631 const QgsRectangle fullExtent = sceneExtent();
632 const double sceneHalfExtent = 0.6 * std::max( fullExtent.width(), fullExtent.height() );
633
634 minHalfExtent = std::min( 50., minHalfExtent );
635 maxHalfExtent = std::min( 100., maxHalfExtent );
636 const double smoothFactor = std::sin( mCameraController->pitch() / 90.0 * M_PI_2 );
637
638 // Using the scene extent to prevent the image from becoming too wide when zooming out
639 const double adjustedHalfExtent = std::min( 3.0 * ( minHalfExtent + smoothFactor * maxHalfExtent ), sceneHalfExtent );
640
641 const QgsRectangle overviewExtent = QgsRectangle::fromCenterAndSize( extentCenter2D, adjustedHalfExtent, adjustedHalfExtent );
642 const bool showFrustum = mMap.viewFrustumVisualizationEnabled();
643 mMapOverlayEntity->update( overviewExtent, extent2DAsPoints, mCameraController->yaw(), showFrustum );
644}
645
646void Qgs3DMapScene::createTerrain()
647{
648 if ( mTerrain )
649 {
650 mSceneEntities.removeOne( mTerrain );
651
652 delete mTerrain;
653 mTerrain = nullptr;
654 }
655
656 if ( mGlobe )
657 {
658 mSceneEntities.removeOne( mGlobe );
659
660 delete mGlobe;
661 mGlobe = nullptr;
662 }
663
664 if ( !mTerrainUpdateScheduled )
665 {
666 // defer re-creation of terrain: there may be multiple invocations of this slot, so create the new entity just once
667 QTimer::singleShot( 0, this, &Qgs3DMapScene::createTerrainDeferred );
668 mTerrainUpdateScheduled = true;
669 setSceneState( Updating );
670 }
671 else
672 {
674 }
675}
676
677void Qgs3DMapScene::createTerrainDeferred()
678{
679 QgsChunkedEntity *terrainOrGlobe = nullptr;
680
681 if ( mMap.sceneMode() == Qgis::SceneMode::Globe && mMap.terrainRenderingEnabled() )
682 {
683 mGlobe = new QgsGlobeEntity( &mMap );
684 terrainOrGlobe = mGlobe;
685 }
686 else if ( mMap.sceneMode() == Qgis::SceneMode::Local && mMap.terrainRenderingEnabled() && mMap.terrainGenerator() )
687 {
688 double tile0width = mMap.terrainGenerator()->rootChunkExtent().width();
689 int maxZoomLevel = Qgs3DUtils::maxZoomLevel( tile0width, mMap.terrainSettings()->mapTileResolution(), mMap.terrainSettings()->maximumGroundError() );
690 const QgsBox3D rootBox3D = mMap.terrainGenerator()->rootChunkBox3D( mMap );
691 float rootError = mMap.terrainGenerator()->rootChunkError( mMap );
692 const QgsBox3D clippingBox3D( mMap.extent(), rootBox3D.zMinimum(), rootBox3D.zMaximum() );
693 mMap.terrainGenerator()->setupQuadtree( rootBox3D, rootError, maxZoomLevel, clippingBox3D );
694
695 mTerrain = new QgsTerrainEntity( &mMap );
696 mTerrain->setObjectName( u"Terrain"_s );
697 terrainOrGlobe = mTerrain;
698 }
699
700 if ( terrainOrGlobe )
701 {
702 // The terrain is not added through addSceneEntity(), so we add the required QLayers here
703 QgsFrameGraph *frameGraph = mEngine->frameGraph();
704 terrainOrGlobe->addComponent( frameGraph->forwardRenderView().renderLayer() );
705 terrainOrGlobe->addComponent( frameGraph->shadowRenderView().entityCastingShadowsLayer() );
706
707 terrainOrGlobe->setParent( this );
708 terrainOrGlobe->setShowBoundingBoxes( mMap.debugFlags().testFlag( Qgis::Map3DDebugFlag::ShowTerrainBoundingBoxes ) );
709
710 mSceneEntities << terrainOrGlobe;
711
712 connect( terrainOrGlobe, &QgsChunkedEntity::pendingJobsCountChanged, this, &Qgs3DMapScene::totalPendingJobsCountChanged );
713 connect( terrainOrGlobe, &Qgs3DMapSceneEntity::newEntityCreated, this, [this]( Qt3DCore::QEntity *entity ) {
714 // let's make sure that any entity we're about to show has the right scene origin set
715 const QList<QgsGeoTransform *> transforms = entity->findChildren<QgsGeoTransform *>();
716 for ( QgsGeoTransform *transform : transforms )
717 {
718 transform->setOrigin( mMap.origin() );
719 }
720
721 // enable clipping on the terrain if necessary
722 handleClippingOnEntity( entity );
723 } );
724 }
725
726 // make sure that renderers for layers are re-created as well
727 const QList<QgsMapLayer *> layers = mMap.layers();
728 for ( QgsMapLayer *layer : layers )
729 {
730 // remove old entity - if any
731 removeLayerEntity( layer );
732
733 // add new entity - if any 3D renderer
734 addLayerEntity( layer );
735 }
736
738 onCameraChanged(); // force update of the new terrain
739 mTerrainUpdateScheduled = false;
740}
741
742void Qgs3DMapScene::onBackgroundColorChanged()
743{
744 mEngine->setClearColor( mMap.backgroundColor() );
745}
746
747void Qgs3DMapScene::updateLights()
748{
749 for ( Qt3DCore::QEntity *entity : std::as_const( mLightEntities ) )
750 entity->deleteLater();
751 mLightEntities.clear();
752
753 QgsFrameGraph *frameGraph = mEngine->frameGraph();
754 const QList<QgsLightSource *> newLights = mMap.lightSources();
755 for ( const QgsLightSource *source : newLights )
756 {
757 Qt3DCore::QEntity *entity = source->createEntity( mMap, this );
758 entity->addComponent( frameGraph->forwardRenderView().renderLayer() );
759 mLightEntities.append( entity );
760 }
761
762 onShadowSettingsChanged();
763}
764
765void Qgs3DMapScene::updateCameraLens()
766{
767 mEngine->camera()->lens()->setFieldOfView( static_cast< float >( mMap.fieldOfView() ) );
768 mEngine->camera()->lens()->setProjectionType( static_cast<Qt3DRender::QCameraLens::ProjectionType>( mMap.projectionType() ) );
769 onCameraChanged();
770}
771
772void Qgs3DMapScene::onLayerRenderer3DChanged()
773{
774 QgsMapLayer *layer = qobject_cast<QgsMapLayer *>( sender() );
775 Q_ASSERT( layer );
776
777 // remove old entity - if any
778 removeLayerEntity( layer );
779
780 // add new entity - if any 3D renderer
781 addLayerEntity( layer );
782}
783
784void Qgs3DMapScene::onLayersChanged()
785{
786 QSet<QgsMapLayer *> layersBefore = qgis::listToSet( mLayerEntities.keys() );
787 QList<QgsMapLayer *> layersAdded;
788 const QList<QgsMapLayer *> layers = mMap.layers();
789 for ( QgsMapLayer *layer : layers )
790 {
791 if ( !layersBefore.contains( layer ) )
792 {
793 layersAdded << layer;
794 }
795 else
796 {
797 layersBefore.remove( layer );
798 }
799 }
800
801 // what is left in layersBefore are layers that have been removed
802 for ( QgsMapLayer *layer : std::as_const( layersBefore ) )
803 {
804 removeLayerEntity( layer );
805 }
806
807 for ( QgsMapLayer *layer : std::as_const( layersAdded ) )
808 {
809 addLayerEntity( layer );
810 }
811}
812
814{
815 const QList<QgsMapLayer *> layers = mLayerEntities.keys();
816 for ( QgsMapLayer *layer : layers )
817 {
818 if ( QgsMapLayerTemporalProperties *temporalProperties = layer->temporalProperties() )
819 {
820 if ( temporalProperties->isActive() )
821 {
822 removeLayerEntity( layer );
823 addLayerEntity( layer );
824 }
825 }
826 }
827}
828
829void Qgs3DMapScene::addSceneEntity( Qgs3DMapSceneEntity *sceneNewEntity )
830{
831 Q_ASSERT( sceneNewEntity );
832
833 mSceneEntities.append( sceneNewEntity );
834
835 sceneNewEntity->setParent( this );
836
837 finalizeNewEntity( sceneNewEntity );
838
839 connect( sceneNewEntity, &Qgs3DMapSceneEntity::newEntityCreated, this, [this]( Qt3DCore::QEntity *entity ) {
840 finalizeNewEntity( entity );
841 // this ensures to update the near/far planes with the exact bounding box of the new entity.
842 updateCameraNearFarPlanes();
843 } );
844
845 connect( sceneNewEntity, &Qgs3DMapSceneEntity::pendingJobsCountChanged, this, &Qgs3DMapScene::totalPendingJobsCountChanged );
846
847 onCameraChanged(); // needed for chunked entities
848}
849
850void Qgs3DMapScene::removeSceneEntity( Qgs3DMapSceneEntity *sceneEntity )
851{
852 Q_ASSERT( sceneEntity );
853
854 mSceneEntities.removeOne( sceneEntity );
855
856 sceneEntity->deleteLater();
857}
858
859
860void Qgs3DMapScene::addLayerEntity( QgsMapLayer *layer )
861{
862 QgsAbstract3DRenderer *renderer = layer->renderer3D();
863 if ( renderer )
864 {
865 // Fix vector layer's renderer to make sure the renderer is pointing to its layer.
866 // It has happened before that renderer pointed to a different layer (probably after copying a style).
867 // This is a bit of a hack and it should be handled in QgsMapLayer::setRenderer3D() but in qgis_core
868 // the vector layer 3D renderer classes are not available.
869 if ( layer->type() == Qgis::LayerType::Vector && ( renderer->type() == "vector"_L1 || renderer->type() == "rulebased"_L1 || renderer->type() == "categorized"_L1 ) )
870 {
871 static_cast<QgsAbstractVectorLayer3DRenderer *>( renderer )->setLayer( static_cast<QgsVectorLayer *>( layer ) );
872 if ( renderer->type() == "vector"_L1 )
873 {
874 QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
875 if ( vlayer->geometryType() == Qgis::GeometryType::Point )
876 {
877 const QgsPoint3DSymbol *pointSymbol = static_cast<const QgsPoint3DSymbol *>( static_cast<QgsVectorLayer3DRenderer *>( renderer )->symbol() );
878 if ( pointSymbol->shape() == Qgis::Point3DShape::Model )
879 {
880 mModelVectorLayers.append( layer );
881 }
882 }
883 }
884 else if ( renderer->type() == "rulebased"_L1 )
885 {
886 const QgsRuleBased3DRenderer::RuleList rules = static_cast<QgsRuleBased3DRenderer *>( renderer )->rootRule()->descendants();
887 for ( auto rule : rules )
888 {
889 const QgsPoint3DSymbol *pointSymbol = dynamic_cast<const QgsPoint3DSymbol *>( rule->symbol() );
890 if ( pointSymbol && pointSymbol->shape() == Qgis::Point3DShape::Model )
891 {
892 mModelVectorLayers.append( layer );
893 break;
894 }
895 }
896 }
897 else if ( renderer->type() == "categorized"_L1 )
898 {
899 const Qgs3DCategoryList categories = static_cast<QgsCategorized3DRenderer *>( renderer )->categories();
900 for ( const Qgs3DRendererCategory &category : categories )
901 {
902 const QgsPoint3DSymbol *pointSymbol = dynamic_cast<const QgsPoint3DSymbol *>( category.symbol() );
903 if ( pointSymbol && pointSymbol->shape() == Qgis::Point3DShape::Model )
904 {
905 mModelVectorLayers.append( layer );
906 break;
907 }
908 }
909 }
910 }
911 else if ( layer->type() == Qgis::LayerType::Mesh && renderer->type() == "mesh"_L1 )
912 {
913 QgsMeshLayer3DRenderer *meshRenderer = static_cast<QgsMeshLayer3DRenderer *>( renderer );
914 meshRenderer->setLayer( static_cast<QgsMeshLayer *>( layer ) );
915
916 // Before entity creation, set the maximum texture size
917 // Not very clean, but for now, only place found in the workflow to do that simple
918 QgsMesh3DSymbol *sym = meshRenderer->symbol()->clone();
919 sym->setMaximumTextureSize( maximumTextureSize() );
920 meshRenderer->setSymbol( sym );
921 }
922 else if ( layer->type() == Qgis::LayerType::PointCloud && renderer->type() == "pointcloud"_L1 )
923 {
924 QgsPointCloudLayer3DRenderer *pointCloudRenderer = static_cast<QgsPointCloudLayer3DRenderer *>( renderer );
925 pointCloudRenderer->setLayer( static_cast<QgsPointCloudLayer *>( layer ) );
926 }
927 else if ( layer->type() == Qgis::LayerType::TiledScene && renderer->type() == "tiledscene"_L1 )
928 {
929 QgsTiledSceneLayer3DRenderer *tiledSceneRenderer = static_cast<QgsTiledSceneLayer3DRenderer *>( renderer );
930 tiledSceneRenderer->setLayer( static_cast<QgsTiledSceneLayer *>( layer ) );
931 }
932 else if ( layer->type() == Qgis::LayerType::Annotation && renderer->type() == "annotation"_L1 )
933 {
934 auto annotationLayerRenderer = qgis::down_cast<QgsAnnotationLayer3DRenderer *>( renderer );
935 annotationLayerRenderer->setLayer( qobject_cast<QgsAnnotationLayer *>( layer ) );
936 }
937
938 Qt3DCore::QEntity *newEntity = renderer->createEntity( &mMap );
939 if ( newEntity )
940 {
941 // Add name to QObject for debugging
942 newEntity->setObjectName( u"%1 3D entity"_s.arg( layer->name() ) );
943
944 mLayerEntities.insert( layer, newEntity );
945
946 if ( Qgs3DMapSceneEntity *sceneNewEntity = qobject_cast<Qgs3DMapSceneEntity *>( newEntity ) )
947 {
948 // also sets this scene as the entity's parent and finalizes it
949 addSceneEntity( sceneNewEntity );
950 }
951 else
952 {
953 newEntity->setParent( this );
954 finalizeNewEntity( newEntity );
955 }
956 }
957 }
958
959 connect( layer, &QgsMapLayer::request3DUpdate, this, &Qgs3DMapScene::onLayerRenderer3DChanged );
960
961 if ( layer->type() == Qgis::LayerType::Vector )
962 {
963 QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
964 connect( vlayer, &QgsVectorLayer::selectionChanged, this, &Qgs3DMapScene::onLayerRenderer3DChanged );
965 connect( vlayer, &QgsVectorLayer::layerModified, this, &Qgs3DMapScene::onLayerRenderer3DChanged );
966 connect( vlayer, &QgsVectorLayer::subsetStringChanged, this, &Qgs3DMapScene::onLayerRenderer3DChanged );
967 }
968
969 if ( layer->type() == Qgis::LayerType::Mesh )
970 {
971 connect( layer, &QgsMapLayer::rendererChanged, this, &Qgs3DMapScene::onLayerRenderer3DChanged );
972 }
973
974 if ( layer->type() == Qgis::LayerType::PointCloud )
975 {
976 QgsPointCloudLayer *pclayer = qobject_cast<QgsPointCloudLayer *>( layer );
977 connect( pclayer, &QgsPointCloudLayer::renderer3DChanged, this, &Qgs3DMapScene::onLayerRenderer3DChanged );
978 connect( pclayer, &QgsPointCloudLayer::subsetStringChanged, this, &Qgs3DMapScene::onLayerRenderer3DChanged );
979 }
980}
981
982void Qgs3DMapScene::removeLayerEntity( QgsMapLayer *layer )
983{
984 Qt3DCore::QEntity *entity = mLayerEntities.take( layer );
985
986 if ( Qgs3DMapSceneEntity *sceneEntity = qobject_cast<Qgs3DMapSceneEntity *>( entity ) )
987 {
988 // also schedules the entity for deletion
989 removeSceneEntity( sceneEntity );
990 }
991 else
992 {
993 if ( entity )
994 entity->deleteLater();
995 }
996
997 disconnect( layer, &QgsMapLayer::request3DUpdate, this, &Qgs3DMapScene::onLayerRenderer3DChanged );
998
999 if ( layer->type() == Qgis::LayerType::Vector )
1000 {
1001 QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
1002 disconnect( vlayer, &QgsVectorLayer::selectionChanged, this, &Qgs3DMapScene::onLayerRenderer3DChanged );
1003 disconnect( vlayer, &QgsVectorLayer::layerModified, this, &Qgs3DMapScene::onLayerRenderer3DChanged );
1004 disconnect( vlayer, &QgsVectorLayer::subsetStringChanged, this, &Qgs3DMapScene::onLayerRenderer3DChanged );
1005 mModelVectorLayers.removeAll( layer );
1006 }
1007
1008 if ( layer->type() == Qgis::LayerType::Mesh )
1009 {
1010 disconnect( layer, &QgsMapLayer::rendererChanged, this, &Qgs3DMapScene::onLayerRenderer3DChanged );
1011 }
1012
1013 if ( layer->type() == Qgis::LayerType::PointCloud )
1014 {
1015 QgsPointCloudLayer *pclayer = qobject_cast<QgsPointCloudLayer *>( layer );
1016 disconnect( pclayer, &QgsPointCloudLayer::renderer3DChanged, this, &Qgs3DMapScene::onLayerRenderer3DChanged );
1017 disconnect( pclayer, &QgsPointCloudLayer::subsetStringChanged, this, &Qgs3DMapScene::onLayerRenderer3DChanged );
1018 disconnect( pclayer, &QgsPointCloudLayer::layerModified, this, &Qgs3DMapScene::onLayerRenderer3DChanged );
1019 }
1020}
1021
1022void Qgs3DMapScene::finalizeNewEntity( Qt3DCore::QEntity *newEntity )
1023{
1024 // let's make sure that any entity we're about to show has the right scene origin set
1025 const QList<QgsGeoTransform *> transforms = newEntity->findChildren<QgsGeoTransform *>();
1026 for ( QgsGeoTransform *transform : transforms )
1027 {
1028 transform->setOrigin( mMap.origin() );
1029 }
1030
1031 // set clip planes on the new entity if necessary
1032 handleClippingOnEntity( newEntity );
1033
1034 // this is probably not the best place for material-specific configuration,
1035 // maybe this could be more generalized when other materials need some specific treatment
1036 const QList<Qt3DRender::QMaterial *> childMaterials = newEntity->findChildren<Qt3DRender::QMaterial *>();
1037
1038 // first pass over materials -- setup viewport sizing logic for ALL materials that require it
1039 // (this needs to apply to all materials, includes those for highlight entities)
1040 for ( Qt3DRender::QMaterial *material : childMaterials )
1041 {
1042 if ( auto lm = qobject_cast< QgsLineMaterial * >( material ) )
1043 {
1044 connect( mEngine, &QgsAbstract3DEngine::sizeChanged, lm, [lm, this] { lm->setViewportSize( mEngine->size() ); } );
1045 lm->setViewportSize( mEngine->size() );
1046 }
1047 else if ( auto bm = qobject_cast< QgsPoint3DBillboardMaterial * >( material ) )
1048 {
1049 connect( mEngine, &QgsAbstract3DEngine::sizeChanged, bm, [bm, this] { bm->setViewportSize( mEngine->size() ); } );
1050 bm->setViewportSize( mEngine->size() );
1051 }
1052 }
1053
1054 QgsFrameGraph *frameGraph = mEngine->frameGraph();
1055
1056 // Here we check if the entity should not be rendered in the forward render view
1057 // For example highlight entities should only be rendered in the highlights render view, so we check for attached QLayers
1058 const QVector<Qt3DRender::QLayer *> layers = newEntity->componentsOfType<Qt3DRender::QLayer>();
1059 if ( layers.contains( frameGraph->highlightsRenderView().highlightsLayer() ) )
1060 return;
1061
1062 // Add the required QLayers to the entity
1063 newEntity->addComponent( frameGraph->forwardRenderView().renderLayer() );
1064
1065 Qt3DRender::QLayer *shadowCastingEntityLayer = frameGraph->shadowRenderView().entityCastingShadowsLayer();
1066 Qt3DRender::QLayer *transparentLayer = frameGraph->forwardRenderView().transparentObjectLayer();
1067 for ( Qt3DRender::QMaterial *material : childMaterials )
1068 {
1069 // find the specific entity this material belongs to -- it may be a child of the parent entity
1070 // being finalized
1071 auto materialEntity = qobject_cast<Qt3DCore::QEntity *>( material->parent() );
1072 if ( !materialEntity )
1073 continue;
1074
1075 bool materialCastsShadows = false;
1076 if ( auto qgsMaterial = qobject_cast< QgsMaterial * >( material ) )
1077 {
1078 if ( qgsMaterial->castsShadows() )
1079 {
1080 materialCastsShadows = true;
1081 }
1082 }
1083 else
1084 {
1085 // for non QgsMaterial materials we assume they need shadows
1086 materialCastsShadows = true;
1087 }
1088
1089 if ( materialCastsShadows && !materialEntity->components().contains( shadowCastingEntityLayer ) )
1090 {
1091 materialEntity->addComponent( shadowCastingEntityLayer );
1092 }
1093
1094 // Finalize adding the 3D transparent objects by adding the layer components to the entities
1095
1096 // This handles the phong material without data defined properties.
1097 if ( auto ph = qobject_cast<Qt3DExtras::QDiffuseSpecularMaterial *>( material ) )
1098 {
1099 if ( ph->diffuse().value<QColor>().alphaF() != 1.0f )
1100 {
1101 if ( !materialEntity->components().contains( transparentLayer ) )
1102 {
1103 materialEntity->addComponent( transparentLayer );
1104 }
1105 }
1106 }
1107#if 0
1108 /*
1109 * Adds transparency layer to QgsPoint3DBillboardMaterial entities,
1110 * so that they get rendered in the transparent pipeline instead
1111 * of the opaque pipeline. Permits semi-opaque pixel rendering.
1112 *
1113 * Pros: nicely smoothed billboard symbol rendering, without harsh
1114 * aliased edges. Billboard symbols can use semi-transparent colors.
1115 *
1116 * Cons: Introduces ordering issues for billboards, where billboards
1117 * which should be shown behind others will appear in front from
1118 * some angles (i.e. the same issue as we get for 3d polygon objects
1119 * with transparency)
1120 *
1121 * Consider enabling if/when we have some workaround for the stacking issue,
1122 * eg CPU based sorting on camera movement...
1123 */
1124 else if ( auto billboardMaterial = qobject_cast<QgsPoint3DBillboardMaterial *>( material ) )
1125 {
1126 Qt3DCore::QEntity *entity = qobject_cast<Qt3DCore::QEntity *>( billboardMaterial->parent() );
1127 if ( !materialEntity->components().contains( transparentLayer ) )
1128 {
1129 materialEntity->addComponent( transparentLayer );
1130 }
1131 }
1132#endif
1133 else
1134 {
1135 // This handles the phong material with data defined properties, the textured case and point (instanced) symbols.
1136 if ( Qt3DRender::QEffect *effect = material->effect() )
1137 {
1138 const QVector<Qt3DRender::QParameter *> parameters = effect->parameters();
1139 for ( const Qt3DRender::QParameter *parameter : parameters )
1140 {
1141 if ( parameter->name() == "opacity" && parameter->value() != 1.0f )
1142 {
1143 if ( !materialEntity->components().contains( transparentLayer ) )
1144 {
1145 materialEntity->addComponent( transparentLayer );
1146 }
1147 break;
1148 }
1149 }
1150 }
1151 }
1152 }
1153
1154 if ( childMaterials.empty() )
1155 {
1156 // handle shadows for entities without materials -- eg point models
1157 newEntity->addComponent( shadowCastingEntityLayer );
1158 }
1159}
1160
1161int Qgs3DMapScene::maximumTextureSize() const
1162{
1163 QSurface *surface = mEngine->surface();
1164 QOpenGLContext context;
1165 context.create();
1166 bool success = context.makeCurrent( surface );
1167
1168 if ( success )
1169 {
1170 QOpenGLFunctions openglFunctions = QOpenGLFunctions( &context );
1171
1172 GLint size;
1173 openglFunctions.initializeOpenGLFunctions();
1174 openglFunctions.glGetIntegerv( GL_MAX_TEXTURE_SIZE, &size );
1175 return int( size );
1176 }
1177 else
1178 {
1179 return 4096; //we can't have a context to defined the max texture size, we use this reasonable value
1180 }
1181}
1182
1183void Qgs3DMapScene::addCameraViewCenterEntity( Qt3DRender::QCamera *camera )
1184{
1185 mEntityCameraViewCenter = new Qt3DCore::QEntity;
1186
1187 Qt3DCore::QTransform *trCameraViewCenter = new Qt3DCore::QTransform;
1188 mEntityCameraViewCenter->addComponent( trCameraViewCenter );
1189 connect( camera, &Qt3DRender::QCamera::viewCenterChanged, this, [trCameraViewCenter, camera] { trCameraViewCenter->setTranslation( camera->viewCenter() ); } );
1190
1191 auto materialCameraViewCenter = new QgsUnlitMaterial();
1192 materialCameraViewCenter->setColor( Qt::red );
1193 materialCameraViewCenter->setCastsShadows( false );
1194 mEntityCameraViewCenter->addComponent( materialCameraViewCenter );
1195
1196 Qt3DExtras::QSphereMesh *rendererCameraViewCenter = new Qt3DExtras::QSphereMesh;
1197 rendererCameraViewCenter->setRadius( 10 );
1198 mEntityCameraViewCenter->addComponent( rendererCameraViewCenter );
1199
1200 mEntityCameraViewCenter->setEnabled( mMap.debugFlags().testFlag( Qgis::Map3DDebugFlag::ShowCameraViewCenter ) );
1201 mEntityCameraViewCenter->setParent( this );
1202
1203 QgsFrameGraph *frameGraph = mEngine->frameGraph();
1204 mEntityCameraViewCenter->addComponent( frameGraph->forwardRenderView().renderLayer() );
1205
1206 connect( &mMap, &Qgs3DMapSettings::showCameraViewCenterChanged, this, [this] { mEntityCameraViewCenter->setEnabled( mMap.debugFlags().testFlag( Qgis::Map3DDebugFlag::ShowCameraViewCenter ) ); } );
1207}
1208
1209void Qgs3DMapScene::setSceneState( Qgs3DMapScene::SceneState state )
1210{
1211 if ( mSceneState == state )
1212 return;
1213 mSceneState = state;
1214 emit sceneStateChanged();
1215}
1216
1217void Qgs3DMapScene::updateSceneState()
1218{
1219 if ( mTerrainUpdateScheduled )
1220 {
1221 setSceneState( Updating );
1222 return;
1223 }
1224
1225 for ( Qgs3DMapSceneEntity *entity : std::as_const( mSceneEntities ) )
1226 {
1227 if ( entity->isEnabled() && entity->pendingJobsCount() > 0 )
1228 {
1229 setSceneState( Updating );
1230 return;
1231 }
1232 }
1233
1234 setSceneState( Ready );
1235}
1236
1237void Qgs3DMapScene::onBackgroundSettingsChanged()
1238{
1239 if ( mBackgroundEntity )
1240 {
1241 mBackgroundEntity->deleteLater();
1242 mBackgroundEntity = nullptr;
1243 }
1244
1245 const QgsAbstract3DMapBackgroundSettings *settings = mMap.backgroundSettings();
1246 if ( !settings )
1247 {
1248 mEnvironmentLight->setMode( QgsEnvironmentLight::Mode::Disabled );
1249 return;
1250 }
1251
1252 QgsFrameGraph *frameGraph = mEngine->frameGraph();
1253
1255 {
1256 const QgsSkyboxSettings *skyboxSettings = dynamic_cast<const QgsSkyboxSettings *>( settings );
1257 const QMap<QString, QString> faces = skyboxSettings->cubeMapFacesPaths();
1258 mBackgroundEntity
1259 = new QgsCubeFacesSkyboxEntity( skyboxSettings->cubeMapping(), faces[u"posX"_s], faces[u"posY"_s], faces[u"posZ"_s], faces[u"negX"_s], faces[u"negY"_s], faces[u"negZ"_s], skyboxSettings->environmentalLightingEnabled(), this );
1260 qgis::down_cast< QgsSkyboxEntity * >( mBackgroundEntity )->updateEnvironmentLight( mEnvironmentLight );
1261 mEnvironmentLight->setStrength( static_cast< float >( skyboxSettings->environmentalLightStrength() ) );
1262 }
1264 {
1265 const QgsFixedGradientBackgroundSettings *gradientSettings = dynamic_cast<const QgsFixedGradientBackgroundSettings *>( settings );
1266 mBackgroundEntity = new QgsGradientBackgroundEntity( gradientSettings->topColor(), gradientSettings->bottomColor(), this );
1267 mEnvironmentLight->setMode( QgsEnvironmentLight::Mode::Disabled );
1268 }
1269 else
1270 {
1271 mEnvironmentLight->setMode( QgsEnvironmentLight::Mode::Disabled );
1272 }
1273
1274 mBackgroundEntity->addComponent( frameGraph->forwardRenderView().backgroundLayer() );
1275 mBackgroundEntity->addComponent( frameGraph->forwardRenderView().renderLayer() );
1276}
1277
1278void Qgs3DMapScene::onShadowSettingsChanged()
1279{
1280 mEngine->frameGraph()->updateShadowSettings( mMap );
1281}
1282
1283void Qgs3DMapScene::onAmbientOcclusionSettingsChanged()
1284{
1285 mEngine->frameGraph()->updateAmbientOcclusionSettings( mMap.ambientOcclusionSettings() );
1286}
1287
1288void Qgs3DMapScene::onBloomSettingsChanged()
1289{
1290 mEngine->frameGraph()->updateBloomSettings( mMap.bloomSettings() );
1291}
1292
1293void Qgs3DMapScene::onColorGradingSettingsChanged()
1294{
1295 mEngine->frameGraph()->updateColorGradingSettings( mMap.colorGradingSettings() );
1296}
1297
1298void Qgs3DMapScene::onDebugDepthMapSettingsChanged()
1299{
1300 mEngine->frameGraph()->updateDebugDepthMapSettings( mMap );
1301}
1302
1303void Qgs3DMapScene::onDebugOverlayEnabledChanged()
1304{
1305 mEngine->frameGraph()->setDebugOverlayEnabled( mMap.isDebugOverlayEnabled() );
1306 mEngine->renderSettings()->setRenderPolicy( mMap.isDebugOverlayEnabled() ? Qt3DRender::QRenderSettings::Always : Qt3DRender::QRenderSettings::OnDemand );
1307}
1308
1309void Qgs3DMapScene::onEyeDomeShadingSettingsChanged()
1310{
1311 mEngine->frameGraph()->updateEyeDomeSettings( mMap );
1312}
1313
1314void Qgs3DMapScene::onMsaaEnabledChanged()
1315{
1316 mEngine->frameGraph()->setMsaaEnabled( mMap.isMsaaEnabled() );
1317}
1318
1319void Qgs3DMapScene::onShowMapOverlayChanged()
1320{
1321 const QVector<QgsPointXY> extent2D = viewFrustum2DExtent();
1322 update2DMapOverlay( extent2D );
1323}
1324
1325void Qgs3DMapScene::onCameraMovementSpeedChanged()
1326{
1327 mCameraController->setCameraMovementSpeed( mMap.cameraMovementSpeed() );
1328}
1329
1330void Qgs3DMapScene::onCameraNavigationModeChanged()
1331{
1332 mCameraController->setCameraNavigationMode( mMap.cameraNavigationMode() );
1333}
1334
1336{
1337 QVector<QString> notParsedLayers;
1338 Qgs3DSceneExporter exporter;
1339
1340 exporter.setTerrainResolution( exportSettings.terrrainResolution() );
1341 exporter.setSmoothEdges( exportSettings.smoothEdges() );
1342 exporter.setExportNormals( exportSettings.exportNormals() );
1343 exporter.setExportTextures( exportSettings.exportTextures() );
1344 exporter.setTerrainTextureResolution( exportSettings.terrainTextureResolution() );
1345 exporter.setScale( exportSettings.scale() );
1346 exporter.setTerrainExportEnabled( exportSettings.terrainExportEnabled() );
1347
1348 for ( auto it = mLayerEntities.constBegin(); it != mLayerEntities.constEnd(); ++it )
1349 {
1350 QgsMapLayer *layer = it.key();
1351 Qt3DCore::QEntity *rootEntity = it.value();
1352 Qgis::LayerType layerType = layer->type();
1353 switch ( layerType )
1354 {
1356 if ( !exporter.parseVectorLayerEntity( rootEntity, qobject_cast<QgsVectorLayer *>( layer ) ) )
1357 notParsedLayers.push_back( layer->name() );
1358 break;
1367 notParsedLayers.push_back( layer->name() );
1368 break;
1369 }
1370 }
1371
1372 if ( mTerrain )
1373 exporter.parseTerrain( mTerrain, "Terrain" );
1374
1375 const bool sceneSaved = exporter.save( exportSettings.sceneName(), exportSettings.sceneFolderPath(), exportSettings.exportFormat() );
1376 if ( !sceneSaved )
1377 {
1378 return false;
1379 }
1380
1381 if ( !notParsedLayers.empty() )
1382 {
1383 QString message = tr( "The following layers were not exported:" ) + "\n";
1384 for ( const QString &layerName : notParsedLayers )
1385 message += layerName + "\n";
1386 QgsMessageOutput::showMessage( tr( "3D exporter warning" ), message, Qgis::StringFormat::PlainText );
1387 }
1388
1389 return true;
1390}
1391
1392QVector<const QgsChunkNode *> Qgs3DMapScene::getLayerActiveChunkNodes( QgsMapLayer *layer )
1393{
1394 QVector<const QgsChunkNode *> chunks;
1395 if ( !mLayerEntities.contains( layer ) )
1396 return chunks;
1397 if ( QgsChunkedEntity *c = qobject_cast<QgsChunkedEntity *>( mLayerEntities[layer] ) )
1398 {
1399 const QList<QgsChunkNode *> activeNodes = c->activeNodes();
1400 for ( QgsChunkNode *n : activeNodes )
1401 chunks.push_back( n );
1402 }
1403 return chunks;
1404}
1405
1407{
1408 return mMap.extent();
1409}
1410
1411QgsDoubleRange Qgs3DMapScene::elevationRange( const bool ignoreTerrain ) const
1412{
1413 double zMin = std::numeric_limits<double>::max();
1414 double zMax = std::numeric_limits<double>::lowest();
1415 if ( mMap.terrainRenderingEnabled() && mTerrain && !ignoreTerrain )
1416 {
1417 const QgsBox3D box3D = mTerrain->rootNode()->box3D();
1418 zMin = std::min( zMin, box3D.zMinimum() );
1419 zMax = std::max( zMax, box3D.zMaximum() );
1420 }
1421
1422 for ( auto it = mLayerEntities.constBegin(); it != mLayerEntities.constEnd(); it++ )
1423 {
1424 QgsMapLayer *layer = it.key();
1425 switch ( layer->type() )
1426 {
1428 {
1429 QgsPointCloudLayer *pcl = qobject_cast<QgsPointCloudLayer *>( layer );
1430 QgsDoubleRange zRange = pcl->elevationProperties()->calculateZRange( pcl );
1431 zMin = std::min( zMin, zRange.lower() );
1432 zMax = std::max( zMax, zRange.upper() );
1433 break;
1434 }
1436 {
1437 QgsMeshLayer *meshLayer = qobject_cast<QgsMeshLayer *>( layer );
1438 QgsAbstract3DRenderer *renderer3D = meshLayer->renderer3D();
1439 if ( renderer3D )
1440 {
1441 QgsMeshLayer3DRenderer *meshLayerRenderer = static_cast<QgsMeshLayer3DRenderer *>( renderer3D );
1442 const int verticalGroupDatasetIndex = meshLayerRenderer->symbol()->verticalDatasetGroupIndex();
1443 const QgsMeshDatasetGroupMetadata verticalGroupMetadata = meshLayer->datasetGroupMetadata( verticalGroupDatasetIndex );
1444 const double verticalScale = meshLayerRenderer->symbol()->verticalScale();
1445 zMin = std::min( zMin, verticalGroupMetadata.minimum() * verticalScale );
1446 zMax = std::max( zMax, verticalGroupMetadata.maximum() * verticalScale );
1447 }
1448 break;
1449 }
1451 {
1452 QgsTiledSceneLayer *sceneLayer = qobject_cast<QgsTiledSceneLayer *>( layer );
1453 const QgsDoubleRange zRange = sceneLayer->elevationProperties()->calculateZRange( sceneLayer );
1454 if ( !zRange.isInfinite() && !zRange.isEmpty() )
1455 {
1456 zMin = std::min( zMin, zRange.lower() );
1457 zMax = std::max( zMax, zRange.upper() );
1458 }
1459 break;
1460 }
1467 break;
1468 }
1469 }
1470 const QgsDoubleRange zRange( std::min( zMin, std::numeric_limits<double>::max() ), std::max( zMax, std::numeric_limits<double>::lowest() ) );
1471 return zRange.isEmpty() ? QgsDoubleRange() : zRange;
1472}
1473
1474QMap<QString, Qgs3DMapScene *> Qgs3DMapScene::openScenes()
1475{
1476 return sOpenScenesFunction();
1477}
1478
1479void Qgs3DMapScene::addCameraRotationCenterEntity( QgsCameraController *controller )
1480{
1481 mEntityRotationCenter = new Qt3DCore::QEntity;
1482
1483 Qt3DCore::QTransform *trRotationCenter = new Qt3DCore::QTransform;
1484 mEntityRotationCenter->addComponent( trRotationCenter );
1485
1486 auto materialRotationCenter = new QgsUnlitMaterial();
1487 materialRotationCenter->setColor( Qt::blue );
1488 materialRotationCenter->setCastsShadows( false );
1489
1490 mEntityRotationCenter->addComponent( materialRotationCenter );
1491 Qt3DExtras::QSphereMesh *rendererRotationCenter = new Qt3DExtras::QSphereMesh;
1492 rendererRotationCenter->setRadius( 10 );
1493 mEntityRotationCenter->addComponent( rendererRotationCenter );
1494 mEntityRotationCenter->setEnabled( false );
1495 mEntityRotationCenter->setParent( this );
1496
1497 QgsFrameGraph *frameGraph = mEngine->frameGraph();
1498 mEntityRotationCenter->addComponent( frameGraph->forwardRenderView().renderLayer() );
1499
1500 connect( controller, &QgsCameraController::cameraRotationCenterChanged, this, [trRotationCenter]( QVector3D center ) { trRotationCenter->setTranslation( center ); } );
1501
1502 connect( &mMap, &Qgs3DMapSettings::showCameraRotationCenterChanged, this, [this] {
1503 mEntityRotationCenter->setEnabled( mMap.debugFlags().testFlag( Qgis::Map3DDebugFlag::ShowCameraRotationCenter ) );
1504 } );
1505}
1506
1507void Qgs3DMapScene::on3DAxisSettingsChanged()
1508{
1509 if ( m3DAxis )
1510 {
1511 m3DAxis->onAxisSettingsChanged();
1512 }
1513 else
1514 {
1515 if ( QgsWindow3DEngine *engine = dynamic_cast<QgsWindow3DEngine *>( mEngine ) )
1516 {
1517 m3DAxis = new Qgs3DAxis( static_cast<Qgs3DMapCanvas *>( engine->window() ), engine->root(), this, mCameraController, &mMap );
1518 }
1519 }
1520}
1521
1522void Qgs3DMapScene::onOriginChanged()
1523{
1524 const QList<QgsGeoTransform *> geoTransforms = findChildren<QgsGeoTransform *>();
1525 for ( QgsGeoTransform *transform : geoTransforms )
1526 {
1527 transform->setOrigin( mMap.origin() );
1528 }
1529
1530 const QList<QgsGeoTransform *> rubberBandGeoTransforms = mEngine->frameGraph()->rubberBandsRootEntity()->findChildren<QgsGeoTransform *>();
1531 for ( QgsGeoTransform *transform : rubberBandGeoTransforms )
1532 {
1533 transform->setOrigin( mMap.origin() );
1534 }
1535
1536 const QgsVector3D oldOrigin = mCameraController->origin();
1537 mCameraController->setOrigin( mMap.origin() );
1538
1539 if ( !mClipPlanesEquations.isEmpty() )
1540 {
1541 // how the math works - for a plane defined as (a,b,c,d), only "d" changes when
1542 // moving the origin - the plane normal vector (a,b,c) stays the same.
1543 // - line equation for old shift: a * (x - x0) + b * (y - y0) + c * (z - z0) + d0 = 0
1544 // - line equation for new shift: a * (x - x1) + b * (y - y1) + c * (z - z1) + d1 = 0
1545 // - we solve for d1:
1546 // d1 = a * (x1 - x0) + b * (y1 - y0) + c * (z1 - z0) + d0
1547
1548 QList<QVector4D> newPlanes;
1549 QgsVector3D originShift = mMap.origin() - oldOrigin;
1550 for ( QVector4D plane : std::as_const( mClipPlanesEquations ) )
1551 {
1552 plane.setW( originShift.x() * plane.x() + originShift.y() * plane.y() + originShift.z() * plane.z() + plane.w() );
1553 newPlanes.append( plane );
1554 }
1555 enableClipping( newPlanes );
1556 }
1557}
1558
1559void Qgs3DMapScene::handleClippingOnEntity( QEntity *entity ) const
1560{
1561 if ( mClipPlanesEquations.isEmpty() ) // no clip plane equations, disable clipping
1562 {
1563 for ( QgsMaterial *material : entity->componentsOfType<QgsMaterial>() )
1564 {
1565 material->disableClipping();
1566 }
1567 }
1568 else // enable clipping
1569 {
1570 for ( QgsMaterial *material : entity->componentsOfType<QgsMaterial>() )
1571 {
1572 material->enableClipping( mClipPlanesEquations );
1573 }
1574 }
1575
1576 // recursive call
1577 // enable or disable clipping on the children accordingly
1578 for ( QObject *child : entity->children() )
1579 {
1580 Qt3DCore::QEntity *childEntity = qobject_cast<Qt3DCore::QEntity *>( child );
1581 if ( childEntity )
1582 {
1583 handleClippingOnEntity( childEntity );
1584 }
1585 }
1586}
1587
1588void Qgs3DMapScene::handleClippingOnAllEntities() const
1589{
1590 // Need to loop mLayerEntities instead of mSceneEntities to handle entities
1591 // which do no inherit from Qgs3DMapSceneEntity. For example, mesh entities.
1592 for ( auto it = mLayerEntities.constBegin(); it != mLayerEntities.constEnd(); ++it )
1593 {
1594 handleClippingOnEntity( it.value() );
1595 }
1596 if ( mTerrain )
1597 {
1598 handleClippingOnEntity( mTerrain );
1599 }
1600 if ( mGlobe )
1601 {
1602 handleClippingOnEntity( mGlobe );
1603 }
1604}
1605
1607{
1608 if ( clipPlaneEquations.size() > mMaxClipPlanes )
1609 {
1610 QgsDebugMsgLevel( u"Qgs3DMapScene::enableClipping: it is not possible to use more than %1 clipping planes."_s.arg( mMaxClipPlanes ), 2 );
1611 }
1612 mClipPlanesEquations = clipPlaneEquations.mid( 0, mMaxClipPlanes );
1613
1614 // enable the clip planes on the framegraph
1615 mEngine->frameGraph()->addClipPlanes( clipPlaneEquations.size() );
1616
1617 // Enable the clip planes for the material of each entity.
1618 handleClippingOnAllEntities();
1619}
1620
1622{
1623 mClipPlanesEquations.clear();
1624
1625 // disable the clip planes on the framegraph
1626 mEngine->frameGraph()->removeClipPlanes();
1627
1628 // Disable the clip planes for the material of each entity.
1629 handleClippingOnAllEntities();
1630}
1631
1632void Qgs3DMapScene::onStopUpdatesChanged()
1633{
1634 mSceneUpdatesEnabled = !mMap.stopUpdates();
1635}
1636
1637void Qgs3DMapScene::schedule2DMapOverlayUpdate()
1638{
1639 // Start the overlay update timer if overlay is active and not already running
1640 if ( mMap.is2DMapOverlayEnabled() && mOverlayUpdateTimer && !mOverlayUpdateTimer->isActive() )
1641 {
1642 mOverlayUpdateTimer->start();
1643 }
1644}
1645
1646void Qgs3DMapScene::applyPendingOverlayUpdate()
1647{
1648 if ( mMap.is2DMapOverlayEnabled() )
1649 {
1650 const QVector<QgsPointXY> extent2D = viewFrustum2DExtent();
1651 update2DMapOverlay( extent2D );
1652 }
1653}
@ DistinctTextureSkybox
Skybox with 6 distinct textures for different faces.
Definition qgis.h:4465
@ FixedGradientBackground
Two color gradient, fixed in place.
Definition qgis.h:4464
@ Model
Model.
Definition qgis.h:4374
@ ShowCameraViewCenter
Shows the camera's view center as a sphere.
Definition qgis.h:4336
@ ShowFPS
Shows the frames per second (FPS).
Definition qgis.h:4339
@ ShowCameraRotationCenter
Shows the camera's rotation center as a sphere.
Definition qgis.h:4337
@ ShowTerrainBoundingBoxes
Displays bounding boxes of terrain tiles.
Definition qgis.h:4334
@ Point
Points.
Definition qgis.h:380
@ PlainText
Text message.
Definition qgis.h:176
LayerType
Types of layers that can be added to a map.
Definition qgis.h:206
@ Group
Composite group layer. Added in QGIS 3.24.
Definition qgis.h:214
@ Plugin
Plugin based layer.
Definition qgis.h:209
@ TiledScene
Tiled scene layer. Added in QGIS 3.34.
Definition qgis.h:215
@ Annotation
Contains freeform, georeferenced annotations. Added in QGIS 3.16.
Definition qgis.h:212
@ Vector
Vector layer.
Definition qgis.h:207
@ VectorTile
Vector tile layer. Added in QGIS 3.14.
Definition qgis.h:211
@ Mesh
Mesh layer. Added in QGIS 3.2.
Definition qgis.h:210
@ Raster
Raster layer.
Definition qgis.h:208
@ PointCloud
Point cloud layer. Added in QGIS 3.18.
Definition qgis.h:213
@ Orthographic
Orthogonal projection.
Definition qgis.h:4353
@ Perspective
Perspective projection.
Definition qgis.h:4354
@ Globe
Scene is represented as a globe using a geocentric CRS.
Definition qgis.h:4505
@ Local
Local scene based on a projected CRS.
Definition qgis.h:4504
Manages the various settings the user can choose from when exporting a 3D scene.
bool exportNormals() const
Returns whether normals will be exported.
int terrrainResolution() const
Returns the terrain resolution.
QString sceneFolderPath() const
Returns the scene folder path.
float scale() const
Returns the scale of the exported model.
int terrainTextureResolution() const
Returns the terrain texture resolution.
bool terrainExportEnabled() const
Returns whether terrain export is enabled.
QString sceneName() const
Returns the scene name.
bool smoothEdges() const
Returns whether triangles edges will look smooth.
bool exportTextures() const
Returns whether textures will be exported.
Qgis::Export3DSceneFormat exportFormat() const
Returns the export format for the 3D scene.
void viewed2DExtentFrom3DChanged(QVector< QgsPointXY > extent)
Emitted when the viewed 2D extent seen by the 3D camera has changed.
QList< QVector4D > clipPlaneEquations() const
Returns list of clipping planes if clipping is enabled, otherwise an empty list.
static std::function< QMap< QString, Qgs3DMapScene * >()> sOpenScenesFunction
Static function for returning open 3D map scenes.
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).
void disableClipping()
Disables OpenGL clipping.
QVector< const QgsChunkNode * > getLayerActiveChunkNodes(QgsMapLayer *layer) SIP_SKIP
Returns the active chunk nodes of layer.
void gpuMemoryLimitReached()
Emitted when one of the entities reaches its GPU memory limit and it is not possible to lower the GPU...
static Q_DECL_DEPRECATED QMap< QString, Qgs3DMapScene * > openScenes() SIP_DEPRECATED
Returns a map of 3D map scenes (by name) open in the QGIS application.
QgsCameraController * cameraController() const
Returns camera controller.
SceneState
Enumeration of possible states of the 3D scene.
@ Ready
The scene is fully loaded/updated.
@ Updating
The scene is still being loaded/updated.
bool exportScene(const Qgs3DMapExportSettings &exportSettings)
Exports the scene according to the scene export settings Returns false if the operation failed.
int totalPendingJobsCount() const
Returns number of pending jobs for all chunked entities.
QList< QgsMapLayer * > layers() const SIP_SKIP
Returns the layers that contain chunked entities.
void addSceneEntity(Qgs3DMapSceneEntity *entity) SIP_SKIP
Adds a 3D map scene entity to the scene.
void updateTemporal()
Updates the temporale entities.
void totalPendingJobsCountChanged()
Emitted when the total number of pending jobs changes.
Qgs3DMapScene(Qgs3DMapSettings &map, QgsAbstract3DEngine *engine) SIP_SKIP
Constructs a 3D scene based on map settings and Qt 3D renderer configuration.
void fpsCounterEnabledChanged(bool fpsCounterEnabled)
Emitted when the FPS counter is activated or deactivated.
void removeSceneEntity(Qgs3DMapSceneEntity *entity) SIP_SKIP
Removes a 3D scene entity for the scene.
QgsDoubleRange elevationRange(bool ignoreTerrain=false) const
Returns the scene's elevation range.
QgsRectangle sceneExtent() const
Returns the scene extent in the map's CRS.
void sceneStateChanged()
Emitted when the scene's state has changed.
QgsAbstract3DEngine * engine() const SIP_SKIP
Returns the abstract 3D engine.
QVector< QgsPointXY > viewFrustum2DExtent() const
Calculates the 2D extent viewed by the 3D camera as the vertices of the viewed trapezoid.
void enableClipping(const QList< QVector4D > &clipPlaneEquations)
Enables OpenGL clipping based on the planes equations defined in clipPlaneEquations.
void terrainEntityChanged()
Emitted when the current terrain entity is replaced by a new one.
void viewZoomFull()
Resets camera view to show the whole scene (top view).
double worldSpaceError(double epsilon, double distance) const
Given screen error (in pixels) and distance from camera (in 3D world coordinates),...
Definition of the world.
void extentChanged()
Emitted when the 3d view's 2d extent has changed.
void originChanged()
Emitted when the world's origin point has been shifted.
void eyeDomeLightingDistanceChanged()
Emitted when the eye dome lighting distance has changed.
void terrainShadingChanged()
Emitted when terrain shading enabled flag or terrain shading material has changed.
void bloomSettingsChanged()
Emitted when the bloom lighting effect settings are changed.
void debugDepthMapSettingsChanged()
Emitted when depth map debugging has changed.
void backgroundSettingsChanged()
Emitted when background settings are changed.
void backgroundColorChanged()
Emitted when the background color has changed.
void showCameraRotationCenterChanged()
Emitted when the flag whether camera's rotation center is shown has changed.
void cameraNavigationModeChanged()
Emitted when the camera navigation mode was changed.
void shadowSettingsChanged()
Emitted when shadow rendering settings are changed.
void show2DMapOverlayChanged()
Emitted when the 2D map overlay is enabled or disabled.
bool stopUpdates() const
Returns whether the scene updates on camera movement.
void eyeDomeLightingEnabledChanged()
Emitted when the flag whether eye dome lighting is used has changed.
void debugOverlayEnabledChanged(bool debugOverlayEnabled)
Emitted when the debug overaly is enabled or disabled.
void setOrigin(const QgsVector3D &origin)
Sets coordinates in map CRS at which our 3D world has origin (0,0,0).
void msaaEnabledChanged()
Emitted when the MSAA enabled flag has changed.
void projectionTypeChanged()
Emitted when the camera lens projection type changes.
void stopUpdatesChanged()
Emitted when the flag whether to keep updating scene has changed.
void lightSourcesChanged()
Emitted when any of the light source settings in the map changes.
void showLightSourceOriginsChanged()
Emitted when the flag whether light source origins are shown has changed.
void terrainSettingsChanged()
Emitted when the terrain settings are changed.
void colorGradingSettingsChanged()
Emitted when the color grading settings are changed.
void fpsCounterEnabledChanged(bool fpsCounterEnabled)
Emitted when the FPS counter is enabled or disabled.
void axisSettingsChanged()
Emitted when 3d axis rendering settings are changed.
void viewFrustumVisualizationEnabledChanged()
Emitted when the camera's view frustum visualization on the main 2D map canvas is enabled or disabled...
void ambientOcclusionSettingsChanged()
Emitted when ambient occlusion rendering settings are changed.
void layersChanged()
Emitted when the list of map layers for 3d rendering has changed.
void eyeDomeLightingStrengthChanged()
Emitted when the eye dome lighting strength has changed.
void cameraMovementSpeedChanged()
Emitted when the camera movement speed was changed.
void fieldOfViewChanged()
Emitted when the camera lens field of view changes.
void terrainGeneratorChanged()
Emitted when the terrain generator has changed.
void showCameraViewCenterChanged()
Emitted when the flag whether camera's view center is shown has changed.
QgsVector3D origin() const
Returns coordinates in map CRS at which 3D scene has origin (0,0,0).
Entity that handles the exporting of 3D scenes.
void setExportTextures(bool exportTextures)
Sets whether the textures will be exported.
void parseTerrain(QgsTerrainEntity *terrain, const QString &layer)
Creates terrain export objects from the terrain entity.
void setTerrainResolution(int resolution)
Sets the terrain resolution.
void setTerrainTextureResolution(int resolution)
Sets the terrain texture resolution.
bool parseVectorLayerEntity(Qt3DCore::QEntity *entity, QgsVectorLayer *layer)
Creates necessary export objects from entity if it represents valid vector layer entity Returns false...
void setScale(float scale)
Sets the scale of the exported 3D model.
bool save(QString sceneName, QString sceneFolderPath, const Qgis::Export3DSceneFormat &exportFormat=Qgis::Export3DSceneFormat::Obj, int precision=6) const
Saves the scene to a file Returns false if the operation failed.
void setExportNormals(bool exportNormals)
Sets whether the normals will be exported.
void setSmoothEdges(bool smoothEdges)
Sets whether the triangles will look smooth.
void setTerrainExportEnabled(bool enabled)
Sets whether terrain export is enabled.
static int maxZoomLevel(double tile0width, double tileResolution, double maxError)
Calculates the highest needed zoom level for tiles in quad-tree given width of the base tile (zoom le...
static QgsAABB mapToWorldExtent(const QgsRectangle &extent, double zMin, double zMax, const QgsVector3D &mapOrigin)
Converts map extent to axis aligned bounding box in 3D world coordinates.
static void computeBoundingBoxNearFarPlanes(const QgsAABB &bbox, const QMatrix4x4 &viewMatrix, float &fnear, float &ffar)
This routine computes nearPlane farPlane from the closest and farthest corners point of bounding box ...
static QVector3D screenPointToWorldPos(const QPoint &screenPoint, double depth, const QSize &screenSize, Qt3DRender::QCamera *camera)
Converts the clicked mouse position to the corresponding 3D world coordinates.
static int openGlMaxClipPlanes(QSurface *surface)
Gets the maximum number of clip planes that can be used.
Base class for 3D engine implementation.
void sizeChanged()
Emitted after a call to setSize().
virtual Qt3DRender::QCamera * camera()=0
Returns pointer to the engine's camera entity.
QgsFrameGraph * frameGraph()
Returns the shadow rendering frame graph object used to render the scene.
virtual Qgis::Map3DBackgroundType type() const =0
Returns the unique type for this background settings class.
Base class for all renderers that participate in 3D views.
virtual QString type() const =0
Returns unique identifier of the renderer class (used to identify subclass).
virtual Qt3DCore::QEntity * createEntity(Qgs3DMapSettings *map) const =0
Returns a 3D entity that will be used to show renderer's data in 3D scene.
virtual void setEnabled(bool enable)
Enable or disable via enable the render view sub tree.
Base class for 3D renderers that are based on vector layers.
static QgsSourceCache * sourceCache()
Returns the application's source cache, used for caching embedded and remote source strings as local ...
A 3-dimensional box composed of x, y, z coordinates.
Definition qgsbox3d.h:45
double zMaximum() const
Returns the maximum z value.
Definition qgsbox3d.h:268
double zMinimum() const
Returns the minimum z value.
Definition qgsbox3d.h:261
Object that controls camera movement based on user input.
Qt3DRender::QCamera * camera() const
Returns camera that is being controlled.
void requestDepthBufferCapture()
Emitted to ask for the depth buffer image.
void cameraChanged()
Emitted when camera has been updated.
void depthBufferReady()
Emitted after the depth buffer has been captured and is ready to sample.
void cameraRotationCenterChanged(QVector3D position)
Emitted when the camera rotation center changes.
QgsRange which stores a range of double values.
Definition qgsrange.h:217
bool isInfinite() const
Returns true if the range consists of all possible values.
Definition qgsrange.h:266
An environment light entity.
@ Disabled
No environment lighting.
QColor topColor() const
Returns the color at the top of the gradient.
QColor bottomColor() const
Returns the color at the bottom of the gradient.
Qt3DRender::QLayer * renderLayer()
Returns a layer object used to indicate that the object is transparent.
Qt3DRender::QLayer * transparentObjectLayer()
Returns a layer object used to indicate that the object is transparent.
Qt3DRender::QLayer * backgroundLayer()
Returns a layer object used for skybox and background gradient entities.
Container class that holds different objects related to frame graphs of 3D scenes.
void updateShadowSettings(const Qgs3DMapSettings &mapSettings)
Updates shadow bias, light and texture size according to shadowSettings and lightSources.
QgsHighlightsRenderView & highlightsRenderView()
Returns the highlights renderview, used for rendering highlight overlays of identified features.
QgsForwardRenderView & forwardRenderView()
Returns forward renderview.
QgsOverlayTextureRenderView & overlayTextureRenderView()
Returns overlay texture renderview.
QgsShadowRenderView & shadowRenderView()
Returns shadow renderview.
Qt3DRender::QLayer * highlightsLayer()
Returns a layer that should be attached to entities meant to be rendered by QgsHighlightsRenderView.
virtual QgsDoubleRange calculateZRange(QgsMapLayer *layer) const
Attempts to calculate the overall elevation or z range for the specified layer, using the settings de...
Base class for storage of map layer temporal properties.
Base class for all map layer types.
Definition qgsmaplayer.h:83
QString name
Definition qgsmaplayer.h:87
QgsAbstract3DRenderer * renderer3D() const
Returns 3D renderer associated with the layer.
void request3DUpdate()
Signal emitted when a layer requires an update in any 3D maps.
void renderer3DChanged()
Signal emitted when 3D renderer associated with the layer has changed.
Qgis::LayerType type
Definition qgsmaplayer.h:93
void rendererChanged()
Signal emitted when renderer is changed.
virtual QgsMapLayerTemporalProperties * temporalProperties()
Returns the layer's temporal properties.
void layerModified()
Emitted when modifications has been done on layer.
double verticalScale() const
Returns mesh vertical scale.
int verticalDatasetGroupIndex() const
Returns the index of the dataset group that will be used to render the vertical component of the 3D m...
void setMaximumTextureSize(int maximumTextureSize)
Sets the maximum texture size supported by the hardware Used to store the GL_MAX_TEXTURE_SIZE value t...
QgsMesh3DSymbol * clone() const override SIP_FACTORY
Returns a new instance of the symbol with the same settings.
A collection of dataset group metadata such as whether the data is vector or scalar,...
double minimum() const
Returns minimum scalar value/vector magnitude present for whole dataset group.
double maximum() const
Returns maximum scalar value/vector magnitude present for whole dataset group.
3D renderer that renders all mesh triangles of a mesh layer.
void setSymbol(QgsMesh3DSymbol *symbol)
Sets 3D symbol associated with the renderer.
const QgsMesh3DSymbol * symbol() const
Returns 3D symbol associated with the renderer.
void setLayer(QgsMeshLayer *layer)
Sets vector layer associated with the renderer.
Represents a mesh layer supporting display of data on structured or unstructured meshes.
QgsMeshDatasetGroupMetadata datasetGroupMetadata(const QgsMeshDatasetIndex &index) const
Returns the dataset groups metadata.
virtual void showMessage(bool blocking=true)=0
display the message to the user and deletes itself
3D symbol that draws point geometries as 3D objects using one of the predefined shapes.
Qgis::Point3DShape shape() const
Returns 3D shape for points.
QVariant shapeProperty(const QString &property) const
Returns the value for a specific shape property.
void setLayer(QgsPointCloudLayer *layer)
Sets point cloud layer associated with the renderer.
Represents a map layer supporting display of point clouds.
QgsMapLayerElevationProperties * elevationProperties() override
Returns the layer's elevation properties.
void subsetStringChanged()
Emitted when the layer's subset string has changed.
Represents a 2D point.
Definition qgspointxy.h:62
double y
Definition qgspointxy.h:66
double x
Definition qgspointxy.h:65
T lower() const
Returns the lower bound of the range.
Definition qgsrange.h:79
T upper() const
Returns the upper bound of the range.
Definition qgsrange.h:86
bool isEmpty() const
Returns true if the range is empty, ie the lower bound equals (or exceeds) the upper bound and either...
Definition qgsrange.h:126
A rectangle specified with double values.
double xMinimum
double yMinimum
double xMaximum
double yMaximum
static QgsRectangle fromCenterAndSize(const QgsPointXY &center, double width, double height)
Creates a new rectangle, given the specified center point and width and height.
QgsPointXY center
A child rule for a QgsRuleBased3DRenderer.
Rule-based 3D renderer.
QList< QgsRuleBased3DRenderer::Rule * > RuleList
Qt3DRender::QLayer * entityCastingShadowsLayer() const
Returns the layer to be used by entities to be included in this renderview.
bool environmentalLightingEnabled() const
Returns true if the skybox should generate environmental lighting effects.
QMap< QString, QString > cubeMapFacesPaths() const
Returns a map containing the path of each texture specified by the user.
double environmentalLightStrength() const
Returns the environmental light strength, as a factor between 0 and 1.
Qgis::SkyboxCubeMapping cubeMapping() const
Returns the cube face mapping scheme.
void remoteSourceFetched(const QString &url)
Emitted when the cache has finished retrieving a 3D model from a remote url.
void setLayer(QgsTiledSceneLayer *layer)
Sets tiled scene layer associated with the renderer.
Represents a map layer supporting display of tiled scene objects.
QgsMapLayerElevationProperties * elevationProperties() override
Returns the layer's elevation properties.
A 3D vector (similar to QVector3D) with the difference that it uses double precision instead of singl...
Definition qgsvector3d.h:33
double y() const
Returns Y coordinate.
Definition qgsvector3d.h:60
double z() const
Returns Z coordinate.
Definition qgsvector3d.h:62
QString toString(int precision=17) const
Returns a string representation of the 3D vector.
double x() const
Returns X coordinate.
Definition qgsvector3d.h:58
3D renderer that renders all features of a vector layer with the same 3D symbol.
const QgsAbstract3DSymbol * symbol() const
Returns 3D symbol associated with the renderer.
Represents a vector layer which manages a vector based dataset.
void subsetStringChanged()
Emitted when the layer's subset string has changed.
Q_INVOKABLE Qgis::GeometryType geometryType() const
Returns point, line or polygon.
void selectionChanged(const QgsFeatureIds &selected, const QgsFeatureIds &deselected, bool clearAndSelect)
Emitted when selection was changed.
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
bool qgsFloatNear(float a, float b, float epsilon=4 *FLT_EPSILON)
Compare two floats (but allow some difference).
Definition qgis.h:7446
QList< Qgs3DRendererCategory > Qgs3DCategoryList
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:80
#define QgsDebugError(str)
Definition qgslogger.h:71
constexpr QObjectUniquePtr< Tp > make_qobject_unique(Args &&...args)
Create an object owned by a QObjectUniquePtr.