Scripting 3D in Flash
      by senocular

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:

if (((B.y-A.y)/(B.x-A.x) - (C.y-A.y)/(C.x-A.x) < 0) ^ (A.x <= B.x == A.x > C.x)){
     visibility = true;
}else{
     visibility = false;
}

 QUICK REVIEW: Bitwise XOR (^)
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.

 




SUPPORTERS:

kirupa.com's fast and reliable hosting provided by Media Temple.