This was supposed to be a kind of tutorial, but I got distracted along the way and it devolved into a demo.
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)TA-DA!
...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 :)