QGIS API Documentation 3.99.0-Master (09f76ad7019)
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 <QString>
35#include <Qt3DCore/QTransform>
36#include <Qt3DRender/QGeometryRenderer>
37
38#include "moc_qgsterrainentity.cpp"
39
40using namespace Qt::StringLiterals;
41
43
45class TerrainMapUpdateJobFactory : public QgsChunkQueueJobFactory
46{
47 public:
48 TerrainMapUpdateJobFactory( QgsTerrainTextureGenerator *textureGenerator )
49 : mTextureGenerator( textureGenerator )
50 {
51 }
52
53 QgsChunkQueueJob *createJob( QgsChunkNode *chunk ) override
54 {
55 return new TerrainMapUpdateJob( mTextureGenerator, chunk );
56 }
57
58 private:
59 QgsTerrainTextureGenerator *mTextureGenerator = nullptr;
60};
61
62
63// -----------
64
65
66QgsTerrainEntity::QgsTerrainEntity( Qgs3DMapSettings *map, Qt3DCore::QNode *parent )
67 : QgsChunkedEntity( map, map->terrainSettings()->maximumScreenError(), map->terrainGenerator(), false, std::numeric_limits<int>::max(), parent )
68{
69 map->terrainGenerator()->setTerrain( this );
70 mIsValid = map->terrainGenerator()->isValid();
71
72 connect( map, &Qgs3DMapSettings::showTerrainBoundingBoxesChanged, this, &QgsTerrainEntity::onShowBoundingBoxesChanged );
73 connect( map, &Qgs3DMapSettings::showTerrainTilesInfoChanged, this, &QgsTerrainEntity::invalidateMapImages );
74 connect( map, &Qgs3DMapSettings::showLabelsChanged, this, &QgsTerrainEntity::invalidateMapImages );
75 connect( map, &Qgs3DMapSettings::layersChanged, this, &QgsTerrainEntity::onLayersChanged );
76 connect( map, &Qgs3DMapSettings::backgroundColorChanged, this, &QgsTerrainEntity::invalidateMapImages );
77 connect( map, &Qgs3DMapSettings::terrainMapThemeChanged, this, &QgsTerrainEntity::invalidateMapImages );
78 connect( map, &Qgs3DMapSettings::terrainSettingsChanged, this, &QgsTerrainEntity::onTerrainElevationOffsetChanged );
79
80 connectToLayersRepaintRequest();
81
82 mTextureGenerator = new QgsTerrainTextureGenerator( *map );
83
84 mUpdateJobFactory = std::make_unique<TerrainMapUpdateJobFactory>( mTextureGenerator );
85
86 mTerrainTransform = new Qt3DCore::QTransform;
87 mTerrainTransform->setScale( 1.0f );
88 mTerrainTransform->setTranslation( QVector3D( 0.0f, 0.0f, map->terrainSettings()->elevationOffset() ) );
89 addComponent( mTerrainTransform );
90}
91
92QgsTerrainEntity::~QgsTerrainEntity()
93{
94 // cancel / wait for jobs
95 cancelActiveJobs();
96
97 delete mTextureGenerator;
98}
99
100QList<QgsRayCastHit> QgsTerrainEntity::rayIntersection( const QgsRay3D &ray, const QgsRayCastContext &context ) const
101{
102 Q_UNUSED( context )
103 QList<QgsRayCastHit> result;
104
105 float minDist = -1;
106 QgsVector3D intersectionPointMapCoords;
107 switch ( mMapSettings->terrainGenerator()->type() )
108 {
110 {
111 if ( ray.direction().z() == 0 )
112 break; // the ray is parallel to the flat terrain
113
114 const float dist = static_cast<float>( mMapSettings->terrainSettings()->elevationOffset() - ray.origin().z() - mMapSettings->origin().z() ) / ray.direction().z();
115 const QVector3D terrainPlanePoint = ray.origin() + ray.direction() * dist;
116 const QgsVector3D mapCoords = Qgs3DUtils::worldToMapCoordinates( terrainPlanePoint, mMapSettings->origin() );
117 if ( mMapSettings->extent().contains( mapCoords.x(), mapCoords.y() ) )
118 {
119 minDist = dist;
120 intersectionPointMapCoords = mapCoords;
121 }
122 break;
123 }
125 {
126 const QList<QgsChunkNode *> activeNodes = this->activeNodes();
127 QVector3D nearestIntersectionPoint;
128 for ( QgsChunkNode *node : activeNodes )
129 {
130 QgsAABB nodeBbox = Qgs3DUtils::mapToWorldExtent( node->box3D(), mMapSettings->origin() );
131
132 if ( node->entity() && ( minDist < 0 || nodeBbox.distanceFromPoint( ray.origin() ) < minDist ) && QgsRayCastingUtils::rayBoxIntersection( ray, nodeBbox ) )
133 {
134 Qt3DRender::QGeometryRenderer *rend = node->entity()->findChild<Qt3DRender::QGeometryRenderer *>();
135 auto *geom = rend->geometry();
136 Qt3DCore::QTransform *tr = node->entity()->findChild<Qt3DCore::QTransform *>();
137 QVector3D nodeIntPoint;
138 DemTerrainTileGeometry *demGeom = static_cast<DemTerrainTileGeometry *>( geom );
139 if ( demGeom->rayIntersection( ray, context, tr->matrix(), nodeIntPoint ) )
140 {
141 const float dist = ( ray.origin() - nodeIntPoint ).length();
142 if ( minDist < 0 || dist < minDist )
143 {
144 minDist = dist;
145 nearestIntersectionPoint = nodeIntPoint;
146 }
147 }
148 }
149 }
150 if ( minDist >= 0 )
151 intersectionPointMapCoords = Qgs3DUtils::worldToMapCoordinates( nearestIntersectionPoint, mMapSettings->origin() );
152 break;
153 }
157 // not supported
158 break;
159 }
160 if ( minDist >= 0 )
161 {
162 QgsRayCastHit hit;
163 hit.setDistance( minDist );
164 hit.setMapCoordinates( intersectionPointMapCoords );
165 result.append( hit );
166 }
167 return result;
168}
169
170void QgsTerrainEntity::onShowBoundingBoxesChanged()
171{
172 setShowBoundingBoxes( mMapSettings->showTerrainBoundingBoxes() );
173}
174
175
176void QgsTerrainEntity::invalidateMapImages()
177{
178 QgsEventTracing::addEvent( QgsEventTracing::Instant, u"3D"_s, u"Invalidate textures"_s );
179
180 // handle active nodes
181
182 updateNodes( mActiveNodes, mUpdateJobFactory.get() );
183
184 // handle inactive nodes afterwards
185
186 QList<QgsChunkNode *> inactiveNodes;
187 const QList<QgsChunkNode *> descendants = mRootNode->descendants();
188 for ( QgsChunkNode *node : descendants )
189 {
190 if ( !node->entity() )
191 continue;
192 if ( mActiveNodes.contains( node ) )
193 continue;
194 inactiveNodes << node;
195 }
196
197 updateNodes( inactiveNodes, mUpdateJobFactory.get() );
198
199 setNeedsUpdate( true );
200}
201
202void QgsTerrainEntity::onLayersChanged()
203{
204 connectToLayersRepaintRequest();
205 invalidateMapImages();
206}
207
208void QgsTerrainEntity::connectToLayersRepaintRequest()
209{
210 for ( QgsMapLayer *layer : std::as_const( mLayers ) )
211 {
212 disconnect( layer, &QgsMapLayer::repaintRequested, this, &QgsTerrainEntity::invalidateMapImages );
213 }
214
215 mLayers = mMapSettings->layers();
216
217 for ( QgsMapLayer *layer : std::as_const( mLayers ) )
218 {
219 connect( layer, &QgsMapLayer::repaintRequested, this, &QgsTerrainEntity::invalidateMapImages );
220 }
221}
222
223void QgsTerrainEntity::onTerrainElevationOffsetChanged()
224{
225 float newOffset = qobject_cast<Qgs3DMapSettings *>( sender() )->terrainSettings()->elevationOffset();
226 mTerrainTransform->setTranslation( QVector3D( 0.0f, 0.0f, newOffset ) );
227}
228
229float QgsTerrainEntity::terrainElevationOffset() const
230{
231 return mMapSettings->terrainSettings()->elevationOffset();
232}
233
234
235// -----------
236
237
238TerrainMapUpdateJob::TerrainMapUpdateJob( QgsTerrainTextureGenerator *textureGenerator, QgsChunkNode *node )
239 : QgsChunkQueueJob( node )
240 , mTextureGenerator( textureGenerator )
241{
242}
243
244void TerrainMapUpdateJob::start()
245{
246 QgsChunkNode *node = chunk();
247
248 QgsTerrainTileEntity *entity = qobject_cast<QgsTerrainTileEntity *>( node->entity() );
249 connect( mTextureGenerator, &QgsTerrainTextureGenerator::tileReady, this, &TerrainMapUpdateJob::onTileReady );
250 mJobId = mTextureGenerator->render( entity->textureImage()->imageExtent(), node->tileId(), entity->textureImage()->imageDebugText() );
251}
252
253void TerrainMapUpdateJob::cancel()
254{
255 if ( mJobId != -1 )
256 mTextureGenerator->cancelJob( mJobId );
257}
258
259
260void TerrainMapUpdateJob::onTileReady( int jobId, const QImage &image )
261{
262 if ( mJobId == jobId )
263 {
264 QgsTerrainTileEntity *entity = qobject_cast<QgsTerrainTileEntity *>( mNode->entity() );
265 entity->textureImage()->setImage( image );
266 mJobId = -1;
267 emit finished();
268 }
269}
270
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:50
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:83
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:33
double y() const
Returns Y coordinate.
Definition qgsvector3d.h:52
double x() const
Returns X coordinate.
Definition qgsvector3d.h:50
bool rayBoxIntersection(const QgsRay3D &ray, const QgsAABB &nodeBbox)
Tests whether an axis aligned box is intersected by a ray.