minetest-mod-blockexport/init.lua

234 lines
6.8 KiB
Lua

---- Configuration Data: Modify to customize behavior
local FILE_PREFIX = "block_";
local FILE_SUFFIX = ".obj";
local EXPORT_NODES = { "wool:white", "wool:red", "wool:green", "wool:blue" };
---- End Configuration Data
Basis =
{
localToGlobal = function(self, pos)
return { [self.e] = pos.x, [self.n] = pos.y, [self.u] = pos.z };
end,
globalToLocal = function(self, pos)
return { x = pos[self.e], y = pos[self.n], z = pos[self.u] };
end
};
setmetatable(
Basis,
{
__call = function(class, up)
local e, n;
if up == "x" then
e, n = "y", "z";
elseif up == "y" then
e, n = "z", "x";
elseif up == "z" then
e, n = "x", "y";
else
error("illegal basis direction");
end
return setmetatable({ e = e, n = n, u = up }, { __index = class });
end
});
local function fileName(x, y, z)
local xs = math.floor(x);
local ys = math.floor(y);
local zs = math.floor(z);
if xs >= 0 then xs = tostring(xs); else xs = "m" .. tostring(-xs); end
if ys >= 0 then ys = tostring(ys); else ys = "m" .. tostring(-ys); end
if zs >= 0 then zs = tostring(zs); else zs = "m" .. tostring(-zs); end
return FILE_PREFIX .. xs .. "-" .. ys .. "-" .. zs .. FILE_SUFFIX;
end
local isCidExported;
do
local exportMap = nil;
isCidExported = function(cid)
if not exportMap then
exportMap = {};
for _, name in ipairs(EXPORT_NODES) do
local cid = minetest.get_content_id(name);
exportMap[cid] = true;
end
end
return exportMap[cid] or false;
end;
end
local function hashVert(pos)
return (pos.x or 0) + 80*(pos.y or 0) + 6400*(pos.z or 0);
end
local function getObjData(xMin, yMin, zMin, xMax, yMax, zMax)
local verts = {};
local quads = {};
local vertMap = {};
local function getRowData(basis, xMin, xMax, y, z)
local vm = minetest.get_voxel_manip();
local pMin = basis:localToGlobal({ x = xMin, y = y, z = z - 0.5 });
local pMax = basis:localToGlobal({ x = xMax, y = y, z = z + 0.5 });
pMin, pMax = vm:read_from_map(pMin, pMax);
local data = vm:get_data();
local va = VoxelArea:new({ MinEdge = pMin, MaxEdge = pMax });
local segs = {};
local start, wasUp = nil, false;
for x = xMin, xMax do
local pL = basis:localToGlobal({ x = x, y = y, z = z - 0.5 });
local pH = basis:localToGlobal({ x = x, y = y, z = z + 0.5 });
local haveL = va:containsp(pL) and isCidExported(data[va:indexp(pL)]);
local haveH = va:containsp(pH) and isCidExported(data[va:indexp(pH)]);
local on, up = (haveL ~= haveH), haveH;
if on then
if not start then
start, wasUp = x, up;
elseif up ~= wasUp then
segs[#segs + 1] = { xs = start, xe = x-1, ys = y, up = wasUp };
start, wasUp = x, up;
end
elseif start then
segs[#segs + 1] = { xs = start, xe = x-1, ys = y, up = wasUp };
start = nil;
end
end
if start then segs[#segs + 1] = { xs = start, xe = xMax, ys = y }; end
return segs;
end
local function vertexIndex(basis, pos)
local vv = basis:localToGlobal(pos);
local vh = hashVert(vv);
local vlist = vertMap[vh];
if vlist then
for v, vi in pairs(vlist) do
if v.x == vv.x and v.y == vv.y and v.z == vv.z then
return vi;
end
end
else
vlist = {};
vertMap[vh] = vlist;
end
local vi = #verts + 1;
verts[vi] = vv;
vlist[vv] = vi;
return vi;
end
local function drawRect(basis, xs, ys, xe, ye, z)
local vi1 = vertexIndex(basis, { x = xs - 0.5, y = ys - 0.5, z = z });
local vi2 = vertexIndex(basis, { x = xe + 0.5, y = ys - 0.5, z = z });
local vi3 = vertexIndex(basis, { x = xe + 0.5, y = ye + 0.5, z = z });
local vi4 = vertexIndex(basis, { x = xs - 0.5, y = ye + 0.5, z = z });
quads[#quads + 1] = { vi1, vi2, vi3, vi4 };
end
local function drawRects(basis, prevRow, nextRow, y, z)
local nri = 1;
local nrSeg = nextRow[nri];
for _, prSeg in ipairs(prevRow) do
while nrSeg and nrSeg.xe < prSeg.xs do
nri = nri + 1;
nrSeg = nextRow[nri];
end
if nrSeg and
(nrSeg.xs == prSeg.xs) and
(nrSeg.xe == prSeg.xe) and
(nrSeg.up == prSeg.up) then
nrSeg.ys = prSeg.ys;
else
drawRect(basis, prSeg.xs, prSeg.ys, prSeg.xe, y, z);
end
end
end
local function drawSlice(basis, xMin, yMin, xMax, yMax, z)
local nextRow = getRowData(basis, xMin, xMax, yMin, z);
for y = yMin+1, yMax do
local prevRow = nextRow;
nextRow = getRowData(basis, xMin, xMax, y, z);
drawRects(basis, prevRow, nextRow, y-1, z);
end
drawRects(basis, nextRow, {}, yMax, z);
end
local function drawSlices(basis, xMin, yMin, zMin, xMax, yMax, zMax)
for z = zMin - 0.5, zMax + 0.5 do
drawSlice(basis, xMin, yMin, xMax, yMax, z);
end
end
for _, up in ipairs({ "x", "y", "z" }) do
local basis = Basis(up);
local pMin = basis:globalToLocal({ x = xMin, y = yMin, z = zMin });
local pMax = basis:globalToLocal({ x = xMax, y = yMax, z = zMax });
drawSlices(basis, pMin.x, pMin.y, pMin.z, pMax.x, pMax.y, pMax.z);
end
return verts, quads;
end
local function exportBlock(pos)
local xMin = 80 * math.floor(pos.x / 80);
local yMin = 80 * math.floor(pos.y / 80);
local zMin = 80 * math.floor(pos.z / 80);
local xMax = xMin + 79;
local yMax = yMin + 79;
local zMax = zMin + 79;
local fname = fileName(xMin, yMin, zMin);
local fpath = minetest.get_worldpath() .. "/" .. fname;
local file, emsg = io.open(fpath, "w");
if not file then error(emsg); end
local verts, quads = getObjData(xMin, yMin, zMin, xMax, yMax, zMax);
for _, v in ipairs(verts) do
file:write("v "..(v.x-xMin).." "..(v.y-yMin).." "..(v.z-zMin).."\n");
end
for _, f in ipairs(quads) do
file:write("f "..f[1].." "..f[2].." "..f[3].." "..f[4].."\n");
end
file:flush();
file:close();
return fname;
end
minetest.register_privilege(
"export",
{
description = "Allows exporting of data to files",
give_to_singleplayer = true
});
minetest.register_chatcommand(
"exportblock",
{
params = "",
description = "Exports your current 80x80x80 node block to a file",
privs = { export = true },
func =
function(name, paramStr)
local player = minetest.get_player_by_name(name);
local fname = exportBlock(player:getpos());
return true, "current block exported to " .. fname;
end
});