Skip to content
October 16, 2012 / geeksretreat

HTML5’s Canvas – A physics’ Race

Cars in motion HTML5I fancied stepping into the unknown with my next HTML5 and canvas project. I’ve always been fascinated by the laws of physics and the programmatic approach one would take to manage this within a software project. As my knowledge on this subject only extends to my education at school, I decided to attempt to carry out the basics of a car race animation using the basic equation for a particle in motion e.g. x = x0 + (v0 * t) + (1/2 * a * t^2). The task I set myself was to simply animate four cars having a race using HTML5’s canvas. Simple – for some maybe!
In this point I have used HTML5’s canvas implementation to draw the following:

HTML5 canvas car racing animation

The HTML mark-up used to do this is:
<!--<span class="hiddenSpellError" pre=""-->DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="utf-8" />
		<title>Canvas Motion</title>
		<script src='motion.js'></script>
	</head>
	<body onload='draw();'>
		<div>
			<canvas id="motion" width="1000" height="600"></canvas>
		</div></pre>
<div>position: absolute; top: 10px; left: 100px;" ></div>
<pre>
			<form name='test'>
				<label for='time'>Time:</label>
				<input type="input" id='time' name='time' value="0" />
				<br>
				<input onclick="javascript:startLoop();" type='button' id='loop' value='Start' />
				<input onclick="javascript:<span class=" type="text" />stopLoop();" type='button' id='stop' value='Stop' />
			</form>
		</div>
	</body>
</html>
The HTML is straightforward. The items of interest are the canvas element and the ‘onload’ event for the body. For those who are not familiar with HTML let me explain how we draw onto the canvas. When any HTML page loads the browser will run the JavaScript code attached to the onload event of the body tag e.g.
<body onload='draw();'>
In order to understand how the ‘draw’ function works we need to understand how the browsers locates it. The browser will load any JavaScript code included in a script tag prior to displaying the page to the user e.g.
<script src="motion.js"></script>
Therefore, the content of the script “motion.js” will be loaded, and hence, the function called ‘draw’ will be available. If you were to look inside the aforementioned script you will be able to see the code e.g.
function draw() {

	// Main entry point got the motion canvas example
	canvas = document.getElementById('motion');

	// Canvas supported?
	if (canvas.getContext) {

		ctx = canvas.getContext('2d');

		loadBackground();

	} else {
		alert("Canvas not supported!");
	}
}
The ‘draw’ function obtains a handle to the canvas element which was included with the HTML. The object is then tested to make sure the browser supports the canvas feature. The 2D context handle is then obtained. This is user later to draw various object onto the canvas. Both the canvas and context variables are stored globally and referenced as required throughout the process. The final step within this function is to call the ‘loadBackground’ function. This has the following code:
function loadCarSprite() {

    // Load the timer
    car_sprite = new Image();
    car_sprite.src = 'car-sprite.gif';
    car_sprite.onload = initCars;
}
The ‘loadCarSprite’ function instantiate a new Image object and loads the ‘car-sprite.gif’ image resource:
Car sprite
The onload event occurs immediately after a the image resource is loaded. It is at this point we can continue with the initialisation phase. Thus, once the image resource is loaded by the browser the ‘iniCars’ function will be executed. This function contains the following:
function initCars() {

    game_data = setupGameData();

    initGameState();
    drawCars();
}
The function has three specific steps. The first step creates an object that holds the games data. The object holding this data is created within the ‘setupGameData’ function:
function setupGameData() {

    var json =
    {
        cars:
        [
            {
                "colour": 'blue',
                "x": 0,
                "y": 450,
                "spritex": 0,
                "spritey": 0,
                "graph": null,
                "step": 77,
                "position": null
            },
            {
                "colour": 'green',
                "x": 0,
                "y": 470,
                "spritex": 0,
                "spritey": 37,
                "graph": null,
                "step": 65,
                "position": null
            },
            {
                "colour": 'red',
                "x": 0,
                "y": 490,
                "spritex": 0,
                "spritey": 74,
                "graph": null,
                "step": 53,
                "position": null
            },
            {
                "colour": 'green',
                "x": 0,
                "y": 510,
                "spritex": 0,
                "spritey": 111,
                "graph": null,
                "step": 39,
                "position": null
            }
        ],
        graphs:
        [
            [0,5,10,20,40,60,70],
            [0,10,20,30,40,50,60],
            [0,20,39,40,50,55,58],
            [0,10,20,30,40,50,55],
            [0,25,45,47,49,50,52],
            [0,10,20,29,38,45,50],
            [0,15,20,25,30,40,45],
            [0,2,4,8,20,30,40],
            [0,5,10,15,20,25,30],
            [0,1,3,14,15,22,30],
            [0,5,11,14,17,22,25],
            [0,20,30,44,67,72,90],
            [0,2,7,24,47,52,65],
            [0,2,9,20,40,52,70]
        ]
    };

    return json;
}
The object that holds the data consist of two arrays, an array of objects that holds the car data. This include each cars x,y positions and the cars’ motion graph. Each car is assigned a random graph after the game data is constructed. The graph data is contained within a second array which is also stored in the game data object. The graph data is used to establish the position of each car using the equation for a particle in motion. The following image depicts the graph data and shows how the cars position will behave based on a time factor.

Once the object is constructed it is returned to the calling interface. This returns control to the function ‘initCars’. The second step within this function is to call the function ‘initGameState’.
function initGameState() {

    var iCarCounter;

    for(iCarCounter = 0; iCarCounter < game_data.cars.length; iCarCounter++) {

        initCar(game_data.cars[iCarCounter]);

    }
}

