18 #include <QElapsedTimer>    20 #include <Qt3DRender/QObjectPicker>    21 #include <Qt3DRender/QPickTriangleEvent>    34 static float screenSpaceError( 
float epsilon, 
float distance, 
float screenSize, 
float fov )
    56   float phi = epsilon * screenSize / ( 2 * distance * tan( fov * M_PI / ( 2 * 180 ) ) );
    60 static float screenSpaceError( QgsChunkNode *node, 
const QgsChunkedEntity::SceneState &state )
    62   if ( node->error() <= 0 ) 
    65   float dist = node->bbox().distanceFromPoint( state.cameraPos );
    69   float sse = screenSpaceError( node->error(), dist, state.screenSizePx, state.cameraFov );
    73 QgsChunkedEntity::QgsChunkedEntity( 
const QgsAABB &rootBbox, 
float rootError, 
float tau, 
int maxLevel, QgsChunkLoaderFactory *loaderFactory, Qt3DCore::QNode *parent )
    76   , mMaxLevel( maxLevel )
    77   , mChunkLoaderFactory( loaderFactory )
    79   mRootNode = 
new QgsChunkNode( 0, 0, 0, rootBbox, rootError );
    80   mChunkLoaderQueue = 
new QgsChunkList;
    81   mReplacementQueue = 
new QgsChunkList;
    85 QgsChunkedEntity::~QgsChunkedEntity()
    90   Q_ASSERT( mActiveJobs.isEmpty() );
    93   while ( !mChunkLoaderQueue->isEmpty() )
    95     QgsChunkListEntry *entry = mChunkLoaderQueue->takeFirst();
    96     QgsChunkNode *node = entry->chunk;
    98     if ( node->state() == QgsChunkNode::QueuedForLoad )
    99       node->cancelQueuedForLoad();
   100     else if ( node->state() == QgsChunkNode::QueuedForUpdate )
   101       node->cancelQueuedForUpdate();
   106   delete mChunkLoaderQueue;
   108   while ( !mReplacementQueue->isEmpty() )
   110     QgsChunkListEntry *entry = mReplacementQueue->takeFirst();
   113     entry->chunk->unloadChunk(); 
   116   delete mReplacementQueue;
   124 void QgsChunkedEntity::update( 
const SceneState &state )
   129   int oldJobsCount = pendingJobsCount();
   131   QSet<QgsChunkNode *> activeBefore = QSet<QgsChunkNode *>::fromList( mActiveNodes );
   132   mActiveNodes.clear();
   134   mCurrentTime = QTime::currentTime();
   136   update( mRootNode, state );
   138   int enabled = 0, disabled = 0, unloaded = 0;
   140   Q_FOREACH ( QgsChunkNode *node, mActiveNodes )
   142     if ( activeBefore.contains( node ) )
   143       activeBefore.remove( node );
   146       node->entity()->setEnabled( 
true );
   152   Q_FOREACH ( QgsChunkNode *node, activeBefore )
   154     node->entity()->setEnabled( 
false );
   160   while ( mReplacementQueue->count() > mMaxLoadedChunks )
   162     QgsChunkListEntry *entry = mReplacementQueue->takeLast();
   163     entry->chunk->unloadChunk();  
   169     QList<QgsAABB> bboxes;
   170     Q_FOREACH ( QgsChunkNode *n, mActiveNodes )
   172     mBboxesEntity->setBoxes( bboxes );
   178   mNeedsUpdate = 
false;  
   180   if ( pendingJobsCount() != oldJobsCount )
   181     emit pendingJobsCountChanged();
   186 void QgsChunkedEntity::setShowBoundingBoxes( 
bool enabled )
   188   if ( ( enabled && mBboxesEntity ) || ( !enabled && !mBboxesEntity ) )
   193     mBboxesEntity = 
new QgsChunkBoundsEntity( 
this );
   197     mBboxesEntity->deleteLater();
   198     mBboxesEntity = 
nullptr;
   202 void QgsChunkedEntity::updateNodes( 
const QList<QgsChunkNode *> &nodes, QgsChunkQueueJobFactory *updateJobFactory )
   204   Q_FOREACH ( QgsChunkNode *node, nodes )
   206     if ( node->state() == QgsChunkNode::QueuedForUpdate )
   208       mChunkLoaderQueue->takeEntry( node->loaderQueueEntry() );
   209       node->cancelQueuedForUpdate();
   211     else if ( node->state() == QgsChunkNode::Updating )
   213       cancelActiveJob( node->updater() );
   216     Q_ASSERT( node->state() == QgsChunkNode::Loaded );
   218     QgsChunkListEntry *entry = 
