"Overgenerate" caverns to open up border regions (#11)

Previously, only nodes strictly within the map block were being carved out as open space. This meant that multi-node decorations (such as giant mushrooms) would collide with walls and ceilings that would be carved out by adjacent block generation later, leaving them sliced off.

Now Subterrane carves out border regions too. Make note of the is_ground_content function, put all your decoration nodes into this to prevent them from being removed. Can't rely on just is_ground_function node properties for most situations, it's too simplistic.
This commit is contained in:
FaceDeer 2020-01-27 20:34:30 -07:00 committed by GitHub
parent 28d1aa3634
commit bd5b1d22c8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 108 additions and 79 deletions

View File

@ -30,6 +30,7 @@ cave_layer_def is a table of the form:
columns = -- optional, a column_def table for producing truly enormous dripstone formations. See below for definition. Set to nil to disable columns. columns = -- optional, a column_def table for producing truly enormous dripstone formations. See below for definition. Set to nil to disable columns.
double_frequency = -- when set to true, uses the absolute value of the cavern field to determine where to place caverns instead. This effectively doubles the number of large non-connected caverns. double_frequency = -- when set to true, uses the absolute value of the cavern field to determine where to place caverns instead. This effectively doubles the number of large non-connected caverns.
on_decorate = -- optional, a function that is given a table of indices and a variety of other mapgen information so that it can place custom decorations on floors and ceilings. It is given the parameters (minp, maxp, seed, vm, cavern_data, area, data). See below for the cavern_data table's member definitions. on_decorate = -- optional, a function that is given a table of indices and a variety of other mapgen information so that it can place custom decorations on floors and ceilings. It is given the parameters (minp, maxp, seed, vm, cavern_data, area, data). See below for the cavern_data table's member definitions.
is_ground_content = -- optional, a function that takes a content_id and returns true if caverns should be carved through that node type. If not provided it defaults to a "is_ground_content" test.
} }
``` ```

View File

