Continuing Adventures With Quil, ClojureScript and Matter.js
Written by Bunkers on March 13, 2017
Yesterday I introduced the experiment I was doing recreating Daniel Schiffman's Matter.js tutorial using Quil, and ClojureScript. I only managed to explain the project setup (using Figwheel for live reloading etc) before realising that I would need to break the post up in to parts.
Today I'll put my equivalent code for the whole video on GitHub, but discuss the main gotcha I had and my eventual solution.
Fun-mode and Time
The demo sketch that gets created when you use the Leiningen template sets up Quil to use the fun-mode
middleware. Essentially that means that one of the arguments to all your Processing functions is a state object, and your functions should return the updated state. This turns it in to a bit more of a functional style. The functions you define that manipulate the state can (should?) be pure, and so you could keep track of the state objects allowing you to move backwards and forwards through the time line.
Matter.js provides a simulation of physics. This simulation gets updated by telling it how many milliseconds have passed. In the tutorial Daniel just calls the run
method on the Engine
object which updates the simulation at a default constant time step of 16.66 milliseconds (or 60 frames per second).
This was my first big mistake of trying to run before I could walk. Instead of just doing the same thing (which would have worked fine), I decided that as this didn't fit the functional model I would manually call the update
method on the Engine
object, each time Quil calls the update-state
function. More than that, I decided to work out the time delta between calls to the update-state
function and use that. What could go wrong eh? Well, it turns out a fair amount!
The simulation ran ludicrously quickly. The box created would fall straight off the bottom of the screen, and always straight through whatever static body I had created to represent the ground. This is a problem that you can see in the video too when boxes pick up too much speed, which Daniel overcomes by creating a thicker floor.
This problem was baffling to me, so I decided to stop passing in a value to the update
method and seeing what happened. To my surprise, everything ran fine. More experimentation showed that any constant value was also fine, but would obviously speed up and slow down the simulation. Delving further in to the documentation I saw that there is another optional parameter to update called correction
. It's specifically for the situation when you're passing a variable value in as the time delta, and is essentially a ratio between the current and previous delta. The idea is that if you're attempting to get 60 frames per second and are only getting 10 then the simulation can speed up. I started calculating the ratio and passing that in, but still no joy.
The reason, it turns out, is how I was handling the initial state. I noticed during debugging that if I adapted some other code and had Figwheel restart the animation, that sometimes it would work correctly. This tipped me off to the possibility that the problem was being cause by a large initial delta. I stored the initial millisecond value during the setup function, which is always zero. However, as the page loads and initialises, it can sometimes take 300-400 milliseconds before the first update-state
function call.
I didn't have a previous value to use to calculate the correction
parameter so I was passing in the default of 1. This effectively sets the simulation to an initial frame rate of about 2-3 frames per second. I think it would correct itself over time, but not quick enough to stop the box from falling through the floor.
There's likely to be better solutions to this, but I took the approach of passing in 16.666 for the initial delta no matter how much time had passed. This creates a simulation that works as expected.
The Power of Hindsight
I managed to give myself a huge headache here by trying to be too clever. I should have simply used as close to the same code as possible from the tutorial and then slowly adapted it to this more functional model. I learnt a lot but it was an unnecessary lesson for a newbie like me!
Purely Functional
You may have noticed that a reliance on the sketch running time in the update functions makes them not pure. I would like to change this so that Fun-mode
includes the milliseconds elapsed in the state object that it passes in to the functions.
The Matter.js library isn't functional friendly in this regard either. I think it's crying our for someone to create a wrapper for it. The addition of a set-elapsed-time
function for the engine to move arbitrarily back and forth through the simulation would be great. The state of the simulation and the bodies in it could be made available via a single state atom in much the same way as a number of the React.js
wrappers out there.
What's next?
I had some other decisions to make about how I store the state for the sketch which I'll write up as well. I'd also like to continue with the development of this demo as in Daniel Schiffman's subsequent videos. Having something like this working does open up a lot of possibilities for developing games and little art projects. Watch out for my new Angry Badgers game, it's going to be a big hit!