//noise2d works!

...just.

Next up more engines and documentation
This commit is contained in:
Starbeamrainbowlabs 2021-07-03 22:53:16 +01:00
parent ad8d8eab6b
commit b2eb76d280
Signed by: sbrl
GPG key ID: 1BE5172E637709C2
11 changed files with 273 additions and 78 deletions

View file

@ -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")

View file

@ -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

View file

@ -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

View 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))

View file

@ -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")

View file

@ -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()
else -- We don't have any other generators just yet
return false, "Error: Unknown noise algorithm '"..params.."' (available algorithms: perlin)."
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 '"..tostring(layer.algorithm).."' in layer "..i.." of "..#params.." (available algorithms: perlin)."
end
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
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
result[i] = worldeditadditions.round(result[i])
end
end
return result
print("NOISE\n", worldeditadditions.format.array_2d(result, size.x))
return true, result
end

View file

@ -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

View file

@ -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,

View file

@ -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

View 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
})

View file

@ -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")