boostworthyisryantaylor

FP10: The ‘drawTriangles’ Method

One of the major additions to the 'Graphics' API in Flash Player 10 is the 'drawTriangles' method. The syntax is as follows:

ActionScript:
  1. drawTriangles(vertices:Vector, indices:Vector = null, uvtData:Vector = null, culling:String = "none"):*

Before exploring each parameter, you need to understand why this method is such a huge addition to the Flash 'Graphics' API. Since the early days of Flash, developers have always toyed with implementations of 3D frameworks. It wasn't until the birth of AVM2 and AS3 that 3D really took off in Flash, thanks to the huge leap in performance. We have since seen the rise of a handful of many well-known and successful open-source Flash libraries such as Papervision3D, Sandy, Away3D, and Alternativa 3D. A lot of award-winning sites have resulted from these libraries and it has truly grabbed the attention of Adobe and become a major focus of Flash Player 10. The 'drawTriangles' method is one of the many additions that the Flash Player team has made to help increase the quality and performance of these open-source libraries. Having said all of that, let's begin examining the 'drawTriangles' method and its parameters.

The first parameter, verticies, is a 'Vector' containing numbers that correspond to points of triangles. If you are new to the concept of the type 'Vector' in Flash Player 10, then you need to understand that 'Vector' is simply a typed array, not a geometric vector. The 'verticies' Vector can contain an infinite amount of triangle points and should always be a factor of six (three sets of x,y points). The even indices will always contain the x values and the odd indices will always contain the y values. For instance, a single triangle could be defined as follows:

ActionScript:
  1. var point1:Point = new Point(0, 0);
  2. var point2:Point = new Point(256, 0);
  3. var point3:Point = new Point(0, 256);
  4.  
  5. var verticies:Vector.<Number> = Vector.<Number>([point1.x, point1.y, point2.x, point2.y, point3.x, point3.y]);

Simple enough. Now suppose that you have two triangles and they share some common verticies:

ActionScript:
  1. var triangle1Point1:Point = new Point(0, 0);
  2. var triangle1Point2:Point = new Point(256, 0);
  3. var triangle1Point3:Point = new Point(0, 256);
  4.  
  5. var triangle2Point1:Point = new Point(256, 0);
  6. var triangle2Point2:Point = new Point(0, 256);
  7. var triangle2Point3:Point = new Point(256, 256);
  8.  
  9. var verticies:Vector.<Number> = Vector.<Number>([triangle1Point1.x, triangle1Point1.y, triangle1Point2.x, triangle1Point2.y, triangle1Point3.x, triangle1Point3.y, triangle2Point1.x, triangle2Point1.y, triangle2Point2.x, triangle2Point2.y, triangle2Point3.x, triangle2Point3.y]);

This correctly describes two triangles, however it is inefficient since is using six verticies when only four are necessary. The second parameter, incidies, allows you to specify sets of indicies in the 'verticies' Vector which make up triangles. When the 'indicies' parameter passed as a Vector rather than null, the assumption that every six indicies in the 'verticies' Vector represents a triangle is no longer made. Instead, the 'verticies' Vector is treated as a random collection of verticies and the pairs of numbers in the 'indices' Vector are treated as pairs of three which specify which verticies represent a triangle. As an example of this, the same two triangles that were described in the example above will now be described using only four points by taking advantage of the 'indicies' parameter. Note that the indicies are zero-based, so if you are having problems picturing this, mentally add one to each index to correctly relate it to the corresponding point number.

ActionScript:
  1. var point1:Point = new Point(0, 0);
  2. var point2:Point = new Point(256, 0);
  3. var point3:Point = new Point(0, 256);
  4. var point4:Point = new Point(256, 256);
  5.  
  6. var verticies:Vector.<Number> = Vector.<Number>([point1.x, point1.y, point2.x, point2.y, point3.x, point3.y, point4.x, point4.y]);
  7. var indices:Vector.<int> = Vector.<int>([0, 1, 2, 1, 3, 2]);

Before moving on, if you are interested in actually seeing this, the example below shows the entire process for drawing the triangles that we have described thus far.

