I've been working on porting an old closed source game to Javascript with Canvas and I've come to a slight problem.

Right now, to display the land, I just have a pretty basic loop that just draws the 44px by 44px diamond-shaped tiles using ctx.drawImage().

It essentially looks like the left side of the image, while I need them to "stretch" to fill the entire tile (like on the right). The tiles have a Z axis which is just in the +j direction on the screen and it's supposed to look like a hill or incline.


Anyway, this leads to my question: Is there a way to correctly do this without using webgl? If not: would it be better to use webgl directly to do this, or use a library to handle it for me?

EDIT 2: Well, after thinking for a while, I remembered that affine transformations are only linear (duh), so I won't be able to use certain transformations like transforming corner tiles... so I guess I can use the transformation matrices for the "light green" tiles, but not the pink tile in this example.

non linear example

EDIT: With Phillips answer, I was able to create a few matrices to apply an affine transformation to the tile. However, I pretty much did "guess and check" to calculate the values for only one Z-value, but I can't seem to create a general form of it.

I'm guessing for different angles (like when there is an incline going from east->west, west->east, north->south, south->north), I will have to use a ton of if-statements for each scenario, but that's not much of a big deal.

I have the JSFiddle here: http://jsfiddle.net/awwPP/1/

window.onload = function(e) {
    multiply = function(a, b) {
        return [a[0]*b[0] + a[1]*b[2], 
               a[0]*b[1] + a[1]*b[3], 
    multiplyAll = function() {
        var result = arguments[0];
        for(var i = 1; i < arguments.length; i++) {
            result = multiply(result, arguments[i]);
        return result;
    drawDiamond = function(ctx, x, y) {

        ctx.moveTo(x, y);
        ctx.lineTo(x+22, y+22);
        ctx.lineTo(x, y+44);
        ctx.lineTo(x-22, y+22);
        ctx.lineTo(x, y);

    var canvas = document.getElementById('c');
    var ctx = canvas.getContext('2d');
    var z = 2 * 3; // each z is like 2px up
    ctx.mozImageSmoothingEnabled = false;

    ctx.fillStyle = 'rgba(50, 50, 50, 0.5)';
    ctx.strokeStyle = 'rgba(255, 50, 50, 0.5)';

    drawDiamond(ctx, 44, 22);
    drawDiamond(ctx, 66, 44);
    drawDiamond(ctx, 88, 66);

    var cos = Math.cos(Math.PI/4);
    var sin = Math.sin(Math.PI/4);

   // just negate 
    var nCos = Math.cos(-Math.PI/4);
    var nSin = Math.sin(-Math.PI/4);

    var result = multiplyAll(
      //rotate it so the tile is a square:
      [cos, sin, 
      -sin, cos], 

      // scale the x axis (although it's actually the Y)
      // to make it look longer
      [50/44, 0,
      0, 1],
      // scew the opposite axis by factor K
      [1, -0.12, 
      0, 1], 

      //negate the rotation
      [nCos, nSin, 
      -nSin, nCos]);

    ctx.setTransform(result[0], result[1],
                     result[2], result[3],
                //dunno why there is an x/y offset
                     0, 10);

    drawDiamond(ctx, 66, 0 - z);
    drawDiamond(ctx, 88, 22 - z);
    drawDiamond(ctx, 110, 44 - z);  

    ctx.setTransform(1, 0,
                         0, 1,
                         0, 0);
    drawDiamond(ctx, 88, -22 - z);
    drawDiamond(ctx, 110, 0 - z);
    drawDiamond(ctx, 132, 22 - z);      

<canvas id="c" width="200" height="200"></canvas>
  • \$\begingroup\$ I am not understanding the problem. Is there a reason you don't simply draw the tiles several pixels down and to the left so that the images overlap? \$\endgroup\$
    – jhocking
    Jan 31, 2013 at 3:28
  • 1
    \$\begingroup\$ Well, the "higher" tiles are supposed to represent a higher Z value, so it's supposed to be a incline/hill of sorts. \$\endgroup\$ Jan 31, 2013 at 5:27
  • \$\begingroup\$ @jhocking I added a better example image. \$\endgroup\$ Jan 31, 2013 at 5:32
  • \$\begingroup\$ ah got it now, thanks for adding in the z axis explanation \$\endgroup\$
    – jhocking
    Jan 31, 2013 at 16:04

3 Answers 3


It is not possible to apply an affine transformation to the tile and get the result you want, because affine transformations preserve coplanarity, whereas the shape you need is not flat in the 3D world. Even if you got the outline to match (which can be done), the texturing would be all wrong and cause seams at the junctions.

I suggest splitting the tile in two triangles first. There are two ways to do so, but the first one (vertical split) will probably be easier later on:

split quad into triangles

The good thing is that it can be done without any additional calculations. You just need to render twice and put transparency in the unused part of the images. You can then reuse exactly the same transformation as for your light green tiles:


  • \$\begingroup\$ I suggest using split on the left for concave corners, and a split on the right for convex corners. Looks more natural, I suppose. \$\endgroup\$
    – Helbreder
    Feb 2, 2013 at 11:11
  • \$\begingroup\$ @Helbreder Can you explain what you mean by "left" and "right" splits? \$\endgroup\$ Feb 2, 2013 at 11:18
  • \$\begingroup\$ As shown on pictures. The left split - I mean a split of a non-planar tile that goes from a upper tile (in means of a height, z) to lower. The right split - this is the one that divides a non-planar tile with a line that goes from left tile to the right tile (on a same plane; picture on the right). \$\endgroup\$
    – Helbreder
    Feb 2, 2013 at 11:44
  • 1
    \$\begingroup\$ @Helbreder oh I understand now. I’ll go with “horizontal” and “vertical” then :-) But I think the problem with the horizontal split is that it requires a whole new transformation to be computed. \$\endgroup\$ Feb 2, 2013 at 12:04
  • \$\begingroup\$ Yay! It works! You have no idea how much I love you right now. (PS: I gotta wait a few hours before rewarding the bounty) \$\endgroup\$ Feb 2, 2013 at 19:25

This is how I'd do it without matrices, sines and divisions. Just integer math. Optimized for clarity, but it can obviously be optimized for speed with few changes:

// (0, 0) is at the top of the diamond. This is where we'll start drawing
sourceX = 200;
sourceY = 20;

// How much we move on the X axis for each tile
tileWidth = 30;
// How much we move on the Y axis for each tile
tileHeight = 15;
// How much we move on the Y axis for each unit of depth
tileDepth = 10;

// The size of the field, and the field itself
width = 5;
height = 5;
depths = [
    [0, 0, 0, 0, 0],
    [0, 0, 1, 0, 0],
    [0, 1, 2, 2, 2],
    [0, 0, 2, 3, 1],
    [0, 0, 0, 1, 0]

function getDepth(x, y)
    // Clamp to the borders, but can be modified for wrapping
    if (x < 0)
        x = 0;
    else if (x >= width)
        x = width - 1;
    if (y < 0)
        y = 0;
    else if (y >= height)
        y = height - 1;
    return depths[y][x];

function getDrawingPos(x, y)
    // Get the drawing position for a given point
    var destX = sourceX + ((x - y) * tileWidth);
    var destY = sourceY + ((x + y) * tileHeight) - (tileDepth * getDepth(x, y));
    return {"x": destX, "y": destY};

function drawDiamond(ctx, x, y)
    // Just find the points for all the corners..
    var point0 = getDrawingPos(x, y);
    var point1 = getDrawingPos(x + 1, y);
    var point2 = getDrawingPos(x + 1, y + 1);
    var point3 = getDrawingPos(x, y + 1);


    // And draw them
    ctx.moveTo(point0.x, point0.y);
    ctx.lineTo(point1.x, point1.y);
    ctx.lineTo(point2.x, point2.y);
    ctx.lineTo(point3.x, point3.y);
    ctx.lineTo(point0.x, point0.y);

function drawField(ctx)
    for (var x = 0; x < width; x++)
        for (var y = 0; y < height; y++)
            drawDiamond(ctx, x, y);

var canvas = document.getElementById('c');
var ctx = canvas.getContext('2d');
ctx.mozImageSmoothingEnabled = false;

ctx.fillStyle = 'rgba(50, 50, 50, 0.5)';
ctx.strokeStyle = 'rgba(255, 50, 50, 0.5)';



  • \$\begingroup\$ Looks nice, but it will be hard to stretch textures along that tiles. \$\endgroup\$
    – Tarion
    May 10, 2014 at 16:50

You could solve this in a pure 2d way by using a projection matrix. I wrote a quite extensive answer about how to use projection matrices in Canvas to turn orthogonal tiles into isometric tiles for a different question.

When your map tiles are all stretched in different ways, you will have to change your projection matrix before drawing each tile.

The 3x3 projection matrices which are offered by the 2d context allow you to do all kinds of perspective transformations to map tiles except for perspective shortening (making close objects larger than small objects).


You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .