A masterful demonstration of how low-level programming can elegantly visualize the intersection of kinetic energy and transcendental constants. It turns a complex mathematical proof into a tangible, high-performance reality through clean and efficient C code.
Deep Dive
Prerequisite Knowledge
- No data available.
Where to go next
- No data available.
Deep Dive
Computing Pi by Colliding Blocks in CAdded:
Hi everybody. In today's video, we're going to code ourselves the colliding blocks simulation that was published and made famous by three blue, one brown that computes the digits of pi purely based on the number of collisions between two blocks. So have a look. This is the running simulation that we're going to create in this video. And as you can see, there's a counter for the collisions between both blocks. And the number of collisions matches exactly the first couple of numbers of pi. And if we modify the weights of the of the masses, then we get different numbers of digits of pi as a result. So that's what we're doing. Let's jump right in and start.
The first thing that we need to do is create a source file. Let's call it pi collider.
C. And in that source file, let's include standard io, which we surely need. Standard lip, I'm not too sure.
Let's just include it because in the past I found at some point we're going to need it. And a main method. The main method I think requires no arguments for a start. And how do we approach this project systematically? Because in the past I got some comments from you guys um complaining about me not approaching the problems in a structured enough way.
I need to break down the problem enough and need to approach it more systematically. So the first thing that that I want to do is okay I want to display something. Let's create a window. How do we create a window?
Let's check out ray.h.
There's a function we're going to use rail for the graphics as a graphics library. So let's use init window to initialize the window. It takes as input a width, height and a title. And the width and height I like to define as macros. And the title can be pi collider.
And let's close that. define the width and height of the window and soon we're going to see something on the screen hopefully. So that's the first thing that I want to achieve. The first thing that a simulation needs is a window in which the simulation is visualized.
Then we are going to close the window.
And let's maybe also add the while window should not close loop here.
And set a target frame rate to 60 frames per second.
And inside this loop, let's begin drawing. and end drawing. We're not going to do anything. This is the rail lip skeleton that every Rail project basically uses just like that.
If we compile this, this won't work because we need to include ray lip.h first which is the library. So let's define a compilation command here. GCC- O. Output file is called pi collider. input file is called pi collider.c and we need to link ray lip and then that should work. Okay, looks good.
What if we execute it? Does it work? And yes, we do have a small window. Okay, what's the next thing that we want to do? The next thing is to define to visualize a single rectangle. Okay, let's do that. After we begin drawing, we clear the background.
This is erasing the data that is currently in the window. This is required because we are drawing we're drawing we're effectively drawing many pictures. So each iteration we need to wipe out what we have drawn previously and then we can draw a rectangle here and there are multiple rectangle drawing functions. I'm going to use the standard one draw rectangle which takes as input a position X Y width height and a color.
Oh, I just accidentally typed something here which I shouldn't have done of course. Okay, so X and Y can be 50/50.
Width and height can be 200 by 200 and then the color is white.
And let's try and see if this works.
This doesn't work because in clear background a color is expected.
That's the color that the entire background is filled with.
So let's try to compile this and see if we execute it. Does it work? And yes, it works. And we have a rectangle. And this also already looks like the big rectangle, the big block that we're going to use for the simulation.
Now the next step would be to draw a smaller rectangle. But instead of drawing the rectangles individually, we should instead create the blocks in memory because we're going to collide blocks. So let's create a block in memory that is block one. And what properties does a block have? Let's create a structure.
type defaf strruct.
What properties does a single block have? A block has a position x and y.
A block has a velocity.
In our case, only the x velocity is required because we're only moving along the x-axis. Oh, and by the way, also according to the same logic, we don't need a y-coordinate because of course it's a it's completely a one-dimensional problem. So, let's create two variables only, x and vx. So, position x and velocity x.
Those are the properties of the block.
And then the block also has a mass which we can abbreviate with a simple m.
Okay, this sounds a lot like this looks a lot like physics. So we have a position, velocity, and mass. Is anything else relevant? I don't think so.
And then we are going to code then we're going to call this structure a block.
Okay. And I think this might be good.
And then inside or outside, maybe outside the loop, let's initialize the block. So block, let's call it the big and the small block. The the big block has a position.
Well, what's the position? Is it just an arbitrary value here? I don't know if any meaningful values should be set. If let's just put the position to 400 or let's put it to height no width multiplied by 0.75. So it's 70 75% along the way of the x-axis.
then vx is minus1 because the big block needs to move from the right side of the screen to the left side of the screen.
So it's negative x direction and then the big block has a mass. The mass of the big block is some some how how's that called? potency of 10 or exponent of n of 10.
It's 10 to some power.
I will just put 100 or 1,000 for a first test. And then we're going to see and maybe even make this variable. Let's see. I don't know yet.
Then the small block is another block here. Small.
And that's the inner colliding block.
Let's create it like this.
And it sits only 25% along the way of the x-axis.
Has no velocity. So velocity zero. And what's its mass? Its mass is just a single kilogram.
Okay.
And now we have defined two blocks. They live entirely in memory right now. They are not yet visualized. So if we compile this, it it works. It still visualizes the hard-coded rectangle that we draw down here. This is of course not what we want. What we now want to do is in this drawing loop, we want to do two things.
We want to inside the begin drawing and end drawing function calls within those two function calls. We want to update the visualization or the s yeah the visualization and draw rectangles and outside of the drawing loop. We don't need to do that inside otherwise we we interfere with the frame rate calculation and things like that which we don't want to do inside that loop or out no outside the begin and end drawing clause. We want to compute the physics. So essentially we create our own small physics engine and we do that outside of the visualization visualization of course and once we have computed all the physics then we are going to visualize the first step I would say is visualize the two blocks.
This is what we can do right now. We already have two blocks in memory here.
Let's visualize them.
And for visualizing the blocks, I would say we can just create a function draw draw rectangles or draw blocks. Draw blocks.
And it takes as input. What does it take as input? We we know that we only ever have two blocks, right? And this is the entire point of the entire simulation.
This the entire simulation only ever has two blocks.
and we want to manipulate them and draw them. We know there's no more blocks than that ever. So, we might as well also store those blocks in the global scope because we know that potentially multiple functions are going to access and manipulate them.
All right. So I would say that draw blocks function takes no arguments and instead just references these global variables directly.
And how can we draw the blocks? We just draw rectangle as before.
Draw rectangle.
And now instead of taking instead of just using some arbitrary x value we of course use the big blocks or the small blocks x value first and the ycoordinate. What is the value of the ycoordinate?
The value of the y-coordinate should be some let's just first assume it's just some hard-coded value such as height divided by two. There's some quirk that we have to adapt to, but you'll see in a second.
And the color is white. Before we set the color, we must of course also set the size of the rectangle. And for that I'd say we should define the size here.
Small size is let's say 50 small size and it's not a rectangle. Let's assume that our blocks are cubes.
So we know that we don't have to define multiple sizes of each side. We can just define a single size and that's the size of all sides. And then let's define a big size for the big cube and that's 100. Or I don't even know if it's reasonable to make it that much bigger.
Maybe also 70 would look good.
Let's keep it 100.
Then the small block has small size as its size of course and then the big no that the color is white of course small size small size white and then let's do the same thing for big dox big size big size and white. Okay. And we call the draw blocks function down here. So now I would expect that we have a window that visualizes two rectangles. Now let's see if it works. It doesn't work because there's a compilation issue.
Let's see. I forgot a semicolon. Oh no, I put a semicolon after the define clause. That's of course wrong. There should be no semicolon there. And now it compiles. So let's just show you. Compiles. And if we execute it, we have two rectangles. Now they are flipped upside down. Why is that? As you can see, the small one is shifted upward relative to the big one.
That's because in computer graphics, the ycoordinate points downward. So, if we have a fixed y-coordinate for both blocks, then that y-coordinate will align the top sides of the cubes rather than the bottom sides, as you might expect from regular algebra from school.
So, we need to adapt the height. No, we don't need to adapt the height, do we need? Oh, yes, we do need to adapt the height.
So the small rectangle is the small block is shifted upward compared to the big one. So what we could do is we could either move the big one up a bit or move the small one down a bit. I would say let's move the small one down a bit. How by how much do we move it down? We move it down by some value that is proportional to big dox minus small dox I think. And that might align no that might align the rectangles. No. Damn it. Wait.
Let me draw out something. Drawing here.
So this is the small rectangle. This is the bigger one. This right here, let's use green.
This right here is the common y value.
Right now, now we should shift downward the smaller block such that the common ycoordinate is this one. It's y dash.
How much should the small rectangle be moved downward to have common y value?
I know I solved this problem already in one of the last episodes, but I already forgot again. So, we can define a base y value that is height divided by two. That's quite reasonable I would say.
And then we offset by wait let me look at the drawing again.
So basically instead of setting the top ycoordinate here of the small rectangle I want to set this bottom left ycoordinate here.
That's the big question for me right now. How do I do that? I just shift. I just add No, I just remove from the ycoordinate the size of the small rectangle.
The small size. Oh, I What did I do before? I subtracted the x- coordinates.
That's of course wrong.
And now this is what's happening. And now the bottom line here is the y-coordinate. And now of course the same thing has to be done for the big one. For the big rectangle, let's have a look here.
And for the big rectangle, we have to do the same thing, of course, by subtracting the big size.
And let's see how that looks like. And now they are aligned. Okay, perfect.
Now I would say they should move down a bit. So let's put them at a base level of at a base Y coordinate.
base Y that we're going to define. Base Y is this.
I don't know the value yet, but I'm going to define it now.
So, let's define a base Y here. And that base Y is height multiplied by 0.75.
Okay.
And this looks okayish.
Maybe let's move it a bit up again. 65.
Just so they are centered a bit more.
And I think this looks fairly good. This looks quite symmetric. This looks This looks quite balanced. It's okay. And now we are drawing the the blocks that we have in memory. Perfect. And now the next step would be to compute physics.
Before we compute physics, we should draw something else. I would say we should also draw blocks. We should also draw walls because the blocks are moving along a floor level. Okay.
But they are also colliding with a wall.
And this is critical for this simulation because otherwise the simulation does not make sense. It needs to collide with some wall.
So let's also draw a a wall. We could also assume that the wall is just the left hand the left hand side of the window.
Okay. But I like to make it explicit like in the original where I remember there was also a wall. So let's draw a wall here. It's fairly simple. It can be just a line probably or a big rectangle.
Let's see. Is there a draw line function? I don't know actually. There's a draw line and this draws a line from position XY to a end position XY.
Um the line has no thickness. So maybe it's worth to use this function. Draw a line X which takes as input a start position, end position, and a thickness.
H or we can also just draw a rectangle.
I think let's keep it simple and reuse our knowledge. Let's not define vectors here and keep it simple.
So the wall is located at and I would say at at an xcoordinate.
Let's define a wall xcoordinate and that's wall x.
And of course, I messed up the define again. I need to give it a value of 10 here. That's the value that I just made up.
The y value will be zero because the wall starts at the top most corner of the screen and goes down to I would say to base Y.
So the width of the wall is let's make it one pixel for now and see how the result is. And the height is base Y because I want the wall to go from the top of the window down to the floor level.
And of course, this doesn't work because I'm missing the color. The color can be white as well.
Okay. And now we don't have a wall. Why?
Where's the wall going? Draw rectangle.
Of course, I need to call the function which I forgot. So, we clear the the background and we draw the wall.
And let's try again. And there's our wall right here.
It does not look so balanced as compared to the rest of the image. So, let's move it into the simulation a bit. the wall X will be let's make it 50 and see how that looks like.
Okay, that's already a bit better. And I would say maybe let's put also a floor.
Draw a wall can also draw the floor because they don't neither of them moves. So that's maybe a function to draw all the static objects to draw all the limitations on in the simulation.
Draw wall and floor and the floor is a rectangle starting at also at wall x going to well what's the what's the y-coordinate of the wall? It's base y.
the the what's that?
The width of the wall is well it should go to the end of the screen. So we could just say width then it will surpass the screen but not show completely but we could also align it perfectly with the right edge of the screen. That would be width minus wall x and the y height should be one. And then I think this might work. Let's just update the function call here. Draw wall and floor.
And now we can compile and execute.
Okay, this looks good.
That's nice.
And I think there's a bit too much breathing room at the bottom of the simulation, but I also don't want to have a simulation that where the where the blocks just move along the bottom edge of the window. I think it's okay.
I think it's okay, guys.
All right. So, this looks already a lot like the actual simulation. Now we have to compute the actual physics.
Let me have a sip of coffee.
Okay, maybe before we compute the the physics, it would be reasonable to test if the blocks can even move currently because we have defined a block here. The big block has a speed, but currently it's not moving. So before we do any elaborate physics, how about we just update the pure movement?
Um yeah, how how about we just compute the effect of the velocity of the blocks?
For that let's just create a function void update blocks or step blocks.
This is a simulation step just like this. And the simulation step needs to take us input a delta time. So how much time has passed since the last simulation step? And there's a delta function I think in Ray lip somewhere.
Frame frame time get frame time. That's the time in seconds for the last frame drawn.
Okay. And it is a floating point number.
Okay.
Float is not too precise, but it's good enough for a delta time. delta time or maybe let's call it just dt as in physics. When you simulate stuff in physics and you're dealing with integrations and different differentiations and all these things then it's always called dt.
Okay. So what do we need to do?
We update the big block first and then we're going to update update the small block.
When we update the big block, what do we do?
We check big do vx the velocity of the big block and we update the big blocks x coordinate accordingly. big dox equals velocity multiplied by time. This is just pure physics. So position equals velocity multiplied by time.
The more time passes, the bigger the change in position is. The bigger the velocity, the bigger the change in position. So this should be good.
Yeah.
And then we do the same thing for the small block except we just do small.x rather than big.x.
Okay.
I'm not sure if this works. Ah, right.
Because I have to compute physics and put the function call here.
Okay.
Let's just put here visualization block, right? This is the visualization block and this is the physics engine block. Although we're not we're not using a real physics engine. We're just doing the physics ourselves.
We're coding our own physics engine. All right. And now there's an error in step blocks of course because it's not receiving a delta time value. So let's put it here dt.
And we receive that value from get frame time up here.
Float dt equals get frame time.
And this compiles.
Perfect. Huh? Does it execute it? Oh my god, this looks horrible. So, something has gone wrong.
We get the frame time.
Well, what could potentially have gone wrong if we remove step blocks? Let's just see what happens. Then the blocks reside in the correct positions. Okay, so the issue must be in the step function.
And I do I know what happened right? Of course the x position the the x coordinate is set is set to the velocity multiplied by delta time. But of course this is the wrong equation. If we have a delta time we have to increment by velocity multiplied by delta time.
Yeah, that's the issue because otherwise if we don't increment, how are we going to update the block's position? The data time is roughly constant. Vx is constant and then we set X to equal that. No, that's wrong because then X will never change. X has to change each frame and therefore we have to increment.
And now is anything happening?
Well, it looks like the blocks aren't even moving. Oh, yes, the big block is moving as you Yeah, you have to be probably very carefully looking, but it's moving by individual pixels at a very, very slow speed.
Okay, probably I think in the original simulation minus one was used for a speed 1 m per second. Um, and If we want to have nice count a nice counter a nice collision counter in the end that resembles pi then probably the speed has to be fixed at minus1 and just for the visualization the just for the visualization we have to zoom in and represent 1 m/s at a different pixel movement rate if you understand what I Mean so currently in our case the unit of distance is one pixel and one pixel corresponds to 1 meter.
This is not ideal.
Maybe we we the the programmatic fix would probably be just to multiply the velocity with 10 and then check if we can still if the simulation still works. Ideally, we should have some differentiation between the screen coordinates and the actual world coordinates.
But I think just increasing the speed by 10 is reasonable. I mean, now the block is moving. It's fine. It's quite slow because it's measured in pixels per second.
What if we 100x the original speed?
Yeah, come on. This is okay. We can probably get a result from that. And as you can see, the block moves off the screen because there's no physics implemented at all.
So now, let's just continue with this. Let's just assume a speed of 100 pixels per second is fine.
and let's implement the the real simulation logic. So the simplest thing to do is if the simplest first thing to do is to check if the block hits a wall. If big x is smaller than or equal to wall x then we reverse big vx is equal to negative big vx. So that's an elastic collision with the wall which is which has an infinite mass and therefore the block is simply reflected.
big do.x smaller than or equal to wall.x and then we reverse the speed. Okay, I think it might work.
Let's just put the same code here for the small block.
And let's see. I think once we hit the wall now, the block should reflect.
Yeah. Okay, perfect.
That's great.
Now let's also just for fun just for this just for checking that everything works.
Let's also give the small cube a speed.
Oh yeah, and it seems to work. Was just way too slow.
Okay, this looks good. The the blocks of course don't collide yet, but they collide with the wall. They don't collide with each other, but they collide with the wall. And it looks like it's working fine. So, let's put here.
This is the wall collision.
And before we collide with the wall, let's check also with block collision.
Let's check for the block collision. And how do we do that? We check first big.x X.
Well, how do we check the block collision? The small block will only ever collide with the left hand side of the big block, right? Because the the big block is always to the right of the small block. So we need need to check is the right side of the small block is it beyond the left side of the big block.
Big dox is the left side of the big block and we check is it smaller than or equal to the small block.
What's the right side of the small block? It's small. X plus small size if yes.
Oh, and now comes the fun part. Now we have to implement actual physics. How do we collide two masses elastically?
There's a formula for that and we can look it up in a second, but I want to just log here. Print f collided.
Um, or maybe small block collided with big block.
Oh, and actually if the small block collides with the big block, then of course the big block also collides with the small block. So we don't have to check any other way, I think.
So let's check if this log is appearing in the terminal.
Let's see. And yeah. Okay, there was a print out as you just saw. Let's just try again just in case you missed it.
And collide. Perfect.
Now, as you can see, there are a ton of collision events logged. That is because there are multiple collisions for each frame because the frames o because the blocks overlap and they don't they are not reflected.
It might be fixed if we apply the elastic collision. Else if it does not work then we might have to also move the blocks apart again a bit. Let's see.
Okay, another sip of coffee and then we're going to continue with the physics.
Okay, so ah damn it is my browser. Okay, elastic collision formula for mula physics. So here it is. An elastic collision conserves both me conserves both total momentum and total kinetic energy in one dimension. This is exactly our case. Final velocities v_sub_1 v2 depend on initial velocities and masses m_sub_1 m2.
Okay. And yeah. Okay.
What's the difference between those formulas here? Ah, that's the conservation of momentum. And what's the difference between those up here and those formulas down here? I think they are the same.
Yes, they are.
Yeah.
Okay, then let's use those ones.
Or which ones do you see better? These ones are probably more legible for you.
Okay. So let's say object one is in our case the small block.
So the velocity of the small block.
Let's just put parentheses around here. How does the velocity of the small block update? The velocity of the small block is small dot vx and it's equal to the difference between the masses of both blocks.
Okay, the difference between the masses of both blocks and the right and one is always the small block.
So we have to write small dot mass small dom minus a big m divided by m1 + m2 or actually everything has to be divided by m1 + m2 so we can divide the common result later. So first of all we have to multiply this with the previous velocity of the small log multiplied by small vx plus and then 2 multiplied by the mass of the big block big do multiplied by the speed of the big block big. Maybe let's space it out a bit.
Two big M multiplied by the speed of the big block.
Oh no, that's nicer.
Speed of the big block. Okay.
And then all of that. Let's divide all of that by m1 + m2.
and m1 + m2 is small m plus big dom theoretically because those values never change. m1 - m2 is constant and m1 + m2 is constant. This entire term here, this entire value in the parenthesis could be part of a macro. and this one as well.
So these are just constants that are always the same across the entire simulation. The only things that change are the velocities. Since I don't do micro optimizations and I want to check whether things first work in the first place, I don't do this right now. I think just coding this is good enough for now. But you could optimize this if you want. You don't have to compute the differences and sums and divide them every frame.
Okay. And now let's also update the velocity of the big block here. Big dovx is equal to And now the velocity of the big block depends on the previous velocity of the small block. But we don't have the previous velocity of the small block anymore because we just updated the small blocks velocity. This is something Oh, I just messed up my terminal. Sorry guys. This is something that I This is a mistake I did in the past if I remember correctly.
And that's why I pay attention to it now. So, let's just be sure to store the velocities that we need to compute with.
Let's call the first one previous small vx or something like that.
Previous small vx is small. VX I previous small vx initial velocity h or yeah initial velocity is nicer initial initial small vx X initial big VX is this.
And then let's put this here. Okay. And then let's update this equation. Even though this one does not have to necessarily be updated, but let's just make it more explicit. So here we put the initial small vx like this. And at the end here we replace the big velocity with the initial big velocity vx. And now we have updated the small vx which we can't reuse in this new equation here. And that's why we use these previously stored initial velocities here.
And the final velocity of object 2 is let's just call it that block here 2 * small multiplied by the previous velocity of the first object. So init small vx plus m_sub_1 minus no m2 - m1 plus m2 is the big m minus small multiplied by u2 multiplied by and that's the big velocities. the big blocks initial velocity in it big vx and then we divide all of that by small m plus big dom.
Okay, this looks okayish.
Let's try and see what happens when we execute this.
Oh.
Oh, holy crap. So, you see that it's overall working. It's working quite nicely, actually. I'm surprised it works that nicely. I mean, yeah, we implemented the physics correctly. It's that's awesome. The only thing I don't quite like is that you can see because so high velocities are achieved that sometimes the small rectangle is going beyond the borders of the wall and that is because the simulation steps are not fine enough and also because yeah the simulation steps are not fine enough and therefore the difference in position is so big that the small block and possibly also the big block at some point penetrates other objects.
So when we collide with another block, yeah, I'm not sure if we should really move apart the blocks and the I know from other simulations that when we collide with a wall, we should definitely move the object to a new position that is not penetrating the wall. Of course, we might we should maybe do that. But on the other hand, for precise physics, we should instead refine the simulation step size probably.
So maybe because this is running at 60 frames per second, maybe the proper fix is to actually yes, still keep the visualization running at 60 frames per second. But maybe the steps maybe per frame there should be multiple steps computed before visualization and this should refine the simulation. I'm not entirely um pro at simulations not at all but this is some concept that I have heard before I think from integration from integration algorithms numeric integration where you perform multiple integration steps before outputting a result.
So, I'm not I'm not not even sure if this works, but let me just try it just because I'm curious. Now, let's say for each frame I want to compute five simulation steps.
Okay.
Then we still use the frame time, but we divide it by five.
And maybe we should oops maybe we should divide it by explicitly a floating point 5.0 zero just to be sure that this is a real floatingoint computation. And I think if this works now we are visualizing a single frame but within a single frame we are performing five small simulation steps under the hood and this might refine the result.
Although this could yield a different problem that is floating point imprecisions because the finer things you want to compute here, the bigger the numeric errors are because you are facing imprecisions in numeric in floating point computations.
So there's always this trade-off. Let's see how this behaves.
Yeah, the penetrations are definitely less. They are fewer than before. So, I like how this looks like actually. This looks very good.
Okay, maybe that's good. Let's just have a one last visual check for realism here.
Okay.
looks okay to my eyes. So yes, I know we are dealing with numeric imprecisions here because we are using floatingoint numbers. We're using doubles here and we are but I don't really want to interfere with the physics by moving the blocks apart on collision and moving the blocks away from the wall. I don't even I don't know how much that interferes with the final computation result because I want to have an as natural computational result as possible.
But on the other hand, a wall is a mass with infinite size. So even in realistic physics, we should the block should be moved away from the wall.
Ah, it's actually quite I I don't know what the right solution is here. So maybe let's implement let's implement the the reflection.
I don't know if this is micro optimization or not. Let's just see. So if we have a velocity if we if we interfere with the wall then first put the xcoordinate to equal the wall's x coordinate and then invert the velocity and then let's do the same thing for the small dox which is also set to wall dox.
Okay, I think now the wall collisions, that was that was actually quite simple.
Moving the blocks apart might be a bit more tricky for the block collisions themselves. Let's just see how this looks like right now.
Okay, I mean this looks good. If if we now also move the blocks apart on each collision, I don't know how much that interferes with the physics.
Okay. And now we're approaching a working simulation. Actually, the only thing that is left is to count the collisions or wait because we are colliding blocks here. Is it possible for a block to com to collide twice?
I mean, if the small block moves too far into the big block, it's collided. Okay, the speed is updated.
Is it possible for the speed to be updated to a new value that is not big enough to move the small block outside of the big block and therefore the small block gets stuck in the big block? That could also happen. But on the other hand, the small block moves at a certain speed into the big block and then the big block does it ever give it does it ever give the small block a new speed that is not big enough to escape the big block after an overlap?
Oo, that's tricky actually.
So here we are currently dealing with pure physics. That's clear. Should we update the positions? How would we update the positions if small dox?
Well, we know the the the blocks collide.
If small dox plus small size is bigger than or equal to big dox then we should set the small block to a new position.
But also we would need to set the big block to a new position. I I don't know how to physically accurately accurately move the blocks apart. I can only think of the case that we should adapt the small block a bit.
Not so sure about that. Okay, let's try to update only the small block. If the small block interferes with the big block, then maybe let's adapt the position of the small block for now.
So we know the small block collides with the big block. Then we update block block positions and we update block velocities.
So how do we update the block positions?
We set small.x X equal to big dox minus small size.
And now we could in proportion to the weight of both blocks. We could split the distance um the shifting the manual shifting here between both blocks. So we could also update big.x X equals big.
But how do how would we update big dox small dox plus small size?
And then this should be well what's the fraction of the what's what's the difference in weight between both?
So the big double shift factor is equal to big dom minus small m divided by big m.
So that's what percentage of the or no it should be simpler small m divided by big m probably. So what percentage of the mass is the small one as compared to the big one? Then we shift.
So this is now a difference. Then we shift the big size. So the small size we shift it by one minus the shift factor and the big size we shift by and the big position we shift it by the shift factor. So the big one is only affected very little because small n divided by big m is a small value.
Therefore, the shift factor is a small value and therefore big.x is moved only by very little. We also need to update an offset here. We need to add on an off an offset.
Big x minus small size multiplied by this.
Let's just think for a second and see if this makes sense. Before you guys put in the comments again that I try out too much stuff before before thinking and I succeed by accident. Oh, I succeed by accident guys. So the small position is the original small position but we move it according to the shift factor. The shift factor is the proportion of small size divided by big size.
So this is effectively let's say the small size is the small one is 10 times as small just a tenth of the mass of the as compared to the big one then this is resulting in 0.9 and then the difference no this must be big x minus small dox minus. Now that's the difference in position. But it must be the difference in position um between the right side of the small block and the left side of the big block small dox plus small size. So this is the actual difference in position. This is the distance between both rectangles. Let's compute the distance between both.
Distance block distance is equal to small dox plus small size. So that's the right side of the small one.
And big dox minus that entire thing.
So here we move small dox by the distance between both blocks. We know that small is always to the left side of big.
Therefore we do small x minus block distance.
And then we give the block distance a weight in proportion to the weight of the small and the big block. So maybe that makes more sense now.
And then we do the same thing for the big block except we add on top the block distance multiplied with that weight. And now we don't use one minus the shift factor but the shift factor because this needs to be a bigger value of course.
Right? So again, if the small mass is just a tenth of the big mass, then here for the small one, the the block distance has a bigger weight, 90%, and here the block distance has a smaller weight, 10%.
So they are moved apart according to their weights. Let's see if this still works.
No, of course it doesn't work. Damn it.
I thought I just solved the problem by pure thinking. But of course, my capacity is not sufficient for that.
Update the block positions. Of course, we should also only update the block positions.
Ah, wait.
I'm setting small.x to equal something.
Maybe I should just decrement it just to make it explicit that this is essentially a decrement and this is an increment. We have to be careful that we always decrement and increment things here plus block distance. The block distance if they collide is negative by the way.
Ah, so maybe we have to invert those because if it's negative then we know the big x.
Ah, wait. What did I type down here?
Let me just redo all of this.
I definitely need to remove this big X offset.
If the block distance is negative, the block distance is always negative because we know that the blocks collide because the block distance is negative, we this entire term here is negative and the decrement results in the big block moving to positive x. Okay, that that sounds reasonable. So, this is likely a sign error that I introduced. Let's see if it works now.
What the hell is that?
That looks like another sign error.
Block distance is big. DX minus small dox plus the small size. That makes sense.
That's the difference. The distance between the blocks. Okay.
Block. Ah, yeah. Yeah. Yeah. I forgot the the increment here. I set the small x equal to. That's of course a a typo, which I did not intend. Let's see.
Physics is always incrementing and decrementing things. At least if you're dealing with simulations, guys. Remember that. If you're setting something equal to something new, then that's always a nonlinearity. And nonlinearities are exceptions in physics. In this case, a collision is a non nonlinearity. Okay?
But in regular movements when you attract forces and magnetism and electromagnetics and things like that, things are very linear. They never just change the value out of nothing. So there's never an equal sign just like that in physics. There's always an increment at least in simulations. All right. By the way, correct me if I'm wrong, guys.
You guys are the pros in the comments.
So now this looks correct.
Looks good. Looks very good to me. Okay.
And now we are not going beyond the borders of the wall. And also the blocks are not penetrating each other again. So the only thing that is missing now is to add a counter. Let's see.
I like how systematic I'm approaching this problem today. Oh, I just messed up the the code because I I accidentally capitalized my keyboard. So let's just undo that.
save and just make sure it still works.
Yeah. Okay, it seems to still work.
That's awesome. I like the result so far. So far, honestly, before I recorded, by the way, I was a bit afraid of the physics, but I mean, it's it's quite simple.
Ah, that's always the creator's block.
You you sit there and contemplate, how can I embarrass myself today?
Nah, it's fine. I mean, what's so fun about programming is that you systematically approach a program. If you remember, we started this project by just creating a simple window. Then we just created a rectangle. Then we created a a model for the for the mass for the block here. And we just approached everything step by step. And just like that, we solved the problem in the end. We're not at the end yet yet, but we're at a much bigger bigger and better state than at the beginning of the video, obviously. And that's how you deal with problems in life in general.
You just incrementally remove layer by layer of complexion until the problem is solved. If you want to be able to run 20 kilometers or even 20 miles, then you just start by running 1k first. or if you can't run 1k, just run 100 meters. It doesn't matter really.
And that's how you approach problems. Or if you I don't know, just improve things by a single percent every day and it will pay off so so much. I promise you guys. Um, all right.
So, let's continue. I was talking too much. Sorry if that interrupted your programming studies. Some some people are really watching these videos to study pro programming. Put it in the comments if you are one of among them.
Um I find it quite surprising because these are very slow paced videos. But maybe that's what helps you. I don't know. Put it in the comments to explain it to me. Guys, I just started this channel to to just record myself doing stuff and now there's this big community putting things in the comments, improving me, um, discussing topics. It's awesome. I love it. Okay, so what do I want to do now? We have a working sim physics simulation. Now, we want the counter. We just want a simple counter. There is a counter in rail lip that we can introduce for example and there's not a counter in rail lip but there's a function to draw a text draw text I think draw texture and I'm always searching for that function there. There it is. So draw text is input a text and then a position x y for the text a font size and a color. So let's put that text drawing in the visualization block, right? Because it has nothing to do with physics.
Draw text.
And maybe let's call it draw counter.
And this can be a new function void draw counter. And of course I know we don't have a counter yet. We're going to introduce it in in just a few moments. Let's just call the function here. draw text like this and put some dummy text here. Let's let me just write Daniel H here for myself and then pause X pause Y. I'm just going to put it at 50/50. So the top left corner of the screen in in screen coordinates.
Then the font size, let's make it 25.
And the color, let's make it white. Why am I choosing everything to be white today? I'm not very creative today, but I I find this looks so simple and awesome. And I just forgot compiling, of course. Let's compile and execute. And now I have a text here. And it's aligned exactly with the left side of the wall, which is not so nice. So, let's move the text a bit to the right. Let's give it an X coordinate of 100.
And now this is good. I just want to increase the size because actually the text is the it's the heart of the project. I want this text to yield a number that somehow corresponds to the digits of pi. I'm so excited to see whether this even works.
So now we have a text here. Perfect.
Okay, what you could actually actually do if you code this project yourself, I'm not going to do it right now, but it's an idea. If the blocks collide, you can also add a small animation for the collisions themselves. So, whenever the blocks collide, either color the blocks a new color or put some small explosion or spark between the blocks. That's also quite a nice thing to do. We don't need it. I'm just going to focus on the physics and the math here. I'm going to do the boring part, but I know that several of you guys you code these projects yourself also in different um languages. So, just play around with these things. It's very much fun. So, let's create a collision counter here. Collision counter. And it starts as zero. It's a global variable because I want to access it in many functions. And I think it's since it's the heart of the logic of this entire project, it's completely fine to have it in a global variable rather than passing it around functions.
And then I'm going to draw the counter here.
How do we draw the number?
We are going to draw. Well, we have to first convert the number to a string. Oh, do we have to deal with some C string stuff again? I think we can use a format string using sprint f maybe.
Let's just see. Let me just go to the docs for a second because now I'm a bit surprised that we have to do deal with strings in C again. So sprint f takes as input a format string.
No, this is the format string and another string here. This is probably a buffer that we are going to write the result into. Is there another simpler way to convert a number to a string a there's a function a to i think which converts a string to an integer right?
Convert a string to an integer. But is there also I2 A? So integer to ASKI and I2 A. No, there's not. Um, oh, let me look it up just here. C integer to string.
Yeah. Okay. And this guy also already recommends sprintf.
These are the magic methods by the way.
SP printf, fr, smp, printf, and all these functions that I don't get how they are implemented. They are crazy. I mean you have to implement a function that takes as input a format specifier like this and then can take as input any argument which can be of different types, characters, integers, anything and then it does some magic and outputs a string based on your specified format.
So it must be an incredible parser that lives underneath.
I I find it astonishing honestly.
So you can use aspirinf to do it. We have to allocate a string here and then we put a yeah we do that.
Let's allocate a string.
So our string is a character array. This is the string string. Let's give it a size of at most 1,024 characters. I think it's fine. There should be no bigger number than that in our result.
And if there is then we deserve to fail because I don't know in which case there should ever be such a such a value.
Then we asprint f into the string. We specify a format specifier and the number we want to print.
So let's sprint f into string. The format specifier in our case is a long decimal because we are we have a long counter to be able to count very large numbers.
And then the next argument was the counter itself. Collision counter.
Okay. And then in draw text we draw the string here. Let's see if we if we get a zero now. We should get a zero. Oh, perfect. We get a zero. So that zero stands for zero collisions detected. Why do we get a zero? We get a zero because we are not counting the collisions yet.
So let's count the collisions.
So whenever we collide then we reach this block here the block collision code and then we can just do collision counter plus plus.
Yes. at some point maybe in a at some point if the number exceeds the maximum 64bit unsigned number. Oh, we can actually make it unsigned. By the way, that's a good point. Unsigned because we never have a negative number of collisions. So, at some point in the far far future, if we have a crazy simulation that deals with incredible differences in masses, then there might be the case where we exceed the size of a 64bit long unsigned integer, but I don't think so in a in any realistic case. Okay, so let's not microoptimize anything here. By the way, if you want to perfect if you want to to have a perfect simulation. Oh, that's so nice. Then you have to use um infinite precision uh numbers. And of course, this is not pi here. Why is why are we getting 48 as a result?
This is why are we getting 48? Is anything in our simulation off?
I paid attention to only use factors or uh powers of 10 here for the speeds.
I don't know if there should be difference. Ah, because the small block has a starting velocity. Of course, this is not what I want. So, the starting velocity for the small block should of course be zero. And then I don't know whether the diff the distance between the wall and the small block starting position and the big block and the small block whether they are any important.
I don't know. Let's just try and see.
Okay, now we have a starting velocity for the small block of zero. Let's see if it works. No. Why 49? Why 49? Now it's 50.
So do we need to maybe give this a starting position that is also a factor of or a power of 10? I don't know.
Holy crap. What did I do here?
Ah, the small block of course needs to have a this starting position. Big block needs to have this one.
So the big block is incoming. Let's see how it turns out. How the simulation turns out.
Ah somehow the the collisions approach 50 always in our case. So what's the difference?
Maybe also the sizes of the blocks needs to be somehow proportional to 10 at least in the simulation. In our case, the display coordinates very strictly correspond to the um simulation values. Let's just try this one.
And this is also 50.
That's crazy.
Then what else could it be? So the sizes don't seem to change anything.
The mass. Okay. What if I make it to 10?
So now's the point where I would need to find out which parameters I'd need to change in order to get the in order to get pi. I think this is something that I need to research before I continue this video. So, let me just have a look for a second.
Okay, what I've tried to do is in the meantime, I've incremented or increased this this the step simulations to 5 million steps per simulation. And the the result is quite saddening because the simulation runs just like before but still even though everything is computed much more fine gradedly we still get exactly 50 collision collisions. So whatever we input or what however we change the code we always get 50 collisions. One difference I just had a look at the three blue one brown simulation is that we count collisions only on interblock collisions which is wrong. So we should also increase the collision counter on whenever the small block hits the left hand wall. Also there never are any collisions between the big block and the wall. So we can just remove that if branch.
If we now compile, we should get twice the amount of collisions. Let's have a look. So now I would expect exactly 100 collisions or 99 depending on fine. No. Okay, it's 99. Okay, 99. Fine.
This is okay. However, the core problem is it does not really matter how fine the block steps are because the computation of pi regardless of how fine the simulation is. The computation of pi only works correctly if you have infinitely infinite precision. So it could happen that in within a single simulation step, regardless of how small you pick the time, there could be multiple collisions because the blocks can collide.
Yeah, the the blocks can collide. No, wait. The blocks can collide. Then the small one moves to the wall again within the same time step. And then well, whatever. There are multiple collisions.
Okay. It's very confusing to show stuff with my hands in an inverted camera view like it's impossible for me. Okay.
So what we need to do instead of simulating a simulation step um for one fixed time frame here we should find and locate all the collisions within that time. How do we do it? There's a trick for that.
We create a while true loop here. while true and within that loop we perform all the collisions and we check whether a collision has happened. So let me just put true here and import include standard bool just so we can use booleans.
Yes, I know in a more recent standard of C it's included in the standard it's included in the language but I'm not I'm using just the standard C compiler whatever is shipped with the operating system I don't bother to upgrade this so while true we loop and now we check if a if a collision happens so collision happened is false at first and if it's still false if no collision has happened after checking all these if branches here all these if conditions if still no condition collision has happened collision has happened then we escape then we escape from the infinite loop we break. Okay.
However, if a collision happens, we check in the next iteration if yet another collision happens.
Okay. So as long as collisions happen within a simple within a single time step, we check if more collisions happen because the collisions can could technically be so fast that well they happen all within infinitesimally small time steps. Okay. So we need to do if we need to check does did the collision happen?
Well, if there's an interblock collision, then yes, a collision happened. So we set it to true here.
And also if there's a wall collision, we set collision happens to true here.
And I think that might already be it. So we check has a condition happened.
Yes. Then we loop again. Then we do a computation of the new parameters of the blocks the new velocity and we already update the position. Now, if there's due to this new velocity, well, there's a new velocity, but there's is there a new position?
Yeah, maybe because the blocks are moved apart here.
Do we need to update the X position as well or do we just update the velocities? So we update the velocities then we say a collision has happened then we loop again then there's another change in well but x here is unchanged so this will enter the collision happened again let's just run this and And this doesn't do anything because we're probably entering the infinite loop. Yes. And we're not escaping it. So this is dangerous.
So somewhere I need to change something.
Ah, of course. Wait.
If a collision happened, then we break.
No, no, no, no, no. If no collision happened, then we break. If no collision happened. Let's see if this changes the situation. Okay, let's see.
One, two. Ah, no. Now it's hanging up.
Ah, damn it. Okay, so we are entering an infinite loop. Now I can already hear the CPU spinning up.
And now the program's crashing.
All right, let's just check. Let's continue checking.
Okay, the issue happens when what? I just wanted to say the issue happens on the first collision with the with the wall. Apparently, it does uh it happens whenever. So somehow the blocks freeze together. Okay, probably that's because the small dox on the wall collision is checked whether it's smaller than or equal to wall x.
And of course if we set small dox equal to wall x and then we loop over and over again. We enter this condition over and over again. So here we have to strictly check for smaller than the wall x. And the same thing we have to do also up here for the block collision, right? We can't check for equals because this will enter over and over again because we're shifting apart the blocks um in this case or we're shifting it them away from the wall to be exactly aligned.
Let's see what happens now. Oh, please please work.
1 2 3 4 5 6 1 4 1 wait.
So this this computed things but it does not look like pi but it looks okay. It looks interesting. This this looks like many many more collisions have been counted. Is there anything that I should adapt? What if I make the mass just 100?
How does this change? So now it looks like we're computing many many more uh collisions.
This is completely wrong.
This looks not so stable.
So what could be the issue? Of course I'm the the steps I'm simulating here, they are ridiculously large. What if I just do 100 again?
I don't know how how fine they have to be now that we compute the collisions with infinite precision.
So how whatever variable I change the collisions change. So it could happen that by accident I accidentally change the variables in such a way that they compute the digits of pi. But of course I don't want that. I actually want to understand why or how the parameter should be designed such that we properly compute pi here.
This looks good.
Maybe before now this is exactly the the number that I was expecting. 31 that's the first two digits of pi guys. Maybe the precision before was too fine.
Right? I I divided this by 5 million. So maybe that was too fine. In the step blocks function, we are advancing the positions by dt. That's fine. If it's too small a number, maybe we get floating point computational errors. And that's of course the nature of floatingoint numbers. I can't change that except by using some library that allows infinite precision which you can of course do. Uh I if this is now correctly implemented there's a chance chance that this is now correctly implemented because the number 31 is exactly the one that I was was expecting. Now ah 32 what? No I don't want no no no that's the wrong number.
Did I even change anything right now between the two simulations? What if I execute it again?
Did I even change anything?
Ah, I just increased this to 1,000 and it was before 100.
So maybe that's the boundary where the errors accumulate too much because the frame time is very very small. Then we divide by 100, it's even smaller. and then errors propagate. Let's keep the simulation step size just like this and instead increase the the mass because the diff the bigger the mass is, the more digits of pi we should get. Let's pray and hope that this works, guys. And if you know which parameters have to be set properly, then put it in the comments. This is ridiculous. It doesn't seem to work.
Why? I noticed this needs to be a power of 100, but then 1,000 should work as well. And 10,000 should also work.
But no, it does it does not seem to to converge to the digits of pi. This so something is wrong.
Maybe I'm miscounting the the collisions that could of course happen. So I'm checking did a collision happen? Yeah. A false. Here I'm setting it to true. If it happened if the collision counter increases.
Then down here I'm also increasing it.
I'm incrementing it and I'm setting collision happened to true.
If no collision happened then we set to false. H looks okay to me so far.
What could it be?
I just discovered when I executed this simulation here that in in one time the the counter actually jumped to three on the first collision of the blocks. I can't reproduce it somehow, but sometimes it seems to happen, but now it doesn't happen. But also I I seem to always get different numbers. So something is really unstable here. And but it definitely jumped to two three on the same on on the first collision. That means that potentially sometimes multiple collisions are counted or the single collision is counted multiple times. This is of course not what we want. So we need to put a fix here. A collision can only be counted once.
So when the blocks collide, we know that they can only ever collide once before these velocities here are updated.
And to ensure that we need to check before entering this this branch if the coordinates overlap of course but also if their movement speeds if they move toward each other because after the first update of the velocities they move apart and then we don't need to update the collisions anymore. Right? This will ensure that we only ever count the collisions a single time.
How do we do it? We check big.vx is well how do we how how do we check it?
The velocity of the big of the big block must be smaller than the velocity of the small block because if if it's bigger then they are never going to collide. Then that means they are moving apart is smaller than small dovx.
And the same thing we can check for the wall collision down here. Small dox no small vx must be smaller than zero. So the small block must move toward the wall in order to collide with it because if it's reflected and we still count the collision due to some floating point number imprecisions, then of course we miscount. So let's see if this works. I'm super excited, guys.
Please.
One.
Holy crap. This looks super good. Now we just We actually need just one more. We actually just need just one more collision.
Oh my god. I forgot the one collision.
We have the digits of pi, guys.
How awesome is that? Oh my god.
Okay. Okay. Okay, so that was the fix.
We were accidentally counting some collisions multiple times due to floating point number imprecision. So that's it now.
And okay, how do we now increase the numbers the digits of pi that we compute? Probably we need more collisions. That only works with higher mass, I think. Let's see. I just 10xed the mass.
Let's see. Oh, this is so exciting.
Why doesn't this work? This worked before, but probably because before I adapted the the step size here. So maybe I have to 10x that as well. So I'm sure before it was correct that this would this would be the craziest coincidence ever. Uh I need to also adapt the step size according such that we don't skip the digits of pi probably. Did I even compile please?
No.
What if I revert So I think this was the case where it worked. Let's check it again.
This is no coincidence, guys. This is pi. This these are the the exact digits of pi. 314.
So this is working.
Now I just need one more digit of pi.
What if I do mass one?
What if I change the velocity? I I think we're just dealing with imprecisions here. This also yielded a correct result. It's crazy.
So, it's just this right here. Maybe let's replace this with a long an unsigned unsigned long int unsigned long. And we simulate the hell out of this right now. But if I just add to zeros or maybe let's even make this a precision or substep number of substeps number of substeps or simulation substeps simulation substeps simulation substeps and that should be a globally defined globally defined variable simulation substeps and it's a large number. Let's make it 10 million.
But I'm not sure the these floating point issues. They come up again probably guys. I'm praying.
Okay. So this is working. This is correctly computing pi. It's now a bit slower because I'm computing so many substeps. This is working.
And now we can set the mass. We can 10x the mass of the big block.
Let's see.
This doesn't work. This doesn't work because there is some imprecision here.
So I would say I'm not too fond of this of this frame time thing here. I think it should be a double instead. I know the frame time returns a floatingoint number. But let's compute let's use it using double precision.
But there's no point for me computing everything in doubles.
having the frame time deteriorate due to floating point imprecision. Um, so double would be way way way better here because it's 32 more bits of course.
Double.
If we now compute this, then we should still get a result. The result won't change too much. Yeah, it's exactly the same as before, but okay. We are now more precise. That's fine. And I found out that for this simulation to work, I should have looked that up earlier, the the mass must be a power of 100. What I that's what I have told you earlier. But of course, powers of 100 are not always the same as powers of 10.
So I thought I can just add zeros here.
No, I can't. I always have to add two zeros because powers we're dealing with powers of 100, of course. So this should this power of 100 or 100 should give us a working result.
Let's see.
This should give us like um exactly 31.
That's that's working. Um it's still super slow because I'm performing so many simulation substeps. Maybe that's too many. Let's reduce it to a 100,000.
It's probably fine now. I think the real issue is that I haven't used powers of 100 and that's the core of the of the mathematics here. So now it's running very smoothly. I'm computing the numbers of pi 31. Okay, perfect. Now let's increase the mass of the big block and I think we should define it here.
Define big mass as some power of 100. So let's put that many zeros. So that's 100 to the power of three.
And let's also put a comment here.
Must be a power of 100 and compile and see. And now I would expect that we have more digits of pi.
And yes, that's perfect. Okay, guys, it's working. It's working, guys. Holy crap, that feels so nice. Okay, let's increase the big mass to something like that.
Let's just add yet another few zeros.
And now we should get another digit of pi. Let's see.
And I would say that this is working.
Let's check the actual value of pi. But I I mean it's obvious that we're really approaching pi. I don't recall a five in pi honestly.
But we are probably doing the right thing.
So digits of pi.
Oh, it it is it actually is the right digit of pi.
It's 31415.
31415.
What was the result right now? Was it was it 31415? Well, 31415. It is the result. Okay, perfect. Guys, that's it.
That's the episode. We have created a working pi computational physics engine, you know the thing that computes digits of pi purely based from the numbers of collisions in the simulation. we have the core issue we faced was that I didn't know that it has to be the big mass has to be a a power of 100 rather than a power of 10 but now I know it obviously and I hope you also learned something it was so much fun if you want to learn C and also get going creating such projects yourself then have a look at the nononsense C programming course that I have linked in the description below the video it's a course that I have created to get everyone and I mean everyone going in C it starts with zero and even if you are already familiar with some programming languages it starts with it gets you going very fast has exercises that force you to code real applications and it's basically the exact process that I used to learn C myself but compiled in a course. All right, have a look at that else. Leave a like, subscribe, and see you in the next episode. I hope you enjoyed it. Bye-bye.
Related Videos
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
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
🚀 BCS613C Compiler Design | Module 1 to 5 Schema Evaluation 🔥 | VTU 6th Sem 💯 #VTU #bcs613c #exam
Pranavaa-y4y
104 views•2026-06-02