ActionScript:
  1. var point1:Point = new Point(0, 0);
  2. var point2:Point = new Point(256, 0);
  3. var point3:Point = new Point(0, 256);
  4. var point4:Point = new Point(256, 256);
  5.  
  6. var verticies:Vector.<Number> = Vector.<Number>([point1.x, point1.y, point2.x, point2.y, point3.x, point3.y, point4.x, point4.y]);
  7. var indices:Vector.<int> = Vector.<int>([0, 1, 2, 1, 3, 2]);
  8.  
  9. var scene:Sprite = new Sprite();
  10.  
  11. scene.graphics.beginFill(0x990000, 1);
  12. scene.graphics.drawTriangles(verticies, indices);
  13. scene.graphics.endFill();
  14.  
  15. addChild(scene);

This is great and all, but what if you want your triangles to contain bitmaps rather than just color fills? No problem - the next parameter, uvtData, is for handling UV mapping of a bitmap texture to a triangle. If you are unfamiliar with the process of UV mapping in a 3D application such as Maya, basically the top left corner of the bitmap is represented as (0, 0) and the bottom right corner is represented as (1, 1). Using this normalized coordinate system, you can specify a UV coordinate that corresponds to each vertex of a triangle. Moving forward with the example that I have been building upon, let's say that you want to map a given image to the two triangles. The triangles form a rectangle, however by default a bitmap texture is going to fill each triangle rather than properly being distributed between the two to correctly form the full rectangular image. UV mapping can be is used to correct this as seen in the example below.

ActionScript:
  1. var point1:Point = new Point(0, 0);
  2. var point2:Point = new Point(256, 0);
  3. var point3:Point = new Point(0, 256);
  4. var point4:Point = new Point(256, 256);
  5.  
  6. var uvPoint1:Point = new Point(0, 0);
  7. var uvPoint2:Point = new Point(1, 0);
  8. var uvPoint3:Point = new Point(0, 1);
  9. var uvPoint4:Point = new Point(1, 1);
  10.  
  11. var verticies:Vector.<Number> = Vector.<Number>([point1.x, point1.y, point2.x, point2.y, point3.x, point3.y, point4.x, point4.y]);
  12. var indices:Vector.<int> = Vector.<int>([0, 1, 2, 1, 3, 2]);
  13. var uvtData:Vector.<Number> = Vector.<Number>([uvPoint1.x, uvPoint1.y, uvPoint2.x, uvPoint2.y, uvPoint3.x, uvPoint3.y, uvPoint4.x, uvPoint4.y]);
  14.  
  15. var scene:Sprite = new Sprite();
  16. var texture:Bitmap = new MyImage() as Bitmap;
  17.  
  18. scene.graphics.beginBitmapFill(texture.bitmapData);
  19. scene.graphics.drawTriangles(verticies, indices, uvtData);
  20. scene.graphics.endFill();
  21.  
  22. addChild(scene);

Up until now, I have only been passing pairs of U and V values in the 'uvtData' Vector. The optional T value is used for calculating the distance from the eye to the texture in z space so that the renderer can correctly apply an accurate perspective of the texture in 3D. T is calculated as focalLength / (focalLength + z), where focalLength is the distance from the eye to the screen and z is th distance from the screen to the texture. The focal length should be defined as a constant value for use in all of your projections. A lower focal length will result in a more dramatically stretched perspective, so you should experiment with the value and settle on one that you feel works best for the cinematographic look that you are after. In the example that follows, I used a value of 400 and found satisfactory results. In addition to including each T value with the 'uvtData' Vector, you will need to apply each T scalar to the corresponding vertex. Here is a demonstration of the rotation of the verticies and the application of the T scalars:

