Compare commits

...

5 Commits

Author SHA1 Message Date
Bram van den Heuvel 67f780b2b3 Save biome size in separate variable 2024-04-22 15:21:10 +02:00
Bram van den Heuvel acfdea349f Add biome generation in engine
Currently, performance seems an issue - the cause of the effects is to be determined.
2024-04-22 09:00:53 +02:00
Bram van den Heuvel b9e18f70eb Add biome API
Note: the engine implementation is still missing as of this commit.
2024-04-22 07:51:05 +02:00
Bram van den Heuvel 1250abe7d8 Unregister cave shapes 2024-04-22 07:50:29 +02:00
Bram van den Heuvel 36b755725d Add biome definition 2024-04-22 07:50:14 +02:00
5 changed files with 256 additions and 18 deletions

61
API.md
View File

@ -100,4 +100,63 @@ The shapes are defined as follows:
Just like the surface world, the underground world uses biomes to decorate their Just like the surface world, the underground world uses biomes to decorate their
caves. The cave biomes are independent of the cave shapes. caves. The cave biomes are independent of the cave shapes.
**Under development.** For shapes, the following functions are available:
- `noordstar_caves.register_biome(biome def)` Define a new cave biome
- `noordstar_caves.unregister_biome(name)` Remove a defined cave biome
- `noordstar_caves.clear_registered_biomes()` Remove all known cave biomes
The biomes are defined as follows:
```lua
{
name = "noordstar_caves:tundra",
-- Unique name identifying the biome
-- Namespacing is not required but recommended
node_dust = "foo:snow",
-- Node dropped onto floor after all else is generated
node_floor = "foo:dirt_with_snow",
-- Node forming the floor that the player walks on
node_wall = "foo:ice",
-- Node forming the side walls of the cave
node_roof = "foo:bluestone",
-- Node forming the ceiling of the cave
node_air = "foo:air",
-- Nodes filling the inside of the cave. By default, this is air.
-- You can replace it with e.g. water to make the entire cave a water cave.
-- Keep in mind that cave biomes can blend, so it might not always look
-- very smooth.
node_shell = "foo:permafrost",
depth_shell = 3,
-- Node forming a layer around the entire cave and thickness of this layer
-- You can make the depth as high as you want, but raising it past 16
-- might cause hard cut-offs at chunk edges.
y_max = -100,
y_min = -31000,
-- Upper and lower limits of the cave biome.
-- Alternatively you can use xyz limits as shown below.
max_pos = { x = 31000, y = -100, z = 31000 }
min_pos = { x = -31000, y = -500, z = -31000 }
-- xyz limits for biome, an alternative to using `y_min` and `y_max`.
-- Cave biome is limited to a cuboid defined by these positions.
-- Any x, y or z field left undefined defaults to -31000 in `min_pos` or
-- 31000 in `max_pos`.
heat_point = 0,
humidity_point = 50,
-- Characteristic temperature and humidity for the biome.
-- Just like the Minetest Lua API for biomes, these values create
-- 'biome points' on a voronoi diagram with heat and humidity as axes.
-- The resulting voronoi cells determine the distribution of the biomes.
-- Heat and humidity have an average of 50, vary mostly between 0 and 100
-- but can exceed these values.
}
```

View File

@ -11,6 +11,9 @@ noordstar_caves = {}
-- Load features to influence cave shapes -- Load features to influence cave shapes
load("shape") load("shape")
-- Load features to influence cave biomes
load("biome")
-- Start engine to generate caves -- Start engine to generate caves
load("engine") load("engine")

91
lua/biome.lua Normal file
View File

@ -0,0 +1,91 @@
-- This file takes care of cave biomes
noordstar_caves.registered_biomes = {}
-- Clean the input and return a valid shape def
-- If the input is invalid, return nil
local function clean_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 -1e5,
y = def.min_pos.y or -1e5,
z = def.min_pos.z or -1e5,
}
elseif type(def.y_min) == "number" then
d.minp = { x = -1e5, y = def.y_min, z = -1e5 }
else
d.minp = { x = -1e5, y = -1e5, z = -1e5 }
end
if type(def.max_pos) == "table" then
d.maxp = {
x = def.max_pos.x or 1e5,
y = def.max_pos.y or 1e5,
z = def.max_pos.z or 1e5,
}
elseif type(def.y_max) == "number" then
d.maxp = { x = 1e5, y = def.y_max, z = 1e5 }
else
d.maxp = { x = 1e5, y = 1e5, z = 1e5 }
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
function noordstar_caves.register_biome(def)
local d = clean_def(def)
if d ~= nil then
noordstar_caves.registered_biomes[d.name] = d
end
end
function noordstar_caves.unregister_biome(name)
noordstar_caves.registered_biomes[name] = nil
end
function noordstar_caves.clear_registered_biomes()
noordstar_caves.registered_biomes = {}
end

View File

