Finish cave generation refactor #1
			
				
			
		
		
		
	|  | @ -1,91 +0,0 @@ | ||||||
| -- 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 |  | ||||||
							
								
								
									
										578
									
								
								lua/engine.lua
								
								
								
								
							
							
						
						
									
										578
									
								
								lua/engine.lua
								
								
								
								
							|  | @ -1,578 +0,0 @@ | ||||||
| 
 |  | ||||||
| -- Constants and magic numbers |  | ||||||
| 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 } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| local cave_shape_chunk_size = 8 |  | ||||||
| 
 |  | ||||||
| local cluster_shape_chunks_x = 4 |  | ||||||
| local cluster_shape_chunks_y = 6 |  | ||||||
| local cluster_shape_chunks_z = 4 |  | ||||||
| 
 |  | ||||||
| local function reduced_pos(pos) |  | ||||||
|     return |  | ||||||
|         { x = math.floor(pos.x / (cave_shape_chunk_size * cluster_shape_chunks_x)) |  | ||||||
|         , y = math.floor(pos.y / (cave_shape_chunk_size * cluster_shape_chunks_y)) |  | ||||||
|         , z = math.floor(pos.z / (cave_shape_chunk_size * cluster_shape_chunks_z)) |  | ||||||
|         } |  | ||||||
| end |  | ||||||
| -- Convert 3d relative coordinates to an index on a flat array |  | ||||||
| local function from_3d_to_flat(dx, dy, dz, nx, ny) |  | ||||||
|     return (nx * ny * dz) + (nx * dy) + dx + 1 |  | ||||||
| end |  | ||||||
| 
 |  | ||||||
| -- Convert an index on a flat array to 3d relative coordinates |  | ||||||
| local function from_flat_to_3d(i, nx, ny) |  | ||||||
|     return { |  | ||||||
|         dx = (i - 1) % nx, |  | ||||||
|         dy = math.floor((i - 1) / nx) % ny, |  | ||||||
|         dz = math.floor((i - 1) / (nx * ny)) |  | ||||||
|     } |  | ||||||
| end |  | ||||||
| 
 |  | ||||||
| -- Iterate over a 3d area, both indexing by the index and the ansolute position |  | ||||||
| local function 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 |  | ||||||
| 
 |  | ||||||
