18 #include <QElapsedTimer> 29 static float screenSpaceError(
float epsilon,
float distance,
float screenSize,
float fov )
51 float phi = epsilon * screenSize / ( 2 * distance * tan( fov * M_PI / ( 2 * 180 ) ) );
55 static float screenSpaceError( QgsChunkNode *node,
const QgsChunkedEntity::SceneState &state )
57 float dist = node->bbox().distanceFromPoint( state.cameraPos );
61 float sse = screenSpaceError( node->error(), dist, state.screenSizePx, state.cameraFov );
65 QgsChunkedEntity::QgsChunkedEntity(
const QgsAABB &rootBbox,
float rootError,
float tau,
int maxLevel, QgsChunkLoaderFactory *loaderFactory, Qt3DCore::QNode *parent )
68 , mMaxLevel( maxLevel )
69 , mChunkLoaderFactory( loaderFactory )
71 mRootNode =
new QgsChunkNode( 0, 0, 0, rootBbox, rootError );
72 mChunkLoaderQueue =
new QgsChunkList;
73 mReplacementQueue =
new QgsChunkList;
77 QgsChunkedEntity::~QgsChunkedEntity()
82 Q_ASSERT( !mActiveJob );
85 while ( !mChunkLoaderQueue->isEmpty() )
87 QgsChunkListEntry *entry = mChunkLoaderQueue->takeFirst();
88 QgsChunkNode *node = entry->chunk;
90 if ( node->state() == QgsChunkNode::QueuedForLoad )
91 node->cancelQueuedForLoad();
92 else if ( node->state() == QgsChunkNode::QueuedForUpdate )
93 node->cancelQueuedForUpdate();
98 delete mChunkLoaderQueue;
100 while ( !mReplacementQueue->isEmpty() )
102 QgsChunkListEntry *entry = mReplacementQueue->takeFirst();
105 entry->chunk->unloadChunk();
108 delete mReplacementQueue;
116 void QgsChunkedEntity::update(
const SceneState &state )
121 int oldJobsCount = pendingJobsCount();
123 QSet<QgsChunkNode *> activeBefore = QSet<QgsChunkNode *>::fromList( mActiveNodes );
124 mActiveNodes.clear();
126 mCurrentTime = QTime::currentTime();
128 update( mRootNode, state );
130 int enabled = 0, disabled = 0, unloaded = 0;
132 Q_FOREACH ( QgsChunkNode *node, mActiveNodes )
134 if ( activeBefore.contains( node ) )
135 activeBefore.remove( node );
138 node->entity()->setEnabled(
true );
144 Q_FOREACH ( QgsChunkNode *node, activeBefore )
146 node->entity()->setEnabled(
false );
152 while ( mReplacementQueue->count() > mMaxLoadedChunks )
154 QgsChunkListEntry *entry = mReplacementQueue->takeLast();
155 entry->chunk->unloadChunk();
161 QList<QgsAABB> bboxes;
162 Q_FOREACH ( QgsChunkNode *n, mActiveNodes )
164 mBboxesEntity->setBoxes( bboxes );
171 mNeedsUpdate =
false;
173 if ( pendingJobsCount() != oldJobsCount )
174 emit pendingJobsCountChanged();
176 qDebug() <<
"update: active " << mActiveNodes.count() <<
" enabled " << enabled <<
" disabled " << disabled <<
" | culled " << mFrustumCulled <<
" | loading " << mChunkLoaderQueue->count() <<
" loaded " << mReplacementQueue->count() <<
" | unloaded " << unloaded <<
" elapsed " << t.elapsed() <<
"ms";
179 void QgsChunkedEntity::setShowBoundingBoxes(
bool enabled )
181 if ( ( enabled && mBboxesEntity ) || ( !enabled && !mBboxesEntity ) )
186 mBboxesEntity =
new QgsChunkBoundsEntity(
this );
190 mBboxesEntity->deleteLater();
191 mBboxesEntity =
nullptr;
195 void QgsChunkedEntity::updateNodes(
const QList<QgsChunkNode *> &nodes, QgsChunkQueueJobFactory *updateJobFactory )
197 Q_FOREACH ( QgsChunkNode *node, nodes )
199 if ( node->state() == QgsChunkNode::QueuedForUpdate )
201 mChunkLoaderQueue->takeEntry( node->loaderQueueEntry() );
202 node->cancelQueuedForUpdate();
204 else if ( node->state() == QgsChunkNode::Updating )
209 Q_ASSERT( node->state() == QgsChunkNode::Loaded );
211 QgsChunkListEntry *entry =
new QgsChunkListEntry( node );
212 node->setQueuedForUpdate( entry, updateJobFactory );
213 mChunkLoaderQueue->insertLast( entry );
221 int QgsChunkedEntity::pendingJobsCount()
const 223 return mChunkLoaderQueue->count() + ( mActiveJob ? 1 : 0 );
227 void QgsChunkedEntity::update( QgsChunkNode *node,
const SceneState &state )
235 node->ensureAllChildrenExist();
239 requestResidency( node );
241 if ( !node->entity() )
249 if ( screenSpaceError( node, state ) <= mTau )
253 mActiveNodes << node;
255 else if ( node->allChildChunksResident( mCurrentTime ) )
259 QgsChunkNode *
const *children = node->children();
260 for (
int i = 0; i < 4; ++i )
261 update( children[i], state );
267 mActiveNodes << node;
269 if ( node->level() < mMaxLevel )
271 QgsChunkNode *
const *children = node->children();
272 for (
int i = 0; i < 4; ++i )
273 requestResidency( children[i] );
279 void QgsChunkedEntity::requestResidency( QgsChunkNode *node )
281 if ( node->state() == QgsChunkNode::Loaded || node->state() == QgsChunkNode::QueuedForUpdate || node->state() == QgsChunkNode::Updating )
283 Q_ASSERT( node->replacementQueueEntry() );
284 Q_ASSERT( node->entity() );
285 mReplacementQueue->takeEntry( node->replacementQueueEntry() );
286 mReplacementQueue->insertFirst( node->replacementQueueEntry() );
288 else if ( node->state() == QgsChunkNode::QueuedForLoad )
291 Q_ASSERT( node->loaderQueueEntry() );
292 Q_ASSERT( !node->loader() );
293 if ( node->loaderQueueEntry()->prev || node->loaderQueueEntry()->next )
295 mChunkLoaderQueue->takeEntry( node->loaderQueueEntry() );
296 mChunkLoaderQueue->insertFirst( node->loaderQueueEntry() );
299 else if ( node->state() == QgsChunkNode::Loading )
303 else if ( node->state() == QgsChunkNode::Skeleton )
305 if ( !node->hasData() )
309 QgsChunkListEntry *entry =
new QgsChunkListEntry( node );
310 node->setQueuedForLoad( entry );
311 mChunkLoaderQueue->insertFirst( entry );
314 Q_ASSERT(
false &&
"impossible!" );
318 void QgsChunkedEntity::onActiveJobFinished()
320 int oldJobsCount = pendingJobsCount();
322 QgsChunkQueueJob *job = qobject_cast<QgsChunkQueueJob *>( sender() );
324 Q_ASSERT( job == mActiveJob );
326 QgsChunkNode *node = job->chunk();
328 if ( QgsChunkLoader *loader = qobject_cast<QgsChunkLoader *>( job ) )
330 Q_ASSERT( node->state() == QgsChunkNode::Loading );
331 Q_ASSERT( node->loader() == loader );
334 Qt3DCore::QEntity *entity = node->loader()->createEntity(
this );
339 node->setLoaded( entity );
341 mReplacementQueue->insertFirst( node->replacementQueueEntry() );
345 node->setHasData(
false );
346 node->cancelLoading();
354 Q_ASSERT( node->state() == QgsChunkNode::Updating );
359 mActiveJob->deleteLater();
360 mActiveJob =
nullptr;
365 if ( pendingJobsCount() != oldJobsCount )
366 emit pendingJobsCountChanged();
369 void QgsChunkedEntity::startJob()
371 Q_ASSERT( !mActiveJob );
372 if ( mChunkLoaderQueue->isEmpty() )
375 QgsChunkListEntry *entry = mChunkLoaderQueue->takeFirst();
377 QgsChunkNode *node = entry->chunk;
380 if ( node->state() == QgsChunkNode::QueuedForLoad )
382 QgsChunkLoader *loader = mChunkLoaderFactory->createChunkLoader( node );
383 connect( loader, &QgsChunkQueueJob::finished,
this, &QgsChunkedEntity::onActiveJobFinished );
384 node->setLoading( loader );
387 else if ( node->state() == QgsChunkNode::QueuedForUpdate )
390 connect( node->updater(), &QgsChunkQueueJob::finished,
this, &QgsChunkedEntity::onActiveJobFinished );
391 mActiveJob = node->updater();
397 void QgsChunkedEntity::cancelActiveJob()
399 Q_ASSERT( mActiveJob );
401 QgsChunkNode *node = mActiveJob->chunk();
403 if ( qobject_cast<QgsChunkLoader *>( mActiveJob ) )
406 node->cancelLoading();
411 node->cancelUpdating();
414 mActiveJob->cancel();
415 mActiveJob->deleteLater();
416 mActiveJob =
nullptr;
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.