Add bag, line, and polygon tools

This commit is contained in:
MrRar 2024-02-19 20:02:24 -06:00
parent 403a24e860
commit 725d199191
16 changed files with 848 additions and 104 deletions

View File

@ -28,6 +28,8 @@ This mod was inspired by the Fill Start and Fill End blocks in Manic Digger.
| Circle | edit:circle | ![](textures/edit_circle.png) |
| Mirror | edit:mirror | ![](textures/edit_mirror.png) |
| Screwdriver | edit:screwdriver | ![](textures/edit_screwdriver.png) |
| Polygon | edit:polygon | ![](textures/edit_polygon.png) |
| Bag | edit:bag | ![](textures/edit_bag.png) |
## Dependencies
@ -39,9 +41,7 @@ None
### Copy Tool
![figure.png](figure.png)
When the copy tool is placed at opposite corners of an area, they select the area as show in the figure. The copy tool uses the location under the placed position. When the copy tool is placed for the first time, a marker entity is placed. To cancel the copy operation, punch the entity marker. When a copy tool is placed a second time, the selected area is copied and the entity marker is removed.
When the copy tool is placed at opposite corners of an area, the area is copied. When the copy tool is placed for the first time, a marker entity is placed. To cancel the copy operation, punch the marker. When a copy tool is placed a second time, the selected area is copied and the markers are removed.
### Paste Tool
@ -51,14 +51,14 @@ The paste tool is used for pasting the area copied by the copy tool or a schemat
### Fill Tool
The fill tool is used to fill a 3D area with a certain item. Start by placing the fill tool two times at opposite corners of the desired area. The selected area includes the positions of the fill markers themselves as shown in the figure.
The fill tool is used to fill a 3D area with a certain item. Start by placing the fill tool two times at opposite corners of the desired area.
Once a second fill marker is placed, a dialog appears listing all items in the players inventory. A search field is also available to search all items. Clicking an item will cause it to be used used for filling the selected area. Clicking on a blank inventory slot will cause the selected area to be filled with air. To cancel the fill, press the "X".
### Replace Tool
The replace tool is used to replace certain nodes in a 3D area with a selected item. Start by placing the replace tool two times at opposite corners of the desired area. The selected area includes the positions of the replace markers themselves as shown in the figure.
The replace tool is used to replace certain nodes in a 3D area with a selected item. Start by placing the replace tool two times at opposite corners of the desired area.
Once a second replace marker is placed, a dialog appears listing all node types in the selected area. Check the nodes that should be replaced and then press the "OK" button to proceed with the next step. Next a dialog will pop up showing all the items in the players inventory. A search field is also available to search all items. Clicking an item will cause it to be used used to replace the nodes that were checked earlier. Clicking on a blank inventory slot will cause the checked nodes to be replaced with air. To cancel the replace, press the "X".
@ -100,6 +100,16 @@ This tool is used to mirror the placement or digging of nodes. Place the tool to
This tool is used for rotating nodes that support rotation. Right clicking a node with the screwdriver rotates the node around the X or Z axis depending on the player's position. Left clicking a node with the screwdriver rotates the node clockwise around the Y axis. Param2 types `wallmounted`, `facedir`, and `degrotate` are supported. The node is rotated 90 degrees for all param2 types except `degrotate` where the node is rotated by either 1.5 or 15 degrees. If the aux1 key (E) is held while rotating a `degrotate` node, the rotation angle will be increased by 4x.
### Polygon Tool
This tool is used to create non-concave polygons in 3D space. Place the polygon tool to create markers. Each marker will create a triangle between itself, the last marker placed, and the first marker placed. The first marker placed will be green. To finish the polygon, place a marker on top of the green marker. After doing so, a dialog will appear to select a node or item to fill the polygon.
### Bag
The bag tool is used to place random items from a list of items. Dig (left click) with the bag to open the bag's inventory. Any item from the player inventory can be moved into the bag. The bag has 16 item slots. When placing the bag an item from the bag is randomly chosen to be placed. If a stack of several items is present in the bag, the item will be more likely to be placed than a single item. The probability of being placed is proportional to the item's count divided by the total count of items in the bag. For example, the probability of getting wood would be 75% for a bag with 3 wood and 1 dirt. Bags can be combined with other edit tools, for example, to fill an area with random kinds of dirt.
## Settings
### edit_paste_preview_max_entities
@ -114,10 +124,12 @@ The maximum volume of any edit operation. Increase to allow larger operations.
### edit_fast_node_fill_threshold
When the fill operation has a larger volume then the specified number, fast node fill will be used.
To disable fast node placement, set the threshold to be equal to the max operation volume.
To disable slow node placement, set the threshold to 0.
With fast node placement, callbacks are not called so some nodes might be broken.
When the fill operation has a larger volume then the specified number, fast node fill will be used. To disable fast node placement, set the threshold to be equal to the max operation volume. To disable slow node placement, set the threshold to 0. With fast node placement, callbacks are not called so some nodes might be broken.
### edit_polygon_preview_wire_frame_threshold
If one side of the polygon preview is greater than this setting, a wire frame is used instead of the full preview. The full preview fills the entire polygon with preview entites. If the polygon gets big, the full preview will quickly crash a server. The wire frame preview outlines the triangle componants of the polygon. This setting only affects the preview. The polygon is always completely filled regardless of this setting.
## Privileges

118
bag.lua Normal file
View File

