# HTML5 Canvas Converting between cartesian and isometric coordinates

I'm having issues wrapping my head around the Cartesian to Isometric coordinate conversion in HTML5 canvas. As I understand it, the process is two fold:

(1) Scale down the y-axis by 0.5, i.e. ctx.scale(1,0.5); or ctx.setTransform(1,0,0,0.5,0,0); This supposedly produces the following matrix:

[x; y] x [1, 0; 0, 0.5]


(2) Rotate the context by 45 degrees, i.e. ctx.rotate(Math.PI/4); This should produce the following matrix:

[x; y] x [cos(45), -sin(45); sin(45), cos(45)]


This (somehow) results in the final matrix of ctx.setTransform(2,-1,1,0.5,0,0); which I cannot seem to understand... How is this matrix derived? I cannot seem to produce this matrix by multiplying the scaling and rotation matrices produced earlier...

Also, if I write out the equation for the final transformation matrix, I get:

newX = 2x + y
newY = -x + y/2


But this doesn't seem to be correct. For example, the following code draws an isometric tile at cartesian coordinates (500, 100).

ctx.setTransform(2,-1,1,0.5,0,0);
ctx.fillRect(500, 100, width*2, height);


When I check the result on the screen, the actual coordinates are (285, 215) which do not satisfy the equations I produced earlier... So what is going on here? I would be very grateful if you could:

(1) Help me understand how the final isometric transformation matrix is derived;

(2) Help me produce the correct equation for finding the on-screen coordinates of an isometric projection.

Many thanks and kind regards

The issue you have is that you're not taking into account the effect of the translation of the square away from the origin.

ctx.rotate and ctx.scale both work relative to the top left of the canvas, at co-ordinates (0, 0). What this means is when you rotate your top left corner at (500, 100), it will move 45 degrees in an arc around from there. when it is scaled, it will just scale these corner positions, not the size of the rectangle around the corners.

When you want to scale and rotate the square, you need to choose the anchor point on the square around which you rotate and undo the translation first. For example, here's an example using the top-left corner:

jsfiddle.net/9atht/1/

ctx.translate(500, 100);
ctx.scale(1, 0.5);
ctx.rotate(Math.PI/4);
ctx.translate(-500, -100);
ctx.fillRect(500,100,20,20);


Note how the transforms are loaded in reverse - the order these are applied is bottom to top.

Clearly it's a bit fiddly to constantly undo then re-do the translation, so a slightly better way is to do your transformations around zero then move it out to where you want it:

jsfiddle.net/auVrK/

ctx.translate(500, 100);
ctx.scale(1, 0.5);
ctx.rotate(Math.PI/4);
ctx.fillRect(0, 0, 20, 20);


Clearly again here the top-left corner in both is the same. If we want the middle of each square to be in the same place (and generally you do since it's the easiest way of thinking about it), we draw our square so the middle of it is initially at zero:

http://jsfiddle.net/hfjuG/

ctx.translate(500, 100);
ctx.scale(1, 0.5);
ctx.rotate(Math.PI/4);
ctx.fillRect(-10, -10, 20, 20);


With each tile you draw you'll need to translate relative to the last one you drew:

http://jsfiddle.net/fZTuF/

ctx.fillStyle="yellow";
ctx.translate(20, 0);
ctx.fillRect(-10, -10, 20, 20);


On the plus side, you can work in your 'world' space to do this. On the down side, you still need to draw from the furthest to the nearest, so you'll have to work diagonally across your map. :)

Last, but not least: I have no idea where on earth you got that transformation matrix from. I work it out to be:

0.707  -0.707    *    1  0     =    0.707  -0.3535
0.707   0.707         0  0.5        0.707   0.3535


which, when you put these into ctx.setTransform (along with the translation X and Y in the last two parameters) yields the correct result:

jsfiddle.net/3ugzy/1/

But only works if the tile is centred at the origin. This is because if not, the offset from the origin also gets factored in to the transform and it goes slightly wonky.

Hope that massive example helps! Sorry about the lack of hyperlinks, but I've kept the important ones in and de-linked the others to keep the spam police happy.