Advertisement
  1. Code
  2. Coding Fundamentals
  3. Game Development

Understanding Affine Transformations With Matrix Mathematics

Scroll to top
Read Time: 17 min
This post is part of a series called You Do The Math.
Circular Motion in AS3: Make One Moving Object Orbit Another
The Math and ActionScript of Curves: Drawing Quadratic and Cubic Curves

Inspired by Prof. Wildberger in his lecture series on linear algebra, I intend to implement his mathematical ideas with Flash. We shall not delve into the mathematical manipulation of matrices through linear algebra: just through vectors. This understanding, although diluting the elegance of linear algebra, is enough to launch us into some interesting possibilities of 2x2 matrix manipulation. In particular, we'll use it to apply various shearing, skewing, flipping, and scaling effects to images at runtime.


Final Result Preview

Let's take a look at the final result we will be working towards. Press the four directional keys - up, down, left, right - to see some effects we can achieve with affine transformations.

If you only use the left and right arrow keys, the fish appears to swim around in a pseudo-3D isometric space.


Step 1: Different Coordinate Spaces

Graphics are drawn onto coordinate spaces. So in order to manipulate them, especially to translate, rotate, scale, reflect and skew graphics, it is vital that we understand coordinate spaces. We generally make use of not just one, but multiple coordinate spaces in a single project - this is true not only for designers using the Flash IDE, but also for programmers writing ActionScript.

In Flash IDE this happens whenever you convert your drawings into MovieClip symbols: each symbol has its own origin.

Coordinate space for stage.

The image above shows the origin of the stage' coordinate space (red dot), and that of the symbol's coordinate space (registration point marked by crosshair). To know which space you are in currently, observe the bar beneath the timeline of Flash IDE as shown by the image below.

Coordinate space for symbol

