Merge branch 'main' into selection-tools

This commit is contained in:
VorTechnix 2021-06-27 15:22:55 -07:00
commit 29bb6a0063
43 changed files with 1848 additions and 41 deletions

View File

@ -27,10 +27,10 @@ Explaining core WorldEdit commands is out of scope of this tutorial, but you can
The purpose of _WorldEditAdditions_ is to extend _WorldEdit_ by adding additional commands. Example commands that have been implemented that are not present in core _WorldEdit_ include (but certainly aren't limited to):
- [`//maze`](/Reference/#maze-replace_node-path_length-path_width-seed): Create instant mazes
- [`//forest`](/Reference/#forest-density-sapling_a-chance_a-sapling_b-chance_b-sapling_n-chance_n-): Plant forests
- [`//torus`](http://localhost:8080/Reference/#torus-major_radius-minor_radius-node_name-axesxy-hollow): Generate [torus](https://en.wikipedia.org/wiki/Torus) shapes
- [`//scale`](/Reference/#scale-axis-scale_factor-factor_x-factor_y-factor_z-anchor_x-anchor_y-anchor_z): Scale things up and down - even both at the same time!
- [`//maze`](/Reference/#maze): Create instant mazes
- [`//forest`](/Reference/#forest): Plant forests
- [`//torus`](http://localhost:8080/Reference/#torus): Generate [torus](https://en.wikipedia.org/wiki/Torus) shapes
- [`//scale`](/Reference/#scale): Scale things up and down - even both at the same time!
See a full list with complete explanations in the [chat command reference](/Reference).
@ -98,9 +98,9 @@ A number of additional concepts that are not required to use WorldEditAdditions
### Meta commands
WorldEditAdditions provides a number of *meta commands*. Such commands don't do anything on their own, but call other commands in various different ways. Examples of meta commands include:
- [`//subdivide`](/Reference#subdivide-size_x-size_y-size_z-cmd_name-args): split a region into chunks, and execute the command once for each chunk
- [`//many`](/Reference#many-times-command): Execute a command multiple times
- [`//multi`](/Reference#multi-command_a-command_b-command_c-): Execute multiple commands in sequence
- [`//subdivide`](/Reference#subdivide): split a region into chunks, and execute the command once for each chunk
- [`//many`](/Reference#many): Execute a command multiple times
- [`//multi`](/!node_modules, !_sitemulti): Execute multiple commands in sequence
Of course, this isn't an exhaustive list - check the [reference](/Reference) for a full list.
@ -109,7 +109,7 @@ Memory (or RAM - Random Access Memory) is used by all the processes running on a
Depending on your system, Minetest and your system can slow to a crawl or even crash if you execute a command on a region that's too big.
To work around this, the [`//subdivide`](/Reference#subdivide-size_x-size_y-size_z-cmd_name-args) command was implemented. It splits the defined region into chunks, and calls the specified command over and over again for each chunk.
To work around this, the [`//subdivide`](/Reference#subdivide) command was implemented. It splits the defined region into chunks, and calls the specified command over and over again for each chunk.
It's not suitable for all commands (since it requires that said command takes 2 points) though, but because it splits the defined region into multiple chunks, it can be executed on *enormous* regions that can't fit into memory all at the same time.

View File

@ -15,7 +15,8 @@ module.exports = function parse_sections(source) {
result.push({
title: htmlentities.encode(title),
slug: title.toLowerCase().replace(/[^a-z0-9-_\s]+/gi, "")
.replace(/\s+/g, "-"),
.replace(/\s+/g, "-")
.replace(/-.*$/, ""),
content: markdown.render(acc.slice(1).join("\n"))
});
}

View File

@ -0,0 +1,35 @@
local Vector3 = require("worldeditadditions.utils.vector3")
describe("Vector3.abs", function()
it("should work with a positive vector", function()
local a = Vector3.new(16, 64, 16)
assert.are.same(
Vector3.new(16, 64, 16),
a:abs()
)
end)
it("should abs another positive vector", function()
local a = Vector3.new(9, 16, 25)
assert.are.same(
Vector3.new(9, 16, 25),
a:abs()
)
end)
it("should abs a negative vector", function()
local a = Vector3.new(-9, -16, -25)
assert.are.same(
Vector3.new(9, 16, 25),
a:abs()
)
end)
it("should return a new Vector3 instance", function()
local a = Vector3.new(9, -16, 25)
local result = a:abs()
assert.are.same(
Vector3.new(9, 16, 25),
result
)
assert.are_not.equal(result, a)
end)
end)

View File

@ -0,0 +1,80 @@
local Vector3 = require("worldeditadditions.utils.vector3")
describe("Vector3.add", function()
it("should add 2 positive vectors", function()
local a = Vector3.new(3, 4, 5)
local b = Vector3.new(1, 1, 1)
assert.are.same(
Vector3.new(4, 5, 6),
a:add(b)
)
end)
it("should support the add operator", function()
local a = Vector3.new(3, 4, 5)
local b = Vector3.new(1, 1, 1)
assert.are.same(
Vector3.new(4, 5, 6),
a + b
)
end)
it("should work with floats", function()
local a = Vector3.new(3.5, 4.5, 5.5)
local b = Vector3.new(1.1, 1.1, 1.1)
assert.are.same(
Vector3.new(4.6, 5.6, 6.6),
a + b
)
end)
it("should work with scalar a", function()
local a = 2
local b = Vector3.new(6, 7, 8)
assert.are.same(
Vector3.new(8, 9, 10),
a + b
)
end)
it("should work with scalar b", function()
local a = Vector3.new(6, 7, 8)
local b = 2
assert.are.same(
Vector3.new(8, 9, 10),
a + b
)
end)
it("should handle negative b", function()
local a = Vector3.new(3, 4, 5)
local b = Vector3.new(-1, -1, -1)
assert.are.same(
Vector3.new(2, 3, 4),
a + b
)
end)
it("should handle negative a", function()
local a = Vector3.new(-3, -4, -5)
local b = Vector3.new(1, 1, 1)
assert.are.same(
Vector3.new(-2, -3, -4),
a + b
)
end)
it("should handle negative a and b", function()
local a = Vector3.new(-3, -4, -5)
local b = Vector3.new(-1, -1, -1)
assert.are.same(
Vector3.new(-4, -5, -6),
a + b
)
end)
it("should return a new Vector3 instance", function()
local a = Vector3.new(3, 4, 5)
local b = Vector3.new(1, 1, 1)
local result = a + b
assert.are.same(
Vector3.new(4, 5, 6),
result
)
assert.are_not.equal(result, a)
assert.are_not.equal(result, b)
end)
end)

View File

@ -0,0 +1,25 @@
local Vector3 = require("worldeditadditions.utils.vector3")
describe("Vector3.area", function()
it("should work with a positive vector", function()
local a = Vector3.new(3, 3, 3)
assert.are.equal(
27,
a:area()
)
end)
it("should work with a negative vector", function()
local a = Vector3.new(-4, -4, -4)
assert.are.equal(
-64,
a:area()
)
end)
it("should work with a mixed vector", function()
local a = Vector3.new(-3, 3, -3)
assert.are.equal(
27,
a:area()
)
end)
end)

View File

@ -0,0 +1,35 @@
local Vector3 = require("worldeditadditions.utils.vector3")
describe("Vector3.ceil", function()
it("should ceil a positive vector", function()
local a = Vector3.new(3.1, 4.2, 5.8)
assert.are.same(
Vector3.new(4, 5, 6),
a:ceil()
)
end)
it("should ceil a negative vector", function()
local a = Vector3.new(-3.1, -4.2, -5.3)
assert.are.same(
Vector3.new(-3, -4, -5),
a:ceil()
)
end)
it("should work with integers", function()
local a = Vector3.new(3, 4, 5)
assert.are.same(
a:ceil(),
Vector3.new(3, 4, 5)
)
end)
it("should return a new Vector3 instance", function()
local a = Vector3.new(3.1, 4.7, 5.99999)
local result = a:ceil()
assert.are.same(
Vector3.new(4, 5, 6),
result
)
assert.are_not.equal(result, a)
end)
end)

View File

@ -0,0 +1,20 @@
local Vector3 = require("worldeditadditions.utils.vector3")
describe("Vector3.clone", function()
it("should return a new Vector3 instance", function()
local a = Vector3.new(3, 4, 5)
local result = a:clone()
result.x = 4
assert.are.same(Vector3.new(3, 4, 5), a)
assert.are.same(Vector3.new(4, 4, 5), result)
end)
it("should return a new Vector3 instance for a different vector", function()
local a = Vector3.new(-99, 66, 88)
local result = a:clone()
result.y = -44
assert.are.same(Vector3.new(-99, 66, 88), a)
assert.are.same(Vector3.new(-99, -44, 88), result)
end)
end)

View File

@ -0,0 +1,80 @@
local Vector3 = require("worldeditadditions.utils.vector3")
describe("Vector3.divide", function()
it("should divide 2 positive vectors", function()
local a = Vector3.new(30, 40, 50)
local b = Vector3.new(2, 2, 2)
assert.are.same(
Vector3.new(15, 20, 25),
a:divide(b)
)
end)
it("should work with the div alias", function()
local a = Vector3.new(30, 40, 50)
local b = Vector3.new(2, 2, 2)
assert.are.same(
Vector3.new(15, 20, 25),
a:div(b)
)
end)
it("should work with scalar a", function()
local a = 2
local b = Vector3.new(12, 14, 16)
assert.are.same(
Vector3.new(6, 7, 8),
a / b
)
end)
it("should work with scalar b", function()
local a = Vector3.new(6, 8, 10)
local b = 2
assert.are.same(
Vector3.new(3, 4, 5),
a / b
)
end)
it("should support the divide operator", function()
local a = Vector3.new(10, 12, 14)
local b = Vector3.new(2, 3, 2)
assert.are.same(
Vector3.new(5, 4, 7),
a / b
)
end)
it("should handle negative b", function()
local a = Vector3.new(30, 40, 50)
local b = Vector3.new(-2, -2, -2)
assert.are.same(
Vector3.new(-15, -20, -25),
a / b
)
end)
it("should handle negative a", function()
local a = Vector3.new(-30, -40, -50)
local b = Vector3.new(2, 4, 2)
assert.are.same(
Vector3.new(-15, -10, -25),
a / b
)
end)
it("should handle negative a and b", function()
local a = Vector3.new(-30, -40, -50)
local b = Vector3.new(-2, -2, -2)
assert.are.same(
Vector3.new(15, 20, 25),
a / b
)
end)
it("should return a new Vector3 instance", function()
local a = Vector3.new(9, 12, 15)
local b = Vector3.new(3, 3, 3)
local result = a / b
assert.are.same(
Vector3.new(3, 4, 5),
result
)
assert.are_not.equal(result, a)
assert.are_not.equal(result, b)
end)
end)

View File

@ -0,0 +1,36 @@
local Vector3 = require("worldeditadditions.utils.vector3")
describe("Vector3.dot", function()
it("should work with a positive vector", function()
local a = Vector3.new(3, 3, 3)
local b = Vector3.new(4, 5, 6)
assert.are.equal(
45,
a:dot(b)
)
end)
it("should work with a negative vector", function()
local a = Vector3.new(-4, -4, -4)
local b = Vector3.new(4, 5, 6)
assert.are.equal(
-60,
a:dot(b)
)
end)
it("should work with a mixed vector", function()
local a = Vector3.new(-3, 3, -3)
local b = Vector3.new(7, 8, 9)
assert.are.equal(
-24,
a:dot(b)
)
end)
it("should work with the dot_product alias", function()
local a = Vector3.new(-3, 3, -3)
local b = Vector3.new(7, 8, 9)
assert.are.equal(
-24,
a:dot_product(b)
)
end)
end)

View File

@ -0,0 +1,40 @@
local Vector3 = require("worldeditadditions.utils.vector3")
describe("Vector3.equals", function()
it("should return true when identical", function()
local a = Vector3.new(3, 4, 5)
local b = Vector3.new(3, 4, 5)
assert.are.same(
true,
a:equals(b)
)
end)
it("should return false when not identical", function()
local a = Vector3.new(3, 4, 5)
local b = Vector3.new(6, 7, 8)
assert.are.same(
false,
a:equals(b)
)
end)
it("should return false when not identical x", function()
local a = Vector3.new(3, 4, 5)
local b = Vector3.new(4, 4, 5)
assert.are.same(
false,
a:equals(b)
)
end)
it("should return false when not identical y", function()
local a = Vector3.new(3, 4, 5)
local b = Vector3.new(3, 5, 5)
assert.are.same(
false,
a:equals(b)
)
end)
end)

View File

@ -0,0 +1,48 @@
local Vector3 = require("worldeditadditions.utils.vector3")
describe("Vector3.expand_region", function()
it("should work with positive vectors", function()
local a = Vector3.new(16, 64, 16)
local b = Vector3.new(1, 4, 6)
local target = Vector3.new(99, 99, 99)
local result_a, result_b = target:expand_region(a, b)
assert.are.same(Vector3.new(1, 4, 6), result_a)
assert.are.same(Vector3.new(99, 99, 99), result_b)
end)
it("should work with mixed components", function()
local a = Vector3.new(16, 1, 16)
local b = Vector3.new(1, 4, 60)
local target = Vector3.new(-99, -99, -99)
local result_a, result_b = target:expand_region(a, b)
assert.are.same(Vector3.new(-99, -99, -99), result_a)
assert.are.same(Vector3.new(16, 4, 60), result_b)
end)
it("should work with negative vectors", function()
local a = Vector3.new(-9, -16, -25)
local b = Vector3.new(-3, -6, -2)
local target = Vector3.new(-99, -99, -99)
local result_a, result_b = target:expand_region(a, b)
assert.are.same(Vector3.new(-99, -99, -99), result_a)
assert.are.same(Vector3.new(-3, -6, -2), result_b)
end)
it("should return new Vector3 instances", function()
local a = Vector3.new(16, 1, 16)
local b = Vector3.new(1, 4, 60)
local target = Vector3.new(99, 99, 99)
local result_a, result_b = target:expand_region(a, b)
assert.are.same(Vector3.new(1, 1, 16), result_a)
assert.are.same(Vector3.new(99, 99, 99), result_b)
result_a.y = 999
result_b.y = 999
assert.are.same(Vector3.new(16, 1, 16), a)
assert.are.same(Vector3.new(1, 4, 60), b)
assert.are.same(Vector3.new(1, 999, 16), result_a)
assert.are.same(Vector3.new(99, 999, 99), result_b)
end)
end)

View File

@ -0,0 +1,35 @@
local Vector3 = require("worldeditadditions.utils.vector3")
describe("Vector3.floor", function()
it("should floor a positive vector", function()
local a = Vector3.new(3.1, 4.75, 5.9)
assert.are.same(
Vector3.new(3, 4, 5),
a:floor()
)
end)
it("should floor a negative vector", function()
local a = Vector3.new(-3.1, -4.2, -5.3)
assert.are.same(
Vector3.new(-4, -5, -6),
a:floor()
)
end)
it("should work with integers", function()
local a = Vector3.new(3, 4, 5)
assert.are.same(
Vector3.new(3, 4, 5),
a:floor()
)
end)
it("should return a new Vector3 instance", function()
local a = Vector3.new(3.1, 4.7, 5.99999)
local result = a:floor()
assert.are.same(
Vector3.new(3, 4, 5),
result
)
assert.are_not.equal(result, a)
end)
end)

View File

@ -0,0 +1,44 @@
local Vector3 = require("worldeditadditions.utils.vector3")
describe("Vector3.is_contained", function()
it("should return true when inside", function()
local a = Vector3.new(3, 4, 5)
local b = Vector3.new(30, 40, 50)
local target = Vector3.new(6, 6, 6)
assert.are.same(
true,
target:is_contained(a, b)
)
end)
it("should return false when outside x", function()
local a = Vector3.new(3, 4, 5)
local b = Vector3.new(30, 40, 50)
local target = Vector3.new(60, 6, 6)
assert.are.same(
false,
target:is_contained(a, b)
)
end)
it("should return false when outside y", function()
local a = Vector3.new(3, 4, 5)
local b = Vector3.new(30, 40, 50)
local target = Vector3.new(6, 60, 6)
assert.are.same(
false,
target:is_contained(a, b)
)
end)
it("should return false when outside z", function()
local a = Vector3.new(3, 4, 5)
local b = Vector3.new(30, 40, 50)
local target = Vector3.new(6, 6, 60)
assert.are.same(
false,
target:is_contained(a, b)
)
end)
end)

View File

@ -0,0 +1,30 @@
local Vector3 = require("worldeditadditions.utils.vector3")
-- To find these numbers, in Javascript:
-- function t(x) { return Math.sqrt((x*x)*3); }
-- for(let i = 0; i < 1000000000; i++) { let r = t(i); if(Math.floor(r) === r) console.log(`i ${i}, r ${r}`); }
describe("Vector3.length", function()
it("should work with a positive vector", function()
local a = Vector3.new(80198051, 80198051, 80198051)
assert.are.equal(
138907099,
a:length()
)
end)
it("should work with a negative vector", function()
local a = Vector3.new(-189750626, -189750626, -189750626)
assert.are.equal(
328657725,
a:length()
)
end)
it("should work with a mixed vector", function()
local a = Vector3.new(-371635731, 371635731, -371635731)
assert.are.equal(
643691968,
a:length()
)
end)
end)

View File

@ -0,0 +1,25 @@
local Vector3 = require("worldeditadditions.utils.vector3")
describe("Vector3.length_squared", function()
it("should work with a positive vector", function()
local a = Vector3.new(3, 3, 3)
assert.are.equal(
27,
a:length_squared()
)
end)
it("should work with a negative vector", function()
local a = Vector3.new(-4, -4, -4)
assert.are.equal(
48,
a:length_squared()
)
end)
it("should work with a mixed vector", function()
local a = Vector3.new(-3, 3, -3)
assert.are.equal(
27,
a:length_squared()
)
end)
end)

View File

@ -0,0 +1,56 @@
local Vector3 = require("worldeditadditions.utils.vector3")
describe("Vector3.limit_to", function()
it("should limit_to a positive vector", function()
local a = Vector3.new(801980510, 801980510, 801980510)
assert.are.same(
Vector3.new(80198051, 80198051, 80198051),
a:limit_to(138907099)
)
end)
it("should limit_to a negative vector", function()
local a = Vector3.new(-1897506260, -1897506260, -1897506260)
assert.are.same(
Vector3.new(-189750626, -189750626, -189750626),
a:limit_to(328657725)
)
end)
it("should work if the length is borderline", function()
local a = Vector3.new(80198051, 80198051, 80198051)
assert.are.same(
Vector3.new(80198051, 80198051, 80198051),
a:limit_to(138907099)
)
end)
it("should not change anything if the length is smaller", function()
local a = Vector3.new(3, 4, 5)
assert.are.same(
Vector3.new(3, 4, 5),
a:limit_to(100)
)
end)
it("should return a new Vector3 instance", function()
local a = Vector3.new(801980510, 801980510, 801980510)
local result = a:limit_to(138907099)
assert.are.same(
Vector3.new(80198051, 80198051, 80198051),
result
)
a.x = 4
assert.are.same(Vector3.new(4, 801980510, 801980510), a)
assert.are.same(Vector3.new(80198051, 80198051, 80198051), result)
end)
it("should return a new Vector3 instance if the length is smaller", function()
local a = Vector3.new(3, 4, 5)
local result = a:limit_to(101)
assert.are.same(
Vector3.new(3, 4, 5),
result
)
a.x = 40
assert.are.same(Vector3.new(40, 4, 5), a)
assert.are.same(Vector3.new(3, 4, 5), result)
end)
end)

View File

@ -0,0 +1,44 @@
local Vector3 = require("worldeditadditions.utils.vector3")
describe("Vector3.max", function()
it("should work with positive vectors", function()
local a = Vector3.new(16, 64, 16)
local b = Vector3.new(1, 4, 6)
assert.are.same(
Vector3.new(16, 64, 16),
Vector3.max(a, b)
)
end)
it("should work with mixed components", function()
local a = Vector3.new(16, 1, 16)
local b = Vector3.new(1, 4, 60)
assert.are.same(
Vector3.new(16, 4, 60),
Vector3.max(a, b)
)
end)
it("should work with negative vectors", function()
local a = Vector3.new(-9, -16, -25)
local b = Vector3.new(-3, -6, -2)
assert.are.same(
Vector3.new(-3, -6, -2),
Vector3.max(a, b)
)
end)
it("should return new Vector3 instances", function()
local a = Vector3.new(16, 1, 16)
local b = Vector3.new(1, 4, 60)
local result = Vector3.max(a, b)
assert.are.same(Vector3.new(16, 4, 60), result)
result.y = 999
assert.are.same(Vector3.new(16, 1, 16), a)
assert.are.same(Vector3.new(1, 4, 60), b)
assert.are.same(Vector3.new(16, 999, 60), result)
end)
end)

View File

@ -0,0 +1,39 @@
local Vector3 = require("worldeditadditions.utils.vector3")
describe("Vector3.max_component", function()
it("should work with a positive vector x", function()
local a = Vector3.new(30, 4, 5)
assert.are.equal(
30,
a:max_component()
)
end)
it("should work with a positive vector y", function()
local a = Vector3.new(3, 10, 5)
assert.are.equal(
10,
a:max_component()
)
end)
it("should work with a positive vector z", function()
local a = Vector3.new(3, 1, 50.5)
assert.are.equal(
50.5,
a:max_component()
)
end)
it("should work with a negative vector", function()
local a = Vector3.new(-4, -5, -1)
assert.are.equal(
-1,
a:max_component()
)
end)
it("should work with a mixed vector", function()
local a = Vector3.new(-30, 3, -3)
assert.are.equal(
3,
a:max_component()
)
end)
end)

View File

@ -0,0 +1,47 @@
local Vector3 = require("worldeditadditions.utils.vector3")
describe("Vector3.mean", function()
it("should work with a positive vector", function()
local a = Vector3.new(2, 2, 2)
local b = Vector3.new(4, 4, 4)
assert.are.same(
Vector3.new(3, 3, 3),
a:mean(b)
)
end)
it("should work with a positive vector the other way around", function()
local a = Vector3.new(2, 2, 2)
local b = Vector3.new(4, 4, 4)
assert.are.same(
Vector3.new(3, 3, 3),
b:mean(a)
)
end)
it("should mean another positive vector", function()
local a = Vector3.new(6, 6, 6)
local b = Vector3.new(10, 10, 10)
assert.are.same(
Vector3.new(8, 8, 8),
a:mean(b)
)
end)
it("should mean a negative vector", function()
local a = Vector3.new(-2, -2, -2)
local b = Vector3.new(0, 0, 0)
assert.are.same(
Vector3.new(-1, -1, -1),
a:mean(b)
)
end)
it("should return a new Vector3 instance", function()
local a = Vector3.new(6, 6, 6)
local b = Vector3.new(10, 10, 10)
assert.are.same(
Vector3.new(8, 8, 8),
a:mean(b)
)
assert.are.same(Vector3.new(6, 6, 6), a)
assert.are.same(Vector3.new(10, 10, 10), b)
end)
end)

View File

@ -0,0 +1,44 @@
local Vector3 = require("worldeditadditions.utils.vector3")
describe("Vector3.min", function()
it("should work with positive vectors", function()
local a = Vector3.new(16, 64, 16)
local b = Vector3.new(1, 4, 6)
assert.are.same(
Vector3.new(1, 4, 6),
Vector3.min(a, b)
)
end)
it("should work with mixed components", function()
local a = Vector3.new(16, 1, 16)
local b = Vector3.new(1, 4, 60)
assert.are.same(
Vector3.new(1, 1, 16),
Vector3.min(a, b)
)
end)
it("should work with negative vectors", function()
local a = Vector3.new(-9, -16, -25)
local b = Vector3.new(-3, -6, -2)
assert.are.same(
Vector3.new(-9, -16, -25),
Vector3.min(a, b)
)
end)
it("should return new Vector3 instances", function()
local a = Vector3.new(16, 1, 16)
local b = Vector3.new(1, 4, 60)
local result = Vector3.min(a, b)
assert.are.same(Vector3.new(1, 1, 16), result)
result.y = 999
assert.are.same(Vector3.new(16, 1, 16), a)
assert.are.same(Vector3.new(1, 4, 60), b)
assert.are.same(Vector3.new(1, 999, 16), result)
end)
end)

View File

@ -0,0 +1,39 @@
local Vector3 = require("worldeditadditions.utils.vector3")
describe("Vector3.min_component", function()
it("should work with a positive vector x", function()
local a = Vector3.new(3, 4, 5)
assert.are.equal(
3,
a:min_component()
)
end)
it("should work with a positive vector y", function()
local a = Vector3.new(3, 1, 5)
assert.are.equal(
1,
a:min_component()
)
end)
it("should work with a positive vector z", function()
local a = Vector3.new(3, 1, 0.5)
assert.are.equal(
0.5,
a:min_component()
)
end)
it("should work with a negative vector", function()
local a = Vector3.new(-4, -5, -46)
assert.are.equal(
-46,
a:min_component()
)
end)
it("should work with a mixed vector", function()
local a = Vector3.new(-30, 3, -3)
assert.are.equal(
-30,
a:min_component()
)
end)
end)

View File

@ -0,0 +1,17 @@
local Vector3 = require("worldeditadditions.utils.vector3")
-- To find these numbers, in Javascript:
-- function t(x) { return Math.sqrt((x*x)*3); }
-- for(let i = 0; i < 1000000000; i++) { let r = t(i); if(Math.floor(r) === r) console.log(`i ${i}, r ${r}`); }
describe("Vector3.move_towards", function()
it("should work with a positive vector", function()
local a = Vector3.new(3, 4, 5)
local b = Vector3.new(10, 10, 10)
assert.are.same(
Vector3.new(5.0022714374157439821, 5.7162326606420661435, 6.4301938838683883048),
a:move_towards(b, 3)
)
end)
end)

View File

@ -0,0 +1,80 @@
local Vector3 = require("worldeditadditions.utils.vector3")
describe("Vector3.multiply", function()
it("should multiply 2 positive vectors", function()
local a = Vector3.new(3, 4, 5)
local b = Vector3.new(2, 2, 2)
assert.are.same(
Vector3.new(6, 8, 10),
a:multiply(b)
)
end)
it("should work with the mul alias", function()
local a = Vector3.new(3, 4, 5)
local b = Vector3.new(2, 2, 2)
assert.are.same(
Vector3.new(6, 8, 10),
a:mul(b)
)
end)
it("should work with scalar a", function()
local a = 2
local b = Vector3.new(6, 7, 8)
assert.are.same(
Vector3.new(12, 14, 16),
a * b
)
end)
it("should work with scalar b", function()
local a = Vector3.new(6, 7, 8)
local b = 2
assert.are.same(
Vector3.new(12, 14, 16),
a * b
)
end)
it("should support the multiply operator", function()
local a = Vector3.new(3, 4, 5)
local b = Vector3.new(2, 2, 2)
assert.are.same(
Vector3.new(6, 8, 10),
a * b
)
end)
it("should handle negative b", function()
local a = Vector3.new(3, 4, 5)
local b = Vector3.new(-1, -1, -1)
assert.are.same(
Vector3.new(-3, -4, -5),
a * b
)
end)
it("should handle negative a", function()
local a = Vector3.new(-3, -4, -5)
local b = Vector3.new(2, 2, 2)
assert.are.same(
Vector3.new(-6, -8, -10),
a * b
)
end)
it("should handle negative a and b", function()
local a = Vector3.new(-3, -4, -5)
local b = Vector3.new(-2, -2, -2)
assert.are.same(
Vector3.new(6, 8, 10),
a * b
)
end)
it("should return a new Vector3 instance", function()
local a = Vector3.new(3, 4, 5)
local b = Vector3.new(3, 3, 3)
local result = a * b
assert.are.same(
Vector3.new(9, 12, 15),
result
)
assert.are_not.equal(result, a)
assert.are_not.equal(result, b)
end)
end)

View File

@ -0,0 +1,25 @@
local Vector3 = require("worldeditadditions.utils.vector3")
describe("Vector3.add", function()
it("should create a new Vector3", function()
assert.are.same(
{ x = 3, y = 4, z = 5 },
Vector3.new(3, 4, 5)
)
end)
it("should throw an error on invalid x", function()
assert.has.errors(function()
Vector3.new("cheese", 4, 5)
end)
end)
it("should throw an error on invalid y", function()
assert.has.errors(function()
Vector3.new(4, "cheese", 5)
end)
end)
it("should throw an error on invalid z", function()
assert.has.errors(function()
Vector3.new(66, 2, "cheese")
end)
end)
end)

View File

@ -0,0 +1,35 @@
local Vector3 = require("worldeditadditions.utils.vector3")
describe("Vector3.round", function()
it("should round a positive vector", function()
local a = Vector3.new(3.1, 4.75, 5.9)
assert.are.same(
Vector3.new(3, 5, 6),
a:round()
)
end)
it("should round a negative vector", function()
local a = Vector3.new(-3.1, -4.2, -5.3)
assert.are.same(
Vector3.new(-3, -4, -5),
a:round()
)
end)
it("should work with integers", function()
local a = Vector3.new(3, 4, 5)
assert.are.same(
Vector3.new(3, 4, 5),
a:round()
)
end)
it("should return a new Vector3 instance", function()
local a = Vector3.new(3.1, 4.7, 5.99999)
local result = a:round()
assert.are.same(
Vector3.new(3, 5, 6),
result
)
assert.are_not.equal(result, a)
end)
end)

View File

@ -0,0 +1,52 @@
local Vector3 = require("worldeditadditions.utils.vector3")
describe("Vector3.set_to", function()
it("should set_to a positive vector", function()
local a = Vector3.new(801980510, 801980510, 801980510)
assert.are.same(
Vector3.new(80198051, 80198051, 80198051),
a:set_to(138907099)
)
end)
it("should set_to a negative vector", function()
local a = Vector3.new(-1897506260, -1897506260, -1897506260)
assert.are.same(
Vector3.new(-189750626, -189750626, -189750626),
a:set_to(328657725)
)
end)
it("should work if the length is borderline", function()
local a = Vector3.new(80198051, 80198051, 80198051)
assert.are.same(
Vector3.new(80198051, 80198051, 80198051),
a:set_to(138907099)
)
end)
it("should work if the length is smaller", function()
local a = Vector3.new(80198051, 80198051, 80198051)
assert.are.same(
Vector3.new(109552575, 109552575, 109552575),
a:set_to(189750626):floor() -- Hack to ignore flating-point errors. In theory we should really use epsilon here instead
)
end)
it("should return a new Vector3 instance", function()
local a = Vector3.new(801980510, 801980510, 801980510)
local result = a:set_to(138907099)
assert.are.same(
Vector3.new(80198051, 80198051, 80198051),
result
)
assert.are_not.equal(result, a)
end)
it("should return a new Vector3 instance if the length is smaller", function()
local a = Vector3.new(80198051, 80198051, 80198051)
local result = a:set_to(189750626):floor()
assert.are.same(
Vector3.new(109552575, 109552575, 109552575),
result
)
assert.are_not.equal(result, a)
end)
end)

View File

@ -0,0 +1,35 @@
local Vector3 = require("worldeditadditions.utils.vector3")
describe("Vector3.snap_to", function()
it("should snap_to a positive vector", function()
local a = Vector3.new(3.1, 4.75, 5.9)
assert.are.same(
Vector3.new(0, 0, 10),
a:snap_to(10)
)
end)
it("should snap_to a negative vector", function()
local a = Vector3.new(-2.5, -4.2, -5.3)
assert.are.same(
Vector3.new(0, -6, -6),
a:snap_to(6)
)
end)
it("should work with integers", function()
local a = Vector3.new(3, 4, 5)
assert.are.same(
Vector3.new(3, 3, 6),
a:snap_to(3)
)
end)
it("should return a new Vector3 instance", function()
local a = Vector3.new(3.1, 4.7, 5.99999)
local result = a:snap_to(3)
assert.are.same(
Vector3.new(3, 6, 6),
result
)
assert.are_not.equal(result, a)
end)
end)

View File

@ -0,0 +1,44 @@
local Vector3 = require("worldeditadditions.utils.vector3")
describe("Vector3.sort", function()
it("should work with positive vectors", function()
local a = Vector3.new(16, 64, 16)
local b = Vector3.new(1, 4, 6)
local result_a, result_b = Vector3.sort(a, b)
assert.are.same(Vector3.new(1, 4, 6), result_a)
assert.are.same(Vector3.new(16, 64, 16), result_b)
end)
it("should work with mixed components", function()
local a = Vector3.new(16, 1, 16)
local b = Vector3.new(1, 4, 60)
local result_a, result_b = Vector3.sort(a, b)
assert.are.same(Vector3.new(1, 1, 16), result_a)
assert.are.same(Vector3.new(16, 4, 60), result_b)
end)
it("should work with negative vectors", function()
local a = Vector3.new(-9, -16, -25)
local b = Vector3.new(-3, -6, -2)
local result_a, result_b = Vector3.sort(a, b)
assert.are.same(Vector3.new(-9, -16, -25), result_a)
assert.are.same(Vector3.new(-3, -6, -2), result_b)
end)
it("should return new Vector3 instances", function()
local a = Vector3.new(16, 1, 16)
local b = Vector3.new(1, 4, 60)
local result_a, result_b = Vector3.sort(a, b)
assert.are.same(Vector3.new(1, 1, 16), result_a)
assert.are.same(Vector3.new(16, 4, 60), result_b)
result_a.y = 999
result_b.y = 999
assert.are.same(Vector3.new(16, 1, 16), a)
assert.are.same(Vector3.new(1, 4, 60), b)
assert.are.same(Vector3.new(1, 999, 16), result_a)
assert.are.same(Vector3.new(16, 999, 60), result_b)
end)
end)

View File

@ -0,0 +1,28 @@
local Vector3 = require("worldeditadditions.utils.vector3")
describe("Vector3.sqrt", function()
it("should sqrt a positive vector", function()
local a = Vector3.new(16, 64, 16)
assert.are.same(
Vector3.new(4, 8, 4),
a:sqrt()
)
end)
it("should sqrt another positive vector", function()
local a = Vector3.new(9, 16, 25)
assert.are.same(
Vector3.new(3, 4, 5),
a:sqrt()
)
end)
it("should return a new Vector3 instance", function()
local a = Vector3.new(9, 16, 25)
local result = a:sqrt()
assert.are.same(
Vector3.new(3, 4, 5),
result
)
assert.are_not.equal(result, a)
end)
end)

View File

@ -0,0 +1,80 @@
local Vector3 = require("worldeditadditions.utils.vector3")
describe("Vector3.subtract", function()
it("should subtract 2 positive vectors", function()
local a = Vector3.new(3, 4, 5)
local b = Vector3.new(1, 1, 1)
assert.are.same(
Vector3.new(2, 3, 4),
a:subtract(b)
)
end)
it("should work with the sub alias", function()
local a = Vector3.new(3, 4, 5)
local b = Vector3.new(1, 1, 1)
assert.are.same(
Vector3.new(2, 3, 4),
a:sub(b)
)
end)
it("should work with scalar a", function()
local a = 2
local b = Vector3.new(6, 7, 8)
assert.are.same(
Vector3.new(4, 5, 6),
a - b
)
end)
it("should work with scalar b", function()
local a = Vector3.new(6, 7, 8)
local b = 2
assert.are.same(
Vector3.new(4, 5, 6),
a - b
)
end)
it("should support the subtract operator", function()
local a = Vector3.new(3, 4, 5)
local b = Vector3.new(1, 1, 1)
assert.are.same(
Vector3.new(2, 3, 4),
a - b
)
end)
it("should handle negative b", function()
local a = Vector3.new(3, 4, 5)
local b = Vector3.new(-1, -1, -1)
assert.are.same(
Vector3.new(4, 5, 6),
a - b
)
end)
it("should handle negative a", function()
local a = Vector3.new(-3, -4, -5)
local b = Vector3.new(1, 1, 1)
assert.are.same(
Vector3.new(-4, -5, -6),
a - b
)
end)
it("should handle negative a and b", function()
local a = Vector3.new(-3, -4, -5)
local b = Vector3.new(-1, -1, -1)
assert.are.same(
Vector3.new(-2, -3, -4),
a - b
)
end)
it("should return a new Vector3 instance", function()
local a = Vector3.new(3, 4, 5)
local b = Vector3.new(1, 1, 1)
local result = a - b
assert.are.same(
Vector3.new(2, 3, 4),
result
)
assert.are_not.equal(result, a)
assert.are_not.equal(result, b)
end)
end)

View File

@ -0,0 +1,39 @@
local Vector3 = require("worldeditadditions.utils.vector3")
describe("Vector3.__tostring", function()
it("should stringify a Vector3", function()
local a = Vector3.new(3, 4, 5)
assert.are.same(
"(3, 4, 5)",
a:__tostring()
)
end)
it("should implicitly stringify a Vector3", function()
local a = Vector3.new(3, 4, 5)
assert.are.same(
"(3, 4, 5)",
tostring(a)
)
end)
it("should implicitly stringify another Vector3", function()
local a = Vector3.new(55, 77, 22)
assert.are.same(
"(55, 77, 22)",
tostring(a)
)
end)
it("should handle negative numbers", function()
local a = Vector3.new(-1, -2, -3)
assert.are.same(
"(-1, -2, -3)",
tostring(a)
)
end)
it("should handle a mix of positive and negative numbers", function()
local a = Vector3.new(-7, 2, -99)
assert.are.same(
"(-7, 2, -99)",
tostring(a)
)
end)
end)

View File

@ -0,0 +1,37 @@
local Vector3 = require("worldeditadditions.utils.vector3")
-- To find these numbers, in Javascript:
-- function t(x) { return Math.sqrt((x*x)*3); }
-- for(let i = 0; i < 1000000000; i++) { let r = t(i); if(Math.floor(r) === r) console.log(`i ${i}, r ${r}`); }
describe("Vector3.unit", function()
it("should work with a positive vector", function()
local a = Vector3.new(10, 10, 10)
assert.are.same(
Vector3.new(57735, 57735, 57735),
a:unit():multiply(100000):floor()
)
end)
it("should work with a the normalise alias", function()
local a = Vector3.new(10, 10, 10)
assert.are.same(
Vector3.new(57735, 57735, 57735),
a:normalise():multiply(100000):floor()
)
end)
it("should work with a negative vector", function()
local a = Vector3.new(10, 10, 10)
assert.are.same(
Vector3.new(57735, 57735, 57735),
a:unit():multiply(100000):floor()
)
end)
it("should work with a mixed vector", function()
local a = Vector3.new(-371635731, 371635731, -371635731)
assert.are.same(
Vector3.new(-57736, 57735, -57736),
a:unit():multiply(100000):floor()
)
end)
end)

View File

@ -3,13 +3,12 @@ It's about time I started a changelog! This will serve from now on as the main c
Note to self: See the bottom of this file for the release template text.
## v1.12: The selection tools update (unreleased)
## v1.12: The selection tools update (26th June 2021)
- Add `//spush`, `//spop`, and `//sstack`
- Add `//srect` (_select rectangle_), `//scol` (_select column_), `//scube` (_select cube_) - thanks, @VorTechnix!
- Add `//scloud` (_select point cloud_), `//scentre` (_select centre node(s)_), `//srel` (_select relative_) - thanks, @VorTechnix!
- Add `//smake` (_selection make_) - thanks, @VorTechnix!
- Significantly refactored backend utility functions (more to come in future updates)
- `//bonemeal`: Try bonemealing everything that isn't an air block (#49)
- Add new universal chance parsing
- Any `<chance>` can now either be a 1-in-N number (e.g. `4`, `10`), or a percentage chance (e.g. `50%`, `10%`).
- Caveat: Percentages are converted to a 1-in-N chance, but additionally that number is rounded down in some places
@ -19,6 +18,7 @@ Note to self: See the bottom of this file for the release template text.
- `//erode`: Add new `river` erosion algorithm for filling in potholes and removing pillars
### Bugfixes
- `//bonemeal`: Try bonemealing everything that isn't an air block (#49)
- `//overlay`: Don't place nodes above water
- `//multi`: Improve resilience by handling some edge cases
- `//layers`: Fix crash due to outdated debug code

View File

@ -505,6 +505,7 @@ While other server commands can be executed while a `//subdivide` is running, `/
//subdivice 25 25 25 fixlight
```
## `//multi <command_a> <command_b> <command_c> .....`
Executes multi chat commands in sequence. Intended for _WorldEdit_ commands, but does work with others too. Don't forget a space between commands!
@ -528,6 +529,7 @@ In addition, this also allows for including a double forward slash in the argume
//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
```
## `//many <times> <command>`
Executes a single chat command many times in a row. Uses `minetest.after()` to yield to the main server thread to allow other things to happen at the same time, so technically you could have multiple `//many` calls going at once (but multithreading support is out of reach, so only a single one will be executing at the same time).
@ -538,6 +540,7 @@ Note that this isn't necessarily limited to executing WorldEdit / WorldEditAddit
//many 100 //multi //1 //2 //outset 20 //set dirt
```
## `//ellipsoidapply <command_name> <args>`
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.
@ -549,6 +552,7 @@ Executes the given command, and then clips the result to the largest ellipsoid t
//ellipsoidapply layers desert_sand sand 2 desert_sandstone 4 sandstone 10
```
## `//scol [<axis1> ] <length>`
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.
@ -557,6 +561,7 @@ Short for _select column_. Sets the pos2 at a set distance along 1 axis from pos
//scol x 3
```
## `//srect [<axis1> [<axis2>]] <length>`
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.
@ -566,6 +571,7 @@ Short for _select rectangle_. Sets the pos2 at a set distance along 2 axes from
//srect -z y 25
```
## `//scube [<axis1> [<axis2> [<axis3>]]] <length>`
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.
@ -576,6 +582,7 @@ Short for _select cube_. Sets the pos2 at a set distance along 3 axes from pos1.
//scube -z 12
```
## `//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.
@ -585,6 +592,7 @@ Short for _select point cloud_. Sets pos1 and pos2 to include the nodes you punc
//scloud stop
```
## `//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.
@ -592,6 +600,7 @@ Short for _select center_. Sets pos1 and pos2 to the centre point(s) of the curr
//scentre
```
## `//srel <axis1> <length1> [<axis2> <length2> [<axis3> <length3>]]`
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.
@ -602,7 +611,8 @@ Short for _select relative_. Sets the pos2 at set distances along 3 axes relativ
//scube -z 12 -y -2 x -2
```
## `//smake <operation> <mode> [<target=xz> [<base>]]`
## `//smake <operation:odd|even|equal> <mode:grow|shrink|average> [<target=xz> [<base>]]`
Short for _selection make_. Modifies existing selection by moving pos2. Allows you to make the selection an odd or even length on one or more axes or set two or more axes equal to each other or the longest, shortest or average of them. Implementation thanks to @VorTechnix.
Usage examples:
@ -615,41 +625,41 @@ Usage examples:
//smake equal zy x
```
### `<operation>`: odd/even/equal/factor
### `<operation>`: odd|even|equal
|Value | Description |
| --- | --- |
odd: | round up or down, based on mode, all axes specified in `<target>` to the nearest odd length relative to pos1
even: | round up or down, based on mode, all axes specified in `<target>` to the nearest even length relative to pos1
equal: | set `<target>` axes length equal to the length of `<base>` axis if specified or to the length of the largest, smallest or average of the `<target>` axes based on mode.
Value | Description
--------|---------------
`odd` | Round up or down, based on mode, all axes specified in `<target>` to the nearest odd length relative to pos1
`even` | Round up or down, based on mode, all axes specified in `<target>` to the nearest even length relative to pos1
`equal` | Set `<target>` axes length equal to the length of `<base>` axis if specified or to the length of the largest, smallest or average of the `<target>` axes based on mode.
### `<mode>:` grow/shrink/average
### `<mode>`: grow|shrink|average
#### *If `<operation>` == odd or even:*
|Value | Description |
| --- | --- |
grow: | grow each axis specified in `<target>` to the nearest odd/even number to itself
shrink: | shrink each axis specified in `<target>` to the nearest odd/even number to itself
average/avg: | take the average of all axes specified in `<target>` and then for each specified axis grow or shrink it, depending on weather it is less than or greater than the average, to the nearest odd/even number to itself
Value | Description
----------------|--------------
`grow` | Grow each axis specified in `<target>` to the nearest odd/even number to itself
`shrink` | Shrink each axis specified in `<target>` to the nearest odd/even number to itself
`average`|`avg` | Take the average of all axes specified in `<target>` and then for each specified axis grow or shrink it, depending on weather it is less than or greater than the average, to the nearest odd/even number to itself
#### *If `<operation>` == equal:* ^[1]
#### *If `<operation>` == equal:*
The `<mode>` argument can be omitted and will not be parsed if present if `<base>` is specified
|Value | Description |
| --- | --- |
grow: | grow each axis specified in `<target>` to the length of the longest specified axis
shrink: | shrink each axis specified in `<target>` to the length of the shortest specified axis
average/avg: | set each axis specified in `<target>` to the average length of all the specified axes
Value | Description
----------------|---------------
`grow` | Grow each axis specified in `<target>` to the length of the longest specified axis
`shrink` | Shrink each axis specified in `<target>` to the length of the shortest specified axis
`average`|`avg` | Set each axis specified in `<target>` to the average length of all the specified axes
### Additional arguments:
|Name | Description |
| --- | --- |
`<target>`: | Specify axes to perform operation on (default= xz)|
`<base>`: If `<operation>` == odd or even: | Does nothing
`<base>`: If `<operation>` == equal: | Overrides `<mode>`^[1] and sets all `<target>` axes equal to itself
^[1]: `<mode>` argument can be omitted and will not be parsed if present if `<base>` is specified
Name | Description
------------|------------------
`<target>` | Specify axes to perform operation on (default= xz)|
`<base>`: If `<operation>` == odd or even | Does nothing
`<base>`: If `<operation>` == equal | Overrides `<mode>`^[1] and sets all `<target>` axes equal to itself
## `//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).
@ -658,6 +668,7 @@ Displays the contents of your per-user selection stack. This stack can be pushed
//sstack
```
## `//spush`
Pushes the currently defined region onto your per-user selection stack. Does not otherwise alter the defined region.

View File

@ -65,6 +65,7 @@ The detailed explanations have moved! Check them out [here](https://github.com/s
- [`//scloud <0-6|stop|reset>`](https://github.com/sbrl/Minetest-WorldEditAdditions/blob/main/Chat-Command-Reference.md#scloud-0-6stopreset)
- [`//scentre`](https://github.com/sbrl/Minetest-WorldEditAdditions/blob/main/Chat-Command-Reference.md#scentre)
- [`//srel <axis1> <length1> [<axis2> <length2> [<axis3> <length3>]]`](https://github.com/sbrl/Minetest-WorldEditAdditions/blob/main/Chat-Command-Reference.md#srel-axis1-length1-axis2-length2-axis3-length3)
- [`//smake <operation:odd|even|equal> <mode:grow|shrink|average> [<target=xz> [<base>]]`](https://github.com/sbrl/Minetest-WorldEditAdditions/blob/main/Chat-Command-Reference.md#smake-operationoddevenequal-modegrowshrinkaverage-targetxz-base)
- [`//sstack`](https://github.com/sbrl/Minetest-WorldEditAdditions/blob/main/Chat-Command-Reference.md#sstack)
- [`//spush`](https://github.com/sbrl/Minetest-WorldEditAdditions/blob/main/Chat-Command-Reference.md#spush)
- [`//spop`](https://github.com/sbrl/Minetest-WorldEditAdditions/blob/main/Chat-Command-Reference.md#spop)
@ -81,6 +82,7 @@ The detailed explanations have moved! Check them out [here](https://github.com/s
### Tools
- [WorldEditAdditions Far Wand](https://github.com/sbrl/Minetest-WorldEditAdditions/blob/main/Chat-Command-Reference.md#far-wand)
- [WorldEditAdditions Cloud Wand](https://github.com/sbrl/Minetest-WorldEditAdditions/blob/main/Chat-Command-Reference.md#cloud-wand)
- [`//farwand skip_liquid (true|false) | maxdist <number>`](https://github.com/sbrl/Minetest-WorldEditAdditions/blob/main/Chat-Command-Reference.md#farwand-skip_liquid-truefalse--maxdist-number)

View File

@ -27,7 +27,7 @@ luarocks_root="${PWD}/.luarocks";
# Setup the lua module path
eval "$(luarocks --tree "${luarocks_root}" path)";
mode="${1}"; shift;
mode="${1}"; if [[ "$#" -gt 0 ]]; then shift; fi
run_setup() {
log_msg "Installing busted";

View File

@ -8,6 +8,8 @@
worldeditadditions = {}
worldeditadditions.modpath = minetest.get_modpath("worldeditadditions")
dofile(worldeditadditions.modpath.."/utils/vector.lua")
dofile(worldeditadditions.modpath.."/utils/vector3.lua")
dofile(worldeditadditions.modpath.."/utils/mesh.lua")
dofile(worldeditadditions.modpath.."/utils/strings/init.lua")
dofile(worldeditadditions.modpath.."/utils/format/init.lua")

View File

@ -0,0 +1,99 @@
local wea = worldeditadditions
-- ███████ █████ ██████ ███████
-- ██ ██ ██ ██ ██
-- █████ ███████ ██ █████
-- ██ ██ ██ ██ ██
-- ██ ██ ██ ██████ ███████
--- A single face of a Mesh.
local Face = {}
Face.__index = Face
--- Creates a new face from a list of vertices.
-- The list of vertices should be anti-clockwise.
-- @param vertices Vector3[] A list of Vector3 vertices that define the face.
function Face.new(vertices)
local result = { vertices = vertices }
setmetatable(result, Face)
return result
end
--- Determines whether this face is equal to another face or not.
-- @param a Face The first face to compare.
-- @param b Face The second face to compare.
-- @returns bool Whether the 2 faces are equal or not.
function Face.equal(a, b)
if #a.vertices ~= #b.vertices then return false end
for i,vertex in ipairs(a) do
if vertex ~= b.vertices[i] then return false end
end
return true
end
function Face.__eq(a, b) return Face.equal(a, b) end
-- ███ ███ ███████ ███████ ██ ██
-- ████ ████ ██ ██ ██ ██
-- ██ ████ ██ █████ ███████ ███████
-- ██ ██ ██ ██ ██ ██ ██
-- ██ ██ ███████ ███████ ██ ██
--- A mesh of faces.
local Mesh = {}
Mesh.__index = Mesh
--- Creates a new empty mesh object container.
-- @returns Mesh
function Mesh.new()
local result = { faces = {} }
setmetatable(result, Mesh)
end
--- Adds a face to this mesh.
-- @param self Mesh The mesh instance to operate on.
-- @param face Face The face to add.
-- @returns void
function Mesh.add_face(self, face)
table.insert(self.faces, face)
end
--- Deduplicate the list of faces in this Mesh.
-- Removes all faces that are exactly equal to one another. This reduces the
-- filesize.
-- @returns number The number of faces removed.
function Mesh.dedupe(self)
-- Find the faces to remove
local toremove = {}
for i,face_check in ipairs(self.faces) do
for j,face_next in ipairs(self.faces) do
if i ~= j -- If we're not comparing a face to itself...
and face_check == face_next -- ....and the 2 faces are equal....
and not wea.table_contains(toremove, j) then -- ...and we haven't already marked it for removal...
-- Mark it for removal
table.insert(toremove, j)
end
end
end
-- Sort the list of indexes marked for removal from largest to smallest
-- This way, removing smaller items doesn't alter the index of larger ones
table.sort(toremove, function(a, b) return a > b end)
-- Remove the faces marked for removal
for i, remove_index in ipairs(toremove) do
table.remove(self.faces, remove_index)
end
return #toremove
end
if worldeditadditions then
worldeditadditions.Face = Face
worldeditadditions.Mesh = Mesh
else
return {
Face = Face,
Mesh = Mesh
}
end

View File

@ -20,3 +20,4 @@ dofile(worldeditadditions.modpath.."/utils/tables/table_map.lua")
dofile(worldeditadditions.modpath.."/utils/tables/table_tostring.lua")
dofile(worldeditadditions.modpath.."/utils/tables/table_unique.lua")
dofile(worldeditadditions.modpath.."/utils/tables/table_unpack.lua")
dofile(worldeditadditions.modpath.."/utils/tables/table_contains.lua")

View File

@ -0,0 +1,18 @@
--- Looks to see whether a given table contains a given value.
-- @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 table_contains(tbl, target)
for key, value in ipairs(tbl) do
if value == target then return true end
end
return false
end
if worldeditadditions then
worldeditadditions.table_contains = table_contains
else
return table_contains
end

View File

@ -110,7 +110,7 @@ end
-- @param pos2 Vector pos2 of the defined region.
-- @return Vector Min values from input vectors.
function worldeditadditions.vector.min(pos1, pos2)
return vector.new(math.min(pos1.x + pos2.x), math.min(pos1.y + pos2.y), math.min(pos1.z + pos2.z))
return vector.new(math.min(pos1.x, pos2.x), math.min(pos1.y, pos2.y), math.min(pos1.z, pos2.z))
end
--- Returns a vector of the max values of 2 positions.
@ -118,5 +118,5 @@ end
-- @param pos2 Vector pos2 of the defined region.
-- @return Vector Max values from input vectors.
function worldeditadditions.vector.max(pos1, pos2)
return vector.new(math.max(pos1.x + pos2.x), math.max(pos1.y + pos2.y), math.max(pos1.z + pos2.z))
return vector.new(math.max(pos1.x, pos2.x), math.max(pos1.y, pos2.y), math.max(pos1.z, pos2.z))
end

View File

@ -0,0 +1,399 @@
--- A 3-dimensional vector.
local Vector3 = {}
Vector3.__index = Vector3
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
local result = {
x = x,
y = y,
z = z
}
setmetatable(result, Vector3)
return result
end
--- Returns a new instance of this vector.
-- @param a Vector3 The vector to clone.
-- @returns Vector3 A new vector whose values are identical to those of the original vector.
function Vector3.clone(a)
return Vector3.new(a.x, a.y, a.z)
end
--- Adds the specified vectors or numbers together.
-- Returns the result as a new vector.
-- If 1 of the inputs is a number and the other a vector, then the number will
-- be added to each of the components of the vector.
-- @param a Vector3|number The first item to add.
-- @param a Vector3|number The second item to add.
-- @returns Vector3 The result as a new Vector3 object.
function Vector3.add(a, b)
if type(a) == "number" then
return Vector3.new(b.x + a, b.y + a, b.z + a)
elseif type(b) == "number" then
return Vector3.new(a.x + b, a.y + b, a.z + b)
end
return Vector3.new(a.x + b.x, a.y + b.y, a.z + b.z)
end
--- Subtracts the specified vectors or numbers together.
-- Returns the result as a new vector.
-- If 1 of the inputs is a number and the other a vector, then the number will
-- be subtracted to each of the components of the vector.
-- @param a Vector3|number The first item to subtract.
-- @param a Vector3|number The second item to subtract.
-- @returns Vector3 The result as a new Vector3 object.
function Vector3.subtract(a, b)
if type(a) == "number" then
return Vector3.new(b.x - a, b.y - a, b.z - a)
elseif type(b) == "number" then
return Vector3.new(a.x - b, a.y - b, a.z - b)
end
return Vector3.new(a.x - b.x, a.y - b.y, a.z - b.z)
end
--- Alias for Vector3.subtract.
function Vector3.sub(a, b) return Vector3.subtract(a, b) end
--- Multiplies the specified vectors or numbers together.
-- Returns the result as a new vector.
-- If 1 of the inputs is a number and the other a vector, then the number will
-- be multiplied to each of the components of the vector.
--
-- If both of the inputs are vectors, then the components are multiplied
-- by each other (NOT the cross product). In other words:
-- a.x * b.x, a.y * b.y, a.z * b.z
--
-- @param a Vector3|number The first item to multiply.
-- @param a Vector3|number The second item to multiply.
-- @returns Vector3 The result as a new Vector3 object.
function Vector3.multiply(a, b)
if type(a) == "number" then
return Vector3.new(b.x * a, b.y * a, b.z * a)
elseif type(b) == "number" then
return Vector3.new(a.x * b, a.y * b, a.z * b)
end
return Vector3.new(a.x * b.x, a.y * b.y, a.z * b.z)
end
--- Alias for Vector3.multiply.
function Vector3.mul(a, b) return Vector3.multiply(a, b) end
--- Divides the specified vectors or numbers together.
-- Returns the result as a new vector.
-- If 1 of the inputs is a number and the other a vector, then the number will
-- be divided to each of the components of the vector.
-- @param a Vector3|number The first item to divide.
-- @param a Vector3|number The second item to divide.
-- @returns Vector3 The result as a new Vector3 object.
function Vector3.divide(a, b)
if type(a) == "number" then
return Vector3.new(b.x / a, b.y / a, b.z / a)
elseif type(b) == "number" then
return Vector3.new(a.x / b, a.y / b, a.z / b)
end
return Vector3.new(a.x / b.x, a.y / b.y, a.z / b.z)
end
--- Alias for Vector3.divide.
function Vector3.div(a, b) return Vector3.divide(a, b) end
--- Rounds the components of this vector down.
-- @param a Vector3 The vector to operate on.
-- @returns Vector3 A new instance with the x/y/z components rounded down.
function Vector3.floor(a)
return Vector3.new(math.floor(a.x), math.floor(a.y), math.floor(a.z))
end
--- Rounds the components of this vector up.
-- @param a Vector3 The vector to operate on.
-- @returns Vector3 A new instance with the x/y/z components rounded up.
function Vector3.ceil(a)
return Vector3.new(math.ceil(a.x), math.ceil(a.y), math.ceil(a.z))
end
--- Rounds the components of this vector.
-- @param a Vector3 The vector to operate on.
-- @returns Vector3 A new instance with the x/y/z components rounded.
function Vector3.round(a)
return Vector3.new(math.floor(a.x+0.5), math.floor(a.y+0.5), math.floor(a.z+0.5))
end
--- Snaps this Vector3 to an imaginary square grid with the specified sized
-- squares.
-- @param a Vector3 The vector to operate on.
-- @param number grid_size The size of the squares on the imaginary grid to which to snap.
-- @returns Vector3 A new Vector3 instance snapped to an imaginary grid of the specified size.
function Vector3.snap_to(a, grid_size)
return (a / grid_size):round() * grid_size
end
--- Returns the area of this vector.
-- In other words, multiplies all the components together and returns a scalar value.
-- @param a Vector3 The vector to return the area of.
-- @returns number The area of this vector.
function Vector3.area(a)
return a.x * a.y * a.z
end
--- Returns the scalar length of this vector squared.
-- @param a Vector3 The vector to operate on.
-- @returns number The length squared of this vector as a scalar value.
function Vector3.length_squared(a)
return a.x * a.x + a.y * a.y + a.z * a.z
end
--- Square roots each component of this vector.
-- @param a Vector3 The vector to operate on.
-- @returns number A new vector with each component square rooted.
function Vector3.sqrt(a)
return Vector3.new(math.sqrt(a.x), math.sqrt(a.y), math.sqrt(a.z))
end
--- Calculates the scalar length of this vector.
-- @param a Vector3 The vector to operate on.
-- @returns number The length of this vector as a scalar value.
function Vector3.length(a)
return math.sqrt(a:length_squared())
end
--- Calculates the dot product of this vector and another vector.
-- @param a Vector3 The first vector to operate on.
-- @param a Vector3 The second vector to operate on.
-- @returns number The dot product of this vector as a scalar value.
function Vector3.dot(a, b)
return a.x * b.x + a.y * b.y + a.z * b.z;
end
--- Alias of Vector3.dot.
function Vector3.dot_product(a, b)
return Vector3.dot(a, b)
end
--- Determines if 2 vectors are equal to each other.
-- 2 vectors are equal if their values are identical.
-- @param a Vector3 The first vector to test.
-- @param a Vector3 The second vector to test.
-- @returns bool Whether the 2 vectors are equal or not.
function Vector3.equals(a, b)
return a.x == b.x
and a.y == b.y
and a.z == b.z
end
--- Returns a new vector whose length clamped to the given length.
-- The direction in which the vector is pointing is not changed.
-- @param a Vector3 The vector to operate on.
-- @returns Vector3 A new Vector3 instance limited to the specified length.
function Vector3.limit_to(a, length)
if type(length) ~= "number" then error("Error: Expected number, but found "..type(length)..".") end
if a:length() > length then
return (a / a:length()) * length
end
return a:clone()
end
--- Returns a new vector whose length clamped to the given length.
-- The direction in which the vector is pointing is not changed.
-- @param a Vector3 The vector to operate on.
-- @returns Vector3 A new Vector3 instance limited to the specified length.
function Vector3.set_to(a, length)
if type(length) ~= "number" then error("Error: Expected number, but found "..type(length)..".") end
return (a / a:length()) * length
end
--- Returns the unit vector of this vector.
-- The unit vector is a vector with a length of 1.
-- Returns a new vector.
-- Does not change the direction of the vector.
-- @param a Vector3 The vector to operate on.
-- @returns Vector3 The unit vector of this vector.
function Vector3.unit(a)
return a / a:length()
end
--- Alias of Vector3.unit.
function Vector3.normalise(a) return a:unit() end
--- Return a vector that is amount distance towards b from a.
-- @param a Vector3 The vector to move from.
-- @param b Vector3 The vector to move towards.
-- @param amount number The amount to move.
function Vector3.move_towards(a, b, amount)
return a + (b - a):limit_to(amount)
end
--- Returns the value of the minimum component of the vector.
-- Returns a scalar value.
-- @param a Vector3 The vector to operate on.
-- @returns number The value of the minimum component of the vector.
function Vector3.min_component(a)
return math.min(a.x, a.y, a.z)
end
--- Returns the value of the maximum component of the vector.
-- Returns a scalar value.
-- @param a Vector3 The vector to operate on.
-- @returns number The value of the maximum component of the vector.
function Vector3.max_component(a)
return math.max(a.x, a.y, a.z)
end
--- Returns the absolute form of this vector.
-- In other words, it removes the minus sign from all components of the vector.
-- @param a Vector3 The vector to operate on.
-- @returns Vector3 The absolute form of the given vector.
function Vector3.abs(a)
return Vector3.new(math.abs(a.x), math.abs(a.y), math.abs(a.z))
end
--- Sorts the components of the given vectors.
-- pos1 will contain the minimum values, and pos2 the maximum values.
-- Returns 2 new vectors.
-- Note that the vectors provided do not *have* to be instances of Vector3.
-- It is only required that they have the keys x, y, and z.
-- Vector3 instances are always returned.
-- This enables convenient ingesting of positions from outside.
-- @param pos1 Vector3 The first vector to operate on.
-- @param pos2 Vector3 The second vector to operate on.
-- @returns Vector3,Vector3 The 2 sorted vectors.
function Vector3.sort(pos1, pos2)
local pos1_new = Vector3.clone(pos1) -- This way we can accept non-Vector3 instances
local pos2_new = Vector3.clone(pos2) -- This way we can accept non-Vector3 instances
if pos1_new.x > pos2_new.x then
pos1_new.x, pos2_new.x = pos2_new.x, pos1_new.x
end
if pos1_new.y > pos2_new.y then
pos1_new.y, pos2_new.y = pos2_new.y, pos1_new.y
end
if pos1_new.z > pos2_new.z then
pos1_new.z, pos2_new.z = pos2_new.z, pos1_new.z
end
return pos1_new, pos2_new
end
--- Determines if this vector is contained within the region defined by the given vectors.
-- @param a Vector3 The target vector to check.
-- @param pos1 Vector3 pos1 of the defined region.
-- @param pos2 Vector3 pos2 of the defined region.
-- @return boolean Whether the given target is contained within the defined worldedit region.
function Vector3.is_contained(target, pos1, pos2)
local pos1, pos2 = Vector3.sort(pos1, pos2)
return pos1.x <= target.x
and pos1.y <= target.y
and pos1.z <= target.z
and pos2.x >= target.x
and pos2.y >= target.y
and pos2.z >= target.z
end
--- Expands the defined region to include the given point.
-- @param target Vector3 The target vector to include.
-- @param pos1 Vector3 pos1 of the defined region.
-- @param pos2 Vector3 pos2 of the defined region.
-- @returns Vector3,Vector3 2 vectors that represent the expand_region.
function Vector3.expand_region(target, pos1, pos2)
local pos1, pos2 = Vector3.sort(pos1, pos2)
if target.x < pos1.x then pos1.x = target.x end
if target.y < pos1.y then pos1.y = target.y end
if target.z < pos1.z then pos1.z = target.z end
if target.x > pos2.x then pos2.x = target.x end
if target.y > pos2.y then pos2.y = target.y end
if target.z > pos2.z then pos2.z = target.z end
return pos1, pos2
end
--- Returns the mean (average) of 2 positions.
-- In other words, returns the centre of 2 points.
-- @param pos1 Vector3 pos1 of the defined region.
-- @param pos2 Vector3 pos2 of the defined region.
-- @param target Vector3 Centre coordinates.
function Vector3.mean(pos1, pos2)
return (pos1 + pos2) / 2
end
--- Returns a vector of the min components of 2 vectors.
-- @param pos1 Vector3 The first vector to operate on.
-- @param pos2 Vector3 The second vector to operate on.
-- @return Vector3 The minimum values from the input vectors
function Vector3.min(pos1, pos2)
return Vector3.new(
math.min(pos1.x, pos2.x),
math.min(pos1.y, pos2.y),
math.min(pos1.z, pos2.z)
)
end
--- Returns a vector of the max values of 2 vectors.
-- @param pos1 Vector3 The first vector to operate on.
-- @param pos2 Vector3 The second vector to operate on.
-- @return Vector3 The maximum values from the input vectors.
function Vector3.max(pos1, pos2)
return Vector3.new(
math.max(pos1.x, pos2.x),
math.max(pos1.y, pos2.y),
math.max(pos1.z, pos2.z)
)
end
-- ██████ ██████ ███████ ██████ █████ ████████ ██████ ██████
-- ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
-- ██ ██ ██████ █████ ██████ ███████ ██ ██ ██ ██████
-- ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
-- ██████ ██ ███████ ██ ██ ██ ██ ██ ██████ ██ ██
--
-- ██████ ██ ██ ███████ ██████ ██████ ██ ██████ ███████ ███████
-- ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
-- ██ ██ ██ ██ █████ ██████ ██████ ██ ██ ██ █████ ███████
-- ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
-- ██████ ████ ███████ ██ ██ ██ ██ ██ ██████ ███████ ███████
function Vector3.__call(x, y, z) return Vector3.new(x, y, z) end
function Vector3.__add(a, b)
return Vector3.add(a, b)
end
function Vector3.__sub(a, b)
return Vector3.sub(a, b)
end
function Vector3.__mul(a, b)
return Vector3.mul(a, b)
end
function Vector3.__div(a, b)
return Vector3.divide(a, b)
end
function Vector3.__eq(a, b)
return Vector3.equals(a, b)
end
--- Returns the current Vector3 as a string.
function Vector3.__tostring(a)
return "("..a.x..", "..a.y..", "..a.z..")"
end
if worldeditadditions then
worldeditadditions.Vector3 = Vector3
else
return Vector3
end

View File

@ -5,7 +5,7 @@
-- ███████ ██ ██ ██ ██ ██ ██ ███████
local wea = worldeditadditions
worldedit.register_command("smake", {
params = "<operation> <mode> [<target=xyz> [<base>]]",
params = "<operation:odd|even|equal> <mode:grow|shrink|average> [<target=xyz> [<base>]]",
description = "Make one or more axes of the current selection odd, even, or equal to another.",
privs = { worldedit = true },
require_pos = 2,