Skip to content

Commit

Permalink
[FEATURE] Vector layers are loaded in 3D view in background
Browse files Browse the repository at this point in the history
We are now using chunked entities for vector layers, so they
are split into several chunks (tiles) and each gets prepared
in background thread separately, not blocking main thread.

This is an initial implementation - work in progress.
  • Loading branch information
wonder-sk committed Jan 8, 2020
1 parent 8a16451 commit ab88777
Show file tree
Hide file tree
Showing 11 changed files with 149 additions and 47 deletions.
2 changes: 2 additions & 0 deletions src/3d/CMakeLists.txt
Expand Up @@ -20,6 +20,7 @@ SET(QGIS_3D_SRCS
qgstessellatedpolygongeometry.cpp
qgstilingscheme.cpp
qgsvectorlayer3drenderer.cpp
qgsvectorlayerchunkloader_p.cpp
qgsmeshlayer3drenderer.cpp
qgswindow3dengine.cpp

Expand Down Expand Up @@ -100,6 +101,7 @@ SET(QGIS_3D_HDRS
)

SET(QGIS_3D_PRIVATE_HDRS
qgsvectorlayerchunkloader_p.h
chunks/qgschunkboundsentity_p.h
chunks/qgschunkedentity_p.h
chunks/qgschunklist_p.h
Expand Down
6 changes: 4 additions & 2 deletions src/3d/chunks/qgschunkedentity_p.cpp
Expand Up @@ -244,9 +244,9 @@ void QgsChunkedEntity::update( QgsChunkNode *node, const SceneState &state )
return;
}

//qDebug() << node->x << "|" << node->y << "|" << node->z << " " << tau << " " << screenSpaceError(node, state);
//qDebug() << node->tileX() << "|" << node->tileY() << "|" << node->tileZ() << " " << mTau << " " << screenSpaceError(node, state);

if ( screenSpaceError( node, state ) <= mTau )
if ( mTau > 0 && screenSpaceError( node, state ) <= mTau )
{
// acceptable error for the current chunk - let's render it

Expand Down Expand Up @@ -343,6 +343,8 @@ void QgsChunkedEntity::onActiveJobFinished()
node->setLoaded( entity );

mReplacementQueue->insertFirst( node->replacementQueueEntry() );

emit newEntityCreated( entity );
}
else
{
Expand Down
3 changes: 3 additions & 0 deletions src/3d/chunks/qgschunkedentity_p.h
Expand Up @@ -108,6 +108,9 @@ class QgsChunkedEntity : public Qt3DCore::QEntity
//! Emitted when the number of pending jobs changes (some jobs have finished or some jobs have been just created)
void pendingJobsCountChanged();

//! Emitted when a new 3D entity has been created. Other components can use that to do extra work
void newEntityCreated( Qt3DCore::QEntity *entity );

protected:
//! root node of the quadtree hierarchy
QgsChunkNode *mRootNode = nullptr;
Expand Down
33 changes: 30 additions & 3 deletions src/3d/qgs3dmapscene.cpp
Expand Up @@ -439,7 +439,7 @@ void Qgs3DMapScene::onLayerEntityPickEvent( Qt3DRender::QPickEvent *event )
for ( Qgs3DMapScenePickHandler *pickHandler : qgis::as_const( mPickHandlers ) )
{
// go figure out feature ID from the triangle index
QgsFeatureId fid = -1;
QgsFeatureId fid = FID_NULL;
for ( Qt3DRender::QGeometryRenderer *geomRenderer : entity->findChildren<Qt3DRender::QGeometryRenderer *>() )
{
// unfortunately we can't access which sub-entity triggered the pick event
Expand All @@ -450,11 +450,17 @@ void Qgs3DMapScene::onLayerEntityPickEvent( Qt3DRender::QPickEvent *event )

if ( QgsTessellatedPolygonGeometry *g = qobject_cast<QgsTessellatedPolygonGeometry *>( geomRenderer->geometry() ) )
{
// because we have a single picker for all chunks of an entity and QPickEvent
// does not give better information than triangle index and world/local intersection point.
// TODO: the code below basically does not work with more than single sub-entity
// TODO: maybe have a separate picker for each chunk?
fid = g->triangleIndexToFeatureId( triangleEvent->triangleIndex() );
break;
if ( !FID_IS_NULL( fid ) )
break;
}
}
pickHandler->handlePickOnVectorLayer( vlayer, fid, event->worldIntersection(), event );
if ( !FID_IS_NULL( fid ) )
pickHandler->handlePickOnVectorLayer( vlayer, fid, event->worldIntersection(), event );
}

}
Expand Down Expand Up @@ -557,6 +563,7 @@ void Qgs3DMapScene::onLayersChanged()

void Qgs3DMapScene::addLayerEntity( QgsMapLayer *layer )
{
bool needsSceneUpdate = false;
QgsAbstract3DRenderer *renderer = layer->renderer3D();
if ( renderer )
{
Expand Down Expand Up @@ -592,9 +599,23 @@ void Qgs3DMapScene::addLayerEntity( QgsMapLayer *layer )
}

finalizeNewEntity( newEntity );

if ( QgsChunkedEntity *chunkedNewEntity = qobject_cast<QgsChunkedEntity *>( newEntity ) )
{
mChunkEntities.append( chunkedNewEntity );
needsSceneUpdate = true;

connect( chunkedNewEntity, &QgsChunkedEntity::newEntityCreated, this, [this]( Qt3DCore::QEntity * entity )
{
finalizeNewEntity( entity );
} );
}
}
}

