Add more code documentation

This commit is contained in:
Wuzzy 2022-04-20 21:36:41 +02:00
parent eb2db02321
commit 9b0f04df18

154
init.lua
View File

@ -1,6 +1,10 @@
local S = minetest.get_translator("perlin_explorer")
local F = minetest.formspec_escape
-----------------------------
-- Variable initialization --
-----------------------------
local mod_storage = minetest.get_mod_storage()
-- If true, the Perlin test nodes will support color
@ -98,6 +102,8 @@ local formspec_states = {}
-- * "user": Profiles created by user. Deletable
local np_profiles = {}
-- The default noiseparams are used as the initial noiseparams
-- or as fallback when stuff fails.
local default_noiseparams = {
offset = 0.0,
scale = 1.0,
@ -223,9 +229,10 @@ current_options.autogen = false
-- Index: Hash of node position, value: true if loaded
local loaded_areas = {}
------------
----------------------
-- Helper functions --
----------------------
-- Helper functions
-- Reduce the pos coordinates down to the closest numbers divisible by sidelen
local sidelen_pos = function(pos, sidelen)
local newpos = {x=pos.x, y=pos.y, z=pos.z}
@ -263,6 +270,8 @@ local unique_formspec_spaces = function(player_name, formspec)
return formspec .. filler
end
-- Takes the 3 noise flags default/eased/absvalue (either true or false)
-- and converts them to a string
local build_flags_string = function(defaults, eased, absvalue)
local flagst = {}
if defaults then
@ -281,6 +290,12 @@ local build_flags_string = function(defaults, eased, absvalue)
local flags = table.concat(flagst, ",")
return flags
end
-- Takes a flags string (in the noiseparams format)
-- and returns a table of the form {
-- defaults = true/false
-- eased = true/false
-- absvalue = true/false
-- }.
local parse_flags_string = function(flags)
local ftable = string.split(flags, ",")
local defaults, eased, absvalue = false, false, false
@ -379,7 +394,14 @@ minetest.register_on_mods_loaded(function()
end
end)
-- Test nodes to generate a map based on a perlin noise
-----------
-- Nodes --
-----------
-- Add a bunch of nodes to generate a map based on a perlin noise.
-- Used for visualization.
-- Each nodes comes in a "high" and "low" variant.
-- high/low means it represents high/low noise values.
local paramtype2, palette
if COLORIZE_NODES then
paramtype2 = "color"
@ -392,9 +414,13 @@ else
palette_low = "perlin_explorer_node_palette_low.png"
end
-- Solid nodes: Visible, walkable, opaque
minetest.register_node("perlin_explorer:node", {
description = S("Solid Perlin Test Node (High Value)"),
paramtype = "light",
-- Intentionally does not cast shadow so that
-- cave structures are always fullbright when
-- the sun shines.
sunlight_propagates = true,
paramtype2 = paramtype2,
tiles = {"perlin_explorer_node.png"},
@ -415,6 +441,8 @@ minetest.register_node("perlin_explorer:node_low", {
drop = "perlin_explorer:node_low",
})
-- Grid nodes: See-through, walkable. Looks like glass.
-- Useful to see "inside" of 3D blobs.
minetest.register_node("perlin_explorer:grid", {
description = S("Grid Perlin Test Node (High Value)"),
paramtype = "light",
@ -441,6 +469,9 @@ minetest.register_node("perlin_explorer:grid_low", {
drop = "perlin_explorer:grid_low",
})
-- Minibox nodes: See-through, non-walkable, climbable.
-- Looks like dot clouds in large numbers.
-- Useful to see and move "inside" of 3D blobs.
minetest.register_node("perlin_explorer:mini", {
description = S("Minibox Perlin Test Node (High Value)"),
paramtype = "light",
@ -478,6 +509,14 @@ minetest.register_node("perlin_explorer:mini_low", {
drop = "perlin_explorer:mini_low",
})
-- Helper function for the getter tool. Gets a noise value at pos
-- and print is in user's chat.
-- * pos: Position to get
-- * user: Player object
-- * precision: Coordinate precision of output (for minetest.pos_to_string)
-- * ptype: One of ...
-- "node": Get value at node position
-- "player": Get value at player position
local print_value = function(pos, user, precision, ptype)
local val
local getpos = sidelen_pos(pos, current_options.sidelen)
@ -503,7 +542,7 @@ local print_value = function(pos, user, precision, ptype)
minetest.chat_send_player(user:get_player_name(), msg)
end
-- Get Perlin value of player pos
-- Get Perlin value of player pos (on_use callback)
local use_getter = function(itemstack, user, pointed_thing)
if not user then
return
@ -528,7 +567,7 @@ local use_getter = function(itemstack, user, pointed_thing)
end
end
-- Get Perlin value of pointed node
-- Get Perlin value of pointed node (on_place callback)
local place_getter = function(itemstack, user, pointed_thing)
if not user then
return
@ -551,6 +590,7 @@ local place_getter = function(itemstack, user, pointed_thing)
end
end
-- Gets perlin noise value
minetest.register_tool("perlin_explorer:getter", {
description = S("Perlin Value Getter"),
_tt_help = S("Place: Display Perlin noise value of the pointed node position").."\n"..
@ -562,13 +602,35 @@ minetest.register_tool("perlin_explorer:getter", {
on_place = place_getter,
})
-- Calculate the Perlin nois valued and generate nodes.
-- * pos: Bottom front left position of where Perlin noise begins
-- * noise: Perlin noise object to use
-- * noiseparams: noise parameters table
-- * options: see at create_perlin function
-- * stats_mode: if true, will only calculate values for statistics but not place any nodes
local update_map = function(pos, noise, noiseparams, options, stats_mode)
--[[ Calculate the Perlin noise value and optionally generate nodes.
* pos: Bottom front left position of where Perlin noise begins
* noise: Perlin noise object to use
* noiseparams: noise parameters table
* options: see at create_perlin function
* stats_mode: if true, will only calculate values for statistics but not place any nodes
Returns: a stats table of the form
{
min, -- minimum calculated noise value
max, -- maximum calculated noise value
avg, -- average calculated noise value
value_count, -- number of values that were calculated
histogram, -- histogram data for the stats screen. A list
-- of "buckets", starting with the lower bounds:
{
[1] = <number of values in 1st bucket>,
[2] = <number of values in 2nd bucket>,
-- ... etc. ...
[HISTOGRAM_BUCKETS] = <number of values in last bucket>,
}
histogram_points, -- List of cutoff points for each of the
-- data buckets
start_pos, -- lower corner of the area that the stats were calculated for
end_pos, -- upper corner of the area that the stats were calculated for
}
Returns nil on error.
]]
local calculate_noise = function(pos, noise, noiseparams, options, stats_mode)
-- Init
local stats
if not noise then
return
@ -586,6 +648,7 @@ local update_map = function(pos, noise, noiseparams, options, stats_mode)
local y_max = endpos.y - startpos.y
if options.dimensions == 2 then
-- We don't need 3rd axis in 2D
y_max = 0
startpos.y = pos.y
endpos.y = pos.y
@ -595,6 +658,7 @@ local update_map = function(pos, noise, noiseparams, options, stats_mode)
local vmanip, emin, emax, vdata, vdata2, varea
local content_test_node, content_test_node_low, node_needs_color
if not stats_mode then
-- Only needed when we want to place nodes
vmanip = VoxelManip(startpos, endpos)
emin, emax = vmanip:get_emerged_area()
vdata = vmanip:get_data()
@ -606,6 +670,7 @@ local update_map = function(pos, noise, noiseparams, options, stats_mode)
node_needs_color = nodetypes[options.nodetype][4]
end
-- Init stats
stats = {}
stats.avg = 0
local sum_of_values = 0
@ -614,6 +679,8 @@ local update_map = function(pos, noise, noiseparams, options, stats_mode)
stats.histogram = {}
local min_possible, max_possible = analyze_noiseparams(noiseparams)
local cutoff_points = {}
-- Calculate the cutoff points for the histogram so we know in which data bucket
-- to put each value into.
for d=1,HISTOGRAM_BUCKETS do
cutoff_points[d] = min_possible + ((max_possible-min_possible) / HISTOGRAM_BUCKETS) * d
stats.histogram[d] = 0
@ -622,6 +689,7 @@ local update_map = function(pos, noise, noiseparams, options, stats_mode)
local perlin_map
if not stats_mode then
-- Initialize Perlin map
-- The noise values will come from this (unless in Stats Mode)
local perlin_map_object = PerlinNoiseMap(noiseparams, size_v)
if options.dimensions == 2 then
perlin_map = perlin_map_object:get_2d_map({x=startpos.x, y=startpos.z})
@ -640,12 +708,16 @@ local update_map = function(pos, noise, noiseparams, options, stats_mode)
z_max = endpos.z - startpos.z
end
-- Main loop (time-critical!)
for x=0, x_max do
for y=0, y_max do
for z=0, z_max do
-- Note: This loop has been optimized for speed, so the code
-- might not look pretty.
-- Be careful of the performance implications when touching this
-- loop
-- Get Perlin value at current pos
-- Note: This section has been optimized. Dont introduce stuff like
-- vectors.new() here.
local abspos
if not stats_mode then
abspos = {
@ -668,6 +740,7 @@ local update_map = function(pos, noise, noiseparams, options, stats_mode)
z = abspos_get.z - startpos.z + 1,
}
-- Finally get the noise value
local perlin_value
if options.dimensions == 2 then
if stats_mode or (indexpos.x < 1 or indexpos.z < 1) then
@ -717,9 +790,11 @@ local update_map = function(pos, noise, noiseparams, options, stats_mode)
break
end
end
-- More stats
sum_of_values = sum_of_values + perlin_value
stats.value_count = stats.value_count + 1
-- This section will set the node
if not stats_mode then
-- Calculate color (param2) for node
local zeropoint = options.mid_color
@ -745,7 +820,7 @@ local update_map = function(pos, noise, noiseparams, options, stats_mode)
return
end
-- Set node and param2
-- Set node and param2 in vmanip
if perlin_value >= zeropoint then
if options.buildmode == BUILDMODE_ALL or options.buildmode == BUILDMODE_HIGH_ONLY or options.buildmode == BUILDMODE_AUTO then
vdata[index] = content_test_node
@ -767,13 +842,14 @@ local update_map = function(pos, noise, noiseparams, options, stats_mode)
end
end
end
-- Final stats
stats.avg = sum_of_values / stats.value_count
stats.histogram_points = cutoff_points
stats.start_pos = vector.new(startpos.x, startpos.y, startpos.z)
stats.end_pos = vector.add(stats.start_pos, vector.new(options.size, options.size, options.size))
if not stats_mode then
-- Set vmanip, return stats
-- Write all the changes to map
vmanip:set_data(vdata)
vmanip:set_param2_data(vdata2)
vmanip:write_to_map()
@ -783,11 +859,10 @@ local update_map = function(pos, noise, noiseparams, options, stats_mode)
local timediff = time2 - time1
minetest.log("verbose", "[perlin_explorer] Noisechunk calculated/generated in "..timediff.." µs")
return stats
end
-- Creates and demonstrates a Perlin noise.
-- Initiates Perlin noise calculation and optionally node generation, too.
-- * pos: Where the Perlin noise starts
-- * noise: Perlin noise object
-- * noiseparams: noise parameters table
@ -804,7 +879,7 @@ local create_perlin = function(pos, noise, noiseparams, options, stats_mode)
local cpos = table.copy(pos)
local mpos = vector.round(cpos)
local stats = update_map(mpos, noise, noiseparams, local_options, stats_mode)
local stats = calculate_noise(mpos, noise, noiseparams, local_options, stats_mode)
if mapgen_star and not stats_mode then
-- Show a particle in the center of the newly generated area
@ -834,6 +909,9 @@ local create_perlin = function(pos, noise, noiseparams, options, stats_mode)
end
end
-- Seeder tool helper function.
-- * player: Player object
-- * regen: If true, force map section to regenerate (if autogen is off)
local seeder_reseed = function(player, regen)
local msg
if regen and (not current_options.autogen and current_options.pos) then
@ -849,6 +927,9 @@ local seeder_reseed = function(player, regen)
end
end
-- Generates a function for the seeder tool callbacks, for the
-- on_use, on_secondary_use, on_place callbacks of that tool.
-- * reseed : If true, force map section to regenerate (if autogen is off)
local function seeder_use(reseed)
return function(itemstack, user, pointed_thing)
if not user then
@ -887,9 +968,10 @@ local fix_noiseparams = function(noiseparams)
return noiseparams
end
-- Tool to set new random seed of the noiseparams
minetest.register_tool("perlin_explorer:seeder", {
description = S("Random Perlin seed setter"),
_tt_help = S("Punch: Set a random seed for the current Perlin noise params").."\n"..
_tt_help = S("Punch: Set a random seed for the active Perlin noise parameters").."\n"..
S("Place: Set a random seed and regenerate nodes (if applicable)"),
inventory_image = "perlin_explorer_seeder.png",
wield_image = "perlin_explorer_seeder.png",
@ -899,6 +981,9 @@ minetest.register_tool("perlin_explorer:seeder", {
on_place = seeder_use(true),
})
-------------------
-- Chat commands --
-------------------
minetest.register_chatcommand("perlin_set_options", {
privs = { server = true },
description = S("Set Perlin map generation options"),
@ -1032,6 +1117,9 @@ minetest.register_chatcommand("perlin_generate", {
end,
})
-- Show an error window to player with the given message.
-- Set analyze_button to true to add a button "Analyze now"
-- to open the analyze window.
local show_error_formspec = function(player, message, analyze_button)
local buttons
if analyze_button then
@ -1055,6 +1143,7 @@ local show_error_formspec = function(player, message, analyze_button)
minetest.show_formspec(player:get_player_name(), "perlin_explorer:error", form)
end
-- Stats loading screen
local show_histogram_loading_formspec = function(player)
local form = [[
formspec_version[4]size[11,2]
@ -1070,6 +1159,7 @@ local show_histogram_loading_formspec = function(player)
minetest.show_formspec(player:get_player_name(), "perlin_explorer:histogram_loading", form)
end
-- Stats screen
local show_histogram_formspec = function(player, options, stats)
local txt = ""
local maxh = 6.0
@ -1215,6 +1305,11 @@ local analyze_noiseparams_and_show_formspec = function(player, noiseparams)
minetest.show_formspec(player:get_player_name(), "perlin_explorer:analyze", form)
end
-- Show the formspec of the Perlin Noise Creator tool to player.
-- The main formspec of this mod!
-- * player: Player object
-- * noiseparams: Initialize formspec with these noiseparams (optional)
-- * profile_id: Preselect this profile (by table index of np_profiles)
local show_noise_formspec = function(player, noiseparams, profile_id)
local player_name = player:get_player_name()
local np
@ -1394,6 +1489,7 @@ local show_noise_formspec = function(player, noiseparams, profile_id)
minetest.show_formspec(player_name, "perlin_explorer:creator", form)
end
-- Formspec handling
minetest.register_on_player_receive_fields(function(player, formname, fields)
-- Require 'server' priv
local privs = minetest.get_player_privs(player:get_player_name())
@ -1660,6 +1756,7 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
end
end)
-- The main tool of this mod. Opens the Perlin noise creation window.
minetest.register_tool("perlin_explorer:creator", {
description = S("Perlin Noise Creator"),
_tt_help = S("Punch to open the Perlin noise creation menu"),
@ -1679,6 +1776,13 @@ minetest.register_tool("perlin_explorer:creator", {
end,
})
-- Automatic map generation (autogen).
-- The autogen is kind of a mapgen, but it doesn't use Minetest's
-- mapgen, instead it writes directly to map. The benefit is
-- that this will instantly update when the active noiseparams
-- are changed.
-- This will generate so-called noisechunks, which are like
-- Minetest mapchunks, except with respect to this mod only.
local timer = 0
minetest.register_globalstep(function(dtime)
timer = timer + dtime
@ -1688,9 +1792,11 @@ minetest.register_globalstep(function(dtime)
timer = 0
if current_perlin.noise and current_options.autogen then
local players = minetest.get_connected_players()
-- Generate close to all connected players
for p=1, #players do
local player = players[p]
local player_name = player:get_player_name()
-- Helper function for building
local build = function(pos, pos_hash, player_name)
if not pos or not pos.x or not pos.y or not pos.z then
minetest.log("error", "[perlin_explorer] build(): Invalid pos!")
@ -1703,11 +1809,15 @@ minetest.register_globalstep(function(dtime)
if not loaded_areas[pos_hash] then
local opts = table.copy(current_options)
opts.size = AUTOBUILD_SIZE
-- This actually builds the nodes
create_perlin(pos, current_perlin.noise, current_perlin.noiseparams, opts, false)
-- Built chunks are marked so they don't get generated over and over
-- again
loaded_areas[pos_hash] = true
end
end
-- Build list of coordinates to generate nodes in
local pos = vector.round(player:get_pos())
pos = sidelen_pos(pos, AUTOBUILD_SIZE)
local neighbors = { vector.new(0, 0, 0) }
@ -1739,6 +1849,7 @@ minetest.register_globalstep(function(dtime)
if #noisechunks > 0 then
minetest.log("verbose", "[perlin_explorer] Started building "..#noisechunks.." noisechunk(s)")
end
-- Build the nodes!
for c=1, #noisechunks do
local npos = noisechunks[c][1]
local nhash = noisechunks[c][2]
@ -1751,6 +1862,7 @@ minetest.register_globalstep(function(dtime)
end
end)
-- Player variable init and cleanup
minetest.register_on_joinplayer(function(player)
local name = player:get_player_name()
local flags = default_noiseparams.flags
@ -1761,6 +1873,8 @@ minetest.register_on_joinplayer(function(player)
absvalue = flags_table.absvalue,
sidelen = 1,
noiseparams = table.copy(default_noiseparams),
-- This is used by the `unique_formspec_spaces` hack
sequence_number = 0,
}
end)