Attractive 3D with ClojureScript
What I learned from combining three.js and ClojureScript
The three.js library has an object-oriented API and as such is not a great fit for ClojureScript. You are left to your own devices to keep the state in your ClojureScript model synchronized with the state in the three.js object-oriented model. These are problems that React solves elegantly for the DOM. But with a little effort you can do powerful stuff with three.js through the super-powers of ClojureScript.
3D on the web
I've always been fascinated by 3D programming and have had a lot of fun with OpenGL and Unity 3D. One day I stumbled upon the three.js 3D library and was blown away by all the amazing examples. I wanted to learn more and thought I'd try to add another dimension to my 2D canvas attractor project.
Before I started I tried to find inspiration from others that had used three.js together with ClojureScript but came up lacking. So hopefully sharing my experiences will be valuable.
Here is the result, you can move the red ball by clicking or tapping.
As you can read in the source code, the challenge here was figuring out a way to keep the ClojureScript model in sync with the three.js model.
I opted for an approach where a unique identifier is assigned to every entity in the ClojureScript model. I then populate the three.js model with objects and assign the same identifiers to them. When drawing a frame, the code iterates through the elements in the model and updates the corresponding object in the three.js model. This allows me to manipulate the model as ClojureScript data structures.
(defn animate [model scene] (doseq [ball (conj (:balls model) (:attractor model))] (let [mesh (.getObjectByName scene (:id ball))] (view/move-mesh! mesh (:pos ball)))))
I considered an alternative solution where I would clear the scene and add new three.js objects at every frame but this would probably be too costly performance wise. It would be interesting to try it out though.
- Write glue to improve the three.js + ClojureScript marriage
- Make code reloadable through figwheel. I just have to clear the three.js scene and add all objects again every time the code is reloaded, should be simple.
- Submit a pull request to the three.js externs file in cljsjs, adding the missing parts required to be able to compile in advanced mode