Orbital
- 19 Devlogs
- 23 Total hours
A 2D astronomical physics simulator built in pure Python Turtle.
A 2D astronomical physics simulator built in pure Python Turtle.
Added some instructions to make it easier, added more colors, and I’m now working on making more rigorous merge logic.
Today I added zooming that works with scrolling up and scrolling down appropriately: when you scroll down, everything gets smaller, and when you scroll up, everything gets bigger. Mass and gravity, of course, stay the same.
I made sure that the sling function (basically spawning new orbitals) works flawlessly. Our aim function—which draws that mini press-and-drag indicator to make it easier to see where you are spawning orbitals—is also working with both camera differentiation and zoom differentiation.
This way, you can zoom all the way out and still spawn orbitals in the correct way, or zoom all the way in and spawn them super close up. All the physics and rendering works beautifully.
I made some more intuitive user changes:
Slingshot Dampening:
Now, the slingshot isn’t like an Angry Bird slingshot that propels your particles to the other side of the universe (though you could do that if you want, just open the code). All the slingshot coordinates and values are now multiplied by 0.2. We’re nerfing it a lot, but it seems more intuitive that way. Since you don’t know it’s nerfed, you won’t notice there is a higher capacity. In our case, a higher capacity actually means friction. Don’t get me wrong: it’s not particle friction, because they go super fast. It’s friction when trying to get orbits to work.
Repository Tweaks:
Calling the script from_scratch.py was killing me, so I found something simpler: orbital.py. It’s much better. I updated the GitHub repository so that the current release is orbital.py (the refactored version). While main.py (the previous version) has a few more features right now, orbital.py is much cleaner. Main.py was more of a learning run, and I feel like I’m bringing my A-game with this new refactor.
Look and Feel:
I really liked the black background and white orbitals of the previous main.py version, so I changed the clinical-seeming style of from_scratch.py. I scrapped that look and made sure the new orbital.py has some taste.
I fixed the “merging from hell”. The first issue was the radius calculation. When two circles merge, simply adding their radii (r1 + r2) is physically wrong because the combined area grows way too fast. In reality, the area of the circles should add up: A_new = A1 + A2. By plugging in the area formula π × r², we get π × r_new² = (π × r1²) + (π × r2²). The π cancels out completely, leaving r_new = math.sqrt(r1² + r2²). I updated the collision code with this, and now the combined masses have physically accurate sizes.To make this look right, I also fixed the visual scaling issue. Previously, every particle rendered at shapesize(1, 1) regardless of its actual mass or radius. I changed the instantiator so that a body’s shapesize dynamically scales with its physics radius: self.turt.shapesize(self.radius / 10, self.radius / 10). Now when objects collide, you can actually see the merged planet grow on the screen.Lastly, I added a quick reset button. Pressing the R key runs a function that loops through the current objects, hides all their turtles to prevent visual ghosts, clears the main objects list, resets the camera back to center coordinates, and spawns a fresh central star
Hey, I added merging, but it’s “merging out of hell” because I made a very unphysically true and crude (if you’d call it that), but working prototype of merge_collisions and the merge function itself.
It works as I intended, but not physically truly. Right now, it adds the radii of the circles themselves. If you think about it, that’s physically wrong because there’s a big difference between adding the areas of the circles versus their radii. That’s something I want to get right, so the next time I’m working on it, I’ll make sure to fix it so that we are adding the areas of the circles rather than their radii.
I also found a bit of a problem where the merged objects themselves have a shape size of (1, 1). That’s not right because if you merge two objects with shape size 1, you’d expect the new shape size to be 2. Right now, no matter how many objects you merge, it still stays at 1, resulting in a very, very dense circle.
I’m going to change that so we add the areas themselves, and have that new, bigger area update the visual representation of those astronomical objects.
I’ll keep you guys updated. See you next time.
Lots of stuff today:
Camera movement: I added camera movement to our new refractor.
Better version of the sling: The older one just expected you to press, drag, and release without seeing much of an indicator. While that works if you know the program a bit, I think a direct, laser-esque pointer that shows you actively as you’re pressing, dragging, and releasing is more intuitive and easier to work with. So I added that.
Now we have intuitive camera movement and intuitive spawning.
For this refactor, we won’t have any UI. All the sliders for gravity and mass will be per keyboard or hotkey.
Welcome!
Today, I fully migrated to the object-oriented programming architecture. Earlier, the class wasn’t really full OOP; it was just a hidden list factory that appended the stuff via a class instead of doing it manually. I wanted to change that because this is going to be a refactor in an OOP perspective, and that’s what I did.I made sure that all the formulas used the latest changes:1. Earlier, they would use the index of the objects inside the objs list of lists. It would be something like object1 and the index of whichever element it wants to access, like mass, radius, velocity, or coordinates. It was very sloppy, and you had to keep up with what each index meant.2. With an OOP version, you just write object1.mass, object1.x, or object1.vx. Each and every line is more intuitive.I also worked more on the planet spawning. Remember, this is a new file with a different perspective of trying to remake this in an OOP sense, using classes and the benefits that ensue from them.Additionally, today I implemented the balancing of the canvas differences in Tkinter and Turtle:- In Turtle, the (0, 0) coordinate is directly in the middle of the screen.- In Tkinter, it’s at the absolute top left of the screen.You have to make sure that all the formulas and all the logic get the correct version, which in my project is the Turtle version. I added and worked out the logic that would make sure that balance is kept.
Actively working on making Orbital (via the from_scratch.py file) more object-oriented. I think OOP (object-oriented programming) will be a great addition to Orbital because we have lots of stuff that can be simplified. These could be new particles, like new planets, and I think grouping everything into classes would be a better way to handle these.
Right now, our sling function just does the same thing again and again. A class that could be called any time we want to make a new particle is probably the way to go.
I’ll keep you guys updated.
Today I spent all my time on from_scratch.py to really get to understand the physics of it all. Before, I just relied on a few formulas.That’s why I’m going to try and maximize my gains from this project by re-understanding all the logic and trying to write it from scratch, using only my notes and not looking at any AI or code from main.py.It’s going good! I added quite a few functions:- A distance function- A force function- An angle function (all of these take two objects, by the way — I still have the same objs list, which is a list of lists)- A velocity update function called velo_update that takes an object, a force, angle, and mass, and updates the velocities of those objects depending on their forces, angles, and massMaybe I can cut back on mass. I don’t know why I added it, since we can already get it from the objs list with our object parameter. I’ll look into that.I also created the scaffold for an update function, which is going to be the main function. I added screen.ontimer(update, 10) to run it every 10 milliseconds. It’s just the main scaffold for now, but I’m really focusing on the logic of it all.
Hey, this is another mini devlog.
I just wanted to say that while waiting for my initial ship to get reviewed, I was working on reviewing my current code and remembering how every part interacted.
I’m doing this by making a from-scratch.py file where I just try to build the general project from the ground up with my direct understanding and recall of it, without looking at every line of code. This is like a mini side quest in the background so that as the file gets big, I don’t lose where I’m going. Right now, it just has the basic functions like distance and force, and has two Python turtles set to circles with different colors.
I’m going to build this from the ground up and really focus on the struggle and pain through the process. I want to maximize my gains from this project this way.
The UI was a very wretched default gray, and to be honest, it didn’t look so visually appealing. Mine still isn’t all peaches and cream, but it’s less of a distraction and more of a subtlety in the grand scheme of things.
I changed this to a better, harmonizing dark charcoal color that I think goes pretty well with the minimalist design of all-white Orbitals and a pure black background.
Hey, remember the UI I had in earlier?
Well, when you change the window size, it just stays hard-coded. If you maximize your window, there’s suddenly a piece of UI that was supposed to help you navigate the simulator that’s in your way.
So, I did some math so that the UI is at the perfect top right of any shape and size of the windows you make. It’s a dynamic UI layout, and it’s dynamic enough to survive window resizing. Pretty cool.
Today I added some UI, so we have some actually good interactive elements. We can use sliders to, in real time, update our gravity. And I also added a reset button that basically lets you clean up all the messes and start from a fresh place.
This is not a full devlog. Because I’m typing this on my phone. Came to say that I’m working on adding a minimalistic UI to the project, like small buttons and maybe sliders for mass and color control. Using tkinter, the underlying library in which turtle was built. I don’t have any screenshots with me, so enjoy this photo from my gallery, shot at the beach at night.
Today I added merging. Now, objects that get too close to each other don’t do weird physics-breaking stuff. They intuitively merge into an object with a total mass, total area, and a new velocity depending on the previous velocities. This way, you can actually build complex systems that fall apart in at least an ordered way.
There was a slight problem if the objects were going too fast: because of their speed, one frame they would be behind each other and the next frame they would be ahead of each other. I had to do some math to see if the path they took went through the object we were checking. That was a very fun, intuitive piece to work on, and merging now works very intuitively and very well.
Added camera movement with WASD. Now, the objects that run out of your view aren’t lost forever: you can catch up to them with only your keyboard and a few fingers.
I finally added slingshotting! Basically, the planets aren’t hidden in the code itself anymore; they are right at your fingertips. You can spawn planets just by clicking and dragging. It’s that simple.
This was a feature I wanted for a very long time, and I’m really satisfied with how it turned out.
Devlog #2 — N-Body Physics
Today I implemented dynamic physics and dynamic moving.
Basically, before, the move function—the function on a one-millisecond timer that updated everything—was extremely hard-coded. It just used the data of the existing two turtles (the two bodies I made myself), and it definitely wasn’t scalable.
So that was what I tackled today. Now, the main function that runs our whole physics simulation is a tad bit less torturous.
I’m building Starshell, a 2D astronomical physics simulator in pure Python turtle. No game engine, no physics library—just the standard library, a turtle canvas, and physics and math.
The idea in my head is quite simple: I want to simulate how gravity actually works. Bodies with mass attract each other, their paths bend, and orbits merge. Eventually, I want collisions, multi-body chaos, and a UI where you click to place stars and planets.
Today’s milestone was the first orbit. To get here, I didn’t just plug in some formulas; I had to build the physics chains from a blank turtle canvas:
The orbit isn’t actually hard to code. The planet doesn’t know it’s supposed to go in a circle; I don’t tell it to trace a circle. It just responds to the pull of the star every single frame. The circle is what falls out of the math when the initial velocity is balanced just right.
One of the trickiest moments was realizing my gravitational constant (G = 1) made the force so small—around 0.0004—that compared to my velocity of two units per frame, the planet just flew away in a straight line. I couldn’t see any curve at all, let alone an orbit. Cranking the gravity up to something like 1,000 to 1,600 (found by experimentation) made the orbits visible.
That moment when the blue dot actually curved and came back around in a full orbit felt very satisfying. Next up, I want multiple bodies that interact with each other, not just a fixed point (aka a star).
Follow Along!