Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
[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 2085dfa
Show file tree
Hide file tree
Showing 16 changed files with 273 additions and 9 deletions.
5 changes: 5 additions & 0 deletions python/gui/auto_generated/qgsmaptoolidentify.sip.in
Expand Up @@ -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);
Expand Down
1 change: 1 addition & 0 deletions src/3d/CMakeLists.txt
Expand Up @@ -56,6 +56,7 @@ SET(QGIS_3D_MOC_HDRS
qgscameracontroller.h
qgslayoutitem3dmap.h
qgsoffscreen3dengine.h
qgstessellatedpolygongeometry.h
qgswindow3dengine.h

chunks/qgschunkedentity_p.h
Expand Down
88 changes: 88 additions & 0 deletions src/3d/qgs3dmapscene.cpp
Expand Up @@ -30,6 +30,7 @@

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

Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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() );
Expand Down Expand Up @@ -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 );
}
}
}

Expand Down
10 changes: 10 additions & 0 deletions src/3d/qgs3dmapscene.h
Expand Up @@ -24,6 +24,7 @@ namespace Qt3DRender
{
class QRenderSettings;
class QCamera;
class QPickEvent;
}

namespace Qt3DLogic
Expand All @@ -39,6 +40,7 @@ namespace Qt3DExtras
class QgsAbstract3DEngine;
class QgsMapLayer;
class QgsCameraController;
class Qgs3DMapScenePickHandler;
class Qgs3DMapSettings;
class QgsTerrainEntity;
class QgsChunkedEntity;
Expand Down Expand Up @@ -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();
Expand All @@ -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 );
Expand All @@ -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
28 changes: 28 additions & 0 deletions src/3d/qgs3dmapscenepickhandler.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
46 changes: 45 additions & 1 deletion src/3d/qgstessellatedpolygongeometry.cpp
Expand Up @@ -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 );
Expand All @@ -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];
}
10 changes: 9 additions & 1 deletion src/3d/qgstessellatedpolygongeometry.h
Expand Up @@ -16,6 +16,7 @@
#ifndef QGSTESSELLATEDPOLYGONGEOMETRY_H
#define QGSTESSELLATEDPOLYGONGEOMETRY_H

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

#include <Qt3DRender/QGeometry>
Expand All @@ -36,6 +37,7 @@ namespace Qt3DRender
*/
class QgsTessellatedPolygonGeometry : public Qt3DRender::QGeometry
{
Q_OBJECT
public:
//! Constructor
QgsTessellatedPolygonGeometry( QNode *parent = nullptr );
Expand All @@ -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;
Expand Down
6 changes: 5 additions & 1 deletion src/3d/symbols/qgsline3dsymbol_p.cpp
Expand Up @@ -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 );
}
Expand All @@ -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 ) )
Expand All @@ -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 )
{
Expand All @@ -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 );
Expand Down
6 changes: 5 additions & 1 deletion src/3d/symbols/qgspolygon3dsymbol_p.cpp
Expand Up @@ -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 );
Expand Down Expand Up @@ -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() );
Expand Down Expand Up @@ -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 );
}
Expand All @@ -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 );
}
Expand All @@ -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 );
Expand Down

0 comments on commit 2085dfa

Please sign in to comment.