diff --git a/src/3d/symbols/qgspointcloud3dsymbol_p.cpp b/src/3d/symbols/qgspointcloud3dsymbol_p.cpp index 3475a41efd89..53c5ace2335f 100644 --- a/src/3d/symbols/qgspointcloud3dsymbol_p.cpp +++ b/src/3d/symbols/qgspointcloud3dsymbol_p.cpp @@ -17,6 +17,8 @@ ///@cond PRIVATE +#include "qgsgeometry.h" +#include "qgsgeos.h" #include "qgspointcloud3dsymbol.h" #include "qgspointcloudattribute.h" #include "qgspointcloudrequest.h" @@ -583,6 +585,17 @@ void QgsSingleColorPointCloud3DSymbolHandler::processNode( QgsPointCloudIndex *p const QgsCoordinateTransform coordinateTransform = context.coordinateTransform(); bool alreadyPrintedDebug = false; + // The request was made on the boundingbox of the real extent + // It is necessary to ensure that the features intersect + // with extentGeom if the scene rotation is not 0. + QgsGeos extentIntersectionGeos( nullptr ); + if ( context.map().zRotation() != 0.0 ) + { + QgsGeometry extentIntersection = context.map().rotatedExtent().intersection( QgsGeometry::fromRect( context.extent() ) ); + extentIntersectionGeos = QgsGeos( extentIntersection.constGet() ); + extentIntersectionGeos.prepareGeometry(); + } + for ( int i = 0; i < count; ++i ) { if ( context.isCanceled() ) @@ -595,6 +608,12 @@ void QgsSingleColorPointCloud3DSymbolHandler::processNode( QgsPointCloudIndex *p double x = blockOffset.x() + blockScale.x() * ix; double y = blockOffset.y() + blockScale.y() * iy; double z = ( blockOffset.z() + blockScale.z() * iz ) * zValueScale + zValueOffset; + + if ( context.map().zRotation() != 0.0 && !extentIntersectionGeos.intersects( x, y ) ) + { + continue; + } + try { coordinateTransform.transformInPlace( x, y, z ); @@ -703,6 +722,17 @@ void QgsColorRampPointCloud3DSymbolHandler::processNode( QgsPointCloudIndex *pc, const QgsVector3D blockScale = block->scale(); const QgsVector3D blockOffset = block->offset(); + // The request was made on the boundingbox of the real extent + // It is necessary to ensure that the features intersect + // with extentGeom if the scene rotation is not 0. + QgsGeos extentIntersectionGeos( nullptr ); + if ( context.map().zRotation() != 0.0 ) + { + QgsGeometry extentIntersection = context.map().rotatedExtent().intersection( QgsGeometry::fromRect( context.extent() ) ); + extentIntersectionGeos = QgsGeos( extentIntersection.constGet() ); + extentIntersectionGeos.prepareGeometry(); + } + for ( int i = 0; i < count; ++i ) { if ( context.isCanceled() ) @@ -715,6 +745,12 @@ void QgsColorRampPointCloud3DSymbolHandler::processNode( QgsPointCloudIndex *pc, double x = blockOffset.x() + blockScale.x() * ix; double y = blockOffset.y() + blockScale.y() * iy; double z = ( blockOffset.z() + blockScale.z() * iz ) * zValueScale + zValueOffset; + + if ( context.map().zRotation() != 0.0 && !extentIntersectionGeos.intersects( x, y ) ) + { + continue; + } + try { coordinateTransform.transformInPlace( x, y, z ); @@ -823,6 +859,18 @@ void QgsRGBPointCloud3DSymbolHandler::processNode( QgsPointCloudIndex *pc, const int ir = 0; int ig = 0; int ib = 0; + + // The request was made on the boundingbox of the real extent + // It is necessary to ensure that the features intersect + // with extentGeom if the scene rotation is not 0. + QgsGeos extentIntersectionGeos( nullptr ); + if ( context.map().zRotation() != 0.0 ) + { + QgsGeometry extentIntersection = context.map().rotatedExtent().intersection( QgsGeometry::fromRect( context.extent() ) ); + extentIntersectionGeos = QgsGeos( extentIntersection.constGet() ); + extentIntersectionGeos.prepareGeometry(); + } + for ( int i = 0; i < count; ++i ) { if ( context.isCanceled() ) @@ -834,6 +882,12 @@ void QgsRGBPointCloud3DSymbolHandler::processNode( QgsPointCloudIndex *pc, const double x = blockOffset.x() + blockScale.x() * ix; double y = blockOffset.y() + blockScale.y() * iy; double z = ( blockOffset.z() + blockScale.z() * iz ) * zValueScale + zValueOffset; + + if ( context.map().zRotation() != 0.0 && !extentIntersectionGeos.intersects( x, y ) ) + { + continue; + } + try { coordinateTransform.transformInPlace( x, y, z ); @@ -986,6 +1040,18 @@ void QgsClassificationPointCloud3DSymbolHandler::processNode( QgsPointCloudIndex } const QSet filteredOutValues = context.getFilteredOutValues(); + + // The request was made on the boundingbox of the real extent + // It is necessary to ensure that the features intersect + // with extentGeom if the scene rotation is not 0. + QgsGeos extentIntersectionGeos( nullptr ); + if ( context.map().zRotation() != 0.0 ) + { + QgsGeometry extentIntersection = context.map().rotatedExtent().intersection( QgsGeometry::fromRect( context.extent() ) ); + extentIntersectionGeos = QgsGeos( extentIntersection.constGet() ); + extentIntersectionGeos.prepareGeometry(); + } + for ( int i = 0; i < count; ++i ) { if ( context.isCanceled() ) @@ -998,6 +1064,12 @@ void QgsClassificationPointCloud3DSymbolHandler::processNode( QgsPointCloudIndex double x = blockOffset.x() + blockScale.x() * ix; double y = blockOffset.y() + blockScale.y() * iy; double z = ( blockOffset.z() + blockScale.z() * iz ) * zValueScale + zValueOffset; + + if ( context.map().zRotation() != 0.0 && !extentIntersectionGeos.intersects( x, y ) ) + { + continue; + } + try { coordinateTransform.transformInPlace( x, y, z ); diff --git a/tests/src/3d/testqgspointcloud3drendering.cpp b/tests/src/3d/testqgspointcloud3drendering.cpp index 79b4125bf4ea..74674fb1d99d 100644 --- a/tests/src/3d/testqgspointcloud3drendering.cpp +++ b/tests/src/3d/testqgspointcloud3drendering.cpp @@ -434,6 +434,10 @@ void TestQgsPointCloud3DRendering::testPointCloudFilteredClassification() void TestQgsPointCloud3DRendering::testPointCloudFilteredSceneExtent() { + /* + *first test - change the extent + */ + const QgsRectangle fullExtent = mLayer->extent(); const QgsRectangle filteredExtent = QgsRectangle( fullExtent.xMinimum(), fullExtent.yMinimum(), fullExtent.xMinimum() + fullExtent.width() / 3.0, fullExtent.yMinimum() + fullExtent.height() / 4.0 ); @@ -470,6 +474,51 @@ void TestQgsPointCloud3DRendering::testPointCloudFilteredSceneExtent() QImage img = Qgs3DUtils::captureSceneImage( engine, scene ); QGSVERIFYIMAGECHECK( "pointcloud_3d_filtered_scene_extent", "pointcloud_3d_filtered_scene_extent", img, QString(), 80, QSize( 0, 0 ), 15 ); + + /* + * second test - change the orientation + */ + + Qgs3DMapSettings *mapSettings2 = new Qgs3DMapSettings; + mapSettings2->setCrs( mProject->crs() ); + mapSettings2->setOrigin( QgsVector3D( filteredExtent.center().x(), filteredExtent.center().y(), 0 ) ); + mapSettings2->setExtent( filteredExtent ); + mapSettings2->setZRotation( 35.0 ); + mapSettings2->setLayers( QList() << mLayer ); + + QgsPointLightSettings defaultLight2; + defaultLight2.setIntensity( 0.5 ); + defaultLight2.setPosition( QgsVector3D( 0, 1000, 0 ) ); + mapSettings2->setLightSources( { defaultLight2.clone() } ); + + QgsOffscreen3DEngine engine2; + Qgs3DMapScene *scene2 = new Qgs3DMapScene( *mapSettings2, &engine2 ); + engine2.setRootEntity( scene2 ); + + QgsClassificationPointCloud3DSymbol *symbol2 = new QgsClassificationPointCloud3DSymbol(); + symbol2->setAttribute( QStringLiteral( "Classification" ) ); + auto categories2 = QgsPointCloudClassifiedRenderer::defaultCategories(); + symbol2->setCategoriesList( categories2 ); + symbol2->setPointSize( 10 ); + + QgsPointCloudLayer3DRenderer *renderer2 = new QgsPointCloudLayer3DRenderer(); + renderer2->setSymbol( symbol2 ); + mLayer->setRenderer3D( renderer2 ); + + scene2->cameraController()->resetView( 90 ); + + // When running the test, it would sometimes return partially rendered image. + // It is probably based on how fast qt3d manages to upload the data to GPU... + // Capturing the initial image and throwing it away fixes that. Hopefully we will + // find a better fix in the future. + Qgs3DUtils::captureSceneImage( engine2, scene2 ); + + QImage img2 = Qgs3DUtils::captureSceneImage( engine2, scene2 ); + + delete scene2; + delete mapSettings2; + + QGSVERIFYIMAGECHECK( "pointcloud_3d_filtered_scene_extent_rotation", "pointcloud_3d_filtered_scene_extent_rotation", img2, QString(), 80, QSize( 0, 0 ), 15 ); } QGSTEST_MAIN( TestQgsPointCloud3DRendering ) diff --git a/tests/testdata/control_images/3d/expected_pointcloud_3d_filtered_scene_extent_rotation/default/expected_pointcloud_3d_filtered_scene_extent_rotation.png b/tests/testdata/control_images/3d/expected_pointcloud_3d_filtered_scene_extent_rotation/default/expected_pointcloud_3d_filtered_scene_extent_rotation.png new file mode 100644 index 000000000000..f66a7812d113 Binary files /dev/null and b/tests/testdata/control_images/3d/expected_pointcloud_3d_filtered_scene_extent_rotation/default/expected_pointcloud_3d_filtered_scene_extent_rotation.png differ diff --git a/tests/testdata/control_images/3d/expected_pointcloud_3d_filtered_scene_extent_rotation/qt6/expected_pointcloud_3d_filtered_scene_extent_rotation.png b/tests/testdata/control_images/3d/expected_pointcloud_3d_filtered_scene_extent_rotation/qt6/expected_pointcloud_3d_filtered_scene_extent_rotation.png new file mode 100644 index 000000000000..2492a5c1402e Binary files /dev/null and b/tests/testdata/control_images/3d/expected_pointcloud_3d_filtered_scene_extent_rotation/qt6/expected_pointcloud_3d_filtered_scene_extent_rotation.png differ