1298 lines
38 KiB
Lua
1298 lines
38 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 = 250, y = 250, z = 250 }
|
|
|
|
-- 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
|
|
, ceiling_deco = 7
|
|
}
|
|
|
|
-- 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 }
|
|
|
|
-- Barrier showing the world where areas should be considered as caves
|
|
-- when determining the weather.
|
|
, weather_y = 0
|
|
|
|
-- 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)
|
|
local y = math.abs(pos.y)
|
|
local d = math.abs(internal.world_depth / 2)
|
|
return 1 - (math.abs(y - d) / d)
|
|
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)
|
|
if type(h) ~= "number" then
|
|
return
|
|
end
|
|
h = math.round(h)
|
|
|
|
internal.world_depth = h
|
|
|
|
if mcl_vars then
|
|
-- internal.weather_y = mcl_vars.mg_overworld_min
|
|
|
|
mcl_vars.mg_overworld_min = h
|
|
mcl_vars.mg_bedrock_overworld_min = h
|
|
mcl_vars.mg_bedrock_overworld_max = h + 4
|
|
mcl_vars.mg_lava_overworld_max = h + 10
|
|
mcl_vars.mg_end_max = h - 2000
|
|
mcl_vars.mg_realm_barrier_overworld_end_max = mcl_vars.mg_end_max
|
|
mcl_vars.mg_realm_barrier_overworld_end_min = mcl_vars.mg_end_max - 11
|
|
|
|
mcl_vars.mg_lava = false
|
|
end
|
|
|
|
if mcl_mapgen then
|
|
-- internal.weather_y = mcl_mapgen.overworld.min
|
|
|
|
mcl_mapgen.overworld.min = h
|
|
mcl_mapgen.overworld.bedrock_min = h
|
|
mcl_mapgen.overworld.bedrock_max = mcl_mapgen.overworld.bedrock_min + (mcl_mapgen.bedrock_is_rough and 4 or h)
|
|
mcl_mapgen.overworld.lava_max = h + 6
|
|
mcl_mapgen.overworld.railcorridors_height_min = -50
|
|
mcl_mapgen.overworld.railcorridors_height_max = -2
|
|
|
|
mcl_mapgen.end_.max = h - 2000
|
|
mcl_mapgen.realm_barrier_overworld_end_max = mcl_mapgen.end_.max
|
|
mcl_mapgen.realm_barrier_overworld_end_min = mcl_mapgen.end_.max - 11
|
|
|
|
if mcl_mapgen.on_settings_changed then
|
|
mcl_mapgen.on_settings_changed()
|
|
else
|
|
minetest.log("error", "The installed version of the mcl_mapgen mod (part of Mineclone 5) "
|
|
.."does not have an mcl_mapgen.on_settings_changed method. This will likely result in "
|
|
.."altitudes below the original bedrock being inaccessible to players.")
|
|
end
|
|
end
|
|
|
|
end
|
|
noordstar_caves.set_world_depth(internal.world_depth)
|
|
|
|
if mcl_worlds then
|
|
local old_has_weather = mcl_worlds.has_weather
|
|
mcl_worlds.has_weather = function(pos)
|
|
-- No weather in the deep caverns
|
|
if pos.y >= internal.world_depth and pos.y <= internal.weather_y then
|
|
return false
|
|
end
|
|
return old_has_weather(pos)
|
|
end
|
|
end
|
|
|
|
-- 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_center_x,place_center_z")
|
|
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,
|
|
}
|
|
|
|
d.y_min = internal.reduced_shape_pos({ x = 0, y = d.y_min, z = 0 }).y + 1
|
|
d.y_max = internal.reduced_shape_pos({ x = 0, y = d.y_max, z = 0 }).y - 1
|
|
|
|
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)
|
|
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
|
|
|
|
if bools:get_index(i + bools.up) == false then
|
|
-- Block is near the ceiling
|
|
return nt.ceiling_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 or nt == internal.node_types.ceiling_deco then
|
|
local schem_placed = false
|
|
local appropriate_place = "floor"
|
|
if nt == internal.node_types.ceiling_deco then
|
|
appropriate_place = "ceiling"
|
|
end
|
|
|
|
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 ~= appropriate_place 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
|
|
local n = "air"
|
|
if nt == internal.node_types.floor_deco then
|
|
n = def.node_dust or n
|
|
end
|
|
place(n)
|
|
schem_placed = true
|
|
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
|
|
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 = {
|
|
x = schem.pos.x,
|
|
y = schem.pos.y + schem.deco.place_offset_y,
|
|
z = schem.pos.z,
|
|
}
|
|
local deco = schem.deco
|
|
|
|
if deco.deco_type ~= "schematic" then
|
|
else
|
|
if deco.place_on == "floor" then
|
|
minetest.place_schematic_on_vmanip(
|
|
vmanip, pos,
|
|
deco.schematic, deco.rotation, deco.replacement,
|
|
true, deco.flags
|
|
)
|
|
elseif deco.place_on == "ceiling" then
|
|
local h = deco.schematic
|
|
|
|
if type(deco.schematic) == "string" then
|
|
h = minetest.read_schematic(h, {})
|
|
|
|
if type(h) == "nil" then
|
|
error("Could not find schematic! Perhaps it the filename is incorrect?")
|
|
end
|
|
end
|
|
|
|
h = h.size.y
|
|
|
|
minetest.place_schematic_on_vmanip(
|
|
vmanip, { x = pos.x, y = pos.y - h + 1, z = pos.z },
|
|
deco.schematic, deco.rotation, deco.replacement,
|
|
true, deco.flags
|
|
)
|
|
else
|
|
error("Unknown place_on value `" .. deco.place_on .. "`")
|
|
end
|
|
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 = {
|
|
x = schem.pos.x,
|
|
y = schem.pos.y + schem.deco.place_offset_y,
|
|
z = schem.pos.z,
|
|
}
|
|
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
|
|
|
|
-- Prevent v7 from spawning large caves underground
|
|
function internal.stop_v7_caverns()
|
|
local s = minetest.get_mapgen_setting("mgv7_spflags")
|
|
|
|
if s then
|
|
s = s:gsub(", nocaverns", "")
|
|
s = s:gsub(", caverns" , "")
|
|
s = s:gsub("nocaverns, ", "")
|
|
s = s:gsub("caverns, " , "")
|
|
s = s:gsub("nocaverns" , "")
|
|
s = s:gsub("caverns" , "")
|
|
|
|
if s == "" then
|
|
minetest.set_mapgen_setting("mgv7_spflags", "nocaverns", true)
|
|
else
|
|
minetest.set_mapgen_setting("mgv7_spflags", s .. ", nocaverns", true)
|
|
end
|
|
end
|
|
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)
|
|
|
|
-- Prevent v7 from spawning large caves underground
|
|
internal.stop_v7_caverns()
|
|
|
|
-------------------------------------------------------------------------------
|
|
-------------------------------------------------------------------------------
|
|
-------------------------------------------------------------------------------
|
|
-------------------------------------------------------------------------------
|
|
-------------------------------------------------------------------------------
|