Three.js + F# + Fable = ❤

Volha Samusik
11 min readDec 5, 2018

--

Intro

Fable is one of the most exciting things that happened in the F# ecosystem in the past few years. It is a bridge that connected F# developers to the huge world of JavaScript, made everything it offers accessible while allowing them to keep coding in their favorite language.

No wonder that by now, pretty much everyone who uses F# for fun & profit and does web development, has already tried Fable and played with SAFE Stack, talked/blogged about their experiences, used these technologies in production successfully, created related tools and libraries or contributed to them. I’ve tried and played as well, however, haven’t yet produced anything useful for others (neither Fable nor F# in general). F# Advent Calendar seemed like a good opportunity to fix that by putting some real pressure on me, so I decided to participate and do something Fable.

This post is a story about my journey: choosing what to do, showing the results, the process, and lessons learned. In the end, I share a step-by-step guide on how to do something similar, along with the links to helpful resources. So, if you don’t feel like reading the long story part, feel free to skip it and jump to ‘Results’ and then to ‘Want To Do Something Similar? Here is a Short Guide’.

Choosing the Topic and Getting Started

To back up my choice a bit. I have been interested in computer graphics for a while, though not in a nerdy, knowing-the-math-behind-it way, but rather in a geeky, playing-with-existing-tools way: had my fun with 3Ds MAX in high school, created a very simple Mesh object viewer in C# using Direct3D as a mid-term project in college, and my graduate project was an attempt to restore three-dimensional objects based on pictures of them taken from different angles. After that, there were 8 years of enterprise web development, and 3D fun has become just a faint but pleasant memory. So hey, why don’t I do something CG-related here? Even doing a small step in that direction would be sweet.

I started googling around and stumbled upon fable-graphics repository, where contributors have created Fable bindings based on Typescript definitions for a bunch of existing JavaScript CG libraries and ported some examples from JS to F#. I noticed that the repository contains the bindings for Three.js, a JavaScript 3D library, but has no examples implemented for it. I thought, ‘Perfect! I’ll then go ahead and port a few examples from JS to F# with the help of Fable!’

I looked more at Three.js, and my mind was blown by it. As I haven’t been closely following the evolution of CG programming for a while, I only had a vague idea of what is possible now, in 2018. Wow! Just look at this sample, for instance. Or this one. It all works in the browser without lagging, and not much code is needed to create such things! This seems even more amazing to me than WiFi on planes.

Having cloned the repository, I tried to build and run the code, but failed and then found out that the repo is pretty much dead now. Comments in the GitHub issue led me to the newer repos, one with the new bindings and one with Pixi Samples (it is a neat 2D graphics library). No new Three.js bindings were there though, so I needed to generate them on my own, which was fine, as I knew that a tool that could do the job existed.

TS 2 Fable to the rescue! If you install this tool, find the TypeScript type definitions for your JS library here in the DefinitelyTyped repo, download them and feed to the tool, it will generate F# ‘interface declarations’. They will allow your F# code to understand that these types exist, and will give enough knowledge to Fable so that it could convert your F# code that uses the library into JavaScript code that references the things from the library correctly.

As I opened the TS definitions for Three.js, I got scared for a bit. Tons of files. What if ts2fable cannot do the generation for multiple files with inter-dependencies? Luckily, it can since not too long ago. Hooray!

Having installed ts2fable and downloaded the type definitions into ‘three’ folder, I ran the following command:

ts2fable three/index.d.ts TestCompile/ThreeJs.fs -e three

You have probably guessed that index.d.ts file is the ‘entry point’ file. As a result, I got a huge bundle file with 6K+ lines of F# code. I included this file into a test project, and it compiled successfully after I made a minor fix of adding ‘interface end’ to empty interface definitions. Pretty cool! However, I ran into an issue while trying to run the application. The errors were of the following kind:

FABLE: Cannot have two module members with same name: Camera

And many more like that. It turned out to be a BABEL bug (or limitation ¯\_(ツ)_/¯). I looked at Three.js type definitions and found out that there is a Core module, and types from it are used in other modules with aliases, which apparently leads to a mess when Fable -> BABEL -> JS things are happening. I thought, ‘OK, what if I find the simplest sample possible and use Core part only?’ Commented everything out except for Core — nope, not going to work, as other modules depend on Core, and Core depends on other modules — lovely! And oh, that is why the namespace in my huge generated F# definitions file is declared as rec, which by the way leads VS Code to go crazy and hang every once in a while (I don’t blame it! 6K lines, rec…). I ended up throwing this file into a gist to have syntax highlighting, and using it to look things up, as I was not going to keep it in the project anyway.

