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