QGIS API Documentation 3.39.0-Master (d85f3c2a281)
Loading...
Searching...
No Matches
qgsquantizedmeshtiles.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsquantizedmeshtiles.cpp
3 ----------------------------
4 begin : May 2024
5 copyright : (C) 2024 by David Koňařík
6 email : dvdkon at konarici dot cz
7
8 ***************************************************************************
9 * *
10 * This program is free software; you can redistribute it and/or modify *
11 * it under the terms of the GNU General Public License as published by *
12 * the Free Software Foundation; either version 2 of the License, or *
13 * (at your option) any later version. *
14 * *
15 ***************************************************************************/
16
18
19#include "qgsexception.h"
20#include <algorithm>
21#include <cstddef>
22#include <qdebug.h>
23#include <qglobal.h>
24#include <qstringliteral.h>
25#include <qvector3d.h>
26
28
32class VectorStream
33{
34 public:
35 size_t mOffset = 0;
36 VectorStream( const QByteArray &vec ) : mVec( vec ) {}
37 const void *read( size_t bytes )
38 {
39 if ( ( size_t ) mVec.size() < mOffset + bytes )
40 {
41 throw QgsQuantizedMeshParsingException( "Tried to read beyond EOF when parsing quantized mesh tile" );
42 }
43 const void *data = mVec.data() + mOffset;
44 mOffset += bytes;
45 return data;
46 }
47 size_t remaining() const
48 {
49 return mVec.size() - mOffset;
50 }
51 private:
52 const QByteArray &mVec;
53};
54
56
57constexpr uint8_t normalsExtensionId = 1;
58
59// Algorithm from http://jcgt.org/published/0003/02/01/
60static QVector3D oct16Decode( uint8_t x, uint8_t y )
61{
62 if ( x == 0 && y == 0 )
63 return QVector3D( 0, 0, 0 );
64 float fx = x / 255.0f * 2.0f - 1.0f;
65 float fy = y / 255.0f * 2.0f - 1.0f;
66 QVector3D decoded( fx, fy, 1.0f - abs( fx ) - abs( fy ) );
67 if ( decoded.z() < 0 )
68 {
69 decoded.setX( ( 1.0f - abs( fy ) ) * ( fx >= 0 ? 1.0f : -1.0f ) );
70 decoded.setY( ( 1.0f - abs( fx ) ) * ( fy >= 0 ? 1.0f : -1.0f ) );
71 }
72 decoded.normalize();
73 return decoded;
74}
75
76// Copied from specification
77static uint16_t zigZagDecode( uint16_t value )
78{
79 return ( value >> 1 ) ^ ( -( value & 1 ) );
80}
81
82static std::vector<uint32_t> parseU32OrU16Array( VectorStream &stream,
83 bool isU32, size_t countMult )
84{
85 std::vector<uint32_t> values;
86 if ( isU32 )
87 {
88 // Pad to multiple of 4
89 stream.read( stream.mOffset % 4 );
90 const uint32_t *countBase =
91 reinterpret_cast<const uint32_t *>( stream.read( sizeof( uint32_t ) ) );
92 size_t count = static_cast<size_t>( *countBase ) * countMult;
93 values.resize( count );
94 const uint32_t *valuesRaw = reinterpret_cast<const uint32_t *>(
95 stream.read( sizeof( uint32_t ) * count ) );
96 std::copy( valuesRaw, valuesRaw + count, values.begin() );
97 }
98 else
99 {
100 const uint32_t *countBase =
101 reinterpret_cast<const uint32_t *>( stream.read( sizeof( uint32_t ) ) );
102 size_t count = static_cast<size_t>( *countBase ) * countMult;
103 values.resize( count );
104 const uint16_t *valuesRaw = reinterpret_cast<const uint16_t *>(
105 stream.read( sizeof( uint16_t ) * count ) );
106 std::copy( valuesRaw, valuesRaw + count, values.begin() );
107 }
108 return values;
109}
110
112{
113 // Check if machine is big endian
114 uint16_t endiannessCheck = 0x1020;
115 if ( reinterpret_cast<char *>( &endiannessCheck )[0] == 0x10 )
117 "Parsing quantized mesh tiles on big endian machines is not supported" );
118
119 VectorStream stream( data );
120 mHeader = *reinterpret_cast<const QgsQuantizedMeshHeader *>(
121 stream.read( sizeof( QgsQuantizedMeshHeader ) ) );
122
123 auto uArr = reinterpret_cast<const uint16_t *>(
124 stream.read( sizeof( uint16_t ) * mHeader.vertexCount ) );
125 auto vArr = reinterpret_cast<const uint16_t *>(
126 stream.read( sizeof( uint16_t ) * mHeader.vertexCount ) );
127 auto heightArr = reinterpret_cast<const uint16_t *>(
128 stream.read( sizeof( uint16_t ) * mHeader.vertexCount ) );
129
130 uint16_t u = 0, v = 0, height = 0;
131 mVertexCoords.resize( static_cast<size_t>( mHeader.vertexCount ) * 3 );
132 for ( size_t i = 0; i < mHeader.vertexCount; i++ )
133 {
134 u += zigZagDecode( uArr[i] );
135 v += zigZagDecode( vArr[i] );
136 height += zigZagDecode( heightArr[i] );
137
138 mVertexCoords[i * 3] = u;
139 mVertexCoords[i * 3 + 1] = v;
140 mVertexCoords[i * 3 + 2] = height;
141 }
142
143 bool vertexIndices32Bit = mHeader.vertexCount > 65536;
145 parseU32OrU16Array( stream, vertexIndices32Bit, 3 );
146 uint32_t highest = 0;
147 for ( auto &idx : mTriangleIndices )
148 {
149 uint32_t code = idx;
150 idx = highest - code;
151 if ( code == 0 )
152 highest++;
153 }
154
156 parseU32OrU16Array( stream, vertexIndices32Bit, 1 );
158 parseU32OrU16Array( stream, vertexIndices32Bit, 1 );
160 parseU32OrU16Array( stream, vertexIndices32Bit, 1 );
162 parseU32OrU16Array( stream, vertexIndices32Bit, 1 );
163
164 while ( stream.remaining() > 0 )
165 {
166 uint8_t extensionId =
167 *reinterpret_cast<const uint8_t *>( stream.read( sizeof( char ) ) );
168 uint32_t length =
169 *reinterpret_cast<const uint32_t *>( stream.read( sizeof( uint32_t ) ) );
170
171 if ( extensionId == normalsExtensionId )
172 {
173 mNormalCoords.reserve( mHeader.vertexCount * 3 );
174 for ( size_t i = 0; i < mHeader.vertexCount; i++ )
175 {
176 auto normal = oct16Decode(
177 *reinterpret_cast<const uint8_t *>( stream.read( sizeof( char ) ) ),
178 *reinterpret_cast<const uint8_t *>( stream.read( sizeof( char ) ) ) );
179 mNormalCoords.insert( mNormalCoords.end(), {normal.x(), normal.y(), normal.z()} );
180 }
181 continue;
182 }
183
184 std::vector<char> data( length );
185 const char *dataPtr = reinterpret_cast<const char *>( stream.read( length ) );
186 std::copy( dataPtr, dataPtr + length, data.begin() );
187 mExtensions[extensionId] = data;
188 }
189}
190
192{
193 std::vector<uint32_t> newTriangleIndices;
194 for ( size_t i = 0; i < mTriangleIndices.size(); i += 3 )
195 {
196 uint32_t a = mTriangleIndices[i];
197 uint32_t b = mTriangleIndices[i + 1];
198 uint32_t c = mTriangleIndices[i + 2];
199 if ( a != b && b != c && a != c )
200 {
201 newTriangleIndices.insert( newTriangleIndices.end(), {a, b, c} );
202 }
203 }
204 mTriangleIndices = newTriangleIndices;
205}
206
208{
209 auto vertexAsVector = [ this ]( size_t idx )
210 {
211 Q_ASSERT( idx * 3 + 2 < mVertexCoords.size() );
212 return QVector3D( mVertexCoords[idx * 3], mVertexCoords[idx * 3 + 1], mVertexCoords[idx * 3 + 2] );
213 };
214
215 Q_ASSERT( mNormalCoords.size() == 0 );
216 mNormalCoords.resize( mVertexCoords.size(), 0.0 );
217
218 // Sum up contributing normals from all triangles
219 for ( size_t i = 0; i < mTriangleIndices.size(); i += 3 )
220 {
221 std::array<size_t, 3> vtxIdxs {mTriangleIndices[i], mTriangleIndices[i + 1], mTriangleIndices[i + 2]};
222 auto a = vertexAsVector( vtxIdxs[0] );
223 auto b = vertexAsVector( vtxIdxs[1] );
224 auto c = vertexAsVector( vtxIdxs[2] );
225 auto n = QVector3D::crossProduct( b - a, c - a );
226 n.normalize();
227 for ( auto vtx : vtxIdxs )
228 {
229 mNormalCoords[vtx * 3] += n.x();
230 mNormalCoords[vtx * 3 + 1] += n.y();
231 mNormalCoords[vtx * 3 + 2] += n.z();
232 }
233 }
234
235 // Normalize (average over triangles)
236 for ( size_t i = 0; i < mNormalCoords.size(); i += 3 )
237 {
238 QVector3D n( mNormalCoords[i], mNormalCoords[i + 1], mNormalCoords[i + 2] );
239 n.normalize();
240 mNormalCoords[i] = n.x();
241 mNormalCoords[i + 1] = n.y();
242 mNormalCoords[i + 2] = n.z();
243 }
244}
245
246tinygltf::Model QgsQuantizedMeshTile::toGltf( bool addSkirt, double skirtDepth, bool withTextureCoords )
247{
248 tinygltf::Model model;
249
250 tinygltf::Buffer vertexBuffer;
251 vertexBuffer.data.resize( mVertexCoords.size() * sizeof( float ) );
252 std::vector<double> coordMinimums = {32767, 32767, mHeader.MaximumHeight};
253 std::vector<double> coordMaximums = {0, 0, mHeader.MinimumHeight};
254
255 for ( size_t i = 0; i < mVertexCoords.size(); i++ )
256 {
257 double coord = mVertexCoords[i] / 32767.0; // Rescale to 0.0 -- 1.0;
258 // Rescale Z to height in meters
259 if ( i % 3 == 2 )
260 {
262 }
263 ( ( float * ) vertexBuffer.data.data() )[i] = ( float )coord;
264 if ( coordMinimums[i % 3] > coord )
265 coordMinimums[i % 3] = coord;
266 if ( coordMaximums[i % 3] < coord )
267 coordMaximums[i % 3] = coord;
268 }
269 tinygltf::Buffer triangleBuffer;
270 triangleBuffer.data.resize( mTriangleIndices.size() * sizeof( uint32_t ) );
271 const char *triData = reinterpret_cast<const char *>( mTriangleIndices.data() );
272 std::copy( triData, triData + triangleBuffer.data.size(), triangleBuffer.data.begin() );
273
274 if ( addSkirt )
275 {
276 // We first need to sort the edge-indices by coordinate to later create a quad for each "gap"
277 std::sort( mWestVertices.begin(), mWestVertices.end(), [&]( uint32_t a, uint32_t b )
278 {
279 return mVertexCoords[a * 3 + 1] < mVertexCoords[b * 3 + 1];
280 } );
281 std::sort( mSouthVertices.begin(), mSouthVertices.end(), [&]( uint32_t a, uint32_t b )
282 {
283 return mVertexCoords[a * 3] > mVertexCoords[b * 3];
284 } );
285 std::sort( mEastVertices.begin(), mEastVertices.end(), [&]( uint32_t a, uint32_t b )
286 {
287 return mVertexCoords[a * 3 + 1] > mVertexCoords[b * 3 + 1];
288 } );
289 std::sort( mNorthVertices.begin(), mNorthVertices.end(), [&]( uint32_t a, uint32_t b )
290 {
291 return mVertexCoords[a * 3] < mVertexCoords[b * 3];
292 } );
293
294 size_t edgeVertexCount = mWestVertices.size() + mSouthVertices.size() + mEastVertices.size() + mNorthVertices.size();
295 size_t skirtBottomCoordCount =
296 ( ( mWestVertices.size() > 1 ) +
297 ( mSouthVertices.size() > 1 ) +
298 ( mEastVertices.size() > 1 ) +
299 ( mNorthVertices.size() > 1 ) ) * 6;
300 // Add new vertex for each existing edge vertex, projected to Z = minHeight
301 coordMinimums[2] = mHeader.MinimumHeight - skirtDepth;
302 size_t skirtVerticesIdxStart = mVertexCoords.size() / 3;
303 vertexBuffer.data.resize( vertexBuffer.data.size() + ( edgeVertexCount * 3 + skirtBottomCoordCount ) * sizeof( float ) );
304 float *skirtVertexCoords = ( float * )( vertexBuffer.data.data() + ( skirtVerticesIdxStart * 3 * sizeof( float ) ) );
305 auto addSkirtVertices = [&]( const std::vector<uint32_t> &idxs )
306 {
307 size_t startIdx = ( ( uint8_t * ) skirtVertexCoords - vertexBuffer.data.data() ) / sizeof( float ) / 3;
308 for ( uint32_t idx : idxs )
309 {
310 *skirtVertexCoords++ = mVertexCoords[idx * 3] / 32767.0f;
311 *skirtVertexCoords++ = mVertexCoords[idx * 3 + 1] / 32767.0f;
312 *skirtVertexCoords++ = mHeader.MinimumHeight;
313 }
314
315 if ( idxs.size() > 1 )
316 {
317 // Add two vertices at two corners, skirtDepth below the tile bottom
318 *skirtVertexCoords++ = mVertexCoords[idxs[0] * 3] / 32767.0f;
319 *skirtVertexCoords++ = mVertexCoords[idxs[0] * 3 + 1] / 32767.0f;
320 *skirtVertexCoords++ = mHeader.MinimumHeight - skirtDepth;
321
322 *skirtVertexCoords++ = mVertexCoords[idxs[idxs.size() - 1] * 3] / 32767.0f;
323 *skirtVertexCoords++ = mVertexCoords[idxs[idxs.size() - 1] * 3 + 1] / 32767.0f;
324 *skirtVertexCoords++ = mHeader.MinimumHeight - skirtDepth;
325 }
326
327 return startIdx;
328 };
329 size_t westBottomVerticesIdx = addSkirtVertices( mWestVertices );
330 size_t southBottomVerticesIdx = addSkirtVertices( mSouthVertices );
331 size_t eastBottomVerticesIdx = addSkirtVertices( mEastVertices );
332 size_t northBottomVerticesIdx = addSkirtVertices( mNorthVertices );
333 // Check that we didn't miscalculate buffer size
334 Q_ASSERT( skirtVertexCoords == ( float * )( vertexBuffer.data.data() + vertexBuffer.data.size() ) );
335
336 // Add skirt triangles (a trapezoid for each pair of edge vertices)
337 size_t skirtTrianglesStartIdx = triangleBuffer.data.size();
338 size_t edgeQuadCount =
339 // For 0/1 point we have 0 quads, for N we have N-1, and an additional one for skirtDepth
340 ( mWestVertices.size() > 1 ? mWestVertices.size() : 0 ) +
341 ( mSouthVertices.size() > 1 ? mSouthVertices.size() : 0 ) +
342 ( mEastVertices.size() > 1 ? mEastVertices.size() : 0 ) +
343 ( mNorthVertices.size() > 1 ? mNorthVertices.size() : 0 );
344 triangleBuffer.data.resize( triangleBuffer.data.size() + edgeQuadCount * 6 * sizeof( uint32_t ) );
345 uint32_t *skirtTriangles = ( uint32_t * )( triangleBuffer.data.data() + skirtTrianglesStartIdx );
346 auto addSkirtTriangles = [&]( const std::vector<uint32_t> &topIdxs, size_t bottomVertexIdxStart )
347 {
348 size_t bottomVertexIdx = bottomVertexIdxStart;
349 for ( size_t i = 1; i < topIdxs.size(); i++ )
350 {
351 uint32_t topVertex1 = topIdxs[i - 1];
352 uint32_t topVertex2 = topIdxs[i];
353 uint32_t bottomVertex1 = bottomVertexIdx;
354 uint32_t bottomVertex2 = ++bottomVertexIdx;
355
356 *skirtTriangles++ = bottomVertex1;
357 *skirtTriangles++ = topVertex1;
358 *skirtTriangles++ = topVertex2;
359
360 *skirtTriangles++ = bottomVertex2;
361 *skirtTriangles++ = bottomVertex1;
362 *skirtTriangles++ = topVertex2;
363 }
364
365 if ( topIdxs.size() > 1 )
366 {
367 uint32_t topVertex1 = bottomVertexIdxStart;
368 uint32_t topVertex2 = bottomVertexIdxStart + topIdxs.size() - 1;
369 uint32_t bottomVertex1 = bottomVertexIdxStart + topIdxs.size();
370 uint32_t bottomVertex2 = bottomVertexIdxStart + topIdxs.size() + 1;
371
372 *skirtTriangles++ = bottomVertex1;
373 *skirtTriangles++ = topVertex1;
374 *skirtTriangles++ = topVertex2;
375
376 *skirtTriangles++ = bottomVertex2;
377 *skirtTriangles++ = bottomVertex1;
378 *skirtTriangles++ = topVertex2;
379 }
380 };
381 addSkirtTriangles( mWestVertices, westBottomVerticesIdx );
382 addSkirtTriangles( mSouthVertices, southBottomVerticesIdx );
383 addSkirtTriangles( mEastVertices, eastBottomVerticesIdx );
384 addSkirtTriangles( mNorthVertices, northBottomVerticesIdx );
385 Q_ASSERT( skirtTriangles == ( uint32_t * )( triangleBuffer.data.data() + triangleBuffer.data.size() ) );
386 }
387
388 model.buffers.push_back( vertexBuffer );
389 model.buffers.push_back( triangleBuffer );
390
391 tinygltf::BufferView vertexBufferView;
392 vertexBufferView.buffer = 0;
393 vertexBufferView.byteLength = vertexBuffer.data.size();
394 vertexBufferView.target = TINYGLTF_TARGET_ARRAY_BUFFER;
395 model.bufferViews.push_back( vertexBufferView );
396
397 tinygltf::BufferView triangleBufferView;
398 triangleBufferView.buffer = 1;
399 triangleBufferView.byteLength = triangleBuffer.data.size();
400 triangleBufferView.target = TINYGLTF_TARGET_ELEMENT_ARRAY_BUFFER;
401 model.bufferViews.push_back( triangleBufferView );
402
403 tinygltf::Accessor vertexAccessor;
404 vertexAccessor.bufferView = 0;
405 vertexAccessor.componentType = TINYGLTF_COMPONENT_TYPE_FLOAT;
406 vertexAccessor.count = vertexBuffer.data.size() / sizeof( float ) / 3;
407 vertexAccessor.type = TINYGLTF_TYPE_VEC3;
408 vertexAccessor.minValues = coordMinimums;
409 vertexAccessor.maxValues = coordMaximums;
410 model.accessors.push_back( vertexAccessor );
411
412 tinygltf::Accessor triangleAccessor;
413 triangleAccessor.bufferView = 1;
414 triangleAccessor.componentType = TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT;
415 triangleAccessor.count = triangleBuffer.data.size() / sizeof( uint32_t );
416 triangleAccessor.type = TINYGLTF_TYPE_SCALAR;
417 model.accessors.push_back( triangleAccessor );
418
419 tinygltf::Mesh mesh;
420 tinygltf::Primitive primitive;
421 primitive.attributes["POSITION"] = 0;
422 primitive.indices = 1;
423 primitive.mode = TINYGLTF_MODE_TRIANGLES;
424
425 if ( mNormalCoords.size() )
426 {
427 tinygltf::Buffer normalBuffer;
428 normalBuffer.data.resize( vertexBuffer.data.size() );
429 // Copy explicit normals, leave rest zeroed
430 size_t explicitNormalsBytes = mNormalCoords.size() * sizeof( float );
431 Q_ASSERT( vertexBuffer.data.size() >= explicitNormalsBytes );
432 const char *normData = reinterpret_cast<const char *>( mNormalCoords.data() );
433 std::copy( normData, normData + explicitNormalsBytes, normalBuffer.data.begin() );
434 memset( normalBuffer.data.data() + explicitNormalsBytes, 0, normalBuffer.data.size() - explicitNormalsBytes );
435 model.buffers.push_back( normalBuffer );
436
437 tinygltf::BufferView normalBufferView;
438 normalBufferView.buffer = model.buffers.size() - 1;
439 normalBufferView.byteLength = normalBuffer.data.size();
440 normalBufferView.target = TINYGLTF_TARGET_ARRAY_BUFFER;
441 model.bufferViews.push_back( normalBufferView );
442
443 std::vector<double> normalMinimums = {1, 1, 1};
444 std::vector<double> normalMaximums = {-1, -1, -1};
445
446 for ( size_t i = 0; i < mNormalCoords.size(); i++ )
447 {
448 float coord = mNormalCoords[i];
449 if ( normalMinimums[i % 3] > coord )
450 normalMinimums[i % 3] = coord;
451 if ( normalMaximums[i % 3] < coord )
452 normalMaximums[i % 3] = coord;
453 }
454
455 tinygltf::Accessor normalAccessor;
456 normalAccessor.bufferView = model.bufferViews.size() - 1;
457 normalAccessor.componentType = TINYGLTF_COMPONENT_TYPE_FLOAT;
458 normalAccessor.count = normalBuffer.data.size() / sizeof( float ) / 3;
459 normalAccessor.type = TINYGLTF_TYPE_VEC3;
460 normalAccessor.minValues = normalMinimums;
461 normalAccessor.maxValues = normalMaximums;
462 model.accessors.push_back( normalAccessor );
463
464 primitive.attributes["NORMAL"] = model.accessors.size() - 1;
465 }
466
467 if ( withTextureCoords )
468 {
469 // Create texture coordinates matching X, Y
470
471 tinygltf::Buffer textureCoordBuffer;
472 textureCoordBuffer.data.resize( mVertexCoords.size() / 3 * 2 * sizeof( float ) );
473 std::vector<double> texCoordMinimums = {1.0, 1.0};
474 std::vector<double> texCoordMaximums = {0.0, 0.0};
475 auto textureCoordFloats = reinterpret_cast<float *>( textureCoordBuffer.data.data() );
476
477 for ( size_t i = 0; i < mVertexCoords.size() / 3; i++ )
478 {
479 double u = mVertexCoords[i * 3] / 32767.0;
480 // V coord needs to be flipped for terrain for some reason
481 double v = 1.0 - ( mVertexCoords[i * 3 + 1] / 32767.0 );
482 if ( texCoordMinimums[0] > u ) texCoordMinimums[0] = u;
483 if ( texCoordMinimums[1] > v ) texCoordMinimums[1] = v;
484 if ( texCoordMaximums[0] < u ) texCoordMaximums[0] = u;
485 if ( texCoordMaximums[1] < v ) texCoordMaximums[1] = v;
486 textureCoordFloats[i * 2] = u;
487 textureCoordFloats[i * 2 + 1] = v;
488 }
489
490 model.buffers.push_back( textureCoordBuffer );
491
492 tinygltf::BufferView textureCoordBufferView;
493 textureCoordBufferView.buffer = model.buffers.size() - 1;
494 //textureCoordBufferView.buffer = vertexBufferView.buffer; // Reuse vertex coords
495 textureCoordBufferView.byteLength = textureCoordBuffer.data.size();
496 //textureCoordBufferView.byteLength = vertexBuffer.data.size();
497 //textureCoordBufferView.byteStride = sizeof(float) * 3;
498 textureCoordBufferView.target = TINYGLTF_TARGET_ARRAY_BUFFER;
499 model.bufferViews.push_back( textureCoordBufferView );
500
501 tinygltf::Accessor textureCoordAccessor;
502 textureCoordAccessor.bufferView = model.bufferViews.size() - 1;
503 textureCoordAccessor.componentType = TINYGLTF_COMPONENT_TYPE_FLOAT;
504 textureCoordAccessor.count = vertexAccessor.count;
505 textureCoordAccessor.type = TINYGLTF_TYPE_VEC2;
506 textureCoordAccessor.minValues = { texCoordMinimums[0], texCoordMinimums[1] };
507 textureCoordAccessor.maxValues = { texCoordMaximums[0], texCoordMaximums[1] };
508 model.accessors.push_back( textureCoordAccessor );
509
510 primitive.attributes["TEXCOORD_0"] = model.accessors.size() - 1;
511 }
512
513 mesh.primitives.push_back( primitive );
514 model.meshes.push_back( mesh );
515
516 tinygltf::Node node;
517 node.mesh = 0;
518 model.nodes.push_back( node );
519
520 tinygltf::Scene scene;
521 scene.nodes.push_back( 0 );
522 model.scenes.push_back( scene );
523 model.defaultScene = 0;
524
525 return model;
526}
Custom exception class which is raised when an operation is not supported.
Exception thrown on failure to parse Quantized Mesh tile (malformed data)
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
constexpr uint8_t normalsExtensionId
std::vector< float > mNormalCoords
std::vector< uint32_t > mTriangleIndices
tinygltf::Model toGltf(bool addSkirt=false, double skirtDepth=0, bool withTextureCoords=false)
QgsQuantizedMeshTile(const QByteArray &data)
std::vector< uint32_t > mNorthVertices
std::vector< uint32_t > mWestVertices
QgsQuantizedMeshHeader mHeader
std::vector< uint32_t > mEastVertices
std::map< uint8_t, std::vector< char > > mExtensions
std::vector< uint16_t > mVertexCoords
std::vector< uint32_t > mSouthVertices