Skip to main content
10 of 10
added 384 characters in body

Basic isometric projection in Javascript

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.

screenshot

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/

<script>
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], 
               a[2]*b[0]+a[3]*b[2], 
               a[2]*b[1]+a[3]*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.beginPath();

        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);
        ctx.stroke();
        ctx.fill();
    };

    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)';

    //left:
    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);
            
    //middle:
    drawDiamond(ctx, 66, 0 - z);
    drawDiamond(ctx, 88, 22 - z);
    drawDiamond(ctx, 110, 44 - z);  

    //right:
    ctx.setTransform(1, 0,
                         0, 1,
                         0, 0);
    drawDiamond(ctx, 88, -22 - z);
    drawDiamond(ctx, 110, 0 - z);
    drawDiamond(ctx, 132, 22 - z);      
}
</script>

<canvas id="c" width="200" height="200"></canvas>