Add R-Tree data structure.
Review URL: https://codereview.appspot.com/6489055 git-svn-id: http://skia.googlecode.com/svn/trunk@5401 2bbb7eff-a529-9590-31e7-b0007b416f81
This commit is contained in:
parent
d6bbbf8a83
commit
1f45e934b6
@ -14,6 +14,7 @@
|
||||
'<(skia_src_path)/core/SkAdvancedTypefaceMetrics.cpp',
|
||||
'<(skia_src_path)/core/SkAlphaRuns.cpp',
|
||||
'<(skia_src_path)/core/SkAntiRun.h',
|
||||
'<(skia_src_path)/core/SkBBoxHierarchy.h',
|
||||
'<(skia_src_path)/core/SkBitmap.cpp',
|
||||
'<(skia_src_path)/core/SkBitmapHeap.cpp',
|
||||
'<(skia_src_path)/core/SkBitmapHeap.h',
|
||||
@ -127,6 +128,8 @@
|
||||
'<(skia_src_path)/core/SkRegion.cpp',
|
||||
'<(skia_src_path)/core/SkRegionPriv.h',
|
||||
'<(skia_src_path)/core/SkRegion_path.cpp',
|
||||
'<(skia_src_path)/core/SkRTree.h',
|
||||
'<(skia_src_path)/core/SkRTree.cpp',
|
||||
'<(skia_src_path)/core/SkScalar.cpp',
|
||||
'<(skia_src_path)/core/SkScalerContext.cpp',
|
||||
'<(skia_src_path)/core/SkScan.cpp',
|
||||
|
@ -75,6 +75,7 @@
|
||||
'../tests/RefCntTest.cpp',
|
||||
'../tests/RefDictTest.cpp',
|
||||
'../tests/RegionTest.cpp',
|
||||
'../tests/RTreeTest.cpp',
|
||||
'../tests/ScalarTest.cpp',
|
||||
'../tests/ShaderOpacityTest.cpp',
|
||||
'../tests/Sk64Test.cpp',
|
||||
|
53
src/core/SkBBoxHierarchy.h
Normal file
53
src/core/SkBBoxHierarchy.h
Normal file
@ -0,0 +1,53 @@
|
||||
|
||||
/*
|
||||
* Copyright 2012 Google Inc.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license that can be
|
||||
* found in the LICENSE file.
|
||||
*/
|
||||
|
||||
#ifndef SkBBoxHierarchy_DEFINED
|
||||
#define SkBBoxHierarchy_DEFINED
|
||||
|
||||
#include "SkRect.h"
|
||||
#include "SkTDArray.h"
|
||||
|
||||
/**
|
||||
* Interface for a spatial data structure that associates user data pointers with axis-aligned
|
||||
* bounding boxes, and allows efficient retrieval of intersections with query rectangles.
|
||||
*/
|
||||
class SkBBoxHierarchy {
|
||||
public:
|
||||
virtual ~SkBBoxHierarchy() { }
|
||||
|
||||
/**
|
||||
* Insert a data pointer and corresponding bounding box
|
||||
* @param data The data pointer, may be NULL
|
||||
* @param bounds The bounding box, should not be empty
|
||||
* @param defer Whether or not it is acceptable to delay insertion of this element (building up
|
||||
* an entire spatial data structure at once is often faster and produces better
|
||||
* structures than repeated inserts) until flushDeferredInserts is called or the first
|
||||
* search.
|
||||
*/
|
||||
virtual void insert(void* data, const SkIRect& bounds, bool defer = false) = 0;
|
||||
|
||||
/**
|
||||
* If any insertions have been deferred, this forces them to be inserted
|
||||
*/
|
||||
virtual void flushDeferredInserts() = 0;
|
||||
|
||||
/**
|
||||
* Populate 'results' with data pointers corresponding to bounding boxes that intersect 'query'
|
||||
*/
|
||||
virtual void search(const SkIRect& query, SkTDArray<void*>* results) = 0;
|
||||
|
||||
virtual void clear() = 0;
|
||||
|
||||
/**
|
||||
* Gets the number of insertions
|
||||
*/
|
||||
virtual int getCount() const = 0;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
470
src/core/SkRTree.cpp
Normal file
470
src/core/SkRTree.cpp
Normal file
@ -0,0 +1,470 @@
|
||||
|
||||
/*
|
||||
* Copyright 2012 Google Inc.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license that can be
|
||||
* found in the LICENSE file.
|
||||
*/
|
||||
|
||||
#include "SkRTree.h"
|
||||
#include "SkTSort.h"
|
||||
|
||||
static inline uint32_t get_area(const SkIRect& rect);
|
||||
static inline uint32_t get_overlap(const SkIRect& rect1, const SkIRect& rect2);
|
||||
static inline uint32_t get_margin(const SkIRect& rect);
|
||||
static inline uint32_t get_overlap_increase(const SkIRect& rect1, const SkIRect& rect2,
|
||||
SkIRect expandBy);
|
||||
static inline uint32_t get_area_increase(const SkIRect& rect1, SkIRect rect2);
|
||||
static inline void join_no_empty_check(const SkIRect& joinWith, SkIRect* out);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
SkRTree* SkRTree::Create(int minChildren, int maxChildren) {
|
||||
if (minChildren < maxChildren && (maxChildren + 1) / 2 >= minChildren &&
|
||||
minChildren > 0 && maxChildren < static_cast<int>(SK_MaxU16)) {
|
||||
return new SkRTree(minChildren, maxChildren);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
SkRTree::SkRTree(int minChildren, int maxChildren)
|
||||
: fMinChildren(minChildren)
|
||||
, fMaxChildren(maxChildren)
|
||||
, fNodeSize(sizeof(Node) + sizeof(Branch) * maxChildren)
|
||||
, fCount(0)
|
||||
, fNodes(fNodeSize * 256) {
|
||||
SkASSERT(minChildren < maxChildren && minChildren > 0 && maxChildren <
|
||||
static_cast<int>(SK_MaxU16));
|
||||
SkASSERT((maxChildren + 1) / 2 >= minChildren);
|
||||
this->validate();
|
||||
}
|
||||
|
||||
SkRTree::~SkRTree() {
|
||||
this->clear();
|
||||
}
|
||||
|
||||
void SkRTree::insert(void* data, const SkIRect& bounds, bool defer) {
|
||||
this->validate();
|
||||
if (bounds.isEmpty()) {
|
||||
SkASSERT(false);
|
||||
return;
|
||||
}
|
||||
Branch newBranch;
|
||||
newBranch.fBounds = bounds;
|
||||
newBranch.fChild.data = data;
|
||||
if (this->isEmpty()) {
|
||||
// since a bulk-load into an existing tree is as of yet unimplemented (and arguably not
|
||||
// of vital importance right now), we only batch up inserts if the tree is empty.
|
||||
if (defer) {
|
||||
fDeferredInserts.push(newBranch);
|
||||
return;
|
||||
} else {
|
||||
fRoot.fChild.subtree = allocateNode(0);
|
||||
fRoot.fChild.subtree->fNumChildren = 0;
|
||||
}
|
||||
}
|
||||
|
||||
Branch* newSibling = insert(fRoot.fChild.subtree, &newBranch);
|
||||
fRoot.fBounds = this->computeBounds(fRoot.fChild.subtree);
|
||||
|
||||
if (NULL != newSibling) {
|
||||
Node* oldRoot = fRoot.fChild.subtree;
|
||||
Node* newRoot = this->allocateNode(oldRoot->fLevel + 1);
|
||||
newRoot->fNumChildren = 2;
|
||||
*newRoot->child(0) = fRoot;
|
||||
*newRoot->child(1) = *newSibling;
|
||||
fRoot.fChild.subtree = newRoot;
|
||||
fRoot.fBounds = this->computeBounds(fRoot.fChild.subtree);
|
||||
}
|
||||
|
||||
++fCount;
|
||||
this->validate();
|
||||
}
|
||||
|
||||
void SkRTree::flushDeferredInserts() {
|
||||
this->validate();
|
||||
if (this->isEmpty() && fDeferredInserts.count() > 0) {
|
||||
fCount = fDeferredInserts.count();
|
||||
if (1 == fCount) {
|
||||
fRoot.fChild.subtree = allocateNode(0);
|
||||
fRoot.fChild.subtree->fNumChildren = 0;
|
||||
this->insert(fRoot.fChild.subtree, &fDeferredInserts[0]);
|
||||
fRoot.fBounds = fDeferredInserts[0].fBounds;
|
||||
} else {
|
||||
fRoot = this->bulkLoad(&fDeferredInserts);
|
||||
}
|
||||
} else {
|
||||
// TODO: some algorithm for bulk loading into an already populated tree
|
||||
SkASSERT(0 == fDeferredInserts.count());
|
||||
}
|
||||
fDeferredInserts.rewind();
|
||||
this->validate();
|
||||
}
|
||||
|
||||
void SkRTree::search(const SkIRect& query, SkTDArray<void*>* results) {
|
||||
this->validate();
|
||||
if (0 != fDeferredInserts.count()) {
|
||||
this->flushDeferredInserts();
|
||||
}
|
||||
if (!this->isEmpty() && SkIRect::IntersectsNoEmptyCheck(fRoot.fBounds, query)) {
|
||||
this->search(fRoot.fChild.subtree, query, results);
|
||||
}
|
||||
this->validate();
|
||||
}
|
||||
|
||||
void SkRTree::clear() {
|
||||
this->validate();
|
||||
fNodes.reset();
|
||||
fDeferredInserts.rewind();
|
||||
fCount = 0;
|
||||
this->validate();
|
||||
}
|
||||
|
||||
SkRTree::Node* SkRTree::allocateNode(uint16_t level) {
|
||||
Node* out = static_cast<Node*>(fNodes.allocThrow(fNodeSize));
|
||||
out->fNumChildren = 0;
|
||||
out->fLevel = level;
|
||||
return out;
|
||||
}
|
||||
|
||||
SkRTree::Branch* SkRTree::insert(Node* root, Branch* branch, uint16_t level) {
|
||||
Branch* toInsert = branch;
|
||||
if (root->fLevel != level) {
|
||||
int childIndex = this->chooseSubtree(root, branch);
|
||||
toInsert = this->insert(root->child(childIndex)->fChild.subtree, branch, level);
|
||||
root->child(childIndex)->fBounds = this->computeBounds(
|
||||
root->child(childIndex)->fChild.subtree);
|
||||
}
|
||||
if (NULL != toInsert) {
|
||||
if (root->fNumChildren == fMaxChildren) {
|
||||
// handle overflow by splitting. TODO: opportunistic reinsertion
|
||||
|
||||
// decide on a distribution to divide with
|
||||
Node* newSibling = this->allocateNode(root->fLevel);
|
||||
Branch* toDivide = SkNEW_ARRAY(Branch, fMaxChildren + 1);
|
||||
for (int i = 0; i < fMaxChildren; ++i) {
|
||||
toDivide[i] = *root->child(i);
|
||||
}
|
||||
toDivide[fMaxChildren] = *toInsert;
|
||||
int splitIndex = this->distributeChildren(toDivide);
|
||||
|
||||
// divide up the branches
|
||||
root->fNumChildren = splitIndex;
|
||||
newSibling->fNumChildren = fMaxChildren + 1 - splitIndex;
|
||||
for (int i = 0; i < splitIndex; ++i) {
|
||||
*root->child(i) = toDivide[i];
|
||||
}
|
||||
for (int i = splitIndex; i < fMaxChildren + 1; ++i) {
|
||||
*newSibling->child(i - splitIndex) = toDivide[i];
|
||||
}
|
||||
SkDELETE_ARRAY(toDivide);
|
||||
|
||||
// pass the new sibling branch up to the parent
|
||||
branch->fChild.subtree = newSibling;
|
||||
branch->fBounds = this->computeBounds(newSibling);
|
||||
return branch;
|
||||
} else {
|
||||
*root->child(root->fNumChildren) = *toInsert;
|
||||
++root->fNumChildren;
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int SkRTree::chooseSubtree(Node* root, Branch* branch) {
|
||||
SkASSERT(!root->isLeaf());
|
||||
if (1 < root->fLevel) {
|
||||
// root's child pointers do not point to leaves, so minimize area increase
|
||||
int32_t minAreaIncrease = SK_MaxS32;
|
||||
int32_t minArea = SK_MaxS32;
|
||||
int32_t bestSubtree = -1;
|
||||
for (int i = 0; i < root->fNumChildren; ++i) {
|
||||
const SkIRect& subtreeBounds = root->child(i)->fBounds;
|
||||
int32_t areaIncrease = get_area_increase(subtreeBounds, branch->fBounds);
|
||||
// break ties in favor of subtree with smallest area
|
||||
if (areaIncrease < minAreaIncrease || (areaIncrease == minAreaIncrease &&
|
||||
static_cast<int32_t>(get_area(subtreeBounds)) < minArea)) {
|
||||
minAreaIncrease = areaIncrease;
|
||||
minArea = get_area(subtreeBounds);
|
||||
bestSubtree = i;
|
||||
}
|
||||
}
|
||||
SkASSERT(-1 != bestSubtree);
|
||||
return bestSubtree;
|
||||
} else if (1 == root->fLevel) {
|
||||
// root's child pointers do point to leaves, so minimize overlap increase
|
||||
int32_t minOverlapIncrease = SK_MaxS32;
|
||||
int32_t minAreaIncrease = SK_MaxS32;
|
||||
int32_t bestSubtree = -1;
|
||||
for (int32_t i = 0; i < root->fNumChildren; ++i) {
|
||||
const SkIRect& subtreeBounds = root->child(i)->fBounds;
|
||||
SkIRect expandedBounds = subtreeBounds;
|
||||
join_no_empty_check(branch->fBounds, &expandedBounds);
|
||||
int32_t overlap = 0;
|
||||
for (int32_t j = 0; j < root->fNumChildren; ++j) {
|
||||
if (j == i) continue;
|
||||
// Note: this would be more correct if we subtracted the original pre-expanded
|
||||
// overlap, but computing overlaps is expensive and omitting it doesn't seem to
|
||||
// hurt query performance. See get_overlap_increase()
|
||||
overlap += get_overlap(expandedBounds, root->child(j)->fBounds);
|
||||
}
|
||||
// break ties with lowest area increase
|
||||
if (overlap < minOverlapIncrease || (overlap == minOverlapIncrease &&
|
||||
static_cast<int32_t>(get_area_increase(branch->fBounds, subtreeBounds)) <
|
||||
minAreaIncrease)) {
|
||||
minOverlapIncrease = overlap;
|
||||
minAreaIncrease = get_area_increase(branch->fBounds, subtreeBounds);
|
||||
bestSubtree = i;
|
||||
}
|
||||
}
|
||||
return bestSubtree;
|
||||
} else {
|
||||
SkASSERT(false);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
SkIRect SkRTree::computeBounds(Node* n) {
|
||||
SkIRect r = n->child(0)->fBounds;
|
||||
for (int i = 1; i < n->fNumChildren; ++i) {
|
||||
join_no_empty_check(n->child(i)->fBounds, &r);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
int SkRTree::distributeChildren(Branch* children) {
|
||||
// We have two sides to sort by on each of two axes:
|
||||
const static SortSide sorts[2][2] = {
|
||||
{&SkIRect::fLeft, &SkIRect::fRight},
|
||||
{&SkIRect::fTop, &SkIRect::fBottom}
|
||||
};
|
||||
|
||||
// We want to choose an axis to split on, then a distribution along that axis; we'll need
|
||||
// three pieces of info: the split axis, the side to sort by on that axis, and the index
|
||||
// to split the sorted array on.
|
||||
int32_t sortSide = -1;
|
||||
int32_t k = -1;
|
||||
int32_t axis = -1;
|
||||
int32_t bestS = SK_MaxS32;
|
||||
|
||||
// Evaluate each axis, we want the min summed margin-value (s) over all distributions
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
int32_t minOverlap = SK_MaxS32;
|
||||
int32_t minArea = SK_MaxS32;
|
||||
int32_t axisBestK = 0;
|
||||
int32_t axisBestSide = 0;
|
||||
int32_t s = 0;
|
||||
|
||||
// Evaluate each sort
|
||||
for (int j = 0; j < 2; ++j) {
|
||||
|
||||
SkQSort(sorts[i][j], children, children + fMaxChildren, &RectLessThan);
|
||||
|
||||
// Evaluate each split index
|
||||
for (int32_t k = 1; k <= fMaxChildren - 2 * fMinChildren + 2; ++k) {
|
||||
SkIRect r1 = children[0].fBounds;
|
||||
SkIRect r2 = children[fMinChildren + k - 1].fBounds;
|
||||
for (int32_t l = 1; l < fMinChildren - 1 + k; ++l) {
|
||||
join_no_empty_check(children[l].fBounds, &r1);
|
||||
}
|
||||
for (int32_t l = fMinChildren + k; l < fMaxChildren + 1; ++l) {
|
||||
join_no_empty_check(children[l].fBounds, &r2);
|
||||
}
|
||||
|
||||
int32_t area = get_area(r1) + get_area(r2);
|
||||
int32_t overlap = get_overlap(r1, r2);
|
||||
s += get_margin(r1) + get_margin(r2);
|
||||
|
||||
if (overlap < minOverlap || (overlap == minOverlap && area < minArea)) {
|
||||
minOverlap = overlap;
|
||||
minArea = area;
|
||||
axisBestSide = j;
|
||||
axisBestK = k;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (s < bestS) {
|
||||
bestS = s;
|
||||
axis = i;
|
||||
sortSide = axisBestSide;
|
||||
k = axisBestK;
|
||||
}
|
||||
}
|
||||
|
||||
// replicate the sort of the winning distribution, (we can skip this if the last
|
||||
// sort ended up being best)
|
||||
if (!(axis == 1 && sortSide == 1)) {
|
||||
SkQSort(sorts[axis][sortSide], children, children + fMaxChildren, &RectLessThan);
|
||||
}
|
||||
|
||||
return fMinChildren - 1 + k;
|
||||
}
|
||||
|
||||
void SkRTree::search(Node* root, const SkIRect query, SkTDArray<void*>* results) const {
|
||||
for (int i = 0; i < root->fNumChildren; ++i) {
|
||||
if (SkIRect::IntersectsNoEmptyCheck(root->child(i)->fBounds, query)) {
|
||||
if (root->isLeaf()) {
|
||||
results->push(root->child(i)->fChild.data);
|
||||
} else {
|
||||
this->search(root->child(i)->fChild.subtree, query, results);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SkRTree::Branch SkRTree::bulkLoad(SkTDArray<Branch>* branches, int level) {
|
||||
if (branches->count() == 1) {
|
||||
// Only one branch: it will be the root
|
||||
Branch out = (*branches)[0];
|
||||
branches->rewind();
|
||||
return out;
|
||||
} else {
|
||||
// First we sort the whole list by y coordinates
|
||||
SkQSort<int, Branch>(level, branches->begin(), branches->end() - 1, &RectLessY);
|
||||
|
||||
int numBranches = branches->count() / fMaxChildren;
|
||||
int remainder = branches->count() % fMaxChildren;
|
||||
int newBranches = 0;
|
||||
|
||||
if (0 != remainder) {
|
||||
++numBranches;
|
||||
// If the remainder isn't enough to fill a node, we'll need to add fewer nodes to
|
||||
// some other branches to make up for it
|
||||
if (remainder >= fMinChildren) {
|
||||
remainder = 0;
|
||||
} else {
|
||||
remainder = fMinChildren - remainder;
|
||||
}
|
||||
}
|
||||
|
||||
int numStrips = SkScalarCeil(SkScalarSqrt(SkIntToScalar(numBranches)));
|
||||
int currentBranch = 0;
|
||||
|
||||
for (int i = 0; i < numStrips; ++i) {
|
||||
int begin = currentBranch;
|
||||
int end = currentBranch + numStrips * fMaxChildren - SkMin32(remainder,
|
||||
(fMaxChildren - fMinChildren) * numStrips);
|
||||
if (end > branches->count()) {
|
||||
end = branches->count();
|
||||
}
|
||||
|
||||
// Now we sort horizontal strips of rectangles by their x coords
|
||||
SkQSort<int, Branch>(level, branches->begin() + begin, branches->begin() + end - 1,
|
||||
&RectLessX);
|
||||
|
||||
for (int j = 0; j < numStrips && currentBranch < branches->count(); ++j) {
|
||||
int incrementBy = fMaxChildren;
|
||||
if (remainder != 0) {
|
||||
// if need be, omit some nodes to make up for remainder
|
||||
if (remainder <= fMaxChildren - fMinChildren) {
|
||||
incrementBy -= remainder;
|
||||
remainder = 0;
|
||||
} else {
|
||||
incrementBy = fMinChildren;
|
||||
remainder -= fMaxChildren - fMinChildren;
|
||||
}
|
||||
}
|
||||
Node* n = allocateNode(level);
|
||||
n->fNumChildren = 1;
|
||||
*n->child(0) = (*branches)[currentBranch];
|
||||
Branch b;
|
||||
b.fBounds = (*branches)[currentBranch].fBounds;
|
||||
b.fChild.subtree = n;
|
||||
++currentBranch;
|
||||
for (int k = 1; k < incrementBy && currentBranch < branches->count(); ++k) {
|
||||
b.fBounds.join((*branches)[currentBranch].fBounds);
|
||||
*n->child(k) = (*branches)[currentBranch];
|
||||
++n->fNumChildren;
|
||||
++currentBranch;
|
||||
}
|
||||
(*branches)[newBranches] = b;
|
||||
++newBranches;
|
||||
}
|
||||
}
|
||||
branches->setCount(newBranches);
|
||||
return this->bulkLoad(branches, level + 1);
|
||||
}
|
||||
}
|
||||
|
||||
void SkRTree::validate() {
|
||||
#ifdef SK_DEBUG
|
||||
if (this->isEmpty()) {
|
||||
return;
|
||||
}
|
||||
SkASSERT(fCount == this->validateSubtree(fRoot.fChild.subtree, fRoot.fBounds, true));
|
||||
#endif
|
||||
}
|
||||
|
||||
int SkRTree::validateSubtree(Node* root, SkIRect bounds, bool isRoot) {
|
||||
// make sure the pointer is pointing to a valid place
|
||||
SkASSERT(fNodes.contains(static_cast<void*>(root)));
|
||||
|
||||
if (isRoot) {
|
||||
// If the root of this subtree is the overall root, we have looser standards:
|
||||
if (root->isLeaf()) {
|
||||
SkASSERT(root->fNumChildren >= 1 && root->fNumChildren <= fMaxChildren);
|
||||
} else {
|
||||
SkASSERT(root->fNumChildren >= 2 && root->fNumChildren <= fMaxChildren);
|
||||
}
|
||||
} else {
|
||||
SkASSERT(root->fNumChildren >= fMinChildren && root->fNumChildren <= fMaxChildren);
|
||||
}
|
||||
|
||||
for (int i = 0; i < root->fNumChildren; ++i) {
|
||||
SkASSERT(bounds.contains(root->child(i)->fBounds));
|
||||
}
|
||||
|
||||
if (root->isLeaf()) {
|
||||
SkASSERT(0 == root->fLevel);
|
||||
return root->fNumChildren;
|
||||
} else {
|
||||
int childCount = 0;
|
||||
for (int i = 0; i < root->fNumChildren; ++i) {
|
||||
SkASSERT(root->child(i)->fChild.subtree->fLevel == root->fLevel - 1);
|
||||
childCount += this->validateSubtree(root->child(i)->fChild.subtree,
|
||||
root->child(i)->fBounds);
|
||||
}
|
||||
return childCount;
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static inline uint32_t get_area(const SkIRect& rect) {
|
||||
return rect.width() * rect.height();
|
||||
}
|
||||
|
||||
static inline uint32_t get_overlap(const SkIRect& rect1, const SkIRect& rect2) {
|
||||
// I suspect there's a more efficient way of computing this...
|
||||
return SkMax32(0, SkMin32(rect1.fRight, rect2.fRight) - SkMax32(rect1.fLeft, rect2.fLeft)) *
|
||||
SkMax32(0, SkMin32(rect1.fBottom, rect2.fBottom) - SkMax32(rect1.fTop, rect2.fTop));
|
||||
}
|
||||
|
||||
// Get the margin (aka perimeter)
|
||||
static inline uint32_t get_margin(const SkIRect& rect) {
|
||||
return 2 * (rect.width() + rect.height());
|
||||
}
|
||||
|
||||
static inline uint32_t get_overlap_increase(const SkIRect& rect1, const SkIRect& rect2,
|
||||
SkIRect expandBy) {
|
||||
join_no_empty_check(rect1, &expandBy);
|
||||
return get_overlap(expandBy, rect2) - get_overlap(rect1, rect2);
|
||||
}
|
||||
|
||||
static inline uint32_t get_area_increase(const SkIRect& rect1, SkIRect rect2) {
|
||||
join_no_empty_check(rect1, &rect2);
|
||||
return get_area(rect2) - get_area(rect1);
|
||||
}
|
||||
|
||||
// Expand 'out' to include 'joinWith'
|
||||
static inline void join_no_empty_check(const SkIRect& joinWith, SkIRect* out) {
|
||||
// since we check for empty bounds on insert, we know we'll never have empty rects
|
||||
// and we can save the empty check that SkIRect::join requires
|
||||
if (joinWith.fLeft < out->fLeft) { out->fLeft = joinWith.fLeft; }
|
||||
if (joinWith.fTop < out->fTop) { out->fTop = joinWith.fTop; }
|
||||
if (joinWith.fRight > out->fRight) { out->fRight = joinWith.fRight; }
|
||||
if (joinWith.fBottom > out->fBottom) { out->fBottom = joinWith.fBottom; }
|
||||
}
|
||||
|
177
src/core/SkRTree.h
Normal file
177
src/core/SkRTree.h
Normal file
@ -0,0 +1,177 @@
|
||||
|
||||
/*
|
||||
* Copyright 2012 Google Inc.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license that can be
|
||||
* found in the LICENSE file.
|
||||
*/
|
||||
|
||||
#ifndef SkRTree_DEFINED
|
||||
#define SkRTree_DEFINED
|
||||
|
||||
#include "SkRect.h"
|
||||
#include "SkTDArray.h"
|
||||
#include "SkChunkAlloc.h"
|
||||
#include "SkBBoxHierarchy.h"
|
||||
|
||||
/**
|
||||
* An R-Tree implementation. In short, it is a balanced n-ary tree containing a hierarchy of
|
||||
* bounding rectangles.
|
||||
*
|
||||
* Much like a B-Tree it maintains balance by enforcing minimum and maximum child counts, and
|
||||
* splitting nodes when they become overfull. Unlike B-trees, however, we're using spatial data; so
|
||||
* there isn't a canonical ordering to use when choosing insertion locations and splitting
|
||||
* distributions. A variety of heuristics have been proposed for these problems; here, we're using
|
||||
* something resembling an R*-tree, which attempts to minimize area and overlap during insertion,
|
||||
* and aims to minimize a combination of margin, overlap, and area when splitting.
|
||||
*
|
||||
* One detail that is thus far unimplemented that may improve tree quality is attempting to remove
|
||||
* and reinsert nodes when they become full, instead of immediately splitting (nodes that may have
|
||||
* been placed well early on may hurt the tree later when more nodes have been added; removing
|
||||
* and reinserting nodes generally helps reduce overlap and make a better tree). Deletion of nodes
|
||||
* is also unimplemented.
|
||||
*
|
||||
* For more details see:
|
||||
*
|
||||
* Beckmann, N.; Kriegel, H. P.; Schneider, R.; Seeger, B. (1990). "The R*-tree:
|
||||
* an efficient and robust access method for points and rectangles"
|
||||
*
|
||||
* It also supports bulk-loading from a batch of bounds and values; if you don't require the tree
|
||||
* to be usable in its intermediate states while it is being constructed, this is significantly
|
||||
* quicker than individual insertions and produces more consistent trees.
|
||||
*/
|
||||
class SkRTree : public SkBBoxHierarchy {
|
||||
public:
|
||||
|
||||
/**
|
||||
* Create a new R-Tree with specified min/max child counts.
|
||||
* The child counts are valid iff:
|
||||
* - (max + 1) / 2 >= min (splitting an overfull node must be enough to populate 2 nodes)
|
||||
* - min < max
|
||||
* - min > 0
|
||||
* - max < SK_MaxU16
|
||||
*/
|
||||
static SkRTree* Create(int minChildren, int maxChildren);
|
||||
virtual ~SkRTree();
|
||||
|
||||
/**
|
||||
* Insert a node, consisting of bounds and a data value into the tree, if we don't immediately
|
||||
* need to use the tree; we may allow the insert to be deferred (this can allow us to bulk-load
|
||||
* a large batch of nodes at once, which tends to be faster and produce a better tree).
|
||||
* @param data The data value
|
||||
* @param bounds The corresponding bounding box
|
||||
* @param defer Can this insert be deferred? (this may be ignored)
|
||||
*/
|
||||
virtual void insert(void* data, const SkIRect& bounds, bool defer = false);
|
||||
|
||||
/**
|
||||
* If any inserts have been deferred, this will add them into the tree
|
||||
*/
|
||||
virtual void flushDeferredInserts();
|
||||
|
||||
/**
|
||||
* Given a query rectangle, populates the passed-in array with the elements it intersects
|
||||
*/
|
||||
virtual void search(const SkIRect& query, SkTDArray<void*>* results);
|
||||
|
||||
virtual void clear();
|
||||
bool isEmpty() const { return 0 == fCount; }
|
||||
int getDepth() const { return this->isEmpty() ? 0 : fRoot.fChild.subtree->fLevel + 1; }
|
||||
|
||||
/**
|
||||
* This gets the insertion count (rather than the node count)
|
||||
*/
|
||||
virtual int getCount() const { return fCount; }
|
||||
|
||||
private:
|
||||
|
||||
struct Node;
|
||||
|
||||
/**
|
||||
* A branch of the tree, this may contain a pointer to another interior node, or a data value
|
||||
*/
|
||||
struct Branch {
|
||||
union {
|
||||
Node* subtree;
|
||||
void* data;
|
||||
} fChild;
|
||||
SkIRect fBounds;
|
||||
};
|
||||
|
||||
/**
|
||||
* A node in the tree, has between fMinChildren and fMaxChildren (the root is a special case)
|
||||
*/
|
||||
struct Node {
|
||||
uint16_t fNumChildren;
|
||||
uint16_t fLevel;
|
||||
bool isLeaf() { return 0 == fLevel; }
|
||||
// Since we want to be able to pick min/max child counts at runtime, we assume the creator
|
||||
// has allocated sufficient space directly after us in memory, and index into that space
|
||||
Branch* child(size_t index) {
|
||||
return reinterpret_cast<Branch*>(this + 1) + index;
|
||||
}
|
||||
};
|
||||
|
||||
typedef int32_t SkIRect::*SortSide;
|
||||
|
||||
// Helper for sorting our children arrays by sides of their rects
|
||||
static bool RectLessThan(SortSide const& side, const Branch lhs, const Branch rhs) {
|
||||
return lhs.fBounds.*side < rhs.fBounds.*side;
|
||||
}
|
||||
|
||||
static bool RectLessX(int&, const Branch lhs, const Branch rhs) {
|
||||
return ((lhs.fBounds.fRight - lhs.fBounds.fLeft) >> 1) <
|
||||
((rhs.fBounds.fRight - lhs.fBounds.fLeft) >> 1);
|
||||
}
|
||||
|
||||
static bool RectLessY(int&, const Branch lhs, const Branch rhs) {
|
||||
return ((lhs.fBounds.fBottom - lhs.fBounds.fTop) >> 1) <
|
||||
((rhs.fBounds.fBottom - lhs.fBounds.fTop) >> 1);
|
||||
}
|
||||
|
||||
SkRTree(int minChildren, int maxChildren);
|
||||
|
||||
/**
|
||||
* Recursively descend the tree to find an insertion position for 'branch', updates
|
||||
* bounding boxes on the way up.
|
||||
*/
|
||||
Branch* insert(Node* root, Branch* branch, uint16_t level = 0);
|
||||
|
||||
int chooseSubtree(Node* root, Branch* branch);
|
||||
SkIRect computeBounds(Node* n);
|
||||
int distributeChildren(Branch* children);
|
||||
void search(Node* root, const SkIRect query, SkTDArray<void*>* results) const;
|
||||
|
||||
/**
|
||||
* This performs a bottom-up bulk load using the STR (sort-tile-recursive) algorithm, this
|
||||
* seems to generally produce better, more consistent trees at significantly lower cost than
|
||||
* repeated insertions.
|
||||
*
|
||||
* This consumes the input array.
|
||||
*
|
||||
* TODO: Experiment with other bulk-load algorithms (in particular the Hilbert pack variant,
|
||||
* which groups rects by position on the Hilbert curve, is probably worth a look). There also
|
||||
* exist top-down bulk load variants (VAMSplit, TopDownGreedy, etc).
|
||||
*/
|
||||
Branch bulkLoad(SkTDArray<Branch>* branches, int level = 0);
|
||||
|
||||
void validate();
|
||||
int validateSubtree(Node* root, SkIRect bounds, bool isRoot = false);
|
||||
|
||||
const int fMinChildren;
|
||||
const int fMaxChildren;
|
||||
const size_t fNodeSize;
|
||||
|
||||
// This is the count of data elements (rather than total nodes in the tree)
|
||||
size_t fCount;
|
||||
|
||||
Branch fRoot;
|
||||
SkChunkAlloc fNodes;
|
||||
SkTDArray<Branch> fDeferredInserts;
|
||||
|
||||
Node* allocateNode(uint16_t level);
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
|
144
tests/RTreeTest.cpp
Normal file
144
tests/RTreeTest.cpp
Normal file
@ -0,0 +1,144 @@
|
||||
|
||||
/*
|
||||
* Copyright 2012 Google Inc.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license that can be
|
||||
* found in the LICENSE file.
|
||||
*/
|
||||
|
||||
#include "Test.h"
|
||||
#include "SkRandom.h"
|
||||
#include "SkRTree.h"
|
||||
#include "SkTSort.h"
|
||||
|
||||
static const size_t MIN_CHILDREN = 6;
|
||||
static const size_t MAX_CHILDREN = 11;
|
||||
|
||||
static const size_t NUM_RECTS = 200;
|
||||
static const size_t NUM_ITERATIONS = 100;
|
||||
static const size_t NUM_QUERIES = 50;
|
||||
|
||||
struct DataRect {
|
||||
SkIRect rect;
|
||||
void* data;
|
||||
};
|
||||
|
||||
static SkIRect random_rect(SkRandom& rand) {
|
||||
SkIRect rect = {0,0,0,0};
|
||||
while (rect.isEmpty()) {
|
||||
rect.fLeft = rand.nextS() % 1000;
|
||||
rect.fRight = rand.nextS() % 1000;
|
||||
rect.fTop = rand.nextS() % 1000;
|
||||
rect.fBottom = rand.nextS() % 1000;
|
||||
rect.sort();
|
||||
}
|
||||
return rect;
|
||||
}
|
||||
|
||||
static void random_data_rects(SkRandom& rand, DataRect out[], int n) {
|
||||
for (int i = 0; i < n; ++i) {
|
||||
out[i].rect = random_rect(rand);
|
||||
out[i].data = reinterpret_cast<void*>(i);
|
||||
}
|
||||
}
|
||||
|
||||
static bool verify_query(SkIRect query, DataRect rects[],
|
||||
SkTDArray<void*>& found) {
|
||||
SkTDArray<void*> expected;
|
||||
// manually intersect with every rectangle
|
||||
for (int i = 0; i < NUM_RECTS; ++i) {
|
||||
if (SkIRect::IntersectsNoEmptyCheck(query, rects[i].rect)) {
|
||||
expected.push(rects[i].data);
|
||||
}
|
||||
}
|
||||
|
||||
if (expected.count() != found.count()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (0 == expected.count()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Just cast to long since sorting by the value of the void*'s was being problematic...
|
||||
SkTQSort(reinterpret_cast<long*>(expected.begin()),
|
||||
reinterpret_cast<long*>(expected.end() - 1));
|
||||
SkTQSort(reinterpret_cast<long*>(found.begin()),
|
||||
reinterpret_cast<long*>(found.end() - 1));
|
||||
return found == expected;
|
||||
}
|
||||
|
||||
static void runQueries(skiatest::Reporter* reporter, SkRandom& rand, DataRect rects[],
|
||||
SkRTree& tree) {
|
||||
for (int i = 0; i < NUM_QUERIES; ++i) {
|
||||
SkTDArray<void*> hits;
|
||||
SkIRect query = random_rect(rand);
|
||||
tree.search(query, &hits);
|
||||
REPORTER_ASSERT(reporter, verify_query(query, rects, hits));
|
||||
}
|
||||
}
|
||||
|
||||
static void TestRTree(skiatest::Reporter* reporter) {
|
||||
DataRect rects[NUM_RECTS];
|
||||
SkRandom rand;
|
||||
SkRTree* rtree = SkRTree::Create(MIN_CHILDREN, MAX_CHILDREN);
|
||||
REPORTER_ASSERT(reporter, NULL != rtree);
|
||||
|
||||
int expectedDepthMin = -1;
|
||||
int expectedDepthMax = -1;
|
||||
|
||||
int tmp = NUM_RECTS;
|
||||
while (tmp > 0) {
|
||||
tmp -= static_cast<int>(pow(static_cast<double>(MAX_CHILDREN),
|
||||
static_cast<double>(expectedDepthMin + 1)));
|
||||
++expectedDepthMin;
|
||||
}
|
||||
|
||||
tmp = NUM_RECTS;
|
||||
while (tmp > 0) {
|
||||
tmp -= static_cast<int>(pow(static_cast<double>(MIN_CHILDREN),
|
||||
static_cast<double>(expectedDepthMax + 1)));
|
||||
++expectedDepthMax;
|
||||
}
|
||||
|
||||
for (int i = 0; i < NUM_ITERATIONS; ++i) {
|
||||
random_data_rects(rand, rects, NUM_RECTS);
|
||||
|
||||
// First try bulk-loaded inserts
|
||||
for (int i = 0; i < NUM_RECTS; ++i) {
|
||||
rtree->insert(rects[i].data, rects[i].rect, true);
|
||||
}
|
||||
rtree->flushDeferredInserts();
|
||||
runQueries(reporter, rand, rects, *rtree);
|
||||
REPORTER_ASSERT(reporter, NUM_RECTS == rtree->getCount());
|
||||
REPORTER_ASSERT(reporter, expectedDepthMin <= rtree->getDepth() &&
|
||||
expectedDepthMax >= rtree->getDepth());
|
||||
rtree->clear();
|
||||
REPORTER_ASSERT(reporter, 0 == rtree->getCount());
|
||||
|
||||
// Then try immediate inserts
|
||||
for (int i = 0; i < NUM_RECTS; ++i) {
|
||||
rtree->insert(rects[i].data, rects[i].rect);
|
||||
}
|
||||
runQueries(reporter, rand, rects, *rtree);
|
||||
REPORTER_ASSERT(reporter, NUM_RECTS == rtree->getCount());
|
||||
REPORTER_ASSERT(reporter, expectedDepthMin <= rtree->getDepth() &&
|
||||
expectedDepthMax >= rtree->getDepth());
|
||||
rtree->clear();
|
||||
REPORTER_ASSERT(reporter, 0 == rtree->getCount());
|
||||
|
||||
// And for good measure try immediate inserts, but in reversed order
|
||||
for (int i = NUM_RECTS - 1; i >= 0; --i) {
|
||||
rtree->insert(rects[i].data, rects[i].rect);
|
||||
}
|
||||
runQueries(reporter, rand, rects, *rtree);
|
||||
REPORTER_ASSERT(reporter, NUM_RECTS == rtree->getCount());
|
||||
REPORTER_ASSERT(reporter, expectedDepthMin <= rtree->getDepth() &&
|
||||
expectedDepthMax >= rtree->getDepth());
|
||||
rtree->clear();
|
||||
REPORTER_ASSERT(reporter, 0 == rtree->getCount());
|
||||
}
|
||||
}
|
||||
|
||||
#include "TestClassDef.h"
|
||||
DEFINE_TESTCLASS("RTree", RTreeTestClass, TestRTree)
|
Loading…
Reference in New Issue
Block a user