"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.
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.
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.paramtype = "light"
base_node_def.paramtype2 = "facedir"
base_node_def.is_ground_content = true
base_node_def.node_box = {type = "fixed"}
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_floor_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()
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.
-- 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.
-- 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
@ -315,11 +325,15 @@ subterrane.register_layer = function(cave_layer_def)
c_warren_column = nil
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
----------------------------------------------------------------------------------
minetest.register_on_generated(function(minp, maxp, seed)
--if out of range of cave definition limits, abort
if minp.y > YMAX or maxp.y < YMIN then
return
@ -336,8 +350,10 @@ minetest.register_on_generated(function(minp, maxp, seed)
end
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 nvals_wave = mapgen_helper.perlin3d("subterrane:wave", minp, maxp, np_wave) --wavy structure of cavern ceilings and floors
local emin = area.MinEdge
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
-- 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 nvals_warrens
-- The interp_yxz iterator iterates upwards in columns along the y axis.
-- 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_y = emin.y
local previous_node_state = outside_region
local this_node_state = outside_region
local column_points = 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.cavern_def = cave_layer_def
for vi, x, y, z in area:iterp_yxz(minp, maxp) do
local vi3d = cave_iterator() -- for use with noise data
-- The interp_yxz iterator iterates upwards in columns along the y axis.
-- 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
-- we've switched to a new column
previous_node_state = outside_region
else
previous_node_state = this_node_state
end
previous_y = y
this_node_state = inside_ground
local cave_local_threshold
if y < y_blend_min then
@ -385,7 +406,7 @@ minetest.register_on_generated(function(minp, maxp, seed)
cave_local_threshold = TCAVE
end
local cave_value = nvals_cave[vi3d]
local cave_value = nvals_cave[vi]
if double_frequency then
if cave_value < 0 then
cave_value = -cave_value
@ -403,28 +424,29 @@ minetest.register_on_generated(function(minp, maxp, seed)
end
-- inside a giant cavern
if cave_value > cave_local_threshold then
if cave_value > cave_local_threshold then
local column_value = 0
if column_def 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
end
column_value = subterrane.get_column_value({x=x, y=y, z=z}, column_points)
end
if column_value > 0 and cave_value - column_value * column_weight < cave_local_threshold then
data[vi] = c_column -- add a column node
previous_node_state = inside_column
else
data[vi] = c_cavern_air --hollow it out to make the cave
cavern_data.contains_cavern = true
if previous_node_state == inside_ground then
-- we just entered the cavern from below
cavern_data.cavern_floor_count = cavern_data.cavern_floor_count + 1
cavern_floor_nodes[cavern_data.cavern_floor_count] = vi - area.ystride
if is_ground_content(data[vi]) then
data[vi] = c_column -- add a column node
end
this_node_state = inside_column
else
if is_ground_content(data[vi]) then
data[vi] = c_cavern_air --hollow it out to make the cave
end
this_node_state = inside_cavern
if is_within_current_mapblock then
cavern_data.contains_cavern = true
end
previous_node_state = inside_cavern
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 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
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
-- we're in a warren-containing area
if solidify_lava and c_lava_set[data[vi]] then
@ -449,7 +471,7 @@ minetest.register_on_generated(function(minp, maxp, seed)
end
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
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 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)
if warren_value > warren_local_threshold then
local column_value = 0
if column_def 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
end
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 c_warren_column then
data[vi] = c_warren_column -- add a column node
previous_node_state = inside_column
if is_ground_content(data[vi]) then
data[vi] = c_warren_column -- add a column node
end
this_node_state = inside_column
end
else
data[vi] = c_warren_air --hollow it out to make the cave
cavern_data.contains_warren = true
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
if is_ground_content(data[vi]) then
data[vi] = c_warren_air --hollow it out to make the cave
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
@ -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 ~= nil then
local c_current_node = data[vi]
local current_node_is_open = mapgen_helper.buildable_to(c_current_node)
if previous_node_state == inside_column then
-- in this case previous node state is actually current node state,
-- we placed a column node during this loop
cavern_data.column_count = cavern_data.column_count + 1
column_nodes[cavern_data.column_count] = vi
elseif previous_node_state == inside_ground and current_node_is_open then
-- we just entered a tunnel from below
cavern_data.tunnel_floor_count = cavern_data.tunnel_floor_count + 1
tunnel_floor_nodes[cavern_data.tunnel_floor_count] = vi-area.ystride
previous_node_state = inside_tunnel
elseif previous_node_state ~= inside_ground and not current_node_is_open then
if 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
elseif previous_node_state == inside_tunnel then
-- we just left a tunnel from below
cavern_data.tunnel_ceiling_count = cavern_data.tunnel_ceiling_count + 1
tunnel_ceiling_nodes[cavern_data.tunnel_ceiling_count] = vi
end
-- if we laid down a column node we don't want to switch to "inside ground",
-- if we hit air next node then it'll get flagged as a floor node and we don't want that for columns
if previous_node_state ~= inside_column then
previous_node_state = inside_ground
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
-- we're in a preexisting open space (tunnel).
this_node_state = inside_tunnel
end
if is_within_current_mapblock then
if this_node_state == inside_column then
cavern_data.column_count = cavern_data.column_count + 1
column_nodes[cavern_data.column_count] = vi
elseif previous_node_state ~= this_node_state and previous_node_state ~= inside_column then
if previous_node_state == inside_ground then
if this_node_state == inside_tunnel then
-- we just entered a tunnel from below.
cavern_data.tunnel_floor_count = cavern_data.tunnel_floor_count + 1
tunnel_floor_nodes[cavern_data.tunnel_floor_count] = vi-area.ystride
elseif this_node_state == inside_cavern then
-- we just entered the cavern from below
cavern_data.cavern_floor_count = cavern_data.cavern_floor_count + 1
cavern_floor_nodes[cavern_data.cavern_floor_count] = vi - area.ystride
elseif this_node_state == inside_warren 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
elseif this_node_state == inside_ground then
if previous_node_state == inside_tunnel then
-- we just left a tunnel from below
cavern_data.tunnel_ceiling_count = cavern_data.tunnel_ceiling_count + 1
tunnel_ceiling_nodes[cavern_data.tunnel_ceiling_count] = vi
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
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

View File

@ -3,7 +3,7 @@
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", {
description = "Dry Dripstone",
@ -18,7 +18,6 @@ minetest.register_node("subterrane:dry_flowstone", {
description = "Dry Flowstone",
tiles = {"default_stone.png^[brighten"},
groups = {cracky = 3, stone = 1},
is_ground_content = true,
drop = 'default:cobble',
sounds = default.node_sound_stone_defaults(),
})
@ -38,7 +37,6 @@ minetest.register_node("subterrane:wet_flowstone", {
description = "Wet Flowstone",
tiles = {"default_stone.png^[brighten^subterrane_dripstone_streaks.png"},
groups = {cracky = 3, stone = 1, subterrane_wet_dripstone = 1},
is_ground_content = true,
drop = 'default:cobble',
sounds = default.node_sound_stone_defaults(),
})
@ -435,7 +433,7 @@ end
local grid_size = mapgen_helper.block_size * 4
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}))
local output = math.random()
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
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