Refactor current_perlin, fix stats button

This commit is contained in:
Wuzzy 2022-04-20 05:39:28 +02:00
parent 2dabd71d42
commit 7743fdeced

259
init.lua
View File

@ -91,25 +91,26 @@ end
table.insert(np_profiles, {noiseparams=current_perlin.noiseparams})
local current_options = {}
-- Side length of calculated perlin area
current_perlin.size = 64
current_options.size = 64
-- Theoretical min and max values for Perlin noise (for colorization)
current_perlin.min = -1
current_perlin.max = 1
current_perlin.nodetype = 1
current_options.min = -1
current_options.max = 1
current_options.nodetype = 1
-- dimensions of current Perlin noise (2 or 3)
current_perlin.dimensions = 2
current_options.dimensions = 2
-- If greater than 1, the Perlin noise values are "pixelized". Noise values at
-- coordinates not divisible by sidelen will be set equal to the noise value
-- of the nearest number (counting downwards) that is divisible by sidelen.
-- This is (kind of) analogous to the "sidelen" parameter of mapgen decorations.
current_perlin.sidelen = 1
current_options.sidelen = 1
-- Place position of current perlin (relevant for single placing)
current_perlin.pos = nil
current_options.pos = nil
-- If enabled, automatically generate nodes around player
current_perlin.autogen = false
current_options.autogen = false
-- Remember which areas have been loaded by the autogen so far
-- Index: Hash of node position, value: true if loaded
@ -290,6 +291,7 @@ minetest.register_node("perlin_explorer:grid", {
minetest.register_node("perlin_explorer:grid_negative", {
description = S("Grid Negative Perlin Test Node"),
paramtype = "light",
drawtype = "allfaces",
sunlight_propagates = true,
paramtype2 = paramtype2,
tiles = {"perlin_explorer_grid_neg.png"},
@ -338,10 +340,10 @@ minetest.register_node("perlin_explorer:mini_negative", {
local print_value = function(pos, user, precision, ptype)
local val
local getpos = sidelen_pos(pos, current_perlin.sidelen)
if current_perlin.dimensions == 2 then
local getpos = sidelen_pos(pos, current_options.sidelen)
if current_options.dimensions == 2 then
val = current_perlin.noise:get_2d({x=getpos.x, y=getpos.z})
elseif current_perlin.dimensions == 3 then
elseif current_options.dimensions == 3 then
val = current_perlin.noise:get_3d(getpos)
else
error("[perlin_explorer] Unknown/invalid number of Perlin noise dimensions!")
@ -420,24 +422,30 @@ minetest.register_tool("perlin_explorer:getter", {
on_place = place_getter,
})
local update_map = function(pos, stats_mode)
-- 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)
local stats
if not current_perlin.noise then
if not noise then
return
end
local time1 = minetest.get_us_time()
local size_v = {
x = current_perlin.size,
y = current_perlin.size,
z = current_perlin.size,
x = options.size,
y = options.size,
z = options.size,
}
local startpos = pos
local endpos = vector.add(startpos, current_perlin.size-1)
local endpos = vector.add(startpos, options.size-1)
local y_max = endpos.y - startpos.y
if current_perlin.dimensions == 2 then
if options.dimensions == 2 then
y_max = 0
startpos.y = pos.y
endpos.y = pos.y
@ -445,17 +453,18 @@ local update_map = function(pos, stats_mode)
end
local vmanip, emin, emax, vdata, vdata2, varea
local content_test_node, content_test_node_negative, node_needs_color
if not stats_mode then
vmanip = VoxelManip(startpos, endpos)
emin, emax = vmanip:get_emerged_area()
vdata = vmanip:get_data()
vdata2 = vmanip:get_param2_data()
varea = VoxelArea:new({MinEdge = emin, MaxEdge = emax})
end
local content_test_node = minetest.get_content_id(nodetypes[current_perlin.nodetype][2])
local content_test_node_negative = minetest.get_content_id(nodetypes[current_perlin.nodetype][3])
local needs_color = nodetypes[current_perlin.nodetype][4]
content_test_node = minetest.get_content_id(nodetypes[options.nodetype][2])
content_test_node_negative = minetest.get_content_id(nodetypes[options.nodetype][3])
node_needs_color = nodetypes[options.nodetype][4]
end
stats = {}
stats.avg = 0
@ -463,7 +472,7 @@ local update_map = function(pos, stats_mode)
stats.value_count = 0
stats.histogram = {}
local min_possible, max_possible = analyze_noiseparams(current_perlin.noiseparams)
local min_possible, max_possible = analyze_noiseparams(noiseparams)
local cutoff_points = {}
for d=1,HISTOGRAM_BUCKETS do
cutoff_points[d] = min_possible + ((max_possible-min_possible) / HISTOGRAM_BUCKETS) * d
@ -473,8 +482,8 @@ local update_map = function(pos, stats_mode)
local perlin_map
if not stats_mode then
-- Initialize Perlin map
local perlin_map_object = PerlinNoiseMap(current_perlin.noiseparams, size_v)
if current_perlin.dimensions == 2 then
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})
else
perlin_map = perlin_map_object:get_3d_map(startpos)
@ -506,13 +515,13 @@ local update_map = function(pos, stats_mode)
}
elseif stats_mode then
abspos = {
x = math.random(startpos.x, startpos.x+current_perlin.size),
y = math.random(startpos.y, startpos.y+current_perlin.size),
z = math.random(startpos.z, startpos.z+current_perlin.size),
x = math.random(startpos.x, startpos.x+options.size),
y = math.random(startpos.y, startpos.y+options.size),
z = math.random(startpos.z, startpos.z+options.size),
}
end
-- Apply sidelen transformation (pixelize)
local abspos_get = sidelen_pos(abspos, current_perlin.sidelen)
local abspos_get = sidelen_pos(abspos, options.sidelen)
local indexpos = {
x = abspos_get.x - startpos.x + 1,
y = abspos_get.y - startpos.y + 1,
@ -520,7 +529,7 @@ local update_map = function(pos, stats_mode)
}
local perlin_value
if current_perlin.dimensions == 2 then
if options.dimensions == 2 then
if stats_mode or (indexpos.x < 1 or indexpos.z < 1) then
-- The pixelization can move indexpos below 1, in this case
-- we get the perlin value directly because it is outside
@ -532,15 +541,15 @@ local update_map = function(pos, stats_mode)
-- been done yet.
-- In stats mode, we always calculate the noise value directy
-- because the values are spread out.
perlin_value = current_perlin.noise:get_2d({x=abspos_get.x, y=abspos_get.z})
perlin_value = noise:get_2d({x=abspos_get.x, y=abspos_get.z})
else
-- Normal case: Get value from perlin map
perlin_value = perlin_map[indexpos.z][indexpos.x]
end
elseif current_perlin.dimensions == 3 then
elseif options.dimensions == 3 then
if stats_mode or (indexpos.x < 1 or indexpos.y < 1 or indexpos.z < 1) then
-- See above
perlin_value = current_perlin.noise:get_3d(abspos_get)
perlin_value = noise:get_3d(abspos_get)
else
-- See above
perlin_value = perlin_map[indexpos.z][indexpos.y][indexpos.x]
@ -574,10 +583,10 @@ local update_map = function(pos, stats_mode)
if not stats_mode then
-- Calculate color (param2) for node
local zeropoint = 0
local min_size = zeropoint - current_perlin.min
local max_size = current_perlin.max - zeropoint
local min_size = zeropoint - options.min
local max_size = options.max - zeropoint
local node_param2 = 0
if needs_color then
if node_needs_color then
if perlin_value >= zeropoint then
node_param2 = (math.abs(perlin_value) / max_size) * 255
else
@ -601,7 +610,7 @@ local update_map = function(pos, stats_mode)
vdata[index] = content_test_node
vdata2[index] = node_param2
else
if current_perlin.show_negative == true then
if options.show_negative == true then
vdata[index] = content_test_node_negative
vdata2[index] = node_param2
else
@ -616,7 +625,7 @@ local update_map = function(pos, stats_mode)
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(current_perlin.size, current_perlin.size, current_perlin.size))
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
@ -635,36 +644,35 @@ end
-- Creates and demonstrates a Perlin noise.
-- * pos: Where the Perlin noise starts
-- * noise: Perlin noise object
-- * noiseparams: noise parameters table
-- * options: table with:
-- * dimensions: number of Perlin noise dimensions (2 or 3)
-- * size: side length of area/volume to calculate)
-- * sidelen: pixelization
-- * show_negative: if true, places nodes for negative Perlin values (default: true for 2 dimensions and false for 3 dimensions)
-- * stats_mode: if true, will only calculate values for statistics but not place any nodes
local create_perlin = function(pos, options)
if not current_perlin.noise then
-- * stats_mode: if true, will only calculate values for statistics but not place any nodes
local create_perlin = function(pos, noise, noiseparams, options, stats_mode)
if not noise then
return false
end
current_perlin.dimensions = options.dimensions
current_perlin.size = options.size
current_perlin.show_negative = options.show_negative
if current_perlin.show_negative == nil then
if current_perlin.dimensions == 2 then
current_perlin.show_negative = true
elseif current_perlin.dimensions == 3 then
current_perlin.show_negative = false
if options.show_negative == nil then
if options.dimensions == 2 then
options.show_negative = true
elseif options.dimensions == 3 then
options.show_negative = false
end
end
local cpos = table.copy(pos)
local mpos = vector.round(cpos)
current_perlin.pos = mpos
local stats_mode = options.stats_mode == true
local stats = update_map(mpos, stats_mode)
local stats = update_map(mpos, noise, noiseparams, options, stats_mode)
if not stats_mode then
-- Show a particle in the center of the newly generated area
local center = vector.new()
center.x = mpos.x + options.size/2
if current_perlin.dimensions == 2 then
if options.dimensions == 2 then
center.y = mpos.y + 3
else
center.y = mpos.y + options.size/2
@ -690,10 +698,10 @@ end
local seeder_reseed = function(player, regen)
local msg
if regen and (not current_perlin.autogen and current_perlin.pos) then
if regen and (not current_options.autogen and current_options.pos) then
msg = S("New random seed set, starting to regenerate nodes ...")
minetest.chat_send_player(player:get_player_name(), msg)
msg = create_perlin(current_perlin.pos, {dimensions = current_perlin.dimensions, size = current_perlin.size})
msg = create_perlin(current_options.pos, current_perlin.noise, current_perlin.noiseparams, current_options, false)
if msg ~= false then
minetest.chat_send_player(player:get_player_name(), msg)
end
@ -754,10 +762,10 @@ minetest.register_chatcommand("perlin_set_options", {
if not dimensions or not sidelen or not min or not max then
return false, S("Invalid parameter type.")
end
current_perlin.dimensions = dimensions
current_perlin.sidelen = fix_sidelen(sidelen)
current_perlin.min = min
current_perlin.max = max
current_options.dimensions = dimensions
current_options.sidelen = fix_sidelen(sidelen)
current_options.min = min
current_options.max = max
loaded_areas = {}
return true, S("Perlin map generation options set!")
end,
@ -828,7 +836,9 @@ minetest.register_chatcommand("perlin_generate", {
local pos = vector.new(x, y, z)
minetest.chat_send_player(name, S("Creating Perlin noise, please wait …"))
local msg = create_perlin(pos, {dimensions=current_perlin.dimensions, size=size})
local opts = table.copy(current_options)
opts.size = size
local msg = create_perlin(pos, current_perlin.noise, current_perlin.noiseparams, opts, false)
if msg == false then
return false, S("No Perlin noise set. Set one first!")
end
@ -874,7 +884,7 @@ local show_histogram_loading_formspec = function(player)
minetest.show_formspec(player:get_player_name(), "perlin_explorer:histogram_loading", form)
end
local show_histogram_formspec = function(player, stats)
local show_histogram_formspec = function(player, options, stats)
local txt = ""
local maxh = 6.0
local histogram
@ -943,7 +953,7 @@ local show_histogram_formspec = function(player, stats)
histogram = histogram .. box
end
local vmin, vmax
if current_perlin.dimensions == 2 then
if options.dimensions == 2 then
vmin = S("(@1,@2)", stats.start_pos.x, stats.start_pos.z)
vmax = S("(@1,@2)", stats.end_pos.x, stats.end_pos.z)
else
@ -1043,16 +1053,16 @@ local show_noise_formspec = function(player, noiseparams, profile_id)
local persistence = tostring(np.persistence or "")
local lacunarity = tostring(np.lacunarity or "")
local size = tostring(current_perlin.size or "")
local sidelen = tostring(current_perlin.sidelen or "")
local size = tostring(current_options.size or "")
local sidelen = tostring(current_options.sidelen or "")
local pos_x, pos_y, pos_z = "", "", ""
if current_perlin.pos then
pos_x = tostring(current_perlin.pos.x or "")
pos_y = tostring(current_perlin.pos.y or "")
pos_z = tostring(current_perlin.pos.z or "")
if current_options.pos then
pos_x = tostring(current_options.pos.x or "")
pos_y = tostring(current_options.pos.y or "")
pos_z = tostring(current_options.pos.z or "")
end
local value_min = tostring(current_perlin.min or "")
local value_max = tostring(current_perlin.max or "")
local value_min = tostring(current_options.min or "")
local value_max = tostring(current_options.max or "")
local flags = np.flags
local flags_table = parse_flags_string(flags)
@ -1076,8 +1086,8 @@ local show_noise_formspec = function(player, noiseparams, profile_id)
end
local noiseparams_list_str = table.concat(noiseparams_list, ",")
local dimensions_index = (current_perlin.dimensions or 2) - 1
local nodetype_index = (current_perlin.nodetype)
local dimensions_index = (current_options.dimensions or 2) - 1
local nodetype_index = (current_options.nodetype)
local nodetypes_list = {}
for i=1, #nodetypes do
@ -1092,7 +1102,7 @@ local show_noise_formspec = function(player, noiseparams, profile_id)
local autogen_label
local create_btn = ""
local xyzsize = ""
if current_perlin.autogen then
if current_options.autogen then
autogen_label = S("Disable mapgen")
else
autogen_label = S("Enable mapgen")
@ -1223,34 +1233,6 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
return
end
-- Start statistics calculation
if fields.statistics then
local max_spread = 0
local noiseparams = formspec_states[player:get_player_name()].noiseparams
max_spread = math.max(max_spread, noiseparams.spread.x)
max_spread = math.max(max_spread, noiseparams.spread.y)
max_spread = math.max(max_spread, noiseparams.spread.z)
local size = max_spread * STATISTICS_SPREAD_FACTOR
-- A very large size is not allowed because the Minetest functions start to fail and would start to distort the statistics.
if size > STATISTICS_MAX_SIZE or max_spread > STATISTICS_MAX_SPREAD then
show_error_formspec(player, S("Sorry, but Perlin Explorer doesnt doesnt support calculating statistics if the spread of any axis is larger than @1.", STATISTICS_MAX_SPREAD), false)
return
end
local start = -math.floor(size/2)
local pos = vector.new(start, start, start)
-- Show a loading formspec
show_histogram_loading_formspec(player)
-- This takes long
local _, stats = create_perlin(pos, {dimensions=current_perlin.dimensions, size=size, stats_mode=true})
if stats then
-- Update the formspec to show the result
show_histogram_formspec(player, stats)
else
minetest.log("error", "[perlin_explorer] Error while creating stats from Perlin noise!")
end
return
end
-- Handle checkboxes
local name = player:get_player_name()
local flags_touched = false
@ -1294,7 +1276,7 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
local enter_pressed = fields.key_enter_field ~= nil
local do_apply = fields.apply ~= nil or fields.toggle_autogen ~= nil
local do_create = fields.create ~= nil
if current_perlin.autogen then
if current_options.autogen then
do_apply = do_apply or enter_pressed
else
do_create = do_create or enter_pressed
@ -1383,39 +1365,75 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
show_error_formspec(player, S("Bad noise parameters given. At least one of the resulting wavelengths got below 1, which is not allowed. Decrease the octaves or lacunarity to reach valid wavelengths. Analyze the noise parameters to see the current wavelengths."), true)
return
end
-- Start statistics calculation
if fields.statistics then
local max_spread = 0
max_spread = math.max(max_spread, noiseparams.spread.x)
max_spread = math.max(max_spread, noiseparams.spread.y)
max_spread = math.max(max_spread, noiseparams.spread.z)
local ssize = max_spread * STATISTICS_SPREAD_FACTOR
-- A very large size is not allowed because the Minetest functions start to fail and would start to distort the statistics.
if ssize > STATISTICS_MAX_SIZE or max_spread > STATISTICS_MAX_SPREAD then
show_error_formspec(player, S("Sorry, but Perlin Explorer doesnt doesnt support calculating statistics if the spread of any axis is larger than @1.", STATISTICS_MAX_SPREAD), false)
return
end
local start = -math.floor(ssize/2)
local pos = vector.new(start, start, start)
local noise = PerlinNoise(noiseparams)
local options = {
dimensions = dimensions,
size = ssize,
sidelen = sidelen,
}
-- Show a loading formspec
show_histogram_loading_formspec(player)
-- This takes long
local _, stats = create_perlin(pos, noise, noiseparams, options, true)
if stats then
-- Update the formspec to show the result
show_histogram_formspec(player, options, stats)
else
minetest.log("error", "[perlin_explorer] Error while creating stats from Perlin noise!")
end
return
end
set_perlin_noise(noiseparams)
minetest.log("action", "[perlin_explorer] Perlin noise set!")
current_perlin.dimensions = dimensions
current_perlin.sidelen = fix_sidelen(sidelen)
current_perlin.min = value_min
current_perlin.max = value_max
current_perlin.nodetype = nodetype
current_options.dimensions = dimensions
current_options.sidelen = fix_sidelen(sidelen)
current_options.min = value_min
current_options.max = value_max
current_options.nodetype = nodetype
if fields.toggle_autogen then
current_perlin.autogen = not current_perlin.autogen
if current_perlin.autogen then
current_options.autogen = not current_options.autogen
if current_options.autogen then
loaded_areas = {}
end
local profile = tonumber(fields.np_profiles)
show_noise_formspec(player, noiseparams, profile)
minetest.log("action", "[perlin_explorer] Autogen state is now: "..tostring(current_perlin.autogen))
minetest.log("action", "[perlin_explorer] Autogen state is now: "..tostring(current_options.autogen))
elseif do_create then
if not px or not py or not pz or not size then
return
end
if current_perlin.autogen then
if current_options.autogen then
loaded_areas = {}
end
current_perlin.size = size
current_options.size = size
local place_pos = vector.new(px, py, pz)
local msg = S("Creating Perlin noise, please wait …")
minetest.chat_send_player(name, msg)
msg = create_perlin(place_pos, {dimensions=dimensions, size=size})
msg = create_perlin(place_pos, current_perlin.noise, current_perlin.noiseparams, current_options, false)
if msg then
minetest.chat_send_player(name, msg)
elseif msg == false then
minetest.log("error", "[perlin_explorer] Error generating Perlin noise nodes!")
end
elseif do_apply and current_perlin.autogen then
elseif do_apply and current_options.autogen then
loaded_areas = {}
end
end
@ -1449,7 +1467,7 @@ minetest.register_globalstep(function(dtime)
return
end
timer = 0
if current_perlin.noise and current_perlin.autogen then
if current_perlin.noise and current_options.autogen then
local player = minetest.get_player_by_name("singleplayer")
if not player then
return
@ -1464,10 +1482,9 @@ minetest.register_globalstep(function(dtime)
return
end
if not loaded_areas[pos_hash] then
create_perlin(pos, {
dimensions = current_perlin.dimensions,
size = AUTOBUILD_SIZE,
})
local opts = table.copy(current_options)
opts.size = AUTOBUILD_SIZE
create_perlin(pos, current_perlin.noise, current_perlin.noiseparams, opts, false)
loaded_areas[pos_hash] = true
end
end
@ -1477,7 +1494,7 @@ minetest.register_globalstep(function(dtime)
local neighbors = { vector.new(0, 0, 0) }
local c = AUTOBUILD_CHUNKDIST
local cc = c
if current_perlin.dimensions == 2 then
if current_options.dimensions == 2 then
cc = 0
end
for cx=-c, c do
@ -1520,6 +1537,8 @@ minetest.register_on_joinplayer(function(player)
defaults = true,
eased = false,
absvalue = false,
sidelen = 1,
noiseparams = table.copy(default_noiseparams),
}
end)
minetest.register_on_leaveplayer(function(player)