Intro
This is the third post in the "canvas drawing series". This one is about bus picking up passengers and distributing them to their destinations. I won't cover basics of drawing etc. because it's done in the previous posts on the canvas:
The main difference to the previous examples is that this one makes a complete redraw every time because there would be a lot of sprite redrawing and recalculating to remove all of the last states of sprites. Also, keep in mind that the browser does a very good job of repainting and keeps an internal off-screen canvas so you don't have to. The animation loop and sprite drawing of this example is in fact pretty simple:(function animloop(){ requestAnimFrame(animloop); ctx.clearRect(0, 0, canvas.width, canvas.height); sim.draw(); })();There are four main entities involved in this simulation:
- Passengers
- Bus
- Stations
- Simulation master
Passengers appear on the stations in regular intervals and wait in the queue for the bus to pick them up. Their destination is denoted by their color, so the passenger appearing on the station always have a color different then the color of the station. The algorithm for color picking is listed at the end of this post.
Bus is drawn with simple geometric shapes and has an aisle and seats for the passengers. There is a separate space for the driver, the door and blinkers for signalization to the rest of the traffic. The bus has fixed loading time, but if it becomes full it's going to leave the station.
Stations are represented by their waiting queues plus the loading and unloading spot. As mentioned before the new passenger arrival interval is linear, but can be very easily configured to take some kind of arrival event time distribution, since the main focus is on the canvas drawing it's just plain simple linear time. Every station has a capacity, so no new passengers arrive when the station is full.
Simulation master is the god object of this simulation and directs the bus around and tells it when to load, unload passengers and syncs it with the stations. It's the usual way of doing this type of simulations.
Passengers loading
The standard bus has an aisle and seats. In every draw cycle while loading the passengers the simulation checks if the station has passengers to load. Before the passenger enters, the bus assigns a seat to the passenger by using this function:
function toXY(seat) { var x = this.rightLine; if (seat % 2 === 0) { x = this.leftLine; } var y = this.startingRow + Math.floor(seat / 2) * passengerSize; return { x : x, y : y }; }
The loading passenger then goes trough the following states:
- outside - ends by going to the beginning of the aisle
- upTheLane - ends by reaching the destination row
- takeSeat - the passenger takes the left or the right seat
It's all better described with the following code listing:
if (state == 'outside') { if (loadingPassenger.x + passengerSpeed <= middleLine) { loadingPassenger.x = middleLine; loadingPassenger.state = 'upTheLane'; } else { loadingPassenger.x -= passengerSpeed; } } else if (state == 'upTheLane') { if (loadingPassenger.y + passengerSpeed >= toXY(loadingPassenger.destinationSeat).y) { loadingPassenger.y = toXY(loadingPassenger.destinationSeat).y; loadingPassenger.state = 'takeSeat'; } else { loadingPassenger.y += passengerSpeed; } } else if (state == 'takeSeat') { var dx = passengerSpeed; var destinationSeat = toXY(loadingPassenger.destinationSeat); if (destinationSeat.x == leftLine) { dx = -dx; } if (loadingPassenger.x + dx <= leftLine || loadingPassenger.x + dx >= rightLine) { loadingPassenger.x = destinationSeat.x; passengers[loadingPassenger.destinationSeat] = loadingPassenger; loadingPassenger = null; } else { loadingPassenger.x += dx; } }
Unloading passengers
In every draw cycle while the bus is in the station there is a check for unloading passengers, basically a check if there are passengers in the bus with the color of the current station. Every unloading passenger goes trough the following states:
- stepToLineL or stepToLineR - depending on the taken seat
- goToDoor
- getOut
Bus and Station
Drawing a bus and a station is pretty straight forward. To make the object drawing as simple as possible and to keep everything relative to the front left corner of a bus and to the loading unloading spot of the station we rotate and translate the whole canvas context. After that all of the drawing is done relative regardless of the current object orientation and position. The alternative would be complex 2d computation but thanks to the canvas it's pretty simple and one can concentrate on the drawing logic. The pattern enabling relative drawing with rotation is listed bellow:
ctx.save(); ctx.translate(this.x, this.y); ctx.rotate(this.angle); // relative drawing code ctx.restore();
Simulation master
The simulation master has the following states:
- busInStation
- busLeaving
- busTraveling
- unloadPassangers
- waitForUnload
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.
The one important thing that should be mentioned is the way the initial passenger color is chosen. Basically it depends on the current station color and all other station colors are all right. Then we are picking a random color from a set until we pick the color that isn't the color of the station. Note that a couple of cycles might pass since we have a new color but writing a complex pseudo random function would just complicate things so the simulation uses the following function:
function randomColor(differentThan) { if (differentThan) { do { var color = stationColors[ Math.floor(Math.random() * stationColors.length) ]; if (color !== differentThan) { return color; } } while (true); } return stationColors[Math.floor(Math.random() * stationColors.length)]; }
dat.gui
When experimenting with graphics and simulations it's best practice to provide a user with some kind of interface for changing variables. There is a small library for creating variable modifying interfaces called "dat.gui". I've found a very interesting and short tutorial about it under dat.gui.
And here's my example on:
Codepen
GitHub
No comments:
Post a Comment