QGIS API Documentation  3.21.0-Master (5b68dc587e)
qgsterrainentity_p.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsterrainentity_p.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 "qgsterrainentity_p.h"
17 
18 #include "qgsaabb.h"
19 #include "qgs3dmapsettings.h"
20 #include "qgschunknode_p.h"
22 #include "qgseventtracing.h"
23 #include "qgsraycastingutils_p.h"
24 #include "qgsterraingenerator.h"
27 #include "qgsterraintileentity_p.h"
28 
29 #include "qgscoordinatetransform.h"
30 
31 #include <Qt3DCore/QTransform>
32 #include <Qt3DRender/QGeometryRenderer>
33 #include <Qt3DRender/QObjectPicker>
34 
35 
37 
39 class TerrainMapUpdateJobFactory : public QgsChunkQueueJobFactory
40 {
41  public:
42  TerrainMapUpdateJobFactory( QgsTerrainTextureGenerator *textureGenerator )
43  : mTextureGenerator( textureGenerator )
44  {
45  }
46 
47  QgsChunkQueueJob *createJob( QgsChunkNode *chunk ) override
48  {
49  return new TerrainMapUpdateJob( mTextureGenerator, chunk );
50  }
51 
52  private:
53  QgsTerrainTextureGenerator *mTextureGenerator = nullptr;
54 };
55 
56 
57 // -----------
58 
59 
60 QgsTerrainEntity::QgsTerrainEntity( const Qgs3DMapSettings &map, Qt3DCore::QNode *parent )
61  : QgsChunkedEntity( map.maxTerrainScreenError(), map.terrainGenerator(), false, std::numeric_limits<int>::max(), parent )
62  , mMap( map )
63 {
64  map.terrainGenerator()->setTerrain( this );
65  mIsValid = map.terrainGenerator()->isValid();
66 
67  connect( &map, &Qgs3DMapSettings::showTerrainBoundingBoxesChanged, this, &QgsTerrainEntity::onShowBoundingBoxesChanged );
68  connect( &map, &Qgs3DMapSettings::showTerrainTilesInfoChanged, this, &QgsTerrainEntity::invalidateMapImages );
69  connect( &map, &Qgs3DMapSettings::showLabelsChanged, this, &QgsTerrainEntity::invalidateMapImages );
70  connect( &map, &Qgs3DMapSettings::layersChanged, this, &QgsTerrainEntity::onLayersChanged );
71  connect( &map, &Qgs3DMapSettings::backgroundColorChanged, this, &QgsTerrainEntity::invalidateMapImages );
72  connect( &map, &Qgs3DMapSettings::terrainMapThemeChanged, this, &QgsTerrainEntity::invalidateMapImages );
73  connect( &map, &Qgs3DMapSettings::terrainElevationOffsetChanged, this, &QgsTerrainEntity::onTerrainElevationOffsetChanged );
74 
75  connectToLayersRepaintRequest();
76 
77  mTerrainToMapTransform = new QgsCoordinateTransform( map.terrainGenerator()->crs(), map.crs(), map.transformContext() );
78 
79  mTextureGenerator = new QgsTerrainTextureGenerator( map );
80 
81  mUpdateJobFactory.reset( new TerrainMapUpdateJobFactory( mTextureGenerator ) );
82 
83  mTerrainPicker = new Qt3DRender::QObjectPicker;
84  // add camera control's terrain picker as a component to be able to capture height where mouse was
85  // pressed in order to correctly pan camera when dragging mouse
86  addComponent( mTerrainPicker );
87 
88  mTerrainTransform = new Qt3DCore::QTransform;
89  mTerrainTransform->setScale( 1.0f );
90  mTerrainTransform->setTranslation( QVector3D( 0.0f, map.terrainElevationOffset(), 0.0f ) );
91  addComponent( mTerrainTransform );
92 }
93 
94 QgsTerrainEntity::~QgsTerrainEntity()
95 {
96  // cancel / wait for jobs
97  cancelActiveJobs();
98 
99  delete mTextureGenerator;
100  delete mTerrainToMapTransform;
101 }
102 
103 bool QgsTerrainEntity::rayIntersection( const QgsRayCastingUtils::Ray3D &ray, QVector3D &intersectionPoint )
104 {
105  if ( !rootNode() )
106  return false;
107 
108  if ( mMap.terrainGenerator()->type() != QgsTerrainGenerator::Dem )
109  return false; // currently only working with DEM terrain
110 
111  float minDist = -1;
112 
113  QList<QgsChunkNode *> lst = activeNodes();
114  for ( QgsChunkNode *n : lst )
115  {
116  if ( n->entity() && ( minDist < 0 || n->bbox().distanceFromPoint( ray.origin() ) < minDist ) && QgsRayCastingUtils::rayBoxIntersection( ray, n->bbox() ) )
117  {
118  Qt3DRender::QGeometryRenderer *rend = n->entity()->findChild<Qt3DRender::QGeometryRenderer *>();
119  Qt3DRender::QGeometry *geom = rend->geometry();
120  DemTerrainTileGeometry *demGeom = static_cast<DemTerrainTileGeometry *>( geom );
121  Qt3DCore::QTransform *tr = n->entity()->findChild<Qt3DCore::QTransform *>();
122  QVector3D nodeIntPoint;
123  if ( demGeom->rayIntersection( ray, tr->matrix(), nodeIntPoint ) )
124  {
125  float dist = ( ray.origin() - intersectionPoint ).length();
126  if ( minDist < 0 || dist < minDist )
127  {
128  minDist = dist;
129  intersectionPoint = nodeIntPoint;
130  }
131  }
132  }
133  }
134 
135  return minDist >= 0;
136 }
137 
138 void QgsTerrainEntity::onShowBoundingBoxesChanged()
139 {
140  setShowBoundingBoxes( mMap.showTerrainBoundingBoxes() );
141 }
142 
143 
144 void QgsTerrainEntity::invalidateMapImages()
145 {
146  QgsEventTracing::addEvent( QgsEventTracing::Instant, QStringLiteral( "3D" ), QStringLiteral( "Invalidate textures" ) );
147 
148  // handle active nodes
149 
150  updateNodes( mActiveNodes, mUpdateJobFactory.get() );
151 
152  // handle inactive nodes afterwards
153 
154  QList<QgsChunkNode *> inactiveNodes;
155  const QList<QgsChunkNode *> descendants = mRootNode->descendants();
156  for ( QgsChunkNode *node : descendants )
157  {
158  if ( !node->entity() )
159  continue;
160  if ( mActiveNodes.contains( node ) )
161  continue;
162  inactiveNodes << node;
163  }
164 
165  updateNodes( inactiveNodes, mUpdateJobFactory.get() );
166 
167  setNeedsUpdate( true );
168 }
169 
170 void QgsTerrainEntity::onLayersChanged()
171 {
172  connectToLayersRepaintRequest();
173  invalidateMapImages();
174 }
175 
176 void QgsTerrainEntity::connectToLayersRepaintRequest()
177 {
178  for ( QgsMapLayer *layer : std::as_const( mLayers ) )
179  {
180  disconnect( layer, &QgsMapLayer::repaintRequested, this, &QgsTerrainEntity::invalidateMapImages );
181  }
182 
183  mLayers = mMap.layers();
184 
185  for ( QgsMapLayer *layer : std::as_const( mLayers ) )
186  {
187  connect( layer, &QgsMapLayer::repaintRequested, this, &QgsTerrainEntity::invalidateMapImages );
188  }
189 }
190 
191 void QgsTerrainEntity::onTerrainElevationOffsetChanged( float newOffset )
192 {
193  mTerrainTransform->setTranslation( QVector3D( 0.0f, newOffset, 0.0f ) );
194 }
195 
196 float QgsTerrainEntity::terrainElevationOffset() const
197 {
198  return mMap.terrainElevationOffset();
199 }
200 
201 
202 // -----------
203 
204 
205 TerrainMapUpdateJob::TerrainMapUpdateJob( QgsTerrainTextureGenerator *textureGenerator, QgsChunkNode *node )
206  : QgsChunkQueueJob( node )
207  , mTextureGenerator( textureGenerator )
208 {
209  QgsTerrainTileEntity *entity = qobject_cast<QgsTerrainTileEntity *>( node->entity() );
210  connect( textureGenerator, &QgsTerrainTextureGenerator::tileReady, this, &TerrainMapUpdateJob::onTileReady );
211  mJobId = textureGenerator->render( entity->textureImage()->imageExtent(), node->tileId(), entity->textureImage()->imageDebugText() );
212 }
213 
214 void TerrainMapUpdateJob::cancel()
215 {
216  if ( mJobId != -1 )
217  mTextureGenerator->cancelJob( mJobId );
218 }
219 
220 
221 void TerrainMapUpdateJob::onTileReady( int jobId, const QImage &image )
222 {
223  if ( mJobId == jobId )
224  {
225  QgsTerrainTileEntity *entity = qobject_cast<QgsTerrainTileEntity *>( mNode->entity() );
226  entity->textureImage()->setImage( image );
227  mJobId = -1;
228  emit finished();
229  }
230 }
231 
QgsTerrainGenerator * terrainGenerator() const
Returns terrain generator. It takes care of producing terrain tiles from the input data.
void backgroundColorChanged()
Emitted when the background color has changed.
void showTerrainBoundingBoxesChanged()
Emitted when the flag whether terrain's bounding boxes are shown has changed.
void terrainMapThemeChanged()
Emitted when terrain's map theme has changed.
float terrainElevationOffset() const
Returns the elevation offset of the terrain (used to move the terrain up or down)
void showLabelsChanged()
Emitted when the flag whether labels are displayed on terrain tiles has changed.
void terrainElevationOffsetChanged(float newElevation)
Emitted when the terrain elevation offset is changed.
void layersChanged()
Emitted when the list of map layers for 3d rendering has changed.
void showTerrainTilesInfoChanged()
Emitted when the flag whether terrain's tile info is shown has changed.
QgsCoordinateReferenceSystem crs() const
Returns coordinate reference system used in the 3D scene.
QgsCoordinateTransformContext transformContext() const
Returns the coordinate transform context, which stores various information regarding which datum tran...
Class for doing transforms between two map coordinate systems.
Base class for all map layer types.
Definition: qgsmaplayer.h:73
void repaintRequested(bool deferredUpdate=false)
By emitting this signal the layer tells that either appearance or content have been changed and any v...
@ Dem
Terrain is built from raster layer with digital elevation model.
QgsCoordinateReferenceSystem crs() const
Returns CRS of the terrain.
void setTerrain(QgsTerrainEntity *t)
Sets terrain entity for the generator (does not transfer ownership)
bool isValid() const
Returns whether the terrain generator is valid.