|     for i = 1, nx * ny * nz do |  | ||||||
|         local dpos = from_flat_to_3d(i, nx, ny) |  | ||||||
|         local pos = { |  | ||||||
|             x = minp.x + dpos.dx, |  | ||||||
|             y = minp.y + dpos.dy, |  | ||||||
|             z = minp.z + dpos.dz, |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         callback(i, pos) |  | ||||||
|     end |  | ||||||
| end |  | ||||||
| 
 |  | ||||||
| -- Helper function to convert a set of coordinates to a readable string |  | ||||||
| local function pos_to_str(pos) |  | ||||||
|     return "(" .. pos.x .. ", " .. pos.y .. ", " .. pos.z .. " )" |  | ||||||
| end |  | ||||||
| 
 |  | ||||||
| local Flat3dArray = {} |  | ||||||
| Flat3dArray.__index = Flat3dArray |  | ||||||
| 
 |  | ||||||
| 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 |  | ||||||
| 
 |  | ||||||
|     instance.nx = nx |  | ||||||
|     instance.ny = ny |  | ||||||
|     instance.nz = nz |  | ||||||
|     instance.minp = minp |  | ||||||
|     instance.maxp = maxp |  | ||||||
|     instance.arr = arr |  | ||||||
| 
 |  | ||||||
|     return instance |  | ||||||
| end |  | ||||||
| 
 |  | ||||||
| function Flat3dArray:from_func(minp, maxp, callback) |  | ||||||
|     local arr = {} |  | ||||||
| 
 |  | ||||||
|     iter_3d_area(minp, maxp, function (i, pos) |  | ||||||
|         arr[i] = callback(i, pos) |  | ||||||
|     end) |  | ||||||
| 
 |  | ||||||
|     return self:new(minp, maxp, arr) |  | ||||||
| end |  | ||||||
| 
 |  | ||||||
| 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 |  | ||||||
| 
 |  | ||||||
| function Flat3dArray:get_pos(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 self:get_index(from_3d_to_flat(dx, dy, dz, self.nx, self.ny)) |  | ||||||
| end |  | ||||||
| 
 |  | ||||||
| local function is_valid_pos(pos, minp, maxp) |  | ||||||
|     if pos.x < minp.x then |  | ||||||
|         return false |  | ||||||
|     elseif pos.x > maxp.x then |  | ||||||
|         return false |  | ||||||
|     elseif pos.y < minp.y then |  | ||||||
|         return false |  | ||||||
|     elseif pos.y > maxp.y then |  | ||||||
|         return false |  | ||||||
|     elseif pos.z < minp.z then |  | ||||||
|         return false |  | ||||||
|     elseif pos.z > maxp.z then |  | ||||||
|         return false |  | ||||||
|     else |  | ||||||
|         return true |  | ||||||
|     end |  | ||||||
| end |  | ||||||
| 
 |  | ||||||
| function Flat3dArray:valid_pos(pos) |  | ||||||
|     return is_valid_pos(pos, self.minp, self.maxp) |  | ||||||
| end |  | ||||||
| 
 |  | ||||||
| function Flat3dArray:validate_pos(pos) |  | ||||||
|     if not self:valid_pos(pos) then |  | ||||||
|         error( |  | ||||||
|             table.concat( |  | ||||||
|                 { "Position " |  | ||||||
|                 , pos_to_str(pos) |  | ||||||
|                 , " out of bounds from minp = " |  | ||||||
|                 , pos_to_str(self.minp) |  | ||||||
|                 , ", maxp = " |  | ||||||
|                 , pos_to_str(self.maxp) |  | ||||||
|                 }, |  | ||||||
|                 "" |  | ||||||
|             ) |  | ||||||
|         ) |  | ||||||
|     end |  | ||||||
| end |  | ||||||
| 
 |  | ||||||
| -- Get an enhanced function from the def function that warns us when the |  | ||||||
| -- function does not behave properly |  | ||||||
| local function enhanced_func(def) |  | ||||||
|     if type(def.name) ~= "string" then |  | ||||||
|         error("Invalid nameless shape definition") |  | ||||||
|     elseif type(def.func) ~= "function" then |  | ||||||
|         error( |  | ||||||
|             "Invalid shape definition misses an adjustment function" |  | ||||||
|         ) |  | ||||||
|     else |  | ||||||
|         return function(pos, n) |  | ||||||
|             local out = def.func(pos, n) |  | ||||||
| 
 |  | ||||||
|             if type(out) == "number" then |  | ||||||
|                 return out |  | ||||||
|             elseif n == nil then |  | ||||||
|                 error( |  | ||||||
|                     "Shape " .. def.name .. " function must return a number. The input `n` was nil. Perhaps your `noise_params` field is invalid?" |  | ||||||
|                 ) |  | ||||||
|             else |  | ||||||
|                 error("Shape " .. def.name .. " function must return a number.") |  | ||||||
|             end |  | ||||||
|         end |  | ||||||
|     end |  | ||||||
| end |  | ||||||
| 
 |  | ||||||
| -- Get a flat array of cave shape noise values from a given cave shape def |  | ||||||
| local function get_flat_from_shape_def(def, minp, maxp) |  | ||||||
|     local f = enhanced_func(def) |  | ||||||
| 
 |  | ||||||
|     local nx = maxp.x - minp.x + 1 |  | ||||||
|     local ny = maxp.y - minp.y + 1 |  | ||||||
|     local nz = maxp.z - minp.z + 1 |  | ||||||
| 
 |  | ||||||
|     local noise_flat_map = {} |  | ||||||
| 
 |  | ||||||
|     -- If noise parameters have been defined, fill the table with noise |  | ||||||
|     -- If not, all values remain nil |  | ||||||
|     if def.noise_params ~= nil 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 |  | ||||||
|      |  | ||||||
|     iter_3d_area(minp, maxp, function(i, pos) |  | ||||||
|         noise_flat_map[i] = f(pos, noise_flat_map[i]) |  | ||||||
|     end) |  | ||||||
| 
 |  | ||||||
|     return Flat3dArray:new(minp, maxp, noise_flat_map) |  | ||||||
| end |  | ||||||
| 
 |  | ||||||
| local function get_flat_from_noise_params(minp, maxp, noise_params) |  | ||||||
|     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 |  | ||||||
| 
 |  | ||||||
| -- Based on the number of cave shapes, calculate how quickly connectivity is |  | ||||||
| -- meant to change |  | ||||||
| local function get_connectivity_noise_params(shape_size) |  | ||||||
|     local factor = math.max(math.abs(shape_size) ^ 0.5, 1) |  | ||||||
| 
 |  | ||||||
|     return { |  | ||||||
|         offset = 50, |  | ||||||
|         scale = 50, |  | ||||||
|         spread = |  | ||||||
|             reduced_pos( |  | ||||||
|                 { x = factor * 250 |  | ||||||
|                 , y = factor * 100 |  | ||||||
|                 , z = factor * 250 |  | ||||||
|                 } |  | ||||||
|             ), |  | ||||||
|         seed = 297948, |  | ||||||
|         octaves = 2, |  | ||||||
|         persistence = 0.2, |  | ||||||
|         lacunarity = 2.0, |  | ||||||
|         flags = "eased" |  | ||||||
|     } |  | ||||||
| end |  | ||||||
| 
 |  | ||||||
| -- Based on the number of cave shapes, calculate how quickly verticality is |  | ||||||
| -- meant to change |  | ||||||
| local function get_verticality_noise_params(shape_size) |  | ||||||
|     local factor = math.max(math.abs(shape_size) ^ 0.5, 1) |  | ||||||
| 
 |  | ||||||
|     return { |  | ||||||
|         offset = 50, |  | ||||||
|         scale = 50, |  | ||||||
|         spread = |  | ||||||
|             reduced_pos( |  | ||||||
|                 { x = factor * 100 |  | ||||||
|                 , y = factor * 250 |  | ||||||
|                 , z = factor * 100 |  | ||||||
|                 } |  | ||||||
|             ), |  | ||||||
|         seed = 35644, |  | ||||||
|         octaves = 2, |  | ||||||
|         persistence = 0.2, |  | ||||||
|         lacunarity = 2.0, |  | ||||||
|         flags = "eased" |  | ||||||
|     } |  | ||||||
| end |  | ||||||
| 
 |  | ||||||
| -- Get whether a cave shape is within distance. |  | ||||||
| local function shape_within_distance(shape_size, dx, dy) |  | ||||||
|     local factor = math.max(math.abs(shape_size) ^ 0.5, 1) |  | ||||||
|     local max_distance = 30 / factor |  | ||||||
| 
 |  | ||||||
|     return dx^2 + dy^2 <= max_distance^2 |  | ||||||
| end |  | ||||||
| 
 |  | ||||||
| local function shape_def_distance(def, x, y) |  | ||||||
|     local dx = math.abs(x - def.connectivity_point) |  | ||||||
|     local dy = math.abs(y - def.verticality_point) |  | ||||||
| 
 |  | ||||||
|     return dx^2 + dy^2 |  | ||||||
| end |  | ||||||
| 
 |  | ||||||
| -- Get a flat map containing all flat threshold values |  | ||||||
| local function get_threshold_flat(minp, maxp) |  | ||||||
|     local connectivity = get_flat_from_noise_params( |  | ||||||
|         reduced_pos(minp), reduced_pos(maxp), |  | ||||||
|         get_connectivity_noise_params(#noordstar_caves.registered_shapes) |  | ||||||
|     ) |  | ||||||
|      |  | ||||||
|     local verticality = get_flat_from_noise_params( |  | ||||||
|         reduced_pos(minp), reduced_pos(maxp), |  | ||||||
|         get_verticality_noise_params(#noordstar_caves.registered_shapes) |  | ||||||
|     ) |  | ||||||
| 
 |  | ||||||
|     local noise = {} |  | ||||||
| 
 |  | ||||||
|     -- Get noise for all cave shapes |  | ||||||
|     for key, def in pairs(noordstar_caves.registered_shapes) do |  | ||||||
|         noise[key] = get_flat_from_shape_def(def, reduced_pos(minp), reduced_pos(maxp)) |  | ||||||
|     end |  | ||||||
| 
 |  | ||||||
|     -- Create a (reduced) flat array |  | ||||||
|     local reduced = Flat3dArray:from_func( |  | ||||||
|         reduced_pos(minp), reduced_pos(maxp), |  | ||||||
|         function(i, pos) |  | ||||||
|             local x = connectivity:get_pos(pos) |  | ||||||
|             local y = verticality:get_pos(pos) |  | ||||||
| 
 |  | ||||||
|             local value = 0 |  | ||||||
|          |  | ||||||
|             for key, def in pairs(noordstar_caves.registered_shapes) do |  | ||||||
|                 local dx = math.abs(x - def.connectivity_point) |  | ||||||
|                 local dy = math.abs(y - def.verticality_point) |  | ||||||
| 
 |  | ||||||
|                 if shape_within_distance(#noordstar_caves.registered_shapes, dx, dy) then |  | ||||||
|                     value = math.max(value, noise[key]:get_pos(pos)) |  | ||||||
|                 end |  | ||||||
|             end |  | ||||||
| 
 |  | ||||||
|             return value |  | ||||||
|         end |  | ||||||
|     ) |  | ||||||
| 
 |  | ||||||
|     -- Create the flat array |  | ||||||
|     local x = Flat3dArray:from_func(minp, maxp, function(i, pos) |  | ||||||
|         return reduced:get_pos(reduced_pos(pos)) |  | ||||||
|     end) |  | ||||||
| 
 |  | ||||||
|     return x |  | ||||||
| end |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| -- Old minimum height for the overworld |  | ||||||
| local old_overworld_min = mcl_vars.mg_overworld_min |  | ||||||
| 
 |  | ||||||
| -- If another mod doesn't override the maximum world depth, we will assume that |  | ||||||
| -- the world depth is the following value. |  | ||||||
| local world_depth = -60 |  | ||||||
| 
 |  | ||||||
| -- Otherwise, this variable can be changed using the following function, |  | ||||||
| -- which will also update all the other necessary variables |  | ||||||
| function noordstar_caves.set_world_depth(h) |  | ||||||
|     -- Set world depth variable |  | ||||||
|     world_depth = h |  | ||||||
| end |  | ||||||
| noordstar_caves.set_world_depth(world_depth) |  | ||||||
| 
 |  | ||||||
| -- 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 |  | ||||||
| --  - This function will be run a LOT so it is very performance sensitive |  | ||||||
| noordstar_caves.cave_vastness = function(pos) |  | ||||||
|     return pos.y / world_depth |  | ||||||
| end |  | ||||||
| 
 |  | ||||||
| -- Secretly, we're using an internal function that also adds a safe layer for |  | ||||||
| -- when we're approaching bedrock levels |  | ||||||
| local function cave_vastness(pos) |  | ||||||
|     if world_depth + 20 < pos.y then |  | ||||||
|         return noordstar_caves.cave_vastness(pos) |  | ||||||
|     elseif world_depth + 5 < pos.y then |  | ||||||
|         return noordstar_caves.cave_vastness(pos) * math.abs(pos.y - world_depth - 5) / 15 |  | ||||||
|     else |  | ||||||
|         return 0 |  | ||||||
|     end |  | ||||||
| end |  | ||||||
| 
 |  | ||||||
| -- Get a flat array of nodes that are either in caves or not in caves |  | ||||||
| local function get_flat_cave_bools(minp, maxp) |  | ||||||
|     local thresholds = get_threshold_flat(minp, maxp) |  | ||||||
| 
 |  | ||||||
|     return Flat3dArray:from_func(minp, maxp, function(i, pos) |  | ||||||
|         return thresholds:get_pos(pos) >= 1 - cave_vastness(pos) |  | ||||||
|     end) |  | ||||||
| end |  | ||||||
| 
 |  | ||||||
| local node_type = |  | ||||||
|     { unknown = 1  -- Edge of a chunk |  | ||||||
|     , floor = 2    -- Floor of a cave |  | ||||||
|     , wall = 3     -- Side wall of a cave |  | ||||||
|     , roof = 4     -- Roof of a cave |  | ||||||
|     , content = 5  -- Air in the cave not adjacent to a wall, floor or roof |  | ||||||
|     , stone = 6    -- Underground node not adjacent to a cave |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| local function get_flat_cave_node_types(minp, maxp) |  | ||||||
|     local bools = get_flat_cave_bools(minp, maxp) |  | ||||||
| 
 |  | ||||||
|     return Flat3dArray:from_func(minp, maxp, function (i, pos) |  | ||||||
|         if bools:get_pos(pos) then |  | ||||||
|             return node_type.content |  | ||||||
|         -- Floor takes precedence |  | ||||||
|         elseif pos.y == maxp.y then -- Could be floor |  | ||||||
|             return node_type.unknown |  | ||||||
|         elseif bools:get_pos({ x = pos.x, y = pos.y + 1, z = pos.z }) then |  | ||||||
|             return node_type.floor |  | ||||||
|         -- Then roof takes precedence |  | ||||||
|         elseif pos.y == minp.y then -- Could be roof |  | ||||||
|             return node_type.unknown |  | ||||||
|         elseif bools:get_pos({ x = pos.x, y = pos.y - 1, z = pos.z }) then |  | ||||||
|             return node_type.roof |  | ||||||
|         else |  | ||||||
|             -- Check for walls |  | ||||||
|             local left  = { x = pos.x - 1, y = pos.y, z = pos.z } |  | ||||||
|             local right = { x = pos.x + 1, y = pos.y, z = pos.z } |  | ||||||
|             local front = { x = pos.x, y = pos.y, z = pos.z - 1 } |  | ||||||
|             local back  = { x = pos.x, y = pos.y, z = pos.z + 1 } |  | ||||||
| 
 |  | ||||||
|             -- Check if the value is near the edge |  | ||||||
|             local on_edge = false |  | ||||||
| 
 |  | ||||||
|             if left.x < minp.x then |  | ||||||
|                 on_edge = true |  | ||||||
|             elseif bools:get_pos(left) then |  | ||||||
|                 return node_type.wall |  | ||||||
|             end |  | ||||||
|             if right.x > maxp.x then |  | ||||||
|                 on_edge = true |  | ||||||
|             elseif bools:get_pos(right) then |  | ||||||
|                 return node_type.wall |  | ||||||
|             end |  | ||||||
|             if front.z < minp.z then |  | ||||||
|                 on_edge = true |  | ||||||
|             elseif bools:get_pos(front) then |  | ||||||
|                 return node_type.wall |  | ||||||
|             end |  | ||||||
|             if back.z > maxp.z then |  | ||||||
|                 on_edge = true |  | ||||||
|             elseif bools:get_pos(back) then |  | ||||||
|                 return node_type.wall |  | ||||||
|             end |  | ||||||
| 
 |  | ||||||
|             if on_edge then |  | ||||||
|                 return node_type.unknown |  | ||||||
|             else |  | ||||||
|                 return node_type.stone |  | ||||||
|             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   = get_node(nil,           original_block) |  | ||||||
|     local node_roof    = get_node(def.node_roof, original_block) |  | ||||||
|     local node_unknown = get_node(nil,           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) |  | ||||||
|     local vminp = |  | ||||||
|         { x = minp.x - mapgen_buffer |  | ||||||
|         , y = minp.y - mapgen_buffer |  | ||||||
|         , z = minp.z - mapgen_buffer |  | ||||||
|         } |  | ||||||
|     local vmaxp = |  | ||||||
|         { x = maxp.x + mapgen_buffer |  | ||||||
|         , y = maxp.y + mapgen_buffer |  | ||||||
|         , z = maxp.z + mapgen_buffer |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|     -- Get voxelmanip |  | ||||||
|     local vm = minetest.get_mapgen_object("voxelmanip") |  | ||||||
|     local flat_data = Flat3dArray:new(vminp, vmaxp, vm:get_data()) |  | ||||||
|      |  | ||||||
|     -- Get threshold values |  | ||||||
|     local node_types = get_flat_cave_node_types(vminp, vmaxp) |  | ||||||
|      |  | ||||||
|     local heat_points = get_flat_from_noise_params(minp, maxp, heat_noise_params) |  | ||||||
|     local humidity_points = get_flat_from_noise_params(minp, maxp, humidity_noise_params) |  | ||||||
| 
 |  | ||||||
|     -- Map block values |  | ||||||
|     local nids = Flat3dArray:from_func(vminp, vmaxp, function(i, pos) |  | ||||||
|         local nt = node_types:get_pos(pos) |  | ||||||
| 
 |  | ||||||
|         if not is_valid_pos(pos, minp, maxp) then |  | ||||||
|             return flat_data:get_pos(pos) |  | ||||||
|         else |  | ||||||
|             local biome = default_biome |  | ||||||
| 
 |  | ||||||
|             local heat = heat_points:get_pos(pos) |  | ||||||
|             local humidity = humidity_points:get_pos(pos) |  | ||||||
| 
 |  | ||||||
|             for _, def in pairs(noordstar_caves.registered_biomes) do |  | ||||||
|                 if pos.x < def.minp.x then |  | ||||||
|                 elseif pos.y < def.minp.y then |  | ||||||
|                 elseif pos.z < def.minp.z then |  | ||||||
|                 elseif pos.x > def.maxp.x then |  | ||||||
|                 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) |  | ||||||
| 
 |  | ||||||
|     -- Write all changes to the Minetest world |  | ||||||
|     vm:set_data(nids.arr) |  | ||||||
|     vm:write_to_map() |  | ||||||
| end) |  | ||||||
|  | @ -1,56 +0,0 @@ | ||||||
| -- This file takes care of the cave shapes. |  | ||||||
| 
 |  | ||||||
| noordstar_caves.registered_shapes = {} |  | ||||||
| 
 |  | ||||||
| -- 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.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 |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     if type(def.noise_params) == "table" then |  | ||||||
|         d.noise_params = def.noise_params |  | ||||||
|     end |  | ||||||
| 
 |  | ||||||
|     if type(def.func) == "function" then |  | ||||||
|         d.func = def.func |  | ||||||
|     else |  | ||||||
|         d.func = function(pos, v) return v end |  | ||||||
|     end |  | ||||||
| 
 |  | ||||||
|     if type(def.y_min) == "number" and type(def.y_max) == "number" then |  | ||||||
|         d.y_min = def.y_min |  | ||||||
|         d.y_max = def.y_max |  | ||||||
|     end |  | ||||||
| 
 |  | ||||||
|     return d |  | ||||||
| end |  | ||||||
| 
 |  | ||||||
| function noordstar_caves.register_shape(def) |  | ||||||
|     local d = clean_def(def) |  | ||||||
| 
 |  | ||||||
|     if d ~= nil then |  | ||||||
|         noordstar_caves.registered_shapes[d.name] = d |  | ||||||
|     end |  | ||||||
| end |  | ||||||
| 
 |  | ||||||
| function noordstar_caves.unregister_shape(name) |  | ||||||
|     noordstar_caves.registered_shapes[name] = nil |  | ||||||
| end |  | ||||||
| 
 |  | ||||||
| function noordstar_caves.clear_registered_shapes() |  | ||||||
|     noordstar_caves.registered_shapes = {} |  | ||||||
| end |  | ||||||
|  | @ -0,0 +1,139 @@ | ||||||
|  | local internal = { | ||||||
|  |     waypoints = {}, | ||||||
|  |     stats = {}, | ||||||
|  |     sessions = 0, | ||||||
|  |     last = os.clock() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | timer = {} | ||||||
|  | 
 | ||||||
|  | function timer.start() | ||||||
|  |     internal.waypoints = {} | ||||||
|  | 
 | ||||||
|  |     internal.now() | ||||||
|  | 
 | ||||||
|  |     internal.sessions = internal.sessions + 1 | ||||||
|  | end | ||||||
|  | 
 | ||||||
|  | function timer.checkpoint(name) | ||||||
|  |     local now = os.clock() | ||||||
|  | 
 | ||||||
|  |     table.insert(internal.waypoints, { name, internal.last, now }) | ||||||
|  | 
 | ||||||
|  |     internal.now() | ||||||
|  | end | ||||||
|  | 
 | ||||||
|  | function timer.stop() | ||||||
|  |     local name_len = 0 | ||||||
|  | 
 | ||||||
|  |     for _, t in ipairs(internal.waypoints) do | ||||||
|  |         local name  = t[1] | ||||||
|  |         local start = t[2] | ||||||
|  |         local stop  = t[3] | ||||||
|  | 
 | ||||||
|  |         local stat = internal.stats[name] | ||||||
|  | 
 | ||||||
|  |         if stat then | ||||||
|  |             internal.stats[name] = stat + (stop - start) | ||||||
|  |         else | ||||||
|  |             internal.stats[name] = stop - start | ||||||
|  |         end | ||||||
|  | 
 | ||||||
|  |         name_len = math.max(name_len, string.len(name)) | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     local h1 = "Task" | ||||||
|  |     local h2 = "Time" | ||||||
|  |     local h3 = "Time (avg)" | ||||||
|  |      | ||||||
|  |     internal.log( | ||||||
|  |         table.concat( | ||||||
|  |             { h1 | ||||||
|  |             , string.rep(" ", name_len - string.len(h1)) | ||||||
|  |             , " | " | ||||||
|  |             , h2 | ||||||
|  |             , string.rep(" ", 8 - string.len(h2)) | ||||||
|  |             , " | " | ||||||
|  |             , h3 | ||||||
|  |             } | ||||||
|  |             , "" | ||||||
|  |         ) | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     internal.log_hr(name_len) | ||||||
|  | 
 | ||||||
|  |     for _, t in ipairs(internal.waypoints) do | ||||||
|  |         local name = t[1] | ||||||
|  | 
 | ||||||
|  |         internal.log_time( | ||||||
|  |             name, | ||||||
|  |             internal.to_ms(t[3] - t[2]), | ||||||
|  |             internal.to_ms(internal.stats[name] / internal.sessions), | ||||||
|  |             name_len | ||||||
|  |         ) | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     internal.log_hr(name_len) | ||||||
|  | 
 | ||||||
|  |     internal.log_time("Total", internal.sum(), internal.sum_avg(), name_len) | ||||||
|  | end | ||||||
|  | 
 | ||||||
|  | -- Log text to the Minetest chat | ||||||
|  | function internal.log(text) | ||||||
|  |     minetest.chat_send_all(os.time() .. " - " .. text) | ||||||
|  | end | ||||||
|  | 
 | ||||||
|  | function internal.log_hr(name_len) | ||||||
|  |     internal.log(string.rep("-", name_len + 3 + 8 + 3 + 8)) | ||||||
|  | end | ||||||
|  | 
 | ||||||
|  | function internal.log_time(header, time, time_avg, header_max_len) | ||||||
|  |     local duration = tostring(time) | ||||||
|  |     local duration_avg = tostring(time_avg) | ||||||
|  | 
 | ||||||
|  |     internal.log( | ||||||
|  |         table.concat( | ||||||
|  |             { header | ||||||
|  |             , string.rep(" ", header_max_len - string.len(header)) | ||||||
|  |             , " | " | ||||||
|  |             , string.rep(" ", 5 - string.len(duration)) | ||||||
|  |             , duration | ||||||
|  |             , " ms | " | ||||||
|  |             , string.rep(" ", 5 - string.len(duration_avg)) | ||||||
|  |             , duration_avg | ||||||
|  |             , " ms" | ||||||
|  |             } | ||||||
|  |             , "" | ||||||
|  |         ) | ||||||
|  |     ) | ||||||
|  | end | ||||||
|  | 
 | ||||||
|  | function internal.now() | ||||||
|  |     internal.last = os.clock() | ||||||
|  | end | ||||||
|  | 
 | ||||||
|  | function internal.sum() | ||||||
|  |     local s = 0 | ||||||
|  | 
 | ||||||
|  |     for _, t in ipairs(internal.waypoints) do | ||||||
|  |         s = s + (t[3] - t[2]) | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     return internal.to_ms(s) | ||||||
|  | end | ||||||
|  | 
 | ||||||
|  | function internal.sum_avg() | ||||||
|  |     local s = 0 | ||||||
|  |      | ||||||
|  |     for _, t in pairs(internal.stats) do | ||||||
|  |         s = s + t | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     return internal.to_ms(s / internal.sessions) | ||||||
|  | end | ||||||
|  | 
 | ||||||
|  | function internal.to_ms(s) | ||||||
|  |     return math.round(s * 1e3) | ||||||
|  | end | ||||||
|  | 
 | ||||||
|  | internal.now() | ||||||
		Loading…
	
		Reference in New Issue