@ -2,6 +2,41 @@
-- Constants and magic numbers -- Constants and magic numbers
local mapgen_buffer = 16 local mapgen_buffer = 16
-- Cave biome range
local biome_spread_range = 50
-- Noise params for heat_point
local heat_noise_params =
{ offset = 50
, scale = 50
, spread = { x = biome_spread_range, y = biome_spread_range, z = biome_spread_range }
, seed = 320523
, octaves = 2
, persistence = 0.1
, lacunarity = 2.0
, flags = ""
}
-- Noise params for humidity_point
local humidity_noise_params =
{ offset = 50
, scale = 50
, spread = { x = biome_spread_range, y = biome_spread_range, z = biome_spread_range }
, seed = 9923473
, octaves = 2
, persistence = 0.1
, lacunarity = 2.0
, flags = ""
}
local default_biome =
{ name = "noordstar_caves:none"
, heat_point = -1e6
, humidity_point = -1e6
, minp = { x = -1e5, y = -1e5, z = -1e5 }
, maxp = { x = 1e5, y = 1e5, z = 1e5 }
}
-- Convert 3d relative coordinates to an index on a flat array -- Convert 3d relative coordinates to an index on a flat array
local function from_3d_to_flat(dx, dy, dz, nx, ny) local function from_3d_to_flat(dx, dy, dz, nx, ny)
return (nx * ny * dz) + (nx * dy) + dx + 1 return (nx * ny * dz) + (nx * dy) + dx + 1
@ -421,6 +456,46 @@ local function get_flat_cave_node_types(minp, maxp)
end) end)
end end
-- Transform categorized blocks into
local function biome_def_to_content_id(def, nt, original_block)
local function get_node(name, alt)
return function()
if name == nil then
return alt
else
return minetest.get_content_id(name)
end
end
end
local node_air = get_node(def.node_air, minetest.get_content_id("air"))
local node_floor = get_node(def.node_floor, original_block)
local node_stone = original_block
local node_roof = get_node(def.node_roof, original_block)
local node_unknown = original_block
local node_wall = get_node(def.node_wall, original_block)
if nt == node_type.unknown then
return node_unknown
elseif nt == node_type.floor then
return node_floor()
elseif nt == node_type.wall then
return node_wall()
elseif nt == node_type.roof then
return node_roof()
elseif nt == node_type.content then
return node_air()
elseif nt == node_type.stone then
return node_stone
else
return original_block
end
end
local function biome_distance(def, heat, humidity)
return (def.heat_point - heat)^2 + (def.humidity_point - humidity)^2
end
minetest.register_on_generated(function(minp, maxp, blockseed) minetest.register_on_generated(function(minp, maxp, blockseed)
local vminp = local vminp =
{ x = minp.x - mapgen_buffer { x = minp.x - mapgen_buffer
@ -440,29 +515,35 @@ minetest.register_on_generated(function(minp, maxp, blockseed)
-- Get threshold values -- Get threshold values
local node_types = get_flat_cave_node_types(vminp, vmaxp) local node_types = get_flat_cave_node_types(vminp, vmaxp)
local node_air = minetest.get_content_id("air") local heat_points = get_flat_from_noise_params(minp, maxp, heat_noise_params)
local node_floor = minetest.get_content_id("mcl_core:glass_green") local humidity_points = get_flat_from_noise_params(minp, maxp, humidity_noise_params)
local node_wall = minetest.get_content_id("mcl_core:glass_purple")
local node_roof = minetest.get_content_id("mcl_core:glass_red")
local node_other = minetest.get_content_id("mcl_core:glass")
-- Map block values
local nids = Flat3dArray:from_func(vminp, vmaxp, function(i, pos) local nids = Flat3dArray:from_func(vminp, vmaxp, function(i, pos)
local nt = node_types:get_pos(pos) local nt = node_types:get_pos(pos)
if not is_valid_pos(pos, minp, maxp) then if not is_valid_pos(pos, minp, maxp) then
return flat_data:get_pos(pos) return flat_data:get_pos(pos)
elseif nt == node_type.unknown then else
return node_other local biome = default_biome
elseif nt == node_type.floor then
return node_floor local heat = heat_points:get_pos(pos)
elseif nt == node_type.wall then local humidity = humidity_points:get_pos(pos)
return node_wall
elseif nt == node_type.roof then for _, def in pairs(noordstar_caves.registered_biomes) do
return node_roof if pos.x < def.minp.x then
elseif nt == node_type.content then elseif pos.y < def.minp.y then
return node_air elseif pos.z < def.minp.z then
elseif nt == node_type.stone then elseif pos.x > def.maxp.x then
return flat_data:get_pos(pos) elseif pos.y > def.maxp.y then
elseif pos.z > def.maxp.z then
elseif biome_distance(def, heat, humidity) > biome_distance(biome, heat, humidity) then
else
biome = def
end
end
return biome_def_to_content_id(biome, nt, flat_data:get_pos(pos))
end end
end) end)

View File

@ -47,6 +47,10 @@ function noordstar_caves.register_shape(def)
end end
end end
function noordstar_caves.unregister_shape(name)
noordstar_caves.registered_shapes[name] = nil
end
function noordstar_caves.clear_registered_shapes() function noordstar_caves.clear_registered_shapes()
noordstar_caves.registered_shapes = {} noordstar_caves.registered_shapes = {}
end end