Román Cortés

Furbee - My Js1k Spring ‘13 entry

26 de Marzo del 2013

Furbee

For the latest editon of Js1k contest (a contest to create something cool with only 1 kilobyte of Javascript), I’ve submitted an entry with a furry bee with springy antennae and my tie. Check it here.

I’ve been pushing the limits of what is possible to render with canvas 2d. As a result, it renders at decent speed only in Chrome with a very fast computer. In case you don’t have one, check out this video capture:

Rendering fur

For rendering the fur, I’ve used the same method as I previously used  in the Christmas Tree I made for a previous js1k edition. The basic idea is to create sprites that represent handfuls of fur, and then draw those sprites/brushes thousands of times to create the body of the bee.

For this entry I’ve enhanced and reduced in code size the sprite generation method used in the Christmas Tree, also making it better suited for fur. Now it generates multiple sprites with different sizes and colors.

Color palette

Creating a nice color palette with the 1kb limitations of js1k is not an easy task. I wanted to have nice gradients with colors switching hue from red/orange/brown to yellow for the bee’s body, and it is very expensive in size to do it with RGB color space. Fortunately, browsers support the HSL space, that makes much easier the generation of nice gradients.

This piece of code is generating the palette, including the grays needed for the tie:

‘hsla(’+[(j&15)*8-x,(j&15)*6+x+’%',(j<17)*60+(j&15)*7+’%',1]+’)’

Point cloud rendering

The spring in the antennae and the tie are rendered with a point cloud rendering method. It consists in representing 3d points in space as little dots (and/or pixels) in the screen. If they are dense enough in the screen, all space will be covered and it will look like a full surface.

Taking advantage of the same rendering code of the fur, and that I have little fur sprites available,  I use them to render the point clouds without the need of additional code.

Clouds rendering

The image above is very descriptive of the process I’ve used for rendering the clouds. Again, the fur sprites are being reused to render clouds. I render 16 of those sprites with perspective transform, translating them in z, and recycling them when they are not visible anymore.

This results in the 3d cloudscape in the background. I have a gradient in the background from cyan to white. The clouds slowly move from top to the bottom, into the white area, and as they are white too, they seem to disappear. At this point is when I recycle them to create a new cloud.

Generating the sprite positions of the bee’s body

The body, stripes and eyes of the bee are non-uniformly scaled spheres. Fur sprites are drawn at multiple points in the surface of those spheres. So there is the need to generate those points in the surface of a sphere.

To make the fur look good, we need the points in the sphere to be well distributed. The ideal for render quality and rendering speed - due the need of less sprites - would be a Poisson disc sampling in a sphere, but it is too expensive, both in pre-computation time and in byte size. So I tried with the simpler uniform random sampling.

Generating uniform random sampling in a sphere surface, while it might sound simple, it is a world of its own with dozens or hundreds of different well known methods. I tried with a good amount of them, the shorter in byte size ones, and figured out the shorter is Normalize and Discard.

It consist in generating a random number between -1 and 1 for each x, y and z coordinate. That would set up uniform random point inside a cube with center at (0, 0, 0) and width=2. Then, we compute its vector magnitude, and discard the points for which magnitude is bigger than 1, that means only points inside a sphere radius 1 will remain. Finally, we normalize the vector (dividing x, y and z by its magnitude) and we project this way the point from the inside to the surface of the sphere.

It is both a very simple and short in size method. However, js1k is very restrictive in size, each byte counts, so I needed to shorten it. So I discarded the idea of a uniform random sampling and opted for just some random sampling.

The first try was just to remove the discarding, and so leaving just the Normalize method. That would just project points from a cube to the surface of the sphere. Unfortunately the distribution is awful as you can see in the interactive illustration above, and it was clearly visible in the fur.

So the next idea, and the one finally used was to perform 2 Euler rotations. As advantages it is shorter in code size, the distribution of points is near 20% denser in the low density areas than Normalize and it has only 2 high density points at the poles.

Finally, I will leave to the reader the option to analyze why Math.cos(i * y) is a rough and low quality approximation of Math.cos(random() * Math.PI * 2), where random is an LCG.

Wings with artistic motion blur

To make the wings looks good (as any very fast moving object), there is a strong need for motion blur.

A real honey bee flaps its wings during flight more than 200 times per second, that should be enough to make them look fully motion blurred and seemingly static with solar light conditions. Well, I’ve to say this is just what I suspect, I’ve not tried to look at a real one; I’ve a strong entomophobia, so I’m sorry, real bees are not my friends. But I like honey. Anyway :P. Oh, and I really hope bees to recover soon from the colony collapse disorder making them disappear so fast in the latest years. Nature (us included) would have a really hard impact without them, it is both amazing and somewhat scaring how such a little animal is so important for the stability of the whole system.

I was writing about motion blur and wings, right? Ok, so… motion blur, motion blur… ah, yes! If I were to represent wings as they are in reality, they would not look as having movement at all, because they would have a full motion blur. That would not be cool at all. So the next option is to represent them with my artistic vision of the motion blur they should have.

It was about adjusting the motion blur to cause sensation of movement, but a movement not so fast it is it is distracting or even annoying. Doing it required a huge amount of trial, mostly because of the size limitations of the compo. It ended pretty close to my artistic vision, so I’m very happy with them.

From a technical stand-point, they are multiple filled cubic bezier curves drawn on top of each other with a very low opacity.

Size optimizations

Furbee was compressed with jscrush, and so a lot of size optimizations were targeted to it. Jscrush is specially effective if you repeat as often as possible, so I used a lot of different reusable patterns for that purpose.

Using the less as possible different keywords is really important with Jscrush, so I limited myself to a very limited subset:

- Only for with no other conditional o looping structure

- Math functions: sin, cos, PI, abs (no random, generating pseudorandomness my own)

- Using fillRect to draw lines instead of moveTo, lineTo, stroke

- Creating gradient without canvas gradients; just reusing fillRect (used for sprite generation too)

I’m using the canvas context object as array to avoid the creation of a new variable and array and most of the common byte saving techniques, that are well described in this post from my good friend Claudio Cañete.

And finally, I use quantization for the bee’s body data. Well… to tell you the truth, I’m not even sure if quantization is the best word to describe it or not… but I’m going to call it like that. For the spheres that create body and eyes I have these parameters:

- Center x position of the sphere

- Center y

- Center z

- Brush size

- A flag to determine is the sphere is stripped or not

- Scale x

- Scale y

- Scale z

- Initial color for gradient

- Final color for gradient

- Gradient color dithering level

Eleven properties and a total of 6 spheres: body, stripes, 2 eyes and 2 pupils. So, 66 properties in total, thinking in clear numbers comma separated from 0 to 300, and averaging around 3 bytes per property, gives us a total of around 200 bytes of data.

A usual trick in js1k is to package data as string characters, that could reduce up to by half (depending on the data) the size, but in the case of that little amount of data as I had, the depackaging code was too big to compensate it.

So, I opted for quantization and lossy compression of it. As an example, suppose I have this code:

[33,11,66,121,22,77,88,55,132][i];

As all numbers are divisible by 11, I can safely do this:

[3,1,6,11,2,7,8,5,12][i]*11;

The size is lower, and the result is the same. Now, suppose I have instead of 132, that I have 133 in the last position:

[33,11,66,121,22,77,88,55,133][i];

Now, not all values are divisible by 11. But suppose that 133 value represents the width of the body sphere. Nobody will notice the difference between width 132 or 133, so this is the lossy compression part I’ve used. So, I end with the same quantization:

[3,1,6,11,2,7,8,5,12][i]*11;

I used this method carefully by hand tuning it to maintain the nice proportions and design of the bee while taking the less space as possible.

Rotation of the bee

The bee rotates with 2 degrees of freedom. Well… not. It just looks like that, but it is just a little trick. In fact it has a 1 degree of freedom for the y rotation axis and 1 degree of freedom for skew. For little angles, the skew is a reasonably good a approximation to rotation, so it does the trick. And it takes just 6 or 7 extra bytes.

Furbee inspector

My best friend Jan Carlo Mityorn has been really supportive during the development of this entry. He has been pushing me hard to make it better and better, and also he provided a lot of help testing it for performance on different computers. Thank you Jan!

So, he asked me for a bee where he could see the wireframe, rotate and play a bit with it. I’ve coded a special version of the bee without size limitations, that renders several times faster than the original so Jan Carlo is able to play in his old computer, so here it is:

22 Comentarios RSS

Comentar