Given the above, my goal in this adventure has become the following:

  • Take not the coolest, but the simplest sample than only uses Three.js and nothing else, because time -> 0
  • Looking at the JavaScript code of the sample, use the ts2fable output file as a reference and find only the types I need for the sample, with only the fields and functions I need, and create my own, minimalist version of F# bindings file
  • Port JavaScript to F#, keeping everything as close to the original sample for transparency, and make things work.

Results

The sample I ended up picking is the following one: camera_array. The idea is to display a scene where N instances of a 3D object are rendered in an animated fashion, turning around. There is also a basic light set up and a background texture that helps to demonstrate the shadow the object casts as it turns, being lit by the light.

Output, Visual Part

I have changed the sample a tiny bit for fun. Just tweaked the cylinders’ dimensions to transform them into cones (these are Hershey’s kisses!) and modified the colors used. This is as ‘Adventsy’ as I could get it without using textures or custom models. Chocolates and Christmas colors!

25 golden Hershey’s kisses dancing in the air over a surface covered with 25 square pieces of poker tablecloth, lit by a red light

In case you haven’t seen these golden Hershey’s kisses in real life, here’s what they look like:

Golden Hershey’s Kisses IRL. Yum!

Output, Code Part

F# code of the ported sample was made as close as possible to the original JavaScript sample. I admit it’s not idiomatic, is pretty much the opposite to ‘F# code we love’ (don’t show this code to your kids! please), and contains some hacks that I’ll go over in the next section. The only nice thing about this code is that it’s good for learning purposes, as it allows to look at the original JS, F# code and generated JS, and easily see what is going on.

I have pushed the code for the sample here: w0lya/fable-three

The project is based on the minimal template for Fable 2, so if you follow the instructions in the template, everything should work for you.

And here is the ts2fable output in a gist: Fable.Import.Three.fs

Process

After I got all the setup and preparation done, the implementation itself was pretty straightforward and not as full of adventures as the getting started part. Just had to figure a few things out.

As the initial JavaScript logic is implemented in an imperative fashion, I had to follow that style in F# to ‘mimic’ it.

  1. The first thing I faced was being able to create objects using constructors with parameters. If I had the type, not interface definitions, it would have been pretty straightforward. For interfaces, I ended up doing the following:
// Out initial type as-is.
type [<AllowNullLiteral>] Vector4 =
abstract x : float with get,setabstract y : float with get, setabstract z : float with get, setabstract w : float with get, set// Add this as a constructor with optional parameters:
and [<AllowNullLiteral>] Vector4Type =
[<Emit("new THREE.$0($1...)")>] abstract Create: ?p1:obj * ?p2:obj * ?p3:obj * ?p4:obj -> Vector4// Further in the type definition file, add a type with global exports for 'constructing' your objects.
type [<Erase>] Globals =
[<Global>] static member Vector4 with get(): Vector4Type = jsNative and set(v: Vector4Type): unit = jsNative

Given that, I can write the following code:

