-- 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="" elseif type(t)=="userdata" then str="" 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.."trains.json", "w") if mappath then mapfile = io.open(mappath, "r") cont = mapfile:read("*a"):sub(1, -7) -- remove end tag svgfile:write(cont) else svgfile:write([[ [ ]]) 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 = , connid = , conn = } -- 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 poly_angle(l, e) local dx = (e.x - l.x) local dz = (e.z - l.z) return math.deg(math.atan2(dz, dx)) end local function polyline_write(pl) local str = {} table.insert(str, '{"type":"Feature","geometry":{"type":"LineString","coordinates":[') local i local e = nil local l = nil local last_angle = -1000 local lastcf = p1y local lastldir = {x=0, y=0} local consider = 0 --print("new polyline\n") for i=1,#pl do local first = true consider = 0 e = pl[i] -- print("point " .. e.x .. "x" .. e.z .. "x" .. e.y) if (l ~= nil) then local angle = poly_angle(l, e) if (angle ~= last_angle) then table.insert(str, "[".. l.x .. "," .. (l.z) .. "],") last_angle = angle end end l = e end if (e ~= nil) then table.insert(str, "[".. l.x .. "," .. (l.z) .. "]") end table.insert(str, ']},"properties":{"name":""}},\n') 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 --print("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) --break 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() --break end end svgfile:write("{}]") svgfile:close() print("\nWrote",plcnt,"polylines. Processed", ndb_nodes_handled, "track,",ndb_nodes_notrack, "non-track nodes out of", ndb_nodes_total)