if ( needsSceneUpdate )
onCameraChanged(); // needed for chunked entities

connect( layer, &QgsMapLayer::renderer3DChanged, this, &Qgs3DMapScene::onLayerRenderer3DChanged );

if ( layer->type() == QgsMapLayerType::VectorLayer )
Expand All @@ -607,6 +628,12 @@ void Qgs3DMapScene::addLayerEntity( QgsMapLayer *layer )
void Qgs3DMapScene::removeLayerEntity( QgsMapLayer *layer )
{
Qt3DCore::QEntity *entity = mLayerEntities.take( layer );

if ( QgsChunkedEntity *chunkedEntity = qobject_cast<QgsChunkedEntity *>( entity ) )
{
mChunkEntities.removeOne( chunkedEntity );
}

if ( entity )
entity->deleteLater();

Expand Down
22 changes: 22 additions & 0 deletions src/3d/qgs3dutils.cpp
Expand Up @@ -442,6 +442,28 @@ QgsVector3D Qgs3DUtils::worldToMapCoordinates( const QgsVector3D &worldCoords, c
worldCoords.y() + origin.z() );
}

QgsAABB Qgs3DUtils::mapToWorldExtent( const QgsRectangle &extent, const QgsCoordinateReferenceSystem &crs, const QgsVector3D &mapOrigin, const QgsCoordinateReferenceSystem &mapCrs )
{
// TODO: transform to map coords
QgsVector3D extentMin3D( extent.xMinimum(), extent.yMinimum(), 0 ); // TODO: is it OK to have zero min/max elevation?
QgsVector3D extentMax3D( extent.xMaximum(), extent.yMaximum(), 500 );
QgsVector3D worldExtentMin3D = mapToWorldCoordinates( extentMin3D, mapOrigin );
QgsVector3D worldExtentMax3D = mapToWorldCoordinates( extentMax3D, mapOrigin );
QgsAABB rootBbox( worldExtentMin3D.x(), worldExtentMin3D.y(), worldExtentMin3D.z(),
worldExtentMax3D.x(), worldExtentMax3D.y(), worldExtentMax3D.z() );
return rootBbox;
}

QgsRectangle Qgs3DUtils::worldToMapExtent( const QgsAABB &bbox, const QgsCoordinateReferenceSystem &crs, const QgsVector3D &mapOrigin, const QgsCoordinateReferenceSystem &mapCrs )
{
QgsVector3D worldExtentMin3D = Qgs3DUtils::worldToMapCoordinates( QgsVector3D( bbox.xMin, bbox.yMin, bbox.zMin ), mapOrigin );
QgsVector3D worldExtentMax3D = Qgs3DUtils::worldToMapCoordinates( QgsVector3D( bbox.xMax, bbox.yMax, bbox.zMax ), mapOrigin );
// TODO: reproject to layer CRS
QgsRectangle rect( worldExtentMin3D.x(), worldExtentMin3D.y(), worldExtentMax3D.x(), worldExtentMax3D.y() );
return rect;
}


QgsVector3D Qgs3DUtils::transformWorldCoordinates( const QgsVector3D &worldPoint1, const QgsVector3D &origin1, const QgsCoordinateReferenceSystem &crs1, const QgsVector3D &origin2, const QgsCoordinateReferenceSystem &crs2, const QgsCoordinateTransformContext &context )
{
QgsVector3D mapPoint1 = worldToMapCoordinates( worldPoint1, origin1 );
Expand Down
5 changes: 5 additions & 0 deletions src/3d/qgs3dutils.h
Expand Up @@ -131,6 +131,11 @@ class _3D_EXPORT Qgs3DUtils
//! Converts 3D world coordinates to map coordinates (applies offset and turns (x,y,z) into (x,-z,y))
static QgsVector3D worldToMapCoordinates( const QgsVector3D &worldCoords, const QgsVector3D &origin );

//! Converts layer extent to axis aligned bounding box in 3D world coordinates
static QgsAABB mapToWorldExtent( const QgsRectangle &extent, const QgsCoordinateReferenceSystem &crs, const QgsVector3D &mapOrigin, const QgsCoordinateReferenceSystem &mapCrs );
//! Converts axis aligned bounding box in 3D world coordinates to extent in map coordinates
static QgsRectangle worldToMapExtent( const QgsAABB &bbox, const QgsCoordinateReferenceSystem &crs, const QgsVector3D &mapOrigin, const QgsCoordinateReferenceSystem &mapCrs );

//! Transforms a world point from (origin1, crs1) to (origin2, crs2)
static QgsVector3D transformWorldCoordinates( const QgsVector3D &worldPoint1, const QgsVector3D &origin1, const QgsCoordinateReferenceSystem &crs1, const QgsVector3D &origin2, const QgsCoordinateReferenceSystem &crs2,
const QgsCoordinateTransformContext &context );
Expand Down
16 changes: 13 additions & 3 deletions src/3d/qgstessellatedpolygongeometry.cpp
Expand Up @@ -86,14 +86,25 @@ void QgsTessellatedPolygonGeometry::setPolygons( const QList<QgsPolygon *> &poly
mNormalAttribute->setCount( nVerts );
}

void QgsTessellatedPolygonGeometry::setData( const QByteArray &vertexBufferData, int vertexCount, const QVector<QgsFeatureId> &triangleIndexFids, const QVector<uint> &triangleIndexStartingIndices )
{
mTriangleIndexStartingIndices = triangleIndexStartingIndices;
mTriangleIndexFids = triangleIndexFids;

mVertexBuffer->setData( vertexBufferData );
mPositionAttribute->setCount( vertexCount );
if ( mNormalAttribute )
mNormalAttribute->setCount( vertexCount );
}


// 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] )
if ( v < data[0] || v >= data[count - 1] )
return -1; // not in the array

while ( idx0 != idx1 )
Expand All @@ -117,6 +128,5 @@ static int binary_search( uint v, const uint *data, int count )
QgsFeatureId QgsTessellatedPolygonGeometry::triangleIndexToFeatureId( uint triangleIndex ) const
{
int i = binary_search( triangleIndex, mTriangleIndexStartingIndices.constData(), mTriangleIndexStartingIndices.count() );
Q_ASSERT( i != -1 );
return mTriangleIndexFids[i];
return i != -1 ? mTriangleIndexFids[i] : FID_NULL;
}
12 changes: 11 additions & 1 deletion src/3d/qgstessellatedpolygongeometry.h
Expand Up @@ -62,7 +62,17 @@ class QgsTessellatedPolygonGeometry : public Qt3DRender::QGeometry
//! Initializes vertex buffer from given polygons. Takes ownership of passed polygon geometries
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)
/**
* Initializes vertex buffer (and other members) from data that were already tessellated.
* This is an alternative to setPolygons() - this method does not do any expensive work in the body.
* \since QGIS 3.12
*/
void setData( const QByteArray &vertexBufferData, int vertexCount, const QVector<QgsFeatureId> &triangleIndexFids, const QVector<uint> &triangleIndexStartingIndices );

