# 3D In 2D Canvas

In my recent simulation of an AC generator, I show the same device from two different views: A top view and a front view. To accomplish that, I used a clever technique called 3D Projection. Here, I’m going to talk about how I did that in JavaScript and rendered it on canvas.

## What is 3D Projection?

Basically, it means that I define items in 3D space (each point is defined as an array `[x, y, z]`) and that I plot them in 2D space. For simplification, the 2D render can only be seen in the `xy`, `xz`, or `yz` plane.

## Defining in 3D

Each point is defined as an array, [x, y, z].

Since we are using Canvas, it is easiest to define a rectangular face as a 2D array of 5 points: The first vertex, the remaining 3 vertices, and then the initial vertex again. This allows us to `moveTo()` the first index and then `lineTo()` till the remaining length of the array.

Since faces exist in 2D, and we’re defining 3 coordinates per point, for a single face, either the x, y, or z, coordinate will remain constant for all points.

A cuboid is defined a 3D array of faces. Depending on our views, we might not need all 6 faces to define a cuboid and can get away with only two or three faces.

Example:

``````var cuboid = [
// xy face
[
[xa1, ya1, za],
[xa2, ya2, za],
[xa3, ya3, za],
[xa4, ya4, za],
[xa5, ya5, za],
],
// xz face
[
[xb1, yb, zb1],
[xb2, yb, zb2],
[xb3, yb, zb3],
[xb4, yb, zb4],
[xb5, yb, zb5],
],
...
]
``````

## Rendering in 2D.

Since we’re defining faces parallel to the primary planes, it is easiest to render the views of the primary planes themselves.

Imagine a shape on the `xy` plane. All points that define it can be written as `(xi, yi, 0)`. Similarly, any shape on the `xz` plane defines all points as `(xi, 0, zi)`. Basically, whichever axis you’re not rendering the shape on is 0.

This makes things easy for us.

Creating a function called `plotFace`, which takes three parameters:

• `face`: The 2D array of points
• `path`: The path to plot the shape on
• `a`: The first axis of the plane
• `b`: The second axis of the plane

Since in our arrays, the 0 index represents x and so on, we can simplify the function if `a` and `b` are directly passed as integers.

The plotFace function basically `movesTo` the initial point and `linesTo` the remaining ones.

``````function plotFace(face, path, a, b) {
var len = face.length;
path.moveTo( face[a], face[b] );
for ( var i = 0; i < len; i++ ) {
var point = face[i];
path.lineTo( point[a], point[b] );
}
}
``````

A simple `map` through all faces and using `plotFace` on each can plot the entire shape to a single path.

``````path = new Path2D();
ctx.beginPath();
item.faces.map(function(face) {
plotFace(face, path, a, b);
})
ctx.closePath();
``````

Finally, rendering the context using `fill()` will get our shape.

``````ctx.fill(path);
``````

…And we’re done!

## What’s next

Currently, we’re only projecting in the `xy`, `xz`, or `xy` plane. Next would be to be able to project in any arbitrary plane. I have yet to figure out the math for it, and I’m trying to do it without external help, so it might be a while before I publish a new article.

After that, perhaps manually raycasting to create shadows? That could be interesting, both aesthetic and performance wise.