Orbital is an experimental simulator / game for orbital mechanics that runs in your browser.
Source code: github.com/gianlucatruda/orbital
I’m using it as a way to:
- advance my computer graphics and front-end skills,
- explore my interests in space travel and physics,
- practise pair-programming with LLMs in a low-stakes setting
The project is currently written in JavaScript (with scatterings of Rust, GLSL, and WebAssembly).
It makes heavy use of the excellent Three.js library, which gives convenient high-level APIs for browser-based graphics atop the cross-platform WebGL. That saves me having to implement low-level features from scratch, which I’ve already done in my browser-based raytracer.
This is the development log for my Orbital project, which I started during my Fall ‘24 batch at The Recurse Center (RC), whilst learning about GLSL shaders and Three.js.
I’ve also written about everything I made at RC, my Recurse experience, and my advice.
Roadmap
- Refactor to modular design: data/parameters, UI, and physics/logic.
- Add a spacecraft
- Fix camera tracking of celestial bodies (particularly for moon)
- Fix UI scaling on small screens (especially mobile devices)
- Incorporate true planet and moon sizes and distances (with object scaling factor in Controls pane)
- Convert to AU-based render units (and fix scaling systems) to deal with numerical stability
- Fix camera tracking to follow the object as it moves (only noticeable at realistic scale)
- Fix bug where only sun is spinning, not any planets.
- Fix orbit path drift
- Fix orbit path drawing for satellites
- Fix bug where animated object diverges from true orbit at higher speedup.
- Fix bug in satellite (child) orbital path tilt
- Fix bug where craft “teleport” to new orbit after burn instead of conserving relative position.
- WIP:
calculateOrbitAtTime
andcalculateElements
in engine (mostly correct) enables demo burn on ISS - WIP: Update engine to allow orbital maneuvers between spacecraft and celestial bodies
- Fix bug where
Array.forEach
throwsRangeError: Array buffer allocation failed
(happened at delta t =16103.61413
) - Add some tests for the core physics (e.g. using orbital formulae to cross-check)
- Fix scaling of objects so only children of currently tracked celestial body are scaled (with smart dynamic limits)
- Fix camera jankiness when using true sizes and distances. Replace OrbitControls with custom camera system?
- Refactor and tidy up the code. Fewer side-effects.
- Cast shadows from objects upon one another
- Add textures (and a proper model) for the spacecraft
- Add a level/puzzle: with a given delta V and time budget, get from body A’s orbit to body B’s orbit
- Make sun appear brighter and give it some kind of light flare / glow effects
- Add a WebGL fragment shader to simulate stars as background
- Calibrate the solar system against https://eyes.nasa.gov/apps/solar-system
- Simulate Earth’s atmosphere (can I write shaders for that? e.g. Perlin noise?)
- Migrate physics to a Rust engine -> WASM bind (see
rustify
branch for WIP)
Development updates
2024-10-21 Specular highlights
It was a week of graphics exploration for me.
There is no discipline more dominated by “clever” than graphics. The field is driven toward, even defined by, the “good enough.” As such, there is no better way to teach clever programming or a solid appreciation of optimizing effort than graphics and simulation. Over half of the coding hacks I’ve learned came from my study of graphics.
— Matt Might, What every CS major should know
Digging into shaders and 3D graphics tooling was a key goal of mine for my time at Recurse and I’ve been doing a fair bit of that over the recent weeks. But I really went hard in the paint in week 10.
I decided to speedrun a 7-hour Three.js course from ZTM and used the code examples to build my own bespoke Three.js template project that I can use to quickly spin up future projects.
I immediately put that to use in the weekly Creative Coding, where the prompt was “switching trains.” I wanted to go down a whole game dev route inspired by Dostoyevsky’s Notes from the Underground — a meditation on free will where the player is a non-human machine who explores the limits of their agency and choices whilst managing the locomotives of a 19th-Century Russian train station. What I ended up doing was spending an hour fighting with model and texture formats to get a basic train model loaded into Three.js and applied to a PBR material. Try it in your browser or view the code.
Ahead of our RC Graphics hangout, I did a speedrun of The Book of Shaders to get an impression of the topics. Having spent a dozen hours playing with WebGL/GLSL shaders at this point, it was a great filling out of my mental map — connecting many tacit concepts to concrete ones.
One of the major challenges with writing fragment shaders is that you’re spawning millions of stateless threads to run in parallel on the GPU, which makes standard debugging tools and techniques impossible. You can’t step through the code or use print debugging, so you have to engineer debugging out of visualisations in the pixel space (or build your own separate tools). This is part of the fun, I think.
The other major graphics undertaking was beginning work on a solar system orbital simulator in Three.js. This was partly inspired by an example from The Zero To Mastery tutorial that I immediately wanted to fix the glaring orbital mechanics errors in. But it’s mainly inspired by the fact that I’m a space nerd and recovering KSP player who’s currently reading 2001: A Space Odyssey and just saw SpaceX literally catch a rocket this past week (and then yeet a NASA probe to Europa via a Mars gravity assist the very next day)!
Ideally I’d like to turn it into an orbital mechanics puzzle game / simulator during week 11’s game jam. I’m currently digging into the woods of breaking the physics calculations out into a Rust library that I compile to WebAssembly and then bind with the JavaScript frontend.
Commit log:
- Initial commit
- Initialised from ztm tutorial project.
- Updated to my preferred structure.
- Aesthetic upgrades.
- Much-needed prettier pass to fix egregious code.
- Realistic Kepler mechanics with variable timeAccel, pane, stats.
- Axial tilt and rotation. Orbital path plots.
- Improved time acceleration.
- Better orbit path line aesthetics.
- Camera can orbit both sun and other bodies. WIP: view from earth.
- Solar rotation. Removed broken earthView. Tidying.
- Hack to fix prograde orbits.
- Tidied up unused textures.
2024-10-28 Orbital: A game-jam work in progress
The big event of the week was my second Recurse game jam (I wrote about my first one here). A few of us set a theme — this time it was Sp00ky
— and focussed as much time as we could over 3 days to working on our games, with blitz presentations to the whole of RC on Thursday.
I decided to mostly ignore the theme and double down on the space/orbit simulation I started last week.
You can try the current release live in your browser at gianluca-orbital.vercel.app or see the latest code at github.com/gianlucatruda/orbital
The idea was to evolve it into a full-blown orbital manoeuvres game where you complete puzzles (e.g. get from planet A to planet B) under certain constraints (mission time, delta-v). As it progresses, you’d have to make increasing use of clever gravity assists to achieve the mission within the constraints.
Unfortunately, I did my usual trick of being overly ambitious in my scope and then overloading myself with other commitments. I ended up only really getting around 8 hours of work on the project and ran into a number of bugs as I transitioned to a physically-accurate model.
One of the rabbit holes I fell down was attempting to migrate the physics engine to Rust with WASM bindings. In principle, this isn’t the worst idea, but I was pretty confident I was going to add a ton of complexity to the build and deploy processes. I justified that by it being a good way to work on more Rust, avoid writing vector operations in JS, and try out WebAssembly compilation. But ultimately I realised that the interfacing between Rust-WASM and the JS frontend is a lot of additional work and fiddling that is precisely the wrong thing to be doing under time constraints and a rapidly-evolving experimental project.
The second rabbit hole was trying to switch from a toy solar system to real-world physical accuracy. I’m still ultimately going this direction, but it introduced a load of additional issues and subtle numerical instability bugs.
The crux is this: the solar system is really big and really empty, but orbits are really fast and really precise. This is not a happy path for 3D scene animation or for 32-bit floats. It’s also tricky to visualise. I’m still working on the fixes in my dev
branch.
Ultimately, I didn’t get to implementing any gameplay or even any spacecraft orbital physics (a whole separate system to the Keplerian orbital model used for the stable-orbit planets). But I did have a fun 3D simulation with camera and time controls to present to the rest of RC.
Commit log:
- Readme with task list.
- Ignore llm.md
- Planet data refactored to own module.
- Orbit physics refactored to engine module.
- Tidy, format, tweak.
- Partially-working spacecraft.
- Working spacecraft added as an Earth ‘moon’
- Fixed camera tracking of satellites/moons and UI scaling.
- WIP: Data schema migration hell.
- Refactored planet creation to recursive.
- Switched to AU units. Better Tweakpane.
- Hacky camera tracking workaround.
- Fixed rotation time bug.
- Tidied. Prettier.
- Updated task list.
- Fixed orbit path drift
- WIP: More modular. Tweaked orbit path computations. Bug in path rotation
2024-11-04 Becoming an armchair physicist to make a game
I spend many hours studying orbital mechanics and applying the theory to my Orbital sim/game that I’ve written about the past few weeks. I mainly worked on streamlining the planetary data file and fixing bugs with inherited rotation in satellites, before moving on to work on dynamic orbit changes for spacecraft under acceleration. So far it’s working on my dev branch for elliptic orbits below an eccentricity of 1.
I also had a little diversion into loading a detailed 3D model of the ISS to replace the sphere I’ve been using as a stand-in. But that ended up being a pain because model loading in Three.js seems to use JavaScript promises, so I’d have to majorly refactor my code so that the model can fully load and be rotated/scaled appropriately before the script progresses. So I’ve tabled that for another time.
With all my space/orbital browsing, I recently rediscovered this 1-minute clip of the greatest shot in television and I can confirm it is still the greatest.
Commit log:
- Fixed divergence of satellites from orbit at speedup by using parent-relative positioning.
- Tidied and streamlined planetary data file.
- Fixed parent-child bug inheriting axial rotation.
- Reformatting with prettier.
- Updated task list.
- WIP: Calculate velocities of Kepler orbits.
- Experiment: satellite statistics calculation with real-time updates for ISS.
- Updated readme.
- Increased ISS orbital elements and adjusted camera maximum distance
- Tweaked ISS stats, updated tasks.
2024-11-22 Tinkering with GLSL shaders
I experimented with using a GLSL shader for Simplex noise as a cloud layer for Earth, but it didn’t end up looking very good and had a problem with wrapping from a 2D shader to a 3D sphere, causing an ugly seam. I’d have to write a shader that loops on boundaries to overcome this.
Commit log:
- Slightly adjusted atmospheric shader.
- Use vite raw-imports to modularise GLSL shaders.
- Primitive Simplex atmosphere for Earth (buggy and ugly)
- Basic passthrough shaders applied to Earth.
2024-12-13 Initial orbital maneuvers
Revisited the project after a hiatus to implement some bugfixes, improved physics engine, and initial orbital maneuvers that require mapping Keplerian orbits to position- and velocity- vectors, then back to Keplerian orbits.
Commit log:
- Dodgy broken calculateElements() on ISS burn
- Primitive test for engine
- Probably bullshit?
- Fixed elements-to-pv, but still not reversing.
- Broken: Better feedback to check units.
- Fixed: simulation step velocity != object velocity
- I am a genius
- Demo burn works (but some marginal errors in some params)
- Formatting with prettier.
- Merge pull request #3 from gianlucatruda/dev-burn
Try the live demo at gianluca-orbital.vercel.app to see the current state of master
branch, but it’s unrepresentative of the project’s current state. I plan to migrate to a different host soon for more granular demo versioning.