Anders Tornblad

All about the code

Monthly archive for May 2013

Artsy (and slightly insane), first two parts now in beta

A couple of minutes ago, I uploaded a new version of Artsy (and slightly insane). It includes JavaScript remakes of the first two parts of the iconic Amiga Demo Arte by Sanity.

The code is pure JavaScript and Canvas. There is no Flash, no Silverlight, no WebGL stuff, and there are no frameworks involved. When I'm done remaking the third and final part of Arte, my plans are to release the full source code for the demo. Also, I'll take the "plumbing" parts of the code and release as a JavaScript demo framework in itself, and I'll open-source it.

But for now, enjoy the first two parts of "Artsy (and slightly insane)". The adress is demo.atornblad.se/artsy.

Phenomenal & Enigmatic, part 4

TV Cube remakeI 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

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

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

Phenomenal & Enigmatic, part 1
Phenomenal & Enigmatic, part 2
Phenomenal & Enigmatic, part 3
Phenomenal & Enigmatic, part 4 (this part)

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

Artsy (and slightly insane), first part now in beta

In between writing about the Phenomenal & Enigmatic JavaScript demo, I'm also doing a JavaScript remake of the Arte demo by Sanity from 1993. The first effect I made was the "disc tunnel" effect, seen 2min 9sec into the YouTube clip, and the entire first part is now live, but still in beta.

The address is demo.atornblad.se/artsy, but I haven't tested it that many browsers and devices yet. I do know that it crashes on Windows Phone 7.8 after just a few scenes, but it works really nicely on my iPad 3, especially in fullscreen mode. Add a shortcut to your iPad's start screen for fullscreen mode.

I will make some changes to the bitmap tunnel effect, and make sure that the demo runs correctly on most browsers and devices. Also, stay tuned for parts 2 and 3 of Sanity Arte, and of course there will be a blow-by-blow description here on atornblad.se when the whole thing is complete.

Phenomenal & Enigmatic, part 3

When I made Phenomenal & Enigmatic, I didn't want to reuse any of the graphics art from the original demo. I had decided to take the music and some sense of the overall demo design, but didn't want to infringe on the original graphics artist's creativity, so for the Enigmatic logo, I turned to rendering the logo using code.

Enigmatic logoOne popular technique of creating good-looking logos back in the Amiga days was to first draw the logo flat, then duplicating it in a new layer a few pixels off, making that new layer translucent. Then one would paint in side surfaces and edge lines with varying levels of opacity. After adding some final touches, like surface textures or lens flares, the end result would be a glossy, glassy look, much like the original Enigma logo by Uno of Scoopex.

Enigmatic logoThe logo scene uses the same technique, painting the front, back and sides of the word ENIGMATIC as filled polygons with slightly different colors and opacity levels. During the scene, I animate some of the transformation vectors for effect. Of course, the original artwork by Uno is much better in exactly every way, but it was a fun exercise.

Pseudocode

function transformLogoCoord(chapterId, time, x, y) {     // Left out: Perform a simple coordinate transformation     return { x : transformedX, y : transformedY }; }   function logoMoveTo(chapterId, time, x, y, xOffset, yOffset) {     var coords = transformLogoCoords(chapterId, time, x, y);     context.moveTo(coords.x, coords.y); }   function logoLineTo(chapterId, time, x, y, xOffset, yOffset) {     var coords = transformLogoCoords(chapterId, time, x, y);     context.lineTo(coords.x, coords.y); }   function renderLogo(chapterId, time) { var xOffset, yOffset;     // Left out: Calculate xOffset and yOffset from the chapterId and time values         // Draw bottom surfaces     context.beginPath();     for (var block, i = 0; block = logoPolygons[i++];) {         logoMoveTo(chapterId, time, block[0].x, block[0].y, 0, 0);         for (var coord, j = 1; j = block[j++];) {             logoLineTo(chapterId, time, coord.x, coord.y, 0, 0); }         logoLineTo(chapterId, time, block[0].x, block[0].y, 0, 0);     }     context.closePath();     context.fill();         // Left out: draw side surfaces         // Left out: draw top surfaces }

Phenomenal & Enigmatic, part 1
Phenomenal & Enigmatic, part 2
Phenomenal & Enigmatic, part 3 (this part)
Phenomenal & Enigmatic, part 4

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

Phenomenal & Enigmatic, part 2

Stars and text intro

Stars and text Enigma screendumpThe opening scene of Enigma by Phenomena starts out looking like an average side-scrolling star parallax, which was very normal in 1991. Nice way to lower people's expectations. :) But after just a couple of 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 simply applying the angle sum identities of Sine and Cosine. I also realized that rotating any 3D coordinate in space could by done by simply the case of rotating around more than one axis, one axis a time.

Stars and text screendumpIn the Phenomenal & Enigmatic demo, I only rotate the stars around two axes. First I rotate the (x,z) components around the Y axis to get (x',z'), and then 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+z'')) for a pseudo-distance feel. The z'' value controls both the color alpha component, and the size of the rectangle being drawn.

The even better way of doing this is through vector addition and multiplication, but I'm sticking to the math that I know. :) After all this math is in place, the trick is to change the offset and rotation variables in a nice way.

Rendering text is just a couple of calls to the context.fillText method and animating the value of context.globalAlpha.

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); }

Phenomenal & Enigmatic, part 1
Phenomenal & Enigmatic, part 2 (this part)
Phenomenal & Enigmatic, part 3
Phenomenal & Enigmatic, part 4

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