123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387 |
- -- 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")
- local serialize = 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 tbl = serialize.read_from_file(datapath.."advtrains_core.ls")
- --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.trains
- --file:close()
- --ndb contains the defs, while ndb2 is the actual contents
- dofile("nodedb.lua")
- local file, err = io.open(datapath.."advtrains_ndb4.ls", "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)
- advtrains.ndb.load_callback(file)
- --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
|