tiistai 24. huhtikuuta 2018

Making of Neon City Wakes Up


I love retro pixel art and 80s graphics in general. I also like to program in an old school way with bitwise operations and all that glam rock. I'm just telling this so you all would understand why this demo is so incredibly last season. Here we go again on our own...

1. Pixel array approach


When I do raycasting I often manipulate the Canvas ImageData array directly. For Dwitter demos that approach is not feasible because the calls to getImageData and putImageData take a lot of space. There is a way to simulate it though. We can use fillRect to draw each pixel on the screen separately and then control them either by setting color with fillStyle or by simply switching pixel width between 1 and 0 and doing black and white. This changes the way we look at drawing on screen as we are drawing every pixel by default and the logic will either define what not to draw or at what color. For the sake of simplicity, lets first look at the case of each pixel on screen being either drawn or not drawn and build our city skyline this way. We have the Dwitter boilerplate and all we need in addition is a tight loop that draws the screen full of small rects...

for(c.width=i=110;i--;)for(j=64;j--;)x.fillRect(i,j,1,1)

That should paint our whole screen black. Basically i is our screen x and j is screen y. If you change the width in fillRect to 0, the screen is left white. 0.5 will make it grey. Adding a conditional statement there lets us control the image. Just to show how it works here's how you'd do a flat horizon...

x.fillRect(i,j,j>32,1)

So if our current screen y coordinate is larger than 32 the width of the rect will be 1, otherwise 0. This results in only the lower half of the screen being black. Now lets get busy and do the silhouette of the buildings using this method...

2. Adding conditions




x.fillRect(i,j,j/9>3+S(i|3),1)

I'm quite proud of this little formula that gives us the city in 12 bytes. It's easier to read as  j>27+S(i|3)*9 but we save an extra byte by scaling things down. The condition basically tells the browser to only draw when the current pixel is below our little sine wave. The blockiness is achieved with the bitwise OR operation. Now to something much more complex - adding the reflections on water...



x.fillRect(i,j,j/8<6+C(i-j-t)&j/9>3+S(i|3),1)

The idea is the same but we don't need the OR operation and we make the reflections angled by adding j to the mix. Well actually subtracting it but you get the idea. Variable t is elapsed time and gives us movement. We use the AND operator to make sure both conditions are met before we draw pixels. Seems to be working nicely and only 82 bytes spent in total. Time for details!

Many 80s computer graphics had wireframe grids vanishing to the horizon and a sun that had horizontal lines going over it. We will simplify this by just doing horizontal lines on the sky - again by adding a condition...


x.fillRect(i,j,j/8<6+C(i-j-t)&(j/9>3+S(i|3)|j%7<1),1)

Only 8 more bytes thanks to the modulus operator! We don't want the lines on water so we need to combine our new condition with our building condition. This requires adding parenthesis. We use the bitwise OR to draw if either condition is true. Notice what a big difference these horizontal lines make in making the image recognizable. Up next, the stars...


x.fillRect(i,j,j/8<6+C(i-j-t)&(j/9>3+S(i|3)|j%7<1)|!(1+j*i&79),1)

12 bytes for a starry sky that reflects on the ocean isn't all that bad. We multiply our x and y coordinates and use some bitwise magic to scatter the stars all over the screen. The logical NOT operator turns the result of our formula into 1 if it's anything other than 0. You might wonder why we add one in the beginning - that's so we don't get a vertical black line on our left edge when we are multiplying zero with zero. Now we have added 4 layers of fairly short conditions. With these rules the end results is a 102 byte black and white version of the demo. I used the remaining space to add animated neon colors and higher resolution. I also changed the order of conditions slightly to separate layers into 3 groups of base color.

3. Code golfing tip


To finish I'll point out one code golfing trick that helped me fit this demo inside the Dwitter limit of 140 byte...

x.fillRect(i*T,j*T,T=18,T)

To get larger pixels for the high resolution version I needed to multiply each argument by 18. The first time I need it is not the best place to define a variable cause I'd have to use parenthesis. What we can do is use a variable that has already been declared in the boilerplate. We don't need Math.tan in our demo so I chose the provided shorthand T. When fillRect is first called it doesn't draw anything cause T is not a number. The second time and from then on the value of T is 18.

