Creating Visual Planetary Systems using Fable and F#

Introduction

In this blogpost, we’ll be continuing our Astrophysics journey into the world of Functional Programming with F#. Previous posts can be found here and here.

We’ll be making use of Fable and its associated Three.js library to generate a planetary system on a webpage that looks something like the following:

Gif of the End Product of this Blogpost

Fable takes F# code and extremely intelligently uses Babel to emit some beautifully created JavaScript.

Fable Logo

The first step to get this working is to render and animate these celestial bodies with some help of Trigonometry on some hardcoded values.

Subsequently, in a future post, we’ll work on parametrizing the inputs and use previously tested functions from AstroSharp.Core to take our PlanetaryInfo object types from before, poke into them and generate mathematically sound planetary systems from these values.

Setting Up

To get things moving fast, I cloned the repository containing samples of Fable projects after going through the preliminaries of downloading .NET core, npm and friends; details of which can be found here.

I followed the instructions of Progletariat’s extremely helpful blog post that can found here to get started. I began by gutting the contents of App.fs to start with a blank slate in the WebGL example in the repository that already contained a reference to Fable’s flavor of the Three.js library.

To build, I invoked the following at the top level directory of the sample project:

sh restore.sh

and then to run, used [ changes to App.fs file are automatically picked up ]:

dotnet fable npm-run start

We can, then, navigate to the following link to see our page in action [ more like equilibrium than action ]:

http://localhost:8080/webGLTerrain/public/index.html

Three.js Basics

Let’s dive into the the basic building blocks of Three.js before we concoct the universe based on hardcoded values shown in the gif above.

Three.js is built on top of WebGL, a JavaScript API for rendering interactive 3D and 2D graphics within any compatible web browser without the use of plugins.

The main components interplaying in the Three.js world are the following:

  1. Scene: The Scene is the component composed of Meshes, Lights and / or other objects; we add objects to the scene to render and animate our webpage.
  2. Camera: The Camera is our view into the scene. We’ll be using the Perspective Camera that is designed to mimic the way the human eye sees.
  3. Renderer: The Renderer renders the page on the basis of the scene and camera. We’ll be developing a render loop which is continuously called to draw the constantly animated page. We’ll be using the WebGLRenderer that displays the scene and camera using WebGL.
  4. Objects: Objects are components that are added to the scene; these are the actors of the scene we are trying to portray. Lights are objects that provide custom illumination of different parts of the scene. We’ll be using Ambient Light that globally and uniformly illuminates all the objects in the scene. Additionally, we’ll be using the Mesh and Particle objects to describe our planets, star and star field.

Some of the relationships we are going to inculcate are the following:

Renderer = Scene + Camera
Scene    = Σ Objects [ Object : { Light, Mesh, Group, ... } ]
Meshes   = Geometry + Material 
Geometry = Vertices + Faces

For the sake of completeness, the hierarchy, overall, this looks like the following:

Source: http://12devsofxmas.co.uk/2012/01/webgl-and-three-js/

Scene and Renderer

We first initialize the scene by defining a simple function called “initScene”:

Similarly, we’ll add the “initRenderer” function that creates a WebGLRenderer and appends the renderer to the DOM as a child. We’ll see below how we hook up the renderer to call the render function on the requestAnimationFrame function to display our scene.

Camera

As mentioned before, we’ll be using the Perspective Camera that mimics the human eye and construct it in the following manner:

The input parameters, in order, to the Perspective Camera are the following:

fov    — Camera frustum vertical field of view.
aspect — Camera frustum aspect ratio i.e. Width / Height
near — Camera frustum near plane.
far — Camera frustum far plane.

The frustum, as per Wikipedia, is the portion of a solid (normally a cone or pyramid) that lies between one or two parallel planes cutting it.

In simple terms, it is the 3d region visible on the screen.

This picture was a better insight into truly understanding what Frustum is than the Wikipedia definition

We position the camera higher up on the z axis to get some what a bird’s eye like perspective of our revolving planets.

For reference, the coordinates of Three.js are the following:

I played around with the numbers to arrive with the final ones like a majority of the hardcoded values. These will make sense once we add the objects to the mix.

Lights

We’ll add an Ambient Light in to uniformly light up all our components.

The parameters of the Ambient Light constructor are the following:

color     — Union case value of a float or string of the RGB component of the color.
intensity - Numeric value of the light's strength/intensity. [ Default = 1.0 ]

Geometry: Star

Now that we have setup the basic structure, we’ll finally have something tangible to demonstrate after adding our Star.

Let’s add the ‘createStarMesh’ function.

We add a Sphere Buffer Geometry that takes in a radius, widthSegments and heightSegments [ others too i.e. defaults provided for them, but these are all we need ].

