javasearchprocessingcollision-detectionquadtree

Why this Quadtree Query often returns nothing?


Any "W" refers to half-width and any "H" refers to half-height. Any "X" and "Y" refers to the center of a rectangle aligned with the co-ordinate axes.

I am attempting to write some general purpose quadtree code for use in physics simulations and game design. At first it was returning ALL points in the contents of a quadtree that was completely enclosed by the search region. After adding a bunch of print() statements to illuminate the process I managed to solve that problem but now I have a new one I can't seem to figure out. For some reason when I query the quadtree for the "Placeables" within a rectangular region I either get a totally correct result, but more often than not I get nothing returned at all. Placeables are defined as follows:

abstract class Placeable{ //things that can be stored in quadtree
  PVector pos;
  abstract void show();}

The important parts of the quadtree code:

class Quadtree{ //Data structure for storing objects in 2D
  Quadtree nw,ne,sw,se; //children
  Quadtree parent;
  ArrayList<Placeable> contents;//contents of THIS branch only, child object contents not included
  float x,y,w,h;//center point, half-width and half-height
  boolean divided;  //false if leaf node
  static final int capacity = 6; //controls maximum capacity all quadtree nodes for case-by-case tuning 
...

...
boolean intersects(float rX, float rY, float rW, float rH){//Do I intersect with the rectangle defined by rX,rY,rW,rH?
    return !(rX-rW >= x+w || rX+rW <= x-w 
          || rY-rH >= y+h || rY+rH <= y-h);}
      
  //return all Placeables inside a rectangular region
  ArrayList<Placeable> query(float X, float Y, float W, float H){
    return query(X,Y,W,H,new ArrayList<Placeable>());}
  ArrayList<Placeable> query(float X, float Y, float W, float H, ArrayList<Placeable> found){
    print("Querying:",contents.size(),"/",Integer.toString(capacity),"\t");
    
    if (!intersects(X,Y,W,H)){ //if no intersection, return arraylist unmodified
      print("Does Not Intersect\n");
      return found;
    } else {  //if intersection, check contents for Placeables within the region
      print("Does Intersect!\n");
      for (Placeable p : contents){
        if ( (p.pos.x <= X+W) && (p.pos.x >= X-W) //capitals refer to search region
          && (p.pos.y <= Y+H) && (p.pos.y >= Y-H) ){
          found.add(p);
          print("Found Point\t",p.pos.x,"\t",p.pos.y,"\n");}
      }
      if (divided){ //after checking own contents, recursively query children appending to same array
        print("Descending\n");
        print("NW ");
        nw.query(X,Y,W,H,found);
        print("NE ");
        ne.query(X,Y,W,H,found);
        print("SW ");
        sw.query(X,Y,W,H,found);
        print("SE ");
        se.query(X,Y,W,H,found);
        print("Raising\n");}
      return found;
    }
  }
.....
}

The following is an example of correct output in a 1024x768 window:

Querying: 6 / 6     Does Intersect!
Descending
NW Querying: 6 / 6  Does Not Intersect
NE Querying: 6 / 6  Does Intersect!
Descending
NW Querying: 4 / 6  Does Not Intersect
NE Querying: 3 / 6  Does Not Intersect
SW Querying: 6 / 6  Does Intersect!
Found Point  665.36365   379.79324 
Descending
NW Querying: 1 / 6  Does Not Intersect
NE Querying: 0 / 6  Does Not Intersect
SW Querying: 0 / 6  Does Intersect!
SE Querying: 0 / 6  Does Intersect!
Raising
SE Querying: 5 / 6  Does Intersect!
Found Point  772.9317    375.3352 
Raising
SW Querying: 6 / 6  Does Not Intersect
SE Querying: 6 / 6  Does Intersect!
Found Point  790.5205    386.4734 
Found Point  633.3443    390.43515 
Found Point  797.5499    397.1355 
Descending
NW Querying: 6 / 6  Does Intersect!
Found Point  723.5054    450.78967 
Found Point  741.98676   389.52292 
Found Point  683.0763    488.69083 
NE Querying: 4 / 6  Does Intersect!
Found Point  775.3038    432.92847 
SW Querying: 6 / 6  Does Not Intersect
SE Querying: 4 / 6  Does Not Intersect
Raising
Raising
Found: 9 

The correct output image: Non bugged output, selected points are green.

The following is an example of incorrect output in the same window that should have returned 3 points:

Querying: 6 / 6     Does Intersect!
Descending
NW Querying: 6 / 6  Does Not Intersect
NE Querying: 6 / 6  Does Not Intersect
SW Querying: 6 / 6  Does Intersect!
Descending
NW Querying: 3 / 6  Does Not Intersect
NE Querying: 5 / 6  Does Not Intersect
SW Querying: 6 / 6  Does Not Intersect
SE Querying: 6 / 6  Does Not Intersect
Raising
SE Querying: 6 / 6  Does Not Intersect
Raising
Found: 0 

This is the window that accompanied the incorrect output. (Found points change to pure green.) Quadtree bug demonstration.

I haven't been able to spot a pattern to which regions produce the correct output but my instincts say regions on the left are incorrect more often.

The full file is available here so you can run it if you need to but I'm stumped and I've come back to this a few times over the last week. Please help.


Solution

  • Wow, this was a difficult problem to find (at least, I hope I solved it).

    The problem is with your definition of rW and rH in setup():

    rW = constrain(randomGaussian(),-1,1)*width/6;
    rH = constrain(randomGaussian(),-1,1)*height/6;
    

    You can't have negative values for rW or rH. For example, suppose we test the following values (suppose the y values intersect as well):

    rW = -100;
    rX = 500
    x = 256;
    w = 256;
    

    Ideally, intersects() would test as follows:

             !(400 >= 512 || 600 <= 512)
    /*-->*/  !(false || false)
    /*-->*/  true //(yes, they intersect)
    

    but what actually happens is

             !(rX-rW >= x+w || rX+rW <= x-w)
    /*-->*/  !(500-(-100) >= 256+256 || 500+(-100) <= 256-256)
    /*-->*/  !(600 >= 512 || 400 <= 0)
    /*-->*/  !(true || false)
    /*-->*/  false //(no, they do not intersect)
    

    It's like you're testing the wrong sides of the region.

    This is a pretty easy fix, just use abs() when you define rW and rH:

    rW = abs(constrain(randomGaussian(),-1,1))*width/6;
    rH = abs(constrain(randomGaussian(),-1,1))*height/6;