Saturday, March 16, 2013

Drawing Patterns in Physijs Height Fields

‹prev | My Chain | next›


The Physijs HeightField with which I have been playing for the past few days seems quite promising. It may be too complicated to include in 3D Game Programming for Kids. Still, the landscapes that it makes are quite nice:



Today, I would like to see how I might changed those parallel “rivers” into a single, winding river. I do not believe that it will be easy. Height fields work on the vertices of shapes. If I have a 900×900 plane that I divide into a 3×3 grid, then I have 9 faces (300×300 each) and 16 vertices:



To make a winding depression in that, I would want vertices 1, 6, 9 and 14 lower than the rest. Let's see how that looks in Three.js.

A Three.js plane is constructed with the width and height as the first two constructor parameters. The second two parameters are the number of faces in each dimension. To replicate the above grid, I want:
  var shape = new THREE.PlaneGeometry(900, 900, 3, 3);
Then, to lower grid points 1, 6, 9, and 14, I do the following:
  var shape = new THREE.PlaneGeometry(900, 900, 3, 3);
  var cover = new THREE.MeshPhongMaterial();
  cover.emissive.setRGB(0.1, 0.6, 0.1);
  cover.specular.setRGB(0.2, 0.2, 0.2);

  shape.vertices[1].z = -100;
  shape.vertices[6].z = -100;
  shape.vertices[9].z = -100;
  shape.vertices[14].z = -100;
  shape.computeFaceNormals();
  shape.computeVertexNormals();

  var ground = new Physijs.HeightfieldMesh(
    shape, cover, 0
  );
I then insert a water plane below the surface of the ground, but above the -100 depressions. The result is:



As proof of concepts go, that is actually not too bad. Clearly it is not a winding river. Height fields do not join depressions quite as ruthlessly as I might like. Still, I got the vertices right.

What I need is more faces to make a smoother transition from vertex to vertex. So I swap back to a large plane with 100 faces in both dimensions:
var size = 5000,
      faces = 100;
  var shape = new THREE.PlaneGeometry(size, size, faces, faces);
Since vertices is a one dimensional array, I need to move through it one row at a time. That means a for-loop incrementing the index variable from 0 to the number of vertices in a row:
  // Doing vertices here, which is faces+1
  var row_size = faces+1;
  for (var i=0; i<row_size; i++) {
    // manipulate the height map here...
  }
Now, I need to manipulate the nature of the winding river. For the frequency that the river undulates, I opt for 2 full sine waves, 4*Math.PI. For the amplitude, I use 0.05 times the number of faces (5% of the total faces in each row). Last, I have to offset the winding to the middle of each row. This all looks like:

  var row_size = faces+1;
  for (var i=0; i<row_size; i++) {
    var j = Math.sin(4*Math.PI*i/row_size);
    j = j * 0.05 * faces;
    j = Math.floor(j + faces/2);
    // manipulate height map here...
  }
With that, all that is left is to create the depression for the river. I do this on the vertex that I found above and two vertices on either side for better effect:
  var row_size = faces+1;
  for (var i=0; i<row_size; i++) {
    var j = Math.sin(4*Math.PI*i/row_size);
    j = j * 0.05 * faces;
    j = Math.floor(j + faces/2);
    shape.vertices[i*(row_size) + j-2].z = -50;
    shape.vertices[i*(row_size) + j-1].z = -90;
    shape.vertices[i*(row_size) + j].z   = -100;
    shape.vertices[i*(row_size) + j+1].z = -90;
    shape.vertices[i*(row_size) + j+2].z = -50;
  }
  shape.computeFaceNormals();
  shape.computeVertexNormals();
I also remember to recompute the normals so that Three.js can do the shading right. With that, I have a nice, winding river:



Best of all, if I move my raft into the river (and point the camera at it), then it interacts well with both the river and the height field ground:



The banks of the river are a little blocky, but, in a 3D-computer-kind-of-way, that is not horrible.

I am a little worried about explaining that for-loop to kids. The mapping of the one dimensional array into two dimensions seems tricky. Especially since the two dimensions are array space that happen to map into coordinate space. That said, this is far easier than some of the river segment solutions that I had previously explored. And it looks nicer. Definitely worth exploring a bit more.

(live code of the river)


Day #692

No comments:

Post a Comment