mirror of
https://github.com/sbrl/Minetest-WorldEditAdditions.git
synced 2024-11-27 09:33:52 +01:00
//noise2d works!
...just. Next up more engines and documentation
This commit is contained in:
parent
ad8d8eab6b
commit
b2eb76d280
@ -44,6 +44,7 @@ dofile(worldeditadditions.modpath.."/lib/scale_down.lua")
|
||||
dofile(worldeditadditions.modpath.."/lib/scale.lua")
|
||||
dofile(worldeditadditions.modpath.."/lib/conv/conv.lua")
|
||||
dofile(worldeditadditions.modpath.."/lib/erode/erode.lua")
|
||||
dofile(worldeditadditions.modpath.."/lib/noise/init.lua")
|
||||
|
||||
dofile(worldeditadditions.modpath.."/lib/count.lua")
|
||||
|
||||
|
@ -29,7 +29,7 @@ function worldeditadditions.noise.apply_2d(heightmap, noise, heightmap_size, reg
|
||||
rescaled = rescaled * region_height * percent
|
||||
heightmap[i] = rescaled
|
||||
else
|
||||
return false, "Error: Unknown apply_mode '"..apply_mode.."'"
|
||||
return false, "Error: Unknown apply mode '"..apply_mode.."'"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -1,31 +1,5 @@
|
||||
local wea = worldeditadditions
|
||||
|
||||
|
||||
local function BitAND(a, b) --Bitwise and
|
||||
local p, c = 1, 0
|
||||
while a > 0 and b > 0 do
|
||||
local ra, rb = a%2, b%2
|
||||
if ra + rb > 1 then c = c + p end
|
||||
a, b, p = (a - ra) / 2, (b - rb) / 2, p * 2
|
||||
end
|
||||
return c
|
||||
end
|
||||
|
||||
local function fade(t)
|
||||
return t * t * t * (t * (t * 6 - 15) + 10)
|
||||
end
|
||||
|
||||
local function lerp(t, a, b)
|
||||
return a + t * (b - a)
|
||||
end
|
||||
|
||||
local function grad(hash, x, y, z)
|
||||
local h = BitAND(hash, 15)
|
||||
local u = h < 8 and x or y
|
||||
local v = h < 4 and y or ((h == 12 or h == 14) and x or z)
|
||||
return ((h and 1) == 0 and u or - u) + ((h and 2) == 0 and v or - v)
|
||||
end
|
||||
|
||||
--- Perlin noise generation engine.
|
||||
-- Original code by Ken Perlin: http://mrl.nyu.edu/~perlin/noise/
|
||||
-- Port from this StackOverflow answer: https://stackoverflow.com/a/33425812/1460422
|
||||
@ -71,42 +45,107 @@ end
|
||||
-- yourself.
|
||||
function Perlin:load()
|
||||
for i = 1, self.size do
|
||||
self.p[i] = self.permutation[i]
|
||||
self.p[255 + i] = self.p[i]
|
||||
self.p[i - 1] = self.permutation[i]
|
||||
self.p[i + 255] = self.permutation[i]
|
||||
end
|
||||
end
|
||||
|
||||
--- Formats a key-value table of values as a string.
|
||||
-- @param map table The table of key-value pairs to format.
|
||||
-- @returns string The given table of key-value pairs formatted as a string.
|
||||
local function format_map(map)
|
||||
local result = {}
|
||||
for key, value in pairs(map) do
|
||||
table.insert(result, key.."\t"..tostring(value))
|
||||
end
|
||||
return table.concat(result, "\n")
|
||||
end
|
||||
|
||||
|
||||
--- Returns a noise value for a given point in 3D space.
|
||||
-- @param x number The x co-ordinate.
|
||||
-- @param y number The y co-ordinate.
|
||||
-- @param z number The z co-ordinate.
|
||||
-- @returns number The calculated noise value.
|
||||
function Perlin:noise( x, y, z )
|
||||
local X = BitAND(math.floor(x), 255) + 1
|
||||
local Y = BitAND(math.floor(y), 255) + 1
|
||||
local Z = BitAND(math.floor(z), 255) + 1
|
||||
y = y or 0
|
||||
z = z or 0
|
||||
local xi = self:BitAND(math.floor(x), 255)
|
||||
local yi = self:BitAND(math.floor(y), 255)
|
||||
local zi = self:BitAND(math.floor(z), 255)
|
||||
|
||||
-- print("x", x, "y", y, "z", z, "xi", xi, "yi", yi, "zi", zi)
|
||||
-- print("p[xi]", self.p[xi])
|
||||
x = x - math.floor(x)
|
||||
y = y - math.floor(y)
|
||||
z = z - math.floor(z)
|
||||
local u = fade(x)
|
||||
local v = fade(y)
|
||||
local w = fade(z)
|
||||
local A = self.p[X] + Y
|
||||
local AA = self.p[A] + Z
|
||||
local AB = self.p[A + 1] + Z
|
||||
local B = self.p[X + 1] + Y
|
||||
local BA = self.p[B] + Z
|
||||
local BB = self.p[B + 1] + Z
|
||||
local u = self:fade(x)
|
||||
local v = self:fade(y)
|
||||
local w = self:fade(z)
|
||||
local A = self.p[xi] + yi
|
||||
local AA = self.p[A] + zi
|
||||
local AB = self.p[A + 1] + zi
|
||||
local AAA = self.p[AA]
|
||||
local ABA = self.p[AB]
|
||||
local AAB = self.p[AA + 1]
|
||||
local ABB = self.p[AB + 1]
|
||||
|
||||
return lerp(w, lerp(v, lerp(u, grad(self.p[AA ], x, y, z ),
|
||||
grad(self.p[BA ], x - 1, y, z )),
|
||||
lerp(u, grad(self.p[AB ], x, y - 1, z ),
|
||||
grad(self.p[BB ], x - 1, y - 1, z ))),
|
||||
lerp(v, lerp(u, grad(self.p[AA + 1], x, y, z - 1),
|
||||
grad(self.p[BA + 1], x - 1, y, z - 1)),
|
||||
lerp(u, grad(self.p[AB + 1], x, y - 1, z - 1),
|
||||
grad(self.p[BB + 1], x - 1, y - 1, z - 1))))
|
||||
local B = self.p[xi + 1] + yi
|
||||
local BA = self.p[B] + zi
|
||||
local BB = self.p[B + 1] + zi
|
||||
local BAA = self.p[BA]
|
||||
local BBA = self.p[BB]
|
||||
local BAB = self.p[BA + 1]
|
||||
local BBB = self.p[BB + 1]
|
||||
|
||||
-- Add 0.5 to rescale to 0 - 1 instead of -0.5 - +0.5
|
||||
return 0.5 + self:lerp(w,
|
||||
self:lerp(v,
|
||||
self:lerp(u,
|
||||
self:grad(AAA,x,y,z),
|
||||
self:grad(BAA,x-1,y,z)
|
||||
),
|
||||
self:lerp(u,
|
||||
self:grad(ABA,x,y-1,z),
|
||||
self:grad(BBA,x-1,y-1,z)
|
||||
)
|
||||
),
|
||||
self:lerp(v,
|
||||
self:lerp(u,
|
||||
self:grad(AAB,x,y,z-1), self:grad(BAB,x-1,y,z-1)
|
||||
),
|
||||
self:lerp(u,
|
||||
self:grad(ABB,x,y-1,z-1), self:grad(BBB,x-1,y-1,z-1)
|
||||
)
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
|
||||
function Perlin:BitAND(a, b) --Bitwise and
|
||||
local p, c = 1, 0
|
||||
while a > 0 and b > 0 do
|
||||
local ra, rb = a%2, b%2
|
||||
if ra + rb > 1 then c = c + p end
|
||||
a, b, p = (a - ra) / 2, (b - rb) / 2, p * 2
|
||||
end
|
||||
return c
|
||||
end
|
||||
|
||||
function Perlin:fade(t)
|
||||
return t * t * t * (t * (t * 6 - 15) + 10)
|
||||
end
|
||||
|
||||
function Perlin:lerp(t, a, b)
|
||||
return a + t * (b - a)
|
||||
end
|
||||
|
||||
function Perlin:grad(hash, x, y, z)
|
||||
local h = self:BitAND(hash, 15)
|
||||
local u = h < 8 and x or y
|
||||
local v = h < 4 and y or ((h == 12 or h == 14) and x or z)
|
||||
return ((h and 1) == 0 and u or - u) + ((h and 2) == 0 and v or - v)
|
||||
end
|
||||
|
||||
|
||||
return Perlin
|
||||
|
70
worldeditadditions/lib/noise/engines/perlin_test.lua
Normal file
70
worldeditadditions/lib/noise/engines/perlin_test.lua
Normal file
@ -0,0 +1,70 @@
|
||||
local Perlin = require("perlin")
|
||||
|
||||
--- Pads str to length len with char from left
|
||||
-- Adapted from the above
|
||||
local function str_padstart(str, len, char)
|
||||
if char == nil then char = ' ' end
|
||||
return string.rep(char, len - #str) .. str
|
||||
end
|
||||
local function array_2d(tbl, width)
|
||||
print("==== count: "..(#tbl+1)..", width:"..width.." ====")
|
||||
local display_width = 1
|
||||
for _i,value in pairs(tbl) do
|
||||
display_width = math.max(display_width, #tostring(value))
|
||||
end
|
||||
display_width = display_width + 2
|
||||
local next = {}
|
||||
for i=0, #tbl do
|
||||
table.insert(next, str_padstart(tostring(tbl[i]), display_width))
|
||||
if #next == width then
|
||||
print(table.concat(next, ""))
|
||||
next = {}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- Finds the minimum value in the given list.
|
||||
-- @param list number[] The list (table) of numbers to find the minimum value of.
|
||||
-- @returns number The minimum value in the given list.
|
||||
function min(list)
|
||||
if #list == 0 then return nil end
|
||||
local min = nil
|
||||
for i,value in ipairs(list) do
|
||||
if min == nil or min > value then
|
||||
min = value
|
||||
end
|
||||
end
|
||||
return min
|
||||
end
|
||||
|
||||
--- Finds the maximum value in the given list.
|
||||
-- @param list number[] The list (table) of numbers to find the maximum value of.
|
||||
-- @returns number The maximum value in the given list.
|
||||
function max(list)
|
||||
if #list == 0 then return nil end
|
||||
local max = nil
|
||||
for i,value in ipairs(list) do
|
||||
if max == nil or max < value then
|
||||
max = value
|
||||
end
|
||||
end
|
||||
return max
|
||||
end
|
||||
|
||||
|
||||
local p = Perlin.new()
|
||||
|
||||
local result = {}
|
||||
local size = { x = 10, z = 25 }
|
||||
|
||||
for x = 0, size.x do
|
||||
for y = 0, size.z do
|
||||
result[y*size.x + x] = p:noise(x, y, 0)
|
||||
end
|
||||
end
|
||||
|
||||
print(array_2d(result, size.x))
|
||||
|
||||
print("MAX ", max(result))
|
||||
print("MIN ", min(result))
|
@ -1,12 +1,14 @@
|
||||
worldeditadditions.noise = {}
|
||||
local wea = worldeditadditions
|
||||
|
||||
wea.noise = {}
|
||||
|
||||
-- The command itself
|
||||
dofile(worldeditadditions.modpath.."/lib/noise/noise2d.lua")
|
||||
dofile(wea.modpath.."/lib/noise/run2d.lua")
|
||||
|
||||
-- Dependencies
|
||||
dofile(worldeditadditions.modpath.."/lib/noise/apply_2d.lua")
|
||||
dofile(worldeditadditions.modpath.."/lib/noise/make_2d.lua")
|
||||
dofile(worldeditadditions.modpath.."/lib/noise/params_apply_default.lua")
|
||||
dofile(wea.modpath.."/lib/noise/apply_2d.lua")
|
||||
dofile(wea.modpath.."/lib/noise/make_2d.lua")
|
||||
dofile(wea.modpath.."/lib/noise/params_apply_default.lua")
|
||||
|
||||
-- Noise generation engines
|
||||
dofile(worldeditadditions.modpath.."/lib/noise/engines/perlin.lua")
|
||||
wea.noise.Perlin = dofile(wea.modpath.."/lib/noise/engines/perlin.lua")
|
||||
|
@ -10,25 +10,43 @@
|
||||
-- Written with help from https://www.redblobgames.com/maps/terrain-from-noise/
|
||||
-- @param size Vector An x/y vector representing the size of the noise area to generate.
|
||||
-- @param params table|table<table> A table of noise params to use to generate the noise. Values that aren't specified are filled in automatically. If a table of tables is specified, it is interpreted as multiple octaves of noise to apply in sequence.
|
||||
function worldeditadditions.noise.make_2d(size, params)
|
||||
function worldeditadditions.noise.make_2d(size, start_pos, params)
|
||||
local result = {}
|
||||
|
||||
local generator;
|
||||
if params.algorithm == "perlin" then
|
||||
generator = worldeditadditions.noise.perlin.new()
|
||||
print("size x", size.x, "z", size.z, "start_pos x", start_pos.x, "z", start_pos.z)
|
||||
|
||||
for i, layer in ipairs(params) do
|
||||
local generator
|
||||
if layer.algorithm == "perlin" then
|
||||
generator = worldeditadditions.noise.Perlin.new()
|
||||
else -- We don't have any other generators just yet
|
||||
return false, "Error: Unknown noise algorithm '"..params.."' (available algorithms: perlin)."
|
||||
return false, "Error: Unknown noise algorithm '"..tostring(layer.algorithm).."' in layer "..i.." of "..#params.." (available algorithms: perlin)."
|
||||
end
|
||||
|
||||
for x=params.offset.x, params.offset.x+size.x do
|
||||
for y=params.offset.y, params.offset.y+size.y do
|
||||
local result = 0
|
||||
for i,params_octave in ipairs(params) do
|
||||
result = result + (generator:noise(x * scale.x, y * scale.y, 0) ^ params.exponent) * params.multiply + params.add
|
||||
end
|
||||
result[y*size.x + x] = result
|
||||
for x = 0, size.x do
|
||||
for y = 0, size.z do
|
||||
local i = y*size.x + x
|
||||
|
||||
local noise_x = (x + 100000+start_pos.x+layer.offset.x) * layer.scale.x
|
||||
local noise_y = (y + 100000+start_pos.z+layer.offset.z) * layer.scale.z
|
||||
local noise_value = generator:noise(noise_x, noise_y, 0)
|
||||
|
||||
-- print("DEBUG NOISE x", noise_x, "y", noise_y, "value", noise_value)
|
||||
if type(result[i]) ~= "number" then result[i] = 0 end
|
||||
result[i] = result[i] + (noise_value ^ layer.exponent) * layer.multiply + layer.add
|
||||
end
|
||||
end
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
for x = 0, size.x do
|
||||
for y = 0, size.z do
|
||||
local i = y*size.x + x
|
||||
result[i] = worldeditadditions.round(result[i])
|
||||
end
|
||||
end
|
||||
|
||||
print("NOISE\n", worldeditadditions.format.array_2d(result, size.x))
|
||||
|
||||
return true, result
|
||||
end
|
||||
|
@ -32,9 +32,14 @@ function worldeditadditions.noise.params_apply_default(params)
|
||||
if not params[1] then params = { params } end
|
||||
|
||||
-- If params[1] is thing, this is a list of params
|
||||
-- This might be a thing if we're dealingw ith multiple octaves
|
||||
-- This might be a thing if we're dealing with multiple octaves
|
||||
for i,params_el in ipairs(params) do
|
||||
local default_copy = worldeditadditions.table.shallowcopy(params_default)
|
||||
|
||||
-- Keyword support
|
||||
if params_el.perlin then params_el.algorithm = "perlin" end
|
||||
print("DEBUG params_el type", type(params_el), "RAW", params_el)
|
||||
-- Apply this table to fill in the gaps
|
||||
worldeditadditions.table.apply(
|
||||
params_el,
|
||||
default_copy
|
||||
|
@ -11,14 +11,15 @@ local wea = worldeditadditions
|
||||
--- Applies a layer of 2d noise over the terrain in the defined region.
|
||||
-- @param pos1 Vector pos1 of the defined region
|
||||
-- @param pos2 Vector pos2 of the defined region
|
||||
-- @param noise_params table A noise parameters table. Will be passed unmodified to PerlinNoise() from the Minetest API.
|
||||
function worldeditadditions.noise.noise2d(pos1, pos2, noise_params)
|
||||
-- @param noise_params table A noise parameters table.
|
||||
function worldeditadditions.noise.run2d(pos1, pos2, noise_params)
|
||||
pos1, pos2 = worldedit.sort_pos(pos1, pos2)
|
||||
local region_height = pos1.y - pos2.y
|
||||
-- pos2 will always have the highest co-ordinates now
|
||||
|
||||
-- Fill in the default params
|
||||
noise_params = worldeditadditions.noise.params_apply_default(noise_params)
|
||||
print("DEBUG noise_params[1] ", wea.format.map(noise_params[1]))
|
||||
|
||||
-- Fetch the nodes in the specified area
|
||||
local manip, area = worldedit.manip_helpers.init(pos1, pos2)
|
||||
@ -31,15 +32,22 @@ function worldeditadditions.noise.noise2d(pos1, pos2, noise_params)
|
||||
)
|
||||
local heightmap_new = worldeditadditions.table.shallowcopy(heightmap_old)
|
||||
|
||||
local noisemap = wea.noise.make_2d(noise_params, heightmap_size)
|
||||
local success, noisemap = wea.noise.make_2d(
|
||||
heightmap_size,
|
||||
pos1,
|
||||
noise_params)
|
||||
if not success then return success, noisemap end
|
||||
|
||||
wea.noise.apply_2d(
|
||||
success, message = wea.noise.apply_2d(
|
||||
heightmap_new,
|
||||
noisemap,
|
||||
heightmap_size,
|
||||
region_height,
|
||||
noise_params.apply
|
||||
noise_params[1].apply
|
||||
)
|
||||
print("RETURNED apply_2d success", success, "message", message)
|
||||
if not success then return success, message end
|
||||
|
||||
|
||||
local success, stats = wea.apply_heightmap_changes(
|
||||
pos1, pos2,
|
@ -13,7 +13,7 @@ function worldeditadditions.parse.map(params_text)
|
||||
if i % 2 == 0 then -- Lua starts at 1 :-/
|
||||
-- Try converting to a number to see if it works
|
||||
local part_converted = tonumber(part)
|
||||
if as_number == nil then part_converted = part end
|
||||
if part_converted == nil then part_converted = part end
|
||||
-- Look for bools
|
||||
if part_converted == "true" then part_converted = true end
|
||||
if part_converted == "false" then part_converted = false end
|
||||
|
51
worldeditadditions_commands/commands/noise2d.lua
Normal file
51
worldeditadditions_commands/commands/noise2d.lua
Normal file
@ -0,0 +1,51 @@
|
||||
|
||||
local wea = worldeditadditions
|
||||
|
||||
worldedit.register_command("noise2d", {
|
||||
params = "[<key_1> [<value_1>]] [<key_2> [<value_2>]] ...]",
|
||||
description = "Applies 2d random noise to the terrain as a 2d heightmap in the defined region. Optionally takes an arbitrary set of key - value pairs representing parameters that control the properties of the noise and how it's applied. See the full documentation for details of these parameters and what they do.",
|
||||
privs = { worldedit = true },
|
||||
require_pos = 2,
|
||||
parse = function(params_text)
|
||||
if not params_text then return true, {} end
|
||||
params_text = wea.trim(params_text)
|
||||
if params_text == "" then return true, {} end
|
||||
|
||||
|
||||
local success, map = worldeditadditions.parse.map(params_text)
|
||||
if not success then return success, map end
|
||||
|
||||
if map.scale then
|
||||
map.scale = tonumber(map.scale)
|
||||
map.scale = wea.Vector3.new(map.scale, map.scale, map.scale)
|
||||
elseif map.scalex or map.scaley or map.scalez then
|
||||
map.scalex = tonumber(map.scalex) or 0
|
||||
map.scaley = tonumber(map.scaley) or 0
|
||||
map.scalez = tonumber(map.scalez) or 0
|
||||
map.scale = wea.Vector3.new(map.scalex, map.scaley, map.scalez)
|
||||
end
|
||||
if map.offsetx or map.offsety or map.offsetz then
|
||||
map.offsetx = tonumber(map.offsetx) or 0
|
||||
map.offsety = tonumber(map.offsety) or 0
|
||||
map.offsetz = tonumber(map.offsetz) or 0
|
||||
map.offset = wea.Vector3.new(map.offsetx, map.offsety, map.offsetz)
|
||||
end
|
||||
|
||||
return true, map
|
||||
end,
|
||||
nodes_needed = function(name)
|
||||
return worldedit.volume(worldedit.pos1[name], worldedit.pos2[name])
|
||||
end,
|
||||
func = function(name, params)
|
||||
local start_time = worldeditadditions.get_ms_time()
|
||||
local success, stats = worldeditadditions.noise.run2d(
|
||||
worldedit.pos1[name], worldedit.pos2[name],
|
||||
params
|
||||
)
|
||||
if not success then return success, stats end
|
||||
local time_taken = worldeditadditions.get_ms_time() - start_time
|
||||
|
||||
minetest.log("action", name .. " used //noise2d at " .. worldeditadditions.vector.tostring(worldedit.pos1[name]) .. ", adding " .. stats.added .. " nodes and removing " .. stats.removed .. " nodes in " .. time_taken .. "s")
|
||||
return true, stats.added .. " nodes added and " .. stats.removed .. " nodes removed in " .. worldeditadditions.format.human_time(time_taken)
|
||||
end
|
||||
})
|
@ -28,6 +28,7 @@ dofile(we_c.modpath.."/commands/hollow.lua")
|
||||
dofile(we_c.modpath.."/commands/layers.lua")
|
||||
dofile(we_c.modpath.."/commands/line.lua")
|
||||
dofile(we_c.modpath.."/commands/maze.lua")
|
||||
dofile(we_c.modpath.."/commands/noise2d.lua")
|
||||
dofile(we_c.modpath.."/commands/overlay.lua")
|
||||
dofile(we_c.modpath.."/commands/replacemix.lua")
|
||||
dofile(we_c.modpath.."/commands/scale.lua")
|
||||
|
Loading…
Reference in New Issue
Block a user