latentspace.

Quadtree

What is a Quadtree ?

A Quadtree is a data structure in which each internal node has exactly four children. Quadtrees are most used to partition a 2D space by recursively subdividing it into quadrants. The subdivided regions may be square or rectangular, or may have arbitrary shapes.

All forms of quadtrees share some common features:


Quadtree conception
Each node has exactly four children

Edge quadtree

Edge quadtrees are used to store lines. The difference is the type of information stored about the cells. Curves are approximated by subdivided cells to a very fine resolution, specifically until there is a single line segment per cell. Near corners/vertices, edge quadtrees will continue dividing until they reach their maximum level of decomposition. This can result in extremely unbalanced trees.

There is an interesting diagram regarding Edge quadtree. Here, the k is may be infinite. That's why, as mentioned above you should designate the maximum level of decomposition.


Edge quadtree diagram
Each node has exactly four children


Implementation

Now, let's implement it using GhPython based on the explanations above. First, define the quadtree class and takes bounding_box and min_size as input values. The min_size is used as the maximum level of decomposition as mentioned above.

    class Quadtree:
        COUNTER = 1

        def __init__(self, bounding_box, min_size=2):
            self.bounding_box = bounding_box
            self.min_size = min_size

            self.crvs = []
            self.children = None
            self.children_geometries = None
    
            self._post_init()
Quadtree has a root node, middle node, and leaf node because its data structure is based on a node. If cannnot subdivide no more, children will be None, else other Quadtree objects.

In the crvs instance variable of the quadtree, the line segments included in each quadtree rectangle are stored. The COUNTER class variable that the quadtree has, is used as a flag to subdivide when each region of the quadtree contains a curve.

In the second, you need a function that checks whether intersects(or contains) between given curves and quadtree region. It can be defined as below, and a return value of this function is worked as a requirement condition to subdivide.

        def _is_intersects(self, crv, bb):
            intersects = rg.Intersect.Intersection.CurveCurve(
                crv, bb, ConstsCollection.TOLERANCE, ConstsCollection.TOLERANCE
            )

            return len(intersects) > 0


Finally, if you define a function to create a quadtrant and a function to insert it into each quadtree, all preparations are complete. Those functions can be defined as below, you can see the full code at this link.

    def _subdivide(self):
        ne_bb = rg.Rectangle3d(self.plane, self.width / 2, self.height / 2)
        ne_bb = ne_bb.ToNurbsCurve()

        nw_bb = rg.Rectangle3d(self.plane, -self.width / 2, self.height / 2)
        nw_bb = nw_bb.ToNurbsCurve()

        se_bb = rg.Rectangle3d(self.plane, self.width / 2, -self.height / 2)
        se_bb = se_bb.ToNurbsCurve()

        sw_bb = rg.Rectangle3d(self.plane, -self.width / 2, -self.height / 2)
        sw_bb = sw_bb.ToNurbsCurve()

        ne = Quadtree(ne_bb)
        nw = Quadtree(nw_bb)
        se = Quadtree(se_bb)
        sw = Quadtree(sw_bb)

        self.children = [ne, nw, se, sw]
        self.children_geometries = [ne_bb, nw_bb, se_bb, sw_bb]

        for crv in self.crvs:
            if self._is_intersects(crv, ne_bb):
                ne.insert(crv)
            if self._is_intersects(crv, nw_bb):
                nw.insert(crv)
            if self._is_intersects(crv, se_bb):
                se.insert(crv)
            if self._is_intersects(crv, sw_bb):
                sw.insert(crv)

    def insert(self, crv):
        """Append the geometry to quadtree

        Args:
            crv (Rhino.Geometry.Curve): Target curve to insert
        """

        if self.children:
            for child in self.children:
                if self._is_intersects(crv, self.bounding_box):
                    child.insert(crv)
        else:
            self.crvs.append(crv)

            if len(self.crvs) >= self.COUNTER and (
                self.width > self.min_size or self.height > self.min_size
            ):
                self._subdivide()


Example

If you have done all implementations now, you can get the geometries regarding Edge quadtree as below.
Edge quadtree example
From the left, given edge · edge quadtree
You can set the direction of the bounding box using the plane of Rhino object