This was supposed to be a kind of tutorial, but I got distracted along the way and it devolved into a demo.

Dwitter 3D "engine"

Or "How to write shaded and animated 3D scenes in <140 chars of javascript"

Dwitter gives us some freebies:
  Our code is called 60 times per second.
  t: Elapsed time in seconds.
  S: Shorthand for Math.sin.
  C: Shorthand for Math.cos.
  T: Shorthand for Math.tan.
  R: Function that generates rgba-strings, usage ex.: R(255, 255, 255, 0.5)
  c: A 1920x1080 canvas.
  x: A 2D context for that canvas.

Here's a circle drawn with 2000 fillRect calls:

c.width|=0 for(i=2e3;i--;x.fillRect(960+S(i)*99,540+C(i)*99,9,9));

Simple shading - color determined by the angle.

c.width|=0 for(i=2e3;i--;x.fillRect(960+S(i)*99,540+C(i)*99,9,9))x.fillStyle=R(S(i)*255)

Let's make it into a tube, instead of using 960 as the horizontal center we'll use i.

c.width|=0 for(i=2e3;i--;x.fillRect(i+S(i)*99,540+C(i)*99,9,9))x.fillStyle=R(S(i)*255)

Not very solid. We can fix that in a few ways. Best not to draw more than 2000 rects per frame for performance though.

So, either lower resolution, bigger rects or a shorter tube.

c.width|=0 for(i=2e3;i--;x.fillRect(i/4+S(i)*99,540+C(i)*99,9,9))x.fillStyle=R(S(i)*255)

Well, that's a start I guess. Let's center it.

c.width|=0 for(i=2e3;i--;x.fillRect(700+i/4+S(i)*99,540+C(i)*99,9,9))x.fillStyle=R(S(i)*255)

And use larger rects.

c.width|=0 for(i=2e3;i--;x.fillRect(700+i/4+S(i)*99,540+C(i)*99,19,19))x.fillStyle=R(S(i)*255)

Let's move it up and down...

c.width|=0 for(i=2e3;i--;x.fillRect(700+i/4+S(i)*99,540+S(t)*400+C(i)*99,19,19))x.fillStyle=R(S(i)*255)

Now - let's try attaching our up/down motion to a change in lighting. The idea is to make it look like tube is moving above and below the light source.

c.width|=0 for(i=2e3;i--;x.fillRect(700+i/4+S(i)*99,540+S(t)*400+C(i)*99,19,19))x.fillStyle=R(S(i-S(t))*255)

Yeah, ok, I can live with that. We can sort of fake specularity by raising our lighting coefficient S(i-S(t)) to an odd power.

c.width|=0 for(i=2e3;i--;x.fillRect(700+i/4+S(i)*99,540+S(t)*400+C(i)*99,19,19))x.fillStyle=R(S(i-S(t))**5*255)

Odd powers mean that negative numbers stay negative. Using even powers will get you highlights on the inside as well - which may or may not be a good thing.

c.width|=0 for(i=2e3;i--;x.fillRect(700+i/4+S(i)*99,540+S(t)*400+C(i)*99,19,19))x.fillStyle=R(S(i-S(t))**6*255)

Let's add some color.

896 is 1110000000 in binary, or 128*7, so i&896 gives us a change in hue every 128 iterations.

c.width|=0 for(i=2e3;i--;x.fillRect(700+i/4+S(i)*99,540+S(t)*400+C(i)*99,19,19))x.fillStyle=`hsl(${i&896},50%,${S(i-S(t))**6*255}%)`

Too much contrast!

c.width|=0 for(i=2e3;i--;x.fillRect(700+i/4+S(i)*99,540+S(t)*400+C(i)*99,19,19))x.fillStyle=`hsl(${i&896},50%,${(.5+S(i+t))**9+22}%)`

And let's make the whole thing rotate along with the light source.

c.width|=0 for(i=2e3;i--;x.fillRect(700+i/4+S(i+t)*99,540+S(t)*400+C(i+t)*99,19,19))x.fillStyle=`hsl(${i&896},50%,${(.5+S(i+t))**9+22}%)`

Make it bigger by stretching the Y portion and updating the sizes in fillRect. Width will remain as is, so our circles will turn into ellipses, which will also help with the 3D feel.

c.width|=0 for(i=2e3;i--;x.fillRect(700+i/4+S(i+t)*99,540+S(t)*400+C(i+t)*200,22,44))x.fillStyle=`hsl(${i&896},50%,${(.5+S(i+t))**9+22}%)`

Flip x and y:

c.width|=0 for(i=2e3;i--;x.fillRect(700+S(t)*400+C(i+t)*200,540+i/4+S(i+t)*99,44,22))x.fillStyle=`hsl(${i&896},50%,${(.5+S(i+t))**9+22}%)`

Recenter and lose the motion

c.width|=0 for(i=2e3;i--;x.fillRect(960+C(i+t)*200,340+i/4+S(i+t)*99,44,22))x.fillStyle=`hsl(${i&896},50%,${(.5+S(i+t))**9+22}%)`

Extract the radius and modulate it

c.width|=0 for(i=2e3;i--;x.fillRect(960+C(i+t)*r*2,340+i/4+S(i+t)*r,44,22))r=C(i/770)*99,x.fillStyle=`hsl(${i&896},50%,${(.5+S(i+t))**9+22}%)`

This messed up the lighting. Since the radius sometimes goes negative, there's no highlight on the bottom now. Simple solution - square the radius so it's always positive.

c.width|=0 for(i=2e3;i--;x.fillRect(960+C(i+t)*r*2,340+i/4+S(i+t)*r,44,22))r=C(i/770)**2*99,x.fillStyle=`hsl(${i&896},50%,${(.5+S(i+t))**9+22}%)`

Let's lose the colors, and just keep the lightness component. Maybe add a small constant to the radius.

c.width|=0 for(i=2e3;i--;x.fillRect(960+C(i+t)*r*2,340+i/4+S(i+t)*r,33,18))r=9+C(i/770)**2*99,x.fillStyle=R(a=(.8+S(i+t))**9+22,a,a)

Change lighting to light the inner part too.

c.width|=0 for(i=2e3;i--;x.fillRect(960+C(i+t)*r*2,340+i/4+S(i+t)*r,33,18))r=9+C(i/770)**2*99,x.fillStyle=R(a=S(i+t)**8*200,a,a)

And change the light direction.

c.width|=0 for(i=2e3;i--;x.fillRect(960+C(i+t)*r*2,340+i/4+S(i+t)*r,33,18))r=9+C(i/770)**2*99,x.fillStyle=R(a=S(i+t+1)**8*200,a,a)

Maybe paint the thing gold?

c.width|=0 for(i=2e3;i--;x.fillRect(960+C(i+t)*r*2,340+i/4+S(i+t)*r,33,18))r=9+C(i/770)**2*99,x.fillStyle=R(a=S(i+t+1)**8*200+99,a*.7,a/4)

Hey! We've got some sort of a golden chalice thing with nasty edges! Let's try lower res (-1 is invalid as a canvas width, so it gets set to the default, which is 300).

c.width=-1 for(i=2e3;i--;x.fillRect(150+C(i+t)*r,40+i/22+S(i+t)*r/2,9,4))r=9+C(i/770)**2*44,x.fillStyle=R(a=S(i+t+1)**8*200+99,a*.7,a/4)

Edges still pretty nasty but we shaved off a few chars. Shave a few more.

c.width=-1 for(i=2e3;i--;x.fillRect(150+C(--m)*r,40+i/22+S(m)*r/2,9,4))r=9+C(i/770)**2*44,x.fillStyle=R(a=S(m=i+t+1)**8*200+99,a*.7,a/4)

We can fix the top of the chalice by adding i/66 as the alpha in our rgba.

c.width=-1 for(i=2e3;i--;x.fillRect(150+C(--m)*r,40+i/22+S(m)*r/2,9,4))r=9+C(i/770)**2*44,x.fillStyle=R(a=S(m=i+t+1)**8*200+99,a*.7,a/4,i/66)

And the bottom by moving it offscreen.

c.width=-1 for(i=2e3;i--;x.fillRect(150+C(--m)*r,70+i/22+S(m)*r/2,9,4))r=9+C(i/770)**2*44,x.fillStyle=R(a=S(m=i+t+1)**8*200+99,a*.7,a/4,i/66)

Now, shave off a bit more and add brightness.

c.width=-1 for(i=2e3;i--;x.fillRect(150+C(--m)*r,70+i/22+S(m)*r/2,9,4))r=9+C(i/770)**2*44,x.fillStyle=R(a=S(m=i+t)**8*255+128,a*.7,a/4,i/66)

Scale up

c.width=-1 for(i=2e3;i--;x.fillRect(150+C(--m)*r,30+i/15+S(m)*r/2,9,6))r=9+C(i/770)**2*44,x.fillStyle=R(a=S(m=i+t)**8*255+128,a*.7,a/4,i/66)


...ok, so it's not the prettiest thing in the world, but with a bit more effort this technique can also be used to make jack o'lanterns, fish, flowers, snails, and bananas(!)

Now go make some tiny 3D demos :)