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 )
132 int oldJobsCount = pendingJobsCount();
134 QSet<QgsChunkNode *> activeBefore = qgis::listToSet( mActiveNodes );
135 mActiveNodes.clear();
137 mCurrentTime = QTime::currentTime();
139 update( mRootNode, state );
141 int enabled = 0, disabled = 0, unloaded = 0;
143 Q_FOREACH ( QgsChunkNode *node, mActiveNodes )
145 if ( activeBefore.contains( node ) )
146 activeBefore.remove( node );
149 node->entity()->setEnabled(
true );
155 Q_FOREACH ( QgsChunkNode *node, activeBefore )
157 node->entity()->setEnabled(
false );
163 while ( mReplacementQueue->count() > mMaxLoadedChunks )
165 QgsChunkListEntry *entry = mReplacementQueue->takeLast();
166 entry->chunk->unloadChunk();
172 QList<QgsAABB> bboxes;
173 Q_FOREACH ( QgsChunkNode *n, mActiveNodes )
175 mBboxesEntity->setBoxes( bboxes );
181 mNeedsUpdate =
false;
183 if ( pendingJobsCount() != oldJobsCount )
184 emit pendingJobsCountChanged();
189 void QgsChunkedEntity::setShowBoundingBoxes(
bool enabled )
191 if ( ( enabled && mBboxesEntity ) || ( !enabled && !mBboxesEntity ) )
196 mBboxesEntity =
new QgsChunkBoundsEntity(
this );
200 mBboxesEntity->deleteLater();
201 mBboxesEntity =
nullptr;
205 void QgsChunkedEntity::updateNodes(
const QList<QgsChunkNode *> &nodes, QgsChunkQueueJobFactory *updateJobFactory )
207 Q_FOREACH ( QgsChunkNode *node, nodes )
209 if ( node->state() == QgsChunkNode::QueuedForUpdate )
211 mChunkLoaderQueue->takeEntry( node->loaderQueueEntry() );
212 node->cancelQueuedForUpdate();
214 else if ( node->state() == QgsChunkNode::Updating )
216 cancelActiveJob( node->updater() );
219 Q_ASSERT( node->state() == QgsChunkNode::Loaded );
221 QgsChunkListEntry *entry =
new QgsChunkListEntry( node );
222 node->setQueuedForUpdate( entry, updateJobFactory );
223 mChunkLoaderQueue->insertLast( entry );
230 int QgsChunkedEntity::pendingJobsCount()
const
232 return mChunkLoaderQueue->count() + mActiveJobs.count();
236 void QgsChunkedEntity::update( QgsChunkNode *node,
const SceneState &state )
244 node->ensureAllChildrenExist();
248 requestResidency( node );
250 if ( !node->entity() )
258 if ( mTau > 0 && screenSpaceError( node, state ) <= mTau )
262 mActiveNodes << node;
264 else if ( node->allChildChunksResident( mCurrentTime ) )
268 QgsChunkNode *
const *children = node->children();
269 for (
int i = 0; i < 4; ++i )
270 update( children[i], state );
276 mActiveNodes << node;
278 if ( node->level() < mMaxLevel )
280 QgsChunkNode *
const *children = node->children();
281 for (
int i = 0; i < 4; ++i )
282 requestResidency( children[i] );
288 void QgsChunkedEntity::requestResidency( QgsChunkNode *node )
290 if ( node->state() == QgsChunkNode::Loaded || node->state() == QgsChunkNode::QueuedForUpdate || node->state() == QgsChunkNode::Updating )
292 Q_ASSERT( node->replacementQueueEntry() );
293 Q_ASSERT( node->entity() );
294 mReplacementQueue->takeEntry( node->replacementQueueEntry() );
295 mReplacementQueue->insertFirst( node->replacementQueueEntry() );
297 else if ( node->state() == QgsChunkNode::QueuedForLoad )
300 Q_ASSERT( node->loaderQueueEntry() );
301 Q_ASSERT( !node->loader() );
302 if ( node->loaderQueueEntry()->prev || node->loaderQueueEntry()->next )
304 mChunkLoaderQueue->takeEntry( node->loaderQueueEntry() );
305 mChunkLoaderQueue->insertFirst( node->loaderQueueEntry() );
308 else if ( node->state() == QgsChunkNode::Loading )
312 else if ( node->state() == QgsChunkNode::Skeleton )
314 if ( !node->hasData() )
318 QgsChunkListEntry *entry =
new QgsChunkListEntry( node );
319 node->setQueuedForLoad( entry );
320 mChunkLoaderQueue->insertFirst( entry );
323 Q_ASSERT(
false &&
"impossible!" );
327 void QgsChunkedEntity::onActiveJobFinished()
329 int oldJobsCount = pendingJobsCount();
331 QgsChunkQueueJob *job = qobject_cast<QgsChunkQueueJob *>( sender() );
333 Q_ASSERT( mActiveJobs.contains( job ) );
335 QgsChunkNode *node = job->chunk();
337 if ( QgsChunkLoader *loader = qobject_cast<QgsChunkLoader *>( job ) )
339 Q_ASSERT( node->state() == QgsChunkNode::Loading );
340 Q_ASSERT( node->loader() == loader );
342 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral(
"3D" ), QStringLiteral(
"Load " ) + node->tileId().text(), node->tileId().text() );
343 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral(
"3D" ), QStringLiteral(
"Load" ), node->tileId().text() );
345 QgsEventTracing::ScopedEvent e(
"3D", QString(
"create" ) );
347 Qt3DCore::QEntity *entity = node->loader()->createEntity(
this );
352 node->setLoaded( entity );
354 mReplacementQueue->insertFirst( node->replacementQueueEntry() );
356 if ( mPickingEnabled )
358 Qt3DRender::QObjectPicker *picker =
new Qt3DRender::QObjectPicker( node->entity() );
359 node->entity()->addComponent( picker );
360 connect( picker, &Qt3DRender::QObjectPicker::clicked,
this, &QgsChunkedEntity::onPickEvent );
363 emit newEntityCreated( entity );
367 node->setHasData(
false );
368 node->cancelLoading();
376 Q_ASSERT( node->state() == QgsChunkNode::Updating );
377 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral(
"3D" ), QStringLiteral(
"Update" ), node->tileId().text() );
382 mActiveJobs.removeOne( job );
388 if ( pendingJobsCount() != oldJobsCount )
389 emit pendingJobsCountChanged();
392 void QgsChunkedEntity::startJobs()
394 while ( mActiveJobs.count() < 4 )
396 if ( mChunkLoaderQueue->isEmpty() )
399 QgsChunkListEntry *entry = mChunkLoaderQueue->takeFirst();
401 QgsChunkNode *node = entry->chunk;
404 QgsChunkQueueJob *job = startJob( node );
405 mActiveJobs.append( job );
409 QgsChunkQueueJob *QgsChunkedEntity::startJob( QgsChunkNode *node )
411 if ( node->state() == QgsChunkNode::QueuedForLoad )
413 QgsEventTracing::addEvent( QgsEventTracing::AsyncBegin, QStringLiteral(
"3D" ), QStringLiteral(
"Load" ), node->tileId().text() );
414 QgsEventTracing::addEvent( QgsEventTracing::AsyncBegin, QStringLiteral(
"3D" ), QStringLiteral(
"Load " ) + node->tileId().text(), node->tileId().text() );
416 QgsChunkLoader *loader = mChunkLoaderFactory->createChunkLoader( node );
417 connect( loader, &QgsChunkQueueJob::finished,
this, &QgsChunkedEntity::onActiveJobFinished );
418 node->setLoading( loader );
421 else if ( node->state() == QgsChunkNode::QueuedForUpdate )
423 QgsEventTracing::addEvent( QgsEventTracing::AsyncBegin, QStringLiteral(
"3D" ), QStringLiteral(
"Update" ), node->tileId().text() );
426 connect( node->updater(), &QgsChunkQueueJob::finished,
this, &QgsChunkedEntity::onActiveJobFinished );
427 return node->updater();
436 void QgsChunkedEntity::cancelActiveJob( QgsChunkQueueJob *job )
440 QgsChunkNode *node = job->chunk();
442 if ( qobject_cast<QgsChunkLoader *>( job ) )
445 node->cancelLoading();
447 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral(
"3D" ), QStringLiteral(
"Load " ) + node->tileId().text(), node->tileId().text() );
448 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral(
"3D" ), QStringLiteral(
"Load" ), node->tileId().text() );
453 node->cancelUpdating();
455 QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral(
"3D" ), QStringLiteral(
"Update" ), node->tileId().text() );
459 mActiveJobs.removeOne( job );
463 void QgsChunkedEntity::cancelActiveJobs()
465 while ( !mActiveJobs.isEmpty() )
467 cancelActiveJob( mActiveJobs.takeFirst() );
472 void QgsChunkedEntity::setPickingEnabled(
bool enabled )
474 if ( mPickingEnabled == enabled )
477 mPickingEnabled = enabled;
481 QgsChunkListEntry *entry = mReplacementQueue->first();
484 QgsChunkNode *node = entry->chunk;
485 Qt3DRender::QObjectPicker *picker =
new Qt3DRender::QObjectPicker( node->entity() );
486 node->entity()->addComponent( picker );
487 connect( picker, &Qt3DRender::QObjectPicker::clicked,
this, &QgsChunkedEntity::onPickEvent );
494 for ( Qt3DRender::QObjectPicker *picker : findChildren<Qt3DRender::QObjectPicker *>() )
495 picker->deleteLater();
499 void QgsChunkedEntity::onPickEvent( Qt3DRender::QPickEvent *event )
501 Qt3DRender::QPickTriangleEvent *triangleEvent = qobject_cast<Qt3DRender::QPickTriangleEvent *>( event );
502 if ( !triangleEvent )
505 Qt3DRender::QObjectPicker *picker = qobject_cast<Qt3DRender::QObjectPicker *>( sender() );
509 Qt3DCore::QEntity *entity = qobject_cast<Qt3DCore::QEntity *>( picker->parent() );
515 for ( Qt3DRender::QGeometryRenderer *geomRenderer : entity->findChildren<Qt3DRender::QGeometryRenderer *>() )
520 if ( geomRenderer->objectName() != QLatin1String(
"main" ) )
525 fid = g->triangleIndexToFeatureId( triangleEvent->triangleIndex() );
533 emit pickedObject( event, fid );