mirror of
				https://github.com/sbrl/Minetest-WorldEditAdditions
				synced 2025-10-31 20:23:05 +01:00 
			
		
		
		
	Implement initial (untested) convolution system.
Next we need to implement a worldedit function to handle fetching the manip data, calculating the heightmap, pushing it through this convolutional system, and saving the changes back again.
This commit is contained in:
		
							parent
							
								
									35001f05f8
								
							
						
					
					
						commit
						9b9a471aa8
					
				
							
								
								
									
										6
									
								
								worldeditadditions/lib/convolution/convolution.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								worldeditadditions/lib/convolution/convolution.lua
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,6 @@ | ||||
| worldeditadditions.conv = {} | ||||
| 
 | ||||
| dofile(worldeditadditions.modpath.."/lib/conv/kernels.lua") | ||||
| dofile(worldeditadditions.modpath.."/lib/conv/kernel_gaussian.lua") | ||||
| 
 | ||||
| dofile(worldeditadditions.modpath.."/lib/conv/convolve.lua") | ||||
							
								
								
									
										43
									
								
								worldeditadditions/lib/convolution/convolve.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								worldeditadditions/lib/convolution/convolve.lua
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,43 @@ | ||||
| 
 | ||||
| --[[ | ||||
| Convolves over a given 2D heightmap with a given matrix. | ||||
| Note that this *mutates* the given heightmap. | ||||
| Note also that the dimensions of the matrix must *only* be odd. | ||||
| @param	{number[]}			heightmap		The 2D heightmap to convolve over. | ||||
| @param	{[number,number]}	heightmap_size	The size of the heightmap as [ height, width ] | ||||
| @param	{number[]}			matrix			The matrix to convolve with. | ||||
| @param	{[number, number]}	matrix_size		The size of the convolution matrix as [ height, width ] | ||||
| ]]-- | ||||
| function worldeditadditions.conv.convole(heightmap, heightmap_size, matrix, matrix_size) | ||||
| 	if matrix_size[0] % 2 ~= 1 or matrix_size[1] % 2 ~= 1 then | ||||
| 		return false, "Error: The matrix size must contain only odd numbers (even number detected)" | ||||
| 	end | ||||
| 	 | ||||
| 	local border_size = { | ||||
| 		(matrix_size[0]-1) / 2,	-- height | ||||
| 		(matrix_size[1]-1) / 2	-- width | ||||
| 	} | ||||
| 	 | ||||
| 	-- Convolve over only the bit that allows us to use the full convolution matrix | ||||
| 	for y = heightmap_size[0] - border_size[0], border_size[0], -1 do | ||||
| 		for x = heightmap_size[1] - border_size[1], border_size[1], -1 do | ||||
| 			local total = 0 | ||||
| 			 | ||||
| 			for my = matrix_size[0], 0, -1 do | ||||
| 				for mx = matrix_size[1], 0, -1 do | ||||
| 					local mi = (my * matrix_size[1]) + mx | ||||
| 					local cy = y + (my - border_size[0]) | ||||
| 					local cx = x + (mx - border_size[1]) | ||||
| 					 | ||||
| 					local i = (cy * heightmap_size[1]) + cx | ||||
| 					 | ||||
| 					total = total + matrix[mi] * heightmap[i] | ||||
| 				end | ||||
| 			end | ||||
| 			 | ||||
| 			heightmap[(y * heightmap_size[1]) + x] = total | ||||
| 		end | ||||
| 	end | ||||
| 	 | ||||
| 	return true, heightmap | ||||
| end | ||||
							
								
								
									
										51
									
								
								worldeditadditions/lib/convolution/kernel_gaussian.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								worldeditadditions/lib/convolution/kernel_gaussian.lua
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,51 @@ | ||||
| -- Ported from Javascript by Starbeamrainbowlabs | ||||
| -- Original source: https://github.com/sidorares/gaussian-convolution-kernel/ | ||||
| -- From  | ||||
| -- the code is taken from https://github.com/mattlockyer/iat455/blob/6493c882f1956703133c1bffa1d7ee9a83741cbe/assignment1/assignment/effects/blur-effect-dyn.js | ||||
| -- (c) Matt Lockyer, https://github.com/mattlockyer | ||||
| 
 | ||||
| -- hypotenuse has moved to utils/numbers.lua | ||||
| 
 | ||||