@ -0,0 +1,118 @@
local function get_item_list(itemstack)
local meta = itemstack:get_meta()
local str = meta:get("edit_bag")
local item_list = {}
if str then item_list = minetest.deserialize(str) end
for i, str in pairs(item_list) do
item_list[i] = ItemStack(str)
end
return item_list
end
local function put_item_list(itemstack, item_list)
local meta = itemstack:get_meta()
local str_list = {}
local description = ""
local description_len = 0
for i, item in pairs(item_list) do
str_list[i] = item:to_string()
if str_list[i] ~= "" then
if description_len < 3 then
description = description ..
"\n" .. item:get_count() ..
" " .. item:get_short_description()
description_len = description_len + 1
elseif description_len == 3 then
description = description .. "\n..."
description_len = description_len + 1
end
end
end
description = minetest.registered_items["edit:bag"].short_description ..
minetest.colorize("yellow", description)
local str = minetest.serialize(str_list)
meta:set_string("edit_bag", str)
meta:set_string("description", description)
end
local function on_inv_change(player)
local bag = player:get_wielded_item()
if bag:get_name() ~= "edit:bag" then return end
if not bag then return end
local name = player:get_player_name()
local inv_ref = minetest.get_inventory({type = "detached", name = "edit_bag_" .. name})
put_item_list(bag, inv_ref:get_list("main"))
player:set_wielded_item(bag)
end
minetest.register_on_joinplayer(function(player)
local inv_ref = minetest.create_detached_inventory("edit_bag_" .. player:get_player_name(), {
on_move = function(inv, from_list, from_index, to_list, to_index, count, player) on_inv_change(player) end,
on_put = function(inv, listname, index, stack, player) on_inv_change(player) end,
on_take = function(inv, listname, index, stack, player) on_inv_change(player) end,
})
inv_ref:set_size("main", 16)
end)
minetest.register_on_leaveplayer(function(player)
minetest.remove_detached_inventory("edit_bag_" .. player:get_player_name())
end)
local function on_place(itemstack, player, pointed_thing)
if pointed_thing.type ~= "node" then return end
local item_list = get_item_list(itemstack)
local total_count = 0
for i, item_stack in ipairs(item_list) do
total_count = total_count + item_stack:get_count()
end
local selected_index = math.round((total_count - 1) * math.random()) + 1
local selected_item
local current_index = 0
for i, item_stack in ipairs(item_list) do
local count = item_stack:get_count()
if count > 0 then
current_index = current_index + count
if current_index >= selected_index then
selected_item = item_stack
break
end
end
end
if selected_item then
local pos = edit.pointed_thing_to_pos(pointed_thing)
edit.place_item_like_player(player, {name = selected_item}, pos)
end
end
local function on_use(itemstack, user, pointed_thing)
local meta = itemstack:get_meta()
local str = meta:get("edit_bag")
local name = user:get_player_name()
local item_list = {}
if str then item_list = minetest.deserialize(str) end
for i, str in pairs(item_list) do
item_list[i] = ItemStack(str)
end
local inv_ref = minetest.get_inventory({type = "detached", name = "edit_bag_" .. name})
inv_ref:set_list("main", item_list)
local formspec = "formspec_version[4]size[10.2,10]" ..
"label[0.2,0.9;Bag contents:]" ..
"button_exit[9,0.2;1,1;quit;X]" ..
"list[detached:edit_bag_" .. name .. ";main;0.2,1.4;8,2;]" ..
"label[0.2,4.5;Inventory:]" ..
"list[current_player;main;0.2,5;8,4;]"
minetest.show_formspec(name, "edit:bag", formspec)
end
minetest.register_tool("edit:bag", {
description = "Edit Bag",
short_description = "Edit Bag",
tiles = {"edit_bag.png"},
inventory_image = "edit_bag.png",
range = 10,
on_place = on_place,
on_secondary_use = on_place,
on_use = on_use,
})

View File

@ -47,7 +47,7 @@ local function place_circle(player, pos, node)
offset1.y * factor.y,
offset1.z * factor.z )
local pos1 = vector.add(center, offset1)
edit.place_node_like_player(player, node, pos1)
edit.place_item_like_player(player, node, pos1)
local offset2 = vector.new(
offset1.z,
@ -55,7 +55,7 @@ local function place_circle(player, pos, node)
offset1.x
)
local pos2 = vector.add(center, offset2)
edit.place_node_like_player(player, node, pos2)
edit.place_item_like_player(player, node, pos2)
end
end

View File

