While working on this personal website, I wanted to create a home page background that matched the site’s design. My old website had an animated one, so I wanted a new one to see how I’ve grown as a programmer since then. I also wanted to experiment with creating experiences in WebGL.
The finished product turned out to be a lot like I originally saw in my mind, but not without overcoming some obstacles.
As I was looking for a concept, my friends and I drove to Lake Tahoe to go stargazing. As a native New Yorker, I was completely surprised. I had never seen that many stars before and the idea for a homage to stargazing became clear. After several sketches and plans in my notebook, I was ready to start coding.
When coding creative projects, constant reiteration is required to build experiences that look and feel great. I like to have a “playground” section of code to hack together ideas with complete disregard for strict coding practices. This way, I can quickly get ideas out into the environment and iterate on behavior and motion design. Once things look good, I can rewrite the section into production code. This has the additional benefit of already knowing how to structure my code, and saves a bit of refactoring later on.
The animation is divided into three main phases:
- A rotating globe
- A starfield, a set of slow-moving points that form connections with nearby neighbors
- A deer constellation formed out of the stars
All of the objects in the phases are composed of invisible nodes and visible connections. I keep track of the phases with a global
_stage object. Note how a
TRANSITION property is added. This stage is used in between the other stages to determine how to move the nodes to a new stage.
I worked on each stage chronologically.
The nodes that make up the sphere actually live in 2D space, but I had to use a 3D object to control the motions of the nodes.
I use three.js to generate a 3D sphere mesh, and then I project the vertices of the mesh into a 2D plane. Then I draw connections between all vertices within a certain distance of each other. The closer two vertices are, the brighter the line between them.
The code here is simpler, where I just move a set of nodes randomly. If a single node reaches the edge of the screen, it turns back in the other direction. The behavior for drawing connections is the same as the sphere’s behavior.
To transition between stages, we look at the next stage’s vertices to generate a set of
_goalPoints. Then, each current node we have is assigned to one point of the
_goalPoints and begins to move to its goal point.
I made the movement of the nodes feel more natural by following Google’s Material Motion Design Handbook. When the nodes start moving, they start from a slow speed and accelerate. When the nodes stop, they decelarate to a stop.
Once all of the nodes have reached their respective goal points, then the transition ends and we set
_stage to the next stage.
Drawing the deer out of nodes was the most complicated part. I modeled the general parts of the deer in Blender, and exported it into a JSON format for three.js to import.
When I had the vertices for the deer, I set the outside boundary points as the goal points for the transition. Then, I generated several uniformly random points inside the deer.
But how does one get a random point inside an irregular polygon? I used three steps:
- Generate an array of triangles that cover the same area as the deer. I found an open source polygon triangulation library called Earcut
- Randomly select which triangle to use, weighted by its area. For instance if triangle A is 75% of the area and triangle B is 25% of the area, triangle A should be picked 75% of the time and B should be 25%. The snippet below does this
- Select a random point within that triangle
After that, I added more random points to bring out the head and ears of the deer. The eye was completely filled in, and I manually drew out a few lines in Blender for the snout.