(I'm using Flash CS3, so its location may differ for CS4 and CS5.) What I want to emphasize is the existence of different coordinate spaces, and the fact that you're already familiar with using them.


Step 2: The Rationale

Now there's a good reason for this. We can use one coordinate space as a reference to change the other coordinate space. This may sound alien, so I've included the Flash presentation below to facilitate my explanation. Click and drag the red arrows. Play around with it.

In the background is a blue grid, and in the foreground is a red grid. The blue and red arrows are initially aligned along the x- and y-axis of Flash coordinate space, whose center I have shifted to the middle of the stage. The blue grid is a reference grid; the gridlines will not change as you interact with the red arrows. The red grid, on the other hand, can be reoriented and scaled by dragging the red arrows.

Note that the arrows also indicate an important property of these grids. They indicate the notion of a unit of x and a unit of y on their respective grid. There are two red arrows on the red grid. Each of them indicate the length of one unit on the x-axis and the y-axis. They also dictate the orientation of the coordinate space. Let's take the red arrow pointing along the x-axis and extend it to be twice as long as the original arrow (shown in blue). Observe the following images.

Original grids without alterationOriginal grids without alterationOriginal grids without alteration
X-axis of red grid altered while blue axis remains unchangedX-axis of red grid altered while blue axis remains unchangedX-axis of red grid altered while blue axis remains unchanged

We see that the image (the green box) drawn on the red grid is now stretched horizontally, due to the fact that this red grid it is drawn onto is now twice as wide. The point I'm trying to make is rather simple: you can use one coordinate space as a basis to change another coordinate space.


Step 3: Affine Coordinate Space

So what's an "affine coordinate space"? Well, I'm sure you are careful enough to observe that these coordinate spaces are drawn using parallel grids. Let's take the red affine space for example: there's no guarantee that both the x-axis and the y-axis are always perpendicular to each other, but rest assured that however you try to tweak the arrows, you will never arrive at such a case as below.

Not affine spaceNot affine spaceNot affine space
This coordinate space is not an affine coordinate space.

In fact, x- and y-axes usually refer to the Cartesian coordinate space, as shown below.

Cartesian as a type of affine spaceCartesian as a type of affine spaceCartesian as a type of affine space

Note that the horizontal and vertical grids are perpendicular to each other. Cartesian is a type of affine coordinate space, but we can transform it to other affine spaces as we prefer. The horizontal and vertical grids do not necessarily have to be perpendicular to each other.

Example of affine spaceExample of affine spaceExample of affine space
Example of an affine coordinate space
Example of affine spaceExample of affine spaceExample of affine space
Another example of an affine coordinate space

Step 4: Affine Transformations

As you might have guessed, the affine transformations are translation, scaling, reflection, skewing and rotation.

Original affine spaceOriginal affine spaceOriginal affine space
Original affine space
scaled affine spacescaled affine spacescaled affine space
Scaled affine space
reflected affine spacereflected affine spacereflected affine space
Reflected affine space
skewed affine spaceskewed affine spaceskewed affine space
Skewed affine space
rotated affine spacerotated affine spacerotated affine space
Rotated and scaled affine space

Needless to say, physical properties such as x, y, scaleX, scaleY and rotation depend on the space. When we make calls to those properties, we are actually transforming affine coordinates.


Step 5: Understanding Matrix

I hope the images shown above are explicit enough to drive home the idea. This is because for a programmer working with FlashDevelop, we will not see those grids that the Flash IDE conveniently displays for designers. All of these have to live in your head.

Apart from imagining these grids, we also need to enlist the help of Matrix class. Thus, having a mathematical understanding of matrices is important, so we shall revise the operations of matrix here: addition and multiplication.

Matrix operations

Step 6: Geometrical Meaning of Matrix Addition

Matrix operations convery meanings geometrically. In other words, you can picture what they mean on a graph. Let's assume that we have four points in our coordinate space and would like to shift them to a set of new locations. This can be done using matrix addition. Check out the image below.

Geometrical meaning of matrix additionGeometrical meaning of matrix additionGeometrical meaning of matrix addition

As you can see, we are actually shifting the whole local coordinate space (red grids) where these four points are drawn. The notation for performing these operations is as shown below:

Notation of matrix additionNotation of matrix additionNotation of matrix addition

We can also see that this shift can actually be represented using a vector of (tx, ty). Let us differentiate vectors and static points in coordinate spaces by our use of parentheses and square brackets. I've rewritten them in the image below.

Notation of matrix addition, differentiatedNotation of matrix addition, differentiatedNotation of matrix addition, differentiated

Step 7: ActionScript Implementation

Here's a simple implementation of matrix addition. Check out the comments:

1
2
public class Addition extends Sprite
3
{
4
	public function Addition() 
5
	{
6
		var m:Matrix = new Matrix();		//instantiate matrix

7
		m.tx = stage.stageWidth * 0.5;		//shift in x

8
		m.ty = stage.stageHeight * 0.5;		//shift in y

9
		var d:DottedBox = new DottedBox();	//create the custom graphic (dotted box is a Sprite)

10
		addChild(d);
11
		d.transform.matrix = m;			//apply the matrix to our graphic

12
	}
13
}

Step 8: Geometrical Meaning of Matrix Multiplication

Matrix multiplication is somewhat more sophisticated than matrix addition but Prof Wildberger has elegantly broken it down to this simple interpretation. I shall humbly attempt to reiterate his explanation. For those who'd like to dive deeper into the understanding of linear algebra that leads to this, check out the professor's lecture series.

Let's start off by tackling the case of the identity matrix, I.

Identity matrixIdentity matrixIdentity matrix

From the image above we know that multiplying an arbitrary matrix, A, by the identity matrix, I, will always produce A. Here's an analogy: 6 x 1 = 6; the identity matrix is likened to the number 1 in that multiplication.

Alternatively, we can write the result in the following vector format which will greatly simplify our interpretation:

Vector formVector formVector form

The geometric interpretation of this formula is shown in the image below.

Interpretation of vector formInterpretation of vector formInterpretation of vector form

From the Cartesian grid (left grid), we can see the blue point is located at (2, 1). Now if we were to transform this original grid of x and y to a new grid (right grid) according to a set of vectors (below the right grid), the blue point will be relocated to (2, 1) on the new grid - but when we map this back to the original grid, it's the same point as before.

Because we are transforming the original grid to another grid that shares the same vectors for x and y, we see no difference. In fact, the changes of x and y in this transformation is nil. This is what it meant by identity matrix, from a geometrical point of view.

However, if we try to perform a mapping using other transformations, we shall see some difference. I know this was not the most revealing example to start with, so let's move on to another example.


Step 9: Scaling Along X

Scaling in x directionScaling in x directionScaling in x direction

Image above demonstrates a scaling of the coordinate space. Check out the vector of x in transformed coordinate space: one unit of the transformed x accounts for two units of the original x. On the transformed coordinate space, the coordinate of the blue point is still (2, 1). However, if you try to map this coordinate from the transformed grid onto the original grid, it is (4, 1).

This whole idea is captured by the image above. How about the formula? The result should be consistent; let's check it out.

I'm sure you recall these formulae. Now, I've added their respective meanings.

Interpretation of matrix

Now to check out the numerical result of our scaling example.

  • Original coordinate: (2, 1)
  • Vector on transformed x-axis: (2, 0)
  • Vector on transformed y-axis: (0, 1)
  • Expected result: (2*2 + 0*1, 0*2 + 1*1) = (4, 1)
Scale numerical result

They do agree with each other! Now we can happily apply this idea to other transformations. But before that, an ActionScript implementation.


Step 10: ActionScript Implementation

Check out the ActionScript implementation (and the resulting SWF) below. Note that one of the overlapping boxes is being stretched along x by a scale of 2. I have highlighted the important values. These values will be tweaked in the later steps to represent different transformations.

1
2
public class Multiplication extends Sprite
3
{
4
	public function Multiplication() 
5
	{
6
		var ref:DottedBox = new DottedBox(); 	//create reference graphic

7
		addChild(ref); ref.x = stage.stageWidth * 0.5; ref.y = stage.stageHeight * 0.5;
8
9
		var m:Matrix = new Matrix(); 		//instantiate matrix

10
		m.tx = stage.stageWidth * 0.5; 		//shift in x

11
		m.ty = stage.stageHeight * 0.5; 	//shift in y

12
		m.a = 2; m.c = 0; 
13
		m.b = 0; m.d = 1; 
14
		var d:DottedBox = new DottedBox(); 	//create the custom graphic

15
		addChild(d);
16
		d.transform.matrix = m 			//apply the matrix onto our graphic

17
	}
18
}

Step 11: Scaling X and Y

scaling x and yscaling x and yscaling x and y

Here we've scaled the grid by a factor of two along both the x- and y-axes. The blue point is at (2, 1) in the original grid before the transformation, and (4, 2) in the original grid after the transformation. (Of course, it's still at (2, 1) in the new grid after the transformation.)

