Flash Texture Mapping

I'm sure you've seen them before - those cool 3D Flash demos made with libraries like Papervision3D and Sandy. It isn't too hard to conceptualize how much of the basic work is done. Most of us know about the drawing APIs in Flash and how you can draw shapes between points. Add a little (ok, maybe a lot of) math into the mix and you can pretty easily get a 3D-looking, albeit flat, shape to be drawn with ActionScript. But what about those textures? How in the world is Flash able to map 3D textures onto those pseudo-3D shapes?


A rhino rendered in Papervision3D

The answer is, not surprisingly, more math. That and a little help from the drawing capabilities added in Flash 8. This tutorial will examine those techniques and show you how to implement them on your own without the help of pre-existing 3D ActionScript libraries.

Flash Transformations

Flash has limited capabilities when it comes to transforming objects on the screen. These transformations are limited to what are known as affine transformations. These transformations include changes in position, scaling, roation, and skewing.


Affine transformations in Flash

What they do not include is anything beyond that, such as tapering or perspective distortion - the very thing 3D requires to give the perception of depth and space.


A perspective transformation

Note: Displacement maps

Using the DisplacementMapFilter in Flash, it is actually possible to distort a bitmap in just about any way imaginable including with perspective. This process is a lot more complicated and requires more effort (memory & CPU) than the technique described in this tutorial.

In terms of flat 3D shapes drawn with ActionScript, these restrictions do not apply because polygon shapes are being drawn dynamically with the drawing API. No transformation distortion is necessary to mold them in to the desired 3D planes; they are just lines connecting points in space. When you throw bitmap texturing into the equation, things become a little more complicated. A bitmap is already "drawn," so to speak, with its pixels pre-defined in a specific pattern. It's not as easy to draw that dynamically to the precision needed. Its much easier to map the bitmap to the plane or area occupied by the polygon it should be displayed in.

But herein lies the problem. Though the polygon drawn with lines is not confined by the affine transformations, bitmap texturing will be. Using traditional affine transformations you are unable to directly take a bitmap in Flash and transform it outside of position, scale, rotation, and skew.

Affine Transformations3

So how do bitmaps get transformed to have perspective as 3D textures in Flash? The truth is, they don't. In fact most 3D textures seen in Flash are not transformed in 3D at all. Instead what you are seeing are a lot of triangular pieces of bitmaps individually transformed with affine transformations to make them fit as best as possible within the 3D space. The smaller the pieces, the more effective the effect and the less aparent the illustion.

A triangle, even with affine transformations, can be distorted to fit between any 3 points. By dividing a bitmap into two triangles and transforming it twice, it can be fit into any polyginal space, no matter what the perspective.

[img two triangles to plane]

Because there is no actual 3D transformation being made, there will be some visual distortion with this technique as you can probably see in the figure above. The seam between the two triangles can be seen pretty easily. This only gets worse as the perspective increases. By dividing the plane further into a mesh of smaller traingles, this distortion can be minimized. With more triangles, however, you are going to be dealing with more caculations and it could potentially hurt your frame rate.

[img subdivided plan]

Transformation Matrices in ActionScript

Affine transformations within Flash are managed in ActionScript using the Matrix class. The Matrix class defines the transformation matrix of an affine transformation and lets you distort objects based on its value. These matrices are typically written out in a 3x3 grid. The basic structure is:

[ a  b  u ]
[ c  d  v ]
[ tx ty w ]

Where a represents x scale, b represents y skew, c represents x skew, d represents, y scale and tx and ty represent x and y position. The u, v, and w propertiers are always 0, 0 and 1 respectively

[ scaleX skewY  0 ]
[ skewX  scaleY 0 ]
[ x      y      1 ]

In ActionScript, new Matrix instances are created with the Matrix class. This class is located within the flash.geom package (so it will need to be imported tobe used). When creating new Matrix instances, the a, b, c, d, tx, and ty values can be specified in the constructor.

import flash.geom.Matrix;
var matrix:Matrix = new Matrix(a, b, c, d, tx, ty);

All code examples are compatible with ActionScript 2.0 and ActionScript 3.0 unless otherwise specified. Flash player 8 or higher should be used.

You can use a Matrix to transform a display object such as a movie clip by assigning the Matrix instance to that instance's transformation.matrix property.

displayObject.transformation.matrix = new Matrix(1,0,0,1,10,0); // x = 10

Note: Defining transformation.matrix

You cannot edit transformation.matrix property values directly, i.e. displayObject.transformation.matrix.tx = value. You have to define a matrix separately and then set the value of transformation.matrix to that matrix. It is OK if you start with the initial value of transformation.matrix, for example:

var matrix:Matrix = displayObject.transformation.matrix;
matrix.tx = 10;
displayObject.transformation.matrix = matrix; // x = 10

Similarly, when using the beginBitmapFill() method of the drawing API, Matrix instances can be used to transform the bitmap being drawn.

// ActionScript 2.0
import flash.display.BitmapData;
import flash.geom.Matrix;

var libraryBitmapData:BitmapData = BitmapData.loadBitmap("libraryBitmapLinkageID");
var matrix:Matrix = new Matrix(1,0,1,1,0,0); // x skew
beginBitmapFill(libraryBitmapData, matrix);
lineTo(100,0);
lineTo(100,100);
lineTo(0,100);
endFill();
// ActionScript 3.0
import flash.display.BitmapData;
import flash.geom.Matrix;

var libraryBitmapData:BitmapData = new LibraryBitmapClassName(0, 0);
var matrix:Matrix = new Matrix(1,0,1,1,0,0); // x skew
graphics.beginBitmapFill(libraryBitmapData, matrix);
graphics.lineTo(100,0);
graphics.lineTo(100,100);
graphics.lineTo(0,100);
graphics.endFill();

[img skewed by factor of 1 from above code]

It helps to know just how these values play in the transformation matrix. Hopefully the position (tx, ty) and even the scale (a, d) values are already obvious. The tx and ty values are pixel-based offset values that shift all parts of a transformation by that value much like the x and y (_x and _y in AS2) properties of a display object. The a and d scale values, on the otherhand, are percentages by which graphics are scaled along the x or y axis. These relate to the scaleX and scaleY (_xscale and _yscale in AS2) display object properties. Skewing is a little different and isn't accessible through other properties like position and scale.

The skew values in a matrix (b, c) are similar to the scale values. They are percentages that modify a pixels position by a value. But instead of affecting pixels along the same axis, skewing affects pixels of the opposite axis. Skewing is also an addative process that is applied on top of scaling. So where as a normal scale (no scaling) is 1, a normal skew (no skewing) is 0.

For example, consider a 10x10 pixel image (or, technically, 11x11 with pixels in positions 0-10). If scaling this image along the x axis by a factor of 2, all of the pixels in each column have their x position multiplied by two. Each pixel in the last column (x=10) of the image will have a new position of x=20 since 10*2 = 20. Conversely, if the image was skewed along the x axis by a factor of 1, it would be the last row (y=10), not column, that would be shifted along the x axis. Now, the pixel in the last column of the first row (y=0) doesn't move since it's y is 0 and 0*1 is still 0. The pixel in the last column of the last row (y=10), however, moves since its y is 10 and 10*1 = 10; where 10 is added to the original value of x.

This all boils down to a formula that determines any pixel's position based on the scale (a, d), skew (b, c), and position (tx, ty) values in a matrix.

newX = originalX*a + originalY*c + tx
newY = originalX*b + originalY*d + ty

Understanding these relations will be important when mapping a bitmap between different points or vertices.