]> git.itanic.dy.fi Git - sdl-planets/commitdiff
quadtree: Implement proper rebalancing
authorTimo Kokkonen <kaapeli@itanic.dy.fi>
Tue, 7 Dec 2010 20:13:46 +0000 (22:13 +0200)
committerTimo Kokkonen <kaapeli@itanic.dy.fi>
Tue, 7 Dec 2010 20:13:46 +0000 (22:13 +0200)
Select nodes in predetermined spatial order that will produce well
balanced tree.

Signed-off-by: Timo Kokkonen <kaapeli@itanic.dy.fi>
quadtree.c

index da3b43d9b81f925b26c4a4608371f8e0e775ad51..5e57afd245cb8acddfe984226fa4ae452adf854f 100644 (file)
@@ -166,6 +166,27 @@ void _quadtree_validate_tree(const struct quadtree *node)
        validate_tree(node);
 }
 
+static struct quadtree *_quadtree_add(struct quadtree *parent,
+                               struct quadtree *new)
+{
+       int ret, i;
+
+       ret = quadtree_compare(parent, new);
+
+       if (ret < 0 || ret >= 4) {
+               printf("Invalid comparison result of %d\n", ret);
+               return 0;
+       }
+
+       if (parent->child[ret])
+               return _quadtree_add(parent->child[ret], new);
+
+       parent->child[ret] = new;
+       new->parent = parent;
+       for (i = 0; i < 4; i++)
+               new->child[i] = 0;
+
+       return new;
 }
 
 /**
@@ -183,26 +204,12 @@ void _quadtree_validate_tree(const struct quadtree *node)
 struct quadtree *quadtree_add(struct quadtree *parent, struct quadtree *new,
                              struct quadtree_ops *ops)
 {
-       int ret, i;
        if (parent == new)
                trap();
 
        validate_tree(parent);
 
-       ret = quadtree_compare(parent, new);
-
-       if (ret < 0 || ret >= 4) {
-               printf("Invalid comparison result of %d\n", ret);
-               return 0;
-       }
-
-       if (parent->child[ret])
-               return quadtree_add(parent->child[ret], new, ops);
-
-       parent->child[ret] = new;
-       new->parent = parent;
-       for (i = 0; i < 4; i++)
-               new->child[i] = 0;
+       _quadtree_add(parent, new);
 
        if (debug) {
                printf("adding node %p to parent %p\n", new, parent);
@@ -216,73 +223,424 @@ struct quadtree *quadtree_add(struct quadtree *parent, struct quadtree *new,
        return new;
 }
 
+static double max_by_dir(double a, double b, int direction)
+{
+       switch (direction) {
+       case 0: /* up */
+       case 3: /* left */
+               return MIN(a, b);
+       case 1: /* right */
+       case 2: /* down */
+               return MAX(a, b);
+       }
+
+       trap();
+       return 0;
+}
+
+static double maxv_by_dir(struct quadtree *a, int direction)
+{
+       switch (direction) {
+       case 0: /* up */
+       case 2: /* down */
+               return a->pos.y;
+       case 1: /* right */
+       case 3: /* left */
+               return a->pos.x;
+       }
+
+       trap();
+       return 0;
+}
+
+static double quadtree_get_max_dimension(struct quadtree *node, int direction)
+{
+       struct quadtree *ch1 = NULL, *ch2 = NULL;
+       double a, b;
+
+       switch (direction) {
+       case 0: /* up */
+               ch1 = node->child[0];
+               ch2 = node->child[1];
+               break;
+       case 1: /* right */
+               ch1 = node->child[1];
+               ch2 = node->child[3];
+               break;
+       case 2: /* down */
+               ch1 = node->child[2];
+               ch2 = node->child[3];
+               break;
+       case 3: /* left */
+               ch1 = node->child[0];
+               ch2 = node->child[2];
+               break;
+       default:
+               trap();
+       }
+
+       if (ch1 && ch2) {
+               a = quadtree_get_max_dimension(ch1, direction);
+               b = quadtree_get_max_dimension(ch2, direction);
+               return max_by_dir(a, b, direction);
+       }
+
+       if (ch1) {
+               a = quadtree_get_max_dimension(ch1, direction);
+               b = maxv_by_dir(ch1, direction);
+               return max_by_dir(a, b, direction);
+       }
+       if (ch2) {
+               a = quadtree_get_max_dimension(ch2, direction);
+               b = maxv_by_dir(ch2, direction);
+               return max_by_dir(a, b, direction);
+       }
+
+       return maxv_by_dir(node, direction);
+}
+
 /*
- * _qt_optimal_del - Delete nodes from a detached subtree in optimal order
- *
- * All nodes are moved under the new parent node
+ * Stores the lower right and upper left corner coordinates to the
+ * corner vectors
  */
