Foo Fighters Celestial Navigator

Building a constellation viewer for the browser

Lee Martin
7 min readAug 25, 2017
UX example

About a month ago, I announced my departure from Songkick and availability for freelancing. Lucky for me a bunch of past clients have new records coming out this Fall, including the Foo Fighters with “Concrete and Gold.” 🤘🏻 Having just spent a week building a simple eclipse app, my brain was spinning with celestial math. As it turned out, so was Grohl’s. Dave was putting together a treatment for a new music video for “The Sky is a Neighborhood” which features the band playing on the rooftop of a cabin in front of a starlit sky. In addition to writing the treatment, Dave had an interactive idea. In his own words:

We should do a performance video that ties in to that
So it’s a video of us performing
But when you point it at the Sky, it shows the actual constellations behind us
So it works just like those apps
But we’re in the foreground playing

It sounds like Dave wants to clone a constellation viewer but put the band’s performance in the foreground. I dig it. In an attempt to make this experience as accessible as possible, I decided to build it as a mobile browser application. If it’s dark enough where you live, try it out tonight and continue reading to learn how we pulled it off.

The Celestial Sphere

Remember when people thought the earth was flat? No, not now… 😓 I’m talking about ancient times. There was another rad idea those days: that the stars were attached to or holes on a very large sphere far in the distance. Naturally that sphere was a barrier into heaven… Still following? While this thought was a sign of the times, the idea of plotting stars, galaxies, and even our own sun on a celestial sphere is an excellent idea.

We can think of the celestial sphere as an imaginary sphere of massive radius with the earth located at it’s center. Both the poles and equator of the celestial sphere are aligned with those of the Earth. We plot celestial objects using a coordinate system similar to that of earth.

Let’s build one.

Building the Celestial Sphere

Since I knew I was going to build this application using three.js, I needed to figure out a way to plot the sky’s constellations onto a sphere. Lucky for me Olaf Frohn had created a D3 library aptly called d3-celestial. Thanks Olaf! While D3 wasn’t what I was after from a visualization perspective, Olaf’s work translating constellations into the GeoJSON format provided a strong foundation for plotting objects based on a coordinate system. In addition to constellation lines, Olaf has created data files for boundaries and locations.

Following the advice of this GeoJSON in Three.js article by Mike Bostock, I converted the GeoJSON to Topojson using topojson to remove visual artifacts from the meridian. TL;DR I removed lines passing through the constellations.

Begin the three.js structure normally by initializing a scene, camera, and renderer. You can then use jQuery or something like Preload.JS to import the JSON data.

$.getJSON("constellations.json", function(data) {
// Universe
});

From within this JSON import call, we’ll call the wireframe function, passing the topojson topojson.mesh function the data we’ve imported. This function will mesh TopoJSON geometry and convert to MultiLineString lines. We’ll also initialize a basic material to establish the aesthetic of our constellation lines.

celestial = wireframe(
topojson.mesh(astrims, astrims.objects.collection),
new THREE.LineBasicMaterial({ color: 0xFFFFFF })
)
scene.add(celestial);

Let’s take a closer look at what’s happening in this wireframe function. Given our freshly meshed TopoJSON, we’ll iterate over every coordinate pair for each of our constellations and convert those coordinates to THREE.Vector3 so we can plot them in 3D space as lines.

function wireframe(multilinestring, material) {
var geometry = new THREE.Geometry;

multilinestring.coordinates.forEach(function(line) {
d3.pairs(line.map(vertex), function(a, b) {
geometry.vertices.push(a, b);
});
});

return new THREE.LineSegments(geometry, material);
}

At this point we should have a sphere mapping of our constellation data. Since we can only see the northern hemisphere of our celestial sphere at any given time, it makes sense to place a camera at the center of our sphere facing up. This has the added benefit of being a great way to test our visualization against something like AstroView’s sky map image generator. This will prove crucial later on as we begin to rotate things.

camera.position.y = -200;
camera.position.z = 180 * Math.PI / 180;
camera.rotation.x = 90 * Math.PI / 180;

For no good reason besides it looks cool, try connecting your three.js scene to some trackball controls. You can now explore the celestial sphere using mouse gestures.

controls = new THREE.TrackballControls(camera);

Don’t forget to remove this once you’re done messing around.

So we were able to plot the constellations on a sphere and place a camera facing up toward the northern celestial hemisphere. This serves as a great base for all of our fans at the north pole, but we’re going to need to rotate the celestial sphere for everyone else.

Rotate the Celestial Sphere

The celestial sphere must be rotated to match our current user’s night sky. In order to do this, we need two variables: their coordinates and current time. In order to pull the current user’s coordinates, we’ll use the HTML5 Geolocation API to locate the user’s position.

