Saturday, November 12, 2016

Time for A Stretch!

I've posted recently about how to generate mountain chains along fault lines, using a standard technique for generating mountains from noise functions, and then masking those mountains onto the map.  This allows me to create a range that looks like it follows a fault line where two tectonic plates have come together to create an upwelling of mountains:
If you examine the picture above, you'll see that it looks most like a bunch of cone shaped mountains  randomly overlapping within the area of the mountain range.  That's because I'm using a noise function that is symmetrical in both the x and y dimensions.  But in reality, the mountains within a range caused by a fault line have a different sort of structure.  Look at mountain ranges in this vintage postcard of Los Angeles:
(Click on any image to see it larger)

As this illustration shows, the mountains actually form ridges that run parallel to the fault line.  The two plates pushing up against one another create multiple folds in the earth.  I'd like to generate mountains that look more like this than my approach. To do this, I'm going to take advantage of an (underused) feature of gradient noise.  

Recall that currently the code in my mountain range generator looks something like this:
 for(var i=0;i<world.loc.length;i++) {
 [...]
    var val = pn.ridgedMF(x*4, x*4, 0.8, 3, 0.90) * height;

Here I'm stepping through the world, and in every mountain location adding 3 octaves of ridged multi-fractal noise with 90% persistence.  (The 0.8 is just a dummy value for the z dimension.)  I'm multiplying the world location by 4 before generating noise so that the frequency of the peaks in the mountains looks about right.  If I used a number less than 4, the peaks would too gradual and far apart.
 And likewise if I used a number bigger than 4, the peaks come too frequently:
The "right" number for this multiplier is dependent upon your coordinate system and is largely a matter of taste.

Typically, the same number is used for both dimensions to get a symmetrical noise.  If the numbers differ, then the noise gets "stretched" in one dimension or the other.  For example, if I set the multiplier for just the x dimension to 16, the mountains look like this:
As you can see, the noise is now varying faster in one dimension (the x dimension) than in the other dimension (the y dimension).  This has the useful effect of creating "ridges" in the noise.  In this case, the ridges run along the y dimension.  The way this mountain range is situated, I want the ridges to run the other way.   To get that, I have to reverse the multipliers so that the noise is varying faster in the y dimension:
And now the ridges run along the mountain range -- much like they do in the Santa Monica Mountains around Los Angeles.  This looks a little too chaotic to me -- I think it looks better if I multiply the y dimension by 8 and reduce the x dimension factor to 2.
If I have a mountain range running north-south, I can just flip the multipliers to create ridges running north-south:
And for any angle in between, I can just adjust the multipliers accordingly.... Right?

Uh, no.  Unfortunately, this technique can only create ridges along the x and y dimensions.  That's easy enough to see from the first image where the multipliers were the same.  That didn't create ridges running at 45 degrees, it created symmetrical peaks:
So it's going to take more work to get ridges at any angle.

Essentially, the solution is to create the mountain range along the x axis and then rotate and transform it into the proper position.  A quick trip to StackOverflow provides the equations for rotating a point (px,py) around a point (ox, oy) by a angle theta:

rx = cos(theta) * (px-ox) - sin(theta) * (py-oy) + ox
ry = sin(theta) * (px-ox) + cos(theta) * (py-oy) + oy

Fortunately there are a few simplifications that will make this easier.  First, since the noise function is infinite, the mountain doesn't actually have to be created along the x axis, but can be created along any line parallel to the x axis.  So it's only necessary to rotate the range, not transform its location -- which means I can choose any point (ox,oy) to anchor the rotation.  If I choose to rotate around the origin (i.e., (ox, oy) == (0, 0)), the rotation equations are simplified to this:

rx = cos(theta) * px - sin(theta) * py
ry = sin(theta) * px + cos(theta) * py


Second, it isn't necessary to create the range and then rotate it -- it suffices to do a "virtual" rotation and generate the heightmap for a location as if it were the rotated location.  In other words, I set the height of a location based upon the noise at the rotated location:

    height[x,y] = noise(rx, ry)

In pseudo-code the basics look like this:

    // Angle to rotate the ridges
    // (x0, y0) and (x1, y1) are two points that define the fault line
    var theta = -Math.atan2((y1-y0), (x1-x0));
    for(var i=0;i<world.loc.length;i++) {
       var x = world.loc[i].coords[0];
       var y = world.loc[i].coords[1];
       // First rotate x and y so that the ridges will run along
       // the length of the mountain chain.
       var rx = Math.cos(theta)*x - Math.sin(theta)*y;
       var ry = Math.sin(theta)*x + Math.cos(theta)*y;
       // Calculate the mountain height at (x,y) using (rx,ry)
       var val = pn.ridgedMF(rx*2, ry*8, 0.8, 3, 0.90) * height; 
       // (Code to calculate the mask omitted)
       world.loc[i].h += mask*val;


With this implemented,  I can now put a mountain range at any angle and have the ridges running along the range:
(The criss-cross patterns on the mountains are just an artifact of the fairly simple 3D display.)

The only remaining issue is the perturbation of the tent mask that creates the mountain range.  Recall that I perturb the tent mask with noise to give the mountain range a more organic shape instead of a ruler-straight rectangle.   To have the mountain ridges follow the same perturbed outline, I have to apply both the rotation transformation and the perturbation before setting the height.  Then I get ridge lines that follow the curves of the mountain range:


And now I have mountain ridges running organically along the underlying fault line.  This trick to create structure in the noise is pretty one-dimensional (so to speak) but it happens to be very handy for this one case of using noise to create mountain ranges.

No comments:

Post a Comment