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