2

I want to create an isometric map. The map exists of isometric rectangles like in the image: isometric planes / (or 'rectangles')

I want to represent each rectangle as a 2d rectangle with a width and a height. So when for example you are creating the map in an editor, you could draw 2d rectangles and this creation can be converted to isometric planes. The width of an isometric plane is north to south-east (from the top of the diamond shape, to to the right.) And the height is from north to south-west. (From top to left)

Now I've got two problems.

First problem

I'm drawing this isometric map on a canvas, I am using offscreen canvases for drawing each plane seperately. I want to calculate the 2d width and height for the offscreen canvas. I used this math for doing this:

var canvasWidth  = Math.cos(30) * planeWidth + Math.cos(30) * planeHeight;
var canvasHeight = Math.sin(30) * planeWidth + Math.cos(30) * planeHeight;
// still remember that planeWidth and planeHeight are isometric, so oblique sides

But this doesn't give me the right sizes for the canvas. Something else that I wonder is whether it is wise to calculate oblique sides with pixels.

Second problem I want to use a 'texture' image for the planes. So, I've got images like: Tile images

My idea was to store the oblique sides of each image, so like the widths and the heights of the planes. When i want to draw a plane with a specific image, I can repeat that image.

The main goal is to create isometric planes with pixel-precise given width and heights. And the planes should have an image as texture. The images are already isometric, so they can be repeated and clipped like isometric tiles.

My questions: 1. I wonder wether it is wise to use the oblique sides as widths and heights, isn't it better to just find a way to keep using 2d sizes anyway, somehow?

  1. Isn't it smarter to create a 2d rectangle on the offscreen canvases, and fill them with a not-isometric texture image, and then transform that canvas.

  2. If I use the oblique sides to represent the width/heights of my planes and images, are that lengths true lengths? It's like a linear function: It are just pixels. Per 2 horizontal, 1 vertical. An odd number for going horizontal, will not give a whole number for going vertical. (And I assume this will give me unnecessary trouble later).

user2190492
  • 1,174
  • 2
  • 9
  • 37
  • The map is 2D 2.5D or 3D? have you consider the use of sprites? take a look at http://stackoverflow.com/a/36454198/2521214. Also do not use `sin, cos` they can add rounding errors to final pixel output (leave x as is and divide y by 2 instead). A good idea is to limit the cell/tile/sprite sizes to be divisible by 2 or 4 to avoid rounding problems. – Spektre Apr 08 '16 at 21:13
  • The answer that that was given to the "duplicate" question does not answer this question as it does not give an accurate isometric projection (namely 1 to 0.5 pixel ratio) as requested by this question. – Blindman67 Apr 09 '16 at 07:05

1 Answers1

2

To draw an isometric plane use the transformation matrix to set the x and y axis.

Assuming that the x axis is along the vector (1,0.5) and the y axis is along the vector (-1,0.5) with the origin still at the top left.

Defined as such.

var xAxis = {
    x : 1,
    y : 0.5,
}
var yAxis = {
    x : -1,
    y : 0.5,
}
var origin = {
    x : 0,
    y : 0,
}

To set the transformation just use

ctx.setTransform(xAxis.x, xAxis.y, yAxis.x, yAxis.y, origin.x, origin.y);

Now all rendering is on that isometric plane.

You can set the axis to which ever isometric projection you wish. The one I used will increase the area of a pixel. It will all depend on the length and relative direction of the x and y axis.

Update

Simply out of interest.

As many isometric projections have a tendency to change the total area of each pixel I have included a simple calculation that will normalise pixel area no matter the projection used. (for the projection I gave it happens to be one over the square root of 2)

With the xAxis and yAxis calculate the area of a pixel for that projection

var area = (xAxis.x * ( xAxis.y + yAxis.y ) + ( xAxis.x + yAxis.x ) * yAxis.y) - (xAxis.y * ( xAxis.x + yAxis.x ) + ( xAxis.y + yAxis.y ) * yAxis.x)

The one over the square root of the area is then the scale requiered

var scaleBy = 1 / Math.sqrt(area);

You can apply that directly to the axis like so...

xAxis.x *= scaleBy;
xAxis.y *= scaleBy;
yAxis.x *= scaleBy;
yAxis.y *= scaleBy;
ctx.setTransform(xAxis.x, xAxis.y, yAxis.x, yAxis.y, origin.x, origin.y);

Or apply it via ctx.scale after you set the transform

ctx.setTransform(xAxis.x, xAxis.y, yAxis.x, yAxis.y, origin.x, origin.y);
ctx.scale(scaleBy, scaleBy);

Example

The demo shows simple application of this method (without pixel normalisation).

    
    var canvas = document.createElement("canvas"); 
    canvas.width          = window.innerWidth;
    canvas.height         = window.innerHeight;
    canvas.style.position = "absolute";
    canvas.style.left     = "0px";
    canvas.style.top      = "0px";
    document.body.appendChild(canvas);
    
function demo(){
    canvas.width  = window.innerWidth;
    canvas.height = window.innerHeight;
    var ctx       = canvas.getContext("2d"); 

    ctx.font         = "80px arial black";
    ctx.lineWidth    = 8;
    ctx.lineJoin     = "round"
    ctx.strokeStyle  = "green";
    ctx.fillStyle    = "#aF6";
    ctx.textAlign    = "center";
    ctx.textBaseline = "middle";

    // the axis and origin
    var xAxis  = {x : 1, y: 0.5};
    var yAxis  = {x : -1, y: 0.5};
    var origin = {x : 0, y : 0};

    // cludge factor dividing by two to fit the display area
    ctx.setTransform(xAxis.x / 2, xAxis.y / 2, yAxis.x / 2, yAxis.y / 2, origin.x, origin.y);

    // draw big text
    ctx.strokeText("Isometric",ctx.canvas.width/2,0);
    ctx.fillText("Isometric",ctx.canvas.width/2,0);

    // half size text
    ctx.font      = "40px arial black";
    ctx.lineWidth = "4";
    ctx.strokeText("projection",ctx.canvas.width/2,60);
    ctx.fillText("projection",ctx.canvas.width/2,60);

     // tiny text
    ctx.font      = "16px arial black";
    ctx.lineWidth = "3";
    ctx.strokeText("using 2D context transformation",ctx.canvas.width/2,100);
    ctx.fillText("using 2D context transformation",ctx.canvas.width/2,100);

    // add an image to demonstrate that the project will be applied to 
    // anything that is rendered.
    var image = new Image();
    image.src = "https://i.stack.imgur.com/C7qq2.png?s=328&g=1";
    image.addEventListener("load",function(){
        ctx.drawImage(this,ctx.canvas.width-this.width,-this.height * 1.2);
    });
}
demo();
window.addEventListener("resize",demo);
Blindman67
  • 51,134
  • 11
  • 73
  • 136