And to confirm the result numerically...

Scale x and y numerical result

... they match again! To see this in the ActionScript implementation, just change the value of m.d from 1 to 2.

(Note that the direction of stretch from y is downwards, not upwards, because y increments downwards in Flash but upwards in the normal Cartesian coordinate space that I used in the diagram.)


Step 12: Reflection

reflectionreflectionreflection

Here we've reflected the grid along the x-axis using these two vectors, so the position of the blue point in the original grid changes from (2, 1) to (-2, 1). The numeric calculation is as follows:

reflect x numerical result

The ActionScript implementation is the same as before, but using these values instead: m.a = -1, m.b = 0 to represent the vector for the x transformation, and: m.c = 0 and m. d = 1 to represent the vector for the y transformation.

Next, what about reflecting simultaneously on x and y? Check out the image below.

reflectionreflectionreflection

Also, numerically computed in image below.

reflect x and y numerical result

For the ActionScript implementation... well, I'm sure you know the values to put into the matrix. m.a = -1, m.b = 0 to represent the vector for the x transformation; m.c = 0 and m. d = -1 to represent the vector for the y transformation. I've included the final SWF below.


Step 13: Skewing and Shearing

Skewing comes with a little fun. For the case of the image below, the transformed grid has had its x-axis reoriented and scaled. Compare the red arrows in both grids below: they are different, but the y-axis remains unchanged.

