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