Skip to main content
8 of 10
forgot url

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.

enter image description here

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?

There is an open source example written in C# for XNA that I was looking at which correctly renders tiles, but I can't seem to do anything like it in Javascript: https://code.google.com/searchframe#WoZ9Hre0UW0/trunk/UltimaXNA/UltimaXNA/TileEngine/Objects/MapObject.cs&q=drawiso%20package:ultimaxna\.googlecode\.com

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>