WebGL Earth

Since falling down a mountain back in November I'm currently in no shape to enjoy the outdoors. So I have spent the last couple of weekends toying with an ancient game idea of mine. Technology has moved on, so this time around I'm using JavaScript and WebGL on the client and a Go server hosted on App Engine. It's been quite a ride so far, learning three new programming languages, a new environment and tricky maths at the same time. But it's fun and the results look rather promising ;-) This blog post journals some of the epiphanies I had along the way.

The design calls for a three dimensional rendering of planet earth as seen from space. So the first thing I wanted to figure out is where the sun is relative to the earth. I'd like to correctly represent the current time and season. I naively assumed I'd type my query into Google and end up with a simple formula that spits out coordinates for a given date and time. Not so much. Turns out astronomy is rather complicated! Apparently there is no exact analytical solution to the set of equations describing the planets' positions. So you end up using approximations that are only valid within a certain time frame of about a hundred years. For the purposes of this calculation it makes sense to pretend the sun is orbiting the earth instead of the other way around. You plug in a bunch of constants like argument of the perihelion, mean anomaly, eccentricity, semi-major axis and inclination to the ecliptic and churn them through a giant formula. You'll end up with declination and right ascension. Declination is easy enough, it corresponds to latitude. But what is this ascension thing? The celestial coordinate system uses a different reference system than the usual latitude/longitude one. Instead of using the earth's equator as the origin it uses the imaginary plane of the earth's orbit around the sun. Similarly instead of putting the longitude zero point at Greenwich it is instead located wherever the March equinox is that year. Fair enough, so where is that? So you learn about Sideral Time and compute the Greenwich Mean Sideral Time to convert from one coordinate system to another. Now JavaScript throws some wrenches into your machinery by counting months starting from zero (March is 2 - seriously, who does that?!) and you confuse radians vs degrees a bunch of times and finally you end up with the lat/lon coordinates for the sun for any given UTC time. Phew! If it wasn't for this awesome write up by Paul Schlyter I'd have given up on the endeavor.

Next up is rendering the surface of the earth. Luckily the Nasa Earth Observatory provides an excellent high resolution data set for free. The Blue Marble has painstakingly been stitched together from thousands of individual satellite images and corrected for distortions, seams, color inconsistencies and cloud cover. Applying this texture to a sphere already looks quite good. It would be nice to see mountains though, so I learn how to write GLSL shaders in order to apply bump mapping. Shaders are small programs that run on the graphics hardware. Vertex shaders transform the geometry while fragment shaders work with pixel data. This is my first contact with the technology and I'm impressed how easy and expressive the language is. It's based on C syntax which I'm very familiar with, but adds powerful primitives for performing linear algebra and parallel processing of texture maps. Bump mapping takes a gray scale texture map as input and interprets the pixel values as elevation data, computing light reflections from that. I'm immediately dissatisfied with the results as the transition from the day side of the planet to the night side causes stark discontinuities. So I ditch bump mapping in favor of normal mapping. It's the same idea but instead of storing elevation values in a gray scale texture map you use a three channel color map to store the three components of the surface normal. In essence you are storing the first derivative or slope instead of the absolute values. The results are much smoother.

Bump map and normal map of the same area in Europe
No bump mapping vs normal mapping in South America

I'm using Phong Shading, a relatively accurate light reflection model. However it can cause overly bright highlights which make the material seem like smooth plastic. To avoid this effect I employ specular texture maps. Generally these indicate the reflectivity of every pixel. For the earth a simple binary map is enough - bodies of water are highly reflective while landmasses are not. You can see a highlight on the ocean, lakes and rivers but not on land.

Next up is the dark side of the planet. When the sun goes down electric lights go on. I use a texture map of the major metropolitan areas on the planet and gradually blend this in on the dark side.

Lastly I overlay a layer of clouds that slowly circles the globe. Since on an average day about 70% of the planet are covered in clouds I have chosen an artificially clear day. This is supposed to be a game eventually after all and realism shouldn't hurt playability. Having the entire planet obscured by clouds is no fun.

It was about three o'clock in the afternoon in Switzerland when I took this screenshot.

To add one final touch to the appearance of the earth I add another shader pass to render an atmosphere. Atmospheric scattering is primarily caused by two effects: Rayleigh Scattering and Mie Scattering. Rayleigh Scattering is caused by small molecules in the air and scatters light of shorter wavelengths much more than longer ones. This is the reason the sky seems blue to an observer - the short wavelength blue light is scattered all over the place and reaches you from all sides. Mie Scattering is caused by larger dust or water particles and is responsible for rainbows or a halo around the sun. Sean O'Neil has published an excellent article in GPU Gems implementing a stochastic approximation of these effects on graphics hardware. It shoots a bunch of sampling rays through the atmosphere and derives a color value from that. The effect is quite dramatic and beautiful.

Sun peeking just around the corner, causing someone on earth to enjoy a beautiful dusk.

Of course the earth isn't alone in space so I use Google image search to find a nice photograph of the Milky Way and tape that on the inside of a sky sphere, producing the starry background you see in the screen shots. Everything until this point is rendered with a perspective camera, causing all lines to converge onto a distant vanishing point. For all intents and purposes the sun is infinitely far away and shouldn't scale based on the observer's position. If I put the sun into the 3D scene it would either have to be much too close or cause numerical instability because of the hugely different scales involved in rendering pixels on the earth's surface vs the sun. So instead I add another scene on top of the 3D scene. This one uses an orthographic projection, thus having no perspective distortion. I render a two dimensional sprite for the sun onto this one. I've also added some lens flares for flair. Lens flares are reflections within the lens of a camera if you point it more or less directly at a light source. For these to work I need to do some fancy math to figure out whether the light source is obscured or not. Generic implementations of lens flares typically resort to using a stochastic process based on occlusion maps calculated on the graphics hardware. Since my scene is rather simple I can get by with calculating the exact ray casting solution.

Finally I render yet another layer on top of the existing 3D and 2D cameras. This last layer is a regular HTML/CSS based website. I use jQuery UI on this to create a user interface with buttons and text as one is used to.

Of course all of this work is rather useless if I can't serve it to players (hopefully that'll include you! ;-)). I decided to use Google App Engine as my host. Google's cloud platform offers a ton of APIs, a free hosting tier and infinite scalability. I dislike Python and Java, which leaves Go as an App Engine supported language choice on the server side. My experience with it is minimal, but so far I'm impressed with its simplicity and elegance. People much more knowledgeable than me keep raving about its virtues, so it must be on to something.

App Engine uses Bigtable as its storage backend. This trades off the convenience a regular SQL database offers with its ACID guarantees for near infinite scalability, a weak query language and eventual consistency. Makes programming against it a little tricky since you are operating at a much lower level and have to be mindful of your transactions.

Anyway. Of course you don't have to take my word for any of this but can try the earth yourself. It requires decent hardware and a good browser (sorry, Internet Explorer doesn't count). That said, I've run it on a phone, a tablet and various laptops and PCs.

Here you go: Operation Survival - Globe Rendering Test.


Wändlispitz (1971m)

I did not cry sitting in the snow waiting for the helicopter, my pelvis twisted and broken in two places, vertebraes cracked and broken, ribs broken, ankle shattered and broken in what they'd later call a complex explosion fracture, one of the long bones in my left foot snapped, my right calf muscle punctured, middle finger with a big crater in it and standing at a completely wrong angle, the tendon ripped, bleeding from an open head wound, freezing wet while my ruptured bladder is seeping blood and poison into my guts, slowly bleeding me to death.

I did not cry when the helicopter jerked me up on a winch, the straps of the harness running between my legs, the weight of my body resting on the broken bones of my pelvis.

I did not cry when they put me down on the snow, head facing down the slope, straightening my legs and putting me into a vacuum bed stretcher.

I am crying now.

The pain is intense. The pain is everywhere. Everything. Anita's soft caress on my good, non injured, hand is hurting. The only sensation I know is pain. I'm waking up after the second round of surgery, several days after the accident. I've spent a lot longer on the operating table than planned. Someone screwed up and didn't tape my eyes shut properly. So I lay unconscious for hours, staring ahead blind and unblinking, wrecking my eyes. I have a towel over my face in a darkened room but it's still blindingly bright. My eyeballs are being rubbed down with rough sandpaper. About a dozen tubes and wires are running into and out of my body. I can't move at all. I can't see. I can't think. The pain from my eyes has a direct connection into my brain, no chance for the cocktail of painkillers to intervene.

I'm lying helpless on my back. Limbs outstretched. Floating, flying, swaying in the center of a dark and endless room. My limbs stretch longer and longer, thinner and thinner, until they completely dissipate into the void. Leaving only the injured bits of my body. Sticking out of the nothingness as bright disconnected islands of pain.

Andrey after we've left the trail for some bushwhacking action.
Sihltal under a veil of clouds.

I completely lose track of time. Turning on the radio to occupy my mind. A drowning man in a black and boiling see of huge waves I cling to songs like life rafts. Repeating the lyrics in my head over and over again until I can't stand it any longer. When I come up for air, another song, it's still the same fucking song playing I started with. Not even minutes have passed. This goes on for an eternity. When the radio plays its jingle marking the passing of a full hour I am suddenly hopeful. Hasn't it gotten brighter outside? Don't I hear more traffic than before? Surely it must be morning by now?
"It is two am. You are listening to..."
I'm heartbroken. Devastated. How am I ever going to get through this endless night? Impossible.

Steep avalanche area.
The grassy band cutting through the cliffs diagonally is our "Schäferweg".

How did I get here? My climbing partner of many previous trips, Andrey, and I set out to climb the peaks of the Fluebrig. The most challenging of the four is the Wändlispitz, so we attack that one first. Arriving with the earliest possible public transport connection we initially make good progress. We are breaking trail, but there is little snow and it is frozen and powdery on easy trails. Soon we take a small turn off and scramble up almost pathless steep slopes, occasionally swiped clean by past avalanches. Somewhat ironically, considering what is to come, Andrey is commending me for being very efficient and confident in this type of tricky terrain.

Andrey scrambling up the steep "Schäferweg".

We follow the Sheppard's trail, a steep grass band cutting diagonally through a vertical rock wall. Luckily it is mostly dry and free of snow. Reaching the ridge we are in deep powder and apparently the first humans in a long while. Mountain goats have trampled a highway of a path though, so progress is quick and easy. We reach the summit at noon, breaking through virgin snow for the last few meters to the cross. The weather is perfect. Sunny and clear for us with a dense sea of clouds below. Spectacular views all around.

We plan to descend via the difficult south ridge. It is rated T6 in good conditions, giving it the most difficult rating possible on that scale. However now it's early afternoon in November and the sun has made the snow sticky and wet. I have to scrape big lumps of snow out of my crampons almost every step of the way. This definitely does not count as "good conditions".

It is very exposed terrain with near vertical drops on either side. Scrambling on slippery snow and lose rocks requires full concentration. Looking down the cliff we agree that falling here would not be wise and almost certainly fatal. I'm leading the way, some 30-50m ahead of Andrey. I clearly remember turning around to him and remarking that we've passed the worst part and are back in walking terrain, not requiring our ice tools and hands for progress anymore. The next thing I remember is waking up 150m lower, lying in the snow, shattered and broken with a trail of blood leading to my resting place.

Wändlispitz South-East Ridge - our path down.
Andrey scrambling down towards me.

I perform a quick self check. Brain seems to work, I can move my back and feel my feet so the spine seems to be ok. Worth going on with this experiment called life. I fumble around looking for my cell phone. It is not in the top flap of my backpack where I expect it. I finally find it in my trouser pockets. Strange. Until I realize that I'm also wearing my fleece jacket and am carrying the backpack in my lap. I was in t-shirt on the ridge with the pack on my back. So I must have been awake and active before with no memory of it whatsoever. I have weird dream like memories of dark and red colors and shapes, paired with the distinct knowledge that something isn't right. However, I also distinctly remember feeling that it's alright, that it isn't so bad because it doesn't affect me.

This is how I like to remember the trip: Relaxed mood in spectacular scenery. Note my footsteps leading to where I'm standing... Also note that I'm still wearing just my t-shirt.
The best smile I could muster. I fell down slightly left of the arc.

Andrey shouts down to me periodically and I remember one or two of my responses. He did this over a period of two hours while waiting for the helicopter. If you had asked me I'd have said that it arrived within 15 minutes of the accident. In reality they had to try several times to find a base to launch from. The fog in the valleys was making safe flights impossible for most. Because I knew from Andrey that rescue was already on its way I called Anita instead. Something people in the hospital would later mock me for. You wake up and call your girlfriend?! I thought it was a rational choice.

Rega landed in the wrong place first - that's how this picture came to be. The emergency doc took it before they picked her up again.
Our descent route and my resting place marked on the same image.

It is 6 weeks and two surgeries later now. My foot is still in continuous pain with a lose splinter of bone poking around the flesh. I have lost more than ten percent of my body weight, most of it muscle, turning me into a weak stick figure scare crow. It took more than four weeks for my blood to return to normal levels. I can hobble around on crutches, but my right hand is unusable and my left foot immobile and not allowed to carry any load.

Pro tip: If your foot looks anything like this - go see a doctor.
My new cybernetic enhancements. I have an internal Wifi antenna and copy of Wikipedia now.