if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(function(position) {
// position.coords.latitude
// position.coords.longitude
});
} else {
// Geolocation is not supported by this browser.
}

We’ll then use the latitude to rotate the celestial sphere around it’s x axis.

celestial.rotation.x = (latitude - 90) * Math.PI / 180;

We’ll also need the percentage of day passed which you can get with a bit of Date() logic. Using the percent of day alongside a required offset, we‘re able to rotate the celestial sphere around it’s y axis and in turn orientate the sky to match the user’s sky.

var d       = new Date();
var percent = (d.getHours() / 24 + d.getMinutes() / (60 * 24));
var offset = 45;
var angle = (percent * 360) + offset;
var rotate = - angle * Math.PI / 180;
celestial.rotation.y = - angle * Math.PI / 180;

Similar to earlier testing, you’ll want to point the camera up and check your location on the Astroviewer. If things don’t match up, take a closer look at your rotation math. I required a white board to get things working correctly.

Stars

I want to write an entirely separate article on the stars themselves because it turned out to be a massive learning experience for me. I’ll cover topics such as the B-V color index and three.js shaders. Stay tuned for part 2.

Device Orientation

We can gain access to the motion information provided by a device’s gyroscope and accelerometer using the HTML5 DeviceOrientation events. However, mapping these events to the three.js camera can be a bit disorienting but lucky for us, three.js provides a library which does exactly that. Similar to the THREE.TrackballControls from earlier, we’ll simply want to connect these new controls to our camera.

controls = new THREE.DeviceOrientationControls(camera);

Now when you fire up your application on a supported device, you’ll notice that when you move your device the camera we placed within the celestial sphere positions itself as well. Pretty rad but there is still one problem. We need to figure out which way is north.

Facing North

When a user’s device first calls the device orientation function, it thinks that north is whichever way the user is facing. As you can imagine, not knowing which way is north, completely throws this whole experience off.

THREE.DeviceOrientationControls comes with a handy alphaOffsetAngle function to remedy this situation but we cannot simply connect the controls to our device’s compass. Not surprisingly, when you’re orientating your device in a bunch of random positions, the compass just can’t keep up and becomes easily disorientated. This makes for one dizzy camera. Since we really only need to make sure the user is facing north once, I decided to make it part of the onboarding rather than trying to force a complicated technical solution. After sharing their geolocation, the user must use a simple compass to face north before they are able to proceed to the celestial navigator.

window.addEventListener("deviceorientation", function(event) {
// event.webkitCompassHeading
});

As long as the user is facing north when DeviceOrientationControls is enabled, everything will work nicely. Since the solution I mentioned above only works on iOS devices, I actually removed it from the final build and simply asked our user’s to face north. Close enough for rock and roll. 😅

Transparent Video

You’d think all this celestial math would be the hardest part of the project, right? Well my friend, you would be wrong. Trying to autoplay a transparent video inline was the real miracle here and I’d love to see Aristotle’s solution to the problem. Luckily, this article Greg Dorsainville presented a working solution that I adopted for our experience.

First you create a single stacked video which contains both the RGB and Alpha videos. Then you add that video as a source to a hidden <video> tag. Finally, as your video plays, draw the image data to two canvas elements: buffer and output. Buffer will then get the alpha information and reapply the transparency to the RGB footage, resulting in a transparent video being drawn to the output canvas. This is the canvas you show the users.

output.drawImage(video, 0, 0);
buffer.drawImage(video, 0, 0);
var image = buffer.getImageData(0, 0, 414, 233);imageData = image.data;
alphaData = buffer.getImageData(0, 233, 414, 233).data;
for (var i = 3; i < imageData.length; i += 4) {
imageData[i] = alphaData[i - 1];
}
output.putImageData(image, 0, 0, 0, 0, 414, 233);

A word of advice: preload your video beforehand if it contains audio and play it back muted initially. This will prevent your browser from trying to play it fullscreen. These rules and regulations on autoplaying media keep evolving so be on the lookout for standardization changes.

As always, thanks for listening. Fire up the app tonight and see if you can spot some constellations. If you like 70s sci-fi mixed with Stranger Things vibes, make sure to check out the new video for “The Sky is a Neighborhood.” Concrete and Gold is out on RCA Records September 12th. Special thanks to Foo Fighters and Silva Artist Management for helping pay my rent.

Sign up to discover human stories that deepen your understanding of the world.

--

--

Written by Lee Martin

Netmaker. Playing the Internet in your favorite band for two decades. Previously Silva Artist Management, SoundCloud, and Songkick.

No responses yet

Write a response