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,
bool ownsFactory, Qt3DCore::QNode *parent )
76 , mMaxLevel( maxLevel )
77 , mChunkLoaderFactory( loaderFactory )
78 , mOwnsFactory( ownsFactory )
80 mRootNode =
new QgsChunkNode( 0, 0, 0, rootBbox, rootError );
81 mChunkLoaderQueue =
new QgsChunkList;
82 mReplacementQueue =
new QgsChunkList;
86 QgsChunkedEntity::~QgsChunkedEntity()
91 Q_ASSERT( mActiveJobs.isEmpty() );
94 while ( !mChunkLoaderQueue->isEmpty() )
96 QgsChunkListEntry *entry = mChunkLoaderQueue->takeFirst();
97 QgsChunkNode *node = entry->chunk;
99 if ( node->state() == QgsChunkNode::QueuedForLoad )
100 node->cancelQueuedForLoad();
101 else if ( node->state() == QgsChunkNode::QueuedForUpdate )
102 node->cancelQueuedForUpdate();
107 delete mChunkLoaderQueue;
109 while ( !mReplacementQueue->isEmpty() )
111 QgsChunkListEntry *entry = mReplacementQueue->takeFirst();
114 entry->chunk->unloadChunk();
117 delete mReplacementQueue;
122 delete mChunkLoaderFactory;
127 void QgsChunkedEntity::update(
const SceneState &state )
135 int oldJobsCount = pendingJobsCount();
137 QSet<QgsChunkNode *> activeBefore = qgis::listToSet( mActiveNodes );
138 mActiveNodes.clear();
140 mCurrentTime = QTime::currentTime();
142 update( mRootNode, state );
144 int enabled = 0, disabled = 0, unloaded = 0;
146 Q_FOREACH ( QgsChunkNode *node, mActiveNodes )
148 if ( activeBefore.contains( node ) )
149 activeBefore.remove( node );
152 node->entity()->setEnabled(
true );
158 Q_FOREACH ( QgsChunkNode *node, activeBefore )
160 node->entity()->setEnabled(
false );
166 while ( mReplacementQueue->count() > mMaxLoadedChunks )
168 QgsChunkListEntry *entry = mReplacementQueue->takeLast();
169 entry->chunk->unloadChunk();
175 QList<QgsAABB> bboxes;
176 Q_FOREACH ( QgsChunkNode *n, mActiveNodes )
178 mBboxesEntity->setBoxes( bboxes );
184 mNeedsUpdate =
false;
186 if ( pendingJobsCount() != oldJobsCount )
187 emit pendingJobsCountChanged();
192 void QgsChunkedEntity::setShowBoundingBoxes(
bool enabled )
194 if ( ( enabled && mBboxesEntity ) || ( !enabled && !mBboxesEntity ) )
199 mBboxesEntity =
new QgsChunkBoundsEntity(
this );
203 mBboxesEntity->deleteLater();
204 mBboxesEntity =
nullptr;
208 void QgsChunkedEntity::updateNodes(
const QList<QgsChunkNode *> &nodes, QgsChunkQueueJobFactory *updateJobFactory )
210 Q_FOREACH ( QgsChunkNode *node, nodes )
212 if ( node->state() == QgsChunkNode::QueuedForUpdate )
214 mChunkLoaderQueue->takeEntry( node->loaderQueueEntry() );
215 node->cancelQueuedForUpdate();
217 else if ( node->state() == QgsChunkNode::Updating )
219 cancelActiveJob( node->updater() );
222 Q_ASSERT( node->state() == QgsChunkNode::Loaded );
224 QgsChunkListEntry *entry =
new QgsChunkListEntry( node );
225 node->setQueuedForUpdate( entry, updateJobFactory );
226 mChunkLoaderQueue->insertLast( entry );
233 int QgsChunkedEntity::pendingJobsCount()
const
235 return mChunkLoaderQueue->count() + mActiveJobs.count();
239 void QgsChunkedEntity::update( QgsChunkNode *node,
const SceneState &state )
247 node->ensureAllChildrenExist();
251 requestResidency( node );
253 if ( !node->entity() )
261 if ( mTau > 0 && screenSpaceError( node, state ) <= mTau )
265 mActiveNodes << node;
267 else if ( node->allChildChunksResident( mCurrentTime ) )
271 QgsChunkNode *
const *children = node->children();
272 for (
int i = 0; i < 4; ++i )
273 update( children[i], state );
279 mActiveNodes << node;
281 if ( node->level() < mMaxLevel )
283 QgsChunkNode *
const *children = node->children();
284 for (
int i = 0; i < 4; ++i )
285 requestResidency( children[i] );
291 void QgsChunkedEntity::requestResidency( QgsChunkNode *node )
293 if ( node->state() == QgsChunkNode::Loaded || node->state() == QgsChunkNode::QueuedForUpdate || node->state() == QgsChunkNode::Updating )
295 Q_ASSERT( node->replacementQueueEntry() );
296 Q_ASSERT( node->entity() );
297 mReplacementQueue->takeEntry( node->replacementQueueEntry() );
298 mReplacementQueue->insertFirst( node->replacementQueueEntry() );
300 else if ( node->state() == QgsChunkNode::QueuedForLoad )
303 Q_ASSERT( node->loaderQueueEntry() );
304 Q_ASSERT( !node->loader() );
305 if ( node->loaderQueueEntry()->prev || node->loaderQueueEntry()->next )
307 mChunkLoaderQueue->takeEntry( node->loaderQueueEntry() );
308 mChunkLoaderQueue->insertFirst( node->loaderQueueEntry() );
311 else if ( node->state() == QgsChunkNode::Loading )
315 else if ( node->state() == QgsChunkNode::Skeleton )
317 if ( !node->hasData() )
321 QgsChunkListEntry *entry =
new QgsChunkListEntry( node );
322 node->setQueuedForLoad( entry );
323 mChunkLoaderQueue->insertFirst( entry );
326 Q_ASSERT(
false &&
"impossible!" );
330 void QgsChunkedEntity::onActiveJobFinished()
332 int oldJobsCount = pendingJobsCount();
334 QgsChunkQueueJob *job = qobject_cast<QgsChunkQueueJob *>( sender() );
336 Q_ASSERT( mActiveJobs.contains( job ) );
338 QgsChunkNode *node = job->chunk();
340 if ( QgsChunkLoader *loader = qobject_cast<QgsChunkLoader *>( job ) )
342 Q_ASSERT( node->state() == QgsChunkNode::Loading );
343 Q_ASSERT( node->loader() == loader );
345 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral(
"3D" ), QStringLiteral(
"Load " ) + node->tileId().text(), node->tileId().text() );
346 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral(
"3D" ), QStringLiteral(
"Load" ), node->tileId().text() );
348 QgsEventTracing::ScopedEvent e(
"3D", QString(
"create" ) );
350 Qt3DCore::QEntity *entity = node->loader()->createEntity(
this );
355 node->setLoaded( entity );
357 mReplacementQueue->insertFirst( node->replacementQueueEntry() );
359 if ( mPickingEnabled )
361 Qt3DRender::QObjectPicker *picker =
new Qt3DRender::QObjectPicker( node->entity() );
362 node->entity()->addComponent( picker );
363 connect( picker, &Qt3DRender::QObjectPicker::clicked,
this, &QgsChunkedEntity::onPickEvent );
366 emit newEntityCreated( entity );
370 node->setHasData(
false );
371 node->cancelLoading();
379 Q_ASSERT( node->state() == QgsChunkNode::Updating );
380 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral(
"3D" ), QStringLiteral(
"Update" ), node->tileId().text() );
385 mActiveJobs.removeOne( job );
391 if ( pendingJobsCount() != oldJobsCount )
392 emit pendingJobsCountChanged();
395 void QgsChunkedEntity::startJobs()
397 while ( mActiveJobs.count() < 4 )
399 if ( mChunkLoaderQueue->isEmpty() )
402 QgsChunkListEntry *entry = mChunkLoaderQueue->takeFirst();
404 QgsChunkNode *node = entry->chunk;
407 QgsChunkQueueJob *job = startJob( node );
408 mActiveJobs.append( job );
412 QgsChunkQueueJob *QgsChunkedEntity::startJob( QgsChunkNode *node )
414 if ( node->state() == QgsChunkNode::QueuedForLoad )
416 QgsEventTracing::addEvent( QgsEventTracing::AsyncBegin, QStringLiteral(
"3D" ), QStringLiteral(
"Load" ), node->tileId().text() );
417 QgsEventTracing::addEvent( QgsEventTracing::AsyncBegin, QStringLiteral(
"3D" ), QStringLiteral(
"Load " ) + node->tileId().text(), node->tileId().text() );
419 QgsChunkLoader *loader = mChunkLoaderFactory->createChunkLoader( node );
420 connect( loader, &QgsChunkQueueJob::finished,
this, &QgsChunkedEntity::onActiveJobFinished );
421 node->setLoading( loader );
424 else if ( node->state() == QgsChunkNode::QueuedForUpdate )
426 QgsEventTracing::addEvent( QgsEventTracing::AsyncBegin, QStringLiteral(
"3D" ), QStringLiteral(
"Update" ), node->tileId().text() );
429 connect( node->updater(), &QgsChunkQueueJob::finished,
this, &QgsChunkedEntity::onActiveJobFinished );
430 return node->updater();
439 void QgsChunkedEntity::cancelActiveJob( QgsChunkQueueJob *job )
443 QgsChunkNode *node = job->chunk();
445 if ( qobject_cast<QgsChunkLoader *>( job ) )
448 node->cancelLoading();
450 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral(
"3D" ), QStringLiteral(
"Load " ) + node->tileId().text(), node->tileId().text() );
451 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral(
"3D" ), QStringLiteral(
"Load" ), node->tileId().text() );
456 node->cancelUpdating();
458 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral(
"3D" ), QStringLiteral(
"Update" ), node->tileId().text() );
462 mActiveJobs.removeOne( job );
466 void QgsChunkedEntity::cancelActiveJobs()
468 while ( !mActiveJobs.isEmpty() )
470 cancelActiveJob( mActiveJobs.takeFirst() );
475 void QgsChunkedEntity::setPickingEnabled(
bool enabled )
477 if ( mPickingEnabled == enabled )
480 mPickingEnabled = enabled;
484 QgsChunkListEntry *entry = mReplacementQueue->first();
487 QgsChunkNode *node = entry->chunk;
488 Qt3DRender::QObjectPicker *picker =
new Qt3DRender::QObjectPicker( node->entity() );
489 node->entity()->addComponent( picker );
490 connect( picker, &Qt3DRender::QObjectPicker::clicked,
this, &QgsChunkedEntity::onPickEvent );
497 for ( Qt3DRender::QObjectPicker *picker : findChildren<Qt3DRender::QObjectPicker *>() )
498 picker->deleteLater();
502 void QgsChunkedEntity::onPickEvent( Qt3DRender::QPickEvent *event )
504 Qt3DRender::QPickTriangleEvent *triangleEvent = qobject_cast<Qt3DRender::QPickTriangleEvent *>( event );
505 if ( !triangleEvent )
508 Qt3DRender::QObjectPicker *picker = qobject_cast<Qt3DRender::QObjectPicker *>( sender() );
512 Qt3DCore::QEntity *entity = qobject_cast<Qt3DCore::QEntity *>( picker->parent() );
518 for ( Qt3DRender::QGeometryRenderer *geomRenderer : entity->findChildren<Qt3DRender::QGeometryRenderer *>() )
523 if ( geomRenderer->objectName() != QLatin1String(
"main" ) )
528 fid = g->triangleIndexToFeatureId( triangleEvent->triangleIndex() );
536 emit pickedObject( event, fid );