DevMaster.net 
[[ Home  Forums  3D Engines Database  Articles/Tutorials  Game Dev Jobs  IRC Chat Network  Contact Us ]] 
BSP Trees: Theory and Implementation  
Samuel RantaEskola
10/09/2003

Introduction
When the original design of the algorithm for Binary Space Partitioning (BSP)trees was formulated the idea was to use it to sort the polygons in the world. The reason for this was there did not exist hardware accelerated Zbuffers, and software Zbuffering was too slow. Today that area of usage is obsolete, since hardware accelerated Zbuffers exist. Instead the usage is to optimize a wide variety of areas, such as radiosity calculations, drawing of the world, collision detection and networking. Binary Space Partioning (BSP)trees were first described in 1969 by Shumacker et al[1]., it was hardly meant to be an algorithm used to develop entertainment products, but since the beginning of the 90’s BSPtrees have been used in the gaming industry to improve performance and make it possible to use more details in the maps[*]. The first game ever to use the technology is Doom, created by John Carmack and John Romero, two legends in the gaming industry[2]. Since then almost all First Person Shooting (FPS[*]) games have been using that technique. We set out to examine the areas where one can draw advantages of the structure supplied and study the generating process. A BSPtree is a very useful structure in most game engines. Although there are some drawbacks with it, such as that it is static and it is very expensive to modify during runtime. Hopefully some ideas can be taken from the BSPtree algorithm to develop a more dynamic structure that has the same advantages as the BSPtree. BSP Trees: BackgroundA Binary Space Partitioningtree is a structure that, as the name suggests, subdivides the space into smaller sets. These days, given hardware accelerated Zbuffers; the benefit of this is that one has a smaller amount of data to consider given a location in space. But in the beginning of the 90’s the main reason BSPtrees were being used was that they sorted the polygons in the scene so that you always drew backtofront, meaning that the polygon with the lowest Zvalue[*] was drawn last. There are other ways to sort the polygons so that the closest polygon is drawn last, for example the Painter’s algorithm[3], but few are as cheap as BSPtrees, because the sorting of the polygons is done during the preprocessing[*] of the map and not under runtime. The algorithm for generating a BSPtree is actually an extension of Painter’s algorithm[5]. Just as the original design of the BSP algorithm, the Painter’s algorithm works by drawing the polygons in the scene in backtofront order. However, there are some major drawbacks with Painter’s algorithm:
The BSP AlgorithmThe original idea for the creation of a BSPtree is that you take a set of polygons that is part of a scene and divide them into smaller sets, where each subset is a convex set of polygons. That is, each polygon in this subset is in front of every other polygon in the same set. Polygon 1 is in front of polygon 2 if each vertex in polygon 1 is on the positive side of the plane polygon 2 defines or in that plane that. A cube made of inward facing polygons is a convex set, whilst a cube made of outwards facing polygons is not.
The functions needed to determine whether a set of polygons is a convex set would look like this: ► CLASSIFYPOINT // Indata: // Polygon – The polygon to classify the point versus. * Point  3Dpoint to classify versus the plane defined by the polygon. * Outdata: * Which side the point is of the polygon. * Effect: * Determines on which side of the plane defined by the polygon the point is located. CLASSIFYPOINT (Polygon, Point) 1 SideValue = Polygon.Normal * Point 2 if (SideValue = Polygon.Distance) 3 then return COINCIDING 4 else if (SideValue < Polygon.Distance) 5 then return BEHIND 6 else return INFRONT ► POLYGONINFRONT * Indata: * Polygon1 – The polygon to determine whether the other polygon is in front of or not * Polygon2 – The polygon to check if it is in front of the first polygon or not * Outdata: * Whether the second is in front of the first polygon or not. * Effect: * Checks each point in the second polygon is in front of * the first polygon. If so is the case it is considered * to be in the front of it. POLYGONINFRONT (Polygon1, Polygon2) 1 for each point p in Polygon2 2 if (CLASSIFYPOINT (Polygon1, p) <> INFRONT) 3 then return false 4 return true ► ISCONVEXSET * Indata: * PolygonSet – The set of polygons to check for convexity * Outdata: * Whether the set is convex or not * Effect: * Checks each polygon against each other polygon, to see if they are * in front of each other, if any two polygons doesn’t fulfill that * criteria the set isn’t convex. ISCONVEXSET (PolygonSet) 1 for i f 0 to PolygonSet.Length () 2 for j f 0 to PolygonSet.Length () 3 if(i <> j && not POLYGONINFRONT(PolygonSet[i], PolygonSet[j])) 4 then return false 5 return true The function POLYGONINFRONT is a nonsymmetric comparison, meaning that if Polygon2 is in front of Polygon1 it does not necessarily imply that Polygon1 is in front of Polygon2. This can easily be shown with the following example:
In Figure 3 Polygon1 is in front of Polygon2 since both p3 and p4 is on the positive side of Polygon2, but Polygon2 is not in front of Polygon1 since p2 is behind Polygon1. The idea can be slightly modified as the need of convex sets is not as acute when you can use hardware accelerated Zbuffers. Later in this series it will be described how this can be solved. The structures needed for a BSPtree can be defined as follows: class BSPTree { BSPTreeNode RootNode // The root node of the tree. } class BSPTreeNode { BSPTree Tree // The tree this node belongs to. BSPTreePolygon Divider // The polygon that lies in middle w of the two sub trees. BSPTreeNode *RightChild // The right sub tree of this node. BSPTreeNode *LeftChild // The left sub tree of this node. BSPTreePolygon PolygonSet[] // The set of polygons in this node. } class BSPTreePolygon { 3DVector Point1 // Vertex 1 in the polygon. 3DVector Point3 // Vertex 2 in the polygon. 3DVector Point3 // Vertex 3 in the polygon. } As you can see each polygon is represented by only three points. This is because the hardware in graphic cards is designed to draw triangles. But the algorithm for generating BSPtrees is designed to take care of polygons with any number of points, as long as all points are in the same plane. There are several ways to split up the set of polygons into smaller subsets. For example, you can choose an arbitrary plane in space and divide the polygons by putting the ones on the positive side of the plane in the right sub tree and the polygons on the negative side in the left sub tree. The problem with this approach is that it is very difficult to find a plane that divides the polygons into two approximately equally sized sets, since there are an infinite set of planes in space. So the most common way to do this is by taking one of the polygons in the scene and dividing the polygons according to the plane that polygon defines. We have defined an algorithm, POLYGONINFRONT, which can classify whether a polygon is on the positive side of another polygon. Now we need to modify that algorithm to be able to also determine whether the polygon is spanning the plane defined by the other polygon. The algorithm is defined as follows: ► CALCULATESIDE * Indata: * Polygon1 – The polygon to classify the other polygon against * Polygon2 – The polygon to classify * Outdata: * Which side of polygon1 polygon 2 is located on. * Effect: * Classifies each point in the second polygon versus the * first polygon. If there are points on the positive side but no * points on the negative side, Polygon2 is considered to be in front * of Polygon1. If there are points on the negative side but no * points on the positive side, Polygon2 is considered to be behind * Polygon1. If all points are coinciding polygon2 is considered to * be coinciding with Polygon1. The last possible case is that there * are points on both the positive and the negative side, then * polygon2 is considered to be spanning Polygon1. CALCULATESIDE (Polygon1, Polygon2) 1 NumPositive = 0, NumNegative = 0 2 for each point p in Polygon2 3 if (CLASSIFYPOINT (Polygon1, p) = INFRONT) 4 then NumPositive = NumPositive + 1 5 if (CLASSIFYPOINT (Polygon1, p) = BEHIND) 6 then NumNegative = NumNegative + 1 7 if (NumPositive > 0 && NumNegative = 0) 8 then return INFRONT 9 else if(NumPositive = 0 && NumNegative > 0) 10 then return BEHIND 11 else if(NumPositive = 0 && NumNegative = 0) 12 then return COINCIDING 13 else return SPANNING This gives us a problem when it comes to determining which subset a polygon that is spanning the plane should be placed in. The algorithm deals with this by splitting such a polygon into two polygons. This also solves two of the problems in Painter’s algorithm, namely cyclic overlap and intersecting polygons. Below is example of how a polygon is splitted:
In the figure above Polygon1 is the classifying polygon and Polygon2 is the polygon that is classified. Since Polygon2 is spanning the plane defined by Polygon1 it has to be splitted. The result is the picture to the right. Polygon2 is now completely in front of Polygon1 and Polygon3 is completely behind. The glitch between Polygon2 and Polygon3 is just there to illustrate that it is two separate polygons, after a split the two resulting polygons will be adjacent to each other. When a BSPtree is created, one has to decide whether the need is of a balanced tree, meaning that there should not be too big a difference in depth between the left and the right sub tree of each node, or try to limit the number of splits, since each split creates new polygons. If too many new polygons is created during the BSPtree creation the graphic card will have a hard time rendering the map, thus reducing the frame rate, while a unbalanced tree will require more expensive traversal of the tree. We decided to accept a certain number of splits in order to get a more balanced tree. But the main concern was reducing the number of new polygons created. Below is our loop for choosing the best dividing polygon from a set of polygons: ► CHOOSEDIVIDINGPOLYGON * Indata: * PolygonSet – The set of polygons to search for the best dividing polygon. * Outdata: * The best dividing polygon * Effect: * Searches through the set of polygons and returns the polygons that * splits the set into the two best resulting sets. If the set is * convex no polygon can be returned. CHOOSEDIVIDINGPOLYGON (PolygonSet) 1 if (ISCONVEXSET (PolygonSet)) 2 then return NOPOLYGON 3 MinRelation = MINIMUMRELATION 4 BestPolygon = NOPOLYGON 5 LeastSplits = INFINITY 6 BestRelation = 0 // Loop to find the polygon that best divides the set. 7 while(BestPolygon = NOPOLYGON) 8 for each polygon P1 in PolygonSet 9 if (Polygon P1 has not been used as divider previously during the creation of the tree) // Count the number of polygons on the positive side, negative side // of and spanning the plane defined by the current polygon. 10 NumPositive = 0, NumNegative = 0, NumSpanning = 0 11 for each polygon P2 in PolygonSet except P1 12 Value = CALCULATESIDE(P1, P2) 13 if(Value = INFRONT) 14 NumPositive = NumPositive + 1 15 else if(Value = BEHIND) 16 NumNegative = NumNegative + 1 17 else if(Value = SPANNING) 18 NumSpanning = NumSpanning + 1 // Calculate the relation between the number of polygons in the two // sets divided by the current polygon. 19 if (NumPositive < NumNegative) 20 Relation = NumPositive / NumNegative 21 else 22 Relation = NumNegative / NumPositive // Compare the results given by the current polygon to the best this // far. If the this polygon splits fewer polygons and the relation // between the resulting sets is acceptable this is the new candidate // polygon. If the current polygon splits the same amount of polygons // as the best polygon this far and the relation between the two // resulting sets is better > this polygon is the new candidate // polygon. 23 if (Relation > MinRelation && (NumSpanning < LeastSplits  NumSpanning = LeastSplits && Relation > BestRelation)) 24 BestPolygon = P1 25 LeastSplits = NumSpanning 26 BestRelation = Relation // Decrease the number least acceptable relation by dividing it with // a predefined constant. 27 MinRelation = MinRelation / MINRELATIONSCALE 28 return BestPolygon Complexity AnalysisBecause of the while loop it is very hard to find a bound to this function. Depending of the structure of the scene the while loop might loop for a very long time. The MINRELATIONSCALE is what decides how much the acceptable relation decreases per iteration, thus how long it will take before the minimum relation will be small enough to accept the best possible solution. The worst case is that we have a set consisting of n polygons that is not a convex set and the best possible solution is a dividing polygon that splits the set into one part consisting of n1 polygons and another set consisting of 1 polygon. This solution will only be acceptable when the minimal acceptable relation is less than 1/(n1) (see line 1923 in the algorithm). Meaning that MinRelation / MINRELATIONSCALE^{i} < 1/(n1) where i is the number of iterations in the loop, this is due the division by MINRELATIONSCALE at line 27 in the algorithm. Let’s assume that the initial value for MinRelation is 1, which is the highest possible value since the relation is always between 0 and 1 (see lines 1922 in the algorithm). We have: 1 / MINRELATIONSCALE^{i} < 1/(n1) This is no upper bound for i, but since i will be very close to log_{MINRELATIONSCALE} (n1) we will, for simplicity assume they are equal. Another practical assumption to make is that MINRELATIONSCALE always should be greater than or equal to 2. Thus giving us: log_{MINRELATIONSCALE} (n1) = i
MINRELATIONSCALE >= 2 Inside the while loop, there are two iterations over the set of polygons. Giving us that the worst case behavior of this algorithm is of order O(n^{2} lg n), but the typical behavior is almost always closer to O(n^{2}) as there tend to exist a polygon that will fulfill the requirements in the first iteration. The loop in CHOOSEDIVIDINGPOLYGON might look as if there are cases where it will not terminate, but this is not true since if the set of polygons is a nonconvex set there is always one polygon that can divide the polygons into two sets. CHOOSEDIVIDINGPOLYGON selects the polygon that splits the least number of polygons. To prevent from choosing polygons that would not divide the set, the relation between the sizes of the two resulting sets must be better than a threshold value. To better illustrate this we show an example where choosing the polygon that splits the fewest amount of polygons would render in an infinite loop:
In the above example choosing either polygon 1,6,7 or 8 would not render in the split of any polygon, but on the other hand each of the polygons in the set is on the positive side of those polygons, so in the next loop the same polygon would be chosen again, rendering in a infinite loop. As a matter of fact 1,2,3 and 4 is on the border of the least convex hull that can hold the polygon set, polygons for which this is true cannot be used as a dividing polygon since all other polygons in the set is on the positive side of them. Choosing polygon 2,3,4 or 5 would each cause one split but it would also divide the set into two smaller sets. Another reason why a it is not always good to choose the polygon that splits the fewest polygons is that in most cases that heuristic will render in a unbalanced set. A balanced tree will perform better during runtime than an unbalanced one. When the best polygon has been chosen the rest of the polygons is divided according to that polygon. There are two ways to do deal with the dividing polygon:
The algorithm for generating a leafy BSPtree looks like this: ► GENERATEBSPTREE * Indata: * Node  The sub tree to build of the type BSPTreeNode. * PolygonSet – The set of polygons to create a BSPtree from. * Outdata: * A BSPtree stored in the incoming node. * Effect: * Generates a BSPtree out of a set of polygons. GENERATEBSPTREE (Node, PolygonSet) 1 if (ISCONVEXSET (PolygonSet)) 2 Tree = BSPTreeNode (PolygonSet) 3 Divider = CHOOSEDIVIDINGPOLYGON (PolygonSet) 4 PositiveSet = {} 5 NegativeSet = {} 6 for each polygon P1 in PolygonSet 7 Value f CALCULATESIDE (Divider, P1) 8 if(Value = INFRONT) 9 PositiveSet = PositiveSet U P1 10 else if (Value = BEHIND) 11 NegativeSet = NegativeSet U P1 12 else if (Value = SPANNING) 13 Split_Polygon (P1, Divider, Front, Back) 14 PositiveSet = PositiveSet U Front 15 NegativeSet = NegativeSet U Back 16 GENERATEBSPTREE (Tree.RightChild, PositiveSet) 17 GENERATEBSPTREE (Tree.LeftChild, NegativeSet) Complexity AnalysisThe call to CHOOSEDIVIDINGPOLYGON is of order O(n^{2} lg n), which dominates the rest of the function except for the recursive calls. If we assume that the division of the polygon set is fairly even we can formulate the following function to calculate the bounds of GENERATEBSPTREE: T(n) = 2T(n/2) + O(n^{2} lg n) Using Masters Theorem[5] we get that the order of complexity is Θ (n^{2} lg n), where n is the number of polygons in the incoming set. Following there is an example of how a BSPtree is generated. The structure below is the original set of polygons, we have numbered them to make the example easier to follow. This set of polygons is going to be divided into a BSPtree.
To be able to run the algorithm we must choose a couple of constants, namely: MINIMUMRELATION and MINRELATIONSCALE. We found that choosing MINIMUMRELATION = 0.8 and MINRELATIONSCALE = 2 will give quite good result, but one can experiment we those numbers. The higher the MINIMUMRELATION is the better balanced the tree will be, but the number of splitted polygons will probably increase too. The starting set of polygons is obviously not a convex set, so a dividing polygon will be chosen. After a quick glance at the structure we can see that polygons {1,2,6,22,28} cannot be used as dividing polygons since they define convex hull that contains the whole set of polygons. But all the other polygons are candidates for being dividing polygon. The polygons that split the fewest number of polygons and give the best relation between the sizes of the two resulting sets are 16 and 17, they lie on the same line and do not split any other polygon. The two resulting sets is almost equally sized namely negative= 15 and positive = 13 polygons in each of the resulting sets. Let us choose polygon 16 as the divider. The result will look as follows:
Neither the right nor the left sub tree contains a convex set of polygons so a new dividing polygon must be chosen in both. In the left sub tree {1,2,6,7} is on the convex hull so they cannot be used as dividers. Polygon 4 and 10 is on the same line and they do not split any other polygon. The sizes of the resulting sets is negative= 7 and positive = 8 which is very balanced. We choose 4 as the divider. {16,17,22,23,28} contains the right sub tree, so they will not be dividers. The polygons that will not split any other polygons are {18,19,27,26} but the sizes of the resulting sets for all of them will be negative= 3 and positive = 11, 3/11 is below the minimum relation(0.5) so we will have to check the other polygons to see if they can provide us with a more balanced solution. Each of {20,21,24,25} splits exactly one polygon, but the most balanced set is attained by polygon 21, which after splitting polygon 22 produces resulting sets of size negative= 6 and positive = 8. The following shows the result after these operations:
None of the sub trees contain a convex set of polygons so the algorithm will move on in the same manner; the resulting tree will look like this:
Even though it is not the optimal solution it is quite close to it and it does not take that long time. Drawing the BSP TreeNow that the BSPtree is created it is very easy to draw the polygons the tree, with zero chance of failure in the drawing. Below the algorithm of that process is described. We assume there is a function ISLEAF that given a BSPnode it returns true if that node is a leaf otherwise false. ► DRAWBSPTREE * Indata: * Node – The node to draw. * Position – The viewer’s position. * Outdata: * None * Effect: * Draws the polygons contained in the node and its sub trees. DRAWBSPTREE (Node, Position) 1 if (ISLEAF(Node)) 2 DRAWPOLYGONS (Node.PolygonSet) 3 return // Calculate which sub tree the viewer is in. 4 Side = CLASSIFYPOINT (Node.Divider, Position) // If the viewer is in the right sub tree draw that tree before the // left. 5 if (Side = INFRONT  Side = COINCIDING) 6 DRAWBSPTREE (Node.RightChild, Position) 7 DRAWBSPTREE (Node.LeftChild, Position) // Otherwise draw the left first. 8 else if(Value = BEHIND) 9 DRAWBSPTREE (Node.LeftChild, Position) 10 DRAWBSPTREE (Node.RightChild, Position) This way of drawing gives us no reduction in number of polygons that is drawn to the screen. Since a map can consist of hundreds of thousands of polygons, it is no good solution. In some way nodes that are not visible or even polygons that are not visible should be discarded. This is called hidden surface removal. There are several ways to do this; we will explain some of them in the next series article. Related Readings
References
GlossaryFPS First person shooter, a game viewed in first person perspective, where the goal is to eliminate the opponents. Map An object that contains the geometry of the world. BSPtree Binary Space Partioning tree, a tree structure used to divide a map into smaller parts. ZValue This is a measurement used to classify how close a polygon is to the viewer’s position. Frame rate The number of times the screen is updated per second. This has nothing to do with the refresh rate of the monitor. It is the number of times the world is drawn per second. Usually this should be above 30 times/sec to give a continuous feeling. Preprocessing Calculations that are done before runtime, thus saving valuable CPU time at run time that can be used for other things. Polygon A polygon is a manysided planar figure composed of vertices and edges. Triangles, squares, hexagons, and pentagons are examples of polygons that have names, but any closed series of line segments forms a polygon. [Unknown Author, Basic Math FAQ]. To be meaningful in this area of usage all vertices must be on the same plane, i.e. 2dimensional. Plane equation The mathematic formula for a 3 dimensional plane. Ax+By+Cz+D, where AD is the constant coefficients of the equation, AC is the normal of the plane and D is the distance from origin in the direction of the normal to the plane. Node A part of a tree. Each node consists of a left and a right sub tree. 

© 20032004 DevMaster.net. All Rights Reserved. Terms of Use & Privacy Policy  Want to write for us? Click here 