-- original cave code modified from paramat's subterrain
-- Modified by HeroOfTheWinds for caverealms
-- Modified by FaceDeer for subterrane
-- Depends default
-- License: code MIT

local c_stone = minetest.get_content_id("default:stone")
local c_clay = minetest.get_content_id("default:clay")
local c_desert_stone = minetest.get_content_id("default:desert_stone")
local c_sandstone = minetest.get_content_id("default:sandstone")

local c_air = minetest.get_content_id("air")
local c_water = minetest.get_content_id("default:water_source")

local c_obsidian = minetest.get_content_id("default:obsidian")

local c_cavern_air = c_air
local c_warren_air = c_air

local subterrane_enable_singlenode_mapping_mode = minetest.settings:get_bool("subterrane_enable_singlenode_mapping_mode", false)
if subterrane_enable_singlenode_mapping_mode then
	c_cavern_air = c_stone
	c_warren_air = c_clay
end

local c_lava_set -- will be populated with a set of nodes that count as lava

-- Performance instrumentation
local t_start = os.clock()

subterrane = {} --create a container for functions and constants

subterrane.registered_layers = {}

--grab a shorthand for the filepath of the mod
local modpath = minetest.get_modpath(minetest.get_current_modname())

--load companion lua files
dofile(modpath.."/defaults.lua")
dofile(modpath.."/features.lua") -- some generic cave features useful for a variety of mapgens
dofile(modpath.."/player_spawn.lua") -- Function for spawning a player in a giant cavern
dofile(modpath.."/legacy.lua") -- contains old node definitions and functions, will be removed at some point in the future.

