QGIS API Documentation  3.24.2-Tisler (13c1a02865)
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Modules Pages
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  addCameraRotationCenterEntity( mCameraController );
122  updateLights();
123 
124  // create terrain entity
125 
126  createTerrainDeferred();
127  connect( &map, &Qgs3DMapSettings::terrainGeneratorChanged, this, &Qgs3DMapScene::createTerrain );
128  connect( &map, &Qgs3DMapSettings::terrainVerticalScaleChanged, this, &Qgs3DMapScene::createTerrain );
129  connect( &map, &Qgs3DMapSettings::mapTileResolutionChanged, this, &Qgs3DMapScene::createTerrain );
130  connect( &map, &Qgs3DMapSettings::maxTerrainScreenErrorChanged, this, &Qgs3DMapScene::createTerrain );
131  connect( &map, &Qgs3DMapSettings::maxTerrainGroundErrorChanged, this, &Qgs3DMapScene::createTerrain );
132  connect( &map, &Qgs3DMapSettings::terrainShadingChanged, this, &Qgs3DMapScene::createTerrain );
133  connect( &map, &Qgs3DMapSettings::pointLightsChanged, this, &Qgs3DMapScene::updateLights );
134  connect( &map, &Qgs3DMapSettings::directionalLightsChanged, this, &Qgs3DMapScene::updateLights );
135  connect( &map, &Qgs3DMapSettings::showLightSourceOriginsChanged, this, &Qgs3DMapScene::updateLights );
136  connect( &map, &Qgs3DMapSettings::fieldOfViewChanged, this, &Qgs3DMapScene::updateCameraLens );
137  connect( &map, &Qgs3DMapSettings::projectionTypeChanged, this, &Qgs3DMapScene::updateCameraLens );
138  connect( &map, &Qgs3DMapSettings::renderersChanged, this, &Qgs3DMapScene::onRenderersChanged );
139  connect( &map, &Qgs3DMapSettings::skyboxSettingsChanged, this, &Qgs3DMapScene::onSkyboxSettingsChanged );
140  connect( &map, &Qgs3DMapSettings::shadowSettingsChanged, this, &Qgs3DMapScene::onShadowSettingsChanged );
141  connect( &map, &Qgs3DMapSettings::eyeDomeLightingEnabledChanged, this, &Qgs3DMapScene::onEyeDomeShadingSettingsChanged );
142  connect( &map, &Qgs3DMapSettings::eyeDomeLightingStrengthChanged, this, &Qgs3DMapScene::onEyeDomeShadingSettingsChanged );
143  connect( &map, &Qgs3DMapSettings::eyeDomeLightingDistanceChanged, this, &Qgs3DMapScene::onEyeDomeShadingSettingsChanged );
144  connect( &map, &Qgs3DMapSettings::debugShadowMapSettingsChanged, this, &Qgs3DMapScene::onDebugShadowMapSettingsChanged );
145  connect( &map, &Qgs3DMapSettings::debugDepthMapSettingsChanged, this, &Qgs3DMapScene::onDebugDepthMapSettingsChanged );
147  connect( &map, &Qgs3DMapSettings::cameraMovementSpeedChanged, this, &Qgs3DMapScene::onCameraMovementSpeedChanged );
148 
149  connect( QgsApplication::sourceCache(), &QgsSourceCache::remoteSourceFetched, this, [ = ]( const QString & url )
150  {
151  const QList<QgsMapLayer *> modelVectorLayers = mModelVectorLayers;
152  for ( QgsMapLayer *layer : modelVectorLayers )
153  {
154  QgsAbstract3DRenderer *renderer = layer->renderer3D();
155  if ( renderer )
156  {
157  if ( renderer->type() == QLatin1String( "vector" ) )
158  {
159  const QgsPoint3DSymbol *pointSymbol = static_cast< const QgsPoint3DSymbol * >( static_cast< QgsVectorLayer3DRenderer *>( renderer )->symbol() );
160  if ( pointSymbol->shapeProperties()[QStringLiteral( "model" )].toString() == url )
161  {
162  removeLayerEntity( layer );
163  addLayerEntity( layer );
164  }
165  }
166  else if ( renderer->type() == QLatin1String( "rulebased" ) )
167  {
168  const QgsRuleBased3DRenderer::RuleList rules = static_cast< QgsRuleBased3DRenderer *>( renderer )->rootRule()->descendants();
169  for ( auto rule : rules )
170  {
171  const QgsPoint3DSymbol *pointSymbol = dynamic_cast< const QgsPoint3DSymbol * >( rule->symbol() );
172  if ( pointSymbol->shapeProperties()[QStringLiteral( "model" )].toString() == url )
173  {
174  removeLayerEntity( layer );
175  addLayerEntity( layer );
176  break;
177  }
178  }
179  }
180  }
181  }
182  } );
183 
184  // create entities of renderers
185 
186  onRenderersChanged();
187 
188  // listen to changes of layers in order to add/remove 3D renderer entities
189  connect( &map, &Qgs3DMapSettings::layersChanged, this, &Qgs3DMapScene::onLayersChanged );
190 
191 
192 #if 0
193  ChunkedEntity *testChunkEntity = new ChunkedEntity( AABB( -500, 0, -500, 500, 100, 500 ), 2.f, 3.f, 7, new TestChunkLoaderFactory );
194  testChunkEntity->setEnabled( false );
195  testChunkEntity->setParent( this );
196  chunkEntities << testChunkEntity;
197 #endif
198 
199  connect( mCameraController, &QgsCameraController::cameraChanged, this, &Qgs3DMapScene::onCameraChanged );
200  connect( mCameraController, &QgsCameraController::viewportChanged, this, &Qgs3DMapScene::onCameraChanged );
201 
202 #if 0
203  // experiments with loading of existing 3D models.
204 
205  // scene loader only gets loaded only when added to a scene...
206  // it loads everything: geometries, materials, transforms, lights, cameras (if any)
207  Qt3DCore::QEntity *loaderEntity = new Qt3DCore::QEntity;
208  Qt3DRender::QSceneLoader *loader = new Qt3DRender::QSceneLoader;
209  loader->setSource( QUrl( "file:///home/martin/Downloads/LowPolyModels/tree.dae" ) );
210  loaderEntity->addComponent( loader );
211  loaderEntity->setParent( this );
212 
213  // mesh loads just geometry as one geometry...
214  // so if there are different materials (e.g. colors) used in the model, this information is lost
215  Qt3DCore::QEntity *meshEntity = new Qt3DCore::QEntity;
216  Qt3DRender::QMesh *mesh = new Qt3DRender::QMesh;
217  mesh->setSource( QUrl( "file:///home/martin/Downloads/LowPolyModels/tree.obj" ) );
218  meshEntity->addComponent( mesh );
219  Qt3DExtras::QPhongMaterial *material = new Qt3DExtras::QPhongMaterial;
220  material->setAmbient( Qt::red );
221  meshEntity->addComponent( material );
222  Qt3DCore::QTransform *meshTransform = new Qt3DCore::QTransform;
223  meshTransform->setScale( 1 );
224  meshEntity->addComponent( meshTransform );
225  meshEntity->setParent( this );
226 #endif
227  onSkyboxSettingsChanged();
228 
229  // force initial update of chunked entities
230  onCameraChanged();
231  // force initial update of eye dome shading
232  onEyeDomeShadingSettingsChanged();
233  // force initial update of debugging setting of preview quads
234  onDebugShadowMapSettingsChanged();
235  onDebugDepthMapSettingsChanged();
236 
237  mCameraController->setCameraNavigationMode( mMap.cameraNavigationMode() );
238  onCameraMovementSpeedChanged();
239 }
240 
242 {
243  QgsRectangle extent = sceneExtent();
244  float side = std::max( extent.width(), extent.height() );
245  float a = side / 2.0f / std::sin( qDegreesToRadians( cameraController()->camera()->fieldOfView() ) / 2.0f );
246  // Note: the 1.5 multiplication is to move the view upwards to look better
247  mCameraController->resetView( 1.5 * std::sqrt( a * a - side * side ) ); // assuming FOV being 45 degrees
248 }
249 
251 {
252  return mTerrain ? mTerrain->pendingJobsCount() : 0;
253 }
254 
256 {
257  int count = 0;
258  for ( QgsChunkedEntity *entity : std::as_const( mChunkEntities ) )
259  count += entity->pendingJobsCount();
260  return count;
261 }
262 
264 {
265  if ( mPickHandlers.isEmpty() )
266  {
267  // we need to add object pickers
268  for ( Qt3DCore::QEntity *entity : mLayerEntities.values() )
269  {
270  if ( QgsChunkedEntity *chunkedEntity = qobject_cast<QgsChunkedEntity *>( entity ) )
271  chunkedEntity->setPickingEnabled( true );
272  }
273  }
274 
275  mPickHandlers.append( pickHandler );
276 }
277 
279 {
280  mPickHandlers.removeOne( pickHandler );
281 
282  if ( mPickHandlers.isEmpty() )
283  {
284  // we need to remove pickers
285  for ( Qt3DCore::QEntity *entity : mLayerEntities.values() )
286  {
287  if ( QgsChunkedEntity *chunkedEntity = qobject_cast<QgsChunkedEntity *>( entity ) )
288  chunkedEntity->setPickingEnabled( false );
289  }
290  }
291 }
292 
293 void Qgs3DMapScene::onLayerEntityPickedObject( Qt3DRender::QPickEvent *pickEvent, QgsFeatureId fid )
294 {
295  QgsMapLayer *layer = mLayerEntities.key( qobject_cast<QgsChunkedEntity *>( sender() ) );
296  if ( !layer )
297  return;
298 
299  QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
300  if ( !vlayer )
301  return;
302 
303  for ( Qgs3DMapScenePickHandler *pickHandler : std::as_const( mPickHandlers ) )
304  {
305  pickHandler->handlePickOnVectorLayer( vlayer, fid, pickEvent->worldIntersection(), pickEvent );
306  }
307 }
308 
309 float Qgs3DMapScene::worldSpaceError( float epsilon, float distance )
310 {
311  Qt3DRender::QCamera *camera = mCameraController->camera();
312  float fov = camera->fieldOfView();
313  QRect rect = mCameraController->viewport();
314  float screenSizePx = std::max( rect.width(), rect.height() ); // TODO: is this correct?
315 
316  // in qgschunkedentity_p.cpp there is inverse calculation (world space error to screen space error)
317  // with explanation of the math.
318  float frustumWidthAtDistance = 2 * distance * tan( fov / 2 );
319  float err = frustumWidthAtDistance * epsilon / screenSizePx;
320  return err;
321 }
322 
323 QgsChunkedEntity::SceneState _sceneState( QgsCameraController *cameraController )
324 {
325  Qt3DRender::QCamera *camera = cameraController->camera();
326  QgsChunkedEntity::SceneState state;
327  state.cameraFov = camera->fieldOfView();
328  state.cameraPos = camera->position();
329  QRect rect = cameraController->viewport();
330  state.screenSizePx = std::max( rect.width(), rect.height() ); // TODO: is this correct?
331  state.viewProjectionMatrix = camera->projectionMatrix() * camera->viewMatrix();
332  return state;
333 }
334 
335 void Qgs3DMapScene::onCameraChanged()
336 {
337  if ( mMap.projectionType() == Qt3DRender::QCameraLens::OrthographicProjection )
338  {
339  QRect viewportRect( QPoint( 0, 0 ), mEngine->size() );
340  const float viewWidthFromCenter = mCameraController->distance();
341  const float viewHeightFromCenter = viewportRect.height() * viewWidthFromCenter / viewportRect.width();
342  mEngine->camera()->lens()->setOrthographicProjection( -viewWidthFromCenter, viewWidthFromCenter, -viewHeightFromCenter, viewHeightFromCenter, mEngine->camera()->nearPlane(), mEngine->camera()->farPlane() );
343  }
344 
345  updateScene();
346  bool changedCameraPlanes = updateCameraNearFarPlanes();
347 
348  if ( changedCameraPlanes )
349  {
350  // repeat update of entities - because we have updated camera's near/far planes,
351  // the active nodes may have changed as well
352  updateScene();
353  updateCameraNearFarPlanes();
354  }
355 
356  onShadowSettingsChanged();
357 }
358 
359 void removeQLayerComponentsFromHierarchy( Qt3DCore::QEntity *entity )
360 {
361  QVector<Qt3DCore::QComponent *> toBeRemovedComponents;
362  for ( Qt3DCore::QComponent *component : entity->components() )
363  {
364  Qt3DRender::QLayer *layer = qobject_cast<Qt3DRender::QLayer *>( component );
365  if ( layer != nullptr )
366  toBeRemovedComponents.push_back( layer );
367  }
368  for ( Qt3DCore::QComponent *component : toBeRemovedComponents )
369  entity->removeComponent( component );
370  for ( Qt3DCore::QEntity *obj : entity->findChildren<Qt3DCore::QEntity *>() )
371  {
372  if ( obj != nullptr )
374  }
375 }
376 
377 void addQLayerComponentsToHierarchy( Qt3DCore::QEntity *entity, const QVector<Qt3DRender::QLayer *> layers )
378 {
379  for ( Qt3DRender::QLayer *layer : layers )
380  entity->addComponent( layer );
381  for ( Qt3DCore::QEntity *child : entity->findChildren<Qt3DCore::QEntity *>() )
382  {
383  if ( child != nullptr )
384  addQLayerComponentsToHierarchy( child, layers );
385  }
386 }
387 
388 void Qgs3DMapScene::updateScene()
389 {
390  QgsEventTracing::addEvent( QgsEventTracing::Instant, QStringLiteral( "3D" ), QStringLiteral( "Update Scene" ) );
391  for ( QgsChunkedEntity *entity : std::as_const( mChunkEntities ) )
392  {
393  if ( entity->isEnabled() )
394  entity->update( _sceneState( mCameraController ) );
395  }
396  updateSceneState();
397 }
398 
399 static void _updateNearFarPlane( const QList<QgsChunkNode *> &activeNodes, const QMatrix4x4 &viewMatrix, float &fnear, float &ffar )
400 {
401  for ( QgsChunkNode *node : activeNodes )
402  {
403  // project each corner of bbox to camera coordinates
404  // and determine closest and farthest point.
405  QgsAABB bbox = node->bbox();
406  for ( int i = 0; i < 8; ++i )
407  {
408  QVector4D p( ( ( i >> 0 ) & 1 ) ? bbox.xMin : bbox.xMax,
409  ( ( i >> 1 ) & 1 ) ? bbox.yMin : bbox.yMax,
410  ( ( i >> 2 ) & 1 ) ? bbox.zMin : bbox.zMax, 1 );
411 
412  QVector4D pc = viewMatrix * p;
413 
414 
415  float dst = -pc.z(); // in camera coordinates, x grows right, y grows down, z grows to the back
416  fnear = std::min( fnear, dst );
417  ffar = std::max( ffar, dst );
418  }
419  }
420 }
421 
422 bool Qgs3DMapScene::updateCameraNearFarPlanes()
423 {
424  // Update near and far plane from the terrain.
425  // this needs to be done with great care as we have kind of circular dependency here:
426  // active nodes are culled based on the current frustum (which involves near + far plane)
427  // and then based on active nodes we set near and far plane.
428  //
429  // All of this is just heuristics assuming that all other stuff is being rendered somewhere
430  // around the area where the terrain is.
431  //
432  // Near/far plane is setup in order to make best use of the depth buffer to avoid:
433  // 1. precision errors - if the range is too great
434  // 2. unwanted clipping of scene - if the range is too small
435 
436  Qt3DRender::QCamera *camera = cameraController()->camera();
437  QMatrix4x4 viewMatrix = camera->viewMatrix();
438  float fnear = 1e9;
439  float ffar = 0;
440  QList<QgsChunkNode *> activeNodes;
441  if ( mTerrain )
442  activeNodes = mTerrain->activeNodes();
443 
444  // it could be that there are no active nodes - they could be all culled or because root node
445  // is not yet loaded - we still need at least something to understand bounds of our scene
446  // so lets use the root node
447  if ( mTerrain && activeNodes.isEmpty() )
448  activeNodes << mTerrain->rootNode();
449 
450  _updateNearFarPlane( activeNodes, viewMatrix, fnear, ffar );
451 
452  // Also involve all the other chunked entities to make sure that they will not get
453  // clipped by the near or far plane
454  for ( QgsChunkedEntity *e : std::as_const( mChunkEntities ) )
455  {
456  if ( e != mTerrain )
457  {
458  QList<QgsChunkNode *> activeEntityNodes = e->activeNodes();
459  if ( activeEntityNodes.empty() )
460  activeEntityNodes << e->rootNode();
461  _updateNearFarPlane( activeEntityNodes, viewMatrix, fnear, ffar );
462  }
463  }
464 
465  if ( fnear < 1 )
466  fnear = 1; // does not really make sense to use negative far plane (behind camera)
467 
468  if ( fnear == 1e9 && ffar == 0 )
469  {
470  // the update didn't work out... this should not happen
471  // well at least temporarily use some conservative starting values
472  qWarning() << "oops... this should not happen! couldn't determine near/far plane. defaulting to 1...1e9";
473  fnear = 1;
474  ffar = 1e9;
475  }
476 
477  // set near/far plane - with some tolerance in front/behind expected near/far planes
478  float newFar = ffar * 2;
479  float newNear = fnear / 2;
480  if ( !qgsFloatNear( newFar, camera->farPlane() ) || !qgsFloatNear( newNear, camera->nearPlane() ) )
481  {
482  camera->setFarPlane( newFar );
483  camera->setNearPlane( newNear );
484  return true;
485  }
486 
487  return false;
488 }
489 
490 void Qgs3DMapScene::onFrameTriggered( float dt )
491 {
492  mCameraController->frameTriggered( dt );
493 
494  for ( QgsChunkedEntity *entity : std::as_const( mChunkEntities ) )
495  {
496  if ( entity->isEnabled() && entity->needsUpdate() )
497  {
498  QgsDebugMsgLevel( QStringLiteral( "need for update" ), 2 );
499  entity->update( _sceneState( mCameraController ) );
500  }
501  }
502 
503  updateSceneState();
504 
505  // lock changing the FPS counter to 5 fps
506  static int frameCount = 0;
507  static float accumulatedTime = 0.0f;
508 
509  if ( !mMap.isFpsCounterEnabled() )
510  {
511  frameCount = 0;
512  accumulatedTime = 0;
513  return;
514  }
515 
516  frameCount++;
517  accumulatedTime += dt;
518  if ( accumulatedTime >= 0.2f )
519  {
520  float fps = ( float )frameCount / accumulatedTime;
521  frameCount = 0;
522  accumulatedTime = 0.0f;
523  emit fpsCountChanged( fps );
524  }
525 }
526 
527 void Qgs3DMapScene::createTerrain()
528 {
529  if ( mTerrain )
530  {
531  mChunkEntities.removeOne( mTerrain );
532 
533  mTerrain->deleteLater();
534  mTerrain = nullptr;
535  }
536 
537  if ( !mTerrainUpdateScheduled )
538  {
539  // defer re-creation of terrain: there may be multiple invocations of this slot, so create the new entity just once
540  QTimer::singleShot( 0, this, &Qgs3DMapScene::createTerrainDeferred );
541  mTerrainUpdateScheduled = true;
542  setSceneState( Updating );
543  }
544  else
545  {
546  emit terrainEntityChanged();
547  }
548 }
549 
550 void Qgs3DMapScene::createTerrainDeferred()
551 {
552  if ( mMap.terrainGenerator() )
553  {
554  double tile0width = mMap.terrainGenerator()->extent().width();
555  int maxZoomLevel = Qgs3DUtils::maxZoomLevel( tile0width, mMap.mapTileResolution(), mMap.maxTerrainGroundError() );
556  QgsAABB rootBbox = mMap.terrainGenerator()->rootChunkBbox( mMap );
557  float rootError = mMap.terrainGenerator()->rootChunkError( mMap );
558  mMap.terrainGenerator()->setupQuadtree( rootBbox, rootError, maxZoomLevel );
559 
560  mTerrain = new QgsTerrainEntity( mMap );
561  mTerrain->setParent( this );
562  mTerrain->setShowBoundingBoxes( mMap.showTerrainBoundingBoxes() );
563 
564  mCameraController->setTerrainEntity( mTerrain );
565 
566  mChunkEntities << mTerrain;
567 
568  connect( mTerrain, &QgsChunkedEntity::pendingJobsCountChanged, this, &Qgs3DMapScene::totalPendingJobsCountChanged );
569  connect( mTerrain, &QgsTerrainEntity::pendingJobsCountChanged, this, &Qgs3DMapScene::terrainPendingJobsCountChanged );
570  }
571  else
572  {
573  mTerrain = nullptr;
574  mCameraController->setTerrainEntity( mTerrain );
575  }
576 
577  // make sure that renderers for layers are re-created as well
578  const QList<QgsMapLayer *> layers = mMap.layers();
579  for ( QgsMapLayer *layer : layers )
580  {
581  // remove old entity - if any
582  removeLayerEntity( layer );
583 
584  // add new entity - if any 3D renderer
585  addLayerEntity( layer );
586  }
587 
588  emit terrainEntityChanged();
589  onCameraChanged(); // force update of the new terrain
590  mTerrainUpdateScheduled = false;
591 }
592 
593 void Qgs3DMapScene::onBackgroundColorChanged()
594 {
595  mEngine->setClearColor( mMap.backgroundColor() );
596 }
597 
598 void Qgs3DMapScene::updateLights()
599 {
600  for ( Qt3DCore::QEntity *entity : std::as_const( mLightEntities ) )
601  entity->deleteLater();
602  mLightEntities.clear();
603  for ( Qt3DCore::QEntity *entity : std::as_const( mLightOriginEntities ) )
604  entity->deleteLater();
605  mLightOriginEntities.clear();
606 
607  auto createLightOriginEntity = [ = ]( QVector3D translation, const QColor & color )->Qt3DCore::QEntity *
608  {
609  Qt3DCore::QEntity *originEntity = new Qt3DCore::QEntity;
610 
611  Qt3DCore::QTransform *trLightOriginCenter = new Qt3DCore::QTransform;
612  trLightOriginCenter->setTranslation( translation );
613  originEntity->addComponent( trLightOriginCenter );
614 
615  Qt3DExtras::QPhongMaterial *materialLightOriginCenter = new Qt3DExtras::QPhongMaterial;
616  materialLightOriginCenter->setAmbient( color );
617  originEntity->addComponent( materialLightOriginCenter );
618 
619  Qt3DExtras::QSphereMesh *rendererLightOriginCenter = new Qt3DExtras::QSphereMesh;
620  rendererLightOriginCenter->setRadius( 20 );
621  originEntity->addComponent( rendererLightOriginCenter );
622 
623  originEntity->setEnabled( true );
624  originEntity->setParent( this );
625 
626  return originEntity;
627  };
628 
629  const auto newPointLights = mMap.pointLights();
630  for ( const QgsPointLightSettings &pointLightSettings : newPointLights )
631  {
632  Qt3DCore::QEntity *lightEntity = new Qt3DCore::QEntity;
633  Qt3DCore::QTransform *lightTransform = new Qt3DCore::QTransform;
634  lightTransform->setTranslation( QVector3D( pointLightSettings.position().x(),
635  pointLightSettings.position().y(),
636  pointLightSettings.position().z() ) );
637 
638  Qt3DRender::QPointLight *light = new Qt3DRender::QPointLight;
639  light->setColor( pointLightSettings.color() );
640  light->setIntensity( pointLightSettings.intensity() );
641 
642  light->setConstantAttenuation( pointLightSettings.constantAttenuation() );
643  light->setLinearAttenuation( pointLightSettings.linearAttenuation() );
644  light->setQuadraticAttenuation( pointLightSettings.quadraticAttenuation() );
645 
646  lightEntity->addComponent( light );
647  lightEntity->addComponent( lightTransform );
648  lightEntity->setParent( this );
649  mLightEntities << lightEntity;
650 
651  if ( mMap.showLightSourceOrigins() )
652  mLightOriginEntities << createLightOriginEntity( lightTransform->translation(), pointLightSettings.color() );
653  }
654 
655  const auto newDirectionalLights = mMap.directionalLights();
656  for ( const QgsDirectionalLightSettings &directionalLightSettings : newDirectionalLights )
657  {
658  Qt3DCore::QEntity *lightEntity = new Qt3DCore::QEntity;
659  Qt3DCore::QTransform *lightTransform = new Qt3DCore::QTransform;
660 
661  Qt3DRender::QDirectionalLight *light = new Qt3DRender::QDirectionalLight;
662  light->setColor( directionalLightSettings.color() );
663  light->setIntensity( directionalLightSettings.intensity() );
664  QgsVector3D direction = directionalLightSettings.direction();
665  light->setWorldDirection( QVector3D( direction.x(), direction.y(), direction.z() ) );
666 
667  lightEntity->addComponent( light );
668  lightEntity->addComponent( lightTransform );
669  lightEntity->setParent( this );
670  mLightEntities << lightEntity;
671  }
672 
673  onShadowSettingsChanged();
674 }
675 
676 void Qgs3DMapScene::updateCameraLens()
677 {
678  mEngine->camera()->lens()->setFieldOfView( mMap.fieldOfView() );
679  mEngine->camera()->lens()->setProjectionType( mMap.projectionType() );
680  onCameraChanged();
681 }
682 
683 void Qgs3DMapScene::onRenderersChanged()
684 {
685  // remove entities (if any)
686  qDeleteAll( mRenderersEntities );
687  mRenderersEntities.clear();
688 
689  // re-add entities from new set of renderers
690  const QList<QgsAbstract3DRenderer *> renderers = mMap.renderers();
691  for ( const QgsAbstract3DRenderer *renderer : renderers )
692  {
693  Qt3DCore::QEntity *newEntity = renderer->createEntity( mMap );
694  if ( newEntity )
695  {
696  newEntity->setParent( this );
697  finalizeNewEntity( newEntity );
698  mRenderersEntities[renderer] = newEntity;
699  }
700  }
701 }
702 
703 void Qgs3DMapScene::onLayerRenderer3DChanged()
704 {
705  QgsMapLayer *layer = qobject_cast<QgsMapLayer *>( sender() );
706  Q_ASSERT( layer );
707 
708  // remove old entity - if any
709  removeLayerEntity( layer );
710 
711  // add new entity - if any 3D renderer
712  addLayerEntity( layer );
713 }
714 
715 void Qgs3DMapScene::onLayersChanged()
716 {
717  QSet<QgsMapLayer *> layersBefore = qgis::listToSet( mLayerEntities.keys() );
718  QList<QgsMapLayer *> layersAdded;
719  const QList<QgsMapLayer *> layers = mMap.layers();
720  for ( QgsMapLayer *layer : layers )
721  {
722  if ( !layersBefore.contains( layer ) )
723  {
724  layersAdded << layer;
725  }
726  else
727  {
728  layersBefore.remove( layer );
729  }
730  }
731 
732  // what is left in layersBefore are layers that have been removed
733  for ( QgsMapLayer *layer : std::as_const( layersBefore ) )
734  {
735  removeLayerEntity( layer );
736  }
737 
738  for ( QgsMapLayer *layer : std::as_const( layersAdded ) )
739  {
740  addLayerEntity( layer );
741  }
742 }
743 
745 {
746  for ( auto layer : mLayerEntities.keys() )
747  {
748  if ( layer->temporalProperties()->isActive() )
749  {
750  removeLayerEntity( layer );
751  addLayerEntity( layer );
752  }
753  }
754 }
755 
756 void Qgs3DMapScene::addLayerEntity( QgsMapLayer *layer )
757 {
758  bool needsSceneUpdate = false;
759  QgsAbstract3DRenderer *renderer = layer->renderer3D();
760  if ( renderer )
761  {
762  // Fix vector layer's renderer to make sure the renderer is pointing to its layer.
763  // It has happened before that renderer pointed to a different layer (probably after copying a style).
764  // This is a bit of a hack and it should be handled in QgsMapLayer::setRenderer3D() but in qgis_core
765  // the vector layer 3D renderer classes are not available.
766  if ( layer->type() == QgsMapLayerType::VectorLayer &&
767  ( renderer->type() == QLatin1String( "vector" ) || renderer->type() == QLatin1String( "rulebased" ) ) )
768  {
769  static_cast<QgsAbstractVectorLayer3DRenderer *>( renderer )->setLayer( static_cast<QgsVectorLayer *>( layer ) );
770  if ( renderer->type() == QLatin1String( "vector" ) )
771  {
772  QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
773  if ( vlayer->geometryType() == QgsWkbTypes::PointGeometry )
774  {
775  const QgsPoint3DSymbol *pointSymbol = static_cast< const QgsPoint3DSymbol * >( static_cast< QgsVectorLayer3DRenderer *>( renderer )->symbol() );
776  if ( pointSymbol->shape() == QgsPoint3DSymbol::Model )
777  {
778  mModelVectorLayers.append( layer );
779  }
780  }
781  }
782  else if ( renderer->type() == QLatin1String( "rulebased" ) )
783  {
784  const QgsRuleBased3DRenderer::RuleList rules = static_cast< QgsRuleBased3DRenderer *>( renderer )->rootRule()->descendants();
785  for ( auto rule : rules )
786  {
787  const QgsPoint3DSymbol *pointSymbol = dynamic_cast< const QgsPoint3DSymbol * >( rule->symbol() );
788  if ( pointSymbol && pointSymbol->shape() == QgsPoint3DSymbol::Model )
789  {
790  mModelVectorLayers.append( layer );
791  break;
792  }
793  }
794  }
795  }
796  else if ( layer->type() == QgsMapLayerType::MeshLayer && renderer->type() == QLatin1String( "mesh" ) )
797  {
798  QgsMeshLayer3DRenderer *meshRenderer = static_cast<QgsMeshLayer3DRenderer *>( renderer );
799  meshRenderer->setLayer( static_cast<QgsMeshLayer *>( layer ) );
800 
801  // Before entity creation, set the maximum texture size
802  // Not very clean, but for now, only place found in the workflow to do that simple
803  QgsMesh3DSymbol *sym = meshRenderer->symbol()->clone();
804  sym->setMaximumTextureSize( maximumTextureSize() );
805  meshRenderer->setSymbol( sym );
806  }
807  else if ( layer->type() == QgsMapLayerType::PointCloudLayer && renderer->type() == QLatin1String( "pointcloud" ) )
808  {
809  QgsPointCloudLayer3DRenderer *pointCloudRenderer = static_cast<QgsPointCloudLayer3DRenderer *>( renderer );
810  pointCloudRenderer->setLayer( static_cast<QgsPointCloudLayer *>( layer ) );
811  }
812 
813  Qt3DCore::QEntity *newEntity = renderer->createEntity( mMap );
814  if ( newEntity )
815  {
816  newEntity->setParent( this );
817  mLayerEntities.insert( layer, newEntity );
818 
819  finalizeNewEntity( newEntity );
820 
821  if ( QgsChunkedEntity *chunkedNewEntity = qobject_cast<QgsChunkedEntity *>( newEntity ) )
822  {
823  mChunkEntities.append( chunkedNewEntity );
824  needsSceneUpdate = true;
825 
826  chunkedNewEntity->setPickingEnabled( !mPickHandlers.isEmpty() );
827  connect( chunkedNewEntity, &QgsChunkedEntity::pickedObject, this, &Qgs3DMapScene::onLayerEntityPickedObject );
828 
829  connect( chunkedNewEntity, &QgsChunkedEntity::newEntityCreated, this, [this]( Qt3DCore::QEntity * entity )
830  {
831  finalizeNewEntity( entity );
832  } );
833 
834  connect( chunkedNewEntity, &QgsChunkedEntity::pendingJobsCountChanged, this, &Qgs3DMapScene::totalPendingJobsCountChanged );
835  }
836  }
837  }
838 
839  if ( needsSceneUpdate )
840  onCameraChanged(); // needed for chunked entities
841 
842  connect( layer, &QgsMapLayer::request3DUpdate, this, &Qgs3DMapScene::onLayerRenderer3DChanged );
843 
844  if ( layer->type() == QgsMapLayerType::VectorLayer )
845  {
846  QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
847  connect( vlayer, &QgsVectorLayer::selectionChanged, this, &Qgs3DMapScene::onLayerRenderer3DChanged );
848  connect( vlayer, &QgsVectorLayer::layerModified, this, &Qgs3DMapScene::onLayerRenderer3DChanged );
849  }
850 
851  if ( layer->type() == QgsMapLayerType::MeshLayer )
852  {
853  connect( layer, &QgsMapLayer::rendererChanged, this, &Qgs3DMapScene::onLayerRenderer3DChanged );
854  }
855 
856  if ( layer->type() == QgsMapLayerType::PointCloudLayer )
857  connect( layer, &QgsMapLayer::renderer3DChanged, this, &Qgs3DMapScene::onLayerRenderer3DChanged );
858 }
859 
860 void Qgs3DMapScene::removeLayerEntity( QgsMapLayer *layer )
861 {
862  Qt3DCore::QEntity *entity = mLayerEntities.take( layer );
863 
864  if ( QgsChunkedEntity *chunkedEntity = qobject_cast<QgsChunkedEntity *>( entity ) )
865  {
866  mChunkEntities.removeOne( chunkedEntity );
867  }
868 
869  if ( entity )
870  entity->deleteLater();
871 
872  disconnect( layer, &QgsMapLayer::request3DUpdate, this, &Qgs3DMapScene::onLayerRenderer3DChanged );
873 
874  if ( layer->type() == QgsMapLayerType::VectorLayer )
875  {
876  QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
877  disconnect( vlayer, &QgsVectorLayer::selectionChanged, this, &Qgs3DMapScene::onLayerRenderer3DChanged );
878  disconnect( vlayer, &QgsVectorLayer::layerModified, this, &Qgs3DMapScene::onLayerRenderer3DChanged );
879  mModelVectorLayers.removeAll( layer );
880  }
881 
882  if ( layer->type() == QgsMapLayerType::MeshLayer )
883  {
884  disconnect( layer, &QgsMapLayer::rendererChanged, this, &Qgs3DMapScene::onLayerRenderer3DChanged );
885  }
886 
887  if ( layer->type() == QgsMapLayerType::PointCloudLayer )
888  disconnect( layer, &QgsMapLayer::renderer3DChanged, this, &Qgs3DMapScene::onLayerRenderer3DChanged );
889 }
890 
891 void Qgs3DMapScene::finalizeNewEntity( Qt3DCore::QEntity *newEntity )
892 {
893  // this is probably not the best place for material-specific configuration,
894  // maybe this could be more generalized when other materials need some specific treatment
895  for ( QgsLineMaterial *lm : newEntity->findChildren<QgsLineMaterial *>() )
896  {
897  connect( mCameraController, &QgsCameraController::viewportChanged, lm, [lm, this]
898  {
899  lm->setViewportSize( mCameraController->viewport().size() );
900  } );
901 
902  lm->setViewportSize( cameraController()->viewport().size() );
903  }
904  // configure billboard's viewport when the viewport is changed.
905  for ( QgsPoint3DBillboardMaterial *bm : newEntity->findChildren<QgsPoint3DBillboardMaterial *>() )
906  {
907  connect( mCameraController, &QgsCameraController::viewportChanged, bm, [bm, this]
908  {
909  bm->setViewportSize( mCameraController->viewport().size() );
910  } );
911 
912  bm->setViewportSize( mCameraController->viewport().size() );
913  }
914 }
915 
916 int Qgs3DMapScene::maximumTextureSize() const
917 {
918  QSurface *surface = mEngine->surface();
919  QOpenGLContext context;
920  context.create();
921  bool success = context.makeCurrent( surface );
922 
923  if ( success )
924  {
925  QOpenGLFunctions openglFunctions = QOpenGLFunctions( &context );
926 
927  GLint size;
928  openglFunctions.initializeOpenGLFunctions();
929  openglFunctions.glGetIntegerv( GL_MAX_TEXTURE_SIZE, &size );
930  return int( size );
931  }
932  else
933  {
934  return 4096; //we can't have a context to defined the max texture size, we use this reasonable value
935  }
936 
937 }
938 
939 void Qgs3DMapScene::addCameraViewCenterEntity( Qt3DRender::QCamera *camera )
940 {
941  mEntityCameraViewCenter = new Qt3DCore::QEntity;
942 
943  Qt3DCore::QTransform *trCameraViewCenter = new Qt3DCore::QTransform;
944  mEntityCameraViewCenter->addComponent( trCameraViewCenter );
945  connect( camera, &Qt3DRender::QCamera::viewCenterChanged, this, [trCameraViewCenter, camera]
946  {
947  trCameraViewCenter->setTranslation( camera->viewCenter() );
948  } );
949 
950  Qt3DExtras::QPhongMaterial *materialCameraViewCenter = new Qt3DExtras::QPhongMaterial;
951  materialCameraViewCenter->setAmbient( Qt::red );
952  mEntityCameraViewCenter->addComponent( materialCameraViewCenter );
953 
954  Qt3DExtras::QSphereMesh *rendererCameraViewCenter = new Qt3DExtras::QSphereMesh;
955  rendererCameraViewCenter->setRadius( 10 );
956  mEntityCameraViewCenter->addComponent( rendererCameraViewCenter );
957 
958  mEntityCameraViewCenter->setEnabled( mMap.showCameraViewCenter() );
959  mEntityCameraViewCenter->setParent( this );
960 
961  connect( &mMap, &Qgs3DMapSettings::showCameraViewCenterChanged, this, [this]
962  {
963  mEntityCameraViewCenter->setEnabled( mMap.showCameraViewCenter() );
964  } );
965 }
966 
967 void Qgs3DMapScene::setSceneState( Qgs3DMapScene::SceneState state )
968 {
969  if ( mSceneState == state )
970  return;
971  mSceneState = state;
972  emit sceneStateChanged();
973 }
974 
975 void Qgs3DMapScene::updateSceneState()
976 {
977  if ( mTerrainUpdateScheduled )
978  {
979  setSceneState( Updating );
980  return;
981  }
982 
983  for ( QgsChunkedEntity *entity : std::as_const( mChunkEntities ) )
984  {
985  if ( entity->isEnabled() && entity->pendingJobsCount() > 0 )
986  {
987  setSceneState( Updating );
988  return;
989  }
990  }
991 
992  setSceneState( Ready );
993 }
994 
995 void Qgs3DMapScene::onSkyboxSettingsChanged()
996 {
997  QgsSkyboxSettings skyboxSettings = mMap.skyboxSettings();
998  if ( mSkybox != nullptr )
999  {
1000  mSkybox->deleteLater();
1001  mSkybox = nullptr;
1002  }
1003 
1004  mEngine->setFrustumCullingEnabled( !mMap.isSkyboxEnabled() );
1005 
1006  if ( mMap.isSkyboxEnabled() )
1007  {
1008  QMap<QString, QString> faces;
1009  switch ( skyboxSettings.skyboxType() )
1010  {
1012  faces = skyboxSettings.cubeMapFacesPaths();
1013  mSkybox = new QgsCubeFacesSkyboxEntity(
1014  faces[QStringLiteral( "posX" )], faces[QStringLiteral( "posY" )], faces[QStringLiteral( "posZ" )],
1015  faces[QStringLiteral( "negX" )], faces[QStringLiteral( "negY" )], faces[QStringLiteral( "negZ" )],
1016  this
1017  );
1018  break;
1020  mSkybox = new QgsPanoramicSkyboxEntity( skyboxSettings.panoramicTexturePath(), this );
1021  break;
1022  }
1023  }
1024 }
1025 
1026 void Qgs3DMapScene::onShadowSettingsChanged()
1027 {
1028  QgsShadowRenderingFrameGraph *shadowRenderingFrameGraph = mEngine->frameGraph();
1029 
1030  QList<QgsDirectionalLightSettings> directionalLights = mMap.directionalLights();
1031  QgsShadowSettings shadowSettings = mMap.shadowSettings();
1032  int selectedLight = shadowSettings.selectedDirectionalLight();
1033  if ( shadowSettings.renderShadows() && selectedLight >= 0 && selectedLight < directionalLights.count() )
1034  {
1035  shadowRenderingFrameGraph->setShadowRenderingEnabled( true );
1036  shadowRenderingFrameGraph->setShadowBias( shadowSettings.shadowBias() );
1037  shadowRenderingFrameGraph->setShadowMapResolution( shadowSettings.shadowMapResolution() );
1038  QgsDirectionalLightSettings light = directionalLights[selectedLight];
1039  shadowRenderingFrameGraph->setupDirectionalLight( light, shadowSettings.maximumShadowRenderingDistance() );
1040  }
1041  else
1042  shadowRenderingFrameGraph->setShadowRenderingEnabled( false );
1043 }
1044 
1045 void Qgs3DMapScene::onDebugShadowMapSettingsChanged()
1046 {
1047  QgsShadowRenderingFrameGraph *shadowRenderingFrameGraph = mEngine->frameGraph();
1048  shadowRenderingFrameGraph->setupShadowMapDebugging( mMap.debugShadowMapEnabled(), mMap.debugShadowMapCorner(), mMap.debugShadowMapSize() );
1049 }
1050 
1051 void Qgs3DMapScene::onDebugDepthMapSettingsChanged()
1052 {
1053  QgsShadowRenderingFrameGraph *shadowRenderingFrameGraph = mEngine->frameGraph();
1054  shadowRenderingFrameGraph->setupDepthMapDebugging( mMap.debugDepthMapEnabled(), mMap.debugDepthMapCorner(), mMap.debugDepthMapSize() );
1055 }
1056 
1057 void Qgs3DMapScene::onEyeDomeShadingSettingsChanged()
1058 {
1059  QgsShadowRenderingFrameGraph *shadowRenderingFrameGraph = mEngine->frameGraph();
1060 
1061  bool edlEnabled = mMap.eyeDomeLightingEnabled();
1062  double edlStrength = mMap.eyeDomeLightingStrength();
1063  double edlDistance = mMap.eyeDomeLightingDistance();
1064  shadowRenderingFrameGraph->setupEyeDomeLighting( edlEnabled, edlStrength, edlDistance );
1065 }
1066 
1067 void Qgs3DMapScene::onCameraMovementSpeedChanged()
1068 {
1069  mCameraController->setCameraMovementSpeed( mMap.cameraMovementSpeed() );
1070 }
1071 
1073 {
1074  QVector<QString> notParsedLayers;
1075  Qgs3DSceneExporter exporter;
1076 
1077  exporter.setTerrainResolution( exportSettings.terrrainResolution() );
1078  exporter.setSmoothEdges( exportSettings.smoothEdges() );
1079  exporter.setExportNormals( exportSettings.exportNormals() );
1080  exporter.setExportTextures( exportSettings.exportTextures() );
1081  exporter.setTerrainTextureResolution( exportSettings.terrainTextureResolution() );
1082  exporter.setScale( exportSettings.scale() );
1083 
1084  for ( auto it = mLayerEntities.constBegin(); it != mLayerEntities.constEnd(); ++it )
1085  {
1086  QgsMapLayer *layer = it.key();
1087  Qt3DCore::QEntity *rootEntity = it.value();
1088  QgsMapLayerType layerType = layer->type();
1089  switch ( layerType )
1090  {
1092  if ( !exporter.parseVectorLayerEntity( rootEntity, qobject_cast<QgsVectorLayer *>( layer ) ) )
1093  notParsedLayers.push_back( layer->name() );
1094  break;
1102  notParsedLayers.push_back( layer->name() );
1103  break;
1104  }
1105  }
1106 
1107  if ( mTerrain )
1108  exporter.parseTerrain( mTerrain, "Terrain" );
1109 
1110  exporter.save( exportSettings.sceneName(), exportSettings.sceneFolderPath() );
1111 
1112  if ( !notParsedLayers.empty() )
1113  {
1114  QString message = tr( "The following layers were not exported:" ) + "\n";
1115  for ( const QString &layerName : notParsedLayers )
1116  message += layerName + "\n";
1117  QgsMessageOutput::showMessage( tr( "3D exporter warning" ), message, QgsMessageOutput::MessageText );
1118  }
1119 }
1120 
1121 QVector<const QgsChunkNode *> Qgs3DMapScene::getLayerActiveChunkNodes( QgsMapLayer *layer )
1122 {
1123  QVector<const QgsChunkNode *> chunks;
1124  if ( !mLayerEntities.contains( layer ) ) return chunks;
1125  if ( QgsChunkedEntity *c = qobject_cast<QgsChunkedEntity *>( mLayerEntities[ layer ] ) )
1126  {
1127  for ( QgsChunkNode *n : c->activeNodes() )
1128  chunks.push_back( n );
1129  }
1130  return chunks;
1131 }
1132 
1134 {
1135  QgsRectangle extent;
1136  extent.setMinimal();
1137 
1138  for ( QgsMapLayer *layer : mLayerEntities.keys() )
1139  {
1140  Qt3DCore::QEntity *layerEntity = mLayerEntities[ layer ];
1141  QgsChunkedEntity *c = qobject_cast<QgsChunkedEntity *>( layerEntity );
1142  if ( !c )
1143  continue;
1144  QgsChunkNode *chunkNode = c->rootNode();
1145  QgsAABB bbox = chunkNode->bbox();
1146  QgsRectangle layerExtent = Qgs3DUtils::worldToLayerExtent( bbox, layer->crs(), mMap.origin(), mMap.crs(), mMap.transformContext() );
1147  extent.combineExtentWith( layerExtent );
1148  }
1149 
1150  if ( QgsTerrainGenerator *terrainGenerator = mMap.terrainGenerator() )
1151  {
1152  QgsRectangle terrainExtent = terrainGenerator->extent();
1153  QgsCoordinateTransform terrainToMapTransform( terrainGenerator->crs(), mMap.crs(), QgsProject::instance() );
1154  terrainToMapTransform.setBallparkTransformsAreAppropriate( true );
1155  terrainExtent = terrainToMapTransform.transformBoundingBox( terrainExtent );
1156  extent.combineExtentWith( terrainExtent );
1157  }
1158 
1159  return extent;
1160 }
1161 
1162 void Qgs3DMapScene::addCameraRotationCenterEntity( QgsCameraController *controller )
1163 {
1164  mEntityRotationCenter = new Qt3DCore::QEntity;
1165 
1166  Qt3DCore::QTransform *trCameraViewCenter = new Qt3DCore::QTransform;
1167  mEntityRotationCenter->addComponent( trCameraViewCenter );
1168  Qt3DExtras::QPhongMaterial *materialCameraViewCenter = new Qt3DExtras::QPhongMaterial;
1169  materialCameraViewCenter->setAmbient( Qt::blue );
1170  mEntityRotationCenter->addComponent( materialCameraViewCenter );
1171  Qt3DExtras::QSphereMesh *rendererCameraViewCenter = new Qt3DExtras::QSphereMesh;
1172  rendererCameraViewCenter->setRadius( 10 );
1173  mEntityRotationCenter->addComponent( rendererCameraViewCenter );
1174  mEntityRotationCenter->setEnabled( true );
1175  mEntityRotationCenter->setParent( this );
1176 
1177  connect( controller, &QgsCameraController::cameraRotationCenterChanged, this, [trCameraViewCenter]( QVector3D center )
1178  {
1179  trCameraViewCenter->setTranslation( center );
1180  } );
1181 
1182  mEntityRotationCenter->setEnabled( mMap.showCameraRotationCenter() );
1183 
1184  connect( &mMap, &Qgs3DMapSettings::showCameraRotationCenterChanged, this, [this]
1185  {
1186  mEntityRotationCenter->setEnabled( mMap.showCameraRotationCenter() );
1187  } );
1188 }
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 showCameraRotationCenterChanged()
Emitted when the flag whether camera's rotation center is shown has changed.
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)
bool showCameraRotationCenter() const
Returns whether to show camera's rotation center as a sphere (for debugging)
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:522
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:220
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 QgsSourceCache * sourceCache()
Returns the application's source cache, used for caching embedded and remote source strings as local ...
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 cameraRotationCenterChanged(QVector3D position)
Emitted when the camera rotation center changes.
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:1489
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:470
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
Point cloud layer. Added in QGIS 3.18.
@ MeshLayer
Mesh layer. Added in QGIS 3.2.
@ VectorLayer
Vector layer.
@ RasterLayer
Raster layer.
@ GroupLayer
Composite group layer. Added in QGIS 3.24.
@ VectorTileLayer
Vector tile layer. Added in QGIS 3.14.
@ AnnotationLayer
Contains freeform, georeferenced annotations. Added in QGIS 3.16.
@ PluginLayer
Plugin based layer.
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:1595
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