18 #include <QElapsedTimer> 
   20 #include <Qt3DRender/QObjectPicker> 
   21 #include <Qt3DRender/QPickTriangleEvent> 
   36 static float screenSpaceError( 
float epsilon, 
float distance, 
float screenSize, 
float fov )
 
   58   float phi = epsilon * screenSize / ( 2 * distance * tan( fov * M_PI / ( 2 * 180 ) ) );
 
   62 static float screenSpaceError( QgsChunkNode *node, 
const QgsChunkedEntity::SceneState &state )
 
   64   if ( node->error() <= 0 ) 
 
   67   float dist = node->bbox().distanceFromPoint( state.cameraPos );
 
   71   float sse = screenSpaceError( node->error(), dist, state.screenSizePx, state.cameraFov );
 
   75 QgsChunkedEntity::QgsChunkedEntity( 
float tau, QgsChunkLoaderFactory *loaderFactory, 
bool ownsFactory, 
int primitiveBudget, Qt3DCore::QNode *parent )
 
   78   , mChunkLoaderFactory( loaderFactory )
 
   79   , mOwnsFactory( ownsFactory )
 
   80   , mPrimitivesBudget( primitiveBudget )
 
   82   mRootNode = loaderFactory->createRootNode();
 
   83   mChunkLoaderQueue = 
new QgsChunkList;
 
   84   mReplacementQueue = 
new QgsChunkList;
 
   88 QgsChunkedEntity::~QgsChunkedEntity()
 
   93   Q_ASSERT( mActiveJobs.isEmpty() );
 
   96   while ( !mChunkLoaderQueue->isEmpty() )
 
   98     QgsChunkListEntry *entry = mChunkLoaderQueue->takeFirst();
 
   99     QgsChunkNode *node = entry->chunk;
 
  101     if ( node->state() == QgsChunkNode::QueuedForLoad )
 
  102       node->cancelQueuedForLoad();
 
  103     else if ( node->state() == QgsChunkNode::QueuedForUpdate )
 
  104       node->cancelQueuedForUpdate();
 
  109   delete mChunkLoaderQueue;
 
  111   while ( !mReplacementQueue->isEmpty() )
 
  113     QgsChunkListEntry *entry = mReplacementQueue->takeFirst();
 
  116     entry->chunk->unloadChunk(); 
 
  119   delete mReplacementQueue;
 
  124     delete mChunkLoaderFactory;
 
  129 void QgsChunkedEntity::update( 
const SceneState &state )
 
  137   int oldJobsCount = pendingJobsCount();
 
  139   QSet<QgsChunkNode *> activeBefore = qgis::listToSet( mActiveNodes );
 
  140   mActiveNodes.clear();
 
  142   mCurrentTime = QTime::currentTime();
 
  144   update( mRootNode, state );
 
  146   int enabled = 0, disabled = 0, unloaded = 0;
 
  148   for ( QgsChunkNode *node : mActiveNodes )
 
  150     if ( activeBefore.contains( node ) )
 
  152       activeBefore.remove( node );
 
  156       node->entity()->setEnabled( 
true );
 
  162   for ( QgsChunkNode *node : activeBefore )
 
  164     node->entity()->setEnabled( 
false );
 
  170   while ( mReplacementQueue->count() > mMaxLoadedChunks )
 
  172     QgsChunkListEntry *entry = mReplacementQueue->takeLast();
 
  173     entry->chunk->unloadChunk();  
 
  179     QList<QgsAABB> bboxes;
 
  180     for ( QgsChunkNode *n : std::as_const( mActiveNodes ) )
 
  182     mBboxesEntity->setBoxes( bboxes );
 
  188   mNeedsUpdate = 
false;  
 
  190   if ( pendingJobsCount() != oldJobsCount )
 
  191     emit pendingJobsCountChanged();
 
  193   QgsDebugMsgLevel( QStringLiteral( 
"update: active %1 enabled %2 disabled %3 | culled %4 | loading %5 loaded %6 | unloaded %7 elapsed %8ms" ).arg( mActiveNodes.count() )
 
  196                     .arg( mFrustumCulled )
 
  197                     .arg( mReplacementQueue->count() )
 
  199                     .arg( t.elapsed() ), 2 );
 
  202 void QgsChunkedEntity::setShowBoundingBoxes( 
bool enabled )
 
  204   if ( ( enabled && mBboxesEntity ) || ( !enabled && !mBboxesEntity ) )
 
  209     mBboxesEntity = 
