1 /* Copyright (c) 2013 Scott Lembcke and Howling Moon Software
3 * Permission is hereby granted, free of charge, to any person obtaining a copy
4 * of this software and associated documentation files (the "Software"), to deal
5 * in the Software without restriction, including without limitation the rights
6 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 * copies of the Software, and to permit persons to whom the Software is
8 * furnished to do so, subject to the following conditions:
10 * The above copyright notice and this permission notice shall be included in
11 * all copies or substantial portions of the Software.
13 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25 #include "chipmunk/chipmunk_private.h"
27 static inline cpSpatialIndexClass *Klass();
29 typedef struct Node Node;
30 typedef struct Pair Pair;
33 cpSpatialIndex spatialIndex;
34 cpBBTreeVelocityFunc velocityFunc;
41 cpArray *allocatedBuffers;
53 struct { Node *a, *b; } children;
63 // Can't use anonymous unions and still get good x-compiler compatability
64 #define A node.children.a
65 #define B node.children.b
66 #define STAMP node.leaf.stamp
67 #define PAIRS node.leaf.pairs
69 typedef struct Thread {
80 //MARK: Misc Functions
83 GetBB(cpBBTree *tree, void *obj)
85 cpBB bb = tree->spatialIndex.bbfunc(obj);
87 cpBBTreeVelocityFunc velocityFunc = tree->velocityFunc;
90 cpFloat x = (bb.r - bb.l)*coef;
91 cpFloat y = (bb.t - bb.b)*coef;
93 cpVect v = cpvmult(velocityFunc(obj), 0.1f);
94 return cpBBNew(bb.l + cpfmin(-x, v.x), bb.b + cpfmin(-y, v.y), bb.r + cpfmax(x, v.x), bb.t + cpfmax(y, v.y));
100 static inline cpBBTree *
101 GetTree(cpSpatialIndex *index)
103 return (index && index->klass == Klass() ? (cpBBTree *)index : NULL);
107 GetRootIfTree(cpSpatialIndex *index){
108 return (index && index->klass == Klass() ? ((cpBBTree *)index)->root : NULL);
111 static inline cpBBTree *
112 GetMasterTree(cpBBTree *tree)
114 cpBBTree *dynamicTree = GetTree(tree->spatialIndex.dynamicIndex);
115 return (dynamicTree ? dynamicTree : tree);
119 IncrementStamp(cpBBTree *tree)
121 cpBBTree *dynamicTree = GetTree(tree->spatialIndex.dynamicIndex);
123 dynamicTree->stamp++;
129 //MARK: Pair/Thread Functions
132 PairRecycle(cpBBTree *tree, Pair *pair)
134 // Share the pool of the master tree.
135 // TODO: would be lovely to move the pairs stuff into an external data structure.
136 tree = GetMasterTree(tree);
138 pair->a.next = tree->pooledPairs;
139 tree->pooledPairs = pair;
143 PairFromPool(cpBBTree *tree)
145 // Share the pool of the master tree.
146 // TODO: would be lovely to move the pairs stuff into an external data structure.
147 tree = GetMasterTree(tree);
149 Pair *pair = tree->pooledPairs;
152 tree->pooledPairs = pair->a.next;
155 // Pool is exhausted, make more
156 int count = CP_BUFFER_BYTES/sizeof(Pair);
157 cpAssertHard(count, "Internal Error: Buffer size is too small.");
159 Pair *buffer = (Pair *)cpcalloc(1, CP_BUFFER_BYTES);
160 cpArrayPush(tree->allocatedBuffers, buffer);
162 // push all but the first one, return the first instead
163 for(int i=1; i<count; i++) PairRecycle(tree, buffer + i);
169 ThreadUnlink(Thread thread)
171 Pair *next = thread.next;
172 Pair *prev = thread.prev;
175 if(next->a.leaf == thread.leaf) next->a.prev = prev; else next->b.prev = prev;
179 if(prev->a.leaf == thread.leaf) prev->a.next = next; else prev->b.next = next;
181 thread.leaf->PAIRS = next;
186 PairsClear(Node *leaf, cpBBTree *tree)
188 Pair *pair = leaf->PAIRS;
192 if(pair->a.leaf == leaf){
193 Pair *next = pair->a.next;
194 ThreadUnlink(pair->b);
195 PairRecycle(tree, pair);
198 Pair *next = pair->b.next;
199 ThreadUnlink(pair->a);
200 PairRecycle(tree, pair);
207 PairInsert(Node *a, Node *b, cpBBTree *tree)
209 Pair *nextA = a->PAIRS, *nextB = b->PAIRS;
210 Pair *pair = PairFromPool(tree);
211 Pair temp = {{NULL, a, nextA},{NULL, b, nextB}, 0};
213 a->PAIRS = b->PAIRS = pair;
217 if(nextA->a.leaf == a) nextA->a.prev = pair; else nextA->b.prev = pair;
221 if(nextB->a.leaf == b) nextB->a.prev = pair; else nextB->b.prev = pair;
226 //MARK: Node Functions
229 NodeRecycle(cpBBTree *tree, Node *node)
231 node->parent = tree->pooledNodes;
232 tree->pooledNodes = node;
236 NodeFromPool(cpBBTree *tree)
238 Node *node = tree->pooledNodes;
241 tree->pooledNodes = node->parent;
244 // Pool is exhausted, make more
245 int count = CP_BUFFER_BYTES/sizeof(Node);
246 cpAssertHard(count, "Internal Error: Buffer size is too small.");
248 Node *buffer = (Node *)cpcalloc(1, CP_BUFFER_BYTES);
249 cpArrayPush(tree->allocatedBuffers, buffer);
251 // push all but the first one, return the first instead
252 for(int i=1; i<count; i++) NodeRecycle(tree, buffer + i);
258 NodeSetA(Node *node, Node *value)
261 value->parent = node;
265 NodeSetB(Node *node, Node *value)
268 value->parent = node;
272 NodeNew(cpBBTree *tree, Node *a, Node *b)
274 Node *node = NodeFromPool(tree);
277 node->bb = cpBBMerge(a->bb, b->bb);
287 NodeIsLeaf(Node *node)
289 return (node->obj != NULL);
293 NodeOther(Node *node, Node *child)
295 return (node->A == child ? node->B : node->A);
299 NodeReplaceChild(Node *parent, Node *child, Node *value, cpBBTree *tree)
301 cpAssertSoft(!NodeIsLeaf(parent), "Internal Error: Cannot replace child of a leaf.");
302 cpAssertSoft(child == parent->A || child == parent->B, "Internal Error: Node is not a child of parent.");
304 if(parent->A == child){
305 NodeRecycle(tree, parent->A);
306 NodeSetA(parent, value);
308 NodeRecycle(tree, parent->B);
309 NodeSetB(parent, value);
312 for(Node *node=parent; node; node = node->parent){
313 node->bb = cpBBMerge(node->A->bb, node->B->bb);
317 //MARK: Subtree Functions
319 static inline cpFloat
320 cpBBProximity(cpBB a, cpBB b)
322 return cpfabs(a.l + a.r - b.l - b.r) + cpfabs(a.b + a.t - b.b - b.t);
326 SubtreeInsert(Node *subtree, Node *leaf, cpBBTree *tree)
330 } else if(NodeIsLeaf(subtree)){
331 return NodeNew(tree, leaf, subtree);
333 cpFloat cost_a = cpBBArea(subtree->B->bb) + cpBBMergedArea(subtree->A->bb, leaf->bb);
334 cpFloat cost_b = cpBBArea(subtree->A->bb) + cpBBMergedArea(subtree->B->bb, leaf->bb);
336 if(cost_a == cost_b){
337 cost_a = cpBBProximity(subtree->A->bb, leaf->bb);
338 cost_b = cpBBProximity(subtree->B->bb, leaf->bb);
342 NodeSetB(subtree, SubtreeInsert(subtree->B, leaf, tree));
344 NodeSetA(subtree, SubtreeInsert(subtree->A, leaf, tree));
347 subtree->bb = cpBBMerge(subtree->bb, leaf->bb);
353 SubtreeQuery(Node *subtree, void *obj, cpBB bb, cpSpatialIndexQueryFunc func, void *data)
355 if(cpBBIntersects(subtree->bb, bb)){
356 if(NodeIsLeaf(subtree)){
357 func(obj, subtree->obj, 0, data);
359 SubtreeQuery(subtree->A, obj, bb, func, data);
360 SubtreeQuery(subtree->B, obj, bb, func, data);
367 SubtreeSegmentQuery(Node *subtree, void *obj, cpVect a, cpVect b, cpFloat t_exit, cpSpatialIndexSegmentQueryFunc func, void *data)
369 if(NodeIsLeaf(subtree)){
370 return func(obj, subtree->obj, data);
372 cpFloat t_a = cpBBSegmentQuery(subtree->A->bb, a, b);
373 cpFloat t_b = cpBBSegmentQuery(subtree->B->bb, a, b);
376 if(t_a < t_exit) t_exit = cpfmin(t_exit, SubtreeSegmentQuery(subtree->A, obj, a, b, t_exit, func, data));
377 if(t_b < t_exit) t_exit = cpfmin(t_exit, SubtreeSegmentQuery(subtree->B, obj, a, b, t_exit, func, data));
379 if(t_b < t_exit) t_exit = cpfmin(t_exit, SubtreeSegmentQuery(subtree->B, obj, a, b, t_exit, func, data));
380 if(t_a < t_exit) t_exit = cpfmin(t_exit, SubtreeSegmentQuery(subtree->A, obj, a, b, t_exit, func, data));
388 SubtreeRecycle(cpBBTree *tree, Node *node)
390 if(!NodeIsLeaf(node)){
391 SubtreeRecycle(tree, node->A);
392 SubtreeRecycle(tree, node->B);
393 NodeRecycle(tree, node);
398 SubtreeRemove(Node *subtree, Node *leaf, cpBBTree *tree)
403 Node *parent = leaf->parent;
404 if(parent == subtree){
405 Node *other = NodeOther(subtree, leaf);
406 other->parent = subtree->parent;
407 NodeRecycle(tree, subtree);
410 NodeReplaceChild(parent->parent, parent, NodeOther(parent, leaf), tree);
416 //MARK: Marking Functions
418 typedef struct MarkContext {
421 cpSpatialIndexQueryFunc func;
426 MarkLeafQuery(Node *subtree, Node *leaf, cpBool left, MarkContext *context)
428 if(cpBBIntersects(leaf->bb, subtree->bb)){
429 if(NodeIsLeaf(subtree)){
431 PairInsert(leaf, subtree, context->tree);
433 if(subtree->STAMP < leaf->STAMP) PairInsert(subtree, leaf, context->tree);
434 context->func(leaf->obj, subtree->obj, 0, context->data);
437 MarkLeafQuery(subtree->A, leaf, left, context);
438 MarkLeafQuery(subtree->B, leaf, left, context);
444 MarkLeaf(Node *leaf, MarkContext *context)
446 cpBBTree *tree = context->tree;
447 if(leaf->STAMP == GetMasterTree(tree)->stamp){
448 Node *staticRoot = context->staticRoot;
449 if(staticRoot) MarkLeafQuery(staticRoot, leaf, cpFalse, context);
451 for(Node *node = leaf; node->parent; node = node->parent){
452 if(node == node->parent->A){
453 MarkLeafQuery(node->parent->B, leaf, cpTrue, context);
455 MarkLeafQuery(node->parent->A, leaf, cpFalse, context);
459 Pair *pair = leaf->PAIRS;
461 if(leaf == pair->b.leaf){
462 pair->id = context->func(pair->a.leaf->obj, leaf->obj, pair->id, context->data);
472 MarkSubtree(Node *subtree, MarkContext *context)
474 if(NodeIsLeaf(subtree)){
475 MarkLeaf(subtree, context);
477 MarkSubtree(subtree->A, context);
478 MarkSubtree(subtree->B, context); // TODO: Force TCO here?
482 //MARK: Leaf Functions
485 LeafNew(cpBBTree *tree, void *obj, cpBB bb)
487 Node *node = NodeFromPool(tree);
489 node->bb = GetBB(tree, obj);
499 LeafUpdate(Node *leaf, cpBBTree *tree)
501 Node *root = tree->root;
502 cpBB bb = tree->spatialIndex.bbfunc(leaf->obj);
504 if(!cpBBContainsBB(leaf->bb, bb)){
505 leaf->bb = GetBB(tree, leaf->obj);
507 root = SubtreeRemove(root, leaf, tree);
508 tree->root = SubtreeInsert(root, leaf, tree);
510 PairsClear(leaf, tree);
511 leaf->STAMP = GetMasterTree(tree)->stamp;
519 static cpCollisionID VoidQueryFunc(void *obj1, void *obj2, cpCollisionID id, void *data){return id;}
522 LeafAddPairs(Node *leaf, cpBBTree *tree)
524 cpSpatialIndex *dynamicIndex = tree->spatialIndex.dynamicIndex;
526 Node *dynamicRoot = GetRootIfTree(dynamicIndex);
528 cpBBTree *dynamicTree = GetTree(dynamicIndex);
529 MarkContext context = {dynamicTree, NULL, NULL, NULL};
530 MarkLeafQuery(dynamicRoot, leaf, cpTrue, &context);
533 Node *staticRoot = GetRootIfTree(tree->spatialIndex.staticIndex);
534 MarkContext context = {tree, staticRoot, VoidQueryFunc, NULL};
535 MarkLeaf(leaf, &context);
539 //MARK: Memory Management Functions
544 return (cpBBTree *)cpcalloc(1, sizeof(cpBBTree));
548 leafSetEql(void *obj, Node *node)
550 return (obj == node->obj);
554 leafSetTrans(void *obj, cpBBTree *tree)
556 return LeafNew(tree, obj, tree->spatialIndex.bbfunc(obj));
560 cpBBTreeInit(cpBBTree *tree, cpSpatialIndexBBFunc bbfunc, cpSpatialIndex *staticIndex)
562 cpSpatialIndexInit((cpSpatialIndex *)tree, Klass(), bbfunc, staticIndex);
564 tree->velocityFunc = NULL;
566 tree->leaves = cpHashSetNew(0, (cpHashSetEqlFunc)leafSetEql);
569 tree->pooledNodes = NULL;
570 tree->allocatedBuffers = cpArrayNew(0);
574 return (cpSpatialIndex *)tree;
578 cpBBTreeSetVelocityFunc(cpSpatialIndex *index, cpBBTreeVelocityFunc func)
580 if(index->klass != Klass()){
581 cpAssertWarn(cpFalse, "Ignoring cpBBTreeSetVelocityFunc() call to non-tree spatial index.");
585 ((cpBBTree *)index)->velocityFunc = func;
589 cpBBTreeNew(cpSpatialIndexBBFunc bbfunc, cpSpatialIndex *staticIndex)
591 return cpBBTreeInit(cpBBTreeAlloc(), bbfunc, staticIndex);
595 cpBBTreeDestroy(cpBBTree *tree)
597 cpHashSetFree(tree->leaves);
599 if(tree->allocatedBuffers) cpArrayFreeEach(tree->allocatedBuffers, cpfree);
600 cpArrayFree(tree->allocatedBuffers);
603 //MARK: Insert/Remove
606 cpBBTreeInsert(cpBBTree *tree, void *obj, cpHashValue hashid)
608 Node *leaf = (Node *)cpHashSetInsert(tree->leaves, hashid, obj, (cpHashSetTransFunc)leafSetTrans, tree);
610 Node *root = tree->root;
611 tree->root = SubtreeInsert(root, leaf, tree);
613 leaf->STAMP = GetMasterTree(tree)->stamp;
614 LeafAddPairs(leaf, tree);
615 IncrementStamp(tree);
619 cpBBTreeRemove(cpBBTree *tree, void *obj, cpHashValue hashid)
621 Node *leaf = (Node *)cpHashSetRemove(tree->leaves, hashid, obj);
623 tree->root = SubtreeRemove(tree->root, leaf, tree);
624 PairsClear(leaf, tree);
625 NodeRecycle(tree, leaf);
629 cpBBTreeContains(cpBBTree *tree, void *obj, cpHashValue hashid)
631 return (cpHashSetFind(tree->leaves, hashid, obj) != NULL);
636 static void LeafUpdateWrap(Node *leaf, cpBBTree *tree) {LeafUpdate(leaf, tree);}
639 cpBBTreeReindexQuery(cpBBTree *tree, cpSpatialIndexQueryFunc func, void *data)
641 if(!tree->root) return;
643 // LeafUpdate() may modify tree->root. Don't cache it.
644 cpHashSetEach(tree->leaves, (cpHashSetIteratorFunc)LeafUpdateWrap, tree);
646 cpSpatialIndex *staticIndex = tree->spatialIndex.staticIndex;
647 Node *staticRoot = (staticIndex && staticIndex->klass == Klass() ? ((cpBBTree *)staticIndex)->root : NULL);
649 MarkContext context = {tree, staticRoot, func, data};
650 MarkSubtree(tree->root, &context);
651 if(staticIndex && !staticRoot) cpSpatialIndexCollideStatic((cpSpatialIndex *)tree, staticIndex, func, data);
653 IncrementStamp(tree);
657 cpBBTreeReindex(cpBBTree *tree)
659 cpBBTreeReindexQuery(tree, VoidQueryFunc, NULL);
663 cpBBTreeReindexObject(cpBBTree *tree, void *obj, cpHashValue hashid)
665 Node *leaf = (Node *)cpHashSetFind(tree->leaves, hashid, obj);
667 if(LeafUpdate(leaf, tree)) LeafAddPairs(leaf, tree);
668 IncrementStamp(tree);
675 cpBBTreeSegmentQuery(cpBBTree *tree, void *obj, cpVect a, cpVect b, cpFloat t_exit, cpSpatialIndexSegmentQueryFunc func, void *data)
677 Node *root = tree->root;
678 if(root) SubtreeSegmentQuery(root, obj, a, b, t_exit, func, data);
682 cpBBTreeQuery(cpBBTree *tree, void *obj, cpBB bb, cpSpatialIndexQueryFunc func, void *data)
684 if(tree->root) SubtreeQuery(tree->root, obj, bb, func, data);
690 cpBBTreeCount(cpBBTree *tree)
692 return cpHashSetCount(tree->leaves);
695 typedef struct eachContext {
696 cpSpatialIndexIteratorFunc func;
700 static void each_helper(Node *node, eachContext *context){context->func(node->obj, context->data);}
703 cpBBTreeEach(cpBBTree *tree, cpSpatialIndexIteratorFunc func, void *data)
705 eachContext context = {func, data};
706 cpHashSetEach(tree->leaves, (cpHashSetIteratorFunc)each_helper, &context);
709 static cpSpatialIndexClass klass = {
710 (cpSpatialIndexDestroyImpl)cpBBTreeDestroy,
712 (cpSpatialIndexCountImpl)cpBBTreeCount,
713 (cpSpatialIndexEachImpl)cpBBTreeEach,
715 (cpSpatialIndexContainsImpl)cpBBTreeContains,
716 (cpSpatialIndexInsertImpl)cpBBTreeInsert,
717 (cpSpatialIndexRemoveImpl)cpBBTreeRemove,
719 (cpSpatialIndexReindexImpl)cpBBTreeReindex,
720 (cpSpatialIndexReindexObjectImpl)cpBBTreeReindexObject,
721 (cpSpatialIndexReindexQueryImpl)cpBBTreeReindexQuery,
723 (cpSpatialIndexQueryImpl)cpBBTreeQuery,
724 (cpSpatialIndexSegmentQueryImpl)cpBBTreeSegmentQuery,
727 static inline cpSpatialIndexClass *Klass(){return &klass;}
730 //MARK: Tree Optimization
733 cpfcompare(const cpFloat *a, const cpFloat *b){
734 return (*a < *b ? -1 : (*b < *a ? 1 : 0));
738 fillNodeArray(Node *node, Node ***cursor){
744 partitionNodes(cpBBTree *tree, Node **nodes, int count)
748 } else if(count == 2) {
749 return NodeNew(tree, nodes[0], nodes[1]);
752 // Find the AABB for these nodes
753 cpBB bb = nodes[0]->bb;
754 for(int i=1; i<count; i++) bb = cpBBMerge(bb, nodes[i]->bb);
756 // Split it on it's longest axis
757 cpBool splitWidth = (bb.r - bb.l > bb.t - bb.b);
759 // Sort the bounds and use the median as the splitting point
760 cpFloat *bounds = (cpFloat *)cpcalloc(count*2, sizeof(cpFloat));
762 for(int i=0; i<count; i++){
763 bounds[2*i + 0] = nodes[i]->bb.l;
764 bounds[2*i + 1] = nodes[i]->bb.r;
767 for(int i=0; i<count; i++){
768 bounds[2*i + 0] = nodes[i]->bb.b;
769 bounds[2*i + 1] = nodes[i]->bb.t;
773 qsort(bounds, count*2, sizeof(cpFloat), (int (*)(const void *, const void *))cpfcompare);
774 cpFloat split = (bounds[count - 1] + bounds[count])*0.5f; // use the medain as the split
777 // Generate the child BBs
779 if(splitWidth) a.r = b.l = split; else a.t = b.b = split;
781 // Partition the nodes
783 for(int left=0; left < right;){
784 Node *node = nodes[left];
785 if(cpBBMergedArea(node->bb, b) < cpBBMergedArea(node->bb, a)){
786 // if(cpBBProximity(node->bb, b) < cpBBProximity(node->bb, a)){
788 nodes[left] = nodes[right];
797 for(int i=0; i<count; i++) node = SubtreeInsert(node, nodes[i], tree);
801 // Recurse and build the node!
803 partitionNodes(tree, nodes, right),
804 partitionNodes(tree, nodes + right, count - right)
809 //cpBBTreeOptimizeIncremental(cpBBTree *tree, int passes)
811 // for(int i=0; i<passes; i++){
812 // Node *root = tree->root;
813 // Node *node = root;
815 // unsigned int path = tree->opath;
817 // while(!NodeIsLeaf(node)){
818 // node = (path&(1<<bit) ? node->a : node->b);
819 // bit = (bit + 1)&(sizeof(unsigned int)*8 - 1);
822 // root = subtreeRemove(root, node, tree);
823 // tree->root = subtreeInsert(root, node, tree);
828 cpBBTreeOptimize(cpSpatialIndex *index)
830 if(index->klass != &klass){
831 cpAssertWarn(cpFalse, "Ignoring cpBBTreeOptimize() call to non-tree spatial index.");
835 cpBBTree *tree = (cpBBTree *)index;
836 Node *root = tree->root;
839 int count = cpBBTreeCount(tree);
840 Node **nodes = (Node **)cpcalloc(count, sizeof(Node *));
841 Node **cursor = nodes;
843 cpHashSetEach(tree->leaves, (cpHashSetIteratorFunc)fillNodeArray, &cursor);
845 SubtreeRecycle(tree, root);
846 tree->root = partitionNodes(tree, nodes, count);
852 //#define CP_BBTREE_DEBUG_DRAW
853 #ifdef CP_BBTREE_DEBUG_DRAW
854 #include "OpenGL/gl.h"
855 #include "OpenGL/glu.h"
856 #include <GLUT/glut.h>
859 NodeRender(Node *node, int depth)
861 if(!NodeIsLeaf(node) && depth <= 10){
862 NodeRender(node->a, depth + 1);
863 NodeRender(node->b, depth + 1);
868 // GLfloat v = depth/2.0f;
869 // glColor3f(1.0f - v, v, 0.0f);
870 glLineWidth(cpfmax(5.0f - depth, 1.0f));
872 glVertex2f(bb.l, bb.b);
873 glVertex2f(bb.l, bb.t);
875 glVertex2f(bb.l, bb.t);
876 glVertex2f(bb.r, bb.t);
878 glVertex2f(bb.r, bb.t);
879 glVertex2f(bb.r, bb.b);
881 glVertex2f(bb.r, bb.b);
882 glVertex2f(bb.l, bb.b);
887 cpBBTreeRenderDebug(cpSpatialIndex *index){
888 if(index->klass != &klass){
889 cpAssertWarn(cpFalse, "Ignoring cpBBTreeRenderDebug() call to non-tree spatial index.");
893 cpBBTree *tree = (cpBBTree *)index;
894 if(tree->root) NodeRender(tree->root, 0);