Browse Source

Initial commit

master
entuland 4 years ago
commit
20189796fc
  1. 2
      .gitattributes
  2. 92
      README.md
  3. 479
      init.lua
  4. 2
      mod.conf
  5. 3
      models/README.txt
  6. 227
      models/zzz_wesh_canvas.obj
  7. BIN
      screenshots/canvas-build.png
  8. BIN
      screenshots/canvas-empty.png
  9. BIN
      screenshots/canvas-recipe.png
  10. BIN
      screenshots/creative-search.png
  11. BIN
      screenshots/prompt-name.png
  12. BIN
      screenshots/save-confirm.png
  13. BIN
      screenshots/version-plain.png
  14. BIN
      screenshots/version-wool.png
  15. BIN
      textures/wool-16.png
  16. BIN
      textures/wool-72.png

2
.gitattributes vendored

@ -0,0 +1,2 @@
# Auto detect text files and perform LF normalization
* text=auto

92
README.md

@ -0,0 +1,92 @@
# Woolen Meshes (wesh)
An in-game mesh creator for Minetest
Developed and tested on Minetest 0.4.16 - try in other versions at your own risk :)
If you like my contributions you may consider reading http://entuland.com/en/support-entuland
# Recipe for the Canvas block
wesh:canvas
W = any wool block
B = bronze ingot
WWW
WBW
WWW
[Canvas recipe](/screenshots/canvas-recipe.png)
# How to use
Place down a Canvas block, you'll see that it extends beyond its node space marking a 16x16x16 space.
[Empty canvas](/screenshots/canvas-empty.png)
In this space you can build anthing you like by using colored wool blocks.
[Building inside the canvas](/screenshots/canvas-build.png)
Once you're done with your build, go to the Canvas block and right click it: you'll be asked to provide a name for your mesh (you can type any text in there, with uppercases and any symbol).
[Request for name](/screenshots/prompt-name.png)
When you confirm such name (you can cancel it by hitting the ESC key) you'll likely get a confirmation like this:
[Save confirmation](/screenshots/save-confirm.png)
If you confirm the name by hitting ENTER you may not be presented with the above confirmation. It will appear in the chat as well just in case.
Upon saving a few temporary files will be created in the "/modstorage/wesh" subfolder in your world's folder:
- the .obj file will contain a model with your build scaled down 16 times (so that it will occupy only one block)
- the .dat file will contain the original name you have chosen for your mesh, along with some other data (read the section about using custom textures below)
- the .matrix.dat file will contain a serialized version of your build, that may eventually get used to rebuild / reimport it in the game allowing you to alter it (right now you can't import them, so make sure you don't dismantle your build if you want to alter and capture it again)
The above files are saved there only temporarily because mods don't have writing permission in their own folder while the world is running. In order to use your new meshes in the game you need to restart the world.
During world startup the mod will move all the temporary files to the "/models" folder and will load them.
By default, two versions of each mesh will be available:
- plain version: it uses flat colors averaged from the colors of each wool block
[Plain version](/screenshots/version-plain.png)
- wool version: it will use the actual textures used by the wool blocks
[Wool version](/screenshots/version-wool.png)
Such new blocks can't be crafted (I plan to make sort of a crafting station where you put some material and chose the model you want to craft), so you either need to give them to yourself or to find them in the Creative inventory. All such meshes show up if you filter for either "wesh" or "mesh".
[Creative search](/screenshots/creative-search.png)
Looking at the filename (or knowing how the name gets converted) you can also work out the actual nodename to be used in your "/give" or "/giveme" chat command, for example:
- chosen name: "Test One!"
- resulting filename: "mesh_test_one.obj"
- resulting nodename: "wesh:mesh_test_one"
# Using custom textures
In the .dat file of each mesh you'll find something like this:
return {
description = "Your mesh name",
variants = {
wool = "wool-72.png",
plain = "wool-16.png",
},
}
In order to add a new variant simply add a line with your texture name and make sure you save such texture file in the "/textures" folder of the mod. You can also remove the lines you're not interested in and the mod will not generate those variants.
For example, here we remove the "wool" version and add a custom one:
return {
description = "Your mesh name",
variants = {
plain = "wool-16.png",
my_texture_plain_name = "my-texture-file-name.png",
},
}
Have a look at "wool-72.png" to see where each color goes.
A couple considerations:
- the bottom-right transparent area never gets used
- the used texture for each face will actually be a bit smaller (in the "wool-72.png" file the squares are 18 pixels in side, but the texture will only use a 16x16 square inside of it)
- you're not forced to use any particular size for your texture as long as it's square (I guess, let me know if you find any problems)

