2013-04-29

Canvas Trains

Intro

This is the fourth post in the "canvas drawing series". This one is about a small railway system and the trains directed by the control tower. I won't cover the basics of drawing etc. because it's done in the previous posts on the canvas:

  1. Canvas Bus
  2. Canvas Crossroad
  3. Canvas Balloons
Most of the techniques and patterns are also described in the previous posts, so I'll kind of concentrate only on the specifics of this example.

Rotating cars depending on the current rail line

Calculating the car angle depending on the current rail line:

  Rail.prototype.toAngle = function (x) {

   for (var i = 0; i < this.railLines.length; i++) {
    // find the rail line
    if (this.railLines[i].x1 <= x && x <= this.railLines[i].x2) {
     // calculate the angle
     var dy = (this.railLines[i].y2 - this.railLines[i].y1);
     var dx = (this.railLines[i].x2 - this.railLines[i].x1);
     
     return Math.atan2(dy, dx);
    }
   }

   return 0;
  };

Canvas text output

There are a couple of catches when working with canvas text. The first one is using the textBaseline property. The other is calculating the actual text width on a canvas used to center the text about train queue in the middle:

  ctx.fillStyle = textColor;
  ctx.font = '20px arial';
  var text = 'Sample text';
  var metrics = ctx.measureText(text);
  console.log(metrics.width);

The rail system

The rail system is made relative to the available canvas space:

  var RailSystem = function () {
   this.rails = [

   new Rail(0, [
    new RailLine(0, height/2, width/9, height/2)
   ]),
   new Rail(1, [
    new RailLine(width/9, height/2, width * 2/9, height/3),
    new RailLine(width * 2/9, height/3, width * 3/9, height/3),
    new RailLine(width * 3/9, height/3, width * 4/9, height/2)
   ]),
   new Rail(2, [
    new RailLine(width/9, height/2, width * 4/9, height/2)
   ]),
   new Rail(3, [
    new RailLine(width/9, height/2, width * 2/9, height * 2/3),
    new RailLine(width * 2/9, height * 2/3, width * 3/9, height * 2/3),
    new RailLine(width * 3/9, height * 2/3, width * 4/9, height/2)
   ]),
   new Rail(4, [
    new RailLine(width * 4/9, height/2, width * 5/9, height/2)
   ]),
   new Rail(5, [
    new RailLine(width * 5/9, height/2, width * 6/9, height/3),
    new RailLine(width * 6/9, height/3, width * 7/9, height/3),
    new RailLine(width * 7/9, height/3, width * 8/9, height/2)
   ]),
   new Rail(6, [
    new RailLine(width * 5/9, height/2, width * 8/9, height/2)
   ]),
   new Rail(7, [
    new RailLine(width * 5/9, height/2, width * 6/9, height * 2/3),
    new RailLine(width * 6/9, height * 2/3, width * 7/9, height * 2/3),
    new RailLine(width * 7/9, height * 2/3, width * 8/9, height/2)
   ]),
   new Rail(8, [
    new RailLine(width * 8/9, height/2, width, height/2)
   ])

   ];
  };

Train states

Every train goes trough the following states:

  • Creation - dat.gui controlled (counter) on every drop to zero and free entry rail
  • firstTurn - takes random free rail and goes into queue for the middle rail
  • waitingForMiddle - remains in this state until the middle rail is free for it to pass
  • goIntoMiddle - goes to the middle rail and passes trough
  • pickWayOut - waits on the trains going out and picks a rail going out of the rail system
  • goToExit - performs the exit, cleans the data describing the train

The states are self explanatory but the transitioning logic is a bit complex and it'll not be listed. But feel free to look the states up in the source code provided.


And here's my example on:
Codepen
GitHub

No comments: