May 30, 2009 will

A 3D Texture-Mapped Globe Rendered with Javascript and HTML

I've been working on a large-ish Javascript project lately (still in stealth mode). One that could be described as a Javascript application. And since I've been up to my elbows in Javascript, I have found my contempt for the language waning. Not entirely, of course – I'm still a Python fan-boy. But enough for me to knock out a 3D(ish) spinning texture-mapped globe, using nothing but Javascript and HTML, as a way of honing my JS skills.

Without further ado, I present you with a 3D Javascript Globe.

An explanation

This effect is off course a hack in the sense that it produces a novel result, but it still only makes use of standard browser capabilities.

One of the few geometry formulas that I have committed to memory is the relationship of the points on a circle and the radius: x^2 + y^2 = r^2

The ‘sphere’ is composed of a sequence of divs, one for each row of pixels. The width and position of the rows are calculated by the Javascript with a little math. Inside of each of the divs is an img tag that is position so that it shows a particular row of the source image (see below). The img tags also have their size set according to there position in the sphere so that the image is scaled more at the top and bottom of the sphere to simulate foreshortening. This gives some illusion of perspective – on one axis at least.

Texture map of the earth for JS Globe

The unwrapped globe image

Shading the sphere

Sphere light map for JS Globe

The translucent PNG used to simulate lighting.

The shading is simply a transparent PNG that is absolute positioned on top of the circle. I rendered this image in Inkscape – it's just a circle with radial shading applied, but it looks convincingly sphere like.

Animating the sphere

This sphere is animated by moving the img tags in each div. The divs have the overflow: hidden; style set, so that only the required number of pixels are shown. The rate at which each row moves is based on the width of the row in the sphere. Rows at the top and bottom of the sphere scroll more slowly than the middle because they have been scaled down.

Alas, its not very fast (at least on my machine). Scaling img tags is processor intensive and wasn't really meant to be used dynamically like this. It could possibly be optimized by pre-processing the map so that the browser needn't do any scaling of its own, but frankly I have satisfied my desire to find out if it is possible at all!

The code

Here's the code. As you will see, its not designed to be re-usable and frankly it uses up too much processor power to be a practical effect on a web-page, but you are welcome do do whatever you want with it! It uses the excellent JQuery library.

<script type="text/javascript">

var anim=0;
function animate_globe(globe)
{
    $globe = $(globe);

    anim+=1;
    var x=( anim*5 ) % 600 - 600;
    $globe.find('.globe-span').each(function(i, span){
        $(span).css({left:x * scales[i]});


   });
}

function make_globe(globe_select, globe_image_select, size)
{
    var span_html_template = '<div style="position:relative;overflow:hidden;width:[WIDTH]px;height:1px;margin-left:[MARGIN]px;"></div>"';
    var span_img_template = '<img class="globe-span" src="[SRC]" width="[WIDTH]" height="[HEIGHT]" style="border:0;padding:0;margin:0;position:relative;width:[WIDTH]px;height:[HEIGHT]px;top:[TOP]px;">'

    function render_template(template, td)
    {
        return template.replace(/\[(.*?)\]/g, function(m, n) {
            return td[n];
            });
    }

    var $globe = $(globe_select);
    var $globe_image = $(globe_image_select);
    var globe_image_src = $globe_image.attr('src');
    var w = $globe_image.width();
    var h = $globe_image.height();


    var r = size/2;
    var max_c = Math.PI * size;
    scales = []
    for(var y = -r; y < r; y++)
    {
        var i = ((y+r)/size);
        var width = Math.sqrt(r*r - y*y)*2.0 + .5;

        var c = Math.PI * width;

        var scale = width / size;

        scales.push(scale);


        var img_width = Math.round(scale * w*2);
        var img_height = Math.round(scale * h);
        var img_y = Math.floor(i*img_height);

        var span_html = render_template(span_html_template, {'WIDTH': Math.ceil(width),
                                                             'MARGIN' : Math.round((size-width)/2)})

        var $span = $(span_html);

        var img_html = render_template(span_img_template, { 'SRC': globe_image_src,
                                                        'WIDTH' : img_width,
                                                        'WIDTH2' : img_width * 2,
                                                        'HEIGHT' : img_height,
                                                        'TOP' : -img_y})

        var $span_img = $(img_html);

        $span.append($span_img);
        $globe.append($span);

    }

    setInterval("animate_globe('#jsglobe')", 50);

}

$(function(){make_globe('#jsglobe', '#jsglobe-image', 300);});


    </script>

    <img id="jsglobe-image" style="display:none;" src="/media/uploads/images/earthmap.jpg" />
    <div style="margin-left:80px;width:100%;padding:20px;float:left;width:300px;height:300px;">
    <div id="jsglobe" style="float:left;position:absolute;">
    </div>
    <div style="float:left;width:300px;height:300px;position:absolute;background-image:url(/media/uploads/images/spherelight.png);">
    </div>
    </div>
<div style="clear:both;">&nbsp;</div>
Use Markdown for formatting
*Italic* **Bold** `inline code` Links to [Google](http://www.google.com) > This is a quote > ```python import this ```
your comment will be previewed here
gravatar
Dave
Have you played with O3D yet?
gravatar
Will McGugan
Dave, haven't look at it. Have you done anything with it?
gravatar
Dino Calvitti

Question-how to have globe fill whole web page? -Dino