Skip to content
Permalink
Browse files

Octree support in chunk nodes, start using chunking for point clouds

  • Loading branch information
wonder-sk authored and nyalldawson committed Oct 26, 2020
1 parent 47ba499 commit 8fd7cc86e857ec703f7eb4e8ba9445e2d0d9d9aa
@@ -70,14 +70,15 @@ static float screenSpaceError( QgsChunkNode *node, const QgsChunkedEntity::Scene
return sse;
}

QgsChunkedEntity::QgsChunkedEntity( const QgsAABB &rootBbox, float rootError, float tau, int maxLevel, QgsChunkLoaderFactory *loaderFactory, bool ownsFactory, Qt3DCore::QNode *parent )
QgsChunkedEntity::QgsChunkedEntity( QgsChunkNode::Type type, const QgsAABB &rootBbox, float rootError, float tau, int maxLevel, QgsChunkLoaderFactory *loaderFactory, bool ownsFactory, Qt3DCore::QNode *parent )
: Qt3DCore::QEntity( parent )
, mTau( tau )
, mMaxLevel( maxLevel )
, mChunkLoaderFactory( loaderFactory )
, mOwnsFactory( ownsFactory )
{
mRootNode = new QgsChunkNode( QgsChunkNodeId( 0, 0, 0 ), rootBbox, rootError );
QgsChunkNodeId rootNodeId( 0, 0, 0, type == QgsChunkNode::Quadtree ? -1 : 0 );
mRootNode = new QgsChunkNode( type, rootNodeId, rootBbox, rootError );
mChunkLoaderQueue = new QgsChunkList;
mReplacementQueue = new QgsChunkList;
}
@@ -269,7 +270,8 @@ void QgsChunkedEntity::update( QgsChunkNode *node, const SceneState &state )
// error is not acceptable and children are ready to be used - recursive descent

QgsChunkNode *const *children = node->children();
for ( int i = 0; i < 4; ++i )
int childCount = node->type() == QgsChunkNode::Quadtree ? 4 : 8;
for ( int i = 0; i < childCount; ++i )
update( children[i], state );
}
else
@@ -281,7 +283,8 @@ void QgsChunkedEntity::update( QgsChunkNode *node, const SceneState &state )
if ( node->level() < mMaxLevel )
{
QgsChunkNode *const *children = node->children();
for ( int i = 0; i < 4; ++i )
int childCount = node->type() == QgsChunkNode::Quadtree ? 4 : 8;
for ( int i = 0; i < childCount; ++i )
requestResidency( children[i] );
}
}
@@ -45,6 +45,7 @@ class QgsChunkQueueJobFactory;
#include <QTime>

#include "qgsfeatureid.h"
#include "qgschunknode_p.h"

