265 lines
8.2 KiB
Lua
Raw Normal View History

2022-04-09 18:25:27 +02:00
local S = minetest.get_translator("perlin_explorer")
2022-04-09 18:23:01 +02:00
-- Holds the currently used Perlin noise
local current_perlin = {}
-- holds the current PerlinNoise object
current_perlin.noise = nil
-- dimensions of current Perlin noise (2 or 3)
current_perlin.dimensions = nil
-- distance from player center in each cardinal direction in which the Perlin noise is calculated and shown (squarius = "square radius")
current_perlin.squarius = nil
-- Theoretical min and max values for Perlin noise (for colorization)
current_perlin.max = 1
current_perlin.min = -1
-- Test nodes to generate a map based on a perlin noise
2022-04-09 18:25:27 +02:00
minetest.register_node("perlin_explorer:node", {
2022-04-09 18:23:01 +02:00
description = S("Perlin Test Node"),
paramtype2 = "color",
2022-04-09 18:25:27 +02:00
tiles = {"perlin_explorer_node.png"},
palette = "perlin_explorer_node_palette.png",
2022-04-09 18:23:01 +02:00
groups = { dig_immediate = 3 },
-- Force-drop without metadata to avoid spamming the inventory
2022-04-09 18:25:27 +02:00
drop = "perlin_explorer:node",
2022-04-09 18:23:01 +02:00
})
2022-04-09 18:25:27 +02:00
minetest.register_node("perlin_explorer:node_negative", {
2022-04-09 18:23:01 +02:00
description = S("Negative Perlin Test Node"),
paramtype2 = "color",
2022-04-09 18:25:27 +02:00
tiles = {"perlin_explorer_node_neg.png"},
palette = "perlin_explorer_node_palette_neg.png",
2022-04-09 18:23:01 +02:00
groups = { dig_immediate = 3 },
-- Force-drop without metadata to avoid spamming the inventory
2022-04-09 18:25:27 +02:00
drop = "perlin_explorer:node_negative",
2022-04-09 18:23:01 +02:00
})
2022-04-09 20:39:15 +02:00
minetest.register_tool("perlin_explorer:getter", {
description = S("Perlin Value Getter"),
_tt_help = S("Punch a node to display the Perlin noise value at this position"),
inventory_image = "perlin_explorer_getter.png",
wield_image = "perlin_explorer_getter.png",
groups = { disable_repair = 1 },
on_use = function(itemstack, user, pointed_thing)
if current_perlin.noise then
if pointed_thing.type ~= "node" then
-- No-op for non-nodes
return
end
local pos = pointed_thing.under
local val
if current_perlin.dimensions == 2 then
val = current_perlin.noise:get_2d({x=pos.x, y=pos.z})
else
val = current_perlin.noise:get_3d(pos)
end
minetest.chat_send_player(user:get_player_name(), S("pos=@1, value=@2", minetest.pos_to_string(pos), val))
else
local msg = S("No Perlin noise set. Set one with /set_perlin!")
minetest.chat_send_player(user:get_player_name(), msg)
end
end,
})
2022-04-09 18:25:27 +02:00
local CONTENT_TEST_NODE = minetest.get_content_id("perlin_explorer:node")
local CONTENT_TEST_NODE_NEGATIVE = minetest.get_content_id("perlin_explorer:node_negative")
2022-04-09 18:23:01 +02:00
local update_map = function(pos)
local stats
if not current_perlin.noise then
return
end
local squarius_v = vector.new(current_perlin.squarius, current_perlin.squarius, current_perlin.squarius)
local startpos = pos
local endpos = vector.add(startpos, current_perlin.squarius*2+1)
local y_max = endpos.y - startpos.y
if current_perlin.dimensions == 2 then
y_max = 0
startpos.y = pos.y
endpos.y = pos.y
end
local vmanip = VoxelManip(startpos, endpos)
local emin, emax = vmanip:get_emerged_area()
local vdata = vmanip:get_data()
local vdata2 = vmanip:get_param2_data()
local varea = VoxelArea:new({MinEdge = emin, MaxEdge = emax})
stats = {}
stats.avg = 0
local sum_of_values = 0
local value_count = 0
for x=0, endpos.x - startpos.x do
for y=0, y_max do
for z=0, endpos.z - startpos.z do
-- Get Perlin value at current pos
local relpos = vector.new(x,y,z)
local abspos = vector.add(startpos, relpos)
local perlin_value
if current_perlin.dimensions == 2 then
perlin_value = current_perlin.noise:get_2d({x=abspos.x, y=abspos.z})
elseif current_perlin.dimensions == 3 then
perlin_value = current_perlin.noise:get_3d(abspos)
else
2022-04-09 18:25:27 +02:00
error("[perlin_explorer] Unknown/invalid number of Perlin noise dimensions!")
2022-04-09 18:23:01 +02:00
return
end
-- Statistics
if not stats.min then
stats.min = perlin_value
elseif perlin_value < stats.min then
stats.min = perlin_value
end
if not stats.max then
stats.max = perlin_value
elseif perlin_value > stats.max then
stats.max = perlin_value
end
sum_of_values = sum_of_values + perlin_value
value_count = value_count + 1
-- Calculate color (param2) for node
local node_param2 = math.floor(math.abs(perlin_value) * 255)
node_param2 = math.max(0, math.min(255, node_param2))
-- Get vmanip index
local index = varea:indexp(abspos)
if not index then
return
end
-- Set node and param2
if perlin_value >= 0 then
vdata[index] = CONTENT_TEST_NODE
vdata2[index] = node_param2
else
if current_perlin.show_negative == true then
vdata[index] = CONTENT_TEST_NODE_NEGATIVE
vdata2[index] = node_param2
else
vdata[index] = minetest.CONTENT_AIR
vdata2[index] = 0
end
end
end
end
end
stats.avg = sum_of_values / value_count
-- Set vmanip, return stats
vmanip:set_data(vdata)
vmanip:set_param2_data(vdata2)
vmanip:calc_lighting()
vmanip:write_to_map()
return stats
end
-- Creates and demonstrates a Perlin noise.
-- * pos: Where the Perlin noise starts
-- * options: table with:
-- * dimensions: number of Perlin noise dimensions (2 or 3)
-- * squarius: distance from center to the edge of the square/cube of the whole Perlin noise area
2022-04-09 19:44:15 +02:00
-- * show_negative: if true, places nodes for negative Perlin values (default: true for 2 dimensions and false for 3 dimensions)
create_perlin = function(pos, options)
if not current_perlin.noise then
return false
end
2022-04-09 18:23:01 +02:00
current_perlin.dimensions = options.dimensions
current_perlin.squarius = options.squarius
current_perlin.show_negative = options.show_negative
if current_perlin.show_negative == nil then
2022-04-09 19:44:15 +02:00
if current_perlin.dimensions == 2 then
2022-04-09 20:39:15 +02:00
current_perlin.show_negative = true
2022-04-09 19:44:15 +02:00
elseif current_perlin.dimensions == 3 then
2022-04-09 20:39:15 +02:00
current_perlin.show_negative = false
2022-04-09 19:44:15 +02:00
end
2022-04-09 18:23:01 +02:00
end
local mpos = vector.round(pos)
local stats = update_map(mpos)
if stats then
2022-04-09 19:44:15 +02:00
return string.format("Perlin noise created! Stats: min. value=%.3f, max. value=%.3f, avg. value=%.3f", stats.min, stats.max, stats.avg)
2022-04-09 18:23:01 +02:00
end
end
2022-04-09 19:44:15 +02:00
-- Sets the currently active Perlin noise.
-- * noiseparams: NoiseParams table (see Minetest's Lua API documentation)
set_perlin_noise = function(noiseparams)
current_perlin.noise = PerlinNoise(noiseparams)
end
minetest.register_chatcommand("perlin_set", {
privs = { server = true },
description = S("Set Perlin noise parameters"),
params = S("<octaves> <offset> <scale> <spread_x> <spread_y> <spread_z> <persistence> <lacunarity> <seed>"),
func = function(name, param)
local octaves, offset, scale, sx, sy, sz, persistence, lacunarity, seed = string.match(param, string.rep("([0-9.-]+) ", 8) .. "([0-9.-]+)")
if not octaves then
return false
end
octaves = tonumber(octaves)
offset = tonumber(offset)
sx = tonumber(sx)
sy = tonumber(sy)
sz = tonumber(sz)
persistence = tonumber(persistence)
lacunarity = tonumber(lacunarity)
seed = tonumber(seed)
if not octaves or not offset or not sx or not sy or not sz or not persistence or not lacunarity or not seed then
return false, S("Invalid parameter type")
end
set_perlin_noise({
octaves = octaves,
offset = offset,
scale = scale,
spread = { x = sx, y = sy, z = sz },
persistence = persistence,
lacunarity = lacunarity,
seed = seed,
})
return true, S("Perlin noise set!")
end,
})
minetest.register_chatcommand("perlin_generate", {
privs = { server = true },
description = S("Generate Perlin noise"),
2022-04-09 20:39:15 +02:00
params = S("<pos> <dimensions> <squarius>"),
2022-04-09 19:44:15 +02:00
func = function(name, param)
2022-04-09 20:39:15 +02:00
local x, y, z, dimensions, squarius = string.match(param, "([0-9.-]+) ([0-9.-]+) ([0-9.-]+) ([23]) ([0-9]+)")
if not x then
2022-04-09 19:44:15 +02:00
return false
end
x = tonumber(x)
y = tonumber(y)
z = tonumber(z)
2022-04-09 20:39:15 +02:00
dimensions = tonumber(dimensions)
squarius = tonumber(squarius)
if not x or not y or not z or not dimensions or not squarius then
2022-04-09 19:44:15 +02:00
return false
end
2022-04-09 20:39:15 +02:00
if not x or not y or not z or not dimensions or not squarius then
2022-04-09 19:44:15 +02:00
return false, S("Invalid parameter type.")
end
local pos = vector.new(x, y, z)
minetest.chat_send_player(name, S("Creating Perlin noise, please wait …"))
2022-04-09 20:39:15 +02:00
local msg = create_perlin(pos, {dimensions=dimensions, squarius=squarius})
2022-04-09 19:44:15 +02:00
if msg == false then
return false, S("No Perlin noise set. Set one with '/perlin_set' first!")
end
return true, msg
end,
})
2022-04-09 18:23:01 +02:00
-- Clears the currently active Perlin noise
clear_perlin = function()
if current_perlin.noise then
current_perlin.noise = nil
end
end