isomer-route

Isometric drawing tool

2020-04-28

About

isomer-route allows you to draw complex isometric shapes and illusions with ease on the HTML5 canvas, extending on the great work ofisomer. Inspired by isometric illusions such as thePenrose triangleand the beautiful gameMonument Valley

Usage

yarn install isomer-route
import IsomerRoute from 'isomer-route'
const canvas = document.querySelector('#isomer-canvas');
const route = new IsomerRoute(canvas);

To start, we can draw a simple path in just a few lines of code

route
  .addTrack(6, DIR.X)
  .addTrack(6, DIR.Y)
  .addColumn(6, DIR.DOWN)
  .draw();

Grid

Here is the same route overlaid with a 3D grid. This is how objects look under an isometric projection. The red lines show the X direction and the blue lines show the Y direction

route
  .addTrack(6, DIR.X)
  .addTrack(6, DIR.Y)
  .addColumn(6, DIR.DOWN)
  .drawGrid(6)
  .draw();

Block Groups

isomer-route is divided into 3 basic components: Track,Column, and Stairs. A block group consists of many blocks, which is a wrapper around an isomer Prismwhich contains many Paths which contain manyPoints

A route consists of many of these block groups, where the origin of the next block group is at the end of the previous. Block groups also allow for complex rotations around any point, or a reference point such as thecenter,startorendof the shape.

Track

Tracks are block groups with the same z value which go in either the X orY direction, by any positive or negative amount.

route
  .addTrack(6, DIR.X)
  .addTrack(6, DIR.Y)
  .addTrack(-7, DIR.X)
  .addTrack(-6, DIR.Y)
  .draw();

Column

Columns are block groups which increase in z value which go in either the UP orDOWN direction, by any positive amount.

route
  .addColumn(4, DIR.UP)
  .addTrack(3, DIR.X)
  .addColumn(4, DIR.DOWN)
  .draw();

Stairs

Stairs are block groups which increase in z and X orY direction, by any positive amount.

route
  .addStairs(2, DIR.X)
  .addTrack(1, DIR.X)
  .updateOrigin(-1, 0, 0)
  .addStairs(2, DIR.Y)
  .draw();

Transformations and rotations

Combining those 3 fundamental building blocks allows you to build complex routes. Remember to call .draw() at the end to draw your route in the right order

Route rotations

You can rotate the entire "route" around the z axis by calling.setRotation() with some multiple of Math.PI. The rotation origin is defined by the the center of the grid. You can change the grid size by either calling .setGridSize(size) or by explicitly drawing the grid with .drawGrid(size)

In the example below, clicking the "Rotate" button will rotate the shape 90deg

route
  .setRotation(rotation)
  .setGridSize(6)
  .addTrack(6, DIR.X)
  .addTrack(6, DIR.Y)
  .addColumn(6, DIR.DOWN)
  .draw();

Block group rotations

An individual block group may be rotated about a point or a reference point by providing a transformation callback to the addTrackoraddColumn methods.

route
  .setGridSize(12)
  .updateOrigin(8, 8, 0)
  .addTrack(-4, DIR.X)
  .addTrack(-8, DIR.Y)
  .addColumn(4, DIR.UP)
  .addColumn(4, DIR.UP, block =>
    block.setColor(darkGreen).rotateYEnd(-rotation),
  )
  .updateOrigin(0, 1, -1)
  .addTrack(4, DIR.Y, block =>
    block.setColor(darkGreen).rotateAlongAxis(-rotation),
  )
  .addTrack(4, DIR.Y)
  .draw();

Joints

You may notice that the problem with the above example is that when the dark green track is eventually rotated to be in line with the light track, the dark green one appears on top of the other, which is in fact the case and the drawing order respects that. To improve the illusion you can draw them at the same z level and join the columns using .addStartExtrusion and .addEndExtrusion. While this is more complicated it allows you to produce much nicer illusions.

The example below looks identical to the one above at first glance, but in fact the surfaces that eventually connect are drawn at the same z level, and the columns which create the illusion of depth are joint using start and end joints to occlude the joining faces. After rotating the block you'll notice that the illusion is much more effective because none of the faces are above the other, they line up perfectly

