Skip to content

Commit

Permalink
qgspointcloud3dsymbol: Handle z rotation
Browse files Browse the repository at this point in the history
If the rotation is not 0, the filter Rect used to request is the
bounding box of the real extent. In that case, one need to ensure that
points are inside the 3d scene extent.
  • Loading branch information
ptitjano committed Jun 6, 2024
1 parent 0d20492 commit d651e29
Show file tree
Hide file tree
Showing 4 changed files with 121 additions and 0 deletions.
72 changes: 72 additions & 0 deletions src/3d/symbols/qgspointcloud3dsymbol_p.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@

///@cond PRIVATE

#include "qgsgeometry.h"
#include "qgsgeos.h"
#include "qgspointcloud3dsymbol.h"
#include "qgspointcloudattribute.h"
#include "qgspointcloudrequest.h"
Expand Down Expand Up @@ -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() )
Expand All @@ -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 );
Expand Down Expand Up @@ -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() )
Expand All @@ -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 );
Expand Down Expand Up @@ -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() )
Expand All @@ -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 );
Expand Down Expand Up @@ -986,6 +1040,18 @@ void QgsClassificationPointCloud3DSymbolHandler::processNode( QgsPointCloudIndex
}

const QSet<int> 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() )
Expand All @@ -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 );
Expand Down
49 changes: 49 additions & 0 deletions tests/src/3d/testqgspointcloud3drendering.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 );
Expand Down Expand Up @@ -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<QgsMapLayer *>() << 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 )
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit d651e29

Please sign in to comment.