| --[[ | ||||
|  * Generates a kernel used for the gaussian blur effect. | ||||
|  * | ||||
|  * @param dimension is an odd integer | ||||
|  * @param sigma is the standard deviation used for our gaussian function. | ||||
|  * | ||||
|  * @returns an array with dimension^2 number of numbers, all less than or equal | ||||
|  *	 to 1. Represents our gaussian blur kernel. | ||||
| ]]-- | ||||
| function worldeditadditions.conv.kernel_gaussian(dimension, sigma) | ||||
| 	if not (dimension % 2) or math.floor(dimension) ~= dimension or dimension < 3 then | ||||
| 		return false, "The dimension must be an odd integer greater than or equal to 3" | ||||
| 	end | ||||
| 	local kernel = {}; | ||||
| 
 | ||||
| 	local two_sigma_square = 2 * sigma * sigma; | ||||
| 	local centre = (dimension - 1) / 2; | ||||
| 	 | ||||
| 	local sum = 0 | ||||
| 	for i = 0, dimension-1 do | ||||
| 		for j = 0, dimension-1 do | ||||
| 			local distance = worldeditadditions.hypotenuse(i, j, centre, centre) | ||||
| 
 | ||||
| 			-- The following is an algorithm that came from the gaussian blur | ||||
| 			-- wikipedia page [1]. | ||||
| 			-- | ||||
| 			-- http://en.wikipedia.org/w/index.php?title=Gaussian_blur&oldid=608793634#Mechanics | ||||
| 			local gaussian = (1 / math.sqrt( | ||||
| 				math.pi * two_sigma_square | ||||
| 			)) * math.exp((-1) * (math.pow(distance, 2) / two_sigma_square)); | ||||
| 			 | ||||
| 			sum = sum + gaussian | ||||
| 			kernel[i*dimension + j] = gaussian | ||||
| 		end | ||||
| 	end | ||||
| 	 | ||||
| 	-- Returns the unit vector of the kernel array. | ||||
| 	for k,v in pairs(kernel) do | ||||
| 		kernel[k] = kernel[k] / sum | ||||
| 	end | ||||
| 	 | ||||
| 	return kernel | ||||
| end | ||||
							
								
								
									
										94
									
								
								worldeditadditions/lib/convolution/kernels.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								worldeditadditions/lib/convolution/kernels.lua
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,94 @@ | ||||
| --- Creates a (normalised) box convolutional kernel. | ||||
| -- Larger box kernels will obviously be slower, but will produce a more blurred | ||||
| -- effect (i.e. smoother terrain). | ||||
| -- @param	width	number	The width of the kernel. | ||||
| -- @param	height	number	The height of the kernel. | ||||
| -- @return	The resulting kernel as a ZERO-indexed list of numbers. | ||||
| function worldeditadditions.conv.kernel_box(width, height) | ||||
| 	local result = {} | ||||
| 	local total = 0 | ||||
| 	for y = 0, height do | ||||
| 		for x = 0, width do | ||||
| 			result[(y*width) + x] = 1 | ||||
| 			total = total + 1 | ||||
| 		end | ||||
| 	end | ||||
| 	-- Ensure that everything sums up to 1 | ||||
| 	for i = 0, #result do | ||||
| 		result[i] = result[i] / total | ||||
| 	end | ||||
| 	return result | ||||
| end | ||||
| 
 | ||||
| --- Computes the Lth line of Pascal's triangle. | ||||
| -- More information: https://en.wikipedia.org/wiki/Pascal%27s_triangle | ||||
| -- There are probably more efficient ways to it that don't repeat themselves as | ||||
| -- much, but this is my solution. | ||||
| -- @param l				number	The 1-indexed row of Pascal's Triangle to return. | ||||
| -- @return number[]		A ZERO-indexed list of numbers in the specified row of Pascal's Triangle. | ||||
| local function pascal(l) | ||||
| 	local prev = {} | ||||
| 	prev[0] = 1 | ||||
| 	if l == 1 then return prev end | ||||
| 	prev[1] = 1 | ||||
| 	if l == 2 then return prev end | ||||
| 	local length_last = 2 | ||||
| 	 | ||||
| 	for n=3, l do | ||||
| 		local next = {} | ||||
| 		for i=0, length_last do | ||||
| 			if i == 0 or i == length_last then | ||||
| 				next[i] = 1 | ||||
| 			else | ||||
| 				next[i] = prev[i - 1] + prev[i] | ||||
| 			end | ||||
| 		end | ||||
| 		prev = next | ||||
| 		length_last = length_last + 1 | ||||
| 	end | ||||
| 	return prev | ||||
| end | ||||
| 
 | ||||