new QgsChunkBoundsEntity( 
this );
 
  213     mBboxesEntity->deleteLater();
 
  214     mBboxesEntity = 
nullptr;
 
  218 void QgsChunkedEntity::updateNodes( 
const QList<QgsChunkNode *> &nodes, QgsChunkQueueJobFactory *updateJobFactory )
 
  220   for ( QgsChunkNode *node : nodes )
 
  222     if ( node->state() == QgsChunkNode::QueuedForUpdate )
 
  224       mChunkLoaderQueue->takeEntry( node->loaderQueueEntry() );
 
  225       node->cancelQueuedForUpdate();
 
  227     else if ( node->state() == QgsChunkNode::Updating )
 
  229       cancelActiveJob( node->updater() );
 
  232     Q_ASSERT( node->state() == QgsChunkNode::Loaded );
 
  234     QgsChunkListEntry *entry = 
new QgsChunkListEntry( node );
 
  235     node->setQueuedForUpdate( entry, updateJobFactory );
 
  236     mChunkLoaderQueue->insertLast( entry );
 
  243 int QgsChunkedEntity::pendingJobsCount()
 const 
  245   return mChunkLoaderQueue->count() + mActiveJobs.count();
 
  248 struct ResidencyRequest
 
  250   QgsChunkNode *node = 
nullptr;
 
  253   ResidencyRequest() = 
default;
 
  266   bool operator()( 
const ResidencyRequest &request, 
const ResidencyRequest &otherRequest )
 const 
  268     if ( request.level == otherRequest.level )
 
  269       return request.dist > otherRequest.dist;
 
  270     return request.level > otherRequest.level;
 
  272 } ResidencyRequestSorter;
 
  274 void QgsChunkedEntity::update( QgsChunkNode *root, 
const SceneState &state )
 
  276   QSet<QgsChunkNode *> nodes;
 
  277   QVector<ResidencyRequest> residencyRequests;
 
  279   using slotItem = std::pair<QgsChunkNode *, float>;
 
  280   auto cmp_funct = []( slotItem & p1, slotItem & p2 )
 
  282     return p1.second <= p2.second;
 
  284   int renderedCount = 0;
 
  285   std::priority_queue<slotItem, std::vector<slotItem>, decltype( cmp_funct )> pq( cmp_funct );
 
  286   pq.push( std::make_pair( root, screenSpaceError( root, state ) ) );
 
  287   while ( !pq.empty() && renderedCount <= mPrimitivesBudget )
 
  289     slotItem s = pq.top();
 
  291     QgsChunkNode *node = s.first;
 
  300     if ( node->childCount() == -1 )
 
  301       node->populateChildren( mChunkLoaderFactory->createChildren( node ) );
 
  305     double dist = node->bbox().center().distanceToPoint( state.cameraPos );
 
  306     residencyRequests.push_back( ResidencyRequest( node, dist, node->level() ) );
 
  308     if ( !node->entity() )
 
  313     bool becomesActive = 
false;
 
  316     if ( node->childCount() == 0 )
 
  320       becomesActive = 
true;
 
  322     else if ( mTau > 0 && screenSpaceError( node, state ) <= mTau )
 
  325       becomesActive = 
true;
 
  327     else if ( node->allChildChunksResident( mCurrentTime ) )
 
  330       if ( mAdditiveStrategy )
 
  335         becomesActive = 
true;
 
  337       QgsChunkNode *
const *children = node->children();
 
  338       for ( 
int i = 0; i < node->childCount(); ++i )
 
  339         pq.push( std::make_pair( children[i], screenSpaceError( children[i], state ) ) );
 
  344       becomesActive = 
true;
 
  346       QgsChunkNode *
