Phenomenal & Enigmatic, part 2 of 4

#retrocomputing #javascript #demoscene #learning #reverse-engineering

Written by Anders Marzi Tornblad

This is part 2 of the Phenomenal & Enigmatic series. If you haven't read the first part, here it is: Phenomenal & Enigmatic, part 1 of 4

Screenshot from the 1991 Amiga demo Enigma by Phenomena. The opening scene. Text: The Phenomenal Phenomena AssociationThe opening scene of Enigma by Phenomena starts out looking like an average side-scrolling star parallax, which was very common in 1991. This was a nice way to subvert people's expectations about the demo. But after just a few seconds, the stars begin twisting and turning in space around all three axes.

Back in 1991, I knew how to rotate a plane around the origo, by applying the angle sum and difference identities of Sine and Cosine. I also understood that rotating any 3D coordinate in space could be done by rotating around more than one axis.

Screenshot from the 2013 JavaScript Canvas demo Phenomenal and Enigmatic. The opening scene. Text: One hundred percent Flash-free (no Sliverlight or Java either)In the demo, I only rotate the stars around two axis. First i rotate the (x,z) components around the Y axis to get (x',z'), and then I rotate the (y,z') components around the X axis to get (y',z''). I also translate each star along the X axis before the rotation takes place. To finally get the 2D screen coordinates of the 3D space coordinate, I take the (x',y'') coordinate and multiply by (2/(2+x'')) for a pseudo-distance feel. The z'' value controls both the color alpha component, and the size of the rectangle being drawn.

A much better way of doing this would be through vector algebra, but I'm sticking to the math that I know. 😁 After putting this bit of math in place, the trick is to change the offset and rotation variables in a nice way that lines up with the music.

Pseudo code

// Prefetch sin and cosine of angles
var cosY = Math.cos(yAngle);
var sinY = Math.sin(yAngle);
var cosX = Math.cos(xAngle);
var sinX = Math.sin(xAngle);

for (var star, i = 0; star = stars[i++]; ) {
    // Fetch x, y, z and translate x
    var x = star.x + xOffset;
    var y = star.y;
    var z = star.z;

    // Limit x to [-1 .. 1]
    while (x > 1) x -= 2;
    while (x < -1) x += 2;

    // Rotate (x, z) around Y axis
    var x2 = x * cosY + z * sinY; // x'
    var z2 = z * cosY - x * sinY; // z'

    // Rotate (y, z') around X axis
    var y2 = y * cosX + z2 * sinX; // y'
    var z3 = z2 * cosX - y * sinX; // z''

    // Transform to screen coordinates
    var screenX = x2 * 2 / (2 + z3) * halfScreenWidth + halfScreenWidth;
    var screenY = y2 * 2 / (2 + z3) * halfScreenWidth + halfScreenHeight;

    // Draw the star
    context.fillRect(screenX, screenY, 2 - z3, 2 - z3);

You can try this solution at The latest version of the code is always available in the GitHub repository.

Articles in this series: