Doodle Insights #20: The Twisting 4-Sided Column Effect (Patreon)
Content
~~~~
These Doodle Insights are brought to you by my super generous supporters on Patreon!
How about learning how to render a really cool effect with just a few fairly simple lines of code? Does that sound good?
The Twisting Four-Sided Column Effect is, I assume, a classic demo-scene thing because I've seen it done on several platforms and by multiple people, including on the Pico-8. I'm pretty sure this effect has an actual name but I have no idea what that would be so I'm just going to call it The Twisting Four-Sided Column Effect. Here's what it looks like!
The code for this effect is no more than 25 lines long, and I'm talking clear code and pretty short lines.
We're going to get there step-by-step, so that at the end you'll be able to make your own version of this effect, hopefully with your own clever twist!
Let's start with the four-sided pillar!
function _draw()
cls()
local a=time()*0.2
for i=0,0.75,0.25 do
local x1=64+32*cos(a+i)
local x2=64+32*cos(a+i+0.25)
if x1>x2 then
rectfill(x1,0,x2,127,7)
rect(x1,0,x1,127,0)
rect(x2,0,x2,127,0)
end
end
end
</code></code>
What this code does is that at every frame, we're drawing the visible sides of the rotating pillars. (and their vertical borders)
First we clear the screen. Then we find a value for 'a', the rotation angle of the pillar.
Now we're at the most important part of this effect, the 4-sides 'for' loop. With 'i' going from 0 to 0.75, by 0.25 at a time, 'i' will be the normal angle for each of the 4 corners of the pillar. (imagine it from above) For each of the corner, the next corner will have the normal 'i+0.25'. From there we can have the two x coordinates delimiting each side of the pillar with these two lines:
<code><code>local x1=64+32*cos(a+i)
local x2=64+32*cos(a+i+0.25)
</code></code>
On the Pico-8, the resolution is 128x128, that's why we're using 64 as the 'center' value and 32 as 'width' value. (mathematically speaking it is not really the width of the pillar but more of a factor - the actual width of the pillar would be 32/√2 - hopefully you get what I mean)
Second note: on Pico-8, angles go from 0 to 1 instead of 0-2PI. I wrote another Doodle Insights about this! So 0.25 is the equivalent of 90 degrees or PI/2 radians.
We have our two x coordinates, now we need to know whether this side of the pillar should be visible or hidden behind the other half of the pillar. Nothing easier, all we have to do is check if x1 comes before or after x2! If x1 comes before, we're on side A, if x2 comes before, we're on side B. It's up to you to choose which side you make visible, it will not affect the effect whatsoever. I'm going with side B here with 'if x1>x2'.
Now that we know we're drawing this side, let's draw this side! We have our two x coordinates, so all we have to do is draw a big rectangle going from x1 to x2 and that takes all the height of the screen. But since we're not playing with colors yet, we're also drawing the limits of each sides with two black rect calls, to differentiate the white sides.
We have our pillar! Let's twist it!
function _draw()
cls()
local t=time()/2
for y=0,127 do
local yy=y/1024
local a=cos(0.2*sin(t*0.1+yy*2))+0.5*cos(-0.2*t+yy/2)
for i=0,0.75,0.25 do
local x1=64+32*cos(a+i)
local x2=64+32*cos(a+i+0.25)
if x1>x2 then
rect(x1,y,x2,y,7)
pset(x1,y,0)
pset(x2,y,0)
end
end
end
end
</code></code>
We have a bigger loop! Now we're processing every horizontal line one by one! You'll note that our rectfill call was traded for a rect call, (not that it really matters, rectfill would still work) as we are only drawing horizontal lines, and the old rect calls, for the limits of each side of the pillar, were traded for pset calls.
Other change: we have two new values, 't' and 'yy'. 't' is just a shortcut value we're using to store the time (divided by two to make the movements slower) and 'yy' is also a shortcut value that is simply 'y' divided by a big number so that it's easier to use with angles.
And of course, the value for 'a' changed! For our previous iteration we only wanted to rotate the pillar, so 'a=time()*0.2' was sufficient, but now we want to twist the pillar in crazy ways, and what's crazier than trigonometry mixed with more trigonometry and some arbitrary numbers?
(pure RNG, that's what would be crazier, but we want our angles to change smoothly from line to line and from frame to frame - the trigonometry functions have this property)
Ok, we have out twisting column! Let's add some colors!
plt={0,1,2,8,14,15,7}
fillp(0b0101010101010101)
function _draw()
cls()
local t=time()/2
for y=0,127 do
local yy=y/1024
local a=cos(0.2*sin(t*0.1+yy*2))+0.5*cos(-0.2*t+yy/2)
for i=0,0.75,0.25 do
local x1=64+32*cos(a+i)
local x2=64+32*cos(a+i+0.25)
if x1>x2 then
local c=(x1-x2)/(1.5*32)*#plt+1
local ca=plt[flr(c)]
local cb=plt[min(flr(c+0.5),#plt)]
rect(x1,y,x2,y,ca+cb*16)
end
end
end
end
</code></code>
Note: Pico-8 uses an indexed color palette of 16 colors rather than RGB values.
Before we get to everything else, let's quickly talk about that 'fillp' function! This function was added in the 0.1.11 version of Pico-8, which is still quite recent as I write this. It lets your next draw calls (rect, circfill, pset, etc...) draw two colors instead of just one, in a pattern of your composition. The pattern is passed as parameter to the 'fillp' function as a bitfield and from there you can use 'color1+color2*16' as color parameter for your draw calls. Here's the bitfield I'm giving fillp in this example:
<code><code>0b 0 1 0 1
0 1 0 1
0 1 0 1
0 1 0 1
</code></code>
And with this bitfield we get vertical stripes!
There's big chances I'll write a Doodle Insights dedicated to 'fillp' at some point, because it's very cool and I'm sure there's tons of creative ways to use it!
Parenthesis closed! Now the line before 'fillp'! We're defining an array with the colors we want to see on our twisting column! They are in order of lightness and we'll want the last color (7 - white) to be shown when the side of the pillar is aligned with the screen.
The other thing that changed is after the 'if x1>x2', where we decide what color should be drawn for each horizontal line.
Maybe you thought we'd need some clever maths to get a value that corresponds to the inclination of the plane we're on. Well nope! All we need is the difference between x1 and x2! If they're far apart then this side of the column if angled towards the screen, if they're close then this side of the column is angled towards the side of the screen. So we want this difference, vaguely normalized (that's what the division by '1.5*32' is about) and then set to the scale of our color array.
To make use of our fillp call, we're taking two colors from our array, the first one is the one from the algebra described above, the second one is the same plus '0.5', which means the two colors will be different only if 'ca%1>0.5' and that's going to give us a very good gradient! (if you didn't understand that last part, just trust me that the result is pretty because of this)
We removed the draw calls for the side outlines because we can make out the sides just from the color difference now!
Let's go further!
function _draw()
cls()
local t=time()/2
for y=0,127 do
local yy=y/1024
local a=cos(0.2*sin(t*0.1+yy*2))+0.5*cos(-0.2*t+yy/2)
local w=32+4*(sin(-t+y/128)+0.5*cos(0.5*t-y/64))
local x=64+16*cos(sin(t*0.1+yy*2))
for i=0,0.75,0.25 do
local x1=x+w*cos(i+a)
local x2=x+w*cos(i+a+0.25)
if x1>x2 then
local c=(x1-x2)/(1.5*w)*#plt+1
local ca=plt[flr(c)]
local cb=plt[min(flr(c+0.5),#plt)]
rect(x1,y,x2,y,ca+cb*16)
end
end
end
end
</code></code>
More trigonometric madness!
Two new values are being defined just after 'a'! For each horizontal line we are taking a width 'w' and a horizontal center 'x', both of which are defined much the same way as 'a', with 'cos', 'sin' and arbitrary values, put in a random order.
From there all we have to do is use these new values! The main two lines that changed are these:
<code><code>local x1=64+32*cos(a+i)
local x2=64+32*cos(a+i+0.25)
--becomes
local x1=x+w*cos(i+a)
local x2=x+w*cos(i+a+0.25)
</code></code>
And that's it! We have our crazy Twisting Four-Sided Column effect!
From there it's up to you to bring your own magic to the formula! Here's a simple example where I made it five-sided instead of four and then a slightly more complex example where it's actually four four-sided columns!
This last one with the four columns, I actually reduced its code to 280 characters - the size of a tweet - for fun! Check it out!
That's it for those doodle insights! If you enjoyed this, maybe check out the other Doodle Insights! I've done a really bad job of writing them regularly these last few months but I'm trying to get back on top of it!
If you have any remarks or questions, do let me know in the comments or on Twitter, I will do my best to reply!
And thank you so much to all my Patreon supporters who are making it possible for me to write this and produce free content in general! Here are their names!
Joseph White, Jefff, Riccardo Straccia, HERVAN, Andreas Bretteville, Bitzawolf, Alan Oliver, Paul Nguyen, Dan Lewis, Christian Östman, Dan Rees-Jones, Reza Esmaili, Thomas Wright, Chris McCluskey, Pizza, Joel Jorgensen, Corey O'Connor, Marty Kovach, Cole Smith, Giles Graham, Tim and Alexandra Swast, Sasha Bilton, berkfrei, Jearl, Dave Hoffman, Flo Devaux, Thomas Morison, David Dresbach, Egor Dorichev, Jakub Wasilewski, amaris, Brent Werness, Nick Hughes, Anne Le Clech, nanoplink, Nate Wiesel, Sean S. LeBlanc, Matt Hughes, C, Andrew Reist and vaporstack.
Special thanks to Ryan Malm who is currently supporting my work at the 16$ tier!
Patreon supporters get exclusive content and updates, please do consider becoming one yourself if you enjoy my work!
Thank you for reading and make plenty of twisting columns!
TRASEVOL_DOG