2018-05-15 15:31:13 -07:00
|
|
|
|
|
|
|
wesh = {
|
|
|
|
name = "wesh",
|
2018-06-08 08:22:56 -07:00
|
|
|
temp_foldername = "wesh_temp_obj_files",
|
2018-06-08 12:24:31 -07:00
|
|
|
default_max_faces = 8000,
|
2018-06-09 04:51:41 -07:00
|
|
|
mod_path = minetest.get_modpath(minetest.get_current_modname()),
|
2018-05-15 15:31:13 -07:00
|
|
|
vt_size = 72,
|
2018-06-07 08:36:39 -07:00
|
|
|
player_canvas = {},
|
|
|
|
forms = {},
|
2018-06-09 04:51:41 -07:00
|
|
|
content_ids = {},
|
2018-05-15 15:31:13 -07:00
|
|
|
}
|
|
|
|
|
2018-06-09 04:51:41 -07:00
|
|
|
wesh.models_path = wesh.mod_path .. "/models/"
|
|
|
|
local storage = dofile(wesh.mod_path .. "/storage.lua")
|
2018-06-18 09:43:12 -07:00
|
|
|
local smartfs = dofile(wesh.mod_path .. "/lib/smartfs.lua")
|
2018-06-08 08:22:56 -07:00
|
|
|
|
2018-06-22 16:07:45 -07:00
|
|
|
-- ========================================================================
|
|
|
|
-- local helpers
|
|
|
|
-- ========================================================================
|
|
|
|
|
|
|
|
local function copy_file(source, dest)
|
|
|
|
local src_file = io.open(source, "rb")
|
|
|
|
if not src_file then
|
|
|
|
return false, "copy_file() unable to open source for reading"
|
|
|
|
end
|
|
|
|
local src_data = src_file:read("*all")
|
|
|
|
src_file:close()
|
|
|
|
|
|
|
|
local dest_file = io.open(dest, "wb")
|
|
|
|
if not dest_file then
|
|
|
|
return false, "copy_file() unable to open dest for writing"
|
|
|
|
end
|
|
|
|
dest_file:write(src_data)
|
|
|
|
dest_file:close()
|
|
|
|
return true, "files copied successfully"
|
|
|
|
end
|
|
|
|
|
|
|
|
local function custom_or_default(modname, path, filename)
|
|
|
|
local default_filename = "default/" .. filename
|
|
|
|
local full_filename = path .. "/custom." .. filename
|
|
|
|
local full_default_filename = path .. "/" .. default_filename
|
|
|
|
|
|
|
|
os.rename(path .. "/" .. filename, full_filename)
|
|
|
|
|
|
|
|
local file = io.open(full_filename, "rb")
|
|
|
|
if not file then
|
|
|
|
minetest.debug("[" .. modname .. "] Copying " .. default_filename .. " to " .. filename .. " (path: " .. path .. ")")
|
|
|
|
local success, err = copy_file(full_default_filename, full_filename)
|
|
|
|
if not success then
|
|
|
|
minetest.debug("[" .. modname .. "] " .. err)
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
file = io.open(full_filename, "rb")
|
|
|
|
if not file then
|
|
|
|
minetest.debug("[" .. modname .. "] Unable to load " .. filename .. " file from path " .. path)
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
end
|
|
|
|
file:close()
|
|
|
|
return full_filename
|
|
|
|
end
|
|
|
|
|
2018-05-15 15:31:13 -07:00
|
|
|
-- ========================================================================
|
|
|
|
-- initialization functions
|
|
|
|
-- ========================================================================
|
|
|
|
|
2018-06-09 16:40:51 -07:00
|
|
|
function wesh.init()
|
2018-06-09 04:51:41 -07:00
|
|
|
wesh.temp_path = minetest.get_worldpath() .. "/mod_storage/" .. wesh.temp_foldername .. "/"
|
2018-05-15 15:31:13 -07:00
|
|
|
wesh.gen_prefix = "mesh_"
|
|
|
|
|
|
|
|
if not minetest.mkdir(wesh.temp_path) then
|
2018-06-07 08:36:39 -07:00
|
|
|
error("[wesh] Unable to create folder " .. wesh.temp_path)
|
2018-05-15 15:31:13 -07:00
|
|
|
end
|
|
|
|
|
2018-06-09 16:40:51 -07:00
|
|
|
wesh.init_colors()
|
|
|
|
wesh.init_geometry()
|
|
|
|
wesh.init_privileges()
|
|
|
|
wesh.init_transforms()
|
|
|
|
wesh.init_variants()
|
|
|
|
wesh.init_vertex_textures()
|
|
|
|
wesh.register_canvas_nodes()
|
2018-05-16 14:48:15 -07:00
|
|
|
|
2018-06-09 16:40:51 -07:00
|
|
|
wesh.delete_marked_objs()
|
|
|
|
wesh.move_temp_files()
|
|
|
|
wesh.load_mod_meshes()
|
2018-05-15 15:31:13 -07:00
|
|
|
end
|
|
|
|
|
2018-06-09 16:40:51 -07:00
|
|
|
function wesh.init_colors()
|
2018-05-15 15:31:13 -07:00
|
|
|
wesh.colors = {
|
|
|
|
"violet",
|
|
|
|
"white",
|
|
|
|
"yellow",
|
|
|
|
"air",
|
|
|
|
"magenta",
|
|
|
|
"orange",
|
|
|
|
"pink",
|
|
|
|
"red",
|
|
|
|
"dark_green",
|
|
|
|
"dark_grey",
|
|
|
|
"green",
|
|
|
|
"grey",
|
|
|
|
"black",
|
|
|
|
"blue",
|
|
|
|
"brown",
|
|
|
|
"cyan",
|
|
|
|
}
|
|
|
|
|
2018-05-17 06:46:11 -07:00
|
|
|
-- The following loop populates the color_vertices table with data like this...
|
|
|
|
--
|
|
|
|
-- wesh.color_vertices = {
|
|
|
|
-- violet = { 1, 2, 3, 4 },
|
|
|
|
-- white = { 5, 6, 7, 8 },
|
|
|
|
--
|
|
|
|
-- ...and so forth, in a boring sequence.
|
|
|
|
--
|
|
|
|
-- Such indices will refer to the vt sequence generated by _init_vertex_textures()
|
|
|
|
-- The same loop will also fill the nodename_to_color table with default fallbacks for wool
|
|
|
|
|
|
|
|
wesh.nodename_to_color = {}
|
2018-05-15 15:31:13 -07:00
|
|
|
wesh.color_vertices = {}
|
|
|
|
for i, color in ipairs(wesh.colors) do
|
|
|
|
local t = {}
|
|
|
|
local j = (i - 1) * 4 + 1
|
|
|
|
for k = j, j + 3 do
|
|
|
|
table.insert(t, k)
|
|
|
|
end
|
|
|
|
wesh.color_vertices[color] = t
|
2018-05-17 06:46:11 -07:00
|
|
|
if color ~= "air" then
|
|
|
|
wesh.nodename_to_color["wool:" .. color] = color
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-06-22 16:07:45 -07:00
|
|
|
local full_colors_filename = custom_or_default(wesh.name, wesh.mod_path, "nodecolors.conf")
|
|
|
|
if not full_colors_filename then return end
|
|
|
|
local file = io.open(full_colors_filename, "rb")
|
|
|
|
if not file then return end
|
|
|
|
|
2018-06-06 11:19:49 -07:00
|
|
|
-- The following loop will fill the nodename_to_color table with custom values
|
2018-05-17 06:46:11 -07:00
|
|
|
local content = file:read("*all")
|
|
|
|
local lines = content:gsub("(\r\n)+", "\r"):gsub("\r+", "\n"):split("\n")
|
|
|
|
for _, line in ipairs(lines) do
|
2018-06-22 16:07:45 -07:00
|
|
|
line = line:gsub("%s+", "")
|
|
|
|
if line:sub(1,1) ~= "#" then
|
|
|
|
local parts = line:split("=")
|
|
|
|
if #parts == 2 then
|
|
|
|
wesh.nodename_to_color[parts[1]] = parts[2]
|
|
|
|
end
|
2018-05-17 06:46:11 -07:00
|
|
|
end
|
|
|
|
end
|
|
|
|
file:close()
|
|
|
|
|
2018-05-15 15:31:13 -07:00
|
|
|
end
|
|
|
|
|
2018-06-09 16:40:51 -07:00
|
|
|
function wesh.init_geometry()
|
2018-05-15 15:31:13 -07:00
|
|
|
|
|
|
|
-- helper table to build the six faces
|
|
|
|
wesh.cube_vertices = {
|
|
|
|
{ x = 1, y = -1, z = -1 }, -- 1
|
|
|
|
{ x = -1, y = -1, z = -1 }, -- 2
|
|
|
|
{ x = -1, y = -1, z = 1 }, -- 3
|
|
|
|
{ x = 1, y = -1, z = 1 }, -- 4
|
|
|
|
{ x = 1, y = 1, z = -1 }, -- 5
|
|
|
|
{ x = 1, y = 1, z = 1 }, -- 6
|
|
|
|
{ x = -1, y = 1, z = 1 }, -- 7
|
|
|
|
{ x = -1, y = 1, z = -1 }, -- 8
|
|
|
|
}
|
|
|
|
|
|
|
|
-- vertices refer to the above cube_vertices table
|
|
|
|
wesh.face_construction = {
|
2018-05-16 06:54:52 -07:00
|
|
|
bottom = { vertices = { 4, 3, 2, 1 }, normal = 1 },
|
|
|
|
top = { vertices = { 8, 7, 6, 5 }, normal = 2 },
|
|
|
|
back = { vertices = { 2, 8, 5, 1 }, normal = 3 },
|
|
|
|
front = { vertices = { 4, 6, 7, 3 }, normal = 4 },
|
|
|
|
left = { vertices = { 6, 4, 1, 5 }, normal = 5 },
|
|
|
|
right = { vertices = { 3, 7, 8, 2 }, normal = 6 },
|
2018-05-16 05:42:03 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
wesh.face_normals = {
|
|
|
|
{x = 0, y = -1, z = 0 },
|
|
|
|
{x = 0, y = 1, z = 0 },
|
|
|
|
{x = 0, y = 0, z = -1 },
|
|
|
|
{x = 0, y = 0, z = 1 },
|
|
|
|
{x = -1, y = 0, z = 0 },
|
|
|
|
{x = 1, y = 0, z = 0 },
|
2018-05-15 15:31:13 -07:00
|
|
|
}
|
2018-06-09 15:15:54 -07:00
|
|
|
end
|
|
|
|
|
2018-06-09 16:40:51 -07:00
|
|
|
function wesh.init_privileges()
|
|
|
|
|
|
|
|
minetest.register_privilege("wesh_capture", {
|
|
|
|
description = "Can use wesh canvases to capture new meshes",
|
|
|
|
give_to_singleplayer = true,
|
|
|
|
})
|
|
|
|
|
|
|
|
minetest.register_privilege("wesh_place", {
|
|
|
|
description = "Can place nodes created from wesh captures",
|
|
|
|
give_to_singleplayer = true,
|
|
|
|
})
|
|
|
|
|
|
|
|
minetest.register_privilege("wesh_delete", {
|
|
|
|
description = "Can delete captured meshes",
|
|
|
|
give_to_singleplayer = true,
|
|
|
|
})
|
|
|
|
|
|
|
|
minetest.register_privilege("wesh_import", {
|
|
|
|
description = "Can import matrix files",
|
|
|
|
give_to_singleplayer = true,
|
|
|
|
})
|
|
|
|
|
2018-06-09 18:14:42 -07:00
|
|
|
minetest.register_privilege("wesh_fill", {
|
|
|
|
description = "Can void the canvas or fill it with arbitrary nodes",
|
2018-06-09 16:40:51 -07:00
|
|
|
give_to_singleplayer = true,
|
|
|
|
})
|
|
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
function wesh.init_transforms()
|
2018-06-09 15:15:54 -07:00
|
|
|
local rot = {}
|
|
|
|
local dir = {}
|
|
|
|
|
|
|
|
-- no rotation
|
2018-06-18 09:43:12 -07:00
|
|
|
rot[0] = matrix{{ 1, 0, 0},
|
|
|
|
{ 0, 1, 0},
|
|
|
|
{ 0, 0, 1}}
|
2018-06-09 15:15:54 -07:00
|
|
|
-- 90 degrees clockwise
|
2018-06-18 09:43:12 -07:00
|
|
|
rot[1] = matrix{{ 0, 0, 1},
|
|
|
|
{ 0, 1, 0},
|
|
|
|
{ -1, 0, 0}}
|
2018-06-09 15:15:54 -07:00
|
|
|
-- 180 degrees
|
2018-06-18 09:43:12 -07:00
|
|
|
rot[2] = matrix{{ -1, 0, 0},
|
|
|
|
{ 0, 1, 0},
|
|
|
|
{ 0, 0, -1}}
|
2018-06-09 15:15:54 -07:00
|
|
|
-- 270 degrees clockwise
|
2018-06-18 09:43:12 -07:00
|
|
|
rot[3] = matrix{{ 0, 0, -1},
|
|
|
|
{ 0, 1, 0},
|
|
|
|
{ 1, 0, 0}}
|
2018-06-09 15:15:54 -07:00
|
|
|
|
|
|
|
-- directions
|
2018-06-09 15:33:36 -07:00
|
|
|
-- Y+
|
2018-06-18 09:43:12 -07:00
|
|
|
dir[0] = matrix{{ 1, 0, 0},
|
|
|
|
{ 0, 1, 0},
|
|
|
|
{ 0, 0, 1}}
|
2018-06-09 15:33:36 -07:00
|
|
|
-- Z+
|
2018-06-18 09:43:12 -07:00
|
|
|
dir[1] = matrix{{ 1, 0, 0},
|
|
|
|
{ 0, 0, -1},
|
|
|
|
{ 0, 1, 0}}
|
2018-06-09 15:15:54 -07:00
|
|
|
-- Z-
|
2018-06-18 09:43:12 -07:00
|
|
|
dir[2] = matrix{{ 1, 0, 0},
|
|
|
|
{ 0, 0, 1},
|
|
|
|
{ 0, -1, 0}}
|
2018-06-09 15:15:54 -07:00
|
|
|
-- X+
|
2018-06-18 09:43:12 -07:00
|
|
|
dir[3] = matrix{{ 0, 1, 0},
|
|
|
|
{ -1, 0, 0},
|
|
|
|
{ 0, 0, 1}}
|
2018-06-09 15:15:54 -07:00
|
|
|
-- X-
|
2018-06-18 09:43:12 -07:00
|
|
|
dir[4] = matrix{{ 0, -1, 0},
|
|
|
|
{ 1, 0, 0},
|
|
|
|
{ 0, 0, 1}}
|
2018-06-09 15:15:54 -07:00
|
|
|
-- Y-
|
2018-06-18 09:43:12 -07:00
|
|
|
dir[5] = matrix{{ -1, 0, 0},
|
|
|
|
{ 0, -1, 0},
|
|
|
|
{ 0, 0, 1}}
|
|
|
|
|
|
|
|
wesh._facedir_transform = {}
|
|
|
|
wesh._matrix_to_facedir = {}
|
2018-06-09 15:15:54 -07:00
|
|
|
|
|
|
|
for facedir = 0, 23 do
|
|
|
|
local direction = math.floor(facedir / 4)
|
|
|
|
local rotation = facedir % 4
|
2018-06-18 09:43:12 -07:00
|
|
|
local transform = dir[direction] * rot[rotation]
|
|
|
|
wesh._facedir_transform[facedir] = transform
|
|
|
|
wesh._matrix_to_facedir[transform:tostring():gsub("%-0", "0")] = facedir
|
2018-06-09 15:15:54 -07:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-06-18 09:43:12 -07:00
|
|
|
function wesh.get_facedir_transform(facedir)
|
|
|
|
return wesh._facedir_transform[facedir] or wesh._facedir_transform[0]
|
|
|
|
end
|
|
|
|
|
|
|
|
function wesh.matrix_to_facedir(mtx)
|
|
|
|
local key = mtx:tostring():gsub("%-0", "0")
|
|
|
|
if not wesh._matrix_to_facedir[key] then
|
|
|
|
error("Unsupported matrix:\n" .. key)
|
|
|
|
end
|
|
|
|
return wesh._matrix_to_facedir[key]
|
|
|
|
end
|
|
|
|
|
|
|
|
function wesh.transform_facedir(canvas_facedir, node_facedir, invert_canvas)
|
|
|
|
if not wesh.transform_cache then
|
|
|
|
wesh.transform_cache = {}
|
|
|
|
end
|
|
|
|
|
|
|
|
local cache_key = canvas_facedir .. "," .. node_facedir .. "," .. (invert_canvas and 1 or 0)
|
|
|
|
print(cache_key)
|
|
|
|
if not wesh.transform_cache[cache_key] then
|
|
|
|
local canvas_transform = wesh.get_facedir_transform(canvas_facedir)
|
|
|
|
local node_transform = wesh.get_facedir_transform(node_facedir)
|
|
|
|
if invert_canvas then
|
|
|
|
canvas_transform = canvas_transform:invert()
|
|
|
|
end
|
|
|
|
local transform = canvas_transform * node_transform
|
|
|
|
wesh.transform_cache[cache_key] = wesh.matrix_to_facedir(transform)
|
|
|
|
end
|
|
|
|
|
|
|
|
print(cache_key .. " == " .. wesh.transform_cache[cache_key])
|
|
|
|
|
|
|
|
return wesh.transform_cache[cache_key]
|
|
|
|
end
|
|
|
|
|
2018-06-22 16:07:45 -07:00
|
|
|
function wesh.init_variants()
|
2018-06-09 16:40:51 -07:00
|
|
|
wesh.variants = {
|
|
|
|
plain = "plain-16.png",
|
2018-05-15 15:31:13 -07:00
|
|
|
}
|
2018-06-22 16:07:45 -07:00
|
|
|
local full_variants_filename = custom_or_default(wesh.name, wesh.mod_path, "nodevariants.lua")
|
|
|
|
if not full_variants_filename then return end
|
|
|
|
|
|
|
|
local file = io.open(full_variants_filename, "rb")
|
|
|
|
if not file then return end
|
|
|
|
|
|
|
|
local custom_variants = minetest.deserialize(file:read("*all"))
|
|
|
|
file:close()
|
2018-06-09 16:40:51 -07:00
|
|
|
|
|
|
|
-- ensure there is at least one valid variant in the custom variants
|
|
|
|
if custom_variants and type(custom_variants) == "table" then
|
|
|
|
for name, texture in pairs(custom_variants) do
|
|
|
|
if name and type(name) == "string" and texture and type(texture) == "string" then
|
|
|
|
wesh.variants = custom_variants
|
|
|
|
break
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2018-05-15 15:31:13 -07:00
|
|
|
end
|
|
|
|
|
2018-06-09 16:40:51 -07:00
|
|
|
function wesh.init_vertex_textures()
|
|
|
|
-- creates a 4x4 grid of UV mappings, each with a margin of one pixel
|
|
|
|
-- will be used by the .OBJ file generator
|
|
|
|
local vt = {}
|
|
|
|
local space = wesh.vt_size / 4
|
|
|
|
local tile = space - 2
|
|
|
|
local offset = tile / 2
|
|
|
|
local start = offset + 1
|
|
|
|
local stop = start + 3 * space
|
|
|
|
local mult = 1 / wesh.vt_size
|
|
|
|
for y = start, stop, space do
|
|
|
|
for x = start, stop, space do
|
|
|
|
table.insert(vt, "vt " .. ((x + offset) * mult) .. " " .. ((y + offset) * mult) .. "\n") -- top right
|
|
|
|
table.insert(vt, "vt " .. ((x + offset) * mult) .. " " .. ((y - offset) * mult) .. "\n") -- bottom right
|
|
|
|
table.insert(vt, "vt " .. ((x - offset) * mult) .. " " .. ((y - offset) * mult) .. "\n") -- bottom left
|
|
|
|
table.insert(vt, "vt " .. ((x - offset) * mult) .. " " .. ((y + offset) * mult) .. "\n") -- top left
|
2018-06-09 15:15:54 -07:00
|
|
|
end
|
|
|
|
end
|
2018-06-09 16:40:51 -07:00
|
|
|
wesh.vertex_textures = table.concat(vt)
|
2018-06-09 15:15:54 -07:00
|
|
|
end
|
|
|
|
|
2018-06-09 16:40:51 -07:00
|
|
|
function wesh.register_canvas_nodes()
|
|
|
|
|
2018-06-22 16:07:45 -07:00
|
|
|
local function register_canvas(index, size, recipe)
|
2018-06-09 16:40:51 -07:00
|
|
|
minetest.register_craft({
|
|
|
|
output = "wesh:canvas" .. size,
|
2018-06-22 16:07:45 -07:00
|
|
|
recipe = recipe,
|
2018-06-09 16:40:51 -07:00
|
|
|
})
|
|
|
|
minetest.register_node("wesh:canvas" .. size, {
|
|
|
|
drawtype = "mesh",
|
|
|
|
mesh = "zzz_canvas" .. size .. ".obj",
|
|
|
|
inventory_image = "canvas_inventory.png^[verticalframe:6:" .. (index-1) .. ".png",
|
|
|
|
tiles = { "canvas.png" },
|
|
|
|
paramtype2 = "facedir",
|
|
|
|
on_rightclick = wesh.canvas_interaction,
|
|
|
|
description = "Woolen Mesh Canvas - Size " .. size,
|
|
|
|
walkable = true,
|
|
|
|
groups = { snappy = 2, choppy = 2, oddly_breakable_by_hand = 3 },
|
|
|
|
})
|
|
|
|
end
|
|
|
|
|
|
|
|
wesh.valid_canvas_sizes = {}
|
|
|
|
|
2018-06-22 16:07:45 -07:00
|
|
|
local full_recipes_filename = custom_or_default(wesh.name, wesh.mod_path, "recipes.lua")
|
|
|
|
if not full_recipes_filename then return end
|
|
|
|
|
|
|
|
local recipes = dofile(full_recipes_filename);
|
|
|
|
|
|
|
|
if type(recipes) ~= "table" then
|
|
|
|
minetest.debug("[" .. wesh.name .. "] custom.recipes.lua is empty or malformed, all relative crafts will be disabled")
|
|
|
|
return
|
2018-06-09 16:40:51 -07:00
|
|
|
end
|
2018-06-18 09:43:12 -07:00
|
|
|
|
2018-06-22 16:07:45 -07:00
|
|
|
local canvas_sizes = { "02", "04", "08", "16", "32", "64" }
|
2018-06-18 09:43:12 -07:00
|
|
|
|
2018-06-22 16:07:45 -07:00
|
|
|
for index, size in pairs(canvas_sizes) do
|
|
|
|
local recipe = recipes["wesh:canvas" .. size]
|
|
|
|
if recipe then
|
|
|
|
wesh.valid_canvas_sizes[tonumber(size)] = true
|
|
|
|
register_canvas(index, size, recipe)
|
|
|
|
end
|
|
|
|
end
|
2018-06-18 09:43:12 -07:00
|
|
|
|
2018-06-22 16:07:45 -07:00
|
|
|
if wesh.valid_canvas_sizes["16"] then
|
|
|
|
minetest.register_alias("wesh:canvas", "wesh:canvas16")
|
|
|
|
end
|
|
|
|
|
|
|
|
if recipes["wesh:faces"] then
|
|
|
|
minetest.register_craft({
|
|
|
|
output = "wesh:faces",
|
|
|
|
recipe = recipes["wesh:faces"],
|
|
|
|
})
|
|
|
|
|
|
|
|
minetest.register_node("wesh:faces", {
|
|
|
|
drawtype = "mesh",
|
|
|
|
mesh = "zzz_faces.obj",
|
|
|
|
tiles = { "faces-96x64.png" },
|
|
|
|
paramtype2 = "facedir",
|
|
|
|
description = "Woolen Mesh Orientation Block",
|
|
|
|
walkable = true,
|
|
|
|
groups = { snappy = 2, choppy = 2, oddly_breakable_by_hand = 3 },
|
|
|
|
})
|
|
|
|
end
|
2018-06-08 13:22:34 -07:00
|
|
|
end
|
|
|
|
|
2018-06-17 11:10:14 -07:00
|
|
|
function wesh.reset_geometry(canvas)
|
|
|
|
canvas.matrix = {}
|
2018-06-18 09:43:12 -07:00
|
|
|
canvas.node_matrix = {}
|
2018-06-17 11:10:14 -07:00
|
|
|
canvas.vertices = {}
|
|
|
|
canvas.vertices_indices = {}
|
|
|
|
canvas.faces = {}
|
2018-06-18 09:43:12 -07:00
|
|
|
canvas.voxel_count = 0
|
|
|
|
canvas.nodename_count = 0
|
|
|
|
|
2018-05-16 14:48:15 -07:00
|
|
|
local function reset(p)
|
2018-06-18 09:43:12 -07:00
|
|
|
if not canvas.matrix[p.x] then
|
|
|
|
canvas.matrix[p.x] = {}
|
|
|
|
canvas.node_matrix[p.x] = {}
|
|
|
|
end
|
|
|
|
|
|
|
|
if not canvas.matrix[p.x][p.y] then
|
|
|
|
canvas.matrix[p.x][p.y] = {}
|
|
|
|
canvas.node_matrix[p.x][p.y] = {}
|
|
|
|
end
|
|
|
|
|
2018-06-17 11:10:14 -07:00
|
|
|
canvas.matrix[p.x][p.y][p.z] = "air"
|
2018-06-18 09:43:12 -07:00
|
|
|
canvas.node_matrix[p.x][p.y][p.z] = {
|
|
|
|
name = "air",
|
|
|
|
param2 = 0,
|
|
|
|
}
|
2018-05-16 14:48:15 -07:00
|
|
|
end
|
2018-06-18 09:43:12 -07:00
|
|
|
|
2018-06-17 11:10:14 -07:00
|
|
|
wesh.traverse_matrix(reset, canvas.size)
|
2018-05-15 15:31:13 -07:00
|
|
|
end
|
|
|
|
|
2018-06-09 16:40:51 -07:00
|
|
|
-- ========================================================================
|
|
|
|
-- forms handling
|
|
|
|
-- ========================================================================
|
|
|
|
|
|
|
|
wesh.forms.capture = smartfs.create("wesh.forms.capture", function(state)
|
|
|
|
state:size(7, 7)
|
2018-06-06 11:19:49 -07:00
|
|
|
|
2018-06-09 16:40:51 -07:00
|
|
|
local meshname_field = state:field(0.5, 0.5, 5, 1, "meshname", "Enter the name for your mesh")
|
|
|
|
local capture_button = state:button(5, 0.2, 2, 1, "capture", "Capture")
|
|
|
|
|
|
|
|
state:checkbox(0.5, 1, "generate_matrix", "Generate backup matrix"):setValue(true)
|
|
|
|
|
|
|
|
state:label(0.5, 2, "label_variants", "Select one or more variants:")
|
|
|
|
local variants_x = 0.5
|
|
|
|
local variants_y = 2.5
|
|
|
|
|
|
|
|
local delete_button = state:button(5, 2.2, 2, 0, "delete", "Manage\nMeshes")
|
|
|
|
local give_button = state:button(5, 3.2, 2, 0, "give", "Giveme\nMeshes")
|
|
|
|
local import_button = state:button(5, 4.2, 2, 0, "import", "Import\nMatrix")
|
2018-06-09 18:14:42 -07:00
|
|
|
local vacuum_button = state:button(5, 5.2, 2, 0, "vacuum", "Fill/Erase\nCanvas")
|
2018-06-09 16:40:51 -07:00
|
|
|
|
|
|
|
local max_faces = state:field(0.5, 6.5, 4, 1, "max_faces", "Max # faces, zero disables limit")
|
|
|
|
local cancel_button = state:button(5, 6.2, 2, 1, "cancel", "Cancel")
|
|
|
|
|
|
|
|
meshname_field:onKeyEnter(wesh.mesh_capture_confirmed)
|
|
|
|
meshname_field:setCloseOnEnter(false)
|
|
|
|
capture_button:onClick(wesh.mesh_capture_confirmed)
|
|
|
|
|
|
|
|
delete_button:setClose(true)
|
|
|
|
give_button:setClose(true)
|
|
|
|
import_button:setClose(true)
|
|
|
|
vacuum_button:setClose(true)
|
|
|
|
cancel_button:setClose(true)
|
|
|
|
|
|
|
|
max_faces:setText(wesh.default_max_faces)
|
|
|
|
max_faces:setCloseOnEnter(false)
|
|
|
|
|
|
|
|
delete_button:onClick(function(_, state)
|
|
|
|
if not minetest.get_player_privs(state.player).wesh_delete then
|
|
|
|
wesh.notify(state.player, "Insufficient privileges to manage meshes")
|
2018-06-06 07:56:23 -07:00
|
|
|
return
|
|
|
|
end
|
2018-06-09 16:40:51 -07:00
|
|
|
minetest.after(0, function()
|
|
|
|
wesh.forms.delete_meshes:show(state.player)
|
|
|
|
end)
|
|
|
|
end)
|
|
|
|
|
|
|
|
give_button:onClick(function(_, state)
|
|
|
|
minetest.after(0, function()
|
|
|
|
wesh.forms.giveme_meshes:show(state.player)
|
|
|
|
end)
|
|
|
|
end)
|
|
|
|
|
|
|
|
import_button:onClick(function(_, state)
|
|
|
|
if not minetest.get_player_privs(state.player).wesh_import then
|
|
|
|
wesh.notify(state.player, "Insufficient privileges to import matrices")
|
|
|
|
return
|
|
|
|
end
|
|
|
|
minetest.after(0, function()
|
|
|
|
wesh.forms.import_matrix:show(state.player)
|
|
|
|
end)
|
|
|
|
end)
|
|
|
|
|
|
|
|
vacuum_button:onClick(function(_, state)
|
2018-06-09 18:14:42 -07:00
|
|
|
if not minetest.get_player_privs(state.player).wesh_fill then
|
|
|
|
wesh.notify(state.player, "Insufficient privileges to fill canvas")
|
2018-06-09 16:40:51 -07:00
|
|
|
return
|
|
|
|
end
|
|
|
|
minetest.after(0, function()
|
2018-06-09 18:14:42 -07:00
|
|
|
wesh.forms.fill_canvas:show(state.player)
|
2018-06-09 16:40:51 -07:00
|
|
|
end)
|
|
|
|
end)
|
|
|
|
|
|
|
|
local first_variant = nil
|
|
|
|
local one_checked = false
|
|
|
|
|
|
|
|
local variant_names = {}
|
|
|
|
|
|
|
|
for name, _ in pairs(wesh.variants) do table.insert(variant_names, name) end
|
|
|
|
|
|
|
|
table.sort(variant_names)
|
|
|
|
|
|
|
|
for _, name in ipairs(variant_names) do
|
|
|
|
local chk = state:checkbox(variants_x, variants_y, "variant_" .. name, name)
|
|
|
|
if name == 'plain' then
|
|
|
|
one_checked = true
|
|
|
|
chk:setValue(true)
|
|
|
|
end
|
|
|
|
variants_y = variants_y + 0.5
|
|
|
|
if not first_variant then
|
|
|
|
first_variant = chk
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
if not one_checked then
|
|
|
|
first_variant:setValue(true)
|
|
|
|
end
|
|
|
|
end)
|
|
|
|
|
|
|
|
wesh.forms.delete_meshes = smartfs.create("wesh.forms.delete_meshes", function(state)
|
|
|
|
state:size(8, 8)
|
|
|
|
|
|
|
|
local all_obj_files_list = state:listbox(0.5, 0.5, 7, 5.5, "all_obj_files_list")
|
|
|
|
local label = state:label(0.5, 6.3, "label", "No OBJ selected")
|
|
|
|
local action_button = state:button(0.5, 7.2, 5, 1, "action_button", "[disabled]")
|
|
|
|
local done_button = state:button(6, 7.2, 2, 1, "done", "Done")
|
|
|
|
done_button:setClose(true)
|
|
|
|
|
|
|
|
local obj_files = nil
|
|
|
|
|
|
|
|
local function update_button(obj)
|
|
|
|
if not obj or not obj.type then
|
|
|
|
action_button:setText("[disabled]")
|
|
|
|
elseif obj.type == "stored" then
|
|
|
|
action_button:setText("Mark for deletion")
|
|
|
|
elseif obj.type == "pending deletion" then
|
|
|
|
action_button:setText("Cancel pending deletion")
|
|
|
|
elseif obj.type == "temporary" then
|
|
|
|
action_button:setText("Delete selected temporary NOW!")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
local function fill_list()
|
|
|
|
obj_files = wesh.get_all_obj_files()
|
|
|
|
all_obj_files_list:clearItems()
|
|
|
|
for index, obj in ipairs(obj_files) do
|
|
|
|
local item = obj.filename .. (obj.type ~= "" and (" (" .. obj.type .. ")") or "")
|
|
|
|
all_obj_files_list:addItem(item)
|
|
|
|
end
|
|
|
|
all_obj_files_list:setSelected("")
|
|
|
|
label:setText("No OBJ selected")
|
|
|
|
end
|
|
|
|
|
|
|
|
all_obj_files_list:onClick(function(self, state)
|
|
|
|
local index = self:getSelected()
|
|
|
|
if index then
|
|
|
|
label:setText("Selected:\n" .. self:getItem(index))
|
|
|
|
else
|
|
|
|
label:setText("No OBJ selected")
|
|
|
|
end
|
|
|
|
update_button(obj_files[index])
|
|
|
|
end)
|
|
|
|
|
|
|
|
action_button:onClick(function(_, state)
|
|
|
|
local index = all_obj_files_list:getSelected()
|
|
|
|
local obj = obj_files[index]
|
|
|
|
if not obj or not obj.type then
|
2018-06-06 07:56:23 -07:00
|
|
|
return
|
2018-06-09 16:40:51 -07:00
|
|
|
elseif obj.type == "stored" then
|
|
|
|
wesh.mark_obj_for_deletion(obj.filename)
|
|
|
|
elseif obj.type == "pending deletion" then
|
|
|
|
wesh.unmark_obj_for_deletion(obj.filename)
|
|
|
|
elseif obj.type == "temporary" then
|
|
|
|
wesh.delete_temp_obj(obj.filename)
|
|
|
|
end
|
|
|
|
fill_list()
|
|
|
|
update_button()
|
|
|
|
end)
|
|
|
|
|
|
|
|
fill_list()
|
|
|
|
|
|
|
|
end)
|
|
|
|
|
|
|
|
wesh.forms.giveme_meshes = smartfs.create("wesh.forms.giveme_meshes", function(state)
|
|
|
|
state:size(8, 8)
|
|
|
|
|
|
|
|
local stored_obj_files = wesh.filter_non_obj(wesh.get_stored_files())
|
|
|
|
|
|
|
|
local stored_variants = state:listbox(0.5, 0.5, 7, 6.5, "stored_variants")
|
|
|
|
for _, obj_filename in pairs(stored_obj_files) do
|
|
|
|
local data = wesh.get_obj_filedata(obj_filename)
|
|
|
|
if not data.variants then break end
|
|
|
|
for variant, _ in pairs(data.variants) do
|
|
|
|
stored_variants:addItem(wesh.create_nodename(obj_filename, variant))
|
2018-06-06 07:56:23 -07:00
|
|
|
end
|
|
|
|
end
|
2018-06-07 13:06:39 -07:00
|
|
|
|
2018-06-09 16:40:51 -07:00
|
|
|
local give_button = state:button(0.5, 7.2, 3, 1, "give", "Giveme selected")
|
|
|
|
|
|
|
|
local done_button = state:button(4, 7.2, 2, 1, "done", "Done")
|
|
|
|
done_button:setClose(true)
|
|
|
|
|
|
|
|
local function give_mesh_callback(_, state)
|
|
|
|
local nodename = state:get("stored_variants"):getSelectedItem()
|
|
|
|
if not nodename then return end
|
|
|
|
local player_inv = minetest.get_player_by_name(state.player):get_inventory()
|
|
|
|
player_inv:add_item("main", {name = nodename, count = 1})
|
|
|
|
wesh.notify(state.player, nodename .. " added to inventory")
|
|
|
|
end
|
|
|
|
stored_variants:onDoubleClick(give_mesh_callback)
|
|
|
|
give_button:onClick(give_mesh_callback)
|
|
|
|
|
|
|
|
end)
|
|
|
|
|
|
|
|
wesh.forms.import_matrix = smartfs.create("wesh.forms.import_matrix", function(state)
|
2018-06-18 09:43:12 -07:00
|
|
|
state:size(8, 9)
|
|
|
|
|
|
|
|
local canvas = wesh.player_canvas[state.player]
|
2018-06-09 16:40:51 -07:00
|
|
|
|
2018-06-18 09:43:12 -07:00
|
|
|
local stored_matrices = wesh.filter_non_matrix(wesh.get_stored_files(), canvas)
|
|
|
|
local temp_matrices = wesh.filter_non_matrix(wesh.get_temp_files(), canvas)
|
2018-06-09 16:40:51 -07:00
|
|
|
|
2018-06-18 09:43:12 -07:00
|
|
|
local matrices_list = state:listbox(0.5, 0.5, 7, 4.5, "matrices_list")
|
2018-06-09 16:40:51 -07:00
|
|
|
|
|
|
|
for _, matrix_filename in pairs(stored_matrices) do
|
|
|
|
matrices_list:addItem(matrix_filename)
|
|
|
|
end
|
|
|
|
|
|
|
|
for _, matrix_filename in pairs(temp_matrices) do
|
|
|
|
matrices_list:addItem(matrix_filename)
|
|
|
|
end
|
|
|
|
|
2018-06-18 09:43:12 -07:00
|
|
|
local original_check = state:checkbox(0.5, 5.2, "original_check", "Import original nodes (if available)")
|
|
|
|
|
2018-06-09 19:17:58 -07:00
|
|
|
local negative_check = state:checkbox(0.5, 6.2, "negative_check", "Invert")
|
2018-06-18 09:43:12 -07:00
|
|
|
|
2018-06-09 19:17:58 -07:00
|
|
|
local mononode_check = state:checkbox(2.3, 6.2, "mononode_check", "Mononode")
|
2018-06-18 09:43:12 -07:00
|
|
|
|
|
|
|
original_check:onToggle(function()
|
|
|
|
negative_check:setValue(false)
|
|
|
|
mononode_check:setValue(false)
|
|
|
|
end)
|
|
|
|
|
|
|
|
negative_check:onToggle(function()
|
|
|
|
original_check:setValue(false)
|
|
|
|
mononode_check:setValue(false)
|
|
|
|
end)
|
|
|
|
|
|
|
|
mononode_check:onToggle(function()
|
|
|
|
negative_check:setValue(false)
|
|
|
|
original_check:setValue(false)
|
|
|
|
end)
|
|
|
|
|
2018-06-09 19:17:58 -07:00
|
|
|
local nodename_field = state:field(4.5, 6.5, 3.5, 1, "nodename", "'modname:nodename' or 'air'")
|
2018-06-18 09:43:12 -07:00
|
|
|
state:label(4.3, 7, "label_nodename", "('Invert' or 'Mononode' only)")
|
2018-06-09 19:17:58 -07:00
|
|
|
nodename_field:setCloseOnEnter(false)
|
|
|
|
nodename_field:setText("air")
|
|
|
|
|
2018-06-18 09:43:12 -07:00
|
|
|
local import_button = state:button(0.5, 8.2, 3, 1, "import", "Import selected")
|
2018-06-09 16:40:51 -07:00
|
|
|
import_button:onClick(function()
|
|
|
|
local full_matrix_filename = false
|
|
|
|
local selected_matrix_filename = matrices_list:getSelectedItem()
|
|
|
|
for _, matrix_filename in pairs(stored_matrices) do
|
|
|
|
if matrix_filename == selected_matrix_filename then
|
|
|
|
full_matrix_filename = wesh.models_path .. matrix_filename
|
2018-06-07 13:06:39 -07:00
|
|
|
break
|
|
|
|
end
|
|
|
|
end
|
2018-06-09 16:40:51 -07:00
|
|
|
|
|
|
|
for _, matrix_filename in pairs(temp_matrices) do
|
|
|
|
if matrix_filename == selected_matrix_filename then
|
|
|
|
full_matrix_filename = wesh.temp_path .. matrix_filename
|
|
|
|
break
|
|
|
|
end
|
|
|
|
end
|
2018-06-09 19:17:58 -07:00
|
|
|
local negative = negative_check:getValue()
|
|
|
|
local mononode = mononode_check:getValue()
|
2018-06-18 09:43:12 -07:00
|
|
|
local original = original_check:getValue()
|
2018-06-09 19:17:58 -07:00
|
|
|
local nodename = nodename_field:getText()
|
2018-06-18 09:43:12 -07:00
|
|
|
if wesh.import_matrix(full_matrix_filename, state.player, original, negative, mononode, nodename) then
|
2018-06-09 19:17:58 -07:00
|
|
|
minetest.after(0, function()
|
|
|
|
minetest.close_formspec(state.player, "wesh.forms.import_matrix")
|
|
|
|
end)
|
|
|
|
end
|
2018-06-09 16:40:51 -07:00
|
|
|
end)
|
|
|
|
|
2018-06-18 09:43:12 -07:00
|
|
|
local close_button = state:button(5.5, 8.2, 2, 1, "close", "Close")
|
2018-06-09 19:17:58 -07:00
|
|
|
close_button:setClose(true)
|
2018-06-09 16:40:51 -07:00
|
|
|
end)
|
|
|
|
|
2018-06-09 18:14:42 -07:00
|
|
|
wesh.forms.fill_canvas = smartfs.create("wesh.forms.fill_canvas", function(state)
|
|
|
|
state:size(6, 4)
|
2018-06-09 16:40:51 -07:00
|
|
|
|
2018-06-09 19:17:58 -07:00
|
|
|
local nodename_field = state:field(0.5, 0.5, 5, 1, "nodename", "'modname:nodename' or 'air'")
|
2018-06-09 18:14:42 -07:00
|
|
|
nodename_field:setCloseOnEnter(false)
|
2018-06-09 19:17:58 -07:00
|
|
|
nodename_field:setText("air")
|
2018-06-09 18:14:42 -07:00
|
|
|
|
|
|
|
local confirm_vacuum = state:button(0.5, 2, 4, 1, "confirm_vacuum", "Fill canvas")
|
2018-06-09 16:40:51 -07:00
|
|
|
confirm_vacuum:onClick(function()
|
2018-06-09 18:14:42 -07:00
|
|
|
local nodename = nodename_field:getText()
|
|
|
|
if wesh.fill_canvas(state.player, nodename) then
|
|
|
|
wesh.notify(state.player, "Canvas filled with " .. nodename)
|
|
|
|
end
|
2018-06-09 16:40:51 -07:00
|
|
|
end)
|
|
|
|
confirm_vacuum:setClose(true)
|
|
|
|
|
2018-06-09 18:14:42 -07:00
|
|
|
local cancel_button = state:button(0.5, 3, 4, 1, "cancel", "Cancel")
|
2018-06-09 16:40:51 -07:00
|
|
|
cancel_button:setClose(true)
|
|
|
|
end)
|
2018-06-06 07:56:23 -07:00
|
|
|
|
2018-05-15 15:31:13 -07:00
|
|
|
-- ========================================================================
|
|
|
|
-- core functions
|
|
|
|
-- ========================================================================
|
|
|
|
|
|
|
|
function wesh.canvas_interaction(clicked_pos, node, clicker)
|
2018-06-05 03:23:17 -07:00
|
|
|
-- called when the player right-clicks on a canvas block
|
2018-06-09 04:51:41 -07:00
|
|
|
local canvas = {
|
|
|
|
pos = clicked_pos,
|
|
|
|
facedir = node.param2,
|
|
|
|
node = node,
|
|
|
|
}
|
|
|
|
|
|
|
|
canvas.size = canvas.node.name:gsub(".*(%d%d)$", "%1")
|
|
|
|
canvas.size = tonumber(canvas.size)
|
|
|
|
if not wesh.valid_canvas_sizes[canvas.size] then
|
|
|
|
canvas.size = 16
|
|
|
|
end
|
|
|
|
|
2018-06-09 08:00:02 -07:00
|
|
|
local playername = clicker:get_player_name()
|
|
|
|
wesh.player_canvas[playername] = canvas
|
|
|
|
wesh.forms.capture:show(playername)
|
2018-05-15 15:31:13 -07:00
|
|
|
end
|
|
|
|
|
2018-06-07 08:36:39 -07:00
|
|
|
function wesh.mesh_capture_confirmed(button_or_field, state)
|
|
|
|
local meshname = state:get("meshname"):getText()
|
|
|
|
local playername = state.player
|
2018-06-07 14:45:50 -07:00
|
|
|
|
|
|
|
if not minetest.get_player_privs(playername).wesh_capture then
|
|
|
|
wesh.notify(playername, "Insufficient privileges to capture new meshes")
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
2018-06-07 08:36:39 -07:00
|
|
|
local canvas = wesh.player_canvas[playername]
|
|
|
|
canvas.generate_matrix = state:get("generate_matrix"):getValue()
|
2018-06-08 12:24:31 -07:00
|
|
|
canvas.max_faces = tonumber(state:get("max_faces"):getText()) or wesh.default_max_faces
|
2018-06-07 08:36:39 -07:00
|
|
|
|
|
|
|
canvas.chosen_variants = {}
|
|
|
|
|
|
|
|
local no_variants = true
|
|
|
|
for name, texture in pairs(wesh.variants) do
|
|
|
|
if state:get("variant_" .. name):getValue() then
|
|
|
|
canvas.chosen_variants[name] = texture
|
|
|
|
no_variants = false
|
2018-05-16 14:48:15 -07:00
|
|
|
end
|
2018-06-07 08:36:39 -07:00
|
|
|
end
|
|
|
|
|
|
|
|
if no_variants then
|
|
|
|
wesh.notify(playername, "Please choose at least one variant")
|
|
|
|
return
|
|
|
|
end
|
2018-05-16 14:48:15 -07:00
|
|
|
|
2018-06-07 08:36:39 -07:00
|
|
|
canvas.boundary = {}
|
|
|
|
if wesh.save_new_mesh(canvas, playername, meshname) then
|
|
|
|
minetest.close_formspec(playername, "wesh.forms.capture")
|
2018-05-15 15:31:13 -07:00
|
|
|
end
|
2018-06-18 09:43:12 -07:00
|
|
|
|
|
|
|
wesh.player_canvas[playername] = {
|
|
|
|
pos = canvas.pos,
|
|
|
|
facedir = canvas.facedir,
|
|
|
|
node = canvas.node,
|
|
|
|
size = canvas.size,
|
|
|
|
}
|
2018-05-15 15:31:13 -07:00
|
|
|
end
|
|
|
|
|
2018-06-07 08:36:39 -07:00
|
|
|
function wesh.save_new_mesh(canvas, playername, description)
|
2018-06-06 13:41:56 -07:00
|
|
|
local sanitized_meshname = wesh.check_plain(description)
|
|
|
|
if sanitized_meshname:len() < 3 then
|
2018-06-07 08:36:39 -07:00
|
|
|
wesh.notify(playername, "Mesh name too short, try again (min. 3 chars)")
|
|
|
|
return false
|
2018-06-06 13:41:56 -07:00
|
|
|
end
|
|
|
|
|
|
|
|
local obj_filename = wesh.gen_prefix .. sanitized_meshname .. ".obj"
|
|
|
|
for _, entry in ipairs(wesh.get_all_files()) do
|
|
|
|
if entry == obj_filename then
|
2018-06-07 08:36:39 -07:00
|
|
|
wesh.notify(playername, "Mesh name '" .. description .. "' already taken, pick a new one")
|
|
|
|
return false
|
2018-06-06 13:41:56 -07:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-05-15 15:31:13 -07:00
|
|
|
-- empty all helper variables
|
2018-06-17 11:10:14 -07:00
|
|
|
wesh.reset_geometry(canvas)
|
2018-06-18 09:43:12 -07:00
|
|
|
|
2018-05-15 15:31:13 -07:00
|
|
|
-- read all nodes from the canvas space in the world
|
|
|
|
-- extract the colors and put them into a helper matrix of color voxels
|
2018-06-02 19:43:01 -07:00
|
|
|
-- generate primary boundary
|
2018-06-02 08:58:57 -07:00
|
|
|
wesh.traverse_matrix(wesh.node_to_voxel, canvas.size, canvas)
|
2018-06-08 12:24:31 -07:00
|
|
|
|
2018-06-18 09:43:12 -07:00
|
|
|
canvas.generate_obj = true
|
2018-06-08 12:24:31 -07:00
|
|
|
|
2018-06-18 09:43:12 -07:00
|
|
|
local meshdata = ""
|
|
|
|
|
|
|
|
if canvas.voxel_count == 0 then
|
|
|
|
canvas.generate_obj = false
|
|
|
|
if canvas.generate_matrix and canvas.nodename_count == 0 then
|
|
|
|
wesh.notify(playername, "Empty canvas, nothing to do")
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
wesh.notify(playername, "WARNING: no 'colored' nodes found, mesh file will not be generated")
|
|
|
|
else
|
|
|
|
-- generate secondary boundaries
|
|
|
|
wesh.generate_secondary_boundaries(canvas)
|
2018-05-15 15:31:13 -07:00
|
|
|
|
2018-06-18 09:43:12 -07:00
|
|
|
-- generate faces according to voxels
|
|
|
|
local success, err = pcall(function()
|
|
|
|
wesh.traverse_matrix(wesh.voxel_to_faces, canvas.size, canvas)
|
|
|
|
end)
|
|
|
|
|
|
|
|
if not success then
|
|
|
|
wesh.notify(playername, err.msg)
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
|
|
|
|
-- this will be the actual content of the .obj file
|
|
|
|
local vt_section = wesh.vertex_textures
|
|
|
|
local v_section = wesh.vertices_to_string(canvas)
|
|
|
|
local vn_section = wesh.normals_to_string()
|
|
|
|
local f_section = table.concat(canvas.faces, "\n")
|
|
|
|
meshdata = vt_section .. v_section .. vn_section .. f_section
|
|
|
|
end
|
2018-05-15 15:31:13 -07:00
|
|
|
|
2018-06-07 08:36:39 -07:00
|
|
|
return wesh.save_mesh_to_file(obj_filename, meshdata, description, playername, canvas)
|
2018-05-15 15:31:13 -07:00
|
|
|
end
|
|
|
|
|
2018-06-09 04:51:41 -07:00
|
|
|
-- ========================================================================
|
2018-06-09 16:40:51 -07:00
|
|
|
-- file generation
|
2018-06-09 04:51:41 -07:00
|
|
|
-- ========================================================================
|
|
|
|
|
2018-06-09 16:40:51 -07:00
|
|
|
function wesh.create_nodename(obj_filename, variant)
|
|
|
|
return "wesh:" .. obj_filename:gsub("[^%w]+", "_"):gsub("_obj", "") .. "_" .. variant
|
2018-06-09 04:51:41 -07:00
|
|
|
end
|
|
|
|
|
2018-06-09 16:40:51 -07:00
|
|
|
function wesh.prepare_data_file(description, canvas)
|
|
|
|
local boxes = {}
|
|
|
|
wesh.merge_collision_boxes(canvas)
|
|
|
|
for _, box in ipairs(canvas.boxes) do
|
|
|
|
table.insert(boxes, wesh.box_to_collision_box(box, canvas.size))
|
2018-06-09 04:51:41 -07:00
|
|
|
end
|
|
|
|
|
2018-06-09 16:40:51 -07:00
|
|
|
local data = {
|
|
|
|
description = description,
|
|
|
|
variants = canvas.chosen_variants,
|
|
|
|
collision_box = {
|
|
|
|
type = "fixed",
|
|
|
|
fixed = boxes,
|
|
|
|
}
|
2018-06-09 04:51:41 -07:00
|
|
|
}
|
2018-06-09 16:40:51 -07:00
|
|
|
return wesh.serialize(data, 2)
|
2018-06-09 04:51:41 -07:00
|
|
|
end
|
|
|
|
|
2018-06-07 08:36:39 -07:00
|
|
|
function wesh.save_mesh_to_file(obj_filename, meshdata, description, playername, canvas)
|
2018-06-22 16:07:45 -07:00
|
|
|
|
2018-06-18 09:43:12 -07:00
|
|
|
if canvas.generate_obj then
|
|
|
|
-- save .obj file
|
|
|
|
local full_filename = wesh.temp_path .. "/" .. obj_filename
|
|
|
|
local file, errmsg = io.open(full_filename, "wb")
|
|
|
|
if not file then
|
|
|
|
wesh.notify(playername, "Unable to write to file '" .. obj_filename .. "' from '" .. wesh.temp_path .. "' - error: " .. errmsg)
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
file:write(meshdata)
|
|
|
|
file:close()
|
|
|
|
|
|
|
|
-- save .dat file
|
|
|
|
local data_filename = obj_filename .. ".dat"
|
|
|
|
local full_data_filename = wesh.temp_path .. "/" .. data_filename
|
|
|
|
local file, errmsg = io.open(full_data_filename, "wb")
|
|
|
|
if not file then
|
|
|
|
wesh.notify(playername, "Unable to write to file '" .. data_filename .. "' from '" .. wesh.temp_path .. "' - error: " .. errmsg)
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
file:write(wesh.prepare_data_file(description, canvas))
|
|
|
|
file:close()
|
|
|
|
wesh.notify(playername, "Mesh saved to '" .. obj_filename .. "' in '" .. wesh.temp_path .. "'")
|
|
|
|
wesh.notify(playername, "Reload the world to move newly created mesh to the mod folder")
|
|
|
|
wesh.notify(playername, "Mesh stats: " .. canvas.voxel_count .. " voxels, " .. #canvas.vertices .. " vertices, " .. #canvas.faces .. " faces, " .. canvas.nodename_count .. " nodenames")
|
2018-05-15 15:31:13 -07:00
|
|
|
end
|
|
|
|
|
2018-06-07 08:36:39 -07:00
|
|
|
if canvas.generate_matrix then
|
|
|
|
-- save .matrix.dat file
|
2018-06-18 09:43:12 -07:00
|
|
|
local matrix_data_filename = obj_filename .. ".matrix" .. canvas.size .. ".dat"
|
2018-06-07 08:36:39 -07:00
|
|
|
local full_matrix_data_filename = wesh.temp_path .. "/" .. matrix_data_filename
|
|
|
|
local file, errmsg = io.open(full_matrix_data_filename, "wb")
|
|
|
|
if not file then
|
|
|
|
wesh.notify(playername, "Unable to write to file '" .. matrix_data_filename .. "' from '" .. wesh.temp_path .. "' - error: " .. errmsg)
|
|
|
|
return false
|
|
|
|
end
|
2018-06-18 09:43:12 -07:00
|
|
|
|
|
|
|
local matrix_data = {
|
|
|
|
colors = canvas.matrix,
|
|
|
|
nodes = canvas.node_matrix,
|
|
|
|
}
|
|
|
|
|
|
|
|
file:write(minetest.serialize(matrix_data))
|
2018-06-07 08:36:39 -07:00
|
|
|
file:close()
|
2018-06-18 09:43:12 -07:00
|
|
|
wesh.notify(playername, "Matrix file saved to '" .. matrix_data_filename .. "' in '" .. wesh.temp_path .. "'")
|
|
|
|
wesh.notify(playername, "Reload the world to move newly created matrix to the mod folder")
|
2018-05-15 15:31:13 -07:00
|
|
|
end
|
2018-06-22 16:07:45 -07:00
|
|
|
|
2018-06-07 08:36:39 -07:00
|
|
|
return true
|
2018-05-15 15:31:13 -07:00
|
|
|
end
|
|
|
|
|
2018-06-09 16:40:51 -07:00
|
|
|
-- ========================================================================
|
|
|
|
-- file listing / filtering
|
|
|
|
-- ========================================================================
|
2018-06-03 05:41:32 -07:00
|
|
|
|
2018-06-07 16:46:32 -07:00
|
|
|
function wesh.filter_non_obj(filelist)
|
|
|
|
local list = {}
|
|
|
|
for _, filename in pairs(filelist) do
|
|
|
|
if wesh.is_valid_obj_filename(filename) then
|
|
|
|
table.insert(list, filename)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
return list
|
|
|
|
end
|
|
|
|
|
2018-06-18 09:43:12 -07:00
|
|
|
function wesh.filter_non_matrix(filelist, canvas)
|
2018-06-09 04:51:41 -07:00
|
|
|
local list = {}
|
|
|
|
for _, filename in pairs(filelist) do
|
2018-06-18 09:43:12 -07:00
|
|
|
if wesh.is_valid_matrix_filename(filename, canvas) then
|
2018-06-09 04:51:41 -07:00
|
|
|
table.insert(list, filename)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
return list
|
|
|
|
end
|
|
|
|
|
2018-06-09 16:40:51 -07:00
|
|
|
function wesh.get_all_files()
|
|
|
|
local all = wesh.get_temp_files()
|
|
|
|
for _, entry in pairs(wesh.get_stored_files()) do
|
|
|
|
table.insert(all, entry)
|
2018-05-15 15:31:13 -07:00
|
|
|
end
|
2018-06-09 16:40:51 -07:00
|
|
|
return all
|
2018-06-07 16:46:32 -07:00
|
|
|
end
|
|
|
|
|
2018-06-08 12:24:31 -07:00
|
|
|
function wesh.get_all_obj_files()
|
|
|
|
local stored_obj_files = wesh.filter_non_obj(wesh.get_stored_files())
|
|
|
|
local temp_obj_files = wesh.filter_non_obj(wesh.get_temp_files())
|
|
|
|
local marked_objs = wesh.retrieve_marked_objs()
|
|
|
|
local result = {}
|
|
|
|
|
|
|
|
for _, obj_filename in pairs(stored_obj_files) do
|
|
|
|
table.insert(result, {
|
|
|
|
filename = obj_filename,
|
|
|
|
type = marked_objs[obj_filename] and "pending deletion" or "stored",
|
|
|
|
})
|
|
|
|
end
|
|
|
|
|
|
|
|
for _, obj_filename in pairs(temp_obj_files) do
|
|
|
|
table.insert(result, {
|
|
|
|
filename = obj_filename,
|
|
|
|
type = "temporary",
|
|
|
|
})
|
|
|
|
end
|
|
|
|
|
|
|
|
return result
|
|
|
|
end
|
|
|
|
|
2018-06-09 16:40:51 -07:00
|
|
|
function wesh.get_stored_files()
|
|
|
|
return minetest.get_dir_list(wesh.models_path, false)
|
|
|
|
end
|
|
|
|
|
|
|
|
function wesh.get_temp_files()
|
|
|
|
return minetest.get_dir_list(wesh.temp_path, false)
|
|
|
|
end
|
|
|
|
|
|
|
|
function wesh.is_valid_obj_filename(obj_filename)
|
|
|
|
return obj_filename:match("^" .. wesh.gen_prefix .. ".-%.obj$")
|
|
|
|
end
|
|
|
|
|
2018-06-18 09:43:12 -07:00
|
|
|
function wesh.is_valid_matrix_filename(matrix_filename, canvas)
|
|
|
|
local pattern = "^" .. wesh.gen_prefix .. ".-%.obj%.matrix(%d*)%.dat$"
|
|
|
|
local result = matrix_filename:match(pattern)
|
|
|
|
return tonumber(result) == canvas.size or result == ""
|
2018-06-09 16:40:51 -07:00
|
|
|
end
|
|
|
|
|
|
|
|
-- ========================================================================
|
|
|
|
-- file movement / copy
|
|
|
|
-- ========================================================================
|
|
|
|
|
|
|
|
function wesh.move_temp_files()
|
|
|
|
local meshes = wesh.get_temp_files()
|
|
|
|
for _, filename in ipairs(meshes) do
|
|
|
|
os.rename(wesh.temp_path .. "/" .. filename, wesh.models_path .. filename)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- ========================================================================
|
|
|
|
-- file deletion
|
|
|
|
-- ========================================================================
|
|
|
|
|
|
|
|
function wesh.delete_marked_objs()
|
|
|
|
for obj_filename, _ in pairs(wesh.retrieve_marked_objs()) do
|
|
|
|
wesh.delete_obj_fileset(wesh.models_path .. obj_filename)
|
|
|
|
end
|
|
|
|
storage:set_string("marked_objs", "")
|
|
|
|
end
|
|
|
|
|
|
|
|
function wesh.delete_obj_fileset(full_obj_filename)
|
|
|
|
os.remove(full_obj_filename)
|
|
|
|
os.remove(full_obj_filename .. ".dat")
|
2018-06-18 09:43:12 -07:00
|
|
|
os.remove(full_obj_filename .. ".matrix.dat")
|
|
|
|
for size, _ in ipairs(wesh.valid_canvas_sizes) do
|
|
|
|
os.remove(full_obj_filename .. ".matrix" .. size .. ".dat")
|
|
|
|
end
|
2018-06-09 16:40:51 -07:00
|
|
|
end
|
|
|
|
|
|
|
|
function wesh.delete_temp_obj(obj_filename)
|
|
|
|
local full_obj_filename = wesh.temp_path .. "/" .. obj_filename
|
|
|
|
wesh.delete_obj_fileset(full_obj_filename)
|
|
|
|
end
|
|
|
|
|
|
|
|
-- ========================================================================
|
|
|
|
-- file marking
|
|
|
|
-- ========================================================================
|
|
|
|
|
|
|
|
function wesh.retrieve_marked_objs()
|
|
|
|
local marked_objs = minetest.deserialize(storage:get_string("marked_objs"))
|
|
|
|
return type(marked_objs) == "table" and marked_objs or {}
|
|
|
|
end
|
|
|
|
|
|
|
|
function wesh.store_marked_objs(marked_objs)
|
|
|
|
storage:set_string("marked_objs", minetest.serialize(marked_objs))
|
|
|
|
end
|
|
|
|
|
|
|
|
function wesh.mark_obj_for_deletion(obj_filename)
|
|
|
|
local marked_objs = wesh.retrieve_marked_objs()
|
|
|
|
marked_objs[obj_filename] = 1
|
|
|
|
wesh.store_marked_objs(marked_objs)
|
|
|
|
end
|
|
|
|
|
|
|
|
function wesh.unmark_obj_for_deletion(obj_filename)
|
|
|
|
local marked_objs = wesh.retrieve_marked_objs()
|
|
|
|
marked_objs[obj_filename] = nil
|
|
|
|
wesh.store_marked_objs(marked_objs)
|
2018-06-07 16:46:32 -07:00
|
|
|
end
|
|
|
|
|
2018-06-09 16:40:51 -07:00
|
|
|
-- ========================================================================
|
|
|
|
-- file loading
|
|
|
|
-- ========================================================================
|
|
|
|
|
|
|
|
function wesh.load_mod_meshes()
|
2018-06-07 16:46:32 -07:00
|
|
|
local meshes = wesh.get_stored_files()
|
|
|
|
for _, filename in ipairs(meshes) do
|
|
|
|
if wesh.is_valid_obj_filename(filename) then
|
2018-06-09 16:40:51 -07:00
|
|
|
wesh.load_mesh(filename)
|
2018-06-07 16:46:32 -07:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-06-09 16:40:51 -07:00
|
|
|
function wesh.load_mesh(obj_filename)
|
2018-06-07 16:46:32 -07:00
|
|
|
local data = wesh.get_obj_filedata(obj_filename)
|
2018-05-15 15:31:13 -07:00
|
|
|
|
|
|
|
local description = data.description or "Custom Woolen Mesh"
|
2018-05-16 06:36:49 -07:00
|
|
|
local variants = data.variants or { plain = "plain-16.png" }
|
2018-05-15 15:31:13 -07:00
|
|
|
|
|
|
|
for variant, tile in pairs(variants) do
|
2018-06-02 08:58:57 -07:00
|
|
|
local props = {
|
2018-05-15 15:31:13 -07:00
|
|
|
drawtype = "mesh",
|
|
|
|
mesh = obj_filename,
|
2018-06-03 05:46:40 -07:00
|
|
|
paramtype = "light",
|
|
|
|
sunlight_propagates = true,
|
2018-05-15 15:31:13 -07:00
|
|
|
paramtype2 = "facedir",
|
|
|
|
description = description .. " (" .. variant .. ")",
|
|
|
|
tiles = { tile },
|
|
|
|
walkable = true,
|
2018-06-05 13:13:29 -07:00
|
|
|
groups = { snappy = 2, choppy = 2, oddly_breakable_by_hand = 3 },
|
2018-06-02 08:58:57 -07:00
|
|
|
}
|
|
|
|
for prop, value in pairs(data) do
|
|
|
|
if prop ~= "variants" and prop ~= "description" then
|
|
|
|
props[prop] = value
|
|
|
|
end
|
|
|
|
end
|
|
|
|
if props.collision_box and not props.selection_box then
|
|
|
|
props.selection_box = props.collision_box
|
|
|
|
end
|
2018-06-07 14:45:50 -07:00
|
|
|
props.on_place = function(itemstack, placer, pointed_thing)
|
|
|
|
local playername = placer:get_player_name()
|
|
|
|
if not minetest.get_player_privs(playername).wesh_place then
|
|
|
|
wesh.notify(playername, "Insufficient privileges to place mesh nodes")
|
|
|
|
return
|
|
|
|
end
|
|
|
|
minetest.item_place(itemstack, placer, pointed_thing)
|
|
|
|
end
|
2018-06-07 16:46:32 -07:00
|
|
|
local nodename = wesh.create_nodename(obj_filename, variant)
|
|
|
|
minetest.register_node(nodename, props)
|
2018-05-15 15:31:13 -07:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-06-09 16:40:51 -07:00
|
|
|
function wesh.get_obj_filedata(obj_filename)
|
|
|
|
local full_data_filename = wesh.models_path .. obj_filename .. ".dat"
|
|
|
|
|
|
|
|
local file = io.open(full_data_filename, "rb")
|
|
|
|
|
|
|
|
local data = {}
|
|
|
|
if file then
|
|
|
|
data = minetest.deserialize(file:read("*all"))
|
|
|
|
data = type(data) == "table" and data or {}
|
|
|
|
file:close()
|
|
|
|
end
|
|
|
|
return data
|
|
|
|
end
|
|
|
|
|
|
|
|
-- ========================================================================
|
|
|
|
-- matrix import
|
|
|
|
-- ========================================================================
|
|
|
|
|
|
|
|
function wesh.get_content_id(nodename)
|
|
|
|
if not wesh.content_ids[nodename] then
|
|
|
|
wesh.content_ids[nodename] = minetest.get_content_id(nodename)
|
|
|
|
end
|
|
|
|
return wesh.content_ids[nodename]
|
|
|
|
end
|
|
|
|
|
2018-06-18 09:43:12 -07:00
|
|
|
function wesh.import_matrix(full_matrix_filename, playername, original, negative, mononode, nodename)
|
2018-06-09 19:17:58 -07:00
|
|
|
if not full_matrix_filename then
|
|
|
|
wesh.notify(playername, "Please select a matrix to import")
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
|
|
|
|
local nodename_id = wesh.get_content_id(nodename)
|
|
|
|
|
|
|
|
local invalid_nodename = (nodename_id == minetest.CONTENT_IGNORE) or (nodename_id == minetest.CONTENT_UNKNOWN)
|
|
|
|
|
|
|
|
if invalid_nodename and (negative or mononode) then
|
|
|
|
wesh.notify(playername, "Unknown nodename: '" .. nodename .. "'")
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
|
2018-06-09 16:40:51 -07:00
|
|
|
local file = io.open(full_matrix_filename, "rb")
|
|
|
|
if not file then
|
|
|
|
wesh.notify(playername, "Unable to open file " .. full_matrix_filename)
|
|
|
|
return false
|
|
|
|
end
|
2018-06-18 09:43:12 -07:00
|
|
|
|
|
|
|
local matrix_all = minetest.deserialize(file:read("*all"))
|
|
|
|
|
|
|
|
if not matrix_all or type(matrix_all) ~= "table" then
|
2018-06-09 16:40:51 -07:00
|
|
|
wesh.notify(playername, "Invalid matrix data inside " .. full_matrix_filename)
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
|
2018-06-18 09:43:12 -07:00
|
|
|
local matrix_data = matrix_all.colors or matrix_all
|
|
|
|
local matrix_nodes = matrix_all.nodes or false
|
|
|
|
|
|
|
|
if not matrix_nodes and original then
|
|
|
|
wesh.notify(playername, "Old matrix without original nodes " .. full_matrix_filename)
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
|
|
|
|
if original and (negative or mononode) then
|
|
|
|
wesh.notify(playername, "Can't import ORIGINAL nodes in INVERT mode or in MONONODE mode")
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
|
|
|
|
if original then
|
|
|
|
matrix_data = matrix_nodes
|
|
|
|
end
|
|
|
|
|
2018-06-09 16:40:51 -07:00
|
|
|
local canvas = wesh.player_canvas[playername]
|
|
|
|
|
|
|
|
local function invalid_size(axis, size)
|
|
|
|
if size ~= canvas.size then
|
|
|
|
wesh.notify(playername, "Trying to import " .. full_matrix_filename)
|
|
|
|
wesh.notify(playername, axis .. " == " .. size .. " doesn't match canvas value of " .. canvas.size)
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
|
2018-06-18 09:43:12 -07:00
|
|
|
if invalid_size("x", #matrix_data) or invalid_size("y", #matrix_data[1]) or invalid_size("z", #matrix_data[1][1]) then
|
2018-06-09 16:40:51 -07:00
|
|
|
return false
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
local min_pos = wesh.make_absolute({ x = 1, y = 1, z = 1 }, canvas)
|
|
|
|
local max_pos = wesh.make_absolute({ x = canvas.size, y = canvas.size, z = canvas.size }, canvas)
|
|
|
|
|
|
|
|
local vm = minetest.get_voxel_manip()
|
|
|
|
local emin, emax = vm:read_from_map(min_pos, max_pos)
|
|
|
|
local a = VoxelArea:new{
|
|
|
|
MinEdge = emin,
|
|
|
|
MaxEdge = emax
|
|
|
|
}
|
|
|
|
|
|
|
|
local data = vm:get_data()
|
2018-06-18 09:43:12 -07:00
|
|
|
|
|
|
|
local data2 = false
|
|
|
|
|
|
|
|
if original then
|
|
|
|
data2 = vm:get_param2_data()
|
|
|
|
end
|
|
|
|
|
2018-06-09 16:40:51 -07:00
|
|
|
local air_id = wesh.get_content_id("air")
|
|
|
|
|
2018-06-18 09:43:12 -07:00
|
|
|
for x = 1, #matrix_data do
|
|
|
|
for y = 1, #matrix_data[x] do
|
|
|
|
for z = 1, #matrix_data[x][y] do
|
|
|
|
|
|
|
|
local cell = matrix_data[x][y][z]
|
2018-06-18 10:24:06 -07:00
|
|
|
local param2 = false
|
2018-06-18 09:43:12 -07:00
|
|
|
|
2018-06-09 19:17:58 -07:00
|
|
|
local final_id = false
|
2018-06-18 09:43:12 -07:00
|
|
|
|
|
|
|
if original then
|
|
|
|
if cell[1] ~= "air" then
|
|
|
|
final_id = wesh.get_content_id(cell[1])
|
2018-06-18 10:24:06 -07:00
|
|
|
if cell[2] then
|
|
|
|
param2 = wesh.transform_facedir(canvas.facedir, cell[2])
|
|
|
|
end
|
2018-06-18 09:43:12 -07:00
|
|
|
end
|
|
|
|
else
|
|
|
|
local color = cell
|
|
|
|
if negative then
|
|
|
|
if color == "air" then
|
|
|
|
final_id = nodename_id
|
|
|
|
end
|
|
|
|
elseif color ~= "air" then
|
|
|
|
final_id = mononode and nodename_id or wesh.get_content_id("wool:" .. color)
|
2018-06-09 19:17:58 -07:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
if final_id then
|
2018-06-09 16:40:51 -07:00
|
|
|
local rel_pos = { x = x, y = y, z = z }
|
|
|
|
local abs_pos = wesh.make_absolute(rel_pos, canvas)
|
|
|
|
local vi = a:index(abs_pos.x, abs_pos.y, abs_pos.z)
|
2018-06-09 19:17:58 -07:00
|
|
|
data[vi] = final_id
|
2018-06-18 10:24:06 -07:00
|
|
|
if data2 and param2 then
|
2018-06-18 09:43:12 -07:00
|
|
|
data2[vi] = param2
|
|
|
|
end
|
2018-06-09 16:40:51 -07:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
vm:set_data(data)
|
2018-06-18 09:43:12 -07:00
|
|
|
|
|
|
|
if data2 then
|
|
|
|
vm:set_param2_data(data2)
|
|
|
|
end
|
|
|
|
|
2018-06-09 16:40:51 -07:00
|
|
|
vm:write_to_map(true)
|
|
|
|
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
2018-06-09 18:14:42 -07:00
|
|
|
function wesh.fill_canvas(playername, nodename)
|
|
|
|
local nodename_id = wesh.get_content_id(nodename)
|
|
|
|
|
|
|
|
if nodename_id == minetest.CONTENT_IGNORE or nodename_id == minetest.CONTENT_UNKNOWN then
|
|
|
|
wesh.notify(playername, "Unknown nodename: '" .. nodename .. "'")
|
|
|
|
return false
|
|
|
|
end
|
2018-06-09 16:40:51 -07:00
|
|
|
|
|
|
|
local canvas = wesh.player_canvas[playername]
|
|
|
|
|
|
|
|
local min_pos = wesh.make_absolute({ x = 1, y = 1, z = 1 }, canvas)
|
|
|
|
local max_pos = wesh.make_absolute({ x = canvas.size, y = canvas.size, z = canvas.size }, canvas)
|
|
|
|
|
|
|
|
local vm = minetest.get_voxel_manip()
|
|
|
|
local emin, emax = vm:read_from_map(min_pos, max_pos)
|
|
|
|
local a = VoxelArea:new{
|
|
|
|
MinEdge = emin,
|
|
|
|
MaxEdge = emax
|
|
|
|
}
|
|
|
|
|
|
|
|
local data = vm:get_data()
|
|
|
|
|
|
|
|
local min = wesh.axis_min(min_pos, max_pos)
|
|
|
|
local max = wesh.axis_max(min_pos, max_pos)
|
|
|
|
|
|
|
|
for x = min.x, max.x do
|
|
|
|
for y = min.y, max.y do
|
|
|
|
for z = min.z, max.z do
|
|
|
|
local vi = a:index(x, y, z)
|
2018-06-09 18:14:42 -07:00
|
|
|
data[vi] = nodename_id
|
2018-06-09 16:40:51 -07:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
vm:set_data(data)
|
|
|
|
vm:write_to_map(true)
|
|
|
|
|
2018-06-09 18:14:42 -07:00
|
|
|
return true
|
2018-06-09 16:40:51 -07:00
|
|
|
end
|
|
|
|
|
2018-06-05 03:23:17 -07:00
|
|
|
-- ========================================================================
|
|
|
|
-- collision box computers
|
|
|
|
-- ========================================================================
|
|
|
|
|
|
|
|
function wesh.box_to_collision_box(box, size)
|
|
|
|
-- transform integral values of the box to the -0.5 ~ 0.5 range
|
|
|
|
-- and return its string representation
|
|
|
|
|
|
|
|
local subvoxel = 1 / size
|
|
|
|
|
|
|
|
local min = vector.subtract(box.min, 1)
|
|
|
|
min = vector.multiply(min, subvoxel)
|
|
|
|
min = vector.subtract(min, 0.5)
|
|
|
|
|
|
|
|
local max = vector.add(box.max, 0)
|
|
|
|
max = vector.multiply(max, subvoxel)
|
|
|
|
max = vector.subtract(max, 0.5)
|
2018-06-06 07:56:23 -07:00
|
|
|
return { min.x, min.y, min.z, max.x, max.y, max.z }
|
2018-06-05 03:23:17 -07:00
|
|
|
end
|
|
|
|
|
|
|
|
function wesh.generate_secondary_boundaries(canvas)
|
|
|
|
-- split_boundary calls itself recursively and splits over the three axes in sequence
|
|
|
|
canvas.boundaries = wesh.split_boundary(canvas.boundary, "x")
|
|
|
|
|
|
|
|
-- boundaries will get converted to boxes with integral values and shrunk if necessary
|
|
|
|
canvas.boxes = {}
|
|
|
|
for index, boundary in ipairs(canvas.boundaries) do
|
|
|
|
canvas.boxes[index] = {}
|
2018-06-17 11:10:14 -07:00
|
|
|
wesh.traverse_matrix(wesh.update_secondary_collision_box, boundary, canvas.boxes[index], canvas)
|
2018-06-05 03:23:17 -07:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
function wesh.merge_collision_boxes(canvas)
|
|
|
|
-- merge collision boxes back if they fall within a relative treshold
|
|
|
|
|
|
|
|
local unmergeable = {}
|
|
|
|
local boxes = {}
|
|
|
|
local treshold = math.floor(canvas.size / 4)
|
|
|
|
|
|
|
|
-- remove all empty boxes
|
|
|
|
for _, box in ipairs(canvas.boxes) do
|
|
|
|
if box.min then
|
|
|
|
table.insert(boxes, box)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- repeatedly iterate over boxes comparing the first to remaining ones
|
|
|
|
while #boxes > 1 do
|
|
|
|
local a = boxes[1]
|
|
|
|
local merged = false
|
|
|
|
for i = 2, #boxes do
|
|
|
|
local b = boxes[i]
|
|
|
|
-- if appropriate, remove and merge pairs together appending resulting box to the table
|
|
|
|
if wesh.mergeable_boxes(a, b, treshold) then
|
|
|
|
table.insert(boxes, {
|
|
|
|
min = wesh.axis_min(a.min, b.min),
|
|
|
|
max = wesh.axis_max(a.max, b.max),
|
|
|
|
})
|
|
|
|
table.remove(boxes, i)
|
|
|
|
table.remove(boxes, 1)
|
|
|
|
merged = true;
|
|
|
|
break
|
|
|
|
end
|
|
|
|
end
|
|
|
|
if not merged then
|
|
|
|
table.insert(unmergeable, boxes[1])
|
|
|
|
table.remove(boxes, 1)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
for _, v in ipairs(unmergeable) do
|
|
|
|
table.insert(boxes, v)
|
|
|
|
end
|
|
|
|
canvas.boxes = boxes;
|
|
|
|
end
|
|
|
|
|
|
|
|
function wesh.mergeable_boxes(a, b, treshold)
|
|
|
|
-- check if boxes are aligned independently on each axis
|
|
|
|
local align_x = math.abs(a.min.x - b.min.x) <= treshold and math.abs(a.max.x - b.max.x) <= treshold
|
|
|
|
local align_y = math.abs(a.min.y - b.min.y) <= treshold and math.abs(a.max.y - b.max.y) <= treshold
|
|
|
|
local align_z = math.abs(a.min.z - b.min.z) <= treshold and math.abs(a.max.z - b.max.z) <= treshold
|
|
|
|
|
|
|
|
-- increase treshold by one to arrange for 2x2x2 corner case with treshold set to zero
|
|
|
|
treshold = treshold + 1
|
|
|
|
|
|
|
|
-- check if spacing between boxes along independent axes is smaller than given treshold
|
|
|
|
local close_x = math.abs(a.min.x - b.max.x) <= treshold or math.abs(b.min.x - a.max.x) <= treshold
|
|
|
|
local close_y = math.abs(a.min.y - b.max.y) <= treshold or math.abs(b.min.y - a.max.y) <= treshold
|
|
|
|
local close_z = math.abs(a.min.z - b.max.z) <= treshold or math.abs(b.min.z - a.max.z) <= treshold
|
|
|
|
|
|
|
|
-- return true only if the boxes are aligned on two axes and close together on the third one
|
|
|
|
return align_x and align_y and close_z
|
|
|
|
or align_y and align_z and close_x
|
|
|
|
or align_z and align_x and close_y
|
|
|
|
end
|
|
|
|
|
|
|
|
function wesh.split_boundary(boundary, axis)
|
|
|
|
-- split the boundary in half over each axis recursively
|
|
|
|
-- this can result in up to 8 secondary boundaries
|
|
|
|
|
|
|
|
local boundaries = {}
|
|
|
|
local span = boundary.max[axis] - boundary.min[axis]
|
|
|
|
local next_axis = nil
|
|
|
|
if axis == "x" then
|
|
|
|
next_axis = "y"
|
|
|
|
elseif axis == "y" then
|
|
|
|
next_axis = "z"
|
|
|
|
end
|
|
|
|
if span > 0 then
|
|
|
|
local limit = math.ceil(span / 2)
|
2018-06-06 04:11:15 -07:00
|
|
|
local sub_one = table.copy(boundary)
|
2018-06-05 03:23:17 -07:00
|
|
|
sub_one.max[axis] = limit
|
2018-06-06 04:11:15 -07:00
|
|
|
local sub_two = table.copy(boundary)
|
2018-06-05 03:23:17 -07:00
|
|
|
sub_two.min[axis] = limit + 1
|
|
|
|
if next_axis then
|
|
|
|
wesh.merge_tables(boundaries, wesh.split_boundary(sub_one, next_axis))
|
|
|
|
wesh.merge_tables(boundaries, wesh.split_boundary(sub_two, next_axis))
|
|
|
|
else
|
|
|
|
table.insert(boundaries, sub_one)
|
|
|
|
table.insert(boundaries, sub_two)
|
|
|
|
end
|
|
|
|
elseif next_axis then
|
|
|
|
wesh.merge_tables(boundaries, wesh.split_boundary(boundary, next_axis))
|
|
|
|
else
|
|
|
|
table.insert(boundaries, boundary)
|
|
|
|
end
|
|
|
|
return boundaries
|
|
|
|
end
|
|
|
|
|
|
|
|
function wesh.update_collision_box(rel_pos, box)
|
|
|
|
-- shrink box boundaries over the three axes separately
|
|
|
|
|
|
|
|
if not box.min then
|
|
|
|
box.min = rel_pos
|
|
|
|
else
|
|
|
|
box.min = wesh.axis_min(box.min, rel_pos)
|
|
|
|
end
|
|
|
|
if not box.max then
|
|
|
|
box.max = rel_pos
|
|
|
|
else
|
|
|
|
box.max = wesh.axis_max(box.max, rel_pos)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-06-17 11:10:14 -07:00
|
|
|
function wesh.update_secondary_collision_box(rel_pos, box, canvas)
|
2018-06-05 03:23:17 -07:00
|
|
|
-- let the box shrink only if the subvoxel isn't empty
|
|
|
|
|
2018-06-17 11:10:14 -07:00
|
|
|
if wesh.get_voxel_color(rel_pos, canvas) ~= "air" then
|
2018-06-05 03:23:17 -07:00
|
|
|
wesh.update_collision_box(rel_pos, box)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-05-15 15:31:13 -07:00
|
|
|
-- ========================================================================
|
2018-06-09 16:40:51 -07:00
|
|
|
-- mesh generation
|
2018-05-15 15:31:13 -07:00
|
|
|
-- ========================================================================
|
|
|
|
|
2018-06-08 12:24:31 -07:00
|
|
|
function wesh.construct_face(rel_pos, canvas, texture_vertices, facename, vertices, normal_index)
|
2018-06-09 16:40:51 -07:00
|
|
|
local function out_of_bounds(pos)
|
|
|
|
return pos.x < 1 or pos.x > canvas.size
|
|
|
|
or pos.y < 1 or pos.y > canvas.size
|
|
|
|
or pos.z < 1 or pos.z > canvas.size
|
|
|
|
end
|
2018-05-16 05:42:03 -07:00
|
|
|
local normal = wesh.face_normals[normal_index]
|
|
|
|
local hider_pos = vector.add(rel_pos, normal)
|
2018-06-17 11:10:14 -07:00
|
|
|
if not out_of_bounds(hider_pos) and wesh.get_voxel_color(hider_pos, canvas) ~= "air" then return end
|
2018-06-08 13:22:34 -07:00
|
|
|
local face_line = { "f " }
|
2018-05-15 15:31:13 -07:00
|
|
|
for i, vertex in ipairs(vertices) do
|
2018-06-17 11:10:14 -07:00
|
|
|
local index = wesh.get_vertex_index(rel_pos, canvas, vertex)
|
2018-06-08 13:22:34 -07:00
|
|
|
table.insert(face_line, index .. "/" .. texture_vertices[i] .. "/" .. normal_index .. " ")
|
2018-05-15 15:31:13 -07:00
|
|
|
end
|
2018-06-17 11:10:14 -07:00
|
|
|
table.insert(canvas.faces, table.concat(face_line))
|
|
|
|
if canvas.max_faces > 0 and #canvas.faces > canvas.max_faces then
|
2018-06-08 12:24:31 -07:00
|
|
|
error({ msg = canvas.max_faces .. " faces limit exceeded"})
|
|
|
|
end
|
2018-05-15 15:31:13 -07:00
|
|
|
end
|
|
|
|
|
|
|
|
function wesh.get_texture_vertices(color)
|
|
|
|
if not wesh.color_vertices[color] then
|
|
|
|
return wesh.color_vertices.air
|
|
|
|
end
|
|
|
|
return wesh.color_vertices[color]
|
|
|
|
end
|
|
|
|
|
2018-06-17 11:10:14 -07:00
|
|
|
function wesh.get_vertex_index(pos, canvas, vertex_number)
|
2018-06-03 05:41:32 -07:00
|
|
|
-- get integral offset of vertices related to voxel center
|
|
|
|
local offset = wesh.cube_vertices[vertex_number]
|
|
|
|
|
|
|
|
-- convert integral offset to real offset
|
2018-06-17 11:10:14 -07:00
|
|
|
offset = vector.multiply(offset, 1/canvas.size/2)
|
2018-06-03 05:41:32 -07:00
|
|
|
|
2018-06-17 11:10:14 -07:00
|
|
|
-- scale voxel center from range 1~canvas.size to range 1/canvas.size ~ 1
|
|
|
|
pos = vector.divide(pos, canvas.size)
|
2018-06-03 05:41:32 -07:00
|
|
|
|
|
|
|
-- center whole mesh around zero and shift it to make room for offsets
|
2018-06-17 11:10:14 -07:00
|
|
|
pos = vector.subtract(pos, 1/2 + 1/canvas.size/2)
|
2018-06-03 05:41:32 -07:00
|
|
|
|
|
|
|
-- not really sure whether this should be done here,
|
|
|
|
-- but if I don't do this the resulting mesh will be wrongly mirrored
|
|
|
|
pos.x = -pos.x
|
|
|
|
|
|
|
|
-- combine voxel center and offset to get final real vertex coordinate
|
|
|
|
pos = vector.add(pos, offset)
|
|
|
|
|
|
|
|
-- bail out if this vertex already exists
|
|
|
|
local lookup = pos.x .. "," .. pos.y .. "," .. pos.z
|
2018-06-17 11:10:14 -07:00
|
|
|
if canvas.vertices_indices[lookup] then return canvas.vertices_indices[lookup] end
|
2018-06-03 05:41:32 -07:00
|
|
|
|
|
|
|
-- add the vertex to the list of needed ones
|
2018-06-17 11:10:14 -07:00
|
|
|
table.insert(canvas.vertices, pos)
|
|
|
|
canvas.vertices_indices[lookup] = #canvas.vertices
|
2018-06-03 05:41:32 -07:00
|
|
|
|
2018-06-17 11:10:14 -07:00
|
|
|
return #canvas.vertices
|
2018-05-15 15:31:13 -07:00
|
|
|
end
|
|
|
|
|
2018-06-17 11:10:14 -07:00
|
|
|
function wesh.get_voxel_color(pos, canvas)
|
|
|
|
return canvas.matrix[pos.x][pos.y][pos.z]
|
2018-05-15 15:31:13 -07:00
|
|
|
end
|
|
|
|
|
2018-06-09 04:51:41 -07:00
|
|
|
function wesh.make_absolute(rel_pos, canvas)
|
|
|
|
-- relative positions range from (1, 1, 1) to (canvas.size, canvas.size, canvas.size)
|
2018-05-15 15:31:13 -07:00
|
|
|
|
|
|
|
-- shift relative to canvas node within canvas space
|
2018-06-09 04:51:41 -07:00
|
|
|
local shifted_pos = {
|
|
|
|
x = rel_pos.x - (canvas.size / 2),
|
|
|
|
y = rel_pos.y - 1,
|
|
|
|
z = rel_pos.z,
|
|
|
|
}
|
2018-05-15 15:31:13 -07:00
|
|
|
|
|
|
|
-- transform according to canvas facedir
|
2018-06-18 09:43:12 -07:00
|
|
|
local transformed_pos = wesh.apply_transform(shifted_pos, wesh.get_facedir_transform(canvas.facedir))
|
|
|
|
|
2018-05-15 15:31:13 -07:00
|
|
|
-- translate to absolute according to canvas position
|
2018-06-09 04:51:41 -07:00
|
|
|
local abs_pos = vector.add(canvas.pos, transformed_pos)
|
2018-05-15 15:31:13 -07:00
|
|
|
|
2018-06-09 04:51:41 -07:00
|
|
|
return abs_pos
|
2018-05-15 15:31:13 -07:00
|
|
|
end
|
|
|
|
|
2018-06-02 08:58:57 -07:00
|
|
|
function wesh.node_to_voxel(rel_pos, canvas)
|
2018-06-09 04:51:41 -07:00
|
|
|
local abs_pos = wesh.make_absolute(rel_pos, canvas)
|
2018-06-18 09:43:12 -07:00
|
|
|
local node = minetest.get_node_or_nil(abs_pos)
|
|
|
|
|
|
|
|
local nodedata = {
|
|
|
|
node and node.name or "air"
|
|
|
|
}
|
|
|
|
|
|
|
|
local paramtype2 = node and wesh.get_nodedef_field(node.name, "paramtype2")
|
|
|
|
|
|
|
|
if paramtype2 == "facedir" then
|
|
|
|
nodedata[2] = wesh.transform_facedir(canvas.facedir, node.param2, true)
|
|
|
|
end
|
|
|
|
|
|
|
|
local nodename = nodedata[1]
|
|
|
|
|
|
|
|
local color = wesh.nodename_to_color[nodename] or "air"
|
|
|
|
|
2018-06-02 08:58:57 -07:00
|
|
|
if color ~= "air" then
|
2018-06-08 12:24:31 -07:00
|
|
|
canvas.voxel_count = canvas.voxel_count + 1
|
2018-06-02 19:43:01 -07:00
|
|
|
wesh.update_collision_box(rel_pos, canvas.boundary)
|
2018-06-02 08:58:57 -07:00
|
|
|
end
|
2018-06-18 09:43:12 -07:00
|
|
|
|
|
|
|
if nodename ~= "air" then
|
|
|
|
canvas.nodename_count = canvas.nodename_count + 1
|
|
|
|
end
|
|
|
|
|
|
|
|
canvas.matrix[rel_pos.x][rel_pos.y][rel_pos.z] = color
|
|
|
|
canvas.node_matrix[rel_pos.x][rel_pos.y][rel_pos.z] = nodedata
|
2018-05-15 15:31:13 -07:00
|
|
|
end
|
|
|
|
|
2018-06-03 05:41:32 -07:00
|
|
|
function wesh.normals_to_string()
|
2018-06-08 13:22:34 -07:00
|
|
|
local output = {}
|
2018-06-03 05:41:32 -07:00
|
|
|
for i, normal in ipairs(wesh.face_normals) do
|
2018-06-08 13:22:34 -07:00
|
|
|
table.insert(output, "vn " .. normal.x .. " " .. normal.y .. " " .. normal.z .. "\n")
|
2018-06-03 05:41:32 -07:00
|
|
|
end
|
2018-06-08 13:22:34 -07:00
|
|
|
return table.concat(output)
|
2018-06-03 05:41:32 -07:00
|
|
|
end
|
|
|
|
|
2018-06-02 08:58:57 -07:00
|
|
|
function wesh.voxel_to_faces(rel_pos, canvas)
|
2018-06-17 11:10:14 -07:00
|
|
|
local color = wesh.get_voxel_color(rel_pos, canvas)
|
2018-05-15 15:31:13 -07:00
|
|
|
if color == "air" then return end
|
|
|
|
for facename, facedata in pairs(wesh.face_construction) do
|
|
|
|
local texture_vertices = wesh.get_texture_vertices(color)
|
2018-06-08 12:24:31 -07:00
|
|
|
wesh.construct_face(rel_pos, canvas, texture_vertices, facename, facedata.vertices, facedata.normal)
|
2018-05-15 15:31:13 -07:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-06-17 11:10:14 -07:00
|
|
|
function wesh.vertices_to_string(canvas)
|
2018-06-08 13:22:34 -07:00
|
|
|
local output = {}
|
2018-06-17 11:10:14 -07:00
|
|
|
for i, vertex in ipairs(canvas.vertices) do
|
2018-06-08 13:22:34 -07:00
|
|
|
table.insert(output, "v " .. vertex.x .. " " .. vertex.y .. " " .. vertex.z .. "\n")
|
2018-05-15 15:31:13 -07:00
|
|
|
end
|
2018-06-08 13:22:34 -07:00
|
|
|
return table.concat(output)
|
2018-05-15 15:31:13 -07:00
|
|
|
end
|
|
|
|
|
|
|
|
-- ========================================================================
|
|
|
|
-- generic helpers
|
|
|
|
-- ========================================================================
|
|
|
|
|
2018-06-09 16:40:51 -07:00
|
|
|
function wesh.apply_transform(pos, transform)
|
|
|
|
return {
|
|
|
|
x = pos.x * transform[1][1] + pos.y * transform[1][2] + pos.z * transform[1][3],
|
|
|
|
y = pos.x * transform[2][1] + pos.y * transform[2][2] + pos.z * transform[2][3],
|
|
|
|
z = pos.x * transform[3][1] + pos.y * transform[3][2] + pos.z * transform[3][3],
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
2018-06-02 19:43:01 -07:00
|
|
|
function wesh.axis_min(pos1, pos2)
|
|
|
|
local result = {}
|
|
|
|
for axis, value in pairs(pos1) do
|
|
|
|
result[axis] = math.min(value, pos2[axis])
|
|
|
|
end
|
|
|
|
return result
|
|
|
|
end
|
|
|
|
|
|
|
|
function wesh.axis_max(pos1, pos2)
|
|
|
|
local result = {}
|
|
|
|
for axis, value in pairs(pos1) do
|
|
|
|
result[axis] = math.max(value, pos2[axis])
|
|
|
|
end
|
|
|
|
return result
|
|
|
|
end
|
|
|
|
|
2018-06-03 05:41:32 -07:00
|
|
|
function wesh.check_plain(text)
|
|
|
|
if type(text) ~= "string" then return "" end
|
|
|
|
text = text:gsub("^[^%w]*(.-)[^%w]*$", "%1")
|
|
|
|
return text:gsub("[^%w]+", "_"):lower()
|
|
|
|
end
|
|
|
|
|
2018-06-18 09:43:12 -07:00
|
|
|
function wesh.get_nodedef_field(nodename, fieldname)
|
|
|
|
if not minetest.registered_nodes[nodename] then
|
|
|
|
return nil
|
2018-06-06 03:57:22 -07:00
|
|
|
end
|
2018-06-18 09:43:12 -07:00
|
|
|
return minetest.registered_nodes[nodename][fieldname]
|
2018-06-06 03:57:22 -07:00
|
|
|
end
|
|
|
|
|
2018-06-03 05:41:32 -07:00
|
|
|
function wesh.merge_tables(t1, t2)
|
|
|
|
for _, value in pairs(t2) do
|
|
|
|
table.insert(t1, value)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-06-07 08:36:39 -07:00
|
|
|
function wesh.notify(playername, message)
|
|
|
|
minetest.chat_send_player(playername, "[wesh] " .. message)
|
2018-06-03 05:41:32 -07:00
|
|
|
end
|
|
|
|
|
2018-06-06 07:56:23 -07:00
|
|
|
function wesh.serialize(object, max_wrapping)
|
|
|
|
local function helper(obj, max_depth, depth, seen)
|
|
|
|
if not depth then
|
|
|
|
depth = 0
|
|
|
|
end
|
|
|
|
if not seen then
|
|
|
|
seen = {}
|
|
|
|
end
|
|
|
|
|
|
|
|
local wrap = max_depth and max_depth > depth or false
|
|
|
|
|
|
|
|
local out = ""
|
|
|
|
local t = type(obj)
|
|
|
|
if t == nil then
|
|
|
|
return "nil"
|
|
|
|
elseif t == "string" then
|
|
|
|
return string.format("%q", obj)
|
|
|
|
elseif t == "boolean" then
|
|
|
|
return obj and "true" or "false"
|
|
|
|
elseif t == "number" then
|
|
|
|
if math.floor(obj) == obj then
|
|
|
|
return string.format("%d", obj)
|
|
|
|
else
|
|
|
|
return tostring(obj)
|
|
|
|
end
|
|
|
|
elseif t == "table" then
|
|
|
|
if seen[tostring(obj)] then
|
|
|
|
error("[wesh] serialize(): Cyclic references not supported")
|
|
|
|
end
|
|
|
|
seen[tostring(obj)] = true;
|
|
|
|
|
2018-06-08 13:22:34 -07:00
|
|
|
local output = { "{\n" }
|
2018-06-06 07:56:23 -07:00
|
|
|
local post_table = string.rep("\t", depth) .. "}"
|
|
|
|
local pre_key = string.rep("\t", depth + 1)
|
|
|
|
local post_value = ",\n";
|
|
|
|
|
|
|
|
if not wrap then
|
2018-06-08 13:22:34 -07:00
|
|
|
output = { "{ " }
|
2018-06-06 07:56:23 -07:00
|
|
|
post_table = "}"
|
|
|
|
pre_key = ""
|
|
|
|
post_value = ", "
|
|
|
|
end
|
|
|
|
|
|
|
|
for k, v in pairs(obj) do
|
|
|
|
local key = k .. " = "
|
|
|
|
if type(k) == "number" then
|
|
|
|
-- remove numeric indices on purpose
|
|
|
|
key = ""
|
|
|
|
elseif type(k) ~= "string" or k:match("[^%w_]") then
|
|
|
|
error("[wesh] serialize(): Unsupported array key " .. helper(k))
|
|
|
|
end
|
2018-06-08 13:22:34 -07:00
|
|
|
table.insert(output, pre_key)
|
|
|
|
table.insert(output, key)
|
|
|
|
table.insert(output, helper(v, max_depth, depth + 1, seen))
|
|
|
|
table.insert(output, post_value)
|
2018-06-06 07:56:23 -07:00
|
|
|
end
|
2018-06-08 13:22:34 -07:00
|
|
|
table.insert(output, post_table)
|
|
|
|
return table.concat(output)
|
2018-06-06 07:56:23 -07:00
|
|
|
else
|
|
|
|
error("[wesh] serialize(): Data type " .. t .. " not supported")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
return "return " .. helper(object, max_wrapping)
|
|
|
|
end
|
|
|
|
|
2018-06-02 19:43:01 -07:00
|
|
|
function wesh.traverse_matrix(callback, boundary, ...)
|
|
|
|
if type(boundary) == "table" then
|
|
|
|
for x = boundary.min.x, boundary.max.x do
|
|
|
|
for y = boundary.min.y, boundary.max.y do
|
|
|
|
for z = boundary.min.z, boundary.max.z do
|
|
|
|
callback({x = x, y = y, z = z}, ...)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
else
|
|
|
|
for x = 1, boundary do
|
|
|
|
for y = 1, boundary do
|
|
|
|
for z = 1, boundary do
|
|
|
|
callback({x = x, y = y, z = z}, ...)
|
|
|
|
end
|
2018-05-15 15:31:13 -07:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-06-09 16:40:51 -07:00
|
|
|
wesh.init()
|