Remember, Geometry + Material [ Made up of Textures ] = Mesh? We used that very relation to set up our star that’ll be chilling right in the middle of our scene.

We download a texture of the sunmap from here, add it to the public folder and load it into the map property of the MeshPhongMaterial that we are going to use for our spherical objects. The MeshPhongMaterial is the material used to represent shiny objects, which seemed like a reasonable match for the Star and Planets [ because they reflect off the light of the Star ].

Displaying the Star

Now that we have the Star defined, we want to see what this looks like in our browser.

For this we first write the ‘initGeometry’ and ‘action’ method and then tie it with the ‘render’ and ‘animate’ function to run the rendering in a continuous loop.

The purpose of the initGeometry function is to create and add all the objects to the scene once they are defined and hence, this function takes the scene as an argument. This method then, via a tuple, returns all the defined geometries.

The action method initializes and returns all the required components. The return type is a tuple of 4 elements. We create the globals components by calling the action method and unpacking the tuple.

We add the render function that simply sets the camera position as that of the scene’s and gets the renderer to render the scene and camera. We then add the recursive animate function to the Window’s requestAnimationFrame calling itself back for each subsequent animation on each browser repaint; more details of the Javascript function can be found here. We then call the animate function @ t = 0 to get initiate the render looping.

And then, there _should_ be light:

Nah, more like:

Let’s get the star rotating on it’s own axis. This is done in the render function that’s called in the loop by adding some float to the rotation property of the Y axis of the star.

Our modified render function now looks like:

Making our webpage looking like:

Geometry: Planets

We start off with a bit of Math described in this picture; I don’t want to this section to get too mathematically dense so, I’ll be briefly skimming over the calculations involved and get into the practical aspects soon of generating planets without much ceremony.

Source: https://www.miniphysics.com/uy1-uniform-circular-motion-non-uniform-circular-motion.html

Where m is the mass, g is gravitation acceleration, θ is the angle between the base component and the undissolved gravitational force vector i.e. mg and R is distance between the star and planet.

To replicate the circular motion, we break up the vector components into mgsinθ on the x-axis and mgcosθ on the z-axis. We’ll, therefore, update the position along the x and z axis according to this calculation. More details about vector resolution can be found here and a good primer about circular motion can be found here.

Additionally, we want to replicate the planets revolving on it’s own axis and this is done by revolving the y-axis by a certain amount like we did in the case of the star.

We grab a generic texture of a planet from here and save it, again, in the public folder as “moonbump.jpg. You can try this method of adding a texture to a material’s map with any other appropriate image.

The parameter, n, is the unique number of the planet that’s used to position the planet relative to the star. Again, these values are obtained via trial and error.

The rest of the updated code looks like:

A bunch of things are going on here that could be confusing. In the initGeometry function, we are currently hard coding details of 3 planets and adding to the scene, equally sized and spaced out planets based on the unique number of planet.

Down at the render function, we are readjusting the x and z axis position of the planets, one at a time, based on the calculations we did above.

Variating the mg component controls the position. Increasing or decreasing the θ component controls the speed of rotation.

We initialize the mutable ‘t’ or time period float to 0 and update it each time the window is repainted by our rendering loop by adding a component of π / 360 along the way.

y = mg * sin(θ) = mg * sin(ωt) = mg * sin(2πt / T) 
z = mg * cos(θ) = mg * cos(ωt) = mg * cos(2πt / T)
t <- t + π / 360
speed = distance / time 
and, angular speed = angular distance / time period

We can go on adding the value of t due to the periodic nature of sinusoidal functions that would wrap back.

The results are AMAZING:

Since this is a gif, I could only capture only a section of the rotation. Video of this can be found here: https://vimeo.com/225749500

Finally figured out a use case of Trigonometry from High School, jk.

Geometry: Star Field

We employ the use of Three.js’ Points object to create 20,000 generated particles randomly positioned and dispersed across the scene.

To highlight this process a bit more, we create 20,000 Vector3 objects, randomly position them and then, add them to the vertices generic Three.Geometry that’s returned from this createStarFieldPoints function. We multiply the 4000 to scale out the random values to try to fit the entire window.

We don’t need to access the star field after adding it to the scene and so, we don’t return it in the initGeometry function.

Here is the result; adding the Star Field really adds character to the scene:

All together the code, cleaned up a bit, looks like:

Conclusion

We used the awesome power of Fable and Three.js to render a beautiful scene of rotating planets, a star and a background Star Field.

Right now we are hardcoding the planetary parameters, but the next step would be to make use of the DSL to generate these values for us that our functions process to create

Goes without say that any feedback will definitely be appreciated. It’s been a fun weekend hacking on this; initially, it was a bit of a climb trying to digest all the information and produce something worthwhile but once I saw the intermediate results, there was nothing better!

Kudos to the guys who developed Fable! I had a great time learning how to use it.

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.