/**************************************************************************** ** ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). ** All rights reserved. ** Contact: Nokia Corporation (qt-info@nokia.com) ** ** This file is part of the QtGui module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** No Commercial Usage ** This file contains pre-release code and may not be distributed. ** You may use this file in accordance with the terms and conditions ** contained in the Technology Preview License Agreement accompanying ** this package. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain additional ** rights. These rights are described in the Nokia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** If you have questions regarding the use of this file, please contact ** Nokia at qt-info@nokia.com. ** ** ** ** ** ** ** ** ** $QT_END_LICENSE$ ** ****************************************************************************/ /*! \class QGraphicsSceneBspTreeIndex \brief The QGraphicsSceneBspTreeIndex class provides an implementation of a BSP indexing algorithm for discovering items in QGraphicsScene. \since 4.6 \ingroup graphicsview-api \internal QGraphicsSceneBspTreeIndex index use a BSP(Binary Space Partitioning) implementation to discover items quickly. This implementation is very efficient for static scene. It has a depth that you can set. The depth directly affects performance and memory usage; the latter growing exponentially with the depth of the tree. With an optimal tree depth, the index can instantly determine the locality of items, even for scenes with thousands or millions of items. This also greatly improves rendering performance. By default, the value is 0, in which case Qt will guess a reasonable default depth based on the size, location and number of items in the scene. If these parameters change frequently, however, you may experience slowdowns as the index retunes the depth internally. You can avoid potential slowdowns by fixating the tree depth through setting this property. The depth of the tree and the size of the scene rectangle decide the granularity of the scene's partitioning. The size of each scene segment is determined by the following algorithm: The BSP tree has an optimal size when each segment contains between 0 and 10 items. \sa QGraphicsScene, QGraphicsView, QGraphicsSceneIndex */ #include #ifndef QT_NO_GRAPHICSVIEW #include #include #include #include #include QT_BEGIN_NAMESPACE static inline int intmaxlog(int n) { return (n > 0 ? qMax(qCeil(qLn(qreal(n)) / qLn(qreal(2))), 5) : 0); } /*! Constructs a private scene bsp index. */ QGraphicsSceneBspTreeIndexPrivate::QGraphicsSceneBspTreeIndexPrivate(QGraphicsScene *scene) : QGraphicsSceneIndexPrivate(scene), bspTreeDepth(0), indexTimerId(0), restartIndexTimer(false), regenerateIndex(true), lastItemCount(0), purgePending(false), sortCacheEnabled(false), updatingSortCache(false) { } /*! This method will update the BSP index by removing the items from the temporary unindexed list and add them in the indexedItems list. This will also update the growingItemsBoundingRect if needed. This will update the BSP implementation as well. \internal */ void QGraphicsSceneBspTreeIndexPrivate::_q_updateIndex() { Q_Q(QGraphicsSceneBspTreeIndex); if (!indexTimerId) return; q->killTimer(indexTimerId); indexTimerId = 0; purgeRemovedItems(); // Add unindexedItems to indexedItems for (int i = 0; i < unindexedItems.size(); ++i) { if (QGraphicsItem *item = unindexedItems.at(i)) { Q_ASSERT(!item->d_ptr->itemDiscovered); if (!freeItemIndexes.isEmpty()) { int freeIndex = freeItemIndexes.takeFirst(); item->d_func()->index = freeIndex; indexedItems[freeIndex] = item; } else { item->d_func()->index = indexedItems.size(); indexedItems << item; } } } // Determine whether we should regenerate the BSP tree. if (bspTreeDepth == 0) { int oldDepth = intmaxlog(lastItemCount); bspTreeDepth = intmaxlog(indexedItems.size()); static const int slack = 100; if (bsp.leafCount() == 0 || (oldDepth != bspTreeDepth && qAbs(lastItemCount - indexedItems.size()) > slack)) { // ### Crude algorithm. regenerateIndex = true; } } // Regenerate the tree. if (regenerateIndex) { regenerateIndex = false; bsp.initialize(sceneRect, bspTreeDepth); unindexedItems = indexedItems; lastItemCount = indexedItems.size(); } // Insert all unindexed items into the tree. for (int i = 0; i < unindexedItems.size(); ++i) { if (QGraphicsItem *item = unindexedItems.at(i)) { if (item->d_ptr->itemIsUntransformable()) { untransformableItems << item; continue; } if (item->d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorClipsChildren) continue; bsp.insertItem(item, item->d_ptr->sceneEffectiveBoundingRect()); } } unindexedItems.clear(); } /*! \internal Removes stale pointers from all data structures. */ void QGraphicsSceneBspTreeIndexPrivate::purgeRemovedItems() { if (!purgePending && removedItems.isEmpty()) return; // Remove stale items from the BSP tree. bsp.removeItems(removedItems); // Purge this list. removedItems.clear(); freeItemIndexes.clear(); for (int i = 0; i < indexedItems.size(); ++i) { if (!indexedItems.at(i)) freeItemIndexes << i; } purgePending = false; } /*! \internal Starts or restarts the timer used for reindexing unindexed items. */ void QGraphicsSceneBspTreeIndexPrivate::startIndexTimer(int interval) { Q_Q(QGraphicsSceneBspTreeIndex); if (indexTimerId) { restartIndexTimer = true; } else { indexTimerId = q->startTimer(interval); } } /*! \internal */ void QGraphicsSceneBspTreeIndexPrivate::resetIndex() { purgeRemovedItems(); for (int i = 0; i < indexedItems.size(); ++i) { if (QGraphicsItem *item = indexedItems.at(i)) { item->d_ptr->index = -1; Q_ASSERT(!item->d_ptr->itemDiscovered); unindexedItems << item; } } indexedItems.clear(); freeItemIndexes.clear(); untransformableItems.clear(); regenerateIndex = true; startIndexTimer(); } /*! \internal */ void QGraphicsSceneBspTreeIndexPrivate::climbTree(QGraphicsItem *item, int *stackingOrder) { if (!item->d_ptr->children.isEmpty()) { QList childList = item->d_ptr->children; qSort(childList.begin(), childList.end(), qt_closestLeaf); for (int i = 0; i < childList.size(); ++i) { QGraphicsItem *item = childList.at(i); if (!(item->flags() & QGraphicsItem::ItemStacksBehindParent)) climbTree(childList.at(i), stackingOrder); } item->d_ptr->globalStackingOrder = (*stackingOrder)++; for (int i = 0; i < childList.size(); ++i) { QGraphicsItem *item = childList.at(i); if (item->flags() & QGraphicsItem::ItemStacksBehindParent) climbTree(childList.at(i), stackingOrder); } } else { item->d_ptr->globalStackingOrder = (*stackingOrder)++; } } /*! \internal */ void QGraphicsSceneBspTreeIndexPrivate::_q_updateSortCache() { Q_Q(QGraphicsSceneBspTreeIndex); _q_updateIndex(); if (!sortCacheEnabled || !updatingSortCache) return; updatingSortCache = false; int stackingOrder = 0; QList topLevels; const QList items = q->items(); for (int i = 0; i < items.size(); ++i) { QGraphicsItem *item = items.at(i); if (item && !item->d_ptr->parent) topLevels << item; } qSort(topLevels.begin(), topLevels.end(), qt_closestLeaf); for (int i = 0; i < topLevels.size(); ++i) climbTree(topLevels.at(i), &stackingOrder); } /*! \internal */ void QGraphicsSceneBspTreeIndexPrivate::invalidateSortCache() { Q_Q(QGraphicsSceneBspTreeIndex); if (!sortCacheEnabled || updatingSortCache) return; updatingSortCache = true; QMetaObject::invokeMethod(q, "_q_updateSortCache", Qt::QueuedConnection); } void QGraphicsSceneBspTreeIndexPrivate::addItem(QGraphicsItem *item, bool recursive) { if (!item) return; // Prevent reusing a recently deleted pointer: purge all removed item from our lists. purgeRemovedItems(); // Invalidate any sort caching; arrival of a new item means we need to resort. // Update the scene's sort cache settings. item->d_ptr->globalStackingOrder = -1; invalidateSortCache(); // Indexing requires sceneBoundingRect(), but because \a item might // not be completely constructed at this point, we need to store it in // a temporary list and schedule an indexing for later. if (item->d_ptr->index == -1) { Q_ASSERT(!unindexedItems.contains(item)); unindexedItems << item; startIndexTimer(0); } else { Q_ASSERT(indexedItems.contains(item)); qWarning("QGraphicsSceneBspTreeIndex::addItem: item has already been added to this BSP"); } if (recursive) { for (int i = 0; i < item->d_ptr->children.size(); ++i) addItem(item->d_ptr->children.at(i), recursive); } } void QGraphicsSceneBspTreeIndexPrivate::removeItem(QGraphicsItem *item, bool recursive, bool moveToUnindexedItems) { if (!item) return; if (item->d_ptr->index != -1) { Q_ASSERT(item->d_ptr->index < indexedItems.size()); Q_ASSERT(indexedItems.at(item->d_ptr->index) == item); Q_ASSERT(!item->d_ptr->itemDiscovered); freeItemIndexes << item->d_ptr->index; indexedItems[item->d_ptr->index] = 0; item->d_ptr->index = -1; if (item->d_ptr->itemIsUntransformable()) { untransformableItems.removeOne(item); } else if (item->d_ptr->inDestructor) { // Avoid virtual function calls from the destructor. purgePending = true; removedItems << item; } else if (!(item->d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorClipsChildren)) { bsp.removeItem(item, item->d_ptr->sceneEffectiveBoundingRect()); } } else { unindexedItems.removeOne(item); } invalidateSortCache(); // ### Only do this when removing from BSP? Q_ASSERT(item->d_ptr->index == -1); Q_ASSERT(!indexedItems.contains(item)); Q_ASSERT(!unindexedItems.contains(item)); Q_ASSERT(!untransformableItems.contains(item)); if (moveToUnindexedItems) addItem(item); if (recursive) { for (int i = 0; i < item->d_ptr->children.size(); ++i) removeItem(item->d_ptr->children.at(i), recursive, moveToUnindexedItems); } } QList QGraphicsSceneBspTreeIndexPrivate::estimateItems(const QRectF &rect, Qt::SortOrder order, bool onlyTopLevelItems) { Q_Q(QGraphicsSceneBspTreeIndex); if (onlyTopLevelItems && rect.isNull()) return q->QGraphicsSceneIndex::estimateTopLevelItems(rect, order); purgeRemovedItems(); _q_updateSortCache(); Q_ASSERT(unindexedItems.isEmpty()); QList rectItems = bsp.items(rect, onlyTopLevelItems); if (onlyTopLevelItems) { for (int i = 0; i < untransformableItems.size(); ++i) { QGraphicsItem *item = untransformableItems.at(i); if (!item->d_ptr->parent) { rectItems << item; } else { item = item->topLevelItem(); if (!rectItems.contains(item)) rectItems << item; } } } else { rectItems += untransformableItems; } sortItems(&rectItems, order, sortCacheEnabled, onlyTopLevelItems); return rectItems; } /*! Returns true if \a item1 is on top of \a item2. \internal */ bool QGraphicsSceneBspTreeIndexPrivate::closestItemFirst_withoutCache(const QGraphicsItem *item1, const QGraphicsItem *item2) { // Siblings? Just check their z-values. const QGraphicsItemPrivate *d1 = item1->d_ptr.data(); const QGraphicsItemPrivate *d2 = item2->d_ptr.data(); if (d1->parent == d2->parent) return qt_closestLeaf(item1, item2); // Find common ancestor, and each item's ancestor closest to the common // ancestor. int item1Depth = d1->depth(); int item2Depth = d2->depth(); const QGraphicsItem *p = item1; const QGraphicsItem *t1 = item1; while (item1Depth > item2Depth && (p = p->d_ptr->parent)) { if (p == item2) { // item2 is one of item1's ancestors; item1 is on top return !(t1->d_ptr->flags & QGraphicsItem::ItemStacksBehindParent); } t1 = p; --item1Depth; } p = item2; const QGraphicsItem *t2 = item2; while (item2Depth > item1Depth && (p = p->d_ptr->parent)) { if (p == item1) { // item1 is one of item2's ancestors; item1 is not on top return (t2->d_ptr->flags & QGraphicsItem::ItemStacksBehindParent); } t2 = p; --item2Depth; } // item1Ancestor is now at the same level as item2Ancestor, but not the same. const QGraphicsItem *a1 = t1; const QGraphicsItem *a2 = t2; while (a1) { const QGraphicsItem *p1 = a1; const QGraphicsItem *p2 = a2; a1 = a1->parentItem(); a2 = a2->parentItem(); if (a1 && a1 == a2) return qt_closestLeaf(p1, p2); } // No common ancestor? Then just compare the items' toplevels directly. return qt_closestLeaf(t1->topLevelItem(), t2->topLevelItem()); } /*! Returns true if \a item2 is on top of \a item1. \internal */ bool QGraphicsSceneBspTreeIndexPrivate::closestItemLast_withoutCache(const QGraphicsItem *item1, const QGraphicsItem *item2) { return closestItemFirst_withoutCache(item2, item1); } /*! Sort a list of \a itemList in a specific \a order and use the cache if requested. \internal */ void QGraphicsSceneBspTreeIndexPrivate::sortItems(QList *itemList, Qt::SortOrder order, bool sortCacheEnabled, bool onlyTopLevelItems) { if (order == Qt::SortOrder(-1)) return; if (onlyTopLevelItems) { if (order == Qt::AscendingOrder) qSort(itemList->begin(), itemList->end(), qt_closestLeaf); else if (order == Qt::DescendingOrder) qSort(itemList->begin(), itemList->end(), qt_notclosestLeaf); return; } if (sortCacheEnabled) { if (order == Qt::AscendingOrder) { qSort(itemList->begin(), itemList->end(), closestItemFirst_withCache); } else if (order == Qt::DescendingOrder) { qSort(itemList->begin(), itemList->end(), closestItemLast_withCache); } } else { if (order == Qt::AscendingOrder) { qSort(itemList->begin(), itemList->end(), closestItemFirst_withoutCache); } else if (order == Qt::DescendingOrder) { qSort(itemList->begin(), itemList->end(), closestItemLast_withoutCache); } } } /*! Constructs a BSP scene index for the given \a scene. */ QGraphicsSceneBspTreeIndex::QGraphicsSceneBspTreeIndex(QGraphicsScene *scene) : QGraphicsSceneIndex(*new QGraphicsSceneBspTreeIndexPrivate(scene), scene) { } QGraphicsSceneBspTreeIndex::~QGraphicsSceneBspTreeIndex() { Q_D(QGraphicsSceneBspTreeIndex); for (int i = 0; i < d->indexedItems.size(); ++i) { // Ensure item bits are reset properly. if (QGraphicsItem *item = d->indexedItems.at(i)) { Q_ASSERT(!item->d_ptr->itemDiscovered); item->d_ptr->index = -1; } } } /*! \internal Clear the all the BSP index. */ void QGraphicsSceneBspTreeIndex::clear() { Q_D(QGraphicsSceneBspTreeIndex); d->bsp.clear(); d->lastItemCount = 0; d->freeItemIndexes.clear(); for (int i = 0; i < d->indexedItems.size(); ++i) { // Ensure item bits are reset properly. if (QGraphicsItem *item = d->indexedItems.at(i)) { Q_ASSERT(!item->d_ptr->itemDiscovered); item->d_ptr->index = -1; } } d->indexedItems.clear(); d->unindexedItems.clear(); d->untransformableItems.clear(); d->regenerateIndex = true; } /*! Add the \a item into the BSP index. */ void QGraphicsSceneBspTreeIndex::addItem(QGraphicsItem *item) { Q_D(QGraphicsSceneBspTreeIndex); d->addItem(item); } /*! Remove the \a item from the BSP index. */ void QGraphicsSceneBspTreeIndex::removeItem(QGraphicsItem *item) { Q_D(QGraphicsSceneBspTreeIndex); d->removeItem(item); } /*! \internal Update the BSP when the \a item 's bounding rect has changed. */ void QGraphicsSceneBspTreeIndex::prepareBoundingRectChange(const QGraphicsItem *item) { if (!item) return; if (item->d_ptr->index == -1 || item->d_ptr->itemIsUntransformable() || (item->d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorClipsChildren)) { return; // Item is not in BSP tree; nothing to do. } Q_D(QGraphicsSceneBspTreeIndex); QGraphicsItem *thatItem = const_cast(item); d->removeItem(thatItem, /*recursive=*/false, /*moveToUnindexedItems=*/true); for (int i = 0; i < item->d_ptr->children.size(); ++i) // ### Do we really need this? prepareBoundingRectChange(item->d_ptr->children.at(i)); } /*! Returns an estimation visible items that are either inside or intersect with the specified \a rect and return a list sorted using \a order. \a deviceTransform is the transformation apply to the view. */ QList QGraphicsSceneBspTreeIndex::estimateItems(const QRectF &rect, Qt::SortOrder order) const { Q_D(const QGraphicsSceneBspTreeIndex); return const_cast(d)->estimateItems(rect, order); } QList QGraphicsSceneBspTreeIndex::estimateTopLevelItems(const QRectF &rect, Qt::SortOrder order) const { Q_D(const QGraphicsSceneBspTreeIndex); return const_cast(d)->estimateItems(rect, order, /*onlyTopLevels=*/true); } /*! \fn QList QGraphicsSceneBspTreeIndex::items(Qt::SortOrder order = Qt::AscendingOrder) const; Return all items in the BSP index and sort them using \a order. */ QList QGraphicsSceneBspTreeIndex::items(Qt::SortOrder order) const { Q_D(const QGraphicsSceneBspTreeIndex); const_cast(d)->purgeRemovedItems(); QList itemList; // If freeItemIndexes is empty, we know there are no holes in indexedItems and // unindexedItems. if (d->freeItemIndexes.isEmpty()) { if (d->unindexedItems.isEmpty()) { itemList = d->indexedItems; } else { itemList = d->indexedItems + d->unindexedItems; } } else { // Rebuild the list of items to avoid holes. ### We could also just // compress the item lists at this point. foreach (QGraphicsItem *item, d->indexedItems + d->unindexedItems) { if (item) itemList << item; } } if (order != -1) { //We sort descending order d->sortItems(&itemList, order, d->sortCacheEnabled); } return itemList; } /*! \property QGraphicsSceneBspTreeIndex::bspTreeDepth \brief the depth of the BSP index tree \since 4.6 This value determines the depth of BSP tree. The depth directly affects performance and memory usage; the latter growing exponentially with the depth of the tree. With an optimal tree depth, the index can instantly determine the locality of items, even for scenes with thousands or millions of items. This also greatly improves rendering performance. By default, the value is 0, in which case Qt will guess a reasonable default depth based on the size, location and number of items in the scene. If these parameters change frequently, however, you may experience slowdowns as the index retunes the depth internally. You can avoid potential slowdowns by fixating the tree depth through setting this property. The depth of the tree and the size of the scene rectangle decide the granularity of the scene's partitioning. The size of each scene segment is determined by the following algorithm: The BSP tree has an optimal size when each segment contains between 0 and 10 items. */ int QGraphicsSceneBspTreeIndex::bspTreeDepth() { Q_D(const QGraphicsSceneBspTreeIndex); return d->bspTreeDepth; } void QGraphicsSceneBspTreeIndex::setBspTreeDepth(int depth) { Q_D(QGraphicsSceneBspTreeIndex); if (d->bspTreeDepth == depth) return; d->bspTreeDepth = depth; d->resetIndex(); } /*! \internal This method react to the \a rect change of the scene and reset the BSP tree index. */ void QGraphicsSceneBspTreeIndex::updateSceneRect(const QRectF &rect) { Q_D(QGraphicsSceneBspTreeIndex); d->sceneRect = rect; d->resetIndex(); } /*! \internal This method react to the \a change of the \a item and use the \a value to update the BSP tree if necessary. */ void QGraphicsSceneBspTreeIndex::itemChange(const QGraphicsItem *item, QGraphicsItem::GraphicsItemChange change, const QVariant &value) { Q_D(QGraphicsSceneBspTreeIndex); switch (change) { case QGraphicsItem::ItemFlagsChange: { // Handle ItemIgnoresTransformations bool ignoredTransform = item->d_ptr->flags & QGraphicsItem::ItemIgnoresTransformations; bool willIgnoreTransform = value.toUInt() & QGraphicsItem::ItemIgnoresTransformations; bool clipsChildren = item->d_ptr->flags & QGraphicsItem::ItemClipsChildrenToShape; bool willClipChildren = value.toUInt() & QGraphicsItem::ItemClipsChildrenToShape; if ((ignoredTransform != willIgnoreTransform) || (clipsChildren != willClipChildren)) { QGraphicsItem *thatItem = const_cast(item); // Remove item and its descendants from the index and append // them to the list of unindexed items. Then, when the index // is updated, they will be put into the bsp-tree or the list // of untransformable items. d->removeItem(thatItem, /*recursive=*/true, /*moveToUnidexedItems=*/true); } break; } case QGraphicsItem::ItemZValueChange: d->invalidateSortCache(); break; case QGraphicsItem::ItemParentChange: { d->invalidateSortCache(); // Handle ItemIgnoresTransformations QGraphicsItem *newParent = qVariantValue(value); bool ignoredTransform = item->d_ptr->itemIsUntransformable(); bool willIgnoreTransform = (item->d_ptr->flags & QGraphicsItem::ItemIgnoresTransformations) || (newParent && newParent->d_ptr->itemIsUntransformable()); bool ancestorClippedChildren = item->d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorClipsChildren; bool ancestorWillClipChildren = newParent && ((newParent->d_ptr->flags & QGraphicsItem::ItemClipsChildrenToShape) || (newParent->d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorClipsChildren)); if ((ignoredTransform != willIgnoreTransform) || (ancestorClippedChildren != ancestorWillClipChildren)) { QGraphicsItem *thatItem = const_cast(item); // Remove item and its descendants from the index and append // them to the list of unindexed items. Then, when the index // is updated, they will be put into the bsp-tree or the list // of untransformable items. d->removeItem(thatItem, /*recursive=*/true, /*moveToUnidexedItems=*/true); } break; } default: break; } return QGraphicsSceneIndex::itemChange(item, change, value); } /*! \reimp Used to catch the timer event. \internal */ bool QGraphicsSceneBspTreeIndex::event(QEvent *event) { Q_D(QGraphicsSceneBspTreeIndex); switch (event->type()) { case QEvent::Timer: if (d->indexTimerId && static_cast(event)->timerId() == d->indexTimerId) { if (d->restartIndexTimer) { d->restartIndexTimer = false; } else { // this call will kill the timer d->_q_updateIndex(); } } // Fallthrough intended - support timers in subclasses. default: return QObject::event(event); } return true; } QT_END_NAMESPACE #include "moc_qgraphicsscenebsptreeindex_p.cpp" #endif // QT_NO_GRAPHICSVIEW