QGIS API Documentation 3.99.0-Master (21b3aa880ba)
Loading...
Searching...
No Matches
qgsterrainentity.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsterrainentity.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.h"
17
18#include <memory>
19
20#include "qgs3dmapsettings.h"
21#include "qgs3dutils.h"
22#include "qgsaabb.h"
24#include "qgschunknode.h"
27#include "qgseventtracing.h"
28#include "qgsraycastingutils.h"
29#include "qgsterraingenerator.h"
33
34#include <Qt3DCore/QTransform>
35#include <Qt3DRender/QGeometryRenderer>
36
37#include "moc_qgsterrainentity.cpp"
38
40
42class TerrainMapUpdateJobFactory : public QgsChunkQueueJobFactory
43{
44 public:
45 TerrainMapUpdateJobFactory( QgsTerrainTextureGenerator *textureGenerator )
46 : mTextureGenerator( textureGenerator )
47 {
48 }
49
50 QgsChunkQueueJob *createJob( QgsChunkNode *chunk ) override
51 {
52 return new TerrainMapUpdateJob( mTextureGenerator, chunk );
53 }
54
55 private:
56 QgsTerrainTextureGenerator *mTextureGenerator = nullptr;
57};
58
59
60// -----------
61
62
63QgsTerrainEntity::QgsTerrainEntity( Qgs3DMapSettings *map, Qt3DCore::QNode *parent )
64 : QgsChunkedEntity( map, map->terrainSettings()->maximumScreenError(), map->terrainGenerator(), false, std::numeric_limits<int>::max(), parent )
65{
66 map->terrainGenerator()->setTerrain( this );
67 mIsValid = map->terrainGenerator()->isValid();
68
69 connect( map, &Qgs3DMapSettings::showTerrainBoundingBoxesChanged, this, &QgsTerrainEntity::onShowBoundingBoxesChanged );
70 connect( map, &Qgs3DMapSettings::showTerrainTilesInfoChanged, this, &QgsTerrainEntity::invalidateMapImages );
71 connect( map, &Qgs3DMapSettings::showLabelsChanged, this, &QgsTerrainEntity::invalidateMapImages );
72 connect( map, &Qgs3DMapSettings::layersChanged, this, &QgsTerrainEntity::onLayersChanged );
73 connect( map, &Qgs3DMapSettings::backgroundColorChanged, this, &QgsTerrainEntity::invalidateMapImages );
74 connect( map, &Qgs3DMapSettings::terrainMapThemeChanged, this, &QgsTerrainEntity::invalidateMapImages );
75 connect( map, &Qgs3DMapSettings::terrainSettingsChanged, this, &QgsTerrainEntity::onTerrainElevationOffsetChanged );
76
77 connectToLayersRepaintRequest();
78
79 mTextureGenerator = new QgsTerrainTextureGenerator( *map );
80
81 mUpdateJobFactory = std::make_unique<TerrainMapUpdateJobFactory>( mTextureGenerator );
82
83 mTerrainTransform = new Qt3DCore::QTransform;
84 mTerrainTransform->setScale( 1.0f );
85 mTerrainTransform->setTranslation( QVector3D( 0.0f, 0.0f, map->terrainSettings()->elevationOffset() ) );
86 addComponent( mTerrainTransform );
87}
88
89QgsTerrainEntity::~QgsTerrainEntity()
90{
91 // cancel / wait for jobs
92 cancelActiveJobs();
93
94 delete mTextureGenerator;
95}
96
97QList<QgsRayCastHit> QgsTerrainEntity::rayIntersection( const QgsRay3D &ray, const QgsRayCastContext &context ) const
98{
99 Q_UNUSED( context )
100 QList<QgsRayCastHit> result;
101
102 float minDist = -1;
103 QgsVector3D intersectionPointMapCoords;
104 switch ( mMapSettings->terrainGenerator()->type() )
105 {
107 {
108 if ( ray.direction().z() == 0 )
109 break; // the ray is parallel to the flat terrain
110
111 const float dist = static_cast<float>( mMapSettings->terrainSettings()->elevationOffset() - ray.origin().z() - mMapSettings->origin().z() ) / ray.direction().z();
112 const QVector3D terrainPlanePoint = ray.origin() + ray.direction() * dist;
113 const QgsVector3D mapCoords = Qgs3DUtils::worldToMapCoordinates( terrainPlanePoint, mMapSettings->origin() );
114 if ( mMapSettings->extent().contains( mapCoords.x(), mapCoords.y() ) )
115 {
116 minDist = dist;
117 intersectionPointMapCoords = mapCoords;
118 }
119 break;
120 }
122 {
123 const QList<QgsChunkNode *> activeNodes = this->activeNodes();
124 QVector3D nearestIntersectionPoint;
125 for ( QgsChunkNode *node : activeNodes )
126 {
127 QgsAABB nodeBbox = Qgs3DUtils::mapToWorldExtent( node->box3D(), mMapSettings->origin() );
128
129 if ( node->entity() && ( minDist < 0 || nodeBbox.distanceFromPoint( ray.origin() ) < minDist ) && QgsRayCastingUtils::rayBoxIntersection( ray, nodeBbox ) )
130 {
131 Qt3DRender::QGeometryRenderer *rend = node->entity()->findChild<Qt3DRender::QGeometryRenderer *>();
132 auto *geom = rend->geometry();
133 Qt3DCore::QTransform *tr = node->entity()->findChild<Qt3DCore::QTransform *>();
134 QVector3D nodeIntPoint;
135 DemTerrainTileGeometry *demGeom = static_cast<DemTerrainTileGeometry *>( geom );
136 if ( demGeom->rayIntersection( ray, context, tr->matrix(), nodeIntPoint ) )
137 {
138 const float dist = ( ray.origin() - nodeIntPoint ).length();
139 if ( minDist < 0 || dist < minDist )
140 {
141 minDist = dist;
142 nearestIntersectionPoint = nodeIntPoint;
143 }
144 }
145 }
146 }
147 if ( minDist >= 0 )
148 intersectionPointMapCoords = Qgs3DUtils::worldToMapCoordinates( nearestIntersectionPoint, mMapSettings->origin() );
149 break;
150 }
154 // not supported
155 break;
156 }
157 if ( minDist >= 0 )
158 {
159 QgsRayCastHit hit;
160 hit.setDistance( minDist );
161 hit.setMapCoordinates( intersectionPointMapCoords );
162 result.append( hit );
163 }
164 return result;
165}
166
167void QgsTerrainEntity::onShowBoundingBoxesChanged()
168{
169 setShowBoundingBoxes( mMapSettings->showTerrainBoundingBoxes() );
170}
171
172
173void QgsTerrainEntity::invalidateMapImages()
174{
175 QgsEventTracing::addEvent( QgsEventTracing::Instant, QStringLiteral( "3D" ), QStringLiteral( "Invalidate textures" ) );
176
177 // handle active nodes
178
179 updateNodes( mActiveNodes, mUpdateJobFactory.get() );
180
181 // handle inactive nodes afterwards
182
183 QList<QgsChunkNode *> inactiveNodes;
184 const QList<QgsChunkNode *> descendants = mRootNode->descendants();
185 for ( QgsChunkNode *node : descendants )
186 {
187 if ( !node->entity() )
188 continue;
189 if ( mActiveNodes.contains( node ) )
190 continue;
191 inactiveNodes << node;
192 }
193
194 updateNodes( inactiveNodes, mUpdateJobFactory.get() );
195
196 setNeedsUpdate( true );
197}
198
199void QgsTerrainEntity::onLayersChanged()
200{
201 connectToLayersRepaintRequest();
202 invalidateMapImages();
203}
204
205void QgsTerrainEntity::connectToLayersRepaintRequest()
206{
207 for ( QgsMapLayer *layer : std::as_const( mLayers ) )
208 {
209 disconnect( layer, &QgsMapLayer::repaintRequested, this, &QgsTerrainEntity::invalidateMapImages );
210 }
211
212 mLayers = mMapSettings->layers();
213
214 for ( QgsMapLayer *layer : std::as_const( mLayers ) )
215 {
216 connect( layer, &QgsMapLayer::repaintRequested, this, &QgsTerrainEntity::invalidateMapImages );
217 }
218}
219
220void QgsTerrainEntity::onTerrainElevationOffsetChanged()
221{
222 float newOffset = qobject_cast<Qgs3DMapSettings *>( sender() )->terrainSettings()->elevationOffset();
223 mTerrainTransform->setTranslation( QVector3D( 0.0f, 0.0f, newOffset ) );
224}
225
226float QgsTerrainEntity::terrainElevationOffset() const
227{
228 return mMapSettings->terrainSettings()->elevationOffset();
229}
230
231
232// -----------
233
234
235TerrainMapUpdateJob::TerrainMapUpdateJob( QgsTerrainTextureGenerator *textureGenerator, QgsChunkNode *node )
236 : QgsChunkQueueJob( node )
237 , mTextureGenerator( textureGenerator )
238{
239}
240
241void TerrainMapUpdateJob::start()
242{
243 QgsChunkNode *node = chunk();
244
245 QgsTerrainTileEntity *entity = qobject_cast<QgsTerrainTileEntity *>( node->entity() );
246 connect( mTextureGenerator, &QgsTerrainTextureGenerator::tileReady, this, &TerrainMapUpdateJob::onTileReady );
247 mJobId = mTextureGenerator->render( entity->textureImage()->imageExtent(), node->tileId(), entity->textureImage()->imageDebugText() );
248}
249
250void TerrainMapUpdateJob::cancel()
251{
252 if ( mJobId != -1 )
253 mTextureGenerator->cancelJob( mJobId );
254}
255
256
257void TerrainMapUpdateJob::onTileReady( int jobId, const QImage &image )
258{
259 if ( mJobId == jobId )
260 {
261 QgsTerrainTileEntity *entity = qobject_cast<QgsTerrainTileEntity *>( mNode->entity() );
262 entity->textureImage()->setImage( image );
263 mJobId = -1;
264 emit finished();
265 }
266}
267
Definition of the world.
void backgroundColorChanged()
Emitted when the background color has changed.
void showTerrainBoundingBoxesChanged()
Emitted when the flag whether terrain's bounding boxes are shown has changed.
const QgsAbstractTerrainSettings * terrainSettings() const
Returns the terrain settings.
void terrainMapThemeChanged()
Emitted when terrain's map theme has changed.
QgsTerrainGenerator * terrainGenerator() const
Returns the terrain generator.
void terrainSettingsChanged()
Emitted when the terrain settings are changed.
void showLabelsChanged()
Emitted when the flag whether labels are displayed on terrain tiles has 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.
static QgsAABB mapToWorldExtent(const QgsRectangle &extent, double zMin, double zMax, const QgsVector3D &mapOrigin)
Converts map extent to axis aligned bounding box in 3D world coordinates.
static QgsVector3D worldToMapCoordinates(const QgsVector3D &worldCoords, const QgsVector3D &origin)
Converts 3D world coordinates to map coordinates (applies offset).
Axis-aligned bounding box - in world coords.
Definition qgsaabb.h:35
float distanceFromPoint(float x, float y, float z) const
Returns shortest distance from the box to a point.
Definition qgsaabb.cpp:46
double elevationOffset() const
Returns the elevation offset of the terrain (used to move the terrain up or down).
Base class for all map layer types.
Definition qgsmaplayer.h:80
void repaintRequested(bool deferredUpdate=false)
By emitting this signal the layer tells that either appearance or content have been changed and any v...
A representation of a ray in 3D.
Definition qgsray3d.h:31
QVector3D origin() const
Returns the origin of the ray.
Definition qgsray3d.h:44
QVector3D direction() const
Returns the direction of the ray see setDirection().
Definition qgsray3d.h:50
Responsible for defining parameters of the ray casting operations in 3D map canvases.
Contains details about the ray intersecting entities when ray casting in a 3D map canvas.
void setMapCoordinates(const QgsVector3D &point)
Sets the hit point position in 3d map coordinates.
void setDistance(double distance)
Sets the hit's distance from the ray's origin.
@ QuantizedMesh
Terrain is built from quantized mesh tiles.
@ Dem
Terrain is built from raster layer with digital elevation model.
@ Online
Terrain is built from downloaded tiles with digital elevation model.
@ Mesh
Terrain is built from mesh layer with z value on vertices.
@ Flat
The whole terrain is flat area.
A 3D vector (similar to QVector3D) with the difference that it uses double precision instead of singl...
Definition qgsvector3d.h:30
double y() const
Returns Y coordinate.
Definition qgsvector3d.h:49
double x() const
Returns X coordinate.
Definition qgsvector3d.h:47
bool rayBoxIntersection(const QgsRay3D &ray, const QgsAABB &nodeBbox)
Tests whether an axis aligned box is intersected by a ray.