1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2016 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the QtWidgets module of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:LGPL$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU Lesser General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
21 | ** packaging of this file. Please review the following information to |
22 | ** ensure the GNU Lesser General Public License version 3 requirements |
23 | ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
24 | ** |
25 | ** GNU General Public License Usage |
26 | ** Alternatively, this file may be used under the terms of the GNU |
27 | ** General Public License version 2.0 or (at your option) the GNU General |
28 | ** Public license version 3 or any later version approved by the KDE Free |
29 | ** Qt Foundation. The licenses are as published by the Free Software |
30 | ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
31 | ** included in the packaging of this file. Please review the following |
32 | ** information to ensure the GNU General Public License requirements will |
33 | ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
34 | ** https://www.gnu.org/licenses/gpl-3.0.html. |
35 | ** |
36 | ** $QT_END_LICENSE$ |
37 | ** |
38 | ****************************************************************************/ |
39 | |
40 | /*! |
41 | \class QGraphicsSceneBspTreeIndex |
42 | \brief The QGraphicsSceneBspTreeIndex class provides an implementation of |
43 | a BSP indexing algorithm for discovering items in QGraphicsScene. |
44 | \since 4.6 |
45 | \ingroup graphicsview-api |
46 | |
47 | \internal |
48 | |
49 | QGraphicsSceneBspTreeIndex index use a BSP(Binary Space Partitioning) |
50 | implementation to discover items quickly. This implementation is |
51 | very efficient for static scenes. It has a depth that you can set. |
52 | The depth directly affects performance and memory usage; the latter |
53 | growing exponentially with the depth of the tree. With an optimal tree |
54 | depth, the index can instantly determine the locality of items, even |
55 | for scenes with thousands or millions of items. This also greatly improves |
56 | rendering performance. |
57 | |
58 | By default, the depth value is 0, in which case Qt will guess a reasonable |
59 | default depth based on the size, location and number of items in the |
60 | scene. If these parameters change frequently, however, you may experience |
61 | slowdowns as the index retunes the depth internally. You can avoid |
62 | potential slowdowns by fixating the tree depth through setting this |
63 | property. |
64 | |
65 | The depth of the tree and the size of the scene rectangle decide the |
66 | granularity of the scene's partitioning. The size of each scene segment is |
67 | determined by the following algorithm: |
68 | |
69 | The BSP tree has an optimal size when each segment contains between 0 and |
70 | 10 items. |
71 | |
72 | \sa QGraphicsScene, QGraphicsView, QGraphicsSceneIndex |
73 | */ |
74 | |
75 | #include <QtCore/qglobal.h> |
76 | |
77 | #include <private/qgraphicsscene_p.h> |
78 | #include <private/qgraphicsscenebsptreeindex_p.h> |
79 | #include <private/qgraphicssceneindex_p.h> |
80 | |
81 | #include <QtCore/qmath.h> |
82 | #include <QtCore/qdebug.h> |
83 | |
84 | #include <algorithm> |
85 | |
86 | QT_BEGIN_NAMESPACE |
87 | |
88 | static inline int intmaxlog(int n) |
89 | { |
90 | return (n > 0 ? qMax(a: qCeil(v: qLn(v: qreal(n)) / qLn(v: qreal(2))), b: 5) : 0); |
91 | } |
92 | |
93 | /*! |
94 | Constructs a private scene bsp index. |
95 | */ |
96 | QGraphicsSceneBspTreeIndexPrivate::QGraphicsSceneBspTreeIndexPrivate(QGraphicsScene *scene) |
97 | : QGraphicsSceneIndexPrivate(scene), |
98 | bspTreeDepth(0), |
99 | indexTimerId(0), |
100 | restartIndexTimer(false), |
101 | regenerateIndex(true), |
102 | lastItemCount(0), |
103 | purgePending(false), |
104 | sortCacheEnabled(false), |
105 | updatingSortCache(false) |
106 | { |
107 | } |
108 | |
109 | |
110 | /*! |
111 | This method will update the BSP index by removing the items from the temporary |
112 | unindexed list and add them in the indexedItems list. This will also |
113 | update the growingItemsBoundingRect if needed. This will update the BSP |
114 | implementation as well. |
115 | |
116 | \internal |
117 | */ |
118 | void QGraphicsSceneBspTreeIndexPrivate::_q_updateIndex() |
119 | { |
120 | Q_Q(QGraphicsSceneBspTreeIndex); |
121 | if (!indexTimerId) |
122 | return; |
123 | |
124 | q->killTimer(id: indexTimerId); |
125 | indexTimerId = 0; |
126 | |
127 | purgeRemovedItems(); |
128 | |
129 | // Add unindexedItems to indexedItems |
130 | for (int i = 0; i < unindexedItems.size(); ++i) { |
131 | if (QGraphicsItem *item = unindexedItems.at(i)) { |
132 | Q_ASSERT(!item->d_ptr->itemDiscovered); |
133 | if (!freeItemIndexes.isEmpty()) { |
134 | int freeIndex = freeItemIndexes.takeFirst(); |
135 | item->d_func()->index = freeIndex; |
136 | indexedItems[freeIndex] = item; |
137 | } else { |
138 | item->d_func()->index = indexedItems.size(); |
139 | indexedItems << item; |
140 | } |
141 | } |
142 | } |
143 | |
144 | // Determine whether we should regenerate the BSP tree. |
145 | if (bspTreeDepth == 0) { |
146 | int oldDepth = intmaxlog(n: lastItemCount); |
147 | bspTreeDepth = intmaxlog(n: indexedItems.size()); |
148 | static const int slack = 100; |
149 | if (bsp.leafCount() == 0 || (oldDepth != bspTreeDepth && qAbs(t: lastItemCount - indexedItems.size()) > slack)) { |
150 | // ### Crude algorithm. |
151 | regenerateIndex = true; |
152 | } |
153 | } |
154 | |
155 | // Regenerate the tree. |
156 | if (regenerateIndex) { |
157 | regenerateIndex = false; |
158 | bsp.initialize(rect: sceneRect, depth: bspTreeDepth); |
159 | unindexedItems = indexedItems; |
160 | lastItemCount = indexedItems.size(); |
161 | } |
162 | |
163 | // Insert all unindexed items into the tree. |
164 | for (int i = 0; i < unindexedItems.size(); ++i) { |
165 | if (QGraphicsItem *item = unindexedItems.at(i)) { |
166 | if (item->d_ptr->itemIsUntransformable()) { |
167 | untransformableItems << item; |
168 | continue; |
169 | } |
170 | if (item->d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorClipsChildren |
171 | || item->d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorContainsChildren) |
172 | continue; |
173 | |
174 | bsp.insertItem(item, rect: item->d_ptr->sceneEffectiveBoundingRect()); |
175 | } |
176 | } |
177 | unindexedItems.clear(); |
178 | } |
179 | |
180 | |
181 | /*! |
182 | \internal |
183 | |
184 | Removes stale pointers from all data structures. |
185 | */ |
186 | void QGraphicsSceneBspTreeIndexPrivate::purgeRemovedItems() |
187 | { |
188 | if (!purgePending && removedItems.isEmpty()) |
189 | return; |
190 | |
191 | // Remove stale items from the BSP tree. |
192 | bsp.removeItems(items: removedItems); |
193 | // Purge this list. |
194 | removedItems.clear(); |
195 | freeItemIndexes.clear(); |
196 | for (int i = 0; i < indexedItems.size(); ++i) { |
197 | if (!indexedItems.at(i)) |
198 | freeItemIndexes << i; |
199 | } |
200 | purgePending = false; |
201 | } |
202 | |
203 | /*! |
204 | \internal |
205 | |
206 | Starts or restarts the timer used for reindexing unindexed items. |
207 | */ |
208 | void QGraphicsSceneBspTreeIndexPrivate::startIndexTimer(int interval) |
209 | { |
210 | Q_Q(QGraphicsSceneBspTreeIndex); |
211 | if (indexTimerId) { |
212 | restartIndexTimer = true; |
213 | } else { |
214 | indexTimerId = q->startTimer(interval); |
215 | } |
216 | } |
217 | |
218 | /*! |
219 | \internal |
220 | */ |
221 | void QGraphicsSceneBspTreeIndexPrivate::resetIndex() |
222 | { |
223 | purgeRemovedItems(); |
224 | for (int i = 0; i < indexedItems.size(); ++i) { |
225 | if (QGraphicsItem *item = indexedItems.at(i)) { |
226 | item->d_ptr->index = -1; |
227 | Q_ASSERT(!item->d_ptr->itemDiscovered); |
228 | unindexedItems << item; |
229 | } |
230 | } |
231 | indexedItems.clear(); |
232 | freeItemIndexes.clear(); |
233 | untransformableItems.clear(); |
234 | regenerateIndex = true; |
235 | startIndexTimer(); |
236 | } |
237 | |
238 | /*! |
239 | \internal |
240 | */ |
241 | void QGraphicsSceneBspTreeIndexPrivate::climbTree(QGraphicsItem *item, int *stackingOrder) |
242 | { |
243 | if (!item->d_ptr->children.isEmpty()) { |
244 | QList<QGraphicsItem *> childList = item->d_ptr->children; |
245 | std::sort(first: childList.begin(), last: childList.end(), comp: qt_closestLeaf); |
246 | for (int i = 0; i < childList.size(); ++i) { |
247 | QGraphicsItem *item = childList.at(i); |
248 | if (!(item->flags() & QGraphicsItem::ItemStacksBehindParent)) |
249 | climbTree(item: childList.at(i), stackingOrder); |
250 | } |
251 | item->d_ptr->globalStackingOrder = (*stackingOrder)++; |
252 | for (int i = 0; i < childList.size(); ++i) { |
253 | QGraphicsItem *item = childList.at(i); |
254 | if (item->flags() & QGraphicsItem::ItemStacksBehindParent) |
255 | climbTree(item: childList.at(i), stackingOrder); |
256 | } |
257 | } else { |
258 | item->d_ptr->globalStackingOrder = (*stackingOrder)++; |
259 | } |
260 | } |
261 | |
262 | /*! |
263 | \internal |
264 | */ |
265 | void QGraphicsSceneBspTreeIndexPrivate::_q_updateSortCache() |
266 | { |
267 | Q_Q(QGraphicsSceneBspTreeIndex); |
268 | _q_updateIndex(); |
269 | |
270 | if (!sortCacheEnabled || !updatingSortCache) |
271 | return; |
272 | |
273 | updatingSortCache = false; |
274 | int stackingOrder = 0; |
275 | |
276 | QList<QGraphicsItem *> topLevels; |
277 | const QList<QGraphicsItem *> items = q->items(); |
278 | for (int i = 0; i < items.size(); ++i) { |
279 | QGraphicsItem *item = items.at(i); |
280 | if (item && !item->d_ptr->parent) |
281 | topLevels << item; |
282 | } |
283 | |
284 | std::sort(first: topLevels.begin(), last: topLevels.end(), comp: qt_closestLeaf); |
285 | for (int i = 0; i < topLevels.size(); ++i) |
286 | climbTree(item: topLevels.at(i), stackingOrder: &stackingOrder); |
287 | } |
288 | |
289 | /*! |
290 | \internal |
291 | */ |
292 | void QGraphicsSceneBspTreeIndexPrivate::invalidateSortCache() |
293 | { |
294 | Q_Q(QGraphicsSceneBspTreeIndex); |
295 | if (!sortCacheEnabled || updatingSortCache) |
296 | return; |
297 | |
298 | updatingSortCache = true; |
299 | QMetaObject::invokeMethod(obj: q, member: "_q_updateSortCache" , type: Qt::QueuedConnection); |
300 | } |
301 | |
302 | void QGraphicsSceneBspTreeIndexPrivate::addItem(QGraphicsItem *item, bool recursive) |
303 | { |
304 | if (!item) |
305 | return; |
306 | |
307 | // Prevent reusing a recently deleted pointer: purge all removed item from our lists. |
308 | purgeRemovedItems(); |
309 | |
310 | // Invalidate any sort caching; arrival of a new item means we need to resort. |
311 | // Update the scene's sort cache settings. |
312 | item->d_ptr->globalStackingOrder = -1; |
313 | invalidateSortCache(); |
314 | |
315 | // Indexing requires sceneBoundingRect(), but because \a item might |
316 | // not be completely constructed at this point, we need to store it in |
317 | // a temporary list and schedule an indexing for later. |
318 | if (item->d_ptr->index == -1) { |
319 | Q_ASSERT(!unindexedItems.contains(item)); |
320 | unindexedItems << item; |
321 | startIndexTimer(interval: 0); |
322 | } else { |
323 | Q_ASSERT(indexedItems.contains(item)); |
324 | qWarning(msg: "QGraphicsSceneBspTreeIndex::addItem: item has already been added to this BSP" ); |
325 | } |
326 | |
327 | if (recursive) { |
328 | for (int i = 0; i < item->d_ptr->children.size(); ++i) |
329 | addItem(item: item->d_ptr->children.at(i), recursive); |
330 | } |
331 | } |
332 | |
333 | void QGraphicsSceneBspTreeIndexPrivate::removeItem(QGraphicsItem *item, bool recursive, |
334 | bool moveToUnindexedItems) |
335 | { |
336 | if (!item) |
337 | return; |
338 | |
339 | if (item->d_ptr->index != -1) { |
340 | Q_ASSERT(item->d_ptr->index < indexedItems.size()); |
341 | Q_ASSERT(indexedItems.at(item->d_ptr->index) == item); |
342 | Q_ASSERT(!item->d_ptr->itemDiscovered); |
343 | freeItemIndexes << item->d_ptr->index; |
344 | indexedItems[item->d_ptr->index] = 0; |
345 | item->d_ptr->index = -1; |
346 | |
347 | if (item->d_ptr->itemIsUntransformable()) { |
348 | untransformableItems.removeOne(t: item); |
349 | } else if (item->d_ptr->inDestructor) { |
350 | // Avoid virtual function calls from the destructor. |
351 | purgePending = true; |
352 | removedItems << item; |
353 | } else if (!(item->d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorClipsChildren |
354 | || item->d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorContainsChildren)) { |
355 | bsp.removeItem(item, rect: item->d_ptr->sceneEffectiveBoundingRect()); |
356 | } |
357 | } else { |
358 | unindexedItems.removeOne(t: item); |
359 | } |
360 | invalidateSortCache(); // ### Only do this when removing from BSP? |
361 | |
362 | Q_ASSERT(item->d_ptr->index == -1); |
363 | Q_ASSERT(!indexedItems.contains(item)); |
364 | Q_ASSERT(!unindexedItems.contains(item)); |
365 | Q_ASSERT(!untransformableItems.contains(item)); |
366 | |
367 | if (moveToUnindexedItems) |
368 | addItem(item); |
369 | |
370 | if (recursive) { |
371 | for (int i = 0; i < item->d_ptr->children.size(); ++i) |
372 | removeItem(item: item->d_ptr->children.at(i), recursive, moveToUnindexedItems); |
373 | } |
374 | } |
375 | |
376 | QList<QGraphicsItem *> QGraphicsSceneBspTreeIndexPrivate::estimateItems(const QRectF &rect, Qt::SortOrder order, |
377 | bool onlyTopLevelItems) |
378 | { |
379 | Q_Q(QGraphicsSceneBspTreeIndex); |
380 | if (onlyTopLevelItems && rect.isNull()) |
381 | return q->QGraphicsSceneIndex::estimateTopLevelItems(rect, order); |
382 | |
383 | purgeRemovedItems(); |
384 | _q_updateSortCache(); |
385 | Q_ASSERT(unindexedItems.isEmpty()); |
386 | |
387 | QList<QGraphicsItem *> rectItems = bsp.items(rect, onlyTopLevelItems); |
388 | if (onlyTopLevelItems) { |
389 | for (int i = 0; i < untransformableItems.size(); ++i) { |
390 | QGraphicsItem *item = untransformableItems.at(i); |
391 | if (!item->d_ptr->parent) { |
392 | rectItems << item; |
393 | } else { |
394 | item = item->topLevelItem(); |
395 | if (!rectItems.contains(t: item)) |
396 | rectItems << item; |
397 | } |
398 | } |
399 | } else { |
400 | rectItems += untransformableItems; |
401 | } |
402 | |
403 | sortItems(itemList: &rectItems, order, cached: sortCacheEnabled, onlyTopLevelItems); |
404 | return rectItems; |
405 | } |
406 | |
407 | /*! |
408 | Sort a list of \a itemList in a specific \a order and use the cache if requested. |
409 | |
410 | \internal |
411 | */ |
412 | void QGraphicsSceneBspTreeIndexPrivate::sortItems(QList<QGraphicsItem *> *itemList, Qt::SortOrder order, |
413 | bool sortCacheEnabled, bool onlyTopLevelItems) |
414 | { |
415 | if (order == Qt::SortOrder(-1)) |
416 | return; |
417 | |
418 | if (onlyTopLevelItems) { |
419 | if (order == Qt::DescendingOrder) |
420 | std::sort(first: itemList->begin(), last: itemList->end(), comp: qt_closestLeaf); |
421 | else if (order == Qt::AscendingOrder) |
422 | std::sort(first: itemList->begin(), last: itemList->end(), comp: qt_notclosestLeaf); |
423 | return; |
424 | } |
425 | |
426 | if (sortCacheEnabled) { |
427 | if (order == Qt::DescendingOrder) { |
428 | std::sort(first: itemList->begin(), last: itemList->end(), comp: closestItemFirst_withCache); |
429 | } else if (order == Qt::AscendingOrder) { |
430 | std::sort(first: itemList->begin(), last: itemList->end(), comp: closestItemLast_withCache); |
431 | } |
432 | } else { |
433 | if (order == Qt::DescendingOrder) { |
434 | std::sort(first: itemList->begin(), last: itemList->end(), comp: qt_closestItemFirst); |
435 | } else if (order == Qt::AscendingOrder) { |
436 | std::sort(first: itemList->begin(), last: itemList->end(), comp: qt_closestItemLast); |
437 | } |
438 | } |
439 | } |
440 | |
441 | /*! |
442 | Constructs a BSP scene index for the given \a scene. |
443 | */ |
444 | QGraphicsSceneBspTreeIndex::QGraphicsSceneBspTreeIndex(QGraphicsScene *scene) |
445 | : QGraphicsSceneIndex(*new QGraphicsSceneBspTreeIndexPrivate(scene), scene) |
446 | { |
447 | |
448 | } |
449 | |
450 | QGraphicsSceneBspTreeIndex::~QGraphicsSceneBspTreeIndex() |
451 | { |
452 | Q_D(QGraphicsSceneBspTreeIndex); |
453 | for (int i = 0; i < d->indexedItems.size(); ++i) { |
454 | // Ensure item bits are reset properly. |
455 | if (QGraphicsItem *item = d->indexedItems.at(i)) { |
456 | Q_ASSERT(!item->d_ptr->itemDiscovered); |
457 | item->d_ptr->index = -1; |
458 | } |
459 | } |
460 | } |
461 | |
462 | /*! |
463 | \internal |
464 | Clear the all the BSP index. |
465 | */ |
466 | void QGraphicsSceneBspTreeIndex::clear() |
467 | { |
468 | Q_D(QGraphicsSceneBspTreeIndex); |
469 | d->bsp.clear(); |
470 | d->lastItemCount = 0; |
471 | d->freeItemIndexes.clear(); |
472 | for (int i = 0; i < d->indexedItems.size(); ++i) { |
473 | // Ensure item bits are reset properly. |
474 | if (QGraphicsItem *item = d->indexedItems.at(i)) { |
475 | Q_ASSERT(!item->d_ptr->itemDiscovered); |
476 | item->d_ptr->index = -1; |
477 | } |
478 | } |
479 | d->indexedItems.clear(); |
480 | d->unindexedItems.clear(); |
481 | d->untransformableItems.clear(); |
482 | d->regenerateIndex = true; |
483 | } |
484 | |
485 | /*! |
486 | Add the \a item into the BSP index. |
487 | */ |
488 | void QGraphicsSceneBspTreeIndex::addItem(QGraphicsItem *item) |
489 | { |
490 | Q_D(QGraphicsSceneBspTreeIndex); |
491 | d->addItem(item); |
492 | } |
493 | |
494 | /*! |
495 | Remove the \a item from the BSP index. |
496 | */ |
497 | void QGraphicsSceneBspTreeIndex::removeItem(QGraphicsItem *item) |
498 | { |
499 | Q_D(QGraphicsSceneBspTreeIndex); |
500 | d->removeItem(item); |
501 | } |
502 | |
503 | /*! |
504 | \internal |
505 | Update the BSP when the \a item 's bounding rect has changed. |
506 | */ |
507 | void QGraphicsSceneBspTreeIndex::prepareBoundingRectChange(const QGraphicsItem *item) |
508 | { |
509 | if (!item) |
510 | return; |
511 | |
512 | if (item->d_ptr->index == -1 || item->d_ptr->itemIsUntransformable() |
513 | || (item->d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorClipsChildren |
514 | || item->d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorContainsChildren)) { |
515 | return; // Item is not in BSP tree; nothing to do. |
516 | } |
517 | |
518 | Q_D(QGraphicsSceneBspTreeIndex); |
519 | QGraphicsItem *thatItem = const_cast<QGraphicsItem *>(item); |
520 | d->removeItem(item: thatItem, /*recursive=*/false, /*moveToUnindexedItems=*/true); |
521 | for (int i = 0; i < item->d_ptr->children.size(); ++i) // ### Do we really need this? |
522 | prepareBoundingRectChange(item: item->d_ptr->children.at(i)); |
523 | } |
524 | |
525 | /*! |
526 | Returns an estimation visible items that are either inside or |
527 | intersect with the specified \a rect and return a list sorted using \a order. |
528 | |
529 | \a deviceTransform is the transformation apply to the view. |
530 | |
531 | */ |
532 | QList<QGraphicsItem *> QGraphicsSceneBspTreeIndex::estimateItems(const QRectF &rect, Qt::SortOrder order) const |
533 | { |
534 | Q_D(const QGraphicsSceneBspTreeIndex); |
535 | return const_cast<QGraphicsSceneBspTreeIndexPrivate*>(d)->estimateItems(rect, order); |
536 | } |
537 | |
538 | QList<QGraphicsItem *> QGraphicsSceneBspTreeIndex::estimateTopLevelItems(const QRectF &rect, Qt::SortOrder order) const |
539 | { |
540 | Q_D(const QGraphicsSceneBspTreeIndex); |
541 | return const_cast<QGraphicsSceneBspTreeIndexPrivate*>(d)->estimateItems(rect, order, /*onlyTopLevels=*/onlyTopLevelItems: true); |
542 | } |
543 | |
544 | /*! |
545 | \fn QList<QGraphicsItem *> QGraphicsSceneBspTreeIndex::items(Qt::SortOrder order = Qt::DescendingOrder) const; |
546 | |
547 | Return all items in the BSP index and sort them using \a order. |
548 | */ |
549 | QList<QGraphicsItem *> QGraphicsSceneBspTreeIndex::items(Qt::SortOrder order) const |
550 | { |
551 | Q_D(const QGraphicsSceneBspTreeIndex); |
552 | const_cast<QGraphicsSceneBspTreeIndexPrivate*>(d)->purgeRemovedItems(); |
553 | QList<QGraphicsItem *> itemList; |
554 | itemList.reserve(size: d->indexedItems.size() + d->unindexedItems.size()); |
555 | |
556 | // Rebuild the list of items to avoid holes. ### We could also just |
557 | // compress the item lists at this point. |
558 | QGraphicsItem *null = nullptr; // work-around for (at least) MSVC 2012 emitting |
559 | // warning C4100 for its own header <algorithm> |
560 | // when passing nullptr directly to remove_copy: |
561 | std::remove_copy(first: d->indexedItems.cbegin(), last: d->indexedItems.cend(), |
562 | result: std::back_inserter(x&: itemList), value: null); |
563 | std::remove_copy(first: d->unindexedItems.cbegin(), last: d->unindexedItems.cend(), |
564 | result: std::back_inserter(x&: itemList), value: null); |
565 | |
566 | d->sortItems(itemList: &itemList, order, sortCacheEnabled: d->sortCacheEnabled); |
567 | return itemList; |
568 | } |
569 | |
570 | /*! |
571 | \property QGraphicsSceneBspTreeIndex::bspTreeDepth |
572 | \brief the depth of the BSP index tree |
573 | \since 4.6 |
574 | |
575 | This value determines the depth of BSP tree. The depth |
576 | directly affects performance and memory usage; the latter |
577 | growing exponentially with the depth of the tree. With an optimal tree |
578 | depth, the index can instantly determine the locality of items, even |
579 | for scenes with thousands or millions of items. This also greatly improves |
580 | rendering performance. |
581 | |
582 | By default, the value is 0, in which case Qt will guess a reasonable |
583 | default depth based on the size, location and number of items in the |
584 | scene. If these parameters change frequently, however, you may experience |
585 | slowdowns as the index retunes the depth internally. You can avoid |
586 | potential slowdowns by fixating the tree depth through setting this |
587 | property. |
588 | |
589 | The depth of the tree and the size of the scene rectangle decide the |
590 | granularity of the scene's partitioning. The size of each scene segment is |
591 | determined by the following algorithm: |
592 | |
593 | The BSP tree has an optimal size when each segment contains between 0 and |
594 | 10 items. |
595 | |
596 | */ |
597 | int QGraphicsSceneBspTreeIndex::bspTreeDepth() const |
598 | { |
599 | Q_D(const QGraphicsSceneBspTreeIndex); |
600 | return d->bspTreeDepth; |
601 | } |
602 | |
603 | void QGraphicsSceneBspTreeIndex::setBspTreeDepth(int depth) |
604 | { |
605 | Q_D(QGraphicsSceneBspTreeIndex); |
606 | if (d->bspTreeDepth == depth) |
607 | return; |
608 | d->bspTreeDepth = depth; |
609 | d->resetIndex(); |
610 | } |
611 | |
612 | /*! |
613 | \internal |
614 | |
615 | This method react to the \a rect change of the scene and |
616 | reset the BSP tree index. |
617 | */ |
618 | void QGraphicsSceneBspTreeIndex::updateSceneRect(const QRectF &rect) |
619 | { |
620 | Q_D(QGraphicsSceneBspTreeIndex); |
621 | d->sceneRect = rect; |
622 | d->resetIndex(); |
623 | } |
624 | |
625 | /*! |
626 | \internal |
627 | |
628 | This method react to the \a change of the \a item and use the \a value to |
629 | update the BSP tree if necessary. |
630 | */ |
631 | void QGraphicsSceneBspTreeIndex::itemChange(const QGraphicsItem *item, QGraphicsItem::GraphicsItemChange change, const void *const value) |
632 | { |
633 | Q_D(QGraphicsSceneBspTreeIndex); |
634 | switch (change) { |
635 | case QGraphicsItem::ItemFlagsChange: { |
636 | // Handle ItemIgnoresTransformations |
637 | QGraphicsItem::GraphicsItemFlags newFlags = *static_cast<const QGraphicsItem::GraphicsItemFlags *>(value); |
638 | bool ignoredTransform = item->d_ptr->flags & QGraphicsItem::ItemIgnoresTransformations; |
639 | bool willIgnoreTransform = newFlags & QGraphicsItem::ItemIgnoresTransformations; |
640 | bool clipsChildren = item->d_ptr->flags & QGraphicsItem::ItemClipsChildrenToShape |
641 | || item->d_ptr->flags & QGraphicsItem::ItemContainsChildrenInShape; |
642 | bool willClipChildren = newFlags & QGraphicsItem::ItemClipsChildrenToShape |
643 | || newFlags & QGraphicsItem::ItemContainsChildrenInShape; |
644 | if ((ignoredTransform != willIgnoreTransform) || (clipsChildren != willClipChildren)) { |
645 | QGraphicsItem *thatItem = const_cast<QGraphicsItem *>(item); |
646 | // Remove item and its descendants from the index and append |
647 | // them to the list of unindexed items. Then, when the index |
648 | // is updated, they will be put into the bsp-tree or the list |
649 | // of untransformable items. |
650 | d->removeItem(item: thatItem, /*recursive=*/true, /*moveToUnidexedItems=*/moveToUnindexedItems: true); |
651 | } |
652 | break; |
653 | } |
654 | case QGraphicsItem::ItemZValueChange: |
655 | d->invalidateSortCache(); |
656 | break; |
657 | case QGraphicsItem::ItemParentChange: { |
658 | d->invalidateSortCache(); |
659 | // Handle ItemIgnoresTransformations |
660 | const QGraphicsItem *newParent = static_cast<const QGraphicsItem *>(value); |
661 | bool ignoredTransform = item->d_ptr->itemIsUntransformable(); |
662 | bool willIgnoreTransform = (item->d_ptr->flags & QGraphicsItem::ItemIgnoresTransformations) |
663 | || (newParent && newParent->d_ptr->itemIsUntransformable()); |
664 | bool ancestorClippedChildren = item->d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorClipsChildren |
665 | || item->d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorContainsChildren; |
666 | bool ancestorWillClipChildren = newParent |
667 | && ((newParent->d_ptr->flags & QGraphicsItem::ItemClipsChildrenToShape |
668 | || newParent->d_ptr->flags & QGraphicsItem::ItemContainsChildrenInShape) |
669 | || (newParent->d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorClipsChildren |
670 | || newParent->d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorContainsChildren)); |
671 | if ((ignoredTransform != willIgnoreTransform) || (ancestorClippedChildren != ancestorWillClipChildren)) { |
672 | QGraphicsItem *thatItem = const_cast<QGraphicsItem *>(item); |
673 | // Remove item and its descendants from the index and append |
674 | // them to the list of unindexed items. Then, when the index |
675 | // is updated, they will be put into the bsp-tree or the list |
676 | // of untransformable items. |
677 | d->removeItem(item: thatItem, /*recursive=*/true, /*moveToUnidexedItems=*/moveToUnindexedItems: true); |
678 | } |
679 | break; |
680 | } |
681 | default: |
682 | break; |
683 | } |
684 | } |
685 | /*! |
686 | \reimp |
687 | |
688 | Used to catch the timer event. |
689 | |
690 | \internal |
691 | */ |
692 | bool QGraphicsSceneBspTreeIndex::event(QEvent *event) |
693 | { |
694 | Q_D(QGraphicsSceneBspTreeIndex); |
695 | if (event->type() == QEvent::Timer) { |
696 | if (d->indexTimerId && static_cast<QTimerEvent *>(event)->timerId() == d->indexTimerId) { |
697 | if (d->restartIndexTimer) { |
698 | d->restartIndexTimer = false; |
699 | } else { |
700 | // this call will kill the timer |
701 | d->_q_updateIndex(); |
702 | } |
703 | } |
704 | } |
705 | return QObject::event(event); |
706 | } |
707 | |
708 | QT_END_NAMESPACE |
709 | |
710 | #include "moc_qgraphicsscenebsptreeindex_p.cpp" |
711 | |