From 725d19919122bd9c0e6c3db14b594ac46a1951b7 Mon Sep 17 00:00:00 2001 From: MrRar Date: Mon, 19 Feb 2024 20:02:24 -0600 Subject: [PATCH] Add bag, line, and polygon tools --- README.md | 30 ++-- bag.lua | 118 ++++++++++++++++ circle.lua | 4 +- copy.lua | 2 +- figure.png | Bin 18842 -> 0 bytes fill.lua | 16 ++- init.lua | 57 ++++---- line.lua | 190 +++++++++++++++++++++++++ mirror.lua | 8 +- polygon.lua | 226 +++++++++++++++++++++++++++++ preview.lua | 291 +++++++++++++++++++++++++++++++------- replace.lua | 6 +- settingtypes.txt | 4 + textures/edit_bag.png | Bin 0 -> 166 bytes textures/edit_line.png | Bin 0 -> 124 bytes textures/edit_polygon.png | Bin 0 -> 175 bytes 16 files changed, 848 insertions(+), 104 deletions(-) create mode 100644 bag.lua delete mode 100644 figure.png create mode 100644 line.lua create mode 100644 polygon.lua create mode 100644 textures/edit_bag.png create mode 100644 textures/edit_line.png create mode 100644 textures/edit_polygon.png diff --git a/README.md b/README.md index 2af2dcd..fd82efe 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/bag.lua b/bag.lua new file mode 100644 index 0000000..ee8f773 --- /dev/null +++ b/bag.lua @@ -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, +}) diff --git a/circle.lua b/circle.lua index 59479a7..89980ec 100644 --- a/circle.lua +++ b/circle.lua @@ -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 diff --git a/copy.lua b/copy.lua index 2a28738..314edb2 100644 --- a/copy.lua +++ b/copy.lua @@ -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) diff --git a/figure.png b/figure.png deleted file mode 100644 index 94dded9340b46e179aa96f2a2d7276c2ea6f0dcd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18842 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 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") diff --git a/line.lua b/line.lua new file mode 100644 index 0000000..a1d6006 --- /dev/null +++ b/line.lua @@ -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 +}) diff --git a/mirror.lua b/mirror.lua index 77b66f0..10cddb1 100644 --- a/mirror.lua +++ b/mirror.lua @@ -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 diff --git a/polygon.lua b/polygon.lua new file mode 100644 index 0000000..b078d1c --- /dev/null +++ b/polygon.lua @@ -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, +}) diff --git a/preview.lua b/preview.lua index 56e0819..c73082f 100644 --- a/preview.lua +++ b/preview.lua @@ -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) diff --git a/replace.lua b/replace.lua index 01a7094..7a3c718 100644 --- a/replace.lua +++ b/replace.lua @@ -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 diff --git a/settingtypes.txt b/settingtypes.txt index 4b0b89a..56d96e3 100644 --- a/settingtypes.txt +++ b/settingtypes.txt @@ -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 diff --git a/textures/edit_bag.png b/textures/edit_bag.png new file mode 100644 index 0000000000000000000000000000000000000000..c76b8771c18261a2a4b82ed76040da34a9d52cb6 GIT binary patch literal 166 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`xt=bLAr`0CPIBa9P~>24{PAD^ zx^U-3u}8b^m{@EHF*m;9-20blMdiCV=7?Zc{%Nz9bI3kIo{!L(CUyliB9El{qL?%_utv Pw3xxu)z4*}Q$iB}{sKMd literal 0 HcmV?d00001 diff --git a/textures/edit_line.png b/textures/edit_line.png new file mode 100644 index 0000000000000000000000000000000000000000..43147f20be80b67d599c31a755e52c364a61051c GIT binary patch literal 124 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`&YmugAr_~Po z%y}N$PK1T%yYW75NMeu?PiAP?efV$B=MPPaX=ZOf-#f0rSHZV>>dg~uf@>D+XW(1+ zEVucTMzfn<6uT0yRc1k>w}X1bzvete%c;4|FAu$7{pzFbF_Y;wOGMm-w{BPY%Wj|3 YFPk!5So~agB+vy6p00i_>zopr0QG@DzyJUM literal 0 HcmV?d00001