ActionScript:
  1. var vertex1:Vector3D = new Vector3D(-128, -128, 0);
  2. var vertex2:Vector3D = new Vector3D(128, -128, 0);
  3. var vertex3:Vector3D = new Vector3D(-128, 128, 0);
  4. var vertex4:Vector3D = new Vector3D(128, 128, 0);
  5.            
  6. var uvPoint1:Point = new Point(0, 0);
  7. var uvPoint2:Point = new Point(1, 0);
  8. var uvPoint3:Point = new Point(0, 1);
  9. var uvPoint4:Point = new Point(1, 1);
  10.            
  11. var angle:Number = 0.9;
  12. var cos:Number = Math.cos(angle) * 128;
  13. var sin:Number = Math.sin(angle) * 128;
  14.            
  15. vertex1.x = -cos;
  16. vertex2.x = cos;
  17. vertex3.x = -cos;
  18. vertex4.x = cos;
  19.            
  20. vertex1.z = -sin;
  21. vertex2.z = sin;
  22. vertex3.z = -sin;
  23. vertex4.z = sin;
  24.            
  25. var focalLength:Number = 400;
  26.            
  27. var t1:Number = focalLength / (focalLength + vertex1.z);
  28. var t2:Number = focalLength / (focalLength + vertex2.z);
  29. var t3:Number = focalLength / (focalLength + vertex3.z);
  30. var t4:Number = focalLength / (focalLength + vertex4.z);
  31.            
  32. vertex1.scaleBy(t1);
  33. vertex2.scaleBy(t2);
  34. vertex3.scaleBy(t3);
  35. vertex4.scaleBy(t4);
  36.            
  37. var verticies:Vector.<Number> = Vector.<Number>([vertex1.x, vertex1.y, vertex2.x, vertex2.y, vertex3.x, vertex3.y, vertex4.x, vertex4.y]);
  38. var indices:Vector.<int> = Vector.<int>([0, 1, 2, 1, 3, 2]);
  39. var uvtData:Vector.<Number> = Vector.<Number>([uvPoint1.x, uvPoint1.y, t1, uvPoint2.x, uvPoint2.y, t2, uvPoint3.x, uvPoint3.y, t3, uvPoint4.x, uvPoint4.y, t4]);
  40.            
  41. var scene:Sprite = new Sprite();
  42.            
  43. var plane:Sprite = new Sprite();
  44. plane.x = 250;
  45. plane.y = 200;
  46.            
  47. var texture:Bitmap = new MyImage() as Bitmap;
  48.            
  49. plane.graphics.beginBitmapFill(texture.bitmapData);
  50. plane.graphics.drawTriangles(verticies, indices, uvtData);
  51. plane.graphics.endFill();
  52.            
  53. scene.addChild(plane);
  54.            
  55. addChild(scene);

Here is a screen shot of the result:

Flash Player 10 drawTriangles method used to render a plane with a bitmap texture in 3D space.

Before moving on, I also want to note that you can use a 'Shader' object for representing a Pixel Bender filter and then apply the shader to your triangles as a fill. The example below demonstrates this practice. Note that you cannot use UVT mapping with shaders; UVT support is strictly used for improving texture quality when a bitmap fill is used.

ActionScript:
  1. _shader = new Shader(new ColorFilter());
  2. _shader.data.color.value[0] = Math.random();
  3. _shader.data.color.value[1] = Math.random();
  4. _shader.data.color.value[2] = Math.random();
  5.            
  6. plane.graphics.beginShaderFill(_shader);
  7. plane.graphics.drawTriangles(verticies, indices);
  8. plane.graphics.endFill();

The forth and final parameter of the 'drawTriangles' method is for culling. Culling is the process of determining what triangles are visible to a camera (view point) in a 3D scene. To summarize the process, first you need to calculate the vector normal of a given triangle. This is done by finding the cross product of two of the sides. Once you have the vector normal of the triangle, you need to find the vector of the camera when pointed towards the triangle. This is simply a matter of picking a point on the triangle, then subtracting the point of the camera from that point. Once you have these two vectors, the dot product will reveal the angle between them. Examining the sign of this angle is how you determine whether or not the triangle should be visible. Fortunately, culling support is built into Flash Player 10, so if you hate math - you are off the hook. The three options that exist for the culling parameter are defined as constants of the 'flash.display.TriangleCulling' class. They are as follows:

    TriangleCulling.NONE - This is the default value. When this setting is used, no culling is performed. Though this is often not desired, there are some situations in which this is necessary to correctly display an object. For instance, say you had a sphere and it's material has some transparency - meaning you can see through the triangles to some extent. In this case, you do not want to perform any culling on the object. Culling would prevent some of the triangles from being rendered and, in this case, the desired result would be to see all of the triangles.

    TriangleCulling.POSITIVE - This is the more common culling method and is known as backface culling. When this approach is used, only the triangles that are facing towards the view point will be rendered. Any triangles that are pointing away from the view point will not be rendered. In the case of an object with a solid material applied, this works perfectly in rendering only what is visible to the camera.

    TriangleCulling.NEGATIVE - The less common culling method, but still useful for some situations. In this case, triangles that are facing away from the view point will be rendered, while triangles that are facing the view point will not. A good example of when you would want to perform this type of culling is for an object being used for an environment map. Let's say that you have a giant cube and the camera is placed inside of it. With the right material mapped correctly to the cube, the illusion that you are in an environment will result. In order to render the internal triangles, but not the ones outside of the cube, this culling method should be used.

