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