For the past month or two, I have been spending time building a game (something I havent done since my Flash 4 days). This has really been a lot of fun, as it has allowed me to use some of the Flash Player APIs which I really haven’t had a chance or need to use before.
One thing which I have been (slowly) learning about are using Matrix transformations on DisplayObjects. I made a post earlier showing how (with much, much help from Senocular), I was able to use Matrix to do hit tests using BitmapData.hitTest on DisplayObjects which have had transformations applied to them (in this case, rotation).
Well, I recently had the need to convert some of my DisplayObjects to use the 2.5D APIs (by setting the z property to a value). Unfortunately, this ended up breaking a lot of my code, mostly because of how it changes how transformations are applied to a DisplayObject. Specifically, when you set the
DisplayObject.z property to any value,
DisplayObject.transform.matrix will return null, and you must use
DisplayObject.transform.matrix3D instead. Where this causes problems is when you are using APIs that expect to use a
Matrix instance, as opposed to
Matrix3D instance, such as
This is exactly the scenario I ran into, and this post will show one solution for how to convert from a Matrix3D to a Matrix instance, which can then be passed to the BitmapData.draw.
First, lets look at my original code:
shipBmpData = new BitmapData(shipBounds.width, shipBounds.height, true, 0); var shipOffset:Matrix = ship.transform.matrix; shipOffset.tx = ship.x - shipBounds.x; shipOffset.ty = ship.y - shipBounds.y; shipBmpData.draw(ship, shipOffset);
Basically, this draws the bitmap data for my ship into a
BitmapData instance, taking into account any transformations which have been applied to the ship (in this case rotation). In my code, this bitmap data is cached, and then later used with
BitmapData.hitTest for collision detection (not shown here).
However, as soon as I set the z property on the ship (which is a DisplayObject), this code will no longer work, as
ship.transform.matrix will return null (since 3D transformations are now being used).
At first, I figured I would just change my code to access the Matrix3D instance like so:
var shipOffset:Matrix3D = ship.transform.matrix3D;
and then pass this to the
BitmapData.draw API. However, I was quickly reminded (by the compiler) that
Matrix3D does not inherit from
Matrix, and thus cannot be passed to
Because of this I needed to convert from a
Matrix3D to a
Matrix instance, which could then be passed to
BitmapData.draw. After some help from Ralph Hauwert (who tracked down a couple of bugs) I was able to get it working.
Before I show the code, it will be useful to look at which values
Matrix3D contain. First, here are the values held by a
a c tx b d ty u v w
You can find a description of the properties in the Matrix docs.
Here are the values held by a Matrix3D instance:
scaleX 0 0 tx 0 scaleY 0 ty 0 0 scaleZ tz 0 0 0 tw
From the Matrix3D docs:
The Matrix3D class uses a 4x4 square matrix: a table of four rows and columns of numbers that hold the data for the transformation. The first three rows of the matrix hold data for each 3D axis (x,y,z). The translation information is in the last column. The orientation and scaling data are in the first three columns. The scaling factors are the diagonal numbers in the first three columns
Basically, in addition to holding values for x and y, the
Matrix3D class also has slots for z properties. Because the
Matrix is a 3x3 matrix, and the
Matrix3D is a 4x4 matrix converting from
Matrix means that we will have to discard some information. Luckily, in my case, I didnt need the z information, so i was able to discard it and map the related x, y values to the
Matrix instance. We can then pass this new
Matrix instance to the
For our purposes, the
Matrix3D mapping to
a c 0 tx b d 0 ty 0 0 scaleZ tz 0 0 0 tw
Here is the code that maps between the two:
var shipOffset:Matrix3D = ship.transform.matrix3D; var rawMatrixData:Vector.<Number> = shipOffset.rawData; var matrix:Matrix = new Matrix(); matrix.a = rawMatrixData; matrix.c = rawMatrixData; matrix.tx = ship.x - shipBounds.x; matrix.b = rawMatrixData; matrix.d = rawMatrixData; matrix.ty = ship.y - shipBounds.y; ship.transform.matrix3D = null; shipBmpData.draw(ship, matrix); ship.transform.matrix3D = shipOffset;
Basically, we get an instance of the
Matrix3D class for the
DisplayObject. We then access the raw data of the
Matrix3D instance, and copy it into a new
Matrix instance (ignoring and dropping the z values), and apply the transformation corrections in the process.
We can then pass this new
Matrix instance to the
BitmapData.draw API. However, as you probably noticed in the code, I had to first do an additional step. Specifically:</strike >
ship.transform.matrix3D = null; shipBmpData.draw(ship, matrix); ship.transform.matrix3D = shipOffset;
Before we draw the data to the
BitmapData instance, we have to clear out the existing
Matrix3D applied to the
DisplayObject. This is to work around a bug discovered by Ralph Hauwert. When passing a
DisplayObject which does not have a
Matrix3D transformation (as in our first example) to
BitmapData.draw, any transformations on the
DisplayObject ARE NOT taken into account when drawing. However, when the the
DisplayObject does have a
Matrix3D transformation applied to it (such as when the z property has been set to a value), then any transformations ARE applied when drawing to
BitmapData.draw. Because of this, we have to first store the
Matrix3D associated with the DisplayObject, set it to null, draw the
DisplayObject to the
BitmapData instance, and then reapply the
Matrix3D to the
shipBmpData = new BitmapData(shipBounds.width, shipBounds.height, true, 0); shipBmpData.draw(ship, null);
Update 2: It turns out passing in null or an identity matrix to
BitmapData.draw. does not work correctly.
Of course, this is not always necessary when converting from a
Matrix instance to a
Matrix3D instance, but in my case it was. As you can imagine, this can have some significant performance implications, both because of memory allocation and deallocation, as well as the fact that removing the
Matrix3D, even temporarily, changes how the
DisplayObject is rendered.
Right now, this trade off is ok in my case, although I am not sure if it will be for the long term. Again, in my case, I may need to look at some re-architecting so I don’t have to work around this issue (such as nesting clips and cacheing the parent
BitmapData, so I don’t have to apply the transformations).
Anyways, I am just learning the implications of using the 2.5D APIs in the Flash Player. This is an area where there is not currently a lot of information or documentation, especially information on the implications of moving from 2D to 2.5D APIs. Hopefully this will be useful.
Here are a couple of other resources which I found useful:
Thibault Imbert’s Flash Player 10 Presentation, which has some good info on the 2.5D APIS.
As I mentioned above, I am still getting my head around some of this stuff, so if you have any clarifications or corrections, post them in the comments.