A deconstruction of Strange crystals v0.140b a 140-byte dwitter demo written with Bálint Csala.

The dwitter demo is an attempt to remake Philippe Deschaseaux's "Strange Crystals" JS1K demos.

This quote from the original Strange Crystals write-up explains why we don't need to clear the canvas on every frame:

"...since you normally can't see the end of the tunnel nor through the walls, that means that all pixels will be repainted at each iteration by wall blocks, making it useless to reset them before. And if by chance Math.random() makes a hole in the wall big enough, this hole will be filled with the color of the block that was there in the previous iteration, and it is likely that the 2 colors are not very different."

Dwitter gives us some freebies:
  Our code (placed inside a function u(t) { ... }) 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.

So -

Minimal spiral - angle is i, radius is also i:


Make closer (outer) rects bigger:


Simple colors (closer is brighter):


Add perspective based on distance from viewer (z), and use it to project rects' positions and scale. The 9000 for the radius (like most constants) was found by trial and error.

Let's use z=i/6, and since z will be 0 when i will be 0, we'll stick it in the loop condition to save some space:


Now use z to project the rect scale as well:


Check with strokeRect to see if the output makes sense:


Increase width (on first frame) to see if the output makes sense:

if(t==0)c.width=c.height=4000; for(i=1e3;z=i--/6;x.fillRect(960+S(i)*r,540+C(i)*r,s=5e3/z,s))r=9e3/z,x.fillStyle=R(a=i/5,a,a)

Ok, at 4000x4000 we have some empty spaces, but our (1920x1080) canvas seems covered, so we're good with the constants for s (5000) and r (9000).

Back to normal size:


Now for motion - we want each particle to start at the back and move forward, and eventually wraparound. `(particleIndex / particleCount + time) % 1` should do it. So in short form: `j=(i/1e3+t)%1`. Let's use it to color our rects.


Looks ok-ish. Let's wrap it with S (Math.sin) to get waves of light/dark.


Ugh, Since S() takes an angle in radians we want S(j*Math.PI*2) for a smooth transition. That's a bit long, though, so let's do j*9.


Ok, now, we still have an annoying white hole in the middle of our tunnel, let's make the tunnel curve to hide it. We can add or substract some offset that increases along with i to our x or y coordinate. Let's try i:


A bit too curvy. Let's try i/6 (which we happen to already have as z):


Almost! Still has a few white pixels in the middle, which would bother me if I didn't already know how it'd all turn out in the end.

The structure looks a bit too uniform. We're using i as the angle for each rect, let's use some hash of the rect's distance (j) instead. We'll use 1000*j|0 to get an integer between 0 and 1000 shifted by time (t) and raise it to the 4th power to add "randomness". We're also using j to set the colors, so the angles and colors of each particle will stay in sync as it moves toward the viewer.


Nice, and as an added bonus - the white patch in the center is gone! Let's just add z to our color to light up the distant part of the tunnel.


Now, we have 3 occurences of `1e3`, in our code. Let's define a variable to save a few bytes.


We also have a 960 as the horizontal center coordinate, let replace it with 1000 (our new k) and save 2 bytes.


The 540 (vertical center) doesn't have an obvious replacement. Let's go with z. It's way off center - but it'll add a small vertical curve, which is nice.


Ok, "tracks"; let's make each 9th rectangle a track. We'll define a new variable, `b`, that will be true if the rect should be a wall and false if the rect should be a track.


True and false get coerced into 1 and 0 when used in arithmetic operations. If the rect should be a track, it should always be at the bottom. Since we have `m` as our angle, we'll just set it to 0 when `b` is false with `&-b`. `-b` will be 0 for tracks so `&0` will zero the angle, otherwise `b` will be -1, and `&-1` is equivalent to the `|0` that we had before.


These tracks are not very visible. We should move them up a bit by making their radius (r) smaller. We'll make the base radius 8000/z and add 1000/z when we're drawing walls.


If we could make the difference in radius bigger (2000/z) it would look nicer, but we'll make a concession here since we're out of space.