skewingskewingskewing
Skewing

Visually, it seems that distortion happens along the y-direction. This is true because our transformed x-axis now has a y-component in its vector.

Numerically, this is what happens...

shear in y numerical result

In terms of implementation, I've listed the tweaks below.

  • m.a = 2
  • m.b = 1
  • m.c = 0
  • m.d = 1

I'm sure at this point you'd like to try out things yourself, so go ahead and tweak

  • the orientation of transformed y-axis while maintaining the x-axis
  • the orientation of both axes altogether

I have included the Flash output for both cases as below. For readers who'd like some help with these values, check out Multiplication_final.as in the source download.


Step 14: Rotation

I consider rotation a subset of skewing. The only difference is that in rotation, the magnitude of a unit of both x and y-axis is maintained, as is the perpendicularity between the two axes.

rotation explained

ActionScript actually provides a method in the Matrix class, rotate(), to do this. But let's go through this anyway.

Now we do not want to alter the magnitude of a unit length in x and y from the original grid; just to change the orientation of each. We can make use of trigonometry to arrive at the result shown in the image above. Given an angle of roation, a, we'll get the desired result by using vectors of (cos a, sin a) for x-axis and (-sin a, cos a) for y-axis. The magnitude for each new axis will still be one unit, but each axis will be at an angle of a, compared to the originals.

For Actionscript implementation, assuming that the angle, a, is 45 degrees (that is, 0.25*Pi radians), just tweak the matrix values to the following:

1
2
var a:Number = 0.25*Math.PI
3
m.a = Math.cos(a); m.c = -1*Math.sin(a); 
4
m.b = Math.sin(a); m.d = Math.cos(a);

The full source can be referred to in Multiplication_final.as.


Step 15: Application

Having a vector interpretation of a 2x2 matrix opens up space for us to explore. Its application in manipulating bitmaps (BitmapData, LineBitmapStyle, LineGradientStyle, etc.) is widespread - but I think I'll save that for another tutorial. For the case of this article, we shall attempt to skew our sprite at run-time so that it looks like it's actually flipping in 3D.

view of isometric world
View of a pseudo-3D isometric world

From the image above we can see that, in a world with an isometric view, any graphic that's "standing" keeps its y-axis vector unchanged while the x-axis vector is rotates. Note that a unit of length for the x- and y-axis does not change - in other words, no scaling should happen in either axis, just rotation around the x-axis.

Here's an example of this idea in Flash. Click anywhere on the stage and begin dragging around to see the fish skew. Release to stop your interaction.

Here's the important bit of Actionscript. I've highlighted the crucial lines that handle the x-axis rotation. You can also refer to FakeIso.as.

1
2
private var f1:Fish, m:Matrix;
3
private var disp:Point;
4
private var axisX:Point, axisY:Point;
5
6
public function FakeIso() {
7
	disp = new Point(stage.stageWidth * 0.5, stage.stageHeight * 0.5);
8
	m = new Matrix();
9
	m.tx = disp.x; m.ty = disp.y;		//displace to the center of stage

10
	f1 = new Fish(); addChild(f1); 
11
	f1.transform.matrix = m;		//apply transformation to onto fish

12
13
	axisX = new Point(1, 0);		//vector for x - axis

14
	axisY = new Point(0, 1);		//vector for y - axis

15
	stage.addEventListener(MouseEvent.MOUSE_DOWN, start);	//start interaction

16
	stage.addEventListener(MouseEvent.MOUSE_UP, end);	//end interaction

17
}
18
19
private function start(e:MouseEvent):void {
20
	f1.addEventListener(Event.ENTER_FRAME, update);
21
}
22
23
private function end(e:MouseEvent):void {
24
	f1.removeEventListener(Event.ENTER_FRAME, update);
25
}
26
27
private function update(e:Event):void {
28
	axisX.setTo(mouseX - f1.x, mouseY - f1.y);	//determine orientation (but magnitude changed as well)

29
	axisX.normalize(1);			//fix magnitude of vector with new orientation to 1 unit

30
	apply2Matrix();				//apply matrix onto fish

31
}
32
33
private function apply2Matrix ():void {
34
	m.setTo(axisX.x, axisX.y, axisY.x, axisY.y, disp.x, disp.y);
35
	f1.transform.matrix = m;
36
}