479
init.lua

@ -0,0 +1,479 @@
wesh = {
name = "wesh",
modpath = minetest.get_modpath(minetest.get_current_modname()),
vt_size = 72,
player_canvas = {}
}
-- ========================================================================
-- 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
error("[" .. wesh.name .. "] Unable to create folder " .. wesh.temp_path)
end
wesh._init_vertex_textures()
wesh._init_colors()
wesh._init_geometry()
wesh._move_temp_files()
wesh._load_mod_meshes()
wesh._main_bindings()
end
function wesh._main_bindings()
minetest.register_on_player_receive_fields(wesh.on_receive_fields)
minetest.register_craft({
output = "wesh:canvas",
recipe = {
{'group:wool', 'group:wool', 'group:wool'},
{'group:wool', 'default:bronze_ingot', 'group:wool'},
{'group:wool', 'group:wool', 'group:wool'},
}
})
minetest.register_node("wesh:canvas", {
drawtype = "mesh",
mesh = "zzz_wesh_canvas.obj",
tiles = { "wool-72.png" },
paramtype2 = "facedir",
on_rightclick = wesh.canvas_interaction,
description = "Woolen Mesh Canvas",
walkable = true,
groups = { dig_immediate = 3},
})
end
-- creates a 4x4 grid of UV mappings, each with a margin of one pixel
function wesh._init_vertex_textures()
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",
}
-- 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()
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
end
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 = {
bottom = { vertices = { 1, 2, 3, 4}, hider_offset = {x = 0, y = -1, z = 0 } },
top = { vertices = { 5, 6, 7, 8}, hider_offset = {x = 0, y = 1, z = 0 } },
back = { vertices = { 1, 5, 8, 2}, hider_offset = {x = 0, y = 0, z = -1 } },
front = { vertices = { 3, 7, 6, 4}, hider_offset = {x = 0, y = 0, z = 1 } },
left = { vertices = { 5, 1, 4, 6}, hider_offset = {x = -1, y = 0, z = 0 } },
right = { vertices = { 2, 8, 7, 3}, hider_offset = {x = 1, y = 0, z = 0 } },
}
-- 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
function wesh._reset_geometry()
wesh.matrix = {}
wesh.vertices = {}
wesh.vertices_indices = {}
wesh.faces = {}
wesh.traverse_matrix(function(p)
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"
end)
end
-- ========================================================================
-- core functions
-- ========================================================================
-- called when the player right-clicks on a canvas block
function wesh.canvas_interaction(clicked_pos, node, clicker)
wesh.player_canvas[clicker:get_player_name()] = { pos = clicked_pos, facedir = node.param2 };
local formspec = "field[meshname;Enter the name for your mesh;]field_close_on_enter[meshname;false]"
minetest.show_formspec(clicker:get_player_name(), "save_mesh", formspec)
end
function wesh.on_receive_fields(player, formname, fields)
if formname == "save_mesh" then
local canvas = wesh.player_canvas[player:get_player_name()]
wesh.save_new_mesh(canvas.pos, canvas.facedir, player, fields.meshname)
end
end
function wesh.save_new_mesh(canvas_pos, facedir, player, description)
-- empty all helper variables
wesh._reset_geometry()
-- read all nodes from the canvas space in the world
-- extract the colors and put them into a helper matrix of color voxels
wesh.traverse_matrix(wesh.node_to_voxel, canvas_pos, facedir)
-- generate faces according to voxels
wesh.traverse_matrix(wesh.voxel_to_faces)
-- this will be the actual content of the .obj file
local vt_section = wesh.vertex_textures
local v_section = wesh.vertices_to_string()
local f_section = table.concat(wesh.faces, "\n")
local meshdata = vt_section .. v_section .. f_section
wesh.save_mesh_to_file(meshdata, description, player)
end
-- ========================================================================
-- mesh management helpers
-- ========================================================================
function wesh.save_mesh_to_file(meshdata, description, player)
local sanitized_meshname = wesh.check_plain(description)
if sanitized_meshname:len() < 3 then
wesh.notify(player, "Mesh name too short, try again (min. 3 chars)")
return
end
local obj_filename = wesh.gen_prefix .. sanitized_meshname .. ".obj"
for _, entry in ipairs(wesh.get_all_files()) do
if entry == obj_filename then
wesh.notify(player, "Mesh name '" .. description .. "' already taken, pick a new one")
return
end
end
-- save .obj file
local full_filename = wesh.temp_path .. "/" .. obj_filename
local file, errmsg = io.open(full_filename, "wb")
if not file then
wesh.notify(player, "Unable to write to file '" .. obj_filename .. "' from '" .. wesh.temp_path .. "' - error: " .. errmsg)
return
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
wesh.notify(player, "Unable to write to file '" .. data_filename .. "' from '" .. wesh.temp_path .. "' - error: " .. errmsg)
return
end
file:write(wesh.prepare_data_file(description))
file:close()
-- 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(player, "Unable to write to file '" .. matrix_data_filename .. "' from '" .. wesh.temp_path .. "' - error: " .. errmsg)
return
end
file:write(minetest.serialize(wesh.matrix))
file:close()
wesh.notify(player, "Mesh saved to '" .. obj_filename .. "' in '" .. wesh.temp_path .. "', reload the world to move them to the mod folder and enable them")
end
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
function wesh.prepare_data_file(description)
local output = [[
return {
description = ]] .. ("%q"):format(description) .. [[,
variants = {
wool = "wool-72.png",
plain = "wool-16.png",
},
}
]]
return output
end
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"
local variants = data.variants or { plain = "wool-16.png" }
local nodename = obj_filename:gsub("[^%w]+", "_"):gsub("_obj", "")
for variant, tile in pairs(variants) do
minetest.register_node("wesh:" .. nodename .. "_" .. variant, {
drawtype = "mesh",
mesh = obj_filename,
paramtype2 = "facedir",
description = description .. " (" .. variant .. ")",
tiles = { tile },
walkable = true,
groups = { dig_immediate = 3 },
})
end
end
-- ========================================================================
-- mesh generation helpers
-- ========================================================================
function wesh.construct_face(rel_pos, texture_vertices, facename, vertices, hider_offset)
local hider_pos = vector.add(rel_pos, hider_offset)
if not wesh.out_of_bounds(hider_pos) and wesh.get_voxel_color(hider_pos) ~= "air" then return end
local face_line = "f "
for i, vertex in ipairs(vertices) do
local index = wesh.get_vertex_index(rel_pos, vertex)
face_line = face_line .. index .. "/" .. texture_vertices[i] .. " "
end
table.insert(wesh.faces, face_line)
end
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
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
end
function wesh.get_voxel_color(pos)
return wesh.matrix[pos.x][pos.y][pos.z]
end
function wesh.get_node_color(pos)
local node = minetest.get_node_or_nil(pos)
if not node then return "trasparent" end
local parts = string.split(node.name, ":")
return parts[#parts]
end
function wesh.make_absolute(canvas_pos, facedir, relative_pos)
-- relative positions range from (1, 1, 1) to (16, 16, 16)
-- shift relative to canvas node within canvas space
local shifted_pos = {}
shifted_pos.y = relative_pos.y - 1
shifted_pos.x = relative_pos.x - 8
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
function wesh.transform(facedir, pos)
return (wesh._transfunc[facedir + 1] or wesh._transfunc[1])(pos)
end
function wesh.node_to_voxel(rel_pos, canvas_pos, facedir)
local abs_pos = wesh.make_absolute(canvas_pos, facedir, rel_pos)
local color = wesh.get_node_color(abs_pos)
wesh.set_voxel_color(rel_pos, color)
end
function wesh.voxel_to_faces(rel_pos)
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)
wesh.construct_face(rel_pos, texture_vertices, facename, facedata.vertices, facedata.hider_offset)
end
end
function wesh.get_vertex_index(pos, 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/32)
-- scale voxel center from range 1~16 to range 1/16 ~ 1
pos = vector.divide(pos, 16)
-- center whole mesh around zero and shift it to make room for offsets
pos = vector.subtract(pos, 1/2 + 1/32)
-- 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
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
-- ========================================================================
function wesh.out_of_bounds(pos)
return pos.x < 1 or pos.x > 16
or pos.y < 1 or pos.y > 16
or pos.z < 1 or pos.z > 16
end
function wesh.check_plain(text)
if type(text) ~= "string" then return "" end
text = text:gsub("^[^%w]*(.-)[^%w]*$", "%1")
return text:gsub("[^%w]+", "_"):lower()
end
function wesh.traverse_matrix(callback, ...)
for x = 1, 16 do
for y = 1, 16 do
for z = 1, 16 do
callback({x = x, y = y, z = z}, ...)
end
end
end
end
function wesh.notify(player, message)
local formspec = "size[10,5]textarea[1,1;8,3;notice;Notice;" .. minetest.formspec_escape(message) .. "]"
.. "button_exit[6,4;3,0;exit;Okay]"
local playername = player:get_player_name()
minetest.show_formspec(playername, "notice_form", formspec)
minetest.chat_send_player(playername, "[" .. wesh.name .. "] " .. message)
end
wesh._init()

