From cae75ec5a2fb8da8697131a99b19be8513b62f0f Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Sat, 3 Jul 2021 00:42:19 +0100 Subject: [PATCH 01/32] Add moar comments --- worldeditadditions/utils/mesh.lua | 2 ++ worldeditadditions/utils/node_identification.lua | 9 +++++++++ worldeditadditions/utils/vector3.lua | 5 +++++ 3 files changed, 16 insertions(+) diff --git a/worldeditadditions/utils/mesh.lua b/worldeditadditions/utils/mesh.lua index 6b0fa58..792e7a7 100644 --- a/worldeditadditions/utils/mesh.lua +++ b/worldeditadditions/utils/mesh.lua @@ -8,6 +8,7 @@ local wea = worldeditadditions -- ██ ██ ██ ██████ ███████ --- A single face of a Mesh. +-- @class local Face = {} Face.__index = Face @@ -41,6 +42,7 @@ function Face.__eq(a, b) return Face.equal(a, b) end -- ██ ██ ███████ ███████ ██ ██ --- A mesh of faces. +-- @class local Mesh = {} Mesh.__index = Mesh diff --git a/worldeditadditions/utils/node_identification.lua b/worldeditadditions/utils/node_identification.lua index 5f42f18..9fe3324 100644 --- a/worldeditadditions/utils/node_identification.lua +++ b/worldeditadditions/utils/node_identification.lua @@ -76,6 +76,11 @@ end local sapling_aliases = {} + +--- Register a new sapling alias. +-- @param sapling_node_name string The canonical name of the sapling. +-- @param alias string The alias name of the sapling. +-- @returns bool[,string] Whether the alias registration was successful or not. If false, then an error message as a string is also returned as the second value. function worldeditadditions.register_sapling_alias(sapling_node_name, alias) if sapling_aliases[sapling_node_name] ~= nil then return false, "Error: An alias against the node name '"..sapling_node_name.."' already exists." @@ -83,6 +88,9 @@ function worldeditadditions.register_sapling_alias(sapling_node_name, alias) sapling_aliases[alias] = sapling_node_name return true end +--- Convenience function to register many sapling aliases at once. +-- @param tbl [string, string][] A list of tables containing exactly 2 strings in the form { sapling_node_name, alias }. +-- @returns bool[,string] Whether the alias registrations were successful or not. If false, then an error message as a string is also returned as the second value. function worldeditadditions.register_sapling_alias_many(tbl) for i, next in ipairs(tbl) do local success, msg = worldeditadditions.register_sapling_alias( @@ -91,6 +99,7 @@ function worldeditadditions.register_sapling_alias_many(tbl) ) if not success then return success, msg end end + return true end --- Returns the current key ⇒ value table of sapling names and aliases. -- @return table diff --git a/worldeditadditions/utils/vector3.lua b/worldeditadditions/utils/vector3.lua index 75f6698..4acc92f 100644 --- a/worldeditadditions/utils/vector3.lua +++ b/worldeditadditions/utils/vector3.lua @@ -1,7 +1,12 @@ --- A 3-dimensional vector. +-- @class local Vector3 = {} Vector3.__index = Vector3 +--- Creates a new Vector3 instance. +-- @param x number The x co-ordinate value. +-- @param y number The y co-ordinate value. +-- @param z number The z co-ordinate value. function Vector3.new(x, y, z) if type(x) ~= "number" then error("Error: Expected number for the value of x, but received argument of type "..type(x)..".") From ee2e5716eb04e5ee7be2f3d7c3fa825b484eedf4 Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Sat, 3 Jul 2021 01:44:36 +0100 Subject: [PATCH 02/32] Implement more of the noise command backend It's still not hooked up yet though, and there's no command parsing logic either --- worldeditadditions/lib/noise/apply_2d.lua | 38 ++++++++++++++++ .../lib/noise/engines/perlin.lua | 44 ++++++++++++------- worldeditadditions/lib/noise/init.lua | 12 +++++ worldeditadditions/lib/noise/make_2d.lua | 4 +- worldeditadditions/lib/noise/noise.lua | 5 --- worldeditadditions/lib/noise/noise2d.lua | 27 +++++++++--- ...se_params.lua => params_apply_default.lua} | 13 +++++- worldeditadditions/utils/terrain.lua | 2 +- 8 files changed, 113 insertions(+), 32 deletions(-) create mode 100644 worldeditadditions/lib/noise/apply_2d.lua create mode 100644 worldeditadditions/lib/noise/init.lua delete mode 100644 worldeditadditions/lib/noise/noise.lua rename worldeditadditions/lib/noise/{noise_params.lua => params_apply_default.lua} (63%) diff --git a/worldeditadditions/lib/noise/apply_2d.lua b/worldeditadditions/lib/noise/apply_2d.lua new file mode 100644 index 0000000..e5e9a8c --- /dev/null +++ b/worldeditadditions/lib/noise/apply_2d.lua @@ -0,0 +1,38 @@ + +--- Applies the given noise field to the given heightmap. +-- Mutates the given heightmap. +-- @param heightmap number[] A table of ZERO indexed numbers representing the heghtmap - see worldeditadditions.make_heightmap(). +-- @param noise number[] An table identical in structure to the heightmap containing the noise values to apply. +-- @param heightmap_size {x:number,z:number} A 2d vector representing the size of the heightmap. +-- @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) + end + + local percent = tonumber(apply_mode:match("^(%d+)%%$")) + + 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] + elseif apply_mode == "multiply" then + heightmap[i] = heightmap[i] * noise[i] + elseif percent 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 + heightmap[i] = rescaled + else + return false, "Error: Unknown apply_mode '"..apply_mode.."'" + end + end + end + + return true +end diff --git a/worldeditadditions/lib/noise/engines/perlin.lua b/worldeditadditions/lib/noise/engines/perlin.lua index 29fa779..387aa22 100644 --- a/worldeditadditions/lib/noise/engines/perlin.lua +++ b/worldeditadditions/lib/noise/engines/perlin.lua @@ -1,8 +1,5 @@ local wea = worldeditadditions --- original code by Ken Perlin: http://mrl.nyu.edu/~perlin/noise/ --- Port from this StackOverflow answer: https://stackoverflow.com/a/33425812/1460422 - local function BitAND(a, b) --Bitwise and local p, c = 1, 0 @@ -29,10 +26,15 @@ local function grad(hash, x, y, 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 +-- @class +local Perlin = {} +Perlin.__index = Perlin -wea.noise.perlin = {} -wea.noise.perlin.p = {} -wea.noise.perlin.permutation = { +Perlin.p = {} +Perlin.permutation = { 151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33, @@ -50,28 +52,36 @@ wea.noise.perlin.permutation = { 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, 93, 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180 } -wea.noise.perlin.size = 256 -wea.noise.perlin.gx = {} -wea.noise.perlin.gy = {} -wea.noise.perlin.randMax = 256 +Perlin.size = 256 +Perlin.gx = {} +Perlin.gy = {} +Perlin.randMax = 256 ---- Creates a new perlin instance. --- @return perlin A new perlin instance. -function wea.noise.perlin.new() +--- Creates a new Perlin instance. +-- @return Perlin A new perlin instance. +function Perlin.new() local instance = {} - setmetatable(instance, wea.noise.perlin) + setmetatable(instance, Perlin) instance:load() return instance end -function wea.noise.perlin:load() +--- Initialises this Perlin instance. +-- Called automatically by the constructor - you do NOT need to call this +-- yourself. +function Perlin:load() for i = 1, self.size do self.p[i] = self.permutation[i] self.p[255 + i] = self.p[i] end end -function wea.noise.perlin:noise( x, y, z ) +--- 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 @@ -98,3 +108,5 @@ function wea.noise.perlin:noise( x, y, z ) lerp(u, grad(self.p[AB + 1], x, y - 1, z - 1), grad(self.p[BB + 1], x - 1, y - 1, z - 1)))) end + +return Perlin diff --git a/worldeditadditions/lib/noise/init.lua b/worldeditadditions/lib/noise/init.lua new file mode 100644 index 0000000..588423b --- /dev/null +++ b/worldeditadditions/lib/noise/init.lua @@ -0,0 +1,12 @@ +worldeditadditions.noise = {} + +-- The command itself +dofile(worldeditadditions.modpath.."/lib/noise/noise2d.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") + +-- Noise generation engines +dofile(worldeditadditions.modpath.."/lib/noise/engines/perlin.lua") diff --git a/worldeditadditions/lib/noise/make_2d.lua b/worldeditadditions/lib/noise/make_2d.lua index 7f8a7bb..8c4f52d 100644 --- a/worldeditadditions/lib/noise/make_2d.lua +++ b/worldeditadditions/lib/noise/make_2d.lua @@ -11,15 +11,13 @@ -- @param size Vector An x/y vector representing the size of the noise area to generate. -- @param params 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) - params = worldeditadditions.noise.params_apply_default(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.."'." + return false, "Error: Unknown noise algorithm '"..params.."' (available algorithms: perlin)." end for x=params.offset.x, params.offset.x+size.x do diff --git a/worldeditadditions/lib/noise/noise.lua b/worldeditadditions/lib/noise/noise.lua deleted file mode 100644 index e7720d7..0000000 --- a/worldeditadditions/lib/noise/noise.lua +++ /dev/null @@ -1,5 +0,0 @@ -worldeditadditions.noise = {} - -dofile(worldeditadditions.modpath.."/lib/noise/noise_params.lua") -dofile(worldeditadditions.modpath.."/lib/noise/make_2d.lua") -dofile(worldeditadditions.modpath.."/lib/noise/engines/perlin.lua") diff --git a/worldeditadditions/lib/noise/noise2d.lua b/worldeditadditions/lib/noise/noise2d.lua index bf83a31..2e82824 100644 --- a/worldeditadditions/lib/noise/noise2d.lua +++ b/worldeditadditions/lib/noise/noise2d.lua @@ -1,6 +1,8 @@ --- Applies a layer of 2D noise over the terrain in the defined region. -- @module worldeditadditions.noise2d +local wea = worldeditadditions + -- ███ ██ ██████ ██ ███████ ███████ ██████ ██████ -- ████ ██ ██ ██ ██ ██ ██ ██ ██ ██ -- ██ ██ ██ ██ ██ ██ ███████ █████ █████ ██ ██ @@ -10,10 +12,14 @@ -- @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.noise2d(pos1, pos2, noise_params) +function worldeditadditions.noise.noise2d(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) + -- Fetch the nodes in the specified area local manip, area = worldedit.manip_helpers.init(pos1, pos2) local data = manip:get_data() @@ -25,14 +31,25 @@ function worldeditadditions.noise2d(pos1, pos2, noise_params) ) local heightmap_new = worldeditadditions.table.shallowcopy(heightmap_old) - local perlin_map = PerlinNoiseMap(noise_params, heightmap_size) + local noisemap = wea.noise.make_2d(noise_params, heightmap_size) - -- TODO: apply the perlin noise map here + wea.noise.apply_2d( + heightmap_new, + noisemap, + heightmap_size, + region_height, + noise_params.apply + ) - local stats = { added = 0, removed = 0 } + local success, stats = wea.apply_heightmap_changes( + pos1, pos2, + area, data, + heightmap_old, heightmap_new, + heightmap_size + ) + if not success then return success, stats end -- Save the modified nodes back to disk & return - -- No need to save - this function doesn't actually change anything worldedit.manip_helpers.finish(manip, data) return true, stats diff --git a/worldeditadditions/lib/noise/noise_params.lua b/worldeditadditions/lib/noise/params_apply_default.lua similarity index 63% rename from worldeditadditions/lib/noise/noise_params.lua rename to worldeditadditions/lib/noise/params_apply_default.lua index c8539be..65329c0 100644 --- a/worldeditadditions/lib/noise/noise_params.lua +++ b/worldeditadditions/lib/noise/params_apply_default.lua @@ -1,14 +1,23 @@ +local wea = worldeditadditions + --- Makes sure that the default settings are all present in the given params table. -- This way not all params have to be specified by the user. -- @param params table The params table generated from the user's input. -- @return table A NEW table with all the missing properties filled in with the default values. function worldeditadditions.noise.params_apply_default(params) local params_default = { + -- How to apply the noise to the heightmap. Different values result in + -- different effects: + -- - 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%", + -- The backend noise algorithm to use algorithm = "perlin", -- Zooms in and out - scale = vector.new(1, 1, 1), + scale = wea.Vector3.new(1, 1, 1), -- Offset the generated noise by this vector. - offset = vector.new(0, 0, 0), + offset = wea.Vector3.new(0, 0, 0), -- Apply this exponent to the resulting noise value exponent = 1, -- Multiply the resulting noise value by this number. Changes the "strength" of the noise diff --git a/worldeditadditions/utils/terrain.lua b/worldeditadditions/utils/terrain.lua index 3811599..b8f4bca 100644 --- a/worldeditadditions/utils/terrain.lua +++ b/worldeditadditions/utils/terrain.lua @@ -92,7 +92,7 @@ end -- @param heightmap_old number[] The original heightmap from worldeditadditions.make_heightmap. -- @param heightmap_new number[] The new heightmap containing the altered updated values. It is expected that worldeditadditions.table.shallowcopy be used to make a COPY of the data worldeditadditions.make_heightmap for this purpose. Both heightmap_old AND heightmap_new are REQUIRED in order for this function to work. -- @param heightmap_size vector The x / z size of the heightmap. Any y value set in the vector is ignored. --- +-- @returns bool, string|{ added: number, removed: number } A bool indicating whether the operation was successful or not, followed by either an error message as a string (if it was not successful) or a table of statistics (if it was successful). function worldeditadditions.apply_heightmap_changes(pos1, pos2, area, data, heightmap_old, heightmap_new, heightmap_size) local stats = { added = 0, removed = 0 } local node_id_air = minetest.get_content_id("air") From b6bee590978925c3432d429e514e69bdd00062fb Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Sat, 3 Jul 2021 13:15:29 +0100 Subject: [PATCH 03/32] Reference: Document //basename --- Chat-Command-Reference.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/Chat-Command-Reference.md b/Chat-Command-Reference.md index 3414858..05da530 100644 --- a/Chat-Command-Reference.md +++ b/Chat-Command-Reference.md @@ -495,6 +495,22 @@ Counts all the nodes in the defined region and returns the result along with cal //count ``` + +## `//basename ` +Returns the absolute canonical name of a node, given an alias or partial node name. For example: + +``` +//basename dirt +``` + +...will return `default:dirt`. Uses `worldedit.normalize_nodename(string)` under the hood. + +``` +//basename stonebrick +//basename glass +``` + + ## `//subdivide ` Splits the current WorldEdit region into `(, , )` sized chunks, and run `// ` over each chunk. From fafe2c22de95b1a0a04fe31eaa6cd4b73e700ec4 Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Sat, 3 Jul 2021 13:26:18 +0100 Subject: [PATCH 04/32] Reference: tiny layout to test build system --- Chat-Command-Reference.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Chat-Command-Reference.md b/Chat-Command-Reference.md index 05da530..73b75d2 100644 --- a/Chat-Command-Reference.md +++ b/Chat-Command-Reference.md @@ -81,6 +81,7 @@ For example, the first 2 examples below are functionally equivalent. //forest 0.5 acacia ``` + ## `//saplingaliases [aliases|all_saplings]` Lists all the currently registered sapling aliases in alphabetical order. These aliases can be used in the `//forest` subcommand. From 33e839db29584aa5170065b4e996c64692547276 Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Sat, 3 Jul 2021 13:28:39 +0100 Subject: [PATCH 05/32] Reference: Another test --- Chat-Command-Reference.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Chat-Command-Reference.md b/Chat-Command-Reference.md index 73b75d2..55ccccb 100644 --- a/Chat-Command-Reference.md +++ b/Chat-Command-Reference.md @@ -110,6 +110,7 @@ Note that the *entire* cave you want filling must be selected, as `//fillcaves` //fillcaves brick ``` + ## `//ellipsoid [h[ollow]]` Creates a solid ellipsoid at position 1 with the radius `(rx, ry, rz)`. From 239113d415143cfb862727a125757a93336a41e7 Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Sat, 3 Jul 2021 13:35:42 +0100 Subject: [PATCH 06/32] Empty commit to test build system From ad8d8eab6bb662662827c4db6cf2e7249801bf6d Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Sat, 3 Jul 2021 14:44:59 +0100 Subject: [PATCH 07/32] wea.format.map: use tostring on values to avoid crashes --- worldeditadditions/utils/format/map.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worldeditadditions/utils/format/map.lua b/worldeditadditions/utils/format/map.lua index 57f1fc1..62a71ef 100644 --- a/worldeditadditions/utils/format/map.lua +++ b/worldeditadditions/utils/format/map.lua @@ -4,7 +4,7 @@ function worldeditadditions.format.map(map) local result = {} for key, value in pairs(map) do - table.insert(result, key.."\t"..value) + table.insert(result, key.."\t"..tostring(value)) end return table.concat(result, "\n") end From b2eb76d2806138c8f7b7db19267507bc8bb82c83 Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Sat, 3 Jul 2021 22:53:16 +0100 Subject: [PATCH 08/32] //noise2d works! ...just. Next up more engines and documentation --- worldeditadditions/init.lua | 1 + worldeditadditions/lib/noise/apply_2d.lua | 2 +- .../lib/noise/engines/perlin.lua | 139 +++++++++++------- .../lib/noise/engines/perlin_test.lua | 70 +++++++++ worldeditadditions/lib/noise/init.lua | 14 +- worldeditadditions/lib/noise/make_2d.lua | 46 ++++-- .../lib/noise/params_apply_default.lua | 7 +- .../lib/noise/{noise2d.lua => run2d.lua} | 18 ++- worldeditadditions/utils/parse/map.lua | 2 +- .../commands/noise2d.lua | 51 +++++++ worldeditadditions_commands/init.lua | 1 + 11 files changed, 273 insertions(+), 78 deletions(-) create mode 100644 worldeditadditions/lib/noise/engines/perlin_test.lua rename worldeditadditions/lib/noise/{noise2d.lua => run2d.lua} (78%) create mode 100644 worldeditadditions_commands/commands/noise2d.lua diff --git a/worldeditadditions/init.lua b/worldeditadditions/init.lua index 372b896..6486a56 100644 --- a/worldeditadditions/init.lua +++ b/worldeditadditions/init.lua @@ -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") diff --git a/worldeditadditions/lib/noise/apply_2d.lua b/worldeditadditions/lib/noise/apply_2d.lua index e5e9a8c..42f0fd0 100644 --- a/worldeditadditions/lib/noise/apply_2d.lua +++ b/worldeditadditions/lib/noise/apply_2d.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 diff --git a/worldeditadditions/lib/noise/engines/perlin.lua b/worldeditadditions/lib/noise/engines/perlin.lua index 387aa22..5577d9a 100644 --- a/worldeditadditions/lib/noise/engines/perlin.lua +++ b/worldeditadditions/lib/noise/engines/perlin.lua @@ -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 - - 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 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] + + 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 diff --git a/worldeditadditions/lib/noise/engines/perlin_test.lua b/worldeditadditions/lib/noise/engines/perlin_test.lua new file mode 100644 index 0000000..33c90e9 --- /dev/null +++ b/worldeditadditions/lib/noise/engines/perlin_test.lua @@ -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)) diff --git a/worldeditadditions/lib/noise/init.lua b/worldeditadditions/lib/noise/init.lua index 588423b..5c67327 100644 --- a/worldeditadditions/lib/noise/init.lua +++ b/worldeditadditions/lib/noise/init.lua @@ -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") diff --git a/worldeditadditions/lib/noise/make_2d.lua b/worldeditadditions/lib/noise/make_2d.lua index 8c4f52d..9fbeb91 100644 --- a/worldeditadditions/lib/noise/make_2d.lua +++ b/worldeditadditions/lib/noise/make_2d.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
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 diff --git a/worldeditadditions/lib/noise/params_apply_default.lua b/worldeditadditions/lib/noise/params_apply_default.lua index 65329c0..6fb8c0a 100644 --- a/worldeditadditions/lib/noise/params_apply_default.lua +++ b/worldeditadditions/lib/noise/params_apply_default.lua @@ -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 diff --git a/worldeditadditions/lib/noise/noise2d.lua b/worldeditadditions/lib/noise/run2d.lua similarity index 78% rename from worldeditadditions/lib/noise/noise2d.lua rename to worldeditadditions/lib/noise/run2d.lua index 2e82824..6e37074 100644 --- a/worldeditadditions/lib/noise/noise2d.lua +++ b/worldeditadditions/lib/noise/run2d.lua @@ -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, diff --git a/worldeditadditions/utils/parse/map.lua b/worldeditadditions/utils/parse/map.lua index aa35983..33f3052 100644 --- a/worldeditadditions/utils/parse/map.lua +++ b/worldeditadditions/utils/parse/map.lua @@ -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 diff --git a/worldeditadditions_commands/commands/noise2d.lua b/worldeditadditions_commands/commands/noise2d.lua new file mode 100644 index 0000000..28ca39b --- /dev/null +++ b/worldeditadditions_commands/commands/noise2d.lua @@ -0,0 +1,51 @@ + +local wea = worldeditadditions + +worldedit.register_command("noise2d", { + params = "[ []] [ []] ...]", + 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 +}) diff --git a/worldeditadditions_commands/init.lua b/worldeditadditions_commands/init.lua index f435b8c..3cf7e39 100644 --- a/worldeditadditions_commands/init.lua +++ b/worldeditadditions_commands/init.lua @@ -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") From 49bf0f19bc81a240cf82213891e52659d8e036dd Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Sun, 4 Jul 2021 12:03:29 +0100 Subject: [PATCH 09/32] Perlin: remove debug functions --- .../lib/noise/engines/perlin.lua | 12 ---- .../lib/noise/engines/perlin_test.lua | 70 ------------------- 2 files changed, 82 deletions(-) delete mode 100644 worldeditadditions/lib/noise/engines/perlin_test.lua diff --git a/worldeditadditions/lib/noise/engines/perlin.lua b/worldeditadditions/lib/noise/engines/perlin.lua index 5577d9a..03bf4ad 100644 --- a/worldeditadditions/lib/noise/engines/perlin.lua +++ b/worldeditadditions/lib/noise/engines/perlin.lua @@ -50,18 +50,6 @@ function Perlin:load() 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. diff --git a/worldeditadditions/lib/noise/engines/perlin_test.lua b/worldeditadditions/lib/noise/engines/perlin_test.lua deleted file mode 100644 index 33c90e9..0000000 --- a/worldeditadditions/lib/noise/engines/perlin_test.lua +++ /dev/null @@ -1,70 +0,0 @@ -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)) From 1d82013d86561cb3ea626a4e22193507f08c9e8c Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Sun, 4 Jul 2021 13:21:13 +0100 Subject: [PATCH 10/32] //noise2d: add sin; sort out apply_mode as number --- worldeditadditions/lib/noise/apply_2d.lua | 35 ++++++++++++++----- worldeditadditions/lib/noise/engines/init.lua | 7 ++++ worldeditadditions/lib/noise/engines/sin.lua | 22 ++++++++++++ worldeditadditions/lib/noise/init.lua | 2 +- worldeditadditions/lib/noise/make_2d.lua | 15 +++----- .../lib/noise/params_apply_default.lua | 5 +-- worldeditadditions/lib/noise/run2d.lua | 5 +-- worldeditadditions/utils/numbers.lua | 11 ++++++ worldeditadditions/utils/parse/map.lua | 20 ++++++++--- .../utils/tables/table_contains.lua | 2 +- .../commands/noise2d.lua | 14 +++++--- 11 files changed, 105 insertions(+), 33 deletions(-) create mode 100644 worldeditadditions/lib/noise/engines/init.lua create mode 100644 worldeditadditions/lib/noise/engines/sin.lua 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 From 0212950d87edde37ff98e29fc50ca73050d353ca Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Sun, 4 Jul 2021 14:30:22 +0100 Subject: [PATCH 11/32] wea.apply_heightmap_changes: fix loop --- worldeditadditions/utils/terrain.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/worldeditadditions/utils/terrain.lua b/worldeditadditions/utils/terrain.lua index b8f4bca..1a9bb44 100644 --- a/worldeditadditions/utils/terrain.lua +++ b/worldeditadditions/utils/terrain.lua @@ -98,8 +98,8 @@ function worldeditadditions.apply_heightmap_changes(pos1, pos2, area, data, heig local node_id_air = minetest.get_content_id("air") local node_id_ignore = minetest.get_content_id("ignore") - for z = heightmap_size.z, 0, -1 do - for x = heightmap_size.x, 0, -1 do + for z = heightmap_size.z - 1, 0, -1 do + for x = heightmap_size.x - 1, 0, -1 do local hi = z*heightmap_size.x + x local height_old = heightmap_old[hi] From 0b0595a1d3347b254528f1c9e97c81a677230441 Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Sun, 4 Jul 2021 14:35:02 +0100 Subject: [PATCH 12/32] Remove a whole bunch of stray debug print statements --- worldeditadditions/lib/forest.lua | 2 +- worldeditadditions/lib/line.lua | 2 -- worldeditadditions/lib/noise/apply_2d.lua | 9 ++------- worldeditadditions/lib/noise/make_2d.lua | 3 --- worldeditadditions/lib/noise/run2d.lua | 5 ++--- worldeditadditions/lib/scale_down.lua | 1 - worldeditadditions/utils/parse/map.lua | 2 -- .../commands/extra/saplingaliases.lua | 1 - worldeditadditions_commands/commands/meta/multi.lua | 4 ++-- worldeditadditions_commands/commands/noise2d.lua | 3 --- worldeditadditions_commands/commands/scale.lua | 2 +- 11 files changed, 8 insertions(+), 26 deletions(-) diff --git a/worldeditadditions/lib/forest.lua b/worldeditadditions/lib/forest.lua index ce27f01..5294174 100644 --- a/worldeditadditions/lib/forest.lua +++ b/worldeditadditions/lib/forest.lua @@ -66,7 +66,7 @@ function worldeditadditions.forest(pos1, pos2, density_multiplier, sapling_weigh end end if not did_grow then - print("[//forest] Failed to grow sapling, detected node id", new_id_at_pos, "name", new_name_at_pos, "was originally", minetest.get_name_from_content_id(node_id)) + -- print("[//forest] Failed to grow sapling, detected node id", new_id_at_pos, "name", new_name_at_pos, "was originally", minetest.get_name_from_content_id(node_id)) -- We can't set it to air here because then when we save back we would overwrite all the newly grown trees stats.failures = stats.failures + 1 end diff --git a/worldeditadditions/lib/line.lua b/worldeditadditions/lib/line.lua index 67e17c9..4fc7a89 100644 --- a/worldeditadditions/lib/line.lua +++ b/worldeditadditions/lib/line.lua @@ -14,7 +14,6 @@ function worldeditadditions.line(pos1, pos2, thickness, node_name) pos2 = vector.new(pos2) local node_id_replace = minetest.get_content_id(node_name) - print("thickness", thickness, "node_name", node_name, "node_id_replace", node_id_replace) -- Fetch the nodes in the specified area local manip, area = worldedit.manip_helpers.init(pos1, pos2) @@ -37,7 +36,6 @@ function worldeditadditions.line(pos1, pos2, thickness, node_name) local distance = vector.length(vector.subtract(here, closest_on_line)) if distance < thickness then - print("[line] vector", closest_on_line.x, closest_on_line.y, closest_on_line.z, "length", distance) data[area:index(x, y, z)] = node_id_replace counts.replaced = counts.replaced + 1 end diff --git a/worldeditadditions/lib/noise/apply_2d.lua b/worldeditadditions/lib/noise/apply_2d.lua index 081b668..4ac888a 100644 --- a/worldeditadditions/lib/noise/apply_2d.lua +++ b/worldeditadditions/lib/noise/apply_2d.lua @@ -16,14 +16,12 @@ function worldeditadditions.noise.apply_2d(heightmap, noise, heightmap_size, pos local region_height = pos2.y - pos1.y - print("NOISE\n") - worldeditadditions.format.array_2d(noise, heightmap_size.x) + -- 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 @@ -35,15 +33,12 @@ function worldeditadditions.noise.apply_2d(heightmap, noise, heightmap_size, pos elseif height then -- Rescale from 0 - 1 to -1 - +1 local rescaled = (noise[i] * 2) - 1 - -- 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/make_2d.lua b/worldeditadditions/lib/noise/make_2d.lua index addea4b..9d07570 100644 --- a/worldeditadditions/lib/noise/make_2d.lua +++ b/worldeditadditions/lib/noise/make_2d.lua @@ -13,8 +13,6 @@ local wea = worldeditadditions function worldeditadditions.noise.make_2d(size, start_pos, params) local result = {} - 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 @@ -33,7 +31,6 @@ function worldeditadditions.noise.make_2d(size, start_pos, params) 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 diff --git a/worldeditadditions/lib/noise/run2d.lua b/worldeditadditions/lib/noise/run2d.lua index 3a14d15..d285a3b 100644 --- a/worldeditadditions/lib/noise/run2d.lua +++ b/worldeditadditions/lib/noise/run2d.lua @@ -17,9 +17,9 @@ function worldeditadditions.noise.run2d(pos1, pos2, noise_params) -- pos2 will always have the highest co-ordinates now -- Fill in the default params - print("DEBUG noise_params_custom ", wea.format.map(noise_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])) + -- 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) @@ -46,7 +46,6 @@ function worldeditadditions.noise.run2d(pos1, pos2, noise_params) pos1, pos2, noise_params[1].apply ) - print("RETURNED apply_2d success", success, "message", message) if not success then return success, message end diff --git a/worldeditadditions/lib/scale_down.lua b/worldeditadditions/lib/scale_down.lua index 7cbc14f..7bc95fc 100644 --- a/worldeditadditions/lib/scale_down.lua +++ b/worldeditadditions/lib/scale_down.lua @@ -25,7 +25,6 @@ function worldeditadditions.scale_down(pos1, pos2, scale, anchor) z = math.floor(1 / scale.z) } local size = vector.subtract(pos2, pos1) - print("[DEBUG] scale_down", worldeditadditions.vector.tostring(scale_down), "size", size) if size.x < scale_down.x or size.y < scale_down.y or size.z < scale.z then return false, "Error: Area isn't big enough to apply scale down by "..worldeditadditions.vector.tostring(scale).."." diff --git a/worldeditadditions/utils/parse/map.lua b/worldeditadditions/utils/parse/map.lua index 805d7fd..58e311d 100644 --- a/worldeditadditions/utils/parse/map.lua +++ b/worldeditadditions/utils/parse/map.lua @@ -13,7 +13,6 @@ function worldeditadditions.parse.map(params_text, keywords) local last_key = nil local mode = "KEY" for i, part in ipairs(parts) do - 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) @@ -27,7 +26,6 @@ function worldeditadditions.parse.map(params_text, keywords) last_key = part -- Keyword support if wea.table.contains(keywords, last_key) then - print("IS KEYWORD") result[last_key] = true else mode = "VALUE" diff --git a/worldeditadditions_commands/commands/extra/saplingaliases.lua b/worldeditadditions_commands/commands/extra/saplingaliases.lua index 5dcb6ac..1c48f57 100644 --- a/worldeditadditions_commands/commands/extra/saplingaliases.lua +++ b/worldeditadditions_commands/commands/extra/saplingaliases.lua @@ -28,7 +28,6 @@ minetest.register_chatcommand("/saplingaliases", { local results = worldeditadditions.registered_nodes_by_group("sapling") table.insert(msg, "Sapling-like nodes:\n") local str = table.concat(results, "\n") - print(str) table.insert(msg, str) else table.insert(msg, "Unknown mode '") diff --git a/worldeditadditions_commands/commands/meta/multi.lua b/worldeditadditions_commands/commands/meta/multi.lua index 81fec34..61ae9b7 100644 --- a/worldeditadditions_commands/commands/meta/multi.lua +++ b/worldeditadditions_commands/commands/meta/multi.lua @@ -45,7 +45,7 @@ minetest.register_chatcommand("/multi", { if not success then return success, commands end for i, command in ipairs(commands) do - print("[DEBUG] i", i, "command: '"..command.."'") + -- print("[DEBUG] i", i, "command: '"..command.."'") local start_time = worldeditadditions.get_ms_time() local found, _, command_name, args = command:find("^([^%s]+)%s(.+)$") @@ -53,7 +53,7 @@ minetest.register_chatcommand("/multi", { -- Things start at 1, not 0 in Lua :-( command_name = worldeditadditions.trim(command_name):sub(2) -- Strip the leading / if not args then args = "" end - print("command_name", command_name) + -- print("command_name", command_name) worldedit.player_notify(name, "#"..i..": "..command) diff --git a/worldeditadditions_commands/commands/noise2d.lua b/worldeditadditions_commands/commands/noise2d.lua index 68e8cee..8ae0973 100644 --- a/worldeditadditions_commands/commands/noise2d.lua +++ b/worldeditadditions_commands/commands/noise2d.lua @@ -18,9 +18,6 @@ worldedit.register_command("noise2d", { }) 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) diff --git a/worldeditadditions_commands/commands/scale.lua b/worldeditadditions_commands/commands/scale.lua index 43044bd..01a3503 100644 --- a/worldeditadditions_commands/commands/scale.lua +++ b/worldeditadditions_commands/commands/scale.lua @@ -102,7 +102,7 @@ worldedit.register_command("scale", { return volume * factor end, func = function(name, scale, anchor) - print("initial scale: "..worldeditadditions.vector.tostring(scale)..", anchor: "..worldeditadditions.vector.tostring(anchor)) + -- print("initial scale: "..worldeditadditions.vector.tostring(scale)..", anchor: "..worldeditadditions.vector.tostring(anchor)) local start_time = worldeditadditions.get_ms_time() From 457f436a09d20f4c6cb724a6b81d41f03b400b95 Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Sun, 4 Jul 2021 16:13:32 +0100 Subject: [PATCH 13/32] Failed implementation of xoshiro256++ becaause Lua doesn't have an integer type :-( :-( :-( :-( :-( :-( :-( --- worldeditadditions/utils/random.lua | 111 +++++++++++++++++++++++ worldeditadditions/utils/random_test.lua | 9 ++ 2 files changed, 120 insertions(+) create mode 100644 worldeditadditions/utils/random.lua create mode 100644 worldeditadditions/utils/random_test.lua diff --git a/worldeditadditions/utils/random.lua b/worldeditadditions/utils/random.lua new file mode 100644 index 0000000..8860934 --- /dev/null +++ b/worldeditadditions/utils/random.lua @@ -0,0 +1,111 @@ +--- Implementation of the xoshiro++ random number generation algorithm. +-- Ported from C by Starbeamrainbowlabs +-- @ref https://en.wikipedia.org/wiki/Xorshift#xoshiro_and_xoroshiro +-- @class +local Xoshiro = {} +Xoshiro.__index = Xoshiro + +--- Creates a new Xoshiro instance. +-- @returns Xoshiro The new Xoshiro instance. +function Xoshiro.new(seed) + seed = seed or 4898416254654 + local result = { + state = { + 34862381629 ^ seed, + 87782734023 ^ seed, + 59217486042 ^ seed, + 68724585416 ^ seed + } + } + setmetatable(result, Xoshiro) + return result +end + +--- Polyfill for the bitshift left operator that's missing in Lua. +-- @source https://ebens.me/post/simulate-bitwise-shift-operators-in-lua/ +-- @param value number The number to bitshift left. +-- @param places number The number of bits to shift it by to the left. +function Xoshiro._bitshift_left(value, places) + return value * 2 ^ places +end + +--- Polyfill for the bitshift right operator that's missing in Lua. +-- @source https://ebens.me/post/simulate-bitwise-shift-operators-in-lua/ +-- @param value number The number to bitshift right. +-- @param places number The number of bits to shift it by to the right. +function Xoshiro._bitshift_right(value, places) + return math.floor(value / 2 ^ places) +end + +-- Bitwise OR polyfill. +-- The source also has polyfills for bitwise AND, NOT, and XOR. +-- @source https://stackoverflow.com/a/25594410/1460422 +-- @param a number The left-hand argument to the bitwise or operation. +-- @param b number The right-hand argument to the bitwise or operation. +-- @returns number The result of the bitwise or operation. +function Xoshiro._bitwise_or(a,b) + local p,c=1,0 + while a+b>0 do + local ra,rb=a%2,b%2 + if ra+rb>0 then c=c+p end + a,b,p=(a-ra)/2,(b-rb)/2,p*2 + end + return c +end + +-- Bitwise XOR polyfill. +-- The source also has polyfills for bitwise AND, OR, and NOT. +-- @source https://stackoverflow.com/a/25594410/1460422 +-- @param a number The left-hand argument to the bitwise xor operation. +-- @param b number The right-hand argument to the bitwise xor operation. +-- @returns number The result of the bitwise xor operation. +function Xoshiro._bitwise_xor(a,b) + local p,c=1,0 + while a>0 and b>0 do + local ra,rb=a%2,b%2 + if ra~=rb then c=c+p end + a,b,p=(a-ra)/2,(b-rb)/2,p*2 + end + if a0 do + local ra=a%2 + if ra>0 then c=c+p end + a,p=(a-ra)/2,p*2 + end + return c +end + + +function Xoshiro._rol64(x, k) + return Xoshiro._bitwise_or( + Xoshiro._bitshift_left(x, k), + Xoshiro._bitshift_right(x, 64 - k) + ) +end + +function Xoshiro.random_raw(self) + local result = self.state[1] + self.state[4] + local t = Xoshiro._bitshift_right(self.state[2], 17) + + self.state[3] = Xoshiro._bitwise_xor(self.state[3], self.state[1]) + self.state[4] = Xoshiro._bitwise_xor(self.state[4], self.state[2]) + self.state[2] = Xoshiro._bitwise_xor(self.state[2], self.state[3]) + self.state[1] = Xoshiro._bitwise_xor(self.state[1], self.state[4]) + + self.state[3] = Xoshiro._bitwise_xor(self.state[3], t) + self.state[4] = Xoshiro._rol64(self.state[4], 45) + + return result +end + +function Xoshiro.random(self) + return self:random_raw() +end + +print("_G", _G) + +for key,value in pairs(_G) do + print(key, "→", value) +end + +return Xoshiro diff --git a/worldeditadditions/utils/random_test.lua b/worldeditadditions/utils/random_test.lua new file mode 100644 index 0000000..ec7b30c --- /dev/null +++ b/worldeditadditions/utils/random_test.lua @@ -0,0 +1,9 @@ +Xoshiro = require("random") + +rand = Xoshiro.new() + +print(rand) + +for i=1,25 do + print(i, "→", rand:random()) +end From 30f7927af45651ffd1229770daefedb395adc6b1 Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Sun, 4 Jul 2021 16:13:55 +0100 Subject: [PATCH 14/32] Delete failed test :-( :-( :-( :-( :-( :-( :-( :-( :-( :-( :-( --- worldeditadditions/utils/random.lua | 111 ----------------------- worldeditadditions/utils/random_test.lua | 9 -- 2 files changed, 120 deletions(-) delete mode 100644 worldeditadditions/utils/random.lua delete mode 100644 worldeditadditions/utils/random_test.lua diff --git a/worldeditadditions/utils/random.lua b/worldeditadditions/utils/random.lua deleted file mode 100644 index 8860934..0000000 --- a/worldeditadditions/utils/random.lua +++ /dev/null @@ -1,111 +0,0 @@ ---- Implementation of the xoshiro++ random number generation algorithm. --- Ported from C by Starbeamrainbowlabs --- @ref https://en.wikipedia.org/wiki/Xorshift#xoshiro_and_xoroshiro --- @class -local Xoshiro = {} -Xoshiro.__index = Xoshiro - ---- Creates a new Xoshiro instance. --- @returns Xoshiro The new Xoshiro instance. -function Xoshiro.new(seed) - seed = seed or 4898416254654 - local result = { - state = { - 34862381629 ^ seed, - 87782734023 ^ seed, - 59217486042 ^ seed, - 68724585416 ^ seed - } - } - setmetatable(result, Xoshiro) - return result -end - ---- Polyfill for the bitshift left operator that's missing in Lua. --- @source https://ebens.me/post/simulate-bitwise-shift-operators-in-lua/ --- @param value number The number to bitshift left. --- @param places number The number of bits to shift it by to the left. -function Xoshiro._bitshift_left(value, places) - return value * 2 ^ places -end - ---- Polyfill for the bitshift right operator that's missing in Lua. --- @source https://ebens.me/post/simulate-bitwise-shift-operators-in-lua/ --- @param value number The number to bitshift right. --- @param places number The number of bits to shift it by to the right. -function Xoshiro._bitshift_right(value, places) - return math.floor(value / 2 ^ places) -end - --- Bitwise OR polyfill. --- The source also has polyfills for bitwise AND, NOT, and XOR. --- @source https://stackoverflow.com/a/25594410/1460422 --- @param a number The left-hand argument to the bitwise or operation. --- @param b number The right-hand argument to the bitwise or operation. --- @returns number The result of the bitwise or operation. -function Xoshiro._bitwise_or(a,b) - local p,c=1,0 - while a+b>0 do - local ra,rb=a%2,b%2 - if ra+rb>0 then c=c+p end - a,b,p=(a-ra)/2,(b-rb)/2,p*2 - end - return c -end - --- Bitwise XOR polyfill. --- The source also has polyfills for bitwise AND, OR, and NOT. --- @source https://stackoverflow.com/a/25594410/1460422 --- @param a number The left-hand argument to the bitwise xor operation. --- @param b number The right-hand argument to the bitwise xor operation. --- @returns number The result of the bitwise xor operation. -function Xoshiro._bitwise_xor(a,b) - local p,c=1,0 - while a>0 and b>0 do - local ra,rb=a%2,b%2 - if ra~=rb then c=c+p end - a,b,p=(a-ra)/2,(b-rb)/2,p*2 - end - if a0 do - local ra=a%2 - if ra>0 then c=c+p end - a,p=(a-ra)/2,p*2 - end - return c -end - - -function Xoshiro._rol64(x, k) - return Xoshiro._bitwise_or( - Xoshiro._bitshift_left(x, k), - Xoshiro._bitshift_right(x, 64 - k) - ) -end - -function Xoshiro.random_raw(self) - local result = self.state[1] + self.state[4] - local t = Xoshiro._bitshift_right(self.state[2], 17) - - self.state[3] = Xoshiro._bitwise_xor(self.state[3], self.state[1]) - self.state[4] = Xoshiro._bitwise_xor(self.state[4], self.state[2]) - self.state[2] = Xoshiro._bitwise_xor(self.state[2], self.state[3]) - self.state[1] = Xoshiro._bitwise_xor(self.state[1], self.state[4]) - - self.state[3] = Xoshiro._bitwise_xor(self.state[3], t) - self.state[4] = Xoshiro._rol64(self.state[4], 45) - - return result -end - -function Xoshiro.random(self) - return self:random_raw() -end - -print("_G", _G) - -for key,value in pairs(_G) do - print(key, "→", value) -end - -return Xoshiro diff --git a/worldeditadditions/utils/random_test.lua b/worldeditadditions/utils/random_test.lua deleted file mode 100644 index ec7b30c..0000000 --- a/worldeditadditions/utils/random_test.lua +++ /dev/null @@ -1,9 +0,0 @@ -Xoshiro = require("random") - -rand = Xoshiro.new() - -print(rand) - -for i=1,25 do - print(i, "→", rand:random()) -end From 8a14d35c0409f1a812358484522d4bfc2639e5af Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Sun, 4 Jul 2021 16:14:48 +0100 Subject: [PATCH 15/32] noise2d: add comment --- worldeditadditions/lib/noise/engines/init.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/worldeditadditions/lib/noise/engines/init.lua b/worldeditadditions/lib/noise/engines/init.lua index 1f819ae..198b11e 100644 --- a/worldeditadditions/lib/noise/engines/init.lua +++ b/worldeditadditions/lib/noise/engines/init.lua @@ -4,4 +4,6 @@ local wea = worldeditadditions return { Perlin = dofile(wea.modpath.."/lib/noise/engines/perlin.lua"), Sin = dofile(wea.modpath.."/lib/noise/engines/sin.lua") + + -- TODO: Follow https://www.redblobgames.com/articles/noise/introduction.html and implement different colours of noise (*especially* red and pink noise) } From 40173b264707e28f48e1364e696d9c1bd40d9828 Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Sun, 4 Jul 2021 23:30:29 +0100 Subject: [PATCH 16/32] Vector3: default to 0 for unspecified args --- worldeditadditions/utils/vector3.lua | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/worldeditadditions/utils/vector3.lua b/worldeditadditions/utils/vector3.lua index 4acc92f..252b49d 100644 --- a/worldeditadditions/utils/vector3.lua +++ b/worldeditadditions/utils/vector3.lua @@ -8,15 +8,9 @@ Vector3.__index = Vector3 -- @param y number The y co-ordinate value. -- @param z number The z co-ordinate value. function Vector3.new(x, y, z) - if type(x) ~= "number" then - error("Error: Expected number for the value of x, but received argument of type "..type(x)..".") - end - if type(y) ~= "number" then - error("Error: Expected number for the value of y, but received argument of type "..type(y)..".") - end - if type(z) ~= "number" then - error("Error: Expected number for the value of z, but received argument of type "..type(z)..".") - end + x = x or 0 + y = y or 0 + z = z or 0 local result = { x = x, From a42e296f4e9950a6d3b47392348934f9984bcddf Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Tue, 6 Jul 2021 01:08:36 +0100 Subject: [PATCH 17/32] Reference: Document //noise2d --- Chat-Command-Reference.md | 47 +++++++++++++++++++ .../lib/noise/params_apply_default.lua | 2 +- 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/Chat-Command-Reference.md b/Chat-Command-Reference.md index 55ccccb..abd0cba 100644 --- a/Chat-Command-Reference.md +++ b/Chat-Command-Reference.md @@ -488,6 +488,53 @@ Usage examples: ``` +## `//noise2d [ []] [ []] ...]` +Applies 2D noise to the terrain in the defined region. Like `//erode`, this command accepts a number of different key-value parameters and provides a number of different underlying algorithms. + +Parameter | Type | Default Value | Description +------------|-----------|---------------|----------------------- +algorithm | `string` | perlin | The 2D noise algorithm to apply - see below. +apply | `string|integer` | 5 | How to apply the noise to the terrain - see below. +scalex | `float` | 1 | The scale of the noise on the x axis. +scaley | `float` | 1 | The scale of the noise on the y axis. +scalez | `float` | 1 | The scale of the noise on the z axis. +offsetx | `float` | 1 | The offset to add to the x axis before calling the noise function. +offsety | `float` | 0 | The offset to add to the y axis before calling the noise function. +offsetz | `float` | 0 | The offset to add to the z axis before calling the noise function. +exponent | `float` | 0 | Raise the generated noise value (with a range of 0 to 1) to this power. Generally results in sharper peaks. +multiply | `float` | 1 | Multiply the generated noise value by this number +add | `float` | 0 | Add this number to the generated noise value. + + +Different values of the `apply` parameter result in the generated noise values being applied in different ways: + + - An integer indicates that the noise should be rescaled to a given amplitude (equal parts of the range above and below 0) before being added to the terrain heightmap.` + - 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. + +The following algorithms are currently available: + +Algorithm | Description +------------|-------------------------- +`perlin` | Perlin noise. Functional, but currently contains artefacts I'm having difficulty tracking down. +`sin` | A sine wave created with `math.sin()`. + +When specifying algorithm names, the `algorithm` parameter name is optional. For example, the following are both equivalent: + +``` +//noise2d offsetx 4 perlin scale 0.2 +//noise2d offsetx 4 algorithm perlin scale 0.2 +``` + +Example invocations: + +``` +//noise2d sin scale 0.5 +//noise2d offsetx 20 perlin +//noise2d sin exponent 4 +``` + + ## `//count` Counts all the nodes in the defined region and returns the result along with calculated percentages (note that if the chat window used a monospace font, the returned result would be a perfect table. If someone has a ~~hack~~ solution to make the columns line up neatly, please [open an issue](https://github.com/sbrl/Minetest-WorldEditAdditions/issues/new) :D) diff --git a/worldeditadditions/lib/noise/params_apply_default.lua b/worldeditadditions/lib/noise/params_apply_default.lua index f77d322..0ae6c19 100644 --- a/worldeditadditions/lib/noise/params_apply_default.lua +++ b/worldeditadditions/lib/noise/params_apply_default.lua @@ -10,7 +10,7 @@ function worldeditadditions.noise.params_apply_default(params) -- different effects: -- - 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. + -- - A string in the form of digits followed, then the noise will is remapped from the range 0 - 1 to the range -1 - +1 and multiplied by this number / 2, and then for each pixel in the heightmap the corresponding noise value will be added to it. apply = 5, -- The backend noise algorithm to use algorithm = "perlin", From b55f7018f91c807719da1d428e34170ef9070c57 Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Tue, 6 Jul 2021 02:47:45 +0100 Subject: [PATCH 18/32] Docs: add syntax highlighting to command invocations --- .docs/.eleventy.js | 22 +++++ .docs/css.njk | 6 ++ .docs/css/prism-custom.css | 159 ++++++++++++++++++++++++++++++++++++ .docs/css/theme.css | 4 + .docs/lib/parse_sections.js | 13 +++ .docs/package-lock.json | 70 +++++++++++++++- .docs/package.json | 4 +- Chat-Command-Reference.md | 126 ++++++++++++++-------------- 8 files changed, 339 insertions(+), 65 deletions(-) create mode 100644 .docs/css/prism-custom.css diff --git a/.docs/.eleventy.js b/.docs/.eleventy.js index 93776bc..3142200 100644 --- a/.docs/.eleventy.js +++ b/.docs/.eleventy.js @@ -1,9 +1,13 @@ +const os = require("os"); const fs = require("fs"); const path = require("path"); const htmlentities = require("html-entities"); +const phin = require("phin"); + const Image = require("@11ty/eleventy-img"); + var nextid = 0; const image_filename_format = (_id, src, width, format, _options) => { @@ -77,7 +81,25 @@ async function shortcode_gallerybox(content, src, idthis, idprev, idnext) { `; } +async function fetch(url) { + let package = JSON.parse(await fs.promises.readFile( + path.join(__dirname, "package.json"), "utf8" + )); + + return (await phin({ + url, + headers: { + "user-agent": `WorldEditAdditionsStaticBuilder/${package.version} (Node.js/${process.version}; ${os.platform()} ${os.arch()}) eleventy/${package.devDependencies["@11ty/eleventy"].replace(/\^/, "")}` + }, + followRedirects: true, + parse: "string" + })).body; +} + module.exports = function(eleventyConfig) { + + eleventyConfig.addAsyncShortcode("fetch", fetch); + // eleventyConfig.addPassthroughCopy("images"); // eleventyConfig.addPassthroughCopy("css"); eleventyConfig.addShortcode("image", shortcode_image); diff --git a/.docs/css.njk b/.docs/css.njk index 0ce986a..86a5920 100644 --- a/.docs/css.njk +++ b/.docs/css.njk @@ -5,3 +5,9 @@ permalink: theme.css {% include "css/theme.css" %} {% include "css/gallerybox.css" %} {% include "css/smallscreens.css" %} +{% include "css/prism-custom.css" %} + + +{# {% fetch "https://unpkg.com/prismjs/themes/prism-okaidia.css" %} #} +{# {% fetch "https://raw.githubusercontent.com/PrismJS/prism-themes/master/themes/prism-shades-of-purple.css" %} #} +{# {% fetch "https://raw.githubusercontent.com/PrismJS/prism-themes/master/themes/prism-material-light.css" %} #} diff --git a/.docs/css/prism-custom.css b/.docs/css/prism-custom.css new file mode 100644 index 0000000..c112af3 --- /dev/null +++ b/.docs/css/prism-custom.css @@ -0,0 +1,159 @@ +/* Ref https://github.com/jGleitz/markdown-it-prism/issues/1; we don't get have + line numbers because we don't have 1 span per line :-/*/ + +pre[class*='language-'] { + counter-reset: line-numbers; + border: 0; +} +span[class*='language-'] { + counter-increment: line-numbers; +} +span[class*='language-']::before { + content: counter(line-numbers); +} + + + + +/* PrismJS 1.16.0 +https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript&plugins=line-highlight+line-numbers+autolinker+data-uri-highlight+toolbar+previewers+autoloader+command-line+normalize-whitespace+show-language */ +/** + * prism.js default theme for JavaScript, CSS and HTML + * Based on dabblet (http://dabblet.com) + * @author Lea Verou + */ + +code[class*="language-"], +pre[class*="language-"] { + color: black; + background: none; + text-shadow: 0 1px white; + font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; + font-size: 1em; + text-align: left; + white-space: pre; + word-spacing: normal; + word-break: normal; + word-wrap: normal; + line-height: 1.5; + + -moz-tab-size: 4; + -o-tab-size: 4; + tab-size: 4; + + -webkit-hyphens: none; + -moz-hyphens: none; + -ms-hyphens: none; + hyphens: none; +} + +pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection, +code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection { + text-shadow: none; + background: #b3d4fc; +} + +pre[class*="language-"]::selection, pre[class*="language-"] ::selection, +code[class*="language-"]::selection, code[class*="language-"] ::selection { + text-shadow: none; + background: #b3d4fc; +} + +@media print { + code[class*="language-"], + pre[class*="language-"] { + text-shadow: none; + } +} + +/* Code blocks */ +pre[class*="language-"] { + padding: 1em; + margin: .5em 0; + overflow: auto; +} + +:not(pre) > code[class*="language-"], +pre[class*="language-"] { + background: #f5f2f0; +} + +/* Inline code */ +:not(pre) > code[class*="language-"] { + padding: .1em; + border-radius: .3em; + white-space: normal; +} + +.token.comment, +.token.prolog, +.token.doctype, +.token.cdata { + color: slategray; +} + +.token.punctuation { + color: #999; +} + +.namespace { + opacity: .7; +} + +.token.property, +.token.tag, +.token.boolean, +.token.number, +.token.constant, +.token.symbol, +.token.deleted { + color: #905; +} + +.token.selector, +.token.attr-name, +.token.string, +.token.char, +.token.builtin, +.token.inserted { + color: #690; +} + +.token.operator, +.token.entity, +.token.url, +.language-css .token.string, +.style .token.string { + color: #9a6e3a; + background: hsla(0, 0%, 100%, .5); +} + +.token.atrule, +.token.attr-value, +.token.keyword { + color: #07a; +} + +.token.function, +.token.class-name { + /* color: #DD4A68; */ + color: hsl(347, 87%, 44%); +} + +.token.regex, +.token.important, +.token.variable { + color: #e90; +} + +.token.important, +.token.bold { + font-weight: bold; +} +.token.italic { + font-style: italic; +} + +.token.entity { + cursor: help; +} diff --git a/.docs/css/theme.css b/.docs/css/theme.css index 636724f..7868e09 100644 --- a/.docs/css/theme.css +++ b/.docs/css/theme.css @@ -20,6 +20,7 @@ --bg-transcluscent-slight: rgba(255, 255, 255, 0.1); --bg-transcluscent: rgba(255, 255, 255, 0.85); --bg-transcluscent-alt: hsla(226, 59%, 38%, 0.8); + --bg-transcluscent-alt-vdark: hsla(226, 59%, 8%, 0.8); --bg-transcluscent-alt-slight: hsla(196, 91%, 62%, 0.23); /* --text-main: #3F57B4; */ @@ -173,6 +174,9 @@ code { border-radius: 0.25em; padding: 0.15em; } +/* pre.language-weacmd { + background: var(--bg-transcluscent-alt-vdark); +} */ label { font-weight: bold; diff --git a/.docs/lib/parse_sections.js b/.docs/lib/parse_sections.js index eaf807c..4e5789d 100644 --- a/.docs/lib/parse_sections.js +++ b/.docs/lib/parse_sections.js @@ -3,6 +3,19 @@ const markdown = require("markdown-it")({ xhtmlOut: true }); +const markdown_prism = require("markdown-it-prism"); +markdown.use(markdown_prism, { + init: (Prism) => { + Prism.languages.weacmd = { + "string": /\<[^>]+?\>/, + "function": /^(?:\/\/\S+)\b/m, + "number": /\b0x[\da-f]+\b|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[+-]?\d+)?%?/i, + "operator": /[<>:=\[\]|{}]/, + "keyword": /\b(?:[-+]?[zyx])\b/ + } + } +}); + module.exports = function parse_sections(source) { const lines = source.split(/\r?\n/gi); const result = []; diff --git a/.docs/package-lock.json b/.docs/package-lock.json index 845642d..3bc23b6 100644 --- a/.docs/package-lock.json +++ b/.docs/package-lock.json @@ -12,7 +12,9 @@ "@11ty/eleventy": "^0.12.1", "@11ty/eleventy-img": "^0.9.0", "html-entities": "^2.3.2", - "htmlentities": "^1.0.0" + "htmlentities": "^1.0.0", + "markdown-it-prism": "^2.1.8", + "phin": "^3.6.0" } }, "node_modules/@11ty/dependency-tree": { @@ -697,6 +699,12 @@ "node": ">=6" } }, + "node_modules/centra": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/centra/-/centra-2.4.2.tgz", + "integrity": "sha512-f1RaP0V1HqVNEXfLfjNBthB2yy3KnSGnPCnOPCFLUk9e/Z4rNJ8nBaJNnghflnp88mi1IT8mfmW+HlMS1/H+bg==", + "dev": true + }, "node_modules/chalk": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", @@ -2479,6 +2487,18 @@ "markdown-it": "bin/markdown-it.js" } }, + "node_modules/markdown-it-prism": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/markdown-it-prism/-/markdown-it-prism-2.1.8.tgz", + "integrity": "sha512-PBiqlX3zGPQnOk7q7TkeveQfXlqzhjfHg2zSwntDNauYY7KFhg2FzO6O+1boillQptEBcIaEAO9gwKq/tXGHUQ==", + "dev": true, + "dependencies": { + "prismjs": "1.24.1" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/maximatch": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/maximatch/-/maximatch-0.1.0.tgz", @@ -3022,6 +3042,18 @@ "node": ">=0.10.0" } }, + "node_modules/phin": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/phin/-/phin-3.6.0.tgz", + "integrity": "sha512-nYY4Qh/yGCoxcwOAS2UfCM8+nVJcbI4f9NC4M4zPAsuswnIIS2aB14uYAbvdxP/4DqzXfrpadT2WQOx9mLDyTA==", + "dev": true, + "dependencies": { + "centra": "^2.4.2" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/picomatch": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", @@ -3144,6 +3176,12 @@ "node": ">=0.10.0" } }, + "node_modules/prismjs": { + "version": "1.24.1", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.24.1.tgz", + "integrity": "sha512-mNPsedLuk90RVJioIky8ANZEwYm5w9LcvCXrxHlwf4fNVSn8jEipMybMkWUyyF0JhnC+C4VcOVSBuHRKs1L5Ow==", + "dev": true + }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -5178,6 +5216,12 @@ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true }, + "centra": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/centra/-/centra-2.4.2.tgz", + "integrity": "sha512-f1RaP0V1HqVNEXfLfjNBthB2yy3KnSGnPCnOPCFLUk9e/Z4rNJ8nBaJNnghflnp88mi1IT8mfmW+HlMS1/H+bg==", + "dev": true + }, "chalk": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", @@ -6604,6 +6648,15 @@ "uc.micro": "^1.0.5" } }, + "markdown-it-prism": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/markdown-it-prism/-/markdown-it-prism-2.1.8.tgz", + "integrity": "sha512-PBiqlX3zGPQnOk7q7TkeveQfXlqzhjfHg2zSwntDNauYY7KFhg2FzO6O+1boillQptEBcIaEAO9gwKq/tXGHUQ==", + "dev": true, + "requires": { + "prismjs": "1.24.1" + } + }, "maximatch": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/maximatch/-/maximatch-0.1.0.tgz", @@ -7011,6 +7064,15 @@ "integrity": "sha1-v8zcjfWxLcUsi0PsONGNcsBLqW0=", "dev": true }, + "phin": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/phin/-/phin-3.6.0.tgz", + "integrity": "sha512-nYY4Qh/yGCoxcwOAS2UfCM8+nVJcbI4f9NC4M4zPAsuswnIIS2aB14uYAbvdxP/4DqzXfrpadT2WQOx9mLDyTA==", + "dev": true, + "requires": { + "centra": "^2.4.2" + } + }, "picomatch": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", @@ -7099,6 +7161,12 @@ "parse-ms": "^0.1.0" } }, + "prismjs": { + "version": "1.24.1", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.24.1.tgz", + "integrity": "sha512-mNPsedLuk90RVJioIky8ANZEwYm5w9LcvCXrxHlwf4fNVSn8jEipMybMkWUyyF0JhnC+C4VcOVSBuHRKs1L5Ow==", + "dev": true + }, "process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", diff --git a/.docs/package.json b/.docs/package.json index a67a1ec..27ec9cc 100644 --- a/.docs/package.json +++ b/.docs/package.json @@ -23,6 +23,8 @@ "@11ty/eleventy": "^0.12.1", "@11ty/eleventy-img": "^0.9.0", "html-entities": "^2.3.2", - "htmlentities": "^1.0.0" + "htmlentities": "^1.0.0", + "markdown-it-prism": "^2.1.8", + "phin": "^3.6.0" } } diff --git a/Chat-Command-Reference.md b/Chat-Command-Reference.md index abd0cba..33edc42 100644 --- a/Chat-Command-Reference.md +++ b/Chat-Command-Reference.md @@ -15,7 +15,7 @@ Other useful links: ## `//floodfill [ []]` Floods all connected nodes of the same type starting at _pos1_ with (which defaults to `water_source`), in a sphere with a radius of (which defaults to 50). -``` +```weacmd //floodfill //floodfill water_source 50 //floodfill glass 25 @@ -30,7 +30,7 @@ Note that all-air columns are skipped - so if you experience issues with it not Note also that columns without any air nodes in them at all are also skipped, so try `//expand y 1` to add an extra layer to your defined region. -``` +```weacmd //overlay grass //overlay glass //overlay grass_with_dirt @@ -45,7 +45,7 @@ Finds the first non-air node in each column and works downwards, replacing non-a The list of nodes has a form similar to that of a chance list you might find in `//replacemix`, `//overlay`, or `//mix` - see the examples below. If the numberr of layers isn't specified, `1` is assumed (i.e. a single layer). -``` +```weacmd //layers dirt_with_grass dirt 3 //layers sand 5 sandstone 4 desert_stone 2 //layers brick stone 3 @@ -74,7 +74,7 @@ If you like, you can also reference the full name of a sapling node instead. The For example, the first 2 examples below are functionally equivalent. -``` +```weacmd //forest aspen //forest default:aspen_sapling //forest 2 oak 3 aspen pine @@ -92,7 +92,7 @@ Mode | Description `aliases` | The default. Lists all the currently registered sapling aliases in alphabetical order. `all_saplings` | Spins through all the nodes currently registered in Minetest, and lists all the nodes that have the `sapling` group. -``` +```weacmd //saplingaliases //saplingaliases all_saplings //saplingaliases aliases @@ -104,7 +104,7 @@ Fills in all airlike nodes beneath non airlike nodes, which gives the effect of Note that the *entire* cave you want filling must be selected, as `//fillcaves` only operates within the defined region (ref #50). -``` +```weacmd //fillcaves //fillcaves dirt //fillcaves brick @@ -114,7 +114,7 @@ Note that the *entire* cave you want filling must be selected, as `//fillcaves` ## `//ellipsoid [h[ollow]]` Creates a solid ellipsoid at position 1 with the radius `(rx, ry, rz)`. -``` +```weacmd //ellipsoid 10 5 15 ice //ellipsoid 3 5 10 dirt //ellipsoid 20 10 40 air @@ -125,7 +125,7 @@ Creates a solid ellipsoid at position 1 with the radius `(rx, ry, rz)`. ## `//hollowellipsoid ` Creates a hollow ellipsoid at position 1 with the radius `(rx, ry, rz)`. Works the same way as `//ellipsoid` does. -``` +```weacmd //hollowellipsoid 10 5 15 glass //hollowellipsoid 21 11 41 stone ``` @@ -135,7 +135,7 @@ Creates a solid torus at position 1 with the specified major and minor radii. Th The optional axes sets the axes upon which the torus will lay flat. Possible values: `xy` (the default), `xz`, `yz`. A single axis may also be specified (i.e. `x`, `y`, or `z`) - this will be interpreted as the axis that runs through the hole in the middle of the torus. -``` +```weacmd //torus 15 5 stone //torus 5 3 meselamp //torus 10 6 sandstone xz @@ -146,7 +146,7 @@ The optional axes sets the axes upon which the torus will lay flat. Possible val ## `//hollowtorus []` Creates a hollow torus at position 1 with the radius major and minor radii. Works the same way as `//torus` does. -``` +```weacmd //hollowtorus 10 5 glass //hollowtorus 21 11 stone //hollowtorus 18 6 dirt xz @@ -159,7 +159,7 @@ The radius can be thought of as the thickness of the line, and is defined as the Floating-point values are fully supported for the radius. -``` +```weacmd //line //line stone //line sandstone 3 @@ -171,7 +171,7 @@ Replaces nodes inside the defined region with air, but leaving a given number of Note that all air-like nodes are also left alone. -``` +```weacmd //hollow //hollow 2 ``` @@ -191,7 +191,7 @@ Note also that since WorldEditAdditions v1.10, the seed doesn't have to be a num The last example below shows how to set the path length and width: -``` +```weacmd //maze ice //maze stone 2 1 1234 //maze dirt 4 2 56789 @@ -205,7 +205,7 @@ The optional `path_depth` parameter defaults to `1` and allows customisation of To get a better look at the generated maze, try inverting it like so: -``` +```weacmd //maze3d stone //replace air dirt //replace stone air @@ -213,7 +213,7 @@ To get a better look at the generated maze, try inverting it like so: Additional examples: -``` +```weacmd //maze3d glass //maze3d bush_leaves 2 1 1 12345 //maze3d dirt 4 2 2 @@ -235,7 +235,7 @@ For example, a chance number of 2 would mean a 50% chance that any given eligibl Since WorldEditAdditions v1.12, a percentage chance is also supported. This is denoted by suffixing a number with a percent sign (e.g. `//bonemeal 1 25%`). -``` +```weacmd //bonemeal //bonemeal 3 25 //bonemeal 4 @@ -247,7 +247,7 @@ Since WorldEditAdditions v1.12, a percentage chance is also supported. This is d ## `//walls ` Creates vertical walls of `` around the inside edges of the defined region. -``` +```weacmd //walls dirt //walls stone //walls goldblock @@ -276,25 +276,25 @@ With this in mind, there are 3 forms that you can tell `//scale` how you want to ### Single Axis If you just need to scale a single axis, you can tell `//scale` that like so: -``` +```weacmd //scale ``` To give a concrete example: -``` +```weacmd //scale y 2 ``` The above will scale the defined region in the positive y direction by 2 times, doubling the height. If you want to scale in the opposite direction, do this: -``` +```weacmd //scale -y 2 ``` This will scale in the _negative_ y direction by 2 times (again, doubling the height). Some more examples: -``` +```weacmd //scale z 50% //scale -x 1/3 ``` @@ -302,7 +302,7 @@ This will scale in the _negative_ y direction by 2 times (again, doubling the he ### All axes To scale on all axes at once, `//scale` takes the shortcut syntax of specifying a single scale factor: -``` +```weacmd //scale 2 //scale 200% ``` @@ -312,13 +312,13 @@ Both of the above will scale the defined region up by 2 times in all directions. ### Multiple scale factors If you want to specify different scale factors for difference axes, then `//scale` also supports a third syntax. Here's an example: -``` +```weacmd //scale 2 3 4 ``` This will scale the defined region by 2x in the positive x, 3x in the positive y, and 4x in the positive z. As these are all scale factors, we can also use the syntax described above to scale up and down in the same operation: -``` +```weacmd //scale 50% 2 1/4 ``` @@ -326,7 +326,7 @@ This will first scale in the positive y by 2x. Once that operation is completed, If you want to change the anchor point of the scaling operation too, `//scale` supports a final syntax like so: -``` +```weacmd //scale 50% 2 1/4 1 -1 1 ``` @@ -340,7 +340,7 @@ Replaces a given node with a random mix of other nodes. Functions like `//mix`. This command is best explained with examples: -``` +```weacmd //replacemix dirt stone ``` @@ -348,19 +348,19 @@ The above functions just like `//replace` - nothing special going on here. It re Let's make it more interesting: -``` +```weacmd //replacemix dirt 5 stone ``` The above replaces 1 in every 5 `dirt` nodes with `stone`. Let's get even fancier: -``` +```weacmd //replacemix stone stone_with_diamond stone_with_gold ``` The above replaces `stone` nodes with a random mix of `stone_with_diamond` and `stone_with_gold` nodes. But wait - there's more! -``` +```weacmd //replacemix stone stone_with_diamond stone_with_gold 4 ``` @@ -368,7 +368,7 @@ The above replaces `stone` nodes with a random mix of `stone_with_diamond` and ` If we wanted to put all of the above features together into a single command, then we might do this: -``` +```weacmd //replacemix dirt 3 sandstone 10 dry_dirt cobble 2 ``` @@ -376,7 +376,7 @@ The above replaces 1 in 3 `dirt` nodes with a mix of `sandstone`, `dry_dirt`, an Since WorldEditAdditions v1.12, you can also use percentages: -``` +```weacmd //replacemix dirt 33% sandstone 75% dry_dirt 10% cobble 15% ``` @@ -384,7 +384,7 @@ Note though that the percentages are internally converted to a 1-in-N chance and Here are all the above examples together: -``` +```weacmd //replacemix dirt stone //replacemix dirt 5 stone //replacemix stone stone_with_diamond stone_with_gold @@ -411,7 +411,7 @@ The width and height (if specified) refer to the dimensions of the kernel and mu The sigma value is only applicable to the `gaussian` kernel, and can be thought of as the 'smoothness' to apply. Greater values result in more smoothing. Default: 2. See the [Gaussian blur](https://en.wikipedia.org/wiki/Gaussian_blur) page on Wikipedia for some pictures showing the effect of the sigma value. -``` +```weacmd //convolve //convolve box 7 //convolve pascal 11,3 @@ -434,7 +434,7 @@ Algorithm | Mode | Description Usage examples: -``` +```weacmd //erode //erode snowballs //erode snowballs count 25000 @@ -461,7 +461,7 @@ noconv | any | n/a | When set to any value, disables to automatic 3x3 gau Usage examples: -``` +```weacmd //erode //erode snowballs //erode snowballs count 50000 @@ -482,7 +482,7 @@ dolower | `boolean` | true | Whether to lower columns in height. If false, Usage examples: -``` +```weacmd //erode river //erode river steps 10 ``` @@ -521,14 +521,14 @@ Algorithm | Description When specifying algorithm names, the `algorithm` parameter name is optional. For example, the following are both equivalent: -``` +```weacmd //noise2d offsetx 4 perlin scale 0.2 //noise2d offsetx 4 algorithm perlin scale 0.2 ``` Example invocations: -``` +```weacmd //noise2d sin scale 0.5 //noise2d offsetx 20 perlin //noise2d sin exponent 4 @@ -540,7 +540,7 @@ Counts all the nodes in the defined region and returns the result along with cal **Note:** The output of `//count` can be rather long sometimes, and Minetest by default only shows the last few lines of chat. Press F10 to show the full chat window that you can then scroll through to inspect the full output. -``` +```weacmd //count ``` @@ -548,13 +548,13 @@ Counts all the nodes in the defined region and returns the result along with cal ## `//basename ` Returns the absolute canonical name of a node, given an alias or partial node name. For example: -``` +```weacmd //basename dirt ``` ...will return `default:dirt`. Uses `worldedit.normalize_nodename(string)` under the hood. -``` +```weacmd //basename stonebrick //basename glass ``` @@ -573,7 +573,7 @@ While other server commands can be executed while a `//subdivide` is running, `/ **Warning:** Once started, this command cannot be stopped without restarting your server! This is the case with all WorldEdit commands, but it's worth a special mention here. -``` +```weacmd //subdivide 10 10 10 set dirt //subdivice 25 25 25 fixlight ``` @@ -582,13 +582,13 @@ While other server commands can be executed while a `//subdivide` is running, `/ ## `//multi .....` Executes multi chat commands in sequence. Intended for _WorldEdit_ commands, but does work with others too. Don't forget a space between commands! -``` +```weacmd //multi //set dirt //shift x 10 //set glass ``` Since WorldEditAdditions v1.12, curly brace syntax has also been introduced to allow nesting of commands: -``` +```weacmd //multi //fixlight {//many 5 //bonemeal 3 100} ``` @@ -596,7 +596,7 @@ This syntax can also be nested arbitrarily in arbitrarily complex combinations, In addition, this also allows for including a double forward slash in the argument list for a command, should you need to do so (e.g. `//multi //example {//bar //baz} //example` will be executed as 3 commands: `/example`, then `/bar` with an argument of `//baz`, then finally `/example`). -``` +```weacmd //multi //1 //2 //shift z -10 //sphere 5 sand //shift z 20 //ellipsoid 5 3 5 ice //multi //1 //hollowtorus 30 5 stone //hollowtorus 20 3 dirt //torus 10 2 dirt_with_grass //multi /time 7:00 //1 //outset h 20 //outset v 5 //overlay dirt_with_grass //1 //2 //sphere 8 air //shift down 1 //floodfill //reset @@ -608,7 +608,7 @@ Executes a single chat command many times in a row. Uses `minetest.after()` to y Note that this isn't necessarily limited to executing WorldEdit / WorldEditAdditions commands. Combine with `//multi` (see above) execute multiple commands at once for even more power and flexibility! -``` +```weacmd //many 10 //bonemeal 3 100 //many 100 //multi //1 //2 //outset 20 //set dirt ``` @@ -617,7 +617,7 @@ Note that this isn't necessarily limited to executing WorldEdit / WorldEditAddit ## `//ellipsoidapply ` Executes the given command, and then clips the result to the largest ellipsoid that will fit inside the defined region. The specified command must obviously take 2 positions - so for example `//set`, `//replacemix`, and `//maze3d` will work, but `//sphere`, `//torus`, and `//floodfill` won't. -``` +```weacmd //ellipsoidapply set dirt //ellipsoidapply maze3d dirt 4 2 2 //ellipsoidapply erode @@ -629,7 +629,7 @@ Executes the given command, and then clips the result to the largest ellipsoid t ## `//scol [ ] ` Short for _select column_. Sets the pos2 at a set distance along 1 axis from pos1. If the axis isn't specified, defaults the direction you are facing. Implementation thanks to @VorTechnix. -``` +```weacmd //scol 10 //scol x 3 ``` @@ -638,7 +638,7 @@ Short for _select column_. Sets the pos2 at a set distance along 1 axis from pos ## `//srect [ []] ` Short for _select rectangle_. Sets the pos2 at a set distance along 2 axes from pos1. If the axes aren't specified, defaults to positive y and the direction you are facing. Implementation thanks to @VorTechnix. -``` +```weacmd //srect x z 10 //srect 3 //srect -z y 25 @@ -648,7 +648,7 @@ Short for _select rectangle_. Sets the pos2 at a set distance along 2 axes from ## `//scube [ [ []]] ` Short for _select cube_. Sets the pos2 at a set distance along 3 axes from pos1. If the axes aren't specified, defaults to positive y, the direction you are facing and the axis to the left of facing. Implementation thanks to @VorTechnix. -``` +```weacmd //scube 5 //scube z a y 12 //scube x z 3 @@ -659,7 +659,7 @@ Short for _select cube_. Sets the pos2 at a set distance along 3 axes from pos1. ## `//scloud <0-6|stop|reset>` Short for _select point cloud_. Sets pos1 and pos2 to include the nodes you punch. Numbers 1-6 designate how many nodes you want to punch before the operation ends. 0 or stop terminate the operation so that any further nodes you punch won't be added to selection. Reset terminates operation if one is running and resets the selection area. -``` +```weacmd //scloud 6 //scloud 5 //scloud stop @@ -669,7 +669,7 @@ Short for _select point cloud_. Sets pos1 and pos2 to include the nodes you punc ## `//scentre` Short for _select center_. Sets pos1 and pos2 to the centre point(s) of the current selection area. 1, 2, 4 or 8 nodes may be selected depending on what parts of the original selection are even in distance. Implementation thanks to @VorTechnix. -``` +```weacmd //scentre ``` @@ -677,7 +677,7 @@ Short for _select center_. Sets pos1 and pos2 to the centre point(s) of the curr ## `//srel [ [ ]]` Short for _select relative_. Sets the pos2 at set distances along 3 axes relative to pos1. If pos1 is not set it will default to the node directly under the player. The axis arguments accept `x, y, z` as well as `up, down, left, right, front, back`. Left, right, front and back are relative to player facing direction. Negative (`-`) can be applied to the axis, the length or both. Implementation thanks to @VorTechnix. -``` +```weacmd //srel front 5 //srel y 12 right -2 //srel left 3 up 5 -front 7 @@ -690,7 +690,7 @@ Short for _selection make_. Modifies existing selection by moving pos2. Allows y Usage examples: -``` +```weacmd //smake odd shrink //smake even avg xz //smake equal grow xy @@ -739,7 +739,7 @@ Short for _selection factor_; alias: `//sfac`. Built specifically for use with ` Usage examples: -``` +```weacmd //sfac grow 5 //sfac avg 3 xy ``` @@ -755,7 +755,7 @@ Value | Description ## `//sstack` Displays the contents of your per-user selection stack. This stack can be pushed to and popped from rather like a stack of plates. See also `//spush` (for pushing to the selection stack) and `//spop` (for popping from the selection stack). -``` +```weacmd //sstack ``` @@ -766,14 +766,14 @@ If the stack is full (currently the limit is set to 100 regions in the stack), t Note that pos2 does _not_ need to be defined in order to use this. if it isn't defined, then a pos2 of `nil` will be pushed onto the stack instead. -``` +```weacmd //spush ``` ## `//spop` Pops a selection off your per-user selection stack and applies it to the currently defined region. If pos2 from the item popped from the stack is nil, then pos2 is explicitly unset. If the stack is empty, this has no effect. -``` +```weacmd //spop ``` @@ -782,7 +782,7 @@ Confirms the execution of a command if it could potentially affect a large numbe -``` +```weacmd //y ``` @@ -791,7 +791,7 @@ Prevents the execution of a command if it could potentially affect a large numbe -``` +```weacmd //n ``` @@ -804,20 +804,20 @@ It functions very similarly to the regular WorldEdit wand, except that it has a ## `//farwand skip_liquid (true|false) | maxdist ` This command helps control the behaviour of the [WorldEditAdditions far wand](#far-wand). Calling it without any arguments shows the current status: -``` +```weacmd //farwand ``` You can decide whether you can select liquids or not like so: -``` +```weacmd //farwand skip_liquid true //farwand skip_liquid false ``` You can change the maximum range with the `maxdist` subcommand: -``` +```weacmd //farwand maxdist 1000 //farwand maxdist 200 //farwand maxdist 9999 From 46c2a02adeda4f27b39df707fa043f71bcc192d8 Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Mon, 12 Jul 2021 02:45:32 +0100 Subject: [PATCH 19/32] //noise2d: add white algorithm; fix nasty bug in apply_2d --- worldeditadditions/lib/noise/apply_2d.lua | 15 +++++++++-- worldeditadditions/lib/noise/engines/init.lua | 5 ++-- worldeditadditions/lib/noise/engines/sin.lua | 1 - .../lib/noise/engines/white.lua | 27 +++++++++++++++++++ worldeditadditions/lib/noise/make_2d.lua | 19 ++++++++++--- .../lib/noise/params_apply_default.lua | 11 +++++--- .../commands/noise2d.lua | 6 ++--- 7 files changed, 67 insertions(+), 17 deletions(-) create mode 100644 worldeditadditions/lib/noise/engines/white.lua diff --git a/worldeditadditions/lib/noise/apply_2d.lua b/worldeditadditions/lib/noise/apply_2d.lua index 4ac888a..d0cd7eb 100644 --- a/worldeditadditions/lib/noise/apply_2d.lua +++ b/worldeditadditions/lib/noise/apply_2d.lua @@ -16,11 +16,13 @@ function worldeditadditions.noise.apply_2d(heightmap, noise, heightmap_size, pos local region_height = pos2.y - pos1.y - -- print("NOISE\n") - -- worldeditadditions.format.array_2d(noise, heightmap_size.x) + + print("NOISE APPLY_2D\n") + worldeditadditions.format.array_2d(noise, heightmap_size.x) local height = tonumber(apply_mode) + print("DEBUG apply_mode", apply_mode, "as height", height) for z = heightmap_size.z - 1, 0, -1 do for x = heightmap_size.x - 1, 0, -1 do @@ -46,5 +48,14 @@ function worldeditadditions.noise.apply_2d(heightmap, noise, heightmap_size, pos end end + -- for z = heightmap_size.z - 1, 0, -1 do + -- x = 0 + -- heightmap[(z * heightmap_size.x) + x] = z + -- end + + print("HEIGHTMAP\n") + worldeditadditions.format.array_2d(heightmap, heightmap_size.x) + + return true end diff --git a/worldeditadditions/lib/noise/engines/init.lua b/worldeditadditions/lib/noise/engines/init.lua index 198b11e..921117e 100644 --- a/worldeditadditions/lib/noise/engines/init.lua +++ b/worldeditadditions/lib/noise/engines/init.lua @@ -2,8 +2,9 @@ local wea = worldeditadditions return { + available = { "perlin", "sin", "white" }, Perlin = dofile(wea.modpath.."/lib/noise/engines/perlin.lua"), - Sin = dofile(wea.modpath.."/lib/noise/engines/sin.lua") - + Sin = dofile(wea.modpath.."/lib/noise/engines/sin.lua"), + White = dofile(wea.modpath.."/lib/noise/engines/white.lua") -- TODO: Follow https://www.redblobgames.com/articles/noise/introduction.html and implement different colours of noise (*especially* red and pink noise) } diff --git a/worldeditadditions/lib/noise/engines/sin.lua b/worldeditadditions/lib/noise/engines/sin.lua index ea7c7d4..1db6b7e 100644 --- a/worldeditadditions/lib/noise/engines/sin.lua +++ b/worldeditadditions/lib/noise/engines/sin.lua @@ -16,7 +16,6 @@ function Sin:noise( x, y, z ) 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/engines/white.lua b/worldeditadditions/lib/noise/engines/white.lua new file mode 100644 index 0000000..773fd12 --- /dev/null +++ b/worldeditadditions/lib/noise/engines/white.lua @@ -0,0 +1,27 @@ +local wea = worldeditadditions + + +local White = {} +White.__index = White + + +function White.new(seed) + local result = { + seed = seed or math.random() + } + setmetatable(result, White) + return result +end + +function White:noise( x, y, z ) + if x == 0 then x = 1 end + if y == 0 then y = 1 end + if z == 0 then z = 1 end + local seed = ((self.seed + (x * y * z)) * 1506359) % 1113883 + + math.randomseed(seed) + local value = math.random() + return value +end + +return White diff --git a/worldeditadditions/lib/noise/make_2d.lua b/worldeditadditions/lib/noise/make_2d.lua index 9d07570..d05db9d 100644 --- a/worldeditadditions/lib/noise/make_2d.lua +++ b/worldeditadditions/lib/noise/make_2d.lua @@ -13,18 +13,20 @@ local wea = worldeditadditions function worldeditadditions.noise.make_2d(size, start_pos, params) local result = {} - for i, layer in ipairs(params) do + for layer_i, layer in ipairs(params) do local generator if layer.algorithm == "perlin" then generator = wea.noise.engines.Perlin.new() elseif layer.algorithm == "sin" then generator = wea.noise.engines.Sin.new() + elseif layer.algorithm == "white" then + generator = wea.noise.engines.White.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)." + return false, "Error: Unknown noise algorithm '"..tostring(layer.algorithm).."' in layer "..layer_i.." of "..#params.." (available algorithms: "..table.concat(wea.noise.engines.available, ", ")..")." end - for x = 0, size.x do - for y = 0, size.z do + for x = 0, size.x - 1 do + for y = 0, size.z - 1 do local i = y*size.x + x local noise_x = (x + 100000+start_pos.x+layer.offset.x) * layer.scale.x @@ -32,12 +34,21 @@ function worldeditadditions.noise.make_2d(size, start_pos, params) local noise_value = generator:noise(noise_x, noise_y, 0) if type(result[i]) ~= "number" then result[i] = 0 end + local result_before = result[i] result[i] = result[i] + (noise_value ^ layer.exponent) * layer.multiply + layer.add + -- if layer_i == 1 and result[i] > 1 then + -- print("NOISE TOO BIG noise_value", noise_value, "noise_x", noise_x, "noise_y", noise_y, "i", i, "result[i]: BEFORE", result_before, "AFTER", result[i]) + -- end end end end + + print("NOISE MAKE_2D\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 diff --git a/worldeditadditions/lib/noise/params_apply_default.lua b/worldeditadditions/lib/noise/params_apply_default.lua index 0ae6c19..cd00852 100644 --- a/worldeditadditions/lib/noise/params_apply_default.lua +++ b/worldeditadditions/lib/noise/params_apply_default.lua @@ -34,14 +34,17 @@ function worldeditadditions.noise.params_apply_default(params) -- If params[1] is thing, this is a list of params -- 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) + local default_copy = wea.table.shallowcopy(params_default) -- Keyword support - if params_el.perlin then params_el.algorithm = "perlin" end - if params_el.sin then params_el.algorithm = "sin" end + for i, keyword in ipairs(wea.noise.engines.available) do + if params_el[keyword] ~= nil then + params_el.algorithm = keyword + end + end -- Apply this table to fill in the gaps - worldeditadditions.table.apply( + wea.table.apply( params_el, default_copy ) diff --git a/worldeditadditions_commands/commands/noise2d.lua b/worldeditadditions_commands/commands/noise2d.lua index 8ae0973..ce2f287 100644 --- a/worldeditadditions_commands/commands/noise2d.lua +++ b/worldeditadditions_commands/commands/noise2d.lua @@ -12,10 +12,8 @@ worldedit.register_command("noise2d", { if params_text == "" then return true, {} end - local success, map = worldeditadditions.parse.map(params_text, { - -- Keywords - "perlin", "sin" - }) + local success, map = worldeditadditions.parse.map(params_text, + wea.noise.engines.available) -- Keywords if not success then return success, map end if map.scale then From 7db2207aea660888432765fc3e205b14d3153034 Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Mon, 12 Jul 2021 02:46:47 +0100 Subject: [PATCH 20/32] noise2d/make_2d: add todo --- worldeditadditions/lib/noise/make_2d.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/worldeditadditions/lib/noise/make_2d.lua b/worldeditadditions/lib/noise/make_2d.lua index d05db9d..9eba7f4 100644 --- a/worldeditadditions/lib/noise/make_2d.lua +++ b/worldeditadditions/lib/noise/make_2d.lua @@ -25,6 +25,7 @@ function worldeditadditions.noise.make_2d(size, start_pos, params) return false, "Error: Unknown noise algorithm '"..tostring(layer.algorithm).."' in layer "..layer_i.." of "..#params.." (available algorithms: "..table.concat(wea.noise.engines.available, ", ")..")." end + -- TODO: Optimise performance by making i count backwards in sequence for x = 0, size.x - 1 do for y = 0, size.z - 1 do local i = y*size.x + x From 0dee65ca1d90fe7469b1b38076d8f28f2834d51d Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Tue, 13 Jul 2021 00:15:23 +0100 Subject: [PATCH 21/32] //noise2d: add red noise --- Chat-Command-Reference.md | 2 + worldeditadditions/lib/noise/engines/init.lua | 5 ++- worldeditadditions/lib/noise/engines/red.lua | 39 +++++++++++++++++++ worldeditadditions/lib/noise/make_2d.lua | 2 + 4 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 worldeditadditions/lib/noise/engines/red.lua diff --git a/Chat-Command-Reference.md b/Chat-Command-Reference.md index 33edc42..3693be5 100644 --- a/Chat-Command-Reference.md +++ b/Chat-Command-Reference.md @@ -518,6 +518,8 @@ Algorithm | Description ------------|-------------------------- `perlin` | Perlin noise. Functional, but currently contains artefacts I'm having difficulty tracking down. `sin` | A sine wave created with `math.sin()`. +`white` | Random white noise. +`red` | Red noise - has a lower frequency than white noise. When specifying algorithm names, the `algorithm` parameter name is optional. For example, the following are both equivalent: diff --git a/worldeditadditions/lib/noise/engines/init.lua b/worldeditadditions/lib/noise/engines/init.lua index 921117e..8918c8b 100644 --- a/worldeditadditions/lib/noise/engines/init.lua +++ b/worldeditadditions/lib/noise/engines/init.lua @@ -2,9 +2,10 @@ local wea = worldeditadditions return { - available = { "perlin", "sin", "white" }, + available = { "perlin", "sin", "white", "red" }, Perlin = dofile(wea.modpath.."/lib/noise/engines/perlin.lua"), Sin = dofile(wea.modpath.."/lib/noise/engines/sin.lua"), - White = dofile(wea.modpath.."/lib/noise/engines/white.lua") + White = dofile(wea.modpath.."/lib/noise/engines/white.lua"), + Red = dofile(wea.modpath.."/lib/noise/engines/red.lua") -- TODO: Follow https://www.redblobgames.com/articles/noise/introduction.html and implement different colours of noise (*especially* red and pink noise) } diff --git a/worldeditadditions/lib/noise/engines/red.lua b/worldeditadditions/lib/noise/engines/red.lua new file mode 100644 index 0000000..3ff9e13 --- /dev/null +++ b/worldeditadditions/lib/noise/engines/red.lua @@ -0,0 +1,39 @@ +local wea = worldeditadditions + +local White = dofile(wea.modpath.."/lib/noise/engines/white.lua") + +local Red = {} +Red.__index = Red + + +function Red.new(seed) + local result = { + seed = seed or math.random(), + white = White.new(seed) + } + setmetatable(result, Red) + return result +end + +function Red:noise( x, y, z ) + local values = { + self.white:noise(x, y, z), + self.white:noise(x + 1, y, z), + self.white:noise(x, y + 1, z), + self.white:noise(x, y, z + 1), + self.white:noise(x - 1, y, z), + self.white:noise(x, y - 1, z), + self.white:noise(x, y, z - 1), + self.white:noise(x, y - 1, z - 1), + self.white:noise(x - 1, y, z - 1), + self.white:noise(x - 1, y - 1, z), + self.white:noise(x - 1, y - 1, z - 1), + self.white:noise(x, y + 1, z + 1), + self.white:noise(x + 1, y, z + 1), + self.white:noise(x + 1, y + 1, z), + self.white:noise(x + 1, y + 1, z + 1), + } + return wea.average(values) +end + +return Red diff --git a/worldeditadditions/lib/noise/make_2d.lua b/worldeditadditions/lib/noise/make_2d.lua index 9eba7f4..06043e4 100644 --- a/worldeditadditions/lib/noise/make_2d.lua +++ b/worldeditadditions/lib/noise/make_2d.lua @@ -21,6 +21,8 @@ function worldeditadditions.noise.make_2d(size, start_pos, params) generator = wea.noise.engines.Sin.new() elseif layer.algorithm == "white" then generator = wea.noise.engines.White.new() + elseif layer.algorithm == "red" then + generator = wea.noise.engines.Red.new() else -- We don't have any other generators just yet return false, "Error: Unknown noise algorithm '"..tostring(layer.algorithm).."' in layer "..layer_i.." of "..#params.." (available algorithms: "..table.concat(wea.noise.engines.available, ", ")..")." end From 8891c47e983d5315bc5be3bcfdc31aa1953e5d80 Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Tue, 13 Jul 2021 00:22:35 +0100 Subject: [PATCH 22/32] wea.parse.map: fix crash if keywords isn't specified --- worldeditadditions/utils/parse/map.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/worldeditadditions/utils/parse/map.lua b/worldeditadditions/utils/parse/map.lua index 58e311d..94bc360 100644 --- a/worldeditadditions/utils/parse/map.lua +++ b/worldeditadditions/utils/parse/map.lua @@ -4,9 +4,10 @@ local wea = worldeditadditions -- 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 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. +-- @param keywords string[]? Optional. 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, keywords) + if not keywords then keywords = {} end local result = {} local parts = wea.split(params_text, "%s+", false) From 03038689e2eb81d467db367b49da6f0a79f504aa Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Tue, 13 Jul 2021 00:54:52 +0100 Subject: [PATCH 23/32] Add infrared noise --- Chat-Command-Reference.md | 3 +- .../lib/noise/engines/infrared.lua | 35 +++++++++++++++++++ worldeditadditions/lib/noise/engines/init.lua | 5 +-- worldeditadditions/lib/noise/make_2d.lua | 2 ++ 4 files changed, 42 insertions(+), 3 deletions(-) create mode 100644 worldeditadditions/lib/noise/engines/infrared.lua diff --git a/Chat-Command-Reference.md b/Chat-Command-Reference.md index 3693be5..5a8fda3 100644 --- a/Chat-Command-Reference.md +++ b/Chat-Command-Reference.md @@ -519,7 +519,8 @@ Algorithm | Description `perlin` | Perlin noise. Functional, but currently contains artefacts I'm having difficulty tracking down. `sin` | A sine wave created with `math.sin()`. `white` | Random white noise. -`red` | Red noise - has a lower frequency than white noise. +`red` | Red noise - has a lower frequency than white noise. +`infrared` | Even smoother than red noise. Tends to also be quite flat unless you use a slightly higher `apply` value (e.g. `20`). When specifying algorithm names, the `algorithm` parameter name is optional. For example, the following are both equivalent: diff --git a/worldeditadditions/lib/noise/engines/infrared.lua b/worldeditadditions/lib/noise/engines/infrared.lua new file mode 100644 index 0000000..60b04f8 --- /dev/null +++ b/worldeditadditions/lib/noise/engines/infrared.lua @@ -0,0 +1,35 @@ +local wea = worldeditadditions + +local White = dofile(wea.modpath.."/lib/noise/engines/white.lua") + +local Infrared = {} +Infrared.__index = Infrared + + +function Infrared.new(seed) + local result = { + seed = seed or math.random(), + white = White.new(seed), + window = 2 + } + setmetatable(result, Infrared) + return result +end + +function Infrared:noise( x, y, z ) + local values = { } + for nx=x-self.window,x+self.window do + for ny=y-self.window,y+self.window do + for nz=z-self.window,z+self.window do + table.insert(values, self.white:noise(nx, ny, nz)) + + print("DEBUG nx", nx, "ny", ny, "nz", nz, "value", value) + end + end + end + for i,value in ipairs(values) do + end + return wea.average(values) +end + +return Infrared diff --git a/worldeditadditions/lib/noise/engines/init.lua b/worldeditadditions/lib/noise/engines/init.lua index 8918c8b..38cd625 100644 --- a/worldeditadditions/lib/noise/engines/init.lua +++ b/worldeditadditions/lib/noise/engines/init.lua @@ -2,10 +2,11 @@ local wea = worldeditadditions return { - available = { "perlin", "sin", "white", "red" }, + available = { "perlin", "sin", "white", "red", "infrared" }, Perlin = dofile(wea.modpath.."/lib/noise/engines/perlin.lua"), Sin = dofile(wea.modpath.."/lib/noise/engines/sin.lua"), White = dofile(wea.modpath.."/lib/noise/engines/white.lua"), - Red = dofile(wea.modpath.."/lib/noise/engines/red.lua") + Red = dofile(wea.modpath.."/lib/noise/engines/red.lua"), + Infrared = dofile(wea.modpath.."/lib/noise/engines/infrared.lua") -- TODO: Follow https://www.redblobgames.com/articles/noise/introduction.html and implement different colours of noise (*especially* red and pink noise) } diff --git a/worldeditadditions/lib/noise/make_2d.lua b/worldeditadditions/lib/noise/make_2d.lua index 06043e4..92e4972 100644 --- a/worldeditadditions/lib/noise/make_2d.lua +++ b/worldeditadditions/lib/noise/make_2d.lua @@ -23,6 +23,8 @@ function worldeditadditions.noise.make_2d(size, start_pos, params) generator = wea.noise.engines.White.new() elseif layer.algorithm == "red" then generator = wea.noise.engines.Red.new() + elseif layer.algorithm == "infrared" then + generator = wea.noise.engines.Infrared.new() else -- We don't have any other generators just yet return false, "Error: Unknown noise algorithm '"..tostring(layer.algorithm).."' in layer "..layer_i.." of "..#params.." (available algorithms: "..table.concat(wea.noise.engines.available, ", ")..")." end From 643e1a2dcecce13234c1dc9d70e8fff99d6d65c8 Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Tue, 13 Jul 2021 00:56:10 +0100 Subject: [PATCH 24/32] Reference: Add ref --- Chat-Command-Reference.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Chat-Command-Reference.md b/Chat-Command-Reference.md index 5a8fda3..a60c543 100644 --- a/Chat-Command-Reference.md +++ b/Chat-Command-Reference.md @@ -519,7 +519,7 @@ Algorithm | Description `perlin` | Perlin noise. Functional, but currently contains artefacts I'm having difficulty tracking down. `sin` | A sine wave created with `math.sin()`. `white` | Random white noise. -`red` | Red noise - has a lower frequency than white noise. +`red` | Red noise - has a lower frequency than white noise. Ref [Noise Functions and Map Generation by Red Blob Games](https://www.redblobgames.com/articles/noise/introduction.html). `infrared` | Even smoother than red noise. Tends to also be quite flat unless you use a slightly higher `apply` value (e.g. `20`). When specifying algorithm names, the `algorithm` parameter name is optional. For example, the following are both equivalent: From e85b91d0748d102e19d0e1f0ecc31f503c536b3c Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Tue, 13 Jul 2021 00:56:26 +0100 Subject: [PATCH 25/32] //noise2d infrared: remove debug print --- worldeditadditions/lib/noise/engines/infrared.lua | 4 ---- 1 file changed, 4 deletions(-) diff --git a/worldeditadditions/lib/noise/engines/infrared.lua b/worldeditadditions/lib/noise/engines/infrared.lua index 60b04f8..469cec9 100644 --- a/worldeditadditions/lib/noise/engines/infrared.lua +++ b/worldeditadditions/lib/noise/engines/infrared.lua @@ -22,13 +22,9 @@ function Infrared:noise( x, y, z ) for ny=y-self.window,y+self.window do for nz=z-self.window,z+self.window do table.insert(values, self.white:noise(nx, ny, nz)) - - print("DEBUG nx", nx, "ny", ny, "nz", nz, "value", value) end end end - for i,value in ipairs(values) do - end return wea.average(values) end From e799a2ea61b56cd2db2ceab55b0e79a8b9c48de5 Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Tue, 13 Jul 2021 22:02:34 +0100 Subject: [PATCH 26/32] Refactor Queue implementation into separate file --- worldeditadditions/init.lua | 2 + worldeditadditions/lib/floodfill.lua | 70 ++++++---------------------- worldeditadditions/utils/queue.lua | 45 ++++++++++++++++++ 3 files changed, 62 insertions(+), 55 deletions(-) create mode 100644 worldeditadditions/utils/queue.lua diff --git a/worldeditadditions/init.lua b/worldeditadditions/init.lua index 6486a56..182bdaa 100644 --- a/worldeditadditions/init.lua +++ b/worldeditadditions/init.lua @@ -12,6 +12,8 @@ worldeditadditions.Vector3 = dofile(worldeditadditions.modpath.."/utils/vector3. worldeditadditions.Mesh, worldeditadditions.Face = dofile(worldeditadditions.modpath.."/utils/mesh.lua") +worldeditadditions.Queue = dofile(worldeditadditions.modpath.."/utils/queue.lua") + dofile(worldeditadditions.modpath.."/utils/strings/init.lua") dofile(worldeditadditions.modpath.."/utils/format/init.lua") diff --git a/worldeditadditions/lib/floodfill.lua b/worldeditadditions/lib/floodfill.lua index 79af1e2..bfefe28 100644 --- a/worldeditadditions/lib/floodfill.lua +++ b/worldeditadditions/lib/floodfill.lua @@ -1,47 +1,7 @@ --- Flood-fill command for complex lakes etc. -- @module worldeditadditions.floodfill -------------------------------------------------------------------------------- ---- A Queue implementation, taken & adapted from https://www.lua.org/pil/11.4.html --- @submodule worldeditadditions.utils.queue - -local Queue = {} -function Queue.new() - return { first = 0, last = -1 } -end - -function Queue.enqueue(queue, value) - local new_last = queue.last + 1 - queue.last = new_last - queue[new_last] = value -end - -function Queue.contains(queue, value) - for i=queue.first,queue.last do - if queue[i] == value then - return true - end - end - return false -end - -function Queue.is_empty(queue) - return queue.first > queue.last -end - -function Queue.dequeue(queue) - local first = queue.first - if Queue.is_empty(queue) then - error("Error: The queue is empty!") - end - - local value = queue[first] - queue[first] = nil -- Help the garbage collector out - queue.first = first + 1 - return value -end - -------------------------------------------------------------------------------- +local wea = worldeditadditions function worldeditadditions.floodfill(start_pos, radius, replace_node) -- Calculate the area we want to modify @@ -67,12 +27,12 @@ function worldeditadditions.floodfill(start_pos, radius, replace_node) end local count = 0 - local remaining_nodes = Queue.new() - Queue.enqueue(remaining_nodes, start_pos_index) + local remaining_nodes = wea.Queue.new() + remaining_nodes:enqueue(start_pos_index) -- Do the floodfill - while Queue.is_empty(remaining_nodes) == false do - local cur = Queue.dequeue(remaining_nodes) + while remaining_nodes:is_empty() == false do + local cur = remaining_nodes:dequeue() -- Replace this node data[cur] = replace_id @@ -83,37 +43,37 @@ function worldeditadditions.floodfill(start_pos, radius, replace_node) local xplus = cur + 1 -- +X if data[xplus] == search_id and worldeditadditions.vector.lengthsquared(vector.subtract(area:position(xplus), start_pos)) < radius_sq and - not Queue.contains(remaining_nodes, xplus) then + not remaining_nodes:contains(xplus) then -- minetest.log("action", "[floodfill] [+X] index " .. xplus .. " is a " .. data[xplus] .. ", searching for a " .. search_id) - Queue.enqueue(remaining_nodes, xplus) + remaining_nodes:enqueue(xplus) end local xminus = cur - 1 -- -X if data[xminus] == search_id and worldeditadditions.vector.lengthsquared(vector.subtract(area:position(xminus), start_pos)) < radius_sq and - not Queue.contains(remaining_nodes, xminus) then + not remaining_nodes:contains(xminus) then -- minetest.log("action", "[floodfill] [-X] index " .. xminus .. " is a " .. data[xminus] .. ", searching for a " .. search_id) - Queue.enqueue(remaining_nodes, xminus) + remaining_nodes:enqueue(xminus) end local zplus = cur + area.zstride -- +Z if data[zplus] == search_id and worldeditadditions.vector.lengthsquared(vector.subtract(area:position(zplus), start_pos)) < radius_sq and - not Queue.contains(remaining_nodes, zplus) then + not remaining_nodes:contains(zplus) then -- minetest.log("action", "[floodfill] [+Z] index " .. zplus .. " is a " .. data[zplus] .. ", searching for a " .. search_id) - Queue.enqueue(remaining_nodes, zplus) + remaining_nodes:enqueue(zplus) end local zminus = cur - area.zstride -- -Z if data[zminus] == search_id and worldeditadditions.vector.lengthsquared(vector.subtract(area:position(zminus), start_pos)) < radius_sq and - not Queue.contains(remaining_nodes, zminus) then + not remaining_nodes:contains(zminus) then -- minetest.log("action", "[floodfill] [-Z] index " .. zminus .. " is a " .. data[zminus] .. ", searching for a " .. search_id) - Queue.enqueue(remaining_nodes, zminus) + remaining_nodes:enqueue(zminus) end local yminus = cur - area.ystride -- -Y if data[yminus] == search_id and worldeditadditions.vector.lengthsquared(vector.subtract(area:position(yminus), start_pos)) < radius_sq and - not Queue.contains(remaining_nodes, yminus) then + not remaining_nodes:contains(yminus) then -- minetest.log("action", "[floodfill] [-Y] index " .. yminus .. " is a " .. data[yminus] .. ", searching for a " .. search_id) - Queue.enqueue(remaining_nodes, yminus) + remaining_nodes:enqueue(yminus) end count = count + 1 diff --git a/worldeditadditions/utils/queue.lua b/worldeditadditions/utils/queue.lua new file mode 100644 index 0000000..126b7ae --- /dev/null +++ b/worldeditadditions/utils/queue.lua @@ -0,0 +1,45 @@ +------------------------------------------------------------------------------- +--- A Queue implementation, taken & adapted from https://www.lua.org/pil/11.4.html +-- @submodule worldeditadditions.utils.queue + +local Queue = {} +Queue.__index = Queue + +function Queue.new() + result = { first = 0, last = -1, items = {} } + setmetatable(result, Queue) + return result +end + +function Queue:enqueue(value) + local new_last = self.last + 1 + self.last = new_last + self.items[new_last] = value +end + +function Queue:contains(value) + for i=self.first,self.last do + if self.items[i] == value then + return true + end + end + return false +end + +function Queue:is_empty() + return self.first > self.last +end + +function Queue:dequeue() + local first = self.first + if Queue.is_empty(self) then + error("Error: The self is empty!") + end + + local value = self.items[first] + self.items[first] = nil -- Help the garbage collector out + self.first = first + 1 + return value +end + +return Queue From 81957070f9549446f13da2b31a4b138df7efe5ae Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Tue, 13 Jul 2021 23:42:08 +0100 Subject: [PATCH 27/32] Implement an LRU cache --- worldeditadditions/init.lua | 1 + worldeditadditions/utils/lru.lua | 80 ++++++++++++++++++++++++++++++ worldeditadditions/utils/queue.lua | 19 +++++-- 3 files changed, 96 insertions(+), 4 deletions(-) create mode 100644 worldeditadditions/utils/lru.lua diff --git a/worldeditadditions/init.lua b/worldeditadditions/init.lua index 182bdaa..6342b07 100644 --- a/worldeditadditions/init.lua +++ b/worldeditadditions/init.lua @@ -13,6 +13,7 @@ worldeditadditions.Mesh, worldeditadditions.Face = dofile(worldeditadditions.modpath.."/utils/mesh.lua") worldeditadditions.Queue = dofile(worldeditadditions.modpath.."/utils/queue.lua") +worldeditadditions.LRU = dofile(worldeditadditions.modpath.."/utils/lru.lua") dofile(worldeditadditions.modpath.."/utils/strings/init.lua") diff --git a/worldeditadditions/utils/lru.lua b/worldeditadditions/utils/lru.lua new file mode 100644 index 0000000..0ec0111 --- /dev/null +++ b/worldeditadditions/utils/lru.lua @@ -0,0 +1,80 @@ +local Queue +if worldeditadditions then + Queue = dofile(worldeditadditions.modpath.."/utils/queue.lua") +else + Queue = require("queue") +end + +--- A least-recently-used cache implementation. +-- @class +local LRU = {} +LRU.__index = LRU + +--- Creates a new LRU cache. +-- Optimal sizes: 8, 16, 32, 64 or any value above +-- @param max_size=32 number The maximum number of items to store in the cache. +-- @returns LRU A new LRU cache. +function LRU.new(max_size) + if not max_size then max_size = 32 end + local result = { + max_size = max_size, + cache = { }, + size = 0, + queue = Queue.new() + } + setmetatable(result, LRU) + return result +end + +--- Determines whether the given key is present in this cache object. +-- Does NOT update the most recently used status of said key. +-- @param key string The key to check. +-- @returns bool Whether the given key exists in the cache or not. +function LRU:has(key) + return self.cache[key] ~= nil +end + +--- Gets the value associated with the given key. +-- @param key string The key to retrieve the value for. +-- @returns any|nil The value associated with the given key, or nil if it doesn't exist in this cache. +function LRU:get(key) + if not self.cache[key] then return nil end + + -- Put it to the end of the queue + self.queue:remove_index(self.cache[key].index) + self.cache[key].index = self.queue:enqueue(key) + + return self.cache[key].value +end + +--- Adds a given key-value pair to this cache. +-- Note that this might (or might not) result in the eviction of another item. +-- @param key string The key of the item to add. +-- @param value any The value to associate with the given key. +-- @returns nil +function LRU:set(key, value) + if self.cache[key] ~= nil then + -- It's already present in the cache - update it + -- Put it to the end of the queue + self.queue:remove_index(self.cache[key].index) + local new_index = self.queue:enqueue(key) + -- Update the cache entry + self.cache[key] = { + value = value, + index = new_index + } + else + -- It's not in the cache -- add it + self.cache[key] = { value = value, index = self.queue:enqueue(key) } + + self.size = self.size + 1 + if self.size > self.max_size then + -- The cache is full, delete the oldest item + local oldest_key = self.queue:dequeue() + self.cache[oldest_key] = nil + self.size = self.size - 1 + end + end +end + +return LRU diff --git a/worldeditadditions/utils/queue.lua b/worldeditadditions/utils/queue.lua index 126b7ae..0bbfafc 100644 --- a/worldeditadditions/utils/queue.lua +++ b/worldeditadditions/utils/queue.lua @@ -15,6 +15,7 @@ function Queue:enqueue(value) local new_last = self.last + 1 self.last = new_last self.items[new_last] = value + return new_last end function Queue:contains(value) @@ -30,15 +31,25 @@ function Queue:is_empty() return self.first > self.last end +function Queue:remove_index(index) + self.items[index] = nil +end + function Queue:dequeue() - local first = self.first if Queue.is_empty(self) then error("Error: The self is empty!") end - local value = self.items[first] - self.items[first] = nil -- Help the garbage collector out - self.first = first + 1 + local first = self.first + -- Find the next non-nil item + local value + while value == nil do + if first >= self.last then return nil end + value = self.items[first] + self.items[first] = nil -- Help the garbage collector out + first = first + 1 + end + self.first = first return value end From aeea17524dc151169629930a8274f520591a5049 Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Tue, 13 Jul 2021 23:42:19 +0100 Subject: [PATCH 28/32] benchmark the new LRU cache implementation --- worldeditadditions/utils/lru_benchmark.lua | 81 ++++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 worldeditadditions/utils/lru_benchmark.lua diff --git a/worldeditadditions/utils/lru_benchmark.lua b/worldeditadditions/utils/lru_benchmark.lua new file mode 100644 index 0000000..0f1d201 --- /dev/null +++ b/worldeditadditions/utils/lru_benchmark.lua @@ -0,0 +1,81 @@ +local LRU = require("lru") +local operations = 100000 +local values = { + "Abiu", "Açaí", "Acerola", "Ackee", "African cucumber", "Apple", "Apricot", + "Avocado", "Banana", "Bilberry", "Blackberry", "Blackcurrant", + "Black sapote", "Blueberry", "Boysenberry", "Breadfruit", "Cactus pear", + "Canistel", "Cempedak", "Cherimoya", "Cherry", "Chico fruit", "Cloudberry", + "Coco De Mer", "Coconut", "Crab apple", "Cranberry", "Currant", "Damson", + "Date", "Dragonfruit (or Pitaya)", "Durian", "Egg Fruit", "Elderberry", + "Feijoa", "Fig", "Finger Lime", "Goji berry", "Gooseberry", "Grape", + "Raisin", "Grapefruit", "Grewia asiatica", "Guava", "Hala Fruit", + "Honeyberry", "Huckleberry", "Jabuticaba", "Jackfruit", "Jambul", + "Japanese plum", "Jostaberry", "Jujube", "Juniper berry", "Kaffir Lime", + "Kiwano", "Kiwifruit", "Kumquat", "Lemon", "Lime", "Loganberry", "Longan", + "Loquat" +} + +-- From http://lua-users.org/wiki/SimpleRound +function round(num, numDecimalPlaces) + local mult = 10^(numDecimalPlaces or 0) + return math.floor(num * mult + 0.5) / mult +end + +--- Pads str to length len with char from right +-- @source https://snipplr.com/view/13092/strlpad--pad-string-to-the-left +local function str_padend(str, len, char) + if char == nil then char = ' ' end + return str .. string.rep(char, len - #str) +end +--- 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 values_count = #values + +function test(size) + local cpu_start = os.clock() + + + local lru = LRU.new(size) + + local hits = 0 + local misses = 0 + for i=1,operations do + local key = values[math.random(1, values_count)] + if math.random() >= 0.5 then + -- set + lru:set(key, math.random()) + else + -- get + if lru:get(key) == nil then + misses = misses + 1 + lru:set(key, math.random()) + else + hits = hits + 1 + end + end + end + + + local cpu_end = os.clock() + print( + size, + str_padend(tostring(round(cpu_end - cpu_start, 6)), 9), + hits, misses, + ((hits/operations)*100) + ) +end + + +io.stderr:write("OPERATIONS\t"..operations.."\n") +print("size", "cpu time", "hits", "misses", "hit ratio (%)") +for i=3,256 do + io.stderr:write("size "..i.."\r") + test(i) + +end From 0d1900d37cf0448104126e9fde1a016f7439efe8 Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Tue, 13 Jul 2021 23:49:21 +0100 Subject: [PATCH 29/32] Add airlike backend command, but it's nto tested or hooked up yet --- worldeditadditions/lib/airapply.lua | 56 +++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 worldeditadditions/lib/airapply.lua diff --git a/worldeditadditions/lib/airapply.lua b/worldeditadditions/lib/airapply.lua new file mode 100644 index 0000000..761d3d0 --- /dev/null +++ b/worldeditadditions/lib/airapply.lua @@ -0,0 +1,56 @@ +-- █████ ██ ██████ +-- ██ ██ ██ ██ ██ +-- ███████ ██ ██████ +-- ██ ██ ██ ██ ██ +-- ██ ██ ██ ██ ██ +-- +-- █████ ██████ ██████ ██ ██ ██ +-- ██ ██ ██ ██ ██ ██ ██ ██ ██ +-- ███████ ██████ ██████ ██ ████ +-- ██ ██ ██ ██ ██ ██ +-- ██ ██ ██ ██ ███████ ██ + +--- Similar to cubeapply, except that it takes 2 positions and only keeps an ellipsoid-shaped area defined by the boundaries of the defined region. +-- Takes a backup copy of the defined region, runs the given function, and then +-- restores the bits around the edge that aren't inside the largest ellipsoid that will fit inside the defined region. +-- @param {Position} pos1 The 1st position defining the region boundary +-- @param {Position} pos2 The 2nd positioon defining the region boundary +-- @param {Function} func The function to call that performs the action in question. It is expected that the given function will accept no arguments. +function worldeditadditions.airapply(pos1, pos2, func) + local time_taken_all = worldeditadditions.get_ms_time() + pos1, pos2 = worldedit.sort_pos(pos1, pos2) + -- pos2 will always have the highest co-ordinates now + + -- Fetch the nodes in the specified area + local manip_before, area_before = worldedit.manip_helpers.init(pos1, pos2) + local data_before = manip_before:get_data() + + local time_taken_fn = worldeditadditions.get_ms_time() + func() + time_taken_fn = worldeditadditions.get_ms_time() - time_taken_fn + + local manip_after, area_after = worldedit.manip_helpers.init(pos1, pos2) + local data_after = manip_after:get_data() + + for z = pos2.z, pos1.z, -1 do + for y = pos2.y, pos1.y, -1 do + for x = pos2.x, pos1.x, -1 do + local i = area:index(x, y, z) + local old_is_airlike = worldeditadditions.is_airlike(data_before[i]) + + -- Roll everything that replaces nodes that aren't airlike + if not old_is_airlike then + data_after[area_after:index(x, y, z)] = data_before[area_before:index(x, y, z)] + end + end + end + end + + -- Save the modified nodes back to disk & return + -- No need to save - this function doesn't actually change anything + worldedit.manip_helpers.finish(manip_after, data_after) + + + time_taken_all = worldeditadditions.get_ms_time() - time_taken_all + return true, { all = time_taken_all, fn = time_taken_fn } +end From 0b379c48cbf1d2d55e5de69fbd4ffdc1cdb2e94e Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Thu, 15 Jul 2021 02:17:14 +0100 Subject: [PATCH 30/32] Implement //airapply For #56, but the docs aren't written yet so we'll wait on closing it until I've written them. --- worldeditadditions/init.lua | 1 + worldeditadditions/lib/airapply.lua | 7 +- .../commands/meta/airapply.lua | 69 +++++++++++++++++++ worldeditadditions_commands/init.lua | 1 + 4 files changed, 75 insertions(+), 3 deletions(-) create mode 100644 worldeditadditions_commands/commands/meta/airapply.lua diff --git a/worldeditadditions/init.lua b/worldeditadditions/init.lua index 6342b07..a46e9d4 100644 --- a/worldeditadditions/init.lua +++ b/worldeditadditions/init.lua @@ -55,6 +55,7 @@ dofile(worldeditadditions.modpath.."/lib/bonemeal.lua") dofile(worldeditadditions.modpath.."/lib/forest.lua") dofile(worldeditadditions.modpath.."/lib/ellipsoidapply.lua") +dofile(worldeditadditions.modpath.."/lib/airapply.lua") dofile(worldeditadditions.modpath.."/lib/subdivide.lua") dofile(worldeditadditions.modpath.."/lib/selection/stack.lua") diff --git a/worldeditadditions/lib/airapply.lua b/worldeditadditions/lib/airapply.lua index 761d3d0..99236bb 100644 --- a/worldeditadditions/lib/airapply.lua +++ b/worldeditadditions/lib/airapply.lua @@ -35,12 +35,13 @@ function worldeditadditions.airapply(pos1, pos2, func) for z = pos2.z, pos1.z, -1 do for y = pos2.y, pos1.y, -1 do for x = pos2.x, pos1.x, -1 do - local i = area:index(x, y, z) - local old_is_airlike = worldeditadditions.is_airlike(data_before[i]) + local i_before = area_before:index(x, y, z) + local i_after = area_after:index(x, y, z) + local old_is_airlike = worldeditadditions.is_airlike(data_before[i_before]) -- Roll everything that replaces nodes that aren't airlike if not old_is_airlike then - data_after[area_after:index(x, y, z)] = data_before[area_before:index(x, y, z)] + data_after[i_after] = data_before[i_before] end end end diff --git a/worldeditadditions_commands/commands/meta/airapply.lua b/worldeditadditions_commands/commands/meta/airapply.lua new file mode 100644 index 0000000..89e2a6b --- /dev/null +++ b/worldeditadditions_commands/commands/meta/airapply.lua @@ -0,0 +1,69 @@ +-- ███████ ██ ██ ██ ██████ ███████ ███████ █████ ██████ ██████ ██ ██ ██ +-- ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +-- █████ ██ ██ ██ ██████ ███████ █████ ███████ ██████ ██████ ██ ████ +-- ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +-- ███████ ███████ ███████ ██ ██ ███████ ███████ ██ ██ ██ ██ ███████ ██ + +worldedit.register_command("airapply", { + params = " ", + description = "Executes the given command (automatically prepending '//'), but only on non-air nodes within the defined region.", + privs = { worldedit = true }, + require_pos = 2, + parse = function(params_text) + if params_text == "" then return false, "Error: No command specified." end + + local cmd_name, args_text = params_text:match("([^%s]+)%s+(.+)") + if not cmd_name then + cmd_name = params_text + args_text = "" + end + + -- Note that we search the worldedit commands here, not the minetest ones + local cmd_we = worldedit.registered_commands[cmd_name] + if cmd_we == nil then + return false, "Error: "..cmd_name.." isn't a valid command." + end + if cmd_we.require_pos ~= 2 then + return false, "Error: The command "..cmd_name.." exists, but doesn't take 2 positions and so can't be used with //airapply." + end + + -- Run parsing of target command + -- Lifted from cubeapply in WorldEdit + local args_parsed = {cmd_we.parse(args_text)} + if not table.remove(args_parsed, 1) then + return false, args_parsed[1] + end + + return true, cmd_we, args_parsed + end, + nodes_needed = function(name) + local pos1, pos2 = worldedit.sort_pos(worldedit.pos1[name], worldedit.pos2[name]) + return math.ceil(4/3 * math.pi * (pos2.x - pos1.x)/2 * (pos2.y - pos1.y)/2 * (pos2.z - pos1.z)/2) + end, + func = function(name, cmd, args_parsed) + if not minetest.check_player_privs(name, cmd.privs) then + return false, "Your privileges are insufficient to execute the command '"..cmd_name.."'." + end + + local pos1, pos2 = worldeditadditions.Vector3.sort( + worldedit.pos1[name], + worldedit.pos2[name] + ) + + + local success, stats_time = worldeditadditions.airapply( + pos1, pos2, + function() + cmd.func(name, worldeditadditions.table.unpack(args_parsed)) + end, args + ) + + + local time_overhead = 100 - worldeditadditions.round((stats_time.fn / stats_time.all) * 100, 3) + local text_time_all = worldeditadditions.format.human_time(stats_time.all) + local text_time_fn = worldeditadditions.format.human_time(stats_time.fn) + + minetest.log("action", name.." used //airapply at "..pos1.." - "..pos2.." in "..text_time_all) + return true, "Complete in "..text_time_all.." ("..text_time_fn.." fn, "..time_overhead.."% airapply overhead)" + end +}) diff --git a/worldeditadditions_commands/init.lua b/worldeditadditions_commands/init.lua index 3cf7e39..9b7796e 100644 --- a/worldeditadditions_commands/init.lua +++ b/worldeditadditions_commands/init.lua @@ -41,6 +41,7 @@ dofile(we_c.modpath.."/commands/meta/multi.lua") dofile(we_c.modpath.."/commands/meta/many.lua") dofile(we_c.modpath.."/commands/meta/subdivide.lua") dofile(we_c.modpath.."/commands/meta/ellipsoidapply.lua") +dofile(we_c.modpath.."/commands/meta/airapply.lua") -- Selection Tools dofile(we_c.modpath.."/commands/selectors/init.lua") From 0fef39d707e9027a162e3839d5756533a35791c7 Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Thu, 15 Jul 2021 02:18:21 +0100 Subject: [PATCH 31/32] Vector3: Implement __concat operator overloading Now you can do this: local v = wea.Vector3.new(3, 4, 5) print(v.."yay") --- .tests/Vector3/concat.test.lua | 28 ++++++++++++++++++++++++++++ worldeditadditions/utils/vector3.lua | 6 ++++++ 2 files changed, 34 insertions(+) create mode 100644 .tests/Vector3/concat.test.lua diff --git a/.tests/Vector3/concat.test.lua b/.tests/Vector3/concat.test.lua new file mode 100644 index 0000000..0760eda --- /dev/null +++ b/.tests/Vector3/concat.test.lua @@ -0,0 +1,28 @@ +local Vector3 = require("worldeditadditions.utils.vector3") + +describe("Vector3.__concat", function() + it("should work when concatenating a Vector3 + Vector3", function() + local a = Vector3.new(3, 4, 5) + local b = Vector3.new(6, 7, 8) + + assert.are.is_true( + type(tostring(a .. b)) == "string" + ) + end) + it("should work when concatenating a string + Vector3", function() + local a = "yay" + local b = Vector3.new(6, 7, 8) + + assert.are.is_true( + type(tostring(a .. b)) == "string" + ) + end) + it("should work when concatenating a Vector3 + string", function() + local a = Vector3.new(3, 4, 5) + local b = "yay" + + assert.are.is_true( + type(tostring(a .. b)) == "string" + ) + end) +end) diff --git a/worldeditadditions/utils/vector3.lua b/worldeditadditions/utils/vector3.lua index 252b49d..734aabb 100644 --- a/worldeditadditions/utils/vector3.lua +++ b/worldeditadditions/utils/vector3.lua @@ -389,5 +389,11 @@ function Vector3.__tostring(a) return "("..a.x..", "..a.y..", "..a.z..")" end +function Vector3.__concat(a, b) + if type(a) ~= "string" then a = tostring(a) end + if type(b) ~= "string" then b = tostring(b) end + return a .. b +end + return Vector3 From 1930b8c64df7bb1eb5f2253e540df2762b6e2261 Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Thu, 15 Jul 2021 02:19:42 +0100 Subject: [PATCH 32/32] tests/new: fix Vector3 tests --- .tests/Vector3/new.test.lua | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.tests/Vector3/new.test.lua b/.tests/Vector3/new.test.lua index b74e5b1..54a5c72 100644 --- a/.tests/Vector3/new.test.lua +++ b/.tests/Vector3/new.test.lua @@ -7,18 +7,18 @@ describe("Vector3.add", function() Vector3.new(3, 4, 5) ) end) - it("should throw an error on invalid x", function() - assert.has.errors(function() + it("should not throw an error on invalid x", function() + assert.has_no.errors(function() Vector3.new("cheese", 4, 5) end) end) - it("should throw an error on invalid y", function() - assert.has.errors(function() + it("should not throw an error on invalid y", function() + assert.has_no.errors(function() Vector3.new(4, "cheese", 5) end) end) - it("should throw an error on invalid z", function() - assert.has.errors(function() + it("should not throw an error on invalid z", function() + assert.has_no.errors(function() Vector3.new(66, 2, "cheese") end) end)