/**
* Returns ID of the feature to which given triangle index belongs (used for picking).
* In case such triangle index does not match any feature, FID_NULL is returned.
*/
QgsFeatureId triangleIndexToFeatureId( uint triangleIndex ) const;

private:
Expand Down
14 changes: 3 additions & 11 deletions src/3d/qgsvectorlayer3drenderer.cpp
Expand Up @@ -15,12 +15,11 @@

#include "qgsvectorlayer3drenderer.h"

#include "qgschunkedentity_p.h"
#include "qgsline3dsymbol.h"
#include "qgspoint3dsymbol.h"
#include "qgspolygon3dsymbol.h"
#include "qgsline3dsymbol_p.h"
#include "qgspoint3dsymbol_p.h"
#include "qgspolygon3dsymbol_p.h"
#include "qgsvectorlayerchunkloader_p.h"

#include "qgsvectorlayer.h"
#include "qgsxmlutils.h"
Expand Down Expand Up @@ -81,14 +80,7 @@ Qt3DCore::QEntity *QgsVectorLayer3DRenderer::createEntity( const Qgs3DMapSetting
if ( !mSymbol || !vl )
return nullptr;

if ( mSymbol->type() == QLatin1String( "polygon" ) )
return Qgs3DSymbolImpl::entityForPolygon3DSymbol( map, vl, *static_cast<QgsPolygon3DSymbol *>( mSymbol.get() ) );
else if ( mSymbol->type() == QLatin1String( "point" ) )
return Qgs3DSymbolImpl::entityForPoint3DSymbol( map, vl, *static_cast<QgsPoint3DSymbol *>( mSymbol.get() ) );
else if ( mSymbol->type() == QLatin1String( "line" ) )
return Qgs3DSymbolImpl::entityForLine3DSymbol( map, vl, *static_cast<QgsLine3DSymbol *>( mSymbol.get() ) );
else
return nullptr;
return new QgsVectorLayerChunkedEntity( vl, mSymbol.get(), map );
}

void QgsVectorLayer3DRenderer::writeXml( QDomElement &elem, const QgsReadWriteContext &context ) const
Expand Down
41 changes: 29 additions & 12 deletions src/3d/symbols/qgsline3dsymbol_p.cpp
Expand Up @@ -19,6 +19,7 @@
#include "qgslinematerial_p.h"
#include "qgslinevertexdata_p.h"
#include "qgstessellatedpolygongeometry.h"
#include "qgstessellator.h"
#include "qgs3dmapsettings.h"
//#include "qgsterraingenerator.h"
#include "qgs3dutils.h"
Expand Down Expand Up @@ -67,10 +68,13 @@ class QgsBufferedLine3DSymbolHandler : public QgsFeature3DHandler
//! temporary data we will pass to the tessellator
struct LineData
{
QList<QgsPolygon *> polygons;
QList<QgsFeatureId> fids;
std::unique_ptr<QgsTessellator> tessellator;
QVector<QgsFeatureId> triangleIndexFids;
QVector<uint> triangleIndexStartingIndices;
};

void processPolygon( QgsPolygon *polyBuffered, QgsFeatureId fid, float height, float extrusionHeight, const Qgs3DRenderContext &context, LineData &out );

void makeEntity( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context, LineData &out, bool selected );

// input specific for this class
Expand All @@ -87,8 +91,11 @@ class QgsBufferedLine3DSymbolHandler : public QgsFeature3DHandler

bool QgsBufferedLine3DSymbolHandler::prepare( const Qgs3DRenderContext &context, QSet<QString> &attributeNames )
{
Q_UNUSED( context )
Q_UNUSED( attributeNames )

outNormal.tessellator.reset( new QgsTessellator( context.map().origin().x(), context.map().origin().y(), true ) );
outSelected.tessellator.reset( new QgsTessellator( context.map().origin().x(), context.map().origin().y(), true ) );

return true;
}

