Fork of git@bananach.space:advtrains_netmapper.git
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

384 lines
9.8 KiB

3 years ago
-- advtrains track map generator
-- Usage: lua main.lua path/to/world
3 years ago
-- Viewport maximum coordinate in all directions
local maxc = 5000
-- embed an image called "world.png"
local wimg = false
3 years ago
-- image file resolution (not world resolution!)
local wimresx = 3000
local wimresy = 3000
-- one pixel is ... nodes
local wimscale = 4
-- y ranges and line colors
-- Minimum y level drawn
local drminy = -30
-- Color of min y level
local colminy = {r=0, g=0, b=255}
-- Intermediate color
local colmedy = {r=255, g=0, b=0}
-- Maximum y level drawn
local drmaxy = 80
-- Color of max y level
local colmaxy = {r=255, g=255, b=0}
datapath = (arg[1] or "").."/"
3 years ago
--Constant for maximum connection value/division of the circle
AT_CMAX = 16
advtrains = {}
minetest = {}
core = minetest
--table for track nodes/connections
trackconns = {}
-- math library seems to be missing this function
math.hypot = function(a,b) return math.sqrt(a*a + b*b) end
-- need to declare this for trackdefs
function attrans(str) return str end
-- pos to string
local function pts(pos)
return pos.x .. "," .. pos.y .. "," .. pos.z
end
--Advtrains dump (special treatment of pos and sigd)
function atdump(t, intend)
local str
if type(t)=="table" then
if t.x and t.y and t.z then
str=minetest.pos_to_string(t)
elseif t.p and t.s then -- interlocking sigd
str="S["..minetest.pos_to_string(t.p).."/"..t.s.."]"
else
str="{"
local intd = (intend or "") .. " "
for k,v in pairs(t) do
if type(k)~="string" or not string.match(k, "^path[_]?") then
-- do not print anything path-related
str = str .. "\n" .. intd .. atdump(k, intd) .. " = " ..atdump(v, intd)
end
end
str = str .. "\n" .. (intend or "") .. "}"
end
elseif type(t)=="boolean" then
if t then
str="true"
else
str="false"
end
elseif type(t)=="function" then
str="<function>"
elseif type(t)=="userdata" then
str="<userdata>"
else
str=""..t
end
return str
end
dofile("vector.lua")
dofile("serialize.lua")
dofile("helpers.lua")
dofile("tracks.lua")
dofile("track_defs.lua")
dofile("nodedb.lua")
function parse_args(argv)
local i = 1
local no_trains = false
local datapath, mappath, worldimage
while i <= #argv do
local a = argv[i]
if (a == "-m") or (a == "--map-file") then
-- only draw trains – requires specifying an already drawn file
i = i+1
if not argv[i] then
error(("missing filename after `%s'"):format(a))
end
mappath = argv[i]
elseif (a == "-t") or (a == "--no-trains") then
-- do not draw trains
no_trains = true
elseif (a == "-w") or (a == "--world-image") then
-- overlay over world image
i = i+1
if not argv[i] then
error(("missing filename after `%s'"):format(a))
end
worldimage = argv[i]
else
datapath = a
end
i = i + 1
end
return datapath, mappath, no_trains, worldimage
end
datapath, mappath, no_trains, worldimage = parse_args(arg)
3 years ago
-- Load saves
local file, err = io.open(datapath.."advtrains_trains", "r")
3 years ago
local tbl = minetest.deserialize(file:read("*a"))
if type(tbl) ~= "table" then
error("Trains file: not a table")
3 years ago
end
advtrains.trains = tbl
file:close()
--ndb contains the defs, while ndb2 is the actual contents
file, err = io.open(datapath.."advtrains_ndb", "r")
tbl = minetest.deserialize(file:read("*a"))
if type(tbl) ~= "table" then
error("Node database file: not a table")
3 years ago
end
advtrains.ndb.load_data(tbl)
3 years ago
file:close()
-- open svg file
local svgfile = io.open(datapath.."out.svg", "w")
3 years ago
if mappath then
mapfile = io.open(mappath, "r")
cont = mapfile:read("*a"):sub(1, -7) -- remove </svg> end tag
svgfile:write(cont)
else
svgfile:write([[
3 years ago
<?xml version="1.0" standalone="no" ?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg width="1024" height="800" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" ]])
svgfile:write('viewBox="'..(-maxc)..' '..(-maxc)..' '..(2*maxc)..' '..(2*maxc)..'" >')
3 years ago
svgfile:write([[
<circle cx="0" cy="0" r="5" stroke="red" stroke-width="1" />
3 years ago
]])
end
if worldimage then
3 years ago
local wimx = -(wimresx*wimscale/2)
local wimy = -(wimresy*wimscale/2)
local wimw = wimresx*wimscale
local wimh = wimresy*wimscale
svgfile:write('<image xlink:href="'..worldimage..'" x="'..wimx..'" y="'..wimy..'" height="'..wimh..'px" width="'..wimw..'px"/>')
3 years ago
end
local function writec(text)
3 years ago
--print("\n"..text)
3 years ago
svgfile:write("<!-- " .. text .. " -->\n")
end
-- everything set up. Start generating an SVG
-- All nodes that have been fit into a polyline are removed from the NDB, in order to not draw them again.
-- "Restart points" for the breadth-first traverser (set when more than 2 conns present)
-- {pos = <position>, connid = <int>, conn = <conndef>}
-- Note that the node at "pos" is already deleted from the NDB at the time of recall, therefore "conn" is specified
local bfs_rsp = {}
3 years ago
local ndb_nodes_handled = 0
3 years ago
-- Points of the current polyline. Inserted as xyz vectors, maybe we'll use the y value one day
local current_polyline = {}
-- Traverser function from interlocking, highly modified
local function gen_rsp_polyline(rsp)
-- trick a bit
local pos, connid, conns = rsp.pos, 1, {rsp.conn}
current_polyline[#current_polyline+1] = pos
while true do
local adj_pos, adj_connid, conn_idx, nextrail_y, next_conns = advtrains.get_adjacent_rail(pos, conns, connid)
if not adj_pos then
-- proceed one more node, for seamless turnout transitions
current_polyline[#current_polyline+1] = advtrains.pos_add_dir(pos, conns[connid].c)
3 years ago
return
end
-- continue traversing
local conn_mainbranch
for nconnid, nconn in ipairs(next_conns) do
if adj_connid ~= nconnid then
if not conn_mainbranch then
--use the first one found to continue
conn_mainbranch = nconnid
--writec(nconnid.." nconn mainbranch")
else
-- insert bfs reminders for other conns
table.insert(bfs_rsp, {pos = adj_pos, connid = nconnid, conn = nconn})
--writec(nconnid.." nconn bfs")
end
end
end
-- save in polyline and delete from ndb
--writec("Saved pos: "..pts(adj_pos).." mainbranch cont "..conn_mainbranch.." nextconns "..atdump(next_conns))
current_polyline[#current_polyline+1] = adj_pos
advtrains.ndb.clear(adj_pos)
3 years ago
ndb_nodes_handled = ndb_nodes_handled + 1
3 years ago
pos, connid, conns = adj_pos, conn_mainbranch, next_conns
end
end
3 years ago
plcnt = 0
3 years ago
local function hexcolor(clr)
return "#"..advtrains.hex(clr.r)..advtrains.hex(clr.g)..advtrains.hex(clr.b)
end
local function cfactor(ry)
local y = ry - (ry%4)
local fac = (y-drminy)/(drmaxy-drminy)
return fac
end
local function pl_header(fac)
local color
if fac<0.5 then
color = {
r = colminy.r + (colmedy.r-colminy.r)*2*fac,
g = colminy.g + (colmedy.g-colminy.g)*2*fac,
b = colminy.b + (colmedy.b-colminy.b)*2*fac,
}
else
color = {
r = colmedy.r + (colmaxy.r-colmedy.r)*(2*fac-1),
g = colmedy.g + (colmaxy.g-colmedy.g)*(2*fac-1),
b = colmedy.b + (colmaxy.b-colmedy.b)*(2*fac-1),
}
end
local c = hexcolor(color)
return '<polyline style="fill:none;stroke:'..c..';stroke-width:1" points="'
end
3 years ago
local function polyline_write(pl)
local p1y = cfactor(pl[1].y)
local str = {}
if p1y <= 1 and p1y >= 0 then
table.insert(str, pl_header(p1y))
end
3 years ago
local i
local e
local lastcf = p1y
3 years ago
local lastldir = {x=0, y=0}
for i=1,#pl do
e = pl[i]
local cf = cfactor(e.y)
if cf ~= lastcf then
if lastcf <= 1 and lastcf >= 0 then
-- insert final point
-- Note that we mirror y, so positive z is up
table.insert(str, e.x .. "," .. -(e.z) .. " ")
table.insert(str, '" />\n')
plcnt = plcnt + 1
end
if cf <= 1 and cf >= 0 then
table.insert(str, pl_header(cf))
end
end
if cf <= 1 and cf >= 0 then
-- Note that we mirror y, so positive z is up
table.insert(str, e.x .. "," .. -(e.z) .. " ")
end
lastcf = cf
3 years ago
end
if lastcf <= 1 and lastcf >= 0 then
table.insert(str, '" />\n')
end
3 years ago
svgfile:write(table.concat(str))
3 years ago
plcnt = plcnt + 1
3 years ago
end
3 years ago
-- while there are entries in the nodedb
-- 1. find a starting point
if not mappath then
3 years ago
local stpos, conns = advtrains.ndb.mapper_find_starting_point()
while stpos do
3 years ago
writec("Restart at position "..pts(stpos))
for connid, conn in ipairs(conns) do
table.insert(bfs_rsp, {pos = stpos, connid = connid, conn = conn})
end
advtrains.ndb.clear(stpos)
3 years ago
-- 2. while there are BFS entries
while #bfs_rsp > 0 do
-- make polylines
local current_rsp = bfs_rsp[#bfs_rsp]
bfs_rsp[#bfs_rsp] = nil
--print("Starting polyline at "..pts(current_rsp.pos).."/"..current_rsp.connid)
current_polyline = {}
gen_rsp_polyline(current_rsp)
polyline_write(current_polyline)
io.write("Progress ", ndb_nodes_handled, "+", ndb_nodes_notrack, "/", ndb_nodes_total, "=", math.floor(((ndb_nodes_handled+ndb_nodes_notrack)/ndb_nodes_total)*100), "%\r")
end
stpos, conns = advtrains.ndb.mapper_find_starting_point()
3 years ago
end
end
-- draw trains
trains = 0
stopped = 0
lines = {}
running = {}
if not no_trains then
for i,v in pairs(advtrains.trains) do
pos = v.last_pos
color = "green"
if v.velocity == 0 then
color = "orange"
stopped = stopped + 1
end
svgfile:write("<circle cx=\""..pos.x.."\" cy=\""..-pos.z.."\" r=\"3\" stroke=\""..color.."\" stroke-width=\"1\" fill=\"none\" />")
if v.line then
lines[v.line] = (lines[v.line] or 0) + 1
if v.velocity ~= 0 then
running[v.line] = (running[v.line] or 0) + 1
end
svgfile:write(" <text x=\""..(pos.x+5).."\" y=\""..-pos.z.."\" class=\"trainline\">"..v.line.."</text>")
end
trains = trains+1
end
end
3 years ago
svgfile:write("</svg>")
svgfile:close()
3 years ago
print("\nWrote",plcnt,"polylines. Processed", ndb_nodes_handled, "track,",ndb_nodes_notrack, "non-track nodes out of", ndb_nodes_total)
print("Drew "..trains.." trains. "..stopped.." stopped trains.")
print("\n Number of trains moving/total:")
for i,v in pairs(lines) do
print(i..": "..(running[i] or 0).."/"..v)
end