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 :)