const *children = node->children();
 
  347       for ( 
int i = 0; i < node->childCount(); ++i )
 
  349         double dist = children[i]->bbox().center().distanceToPoint( state.cameraPos );
 
  350         residencyRequests.push_back( ResidencyRequest( children[i], dist, children[i]->level() ) );
 
  355       mActiveNodes << node;
 
  357       if ( !mAdditiveStrategy && node->parent() && nodes.contains( node->parent() ) )
 
  359         nodes.remove( node->parent() );
 
  360         renderedCount -= mChunkLoaderFactory->primitivesCount( node->parent() );
 
  362       renderedCount += mChunkLoaderFactory->primitivesCount( node );
 
  363       nodes.insert( node );
 
  368   std::sort( residencyRequests.begin(), residencyRequests.end(), ResidencyRequestSorter );
 
  369   for ( 
const auto &request : residencyRequests )
 
  370     requestResidency( request.node );
 
  373 void QgsChunkedEntity::requestResidency( QgsChunkNode *node )
 
  375   if ( node->state() == QgsChunkNode::Loaded || node->state() == QgsChunkNode::QueuedForUpdate || node->state() == QgsChunkNode::Updating )
 
  377     Q_ASSERT( node->replacementQueueEntry() );
 
  378     Q_ASSERT( node->entity() );
 
  379     mReplacementQueue->takeEntry( node->replacementQueueEntry() );
 
  380     mReplacementQueue->insertFirst( node->replacementQueueEntry() );
 
  382   else if ( node->state() == QgsChunkNode::QueuedForLoad )
 
  385     Q_ASSERT( node->loaderQueueEntry() );
 
  386     Q_ASSERT( !node->loader() );
 
  387     if ( node->loaderQueueEntry()->prev || node->loaderQueueEntry()->next )
 
  389       mChunkLoaderQueue->takeEntry( node->loaderQueueEntry() );
 
  390       mChunkLoaderQueue->insertFirst( node->loaderQueueEntry() );
 
  393   else if ( node->state() == QgsChunkNode::Loading )
 
  397   else if ( node->state() == QgsChunkNode::Skeleton )
 
  399     if ( !node->hasData() )
 
  403     QgsChunkListEntry *entry = 
new QgsChunkListEntry( node );
 
  404     node->setQueuedForLoad( entry );
 
  405     mChunkLoaderQueue->insertFirst( entry );
 
  408     Q_ASSERT( 
false && 
"impossible!" );
 
  412 void QgsChunkedEntity::onActiveJobFinished()
 
  414   int oldJobsCount = pendingJobsCount();
 
  416   QgsChunkQueueJob *job = qobject_cast<QgsChunkQueueJob *>( sender() );
 
  418   Q_ASSERT( mActiveJobs.contains( job ) );
 
  420   QgsChunkNode *node = job->chunk();
 
  422   if ( QgsChunkLoader *loader = qobject_cast<QgsChunkLoader *>( job ) )
 
  424     Q_ASSERT( node->state() == QgsChunkNode::Loading );
 
  425     Q_ASSERT( node->loader() == loader );
 
  427     QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral( 
"3D" ), QStringLiteral( 
"Load " ) + node->tileId().text(), node->tileId().text() );
 
  428     QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral( 
"3D" ), QStringLiteral( 
"Load" ), node->tileId().text() );
 
  430     QgsEventTracing::ScopedEvent e( 
"3D", QString( 
"create" ) );
 
  432     Qt3DCore::QEntity *entity = node->loader()->createEntity( 
this );
 
  437       node->setLoaded( entity );
 
  439       mReplacementQueue->insertFirst( node->replacementQueueEntry() );
 
  441       if ( mPickingEnabled )
 
  443         Qt3DRender::QObjectPicker *picker = 