local disable_mapgen_caverns = function()
	local mg_name = minetest.get_mapgen_setting("mg_name")
	local flags_name
	local default_flags
	
	if mg_name == "v7" then 
		flags_name = "mgv7_spflags"
		default_flags = "mountains,ridges,nofloatlands"
	elseif mg_name == "v5" then
		flags_name = "mgv5_spflags"
		default_flags = ""
	else
		return
	end
	
	local function split(source, delimiters)
		local elements = {}
		local pattern = '([^'..delimiters..']+)'
		string.gsub(source, pattern, function(value) elements[#elements + 1] = value; end);
		return elements
	end
	
	local flags_setting = minetest.get_mapgen_setting(flags_name) or default_flags
	local new_flags = {}
	local flags = split(flags_setting, ", ")
	local nocaverns_present = false
	for _, flag in pairs(flags) do
		if flag ~= "caverns" then
			table.insert(new_flags, flag)
		end
		if flag == "nocaverns" then
			nocaverns_present = true
		end
	end
	if not nocaverns_present then
		table.insert(new_flags, "nocaverns")
	end
	minetest.set_mapgen_setting(flags_name, table.concat(new_flags, ","), true)
end
disable_mapgen_caverns()

-- Column stuff
----------------------------------------------------------------------------------

local grid_size = mapgen_helper.block_size * 4

subterrane.get_column_points = function(minp, maxp, column_def)
	local grids = mapgen_helper.get_nearest_regions(minp, grid_size)
	local points = {}
	for _, grid in ipairs(grids) do
		--The y value of the returned point will be the radius of the column
		local minp = {x=grid.x, y = column_def.minimum_radius*100, z=grid.z}
		local maxp = {x=grid.x+grid_size-1, y=column_def.maximum_radius*100, z=grid.z+grid_size-1}
		for _, point in ipairs(mapgen_helper.get_random_points(minp, maxp, column_def.minimum_count, column_def.maximum_count)) do
			point.y = point.y / 100
			if point.x > minp.x - point.y
				and point.x < maxp.x + point.y
				and point.z > minp.z - point.y
				and point.z < maxp.z + point.y then
				table.insert(points, point)
			end			
		end
	end
	return points
end

subterrane.get_column_value = function(pos, points)
	local heat = 0
	for _, point in ipairs(points) do
		local axis_point = {x=point.x, y=pos.y, z=point.z}
		local radius = point.y
		if (pos.x >= axis_point.x-radius and pos.x <= axis_point.x+radius
			and pos.z >= axis_point.z-radius and pos.z <= axis_point.z+radius) then
			
			local dist = vector.distance(pos, axis_point)
			if dist < radius then
				heat = math.max(heat, 1 - dist/radius)
			end
			
		end
	end
	return heat
end


-- Decoration node lists
----------------------------------------------------------------------------------

-- States any given node can be in. Used to detect boundaries
local outside_region = 1
local inside_ground = 2
local inside_tunnel = 3
local inside_cavern = 4
local inside_warren = 5
local inside_column = 6

-- These arrays will contain the indices of various nodes relevant to decoration
-- Note that table.getn and # will not correctly report the number of items in these since they're reused
-- between calls and are not cleared for efficiency. You can iterate through them using ipairs,
-- and you can get their content count from the similarly-named variable associated with them.
local cavern_data
local cavern_floor_nodes
local cavern_ceiling_nodes
local warren_floor_nodes
local warren_ceiling_nodes
local tunnel_floor_nodes
local tunnel_ceiling_nodes
local column_nodes

local initialize_node_arrays = function()
	cavern_data = {}
	cavern_floor_nodes = {}
	cavern_data.cavern_floor_nodes = cavern_floor_nodes
	cavern_data.cavern_floor_count = 0
	cavern_ceiling_nodes = {}
	cavern_data.cavern_ceiling_nodes = cavern_ceiling_nodes
	cavern_data.cavern_ceiling_count = 0
	warren_floor_nodes = {}
	cavern_data.warren_floor_nodes = warren_floor_nodes
	cavern_data.warren_floor_count = 0
	warren_ceiling_nodes = {}
	cavern_data.warren_ceiling_nodes = warren_ceiling_nodes
	cavern_data.warren_ceiling_count = 0
	tunnel_floor_nodes = {}
	cavern_data.tunnel_floor_nodes = tunnel_floor_nodes
	cavern_data.tunnel_floor_count = 0
	tunnel_ceiling_nodes = {}
	cavern_data.tunnel_ceiling_nodes = tunnel_ceiling_nodes
	cavern_data.tunnel_ceiling_count = 0
	column_nodes = {}
	cavern_data.column_nodes = column_nodes
	cavern_data.column_count = 0
end
initialize_node_arrays()

-- inserts nil after the last node so that ipairs will function correctly
local close_node_arrays = function()
	cavern_ceiling_nodes[cavern_data.cavern_ceiling_count + 1] = nil
	cavern_floor_nodes[cavern_data.cavern_floor_count + 1] = nil
	warren_ceiling_nodes[cavern_data.warren_ceiling_count + 1] = nil
	warren_floor_nodes[cavern_data.warren_floor_count + 1] = nil
	tunnel_ceiling_nodes[cavern_data.tunnel_ceiling_count + 1] = nil
	tunnel_floor_nodes[cavern_data.tunnel_floor_count + 1] = nil
	column_nodes[cavern_data.column_count + 1] = nil
end

-- clear the tables without deleting them - easer on memory management this way
local clear_node_arrays = function()
	cavern_data.cavern_ceiling_count = 0
	cavern_data.cavern_floor_count = 0
	cavern_data.warren_ceiling_count = 0
	cavern_data.warren_floor_count = 0
	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

-- cave_layer_def
--{
--	name = -- optional, defaults to the string "y_min to y_max" (with actual values inserted in place of y_min and y_max). Used for logging.
--	y_max = -- required, the highest elevation this cave layer will be generated in.
--	y_min = -- required, the lowest elevation this cave layer will be generated in.
--	cave_threshold = -- optional, Cave threshold. Defaults to 0.5. 1 = small rare caves, 0 = 1/2 ground volume
--	warren_region_threshold = -- optional, defaults to 0.25. Used to determine how much volume warrens take up around caverns. Set it to be equal to or greater than the cave threshold to disable warrens entirely.
--	warren_region_variability_threshold = -- optional, defaults to 0.25. Used to determine how much of the region contained within the warren_region_threshold actually has warrens in it.
--	warren_threshold = -- Optional, defaults to 0.25. Determines how "spongey" warrens are, lower numbers make tighter, less-connected warren passages.
--	boundary_blend_range = -- optional, range near ymin and ymax over which caves diminish to nothing. Defaults to 128.
--	perlin_cave = -- optional, a 3D perlin noise definition table to define the shape of the caves
--	perlin_wave = -- optional, a 3D perlin noise definition table that's averaged with the cave noise to add more horizontal surfaces (squash its spread on the y axis relative to perlin_cave to accomplish this)
--	perlin_warren_area = -- optional, a 3D perlin noise definition table for defining what places warrens form in
--	perlin_warrens = -- optional, a 3D perlin noise definition table for defining the warrens
--	solidify_lava = -- when set to true, lava near the edges of caverns is converted into obsidian to prevent it from spilling in.
--	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
--{
--	maximum_radius = -- Maximum radius for individual columns, defaults to 10
--	minimum_radius = -- Minimum radius for individual columns, defaults to 4 (going lower that this can increase the likelihood of "intermittent" columns with floating sections)
--	node = -- node name to build columns out of. Defaults to default:stone
--	warren_node = -- node name to build columns out of in warren areas. If not set, the nodes that would be columns in warrens will be left as original ground contents
--	weight = -- a floating point value (usually in the range of 0.5-1) to modify how strongly the column is affected by the surrounding cave. Lower values create a more variable, tapered stalactite/stalagmite combination whereas a value of 1 produces a roughly cylindrical column. Defaults to 0.25
--	maximum_count = -- The maximum number of columns placed in any given column region (each region being a square 4 times the length and width of a map chunk). Defaults to 50
--	minimum_count = -- The minimum number of columns placed in a column region. The actual number placed will be randomly selected between this range. Defaults to 0.
--}

-- cavern_data -- This is passed into the decorate method.
--{
--	cavern_floor_nodes = {} -- List of data indexes for nodes that are part of cavern floors. Note: Use ipairs() when iterating this, not pairs()
--	cavern_floor_count = 0 -- the count of nodes in the preceeding list.
--	cavern_ceiling_nodes = {} -- List of data indexes for nodes that are part of cavern ceilings. Note: Use ipairs() when iterating this, not pairs()
--	cavern_ceiling_count = 0 -- the count of nodes in the preceeding list.
--	warren_floor_nodes = {} -- List of data indexes for nodes that are part of warren floors. Note: Use ipairs() when iterating this, not pairs()
--	warren_floor_count = 0 -- the count of nodes in the preceeding list.
--	warren_ceiling_nodes = {} -- List of data indexes for nodes that are part of warren floors. Note: Use ipairs() when iterating this, not pairs()
--	warren_ceiling_count = 0 -- the count of nodes in the preceeding list.
--	tunnel_floor_nodes = {} -- List of data indexes for nodes that are part of floors in pre-existing tunnels (anything generated before this mapgen runs). Note: Use ipairs() when iterating this, not pairs()
--	tunnel_floor_count = 0 -- the count of nodes in the preceeding list.
--	tunnel_ceiling_nodes = {}  -- List of data indexes for nodes that are part of ceiling in pre-existing tunnels (anything generated before this mapgen runs). Note: Use ipairs() when iterating this, not pairs()
--	tunnel_ceiling_count = 0 -- the count of nodes in the preceeding list.
--	column_nodes = {} -- Nodes that belong to columns. Note that if the warren_node was not set in the column definition these might not have been replaced by anything yet. This list contains *all* column nodes, not just ones on the surface.
--	column_count = 0 -- the count of nodes in the preceeding list.

--	contains_cavern = false -- Use this if you want a quick check if the generated map chunk has any cavern volume in it. Don't rely on the node counts above, if the map chunk doesn't intersect the floor or ceiling those could be 0 even if a cavern is present.
--	contains_warren = false -- Ditto for contains_cavern
--	nvals_cave = nvals_cave -- The noise array used to generate the cavern.
--	cave_area = cave_area -- a VoxelArea for indexing nvals_cave
--	cavern_def = cave_layer_def -- a reference to the cave layer def.
--}

subterrane.register_layer = function(cave_layer_def)
	local error_out = false
	if cave_layer_def.y_min == nil then
		minetest.log("error", "[subterrane] cave layer def " .. tostring(cave_layer_def.name) .. " did not have a y_min defined. Not registered.")
		error_out = true
	end
	if cave_layer_def.y_max == nil then
		minetest.log("error", "[subterrane] cave layer def " .. tostring(cave_layer_def.name) .. " did not have a y_max defined. Not registered.")
		error_out = true
	end
	if error_out then return end
	
	local cave_name = cave_layer_def.name

	subterrane.set_defaults(cave_layer_def)
	
	local YMIN = cave_layer_def.y_min
	local YMAX = cave_layer_def.y_max
	
	if cave_layer_def.name == nil then
		cave_layer_def.name = tostring(YMIN) .. " to " .. tostring(YMAX)
	end
	
	table.insert(subterrane.registered_layers, cave_layer_def)

	local block_size = mapgen_helper.block_size
	
	if (YMAX+32+1)%block_size ~= 0 then
		local boundary = YMAX -(YMAX+32+1)%block_size
		minetest.log("warning", "[subterrane] The y_max setting "..tostring(YMAX)..
			" for cavern layer " .. cave_layer_def.name .. " is not aligned with map chunk boundaries. Consider "..
			tostring(boundary) .. " or " .. tostring(boundary+block_size) .. " for maximum mapgen efficiency.")
	end
	if (YMIN+32)%block_size ~= 0 then
		local boundary = YMIN - (YMIN+32)%block_size
		minetest.log("warning", "[subterrane] The y_min setting "..tostring(YMIN)..
			" for cavern layer " .. cave_layer_def.name .. " is not aligned with map chunk boundaries. Consider "..
			tostring(boundary) .. " or " .. tostring(boundary+block_size) .. " for maximum mapgen efficiency.")
	end

	local BLEND = math.min(cave_layer_def.boundary_blend_range, (YMAX-YMIN)/2)

	local TCAVE = cave_layer_def.cave_threshold
	local warren_area_threshold = cave_layer_def.warren_region_threshold -- determines how much volume warrens are found in around caverns
	local warren_area_variability_threshold = cave_layer_def.warren_region_variability_threshold -- determines how much of the warren_area_threshold volume actually has warrens in it
	local warren_threshold = cave_layer_def.warren_threshold -- determines narrowness of warrens themselves

	local solidify_lava = cave_layer_def.solidify_lava
	
	local np_cave = cave_layer_def.perlin_cave
	local np_wave = cave_layer_def.perlin_wave
	local np_warren_area = cave_layer_def.perlin_warren_area
	local np_warrens = cave_layer_def.perlin_warrens
	
	local y_blend_min = YMIN + BLEND * 1.5
	local y_blend_max = YMAX - BLEND * 1.5	
	
	local column_def = cave_layer_def.columns
	local c_column
	local c_warren_column

	if column_def then
		c_column = minetest.get_content_id(column_def.node)
		if column_def.warren_node then
			c_warren_column = minetest.get_content_id(column_def.warren_node)
		end
	end

	local double_frequency = cave_layer_def.double_frequency
		
	local decorate = cave_layer_def.decorate

	if subterrane_enable_singlenode_mapping_mode then
		decorate = nil
		c_column = c_air
		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
	end
	t_start = os.clock()

	if c_lava_set == nil then
		c_lava_set = {}
		for name, def in pairs(minetest.registered_nodes) do
			if def.groups ~= nil and def.groups.lava ~= nil then
				c_lava_set[minetest.get_content_id(name)] = true
			end
		end
	end
	
	local vm, data, data_param2, area = mapgen_helper.mapgen_vm_data_param2()
	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
	for vi, value in ipairs(nvals_cave) do
		nvals_cave[vi] = (value + nvals_wave[vi])/2
	end
	
	local warren_area_uninitialized = true
	local nvals_warren_area
	local warrens_uninitialized = true
	local nvals_warrens

	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
	
	-- This information might be of use to the decorate function.
	cavern_data.contains_cavern = false
	cavern_data.contains_warren = false
	cavern_data.nvals_cave = nvals_cave
	cavern_data.cave_area = cave_area
	cavern_data.cavern_def = cave_layer_def
	
	-- 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
			cave_local_threshold = TCAVE + ((y_blend_min - y) / BLEND) ^ 2
		elseif y > y_blend_max then
			cave_local_threshold = TCAVE + ((y - y_blend_max) / BLEND) ^ 2
		else
			cave_local_threshold = TCAVE
		end

		local cave_value = nvals_cave[vi]
		if double_frequency then
			if cave_value < 0 then
				cave_value = -cave_value
				if subterrane_enable_singlenode_mapping_mode then
						c_cavern_air = c_desert_stone
						c_warren_air = c_sandstone
				end
			else
				if subterrane_enable_singlenode_mapping_mode then
						c_cavern_air = c_stone
						c_warren_air = c_clay
				end

			end			
		end
		
		-- inside a giant cavern
		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(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
				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
			end
		end
		
		-- If there's lava near the edges of the cavern, solidify it.
		if solidify_lava and cave_value > cave_local_threshold - 0.05 and c_lava_set[data[vi]] then
			data[vi] = c_obsidian
		end
			
		--borderlands of a giant cavern, possible warren area
		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", emin, emax, np_warren_area) -- determine which areas are spongey with warrens
				warren_area_uninitialized = false
			end
			
			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
					data[vi] = c_obsidian					
				end
				
				if warrens_uninitialized then
					nvals_warrens = mapgen_helper.perlin3d("subterrane:warrens", emin, emax, np_warrens) --spongey warrens
					warrens_uninitialized = false
				end
				
				-- we don't want warrens "cutting off" abruptly at the large-scale boundary noise thresholds, so turn these into gradients
				-- that can be applied to choke off the warren gradually.
				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[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(emin, emax, 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 column_value + (warren_local_threshold - warren_value) * column_weight > 0 then
						if c_warren_column then
							if is_ground_content(data[vi]) then
								data[vi] = c_warren_column -- add a column node
							end
							this_node_state = inside_column
						end
					else
						if is_ground_content(data[vi]) then
							data[vi] = c_warren_air --hollow it out to make the cave
						end
						if is_within_current_mapblock then
							cavern_data.contains_warren = true
						end
						this_node_state = inside_warren
					end
				end
			end
		end
		
		-- 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 = 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
		end
	end
	
	if decorate then
		close_node_arrays() -- inserts nil after the last node so that ipairs will function correctly
		decorate(minp, maxp, seed, vm, cavern_data, area, data)
		clear_node_arrays() -- if decorate is not defined these arrays will never have anything added to them, so it's safe to not call this in that case
	end
	
	--send data back to voxelmanip
	vm:set_data(data)
	--calc lighting
	vm:set_lighting({day = 0, night = 0})
	vm:calc_lighting()
	vm:update_liquids()
	--write it to world
	vm:write_to_map()
	
	local time_taken = os.clock() - t_start -- how long this chunk took, in seconds
	mapgen_helper.record_time(cave_name, time_taken)
end)

end

local last_check = os.clock()
minetest.register_globalstep(function(dtime)
	local current_time = os.clock()
	local threshold = t_start + 60
	if last_check < threshold and current_time > threshold then
		-- It's been 60 seconds since we last generated a cavern, release the memory the cavern arrays are being stored in.
		initialize_node_arrays()
	end
	last_check = current_time
end)

minetest.log("info", "[Subterrane] loaded!")