2
mod.conf

@ -0,0 +1,2 @@
name = wesh

3
models/README.txt

@ -0,0 +1,3 @@
This folder will contain your generated meshes, if you want to get rid of any of them you need to delete them from here
IMPORTANT: do NOT delete the file "zzz_wesh_canvas.obj"

227
models/zzz_wesh_canvas.obj

@ -0,0 +1,227 @@
v 7.437500 15.437500 16.437500
v 7.437500 15.500000 16.437500
v -8.437500 15.437500 16.437500
v -8.437500 15.500000 16.437500
v 7.437499 15.437500 0.562499
v 7.437499 15.500000 0.562499
v -8.437501 15.437500 0.562499
v -8.437501 15.500000 0.562499
v -8.500000 15.437500 16.500000
v 7.500000 15.437500 16.500000
v 7.500000 15.500000 16.500000
v -8.500000 15.500000 16.500000
v -8.500001 15.437500 0.499999
v -8.500001 15.500000 0.499999
v 7.499999 15.437500 0.499999
v 7.499999 15.500000 0.499999
v 7.437500 -0.500001 16.437498
v 7.437500 -0.437501 16.437498
v -8.437500 -0.500001 16.437498
v -8.437500 -0.437501 16.437498
v 7.437499 -0.499999 0.562500
v 7.437499 -0.437499 0.562500
v -8.437501 -0.499999 0.562500
v -8.437501 -0.437499 0.562500
v -8.500000 -0.500001 16.499998
v 7.500000 -0.500001 16.499998
v 7.500000 -0.437501 16.499998
v -8.500000 -0.437501 16.499998
v -8.500001 -0.499999 0.500000
v -8.500001 -0.437499 0.500000
v 7.499999 -0.499999 0.500000
v 7.499999 -0.437499 0.500000
v -0.500001 -0.499999 -0.500000
v 0.499999 -0.499999 -0.500000
v 0.499999 -0.499999 0.500000
v -0.500001 -0.499999 0.500000
v -0.500000 0.500001 -0.500000
v 0.499999 0.500001 -0.499999
v 0.499999 0.500001 0.500000
v -0.500001 0.500001 0.500000
vt 0.765625 0.421875
vt 0.765625 0.406250
vt 0.984375 0.406250
vt 0.984375 0.421875
vt 0.984375 0.359375
vt 0.984375 0.375000
vt 0.765625 0.375000
vt 0.765625 0.359375
vt 0.984375 0.343750
vt 0.984375 0.359375
vt 0.765625 0.359375
vt 0.765625 0.343750
vt 0.765625 0.390625
vt 0.765625 0.375000
vt 0.984375 0.375000
vt 0.984375 0.390625
vt 0.984375 0.390625
vt 0.765625 0.390625
vt 0.765625 0.375000
vt 0.984375 0.375000
vt 0.765625 0.406250
vt 0.984375 0.406250
vt 0.984375 0.421875
vt 0.765625 0.421875
vt 0.765625 0.390625
vt 0.984375 0.406250
vt 0.765625 0.406250
vt 0.984375 0.484375
vt 0.765625 0.484375
vt 0.765625 0.468750
vt 0.984375 0.468750
vt 0.765625 0.296875
vt 0.984375 0.296875
vt 0.984375 0.312500
vt 0.765625 0.312500
vt 0.984375 0.296875
vt 0.765625 0.296875
vt 0.765625 0.281250
vt 0.984375 0.281250
vt 0.984375 0.281250
vt 0.765625 0.281250
vt 0.765625 0.265625
vt 0.984375 0.265625
vt 0.765625 0.421875
vt 0.984375 0.421875
vt 0.984375 0.437500
vt 0.765625 0.437500
vt 0.765625 0.453125
vt 0.765625 0.437500
vt 0.984375 0.437500
vt 0.984375 0.453125
vt 0.765625 0.437500
vt 0.765625 0.421875
vt 0.984375 0.421875
vt 0.984375 0.437500
vt 0.984375 0.453125
vt 0.984375 0.468750
vt 0.765625 0.468750
vt 0.765625 0.453125
vt 0.984375 0.468750
vt 0.984375 0.484375
vt 0.765625 0.484375
vt 0.765625 0.468750
vt 0.765625 0.437500
vt 0.765625 0.421875
vt 0.984375 0.421875
vt 0.984375 0.437500
vt 0.984375 0.281250
vt 0.984375 0.296875
vt 0.765625 0.296875
vt 0.765625 0.281250
vt 0.984375 0.328125
vt 0.984375 0.343750
vt 0.765625 0.343750
vt 0.765625 0.328125
vt 0.765625 0.312500
vt 0.984375 0.312500
vt 0.984375 0.328125
vt 0.984375 0.359375
vt 0.765625 0.359375
vt 0.765625 0.343750
vt 0.984375 0.343750
vt 0.765625 0.328125
vt 0.984375 0.328125
vt 0.984375 0.343750
vt 0.765625 0.343750
vt 0.765625 0.453125
vt 0.984375 0.453125
vt 0.984375 0.468750
vt 0.765625 0.468750
vt 0.984375 0.375000
vt 0.765625 0.375000
vt 0.765625 0.359375
vt 0.984375 0.359375
vt 0.765625 0.312500
vt 0.984375 0.312500
vt 0.984375 0.281250
vt 0.765625 0.281250
vt 0.765625 0.265625
vt 0.984375 0.265625
vt 0.984375 0.453125
vt 0.765625 0.453125
vt 0.765625 0.437500
vt 0.984375 0.437500
vt 0.765625 0.390625
vt 0.984375 0.390625
vt 0.984375 0.406250
vt 0.765625 0.406250
vt 0.765625 0.421875
vt 0.765625 0.406250
vt 0.984375 0.406250
vt 0.984375 0.421875
vt 0.765625 0.468750
vt 0.765625 0.453125
vt 0.984375 0.453125
vt 0.984375 0.468750
vt 0.984375 0.296875
vt 0.984375 0.312500
vt 0.765625 0.312500
vt 0.765625 0.296875
vt 0.984375 0.437500
vt 0.984375 0.453125
vt 0.765625 0.453125
vt 0.765625 0.437500
vt 0.015625 0.984375
vt 0.015625 0.265625
vt 0.734375 0.265625
vt 0.734375 0.984375
vt 0.015625 0.984375
vt 0.015625 0.265625
vt 0.734375 0.265625
vt 0.734375 0.984375
vt 0.734375 0.265625
vt 0.734375 0.984375
vt 0.015625 0.984375
vt 0.734375 0.265625
vt 0.734375 0.984375
vt 0.015625 0.984375
vt 0.015625 0.984375
vt 0.015625 0.265625
vt 0.015625 0.265625
vt 0.734375 0.265625
vt 0.734375 0.984375
vn 0.0000 0.0000 1.0000
vn -1.0000 0.0000 0.0000
vn -0.0000 0.0000 -1.0000
vn 1.0000 0.0000 -0.0000
vn 0.0000 -1.0000 0.0000
vn 0.0000 1.0000 0.0000
f 10/1/1 11/2/1 12/3/1 9/4/1
f 9/5/2 12/6/2 14/7/2 13/8/2
f 13/9/3 14/10/3 16/11/3 15/12/3
f 15/13/4 16/14/4 11/15/4 10/16/4
f 3/17/5 1/18/5 10/19/5 9/20/5
f 2/21/6 4/22/6 12/23/6 11/24/6
f 7/25/5 3/17/5 9/26/5 13/27/5
f 4/28/6 8/29/6 14/30/6 12/31/6
f 5/32/5 7/33/5 13/34/5 15/35/5
f 8/36/6 6/37/6 16/38/6 14/39/6
f 1/40/5 5/41/5 15/42/5 10/43/5
f 6/44/6 2/45/6 11/46/6 16/47/6
f 5/48/1 6/49/1 8/50/1 7/51/1
f 7/52/4 8/53/4 4/54/4 3/55/4
f 3/56/3 4/57/3 2/58/3 1/59/3
f 1/60/2 2/61/2 6/62/2 5/63/2
f 26/64/1 27/65/1 28/66/1 25/67/1
f 25/68/2 28/69/2 30/70/2 29/71/2
f 29/72/3 30/73/3 32/74/3 31/75/3
f 31/75/4 32/76/4 27/77/4 26/78/4
f 19/79/5 17/80/5 26/81/5 25/82/5
f 18/83/6 20/84/6 28/85/6 27/86/6
f 23/87/5 19/88/5 25/89/5 29/90/5
f 20/91/6 24/92/6 30/93/6 28/94/6
f 21/95/5 23/96/5 29/72/5 31/75/5
f 24/97/6 22/98/6 32/99/6 30/100/6
f 17/101/5 21/102/5 31/103/5 26/104/5
f 22/105/6 18/106/6 27/107/6 32/108/6
f 21/109/1 22/110/1 24/111/1 23/112/1
f 23/113/4 24/114/4 20/115/4 19/116/4
f 19/117/3 20/118/3 18/119/3 17/120/3
f 17/121/2 18/122/2 22/123/2 21/124/2
f 33/125/5 34/126/5 35/127/5 36/128/5
f 37/129/6 40/130/6 39/131/6 38/132/6
f 33/133/3 37/134/3 38/135/3 34/126/3
f 34/126/4 38/136/4 39/137/4 35/138/4
f 35/127/1 39/137/1 40/139/1 36/140/1
f 37/129/2 33/141/2 36/142/2 40/143/2

BIN
screenshots/canvas-build.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 310 KiB

BIN
screenshots/canvas-empty.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 KiB

BIN
screenshots/canvas-recipe.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

BIN
screenshots/creative-search.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 KiB

BIN
screenshots/prompt-name.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 296 KiB

BIN
screenshots/save-confirm.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 316 KiB

BIN
screenshots/version-plain.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 312 KiB

BIN
screenshots/version-wool.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 390 KiB

BIN
textures/wool-16.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 261 B

BIN
textures/wool-72.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Loading…
Cancel
Save