QGIS API Documentation 3.28.0-Firenze (ed3ad0430f)
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"
24#include "qgsterraingenerator.h"
28
30
31#include <Qt3DCore/QTransform>
32#include <Qt3DRender/QGeometryRenderer>
33#include <Qt3DRender/QObjectPicker>
34
35
37
39class 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
60QgsTerrainEntity::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
94QgsTerrainEntity::~QgsTerrainEntity()
95{
96 // cancel / wait for jobs
97 cancelActiveJobs();
98
99 delete mTextureGenerator;
100 delete mTerrainToMapTransform;
101}
102
103bool 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 auto *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
138void QgsTerrainEntity::onShowBoundingBoxesChanged()
139{
140 setShowBoundingBoxes( mMap.showTerrainBoundingBoxes() );
141}
142
143
144void 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
170void QgsTerrainEntity::onLayersChanged()
171{
172 connectToLayersRepaintRequest();
173 invalidateMapImages();
174}
175
176void 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
191void QgsTerrainEntity::onTerrainElevationOffsetChanged( float newOffset )
192{
193 mTerrainTransform->setTranslation( QVector3D( 0.0f, newOffset, 0.0f ) );
194}
195
196float QgsTerrainEntity::terrainElevationOffset() const
197{
198 return mMap.terrainElevationOffset();
199}
200
201
202// -----------
203
204
205TerrainMapUpdateJob::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
214void TerrainMapUpdateJob::cancel()
215{
216 if ( mJobId != -1 )
217 mTextureGenerator->cancelJob( mJobId );
218}
219
220
221void 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
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)
QgsTerrainGenerator * terrainGenerator() const
Returns the terrain generator.
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.