route
  .setGridSize(12)
  .setOrigin(Point(3, -1, 3))
  .addColumn(2, DIR.UP, block => block.addStartExtrusion())
  .addColumn(5, DIR.UP, block =>
    block.setColor(darkGreen).rotateYEnd(-rotation),
  )
  .updateOrigin(0, 1, -1)
  .addTrack(4, DIR.Y, block =>
    block.setColor(darkGreen).rotateAlongAxis(-rotation),
  )
  .addTrack(5, DIR.Y)
  .setOrigin(Point(-3, -2, 10))
  .addTrack(-5, DIR.X)
  .addTrack(-10, DIR.Y)
  .addColumn(2, DIR.UP, block => block.addEndExtrusion())
  .draw();

Reference

IsomerRoute

Most of the methods below return the instance, in a builder like fashion


constructor(canvas, origin, color)
  • canvas - reference to the HTML canvas element
  • origin = Point(0, 0, 0) - the starting isomerPoint
  • color = Color(59, 188, 188) - the default blockColor
clearCanvas()

Clears the canvas. Helpful when animating the route

setColor(color)

Changes the default color

updateOrigin(dx, dy, dz)

Updates the current origin by the provided values, each default to zero

setOrigin(origin)

Sets the current origin

  • origin - an isomer Point
setGridSize(size)

Sets the grid size, used when drawing the grid, or used to calculate the center of the grid when rotating

  • size - an integer
drawGrid(size, drawNegative)

Sets the grid size and draws it on the canvas. It is better to call this at the end, right before .draw() because it doesn't respect drawing order

  • size - an integer
  • drawNegative = false - when true it doubles the size of the grid, effectively drawing it to the canvas edge
rotate(rotation)

Updates the current rotation by the provided amount which rotates the route around the center of the grid by the z axis.

  • rotation = Math.PI/8- an integer, preferably a multiple of pi
setRotation(rotation)

Sets the current rotation around z around the center of the grid.

addColumn(height, direction, transformation)
  • height - a positive integer
  • direction - one of UP or DOWN
  • transformation - a function which transforms the current block group, using any of the methods defined on theBlockGroup below.
addTrack(length, direction, transformation)
  • length - a positive or negative integer
  • direction - one of X or Y
  • transformation - a function which transforms the current block group, using any of the methods defined on theBlockGroup below.
addStairs(length, direction, incrementPerStair, transformation)
  • length - a positive integer
  • direction - one of X or Y
  • incrementPerStair = 5 the approx number of small steps in a block
  • transformation - a function which transforms the current block group, using any of the methods defined on theBlockGroup below.
draw()

The last thing you need to call, it sorts the provided blocks using topological sort and then draws them in the right order (if it can)


BlockGroup

Most of the methods below work on Track, Column and Stairs and return the instance, as above


setColor(color)

Changes the color of the block group, useful when you want different coloured blocks

rotateXStart(rotation)

Also, rotateYStart and rotateZStart.
Rotates the block group around the specified axis about the start point of the block group by the specified rotation

rotateXCenter(rotation)

Also, rotateYCenter and rotateZCenter.
Rotates the block group around the specified axis about the center point of the block group by the specified rotation

rotateXEnd(rotation)

Also, rotateYEnd and rotateZEnd.
Rotates the block group around the specified axis about the end point of the block group by the specified rotation

rotateAlongAxis(rotation)

Rotates the block group around its direction by the specified rotation. Similar to a barrel roll

rotateX(point, rotation)

Also, rotateY and rotateZ.
Rotates the block group around the specified axis about the provided point by the specified rotation

  • point - an isomer Point specifying the origin of the rotation
  • rotation - an integer specifying the rotation amount


Credits

This project wouldn't have been possible without the underlying work done by@jdanonisomer. I also received a lot of help in getting my head around the isometric grid, drawing order and transformations from my two roommatesOisin MoranandConor Power. They were also excellent rubber ducks. Go check out their cool stuff.

Another really helpful resource, and interesting read isDrawing isometric boxes in the correct order, which helped me calculate which box is in front when comparing two boxes, how to draw them in the right order and how to solveimpossible cases.

For more fun examples check out myblog post