Horizontal shifting according to 3D noises:

makes slopes more irregular and natural-looking, allows overhanging.
This is done by generating an intermediate 2D elevation map and, for each node in 3D, add a 2D offset vector to the position, and seek this position on the heightmap.
This commit is contained in:
Gael-de-Sailly 2020-05-23 15:52:16 +02:00
parent f56857e804
commit 4edd1a946e
2 changed files with 248 additions and 103 deletions

119
heightmap.lua Normal file
View File

@ -0,0 +1,119 @@
local modpath = minetest.get_modpath(minetest.get_current_modname()) .. '/'
local make_polygons = dofile(modpath .. 'polygons.lua')
local transform_quadri = dofile(modpath .. 'geometry.lua')
local blocksize = mapgen_rivers.blocksize
local sea_level = mapgen_rivers.sea_level
local riverbed_slope = mapgen_rivers.riverbed_slope
-- Linear interpolation
local function interp(v00, v01, v11, v10, xf, zf)
local v0 = v01*xf + v00*(1-xf)
local v1 = v11*xf + v10*(1-xf)
return v1*zf + v0*(1-zf)
end
local function heightmaps(minp, maxp)
local polygons = make_polygons(minp, maxp)
local incr = maxp.x-minp.x+1
local i0 = (minp.z-minp.z) * incr + (minp.x-minp.x) + 1
local terrain_height_map = {}
local lake_height_map = {}
local i = 1
for x=minp.x, maxp.x do
for z=minp.z, maxp.z do
local poly = polygons[i]
if poly then
local xf, zf = transform_quadri(poly.x, poly.z, x/blocksize, z/blocksize)
local i00, i01, i11, i10 = unpack(poly.i)
-- Load river width on 4 edges and corners
local r_west, r_north, r_east, r_south = unpack(poly.rivers)
local c_NW, c_NE, c_SE, c_SW = unpack(poly.river_corners)
-- Calculate the depth factor for each edge and corner.
-- Depth factor:
-- < 0: outside river
-- = 0: on riverbank
-- > 0: inside river
local depth_factors = {
r_west - xf,
r_north - zf,
xf - r_east,
zf - r_south,
c_NW-xf-zf,
xf-zf-c_NE,
xf+zf-c_SE,
zf-xf-c_SW,
}
-- Find the maximal depth factor and determine to which river it belongs
local depth_factor_max = 0
local imax = 0
for i=1, 8 do
if depth_factors[i] >= depth_factor_max then
depth_factor_max = depth_factors[i]
imax = i
end
end
-- Transform the coordinates to have xf and zf = 0 or 1 in rivers (to avoid rivers having lateral slope and to accomodate the surrounding smoothly)
if imax == 0 then
local x0 = math.max(r_west, c_NW-zf, zf-c_SW)
local x1 = math.min(r_east, c_NE+zf, c_SE-zf)
local z0 = math.max(r_north, c_NW-xf, xf-c_NE)
local z1 = math.min(r_south, c_SW+xf, c_SE-xf)
xf = (xf-x0) / (x1-x0)
zf = (zf-z0) / (z1-z0)
elseif imax == 1 then
xf = 0
elseif imax == 2 then
zf = 0
elseif imax == 3 then
xf = 1
elseif imax == 4 then
zf = 1
elseif imax == 5 then
xf, zf = 0, 0
elseif imax == 6 then
xf, zf = 1, 0
elseif imax == 7 then
xf, zf = 1, 1
elseif imax == 8 then
xf, zf = 0, 1
end
-- Determine elevation by interpolation
local vdem = poly.dem
local terrain_height = math.floor(0.5+interp(
vdem[1],
vdem[2],
vdem[3],
vdem[4],
xf, zf
))
local lake_height = math.max(math.floor(poly.lake), terrain_height)
if imax > 0 and depth_factor_max > 0 then
terrain_height = math.min(math.max(lake_height, sea_level) - math.floor(1+depth_factor_max*riverbed_slope), terrain_height)
end
terrain_height_map[i] = terrain_height
lake_height_map[i] = lake_height
else
terrain_height_map[i] = -31000
lake_height_map[i] = -31000
end
i = i + 1
end
end
return terrain_height_map, lake_height_map
end
return heightmaps

222
init.lua
View File