I hope you enjoyed this article. Would love to see your work on Dwitter too!

Follow me on Twitter if you want to hear of new demos and blog updates - @jylikangas




sunnuntai 22. huhtikuuta 2018

Making of White Room Traps You


This time we'll try to create a 3D wireframe cube with the smallest possible charcount. I think that you should always put extra effort into simplifying the object or rendering technique that is the center of your demo. If we can manage to do the cube with circa 100 bytes that leaves us plenty of (white) room to build something special. In the case of my Dwitter demo "White Room Traps You" the box gets some nice shading and becomes alive to trap you inside. Spooky.

1. Start with 2D


Before trying anything in 3D it can help to do a 2D version first. Cube in two dimensions is a square. Since it's perfectly symmetrical we can define it as the largest square that fits inside a circle of a given radius. So we only need a radius - no vertices! We have four corners so we go around a circle in steps of 90 degrees and draw lines between those points like in the animation below...


When we have a flat object parallel to the x-axis we can imagine creating another copy with different y-axis position. When we connect the corners we end up with a 3D wireframe version...


Now we have visualized in a simple way what a cube essentially is. Next we'll look at an efficient way to render it in a single loop.

2. Rendering the cube with a single loop


I often approach a problem with pen and paper before programming anything. To solve this puzzle I drew a 3D cube and followed the edges with a pen to find a simple way to visit all the edges. Since I'm a programmer I can automatically think of the required logic for different paths. It seemed the easiest way was to draw a square wave going around the cube once and then another but with inverted phase. Let's first draw a square wave...


Then do it in 3D around a center point...


Then do the same inverted...


Cube is what happens when we put the two together. But instead of inverting the phase and doing another round we can start to go backwards when we meet our own tail. This keeps the pattern of consecutive vertical and horizontal movements going thus making our JavaScript implementation smaller.


Of course we are drawing some edges twice but dirty tricks are what code golfing is all about. Plus it adds character to the box. Lets look at some code already!



That's the smallest and ugliest rotating wireframe box I could do with the Dwitter boilerplate. I'll try to explain what's going on in the 87 bytes. First we define our canvas size which also will clean it. We will use 64 for the loop repetition count even though 16 would be enough. Again it doesn't matter that we are drawing edges multiple times and we save space when we don't need to define another number. There's also no need to use functions like moveTo, beginPath or closePath usually associated with path drawing. We just keep calling lineTo without worrying about performance or super clean lines. Here's what's going on with our line drawing coordinates if we clean it up...

X = 32 + S(t += (7 - i) % 2 * 1.57) * 20

The first number, 32, is just an offset that places us nicely on the center of the canvas. The radius of the bounding circle is 20 and for x-coordinate we need to multiply that with the sine of our current angle. The angle t is incremented every other round in steps of 1.57 (approximation of PI / 2) with the help of the modulus operator. Manipulating the variable t as our angle is a nice code golfing trick because it's predefined by the framework and resets to elapsed time before each call to u. It gives us rotation animation with only 1 extra byte versus a static cube. A byte well spent I'd say. However the most important part to note is that we turn backwards after 8 iterations by using 7 - i.

Y = 9 + (i % 4 >> 1) * 20 + C(t) * 5

For y-coordinate we multiply the radius by 0 or 1 based on if we are drawing the top or bottom edges of the cube. We need to alternate every other round which is a bit more tricky. The more typical case, which we already had with the angle change, would be to alternate each time by using i % 2. We need to manipulate this by taking the remainder of i divided by 4, then diving it by 2 and rounding down. In the last part we add the cosine of current angle multiplied by 5 to get nice parallel projection.

That's a lot of text and images to explain 87 bytes but we did it! I feel this was the most important part of the demo. Without getting the cube down to this size it would be impossible to add exciting visual additions and a storyline which we'll discuss next.

3. Fake shading


Having a lot of space left allows us to use more of the Canvas API possibilities. I'd seen other Dweets use drawImage function for cool effects and wanted to try some things with it myself. The function can be used to create a copy of the current canvas or a part of it and draw it in another position. This can be useful for a mirror effect by combining it with scale or rotate function. What I did here was to adjust globalAlpha to 0.1 and copy the canvas onto itself at each iteration but with incrementing y-position. Since the stroke-function is drawing the lines several times at the same position they look dense. But the copies are moved so they remain transparent.

