Archive 17/01/2023.

Quadtree

slapin

I decided to share a piece in AngelScript. It is useful to greatly reduce number of tests looking for neighbours.

Main code:

class Vertex {
    Vector2 coords;
    Vertex(Vector2 c)
    {
        this.coords = c;
    }
    int opCmp(Vertex@ other)
    {
        if (coords.x > other.coords.x)
            return 1;
        else if (coords.x < other.coords.x)
            return -1;
        else if (coords.y > other.coords.y)
            return 1;
        else if (coords.y < other.coords.y)
            return -1;
        else return 0;
    }
    bool opEquals(Vertex@ other)
    {
        if (other.coords == coords)
            return true;
        else
            return false;
    }
    String ToString() {
        return "Vertex [" + String(coords.x) + " " + String(coords.y) + "]";
    }
};
class Quadtree {
    Array<Vertex@> vertices(4);
    Rect rect;
    int num_stored = 0;
    bool split = false;
    Quadtree@ NE;
    Quadtree@ NW;
    Quadtree@ SW;
    Quadtree@ SE;
    Array<Quadtree@> sa(4);
    int count()
    {
        int ret = num_stored;
        if (split) {
           for (int i = 0; i < sa.length; i++)
               ret += sa[i].count();
        }
        return ret;
    }
    String ToString()
    {
        String rectstr = rect.ToVector4().ToString();
        return rectstr;
    }
    private void do_split()
    {
        float midx = (rect.left + rect.right) / 2.0;
        float midy = (rect.top + rect.bottom) / 2.0;
        NE = Quadtree(Rect(midx, rect.top, rect.right, midy));
        NW = Quadtree(Rect(rect.left, rect.top, midx, midy));
        SW = Quadtree(Rect(rect.left, midy, midx, rect.bottom));
        SE = Quadtree(Rect(midx, midy, rect.right, rect.bottom));
        split = true;
        sa[0] = NE;
        sa[1] = NW;
        sa[2] = SW;
        sa[3] = SE;
    }
    bool insert(Vertex@ vertex)
    {
        if ((rect.IsInside(vertex.coords) == OUTSIDE)) {
            return false;
        }
        if (num_stored < 4) {
            vertices[num_stored] = vertex;
            num_stored++;
            return true;
        } else {
            if (!split)
                do_split();
            for (int i = 0; i < sa.length; i++)
                if (sa[i].insert(vertex))
                    return true;
        }
        return false;
    }
    Array<Vertex@> search(Rect r)
    {
        int intersecting = 0, i, j;
        Array<Vertex@> ret;
        if (num_stored == 0 && !split)
            return ret;
        Rect tr(r);
        tr.Clip(rect);
        if (tr.size.length == M_INFINITY)
            return ret;
        ret.Resize(num_stored);
        for (i = 0; i < num_stored; i++)
            ret[i] = vertices[i];
        if (split) {
            for (i = 0; i < sa.length; i++) {
                Array<Vertex@> tmp = sa[i].search(r);
                int offset = ret.length;
                ret.Resize(offset + tmp.length);
                for (j = 0; j < tmp.length; j++) {
                    ret[offset + j] = tmp[j];
                }
            }
        }
        return ret;
    }
    Rect find_rect(Vertex@ v)
    {
        vertices.Resize(num_stored);
        if (vertices.Find(v) >= 0)
            return rect;
        else if (split)
            for (int i = 0; i < sa.length; i++) {
                Rect r = sa[i].find_rect(v);
                if (r.size.length > 0)
                    return r;
            }
         return Rect();
    }
    Quadtree(Rect r)
    {
        rect = r;
    }
};

Test program / usage example:

#include "Vertex2.as"

void Start()
{
    Quadtree q(Rect(-500, -500, 500, 500));
    Image img;
    Rect search_rect(-100, -100, 100, 100);
    img.SetSize(1000, 1000, 3);
    img.ClearInt(0);
    int inrect = 0;
    int count = 0;
    Array<Vertex@> all_points;
    for (int i = 0; i < 100000; i++) {
        Vertex v(Vector2(Random(-500, 500), Random(-500, 500)));
        if (q.insert(v)) {
            count++;
            if (search_rect.IsInside(v.coords) != OUTSIDE) {
                all_points.Push(v);
                inrect++;
                img.SetPixel(v.coords.x + 500, v.coords.y + 500, Color(1.0, 1.0, 0.0));
            } else
                img.SetPixel(v.coords.x + 500, v.coords.y + 500, Color(1.0, 0.0, 0.0));
        } else
            img.SetPixel(v.coords.x + 500, v.coords.y + 500, Color(0.1, 0.1, 1.0));
    }
    Print("Searching");
    Array<Vertex@> vs = q.search(search_rect);
    Print("Search done");
    for (int i = 0; i < vs.length; i++)
        img.SetPixel(vs[i].coords.x + 500, vs[i].coords.y + 500, Color(0.6, 1.0, 0.6));
    Print("inrect: " + String(inrect));
    img.SavePNG("quadtree.png");
    Print("Saved");
    engine.Exit();
}

The idea is that you use “insert” method to insert vertices and search for looking at them.

On image - red vertices are all vertices, green - the ones found by search.