lifo-advtrains_netmapper/main.lua

384 lines
9.8 KiB
Lua
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

-- advtrains track map generator
-- Usage: lua main.lua path/to/world
-- Viewport maximum coordinate in all directions
local maxc = 5000
-- embed an image called "world.png"
local wimg = false
-- 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 "").."/"
--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)
-- Load saves
local file, err = io.open(datapath.."advtrains_trains", "r")
local tbl = minetest.deserialize(file:read("*a"))
if type(tbl) ~= "table" then
error("Trains file: not a table")
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")
end
advtrains.ndb.load_data(tbl)
file:close()
-- open svg file
local svgfile = io.open(datapath.."out.svg", "w")
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([[
<?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)..'" >')
svgfile:write([[
<circle cx="0" cy="0" r="5" stroke="red" stroke-width="1" />
]])
end
if worldimage then
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"/>')
end
local function writec(text)
--print("\n"..text)
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 = {}
local ndb_nodes_handled = 0
-- 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)
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)
ndb_nodes_handled = ndb_nodes_handled + 1
pos, connid, conns = adj_pos, conn_mainbranch, next_conns
end
end
plcnt = 0
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
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
local i
local e
local lastcf = p1y
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
end
if lastcf <= 1 and lastcf >= 0 then
table.insert(str, '" />\n')
end
svgfile:write(table.concat(str))
plcnt = plcnt + 1
end
-- while there are entries in the nodedb
-- 1. find a starting point
if not mappath then
local stpos, conns = advtrains.ndb.mapper_find_starting_point()
while stpos do
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)
-- 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()
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
svgfile:write("</svg>")
svgfile:close()
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