Home
One-A-Day 1: 2d Physics Simulation in Clojure
24 March 02021
In Kara Swisher's recent interview with Beeple, the artist behind the $69 million sale that brought NFTs into the mainstream, he mentioned that he started with a routine of doing one drawing a day for a year. So in that spirit I'm going to do one project a day and post about it in a dedicated section on my blog. I'll start out doing it for a week and keep doing it if I like it. I'll try to make them as accessable as possible, favoring code that has an output that you can easily see.
For my first one-a-day is building simple two body physics simulation in clojure. On first glance a functional programming language would make a less ideal candidate for a physics simulator since object oriented programming lends itself to keeping track of and updating multiple physics objects. Luckily, I was using Quil, which is a graphical library for clojure and more imporantly has the ability to track and update a sketch's state. The state acts basically like a hash table, and the big disadvantage of using a hashtable for keeping track of objects' states is that it is a flat structure. By that I mean it looks like {'position1': [42, 10], 'position2': [12, 64], 'velocity1': [14,100], 'velocity2': [23, 15]}, which makes it difficult to scale up since the information for all your physics objects is housed in one variable.
How quil works is that it passes the state between two functions, draw-state and update-state. Draw-state does essentially what it says, it draws whatever you tell it to into the sketch using the values in the sketch. In this case it draws two circles, one at state['position1'] and the other at state['position2']. Update-state is where you calculate the force on each point mass and use that to update the state's position and velocity, which is just newton's laws of motion.
If I had more time to work on this and wanted to improve it I would:
- Add a normal force to stop the point masses from slingshotting when they get too close.
- I had to use a non-extensible method since map returns a lazy seq when I wanted a vector, so I had to write something like position = [v[0] + f[0]*dt, v[1] + f[1]*dt] instead of (map + v (map * f (cycle dt))).
- This would have been an easy fix but I should have made the timestep one over the framerate instead of a fixed constant, so that the visual speed of the objects isn't dependent on framerate.
Lesson learned:
- Functional programming for physics simulations is probably a bad idea outside of some niches. According to this stackoverflow answer Lau Jensen did some fluid simulations in clojure, so it's definitely possible and there is probably some other niche that I'm overlooking where functional programming would be useful.
Future project ideas:
- The game galcon, but graph theory
- Is there an optimal algorithm for the game for an arbitrary graph?
- If the sides had an equal number of nodes does it result in a perpetual stalemate?
- An implementation of elliptic curve cryptography
- Implementation of the SIR epidemic model