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, Qt3DCore::QNode *parent )
76 , mMaxLevel( maxLevel )
77 , mChunkLoaderFactory( loaderFactory )
79 mRootNode =
new QgsChunkNode( 0, 0, 0, rootBbox, rootError );
80 mChunkLoaderQueue =
new QgsChunkList;
81 mReplacementQueue =
new QgsChunkList;
85 QgsChunkedEntity::~QgsChunkedEntity()
90 Q_ASSERT( mActiveJobs.isEmpty() );
93 while ( !mChunkLoaderQueue->isEmpty() )
95 QgsChunkListEntry *entry = mChunkLoaderQueue->takeFirst();
96 QgsChunkNode *node = entry->chunk;
98 if ( node->state() == QgsChunkNode::QueuedForLoad )
99 node->cancelQueuedForLoad();
100 else if ( node->state() == QgsChunkNode::QueuedForUpdate )
101 node->cancelQueuedForUpdate();
106 delete mChunkLoaderQueue;
108 while ( !mReplacementQueue->isEmpty() )
110 QgsChunkListEntry *entry = mReplacementQueue->takeFirst();
113 entry->chunk->unloadChunk();
116 delete mReplacementQueue;
124 void QgsChunkedEntity::update(
const SceneState &state )
129 int oldJobsCount = pendingJobsCount();
131 QSet<QgsChunkNode *> activeBefore = QSet<QgsChunkNode *>::fromList( mActiveNodes );
132 mActiveNodes.clear();
134 mCurrentTime = QTime::currentTime();
136 update( mRootNode, state );
138 int enabled = 0, disabled = 0, unloaded = 0;
140 Q_FOREACH ( QgsChunkNode *node, mActiveNodes )
142 if ( activeBefore.contains( node ) )
143 activeBefore.remove( node );
146 node->entity()->setEnabled(
true );
152 Q_FOREACH ( QgsChunkNode *node, activeBefore )
154 node->entity()->setEnabled(
false );
160 while ( mReplacementQueue->count() > mMaxLoadedChunks )
162 QgsChunkListEntry *entry = mReplacementQueue->takeLast();
163 entry->chunk->unloadChunk();
169 QList<QgsAABB> bboxes;
170 Q_FOREACH ( QgsChunkNode *n, mActiveNodes )
172 mBboxesEntity->setBoxes( bboxes );
178 mNeedsUpdate =
false;
180 if ( pendingJobsCount() != oldJobsCount )
181 emit pendingJobsCountChanged();
186 void QgsChunkedEntity::setShowBoundingBoxes(
bool enabled )
188 if ( ( enabled && mBboxesEntity ) || ( !enabled && !mBboxesEntity ) )
193 mBboxesEntity =
new QgsChunkBoundsEntity(
this );
197 mBboxesEntity->deleteLater();
198 mBboxesEntity =
nullptr;
202 void QgsChunkedEntity::updateNodes(
const QList<QgsChunkNode *> &nodes, QgsChunkQueueJobFactory *updateJobFactory )
204 Q_FOREACH ( QgsChunkNode *node, nodes )
206 if ( node->state() == QgsChunkNode::QueuedForUpdate )
208 mChunkLoaderQueue->takeEntry( node->loaderQueueEntry() );
209 node->cancelQueuedForUpdate();
211 else if ( node->state() == QgsChunkNode::Updating )
213 cancelActiveJob( node->updater() );
216 Q_ASSERT( node->state() == QgsChunkNode::Loaded );
218 QgsChunkListEntry *entry =
new QgsChunkListEntry( node );
219 node->setQueuedForUpdate( entry, updateJobFactory );
220 mChunkLoaderQueue->insertLast( entry );
227 int QgsChunkedEntity::pendingJobsCount()
const 229 return mChunkLoaderQueue->count() + mActiveJobs.count();
233 void QgsChunkedEntity::update( QgsChunkNode *node,
const SceneState &state )
241 node->ensureAllChildrenExist();
245 requestResidency( node );
247 if ( !node->entity() )
255 if ( mTau > 0 && screenSpaceError( node, state ) <= mTau )
259 mActiveNodes << node;
261 else if ( node->allChildChunksResident( mCurrentTime ) )
265 QgsChunkNode *
const *children = node->children();
266 for (
int i = 0; i < 4; ++i )
267 update( children[i], state );
273 mActiveNodes << node;
275 if ( node->level() < mMaxLevel )
277 QgsChunkNode *
const *children = node->children();
278 for (
int i = 0; i < 4; ++i )
279 requestResidency( children[i] );
285 void QgsChunkedEntity::requestResidency( QgsChunkNode *node )
287 if ( node->state() == QgsChunkNode::Loaded || node->state() == QgsChunkNode::QueuedForUpdate || node->state() == QgsChunkNode::Updating )
289 Q_ASSERT( node->replacementQueueEntry() );
290 Q_ASSERT( node->entity() );
291 mReplacementQueue->takeEntry( node->replacementQueueEntry() );
292 mReplacementQueue->insertFirst( node->replacementQueueEntry() );
294 else if ( node->state() == QgsChunkNode::QueuedForLoad )
297 Q_ASSERT( node->loaderQueueEntry() );
298 Q_ASSERT( !node->loader() );
299 if ( node->loaderQueueEntry()->prev || node->loaderQueueEntry()->next )
301 mChunkLoaderQueue->takeEntry( node->loaderQueueEntry() );
302 mChunkLoaderQueue->insertFirst( node->loaderQueueEntry() );
305 else if ( node->state() == QgsChunkNode::Loading )
309 else if ( node->state() == QgsChunkNode::Skeleton )
311 if ( !node->hasData() )
315 QgsChunkListEntry *entry =
new QgsChunkListEntry( node );
316 node->setQueuedForLoad( entry );
317 mChunkLoaderQueue->insertFirst( entry );
320 Q_ASSERT(
false &&
"impossible!" );
324 void QgsChunkedEntity::onActiveJobFinished()
326 int oldJobsCount = pendingJobsCount();
328 QgsChunkQueueJob *job = qobject_cast<QgsChunkQueueJob *>( sender() );
330 Q_ASSERT( mActiveJobs.contains( job ) );
332 QgsChunkNode *node = job->chunk();
334 if ( QgsChunkLoader *loader = qobject_cast<QgsChunkLoader *>( job ) )
336 Q_ASSERT( node->state() == QgsChunkNode::Loading );
337 Q_ASSERT( node->loader() == loader );
339 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral(
"3D" ), QStringLiteral(
"Load " ) + node->tileId().text(), node->tileId().text() );
340 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral(
"3D" ), QStringLiteral(
"Load" ), node->tileId().text() );
342 QgsEventTracing::ScopedEvent e(
"3D", QString(
"create" ) );
344 Qt3DCore::QEntity *entity = node->loader()->createEntity(
this );
349 node->setLoaded( entity );
351 mReplacementQueue->insertFirst( node->replacementQueueEntry() );
353 if ( mPickingEnabled )
355 Qt3DRender::QObjectPicker *picker =
new Qt3DRender::QObjectPicker( node->entity() );
356 node->entity()->addComponent( picker );
357 connect( picker, &Qt3DRender::QObjectPicker::clicked,
this, &QgsChunkedEntity::onPickEvent );
360 emit newEntityCreated( entity );
364 node->setHasData(
false );
365 node->cancelLoading();
373 Q_ASSERT( node->state() == QgsChunkNode::Updating );
374 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral(
"3D" ), QStringLiteral(
"Update" ), node->tileId().text() );
379 mActiveJobs.removeOne( job );
385 if ( pendingJobsCount() != oldJobsCount )
386 emit pendingJobsCountChanged();
389 void QgsChunkedEntity::startJobs()
391 while ( mActiveJobs.count() < 4 )
393 if ( mChunkLoaderQueue->isEmpty() )
396 QgsChunkListEntry *entry = mChunkLoaderQueue->takeFirst();
398 QgsChunkNode *node = entry->chunk;
401 QgsChunkQueueJob *job = startJob( node );
402 mActiveJobs.append( job );
406 QgsChunkQueueJob *QgsChunkedEntity::startJob( QgsChunkNode *node )
408 if ( node->state() == QgsChunkNode::QueuedForLoad )
410 QgsEventTracing::addEvent( QgsEventTracing::AsyncBegin, QStringLiteral(
"3D" ), QStringLiteral(
"Load" ), node->tileId().text() );
411 QgsEventTracing::addEvent( QgsEventTracing::AsyncBegin, QStringLiteral(
"3D" ), QStringLiteral(
"Load " ) + node->tileId().text(), node->tileId().text() );
413 QgsChunkLoader *loader = mChunkLoaderFactory->createChunkLoader( node );
414 connect( loader, &QgsChunkQueueJob::finished,
this, &QgsChunkedEntity::onActiveJobFinished );
415 node->setLoading( loader );
418 else if ( node->state() == QgsChunkNode::QueuedForUpdate )
420 QgsEventTracing::addEvent( QgsEventTracing::AsyncBegin, QStringLiteral(
"3D" ), QStringLiteral(
"Update" ), node->tileId().text() );
423 connect( node->updater(), &QgsChunkQueueJob::finished,
this, &QgsChunkedEntity::onActiveJobFinished );
424 return node->updater();
433 void QgsChunkedEntity::cancelActiveJob( QgsChunkQueueJob *job )
437 QgsChunkNode *node = job->chunk();
439 if ( qobject_cast<QgsChunkLoader *>( job ) )
442 node->cancelLoading();
444 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral(
"3D" ), QStringLiteral(
"Load " ) + node->tileId().text(), node->tileId().text() );
445 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral(
"3D" ), QStringLiteral(
"Load" ), node->tileId().text() );
450 node->cancelUpdating();
452 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral(
"3D" ), QStringLiteral(
"Update" ), node->tileId().text() );
456 mActiveJobs.removeOne( job );
460 void QgsChunkedEntity::cancelActiveJobs()
462 while ( !mActiveJobs.isEmpty() )
464 cancelActiveJob( mActiveJobs.takeFirst() );
469 void QgsChunkedEntity::setPickingEnabled(
bool enabled )
471 if ( mPickingEnabled == enabled )
474 mPickingEnabled = enabled;
478 QgsChunkListEntry *entry = mReplacementQueue->first();
481 QgsChunkNode *node = entry->chunk;
482 Qt3DRender::QObjectPicker *picker =
new Qt3DRender::QObjectPicker( node->entity() );
483 node->entity()->addComponent( picker );
484 connect( picker, &Qt3DRender::QObjectPicker::clicked,
this, &QgsChunkedEntity::onPickEvent );
491 for ( Qt3DRender::QObjectPicker *picker : findChildren<Qt3DRender::QObjectPicker *>() )
492 picker->deleteLater();
496 void QgsChunkedEntity::onPickEvent( Qt3DRender::QPickEvent *event )
498 Qt3DRender::QPickTriangleEvent *triangleEvent = qobject_cast<Qt3DRender::QPickTriangleEvent *>( event );
499 if ( !triangleEvent )
502 Qt3DRender::QObjectPicker *picker = qobject_cast<Qt3DRender::QObjectPicker *>( sender() );
506 Qt3DCore::QEntity *entity = qobject_cast<Qt3DCore::QEntity *>( picker->parent() );
512 for ( Qt3DRender::QGeometryRenderer *geomRenderer : entity->findChildren<Qt3DRender::QGeometryRenderer *>() )
517 if ( geomRenderer->objectName() != QLatin1String(
"main" ) )
522 fid = g->triangleIndexToFeatureId( triangleEvent->triangleIndex() );
530 emit pickedObject( event, fid );
3 Axis-aligned bounding box - in world coords.
static bool isCullable(const QgsAABB &bbox, const QMatrix4x4 &viewProjectionMatrix)
Returns true if bbox is completely outside the current viewing volume.
3 Class derived from Qt3DRender::QGeometry that represents polygons tessellated into 3D geometry...