From ee2e5716eb04e5ee7be2f3d7c3fa825b484eedf4 Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Sat, 3 Jul 2021 01:44:36 +0100 Subject: [PATCH] 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")