new QgsChunkListEntry( node );
   219     node->setQueuedForUpdate( entry, updateJobFactory );
   220     mChunkLoaderQueue->insertLast( entry );
   227 int QgsChunkedEntity::pendingJobsCount()
 const   229   return mChunkLoaderQueue->count() + mActiveJobs.count();
   233 void QgsChunkedEntity::update( QgsChunkNode *node, 
const SceneState &state )
   241   node->ensureAllChildrenExist();
   245   requestResidency( node );
   247   if ( !node->entity() )
   255   if ( mTau > 0 && screenSpaceError( node, state ) <= mTau )
   259     mActiveNodes << node;
   261   else if ( node->allChildChunksResident( mCurrentTime ) )
   265     QgsChunkNode *
const *children = node->children();
   266     for ( 
int i = 0; i < 4; ++i )
   267       update( children[i], state );
   273     mActiveNodes << node;
   275     if ( node->level() < mMaxLevel )
   277       QgsChunkNode *
const *children = node->children();
   278       for ( 
int i = 0; i < 4; ++i )
   279         requestResidency( children[i] );
   285 void QgsChunkedEntity::requestResidency( QgsChunkNode *node )
   287   if ( node->state() == QgsChunkNode::Loaded || node->state() == QgsChunkNode::QueuedForUpdate || node->state() == QgsChunkNode::Updating )
   289     Q_ASSERT( node->replacementQueueEntry() );
   290     Q_ASSERT( node->entity() );
   291     mReplacementQueue->takeEntry( node->replacementQueueEntry() );
   292     mReplacementQueue->insertFirst( node->replacementQueueEntry() );
   294   else if ( node->state() == QgsChunkNode::QueuedForLoad )
   297     Q_ASSERT( node->loaderQueueEntry() );
   298     Q_ASSERT( !node->loader() );
   299     if ( node->loaderQueueEntry()->prev || node->loaderQueueEntry()->next )
   301       mChunkLoaderQueue->takeEntry( node->loaderQueueEntry() );
   302       mChunkLoaderQueue->insertFirst( node->loaderQueueEntry() );
   305   else if ( node->state() == QgsChunkNode::Loading )
   309   else if ( node->state() == QgsChunkNode::Skeleton )
   311     if ( !node->hasData() )
   315     QgsChunkListEntry *entry = 
new QgsChunkListEntry( node );
   316     node->setQueuedForLoad( entry );
   317     mChunkLoaderQueue->insertFirst( entry );
   320     Q_ASSERT( 
false && 
"impossible!" );
   324 void QgsChunkedEntity::onActiveJobFinished()
   326   int oldJobsCount = pendingJobsCount();
   328   QgsChunkQueueJob *job = qobject_cast<QgsChunkQueueJob *>( sender() );
   330   Q_ASSERT( mActiveJobs.contains( job ) );
   332   QgsChunkNode *node = job->chunk();
   334   if ( QgsChunkLoader *loader = qobject_cast<QgsChunkLoader *>( job ) )
   336     Q_ASSERT( node->state() == QgsChunkNode::Loading );
   337     Q_ASSERT( node->loader() == loader );
   339     QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral( 
"3D" ), QStringLiteral( 
"Load " ) + node->tileId().text(), node->tileId().text() );
   340     QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral( 
"3D" ), QStringLiteral( 
"Load" ), node->tileId().text() );
   342     QgsEventTracing::ScopedEvent e( 
"3D", QString( 
"create" ) );
   344     Qt3DCore::QEntity *entity = node->loader()->createEntity( 
this );
   349       node->setLoaded( entity );
   351       mReplacementQueue->insertFirst( node->replacementQueueEntry() );
   353       if ( mPickingEnabled )
   355         Qt3DRender::QObjectPicker *picker = 
