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