Skip to content
Permalink
Browse files

Refactor QgsChunkNode + QgsChunkedEntity for more flexibility

- QgsChunkNode can have variable number of children
- QgsChunkLoaderFactory implementations get responsibility to return
  root node and child nodes of any node in the hierarchy
- new QgsQuadtreeChunkLoaderFactory class to implement quadtree strategy
- point cloud chunk loader updated to only return children that exist in data
  • Loading branch information
wonder-sk authored and nyalldawson committed Oct 26, 2020
1 parent 0638f75 commit 8d12772405075bd8dc1674443c61981bd84a6c00
@@ -43,6 +43,7 @@ SET(QGIS_3D_SRCS
chunks/qgschunkboundsentity_p.cpp
chunks/qgschunkedentity_p.cpp
chunks/qgschunklist_p.cpp
chunks/qgschunkloader_p.cpp
chunks/qgschunknode_p.cpp
chunks/qgschunkqueuejob_p.cpp

@@ -70,15 +70,13 @@ static float screenSpaceError( QgsChunkNode *node, const QgsChunkedEntity::Scene
return sse;
}

QgsChunkedEntity::QgsChunkedEntity( QgsChunkNode::Type type, const QgsAABB &rootBbox, float rootError, float tau, int maxLevel, QgsChunkLoaderFactory *loaderFactory, bool ownsFactory, Qt3DCore::QNode *parent )
QgsChunkedEntity::QgsChunkedEntity( float tau, QgsChunkLoaderFactory *loaderFactory, bool ownsFactory, Qt3DCore::QNode *parent )
: Qt3DCore::QEntity( parent )
, mTau( tau )
, mMaxLevel( maxLevel )
, mChunkLoaderFactory( loaderFactory )
, mOwnsFactory( ownsFactory )
{
QgsChunkNodeId rootNodeId( 0, 0, 0, type == QgsChunkNode::Quadtree ? -1 : 0 );
mRootNode = new QgsChunkNode( type, rootNodeId, rootBbox, rootError );
mRootNode = loaderFactory->createRootNode();
mChunkLoaderQueue = new QgsChunkList;
mReplacementQueue = new QgsChunkList;
}
@@ -245,7 +243,11 @@ void QgsChunkedEntity::update( QgsChunkNode *node, const SceneState &state )
return;
}

node->ensureAllChildrenExist();
// ensure we have child nodes (at least skeletons) available, if any
if ( node->childCount() == -1 )
{
node->populateChildren( mChunkLoaderFactory->createChildren( node ) );
}

// make sure all nodes leading to children are always loaded
// so that zooming out does not create issues
@@ -270,8 +272,7 @@ 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();
int childCount = node->type() == QgsChunkNode::Quadtree ? 4 : 8;
for ( int i = 0; i < childCount; ++i )
for ( int i = 0; i < node->childCount(); ++i )
update( children[i], state );
}
else
@@ -280,13 +281,9 @@ void QgsChunkedEntity::update( QgsChunkNode *node, const SceneState &state )

mActiveNodes << node;

if ( node->level() < mMaxLevel )
{
QgsChunkNode *const *children = node->children();
int childCount = node->type() == QgsChunkNode::Quadtree ? 4 : 8;
for ( int i = 0; i < childCount; ++i )
requestResidency( children[i] );
}
QgsChunkNode *const *children = node->children();
for ( int i = 0; i < node->childCount(); ++i )
requestResidency( children[i] );
}
}

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