new Qt3DRender::QObjectPicker( node->entity() );
   356         node->entity()->addComponent( picker );
   357         connect( picker, &Qt3DRender::QObjectPicker::clicked, 
this, &QgsChunkedEntity::onPickEvent );
   360       emit newEntityCreated( entity );
   364       node->setHasData( 
false );
   365       node->cancelLoading();
   373     Q_ASSERT( node->state() == QgsChunkNode::Updating );
   374     QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral( 
"3D" ), QStringLiteral( 
"Update" ), node->tileId().text() );
   379   mActiveJobs.removeOne( job );
   385   if ( pendingJobsCount() != oldJobsCount )
   386     emit pendingJobsCountChanged();
   389 void QgsChunkedEntity::startJobs()
   391   while ( mActiveJobs.count() < 4 )
   393     if ( mChunkLoaderQueue->isEmpty() )
   396     QgsChunkListEntry *entry = mChunkLoaderQueue->takeFirst();
   398     QgsChunkNode *node = entry->chunk;
   401     QgsChunkQueueJob *job = startJob( node );
   402     mActiveJobs.append( job );
   406 QgsChunkQueueJob *QgsChunkedEntity::startJob( QgsChunkNode *node )
   408   if ( node->state() == QgsChunkNode::QueuedForLoad )
   410     QgsEventTracing::addEvent( QgsEventTracing::AsyncBegin, QStringLiteral( 
"3D" ), QStringLiteral( 
"Load" ), node->tileId().text() );
   411     QgsEventTracing::addEvent( QgsEventTracing::AsyncBegin, QStringLiteral( 
"3D" ), QStringLiteral( 
"Load " ) + node->tileId().text(), node->tileId().text() );
   413     QgsChunkLoader *loader = mChunkLoaderFactory->createChunkLoader( node );
   414     connect( loader, &QgsChunkQueueJob::finished, 
this, &QgsChunkedEntity::onActiveJobFinished );
   415     node->setLoading( loader );
   418   else if ( node->state() == QgsChunkNode::QueuedForUpdate )
   420     QgsEventTracing::addEvent( QgsEventTracing::AsyncBegin, QStringLiteral( 
"3D" ), QStringLiteral( 
"Update" ), node->tileId().text() );
   423     connect( node->updater(), &QgsChunkQueueJob::finished, 
this, &QgsChunkedEntity::onActiveJobFinished );
   424     return node->updater();
   433 void QgsChunkedEntity::cancelActiveJob( QgsChunkQueueJob *job )
   437   QgsChunkNode *node = job->chunk();
   439   if ( qobject_cast<QgsChunkLoader *>( job ) )
   442     node->cancelLoading();
   444     QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral( 
"3D" ), QStringLiteral( 
"Load " ) + node->tileId().text(), node->tileId().text() );
   445     QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral( 
"3D" ), QStringLiteral( 
"Load" ), node->tileId().text() );
   450     node->cancelUpdating();
   452     QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral( 
"3D" ), QStringLiteral( 
"Update" ), node->tileId().text() );
   456   mActiveJobs.removeOne( job );
   460 void QgsChunkedEntity::cancelActiveJobs()
   462   while ( !mActiveJobs.isEmpty() )
   464     cancelActiveJob( mActiveJobs.takeFirst() );
   469 void QgsChunkedEntity::setPickingEnabled( 
bool enabled )
   471   if ( mPickingEnabled == enabled )
   474   mPickingEnabled = enabled;
   478     QgsChunkListEntry *entry = mReplacementQueue->first();
   481       QgsChunkNode *node = entry->chunk;
   482       Qt3DRender::QObjectPicker *picker = 
new Qt3DRender::QObjectPicker( node->entity() );
   483       node->entity()->addComponent( picker );
   484       connect( picker, &Qt3DRender::QObjectPicker::clicked, 
this, &QgsChunkedEntity::onPickEvent );
   491     for ( Qt3DRender::QObjectPicker *picker : findChildren<Qt3DRender::QObjectPicker *>() )
   492       picker->deleteLater();
   496 void QgsChunkedEntity::onPickEvent( Qt3DRender::QPickEvent *event )
   498   Qt3DRender::QPickTriangleEvent *triangleEvent = qobject_cast<Qt3DRender::QPickTriangleEvent *>( event );
   499   if ( !triangleEvent )
   502   Qt3DRender::QObjectPicker *picker = qobject_cast<Qt3DRender::QObjectPicker *>( sender() );
   506   Qt3DCore::QEntity *entity = qobject_cast<Qt3DCore::QEntity *>( picker->parent() );
   512   for ( Qt3DRender::QGeometryRenderer *geomRenderer : entity->findChildren<Qt3DRender::QGeometryRenderer *>() )
   517     if ( geomRenderer->objectName() != QLatin1String( 
"main" ) )
   522       fid = g->triangleIndexToFeatureId( triangleEvent->triangleIndex() );
   530     emit pickedObject( event, fid );
 3 Axis-aligned bounding box - in world coords. 
 
static bool isCullable(const QgsAABB &bbox, const QMatrix4x4 &viewProjectionMatrix)
Returns true if bbox is completely outside the current viewing volume. 
 
3 Class derived from Qt3DRender::QGeometry that represents polygons tessellated into 3D geometry...