1351 lines
		
	
	
		
			39 KiB
		
	
	
	
		
			Lua
		
	
	
			
		
		
	
	
			1351 lines
		
	
	
		
			39 KiB
		
	
	
	
		
			Lua
		
	
	
-- DEVELOPMENT ONLY: Timer module for debugging performance
 | 
						|
local modpath = minetest.get_modpath(minetest.get_current_modname())
 | 
						|
 | 
						|
dofile(modpath.."/lua/timer.lua")
 | 
						|
 | 
						|
-- DEVELOPMENT ONLY: End of timer module
 | 
						|
 | 
						|
-- Local variables used for programming
 | 
						|
local internal =
 | 
						|
    { a = nil
 | 
						|
 | 
						|
    -- Average size of a cave biome
 | 
						|
    , biome_size  = { x =   50, y =   50, z =   50 }
 | 
						|
 | 
						|
    -- Average blob size of connectivity noise params
 | 
						|
    , cnct_blob   = { x =  250, y =  100, z =  250 }
 | 
						|
 | 
						|
    -- The margin that a voxelmanip gives around the chunk being generated
 | 
						|
    , mapgen_buffer = 16
 | 
						|
 | 
						|
    -- Enum type used during cave generation for classifying different types
 | 
						|
    -- of cave nodes. Do not touch unless you're changing the code
 | 
						|
    , node_types =
 | 
						|
        { stick_edge = 0
 | 
						|
        , air = 1
 | 
						|
        , stone = 2
 | 
						|
        , floor = 3
 | 
						|
        , wall = 4
 | 
						|
        , ceiling = 5
 | 
						|
        , floor_deco = 6
 | 
						|
        }
 | 
						|
 | 
						|
    -- Point that can be used for generating Voronoi graphs
 | 
						|
    -- The number can be anything, as long as it reasonably far away from
 | 
						|
    -- the range 0 - 100
 | 
						|
    , outlandish_point = -1e3
 | 
						|
 | 
						|
    -- Random seeds used to influence noise
 | 
						|
    , seeds =
 | 
						|
        { connectivity =  297948
 | 
						|
        , heat         =  320523
 | 
						|
        , humidity     = 9923473
 | 
						|
        , verticality  =   35644
 | 
						|
        }
 | 
						|
 | 
						|
    -- Size of blocks being combined to simplify cave shape generation
 | 
						|
    -- Higher values means better performance, but lower detail and variations
 | 
						|
    , shape_cluster = { x =   32, y =   32, z =   32 }
 | 
						|
 | 
						|
    -- Average size of a shape biome
 | 
						|
    , shape_size    = { x =   50, y =   50, z =   50 }
 | 
						|
 | 
						|
    -- Average blob size of verticality noise params
 | 
						|
    , vrtcl_blob    = { x =  100, y =  250, z =  100 }
 | 
						|
 | 
						|
    -- If another mod doesn't override this value, we will assume that this is
 | 
						|
    -- the world's depth.
 | 
						|
    , world_depth   = -60
 | 
						|
 | 
						|
    -- Lower bound for the entire world
 | 
						|
    , world_minp    = { x = -1e6, y = -1e6, z = -1e6 }
 | 
						|
 | 
						|
    -- Upper bound for cave generation
 | 
						|
    , world_maxp    = { x =  1e6, y =  1e6, z =  1e6 }
 | 
						|
    }
 | 
						|
 | 
						|
-------------------------------------------------------------------------------
 | 
						|
-- Flat3dArray
 | 
						|
-------------------------------------------------------------------------------
 | 
						|
-- The Flat3dArray helps translate, transform and read data from different
 | 
						|
-- 1-dimensional flat tables that all represent some 3-dimensional set.
 | 
						|
local Flat3dArray = {}
 | 
						|
Flat3dArray.__index = Flat3dArray
 | 
						|
 | 
						|
-- Global variable that can be used by other mods
 | 
						|
-- Please see API.md for reference
 | 
						|
noordstar_caves =
 | 
						|
    { a = nil
 | 
						|
 | 
						|
    -- The `cave_vastness` is a variable that determines the size of caves.
 | 
						|
    -- The function takes in an x, y and z variable, and returns a number
 | 
						|
    -- between 0 and 1.
 | 
						|
    -- Low numbers mean very rare and tiny caves, while high numbers mean
 | 
						|
    -- massive caves, with massive open spaces.
 | 
						|
    -- If you wish to overwrite this function, it is good to keep in mind to:
 | 
						|
    --  - Make sure that the output changes VERY SLOWLY over time
 | 
						|
    --  - Keep the function performant as it will be executed many, many times
 | 
						|
    , cave_vastness = function(pos)
 | 
						|
        return pos.y / internal.world_depth
 | 
						|
    end
 | 
						|
 | 
						|
    -- A public list of all registered biomes
 | 
						|
    , registered_biomes = {}
 | 
						|
 | 
						|
    -- A public list of all registered decorations
 | 
						|
    , registered_decorations = {}
 | 
						|
 | 
						|
    -- A public list of all registered shapes
 | 
						|
    , registered_shapes = {}
 | 
						|
 | 
						|
    }
 | 
						|
 | 
						|
-- Remove all registered biomes and start with a clean slate
 | 
						|
function noordstar_caves.clear_registered_biomes()
 | 
						|
    noordstar_caves.registered_biomes = {}
 | 
						|
end
 | 
						|
 | 
						|
-- Remove all registered decorations and start with a clean slate
 | 
						|
function noordstar_caves.clear_registered_decoration()
 | 
						|
    noordstar_caves.registered_decorations = {}
 | 
						|
end
 | 
						|
 | 
						|
-- Remove all registered shapes and start with a clean slate
 | 
						|
function noordstar_caves.clear_registered_shapes()
 | 
						|
    noordstar_caves.registered_shapes = {}
 | 
						|
end
 | 
						|
 | 
						|
-- Register a new cave biome
 | 
						|
function noordstar_caves.register_biome(def)
 | 
						|
    local d = internal.clean_biome_def(def)
 | 
						|
 | 
						|
    if d then
 | 
						|
        noordstar_caves.registered_biomes[d.name] = d
 | 
						|
    end
 | 
						|
end
 | 
						|
 | 
						|
-- Register a new cave decoration
 | 
						|
function noordstar_caves.register_decoration(def)
 | 
						|
    local d = internal.clean_deco_def(def)
 | 
						|
 | 
						|
    if d then
 | 
						|
        table.insert(noordstar_caves.registered_decorations, d)
 | 
						|
    end
 | 
						|
end
 | 
						|
 | 
						|
-- Register a new cave shape
 | 
						|
function noordstar_caves.register_shape(def)
 | 
						|
    local d = internal.clean_shape_def(def)
 | 
						|
 | 
						|
    if d then
 | 
						|
        noordstar_caves.registered_shapes[d.name] = d
 | 
						|
    end
 | 
						|
end
 | 
						|
 | 
						|
-- Override the world's depth
 | 
						|
function noordstar_caves.set_world_depth(h)
 | 
						|
    internal.world_depth = h
 | 
						|
end
 | 
						|
noordstar_caves.set_world_depth(internal.world_depth)
 | 
						|
 | 
						|
-- Remove a specific registered cave biome
 | 
						|
function noordstar_caves.unregister_biome(name)
 | 
						|
    noordstar_caves.registered_biomes[name] = nil
 | 
						|
end
 | 
						|
 | 
						|
-- Remove a specific registered cave shape
 | 
						|
function noordstar_caves.unregister_shape(name)
 | 
						|
    noordstar_caves.registered_shapes[name] = nil
 | 
						|
