Terrains using Three.js and SRTM Data

By taha No comments

Let’s jump right in to three.js plane buffer geometry. Three.js offers a buffer geometry object which is a simple array of height maps, much like our binary file we used from the SRTM data. Compared to the normally used plane geometry, buffer geometry uses much less overhead and is significantly faster if the object does not need to be modified once initialised.

To make things simple, I will assume that the terrain we need to generate is contained only within one binary hgt file, i.e., the terrain is within one latitude and longitude cell (this means that the bounding box of the terrain will be within one integer long and wide: N58.79 – N58.92, E006.22 – E006.33 is within one integer wide and long while N58.79 – N59.12, E006.22 – E007.13 will require more than one hgt file to process and will be needlessly complicated for this example).

The code can be made into the following function:

function terrainGenerator(lat1,lng1,lat2,lng2, buffer, scene, initialPos) {

    var rawRes = 1;
    var resolution = 0.00027778;
    var hgtGridSize = 3601;


    var y1 = Math.floor((1-(lat1%1)) / resolution);
    var y2 = Math.ceil((1-(lat2%1)) / resolution);
    var x1 = Math.floor((lng1%1) / resolution);
    var x2 = Math.ceil((lng2%1) / resolution);

    var delx = x2-x1;
    var dely = y2-y1;

    var heights = [];

So far, we have passed in the opposing corners of the bounding triangle, the binary file (buffer), the threejs scene and the initial position. The first step is to find the binary positions of these corners (y1,y2,x1,x2) and also the distance between these points in the binary file.

Now we populate the heights array using recursion on the binary heights file (buffer).

for (var i=y1;i<=y2;i++) {
    for (var j=x1;j<=x2;j++){
        var px = ((i - 1) * hgtGridSize * 2) + (j * 2);
        var g = new DataView(buffer, px, 2);
        var elevation = g.getInt16(0, false);
        heights.push(elevation);
    }
}

Next we create a plane in three.js using the plane buffer geometry object class.

//find distance for terrain bounding box, will be given as a param to the geometry constructor
// the distanceBetween function is in the WGS84 Projections post http://legolasthesheep.com/post/wgs84projections/
var rec = distanceBetween(lat1,lng1,lat2,lng2);
var geometry = new THREE.PlaneBufferGeometry(Math.floor(rec.h),Math.floor(rec.v),delx, dely);
// rotate to make it flat
geometry.rotateX( - Math.PI / 2);
var vertices = geometry.attributes.position.array;

//copy the heightmap into the geometry vertices
for ( var i = 0, j = 0, l = vertices.length; j < l; i ++, j += 3 ) {
    vertices[ j + 1 ] = heights[i];
}

// create a blueish hue plane
var material2  = new THREE.MeshLambertMaterial({
  color     : '#93d4e7',
  transparent: true,
  opacity    : 0.7,
  wireframe: false,
});

plane = new THREE.Mesh(geometry, material2);

//find centre point in latlng
//both of the following two functions in WGS84 Post http://legolasthesheep.com/post/wgs84projections/

var wgs84Pos = midPoint(lat1,lng1,lat2,lng2);
var meshPos = toLocal(initialPos.lat, initialPos.lng, wgs84Pos.lat, wgs84Pos.lng);

plane.rotation.set(0,-Math.PI/2,0);
plane.position.set(meshPos.x,meshPos.y,meshPos.z);

//add the plane to the scene
scene.add(plane);
return plane;

This is fairly simple and self-explanatory code with a few comments. In short, the heights array is recursively copied as the vertice position attributes of the plane. Since the array storing the position stores the height in the second position of each coordinate in the order [xyzxyzxyz…]
the y position is changed in the recursive function.

The plane is then added to the scene next.

Hong Kong terrain

The generated bluish terrain of Hong Kong (along with some fancy buildings)