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 ( !node->entity() )
158 QgsDebugMsg(
"Active node has null entity - this should never happen!" );
161 node->entity()->setEnabled(
true );
167 for ( QgsChunkNode *node : activeBefore )
169 if ( !node->entity() )
171 QgsDebugMsg(
"Active node has null entity - this should never happen!" );
174 node->entity()->setEnabled(
false );
180 while ( mReplacementQueue->count() > mMaxLoadedChunks )
182 QgsChunkListEntry *entry = mReplacementQueue->takeLast();
183 entry->chunk->unloadChunk();
184 mActiveNodes.removeOne( entry->chunk );
190 QList<QgsAABB> bboxes;
191 for ( QgsChunkNode *n : std::as_const( mActiveNodes ) )
193 mBboxesEntity->setBoxes( bboxes );
199 mNeedsUpdate =
false;
201 if ( pendingJobsCount() != oldJobsCount )
202 emit pendingJobsCountChanged();
204 QgsDebugMsgLevel( QStringLiteral(
"update: active %1 enabled %2 disabled %3 | culled %4 | loading %5 loaded %6 | unloaded %7 elapsed %8ms" ).arg( mActiveNodes.count() )
207 .arg( mFrustumCulled )
208 .arg( mReplacementQueue->count() )
210 .arg( t.elapsed() ), 2 );
213 void QgsChunkedEntity::setShowBoundingBoxes(
bool enabled )
215 if ( ( enabled && mBboxesEntity ) || ( !enabled && !mBboxesEntity ) )
220 mBboxesEntity =
new QgsChunkBoundsEntity(
this );
224 mBboxesEntity->deleteLater();
225 mBboxesEntity =
nullptr;
229 void QgsChunkedEntity::updateNodes(
const QList<QgsChunkNode *> &nodes, QgsChunkQueueJobFactory *updateJobFactory )
231 for ( QgsChunkNode *node : nodes )
233 if ( node->state() == QgsChunkNode::QueuedForUpdate )
235 mChunkLoaderQueue->takeEntry( node->loaderQueueEntry() );
236 node->cancelQueuedForUpdate();
238 else if ( node->state() == QgsChunkNode::Updating )
240 cancelActiveJob( node->updater() );
243 Q_ASSERT( node->state() == QgsChunkNode::Loaded );
245 QgsChunkListEntry *entry =
new QgsChunkListEntry( node );
246 node->setQueuedForUpdate( entry, updateJobFactory );
247 mChunkLoaderQueue->insertLast( entry );
254 int QgsChunkedEntity::pendingJobsCount()
const
256 return mChunkLoaderQueue->count() + mActiveJobs.count();
259 struct ResidencyRequest
261 QgsChunkNode *node =
nullptr;
264 ResidencyRequest() =
default;
277 bool operator()(
const ResidencyRequest &request,
const ResidencyRequest &otherRequest )
const
279 if ( request.level == otherRequest.level )
280 return request.dist > otherRequest.dist;
281 return request.level > otherRequest.level;
283 } ResidencyRequestSorter;
285 void QgsChunkedEntity::update( QgsChunkNode *root,
const SceneState &state )
287 QSet<QgsChunkNode *> nodes;
288 QVector<ResidencyRequest> residencyRequests;
290 using slotItem = std::pair<QgsChunkNode *, float>;
291 auto cmp_funct = []( slotItem & p1, slotItem & p2 )
293 return p1.second <= p2.second;
295 int renderedCount = 0;
296 std::priority_queue<slotItem, std::vector<slotItem>, decltype( cmp_funct )> pq( cmp_funct );
297 pq.push( std::make_pair( root, screenSpaceError( root, state ) ) );
298 while ( !pq.empty() && renderedCount <= mPrimitivesBudget )
300 slotItem s = pq.top();
302 QgsChunkNode *node = s.first;
311 if ( node->childCount() == -1 )
312 node->populateChildren( mChunkLoaderFactory->createChildren( node ) );
316 double dist = node->bbox().center().distanceToPoint( state.cameraPos );
317 residencyRequests.push_back( ResidencyRequest( node, dist, node->level() ) );
319 if ( !node->entity() )
324 bool becomesActive =
false;
327 if ( node->childCount() == 0 )
331 becomesActive =
true;
333 else if ( mTau > 0 && screenSpaceError( node, state ) <= mTau )
336 becomesActive =
true;
338 else if ( node->allChildChunksResident( mCurrentTime ) )
341 if ( mAdditiveStrategy )
346 becomesActive =
true;
348 QgsChunkNode *
const *children = node->children();
349 for (
int i = 0; i < node->childCount(); ++i )
350 pq.push( std::make_pair( children[i], screenSpaceError( children[i], state ) ) );
355 becomesActive =
true;
357 QgsChunkNode *
const *children = node->children();
358 for (
int i = 0; i < node->childCount(); ++i )
360 double dist = children[i]->bbox().center().distanceToPoint( state.cameraPos );
361 residencyRequests.push_back( ResidencyRequest( children[i], dist, children[i]->level() ) );
366 mActiveNodes << node;
368 if ( !mAdditiveStrategy && node->parent() && nodes.contains( node->parent() ) )
370 nodes.remove( node->parent() );
371 renderedCount -= mChunkLoaderFactory->primitivesCount( node->parent() );
373 renderedCount += mChunkLoaderFactory->primitivesCount( node );
374 nodes.insert( node );
379 std::sort( residencyRequests.begin(), residencyRequests.end(), ResidencyRequestSorter );
380 for (
const auto &request : residencyRequests )
381 requestResidency( request.node );
384 void QgsChunkedEntity::requestResidency( QgsChunkNode *node )
386 if ( node->state() == QgsChunkNode::Loaded || node->state() == QgsChunkNode::QueuedForUpdate || node->state() == QgsChunkNode::Updating )
388 Q_ASSERT( node->replacementQueueEntry() );
389 Q_ASSERT( node->entity() );
390 mReplacementQueue->takeEntry( node->replacementQueueEntry() );
391 mReplacementQueue->insertFirst( node->replacementQueueEntry() );
393 else if ( node->state() == QgsChunkNode::QueuedForLoad )
396 Q_ASSERT( node->loaderQueueEntry() );
397 Q_ASSERT( !node->loader() );
398 if ( node->loaderQueueEntry()->prev || node->loaderQueueEntry()->next )
400 mChunkLoaderQueue->takeEntry( node->loaderQueueEntry() );
401 mChunkLoaderQueue->insertFirst( node->loaderQueueEntry() );
404 else if ( node->state() == QgsChunkNode::Loading )
408 else if ( node->state() == QgsChunkNode::Skeleton )
410 if ( !node->hasData() )
414 QgsChunkListEntry *entry =
new QgsChunkListEntry( node );
415 node->setQueuedForLoad( entry );
416 mChunkLoaderQueue->insertFirst( entry );
419 Q_ASSERT(
false &&
"impossible!" );
423 void QgsChunkedEntity::onActiveJobFinished()
425 int oldJobsCount = pendingJobsCount();
427 QgsChunkQueueJob *job = qobject_cast<QgsChunkQueueJob *>( sender() );
429 Q_ASSERT( mActiveJobs.contains( job ) );
431 QgsChunkNode *node = job->chunk();
433 if ( QgsChunkLoader *loader = qobject_cast<QgsChunkLoader *>( job ) )
435 Q_ASSERT( node->state() == QgsChunkNode::Loading );
436 Q_ASSERT( node->loader() == loader );
438 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral(
"3D" ), QStringLiteral(
"Load " ) + node->tileId().text(), node->tileId().text() );
439 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral(
"3D" ), QStringLiteral(
"Load" ), node->tileId().text() );
441 QgsEventTracing::ScopedEvent e(
"3D", QString(
"create" ) );
443 Qt3DCore::QEntity *entity = node->loader()->createEntity(
this );
448 node->setLoaded( entity );
450 mReplacementQueue->insertFirst( node->replacementQueueEntry() );
452 if ( mPickingEnabled )
454 Qt3DRender::QObjectPicker *picker =
new Qt3DRender::QObjectPicker( node->entity() );
455 node->entity()->addComponent( picker );
456 connect( picker, &Qt3DRender::QObjectPicker::clicked,
this, &QgsChunkedEntity::onPickEvent );
459 emit newEntityCreated( entity );
463 node->setHasData(
false );
464 node->cancelLoading();
472 Q_ASSERT( node->state() == QgsChunkNode::Updating );
473 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral(
"3D" ), QStringLiteral(
"Update" ), node->tileId().text() );
478 mActiveJobs.removeOne( job );
484 if ( pendingJobsCount() != oldJobsCount )
485 emit pendingJobsCountChanged();
488 void QgsChunkedEntity::startJobs()
490 while ( mActiveJobs.count() < 4 )
492 if ( mChunkLoaderQueue->isEmpty() )
495 QgsChunkListEntry *entry = mChunkLoaderQueue->takeFirst();
497 QgsChunkNode *node = entry->chunk;
500 QgsChunkQueueJob *job = startJob( node );
501 mActiveJobs.append( job );
505 QgsChunkQueueJob *QgsChunkedEntity::startJob( QgsChunkNode *node )
507 if ( node->state() == QgsChunkNode::QueuedForLoad )
509 QgsEventTracing::addEvent( QgsEventTracing::AsyncBegin, QStringLiteral(
"3D" ), QStringLiteral(
"Load" ), node->tileId().text() );
510 QgsEventTracing::addEvent( QgsEventTracing::AsyncBegin, QStringLiteral(
"3D" ), QStringLiteral(
"Load " ) + node->tileId().text(), node->tileId().text() );
512 QgsChunkLoader *loader = mChunkLoaderFactory->createChunkLoader( node );
513 connect( loader, &QgsChunkQueueJob::finished,
this, &QgsChunkedEntity::onActiveJobFinished );
514 node->setLoading( loader );
517 else if ( node->state() == QgsChunkNode::QueuedForUpdate )
519 QgsEventTracing::addEvent( QgsEventTracing::AsyncBegin, QStringLiteral(
"3D" ), QStringLiteral(
"Update" ), node->tileId().text() );
522 connect( node->updater(), &QgsChunkQueueJob::finished,
this, &QgsChunkedEntity::onActiveJobFinished );
523 return node->updater();
532 void QgsChunkedEntity::cancelActiveJob( QgsChunkQueueJob *job )
536 QgsChunkNode *node = job->chunk();
538 if ( qobject_cast<QgsChunkLoader *>( job ) )
541 node->cancelLoading();
543 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral(
"3D" ), QStringLiteral(
"Load " ) + node->tileId().text(), node->tileId().text() );
544 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral(
"3D" ), QStringLiteral(
"Load" ), node->tileId().text() );
549 node->cancelUpdating();
551 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral(
"3D" ), QStringLiteral(
"Update" ), node->tileId().text() );
555 mActiveJobs.removeOne( job );
559 void QgsChunkedEntity::cancelActiveJobs()
561 while ( !mActiveJobs.isEmpty() )
563 cancelActiveJob( mActiveJobs.takeFirst() );
568 void QgsChunkedEntity::setPickingEnabled(
bool enabled )
570 if ( mPickingEnabled == enabled )
573 mPickingEnabled = enabled;
577 QgsChunkListEntry *entry = mReplacementQueue->first();
580 QgsChunkNode *node = entry->chunk;
581 Qt3DRender::QObjectPicker *picker =
new Qt3DRender::QObjectPicker( node->entity() );
582 node->entity()->addComponent( picker );
583 connect( picker, &Qt3DRender::QObjectPicker::clicked,
this, &QgsChunkedEntity::onPickEvent );
590 for ( Qt3DRender::QObjectPicker *picker : findChildren<Qt3DRender::QObjectPicker *>() )
591 picker->deleteLater();
595 void QgsChunkedEntity::onPickEvent( Qt3DRender::QPickEvent *event )
597 Qt3DRender::QPickTriangleEvent *triangleEvent = qobject_cast<Qt3DRender::QPickTriangleEvent *>( event );
598 if ( !triangleEvent )
601 Qt3DRender::QObjectPicker *picker = qobject_cast<Qt3DRender::QObjectPicker *>( sender() );
605 Qt3DCore::QEntity *entity = qobject_cast<Qt3DCore::QEntity *>( picker->parent() );
611 for ( Qt3DRender::QGeometryRenderer *geomRenderer : entity->findChildren<Qt3DRender::QGeometryRenderer *>() )
616 if ( geomRenderer->objectName() != QLatin1String(
"main" ) )
621 fid = g->triangleIndexToFeatureId( triangleEvent->triangleIndex() );
629 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)