diff --git a/worldeditadditions/lib/noise/apply_2d.lua b/worldeditadditions/lib/noise/apply_2d.lua index 42f0fd0..081b668 100644 --- a/worldeditadditions/lib/noise/apply_2d.lua +++ b/worldeditadditions/lib/noise/apply_2d.lua @@ -1,3 +1,5 @@ +local wea = worldeditadditions + --- Applies the given noise field to the given heightmap. -- Mutates the given heightmap. @@ -7,26 +9,41 @@ -- @param region_height number The height of the defined region. -- @param apply_mode string The apply mode to use to apply the noise to the heightmap. -- @returns bool[,string] A boolean value representing whether the application was successful or not. If false, then an error message as a string is also returned describing the error that occurred. -function worldeditadditions.noise.apply_2d(heightmap, noise, heightmap_size, region_height, apply_mode) - if type(apply_mode) ~= "string" then - return false, "Error: Expected string value for apply_mode, but received value of type "..type(apply_mode) +function worldeditadditions.noise.apply_2d(heightmap, noise, heightmap_size, pos1, pos2, apply_mode) + if type(apply_mode) ~= "string" and type(apply_mode) ~= "number" then + return false, "Error: Expected value of type string or number for apply_mode, but received value of type "..type(apply_mode) end - local percent = tonumber(apply_mode:match("^(%d+)%%$")) + local region_height = pos2.y - pos1.y + + print("NOISE\n") + worldeditadditions.format.array_2d(noise, heightmap_size.x) + + + local height = tonumber(apply_mode) + + print("HEIGHT", height) for z = heightmap_size.z - 1, 0, -1 do for x = heightmap_size.x - 1, 0, -1 do local i = (z * heightmap_size.x) + x if apply_mode == "add" then - heightmap[i] = heightmap[i] + noise[i] + heightmap[i] = wea.round(heightmap[i] + noise[i]) elseif apply_mode == "multiply" then - heightmap[i] = heightmap[i] * noise[i] - elseif percent then + heightmap[i] = wea.round(heightmap[i] * noise[i]) + elseif height then -- Rescale from 0 - 1 to -1 - +1 local rescaled = (noise[i] * 2) - 1 - -- Rescale to match the percentage specified - rescaled = rescaled * region_height * percent + -- print("DEBUG x", x, "z", z, "rescaled 1", rescaled) + -- Rescale to match the height specified + rescaled = rescaled * height + -- print("DEBUG x", x, "z", z, "rescaled 2", rescaled) + rescaled = math.floor(wea.clamp( + heightmap[i] + rescaled, + 0, region_height + )) + -- print("DEBUG x", x, "z", z, "before", heightmap[i], "after", rescaled) heightmap[i] = rescaled else return false, "Error: Unknown apply mode '"..apply_mode.."'" diff --git a/worldeditadditions/lib/noise/engines/init.lua b/worldeditadditions/lib/noise/engines/init.lua new file mode 100644 index 0000000..1f819ae --- /dev/null +++ b/worldeditadditions/lib/noise/engines/init.lua @@ -0,0 +1,7 @@ +local wea = worldeditadditions + + +return { + Perlin = dofile(wea.modpath.."/lib/noise/engines/perlin.lua"), + Sin = dofile(wea.modpath.."/lib/noise/engines/sin.lua") +} diff --git a/worldeditadditions/lib/noise/engines/sin.lua b/worldeditadditions/lib/noise/engines/sin.lua new file mode 100644 index 0000000..ea7c7d4 --- /dev/null +++ b/worldeditadditions/lib/noise/engines/sin.lua @@ -0,0 +1,22 @@ +local wea = worldeditadditions + + +local Sin = {} +Sin.__index = Sin + + +function Sin.new() + local result = {} + setmetatable(result, Sin) + return result +end + +function Sin:noise( x, y, z ) + -- local value = math.sin(x) + local value = (math.sin(x) + math.sin(y) + math.sin(z)) / 3 + -- Rescale from -1 - +1 to 0 - 1 + return (value + 1) / 2 + -- return ( +end + +return Sin diff --git a/worldeditadditions/lib/noise/init.lua b/worldeditadditions/lib/noise/init.lua index 5c67327..e09713a 100644 --- a/worldeditadditions/lib/noise/init.lua +++ b/worldeditadditions/lib/noise/init.lua @@ -11,4 +11,4 @@ dofile(wea.modpath.."/lib/noise/make_2d.lua") dofile(wea.modpath.."/lib/noise/params_apply_default.lua") -- Noise generation engines -wea.noise.Perlin = dofile(wea.modpath.."/lib/noise/engines/perlin.lua") +wea.noise.engines = dofile(wea.modpath.."/lib/noise/engines/init.lua") diff --git a/worldeditadditions/lib/noise/make_2d.lua b/worldeditadditions/lib/noise/make_2d.lua index 9fbeb91..addea4b 100644 --- a/worldeditadditions/lib/noise/make_2d.lua +++ b/worldeditadditions/lib/noise/make_2d.lua @@ -4,7 +4,7 @@ -- ██ ████ ██ ███████ █████ █████ █████ ██ ██ -- ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ -- ██ ██ ██ ██ ██ ██ ███████ ███████ ███████ ██████ - +local wea = worldeditadditions -- Generate a flat array of 2D noise. -- Written with help from https://www.redblobgames.com/maps/terrain-from-noise/ @@ -18,7 +18,9 @@ function worldeditadditions.noise.make_2d(size, start_pos, params) for i, layer in ipairs(params) do local generator if layer.algorithm == "perlin" then - generator = worldeditadditions.noise.Perlin.new() + generator = wea.noise.engines.Perlin.new() + elseif layer.algorithm == "sin" then + generator = wea.noise.engines.Sin.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 @@ -39,14 +41,7 @@ function worldeditadditions.noise.make_2d(size, start_pos, params) 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)) + -- We don't round here, because otherwise when we apply it it'll be inaccurate return true, result end diff --git a/worldeditadditions/lib/noise/params_apply_default.lua b/worldeditadditions/lib/noise/params_apply_default.lua index 6fb8c0a..f77d322 100644 --- a/worldeditadditions/lib/noise/params_apply_default.lua +++ b/worldeditadditions/lib/noise/params_apply_default.lua @@ -11,7 +11,7 @@ function worldeditadditions.noise.params_apply_default(params) -- - The exact string "add": Noise values are added to each heightmap pixel. -- - The exact string "multiply": Each heightmap pixel is multiplied by the corresponding noise value. -- - A string in the form of digits followed by a percent sign (e.g. "40%"), then the noise will is remapped from the range 0 - 1 to the range -1 - +1, and then for each pixel in the heightmap will be altered at most the given percentage of the total height of the defined region. - apply = "40%", + apply = 5, -- The backend noise algorithm to use algorithm = "perlin", -- Zooms in and out @@ -38,7 +38,8 @@ function worldeditadditions.noise.params_apply_default(params) -- Keyword support if params_el.perlin then params_el.algorithm = "perlin" end - print("DEBUG params_el type", type(params_el), "RAW", params_el) + if params_el.sin then params_el.algorithm = "sin" end + -- Apply this table to fill in the gaps worldeditadditions.table.apply( params_el, diff --git a/worldeditadditions/lib/noise/run2d.lua b/worldeditadditions/lib/noise/run2d.lua index 6e37074..3a14d15 100644 --- a/worldeditadditions/lib/noise/run2d.lua +++ b/worldeditadditions/lib/noise/run2d.lua @@ -14,10 +14,10 @@ local wea = worldeditadditions -- @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 + print("DEBUG noise_params_custom ", wea.format.map(noise_params)) noise_params = worldeditadditions.noise.params_apply_default(noise_params) print("DEBUG noise_params[1] ", wea.format.map(noise_params[1])) @@ -38,11 +38,12 @@ function worldeditadditions.noise.run2d(pos1, pos2, noise_params) noise_params) if not success then return success, noisemap end + local message success, message = wea.noise.apply_2d( heightmap_new, noisemap, heightmap_size, - region_height, + pos1, pos2, noise_params[1].apply ) print("RETURNED apply_2d success", success, "message", message) diff --git a/worldeditadditions/utils/numbers.lua b/worldeditadditions/utils/numbers.lua index 6abc787..2bc93d1 100644 --- a/worldeditadditions/utils/numbers.lua +++ b/worldeditadditions/utils/numbers.lua @@ -87,6 +87,17 @@ function worldeditadditions.getsign(src) else return src:match('-') and -1 or 1 end end +--- Clamp a number to ensure it falls within a given range. +-- @param value number The value to clamp. +-- @param min number The minimum allowed value. +-- @param max number The maximum allowed value. +-- @returns number The clamped number. +function worldeditadditions.clamp(value, min, max) + if value < min then return min end + if value > max then return max end + return value +end + -- For Testing: -- worldeditadditions = {} -- print(worldeditadditions.getsign('-y')) diff --git a/worldeditadditions/utils/parse/map.lua b/worldeditadditions/utils/parse/map.lua index 33f3052..805d7fd 100644 --- a/worldeditadditions/utils/parse/map.lua +++ b/worldeditadditions/utils/parse/map.lua @@ -1,16 +1,20 @@ +local wea = worldeditadditions --- Parses a map of key-value pairs into a table. -- For example, "count 25000 speed 0.8 rate_erosion 0.006 doawesome true" would be parsed into -- the following table: { count = 25000, speed = 0.8, rate_erosion = 0.006, doawesome = true }. --- @param params_text string The string to parse. +-- @param params_text string The string to parse. +-- @param keywords string[] A list of keywords. Keywords can be present on their own without a value. If found, their value will be automatically set to bool true. -- @returns table A table of key-value pairs parsed out from the given string. -function worldeditadditions.parse.map(params_text) +function worldeditadditions.parse.map(params_text, keywords) local result = {} - local parts = worldeditadditions.split(params_text, "%s+", false) + local parts = wea.split(params_text, "%s+", false) local last_key = nil + local mode = "KEY" for i, part in ipairs(parts) do - if i % 2 == 0 then -- Lua starts at 1 :-/ + print("PARSE_MAP | i", i, "MODE", mode, "PART", part) + if mode == "VALUE" then -- Try converting to a number to see if it works local part_converted = tonumber(part) if part_converted == nil then part_converted = part end @@ -18,8 +22,16 @@ function worldeditadditions.parse.map(params_text) if part_converted == "true" then part_converted = true end if part_converted == "false" then part_converted = false end result[last_key] = part + mode = "KEY" else last_key = part + -- Keyword support + if wea.table.contains(keywords, last_key) then + print("IS KEYWORD") + result[last_key] = true + else + mode = "VALUE" + end end end return true, result diff --git a/worldeditadditions/utils/tables/table_contains.lua b/worldeditadditions/utils/tables/table_contains.lua index 148ecb4..e69e7cf 100644 --- a/worldeditadditions/utils/tables/table_contains.lua +++ b/worldeditadditions/utils/tables/table_contains.lua @@ -3,7 +3,7 @@ -- @param tbl table The table to look in. -- @param target any The target to look for. -- @returns bool Whether the table contains the given target or not. -local function contains(tbl, target) +local function table_contains(tbl, target) for key, value in ipairs(tbl) do if value == target then return true end end diff --git a/worldeditadditions_commands/commands/noise2d.lua b/worldeditadditions_commands/commands/noise2d.lua index 28ca39b..68e8cee 100644 --- a/worldeditadditions_commands/commands/noise2d.lua +++ b/worldeditadditions_commands/commands/noise2d.lua @@ -12,16 +12,22 @@ worldedit.register_command("noise2d", { if params_text == "" then return true, {} end - local success, map = worldeditadditions.parse.map(params_text) + local success, map = worldeditadditions.parse.map(params_text, { + -- Keywords + "perlin", "sin" + }) if not success then return success, map end + print("DEBUG noise_params raw ", wea.format.map(map)) + + 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.scalex = tonumber(map.scalex) or 1 + map.scaley = tonumber(map.scaley) or 1 + map.scalez = tonumber(map.scalez) or 1 map.scale = wea.Vector3.new(map.scalex, map.scaley, map.scalez) end if map.offsetx or map.offsety or map.offsetz then