Backface
Culling
The pyramid, while still cool, has its issues.
Namely, every side of the pyramid is drawn so
you still see every side whether you're supposed
to or not. This was the reason for the semi-transparent
fills. This may or not be a desirable effect,
but what it you want to see it as a solid 3D shape
and not a translucent one? In that case, you'll
just need to figure out which which shapes are
hidden and which aren't - not drawing those that
are. Normally, in 3D environments, faces are single-sided
in that they can be seen only on one side and
should you look at their other side, the face
would be completely invisible! Why this is, is
because in 3D, you assume that if you aren't looking
at the front of a face, then it's probably hidden
somewhere covered up by another object or on the
opposite side of a shape or something. Making
face invisible like this means that the problem
of the overlapping faces in the previous Filled
Pyramid example is gone. The overlapping there
is only between faces that are facing you and
the faces which are on the other side of the pyramid
and shouldn't technically be seen at all. The
process of determining this visibility is commonly
refereed to as backface culling.
There are a couple ways of determining
backface culling, whether or not a particular
triangle or face in 3D is to be drawn or not.
One, and probably the most accepted is done through
checking the direction the plane and determining
whether it's facing towards the camera or away.
This is done by making a theoretical line come
straight out from the face pointing outward. This
line, a normal, isn't physically drawn
(unless you want to draw it); it's just a reference.
From that line an angle can be calculated which
would be the angle from that line to a similar
line coming straight out from the camera. If this
angle is greater than 90 degrees then that face
is pointing away from the camera and cannot be
seen.
[ visibility based on angle from
camera ]
This method may be the "normal" (pun intended) way
of determining visibility as such normals are
used in all kinds of calculations when dealing
with complex objects and behaviors in 3D. Remember,
though, Flash is not a 3D application, and all
this 3D is being done from scratch and only to
a certain extent. With that in mind, it's a good
idea to drop all normal brouhaha and stick to
the simple stuff.
The simple, or should I say "simpler"
way of checking for visibility is through 2D.
It's this technique that will be used in the examples
to follow as it is simpler and faster for Flash
to calculate. The idea is to take the points in
2D that represent a rendered triangular face (or
the first three points of a face if it's not a
triangle and has more than three) and simply compare
their relations with each other. The way that
they are drawn in 2D space is a reflection of
the points, and the face, in 3D space so they
can be used to figure out just how the face is
oriented.
[ visibility from points in 2D ]
These two triangles are actually the same
triangle. On the left, you have your basic view where the triangle would be
visible. On the right you have the same triangle but just flipped. Being that it
is flipped, you can assume that this triangle would be facing away from you and
not towards you, therefore making it invisible. How you can tell its
flipped is simply by looking at it. Your brain has the capacity to determine
that from appearances seeing that the B point is over on the left side of the A
and C points and that would mean a flipped triangle. The mathematical logic
behind that, though, is based on slightly more specific principles than the
"just look at it" principle. Those principles revolve around the relation those
three points have have with each other in that space; relations such as position
and the amount of slope lines drawn between those points have.
Slope is the biggie here. Take a look at this example. Move
the points A, B and C around with your mouse and
watch as the triangle becomes visible and invisible,
almost as if you are flipping it in 3D space even
though you are only moving the points in 2D space.
[ visibility function in action
]
You can see a change in visibility one line
crosses another line. Such a crossing marks a change in when the slope of one
line is no longer either greater or less than the slope of the other - a change
from when one slope is greater than the other. But how do you develop a way to
check that?
Here's the idea. Use one point as a common
reference point or a point which is shared by 2 line segments. We'll say A. From
A you have line segments AB and AC. When ever these line segments cross, there's
a change in visibility. Changing how is just a matter of determining for
yourself whether you want the face to be visible when AB has a greater slope or
when AC has a greater slope. So comparing the slopes of those two lines gives
you:
(B.y-A.y)/(B.x-A.x) - (C.y-A.y)/(C.x-A.x) < 0
Problem. If you rotate the triangle and not actually flip it,
you can still cause a situation where the slopes
of AB and AC will "cross" or at least
where the one with the greater slope will switch
over to the one with the smaller one. This means
testing for visibility with the above comparison
just won't do. We will need to add another check
to make sure the check above only works when it
should and doesn't when it isn't supposed to,
or actually, reverse itself since it will need
to be the opposite once such a rotation occurs.
How that is done is through checking
the x values of B and C with the x value of A.
The problem occurs when AB or AC suddenly shifts
from being a very steep positive slope to a very
steep negative slope or vise versa. This happens
when B or C's x value moves past A's x value.
It's then when you need to say, "Ok slope
comparison, we need you to work backwards now
since the slopes suddenly pulled a switcher-oo
on us here." Now we have us another comparison
to include for the visibility check
(A.x < B.x == A.x > C.x)
Putting those comparisons together into an
equation gives the following for a fully functional visibility check:
The bitwise XOR operator is like the
digit-by-digit inequality operator for binary numbers. What it does is
takes 2 numbers (one on each side of the operator) and performs an
inequality check for each of the digits of those numbers as they exist
in binary. The resulting number is a binary representation of that
result. Example:
1001 == 1111 ^ 0110
XOR goes through each of those digits of each number
and performs an inequality check on them. The result of that check is
either 1 (true) or 0 (false) depending on whether or not the digits of
the two numbers at that position equal each other or not. In the
example, starting from the right, for the two numbers in the ^
operation, the first digits are 1 and 0. Are these equal? No, therefore
they are unequal and an inequality check would return true. That's why
the right-most digit in the 1001 number is 1, because its true that 1
does not equal 0. Each of the remaining digits are checked and assigned
to the final product.
In terms of booleans, you have only 2 values, true or
false, which can be equated to 1 or 0. Using ^ on these are the same as
using !=. The differences are you save yourself from having to type two
characters (! and =) now just have to type one, and ^ is actually
slightly faster than a != comparison. In dealing with 3D in Flash, speed
is a high priority.
Remember, for this equation to work right, you need to make
sure you are using the right A, B and C representations
for your points. In other words, make sure that
the points you are using are the correct A, B
and C points for the face to be visible only when
it should. Often the best way to know the order
is to guess once and if wrong, reverse the order.
Then everything should be fine.
Per-face Depth Determination
Backface culling is not the only way to make a
3D shape solid. You can also separate each face
of a shape to be its own movieclip and then use
swapDepths on each face movieclip based on the
position of that face in the 3D scene. Closer
faces, just as would be the case for any shape
in general, would be on top, and faces further
away would be on the bottom.
This technique may be
a little easier to use, but it does require managing
the extra movieclips and it would mean drawing
faces which wouldn't even be seen by the viewer
which may be a waste of time. The separation of
faces into movieclips, though, will be needed
for other things as well, which we'll get into
a little later on. For the most part though, since
it's so cool, the backface culling technique will
be used to test for visibility of faces.