voxelizer/main.lua

222 lines
8.8 KiB
Lua

local vector = modlib.vector
function get_media(name)
local path = minetest.get_worldpath() .. "/media/" .. name
if not modlib.file.exists(path) then
path = default_media_path .. "/" .. name
end
return path
end
function get_obj_bounding_box(vertexes)
local min={math.huge, math.huge, math.huge}
local max={-math.huge, -math.huge, -math.huge}
for _, vertex in pairs(vertexes) do
for i=1, 3 do
if vertex[i] < min[i] then
min[i]=vertex[i]
elseif vertex[i] > max[i] then
max[i]=vertex[i]
end
end
end
return min, max
end
function get_voxel_area(min, max, vm)
local vox_min, vox_max = {}, {}
for i=1, 3 do
if min[i] < 0 then vox_min[i]=math.floor(min[i]) else vox_min[i]=math.ceil(min[i]) end
if max[i] < 0 then vox_max[i]=math.floor(max[i]) else vox_max[i]=math.ceil(max[i]) end
end -- Floor/ceil min/max for VoxelArea
-- TODO ensure this margin is needed
vox_min, vox_max = vector.to_minetest(vector.subtract(vox_min, {16,16,16})), vector.to_minetest(vector.add(vox_max, {16,16,16}))
local c1, c2 = vm:read_from_map(vox_min, vox_max)
local area = VoxelArea:new{MinEdge=c1, MaxEdge=c2}
return area
end
local steps = 6
local min_amount = 0.1
function place_obj(params)
local path_to_obj, path_to_texture, path_to_nodemap, pos1, pos2, scale = params.model, params.texture, params.nodemap, params.pos1, params.pos2, params.scale
local steps = params.precision or steps
local min_amount = (params.min_amount or min_amount) * 255 * steps * steps
local obj_content = modlib.file.read(path_to_obj)
if not obj_content then
return ("OBJ doesn't exist."):format(path_to_obj)
end
if not modlib.file.exists(path_to_texture) then
return ("Texture doesn't exist."):format(path_to_texture)
end
local nodemap_content=modlib.file.read(path_to_nodemap)
if not nodemap_content then
return ("Nodemap doesn't exist."):format(path_to_nodemap)
end
local nodemap = read_node_map(nodemap_content)
local colors = modlib.table.keys(nodemap)
modlib.table.map(colors, rgb_number_to_table)
local closest_color_finder = closest_color_finder(colors)
local texture = read_texture(path_to_texture)
if params.dithering then
texture = dither(texture, closest_color_finder, params.dithering)
end
local filtering
if params.filtering == "bilinear" then
filtering = function(pos_uv) return bilinear_filtering(texture, pos_uv) end
else -- default : nearest
filtering = function(pos_uv) return nearest_filtering(texture, pos_uv) end
end
local triangle_consumer_factory, transform, min, max, area, nodes
nodes={} -- VoxelArea index -> colors
local vm = minetest.get_voxel_manip()
local data
triangle_consumer_factory=function(vertexes)
min, max = get_obj_bounding_box(vertexes)
if pos2 then -- do something with vertexes
-- transforms a vector : OBJ space -> MT space from pos1 to pos2
-- steps :
-- 0. translate to 0
-- 1. normalize it (squash/stretch it to a 1x1x1 cube)
-- 2. stretch it to pos1 - pos2 thingy
-- 3. translate to pos1
local mt_space = vector.subtract(pos2, pos1)
local obj_space = vector.subtract(max, min)
local transform_vec = vector.divide(mt_space, obj_space)
function transform(v)
local vec = vector.subtract(v, min) -- translate to 0
local res = vector.multiply(vec, transform_vec)
res = vector.add(res, pos1)
return res
end
elseif scale then
function transform(v)
local res = vector.add(vector.multiply_scalar(v, scale), pos1)
return res
end
else
function transform(v)
local res = vector.add(v, pos1)
return res
end
end
modlib.table.map(vertexes, transform) -- Transforming the vertices to MT space
area = get_voxel_area(transform(min), transform(max), vm)
data = vm:get_data()
local is_protected = function(pos) return true end
if params.playername and not params.protection_bypass then
is_protected = function(pos) return minetest.is_protected(pos, params.playername) ~= true end
end
local is_mergeable = function(index) return true end
if params.merge_mode == "add" then
is_mergeable = function(index)
local d = data[index]
if d == minetest.CONTENT_AIR or d == minetest.CONTENT_IGNORE or d == minetest.CONTENT_UNKNOWN then
return true
end
return false
end
elseif params.merge_mode == "intersection" then
is_mergeable = function(index)
local d = data[index]
if d == minetest.CONTENT_AIR or d == minetest.CONTENT_IGNORE then
return false
end
return true
end
end
local merge_at = function(pos, index) return is_protected(pos) and is_mergeable(index) end
return function(points, uvs)
local b1=vector.subtract(points[2], points[1]) -- First basis vector, vertices
local len1=vector.length(b1)
local b2=vector.subtract(points[3], points[1]) -- Second basis vector, vertices
local len2=vector.length(b2)
local u1=vector.subtract(uvs[2], uvs[1]) -- First basis vector, UVs
local u2=vector.subtract(uvs[3], uvs[1]) -- Second basis vector, UVs
for l1=0,1,1/(len1*steps) do -- Lambda 1 - scalar of first basis
for l2=0,1,1/(len2*steps) do -- Lambda 2 - 2nd one
if l1+l2 <= 1 then -- On triangle
local res = vector.add(vector.multiply_scalar(b1, l1), vector.multiply_scalar(b2, l2))
local pos = vector.add(points[1], res)
local floor_pos = vector.to_minetest(vector.floor(pos))
local index = area:indexp(floor_pos)
if merge_at(floor_pos, index) then
nodes[index] = nodes[index] or {amount=0}
nodes[index].amount = nodes[index].amount + 1
-- Now finding the same coord on texture
local pos_uv = vector.add(uvs[1], vector.add(vector.multiply_scalar(u1, l1), vector.multiply_scalar(u2, l2)))
local color_uv = get_texture_color_at(texture, math.floor(pos_uv[1]*(texture.width-0.0000001)), math.floor((1-pos_uv[2])*(texture.height-0.0000001)))
nodes[index][color_uv] = ((nodes[index][color_uv]) and (nodes[index][color_uv]+1)) or 1
end
end
end
end
end
end
local get_color
if params.color_choosing == "best" then
function get_color(node)
local best_color = -math.huge
local best_amount = -math.huge
for color, amount in pairs(node) do
local color = rgba_number_to_table(color)
if amount >= best_amount then
best_color = color
best_amount = amount
end
end
return best_color, best_amount
end
else -- average
function get_color(node)
local average_color = {0, 0, 0, 0}
local sumcount = 0
for color, count in pairs(node) do
sumcount = sumcount + count
local color = rgba_number_to_table(color)
average_color = vector.add(average_color, vector.multiply_scalar(color, count))
end
average_color = vector.divide_scalar(average_color, sumcount)
return average_color
end
end
read_obj(obj_content, triangle_consumer_factory)
for index, node in pairs(nodes) do
local amount = node.amount
node.amount = nil
if params.weighed or true then
for c, v in pairs(node) do
local k = rgba_number_to_table(c)
node[c] = v * k[1]
end
end
local best_color = get_color(node)
if best_color then
if best_color[1]*amount >= min_amount then
table.remove(best_color, 1)
local closest_color = closest_color_finder(best_color)
closest_color = rgb_table_to_number(closest_color)
data[index] = nodemap[closest_color]
end
end
end
vm:set_data(data)
vm:calc_lighting()
vm:update_liquids()
vm:write_to_map()
end