Improve area selection, add translation support.
This commit is contained in:
parent
ff62e98edc
commit
c6d418d3db
@ -11,7 +11,6 @@ read_globals = {
|
|||||||
table = {fields = {"copy", "getn", "indexof"}},
|
table = {fields = {"copy", "getn", "indexof"}},
|
||||||
|
|
||||||
"minetest",
|
"minetest",
|
||||||
"DIR_DELIM",
|
|
||||||
"PseudoRandom",
|
"PseudoRandom",
|
||||||
"vector",
|
"vector",
|
||||||
"VoxelArea",
|
"VoxelArea",
|
||||||
|
27
README.md
27
README.md
@ -1,6 +1,7 @@
|
|||||||
# Meshport (Minetest Mesh Exporter)
|
# Meshport (Minetest Mesh Exporter)
|
||||||
|
|
||||||
[](https://github.com/random-geek/meshport/actions)
|
[](https://github.com/random-geek/meshport/actions)
|
||||||
|
[](https://content.minetest.net/packages/random_geek/meshport/)
|
||||||
[](https://www.gnu.org/licenses/lgpl-3.0.en.html)
|
[](https://www.gnu.org/licenses/lgpl-3.0.en.html)
|
||||||
|
|
||||||

|

|
||||||
@ -14,8 +15,25 @@ drawtypes are not yet supported.
|
|||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
Use `/mesh1` and `/mesh2` to set the corners of the area you want exported,
|
Only players with the `meshport` privilege are allowed to select areas and
|
||||||
then use `/meshport [filename]` to export the mesh (filename is optional).
|
export meshes. This privilege is granted to singleplayer/admin players by
|
||||||
|
default.
|
||||||
|
|
||||||
|
To export a mesh, first select the area you want to export. There are two ways
|
||||||
|
to do this:
|
||||||
|
|
||||||
|
- Use the **Meshport Area Selector** tool. Left- or right-click on a node or
|
||||||
|
object to select either corner of the area. Hold sneak while clicking a node
|
||||||
|
to select the node in front of the face you clicked on.
|
||||||
|
- Or, use the `/mesh1` and `/mesh2` commands to set either corner. You can
|
||||||
|
specify a position (e.g. `/mesh1 -24 0 24`) or leave the argument blank to
|
||||||
|
use your current position (e.g. `/mesh1`).
|
||||||
|
|
||||||
|
After selecting an area, use `/meshport [filename]` to export the mesh
|
||||||
|
(filename is optional).
|
||||||
|
|
||||||
|
The `/meshrst` command can be used to clear the current
|
||||||
|
selection.
|
||||||
|
|
||||||
Folders containing exported meshes, including `.obj` and `.mtl` files, are
|
Folders containing exported meshes, including `.obj` and `.mtl` files, are
|
||||||
saved in the `meshport` folder of the world directory.
|
saved in the `meshport` folder of the world directory.
|
||||||
@ -91,4 +109,7 @@ allowing both faces to be visible.
|
|||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
Meshport is licensed under the GNU LGPL v3.0.
|
Textures are licensed under [CC BY 4.0][2]. Everything else (including source code)
|
||||||
|
is licensed under the GNU LGPL v3.0.
|
||||||
|
|
||||||
|
[2]: https://creativecommons.org/licenses/by/4.0/
|
||||||
|
13
export.lua
13
export.lua
@ -21,6 +21,9 @@
|
|||||||
-- Much of the mesh generation code in this file is derived from Minetest's
|
-- Much of the mesh generation code in this file is derived from Minetest's
|
||||||
-- MapblockMeshGenerator class. See minetest/src/client/content_mapblock.cpp.
|
-- MapblockMeshGenerator class. See minetest/src/client/content_mapblock.cpp.
|
||||||
|
|
||||||
|
local S = meshport.S
|
||||||
|
local vec = vector.new -- Makes defining tables of vertices a little less painful.
|
||||||
|
|
||||||
--[[
|
--[[
|
||||||
THE CUBIC NODE PRIORITY SYSTEM
|
THE CUBIC NODE PRIORITY SYSTEM
|
||||||
|
|
||||||
@ -58,8 +61,6 @@ local CUBIC_FACE_PRIORITY = {
|
|||||||
plantlike_rooted = 4, -- base of plantlike_rooted is equivalent to `normal`.
|
plantlike_rooted = 4, -- base of plantlike_rooted is equivalent to `normal`.
|
||||||
}
|
}
|
||||||
|
|
||||||
local vec = vector.new -- Makes defining tables of vertices a little less painful.
|
|
||||||
|
|
||||||
local CUBIC_SIDE_FACES = {
|
local CUBIC_SIDE_FACES = {
|
||||||
{vec(-0.5, 0.5, -0.5), vec( 0.5, 0.5, -0.5), vec( 0.5, 0.5, 0.5), vec(-0.5, 0.5, 0.5)}, -- Y+
|
{vec(-0.5, 0.5, -0.5), vec( 0.5, 0.5, -0.5), vec( 0.5, 0.5, 0.5), vec(-0.5, 0.5, 0.5)}, -- Y+
|
||||||
{vec(-0.5, -0.5, 0.5), vec( 0.5, -0.5, 0.5), vec( 0.5, -0.5, -0.5), vec(-0.5, -0.5, -0.5)}, -- Y-
|
{vec(-0.5, -0.5, 0.5), vec( 0.5, -0.5, 0.5), vec( 0.5, -0.5, -0.5), vec(-0.5, -0.5, -0.5)}, -- Y-
|
||||||
@ -573,9 +574,9 @@ local function create_mesh_node(nodeDef, param2, playerName)
|
|||||||
|
|
||||||
if not meshport.obj_paths[meshName] then
|
if not meshport.obj_paths[meshName] then
|
||||||
if string.lower(string.sub(meshName, -4)) ~= ".obj" then
|
if string.lower(string.sub(meshName, -4)) ~= ".obj" then
|
||||||
meshport.log(playerName, "warning", string.format("Mesh %q is not supported.", meshName))
|
meshport.log(playerName, "warning", S("Mesh \"@1\" is not supported.", meshName))
|
||||||
else
|
else
|
||||||
meshport.log(playerName, "warning", string.format("Mesh %q could not be found.", meshName))
|
meshport.log(playerName, "warning", S("Mesh \"@1\" could not be found.", meshName))
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Cache a blank faces object so the player isn't warned again.
|
-- Cache a blank faces object so the player isn't warned again.
|
||||||
@ -760,7 +761,7 @@ end
|
|||||||
|
|
||||||
|
|
||||||
function meshport.create_mesh(playerName, p1, p2, path)
|
function meshport.create_mesh(playerName, p1, p2, path)
|
||||||
meshport.log(playerName, "info", "Generating mesh...")
|
meshport.log(playerName, "info", S("Generating mesh..."))
|
||||||
initialize_resources()
|
initialize_resources()
|
||||||
|
|
||||||
p1, p2 = vector.sort(p1, p2)
|
p1, p2 = vector.sort(p1, p2)
|
||||||
@ -795,5 +796,5 @@ function meshport.create_mesh(playerName, p1, p2, path)
|
|||||||
mesh:write_mtl(path, playerName)
|
mesh:write_mtl(path, playerName)
|
||||||
|
|
||||||
cleanup_resources()
|
cleanup_resources()
|
||||||
meshport.log(playerName, "info", "Finished. Saved to " .. path)
|
meshport.log(playerName, "info", S("Finished. Saved to @1", path))
|
||||||
end
|
end
|
||||||
|
286
init.lua
286
init.lua
@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
meshport = {
|
meshport = {
|
||||||
player_data = {},
|
player_data = {},
|
||||||
|
S = minetest.get_translator("meshport"),
|
||||||
}
|
}
|
||||||
|
|
||||||
modpath = minetest.get_modpath("meshport")
|
modpath = minetest.get_modpath("meshport")
|
||||||
@ -28,81 +29,298 @@ dofile(modpath .. "/parse_obj.lua")
|
|||||||
dofile(modpath .. "/nodebox.lua")
|
dofile(modpath .. "/nodebox.lua")
|
||||||
dofile(modpath .. "/export.lua")
|
dofile(modpath .. "/export.lua")
|
||||||
|
|
||||||
minetest.register_privilege("meshport", "Can save meshes with meshport.")
|
local S = meshport.S
|
||||||
|
local vec = vector.new
|
||||||
|
|
||||||
|
minetest.register_privilege("meshport", S("Can save meshes with Meshport."))
|
||||||
|
|
||||||
minetest.register_on_leaveplayer(function(player, timed_out)
|
minetest.register_on_leaveplayer(function(player, timed_out)
|
||||||
local name = player:get_player_name()
|
local name = player:get_player_name()
|
||||||
meshport.player_data[name] = nil
|
meshport.player_data[name] = nil
|
||||||
end)
|
end)
|
||||||
|
|
||||||
for i = 1, 2 do
|
for n = 1, 2 do
|
||||||
minetest.register_chatcommand("mesh" .. i, {
|
local tex = "meshport_corner_" .. n .. ".png"
|
||||||
|
|
||||||
|
minetest.register_entity("meshport:corner_" .. n, {
|
||||||
|
initial_properties = {
|
||||||
|
physical = false,
|
||||||
|
visual = "cube",
|
||||||
|
visual_size = {x = 1.04, y = 1.04, z = 1.04},
|
||||||
|
selectionbox = {-0.52, -0.52, -0.52, 0.52, 0.52, 0.52},
|
||||||
|
textures = {tex, tex, tex, tex, tex, tex},
|
||||||
|
static_save = false,
|
||||||
|
glow = minetest.LIGHT_MAX,
|
||||||
|
},
|
||||||
|
|
||||||
|
on_punch = function(self, hitter)
|
||||||
|
self.object:remove()
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
minetest.register_entity("meshport:border", {
|
||||||
|
initial_properties = {
|
||||||
|
physical = false,
|
||||||
|
visual = "upright_sprite",
|
||||||
|
textures = {
|
||||||
|
"meshport_border.png",
|
||||||
|
"meshport_border.png^[transformFX",
|
||||||
|
},
|
||||||
|
static_save = false,
|
||||||
|
glow = minetest.LIGHT_MAX,
|
||||||
|
},
|
||||||
|
|
||||||
|
on_punch = function(self, hitter)
|
||||||
|
if not hitter then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local playerName = hitter:get_player_name()
|
||||||
|
if not playerName then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local borders = meshport.player_data[playerName].borders
|
||||||
|
for i = 1, 6 do -- Remove all borders at once.
|
||||||
|
if borders[i] then
|
||||||
|
borders[i]:remove()
|
||||||
|
borders[i] = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
|
||||||
|
local SIDE_ROTATIONS = {
|
||||||
|
vec(0.5 * math.pi, 0, 0), -- Y+
|
||||||
|
vec(1.5 * math.pi, 0, 0), -- Y-
|
||||||
|
vec(0, 1.5 * math.pi, 0), -- X+
|
||||||
|
vec(0, 0.5 * math.pi, 0), -- X-
|
||||||
|
vec(0, 0, 0), -- Z+
|
||||||
|
vec(0, math.pi, 0), -- Z-
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
local function mark_borders(playerData)
|
||||||
|
local pos1, pos2 = vector.sort(playerData.pos[1], playerData.pos[2])
|
||||||
|
local center = vector.multiply(vector.add(pos1, pos2), 0.5)
|
||||||
|
-- Add 0.01 to avoid z-fighting with blocks or corner markers.
|
||||||
|
local c1, c2 = vector.subtract(pos1, 0.5 + 0.01), vector.add(pos2, 0.5 + 0.01)
|
||||||
|
|
||||||
|
local sideCenters = {
|
||||||
|
vec(center.x, c2.y, center.z), -- Y+
|
||||||
|
vec(center.x, c1.y, center.z), -- Y-
|
||||||
|
vec(c2.x, center.y, center.z), -- X+
|
||||||
|
vec(c1.x, center.y, center.z), -- X-
|
||||||
|
vec(center.x, center.y, c2.z), -- Z+
|
||||||
|
vec(center.x, center.y, c1.z), -- Z-
|
||||||
|
}
|
||||||
|
|
||||||
|
local size = vector.subtract(c2, c1)
|
||||||
|
local sideSizes = {
|
||||||
|
{x = size.x, y = size.z}, -- Y+
|
||||||
|
{x = size.x, y = size.z}, -- Y-
|
||||||
|
{x = size.z, y = size.y}, -- X+
|
||||||
|
{x = size.z, y = size.y}, -- X-
|
||||||
|
{x = size.x, y = size.y}, -- Z+
|
||||||
|
{x = size.x, y = size.y}, -- Z-
|
||||||
|
}
|
||||||
|
|
||||||
|
local half = vector.multiply(size, 0.5)
|
||||||
|
local selectionBoxes = {
|
||||||
|
{-half.x, -0.02, -half.z, half.x, 0, half.z}, -- Y+
|
||||||
|
{-half.x, 0, -half.z, half.x, 0.02, half.z}, -- Y-
|
||||||
|
{-0.02, -half.y, -half.z, 0, half.y, half.z}, -- X+
|
||||||
|
{0, -half.y, -half.z, 0.02, half.y, half.z}, -- X-
|
||||||
|
{-half.x, -half.y, -0.02, half.x, half.y, 0}, -- Z+
|
||||||
|
{-half.x, -half.y, 0, half.x, half.y, 0.02}, -- Z-
|
||||||
|
}
|
||||||
|
|
||||||
|
for i = 1, 6 do
|
||||||
|
local entity = minetest.add_entity(sideCenters[i], "meshport:border")
|
||||||
|
entity:set_properties({
|
||||||
|
visual_size = sideSizes[i],
|
||||||
|
selectionbox = selectionBoxes[i],
|
||||||
|
})
|
||||||
|
entity:set_rotation(SIDE_ROTATIONS[i])
|
||||||
|
playerData.borders[i] = entity
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function set_position(playerName, n, pos)
|
||||||
|
if not meshport.player_data[playerName] then
|
||||||
|
meshport.player_data[playerName] = {
|
||||||
|
pos = {},
|
||||||
|
corners = {},
|
||||||
|
borders = {},
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
local data = meshport.player_data[playerName]
|
||||||
|
data.pos[n] = pos
|
||||||
|
|
||||||
|
if data.corners[n] then
|
||||||
|
data.corners[n]:remove()
|
||||||
|
end
|
||||||
|
|
||||||
|
data.corners[n] = minetest.add_entity(pos, "meshport:corner_" .. n)
|
||||||
|
|
||||||
|
for i = 1, 6 do
|
||||||
|
if data.borders[i] then
|
||||||
|
data.borders[i]:remove()
|
||||||
|
data.borders[i] = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if data.pos[1] and data.pos[2] then
|
||||||
|
mark_borders(data)
|
||||||
|
end
|
||||||
|
|
||||||
|
meshport.log(playerName, "info", S("Position @1 set to @2.", n, minetest.pos_to_string(pos)))
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
for n = 1, 2 do
|
||||||
|
minetest.register_chatcommand("mesh" .. n, {
|
||||||
params = "[pos]",
|
params = "[pos]",
|
||||||
description = string.format(
|
description = S(
|
||||||
"Set position %i for meshport. Player's position is used if no other position is specified.", i),
|
"Set position @1 for Meshport. Player's position is used if no other position is specified.", n),
|
||||||
privs = {meshport = true},
|
privs = {meshport = true},
|
||||||
|
|
||||||
func = function(name, param)
|
func = function(playerName, param)
|
||||||
local pos
|
local pos
|
||||||
|
|
||||||
if param == "" then
|
if param == "" then
|
||||||
pos = minetest.get_player_by_name(name):get_pos()
|
pos = minetest.get_player_by_name(playerName):get_pos()
|
||||||
else
|
else
|
||||||
pos = minetest.string_to_pos(param)
|
pos = minetest.string_to_pos(param)
|
||||||
end
|
end
|
||||||
|
|
||||||
if not pos then
|
if not pos then
|
||||||
meshport.log(name, "error", "Not a valid position.")
|
meshport.log(playerName, "error", S("Not a valid position."))
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
pos = vector.round(pos)
|
pos = vector.round(pos)
|
||||||
|
set_position(playerName, n, pos)
|
||||||
if not meshport.player_data[name] then
|
|
||||||
meshport.player_data[name] = {}
|
|
||||||
end
|
|
||||||
|
|
||||||
if i == 1 then
|
|
||||||
meshport.player_data[name].p1 = pos
|
|
||||||
elseif i == 2 then
|
|
||||||
meshport.player_data[name].p2 = pos
|
|
||||||
end
|
|
||||||
|
|
||||||
meshport.log(name, "info", string.format("Position %i set to %s.", i, minetest.pos_to_string(pos)))
|
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
minetest.register_chatcommand("meshport", {
|
|
||||||
params = "[filename]",
|
local function on_wand_click(itemstack, player, pointedThing, n)
|
||||||
description = "Save a mesh of the selected area (filename optional).",
|
if not player or pointedThing.type == "nothing" then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local playerName = player:get_player_name()
|
||||||
|
|
||||||
|
if not minetest.check_player_privs(playerName, "meshport") then
|
||||||
|
meshport.log(playerName, "error", S("You must have the meshport privilege to use this tool."))
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local pos
|
||||||
|
if pointedThing.type == "node" then
|
||||||
|
if player:get_player_control().sneak then
|
||||||
|
pos = pointedThing.above
|
||||||
|
else
|
||||||
|
pos = pointedThing.under
|
||||||
|
end
|
||||||
|
elseif pointedThing.type == "object" then
|
||||||
|
local entity = pointedThing.ref:get_luaentity()
|
||||||
|
if entity.name == "meshport:border" then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
pos = vector.round(pointedThing.ref:get_pos())
|
||||||
|
else
|
||||||
|
return -- In case another pointed_thing.type is added
|
||||||
|
end
|
||||||
|
|
||||||
|
set_position(playerName, n, pos)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
minetest.register_tool("meshport:wand", {
|
||||||
|
description = S("Meshport Area Selector\nLeft-click to set 1st corner, right-click to set 2nd corner."),
|
||||||
|
short_description = S("Meshport Area Selector"),
|
||||||
|
inventory_image = "meshport_wand.png",
|
||||||
|
|
||||||
|
on_use = function(itemstack, placer, pointedThing) -- Left-click
|
||||||
|
on_wand_click(itemstack, placer, pointedThing, 1)
|
||||||
|
end,
|
||||||
|
|
||||||
|
on_place = function(itemstack, placer, pointedThing) -- Right-click
|
||||||
|
on_wand_click(itemstack, placer, pointedThing, 2)
|
||||||
|
return itemstack -- Required by on_place
|
||||||
|
end,
|
||||||
|
|
||||||
|
on_secondary_use = function(itemstack, placer, pointedThing) -- Right-click on non-node
|
||||||
|
on_wand_click(itemstack, placer, pointedThing, 2)
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
|
||||||
|
minetest.register_chatcommand("meshrst", {
|
||||||
|
description = S("Clear the current Meshport area."),
|
||||||
privs = {meshport = true},
|
privs = {meshport = true},
|
||||||
|
|
||||||
func = function(name, filename)
|
func = function(playerName, param)
|
||||||
local playerData = meshport.player_data[name] or {}
|
local data = meshport.player_data[playerName]
|
||||||
|
if data then
|
||||||
|
for n = 1, 2 do
|
||||||
|
data.pos[n] = nil
|
||||||
|
if data.corners[n] then
|
||||||
|
data.corners[n]:remove()
|
||||||
|
data.corners[n] = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
if not playerData.p1 or not playerData.p2 then
|
for i = 1, 6 do
|
||||||
meshport.log(name, "error", "No area selected. Use /mesh1 and /mesh2 to select an area.")
|
if data.borders[i] then
|
||||||
|
data.borders[i]:remove()
|
||||||
|
data.borders[i] = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
meshport.log(playerName, "info", S("Cleared the current area."))
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
|
||||||
|
minetest.register_chatcommand("meshport", {
|
||||||
|
params = "[filename]",
|
||||||
|
description = S("Save a mesh of the selected area (filename optional)."),
|
||||||
|
privs = {meshport = true},
|
||||||
|
|
||||||
|
func = function(playerName, filename)
|
||||||
|
local playerData = meshport.player_data[playerName] or {}
|
||||||
|
|
||||||
|
if not (playerData.pos and playerData.pos[1] and playerData.pos[2]) then
|
||||||
|
meshport.log(playerName, "error",
|
||||||
|
S("No area selected. Use the Meshport Area Selector or /mesh1 and /mesh2 to select an area."))
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
if filename:find("[^%w-_]") then
|
if filename:find("[^%w-_]") then
|
||||||
meshport.log(name, "error", "Invalid name supplied. Please use valid characters ([A-Z][a-z][0-9][-_]).")
|
meshport.log(playerName, "error",
|
||||||
|
S("Invalid name supplied. Please use valid characters: [A-Z][a-z][0-9][-_]"))
|
||||||
return
|
return
|
||||||
elseif filename == "" then
|
elseif filename == "" then
|
||||||
filename = os.date("%Y-%m-%d_%H-%M-%S")
|
filename = os.date("%Y-%m-%d_%H-%M-%S")
|
||||||
end
|
end
|
||||||
|
|
||||||
local mpPath = minetest.get_worldpath() .. DIR_DELIM .. "meshport"
|
local mpPath = minetest.get_worldpath() .. "/" .. "meshport"
|
||||||
local folderName = name .. "_" .. filename
|
local folderName = playerName .. "_" .. filename
|
||||||
|
|
||||||
if table.indexof(minetest.get_dir_list(mpPath, true), folderName) > 0 then
|
if table.indexof(minetest.get_dir_list(mpPath, true), folderName) > 0 then
|
||||||
meshport.log(name, "error",
|
meshport.log(playerName, "error",
|
||||||
string.format("Folder %q already exists. Try using a different name.", folderName))
|
S("Folder \"@1\" already exists. Try using a different name.", folderName))
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local path = mpPath .. DIR_DELIM .. folderName
|
local path = mpPath .. "/" .. folderName
|
||||||
meshport.create_mesh(name, playerData.p1, playerData.p2, path)
|
meshport.create_mesh(playerName, playerData.pos[1], playerData.pos[2], path)
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
|
23
locale/template.txt
Normal file
23
locale/template.txt
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
# textdomain:meshport
|
||||||
|
|
||||||
|
Warning: @1
|
||||||
|
Error: @1
|
||||||
|
Can save meshes with Meshport.
|
||||||
|
Position @1 set to @2.
|
||||||
|
Set position @1 for Meshport. Player's position is used if no other position is specified.
|
||||||
|
Not a valid position.
|
||||||
|
You must have the meshport privilege to use this tool.
|
||||||
|
Meshport Area Selector@\nLeft-click to set 1st corner, right-click to set 2nd corner.
|
||||||
|
Meshport Area Selector
|
||||||
|
Clear the current Meshport area.
|
||||||
|
Cleared the current area.
|
||||||
|
Save a mesh of the selected area (filename optional).
|
||||||
|
No area selected. Use the Meshport Area Selector or /mesh1 and /mesh2 to select an area.
|
||||||
|
Invalid name supplied. Please use valid characters: [A-Z][a-z][0-9][-_]
|
||||||
|
Folder "@1" already exists. Try using a different name.
|
||||||
|
Mesh "@1" is not supported.
|
||||||
|
Mesh "@1" could not be found.
|
||||||
|
Generating mesh...
|
||||||
|
Finished. Saved to @1
|
||||||
|
Ignoring texture modifers in material "@1".
|
||||||
|
Could not find texture "@1". Using a dummy material instead.
|
10
mesh.lua
10
mesh.lua
@ -17,6 +17,8 @@
|
|||||||
along with Meshport. If not, see <https://www.gnu.org/licenses/>.
|
along with Meshport. If not, see <https://www.gnu.org/licenses/>.
|
||||||
]]
|
]]
|
||||||
|
|
||||||
|
local S = meshport.S
|
||||||
|
|
||||||
--[[
|
--[[
|
||||||
A buffer of faces.
|
A buffer of faces.
|
||||||
|
|
||||||
@ -279,7 +281,7 @@ end
|
|||||||
function meshport.Mesh:write_obj(path)
|
function meshport.Mesh:write_obj(path)
|
||||||
local objFile = io.open(path .. "/model.obj", "w")
|
local objFile = io.open(path .. "/model.obj", "w")
|
||||||
|
|
||||||
objFile:write("# Created using meshport (https://github.com/random-geek/meshport).\n")
|
objFile:write("# Created using Meshport (https://github.com/random-geek/meshport).\n")
|
||||||
objFile:write("mtllib materials.mtl\n")
|
objFile:write("mtllib materials.mtl\n")
|
||||||
|
|
||||||
-- Write vertices.
|
-- Write vertices.
|
||||||
@ -312,7 +314,7 @@ end
|
|||||||
function meshport.Mesh:write_mtl(path, playerName)
|
function meshport.Mesh:write_mtl(path, playerName)
|
||||||
local matFile = io.open(path .. "/materials.mtl", "w")
|
local matFile = io.open(path .. "/materials.mtl", "w")
|
||||||
|
|
||||||
matFile:write("# Created using meshport (https://github.com/random-geek/meshport).\n")
|
matFile:write("# Created using Meshport (https://github.com/random-geek/meshport).\n")
|
||||||
|
|
||||||
-- Write material information.
|
-- Write material information.
|
||||||
for mat, _ in pairs(self.faces) do
|
for mat, _ in pairs(self.faces) do
|
||||||
@ -323,13 +325,13 @@ function meshport.Mesh:write_mtl(path, playerName)
|
|||||||
|
|
||||||
if meshport.texture_paths[texName] then
|
if meshport.texture_paths[texName] then
|
||||||
if texName ~= mat then
|
if texName ~= mat then
|
||||||
meshport.log(playerName, "warning", string.format("Ignoring texture modifers in material %q.", mat))
|
meshport.log(playerName, "warning", S("Ignoring texture modifers in material \"@1\".", mat))
|
||||||
end
|
end
|
||||||
|
|
||||||
matFile:write(string.format("map_Kd %s\n", meshport.texture_paths[texName]))
|
matFile:write(string.format("map_Kd %s\n", meshport.texture_paths[texName]))
|
||||||
else
|
else
|
||||||
meshport.log(playerName, "warning",
|
meshport.log(playerName, "warning",
|
||||||
string.format("Could not find texture %q. Using a dummy material instead.", texName))
|
S("Could not find texture \"@1\". Using a dummy material instead.", texName))
|
||||||
matFile:write(string.format("Kd %f %f %f\n", math.random(), math.random(), math.random()))
|
matFile:write(string.format("Kd %f %f %f\n", math.random(), math.random(), math.random()))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
BIN
textures/meshport_border.png
Normal file
BIN
textures/meshport_border.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 125 B |
BIN
textures/meshport_corner_1.png
Normal file
BIN
textures/meshport_corner_1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 139 B |
BIN
textures/meshport_corner_2.png
Normal file
BIN
textures/meshport_corner_2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 147 B |
BIN
textures/meshport_wand.png
Normal file
BIN
textures/meshport_wand.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 200 B |
10
utils.lua
10
utils.lua
@ -17,6 +17,8 @@
|
|||||||
along with Meshport. If not, see <https://www.gnu.org/licenses/>.
|
along with Meshport. If not, see <https://www.gnu.org/licenses/>.
|
||||||
]]
|
]]
|
||||||
|
|
||||||
|
local S = meshport.S
|
||||||
|
|
||||||
meshport.NEIGHBOR_DIRS = {
|
meshport.NEIGHBOR_DIRS = {
|
||||||
-- face neighbors
|
-- face neighbors
|
||||||
vector.new( 0, 1, 0), -- 1
|
vector.new( 0, 1, 0), -- 1
|
||||||
@ -111,9 +113,9 @@ function meshport.log(name, level, s)
|
|||||||
if level == "info" then
|
if level == "info" then
|
||||||
message = minetest.colorize("#00EF00", s)
|
message = minetest.colorize("#00EF00", s)
|
||||||
elseif level == "warning" then
|
elseif level == "warning" then
|
||||||
message = minetest.colorize("#EFEF00", "Warning: " .. s)
|
message = minetest.colorize("#EFEF00", S("Warning: @1", s))
|
||||||
elseif level == "error" then
|
elseif level == "error" then
|
||||||
message = minetest.colorize("#EF0000", "Error: " .. s)
|
message = minetest.colorize("#EF0000", S("Error: @1", s))
|
||||||
end
|
end
|
||||||
|
|
||||||
minetest.chat_send_player(name, "[meshport] " .. message)
|
minetest.chat_send_player(name, "[meshport] " .. message)
|
||||||
@ -378,13 +380,13 @@ function meshport.get_asset_paths(assetFolderName, extension)
|
|||||||
|
|
||||||
-- Iterate through each enabled mod.
|
-- Iterate through each enabled mod.
|
||||||
for _, modName in ipairs(minetest.get_modnames()) do
|
for _, modName in ipairs(minetest.get_modnames()) do
|
||||||
modAssetPath = minetest.get_modpath(modName) .. DIR_DELIM .. assetFolderName
|
modAssetPath = minetest.get_modpath(modName) .. "/" .. assetFolderName
|
||||||
|
|
||||||
-- Iterate through all the files in the requested folder of the mod.
|
-- Iterate through all the files in the requested folder of the mod.
|
||||||
for _, fileName in ipairs(minetest.get_dir_list(modAssetPath, false)) do
|
for _, fileName in ipairs(minetest.get_dir_list(modAssetPath, false)) do
|
||||||
-- Add files to the table. If an extension is specified, only add files with that extension.
|
-- Add files to the table. If an extension is specified, only add files with that extension.
|
||||||
if not extension or string.lower(string.sub(fileName, -string.len(extension))) == extension then
|
if not extension or string.lower(string.sub(fileName, -string.len(extension))) == extension then
|
||||||
assets[fileName] = modAssetPath .. DIR_DELIM .. fileName
|
assets[fileName] = modAssetPath .. "/" .. fileName
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
Loading…
x
Reference in New Issue
Block a user