BSP

From DmWiki

A Binary Space Partitioning Tree (or BSP Tree) is a data structure that is used to organize objects within a space. Within the field of computer graphics, it has applications in hidden surface removal and ray tracing. A BSP tree is a recursive sub-division of space that treats each line segment (or polygon, in 3D) as a cutting plane which is used to categorize all remaining objects in the space as either being in "front" or in "back" of that plane. In other words, when a partition is inserted into the tree, it is first categorized with respect to the root node, and then recursively with respect to each appropriate child.


  BSP
      
      Overview A Binary Space Partitioning (BSP) tree represents a
      recursive, hierarchical partitioning, or subdivision, of
      n-dimensional space into convex subspaces. BSP tree construction
      is a process which takes a subspace and partitions it by any
      hyperplane that intersects the interior of that subspace. The
      result is two new subspaces that can be further partitioned by
      recursive application of the method.
      
      A "hyperplane" in n-dimensional space is an n-1 dimensional object
      which can be used to divide the space into two half-spaces. For
      example, in three dimensional space, the "hyperplane" is a plane.
      In two dimensional space, a line is used.
      
      BSP trees are extremely versatile, because they are powerful
      sorting and classification structures. They have uses ranging from
      hidden surface removal and ray tracing hierarchies to solid
      modeling and robot motion planning.
      
      Example
     
      An easy way to think about BSP trees is to limit the discussion to
      two dimensions. To simplify the situation, let's say that we will
      use only lines parallel to the X or Y axis, and that we will
      divide the space equally at each node. For example, given a square
      somewhere in the XY plane, we select the first split, and thus the
      root of the BSP Tree, to cut the square in half in the X
      direction. At each slice, we will choose a line of the opposite
      orientation from the last one, so the second slice will divide
      each of the new pieces in the Y direction. This process will
      continue recursively until we reach a stopping point, and looks
      like this
      
        +-----------+      +-----+-----+      +-----+-----+
        |           |      |     |     |      |     |     |
        |           |      |     |     |      |  d  |     |
        |           |      |     |     |      |     |     |
        |     a     |  ->  |  b  X  c  |  ->  +--Y--+  f  |  -> ...
        |           |      |     |     |      |     |     |
        |           |      |     |     |      |  e  |     |
        |           |      |     |     |      |     |     |
        +-----------+      +-----+-----+      +-----+-----+
  
      
      The resulting BSP tree looks like this at each step
     
     a                  X                  X           ...
                      -/ \+              -/ \+
                      /   \              /   \
                     b     c            Y     f
                                      -/ \+
                                      /   \
                                     e     d
  
      
      Other space partitioning structures
     
      BSP trees are closely related to Quadtrees and Octrees. Quadtrees
      and Octrees are space partitioning trees which recursively divide
      subspaces into four and eight new subspaces, respectively. A BSP
      Tree can be used to simulate both of these structures.


  How do you build a BSP Tree?
      
      Overview
      Given a set of polygons in three dimensional space, we want to
      build a BSP tree which contains all of the polygons. For now, we
      will ignore the question of how the resulting tree is going to be
      used.
      
      The algorithm to build a BSP tree is very simple:
      
        1. Select a partition plane.
        2. Partition the set of polygons with the plane.
        3. Recurse with each of the two new sets.
  
      
      Choosing the partition plane
      The choice of partition plane depends on how the tree will be
      used, and what sort of efficiency criteria you have for the
      construction. For some purposes, it is appropriate to choose the
      partition plane from the input set of polygons. Other applications
      may benefit more from axis aligned orthogonal partitions.
      
      In any case, you want to evaluate how your choice will affect the
      results. It is desirable to have a balanced tree, where each leaf
      contains roughly the same number of polygons. However, there is
      some cost in achieving this. If a polygon happens to span the
      partition plane, it will be split into two or more pieces. A poor
      choice of the partition plane can result in many such splits, and
      a marked increase in the number of polygons. Usually there will be
      some trade off between a well balanced tree and a large number of
      splits.
      
      Partitioning polygons
      Partitioning a set of polygons with a plane is done by classifying
      each member of the set with respect to the plane. If a polygon
      lies entirely to one side or the other of the plane, then it is
      not modified, and is added to the partition set for the side that
      it is on. If a polygon spans the plane, it is split into two or
      more pieces and the resulting parts are added to the sets
      associated with either side as appropriate.
      
      When to stop
      The decision to terminate tree construction is, again, a matter of
      the specific application. Some methods terminate when the number
      of polygons in a leaf node is below a maximum value. Other methods
      continue until every polygon is placed in an internal node.
      Another criteria is a maximum tree depth.
      
      Pseudo C++ code example
      Here is an example of how you might code a BSP tree:
      

struct BSP_tree {

  plane     partition;
  list      polygons;
  BSP_tree  *front,
            *back;

};

      This structure definition will be used for all subsequent example
      code. It stores pointers to its children, the partitioning plane
      for the node, and a list of polygons coincident with the partition
      plane. For this example, there will always be at least one polygon
      in the coincident list: the polygon used to determine the
      partition plane. A constructor method for this structure should
      initialize the child pointers to NULL.
      

void Build_BSP_Tree (BSP_tree *tree, list polygons) {

  polygon   *root = polygons.Get_From_List ();
  tree->partition = root->Get_Plane ();
  tree->polygons.Add_To_List (root);
  list      front_list,
            back_list;
  polygon   *poly;
  while ((poly = polygons.Get_From_List ()) != 0)
  {
     int   result = tree->partition.Classify_Polygon (poly);
     switch (result)
     {
        case COINCIDENT:
           tree->polygons.Add_To_List (poly);
           break;
        case IN_BACK_OF:
           backlist.Add_To_List (poly);
           break;
        case IN_FRONT_OF:
           frontlist.Add_To_List (poly);
           break;
        case SPANNING:
           polygon   *front_piece, *back_piece;
           Split_Polygon (poly, tree->partition, front_piece, back_piece);
           backlist.Add_To_List (back_piece);
           frontlist.Add_To_List (front_piece);
           break;
     }
  }
  if ( ! front_list.Is_Empty_List ())
  {
     tree->front = new BSP_tree;
     Build_BSP_Tree (tree->front, front_list);
  }
  if ( ! back_list.Is_Empty_List ())
  {
     tree->back = new BSP_tree;
     Build_BSP_Tree (tree->back, back_list);
  }

}

      This routine recursively constructs a BSP tree using the above
      definition. It takes the first polygon from the input list and
      uses it to partition the remainder of the set. The routine then
      calls itself recursively with each of the two partitions. This
      implementation assumes that all of the input polygons are convex.
      
      One obvious improvement to this example is to choose the
      partitioning plane more intelligently. This issue is addressed
      separately in the section, "How can you make a BSP Tree more
      efficient?".    


  How do you partition a polygon with a plane?
      
      Overview
      Partitioning a polygon with a plane is a matter of determining
      which side of the plane the polygon is on. This is referred to as
      a front/back test, and is performed by testing each point in the
      polygon against the plane. If all of the points lie to one side of
      the plane, then the entire polygon is on that side and does not
      need to be split. If some points lie on both sides of the plane,
      then the polygon is split into two or more pieces.
      
      The basic algorithm is to loop across all the edges of the polygon
      and find those for which one vertex is on each side of the
      partition plane. The intersection points of these edges and the
      plane are computed, and those points are used as new vertices for
      the resulting pieces.
      
      Implementation notes
      Classifying a point with respect to a plane is done by passing the
      (x, y, z) values of the point into the plane equation, Ax + By +
      Cz + D = 0. The result of this operation is the distance from the
      plane to the point along the plane's normal vector. It will be
      positive if the point is on the side of the plane pointed to by
      the normal vector, negative otherwise. If the result is 0, the
      point is on the plane.
      
      For those not familiar with the plane equation, The values A, B,
      and C are the coordinate values of the normal vector. D can be
      calculated by substituting a point known to be on the plane for x,
      y, and z.
      
      Convex polygons are generally easier to deal with in BSP tree
      construction than concave ones, because splitting them with a
      plane always results in exactly two convex pieces. Furthermore,
      the algorithm for splitting convex polygons is straightforward and
      robust. Splitting of concave polygons, especially self
      intersecting ones, is a significant problem in its own right.
      
      Pseudo C++ code example
      Here is a very basic function to split a convex polygon with a plane
      

void Split_Polygon (polygon *poly, plane *part, polygon *&front, polygon *&back ) {

  int   count = poly->NumVertices (),
        out_c = 0, in_c = 0;
  point ptA, ptB,
        outpts[MAXPTS],
        inpts[MAXPTS];
  real sideA, sideB;
  ptA = poly->Vertex (count - 1);
  sideA = part->Classify_Point (ptA);
  for (short i = -1; ++i < count;)
  {
     ptB = poly->Vertex (i);
     sideB = part->Classify_Point (ptB);
     if (sideB > 0)
     {
        if (sideA < 0)
        {
           // compute the intersection point of the line
           // from point A to point B with the partition
           // plane. This is a simple ray-plane intersection.
           vector v = ptB - ptA;
           real   sect = - part->Classify_Point (ptA) / (part->Normal () | v);
           outpts[out_c++] = inpts[in_c++] = ptA + (v * sect);
        }
        outpts[out_c++] = ptB;
     }
     else if (sideB < 0)
     {
        if (sideA > 0)
        {
           // compute the intersection point of the line
           // from point A to point B with the partition
           // plane. This is a simple ray-plane intersection.
           vector v = ptB - ptA;
           real   sect = - part->Classify_Point (ptA) / (part->Normal () | v);
           outpts[out_c++] = inpts[in_c++] = ptA + (v * sect);
        }
        inpts[in_c++] = ptB;
     }
     else
        outpts[out_c++] = inpts[in_c++] = ptB;
     ptA = ptB;
     sideA = sideB;
  }
  front = new polygon (outpts, out_c);
  back = new polygon (inpts, in_c);

}

      A simple extension to this code that is good for BSP trees is to
      combine its functionality with the routine to classify a polygon
      with respect to a plane.
      
      Note that this code is not robust, since numerical stability may
      cause errors in the classification of a point. The standard
      solution is to make the plane "thick" by use of an epsilon value.      


  How do you remove hidden surfaces with a BSP Tree?
      
      Overview
      Probably the most common application of BSP trees is hidden
      surface removal in three dimensions. BSP trees provide an elegant,
      efficient method for sorting polygons via a depth first tree walk.
      This fact can be exploited in a back to front "painter's
      algorithm" approach to the visible surface problem, or a front to
      back scanline approach.
      
      BSP trees are well suited to interactive display of static (not
      moving) geometry because the tree can be constructed as a
      preprocess. Then the display from any arbitrary viewpoint can be
      done in linear time. Adding dynamic (moving) objects to the scene
      is discussed in another section of this document.
      
      Painter's algorithm
      The idea behind the painter's algorithm is to draw polygons far
      away from the eye first, followed by drawing those that are close
      to the eye. Hidden surfaces will be written over in the image as
      the surfaces that obscure them are drawn. One condition for a
      successful painter's algorithm is that there be a single plane
      which separates any two objects. This means that it might be
      necessary to split polygons in certain configurations. For
      example, this case can not be drawn correctly with a painter's
      algorithm
      
                         +------+
                         |      |
         +---------------|      |--+
         |               |      |  |
         |               |      |  |
         |               |      |  |
         |      +--------|      |--+
         |      |        |      |
      +--|      |--------+      |
      |  |      |               |
      |  |      |               |
      |  |      |               |
      +--|      |---------------+
         |      |
         +------+
      One reason that BSP trees are so elegant for the painter's algorithm
      is that the splitting of difficult polygons is an automatic part
      of tree construction. Note that only one of these two polygons
      needs to be split in order to resolve the problem.
      
      To draw the contents of the tree, perform a back to front tree
      traversal. Begin at the root node and classify the eye point with
      respect to its partition plane. Draw the subtree at the far child
      from the eye, then draw the polygons in this node, then draw the
      near subtree. Repeat this procedure recursively for each subtree.
      
      Scanline hidden surface removal
      It is just as easy to traverse the BSP tree in front to back order
      as it is for back to front. We can use this to our advantage in a
      scanline method method by using a write mask which will prevent
      pixels from being written more than once. This will represent
      significant speedups if a complex lighting model is evaluated for
      each pixel, because the painter's algorithm will blindly evaluate
      the same pixel many times.
      
      The trick to making a scanline approach successful is to have an
      efficient method for masking pixels. One way to do this is to
      maintain a list of pixel spans which have not yet been written to
      for each scan line. For each polygon scan converted, only pixels
      in the available spans are written, and the spans are updated
      accordingly.
      
      The scan line spans can be represented as binary trees, which are
      just one dimensional BSP trees. This technique can be expanded to
      a two dimensional screen coverage algorithm using a two
      dimensional BSP tree to represent the masked regions. Any convex
      partitioning scheme, such as a quadtree, can be used with similar
      effect.
      
      Implementation notes
      When building a BSP tree specifically for hidden surface removal,
      the partition planes are usually chosen from the input polygon
      set. However, any arbitrary plane can be used if there are no
      intersecting or concave polygons, as in the example above.
      
      Pseudo C++ code example
      Using the BSP_tree structure defined in the section, "How do you
      build a BSP Tree?", here is a simple example of a back to front
      tree traversal:
      