Expand Down Expand Up @@ -119,9 +126,7 @@ void QgsBufferedLine3DSymbolHandler::processFeature( QgsFeature &f, const Qgs3DR
if ( QgsWkbTypes::flatType( buffered->wkbType() ) == QgsWkbTypes::Polygon )
{
QgsPolygon *polyBuffered = static_cast<QgsPolygon *>( buffered );
Qgs3DUtils::clampAltitudes( polyBuffered, mSymbol.altitudeClamping(), mSymbol.altitudeBinding(), mSymbol.height(), context.map() );
out.polygons.append( polyBuffered );
out.fids.append( f.id() );
processPolygon( polyBuffered, f.id(), mSymbol.height(), mSymbol.extrusionHeight(), context, out );
}
else if ( QgsWkbTypes::flatType( buffered->wkbType() ) == QgsWkbTypes::MultiPolygon )
{
Expand All @@ -131,14 +136,23 @@ void QgsBufferedLine3DSymbolHandler::processFeature( QgsFeature &f, const Qgs3DR
QgsAbstractGeometry *partBuffered = mpolyBuffered->geometryN( i );
Q_ASSERT( QgsWkbTypes::flatType( partBuffered->wkbType() ) == QgsWkbTypes::Polygon );
QgsPolygon *polyBuffered = static_cast<QgsPolygon *>( partBuffered )->clone(); // need to clone individual geometry parts
Qgs3DUtils::clampAltitudes( polyBuffered, mSymbol.altitudeClamping(), mSymbol.altitudeBinding(), mSymbol.height(), context.map() );
out.polygons.append( polyBuffered );
out.fids.append( f.id() );
processPolygon( polyBuffered, f.id(), mSymbol.height(), mSymbol.extrusionHeight(), context, out );
}
delete buffered;
}
}

void QgsBufferedLine3DSymbolHandler::processPolygon( QgsPolygon *polyBuffered, QgsFeatureId fid, float height, float extrusionHeight, const Qgs3DRenderContext &context, LineData &out )
{
Qgs3DUtils::clampAltitudes( polyBuffered, mSymbol.altitudeClamping(), mSymbol.altitudeBinding(), height, context.map() );

Q_ASSERT( out.tessellator->dataVerticesCount() % 3 == 0 );
uint startingTriangleIndex = static_cast<uint>( out.tessellator->dataVerticesCount() / 3 );
out.triangleIndexStartingIndices.append( startingTriangleIndex );
out.triangleIndexFids.append( fid );
out.tessellator->addPolygon( *polyBuffered, extrusionHeight );
delete polyBuffered;
}

void QgsBufferedLine3DSymbolHandler::finalize( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context )
{
Expand All @@ -150,7 +164,7 @@ void QgsBufferedLine3DSymbolHandler::finalize( Qt3DCore::QEntity *parent, const

void QgsBufferedLine3DSymbolHandler::makeEntity( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context, LineData &out, bool selected )
{
if ( out.polygons.isEmpty() )
if ( !out.tessellator->dataVerticesCount() )
return; // nothing to show - no need to create the entity

Qt3DExtras::QPhongMaterial *mat = _material( mSymbol );
Expand All @@ -161,9 +175,12 @@ void QgsBufferedLine3DSymbolHandler::makeEntity( Qt3DCore::QEntity *parent, cons
mat->setAmbient( context.map().selectionColor().darker() );
}

QgsPointXY origin( context.map().origin().x(), context.map().origin().y() );
// extract vertex buffer data from tessellator
QByteArray data( ( const char * )out.tessellator->data().constData(), out.tessellator->data().count() * sizeof( float ) );
int nVerts = data.count() / out.tessellator->stride();

QgsTessellatedPolygonGeometry *geometry = new QgsTessellatedPolygonGeometry;
geometry->setPolygons( out.polygons, out.fids, origin, mSymbol.extrusionHeight() );
geometry->setData( data, nVerts, out.triangleIndexFids, out.triangleIndexStartingIndices );

Qt3DRender::QGeometryRenderer *renderer = new Qt3DRender::QGeometryRenderer;
renderer->setGeometry( geometry );
Expand Down

0 comments on commit ab88777

Please sign in to comment.