@ -46,7 +46,7 @@ minetest.register_tool("edit:copy",{
tiles = {"edit_copy.png"},
inventory_image = "edit_copy.png",
range = 10,
groups = {edit_place_preview = 1,},
groups = {edit_place_preview = 1, edit_box_select_preview = 1},
on_place = copy_on_place,
on_secondary_use = copy_on_place,
_edit_get_selection_points = function(player)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

View File

@ -1,5 +1,5 @@
local function player_select_node_formspec(player)
edit.player_select_node(player, "Select item to use for fill", function(player, name)
local function player_select_item_formspec(player)
edit.player_select_item(player, "Select item to use for fill", function(player, item_str)
local d = edit.player_data[player]
if
@ -12,7 +12,9 @@ local function player_select_node_formspec(player)
d.fill1.object:remove()
if not name then return end
local item = ItemStack(item_str)
local name = item:get_name()
if name == "" then return end
local def = minetest.registered_items[name]
@ -63,13 +65,13 @@ local function player_select_node_formspec(player)
voxel_manip:write_to_map(true)
voxel_manip:update_liquids()
else
local node = {name = name, param2 = param2}
local node = {name = item_str, param2 = param2}
-- Work top to bottom so we can remove falling nodes
for x = _end.x, start.x, -1 do
for y = _end.y, start.y, -1 do
for z = _end.z, start.z, -1 do
local pos = vector.new(x, y, z)
edit.place_node_like_player(player, node, pos)
edit.place_item_like_player(player, node, pos)
end
end
end
@ -100,7 +102,7 @@ local function fill_on_place(itemstack, player, pointed_thing)
return
end
player_select_node_formspec(player)
player_select_item_formspec(player)
elseif pos then
player_data.fill1 = edit.add_marker("edit:fill", pos, player)
end
@ -111,7 +113,7 @@ minetest.register_tool("edit:fill", {
tiles = {"edit_fill.png"},
inventory_image = "edit_fill.png",
range = 10,
groups = {edit_place_preview = 1,},
groups = {edit_place_preview = 1, edit_box_select_preview = 1},
on_place = fill_on_place,
on_secondary_use = fill_on_place,
_edit_get_selection_points = function(player)

View File

@ -4,6 +4,7 @@ edit.player_data = {}
edit.paste_preview_max_entities = tonumber(minetest.settings:get("edit_paste_preview_max_entities") or 2000)
edit.max_operation_volume = tonumber(minetest.settings:get("edit_max_operation_volume") or 20000)
edit.fast_node_fill_threshold = tonumber(minetest.settings:get("edit_fast_node_fill_threshold") or 2000)
edit.polygon_preview_wire_frame_threshold = tonumber(minetest.settings:get("edit_polygon_preview_wire_frame_threshold") or 40)
minetest.register_privilege("edit", {
description = "Allows usage of edit mod nodes",
@ -128,10 +129,13 @@ minetest.register_on_placenode = function(func)
return old_register_on_placenode(func)
end
function edit.place_node_like_player(player, node, pos)
function edit.place_item_like_player(player, item, pos)
local node = table.copy(item)
local itemstack = ItemStack(item.name)
node.name = itemstack:get_name()
local def = minetest.registered_items[node.name]
if not def then return end
local is_node = minetest.registered_nodes[node.name] ~= nil
local itemstack = ItemStack(node.name)
local pointed_thing = {
type = "node",
above = pos,
@ -173,9 +177,9 @@ function edit.add_marker(id, pos, player)
return luaentity
end
local function player_select_node_formspec(player)
local function player_select_item_formspec(player)
local d = edit.player_data[player]
local search_value = d.player_select_node_search_value
local search_value = d.player_select_item_search_value
local doing_search = #search_value > 0
local inv = minetest.get_inventory({type = "player", name = player:get_player_name()})
local size = doing_search and 12 * 8 or inv:get_size("main")
@ -223,7 +227,7 @@ local function player_select_node_formspec(player)
local formspec = "formspec_version[4]size[" .. formspec_width .. "," .. formspec_height .. "]" ..
"button[0,0;0,0;minetest_sucks;" .. math.random() .. "]" .. -- Force Minetest to show this formspec
"label[0.5,0.7;" .. d.player_select_node_message .. "]" ..
"label[0.5,0.7;" .. d.player_select_item_message .. "]" ..
"button_exit[" .. formspec_width - 1.2 .. ",0.2;1,1;quit;X]" ..
"field_close_on_enter[search_field;false]" ..
"label[0.2,1.7;Search all items]" ..
@ -237,7 +241,7 @@ local function player_select_node_formspec(player)
if doing_search then
name = search_results[i]
else
name = inv:get_stack("main", i):get_name()
name = inv:get_stack("main", i):to_string()
end
if not name then break end
@ -255,28 +259,28 @@ local function player_select_node_formspec(player)
name .. ";" ..
name .. ";]"
end
edit.reliable_show_formspec(player, "edit:player_select_node", formspec)
edit.reliable_show_formspec(player, "edit:player_select_item", formspec)
end
minetest.register_on_player_receive_fields(function(player, formname, fields)
if formname ~= "edit:player_select_node" then return false end
if formname ~= "edit:player_select_item" then return false end
local d = edit.player_data[player]
for key, val in pairs(fields) do
if key:find(":") or key == "air" then
if d.player_select_node_callback then
d.player_select_node_callback(player, key)
d.player_select_node_callback = nil
minetest.close_formspec(player:get_player_name(), "edit:player_select_node")
if d.player_select_item_callback then
d.player_select_item_callback(player, key)
d.player_select_item_callback = nil
minetest.close_formspec(player:get_player_name(), "edit:player_select_item")
end
return true
end
end
if fields.quit then
if d.player_select_node_callback then
d.player_select_node_callback(player, nil)
d.player_select_node_callback = nil
if d.player_select_item_callback then
d.player_select_item_callback(player, nil)
d.player_select_item_callback = nil
end
return true
elseif fields.cancel_search then
@ -285,24 +289,24 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
if
fields.search_field and
fields.search_field ~= d.player_select_node_search_value
fields.search_field ~= d.player_select_item_search_value
then
d.player_select_node_search_value = fields.search_field
player_select_node_formspec(player)
d.player_select_item_search_value = fields.search_field
player_select_item_formspec(player)
return true
end
return true
end)
function edit.player_select_node(player, message, callback)
function edit.player_select_item(player, message, callback)
local d = edit.player_data[player]
if d.player_select_node_callback then
d.player_select_node_callback(player, nil)
if d.player_select_item_callback then
d.player_select_item_callback(player, nil)
end
d.player_select_node_callback = callback
d.player_select_node_search_value = d.player_select_node_search_value or ""
d.player_select_node_message = message
player_select_node_formspec(player)
d.player_select_item_callback = callback
d.player_select_item_search_value = d.player_select_item_search_value or ""
d.player_select_item_message = message
player_select_item_formspec(player)
end
edit.modpath = minetest.get_modpath("edit")
@ -318,3 +322,6 @@ dofile(edit.modpath .. "/circle.lua")
dofile(edit.modpath .. "/mirror.lua")
dofile(edit.modpath .. "/screwdriver.lua")
dofile(edit.modpath .. "/replace.lua")
dofile(edit.modpath .. "/polygon.lua")
dofile(edit.modpath .. "/line.lua")
dofile(edit.modpath .. "/bag.lua")

190
line.lua Normal file
View File

@ -0,0 +1,190 @@
-- https://www.geeksforgeeks.org/bresenhams-algorithm-for-3-d-line-drawing/
-- This Site is affiliated under CCBY-SA https://www.geeksforgeeks.org/legal/copyright-information/
-- JS code for generating points on a 3-D line
-- using Bresenham's Algorithm
-- Converted from the original to Lua
function edit.calculate_line_points(p1, p2)
p1 = vector.copy(p1)
p2 = vector.copy(p2)
local output = {vector.copy(p1)}
local d = vector.apply(vector.subtract(p1, p2), math.abs)
local s = vector.new(
p2.x > p1.x and 1 or -1,
p2.y > p1.y and 1 or -1,
p2.z > p1.z and 1 or -1
)
-- Driving axis is X-axis
if d.x >= d.y and d.x >= d.z then
local n1 = 2 * d.y - d.x
local n2 = 2 * d.z - d.x
while p1.x ~= p2.x do
p1.x = p1.x + s.x
if n1 >= 0 then
p1.y = p1.y + s.y
n1 = n1 - 2 * d.x
end
if n2 >= 0 then
p1.z = p1.z + s.z
n2 = n2 - 2 * d.x
end
n1 = n1 + 2 * d.y
n2 = n2 + 2 * d.z
table.insert(output, vector.copy(p1))
end
-- Driving axis is Y-axis
elseif d.y >= d.x and d.y >= d.z then
local n1 = 2 * d.x - d.y
local n2 = 2 * d.z - d.y
while p1.y ~= p2.y do
p1.y = p1.y + s.y
if n1 >= 0 then
p1.x = p1.x + s.x
n1 = n1 - 2 * d.y
end
if n2 >= 0 then
p1.z = p1.z + s.z
n2 = n2 - 2 * d.y
end
n1 = n1 + 2 * d.x
n2 = n2 + 2 * d.z
table.insert(output, vector.copy(p1))
end
-- Driving axis is Z-axis
else
local n1 = 2 * d.y - d.z
local n2 = 2 * d.x - d.z
while p1.z ~= p2.z do
p1.z = p1.z + s.z
if n1 >= 0 then
p1.y = p1.y + s.y
n1 = n1 - 2 * d.z
end
if n2 >= 0 then
p1.x = p1.x + s.x
n2 = n2 - 2 * d.z
end
n1 = n1 + 2 * d.y
n2 = n2 + 2 * d.x
table.insert(output, vector.copy(p1))
end
end
return output
end
minetest.register_entity("edit:line", {
initial_properties = {
visual = "cube",
visual_size = { x = 1.1, y = 1.1 },
physical = false,
collide_with_objects = false,
static_save = false,
use_texture_alpha = true,
glow = -1,
backface_culling = false,
hp_max = 1,
textures = {
"edit_line.png",
"edit_line.png",
"edit_line.png",
"edit_line.png",
"edit_line.png",
"edit_line.png",
},
},
on_deactivate = function(self)
local player_data = edit.player_data[self._placer]
self.remove_called = true
if player_data then
local line1 = player_data.line1
if line1 and not line1.remove_called then
line1.object:remove()
end
player_data.line1 = nil
local line2 = player_data.line2
if line2 and not line2.remove_called then
line2.object:remove()
end
player_data.line2 = nil
player_data.old_pointed_pos = nil
end
end,
})
local function place_line(player, item_name)
local player_data = edit.player_data[player]
if not player_data.line1 then return end
if not item_name then
player_data.line1.object:remove()
return
end
local pos1 = player_data.line1._pos
local pos2 = player_data.line2._pos
local size = vector.add(vector.apply(vector.subtract(pos1, pos2), math.abs), vector.new(1, 1, 1))
local pos = vector.new(
math.min(pos1.x, pos2.x),
math.min(pos1.y, pos2.y),
math.min(pos1.z, pos2.z)
)
player_data.undo_schematic = edit.schematic_from_map(pos, size)
local line_points = edit.calculate_line_points(pos1, pos2)
local item = {name = item_name}
for i, pos in pairs(line_points) do
edit.place_item_like_player(player, item, pos)
end
player_data.line1.object:remove()
end
local function line_on_place(itemstack, player, pointed_thing)
if not edit.on_place_checks(player) then return end
if not pointed_thing.above then
pointed_thing = edit.get_pointed_thing_node(player)
end
local pos = edit.pointed_thing_to_pos(pointed_thing)
if not pos then return end
local player_data = edit.player_data[player]
if not player_data.line1 then
player_data.line1 = edit.add_marker("edit:line", pos, player)
if not player_data.line1 then return end
else
player_data.line2 = edit.add_marker("edit:line", pos, player)
if not player_data.line2 then return end
local diff = vector.subtract(player_data.line1._pos, pos)
local volume = vector.add(vector.apply(diff, math.abs), 1)
if volume.x * volume.y * volume.z > edit.max_operation_volume then
edit.display_size_error(player)
player_data.line1.object:remove()
return
end
edit.player_select_item(player, "Select item to fill the line", place_line)
end
edit.old_pointed_pos = nil
end
minetest.register_tool("edit:line", {
description = "Edit Line",
tiles = {"edit_line.png"},
inventory_image = "edit_line.png",
range = 10,
groups = {edit_place_preview = 1,},
on_place = line_on_place,
on_secondary_use = line_on_place,
_edit_get_selection_points = function(player)
local d = edit.player_data[player]
return d.line1 and d.line1._pos, d.line2 and d.line2._pos
end
})

View File

@ -21,10 +21,10 @@ local function do_mirror(player, pos, node)
if d.mirror_mode == "x" then
offset.x = -offset.x
edit.place_node_like_player(player, node, vector.add(center, offset))
edit.place_item_like_player(player, node, vector.add(center, offset))
elseif d.mirror_mode == "z" then
offset.z = -offset.z
edit.place_node_like_player(player, node, vector.add(center, offset))
edit.place_item_like_player(player, node, vector.add(center, offset))
elseif d.mirror_mode == "xz" then
for i = 1, 4 do
local axis = "x"
@ -32,7 +32,7 @@ local function do_mirror(player, pos, node)
axis = "z"
end
offset[axis] = -offset[axis]
edit.place_node_like_player(player, node, vector.add(center, offset))
edit.place_item_like_player(player, node, vector.add(center, offset))
end
elseif d.mirror_mode == "eighths" then
for i = 1, 8 do
@ -44,7 +44,7 @@ local function do_mirror(player, pos, node)
offset = vector.new(offset.z, offset.y, offset.x)
end
offset[axis] = -offset[axis]
edit.place_node_like_player(player, node, vector.add(center, offset))
edit.place_item_like_player(player, node, vector.add(center, offset))
end
end

226
polygon.lua Normal file
View File

@ -0,0 +1,226 @@
-- https://en.wikipedia.org/wiki/M%C3%B6ller%E2%80%93Trumbore_intersection_algorithm#C++_implementation
-- Converted from C++ to Lua
-- License: CC BY-SA https://creativecommons.org/licenses/by-sa/4.0/
local function ray_intersects_triangle(ray_origin, ray_vector, vertex_a, vertex_b, vertex_c)
local epsilon = 0.0000001
local edge1 = vector.subtract(vertex_b, vertex_a)
local edge2 = vector.subtract(vertex_c, vertex_a)
local ray_cross_e2 = vector.cross(ray_vector, edge2)
local det = vector.dot(edge1, ray_cross_e2)
if det > -epsilon and det < epsilon then
return -- This ray is parallel to this triangle.
end
local inv_det = 1.0 / det
local s = vector.subtract(ray_origin, vertex_a)
local u = inv_det * vector.dot(s, ray_cross_e2)
if u < 0 or u > 1 then return end
local s_cross_e1 = vector.cross(s, edge1)
local v = inv_det * vector.dot(ray_vector, s_cross_e1)
if v < 0 or u + v > 1 then return end
-- At this stage we can compute t to find out where the intersection point is on the line.
local t = inv_det * vector.dot(edge2, s_cross_e1)
if t > epsilon then -- ray intersection
return vector.add(ray_origin, vector.multiply(ray_vector, t))
else -- This means that there is a line intersection but not a ray intersection.
return
end
end
function edit.calculate_triangle_points(a, b, c)
local bounding_box_min = vector.copy(a)
local bounding_box_max = vector.copy(a)
for index, axis in pairs({"x", "y", "z"}) do
bounding_box_min[axis] = math.min(a[axis], b[axis], c[axis])
bounding_box_max[axis] = math.max(a[axis], b[axis], c[axis])
end
-- Calculate normal
local u = vector.subtract(b, a)
local v = vector.subtract(c, a)
local normal = vector.new(
u.y * v.z - u.z * v.y,
u.z * v.x - u.x * v.z,
u.x * v.y - u.y * v.x
)
local selected_axis = "y"
local longest_length = 0
for axis, length in pairs(normal) do
length = math.abs(length)
if length > longest_length then
longest_length = length
selected_axis = axis
end
end
-- Switch from local to global coordinate system.
-- Also works the same to convert local to global coordinate system.
local function swap_coord_sys(v)
v = vector.copy(v)
local old_selected = v[selected_axis]
v[selected_axis] = v.y
v.y = old_selected
return v
end
local bounding_box_min_local = swap_coord_sys(bounding_box_min)
local bounding_box_max_local = swap_coord_sys(bounding_box_max)
local a_local = swap_coord_sys(a)
local b_local = swap_coord_sys(b)
local c_local = swap_coord_sys(c)
local results = {}
for x = bounding_box_min_local.x, bounding_box_max_local.x do
for z = bounding_box_min_local.z, bounding_box_max_local.z do
local intersection = ray_intersects_triangle(vector.new(x, 30928, z), vector.new(0, -1, 0), a_local, b_local, c_local)
if intersection then
table.insert(results, vector.round(swap_coord_sys(intersection)))
end
end
end
return results
end
local function place_polygon(player, item_name)
local player_data = edit.player_data[player]
if not player_data then return end
if not item_name or #player_data.polygon_markers < 2 then
player_data.polygon_markers.object:remove()
return
end
local markers = player_data.polygon_markers
local inf = 1 / 0
local bounding_box_min = vector.new(inf, inf, inf)
local bounding_box_max = vector.new(-inf, -inf, -inf)
for index, axis in pairs({"x", "y", "z"}) do
for i, marker in ipairs(markers) do
bounding_box_min[axis] = math.min(bounding_box_min[axis], marker._pos[axis])
bounding_box_max[axis] = math.max(bounding_box_max[axis], marker._pos[axis])
end
end
local volume = vector.add(vector.subtract(bounding_box_max, bounding_box_min), vector.new(1, 1, 1))
if volume.x * volume.y * volume.z > edit.max_operation_volume then
edit.display_size_error(player)
player_data.polygon_markers.object:remove()
return
end
player_data.undo_schematic = edit.schematic_from_map(bounding_box_min, volume)
local points = {}
for i = 3, #markers do
table.insert_all(
points,
edit.calculate_triangle_points(
markers[i]._pos,
markers[i - 1]._pos,
markers[1]._pos
)
)
end
local item = {name = item_name}
edit.place_item_like_player(player, item, markers[1]._pos)
item.param2 = minetest.get_node(markers[1]._pos).param2
for i, pos in pairs(points) do
edit.place_item_like_player(player, item, pos)
end
player_data.polygon_markers.object:remove()
end
minetest.register_entity("edit:polygon", {
initial_properties = {
visual = "cube",
visual_size = { x = 1.1, y = 1.1 },
physical = false,
collide_with_objects = false,
static_save = false,
use_texture_alpha = true,
glow = -1,
backface_culling = false,
hp_max = 1,
textures = {
"edit_polygon.png",
"edit_polygon.png",
"edit_polygon.png",
"edit_polygon.png",
"edit_polygon.png",
"edit_polygon.png",
},
},
on_deactivate = function(self)
local player_data = edit.player_data[self._placer]
if player_data then
local index = table.indexof(player_data.polygon_markers, self)
table.remove(player_data.polygon_markers, index)
local marker = player_data.polygon_markers[1]
if index == 1 and marker then
local textures = marker.object:get_properties().textures
for i, texture in pairs(textures) do
textures[i] = texture .. "^[multiply:green"
end
marker.object:set_properties({textures = textures})
end
end
player_data.old_pointed_pos = nil
end,
})
local function polygon_on_place(itemstack, player, pointed_thing)
if not edit.on_place_checks(player) then return end
if not pointed_thing.above then
pointed_thing = edit.get_pointed_thing_node(player)
end
local pos = edit.pointed_thing_to_pos(pointed_thing)
if not pos then return end
local player_data = edit.player_data[player]
if not player_data.polygon_markers then
player_data.polygon_markers = {}
player_data.polygon_markers.object = player_data.polygon_markers
player_data.polygon_markers.object.remove = function(self)
for i, luaentity in ipairs(table.copy(self)) do
luaentity.object:remove()
end
end
end
if player_data.polygon_markers[1] and vector.equals(player_data.polygon_markers[1]._pos, pos) then
edit.player_select_item(player, "Select item to fill the polygon", place_polygon)
return
end
local marker = edit.add_marker("edit:polygon", pos, player)
if not marker then return end
table.insert(player_data.polygon_markers, marker)
if marker == player_data.polygon_markers[1] then
local textures = marker.object:get_properties().textures
for i, texture in pairs(textures) do
textures[i] = texture .. "^[multiply:green"
end
marker.object:set_properties({textures = textures})
end
end
minetest.register_tool("edit:polygon", {
description = "Edit Polygon",
tiles = {"edit_polygon.png"},
inventory_image = "edit_polygon.png",
range = 10,
groups = {edit_place_preview = 1,},
on_place = polygon_on_place,
on_secondary_use = polygon_on_place,
})

View File

@ -103,6 +103,135 @@ local function create_paste_preview(player)
edit.rotate_paste_preview(player)
end
minetest.register_entity("edit:polygon_preview", {
initial_properties = {
visual = "cube",
physical = false,
pointable = false,
collide_with_objects = false,
static_save = false,
use_texture_alpha = true,
glow = -1,
backface_culling = false,
visual_size = { x = 1.05, y = 1.05 },
textures = {
"edit_select_preview.png^[sheet:8x8:1,1",
"edit_select_preview.png^[sheet:8x8:1,1",
"edit_select_preview.png^[sheet:8x8:1,1",
"edit_select_preview.png^[sheet:8x8:1,1",
"edit_select_preview.png^[sheet:8x8:1,1",
"edit_select_preview.png^[sheet:8x8:1,1",
},
}
})
local function hide_polygon_preview(player)
local player_data = edit.player_data[player]
local previews = player_data.polygon_previews
for i, obj_ref in ipairs(previews) do
obj_ref:set_properties({is_visible = false})
end
if player_data.polygon_preview_hud then
player:hud_remove(player_data.polygon_preview_hud)
player_data.polygon_preview_hud = nil
end
player_data.polygon_preview_shown = false
end
local function show_polygon_preview(player, show_polygon_hud)
local player_data = edit.player_data[player]
if not player_data.polygon_previews then
player_data.polygon_previews = {}
player_data.polygon_previews.object = player_data.polygon_previews
player_data.polygon_previews.object.remove = function(self)
for i, luaentity in ipairs(table.copy(self)) do
luaentity:remove()
end
end
end
for i, obj_ref in ipairs(player_data.polygon_previews) do
obj_ref:set_properties({is_visible = true})
end
player_data.polygon_preview_shown = true
end
local function update_polygon_preview(player, marker_pos_list, show_polygon_hud)
local player_pos = player:get_pos()
local player_data = edit.player_data[player]
local show_full_preview = true
local bounding_box_min = vector.copy(marker_pos_list[1])
local bounding_box_max = vector.copy(marker_pos_list[1])
for index, axis in pairs({"x", "y", "z"}) do
for i, pos in ipairs(marker_pos_list) do
bounding_box_min[axis] = math.min(bounding_box_min[axis], pos[axis])
bounding_box_max[axis] = math.max(bounding_box_max[axis], pos[axis])
end
if
bounding_box_max[axis] - bounding_box_min[axis] + 1 >
edit.polygon_preview_wire_frame_threshold
then
show_full_preview = false
end
end
local pos_list = {}
local volume = vector.add(vector.subtract(bounding_box_max, bounding_box_min), vector.new(1, 1, 1))
if volume.x * volume.y * volume.z <= edit.max_operation_volume then
if #marker_pos_list == 2 or not show_full_preview then
table.insert_all(
pos_list,
edit.calculate_line_points(marker_pos_list[1], marker_pos_list[2])
)
end
for i = 3, #marker_pos_list do
if show_full_preview then
table.insert_all(
pos_list,
edit.calculate_triangle_points(
marker_pos_list[i],
marker_pos_list[i - 1],
marker_pos_list[1]
)
)
else
table.insert_all(
pos_list,
edit.calculate_line_points(marker_pos_list[i], marker_pos_list[i - 1])
)
table.insert_all(
pos_list,
edit.calculate_line_points(marker_pos_list[i], marker_pos_list[1])
)
end
end
end
local preview_objs = player_data.polygon_previews
if #preview_objs > #pos_list then
for i = #pos_list + 1, #preview_objs do
preview_objs[#pos_list + 1]:remove()
table.remove(preview_objs, #pos_list + 1)
end
elseif #preview_objs < #pos_list then
for i = #preview_objs + 1, #pos_list do
local obj_ref = minetest.add_entity(player_pos, "edit:polygon_preview")
table.insert(preview_objs, obj_ref)
end
end
for i, pos in pairs(pos_list) do
preview_objs[i]:set_pos(pos)
end
end
minetest.register_entity("edit:select_preview", {
initial_properties = {
visual = "cube",
@ -289,6 +418,36 @@ local function set_schematic_offset(player)
d.schematic_offset = offset
end
local function show_place_preview(player, pos, item)
local d = edit.player_data[player]
if not d.place_preview or not d.place_preview.object:get_pos() then
local obj_ref = minetest.add_entity(player:get_pos(), "edit:place_preview")
if not obj_ref then return end
d.place_preview = obj_ref:get_luaentity()
d.place_preview_shown = true
d.place_preview_item = nil
elseif not d.place_preview_shown then
d.place_preview.object:set_properties({ is_visible = true })
d.place_preview.object:set_detach()
d.place_preview_shown = true
end
if not vector.equals(d.place_preview.object:get_pos(), pos) then
d.place_preview.object:set_pos(pos)
end
if d.place_preview_item ~= item then
local tex = minetest.registered_items[item].tiles[1] ..
"^[opacity:150"
d.place_preview_item = item
d.place_preview.object:set_properties({
textures = { tex, tex, tex, tex, tex, tex }
})
end
end
minetest.register_globalstep(function(dtime)
for _, player in pairs(minetest.get_connected_players()) do
local item = player:get_wielded_item():get_name()
@ -314,70 +473,96 @@ minetest.register_globalstep(function(dtime)
elseif d.paste_preview_hud then hide_paste_preview(player) end
elseif d.paste_preview_hud then hide_paste_preview(player) end
-- Select preview
local node1_pos
local node2_pos
local pointed_pos
local tool_def = minetest.registered_items[item] or minetest.registered_items["air"]
-- Stuff for Place preview and box select preview
local marker1_pos
local marker2_pos
local should_show_place_preview = minetest.get_item_group(item, "edit_place_preview") ~= 0
local should_use_box_select_preview = minetest.get_item_group(item, "edit_box_select_preview") ~= 0
if tool_def._edit_get_selection_points then
node1_pos, node2_pos = tool_def._edit_get_selection_points(player)
if should_show_place_preview or should_use_box_select_preview then
local tool_def = minetest.registered_items[item]
if tool_def._edit_get_selection_points then
marker1_pos, marker2_pos = tool_def._edit_get_selection_points(player)
end
if not marker2_pos then
if tool_def._edit_get_pointed_pos then
marker2_pos = tool_def._edit_get_pointed_pos(player)
else
local pointed_thing = edit.get_pointed_thing_node(player)
marker2_pos = edit.pointed_thing_to_pos(pointed_thing)
end
else should_show_place_preview = false end
end
if not node2_pos or not node1_pos then
if tool_def._edit_get_pointed_pos then
pointed_pos = tool_def._edit_get_pointed_pos(player)
else
local pointed_thing = edit.get_pointed_thing_node(player)
pointed_pos = edit.pointed_thing_to_pos(pointed_thing)
end
end
-- Box select preview
if should_use_box_select_preview and marker1_pos and marker2_pos then
local diff = vector.subtract(marker1_pos, marker2_pos)
local size = vector.apply(diff, math.abs)
size = vector.add(size, vector.new(1, 1, 1))
local size_too_big = size.x * size.y * size.z > edit.max_operation_volume
if not size_too_big then
local preview_pos = vector.add(marker2_pos, vector.multiply(diff, 0.5))
update_select_preview(player, preview_pos, size)
elseif d.select_preview_shown then hide_select_preview(player) end
elseif d.select_preview_shown then hide_select_preview(player) end
if minetest.get_item_group(item, "edit_place_preview") ~= 0 and not node2_pos and pointed_pos then
if not d.place_preview or not d.place_preview.object:get_pos() then
local obj_ref = minetest.add_entity(player:get_pos(), "edit:place_preview")
if not obj_ref then return end
d.place_preview = obj_ref:get_luaentity()
d.place_preview_shown = true
d.place_preview_item = nil
elseif not d.place_preview_shown then
d.place_preview.object:set_properties({ is_visible = true })
d.place_preview.object:set_detach()
d.place_preview_shown = true
end
if not vector.equals(d.place_preview.object:get_pos(), pointed_pos) then
d.place_preview.object:set_pos(pointed_pos)
end
if d.place_preview_item ~= item then
local tex = minetest.registered_items[item].tiles[1] ..
"^[opacity:150"
d.place_preview_item = item
d.place_preview.object:set_properties({
textures = { tex, tex, tex, tex, tex, tex }
})
end
-- Place preview
if should_show_place_preview and marker2_pos then
show_place_preview(player, marker2_pos, item)
elseif d.place_preview_shown then
d.place_preview.object:set_properties({ is_visible = false })
d.place_preview.object:set_attach(player)
d.place_preview_shown = false
end
if not node2_pos then
node2_pos = pointed_pos
-- Polygon preview
if item == "edit:polygon" or item == "edit:line" then
if marker2_pos then
if not d.polygon_preview_shown then
show_polygon_preview(player)
end
if not d.old_pointed_pos then d.old_pointed_pos = vector.new(0.5, 0.5, 0.5) end
if
d.old_polygon_item ~= item or
not vector.equals(d.old_pointed_pos, marker2_pos)
then
if item == "edit:polygon" then
local markers = d.polygon_markers or {}
local marker_pos_list = {}
for i, marker in ipairs(markers) do
table.insert(marker_pos_list, marker._pos)
end
table.insert(marker_pos_list, marker2_pos)
update_polygon_preview(player, marker_pos_list)
else
local marker_pos_list = {}
if marker1_pos then table.insert(marker_pos_list, marker1_pos) end
table.insert(marker_pos_list, marker2_pos)
update_polygon_preview(player, marker_pos_list)
end
d.old_pointed_pos = marker2_pos
d.old_polygon_item = item
end
end
elseif d.polygon_preview_shown then
hide_polygon_preview(player)
end
if node1_pos and node2_pos then
local diff = vector.subtract(node1_pos, node2_pos)
local size = vector.apply(diff, math.abs)
size = vector.add(size, vector.new(1, 1, 1))
local size_too_big = size.x * size.y * size.z > edit.max_operation_volume
if not size_too_big then
local preview_pos = vector.add(node2_pos, vector.multiply(diff, 0.5))
update_select_preview(player, preview_pos, size)
elseif d.select_preview_shown then hide_select_preview(player) end
elseif d.select_preview_shown then hide_select_preview(player) end
if item == "edit:polygon" then
if not d.polygon_preview_hud then
d.polygon_preview_hud = player:hud_add({
hud_elem_type = "text",
text = "Finish the polygon by placing a marker on the green marker",
position = {x = 0.5, y = 0.8},
z_index = 100,
number = 0xffffff
})
end
elseif d.polygon_preview_hud then
player:hud_remove(d.polygon_preview_hud)
d.polygon_preview_hud = nil
end
end
end)

View File

@ -102,7 +102,7 @@ minetest.register_tool("edit:replace", {
tiles = {"edit_replace.png"},
inventory_image = "edit_replace.png",
range = 10,
groups = {edit_place_preview = 1,},
groups = {edit_place_preview = 1, edit_box_select_preview = 1},
on_place = replace_on_place,
on_secondary_use = replace_on_place,
_edit_get_selection_points = function(player)
@ -187,7 +187,7 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
if not fields.continue then return true end
edit.player_select_node(player, "Select item to replace nodes", function(player, name)
edit.player_select_item(player, "Select item to replace nodes", function(player, name)
if
not d.replace1 or not d.replace2 or
not d.replace_source_nodes or
@ -233,7 +233,7 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
if is_node then
minetest.swap_node(pos, node)
else
edit.place_node_like_player(player, node, pos)
edit.place_item_like_player(player, node, pos)
end
end
end

View File

@ -10,3 +10,7 @@ edit_max_operation_volume (Max edit operation volume) int 20000
# To disable slow node placement, set the threshold to 0.
# With fast node placement, callbacks are not called so some nodes might be broken.
edit_fast_node_fill_threshold (Fast node fill threshold volume) int 2000
# If one side of the polygon preview is greater than this setting,
# a wire frame is used instead of the full preview.
edit_polygon_preview_wire_frame_threshold (Polygon preview wire frame threshold) int 40

BIN
textures/edit_bag.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 B

BIN
textures/edit_line.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 B

BIN
textures/edit_polygon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 B