subcamera.bounds <- Globals.Vector4.Create( (x / amount, y / amount, size, size )

And get the following JavaScript as a result, which is exactly what I need:

subcamera.bounds = new THREE.Vector4(x / amount, y / amount, size, size);

You have probably noticed [<Emit>], [<Erase>], and [<Global>] attributes in the code. They are pretty intuitive: the first one overrides what JS code is being generated based on your F# code, the second one marks things to ignore when generating JS, and the third one denotes the things that are to be globally accessible in your resulting JS. To learn more about these, check out the links I share at the end of the post.

2. The second thing that caused a bit of trouble was that in the original JS, there are a few global variables. Unfortunately, I had to overcome that issue using not the nicest kind of Emit hackery. My problem was that if I declare the global variables this way,

let globalCamera = Globals.ArrayCamera.Create()

I end up getting this in JavaScript:

const globalCamera = new THREE.ArrayCamera()

If I add ‘mutable’, the global declaration is then treated as a function, and if I try to set some properties on it, I get

globalCamera().propertyname = ...

which is not what I want.

To overcome this, I had to define global things 2 times, one to make JS happy and to actually have vars there, and one to make F# happy:

// 1) To actually have vars in JS
[<Emit("var globalScene, globalMesh, globalCamera, globalRenderer;")>]
let globalVarsDeclaration: unit = jsNative
// 2) For F# not to be sad. [<Erase>] helps to exclude this stuff when generating JS.let [<Erase>] globalCamera = Globals.ArrayCamera.Create()let [<Erase>] globalScene = Globals.Scene.Create()let [<Erase>] globalRenderer = Globals.WebGLRenderer.Create()let [<Erase>] globalMesh = Globals.Mesh.Create()

With the above, I do get the correct declaration, but if I reference these and try to set some property on these objects, in JavaScript output, I am getting the following:

Object(_fable_fable_core_2_0_10_Util__WEBPACK_IMPORTED_MODULE_0__["equals"])(globalScene, scene)

And of course it doesn’t work :) This led me to more questionable hackery. I declared a local camera, scene, mesh and renderer inside the init() function, as unlike global, local ‘let mutable’ declaration results in reasonable JS and allows setting the properties as needed. Having the local objects set up properly, I then assigned them to the global variables 2 times each, one through Emit (for JS), one just plain assignment (for F#). I am not adding code snippets for this part here not to overload the post, but if you are curious, it’s all in the App.fs file. Though, I am pretty sure that a nicer way to overcome these issues exists.

Other than that, there were bits in the type definitions to be tweaked a bit, e.g. a method missing, or optional parameters needed, or a base type needed. But overall, I was able to get things working relatively painlessly.

Tools

Here I will just list the tools I was using. I was doing everything on Linux (Ubuntu 18.04), so it affected some of the choices.

  • Visual Studio Code with Ionide
  • GitKraken
  • Chromium developer tools
  • gitzip to get small pieces of large repos (e.g. three Typescript bindings)
  • ts2fable, both as a terminal tool and online
  • new Fable 2.0 Online REPL
  • legacy Fable REPL because the new REPL would output things that scared me. e.g. this:
let returnVal$$1 = Mesh$$$$002Ector();Mesh$$set_geometry$$Z47ACBE6C(returnVal$$1, geometry);

However, as my resulting JavaScript became debuggable, I stopped using the online REPL and simply switched to debugging the emitted JS in VS Code with hot reload, which was just a great dev experience. By the way, using Emit to insert debugger statement can be useful here.

Want To Do Something Similar? Here is a Short Guide

If you are feeling adventurous and would like to port one of the more exciting Three.js samples (or do a similar thing with any other JS library that has TypeScript definitions available), here is a recap.

Steps

  1. Find your library’s TypeScript definitions here and download using a tool like gitzip
  2. Install ts2fable, in the directory with TS stuff run the command (see the readme for the right one for your case)
  3. Create an F# project, e.g. by copying and using the minimal template for Fable 2. Add the F# TS file there and reference in the .fsproj. You can either check that the file is good in the project by building and running it, or throw the file into online REPL and try running it.
  4. If you are not lucky, you will have to create your own type definitions manually, tweak them to support constructors, correct JS emission etc. :)

5. Don’t forget to include the JavaScript library, as a NuGet package or a CDN reference.

6. Write your F# code in App.fs and make sure it gets transformed into working JS :)

Useful Links

Possible Next Steps

Possibilities are endless. For me or anyone who got excited about this too, here are a few ideas to consider implementing:

  • Create a full-blown SAFE stack-based app with the 3D awesomeness in it.
  • Add interaction with the user, make the app configurable: change count, color and what not. This could even become a Dojo?
  • Re-arrange the logic flow and make F# code look more like F# code we love than the code we don’t
  • Think about how to overcome the Babel naming-related bug. how can I automagically re-generate the type definitions, so that they wouldn’t have this issue, and at the same time the JS output would be instantly usable, without having to do namespace hackery. This would be amazing, as it would ‘unlock’ the entire Three.js library for usage from F#!

The end.

Any feedback/questions/comments/criticism/trolling will be very much appreciated :) Feel free to bug me here or on Twitter.

--

--

Volha Samusik

Software engineer, amateur dancer, occasional artist. Belarusian.