I have become part of the statistic and I hate myself for it. It doesn't fit the image I have of myself as a climber. Some people think I had this coming, that something like this was inevitable eventually. That my attitude towards the risks involved was too cavalier. I disagree. When we set out for this trip we were well trained, in fact I have never been more fit in my life. We were equipped  appropriately, experienced in this type of terrain, a routine climbing team, the weather good and our spirits high. I don't think we committed any major mistakes or behaved irresponsibly. And indeed I fell from a spot well past the most difficult section. No reason really. That's why it frustrates me to no end to have no memory of the actual misstep, the crucial mistake. As for my attitude towards risk. I'll grant that I may often sound macho when talking or writing about our exploits. However, actually being there I think I have a good track record of behaving responsibly and not doing anything reckless.

Morning after the "endless night" - not a good time to be me.
First time out of bed. My physio therapist Jan is watching me intently for any signs that I should faint. I need two people to maneuver my legs out of bed without changing the angle at my hips because that is still intensely painful.
Learning to walk stairs again a couple of days later. I was so happy to walk again I didn't care about the hospital gown being open at the back and my naked bum showing... A passing nurse took mercy and taped it (the gown!) shut.

During recovery in the hospital friends and strangers alike kept remarking on my high spirits and good mood considering the circumstances. It's true. While at times it required a large amount of will power and a very conscious effort to pick myself up I managed a cheery attitude most of the time. This is in no small part thanks to you. I received the best support from friends and family you could possibly hope for. I have received more kind support than I ever know how to repay you for. There have been days where my voice was hoarse from talking on the phone and I had to be very careful not to laugh too hard because my broken ribs would literally pop out and my stomach muscles pull on the pelvis. I've had so many visitors that they'd hold open the door for one another, coming and going in shifts. I could bind the get well postcards into a book. But first and foremost Anita has been absolutely tireless in her care for me, visiting every day while holding up a job and the household at the same time. I am immensely sorry that I caused all of you worry and tremendously grateful for your support. Thank you!

The view from the rehab clinic where I spent the next couple of weeks. Not too shabby.

I would also like to extend my thanks to the emergency and medical teams that got me off the mountain and stitched me back together. I keep joking that if you have to pull a stunt like mine then Switzerland is the best country in the world to do it in. I think it's true. All people involved acted professionally, quickly and efficiently. Although the final result is still outstanding I'm sure puzzling my foot back together was no small feat of surgery. The way it has come thus far gives me hope for a full recovery.

Coming home!
Playing board games with Anita and my brothers...
... who stayed with us for a week over Christmas.


Rinderhorn (3448m)

A couple of days ago I posted the following email to the Google internal climbers mailing list:

Subject: "Rinderhorn (3448m) on Saturday"

Start from Leukerbad and reach the summit via the normal route. Descend the long way to Kandersteg. The normal route is via the North Ridge and is rated PD, featuring steep (40°+) snow/ice, but no crevasses. Doable as a solo tour, so no rope. Fast and light.

According to the weather forecast it'll be snowing and blowing with a wind chill of around -10°C.

Warning: 2000m ascent! The normal route to the summit, in good conditions, starting a full 1000m higher than I intend to, already takes 5 hours! This will be a loooong day and a descent in the dark.

Adliswil ab 05:35
Zürich HB ab 06:02
Visp ab 08:07
Leuk ab 08:22
Leukerbad an 08:35

Last train back from Kandersteg: 22:43
Last train back from Leukerbad: 21:05

There is the possibility of cheating at either end by using a cable car, but that doesn't seem like good style to me.

Anyone up for it?

For some reason no one signed up. Their loss really, as it should turn into one of the most beautiful, and strongest, days in the mountains I've had yet.

I left our apartment in the rain at 5:15 in the morning. A little disheartened by the bad weather I arrive in Leukerbad where it is still raining and I can only steal brief glimpses of the surrounding peaks through the clouds. The first 1000m of ascend up to the Gemmipass look impossibly steep. Yet they managed to thread a very comfortable, high way style, hiking path through the wall. It is here that I meet the only two souls for the entire day. Two women who took the cable car up and are hiking back down to the village.

Someone had enough vision to see a path up there!
The village of Leukerbad.
Trail through the cliffs.

After circling lake Dauben I have my first and only rest for the day. 15 minutes sheltered from the wind behind a big boulder at noon to have a snickers bar for breakfast. Leaving the luxury trails I'm now headed for the Rindersattel at 2909m through loose scree and snow. It is very windy on the ridge and looking up ahead clouds are boiling and swirling around the mountain. A very dramatic sight.

Lonely footsteps.
View towards Rindersattel.
Long summit ridge.

I don crampons and start up the ridge through perfectly dry powder snow. I'm very careful about overhanging cornices and stay well away from the drop and on the face instead. Spindrift is sandblasting my face. Instead of following gravity the snow I kick up with every step whirls straight up the mountain. A very primal experience. I love it!

I experience a brief moment of terror until I realize that the growling thunder I heard is just an airplane passing overhead and not an avalanche. It's getting late and to stay within schedule I have to activate "beast mode" a couple of hundred meters below the summit: Stop with the sissy switchbacks. Point the front points of my crampons directly at the mountain and have at it!

Leukerbad 2000m below.

I sign the summit book at 16:00 with about one and a half hours of daylight left. The final few meters on the ridge are very steep, very exposed and very windy. Gusts of wind are strong enough to get me off balance, so a lot of concentration is required not to fall. Considering the forecast and the rain in the morning the day turned out fantastically beautiful in the end. Dramatic cloud formations racing the sky, the sun shining and winter clear air with infinite visibility. The last entry in the summit book is more than a week old and there are no traces of humans anywhere.

Running down the steep face with huge steps is so much fun that I miss my "exit" and get too low on the face. Bad idea with some near vertical drops below me. So I endure an uncomfortable traverse back to my ascent route and backtrace my steps down. The snow was such that I seeked out frozen solid parts (blank ice reflecting the sun) during the ascent to avoid sinking in too much and powdery sections on the way down so I could dig in my heels.


By the time it gets dark I'm on a well maintained and well marked trail down to Kandersteg. It follows a beautiful canyon and later the Kander river. It's pitch black beneath the trees in a dense forest, very Blair Witch. I arrive at the train station with the train already waiting at the platform. The conductor is nice enough to allow me to purchase my ticket on the train using my cell phone and I'm headed home at 19:13. 10 solid hours of breaking trail and straight hiking with a single 15 minute rest. 2100m up, 2300m down, around 30km distance covered.

The still frames make it look so idyllic and peaceful and don't really capture the action in the sky. So here's a video ;-)


Eggstock (2449m) via “Sött mögli si” (5c+, 12 pitches, ~450m)

Andrey, Eduard and I want to take advantage of what is predicted to be the only good weather day this weekend and head out to Braunwald early on Saturday. It was a very spontaneous decision for me, only coming across Andrey by chance at Google's weekly TGIF celebration. Anita's friend Anne is staying with us for a couple of days and I had just given them the tour of the office and we were having our second round of beers. So the ladies decided to go sight seeing and window shopping in the city while we went climbing - I think we got the better deal ;-)

Comfortable approach. Ortstock (2717m) in the background.
Climbing is good for your neck.
Andrey following Eduard's lead on the first hard pitch of the route.

Andrey chose the route "Sött mögli si", ("Should be possible"), a 12-13 pitch 5c+/6a climb up the Vorder Eggstock (2449m) near the car free mountain village of Braunwald. The combination of train, funicular and cable car offers a very comfortable approach so that we are on the route around 10 in the morning.

Almost there...
Andrey leading a tricky traverse, moments before he fell.

The gneiss/limestone rock is of very mixed quality. The first two pitches are an easy, but annoying and annoyingly dangerous scramble up steep scree slopes. We cannot avoid dropping some rocks, swiped off by our ropes or feet. They come whizzing past the followers. It gradually gets steeper and better though. You only have to watch out on the grassy bands in between pitches.

Eduard enjoying the view.
The route book might need a replacement.

The climbing is very inhomogeneous, some pitches are almost trivially easy with walking sections while others are steep and tricky. Andrey takes a lead fall while traversing a steep slab. Luckily he's not far away from the last bolt and above an overhanging section, so no damage done. He continues on for a couple of meters but gives up and retreats back down. I finish the pitch - a steep slab that requires jamming your hands in a crack for holds.

Soloing up the final pitches to the summit.

We solo through the final 120 or so meters towards the summit. I feel a bit uncomfortable about this at first. The climbing is very easy and mistakes unlikely. However, in the beginning exposure is still great and falling would likely have fatal consequences. This makes you climb very deliberately on the brittle rock, careful not to break off any holds or drop stuff on your comrades.

On the way down, taking advantage of the Braunwald Via Ferrata.

From the summit we follow the Braunwald Via Ferrata back down. While I initially thought it would add to the experience it turns out to be mostly boring. We have our first beers and the first hot meal (or any meal really) of the day on the train back to Zürich. Great day, successful and fun climb. Now I'm sitting at my desk writing this on a rainy Sunday ;-)

Massive ladder.
Looking back: mission accomplished.