The concept of "terrain" in programming usually means generating some sort of "height map".
That is a map that takes a position along the horizontal plane (x, z) and turns it into a height.
There are some useful observations about the sorts of height maps you want when generating realistic terrain:
- They are more or less smooth, meaning they aren't jagged
- They are almost always continuous, meaning even when there are peaks / ridges, there isn't a vertical cliff
- They are more or less irregular -- there isn't some visible repeating pattern
So the goal can usually be stated as, *make a function h(x, z) that gives me a height for a part positioned at Vector3.new(x, __, z).
Since we want it to be wavy, we could take advantage of sine and cosine;
Simple Smooth Functions
function h1(x, z)
x = x / 20
z = z / 20
return (math.sin(x) + math.sin(z)) * 20
end
You'll get a wavy pattern if you use something like that.
Here's a different one:
function h2(x, z)
x, z = x / 20, z / 20
return math.sin(x) * math.cos(z) * 20
end
Interestingly, you can also add these two functions to get something that has a bit of both features:
function h3(x, z)
return (h1(x, z) + h2(x, z) ) /2
end
Fractal Noise
With this in mind, there is the concept of fractal noise which is similar to Perlin noise.
We use a simple noise function over and over but at different scales, e.g.,
function h(x, z)
local weight = 0
local sum = 0
for i = 1, 10 do
local w = math.exp(-i / 5)
local f = h3(x * i, z * i);
sum = sum + f * w
weight = weight + w
end
return sum / weight
end
Node Based Smooth Noise
Another basic approach would be to place "nodes" which control the terrain -- "higher" nodes make mountains, lower ones valleys.
When you put enough mixed together, then you get a messy but smoothish result -- which is ideal for nice terrain.
Here is an inefficient, but simple algorithm. It consists of generating random points; the height function is then a weighted average of all of the values of the random points, where the weight of each decreases when you get further from it.
local points = {};
while #points < 100 do
table.insert(points, {x = math.random() * 100, y = math.random() * 100; v = math.random()})
end
function h(x, z)
local query = Vector3.new(x, 0, z);
local totalWeight = 0;
local total = 0;
for _, point in pairs(points) do
local position = Vector3.new(point.x, 0, point.y);
local distance = position - query.magnitude;
local weight = math.exp(-distance^2 / 1000.0);
totalWeight = totalWeight + weight
total = total + weight * point.v
end
return total / totalWeight
end
1 comment
If you want to avoid all that math which still wont get you exactly what you want, I suggest 3d modeling it, then importing it with a OBJ importing script. It's a easy way of avoiding that whole mess.