Skip Navigation
Resources
Group Information

Hint Brush Tutorial

Okey doke, where to begin?

Well, let's start at the very beginning, a very good place to start, as the song goes.

Before looking at what the compiler does, we'll take a look inside a .map file.


{"classname" "worldspawn"

// brush 0
{
( 8 256 192 ) ( -320 256 192 ) ( -320 0 192 ) evil6_bmtls/e6bmetal 0 0 0 0.500000 0.500000 134217728 0 0( -320 0 208 ) ( -320 256 208 ) ( 8 256 208 ) common/caulk 0 0 0 0.500000 0.500000 134217728 0 0( -320 0 320 ) ( 8 0 320 ) ( 8 0 192 ) common/caulk 0 0 0 0.500000 0.500000 134217728 0 0( 0 0 320 ) ( 0 256 320 ) ( 0 256 192 ) common/caulk 0 0 0 0.500000 0.500000 134217728 0 0( 8 256 320 ) ( -320 256 320 ) ( -320 256 192 ) common/caulk 0 0 0 0.500000 0.500000 134217728 0 0( -320 256 320 ) ( -320 0 320 ) ( -320 0 192 ) common/caulk 0 0 0 0.500000 0.500000 134217728 0 0}

// brush 1
{
( 8 256 48 ) ( -320 256 48 ) ( -320 0 48 ) common/caulk 0 0 0 0.500000 0.500000 134217728 0 0( -320 0 64 ) ( -320 256 64 ) ( 8 256 64 ) evil6_floors/e6c_floor_b 0 0 0 0.500000 0.500000 134217728 0 0( -320 0 176 ) ( 8 0 176 ) ( 8 0 48 ) common/caulk 0 0 0 0.500000 0.500000 134217728 0 0( 0 0 176 ) ( 0 256 176 ) ( 0 256 48 ) common/caulk 0 0 0 0.500000 0.500000 134217728 0 0( 8 256 176 ) ( -320 256 176 ) ( -320 256 48 ) common/caulk 0 0 0 0.500000 0.500000 134217728 0 0( -320 256 176 ) ( -320 0 176 ) ( -320 0 48 ) common/caulk 0 0 0 0.500000 0.500000 134217728 0 0}

// brush 2
{
( -320 0 64 ) ( -320 192 64 ) ( -336 192 64 ) common/caulk 0 0 0 0.500000 0.500000 134217728 0 0( -336 192 192 ) ( -320 192 192 ) ( -320 0 192 ) common/caulk 0 0 0 0.500000 0.500000 134217728 0 0( -336 192 192 ) ( -336 0 192 ) ( -336 0 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 0 0( -320 64 192 ) ( -304 64 192 ) ( -304 64 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 0 0( -320 0 256 ) ( -320 192 256 ) ( -320 192 64 ) evil6_walls/e6gridergrtwll 128 128 0 -0.500000 0.500000 134217728 0 0( -320 192 192 ) ( -336 192 192 ) ( -336 192 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 0 0}

// brush 3
{
( 16 0 64 ) ( 16 192 64 ) ( 0 192 64 ) common/caulk 0 0 0 0.500000 0.500000 134217728 0 0( 0 192 192 ) ( 16 192 192 ) ( 16 0 192 ) common/caulk 0 0 0 0.500000 0.500000 134217728 0 0( 0 192 256 ) ( 0 0 256 ) ( 0 0 64 ) evil6_walls/e6gridergrtwll 128 128 0 -0.500000 0.500000 134217728 0 0( 16 64 192 ) ( 32 64 192 ) ( 32 64 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 0 0( 16 0 192 ) ( 16 192 192 ) ( 16 192 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 0 0( 16 192 192 ) ( 0 192 192 ) ( 0 192 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 0 0}

// brush 4
{
( -64 272 64 ) ( -256 272 64 ) ( -256 256 64 ) common/caulk 0 0 0 0.500000 0.500000 134217728 0 0( -256 256 192 ) ( -256 272 192 ) ( -64 272 192 ) common/caulk 0 0 0 0.500000 0.500000 134217728 0 0( -256 256 192 ) ( -64 256 192 ) ( -64 256 0 ) evil6_walls/e6gridergrtwll -171 128 0 -0.375000 0.500000 134217728 0 0( -64 256 256 ) ( -64 272 256 ) ( -64 272 64 ) common/caulk 0 0 0 0.500000 0.500000 134217728 0 0( -64 272 192 ) ( -256 272 192 ) ( -256 272 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 0 0( -256 272 256 ) ( -256 256 256 ) ( -256 256 64 ) common/caulk 0 0 0 0.500000 0.500000 134217728 0 0}

// brush 5
{
( -64 0 64 ) ( -256 0 64 ) ( -256 -16 64 ) common/caulk 0 0 0 0.500000 0.500000 134217728 0 0( -256 -16 192 ) ( -256 0 192 ) ( -64 0 192 ) common/caulk 0 0 0 0.500000 0.500000 134217728 0 0( -256 -16 192 ) ( -64 -16 192 ) ( -64 -16 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 0 0( -64 -16 256 ) ( -64 0 256 ) ( -64 0 64 ) common/caulk 0 0 0 0.500000 0.500000 134217728 0 0( -64 0 192 ) ( -256 0 192 ) ( -256 0 0 ) evil6_walls/e6gridergrtwll -171 128 0 -0.375000 0.500000 134217728 0 0( -256 0 256 ) ( -256 -16 256 ) ( -256 -16 64 ) common/caulk 0 0 0 0.500000 0.500000 134217728 0 0}

// brush 6
{
( 0 256 192 ) ( -320 256 192 ) ( -320 240 192 ) common/caulk 0 0 0 0.500000 0.500000 0 0 0( -320 240 208 ) ( -320 256 208 ) ( 0 256 208 ) common/caulk 0 0 0 0.500000 0.500000 0 0 0( -320 0 216 ) ( 0 0 216 ) ( 0 0 200 ) common/caulk 0 0 0 0.500000 0.500000 0 0 0( 0 240 208 ) ( 0 256 208 ) ( 0 256 192 ) common/caulk 0 0 0 0.500000 0.500000 0 0 0( 0 256 208 ) ( -320 256 208 ) ( -320 256 192 ) common/caulk 0 0 0 0.500000 0.500000 0 0 0( -320 256 208 ) ( -320 240 208 ) ( -320 240 192 ) common/caulk 0 0 0 0.500000 0.500000 0 0 0}


Now, you're looking at that, and going WTF?, yeah, you can make out the texture names, and probably the rotation, scale etc., but what's that stuff at the beginning, I hear you cry. Lets dissect a face (plane) definition.

( 8 256 192 ) ( -320 256 192 ) ( -320 0 192 ) evil6_bmtls/e6bmetal 0 0 0 0.500000 0.500000 134217728 0 0

If you dont know much about 3d geometry, just take it as given that three points are needed to define a plane (infinite flat surface), or a vector (pointy thing :]) and an offset distance. This vector is known as a normal, and points at 90 degrees from the surface of the plane.

The first three sections of the face definition are the three points required to define our plane. I wont go into how you calculate the normal etc, because it's not important for the purposes of this "tutorial".

Each brush is just made up of lots of big flat surfaces. The faces are made by cutting all of the other planes against each plane.

Now, what does the compiler do?

It starts off with a large volume, which is a node, in this case the head node for the BSP tree.


. . . . . . O . . . . .
. . . . . / . \. . . . .
. . . . O. . .O. . . .
. . . / . \ . / .\. . .


(ph34r my ascii art skills)
(They were lined up, but I couldnt get them to line up in the webpage, sorry -eyeronik)

This is the structure of the BSP tree, the first circle being the head node, which has two children, which in turn can also have two children, ad infinitum.

To create the child nodes (volumes), the compiler selects a plane, from all the structural planes of faces contained within that node, and splits the volume into two pieces. The criteria for choosing the splitting plane is this:

No. of faces which share the plane - No. of faces the plane intersects with + 1 if an axial plane

The plane with the highest number from this will be chosen, on a first come first serve basis, if more than one plane has the same value.

The process repeats through each child node, until they all no longer contain any faces, at which point, they are known as leaf nodes. The planes that are boundaries between two nodes are referred to as portals, and are important for the vis process.

This is as far as we'll look into the BSP process.

Next we'll look at vis. Vis builds up a list of leafs that each leaf can see. Think of it like a table, like this:


.0.1.2
0\.X.O
......
1X.\.X
......
2O.X.\


The map




The picture above shows a typical 90 degree corner - any fancy detail. The blue line shows the portal created in that hallway. The leaf that would exist (if I had bothered to build a room) where the player (the red blob) is standing can "see" the portal, and therefore, everything in the leafs either side of it will be drawn. For the one close to the player, that's fine, as it is clearly visible, but the one further away could have much of its volume, and therefore surfaces, hidden by other surfaces. So.....






We introduce an angled split, by placing a hint brush with that angled plane as one of its faces. Now, from in the leaf where the player stands, you are unable to see the far blue line (NOTE: the diagonal blue line is 2 portals, each ending at the inner edge of the corner), and so the leaf coloured red will not be drawn. The hint split doesn't have to be where it is; the white line shows an alternate position. As long as the portal is not visible from anywhere in the leaf, the far away leaf's surfaces will not be rendered.

The same principle can be applied to many other situations. PrtView can be an excellent help in determining where/when you should hint.

A couple of extra notes:

Unlike Quake 2, surfaces can belong to multiple leafs. Take a look at the second picture again. The wall furthest away from the player would have been split into 3 sections in Quake 2, so that each bit would only belong to one leaf. This isn't done in Quake 3, which may lead to some more overdraw, but keeps the BSP size down a little.

I missed out one of the first things the BSP process does, which is create some default splits, at 1024 unit intervals. If you go to the view menu in radiant, and enable view blocks, you can see these splits in-editor. I think these help the compiler be slightly more organized, making the BSP balanced, so you don't end up with lopsided branches, but I can't be sure. As of yet, no confirmation or otherwise.

And now for example 2:





The first shot here is just a general view of the room in which i was having some problems. When you are standing in the upper area at the back, the engine should not really be drawing the tunnels, or rooms beyond the tunnels now should it?

Of course not. So I set about fixing this problem, it's fairly simple really.





Second shot just shows the room again, minus the detail.





This shot is now taken from on the upper area, I added a simple block of hint, the bottom of which is flush with the upper area floor. The block aligns neatly with the rest of the room. I also added another block below this one, to make sure the tunnel exits had portals flat across their openings. Now, when standing I the upper area, it is impossible to see the portals in the tunnel exits, and so, nothing inside them, or behind them, is drawn.

Thanks to ydnar and K for looking over it and suggesting a couple of alterations.
Thanks to pjw for his massive grammar/spelling check :)



Problems, Comments, Queries > Forum

Tutorial by djbob


Surface mini-logo Surface mini-logo