Compare commits
5 Commits
e6bf2a10ee
...
b56bd0d84b
Author | SHA1 | Date |
---|---|---|
Starbeamrainbowlabs | b56bd0d84b | |
Starbeamrainbowlabs | dc39dbff08 | |
Starbeamrainbowlabs | 54c66e0dc9 | |
Starbeamrainbowlabs | 0ac76bc694 | |
Starbeamrainbowlabs | 39f671c1ef |
|
@ -35,13 +35,8 @@
|
|||
<input type="hidden" id="category-names" value="{{ categories }}" />
|
||||
<div class="command-container">
|
||||
<h3>Alphabetical</h3>
|
||||
<ul class="command-list">
|
||||
{% for section in sections_help %}
|
||||
<li data-filtermode-force="all" data-category="{{ section.category }}" style="--cat-colour: {{ section.category_colour }};"><a href="#{{ section.slug }}">
|
||||
<code>{{ section.title }}</code>
|
||||
</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<ul class="command-list">{% for section in sections_help %}
|
||||
<li data-filtermode-force="all" data-category="{{ section.category }}" style="--cat-colour: {{ section.category_colour }};"><a href="#{{ section.slug }}"><code>{{ section.title }}</code></a></li>{% endfor %}</ul>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
|
|
@ -419,16 +419,23 @@ footer {
|
|||
padding: 0;
|
||||
list-style-type: none;
|
||||
word-wrap: anywhere;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.command-list > li {
|
||||
margin: 0.25em 0;
|
||||
}
|
||||
.command-list > li > a {
|
||||
text-decoration: none;
|
||||
padding: 0;
|
||||
}
|
||||
.command-list.coloured code { background: var(--cat-colour); }
|
||||
.command-list code {
|
||||
display: block;
|
||||
padding: 0.5em;
|
||||
box-sizing: border-box;
|
||||
margin: 0.5em 0;
|
||||
/* margin: 0.5em 0; */
|
||||
margin: 0 0;
|
||||
}
|
||||
|
||||
.filterable {
|
||||
|
|
|
@ -10,6 +10,7 @@ Note to self: See the bottom of this file for the release template text.
|
|||
- This is not a hotfix to avoid endless small releases fixing the bug, as it's clear it's much more difficult to fix on all systems than initially expected
|
||||
- Added [`//nodeapply`](https://worldeditadditions.mooncarrot.space/Reference/#nodeapply), a generalisation of [`//airapply`](https://worldeditadditions.mooncarrot.space/Reference/#airapply) that works with a defined list of nodes. Check out [the reference](https://worldeditadditions.mooncarrot.space/Reference/#nodeapply) - it has some cool tricks to it! (thanks for suggesting, @kliv91 from the Discord server!)
|
||||
- Added [`//ngroups`](https://worldeditadditions.mooncarrot.space/Reference/#ngroups), which lists the groups that a given node is a member of. Useful when paired with [`//nodeapply`](https://worldeditadditions.mooncarrot.space/Reference/#nodeapply)!
|
||||
- Added [`//rotate+`](https://worldeditadditions.mooncarrot.space/Reference/#rotate) rotate regions through arbitrary series of potentially non-axis-aligned rotations.
|
||||
|
||||
### Bugfixes and changes
|
||||
- Don't warn on failed registration of `//flora` → [`//bonemeal`](https://worldeditadditions.mooncarrot.space/Reference/#bonemeal) if the `bonemeal` mod isn't installed (e.g. in MineClone2) - thanks @VorTechnix in #106
|
||||
|
|
|
@ -284,6 +284,50 @@ For those interested, WorldEditAdditions uses the [chaikin curve algorithm](http
|
|||
-->
|
||||
|
||||
|
||||
### `//rotate+ <axis> <degrees> [<axis> <degrees> ...] [origin|o [<pos_number>]]`
|
||||
Rotates the defined region using the specified list of rotation operations, optionally around the defined rotation origin. For example, the following:
|
||||
|
||||
```weacmd
|
||||
//rotate+ y 45
|
||||
```
|
||||
|
||||
...will rotate the defined region around it's centre by 45° on the Y (vertical) axis.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> There is a known issue in which holes appear in previously flat surfaces when rotating through non-axis-aligned rotations. The cause for this is currently unclear.
|
||||
>
|
||||
> If you can shed any light on this issue or make any suggestions, please get in touch.
|
||||
|
||||
We can also extend this to rotate on multiple axes:
|
||||
|
||||
```weacmd
|
||||
//rotate+ z 90 y 45
|
||||
```
|
||||
|
||||
The rotations will be processed in order as specified from left to right. The following relative keywords are currently supported in place of an absolute axis name:
|
||||
|
||||
Keyword | Meaning
|
||||
------------|--------------------
|
||||
`right` | Rotates around the Y axis to the right - aka clockwise.
|
||||
`left` | Rotates around the Y axis to the left - aka anticlockwise.
|
||||
|
||||
A custom rotation origin can be specified too. This is done via the `origin` keyword and takes the form of specifying a position to rotate around instead of the default (picking the centre point between pos1 and pos2). For example:
|
||||
|
||||
```weacmd
|
||||
//rotate+ x 20 origin 3
|
||||
```
|
||||
|
||||
....will rotate 20° on the X axis around position 3. See the [multi-point wand](#multipoint) for more information on setting positions other than pos1 and pos2.
|
||||
|
||||
The `origin` keyword's argument is optional, and if no position number is specified defaults to position 3.
|
||||
|
||||
```weacmd
|
||||
//rotate+ y 90 x 45 origin
|
||||
//rotate+ y 90 x 45 o
|
||||
//rotate+ x 60 origin 5 z 20
|
||||
```
|
||||
|
||||
|
||||
### `//floodfill [<replace_node> [<radius>]]`
|
||||
Floods all connected nodes of the same type starting at _pos1_ with `<replace_node>` (which defaults to `water_source`), in a sphere with a radius of `<radius>` (which defaults to 50).
|
||||
|
||||
|
@ -1518,7 +1562,7 @@ It has the range of the _Far Wand_ mentioned above too, so you can select nodes
|
|||
Note that punching out the positions **does not unset them**. Use `//reset` to reset the defined region.
|
||||
|
||||
|
||||
### Multi-Point Wand
|
||||
### MultiPoint Wand
|
||||
The third type of wand provided by WorldEditAdditions is completely different, in that it allows you to select up to **999 points** at once! It looks like this: ![A picture of the multi-point wand](https://raw.githubusercontent.com/sbrl/Minetest-WorldEditAdditions/main/worldeditadditions_farwand/textures/worldeditadditions_multiwand.png)
|
||||
|
||||
It is important to note that (at present) the points selected by this wand **are not compatible with normal points**. This will change in the future, but requires a lot of work to implement.
|
||||
|
|
|
@ -7,32 +7,6 @@ local Vector3 = weac.Vector3
|
|||
-- ██ ██ ██ ██ ██ ██ ██ ██ ██
|
||||
-- ██ ██ ██████ ██ ██ ██ ██ ███████
|
||||
|
||||
--- Compiles a list of rotations into something we can iteratively pass to Vector3.rotate3d.
|
||||
-- TODO Learn Quaternions.
|
||||
-- @param rotlist table<{axis: string|Vector3, rad: number}> The list of rotations. Rotations will be processed in order. Each rotation is a table with a SINGLE axis as a string (x, y, z, -x, -y, or -z; the axis parameter) or a Vector3 (only a SINGLE AXIS set to anything other than 0, and ONLY with a value of 1 or -1), and an amount in radians to rotate by (the rad parameter.
|
||||
-- @returns Vector3[] The list of the compiled rotations, in a form that Vector3.rotate3d understands.
|
||||
local function __compile_rotlist(rotlist)
|
||||
return weac.table.map(rotlist, function(rot)
|
||||
--- 1: Construct a Vector3 to represent which axis we want to rotate on
|
||||
local rotval = Vector3.new(0, 0, 0)
|
||||
-- Assume that if it's a table, it's a Vector3 instance
|
||||
if type(rot) == "table" then
|
||||
rotval = rot.axis:clone()
|
||||
else
|
||||
-- Otherwise, treat it as a string
|
||||
if rot.axis:find("x", 1, true) then rotval.x = 1
|
||||
elseif rot.axis:find("y", 1, true) then rotval.y = 1
|
||||
elseif rot.axis:find("z", 1, true) then rotval.z = 1 end
|
||||
if rot.axis:sub(1, 1) == "-" then
|
||||
rotval = rotval * -1
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- 2: Rotate & apply amount of rotation to apply in radians
|
||||
return rotval * rot.rad
|
||||
end)
|
||||
end
|
||||
|
||||
|
||||
--- Rotates the given region around a given origin point using a set of rotations.
|
||||
|
@ -50,22 +24,25 @@ function worldeditadditions.rotate(pos1, pos2, origin, rotlist)
|
|||
pos1, pos2 = Vector3.sort(pos1, pos2)
|
||||
|
||||
--- 1: Compile the rotation list
|
||||
local rotlist_c = __compile_rotlist(rotlist)
|
||||
local rotlist_c = weac.rotation.rotlist_compile(rotlist)
|
||||
|
||||
|
||||
--- 2: Find the target area we will be rotating into
|
||||
-- First, rotate the defined region to find the target region
|
||||
local pos1_rot, pos2_rot = pos1:clone(), pos2:clone()
|
||||
for i, rot in ipairs(rotlist_c) do
|
||||
pos1_rot = Vector3.rotate3d(origin, pos1_rot, rot)
|
||||
pos2_rot = Vector3.rotate3d(origin, pos2_rot, rot)
|
||||
end
|
||||
-- local pos1_rot, pos2_rot = pos1:clone(), pos2:clone()
|
||||
-- for i, rot in ipairs(rotlist_c) do
|
||||
-- pos1_rot = Vector3.rotate3d(origin, pos1_rot, rot)
|
||||
-- pos2_rot = Vector3.rotate3d(origin, pos2_rot, rot)
|
||||
-- end
|
||||
|
||||
-- Then, align it to the world axis so we can grab a VoxelManipulator
|
||||
-- We add 1 node either side for safety just in case of rounding errors when actually rotating
|
||||
local pos1_dstvm, pos2_dstvm = Vector3.sort(pos1_rot, pos2_rot)
|
||||
local pos1_dstvm, pos2_dstvm = weac.rotation.find_rotated_vm(pos1, pos2, origin, rotlist)
|
||||
pos1_dstvm = pos1_dstvm:floor() - Vector3.new(1, 1, 1)
|
||||
pos2_dstvm = pos2_dstvm:ceil() + Vector3.new(1, 1, 1)
|
||||
|
||||
-- print("DEBUG:rotate pos1", pos1, "pos1_rot", pos1_rot, "pos1_dstvm", pos1_dstvm, "pos2", pos2, "pos2_rot", pos2_rot, "pos2_dstvm", pos2_dstvm)
|
||||
|
||||
|
||||
--- 3: Check out a VoxelManipulator for the source and target regions
|
||||
-- TODO support param2 here
|
||||
|
@ -136,6 +113,9 @@ function worldeditadditions.rotate(pos1, pos2, origin, rotlist)
|
|||
|
||||
--- 8: Return
|
||||
return true, {
|
||||
count_rotated = count_rotated
|
||||
count_rotated = count_rotated,
|
||||
-- Undo the +/-1 when passing back
|
||||
pos1_dstvm = pos1_dstvm + Vector3.new(1, 1, 1),
|
||||
pos2_dstvm = pos2_dstvm - Vector3.new(1, 1, 1)
|
||||
}
|
||||
end
|
|
@ -47,6 +47,7 @@ worldeditadditions_core.register_command("rotate+", {
|
|||
mode = mode_store
|
||||
mode_store = nil
|
||||
elseif mode == "AXIS" then
|
||||
-- TODO: Somehow move this parsing ----> main func to get player reference to allow for relative stuff
|
||||
success, axis_next = wea_c.parse.axes_rotation.axis_name(part)
|
||||
if not success then return success, axis_next end
|
||||
mode = "ANGLE"
|
||||
|
@ -118,6 +119,9 @@ worldeditadditions_core.register_command("rotate+", {
|
|||
)
|
||||
if not success then return success, stats end
|
||||
|
||||
|
||||
wea_c.pos.set1(name, stats.pos1_dstvm)
|
||||
wea_c.pos.set2(name, stats.pos2_dstvm)
|
||||
-- TODO: Adjust the defined area to match the target here? Maybe make this optional somehow given the target may or may nor be axis-aligned
|
||||
-------------------------------------------------
|
||||
local time_taken = wea_c.get_ms_time() - start_time
|
||||
|
|
|
@ -26,6 +26,7 @@ wea_c.Set = dofile(wea_c.modpath.."/utils/set.lua")
|
|||
|
||||
wea_c.Vector3 = dofile(wea_c.modpath.."/utils/vector3.lua")
|
||||
wea_c.Mesh, wea_c.Face = dofile(wea_c.modpath.."/utils/mesh.lua")
|
||||
wea_c.rotation = dofile(wea_c.modpath .. "/utils/rotation.lua")
|
||||
|
||||
wea_c.Queue = dofile(wea_c.modpath.."/utils/queue.lua")
|
||||
wea_c.LRU = dofile(wea_c.modpath.."/utils/lru.lua")
|
||||
|
@ -48,6 +49,7 @@ dofile(wea_c.modpath.."/utils/format/init.lua")
|
|||
dofile(wea_c.modpath.."/utils/parse/init.lua")
|
||||
dofile(wea_c.modpath.."/utils/table/init.lua")
|
||||
|
||||
|
||||
dofile(wea_c.modpath.."/utils/numbers.lua")
|
||||
dofile(wea_c.modpath.."/utils/nodes.lua")
|
||||
dofile(wea_c.modpath.."/utils/node_identification.lua")
|
||||
|
|
|
@ -137,7 +137,7 @@ end
|
|||
-- @param: tbl: Table: Keyword table to parse
|
||||
-- @param: facing: Table: Output from worldeditadditions_core.player_dir(name)
|
||||
-- @param: sum: Bool | String | nil: Return a single vector by summing the 2 output vectors together
|
||||
-- @returns: Vector3, [Vector3]: returns min, max Vector3s or sum Vector3 (if @param: sum ~= nil)
|
||||
-- @returns: Vector3, [Vector3]: returns min, max Vector3 or sum Vector3 (if @param: sum ~= nil)
|
||||
-- if error: @returns: false, String: error message
|
||||
function parse.keytable(tbl, facing, sum)
|
||||
local min, max = Vector3.new(), Vector3.new()
|
||||
|
|
|
@ -43,9 +43,11 @@ local function parse_rotation_axis_name(str, player)
|
|||
if str:find("x", 1, true) then vector.x = 1
|
||||
elseif str:find("y", 1, true) then vector.y = 1
|
||||
elseif str:find("z", 1, true) then vector.z = 1
|
||||
elseif str:find("left", 1, true) then vector.z = -1
|
||||
elseif str:find("left", 1, true)
|
||||
or str:find("l$") then vector.y = -1
|
||||
elseif str:find("right", 1, true)
|
||||
or str:find("yaw") then vector.z = 1
|
||||
or str:find("r$")
|
||||
or str:find("yaw") then vector.y = 1
|
||||
elseif type(player) ~= "nil" then
|
||||
local player_rotation_h = calc_rotation_text(player:get_look_horizontal())
|
||||
|
||||
|
@ -53,12 +55,12 @@ local function parse_rotation_axis_name(str, player)
|
|||
|
||||
if str:find("front", 1, true)
|
||||
or str:find("f$")
|
||||
or str:find("pitch") then
|
||||
or str:find("pitch", 1, true) then
|
||||
vector = rot_axis_left(player_rotation_h)
|
||||
elseif str:find("back", 1, true)
|
||||
or str:find("b$") then
|
||||
vector = rot_axis_left(player_rotation_h) * -1
|
||||
elseif str:find("roll") then
|
||||
elseif str:find("roll", 1, true) then
|
||||
vector = player_rotation_h
|
||||
else
|
||||
return false, "Error: Could not understand rotational axis keyword '"..str.."'."
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
local weac = worldeditadditions_core
|
||||
local Vector3 = weac.Vector3
|
||||
|
||||
--- Compiles a list of rotations into something we can iteratively pass to Vector3.rotate3d.
|
||||
-- This function is called internally. You are unlikely to need to call this function unless you are implementing something like worldeditadditions.rotate() or similar.
|
||||
--
|
||||
-- TODO Learn Quaternions.
|
||||
-- @internal
|
||||
-- @param rotlist table<{axis: string|Vector3, rad: number}> The list of rotations. Rotations will be processed in order. Each rotation is a table with a SINGLE axis as a string (x, y, z, -x, -y, or -z; the axis parameter) or a Vector3 (only a SINGLE AXIS set to anything other than 0, and ONLY with a value of 1 or -1), and an amount in radians to rotate by (the rad parameter.
|
||||
-- @returns Vector3[] The list of the compiled rotations, in a form that Vector3.rotate3d understands.
|
||||
local function rotlist_compile(rotlist)
|
||||
return weac.table.map(rotlist, function(rot)
|
||||
--- 1: Construct a Vector3 to represent which axis we want to rotate on
|
||||
local rotval = Vector3.new(0, 0, 0)
|
||||
-- Assume that if it's a table, it's a Vector3 instance
|
||||
if type(rot) == "table" then
|
||||
rotval = rot.axis:clone()
|
||||
else
|
||||
-- Otherwise, treat it as a string
|
||||
if rot.axis:find("x", 1, true) then
|
||||
rotval.x = 1
|
||||
elseif rot.axis:find("y", 1, true) then
|
||||
rotval.y = 1
|
||||
elseif rot.axis:find("z", 1, true) then
|
||||
rotval.z = 1
|
||||
end
|
||||
if rot.axis:sub(1, 1) == "-" then
|
||||
rotval = rotval * -1
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- 2: Rotate & apply amount of rotation to apply in radians
|
||||
return rotval * rot.rad
|
||||
end)
|
||||
end
|
||||
|
||||
|
||||
--- Applies a given list of rotatiosn rotlist to rotate pos1 and pos2 around a given origin, and returns a pos1/pos2 pair of a region that bounds the rotated area.
|
||||
-- The returned pos1/pos2 are guaranteed to be integer values that fully enclose the rotated region. This function is designed to be used to e.g. find the bounds of a region to pass to a VoxelManip to ensure we grab everything.
|
||||
-- @param pos1 Vector3 Position 1 to rotate.
|
||||
-- @param pos2 Vector3 Position 2 to rotate.
|
||||
-- @param origin Vector3 The position to rotate pos1 and pos2 around. May be a decimal value.
|
||||
-- @param rotlist table<{axis: string, rad: number}> The list of rotations. Rotations will be processed in order. Each rotation is a table with a SINGLE axis as a string (x, y, z, -x, -y, or -z; the axis parameter), and an amount in radians to rotate by (the rad parameter). See worldeditadditions.rotate() for more information.
|
||||
-- @returns Vector3,Vector3 A pos1 & pos2 that fully enclose the rotated region as described above.
|
||||
local function find_rotated_vm(pos1, pos2, origin, rotlist)
|
||||
local rotlist_c = rotlist_compile(rotlist)
|
||||
pos1, pos2 = Vector3.sort(pos1, pos2)
|
||||
|
||||
local corners = {
|
||||
Vector3.new(pos1.x, pos1.y, pos1.z),
|
||||
Vector3.new(pos2.x, pos1.y, pos1.z),
|
||||
Vector3.new(pos1.x, pos2.y, pos1.z),
|
||||
Vector3.new(pos1.x, pos1.y, pos2.z),
|
||||
|
||||
Vector3.new(pos2.x, pos2.y, pos1.z),
|
||||
Vector3.new(pos1.x, pos2.y, pos2.z),
|
||||
Vector3.new(pos2.x, pos1.y, pos2.z),
|
||||
Vector3.new(pos2.x, pos2.y, pos2.z),
|
||||
}
|
||||
local corners_rot = weac.table.map(corners, function(vec)
|
||||
local result = vec:clone()
|
||||
for i, rot in ipairs(rotlist_c) do
|
||||
result = Vector3.rotate3d(origin, result, rot)
|
||||
end
|
||||
return result
|
||||
end)
|
||||
|
||||
local pos1_dstvm = weac.table.reduce(corners_rot, function(acc, vec)
|
||||
return Vector3.min(acc, vec)
|
||||
end, corners_rot[1]:clone())
|
||||
local pos2_dstvm = weac.table.reduce(corners_rot, function(acc, vec)
|
||||
return Vector3.max(acc, vec)
|
||||
end, corners_rot[1]:clone())
|
||||
|
||||
print("DEBUG:find_rotated_vm pos1_dstvm", pos1_dstvm, "pos2_dstvm", pos2_dstvm)
|
||||
|
||||
return pos1_dstvm, pos2_dstvm
|
||||
end
|
||||
|
||||
|
||||
|
||||
return {
|
||||
find_rotated_vm = find_rotated_vm,
|
||||
rotlist_compile = rotlist_compile
|
||||
}
|
Loading…
Reference in New Issue