@ -12,6 +12,8 @@ local make_polygons = dofile(modpath .. 'polygons.lua')
local transform_quadri = dofile(modpath .. 'geometry.lua')
local heightmaps = dofile(modpath .. 'heightmap.lua')
-- Linear interpolation
local function interp(v00, v01, v11, v10, xf, zf)
local v0 = v01*xf + v00*(1-xf)
@ -21,7 +23,95 @@ end
local data = {}
local noise_x_params = {
offset = 0,
scale = 1,
seed = -4574,
spread = {x=64, y=32, z=64},
octaves = 3,
persistence = 0.75,
lacunarity = 2,
}
local noise_z_params = {
offset = 0,
scale = 1,
seed = -7940,
spread = {x=64, y=32, z=64},
octaves = 3,
persistence = 0.75,
lacunarity = 2,
}
local noise_distort_params = {
offset = 0,
scale = 10,
seed = 676,
spread = {x=1024, y=1024, z=1024},
octaves = 5,
persistence = 0.5,
lacunarity = 2,
flags = "absvalue",
}
local noise_x_obj, noise_z_obj, noise_distort_obj
local noise_x_map = {}
local noise_z_map = {}
local noise_distort_map = {}
local mapsize
local init = false
local function generate(minp, maxp, seed)
local chulens = {
x = maxp.x-minp.x+1,
y = maxp.y-minp.y+1,
z = maxp.z-minp.z+1,
}
if not init then
mapsize = {
x = chulens.x,
y = chulens.y+1,
z = chulens.z,
}
noise_x_obj = minetest.get_perlin_map(noise_x_params, mapsize)
noise_z_obj = minetest.get_perlin_map(noise_z_params, mapsize)
noise_distort_obj = minetest.get_perlin_map(noise_distort_params, chulens)
init = true
end
noise_x_obj:get_3d_map_flat(minp, noise_x_map)
noise_z_obj:get_3d_map_flat(minp, noise_z_map)
noise_distort_obj:get_2d_map_flat(minp, noise_distort_map)
local xmin, xmax, zmin, zmax = minp.x, maxp.x, minp.z, maxp.z
local i = 0
local i2d = 0
for z=minp.z, maxp.z do
for y=minp.y, maxp.y+1 do
for x=minp.x, maxp.x do
i = i+1
i2d = i2d+1
local distort = noise_distort_map[i2d]
local xv = noise_x_map[i]*distort + x
if xv < xmin then xmin = xv end
if xv > xmax then xmax = xv end
noise_x_map[i] = xv
local zv = noise_z_map[i]*distort + z
if zv < zmin then zmin = zv end
if zv > zmax then zmax = zv end
noise_z_map[i] = zv
end
i2d = i2d-chulens.x
end
end
local pminp = {x=math.floor(xmin), z=math.floor(zmin)}
local pmaxp = {x=math.floor(xmax)+1, z=math.floor(zmax)+1}
local incr = pmaxp.z-pminp.z+1
local i_origin = 1 - pminp.x*incr - pminp.z
local terrain_map, lake_map = heightmaps(pminp, pmaxp)
local c_stone = minetest.get_content_id("default:stone")
local c_dirt = minetest.get_content_id("default:dirt")
local c_lawn = minetest.get_content_id("default:dirt_with_grass")
@ -35,119 +125,55 @@ local function generate(minp, maxp, seed)
local a = VoxelArea:new({MinEdge = emin, MaxEdge = emax})
local ystride = a.ystride -- Tip : the ystride of a VoxelArea is the number to add to the array index to get the index of the position above. It's faster because it avoids to completely recalculate the index.
local polygons = make_polygons(minp, maxp)
local nid = mapsize.x*(mapsize.y-1) + 1
local incrY = -mapsize.x
local incrX = 1 - mapsize.y*incrY
local incrZ = mapsize.x*mapsize.y - mapsize.x*incrX - mapsize.x*mapsize.y*incrY
local i = 1
for x = minp.x, maxp.x do
for z = minp.z, maxp.z do
local poly = polygons[i]
if poly then
local xf, zf = transform_quadri(poly.x, poly.z, x/blocksize, z/blocksize)
local i00, i01, i11, i10 = unpack(poly.i)
for x = minp.x, maxp.x do
local ivm = a:index(x, minp.y, z)
local ground_above = false
for y = maxp.y+1, minp.y, -1 do
local xn = noise_x_map[nid]
local zn = noise_z_map[nid]
local x0 = math.floor(xn)
local z0 = math.floor(zn)
-- Load river width on 4 edges and corners
local r_west, r_north, r_east, r_south = unpack(poly.rivers)
local c_NW, c_NE, c_SE, c_SW = unpack(poly.river_corners)
local i0 = i_origin + x0*incr + z0
local i1 = i0+incr
local i2 = i1+1
local i3 = i0+1
-- Calculate the depth factor for each edge and corner.
-- Depth factor:
-- < 0: outside river
-- = 0: on riverbank
-- > 0: inside river
local depth_factors = {
r_west - xf,
r_north - zf,
xf - r_east,
zf - r_south,
c_NW-xf-zf,
xf-zf-c_NE,
xf+zf-c_SE,
zf-xf-c_SW,
}
local terrain = interp(terrain_map[i0], terrain_map[i1], terrain_map[i2], terrain_map[i3], xn-x0, zn-z0)
if y <= maxp.y then
local lake = math.min(lake_map[i0], lake_map[i1], lake_map[i2], lake_map[i3])
-- Find the maximal depth factor and determine to which river it belongs
local depth_factor_max = 0
local imax = 0
for i=1, 8 do
if depth_factors[i] >= depth_factor_max then
depth_factor_max = depth_factors[i]
imax = i
end
end
-- Transform the coordinates to have xf and zf = 0 or 1 in rivers (to avoid rivers having lateral slope and to accomodate the surrounding smoothly)
if imax == 0 then
local x0 = math.max(r_west, c_NW-zf, zf-c_SW)
local x1 = math.min(r_east, c_NE+zf, c_SE-zf)
local z0 = math.max(r_north, c_NW-xf, xf-c_NE)
local z1 = math.min(r_south, c_SW+xf, c_SE-xf)
xf = (xf-x0) / (x1-x0)
zf = (zf-z0) / (z1-z0)
elseif imax == 1 then
xf = 0
elseif imax == 2 then
zf = 0
elseif imax == 3 then
xf = 1
elseif imax == 4 then
zf = 1
elseif imax == 5 then
xf, zf = 0, 0
elseif imax == 6 then
xf, zf = 1, 0
elseif imax == 7 then
xf, zf = 1, 1
elseif imax == 8 then
xf, zf = 0, 1
end
-- Determine elevation by interpolation
local vdem = poly.dem
local terrain_height = math.floor(0.5+interp(
vdem[1],
vdem[2],
vdem[3],
vdem[4],
xf, zf
))
local lake_height = math.max(math.floor(poly.lake), terrain_height)
if imax > 0 and depth_factor_max > 0 then
terrain_height = math.min(math.max(lake_height, sea_level) - math.floor(1+depth_factor_max*riverbed_slope), terrain_height)
end
local is_lake = lake_height > terrain_height
local ivm = a:index(x, minp.y-1, z)
if terrain_height >= minp.y then
for y=minp.y, math.min(maxp.y, terrain_height) do
if y == terrain_height then
if is_lake or y <= sea_level then
local is_lake = lake > terrain
local ivm = a:index(x, y, z)
if y <= terrain then
if y <= terrain-1 or ground_above then
data[ivm] = c_stone
elseif is_lake then
data[ivm] = c_sand
else
data[ivm] = c_lawn
end
else
data[ivm] = c_stone
end
ivm = ivm + ystride
elseif y <= lake and lake > sea_level then
data[ivm] = c_rwater
elseif y <= sea_level then
data[ivm] = c_water
end
end
if lake_height > sea_level then
if is_lake and lake_height >= minp.y then
for y=math.max(minp.y, terrain_height+1), math.min(maxp.y, lake_height) do
data[ivm] = c_rwater
ground_above = y <= terrain
ivm = ivm + ystride
nid = nid + incrY
end
nid = nid + incrX
end
else
for y=math.max(minp.y, terrain_height+1), math.min(maxp.y, sea_level) do
data[ivm] = c_water
ivm = ivm + ystride
end
end
end
i = i + 1
end
nid = nid + incrZ
end
vm:set_data(data)