diff --git a/src/3d/qgs3dmapscene.cpp b/src/3d/qgs3dmapscene.cpp index baa60913ef0c..41690e9d6161 100644 --- a/src/3d/qgs3dmapscene.cpp +++ b/src/3d/qgs3dmapscene.cpp @@ -310,15 +310,7 @@ void Qgs3DMapScene::onCameraChanged() } updateScene( true ); - bool changedCameraPlanes = updateCameraNearFarPlanes(); - - if ( changedCameraPlanes ) - { - // repeat update of entities - because we have updated camera's near/far planes, - // the active nodes may have changed as well - updateScene( true ); - updateCameraNearFarPlanes(); - } + updateCameraNearFarPlanes(); onShadowSettingsChanged(); @@ -342,7 +334,22 @@ void Qgs3DMapScene::updateScene( bool forceUpdate ) sceneContext.cameraPos = camera->position(); const QSize size = mEngine->size(); sceneContext.screenSizePx = std::max( size.width(), size.height() ); // TODO: is this correct? - sceneContext.viewProjectionMatrix = camera->projectionMatrix() * camera->viewMatrix(); + + // Make our own projection matrix so that frustum culling done by the + // entities isn't dependent on the current near/far planes, which would then + // require multiple steps to stabilize. + // The matrix is constructed just like in QMatrix4x4::perspective(), but for + // all elements involving the near and far plane, the limit of the expression + // with the far plane going to infinity is taken. + float fovRadians = ( camera->fieldOfView() / 2.0f ) * static_cast( M_PI ) / 180.0f; + float fovCotan = std::cos( fovRadians ) / std::sin( fovRadians ); + QMatrix4x4 projMatrix( + fovCotan / camera->aspectRatio(), 0, 0, 0, + 0, fovCotan, 0, 0, + 0, 0, -1, -2, + 0, 0, -1, 0 + ); + sceneContext.viewProjectionMatrix = projMatrix * camera->viewMatrix(); for ( Qgs3DMapSceneEntity *entity : std::as_const( mSceneEntities ) ) diff --git a/src/3d/qgsvirtualpointcloudentity_p.cpp b/src/3d/qgsvirtualpointcloudentity_p.cpp index f2afd4b24e48..0c9dea1b7a25 100644 --- a/src/3d/qgsvirtualpointcloudentity_p.cpp +++ b/src/3d/qgsvirtualpointcloudentity_p.cpp @@ -130,9 +130,18 @@ void QgsVirtualPointCloudEntity::handleSceneUpdate( const SceneContext &sceneCon const QVector subIndexes = provider()->subIndexes(); for ( int i = 0; i < subIndexes.size(); ++i ) { + // If the chunked entity needs an update, do it even if it's occluded, + // since otherwise we'd return needsUpdate() == true until it comes into + // view again. + bool needsUpdate = mChunkedEntitiesMap.contains( i ) && mChunkedEntitiesMap[i]->needsUpdate(); + const QgsBox3D &box3D = mBboxes.at( i ); - if ( box3D.isEmpty() ) + if ( !needsUpdate && box3D.isEmpty() ) + continue; + + QgsAABB aabb = QgsAABB::fromBox3D( box3D, mMapSettings->origin() ); + if ( !needsUpdate && Qgs3DUtils::isCullable( aabb, sceneContext.viewProjectionMatrix ) ) continue; // magic number 256 is the common span value for a COPC root node @@ -181,7 +190,7 @@ QgsRange QgsVirtualPointCloudEntity::getNearFarPlaneRange( const QMatrix4 { for ( const QgsBox3D &box : mBboxes ) { - QgsAABB aabb = QgsAABB::fromBox3D( box, mBboxesEntity->vertexDataOrigin() ); + QgsAABB aabb = QgsAABB::fromBox3D( box, mMapSettings->origin() ); float bboxfnear; float bboxffar; Qgs3DUtils::computeBoundingBoxNearFarPlanes( aabb, viewMatrix, bboxfnear, bboxffar );