new Qt3DRender::QObjectPicker( node->entity() );
 
  444         node->entity()->addComponent( picker );
 
  445         connect( picker, &Qt3DRender::QObjectPicker::clicked, 
this, &QgsChunkedEntity::onPickEvent );
 
  448       emit newEntityCreated( entity );
 
  452       node->setHasData( 
false );
 
  453       node->cancelLoading();
 
  461     Q_ASSERT( node->state() == QgsChunkNode::Updating );
 
  462     QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral( 
"3D" ), QStringLiteral( 
"Update" ), node->tileId().text() );
 
  467   mActiveJobs.removeOne( job );
 
  473   if ( pendingJobsCount() != oldJobsCount )
 
  474     emit pendingJobsCountChanged();
 
  477 void QgsChunkedEntity::startJobs()
 
  479   while ( mActiveJobs.count() < 4 )
 
  481     if ( mChunkLoaderQueue->isEmpty() )
 
  484     QgsChunkListEntry *entry = mChunkLoaderQueue->takeFirst();
 
  486     QgsChunkNode *node = entry->chunk;
 
  489     QgsChunkQueueJob *job = startJob( node );
 
  490     mActiveJobs.append( job );
 
  494 QgsChunkQueueJob *QgsChunkedEntity::startJob( QgsChunkNode *node )
 
  496   if ( node->state() == QgsChunkNode::QueuedForLoad )
 
  498     QgsEventTracing::addEvent( QgsEventTracing::AsyncBegin, QStringLiteral( 
"3D" ), QStringLiteral( 
"Load" ), node->tileId().text() );
 
  499     QgsEventTracing::addEvent( QgsEventTracing::AsyncBegin, QStringLiteral( 
"3D" ), QStringLiteral( 
"Load " ) + node->tileId().text(), node->tileId().text() );
 
  501     QgsChunkLoader *loader = mChunkLoaderFactory->createChunkLoader( node );
 
  502     connect( loader, &QgsChunkQueueJob::finished, 
this, &QgsChunkedEntity::onActiveJobFinished );
 
  503     node->setLoading( loader );
 
  506   else if ( node->state() == QgsChunkNode::QueuedForUpdate )
 
  508     QgsEventTracing::addEvent( QgsEventTracing::AsyncBegin, QStringLiteral( 
"3D" ), QStringLiteral( 
"Update" ), node->tileId().text() );
 
  511     connect( node->updater(), &QgsChunkQueueJob::finished, 
this, &QgsChunkedEntity::onActiveJobFinished );
 
  512     return node->updater();
 
  521 void QgsChunkedEntity::cancelActiveJob( QgsChunkQueueJob *job )
 
  525   QgsChunkNode *node = job->chunk();
 
  527   if ( qobject_cast<QgsChunkLoader *>( job ) )
 
  530     node->cancelLoading();
 
  532     QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral( 
"3D" ), QStringLiteral( 
"Load " ) + node->tileId().text(), node->tileId().text() );
 
  533     QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral( 
"3D" ), QStringLiteral( 
"Load" ), node->tileId().text() );
 
  538     node->cancelUpdating();
 
  540     QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral( 
"3D" ), QStringLiteral( 
"Update" ), node->tileId().text() );
 
  544   mActiveJobs.removeOne( job );
 
  548 void QgsChunkedEntity::cancelActiveJobs()
 
  550   while ( !mActiveJobs.isEmpty() )
 
  552     cancelActiveJob( mActiveJobs.takeFirst() );
 
  557 void QgsChunkedEntity::setPickingEnabled( 
bool enabled )
 
  559   if ( mPickingEnabled == enabled )
 
  562   mPickingEnabled = enabled;
 
  566     QgsChunkListEntry *entry = mReplacementQueue->first();
 
  569       QgsChunkNode *node = entry->chunk;
 
  570       Qt3DRender::QObjectPicker *picker = 
new Qt3DRender::QObjectPicker( node->entity() );
 
  571       node->entity()->addComponent( picker );
 
  572       connect( picker, &Qt3DRender::QObjectPicker::clicked, 
this, &QgsChunkedEntity::onPickEvent );
 
  579     for ( Qt3DRender::QObjectPicker *picker : findChildren<Qt3DRender::QObjectPicker *>() )
 
  580       picker->deleteLater();
 
  584 void QgsChunkedEntity::onPickEvent( Qt3DRender::QPickEvent *event )
 
  586   Qt3DRender::QPickTriangleEvent *triangleEvent = qobject_cast<Qt3DRender::QPickTriangleEvent *>( event );
 
  587   if ( !triangleEvent )
 
  590   Qt3DRender::QObjectPicker *picker = qobject_cast<Qt3DRender::QObjectPicker *>( sender() );
 
  594   Qt3DCore::QEntity *entity = qobject_cast<Qt3DCore::QEntity *>( picker->parent() );
 
  600   for ( Qt3DRender::QGeometryRenderer *geomRenderer : entity->findChildren<Qt3DRender::QGeometryRenderer *>() )
 
  605     if ( geomRenderer->objectName() != QLatin1String( 
"main" ) )
 
  610       fid = g->triangleIndexToFeatureId( triangleEvent->triangleIndex() );
 
  618     emit pickedObject( event, fid );
 
static bool isCullable(const QgsAABB &bbox, const QMatrix4x4 &viewProjectionMatrix)
Returns true if bbox is completely outside the current viewing volume.
qint64 QgsFeatureId
64 bit feature ids negative numbers are used for uncommitted/newly added features
#define QgsDebugMsgLevel(str, level)