forked from VoxeLibre/VoxeLibre
add color tools for minetestmapper
This commit is contained in:
parent
62bb73539b
commit
34a8ebf023
4030
tools/colors.txt
4030
tools/colors.txt
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,4 @@
|
|||
Miroslav Bendík <miroslav.bendik@gmail.com>
|
||||
ShadowNinja <shadowninja@minetest.net>
|
||||
sfan5 <sfan5@live.de>
|
||||
kno10 <erich.schubert@gmail.com>
|
|
@ -0,0 +1,22 @@
|
|||
Copyright (c) 2013-2014, Miroslav Bendík and various contributors (see AUTHORS)
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -0,0 +1,13 @@
|
|||
Customized version of the minetestmapper utilities for voxelibre.
|
||||
|
||||
This is *not* the full minetestmapper, just the mod + script to generate colors.txt
|
||||
|
||||
Original version:
|
||||
https://github.com/minetest/minetestmapper/tree/master/util/
|
||||
|
||||
To use minetestmapper, first install minetestmapper (e.g., "apt install minetestmapper").
|
||||
|
||||
Example call:
|
||||
```
|
||||
minetestmapper -i worlds/myworld --colors games/voxelibre/tools/colors.txt --drawalpha -o /tmp/map.png
|
||||
```
|
|
@ -0,0 +1,65 @@
|
|||
local function get_tile(tiles, n)
|
||||
local tile = tiles[n]
|
||||
if type(tile) == 'table' then
|
||||
return tile.name or tile.image
|
||||
end
|
||||
return tile
|
||||
end
|
||||
|
||||
local function pairs_s(dict)
|
||||
local keys = {}
|
||||
for k in pairs(dict) do
|
||||
keys[#keys+1] = k
|
||||
end
|
||||
table.sort(keys)
|
||||
return ipairs(keys)
|
||||
end
|
||||
|
||||
minetest.register_chatcommand("dumpnodes", {
|
||||
description = "Dump node and texture list for use with minetestmapper",
|
||||
func = function()
|
||||
local ntbl = {}
|
||||
for _, nn in pairs_s(minetest.registered_nodes) do
|
||||
local prefix, name = nn:match('(.*):(.*)')
|
||||
if prefix == nil or name == nil then
|
||||
print("ignored(1): " .. nn)
|
||||
else
|
||||
if ntbl[prefix] == nil then
|
||||
ntbl[prefix] = {}
|
||||
end
|
||||
ntbl[prefix][name] = true
|
||||
end
|
||||
end
|
||||
local out, err = io.open(minetest.get_worldpath() .. "/nodes.txt", 'wb')
|
||||
if not out then
|
||||
return true, err
|
||||
end
|
||||
local n = 0
|
||||
for _, prefix in pairs_s(ntbl) do
|
||||
out:write('# ' .. prefix .. '\n')
|
||||
for _, name in pairs_s(ntbl[prefix]) do
|
||||
local nn = prefix .. ":" .. name
|
||||
local nd = minetest.registered_nodes[nn]
|
||||
local tiles = nd.tiles or nd.tile_images
|
||||
if tiles == nil or nd.drawtype == 'airlike' then
|
||||
print("ignored(2): " .. nn)
|
||||
else
|
||||
local tex = get_tile(tiles, 1)
|
||||
tex = (tex .. '^'):match('%(*(.-)%)*^') -- strip modifiers
|
||||
if tex:find("[combine", 1, true) then
|
||||
tex = tex:match('.-=([^:]-)') -- extract first texture
|
||||
end
|
||||
local opts = ""
|
||||
if nd.paramtype2 and nd.paramtype2:sub(1,5) == "color" and nd.palette ~= "" then
|
||||
opts = " " .. nd.paramtype2 .. " " .. nd.palette
|
||||
end
|
||||
out:write(nn .. ' ' .. tex .. opts .. '\n')
|
||||
n = n + 1
|
||||
end
|
||||
end
|
||||
out:write('\n')
|
||||
end
|
||||
out:close()
|
||||
return true, n .. " nodes dumped."
|
||||
end,
|
||||
})
|
|
@ -0,0 +1,2 @@
|
|||
name = dumpnodes
|
||||
description = minetestmapper development mod (node dumper)
|
|
@ -0,0 +1,210 @@
|
|||
#!/usr/bin/env python3
|
||||
import sys
|
||||
import os.path
|
||||
import getopt
|
||||
import re
|
||||
from math import sqrt
|
||||
try:
|
||||
from PIL import Image
|
||||
except:
|
||||
print("Could not load image routines, install PIL ('pillow' on pypi)!", file=sys.stderr)
|
||||
exit(1)
|
||||
|
||||
############
|
||||
############
|
||||
# Instructions for generating a colors.txt file for custom games and/or mods:
|
||||
# 1) Add the dumpnodes mod to a Minetest world with the chosen game and mods enabled.
|
||||
# 2) Join ingame and run the /dumpnodes chat command.
|
||||
# 3) Run this script and poin it to the installation path of the game using -g,
|
||||
# the path(s) where mods are stored using -m and the nodes.txt in your world folder.
|
||||
# Example command line:
|
||||
# ./util/generate_colorstxt.py --game /usr/share/minetest/games/minetest_game \
|
||||
# -m ~/.minetest/mods ~/.minetest/worlds/my_world/nodes.txt
|
||||
# 4) Copy the resulting colors.txt file to your world folder or to any other places
|
||||
# and use it with minetestmapper's --colors option.
|
||||
###########
|
||||
###########
|
||||
|
||||
# minimal sed syntax, s|match|replace| and /match/d supported
|
||||
REPLACEMENTS = [
|
||||
# Delete some nodes that are usually hidden
|
||||
r'/^fireflies:firefly /d',
|
||||
r'/^butterflies:butterfly_/d',
|
||||
# Nicer colors for water and lava
|
||||
r's/^(default:(river_)?water_(flowing|source)) [0-9 ]+$/\1 39 66 106 128 224/',
|
||||
r's/^(default:lava_(flowing|source)) [0-9 ]+$/\1 255 100 0/',
|
||||
r's/^(mclx?_core:(river_)?water_(flowing|source)) [0-9 ]+$/\1 35 66 128 224 128/',
|
||||
r's/^(mcl_core:lava_(flowing|source)) [0-9 ]+$/\1 230 90 0/',
|
||||
# Transparency for glass nodes and panes
|
||||
r's/^(default:.*glass) (\d+ \d+ \d+)( \d+)*$/\1 \2 64 16/',
|
||||
r's/^(doors:.*glass[^ ]*) (\d+ \d+ \d+)( \d+)*$/\1 \2 64 16/',
|
||||
r's/^(mcl_core:.*glass[^ ]*) (\d+ \d+ \d+)( \d+)*$/\1 \2 64 16/',
|
||||
r's/^(xpanes:.*(pane|bar)[^ ]*) (\d+ \d+ \d+)( \d+)*$/\1 \3 64 16/',
|
||||
r's/^(mcl_core:.*leaves(?:_orphan)?) (\d+ \d+ \d+)( \d+)*$/\1 \2/', # no alpha
|
||||
r's/^(mcl_core:.*ice(?:_\d+)?) (\d+ \d+ \d+)( \d+)*$/\1 \2/', # no alpha
|
||||
r's/^(mcl_core:snow) (\d+ \d+ \d+)( \d+)*$/\1 \2 223 31/', # almost no alpha
|
||||
]
|
||||
|
||||
def usage():
|
||||
print("Usage: generate_colorstxt.py [options] [input file] [output file]")
|
||||
print("If not specified the input file defaults to ./nodes.txt and the output file to ./colors.txt")
|
||||
print(" -g / --game <folder>\t\tSet path to the game (for textures), required")
|
||||
print(" -m / --mods <folder>\t\tAdd search path for mod textures")
|
||||
print(" --replace <file>\t\tLoad replacements from file (ADVANCED)")
|
||||
|
||||
def collect_files(path):
|
||||
for dirpath, dirnames, filenames in os.walk(path):
|
||||
for f in filenames:
|
||||
if not f in textures:
|
||||
textures[f] = os.path.join(dirpath, f)
|
||||
|
||||
def average_color(filename, color2):
|
||||
inp = Image.open(filename).convert('RGBA')
|
||||
data = inp.load()
|
||||
|
||||
c, w = [0, 0, 0], 0
|
||||
for y in range(inp.size[0]):
|
||||
for x in range(inp.size[1]):
|
||||
px = data[y, x]
|
||||
a = px[3] / 255
|
||||
if a == 0: continue
|
||||
c[0] = c[0] + px[0] * a
|
||||
c[1] = c[1] + px[1] * a
|
||||
c[2] = c[2] + px[2] * a
|
||||
w = w + a
|
||||
|
||||
if w == 0:
|
||||
print(f"didn't find color for '{os.path.basename(filename)}'", file=sys.stderr)
|
||||
return "0 0 0"
|
||||
c0, c1, c2 = c[0] / w, c[1] / w, c[2] / w
|
||||
if color2: # param2 blending
|
||||
c0, c1, c2 = c0 * color2[0] / 255., c1 * color2[1] / 255., c2 * color2[2] / 255.
|
||||
# for alpha, find maximum alpha in chunks to account for complex textures
|
||||
a = 0
|
||||
for y2 in range(0,inp.size[0],8):
|
||||
for x2 in range(0,inp.size[1],8):
|
||||
a2, n = 0, 0
|
||||
for y in range(y2, min(y2+16,inp.size[0])):
|
||||
for x in range(x2, min(x2+16,inp.size[1])):
|
||||
a2 = a2 + data[y,x][3]
|
||||
n = n + 1
|
||||
a2 = a2 / n
|
||||
a = max(a, a2)
|
||||
|
||||
if a > 0 and a < 190:
|
||||
return "%d %d %d %d" % (c0, c1, c2, a)
|
||||
return "%d %d %d" % (c0, c1, c2)
|
||||
|
||||
def get_param2_color(filename, param2):
|
||||
if not filename: return None
|
||||
inp = Image.open(filename).convert('RGBA')
|
||||
data = inp.load()
|
||||
x, y = param2 % inp.size[0], param2 // inp.size[0]
|
||||
col = data[y, x]
|
||||
return col[0], col[1], col[2] # copy
|
||||
|
||||
def apply_sed(line, exprs):
|
||||
for expr in exprs:
|
||||
if expr[0] == '/':
|
||||
if not expr.endswith("/d"): raise ValueError()
|
||||
if re.search(expr[1:-2], line):
|
||||
return ''
|
||||
elif expr[0] == 's':
|
||||
expr = expr.split(expr[1])
|
||||
if len(expr) != 4 or expr[3] != '': raise ValueError()
|
||||
line = re.sub(expr[1], expr[2], line)
|
||||
else:
|
||||
raise ValueError()
|
||||
return line
|
||||
#
|
||||
|
||||
try:
|
||||
opts, args = getopt.getopt(sys.argv[1:], "hg:m:", ["help", "game=", "mods=", "replace="])
|
||||
except getopt.GetoptError as e:
|
||||
print(str(e))
|
||||
exit(1)
|
||||
if ('-h', '') in opts or ('--help', '') in opts:
|
||||
usage()
|
||||
exit(0)
|
||||
|
||||
input_file = "./nodes.txt"
|
||||
output_file = "./colors.txt"
|
||||
texturepaths = []
|
||||
|
||||
try:
|
||||
gamepath = next(o[1] for o in opts if o[0] in ('-g', '--game'))
|
||||
if not os.path.isdir(os.path.join(gamepath, "mods")):
|
||||
print(f"'{gamepath}' doesn't exist or does not contain a game.", file=sys.stderr)
|
||||
exit(1)
|
||||
texturepaths.append(gamepath)
|
||||
except StopIteration:
|
||||
print("No game path set but one is required. (see --help)", file=sys.stderr)
|
||||
exit(1)
|
||||
|
||||
try:
|
||||
tmp = next(o[1] for o in opts if o[0] == "--replace")
|
||||
REPLACEMENTS.clear()
|
||||
with open(tmp, 'r') as f:
|
||||
for line in f:
|
||||
if not line or line[0] == '#': continue
|
||||
REPLACEMENTS.append(line.strip())
|
||||
except StopIteration:
|
||||
pass
|
||||
|
||||
for o in opts:
|
||||
if o[0] not in ('-m', '--mods'): continue
|
||||
if not os.path.isdir(o[1]):
|
||||
print(f"Given path '{o[1]}' does not exist.'", file=sys.stderr)
|
||||
exit(1)
|
||||
texturepaths.append(o[1])
|
||||
|
||||
if len(args) > 2:
|
||||
print("Too many arguments.", file=sys.stderr)
|
||||
exit(1)
|
||||
if len(args) > 1:
|
||||
output_file = args[1]
|
||||
if len(args) > 0:
|
||||
input_file = args[0]
|
||||
|
||||
if not os.path.exists(input_file) or os.path.isdir(input_file):
|
||||
print(f"Input file '{input_file}' does not exist.", file=sys.stderr)
|
||||
exit(1)
|
||||
|
||||
#
|
||||
|
||||
print(f"Collecting textures from {len(texturepaths)} path(s)... ", end="", flush=True)
|
||||
textures = {}
|
||||
for path in texturepaths:
|
||||
collect_files(path)
|
||||
print("done", len(textures), "files")
|
||||
|
||||
print("Processing nodes...")
|
||||
fin = open(input_file, 'r')
|
||||
fout = open(output_file, 'w')
|
||||
n = 0
|
||||
for line in fin:
|
||||
line = line.rstrip('\r\n')
|
||||
if not line or line[0] == '#':
|
||||
fout.write(line + '\n')
|
||||
continue
|
||||
line = line.split(" ")
|
||||
node, tex = line[0], line[1]
|
||||
if not tex or tex == "blank.png":
|
||||
continue
|
||||
if tex not in textures:
|
||||
print(f"skip {node} texture {tex} not found")
|
||||
continue
|
||||
# TODO: full param2 support
|
||||
color2 = None
|
||||
if len(line) > 3 and line[2].startswith("color"):
|
||||
color2 = get_param2_color(textures.get(line[3]), 0)
|
||||
color = average_color(textures[tex], color2)
|
||||
line = f"{node} {color}"
|
||||
#print(f"ok {node}")
|
||||
line = apply_sed(line, REPLACEMENTS)
|
||||
if line:
|
||||
fout.write(line + '\n')
|
||||
n += 1
|
||||
fin.close()
|
||||
fout.close()
|
||||
print(f"Done, {n} entries written.")
|
Loading…
Reference in New Issue