2018-05-15 15:31:13 -07:00
wesh = {
name = " wesh " ,
modpath = minetest.get_modpath ( minetest.get_current_modname ( ) ) ,
vt_size = 72 ,
2018-06-07 08:36:39 -07:00
player_canvas = { } ,
forms = { } ,
2018-05-15 15:31:13 -07:00
}
2018-06-07 08:36:39 -07:00
local smartfs = dofile ( wesh.modpath .. " /smartfs.lua " )
wesh.forms . capture = smartfs.create ( " wesh.forms.capture " , function ( state )
state : size ( 6 , 6 )
local meshname_field = state : field ( 0.5 , 0.5 , 4 , 1 , " meshname " , " Enter the name for your mesh " )
meshname_field : onKeyEnter ( wesh.mesh_capture_confirmed )
meshname_field : setCloseOnEnter ( false )
local capture_button = state : button ( 4 , 0.2 , 2 , 1 , " capture " , " Capture " )
capture_button : click ( wesh.mesh_capture_confirmed )
-- capture_button:setClose(true)
local cancel_button = state : button ( 4 , 5.2 , 2 , 1 , " cancel " , " Cancel " )
cancel_button : setClose ( true )
state : checkbox ( 0.5 , 1 , " generate_matrix " , " Generate backup matrix " )
state : label ( 0.5 , 2 , " label_variants " , " Select one or more variants: " )
local y = 2.5
for name , texture in pairs ( wesh.variants ) do
local chk = state : checkbox ( 0.5 , y , " variant_ " .. name , name )
chk : setValue ( true )
y = y + 0.5
end
end )
2018-05-15 15:31:13 -07:00
-- ========================================================================
-- initialization functions
-- ========================================================================
function wesh . _init ( )
wesh.temp_path = minetest.get_worldpath ( ) .. " /mod_storage/ " .. wesh.name
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
wesh._init_vertex_textures ( )
wesh._init_colors ( )
wesh._init_geometry ( )
2018-06-06 07:56:23 -07:00
wesh._init_variants ( )
2018-05-15 15:31:13 -07:00
wesh._move_temp_files ( )
wesh._load_mod_meshes ( )
2018-06-05 03:23:17 -07:00
wesh._register_canvas_nodes ( )
2018-05-15 15:31:13 -07:00
end
2018-06-05 03:23:17 -07:00
function wesh . _register_canvas_nodes ( )
2018-05-15 15:31:13 -07:00
2018-06-05 13:01:07 -07:00
local function register_canvas ( index , size , inner )
2018-05-16 14:48:15 -07:00
minetest.register_craft ( {
output = " wesh:canvas " .. size ,
recipe = {
{ " group:wool " , " group:wool " , " group:wool " } ,
2018-06-05 04:05:56 -07:00
{ " group:wool " , inner , " group:wool " } ,
2018-05-16 14:48:15 -07:00
{ " group:wool " , " group:wool " , " group:wool " } ,
}
} )
minetest.register_node ( " wesh:canvas " .. size , {
drawtype = " mesh " ,
mesh = " zzz_canvas " .. size .. " .obj " ,
2018-06-05 13:01:07 -07:00
inventory_image = " canvas_inventory.png^[verticalframe:6: " .. ( index - 1 ) .. " .png " ,
2018-05-16 14:48:15 -07:00
tiles = { " canvas.png " } ,
paramtype2 = " facedir " ,
on_rightclick = wesh.canvas_interaction ,
description = " Woolen Mesh Canvas - Size " .. size ,
walkable = true ,
2018-06-05 13:13:29 -07:00
groups = { snappy = 2 , choppy = 2 , oddly_breakable_by_hand = 3 } ,
2018-05-16 14:48:15 -07:00
} )
end
2018-06-05 04:05:56 -07:00
local canvas_sizes = {
2018-06-05 13:01:07 -07:00
{ " 02 " , " default:steel_ingot " } ,
{ " 04 " , " default:copper_ingot " } ,
{ " 08 " , " default:tin_ingot " } ,
{ " 16 " , " default:bronze_ingot " } ,
{ " 32 " , " default:gold_ingot " } ,
{ " 64 " , " default:diamond " } ,
2018-06-05 04:05:56 -07:00
}
wesh.valid_canvas_sizes = { }
2018-06-05 13:01:07 -07:00
for index , canvas_data in pairs ( canvas_sizes ) do
local size = canvas_data [ 1 ]
local inner = canvas_data [ 2 ]
2018-06-05 04:05:56 -07:00
wesh.valid_canvas_sizes [ tonumber ( size ) ] = true
2018-06-05 13:01:07 -07:00
register_canvas ( index , size , inner )
2018-06-05 04:05:56 -07:00
end
2018-05-16 14:48:15 -07:00
minetest.register_alias ( " wesh:canvas " , " wesh:canvas16 " )
2018-05-15 15:31:13 -07:00
end
function wesh . _init_vertex_textures ( )
2018-06-05 03:23:17 -07:00
-- creates a 4x4 grid of UV mappings, each with a margin of one pixel
-- will be used by the .OBJ file generator
2018-05-15 15:31:13 -07:00
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
vt = vt .. " vt " .. ( ( x + offset ) * mult ) .. " " .. ( ( y + offset ) * mult ) .. " \n " -- top right
vt = vt .. " vt " .. ( ( x + offset ) * mult ) .. " " .. ( ( y - offset ) * mult ) .. " \n " -- bottom right
vt = vt .. " vt " .. ( ( x - offset ) * mult ) .. " " .. ( ( y - offset ) * mult ) .. " \n " -- bottom left
vt = vt .. " vt " .. ( ( x - offset ) * mult ) .. " " .. ( ( y + offset ) * mult ) .. " \n " -- top left
end
end
wesh.vertex_textures = vt
end
function wesh . _init_colors ( )
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-06 11:19:49 -07:00
local colors_filename = " nodecolors.conf "
local default_colors_filename = " default. " .. colors_filename
local full_colors_filename = wesh.modpath .. " / " .. colors_filename
local full_default_colors_filename = wesh.modpath .. " / " .. default_colors_filename
local file = io.open ( full_colors_filename , " rb " )
2018-05-17 06:46:11 -07:00
if not file then
2018-06-06 11:19:49 -07:00
minetest.debug ( " [wesh] Copying " .. default_colors_filename .. " to " .. colors_filename )
local success , err = wesh.copy_file ( full_default_colors_filename , full_colors_filename )
2018-06-06 03:57:22 -07:00
if not success then
minetest.debug ( " [wesh] " .. err )
return
end
2018-06-06 11:19:49 -07:00
file = io.open ( full_colors_filename , " rb " )
2018-06-06 03:57:22 -07:00
if not file then
2018-06-06 11:19:49 -07:00
minetest.debug ( " [wesh] Unable to load " .. colors_filename .. " file from mod folder " )
2018-06-06 03:57:22 -07:00
return
end
2018-05-15 15:31:13 -07:00
end
2018-05-17 06:46:11 -07:00
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
local parts = line : gsub ( " %s+ " , " " ) : split ( " = " )
if # parts == 2 then
wesh.nodename_to_color [ parts [ 1 ] ] = parts [ 2 ]
end
end
file : close ( )
2018-05-15 15:31:13 -07:00
end
function wesh . _init_geometry ( )
-- 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
}
-- helper mapper for transformation functions
-- only upright canvases supported
wesh._transfunc = {
-- facedir 0, +Y, no rotation
function ( p ) return p end ,
-- facedir 1, +Y, 90 deg
function ( p ) p.x , p.z = p.z , - p.x return p end ,
-- facedir 2, +Y, 180 deg
function ( p ) p.x , p.z = - p.x , - p.z return p end ,
-- facedir 3, +Y, 270 deg
function ( p ) p.x , p.z = - p.z , p.x return p end ,
}
end
2018-05-16 14:48:15 -07:00
function wesh . _reset_geometry ( canv_size )
2018-05-15 15:31:13 -07:00
wesh.matrix = { }
wesh.vertices = { }
wesh.vertices_indices = { }
wesh.faces = { }
2018-05-16 14:48:15 -07:00
local function reset ( p )
2018-05-15 15:31:13 -07:00
if not wesh.matrix [ p.x ] then wesh.matrix [ p.x ] = { } end
if not wesh.matrix [ p.x ] [ p.y ] then wesh.matrix [ p.x ] [ p.y ] = { } end
wesh.matrix [ p.x ] [ p.y ] [ p.z ] = " air "
2018-05-16 14:48:15 -07:00
end
wesh.traverse_matrix ( reset , canv_size )
2018-05-15 15:31:13 -07:00
end
2018-06-06 07:56:23 -07:00
function wesh . _init_variants ( )
local variants_filename = " nodevariants.lua "
2018-06-06 11:19:49 -07:00
local default_variants_filename = " default. " .. variants_filename
local full_variants_filename = wesh.modpath .. " / " .. variants_filename
local full_default_variants_filename = wesh.modpath .. " / " .. default_variants_filename
local file = io.open ( full_variants_filename , " rb " )
2018-06-06 07:56:23 -07:00
if not file then
2018-06-06 11:19:49 -07:00
minetest.debug ( " [wesh] Copying " .. default_variants_filename .. " to " .. variants_filename )
local success , err = wesh.copy_file ( full_default_variants_filename , full_variants_filename )
2018-06-06 07:56:23 -07:00
if not success then
minetest.debug ( " [wesh] " .. err )
return
end
2018-06-06 11:19:49 -07:00
file = io.open ( full_variants_filename , " rb " )
2018-06-06 07:56:23 -07:00
if not file then
minetest.debug ( " [wesh] Unable to load " .. variants_filename .. " file from mod folder " )
return
end
end
2018-06-07 13:06:39 -07:00
local custom_variants = minetest.deserialize ( file : read ( " *all " ) )
wesh.variants = {
plain = " plain-16.png " ,
}
-- 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-06-06 07:56:23 -07:00
file : close ( )
end
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-05-15 15:31:13 -07:00
wesh.player_canvas [ clicker : get_player_name ( ) ] = { pos = clicked_pos , facedir = node.param2 } ;
2018-06-07 08:36:39 -07:00
wesh.forms . capture : show ( clicker : get_player_name ( ) )
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
local canvas = wesh.player_canvas [ playername ]
canvas.generate_matrix = state : get ( " generate_matrix " ) : getValue ( )
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
canvas.node = minetest.get_node_or_nil ( canvas.pos )
if not canvas.node then return end
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-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
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-02 08:58:57 -07:00
wesh._reset_geometry ( canvas.size )
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-02 19:43:01 -07:00
-- generate secondary boundaries
wesh.generate_secondary_boundaries ( canvas )
2018-05-15 15:31:13 -07:00
-- generate faces according to voxels
2018-06-02 08:58:57 -07:00
wesh.traverse_matrix ( wesh.voxel_to_faces , canvas.size , canvas )
2018-05-15 15:31:13 -07:00
-- this will be the actual content of the .obj file
local vt_section = wesh.vertex_textures
local v_section = wesh.vertices_to_string ( )
2018-05-16 05:42:03 -07:00
local vn_section = wesh.normals_to_string ( )
2018-05-15 15:31:13 -07:00
local f_section = table.concat ( wesh.faces , " \n " )
2018-05-16 05:42:03 -07:00
local meshdata = vt_section .. v_section .. vn_section .. f_section
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
-- ========================================================================
-- mesh management helpers
-- ========================================================================
2018-06-07 08:36:39 -07:00
function wesh . save_mesh_to_file ( obj_filename , meshdata , description , playername , canvas )
2018-05-15 15:31:13 -07:00
-- save .obj file
local full_filename = wesh.temp_path .. " / " .. obj_filename
local file , errmsg = io.open ( full_filename , " wb " )
if not file then
2018-06-07 08:36:39 -07:00
wesh.notify ( playername , " Unable to write to file ' " .. obj_filename .. " ' from ' " .. wesh.temp_path .. " ' - error: " .. errmsg )
return false
2018-05-15 15:31:13 -07:00
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
2018-06-07 08:36:39 -07:00
wesh.notify ( playername , " Unable to write to file ' " .. data_filename .. " ' from ' " .. wesh.temp_path .. " ' - error: " .. errmsg )
return false
2018-05-15 15:31:13 -07:00
end
2018-06-02 08:58:57 -07:00
file : write ( wesh.prepare_data_file ( description , canvas ) )
2018-05-15 15:31:13 -07:00
file : close ( )
2018-06-07 08:36:39 -07:00
if canvas.generate_matrix then
-- save .matrix.dat file
local matrix_data_filename = obj_filename .. " .matrix.dat "
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
file : write ( minetest.serialize ( wesh.matrix ) )
file : close ( )
2018-05-15 15:31:13 -07:00
end
2018-06-07 08:36:39 -07:00
wesh.notify ( playername , " Mesh saved to ' " .. obj_filename .. " ' in ' " .. wesh.temp_path .. " ', reload the world to move them to the mod folder and enable them " )
return true
2018-05-15 15:31:13 -07:00
end
2018-06-02 08:58:57 -07:00
function wesh . prepare_data_file ( description , canvas )
2018-06-06 07:56:23 -07:00
local boxes = { }
wesh.merge_collision_boxes ( canvas )
2018-06-02 19:43:01 -07:00
for _ , box in ipairs ( canvas.boxes ) do
2018-06-06 07:56:23 -07:00
table.insert ( boxes , wesh.box_to_collision_box ( box , canvas.size ) )
end
2018-06-07 08:36:39 -07:00
2018-06-06 07:56:23 -07:00
local data = {
description = description ,
2018-06-07 08:36:39 -07:00
variants = canvas.chosen_variants ,
2018-06-06 07:56:23 -07:00
collision_box = {
type = " fixed " ,
fixed = boxes ,
2018-06-02 19:43:01 -07:00
}
2018-06-06 07:56:23 -07:00
}
return wesh.serialize ( data , 2 )
2018-06-02 19:43:01 -07:00
end
2018-06-03 05:41:32 -07:00
function wesh . get_temp_files ( )
return minetest.get_dir_list ( wesh.temp_path , false )
end
function wesh . get_stored_files ( )
return minetest.get_dir_list ( wesh.modpath .. " /models " , false )
end
function wesh . get_all_files ( )
local all = wesh.get_temp_files ( )
for _ , entry in pairs ( wesh.get_stored_files ( ) ) do
table.insert ( all , entry )
end
return all
end
2018-05-15 15:31:13 -07:00
function wesh . _move_temp_files ( )
local meshes = wesh.get_temp_files ( )
for _ , filename in ipairs ( meshes ) do
os.rename ( wesh.temp_path .. " / " .. filename , wesh.modpath .. " /models/ " .. filename )
end
end
function wesh . _load_mod_meshes ( )
local meshes = wesh.get_stored_files ( )
for _ , filename in ipairs ( meshes ) do
if filename : match ( " ^ " .. wesh.gen_prefix .. " .-%.obj$ " ) then
wesh._load_mesh ( filename )
end
end
end
function wesh . _load_mesh ( obj_filename )
local full_data_filename = wesh.modpath .. " /models/ " .. obj_filename .. " .dat "
local file = io.open ( full_data_filename , " rb " )
local data = { }
if file then
data = minetest.deserialize ( file : read ( " *all " ) ) or { }
file : close ( )
end
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
local nodename = obj_filename : gsub ( " [^%w]+ " , " _ " ) : gsub ( " _obj " , " " )
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
minetest.register_node ( " wesh: " .. nodename .. " _ " .. variant , props )
2018-05-15 15:31:13 -07:00
end
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 ] = { }
wesh.traverse_matrix ( wesh.update_secondary_collision_box , boundary , canvas.boxes [ index ] )
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
function wesh . update_secondary_collision_box ( rel_pos , box )
-- let the box shrink only if the subvoxel isn't empty
if wesh.get_voxel_color ( rel_pos ) ~= " air " then
wesh.update_collision_box ( rel_pos , box )
end
end
2018-05-15 15:31:13 -07:00
-- ========================================================================
-- mesh generation helpers
-- ========================================================================
2018-05-16 14:48:15 -07:00
function wesh . construct_face ( rel_pos , canv_size , texture_vertices , facename , vertices , normal_index )
2018-05-16 05:42:03 -07:00
local normal = wesh.face_normals [ normal_index ]
local hider_pos = vector.add ( rel_pos , normal )
2018-05-16 14:48:15 -07:00
if not wesh.out_of_bounds ( hider_pos , canv_size ) and wesh.get_voxel_color ( hider_pos ) ~= " air " then return end
2018-05-15 15:31:13 -07:00
local face_line = " f "
for i , vertex in ipairs ( vertices ) do
2018-05-16 14:48:15 -07:00
local index = wesh.get_vertex_index ( rel_pos , canv_size , vertex )
2018-05-16 05:42:03 -07:00
face_line = face_line .. index .. " / " .. texture_vertices [ i ] .. " / " .. normal_index .. " "
2018-05-15 15:31:13 -07:00
end
table.insert ( wesh.faces , face_line )
end
2018-06-03 05:41:32 -07:00
function wesh . get_node_color ( pos )
local node = minetest.get_node_or_nil ( pos )
if not node then return " air " end
return wesh.nodename_to_color [ node.name ] or " air "
end
2018-05-15 15:31:13 -07:00
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-03 05:41:32 -07:00
function wesh . get_vertex_index ( pos , canv_size , vertex_number )
-- get integral offset of vertices related to voxel center
local offset = wesh.cube_vertices [ vertex_number ]
-- convert integral offset to real offset
offset = vector.multiply ( offset , 1 / canv_size / 2 )
-- scale voxel center from range 1~canv_size to range 1/canv_size ~ 1
pos = vector.divide ( pos , canv_size )
-- center whole mesh around zero and shift it to make room for offsets
pos = vector.subtract ( pos , 1 / 2 + 1 / canv_size / 2 )
-- 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
if wesh.vertices_indices [ lookup ] then return wesh.vertices_indices [ lookup ] end
-- add the vertex to the list of needed ones
table.insert ( wesh.vertices , pos )
wesh.vertices_indices [ lookup ] = # wesh.vertices
return # wesh.vertices
2018-05-15 15:31:13 -07:00
end
function wesh . get_voxel_color ( pos )
return wesh.matrix [ pos.x ] [ pos.y ] [ pos.z ]
end
2018-05-16 14:48:15 -07:00
function wesh . make_absolute ( canvas_pos , canv_size , facedir , relative_pos )
-- relative positions range from (1, 1, 1) to (canv_size, canv_size, canv_size)
2018-05-15 15:31:13 -07:00
-- shift relative to canvas node within canvas space
local shifted_pos = { }
shifted_pos.y = relative_pos.y - 1
2018-05-16 14:48:15 -07:00
shifted_pos.x = relative_pos.x - ( canv_size / 2 )
2018-05-15 15:31:13 -07:00
shifted_pos.z = relative_pos.z
-- transform according to canvas facedir
local transformed_pos = wesh.transform ( facedir , shifted_pos )
-- translate to absolute according to canvas position
local absolute_pos = vector.add ( canvas_pos , transformed_pos )
return absolute_pos
end
2018-06-03 05:41:32 -07:00
function wesh . set_voxel_color ( pos , color )
if not wesh.color_vertices [ color ] then color = " air " end
wesh.matrix [ pos.x ] [ pos.y ] [ pos.z ] = color
2018-06-02 19:43:01 -07:00
end
2018-06-02 08:58:57 -07:00
function wesh . node_to_voxel ( rel_pos , canvas )
local abs_pos = wesh.make_absolute ( canvas.pos , canvas.size , canvas.facedir , rel_pos )
2018-05-15 15:31:13 -07:00
local color = wesh.get_node_color ( abs_pos )
2018-06-02 08:58:57 -07:00
if color ~= " air " then
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-05-15 15:31:13 -07:00
wesh.set_voxel_color ( rel_pos , color )
end
2018-06-03 05:41:32 -07:00
function wesh . normals_to_string ( )
local output = " "
for i , normal in ipairs ( wesh.face_normals ) do
output = output .. " vn " .. normal.x .. " " .. normal.y .. " " .. normal.z .. " \n "
end
return output
end
2018-06-02 08:58:57 -07:00
function wesh . voxel_to_faces ( rel_pos , canvas )
2018-05-15 15:31:13 -07:00
local color = wesh.get_voxel_color ( rel_pos )
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-02 08:58:57 -07:00
wesh.construct_face ( rel_pos , canvas.size , texture_vertices , facename , facedata.vertices , facedata.normal )
2018-05-15 15:31:13 -07:00
end
end
function wesh . vertices_to_string ( )
local output = " "
for i , vertex in ipairs ( wesh.vertices ) do
output = output .. " v " .. vertex.x .. " " .. vertex.y .. " " .. vertex.z .. " \n "
end
return output
end
-- ========================================================================
-- generic helpers
-- ========================================================================
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-06 03:57:22 -07:00
function wesh . 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
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-05-16 14:48:15 -07:00
function wesh . out_of_bounds ( pos , canv_size )
return pos.x < 1 or pos.x > canv_size
or pos.y < 1 or pos.y > canv_size
or pos.z < 1 or pos.z > canv_size
2018-05-15 15:31:13 -07:00
end
2018-06-03 05:41:32 -07:00
function wesh . transform ( facedir , pos )
return ( wesh._transfunc [ facedir + 1 ] or wesh._transfunc [ 1 ] ) ( pos )
2018-05-15 15:31:13 -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 ;
local output = " { \n "
local post_table = string.rep ( " \t " , depth ) .. " } "
local pre_key = string.rep ( " \t " , depth + 1 )
local post_value = " , \n " ;
if not wrap then
output = " { "
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
output = output .. pre_key .. key .. helper ( v , max_depth , depth + 1 , seen ) .. post_value
end
return output .. post_table
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
wesh._init ( )