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 Q_FOREACH ( QgsChunkNode *n, 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 Q_FOREACH ( 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 void QgsChunkedEntity::update( QgsChunkNode *root,
const SceneState &state )
250 QSet<QgsChunkNode *> nodes;
251 QVector<std::tuple<QgsChunkNode *, float, int>> residencyRequests;
253 using slot = std::pair<QgsChunkNode *, float>;
254 auto cmp_funct = []( slot & p1, slot & p2 )
256 return p1.second <= p2.second;
258 int renderedCount = 0;
259 std::priority_queue<slot, std::vector<slot>, decltype( cmp_funct )> pq( cmp_funct );
260 pq.push( std::make_pair( root, screenSpaceError( root, state ) ) );
261 while ( !pq.empty() && renderedCount <= mPrimitivesBudget )
265 QgsChunkNode *node = s.first;
274 if ( node->childCount() == -1 )
275 node->populateChildren( mChunkLoaderFactory->createChildren( node ) );
279 double dist = node->bbox().center().distanceToPoint( state.cameraPos );
280 residencyRequests.push_back( std::make_tuple( node, dist, node->level() ) );
282 if ( !node->entity() )
287 bool becomesActive =
false;
290 if ( node->childCount() == 0 )
294 becomesActive =
true;
296 else if ( mTau > 0 && screenSpaceError( node, state ) <= mTau )
299 becomesActive =
true;
301 else if ( node->allChildChunksResident( mCurrentTime ) )
304 if ( mAdditiveStrategy )
309 becomesActive =
true;
311 QgsChunkNode *
const *children = node->children();
312 for (
int i = 0; i < node->childCount(); ++i )
313 pq.push( std::make_pair( children[i], screenSpaceError( children[i], state ) ) );
318 becomesActive =
true;
320 QgsChunkNode *
const *children = node->children();
321 for (
int i = 0; i < node->childCount(); ++i )
323 double dist = children[i]->bbox().center().distanceToPoint( state.cameraPos );
324 residencyRequests.push_back( std::make_tuple( children[i], dist, children[i]->level() ) );
329 mActiveNodes << node;
331 if ( !mAdditiveStrategy && node->parent() && nodes.contains( node->parent() ) )
333 nodes.remove( node->parent() );
334 renderedCount -= mChunkLoaderFactory->primitivesCount( node->parent() );
336 renderedCount += mChunkLoaderFactory->primitivesCount( node );
337 nodes.insert( node );
341 std::sort( residencyRequests.begin(), residencyRequests.end(), [&]( std::tuple<QgsChunkNode *, float, int> &n1, std::tuple<QgsChunkNode *, float, int> &n2 )
343 if ( std::get<2>( n1 ) == std::get<2>( n2 ) )
344 return std::get<1>( n1 ) >= std::get<1>( n1 );
345 return std::get<2>( n1 ) >= std::get<2>( n2 );
347 for ( std::tuple<QgsChunkNode *, float, int> n : residencyRequests )
348 requestResidency( std::get<0>( n ) );
351 void QgsChunkedEntity::requestResidency( QgsChunkNode *node )
353 if ( node->state() == QgsChunkNode::Loaded || node->state() == QgsChunkNode::QueuedForUpdate || node->state() == QgsChunkNode::Updating )
355 Q_ASSERT( node->replacementQueueEntry() );
356 Q_ASSERT( node->entity() );
357 mReplacementQueue->takeEntry( node->replacementQueueEntry() );
358 mReplacementQueue->insertFirst( node->replacementQueueEntry() );
360 else if ( node->state() == QgsChunkNode::QueuedForLoad )
363 Q_ASSERT( node->loaderQueueEntry() );
364 Q_ASSERT( !node->loader() );
365 if ( node->loaderQueueEntry()->prev || node->loaderQueueEntry()->next )
367 mChunkLoaderQueue->takeEntry( node->loaderQueueEntry() );
368 mChunkLoaderQueue->insertFirst( node->loaderQueueEntry() );
371 else if ( node->state() == QgsChunkNode::Loading )
375 else if ( node->state() == QgsChunkNode::Skeleton )
377 if ( !node->hasData() )
381 QgsChunkListEntry *entry =
new QgsChunkListEntry( node );
382 node->setQueuedForLoad( entry );
383 mChunkLoaderQueue->insertFirst( entry );
386 Q_ASSERT(
false &&
"impossible!" );
390 void QgsChunkedEntity::onActiveJobFinished()
392 int oldJobsCount = pendingJobsCount();
394 QgsChunkQueueJob *job = qobject_cast<QgsChunkQueueJob *>( sender() );
396 Q_ASSERT( mActiveJobs.contains( job ) );
398 QgsChunkNode *node = job->chunk();
400 if ( QgsChunkLoader *loader = qobject_cast<QgsChunkLoader *>( job ) )
402 Q_ASSERT( node->state() == QgsChunkNode::Loading );
403 Q_ASSERT( node->loader() == loader );
405 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral(
"3D" ), QStringLiteral(
"Load " ) + node->tileId().text(), node->tileId().text() );
406 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral(
"3D" ), QStringLiteral(
"Load" ), node->tileId().text() );
408 QgsEventTracing::ScopedEvent e(
"3D", QString(
"create" ) );
410 Qt3DCore::QEntity *entity = node->loader()->createEntity(
this );
415 node->setLoaded( entity );
417 mReplacementQueue->insertFirst( node->replacementQueueEntry() );
419 if ( mPickingEnabled )
421 Qt3DRender::QObjectPicker *picker =
new Qt3DRender::QObjectPicker( node->entity() );
422 node->entity()->addComponent( picker );
423 connect( picker, &Qt3DRender::QObjectPicker::clicked,
this, &QgsChunkedEntity::onPickEvent );
426 emit newEntityCreated( entity );
430 node->setHasData(
false );
431 node->cancelLoading();
439 Q_ASSERT( node->state() == QgsChunkNode::Updating );
440 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral(
"3D" ), QStringLiteral(
"Update" ), node->tileId().text() );
445 mActiveJobs.removeOne( job );
451 if ( pendingJobsCount() != oldJobsCount )
452 emit pendingJobsCountChanged();
455 void QgsChunkedEntity::startJobs()
457 while ( mActiveJobs.count() < 4 )
459 if ( mChunkLoaderQueue->isEmpty() )
462 QgsChunkListEntry *entry = mChunkLoaderQueue->takeFirst();
464 QgsChunkNode *node = entry->chunk;
467 QgsChunkQueueJob *job = startJob( node );
468 mActiveJobs.append( job );
472 QgsChunkQueueJob *QgsChunkedEntity::startJob( QgsChunkNode *node )
474 if ( node->state() == QgsChunkNode::QueuedForLoad )
476 QgsEventTracing::addEvent( QgsEventTracing::AsyncBegin, QStringLiteral(
"3D" ), QStringLiteral(
"Load" ), node->tileId().text() );
477 QgsEventTracing::addEvent( QgsEventTracing::AsyncBegin, QStringLiteral(
"3D" ), QStringLiteral(
"Load " ) + node->tileId().text(), node->tileId().text() );
479 QgsChunkLoader *loader = mChunkLoaderFactory->createChunkLoader( node );
480 connect( loader, &QgsChunkQueueJob::finished,
this, &QgsChunkedEntity::onActiveJobFinished );
481 node->setLoading( loader );
484 else if ( node->state() == QgsChunkNode::QueuedForUpdate )
486 QgsEventTracing::addEvent( QgsEventTracing::AsyncBegin, QStringLiteral(
"3D" ), QStringLiteral(
"Update" ), node->tileId().text() );
489 connect( node->updater(), &QgsChunkQueueJob::finished,
this, &QgsChunkedEntity::onActiveJobFinished );
490 return node->updater();
499 void QgsChunkedEntity::cancelActiveJob( QgsChunkQueueJob *job )
503 QgsChunkNode *node = job->chunk();
505 if ( qobject_cast<QgsChunkLoader *>( job ) )
508 node->cancelLoading();
510 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral(
"3D" ), QStringLiteral(
"Load " ) + node->tileId().text(), node->tileId().text() );
511 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral(
"3D" ), QStringLiteral(
"Load" ), node->tileId().text() );
516 node->cancelUpdating();
518 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral(
"3D" ), QStringLiteral(
"Update" ), node->tileId().text() );
522 mActiveJobs.removeOne( job );
526 void QgsChunkedEntity::cancelActiveJobs()
528 while ( !mActiveJobs.isEmpty() )
530 cancelActiveJob( mActiveJobs.takeFirst() );
535 void QgsChunkedEntity::setPickingEnabled(
bool enabled )
537 if ( mPickingEnabled == enabled )
540 mPickingEnabled = enabled;
544 QgsChunkListEntry *entry = mReplacementQueue->first();
547 QgsChunkNode *node = entry->chunk;
548 Qt3DRender::QObjectPicker *picker =
new Qt3DRender::QObjectPicker( node->entity() );
549 node->entity()->addComponent( picker );
550 connect( picker, &Qt3DRender::QObjectPicker::clicked,
this, &QgsChunkedEntity::onPickEvent );
557 for ( Qt3DRender::QObjectPicker *picker : findChildren<Qt3DRender::QObjectPicker *>() )
558 picker->deleteLater();
562 void QgsChunkedEntity::onPickEvent( Qt3DRender::QPickEvent *event )
564 Qt3DRender::QPickTriangleEvent *triangleEvent = qobject_cast<Qt3DRender::QPickTriangleEvent *>( event );
565 if ( !triangleEvent )
568 Qt3DRender::QObjectPicker *picker = qobject_cast<Qt3DRender::QObjectPicker *>( sender() );
572 Qt3DCore::QEntity *entity = qobject_cast<Qt3DCore::QEntity *>( picker->parent() );
578 for ( Qt3DRender::QGeometryRenderer *geomRenderer : entity->findChildren<Qt3DRender::QGeometryRenderer *>() )
583 if ( geomRenderer->objectName() != QLatin1String(
"main" ) )
588 fid = g->triangleIndexToFeatureId( triangleEvent->triangleIndex() );
596 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)