//! Records some bits about the scene (context for update() method)
@@ -145,8 +145,6 @@ class QgsChunkedEntity : public Qt3DCore::QEntity
* it reaches leafs.
*/
float mTau;
//! maximum allowed depth of quad tree
int mMaxLevel;
//! factory that creates loaders for individual chunk nodes
QgsChunkLoaderFactory *mChunkLoaderFactory = nullptr;
//! True if entity owns the factory
@@ -0,0 +1,66 @@
/***************************************************************************
qgschunkloader_p.cpp
--------------------------------------
Date : October 2020
Copyright : (C) 2020 by Martin Dobias
Email : wonder dot sk at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/

#include "qgschunkloader_p.h"

#include "qgschunknode_p.h"
#include <QVector>


QgsQuadtreeChunkLoaderFactory::QgsQuadtreeChunkLoaderFactory() = default;

QgsQuadtreeChunkLoaderFactory::~QgsQuadtreeChunkLoaderFactory() = default;

void QgsQuadtreeChunkLoaderFactory::setupQuadtree( const QgsAABB &rootBbox, float rootError, int maxLevel )
{
mRootBbox = rootBbox;
mRootError = rootError;
mMaxLevel = maxLevel;
}

QgsChunkNode *QgsQuadtreeChunkLoaderFactory::createRootNode() const
{
return new QgsChunkNode( QgsChunkNodeId( 0, 0, 0 ), mRootBbox, mRootError );
}

QVector<QgsChunkNode *> QgsQuadtreeChunkLoaderFactory::createChildren( QgsChunkNode *node ) const
{
QVector<QgsChunkNode *> children;

if ( node->level() >= mMaxLevel )
return children;

QgsChunkNodeId nodeId = node->tileId();
float childError = node->error() / 2;
QgsAABB bbox = node->bbox();
float xc = bbox.xCenter(), zc = bbox.zCenter();

for ( int i = 0; i < 4; ++i )
{
int dx = i & 1, dy = !!( i & 2 );
QgsChunkNodeId childId( nodeId.d + 1, nodeId.x * 2 + dx, nodeId.y * 2 + dy );
// 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 : bbox.xMin;
float chXMax = dx ? bbox.xMax : xc;
float chZMin = dy ? zc : bbox.zMin;
float chZMax = dy ? bbox.zMax : zc;
float chYMin = bbox.yMin;
float chYMax = bbox.yMax;
children << new QgsChunkNode( childId, QgsAABB( chXMin, chYMin, chZMin, chXMax, chYMax, chZMax ), childError, node );
}
return children;
}
@@ -70,6 +70,41 @@ class QgsChunkLoaderFactory

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

//! Creates root node of the hierarchy. Ownership of the returned object is passed to the caller.
virtual QgsChunkNode *createRootNode() const = 0;
//! Creates child nodes for the given node. Ownership of the returned objects is passed to the caller.
virtual QVector<QgsChunkNode *> createChildren( QgsChunkNode *node ) const = 0;
};


#include "qgsaabb.h"

/**
* \ingroup 3d
* Base class for factories where the hierarchy is a quadtree where all leaves
* are in the same depth.
*
* \since QGIS 3.18
*/
class _3D_EXPORT QgsQuadtreeChunkLoaderFactory : public QgsChunkLoaderFactory
{
public:
QgsQuadtreeChunkLoaderFactory();
virtual ~QgsQuadtreeChunkLoaderFactory();

//! Initializes the root node setup (bounding box and error) and tree depth
void setupQuadtree( const QgsAABB &rootBbox, float rootError, int maxLevel );

virtual QgsChunkNode *createRootNode() const override;
virtual QVector<QgsChunkNode *> createChildren( QgsChunkNode *node ) const override;

protected:
QgsAABB mRootBbox;
float mRootError;
//! maximum allowed depth of quad tree
int mMaxLevel;

};

/// @endcond
@@ -22,9 +22,8 @@

///@cond PRIVATE

