many worldgen mods and gonna try technic now

This commit is contained in:
2026-09-13 03:30:48 -06:00
parent bac6ac94a3
commit 548749cb1a
4811 changed files with 163271 additions and 1 deletions
@@ -0,0 +1,2 @@
# Auto detect text files and perform LF normalization
* text=auto
+41
View File
@@ -0,0 +1,41 @@
# Compiled Lua sources
luac.out
# luarocks build files
*.src.rock
*.zip
*.tar.gz
# Object files
*.o
*.os
*.ko
*.obj
*.elf
# Precompiled Headers
*.gch
*.pch
# Libraries
*.lib
*.a
*.la
*.lo
*.def
*.exp
# Shared objects (inc. Windows DLLs)
*.dll
*.so
*.so.*
*.dylib
# Executables
*.exe
*.out
*.app
*.i*86
*.x86_64
*.hex
+25
View File
@@ -0,0 +1,25 @@
NOTE: several files in this mod were derived from Minetest code that is licensed under the GNU Lesser General Public License version 2.1 or later. These files have headers indicating the specific licensing of that file's code.
The rest of this mod is licensed under the MIT license:
MIT License
Copyright (c) 2018 FaceDeer
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+177
View File
@@ -0,0 +1,177 @@
Mapgen Helper
=============
This mod provides a library of methods intended for use with other Minetest mapgen mods, automating routine tasks and giving tools to more easily perform mapgen operations. It's a bit of a random grab-bag, I put methods in here when I find that I use them frequently in other mods or when I just did a bunch of work solving a specific problem that could potentially be reused in future mods.
Part of the purpose of this mod is to make sure these methods are as efficient as possible, allowing them to be used in mapgens or ABMs or other such contexts where speed is of the essence or where the code will be executed many millions of times. Several of these methods may seem trivial to accomplish already by using a combination of existing methods (for example `VoxelArea:get_y(vi)` can already be accomplished with `VoxelArea:position(i).y`) but in this mod they've been written to eliminate unnecessary intermediate steps and unused calculations.
## Voxel Manipulator methods
* `mapgen_helper.mapgen_vm_data()`
* returns vm, data, area
* Almost every mapgen mod does this operation first. This method retrieves the Voxel Manipulator for the generated map block, the voxel data array (automatically using a buffer internal to mapgen_helper to store it), and a VoxelArea for indexing the data array.
* Note that you still need to write the data array to the voxel manipulator at the end of map generation.
* `mapgen_helper.mapgen_vm_data_param2()`
* returns vm, data, param2_data, area
* Almost identical to the previous method, this one also returns the param2 data array.
# VoxelArea methods
These methods are added to the existing VoxelArea LUA class.
* `VoxelArea:iterp_xyz(minp, maxp)`
* Returns an iterator that gives not just the voxel index (vi), but also the x, y and z coordinates it represents. This lets you perform actions in your mapgen loop based on x, y and z coordinates without having to look them up from the voxel index. For example:
`for vi, x, y, z in area:iterp_xyz(minp, maxp) do`
* `VoxelArea:iter_xyz(minx, miny, minz, maxx, maxy, maxz)`
* The same as above, but takes individual coordinate arguments instead of points.
* `VoxelArea:iterp_yxz(minp, maxp)`
* Similar to the above, but the order of iteration is changed. Using this iterator, indices are visited in the +y direction along "columns". This is handy for mapgens that want to build stuff based on the nodes that are "below" the current node, for example if you're scanning through the map block looking for a walkable surface to place something on.
* `VoxelArea:iter_yxz(minx, miny, minz, maxx, maxy, maxz)`
* Same as above, but takes individual coordinate arguments instead of points.
* `VoxelArea:get_y(vi)`
* Returns the absolute y coordinate of the index provided. Useful for mapgens that want to do something at a specific elevation.
* `VoxelArea:transform(area, vi)`
* Takes a VoxelArea and an index into that VoxelArea as parameters returns an index into its own VoxelArea, or nil if it's not in the voxelarea. This is useful when you've got two 3d data arrays that cover different volumes and would like to "map" values from one into the other. For example, if you've generated a 3d noise array using the mapgen's minp and maxp and want to map it onto the voxelmanipulator's emin and emax data array.
## Biomes
* `mapgen_helper.get_biome_def(biome_id)`
* Given an id from the biome map, returns a biome definition. Normally translating a biome_id into a biome definition requires several steps and lookups, this method caches the intermediate results internal to mapgen_helper to speed up the operation.
* `mapgen_helper.get_biome_def_i(biomemap, minp, maxp, area, vi)`
* When iterating through a voxelmanip's data array it is common to want to check what biome a given index belongs to. This method automates that lookup. Give it the biomemap that was provided by get_mapgen_object("biomemap"), the minp and maxp parameters from register_on_generated, the VoxelArea representing the generated map chunk, and the voxel index, and this method returns the biome_def that that voxel index lies inside.
## Node definitions
* `mapgen_helper.buildable_to(c_node)`
*Given a node id, returns whether that node's definition is "buildable_to"
* `mapgen_helper.is_ground_content(c_node)`
* Given a node id, returns whether that node's definition is "ground_content"
## Noise manager
* `mapgen_helper.perlin3d(name, minp, maxp, perlin_params)`
* Returns nvals_perlin, area
* Efficient generation and use of 3d perlin noise requires a bunch of steps that this method automates. It uses the "name" parameter to allocate buffers and generator objects in a table stored internally by mapgen_helper, if you're using multiple different noise fields in the same scope make sure to give them different names.
* The perlin_params argument is actually optional if you've already made a call to this method (or used the 'mapgen_helper.register_perlin' method below), the perlin parameters are stored when provided and can be looked back up again by mapgen_helper.
* The second return value is a VoxelArea for minp, maxp.
* `mapgen_helper.perlin2d(name, minp, maxp, perlin_params)`
* Similar to the above method but for generating 2d noise. This method only returns the data array, it does not return a VoxelArea.
* `mapgen_helper.register_perlin(name, perlin_params)`
* Pre-registering a perlin_params table is optional, any call to one of the above perlin methods with perlin_params provided will override the parameters registered here.
## 2D array indexing
* `mapgen_helper.index2d(minp, maxp, x, z)`
* Returns an index for getting values out of 2d arrays (such as heightmap, biomemap, heatmap, arrays generated by perlin2d, etc) given x and z coordinates.
* minp and maxp must be provided, these are the extents that were used when generating the 2d array.
* `mapgen_helper.index2dp(minp, maxp, pos)`
* Version of the above that takes a pos instead of separate coordinates. pos.y is ignored.
* `mapgen_helper.index2di(minp, maxp, area, vi)`
* Version of the above that indexes a 2d array based on the index in a 3d array.
## Schematics
*NOTE:* These methods operate on the LUA table format of schematic. To convert a .mts file into its lua representation, use `minetest.deserialize(minetest.serialize_schematic(schematic, "lua"))` to convert an .mts into a lua table.
* `mapgen_helper.get_schematic_bounding_box(pos, schematic, rotation, flags)`
* Returns the minpos and maxpos of the bounding box that this schematic will be placed in given the rotation and flag parameters. Useful for testing whether a schematic will fit in a place before actually writing it to the data.
* `mapgen_helper.place_schematic_on_data(data, data_param2, area, pos, schematic, rotation, replacements, force_placement, flags)`
* Takes a lua-format schematic and applies it to the data and param2_data arrays produced by vmanip instead of being applied to the vmanip directly. Useful in a mapgen loop that's doing other things with the data before and after applying schematics. A VoxelArea for the data also needs to be provided.
* *NOTE:* As of the writing of this documentation, this method is still somewhat of a work-in-progress. It only supports the table version of the `flags` parameter (eg, `{["place_center_x"] = true}`), and doesn't support rotating nodes of paramtype2 `wallmounted`, `colorfacedir`, or `colorwallmounted`. Support is planned in the near future.
* This method has some additional features that the existing minetest schematic-placement API doesn't support:
* node defs can have a "`place_on_condition(c_node)`" property defined. This is a function that is given the node content ID of the node the schematic is about to replace and returns true to indicate the schematic should replace it or false to indicate it should not. Useful, for example, in a schematic that should replace water but not earth (placing decorations on the bottom of the ocean), or a schematic that replaces all buildable_to nodes (to prevent tufts of grass from knocking holes in foundations).
* The schematic can have a "`center_pos`" position defined relative to the schematic's minpos that will be treated as the rotation and placement origin, unless overridden by the flags parameter.
* Returns true if the schematic was entirely contained within the given voxelarea, false otherwise.
## Bounding boxes
* `mapgen_helper.is_within_distance_box(pos1, pos2, distance)`
* A very cheap nearness test using Manhattan distance, useful as a fast first-pass check to see whether a more math-heavy test is needed.
* `mapgen_helper.intersect(minpos1, maxpos1, minpos2, maxpos2)`
* Given two bounding boxes, returns a pair of points representing the minpos and maxpos of the intersection between them. If no intersection exists it returns nil.
* `mapgen_helper.intersect_exists(minpos1, maxpos1, minpos2, maxpos2)`
* A cheaper version of the above method that only returns a true/false indication that an intersection exists.
* `mapgen_helper.intersect_exists_xz(minpos1, maxpos1, minpos2, maxpos2)`
* An _even cheaper_ version of the above method that returns true/false based on whether the two bounding boxes overlap on the x-z plane.
## Lines
* `mapgen_helper.distance_to_segment(pos, start, end)`
* Returns the minimum distance from the point "pos" to any point along the line segment defined by the points "start" and "end"
* `mapgen_helper.distance_to_path(pos, path)`
* Similar to the above, but "path" is a list of points forming a continuous series of line segments. Distance returned is the shortest distance to any segment in the list.
## Random values
* `mapgen_helper.get_random_points(minp, maxp, min_output_size, max_output_size)`
* Returns a consistent list of random points within a volume. Each call to this method will give the same set of points if the same parameters are provided, making this useful for placing things that extend outside of the currently-generated map chunk - for example, minp and maxp could be kilometers apart and the points designate the locations where mountains are to be placed.
* `mapgen_helper.xz_consistent_randomp(pos)'
* Returns a random value based on the x and z coordinates of pos. The random value is always the same for the same x and z.
* `mapgen_helper.xz_consistent_randomi(area, vi)`
* Same as above, but takes a VoxelArea and a voxel index as parameters.
## Region tiling
These methods are useful with things like `mapgen_helper.get_random_points`, allowing you to seamlessly place items that cross mapchunk boundaries by generating sets of random points for all adjacent regions.
* `mapgen_helper.get_region_minp_xz(pos_xz, mapblocks)`
* Returns a consistent large region that doesn't cross mapgen block boundaries. Regions are `mapblocks * chunksize` nodes in width and depth. Ignores the y coordinate.
* `mapgen_helper.get_nearest_regions(pos_xz, gridscale)`
* Given a position and a region scale, returns the minp corners of the four regions that the player is closest to.
* For example, if we have a region scale of 6 and the parameter position is at "*", the four points marked "R" would be returned:
|-----|-----|-----| |-----|-----|-----| |-----|-----|-----|
| | | | | | | | | | | |
| | | | | | | | | | | |
| | | | | | | | | | | |
| | | | | | | | | | | |
| | | | | | | | | | | |
|-----|-----|-----| |-----|-----|-----| |-----R-----R-----|
| | | | | | | | | | | |
| | | | | | | | | | *| |
| | | | | | | | | | | |
| | * | | | | *| | | | | |
| | | | | | | | | | | |
R-----R-----|-----| |-----R-----R-----| |-----R-----R-----|
| | | | | | | | | | | |
| | | | | | | | | | | |
| | | | | | | | | | | |
| | | | | | | | | | | |
| | | | | | | | | | | |
R-----R-----|-----| |-----R-----R-----| |-----|-----|-----|
* `mapgen_helper.get_adjacent_regions(pos_xz, gridscale)`
* Similar to the above, but this method returns all adjacent regions and not just the nearest ones:
|---|---|---|
| | | |
| | | |
R---R---R---|
| | | |
| | *| |
R---R---R---|
| | | |
| | | |
R---R---R---|
@@ -0,0 +1,54 @@
-- A cheap nearness test, using Manhattan distance.
mapgen_helper.is_within_distance_box = function(pos1, pos2, distance)
return math.abs(pos1.x-pos2.x) <= distance and
math.abs(pos1.y-pos2.y) <= distance and
math.abs(pos1.z-pos2.z) <= distance
end
-- Finds an intersection between two axis-aligned bounding boxes (AABB)s, or nil if there's no overlap
mapgen_helper.intersect = function(minpos1, maxpos1, minpos2, maxpos2)
--checking x and z first since they're more likely to fail to overlap
if minpos1.x <= maxpos2.x and maxpos1.x >= minpos2.x and
minpos1.z <= maxpos2.z and maxpos1.z >= minpos2.z and
minpos1.y <= maxpos2.y and maxpos1.y >= minpos2.y then
return {
x = math.max(minpos1.x, minpos2.x),
y = math.max(minpos1.y, minpos2.y),
z = math.max(minpos1.z, minpos2.z)
},
{
x = math.min(maxpos1.x, maxpos2.x),
y = math.min(maxpos1.y, maxpos2.y),
z = math.min(maxpos1.z, maxpos2.z)
}
end
return nil, nil
end
-- a simpler version of the above if all you care about is whether an intersection exists
mapgen_helper.intersect_exists = function(minpos1, maxpos1, minpos2, maxpos2)
--checking x and z first since they're more likely to fail to overlap
return (minpos1.x <= maxpos2.x and maxpos1.x >= minpos2.x and
minpos1.z <= maxpos2.z and maxpos1.z >= minpos2.z and
minpos1.y <= maxpos2.y and maxpos1.y >= minpos2.y)
end
-- A version of the above that only cares about the x and z parameters, useful for example to lay out non-overlapping trees or buildings
mapgen_helper.intersect_exists_xz = function(minpos1, maxpos1, minpos2, maxpos2)
return (minpos1.x <= maxpos2.x and maxpos1.x >= minpos2.x and
minpos1.z <= maxpos2.z and maxpos1.z >= minpos2.z)
end
-- Simply tests whether pos is within the bounding box defined by minpos and maxpos
local intersect_exists = mapgen_helper.intersect_exists
mapgen_helper.is_pos_within_box = function(pos, minpos, maxpos)
return intersect_exists(pos, pos, minpos, maxpos)
end
-- Tests whether box 1 is entirely contained within box 2
mapgen_helper.is_box_within_box = function(minpos1, maxpos1, minpos2, maxpos2)
return (minpos1.x >= minpos2.x and maxpos1.x <= maxpos2.x and
minpos1.z >= minpos2.z and maxpos1.z <= maxpos2.z and
minpos1.y >= minpos2.y and maxpos1.y <= maxpos2.y)
end
+84
View File
@@ -0,0 +1,84 @@
mapgen_helper = {}
mapgen_helper.mapgen_seed = tonumber(minetest.get_mapgen_setting("seed"))
-- The "sidelen" used in almost every mapgen
mapgen_helper.block_size = tonumber(minetest.get_mapgen_setting("chunksize")) * 16
local MP = minetest.get_modpath(minetest.get_current_modname())
dofile(MP.."/bounding_boxes.lua")
dofile(MP.."/random.lua")
dofile(MP.."/voxelarea_iterator.lua")
dofile(MP.."/voxeldata.lua")
dofile(MP.."/region_functions.lua")
dofile(MP.."/lines.lua")
dofile(MP.."/place_schematic.lua")
dofile(MP.."/noise_manager.lua")
dofile(MP.."/metrics.lua")
dofile(MP.."/log_location.lua")
local map_data = {}
local map_param2_data = {}
mapgen_helper.mapgen_vm_data = function()
local vm, emin, emax = minetest.get_mapgen_object("voxelmanip")
vm:get_data(map_data)
return vm, map_data, VoxelArea:new{MinEdge=emin, MaxEdge=emax}
end
mapgen_helper.mapgen_vm_data_param2 = function()
local vm, emin, emax = minetest.get_mapgen_object("voxelmanip")
vm:get_data(map_data)
vm:get_param2_data(map_param2_data)
return vm, map_data, map_param2_data, VoxelArea:new{MinEdge=emin, MaxEdge=emax}
end
mapgen_helper.biome_defs = nil
mapgen_helper.get_biome_def = function(biome_id) -- given an id from the biome map, returns a biome definition.
if mapgen_helper.biome_defs == nil then
-- First time this was asked for, populate the table.
-- Biome registration is only done at load time so we don't have to worry about new biomes invalidating this table
mapgen_helper.biome_defs = {}
for name, desc in pairs(minetest.registered_biomes) do
local i = minetest.get_biome_id(desc.name)
mapgen_helper.biome_defs[i] = desc
end
end
return mapgen_helper.biome_defs[biome_id]
end
mapgen_helper.get_biome_def_i = function(biomemap, minp, maxp, area, vi)
if biomemap == nil then
return nil
end
local index2d = mapgen_helper.index2di(minp, maxp, area, vi)
return mapgen_helper.get_biome_def(biomemap[index2d])
end
-- returns whether a content ID is "buildable to"
local buildable_to
mapgen_helper.buildable_to = function(c_node)
if buildable_to then return buildable_to[c_node] end
buildable_to = {}
for k, v in pairs(minetest.registered_nodes) do
if v.buildable_to then
buildable_to[minetest.get_content_id(k)] = true
end
end
return buildable_to[c_node]
end
local is_ground_content = nil
mapgen_helper.is_ground_content = function(c_node) -- If false, the cave generator will not carve through this node
if is_ground_content ~= nil then
return is_ground_content[c_node]
end
is_ground_content = {}
for k, v in pairs(minetest.registered_nodes) do
if v.is_ground_content == true then
is_ground_content[minetest.get_content_id(k)] = true
end
end
return is_ground_content[c_node]
end
+76
View File
@@ -0,0 +1,76 @@
-- algorithms taken from https://gist.github.com/reciprocum/4e3599a9563ec83ba2a63f5a6cdd39eb
local dot = function(a, b)
return a.x * b.x + a.y * b.y + a.z * b.z
end
local modulus = function(v)
return math.sqrt(dot(v,v))
end
local cross = function(a, b)
return {x= a.y * b.z - a.z * b.y,
y= a.z * b.x - a.x * b.z,
z= a.x * b.y - a.y * b.x}
end
-- TODO: add bounding box tests to early-out on distant segments
-- Calculates the distance from a point to a line segment.
-- v - the point
-- a - start of line segment
-- b - end of line segment
mapgen_helper.distance_to_segment = function(v, a, b)
local ab = vector.subtract(b, a)
local av = vector.subtract(v, a)
if dot(av, ab) <= 0.0 then -- Point is lagging behind start of the segment, so perpendicular distance is not viable.
return modulus(av) -- Use distance to start of segment instead.
end
local bv = vector.subtract(v, b)
if dot(bv, ab) >= 0.0 then -- Point is advanced past the end of the segment, so perpendicular distance is not viable.
return modulus(bv) -- Use distance to end of the segment instead.
end
return modulus( cross(ab, av )) / modulus(ab) -- Perpendicular distance of point to segment.
end
-- Calculates the euclidean distance from a point to a line segmented path.
-- v - the point from with the distance is measured
-- path - the array of points which, when sequentialy joined by line segments, form a path
-- returns the distance from v to the closest of the path forming line segments
mapgen_helper.distance_to_path = function(v, path)
local minimum_distance = 2^32 -- Should be big enough for any practical minetest needs
for i = 2, table.getn(path) do
local distance = mapgen_helper.distance_to_segment(path[i-1], path[i])
if (distance < minimum_distance) then
minimum_distance = distance
end
end
return minimum_distance
end
-- For digging out straight lines
local dist_sq = function(x1, y1, z1, x2, y2, z2)
return (x2-x1)^2 + (y2-y1)^2 + (z2-z1)^2
end
local constrain = function(n, min_n, max_n)
if n < min_n then return min_n
elseif n > max_n then return max_n
else return n end
end
local dist_to_segment_squared = function(px, py, pz, lx1, ly1, lz1, lx2, ly2, lz2)
local line_dist = dist_sq(lx1, ly1, lz1, lx2, ly2, lz2)
if (line_dist == 0) then
return dist_sq(px, py, pz, lx1, ly1, lz1)
end
local t = ((px - lx1) * (lx2 - lx1) + (py - ly1) * (ly2 - ly1) + (pz - lz1) * (lz2 - lz1)) / line_dist
t = constrain(t, 0, 1)
return dist_sq(px, py, pz, lx1 + t * (lx2 - lx1), ly1 + t * (ly2 - ly1), lz1 + t * (lz2 - lz1))
end
@@ -0,0 +1,123 @@
if not minetest.settings:get_bool("mapgen_helper_log_locations", false) then
mapgen_helper.log_first_location = function()
return
end
return
end
mapgen_helper.log_location_enabled = true
local worldpath = minetest.get_worldpath()
local locations = {}
local save_data = function()
local file = io.open(worldpath.."/mapgen_helper_test_locations.json", "w")
if file then
local data = {}
data.locations = locations
file:write(minetest.serialize(data))
file:close()
end
end
local read_data = function()
local file = io.open(worldpath.."/mapgen_helper_test_locations.json", "r")
if file then
local data = minetest.deserialize(file:read("*all"))
locations = data.locations or {}
-- to upgrade legacy data that didn't have counts recorded
for _, location in pairs(locations) do
location.count = location.count or 1
end
file:close()
else
locations = {}
end
end
read_data()
-- logs only the first location of a particular type, ignores any subsequent ones
mapgen_helper.log_first_location = function(name, pos, desc, notes)
if not locations[name] then
locations[name] = {pos=pos, desc=desc, notes=notes, count = 1}
minetest.log("info", "[mapgen_helper] recorded location " .. name .. " at " .. minetest.pos_to_string(pos) .. " with desc '" .. (desc or "") .. "'")
save_data()
end
end
mapgen_helper.log_location = function(name, pos, desc, notes)
local location = locations[name] or {pos=pos, desc=desc, notes=notes, count = 0}
locations[name] = location
location.pos = pos
location.desc = desc
location.notes = notes
location.count = location.count + 1
end
minetest.register_chatcommand("mapgen_helper_loc", {
params = "[location name]",
description = "sends the player to the named test location, or lists recorded locations if no parameter is given",
privs = {server=true},
func = function(name, param)
if not param or param == "" then
minetest.chat_send_player(name,"test locations available:")
local names = {}
for loc_name, loc in pairs(locations) do
table.insert(names, loc_name)
end
table.sort(names)
for number, loc_name in ipairs(names) do
minetest.chat_send_player(name, "\t"..loc_name .. " - " .. (locations[loc_name].desc or "") .. " - " .. tostring(locations[loc_name].count))
end
return
end
local loc = locations[param]
if not loc then
minetest.chat_send_all("test location " .. param .. " not found")
return
end
local player = minetest.get_player_by_name(name)
player:set_pos(loc.pos)
if loc.notes then
minetest.chat_send_player(name, loc.notes)
end
end,
})
local tour_index = 0
minetest.register_chatcommand("mapgen_helper_tour", {
params = "<next|prev>",
description = "cycles through the various recorded mapgen test locations, forward or backward",
privs = {server=true},
func = function(name, param)
local names = {}
for name, loc in pairs(locations) do
table.insert(names, name)
end
table.sort(names)
if param == "prev" then
tour_index = tour_index - 1
if tour_index < 1 then
tour_index = #names
end
else
tour_index = tour_index + 1
if tour_index > #names then
tour_index = 1
end
end
local loc = locations[names[tour_index]]
local player = minetest.get_player_by_name(name)
player:set_pos(loc.pos)
minetest.chat_send_player(name, "Teleport to test location " .. names[tour_index])
if loc.notes then
minetest.chat_send_player(name, loc.notes)
end
end
})
minetest.register_on_shutdown(function()
save_data()
end)
+113
View File
@@ -0,0 +1,113 @@
if not minetest.settings:get_bool("mapgen_helper_record_time", false) then
mapgen_helper.record_time = function()
return
end
return
end
local worldpath = minetest.get_worldpath()
local persist = minetest.settings:get_bool("mapgen_helper_persist_recorded_time", false)
local filename = "mapgen_helper_metrics.lua"
local filename_old = "mapgen_helper_metrics_old.lua"
local resolution = minetest.settings:get("mapgen_helper_record_time_resolution") or 0.1
local data = {}
data.times = {}
if persist then
local read_data = loadfile(worldpath.."/"..filename)
if read_data then
data = read_data()
end
end
if data.resolution ~= nil and data.resolution ~= resolution then
minetest.log("error", "[mapgen_helper] resolution of recorded metrics doesn't match saved metrics. Moving the old saved metrics to " .. filename_old)
local file, e = io.open(worldpath.."/"..filename_old, "w");
if not file then
return error(e);
end
file:write(minetest.serialize(data))
file:close()
data = {}
data.times = {}
end
data.resolution = resolution
local times = data.times
mapgen_helper.record_time = function(label, time)
local time_slot = math.ceil(time/resolution)
local label_data = times[label] or {}
local counts = label_data.counts or {}
local existing_count = counts[time_slot] or 0
local total_time = label_data.total_time or 0
counts[time_slot] = existing_count + 1
total_time = total_time + time
label_data.total_time = total_time
label_data.counts = counts
times[label] = label_data
end
minetest.register_on_shutdown(function()
if next(times) == nil then
return
end
minetest.log("warning", "[mapgen_helper] Average chunk generation times:")
for name, label_data in pairs(times) do
minetest.log("warning", string.rep("-", 40))
local min_slot
local max_slot
local highest_slot_count = 0
local total_count = 0
local max_digits
for slot, count in pairs(label_data.counts) do
if (not min_slot) or (slot < min_slot) then
min_slot = slot
end
if (not max_slot) or (slot > max_slot) then
max_slot = slot
end
if (not max_digits) or (#tostring(slot*resolution) > max_digits) then
max_digits = #tostring(slot*resolution)
end
total_count = total_count + count
highest_slot_count = math.max(highest_slot_count, count)
end
local average = label_data.total_time / total_count
minetest.log("warning", "For " .. name .. ":")
if min_slot == max_slot then
minetest.log("warning", "All chunks took between " .. (min_slot-1)*resolution .. " and " .. min_slot*resolution .. " seconds.")
else
for i = min_slot, max_slot do
local count = label_data.counts[i] or 0
local spacing = max_digits - #tostring(i*resolution)
local count_string = ""
if count > 0 then
count_string = " " .. count
end
minetest.log("warning", i*resolution .. "s" .. string.rep(" ", spacing) .. ": " ..
string.rep("=", math.ceil((count / highest_slot_count) * 40)) .. count_string)
end
end
minetest.log("warning", "Total number of chunks: " .. total_count .. ". Average time per chunk: " .. average .. " seconds.")
end
minetest.log("warning", string.rep("-", 40))
if persist then
local file, e = io.open(worldpath.."/"..filename, "w");
if not file then
return error(e);
end
file:write(minetest.serialize(data))
file:close()
end
end)
+2
View File
@@ -0,0 +1,2 @@
name = mapgen_helper
description = A library of mapgen-related functions and tools.
@@ -0,0 +1,114 @@
local perlin_buffers = {}
mapgen_helper.register_perlin = function(name, perlin_params)
perlin_buffers[name] = perlin_buffers[name] or {}
if perlin_buffers[name].perlin_params then
minetest.log("error", "mapgen_helper.register_perlin3d called for " .. name .. " when perlin parameters were already registered.")
return
end
perlin_buffers[name].perlin_params = perlin_params
end
local get_buffer = function(name, sidelen, perlin_params)
perlin_buffers[name] = perlin_buffers[name] or {}
local buffer = perlin_buffers[name]
if buffer.sidelen ~= nil and buffer.sidelen ~= sidelen then
buffer.nobj_perlin = nil -- parameter changed, force regenerate of object
end
buffer.sidelen = sidelen
if perlin_params then
if buffer.perlin_params then
for k, v in pairs(buffer.perlin_params) do
if perlin_params[k] ~= v then
buffer.nobj_perlin = nil -- parameter changed, force regenerate of object
end
end
end
buffer.perlin_params = perlin_params -- record provided parameters
elseif buffer.perlin_params == nil then
minetest.log("error", "mapgen_helper.register_perlin3d called for " .. name .. " with no registered or provided perlin parameters.")
return
else
perlin_params = buffer.perlin_params -- retrieve recorded parameters
end
buffer.last_used = minetest.get_gametime()
return buffer, perlin_params
end
mapgen_helper.perlin3d = function(name, minp, maxp, perlin_params)
local minx = minp.x
local minz = minp.z
local sidelen = maxp.x - minp.x + 1 --length of a mapblock
local chunk_lengths = {x = sidelen, y = sidelen, z = sidelen} --table of chunk edges
local buffer, perlin_params = get_buffer(name, sidelen, perlin_params)
buffer.nvals_perlin_buffer = buffer.nvals_perlin_buffer or {}
buffer.nobj_perlin = buffer.nobj_perlin or minetest.get_perlin_map(perlin_params, chunk_lengths)
if buffer.nobj_perlin.get_3d_map_flat then
return buffer.nobj_perlin:get_3d_map_flat(minp, buffer.nvals_perlin_buffer), VoxelArea:new{MinEdge=minp, MaxEdge=maxp}
else
return buffer.nobj_perlin:get3dMap_flat(minp, buffer.nvals_perlin_buffer), VoxelArea:new{MinEdge=minp, MaxEdge=maxp}
end
end
mapgen_helper.perlin2d = function(name, minp, maxp, perlin_params)
local minx = minp.x
local minz = minp.z
local sidelen = maxp.x - minp.x + 1 --length of a mapblock
local chunk_lengths = {x = sidelen, y = sidelen, z = sidelen} --table of chunk edges
local buffer, perlin_params = get_buffer(name, sidelen, perlin_params)
buffer.nvals_perlin_buffer = perlin_buffers[name].nvals_perlin_buffer or {}
buffer.nobj_perlin = perlin_buffers[name].nobj_perlin or minetest.get_perlin_map(perlin_params, chunk_lengths)
if buffer.nobj_perlin.get_2d_map_flat then
return buffer.nobj_perlin:get_2d_map_flat({x=minp.x, y=minp.z}, perlin_buffers[name].nvals_perlin_buffer)
else
return buffer.nobj_perlin:get2dMap_flat({x=minp.x, y=minp.z}, perlin_buffers[name].nvals_perlin_buffer)
end
end
mapgen_helper.index2d = function(minp, maxp, x, z)
return x - minp.x +
(maxp.x - minp.x + 1) -- sidelen
*(z - minp.z)
+ 1
end
mapgen_helper.index2dp = function(minp, maxp, pos)
return mapgen_helper.index2d(minp, maxp, pos.x, pos.z)
end
-- Takes an index into a 3D area and returns the corresponding 2D index
-- assumes equal edge lengths
mapgen_helper.index2di = function(minp, maxp, area, vi)
local MinEdge = area.MinEdge
local zstride = area.zstride
local minpx = minp.x
local i = vi - 1
local z = math.floor(i / zstride) + MinEdge.z
local x = math.floor((i % zstride) % area.ystride) + MinEdge.x
return x - minpx +
(maxp.x - minpx + 1) -- sidelen
*(z - minp.z)
+ 1
end
-- If a noise buffer hasn't been used in 60 seconds, let garbage collection take it.
local time_elapsed = 0
minetest.register_globalstep(function(dtime)
time_elapsed = time_elapsed + dtime
if time_elapsed > 60 then
local current_time = minetest.get_gametime()
time_elapsed = 0
for _, buffer in pairs(perlin_buffers) do
if current_time - buffer.last_used > 60 then
buffer.nvals_perlin_buffer = nil
end
end
end
end)
@@ -0,0 +1,319 @@
-- These functions are a modification of the schematic placement code from src/mapgen/mg_schematic.cpp.
-- As such, this file is separately licened under the LGPL as follows:
-- License of Minetest source code
-------------------------------
--Minetest
--Copyright (C) 2010-2018 celeron55, Perttu Ahola <celeron55@gmail.com>
--This program is free software; you can redistribute it and/or modify
--it under the terms of the GNU Lesser General Public License as published by
--the Free Software Foundation; either version 2.1 of the License, or
--(at your option) any later version.
--This program is distributed in the hope that it will be useful,
--but WITHOUT ANY WARRANTY; without even the implied warranty of
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
--GNU Lesser General Public License for more details.
--You should have received a copy of the GNU Lesser General Public License along
--with this program; if not, write to the Free Software Foundation, Inc.,
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
local c_air = minetest.get_content_id("air")
local c_ignore = minetest.get_content_id("ignore")
-- Table value = rotated facedir
-- Columns: 90, 180, 270 degrees rotation around vertical axis
-- Rotation is anticlockwise as seen from above (+Y)
local rotate_facedir_y =
{
[0] = {1, 2, 3} ,
[1] = {2, 3, 0} ,
[2] = {3, 0, 1} ,
[3] = {0, 1, 2} ,
[4] = {13, 10, 19} ,
[5] = {14, 11, 16} ,
[6] = {15, 8, 17} ,
[7] = {12, 9, 18} ,
[8] = {17, 6, 15} ,
[9] = {18, 7, 12} ,
[10] = {19, 4, 13} ,
[11] = {16, 5, 14} ,
[12] = {9, 18, 7} ,
[13] = {10, 19, 4} ,
[14] = {11, 16, 5} ,
[15] = {8, 17, 6} ,
[16] = {5, 14, 11} ,
[17] = {6, 15, 8} ,
[18] = {7, 12, 9} ,
[19] = {4, 13, 10} ,
[20] = {23, 22, 21} ,
[21] = {20, 23, 22} ,
[22] = {21, 20, 23} ,
[23] = {22, 21, 20} ,
}
local random_rotations = {0, 90, 180, 270}
local rotate_param2 = function(param2, paramtype2, rotation)
param2 = param2 or 0
if paramtype2 == "facedir" then
if rotation == 90 then
param2 = rotate_facedir_y[param2][1]
elseif rotation == 180 then
param2 = rotate_facedir_y[param2][2]
elseif rotation == 270 then
param2 = rotate_facedir_y[param2][3]
end
elseif paramtype2 == "wallmounted" then
--TODO
elseif paramtype2 == "colorfacedir" then
--TODO
elseif paramtype2 == "colorwallmounted" then
--TODO
end
return param2
end
local swap = function(size_x, size_z)
return size_z, size_x
end
-- Returns the minpos and maxpos of the bounding box that this schematic will be placed in given
-- the rotation and flag parameters. Useful for testing whether a schematic will fit in a place before actually
-- writing it to the data, so that you can abort and try something else instead.
mapgen_helper.get_schematic_bounding_box = function(pos, schematic, rotation, flags)
flags = flags or {}
local size = schematic.size
local size_x = size.x
local size_y = size.y
local size_z = size.z
local center_pos = schematic.center_pos
if center_pos and rotation ~= nil and rotation ~= 0 then
center_pos = vector.new(center_pos) -- make a copy so we can mess with it without damaging the schematic
if rotation == 90 then
center_pos.x, center_pos.z = swap(size_x - center_pos.x - 1, center_pos.z)
elseif rotation == 180 then
center_pos.z = size_z - center_pos.z - 1
center_pos.x = size_x - center_pos.x - 1
elseif rotation == 270 then
center_pos.x, center_pos.z = swap(center_pos.x, size_z - center_pos.z - 1)
end
end
if rotation == 90 or rotation == 270 then
size_x, size_z = swap(size_x, size_z)
end
local minpos = vector.new(pos)
if center_pos then
if not flags.place_center_x then
minpos.x = minpos.x - center_pos.x
end
if not flags.place_center_y then
minpos.y = minpos.y - center_pos.y
end
if not flags.place_center_z then
minpos.z = minpos.z - center_pos.z
end
end
if flags.place_center_x then
minpos.x = math.floor(minpos.x - (size_x - 1) / 2)
end
if flags.place_center_y then
minpos.y = math.floor(minpos.y - (size_y - 1) / 2)
end
if flags.place_center_z then
minpos.z = math.floor(minpos.z - (size_z - 1) / 2)
end
local maxpos = vector.add(minpos, {x=size_x-1, y=size_y-1, z=size_z-1})
return minpos, maxpos
end
-- Takes a lua-format schematic and applies it to the data and param2_data arrays produced by vmanip instead of being applied to the vmanip directly. Useful in a mapgen loop that's doing other things with the data before and after applying schematics. A VoxelArea for the data also needs to be provided.
-- Schematic enhancements beyond the basic Minetest API that work with this:
-- * node defs can have a "place_on_condition" property defined, which is a function that takes a node content ID parameter and returns true to indicate the schematic should replace it or false to indicate it should not. Useful for, for example, a schematic that should replace water but not stone (placing decorations on the bottom of the ocean), or a schematic that replaces all buildable_to nodes (to prevent tufts of grass from knocking holes in foundations). "data, area, vi" parameters are also provided if you want to get fancy and base the condition on surrounding nodes, but bear in mind that the schematic is in the process of being written already so some neighbors will already have been replaced with schematic nodes and some neighbors will be replaced in the future.
-- * schematic can have a "center_pos" position defined relative to the placement pos that will be treated as the rotation and placement origin, unless overridden by the flags parameter
-- returns true if the schematic was entirely contained within the voxelarea, false otherwise.
local empty_table = {}
mapgen_helper.place_schematic_on_data = function(data, data_param2, area, pos, schematic, rotation, replacements, force_placement, flags)
pos = vector.new(pos)
replacements = replacements or empty_table
flags = flags or empty_table -- TODO: support all flags formats
if flags.force_placement ~= nil then
force_placement = flags.force_placement -- TODO: unclear which force_placement parameter should have prededence here
end
local center_pos = schematic.center_pos
if rotation == "random" then rotation = random_rotations[math.random(1,4)] end
local schemdata = schematic.data
local slice_probs = schematic.yslice_prob or {}
local size = schematic.size
local size_x = size.x
local size_y = size.y
local size_z = size.z
local xstride = 1
local ystride = size_x
local zstride = size_x * size_y
if center_pos and rotation ~= nil and rotation ~= 0 then
center_pos = vector.new(center_pos) -- make a copy so we can mess with it without damaging the schematic
if rotation == 90 then
center_pos.x, center_pos.z = swap(size_x - center_pos.x - 1, center_pos.z)
elseif rotation == 180 then
center_pos.z = size_z - center_pos.z - 1
center_pos.x = size_x - center_pos.x - 1
elseif rotation == 270 then
center_pos.x, center_pos.z = swap(center_pos.x, size_z - center_pos.z - 1)
end
end
local i_start, i_step_x, i_step_z
if rotation == 90 then
i_start = size_x
i_step_x = zstride
i_step_z = -xstride
size_x, size_z = swap(size_x, size_z)
elseif rotation == 180 then
i_start = zstride * (size_z - 1) + size_x
i_step_x = -xstride
i_step_z = -zstride
elseif rotation == 270 then
i_start = zstride * (size_z - 1) + 1
i_step_x = -zstride
i_step_z = xstride
size_x, size_z = swap(size_x, size_z)
else
i_start = 1
i_step_x = xstride
i_step_z = zstride
end
-- Adjust placement position if necessary
if center_pos then
if not flags.place_center_x then
pos.x = pos.x - center_pos.x
end
if not flags.place_center_y then
pos.y = pos.y - center_pos.y
end
if not flags.place_center_z then
pos.z = pos.z - center_pos.z
end
end
if flags.place_center_x then
pos.x = math.floor(pos.x - (size_x - 1) / 2)
end
if flags.place_center_y then
pos.y = math.floor(pos.y - (size_y - 1) / 2)
end
if flags.place_center_z then
pos.z = math.floor(pos.z - (size_z - 1) / 2)
end
local maxpos = vector.add(pos, {x=size_x-1, y=size_y-1, z=size_z-1})
local minEdge = area.MinEdge
local maxEdge = area.MaxEdge
if not (pos.x <= maxEdge.x and maxpos.x >= minEdge.x and
pos.z <= maxEdge.z and maxpos.z >= minEdge.z and
pos.y <= maxEdge.y and maxpos.y >= minEdge.y) then
return false -- the bounding boxes of the area and the schematic don't overlap at all
end
local contained_in_area = true
local on_place_callbacks = {}
local y_map = pos.y
for y = 0, size_y-1 do
if slice_probs[y] == nil or slice_probs[y] == 255 or slice_probs[y] <= math.random(1, 255) then
for z = 0, size_z-1 do
local i = z * i_step_z + y * ystride + i_start
for x = 0, size_x-1 do
local vi = area:index(pos.x + x, y_map, pos.z + z)
if area:containsi(vi) then
local node_def = schemdata[i]
local node_name = replacements[node_def.name] or node_def.name
if node_name ~= "ignore" then
local placement_prob = node_def.prob or 255
if placement_prob ~= 0 then
local force_place_node = node_def.force_place
local place_on_condition = node_def.place_on_condition
local on_place = node_def.on_place
local old_node_id = data[vi]
if (force_placement or force_place_node
or (place_on_condition and place_on_condition(old_node_id, data, area, vi))
or (not place_on_condition and (old_node_id == c_air or old_node_id == c_ignore)))
and (placement_prob == 255 or math.random(1,255) <= placement_prob)
then
local registered_def = minetest.registered_nodes[node_name]
if registered_def ~= nil then
local paramtype2 = registered_def.paramtype2
data[vi] = minetest.get_content_id(node_name)
data_param2[vi] = rotate_param2(node_def.param2, paramtype2, rotation)
if on_place then
table.insert(on_place_callbacks, {on_place, vi})
end
else
minetest.log("error", "mapgen_helper.place_schematic was given a schematic with unregistered node " .. tostring(node_name) .. " in it.")
end
end
end
end
else
contained_in_area = false -- schematic spilled over the edge of the area
end
i = i + i_step_x
end
end
end
y_map = y_map + 1
end
for k, callback in pairs(on_place_callbacks) do
callback[1](callback[2], data, data_param2, area, pos, schematic, rotation, replacements, force_placement, flags)
end
return contained_in_area
end
-- aborts schematic placement if it won't fit into the provided data
mapgen_helper.place_schematic_on_data_if_it_fits = function(data, data_param2, area, pos, schematic, rotation, replacements, force_placement, flags)
local minbound, maxbound = mapgen_helper.get_schematic_bounding_box(pos, schematic, rotation, flags)
if mapgen_helper.is_box_within_box(minbound, maxbound, area.MinEdge, area.MaxEdge) then
return mapgen_helper.place_schematic_on_data(data, data_param2, area, pos, schematic, rotation, replacements, force_placement, flags)
end
return false
end
-- wraps the above for convenience, so you can use this style of schematic in non-mapgen contexts as well
mapgen_helper.place_schematic = function(pos, schematic, rotation, replacements, force_placement, flags)
local minpos, maxpos = mapgen_helper.get_schematic_bounding_box(pos, schematic, rotation, flags)
local vmanip = minetest.get_voxel_manip(minpos, maxpos)
local data = vmanip:get_data()
local data_param2 = vmanip:get_param2_data()
local emin, emax = vmanip:get_emerged_area()
local area = VoxelArea:new{MinEdge=emin, MaxEdge=emax}
local ret = mapgen_helper.place_schematic_on_data(data, data_param2, area, pos, schematic, rotation, replacements, force_placement, flags)
vmanip:set_data(data)
vmanip:set_param2_data(data_param2)
vmanip:write_to_map()
return ret -- should always be true since we created the voxelarea to fit the schematic
end
+35
View File
@@ -0,0 +1,35 @@
-- Returns a consistent list of random points within a volume.
-- Each call to this method will give the same set of points if the same parameters are provided
mapgen_helper.get_random_points = function(minp, maxp, min_output_size, max_output_size)
local next_seed = math.random(1, 2^21) -- should be 2^31, but I've had a report that this causes a crash in the Lua interpreter on some systems.
math.randomseed(minetest.hash_node_position(minp) + mapgen_helper.mapgen_seed)
local count = math.random(min_output_size, max_output_size)
local result = {}
while count > 0 do
local point = {}
point.x = math.random(minp.x, maxp.x)
point.y = math.random(minp.y, maxp.y)
point.z = math.random(minp.z, maxp.z)
table.insert(result, point)
count = count - 1
end
math.randomseed(next_seed)
return result
end
-- Returns a random value based on the x and z coordinates of pos, always the same for the same x and z
mapgen_helper.xz_consistent_randomp = function(pos)
local next_seed = math.random(1, 2^21) -- should be 2^31, but I've had a report that this causes a crash in the Lua interpreter on some systems.
math.randomseed(pos.x + pos.z * 2 ^ 8)
local output = math.random()
math.randomseed(next_seed)
return output
end
mapgen_helper.xz_consistent_randomi = function(area, vi)
local pos = area:position(vi)
return mapgen_helper.xz_consistent_randomp(pos)
end
@@ -0,0 +1,78 @@
local mapgen_chunksize = tonumber(minetest.get_mapgen_setting("chunksize"))
-- For getting large regions that don't cross mapgen block boundaries
-- For some reason, map chunks generate with -32, -32, -32 as the "origin" minp. To make the large-scale grid align with map chunks it needs to be offset like this.
mapgen_helper.get_region_minp_xz = function(pos_xz, mapblocks)
local region_size = mapblocks * mapgen_chunksize * 16
return {x = math.floor((pos.x+32) / region_size) * region_size - 32, y = 0, z = math.floor((pos.z+32) / region_size) * region_size - 32}
end
-- Given a position and a region scale, returns the minp corners of the four regions that the player is closest to.
-- For example, if we have a region scale of 6 and the parameter position is at "*", the four points marked "R" would be returned.
-- |-----|-----|-----| |-----|-----|-----| |-----|-----|-----|
-- | | | | | | | | | | | |
-- | | | | | | | | | | | |
-- | | | | | | | | | | | |
-- | | | | | | | | | | | |
-- | | | | | | | | | | | |
-- |-----|-----|-----| |-----|-----|-----| |-----R-----R-----|
-- | | | | | | | | | | | |
-- | | | | | | | | | | *| |
-- | | | | | | | | | | | |
-- | | * | | | | *| | | | | |
-- | | | | | | | | | | | |
-- R-----R-----|-----| |-----R-----R-----| |-----R-----R-----|
-- | | | | | | | | | | | |
-- | | | | | | | | | | | |
-- | | | | | | | | | | | |
-- | | | | | | | | | | | |
-- | | | | | | | | | | | |
-- R-----R-----|-----| |-----R-----R-----| |-----|-----|-----|
-- In this way you can be sure you aren't near an area you haven't tested.
mapgen_helper.get_nearest_regions = function(pos_xz, gridscale)
local half_scale = gridscale / 2
local grid_cell = {x = math.floor(pos_xz.x / gridscale) * gridscale, z = math.floor(pos_xz.z / gridscale) * gridscale}
local pos_internal = {x = pos_xz.x % gridscale, z = pos_xz.z % gridscale}
local result = {grid_cell}
local shift_x = gridscale
local shift_z = gridscale
if (pos_internal.x < half_scale) then
shift_x = -gridscale
end
if (pos_internal.z < half_scale) then
shift_z = -gridscale
end
table.insert(result, {x = grid_cell.x + shift_x, z = grid_cell.z + shift_z})
table.insert(result, {x = grid_cell.x + shift_x, z = grid_cell.z})
table.insert(result, {x = grid_cell.x, z = grid_cell.z + shift_z})
return result
end
-- |---|---|---|
-- | | | |
-- | | | |
-- R---R---R---|
-- | | | |
-- | | *| |
-- R---R---R---|
-- | | | |
-- | | | |
-- R---R---R---|
mapgen_helper.get_adjacent_regions = function(pos_xz, gridscale)
local grid_cell = {x = math.floor(pos_xz.x / gridscale) * gridscale, z = math.floor(pos_xz.z / gridscale) * gridscale}
local result = {grid_cell}
table.insert(result, {x = grid_cell.x + gridscale, z = grid_cell.y + gridscale})
table.insert(result, {x = grid_cell.x + gridscale, z = grid_cell.y})
table.insert(result, {x = grid_cell.x, z = grid_cell.y + gridscale})
table.insert(result, {x = grid_cell.x - gridscale, z = grid_cell.y - gridscale})
table.insert(result, {x = grid_cell.x - gridscale, z = grid_cell.y})
table.insert(result, {x = grid_cell.x, z = grid_cell.y - gridscale})
return result
end
@@ -0,0 +1,4 @@
mapgen_helper_record_time (Enable time-taken logging) bool false
mapgen_helper_persist_recorded_time (Persist time-taken logging between server sessions) bool false
mapgen_helper_record_time_resolution (Resolution of time-taken histogram) float 0.1
mapgen_helper_log_locations (Enable location logging) bool false
@@ -0,0 +1,75 @@
-- These functions are a minor modification of the voxel area iterator defined in the built in voxelarea.lua lua code.
-- As such, this file is separately licened under the LGPL as follows:
-- License of Minetest source code
-------------------------------
--Minetest
--Copyright (C) 2010-2018 celeron55, Perttu Ahola <celeron55@gmail.com>
--This program is free software; you can redistribute it and/or modify
--it under the terms of the GNU Lesser General Public License as published by
--the Free Software Foundation; either version 2.1 of the License, or
--(at your option) any later version.
--This program is distributed in the hope that it will be useful,
--but WITHOUT ANY WARRANTY; without even the implied warranty of
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
--GNU Lesser General Public License for more details.
--You should have received a copy of the GNU Lesser General Public License along
--with this program; if not, write to the Free Software Foundation, Inc.,
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
function VoxelArea:iter_xyz(minx, miny, minz, maxx, maxy, maxz)
local i = self:index(minx, miny, minz) - 1
local x = minx - 1 -- subtracting one because x gets incremented before it gets returned the first time.
local xrange = maxx - minx + 1
local nextaction = i + 1 + xrange
local y = 0
local yrange = maxy - miny + 1
local yreqstride = self.ystride - xrange
local z = 0
local zrange = maxz - minz + 1
local multistride = self.zstride - ((yrange - 1) * self.ystride + xrange)
return function()
-- continue i until it needs to jump
i = i + 1
x = x + 1
if i ~= nextaction then
return i, x, y+miny, z+minz
end
-- continue y until maxy is exceeded
y = y + 1
x = minx -- new line
if y ~= yrange then
-- set i to index(minx, miny + y, minz + z) - 1
i = i + yreqstride
nextaction = i + xrange
return i, x, y+miny, z+minz
end
-- continue z until maxz is exceeded
z = z + 1
if z == zrange then
-- cuboid finished, return nil
return
end
-- set i to index(minx, miny, minz + z) - 1
i = i + multistride
y = 0
nextaction = i + xrange
return i, x, y+miny, z+minz
end
end
function VoxelArea:iterp_xyz(minp, maxp)
return self:iter_xyz(minp.x, minp.y, minp.z, maxp.x, maxp.y, maxp.z)
end
@@ -0,0 +1,67 @@
-- similar to iter_xyz, but iterates first along the y axis. Useful in mapgens that want to detect a vertical transition (eg, finding ground level)
function VoxelArea:iter_yxz(minx, miny, minz, maxx, maxy, maxz)
local i = self:index(minx, miny, minz) - self.ystride
local x = minx
local y = miny - 1
local z = minz
return function()
y = y + 1
if y <= maxy then
i = i + self.ystride
return i, x, y, z
end
y = miny
x = x + 1
if x <= maxx then
i = self:index(x, y, z)
return i, x, y, z
end
x = minx
z = z + 1
if z <= maxz then
i = self:index(x, y, z)
return i, x, y, z
end
end
end
function VoxelArea:iterp_yxz(minp, maxp)
return self:iter_yxz(minp.x, minp.y, minp.z, maxp.x, maxp.y, maxp.z)
end
function VoxelArea:get_y(i)
return math.floor(((i - 1) % self.zstride) / self.ystride) + self.MinEdge.y
end
-- Used to make transform more efficient by skipping a table creation
function VoxelArea:position_xyz(vi)
local MinEdge = self.MinEdge
local Zstride = self.zstride
local Ystride = self.ystride
vi = vi - 1
local z = math.floor(vi / Zstride) + MinEdge.z
vi = vi % Zstride
local y = math.floor(vi / Ystride) + MinEdge.y
vi = vi % Ystride
local x = vi + MinEdge.x
return x, y, z
end
-- Takes another voxelarea and an index in it and transforms it into an index into its own
-- voxelarea, or nil if it's not in the voxelarea. This is useful when you've got, for example,
-- a mapgen's voxelmanipulator and a perlin noise array covering the map block but not the entire
-- emerged volume.
function VoxelArea:transform(area, vi)
local x,y,z = area:position_xyz(vi)
if self:contains(x,y,z) then
return self:index(x,y,z)
end
return nil
end