| --- Creates a pascal convolutional kernel. | ||||
| -- Larger pascal kernels will obviously be slower, but will produce a more blurred | ||||
| -- effect (i.e. smoother terrain). | ||||
| -- @param	width			number	The width of the kernel. | ||||
| -- @param	height			number	The height of the kernel. | ||||
| -- @param	normalise=true	bool	Whether to normalise the resulting kernel (default: true) | ||||
| -- @return	The resulting kernel as a ZERO-indexed list of numbers. | ||||
| function worldeditadditions.conv.kernel_pascal(width, height, normalise) | ||||
| 	if normalise == nil then normalise = true end | ||||
| 	 | ||||
| 	local result = {} | ||||
| 	local pascal_width = width | ||||
| 	local height_middle = ((height - 2) / 2) | ||||
| 	local total = 0 | ||||
| 	for y = 0, height-1 do | ||||
| 		local pascal_numbers = pascal(pascal_width) | ||||
| 		local pascal_start = (pascal_width - width) / 2 | ||||
| 		for x = 0, width - 1 do | ||||
| 			result[(y*width) + x] = pascal_numbers[pascal_start + x] | ||||
| 			total = total + pascal_numbers[pascal_start + x] | ||||
| 		end | ||||
| 		 | ||||
| 		if y > height_middle then pascal_width = pascal_width - 2 | ||||
| 		else pascal_width = pascal_width + 2 end | ||||
| 	end | ||||
| 	 | ||||
| 	if normalise then | ||||
| 		for k,v in pairs(result) do | ||||
| 			result[k] = result[k] / total | ||||
| 		end | ||||
| 	end | ||||
| 	 | ||||
| 	return result | ||||
| end | ||||
| 
 | ||||
| -- print("box, 5x5") | ||||
| -- print_2d(kernel_box(5, 5), 5) | ||||
| -- print("pascal, 5x5") | ||||
| -- print_2d(kernel_pascal(5, 5), 5) | ||||
| -- print("pascal, 7x7") | ||||
| -- print_2d(kernel_pascal(7, 7), 7) | ||||
| -- print("pascal, 9x9") | ||||
| -- print_2d(kernel_pascal(9, 9), 9) | ||||
| @ -3,3 +3,9 @@ function worldeditadditions.round(num, numDecimalPlaces) | ||||
|   local mult = 10^(numDecimalPlaces or 0) | ||||
|   return math.floor(num * mult + 0.5) / mult | ||||
| end | ||||
| 
 | ||||
| function worldeditadditions.hypotenuse(x1, y1, x2, y2) | ||||
| 	local xSquare = math.pow(x1 - x2, 2); | ||||
| 	local ySquare = math.pow(y1 - y2, 2); | ||||
| 	return math.sqrt(xSquare + ySquare); | ||||
| end | ||||
|  | ||||
| @ -73,10 +73,31 @@ function worldeditadditions.str_padstart(str, len, char) | ||||
|     return string.rep(char, len - #str) .. str | ||||
| end | ||||
| 
 | ||||
| --- Equivalent to string.startsWith in JS | ||||
| -- @param	str		string	The string to operate on | ||||
| -- @param	start	number	The start string to look for | ||||
| -- @returns	bool	Whether start is present at the beginning of str | ||||
| function worldeditadditions.string_starts(str,start) | ||||
|    return string.sub(str,1,string.len(start))==start | ||||
| end | ||||
| 
 | ||||
| --- Prints a 2d array of numbers formatted like a JS TypedArray (e.g. like a manip node list or a convolutional kernel) | ||||
| -- In other words, the numbers should be formatted as a single flat array. | ||||
| -- @param	tbl		number[]	The ZERO-indexed list of numbers | ||||
| -- @param	width	number		The width of 2D array. | ||||
| function worldeditadditions.print_2d(tbl, width) | ||||
| 	local next = {} | ||||
| 	for i=0, #tbl do | ||||
| 		table.insert(next, tbl[i]) | ||||
| 		if #next == width then | ||||
| 			print(table.concat(next, "\t")) | ||||
| 			next = {} | ||||
| 		end | ||||
| 	end | ||||
| end | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| --- Turns an associative node_id → count table into a human-readable list. | ||||
| -- Works well with worldeditadditions.make_ascii_table(). | ||||
| function worldeditadditions.node_distribution_to_list(distribution, nodes_total) | ||||
| @ -127,8 +148,9 @@ function worldeditadditions.make_ascii_table(data, total) | ||||
| 	return table.concat(result, "\n") | ||||
| end | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| --- Parses a list of strings as a list of weighted nodes - e.g. like in the //mix command. | ||||
| -- @param	parts	string[]	The list of strings to parse (try worldeditadditions.split) | ||||
| -- @returns	table	A table in the form node_name => weight. | ||||
| function worldeditadditions.parse_weighted_nodes(parts) | ||||
| 	local MODE_EITHER = 1 | ||||
| 	local MODE_NODE = 2 | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user