![]() |
Binary Space
Partitioning Tutorial |
![]() |
![]() |
written by Gary Simmons |
Camera Position | Correct Drawing Order |
Ca | D or E in any order, A , C & B is Unclear |
Cb | D or E in any order, A , B & C is Unclear |
Cc | C & B is Unclear , A , D or E in any order |
In case you dont know what a plane is , every polygon lies on an infinite plane.Get a piece of A4 paper and draw a small triangle in the middle of it.The triangle is the Polygon but the Paper is the Plane.The plane goes on forever (it has no edges like the paper) though but can you see that if you hold the paper up infront of you and start turning it to different angles the paper is always at the same angle as the triangle.In other words a plane is almost like a polygon but with NO edges around the outside.You can also see that if you are sat in your living room and the paper went on forever it would carve a line in the walls of your house.If you imagine that walls of your house are polygons you can see that after the wall has been split by the paper one half would lie to one side of the paper and the other half would lie the other side (front and back splitting). |
![]() |
Front List of split (A) | Back List of Split (A) | |
B , C | D , E | ||
Front List of split (B) | Back List of Split (B) | ||
Ca | Cb | ||
Front List of split (D) | Back List of Split (D) | ||
E | Nothing |
Note: The reason I keep reminding you that the polygons stored at each node are also the splitters is that it is possible to use any arbitrary planes as the splitters.For example, you may wish to devise a tree where all the splitters are planes constructed to be aligned to the principle world axis and in some instances this can be beneficial , but this usually causes more polygons to be split which raises the polygon count.Also its much easier to use the polygons as the splitting planes because we already have them. |
![]() |
First we choose polygon R as our splitter.This means that the polygons get divided into two lists (Front & Back).At this point the Front list contains polygons G,H,I,J,K,L,M and the Back list contains polygons A,B,C,D,E,F. So then first we take a look at splitter R's Front list.We then choose another polygon (G in our example) and make Polygon R point to polygon G with its front child pointer .We further split this list into front and back lists.There are no children to the back of polygon G so all the remaining polygons (H,I,J,K,L,M) are put into G's front list.We then choose another splitter (H) and point to this with Polygon G's front child pointer.When the front list is done for each splitter we then do the same with the back list.Study the BSP Tree diagram hard an make sure you can see exactly how the splitters are linked to each other.When you have this in your head carry on reading... |
struct BSPNode{
POLYGON
*splitter;
BSPNode *FrontChild;
BSPNode
*BackChild;
};
void RenderBSP (NODE *
CurrentNode)
{
int
Result;
Result=ClassifyPoint(CameraPosition,CurrentNode->Polygon);
if
(Result==Front)
{
if (CurrentNode->BackChild!=NULL) RenderBSP
(CurrentNode->BackChild);
DrawPolygon(CurrentNode->Polygon);
if
(CurrentNode->FrontChild!=NULL) RenderBSP
(CurrentNode->FrontChild);
}
else
{
if
(CurrentNode->FrontChild!=NULL) RenderBSP
(CurrentNode->FrontChild);
DrawPolygon(CurrentNode->Polygon);
if
(CurrentNode->BackChild!=NULL) RenderBSP
(CurrentNode->BackChild);
}
}
![]() |
We first call our function passing in our ROOT Node A. Are we in front of Node A? No we are behind it so examine Node A's front child.This points to node B.Is the camera in front of Node B ? No so check Node B's front Child.This is Node Ca(remember our compiler had to split this line because it straddled B's split line) of which there are NO front or back child Nodes so we render Polygon Ca.We then return to the function that was processing Node B.We draw Node B's polygon and then check Node B's Back child pointer which points to node Cb.This node has no front or back Child Nodes so we render polygon Cb which returns to Node B's function which itself is now finished with its work.So node B's function returns and we are now back in the original function call we made to A.We have rendered all the walls in front of A so now we Render Polygon A.We now step back and examine Node A's Back Child pointer which points to Node D.The camera is in front of Node D so we draw the Back Nodes first but there are none so we simply Draw Node D and then Check for any Nodes in front of D.There is one, Node E.Is the camera in front of E? Yes so draw back wall first but there aren't any so simply draw Polygon E and then check for Nodes that are in front of wall D.There aren't any so we are done and our level has been drawn in the correct order.WOW. |
void WalkBspTree(NODE *Node,D3DVECTOR *pos)
{
POLYGON
*shared;
int result=ClassifyPoint(pos,Node-> Splitter);
if
(result==CP_FRONT)
{
shared=Node->
Splitter->SameFacingShared;
if (Node-> Back!=NULL)
WalkBspTree(Node-> Back,pos);
lpDevice->
DrawIndexedPrimitive(D3DPT_TRIANGLELIST,D3DFVF_LVERTEX,&Node->
Splitter-> VertexList[0],Node-> Splitter->
NumberOfVertices,&Node-> Splitter->Indices[0],Node-> Splitter->
NumberOfIndices,NULL);
while
(shared!=NULL)
{
lpDevice->
DrawIndexedPrimitive(D3DPT_TRIANGLELIST,D3DFVF_LVERTEX,&shared->
VertexList[0],shared-> NumberOfVertices,&shared->
Indices[0],shared-> NumberOfIndices,NULL);
shared=shared->
SameFacingShared;
}
if (Node->Front!=NULL)
WalkBspTree(Node->Front,pos);
return ;
}
// this means we are at
back of node
shared=Node->Splitter->OppositeFacingShared;
if
(Node->Front!=NULL) WalkBspTree(Node->Front,pos);
while (shared!=NULL)
{
lpDevice->
DrawIndexedPrimitive(D3DPT_TRIANGLELIST,D3DFVF_LVERTEX,&shared->
VertexList[0],shared-> NumberOfVertices,&shared->
Indices[0],shared-> NumberOfIndices,NULL);
shared=shared->
OppositeFacingShared;
}
if (Node-> Back!=NULL)
WalkBspTree(Node->Back,pos);
return;
}
I have
highlighted the lines of interest.If we are in front of the current Node then we
Render the Back Tree first and then the Nodes polygon and also render all
polygons that are facing the same way.If we are Behind this node then we render
the front Tree but we DO NOT bother rendering the actual Nodes polygon because
we know we are behind so will be back face culled anyway.Then we render ALL
polygons that are facing the opposite way to the Node(in other words they are
front facing because the node itself is back facing.![]() |
Ok then, our compiler function is passed all the polygons A through M to compile.It choose splitter A as the first split and stores it in the Root Node.We then test all the polygons against polygon A so that they are put into the respective Front and Back Lists.Node A's front List will contain Polygons E through M and A's back list will contain polygons B through D.Lets just concentrate on polygon A's back list for now.The Compiler function creates a new node and calls itself with this new node and poygon A's back list B,C,D.The compiler then chooses another wall as a splitter from this list .Wall B in our example.This wall is then stored in the new Node we passed in to the function and linked to Node A's Back Pointer. |
![]() |
Now with Node B stored we then test the remaining walls in the list (C & D) against wall B and as you can see they both go in wall B's back list.There are no walls in front of Node B so instead of just setting it to NULL we create New node and add it to Node B's Front Pointer.This Node has no polygon though because it is a leaf.Instead we just set the variable 'IsLeaf' to true and because this leaf is in front of its parent node (B) we set 'IsSolid' to false.This is an empty space that can be walked in.If we traverse the tree and end up in this leaf then this means we are somewhere in the Blue box shown opposite.There are walls however in Node B's front list so the compiler function calls itself again once again passing in the Front list (walls C&D) and a newly created Node. |
![]() |
This time through the compiler function has a choice of either wall C or wall D as a splitter.It chooses wall C and stores it in the newly passed node (which was attached to B's Back pointer).There are no walls in front of C so once again a new leaf node is created and added to Node C's Front Pointer .Once again 'IsSolid' is set to false because it is in front of C and 'IsLeaf' is set to true so we know that this is leaf and no polygon is stored here.Node C does have a back wall though (D) so once again the function called itself passing in wall D as the polygon list and also creates a new node that is attached to C's Back Pointer.This is passed to the function also and will end up containing D as the splitter because it is the only one left in the list. |
![]() |
Now we choose splitter D because it is the only one left in the list.There are no front walls so once again a new leaf Node is created and attached to Node D's front pointer once again setting 'IsSolid' to false.If we end up in this leaf then we are somewhere within the green box shown opposite.However, there are no walls behind Node D either so once again we create a new leaf node and attach it to Node D's back list but because this leaf is BEHIND node D we set 'IsSolid' to true.If we end up in this leaf then we are in the soild area bounded by walls A,B,C,D and this is not allowed. Its important to realize how a leaf is bounded by its parent Nodes to create an atomic space.(convex Hull in other words).The function returns to Node C's function which in turn returns to Node B's function which ends up back at A (Root node and the first instance of the function that we called) where we have only processed the back list.Now we process Node A's front list which if you remember consited of walls E through M. |
![]() |
The compiler function now recursivly calls itself again with Node A's front list.We choose wall E as the splitter,store it in a new node and again attach it to Node A's front pointer.The remaining polygons are tested against wall E and we end up with a back list of polygon F & G and a front list for E containing polygons H through M.Lets have a look at the back list first.The compiler function calls itself passing in the back list of E and a new node and this time wall F is selected and stored in the new node and the new node is conected to Node E's Back pointer.There are no walls in front of wall F so once again a new leaf is created and added to Node F's Front pointer once again setting 'IsLeaf' to true and because this leaf node is in front of wall F once again 'IsSolid' is set to false because this is an empty space.If we end up in this leaf we are in the dark red box to the right of wall F. G is the last wall sent which has no front or back walls.Once again a front leaf is created as being empty and attached to G's front Pointer and a Solid leaf is created and attached to G's back pointer.This leaf represents the solid are bounded by walls E,F & G.You can now see the top solid area is now completely represented in the tree. |
![]() |
Here is this section finished being compiled.You step through the other splits in your head and make sure that you fully understand the THEORY of whats going on even if you do not know how to code it yet.The most important point to remember is that a splitter is not in ANY of its own lists.For example wall A may look like it is behind wall B but remember that wall B is a child of A and can only divide wall A's back subspace.Wall A is not in B's lists at all.Just like wall C is a child of B so wall C can not SEE B because it can only divide wall B's back sub space.This is the very core process to spacial sub division other wise the compiler could not section off bits of space. |
Note: The above technique is a simplified version of how collision detection and line of sight works.Ofcourse it does not allow for the fact that the point you are about to move to is empty but there may be something in between your current position and the position you are about to move to.We will cover this in detail later when we write our LineOfSight function and its still very easy to solve but I do not want to confuse you any more than you probably already are at the moment. |
![]() |
You can see that this polygon is made up of 5 vertices
but has to be broken into three triangles in order to be rendered.Although
this shape has 5 vertices it will need 9 indices to describe it to the
renderer because the renderer needs to render triangles and each triangle
needs 3 points.The nine indices would be as follows
v1,v2,v3,v1,v3,v4,v1,v4,v5.You can calculate the number of indices needed
with the following
equation:- NumberOfIndices=(NumberOfVertices-2)*3; This of course has nothing whatsoever to do with BSP Trees but I am explaining how our BSP Compiler expects to see the polygons represented. This polygon can be rendered using the following line:- lpDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST,D3DFVF_LVERTEX,pVertexList, 5 ,pIndices, 9 ,NULL); As you can see the above shape may render three triangles and that would normally mean 9 vertices have to be transformed and lit.However because we are using indices to describe the triangle only five vertices actually get transformed and lit and are re used by faces that share them.We will look at the code responsable for breaking up the polygons into multiple triangles later when we write our AddPolygon function which will add polygons to the scene. |
BYTE BSPMAP []= {0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 0,0,2,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0, 0,2,0,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0, 1,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,0,1, 0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1, 0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1, 0,1,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,3,1, 0,2,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,1, 1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,1, 0,1,0,0,0,0,1,2,0,0,0,1,0,0,0,1,0,0,0,1, 0,1,0,0,0,1,2,0,0,0,0,1,1,0,0,0,0,0,0,1, 0,1,0,0,0,1,0,0,0,0,0,3,1,0,0,0,0,0,0,1, 0,1,0,1,1,2,0,0,0,0,0,0,1,0,0,0,0,0,0,1, 1,2,0,0,0,0,0,0,1,0,0,0,1,1,1,1,0,0,0,1, 1,0,0,0,1,1,0,0,0,0,0,0,0,0,0,1,0,0,0,1, 1,0,0,1,2,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1, 1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}; |
With this array we are going to build a function that
will loop through each element of the array.One entry in the array
represents 1 unit squared of 3D World Space so when a 1 is encountered
with zeros all around it 4 polygons are created for the four walls of a
cube all facing outwards. If a number other than zero is to any side of
the 1 then a wall is not built for that side because it is covered up by
an adjacent wall.So in other words each 1 digit represent a 1x1 cube in
world space and we will texture the four walls (n,s,e & w) with some
brick wall texture.The 2 and 3's in the map were just put in there by me
at the last minute to give the map a few angles.A 2 digit represents a NE
facing wall and a 3 represents a NW facing wall.I didn't bother to create
SW or SE but you can do that yourself if you want.Just build them the same
as the NW and NE walls but reverse the winding order of the vertices.The
polygons are created in WORLD space with the center of space 0,0,0 being
in the middle of the map and each digit as I said represents 1x1 unit in
space.The full grid is 20x40 in dimensions (not all shown
here). Note:- For those of you reading this and thinking what a dodgy way to make a level you are of course right.But all I needed was an easy way to create lots of polygons to throw at the BSP Compiler.Also the last thing we need right now is a tutorial on how to import levels from various editors which actually is just as well because I have not got a clue. |
void
InitPolygons(void)
{
D3DLVERTEX VERTLIST[4][4];
PolygonList=NULL;//
this is a GLOBAL pointer
POLYGON *child=NULL;
int direction[4];
for
(int y=0;y< 40;y++)
{
for (int x=0;x<
20;x++)
{
ZeroMemory(direction,sizeof(int)*4);
int
offset=(y*20)+x;
// check what the digit is in the current map location
if
(BSPMAP[offset]!=0)
{
if (BSPMAP[offset]==2)// North East Wall
{
VERTLIST[0][0]=D3DLVERTEX(D3DVECTOR(x-10.5f,3.0f,(20.0f-y)-0.5f),RGB_MAKE(
255, 255,
255),0,0,0);
VERTLIST[0][1]=D3DLVERTEX(D3DVECTOR(x-9.5f,3.0f,(20.0f-y)+0.5f),RGB_MAKE(
255, 255,
255),0,1,0);
VERTLIST[0][2]=D3DLVERTEX(D3DVECTOR(x-9.5f,0.0f,(20.0f-y)+0.5f),RGB_MAKE(
255, 255,
255),0,1,1);
VERTLIST[0][3]=D3DLVERTEX(D3DVECTOR(x-10.5f,0.0f,(20.0f-y)-0.5f),RGB_MAKE(
255, 255, 255),0,0,1);
direction[0]=1;
}
if (BSPMAP[offset]==3)// North
West Wall
{
VERTLIST[0][0]=D3DLVERTEX(D3DVECTOR(x-10.5f,3.0f,(20.0f-y)+0.5f),RGB_MAKE(
255, 255,
255),0,0,0);
VERTLIST[0][1]=D3DLVERTEX(D3DVECTOR(x-9.5f,3.0f,(20.0f-y)-0.5f),RGB_MAKE(
255, 255,
255),0,1,0);
VERTLIST[0][2]=D3DLVERTEX(D3DVECTOR(x-9.5f,0.0f,(20.0f-y)-0.5f),RGB_MAKE(
255, 255,
255),0,1,1);
VERTLIST[0][3]=D3DLVERTEX(D3DVECTOR(x-10.5f,0.0f,(20.0f-y)+0.5f),RGB_MAKE(
255, 255, 255),0,0,1);
direction[0]=1;
}
if (BSPMAP[offset]==1)//
Its a Standared wall
{
if (x > 0)
{
if (BSPMAP[offset-1]==0)// if
theres nothing to the left add a left facing
wall
{
VERTLIST[0][0]=D3DLVERTEX(D3DVECTOR(x-10.5f,3.0f,(20.0f-y)+0.5f),RGB_MAKE(255,255,255),0,0,0);
VERTLIST[0][1]=D3DLVERTEX(D3DVECTOR(x-10.5f,3.0f,(20.0f-y)-0.5f),RGB_MAKE(255,255,255),0,1,0);
VERTLIST[0][2]=D3DLVERTEX(D3DVECTOR(x-10.5f,0.0f,(20.0f-y)-0.5f),RGB_MAKE(255,255,255),0,1,1);
VERTLIST[0][3]=D3DLVERTEX(D3DVECTOR(x-10.5f,0.0f,(20.0f-y)+0.5f),RGB_MAKE(255,255,255),0,0,1);
direction[0]=1;
}
}
if
(x < 19)
{
if (BSPMAP[offset+1]==0)// if there is nothing to the right
add a right facing
wall
{
VERTLIST[1][0]=D3DLVERTEX(D3DVECTOR(x-9.5f,3.0f,(20.0f-y)-0.5f),RGB_MAKE(255,255,255),0,0,0);
VERTLIST[1][1]=D3DLVERTEX(D3DVECTOR(x-9.5f,3.0f,(20.0f-y)+0.5f),RGB_MAKE(255,255,255),0,1,0);
VERTLIST[1][2]=D3DLVERTEX(D3DVECTOR(x-9.5f,0.0f,(20.0f-y)+0.5f),RGB_MAKE(255,255,255),0,1,1);
VERTLIST[1][3]=D3DLVERTEX(D3DVECTOR(x-9.5f,0.0f,(20.0f-y)-0.5f),RGB_MAKE(255,255,255),0,0,1);
direction[1]=1;
}
}
if
(y > 0)
{
if (BSPMAP[offset-20]==0)// if there is nothing south add a
south facing
wall
{
VERTLIST[2][0]=D3DLVERTEX(D3DVECTOR(x-9.5f,3.0f,(20.0f-y)+0.5f),RGB_MAKE(255,255,255),0,0,0);
VERTLIST[2][1]=D3DLVERTEX(D3DVECTOR(x-10.5f,3.0f,(20.0f-y)+0.5f),RGB_MAKE(255,255,255),0,1,0);
VERTLIST[2][2]=D3DLVERTEX(D3DVECTOR(x-10.5f,0.0f,(20.0f-y)+0.5f),RGB_MAKE(255,255,255),0,1,1);
VERTLIST[2][3]=D3DLVERTEX(D3DVECTOR(x-9.5f,0.0f,(20.0f-y)+0.5f),RGB_MAKE(
255, 255, 255),0,0,1);
direction[2]=1;;
}
}
if(y < 39)
{
if (BSPMAP[offset+20]==0)// if there is nothing to the north add a north
facing
wall
{
VERTLIST[3][0]=D3DLVERTEX(D3DVECTOR(x-10.5f,3.0f,(20.0f-y)-0.5f),RGB_MAKE(255,255,255),0,0,0);
VERTLIST[3][1]=D3DLVERTEX(D3DVECTOR(x-9.5f,3.0f,(20.0f-y)-0.5f),RGB_MAKE(255,
255,
255),0,1,0);
VERTLIST[3][2]=D3DLVERTEX(D3DVECTOR(x-9.5f,0.0f,(20.0f-y)-0.5f),RGB_MAKE(
255, 255,
255),0,1,1);
VERTLIST[3][3]=D3DLVERTEX(D3DVECTOR(x-10.5f,0.0f,(20.0f-y)-0.5f),RGB_MAKE(
255, 255, 255),0,0,1);
direction[3]=1;;
}
}
}// end for if
offset==1
// build the polygons
for (int
a=0;a<4;a++)
{
if (direction[a]!=0)
{
if
(PolygonList==NULL)
{
PolygonList=AddPolygon(NULL,&VERTLIST[a][0],4);
child=PolygonList;
}
else
{
child=AddPolygon(child,&VERTLIST[a][0],4);
}
}//
}////
}//
end for if offset!=0
}
}
BSPTreeRootNode=new
NODE;
BuildBspTree(BSPTreeRootNode,PolygonList);
}
POLYGON * AddPolygon(POLYGON* Parent,D3DLVERTEX
*Vertices,WORD NOV)
{
int loop;
POLYGON * Child=new
POLYGON;
Child-> NumberOfVertices=NOV;
Child->
NumberOfIndices=(NOV-2)*3;
Child-> Next=NULL;
for (loop=0;loop<
NOV;loop++)
{
Child->
VertexList[loop]=Vertices[loop];
}
//calculate
indices
WORD v0,v1,v2;
for (loop=0;loop< Child->
NumberOfIndices/3;loop++)
{
if
(loop==0)
{
v0=0;
v1=1;
v2=2;
}
else
{
v1=v2;
v2++;
}
Child->
Indices[loop*3]=v0;
Child-> Indices[(loop*3)+1]=v1;
Child->
Indices[(loop*3)+2]=v2;
}
Not very long this bit is it
when you think how clever it is.Can you see whats happening each time through
the loop? Get a pen and paper and try it drawing a square with four vertices (or
any convex shape) and have a look at the indices list you get.Actually forget
the pen and paper I will fire up my paint package and show you exactly what is
happening to our wall (square polygon) in this loop. ![]() |
You can see that the two triangles are created out of the square yet
only four Vertices are needed to describe them.The Indices list for our
wall would hold 6 entries like so:- v1,v2,v3,v1,v3,v4 Three Indicies a triangle so two triangles need six Indices. |
// generate polygon
normal
D3DVECTOR * vec0=(D3DVECTOR *)
&Child->VertexList[0];
D3DVECTOR * vec1=(D3DVECTOR *)
&Child->VertexList[1];
D3DVECTOR * vec2=(D3DVECTOR *)
&Child->VertexList[Child->NumberOfVertices-1];// the last
vert
D3DVECTOR edge1=(*vec1)-(*vec0);
D3DVECTOR
edge2=(*vec2)-(*vec0);
Child->Normal=CrossProduct(edge1,edge2);
Child->Normal=Normalize(Child->Normal);
if (Parent!=NULL)
{
Parent->Next=Child;
}
return
Child;
}
POLYGON *
AddPolygon(POLYGON* Parent,D3DLVERTEX *Vertices,WORD NOV)
{
int
loop;
POLYGON * Child=new POLYGON;
Child->
NumberOfVertices=NOV;
Child-> NumberOfIndices=(NOV-2)*3;
Child->
Next=NULL;
for (loop=0;loop< NOV;loop++)
{
Child->
VertexList[loop]=Vertices[loop];
}
//calculate indices
WORD v0,v1,v2;
for (loop=0;loop<
Child-> NumberOfIndices/3;loop++)
{
if
(loop==0)
{
v0=0;
v1=1;
v2=2;
}
else
{
v1=v2;
v2++;
}
Child->
Indices[loop*3]=v0;
Child-> Indices[(loop*3)+1]=v1;
Child->
Indices[(loop*3)+2]=v2;
}
// generate polygon
normal
D3DVECTOR * vec0=(D3DVECTOR *)
&Child->VertexList[0];
D3DVECTOR * vec1=(D3DVECTOR *)
&Child->VertexList[1];
D3DVECTOR * vec2=(D3DVECTOR *)
&Child->VertexList[Child->NumberOfVertices-1];// the last
vert
D3DVECTOR edge1=(*vec1)-(*vec0);
D3DVECTOR
edge2=(*vec2)-(*vec0);
Child->Normal=CrossProduct(edge1,edge2);
Child->Normal=Normalize(Child->Normal);
if (Parent!=NULL)
{
Parent->Next=Child;
}
return
Child;
}
BSPTreeRootNode=new
NODE;
BuildBspTree(BSPTreeRootNode,PolygonList);
int ClassifyPoint(D3DVECTOR *pos,POLYGON * Plane)
{
float
result;
D3DVECTOR *vec1=(D3DVECTOR *)&Plane->
VertexList[0];
D3DVECTOR
Direction=(*vec1)-(*pos);
result=DotProduct(Direction,Plane->Normal);
if
(result< -0.001) return CP_FRONT;
if (result> 0.001) return
CP_BACK;
return CP_ONPLANE;
int ClassifyPoly(POLYGON
*Plane,POLYGON * Poly)
{
int Infront=0;
int Behind=0;
int
OnPlane=0;
float result;
D3DVECTOR *vec1=(D3DVECTOR
*)&Plane->VertexList[0];
for (int
a=0;aNumberOfVertices;a++)
{
D3DVECTOR *vec2=(D3DVECTOR
*)&Poly->VertexList[a];
D3DVECTOR
Direction=(*vec1)-(*vec2);
result=DotProduct(Direction,Plane->Normal);
if
(result> 0.001)
{
Behind++;
}
else
if (result<
-0.001)
{
Infront++;
}
else
{
OnPlane++;
Infront++;
Behind++;
}
}
if
(OnPlane==Poly-> NumberOfVertices) return CP_FRONT;// this would nomrally be
CP_ONPLANE
if (Behind==Poly-> NumberOfVertices) return CP_BACK;
if
(Infront==Poly-> NumberOfVertices) return CP_FRONT;
return
CP_SPANNING;
}
Not a lot to explain here.It just
classifies each point in the Polygon with the Plane (which is passed in as a
polygon itself) and for each point behind,in front or on the plane a counter is
kept.At the end of the function if all the vertices are not on the plane and all
the vertices are not in front of the plane and all the vertices are not behind
the plane then it means that vertices must be on opposing sides of the plane
meaning a split will have to be performed.In this case the function returns
CP_SPANNING.This will tell our compiler function that the polygon needs to be
split.void SplitPolygon(POLYGON
*Poly,POLYGON *Plane,POLYGON *FrontSplit,POLYGON
*BackSplit);
![]() |
As you can see the left tree has exactly one node behind the root and one node in front of the root.The right most tree has no nodes in front of it but has two nodes behind it.One very important thing to notice here is that the left most tree has a depth of 1 but the right most tree actually has a depth of 2.By the depth I mean in the diagram the number of nodes vertically descending from the root node.The deeper a tree (the more nodes you have to traverse before you reach a node) the more time it takes to traverse the tree.Sometimes frame rate consistency is more important than out right speed.For example it is better to have a game engine run at 30 fps consistently throughout the level than have a level run at 90fps in some places and drop to 7fps in others.This is where the Balance of the tree comes into play.Although the balance of the tree does not effect OUR rendering routines because we will be using the painters algorithm to draw back to front which means every node will be visited anyway (until we implemement Fustrum rejection bounding boxes.more on this later), the balance will effect the collision detection routines we write later.Look at the right most example in the above picture.If we are in front of R we no longer have to check any walls because there are no front walls in Rs list.However if we are behind wall R we will have to descend into the tree and visit the nodes down the back list.Now imagine this simple example being converted to thousands of polygons and you can see that the frame rate would drop if we were behind wall R and speed up in front of wall R.The balanced tree above would be consistent because it would be equally deep at each leaf node. |
POLYGON *
SelectBestSplitter(POLYGON *PolyList)
{
POLYGON*
Splitter=PolyList;
POLYGON* CurrentPoly=NULL;
unsigned long
BestScore=100000;// just set to a very high value to begin
POLYGON *
SelectedPoly=NULL;
while
(Splitter!=NULL)
{
CurrentPoly=PolyList;
unsigned long
score,splits,backfaces,frontfaces;
score=splits=backfaces=frontfaces=0;
while
(CurrentPoly!=NULL)
{
if (CurrentPoly!=Splitter)
{
int
result=ClassifyPoly(Splitter,CurrentPoly);
switch ( result)
{
case
CP_ONPLANE:
break;
case CP_FRONT:
frontfaces++;
break;
case
CP_BACK:
backfaces++;
break;
case
CP_SPANNING:
splits++;
break;
default:
break;
}
}
CurrentPoly=CurrentPoly->
Next;
}// end while current
poly
score=abs(frontfaces-backfaces)+(splits*8);
if (score<
BestScore)
{
BestScore=score;
SelectedPoly=Splitter;
}
Splitter=Splitter->
Next;
}// end while splitter == null
return
SelectedPoly;
}
void
BuildBspTree(NODE * CurrentNode,POLYGON * PolyList)
{
POLYGON
*polyTest=NULL;
POLYGON *FrontList=NULL;
POLYGON
*BackList=NULL;
POLYGON *NextPolygon=NULL;
POLYGON
*FrontSplit=NULL;
POLYGON *BackSplit=NULL;
D3DVECTOR
vec1,vec2;
CurrentNode->
Splitter=SelectBestSplitter(PolyList);
polyTest=PolyList;
while
(polyTest!=NULL)
{
NextPolygon=polyTest-> Next;// remember because
polytest-> Next will be altered
if (polyTest!=CurrentNode->
Splitter)
{
switch (ClassifyPoly(CurrentNode->
Splitter,polyTest))
{
case CP_FRONT:
polyTest->
Next=FrontList;
FrontList=polyTest;
break;
case
CP_BACK:
polyTest-> Next=BackList;
BackList=polyTest;
break;
Nothing special with the first two cases.If the
polygon is behind or front of the splitter it is added to the appropriate
lists.Just in case you are confused by the switching around I will explain it.In
the case of CP_FRONT above, the Current Polygon being tested has its next
pointer set to point at the 'FrontList' pointer.This will be NULL the first time
a polygon is assigned to the front list.Then the FrontList pointer is altered to
point at the newly assigned polygon.In other words the new polygon is added to
the top of the list and the polygons 'Next' pointer is set to point at whatever
FrontList pointed at before so we still have a linked list.Notice that we have
changed the 'Next' Pointer of the polygon which we would normally use to look at
the next polygon in the global polygon list.We cannot do this now because it
only points to the back list.This is why we made a copy of this pointer above so
that we can still step through and access the rest of the polygons.case CP_SPANNING:
FrontSplit=new
POLYGON;
BackSplit=new
POLYGON;
ZeroMemory(FrontSplit,sizeof(POLYGON));
ZeroMemory(BackSplit,sizeof(POLYGON));
SplitPolygon(polyTest,CurrentNode->
Splitter,FrontSplit,BackSplit);
delete polyTest;
FrontSplit->
Next=FrontList;
FrontList=FrontSplit;
BackSplit->
Next=BackList;
BackList=BackSplit;
break;
default:
break;
}
}//
end if polypoint!=CurrentNodesplitter
polyTest=NextPolygon;
}// end while
loop
If the current polygon being tested does straddle the
splitter then we create two new polygons and zero the memory.We then send these
pointers along with the polygons that needs to be split and the Splitter it
needs to be split against to the 'SplitPolygon' function which will when returns
will have split the polygon into two halves and they will be pointed to by the
FrontSplit and BackSplit pointers.Interestingly enough we no longer need the
orginal polygon so we free up the memory.This may sound strange at first but
remember a polygon isn't in the tree until it is used as a splitter and I may
have been split many times by different splitters at this point.If a polygon is
split and then those to splits are split the first two splits not only are not
used by the tree anymore but we also loose pointers to them in memory so we must
free them up here while a valid temporary pointer to the unsplit polygon still
exists.The FrontSplit and BackSplit pointers which now point to the two newly
created splits are added to the front list and back list respectively.After this
polyTest is then reloaded with the orginal 'Next' pointers value which of course
is the next polygon in the input list.We do this loop for every polygon in the
input list (ignoring the splitter itself of course so this does not get added to
any list).if (FrontList==NULL)
{
NODE
*leafnode=new NODE;
ZeroMemory(leafnode,sizeof(leafnode));
leafnode->
IsLeaf=true;
leafnode-> IsSolid=false;
CurrentNode->
Front=leafnode;
}
else
{
NODE * newnode=new
NODE;
ZeroMemory(newnode,sizeof(newnode));
newnode->
IsLeaf=false;
CurrentNode->
Front=newnode;
BuildBspTree(newnode,FrontList);
}
if
(BackList==NULL)
{
NODE *leafnode=new
NODE;
ZeroMemory(leafnode,sizeof(leafnode));
leafnode->
IsLeaf=true;
leafnode-> IsSolid=true;
CurrentNode->
Back=leafnode;;
}
else
{
NODE * newnode=new
NODE;
ZeroMemory(newnode,sizeof(newnode));
newnode->
IsLeaf=false;
CurrentNode->
Back=newnode;
BuildBspTree(newnode,BackList);
}
}// end
function
void BuildBspTree(NODE * CurrentNode,POLYGON *
PolyList)
{
POLYGON *polyTest=NULL;
POLYGON *FrontList=NULL;
POLYGON
*BackList=NULL;
POLYGON *NextPolygon=NULL;
POLYGON
*FrontSplit=NULL;
POLYGON *BackSplit=NULL;
D3DVECTOR
vec1,vec2;
CurrentNode->
Splitter=SelectBestSplitter(PolyList);
polyTest=PolyList;
while (polyTest!=NULL)
{
NextPolygon=polyTest-> Next;//
remember because polytest-> Next will be altered
if
(polyTest!=CurrentNode-> Splitter)
{
switch
(ClassifyPoly(CurrentNode-> Splitter,polyTest))
{
case
CP_FRONT:
polyTest-> Next=FrontList;
FrontList=polyTest;
break;
case CP_BACK:
polyTest->
Next=BackList;
BackList=polyTest;
break;
case CP_SPANNING:
FrontSplit=new POLYGON;
BackSplit=new
POLYGON;
ZeroMemory(FrontSplit,sizeof(POLYGON));
ZeroMemory(BackSplit,sizeof(POLYGON));
SplitPolygon(polyTest,CurrentNode->
Splitter,FrontSplit,BackSplit);
delete polyTest;
FrontSplit->
Next=FrontList;
FrontList=FrontSplit;
BackSplit->
Next=BackList;
BackList=BackSplit;
break;
default:
break;
}
}//
end if polypoint!=CurrentNodesplitter
polyTest=NextPolygon;
}// end while
loop
if (FrontList==NULL)
{
NODE
*leafnode=new NODE;
ZeroMemory(leafnode,sizeof(leafnode));
leafnode->
IsLeaf=true;
leafnode-> IsSolid=false;
CurrentNode->
Front=leafnode;
}
else
{
NODE * newnode=new
NODE;
ZeroMemory(newnode,sizeof(newnode));
newnode->
IsLeaf=false;
CurrentNode->
Front=newnode;
BuildBspTree(newnode,FrontList);
}
if
(BackList==NULL)
{
NODE *leafnode=new
NODE;
ZeroMemory(leafnode,sizeof(leafnode));
leafnode->
IsLeaf=true;
leafnode-> IsSolid=true;
CurrentNode->
Back=leafnode;;
}
else
{
NODE * newnode=new
NODE;
ZeroMemory(newnode,sizeof(newnode));
newnode->
IsLeaf=false;
CurrentNode->
Back=newnode;
BuildBspTree(newnode,BackList);
}
}// end
function
bool
Get_Intersect (D3DVECTOR *linestart,D3DVECTOR *lineend,D3DVECTOR
*vertex,D3DVECTOR *normal,D3DVECTOR * intersection, float
*percentage)
{
D3DVECTOR direction,L1;
float
linelength,dist_from_plane;
direction.x=lineend->x-linestart->x;
direction.y=lineend->y-linestart->y;
direction.z=lineend->z-linestart->z;
linelength=DotProduct(direction,*normal);
if
(fabsf(linelength)<0.0001)
{
return
false;
}
L1.x=vertex->x-linestart->x;
L1.y=vertex->y-linestart->y;
L1.z=vertex->z-linestart->z;
dist_from_plane=DotProduct(L1,*normal);
*percentage=dist_from_plane/linelength;
if (*percentage<0.0f)
{
return false;
}
else
if
(*percentage>1.0f)
{
return
false;
}
intersection->x=linestart->x+direction.x*(*percentage);
intersection->y=linestart->y+direction.y*(*percentage);
intersection->z=linestart->z+direction.z*(*percentage);
return
true;
}
void SplitPolygon(POLYGON *Poly,POLYGON *Plane,POLYGON
*FrontSplit,POLYGON *BackSplit)
{
D3DLVERTEX
FrontList[20],BackList[20],FirstVertex;
D3DVECTOR
PlaneNormal,IntersectPoint,PointOnPlane,PointA,PointB;
WORD
FrontCounter=0,BackCounter=0,loop=0,CurrentVertex=0;
float percent;
// Find any vertex on the plane to use later in plane
intersect routine
PointOnPlane=*(D3DVECTOR
*)&Plane->VertexList[0];
The first thing we do above
is store the first vertex of the splitter polygon in the PointOnPlane vector.The
line may look a bit weird with all its casting etc but remember that the first
part of a D3DLVERTEX structure is just a vector so can be cast as such.We will
use this PointOnPlane variable later when we call the GetIntersect
routine.// first we find
out if the first vertex belongs in front or back
list
FirstVertex=Poly->VertexList[0];
switch (ClassifyPoint(
(D3DVECTOR *)&FirstVertex,Plane))
{
case
CP_FRONT:
FrontList[FrontCounter++]=FirstVertex;
break;
case
CP_BACK:
BackList[BackCounter++]=FirstVertex;
break;
case
CP_ONPLANE:
BackList[BackCounter++]=FirstVertex;
FrontList[FrontCounter++]=FirstVertex;
break;
default:
break;
}
As
you have probably gathered the whole purpose of this function is just to
classify edges of the polygon to the splitter and assigned them to the relative
lists.Once we have done this we we have two lists which we can then construct
the polygons out of.
![]() |
We have already assigned V0 to the front list in our
example with the preceeding code.Now we loop through vertices 1 though
0(again) and each edge being tested is the Current Vertex with the
previous.So for example we test point 1 & 0 then point 2 & 1
etc. This is how it works using the example opposite.We test v1 and v0 and discover they are both on the front side of the splitter so v1 gets added to the Front list (which already contains v0) and so all is done.Next we compare v2 with v1 and discover they are on opposing sides of the splitter so we call GetIntersect which will return the position of the new vertex on the split line (v1a opposite).This intersection is added to both lists and v2 is added to the back list. Front List so far contains : v0,v1,v1a Back List so far contains : v1a,v2. Now next time through the loop we test v3 with v2 and they are both behind the plane so v3 gets added to the back list.We then loop round to compare v4 with v3 and find another intersection.The intersection (v3a) is added to the back list and the front list and v4 is added to the front list also. Front List so far contains : v0,v1,v1a,v3a,v4 Back List so far contains : v1a,v2,v3,v3a We then loop through again but roll back over to test zero and the previous vertex (v4) there is no split they are both on the same side of the plane but because this is vertex zero which is already in the list it does not get added to any list and we are done.However had there been a split between v4 & v0 (lets call it v4a) then v4a would have been added to both front and back lists. |
for
(loop=1;loop<Poly->NumberOfVertices+1;loop++)
{
if
(loop==Poly->NumberOfVertices)
{
CurrentVertex=0;
}
else
{
CurrentVertex=loop;
}
PointA=*(D3DVECTOR
*)&Poly->VertexList[loop-1];
PointB=*(D3DVECTOR
*)&Poly->VertexList[CurrentVertex];
As you can see
we loop through the vertices of the polygon that needs to be split.The current
vertex to be worked on is equal to the current value of the loop unless it we
have rolled over the last vertex which means we set the current vertex to zero.
At this point we now know the two vertices that make up the edge we are going to
test.PointA a takes the previous vertex in the polygon (which would have already
been assigned to a list) and casts it to a D3DVECTOR so that we can use it with
all our functions and PointB casts the current vertex (not in any list yet) to a
D3DVECTOR.In other words, the first time through the loop PointA will hold
vertex zero and PointB will hold vertex one.This ofcourse makes up the first
edge.PlaneNormal=Plane->Normal;
int
Result=ClassifyPoint(&PointB,Plane);
if
(Result==CP_ONPLANE)
{
BackList[BackCounter++]=Poly->VertexList[CurrentVertex];
FrontList[FrontCounter++]=Poly->VertexList[CurrentVertex];
}
else
{
If
PointB is not on the plane then we test for an intersection with the plane.We
call GetIntersect passing in PointA,PointB and the Ray start and end and also
pass in the Point on the plane we got earlier (first vertex in the splitter) and
the Plane Normal(splitters Normal).If this function returns 'true' IntersecPoint
will hold the X,Y,Z of the new vertex created by the split that is on the plane
(v1a or v3a in the above example).We can create a new vertex out of this
vector.Also if the function returns 'true' the float 'percentage' will hold how
far down the ray from the start (as a percentage between 0 and 1) the
intersection occurred.We will use this for generating a NEW texture coordinate
for the NEW vertex.if
(Get_Intersect(&PointA,&PointB,&PointOnPlane,&PlaneNormal,&IntersectPoint,
&percent)==true)
{
If we pass the test and a
collision has occurred we already have the new vertex position in
'IntersectPoint'.When I First wrote my compiler the level was not texture
mapped.I simply created a new vertex out of the IntersectPoint returned and fely
very happy with my self until I tried to to texture the level and all the
textures screwed up.I forgot that the new vertex is going to need a new Texture
Coordinate.This was quite easy to overcome (after a bit of thought) which is why
the 'GetIntersect' function was modified to also return a percentage.Lets see
how it works.(I assume you are familiar with the Normalized Texture Coordinates
that D3D uses).![]() |
The picture opposite shows how a triangle polygon may be
mapped to a texture.You can see the texture coordinates for v1 and v2.The
red line shows where an intersection with the plane has occurred with this
polygon and infact shows the point that a new texture coordinate needs to
be created for.First of all we subtract the first vertex texture
coordintes from the second and we end up with the Vector Length of the
line between v1 and v2 on the texture.You can see opposite that
<0.8,0.7>-<0.4,0.2>=<0.4,0.5> which is the direction and
the length between texture coordinate 1 & 2.Now the great thing is
that our GetIntersect function returned a percentage from the start of the
line that the intersect occurred.We can reuse this value for texture
coordinates as well.For example imagine that the percentage returned by
GetIntersect is 0.5 meaning that the plane intersect the polygon exactly
half way between vertex 1 & vertex 2.If we multiply the Vector
opposite (vector length) by the percentage (0.5 in this example) we get a
vector of Now just add this vector so it is relative to the start point (the start of the ray or in our case the first vertices texture coordinates) New Texture Coordinates=<0.4 , 0.2> + <0.2 , 0.25>=<0.6 , 0.45> This same interpolation could also be used for calculating a Vertex light value as well. |
float
deltax,deltay,texx,texy;
deltax=Poly->VertexList[CurrentVertex].tu-Poly->VertexList[loop-1].tu;
deltay=Poly->VertexList[CurrentVertex].tv-Poly->VertexList[loop-1].tv;
texx=Poly->VertexList[loop-1].tu+(deltax*percent);
texy=Poly->VertexList[loop-1].tv+(deltay*percent);
D3DLVERTEX
copy=D3DLVERTEX(IntersectPoint,RGB_MAKE(255,255,255),0,texx,texy);
Now
we have the new Vertex and we know we have to add it to both front and back
polygon list (because it belongs in both the front split and the back split
polygons) but we also have to put the Current Vertex into the correct list
also.If PointB is in front of the splitter then we add the New Vertex to Both
lists and then add PointB to the Front List AS LONG AS the current vertex is NOT
vertex zero because this one would already be in the list.The same is true if
PointB is behind the splitter but the other way around obviously. if (Result==CP_FRONT
)
{
BackList[BackCounter++]=copy;
FrontList[FrontCounter++]=copy;
if
(CurrentVertex!=0)
{
FrontList[FrontCounter++]=Poly->VertexList[CurrentVertex];
}
}
if
(Result==CP_BACK)
{
FrontList[FrontCounter++]=copy;
BackList[BackCounter++]=copy;
if
(CurrentVertex!=0)
{
BackList[BackCounter++]=Poly->VertexList[CurrentVertex];
}
}
}// end if intersection (get
intersect==true)
else
{
if (Result==CP_FRONT)
{
if
(CurrentVertex!=0)
{
FrontList[FrontCounter++]=Poly->VertexList[CurrentVertex];
}
}
if
(Result==CP_BACK)
{
if
(CurrentVertex!=0)
{
BackList[BackCounter++]=Poly->VertexList[CurrentVertex];
}
}
}
}
}//end
loop through each edge
At this point in the function we
all the loops have ended and we have two lists of vertices.One for the front
split polygon and one for the back split polygon.The remainder of the function
simply builds the polygons using exactly the same technique as we used earlier
in our AddPolygon routine.It copies the Vertex Lists in to the Front and Back
split polygon structures, Calculates the indices (because remember even if a
triangle is split with a plane one of the splits will now have four vertices)
and cuilds the indices for each polygon.Then as a last step it generates the
polygon Normals for the two polygons.Heres the rest of the
function:-//OK THEN LETS BUILD
THESE TWO POLYGONAL BAD
BOYS
FrontSplit->NumberOfVertices=0;
BackSplit->NumberOfVertices=0;
for
(loop=0;loop<FrontCounter;loop++)
{
FrontSplit->NumberOfVertices++;
FrontSplit->VertexList[loop]=FrontList[loop];
}
for
(loop=0;loop<BackCounter;loop++)
{
BackSplit->NumberOfVertices++;
BackSplit->VertexList[loop]=BackList[loop];
}
BackSplit->NumberOfIndices=(BackSplit->NumberOfVertices-2)*3;
FrontSplit->NumberOfIndices=(FrontSplit->NumberOfVertices-2)*3;
// Fill in the Indices Array
WORD v0,v1,v2;
for
(loop=0;loop<FrontSplit->NumberOfIndices/3;loop++)
{
if
(loop==0)
{
v0=0;
v1=1;
v2=2;
}
else
{
v1=v2;
v2++;
}
FrontSplit->Indices[loop*3]=v0;
FrontSplit->Indices[(loop*3)+1]=v1;
FrontSplit->Indices[(loop*3)+2]=v2;
}
for
(loop=0;loop<BackSplit->NumberOfIndices/3;loop++)
{
if (loop==0)
{
v0=0;
v1=1;
v2=2;
}
else
{
v1=v2;
v2++;
}
BackSplit->Indices[loop*3]=v0;
BackSplit->Indices[(loop*3)+1]=v1;
BackSplit->Indices[(loop*3)+2]=v2;
}
// calculate polygon Normals
D3DVECTOR
edge1,edge2;
edge1=*(D3DVECTOR
*)&FrontSplit->VertexList[FrontSplit->Indices[1]]-*(D3DVECTOR
*)&FrontSplit->VertexList[FrontSplit->Indices[0]];
edge2=*(D3DVECTOR
*)&FrontSplit->VertexList[FrontSplit->Indices[FrontSplit->NumberOfIndices-1]]-*(D3DVECTOR
*)&FrontSplit->VertexList[FrontSplit->Indices[0]];
FrontSplit->Normal=CrossProduct(edge1,edge2);
FrontSplit->Normal=Normalize(FrontSplit->Normal);
edge1=*(D3DVECTOR
*)&BackSplit->VertexList[BackSplit->Indices[1]]-*(D3DVECTOR
*)&BackSplit->VertexList[BackSplit->Indices[0]];
edge2=*(D3DVECTOR
*)&BackSplit->VertexList[BackSplit->Indices[BackSplit->NumberOfIndices-1]]-*(D3DVECTOR
*)&BackSplit->VertexList[BackSplit->Indices[0]];
BackSplit->Normal=CrossProduct(edge1,edge2);
BackSplit->Normal=Normalize(BackSplit->Normal);
}
Phew,
that function was larger than the actual BSP Compiler function.But it is the
last function needed to make our BSP Compiler function work.Once again then ,
here is the Split Polygon Function in its entirety.void SplitPolygon(POLYGON *Poly,POLYGON *Plane,POLYGON
*FrontSplit,POLYGON *BackSplit)
{
D3DLVERTEX
FrontList[20],BackList[20],FirstVertex;
D3DVECTOR
PlaneNormal,IntersectPoint,PointOnPlane,PointA,PointB;
WORD
FrontCounter=0,BackCounter=0,loop=0,CurrentVertex=0;
float percent;
// Find any vertex on the plane to use later in plane
intersect routine
PointOnPlane=*(D3DVECTOR
*)&Plane->VertexList[0];
// first we find out if the first vertex belongs in front or back
list
FirstVertex=Poly->VertexList[0];
switch (ClassifyPoint(
(D3DVECTOR *)&FirstVertex,Plane))
{
case
CP_FRONT:
FrontList[FrontCounter++]=FirstVertex;
break;
case
CP_BACK:
BackList[BackCounter++]=FirstVertex;
break;
case
CP_ONPLANE:
BackList[BackCounter++]=FirstVertex;
FrontList[FrontCounter++]=FirstVertex;
break;
default:
break;
}
for (loop=1;loop<Poly->NumberOfVertices+1;loop++)
{
if
(loop==Poly->NumberOfVertices)
{
CurrentVertex=0;
}
else
{
CurrentVertex=loop;
}
PointA=*(D3DVECTOR
*)&Poly->VertexList[loop-1];
PointB=*(D3DVECTOR
*)&Poly->VertexList[CurrentVertex];
PlaneNormal=Plane->Normal;
int
Result=ClassifyPoint(&PointB,Plane);
if
(Result==CP_ONPLANE)
{
BackList[BackCounter++]=Poly->VertexList[CurrentVertex];
FrontList[FrontCounter++]=Poly->VertexList[CurrentVertex];
}
else
{
if
(Get_Intersect(&PointA,&PointB,&PointOnPlane,&PlaneNormal,&IntersectPoint,
&percent)==true)
{
float
deltax,deltay,texx,texy;
deltax=Poly->VertexList[CurrentVertex].tu-Poly->VertexList[loop-1].tu;
deltay=Poly->VertexList[CurrentVertex].tv-Poly->VertexList[loop-1].tv;
texx=Poly->VertexList[loop-1].tu+(deltax*percent);
texy=Poly->VertexList[loop-1].tv+(deltay*percent);
D3DLVERTEX
copy=D3DLVERTEX(IntersectPoint,RGB_MAKE(255,255,255),0,texx,texy);
if (Result==CP_FRONT
)
{
BackList[BackCounter++]=copy;
FrontList[FrontCounter++]=copy;
if
(CurrentVertex!=0)
{
FrontList[FrontCounter++]=Poly->VertexList[CurrentVertex];
}
}
if
(Result==CP_BACK)
{
FrontList[FrontCounter++]=copy;
BackList[BackCounter++]=copy;
if
(CurrentVertex!=0)
{
BackList[BackCounter++]=Poly->VertexList[CurrentVertex];
}
}
}// end if intersection (get
intersect==true)
else
{
if
(Result==CP_FRONT)
{
if
(CurrentVertex!=0)
{
FrontList[FrontCounter++]=Poly->VertexList[CurrentVertex];
}
}
if
(Result==CP_BACK)
{
if
(CurrentVertex!=0)
{
BackList[BackCounter++]=Poly->VertexList[CurrentVertex];
}
}
}
}
}//end
loop through each edge
//OK THEN LETS BUILD THESE TWO POLYGONAL BAD
BOYS
FrontSplit->NumberOfVertices=0;
BackSplit->NumberOfVertices=0;
for
(loop=0;loop<FrontCounter;loop++)
{
FrontSplit->NumberOfVertices++;
FrontSplit->VertexList[loop]=FrontList[loop];
}
for
(loop=0;loop<BackCounter;loop++)
{
BackSplit->NumberOfVertices++;
BackSplit->VertexList[loop]=BackList[loop];
}
BackSplit->NumberOfIndices=(BackSplit->NumberOfVertices-2)*3;
FrontSplit->NumberOfIndices=(FrontSplit->NumberOfVertices-2)*3;
// Fill in the Indices Array
WORD v0,v1,v2;
for
(loop=0;loop<FrontSplit->NumberOfIndices/3;loop++)
{
if
(loop==0)
{
v0=0;
v1=1;
v2=2;
}
else
{
v1=v2;
v2++;
}
FrontSplit->Indices[loop*3]=v0;
FrontSplit->Indices[(loop*3)+1]=v1;
FrontSplit->Indices[(loop*3)+2]=v2;
}
for
(loop=0;loop<BackSplit->NumberOfIndices/3;loop++)
{
if (loop==0)
{
v0=0;
v1=1;
v2=2;
}
else
{
v1=v2;
v2++;
}
BackSplit->Indices[loop*3]=v0;
BackSplit->Indices[(loop*3)+1]=v1;
BackSplit->Indices[(loop*3)+2]=v2;
}
// calculate polygon Normals
D3DVECTOR
edge1,edge2;
edge1=*(D3DVECTOR
*)&FrontSplit->VertexList[FrontSplit->Indices[1]]-*(D3DVECTOR
*)&FrontSplit->VertexList[FrontSplit->Indices[0]];
edge2=*(D3DVECTOR
*)&FrontSplit->VertexList[FrontSplit->Indices[FrontSplit->NumberOfIndices-1]]-*(D3DVECTOR
*)&FrontSplit->VertexList[FrontSplit->Indices[0]];
FrontSplit->Normal=CrossProduct(edge1,edge2);
FrontSplit->Normal=Normalize(FrontSplit->Normal);
edge1=*(D3DVECTOR
*)&BackSplit->VertexList[BackSplit->Indices[1]]-*(D3DVECTOR
*)&BackSplit->VertexList[BackSplit->Indices[0]];
edge2=*(D3DVECTOR
*)&BackSplit->VertexList[BackSplit->Indices[BackSplit->NumberOfIndices-1]]-*(D3DVECTOR
*)&BackSplit->VertexList[BackSplit->Indices[0]];
BackSplit->Normal=CrossProduct(edge1,edge2);
BackSplit->Normal=Normalize(BackSplit->Normal);
}
void WalkBspTree(NODE *Node,D3DVECTOR
*pos)
{
if (Node->IsLeaf==true) return;
int
result=ClassifyPoint(pos,Node->Splitter);
if (result==CP_FRONT)
{
if
(Node->Back!=NULL)
WalkBspTree(Node->Back,pos);
lpDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST,D3DFVF_LVERTEX,&Node->Splitter->VertexList[0],Node->Splitter->NumberOfVertices,&Node->Splitter->Indices[0],Node->Splitter->NumberOfIndices,NULL);
if
(Node->Front!=NULL) WalkBspTree(Node->Front,pos);
return ;
} // this
happens if we are at back or on plane
if (Node->Front!=NULL)
WalkBspTree(Node->Front,pos);
if (Node->Back!=NULL)
WalkBspTree(Node->Back,pos);
return;
}
bool LineOfSight (D3DVECTOR
*Start,D3DVECTOR *End, NODE *Node)
{
float temp;
D3DVECTOR
intersection;
if (Node->IsLeaf==true)
{
return
!Node->IsSolid;
}
int
PointA=ClassifyPoint(Start,Node->Splitter);
int
PointB=ClassifyPoint(End,Node->Splitter);
if (PointA==CP_ONPLANE
&& PointB==CP_ONPLANE)
{
return
LineOfSight(Start,End,Node->Front);
}
if (PointA==CP_FRONT
&& PointB==CP_BACK)
{
Get_Intersect (Start,End,(D3DVECTOR *)
&Node->Splitter->VertexList[0],&Node->Splitter->Normal,&intersection,&temp);
return
LineOfSight(Start,&intersection,Node->Front) &&
LineOfSight(End,&intersection,Node->Back) ;
}
if
(PointA==CP_BACK && PointB==CP_FRONT)
{
Get_Intersect
(Start,End,(D3DVECTOR *)
&Node->Splitter->VertexList[0],&Node->Splitter->Normal,&intersection,&temp);
return
LineOfSight(End,&intersection,Node->Front) &&
LineOfSight(Start,&intersection,Node->Back) ;
}
// if we
get here one of the points is on the plane
if (PointA==CP_FRONT ||
PointB==CP_FRONT)
{
return
LineOfSight(Start,End,Node->Front);
}
else
{
return
LineOfSight(Start,End,Node->Back);
}
return
true;
}
With a little help from the GetIntersect
function the above code sorts out the whole collision detection thing in just a
couple of lines of code by just recursively calling itself until every section
of the reay ends up in a leaf (either solid or empty).It's quick too.