A smooth terrain generator using my Perlin Noise and 3D Engine JS libraries
take a look here
So to summarise the two libraries, we essentially have two funcitons to work with:
The 3d generation function which takes a world, described through faces, a camera, described with coordinates and angles of which direction its facing etc., a canvas element to write to and a simple boolean wireframe value.
//cam = {x: ,y: ,z: , yaw: ,pitch: ,roll: ,fov: } //world = [{verts: [{x: ,y: ,z: }, {x: ,y: ,z: }, ...], col: }, {verts: [{x: ,y: ,z: }, {x: ,y: ,z: }, ...], col: }, ...] function render(world, cam, canvas, wireframe){ ... }
We next have my Perlin Noise function which basically has a 'get' function (below) that will take a floating point x,y coodinate and return the result of the 2d noise funciton at that point. It is inside a simple 'perlin' object:
var perlin = { get: function(x, y) { ... } }
It is then just a matter of converting the output of the noise function to 3d coordinates to describe a world that we can pass into the render function.
As the noise function wants floats (each cell is defined by integer grids) and we want to be able to have a parameter which sets how many cells there are, we need to divide thenumber of cells by the minimum of the grid width and height (so that the cells are squares, not reactangles) to get floating point coordinates to pass in. Then we need to times the output of the perlin noise function (a float between -1 and 1) by a variable so that we can set the height of the hills. This method for creating a 'heights' array is seen in the following code.
heights = [] for (var r = 0; r < grid.length + 1; r++){ var row = [] for (var c = 0; c < grid.width + 1; c++){ row.push(perlin.get(c * (cells/Math.min(grid.width, grid.length)), r * (cells/Math.min(grid.width, grid.length))) * hillHeight) } heights.push(row) }
Now that we have the heights array, we need to convert this into 3d coordinates in the format at the top of this page.
A 3d world is described as faces (planes) that make up 3d objects. As we essentially have a grid now of heights (2d array), we need a way of describing this with faces. The method I used is to have two triangles (as you can see in the finished thing) that split the square face so that it can bend. The first triangle is connected between the bottom right, bottom left and top left vertexes and the second between the bottom right, top right and top left. These coordinates then just get scaled up by a variable that describes the size of each square and the height of every corner is simply the corresponding entry in the heights array.
The final aspect is the color of each square (so that we have nice color gradients over the terrain/hills). This is really easy as JS allows you to describe colors with hsl (hue, saturation and lightness). The s and l terms are just constant throughout the terrain, but the hue (ranging from 0 to 360 but can be modded around) determines the colour from a color wheel - which we need to relate to the height of the square. To convert, we need to first divide the height by our hill height variable again so we are now working with a value between -1 and 1. We now just need to times this by a constant and then add a constant which will affect the range of the colours and which colours appear where on the hills (I decided to have mine loop through 2 cycles of the colour wheel (720 deg) by multiplying by 360 and then offsetting this by 180 so there is a neutral blue at 0 and reds at the peaks and troughs).
These two aspects can be seen in this 'genWorld' function:
function genWorld(){ world = [] for (var r = 0; r < grid.length; r++){ for (var c = 0; c < grid.width; c++){ //var col = randCol() var v = parseInt((heights[r][c] / hillHeight) * 360 + 180) var col = 'hsl(' + v + ', 50%, 60%)' world.push( {verts: [{x: c * grid.squareSize, y: r * grid.squareSize, z: heights[r][c]}, {x: c * grid.squareSize + grid.squareSize, y: r * grid.squareSize, z: heights[r][c+1]}, {x: c * grid.squareSize, y: r * grid.squareSize + grid.squareSize, z: heights[r+1][c]}], col: col}, {verts: [{x: c * grid.squareSize + grid.squareSize, y: r * grid.squareSize, z: heights[r][c+1]}, {x: c * grid.squareSize + grid.squareSize, y: r * grid.squareSize + grid.squareSize, z:heights[r+1][c+1]}, {x: c * grid.squareSize, y: r * grid.squareSize + grid.squareSize, z: heights[r+1][c]}], col: col} ) } } }
And that's it! The rest of the code (sliders, info stuff, controls) is pretty boring so I won't talk about that, but feel free to checkout the source code on GitHub if you are interested.
Link at top to try it out!