Updates via shellscript
|
@ -0,0 +1,119 @@
|
|||
![Logo](/home/lars/.minetest/mods/voxelizer/logo.png)
|
||||
# Voxelizer (`voxelizer`)
|
||||
Turns 3D models into astonishing voxel builds. Sort of the opposite of [`wesh`](https://github.com/entuland/wesh) and [`meshport`](https://github.com/random-geek/meshport).
|
||||
Another mighty world manipulation tool like [`worldedit`](https://github.com/Uberi/Minetest-WorldEdit). Blazing fast.
|
||||
|
||||
## About
|
||||
|
||||
**Note : Voxelizer needs to be added to the "trusted mods" if "mod security" is enabled.**
|
||||
Depends on [`modlib`](https://github.com/appgurueu/modlib) and [`cmdlib`](https://github.com/appgurueu/cmdlib). IntelliJ IDEA with EmmyLua plugin project.
|
||||
Code licensed under the [NPOSLv3 (Non-Profit Open Software License version 3.0)](https://opensource.org/licenses/NPOSL-3.0) for now.
|
||||
Written by Lars Mueller alias LMD or appguru(eu).
|
||||
|
||||
Media licenses (files in the `media` folder) :
|
||||
* `character.obj` - [CC BY-SA 3.0](https://github.com/minetest/minetest_game/tree/master/mods/player_api/README.txt), by MirceaKitsune & stujones11
|
||||
* `character.png` - [CC BY-SA 3.0](https://github.com/minetest/minetest_game/tree/master/mods/player_api/README.txt), by Jordach
|
||||
* `colors.txt` - [BSD 2-Clause "Simplified" License](https://github.com/minetest/minetestmapper/blob/master/COPYING), by sfan5
|
||||
* `wool.txt` - derived from `colors.txt`, same license
|
||||
|
||||
Logo license (`logo.png`) : derived from `character.png` by Jordach (see above), same license (CC BY-SA 3.0), rendering & modifications by me (LMD)
|
||||
|
||||
|
||||
## Screenshots
|
||||
|
||||
![Screenshot 1](/home/lars/.minetest/mods/voxelizer/screenshot.png)
|
||||
Some Sams, using reduced palettes.
|
||||
![Screenshot 2](/home/lars/.minetest/mods/voxelizer/screenshot_2.png)
|
||||
Another Sam, using the full `colors.txt` palette from Minetestmapper.
|
||||
![Screenshot 3](/home/lars/.minetest/mods/voxelizer/screenshot_3.png)
|
||||
Same Sam, rear view.
|
||||
![Screenshot 4](/home/lars/.minetest/mods/voxelizer/screenshot_4.png)
|
||||
2 mages & Ironmen (thanks to [Jordach](http://minetest.fensta.bplaced.net/#author=Jordach) and [Ginsu23](http://minetest.fensta.bplaced.net/#author=Ginsu23) for the skins)
|
||||
|
||||
The used texture pack was [MTUOTP](https://content.minetest.net/packages/GamingAssociation39/mtuotp/) by Aurailus and GamingAssociation39.
|
||||
Other textures seen are from [Minimal Development Test](https://github.com/minetest/minetest/tree/master/games/minimal) or the [Wool mod](https://github.com/minetest/minetest_game/tree/master/mods/wool) (wool textures by Cisoun).
|
||||
|
||||
## Usage
|
||||
|
||||
All commands are executed with `/vox <command> {params}`. If in need for help, just do `/help vox`.
|
||||
You need the `voxelizer` priv to use any of the Voxelizer commands. Some commands require extra privs.
|
||||
Media - models, textures and nodemaps (color lookups) - is stored in `<worldpath>/media`.
|
||||
If you are unsure about which settings to use, either do some research or *try it and see*.
|
||||
Editing the placed models is recommended; Voxelizer might place a few blocks undesirably, which needs to be fixed by hand.
|
||||
|
||||
### Configuration
|
||||
|
||||
Per-player configuration commands. Configuration remains after shutdown (is persistent).
|
||||
|
||||
* `texture [path]` - set/get the current texture (see [Supported File Formats](#supported-file-formats))
|
||||
* `nodemap [path]` - set/get the current nodemap (see [Supported File Formats](#supported-file-formats))
|
||||
* `dithering [id]` - set/get the current error diffusion dithering algorithms (specify algorithm ID)
|
||||
* `color_choosing [id]` - set/get current color choosing mode (best/average)
|
||||
* `filtering [id]` - set/get current filtering mode (nearest/bilinear)
|
||||
* `placement [id]` - set/get merge modes (specify mode ID)
|
||||
* `model [path]` - set/get the current 3D model (see [Supported File Formats](#supported-file-formats))
|
||||
* `alpha_weighing [enable/disable]` - get/enable/disable weighing colors (see `color_choosing`) by their alpha
|
||||
* `protection_bypass [enable/disable]` - get/enable/disable protection bypass (you need the priv `protection_bypass` to enable it)
|
||||
* `precision [number]` - set/get the current rasterization accuracy (integer). Note that this increases computation time quadratically. Values higher than `10` are not recommended.
|
||||
|
||||
#### Supported file formats
|
||||
|
||||
##### Textures
|
||||
|
||||
All file formats supported by `ImageIO` on your Java setup. You can find them out using the following commands :
|
||||
```bash
|
||||
cd ~/.minetest/mods/voxelizer/production/voxelizer
|
||||
java SupportedTextureFormats
|
||||
```
|
||||
On my system (Java 11), the output was :
|
||||
```
|
||||
The supported image file formats are : JPG, jpg, tiff, bmp, BMP, gif, GIF, WBMP, png, PNG, JPEG, tif, TIF, TIFF, wbmp, jpeg
|
||||
```
|
||||
|
||||
Internally, the `SIF` (`.sif`, "Simple Image File") file format (just gave it some name) is used :
|
||||
* 4 byte header : 2 times a 2 byte unsigned short, first is width, second is height
|
||||
* Followed by uncompressed image data : array of 4 byte tuples, consisting of ARGB unsigned bytes, positions in array are calculated as `x + y * width`
|
||||
|
||||
##### Node Map
|
||||
|
||||
Any valid `minetestmapper-colors.txt` will be accepted by this mod. The format is :
|
||||
|
||||
Multiple lines like `[(<content_id:hex>|<nodename>) <red> <green> <blue>][#<comment>]`
|
||||
|
||||
##### 3D Models
|
||||
|
||||
Only the `.obj` file format is (with certain restrictions) supported. It is recommended to export your models from Blender.
|
||||
|
||||
Restrictions :
|
||||
|
||||
* No free form geometry (`vp`s)
|
||||
* No complex texture coordinates (`vt`s with more than 2 coordinates given), use simple ones
|
||||
* No polygonal faces (`f`s with more than 3 indexes), use triangles
|
||||
* No line (`l`) elements
|
||||
* No material (`.mtl`) file usage, only a single texture
|
||||
* No smooth shading (`s`)
|
||||
* No normals (`vn`)
|
||||
|
||||
All of the above will be ignored whenever possible.
|
||||
|
||||
Export your .obj files with Blender properly by ticking the right options, as seen here :
|
||||
|
||||
![Ticked checkboxes in Blender's "Export OBJ" dialog](http://www.opengl-tutorial.org/assets/images/tuto-7-model-loading/Blender.png)
|
||||
|
||||
So summarized, the following boxes should be ticked :
|
||||
|
||||
- [x] Apply modifiers
|
||||
- [x] Write normals (not required)
|
||||
- [x] Write UVs
|
||||
- [x] Triangulate faces
|
||||
- [x] Objects as OBJ objects
|
||||
|
||||
Everything else should not be ticked.
|
||||
|
||||
### Actions
|
||||
|
||||
* `1`/`2` - set first and second edge position, model will be placed thereafter and positions will be deleted
|
||||
* `place [scale]` - place model with given scale (defaults to `1`)
|
||||
* `download <url> [filename]` - download a file from the internet using a GET request, requires `voxelizer:download` priv additionally.
|
||||
File will be downloaded to `<worldpath>/media/filename`. The URL filename will be taken if `filename` is not specified.
|
||||
**Likely not safe due to the usage of HTTP Requests.**
|
|
@ -0,0 +1,349 @@
|
|||
conf = {max_precision = 15, download = true}
|
||||
local mediapath = minetest.get_modpath("voxelizer").."/media/"
|
||||
defaults = {texture = mediapath.."character.png",
|
||||
model = mediapath.."character.obj",
|
||||
nodemap = mediapath.."colors.txt",
|
||||
placement = 1,
|
||||
dithering = 10,
|
||||
precision = 4,
|
||||
min_density = 0.1}
|
||||
|
||||
minetest.register_privilege("protection_bypass", {
|
||||
description = "Can bypass protection",
|
||||
give_to_admin = true,
|
||||
give_to_singleplayer = true
|
||||
})
|
||||
|
||||
minetest.register_privilege("voxelizer", {
|
||||
description = "Can use the voxelizer mod",
|
||||
give_to_admin = true,
|
||||
give_to_singleplayer = true
|
||||
})
|
||||
|
||||
cmd_ext.register_chatcommand("vox", {
|
||||
description = "Voxelizer",
|
||||
privs = {voxelizer = true},
|
||||
func = function(sendername, params)
|
||||
return false, "Use the commands of the voxelizer mod using /vox <command> {params} - for a list of commands do /help vox."
|
||||
end
|
||||
})
|
||||
|
||||
function get_setting_int(playername, settingname)
|
||||
local setting = minetest.get_player_by_name(playername):get_meta():get_int("voxelizer_"..settingname)
|
||||
return setting ~= 0 and setting
|
||||
end
|
||||
|
||||
function set_setting_int(playername, settingname, value)
|
||||
return minetest.get_player_by_name(playername):get_meta():set_int("voxelizer_"..settingname, value)
|
||||
end
|
||||
|
||||
function get_setting_float(playername, settingname)
|
||||
local setting = minetest.get_player_by_name(playername):get_meta():get_float("voxelizer_"..settingname)
|
||||
return setting ~= 0 and setting
|
||||
end
|
||||
|
||||
function set_setting_float(playername, settingname, value)
|
||||
return minetest.get_player_by_name(playername):get_meta():set_float("voxelizer_"..settingname, value)
|
||||
end
|
||||
|
||||
function get_setting(playername, settingname)
|
||||
local setting = minetest.get_player_by_name(playername):get_meta():get_string("voxelizer_"..settingname)
|
||||
return setting ~= "" and setting
|
||||
end
|
||||
|
||||
function set_setting(playername, settingname, value)
|
||||
return minetest.get_player_by_name(playername):get_meta():set_string("voxelizer_"..settingname, value)
|
||||
end
|
||||
|
||||
function register_setting_command(commandname, values, settingname)
|
||||
settingname = settingname or commandname
|
||||
local root = number_ext.round(math.sqrt(#values))
|
||||
local display_vals = {}
|
||||
for index, val in ipairs(values) do
|
||||
table.insert(display_vals, index..". "..val)
|
||||
end
|
||||
cmd_ext.register_chatcommand("vox "..commandname, {
|
||||
description = "Get and list or set player-specific setting "..settingname,
|
||||
params = "[index]",
|
||||
func = function(sendername, params)
|
||||
if params.index then
|
||||
params.index = tonumber(params.index)
|
||||
if not params.index or params.index < 1 or params.index > #values or params.index % 1 > 0 then
|
||||
return false, "Index out of range : Needs to be an integer between 1 and "..#values
|
||||
end
|
||||
set_setting_int(sendername, settingname, params.index)
|
||||
return true, string.format('Setting "%s" was set to %d - "%s"', settingname, params.index, values[params.index])
|
||||
end
|
||||
local display_vals = {unpack(display_vals)}
|
||||
local setting = get_setting_int(sendername, settingname, params.index) or defaults[settingname]
|
||||
if display_vals[setting] then
|
||||
display_vals[setting] = minetest.get_color_escape_sequence("#FFFF66")..display_vals[setting].." (active)"..minetest.get_color_escape_sequence("#FFFFFF")
|
||||
end
|
||||
local options = {}
|
||||
for i=1, #values, root do
|
||||
local row = {unpack(display_vals, i, i+root-1)}
|
||||
table.insert(options, table.concat(row, ", "))
|
||||
end
|
||||
return nil, table.concat(options, "\n")
|
||||
end
|
||||
})
|
||||
end
|
||||
|
||||
function register_file_command(commandname, settingname)
|
||||
settingname = settingname or commandname
|
||||
cmd_ext.register_chatcommand("vox "..commandname, {
|
||||
description = "Get or set player-specific file "..settingname,
|
||||
params = "[filename]",
|
||||
func = function(sendername, params)
|
||||
if params.filename then
|
||||
local path = get_media(params.filename)
|
||||
if not file_ext.exists(path) then return false, "File doesn't exist" end
|
||||
set_setting(sendername, settingname, params.filename)
|
||||
return true, string.format('File "%s" was set to "%s"', settingname, params.filename)
|
||||
end
|
||||
local value = get_setting(sendername, settingname)
|
||||
if value then
|
||||
return true, string.format('File "%s" is currently set to "%s"', settingname, value)
|
||||
end
|
||||
return true, string.format('File "%s" is currently not set, using default', settingname)
|
||||
end
|
||||
})
|
||||
end
|
||||
|
||||
local algorithm_names = {}
|
||||
for index, matrix in ipairs(dithering_matrices) do
|
||||
algorithm_names[index] = matrix.name
|
||||
end
|
||||
table.insert(algorithm_names, "No preprocessing")
|
||||
|
||||
register_setting_command("dithering", algorithm_names)
|
||||
|
||||
local color_choosing = {"best", "average"}
|
||||
register_setting_command("color_choosing", {"Best", "Average"})
|
||||
|
||||
local merge_mode = {"overwrite", "add", "intersection"}
|
||||
register_setting_command("placement", {"Overwrite", "Add", "Intersection"})
|
||||
|
||||
local filtering = {"nearest", "bilinear"}
|
||||
register_setting_command("filtering", {"Nearest neighbor", "Bilinear"})
|
||||
|
||||
register_file_command("model")
|
||||
register_file_command("texture")
|
||||
register_file_command("nodemap")
|
||||
|
||||
function register_bool_setting_command(name)
|
||||
cmd_ext.register_chatcommand("vox "..name, {
|
||||
description = "Check whether "..name.." is enabled",
|
||||
func = function(sendername)
|
||||
local enabled = get_setting_int(sendername, name) == 1
|
||||
return true, string.format("Setting %s is %s", name, (enabled and "enabled") or "disabled")
|
||||
end
|
||||
})
|
||||
|
||||
cmd_ext.register_chatcommand("vox "..name.." enable", {
|
||||
description = "Enable "..name,
|
||||
privs = {protection_bypass = true},
|
||||
func = function(sendername)
|
||||
set_setting_int(sendername, name, 1)
|
||||
return true, name.." was enabled."
|
||||
end
|
||||
})
|
||||
|
||||
cmd_ext.register_chatcommand("vox "..name.." disable", {
|
||||
description = "Disable "..name,
|
||||
privs = {protection_bypass = true},
|
||||
func = function(sendername)
|
||||
set_setting_int(sendername, name, 2)
|
||||
return true, name.." was disabled."
|
||||
end
|
||||
})
|
||||
end
|
||||
|
||||
register_bool_setting_command("protection_bypass")
|
||||
|
||||
register_bool_setting_command("alpha_weighing")
|
||||
|
||||
local max_precision = conf.max_precision
|
||||
cmd_ext.register_chatcommand("vox precision", {
|
||||
description = "Set/get current precision",
|
||||
params = "[number]",
|
||||
func = function(sendername, params)
|
||||
if params.number then
|
||||
params.number = tonumber(params.number)
|
||||
if params.number % 1 ~= 0 or params.number < 1 or params.number > max_precision then
|
||||
return false, "Number needs to be an integer between 1 and "..max_precision
|
||||
end
|
||||
set_setting_int(sendername, "precision", params.number)
|
||||
return true, "Precision was set to "..params.number
|
||||
end
|
||||
return true, "Precision currently is "..(get_setting_int(sendername, "precision") or defaults.precision)
|
||||
end
|
||||
})
|
||||
|
||||
cmd_ext.register_chatcommand("vox min_density", {
|
||||
description = "Set/get minimum density",
|
||||
params = "[number]",
|
||||
func = function(sendername, params)
|
||||
if params.number then
|
||||
params.number = tonumber(params.number)
|
||||
if params.number < 0 or params.number > 1 then
|
||||
return false, "Number needs to be a floating-point number between 0 and 1"
|
||||
end
|
||||
set_setting_float(sendername, "min_density", params.number)
|
||||
return true, "Precision was set to "..params.number
|
||||
end
|
||||
return true, "Precision currently is "..(get_setting_int(sendername, "min_density") or defaults.min_density)
|
||||
end
|
||||
})
|
||||
|
||||
local function substitute_coords(pos, playername)
|
||||
local player_pos = minetest.get_player_by_name(playername):get_pos()
|
||||
local x, y, z = unpack(pos)
|
||||
local pos = {x or player_pos.x, y or player_pos.y, z or player_pos.z}
|
||||
return pos
|
||||
end
|
||||
|
||||
local function place(sendername, additional)
|
||||
local function setting(name) return get_setting(sendername, name) end
|
||||
local function setting_int(name) return get_setting_int(sendername, name) end
|
||||
local function setting_float(name) return get_setting_float(sendername, name) end
|
||||
local model, texture, nodemap = setting("model"), setting("texture"), setting("nodemap")
|
||||
local params = {
|
||||
playername = sendername,
|
||||
model = (model and get_media(model)) or defaults.model,
|
||||
texture = (texture and get_media(texture)) or defaults.texture,
|
||||
nodemap = (nodemap and get_media(nodemap)) or defaults.nodemap,
|
||||
min_density = setting_float("min_density") or defaults.min_density,
|
||||
precision = setting_int("precision") or defaults.precision,
|
||||
dithering = dithering_matrices[setting_int("dithering")],
|
||||
protection_bypass = setting_int("protection_bypass") == 1,
|
||||
weighed = setting_int("alpha_weighing") == 1,
|
||||
filtering = filtering[setting_int("filtering")],
|
||||
color_choosing = color_choosing[setting_int("color_choosing")],
|
||||
merge_mode = merge_mode[setting_int("placement")],
|
||||
}
|
||||
table_ext.add_all(params, additional)
|
||||
return place_obj(params)
|
||||
end
|
||||
|
||||
cmd_ext.register_chatcommand("vox place", {
|
||||
description = "Place model at position with given size. Missing coordinates will be replaced by player coordinates.",
|
||||
params = "[scale] {position}",
|
||||
func = function(sendername, params)
|
||||
if params.scale then
|
||||
params.scale = tonumber(params.scale)
|
||||
if not params.scale then return false, "Scale needs to be a valid number" end
|
||||
if params.position and #params.position > 3 then return false, "Only 3 coordinates (x, y, z) are allowed" end
|
||||
end
|
||||
local pos = substitute_coords(params.position or {}, sendername)
|
||||
local error = place(sendername, { pos1 = pos, scale = params.scale })
|
||||
if error then return false, "Failed to place model : "..error end
|
||||
return true, "Placed model at "..vector.to_string(pos).." with the scale "..(params.scale or 1)
|
||||
end
|
||||
})
|
||||
|
||||
cmd_ext.register_chatcommand("vox 1", {
|
||||
description = "Set first corner position of model to be placed. Missing coordinates will be replaced by player coordinates.",
|
||||
params = "{position}",
|
||||
func = function(sendername, params)
|
||||
if params.position and #params.position > 3 then return false, "Only 3 coordinates (x, y, z) are allowed" end
|
||||
local pos = substitute_coords(params.position or {}, sendername)
|
||||
set_setting(sendername, "1", minetest.write_json(pos))
|
||||
local old_id = get_setting_int(sendername, "pos1_waypoint")
|
||||
if old_id then minetest.get_player_by_name(sendername):hud_remove(old_id) end
|
||||
local id = minetest.get_player_by_name(sendername):hud_add({hud_elem_type="waypoint",
|
||||
name="Voxelizer Position 1",
|
||||
number=0xFFFF00,
|
||||
world_pos = vector.to_minetest(pos)})
|
||||
minetest.add_particle({
|
||||
pos = vector.to_minetest(pos),
|
||||
velocity = {x=0, y=0, z=0},
|
||||
acceleration = {x=0, y=0, z=0},
|
||||
expirationtime = 6,
|
||||
size = 16,
|
||||
collisiondetection = false,
|
||||
collision_removal = false,
|
||||
object_collision = false,
|
||||
vertical = false,
|
||||
texture = "voxelizer_marker.png",
|
||||
playername = sendername,
|
||||
animation = { type = "vertical_frames", aspect_w = 16, aspect_h = 16, length = 2.0 },
|
||||
glow = 14
|
||||
})
|
||||
set_setting_int(sendername, "pos1_waypoint", id)
|
||||
end
|
||||
})
|
||||
|
||||
cmd_ext.register_chatcommand("vox 2", {
|
||||
description = "Set second corner position of model & place it. Missing coordinates will be replaced by player coordinates.",
|
||||
params = "{position}",
|
||||
func = function(sendername, params)
|
||||
if params.position and #params.position > 3 then return false, "Only 3 coordinates (x, y, z) are allowed" end
|
||||
local pos1, pos2 = minetest.parse_json(get_setting(sendername, "1")), substitute_coords(params.position or {}, sendername)
|
||||
if not pos1 then
|
||||
return false, "First corner position not set. Do \"/vox 1\" first to set it."
|
||||
end
|
||||
local old_id = get_setting_int(sendername, "pos1_waypoint")
|
||||
if old_id then minetest.get_player_by_name(sendername):hud_remove(old_id) end
|
||||
local error = place(sendername, { pos1 = pos1, pos2 = pos2 })
|
||||
if error then return false, "Failed to place model : "..error end
|
||||
return true, "Placed model from "..vector.to_string(pos1).." to "..vector.to_string(pos2)
|
||||
end
|
||||
})
|
||||
|
||||
cmd_ext.register_chatcommand("vox reset", {
|
||||
description = "Reset settings",
|
||||
func = function(sendername)
|
||||
for setting, _ in pairs(defaults) do
|
||||
set_setting(sendername, setting, "")
|
||||
end
|
||||
return true, "Settings resetted."
|
||||
end
|
||||
})
|
||||
|
||||
if conf.download then
|
||||
minetest.register_privilege("voxelizer:download", {
|
||||
description = "Can download files from the internet using the voxelizer mod",
|
||||
give_to_admin = true,
|
||||
give_to_singleplayer = true
|
||||
})
|
||||
|
||||
local errors = {
|
||||
"URL and output path need to be given",
|
||||
"Couldn't create file",
|
||||
"Output file doesn't exist or can't be written",
|
||||
"Couldn't download file",
|
||||
"Couldn't write to file",
|
||||
"Malformed URL"
|
||||
}
|
||||
|
||||
cmd_ext.register_chatcommand("vox download", {
|
||||
description = "Download a file from the internet",
|
||||
params = "<url> [filename]",
|
||||
privs = {["voxelizer:download"]=true},
|
||||
func = function(sendername, params)
|
||||
if not params.filename then
|
||||
local last_slash
|
||||
for i = params.url:len(), 1, -1 do
|
||||
if params.url:sub(i, i) == "/" then
|
||||
last_slash = i
|
||||
break
|
||||
end
|
||||
end
|
||||
params.filename = params.url:sub(last_slash+1)
|
||||
end
|
||||
|
||||
local path = voxelizer.get_media(params.filename)
|
||||
local call = string.format('java -classpath "%s/production" FileDownloader "%s" "%s"', minetest.get_modpath("voxelizer"), params.url, path)
|
||||
print(call)
|
||||
local response_code = os.execute(call)
|
||||
if response_code ~= 0 then
|
||||
local error = errors[response_code]
|
||||
if error then return false, "Download failed : "..error end
|
||||
return false, "Download failed"
|
||||
end
|
||||
|
||||
return true, "Download successful"
|
||||
end
|
||||
})
|
||||
end
|
|
@ -0,0 +1,84 @@
|
|||
local max_linear=10 -- Maximum number of colors for using linear search
|
||||
|
||||
-- Finds the closest color using a binary search like thing - point cloud is split into two (ideally equally sized) clouds multiple times which gives a searchable tree
|
||||
|
||||
-- Builds a k-d tree for 3d points
|
||||
function kd_tree_builder(dim)
|
||||
return function(points, axis)
|
||||
if #points == 1 then
|
||||
return points[1]
|
||||
end
|
||||
axis=(axis or 0)+1
|
||||
table.sort(points, function(a,b) return a[axis] > b[axis] end)
|
||||
local median=math.floor(#points/2)
|
||||
local next_axis=(axis+1)%dim
|
||||
return {
|
||||
axis=axis,
|
||||
pivot=points[median],
|
||||
left=build_kd_tree({unpack(points, 1, median)}, next_axis),
|
||||
right=build_kd_tree({unpack(points, median+1)}, next_axis)
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
build_kd_tree = kd_tree_builder(3)
|
||||
|
||||
distance = function(c1, c2) return math.sqrt(math.pow((c1[1]-c2[1]), 2)+math.pow((c1[2]-c2[2]), 2)+math.pow((c1[3]-c2[3]), 2)) end
|
||||
|
||||
-- Returns a function which gives you the closest color, based on k-d trees
|
||||
function kd_closest_color_finder(colors)
|
||||
local tree = build_kd_tree(colors)
|
||||
return function(color)
|
||||
local min_distance=math.huge
|
||||
local closest_color
|
||||
f=function(tree)
|
||||
local axis=tree.axis
|
||||
if #tree > 0 then -- Subtree is leaf
|
||||
local distance = distance(tree, color)
|
||||
if distance < min_distance then
|
||||
min_distance = distance
|
||||
closest_color = tree
|
||||
end
|
||||
return
|
||||
else
|
||||
local new_tree, other_tree = tree.right, tree.left
|
||||
if color[axis] < tree.pivot[axis] then
|
||||
new_tree, other_tree = tree.left, tree.right
|
||||
end
|
||||
f(other_tree)
|
||||
if tree.pivot then
|
||||
local dist = math.abs(tree.pivot[axis]-color[axis])
|
||||
if dist <= min_distance then
|
||||
f(new_tree)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
f(tree)
|
||||
return closest_color, min_distance
|
||||
end
|
||||
end
|
||||
|
||||
-- returns a function which returns closest color, based on linear search
|
||||
function linear_closest_color_finder(colors)
|
||||
return function(c1)
|
||||
local min=math.huge
|
||||
local clos
|
||||
for _, c2 in pairs(colors) do
|
||||
local dis=distance(c1, c2)
|
||||
if dis < min then
|
||||
min=dis
|
||||
clos=c2
|
||||
end
|
||||
end
|
||||
return clos, min
|
||||
end
|
||||
end
|
||||
|
||||
-- returns a function for finding the closest color, based on length of colors uses either k-d tree or linear search
|
||||
function closest_color_finder(colors)
|
||||
if #colors <= max_linear then
|
||||
return linear_closest_color_finder(colors)
|
||||
end
|
||||
return kd_closest_color_finder(colors)
|
||||
end
|
|
@ -0,0 +1,58 @@
|
|||
local int = function(value) if value % 1 ~= 0 then return "Integer instead of float expected." end end
|
||||
local conf_spec = {
|
||||
type = "table",
|
||||
children = {
|
||||
max_precision = {
|
||||
type = "number",
|
||||
func = function(value) if value % 1 ~= 0 then return "Integer instead of float expected." end end
|
||||
},
|
||||
download = {
|
||||
type = "boolean"
|
||||
},
|
||||
defaults = {
|
||||
type = "table",
|
||||
possible_keys = {
|
||||
model = {type = "string"},
|
||||
texture = {type = "string"},
|
||||
nodemap = {type = "string"}
|
||||
},
|
||||
required_keys = {
|
||||
min_density = {type = "number", range = {0, 1}},
|
||||
precision = {
|
||||
type = "number",
|
||||
range = {1, 100},
|
||||
func = int
|
||||
},
|
||||
dithering = {
|
||||
type = "number",
|
||||
range = {1, 10},
|
||||
func = int
|
||||
},
|
||||
placement = {
|
||||
type = "number",
|
||||
range = {1, 3},
|
||||
func = int
|
||||
},
|
||||
color_choosing = {
|
||||
type = "number",
|
||||
range = {1, 2},
|
||||
func = int
|
||||
},
|
||||
filtering = {
|
||||
type = "number",
|
||||
range = {1, 2},
|
||||
func = int
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
config=conf.import("voxelizer", conf_spec)
|
||||
local mediapath = minetest.get_modpath("voxelizer").."/media/"
|
||||
local fallback_defaults = {texture = mediapath.."character.png", model = mediapath.."character.obj", nodemap = mediapath.."colors.txt"}
|
||||
for key, alt in pairs(fallback_defaults) do
|
||||
if config.defaults[key] == nil then
|
||||
config.defaults[key] = alt
|
||||
end
|
||||
end
|
|
@ -0,0 +1,47 @@
|
|||
# Voxelizer - Configuration
|
||||
|
||||
JSON Configuration : `<worldpath>/config/voxelizer.json`
|
||||
|
||||
Text Logs : `<worldpath>/logs/voxelizer/<date>.txt`
|
||||
|
||||
Explaining document(this, Markdown) : `<modpath/gamepath>/voxelizer/config_help.md`
|
||||
|
||||
Readme : `<modpath/gamepath>/voxelizer/Readme.md`
|
||||
|
||||
Default Configuration : `<modpath/gamepath>/voxelizer/default_config.json`
|
||||
```json
|
||||
{
|
||||
"max_precision" : 15,
|
||||
"download" : true,
|
||||
"defaults" : {
|
||||
"precision" : 4,
|
||||
"min_density" : 0.1,
|
||||
"dithering" : 10,
|
||||
"placement" : 1,
|
||||
"color_choosing" : 1,
|
||||
"filtering" : 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### `max_precision`
|
||||
Integer, maximum settable precision.
|
||||
#### `download`
|
||||
Boolean, whether to enable the `/vox download` chatcommand.
|
||||
#### `defaults`
|
||||
Dictionairy / table, default names assigned to corresponding values. Possible names below.
|
||||
##### `min_density`
|
||||
Float between 0 and 1. Minimum density default.
|
||||
##### `precision`
|
||||
Integer > 1 and < 100. Precision default.
|
||||
##### `dithering`
|
||||
Default dithering algorithm ID (see `/vox dithering`).
|
||||
##### `placement`
|
||||
Default placement mode ID (see `/vox placement`).
|
||||
##### `color_choosing`
|
||||
Default color choosing algorithm ID (see `/vox color_choosing`).
|
||||
##### `filtering`
|
||||
Default filtering algorithm ID (see `/vox filtering`).
|
||||
##### `model` / `texture` / `nodemap`
|
||||
Optional default filenames. Files will be searched in world's media folder.
|
||||
If not given, Voxelizer falls back to default files from mod's media folder.
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"max_precision" : 15,
|
||||
"download" : true,
|
||||
"defaults" : {
|
||||
"precision" : 4,
|
||||
"min_density" : 0.1,
|
||||
"dithering" : 10,
|
||||
"placement" : 1,
|
||||
"color_choosing" : 1,
|
||||
"filtering" : 1
|
||||
}
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
dithering_matrices = {
|
||||
{
|
||||
name = "Floyd-Steinberg",
|
||||
{x_off=1, 7/16},
|
||||
{x_off=-1, 3/16, 5/16, 1/16}
|
||||
},
|
||||
{
|
||||
name = "Jarvis, Judice & Ninke",
|
||||
{x_off = 1, 7/48, 5/48},
|
||||
{x_off = -2, 3/48, 5/48, 7/48, 5/48, 3/48},
|
||||
{x_off = -2, 1/48, 3/48, 5/48, 3/48, 1/48}
|
||||
},
|
||||
{
|
||||
name = "Stucke",
|
||||
{x_off = 1, 8/42, 4/42},
|
||||
{x_off = -2, 2/42, 4/42, 8/42, 4/42, 2/42},
|
||||
{x_off = -2, 1/42, 2/42, 4/42, 2/42, 1/42}
|
||||
},
|
||||
{
|
||||
name = "Atkinson",
|
||||
{x_off=1, 1/8, 1/8},
|
||||
{x_off = -1, 1/8, 1/8, 1/8},
|
||||
{x_off = 0, 1/8}
|
||||
},
|
||||
{
|
||||
name = "Burkes",
|
||||
{x_off = 1, 8/42, 4/42},
|
||||
{x_off = -2, 2/42, 4/42, 8/42, 4/42, 2/42}
|
||||
},
|
||||
{
|
||||
name = "Sierra",
|
||||
{x_off = 1, 4/16, 3/16},
|
||||
{x_off = -2, 1/16, 2/16, 3/16, 2/16, 1/16}
|
||||
},
|
||||
{
|
||||
name = "Sierra Lite",
|
||||
{x_off = 1, 2/4},
|
||||
{x_off = -1, 1/4, 1/4}
|
||||
},
|
||||
{
|
||||
name = "Two row Sierra",
|
||||
{x_off = 1, 5/32, 3/32},
|
||||
{x_off = -2, 2/32, 4/32, 5/32, 4/32, 2/32},
|
||||
{x_off = -1, 2/32, 3/32, 2/32}
|
||||
},
|
||||
{
|
||||
name = "No dithering"
|
||||
}
|
||||
}
|
||||
function dither(texture, closest_color_finder, matrix)
|
||||
matrix = matrix or dithering_matrices[1]
|
||||
local newtexture = {width = texture.width, height = texture.height}
|
||||
for x=0, texture.width-1 do
|
||||
for y=0, texture.height-1 do
|
||||
local color = rgba_number_to_table(get_texture_color_at(texture, x, y))
|
||||
local alpha = color[1]
|
||||
table.remove(color, 1)
|
||||
local closest_color = closest_color_finder(color)
|
||||
set_texture_color_at(newtexture, x, y, rgba_tuple_to_number(alpha, unpack(closest_color)))
|
||||
local error = vector.subtract(closest_color, color) -- Difference between closest color - actual color
|
||||
table.insert(error, 1, 0)
|
||||
-- now push the error to unprocessed pixels
|
||||
for ydif, line in ipairs(matrix) do
|
||||
for index, diff in ipairs(line) do
|
||||
local nx, ny = x + index + line.x_off-1, y+ydif-1
|
||||
if nx >= 0 and ny >= 0 and nx <= texture.width-1 and ny <= texture.height-1 then
|
||||
if not rgba_number_to_table(get_texture_color_at(texture, x, y)) then error("AAAA") end
|
||||
local target_color = vector.floor(
|
||||
vector.clamp(
|
||||
vector.add(
|
||||
rgba_number_to_table(get_texture_color_at(texture, x, y)),
|
||||
vector.multiply(error, diff)
|
||||
),
|
||||
0, 255
|
||||
)
|
||||
)
|
||||
set_texture_color_at(texture, x, y, rgba_table_to_number(target_color))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return newtexture
|
||||
end
|
|
@ -0,0 +1,17 @@
|
|||
voxelizer={}
|
||||
minetest.mkdir(minetest.get_worldpath().."/media") -- Create media dir
|
||||
--cmd_ext.register_chatcommand()
|
||||
extend_mod("voxelizer", "conf") -- Load JSON configuration stored in worldpath
|
||||
extend_mod("voxelizer", "vector") -- Own vector lib, operating on lists
|
||||
extend_mod("voxelizer", "closest_color") -- Closest color finder, uses linear search / k-d tree depending on number of colors
|
||||
extend_mod("voxelizer", "texture_reader") -- Texture reader, reads textures, uses Java program
|
||||
extend_mod("voxelizer", "dithering") -- Error diffusion dithering
|
||||
extend_mod("voxelizer", "obj_reader") -- OBJ reader, reads simple OBJ models
|
||||
extend_mod("voxelizer", "node_map_reader") -- Node map reader, reads minetestmapper-colors.txt like files
|
||||
extend_mod("voxelizer", "main") -- Main : Actual API for placing of models using VoxelManip
|
||||
extend_mod("voxelizer", "chatcommands") -- Chatcommands for making use of the API
|
||||
|
||||
-- Tests : Only uncomment if you actually want to test something !
|
||||
--[[minetest.register_on_mods_loaded(function()
|
||||
extend_mod("voxelizer", "test")
|
||||
end)]]
|
|
@ -0,0 +1,214 @@
|
|||
function get_media(name)
|
||||
return minetest.get_worldpath().."/media/"..name
|
||||
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
|
||||
vox_min, vox_max = vector.convert(vector.subtract(vox_min, {16,16,16})), vector.convert(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 = file_ext.read(path_to_obj)
|
||||
if not obj_content then
|
||||
return ("OBJ doesn't exist."):format(path_to_obj)
|
||||
end
|
||||
if not file_ext.exists(path_to_texture) then
|
||||
return ("Texture doesn't exist."):format(path_to_texture)
|
||||
end
|
||||
local nodemap_content=file_ext.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 = table_ext.keys(nodemap)
|
||||
table_ext.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_vector(vec, transform_vec)
|
||||
res = vector.add(res, pos1)
|
||||
return res
|
||||
end
|
||||
elseif scale then
|
||||
function transform(v)
|
||||
local res = vector.add(vector.multiply(v, scale), pos1)
|
||||
return res
|
||||
end
|
||||
else
|
||||
function transform(v)
|
||||
local res = vector.add(v, pos1)
|
||||
return res
|
||||
end
|
||||
end
|
||||
table_ext.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) 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(b1, l1), vector.multiply(b2, l2))
|
||||
local pos = vector.add(points[1], res)
|
||||
local floor_pos = vector.convert(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(u1, l1), vector.multiply(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(color, count))
|
||||
end
|
||||
average_color = vector.multiply(average_color, 1/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
|
|
@ -0,0 +1,314 @@
|
|||
# Blender v2.76 (sub 0) OBJ File: 'character.blend'
|
||||
# www.blender.org
|
||||
o Player_Cube
|
||||
v -2.100000 6.299495 1.049999
|
||||
v -2.100000 6.300505 -1.050001
|
||||
v -2.100000 12.599493 1.053028
|
||||
v -2.100000 12.600502 -1.046972
|
||||
v -4.200159 12.600022 -1.047291
|
||||
v -4.199840 12.599971 1.052709
|
||||
v -2.099840 12.599971 1.052389
|
||||
v -2.100159 12.600022 -1.047610
|
||||
v -4.200159 6.300025 -1.047446
|
||||
v -4.199840 6.299973 1.052554
|
||||
v -2.099839 6.299973 1.052234
|
||||
v -2.100159 6.300025 -1.047766
|
||||
v -2.100000 0.000002 1.049999
|
||||
v -2.100000 0.000002 -1.050001
|
||||
v -2.100000 6.300002 1.049999
|
||||
v -2.100000 6.300002 -1.050001
|
||||
v -2.100504 12.600028 2.102523
|
||||
v -2.099495 12.600011 -2.097476
|
||||
v -2.100460 16.800030 2.102506
|
||||
v -2.099451 16.800011 -2.097494
|
||||
v 2.100000 6.300505 -1.050001
|
||||
v 2.100000 6.299495 1.049999
|
||||
v 2.100000 12.600502 -1.046972
|
||||
v 2.100000 12.599493 1.053028
|
||||
v 2.100159 6.300025 -1.047766
|
||||
v 2.099839 6.299973 1.052234
|
||||
v 4.199840 6.299973 1.052554
|
||||
v 4.200159 6.300025 -1.047446
|
||||
v 2.100159 12.600022 -1.047610
|
||||
v 2.099840 12.599971 1.052389
|
||||
v 4.199840 12.599971 1.052709
|
||||
v 4.200159 12.600022 -1.047291
|
||||
v 0.000000 -0.000000 -1.050001
|
||||
v -0.000000 0.000000 1.049999
|
||||
v 0.000000 6.300000 1.049999
|
||||
v 0.000000 6.300000 -1.050001
|
||||
v 2.100504 12.599967 -2.096467
|
||||
v 2.099495 12.599984 2.103532
|
||||
v 2.100549 16.799969 -2.096485
|
||||
v 2.099540 16.799984 2.103515
|
||||
v 2.100000 6.300002 -1.050001
|
||||
v 2.100000 6.300002 1.049999
|
||||
v 2.100000 0.000002 -1.050001
|
||||
v 2.100000 0.000002 1.049999
|
||||
v 0.000000 0.000000 -1.050001
|
||||
v -0.000000 -0.000000 1.049999
|
||||
v -0.000000 6.300000 1.049999
|
||||
v -0.000000 6.300000 -1.050001
|
||||
v -2.300554 12.400033 2.302476
|
||||
v -2.299449 12.400013 -2.297523
|
||||
v -2.300506 17.000034 2.302457
|
||||
v -2.299401 17.000015 -2.297543
|
||||
v 2.300550 12.399964 -2.296418
|
||||
v 2.299445 12.399984 2.303581
|
||||
v 2.300599 16.999966 -2.296438
|
||||
v 2.299494 16.999985 2.303562
|
||||
v 2.100000 12.600502 -1.046972
|
||||
v 2.100000 12.600502 -1.046972
|
||||
v 2.100000 12.599493 1.053028
|
||||
v 2.100000 12.599493 1.053028
|
||||
v 2.100000 6.299495 1.049999
|
||||
v 2.100000 6.299495 1.049999
|
||||
v 2.100000 6.300505 -1.050001
|
||||
v 2.100000 6.300505 -1.050001
|
||||
v -2.100000 12.599493 1.053028
|
||||
v -2.100000 12.599493 1.053028
|
||||
v -2.100000 6.299495 1.049999
|
||||
v -2.100000 6.299495 1.049999
|
||||
v -2.100000 12.600502 -1.046972
|
||||
v -2.100000 12.600502 -1.046972
|
||||
v -2.100000 6.300505 -1.050001
|
||||
v -2.100000 6.300505 -1.050001
|
||||
v -4.200159 12.600022 -1.047291
|
||||
v -4.200159 12.600022 -1.047291
|
||||
v -4.200159 6.300025 -1.047446
|
||||
v -4.200159 6.300025 -1.047446
|
||||
v -2.099840 12.599971 1.052389
|
||||
v -2.099840 12.599971 1.052389
|
||||
v -2.099839 6.299973 1.052234
|
||||
v -2.099839 6.299973 1.052234
|
||||
v -4.199840 12.599971 1.052709
|
||||
v -4.199840 12.599971 1.052709
|
||||
v -4.199840 6.299973 1.052554
|
||||
v -4.199840 6.299973 1.052554
|
||||
v -2.100159 12.600022 -1.047610
|
||||
v -2.100159 12.600022 -1.047610
|
||||
v -2.100159 6.300025 -1.047766
|
||||
v -2.100159 6.300025 -1.047766
|
||||
v -0.000000 0.000000 1.049999
|
||||
v -0.000000 0.000000 1.049999
|
||||
v 0.000000 -0.000000 -1.050001
|
||||
v 0.000000 -0.000000 -1.050001
|
||||
v -2.100000 0.000002 1.049999
|
||||
v -2.100000 0.000002 1.049999
|
||||
v -2.100000 6.300002 1.049999
|
||||
v -2.100000 6.300002 1.049999
|
||||
v -2.100000 6.300002 -1.050001
|
||||
v -2.100000 6.300002 -1.050001
|
||||
v -2.100000 0.000002 -1.050001
|
||||
v -2.100000 0.000002 -1.050001
|
||||
v 2.100549 16.799969 -2.096485
|
||||
v 2.100549 16.799969 -2.096485
|
||||
v 2.099540 16.799984 2.103515
|
||||
v 2.099540 16.799984 2.103515
|
||||
v 2.099495 12.599984 2.103532
|
||||
v 2.099495 12.599984 2.103532
|
||||
v 2.100504 12.599967 -2.096467
|
||||
v 2.100504 12.599967 -2.096467
|
||||
v -2.100460 16.800030 2.102506
|
||||
v -2.100460 16.800030 2.102506
|
||||
v -2.100504 12.600028 2.102523
|
||||
v -2.100504 12.600028 2.102523
|
||||
v -2.099451 16.800011 -2.097494
|
||||
v -2.099451 16.800011 -2.097494
|
||||
v -2.099495 12.600011 -2.097476
|
||||
v -2.099495 12.600011 -2.097476
|
||||
v 2.100159 12.600022 -1.047610
|
||||
v 2.100159 12.600022 -1.047610
|
||||
v 2.099840 12.599971 1.052389
|
||||
v 2.099840 12.599971 1.052389
|
||||
v 2.099839 6.299973 1.052234
|
||||
v 2.099839 6.299973 1.052234
|
||||
v 2.100159 6.300025 -1.047766
|
||||
v 2.100159 6.300025 -1.047766
|
||||
v 4.199840 12.599971 1.052709
|
||||
v 4.199840 12.599971 1.052709
|
||||
v 4.199840 6.299973 1.052554
|
||||
v 4.199840 6.299973 1.052554
|
||||
v 4.200159 12.600022 -1.047291
|
||||
v 4.200159 12.600022 -1.047291
|
||||
v 4.200159 6.300025 -1.047446
|
||||
v 4.200159 6.300025 -1.047446
|
||||
v 0.000000 6.300000 1.049999
|
||||
v 0.000000 6.300000 1.049999
|
||||
v -0.000000 6.300000 1.049999
|
||||
v -0.000000 6.300000 1.049999
|
||||
v 2.100000 6.300002 1.049999
|
||||
v 2.100000 6.300002 1.049999
|
||||
v -0.000000 -0.000000 1.049999
|
||||
v -0.000000 -0.000000 1.049999
|
||||
v 0.000000 6.300000 -1.050001
|
||||
v 0.000000 6.300000 -1.050001
|
||||
v 2.100000 0.000002 1.049999
|
||||
v 2.100000 0.000002 1.049999
|
||||
v 0.000000 0.000000 -1.050001
|
||||
v 0.000000 0.000000 -1.050001
|
||||
v 2.100000 0.000002 -1.050001
|
||||
v 2.100000 0.000002 -1.050001
|
||||
v 2.100000 6.300002 -1.050001
|
||||
v 2.100000 6.300002 -1.050001
|
||||
v -0.000000 6.300000 -1.050001
|
||||
v -0.000000 6.300000 -1.050001
|
||||
v 2.300599 16.999966 -2.296438
|
||||
v 2.300599 16.999966 -2.296438
|
||||
v 2.299494 16.999985 2.303562
|
||||
v 2.299494 16.999985 2.303562
|
||||
v 2.299445 12.399984 2.303581
|
||||
v 2.299445 12.399984 2.303581
|
||||
v 2.300550 12.399964 -2.296418
|
||||
v 2.300550 12.399964 -2.296418
|
||||
v -2.300506 17.000034 2.302457
|
||||
v -2.300506 17.000034 2.302457
|
||||
v -2.300554 12.400033 2.302476
|
||||
v -2.300554 12.400033 2.302476
|
||||
v -2.299401 17.000015 -2.297543
|
||||
v -2.299401 17.000015 -2.297543
|
||||
v -2.299449 12.400013 -2.297523
|
||||
v -2.299449 12.400013 -2.297523
|
||||
vt 0.500000 0.375000
|
||||
vt 0.500000 0.000000
|
||||
vt 0.625000 0.000000
|
||||
vt 0.437500 0.375000
|
||||
vt 0.437500 0.000000
|
||||
vt 0.312500 0.375000
|
||||
vt 0.312500 0.000000
|
||||
vt 0.562500 0.375000
|
||||
vt 0.562500 0.500000
|
||||
vt 0.437500 0.500000
|
||||
vt 0.312500 0.500000
|
||||
vt 0.125000 0.000000
|
||||
vt 0.187500 0.000000
|
||||
vt 0.187500 0.375000
|
||||
vt 0.875000 0.375000
|
||||
vt 0.875000 0.000000
|
||||
vt 0.812500 0.000000
|
||||
vt 0.750000 0.375000
|
||||
vt 0.812500 0.375000
|
||||
vt 0.187500 0.500000
|
||||
vt 0.125000 0.500000
|
||||
vt 0.125000 0.375000
|
||||
vt 0.062500 0.375000
|
||||
vt 0.062500 0.000000
|
||||
vt 0.000000 0.000000
|
||||
vt 0.375000 0.750000
|
||||
vt 0.375000 0.500000
|
||||
vt 0.500000 0.500000
|
||||
vt 0.250000 0.750000
|
||||
vt 0.250000 0.500000
|
||||
vt 0.125000 0.750000
|
||||
vt 0.375000 1.000000
|
||||
vt 0.250000 1.000000
|
||||
vt 0.125000 1.000000
|
||||
vt 0.250000 0.375000
|
||||
vt 0.250000 0.000000
|
||||
vt 0.812500 0.500000
|
||||
vt 0.750000 0.500000
|
||||
vt 0.687500 0.375000
|
||||
vt 0.000000 0.750000
|
||||
vt 0.000000 0.500000
|
||||
vt 0.062500 0.500000
|
||||
vt 0.750000 0.000000
|
||||
vt 0.687500 0.000000
|
||||
vt 0.687500 0.500000
|
||||
vt 0.875000 0.750000
|
||||
vt 0.875000 0.500000
|
||||
vt 1.000000 0.500000
|
||||
vt 0.750000 0.750000
|
||||
vt 0.625000 0.750000
|
||||
vt 0.625000 0.500000
|
||||
vt 0.875000 1.000000
|
||||
vt 0.750000 1.000000
|
||||
vt 0.625000 1.000000
|
||||
vt 0.625000 0.375000
|
||||
vt 0.000000 0.375000
|
||||
vt 0.500000 0.750000
|
||||
vt 1.000000 0.750000
|
||||
s off
|
||||
f 3/1 1/2 22/3
|
||||
f 4/4 2/5 67/2
|
||||
f 57/6 63/7 71/5
|
||||
f 21/8 61/9 68/10
|
||||
f 66/10 24/11 23/6
|
||||
f 145/12 46/13 135/14
|
||||
f 6/15 10/16 11/17
|
||||
f 8/18 77/19 79/17
|
||||
f 34/20 13/21 14/22
|
||||
f 16/23 99/24 93/25
|
||||
f 19/26 17/27 38/28
|
||||
f 20/29 18/30 111/27
|
||||
f 113/29 101/31 107/21
|
||||
f 105/32 112/33 116/29
|
||||
f 110/33 40/34 39/31
|
||||
f 35/14 95/35 94/36
|
||||
f 43/24 45/12 151/22
|
||||
f 141/22 91/12 100/24
|
||||
f 28/19 27/37 26/38
|
||||
f 32/39 29/18 30/38
|
||||
f 47/14 139/13 44/36
|
||||
f 133/14 90/13 92/12
|
||||
f 143/25 147/24 41/23
|
||||
f 62/36 64/7 58/6
|
||||
f 104/40 106/41 108/21
|
||||
f 96/21 134/42 142/23
|
||||
f 85/18 87/43 9/44
|
||||
f 73/39 75/44 83/3
|
||||
f 127/3 131/44 129/39
|
||||
f 74/39 82/45 78/38
|
||||
f 132/44 123/43 117/18
|
||||
f 148/22 144/21 140/20
|
||||
f 118/18 124/43 121/17
|
||||
f 80/38 84/37 76/19
|
||||
f 122/17 128/16 126/15
|
||||
f 152/23 136/42 138/21
|
||||
f 51/46 49/47 54/48
|
||||
f 52/49 50/38 163/47
|
||||
f 153/50 159/51 167/38
|
||||
f 157/52 164/53 168/49
|
||||
f 162/53 56/54 55/50
|
||||
f 158/28 160/51 154/50
|
||||
f 59/55 3/1 22/3
|
||||
f 65/1 4/4 67/2
|
||||
f 69/4 57/6 71/5
|
||||
f 72/4 21/8 68/10
|
||||
f 70/4 66/10 23/6
|
||||
f 48/22 145/12 135/14
|
||||
f 7/19 6/15 11/17
|
||||
f 12/43 8/18 79/17
|
||||
f 33/14 34/20 14/22
|
||||
f 15/56 16/23 93/25
|
||||
f 103/57 19/26 38/28
|
||||
f 109/26 20/29 111/27
|
||||
f 115/30 113/29 107/21
|
||||
f 37/26 105/32 116/29
|
||||
f 114/29 110/33 39/31
|
||||
f 89/13 35/14 94/36
|
||||
f 149/23 43/24 151/22
|
||||
f 97/23 141/22 100/24
|
||||
f 25/18 28/19 26/38
|
||||
f 31/45 32/39 30/38
|
||||
f 42/35 47/14 44/36
|
||||
f 36/22 133/14 92/12
|
||||
f 137/56 143/25 41/23
|
||||
f 60/35 62/36 58/6
|
||||
f 102/31 104/40 108/21
|
||||
f 98/22 96/21 142/23
|
||||
f 5/39 85/18 9/44
|
||||
f 81/55 73/39 83/3
|
||||
f 125/55 127/3 129/39
|
||||
f 86/18 74/39 78/38
|
||||
f 130/39 132/44 117/18
|
||||
f 146/14 148/22 140/20
|
||||
f 119/19 118/18 121/17
|
||||
f 88/18 80/38 76/19
|
||||
f 120/19 122/17 126/15
|
||||
f 150/22 152/23 138/21
|
||||
f 155/58 51/46 54/48
|
||||
f 161/46 52/49 163/47
|
||||
f 165/49 153/50 167/38
|
||||
f 53/46 157/52 168/49
|
||||
f 166/49 162/53 55/50
|
||||
f 156/57 158/28 154/50
|
After Width: | Height: | Size: 2.9 KiB |
|
@ -0,0 +1,234 @@
|
|||
v -20.000000 -32.500000 9.999993
|
||||
v -20.000000 -32.500000 -10.000005
|
||||
v -20.000000 35.000000 9.999993
|
||||
v -20.000000 35.000000 -10.000005
|
||||
v -40.000000 35.000000 -10.000000
|
||||
v -40.000000 35.000000 9.999999
|
||||
v -20.000000 34.999985 9.999995
|
||||
v -20.000000 34.999985 -10.000001
|
||||
v -39.999996 -32.500008 -10.000000
|
||||
v -39.999996 -32.500008 9.999999
|
||||
v -20.000000 -32.500015 9.999995
|
||||
v -20.000000 -32.500015 -10.000001
|
||||
v -20.000000 -100.000000 9.999996
|
||||
v -20.000000 -100.000000 -10.000005
|
||||
v -20.000000 -32.500000 9.999996
|
||||
v -20.000000 -32.500000 -10.000005
|
||||
v -20.000000 35.000000 19.999996
|
||||
v -20.000000 35.000000 -20.000004
|
||||
v -20.000000 75.000000 19.999996
|
||||
v -20.000000 75.000000 -20.000004
|
||||
v 20.000000 -32.500000 -10.000002
|
||||
v 20.000000 -32.500000 9.999996
|
||||
v 20.000000 35.000000 -10.000002
|
||||
v 20.000000 35.000000 9.999996
|
||||
v 20.000000 -32.500015 -10.000001
|
||||
v 20.000000 -32.500015 9.999995
|
||||
v 39.999996 -32.500008 9.999999
|
||||
v 39.999996 -32.500008 -10.000000
|
||||
v 20.000000 34.999985 -10.000001
|
||||
v 20.000000 34.999985 9.999995
|
||||
v 40.000000 35.000000 9.999999
|
||||
v 40.000000 35.000000 -10.000000
|
||||
v 0.000001 -100.000000 -10.000005
|
||||
v -0.000001 -100.000000 9.999996
|
||||
v 0.000000 -32.500000 9.999996
|
||||
v 0.000000 -32.500000 -10.000005
|
||||
v 20.000000 35.000000 -20.000004
|
||||
v 20.000000 35.000000 19.999996
|
||||
v 20.000000 75.000000 -20.000004
|
||||
v 20.000000 75.000000 19.999996
|
||||
v 20.000000 -32.500000 -10.000005
|
||||
v 20.000000 -32.500000 9.999996
|
||||
v 20.000000 -100.000000 -10.000005
|
||||
v 20.000000 -100.000000 9.999996
|
||||
v 0.000000 -100.000000 -10.000005
|
||||
v 0.000000 -100.000000 9.999996
|
||||
v 0.000000 -32.500000 9.999996
|
||||
v 0.000000 -32.500000 -10.000005
|
||||
v -22.000000 33.029953 21.999996
|
||||
v -22.000000 33.029953 -22.000006
|
||||
v -22.000000 77.029892 21.999996
|
||||
v -22.000000 77.029892 -22.000006
|
||||
v 22.000000 33.029953 -22.000006
|
||||
v 22.000000 33.029953 21.999996
|
||||
v 22.000000 77.029892 -22.000006
|
||||
v 22.000000 77.029892 21.999996
|
||||
v -20.000000 -32.500000 13.644027
|
||||
v -20.000000 35.000000 13.644027
|
||||
v 20.000000 -32.500000 13.644027
|
||||
v 20.000000 35.000000 13.644027
|
||||
v 20.000000 35.000000 9.999999
|
||||
v 20.000000 -32.500000 9.999999
|
||||
v -20.000000 -32.500000 9.999999
|
||||
v -20.000000 35.000000 9.999999
|
||||
vt 0.500000 0.375000
|
||||
vt 0.500000 0.000000
|
||||
vt 0.625000 0.000000
|
||||
vt 0.437500 0.375000
|
||||
vt 0.437500 0.000000
|
||||
vt 0.312500 0.375000
|
||||
vt 0.312500 0.000000
|
||||
vt 0.562500 0.375000
|
||||
vt 0.562500 0.500000
|
||||
vt 0.437500 0.500000
|
||||
vt 0.312500 0.500000
|
||||
vt 0.187500 0.000000
|
||||
vt 0.125000 0.000000
|
||||
vt 0.125000 0.375000
|
||||
vt 0.812500 0.375000
|
||||
vt 0.875000 0.375000
|
||||
vt 0.875000 0.000000
|
||||
vt 0.812500 0.000000
|
||||
vt 0.750000 0.000000
|
||||
vt 0.187500 0.500000
|
||||
vt 0.125000 0.500000
|
||||
vt 0.062500 0.375000
|
||||
vt 0.062500 0.000000
|
||||
vt 0.000000 0.000000
|
||||
vt 0.375000 0.750000
|
||||
vt 0.375000 0.500000
|
||||
vt 0.500000 0.500000
|
||||
vt 0.250000 0.750000
|
||||
vt 0.250000 0.500000
|
||||
vt 0.125000 0.750000
|
||||
vt 0.375000 1.000000
|
||||
vt 0.250000 1.000000
|
||||
vt 0.125000 1.000000
|
||||
vt 0.187500 0.375000
|
||||
vt 0.250000 0.000000
|
||||
vt 0.812500 0.500000
|
||||
vt 0.750000 0.500000
|
||||
vt 0.687500 0.375000
|
||||
vt 0.750000 0.375000
|
||||
vt 0.000000 0.500000
|
||||
vt 0.062500 0.500000
|
||||
vt 0.687500 0.000000
|
||||
vt 0.625000 0.375000
|
||||
vt 0.687500 0.500000
|
||||
vt 0.875000 0.750000
|
||||
vt 0.875000 0.500000
|
||||
vt 1.000000 0.500000
|
||||
vt 0.750000 0.750000
|
||||
vt 0.625000 0.750000
|
||||
vt 0.625000 0.500000
|
||||
vt 0.875000 1.000000
|
||||
vt 0.750000 1.000000
|
||||
vt 0.625000 1.000000
|
||||
vt 1.000000 0.000000
|
||||
vt 1.000000 0.375000
|
||||
vt 1.000000 0.343750
|
||||
vt 0.875000 0.343750
|
||||
vt 0.984375 0.000000
|
||||
vt 0.984375 0.375000
|
||||
vt 0.890625 0.375000
|
||||
vt 0.890625 0.000000
|
||||
vt 0.875000 0.031250
|
||||
vt 1.000000 0.031250
|
||||
vt 0.000000 0.375000
|
||||
vt 0.500000 0.750000
|
||||
vt 0.250000 0.375000
|
||||
vt 0.000000 0.750000
|
||||
vt 1.000000 0.750000
|
||||
vn -0.000000 0.000000 1.000000
|
||||
vn -1.000000 0.000000 0.000000
|
||||
vn 0.000000 0.000000 -1.000000
|
||||
vn 0.000000 -1.000000 0.000000
|
||||
vn 0.000000 1.000000 0.000000
|
||||
vn 1.000000 0.000000 0.000000
|
||||
f 3/1/1 1/2/1 22/3/1
|
||||
f 4/4/2 2/5/2 1/2/2
|
||||
f 23/6/3 21/7/3 2/5/3
|
||||
f 21/8/4 22/9/4 1/10/4
|
||||
f 3/10/5 24/11/5 23/6/5
|
||||
f 45/12/2 46/13/2 47/14/2
|
||||
f 7/15/1 6/16/1 10/17/1
|
||||
f 7/15/6 11/18/6 12/19/6
|
||||
f 34/20/4 13/21/4 14/14/4
|
||||
f 16/22/2 14/23/2 13/24/2
|
||||
f 19/25/1 17/26/1 38/27/1
|
||||
f 20/28/2 18/29/2 17/26/2
|
||||
f 39/30/3 37/21/3 18/29/3
|
||||
f 38/31/4 17/32/4 18/28/4
|
||||
f 19/32/5 40/33/5 39/30/5
|
||||
f 15/34/1 13/12/1 34/35/1
|
||||
f 43/13/3 45/23/3 48/22/3
|
||||
f 36/22/3 33/23/3 14/13/3
|
||||
f 28/15/4 27/36/4 26/37/4
|
||||
f 32/38/5 29/39/5 30/37/5
|
||||
f 46/35/1 44/12/1 42/34/1
|
||||
f 35/14/6 34/13/6 33/12/6
|
||||
f 44/24/6 43/23/6 41/22/6
|
||||
f 22/35/6 21/7/6 23/6/6
|
||||
f 38/40/6 37/21/6 39/30/6
|
||||
f 15/21/5 35/41/5 36/22/5
|
||||
f 8/39/3 12/19/3 9/42/3
|
||||
f 5/43/2 9/3/2 10/42/2
|
||||
f 27/42/6 28/3/6 32/43/6
|
||||
f 5/38/5 6/44/5 7/37/5
|
||||
f 28/42/3 25/19/3 29/39/3
|
||||
f 43/14/4 44/21/4 46/20/4
|
||||
f 25/19/2 26/18/2 30/15/2
|
||||
f 11/37/4 10/36/4 9/15/4
|
||||
f 30/15/1 26/18/1 27/17/1
|
||||
f 48/22/5 47/41/5 42/21/5
|
||||
f 51/45/1 49/46/1 54/47/1
|
||||
f 52/48/2 50/37/2 49/46/2
|
||||
f 55/49/3 53/50/3 50/37/3
|
||||
f 54/51/4 49/52/4 50/48/4
|
||||
f 51/52/5 56/53/5 55/49/5
|
||||
f 54/27/6 53/50/6 55/49/6
|
||||
f 58/16/1 57/17/1 59/54/1
|
||||
f 62/54/3 63/17/3 64/16/3
|
||||
f 60/55/5 61/56/5 64/57/5
|
||||
f 59/54/6 62/58/6 61/59/6
|
||||
f 58/16/2 64/60/2 63/61/2
|
||||
f 57/17/4 63/62/4 62/63/4
|
||||
f 24/43/1 3/1/1 22/3/1
|
||||
f 3/1/2 4/4/2 1/2/2
|
||||
f 4/4/3 23/6/3 2/5/3
|
||||
f 2/4/4 21/8/4 1/10/4
|
||||
f 4/4/5 3/10/5 23/6/5
|
||||
f 48/34/2 45/12/2 47/14/2
|
||||
f 11/18/1 7/15/1 10/17/1
|
||||
f 8/39/6 7/15/6 12/19/6
|
||||
f 33/34/4 34/20/4 14/14/4
|
||||
f 15/64/2 16/22/2 13/24/2
|
||||
f 40/65/1 19/25/1 38/27/1
|
||||
f 19/25/2 20/28/2 17/26/2
|
||||
f 20/28/3 39/30/3 18/29/3
|
||||
f 37/25/4 38/31/4 18/28/4
|
||||
f 20/28/5 19/32/5 39/30/5
|
||||
f 35/66/1 15/34/1 34/35/1
|
||||
f 41/14/3 43/13/3 48/22/3
|
||||
f 16/14/3 36/22/3 14/13/3
|
||||
f 25/39/4 28/15/4 26/37/4
|
||||
f 31/44/5 32/38/5 30/37/5
|
||||
f 47/66/1 46/35/1 42/34/1
|
||||
f 36/34/6 35/14/6 33/12/6
|
||||
f 42/64/6 44/24/6 41/22/6
|
||||
f 24/66/6 22/35/6 23/6/6
|
||||
f 40/67/6 38/40/6 39/30/6
|
||||
f 16/14/5 15/21/5 36/22/5
|
||||
f 5/38/3 8/39/3 9/42/3
|
||||
f 6/38/2 5/43/2 10/42/2
|
||||
f 31/38/6 27/42/6 32/43/6
|
||||
f 8/39/5 5/38/5 7/37/5
|
||||
f 32/38/3 28/42/3 29/39/3
|
||||
f 45/34/4 43/14/4 46/20/4
|
||||
f 29/39/2 25/19/2 30/15/2
|
||||
f 12/39/4 11/37/4 9/15/4
|
||||
f 31/16/1 30/15/1 27/17/1
|
||||
f 41/14/5 48/22/5 42/21/5
|
||||
f 56/68/1 51/45/1 54/47/1
|
||||
f 51/45/2 52/48/2 49/46/2
|
||||
f 52/48/3 55/49/3 50/37/3
|
||||
f 53/45/4 54/51/4 50/48/4
|
||||
f 52/48/5 51/52/5 55/49/5
|
||||
f 56/65/6 54/27/6 55/49/6
|
||||
f 60/55/1 58/16/1 59/54/1
|
||||
f 61/55/3 62/54/3 64/16/3
|
||||
f 58/16/5 60/55/5 64/57/5
|
||||
f 60/55/6 59/54/6 61/59/6
|
||||
f 57/17/2 58/16/2 63/61/2
|
||||
f 59/54/4 57/17/4 62/63/4
|
|
@ -0,0 +1,76 @@
|
|||
0 128 128 128 # CONTENT_STONE
|
||||
2 39 66 106 # CONTENT_WATER
|
||||
3 255 255 0 # CONTENT_TORCH
|
||||
9 39 66 106 # CONTENT_WATERSOURCE
|
||||
e 117 86 41 # CONTENT_SIGN_WALL
|
||||
f 128 79 0 # CONTENT_CHEST
|
||||
15 103 78 42 # CONTENT_FENCE
|
||||
1e 162 119 53 # CONTENT_RAIL
|
||||
1f 154 110 40 # CONTENT_LADDER
|
||||
20 255 100 0 # CONTENT_LAVA
|
||||
21 255 100 0 # CONTENT_LAVASOURCE
|
||||
800 107 134 51 # CONTENT_GRASS
|
||||
801 86 58 31 # CONTENT_TREE
|
||||
802 48 95 8 # CONTENT_LEAVES
|
||||
803 102 129 38 # CONTENT_GRASS_FOOTSTEPS
|
||||
804 178 178 0 # CONTENT_MESE
|
||||
805 101 84 36 # CONTENT_MUD
|
||||
808 104 78 42 # CONTENT_WOOD
|
||||
809 210 194 156 # CONTENT_SAND
|
||||
80a 123 123 123 # CONTENT_COBBLE
|
||||
80b 199 199 199 # CONTENT_STEEL
|
||||
80c 183 183 222 # CONTENT_GLASS
|
||||
80d 219 202 178 # CONTENT_MOSSYCOBBLE
|
||||
80e 70 70 70 # CONTENT_GRAVEL
|
||||
80f 204 0 0 # CONTENT_SANDSTONE
|
||||
810 0 215 0 # CONTENT_CACTUS
|
||||
811 170 50 25 # CONTENT_BRICK
|
||||
812 104 78 42 # CONTENT_CLAY
|
||||
813 58 105 18 # CONTENT_PAPYRUS
|
||||
814 196 160 0 # CONTENT_BOOKSHELF
|
||||
815 205 190 121 # CONTENT_JUNGLETREE
|
||||
816 62 101 25 # CONTENT_JUNGLEGRASS
|
||||
817 255 153 255 # CONTENT_NC
|
||||
818 102 50 255 # CONTENT_NC_RB
|
||||
819 200 0 0 # CONTENT_APPLE
|
||||
|
||||
default:stone 128 128 128
|
||||
default:stone_with_coal 50 50 50
|
||||
default:water_flowing 39 66 106
|
||||
default:torch 255 255 0
|
||||
default:water_source 39 66 106
|
||||
default:sign_wall 117 86 41
|
||||
default:chest 128 79 0
|
||||
default:fence_wood 103 78 42
|
||||
default:rail 162 119 53
|
||||
default:ladder 154 110 40
|
||||
default:lava_flowing 255 100 0
|
||||
default:lava_source 255 100 0
|
||||
default:dirt_with_grass 107 134 51
|
||||
default:tree 86 58 31
|
||||
default:leaves 48 95 8
|
||||
default:dirt_with_grass_and_footsteps 102 129 38
|
||||
default:mese 178 178 0
|
||||
default:dirt 101 84 36
|
||||
default:wood 104 78 42
|
||||
default:sand 210 194 156
|
||||
default:cobble 123 123 123
|
||||
default:steelblock 199 199 199
|
||||
default:glass 183 183 222
|
||||
default:mossycobble 219 202 178
|
||||
default:gravel 70 70 70
|
||||
default:sandstone 204 0 0
|
||||
default:cactus 0 215 0
|
||||
default:brick 170 50 25
|
||||
default:clay 104 78 42
|
||||
default:papyrus 58 105 18
|
||||
default:bookshelf 196 160 0
|
||||
default:jungletree 205 190 121
|
||||
default:junglegrass 62 101 25
|
||||
default:nyancat 255 153 255
|
||||
default:nyancat_rainbow 102 50 255
|
||||
default:apple 200 0 0
|
||||
default:desert_sand 210 180 50
|
||||
default:desert_stone 150 100 30
|
||||
default:dry_shrub 100 80 40
|
||||
|
After Width: | Height: | Size: 115 B |
After Width: | Height: | Size: 3.4 KiB |
After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 2.2 KiB |
|
@ -0,0 +1,76 @@
|
|||
0 128 128 128 # CONTENT_STONE
|
||||
2 39 66 106 # CONTENT_WATER
|
||||
3 255 255 0 # CONTENT_TORCH
|
||||
9 39 66 106 # CONTENT_WATERSOURCE
|
||||
e 117 86 41 # CONTENT_SIGN_WALL
|
||||
f 128 79 0 # CONTENT_CHEST
|
||||
15 103 78 42 # CONTENT_FENCE
|
||||
1e 162 119 53 # CONTENT_RAIL
|
||||
1f 154 110 40 # CONTENT_LADDER
|
||||
20 255 100 0 # CONTENT_LAVA
|
||||
21 255 100 0 # CONTENT_LAVASOURCE
|
||||
800 107 134 51 # CONTENT_GRASS
|
||||
801 86 58 31 # CONTENT_TREE
|
||||
802 48 95 8 # CONTENT_LEAVES
|
||||
803 102 129 38 # CONTENT_GRASS_FOOTSTEPS
|
||||
804 178 178 0 # CONTENT_MESE
|
||||
805 101 84 36 # CONTENT_MUD
|
||||
808 104 78 42 # CONTENT_WOOD
|
||||
809 210 194 156 # CONTENT_SAND
|
||||
80a 123 123 123 # CONTENT_COBBLE
|
||||
80b 199 199 199 # CONTENT_STEEL
|
||||
80c 183 183 222 # CONTENT_GLASS
|
||||
80d 219 202 178 # CONTENT_MOSSYCOBBLE
|
||||
80e 70 70 70 # CONTENT_GRAVEL
|
||||
80f 204 0 0 # CONTENT_SANDSTONE
|
||||
810 0 215 0 # CONTENT_CACTUS
|
||||
811 170 50 25 # CONTENT_BRICK
|
||||
812 104 78 42 # CONTENT_CLAY
|
||||
813 58 105 18 # CONTENT_PAPYRUS
|
||||
814 196 160 0 # CONTENT_BOOKSHELF
|
||||
815 205 190 121 # CONTENT_JUNGLETREE
|
||||
816 62 101 25 # CONTENT_JUNGLEGRASS
|
||||
817 255 153 255 # CONTENT_NC
|
||||
818 102 50 255 # CONTENT_NC_RB
|
||||
819 200 0 0 # CONTENT_APPLE
|
||||
|
||||
default:stone 128 128 128
|
||||
default:stone_with_coal 50 50 50
|
||||
default:water_flowing 39 66 106
|
||||
default:torch 255 255 0
|
||||
default:water_source 39 66 106
|
||||
default:sign_wall 117 86 41
|
||||
default:chest 128 79 0
|
||||
default:fence_wood 103 78 42
|
||||
default:rail 162 119 53
|
||||
default:ladder 154 110 40
|
||||
default:lava_flowing 255 100 0
|
||||
default:lava_source 255 100 0
|
||||
default:dirt_with_grass 107 134 51
|
||||
default:tree 86 58 31
|
||||
default:leaves 48 95 8
|
||||
default:dirt_with_grass_and_footsteps 102 129 38
|
||||
default:mese 178 178 0
|
||||
default:dirt 101 84 36
|
||||
default:wood 104 78 42
|
||||
default:sand 210 194 156
|
||||
default:cobble 123 123 123
|
||||
default:steelblock 199 199 199
|
||||
default:glass 183 183 222
|
||||
default:mossycobble 219 202 178
|
||||
default:gravel 70 70 70
|
||||
default:sandstone 204 0 0
|
||||
default:cactus 0 215 0
|
||||
default:brick 170 50 25
|
||||
default:clay 104 78 42
|
||||
default:papyrus 58 105 18
|
||||
default:bookshelf 196 160 0
|
||||
default:jungletree 205 190 121
|
||||
default:junglegrass 62 101 25
|
||||
default:nyancat 255 153 255
|
||||
default:nyancat_rainbow 102 50 255
|
||||
default:apple 200 0 0
|
||||
default:desert_sand 210 180 50
|
||||
default:desert_stone 150 100 30
|
||||
default:dry_shrub 100 80 40
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
name=voxelizer
|
||||
title=Voxelizer
|
||||
description=Turns 3D models into astonishing voxel builds.
|
||||
depends=modlib, cmdlib
|
|
@ -0,0 +1,21 @@
|
|||
-- Reads a node map in a similar format as minetestmapper.txt
|
||||
function read_node_map(minetestmapper_content)
|
||||
local lines=string_ext.split_without_limit(minetestmapper_content, "\n")
|
||||
local iterator, _, index=ipairs(lines)
|
||||
local color_to_cid={}
|
||||
|
||||
--Process lines
|
||||
index, line=iterator(lines, index)
|
||||
while line do
|
||||
parts=string_ext.split(line, " ",5)
|
||||
if #parts >= 4 then
|
||||
local c_id=tonumber(parts[1], 16)
|
||||
if not c_id then c_id=minetest.get_content_id(parts[1]) end
|
||||
local r, g, b=tonumber(parts[2]), tonumber(parts[3]), tonumber(parts[4])
|
||||
if c_id and r and g and b and minetest.get_name_from_content_id(c_id) ~= "unknown" and minetest.get_name_from_content_id(c_id) ~= "ignore" then color_to_cid[r*256*256+g*256+b]=c_id end
|
||||
end
|
||||
index, line=iterator(lines, index)
|
||||
end
|
||||
|
||||
return color_to_cid
|
||||
end
|
|
@ -0,0 +1,61 @@
|
|||
--obj_content: string, OBJ file content; triangle_consumer_factory: function(vertexes), returns triangle_consumer: function(vertexes, uvs)
|
||||
-- TODO add support for colors
|
||||
function read_obj(obj_content, triangle_consumer_factory)
|
||||
print(obj_content)
|
||||
local lines=string_ext.split_without_limit(obj_content, "\n")
|
||||
local iterator, _, index=ipairs(lines)
|
||||
|
||||
::next_object::
|
||||
-- Vertices
|
||||
local vertices = {}
|
||||
local counter=1
|
||||
index, line=iterator(lines, index)
|
||||
repeat
|
||||
if string_ext.starts_with(line, "v ") then
|
||||
local parts=string_ext.split(line:sub(3), " ", 4) -- x, y, z, unneeded
|
||||
local x, y, z = tonumber(parts[1]), tonumber(parts[2]), tonumber(parts[3])
|
||||
if x and y and z then
|
||||
vertices[counter] = {x,y,z}
|
||||
end
|
||||
counter = counter + 1
|
||||
end
|
||||
index, line=iterator(lines, index)
|
||||
until string_ext.starts_with(line, "vt ")
|
||||
|
||||
--UVs
|
||||
local uvs={}
|
||||
counter=1
|
||||
repeat
|
||||
if string_ext.starts_with(line, "vt ") then
|
||||
local parts=string_ext.split(line:sub(4), " ", 3)
|
||||
local x, y = tonumber(parts[1]), tonumber(parts[2])
|
||||
if x and y then
|
||||
uvs[counter] = {x, y}
|
||||
end
|
||||
counter = counter + 1
|
||||
end
|
||||
index, line=iterator(lines, index)
|
||||
until string_ext.starts_with(line, "f ")
|
||||
|
||||
local triangle_consumer=triangle_consumer_factory(vertices)
|
||||
--Faces (need to be triangles), polygons are ignored
|
||||
repeat
|
||||
if string_ext.starts_with(line, "f ") then --Face
|
||||
local parts=string_ext.split(line:sub(3), " ", 4)
|
||||
local verts={}
|
||||
local texs={}
|
||||
for i=1, 3 do
|
||||
local indices = string_ext.split(parts[i], "/", 3)
|
||||
local vert, uv = tonumber(indices[1]), tonumber(indices[2])
|
||||
verts[i] = vertices[vert]
|
||||
texs[i] = uvs[uv]
|
||||
if not verts[i] or not texs[i] then goto invalid end
|
||||
end
|
||||
triangle_consumer(verts, texs)
|
||||
::invalid::
|
||||
elseif string_ext.starts_with(line, "o ") then
|
||||
goto next_object
|
||||
end
|
||||
index, line=iterator(lines, index)
|
||||
until not line
|
||||
end
|
After Width: | Height: | Size: 1.0 MiB |
After Width: | Height: | Size: 510 KiB |
After Width: | Height: | Size: 1.3 MiB |
After Width: | Height: | Size: 1.3 MiB |
|
@ -0,0 +1,51 @@
|
|||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.nio.channels.Channels;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
|
||||
public class FileDownloader {
|
||||
// Takes URL to file and downloads it to given destination
|
||||
public static void main(String[] args) {
|
||||
if (args.length < 2) {
|
||||
System.out.println("URL and output path need to be given");
|
||||
System.exit(1);
|
||||
} else {
|
||||
try {
|
||||
URL in = new URL(args[0]);
|
||||
File out = new File(args[1]);
|
||||
if (!out.exists()) {
|
||||
try {
|
||||
out.createNewFile();
|
||||
} catch (IOException e) {
|
||||
System.out.println("Couldn't create file");
|
||||
System.exit(2);
|
||||
}
|
||||
}
|
||||
if (!out.canWrite()) {
|
||||
System.out.println("Output file doesn't exist or can't be written");
|
||||
System.exit(3);
|
||||
}
|
||||
try {
|
||||
FileOutputStream outStream = new FileOutputStream(out);
|
||||
try {
|
||||
ReadableByteChannel download = Channels.newChannel(in.openStream());
|
||||
outStream.getChannel().transferFrom(download, 0, Long.MAX_VALUE);
|
||||
} catch (IOException e) {
|
||||
System.out.println("Couldn't download file");
|
||||
System.exit(4);
|
||||
}
|
||||
} catch (FileNotFoundException e) {
|
||||
System.out.println("Couldn't write to file");
|
||||
System.exit(5);
|
||||
}
|
||||
} catch (MalformedURLException e) {
|
||||
System.out.println("Malformed URL");
|
||||
System.exit(6);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
import javax.imageio.ImageIO;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.Color;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
public class LogoCreator {
|
||||
public static void main(String[] args) {
|
||||
if (args.length < 2) {
|
||||
System.out.println("Error : Output and input path need to be given.");
|
||||
System.exit(1);
|
||||
} else {
|
||||
File in = new File(args[0]);
|
||||
File out = new File(args[1]);
|
||||
int scale = args.length >=3 ? Integer.parseInt(args[2]) : 3;
|
||||
if (!in.exists() || !in.canRead()) {
|
||||
System.out.println("Error : Input file doesn't exist or can't be read.");
|
||||
System.exit(1);
|
||||
}
|
||||
try {
|
||||
BufferedImage input = ImageIO.read(in);
|
||||
BufferedImage output = new BufferedImage(input.getWidth() * scale, input.getHeight() * scale, BufferedImage.TYPE_INT_ARGB);
|
||||
for (int x = 0; x < input.getWidth(); x++) {
|
||||
for (int y = 0; y < input.getHeight(); y++) {
|
||||
int c = input.getRGB(x, y);
|
||||
for (int x_2 = x * scale + 1; x_2 < x * scale + scale - 1; x_2++) {
|
||||
for (int y_2 = y * scale + 1; y_2 < y * scale + scale - 1; y_2++) {
|
||||
output.setRGB(x_2, y_2, c);
|
||||
}
|
||||
}
|
||||
int b = new Color(c, true).brighter().getRGB();
|
||||
for (int x_2 = 0; x_2 < scale; x_2 += scale-1) {
|
||||
for (int y_2 = 0; y_2 < scale; y_2++) {
|
||||
output.setRGB(x_2 + x*scale, y_2 + y*scale, b);
|
||||
output.setRGB(y_2 + x*scale, x_2 + y*scale, b);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ImageIO.write(output, "PNG", out);
|
||||
System.out.println("Texture created successfully.");
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
import javax.imageio.ImageIO;
|
||||
|
||||
public class SupportedTextureFormats {
|
||||
public static void main(String... args) {
|
||||
System.out.println("The supported image file formats are : "+String.join(", ", ImageIO.getReaderFormatNames()));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
import javax.imageio.ImageIO;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.DataBuffer;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
public class TextureLoader {
|
||||
|
||||
/* Takes two file paths as inputs : Input and output. Input file needs to be a supported image, and output needs to be writable. Image will be stored in output using SIF file format.*/
|
||||
public static void main(String... args) {
|
||||
if (args.length < 2) {
|
||||
System.out.println("Output and input path need to be given");
|
||||
System.exit(1);
|
||||
} else {
|
||||
File in = new File(args[0]);
|
||||
File out = new File(args[1]);
|
||||
if (!out.exists()) {
|
||||
try {
|
||||
out.createNewFile();
|
||||
} catch (IOException e) {
|
||||
System.out.println("Couldn't create output file");
|
||||
System.exit(2);
|
||||
}
|
||||
}
|
||||
if (!in.exists() || !in.canRead() || !out.canWrite()) {
|
||||
System.out.println("Output or input file doesn't exist or can't be read/written");
|
||||
System.exit(3);
|
||||
}
|
||||
try {
|
||||
BufferedImage img = ImageIO.read(in);
|
||||
FileOutputStream outputStream = new FileOutputStream(out);
|
||||
|
||||
/* Write headers */
|
||||
outputStream.write(img.getWidth()/256);
|
||||
outputStream.write(img.getWidth()%256);
|
||||
outputStream.write(img.getHeight()/256);
|
||||
outputStream.write(img.getHeight()%256);
|
||||
|
||||
/* Write data */
|
||||
for (int y = 0; y < img.getHeight(); y++) {
|
||||
for (int x = 0; x < img.getWidth(); x++) {
|
||||
int color = img.getRGB(x, y);
|
||||
int alpha = color >>> 24;
|
||||
int red = (color & 0x00FF0000) >> 16;
|
||||
int green = (color & 0x0000FF00) >> 8;
|
||||
int blue = color & 0x000000FF;
|
||||
outputStream.write(alpha);
|
||||
outputStream.write(red);
|
||||
outputStream.write(green);
|
||||
outputStream.write(blue);
|
||||
}
|
||||
}
|
||||
|
||||
outputStream.close();
|
||||
} catch (IOException e) {
|
||||
System.out.println("File couldn't be written");
|
||||
System.exit(4);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
math.randomseed(os.time())
|
||||
|
||||
local function random_color()
|
||||
color={}
|
||||
for i=1, 3 do color[i]=math.random(0, 255) end
|
||||
return color
|
||||
end
|
||||
|
||||
local function random_colors(k)
|
||||
local random_colors={}
|
||||
for i=1, k or 10000 do
|
||||
table.insert(random_colors, random_color())
|
||||
end
|
||||
return random_colors
|
||||
end
|
||||
|
||||
local function test_correctness()
|
||||
local k=0
|
||||
for i = 1, 1000 do
|
||||
local colors=random_colors(1000)
|
||||
local tree=kd_closest_color_finder(colors)
|
||||
local color=random_color() --colors[math.random(1, 20)]
|
||||
local linear, lin_distance=linear_closest_color_finder(colors)(color)
|
||||
local kd, kd_distance=tree(color)
|
||||
if lin_distance == kd_distance then
|
||||
k=k+1
|
||||
end
|
||||
end
|
||||
print(tostring(k).." of 1000 samples")
|
||||
end
|
||||
|
||||
local function test_performance()
|
||||
local color=random_color() --colors[math.random(1, 20)]
|
||||
local colors=random_colors(10000)
|
||||
local tree=kd_closest_color_finder(colors)
|
||||
local linear=linear_closest_color_finder(colors)
|
||||
for _, tree in ipairs({tree, linear}) do
|
||||
local x = os.clock()
|
||||
local s = 0
|
||||
for i = 1, 1000 do kd, kd_distance=tree(color) end
|
||||
print(string.format("elapsed time: %.2f", os.clock() - x))
|
||||
end
|
||||
end
|
||||
|
||||
print("Closest Color Finder Test : ")
|
||||
test_correctness()
|
||||
test_performance()
|
||||
|
||||
local function test_texture_reader()
|
||||
image = read_texture(get_resource("voxelizer", "test/image.png"))
|
||||
print("Texture Reader Test : ")
|
||||
print(color_to_number(color_to_table(get_texture_color_at(image, 0, 0))) == 0x00000000)
|
||||
print(color_to_number(color_to_table(get_texture_color_at(image, 1, 0))) == 0xFFFF0000)
|
||||
print(color_to_number(color_to_table(get_texture_color_at(image, 0, 1))) == 0xFF00FF00)
|
||||
print(color_to_number(color_to_table(get_texture_color_at(image, 1, 1))) == 0xFF0000FF)
|
||||
end
|
||||
|
||||
test_texture_reader()
|
||||
|
||||
local function test_nodemap_reader()
|
||||
print("Nodemap Reader Test : ")
|
||||
local color_to_cid = read_node_map(file_ext.read("/usr/share/games/minetest/minetestmapper-colors.txt"))
|
||||
for c, cid in pairs(color_to_cid) do
|
||||
print(string.format("%x", c).." -> "..minetest.get_name_from_content_id(cid))
|
||||
end
|
||||
end
|
||||
|
||||
test_nodemap_reader()
|
After Width: | Height: | Size: 115 B |
|
@ -0,0 +1,127 @@
|
|||
function rgba_number_to_table(number)
|
||||
local b = number % 256
|
||||
local g = math.floor(number / 256) % 256
|
||||
local r = math.floor(number / 256 / 256) % 256
|
||||
local a = math.floor(number / 256 / 256 / 256) % 256
|
||||
return {a, r, g, b}
|
||||
end
|
||||
|
||||
function rgb_number_to_table(number)
|
||||
local b = number % 256
|
||||
local g = math.floor(number / 256) % 256
|
||||
local r = math.floor(number / 256 / 256) % 256
|
||||
return {r, g, b}
|
||||
end
|
||||
|
||||
function rgba_tuple_to_number(a, r, g, b)
|
||||
return a*256*256*256+r*256*256+g*256+b
|
||||
end
|
||||
|
||||
function rgba_table_to_number(table)
|
||||
return rgba_tuple_to_number(unpack(table))
|
||||
end
|
||||
|
||||
function rgb_tuple_to_number(r, g, b)
|
||||
return r*256*256+g*256+b
|
||||
end
|
||||
|
||||
function rgb_table_to_number(table)
|
||||
return rgb_tuple_to_number(unpack(table))
|
||||
end
|
||||
|
||||
function get_texture_color_at(texture, x, y)
|
||||
return texture[x+y*texture.width+1]
|
||||
end
|
||||
|
||||
function set_texture_color_at(texture, x, y, color)
|
||||
texture[x+y*texture.width+1] = color
|
||||
end
|
||||
|
||||
function in_bounds(texture, x, y)
|
||||
return x >= 0 and y >= 0 and x < texture.width and x < texture.height
|
||||
end
|
||||
|
||||
function nearest_filtering(texture, pos_uv)
|
||||
local x = math.min(math.floor(pos_uv[1]*texture.width), texture.width-1)
|
||||
local y = math.min(math.floor((1-pos_uv[2])*texture.height), texture.height-1)
|
||||
return get_texture_color_at(texture, x, y)
|
||||
end
|
||||
|
||||
function bilinear_filtering(texture, pos_uv)
|
||||
local x = pos_uv[1]*texture.width
|
||||
local x_line = number_ext.round(x)
|
||||
local y = (1-pos_uv[2])*texture.height
|
||||
local y_line = number_ext.round(y)
|
||||
|
||||
local affected, affected_alpha = 0, 0
|
||||
local avg_alpha = 0
|
||||
local avg = {0, 0, 0}
|
||||
for xf = -1, 1, 2 do
|
||||
local px = x+xf*0.5
|
||||
local f1 = math.max(0, xf*x_line-px)
|
||||
for xf = -1, 1, 2 do
|
||||
local py = y+xf*0.5
|
||||
local f2 = math.max(0, xf*y_line-py)
|
||||
local factor = f1 * f2
|
||||
if factor > 0 then
|
||||
local a, r, g, b = unpack(get_texture_color_at(texture, math.floor(px), math.floor(py)))
|
||||
affected = affected + factor * a
|
||||
avg = vector.add(avg, vector.multiply({r, g, b}, factor * a))
|
||||
affected_alpha = affected_alpha + factor
|
||||
avg_alpha = avg_alpha + factor * a
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
avg_alpha = avg_alpha / affected_alpha
|
||||
avg = vector.multiply(avg, 1 / affected)
|
||||
local color = {avg_alpha, unpack(avg)}
|
||||
return color
|
||||
end
|
||||
|
||||
local errors = {
|
||||
"Output and input path need to be given",
|
||||
"Couldn't create output file",
|
||||
"Output or input file doesn't exist or can't be read/written",
|
||||
"File couldn't be written"
|
||||
}
|
||||
|
||||
function read_texture(path_to_texture)
|
||||
local last_dot
|
||||
for i = path_to_texture:len(), 1, -1 do
|
||||
if path_to_texture:sub(i, i) == "." then
|
||||
last_dot = i
|
||||
break
|
||||
elseif path_to_texture:sub(i, i) == "/" then
|
||||
break
|
||||
end
|
||||
end
|
||||
local path_to_output
|
||||
if path_to_texture:sub(last_dot+1):lower() == "sif" then -- we can assume it's already .sif
|
||||
path_to_output = path_to_texture
|
||||
else -- else, convert
|
||||
if not last_dot then
|
||||
path_to_output = path_to_texture..".sif"
|
||||
else
|
||||
path_to_output = path_to_texture:sub(1, last_dot-1)..".sif"
|
||||
end
|
||||
local response_code = os.execute('java -classpath "'..minetest.get_modpath("voxelizer")..'/production" TextureLoader "'..path_to_texture..'" "'..path_to_output..'"')
|
||||
if response_code ~= 0 then
|
||||
return errors[response_code] or "Texture couldn't be converted"
|
||||
end
|
||||
end
|
||||
|
||||
local texture_content = io.open(path_to_output, "rb")
|
||||
-- Image ending : .sif (Simple Image Format)
|
||||
-- 4 bytes image header (2 bytes width, 2 bytes height)
|
||||
-- Content : 4 byte ARGB colors
|
||||
local image={}
|
||||
image.width = texture_content:read(1):byte()*255+texture_content:read(1):byte()
|
||||
image.height = texture_content:read(1):byte()*255+texture_content:read(1):byte()
|
||||
local bytes = texture_content:read("*all")
|
||||
for i=1, bytes:len(), 4 do
|
||||
table.insert(image, rgba_tuple_to_number(bytes:byte(i), bytes:byte(i+1), bytes:byte(i+2), bytes:byte(i+3)))
|
||||
end
|
||||
texture_content:close()
|
||||
return image
|
||||
end
|
After Width: | Height: | Size: 250 B |
|
@ -0,0 +1,88 @@
|
|||
vector={}
|
||||
|
||||
vector.subtract=function(v1, v2)
|
||||
local res={}
|
||||
for i=1, #v1 do
|
||||
res[i] = v1[i]-v2[i]
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
vector.add=function(v1, v2)
|
||||
local res={}
|
||||
for i=1, #v1 do
|
||||
res[i] = v1[i]+v2[i]
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
vector.multiply_vector=function(v1, v2)
|
||||
local res={}
|
||||
for i=1, #v1 do
|
||||
res[i] = v1[i]*v2[i]
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
-- Scalar multiplication
|
||||
vector.multiply=function(v1, s2)
|
||||
local res={}
|
||||
for i=1, #v1 do
|
||||
res[i] = v1[i]*s2
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
vector.divide=function(v1, v2)
|
||||
local res={}
|
||||
for i=1, #v1 do
|
||||
if v2[i] ~= 0 then
|
||||
res[i] = v1[i]/v2[i]
|
||||
else
|
||||
res[i] = 0
|
||||
end
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
vector.length=function(v)
|
||||
local res=0
|
||||
for i=1, #v do
|
||||
res = res + (v[i] * v[i])
|
||||
end
|
||||
return math.sqrt(res)
|
||||
end
|
||||
|
||||
vector.floor=function(v)
|
||||
local res={}
|
||||
for i=1, #v do
|
||||
res[i] = math.floor(v[i])
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
vector.convert=function(v)
|
||||
return {x=v[1], y=v[2], z=v[3]}
|
||||
end
|
||||
|
||||
vector.to_minetest=vector.convert
|
||||
|
||||
vector.from_minetest = function(v)
|
||||
return {v.x, v.y, v.z}
|
||||
end
|
||||
|
||||
vector.clamp=function(v, min, max)
|
||||
local res={}
|
||||
for i=1, #v do
|
||||
res[i] = math.max(min, math.min(v[i], max))
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
vector.to_string = function(v)
|
||||
local c = {}
|
||||
for i=1, #v do
|
||||
c[i] = number_ext.round(v[i], 100)
|
||||
end
|
||||
return table.concat(c, ", ")
|
||||
end
|
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<output url="file://$MODULE_DIR$/production" />
|
||||
<output-test url="file://$MODULE_DIR$/test/voxelizer" />
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|