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