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