QGIS API Documentation  3.12.1-BucureČ™ti (121cc00ff0)
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/QRenderSettings>
26 #include <Qt3DRender/QSceneLoader>
27 #include <Qt3DExtras/QForwardRenderer>
28 #include <Qt3DExtras/QPhongMaterial>
29 #include <Qt3DExtras/QSkyboxEntity>
30 #include <Qt3DExtras/QSphereMesh>
31 #include <Qt3DLogic/QFrameAction>
32 
33 #include <QTimer>
34 
35 #include "qgsaabb.h"
36 #include "qgsabstract3dengine.h"
38 #include "qgs3dmapsettings.h"
39 #include "qgs3dutils.h"
40 #include "qgsabstract3drenderer.h"
41 #include "qgscameracontroller.h"
42 #include "qgschunkedentity_p.h"
43 #include "qgschunknode_p.h"
44 #include "qgseventtracing.h"
45 #include "qgsmeshlayer.h"
46 #include "qgsmeshlayer3drenderer.h"
47 #include "qgsrulebased3drenderer.h"
48 #include "qgsterrainentity_p.h"
49 #include "qgsterraingenerator.h"
51 #include "qgsvectorlayer.h"
54 
55 #include "qgslinematerial_p.h"
56 
58  : mMap( map )
59  , mEngine( engine )
60 {
61 
62  connect( &map, &Qgs3DMapSettings::backgroundColorChanged, this, &Qgs3DMapScene::onBackgroundColorChanged );
63  onBackgroundColorChanged();
64 
65  // TODO: strange - setting OnDemand render policy still keeps QGIS busy (Qt 5.9.0)
66  // actually it is more busy than with the default "Always" policy although there are no changes in the scene.
67  //mRenderer->renderSettings()->setRenderPolicy( Qt3DRender::QRenderSettings::OnDemand );
68 
69 #if QT_VERSION >= 0x050900
70  // we want precise picking of terrain (also bounding volume picking does not seem to work - not sure why)
71  mEngine->renderSettings()->pickingSettings()->setPickMethod( Qt3DRender::QPickingSettings::TrianglePicking );
72 #endif
73 
74  QRect viewportRect( QPoint( 0, 0 ), mEngine->size() );
75 
76  // Camera
77  float aspectRatio = ( float )viewportRect.width() / viewportRect.height();
78  mEngine->camera()->lens()->setPerspectiveProjection( mMap.fieldOfView(), aspectRatio, 10.f, 10000.0f );
79 
80  mFrameAction = new Qt3DLogic::QFrameAction();
81  connect( mFrameAction, &Qt3DLogic::QFrameAction::triggered,
82  this, &Qgs3DMapScene::onFrameTriggered );
83  addComponent( mFrameAction ); // takes ownership
84 
85  // Camera controlling
86  mCameraController = new QgsCameraController( this ); // attaches to the scene
87  mCameraController->setViewport( viewportRect );
88  mCameraController->setCamera( mEngine->camera() );
89  mCameraController->resetView( 1000 );
90 
91  addCameraViewCenterEntity( mEngine->camera() );
92 
93  // create terrain entity
94 
95  createTerrainDeferred();
96  connect( &map, &Qgs3DMapSettings::terrainGeneratorChanged, this, &Qgs3DMapScene::createTerrain );
97  connect( &map, &Qgs3DMapSettings::terrainVerticalScaleChanged, this, &Qgs3DMapScene::createTerrain );
98  connect( &map, &Qgs3DMapSettings::mapTileResolutionChanged, this, &Qgs3DMapScene::createTerrain );
99  connect( &map, &Qgs3DMapSettings::maxTerrainScreenErrorChanged, this, &Qgs3DMapScene::createTerrain );
100  connect( &map, &Qgs3DMapSettings::maxTerrainGroundErrorChanged, this, &Qgs3DMapScene::createTerrain );
101  connect( &map, &Qgs3DMapSettings::terrainShadingChanged, this, &Qgs3DMapScene::createTerrain );
102  connect( &map, &Qgs3DMapSettings::pointLightsChanged, this, &Qgs3DMapScene::updateLights );
103  connect( &map, &Qgs3DMapSettings::fieldOfViewChanged, this, &Qgs3DMapScene::updateCameraLens );
104  connect( &map, &Qgs3DMapSettings::renderersChanged, this, &Qgs3DMapScene::onRenderersChanged );
105 
106  // create entities of renderers
107 
108  onRenderersChanged();
109 
110  // listen to changes of layers in order to add/remove 3D renderer entities
111  connect( &map, &Qgs3DMapSettings::layersChanged, this, &Qgs3DMapScene::onLayersChanged );
112 
113  updateLights();
114 
115 #if 0
116  ChunkedEntity *testChunkEntity = new ChunkedEntity( AABB( -500, 0, -500, 500, 100, 500 ), 2.f, 3.f, 7, new TestChunkLoaderFactory );
117  testChunkEntity->setEnabled( false );
118  testChunkEntity->setParent( this );
119  chunkEntities << testChunkEntity;
120 #endif
121 
122  connect( mCameraController, &QgsCameraController::cameraChanged, this, &Qgs3DMapScene::onCameraChanged );
123  connect( mCameraController, &QgsCameraController::viewportChanged, this, &Qgs3DMapScene::onCameraChanged );
124 
125 #if 0
126  // experiments with loading of existing 3D models.
127 
128  // scene loader only gets loaded only when added to a scene...
129  // it loads everything: geometries, materials, transforms, lights, cameras (if any)
130  Qt3DCore::QEntity *loaderEntity = new Qt3DCore::QEntity;
131  Qt3DRender::QSceneLoader *loader = new Qt3DRender::QSceneLoader;
132  loader->setSource( QUrl( "file:///home/martin/Downloads/LowPolyModels/tree.dae" ) );
133  loaderEntity->addComponent( loader );
134  loaderEntity->setParent( this );
135 
136  // mesh loads just geometry as one geometry...
137  // so if there are different materials (e.g. colors) used in the model, this information is lost
138  Qt3DCore::QEntity *meshEntity = new Qt3DCore::QEntity;
139  Qt3DRender::QMesh *mesh = new Qt3DRender::QMesh;
140  mesh->setSource( QUrl( "file:///home/martin/Downloads/LowPolyModels/tree.obj" ) );
141  meshEntity->addComponent( mesh );
142  Qt3DExtras::QPhongMaterial *material = new Qt3DExtras::QPhongMaterial;
143  material->setAmbient( Qt::red );
144  meshEntity->addComponent( material );
145  Qt3DCore::QTransform *meshTransform = new Qt3DCore::QTransform;
146  meshTransform->setScale( 1 );
147  meshEntity->addComponent( meshTransform );
148  meshEntity->setParent( this );
149 #endif
150 
151  if ( map.hasSkyboxEnabled() )
152  {
153  Qt3DExtras::QSkyboxEntity *skybox = new Qt3DExtras::QSkyboxEntity;
154  skybox->setBaseName( map.skyboxFileBase() );
155  skybox->setExtension( map.skyboxFileExtension() );
156  skybox->setParent( this );
157 
158  // docs say frustum culling must be disabled for skybox.
159  // it _somehow_ works even when frustum culling is enabled with some camera positions,
160  // but then when zoomed in more it would disappear - so let's keep frustum culling disabled
161  mEngine->setFrustumCullingEnabled( false );
162  }
163 
164  // force initial update of chunked entities
165  onCameraChanged();
166 }
167 
169 {
170  QgsRectangle extent = mMap.terrainGenerator()->extent();
171  float side = std::max( extent.width(), extent.height() );
172  mCameraController->resetView( side ); // assuming FOV being 45 degrees
173 }
174 
176 {
177  return mTerrain ? mTerrain->pendingJobsCount() : 0;
178 }
179 
181 {
182  int count = 0;
183  for ( QgsChunkedEntity *entity : qgis::as_const( mChunkEntities ) )
184  count += entity->pendingJobsCount();
185  return count;
186 }
187 
189 {
190  if ( mPickHandlers.isEmpty() )
191  {
192  // we need to add object pickers
193  for ( Qt3DCore::QEntity *entity : mLayerEntities.values() )
194  {
195  if ( QgsChunkedEntity *chunkedEntity = qobject_cast<QgsChunkedEntity *>( entity ) )
196  chunkedEntity->setPickingEnabled( true );
197  }
198  }
199 
200  mPickHandlers.append( pickHandler );
201 }
202 
204 {
205  mPickHandlers.removeOne( pickHandler );
206 
207  if ( mPickHandlers.isEmpty() )
208  {
209  // we need to remove pickers
210  for ( Qt3DCore::QEntity *entity : mLayerEntities.values() )
211  {
212  if ( QgsChunkedEntity *chunkedEntity = qobject_cast<QgsChunkedEntity *>( entity ) )
213  chunkedEntity->setPickingEnabled( false );
214  }
215  }
216 }
217 
218 void Qgs3DMapScene::onLayerEntityPickedObject( Qt3DRender::QPickEvent *pickEvent, QgsFeatureId fid )
219 {
220  QgsMapLayer *layer = mLayerEntities.key( qobject_cast<QgsChunkedEntity *>( sender() ) );
221  if ( !layer )
222  return;
223 
224  QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
225  if ( !vlayer )
226  return;
227 
228  for ( Qgs3DMapScenePickHandler *pickHandler : qgis::as_const( mPickHandlers ) )
229  {
230  pickHandler->handlePickOnVectorLayer( vlayer, fid, pickEvent->worldIntersection(), pickEvent );
231  }
232 }
233 
234 
235 float Qgs3DMapScene::worldSpaceError( float epsilon, float distance )
236 {
237  Qt3DRender::QCamera *camera = mCameraController->camera();
238  float fov = camera->fieldOfView();
239  QRect rect = mCameraController->viewport();
240  float screenSizePx = std::max( rect.width(), rect.height() ); // TODO: is this correct?
241 
242  // in qgschunkedentity_p.cpp there is inverse calculation (world space error to screen space error)
243  // with explanation of the math.
244  float frustumWidthAtDistance = 2 * distance * tan( fov / 2 );
245  float err = frustumWidthAtDistance * epsilon / screenSizePx;
246  return err;
247 }
248 
249 QgsChunkedEntity::SceneState _sceneState( QgsCameraController *cameraController )
250 {
251  Qt3DRender::QCamera *camera = cameraController->camera();
252  QgsChunkedEntity::SceneState state;
253  state.cameraFov = camera->fieldOfView();
254  state.cameraPos = camera->position();
255  QRect rect = cameraController->viewport();
256  state.screenSizePx = std::max( rect.width(), rect.height() ); // TODO: is this correct?
257  state.viewProjectionMatrix = camera->projectionMatrix() * camera->viewMatrix();
258  return state;
259 }
260 
261 void Qgs3DMapScene::onCameraChanged()
262 {
263  updateScene();
264  bool changedCameraPlanes = updateCameraNearFarPlanes();
265 
266  if ( changedCameraPlanes )
267  {
268  // repeat update of entities - because we have updated camera's near/far planes,
269  // the active nodes may have changed as well
270  updateScene();
271  updateCameraNearFarPlanes();
272  }
273 }
274 
275 void Qgs3DMapScene::updateScene()
276 {
277  QgsEventTracing::addEvent( QgsEventTracing::Instant, QStringLiteral( "3D" ), QStringLiteral( "Update Scene" ) );
278 
279  for ( QgsChunkedEntity *entity : qgis::as_const( mChunkEntities ) )
280  {
281  if ( entity->isEnabled() )
282  entity->update( _sceneState( mCameraController ) );
283  }
284 
285  updateSceneState();
286 }
287 
288 bool Qgs3DMapScene::updateCameraNearFarPlanes()
289 {
290  // Update near and far plane from the terrain.
291  // this needs to be done with great care as we have kind of circular dependency here:
292  // active nodes are culled based on the current frustum (which involves near + far plane)
293  // and then based on active nodes we set near and far plane.
294  //
295  // All of this is just heuristics assuming that all other stuff is being rendered somewhere
296  // around the area where the terrain is.
297  //
298  // Near/far plane is setup in order to make best use of the depth buffer to avoid:
299  // 1. precision errors - if the range is too great
300  // 2. unwanted clipping of scene - if the range is too small
301 
302  if ( mTerrain )
303  {
304  Qt3DRender::QCamera *camera = cameraController()->camera();
305  QMatrix4x4 viewMatrix = camera->viewMatrix();
306  float fnear = 1e9;
307  float ffar = 0;
308 
309  QList<QgsChunkNode *> activeNodes = mTerrain->activeNodes();
310 
311  // it could be that there are no active nodes - they could be all culled or because root node
312  // is not yet loaded - we still need at least something to understand bounds of our scene
313  // so lets use the root node
314  if ( activeNodes.isEmpty() )
315  activeNodes << mTerrain->rootNode();
316 
317  Q_FOREACH ( QgsChunkNode *node, activeNodes )
318  {
319  // project each corner of bbox to camera coordinates
320  // and determine closest and farthest point.
321  QgsAABB bbox = node->bbox();
322  for ( int i = 0; i < 8; ++i )
323  {
324  QVector4D p( ( ( i >> 0 ) & 1 ) ? bbox.xMin : bbox.xMax,
325  ( ( i >> 1 ) & 1 ) ? bbox.yMin : bbox.yMax,
326  ( ( i >> 2 ) & 1 ) ? bbox.zMin : bbox.zMax, 1 );
327  QVector4D pc = viewMatrix * p;
328 
329  float dst = -pc.z(); // in camera coordinates, x grows right, y grows down, z grows to the back
330  if ( dst < fnear )
331  fnear = dst;
332  if ( dst > ffar )
333  ffar = dst;
334  }
335  }
336  if ( fnear < 1 )
337  fnear = 1; // does not really make sense to use negative far plane (behind camera)
338 
339  if ( fnear == 1e9 && ffar == 0 )
340  {
341  // the update didn't work out... this should not happen
342  // well at least temporarily use some conservative starting values
343  qDebug() << "oops... this should not happen! couldn't determine near/far plane. defaulting to 1...1e9";
344  fnear = 1;
345  ffar = 1e9;
346  }
347 
348  // set near/far plane - with some tolerance in front/behind expected near/far planes
349  float newFar = ffar * 2;
350  float newNear = fnear / 2;
351  if ( !qgsFloatNear( newFar, camera->farPlane() ) || !qgsFloatNear( newNear, camera->nearPlane() ) )
352  {
353  camera->setFarPlane( newFar );
354  camera->setNearPlane( newNear );
355  return true;
356  }
357  }
358  else
359  qDebug() << "no terrain - not setting near/far plane";
360 
361  return false;
362 }
363 
364 void Qgs3DMapScene::onFrameTriggered( float dt )
365 {
366  mCameraController->frameTriggered( dt );
367 
368  for ( QgsChunkedEntity *entity : qgis::as_const( mChunkEntities ) )
369  {
370  if ( entity->isEnabled() && entity->needsUpdate() )
371  {
372  qDebug() << "need for update";
373  entity->update( _sceneState( mCameraController ) );
374  }
375  }
376 
377  updateSceneState();
378 }
379 
380 void Qgs3DMapScene::createTerrain()
381 {
382  if ( mTerrain )
383  {
384  mChunkEntities.removeOne( mTerrain );
385 
386  mTerrain->deleteLater();
387  mTerrain = nullptr;
388 
389  emit terrainEntityChanged();
390  }
391 
392  if ( !mTerrainUpdateScheduled )
393  {
394  // defer re-creation of terrain: there may be multiple invocations of this slot, so create the new entity just once
395  QTimer::singleShot( 0, this, &Qgs3DMapScene::createTerrainDeferred );
396  mTerrainUpdateScheduled = true;
397  setSceneState( Updating );
398  }
399 }
400 
401 void Qgs3DMapScene::createTerrainDeferred()
402 {
403  double tile0width = mMap.terrainGenerator()->extent().width();
404  int maxZoomLevel = Qgs3DUtils::maxZoomLevel( tile0width, mMap.mapTileResolution(), mMap.maxTerrainGroundError() );
405 
406  mTerrain = new QgsTerrainEntity( maxZoomLevel, mMap );
407  //mTerrain->setEnabled(false);
408  mTerrain->setParent( this );
409 
410  if ( mMap.showTerrainBoundingBoxes() )
411  mTerrain->setShowBoundingBoxes( true );
412 
413  mCameraController->setTerrainEntity( mTerrain );
414 
415  mChunkEntities << mTerrain;
416 
417  onCameraChanged(); // force update of the new terrain
418 
419  // make sure that renderers for layers are re-created as well
420  Q_FOREACH ( QgsMapLayer *layer, mMap.layers() )
421  {
422  // remove old entity - if any
423  removeLayerEntity( layer );
424 
425  // add new entity - if any 3D renderer
426  addLayerEntity( layer );
427  }
428 
429  mTerrainUpdateScheduled = false;
430 
431  connect( mTerrain, &QgsChunkedEntity::pendingJobsCountChanged, this, &Qgs3DMapScene::totalPendingJobsCountChanged );
432  connect( mTerrain, &QgsTerrainEntity::pendingJobsCountChanged, this, &Qgs3DMapScene::terrainPendingJobsCountChanged );
433 
434  emit terrainEntityChanged();
435 }
436 
437 void Qgs3DMapScene::onBackgroundColorChanged()
438 {
439  mEngine->setClearColor( mMap.backgroundColor() );
440 }
441 
442 
443 void Qgs3DMapScene::updateLights()
444 {
445  for ( Qt3DCore::QEntity *entity : qgis::as_const( mLightEntities ) )
446  entity->deleteLater();
447  mLightEntities.clear();
448 
449  const auto newPointLights = mMap.pointLights();
450  for ( const QgsPointLightSettings &pointLightSettings : newPointLights )
451  {
452  Qt3DCore::QEntity *lightEntity = new Qt3DCore::QEntity;
453  Qt3DCore::QTransform *lightTransform = new Qt3DCore::QTransform;
454  lightTransform->setTranslation( QVector3D( pointLightSettings.position().x(),
455  pointLightSettings.position().y(),
456  pointLightSettings.position().z() ) );
457 
458  Qt3DRender::QPointLight *light = new Qt3DRender::QPointLight;
459  light->setColor( pointLightSettings.color() );
460  light->setIntensity( pointLightSettings.intensity() );
461 
462  light->setConstantAttenuation( pointLightSettings.constantAttenuation() );
463  light->setLinearAttenuation( pointLightSettings.linearAttenuation() );
464  light->setQuadraticAttenuation( pointLightSettings.quadraticAttenuation() );
465 
466  lightEntity->addComponent( light );
467  lightEntity->addComponent( lightTransform );
468  lightEntity->setParent( this );
469  mLightEntities << lightEntity;
470  }
471 }
472 
473 void Qgs3DMapScene::updateCameraLens()
474 {
475  mEngine->camera()->lens()->setFieldOfView( mMap.fieldOfView() );
476  onCameraChanged();
477 }
478 
479 void Qgs3DMapScene::onRenderersChanged()
480 {
481  // remove entities (if any)
482  qDeleteAll( mRenderersEntities );
483  mRenderersEntities.clear();
484 
485  // re-add entities from new set of renderers
486  const QList<QgsAbstract3DRenderer *> renderers = mMap.renderers();
487  for ( const QgsAbstract3DRenderer *renderer : renderers )
488  {
489  Qt3DCore::QEntity *newEntity = renderer->createEntity( mMap );
490  if ( newEntity )
491  {
492  newEntity->setParent( this );
493  finalizeNewEntity( newEntity );
494  mRenderersEntities[renderer] = newEntity;
495  }
496  }
497 }
498 
499 void Qgs3DMapScene::onLayerRenderer3DChanged()
500 {
501  QgsMapLayer *layer = qobject_cast<QgsMapLayer *>( sender() );
502  Q_ASSERT( layer );
503 
504  // remove old entity - if any
505  removeLayerEntity( layer );
506 
507  // add new entity - if any 3D renderer
508  addLayerEntity( layer );
509 }
510 
511 void Qgs3DMapScene::onLayersChanged()
512 {
513  QSet<QgsMapLayer *> layersBefore = QSet<QgsMapLayer *>::fromList( mLayerEntities.keys() );
514  QList<QgsMapLayer *> layersAdded;
515  Q_FOREACH ( QgsMapLayer *layer, mMap.layers() )
516  {
517  if ( !layersBefore.contains( layer ) )
518  {
519  layersAdded << layer;
520  }
521  else
522  {
523  layersBefore.remove( layer );
524  }
525  }
526 
527  // what is left in layersBefore are layers that have been removed
528  Q_FOREACH ( QgsMapLayer *layer, layersBefore )
529  {
530  removeLayerEntity( layer );
531  }
532 
533  Q_FOREACH ( QgsMapLayer *layer, layersAdded )
534  {
535  addLayerEntity( layer );
536  }
537 }
538 
539 void Qgs3DMapScene::addLayerEntity( QgsMapLayer *layer )
540 {
541  bool needsSceneUpdate = false;
542  QgsAbstract3DRenderer *renderer = layer->renderer3D();
543  if ( renderer )
544  {
545  // Fix vector layer's renderer to make sure the renderer is pointing to its layer.
546  // It has happened before that renderer pointed to a different layer (probably after copying a style).
547  // This is a bit of a hack and it should be handled in QgsMapLayer::setRenderer3D() but in qgis_core
548  // the vector layer 3D renderer classes are not available.
549  if ( layer->type() == QgsMapLayerType::VectorLayer &&
550  ( renderer->type() == QLatin1String( "vector" ) || renderer->type() == QLatin1String( "rulebased" ) ) )
551  {
552  static_cast<QgsAbstractVectorLayer3DRenderer *>( renderer )->setLayer( static_cast<QgsVectorLayer *>( layer ) );
553  }
554  else if ( layer->type() == QgsMapLayerType::MeshLayer && renderer->type() == QLatin1String( "mesh" ) )
555  {
556  static_cast<QgsMeshLayer3DRenderer *>( renderer )->setLayer( static_cast<QgsMeshLayer *>( layer ) );
557  }
558 
559  Qt3DCore::QEntity *newEntity = renderer->createEntity( mMap );
560  if ( newEntity )
561  {
562  newEntity->setParent( this );
563  mLayerEntities.insert( layer, newEntity );
564 
565  finalizeNewEntity( newEntity );
566 
567  if ( QgsChunkedEntity *chunkedNewEntity = qobject_cast<QgsChunkedEntity *>( newEntity ) )
568  {
569  mChunkEntities.append( chunkedNewEntity );
570  needsSceneUpdate = true;
571 
572  chunkedNewEntity->setPickingEnabled( !mPickHandlers.isEmpty() );
573  connect( chunkedNewEntity, &QgsChunkedEntity::pickedObject, this, &Qgs3DMapScene::onLayerEntityPickedObject );
574 
575  connect( chunkedNewEntity, &QgsChunkedEntity::newEntityCreated, this, [this]( Qt3DCore::QEntity * entity )
576  {
577  finalizeNewEntity( entity );
578  } );
579 
580  connect( chunkedNewEntity, &QgsChunkedEntity::pendingJobsCountChanged, this, &Qgs3DMapScene::totalPendingJobsCountChanged );
581  }
582  }
583  }
584 
585  if ( needsSceneUpdate )
586  onCameraChanged(); // needed for chunked entities
587 
588  connect( layer, &QgsMapLayer::renderer3DChanged, this, &Qgs3DMapScene::onLayerRenderer3DChanged );
589 
590  if ( layer->type() == QgsMapLayerType::VectorLayer )
591  {
592  QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
593  connect( vlayer, &QgsVectorLayer::selectionChanged, this, &Qgs3DMapScene::onLayerRenderer3DChanged );
594  }
595 }
596 
597 void Qgs3DMapScene::removeLayerEntity( QgsMapLayer *layer )
598 {
599  Qt3DCore::QEntity *entity = mLayerEntities.take( layer );
600 
601  if ( QgsChunkedEntity *chunkedEntity = qobject_cast<QgsChunkedEntity *>( entity ) )
602  {
603  mChunkEntities.removeOne( chunkedEntity );
604  }
605 
606  if ( entity )
607  entity->deleteLater();
608 
609  disconnect( layer, &QgsMapLayer::renderer3DChanged, this, &Qgs3DMapScene::onLayerRenderer3DChanged );
610 
611  if ( layer->type() == QgsMapLayerType::VectorLayer )
612  {
613  QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
614  disconnect( vlayer, &QgsVectorLayer::selectionChanged, this, &Qgs3DMapScene::onLayerRenderer3DChanged );
615  }
616 }
617 
618 void Qgs3DMapScene::finalizeNewEntity( Qt3DCore::QEntity *newEntity )
619 {
620  // this is probably not the best place for material-specific configuration,
621  // maybe this could be more generalized when other materials need some specific treatment
622  for ( QgsLineMaterial *lm : newEntity->findChildren<QgsLineMaterial *>() )
623  {
624  connect( mCameraController, &QgsCameraController::viewportChanged, lm, [lm, this]
625  {
626  lm->setViewportSize( mCameraController->viewport().size() );
627  } );
628 
629  lm->setViewportSize( cameraController()->viewport().size() );
630  }
631  // configure billboard's viewport when the viewport is changed.
632  for ( QgsPoint3DBillboardMaterial *bm : newEntity->findChildren<QgsPoint3DBillboardMaterial *>() )
633  {
634  connect( mCameraController, &QgsCameraController::viewportChanged, bm, [bm, this]
635  {
636  bm->setViewportSize( mCameraController->viewport().size() );
637  } );
638 
639  bm->setViewportSize( mCameraController->viewport().size() );
640  }
641 }
642 
643 void Qgs3DMapScene::addCameraViewCenterEntity( Qt3DRender::QCamera *camera )
644 {
645  mEntityCameraViewCenter = new Qt3DCore::QEntity;
646 
647  Qt3DCore::QTransform *trCameraViewCenter = new Qt3DCore::QTransform;
648  mEntityCameraViewCenter->addComponent( trCameraViewCenter );
649  connect( camera, &Qt3DRender::QCamera::viewCenterChanged, this, [trCameraViewCenter, camera]
650  {
651  trCameraViewCenter->setTranslation( camera->viewCenter() );
652  } );
653 
654  Qt3DExtras::QPhongMaterial *materialCameraViewCenter = new Qt3DExtras::QPhongMaterial;
655  materialCameraViewCenter->setAmbient( Qt::red );
656  mEntityCameraViewCenter->addComponent( materialCameraViewCenter );
657 
658  Qt3DExtras::QSphereMesh *rendererCameraViewCenter = new Qt3DExtras::QSphereMesh;
659  rendererCameraViewCenter->setRadius( 10 );
660  mEntityCameraViewCenter->addComponent( rendererCameraViewCenter );
661 
662  mEntityCameraViewCenter->setEnabled( mMap.showCameraViewCenter() );
663  mEntityCameraViewCenter->setParent( this );
664 
665  connect( &mMap, &Qgs3DMapSettings::showCameraViewCenterChanged, this, [this]
666  {
667  mEntityCameraViewCenter->setEnabled( mMap.showCameraViewCenter() );
668  } );
669 }
670 
671 void Qgs3DMapScene::setSceneState( Qgs3DMapScene::SceneState state )
672 {
673  if ( mSceneState == state )
674  return;
675  mSceneState = state;
676  emit sceneStateChanged();
677 }
678 
679 void Qgs3DMapScene::updateSceneState()
680 {
681  if ( mTerrainUpdateScheduled )
682  {
683  setSceneState( Updating );
684  return;
685  }
686 
687  for ( QgsChunkedEntity *entity : qgis::as_const( mChunkEntities ) )
688  {
689  if ( entity->isEnabled() && entity->pendingJobsCount() > 0 )
690  {
691  setSceneState( Updating );
692  return;
693  }
694  }
695 
696  setSceneState( Ready );
697 }
3 Abstract base class for handlers that process pick events from a 3D map scene.
3 Material of the billboard rendering for points in 3D map view.
QList< QgsMapLayer * > layers() const
Returns the list of map layers to be rendered as a texture of the terrain.
void cameraChanged()
Emitted when camera has been updated.
3 Axis-aligned bounding box - in world coords.
Definition: qgsaabb.h:30
void renderersChanged()
Emitted when the list of map&#39;s extra renderers have been modified.
A rectangle specified with double values.
Definition: qgsrectangle.h:41
Base class for all map layer types.
Definition: qgsmaplayer.h:79
void maxTerrainGroundErrorChanged()
Emitted when the maximum terrain ground error has changed.
bool showTerrainBoundingBoxes() const
Returns whether to display bounding boxes of terrain tiles (for debugging)
void frameTriggered(float dt)
Called internally from 3D scene when a new frame is generated. Updates camera according to keyboard/m...
float worldSpaceError(float epsilon, float distance)
Given screen error (in pixels) and distance from camera (in 3D world coordinates), this function estimates the error in world space.
Base class for all renderers that may to participate in 3D view.
QgsMapLayerType type() const
Returns the type of the layer.
void layersChanged()
Emitted when the list of map layers for terrain texture has changed.
bool qgsFloatNear(float a, float b, float epsilon=4 *FLT_EPSILON)
Compare two floats (but allow some difference)
Definition: qgis.h:330
bool showCameraViewCenter() const
Returns whether to show camera&#39;s view center as a sphere (for debugging)
QString skyboxFileBase() const
Returns base part of filenames of skybox (see setSkybox())
virtual Qt3DCore::QEntity * createEntity(const Qgs3DMapSettings &map) const =0
Returns a 3D entity that will be used to show renderer&#39;s data in 3D scene.
3D renderer that renders all mesh triangles of a mesh layer.
void setViewport(QRect viewport)
Sets viewport rectangle. Called internally from 3D canvas. Allows conversion of mouse coordinates...
SceneState
Enumeration of possible states of the 3D scene.
Definition: qgs3dmapscene.h:83
void terrainPendingJobsCountChanged()
Emitted when the number of terrain&#39;s pending jobs changes.
float zMax
Definition: qgsaabb.h:80
qint64 QgsFeatureId
Definition: qgsfeatureid.h:25
void resetView(float distance)
Move camera back to the initial position (looking down towards origin of world&#39;s coordinates) ...
QList< QgsPointLightSettings > pointLights() const
Returns list of point lights defined in the scene.
void totalPendingJobsCountChanged()
Emitted when the total number of pending jobs changes.
void terrainVerticalScaleChanged()
Emitted when the vertical scale of the terrain has changed.
3 Definition of the world
int totalPendingJobsCount() const
Returns number of pending jobs for all chunked entities.
void registerPickHandler(Qgs3DMapScenePickHandler *pickHandler)
Registers an object that will get results of pick events on 3D entities. Does not take ownership of t...
virtual Qt3DRender::QRenderSettings * renderSettings()=0
Returns access to the engine&#39;s render settings (the frame graph can be accessed from here) ...
3 Definition of a point light in a 3D map scene
void pointLightsChanged()
Emitted when the list of point lights changes.
float maxTerrainGroundError() const
Returns maximum ground error of terrain tiles in world units.
float zMin
Definition: qgsaabb.h:77
void viewportChanged()
Emitted when viewport rectangle has been updated.
float yMax
Definition: qgsaabb.h:79
virtual QString type() const =0
Returns unique identifier of the renderer class (used to identify subclass)
bool hasSkyboxEnabled() const
Returns whether skybox is enabled.
double width() const
Returns the width of the rectangle.
Definition: qgsrectangle.h:202
void fieldOfViewChanged()
Emitted when the camera lens field of view changes.
void showCameraViewCenterChanged()
Emitted when the flag whether camera&#39;s view center is shown has changed.
int mapTileResolution() const
Returns resolution (in pixels) of the texture of a terrain tile.
void terrainGeneratorChanged()
Emitted when the terrain generator has changed.
3 Base class for 3D engine implementation.
QColor backgroundColor() const
Returns background color of the 3D map view.
void selectionChanged(const QgsFeatureIds &selected, const QgsFeatureIds &deselected, bool clearAndSelect)
Emitted when selection was changed.
3 Object that controls camera movement based on user input
The scene is fully loaded/updated.
Definition: qgs3dmapscene.h:85
virtual Qt3DRender::QCamera * camera()=0
Returns pointer to the engine&#39;s camera entity.
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:171
void mapTileResolutionChanged()
Emitted when the map tile resoulution has changed.
virtual void setClearColor(const QColor &color)=0
Sets background color of the scene.
Qt3DRender::QCamera camera
QgsAbstract3DRenderer * renderer3D() const
Returns 3D renderer associated with the layer.
float xMin
Definition: qgsaabb.h:75
void unregisterPickHandler(Qgs3DMapScenePickHandler *pickHandler)
Unregisters previously registered pick handler. Pick handler is not deleted. Also removes object pick...
float xMax
Definition: qgsaabb.h:78
void setCamera(Qt3DRender::QCamera *camera)
Assigns camera that should be controlled by this class. Called internally from 3D scene...
void maxTerrainScreenErrorChanged()
Emitted when the maximum terrain screen error has changed.
QList< QgsAbstract3DRenderer * > renderers() const
Returns list of extra 3D renderers.
float yMin
Definition: qgsaabb.h:76
void sceneStateChanged()
Emitted when the scene&#39;s state has changed.
virtual QSize size() const =0
Returns size of the engine&#39;s rendering area in pixels.
void viewZoomFull()
Resets camera view to show the whole scene (top view)
int terrainPendingJobsCount() const
Returns number of pending jobs of the terrain entity.
virtual QgsRectangle extent() const =0
extent of the terrain in terrain&#39;s CRS
QString skyboxFileExtension() const
Returns extension part of filenames of skybox (see setSkybox())
3 Base class for 3D renderers that are based on vector layers.
float fieldOfView() const
Returns the camera lens&#39; field of view.
void terrainEntityChanged()
Emitted when the current terrain entity is replaced by a new one.
void backgroundColorChanged()
Emitted when the background color has changed.
QgsChunkedEntity::SceneState _sceneState(QgsCameraController *cameraController)
Represents a vector layer which manages a vector based data sets.
Qgs3DMapScene(const Qgs3DMapSettings &map, QgsAbstract3DEngine *engine)
Constructs a 3D scene based on map settings and Qt 3D renderer configuration.
The scene is still being loaded/updated.
Definition: qgs3dmapscene.h:86
void setTerrainEntity(QgsTerrainEntity *te)
Connects to object picker attached to terrain entity.
void terrainShadingChanged()
Emitted when terrain shading enabled flag or terrain shading material has changed.
QgsCameraController * cameraController()
Returns camera controller.
Definition: qgs3dmapscene.h:66
virtual void setFrustumCullingEnabled(bool enabled)=0
Sets whether frustum culling is enabled (this should make rendering faster by not rendering entities ...
double height() const
Returns the height of the rectangle.
Definition: qgsrectangle.h:209
void renderer3DChanged()
Signal emitted when 3D renderer associated with the layer has changed.
QgsTerrainGenerator * terrainGenerator() const
Returns terrain generator. It takes care of producing terrain tiles from the input data...