drawImage(c,globalAlpha=.1,i)

Below you can see the difference this subtle shading trick makes. I think it adds a sense of atmosphere and presence to a simple wireframe object...




At this point I was very excited how the demo was evolving and knew I had to fit a storyline to it.

4. Animating with perspective projection manipulation


When you want to manipulate how an object looks you have two options. Either you actually change the shape of the object by adjusting the data or you just change the way it is rendered. For our cube we could change the radius over time to make it larger. While this would otherwise work for the demo it would require more bytes than adjusting the projection variables. What we will do is change our perspective divider Z when the combination of elapsed time and current angle gets past 6.

Z=(t<6)+1+C(t)

What this does is zoom us inside the box over time. Because we do this to one corner at a time the sides of the box appear to open and wrap around us. I hope people actually let the demo run long enough to notice this. :) So that's my white room (without black curtains) which I personally think is among my nicest dweets. I leave you with the full 140 byte source code. Follow me on Twitter if you want to hear of new demos and blog updates - @jylikangas

with(x)for(i=16,c.width=198;i--;stroke()&drawImage(c,globalAlpha=.1,i))
lineTo(99+S(t+=(7-i)%2/.64)*99/(Z=(t<6)+1+C(t)),49+((i%4/2<<6)-25)/Z)

torstai 19. huhtikuuta 2018

Making of Choo Choo


In this article I will explain how my Dwitter demo 'Choo Choo!' was created. It will hopefully show that the key to fitting interesting demos in 140 chars is to extremely simplify the logic. After that you can just use any and all dirty code golfing tricks to shave off unnecessary bytes. All aboard! :)

1. Thinking in angles


If you start to define XYZ-coordinates for anything in your dweet you'll have used up your space in no time. Luckily there are simpler ways to look at things. For this demo I chose to think of everything in angles. We'll be using rotation of axes in 2D. The mathematically correct formula to do this would (probably) be...
x = r cos α
y = r sin α

Let's first draw an overhead view of a track that is shaped like a circle. A full circle in radians is PI * 2 so normally we would loop from 0 to circa 6.283 in really small increments. Here we don't have to be that precise. We'll increment the angle by 1 and just go around so many times that we end up filling the gaps. In the code examples w is canvas width, a is current angle and r is radius. We are using the Dwitter boilerplate which gives us shorthands S and C for Math.sin and Math.cos. It also gives us t for elapsed time...

for(c.width = w = i = 500; i--;){
    x.fillRect(250 + S(a = i) * (r = 100), w/4 + C(a) * r, 5, 5)
}

So our track is just a series of angles rendered with the rotation formula. Now let's think of a train as just another track, but positioned above the actual track. For presentation purposes we'll do this by using a slightly larger radius...

for(c.width = w = i = 500; i--;){
    x.fillRect(250 + S(a = i) * (r = 100), w/4 + C(a) * r, 5, 5)
    x.fillRect(250 + S(a = i) * (r = 110), w/4 + C(a) * r, 5, 5)
}


Now we have a train but it's as long as the track which is not very useful. Let's squeeze it smaller by dividing i with 500. This means our train is 1 radians in length while the track is circa 6.283.

for(c.width = w = i = 500; i--;){
    x.fillRect(250 + S(a = i) * (r = 100), w/4 + C(a) * r, 5, 5)
    x.fillRect(250 + S(a = i / 500) * (r = 110), w/4 + C(a) * r, 5, 5)
}

It would be nice to get the train in motion to make things more interesting. Since the train is just a series of angles, we can get it moving by adding time to each angle at each frame...

for(c.width = w = i = 500; i--;){
    x.fillRect(250 + S(a = i) * (r = 100), w/4 + C(a) * r, 5, 5)
    x.fillRect(250 + S(a = i / 500 + t) * (r = 110), w/4 + C(a) * r, 5, 5)
}

