From 2ae241aee527e714f76b08c664a4f97af08852c7 Mon Sep 17 00:00:00 2001
From: Starbeamrainbowlabs <sbrl@starbeamrainbowlabs.com>
Date: Thu, 14 Oct 2021 01:50:27 +0100
Subject: [PATCH] Add //ellipsoid2

---
 CHANGELOG.md                                  |  3 +-
 Chat-Command-Reference.md                     | 12 +++
 README.md                                     |  1 +
 worldeditadditions/init.lua                   |  1 +
 worldeditadditions/lib/ellipsoid2.lua         | 76 +++++++++++++++++++
 .../commands/ellipsoid2.lua                   | 51 +++++++++++++
 worldeditadditions_commands/init.lua          |  1 +
 7 files changed, 144 insertions(+), 1 deletion(-)
 create mode 100644 worldeditadditions/lib/ellipsoid2.lua
 create mode 100644 worldeditadditions_commands/commands/ellipsoid2.lua

diff --git a/CHANGELOG.md b/CHANGELOG.md
index a0305cf..e6c3891 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,7 +9,8 @@ Note to self: See the bottom of this file for the release template text.
  - Add `//airapply` for applying commands only to air nodes in the defined region
  - Add `//noise2d` for perturbing terrain with multiple different noise functions
  - Add `//noiseapply2d` for running commands on columns where a noise value is over a threshold