To see culling in action, modify the previous example by passing the 'positive' culling value as the forth parameter.

ActionScript:
  1. plane.graphics.drawTriangles(verticies, indices, uvtData, TriangleCulling.POSITIVE);

Experimenting with various angles of rotation will result in the culling of the triangles when appropriate. Note that the order of the indicies in the 'indicies' Vector directly affects the direction of the triangle's vector normal. If you ever experience undesired culling results for some triangles but not others, changing the order of the indicies will likely resolve the issues.

That concludes my foundational exploration of Flash Player 10's new 'drawTriangles' method. In the future, I will be posting some more advanced examples of how 3D frameworks can evolve from all of this.

8 Comments so far

  1. fazeaction May 27th, 2008 5:54 am

    Thanks really usefull... and other posts about flash player 10 too!!!

  2. Andrew June 26th, 2008 1:51 pm

    Have you had any experience getting the dot product of a normal DisplayObject? I'm needing to know if the clip I have is complete spun around. Checking the rotationY doesn't seem to help if you have the clip off center from the projectProjection. The rotationY seems to be return 90, but it looks like it should be returning something more like 75 or something.

  3. Ryan Taylor June 26th, 2008 10:14 pm

    Hey Andrew,

    In order to find the dot product of the camera vector and the DisplayObject's normal, you will need to begin by calculating Vector3Ds of two of the sides that aren't parallel to each other. Make sure and transform the original points using the object's Matrix3D before subtracting them to find those Vector3Ds. The cross product of the Vector3Ds is the DisplayObject's normal.

    Hopefully that makes sense. An example would be a lot clearer, so I will try find some spare time to throw together an example of this in the near future.

  4. Andrew June 27th, 2008 2:12 pm

    An example would be great. Your last post makes sense, but I'm not sure how I'm going to get the two side's Vector3D information since they will always be parallel. The demo I am creating is a two-sided movie clip. I'm trying to figure out when to hide one face and display the other depending on the container's rotationY. I was checking the angle the rotation was at, but that doesn't work when the the card is not centered on the perspectiveProjection since everything is skewed due to perspective.

    Thank you for your last reply. I forgot to mention how helpful the above tutorial was on another project.

  5. Ryan Taylor June 27th, 2008 3:05 pm

    Thanks Andrew. I'm not sure I follow when you say that you can't find Vector3Ds for two non-parallel sides. Each and every DisplayObject has a rectangular bounding box that surrounds it; so the top and left sides for instance would do just fine.

  6. Andrew July 1st, 2008 10:19 pm

    Using the getRect() method, I was able to get the two Vectors to calculate the cross product of the plane and calculate the facing of the plane.

    Thanks for your help!

  7. Patrick July 11th, 2009 8:07 pm

    Has anyone an idea, how to map a simple cube that consist of 8 3D points and 36 indices. Please have a look at my article

    http://patrickwolleb.subumbo.com/?p=23

    Thanks for the nice article :)

  8. aaa January 6th, 2010 5:27 am

    man this 3d shit give me a freak out. its so crappy as soon as you have many cubes and want to rotate around all then you fucked... try to build a 3d object like a table.

    the abode developers are overpaid. the functions are useless. you either use a framework like pv3d or you give yourself the pain and build a new framework based on this great innovations.

Leave a reply

*
To prove you are human (not an imperial spam script), type the security word shown in the picture. Click on the picture to hear an audio file of the word.
Click to hear an audio file of the anti-spam word