Here, I have used the Point class for storing vectors.


Step 16: Add Keyboard Control

In this step, we shall attempt to add keyboard controls. The fish's location will update according to its velocity, velo. We'll define incremental steps for positive (clockwise) rotation and negative (anti-clockwise) rotation as well.

1
2
velo = new Point(1, 0); //velo will be used to define x-axis

3
axisY = new Point(0, 1);
4
delta_positive = new Matrix(); delta_positive.rotate(Math.PI * 0.01); //positive rotation

5
delta_negative = new Matrix(); delta_negative.rotate(Math.PI * -0.01); //negative rotation

Upon a key press, velo will rotate:

1
2
private function keyUp(e:KeyboardEvent):void {
3
if (e.keyCode == Keyboard.LEFT) {
4
	velo = delta_negative.transformPoint(velo) //rotate velo counter-clockwise

5
}
6
else if (e.keyCode == Keyboard.RIGHT) {
7
	velo = delta_positive.transformPoint(velo) //rotate velo clockwise

8
}
9
}

Now for each frame, we shall attempt to color the front side of the fish, and skew the fish as well. If the velocity, velo, has a magnitude of more than 1 and we apply it to the fish's matrix, m, we will get a scaling effect as well - so in order to eliminate this possibility, we shall normalise the velocity and then only apply that to the fish's matrix.

1
2
private function update(e:Event):void {
3
	var front_side:Boolean = velo.x > 0 		//checking for the front side of fish

4
	if (front_side) { f1.colorBody(0x002233,0.5) } 	//color the front side of fish

5
	else f1.colorBody(0xFFFFFF,0.5) 		//white applied to back side of fish

6
7
	disp = disp.add(velo); 			//update current displacement with velocity

8
	var velo_norm:Point = velo.clone(); 	//in case velo > 0, we need to recalculate 1 unit of length for x.

9
	velo_norm.normalize(1); 		//note that x-axis more than 1 will perform scaling. We dont want that for now

10
	m.setTo(velo_norm.x, velo_norm.y, axisY.x, axisY.y, disp.x, disp.y);
11
	f1.transform.matrix = m;
12
}

Step 17: Your Fish

Click the stage, then press the left and right arrow keys to see make the fish change direction.


Step 18: Another Keyboard Control

To spice things up, let us allow the control of the y-axis vector as well.

1
2
private function keyUp(e:KeyboardEvent):void {
3
if (e.keyCode == Keyboard.LEFT) {
4
	velo = delta_negative.transformPoint(velo)
5
}
6
else if (e.keyCode == Keyboard.RIGHT) {
7
	velo = delta_positive.transformPoint(velo)
8
}
9
if (e.keyCode == Keyboard.UP) {
10
	axisY = delta_negative.transformPoint(axisY)
11
}
12
else if (e.keyCode == Keyboard.DOWN) {
13
	axisY = delta_positive.transformPoint(axisY)
14
}
15
}

Also to determine the front side of the fish, we now need to incorporate the y-axis in. Here's the code for that:

1
2
var front_side:Boolean = velo.x * axisY.y > 0
3
if (front_side) { f1.colorBody(0x002233,0.5) }
4
else f1.colorBody(0xFFFFFF,0.5)

Step 19: Your No-So-Regular Fish

Well, for some the result of controlling both axes may prove to be a little confusing, but the point is that you now can skew your fish, translate it, reflect it, and even rotate it! Try out the combos of up + left, up + right, down + left, down + right.

Also, see if you can maintain the "front" side of fish (fish will be grayed). Hint: Tap up continuously, then left, then down, then right. You are making a rotation!

Conclusion

I hope you find matrix math a valuable asset to your projects after reading this article. I'm hoping to write a little more on applications of 2x2 matrix in little Quick Tips branching out of this article, and on Matrix3d which is essential for 3D manipulations. Thanks for the read, terima kasih.

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.