QGIS API Documentation  3.22.4-Białowieża (ce8e65e95e)
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 <Qt3DRender/QCamera>
19 #include <Qt3DRender/QMesh>
20 #include <Qt3DRender/QObjectPicker>
21 #include <Qt3DRender/QPickEvent>
22 #include <Qt3DRender/QPickingSettings>
23 #include <Qt3DRender/QPickTriangleEvent>
24 #include <Qt3DRender/QPointLight>
25 #include <Qt3DRender/QDirectionalLight>
26 #include <Qt3DRender/QRenderSettings>
27 #include <Qt3DRender/QSceneLoader>
28 #include <Qt3DExtras/QForwardRenderer>
29 #include <Qt3DExtras/QPhongMaterial>
30 #include <Qt3DExtras/QSphereMesh>
31 #include <Qt3DLogic/QFrameAction>
32 #include <Qt3DRender/QEffect>
33 #include <Qt3DRender/QTechnique>
34 #include <Qt3DRender/QRenderPass>
35 #include <Qt3DRender/QRenderState>
36 #include <Qt3DRender/QCullFace>
37 #include <Qt3DRender/QDepthTest>
38 #include <QSurface>
39 #include <QUrl>
40 #include <QtMath>
41 
42 #include <QOpenGLContext>
43 #include <QOpenGLFunctions>
44 #include <QTimer>
45 
46 #include "qgslogger.h"
47 #include "qgsapplication.h"
48 #include "qgsaabb.h"
49 #include "qgsabstract3dengine.h"
51 #include "qgs3dmapsettings.h"
52 #include "qgs3dutils.h"
53 #include "qgsabstract3drenderer.h"
54 #include "qgscameracontroller.h"
55 #include "qgschunkedentity_p.h"
56 #include "qgschunknode_p.h"
57 #include "qgseventtracing.h"
58 #include "qgsmeshlayer.h"
59 #include "qgsmeshlayer3drenderer.h"
60 #include "qgspoint3dsymbol.h"
61 #include "qgsrulebased3drenderer.h"
62 #include "qgspointcloudlayer.h"
64 #include "qgssourcecache.h"
65 #include "qgsterrainentity_p.h"
66 #include "qgsterraingenerator.h"
68 #include "qgsvectorlayer.h"
72 
73 #include "qgslinematerial_p.h"
74 #include "qgs3dsceneexporter.h"
75 #include "qgsabstract3drenderer.h"
76 #include "qgs3dmapexportsettings.h"
77 #include "qgsmessageoutput.h"
78 
79 #include "qgsskyboxentity.h"
80 #include "qgsskyboxsettings.h"
81 
82 #include "qgswindow3dengine.h"
84 #include "qgspointcloudlayer.h"
86 
88  : mMap( map )
89  , mEngine( engine )
90 {
91 
92  connect( &map, &Qgs3DMapSettings::backgroundColorChanged, this, &Qgs3DMapScene::onBackgroundColorChanged );
93  onBackgroundColorChanged();
94 
95  // The default render policy in Qt3D is "Always" - i.e. the 3D map scene gets refreshed up to 60 fps
96  // even if there's no change. Switching to "on demand" should only re-render when something has changed
97  // and we save quite a lot of resources
98  mEngine->renderSettings()->setRenderPolicy( Qt3DRender::QRenderSettings::OnDemand );
99 
100  // we want precise picking of terrain (also bounding volume picking does not seem to work - not sure why)
101  mEngine->renderSettings()->pickingSettings()->setPickMethod( Qt3DRender::QPickingSettings::TrianglePicking );
102 
103  QRect viewportRect( QPoint( 0, 0 ), mEngine->size() );
104 
105  // Camera
106  float aspectRatio = ( float )viewportRect.width() / viewportRect.height();
107  mEngine->camera()->lens()->setPerspectiveProjection( mMap.fieldOfView(), aspectRatio, 10.f, 10000.0f );
108 
109  mFrameAction = new Qt3DLogic::QFrameAction();
110  connect( mFrameAction, &Qt3DLogic::QFrameAction::triggered,
111  this, &Qgs3DMapScene::onFrameTriggered );
112  addComponent( mFrameAction ); // takes ownership
113 
114  // Camera controlling
115  mCameraController = new QgsCameraController( this ); // attaches to the scene
116  mCameraController->setViewport( viewportRect );
117  mCameraController->setCamera( mEngine->camera() );
118  mCameraController->resetView( 1000 );
119 
120  addCameraViewCenterEntity( mEngine->camera() );
121  updateLights();
122 
123  // create terrain entity
124 
125  createTerrainDeferred();
126  connect( &map, &Qgs3DMapSettings::terrainGeneratorChanged, this, &Qgs3DMapScene::createTerrain );
127  connect( &map, &Qgs3DMapSettings::terrainVerticalScaleChanged, this, &Qgs3DMapScene::createTerrain );
128  connect( &map, &Qgs3DMapSettings::mapTileResolutionChanged, this, &Qgs3DMapScene::createTerrain );
129  connect( &map, &Qgs3DMapSettings::maxTerrainScreenErrorChanged, this, &Qgs3DMapScene::createTerrain );
130  connect( &map, &Qgs3DMapSettings::maxTerrainGroundErrorChanged, this, &Qgs3DMapScene::createTerrain );
131  connect( &map, &Qgs3DMapSettings::terrainShadingChanged, this, &Qgs3DMapScene::createTerrain );
132  connect( &map, &Qgs3DMapSettings::pointLightsChanged, this, &Qgs3DMapScene::updateLights );
133  connect( &map, &Qgs3DMapSettings::directionalLightsChanged, this, &Qgs3DMapScene::updateLights );
134  connect( &map, &Qgs3DMapSettings::showLightSourceOriginsChanged, this, &Qgs3DMapScene::updateLights );
135  connect( &map, &Qgs3DMapSettings::fieldOfViewChanged, this, &Qgs3DMapScene::updateCameraLens );
136  connect( &map, &Qgs3DMapSettings::projectionTypeChanged, this, &Qgs3DMapScene::updateCameraLens );
137  connect( &map, &Qgs3DMapSettings::renderersChanged, this, &Qgs3DMapScene::onRenderersChanged );
138  connect( &map, &Qgs3DMapSettings::skyboxSettingsChanged, this, &Qgs3DMapScene::onSkyboxSettingsChanged );
139  connect( &map, &Qgs3DMapSettings::shadowSettingsChanged, this, &Qgs3DMapScene::onShadowSettingsChanged );
140  connect( &map, &Qgs3DMapSettings::eyeDomeLightingEnabledChanged, this, &Qgs3DMapScene::onEyeDomeShadingSettingsChanged );
141  connect( &map, &Qgs3DMapSettings::eyeDomeLightingStrengthChanged, this, &Qgs3DMapScene::onEyeDomeShadingSettingsChanged );
142  connect( &map, &Qgs3DMapSettings::eyeDomeLightingDistanceChanged, this, &Qgs3DMapScene::onEyeDomeShadingSettingsChanged );
143  connect( &map, &Qgs3DMapSettings::debugShadowMapSettingsChanged, this, &Qgs3DMapScene::onDebugShadowMapSettingsChanged );
144  connect( &map, &Qgs3DMapSettings::debugDepthMapSettingsChanged, this, &Qgs3DMapScene::onDebugDepthMapSettingsChanged );
146  connect( &map, &Qgs3DMapSettings::cameraMovementSpeedChanged, this, &Qgs3DMapScene::onCameraMovementSpeedChanged );
147 
148  connect( QgsApplication::instance()->sourceCache(), &QgsSourceCache::remoteSourceFetched, this, [ = ]( const QString & url )
149  {
150  const QList<QgsMapLayer *> modelVectorLayers = mModelVectorLayers;
151  for ( QgsMapLayer *layer : modelVectorLayers )
152  {
153  QgsAbstract3DRenderer *renderer = layer->renderer3D();
154  if ( renderer )
155  {
156  if ( renderer->type() == QLatin1String( "vector" ) )
157  {
158  const QgsPoint3DSymbol *pointSymbol = static_cast< const QgsPoint3DSymbol * >( static_cast< QgsVectorLayer3DRenderer *>( renderer )->symbol() );
159  if ( pointSymbol->shapeProperties()[QStringLiteral( "model" )].toString() == url )
160  {
161  removeLayerEntity( layer );
162  addLayerEntity( layer );
163  }
164  }
165  else if ( renderer->type() == QLatin1String( "rulebased" ) )
166  {
167  const QgsRuleBased3DRenderer::RuleList rules = static_cast< QgsRuleBased3DRenderer *>( renderer )->rootRule()->descendants();
168  for ( auto rule : rules )
169  {
170  const QgsPoint3DSymbol *pointSymbol = dynamic_cast< const QgsPoint3DSymbol * >( rule->symbol() );
171  if ( pointSymbol->shapeProperties()[QStringLiteral( "model" )].toString() == url )
172  {
173  removeLayerEntity( layer );
174  addLayerEntity( layer );
175  break;
176  }
177  }
178  }
179  }
180  }
181  } );
182 
183  // create entities of renderers
184 
185  onRenderersChanged();
186 
187  // listen to changes of layers in order to add/remove 3D renderer entities
188  connect( &map, &Qgs3DMapSettings::layersChanged, this, &Qgs3DMapScene::onLayersChanged );
189 
190 
191 #if 0
192  ChunkedEntity *testChunkEntity = new ChunkedEntity( AABB( -500, 0, -500, 500, 100, 500 ), 2.f, 3.f, 7, new TestChunkLoaderFactory );
193  testChunkEntity->setEnabled( false );
194  testChunkEntity->setParent( this );
195  chunkEntities << testChunkEntity;
196 #endif
197 
198  connect( mCameraController, &QgsCameraController::cameraChanged, this, &Qgs3DMapScene::onCameraChanged );
199  connect( mCameraController, &QgsCameraController::viewportChanged, this, &Qgs3DMapScene::onCameraChanged );
200 
201 #if 0
202  // experiments with loading of existing 3D models.
203 
204  // scene loader only gets loaded only when added to a scene...
205  // it loads everything: geometries, materials, transforms, lights, cameras (if any)
206  Qt3DCore::QEntity *loaderEntity = new Qt3DCore::QEntity;
207  Qt3DRender::QSceneLoader *loader = new Qt3DRender::QSceneLoader;
208  loader->setSource( QUrl( "file:///home/martin/Downloads/LowPolyModels/tree.dae" ) );
209  loaderEntity->addComponent( loader );
210  loaderEntity->setParent( this );
211 
212  // mesh loads just geometry as one geometry...
213  // so if there are different materials (e.g. colors) used in the model, this information is lost
214  Qt3DCore::QEntity *meshEntity = new Qt3DCore::QEntity;
215  Qt3DRender::QMesh *mesh = new Qt3DRender::QMesh;
216  mesh->setSource( QUrl( "file:///home/martin/Downloads/LowPolyModels/tree.obj" ) );
217  meshEntity->addComponent( mesh );
218  Qt3DExtras::QPhongMaterial *material = new Qt3DExtras::QPhongMaterial;
219  material->setAmbient( Qt::red );
220  meshEntity->addComponent( material );
221  Qt3DCore::QTransform *meshTransform = new Qt3DCore::QTransform;
222  meshTransform->setScale( 1 );
223  meshEntity->addComponent( meshTransform );
224  meshEntity->setParent( this );
225 #endif
226  onSkyboxSettingsChanged();
227 
228  // force initial update of chunked entities
229  onCameraChanged();
230  // force initial update of eye dome shading
231  onEyeDomeShadingSettingsChanged();
232  // force initial update of debugging setting of preview quads
233  onDebugShadowMapSettingsChanged();
234  onDebugDepthMapSettingsChanged();
235 
236  mCameraController->setCameraNavigationMode( mMap.cameraNavigationMode() );
237  onCameraMovementSpeedChanged();
238 }
239 
241 {
242  QgsRectangle extent = sceneExtent();
243  float side = std::max( extent.width(), extent.height() );
244  float a = side / 2.0f / std::sin( qDegreesToRadians( cameraController()->camera()->fieldOfView() ) / 2.0f );
245  // Note: the 1.5 multiplication is to move the view upwards to look better
246  mCameraController->resetView( 1.5 * std::sqrt( a * a - side * side ) ); // assuming FOV being 45 degrees
247 }
248 
250 {
251  return mTerrain ? mTerrain->pendingJobsCount() : 0;
252 }
253 
255 {
256  int count = 0;
257  for ( QgsChunkedEntity *entity : std::as_const( mChunkEntities ) )
258  count += entity->pendingJobsCount();
259  return count;
260 }
261 
263 {
264  if ( mPickHandlers.isEmpty() )
265  {
266  // we need to add object pickers
267  for ( Qt3DCore::QEntity *entity : mLayerEntities.values() )
268  {
269  if ( QgsChunkedEntity *chunkedEntity = qobject_cast<QgsChunkedEntity *>( entity ) )
270  chunkedEntity->setPickingEnabled( true );
271  }
272  }
273 
274  mPickHandlers.append( pickHandler );
275 }
276 
278 {
279  mPickHandlers.removeOne( pickHandler );
280 
281  if ( mPickHandlers.isEmpty() )
282  {
283  // we need to remove pickers
284  for ( Qt3DCore::QEntity *entity : mLayerEntities.values() )
285  {
286  if ( QgsChunkedEntity *chunkedEntity = qobject_cast<QgsChunkedEntity *>( entity ) )
287  chunkedEntity->setPickingEnabled( false );
288  }
289  }
290 }
291 
292 void Qgs3DMapScene::onLayerEntityPickedObject( Qt3DRender::QPickEvent *pickEvent, QgsFeatureId fid )
293 {
294  QgsMapLayer *layer = mLayerEntities.key( qobject_cast<QgsChunkedEntity *>( sender() ) );
295  if ( !layer )
296  return;
297 
298  QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
299  if ( !vlayer )
300  return;
301 
302  for ( Qgs3DMapScenePickHandler *pickHandler : std::as_const( mPickHandlers ) )
303  {
304  pickHandler->handlePickOnVectorLayer( vlayer, fid, pickEvent->worldIntersection(), pickEvent );
305  }
306 }
307 
308 float Qgs3DMapScene::worldSpaceError( float epsilon, float distance )
309 {
310  Qt3DRender::QCamera *camera = mCameraController->camera();
311  float fov = camera->fieldOfView();
312  QRect rect = mCameraController->viewport();
313  float screenSizePx = std::max( rect.width(), rect.height() ); // TODO: is this correct?
314 
315  // in qgschunkedentity_p.cpp there is inverse calculation (world space error to screen space error)
316  // with explanation of the math.
317  float frustumWidthAtDistance = 2 * distance * tan( fov / 2 );
318  float err = frustumWidthAtDistance * epsilon / screenSizePx;
319  return err;
320 }
321 
322 QgsChunkedEntity::SceneState _sceneState( QgsCameraController *cameraController )
323 {
324  Qt3DRender::QCamera *camera = cameraController->camera();
325  QgsChunkedEntity::SceneState state;
326  state.cameraFov = camera->fieldOfView();
327  state.cameraPos = camera->position();
328  QRect rect = cameraController->viewport();
329  state.screenSizePx = std::max( rect.width(), rect.height() ); // TODO: is this correct?
330  state.viewProjectionMatrix = camera->projectionMatrix() * camera->viewMatrix();
331  return state;
332 }
333 
334 void Qgs3DMapScene::onCameraChanged()
335 {
336  if ( mMap.projectionType() == Qt3DRender::QCameraLens::OrthographicProjection )
337  {
338  QRect viewportRect( QPoint( 0, 0 ), mEngine->size() );
339  const float viewWidthFromCenter = mCameraController->distance();
340  const float viewHeightFromCenter = viewportRect.height() * viewWidthFromCenter / viewportRect.width();
341  mEngine->camera()->lens()->setOrthographicProjection( -viewWidthFromCenter, viewWidthFromCenter, -viewHeightFromCenter, viewHeightFromCenter, mEngine->camera()->nearPlane(), mEngine->camera()->farPlane() );
342  }
343 
344  updateScene();
345  bool changedCameraPlanes = updateCameraNearFarPlanes();
346 
347  if ( changedCameraPlanes )
348  {
349  // repeat update of entities - because we have updated camera's near/far planes,
350  // the active nodes may have changed as well
351  updateScene();
352  updateCameraNearFarPlanes();
353  }
354 
355  onShadowSettingsChanged();
356 }
357 
358 void removeQLayerComponentsFromHierarchy( Qt3DCore::QEntity *entity )
359 {
360  QVector<Qt3DCore::QComponent *> toBeRemovedComponents;
361  for ( Qt3DCore::QComponent *component : entity->components() )
362  {
363  Qt3DRender::QLayer *layer = qobject_cast<Qt3DRender::QLayer *>( component );
364  if ( layer != nullptr )
365  toBeRemovedComponents.push_back( layer );
366  }
367  for ( Qt3DCore::QComponent *component : toBeRemovedComponents )
368  entity->removeComponent( component );
369  for ( Qt3DCore::QEntity *obj : entity->findChildren<Qt3DCore::QEntity *>() )
370  {
371  if ( obj != nullptr )
373  }
374 }
375 
376 void addQLayerComponentsToHierarchy( Qt3DCore::QEntity *entity, const QVector<Qt3DRender::QLayer *> layers )
377 {
378  for ( Qt3DRender::QLayer *layer : layers )
379  entity->addComponent( layer );
380  for ( Qt3DCore::QEntity *child : entity->findChildren<Qt3DCore::QEntity *>() )
381  {
382  if ( child != nullptr )
383  addQLayerComponentsToHierarchy( child, layers );
384  }
385 }
386 
387 void Qgs3DMapScene::updateScene()
388 {
389  QgsEventTracing::addEvent( QgsEventTracing::Instant, QStringLiteral( "3D" ), QStringLiteral( "Update Scene" ) );
390  for ( QgsChunkedEntity *entity : std::as_const( mChunkEntities ) )
391  {
392  if ( entity->isEnabled() )
393  entity->update( _sceneState( mCameraController ) );
394  }
395  updateSceneState();
396 }
397 
398 static void _updateNearFarPlane( const QList<QgsChunkNode *> &activeNodes, const QMatrix4x4 &viewMatrix, float &fnear, float &ffar )
399 {
400  for ( QgsChunkNode *node : activeNodes )
401  {
402  // project each corner of bbox to camera coordinates
403  // and determine closest and farthest point.
404  QgsAABB bbox = node->bbox();
405  for ( int i = 0; i < 8; ++i )
406  {
407  QVector4D p( ( ( i >> 0 ) & 1 ) ? bbox.xMin : bbox.xMax,
408  ( ( i >> 1 ) & 1 ) ? bbox.yMin : bbox.yMax,
409  ( ( i >> 2 ) & 1 ) ? bbox.zMin : bbox.zMax, 1 );
410 
411  QVector4D pc = viewMatrix * p;
412 
413  float dst = -pc.z(); // in camera coordinates, x grows right, y grows down, z grows to the back
414  fnear = std::min( fnear, dst );
415  ffar = std::max( ffar, dst );
416  }
417  }
418 }
419 
420 bool Qgs3DMapScene::updateCameraNearFarPlanes()
421 {
422  // Update near and far plane from the terrain.
423  // this needs to be done with great care as we have kind of circular dependency here:
424  // active nodes are culled based on the current frustum (which involves near + far plane)
425  // and then based on active nodes we set near and far plane.
426  //
427  // All of this is just heuristics assuming that all other stuff is being rendered somewhere
428  // around the area where the terrain is.
429  //
430  // Near/far plane is setup in order to make best use of the depth buffer to avoid:
431  // 1. precision errors - if the range is too great
432  // 2. unwanted clipping of scene - if the range is too small
433 
434  Qt3DRender::QCamera *camera = cameraController()->camera();
435  QMatrix4x4 viewMatrix = camera->viewMatrix();
436  float fnear = 1e9;
437  float ffar = 0;
438  QList<QgsChunkNode *> activeNodes;
439  if ( mTerrain )
440  activeNodes = mTerrain->activeNodes();
441 
442  // it could be that there are no active nodes - they could be all culled or because root node
443  // is not yet loaded - we still need at least something to understand bounds of our scene
444  // so lets use the root node
445  if ( mTerrain && activeNodes.isEmpty() )
446  activeNodes << mTerrain->rootNode();
447 
448  _updateNearFarPlane( activeNodes, viewMatrix, fnear, ffar );
449 
450  // Also involve all the other chunked entities to make sure that they will not get
451  // clipped by the near or far plane
452  for ( QgsChunkedEntity *e : std::as_const( mChunkEntities ) )
453  {
454  if ( e != mTerrain )
455  {
456  QList<QgsChunkNode *> activeEntityNodes = e->activeNodes();
457  if ( activeEntityNodes.empty() )
458  activeEntityNodes << e->rootNode();
459  _updateNearFarPlane( activeEntityNodes, viewMatrix, fnear, ffar );
460  }
461  }
462 
463  if ( fnear < 1 )
464  fnear = 1; // does not really make sense to use negative far plane (behind camera)
465 
466  if ( fnear == 1e9 && ffar == 0 )
467  {
468  // the update didn't work out... this should not happen
469  // well at least temporarily use some conservative starting values
470  qWarning() << "oops... this should not happen! couldn't determine near/far plane. defaulting to 1...1e9";
471  fnear = 1;
472  ffar = 1e9;
473  }
474 
475  // set near/far plane - with some tolerance in front/behind expected near/far planes
476  float newFar = ffar * 2;
477  float newNear = fnear / 2;
478  if ( !qgsFloatNear( newFar, camera->farPlane() ) || !qgsFloatNear( newNear, camera->nearPlane() ) )
479  {
480  camera->setFarPlane( newFar );
481  camera->setNearPlane( newNear );
482  return true;
483  }
484 
485  return false;
486 }
487 
488 void Qgs3DMapScene::onFrameTriggered( float dt )
489 {
490  mCameraController->frameTriggered( dt );
491 
492  for ( QgsChunkedEntity *entity : std::as_const( mChunkEntities ) )
493  {
494  if ( entity->isEnabled() && entity->needsUpdate() )
495  {
496  QgsDebugMsgLevel( QStringLiteral( "need for update" ), 2 );
497  entity->update( _sceneState( mCameraController ) );
498  }
499  }
500 
501  updateSceneState();
502 
503  // lock changing the FPS counter to 5 fps
504  static int frameCount = 0;
505  static float accumulatedTime = 0.0f;
506 
507  if ( !mMap.isFpsCounterEnabled() )
508  {
509  frameCount = 0;
510  accumulatedTime = 0;
511  return;
512  }
513 
514  frameCount++;
515  accumulatedTime += dt;
516  if ( accumulatedTime >= 0.2f )
517  {
518  float fps = ( float )frameCount / accumulatedTime;
519  frameCount = 0;
520  accumulatedTime = 0.0f;
521  emit fpsCountChanged( fps );
522  }
523 }
524 
525 void Qgs3DMapScene::createTerrain()
526 {
527  if ( mTerrain )
528  {
529  mChunkEntities.removeOne( mTerrain );
530 
531  mTerrain->deleteLater();
532  mTerrain = nullptr;
533  }
534 
535  if ( !mTerrainUpdateScheduled )
536  {
537  // defer re-creation of terrain: there may be multiple invocations of this slot, so create the new entity just once
538  QTimer::singleShot( 0, this, &Qgs3DMapScene::createTerrainDeferred );
539  mTerrainUpdateScheduled = true;
540  setSceneState( Updating );
541  }
542  else
543  {
544  emit terrainEntityChanged();
545  }
546 }
547 
548 void Qgs3DMapScene::createTerrainDeferred()
549 {
550  if ( mMap.terrainGenerator() )
551  {
552  double tile0width = mMap.terrainGenerator()->extent().width();
553  int maxZoomLevel = Qgs3DUtils::maxZoomLevel( tile0width, mMap.mapTileResolution(), mMap.maxTerrainGroundError() );
554  QgsAABB rootBbox = mMap.terrainGenerator()->rootChunkBbox( mMap );
555  float rootError = mMap.terrainGenerator()->rootChunkError( mMap );
556  mMap.terrainGenerator()->setupQuadtree( rootBbox, rootError, maxZoomLevel );
557 
558  mTerrain = new QgsTerrainEntity( mMap );
559  mTerrain->setParent( this );
560  mTerrain->setShowBoundingBoxes( mMap.showTerrainBoundingBoxes() );
561 
562  mCameraController->setTerrainEntity( mTerrain );
563 
564  mChunkEntities << mTerrain;
565 
566  connect( mTerrain, &QgsChunkedEntity::pendingJobsCountChanged, this, &Qgs3DMapScene::totalPendingJobsCountChanged );
567  connect( mTerrain, &QgsTerrainEntity::pendingJobsCountChanged, this, &Qgs3DMapScene::terrainPendingJobsCountChanged );
568  }
569  else
570  {
571  mTerrain = nullptr;
572  mCameraController->setTerrainEntity( mTerrain );
573  }
574 
575  // make sure that renderers for layers are re-created as well
576  const QList<QgsMapLayer *> layers = mMap.layers();
577  for ( QgsMapLayer *layer : layers )
578  {
579  // remove old entity - if any
580  removeLayerEntity( layer );
581 
582  // add new entity - if any 3D renderer
583  addLayerEntity( layer );
584  }
585 
586  emit terrainEntityChanged();
587  onCameraChanged(); // force update of the new terrain
588  mTerrainUpdateScheduled = false;
589 }
590 
591 void Qgs3DMapScene::onBackgroundColorChanged()
592 {
593  mEngine->setClearColor( mMap.backgroundColor() );
594 }
595 
596 void Qgs3DMapScene::updateLights()
597 {
598  for ( Qt3DCore::QEntity *entity : std::as_const( mLightEntities ) )
599  entity->deleteLater();
600  mLightEntities.clear();
601  for ( Qt3DCore::QEntity *entity : std::as_const( mLightOriginEntities ) )
602  entity->deleteLater();
603  mLightOriginEntities.clear();
604 
605  auto createLightOriginEntity = [ = ]( QVector3D translation, const QColor & color )->Qt3DCore::QEntity *
606  {
607  Qt3DCore::QEntity *originEntity = new Qt3DCore::QEntity;
608 
609  Qt3DCore::QTransform *trLightOriginCenter = new Qt3DCore::QTransform;
610  trLightOriginCenter->setTranslation( translation );
611  originEntity->addComponent( trLightOriginCenter );
612 
613  Qt3DExtras::QPhongMaterial *materialLightOriginCenter = new Qt3DExtras::QPhongMaterial;
614  materialLightOriginCenter->setAmbient( color );
615  originEntity->addComponent( materialLightOriginCenter );
616 
617  Qt3DExtras::QSphereMesh *rendererLightOriginCenter = new Qt3DExtras::QSphereMesh;
618  rendererLightOriginCenter->setRadius( 20 );
619  originEntity->addComponent( rendererLightOriginCenter );
620 
621  originEntity->setEnabled( true );
622  originEntity->setParent( this );
623 
624  return originEntity;
625  };
626 
627  const auto newPointLights = mMap.pointLights();
628  for ( const QgsPointLightSettings &pointLightSettings : newPointLights )
629  {
630  Qt3DCore::QEntity *lightEntity = new Qt3DCore::QEntity;
631  Qt3DCore::QTransform *lightTransform = new Qt3DCore::QTransform;
632  lightTransform->setTranslation( QVector3D( pointLightSettings.position().x(),
633  pointLightSettings.position().y(),
634  pointLightSettings.position().z() ) );
635 
636  Qt3DRender::QPointLight *light = new Qt3DRender::QPointLight;
637  light->setColor( pointLightSettings.color() );
638  light->setIntensity( pointLightSettings.intensity() );
639 
640  light->setConstantAttenuation( pointLightSettings.constantAttenuation() );
641  light->setLinearAttenuation( pointLightSettings.linearAttenuation() );
642  light->setQuadraticAttenuation( pointLightSettings.quadraticAttenuation() );
643 
644  lightEntity->addComponent( light );
645  lightEntity->addComponent( lightTransform );
646  lightEntity->setParent( this );
647  mLightEntities << lightEntity;
648 
649  if ( mMap.showLightSourceOrigins() )
650  mLightOriginEntities << createLightOriginEntity( lightTransform->translation(), pointLightSettings.color() );
651  }
652 
653  const auto newDirectionalLights = mMap.directionalLights();
654  for ( const QgsDirectionalLightSettings &directionalLightSettings : newDirectionalLights )
655  {
656  Qt3DCore::QEntity *lightEntity = new Qt3DCore::QEntity;
657  Qt3DCore::QTransform *lightTransform = new Qt3DCore::QTransform;
658 
659  Qt3DRender::QDirectionalLight *light = new Qt3DRender::QDirectionalLight;
660  light->setColor( directionalLightSettings.color() );
661  light->setIntensity( directionalLightSettings.intensity() );
662  QgsVector3D direction = directionalLightSettings.direction();
663  light->setWorldDirection( QVector3D( direction.x(), direction.y(), direction.z() ) );
664 
665  lightEntity->addComponent( light );
666  lightEntity->addComponent( lightTransform );
667  lightEntity->setParent( this );
668  mLightEntities << lightEntity;
669  }
670 
671  onShadowSettingsChanged();
672 }
673 
674 void Qgs3DMapScene::updateCameraLens()
675 {
676  mEngine->camera()->lens()->setFieldOfView( mMap.fieldOfView() );
677  mEngine->camera()->lens()->setProjectionType( mMap.projectionType() );
678  onCameraChanged();
679 }
680 
681 void Qgs3DMapScene::onRenderersChanged()
682 {
683  // remove entities (if any)
684  qDeleteAll( mRenderersEntities );
685  mRenderersEntities.clear();
686 
687  // re-add entities from new set of renderers
688  const QList<QgsAbstract3DRenderer *> renderers = mMap.renderers();
689  for ( const QgsAbstract3DRenderer *renderer : renderers )
690  {
691  Qt3DCore::QEntity *newEntity = renderer->createEntity( mMap );
692  if ( newEntity )
693  {
694  newEntity->setParent( this );
695  finalizeNewEntity( newEntity );
696  mRenderersEntities[renderer] = newEntity;
697  }
698  }
699 }
700 
701 void Qgs3DMapScene::onLayerRenderer3DChanged()
702 {
703  QgsMapLayer *layer = qobject_cast<QgsMapLayer *>( sender() );
704  Q_ASSERT( layer );
705 
706  // remove old entity - if any
707  removeLayerEntity( layer );
708 
709  // add new entity - if any 3D renderer
710  addLayerEntity( layer );
711 }
712 
713 void Qgs3DMapScene::onLayersChanged()
714 {
715  QSet<QgsMapLayer *> layersBefore = qgis::listToSet( mLayerEntities.keys() );
716  QList<QgsMapLayer *> layersAdded;
717  const QList<QgsMapLayer *> layers = mMap.layers();
718  for ( QgsMapLayer *layer : layers )
719  {
720  if ( !layersBefore.contains( layer ) )
721  {
722  layersAdded << layer;
723  }
724  else
725  {
726  layersBefore.remove( layer );
727  }
728  }
729 
730  // what is left in layersBefore are layers that have been removed
731  for ( QgsMapLayer *layer : std::as_const( layersBefore ) )
732  {
733  removeLayerEntity( layer );
734  }
735 
736  for ( QgsMapLayer *layer : std::as_const( layersAdded ) )
737  {
738  addLayerEntity( layer );
739  }
740 }
741 
743 {
744  for ( auto layer : mLayerEntities.keys() )
745  {
746  if ( layer->temporalProperties()->isActive() )
747  {
748  removeLayerEntity( layer );
749  addLayerEntity( layer );
750  }
751  }
752 }
753 
754 void Qgs3DMapScene::addLayerEntity( QgsMapLayer *layer )
755 {
756  bool needsSceneUpdate = false;
757  QgsAbstract3DRenderer *renderer = layer->renderer3D();
758  if ( renderer )
759  {
760  // Fix vector layer's renderer to make sure the renderer is pointing to its layer.
761  // It has happened before that renderer pointed to a different layer (probably after copying a style).
762  // This is a bit of a hack and it should be handled in QgsMapLayer::setRenderer3D() but in qgis_core
763  // the vector layer 3D renderer classes are not available.
764  if ( layer->type() == QgsMapLayerType::VectorLayer &&
765  ( renderer->type() == QLatin1String( "vector" ) || renderer->type() == QLatin1String( "rulebased" ) ) )
766  {
767  static_cast<QgsAbstractVectorLayer3DRenderer *>( renderer )->setLayer( static_cast<QgsVectorLayer *>( layer ) );
768  if ( renderer->type() == QLatin1String( "vector" ) )
769  {
770  QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
771  if ( vlayer->geometryType() == QgsWkbTypes::PointGeometry )
772  {
773  const QgsPoint3DSymbol *pointSymbol = static_cast< const QgsPoint3DSymbol * >( static_cast< QgsVectorLayer3DRenderer *>( renderer )->symbol() );
774  if ( pointSymbol->shape() == QgsPoint3DSymbol::Model )
775  {
776  mModelVectorLayers.append( layer );
777  }
778  }
779  }
780  else if ( renderer->type() == QLatin1String( "rulebased" ) )
781  {
782  const QgsRuleBased3DRenderer::RuleList rules = static_cast< QgsRuleBased3DRenderer *>( renderer )->rootRule()->descendants();
783  for ( auto rule : rules )
784  {
785  const QgsPoint3DSymbol *pointSymbol = dynamic_cast< const QgsPoint3DSymbol * >( rule->symbol() );
786  if ( pointSymbol && pointSymbol->shape() == QgsPoint3DSymbol::Model )
787  {
788  mModelVectorLayers.append( layer );
789  break;
790  }
791  }
792  }
793  }
794  else if ( layer->type() == QgsMapLayerType::MeshLayer && renderer->type() == QLatin1String( "mesh" ) )
795  {
796  QgsMeshLayer3DRenderer *meshRenderer = static_cast<QgsMeshLayer3DRenderer *>( renderer );
797  meshRenderer->setLayer( static_cast<QgsMeshLayer *>( layer ) );
798 
799  // Before entity creation, set the maximum texture size
800  // Not very clean, but for now, only place found in the workflow to do that simple
801  QgsMesh3DSymbol *sym = meshRenderer->symbol()->clone();
802  sym->setMaximumTextureSize( maximumTextureSize() );
803  meshRenderer->setSymbol( sym );
804  }
805  else if ( layer->type() == QgsMapLayerType::PointCloudLayer && renderer->type() == QLatin1String( "pointcloud" ) )
806  {
807  QgsPointCloudLayer3DRenderer *pointCloudRenderer = static_cast<QgsPointCloudLayer3DRenderer *>( renderer );
808  pointCloudRenderer->setLayer( static_cast<QgsPointCloudLayer *>( layer ) );
809  }
810 
811  Qt3DCore::QEntity *newEntity = renderer->createEntity( mMap );
812  if ( newEntity )
813  {
814  newEntity->setParent( this );
815  mLayerEntities.insert( layer, newEntity );
816 
817  finalizeNewEntity( newEntity );
818 
819  if ( QgsChunkedEntity *chunkedNewEntity = qobject_cast<QgsChunkedEntity *>( newEntity ) )
820  {
821  mChunkEntities.append( chunkedNewEntity );
822  needsSceneUpdate = true;
823 
824  chunkedNewEntity->setPickingEnabled( !mPickHandlers.isEmpty() );
825  connect( chunkedNewEntity, &QgsChunkedEntity::pickedObject, this, &Qgs3DMapScene::onLayerEntityPickedObject );
826 
827  connect( chunkedNewEntity, &QgsChunkedEntity::newEntityCreated, this, [this]( Qt3DCore::QEntity * entity )
828  {
829  finalizeNewEntity( entity );
830  } );
831 
832  connect( chunkedNewEntity, &QgsChunkedEntity::pendingJobsCountChanged, this, &Qgs3DMapScene::totalPendingJobsCountChanged );
833  }
834  }
835  }
836 
837  if ( needsSceneUpdate )
838  onCameraChanged(); // needed for chunked entities
839 
840  connect( layer, &QgsMapLayer::request3DUpdate, this, &Qgs3DMapScene::onLayerRenderer3DChanged );
841 
842  if ( layer->type() == QgsMapLayerType::VectorLayer )
843  {
844  QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
845  connect( vlayer, &QgsVectorLayer::selectionChanged, this, &Qgs3DMapScene::onLayerRenderer3DChanged );
846  connect( vlayer, &QgsVectorLayer::layerModified, this, &Qgs3DMapScene::onLayerRenderer3DChanged );
847  }
848 
849  if ( layer->type() == QgsMapLayerType::MeshLayer )
850  {
851  connect( layer, &QgsMapLayer::rendererChanged, this, &Qgs3DMapScene::onLayerRenderer3DChanged );
852  }
853 
854  if ( layer->type() == QgsMapLayerType::PointCloudLayer )
855  connect( layer, &QgsMapLayer::renderer3DChanged, this, &Qgs3DMapScene::onLayerRenderer3DChanged );
856 }
857 
858 void Qgs3DMapScene::removeLayerEntity( QgsMapLayer *layer )
859 {
860  Qt3DCore::QEntity *entity = mLayerEntities.take( layer );
861 
862  if ( QgsChunkedEntity *chunkedEntity = qobject_cast<QgsChunkedEntity *>( entity ) )
863  {
864  mChunkEntities.removeOne( chunkedEntity );
865  }
866 
867  if ( entity )
868  entity->deleteLater();
869 
870  disconnect( layer, &QgsMapLayer::request3DUpdate, this, &Qgs3DMapScene::onLayerRenderer3DChanged );
871 
872  if ( layer->type() == QgsMapLayerType::VectorLayer )
873  {
874  QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
875  disconnect( vlayer, &QgsVectorLayer::selectionChanged, this, &Qgs3DMapScene::onLayerRenderer3DChanged );
876  disconnect( vlayer, &QgsVectorLayer::layerModified, this, &Qgs3DMapScene::onLayerRenderer3DChanged );
877  mModelVectorLayers.removeAll( layer );
878  }
879 
880  if ( layer->type() == QgsMapLayerType::MeshLayer )
881  {
882  disconnect( layer, &QgsMapLayer::rendererChanged, this, &Qgs3DMapScene::onLayerRenderer3DChanged );
883  }
884 
885  if ( layer->type() == QgsMapLayerType::PointCloudLayer )
886  disconnect( layer, &QgsMapLayer::renderer3DChanged, this, &Qgs3DMapScene::onLayerRenderer3DChanged );
887 }
888 
889 void Qgs3DMapScene::finalizeNewEntity( Qt3DCore::QEntity *newEntity )
890 {
891  // this is probably not the best place for material-specific configuration,
892  // maybe this could be more generalized when other materials need some specific treatment
893  for ( QgsLineMaterial *lm : newEntity->findChildren<QgsLineMaterial *>() )
894  {
895  connect( mCameraController, &QgsCameraController::viewportChanged, lm, [lm, this]
896  {
897  lm->setViewportSize( mCameraController->viewport().size() );
898  } );
899 
900  lm->setViewportSize( cameraController()->viewport().size() );
901  }
902  // configure billboard's viewport when the viewport is changed.
903  for ( QgsPoint3DBillboardMaterial *bm : newEntity->findChildren<QgsPoint3DBillboardMaterial *>() )
904  {
905  connect( mCameraController, &QgsCameraController::viewportChanged, bm, [bm, this]
906  {
907  bm->setViewportSize( mCameraController->viewport().size() );
908  } );
909 
910  bm->setViewportSize( mCameraController->viewport().size() );
911  }
912 }
913 
914 int Qgs3DMapScene::maximumTextureSize() const
915 {
916  QSurface *surface = mEngine->surface();
917  QOpenGLContext context;
918  context.create();
919  context.makeCurrent( surface );
920  QOpenGLFunctions openglFunctions( &context );
921  GLint size;
922  openglFunctions.glGetIntegerv( GL_MAX_TEXTURE_SIZE, &size );
923  return int( size );
924 }
925 
926 void Qgs3DMapScene::addCameraViewCenterEntity( Qt3DRender::QCamera *camera )
927 {
928  mEntityCameraViewCenter = new Qt3DCore::QEntity;
929 
930  Qt3DCore::QTransform *trCameraViewCenter = new Qt3DCore::QTransform;
931  mEntityCameraViewCenter->addComponent( trCameraViewCenter );
932  connect( camera, &Qt3DRender::QCamera::viewCenterChanged, this, [trCameraViewCenter, camera]
933  {
934  trCameraViewCenter->setTranslation( camera->viewCenter() );
935  } );
936 
937  Qt3DExtras::QPhongMaterial *materialCameraViewCenter = new Qt3DExtras::QPhongMaterial;
938  materialCameraViewCenter->setAmbient( Qt::red );
939  mEntityCameraViewCenter->addComponent( materialCameraViewCenter );
940 
941  Qt3DExtras::QSphereMesh *rendererCameraViewCenter = new Qt3DExtras::QSphereMesh;
942  rendererCameraViewCenter->setRadius( 10 );
943  mEntityCameraViewCenter->addComponent( rendererCameraViewCenter );
944 
945  mEntityCameraViewCenter->setEnabled( mMap.showCameraViewCenter() );
946  mEntityCameraViewCenter->setParent( this );
947 
948  connect( &mMap, &Qgs3DMapSettings::showCameraViewCenterChanged, this, [this]
949  {
950  mEntityCameraViewCenter->setEnabled( mMap.showCameraViewCenter() );
951  } );
952 }
953 
954 void Qgs3DMapScene::setSceneState( Qgs3DMapScene::SceneState state )
955 {
956  if ( mSceneState == state )
957  return;
958  mSceneState = state;
959  emit sceneStateChanged();
960 }
961 
962 void Qgs3DMapScene::updateSceneState()
963 {
964  if ( mTerrainUpdateScheduled )
965  {
966  setSceneState( Updating );
967  return;
968  }
969 
970  for ( QgsChunkedEntity *entity : std::as_const( mChunkEntities ) )
971  {
972  if ( entity->isEnabled() && entity->pendingJobsCount() > 0 )
973  {
974  setSceneState( Updating );
975  return;
976  }
977  }
978 
979  setSceneState( Ready );
980 }
981 
982 void Qgs3DMapScene::onSkyboxSettingsChanged()
983 {
984  QgsSkyboxSettings skyboxSettings = mMap.skyboxSettings();
985  if ( mSkybox != nullptr )
986  {
987  mSkybox->deleteLater();
988  mSkybox = nullptr;
989  }
990 
991  mEngine->setFrustumCullingEnabled( !mMap.isSkyboxEnabled() );
992 
993  if ( mMap.isSkyboxEnabled() )
994  {
995  QMap<QString, QString> faces;
996  switch ( skyboxSettings.skyboxType() )
997  {
999  faces = skyboxSettings.cubeMapFacesPaths();
1000  mSkybox = new QgsCubeFacesSkyboxEntity(
1001  faces[QStringLiteral( "posX" )], faces[QStringLiteral( "posY" )], faces[QStringLiteral( "posZ" )],
1002  faces[QStringLiteral( "negX" )], faces[QStringLiteral( "negY" )], faces[QStringLiteral( "negZ" )],
1003  this
1004  );
1005  break;
1007  mSkybox = new QgsPanoramicSkyboxEntity( skyboxSettings.panoramicTexturePath(), this );
1008  break;
1009  }
1010  }
1011 }
1012 
1013 void Qgs3DMapScene::onShadowSettingsChanged()
1014 {
1015  QgsShadowRenderingFrameGraph *shadowRenderingFrameGraph = mEngine->frameGraph();
1016 
1017  QList<QgsDirectionalLightSettings> directionalLights = mMap.directionalLights();
1018  QgsShadowSettings shadowSettings = mMap.shadowSettings();
1019  int selectedLight = shadowSettings.selectedDirectionalLight();
1020  if ( shadowSettings.renderShadows() && selectedLight >= 0 && selectedLight < directionalLights.count() )
1021  {
1022  shadowRenderingFrameGraph->setShadowRenderingEnabled( true );
1023  shadowRenderingFrameGraph->setShadowBias( shadowSettings.shadowBias() );
1024  shadowRenderingFrameGraph->setShadowMapResolution( shadowSettings.shadowMapResolution() );
1025  QgsDirectionalLightSettings light = directionalLights[selectedLight];
1026  shadowRenderingFrameGraph->setupDirectionalLight( light, shadowSettings.maximumShadowRenderingDistance() );
1027  }
1028  else
1029  shadowRenderingFrameGraph->setShadowRenderingEnabled( false );
1030 }
1031 
1032 void Qgs3DMapScene::onDebugShadowMapSettingsChanged()
1033 {
1034  QgsShadowRenderingFrameGraph *shadowRenderingFrameGraph = mEngine->frameGraph();
1035  shadowRenderingFrameGraph->setupShadowMapDebugging( mMap.debugShadowMapEnabled(), mMap.debugShadowMapCorner(), mMap.debugShadowMapSize() );
1036 }
1037 
1038 void Qgs3DMapScene::onDebugDepthMapSettingsChanged()
1039 {
1040  QgsShadowRenderingFrameGraph *shadowRenderingFrameGraph = mEngine->frameGraph();
1041  shadowRenderingFrameGraph->setupDepthMapDebugging( mMap.debugDepthMapEnabled(), mMap.debugDepthMapCorner(), mMap.debugDepthMapSize() );
1042 }
1043 
1044 void Qgs3DMapScene::onEyeDomeShadingSettingsChanged()
1045 {
1046  QgsShadowRenderingFrameGraph *shadowRenderingFrameGraph = mEngine->frameGraph();
1047 
1048  bool edlEnabled = mMap.eyeDomeLightingEnabled();
1049  double edlStrength = mMap.eyeDomeLightingStrength();
1050  double edlDistance = mMap.eyeDomeLightingDistance();
1051  shadowRenderingFrameGraph->setupEyeDomeLighting( edlEnabled, edlStrength, edlDistance );
1052 }
1053 
1054 void Qgs3DMapScene::onCameraMovementSpeedChanged()
1055 {
1056  mCameraController->setCameraMovementSpeed( mMap.cameraMovementSpeed() );
1057 }
1058 
1060 {
1061  QVector<QString> notParsedLayers;
1062  Qgs3DSceneExporter exporter;
1063 
1064  exporter.setTerrainResolution( exportSettings.terrrainResolution() );
1065  exporter.setSmoothEdges( exportSettings.smoothEdges() );
1066  exporter.setExportNormals( exportSettings.exportNormals() );
1067  exporter.setExportTextures( exportSettings.exportTextures() );
1068  exporter.setTerrainTextureResolution( exportSettings.terrainTextureResolution() );
1069  exporter.setScale( exportSettings.scale() );
1070 
1071  for ( auto it = mLayerEntities.constBegin(); it != mLayerEntities.constEnd(); ++it )
1072  {
1073  QgsMapLayer *layer = it.key();
1074  Qt3DCore::QEntity *rootEntity = it.value();
1075  QgsMapLayerType layerType = layer->type();
1076  switch ( layerType )
1077  {
1079  if ( !exporter.parseVectorLayerEntity( rootEntity, qobject_cast<QgsVectorLayer *>( layer ) ) )
1080  notParsedLayers.push_back( layer->name() );
1081  break;
1088  notParsedLayers.push_back( layer->name() );
1089  break;
1090  }
1091  }
1092 
1093  if ( mTerrain )
1094  exporter.parseTerrain( mTerrain, "Terrain" );
1095 
1096  exporter.save( exportSettings.sceneName(), exportSettings.sceneFolderPath() );
1097 
1098  if ( !notParsedLayers.empty() )
1099  {
1100  QString message = tr( "The following layers were not exported:" ) + "\n";
1101  for ( const QString &layerName : notParsedLayers )
1102  message += layerName + "\n";
1103  QgsMessageOutput::showMessage( tr( "3D exporter warning" ), message, QgsMessageOutput::MessageText );
1104  }
1105 }
1106 
1107 QVector<const QgsChunkNode *> Qgs3DMapScene::getLayerActiveChunkNodes( QgsMapLayer *layer )
1108 {
1109  QVector<const QgsChunkNode *> chunks;
1110  if ( !mLayerEntities.contains( layer ) ) return chunks;
1111  if ( QgsChunkedEntity *c = qobject_cast<QgsChunkedEntity *>( mLayerEntities[ layer ] ) )
1112  {
1113  for ( QgsChunkNode *n : c->activeNodes() )
1114  chunks.push_back( n );
1115  }
1116  return chunks;
1117 }
1118 
1120 {
1121  QgsRectangle extent;
1122  extent.setMinimal();
1123 
1124  for ( QgsMapLayer *layer : mLayerEntities.keys() )
1125  {
1126  Qt3DCore::QEntity *layerEntity = mLayerEntities[ layer ];
1127  QgsChunkedEntity *c = qobject_cast<QgsChunkedEntity *>( layerEntity );
1128  if ( !c )
1129  continue;
1130  QgsChunkNode *chunkNode = c->rootNode();
1131  QgsAABB bbox = chunkNode->bbox();
1132  QgsRectangle layerExtent = Qgs3DUtils::worldToLayerExtent( bbox, layer->crs(), mMap.origin(), mMap.crs(), mMap.transformContext() );
1133  extent.combineExtentWith( layerExtent );
1134  }
1135 
1136  if ( QgsTerrainGenerator *terrainGenerator = mMap.terrainGenerator() )
1137  {
1138  QgsRectangle terrainExtent = terrainGenerator->extent();
1139  QgsCoordinateTransform terrainToMapTransform( terrainGenerator->crs(), mMap.crs(), QgsProject::instance() );
1140  terrainToMapTransform.setBallparkTransformsAreAppropriate( true );
1141  terrainExtent = terrainToMapTransform.transformBoundingBox( terrainExtent );
1142  extent.combineExtentWith( terrainExtent );
1143  }
1144 
1145  return extent;
1146 }
Manages the various settings the user can choose from when exporting a 3D scene 3.
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.
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.
void unregisterPickHandler(Qgs3DMapScenePickHandler *pickHandler)
Unregisters previously registered pick handler. Pick handler is not deleted. Also removes object pick...
QVector< const QgsChunkNode * > getLayerActiveChunkNodes(QgsMapLayer *layer)
Returns the active chunk nodes of layer.
void exportScene(const Qgs3DMapExportSettings &exportSettings)
Exports the scene according to the scene export settings.
void terrainPendingJobsCountChanged()
Emitted when the number of terrain's pending jobs changes.
void fpsCountChanged(float fpsCount)
Emitted when the FPS count changes.
QgsRectangle sceneExtent()
Returns the scene extent in the map's CRS.
void registerPickHandler(Qgs3DMapScenePickHandler *pickHandler)
Registers an object that will get results of pick events on 3D entities. Does not take ownership of t...
SceneState
Enumeration of possible states of the 3D scene.
Definition: qgs3dmapscene.h:95
@ Ready
The scene is fully loaded/updated.
Definition: qgs3dmapscene.h:96
@ Updating
The scene is still being loaded/updated.
Definition: qgs3dmapscene.h:97
int totalPendingJobsCount() const
Returns number of pending jobs for all chunked entities.
void updateTemporal()
Updates the temporale entities.
void totalPendingJobsCountChanged()
Emitted when the total number of pending jobs changes.
void fpsCounterEnabledChanged(bool fpsCounterEnabled)
Emitted when the FPS counter is activated or deactivated.
QgsCameraController * cameraController()
Returns camera controller.
Definition: qgs3dmapscene.h:77
void sceneStateChanged()
Emitted when the scene's state has changed.
int terrainPendingJobsCount() const
Returns number of pending jobs of the terrain entity.
float worldSpaceError(float epsilon, float distance)
Given screen error (in pixels) and distance from camera (in 3D world coordinates),...
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)
Qgs3DMapScene(const Qgs3DMapSettings &map, QgsAbstract3DEngine *engine)
Constructs a 3D scene based on map settings and Qt 3D renderer configuration.
void mapTileResolutionChanged()
Emitted when the map tile resoulution has changed.
void terrainVerticalScaleChanged()
Emitted when the vertical scale of the terrain has changed.
Qt::Corner debugDepthMapCorner() const
Returns the corner where the shadow map preview is displayed.
void renderersChanged()
Emitted when the list of map's extra renderers have been modified.
QList< QgsAbstract3DRenderer * > renderers() const
Returns list of extra 3D renderers.
QgsTerrainGenerator * terrainGenerator() const
Returns terrain generator. It takes care of producing terrain tiles from the input data.
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.
double cameraMovementSpeed() const
Returns the camera movement speed.
Qt3DRender::QCameraLens::ProjectionType projectionType() const
Returns the camera lens' projection type.
bool debugDepthMapEnabled() const
Returns whether the shadow map debugging is enabled.
bool isSkyboxEnabled() const
Returns whether the skybox is enabled.
void debugDepthMapSettingsChanged()
Emitted when depth map debugging has changed.
QList< QgsDirectionalLightSettings > directionalLights() const
Returns list of directional lights defined in the scene.
double eyeDomeLightingStrength() const
Returns the eye dome lighting strength value.
void backgroundColorChanged()
Emitted when the background color has changed.
Qt::Corner debugShadowMapCorner() const
Returns the corner where the shadow map preview is displayed.
bool showCameraViewCenter() const
Returns whether to show camera's view center as a sphere (for debugging)
void directionalLightsChanged()
Emitted when the list of directional lights changes.
void shadowSettingsChanged()
Emitted when shadow rendering settings are changed.
float maxTerrainGroundError() const
Returns maximum ground error of terrain tiles in world units.
void eyeDomeLightingEnabledChanged()
Emitted when the flag whether eye dome lighting is used has changed.
void skyboxSettingsChanged()
Emitted when skybox settings are changed.
QgsShadowSettings shadowSettings() const
Returns the current configuration of shadows.
void pointLightsChanged()
Emitted when the list of point lights changes.
double debugDepthMapSize() const
Returns the size of the shadow map preview.
void projectionTypeChanged()
Emitted when the camera lens projection type changes.
float fieldOfView() const
Returns the camera lens' field of view.
int eyeDomeLightingDistance() const
Returns the eye dome lighting distance value (contributes to the contrast of the image)
void showLightSourceOriginsChanged()
Emitted when the flag whether light source origins are shown has changed.
QColor backgroundColor() const
Returns background color of the 3D map view.
double debugShadowMapSize() const
Returns the size of the shadow map preview.
bool showTerrainBoundingBoxes() const
Returns whether to display bounding boxes of terrain tiles (for debugging)
void maxTerrainScreenErrorChanged()
Emitted when the maximum terrain screen error has changed.
int mapTileResolution() const
Returns resolution (in pixels) of the texture of a terrain tile.
bool debugShadowMapEnabled() const
Returns whether the shadow map debugging is enabled.
void fpsCounterEnabledChanged(bool fpsCounterEnabled)
Emitted when the FPS counter is enabled or disabled.
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.
QgsSkyboxSettings skyboxSettings() const
Returns the current configuration of the skybox.
void cameraMovementSpeedChanged()
Emitted when the camera movement speed was changed.
QgsCoordinateReferenceSystem crs() const
Returns coordinate reference system used in the 3D scene.
bool eyeDomeLightingEnabled() const
Returns whether eye dome lighting is used.
bool isFpsCounterEnabled() const
Returns whether FPS counter label is enabled.
void fieldOfViewChanged()
Emitted when the camera lens field of view changes.
QList< QgsPointLightSettings > pointLights() const
Returns list of point lights defined in the scene.
QList< QgsMapLayer * > layers() const
Returns the list of 3D map layers to be rendered in the scene.
QgsCameraController::NavigationMode cameraNavigationMode() const
Returns the navigation mode used by the camera.
void terrainGeneratorChanged()
Emitted when the terrain generator has changed.
bool showLightSourceOrigins() const
Returns whether to show light source origins as a sphere (for debugging)
void debugShadowMapSettingsChanged()
Emitted when shadow map debugging has changed.
void showCameraViewCenterChanged()
Emitted when the flag whether camera's view center is shown has changed.
void maxTerrainGroundErrorChanged()
Emitted when the maximum terrain ground error has changed.
QgsCoordinateTransformContext transformContext() const
Returns the coordinate transform context, which stores various information regarding which datum tran...
QgsVector3D origin() const
Returns coordinates in map CRS at which 3D scene has origin (0,0,0)
Entity that handles the exporting of 3D scene.
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 save(const QString &sceneName, const QString &sceneFolderPath)
Saves the scene to a .obj file.
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.
void setExportNormals(bool exportNormals)
Sets whether the normals will be exported.
void setSmoothEdges(bool smoothEdges)
Sets whether the triangles will look smooth.
static QgsRectangle worldToLayerExtent(const QgsAABB &bbox, const QgsCoordinateReferenceSystem &layerCrs, const QgsVector3D &mapOrigin, const QgsCoordinateReferenceSystem &mapCrs, const QgsCoordinateTransformContext &context)
Converts axis aligned bounding box in 3D world coordinates to extent in map layer CRS.
Definition: qgs3dutils.cpp:476
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...
Definition: qgs3dutils.cpp:174
3
Definition: qgsaabb.h:34
float yMax
Definition: qgsaabb.h:85
float xMax
Definition: qgsaabb.h:84
float xMin
Definition: qgsaabb.h:81
float zMax
Definition: qgsaabb.h:86
float yMin
Definition: qgsaabb.h:82
float zMin
Definition: qgsaabb.h:83
virtual void setClearColor(const QColor &color)=0
Sets background color of the scene.
virtual Qt3DRender::QRenderSettings * renderSettings()=0
Returns access to the engine's render settings (the frame graph can be accessed from here)
virtual Qt3DRender::QCamera * camera()=0
Returns pointer to the engine's camera entity.
virtual QSurface * surface() const =0
Returns the surface of the engine.
QgsShadowRenderingFrameGraph * frameGraph()
Returns the shadow rendering frame graph object used to render the scene.
virtual void setFrustumCullingEnabled(bool enabled)=0
Sets whether frustum culling is enabled (this should make rendering faster by not rendering entities ...
virtual QSize size() const =0
Returns size of the engine's rendering area in pixels.
Base class for all renderers that may to participate in 3D view.
virtual QString type() const =0
Returns unique identifier of the renderer class (used to identify subclass)
virtual Qt3DCore::QEntity * createEntity(const Qgs3DMapSettings &map) const =0
Returns a 3D entity that will be used to show renderer's data in 3D scene.
static QgsApplication * instance()
Returns the singleton instance of the QgsApplication.
void setViewport(QRect viewport)
Sets viewport rectangle. Called internally from 3D canvas. Allows conversion of mouse coordinates.
void setCameraNavigationMode(QgsCameraController::NavigationMode navigationMode)
Sets the navigation mode used by the camera controller.
Qt3DRender::QCamera * camera
float distance() const
Returns distance of the camera from the point it is looking at.
void setCamera(Qt3DRender::QCamera *camera)
Assigns camera that should be controlled by this class. Called internally from 3D scene.
void cameraChanged()
Emitted when camera has been updated.
void frameTriggered(float dt)
Called internally from 3D scene when a new frame is generated. Updates camera according to keyboard/m...
void resetView(float distance)
Move camera back to the initial position (looking down towards origin of world's coordinates)
void setTerrainEntity(QgsTerrainEntity *te)
Connects to object picker attached to terrain entity.
void setCameraMovementSpeed(double movementSpeed)
Sets the camera movement speed.
void viewportChanged()
Emitted when viewport rectangle has been updated.
Class for doing transforms between two map coordinate systems.
void setBallparkTransformsAreAppropriate(bool appropriate)
Sets whether approximate "ballpark" results are appropriate for this coordinate transform.
QgsRectangle transformBoundingBox(const QgsRectangle &rectangle, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward, bool handle180Crossover=false) const SIP_THROW(QgsCsException)
Transforms a rectangle from the source CRS to the destination CRS.
A skybox constructed from a 6 cube faces.
Base class for all map layer types.
Definition: qgsmaplayer.h:73
QString name
Definition: qgsmaplayer.h:76
QgsAbstract3DRenderer * renderer3D() const
Returns 3D renderer associated with the layer.
void request3DUpdate()
Signal emitted when a layer requires an update in any 3D maps.
QgsMapLayerType type
Definition: qgsmaplayer.h:80
QgsCoordinateReferenceSystem crs
Definition: qgsmaplayer.h:79
void renderer3DChanged()
Signal emitted when 3D renderer associated with the layer has changed.
void rendererChanged()
Signal emitted when renderer is changed.
void layerModified()
Emitted when modifications has been done on layer.
virtual QgsMapLayerTemporalProperties * temporalProperties()
Returns the layer's temporal properties.
Definition: qgsmaplayer.h:1486
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.
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.
Definition: qgsmeshlayer.h:97
virtual void showMessage(bool blocking=true)=0
display the message to the user and deletes itself
A skybox constructed from a panoramic image.
Shape shape() const
Returns 3D shape for points.
QVariantMap shapeProperties() const
Returns a key-value dictionary of point shape properties.
3D renderer that renders all points from a point cloud layer
void setLayer(QgsPointCloudLayer *layer)
Sets point cloud layer associated with the renderer.
Represents a map layer supporting display of point clouds.
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:467
A rectangle specified with double values.
Definition: qgsrectangle.h:42
double height() const SIP_HOLDGIL
Returns the height of the rectangle.
Definition: qgsrectangle.h:230
void setMinimal() SIP_HOLDGIL
Set a rectangle so that min corner is at max and max corner is at min.
Definition: qgsrectangle.h:172
double width() const SIP_HOLDGIL
Returns the width of the rectangle.
Definition: qgsrectangle.h:223
void combineExtentWith(const QgsRectangle &rect)
Expands the rectangle so that it covers both the original rectangle and the given rectangle.
Definition: qgsrectangle.h:391
QList< QgsRuleBased3DRenderer::Rule * > RuleList
void setupDepthMapDebugging(bool enabled, Qt::Corner corner, double size)
Sets the depth map debugging view port.
void setupShadowMapDebugging(bool enabled, Qt::Corner corner, double size)
Sets the shadow map debugging view port.
void setShadowBias(float shadowBias)
Sets the shadow bias value.
void setShadowMapResolution(int resolution)
Sets the resolution of the shadow map.
void setupEyeDomeLighting(bool enabled, double strength, int distance)
Sets eye dome lighting shading related settings.
void setupDirectionalLight(const QgsDirectionalLightSettings &light, float maximumShadowRenderingDistance)
Sets shadow rendering to use a directional light.
void setShadowRenderingEnabled(bool enabled)
Sets whether the shadow rendering is enabled.
class containing the configuration of shadows rendering 3
int selectedDirectionalLight() const
Returns the selected direcctional light used to cast shadows.
bool renderShadows() const
Returns whether shadow rendering is enabled.
int shadowMapResolution() const
Returns the resolution of the shadow map texture used to generate the shadows.
double maximumShadowRenderingDistance() const
Returns the maximum shadow rendering distance accounted for when rendering shadows Objects further aw...
double shadowBias() const
Returns the shadow bias used to correct the numerical imprecision of shadows (for the depth test) Thi...
Contains the configuration of a skybox entity.
QgsSkyboxEntity::SkyboxType skyboxType() const
Returns the type of the skybox.
QString panoramicTexturePath() const
Returns the panoramic texture path of a skybox of type "Panormaic skybox".
QMap< QString, QString > cubeMapFacesPaths() const
Returns a map containing the path of each texture specified by the user.
void remoteSourceFetched(const QString &url)
Emitted when the cache has finished retrieving a 3D model from a remote url.
bool isActive() const
Returns true if the temporal property is active.
virtual QgsRectangle extent() const =0
extent of the terrain in terrain's CRS
virtual float rootChunkError(const Qgs3DMapSettings &map) const
Returns error of the root chunk in world coordinates.
virtual QgsAABB rootChunkBbox(const Qgs3DMapSettings &map) const
Returns bounding box of the root chunk.
double y() const
Returns Y coordinate.
Definition: qgsvector3d.h:51
double z() const
Returns Z coordinate.
Definition: qgsvector3d.h:53
double x() const
Returns X coordinate.
Definition: qgsvector3d.h:49
3D renderer that renders all features of a vector layer with the same 3D symbol.
Represents a vector layer which manages a vector based data sets.
Q_INVOKABLE QgsWkbTypes::GeometryType geometryType() const
Returns point, line or polygon.
void selectionChanged(const QgsFeatureIds &selected, const QgsFeatureIds &deselected, bool clearAndSelect)
Emitted when selection was changed.
QgsMapLayerType
Types of layers that can be added to a map.
Definition: qgis.h:47
@ PointCloudLayer
Added in 3.18.
@ MeshLayer
Added in 3.2.
@ VectorTileLayer
Added in 3.14.
@ AnnotationLayer
Contains freeform, georeferenced annotations. Added in QGIS 3.16.
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:1263
void addQLayerComponentsToHierarchy(Qt3DCore::QEntity *entity, const QVector< Qt3DRender::QLayer * > layers)
QgsChunkedEntity::SceneState _sceneState(QgsCameraController *cameraController)
void removeQLayerComponentsFromHierarchy(Qt3DCore::QEntity *entity)
qint64 QgsFeatureId
64 bit feature ids negative numbers are used for uncommitted/newly added features
Definition: qgsfeatureid.h:28
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39