- - Use [luacheck](https://github.com/mpeterv/luacheck) to find and fix a large number of bugs and other issues
+ - Add `//ellipsoid2` which creates an ellipsoid that fills the defined region
+ - Use [luacheck](https://github.com/mpeterv/luacheck) to find and fix a large number of bugs and other issues [code quality from now on will be significantly improved]
  - Multiple commands: Allow using quotes (`"thing"`, `'thing'`) to quote values when splitting
  - `//layers`: Add optional slope constraint (inspired by [WorldPainter](https://worldpainter.net/))
  - `//bonemeal`: Add optional node list constraint
diff --git a/Chat-Command-Reference.md b/Chat-Command-Reference.md
index 86bc4ca..de93b5a 100644
--- a/Chat-Command-Reference.md
+++ b/Chat-Command-Reference.md
@@ -42,6 +42,18 @@ Creates a hollow ellipsoid at position 1 with the radius `(rx, ry, rz)`. Works t
 //hollowellipsoid 21 11 41 stone
 ```
 
+### `//ellipsoid2 [<node_name:dirt> [h[ollow]]]`
+Creates an (optionally hollow) solid ellipsoid that fills the defined region.
+
+```weacmd
+//ellipsoid2
+//ellipsoid2 ice
+//ellipsoid2 air
+//ellipsoid2 steelblock h
+//ellipsoid2 papyrus hollow
+```
+
+
 ### `//torus <major_radius> <minor_radius> <node_name> [<axes=xy> [h[ollow]]]`
 Creates a solid torus at position 1 with the specified major and minor radii. The major radius is the distance from the centre of the torus to the centre of the circle bit, and the minor radius is the radius of the circle bit.
 
diff --git a/README.md b/README.md
index 670ef3a..4ebc5f7 100644
--- a/README.md
+++ b/README.md
@@ -29,6 +29,7 @@ The detailed explanations have moved! Check them out [here](https://github.com/s
 
 ### Geometry
  - [`//ellipsoid <rx> <ry> <rz> <node_name> [h[ollow]]`](https://worldeditadditions.mooncarrot.space/Reference/#ellipsoid)
+ - [`//ellipsoid2 <node_name> [h[ollow]]`](https://worldeditadditions.mooncarrot.space/Reference/#ellipsoid2)
  - [`//hollowellipsoid <rx> <ry> <rz> <node_name>`](https://worldeditadditions.mooncarrot.space/Reference/#hollowellipsoid)
  - [`//torus <major_radius> <minor_radius> <node_name> [<axes=xy> [h[ollow]]]`](https://worldeditadditions.mooncarrot.space/Reference/#torus)
  - [`//hollowtorus <major_radius> <minor_radius> <node_name> [<axes=xy>]`](https://worldeditadditions.mooncarrot.space/Reference/#hollowtorus)
diff --git a/worldeditadditions/init.lua b/worldeditadditions/init.lua
index 8dfe215..2e83be2 100644
--- a/worldeditadditions/init.lua
+++ b/worldeditadditions/init.lua
@@ -39,6 +39,7 @@ dofile(wea.modpath.."/lib/overlay.lua")
 dofile(wea.modpath.."/lib/layers.lua")
 dofile(wea.modpath.."/lib/fillcaves.lua")
 dofile(wea.modpath.."/lib/ellipsoid.lua")
+dofile(wea.modpath.."/lib/ellipsoid2.lua")
 dofile(wea.modpath.."/lib/torus.lua")
 dofile(wea.modpath.."/lib/line.lua")
 dofile(wea.modpath.."/lib/walls.lua")
diff --git a/worldeditadditions/lib/ellipsoid2.lua b/worldeditadditions/lib/ellipsoid2.lua
new file mode 100644
index 0000000..6a677dd
--- /dev/null
+++ b/worldeditadditions/lib/ellipsoid2.lua
@@ -0,0 +1,76 @@
+local wea = worldeditadditions
+
+-- ███████ ██      ██      ██ ██████  ███████ ███████
+-- ██      ██      ██      ██ ██   ██ ██      ██
+-- █████   ██      ██      ██ ██████  ███████ █████
+-- ██      ██      ██      ██ ██           ██ ██
+-- ███████ ███████ ███████ ██ ██      ███████ ███████
+
+
+function worldeditadditions.ellipsoid2(pos1, pos2, target_node, hollow)
+	pos1, pos2 = wea.Vector3.sort(pos1, pos2)
+	local volume = pos2:subtract(pos1)
+	local volume_half = volume:divide(2)
+	
+	local radius = pos2:subtract(pos1):divide(2)
+	
+	print("DEBUG:ellipsoid2 | pos1: "..pos1..", pos2: "..pos2..", target_node: "..target_node)
+	print("DEBUG:ellipsoid2 radius", radius)
+	
+	-- position = { x, y, z }
+	local hollow_inner_radius = {
+		x = radius.x - 1,
+		y = radius.y - 1,
+		z = radius.z - 1
+	}
+	
+	-- Fetch the nodes in the specified area
+	-- OPTIMIZE: We should be able to calculate a more efficient box-area here
+	local manip, area = worldedit.manip_helpers.init(pos1, pos2)
+	local data = manip:get_data()
+	
+	local node_id = minetest.get_content_id(target_node)
+	
+	local count = 0 -- The number of nodes replaced
+	
+	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 pos_relative = wea.Vector3.new(x, y, z):subtract(pos1)
+					:subtract(volume_half)
+				
+				print("DEBUG pos1", pos1, "pos2", pos2, "volume_half", volume_half, "pos_relative", pos_relative)
+				
+				-- If we're inside the ellipse, then fill it in
+				local comp = pos_relative:divide(radius)
+				local ellipsoid_dist = comp:length_squared()
+				if ellipsoid_dist <= 1 then
+					local place_ok = not hollow;
+					
+					if not place_ok then
+						-- It must be hollow! Do some additional calculations.
+						local hx_comp = x/hollow_inner_radius.x
+						local hy_comp = y/hollow_inner_radius.y
+						local hz_comp = z/hollow_inner_radius.z
+						
+						-- It's only ok to place it if it's outside our inner ellipse
+						place_ok = hx_comp*hx_comp + hy_comp*hy_comp + hz_comp*hz_comp >= 1
+					end
+					
+					if place_ok then
+						data[area:index(x, y, z)] = node_id
+						count = count + 1
+					end
+				end
+				
+			end
+		end
+	end
+	
+	
+	-- Save the modified nodes back to disk & return
+	worldedit.manip_helpers.finish(manip, data)
+	
+	return count
+end
diff --git a/worldeditadditions_commands/commands/ellipsoid2.lua b/worldeditadditions_commands/commands/ellipsoid2.lua
new file mode 100644
index 0000000..541532a
--- /dev/null
+++ b/worldeditadditions_commands/commands/ellipsoid2.lua
@@ -0,0 +1,51 @@
+-- ███████ ██      ██      ██ ██████  ███████  ██████  ██ ██████
+-- ██      ██      ██      ██ ██   ██ ██      ██    ██ ██ ██   ██
+-- █████   ██      ██      ██ ██████  ███████ ██    ██ ██ ██   ██
+-- ██      ██      ██      ██ ██           ██ ██    ██ ██ ██   ██
+-- ███████ ███████ ███████ ██ ██      ███████  ██████  ██ ██████
+local wea = worldeditadditions
+
+worldedit.register_command("ellipsoid2", {
+	params = "[<replace_node:dirt> [h[ollow]]]",
+	description = "Creates am optionally hollow 3D ellipsoid that fills the defined region, filled with <replace_node>.",
+	privs = { worldedit = true },
+	require_pos = 2,
+	parse = function(params_text)
+		if not params_text or params_text == "" then
+			params_text = "dirt"
+		end
+		
+		local parts = wea.split_shell(params_text)
+		
+		
+		local replace_node = worldedit.normalize_nodename(parts[1])
+		if not replace_node then
+			return false, "Error: Invalid replace_node specified."
+		end
+		
+		local hollow = false
+		if parts[2] == "hollow" or parts[2] == "h" then
+			hollow = true
+		end
+		
+		return true, replace_node, hollow
+	end,
+	nodes_needed = function(name, target_node)
+		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, target_node, radius, hollow)
+		local start_time = wea.get_ms_time()
+		local pos1, pos2 = wea.Vector3.sort(worldedit.pos1[name], worldedit.pos2[name])
+		
+		local replaced = wea.ellipsoid2(
+			pos1, pos2,
+			target_node,
+			hollow
+		)
+		local time_taken = wea.get_ms_time() - start_time
+		
+		minetest.log("action", name .. " used //ellipsoid2 at "..pos1.." - "..pos2..", replacing " .. replaced .. " nodes in " .. time_taken .. "s")
+		return true, replaced .. " nodes replaced in " .. wea.format.human_time(time_taken)
+	end
+})
diff --git a/worldeditadditions_commands/init.lua b/worldeditadditions_commands/init.lua
index cc3cdf5..491669d 100644
--- a/worldeditadditions_commands/init.lua
+++ b/worldeditadditions_commands/init.lua
@@ -21,6 +21,7 @@ dofile(we_c.modpath.."/player_notify_suppress.lua")
 
 dofile(we_c.modpath.."/commands/convolve.lua")
 dofile(we_c.modpath.."/commands/ellipsoid.lua")
+dofile(we_c.modpath.."/commands/ellipsoid2.lua")
 dofile(we_c.modpath.."/commands/erode.lua")
 dofile(we_c.modpath.."/commands/fillcaves.lua")
 dofile(we_c.modpath.."/commands/floodfill.lua")
