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