QGIS API Documentation 3.99.0-Master (21b3aa880ba)
Loading...
Searching...
No Matches
qgsdemterraintilegeometry_p.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsdemterraintilegeometry_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
17
18#include <QMatrix4x4>
19
20#include "moc_qgsdemterraintilegeometry_p.cpp"
21
22#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
23#include <Qt3DRender/QAttribute>
24#include <Qt3DRender/QBuffer>
25#include <Qt3DRender/QAbstractFunctor>
26typedef Qt3DRender::QAttribute Qt3DQAttribute;
27typedef Qt3DRender::QBuffer Qt3DQBuffer;
28typedef Qt3DRender::QAbstractFunctor Qt3DQAbstractFunctor;
29#else
30#include <Qt3DCore/QAttribute>
31#include <Qt3DCore/QBuffer>
32#include <Qt3DCore/QAbstractFunctor>
33typedef Qt3DCore::QAttribute Qt3DQAttribute;
34typedef Qt3DCore::QBuffer Qt3DQBuffer;
35typedef Qt3DCore::QAbstractFunctor Qt3DQAbstractFunctor;
36#endif
37#include <limits>
38#include <cmath>
39#include "qgsraycastingutils.h"
40#include "qgsray3d.h"
41#include "qgsraycastcontext.h"
42#include "qgis.h"
43
45
46using namespace Qt3DRender;
47
48static QByteArray createPlaneVertexData( int res, float side, float vertScale, float skirtHeight, const QByteArray &heights )
49{
50 Q_ASSERT( res >= 2 );
51 Q_ASSERT( heights.count() == res * res * static_cast<int>( sizeof( float ) ) );
52
53 const float *zData = ( const float * ) heights.constData();
54 const float *zBits = zData;
55
56 const int nVerts = ( res + 2 ) * ( res + 2 );
57
58 // Populate a buffer with the interleaved per-vertex data with
59 // vec3 pos, vec2 texCoord, vec3 normal
60 const quint32 elementSize = 3 + 2 + 3;
61 const quint32 stride = elementSize * sizeof( float );
62 QByteArray bufferBytes;
63 bufferBytes.resize( stride * nVerts );
64 float *fptr = reinterpret_cast<float *>( bufferBytes.data() );
65
66 QSize resolution( res, res );
67 const float x0 = 0;
68 const float y0 = side;
69 const float dx = side / static_cast<float>( resolution.width() - 1 );
70 const float dy = side / static_cast<float>( resolution.height() - 1 );
71 const float du = 1.0f / static_cast<float>( resolution.width() - 1 );
72 const float dv = 1.0f / static_cast<float>( resolution.height() - 1 );
73
74 // the height of vertices with no-data value... the value should not really matter
75 // as we do not create valid triangles that would use such vertices
76 const float noDataHeight = 0;
77
78 const int iMax = resolution.width() - 1;
79 const int jMax = resolution.height() - 1;
80
81 // Iterate over y
82 for ( int j = -1; j <= resolution.height(); ++j )
83 {
84 int jBound = std::clamp( j, 0, jMax );
85 const float y = y0 - static_cast<float>( jBound ) * dy;
86 const float v = static_cast<float>( jBound ) * dv;
87
88 // Iterate over x
89 for ( int i = -1; i <= resolution.width(); ++i )
90 {
91 int iBound = std::clamp( i, 0, iMax );
92 const float x = x0 + static_cast<float>( iBound ) * dx;
93 const float u = static_cast<float>( iBound ) * du;
94
95 float height;
96 if ( i == iBound && j == jBound )
97 height = *zBits++;
98 else
99 height = zData[jBound * resolution.width() + iBound] - skirtHeight;
100
101 if ( std::isnan( height ) )
102 height = noDataHeight;
103
104 // position
105 *fptr++ = x;
106 *fptr++ = y;
107 *fptr++ = height * vertScale;
108
109 // texture coordinates
110 *fptr++ = u;
111 *fptr++ = v;
112
113 // calculate normal coordinates
114#define zAt( ii, jj ) zData[jj * resolution.width() + ii] * vertScale
115 float zi0 = zAt( std::clamp( i - 1, 0, iMax ), jBound );
116 float zi1 = zAt( std::clamp( i + 1, 0, iMax ), jBound );
117 float zj0 = zAt( iBound, std::clamp( j - 1, 0, jMax ) );
118 float zj1 = zAt( iBound, std::clamp( j + 1, 0, jMax ) );
119
120 QVector3D n;
121 if ( std::isnan( zi0 ) || std::isnan( zi1 ) || std::isnan( zj0 ) || std::isnan( zj1 ) )
122 n = QVector3D( 0, 0, 1 );
123 else
124 {
125 float di, dj;
126 float zij = height * vertScale;
127
128 if ( i == 0 )
129 di = 2 * ( zij - zi1 );
130 else if ( i == iMax )
131 di = 2 * ( zi0 - zij );
132 else
133 di = zi0 - zi1;
134
135 if ( j == 0 )
136 dj = 2 * ( zij - zj1 );
137 else if ( j == jMax )
138 dj = 2 * ( zj0 - zij );
139 else
140 dj = zj0 - zj1;
141
142 n = QVector3D( di, -dj, 2 * side / static_cast<float>( res ) );
143 n.normalize();
144 }
145
146 *fptr++ = n.x();
147 *fptr++ = n.y();
148 *fptr++ = n.z();
149 }
150 }
151
152 return bufferBytes;
153}
154
155inline int ijToHeightMapIndex( int i, int j, int resX, int resZ )
156{
157 i = std::clamp( i, 1, resX ) - 1;
158 j = std::clamp( j, 1, resZ ) - 1;
159 return j * resX + i;
160}
161
162static bool hasNoData( int i, int j, const float *heightMap, int resX, int resZ )
163{
164 return std::isnan( heightMap[ijToHeightMapIndex( i, j, resX, resZ )] ) || std::isnan( heightMap[ijToHeightMapIndex( i + 1, j, resX, resZ )] ) || std::isnan( heightMap[ijToHeightMapIndex( i, j + 1, resX, resZ )] ) || std::isnan( heightMap[ijToHeightMapIndex( i + 1, j + 1, resX, resZ )] );
165}
166
167static QByteArray createPlaneIndexData( int res, const QByteArray &heightMap )
168{
169 QSize resolution( res, res );
170 int numVerticesX = resolution.width() + 2;
171 int numVerticesZ = resolution.height() + 2;
172
173 // Create the index data. 2 triangles per rectangular face
174 const int faces = 2 * ( numVerticesX - 1 ) * ( numVerticesZ - 1 );
175 const quint32 indices = 3 * faces;
176 Q_ASSERT( indices < std::numeric_limits<quint32>::max() );
177 QByteArray indexBytes;
178 indexBytes.resize( indices * sizeof( quint32 ) );
179 quint32 *indexPtr = reinterpret_cast<quint32 *>( indexBytes.data() );
180
181 const float *heightMapFloat = reinterpret_cast<const float *>( heightMap.constData() );
182
183 // Iterate over z
184 for ( int j = 0; j < numVerticesZ - 1; ++j )
185 {
186 const int rowStartIndex = j * numVerticesX;
187 const int nextRowStartIndex = ( j + 1 ) * numVerticesX;
188
189 // Iterate over x
190 for ( int i = 0; i < numVerticesX - 1; ++i )
191 {
192 if ( hasNoData( i, j, heightMapFloat, res, res ) )
193 {
194 // at least one corner of the quad has no-data value
195 // so let's make two invalid triangles
196 *indexPtr++ = rowStartIndex + i;
197 *indexPtr++ = rowStartIndex + i;
198 *indexPtr++ = rowStartIndex + i;
199
200 *indexPtr++ = rowStartIndex + i;
201 *indexPtr++ = rowStartIndex + i;
202 *indexPtr++ = rowStartIndex + i;
203 continue;
204 }
205
206 // Split quad into two triangles
207 *indexPtr++ = rowStartIndex + i;
208 *indexPtr++ = nextRowStartIndex + i;
209 *indexPtr++ = rowStartIndex + i + 1;
210
211 *indexPtr++ = nextRowStartIndex + i;
212 *indexPtr++ = nextRowStartIndex + i + 1;
213 *indexPtr++ = rowStartIndex + i + 1;
214 }
215 }
216
217 return indexBytes;
218}
219
220// QAbstractFunctor marked as deprecated in 5.15, but undeprecated for Qt 6.0. TODO -- remove when we require 6.0
222
224class PlaneVertexBufferFunctor : public Qt3DQAbstractFunctor
225{
226 public:
227 explicit PlaneVertexBufferFunctor( int resolution, float side, float vertScale, float skirtHeight, const QByteArray &heightMap )
228 : mResolution( resolution )
229 , mSide( side )
230 , mVertScale( vertScale )
231 , mSkirtHeight( skirtHeight )
232 , mHeightMap( heightMap )
233 {}
234
235 QByteArray operator()()
236 {
237 return createPlaneVertexData( mResolution, mSide, mVertScale, mSkirtHeight, mHeightMap );
238 }
239
240 qintptr id() const override
241 {
242#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
243 return reinterpret_cast<qintptr>( &Qt3DRender::FunctorType<PlaneVertexBufferFunctor>::id );
244#else
245 return reinterpret_cast<qintptr>( &Qt3DCore::FunctorType<PlaneVertexBufferFunctor>::id );
246#endif
247 }
248
249 bool operator==( const Qt3DQAbstractFunctor &other ) const
250 {
251 const PlaneVertexBufferFunctor *otherFunctor = dynamic_cast<const PlaneVertexBufferFunctor *>( &other );
252 if ( otherFunctor )
253 return ( otherFunctor->mResolution == mResolution && otherFunctor->mSide == mSide && otherFunctor->mVertScale == mVertScale && otherFunctor->mSkirtHeight == mSkirtHeight && otherFunctor->mHeightMap == mHeightMap );
254 return false;
255 }
256
257 private:
258 int mResolution;
259 float mSide;
260 float mVertScale;
261 float mSkirtHeight;
262 QByteArray mHeightMap;
263};
264
266
267// QAbstractFunctor marked as deprecated in 5.15, but undeprecated for Qt 6.0. TODO -- remove when we require 6.0
269
271class PlaneIndexBufferFunctor : public Qt3DQAbstractFunctor
272{
273 public:
274 explicit PlaneIndexBufferFunctor( int resolution, const QByteArray &heightMap )
275 : mResolution( resolution )
276 , mHeightMap( heightMap )
277 {}
278
279 QByteArray operator()()
280 {
281 return createPlaneIndexData( mResolution, mHeightMap );
282 }
283
284 qintptr id() const override
285 {
286#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
287 return reinterpret_cast<qintptr>( &Qt3DRender::FunctorType<PlaneIndexBufferFunctor>::id );
288#else
289 return reinterpret_cast<qintptr>( &Qt3DCore::FunctorType<PlaneIndexBufferFunctor>::id );
290#endif
291 }
292
293 bool operator==( const Qt3DQAbstractFunctor &other ) const
294 {
295 const PlaneIndexBufferFunctor *otherFunctor = dynamic_cast<const PlaneIndexBufferFunctor *>( &other );
296 if ( otherFunctor )
297 return ( otherFunctor->mResolution == mResolution );
298 return false;
299 }
300
301 private:
302 int mResolution;
303 QByteArray mHeightMap;
304};
305
307
308// ------------
309
310
311DemTerrainTileGeometry::DemTerrainTileGeometry( int resolution, float side, float vertScale, float skirtHeight, const QByteArray &heightMap, DemTerrainTileGeometry::QNode *parent )
312 : QGeometry( parent )
313 , mResolution( resolution )
314 , mSide( side )
315 , mVertScale( vertScale )
316 , mSkirtHeight( skirtHeight )
317 , mHeightMap( heightMap )
318{
319 init();
320}
321
322static bool intersectionDemTriangles( const QByteArray &vertexBuf, const QByteArray &indexBuf, const QgsRay3D &r, const QgsRayCastContext &context, const QMatrix4x4 &worldTransform, QVector3D &intPt )
323{
324 // WARNING! this code is specific to how vertex buffers are built for DEM tiles,
325 // it is not usable for any mesh...
326
327 const float *vertices = reinterpret_cast<const float *>( vertexBuf.constData() );
328 const uint *indices = reinterpret_cast<const uint *>( indexBuf.constData() );
329#ifdef QGISDEBUG
330 int vertexCnt = vertexBuf.count() / sizeof( float );
331 Q_ASSERT( vertexCnt % 8 == 0 );
332#endif
333 int indexCnt = indexBuf.count() / sizeof( uint );
334 Q_ASSERT( indexCnt % 3 == 0 );
335 int triangleCount = indexCnt / 3;
336
337 QVector3D intersectionPt, minIntersectionPt;
338 float distance;
339 float minDistance = -1;
340
341 for ( int i = 0; i < triangleCount; ++i )
342 {
343 int v0 = indices[i * 3], v1 = indices[i * 3 + 1], v2 = indices[i * 3 + 2];
344 QVector3D a( vertices[v0 * 8], vertices[v0 * 8 + 1], vertices[v0 * 8 + 2] );
345 QVector3D b( vertices[v1 * 8], vertices[v1 * 8 + 1], vertices[v1 * 8 + 2] );
346 QVector3D c( vertices[v2 * 8], vertices[v2 * 8 + 1], vertices[v2 * 8 + 2] );
347
348 const QVector3D tA = worldTransform * a;
349 const QVector3D tB = worldTransform * b;
350 const QVector3D tC = worldTransform * c;
351
352 QVector3D uvw;
353 float t = 0;
354 if ( QgsRayCastingUtils::rayTriangleIntersection( r, context.maximumDistance(), tA, tB, tC, uvw, t ) )
355 {
356 intersectionPt = r.point( t * context.maximumDistance() );
357 distance = r.projectedDistance( intersectionPt );
358
359 // we only want the first intersection of the ray with the mesh (closest to the ray origin)
360 if ( minDistance == -1 || distance < minDistance )
361 {
362 minDistance = distance;
363 minIntersectionPt = intersectionPt;
364 }
365 }
366 }
367
368 if ( minDistance != -1 )
369 {
370 intPt = minIntersectionPt;
371 return true;
372 }
373 else
374 return false;
375}
376
377bool DemTerrainTileGeometry::rayIntersection( const QgsRay3D &ray, const QgsRayCastContext &context, const QMatrix4x4 &worldTransform, QVector3D &intersectionPoint )
378{
379 return intersectionDemTriangles( mVertexBuffer->data(), mIndexBuffer->data(), ray, context, worldTransform, intersectionPoint );
380}
381
382void DemTerrainTileGeometry::init()
383{
384 mPositionAttribute = new Qt3DQAttribute( this );
385 mNormalAttribute = new Qt3DQAttribute( this );
386 mTexCoordAttribute = new Qt3DQAttribute( this );
387 mIndexAttribute = new Qt3DQAttribute( this );
388 mVertexBuffer = new Qt3DQBuffer( this );
389 mIndexBuffer = new Qt3DQBuffer( this );
390
391 int nVertsX = mResolution + 2;
392 int nVertsZ = mResolution + 2;
393 const int nVerts = nVertsX * nVertsZ;
394 const int stride = ( 3 + 2 + 3 ) * sizeof( float );
395 const int faces = 2 * ( nVertsX - 1 ) * ( nVertsZ - 1 );
396
397 mPositionAttribute->setName( Qt3DQAttribute::defaultPositionAttributeName() );
398 mPositionAttribute->setVertexBaseType( Qt3DQAttribute::Float );
399 mPositionAttribute->setVertexSize( 3 );
400 mPositionAttribute->setAttributeType( Qt3DQAttribute::VertexAttribute );
401 mPositionAttribute->setBuffer( mVertexBuffer );
402 mPositionAttribute->setByteStride( stride );
403 mPositionAttribute->setCount( nVerts );
404
405 mTexCoordAttribute->setName( Qt3DQAttribute::defaultTextureCoordinateAttributeName() );
406 mTexCoordAttribute->setVertexBaseType( Qt3DQAttribute::Float );
407 mTexCoordAttribute->setVertexSize( 2 );
408 mTexCoordAttribute->setAttributeType( Qt3DQAttribute::VertexAttribute );
409 mTexCoordAttribute->setBuffer( mVertexBuffer );
410 mTexCoordAttribute->setByteStride( stride );
411 mTexCoordAttribute->setByteOffset( 3 * sizeof( float ) );
412 mTexCoordAttribute->setCount( nVerts );
413
414 mNormalAttribute->setName( Qt3DQAttribute::defaultNormalAttributeName() );
415 mNormalAttribute->setVertexBaseType( Qt3DQAttribute::Float );
416 mNormalAttribute->setVertexSize( 3 );
417 mNormalAttribute->setAttributeType( Qt3DQAttribute::VertexAttribute );
418 mNormalAttribute->setBuffer( mVertexBuffer );
419 mNormalAttribute->setByteStride( stride );
420 mNormalAttribute->setByteOffset( 5 * sizeof( float ) );
421 mNormalAttribute->setCount( nVerts );
422
423 mIndexAttribute->setAttributeType( Qt3DQAttribute::IndexAttribute );
424 mIndexAttribute->setVertexBaseType( Qt3DQAttribute::UnsignedInt );
425 mIndexAttribute->setBuffer( mIndexBuffer );
426
427 // Each primitive has 3 vertives
428 mIndexAttribute->setCount( faces * 3 );
429
430 // switched to setting data instead of just setting data generators because we also need the buffers
431 // available for ray-mesh intersections and we can't access the private copy of data in Qt (if there is any)
432
433 mVertexBuffer->setData( PlaneVertexBufferFunctor( mResolution, mSide, mVertScale, mSkirtHeight, mHeightMap )() );
434 mIndexBuffer->setData( PlaneIndexBufferFunctor( mResolution, mHeightMap )() );
435
436 addAttribute( mPositionAttribute );
437 addAttribute( mTexCoordAttribute );
438 addAttribute( mNormalAttribute );
439 addAttribute( mIndexAttribute );
440}
441
A representation of a ray in 3D.
Definition qgsray3d.h:31
float projectedDistance(const QVector3D &point) const
Returns the distance of the projection of a point to the ray.
Definition qgsray3d.cpp:47
QVector3D point(float distance) const
Returns the point along the ray with the specified distance from the ray's origin.
Definition qgsray3d.cpp:68
Responsible for defining parameters of the ray casting operations in 3D map canvases.
float maximumDistance() const
The maximum distance from ray origin to look for hits when casting a ray.
bool rayTriangleIntersection(const QgsRay3D &ray, float maxDist, const QVector3D &a, const QVector3D &b, const QVector3D &c, QVector3D &uvw, float &t)
Tests whether a triangle is intersected by a ray.
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
#define Q_NOWARN_DEPRECATED_POP
Definition qgis.h:7170
#define Q_NOWARN_DEPRECATED_PUSH
Definition qgis.h:7169
Qt3DCore::QAttribute Qt3DQAttribute
Qt3DCore::QBuffer Qt3DQBuffer
Qt3DCore::QAbstractFunctor Qt3DQAbstractFunctor
bool operator==(const QgsFeatureIterator &fi1, const QgsFeatureIterator &fi2)