222 lines
8.8 KiB
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
|