3D with HTML5 Canvas: Part 2

Part 2! Unbelievable, my attention span is expanding in my old age... Today we are going to look at building on the 3d starfield of Part 1 by moving from dots to lines! By the end of this tutorial, we'll have a floating cube demo that we can scale and translate in 3D space. Use the arrow keys to move the "camera" in the X and Y directions and the ctrl and space keys to update the Z direction. When you lose the cube (and you will) just click on the page to reset everything.

If that wireframe madness doesn't fill you with wonder and glee, then it's safe to say the remainder of this post ain't going to do much for you either. If you came looking for real 3D information - it's not below. Go check the Google O3D stuff... it's amazing! Right, disclaimers complete. Let's have a look at our mind-boggling cube.

The Cube Model

Before we can display it, we need to model it. Modelling objects, and creating data structures to hold 3D models is a very well established area that I have no intention of researching. What we're going to do, is cheat and just treat the cube as 8 of our starfield points.

Doing so means there is very little that is different between the starfield and the cube. Our starfield had the following pseudo-code:

  1. Randomly assign X, Y, Z
  2. Move Z a bit closer (if it's too close, send it to the back)
  3. Draw all the points at X/Z, Y/Z
  4. GOTO 2

But our cube will be more like:

  1. Carefully assign X, Y, Z
  2. Move our "camera" X, Y and Z around if pressin' keys
  3. Draw all the points at X/Z, Y/Z
  4. GOTO 2

Instead of randomly drawing 50 or so "stars", we carefully choose 8 points that form the vertices of our cube. Our drawing routine will join the dots. That's it! We still just divide by Z to simulate perspective. For modelling the cube, I'll just use an array of arrays - which will define our X, Y & Z coordinates respectively:

model : [ 
	[ -10, 10, 20 ], [ 10, 10, 20 ], 
	[ 10, 10, 30 ], [ -10, 10, 30 ],
	[ -10, -10, 20 ], [ 10, -10, 20 ], 
	[ 10, -10, 30 ], [ -10, -10, 30 ]

The first 4 points will define the top of the cube, the last 4 points will define the bottom. Play around with these values to see some crazy models ensue.

Next, we use the model array to create our Points. Each array element creates a new Point which is pushed onto our cube.main.points array, ready to be drawn.

Introducing the camera view

The calculations for determining where to draw the points is exactly the same as we did for the starfield so I'm not going to cover them again. But to make things a bit more interesting, we are going to define a camera view that adjusts the viewer's eye relative to objects on the screen (in our case, just the cube). This makes much more sense when you have heaps of objects on screen: we want to be able to move the cube in the 3D world, but also be able to move where we are looking.

Thankfully, if we just want to look straight down the Z plane (and we do, 'cause it's heaps easier to have this constraint) then it's simpler than PI. All we need is to define a point in space at our camera location:
this.camera = new cube.Point();
this.camera.x = 0;
this.camera.y = 0;
this.camera.z = 0;

Then when we calculate where each point should be drawn, we just take the camera position into consideration:
// Apply camera transform
point.x += this.camera.x;
point.y += this.camera.y;
point.z += this.camera.z;

The camera's coordinates get updated whenever we hit the appropriate key.
// Update the camera position from keyboard
this.camera.x += this.keys[0] ? -0.2 : this.keys[1] ? 0.2 : 0;
this.camera.y += this.keys[2] ? 0.2 : this.keys[3] ? -0.2 : 0;
this.camera.z += this.keys[4] ? 0.2 : this.keys[5] ? -0.2 : 0;

Woo! Now we can zoom around our 3D land and draw our objects appropriately.

Drawing the cube

The only thing left to do is to actually draw the cube on screen. In our starfield we simply drew some filled circles. This time, we'll just draw lines between the points, primarily using the canvas commands moveTo and lineTo. Here's how we draw the top of the cube:
// Top polygon
ctx.moveTo(this.points[0].projectedX, this.points[0].projectedY);
ctx.lineTo(this.points[1].projectedX, this.points[1].projectedY);
ctx.lineTo(this.points[2].projectedX, this.points[2].projectedY);
ctx.lineTo(this.points[3].projectedX, this.points[3].projectedY);
ctx.lineTo(this.points[0].projectedX, this.points[0].projectedY);

It's just a case of moving to the first point then drawing a line to the second point. Then the third, then finally back to the first. Horribly simple. In fact, uselessly simple: we've had to hard code the lines between all the points - making it a very inflexible way to draw other models. But hey, this is just about drawing a cube - and that's all we're going to do.

In conclusion

This is a verrry simplified version of 3D - we still aren't considering the Z order of things (so we could be drawing things that are far away in front of things that are close) and we are still doing calculations on every point even if they aren't on the screen. But we didn't have to modify much from our starfield code, and now we've got a cube flying around! It doesn't rotate yet, but we are one step closer to being able to replicate graphics from the 1980's, and that's the important thing.