While this long ass train is nice it would probably break from bending like that. Luckily someone invented railcars. We can get them by splitting our train into groups of angles. By experimenting we can find a suitable number of cars and make the locomotive itself a bit shorter. Note that we need to round to integers to get the gaps. In code golfing we love to do this with bitwise OR...

for(c.width = w = i = 500; i--;){
    x.fillRect(250 + S(a = i) * (r = 100), w/4 + C(a) * r, 5, 5)
    x.fillRect(250 + S(a = i / 500 + t + (i / 90 | 0) / 9) * (r = 110) , w/4 + C(a) * r, 5, 5)
}


At this point it should be fairly obvious that we already have all the needed data and logic for our demo. We have successfully simplified the train and the track into angles. In the next step we will make the rendering more interesting by going 3D.

2. Rendering in 3D


The demo has perspective projection, but first we'll use parellel projection to achieve a 3D look with very little effort. All we need to do is make the radius used for the Y-axis rotation smaller by dividing it by 3. We'll do this to the track first.

for(c.width=w=i=500;i--;){
    x.fillRect(250 + S(a = i) * (r = 100), w/4 + C(a) * r / 3, 5, 5)
}
Now comes another important simplification - we can render the track and train at the same coordinates, but with inverse height. When we give the train a negative height it is drawn above its position and thus ends up nicely on the tracks...


for(c.width=w=i=500;i--;){
    x.fillRect(250 + S(a = i) * (r = 100), w/4 + C(a) * r / 3, 5, 5)
    x.fillRect(250 + S(a = i / 500 + t + (i / 90 | 0) / 9) * r, w/4 + C(a) * r / 3, 5, -5)
}

3. Different tracks


I always want my demos to have a storyline. What I mean by that is that the visuals somehow evolve over time. A good demo will suprise you in the end just when you think you've seen it all. In 'Choo Choo!' I achieved this by changing two separate things at different time intervals. First we'll look at how I change the track shape. It turns out all I had to do was to multiply the angle used for the y rotation formula. This adds waves to the circle. The final demo uses 4 multipliers which doesn't make it look too busy. I'm tempted to do a remix of the demo with some other shapes though.

for(c.width=w=i=500;i--;){
    x.fillRect(250 + S(a = i) * (r = 100), w/4 + C(a * 3) * r / 3, 5, 5)
    x.fillRect(250 + S(a = i / 500 + t + (i / 90 | 0) / 9) * r, w/4 + C(a * 3) * r / 3, 5, -5)
}


4. Multiple perspectives


Well actually there's only two but since the track shape keeps changing we get the illusion of a much more complex camera work. Perspective projection makes things appear smaller the further away they are from the viewer. Since we are thinking in angles our distance divider can be taken from our y axis rotation formula. To get our Z for a point in the track we just use the cosine of the angle and zoom out by adding either 1 or 2 to it based on elapsed time. Here we benefit from Javascript logical expressions which evaluate to 1 or 0.

Z=C(a)+1+(t%6<3)



I get easily obsessed with small details that I just have to have in the demos. Instead of taking the easy way out and having the track and train be the same width, I wanted the train to be wider. With this comes a problem of centering the train on the track. It took several extra bytes but I think the demo benefits form this detail. Another 2 byte cost came from deciding to use strokeRect instead of fillRect. I felt the wireframe style rendering emphasized the 3D look better.

When the overall idea is really simple the implementation shouldn't take much space. Still 140 bytes is so little that I had to dig deep into my bag of golfing tricks to get this all in. Below is the final source code and it looks like a mess. The most important thing was to create a function for drawing. It's rarely advisable on Dwitter but it works here cause we can reuse the drawing code to the extent where the track is basically added with just 6 more chars - F(i,4).

for(c.width=i=714;i--;F(t+i/714+(i>>7)/9,-9))(F=(a,o)=>x.strokeRect(357+S(a)*290-o/(Z=C(a)+1+(t%6<3)),99+C(a*(t/6%4|0))*99/Z,o/=Z/2,o))(i,4)

That's the 140 bytes of JavaScript that on a Dwitter boilerplate gives you a fun train ride. I hope this article has inspired you to have a go at code golfing yourself. Choo choo!

Follow me on Twitter if you want to hear of new demos and blog updates - @jylikangas