namespace Qt3DRender
{
@@ -62,7 +63,7 @@ class QgsChunkedEntity : public Qt3DCore::QEntity
Q_OBJECT
public:
//! Constructs a chunked entity
QgsChunkedEntity( const QgsAABB &rootBbox, float rootError, float mTau, int mMaxLevel, QgsChunkLoaderFactory *loaderFactory, bool ownsFactory, Qt3DCore::QNode *parent = nullptr );
QgsChunkedEntity( QgsChunkNode::Type type, const QgsAABB &rootBbox, float rootError, float mTau, int mMaxLevel, QgsChunkLoaderFactory *loaderFactory, bool ownsFactory, Qt3DCore::QNode *parent = nullptr );
~QgsChunkedEntity() override;

//! Records some bits about the scene (context for update() method)
@@ -22,8 +22,9 @@

///@cond PRIVATE

QgsChunkNode::QgsChunkNode( const QgsChunkNodeId &nodeId, const QgsAABB &bbox, float error, QgsChunkNode *parent )
: mBbox( bbox )
QgsChunkNode::QgsChunkNode( Type type, const QgsChunkNodeId &nodeId, const QgsAABB &bbox, float error, QgsChunkNode *parent )
: mType( type )
, mBbox( bbox )
, mError( error )
, mNodeId( nodeId )
, mParent( parent )
@@ -35,7 +36,8 @@ QgsChunkNode::QgsChunkNode( const QgsChunkNodeId &nodeId, const QgsAABB &bbox, f
, mUpdaterFactory( nullptr )
, mUpdater( nullptr )
{
for ( int i = 0; i < 4; ++i )
int childCount = mType == Quadtree ? 4 : 8;
for ( int i = 0; i < childCount; ++i )
mChildren[i] = nullptr;
}

@@ -48,13 +50,16 @@ QgsChunkNode::~QgsChunkNode()
Q_ASSERT( !mEntity ); // should be deleted when removed from replacement queue
Q_ASSERT( !mUpdater );
Q_ASSERT( !mUpdaterFactory );
for ( int i = 0; i < 4; ++i )

int childCount = mType == Quadtree ? 4 : 8;
for ( int i = 0; i < childCount; ++i )
delete mChildren[i];
}

bool QgsChunkNode::allChildChunksResident( QTime currentTime ) const
{
for ( int i = 0; i < 4; ++i )
int childCount = mType == Quadtree ? 4 : 8;
for ( int i = 0; i < childCount; ++i )
{
if ( !mChildren[i] )
return false; // not even a skeleton
@@ -70,21 +75,32 @@ bool QgsChunkNode::allChildChunksResident( QTime currentTime ) const
void QgsChunkNode::ensureAllChildrenExist()
{
float childError = mError / 2;
float xc = mBbox.xCenter(), zc = mBbox.zCenter();
float ymin = mBbox.yMin;
float ymax = mBbox.yMax;

if ( !mChildren[0] )
mChildren[0] = new QgsChunkNode( QgsChunkNodeId( mNodeId.x * 2 + 0, mNodeId.y * 2 + 1, mNodeId.z + 1 ), QgsAABB( mBbox.xMin, ymin, mBbox.zMin, xc, ymax, zc ), childError, this );

if ( !mChildren[1] )
mChildren[1] = new QgsChunkNode( QgsChunkNodeId( mNodeId.x * 2 + 0, mNodeId.y * 2 + 0, mNodeId.z + 1 ), QgsAABB( mBbox.xMin, ymin, zc, xc, ymax, mBbox.zMax ), childError, this );

if ( !mChildren[2] )
mChildren[2] = new QgsChunkNode( QgsChunkNodeId( mNodeId.x * 2 + 1, mNodeId.y * 2 + 1, mNodeId.z + 1 ), QgsAABB( xc, ymin, mBbox.zMin, mBbox.xMax, ymax, zc ), childError, this );
float xc = mBbox.xCenter(), yc = mBbox.yCenter(), zc = mBbox.zCenter();

if ( !mChildren[3] )
mChildren[3] = new QgsChunkNode( QgsChunkNodeId( mNodeId.x * 2 + 1, mNodeId.y * 2 + 0, mNodeId.z + 1 ), QgsAABB( xc, ymin, zc, mBbox.xMax, ymax, mBbox.zMax ), childError, this );
int childCount = mType == Quadtree ? 4 : 8;
for ( int i = 0; i < childCount; ++i )
{
if ( mChildren[i] )
continue;

int dx = i & 1, dy = !!( i & 2 ), dz = !!( i & 4 );
QgsChunkNodeId childId( mNodeId.d + 1, mNodeId.x * 2 + dx, mNodeId.y * 2 + dy, mType == Quadtree ? -1 : mNodeId.z * 2 + dz );
// the Y and Z coordinates below are intentionally flipped, because
// in chunk node IDs the X,Y axes define horizontal plane,
// while in our 3D scene the X,Z axes define the horizontal plane
float chXMin = dx ? xc : mBbox.xMin;
float chXMax = dx ? mBbox.xMax : xc;
float chZMin = dy ? zc : mBbox.zMin;
float chZMax = dy ? mBbox.zMax : zc;
float chYMin = mBbox.yMin;
float chYMax = mBbox.yMax;
if ( mType == Octree )
{
chYMin = dz ? yc : mBbox.yMin;
chYMax = dz ? mBbox.yMax : yc;
}
mChildren[i] = new QgsChunkNode( mType, childId, QgsAABB( chXMin, chYMin, chZMin, chXMax, chYMax, chZMax ), childError, this );
}
}

int QgsChunkNode::level() const
@@ -104,7 +120,8 @@ QList<QgsChunkNode *> QgsChunkNode::descendants()
QList<QgsChunkNode *> lst;
lst << this;

for ( int i = 0; i < 4; ++i )
int childCount = mType == Quadtree ? 4 : 8;
for ( int i = 0; i < childCount; ++i )
{
if ( mChildren[i] )
lst << mChildren[i]->descendants();
@@ -44,17 +44,29 @@ class QgsChunkQueueJob;
class QgsChunkQueueJobFactory;


//! Helper class to store X,Y,Z integer coordinates of a node
/**
* Helper class to store integer coordinates of a chunk node.
*
* - "d" is the depth of the tree
* - when used with a quadtree, "x" and "y" are the coordinates within the depth level of the tree ("z" coordinate is always -1)
* - when used with an octree, "x", "y" and "z" are the coordinates within the depth level of the tree
*/
struct QgsChunkNodeId
{
//! Constructs node ID
QgsChunkNodeId( int _x = -1, int _y = -1, int _z = -1 )
: x( _x ), y( _y ), z( _z ) {}
QgsChunkNodeId( int _d = -1, int _x = -1, int _y = -1, int _z = -1 )
: d( _d ), x( _x ), y( _y ), z( _z ) {}

int x, y, z;
int d, x, y, z;

//! Returns textual representation of the node ID in form of "Z/X/Y"
QString text() const { return QStringLiteral( "%1/%2/%3" ).arg( z ).arg( x ).arg( y ); }
QString text() const
{
if ( z == -1 )
return QStringLiteral( "%1/%2/%3" ).arg( d ).arg( x ).arg( y ); // quadtree
else
return QStringLiteral( "%1/%2/%3/%4" ).arg( d ).arg( x ).arg( y ).arg( z ); // octree
}
};

/**
@@ -74,8 +86,15 @@ struct QgsChunkNodeId
class QgsChunkNode
{
public:

enum Type
{
Quadtree, //!< Tree where each node has up to 4 children, splitting along X and Y axes
Octree, //!< Tree where each node has up to 8 children, splitting along all axes (X, Y and Z)
};

//! constructs a skeleton chunk
QgsChunkNode( const QgsChunkNodeId &nodeId, const QgsAABB &bbox, float error, QgsChunkNode *parent = nullptr );
QgsChunkNode( Type type, const QgsChunkNodeId &nodeId, const QgsAABB &bbox, float error, QgsChunkNode *parent = nullptr );

~QgsChunkNode();

@@ -119,6 +138,8 @@ class QgsChunkNode
QgsChunkNode *const *children() const { return mChildren; }
//! Returns current state of the node
State state() const { return mState; }
//! Returns type of the tree (quadtree or octree)
Type type() const { return mType; }

//! Returns node's entry in the loader queue. Not NULLPTR only when in QueuedForLoad / QueuedForUpdate state
QgsChunkListEntry *loaderQueueEntry() const { return mLoaderQueueEntry; }
@@ -189,13 +210,14 @@ class QgsChunkNode
bool hasData() const { return mHasData; }

private:
Type mType; //!< Type of the tree this node belongs to
QgsAABB mBbox; //!< Bounding box in world coordinates
float mError; //!< Error of the node in world coordinates (negative error means that chunk at this level has no data, but there may be children that do)

QgsChunkNodeId mNodeId; //!< Chunk coordinates (for use with a tiling scheme)

QgsChunkNode *mParent; //!< TODO: should be shared pointer
QgsChunkNode *mChildren[4]; //!< TODO: should be weak pointers. May be nullptr if not created yet or removed already
QgsChunkNode *mChildren[8]; //!< TODO: should be weak pointers. May be nullptr if not created yet or removed already

State mState; //!< State of the node

@@ -70,12 +70,10 @@ QgsPointCloudLayer3DRenderer *QgsPointCloudLayer3DRenderer::clone() const
Qt3DCore::QEntity *QgsPointCloudLayer3DRenderer::createEntity( const Qgs3DMapSettings &map ) const
{
QgsPointCloudLayer *pcl = layer();
if ( !pcl || !pcl->dataProvider() )
if ( !pcl || !pcl->dataProvider() || !pcl->dataProvider()->index() )
return nullptr;

QgsPointCloudIndex *index = pcl->dataProvider()->index();

return new QgsPointCloudLayerChunkedEntity( pcl, index->zMin(), index->zMax(), map );
return new QgsPointCloudLayerChunkedEntity( pcl->dataProvider()->index(), map );
}

void QgsPointCloudLayer3DRenderer::writeXml( QDomElement &elem, const QgsReadWriteContext &context ) const
@@ -197,45 +197,38 @@ QgsPointCloudLayerChunkLoader::QgsPointCloudLayerChunkLoader( const QgsPointClou
, mFactory( factory )
, mContext( factory->mMap )
{
if ( node->level() < mFactory->mLeafLevel )
QgsPointCloudIndex *pc = mFactory->mPointCloudIndex;
QgsChunkNodeId nodeId = node->tileId();
IndexedPointCloudNode pcNode( nodeId.d, nodeId.x, nodeId.y, nodeId.z );

if ( !pc->hasNode( pcNode ) )
{
qDebug() << "child not exists" << pcNode.toString();
QTimer::singleShot( 0, this, &QgsPointCloudLayerChunkLoader::finished );
return;
}

qDebug() << "creating entity!";

QgsPointCloudLayer *layer = mFactory->mLayer;
const Qgs3DMapSettings &map = mFactory->mMap;
//const Qgs3DMapSettings &map = mFactory->mMap;

QgsPointCloud3DSymbolHandler *handler = new QgsPointCloud3DSymbolHandler;
mHandler.reset( handler );

QgsPointCloudIndex *pc = layer->dataProvider()->index();

// only a subset of data to be queried
QgsRectangle rect = Qgs3DUtils::worldToMapExtent( node->bbox(), map.origin() );
//req.setFilterRect( rect );

// TODO: set depth based on map units per pixel
int depth = 3;
QList<IndexedPointCloudNode> nodes = pc->traverseTree( rect, pc->root(), depth );

//
// this will be run in a background thread
//
QFuture<void> future = QtConcurrent::run( [pc, nodes, this]
QFuture<void> future = QtConcurrent::run( [pc, pcNode, this]
{
QgsEventTracing::ScopedEvent e( QStringLiteral( "3D" ), QStringLiteral( "PC chunk load" ) );

for ( const IndexedPointCloudNode &n : nodes )
qDebug() << "loading " << pcNode.toString();
if ( mCanceled )
{
if ( mCanceled )
{
qDebug() << "canceled";
break;
}
mHandler->processNode( pc, n, mContext );
qDebug() << "canceled";
return;
}
mHandler->processNode( pc, pcNode, mContext );
} );

// emit finished() as soon as the handler is populated with features
@@ -261,7 +254,10 @@ void QgsPointCloudLayerChunkLoader::cancel()

Qt3DCore::QEntity *QgsPointCloudLayerChunkLoader::createEntity( Qt3DCore::QEntity *parent )
{
if ( mNode->level() < mFactory->mLeafLevel )
QgsPointCloudIndex *pc = mFactory->mPointCloudIndex;
QgsChunkNodeId nodeId = mNode->tileId();
IndexedPointCloudNode pcNode( nodeId.d, nodeId.x, nodeId.y, nodeId.z );
if ( !pc->hasNode( pcNode ) )
{
return new Qt3DCore::QEntity( parent ); // dummy entity
}
@@ -275,9 +271,9 @@ Qt3DCore::QEntity *QgsPointCloudLayerChunkLoader::createEntity( Qt3DCore::QEntit
///////////////


QgsPointCloudLayerChunkLoaderFactory::QgsPointCloudLayerChunkLoaderFactory( const Qgs3DMapSettings &map, QgsPointCloudLayer *vl, int leafLevel )
QgsPointCloudLayerChunkLoaderFactory::QgsPointCloudLayerChunkLoaderFactory( const Qgs3DMapSettings &map, QgsPointCloudIndex *pc, int leafLevel )
: mMap( map )
, mLayer( vl )
, mPointCloudIndex( pc )
, mLeafLevel( leafLevel )
{
}
@@ -290,12 +286,26 @@ QgsChunkLoader *QgsPointCloudLayerChunkLoaderFactory::createChunkLoader( QgsChun

///////////////

QgsPointCloudLayerChunkedEntity::QgsPointCloudLayerChunkedEntity( QgsPointCloudLayer *vl, double zMin, double zMax, const Qgs3DMapSettings &map )
: QgsChunkedEntity( Qgs3DUtils::layerToWorldExtent( vl->extent(), zMin, zMax, vl->crs(), map.origin(), map.crs(), map.transformContext() ),
-1, // rootError (negative error means that the node does not contain anything)
-1, // max. allowed screen error (negative tau means that we need to go until leaves are reached)
0,
new QgsPointCloudLayerChunkLoaderFactory( map, vl, 0 ), true )

QgsAABB nodeBoundsToAABB( QgsPointCloudDataBounds nodeBounds, QgsVector3D offset, QgsVector3D scale, const Qgs3DMapSettings &map )
{
// TODO: reprojection from layer to map coordinates if needed
QgsVector3D extentMin3D( nodeBounds.xMin() * scale.x() + offset.x(), nodeBounds.yMin() * scale.y() + offset.y(), nodeBounds.zMin() * scale.z() + offset.z() );
QgsVector3D extentMax3D( nodeBounds.xMax() * scale.x() + offset.x(), nodeBounds.yMax() * scale.y() + offset.y(), nodeBounds.zMax() * scale.z() + offset.z() );
QgsVector3D worldExtentMin3D = Qgs3DUtils::mapToWorldCoordinates( extentMin3D, map.origin() );
QgsVector3D worldExtentMax3D = Qgs3DUtils::mapToWorldCoordinates( extentMax3D, map.origin() );
QgsAABB rootBbox( worldExtentMin3D.x(), worldExtentMin3D.y(), worldExtentMin3D.z(),
worldExtentMax3D.x(), worldExtentMax3D.y(), worldExtentMax3D.z() );
return rootBbox;
}

QgsPointCloudLayerChunkedEntity::QgsPointCloudLayerChunkedEntity( QgsPointCloudIndex *pc, const Qgs3DMapSettings &map )
: QgsChunkedEntity( QgsChunkNode::Octree,
nodeBoundsToAABB( pc->nodeBounds( IndexedPointCloudNode( 0, 0, 0, 0 ) ), pc->offset(), pc->scale(), map ),
pc->nodeError( IndexedPointCloudNode( 0, 0, 0, 0 ) ), // rootError
5, // max. allowed screen error (in pixels) -- // TODO
20, // max. number of levels (TODO)
new QgsPointCloudLayerChunkLoaderFactory( map, pc, 20 ), true )
{
setShowBoundingBoxes( true );
}
@@ -112,13 +112,13 @@ class QgsPointCloudLayerChunkLoaderFactory : public QgsChunkLoaderFactory
{
public:
//! Constructs the factory
QgsPointCloudLayerChunkLoaderFactory( const Qgs3DMapSettings &map, QgsPointCloudLayer *vl, int leafLevel );
QgsPointCloudLayerChunkLoaderFactory( const Qgs3DMapSettings &map, QgsPointCloudIndex *pc, int leafLevel );

//! Creates loader for the given chunk node. Ownership of the returned is passed to the caller.
virtual QgsChunkLoader *createChunkLoader( QgsChunkNode *node ) const override;

const Qgs3DMapSettings &mMap;
QgsPointCloudLayer *mLayer;
QgsPointCloudIndex *mPointCloudIndex;
int mLeafLevel;
};

@@ -164,7 +164,7 @@ class QgsPointCloudLayerChunkedEntity : public QgsChunkedEntity
{
Q_OBJECT
public:
explicit QgsPointCloudLayerChunkedEntity( QgsPointCloudLayer *vl, double zMin, double zMax, const Qgs3DMapSettings &map );
explicit QgsPointCloudLayerChunkedEntity( QgsPointCloudIndex *pc, const Qgs3DMapSettings &map );

~QgsPointCloudLayerChunkedEntity();
};
@@ -166,7 +166,8 @@ QgsChunkLoader *QgsRuleBasedChunkLoaderFactory::createChunkLoader( QgsChunkNode
///////////////

QgsRuleBasedChunkedEntity::QgsRuleBasedChunkedEntity( QgsVectorLayer *vl, double zMin, double zMax, const QgsVectorLayer3DTilingSettings &tilingSettings, QgsRuleBased3DRenderer::Rule *rootRule, const Qgs3DMapSettings &map )
: QgsChunkedEntity( Qgs3DUtils::layerToWorldExtent( vl->extent(), zMin, zMax, vl->crs(), map.origin(), map.crs(), map.transformContext() ),
: QgsChunkedEntity( QgsChunkNode::Quadtree,
Qgs3DUtils::layerToWorldExtent( vl->extent(), zMin, zMax, vl->crs(), map.origin(), map.crs(), map.transformContext() ),
-1, // rootError (negative error means that the node does not contain anything)
-1, // max. allowed screen error (negative tau means that we need to go until leaves are reached)
tilingSettings.zoomLevelsCount() - 1,

0 comments on commit 8fd7cc8

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