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