A textbook execution of recursive elegance that perfectly illustrates the harmony between C and geometry. It is a solid, no-nonsense demonstration, even if it treads very familiar academic ground.
Approfondir
Prérequis
- Pas de données disponibles.
Prochaines étapes
- Pas de données disponibles.
Approfondir
Sierpinski Triangle in CAjouté :
Hello everybody. In today's episode, we're going to create this Serpinsky triangle animation. This is an animation that relies on the recursive Sir Pinsky triangle. It's a big triangle that you start splitting up into smaller triangles. And by recursing over this and animating it in Rayip, you will get this nice little animation.
We learn a couple of things about geometry. We derive all the ge geometry ourselves. Of course, let's have a look here. We're going to do some geometry here. We're going to do some rail drawing, some keyboard input handling.
So, there's a lot of stuff to learn about C here and it's quite a fun project in my opinion. So, let's just jump right in and start.
All right. Where do we even start? So I've pulled up this resource here explaining how a Sinsky triangle is constructed. It starts with a simple triangle and then you put another triangle inside. it's flipped upside down or rather you reconstruct it in such a way that you now have three triangles stacked upon each other and then you somehow recurse on that structure and it all results in this big thing. So you replace one big triangle with three smaller ones. Then you replace the three smaller ones with yet another set of three smaller ones per smaller one from the original from the original.
[sighs and gasps] Okay, this is going to be very interesting. I have not prepared this video so no idea how it's going to work out.
Let's just adjust the camera a bit.
Okay. So, Sir Pinsky triangle.
That's the project directory.
I hope I spelled it correctly. Let's check Sir Pinsky. Yes. Okay. And I'm going to call the source file triangle.
C. We are going to include standard IO because we always need it. And what's the first thing that we definitely need?
Well, we definitely need to be able to draw a triangle. That's absolutely clear. But even before that, we need to be able to we need to be able to how the hell do I get myself centered on the screen like Am I stupid?
Ah, this is okay. [laughter] Okay.
I always have issues with setting the camera up properly. So, what we definitely need to do is generate a triangle. But we also need to be able to generate the triangle on some surface in some window. So, the first thing, the very very very first thing that we need to be able to do is create a window. We can do that using ray lip.h. h ray lip the ray lip library which is a graphics library for those of you who are not aware of it and we're going to create a main method but we are not going to use any input arguments I think at least not for a start and to work with ray let me just open the header file user include rayip.h H.
There it is. And the first thing we're going to do is init a window. So, initialize a window. Again, the first thing that we want to do is pull up a simple window on the screen with a specific width and a height and a title, as you can see in this library function prototype thingy. [gasps] And I'm going to call it Seal Pinsky triangle.
That's the window title. And then what are we going to do? We are defining a width and a height for the screen for the window. I mean 900 by 600. This is just my standard size for all projects.
It just somehow established itself like this.
And then after initializing a window, we close the window in the end. Okay, let's do that. Close window that does not take any arguments as input. And then we can loop while the window while the window should not close.
We do something here. Do something. I don't know what we're going to do.
And we're also going to use Let's see.
Maximize window. No, we don't need that.
We are going to use What are we going to use? We're going to set a frame rate, a target frame rate for the project. Otherwise, the executable, this loop will run as fast as it can, which will of course um just consume unnecessary CPU. So, let's deal with frame rates here. Set FPS. And I'm going to set it to I don't know. It's not even going to be a very dynamic simulation. So, it's how about just one frame per second? We don't even need more than that. I know in video games you would set a standard value like 60 or 120 frames per second or you could you could even make it configurable by the user in some configuration file and then inside this loop actually if we compile this I just wanted to write something in here but I think if we compile this and execute it we will already see a window.
So let's go into the sepinsky triangle project directory here and compile the file to a triangle named executable. Input file is called triangle.c and then we are going to link ray lip and I think this should already compile and it does not compile right because I called set fps that is not defined. That should be set target FPS. Let's just confirm this in the header file. Set target FPS. There it is.
Okay. Now we can compile it. And this compiled. Let me just show it to you again. And now there should be an executable in our directory here called triangle that we can run. And now we have a small window. And it's black. Perfect. and it's not responding and therefore force quitting what therefore uh telling us uh we should force quit the app. Why is that happening?
Well, probably because of this loop and nothing is happening inside this loop and so everything is just um ah so probably probably we set a target frame rate per second here but it's not effective yet. We can confirm this by checking the system monitor. Let me just show you the system monitor here on my system. Then if we go into resources, you will see that the topmost entry is the CPU usage. And if I execute this right now, then you will see that the CPU usage spikes.
And this is because the loop is running as fast as it can. And this is in turn because there is no FPS handling right now. And this is implicitly done, I think, by begin drawing.
and end drawing.
And those two method calls, function calls, I mean they signal to the rail library somehow which which frame delta time is required or is currently is current which which frame delta time is currently uh used. how how long a frame needs to be h how much time a frame needs to to render and then I think there's an implicit pause in those beginning and drawing calls. So if we now compile and execute this again we should see a black window and we don't get any spiking CPU usage. Let's just confirm it in the CPU monitor again. And as you can see, the CPU usage is fairly low.
Okay, so that looks fine. We now have a window that we can draw something on.
And this is the foundation, the skeleton, the base skeleton for our project. And now what's left is we need to draw a triangle. How do we draw a triangle? I have never drawn a triangle in Rail before, I think.
So in those cases what I usually do is I just search the header file for the keyword triangle and there it is. Draw a triangle which is exactly what we want probably. How is it used? It takes as input a vector v1 and a vector v2.
Those are probably just positions. Oh, and a vector v3. Then those might be coordinates of the corners.
I don't know honestly. So it takes as input three vectors and then those must be the corners of the triangle.
What other triangles triangle functions are there? Because that's quite requiring on us. It's quite demanding.
to compute all three corners. Is it maybe possible to just draw an a rectangle with all sides being equal here somewhere? This is a draw rectangle draw triangle lines function which probably just draws the the boundary of a triangle.
Draw triangle fan. What's that?
Probably not required. It's not probably not matching our use case. And draw triangle script is for draw triangle strip is for drawing multiple triangles in one go that are somehow connected which with each other. We have used that function in the digital clock in Rayip episode and then that's it. So we are probably left with the first method here and the first function draw triangle using the three corners. So let's try and draw a triangle.
Before we draw something, we need to clear the background because only then we are sure we draw on a black canvas.
Again, this is important when we draw multiple rounds of whatever animation and we always want to be sure that we start with a clean frame. again. And then we draw a rectangle here.
And let's define a couple of corners here or a couple of points. Vector to points.
Should we make it an array? No, probably not. So let's let's define a top point, a bottom left point, and a bottom right point. So the top point is located at x coordinate that is the center of the screen. So it's width divided by two and the ycoordinate is I don't know something.
Let's just hardcode something here.
And then the bottom left bottom left point is a another vector 2.
Vector two. here.
It's located at width also around the center of the screen, but we offset it. Let's say minus 50.
And then the y-coordinate is 100. I'm just guessing what might make sense. And the bottom right is width divided by two + 50. So it's shifted to the right again. And the y-coordinate is the same as the bottom left corner. So let's see. Now we draw a rectangle using the top, the bottom left, and the bottom right coordinates. And probably also a color, right? A color.
Oh, and there's an important note here.
Draw a color fil triangle vertex in counterclockwise order. So, we're probably going to need to define the coordinates in a different order. So, let's just try and see what happens now if we compile this. Color white is not defined. Why?
I know it is defined, but it is an incompatible type for argument four of draw rectangle. How is that possible?
Argument four is a color.
What? Ah, I'm drawing a rectangle. And of course, I need to draw a triangle.
So, that was probably just out of habit.
It's very likely I I mix up rectangle and triangle over and over again in this episode. So, now we have a couple more errors here.
Error incompatible type for argument three of draw triangle. Top, bottom left, bottom right. Why does bottom right require an integer?
Draw triangle.
[sighs] Vector V1. Vector V2.
I don't know if you hear it, but someone's hammering here.
Vector V3 color.
H. [clears throat] So what's the issue?
We define three vectors.
Ah, of course. What the hell am I always doing here?
So I forgot the vector two here and I just called it called them vectors. Now if we compile it works and if we execute we see a rectangle a a rectangle a triangle on the screen. So this looks very very good so far.
[snorts] But what about the order of the coordinates? There was some warning about the order of the coordinates. The order must be but wait vertex in counterclockwise order.
I defined the the points in counterclockwise order.
Yes, I did that because the I first defined I I first provided the top and then the bottom left. So that's already counterclockwise.
Okay, so by accident I did it correctly.
And this is how we draw a triangle. And now I think the next step would be to draw a triangle of equal side side lengths, right? because the one that I'm currently drawing is very skewed as you can see here.
So maybe it makes sense to define a starting side length. Define start side length like this.
And then draw a triangle void draw triangle with a specific side length at a specific point. But how do we def which point do we use? Do we use a top coordinate? Maybe let's just assume we pass a vector 2 top coordinate and a side length. And the side length is a floating point number. Let's say it starts at 300 side length and then we draw a triangle because we we need to create a function in order to create multiple such triangles in the future because we're going to recursively call it somehow.
So now my brain is braining Instead of using all of this, we do instead we move Let's just move this logic into the function.
Oh, I just messed up the function.
So side length and then copy this stuff up here.
Okay.
And the next step would be to call this function draw triangle with a side length that is the starting side length and the starting top coordinate. So let's pass a vector two here with a width divided by two 50 top coordinate.
This doesn't work.
What if I specify a vector 2 top here?
That is a vector 2 width / 2 and 50. That's the new top coordinate semicolon at the end. Remove all of this and pass it here.
This doesn't compile.
Top is redeclared as a different kind of symbol. Where is top used?
Why is it redeclared?
Ah, of course, of course I was using this line here from the previous um way of creating a rectangle.
But here we are passing a top vector. So now I think it should compile. Yes, it does compile. And we are still drawing the same rectangle dry triangle of course. I mean then now we specify which side length this triangle needs to have.
How do we know the side length of this of this triangle? Now if we have a triangle with a specific side length then how given the top coordinate do we know the bottom left and the bottom right coordinates?
I shouldn't be too hard actually. I know in right triang triangles we could use um the Pythagorean theorem theorem uh but in equalssided triangles I don't know honestly just let me think for a second we start with some triangle Here we know ah we know that the bottom side length ah of course the well we definitely know the x coordinate and the the x-coordinates of the bottom left and the bottom right because they are just let's just write that down programmatically the x coordinate is just width divided by two.
Oh no, it's just the top dox / 2. No, it's the top dox minus the side length / two.
That's the x coordinate of the bottom left. And then the same thing for the bottom right. It's the top dox plus the side length / two. But what about the y-coordinate? The y-coordinate must be the same for both bottom [snorts] coordinates because they are both well at the same height.
And it's not the full side length of course because here the side length the side of this triangle is skewed.
But if I use this drawing app to visualize it, let's say this is the triangle. Oh my god, that's that's horrible.
Can I make it bigger for you? Yes.
Let's say this is the triangle and we have now found this X and this X coordinate.
And if we divide this triangle in two halves like this, then we know that this is a right angle.
And we know that this is 60° because the sum of all angles must be 180. So 180 / 3 equal angles is 60°.
Then we can find the ycoordinate here which is this value y.
y is equal to the starting y value here.
Well let's put let's call it y dash.
This is this bottom one is ydash. The ydash is equal to the starting y of course plus and then we do something with s or cosine the side length the side length here side multiplied by either s or cosine or tan.
No, this right here is the side length we're talking about.
And y / the side length is the sign should be the sign of 60° I think.
[clears throat] Does it make sense? Because if the angle 60 was zero then this y would also be offset by zero. Yeah. Perfect. Okay. So I think this this is correct. So [clears throat] float y is equal to top doy plus side length multiplied by sin f for floating point numbers as opposed to sign which which usually works or normally works with doubles. But we don't need that precision here.
M sin f of 60° and 60° is 60. And then sin f is of course expecting the angle in radians as all mathematic functions are expecting it in radians.
So we do which is quite handy degrees to radians which is a precomputed macro provided by ray. So let's look up degrees to radians. It's defined here as pi / 180.
So this is coming from the rail library.
Okay.
And for all of this we need to include math.h math.h since we are using the sign function.
And I think now we should be drawing a triangle of equal side lengths after we have solved the comp the compilation error. Of course, error expected expression before vector 2.
And of course I forgot providing the y-coordinate here y and same thing here y And now we can compile this and execute.
And now we have an equalssided triangle. At least it looks like an equal-sided triangle.
And I think I can make the starting side length a bit bigger here. Let's make it 600.
And this one looks very, very nice.
Maybe even a bit too long. 580. How about that?
And I would say that one is almost perfect. 560.
Shouldn't be too close to the bottom corner of the window. The bottom side of the window. This one is perfect. Okay.
So, this is our starting triangle for the Sir Pinsky triangle.
Now what we have done is we have created all the logic to create a triangle given a top coordinate an equals sided triangle given a top coordinate and a side length and that's already a lot of the logic and I think the rest of the project is just somehow to somehow implement this recursion and this is the interesting part of this project [gasps] by the way a fun fact there a couple of program programming languages or maybe one I I know of one the Huskell programming language does not provide any loops so there's no concept as as a for loop or a y loop in Huskell there's only recursion so if you want to do any looping over anything you always have to loop over it I know the language is not used in practice that much there are a couple of good um well-known software projects project projects I think pandoc pandock this one is a huskell project it's quite well known it's a document converter but yeah some people really live without while and for loops some programmers are crazy so for us Recursion is just a fun concept that we're going to practice today.
And let's see how we do it. So we have a starting triangle and it has a top coordinate.
Ah, and now how do we split it into more than one triangle?
Well, we know the top coordinate and then we can divide the y. We can just we can just find the other coordinates probably using similar computation as we have derived here.
So we can find the other top coordinates here for the other two rectangles.
The first one has the same top coordinate and then we divide the side length by two.
>> [sighs] >> But in practice, do we need any data structure to store all these triangles?
That's what I'm wondering about. Do I need to somehow store a global top coordinate or maybe even all the coordinates of the other triangles? I don't think so, honestly.
So maybe we can just draw a triangle and we can just create some function here.
I don't even know if it returns anything. So this is now the hard part.
The thinking recursively is not really straightforward to me.
This is the sir pinsky sirinsky triangle function. We're going to define What is the input argument actually of the sepinsky triangle?
The sepinsky triangle takes as input no argument for now unless we find out that it needs some argument.
And then this function calls the draw the draw triangle function which will somehow recurse or no that sepinsky triangle function should be the recursor.
Then let's undo all of this.
We do a sirpinsky triangle starting from a top coordinate and what's the other argument? It's the side length. So it has the same the same arguments as the draw triangle function but this one is the recursor. This will be responsible for recursing.
Then we draw a triangle here.
Okay, wait, wait, wait. Let's take it slow. How do we recurse? At some point, we are going to call the function itself, Sinsky triangle for some new coordinate here with some new side length. And we're going to do it for every Serpinsky triangle. We're going to create three children.
So we're going to loop three times for int i equals z i smaller than 3 i ++. We are looping three more times.
This is the loop.
And then all recursors need some end condition because otherwise we will infinitely recurse and the program will just hang up. So when do we end? The simple approach would be and and this is something that some of you have commented in past episodes would be to define a maximum depth of the sinsky triangle.
However, then we would need to track the depth somehow and how I did it in a past episode where we recursed on something was that I checked if the side length is smaller than one pixel and then we can just stop recursing. This is actually quite a nice solution. However, then we guarantee that we will always draw a certain amount of a certain depth and never go beyond it and never stop above it. And I think it would be nice to somehow animate this Serpinsky triangle by drawing the first triangle first and then second after what? Second Serpinsky triangle and then yeah another second after that we add more depth for each iteration. And we I would like to have it um to see all of that visually.
So I think maybe it would make sense to to add a depth parameter in current depth.
No, we don't pass the current depth. We pass the maximum depth or the maximum depth should be a global variable. No, no, no, no, no. Max depth is this.
And then we also pass the current depth.
So with these recursive functions, I feel like we always have to be careful what to pass. It's really hard to have a neat a neatly defined recursive function in my opinion.
And then we do a terminating. we create a terminating condition. So when do we stop the recursion?
We stop if the current depth is equal to the max depth.
Then we return.
And if you are paranoid, you can also do the current depth is bigger than or equal to the max depth just in case there's a lightning strike um into your computer and it flips just some register in your CPU and then the current depth skips by one value and you therefore prefer should prefer to check for bigger than or equal to rather than equal to in case the counter somehow skips over max depth. This is incredibly unlikely and in case lightning strikes into your computer, you have a whole probably a different problem at that moment other than your Sinsky triangle not generating. But I think this is more defensive and therefore it's fine.
So here this is we check is the current is the maximum depth reached. If yes then we terminate the recursion and then what's what else are we going to do? Of course we're now recursing and we are never drawing anything. So now we can draw a triangle from from a top coordinate given a side length.
And then we need to determine the new coordinates for the other triangles here.
Hm.
We could return the the new coordinates from the dry draw triangle function, but I I prefer to draw a triangle and have this method not serve multiple uses. So maybe it's better to define a new function get bottom coordinates.
This returns this returns. What does it return?
[sighs] I know this is stupid. This is all stupid. Maybe it's simpler to just do it in inside this Yapinsky triangle method here. So, we draw a triangle and then we determine the bottom left and the bottom right. Let's just copy this code and then it's fine.
And then we draw a new triangle. We we draw new triangles here.
We draw three new triangles. The first one or maybe we should do this manually rather than in a loop because the first one takes this input the same top coordinate. The side length is the the original side length divided by two. So let's replace that here.
Then we draw another triangle here. The bottom left one.
[sighs] Oh, and actually we don't want to draw.
Wait, this is not going to work. I'll explain in a second. Bottom left, bottom right.
So this is what I thought how it's going to work. But here we draw a big triangle.
No.
Right.
We draw a big triangle in white and then we try to draw other white triangles over it.
That won't work. And also the coordinates are wrong because this right here returns the bottom left and bottom right of the big triangle.
No, no, no. This can't be correct.
So instead, we might have to do something different.
We draw a triangle here. That's fine.
This is now the first depth.
Then we increase the depth. So we do current depth ++.
[sighs] Let's delete all of that. I don't think we need it here. We draw a single triangle. We increase the depth. We [snorts] increment the the depth. We pass on the max depth. Here we pass the current depth which we have just incremented.
And then oh I also need to fix up the side lengths here.
But now what do we do here?
I draw a single triangle.
Current depth is incremented and then the other triangles I draw at at new positions.
H the first one I draw at top side length divided by two. That's fine. The this one I draw at the bottom left. So I need to pass in a new coordinate. Wait, let's just copy this stack of computation down here and fix it up.
So we draw a big triangle and then in a recursor method we pass as new tops this top right here and this top right here and of course the topmost top and we get this bottom left top. How do we get it? The bottom left top is the top x minus side length divided by two.
This is not even true anymore. Now it's side length divided by four because this is just a quarter of the way. Same thing here for the bottom right one. And then y is also modified by side length divided by 2 * sin f 60°.
And now I think it might make sense.
And then before we draw a triangle, we should always be sure to clear the background because now here we draw a big white triangle.
Well, let's not talk too much and I just show you because now if we are trying to draw something, I'm not even calling it here. Let's just call the function here.
If we try to draw something, I think we're just going to draw a white a big white triangle. And but then we don't see the nuance of the smaller ones. Um because we just draw smaller white triangles on top of the big triangle.
Today my mouth is incredibly dry by the way because yesterday I ate two donors.
I don't know if you know donor.
Actually, it's feels super stupid to call it donor. It's how Americans pronounce it. It's actually called Duna.
Um, it's this Turkish, German sandwich right here.
Super delicious. And yesterday was the first time when I actually ate two of them in my entire life. And so now, now I'm still ridiculously full. And the night felt like I I somehow my body is completely dried out because it's absorbing so much water and I I drink like a camel the entire day already.
Okay, let let's not distract from the Serpinsky triangles.
[sighs and gasps] All right. So, we need to call the triangle function and we pass the top, the big side length, the maximum depth and the current depth.
So, the top is already provided here. Then the side length, we have a starting side length defined.
Let's reuse that or not reuse it. Let's use it here.
That's it's intended to be used here.
Then the maximum depth the maximum depth is let's say three for a start.
And then we pass the current depth and the current depth is zero.
And if we execute it, it compiled.
That's already surprising because usually when I type code for a couple of minutes, then it never compiles. It's it's it's incredibly unlikely to compile something and it just works right away. Let's try.
[laughter] And of course, now we have this triangle.
And it seems like we don't well, at least I don't hear any CPU spiking. I don't see any I don't hear any fans starting to blow, which is a good indicator that my recursion is not going crazy. It's not stuck in an infinite loop.
And what's happening now is we are drawing one big triangle that is wide.
And then we're drawing the smaller triangles on top of that big triangle. So what we need to fix is before drawing a tri triangle, we need to clear the background.
Oh wait, we draw a triangle here.
Where? Wait, where do we clear the background?
I think before each recursion we should clear the background maybe here because this this clear background thing it essentially discards off the triangle up here but we don't need it anymore.
Let's say we draw this black triangle here. In our case it's white. And then we draw three smaller triangles. And as you can see here the big black rectangle is discarded off.
If we think of this as three smaller black rectangles, we can of course think of this as one big rectangle with a small inverted white rectangle inside of it.
So this would um flip the drawing logic a bit. But I think we draw a big rectangle.
Discard it. Use its coordinates or from the coordinates derive the smaller rect rectangles. Triangles of course. Oh, sorry. I'm probably saying rectangle all the time. We draw the smaller triangles. If the maximum depth is not yet reached, we discard those and then we draw those.
No, but this won't work.
It somehow feels like it's not really Let's just compile and see. I think it won't work because it will wipe away all the triangles except the three last ones in the end.
So the the very last triangles, let's say uh the bottom right triangles in this corner right here, they will be drawn and before that and the entire history will be wiped away. That's how I think about about it right now.
So, I forgot a semicolon somewhere in line 50.
50.
Oops.
And drawing. Ah, there three zero. That those are the depths.
Probably I mistyped something and this undid the changes. So, and now we have one big rectangle and with triangle again. But why?
We draw a triangle. We increment the depth. We get the new coordinates.
We clear the background.
Well, the clearing of the background is definitely wrong.
And then we draw the other triangles. Did I I probably forgot to compile.
No.
Then I don't know why we still have one big uh white triangle.
We draw one big white triangle here.
We increment the depth and then we clear it. Then we draw smaller white triangles using a smaller side length and new coordinates. What if I remove those?
Then probably we will see just a black screen. No.
Why? Because the I mean the last thing we do is clear the background here.
Sinsky triangle.
Wait, if I remove the Sinsky triangle function, then it will be black. Okay, that's fine. That's expected.
[sighs] We draw a triangle.
We check if the current depth is bigger than or equal the max depth. Then we return. That's fine.
Then we draw a triangle here, a big one.
Then we increment the depth.
We compute y which is top y + side length divided by 2 mult* sin f. That's also correct.
[sighs] We clear the background and we draw smaller triangles.
Or actually what we could do would be to not even draw a triangle and only draw all the small triangles when we have reached the maximum depth because all the intermittent drawing is technically not necessary.
>> [sighs] [snorts] >> But really, I'm I'm surprised that we see a big white rectangle. Why? We are clearing the background here. Or am I doing something wrong somewhere? I'm drawing a triangle here. That's fine.
That's the origin of the big white rectang triangle.
[snorts] Then bottom left is this.
bottom right. Is that clear background clear background?
Let's check that function as well. Clear background with a color. That's correct.
Clear background top.
Ah, wait. We are of course with 60 frames per second restarting this drawing every time. So maybe that's not something we want. But still that shouldn't make a difference because we should get as a result the small rectangles the small triangles every time.
So now we're drawing with one frames per second which is completely fine.
Pinsky triangle. What are the input arguments here? The side length, max depth, current depth.
Then bottom left, bottom right, clear background.
Top, bottom left, bottom right.
What if I just remove those two function calls? Let's see what happens. Then we still get one big triangle. Why?
And if I redo them and remove the top cornered one, you still get one big triangle.
Draw triangle top side length. It we pass the top but the top is of course the bottom left in the recursed function call.
[snorts] Wait, I didn't even link math. Is that an issue?
And what if I I think I need to do also lm to link math?
Yeah, but it's it should be linked by ray as well. So, it's not critical.
Let's enable also all warnings.
But there's no warning. What if I enable extra warning? Those are compiler warnings for those who are not aware which usually point you to very interesting coding issues that you introduced.
So the side length H bottom left bottom right. Okay, I'm going to step into a debugger now. So let's compile this with the with the debugger flag and do GDB triangle and see where we can put a break point. Let's say we put a break point in line 30 right here where we draw the first Sinsky triangle.
And then we run.
We haven't drawn anything yet. Let's refresh this view because usually the whenever the application prints something then the perspective will break. And now let's step through the code or let's print first the top that is fine. Let's print the bottom left that we have computed also looks fine to me. then print bottom right.
Okay, it's the same thing except with a different x coordinate. That's also fine.
So somehow feel like the clear background is not doing anything.
And then we step into this function draw another triangle.
Ah we are stepping of course and then drawing smaller and smaller triangles again. And before no after. Wait.
Well we are definitely clearing the background a lot of times.
What if we step into this? We return current depth current depth is three. So we should return soon.
I don't get it. Somehow the code seems correct except of course for the clearing of the background. This is not correct.
But how can we completely circumvent our step around the issue that we're currently facing? Well, probably we should only draw triangles if we have reached the current depth. [gasps] Then we draw a triangle.
Draw a triangle.
Given the top coordinate, the side length and the and nothing else because all intermediate drawing is going to be discarded anyways. Then we return which is fine.
Let's check the warnings here. This if clause does not guard. All right, because I need to put all of this in paren in curly braces now.
So we only draw a we only draw triangles when we have the when we have reached the final depth that we wanted to visualize.
Else we increment the depth. We compute the new coordinates and we step into these functions here.
This way we never draw one big white triangle.
Oops. Now I [snorts] messed up my terminal.
We never draw one big wide triangle.
And we should be drawing only the small ones. Let's compile this again. Triangle triangle dot C link rail lip link math enable all warnings w all w extra and now it compiles without any warnings if we execute it. Oh now it's working.
Okay. So I have no idea why it didn't work before but some but of course we shouldn't do any unnecessary drawing.
And this is now a working Sinsky triangle. Perfect. And now I would say let's animate this because I would say we define a a global maximum depth. Max depth for example. Let's make this I don't know 50 or something or maybe 30. We won't be seeing any more detail than that anyways. And then while this rail loop is iterating we starting from the top draw your pinsky triangles but each iteration we increase the maximum depth for the given loop. So the depth starts at zero or maybe one.
And then we check is the depth bigger than or equal to the max depth.
If yes, then no. If if it's not bigger than if it's smaller than or equal to the max depth.
Let's do that. Smaller than or equal to the max depth. Then we increment it but only if it's smaller than the max depth. Once it has reached the max depth, then we don't do anything anymore. And then we draw a Serpinsky triangle up to this depth that we want to draw.
And then we should be seeing already let's have a look a sinsky triangle that's getting finer and finer over time as you can see. Oh, and this is then okay this is these are aliasing effects in that sense because they are now drawing uh [sighs and gasps] yeah of course at some point you can't draw infinitely fine graphics. Therefore small changes in pixels they will yield these results where you overwrite pixels that you actually didn't want to overwrite because it's just getting too fine. So the maximum depth of 30 is way too high.
Maybe let's make it five or something like that.
And the depth should start let's say the current depth at the start is one. And then we should start with a single big triangle. Let's see. No, this is not even a single big triangle.
How can I start with a single big triangle?
If this if the current depth is bigger than or equal to the max depth.
So we start with one. We enter current depth is one.
Then we should draw a triangle and return.
I don't know. We can we can increase the max depth a bit more than five. Let's make it maybe seven. And I think after that it starts degrading into something that is not recognizable as a clear sapinsky triangle anymore.
And now I would like to be able to restart the simulation. So on the press of a button for example when the space key is pressed here is key pressed then yeah this checks if a key has been pressed once. So we can do if key if is key pressed and then we do key underscore space probably key where is it well I can also just type in complete like this if the space space key is pressed then we reset the simulation we don't need the parenthesis let's keep it succinct So we set we can just reset by setting the depth to one again. It's as simple as that. And let's see how this looks like in practice.
No, but I also I want to be able to start from a big triangle.
I don't know why it starts with a nested triangle already.
So if the current depth is bigger than or equal to the max depth which is the case if we pass in depth equals one and max depth is also one here depth is equal to one.
[clears throat and cough] Ah, but it's incremented here already.
[clears throat] I think we should increment only after drawing or we should start with depth equals zero here because then depth is smaller than or equal to max depth here. Then we increment. So the starting depth is one.
Yeah. Okay, that's perfect.
So now we have a Sinsky triangle animation. It looks like it's working.
It's getting finer and finer. Now it has now it has stopped because it has reached the maximum maximum depth. And now if I hit the space key, wait, it does not repeat.
I I wanted it to repeat when the space key is pressed. Why doesn't it restart the drawing sequence?
Probably because I didn't compile. So, let's try again. So, this looks good.
Let's hit the space key.
No.
Oh, why is it not working?
Is key pressed then depth is equal to one again or actually it should be equal to zero again.
And then we begin drawing. Do something here.
Is key pressed?
And this should return if the key has been pressed once in the current iteration or in the past iteration.
Maybe is key get key pressed.
M is key down.
Let's look at the key keyboard keys.
There should be the space key here. Yes, there it is. So, what is not valid here?
This is completely valid code. Somehow I feel like somehow I feel like uh [sighs and gasps] Rayop wants to wants to play with me.
I execute I press space. Why doesn't it work?
Maybe it would make sense to print when a key press has been detected. That would be nice.
What's the issue here?
Check key press is key pressed.
So here's some example about key presses in RIP. I don't know how it's supposed to be. Ah, it's supposed to uh check arrow keys and there should be sample code down here. Is key down. It's using is key down but is key press should work just the same. But I remember having some uncertainty about this in a past episode. What if I replace it with is key down?
Is key down key space. We compile and execute and then I press the space key and it doesn't do anything.
Ah, what? But why is key pressed? Should work just fine.
Is key pressed. [snorts] Do I maybe need to am I maybe missing some Rail functionality because I'm not linking everything? because [snorts] I know there's a a there's a on the Railip GitHub page, there is a sample build command that includes a couple more libraries than I'm using.
I'm currently including only RAI lip and math.h and the sample compilation command here.
Where is it? Should be here somewhere.
Here the simplest possible build command is including many more.
So maybe since this is including X11 which is the windowing system that I'm using maybe this is what supports the key presses and it's required for the computation. So I'm going to include those.
Let me just remove my two custom linking statements. So now it should be compiling with everything. Does it make a difference? I press space now. It does not make Wait, it just reacted.
Wait.
So, sometimes it catches the space key, but sometimes it doesn't.
Wait, why is that? That's odd.
So, maybe it was not due to the linking.
Maybe it's due to the timing when the is key pressed is being checked.
Rail lip is key pressed does not work.
Is key pressed for some keys not working? Hm.
It might not be that well done. Blah blah blah blah.
when a mouse click or a key press lasts less than the frame time which is which is for sure in our case the case because in our case the frame time is a single frame per second that could be an issue actually then it will not register.
Okay, it is possible to lose inputs if your program is single threaded and you pull using is key down or is mouse button down every frame.
When a mouse click or a key press lasts less than the frame time, then it will not register. There are also is key pressed and is mouse button pressed. It theoretically should solve this but they have other problems.
So if I replace ah this is is key pressed it should theoretically solve this but they have other problems is key down the solution is to put input polling into a separate thread. I did it and it works fine. Oh this is crazy. Now we're going to solve it differently.
We're going to do is keep pressed. I'm going to keep relying on this. I'm going to increase the the frame the frame rate here.
And now it will render the triangles very fast as you can see here. So now we don't have this nice animation. At least it's not as visual because it's way too fast for the user to grasp. And instead, how about we check [snorts] how can I now make something happen every sec every second?
Maybe if I introduce a timing variable here. So um let's say interval or animation interval is equal to uh let's make it ah let's set it to zero here and for every frame we add on top of the animation interval.
So we increment it by the frame time get frame time. So this is the difference in time between the individual frames which is now a 60th of a second approximately but not exactly.
And then if the animation interval has surpassed 1 second let's make it 1.0 to make sure that this is definitely a float.
Then we then we reset the animation interval.
Then we set it to zero again. And also we increment the depth.
We do the incrementing handling inside here.
It's probably not the nicest solution, but it definitely is going to work. So this is um [clears throat] add adding depth. This adds depth every full second considering the frames per second. Okay.
And now if we do compile and execute, we do get the animation. And if I press the space key, it resets. Okay, this did did this worked nicely. If I press it again, it resets. Perfect.
Okay, that's awesome, guys. I think it's working. Yeah, it's working.
[sighs and gasps] Ah, it's always so nice to have these awesome little projects working and s finally see them come to life. Guys, this was the Sir Pinsky triangle, a recursive structure that is really nice to look at. I love recur I love recursion. Um, it's so nice how so much complexity grows out of out of recursion.
And this is just a couple of simple rules where you have to place small triangles at new coordinates depending on the previous triangles coordinates and you will recurse over that and then you get this awesome structure.
This episode was really fun. I hope you learned something. If you want to do more such projects or you want to improve your C understanding then have a look at the nononsense C programming course. It's linked in the description below this video. And in the course you will you will learn C from scratch the way I do it. It does not rely on boring theory. It's only practical lessons directly in the terminal as you see in all the episodes on this YouTube channel. So definitely check it out. Definitely check it out.
Link is in the description below the video and I hope you will all come back to the next video. All right, let's have a look at the animation one last time and enjoy our small little engineering result here.
Awesome.
Nice.
See you next time. Bye-bye.
Vidéos Similaires
Agentforce NOW AMA: Build with React and Salesforce Multi-Framework
SalesforceDevs
490 views•2026-05-28
How agent o11y differs from traditional o11y — Phil Hetzel, Braintrust
aiDotEngineer
450 views•2026-05-28
Re: 🗣️📍theprophedu📍2026 GST 103 CLASS (E-EXAM REVISION)
theprophedu
636 views•2026-06-04
WEB TECHNOLOGIES UNIT-2 | Degree 4th sem BCOM Computers web technologies unit-2 full explanation💯✅
LearnwithSahera
1K views•2026-05-29
More tests are always better? How to use AI to identify tests that bring little value
Alliance4Qualification
335 views•2026-05-29
Search Algorithms Explained in 60 Seconds! 🤖💨
samarthtuliofficial
218 views•2026-06-01
People of Game of Thrones using JavaScript DOM
AltCampus
296 views•2026-05-30
Introduction to Problem Solving Part - 1 | Lecture 1 | Intermediate DSA
ascensionix
107 views•2026-05-29