end
 | 
						|
 | 
						|
 | 
						|
-- Calculate the distance of a given biome to a given point
 | 
						|
function internal.biome_def_distance(def, heat, humidity)
 | 
						|
    local dx = math.abs(humidity - def.humidity_point)
 | 
						|
    local dy = math.abs(heat - def.heat_point)
 | 
						|
 | 
						|
    return dx^2 + dy^2
 | 
						|
end
 | 
						|
 | 
						|
-- While a public `noordstar_caves.cave_vastness` function is exposed,
 | 
						|
-- this mod uses an improved internal function to keep the edges pleasant
 | 
						|
-- and to avoid potential errors.
 | 
						|
function internal.cave_vastness(pos)
 | 
						|
    -- Calculate value and put it in bounds
 | 
						|
    local v = noordstar_caves.cave_vastness(pos)
 | 
						|
    v = math.min(v, 1)
 | 
						|
    v = math.max(v, 0)
 | 
						|
 | 
						|
    local d = internal.world_depth
 | 
						|
 | 
						|
    if pos.y > d + 20 then
 | 
						|
        return v
 | 
						|
    elseif pos.y > d + 5 then
 | 
						|
        return v * (pos.y - (d + 5)) / 15
 | 
						|
    else
 | 
						|
        return 0
 | 
						|
    end
 | 
						|
end
 | 
						|
 | 
						|
-- Clean the user input on a biome definition before inserting it.
 | 
						|
function internal.clean_biome_def(def)
 | 
						|
    if type(def.name) ~= "string" then
 | 
						|
        return nil
 | 
						|
    end
 | 
						|
    if type(def.heat_point) ~= "number" then
 | 
						|
        return nil
 | 
						|
    end
 | 
						|
    if type(def.humidity_point) ~= "number" then
 | 
						|
        return nil
 | 
						|
    end
 | 
						|
 | 
						|
    local d = {
 | 
						|
        name = def.name,
 | 
						|
        heat_point = def.heat_point,
 | 
						|
        humidity_point = def.humidity_point,
 | 
						|
    }
 | 
						|
 | 
						|
    -- Position
 | 
						|
    if type(def.min_pos) == "table" then
 | 
						|
        d.minp = {
 | 
						|
            x = def.min_pos.x or internal.world_minp.x,
 | 
						|
            y = def.min_pos.y or internal.world_minp.y,
 | 
						|
            z = def.min_pos.z or internal.world_minp.z,
 | 
						|
        }
 | 
						|
    elseif type(def.y_min) == "number" then
 | 
						|
        d.minp = { 
 | 
						|
            x = internal.world_minp.x,
 | 
						|
            y = def.y_min,
 | 
						|
            z = internal.world_minp.z,
 | 
						|
        }
 | 
						|
    else
 | 
						|
        d.minp = {
 | 
						|
            x = internal.world_minp.x,
 | 
						|
            y = internal.world_minp.y,
 | 
						|
            z = internal.world_minp.z,
 | 
						|
        }
 | 
						|
    end
 | 
						|
 | 
						|
    if type(def.max_pos) == "table" then
 | 
						|
        d.maxp = {
 | 
						|
            x = def.max_pos.x or internal.world_maxp.x,
 | 
						|
            y = def.max_pos.y or internal.world_maxp.y,
 | 
						|
            z = def.max_pos.z or internal.world_maxp.z,
 | 
						|
        }
 | 
						|
    elseif type(def.y_max) == "number" then
 | 
						|
        d.maxp = {
 | 
						|
            x = internal.world_maxp.x,
 | 
						|
            y = def.y_max,
 | 
						|
            z = internal.world_maxp.z,
 | 
						|
        }
 | 
						|
    else
 | 
						|
        d.maxp = {
 | 
						|
            x = internal.world_maxp.x,
 | 
						|
            y = internal.world_maxp.y,
 | 
						|
            z = internal.world_maxp.z,
 | 
						|
        }
 | 
						|
    end
 | 
						|
 | 
						|
    -- Optional nodes
 | 
						|
    if type(def.node_dust) == "string" then
 | 
						|
        d.node_dust = def.node_dust
 | 
						|
    end
 | 
						|
    if type(def.node_floor) == "string" then
 | 
						|
        d.node_floor = def.node_floor
 | 
						|
    end
 | 
						|
    if type(def.node_wall) == "string" then
 | 
						|
        d.node_wall = def.node_wall
 | 
						|
    end
 | 
						|
    if type(def.node_roof) == "string" then
 | 
						|
        d.node_roof = def.node_roof
 | 
						|
    end
 | 
						|
    if type(def.node_air) == "string" then
 | 
						|
        d.node_air = def.node_air
 | 
						|
    end
 | 
						|
    if type(def.node_shell) == "string" then
 | 
						|
        d.node_shell = def.node_shell
 | 
						|
    end
 | 
						|
    if type(def.depth_shell) == "number" then
 | 
						|
        d.depth_shell = def.depth_shell
 | 
						|
    else
 | 
						|
        d.depth_shell = 0
 | 
						|
    end
 | 
						|
 | 
						|
    return d
 | 
						|
end
 | 
						|
 | 
						|
-- Clean the user input on a decoration definition before inserting it.
 | 
						|
function internal.clean_deco_def(def)
 | 
						|
    if def.deco_type ~= "simple" and def.deco_type ~= "schematic" then
 | 
						|
        return nil
 | 
						|
    end
 | 
						|
    if def.deco_type == "simple" and type(def.decoration) ~= "string" then
 | 
						|
        return nil
 | 
						|
    end
 | 
						|
    if def.deco_type == "schematic" then
 | 
						|
        if type(def.schematic) == "string" then
 | 
						|
        elseif type(def.schematic) == "table" then
 | 
						|
        else
 | 
						|
            return nil
 | 
						|
        end
 | 
						|
    end
 | 
						|
    if type(def.fill_ratio) ~= "number" then
 | 
						|
        return nil
 | 
						|
    end
 | 
						|
 | 
						|
    local d = {
 | 
						|
        deco_type = def.deco_type,
 | 
						|
        decoration = def.decoration,
 | 
						|
        schematic = def.schematic,
 | 
						|
        fill_ratio = def.fill_ratio,
 | 
						|
        biomes = def.biomes,
 | 
						|
    }
 | 
						|
 | 
						|
    if def.place_on == "floor" or def.place_on == "ceiling" then
 | 
						|
        d.place_on = def.place_on
 | 
						|
    else
 | 
						|
        d.place_on = "floor"
 | 
						|
    end
 | 
						|
 | 
						|
    local function place(key, t, default)
 | 
						|
        if type(def[key]) == t then
 | 
						|
            d[key] = def[key]
 | 
						|
        else
 | 
						|
            d[key] = default
 | 
						|
        end
 | 
						|
    end
 | 
						|
 | 
						|
    place("y_min", "number", internal.world_minp.y)
 | 
						|
    place("y_max", "number", internal.world_maxp.y)
 | 
						|
    place("height_max", "number", 0)
 | 
						|
    place("height", "number", 1)
 | 
						|
    place("place_offset_y", "number", 0)
 | 
						|
    place("replacements", "table", {})
 | 
						|
    place("flags", "string", "")
 | 
						|
    place("rotation", "string", "0")
 | 
						|
 | 
						|
    d.height     = math.max(d.height    ,  0)
 | 
						|
    d.height     = math.min(d.height    , 16)
 | 
						|
    d.height_max = math.max(d.height_max,  0)
 | 
						|
    d.height_max = math.min(d.height_max, 16)
 | 
						|
 | 
						|
    return d
 | 
						|
end
 | 
						|
 | 
						|