-static void _qt_optimal_del(struct quadtree *parent, struct quadtree *node,
-                      struct quadtree_ops *ops, int parents)
+static void quadtree_get_tree_dimensions(struct quadtree *node,
+                                       struct vector *corner)
 {
-       struct quadtree *child[4] = {0};
-       int i, children = 0, max_c, max_i;
+       corner[0].y = quadtree_get_max_dimension(node, 2);
+       corner[0].x = quadtree_get_max_dimension(node, 1);
+       corner[1].y = quadtree_get_max_dimension(node, 0);
+       corner[1].x = quadtree_get_max_dimension(node, 3);
+
+       if (debug) {
+               printf("\nInitial Search rectangle (%f %f) (%f %f), (%f %f)\n",
+                       corner[0].x, corner[0].y,
+                       corner[1].x, corner[1].y,
+                       node->pos.x, node->pos.y);
+               fflush(stdout);
+       }
+
+       if ((corner[0].x < corner[1].x) ||
+           (corner[0].y < corner[1].y))
+               trap();
+}
+
+static int is_within(struct vector *pos, struct vector *corner)
+{
+       if ((pos->x >= corner[1].x) && (pos->x <= corner[0].x) &&
+           (pos->y >= corner[1].y) && (pos->y <= corner[0].y))
+               return 1;
+       return 0;
+}
+
+static int quadrants_to_search(struct quadtree *node, struct vector *corner)
+{
+       int direction = 0, i;
+       int up = 0, left = 0, right = 0, down = 0;
+
+       for (i = 0; i < 2; i++) {
+               if (corner[i].x <= node->pos.x)
+                       left = 1;
+               else if (corner[i].x >= node->pos.x)
+                       right = 1;
+               if (corner[i].y <= node->pos.y)
+                       up = 1;
+               else if (corner[i].y >= node->pos.y)
+                       down = 1;
+       }
+
+       if (left || up)
+               direction |= QUADTREE_UPLEFT;
+       if (right || up)
+               direction |= QUADTREE_UPRIGHT;
+       if (left || down)
+               direction |= QUADTREE_DOWNLEFT;
+       if (right || down)
+               direction |= QUADTREE_DOWNRIGHT;
+
+       return direction;
+}
+
+static struct quadtree *quadtree_find_nearest(struct quadtree *tree,
+                                       struct vector *pos,
+                                       struct vector *corner)
+{
+       struct vector tmp;
+       struct quadtree *nearest, *node;
+       double distance, dist;
+       int ret, i, directions;
+
+       if (!is_within(pos, corner)) {
+               if (debug) {
+                       printf("No nearest to be found from (%f %f) (%f %f) "
+                               "pos (%f %f)\n",
+                               corner[0].x, corner[0].y,
+                               corner[1].x, corner[1].y,
+                               pos->x, pos->y);
+                       fflush(stdout);
+               }
+               return NULL;
+       }
+
+       if (is_within(&tree->pos, corner)) {
+               vector_sub(pos, &tree->pos, &tmp);
+               distance = vector_abs(&tmp);
+               nearest = tree;
+       } else {
+               nearest = NULL;
+       }
+
+       directions = quadrants_to_search(tree, corner);
 
        for (i = 0; i < 4; i++) {
-               if (!node->child[i]) {
-                       child[i] = 0;
+               if (!tree->child[i])
                        continue;
+
+/*
+               if (!(directions & (1 << i)))
+                       continue;
+*/
+               node = quadtree_find_nearest(tree->child[i], pos, corner);
+
+               if (!node)
+                       continue;
+
+               vector_sub(pos, &node->pos, &tmp);
+               dist = vector_abs(&tmp);
+
+               if (!nearest || dist < distance) {
+                       nearest = node;
+                       distance = dist;
+
                }
+       }
 
-               child[children] = node->child[i];
-               node->child[i] = NULL;
-               children++;
+       if (nearest && !is_within(&nearest->pos, corner)) {
+               if (debug)
+                       printf("Node %p (%f %f) is not within "
+                               "search window (%f %f) (%f %f)\n",
+                               nearest, nearest->pos.x, nearest->pos.y,
+                               corner[0].x, corner[0].y,
+                               corner[1].x, corner[1].y);
+               return NULL;
        }
 
+       return nearest;
+}
+
+static struct quadtree *quadtree_find_nearest_noparent(struct quadtree *tree,
+                                               struct vector *pos,
+                                               struct vector *corner)
+{
+       struct quadtree *nearest;
+       struct vector sub;
+       struct quadtree *node;
+       double dist = 0, dist2;
+       int i;
+
+       nearest = quadtree_find_nearest(tree, pos, corner);
+
+       if (nearest != tree)
+               goto out;
+
+       if (debug)
+               printf("Avoiding parent or NULL node %p\n", nearest);
+
        /*
-        * Try to remove the nodes in such order that the node which
-        * has child number one quarter of the parent's child numer
-        * gets deleted first.
+        * oops, we don't want to pick the parent node, let's choose
+        * another one
         */
+       nearest = NULL;
+       for (i = 0; i < 4; i++) {
+               if (!tree->child[i])
+                       continue;
 
-       if (node->children <= parents / 4) {
-               if (debug) {
-                       printf("%d: Moving node %p away from parent %p\n",
-                              __LINE__, node, parent);
-                       fflush(stdout);
+               if (!nearest) {
+                       nearest = quadtree_find_nearest(tree->child[i], pos,
+                                                       corner);
+
+                       if (nearest == NULL)
+                               continue;
+
+                       vector_sub(pos, &nearest->pos, &sub);
+                       dist = vector_abs(&sub);
+                       continue;
+               }
+               node = quadtree_find_nearest(tree->child[i],
+                                       pos, corner);
+
+               if (node == NULL)
+                       continue;
+
+               vector_sub(pos, &node->pos, &sub);
+               dist2 = vector_abs(&sub);
+
+               if (dist2 < dist) {
+                       nearest = node;
+                       dist = dist2;
                }
-               validate_tree(parent);
-               quadtree_add(parent, node, ops);
-               node = NULL;
-               parents /= 4;
        }
 
-       while(children) {
-               max_c = -1;
-               max_i = -1;
-               for (i = 0; i < 4; i++) {
-                       if (!child[i])
-                               continue;
+out:
+       return nearest;
+}
+
+static void get_middle_point(struct vector *corner, struct vector *middle)
+{
+       middle->x = (corner[0].x + corner[1].x) / (double)2;
+       middle->y = (corner[0].y + corner[1].y) / (double)2;
+}
+
+static int quadtree_split_by_node(struct quadtree *node,
+                               struct vector *corner, int dir)
+{
+       if (!is_within(&node->pos, corner)) {
+               if (debug)
+                       printf("Not within search rectangle\n");
+               return 0;
+       }
+
+       switch (dir) {
+       case QUADTREE_UPLEFT:
+               corner[0] = node->pos;
+               break;
+       case QUADTREE_UPRIGHT:
+               corner[0].y = node->pos.y;
+               corner[1].x = node->pos.x;
+               break;
+       case QUADTREE_DOWNRIGHT:
+               corner[1] = node->pos;
+               break;
+       case QUADTREE_DOWNLEFT:
+               corner[0].x = node->pos.x;
+               corner[1].y = node->pos.y;
+               break;
+       default:
+               trap();
+       }
+
+       if (debug)
+               printf("Search rectangle is now (%f %f) (%f %f), (%f %f)\n",
+                       corner[0].x, corner[0].y,
+                       corner[1].x, corner[1].y,
+                       node->pos.x, node->pos.y);
+
+       if ((corner[0].x < corner[1].x) ||
+           (corner[0].y < corner[1].y))
+               trap();
 
-                       if (max_c < child[i]->children) {
-                               max_c = child[i]->children;
-                               max_i = i;
+       return 1;
+}
+
+/*
+ * Quickly detach a node from a tree. Move all child nodes under
+ * @parent node.
+ */
+static int _quadtree_del(struct quadtree *node, struct quadtree *parent)
+{
+       struct quadtree *n;
+       int i;
+
+       /* Detach from the tree */
+       if (node->parent)
+               for (i = 0; i < 4; i++) {
+                       if (node->parent->child[i] == node) {
+                               node->parent->child[i] = 0;
+                               break;
                        }
                }
 
-               _qt_optimal_del(parent, child[max_i], ops, parents / 4);
-               child[max_i] = 0;
-               children--;
+       if (i == 4)
+               trap();
+
+
+       for (i = 0; i < 4; i++) {
+               if (!node->child[i])
+                       continue;
+
+               n = node->child[i];
+               _quadtree_del(n, parent);
+               _quadtree_add(parent, n);
        }
 
-       if (node) {
-               validate_tree(parent);
-               if (debug) {
-                       printf("%d: Moving node %p away from parent %p\n",
-                              __LINE__, node, parent);
-                       fflush(stdout);
-               }
-               quadtree_add(parent, node, ops);
+       return 0;
+}
+
+/**
+ * Move everything under @tree node to @parent node, everything
+ * else except the @tree node itself
+ */
+static int optimally_move_tree(struct quadtree *tree, struct quadtree *parent,
+                       struct vector *corner_orig,
+                       struct quadtree_ops *ops)
+{
+       struct vector corner[2], mid;
+       struct quadtree *t, *tmp;
+       int moved = 0;
+
+       get_middle_point(corner_orig, &mid);
+       t = quadtree_find_nearest_noparent(tree, &mid, corner_orig);
+
+       if (!t) {
+               if (debug)
+                       printf("Cannot find nearest node\n");
+
+               goto out;
        }
+
+       /*
+        * Now we have the t node which contains the object of the
+        * spatial middle coordinates of the tree.
+        */
+
+       if (debug) {
+               printf("Relocating node %p (%f %f) under parent %p\n", t,
+                       t->pos.x, t->pos.y, parent);
+               printf("There are %ld child nodes left\n", tree->children);
+               fflush(stdout);
+       }
+       _quadtree_del(t, tree);
+       quadtree_add(parent, t, ops);
+
+       moved++;
+       tree->children--;
+       if (!tree->children)
+               goto out;
+
+       /*
+        * Now split the search rectangle in quadtres and do the same
+        * with all of the quarters.
+        */
+
+       corner[0] = corner_orig[0];
+       corner[1] = corner_orig[1];
+       if (quadtree_split_by_node(t, corner, QUADTREE_UPLEFT))
+               moved += optimally_move_tree(tree, parent, corner, ops);
+
+       corner[0] = corner_orig[0];
+       corner[1] = corner_orig[1];
+       if (quadtree_split_by_node(t, corner, QUADTREE_UPRIGHT))
+               moved += optimally_move_tree(tree, parent, corner, ops);
+
+       corner[0] = corner_orig[0];
+       corner[1] = corner_orig[1];
+       if (quadtree_split_by_node(t, corner, QUADTREE_DOWNLEFT))
+               moved += optimally_move_tree(tree, parent, corner, ops);
+
+       corner[0] = corner_orig[0];
+       corner[1] = corner_orig[1];
+       if (quadtree_split_by_node(t, corner, QUADTREE_DOWNRIGHT))
+               moved += optimally_move_tree(tree, parent, corner, ops);
+
+       get_middle_point(corner_orig, &mid);
+       tmp = quadtree_find_nearest(tree, &mid, corner_orig);
+       if (tmp && tmp != tree)
+               trap();
+
+out:
+       if (debug)
+               printf("Now moved %d nodes, %ld left\n", moved, tree->children);
+
+       return moved;
 }
 
 /*
@@ -295,74 +653,96 @@ static void _qt_optimal_del(struct quadtree *parent, struct quadtree *node,
 struct quadtree *quadtree_del(struct quadtree *node,
                              struct quadtree_ops *ops)
 {
-       struct quadtree *parent = 0;
-       int i;
+       struct quadtree *parent = NULL;
+       struct vector corner[2];
+       int i, children = node->children, moved;
+
+       validate_tree(node);
 
        if (debug) {
                printf("Deleting node %p under parent %p\n",
                               node, node->parent);
+               printf("Relocating %ld children\n", node->children);
                fflush(stdout);
        }
-       /*
-        * We are deleting the root node. This means we have to select
-        * a new root node and reconstruct the entire tree under it
-        * again.
-        */
+
        if (!node->parent) {
+               /*
+                * We are deleting the root node. This means we have
+                * to select a new root node and reconstruct the
+                * entire tree under it again.
+                */
+               if (debug) {
+                       printf("Deleting root node\n");
+                       fflush(stdout);
+               }
                for (i = 0; i < 4; i++) {
                        if (!node->child[i])
                                continue;
 
-                       if (!parent) {
-                               parent = node->child[i];
-                               parent->parent = 0;
-                               continue;
-                       }
-                       _qt_optimal_del(parent, node->child[i], ops,
-                                       node->children);
-                       node->child[i] = 0;
+                       parent = node->child[i];
+                       _quadtree_del(parent, node);
+                       parent->parent = 0;
+                       quadtree_recalculate_parent_stats(parent, ops);
+                       break;
                }
+       } else {
+               /*
+                * The node has a parent. Detach the node from it and
+                * relocate the children.
+                */
 
-               return parent;
-       }
+               for (i = 0; i < 4; i++) {
+                       if (node->parent->child[i] == node) {
+                               node->parent->child[i] = 0;
+                               break;
+                       }
+               }
+               if (i == 4)
+                       trap();
 
-       /*
-        * We are not deleting the parent. Detach the node from the
-        * parent abd relocate the children. The node will be then
-        * detached from the tree.
-        */
+               parent = node->parent;
 
-       for (i = 0; i < 4; i++) {
-               if (node->parent->child[i] == node) {
-                       node->parent->child[i] = 0;
-                       break;
-               }
+               /*
+                * The sub branch is now detached from the main
+                * tree. Fix the stats.
+                */
+               quadtree_recalculate_parent_stats(node->parent, ops);
        }
-       if (i == 4)
-               trap();
 
-       parent = node->parent;
+       validate_tree(parent);
 
+       if (!node->children)
+               goto out;
        /*
-        * The sub branch is now detached from the main tree. Fix the
-        * stats.
+        * Now we are ready to prepare for relocating the nodes under
+        * the parent.
         */
-       quadtree_recalculate_parent_stats(node->parent, ops);
-
-       parent = quadtree_find_parent(parent);
 
-       if (node->children) {
-               for (i = 0; i < 4; i++) {
-                       if (!node->child[i])
-                               continue;
+       quadtree_get_tree_dimensions(node, corner);
+       moved = optimally_move_tree(node, parent, corner, ops);
 
-                       _qt_optimal_del(parent, node->child[i], ops,
-                                       node->children);
+       if (debug) {
+               if (moved != children) {
+                       printf("Got %d children but %d were moved\n",
+                               children, moved);
+                       printf("nearest children left:\n");
+                       for (i = 0 ; i < 4; i++)
+                               if (node->child[i])
+                                       printf("   node %d %p, (%f %f)\n",
+                                               i, node->child[i],
+                                               node->child[i]->pos.x,
+                                               node->child[i]->pos.y);
+                       fflush(stdout);
+                       trap();
                }
+               printf("Delete done, returning parent %p\n", parent);
+               fflush(stdout);
        }
 
+out:
        validate_tree(parent);
-       return parent;
+       return quadtree_find_parent(parent);
 }