## Phenomenal & Enigmatic, part 4 I remember seeing the "TV Cube" part of Enigma for the first time – and not really being able to figure out how it was made. Heck, I couldn't even do the math for a proper backface culling, so back in the 1990s my occational 3D objects were never any good. So the thought of making 2D and 3D objects appear on the surfaces of another 3D object was way beyond my understanding of math.

Once again, I am aware that the prettier way of doing this is by manipulating a transformation matrix to rotate, translate and project coordinates from different branches of a hierarchical coordinate system. But I ignored that and rolled it all by hand.

### Star field

The stars on the front of the cube might look as if there is some depth, but that's just an illusion. Each star has an (X,Y) coordinate, and a third constant (which I called Z) that governs speed along the X axis and also the alpha component of its color. The lower the speed, the dimmer the light. When observed face on, it gives the impression of a 3D space, but it's really just a form of parallax scroller.

#### Pseudo code

``````// Star field
for (var star, i = 0; star = stars[i++];) {
// Move the star a bit to the "right"
star.x += (star.z * star.z * speedConstant);

// Limit x to (-1 .. 1)
if (star.x > 1) star.x -= 2;

// Left out: Project the star's coordinates to screen coordinates
var screenCoords = ( /* left out */ );

// Draw the star, using Z to determine alpha and size
context.fillStyle = "rgba(255,255,255," + (star.z * star.z).toFixed(3) + ")";
context.fillRect(screenCoords.x, screenCoords.2, star.z * 2, star.z * 2);
}``````

### Hidden line vector

Back in the days, I could never do a proper hidden line vector, because I didn't know how to properly cull back-facing polygons. For the Phenomenal & Enigmatic "TV Cube" part, I arranged all polygons in the hidden line pyramid so that when facing the camera, each polygon is to be drawn clockwise. That way I could use a very simple algorithm to determine each polygon's winding order.

I found one really efficient algorithm on StackOverflow, and I learned that since all five polygons are convex (triangles cannot be concave, and the only quadrangle is a true square), it's really enough to only check the first three coordinates, even for the quadrangle.

Rotating the pyramid in 3D space was exactly the same as with the intro part of the demo, and after all coordinates are rotated, I simple use the polygon winding order algorithm to perform backface culling, then drawing all polygons' outlines. Voilá, a hidden line vector.

#### Pseudo code

``````///Hidden line vector
// Points
var points = [
{ x : -40, y : -40, z :  70 }, // Four corners at the bottom
{ x :  40, y : -40, z :  70 },
{ x :  40, y :  40, z :  70 },
{ x : -40, y :  40, z :  70 },
{ x :   0, y :   0, z : -70 }  // And finally the top
];

// Each polygon is just an array of point indices
var polygons = [
[0, 4, 3], // Four triangle sides
[1, 4, 0],
[2, 4, 1],
[3, 4, 2],
[3, 2, 1, 0] // And a quadrangle bottom
];

// First rotate the points in space and project to screen coordinates
var screenCoords = [];

for (var point, i = 0; point = points[i++];) {
screenCoords.push(rotateAndProject(point)); // rotateAndProject is left out
}

// Then go through each polygon and draw those facing forward
for (var polygon, i = 0; polygon = polygons[i++];) {
var edgeSum = 0;
for (var j = 0; j < 3; ++j) {
var pointIndex = polygon[j];
var pointIndex2 = polygon[(j + 1) % 3];

var point = screenCoords[pointIndex];
var point2 = screenCoords[pointIndex2];

edgeSum += (point2.x - point.x) * (point2.y + point.y);
}

if (edgeSum < 0) {
// This polygon is facing the camera
// Left out: Draw the polygon using screenCoords, context.moveTo and context.lineTo
}
}``````

### Plane vector

The plane vector is super-simple. Just rotating a plane around its center and then using the code already in place to project it to screen coordinates.

### Projection

The function responsible for translating coordinates in the 3D space to screen coordinates is not particularly complex, since it's basically the exact same thing as for the intro part of the demo. Also, to determine which faces of the cube that are facing the camera, I just used the same backface culling algorithm as for the hidden line vector. I was really pleased with the end result.

The entire demo, including non-minified JavaScript, is available on GitHub: /lbrtw/enigmatic

Posted by on Category JavaScript Labels
Tweet this