@ -75,7 +75,6 @@ subterrane.register_stalagmite_nodes = function(base_name, base_node_def, drop_b
base_node_def.drawtype = "nodebox" base_node_def.drawtype = "nodebox"
base_node_def.paramtype = "light" base_node_def.paramtype = "light"
base_node_def.paramtype2 = "facedir" base_node_def.paramtype2 = "facedir"
base_node_def.is_ground_content = true
base_node_def.node_box = {type = "fixed"} base_node_def.node_box = {type = "fixed"}
local def1 = deep_copy(base_node_def) local def1 = deep_copy(base_node_def)

172
init.lua
View File

@ -181,6 +181,15 @@ local clear_node_arrays = function()
cavern_data.tunnel_ceiling_count = 0 cavern_data.tunnel_ceiling_count = 0
cavern_data.tunnel_floor_count = 0 cavern_data.tunnel_floor_count = 0
cavern_data.column_count = 0 cavern_data.column_count = 0
-- for k,v in pairs(cavern_ceiling_nodes) do cavern_ceiling_nodes[k] = nil end
-- for k,v in pairs(cavern_floor_nodes) do cavern_floor_nodes[k] = nil end
-- for k,v in pairs(warren_ceiling_nodes) do warren_ceiling_nodes[k] = nil end
-- for k,v in pairs(warren_floor_nodes) do warren_floor_nodes[k] = nil end
-- for k,v in pairs(tunnel_ceiling_nodes) do tunnel_ceiling_nodes[k] = nil end
-- for k,v in pairs(tunnel_floor_nodes) do tunnel_floor_nodes[k] = nil end
-- for k,v in pairs(column_nodes) do column_nodes[k] = nil end
close_node_arrays() close_node_arrays()
end end
@ -202,6 +211,7 @@ end
-- columns = -- optional, a column_def table for producing truly enormous dripstone formations. See below for definition. Set to nil to disable columns. -- columns = -- optional, a column_def table for producing truly enormous dripstone formations. See below for definition. Set to nil to disable columns.
-- double_frequency = -- when set to true, uses the absolute value of the cavern field to determine where to place caverns instead. This effectively doubles the number of large non-connected caverns. -- double_frequency = -- when set to true, uses the absolute value of the cavern field to determine where to place caverns instead. This effectively doubles the number of large non-connected caverns.
-- decorate = -- optional, a function that is given a table of indices and a variety of other mapgen information so that it can place custom decorations on floors and ceilings. It is given the parameters (minp, maxp, seed, vm, cavern_data, area, data). See below for the cavern_data table's member definitions. -- decorate = -- optional, a function that is given a table of indices and a variety of other mapgen information so that it can place custom decorations on floors and ceilings. It is given the parameters (minp, maxp, seed, vm, cavern_data, area, data). See below for the cavern_data table's member definitions.
-- is_ground_content = -- optional, a function that takes a content_id and returns true if caverns should be carved through that node type. If not provided it defaults to a "is_ground_content" test.
--} --}
-- column_def -- column_def
@ -315,11 +325,15 @@ subterrane.register_layer = function(cave_layer_def)
c_warren_column = nil c_warren_column = nil
end end
local is_ground_content = cave_layer_def.is_ground_content
if is_ground_content == nil then
is_ground_content = mapgen_helper.is_ground_content
end
-- On generated -- On generated
---------------------------------------------------------------------------------- ----------------------------------------------------------------------------------
minetest.register_on_generated(function(minp, maxp, seed) minetest.register_on_generated(function(minp, maxp, seed)
--if out of range of cave definition limits, abort --if out of range of cave definition limits, abort
if minp.y > YMAX or maxp.y < YMIN then if minp.y > YMAX or maxp.y < YMIN then
return return
@ -336,8 +350,10 @@ minetest.register_on_generated(function(minp, maxp, seed)
end end
local vm, data, data_param2, area = mapgen_helper.mapgen_vm_data_param2() local vm, data, data_param2, area = mapgen_helper.mapgen_vm_data_param2()
local nvals_cave, cave_area = mapgen_helper.perlin3d("subterrane:cave", minp, maxp, np_cave) --cave noise for structure local emin = area.MinEdge
local nvals_wave = mapgen_helper.perlin3d("subterrane:wave", minp, maxp, np_wave) --wavy structure of cavern ceilings and floors local emax = area.MaxEdge
local nvals_cave, cave_area = mapgen_helper.perlin3d("subterrane:cave", emin, emax, np_cave) --cave noise for structure
local nvals_wave = mapgen_helper.perlin3d("subterrane:wave", emin, emax, np_wave) --wavy structure of cavern ceilings and floors
-- pre-average everything so that the final values can be passed -- pre-average everything so that the final values can be passed
-- along to the decorate function if it wants them -- along to the decorate function if it wants them
@ -350,12 +366,9 @@ minetest.register_on_generated(function(minp, maxp, seed)
local warrens_uninitialized = true local warrens_uninitialized = true
local nvals_warrens local nvals_warrens
-- The interp_yxz iterator iterates upwards in columns along the y axis. local previous_y = emin.y
-- starts at miny, goes to maxy, then switches to a new x,z and repeats.
local cave_iterator = cave_area:iterp_yxz(minp, maxp)
local previous_y = minp.y
local previous_node_state = outside_region local previous_node_state = outside_region
local this_node_state = outside_region
local column_points = nil local column_points = nil
local column_weight = nil local column_weight = nil
@ -367,14 +380,22 @@ minetest.register_on_generated(function(minp, maxp, seed)
cavern_data.cave_area = cave_area cavern_data.cave_area = cave_area
cavern_data.cavern_def = cave_layer_def cavern_data.cavern_def = cave_layer_def
for vi, x, y, z in area:iterp_yxz(minp, maxp) do -- The interp_yxz iterator iterates upwards in columns along the y axis.
local vi3d = cave_iterator() -- for use with noise data -- starts at miny, goes to maxy, then switches to a new x,z and repeats.
for vi, x, y, z in area:iterp_yxz(emin, emax) do
-- We're "over-generating" when carving out the empty space of the cave volume so that decorations
-- can slop over the boundaries of the mapblock without being cut off.
-- We only want to add vi to the various decoration node lists if we're actually whithin the mapblock.
local is_within_current_mapblock = mapgen_helper.is_pos_within_box({x=x, y=y, z=z}, minp, maxp)
if y < previous_y then if y < previous_y then
-- we've switched to a new column -- we've switched to a new column
previous_node_state = outside_region previous_node_state = outside_region
else
previous_node_state = this_node_state
end end
previous_y = y previous_y = y
this_node_state = inside_ground
local cave_local_threshold local cave_local_threshold
if y < y_blend_min then if y < y_blend_min then
@ -385,7 +406,7 @@ minetest.register_on_generated(function(minp, maxp, seed)
cave_local_threshold = TCAVE cave_local_threshold = TCAVE
end end
local cave_value = nvals_cave[vi3d] local cave_value = nvals_cave[vi]
if double_frequency then if double_frequency then
if cave_value < 0 then if cave_value < 0 then
cave_value = -cave_value cave_value = -cave_value
@ -403,28 +424,29 @@ minetest.register_on_generated(function(minp, maxp, seed)
end end
-- inside a giant cavern -- inside a giant cavern
if cave_value > cave_local_threshold then if cave_value > cave_local_threshold then
local column_value = 0 local column_value = 0
if column_def then if column_def then
if column_points == nil then if column_points == nil then
column_points = subterrane.get_column_points(minp, maxp, column_def) column_points = subterrane.get_column_points(emin, maxp, column_def)
column_weight = column_def.weight column_weight = column_def.weight
end end
column_value = subterrane.get_column_value({x=x, y=y, z=z}, column_points) column_value = subterrane.get_column_value({x=x, y=y, z=z}, column_points)
end end
if column_value > 0 and cave_value - column_value * column_weight < cave_local_threshold then if column_value > 0 and cave_value - column_value * column_weight < cave_local_threshold then
data[vi] = c_column -- add a column node if is_ground_content(data[vi]) then
previous_node_state = inside_column data[vi] = c_column -- add a column node
else end
data[vi] = c_cavern_air --hollow it out to make the cave this_node_state = inside_column
cavern_data.contains_cavern = true else
if previous_node_state == inside_ground then if is_ground_content(data[vi]) then
-- we just entered the cavern from below data[vi] = c_cavern_air --hollow it out to make the cave
cavern_data.cavern_floor_count = cavern_data.cavern_floor_count + 1 end
cavern_floor_nodes[cavern_data.cavern_floor_count] = vi - area.ystride this_node_state = inside_cavern
if is_within_current_mapblock then
cavern_data.contains_cavern = true
end end
previous_node_state = inside_cavern
end end
end end
@ -437,11 +459,11 @@ minetest.register_on_generated(function(minp, maxp, seed)
if cave_value <= cave_local_threshold and cave_value > warren_area_threshold then if cave_value <= cave_local_threshold and cave_value > warren_area_threshold then
if warren_area_uninitialized then if warren_area_uninitialized then
nvals_warren_area = mapgen_helper.perlin3d("subterrane:warren_area", minp, maxp, np_warren_area) -- determine which areas are spongey with warrens nvals_warren_area = mapgen_helper.perlin3d("subterrane:warren_area", emin, emax, np_warren_area) -- determine which areas are spongey with warrens
warren_area_uninitialized = false warren_area_uninitialized = false
end end
local warren_area_value = nvals_warren_area[vi3d] local warren_area_value = nvals_warren_area[vi]
if warren_area_value > warren_area_variability_threshold then if warren_area_value > warren_area_variability_threshold then
-- we're in a warren-containing area -- we're in a warren-containing area
if solidify_lava and c_lava_set[data[vi]] then if solidify_lava and c_lava_set[data[vi]] then
@ -449,7 +471,7 @@ minetest.register_on_generated(function(minp, maxp, seed)
end end
if warrens_uninitialized then if warrens_uninitialized then
nvals_warrens = mapgen_helper.perlin3d("subterrane:warrens", minp, maxp, np_warrens) --spongey warrens nvals_warrens = mapgen_helper.perlin3d("subterrane:warrens", emin, emax, np_warrens) --spongey warrens
warrens_uninitialized = false warrens_uninitialized = false
end end
@ -458,14 +480,14 @@ minetest.register_on_generated(function(minp, maxp, seed)
local cave_value_edge = math.min(1, (cave_value - warren_area_threshold) * 20) -- make 0.3 = 0 and 0.25 = 1 to produce a border gradient local cave_value_edge = math.min(1, (cave_value - warren_area_threshold) * 20) -- make 0.3 = 0 and 0.25 = 1 to produce a border gradient
local warren_area_value_edge = math.min(1, warren_area_value * 50) -- make 0 = 0 and 0.02 = 1 to produce a border gradient local warren_area_value_edge = math.min(1, warren_area_value * 50) -- make 0 = 0 and 0.02 = 1 to produce a border gradient
local warren_value = nvals_warrens[vi3d] local warren_value = nvals_warrens[vi]
local warren_local_threshold = warren_threshold + (2 - warren_area_value_edge - cave_value_edge) local warren_local_threshold = warren_threshold + (2 - warren_area_value_edge - cave_value_edge)
if warren_value > warren_local_threshold then if warren_value > warren_local_threshold then
local column_value = 0 local column_value = 0
if column_def then if column_def then
if column_points == nil then if column_points == nil then
column_points = subterrane.get_column_points(minp, maxp, column_def) column_points = subterrane.get_column_points(emin, emax, column_def)
column_weight = column_def.weight column_weight = column_def.weight
end end
column_value = subterrane.get_column_value({x=x, y=y, z=z}, column_points) column_value = subterrane.get_column_value({x=x, y=y, z=z}, column_points)
@ -473,18 +495,19 @@ minetest.register_on_generated(function(minp, maxp, seed)
if column_value > 0 and column_value + (warren_local_threshold - warren_value) * column_weight > 0 then if column_value > 0 and column_value + (warren_local_threshold - warren_value) * column_weight > 0 then
if c_warren_column then if c_warren_column then
data[vi] = c_warren_column -- add a column node if is_ground_content(data[vi]) then
previous_node_state = inside_column data[vi] = c_warren_column -- add a column node
end
this_node_state = inside_column
end end
else else
data[vi] = c_warren_air --hollow it out to make the cave if is_ground_content(data[vi]) then
cavern_data.contains_warren = true data[vi] = c_warren_air --hollow it out to make the cave
if previous_node_state == inside_ground then
-- we just entered the warren from below
cavern_data.warren_floor_count = cavern_data.warren_floor_count + 1
warren_floor_nodes[cavern_data.warren_floor_count] = vi - area.ystride
end end
previous_node_state = inside_warren if is_within_current_mapblock then
cavern_data.contains_warren = true
end
this_node_state = inside_warren
end end
end end
end end
@ -493,43 +516,48 @@ minetest.register_on_generated(function(minp, maxp, seed)
-- If decorate is defined, we want to track all this stuff -- If decorate is defined, we want to track all this stuff
if decorate ~= nil then if decorate ~= nil then
local c_current_node = data[vi] local c_current_node = data[vi]
local current_node_is_open = mapgen_helper.buildable_to(c_current_node) local current_node_is_open = c_current_node == c_air -- mapgen_helper.buildable_to(c_current_node)
if current_node_is_open and this_node_state == inside_ground then
if previous_node_state == inside_column then -- we're in a preexisting open space (tunnel).
-- in this case previous node state is actually current node state, this_node_state = inside_tunnel
-- we placed a column node during this loop end
cavern_data.column_count = cavern_data.column_count + 1
column_nodes[cavern_data.column_count] = vi if is_within_current_mapblock then
elseif previous_node_state == inside_ground and current_node_is_open then if this_node_state == inside_column then
-- we just entered a tunnel from below cavern_data.column_count = cavern_data.column_count + 1
cavern_data.tunnel_floor_count = cavern_data.tunnel_floor_count + 1 column_nodes[cavern_data.column_count] = vi
tunnel_floor_nodes[cavern_data.tunnel_floor_count] = vi-area.ystride elseif previous_node_state ~= this_node_state and previous_node_state ~= inside_column then
previous_node_state = inside_tunnel if previous_node_state == inside_ground then
elseif previous_node_state ~= inside_ground and not current_node_is_open then if this_node_state == inside_tunnel then
if previous_node_state == inside_cavern then -- we just entered a tunnel from below.
--we just left the cavern from below cavern_data.tunnel_floor_count = cavern_data.tunnel_floor_count + 1
cavern_data.cavern_ceiling_count = cavern_data.cavern_ceiling_count + 1 tunnel_floor_nodes[cavern_data.tunnel_floor_count] = vi-area.ystride
cavern_ceiling_nodes[cavern_data.cavern_ceiling_count] = vi elseif this_node_state == inside_cavern then
elseif previous_node_state == inside_warren then -- we just entered the cavern from below
--we just left the cavern from below cavern_data.cavern_floor_count = cavern_data.cavern_floor_count + 1
cavern_data.warren_ceiling_count = cavern_data.warren_ceiling_count + 1 cavern_floor_nodes[cavern_data.cavern_floor_count] = vi - area.ystride
warren_ceiling_nodes[cavern_data.warren_ceiling_count] = vi elseif this_node_state == inside_warren then
elseif previous_node_state == inside_tunnel then -- we just entered the warren from below
-- we just left a tunnel from below cavern_data.warren_floor_count = cavern_data.warren_floor_count + 1
cavern_data.tunnel_ceiling_count = cavern_data.tunnel_ceiling_count + 1 warren_floor_nodes[cavern_data.warren_floor_count] = vi - area.ystride
tunnel_ceiling_nodes[cavern_data.tunnel_ceiling_count] = vi end
end elseif this_node_state == inside_ground then
if previous_node_state == inside_tunnel then
-- if we laid down a column node we don't want to switch to "inside ground", -- we just left a tunnel from below
-- if we hit air next node then it'll get flagged as a floor node and we don't want that for columns cavern_data.tunnel_ceiling_count = cavern_data.tunnel_ceiling_count + 1
if previous_node_state ~= inside_column then tunnel_ceiling_nodes[cavern_data.tunnel_ceiling_count] = vi
previous_node_state = inside_ground elseif previous_node_state == inside_cavern then
--we just left the cavern from below
cavern_data.cavern_ceiling_count = cavern_data.cavern_ceiling_count + 1
cavern_ceiling_nodes[cavern_data.cavern_ceiling_count] = vi
elseif previous_node_state == inside_warren then
--we just left the cavern from below
cavern_data.warren_ceiling_count = cavern_data.warren_ceiling_count + 1
warren_ceiling_nodes[cavern_data.warren_ceiling_count] = vi
end
end
end end
end end
else
-- This will prevent any values from being inserted into the node lists, saving
-- a bunch of memory and processor time
previous_node_state = outside_region
end end
end end

View File

@ -3,7 +3,7 @@
local enable_legacy = minetest.setting_getbool("subterrane_enable_legacy_dripstone") local enable_legacy = minetest.setting_getbool("subterrane_enable_legacy_dripstone")
if enable_legacy == nil or enable_legacy == true then if enable_legacy then
subterrane.register_stalagmite_nodes("subterrane:dry_stal", { subterrane.register_stalagmite_nodes("subterrane:dry_stal", {
description = "Dry Dripstone", description = "Dry Dripstone",
@ -18,7 +18,6 @@ minetest.register_node("subterrane:dry_flowstone", {
description = "Dry Flowstone", description = "Dry Flowstone",
tiles = {"default_stone.png^[brighten"}, tiles = {"default_stone.png^[brighten"},
groups = {cracky = 3, stone = 1}, groups = {cracky = 3, stone = 1},
is_ground_content = true,
drop = 'default:cobble', drop = 'default:cobble',
sounds = default.node_sound_stone_defaults(), sounds = default.node_sound_stone_defaults(),
}) })
@ -38,7 +37,6 @@ minetest.register_node("subterrane:wet_flowstone", {
description = "Wet Flowstone", description = "Wet Flowstone",
tiles = {"default_stone.png^[brighten^subterrane_dripstone_streaks.png"}, tiles = {"default_stone.png^[brighten^subterrane_dripstone_streaks.png"},
groups = {cracky = 3, stone = 1, subterrane_wet_dripstone = 1}, groups = {cracky = 3, stone = 1, subterrane_wet_dripstone = 1},
is_ground_content = true,
drop = 'default:cobble', drop = 'default:cobble',
sounds = default.node_sound_stone_defaults(), sounds = default.node_sound_stone_defaults(),
}) })
@ -435,7 +433,7 @@ end
local grid_size = mapgen_helper.block_size * 4 local grid_size = mapgen_helper.block_size * 4
function subterrane:vertically_consistent_randomp(pos) function subterrane:vertically_consistent_randomp(pos)
local next_seed = math.random(1, 2^31) local next_seed = math.floor(math.random() * 2^31)
math.randomseed(minetest.hash_node_position({x=pos.x, y=0, z=pos.z})) math.randomseed(minetest.hash_node_position({x=pos.x, y=0, z=pos.z}))
local output = math.random() local output = math.random()
math.randomseed(next_seed) math.randomseed(next_seed)

View File

@ -1 +1,4 @@
name = subterrane name = subterrane
description = A mapgen helper mod that facilitates the creation of vast underground caverns
depends = default, mapgen_helper
optional_depends = intllib

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -1,3 +1,3 @@
subterrane_enable_legacy_dripstone (Adds old dripstone node definitions) bool true subterrane_enable_legacy_dripstone (Adds old dripstone node definitions) bool false
#Use this mode with singlenode mapgen to produce a large-scale map of a particular world's caverns #Use this mode with singlenode mapgen to produce a large-scale map of a particular world's caverns
subterrane_enable_singlenode_mapping_mode (Fills cavern volume with stone and warrens with desertstone) bool false subterrane_enable_singlenode_mapping_mode (Fills cavern volume with stone and warrens with desertstone) bool false

Binary file not shown.

Before

Width:  |  Height:  |  Size: 465 B

After

Width:  |  Height:  |  Size: 381 B