QgsChunkNode::QgsChunkNode( Type type, const QgsChunkNodeId &nodeId, const QgsAABB &bbox, float error, QgsChunkNode *parent )
: mType( type )
, mBbox( bbox )
QgsChunkNode::QgsChunkNode( const QgsChunkNodeId &nodeId, const QgsAABB &bbox, float error, QgsChunkNode *parent )
: mBbox( bbox )
, mError( error )
, mNodeId( nodeId )
, mParent( parent )
@@ -36,8 +35,8 @@ QgsChunkNode::QgsChunkNode( Type type, const QgsChunkNodeId &nodeId, const QgsAA
, mUpdaterFactory( nullptr )
, mUpdater( nullptr )
{
int childCount = mType == Quadtree ? 4 : 8;
for ( int i = 0; i < childCount; ++i )
// TODO: still using a fixed size array. Use QVector instead?
for ( int i = 0; i < 8; ++i )
mChildren[i] = nullptr;
}

@@ -51,15 +50,14 @@ QgsChunkNode::~QgsChunkNode()
Q_ASSERT( !mUpdater );
Q_ASSERT( !mUpdaterFactory );

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

bool QgsChunkNode::allChildChunksResident( QTime currentTime ) const
{
int childCount = mType == Quadtree ? 4 : 8;
for ( int i = 0; i < childCount; ++i )
Q_ASSERT( mChildCount != -1 );
for ( int i = 0; i < childCount(); ++i )
{
if ( !mChildren[i] )
return false; // not even a skeleton
@@ -72,35 +70,12 @@ bool QgsChunkNode::allChildChunksResident( QTime currentTime ) const
return true;
}

void QgsChunkNode::ensureAllChildrenExist()
void QgsChunkNode::populateChildren( const QVector<QgsChunkNode *> &children )
{
float childError = mError / 2;
float xc = mBbox.xCenter(), yc = mBbox.yCenter(), zc = mBbox.zCenter();

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 );
}
Q_ASSERT( mChildCount == -1 );
mChildCount = children.count();
for ( int i = 0; i < mChildCount; ++i )
mChildren[i] = children[i];
}

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

int childCount = mType == Quadtree ? 4 : 8;
for ( int i = 0; i < childCount; ++i )
for ( int i = 0; i < childCount(); ++i )
{
if ( mChildren[i] )
lst << mChildren[i]->descendants();
@@ -87,14 +87,8 @@ 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( Type type, const QgsChunkNodeId &nodeId, const QgsAABB &bbox, float error, QgsChunkNode *parent = nullptr );
QgsChunkNode( const QgsChunkNodeId &nodeId, const QgsAABB &bbox, float error, QgsChunkNode *parent = nullptr );

~QgsChunkNode();

@@ -134,12 +128,12 @@ class QgsChunkNode
QgsChunkNodeId tileId() const { return mNodeId; }
//! Returns pointer to the parent node. Parent is NULLPTR in the root node
QgsChunkNode *parent() const { return mParent; }
//! Returns number of children of the node (returns -1 if the node has not yet been populated with populateChildren())
int childCount() const { return mChildCount; }
//! Returns array of the four children. Children may be NULLPTR if they were not created yet
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; }
@@ -155,8 +149,8 @@ class QgsChunkNode
//! Returns TRUE if all child chunks are available and thus this node could be swapped to the child nodes
bool allChildChunksResident( QTime currentTime ) const;

//! make sure that all child nodes are at least skeleton nodes
void ensureAllChildrenExist();
//! Sets child nodes of this node. Takes ownership of all objects. Must be only called once.
void populateChildren( const QVector<QgsChunkNode *> &children );

//! how deep is the node in the tree (zero means root node, every level adds one)
int level() const;
@@ -210,14 +204,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[8]; //!< TODO: should be weak pointers. May be nullptr if not created yet or removed already
int mChildCount = -1; //! Number of children (-1 == not yet populated)

State mState; //!< State of the node

@@ -492,8 +492,11 @@ void Qgs3DMapScene::createTerrainDeferred()
{
double tile0width = mMap.terrainGenerator()->extent().width();
int maxZoomLevel = Qgs3DUtils::maxZoomLevel( tile0width, mMap.mapTileResolution(), mMap.maxTerrainGroundError() );
QgsAABB rootBbox = mMap.terrainGenerator()->rootChunkBbox( mMap );
float rootError = mMap.terrainGenerator()->rootChunkError( mMap );
mMap.terrainGenerator()->setupQuadtree( rootBbox, rootError, maxZoomLevel );

mTerrain = new QgsTerrainEntity( maxZoomLevel, mMap );
mTerrain = new QgsTerrainEntity( mMap );
//mTerrain->setEnabled(false);
mTerrain->setParent( this );

0 comments on commit 8d12772

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