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