meshport/export.lua
2021-07-05 01:05:06 -07:00

800 lines
25 KiB
Lua

--[[
Copyright (C) 2021 random-geek (https://github.com/random-geek)
Minetest: Copyright (C) 2010-2021 celeron55, Perttu Ahola <celeron55@gmail.com>
This file is part of Meshport.
Meshport is free software: you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the Free
Software Foundation, either version 3 of the License, or (at your option)
any later version.
Meshport is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
more details.
You should have received a copy of the GNU Lesser General Public License
along with Meshport. If not, see <https://www.gnu.org/licenses/>.
]]
-- Much of the mesh generation code in this file is derived from Minetest's
-- MapblockMeshGenerator class. See minetest/src/client/content_mapblock.cpp.
--[[
THE CUBIC NODE PRIORITY SYSTEM
For each face on each cubic node, Meshport decides whether or not to draw
that face based on a combination of the current node's drawtype (show in
the top row of the table below), the neighboring node's drawtype (shown in
the leftmost column), the direction of the face, and both nodes'
visual_scale.
A "YES" combination means the face is drawn, "no" means the face is not
drawn, and "Offset" means the face is drawn, but slightly inset to avoid
duplication of faces.
| This node => | allfaces (1) | glasslike | liquid | normal (2) |
|--------------:|:------------:|:---------:|:-------:|:----------:|
| air/non-cubic | YES | YES | YES (3) | YES |
| allfaces | (4) | YES | YES | YES |
| glasslike | Offset | (5) | YES | YES |
| liquid | Offset | Offset | no | YES |
| normal (2) | no | no | no | no |
1. Allfaces faces are always drawn if `visual_scale` is not 1.
2. The base of `plantlike_rooted` is treated as a normal node.
3. Liquid faces are not drawn bordering a corresponding flowing liquid.
4. Only drawn if facing X+, Y+, or Z+, or if either node's `visual_scale`
is not 1.
5. Only drawn if the nodes are different. X-, Z-, and Y- faces are offset.
]]
local CUBIC_FACE_PRIORITY = {
allfaces = 1,
glasslike = 2,
liquid = 3,
normal = 4,
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 = {
{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)}, -- X+
{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)}, -- X-
{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)}, -- Z+
{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)}, -- Z-
}
-- For normal, plantlike_rooted, and liquid drawtypes
local function create_cubic_node(pos, content, param2, nodeDef, drawtype, neighbors)
local facedir = meshport.get_facedir(nodeDef.paramtype2, param2)
local selfPriority = CUBIC_FACE_PRIORITY[drawtype]
-- If the current node is a liquid, get the flowing version of it.
local flowingLiquid = drawtype == "liquid"
and meshport.get_content_id_or_nil(nodeDef.liquid_alternative_flowing) or nil
local faces = meshport.Faces:new()
for i = 1, 6 do
local drawFace
if neighbors[i] == minetest.CONTENT_AIR then
drawFace = true
elseif neighbors[i] == minetest.CONTENT_IGNORE
-- Don't draw faces between identical nodes
or neighbors[i] == content
-- Don't draw liquid faces bordering a corresponding flowing liquid
or neighbors[i] == flowingLiquid then
drawFace = false
else
local neighborDef = meshport.get_def_from_id(neighbors[i])
local neighborDrawtype = meshport.get_aliased_drawtype(neighborDef.drawtype)
drawFace = selfPriority > (CUBIC_FACE_PRIORITY[neighborDrawtype] or 0)
end
if drawFace then
local norm = meshport.NEIGHBOR_DIRS[i]
faces:insert_face(meshport.prepare_cuboid_face({
verts = table.copy(CUBIC_SIDE_FACES[i]),
vert_norms = {norm, norm, norm, norm},
tex_coords = {{x = 0, y = 0}, {x = 1, y = 0}, {x = 1, y = 1}, {x = 0, y = 1}},
}, nodeDef.tiles, pos, facedir, i))
end
end
return faces
end
-- For allfaces and glasslike drawtypes, and equivalent variants.
local function create_special_cubic_node(pos, content, nodeDef, drawtype, neighbors)
local selfPriority = CUBIC_FACE_PRIORITY[drawtype]
local isAllfaces = drawtype == "allfaces"
local allfacesScale = isAllfaces and nodeDef.visual_scale or 1
local faces = meshport.Faces:new()
for i = 1, 6 do
local drawFace
local inset = false
if allfacesScale ~= 1 or neighbors[i] == minetest.CONTENT_AIR or neighbors[i] == minetest.CONTENT_IGNORE then
drawFace = true
elseif neighbors[i] == content then
drawFace = isAllfaces and i % 2 == 1
else
local neighborDef = meshport.get_def_from_id(neighbors[i])
local neighborDrawtype = meshport.get_aliased_drawtype(neighborDef.drawtype)
local neighborPriority = CUBIC_FACE_PRIORITY[neighborDrawtype] or 0
if neighborPriority < selfPriority then
drawFace = true
elseif neighborPriority >= 4 then
-- Don't draw faces bordering normal nodes.
drawFace = false
elseif neighborPriority > selfPriority then
drawFace = true
inset = true
elseif isAllfaces then -- neighborPriority == selfPriority
drawFace = i % 2 == 1 or neighborDef.visual_scale ~= 1
else -- neighborPriority == selfPriority
drawFace = true
inset = i % 2 == 0
end
end
if drawFace then
local verts = table.copy(CUBIC_SIDE_FACES[i])
if inset then
local offset = vector.multiply(meshport.NEIGHBOR_DIRS[i], -0.003)
for j, vert in ipairs(verts) do
verts[j] = vector.add(vert, offset)
end
end
local norm = meshport.NEIGHBOR_DIRS[i]
faces:insert_face(meshport.prepare_cuboid_face({
verts = verts,
vert_norms = {norm, norm, norm, norm},
tex_coords = {{x = 0, y = 0}, {x = 1, y = 0}, {x = 1, y = 1}, {x = 0, y = 1}},
tile_idx = 1, -- Only the first tile is used.
}, nodeDef.tiles, pos, 0, i))
end
end
faces:scale(allfacesScale)
return faces
end
local GLASSLIKE_FRAMED_CONSTANTS = (function()
local a = 0.5
local g = 0.5 - 0.003
local b = 0.876 * 0.5
return {
G = g,
B = b,
FRAME_EDGES = {
{ b, b, -a, a, a, a}, -- Y+ / X+
{-a, b, -a, -b, a, a}, -- Y+ / X-
{ b, -a, -a, a, -b, a}, -- Y- / X+
{-a, -a, -a, -b, -b, a}, -- Y- / X-
{ b, -a, b, a, a, a}, -- X+ / Z+
{ b, -a, -a, a, a, -b}, -- X+ / Z-
{-a, -a, b, -b, a, a}, -- X- / Z+
{-a, -a, -a, -b, a, -b}, -- X- / Z-
{-a, b, b, a, a, a}, -- Z+ / Y+
{-a, -a, b, a, -b, a}, -- Z+ / Y-
{-a, b, -a, a, a, -b}, -- Z- / Y+
{-a, -a, -a, a, -b, -b}, -- Z- / Y-
},
GLASS_FACES = {
{vec(-a, g, -a), vec( a, g, -a), vec( a, g, a), vec(-a, g, a)}, -- Y+
{vec(-a, -g, a), vec( a, -g, a), vec( a, -g, -a), vec(-a, -g, -a)}, -- Y-
{vec( g, -a, -a), vec( g, -a, a), vec( g, a, a), vec( g, a, -a)}, -- X+
{vec(-g, -a, a), vec(-g, -a, -a), vec(-g, a, -a), vec(-g, a, a)}, -- X-
{vec( a, -a, g), vec(-a, -a, g), vec(-a, a, g), vec( a, a, g)}, -- Z+
{vec(-a, -a, -g), vec( a, -a, -g), vec( a, a, -g), vec(-a, a, -g)}, -- Z-
},
EDGE_NEIGHBORS = {
{1, 3, 8}, {1, 4, 7}, {2, 3, 16}, {2, 4, 15},
{3, 5, 12}, {3, 6, 14}, {4, 5, 11}, {4, 6, 13},
{5, 1, 9}, {5, 2, 17}, {6, 1, 10}, {6, 2, 18},
},
}
end)()
local function create_glasslike_framed_node(pos, param2, nodeDef, area, vContent)
local idx = area:indexp(pos)
local llParam2 = nodeDef.paramtype2 == "glasslikeliquidlevel" and param2 or 0
local hMerge = llParam2 < 128 -- !(param2 & 128)
local vMerge = llParam2 % 128 < 64 -- !(param2 & 64)
local intLevel = llParam2 % 64
-- Localize constants
local G, B, FRAME_EDGES, GLASS_FACES, EDGE_NEIGHBORS = (function(c)
return c.G, c.B, c.FRAME_EDGES, c.GLASS_FACES, c.EDGE_NEIGHBORS
end)(GLASSLIKE_FRAMED_CONSTANTS)
local neighbors = {
false, false, false, false, false, false, false, false, false,
false, false, false, false, false, false, false, false, false
}
if hMerge or vMerge then
for i = 1, 18 do
local dir = meshport.NEIGHBOR_DIRS[i]
if (hMerge or (dir.x == 0 and dir.z == 0)) and (vMerge or dir.y == 0) then
local nIdx = area:indexp(vector.add(pos, dir))
neighbors[i] = vContent[nIdx] == vContent[idx]
end
end
end
local boxes = meshport.Boxes:new()
for i = 1, 12 do
local edgeVisible
local touching = EDGE_NEIGHBORS[i]
if neighbors[touching[3]] then
edgeVisible = not (neighbors[touching[1]] and neighbors[touching[2]])
else
edgeVisible = neighbors[touching[1]] == neighbors[touching[2]]
end
if edgeVisible then
boxes:insert_box(FRAME_EDGES[i])
end
end
local faces = boxes:to_faces(nodeDef, pos, 0, 1)
for i = 1, 6 do
if not neighbors[i] then
local norm = meshport.NEIGHBOR_DIRS[i]
faces:insert_face({
verts = table.copy(GLASS_FACES[i]),
vert_norms = {norm, norm, norm, norm},
tex_coords = {{x = 0, y = 0}, {x = 1, y = 0}, {x = 1, y = 1}, {x = 0, y = 1}},
tile_idx = 2,
})
end
end
if intLevel > 0 and nodeDef.special_tiles and nodeDef.special_tiles[1] then
local level = intLevel / 63 * 2 - 1
local liquidBoxes = meshport.Boxes:new()
liquidBoxes:insert_box({
-(neighbors[4] and G or B),
-(neighbors[2] and G or B),
-(neighbors[6] and G or B),
(neighbors[3] and G or B),
(neighbors[1] and G or B) * level,
(neighbors[5] and G or B)
})
faces:insert_all(liquidBoxes:to_faces(nodeDef, pos, 0, 1, true))
end
return faces
end
local FLOWING_LIQUID_CONSTANTS = {
SIDE_DIRS = {vec(1, 0, 0), vec(-1, 0, 0), vec(0, 0, 1), vec(0, 0, -1)},
SIDE_CORNERS = {
{{x = 1, z = 1}, {x = 1, z = 0}}, -- X+
{{x = 0, z = 0}, {x = 0, z = 1}}, -- X-
{{x = 0, z = 1}, {x = 1, z = 1}}, -- Z+
{{x = 1, z = 0}, {x = 0, z = 0}}, -- Z-
},
}
local function create_flowing_liquid_node(pos, nodeDef, area, vContent, vParam2)
local cSource = meshport.get_content_id_or_nil(nodeDef.liquid_alternative_source)
local cFlowing = meshport.get_content_id_or_nil(nodeDef.liquid_alternative_flowing)
local range = math.min(math.max(meshport.get_def_from_id(cFlowing).liquid_range or 8, 1), 8)
--[[ Step 1: Gather neighbor data ]]
local neighbors = {[-1] = {}, [0] = {}, [1] = {}}
for dz = -1, 1 do
for dx = -1, 1 do
local nPos = vector.add(pos, vector.new(dx, 0, dz))
local nIdx = area:indexp(nPos)
neighbors[dz][dx] = {
content = vContent[nIdx],
level = -0.5,
is_same_liquid = false,
top_is_same_liquid = false,
}
local nData = neighbors[dz][dx]
if vContent[nIdx] ~= minetest.CONTENT_IGNORE then
if vContent[nIdx] == cSource then
nData.is_same_liquid = true
nData.level = 0.5
elseif vContent[nIdx] == cFlowing then
nData.is_same_liquid = true
local intLevel = math.max(vParam2[nIdx] % 8 - 8 + range, 0)
nData.level = -0.5 + (intLevel + 0.5) / range
end
local tPos = vector.add(nPos, vector.new(0, 1, 0))
local tIdx = area:indexp(tPos)
if vContent[tIdx] == cSource or vContent[tIdx] == cFlowing then
nData.top_is_same_liquid = true
end
end
end
end
--[[ Step 2: Determine level at each corner ]]
local cornerLevels = {[0] = {[0] = 0, 0}, {[0] = 0, 0}}
local function get_corner_level(cx, cz)
local sum = 0
local count = 0
local airCount = 0
for dz = -1, 0 do
for dx = -1, 0 do
local nData = neighbors[cz + dz][cx + dx]
if nData.top_is_same_liquid or nData.content == cSource then
return 0.5
elseif nData.content == cFlowing then
sum = sum + nData.level
count = count + 1
elseif nData.content == minetest.CONTENT_AIR then
airCount = airCount + 1
if airCount >= 2 then
return -0.5 + 0.02
end
end
end
end
if count > 0 then
return sum / count
end
return 0
end
for cz = 0, 1 do
for cx = 0, 1 do
cornerLevels[cz][cx] = get_corner_level(cx, cz)
end
end
--[[ Step 3: Actually create the liquid mesh ]]
local faces = meshport.Faces:new()
-- Localize constants
local SIDE_DIRS, SIDE_CORNERS = (function(c)
return c.SIDE_DIRS, c.SIDE_CORNERS
end)(FLOWING_LIQUID_CONSTANTS)
-- Add side faces
local sideVerts = {
{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)}, -- X+
{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)}, -- X-
{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)}, -- Z+
{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)}, -- Z-
}
local function need_side(dir)
local neighbor = neighbors[dir.z][dir.x]
if neighbor.is_same_liquid
and (not neighbors[0][0].top_is_same_liquid or neighbor.top_is_same_liquid) then
return false
end
local nContent = neighbors[dir.z][dir.x].content
local drawtype = meshport.get_aliased_drawtype(meshport.get_def_from_id(nContent).drawtype)
if (CUBIC_FACE_PRIORITY[drawtype] or 0) >= 4 then
return false -- Don't draw bordering normal nodes
end
return true
end
for i = 1, 4 do
local dir = SIDE_DIRS[i]
if need_side(dir) then
local verts = sideVerts[i]
local sideTexCoords = {{x = 1, y = 1}, {x = 0, y = 1}, {x = 0, y = 0}, {x = 1, y = 0}}
if not neighbors[0][0].top_is_same_liquid then -- If there's liquid above, default to a full block.
local corners = SIDE_CORNERS[i]
for j = 1, 2 do
local corner = cornerLevels[corners[j].z][corners[j].x]
verts[j].y = corner
sideTexCoords[j].y = corner + 0.5
end
end
faces:insert_face({
verts = verts,
vert_norms = {dir, dir, dir, dir},
tex_coords = sideTexCoords,
tile_idx = 2,
use_special_tiles = true,
})
end
end
-- Add top faces
if not neighbors[0][0].top_is_same_liquid then -- Check node above the current node
local verts = {
vec( 0.5, cornerLevels[0][1], -0.5),
vec( 0.5, cornerLevels[1][1], 0.5),
vec(-0.5, cornerLevels[1][0], 0.5),
vec(-0.5, cornerLevels[0][0], -0.5),
}
local norm1 = vector.normalize(vector.cross(
vector.subtract(verts[1], verts[2]),
vector.subtract(verts[3], verts[2])
))
local norm2 = vector.normalize(vector.cross(
vector.subtract(verts[3], verts[4]),
vector.subtract(verts[1], verts[4])
))
local dz = (cornerLevels[0][0] + cornerLevels[0][1]) -
(cornerLevels[1][0] + cornerLevels[1][1])
local dx = (cornerLevels[0][0] + cornerLevels[1][0]) -
(cornerLevels[0][1] + cornerLevels[1][1])
local textureAngle = -math.atan2(dz, dx)
-- Get texture coordinate offset based on position.
local tx, ty = pos.z, -pos.x
-- Rotate offset around (0, 0) by textureAngle.
-- Then isolate the fractional part, since the texture is tiled anyway.
local sinTA = math.sin(textureAngle)
local cosTA = math.cos(textureAngle)
local textureOffset = {
x = (tx * cosTA - ty * sinTA) % 1,
y = (tx * sinTA + ty * cosTA) % 1
}
faces:insert_face({
verts = {verts[1], verts[2], verts[3]},
vert_norms = {norm1, norm1, norm1},
tex_coords = meshport.translate_texture_coordinates(
meshport.rotate_texture_coordinates_rad(
{{x = 0, y = 0}, {x = 1, y = 0}, {x = 1, y = 1}},
textureAngle
),
textureOffset
),
tile_idx = 1,
use_special_tiles = true,
})
faces:insert_face({
verts = {verts[3], verts[4], verts[1]},
vert_norms = {norm2, norm2, norm2},
tex_coords = meshport.translate_texture_coordinates(
meshport.rotate_texture_coordinates_rad(
{{x = 1, y = 1}, {x = 0, y = 1}, {x = 0, y = 0}},
textureAngle
),
textureOffset
),
tile_idx = 1,
use_special_tiles = true,
})
end
-- Add bottom face
local function need_liquid_bottom()
local bContent = vContent[area:indexp(vector.add(pos, vector.new(0, -1, 0)))]
if bContent == cSource or bContent == cFlowing then
return false
end
local drawtype = meshport.get_aliased_drawtype(meshport.get_def_from_id(bContent).drawtype)
if (CUBIC_FACE_PRIORITY[drawtype] or 0) >= 4 then
return false -- Don't draw bordering normal nodes
end
return true
end
if need_liquid_bottom() then
local norm = vector.new(0, -1, 0)
faces:insert_face({
verts = {
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),
},
vert_norms = {norm, norm, norm, norm},
tex_coords = {{x = 0, y = 0}, {x = 1, y = 0}, {x = 1, y = 1}, {x = 0, y = 1}},
tile_idx = 1,
use_special_tiles = true,
})
end
return faces
end
local function create_nodebox_node(pos, content, param2, neighbors)
local nodeName = minetest.get_name_from_content_id(content)
local nodeDef = minetest.registered_nodes[nodeName]
if not meshport.nodebox_cache[nodeName] then
meshport.nodebox_cache[nodeName] = meshport.prepare_nodebox(nodeDef.node_box)
end
local facedir = meshport.get_facedir(nodeDef.paramtype2, param2)
local boxes = meshport.collect_boxes(meshport.nodebox_cache[nodeName], nodeDef, param2, facedir, neighbors)
if meshport.nodebox_cache[nodeName].type ~= "connected" then
boxes:rotate_by_facedir(facedir)
end
return boxes:to_faces(nodeDef, pos, facedir)
end
local function create_mesh_node(nodeDef, param2, playerName)
local meshName = nodeDef.mesh
if not meshName then
return
end
if not meshport.mesh_cache[meshName] then
-- Get the paths of all .obj meshes.
if not meshport.obj_paths then
meshport.obj_paths = meshport.get_asset_paths("models", ".obj")
end
if not meshport.obj_paths[meshName] then
if string.lower(string.sub(meshName, -4)) ~= ".obj" then
meshport.log(playerName, "warning", string.format("Mesh %q is not supported.", meshName))
else
meshport.log(playerName, "warning", string.format("Mesh %q could not be found.", meshName))
end
-- Cache a blank faces object so the player isn't warned again.
meshport.mesh_cache[meshName] = meshport.Faces:new()
else
-- TODO: pcall this in case of failure
local meshFaces = meshport.parse_obj(meshport.obj_paths[meshName])
meshFaces:scale(nodeDef.visual_scale)
meshport.mesh_cache[meshName] = meshFaces
end
end
local faces = meshport.mesh_cache[meshName]:copy()
local facedir = meshport.get_facedir(nodeDef.paramtype2, param2)
faces:rotate_by_facedir(facedir)
local rotation = meshport.get_degrotate(nodeDef.paramtype2, param2)
faces:rotate_xz_degrees(rotation)
return faces
end
local function create_plantlike_node(pos, param2, nodeDef)
local style = 0
local height = 1.0
local scale = 0.5 * nodeDef.visual_scale
local rotation = meshport.get_degrotate(nodeDef.paramtype2, param2)
local offset = vector.new(0, 0, 0)
local randomOffsetY = false
local faceNum = 0
if nodeDef.paramtype2 == "meshoptions" then
style = param2 % 8
if param2 % 16 >= 8 then -- param2 % 8
-- TODO: Use MT's seed generators
local seed = (pos.x % 0xFF) * 0x100 + (pos.z % 0xFF) + (pos.y % 0xFF) * 0x10000
local rng = PseudoRandom(seed)
offset.x = (rng:next() % 16 / 16) * 0.29 - 0.145
offset.z = (rng:next() % 16 / 16) * 0.29 - 0.145
end
if param2 % 32 >= 16 then -- param2 & 16
scale = scale * 1.41421
end
if param2 % 64 >= 32 then -- param2 & 32
randomOffsetY = true
end
elseif nodeDef.paramtype2 == "leveled" then
height = param2 / 16
end
local function create_plantlike_quad(faceRotation, topOffset, bottomOffset)
local faces = meshport.Faces:new()
local plantHeight = 2.0 * scale * height
local norm = vector.normalize(vector.new(0, bottomOffset - topOffset, plantHeight))
faces:insert_face({
verts = {
vec(-scale, -0.5 + plantHeight, topOffset),
vec( scale, -0.5 + plantHeight, topOffset),
vec( scale, -0.5, bottomOffset),
vec(-scale, -0.5, bottomOffset),
},
tex_coords = {{x = 0, y = height}, {x = 1, y = height}, {x = 1, y = 0}, {x = 0, y = 0}},
vert_norms = {norm, norm, norm, norm},
tile_idx = 0,
use_special_tiles = nodeDef.drawtype == "plantlike_rooted"
})
if randomOffsetY then
local seed = faceNum + (pos.x % 0xFF) * 0x10000 + (pos.z % 0xFF) * 0x100 + (pos.y % 0xFF) * 0x1000000
local yRng = PseudoRandom(seed)
faces:translate(vector.new(0, (yRng:next() % 16) / 16.0 * -0.125, 0))
faceNum = faceNum + 1
end
faces:rotate_xz_degrees(faceRotation + rotation)
return faces
end
local faces = meshport.Faces:new()
if style == 0 then
faces:insert_all(create_plantlike_quad(46, 0, 0))
faces:insert_all(create_plantlike_quad(-44, 0, 0))
elseif style == 1 then
faces:insert_all(create_plantlike_quad(91, 0, 0))
faces:insert_all(create_plantlike_quad(1, 0, 0))
elseif style == 2 then
faces:insert_all(create_plantlike_quad(121, 0, 0))
faces:insert_all(create_plantlike_quad(241, 0, 0))
faces:insert_all(create_plantlike_quad(1, 0, 0))
elseif style == 3 then
faces:insert_all(create_plantlike_quad(1, 0.25, 0.25))
faces:insert_all(create_plantlike_quad(91, 0.25, 0.25))
faces:insert_all(create_plantlike_quad(181, 0.25, 0.25))
faces:insert_all(create_plantlike_quad(271, 0.25, 0.25))
elseif style == 4 then
faces:insert_all(create_plantlike_quad(1, -0.5, 0))
faces:insert_all(create_plantlike_quad(91, -0.5, 0))
faces:insert_all(create_plantlike_quad(181, -0.5, 0))
faces:insert_all(create_plantlike_quad(271, -0.5, 0))
end
faces:translate(offset)
return faces
end
local function create_node(idx, area, vContent, vParam2, playerName)
if vContent[idx] == minetest.CONTENT_AIR
or vContent[idx] == minetest.CONTENT_IGNORE
or vContent[idx] == minetest.CONTENT_UNKNOWN then -- TODO: Export unknown nodes?
return
end
local nodeDef = meshport.get_def_from_id(vContent[idx])
if nodeDef.drawtype == "airlike" then
return
end
local pos = area:position(idx)
local nodeDrawtype = meshport.get_aliased_drawtype(nodeDef.drawtype)
local neighbors, faces
if CUBIC_FACE_PRIORITY[nodeDrawtype] or nodeDrawtype == "nodebox" then
neighbors = meshport.get_node_neighbors(vContent, area, idx)
end
if (CUBIC_FACE_PRIORITY[nodeDrawtype] or 0) >= 3 then -- liquid, normal, plantlike_rooted
faces = create_cubic_node(pos, vContent[idx], vParam2[idx], nodeDef, nodeDrawtype, neighbors)
if nodeDrawtype == "plantlike_rooted" then
local plantPos = vector.add(pos, vector.new(0, 1, 0))
local plantFaces = create_plantlike_node(nodeDef, plantPos, vParam2[idx])
plantFaces:translate(vector.new(0, 1, 0))
faces:insert_all(plantFaces)
end
elseif CUBIC_FACE_PRIORITY[nodeDrawtype] then -- Any other cubic nodes (allfaces, glasslike)
faces = create_special_cubic_node(pos, vContent[idx], nodeDef, nodeDrawtype, neighbors)
elseif nodeDrawtype == "glasslike_framed" then
faces = create_glasslike_framed_node(pos, vParam2[idx], nodeDef, area, vContent)
elseif nodeDrawtype == "flowingliquid" then
faces = create_flowing_liquid_node(pos, nodeDef, area, vContent, vParam2)
elseif nodeDrawtype == "nodebox" then
faces = create_nodebox_node(pos, vContent[idx], vParam2[idx], neighbors)
elseif nodeDrawtype == "mesh" then
faces = create_mesh_node(nodeDef, vParam2[idx], playerName)
elseif nodeDrawtype == "plantlike" then
faces = create_plantlike_node(pos, vParam2[idx], nodeDef)
end
if not faces then
return
end
faces:apply_tiles(nodeDef)
return faces
end
local function initialize_resources()
meshport.texture_paths = meshport.get_asset_paths("textures")
meshport.texture_dimension_cache = {}
-- meshport.obj_paths is only loaded if needed
meshport.nodebox_cache = {}
meshport.mesh_cache = {}
end
local function cleanup_resources()
meshport.texture_paths = nil
meshport.texture_dimension_cache = nil
meshport.obj_paths = nil
meshport.nodebox_cache = nil
meshport.mesh_cache = nil
end
function meshport.create_mesh(playerName, p1, p2, path)
meshport.log(playerName, "info", "Generating mesh...")
initialize_resources()
p1, p2 = vector.sort(p1, p2)
local vm = minetest.get_voxel_manip()
-- Add one node of padding to area so we can read neighbor blocks.
local vp1, vp2 = vm:read_from_map(vector.subtract(p1, 1), vector.add(p2, 1))
local vContent = vm:get_data()
local vParam2 = vm:get_param2_data()
-- Create a VoxelArea for converting from flat array indices to position vectors.
local vArea = VoxelArea:new{MinEdge = vp1, MaxEdge = vp2}
local meshOrigin = vector.subtract(p1, 0.5)
local mesh = meshport.Mesh:new()
-- Loop through all positions in the desired area.
for idx in vArea:iterp(p1, p2) do
-- Generate a mesh for the node.
local faces = create_node(idx, vArea, vContent, vParam2, playerName)
if faces then
-- Move the node to its proper position.
faces:translate(vector.subtract(vArea:position(idx), meshOrigin))
-- Add faces to our final mesh.
mesh:insert_faces(faces)
end
end
minetest.mkdir(path)
mesh:write_obj(path)
mesh:write_mtl(path, playerName)
cleanup_resources()
meshport.log(playerName, "info", "Finished. Saved to " .. path)
end