-- Clean the user input on a shape definition before inserting it.
 | 
						|
function internal.clean_shape_def(def)
 | 
						|
    if type(def.name) ~= "string" then
 | 
						|
        return nil
 | 
						|
    end
 | 
						|
    if type(def.connectivity_point) ~= "number" then
 | 
						|
        return nil
 | 
						|
    end
 | 
						|
    if type(def.verticality_point) ~= "number" then
 | 
						|
        return nil
 | 
						|
    end
 | 
						|
 | 
						|
    local d = {
 | 
						|
        name = def.name,
 | 
						|
        connectivity_point = def.connectivity_point,
 | 
						|
        verticality_point = def.verticality_point,
 | 
						|
 | 
						|
        y_min = def.y_min or internal.world_minp.y,
 | 
						|
        y_max = def.y_max or internal.world_maxp.y,
 | 
						|
    }
 | 
						|
 | 
						|
    if type(def.noise_params) == "table" then
 | 
						|
        d.noise_params = def.noise_params
 | 
						|
    end
 | 
						|
 | 
						|
    local func = function(pos, v) return v end
 | 
						|
    if type(def.func) == "function" then
 | 
						|
        func = def.func
 | 
						|
    end
 | 
						|
    d.func = internal.enhanced_shape_func(def.name, func)
 | 
						|
 | 
						|
    return d
 | 
						|
end
 | 
						|
 | 
						|
-- Get the most nearby cave biome
 | 
						|
function internal.closest_cave_biome(heat, humidity, pos)
 | 
						|
    local biome = internal.default_biome()
 | 
						|
    local d = internal.biome_def_distance(biome, heat, humidity)
 | 
						|
 | 
						|
    for _, def in pairs(noordstar_caves.registered_biomes) do
 | 
						|
        if internal.is_valid_pos(pos, def.minp, def.maxp) then
 | 
						|
            local new_d = internal.biome_def_distance(def, heat, humidity)
 | 
						|
    
 | 
						|
            if new_d <= d then
 | 
						|
                biome = def
 | 
						|
                d = new_d
 | 
						|
            end
 | 
						|
        end
 | 
						|
    end
 | 
						|
 | 
						|
    return biome
 | 
						|
end
 | 
						|
 | 
						|
-- Get the most nearby cave shape
 | 
						|
function internal.closest_cave_shape(cnct, vrtcl, y)
 | 
						|
    local shape = internal.default_shape()
 | 
						|
    local d = internal.shape_def_distance(shape, cnct, vrtcl)
 | 
						|
 | 
						|
    for key, def in pairs(noordstar_caves.registered_shapes) do
 | 
						|
        if def.y_min <= y and y <= def.y_max then
 | 
						|
            local new_d = internal.shape_def_distance(def, cnct, vrtcl)
 | 
						|
    
 | 
						|
            if new_d <= d then
 | 
						|
                shape = def
 | 
						|
                d = new_d
 | 
						|
            end
 | 
						|
        end
 | 
						|
    end
 | 
						|
 | 
						|
    return shape
 | 
						|
end
 | 
						|
 | 
						|
-- Get connectivity noise params
 | 
						|
function internal.connectivity_noise_params()
 | 
						|
    local factor = math.max(
 | 
						|
        math.abs(#noordstar_caves.registered_shapes) ^ 0.5
 | 
						|
        , 1
 | 
						|
    )
 | 
						|
 | 
						|
    return {
 | 
						|
        offset = 50,
 | 
						|
        scale = 50,
 | 
						|
        spread =
 | 
						|
            internal.reduced_shape_pos(
 | 
						|
                { x = factor * internal.cnct_blob.x
 | 
						|
                , y = factor * internal.cnct_blob.y
 | 
						|
                , z = factor * internal.cnct_blob.z
 | 
						|
                }
 | 
						|
            ),
 | 
						|
        seed = internal.seeds.connectivity,
 | 
						|
        octaves = 2,
 | 
						|
        persistence = 0.2,
 | 
						|
        lacunarity = 2.0,
 | 
						|
        flags = "eased"
 | 
						|
    }
 | 
						|
end
 | 
						|
 | 
						|
-- Get a default cave biome in case no biomes are registered
 | 
						|
function internal.default_biome()
 | 
						|
    return internal.clean_biome_def(
 | 
						|
        { name = "noordstar_caves:default_biome"
 | 
						|
        , heat_point = internal.outlandish_point
 | 
						|
        , humidity_point = internal.outlandish_point
 | 
						|
        }
 | 
						|
    )
 | 
						|
end
 | 
						|
 | 
						|
-- Get a default cave shape in case no shapes are registered
 | 
						|
function internal.default_shape()
 | 
						|
    return internal.clean_shape_def(
 | 
						|
        { name = "noordstar_caves:none"
 | 
						|
        , connectivity_point = internal.outlandish_point
 | 
						|
        , verticality_point = internal.outlandish_point
 | 
						|
        , func = function (pos, v) return 0 end
 | 
						|
        }
 | 
						|
    )
 | 
						|
end
 | 
						|
 | 
						|
-- This library decides not to trust that the user is giving a proper input
 | 
						|
-- on its function field. To help the developer insert a proper function, a few
 | 
						|
-- error messages are inserted to give the developer more insight in how their
 | 
						|
-- function behaves.
 | 
						|
function internal.enhanced_shape_func(name, func)
 | 
						|
    if type(func) ~= "function" then
 | 
						|
        error("Expected function, found type `" .. type(func) .. "`")
 | 
						|
    end
 | 
						|
 | 
						|
    return function (pos, n)
 | 
						|
        local out = func(pos, n)
 | 
						|
 | 
						|
        if type(out) == "number" then
 | 
						|
            return out
 | 
						|
        elseif n == nil then
 | 
						|
            error(
 | 
						|
                table.concat(
 | 
						|
                    { "Shape"
 | 
						|
                    , name
 | 
						|
                    , "function must return a number."
 | 
						|
                    , "Your function's input `n` was nil."
 | 
						|
                    , "Perhaps your `noise_params` field is invalid?"
 | 
						|
                    }
 | 
						|
                    , " "
 | 
						|
                )
 | 
						|
            )
 | 
						|
        else
 | 
						|
            error(
 | 
						|
                table.concat(
 | 
						|
                    { "Shape"
 | 
						|
                    , name
 | 
						|
                    , "function must return a number."
 | 
						|
                    }
 | 
						|
                    , " "
 | 
						|
                )
 | 
						|
            )
 | 
						|
        end
 | 
						|
    end
 | 
						|
end
 | 
						|
 | 
						|
-- Get a Flat3dArray showing whether nodes are cave bools
 | 
						|
function internal.flat_from_cave_bools(minp, maxp)
 | 
						|
    -- Calculate connectivity and verticality for each point
 | 
						|
    local connectivity = internal.flat_from_noise_params(
 | 
						|
        internal.connectivity_noise_params(),
 | 
						|
        internal.reduced_shape_pos(minp),
 | 
						|
        internal.reduced_shape_pos(maxp)
 | 
						|
    )
 | 
						|
    local verticality = internal.flat_from_noise_params(
 | 
						|
        internal.verticality_noise_params(),
 | 
						|
        internal.reduced_shape_pos(minp),
 | 
						|
        internal.reduced_shape_pos(maxp)
 | 
						|
    )
 | 
						|
    
 | 
						|
    -- Using the calculated noise, determine the cave shape per shape chunk
 | 
						|
    local reduced = Flat3dArray:from_func(
 | 
						|
        internal.reduced_shape_pos(minp),
 | 
						|
        internal.reduced_shape_pos(maxp),
 | 
						|
        function (i, pos)
 | 
						|
            -- DEVELOPMENT ONLY: Unittest
 | 
						|
            if connectivity:pos_to_index(pos) ~= i then
 | 
						|
                error("Connectivity index doesn't match local index")
 | 
						|
            end
 | 
						|
            if verticality:pos_to_index(pos) ~= i then
 | 
						|
                error("Verticality index doesn't match local index")
 | 
						|
            end
 | 
						|
            -- DEVELOPMENT ONLY: End unittest
 | 
						|
 | 
						|
            local cnct = connectivity:get_index(i)
 | 
						|
            local vrtcl = verticality:get_index(i)
 | 
						|
 | 
						|
            local def = internal.closest_cave_shape(cnct, vrtcl, pos.y)
 | 
						|
            
 | 
						|
            return def.name
 | 
						|
    end)
 | 
						|
 | 
						|
    -- Calculate noise for each shape
 | 
						|
    local noise = {}
 | 
						|
    for key, shape in pairs(noordstar_caves.registered_shapes) do
 | 
						|
        noise[key] = internal.flat_from_shape_def(shape, minp, maxp)
 | 
						|
    end
 | 
						|
 | 
						|
    local default = internal.default_shape()
 | 
						|
    noise[default.name] = internal.flat_from_shape_def(default, minp, maxp)
 | 
						|
 | 
						|
    -- Create a flat array of bools
 | 
						|
    local bools = Flat3dArray:from_func(minp, maxp, function (i, pos)
 | 
						|
        local key = reduced:get_pos(internal.reduced_shape_pos(pos))
 | 
						|
 | 
						|
        if noise[key] == nil then
 | 
						|
            error("Key " .. key .. " gave no value on noise")
 | 
						|
        end
 | 
						|
 | 
						|
        local n = noise[key]:get_index(i)
 | 
						|
        local v = internal.cave_vastness(pos)
 | 
						|
 | 
						|
        return internal.is_cave_node(n, v)
 | 
						|
    end)
 | 
						|
 | 
						|
    return bools
 | 
						|
end
 | 
						|
 | 
						|
function internal.flat_from_node_types(bools, minp, maxp)
 | 
						|
    local nt = internal.node_types
 | 
						|
 | 
						|
    -- local bools = internal.flat_from_cave_bools(minp, maxp)
 | 
						|
 | 
						|
    local node_types = Flat3dArray:from_func(minp, maxp, function (i, pos)
 | 
						|
        -- Simplify calculation by ignoring edges
 | 
						|
        if pos.x == minp.x or pos.x == maxp.x then
 | 
						|
            return nt.stick_edge
 | 
						|
        elseif pos.y == minp.y or pos.y == maxp.y then
 | 
						|
            return nt.stick_edge
 | 
						|
        elseif pos.z == maxp.z or pos.z == minp.z then
 | 
						|
            return nt.stick_edge
 | 
						|
        end
 | 
						|
        
 | 
						|
        if bools:get_index(i) then
 | 
						|
            -- PART OF CAVE
 | 
						|
 | 
						|
            if bools:get_index(i + bools.down) == false then
 | 
						|
                -- Block is right on the floor
 | 
						|
                return nt.floor_deco
 | 
						|
            end
 | 
						|
 | 
						|
            return nt.air
 | 
						|
        else
 | 
						|
            -- NOT PART OF CAVE
 | 
						|
 | 
						|
            -- Floor
 | 
						|
            if bools:get_index(i + bools.up) then
 | 
						|
                return nt.floor
 | 
						|
            end
 | 
						|
 | 
						|
            -- Ceiling
 | 
						|
            if bools:get_index(i + bools.down) then
 | 
						|
                return nt.ceiling
 | 
						|
            end
 | 
						|
 | 
						|
            -- Walls
 | 
						|
            if bools:get_index(i + bools.north) then
 | 
						|
                return nt.wall
 | 
						|
            end
 | 
						|
            if bools:get_index(i + bools.east) then
 | 
						|
                return nt.wall
 | 
						|
            end
 | 
						|
            if bools:get_index(i + bools.south) then
 | 
						|
                return nt.wall
 | 
						|
            end
 | 
						|
            if bools:get_index(i + bools.west) then
 | 
						|
                return nt.wall
 | 
						|
            end
 | 
						|
 | 
						|
            return nt.stone
 | 
						|
        end
 | 
						|
    end)
 | 
						|
 | 
						|
    return node_types
 | 
						|
end
 | 
						|
 | 
						|
-- Get a Flat3dArray using noise_params
 | 
						|
function internal.flat_from_noise_params(noise_params, minp, maxp)
 | 
						|
    local nx = maxp.x - minp.x + 1
 | 
						|
    local ny = maxp.y - minp.y + 1
 | 
						|
    local nz = maxp.z - minp.z + 1
 | 
						|
 | 
						|
    local buffer = {}
 | 
						|
 | 
						|
    local p = PerlinNoiseMap(noise_params, { x = nx, y = ny, z = nz })
 | 
						|
 | 
						|
    if nz == 1 then
 | 
						|
        p:get_2d_map_flat(minp, buffer)
 | 
						|
    else
 | 
						|
        p:get_3d_map_flat(minp, buffer)
 | 
						|
    end
 | 
						|
 | 
						|
    return Flat3dArray:new(minp, maxp, buffer)
 | 
						|
end
 | 
						|
 | 
						|
-- Get a Flat3dArray about shape noise
 | 
						|
-- This function is defined differently from other definitions because the
 | 
						|
-- noise_params are optional in the shape definition
 | 
						|
function internal.flat_from_shape_def(def, minp, maxp)
 | 
						|
    local noise_flat_map = {}
 | 
						|
 | 
						|
    local nx = maxp.x - minp.x + 1
 | 
						|
    local ny = maxp.y - minp.y + 1
 | 
						|
    local nz = maxp.z - minp.z + 1
 | 
						|
 | 
						|
    if def.noise_params then
 | 
						|
        local p = PerlinNoiseMap(def.noise_params, { x = nx, y = ny, z = nz })
 | 
						|
        
 | 
						|
        if nz == 1 then
 | 
						|
            p:get_2d_map_flat(minp, noise_flat_map)
 | 
						|
        else
 | 
						|
            p:get_3d_map_flat(minp, noise_flat_map)
 | 
						|
        end
 | 
						|
    end
 | 
						|
 | 
						|
    internal.iter_3d_area(minp, maxp, function (i, pos)
 | 
						|
        noise_flat_map[i] = def.func(pos, noise_flat_map[i])
 | 
						|
    end)
 | 
						|
 | 
						|
    return Flat3dArray:new(minp, maxp, noise_flat_map)
 | 
						|
end
 | 
						|
 | 
						|
-- Convert 3d relative coordinates to an index on a flat array
 | 
						|
function internal.from_3d_to_flat(dx, dy, dz, nx, ny)
 | 
						|
    return (nx * ny * dz) + (nx * dy) + dx + 1
 | 
						|
end
 | 
						|
 | 
						|
-- Generate caves given a voxelmanip array
 | 
						|
function internal.generate_caves(data, minp, maxp)
 | 
						|
    -- Increased voxelmanip size
 | 
						|
    local vminp =
 | 
						|
        { x = minp.x - internal.mapgen_buffer
 | 
						|
        , y = minp.y - internal.mapgen_buffer
 | 
						|
        , z = minp.z - internal.mapgen_buffer
 | 
						|
        }
 | 
						|
    local vmaxp =
 | 
						|
        { x = maxp.x + internal.mapgen_buffer
 | 
						|
        , y = maxp.y + internal.mapgen_buffer
 | 
						|
        , z = maxp.z + internal.mapgen_buffer
 | 
						|
        }
 | 
						|
 | 
						|
    -- Increased node type size to account for unknown edges
 | 
						|
    local bminp =
 | 
						|
        { x = minp.x - 1
 | 
						|
        , y = minp.y - 1
 | 
						|
        , z = minp.z - 1
 | 
						|
        }
 | 
						|
    local bmaxp =
 | 
						|
        { x = maxp.x + 1
 | 
						|
        , y = maxp.y + 1
 | 
						|
        , z = maxp.z + 1
 | 
						|
        }
 | 
						|
 | 
						|
    -- Load data
 | 
						|
    local vmanip = Flat3dArray:new(vminp, vmaxp, data)
 | 
						|
    
 | 
						|
    -- Get cave bools
 | 
						|
    local bools = internal.flat_from_cave_bools(bminp, bmaxp)
 | 
						|
 | 
						|
    timer.checkpoint("Calculate cave shape")
 | 
						|
 | 
						|
    -- Get node types
 | 
						|
    local nts = internal.flat_from_node_types(bools, bminp, bmaxp)
 | 
						|
 | 
						|
    -- Calculate biome heat & humidity
 | 
						|
    local heat_points = internal.flat_from_noise_params(
 | 
						|
        internal.heat_noise_params(), bminp, bmaxp
 | 
						|
    )
 | 
						|
    local humidity_points = internal.flat_from_noise_params(
 | 
						|
        internal.humidity_noise_params(), bminp, bmaxp
 | 
						|
    )
 | 
						|
 | 
						|
    timer.checkpoint("Calculate block types & biomes")
 | 
						|
 | 
						|
    local air = minetest.get_content_id("air")
 | 
						|
    local schems = {}
 | 
						|
 | 
						|
    -- Place blocks where necessary
 | 
						|
    internal.iter_3d_area(bminp, bmaxp, function (i, pos)
 | 
						|
        
 | 
						|
        local vi = vmanip:pos_to_index(pos)
 | 
						|
        local function place(name)
 | 
						|
 | 
						|
            if vmanip:get_index(vi) == air then
 | 
						|
            elseif type(name) == "string" then
 | 
						|
                vmanip:set_index(vi, minetest.get_content_id(name))
 | 
						|
            elseif type(name) == "nil" then
 | 
						|
            else
 | 
						|
                error("Inserted invalid type " .. type(name) .. " into voxelmanip array")
 | 
						|
            end
 | 
						|
        end
 | 
						|
 | 
						|
        local nt = nts:get_index(i)
 | 
						|
 | 
						|
        if nt == internal.node_types.stone then
 | 
						|
        elseif nt == internal.node_types.stick_edge then
 | 
						|
        elseif nt == internal.node_types.air then
 | 
						|
            place("air")
 | 
						|
        else
 | 
						|
            -- Find appropriate biome
 | 
						|
            local heat = heat_points:get_index(i)
 | 
						|
            local humidity = humidity_points:get_index(i)
 | 
						|
 | 
						|
            local def = internal.closest_cave_biome(heat, humidity, pos)
 | 
						|
 | 
						|
            if nt == internal.node_types.floor then
 | 
						|
                place(def.node_floor)
 | 
						|
            elseif nt == internal.node_types.wall then
 | 
						|
                place(def.node_wall)
 | 
						|
            elseif nt == internal.node_types.ceiling then
 | 
						|
                place(def.node_roof)
 | 
						|
            elseif nt == internal.node_types.floor_deco then
 | 
						|
                local schem_placed = false
 | 
						|
 | 
						|
                if not schem_placed then
 | 
						|
                    -- Prevent decorations from spawning in the air
 | 
						|
                    if vmanip:get_index(vi) == air then
 | 
						|
                        schem_placed = true
 | 
						|
                    end
 | 
						|
                end
 | 
						|
 | 
						|
                if not schem_placed then
 | 
						|
                    for _, deco in ipairs(noordstar_caves.registered_decorations) do
 | 
						|
                        if deco.place_on ~= "floor" then
 | 
						|
                        elseif math.random() > deco.fill_ratio then
 | 
						|
                        elseif not internal.is_deco_biome(deco, def.name) then
 | 
						|
                        else
 | 
						|
                            table.insert(schems, { pos = pos, deco = deco })
 | 
						|
                            schem_placed = true
 | 
						|
                            break
 | 
						|
                        end
 | 
						|
                    end
 | 
						|
                end
 | 
						|
 | 
						|
                if not schem_placed then
 | 
						|
                    place(def.node_dust or "air")
 | 
						|
                end
 | 
						|
            else
 | 
						|
                error(
 | 
						|
                    table.concat(
 | 
						|
                        { "Found unknown node type "
 | 
						|
                        , nt
 | 
						|
                        , " (type "
 | 
						|
                        , type(nt)
 | 
						|
                        , ")"
 | 
						|
                        }
 | 
						|
                        , ""
 | 
						|
                    )
 | 
						|
                )
 | 
						|
            end
 | 
						|
        end
 | 
						|
    end)
 | 
						|
 | 
						|
    return vmanip, schems
 | 
						|
end
 | 
						|
 | 
						|
-- Get the noise params for the cave biome temperature.
 | 
						|
function internal.heat_noise_params()
 | 
						|
    return {
 | 
						|
        offset = 50,
 | 
						|
        scale = 50,
 | 
						|
        spread = internal.biome_size,
 | 
						|
        seed = internal.seeds.heat,
 | 
						|
        octaves = 2,
 | 
						|
        persistence = 0.1,
 | 
						|
        lacunarity = 2.0,
 | 
						|
        flags = ""
 | 
						|
    }
 | 
						|
end
 | 
						|
 | 
						|
-- Get the noise params for the cave biome humidity.
 | 
						|
function internal.humidity_noise_params()
 | 
						|
    return { 
 | 
						|
        offset = 50,
 | 
						|
        scale = 50,
 | 
						|
        spread = internal.biome_size,
 | 
						|
        seed = internal.seeds.humidity,
 | 
						|
        octaves = 2,
 | 
						|
        persistence = 0.1,
 | 
						|
        lacunarity = 2.0,
 | 
						|
        flags = ""
 | 
						|
    }
 | 
						|
end
 | 
						|
 | 
						|
-- Return whether a node is part of a cave
 | 
						|
function internal.is_cave_node(noise, vastness)
 | 
						|
    return noise > 1 - vastness
 | 
						|
end
 | 
						|
 | 
						|
-- Return whether a given decoration definition can spawn in a given biome name
 | 
						|
function internal.is_deco_biome(def, biome_name)
 | 
						|
    if type(def.biomes) == "nil" then
 | 
						|
        return true
 | 
						|
    elseif type(def.biomes) == "string" then
 | 
						|
        return def.biomes == biome_name
 | 
						|
    elseif type(def.biomes) == "table" then
 | 
						|
        for _, name in pairs(def.biomes) do
 | 
						|
            if name == biome_name then
 | 
						|
                return true
 | 
						|
            end
 | 
						|
        end
 | 
						|
    end
 | 
						|
 | 
						|
    return false
 | 
						|
end
 | 
						|
 | 
						|
-- Return a bool whether a position is within a given volume
 | 
						|
function internal.is_valid_pos(pos, minp, maxp)
 | 
						|
    if minp.x <= pos.x and pos.x <= maxp.x then
 | 
						|
        if minp.y <= pos.y and pos.y <= maxp.y then
 | 
						|
            if minp.z <= pos.z and pos.z <= maxp.z then
 | 
						|
                return true
 | 
						|
            end
 | 
						|
        end
 | 
						|
    end
 | 
						|
 | 
						|
    return false
 | 
						|
end
 | 
						|
 | 
						|
-- Iterate over a 3d area, both indexing by the index and the ansolute position
 | 
						|
function internal.iter_3d_area(minp, maxp, callback)
 | 
						|
    local nx = maxp.x - minp.x + 1
 | 
						|
    local ny = maxp.y - minp.y + 1
 | 
						|
    -- local nz = maxp.z - minp.z + 1
 | 
						|
 | 
						|
    local i = 0
 | 
						|
 | 
						|
    for z = minp.z, maxp.z, 1 do
 | 
						|
        for y = minp.y, maxp.y, 1 do
 | 
						|
            for x = minp.x, maxp.x, 1 do
 | 
						|
                local pos = { x = x, y = y, z = z }
 | 
						|
                i = i + 1
 | 
						|
 | 
						|
                -- DEVELOPMENT ONLY: This function only serves as a unittest
 | 
						|
                -- DEVELOPMENT ONLY: to verify that the iteration across a 3D
 | 
						|
                -- DEVELOPMENT ONLY: table works appropriately.
 | 
						|
                -- DEVELOPMENT ONLY: This calculation is highly inefficient and
 | 
						|
                -- DEVELOPMENT ONLY: unnecessary so it is highly recommended
 | 
						|
                -- DEVELOPMENT ONLY: to remove this code in production.
 | 
						|
                local dx = x - minp.x
 | 
						|
                local dy = y - minp.y
 | 
						|
                local dz = z - minp.z
 | 
						|
                local si = internal.from_3d_to_flat(dx, dy, dz, nx, ny)
 | 
						|
 | 
						|
                if i ~= si then
 | 
						|
                    error(
 | 
						|
                        table.concat(
 | 
						|
                            { "Expected position"
 | 
						|
                            , internal.pos_to_str(pos)
 | 
						|
                            , "to correspond to index"
 | 
						|
                            , si
 | 
						|
                            , "but our iteration arrived at index"
 | 
						|
                            , i
 | 
						|
                            }
 | 
						|
                            , " "
 | 
						|
                        )
 | 
						|
                    )
 | 
						|
                end
 | 
						|
                -- DEVELOPMENT ONLY:
 | 
						|
                -- DEVELOPMENT ONLY: This is the bottom of the unittest.
 | 
						|
                -- DEVELOPMENT ONLY: Please remove this code in a future commit
 | 
						|
                -- DEVELOPMENT ONLY: before the mod is assumed to be
 | 
						|
                -- DEVELOPMENT ONLY: production-ready.
 | 
						|
                -- DEVELOPMENT ONLY:
 | 
						|
 | 
						|
                callback(i, pos)
 | 
						|
            end
 | 
						|
        end
 | 
						|
    end
 | 
						|
end
 | 
						|
 | 
						|
-- -- Log text to the Minetest chat
 | 
						|
-- function internal.log(text)
 | 
						|
--     minetest.chat_send_all(os.time() .. " - " .. text)
 | 
						|
-- end
 | 
						|
 | 
						|
-- Place all schematic decorations into the world
 | 
						|
function internal.place_schematic_decorations(vmanip, schems)
 | 
						|
    for _, schem in ipairs(schems) do
 | 
						|
        local pos  = schem.pos
 | 
						|
        local deco = schem.deco
 | 
						|
 | 
						|
        if deco.deco_type ~= "schematic" then
 | 
						|
        else
 | 
						|
            minetest.place_schematic_on_vmanip(
 | 
						|
                vmanip, pos,
 | 
						|
                deco.schematic, deco.rotation, deco.replacement,
 | 
						|
                true, deco.flags
 | 
						|
            )
 | 
						|
        end
 | 
						|
    end
 | 
						|
end
 | 
						|
 | 
						|
-- Place all simple decorations into the world
 | 
						|
function internal.place_simple_decorations(flat, schems)
 | 
						|
    for _, schem in ipairs(schems) do
 | 
						|
        if schem.deco.deco_type == "simple" then
 | 
						|
            local pos  = schem.pos
 | 
						|
            local deco = schem.deco
 | 
						|
            
 | 
						|
            local i = flat:pos_to_index(pos)
 | 
						|
            
 | 
						|
            local dir = nil
 | 
						|
            
 | 
						|
            if deco.place_on == "floor" then
 | 
						|
                dir = flat.up
 | 
						|
            elseif deco.place_on == "ceiling" then
 | 
						|
                dir = flat.down
 | 
						|
            else
 | 
						|
                error("Invalid place_on value `" .. deco.place_on .. "`")
 | 
						|
            end
 | 
						|
            
 | 
						|
            local h_min = deco.height
 | 
						|
            local h_max = math.max(deco.height, deco.height_max)
 | 
						|
            
 | 
						|
            for dy = 0, math.random(h_min, h_max) - 1, 1 do
 | 
						|
                local li = i + dir * dy
 | 
						|
                
 | 
						|
                flat:set_index(li, minetest.get_content_id(deco.decoration))
 | 
						|
            end
 | 
						|
        
 | 
						|
        end
 | 
						|
    end
 | 
						|
end
 | 
						|
 | 
						|
-- Helper function to convert a set of coordinates to a readable string
 | 
						|
function internal.pos_to_str(pos)
 | 
						|
    return "(" .. pos.x .. ", " .. pos.y .. ", " .. pos.z .. " )"
 | 
						|
end
 | 
						|
 | 
						|
-- Get a reduced position based on the chunks
 | 
						|
function internal.reduced_shape_pos(pos)
 | 
						|
    return
 | 
						|
        { x = math.floor(pos.x / internal.shape_cluster.x)
 | 
						|
        , y = math.floor(pos.y / internal.shape_cluster.y)
 | 
						|
        , z = math.floor(pos.z / internal.shape_cluster.z)
 | 
						|
        }
 | 
						|
end
 | 
						|
 | 
						|
-- Calculate the relative index change to move downwards one position
 | 
						|
function internal.relative_get_down(nx, ny)
 | 
						|
    return internal.relative_index_diff(0, -1, 0, nx, ny)
 | 
						|
end
 | 
						|
 | 
						|
-- Calculate the relative index change to move eastwards one position
 | 
						|
function internal.relative_get_east(nx, ny)
 | 
						|
    return internal.relative_index_diff(1, 0, 0, nx, ny)
 | 
						|
end
 | 
						|
 | 
						|
-- Calculate the relative index change to move northwards one position
 | 
						|
function internal.relative_get_north(nx, ny)
 | 
						|
    return internal.relative_index_diff(0, 0, 1, nx, ny)
 | 
						|
end
 | 
						|
 | 
						|
-- Calculate the relative index change to move southwards one position
 | 
						|
function internal.relative_get_south(nx, ny)
 | 
						|
    return internal.relative_index_diff(0, 0, -1, nx, ny)
 | 
						|
end
 | 
						|
 | 
						|
-- Calculate the relative index change to move upwards one position
 | 
						|
function internal.relative_get_up(nx, ny)
 | 
						|
    return internal.relative_index_diff(0, 1, 0, nx, ny)
 | 
						|
end
 | 
						|
 | 
						|
-- Calculate the relative index change to move westwards one position
 | 
						|
function internal.relative_get_west(nx, ny)
 | 
						|
    return internal.relative_index_diff(-1, 0, 0, nx, ny)
 | 
						|
end
 | 
						|
 | 
						|
-- Calculate the difference in index to relative coordinates.
 | 
						|
-- Used to speed up index lookup on flat 3d arrays.
 | 
						|
function internal.relative_index_diff(dx, dy, dz, nx, ny)
 | 
						|
    return
 | 
						|
        ( internal.from_3d_to_flat(dx, dy, dz, nx, ny)
 | 
						|
        - internal.from_3d_to_flat( 0,  0,  0, nx, ny)
 | 
						|
        )
 | 
						|
end
 | 
						|
 | 
						|
-- Calculate the distance of a given shape to a given point
 | 
						|
function internal.shape_def_distance(def, cnct, vrtcl)
 | 
						|
    local dx = math.abs(cnct - def.connectivity_point)
 | 
						|
    local dy = math.abs(vrtcl - def.verticality_point)
 | 
						|
 | 
						|
    return dx^2 + dy^2
 | 
						|
end
 | 
						|
 | 
						|
-- Get verticality noise params
 | 
						|
function internal.verticality_noise_params()
 | 
						|
    local factor = math.max(
 | 
						|
        math.abs(#noordstar_caves.registered_shapes) ^ 0.5
 | 
						|
        , 1
 | 
						|
    )
 | 
						|
 | 
						|
    return {
 | 
						|
        offset = 50,
 | 
						|
        scale = 50,
 | 
						|
        spread =
 | 
						|
            internal.reduced_shape_pos(
 | 
						|
                { x = factor * internal.vrtcl_blob.x
 | 
						|
                , y = factor * internal.vrtcl_blob.y
 | 
						|
                , z = factor * internal.vrtcl_blob.z
 | 
						|
                }
 | 
						|
            ),
 | 
						|
        seed = internal.seeds.verticality,
 | 
						|
        octaves = 2,
 | 
						|
        persistence = 0.2,
 | 
						|
        lacunarity = 2.0,
 | 
						|
        flags = "eased"
 | 
						|
    }
 | 
						|
end
 | 
						|
 | 
						|
-- Determine whether a position is within given bounds
 | 
						|
function internal.within_bounds(pos, minp, maxp)
 | 
						|
    if minp.x <= pos.x and pos.x <= maxp.x then
 | 
						|
        if minp.y <= pos.y and pos.y <= maxp.y then
 | 
						|
            if minp.z <= pos.z and pos.z <= maxp.z then
 | 
						|
                return true
 | 
						|
            end
 | 
						|
        end
 | 
						|
    end
 | 
						|
 | 
						|
    return false
 | 
						|
end
 | 
						|
 | 
						|
 | 
						|
-- Create a new flat 3d array based on a function callback that gets the index
 | 
						|
-- and position as inputs
 | 
						|
function Flat3dArray:from_func(minp, maxp, callback)
 | 
						|
    local arr = {}
 | 
						|
 | 
						|
    internal.iter_3d_area(minp, maxp, function (i, pos)
 | 
						|
        arr[i] = callback(i, pos)
 | 
						|
    end)
 | 
						|
 | 
						|
    return self:new(minp, maxp, arr)
 | 
						|
end
 | 
						|
 | 
						|
-- Get the Flat3dArray's value on the i'th index
 | 
						|
function Flat3dArray:get_index(i)
 | 
						|
    local out = self.arr[i]
 | 
						|
    if out == nil then
 | 
						|
        error(
 | 
						|
            "Index " .. i .. " not found in array of length " .. #self.arr
 | 
						|
        )
 | 
						|
    end
 | 
						|
    return out
 | 
						|
end
 | 
						|
 | 
						|
-- Get a value at a given position
 | 
						|
function Flat3dArray:get_pos(pos)
 | 
						|
    -- pos_to_index already validates so there's no need to validate here
 | 
						|
    return self:get_index(self:pos_to_index(pos))
 | 
						|
end
 | 
						|
 | 
						|
-- Create a new Flat3dArray instance
 | 
						|
function Flat3dArray:new(minp, maxp, arr)
 | 
						|
    local instance = {}
 | 
						|
    setmetatable(instance, Flat3dArray)
 | 
						|
 | 
						|
    local nx = maxp.x - minp.x + 1
 | 
						|
    local ny = maxp.y - minp.y + 1
 | 
						|
    local nz = maxp.z - minp.z + 1
 | 
						|
 | 
						|
    if #arr ~= nx * ny * nz then
 | 
						|
        error(
 | 
						|
            "Input array doesn't match dimension lengths: " .. nx .. " x " ..
 | 
						|
            ny .. " x " .. nz .. " = " .. (nx*ny*nz) .. ", but found " .. #arr
 | 
						|
        )
 | 
						|
    end
 | 
						|
 | 
						|
    -- Variables
 | 
						|
    instance.nx    = nx
 | 
						|
    instance.ny    = ny
 | 
						|
    instance.nz    = nz
 | 
						|
    instance.minp  = minp
 | 
						|
    instance.maxp  = maxp
 | 
						|
    instance.arr   = arr
 | 
						|
 | 
						|
    -- Constants
 | 
						|
    instance.up    = internal.relative_get_up(nx, ny)
 | 
						|
    instance.down  = internal.relative_get_down(nx, ny)
 | 
						|
    instance.north = internal.relative_get_north(nx, ny)
 | 
						|
    instance.east  = internal.relative_get_east(nx, ny)
 | 
						|
    instance.south = internal.relative_get_south(nx, ny)
 | 
						|
    instance.west  = internal.relative_get_west(nx, ny)
 | 
						|
 | 
						|
    return instance
 | 
						|
end
 | 
						|
 | 
						|
-- Convert a position to an index
 | 
						|
function Flat3dArray:pos_to_index(pos)
 | 
						|
    self:validate_pos(pos)
 | 
						|
 | 
						|
    local dx = pos.x - self.minp.x
 | 
						|
    local dy = pos.y - self.minp.y
 | 
						|
    local dz = pos.z - self.minp.z
 | 
						|
 | 
						|
    return internal.from_3d_to_flat(dx, dy, dz, self.nx, self.ny)
 | 
						|
end
 | 
						|
 | 
						|
-- Override a value at a given index
 | 
						|
function Flat3dArray:set_index(i, val)
 | 
						|
    self:validate_index(i)
 | 
						|
    self.arr[i] = val
 | 
						|
end
 | 
						|
 | 
						|
-- Validate an index to be in the Flat3dArray
 | 
						|
function Flat3dArray:validate_index(i)
 | 
						|
    if i < 1 or i > #self.arr then
 | 
						|
        error(
 | 
						|
            table.concat(
 | 
						|
                { "Index"
 | 
						|
                , i
 | 
						|
                , "is not in range 1 -"
 | 
						|
                , #self.arr
 | 
						|
                }
 | 
						|
                , " "
 | 
						|
            )
 | 
						|
        )
 | 
						|
    end
 | 
						|
end
 | 
						|
 | 
						|
-- Validate a position to be in the Flat3dArray
 | 
						|
function Flat3dArray:validate_pos(pos)
 | 
						|
    if not internal.is_valid_pos(pos, self.minp, self.maxp) then
 | 
						|
        error(
 | 
						|
            table.concat(
 | 
						|
                { "Position"
 | 
						|
                , internal.pos_to_str(pos)
 | 
						|
                , "is not within minp ="
 | 
						|
                , internal.pos_to_str(self.minp)
 | 
						|
                , "and maxp ="
 | 
						|
                , internal.pos_to_str(self.maxp)
 | 
						|
                }
 | 
						|
                , " "
 | 
						|
            )
 | 
						|
        )
 | 
						|
    end
 | 
						|
end
 | 
						|
 | 
						|
-------------------------------------------------------------------------------
 | 
						|
-------------------------------------------------------------------------------
 | 
						|
-------------------------------------------------------------------------------
 | 
						|
-------------------------------------------------------------------------------
 | 
						|
-------------------------------------------------------------------------------
 | 
						|
minetest.register_on_generated(function(minp, maxp, blockseed)
 | 
						|
    timer.start()
 | 
						|
 | 
						|
    math.randomseed(blockseed)
 | 
						|
 | 
						|
    local vm = minetest.get_mapgen_object("voxelmanip")
 | 
						|
    local data = vm:get_data()
 | 
						|
 | 
						|
    local flat, schems = internal.generate_caves(data, minp, maxp)
 | 
						|
    timer.checkpoint("Place caves")
 | 
						|
 | 
						|
    internal.place_simple_decorations(flat, schems)
 | 
						|
 | 
						|
    vm:set_data(flat.arr)
 | 
						|
    vm:write_to_map()
 | 
						|
 | 
						|
    internal.place_schematic_decorations(vm, schems)
 | 
						|
    timer.checkpoint("Place decorations")
 | 
						|
 | 
						|
    timer.stop()
 | 
						|
end)
 | 
						|
-------------------------------------------------------------------------------
 | 
						|
-------------------------------------------------------------------------------
 | 
						|
-------------------------------------------------------------------------------
 | 
						|
-------------------------------------------------------------------------------
 | 
						|
-------------------------------------------------------------------------------
 | 
						|
 | 
						|
-------------------------------------------------------------------------------
 | 
						|
-------------------------------------------------------------------------------
 | 
						|
-------------------------------------------------------------------------------
 | 
						|
-------------------------------------------------------------------------------
 | 
						|
-------------------------------------------------------------------------------
 | 
						|
-- For show, the following code COULD in theory be run elsewhere
 | 
						|
-------------------------------------------------------------------------------
 | 
						|
-------------------------------------------------------------------------------
 | 
						|
-------------------------------------------------------------------------------
 | 
						|
-------------------------------------------------------------------------------
 | 
						|
-------------------------------------------------------------------------------
 | 
						|
 | 
						|
noordstar_caves.register_shape({
 | 
						|
    name = "noordstar_caves:bubbles",
 | 
						|
    
 | 
						|
    noise_params = {
 | 
						|
        offset = 0.5,
 | 
						|
        scale = 0.5,
 | 
						|
        spread = { x = 20, y = 20, z = 20 },
 | 
						|
        seed = 248039,
 | 
						|
        octaves = 2,
 | 
						|
        persistence = 0.6,
 | 
						|
        lacunarity = 2.0,
 | 
						|
        flags = "eased"
 | 
						|
    },
 | 
						|
 | 
						|
    func = function(pos, n)
 | 
						|
        return n
 | 
						|
    end,
 | 
						|
 | 
						|
    -- TODO: Implement y limits
 | 
						|
    -- y_min = -31000,
 | 
						|
    -- y_max = 31000,
 | 
						|
 | 
						|
    connectivity_point = 10,
 | 
						|
    verticality_point = 40,
 | 
						|
})
 | 
						|
-- noordstar_caves.register_shape({
 | 
						|
--     name = "noordstar_caves:cliffs",
 | 
						|
    
 | 
						|
--     noise_params = {
 | 
						|
--         offset = 0.4,
 | 
						|
--         scale = 0.5,
 | 
						|
--         spread = { x = 20, y = 100, z = 20 },
 | 
						|
--         seed = 97354,
 | 
						|
--         octaves = 4,
 | 
						|
--         persistence = 0.6,
 | 
						|
--         lacunarity = 2.0,
 | 
						|
--         flags = ""
 | 
						|
--     },
 | 
						|
 | 
						|
--     func = function(pos, n)
 | 
						|
--         return n
 | 
						|
--     end,
 | 
						|
 | 
						|
--     connectivity_point = 30,
 | 
						|
--     verticality_point = 80,
 | 
						|
-- })
 | 
						|
-- noordstar_caves.register_shape({
 | 
						|
--     name = "noordstar_caves:donuts",
 | 
						|
    
 | 
						|
--     noise_params = {
 | 
						|
--         offset = 0.0,
 | 
						|
--         scale = 1.0,
 | 
						|
--         spread = { x = 30, y = 30, z = 30 },
 | 
						|
--         seed = 3934,
 | 
						|
--         octaves = 1,
 | 
						|
--         persistence = 0.6,
 | 
						|
--         lacunarity = 2.0,
 | 
						|
--         flags = "eased"
 | 
						|
--     },
 | 
						|
 | 
						|
--     func = function(pos, n)
 | 
						|
--         return 1 - 2 * math.abs(n)^0.5
 | 
						|
--     end,
 | 
						|
 | 
						|
--     connectivity_point = 50,
 | 
						|
--     verticality_point = 40,
 | 
						|
-- })
 | 
						|
-- noordstar_caves.register_shape({
 | 
						|
--     name = "noordstar_caves:wall",
 | 
						|
 | 
						|
--     func = function(pos, n)
 | 
						|
--         return -0.5
 | 
						|
--     end,
 | 
						|
 | 
						|
--     connectivity_point = 0,
 | 
						|
--     verticality_point = 0,
 | 
						|
-- })
 | 
						|
 | 
						|
-- noordstar_caves.set_world_depth(-60)
 | 
						|
-- noordstar_caves.cave_vastness = function(pos) return math.abs(pos.y - 60) / 120 end
 | 
						|
 | 
						|
noordstar_caves.register_biome({
 | 
						|
    name = "test",
 | 
						|
    node_floor = "mcl_core:crying_obsidian",
 | 
						|
    node_wall = "mcl_core:sand",
 | 
						|
    node_roof = "mcl_ocean:sea_lantern",
 | 
						|
    node_dust = "mcl_core:snow",
 | 
						|
    heat_point = 80,
 | 
						|
    humidity_point = 80,
 | 
						|
})
 | 
						|
noordstar_caves.register_biome({
 | 
						|
    name = "test2",
 | 
						|
    node_floor = "mcl_amethyst:amethyst_block",
 | 
						|
    node_wall = "mcl_crimson:shroomlight",
 | 
						|
    node_roof = "mcl_colorblocks:glazed_terracotta_silver",
 | 
						|
    heat_point = 100,
 | 
						|
    humidity_point = 100,
 | 
						|
})
 | 
						|
-- noordstar_caves.register_biome({
 | 
						|
--     name = "test3",
 | 
						|
--     heat_point = 50,
 | 
						|
--     humidity_point = 50,
 | 
						|
-- })
 | 
						|
 | 
						|
noordstar_caves.register_decoration({
 | 
						|
    deco_type = "simple",
 | 
						|
    place_on = "floor",
 | 
						|
    fill_ratio = 0.01,
 | 
						|
    decoration = "mcl_core:cactus",
 | 
						|
    height = 3,
 | 
						|
    height_max = 8,
 | 
						|
    biomes = { "test" },
 | 
						|
})
 | 
						|
noordstar_caves.register_decoration({
 | 
						|
    deco_type = "simple",
 | 
						|
    place_on = "floor",
 | 
						|
    fill_ratio = 0.1,
 | 
						|
    decoration = "mcl_ocean:sea_lantern",
 | 
						|
    height = 1,
 | 
						|
    height_max = 4,
 | 
						|
    biomes = { "test2" },
 | 
						|
})
 | 
						|
noordstar_caves.register_decoration({
 | 
						|
    deco_type = "schematic",
 | 
						|
    place_on = "floor",
 | 
						|
    fill_ratio = 0.005,
 | 
						|
    schematic = squeak,
 | 
						|
    rotation = "random",
 | 
						|
    -- place_offset_y = 5,
 | 
						|
})
 |