(train whistle)
- Hello, and welcome to a holiday coding challenge.
This coding challenge is a fundraiser
for Support P5 this Giving Season.
The Processing Foundation is the foundation
that maintains the open source software projects Processing,
p5 JS, Python Mode for Processing,
Processing for the Raspberry Pi, and Processing for Android.
And, if you use any of those tools
in your professional life, as a student, as a teacher,
and you have the means and can donate, please do.
There's a link in this video's description
for how to donate.
Also, you can donate directly on YouTube.
Right here, somewhere over there, I think,
is where you can donate on YouTube.
All of the information about Support P5 is on this post.
There are wonderful artworks that you can get
as giveaways from Maya Man, from Kate Hollenbach,
from The Coding Train itself.
That's me.
And Cy, The Coding Train community manager, designed a zine.
Saskia Freeke and more, so please donate.
The giveaways are available through this webpage.
And, of course, we'll gladly accept your donation
through the donate link right here on this page itself.
Thank you to Violet, who suggested this idea
as the holiday coding challenge.
This is a kaleidoscope painter that I found online
on this website, permadi.com.
Incidentally, this website has a lot
of wonderful tutorials and other information.
Go check it out.
But, we were thinking,
"Could this be used to create a snowflake?
"Could I make something like this where you could paint
"in p5 and make a snowflake pattern
"with six fold symmetry because we all know
"that snowflakes have six fold symmetry."
Which would give it some symmetry
and then rotate it also around six times,
and I think I'm pretty sure that's what that is happening
in that animation that I showed at the beginning.
So, this should be, hopefully, pretty beginner friendly.
If you're new to coding, I'm gonna do this
right here in the p5 web editor.
And, if you're using the p5 web editor,
that's because the Processing Foundation
maintains the p5 web editor.
It's a project created by Cassie Tarakajian,
and your donations are helping to support the web editor,
the p5 library, and more throughout 2020
as we go into this new year, new decade.
So, first, let's begin by just creating a program where,
when I click the mouse, I draw something.
And, this is actually quite easy to do because,
in the draw loop itself p5 has set up,
which will create a canvas, you can see it right there.
That's 400 by 400 pixels.
The draw function loops over and over again
and is always drawing a black background.
Black because of the number zero in the background function.
So, if I say stroke, which sets a stroke color,
and I say line between mouse X, mouse Y, those are
the coordinates where your mouse is actually in the sketch.
And, I wanna draw a line from there
to where I previously was, where the mouse previously was.
So, I could use some kind of variable
to keep track of the previous mouse
and know the current mouse and update it.
But, guess what.
(dings)
P5 has that built in for you, and I can say P mouse X
for previous mouse X, and P mouse Y for previous mouse Y.
So, if I run this again.
(laughs)
No!
What have I done wrong?
Oh, I know what!
I've forgotten everything that I ever learned
in the last 20 years.
(laughs)
Look, there's the little line.
Do you see it?
There it is.
I'm drawing the background over and over again in draw.
(sighs)
Classic.
So, I've got to move this into setup
because I only wanna draw the background once so that,
now, the line always appears, so there I am drawing.
So, some things I could do.
I can say, "If mouse is pressed,
"I only wanna draw that line if I am pressing the mouse."
That's going to allow me to do things like this.
And then, we can start to think about the thickness
of the line, so there's a lot of different ways I could.
Maybe I'll have that be pro line noise or something,
but I'm just gonna make it a little thicker right now.
Stroke weight, four.
Oh, let me auto refresh here.
So, that's thicker.
Let's do this, let's do this.
The speed of how I move the mouse is
how thick the line will be, so slower will be thicker.
Faster will be thinner.
I think that makes sense.
I think that's probably
what that kaleidoscope picture I showed does.
I can find the distance
between the current mouse and the previous mouse.
That's, you know, basically a value
that indicates to me the speed.
How far have I gone on each cycle through draw?
And then, I can say, "The stroke weight is map the distance,
"which could be anywhere from zero, right,
"if I'm not moving at all, all the way up."
I mean, it could.
Let's just give it, sort of, think about a maximum range
of something like 20, and I'm gonna invert that, 20 down.
I'm always going to have a stroke weight of at least one,
so let's see what happens there.
So, as I move.
Oh, well, of course, then
(laughs)
I'm making all of these kind of crazy mistakes.
Then, I also need to actually use
the stroke weight variable in the stroke weight function.
So, if I move really fast, it's really thick.
Well, I kind of wanna interpolate that
so that it's a little smoother, but this'll do.
This'll do just fine.
All right, so here's the thing.
This was the easy part, right.
This is the easy part.
I just want to draw using the mouse.
Now, I need to figure out how to get that symmetry.
I think maybe we'll wanna use
the whiteboard for this for a second.
So, we have a canvas.
That's 400 by 400.
We can see here this idea
of a snowflake with six fold symmetry.
One, two, three, four, five, six.
So, the angle that's important here is
this particular angle, right, which is 60 degrees I believe.
So, lets say, for the sake of argument,
I'm using the mouse to draw over here, so mouse X.
Mouse Y is happening over here.
What I wanna do is draw the same thing here,
and here, and here, and here, kind of rotating along.
But, also, I think I wanna flip it each time.
In this case, I think that's going to create
where you can see that these two are kind of pointing
at each other, so I think there's
also gonna be some aspect of flipping it.
No wonder this, by the way.
There's a big mistake here.
No wonder this was going wonky.
Code Bulletin pointed this out.
And, also, maybe other people in the chat.
P mouse Y, that's why it was kind of doing some weird stuff.
Yeah, this is much more like what I expected.
Let's try this.
So, just as the simplest way of thinking about it
for a second, let's go, "I goes from zero to six, right."
I wanna repeat what I'm doing six times.
I think all this is also going to be easier
if I translate to the center,
so I'm gonna need to use translate and rotate.
Translate and rotate.
Translate takes as its arguments an X and a Y,
and rotate takes as its argument an angle.
And, what this allows me to do is reposition
wherever the origin is, so I want
the origin to be in the center.
And, I'm gonna draw a relative to that,
but then I can also rotate what I'm drawing
to also duplicate it here, rotate to duplicate it here,
rotate to duplicate it here.
I kind of glossed over that very quickly, but I will refer
you to my p5 JS transformations video tutorial,
which goes through translate and rotate in much more detail.
So, I'm also going to now create a variable called MX,
which is mouse X minus minus width divided by two.
And, MY and PMX and PMY.
Ultimately, what I want is I want the mouse position
relative to the center, so the easiest way
for me to do that is just store it in separate variables.
So, I've got the same code here.
Same thing's happening.
It's just everything's now being drawn relative
to the center, and I am also drawing it six times.
But, I'm drawing it six times in the same place.
I'm gonna add some alpha so we see what's going on.
Could I now say, "Rotate by I times 60 degrees"?
Because what I wanna do is, first, draw it here,
then here, then here, and I wanna rotate one,
two, three, four, five, six times.
Oh, and let's set angle mode to degrees
because I'm thinking, in terms of degrees, 60 degrees.
I could think in terms of radians,
and that would be pi divided by three.
There we go.
Now, this is isn't exactly doing what I would expect.
It's the right idea, interesting.
Getting kind of a little bit of a kaleidoscope feel to it.
I think what I actually want is
to bisect each one of these and, basically,
take whatever I'm drawing here and invert it.
How can I draw this?
I'm so bad at drawing.
Invert it there, right.
I need, actually, to create something
that feels much more like a snowflake pattern.
What I'm drawing, essentially, is this element here
that's on this side of the snowflake.
So, I need to flip it over, draw it here,
flip it, draw it here, so I actually
wanna rotate not six times but 12 times.
And, each time, I want to flip what I'm drawing.
So, I want to rotate 12 times.
And, this, by the way,
I should have the angle, 360 divided by 12.
I mean, this is kind of crazy
that I'm having these hard coded values.
There, so this is what I want.
This is much more close to what I want,
but what I want to see now is, also,
I want to invert every other one.
And, Simon is making a good suggestion
in the chat, to draw a line.
So, I think it actually would be
quite useful for me to do that.
I'm gonna draw more than just the background,
just so we sort of see.
I wanna just draw these lines.
Woo, something weird is going on.
(buzzing)
I've done something ridiculous.
I've made a pretty serious error.
I forgot.
I mean, I should know this.
I should know all of this stuff, but even I.
I've done this so many times I just forget it.
Transformations and the word transformation applies
to any translation, rotation, or scale.
I'm gonna scale in a minute.
They're cumulative.
So, if I say rotate by 30 degrees, I'm gonna do this.
So, rotate I times 30.
Rotate 30 degrees.
Now, well, rotate zero.
Then, I is one, rotate one times 30 is 30.
Then, I is two, rotate two times 30 is 60.
Ah!
I just wanna rotate 30 each time.
I was thinking about how far,
from the beginning, I wanna rotate.
But, ultimately, of course, I just wanna rotate that amount.
Let's create a variable.
We'll call it symmetry,
and we're gonna have 12 fold symmetry here.
I really want six fold symmetry,
but each six fold symmetry segment will be symmetrical.
So, I'm gonna say, "Angle equals 360 divided by symmetry."
I goes from zero to symmetry.
And then, I'm just gonna rotate by the angle.
There we go.
That's what I'm looking to do.
And then, the same thing.
And, this, I think, can just be
a global variable for right now.
That way, I can just do the same exact thing here.
And, we see this is what I was looking to see,
something more like this.
Now, this is one, two, three,
four, five, six, seven, eight, nine, 10.
We should count, we should get 12.
So, let me see if I can do that extra flipping.
Another transformation function that I can use,
beside translate and rotate, is a function called scale.
Now, typically, we think of scale
as scaling something bigger or smaller.
So, for example, if I were to use the ellipse function
to draw a circle and, right before that, I were
to say scale is two, that means double the size or 200%.
So, it would look, yeah.
I'm not gonna.
In this case, it's double the radius, so I would have
something like an ellipse that looks like this.
But, scale, first of all, you can scale on different axes.
So, I could say, "2, 1."
And, if I did that, it would just scale the horizontal axis
but not the vertical, would still be the same scale.
So, my ellipse would look like this.
It would be stretched horizontally.
Incidentally, I can also apply a negative number to scale.
If I apply a negative number to scale,
then it's going to draw it in the opposite direction.
It's going to invert it, which, in the case of a circle,
doesn't matter because it's symmetrical along the axis.
But, if I were drawing something like a triangle,
then, if I had negative two, I would see
the triangle suddenly be drawn like this.
What I thought would make sense is for me to, for any odd.
I mean, it doesn't really matter.
I don't know if I want odd or even.
For every other one, if I module is, is zero or one,
I'm not sure which one, to invert the scale horizontally.
So, in that, say, "Scale, negative one, one."
But, once again, this is happening cumulatively.
So, in this case, maybe what I wanna do just to allow myself
to be less confused is to add push and pop
so that any rotations or translations or scales
that happen within each, and not outside the loop,
right inside the loop, for each section, are contained.
And so, in this case, I would wanna go back
to rotating I times the angle.
So, now, they're not cumulative because I'm saving
the transformation state in its original way,
scaling by inverting it, drawing, sorry, rotating.
Do I wanna scale before rotate?
Rotate?
I think I wanna rotate and then flip it.
We're gonna find out.
Okay, so this is kind of what I was expecting,
except I'm not actually seeing where I'm drawing
because I think this should be one.
Yeah, there we go.
This is more like what I was expecting, there.
Is this a way to draw a snowflake?
(laughs)
I feel like these are snowflake like.
One, two, three.
But, really getting 12.
If I just change this now to six,
did I overthink the symmetry?
And, actually, this is now gonna give me six fold symmetry.
Yeah, okay, I overthought the symmetry, I guess.
Oh, wait, this has to be symmetry.
All right, I think we've made kaleidoscope snowflakes.
Basically, I see.
Look at this.
Rotate by the angle, push, pop,
and draw the line twice.
And, the second time with it flipped.
There we go.
Ah!
Finally.
Tada!
Okay, what we have now.
Now, Zachary is suggesting, yeah.
I think this would make a lot more sense,
to give a lot more space now, and here we go.
So, what I would like to do, and then the next thing
I wanna do is add a button to save the image.
Button mouse pressed, and save snowflake.
And, I'm gonna write a little function.
Save snowflake, that, and let's make a clear button also.
So, we're gonna have a save button and a clear button.
And, with two callbacks, save snowflake and clear canvas.
So, when I wanna save, I just wanna save snowflake.png.
This will take whatever's in the canvas
and send it to the downloads directory.
And, when I wanna clear, I just wanna draw the background,
reclear the background at zero.
I have a pair of unnecessary push and pop.
Yeah, so you're right that this push and pop
is unnecessary, so let's take that out.
I sort of like having it in there,
but you're right that it's unnecessary.
Let's think about color,
and let's think about line thickness.
So, right now, the line thickness is determined
by the speed of the mouse.
I guess that kind of makes sense,
but I wonder if it would actually be better
to have the line thickness be controlled by a slider.
And, we'll have color be automated.
So, we're gonna create a slider that has a range
between one pixel and 32, and we'll start it at four.
And, we'll give it increments of 0.1.
And then, what I'm gonna do is say
the stroke weight is not the distance any more,
but is actually the value from the slider.
So, I wanna actually be able to see this more full screen,
so I'm going to go to share.
And, I'm gonna look at this full screen URL
and just open that up here.
So, now, if I'm drawing, that's the line thickness.
If I were to make this larger, look at this.
So, I need the mouse is pressed to be only if the mouse is
within the canvas, but that's not a huge deal.
But, if I make it thicker, right,
I could do something thicker here, and then I could
really be much more intricate about my design.
This is kind of awkward to have to go down to the slider.
I don't love this interaction, but this is better.
So, I'm just gonna fix that bug to only do stuff
if mouse X is greater than zero, less than the width.
I wanna make sure the mouse is in the window,
so that should fix this issue.
And, I'm gonna just refresh over here where,
if I'm operating the slider,
we don't see any drawing happen.
Let's use noise for the color,
so I'm gonna have an offset start at zero.
I'm gonna set the color mode to HSB, 255, 255, 255.
And then, what I will do is I will get the hue value
to be the noise function of X offset times 255.
And, we'll set hue, and we'll have a saturation
and brightness all the way up.
So, you can see it's always green right now.
But, now, if I were to have X offset change over time,
that hue is gonna change,
so this is my rainbow, multicolored snowflake.
So, I think this is done.
I'm gonna attempt to design a snowflake now.
We're gonna see.
How good is my snowflake design tool?
So, right now, if I design the snowflake,
get a little thicker, and get in the center,
and then make it a little thinner for over here,
a lot of green and blue.
If I do this and then I hit save, now I have my snowflake.
So, I want a way of collecting all of these
into a nice collection of snowflakes.
Let's have the hue move with the sign function.
Needs to stay gray scale.
The people are really advocating for gray scale.
(laughs)
I'm good with that.
So, I'm gonna start out with sort of a thick line
towards the center, and I'm gonna use
a thinner line to do some nice detail.
There, this is my snowflake that I made.
Goth snowflakes.
People prefer the color.
Okay, I give up.
This is the end of this coding challenge.
Here is a kaleidoscope snowflake drawing generator.
I'm sure you can make your own version of this
and think about color and the interface
and all sorts of different and creative ways,
and I hope that you will do that.
♪ On the 12th day of coding ♪
♪ My p5 gave to me ♪
♪ 12 neural networks ♪
♪ 11 index errors ♪
♪ 10 people posing ♪
♪ Nine for loops looping ♪
♪ Eight fractals forming ♪
♪ Seven sorters sorting ♪
♪ Six cameras switching ♪
♪ Five golden ♪
- Ah!
♪ Five golden rings ♪
♪ Four dripping ♪
♪ Four flocking boids ♪
♪ Three of these dots ♪
♪ Two pull requests ♪
♪ And a null pointing binary tree ♪
(upbeat ukulele music)
(air kiss)
- Happy holidays!
(upbeat music)