QGIS API Documentation 4.3.0-Master (6402a64e93b)
Loading...
Searching...
No Matches
qgs3dmapscene.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgs3dmapscene.cpp
3 --------------------------------------
4 Date : July 2017
5 Copyright : (C) 2017 by Martin Dobias
6 Email : wonder dot sk at gmail dot com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
16#include "qgs3dmapscene.h"
17
18#include <limits>
19
20#include "qgs3daxis.h"
22#include "qgs3dmapsettings.h"
23#include "qgs3dsceneexporter.h"
24#include "qgs3dutils.h"
25#include "qgsaabb.h"
26#include "qgsabstract3dengine.h"
30#include "qgsannotationlayer.h"
32#include "qgsapplication.h"
33#include "qgscameracontroller.h"
35#include "qgschunkedentity.h"
36#include "qgschunknode.h"
37#include "qgsenvironmentlight.h"
38#include "qgseventtracing.h"
41#include "qgsframegraph.h"
42#include "qgsgeotransform.h"
46#include "qgslightsource.h"
47#include "qgslinematerial_p.h"
48#include "qgslogger.h"
51#include "qgsmaterial.h"
52#include "qgsmeshlayer.h"
54#include "qgsmessageoutput.h"
58#include "qgspoint3dsymbol.h"
59#include "qgspointcloudlayer.h"
64#include "qgsshadowrenderview.h"
65#include "qgsskyboxentity.h"
66#include "qgsskyboxsettings.h"
67#include "qgssourcecache.h"
68#include "qgsterrainentity.h"
69#include "qgsterraingenerator.h"
70#include "qgstiledscenelayer.h"
72#include "qgsunlitmaterial.h"
73#include "qgsvectorlayer.h"
75#include "qgswindow3dengine.h"
76
77#include <QOpenGLContext>
78#include <QOpenGLFunctions>
79#include <QString>
80#include <QSurface>
81#include <QTimer>
82#include <QUrl>
83#include <Qt3DExtras/QDiffuseSpecularMaterial>
84#include <Qt3DExtras/QForwardRenderer>
85#include <Qt3DExtras/QSphereMesh>
86#include <Qt3DLogic/QFrameAction>
87#include <Qt3DRender/QCamera>
88#include <Qt3DRender/QCullFace>
89#include <Qt3DRender/QDepthTest>
90#include <Qt3DRender/QEffect>
91#include <Qt3DRender/QMaterial>
92#include <Qt3DRender/QMesh>
93#include <Qt3DRender/QRenderPass>
94#include <Qt3DRender/QRenderSettings>
95#include <Qt3DRender/QRenderState>
96#include <Qt3DRender/QSceneLoader>
97#include <Qt3DRender/QTechnique>
98#include <QtMath>
99
100#ifdef HAVE_TRACY
101#include "tracy/Tracy.hpp"
102#endif
103
104#include "moc_qgs3dmapscene.cpp"
105
106using namespace Qt::StringLiterals;
107
108std::function<QMap<QString, Qgs3DMapScene *>()> Qgs3DMapScene::sOpenScenesFunction = [] { return QMap<QString, Qgs3DMapScene *>(); };
109
111 : mMap( map )
112 , mEngine( engine )
113{
114 connect( &map, &Qgs3DMapSettings::backgroundColorChanged, this, &Qgs3DMapScene::onBackgroundColorChanged );
115 onBackgroundColorChanged();
116
117 // The default render policy in Qt3D is "Always" - i.e. the 3D map scene gets refreshed up to 60 fps
118 // even if there's no change. Switching to "on demand" should only re-render when something has changed
119 // and we save quite a lot of resources
120 mEngine->renderSettings()->setRenderPolicy( Qt3DRender::QRenderSettings::OnDemand );
121
122 QRect viewportRect( QPoint( 0, 0 ), mEngine->size() );
123
124 // Get the maximum of clip planes available
125 mMaxClipPlanes = Qgs3DUtils::openGlMaxClipPlanes( mEngine->surface() );
126
127 // Camera
128 float aspectRatio = ( float ) viewportRect.width() / viewportRect.height();
129 mEngine->camera()->lens()->setPerspectiveProjection( static_cast< float >( mMap.fieldOfView() ), aspectRatio, 10.f, 10000.0f );
130
131 mFrameAction = new Qt3DLogic::QFrameAction();
132 connect( mFrameAction, &Qt3DLogic::QFrameAction::triggered, this, &Qgs3DMapScene::onFrameTriggered );
133 addComponent( mFrameAction ); // takes ownership
134
135 // Camera controlling
136 mCameraController = new QgsCameraController( this ); // attaches to the scene
137
138 if ( mMap.sceneMode() == Qgis::SceneMode::Globe )
139 mCameraController->resetGlobe( 10'000'000 );
140 else
141 mCameraController->resetView( 1000 );
142
143 addCameraViewCenterEntity( mEngine->camera() );
144 addCameraRotationCenterEntity( mCameraController );
145 updateLights();
146
147 // create terrain entity
148
149 createTerrainDeferred();
150 connect( &map, &Qgs3DMapSettings::extentChanged, this, &Qgs3DMapScene::createTerrain );
151 connect( &map, &Qgs3DMapSettings::terrainGeneratorChanged, this, &Qgs3DMapScene::createTerrain );
152
153 connect( &map, &Qgs3DMapSettings::terrainSettingsChanged, this, &Qgs3DMapScene::createTerrain );
154
155 connect( &map, &Qgs3DMapSettings::terrainShadingChanged, this, &Qgs3DMapScene::createTerrain );
156 connect( &map, &Qgs3DMapSettings::lightSourcesChanged, this, &Qgs3DMapScene::updateLights );
157 connect( &map, &Qgs3DMapSettings::showLightSourceOriginsChanged, this, &Qgs3DMapScene::updateLights );
158 connect( &map, &Qgs3DMapSettings::fieldOfViewChanged, this, &Qgs3DMapScene::updateCameraLens );
159 connect( &map, &Qgs3DMapSettings::projectionTypeChanged, this, &Qgs3DMapScene::updateCameraLens );
160 connect( &map, &Qgs3DMapSettings::shadowSettingsChanged, this, &Qgs3DMapScene::onShadowSettingsChanged );
161 connect( &map, &Qgs3DMapSettings::ambientOcclusionSettingsChanged, this, &Qgs3DMapScene::onAmbientOcclusionSettingsChanged );
162 connect( &map, &Qgs3DMapSettings::bloomSettingsChanged, this, &Qgs3DMapScene::onBloomSettingsChanged );
163 connect( &map, &Qgs3DMapSettings::colorGradingSettingsChanged, this, &Qgs3DMapScene::onColorGradingSettingsChanged );
164 connect( &map, &Qgs3DMapSettings::eyeDomeLightingEnabledChanged, this, &Qgs3DMapScene::onEyeDomeShadingSettingsChanged );
165 connect( &map, &Qgs3DMapSettings::eyeDomeLightingStrengthChanged, this, &Qgs3DMapScene::onEyeDomeShadingSettingsChanged );
166 connect( &map, &Qgs3DMapSettings::eyeDomeLightingDistanceChanged, this, &Qgs3DMapScene::onEyeDomeShadingSettingsChanged );
167 connect( &map, &Qgs3DMapSettings::msaaEnabledChanged, this, &Qgs3DMapScene::onMsaaEnabledChanged );
168 connect( &map, &Qgs3DMapSettings::debugDepthMapSettingsChanged, this, &Qgs3DMapScene::onDebugDepthMapSettingsChanged );
170 connect( &map, &Qgs3DMapSettings::cameraMovementSpeedChanged, this, &Qgs3DMapScene::onCameraMovementSpeedChanged );
171 connect( &map, &Qgs3DMapSettings::cameraNavigationModeChanged, this, &Qgs3DMapScene::onCameraNavigationModeChanged );
172 connect( &map, &Qgs3DMapSettings::debugOverlayEnabledChanged, this, &Qgs3DMapScene::onDebugOverlayEnabledChanged );
173 connect( &map, &Qgs3DMapSettings::stopUpdatesChanged, this, &Qgs3DMapScene::onStopUpdatesChanged );
174 connect( &map, &Qgs3DMapSettings::show2DMapOverlayChanged, this, &Qgs3DMapScene::onShowMapOverlayChanged );
175 connect( &map, &Qgs3DMapSettings::viewFrustumVisualizationEnabledChanged, this, &Qgs3DMapScene::onShowMapOverlayChanged );
176
177 connect( &map, &Qgs3DMapSettings::axisSettingsChanged, this, &Qgs3DMapScene::on3DAxisSettingsChanged );
178
179 connect( &map, &Qgs3DMapSettings::originChanged, this, &Qgs3DMapScene::onOriginChanged );
180
181 connect( QgsApplication::sourceCache(), &QgsSourceCache::remoteSourceFetched, this, [this]( const QString &url ) {
182 const QList<QgsMapLayer *> modelVectorLayers = mModelVectorLayers;
183 for ( QgsMapLayer *layer : modelVectorLayers )
184 {
185 QgsAbstract3DRenderer *renderer = layer->renderer3D();
186 if ( renderer )
187 {
188 if ( renderer->type() == "vector"_L1 )
189 {
190 const QgsPoint3DSymbol *pointSymbol = static_cast<const QgsPoint3DSymbol *>( static_cast<QgsVectorLayer3DRenderer *>( renderer )->symbol() );
191 if ( pointSymbol && pointSymbol->shapeProperty( u"model"_s ).toString() == url )
192 {
193 removeLayerEntity( layer );
194 addLayerEntity( layer );
195 }
196 }
197 else if ( renderer->type() == "rulebased"_L1 )
198 {
199 const QgsRuleBased3DRenderer::RuleList rules = static_cast<QgsRuleBased3DRenderer *>( renderer )->rootRule()->descendants();
200 for ( const QgsRuleBased3DRenderer::Rule *rule : rules )
201 {
202 const QgsPoint3DSymbol *pointSymbol = dynamic_cast<const QgsPoint3DSymbol *>( rule->symbol() );
203 if ( pointSymbol && pointSymbol->shapeProperty( u"model"_s ).toString() == url )
204 {
205 removeLayerEntity( layer );
206 addLayerEntity( layer );
207 break;
208 }
209 }
210 }
211 }
212 }
213 } );
214
215 // listen to changes of layers in order to add/remove 3D renderer entities
216 connect( &map, &Qgs3DMapSettings::layersChanged, this, &Qgs3DMapScene::onLayersChanged );
217
218 connect( mCameraController, &QgsCameraController::cameraChanged, this, &Qgs3DMapScene::onCameraChanged );
219 connect( mEngine, &QgsAbstract3DEngine::sizeChanged, this, &Qgs3DMapScene::onCameraChanged );
220 connect( mCameraController, &QgsCameraController::depthBufferReady, this, &Qgs3DMapScene::onViewed2DExtentFrom3DChanged );
221
222 mEnvironmentLight = new QgsEnvironmentLight( mEngine->frameGraph(), this );
223
224 connect( &map, &Qgs3DMapSettings::backgroundSettingsChanged, this, &Qgs3DMapScene::onBackgroundSettingsChanged );
225 onBackgroundSettingsChanged();
226
227 // force initial update of chunked entities
228 onCameraChanged();
229 // force initial update of eye dome shading
230 onEyeDomeShadingSettingsChanged();
231 // force initial update of debugging setting of preview quads
232 onDebugDepthMapSettingsChanged();
233 // force initial update of ambient occlusion settings
234 onAmbientOcclusionSettingsChanged();
235 // force initial update of MSAA setting
236 onMsaaEnabledChanged();
237 // initial state of bloom setting
238 onBloomSettingsChanged();
239 // initial state of color grading
240 onColorGradingSettingsChanged();
241
242 // timer used to refresh the map overlay every 250 ms while the camera is moving.
243 // schedule2DMapOverlayUpdate() is called to schedule the update.
244 // At the end of the delay, applyPendingOverlayUpdate() performs the update.
245 mOverlayUpdateTimer = new QTimer( this );
246 mOverlayUpdateTimer->setSingleShot( true );
247 mOverlayUpdateTimer->setInterval( 250 );
248 connect( mOverlayUpdateTimer, &QTimer::timeout, this, &Qgs3DMapScene::applyPendingOverlayUpdate );
249
250 // force initial update of map overlay entity
251 onShowMapOverlayChanged();
252
253 onCameraMovementSpeedChanged();
254
255 on3DAxisSettingsChanged();
256
257 mDepthBufferRefreshTimer = new QTimer( this );
258 mDepthBufferRefreshTimer->setSingleShot( true );
259 mDepthBufferRefreshTimer->setInterval( 100 );
260 connect( mDepthBufferRefreshTimer, &QTimer::timeout, mCameraController, &QgsCameraController::requestDepthBufferCapture );
261}
262
264{
265 if ( mMap.sceneMode() == Qgis::SceneMode::Globe )
266 {
267 mCameraController->resetGlobe( 10'000'000 );
268 return;
269 }
270
271 const QgsDoubleRange zRange = elevationRange();
272 const QgsRectangle extent = sceneExtent();
273 const double side = std::max( extent.width(), extent.height() );
274 double d = side / 2 / std::tan( cameraController()->camera()->fieldOfView() / 2 * M_PI / 180 );
275 d += zRange.isInfinite() ? 0. : zRange.upper();
276 mCameraController->resetView( static_cast<float>( d ) );
277}
278
280{
281 QgsPointXY center = extent.center();
282 const QgsVector3D origin = mMap.origin();
283
284 const QgsVector3D p1 = mMap.mapToWorldCoordinates( QgsVector3D( extent.xMinimum(), extent.yMinimum(), 0 ) );
285 const QgsVector3D p2 = mMap.mapToWorldCoordinates( QgsVector3D( extent.xMaximum(), extent.yMaximum(), 0 ) );
286
287 const double xSide = std::abs( p1.x() - p2.x() );
288 const double ySide = std::abs( p1.y() - p2.y() );
289 const double side = std::max( xSide, ySide );
290
291 const double fov = qDegreesToRadians( cameraController()->camera()->fieldOfView() );
292 double distance = side / 2.0f / std::tan( fov / 2.0f );
293
294 // adjust by elevation
295 const QgsDoubleRange zRange = elevationRange();
296 if ( !zRange.isInfinite() )
297 distance += zRange.upper();
298
299 // subtract map origin so coordinates are relative to it
300 // clang-format off
301 mCameraController->setViewFromTop(
302 static_cast<float>( center.x() - origin.x() ),
303 static_cast<float>( center.y() - origin.y() ),
304 static_cast<float>( distance )
305 );
306 // clang-format on
307}
308
309QVector<QgsPointXY> Qgs3DMapScene::viewFrustum2DExtent() const
310{
311 Qt3DRender::QCamera *camera = mCameraController->camera();
312 QVector<QgsPointXY> extent;
313
314 const QSize size = mEngine->size();
315
316 const QPoint center( size.width() / 2, size.height() / 2 );
317
318 const QVector3D centerWorldPos = Qgs3DUtils::screenPointToWorldPos( center, static_cast<float>( mLastCenterDepth ), size, camera );
319 const float centerZ = centerWorldPos.z();
320 const QVector3D cameraPosition = camera->position();
321
322 QVector<int> pointsOrder = { 0, 1, 3, 2 };
323 for ( int i : pointsOrder )
324 {
325 const QPoint p( ( ( i >> 0 ) & 1 ) ? 0 : size.width(), ( ( i >> 1 ) & 1 ) ? 0 : size.height() );
326
327 const QVector3D edgePoint = Qgs3DUtils::screenPointToWorldPos( p, static_cast<float>( mLastCenterDepth ), size, camera );
328 const QVector3D rayDir = ( edgePoint - cameraPosition ).normalized();
329
330 QVector3D worldPos;
331 if ( std::abs( rayDir.z() ) > 0 )
332 {
333 float t = ( centerZ - cameraPosition.z() ) / rayDir.z();
334 if ( t < 0 )
335 t = camera->farPlane();
336 else
337 t = std::min<float>( t, camera->farPlane() );
338 worldPos = cameraPosition + t * rayDir;
339 }
340 else
341 {
342 worldPos = cameraPosition + camera->farPlane() * rayDir;
343 worldPos.setZ( centerZ );
344 }
345
346 QgsVector3D mapPos = mMap.worldToMapCoordinates( worldPos );
347 extent.push_back( QgsPointXY( mapPos.x(), mapPos.y() ) );
348 }
349 return extent;
350}
351
353{
354 int count = 0;
355 for ( Qgs3DMapSceneEntity *entity : std::as_const( mSceneEntities ) )
356 count += entity->pendingJobsCount();
357 return count;
358}
359
360double Qgs3DMapScene::worldSpaceError( double epsilon, double distance ) const
361{
362 Qt3DRender::QCamera *camera = mCameraController->camera();
363 const double fov = camera->fieldOfView();
364 const QSize size = mEngine->size();
365 const int screenSizePx = std::max( size.width(), size.height() ); // TODO: is this correct?
366
367 // see Qgs3DUtils::screenSpaceError() for the inverse calculation (world space error to screen space error)
368 // with explanation of the math.
369 const double frustumWidthAtDistance = 2 * distance * tan( fov / 2 );
370 const double err = frustumWidthAtDistance * epsilon / screenSizePx;
371 return err;
372}
373
374void Qgs3DMapScene::onCameraChanged()
375{
376 if ( mDepthBufferRefreshTimer )
377 mDepthBufferRefreshTimer->start();
378
379 updateScene( true );
380 updateCameraNearFarPlanes();
381
382 onShadowSettingsChanged();
383
384 const QVector<QgsPointXY> extent2D = viewFrustum2DExtent();
385 emit viewed2DExtentFrom3DChanged( extent2D );
386 schedule2DMapOverlayUpdate();
387
388 // The magic to make things work better in large scenes (e.g. more than 50km across)
389 // is here: we will simply move the origin of the scene, and update transforms
390 // of the camera and all other entities. That should ensure we will not need to deal
391 // with large coordinates in 32-bit floats (and if we do have large coordinates,
392 // because the scene is far from the camera, we don't care, because those errors
393 // end up being tiny when viewed from far away).
394 constexpr float ORIGIN_SHIFT_THRESHOLD = 10'000;
395 if ( mSceneOriginShiftEnabled && mEngine->camera()->position().length() > ORIGIN_SHIFT_THRESHOLD )
396 {
397 const QgsVector3D newOrigin = mMap.origin() + QgsVector3D( mEngine->camera()->position() );
398 QgsDebugMsgLevel( u"Rebasing scene origin from %1 to %2"_s.arg( mMap.origin().toString( 1 ), newOrigin.toString( 1 ) ), 2 );
399 mMap.setOrigin( newOrigin );
400 }
401}
402
403void Qgs3DMapScene::onViewed2DExtentFrom3DChanged()
404{
405 const QSize size = mEngine->size();
406 const QPoint center( size.width() / 2, size.height() / 2 );
407
408 const double sampledDepth = mCameraController->sampleDepthBuffer( center.x(), center.y() );
409
410 if ( sampledDepth != 1.0 )
411 mLastCenterDepth = sampledDepth;
412
413 const QVector<QgsPointXY> extent2D = viewFrustum2DExtent();
414 emit viewed2DExtentFrom3DChanged( extent2D );
415}
416
417bool Qgs3DMapScene::updateScene( bool forceUpdate )
418{
419 if ( !mSceneUpdatesEnabled )
420 {
421 QgsDebugMsgLevel( "Scene update skipped", 2 );
422 return false;
423 }
424
425 QgsScopedEvent traceEvent( u"3D"_s, forceUpdate ? u"Force update scene"_s : u"Update scene"_s );
426
427 Qgs3DMapSceneEntity::SceneContext sceneContext;
428 Qt3DRender::QCamera *camera = mEngine->camera();
429 sceneContext.cameraFov = camera->fieldOfView();
430 sceneContext.cameraPos = camera->position();
431 const QSize size = mEngine->size();
432 sceneContext.screenSizePx = std::max( size.width(), size.height() ); // TODO: is this correct?
433
434 // Make our own projection matrix so that frustum culling done by the
435 // entities isn't dependent on the current near/far planes, which would then
436 // require multiple steps to stabilize.
437 // The matrix is constructed just like in QMatrix4x4::perspective() /
438 // ortho(), but for all elements involving the near and far plane, the limit
439 // of the expression with the far plane going to infinity is taken.
440 QMatrix4x4 projMatrix;
441 switch ( mMap.projectionType() )
442 {
444 {
445 float fovRadians = ( camera->fieldOfView() / 2.0f ) * static_cast<float>( M_PI ) / 180.0f;
446 float fovCotan = std::cos( fovRadians ) / std::sin( fovRadians );
447 // clang-format off
448 projMatrix = {
449 fovCotan / camera->aspectRatio(), 0, 0, 0,
450 0, fovCotan, 0, 0,
451 0, 0, -1, -2,
452 0, 0, -1, 0
453 };
454 // clang-format on
455 break;
456 }
458 {
459 Qt3DRender::QCameraLens *lens = camera->lens();
460 // clang-format off
461 projMatrix = {
462 2.0f / ( lens->right() - lens->left() ), 0, 0, 0,
463 0, 2.0f / ( lens->top() - lens->bottom() ), 0, 0,
464 0, 0, 1, 0,
465 -( lens->left() + lens->right() ) / ( lens->right() - lens->left() ), -( lens->top() + lens->bottom() ) / ( lens->top() - lens->bottom() ), -1.0f, 1.0f
466 };
467 // clang-format on
468 break;
469 }
470 default:
471 QgsDebugError( "Unhandled 3D projection type" );
472 projMatrix = camera->projectionMatrix(); // Give up and use the current matrix
473 }
474 sceneContext.viewProjectionMatrix = projMatrix * camera->viewMatrix();
475
476
477 bool anyUpdated = false;
478 for ( Qgs3DMapSceneEntity *entity : std::as_const( mSceneEntities ) )
479 {
480 if ( forceUpdate || ( entity->isEnabled() && entity->needsUpdate() ) )
481 {
482 anyUpdated = true;
483 entity->handleSceneUpdate( sceneContext );
484 if ( entity->hasReachedGpuMemoryLimit() )
486 }
487 }
488
489 updateSceneState();
490
491 return anyUpdated;
492}
493
494bool Qgs3DMapScene::updateCameraNearFarPlanes()
495{
496 // Update near and far plane from the terrain.
497 // this needs to be done with great care as we have kind of circular dependency here:
498 // active nodes are culled based on the current frustum (which involves near + far plane)
499 // and then based on active nodes we set near and far plane.
500 //
501 // All of this is just heuristics assuming that all other stuff is being rendered somewhere
502 // around the area where the terrain is.
503 //
504 // Near/far plane is setup in order to make best use of the depth buffer to avoid:
505 // 1. precision errors - if the range is too great
506 // 2. unwanted clipping of scene - if the range is too small
507
508 Qt3DRender::QCamera *camera = cameraController()->camera();
509 QMatrix4x4 viewMatrix = camera->viewMatrix();
510 float fnear = 1e9;
511 float ffar = 0;
512
513 // Iterate all scene entities to make sure that they will not get
514 // clipped by the near or far plane
515 for ( Qgs3DMapSceneEntity *se : std::as_const( mSceneEntities ) )
516 {
517 const QgsRange<float> depthRange = se->getNearFarPlaneRange( viewMatrix );
518
519 fnear = std::min( fnear, depthRange.lower() );
520 ffar = std::max( ffar, depthRange.upper() );
521 }
522
523 if ( fnear < 1 )
524 fnear = 1; // does not really make sense to use negative far plane (behind camera)
525
526 // the update didn't work out... this can happen if the scene does not contain
527 // any Qgs3DMapSceneEntity. Use the scene extent to compute near and far planes
528 // as a fallback.
529 if ( fnear == 1e9 && ffar == 0 )
530 {
531 QgsDoubleRange sceneZRange = elevationRange();
532 sceneZRange = sceneZRange.isInfinite() ? QgsDoubleRange( 0.0, 0.0 ) : sceneZRange;
533 const QgsAABB sceneBbox = Qgs3DUtils::mapToWorldExtent( mMap.extent(), sceneZRange.lower(), sceneZRange.upper(), mMap.origin() );
534 Qgs3DUtils::computeBoundingBoxNearFarPlanes( sceneBbox, viewMatrix, fnear, ffar );
535 }
536
537 // when zooming in a lot, fnear can become smaller than ffar. This should not happen
538 if ( fnear > ffar )
539 std::swap( fnear, ffar );
540
541 // set near/far plane - with some tolerance in front/behind expected near/far planes
542 float newFar = ffar * 2;
543 float newNear = fnear / 2;
544 if ( !qgsFloatNear( newFar, camera->farPlane() ) || !qgsFloatNear( newNear, camera->nearPlane() ) )
545 {
546 camera->setFarPlane( newFar );
547 camera->setNearPlane( newNear );
548 return true;
549 }
550
551 return false;
552}
553
554void Qgs3DMapScene::onFrameTriggered( float dt )
555{
556#ifdef HAVE_TRACY
557 FrameMark;
558#endif
559 QgsEventTracing::addEventToQgisTrace( QgsEventTracing::EventType::Instant, u"3D"_s, u"Frame begins"_s );
560
561 mCameraController->frameTriggered( dt );
562
563 if ( updateScene() )
564 // If the scene was changed, node bboxes might have changed, so we need to
565 // update near/far planes.
566 updateCameraNearFarPlanes();
567
568 // lock changing the FPS counter to 5 fps
569 static int frameCount = 0;
570 static float accumulatedTime = 0.0f;
571
572 if ( !mMap.debugFlags().testFlag( Qgis::Map3DDebugFlag::ShowFPS ) )
573 {
574 frameCount = 0;
575 accumulatedTime = 0;
576 return;
577 }
578
579 frameCount++;
580 accumulatedTime += dt;
581 if ( accumulatedTime >= 0.2f )
582 {
583 float fps = ( float ) frameCount / accumulatedTime;
584 frameCount = 0;
585 accumulatedTime = 0.0f;
586 emit fpsCountChanged( fps );
587 }
588}
589
590void Qgs3DMapScene::update2DMapOverlay( const QVector<QgsPointXY> &extent2DAsPoints )
591{
592 QgsFrameGraph *frameGraph = mEngine->frameGraph();
593 QgsOverlayTextureRenderView &overlayRenderView = frameGraph->overlayTextureRenderView();
594
595 if ( !mMap.is2DMapOverlayEnabled() )
596 {
597 if ( mMapOverlayEntity )
598 {
599 mMapOverlayEntity.reset();
600 }
601 overlayRenderView.setEnabled( mMap.debugDepthMapEnabled() );
602 return;
603 }
604
605 if ( !mMapOverlayEntity )
606 {
607 QgsWindow3DEngine *engine = qobject_cast<QgsWindow3DEngine *>( mEngine );
608 mMapOverlayEntity = make_qobject_unique<QgsMapOverlayEntity>( engine, &overlayRenderView, &mMap, this );
609 mMapOverlayEntity->setEnabled( true );
610 overlayRenderView.setEnabled( true );
611 }
612
613
614 Qt3DRender::QCamera *camera = mEngine->camera();
615 const QgsVector3D extentCenter3D = mMap.worldToMapCoordinates( camera->position() );
616 const QgsPointXY extentCenter2D( extentCenter3D.x(), extentCenter3D.y() );
617
618 // Compute an extent that provides an overview around the camera position.
619 // When the view is from above (pitch near 0°), the scene's extent is reduced.
620 // As the pitch increases (up to 90°), a larger portion of the scene becomes visible.
621 // The smoothing factor allows reproducing the behavior of the maximum extent
622 // as the pitch changes. The calculated extent is bounded to prevent covering too large a scene.
623 double minHalfExtent = std::numeric_limits<double>::max();
624 double maxHalfExtent = 0.0;
625 for ( const QgsPointXY &extentPoint : extent2DAsPoints )
626 {
627 const double distance = extentCenter2D.distance( extentPoint );
628 minHalfExtent = std::min( minHalfExtent, distance );
629 maxHalfExtent = std::max( maxHalfExtent, distance );
630 }
631
632 const QgsRectangle fullExtent = sceneExtent();
633 const double sceneHalfExtent = 0.6 * std::max( fullExtent.width(), fullExtent.height() );
634
635 minHalfExtent = std::min( 50., minHalfExtent );
636 maxHalfExtent = std::min( 100., maxHalfExtent );
637 const double smoothFactor = std::sin( mCameraController->pitch() / 90.0 * M_PI_2 );
638
639 // Using the scene extent to prevent the image from becoming too wide when zooming out
640 const double adjustedHalfExtent = std::min( 3.0 * ( minHalfExtent + smoothFactor * maxHalfExtent ), sceneHalfExtent );
641
642 const QgsRectangle overviewExtent = QgsRectangle::fromCenterAndSize( extentCenter2D, adjustedHalfExtent, adjustedHalfExtent );
643 const bool showFrustum = mMap.viewFrustumVisualizationEnabled();
644 mMapOverlayEntity->update( overviewExtent, extent2DAsPoints, mCameraController->yaw(), showFrustum );
645}
646
647void Qgs3DMapScene::createTerrain()
648{
649 if ( mTerrain )
650 {
651 mSceneEntities.removeOne( mTerrain );
652
653 delete mTerrain;
654 mTerrain = nullptr;
655 }
656
657 if ( mGlobe )
658 {
659 mSceneEntities.removeOne( mGlobe );
660
661 delete mGlobe;
662 mGlobe = nullptr;
663 }
664
665 if ( !mTerrainUpdateScheduled )
666 {
667 // defer re-creation of terrain: there may be multiple invocations of this slot, so create the new entity just once
668 QTimer::singleShot( 0, this, &Qgs3DMapScene::createTerrainDeferred );
669 mTerrainUpdateScheduled = true;
670 setSceneState( Updating );
671 }
672 else
673 {
675 }
676}
677
678void Qgs3DMapScene::createTerrainDeferred()
679{
680 QgsChunkedEntity *terrainOrGlobe = nullptr;
681
682 if ( mMap.sceneMode() == Qgis::SceneMode::Globe && mMap.terrainRenderingEnabled() )
683 {
684 mGlobe = new QgsGlobeEntity( &mMap );
685 terrainOrGlobe = mGlobe;
686 }
687 else if ( mMap.sceneMode() == Qgis::SceneMode::Local && mMap.terrainRenderingEnabled() && mMap.terrainGenerator() )
688 {
689 double tile0width = mMap.terrainGenerator()->rootChunkExtent().width();
690 int maxZoomLevel = Qgs3DUtils::maxZoomLevel( tile0width, mMap.terrainSettings()->mapTileResolution(), mMap.terrainSettings()->maximumGroundError() );
691 const QgsBox3D rootBox3D = mMap.terrainGenerator()->rootChunkBox3D( mMap );
692 float rootError = mMap.terrainGenerator()->rootChunkError( mMap );
693 const QgsBox3D clippingBox3D( mMap.extent(), rootBox3D.zMinimum(), rootBox3D.zMaximum() );
694 mMap.terrainGenerator()->setupQuadtree( rootBox3D, rootError, maxZoomLevel, clippingBox3D );
695
696 mTerrain = new QgsTerrainEntity( &mMap );
697 mTerrain->setObjectName( u"Terrain"_s );
698 terrainOrGlobe = mTerrain;
699 }
700
701 if ( terrainOrGlobe )
702 {
703 // The terrain is not added through addSceneEntity(), so we add the required QLayers here
704 QgsFrameGraph *frameGraph = mEngine->frameGraph();
705 terrainOrGlobe->addComponent( frameGraph->forwardRenderView().renderLayer() );
706 terrainOrGlobe->addComponent( frameGraph->shadowRenderView().entityCastingShadowsLayer() );
707
708 terrainOrGlobe->setParent( this );
709 terrainOrGlobe->setShowBoundingBoxes( mMap.debugFlags().testFlag( Qgis::Map3DDebugFlag::ShowTerrainBoundingBoxes ) );
710
711 mSceneEntities << terrainOrGlobe;
712
713 connect( terrainOrGlobe, &QgsChunkedEntity::pendingJobsCountChanged, this, &Qgs3DMapScene::totalPendingJobsCountChanged );
714 connect( terrainOrGlobe, &Qgs3DMapSceneEntity::newEntityCreated, this, [this]( Qt3DCore::QEntity *entity ) {
715 // let's make sure that any entity we're about to show has the right scene origin set
716 const QList<QgsGeoTransform *> transforms = entity->findChildren<QgsGeoTransform *>();
717 for ( QgsGeoTransform *transform : transforms )
718 {
719 transform->setOrigin( mMap.origin() );
720 }
721
722 // enable clipping on the terrain if necessary
723 handleClippingOnEntity( entity );
724 } );
725 }
726
727 // make sure that renderers for layers are re-created as well
728 const QList<QgsMapLayer *> layers = mMap.layers();
729 for ( QgsMapLayer *layer : layers )
730 {
731 // remove old entity - if any
732 removeLayerEntity( layer );
733
734 // add new entity - if any 3D renderer
735 addLayerEntity( layer );
736 }
737
739 onCameraChanged(); // force update of the new terrain
740 mTerrainUpdateScheduled = false;
741}
742
743void Qgs3DMapScene::onBackgroundColorChanged()
744{
745 mEngine->setClearColor( mMap.backgroundColor() );
746}
747
748void Qgs3DMapScene::updateLights()
749{
750 for ( Qt3DCore::QEntity *entity : std::as_const( mLightEntities ) )
751 entity->deleteLater();
752 mLightEntities.clear();
753
754 QgsFrameGraph *frameGraph = mEngine->frameGraph();
755 const QList<QgsLightSource *> newLights = mMap.lightSources();
756 for ( const QgsLightSource *source : newLights )
757 {
758 Qt3DCore::QEntity *entity = source->createEntity( mMap, this );
759 entity->addComponent( frameGraph->forwardRenderView().renderLayer() );
760 mLightEntities.append( entity );
761 }
762
763 onShadowSettingsChanged();
764}
765
766void Qgs3DMapScene::updateCameraLens()
767{
768 mEngine->camera()->lens()->setFieldOfView( static_cast< float >( mMap.fieldOfView() ) );
769 mEngine->camera()->lens()->setProjectionType( static_cast<Qt3DRender::QCameraLens::ProjectionType>( mMap.projectionType() ) );
770 onCameraChanged();
771}
772
773void Qgs3DMapScene::onLayerRenderer3DChanged()
774{
775 QgsMapLayer *layer = qobject_cast<QgsMapLayer *>( sender() );
776 Q_ASSERT( layer );
777
778 // remove old entity - if any
779 removeLayerEntity( layer );
780
781 // add new entity - if any 3D renderer
782 addLayerEntity( layer );
783}
784
785void Qgs3DMapScene::onLayersChanged()
786{
787 QSet<QgsMapLayer *> layersBefore = qgis::listToSet( mLayerEntities.keys() );
788 QList<QgsMapLayer *> layersAdded;
789 const QList<QgsMapLayer *> layers = mMap.layers();
790 for ( QgsMapLayer *layer : layers )
791 {
792 if ( !layersBefore.contains( layer ) )
793 {
794 layersAdded << layer;
795 }
796 else
797 {
798 layersBefore.remove( layer );
799 }
800 }
801
802 // what is left in layersBefore are layers that have been removed
803 for ( QgsMapLayer *layer : std::as_const( layersBefore ) )
804 {
805 removeLayerEntity( layer );
806 }
807
808 for ( QgsMapLayer *layer : std::as_const( layersAdded ) )
809 {
810 addLayerEntity( layer );
811 }
812}
813
815{
816 const QList<QgsMapLayer *> layers = mLayerEntities.keys();
817 for ( QgsMapLayer *layer : layers )
818 {
819 if ( QgsMapLayerTemporalProperties *temporalProperties = layer->temporalProperties() )
820 {
821 if ( temporalProperties->isActive() )
822 {
823 removeLayerEntity( layer );
824 addLayerEntity( layer );
825 }
826 }
827 }
828}
829
830void Qgs3DMapScene::addSceneEntity( Qgs3DMapSceneEntity *sceneNewEntity )
831{
832 Q_ASSERT( sceneNewEntity );
833
834 mSceneEntities.append( sceneNewEntity );
835
836 sceneNewEntity->setParent( this );
837
838 finalizeNewEntity( sceneNewEntity );
839
840 connect( sceneNewEntity, &Qgs3DMapSceneEntity::newEntityCreated, this, [this]( Qt3DCore::QEntity *entity ) {
841 finalizeNewEntity( entity );
842 // this ensures to update the near/far planes with the exact bounding box of the new entity.
843 updateCameraNearFarPlanes();
844 } );
845
846 connect( sceneNewEntity, &Qgs3DMapSceneEntity::pendingJobsCountChanged, this, &Qgs3DMapScene::totalPendingJobsCountChanged );
847
848 onCameraChanged(); // needed for chunked entities
849}
850
851void Qgs3DMapScene::removeSceneEntity( Qgs3DMapSceneEntity *sceneEntity )
852{
853 Q_ASSERT( sceneEntity );
854
855 mSceneEntities.removeOne( sceneEntity );
856
857 sceneEntity->deleteLater();
858}
859
860
861void Qgs3DMapScene::addLayerEntity( QgsMapLayer *layer )
862{
863 QgsAbstract3DRenderer *renderer = layer->renderer3D();
864 if ( renderer )
865 {
866 // Fix vector layer's renderer to make sure the renderer is pointing to its layer.
867 // It has happened before that renderer pointed to a different layer (probably after copying a style).
868 // This is a bit of a hack and it should be handled in QgsMapLayer::setRenderer3D() but in qgis_core
869 // the vector layer 3D renderer classes are not available.
870 if ( layer->type() == Qgis::LayerType::Vector && ( renderer->type() == "vector"_L1 || renderer->type() == "rulebased"_L1 || renderer->type() == "categorized"_L1 ) )
871 {
872 static_cast<QgsAbstractVectorLayer3DRenderer *>( renderer )->setLayer( static_cast<QgsVectorLayer *>( layer ) );
873 if ( renderer->type() == "vector"_L1 )
874 {
875 QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
876 if ( vlayer->geometryType() == Qgis::GeometryType::Point )
877 {
878 const QgsPoint3DSymbol *pointSymbol = static_cast<const QgsPoint3DSymbol *>( static_cast<QgsVectorLayer3DRenderer *>( renderer )->symbol() );
879 if ( pointSymbol->shape() == Qgis::Point3DShape::Model )
880 {
881 mModelVectorLayers.append( layer );
882 }
883 }
884 }
885 else if ( renderer->type() == "rulebased"_L1 )
886 {
887 const QgsRuleBased3DRenderer::RuleList rules = static_cast<QgsRuleBased3DRenderer *>( renderer )->rootRule()->descendants();
888 for ( auto rule : rules )
889 {
890 const QgsPoint3DSymbol *pointSymbol = dynamic_cast<const QgsPoint3DSymbol *>( rule->symbol() );
891 if ( pointSymbol && pointSymbol->shape() == Qgis::Point3DShape::Model )
892 {
893 mModelVectorLayers.append( layer );
894 break;
895 }
896 }
897 }
898 else if ( renderer->type() == "categorized"_L1 )
899 {
900 const Qgs3DCategoryList categories = static_cast<QgsCategorized3DRenderer *>( renderer )->categories();
901 for ( const Qgs3DRendererCategory &category : categories )
902 {
903 const QgsPoint3DSymbol *pointSymbol = dynamic_cast<const QgsPoint3DSymbol *>( category.symbol() );
904 if ( pointSymbol && pointSymbol->shape() == Qgis::Point3DShape::Model )
905 {
906 mModelVectorLayers.append( layer );
907 break;
908 }
909 }
910 }
911 }
912 else if ( layer->type() == Qgis::LayerType::Mesh && renderer->type() == "mesh"_L1 )
913 {
914 QgsMeshLayer3DRenderer *meshRenderer = static_cast<QgsMeshLayer3DRenderer *>( renderer );
915 meshRenderer->setLayer( static_cast<QgsMeshLayer *>( layer ) );
916
917 // Before entity creation, set the maximum texture size
918 // Not very clean, but for now, only place found in the workflow to do that simple
919 QgsMesh3DSymbol *sym = meshRenderer->symbol()->clone();
920 sym->setMaximumTextureSize( maximumTextureSize() );
921 meshRenderer->setSymbol( sym );
922 }
923 else if ( layer->type() == Qgis::LayerType::PointCloud && renderer->type() == "pointcloud"_L1 )
924 {
925 QgsPointCloudLayer3DRenderer *pointCloudRenderer = static_cast<QgsPointCloudLayer3DRenderer *>( renderer );
926 pointCloudRenderer->setLayer( static_cast<QgsPointCloudLayer *>( layer ) );
927 }
928 else if ( layer->type() == Qgis::LayerType::TiledScene && renderer->type() == "tiledscene"_L1 )
929 {
930 QgsTiledSceneLayer3DRenderer *tiledSceneRenderer = static_cast<QgsTiledSceneLayer3DRenderer *>( renderer );
931 tiledSceneRenderer->setLayer( static_cast<QgsTiledSceneLayer *>( layer ) );
932 }
933 else if ( layer->type() == Qgis::LayerType::Annotation && renderer->type() == "annotation"_L1 )
934 {
935 auto annotationLayerRenderer = qgis::down_cast<QgsAnnotationLayer3DRenderer *>( renderer );
936 annotationLayerRenderer->setLayer( qobject_cast<QgsAnnotationLayer *>( layer ) );
937 }
938
939 Qt3DCore::QEntity *newEntity = renderer->createEntity( &mMap );
940 if ( newEntity )
941 {
942 // Add name to QObject for debugging
943 newEntity->setObjectName( u"%1 3D entity"_s.arg( layer->name() ) );
944
945 mLayerEntities.insert( layer, newEntity );
946
947 if ( Qgs3DMapSceneEntity *sceneNewEntity = qobject_cast<Qgs3DMapSceneEntity *>( newEntity ) )
948 {
949 // also sets this scene as the entity's parent and finalizes it
950 addSceneEntity( sceneNewEntity );
951 }
952 else
953 {
954 newEntity->setParent( this );
955 finalizeNewEntity( newEntity );
956 }
957 }
958 }
959
960 connect( layer, &QgsMapLayer::request3DUpdate, this, &Qgs3DMapScene::onLayerRenderer3DChanged );
961
962 if ( layer->type() == Qgis::LayerType::Vector )
963 {
964 QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
965 connect( vlayer, &QgsVectorLayer::selectionChanged, this, &Qgs3DMapScene::onLayerRenderer3DChanged );
966 connect( vlayer, &QgsVectorLayer::layerModified, this, &Qgs3DMapScene::onLayerRenderer3DChanged );
967 connect( vlayer, &QgsVectorLayer::subsetStringChanged, this, &Qgs3DMapScene::onLayerRenderer3DChanged );
968 }
969
970 if ( layer->type() == Qgis::LayerType::Mesh )
971 {
972 connect( layer, &QgsMapLayer::rendererChanged, this, &Qgs3DMapScene::onLayerRenderer3DChanged );
973 }
974
975 if ( layer->type() == Qgis::LayerType::PointCloud )
976 {
977 QgsPointCloudLayer *pclayer = qobject_cast<QgsPointCloudLayer *>( layer );
978 connect( pclayer, &QgsPointCloudLayer::renderer3DChanged, this, &Qgs3DMapScene::onLayerRenderer3DChanged );
979 connect( pclayer, &QgsPointCloudLayer::subsetStringChanged, this, &Qgs3DMapScene::onLayerRenderer3DChanged );
980 }
981}
982
983void Qgs3DMapScene::removeLayerEntity( QgsMapLayer *layer )
984{
985 Qt3DCore::QEntity *entity = mLayerEntities.take( layer );
986
987 if ( Qgs3DMapSceneEntity *sceneEntity = qobject_cast<Qgs3DMapSceneEntity *>( entity ) )
988 {
989 // also schedules the entity for deletion
990 removeSceneEntity( sceneEntity );
991 }
992 else
993 {
994 if ( entity )
995 entity->deleteLater();
996 }
997
998 disconnect( layer, &QgsMapLayer::request3DUpdate, this, &Qgs3DMapScene::onLayerRenderer3DChanged );
999
1000 if ( layer->type() == Qgis::LayerType::Vector )
1001 {
1002 QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
1003 disconnect( vlayer, &QgsVectorLayer::selectionChanged, this, &Qgs3DMapScene::onLayerRenderer3DChanged );
1004 disconnect( vlayer, &QgsVectorLayer::layerModified, this, &Qgs3DMapScene::onLayerRenderer3DChanged );
1005 disconnect( vlayer, &QgsVectorLayer::subsetStringChanged, this, &Qgs3DMapScene::onLayerRenderer3DChanged );
1006 mModelVectorLayers.removeAll( layer );
1007 }
1008
1009 if ( layer->type() == Qgis::LayerType::Mesh )
1010 {
1011 disconnect( layer, &QgsMapLayer::rendererChanged, this, &Qgs3DMapScene::onLayerRenderer3DChanged );
1012 }
1013
1014 if ( layer->type() == Qgis::LayerType::PointCloud )
1015 {
1016 QgsPointCloudLayer *pclayer = qobject_cast<QgsPointCloudLayer *>( layer );
1017 disconnect( pclayer, &QgsPointCloudLayer::renderer3DChanged, this, &Qgs3DMapScene::onLayerRenderer3DChanged );
1018 disconnect( pclayer, &QgsPointCloudLayer::subsetStringChanged, this, &Qgs3DMapScene::onLayerRenderer3DChanged );
1019 disconnect( pclayer, &QgsPointCloudLayer::layerModified, this, &Qgs3DMapScene::onLayerRenderer3DChanged );
1020 }
1021}
1022
1023void Qgs3DMapScene::finalizeNewEntity( Qt3DCore::QEntity *newEntity )
1024{
1025 // let's make sure that any entity we're about to show has the right scene origin set
1026 const QList<QgsGeoTransform *> transforms = newEntity->findChildren<QgsGeoTransform *>();
1027 for ( QgsGeoTransform *transform : transforms )
1028 {
1029 transform->setOrigin( mMap.origin() );
1030 }
1031
1032 // set clip planes on the new entity if necessary
1033 handleClippingOnEntity( newEntity );
1034
1035 // this is probably not the best place for material-specific configuration,
1036 // maybe this could be more generalized when other materials need some specific treatment
1037 const QList<Qt3DRender::QMaterial *> childMaterials = newEntity->findChildren<Qt3DRender::QMaterial *>();
1038
1039 // first pass over materials -- setup viewport sizing logic for ALL materials that require it
1040 // (this needs to apply to all materials, includes those for highlight entities)
1041 for ( Qt3DRender::QMaterial *material : childMaterials )
1042 {
1043 if ( auto lm = qobject_cast< QgsLineMaterial * >( material ) )
1044 {
1045 connect( mEngine, &QgsAbstract3DEngine::sizeChanged, lm, [lm, this] { lm->setViewportSize( mEngine->size() ); } );
1046 lm->setViewportSize( mEngine->size() );
1047 }
1048 else if ( auto bm = qobject_cast< QgsPoint3DBillboardMaterial * >( material ) )
1049 {
1050 connect( mEngine, &QgsAbstract3DEngine::sizeChanged, bm, [bm, this] { bm->setViewportSize( mEngine->size() ); } );
1051 bm->setViewportSize( mEngine->size() );
1052 }
1053 }
1054
1055 QgsFrameGraph *frameGraph = mEngine->frameGraph();
1056
1057 // Here we check if the entity should not be rendered in the forward render view
1058 // For example highlight entities should only be rendered in the highlights render view, so we check for attached QLayers
1059 const QVector<Qt3DRender::QLayer *> layers = newEntity->componentsOfType<Qt3DRender::QLayer>();
1060 if ( layers.contains( frameGraph->highlightsRenderView().highlightsLayer() ) )
1061 return;
1062
1063 // Add the required QLayers to the entity
1064 newEntity->addComponent( frameGraph->forwardRenderView().renderLayer() );
1065
1066 Qt3DRender::QLayer *shadowCastingEntityLayer = frameGraph->shadowRenderView().entityCastingShadowsLayer();
1067 Qt3DRender::QLayer *transparentLayer = frameGraph->forwardRenderView().transparentObjectLayer();
1068 for ( Qt3DRender::QMaterial *material : childMaterials )
1069 {
1070 // find the specific entity this material belongs to -- it may be a child of the parent entity
1071 // being finalized
1072 auto materialEntity = qobject_cast<Qt3DCore::QEntity *>( material->parent() );
1073 if ( !materialEntity )
1074 continue;
1075
1076 bool materialCastsShadows = false;
1077 if ( auto qgsMaterial = qobject_cast< QgsMaterial * >( material ) )
1078 {
1079 if ( qgsMaterial->castsShadows() )
1080 {
1081 materialCastsShadows = true;
1082 }
1083 }
1084 else
1085 {
1086 // for non QgsMaterial materials we assume they need shadows
1087 materialCastsShadows = true;
1088 }
1089
1090 if ( materialCastsShadows && !materialEntity->components().contains( shadowCastingEntityLayer ) )
1091 {
1092 materialEntity->addComponent( shadowCastingEntityLayer );
1093 }
1094
1095 // Finalize adding the 3D transparent objects by adding the layer components to the entities
1096
1097 // This handles the phong material without data defined properties.
1098 if ( auto ph = qobject_cast<Qt3DExtras::QDiffuseSpecularMaterial *>( material ) )
1099 {
1100 if ( ph->diffuse().value<QColor>().alphaF() != 1.0f )
1101 {
1102 if ( !materialEntity->components().contains( transparentLayer ) )
1103 {
1104 materialEntity->addComponent( transparentLayer );
1105 }
1106 }
1107 }
1108#if 0
1109 /*
1110 * Adds transparency layer to QgsPoint3DBillboardMaterial entities,
1111 * so that they get rendered in the transparent pipeline instead
1112 * of the opaque pipeline. Permits semi-opaque pixel rendering.
1113 *
1114 * Pros: nicely smoothed billboard symbol rendering, without harsh
1115 * aliased edges. Billboard symbols can use semi-transparent colors.
1116 *
1117 * Cons: Introduces ordering issues for billboards, where billboards
1118 * which should be shown behind others will appear in front from
1119 * some angles (i.e. the same issue as we get for 3d polygon objects
1120 * with transparency)
1121 *
1122 * Consider enabling if/when we have some workaround for the stacking issue,
1123 * eg CPU based sorting on camera movement...
1124 */
1125 else if ( auto billboardMaterial = qobject_cast<QgsPoint3DBillboardMaterial *>( material ) )
1126 {
1127 Qt3DCore::QEntity *entity = qobject_cast<Qt3DCore::QEntity *>( billboardMaterial->parent() );
1128 if ( !materialEntity->components().contains( transparentLayer ) )
1129 {
1130 materialEntity->addComponent( transparentLayer );
1131 }
1132 }
1133#endif
1134 else
1135 {
1136 // This handles the phong material with data defined properties, the textured case and point (instanced) symbols.
1137 if ( Qt3DRender::QEffect *effect = material->effect() )
1138 {
1139 const QVector<Qt3DRender::QParameter *> parameters = effect->parameters();
1140 for ( const Qt3DRender::QParameter *parameter : parameters )
1141 {
1142 if ( parameter->name() == "opacity" && parameter->value() != 1.0f )
1143 {
1144 if ( !materialEntity->components().contains( transparentLayer ) )
1145 {
1146 materialEntity->addComponent( transparentLayer );
1147 }
1148 break;
1149 }
1150 }
1151 }
1152 }
1153 }
1154
1155 if ( childMaterials.empty() )
1156 {
1157 // handle shadows for entities without materials -- eg point models
1158 newEntity->addComponent( shadowCastingEntityLayer );
1159 }
1160}
1161
1162int Qgs3DMapScene::maximumTextureSize() const
1163{
1164 QSurface *surface = mEngine->surface();
1165 QOpenGLContext context;
1166 context.create();
1167 bool success = context.makeCurrent( surface );
1168
1169 if ( success )
1170 {
1171 QOpenGLFunctions openglFunctions = QOpenGLFunctions( &context );
1172
1173 GLint size;
1174 openglFunctions.initializeOpenGLFunctions();
1175 openglFunctions.glGetIntegerv( GL_MAX_TEXTURE_SIZE, &size );
1176 return int( size );
1177 }
1178 else
1179 {
1180 return 4096; //we can't have a context to defined the max texture size, we use this reasonable value
1181 }
1182}
1183
1184void Qgs3DMapScene::addCameraViewCenterEntity( Qt3DRender::QCamera *camera )
1185{
1186 mEntityCameraViewCenter = new Qt3DCore::QEntity;
1187
1188 Qt3DCore::QTransform *trCameraViewCenter = new Qt3DCore::QTransform;
1189 mEntityCameraViewCenter->addComponent( trCameraViewCenter );
1190 connect( camera, &Qt3DRender::QCamera::viewCenterChanged, this, [trCameraViewCenter, camera] { trCameraViewCenter->setTranslation( camera->viewCenter() ); } );
1191
1192 auto materialCameraViewCenter = new QgsUnlitMaterial();
1193 materialCameraViewCenter->setColor( Qt::red );
1194 materialCameraViewCenter->setCastsShadows( false );
1195 mEntityCameraViewCenter->addComponent( materialCameraViewCenter );
1196
1197 Qt3DExtras::QSphereMesh *rendererCameraViewCenter = new Qt3DExtras::QSphereMesh;
1198 rendererCameraViewCenter->setRadius( 10 );
1199 mEntityCameraViewCenter->addComponent( rendererCameraViewCenter );
1200
1201 mEntityCameraViewCenter->setEnabled( mMap.debugFlags().testFlag( Qgis::Map3DDebugFlag::ShowCameraViewCenter ) );
1202 mEntityCameraViewCenter->setParent( this );
1203
1204 QgsFrameGraph *frameGraph = mEngine->frameGraph();
1205 mEntityCameraViewCenter->addComponent( frameGraph->forwardRenderView().renderLayer() );
1206
1207 connect( &mMap, &Qgs3DMapSettings::showCameraViewCenterChanged, this, [this] { mEntityCameraViewCenter->setEnabled( mMap.debugFlags().testFlag( Qgis::Map3DDebugFlag::ShowCameraViewCenter ) ); } );
1208}
1209
1210void Qgs3DMapScene::setSceneState( Qgs3DMapScene::SceneState state )
1211{
1212 if ( mSceneState == state )
1213 return;
1214 mSceneState = state;
1215 emit sceneStateChanged();
1216}
1217
1218void Qgs3DMapScene::updateSceneState()
1219{
1220 if ( mTerrainUpdateScheduled )
1221 {
1222 setSceneState( Updating );
1223 return;
1224 }
1225
1226 for ( Qgs3DMapSceneEntity *entity : std::as_const( mSceneEntities ) )
1227 {
1228 if ( entity->isEnabled() && entity->pendingJobsCount() > 0 )
1229 {
1230 setSceneState( Updating );
1231 return;
1232 }
1233 }
1234
1235 setSceneState( Ready );
1236}
1237
1238void Qgs3DMapScene::onBackgroundSettingsChanged()
1239{
1240 if ( mBackgroundEntity )
1241 {
1242 mBackgroundEntity->deleteLater();
1243 mBackgroundEntity = nullptr;
1244 }
1245
1246 const QgsAbstract3DMapBackgroundSettings *settings = mMap.backgroundSettings();
1247 if ( !settings )
1248 {
1249 mEnvironmentLight->setMode( QgsEnvironmentLight::Mode::Disabled );
1250 return;
1251 }
1252
1253 QgsFrameGraph *frameGraph = mEngine->frameGraph();
1254
1256 {
1257 const QgsSkyboxSettings *skyboxSettings = dynamic_cast<const QgsSkyboxSettings *>( settings );
1258 const QMap<QString, QString> faces = skyboxSettings->cubeMapFacesPaths();
1259 mBackgroundEntity
1260 = new QgsCubeFacesSkyboxEntity( skyboxSettings->cubeMapping(), faces[u"posX"_s], faces[u"posY"_s], faces[u"posZ"_s], faces[u"negX"_s], faces[u"negY"_s], faces[u"negZ"_s], skyboxSettings->environmentalLightingEnabled(), this );
1261 qgis::down_cast< QgsSkyboxEntity * >( mBackgroundEntity )->updateEnvironmentLight( mEnvironmentLight );
1262 mEnvironmentLight->setStrength( static_cast< float >( skyboxSettings->environmentalLightStrength() ) );
1263 }
1265 {
1266 const QgsFixedGradientBackgroundSettings *gradientSettings = dynamic_cast<const QgsFixedGradientBackgroundSettings *>( settings );
1267 mBackgroundEntity = new QgsGradientBackgroundEntity( gradientSettings->topColor(), gradientSettings->bottomColor(), this );
1268 mEnvironmentLight->setMode( QgsEnvironmentLight::Mode::Disabled );
1269 }
1270 else
1271 {
1272 mEnvironmentLight->setMode( QgsEnvironmentLight::Mode::Disabled );
1273 }
1274
1275 mBackgroundEntity->addComponent( frameGraph->forwardRenderView().backgroundLayer() );
1276 mBackgroundEntity->addComponent( frameGraph->forwardRenderView().renderLayer() );
1277}
1278
1279void Qgs3DMapScene::onShadowSettingsChanged()
1280{
1281 mEngine->frameGraph()->updateShadowSettings( mMap );
1282}
1283
1284void Qgs3DMapScene::onAmbientOcclusionSettingsChanged()
1285{
1286 mEngine->frameGraph()->updateAmbientOcclusionSettings( mMap.ambientOcclusionSettings() );
1287}
1288
1289void Qgs3DMapScene::onBloomSettingsChanged()
1290{
1291 mEngine->frameGraph()->updateBloomSettings( mMap.bloomSettings() );
1292}
1293
1294void Qgs3DMapScene::onColorGradingSettingsChanged()
1295{
1296 mEngine->frameGraph()->updateColorGradingSettings( mMap.colorGradingSettings() );
1297}
1298
1299void Qgs3DMapScene::onDebugDepthMapSettingsChanged()
1300{
1301 mEngine->frameGraph()->updateDebugDepthMapSettings( mMap );
1302}
1303
1304void Qgs3DMapScene::onDebugOverlayEnabledChanged()
1305{
1306 mEngine->frameGraph()->setDebugOverlayEnabled( mMap.isDebugOverlayEnabled() );
1307 mEngine->renderSettings()->setRenderPolicy( mMap.isDebugOverlayEnabled() ? Qt3DRender::QRenderSettings::Always : Qt3DRender::QRenderSettings::OnDemand );
1308}
1309
1310void Qgs3DMapScene::onEyeDomeShadingSettingsChanged()
1311{
1312 mEngine->frameGraph()->updateEyeDomeSettings( mMap );
1313}
1314
1315void Qgs3DMapScene::onMsaaEnabledChanged()
1316{
1317 mEngine->frameGraph()->setMsaaEnabled( mMap.isMsaaEnabled() );
1318}
1319
1320void Qgs3DMapScene::onShowMapOverlayChanged()
1321{
1322 const QVector<QgsPointXY> extent2D = viewFrustum2DExtent();
1323 update2DMapOverlay( extent2D );
1324}
1325
1326void Qgs3DMapScene::onCameraMovementSpeedChanged()
1327{
1328 mCameraController->setCameraMovementSpeed( mMap.cameraMovementSpeed() );
1329}
1330
1331void Qgs3DMapScene::onCameraNavigationModeChanged()
1332{
1333 mCameraController->setCameraNavigationMode( mMap.cameraNavigationMode() );
1334}
1335
1337{
1338 QVector<QString> notParsedLayers;
1339 Qgs3DSceneExporter exporter;
1340
1341 exporter.setTerrainResolution( exportSettings.terrrainResolution() );
1342 exporter.setSmoothEdges( exportSettings.smoothEdges() );
1343 exporter.setExportNormals( exportSettings.exportNormals() );
1344 exporter.setExportTextures( exportSettings.exportTextures() );
1345 exporter.setTerrainTextureResolution( exportSettings.terrainTextureResolution() );
1346 exporter.setScale( exportSettings.scale() );
1347 exporter.setTerrainExportEnabled( exportSettings.terrainExportEnabled() );
1348
1349 for ( auto it = mLayerEntities.constBegin(); it != mLayerEntities.constEnd(); ++it )
1350 {
1351 QgsMapLayer *layer = it.key();
1352 Qt3DCore::QEntity *rootEntity = it.value();
1353 Qgis::LayerType layerType = layer->type();
1354 switch ( layerType )
1355 {
1357 if ( !exporter.parseVectorLayerEntity( rootEntity, qobject_cast<QgsVectorLayer *>( layer ) ) )
1358 notParsedLayers.push_back( layer->name() );
1359 break;
1368 notParsedLayers.push_back( layer->name() );
1369 break;
1370 }
1371 }
1372
1373 if ( mTerrain )
1374 exporter.parseTerrain( mTerrain, "Terrain" );
1375
1376 const bool sceneSaved = exporter.save( exportSettings.sceneName(), exportSettings.sceneFolderPath(), exportSettings.exportFormat() );
1377 if ( !sceneSaved )
1378 {
1379 return false;
1380 }
1381
1382 if ( !notParsedLayers.empty() )
1383 {
1384 QString message = tr( "The following layers were not exported:" ) + "\n";
1385 for ( const QString &layerName : notParsedLayers )
1386 message += layerName + "\n";
1387 QgsMessageOutput::showMessage( tr( "3D exporter warning" ), message, Qgis::StringFormat::PlainText );
1388 }
1389
1390 return true;
1391}
1392
1393QVector<const QgsChunkNode *> Qgs3DMapScene::getLayerActiveChunkNodes( QgsMapLayer *layer )
1394{
1395 QVector<const QgsChunkNode *> chunks;
1396 if ( !mLayerEntities.contains( layer ) )
1397 return chunks;
1398 if ( QgsChunkedEntity *c = qobject_cast<QgsChunkedEntity *>( mLayerEntities[layer] ) )
1399 {
1400 const QList<QgsChunkNode *> activeNodes = c->activeNodes();
1401 for ( QgsChunkNode *n : activeNodes )
1402 chunks.push_back( n );
1403 }
1404 return chunks;
1405}
1406
1408{
1409 return mMap.extent();
1410}
1411
1412QgsDoubleRange Qgs3DMapScene::elevationRange( const bool ignoreTerrain ) const
1413{
1414 double zMin = std::numeric_limits<double>::max();
1415 double zMax = std::numeric_limits<double>::lowest();
1416 if ( mMap.terrainRenderingEnabled() && mTerrain && !ignoreTerrain )
1417 {
1418 const QgsBox3D box3D = mTerrain->rootNode()->box3D();
1419 zMin = std::min( zMin, box3D.zMinimum() );
1420 zMax = std::max( zMax, box3D.zMaximum() );
1421 }
1422
1423 for ( auto it = mLayerEntities.constBegin(); it != mLayerEntities.constEnd(); it++ )
1424 {
1425 QgsMapLayer *layer = it.key();
1426 switch ( layer->type() )
1427 {
1429 {
1430 QgsPointCloudLayer *pcl = qobject_cast<QgsPointCloudLayer *>( layer );
1431 QgsDoubleRange zRange = pcl->elevationProperties()->calculateZRange( pcl );
1432 zMin = std::min( zMin, zRange.lower() );
1433 zMax = std::max( zMax, zRange.upper() );
1434 break;
1435 }
1437 {
1438 QgsMeshLayer *meshLayer = qobject_cast<QgsMeshLayer *>( layer );
1439 QgsAbstract3DRenderer *renderer3D = meshLayer->renderer3D();
1440 if ( renderer3D )
1441 {
1442 QgsMeshLayer3DRenderer *meshLayerRenderer = static_cast<QgsMeshLayer3DRenderer *>( renderer3D );
1443 const int verticalGroupDatasetIndex = meshLayerRenderer->symbol()->verticalDatasetGroupIndex();
1444 const QgsMeshDatasetGroupMetadata verticalGroupMetadata = meshLayer->datasetGroupMetadata( verticalGroupDatasetIndex );
1445 const double verticalScale = meshLayerRenderer->symbol()->verticalScale();
1446 zMin = std::min( zMin, verticalGroupMetadata.minimum() * verticalScale );
1447 zMax = std::max( zMax, verticalGroupMetadata.maximum() * verticalScale );
1448 }
1449 break;
1450 }
1452 {
1453 QgsTiledSceneLayer *sceneLayer = qobject_cast<QgsTiledSceneLayer *>( layer );
1454 const QgsDoubleRange zRange = sceneLayer->elevationProperties()->calculateZRange( sceneLayer );
1455 if ( !zRange.isInfinite() && !zRange.isEmpty() )
1456 {
1457 zMin = std::min( zMin, zRange.lower() );
1458 zMax = std::max( zMax, zRange.upper() );
1459 }
1460 break;
1461 }
1468 break;
1469 }
1470 }
1471 const QgsDoubleRange zRange( std::min( zMin, std::numeric_limits<double>::max() ), std::max( zMax, std::numeric_limits<double>::lowest() ) );
1472 return zRange.isEmpty() ? QgsDoubleRange() : zRange;
1473}
1474
1475QMap<QString, Qgs3DMapScene *> Qgs3DMapScene::openScenes()
1476{
1477 return sOpenScenesFunction();
1478}
1479
1480void Qgs3DMapScene::addCameraRotationCenterEntity( QgsCameraController *controller )
1481{
1482 mEntityRotationCenter = new Qt3DCore::QEntity;
1483
1484 Qt3DCore::QTransform *trRotationCenter = new Qt3DCore::QTransform;
1485 mEntityRotationCenter->addComponent( trRotationCenter );
1486
1487 auto materialRotationCenter = new QgsUnlitMaterial();
1488 materialRotationCenter->setColor( Qt::blue );
1489 materialRotationCenter->setCastsShadows( false );
1490
1491 mEntityRotationCenter->addComponent( materialRotationCenter );
1492 Qt3DExtras::QSphereMesh *rendererRotationCenter = new Qt3DExtras::QSphereMesh;
1493 rendererRotationCenter->setRadius( 10 );
1494 mEntityRotationCenter->addComponent( rendererRotationCenter );
1495 mEntityRotationCenter->setEnabled( false );
1496 mEntityRotationCenter->setParent( this );
1497
1498 QgsFrameGraph *frameGraph = mEngine->frameGraph();
1499 mEntityRotationCenter->addComponent( frameGraph->forwardRenderView().renderLayer() );
1500
1501 connect( controller, &QgsCameraController::cameraRotationCenterChanged, this, [trRotationCenter]( QVector3D center ) { trRotationCenter->setTranslation( center ); } );
1502
1503 connect( &mMap, &Qgs3DMapSettings::showCameraRotationCenterChanged, this, [this] {
1504 mEntityRotationCenter->setEnabled( mMap.debugFlags().testFlag( Qgis::Map3DDebugFlag::ShowCameraRotationCenter ) );
1505 } );
1506}
1507
1508void Qgs3DMapScene::on3DAxisSettingsChanged()
1509{
1510 if ( m3DAxis )
1511 {
1512 m3DAxis->onAxisSettingsChanged();
1513 }
1514 else
1515 {
1516 if ( QgsWindow3DEngine *engine = dynamic_cast<QgsWindow3DEngine *>( mEngine ) )
1517 {
1518 m3DAxis = new Qgs3DAxis( static_cast<Qgs3DMapCanvas *>( engine->window() ), engine->root(), this, mCameraController, &mMap );
1519 }
1520 }
1521}
1522
1523void Qgs3DMapScene::onOriginChanged()
1524{
1525 const QList<QgsGeoTransform *> geoTransforms = findChildren<QgsGeoTransform *>();
1526 for ( QgsGeoTransform *transform : geoTransforms )
1527 {
1528 transform->setOrigin( mMap.origin() );
1529 }
1530
1531 Qt3DCore::QEntity *parentEntity = mEngine->frameGraph()->rubberBandRenderView().rubberBandEntity();
1532 const QList<QgsGeoTransform *> rubberBandGeoTransforms = parentEntity->findChildren<QgsGeoTransform *>();
1533 for ( QgsGeoTransform *transform : rubberBandGeoTransforms )
1534 {
1535 transform->setOrigin( mMap.origin() );
1536 }
1537
1538 const QgsVector3D oldOrigin = mCameraController->origin();
1539 mCameraController->setOrigin( mMap.origin() );
1540
1541 if ( !mClipPlanesEquations.isEmpty() )
1542 {
1543 // how the math works - for a plane defined as (a,b,c,d), only "d" changes when
1544 // moving the origin - the plane normal vector (a,b,c) stays the same.
1545 // - line equation for old shift: a * (x - x0) + b * (y - y0) + c * (z - z0) + d0 = 0
1546 // - line equation for new shift: a * (x - x1) + b * (y - y1) + c * (z - z1) + d1 = 0
1547 // - we solve for d1:
1548 // d1 = a * (x1 - x0) + b * (y1 - y0) + c * (z1 - z0) + d0
1549
1550 QList<QVector4D> newPlanes;
1551 QgsVector3D originShift = mMap.origin() - oldOrigin;
1552 for ( QVector4D plane : std::as_const( mClipPlanesEquations ) )
1553 {
1554 plane.setW( originShift.x() * plane.x() + originShift.y() * plane.y() + originShift.z() * plane.z() + plane.w() );
1555 newPlanes.append( plane );
1556 }
1557 enableClipping( newPlanes );
1558 }
1559}
1560
1561void Qgs3DMapScene::handleClippingOnEntity( QEntity *entity ) const
1562{
1563 if ( mClipPlanesEquations.isEmpty() ) // no clip plane equations, disable clipping
1564 {
1565 for ( QgsMaterial *material : entity->componentsOfType<QgsMaterial>() )
1566 {
1567 material->disableClipping();
1568 }
1569 }
1570 else // enable clipping
1571 {
1572 for ( QgsMaterial *material : entity->componentsOfType<QgsMaterial>() )
1573 {
1574 material->enableClipping( mClipPlanesEquations );
1575 }
1576 }
1577
1578 // recursive call
1579 // enable or disable clipping on the children accordingly
1580 for ( QObject *child : entity->children() )
1581 {
1582 Qt3DCore::QEntity *childEntity = qobject_cast<Qt3DCore::QEntity *>( child );
1583 if ( childEntity )
1584 {
1585 handleClippingOnEntity( childEntity );
1586 }
1587 }
1588}
1589
1590void Qgs3DMapScene::handleClippingOnAllEntities() const
1591{
1592 // Need to loop mLayerEntities instead of mSceneEntities to handle entities
1593 // which do no inherit from Qgs3DMapSceneEntity. For example, mesh entities.
1594 for ( auto it = mLayerEntities.constBegin(); it != mLayerEntities.constEnd(); ++it )
1595 {
1596 handleClippingOnEntity( it.value() );
1597 }
1598 if ( mTerrain )
1599 {
1600 handleClippingOnEntity( mTerrain );
1601 }
1602 if ( mGlobe )
1603 {
1604 handleClippingOnEntity( mGlobe );
1605 }
1606}
1607
1609{
1610 if ( clipPlaneEquations.size() > mMaxClipPlanes )
1611 {
1612 QgsDebugMsgLevel( u"Qgs3DMapScene::enableClipping: it is not possible to use more than %1 clipping planes."_s.arg( mMaxClipPlanes ), 2 );
1613 }
1614 mClipPlanesEquations = clipPlaneEquations.mid( 0, mMaxClipPlanes );
1615
1616 // enable the clip planes on the framegraph
1617 mEngine->frameGraph()->addClipPlanes( clipPlaneEquations.size() );
1618
1619 // Enable the clip planes for the material of each entity.
1620 handleClippingOnAllEntities();
1621}
1622
1624{
1625 mClipPlanesEquations.clear();
1626
1627 // disable the clip planes on the framegraph
1628 mEngine->frameGraph()->removeClipPlanes();
1629
1630 // Disable the clip planes for the material of each entity.
1631 handleClippingOnAllEntities();
1632}
1633
1634void Qgs3DMapScene::onStopUpdatesChanged()
1635{
1636 mSceneUpdatesEnabled = !mMap.stopUpdates();
1637}
1638
1639void Qgs3DMapScene::schedule2DMapOverlayUpdate()
1640{
1641 // Start the overlay update timer if overlay is active and not already running
1642 if ( mMap.is2DMapOverlayEnabled() && mOverlayUpdateTimer && !mOverlayUpdateTimer->isActive() )
1643 {
1644 mOverlayUpdateTimer->start();
1645 }
1646}
1647
1648void Qgs3DMapScene::applyPendingOverlayUpdate()
1649{
1650 if ( mMap.is2DMapOverlayEnabled() )
1651 {
1652 const QVector<QgsPointXY> extent2D = viewFrustum2DExtent();
1653 update2DMapOverlay( extent2D );
1654 }
1655}
@ DistinctTextureSkybox
Skybox with 6 distinct textures for different faces.
Definition qgis.h:4486
@ FixedGradientBackground
Two color gradient, fixed in place.
Definition qgis.h:4485
@ Model
Model.
Definition qgis.h:4395
@ ShowCameraViewCenter
Shows the camera's view center as a sphere.
Definition qgis.h:4357
@ ShowFPS
Shows the frames per second (FPS).
Definition qgis.h:4360
@ ShowCameraRotationCenter
Shows the camera's rotation center as a sphere.
Definition qgis.h:4358
@ ShowTerrainBoundingBoxes
Displays bounding boxes of terrain tiles.
Definition qgis.h:4355
@ Point
Points.
Definition qgis.h:380
@ PlainText
Text message.
Definition qgis.h:176
LayerType
Types of layers that can be added to a map.
Definition qgis.h:206
@ Group
Composite group layer. Added in QGIS 3.24.
Definition qgis.h:214
@ Plugin
Plugin based layer.
Definition qgis.h:209
@ TiledScene
Tiled scene layer. Added in QGIS 3.34.
Definition qgis.h:215
@ Annotation
Contains freeform, georeferenced annotations. Added in QGIS 3.16.
Definition qgis.h:212
@ Vector
Vector layer.
Definition qgis.h:207
@ VectorTile
Vector tile layer. Added in QGIS 3.14.
Definition qgis.h:211
@ Mesh
Mesh layer. Added in QGIS 3.2.
Definition qgis.h:210
@ Raster
Raster layer.
Definition qgis.h:208
@ PointCloud
Point cloud layer. Added in QGIS 3.18.
Definition qgis.h:213
@ Orthographic
Orthogonal projection.
Definition qgis.h:4374
@ Perspective
Perspective projection.
Definition qgis.h:4375
@ Globe
Scene is represented as a globe using a geocentric CRS.
Definition qgis.h:4526
@ Local
Local scene based on a projected CRS.
Definition qgis.h:4525
Manages the various settings the user can choose from when exporting a 3D scene.
bool exportNormals() const
Returns whether normals will be exported.
int terrrainResolution() const
Returns the terrain resolution.
QString sceneFolderPath() const
Returns the scene folder path.
float scale() const
Returns the scale of the exported model.
int terrainTextureResolution() const
Returns the terrain texture resolution.
bool terrainExportEnabled() const
Returns whether terrain export is enabled.
QString sceneName() const
Returns the scene name.
bool smoothEdges() const
Returns whether triangles edges will look smooth.
bool exportTextures() const
Returns whether textures will be exported.
Qgis::Export3DSceneFormat exportFormat() const
Returns the export format for the 3D scene.
void viewed2DExtentFrom3DChanged(QVector< QgsPointXY > extent)
Emitted when the viewed 2D extent seen by the 3D camera has changed.
QList< QVector4D > clipPlaneEquations() const
Returns list of clipping planes if clipping is enabled, otherwise an empty list.
static std::function< QMap< QString, Qgs3DMapScene * >()> sOpenScenesFunction
Static function for returning open 3D map scenes.
void fpsCountChanged(float fpsCount)
Emitted when the FPS count changes.
void setViewFrom2DExtent(const QgsRectangle &extent)
Resets camera view to show the extent extent (top view).
void disableClipping()
Disables OpenGL clipping.
QVector< const QgsChunkNode * > getLayerActiveChunkNodes(QgsMapLayer *layer) SIP_SKIP
Returns the active chunk nodes of layer.
void gpuMemoryLimitReached()
Emitted when one of the entities reaches its GPU memory limit and it is not possible to lower the GPU...
static Q_DECL_DEPRECATED QMap< QString, Qgs3DMapScene * > openScenes() SIP_DEPRECATED
Returns a map of 3D map scenes (by name) open in the QGIS application.
QgsCameraController * cameraController() const
Returns camera controller.
SceneState
Enumeration of possible states of the 3D scene.
@ Ready
The scene is fully loaded/updated.
@ Updating
The scene is still being loaded/updated.
bool exportScene(const Qgs3DMapExportSettings &exportSettings)
Exports the scene according to the scene export settings Returns false if the operation failed.
int totalPendingJobsCount() const
Returns number of pending jobs for all chunked entities.
QList< QgsMapLayer * > layers() const SIP_SKIP
Returns the layers that contain chunked entities.
void addSceneEntity(Qgs3DMapSceneEntity *entity) SIP_SKIP
Adds a 3D map scene entity to the scene.
void updateTemporal()
Updates the temporale entities.
void totalPendingJobsCountChanged()
Emitted when the total number of pending jobs changes.
Qgs3DMapScene(Qgs3DMapSettings &map, QgsAbstract3DEngine *engine) SIP_SKIP
Constructs a 3D scene based on map settings and Qt 3D renderer configuration.
void fpsCounterEnabledChanged(bool fpsCounterEnabled)
Emitted when the FPS counter is activated or deactivated.
void removeSceneEntity(Qgs3DMapSceneEntity *entity) SIP_SKIP
Removes a 3D scene entity for the scene.
QgsDoubleRange elevationRange(bool ignoreTerrain=false) const
Returns the scene's elevation range.
QgsRectangle sceneExtent() const
Returns the scene extent in the map's CRS.
void sceneStateChanged()
Emitted when the scene's state has changed.
QgsAbstract3DEngine * engine() const SIP_SKIP
Returns the abstract 3D engine.
QVector< QgsPointXY > viewFrustum2DExtent() const
Calculates the 2D extent viewed by the 3D camera as the vertices of the viewed trapezoid.
void enableClipping(const QList< QVector4D > &clipPlaneEquations)
Enables OpenGL clipping based on the planes equations defined in clipPlaneEquations.
void terrainEntityChanged()
Emitted when the current terrain entity is replaced by a new one.
void viewZoomFull()
Resets camera view to show the whole scene (top view).
double worldSpaceError(double epsilon, double distance) const
Given screen error (in pixels) and distance from camera (in 3D world coordinates),...
Definition of the world.
void extentChanged()
Emitted when the 3d view's 2d extent has changed.
void originChanged()
Emitted when the world's origin point has been shifted.
void eyeDomeLightingDistanceChanged()
Emitted when the eye dome lighting distance has changed.
void terrainShadingChanged()
Emitted when terrain shading enabled flag or terrain shading material has changed.
void bloomSettingsChanged()
Emitted when the bloom lighting effect settings are changed.
void debugDepthMapSettingsChanged()
Emitted when depth map debugging has changed.
void backgroundSettingsChanged()
Emitted when background settings are changed.
void backgroundColorChanged()
Emitted when the background color has changed.
void showCameraRotationCenterChanged()
Emitted when the flag whether camera's rotation center is shown has changed.
void cameraNavigationModeChanged()
Emitted when the camera navigation mode was changed.
void shadowSettingsChanged()
Emitted when shadow rendering settings are changed.
void show2DMapOverlayChanged()
Emitted when the 2D map overlay is enabled or disabled.
bool stopUpdates() const
Returns whether the scene updates on camera movement.
void eyeDomeLightingEnabledChanged()
Emitted when the flag whether eye dome lighting is used has changed.
void debugOverlayEnabledChanged(bool debugOverlayEnabled)
Emitted when the debug overaly is enabled or disabled.
void setOrigin(const QgsVector3D &origin)
Sets coordinates in map CRS at which our 3D world has origin (0,0,0).
void msaaEnabledChanged()
Emitted when the MSAA enabled flag has changed.
void projectionTypeChanged()
Emitted when the camera lens projection type changes.
void stopUpdatesChanged()
Emitted when the flag whether to keep updating scene has changed.
void lightSourcesChanged()
Emitted when any of the light source settings in the map changes.
void showLightSourceOriginsChanged()
Emitted when the flag whether light source origins are shown has changed.
void terrainSettingsChanged()
Emitted when the terrain settings are changed.
void colorGradingSettingsChanged()
Emitted when the color grading settings are changed.
void fpsCounterEnabledChanged(bool fpsCounterEnabled)
Emitted when the FPS counter is enabled or disabled.
void axisSettingsChanged()
Emitted when 3d axis rendering settings are changed.
void viewFrustumVisualizationEnabledChanged()
Emitted when the camera's view frustum visualization on the main 2D map canvas is enabled or disabled...
void ambientOcclusionSettingsChanged()
Emitted when ambient occlusion rendering settings are changed.
void layersChanged()
Emitted when the list of map layers for 3d rendering has changed.
void eyeDomeLightingStrengthChanged()
Emitted when the eye dome lighting strength has changed.
void cameraMovementSpeedChanged()
Emitted when the camera movement speed was changed.
void fieldOfViewChanged()
Emitted when the camera lens field of view changes.
void terrainGeneratorChanged()
Emitted when the terrain generator has changed.
void showCameraViewCenterChanged()
Emitted when the flag whether camera's view center is shown has changed.
QgsVector3D origin() const
Returns coordinates in map CRS at which 3D scene has origin (0,0,0).
Entity that handles the exporting of 3D scenes.
void setExportTextures(bool exportTextures)
Sets whether the textures will be exported.
void parseTerrain(QgsTerrainEntity *terrain, const QString &layer)
Creates terrain export objects from the terrain entity.
void setTerrainResolution(int resolution)
Sets the terrain resolution.
void setTerrainTextureResolution(int resolution)
Sets the terrain texture resolution.
bool parseVectorLayerEntity(Qt3DCore::QEntity *entity, QgsVectorLayer *layer)
Creates necessary export objects from entity if it represents valid vector layer entity Returns false...
void setScale(float scale)
Sets the scale of the exported 3D model.
bool save(QString sceneName, QString sceneFolderPath, const Qgis::Export3DSceneFormat &exportFormat=Qgis::Export3DSceneFormat::Obj, int precision=6) const
Saves the scene to a file Returns false if the operation failed.
void setExportNormals(bool exportNormals)
Sets whether the normals will be exported.
void setSmoothEdges(bool smoothEdges)
Sets whether the triangles will look smooth.
void setTerrainExportEnabled(bool enabled)
Sets whether terrain export is enabled.
static int maxZoomLevel(double tile0width, double tileResolution, double maxError)
Calculates the highest needed zoom level for tiles in quad-tree given width of the base tile (zoom le...
static QgsAABB mapToWorldExtent(const QgsRectangle &extent, double zMin, double zMax, const QgsVector3D &mapOrigin)
Converts map extent to axis aligned bounding box in 3D world coordinates.
static void computeBoundingBoxNearFarPlanes(const QgsAABB &bbox, const QMatrix4x4 &viewMatrix, float &fnear, float &ffar)
This routine computes nearPlane farPlane from the closest and farthest corners point of bounding box ...
static QVector3D screenPointToWorldPos(const QPoint &screenPoint, double depth, const QSize &screenSize, Qt3DRender::QCamera *camera)
Converts the clicked mouse position to the corresponding 3D world coordinates.
static int openGlMaxClipPlanes(QSurface *surface)
Gets the maximum number of clip planes that can be used.
Base class for 3D engine implementation.
void sizeChanged()
Emitted after a call to setSize().
virtual Qt3DRender::QCamera * camera()=0
Returns pointer to the engine's camera entity.
QgsFrameGraph * frameGraph()
Returns the shadow rendering frame graph object used to render the scene.
virtual Qgis::Map3DBackgroundType type() const =0
Returns the unique type for this background settings class.
Base class for all renderers that participate in 3D views.
virtual QString type() const =0
Returns unique identifier of the renderer class (used to identify subclass).
virtual Qt3DCore::QEntity * createEntity(Qgs3DMapSettings *map) const =0
Returns a 3D entity that will be used to show renderer's data in 3D scene.
virtual void setEnabled(bool enable)
Enable or disable via enable the render view sub tree.
Base class for 3D renderers that are based on vector layers.
static QgsSourceCache * sourceCache()
Returns the application's source cache, used for caching embedded and remote source strings as local ...
A 3-dimensional box composed of x, y, z coordinates.
Definition qgsbox3d.h:45
double zMaximum() const
Returns the maximum z value.
Definition qgsbox3d.h:268
double zMinimum() const
Returns the minimum z value.
Definition qgsbox3d.h:261
Object that controls camera movement based on user input.
Qt3DRender::QCamera * camera() const
Returns camera that is being controlled.
void requestDepthBufferCapture()
Emitted to ask for the depth buffer image.
void cameraChanged()
Emitted when camera has been updated.
void depthBufferReady()
Emitted after the depth buffer has been captured and is ready to sample.
void cameraRotationCenterChanged(QVector3D position)
Emitted when the camera rotation center changes.
QgsRange which stores a range of double values.
Definition qgsrange.h:217
bool isInfinite() const
Returns true if the range consists of all possible values.
Definition qgsrange.h:266
An environment light entity.
@ Disabled
No environment lighting.
QColor topColor() const
Returns the color at the top of the gradient.
QColor bottomColor() const
Returns the color at the bottom of the gradient.
Qt3DRender::QLayer * renderLayer()
Returns a layer object used to indicate that the object is transparent.
Qt3DRender::QLayer * transparentObjectLayer()
Returns a layer object used to indicate that the object is transparent.
Qt3DRender::QLayer * backgroundLayer()
Returns a layer object used for skybox and background gradient entities.
Container class that holds different objects related to frame graphs of 3D scenes.
void updateShadowSettings(const Qgs3DMapSettings &mapSettings)
Updates shadow bias, light and texture size according to shadowSettings and lightSources.
QgsHighlightsRenderView & highlightsRenderView()
Returns the highlights renderview, used for rendering highlight overlays of identified features.
QgsForwardRenderView & forwardRenderView()
Returns forward renderview.
QgsOverlayTextureRenderView & overlayTextureRenderView()
Returns overlay texture renderview.
QgsShadowRenderView & shadowRenderView()
Returns shadow renderview.
Qt3DRender::QLayer * highlightsLayer()
Returns a layer that should be attached to entities meant to be rendered by QgsHighlightsRenderView.
virtual QgsDoubleRange calculateZRange(QgsMapLayer *layer) const
Attempts to calculate the overall elevation or z range for the specified layer, using the settings de...
Base class for storage of map layer temporal properties.
Base class for all map layer types.
Definition qgsmaplayer.h:83
QString name
Definition qgsmaplayer.h:87
QgsAbstract3DRenderer * renderer3D() const
Returns 3D renderer associated with the layer.
void request3DUpdate()
Signal emitted when a layer requires an update in any 3D maps.
void renderer3DChanged()
Signal emitted when 3D renderer associated with the layer has changed.
Qgis::LayerType type
Definition qgsmaplayer.h:93
void rendererChanged()
Signal emitted when renderer is changed.
virtual QgsMapLayerTemporalProperties * temporalProperties()
Returns the layer's temporal properties.
void layerModified()
Emitted when modifications has been done on layer.
double verticalScale() const
Returns mesh vertical scale.
int verticalDatasetGroupIndex() const
Returns the index of the dataset group that will be used to render the vertical component of the 3D m...
void setMaximumTextureSize(int maximumTextureSize)
Sets the maximum texture size supported by the hardware Used to store the GL_MAX_TEXTURE_SIZE value t...
QgsMesh3DSymbol * clone() const override SIP_FACTORY
Returns a new instance of the symbol with the same settings.
A collection of dataset group metadata such as whether the data is vector or scalar,...
double minimum() const
Returns minimum scalar value/vector magnitude present for whole dataset group.
double maximum() const
Returns maximum scalar value/vector magnitude present for whole dataset group.
3D renderer that renders all mesh triangles of a mesh layer.
void setSymbol(QgsMesh3DSymbol *symbol)
Sets 3D symbol associated with the renderer.
const QgsMesh3DSymbol * symbol() const
Returns 3D symbol associated with the renderer.
void setLayer(QgsMeshLayer *layer)
Sets vector layer associated with the renderer.
Represents a mesh layer supporting display of data on structured or unstructured meshes.
QgsMeshDatasetGroupMetadata datasetGroupMetadata(const QgsMeshDatasetIndex &index) const
Returns the dataset groups metadata.
virtual void showMessage(bool blocking=true)=0
display the message to the user and deletes itself
3D symbol that draws point geometries as 3D objects using one of the predefined shapes.
Qgis::Point3DShape shape() const
Returns 3D shape for points.
QVariant shapeProperty(const QString &property) const
Returns the value for a specific shape property.
void setLayer(QgsPointCloudLayer *layer)
Sets point cloud layer associated with the renderer.
Represents a map layer supporting display of point clouds.
QgsMapLayerElevationProperties * elevationProperties() override
Returns the layer's elevation properties.
void subsetStringChanged()
Emitted when the layer's subset string has changed.
Represents a 2D point.
Definition qgspointxy.h:62
double y
Definition qgspointxy.h:66
double x
Definition qgspointxy.h:65
T lower() const
Returns the lower bound of the range.
Definition qgsrange.h:79
T upper() const
Returns the upper bound of the range.
Definition qgsrange.h:86
bool isEmpty() const
Returns true if the range is empty, ie the lower bound equals (or exceeds) the upper bound and either...
Definition qgsrange.h:126
A rectangle specified with double values.
double xMinimum
double yMinimum
double xMaximum
double yMaximum
static QgsRectangle fromCenterAndSize(const QgsPointXY &center, double width, double height)
Creates a new rectangle, given the specified center point and width and height.
QgsPointXY center
A child rule for a QgsRuleBased3DRenderer.
Rule-based 3D renderer.
QList< QgsRuleBased3DRenderer::Rule * > RuleList
Qt3DRender::QLayer * entityCastingShadowsLayer() const
Returns the layer to be used by entities to be included in this renderview.
bool environmentalLightingEnabled() const
Returns true if the skybox should generate environmental lighting effects.
QMap< QString, QString > cubeMapFacesPaths() const
Returns a map containing the path of each texture specified by the user.
double environmentalLightStrength() const
Returns the environmental light strength, as a factor between 0 and 1.
Qgis::SkyboxCubeMapping cubeMapping() const
Returns the cube face mapping scheme.
void remoteSourceFetched(const QString &url)
Emitted when the cache has finished retrieving a 3D model from a remote url.
void setLayer(QgsTiledSceneLayer *layer)
Sets tiled scene layer associated with the renderer.
Represents a map layer supporting display of tiled scene objects.
QgsMapLayerElevationProperties * elevationProperties() override
Returns the layer's elevation properties.
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
QString toString(int precision=17) const
Returns a string representation of the 3D vector.
double x() const
Returns X coordinate.
Definition qgsvector3d.h:58
3D renderer that renders all features of a vector layer with the same 3D symbol.
const QgsAbstract3DSymbol * symbol() const
Returns 3D symbol associated with the renderer.
Represents a vector layer which manages a vector based dataset.
void subsetStringChanged()
Emitted when the layer's subset string has changed.
Q_INVOKABLE Qgis::GeometryType geometryType() const
Returns point, line or polygon.
void selectionChanged(const QgsFeatureIds &selected, const QgsFeatureIds &deselected, bool clearAndSelect)
Emitted when selection was changed.
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
bool qgsFloatNear(float a, float b, float epsilon=4 *FLT_EPSILON)
Compare two floats (but allow some difference).
Definition qgis.h:7553
QList< Qgs3DRendererCategory > Qgs3DCategoryList
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:80
#define QgsDebugError(str)
Definition qgslogger.h:71
constexpr QObjectUniquePtr< Tp > make_qobject_unique(Args &&...args)
Create an object owned by a QObjectUniquePtr.