QGIS API Documentation 4.3.0-Master (35968c35f7e)
Loading...
Searching...
No Matches
qgstiledscenelayerrenderer.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgstiledscenelayerrenderer.cpp
3 --------------------
4 begin : June 2023
5 copyright : (C) 2023 by Nyall Dawson
6 email : nyall dot dawson at gmail dot com
7 ***************************************************************************/
8
9/***************************************************************************
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 ***************************************************************************/
17
18
20
21#include <memory>
22
23#include "qgsapplication.h"
24#include "qgscesiumutils.h"
25#include "qgscurve.h"
26#include "qgscurvepolygon.h"
27#include "qgsfeedback.h"
28#include "qgsgltfutils.h"
29#include "qgslogger.h"
30#include "qgsmapclippingutils.h"
31#include "qgspainting.h"
33#include "qgsrendercontext.h"
34#include "qgsruntimeprofiler.h"
35#include "qgstextrenderer.h"
36#include "qgsthreadingutils.h"
38#include "qgstiledscenelayer.h"
41#include "qgstiledscenetile.h"
42
43#include <QMatrix4x4>
44#include <QString>
45#include <qglobal.h>
46
47using namespace Qt::StringLiterals;
48
49#define TINYGLTF_NO_STB_IMAGE // we use QImage-based reading of images
50#define TINYGLTF_NO_STB_IMAGE_WRITE // we don't need writing of images
51#include "tiny_gltf.h"
52
54 : QgsMapLayerRenderer( layer->id(), &context )
55 , mLayerName( layer->name() )
56 , mFeedback( new QgsFeedback )
57 , mEnableProfile( context.flags() & Qgis::RenderContextFlag::RecordProfile )
58{
59 // We must not keep pointer to mLayer (it's dangerous) - we must copy anything we need for rendering
60 // or use some locking to prevent read/write from multiple threads
61 if ( !layer->dataProvider() || !layer->renderer() )
62 return;
63
64 QElapsedTimer timer;
65 timer.start();
66
67 mRenderer.reset( layer->renderer()->clone() );
68
69 mSceneCrs = layer->dataProvider()->sceneCrs();
70 mLayerCrs = layer->dataProvider()->crs();
71
73 mLayerBoundingVolume = layer->dataProvider()->boundingVolume();
74
75 mIndex = layer->dataProvider()->index();
76 mRenderTileBorders = mRenderer->isTileBorderRenderingEnabled();
77
78 mReadyToCompose = false;
79
80 mPreparationTime = timer.elapsed();
81}
82
84
86{
87 QgsScopedThreadName threadName( u"render:%1"_s.arg( mLayerName ) );
88
89 if ( !mIndex.isValid() )
90 return false;
91
92 std::unique_ptr< QgsScopedRuntimeProfile > profile;
93 if ( mEnableProfile )
94 {
95 profile = std::make_unique< QgsScopedRuntimeProfile >( mLayerName, u"rendering"_s, layerId() );
96 if ( mPreparationTime > 0 )
97 QgsApplication::profiler()->record( QObject::tr( "Create renderer" ), mPreparationTime / 1000.0, u"rendering"_s );
98 }
99
100 std::unique_ptr< QgsScopedRuntimeProfile > preparingProfile;
101 if ( mEnableProfile )
102 {
103 preparingProfile = std::make_unique< QgsScopedRuntimeProfile >( QObject::tr( "Preparing render" ), u"rendering"_s );
104 }
105
107 QgsTiledSceneRenderContext context( *rc, mFeedback.get() );
108
109 // Set up the render configuration options
110 QPainter *painter = rc->painter();
111
112 QgsScopedQPainterState painterState( painter );
113 rc->setPainterFlagsUsingContext( painter );
114
115 if ( !mClippingRegions.empty() )
116 {
117 bool needsPainterClipPath = false;
118 const QPainterPath path = QgsMapClippingUtils::calculatePainterClipRegion( mClippingRegions, *rc, Qgis::LayerType::VectorTile, needsPainterClipPath );
119 if ( needsPainterClipPath )
120 rc->painter()->setClipPath( path, Qt::IntersectClip );
121 }
122
123 mElapsedTimer.start();
124
125 mSceneToMapTransform = QgsCoordinateTransform( mSceneCrs, rc->coordinateTransform().destinationCrs(), rc->transformContext() );
126
127 mRenderer->startRender( context );
128
129 preparingProfile.reset();
130 std::unique_ptr< QgsScopedRuntimeProfile > renderingProfile;
131 if ( mEnableProfile )
132 {
133 renderingProfile = std::make_unique< QgsScopedRuntimeProfile >( QObject::tr( "Rendering" ), u"rendering"_s );
134 }
135
136 const bool result = renderTiles( context );
137 mRenderer->stopRender( context );
138 mReadyToCompose = true;
139
140 return result;
141}
142
144{
145 // we want to show temporary incremental renders we retrieve each tile in the scene, as this can be slow and
146 // we need to show the user that some activity is happening here.
147 // But we can't render the final layer result incrementally, as we need to collect ALL the content from the
148 // scene before we can sort it by z order and avoid random z-order stacking artifacts!
149 // So we request here a preview render image for the temporary incremental updates:
151}
152
154{
155 return mRenderer ? ( mRenderer->flags() & Qgis::TiledSceneRendererFlag::ForceRasterRender ) : false;
156}
157
158QgsTiledSceneRequest QgsTiledSceneLayerRenderer::createBaseRequest()
159{
160 const QgsRenderContext *context = renderContext();
161 const QgsRectangle mapExtent = context->mapExtent();
162
163 // calculate maximum screen error in METERS
164 const double maximumErrorPixels = context->convertToPainterUnits( mRenderer->maximumScreenError(), mRenderer->maximumScreenErrorUnit() );
165 // calculate width in meters across one pixel in the middle of the map
166 const double mapYCenter = 0.5 * ( mapExtent.yMinimum() + mapExtent.yMaximum() );
167 const double mapXCenter = 0.5 * ( mapExtent.xMinimum() + mapExtent.xMaximum() );
168 const double onePixelDistanceX = ( mapExtent.xMaximum() - mapExtent.xMinimum() ) / context->outputSize().width();
169 double mapMetersPerPixel = 0;
170 try
171 {
172 mapMetersPerPixel = context->distanceArea().measureLine( QgsPointXY( mapXCenter, mapYCenter ), QgsPointXY( mapXCenter + onePixelDistanceX, mapYCenter ) );
173 }
174 catch ( QgsCsException & )
175 {
176 // TODO report errors to user
177 QgsDebugError( u"An error occurred while calculating length"_s );
178 }
179
180 const double maximumErrorInMeters = maximumErrorPixels * mapMetersPerPixel;
181
182 QgsTiledSceneRequest request;
183 request.setFeedback( feedback() );
184
185 // TODO what z range makes sense here??
186 const QVector< QgsVector3D > corners = QgsBox3D( mapExtent, -10000, 10000 ).corners();
187 QVector< double > x;
188 x.reserve( 8 );
189 QVector< double > y;
190 y.reserve( 8 );
191 QVector< double > z;
192 z.reserve( 8 );
193 for ( int i = 0; i < 8; ++i )
194 {
195 const QgsVector3D &corner = corners[i];
196 x.append( corner.x() );
197 y.append( corner.y() );
198 z.append( corner.z() );
199 }
200 mSceneToMapTransform.transformInPlace( x, y, z, Qgis::TransformDirection::Reverse );
201
202 const auto minMaxX = std::minmax_element( x.constBegin(), x.constEnd() );
203 const auto minMaxY = std::minmax_element( y.constBegin(), y.constEnd() );
204 const auto minMaxZ = std::minmax_element( z.constBegin(), z.constEnd() );
205 request.setFilterBox( QgsOrientedBox3D::fromBox3D( QgsBox3D( *minMaxX.first, *minMaxY.first, *minMaxZ.first, *minMaxX.second, *minMaxY.second, *minMaxZ.second ) ) );
206
207 request.setRequiredGeometricError( maximumErrorInMeters );
208
209 return request;
210}
211
212bool QgsTiledSceneLayerRenderer::renderTiles( QgsTiledSceneRenderContext &context )
213{
214 const QgsRectangle mapExtent = context.renderContext().mapExtent();
215 auto tileIsVisibleInMap = [mapExtent, this]( const QgsTiledSceneTile &tile ) -> bool {
216 // the trip from map CRS to scene CRS will have expanded out the bounding volumes for the tile request, so
217 // we want to cull any tiles which we've been given which don't actually intersect our visible map extent
218 // when we transform them back into the destination map CRS.
219 // This potentially saves us requesting data for tiles which aren't actually visible in the map.
220 const QgsGeometry tileGeometry( tile.boundingVolume().as2DGeometry( mSceneToMapTransform ) );
221 return tileGeometry.intersects( mapExtent );
222 };
223
224 QgsTiledSceneRequest request = createBaseRequest();
225 QVector< long long > tileIds = mIndex.getTiles( request );
226 while ( !tileIds.empty() )
227 {
228 if ( feedback() && feedback()->isCanceled() )
229 return false;
230
231 const long long tileId = tileIds.first();
232 tileIds.pop_front();
233
234 const QgsTiledSceneTile tile = mIndex.getTile( tileId );
235 if ( !tile.isValid() || !tileIsVisibleInMap( tile ) )
236 continue;
237
238 switch ( mIndex.childAvailability( tileId ) )
239 {
242 {
243 renderTile( tile, context );
244 break;
245 }
246
248 {
249 if ( mIndex.fetchHierarchy( tileId, feedback() ) )
250 {
251 request.setParentTileId( tileId );
252 const QVector< long long > newTileIdsToRender = mIndex.getTiles( request );
253 tileIds.append( newTileIdsToRender );
254
255 // do we still need to render the parent? Depends on the parent's refinement process...
256 const QgsTiledSceneTile tile = mIndex.getTile( tileId );
257 if ( tile.isValid() )
258 {
259 switch ( tile.refinementProcess() )
260 {
262 break;
264 renderTile( tile, context );
265 break;
266 }
267 }
268 }
269 break;
270 }
271 }
272 }
273 if ( feedback() && feedback()->isCanceled() )
274 return false;
275
276 const bool needsTextures = mRenderer->flags() & Qgis::TiledSceneRendererFlag::RequiresTextures;
277
278 std::sort( mPrimitiveData.begin(), mPrimitiveData.end(), []( const PrimitiveData &a, const PrimitiveData &b ) {
279 // this isn't an exact science ;)
280 if ( qgsDoubleNear( a.z, b.z, 0.001 ) )
281 {
282 // for overlapping lines/triangles, ensure the line is drawn over the triangle
283 if ( a.type == PrimitiveType::Line )
284 return false;
285 else if ( b.type == PrimitiveType::Line )
286 return true;
287 }
288 return a.z < b.z;
289 } );
290 for ( const PrimitiveData &data : std::as_const( mPrimitiveData ) )
291 {
292 switch ( data.type )
293 {
294 case PrimitiveType::Line:
295 mRenderer->renderLine( context, data.coordinates );
296 break;
297
298 case PrimitiveType::Triangle:
299 if ( needsTextures )
300 {
301 context.setTextureImage( mTextures.value( data.textureId ) );
302 context.setTextureCoordinates( data.textureCoords[0], data.textureCoords[1], data.textureCoords[2], data.textureCoords[3], data.textureCoords[4], data.textureCoords[5] );
303 }
304 mRenderer->renderTriangle( context, data.coordinates );
305 break;
306 }
307 }
308
309 if ( mRenderTileBorders )
310 {
311 QPainter *painter = renderContext()->painter();
312 for ( const TileDetails &tile : std::as_const( mTileDetails ) )
313 {
314 QPen pen;
315 QBrush brush;
316 if ( tile.hasContent )
317 {
318 brush = QBrush( QColor( 0, 0, 255, 10 ) );
319 pen = QPen( QColor( 0, 0, 255, 150 ) );
320 }
321 else
322 {
323 brush = QBrush( QColor( 255, 0, 255, 10 ) );
324 pen = QPen( QColor( 255, 0, 255, 150 ) );
325 }
326 pen.setWidth( 2 );
327 painter->setPen( pen );
328 painter->setBrush( brush );
329 painter->drawPolygon( tile.boundary );
330#if 1
331 QgsTextFormat format;
332 format.setColor( QColor( 255, 0, 0 ) );
333 format.buffer().setEnabled( true );
334
336 drawText( QRectF( QPoint( 0, 0 ), renderContext()->outputSize() ).intersected( tile.boundary.boundingRect() ), 0, Qgis::TextHorizontalAlignment::Center, { tile.id }, *renderContext(), format, true, Qgis::TextVerticalAlignment::VerticalCenter );
337#endif
338 }
339 }
340
341 return true;
342}
343
344void QgsTiledSceneLayerRenderer::renderTile( const QgsTiledSceneTile &tile, QgsTiledSceneRenderContext &context )
345{
346 const bool hasContent = renderTileContent( tile, context );
347
348 if ( mRenderTileBorders )
349 {
350 const QgsTiledSceneBoundingVolume &volume = tile.boundingVolume();
351 try
352 {
353 std::unique_ptr< QgsAbstractGeometry > volumeGeometry( volume.as2DGeometry( mSceneToMapTransform ) );
354 if ( QgsCurvePolygon *polygon = qgsgeometry_cast< QgsCurvePolygon * >( volumeGeometry.get() ) )
355 {
356 QPolygonF volumePolygon = polygon->exteriorRing()->asQPolygonF();
357
358 // remove non-finite points, e.g. infinite or NaN points caused by reprojecting errors
359 volumePolygon
360 .erase( std::remove_if( volumePolygon.begin(), volumePolygon.end(), []( const QPointF point ) { return !std::isfinite( point.x() ) || !std::isfinite( point.y() ); } ), volumePolygon.end() );
361
362 QPointF *ptr = volumePolygon.data();
363 for ( int i = 0; i < volumePolygon.size(); ++i, ++ptr )
364 {
365 renderContext()->mapToPixel().transformInPlace( ptr->rx(), ptr->ry() );
366 }
367
368 TileDetails details;
369 details.boundary = volumePolygon;
370 details.hasContent = hasContent;
371 details.id = QString::number( tile.id() );
372 mTileDetails.append( details );
373 }
374 }
375 catch ( QgsCsException & )
376 {
377 QgsDebugError( u"Error transforming bounding volume"_s );
378 }
379 }
380}
381
382bool QgsTiledSceneLayerRenderer::renderTileContent( const QgsTiledSceneTile &tile, QgsTiledSceneRenderContext &context )
383{
384 const QString contentUri = tile.resources().value( u"content"_s ).toString();
385 if ( contentUri.isEmpty() )
386 return false;
387
388 const QByteArray tileContent = mIndex.retrieveContent( contentUri, feedback() );
389 // When the operation is canceled, retrieveContent() will silently return an empty array
390 if ( feedback()->isCanceled() )
391 return false;
392
393 mCurrentModelId++;
394 // TODO: Somehow de-hardcode this switch?
395 const auto &format = tile.metadata().value( u"contentFormat"_s ).value<QString>();
396 if ( format == "quantizedmesh"_L1 )
397 {
398 try
399 {
400 QgsQuantizedMeshTile qmTile( tileContent );
401 qmTile.removeDegenerateTriangles();
402 tinygltf::Model model = qmTile.toGltf();
403 renderModel( model, QgsVector3D(), std::nullopt, tile, context );
404 return true;
405 }
406 catch ( QgsQuantizedMeshParsingException &ex )
407 {
408 QgsDebugError( u"Failed to parse tile from '%1'"_s.arg( contentUri ) );
409 return false;
410 }
411 }
412 else if ( format == "cesiumtiles"_L1 )
413 {
414 const QVector<QgsCesiumUtils::TileContents> contents = QgsCesiumUtils::extractTileContent( tileContent, contentUri );
415 if ( contents.isEmpty() )
416 {
417 return false;
418 }
419
420 for ( int i = 0; i < contents.size(); ++i )
421 {
422 const QgsCesiumUtils::TileContents &content = contents[i];
423 if ( content.gltf.isEmpty() )
424 continue;
425
426 tinygltf::Model innerModel;
427 QString gltfErrors;
428 QString gltfWarnings;
429 const bool res = QgsGltfUtils::loadGltfModel( content.gltf, innerModel, &gltfErrors, &gltfWarnings );
430 if ( !gltfErrors.isEmpty() )
431 {
432 if ( !mErrors.contains( gltfErrors ) )
433 mErrors.append( gltfErrors );
434 QgsDebugError( u"Error raised reading %1: %2"_s.arg( contentUri, gltfErrors ) );
435 }
436 if ( !gltfWarnings.isEmpty() )
437 {
438 QgsDebugError( u"Warnings raised reading %1: %2"_s.arg( contentUri, gltfWarnings ) );
439 }
440 if ( !res )
441 {
442 QgsDebugMsgLevel( u"renderTileContent: failed to load glTF model for entry %1"_s.arg( i + 1 ), 2 );
443 continue;
444 }
445
446 renderModel( innerModel, content.rtcCenter, content.instancing, tile, context );
447 mCurrentModelId++;
448 }
449 return true;
450 }
451 else if ( format == "draco"_L1 )
452 {
453 QgsGltfUtils::I3SNodeContext i3sContext;
454 i3sContext.initFromTile( tile, mLayerCrs, mSceneCrs, context.renderContext().transformContext() );
455
456 tinygltf::Model model;
457 QString errors;
458 if ( !QgsGltfUtils::loadDracoModel( tileContent, i3sContext, model, &errors ) )
459 {
460 if ( !mErrors.contains( errors ) )
461 mErrors.append( errors );
462 QgsDebugError( u"Error raised reading %1: %2"_s.arg( contentUri, errors ) );
463 return false;
464 }
465
466 renderModel( model, QgsVector3D(), std::nullopt, tile, context );
467 return true;
468 }
469
470 return false;
471}
472
473
474void QgsTiledSceneLayerRenderer::renderModel(
475 tinygltf::Model &model, const QgsVector3D &centerOffset, const std::optional<QgsCesiumUtils::TileI3dmData> &tileInstancing, const QgsTiledSceneTile &tile, QgsTiledSceneRenderContext &context
476)
477{
478 const QString contentUri = tile.resources().value( u"content"_s ).toString();
479 const Qgis::Axis gltfUpAxis = static_cast<Qgis::Axis>( tile.metadata().value( u"gltfUpAxis"_s, static_cast<int>( Qgis::Axis::Y ) ).toInt() );
480
481 const QgsVector3D tileTranslationEcef = centerOffset + QgsGltfUtils::extractTileTranslation( model, gltfUpAxis );
482
483 // Try to resolve instancing (i3dm or EXT_mesh_gpu_instancing)
484 const QgsMatrix4x4 tileTransform = tile.transform() ? *tile.transform() : QgsMatrix4x4();
485 const QVector<QgsGltfUtils::InstancedPrimitive> instancedPrimitives = QgsCesiumUtils::resolveInstancing( model, tileInstancing, gltfUpAxis, tileTransform, centerOffset );
486 bool wholeTileUsesInstancing = tileInstancing.has_value(); // when using i3dm tile from 3D Tiles 1.0
487
488 bool sceneOk = false;
489 const std::size_t sceneIndex = QgsGltfUtils::sourceSceneForModel( model, sceneOk );
490 if ( !sceneOk )
491 {
492 const QString error = QObject::tr( "No scenes found in model" );
493 mErrors.append( error );
494 QgsDebugError( u"Error raised reading %1: %2"_s.arg( contentUri, error ) );
495 return;
496 }
497
498 // Render non-instanced primitives (the most common rendering path):
499 // - 3D Tiles 1.0: b3dm tiles
500 // - 3D Tiles 1.1: all gltf nodes that don't use instancing (via EXT_mesh_gpu_instancing extension)
501 // - I3S: 3DObject or IntegratedMesh layer types
502 if ( !wholeTileUsesInstancing )
503 {
504 const tinygltf::Scene &scene = model.scenes[sceneIndex];
505
506 std::function< void( int nodeIndex, const QMatrix4x4 &transform ) > traverseNode;
507 traverseNode = [&model, &context, &tileTranslationEcef, &tile, &contentUri, &gltfUpAxis, &traverseNode, this]( int nodeIndex, const QMatrix4x4 &parentTransform ) {
508 const tinygltf::Node &gltfNode = model.nodes[nodeIndex];
509 std::unique_ptr< QMatrix4x4 > gltfLocalTransform = QgsGltfUtils::parseNodeTransform( gltfNode );
510
511 if ( !parentTransform.isIdentity() )
512 {
513 if ( gltfLocalTransform )
514 *gltfLocalTransform = parentTransform * *gltfLocalTransform;
515 else
516 {
517 gltfLocalTransform = std::make_unique<QMatrix4x4>( parentTransform );
518 }
519 }
520
521 if ( gltfNode.mesh >= 0 )
522 {
523 // Skip nodes with EXT_mesh_gpu_instancing — already handled
524 if ( gltfNode.extensions.find( "EXT_mesh_gpu_instancing" ) == gltfNode.extensions.end() )
525 {
526 const tinygltf::Mesh &mesh = model.meshes[gltfNode.mesh];
527
528 for ( const tinygltf::Primitive &primitive : mesh.primitives )
529 {
530 if ( context.renderContext().renderingStopped() )
531 break;
532
533 renderPrimitive( model, primitive, tile, tileTranslationEcef, gltfLocalTransform.get(), gltfUpAxis, contentUri, context );
534 }
535 }
536 }
537
538 for ( int childNode : gltfNode.children )
539 {
540 traverseNode( childNode, gltfLocalTransform ? *gltfLocalTransform : QMatrix4x4() );
541 }
542 };
543
544 for ( int nodeIndex : scene.nodes )
545 {
546 traverseNode( nodeIndex, QMatrix4x4() );
547 }
548 }
549
550 // Render instanced primitives (if any):
551 // - 3D Tiles 1.0: i3dm tiles
552 // - 3D Tiles 1.1: all gltf nodes that use instancing
553 // - I3S: not supported yet (the "Point" layer type)
554 for ( const QgsGltfUtils::InstancedPrimitive &entry : instancedPrimitives )
555 {
556 if ( entry.meshIndex < 0 || entry.meshIndex >= static_cast<int>( model.meshes.size() ) )
557 continue;
558 const tinygltf::Mesh &mesh = model.meshes[entry.meshIndex];
559 if ( entry.primitiveIndex < 0 || entry.primitiveIndex >= static_cast<int>( mesh.primitives.size() ) )
560 continue;
561 const tinygltf::Primitive &primitive = mesh.primitives[entry.primitiveIndex];
562
563 for ( const QMatrix4x4 &instanceMatrix : entry.instanceTransforms )
564 {
565 if ( context.renderContext().renderingStopped() )
566 break;
567
568 // The instance matrices already include the axis flip and node transform,
569 // so we use gltfUpAxis=Z (no additional flip) and no node transform.
570 renderPrimitive( model, primitive, tile, tileTranslationEcef, &instanceMatrix, Qgis::Axis::Z, contentUri, context );
571 }
572 }
573}
574
575void QgsTiledSceneLayerRenderer::renderPrimitive(
576 const tinygltf::Model &model,
577 const tinygltf::Primitive &primitive,
578 const QgsTiledSceneTile &tile,
579 const QgsVector3D &tileTranslationEcef,
580 const QMatrix4x4 *gltfLocalTransform,
581 Qgis::Axis gltfUpAxis,
582 const QString &contentUri,
584)
585{
586 switch ( primitive.mode )
587 {
588 case TINYGLTF_MODE_TRIANGLES:
589 if ( mRenderer->flags() & Qgis::TiledSceneRendererFlag::RendersTriangles )
590 renderTrianglePrimitive( model, primitive, tile, tileTranslationEcef, gltfLocalTransform, gltfUpAxis, contentUri, context );
591 break;
592
593 case TINYGLTF_MODE_LINE:
594 if ( mRenderer->flags() & Qgis::TiledSceneRendererFlag::RendersLines )
595 renderLinePrimitive( model, primitive, tile, tileTranslationEcef, gltfLocalTransform, gltfUpAxis, contentUri, context );
596 return;
597
598 case TINYGLTF_MODE_POINTS:
599 if ( !mWarnedPrimitiveTypes.contains( TINYGLTF_MODE_POINTS ) )
600 {
601 mErrors << QObject::tr( "Point objects in tiled scenes are not supported" );
602 mWarnedPrimitiveTypes.insert( TINYGLTF_MODE_POINTS );
603 }
604 return;
605
606 case TINYGLTF_MODE_LINE_LOOP:
607 if ( !mWarnedPrimitiveTypes.contains( TINYGLTF_MODE_LINE_LOOP ) )
608 {
609 mErrors << QObject::tr( "Line loops in tiled scenes are not supported" );
610 mWarnedPrimitiveTypes.insert( TINYGLTF_MODE_LINE_LOOP );
611 }
612 return;
613
614 case TINYGLTF_MODE_LINE_STRIP:
615 if ( !mWarnedPrimitiveTypes.contains( TINYGLTF_MODE_LINE_STRIP ) )
616 {
617 mErrors << QObject::tr( "Line strips in tiled scenes are not supported" );
618 mWarnedPrimitiveTypes.insert( TINYGLTF_MODE_LINE_STRIP );
619 }
620 return;
621
622 case TINYGLTF_MODE_TRIANGLE_STRIP:
623 if ( !mWarnedPrimitiveTypes.contains( TINYGLTF_MODE_TRIANGLE_STRIP ) )
624 {
625 mErrors << QObject::tr( "Triangular strips in tiled scenes are not supported" );
626 mWarnedPrimitiveTypes.insert( TINYGLTF_MODE_TRIANGLE_STRIP );
627 }
628 return;
629
630 case TINYGLTF_MODE_TRIANGLE_FAN:
631 if ( !mWarnedPrimitiveTypes.contains( TINYGLTF_MODE_TRIANGLE_FAN ) )
632 {
633 mErrors << QObject::tr( "Triangular fans in tiled scenes are not supported" );
634 mWarnedPrimitiveTypes.insert( TINYGLTF_MODE_TRIANGLE_FAN );
635 }
636 return;
637
638 default:
639 if ( !mWarnedPrimitiveTypes.contains( primitive.mode ) )
640 {
641 mErrors << QObject::tr( "Primitive type %1 in tiled scenes are not supported" ).arg( primitive.mode );
642 mWarnedPrimitiveTypes.insert( primitive.mode );
643 }
644 return;
645 }
646}
647
648void QgsTiledSceneLayerRenderer::renderTrianglePrimitive(
649 const tinygltf::Model &model,
650 const tinygltf::Primitive &primitive,
651 const QgsTiledSceneTile &tile,
652 const QgsVector3D &tileTranslationEcef,
653 const QMatrix4x4 *gltfLocalTransform,
654 Qgis::Axis gltfUpAxis,
655 const QString &contentUri,
657)
658{
659 auto posIt = primitive.attributes.find( "POSITION" );
660 if ( posIt == primitive.attributes.end() )
661 {
662 mErrors << QObject::tr( "Could not find POSITION attribute for primitive" );
663 return;
664 }
665 int positionAccessorIndex = posIt->second;
666
667 QVector< double > x;
668 QVector< double > y;
669 QVector< double > z;
670 QgsGltfUtils::
671 accessorToMapCoordinates( model, positionAccessorIndex, tile.transform() ? *tile.transform() : QgsMatrix4x4(), &mSceneToMapTransform, tileTranslationEcef, gltfLocalTransform, gltfUpAxis, x, y, z );
672
674
675 const bool needsTextures = mRenderer->flags() & Qgis::TiledSceneRendererFlag::RequiresTextures;
676
677 QImage textureImage;
678 QVector< float > texturePointX;
679 QVector< float > texturePointY;
680 QPair< int, int > textureId { -1, -1 };
681 if ( needsTextures && primitive.material != -1 )
682 {
683 const tinygltf::Material &material = model.materials[primitive.material];
684 const tinygltf::PbrMetallicRoughness &pbr = material.pbrMetallicRoughness;
685
686 if ( pbr.baseColorTexture.index >= 0 && static_cast< int >( model.textures.size() ) > pbr.baseColorTexture.index )
687 {
688 const tinygltf::Texture &tex = model.textures[pbr.baseColorTexture.index];
689
690 // Source can be undefined if texture is provided by an extension
691 if ( tex.source >= 0 )
692 {
693 switch ( QgsGltfUtils::imageResourceType( model, tex.source ) )
694 {
695 case QgsGltfUtils::ResourceType::Embedded:
696 textureImage = QgsGltfUtils::extractEmbeddedImage( model, tex.source );
697 break;
698
699 case QgsGltfUtils::ResourceType::Linked:
700 {
701 const QString linkedPath = QgsGltfUtils::linkedImagePath( model, tex.source );
702 const QString textureUri = QUrl( contentUri ).resolved( linkedPath ).toString();
703 const QByteArray rep = mIndex.retrieveContent( textureUri, feedback() );
704 if ( !rep.isEmpty() )
705 {
706 textureImage = QImage::fromData( rep );
707 }
708 break;
709 }
710 }
711 }
712
713 if ( !textureImage.isNull() )
714 {
715 auto texIt = primitive.attributes.find( "TEXCOORD_0" );
716 if ( texIt != primitive.attributes.end() )
717 {
718 QgsGltfUtils::extractTextureCoordinates( model, texIt->second, texturePointX, texturePointY );
719 }
720
721 textureId = qMakePair( mCurrentModelId, pbr.baseColorTexture.index );
722 }
723 }
724 else if ( qgsDoubleNear( pbr.baseColorFactor[3], 0 ) )
725 {
726 // transparent primitive, skip
727 return;
728 }
729 }
730
731 const QRect outputRect = QRect( QPoint( 0, 0 ), context.renderContext().outputSize() );
732 auto needTriangle = [&outputRect]( const QPolygonF &triangle ) -> bool { return triangle.boundingRect().intersects( outputRect ); };
733
734 const bool useTexture = !textureImage.isNull();
735 bool hasStoredTexture = false;
736
737 QVector< PrimitiveData > thisTileTriangleData;
738
739 if ( primitive.indices == -1 )
740 {
741 Q_ASSERT( x.size() % 3 == 0 );
742
743 thisTileTriangleData.reserve( x.size() );
744 for ( int i = 0; i < x.size(); i += 3 )
745 {
746 if ( context.renderContext().renderingStopped() )
747 break;
748
749 PrimitiveData data;
750 data.type = PrimitiveType::Triangle;
751 data.textureId = textureId;
752 if ( useTexture )
753 {
754 data.textureCoords[0] = texturePointX[i];
755 data.textureCoords[1] = texturePointY[i];
756 data.textureCoords[2] = texturePointX[i + 1];
757 data.textureCoords[3] = texturePointY[i + 1];
758 data.textureCoords[4] = texturePointX[i + 2];
759 data.textureCoords[5] = texturePointY[i + 2];
760 }
761 data.coordinates = QVector<QPointF> { QPointF( x[i], y[i] ), QPointF( x[i + 1], y[i + 1] ), QPointF( x[i + 2], y[i + 2] ), QPointF( x[i], y[i] ) };
762 data.z = ( z[i] + z[i + 1] + z[i + 2] ) / 3;
763 if ( needTriangle( data.coordinates ) )
764 {
765 thisTileTriangleData.push_back( data );
766 if ( !hasStoredTexture && !textureImage.isNull() )
767 {
768 // have to make an explicit .copy() here, as we don't necessarily own the image data
769 mTextures.insert( textureId, textureImage.copy() );
770 hasStoredTexture = true;
771 }
772 }
773 }
774 }
775 else
776 {
777 const tinygltf::Accessor &primitiveAccessor = model.accessors[primitive.indices];
778 const tinygltf::BufferView &bvPrimitive = model.bufferViews[primitiveAccessor.bufferView];
779 const tinygltf::Buffer &bPrimitive = model.buffers[bvPrimitive.buffer];
780
781 Q_ASSERT(
782 ( primitiveAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT
783 || primitiveAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT
784 || primitiveAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE )
785 && primitiveAccessor.type == TINYGLTF_TYPE_SCALAR
786 );
787
788 const char *primitivePtr = reinterpret_cast< const char * >( bPrimitive.data.data() ) + bvPrimitive.byteOffset + primitiveAccessor.byteOffset;
789
790 thisTileTriangleData.reserve( primitiveAccessor.count / 3 );
791 for ( std::size_t i = 0; i < primitiveAccessor.count / 3; i++ )
792 {
793 if ( context.renderContext().renderingStopped() )
794 break;
795
796 unsigned int index1 = 0;
797 unsigned int index2 = 0;
798 unsigned int index3 = 0;
799
800 PrimitiveData data;
801 data.type = PrimitiveType::Triangle;
802 data.textureId = textureId;
803
804 if ( primitiveAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT )
805 {
806 const unsigned short *usPtrPrimitive = reinterpret_cast< const unsigned short * >( primitivePtr );
807 if ( bvPrimitive.byteStride )
808 primitivePtr += bvPrimitive.byteStride;
809 else
810 primitivePtr += 3 * sizeof( unsigned short );
811
812 index1 = usPtrPrimitive[0];
813 index2 = usPtrPrimitive[1];
814 index3 = usPtrPrimitive[2];
815 }
816 else if ( primitiveAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE )
817 {
818 const unsigned char *usPtrPrimitive = reinterpret_cast< const unsigned char * >( primitivePtr );
819 if ( bvPrimitive.byteStride )
820 primitivePtr += bvPrimitive.byteStride;
821 else
822 primitivePtr += 3 * sizeof( unsigned char );
823
824 index1 = usPtrPrimitive[0];
825 index2 = usPtrPrimitive[1];
826 index3 = usPtrPrimitive[2];
827 }
828 else
829 {
830 const unsigned int *uintPtrPrimitive = reinterpret_cast< const unsigned int * >( primitivePtr );
831 if ( bvPrimitive.byteStride )
832 primitivePtr += bvPrimitive.byteStride;
833 else
834 primitivePtr += 3 * sizeof( unsigned int );
835
836 index1 = uintPtrPrimitive[0];
837 index2 = uintPtrPrimitive[1];
838 index3 = uintPtrPrimitive[2];
839 }
840
841 if ( useTexture )
842 {
843 data.textureCoords[0] = texturePointX[index1];
844 data.textureCoords[1] = texturePointY[index1];
845 data.textureCoords[2] = texturePointX[index2];
846 data.textureCoords[3] = texturePointY[index2];
847 data.textureCoords[4] = texturePointX[index3];
848 data.textureCoords[5] = texturePointY[index3];
849 }
850
851 data.coordinates = { QVector<QPointF> { QPointF( x[index1], y[index1] ), QPointF( x[index2], y[index2] ), QPointF( x[index3], y[index3] ), QPointF( x[index1], y[index1] ) } };
852 data.z = ( z[index1] + z[index2] + z[index3] ) / 3;
853 if ( needTriangle( data.coordinates ) )
854 {
855 thisTileTriangleData.push_back( data );
856 if ( !hasStoredTexture && !textureImage.isNull() )
857 {
858 // have to make an explicit .copy() here, as we don't necessarily own the image data
859 mTextures.insert( textureId, textureImage.copy() );
860 hasStoredTexture = true;
861 }
862 }
863 }
864 }
865
866 if ( context.renderContext().previewRenderPainter() )
867 {
868 // swap out the destination painter for the preview render painter, and render
869 // the triangles from this tile in a sorted order
870 QPainter *finalPainter = context.renderContext().painter();
872
873 std::sort( thisTileTriangleData.begin(), thisTileTriangleData.end(), []( const PrimitiveData &a, const PrimitiveData &b ) { return a.z < b.z; } );
874
875 for ( const PrimitiveData &data : std::as_const( thisTileTriangleData ) )
876 {
877 if ( useTexture && data.textureId.first >= 0 )
878 {
879 context.setTextureImage( mTextures.value( data.textureId ) );
880 context.setTextureCoordinates( data.textureCoords[0], data.textureCoords[1], data.textureCoords[2], data.textureCoords[3], data.textureCoords[4], data.textureCoords[5] );
881 }
882 mRenderer->renderTriangle( context, data.coordinates );
883 }
884 context.renderContext().setPainter( finalPainter );
885 }
886
887 mPrimitiveData.append( thisTileTriangleData );
888
889 // as soon as first tile is rendered, we can start showing layer updates. But we still delay
890 // this by e.g. 3 seconds before we start forcing progressive updates, so that we don't show the unsorted
891 // z triangle render if the overall layer render only takes a second or so.
892 if ( mElapsedTimer.elapsed() > MAX_TIME_TO_USE_CACHED_PREVIEW_IMAGE )
893 {
894 mReadyToCompose = true;
895 }
896}
897
898void QgsTiledSceneLayerRenderer::renderLinePrimitive(
899 const tinygltf::Model &model,
900 const tinygltf::Primitive &primitive,
901 const QgsTiledSceneTile &tile,
902 const QgsVector3D &tileTranslationEcef,
903 const QMatrix4x4 *gltfLocalTransform,
904 Qgis::Axis gltfUpAxis,
905 const QString &,
907)
908{
909 auto posIt = primitive.attributes.find( "POSITION" );
910 if ( posIt == primitive.attributes.end() )
911 {
912 mErrors << QObject::tr( "Could not find POSITION attribute for primitive" );
913 return;
914 }
915 int positionAccessorIndex = posIt->second;
916
917 QVector< double > x;
918 QVector< double > y;
919 QVector< double > z;
920 QgsGltfUtils::
921 accessorToMapCoordinates( model, positionAccessorIndex, tile.transform() ? *tile.transform() : QgsMatrix4x4(), &mSceneToMapTransform, tileTranslationEcef, gltfLocalTransform, gltfUpAxis, x, y, z );
922
924
925 const QRect outputRect = QRect( QPoint( 0, 0 ), context.renderContext().outputSize() );
926 auto needLine = [&outputRect]( const QPolygonF &line ) -> bool { return line.boundingRect().intersects( outputRect ); };
927
928 QVector< PrimitiveData > thisTileLineData;
929
930 if ( primitive.indices == -1 )
931 {
932 Q_ASSERT( x.size() % 2 == 0 );
933
934 thisTileLineData.reserve( x.size() );
935 for ( int i = 0; i < x.size(); i += 2 )
936 {
937 if ( context.renderContext().renderingStopped() )
938 break;
939
940 PrimitiveData data;
941 data.type = PrimitiveType::Line;
942 data.coordinates = QVector<QPointF> { QPointF( x[i], y[i] ), QPointF( x[i + 1], y[i + 1] ) };
943 // note -- we take the maximum z here, as we'd ideally like lines to be placed over similarish z valued triangles
944 data.z = std::max( z[i], z[i + 1] );
945 if ( needLine( data.coordinates ) )
946 {
947 thisTileLineData.push_back( data );
948 }
949 }
950 }
951 else
952 {
953 const tinygltf::Accessor &primitiveAccessor = model.accessors[primitive.indices];
954 const tinygltf::BufferView &bvPrimitive = model.bufferViews[primitiveAccessor.bufferView];
955 const tinygltf::Buffer &bPrimitive = model.buffers[bvPrimitive.buffer];
956
957 Q_ASSERT(
958 ( primitiveAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT
959 || primitiveAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT
960 || primitiveAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE )
961 && primitiveAccessor.type == TINYGLTF_TYPE_SCALAR
962 );
963
964 const char *primitivePtr = reinterpret_cast< const char * >( bPrimitive.data.data() ) + bvPrimitive.byteOffset + primitiveAccessor.byteOffset;
965
966 thisTileLineData.reserve( primitiveAccessor.count / 2 );
967 for ( std::size_t i = 0; i < primitiveAccessor.count / 2; i++ )
968 {
969 if ( context.renderContext().renderingStopped() )
970 break;
971
972 unsigned int index1 = 0;
973 unsigned int index2 = 0;
974
975 PrimitiveData data;
976 data.type = PrimitiveType::Line;
977
978 if ( primitiveAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT )
979 {
980 const unsigned short *usPtrPrimitive = reinterpret_cast< const unsigned short * >( primitivePtr );
981 if ( bvPrimitive.byteStride )
982 primitivePtr += bvPrimitive.byteStride;
983 else
984 primitivePtr += 2 * sizeof( unsigned short );
985
986 index1 = usPtrPrimitive[0];
987 index2 = usPtrPrimitive[1];
988 }
989 else if ( primitiveAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE )
990 {
991 const unsigned char *usPtrPrimitive = reinterpret_cast< const unsigned char * >( primitivePtr );
992 if ( bvPrimitive.byteStride )
993 primitivePtr += bvPrimitive.byteStride;
994 else
995 primitivePtr += 2 * sizeof( unsigned char );
996
997 index1 = usPtrPrimitive[0];
998 index2 = usPtrPrimitive[1];
999 }
1000 else
1001 {
1002 const unsigned int *uintPtrPrimitive = reinterpret_cast< const unsigned int * >( primitivePtr );
1003 if ( bvPrimitive.byteStride )
1004 primitivePtr += bvPrimitive.byteStride;
1005 else
1006 primitivePtr += 2 * sizeof( unsigned int );
1007
1008 index1 = uintPtrPrimitive[0];
1009 index2 = uintPtrPrimitive[1];
1010 }
1011
1012 data.coordinates = { QVector<QPointF> { QPointF( x[index1], y[index1] ), QPointF( x[index2], y[index2] ) } };
1013 // note -- we take the maximum z here, as we'd ideally like lines to be placed over similarish z valued triangles
1014 data.z = std::max( z[index1], z[index2] );
1015 if ( needLine( data.coordinates ) )
1016 {
1017 thisTileLineData.push_back( data );
1018 }
1019 }
1020 }
1021
1022 if ( context.renderContext().previewRenderPainter() )
1023 {
1024 // swap out the destination painter for the preview render painter, and render
1025 // the triangles from this tile in a sorted order
1026 QPainter *finalPainter = context.renderContext().painter();
1028
1029 std::sort( thisTileLineData.begin(), thisTileLineData.end(), []( const PrimitiveData &a, const PrimitiveData &b ) { return a.z < b.z; } );
1030
1031 for ( const PrimitiveData &data : std::as_const( thisTileLineData ) )
1032 {
1033 mRenderer->renderLine( context, data.coordinates );
1034 }
1035 context.renderContext().setPainter( finalPainter );
1036 }
1037
1038 mPrimitiveData.append( thisTileLineData );
1039
1040 // as soon as first tile is rendered, we can start showing layer updates. But we still delay
1041 // this by e.g. 3 seconds before we start forcing progressive updates, so that we don't show the unsorted
1042 // z primitive render if the overall layer render only takes a second or so.
1043 if ( mElapsedTimer.elapsed() > MAX_TIME_TO_USE_CACHED_PREVIEW_IMAGE )
1044 {
1045 mReadyToCompose = true;
1046 }
1047}
Provides global constants and enumerations for use throughout the application.
Definition qgis.h:62
QFlags< MapLayerRendererFlag > MapLayerRendererFlags
Flags which control how map layer renderers behave.
Definition qgis.h:3013
@ RendersLines
Renderer can render line primitives.
Definition qgis.h:6524
@ RequiresTextures
Renderer requires textures.
Definition qgis.h:6521
@ ForceRasterRender
Layer should always be rendered as a raster image.
Definition qgis.h:6522
@ RendersTriangles
Renderer can render triangle primitives.
Definition qgis.h:6523
@ Available
Tile children are already available.
Definition qgis.h:6490
@ NeedFetching
Tile has children, but they are not yet available and must be fetched.
Definition qgis.h:6491
@ NoChildren
Tile is known to have no children.
Definition qgis.h:6489
@ VectorTile
Vector tile layer. Added in QGIS 3.14.
Definition qgis.h:211
@ RenderPartialOutputOverPreviousCachedImage
When rendering temporary in-progress preview renders, these preview renders can be drawn over any pre...
Definition qgis.h:3003
@ RenderPartialOutputs
The renderer benefits from rendering temporary in-progress preview renders. These are temporary resul...
Definition qgis.h:3002
@ VerticalCenter
Center align.
Definition qgis.h:3188
Axis
Cartesian axes.
Definition qgis.h:2658
@ Z
Z-axis.
Definition qgis.h:2661
@ Y
Y-axis.
Definition qgis.h:2660
@ Center
Center align.
Definition qgis.h:3169
@ Additive
When tile is refined its content should be used alongside its children simultaneously.
Definition qgis.h:6478
@ Replacement
When tile is refined then its children should be used in place of itself.
Definition qgis.h:6477
@ Reverse
Reverse/inverse transform (from destination to source).
Definition qgis.h:2884
static QgsRuntimeProfiler * profiler()
Returns the application runtime profiler.
static QVector< QgsGltfUtils::InstancedPrimitive > resolveInstancing(const tinygltf::Model &model, const std::optional< TileI3dmData > &tileInstancing, Qgis::Axis gltfUpAxis, const QgsMatrix4x4 &tileTransform, const QgsVector3D &rtcCenter)
Resolves instancing from either i3dm data or EXT_mesh_gpu_instancing.
static QVector< QgsCesiumUtils::TileContents > extractTileContent(const QByteArray &tileContent, const QString &baseUri=QString())
Parses tile content and returns a list of TileContents.
Handles coordinate transforms between two coordinate systems.
QgsCoordinateReferenceSystem destinationCrs() const
Returns the destination coordinate reference system, which the transform will transform coordinates t...
virtual QgsCoordinateReferenceSystem crs() const =0
Returns the coordinate system for the data source.
double measureLine(const QVector< QgsPointXY > &points) const
Measures the length of a line with multiple segments.
Base class for feedback objects to be used for cancellation of something running in a worker thread.
Definition qgsfeedback.h:44
static QPainterPath calculatePainterClipRegion(const QList< QgsMapClippingRegion > &regions, const QgsRenderContext &context, Qgis::LayerType layerType, bool &shouldClip)
Returns a QPainterPath representing the intersection of clipping regions from context which should be...
static QList< QgsMapClippingRegion > collectClippingRegionsForLayer(const QgsRenderContext &context, const QgsMapLayer *layer)
Collects the list of map clipping regions from a context which apply to a map layer.
bool mReadyToCompose
The flag must be set to false in renderer's constructor if wants to use the smarter map redraws funct...
static constexpr int MAX_TIME_TO_USE_CACHED_PREVIEW_IMAGE
Maximum time (in ms) to allow display of a previously cached preview image while rendering layers,...
QString layerId() const
Gets access to the ID of the layer rendered by this class.
QgsRenderContext * renderContext()
Returns the render context associated with the renderer.
QStringList errors() const
Returns list of errors (problems) that happened during the rendering.
QgsMapLayerRenderer(const QString &layerID, QgsRenderContext *context=nullptr)
Constructor for QgsMapLayerRenderer, with the associated layerID and render context.
void transformInPlace(double &x, double &y) const
Transforms map coordinates to device coordinates.
static QgsOrientedBox3D fromBox3D(const QgsBox3D &box)
Constructs an oriented box from an axis-aligned bounding box.
Represents a 2D point.
Definition qgspointxy.h:62
A rectangle specified with double values.
double xMinimum
double yMinimum
double xMaximum
double yMaximum
Contains information about the context of a rendering operation.
double convertToPainterUnits(double size, Qgis::RenderUnit unit, const QgsMapUnitScale &scale=QgsMapUnitScale(), Qgis::RenderSubcomponentProperty property=Qgis::RenderSubcomponentProperty::Generic) const
Converts a size from the specified units to painter units (pixels).
const QgsDistanceArea & distanceArea() const
A general purpose distance and area calculator, capable of performing ellipsoid based calculations.
QPainter * painter()
Returns the destination QPainter for the render operation.
void setPainterFlagsUsingContext(QPainter *painter=nullptr) const
Sets relevant flags on a destination painter, using the flags and settings currently defined for the ...
QgsCoordinateTransformContext transformContext() const
Returns the context's coordinate transform context, which stores various information regarding which ...
QSize outputSize() const
Returns the size of the resulting rendered image, in pixels.
QgsRectangle mapExtent() const
Returns the original extent of the map being rendered.
const QgsMapToPixel & mapToPixel() const
Returns the context's map to pixel transform, which transforms between map coordinates and device coo...
void setPainter(QPainter *p)
Sets the destination QPainter for the render operation.
bool renderingStopped() const
Returns true if the rendering operation has been stopped and any ongoing rendering should be canceled...
QPainter * previewRenderPainter()
Returns the const destination QPainter for temporary in-progress preview renders.
QgsCoordinateTransform coordinateTransform() const
Returns the current coordinate transform for the context.
void record(const QString &name, double time, const QString &group="startup", const QString &id=QString())
Manually adds a profile event with the given name and total time (in seconds).
Scoped object for saving and restoring a QPainter object's state.
Scoped object for setting the current thread name.
void setEnabled(bool enabled)
Sets whether the text buffer will be drawn.
Container for all settings relating to text rendering.
void setColor(const QColor &color)
Sets the color that text will be rendered in.
QgsTextBufferSettings & buffer()
Returns a reference to the text buffer settings.
static void drawText(const QRectF &rect, double rotation, Qgis::TextHorizontalAlignment alignment, const QStringList &textLines, QgsRenderContext &context, const QgsTextFormat &format, bool drawAsOutlines=true, Qgis::TextVerticalAlignment vAlignment=Qgis::TextVerticalAlignment::Top, Qgis::TextRendererFlags flags=Qgis::TextRendererFlags(), Qgis::TextLayoutMode mode=Qgis::TextLayoutMode::Rectangle)
Draws text within a rectangle using the specified settings.
std::unique_ptr< QgsAbstractGeometry > as2DGeometry(const QgsCoordinateTransform &transform=QgsCoordinateTransform(), Qgis::TransformDirection direction=Qgis::TransformDirection::Forward) const
Returns a new geometry representing the 2-dimensional X/Y center slice of the volume.
virtual const QgsCoordinateReferenceSystem sceneCrs() const =0
Returns the original coordinate reference system for the tiled scene data.
bool forceRasterRender() const override
Returns true if the renderer must be rendered to a raster paint device (e.g.
QgsTiledSceneLayerRenderer(QgsTiledSceneLayer *layer, QgsRenderContext &context)
Ctor.
QgsFeedback * feedback() const override
Access to feedback object of the layer renderer (may be nullptr).
bool render() override
Do the rendering (based on data stored in the class).
~QgsTiledSceneLayerRenderer() override
Qgis::MapLayerRendererFlags flags() const override
Returns flags which control how the map layer rendering behaves.
Represents a map layer supporting display of tiled scene objects.
QgsTiledSceneDataProvider * dataProvider() override
Returns the layer's data provider, it may be nullptr.
QgsTiledSceneRenderer * renderer()
Returns the 2D renderer for the tiled scene.
Encapsulates the render context for a 2D tiled scene rendering operation.
void setTextureImage(const QImage &image)
Sets the current texture image.
QgsRenderContext & renderContext()
Returns a reference to the context's render context.
void setTextureCoordinates(float textureX1, float textureY1, float textureX2, float textureY2, float textureX3, float textureY3)
Sets the current texture coordinates.
virtual QgsTiledSceneRenderer * clone() const =0
Create a deep copy of this renderer.
Tiled scene data request.
void setParentTileId(long long id)
Sets the parent tile id, if filtering is to be limited to children of a specific tile.
void setFilterBox(const QgsOrientedBox3D &box)
Sets the box from which data will be taken.
void setFeedback(QgsFeedback *feedback)
Attach a feedback object that can be queried regularly by the request to check if it should be cancel...
void setRequiredGeometricError(double error)
Sets the required geometric error threshold for the returned tiles, in meters.
Represents an individual tile from a tiled scene data source.
bool isValid() const
Returns true if the tile is a valid tile (i.e.
Qgis::TileRefinementProcess refinementProcess() const
Returns the tile's refinement process.
QVariantMap resources() const
Returns the resources attached to the tile.
const QgsTiledSceneBoundingVolume & boundingVolume() const
Returns the bounding volume for the tile.
QVariantMap metadata() const
Returns additional metadata attached to the tile.
long long id() const
Returns the tile's unique ID.
const QgsMatrix4x4 * transform() const
Returns the tile's transform.
A 3D vector (similar to QVector3D) with the difference that it uses double precision instead of singl...
Definition qgsvector3d.h:33
double y() const
Returns Y coordinate.
Definition qgsvector3d.h:60
double z() const
Returns Z coordinate.
Definition qgsvector3d.h:62
double x() const
Returns X coordinate.
Definition qgsvector3d.h:58
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference).
Definition qgis.h:7725
T qgsgeometry_cast(QgsAbstractGeometry *geom)
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:80
#define QgsDebugError(str)
Definition qgslogger.h:71
QgsVector3D rtcCenter
Center position of relative-to-center coordinates (when used).
QByteArray gltf
GLTF binary content.
std::optional< TileI3dmData > instancing
Optional instancing data, populated for i3dm tiles.