This function iterates around the cars array and initiates each car by calling the ‘initCar’ function:

function initCar(current_car) {

    current_car.graph = Math.floor(Math.random() * game_data.graphs.length);

}

Each car is assigned a random graph using the Math.random() call. Once each car has been processed the function returns and the final step within the ‘initCars’ function is performed. This step consists of a call to the ‘drawCars’ function.
function drawCars() {

    var iCarCounter;

    for(iCarCounter = 0; iCarCounter < game_data.cars.length; iCarCounter++) {

        drawCar(game_data.cars[iCarCounter]);
    }
}
This function iterates around each car within the car array and draws each by calling the ‘drawCar’ function:

function drawCar(car) {

    // Draw the car onto the canvas
    ctx.drawImage(car_sprite,
        car.spritex, car.spritey,
        CAR_WIDTH, CAR_HEIGHT,
        car.x + car.step, car.y,
        CAR_WIDTH, CAR_HEIGHT);
}
When each car is drawn the race is ready to commence. The race is invoked when the user clicks the start button.

The function associated with the onclick contains the following:

function startLoop() {

    stopLoop();

    job = setInterval(startRace, STEP_COUNT_MILLISECONDS);

}

This function starts by resetting the game’s state by calling the ‘stopLoop’ function. This function re-initialises the cars by assigned a new graph to each, along with resetting the variable which represents the time. The ‘startRace’ function is then iteratively called until the race is complete. This function contains the following:

function startRace() {

    var iCarCounter;

    redrawRoadSection();

    for(iCarCounter = 0; iCarCounter < game_data.cars.length; iCarCounter++) {

        moveCar(iCarCounter);

    }

    updateDebugWindow();

    if(iFinishPlace > 4) {

        stopTimer();
    }

    iTime += STEP_COUNT_MILLISECONDS;
}
The above starts off by calling the ‘redrawRoadSection’ function which simply draws the road every time the timer ticks. The following images depicts the movement of the car from left to right.

Animation technique HTML5 Canvas

As the x position of each car increases the car needs to be redrawn in its new place. Thus, the area which the cars are vacating needs restoring: the ‘redrawRoadSection’ function performs this action as follows:

function redrawRoadSection() {

    ctx.drawImage(background, 0, 400, 1000, 200, 0, 400, 1000, 200);

}

The ‘startRace’ function then adjusts the x position of each call by calling the ‘moveCar’ function:

function moveCar(iCarCounter) {

    var current_car =  game_data.cars[iCarCounter],
        seconds = iTime / 1000,
        percentageElapsed = (seconds / RACE_LENGTH) * 100,
        a = 20,
        velocity = 2,
        position = getPositionAtTime(current_car.graph, percentageElapsed);

    if(current_car.x + current_car.step < RACE_FINISH_LINE_X) {

        current_car.x =  graphPosToScreenPos(position) + (velocity * seconds) + (1/2 * a * Math.pow(seconds, 2));

    }
    else {

        current_car.x = RACE_FINISH_LINE_X;

        if(current_car.position === null) {

            current_car.position = iFinishPlace++;
        }

        drawText(current_car);

    }

    drawCar(current_car);
}

This function performs a couple of significant tasks. It firsts obtains the position of the car by references the graph assigned to the current car. This logic is within the ‘getPositionAtTime’ function:

function getPositionAtTime(graph_index, percentageElapsed) {

    var graph = game_data.graphs[graph_index],
        iNumberOfGraphPoints = graph.length,
        iGraphPosition = null,
        iFloor = null,
        iCeil = null,
        p = null;
        position = null;

    iGraphPosition = (iNumberOfGraphPoints / 100) * percentageElapsed;

    iFloor = Math.floor(iGraphPosition);
    iCeil = Math.ceil(iGraphPosition);

    if(iGraphPosition === iFloor) {

        position = graph[iFloor];

    } else if(iGraphPosition === iCeil) {

        position = graph[iCeil];

    } else {

        p = (graph[iCeil] - graph[iFloor]) / 100;

        position = ((iGraphPosition - iFloor) * 100) * p + graph[iFloor];

    }

    return position;

}

The graphs consist of finite number of data points along the x axis, each point relates to a position in time. Using basic interpolation this function calculates the position using the data point either side of the time. If the position in time is exactly on one of the data points the related value is used and no interpolation is necessary. The figure obtained is then passed back to the calling interface which returns controls to the ‘moveCar’ function.
The next step within the aforementioned function is a simple conditional statement which checks whether the current car has passed the finish line. If so, the car’s position is fixed to the finish line and the appropriate text containing the car’s finish position is printed next to the car. If the car is still racing the calculated x position taken from the graph and is used within the formula for a particle in motion to calculate the car’s new position. The car is then drawn onto the canvas by calling the ‘drawCar’ function.
Finally once all of the cars have completed the race the timer is cancelled, leaving the cars in their finish positions:

HTML5 canvas car race results

That concludes this post. As this is my first foray into the physics world I am very interest in any feedback and recommended reading to improve knowledge. Thanks in advanced. A working version can be viewed here. The source code is available in Github.
About these ads

2 Comments

Leave a Comment
  1. juan / Mar 22 2013 19:29

    wow man i didnt know that kind of sh***t can be made by html5 :P

    • geeksretreat / Mar 22 2013 21:08

      HTML5 brings a lot of useful features to the table. Most things are now within the bounds of the browser!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 162 other followers

%d bloggers like this: