# 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.