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