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 if ( Qt3DCore::QEntity *entity = node->entity() )
158 entity->setEnabled(
true );
165 for ( QgsChunkNode *node : activeBefore )
167 if ( Qt3DCore::QEntity *entity = node->entity() )
169 entity->setEnabled(
false );
176 while ( mReplacementQueue->count() > mMaxLoadedChunks )
178 QgsChunkListEntry *entry = mReplacementQueue->takeLast();
179 entry->chunk->unloadChunk();
185 QList<QgsAABB> bboxes;
186 for ( QgsChunkNode *n : std::as_const( mActiveNodes ) )
188 mBboxesEntity->setBoxes( bboxes );
194 mNeedsUpdate =
false;
196 if ( pendingJobsCount() != oldJobsCount )
197 emit pendingJobsCountChanged();
199 QgsDebugMsgLevel( QStringLiteral(
"update: active %1 enabled %2 disabled %3 | culled %4 | loading %5 loaded %6 | unloaded %7 elapsed %8ms" ).arg( mActiveNodes.count() )
202 .arg( mFrustumCulled )
203 .arg( mReplacementQueue->count() )
205 .arg( t.elapsed() ), 2 );
208 void QgsChunkedEntity::setShowBoundingBoxes(
bool enabled )
210 if ( ( enabled && mBboxesEntity ) || ( !enabled && !mBboxesEntity ) )
215 mBboxesEntity =
new QgsChunkBoundsEntity(
this );
219 mBboxesEntity->deleteLater();
220 mBboxesEntity =
nullptr;
224 void QgsChunkedEntity::updateNodes(
const QList<QgsChunkNode *> &nodes, QgsChunkQueueJobFactory *updateJobFactory )
226 for ( QgsChunkNode *node : nodes )
228 if ( node->state() == QgsChunkNode::QueuedForUpdate )
230 mChunkLoaderQueue->takeEntry( node->loaderQueueEntry() );
231 node->cancelQueuedForUpdate();
233 else if ( node->state() == QgsChunkNode::Updating )
235 cancelActiveJob( node->updater() );
238 Q_ASSERT( node->state() == QgsChunkNode::Loaded );
240 QgsChunkListEntry *entry =
new QgsChunkListEntry( node );
241 node->setQueuedForUpdate( entry, updateJobFactory );
242 mChunkLoaderQueue->insertLast( entry );
249 int QgsChunkedEntity::pendingJobsCount()
const
251 return mChunkLoaderQueue->count() + mActiveJobs.count();
254 struct ResidencyRequest
256 QgsChunkNode *node =
nullptr;
259 ResidencyRequest() =
default;
272 bool operator()(
const ResidencyRequest &request,
const ResidencyRequest &otherRequest )
const
274 if ( request.level == otherRequest.level )
275 return request.dist > otherRequest.dist;
276 return request.level > otherRequest.level;
278 } ResidencyRequestSorter;
280 void QgsChunkedEntity::update( QgsChunkNode *root,
const SceneState &state )
282 QSet<QgsChunkNode *> nodes;
283 QVector<ResidencyRequest> residencyRequests;
285 using slotItem = std::pair<QgsChunkNode *, float>;
286 auto cmp_funct = []( slotItem & p1, slotItem & p2 )
288 return p1.second <= p2.second;
290 int renderedCount = 0;
291 std::priority_queue<slotItem, std::vector<slotItem>, decltype( cmp_funct )> pq( cmp_funct );
292 pq.push( std::make_pair( root, screenSpaceError( root, state ) ) );
293 while ( !pq.empty() && renderedCount <= mPrimitivesBudget )
295 slotItem s = pq.top();
297 QgsChunkNode *node = s.first;
306 if ( node->childCount() == -1 )
307 node->populateChildren( mChunkLoaderFactory->createChildren( node ) );
311 double dist = node->bbox().center().distanceToPoint( state.cameraPos );
312 residencyRequests.push_back( ResidencyRequest( node, dist, node->level() ) );
314 if ( !node->entity() )
319 bool becomesActive =
false;
322 if ( node->childCount() == 0 )
326 becomesActive =
true;
328 else if ( mTau > 0 && screenSpaceError( node, state ) <= mTau )
331 becomesActive =
true;
333 else if ( node->allChildChunksResident( mCurrentTime ) )
336 if ( mAdditiveStrategy )
341 becomesActive =
true;
343 QgsChunkNode *
const *children = node->children();
344 for (
int i = 0; i < node->childCount(); ++i )
345 pq.push( std::make_pair( children[i], screenSpaceError( children[i], state ) ) );
350 becomesActive =
true;
352 QgsChunkNode *
const *children = node->children();
353 for (
int i = 0; i < node->childCount(); ++i )
355 double dist = children[i]->bbox().center().distanceToPoint( state.cameraPos );
356 residencyRequests.push_back( ResidencyRequest( children[i], dist, children[i]->level() ) );
361 mActiveNodes << node;
363 if ( !mAdditiveStrategy && node->parent() && nodes.contains( node->parent() ) )
365 nodes.remove( node->parent() );
366 renderedCount -= mChunkLoaderFactory->primitivesCount( node->parent() );
368 renderedCount += mChunkLoaderFactory->primitivesCount( node );
369 nodes.insert( node );
374 std::sort( residencyRequests.begin(), residencyRequests.end(), ResidencyRequestSorter );
375 for (
const auto &request : residencyRequests )
376 requestResidency( request.node );
379 void QgsChunkedEntity::requestResidency( QgsChunkNode *node )
381 if ( node->state() == QgsChunkNode::Loaded || node->state() == QgsChunkNode::QueuedForUpdate || node->state() == QgsChunkNode::Updating )
383 Q_ASSERT( node->replacementQueueEntry() );
384 Q_ASSERT( node->entity() );
385 mReplacementQueue->takeEntry( node->replacementQueueEntry() );
386 mReplacementQueue->insertFirst( node->replacementQueueEntry() );
388 else if ( node->state() == QgsChunkNode::QueuedForLoad )
391 Q_ASSERT( node->loaderQueueEntry() );
392 Q_ASSERT( !node->loader() );
393 if ( node->loaderQueueEntry()->prev || node->loaderQueueEntry()->next )
395 mChunkLoaderQueue->takeEntry( node->loaderQueueEntry() );
396 mChunkLoaderQueue->insertFirst( node->loaderQueueEntry() );
399 else if ( node->state() == QgsChunkNode::Loading )
403 else if ( node->state() == QgsChunkNode::Skeleton )
405 if ( !node->hasData() )
409 QgsChunkListEntry *entry =
new QgsChunkListEntry( node );
410 node->setQueuedForLoad( entry );
411 mChunkLoaderQueue->insertFirst( entry );
414 Q_ASSERT(
false &&
"impossible!" );
418 void QgsChunkedEntity::onActiveJobFinished()
420 int oldJobsCount = pendingJobsCount();
422 QgsChunkQueueJob *job = qobject_cast<QgsChunkQueueJob *>( sender() );
424 Q_ASSERT( mActiveJobs.contains( job ) );
426 QgsChunkNode *node = job->chunk();
428 if ( QgsChunkLoader *loader = qobject_cast<QgsChunkLoader *>( job ) )
430 Q_ASSERT( node->state() == QgsChunkNode::Loading );
431 Q_ASSERT( node->loader() == loader );
433 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral(
"3D" ), QStringLiteral(
"Load " ) + node->tileId().text(), node->tileId().text() );
434 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral(
"3D" ), QStringLiteral(
"Load" ), node->tileId().text() );
436 QgsEventTracing::ScopedEvent e(
"3D", QString(
"create" ) );
438 Qt3DCore::QEntity *entity = node->loader()->createEntity(
this );
443 node->setLoaded( entity );
445 mReplacementQueue->insertFirst( node->replacementQueueEntry() );
447 if ( mPickingEnabled )
449 Qt3DRender::QObjectPicker *picker =
new Qt3DRender::QObjectPicker( node->entity() );
450 node->entity()->addComponent( picker );
451 connect( picker, &Qt3DRender::QObjectPicker::clicked,
this, &QgsChunkedEntity::onPickEvent );
454 emit newEntityCreated( entity );
458 node->setHasData(
false );
459 node->cancelLoading();
467 Q_ASSERT( node->state() == QgsChunkNode::Updating );
468 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral(
"3D" ), QStringLiteral(
"Update" ), node->tileId().text() );
473 mActiveJobs.removeOne( job );
479 if ( pendingJobsCount() != oldJobsCount )
480 emit pendingJobsCountChanged();
483 void QgsChunkedEntity::startJobs()
485 while ( mActiveJobs.count() < 4 )
487 if ( mChunkLoaderQueue->isEmpty() )
490 QgsChunkListEntry *entry = mChunkLoaderQueue->takeFirst();
492 QgsChunkNode *node = entry->chunk;
495 QgsChunkQueueJob *job = startJob( node );
496 mActiveJobs.append( job );
500 QgsChunkQueueJob *QgsChunkedEntity::startJob( QgsChunkNode *node )
502 if ( node->state() == QgsChunkNode::QueuedForLoad )
504 QgsEventTracing::addEvent( QgsEventTracing::AsyncBegin, QStringLiteral(
"3D" ), QStringLiteral(
"Load" ), node->tileId().text() );
505 QgsEventTracing::addEvent( QgsEventTracing::AsyncBegin, QStringLiteral(
"3D" ), QStringLiteral(
"Load " ) + node->tileId().text(), node->tileId().text() );
507 QgsChunkLoader *loader = mChunkLoaderFactory->createChunkLoader( node );
508 connect( loader, &QgsChunkQueueJob::finished,
this, &QgsChunkedEntity::onActiveJobFinished );
509 node->setLoading( loader );
512 else if ( node->state() == QgsChunkNode::QueuedForUpdate )
514 QgsEventTracing::addEvent( QgsEventTracing::AsyncBegin, QStringLiteral(
"3D" ), QStringLiteral(
"Update" ), node->tileId().text() );
517 connect( node->updater(), &QgsChunkQueueJob::finished,
this, &QgsChunkedEntity::onActiveJobFinished );
518 return node->updater();
527 void QgsChunkedEntity::cancelActiveJob( QgsChunkQueueJob *job )
531 QgsChunkNode *node = job->chunk();
533 if ( qobject_cast<QgsChunkLoader *>( job ) )
536 node->cancelLoading();
538 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral(
"3D" ), QStringLiteral(
"Load " ) + node->tileId().text(), node->tileId().text() );
539 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral(
"3D" ), QStringLiteral(
"Load" ), node->tileId().text() );
544 node->cancelUpdating();
546 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral(
"3D" ), QStringLiteral(
"Update" ), node->tileId().text() );
550 mActiveJobs.removeOne( job );
554 void QgsChunkedEntity::cancelActiveJobs()
556 while ( !mActiveJobs.isEmpty() )
558 cancelActiveJob( mActiveJobs.takeFirst() );
563 void QgsChunkedEntity::setPickingEnabled(
bool enabled )
565 if ( mPickingEnabled == enabled )
568 mPickingEnabled = enabled;
572 QgsChunkListEntry *entry = mReplacementQueue->first();
575 QgsChunkNode *node = entry->chunk;
576 Qt3DRender::QObjectPicker *picker =
new Qt3DRender::QObjectPicker( node->entity() );
577 node->entity()->addComponent( picker );
578 connect( picker, &Qt3DRender::QObjectPicker::clicked,
this, &QgsChunkedEntity::onPickEvent );
585 for ( Qt3DRender::QObjectPicker *picker : findChildren<Qt3DRender::QObjectPicker *>() )
586 picker->deleteLater();
590 void QgsChunkedEntity::onPickEvent( Qt3DRender::QPickEvent *event )
592 Qt3DRender::QPickTriangleEvent *triangleEvent = qobject_cast<Qt3DRender::QPickTriangleEvent *>( event );
593 if ( !triangleEvent )
596 Qt3DRender::QObjectPicker *picker = qobject_cast<Qt3DRender::QObjectPicker *>( sender() );
600 Qt3DCore::QEntity *entity = qobject_cast<Qt3DCore::QEntity *>( picker->parent() );
606 for ( Qt3DRender::QGeometryRenderer *geomRenderer : entity->findChildren<Qt3DRender::QGeometryRenderer *>() )
611 if ( geomRenderer->objectName() != QLatin1String(
"main" ) )
616 fid = g->triangleIndexToFeatureId( triangleEvent->triangleIndex() );
624 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)