void Draw_BSP_Tree (BSP_tree *tree, point eye) {

  real   result = tree->partition.Classify_Point (eye);
  if (result > 0)
  {
     Draw_BSP_Tree (tree->back, eye);
     tree->polygons.Draw_Polygon_List ();
     Draw_BSP_Tree (tree->front, eye);
  }
  else if (result < 0)
  {
     Draw_BSP_Tree (tree->front, eye);
     tree->polygons.Draw_Polygon_List ();
     Draw_BSP_Tree (tree->back, eye);
  }
  else // result is 0
  {
     // the eye point is on the partition plane...
     Draw_BSP_Tree (tree->front, eye);
     Draw_BSP_Tree (tree->back, eye);
  }

}

      If the eye point is classified as being on the partition plane, the
      drawing order is unclear. This is not a problem if the
      Draw_Polygon_List routine is smart enough to not draw polygons
      that are not within the viewing frustum. The coincident polygon
      list does not need to be drawn in this case, because those
      polygons will not be visible to the user.
      
      It is possible to substantially improve the quality of this
      example by including the viewing direction vector in the
      computation. You can determine that entire subtrees are behind the
      viewer by comparing the view vector to the partition plane normal
      vector. This test can also make a better decision about tree
      drawing when the eye point lies on the partition plane. It is
      worth noting that this improvement resembles the method for
      tracing a ray through a BSP tree, which is discussed in another
      section of this document.
      
      Front to back tree traversal is accomplished in exactly the same
      manner, except that the recursive calls to Draw_BSP_Tree occur in
      reverse order.       


  How do you accelerate ray tracing with a BSP Tree?
      
      Overview
      Ray tracing a BSP tree is very similar to hidden surface removal
      with a BSP tree. The algorithm is a simple forward tree walk, with
      a few additions that apply to ray casting.       


  How do you perform boolean operations on polytopes with a BSP Tree?
      
      Overview
      There are two major classes of solid modeling methods with BSP
      trees. For both methods, it is useful to introduce the notion of
      an in/out test.
      
      An in/out test is a different way of talking about the front/back
      test we have been using to classify points with respect to planes.
      The necessity for this shift in thought is evident when
      considering polytopes instead of just polygons. A point can not be
      merely in front or back of a polytope, but inside or outside.
      Somewhat formally, a point is inside of a polytope if it is inside
      of, or in back of, each hyperplane which composes the polytope,
      otherwise it is outside.
      
      Incremental construction
      Incremental construction of a BSP Tree is the process of inserting
      convex polytopes into the tree one by one. Each polytope has to be
      processed according to the operation desired.
      
      It is useful to examine the construction process in two
      dimensions. Consider the following figure
      

A B

+-------------+
|             |
|             |
|      E      |        F
|       +-----+-------+
|       |     |       |
|       |     |       |
|       |     |       |
+-------+-----+       |

D | C |

        |             |
        |             |
        +-------------+
       H               G
      Two polygons, ABCD, and EFGH, are to be inserted into the tree. We
      wish to find the union of these two polygons. Start by inserting
      polygon ABCD into the tree, choosing the splitting hyperplanes to
      be coincident with the edges. The tree looks like this after
      insertion of ABCD
      
               AB
             -/  \+
             /    \
            /      *
           BC
         -/  \+
         /    \
        /      *
       CD
     -/  \+
     /    \
    /      *
   DA
 -/  \+
 /    \
*      *
      Now, polygon EFGH is inserted into the tree, one polygon at a time.
      The result looks like this

A B

+-------------+
|             |
|             |
|      E      |J       F
|       +-----+-------+
|       |     |       |
|       |     |       |
|       |     |       |
+-------+-----+       |

D |L |C |

        |     |       |
        |     |       |
        +-----+-------+
       H      K        G
                       AB
                     -/  \+
                     /    \
                    /      *
                   BC
                 -/  \+
                 /    \
                /      \
               CD       \
             -/  \+      \
             /    \       \
            /      \       \
           DA       \       \
         -/  \+      \       \
         /    \       \       \
        /      *       \       \
       EJ              KH       \
     -/  \+          -/  \+      \
     /    \          /    \       \
    /      *        /      *       \
   LE              HL              JF
 -/  \+          -/  \+          -/  \+
 /    \          /    \          /    \
*      *        *      *        FG     *
                              -/  \+
                              /    \
                             /      *
                            GK
                          -/  \+
                          /    \
                         *      *
      Notice that when we insert EFGH, we split edges EF and HE along the
      edges of ABCD. this has the effect of dividing these segments into
      pieces which are inside ABCD, and outside ABCD. Segments EJ and LE
      will not be part of the boundary of the union. We could have saved
      our selves some work by not inserting them into the tree at all.
      For a union operation, you can always throw away segments that
      land in inside nodes. You must be careful about this though. What
      I mean is that any segments which land in inside nodes of side the
      pre-existing tree, not the tree as it is being constructed. EJ and
      LE landed in an inside node of the tree for polygon ABCD, and so
      can be discarded.
      
      Our tree now looks like this

A B

+-------------+
|             |
|             |
|             |J       F
|             +-------+
|             |       |
|             |       |
|             |       |
+-------+-----+       |

D |L |C |

        |     |       |
        |     |       |
        +-----+-------+
       H      K        G
                       AB
                     -/  \+
                     /    \
                    /      *
                   BC
                 -/  \+
                 /    \
                /      \
               CD       \
             -/  \+      \
             /    \       \
            /      \       \
           DA       \       \
         -/  \+      \       \
         /    \       \       \
        *      *       \       \
                       KH       \
                     -/  \+      \
                     /    \       \
                    /      *       \
                   HL              JF
                 -/  \+          -/  \+
                 /    \          /    \
                *      *        FG     *
                              -/  \+
                              /    \
                             /      *
                            GK
                          -/  \+
                          /    \
                         *      *
      Now, we would like some way to eliminate the segments JC and CL, so
      that we will be left with the boundary segments of the union.
      Examine the segment BC in the tree. What we would like to do is
      split BC with the hyperplane JF. Conveniently, we can do this by
      pushing the BC segment through the node for JF. The resulting
      segments can be classified with the rest of the JF subtree. Notice
      that the segment BJ lands in an out node, and that JC lands in an
      in node. Remembering that we can discard interior nodes, we can
      eliminate JC. The segment BJ replaces BC in the original tree.
      This process is repeated for segment CD, yielding the segments CL
      and LD. CL is discarded as landing in an interior node, and LD
      replaces CD in the original tree. The result looks like this

A B

+-------------+
|             |
|             |
|             |J       F
|             +-------+
|                     |
|                     |
|        L            |
+-------+             |

D | |

        |             |
        |             |
        +-----+-------+
       H      K        G
                       AB
                     -/  \+
                     /    \
                    /      *
                   BJ
                 -/  \+
                 /    \
                /      \
               LD       \
             -/  \+      \
             /    \       \
            /      \       \
           DA       \       \
         -/  \+      \       \
         /    \       \       \
        *      *       \       \
                       KH       \
                     -/  \+      \
                     /    \       \
                    /      *       \
                   HL              JF
                 -/  \+          -/  \+
                 /    \          /    \
                *      *        FG     *
                              -/  \+
                              /    \
                             /      *
                            GK
                          -/  \+
                          /    \
                         *      *
      As you can see, the result is the union of the polygons ABCD and EFGH.
      
      To perform other boolean operations, the process is similar. For
      intersection, you discard segments which land in exterior nodes
      instead of internal ones. The difference operation is special. It
      requires that you invert the polytope before insertion. For simple
      objects, this can be achieved by scaling with a factor of -1. The
      insertion process is then cinducted as an intersection operation,
      where segments landing in external nodes are discarded.       


  How do you perform collision detection with a BSP Tree?
      
      Overview
      Detecting whether or not a point moving along a line intersects
      some object in space is essentially a ray tracing problem.
      Detecting whether or not two complex objects intersect is
      something of a tree merging problem.
      
      Typically, motion is computed in a series of Euler steps. This
      just means that the motion is computed at discrete time intervals
      using some description of the speed of motion. For any given point
      P moving from point A with a velocity V, it's location can be
      computed at time T as P = A + (T * V).
      
      Consider the case where T = 1, and we are computing the motion in
      one second steps. To find out if the point P has collided with any
      part of the scene, we will first compute the endpoints of the
      motion for this time step. P1 = A + V, and P2 = A + (2 * V). These
      two endpoints will be classified with respect to the BSP tree. If
      P1 is outside of all objects, and P2 is inside some object, then
      an intersection has clearly occurred. However, if P2 is also
      outside, we still have to check for a collision in between.
      
      Two approaches are possible. The first is commonly used in
      applications like games, where speed is critical, and accuracy is
      not. This approach is to recursively divide the motion segment in
      half, and check the midpoint for containment by some object.
      Typically, it is good enough to say that an intersection occurred,
      and not be very accurate about where it occurred.
      
      The second approach, which is more accurate, but also more time
      consuming, is to treat the motion segment as a ray, and intersect
      the ray with the BSP Tree. This also has the advantage that the
      motion resulting from the impact can be computed more accurately.      


  How do you handle dynamic scenes with a BSP Tree?
      
      Overview
      So far the discussion of BSP tree structures has been limited to
      handling objects that don't move. However, because the hidden
      surface removal algorithm is so simple and efficient, it would be
      nice if it could be used with dynamic scenes too. Faster animation
      is the goal for many applications, most especially games.
      
      The BSP tree hidden surface removal algorithm can easily be
      extended to allow for dynamic objects. For each frame, start with
      a BSP tree containing all the static objects in the scene, and
      reinsert the dynamic objects. While this is straightforward to
      implement, it can involve substantial computation.
      
      If a dynamic object is separated from each static object by a
      plane, the dynamic object can be represented as a single point
      regardless of its complexity. This can dramatically reduce the
      computation per frame because only one node per dynamic object is
      inserted into the BSP tree. Compare that to one node for every
      polygon in the object, and the reason for the savings is obvious.
      During tree traversal, each point is expanded into the original
      object.
      
      Implementation notes
      Inserting a point into the BSP tree is very cheap, because there
      is only one front/back test at each node. Points are never split,
      which explains the requirement of separation by a plane. The
      dynamic object will always be drawn completely in front of the
      static objects behind it.
      
      A dynamic object inserted into the tree as a point can become a
      child of either a static or dynamic node. If the parent is a
      static node, perform a front/back test and insert the new node
      appropriately. If it is a dynamic node, a different front/back
      test is necessary, because a point doesn't partition three
      dimesnional space. The correct front/back test is to simply
      compare distances to the eye. Once computed, this distance can be
      cached at the node until the frame is drawn.
      
      An alternative when inserting a dynamic node is to construct a
      plane whose normal is the vector from the point to the eye. This
      plane is used in front/back tests just like the partition plane in
      a static node. The plane should be computed lazily and it is not
      necessary to normalize the vector.
      
      Cleanup at the end of each frame is easy. A static node can never
      be a child of a dynamic node, since all dynamic nodes are inserted
      after the static tree is completed. This implies that all subtrees
      of dynamic nodes can be removed at the same time as the dynamic
      parent node.

(note) I could'nt format this properly within the wiki and shall try to fix it at some other point in time.

DevMaster navigation