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)