Phenomenal & Enigmatic, part 2 of 4
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
The 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.
In the atornblad.github.io/enigmatic 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 atornblad.github.io/enigmatic. The latest version of the code is always available in the GitHub repository.
Articles in this series: