Skip to content
Permalink
Browse files

[FEATURE] 3D identify tool working on 3D entities

Until now the tool only considered terrain. This commit adds support
for 3D renderers created from vector layers, so it is possible to
correctly identify polygons and linestrings in 3D.
  • Loading branch information
wonder-sk committed Sep 13, 2018
1 parent f99b2db commit 2085dfa6aefc39800f453f2ab4a1242568701817
@@ -158,6 +158,11 @@ Call the right method depending on layer type
bool identifyRasterLayer( QList<QgsMapToolIdentify::IdentifyResult> *results, QgsRasterLayer *layer, QgsPointXY point, const QgsRectangle &viewExtent, double mapUnitsPerPixel );
bool identifyVectorLayer( QList<QgsMapToolIdentify::IdentifyResult> *results, QgsVectorLayer *layer, const QgsPointXY &point );

QMap< QString, QString > derivedAttributesForPoint( const QgsPoint &point );
%Docstring
Returns derived attributes map for a clicked point in map coordinates. May be 2D or 3D point.
%End

};

QFlags<QgsMapToolIdentify::Type> operator|(QgsMapToolIdentify::Type f1, QFlags<QgsMapToolIdentify::Type> f2);
@@ -56,6 +56,7 @@ SET(QGIS_3D_MOC_HDRS
qgscameracontroller.h
qgslayoutitem3dmap.h
qgsoffscreen3dengine.h
qgstessellatedpolygongeometry.h
qgswindow3dengine.h

chunks/qgschunkedentity_p.h
@@ -30,6 +30,7 @@

#include "qgsaabb.h"
#include "qgsabstract3dengine.h"
#include "qgs3dmapscenepickhandler.h"
#include "qgs3dmapsettings.h"
#include "qgs3dutils.h"
#include "qgsabstract3drenderer.h"
@@ -38,6 +39,7 @@
#include "qgschunknode_p.h"
#include "qgsterrainentity_p.h"
#include "qgsterraingenerator.h"
#include "qgstessellatedpolygongeometry.h"
#include "qgsvectorlayer.h"
#include "qgsvectorlayer3drenderer.h"

@@ -177,6 +179,37 @@ int Qgs3DMapScene::terrainPendingJobsCount() const
return mTerrain ? mTerrain->pendingJobsCount() : 0;
}

void Qgs3DMapScene::registerPickHandler( Qgs3DMapScenePickHandler *pickHandler )
{
if ( mPickHandlers.isEmpty() )
{
// we need to add object pickers
for ( Qt3DCore::QEntity *entity : mLayerEntities.values() )
{
Qt3DRender::QObjectPicker *picker = new Qt3DRender::QObjectPicker( entity );
entity->addComponent( picker );
connect( picker, &Qt3DRender::QObjectPicker::pressed, this, &Qgs3DMapScene::onLayerEntityPickEvent );
}
}

mPickHandlers.append( pickHandler );
}

void Qgs3DMapScene::unregisterPickHandler( Qgs3DMapScenePickHandler *pickHandler )
{
mPickHandlers.removeOne( pickHandler );

if ( mPickHandlers.isEmpty() )
{
// we need to remove pickers
for ( Qt3DCore::QEntity *entity : mLayerEntities.values() )
{
Qt3DRender::QObjectPicker *picker = entity->findChild<Qt3DRender::QObjectPicker *>();
picker->deleteLater();
}
}
}

QgsChunkedEntity::SceneState _sceneState( QgsCameraController *cameraController )
{
Qt3DRender::QCamera *camera = cameraController->camera();
@@ -365,6 +398,54 @@ void Qgs3DMapScene::onBackgroundColorChanged()
mEngine->setClearColor( mMap.backgroundColor() );
}

void Qgs3DMapScene::onLayerEntityPickEvent( Qt3DRender::QPickEvent *event )
{
if ( event->button() != Qt3DRender::QPickEvent::LeftButton )
return;

Qt3DRender::QPickTriangleEvent *triangleEvent = qobject_cast<Qt3DRender::QPickTriangleEvent *>( event );
if ( !triangleEvent )
return;

Qt3DRender::QObjectPicker *picker = qobject_cast<Qt3DRender::QObjectPicker *>( sender() );
if ( !picker )
return;

Qt3DCore::QEntity *entity = qobject_cast<Qt3DCore::QEntity *>( picker->parent() );
if ( !entity )
return;

QgsMapLayer *layer = mLayerEntities.key( entity );
if ( !layer )
return;

QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
if ( !vlayer )
return;

for ( Qgs3DMapScenePickHandler *pickHandler : qgis::as_const( mPickHandlers ) )
{
// go figure out feature ID from the triangle index
QgsFeatureId fid = -1;
for ( Qt3DRender::QGeometryRenderer *geomRenderer : entity->findChildren<Qt3DRender::QGeometryRenderer *>() )
{
// unfortunately we can't access which sub-entity triggered the pick event
// so as a temporary workaround let's just ignore the entity with selection
// and hope the event was the main entity (QTBUG-58206)
if ( geomRenderer->objectName() != "main" )
continue;

if ( QgsTessellatedPolygonGeometry *g = qobject_cast<QgsTessellatedPolygonGeometry *>( geomRenderer->geometry() ) )
{
fid = g->triangleIndexToFeatureId( triangleEvent->triangleIndex() );
break;
}
}
pickHandler->handlePickOnVectorLayer( vlayer, fid, event->worldIntersection() );
}

}

void Qgs3DMapScene::onLayerRenderer3DChanged()
{
QgsMapLayer *layer = qobject_cast<QgsMapLayer *>( sender() );
@@ -425,6 +506,13 @@ void Qgs3DMapScene::addLayerEntity( QgsMapLayer *layer )
{
newEntity->setParent( this );
mLayerEntities.insert( layer, newEntity );

if ( !mPickHandlers.isEmpty() )
{
Qt3DRender::QObjectPicker *picker = new Qt3DRender::QObjectPicker( newEntity );
newEntity->addComponent( picker );
connect( picker, &Qt3DRender::QObjectPicker::pressed, this, &Qgs3DMapScene::onLayerEntityPickEvent );
}
}
}

@@ -24,6 +24,7 @@ namespace Qt3DRender
{
class QRenderSettings;
class QCamera;
class QPickEvent;
}

namespace Qt3DLogic
@@ -39,6 +40,7 @@ namespace Qt3DExtras
class QgsAbstract3DEngine;
class QgsMapLayer;
class QgsCameraController;
class Qgs3DMapScenePickHandler;
class Qgs3DMapSettings;
class QgsTerrainEntity;
class QgsChunkedEntity;
@@ -76,6 +78,11 @@ class _3D_EXPORT Qgs3DMapScene : public Qt3DCore::QEntity
//! Returns the current state of the scene
SceneState sceneState() const { return mSceneState; }

//! Registers an object that will get results of pick events on 3D entities. Does not take ownerhip of the pick handler. Adds object picker components to 3D entities.
void registerPickHandler( Qgs3DMapScenePickHandler *pickHandler );
//! Unregisters previously registered pick handler. Pick handler is not deleted. Also removes object picker components from 3D entities.
void unregisterPickHandler( Qgs3DMapScenePickHandler *pickHandler );

signals:
//! Emitted when the current terrain entity is replaced by a new one
void terrainEntityChanged();
@@ -92,6 +99,7 @@ class _3D_EXPORT Qgs3DMapScene : public Qt3DCore::QEntity
void onLayersChanged();
void createTerrainDeferred();
void onBackgroundColorChanged();
void onLayerEntityPickEvent( Qt3DRender::QPickEvent *event );

private:
void addLayerEntity( QgsMapLayer *layer );
@@ -116,6 +124,8 @@ class _3D_EXPORT Qgs3DMapScene : public Qt3DCore::QEntity
QMap<QgsMapLayer *, Qt3DCore::QEntity *> mLayerEntities;
bool mTerrainUpdateScheduled = false;
SceneState mSceneState = Ready;
//! List of currently registered pick handlers (used by identify tool)
QList<Qgs3DMapScenePickHandler *> mPickHandlers;
};

#endif // QGS3DMAPSCENE_H
@@ -0,0 +1,28 @@
#ifndef QGS3DMAPSCENEPICKHANDLER_H
#define QGS3DMAPSCENEPICKHANDLER_H

#include "qgsfeatureid.h"

class QVector3D;
class QgsVectorLayer;

/**
* \ingroup 3d
* Abstract base class for handlers that process pick events from a 3D map scene.
* 3D entities in map scene get QObjectPicker components assigned and mouse press events trigger virtual methods
* or pick handlers.
*
* This is used for identify tool to be able to identify entities coming from 3D renderers assigned to map layers.
*
* \since QGIS 3.4
*/
class Qgs3DMapScenePickHandler
{
public:
virtual ~Qgs3DMapScenePickHandler() {}

//! Called when user clicked a 3D entity belonging to a feature of a vector layer
virtual void handlePickOnVectorLayer( QgsVectorLayer *vlayer, QgsFeatureId id, const QVector3D &worldIntersection ) = 0;
};

#endif // QGS3DMAPSCENEPICKHANDLER_H
@@ -56,11 +56,20 @@ QgsTessellatedPolygonGeometry::QgsTessellatedPolygonGeometry( QNode *parent )
}
}

void QgsTessellatedPolygonGeometry::setPolygons( const QList<QgsPolygon *> &polygons, const QgsPointXY &origin, float extrusionHeight, const QList<float> &extrusionHeightPerPolygon )
void QgsTessellatedPolygonGeometry::setPolygons( const QList<QgsPolygon *> &polygons, const QList<QgsFeatureId> &featureIds, const QgsPointXY &origin, float extrusionHeight, const QList<float> &extrusionHeightPerPolygon )
{
Q_ASSERT( polygons.count() == featureIds.count() );
mTriangleIndexStartingIndices.reserve( polygons.count() );
mTriangleIndexFids.reserve( polygons.count() );

QgsTessellator tessellator( origin.x(), origin.y(), mWithNormals, mInvertNormals, mAddBackFaces );
for ( int i = 0; i < polygons.count(); ++i )
{
Q_ASSERT( tessellator.dataVerticesCount() % 3 == 0 );
uint startingTriangleIndex = static_cast<uint>( tessellator.dataVerticesCount() / 3 );
mTriangleIndexStartingIndices.append( startingTriangleIndex );
mTriangleIndexFids.append( featureIds[i] );

QgsPolygon *polygon = polygons.at( i );
float extr = extrusionHeightPerPolygon.isEmpty() ? extrusionHeight : extrusionHeightPerPolygon.at( i );
tessellator.addPolygon( *polygon, extr );
@@ -76,3 +85,38 @@ void QgsTessellatedPolygonGeometry::setPolygons( const QList<QgsPolygon *> &poly
if ( mNormalAttribute )
mNormalAttribute->setCount( nVerts );
}


// run binary search on a sorted array, return index i where data[i] <= x < data[i+1]
static int binary_search( uint v, const uint *data, int count )
{
int idx0 = 0;
int idx1 = count - 1;

if ( v < data[0] )
return -1; // not in the array

while ( idx0 != idx1 )
{
int idxPivot = ( idx0 + idx1 ) / 2;
uint pivot = data[idxPivot];
if ( pivot <= v )
{
if ( data[idxPivot + 1] > v )
return idxPivot; // we're done!
else // continue searching values greater than the pivot
idx0 = idxPivot;
}
else // continue searching values lower than the pivot
idx1 = idxPivot;
}
return idx0;
}


QgsFeatureId QgsTessellatedPolygonGeometry::triangleIndexToFeatureId( uint triangleIndex )
{
int i = binary_search( triangleIndex, mTriangleIndexStartingIndices.constData(), mTriangleIndexStartingIndices.count() );
Q_ASSERT( i != -1 );
return mTriangleIndexFids[i];
}
@@ -16,6 +16,7 @@
#ifndef QGSTESSELLATEDPOLYGONGEOMETRY_H
#define QGSTESSELLATEDPOLYGONGEOMETRY_H

#include "qgsfeatureid.h"
#include "qgspolygon.h"

#include <Qt3DRender/QGeometry>
@@ -36,6 +37,7 @@ namespace Qt3DRender
*/
class QgsTessellatedPolygonGeometry : public Qt3DRender::QGeometry
{
Q_OBJECT
public:
//! Constructor
QgsTessellatedPolygonGeometry( QNode *parent = nullptr );
@@ -58,14 +60,20 @@ class QgsTessellatedPolygonGeometry : public Qt3DRender::QGeometry
void setAddBackFaces( bool add ) { mAddBackFaces = add; }

//! Initializes vertex buffer from given polygons. Takes ownership of passed polygon geometries
void setPolygons( const QList<QgsPolygon *> &polygons, const QgsPointXY &origin, float extrusionHeight, const QList<float> &extrusionHeightPerPolygon = QList<float>() );
void setPolygons( const QList<QgsPolygon *> &polygons, const QList<QgsFeatureId> &featureIds, const QgsPointXY &origin, float extrusionHeight, const QList<float> &extrusionHeightPerPolygon = QList<float>() );

//! Returns ID of the feature to which given triangle index belongs (used for picking)
QgsFeatureId triangleIndexToFeatureId( uint triangleIndex );

private:

Qt3DRender::QAttribute *mPositionAttribute = nullptr;
Qt3DRender::QAttribute *mNormalAttribute = nullptr;
Qt3DRender::QBuffer *mVertexBuffer = nullptr;

QVector<QgsFeatureId> mTriangleIndexFids;
QVector<uint> mTriangleIndexStartingIndices;

bool mWithNormals = true;
bool mInvertNormals = false;
bool mAddBackFaces = false;
@@ -85,6 +85,7 @@ void QgsLine3DSymbolEntity::addEntityForNotSelectedLines( const Qgs3DMapSettings

// build the entity
QgsLine3DSymbolEntityNode *entity = new QgsLine3DSymbolEntityNode( map, layer, symbol, req );
entity->findChild<Qt3DRender::QGeometryRenderer *>()->setObjectName( "main" ); // temporary measure to distinguish between "selected" and "main"
entity->addComponent( mat );
entity->setParent( this );
}
@@ -106,6 +107,7 @@ Qt3DRender::QGeometryRenderer *QgsLine3DSymbolEntityNode::renderer( const Qgs3DM
double mitreLimit = 0;

QList<QgsPolygon *> polygons;
QList<QgsFeatureId> fids;
QgsFeature f;
QgsFeatureIterator fi = layer->getFeatures( request );
while ( fi.nextFeature( f ) )
@@ -129,6 +131,7 @@ Qt3DRender::QGeometryRenderer *QgsLine3DSymbolEntityNode::renderer( const Qgs3DM
QgsPolygon *polyBuffered = static_cast<QgsPolygon *>( buffered );
Qgs3DUtils::clampAltitudes( polyBuffered, symbol.altitudeClamping(), symbol.altitudeBinding(), symbol.height(), map );
polygons.append( polyBuffered );
fids.append( f.id() );
}
else if ( QgsWkbTypes::flatType( buffered->wkbType() ) == QgsWkbTypes::MultiPolygon )
{
@@ -140,13 +143,14 @@ Qt3DRender::QGeometryRenderer *QgsLine3DSymbolEntityNode::renderer( const Qgs3DM
QgsPolygon *polyBuffered = static_cast<QgsPolygon *>( partBuffered )->clone(); // need to clone individual geometry parts
Qgs3DUtils::clampAltitudes( polyBuffered, symbol.altitudeClamping(), symbol.altitudeBinding(), symbol.height(), map );
polygons.append( polyBuffered );
fids.append( f.id() );
}
delete buffered;
}
}

mGeometry = new QgsTessellatedPolygonGeometry;
mGeometry->setPolygons( polygons, origin, symbol.extrusionHeight() );
mGeometry->setPolygons( polygons, fids, origin, symbol.extrusionHeight() );

Qt3DRender::QGeometryRenderer *renderer = new Qt3DRender::QGeometryRenderer;
renderer->setGeometry( mGeometry );
@@ -101,6 +101,7 @@ void QgsPolygon3DSymbolEntity::addEntityForNotSelectedPolygons( const Qgs3DMapSe

// build the entity
QgsPolygon3DSymbolEntityNode *entity = new QgsPolygon3DSymbolEntityNode( map, layer, symbol, req );
entity->findChild<Qt3DRender::QGeometryRenderer *>()->setObjectName( "main" ); // temporary measure to distinguish between "selected" and "main"
entity->addComponent( mat );
entity->addComponent( tform );
entity->setParent( this );
@@ -141,6 +142,7 @@ Qt3DRender::QGeometryRenderer *QgsPolygon3DSymbolEntityNode::renderer( const Qgs
{
QgsPointXY origin( map.origin().x(), map.origin().y() );
QList<QgsPolygon *> polygons;
QList<QgsFeatureId> fids;
QList<float> extrusionHeightPerPolygon; // will stay empty if not needed per polygon

QgsExpressionContext ctx( _expressionContext3D() );
@@ -178,6 +180,7 @@ Qt3DRender::QGeometryRenderer *QgsPolygon3DSymbolEntityNode::renderer( const Qgs
QgsPolygon *polyClone = poly->clone();
Qgs3DUtils::clampAltitudes( polyClone, symbol.altitudeClamping(), symbol.altitudeBinding(), height, map );
polygons.append( polyClone );
fids.append( f.id() );
if ( hasDDExtrusion )
extrusionHeightPerPolygon.append( extrusionHeight );
}
@@ -190,6 +193,7 @@ Qt3DRender::QGeometryRenderer *QgsPolygon3DSymbolEntityNode::renderer( const Qgs
QgsPolygon *polyClone = static_cast< const QgsPolygon *>( g2 )->clone();
Qgs3DUtils::clampAltitudes( polyClone, symbol.altitudeClamping(), symbol.altitudeBinding(), height, map );
polygons.append( polyClone );
fids.append( f.id() );
if ( hasDDExtrusion )
extrusionHeightPerPolygon.append( extrusionHeight );
}
@@ -201,7 +205,7 @@ Qt3DRender::QGeometryRenderer *QgsPolygon3DSymbolEntityNode::renderer( const Qgs
mGeometry = new QgsTessellatedPolygonGeometry;
mGeometry->setInvertNormals( symbol.invertNormals() );
mGeometry->setAddBackFaces( symbol.addBackFaces() );
mGeometry->setPolygons( polygons, origin, symbol.extrusionHeight(), extrusionHeightPerPolygon );
mGeometry->setPolygons( polygons, fids, origin, symbol.extrusionHeight(), extrusionHeightPerPolygon );

Qt3DRender::QGeometryRenderer *renderer = new Qt3DRender::QGeometryRenderer;
renderer->setGeometry( mGeometry );

0 comments on commit 2085dfa

Please sign in to comment.
You can’t perform that action at this time.