# Converting from Matrix3D to Matrix in ActionScript 3

For the past month or two, I have been spending time building a game (something I haven’t 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 `BitmapData.draw`

.

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 `BitmapData.draw`

.

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 `Matrix`

and `Matrix3D`

contain. First, here are the values held by a `Matrix`

instance:

```
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 `Matrix3d`

to `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 `BitmapData.draw`

class.

For our purposes, the `Matrix3D`

mapping to `Matrix`

is:

```
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[0];
matrix.c = rawMatrixData[1];
matrix.tx = ship.x - shipBounds.x;
matrix.b = rawMatrixData[4];
matrix.d = rawMatrixData[5];
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:

```
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 `DisplayObject`

.

**Update**: as Ben Garney points out in the comments, since the transformation is applied to `BitmapData.draw`

when `Matrix3D`

is set, all we need to do is pass in either an identiy matrix, or null to the second argument of the API. So, in my case, this solves my problems, and removes the need to convert bewteen the matrix types.

~~The updated code is simply:~~

```
shipBmpData = new BitmapData(shipBounds.width,
shipBounds.height, true, 0);
shipBmpData.draw(ship, null);
```

~~which turns out to be much less complex than when you use the 2D APIs. However, the technique above for converting between matrix types is still valid.~~

**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 caching 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:

API docs for Matrix, Matrix3D, Transformation and DisplayObject.

Thibault Imbert’s Flash Player 10 Presentation, which has some good info on the 2.5D APIS.

Flash CS4 Docs : Working in three dimensions

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.