main.lua 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387
  1. -- advtrains track map generator
  2. -- Usage: lua main.lua path/to/world
  3. -- Viewport maximum coordinate in all directions
  4. local maxc = 5000
  5. -- embed an image called "world.png"
  6. local wimg = false
  7. -- image file resolution (not world resolution!)
  8. local wimresx = 3000
  9. local wimresy = 3000
  10. -- one pixel is ... nodes
  11. local wimscale = 4
  12. -- y ranges and line colors
  13. -- Minimum y level drawn
  14. local drminy = -30
  15. -- Color of min y level
  16. local colminy = {r=0, g=0, b=255}
  17. -- Intermediate color
  18. local colmedy = {r=255, g=0, b=0}
  19. -- Maximum y level drawn
  20. local drmaxy = 80
  21. -- Color of max y level
  22. local colmaxy = {r=255, g=255, b=0}
  23. datapath = (arg[1] or "").."/"
  24. --Constant for maximum connection value/division of the circle
  25. AT_CMAX = 16
  26. advtrains = {}
  27. minetest = {}
  28. core = minetest
  29. --table for track nodes/connections
  30. trackconns = {}
  31. -- math library seems to be missing this function
  32. math.hypot = function(a,b) return math.sqrt(a*a + b*b) end
  33. -- need to declare this for trackdefs
  34. function attrans(str) return str end
  35. -- pos to string
  36. local function pts(pos)
  37. return pos.x .. "," .. pos.y .. "," .. pos.z
  38. end
  39. --Advtrains dump (special treatment of pos and sigd)
  40. function atdump(t, intend)
  41. local str
  42. if type(t)=="table" then
  43. if t.x and t.y and t.z then
  44. str=minetest.pos_to_string(t)
  45. elseif t.p and t.s then -- interlocking sigd
  46. str="S["..minetest.pos_to_string(t.p).."/"..t.s.."]"
  47. else
  48. str="{"
  49. local intd = (intend or "") .. " "
  50. for k,v in pairs(t) do
  51. if type(k)~="string" or not string.match(k, "^path[_]?") then
  52. -- do not print anything path-related
  53. str = str .. "\n" .. intd .. atdump(k, intd) .. " = " ..atdump(v, intd)
  54. end
  55. end
  56. str = str .. "\n" .. (intend or "") .. "}"
  57. end
  58. elseif type(t)=="boolean" then
  59. if t then
  60. str="true"
  61. else
  62. str="false"
  63. end
  64. elseif type(t)=="function" then
  65. str="<function>"
  66. elseif type(t)=="userdata" then
  67. str="<userdata>"
  68. else
  69. str=""..t
  70. end
  71. return str
  72. end
  73. dofile("vector.lua")
  74. local serialize = dofile("serialize.lua")
  75. dofile("helpers.lua")
  76. dofile("tracks.lua")
  77. dofile("track_defs.lua")
  78. dofile("nodedb.lua")
  79. function parse_args(argv)
  80. local i = 1
  81. local no_trains = false
  82. local datapath, mappath, worldimage
  83. while i <= #argv do
  84. local a = argv[i]
  85. if (a == "-m") or (a == "--map-file") then
  86. -- only draw trains – requires specifying an already drawn file
  87. i = i+1
  88. if not argv[i] then
  89. error(("missing filename after `%s'"):format(a))
  90. end
  91. mappath = argv[i]
  92. elseif (a == "-t") or (a == "--no-trains") then
  93. -- do not draw trains
  94. no_trains = true
  95. elseif (a == "-w") or (a == "--world-image") then
  96. -- overlay over world image
  97. i = i+1
  98. if not argv[i] then
  99. error(("missing filename after `%s'"):format(a))
  100. end
  101. worldimage = argv[i]
  102. else
  103. datapath = a
  104. end
  105. i = i + 1
  106. end
  107. return datapath, mappath, no_trains, worldimage
  108. end
  109. datapath, mappath, no_trains, worldimage = parse_args(arg)
  110. -- Load saves
  111. local tbl = serialize.read_from_file(datapath.."advtrains_core.ls")
  112. --local file, err = io.open(datapath.."advtrains_trains", "r")
  113. --local tbl = minetest.deserialize(file:read("*a"))
  114. if type(tbl) ~= "table" then
  115. error("Trains file: not a table")
  116. end
  117. advtrains.trains = tbl.trains
  118. --file:close()
  119. --ndb contains the defs, while ndb2 is the actual contents
  120. dofile("nodedb.lua")
  121. local file, err = io.open(datapath.."advtrains_ndb4.ls", "r")
  122. --tbl = minetest.deserialize(file:read("*a"))
  123. --if type(tbl) ~= "table" then
  124. --error("Node database file: not a table")
  125. --end
  126. --advtrains.ndb.load_data(tbl)
  127. advtrains.ndb.load_callback(file)
  128. --file:close()
  129. -- open svg file
  130. local svgfile = io.open(datapath.."out.svg", "w")
  131. if mappath then
  132. mapfile = io.open(mappath, "r")
  133. cont = mapfile:read("*a"):sub(1, -7) -- remove </svg> end tag
  134. svgfile:write(cont)
  135. else
  136. svgfile:write([[
  137. <?xml version="1.0" standalone="no" ?>
  138. <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
  139. "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
  140. <svg width="1024" height="800" xmlns="http://www.w3.org/2000/svg"
  141. xmlns:xlink="http://www.w3.org/1999/xlink" ]])
  142. svgfile:write('viewBox="'..(-maxc)..' '..(-maxc)..' '..(2*maxc)..' '..(2*maxc)..'" >')
  143. svgfile:write([[
  144. <circle cx="0" cy="0" r="5" stroke="red" stroke-width="1" />
  145. ]])
  146. end
  147. if worldimage then
  148. local wimx = -(wimresx*wimscale/2)
  149. local wimy = -(wimresy*wimscale/2)
  150. local wimw = wimresx*wimscale
  151. local wimh = wimresy*wimscale
  152. svgfile:write('<image xlink:href="'..worldimage..'" x="'..wimx..'" y="'..wimy..'" height="'..wimh..'px" width="'..wimw..'px"/>')
  153. end
  154. local function writec(text)
  155. --print("\n"..text)
  156. svgfile:write("<!-- " .. text .. " -->\n")
  157. end
  158. -- everything set up. Start generating an SVG
  159. -- All nodes that have been fit into a polyline are removed from the NDB, in order to not draw them again.
  160. -- "Restart points" for the breadth-first traverser (set when more than 2 conns present)
  161. -- {pos = <position>, connid = <int>, conn = <conndef>}
  162. -- Note that the node at "pos" is already deleted from the NDB at the time of recall, therefore "conn" is specified
  163. local bfs_rsp = {}
  164. local ndb_nodes_handled = 0
  165. -- Points of the current polyline. Inserted as xyz vectors, maybe we'll use the y value one day
  166. local current_polyline = {}
  167. -- Traverser function from interlocking, highly modified
  168. local function gen_rsp_polyline(rsp)
  169. -- trick a bit
  170. local pos, connid, conns = rsp.pos, 1, {rsp.conn}
  171. current_polyline[#current_polyline+1] = pos
  172. while true do
  173. local adj_pos, adj_connid, conn_idx, nextrail_y, next_conns = advtrains.get_adjacent_rail(pos, conns, connid)
  174. if not adj_pos then
  175. -- proceed one more node, for seamless turnout transitions
  176. current_polyline[#current_polyline+1] = advtrains.pos_add_dir(pos, conns[connid].c)
  177. return
  178. end
  179. -- continue traversing
  180. local conn_mainbranch
  181. for nconnid, nconn in ipairs(next_conns) do
  182. if adj_connid ~= nconnid then
  183. if not conn_mainbranch then
  184. --use the first one found to continue
  185. conn_mainbranch = nconnid
  186. --writec(nconnid.." nconn mainbranch")
  187. else
  188. -- insert bfs reminders for other conns
  189. table.insert(bfs_rsp, {pos = adj_pos, connid = nconnid, conn = nconn})
  190. --writec(nconnid.." nconn bfs")
  191. end
  192. end
  193. end
  194. -- save in polyline and delete from ndb
  195. --writec("Saved pos: "..pts(adj_pos).." mainbranch cont "..conn_mainbranch.." nextconns "..atdump(next_conns))
  196. current_polyline[#current_polyline+1] = adj_pos
  197. advtrains.ndb.clear(adj_pos)
  198. ndb_nodes_handled = ndb_nodes_handled + 1
  199. pos, connid, conns = adj_pos, conn_mainbranch, next_conns
  200. end
  201. end
  202. plcnt = 0
  203. local function hexcolor(clr)
  204. return "#"..advtrains.hex(clr.r)..advtrains.hex(clr.g)..advtrains.hex(clr.b)
  205. end
  206. local function cfactor(ry)
  207. local y = ry - (ry%4)
  208. local fac = (y-drminy)/(drmaxy-drminy)
  209. return fac
  210. end
  211. local function pl_header(fac)
  212. local color
  213. if fac<0.5 then
  214. color = {
  215. r = colminy.r + (colmedy.r-colminy.r)*2*fac,
  216. g = colminy.g + (colmedy.g-colminy.g)*2*fac,
  217. b = colminy.b + (colmedy.b-colminy.b)*2*fac,
  218. }
  219. else
  220. color = {
  221. r = colmedy.r + (colmaxy.r-colmedy.r)*(2*fac-1),
  222. g = colmedy.g + (colmaxy.g-colmedy.g)*(2*fac-1),
  223. b = colmedy.b + (colmaxy.b-colmedy.b)*(2*fac-1),
  224. }
  225. end
  226. local c = hexcolor(color)
  227. return '<polyline style="fill:none;stroke:'..c..';stroke-width:1" points="'
  228. end
  229. local function polyline_write(pl)
  230. local p1y = cfactor(pl[1].y)
  231. local str = {}
  232. if p1y <= 1 and p1y >= 0 then
  233. table.insert(str, pl_header(p1y))
  234. end
  235. local i
  236. local e
  237. local lastcf = p1y
  238. local lastldir = {x=0, y=0}
  239. for i=1,#pl do
  240. e = pl[i]
  241. local cf = cfactor(e.y)
  242. if cf ~= lastcf then
  243. if lastcf <= 1 and lastcf >= 0 then
  244. -- insert final point
  245. -- Note that we mirror y, so positive z is up
  246. table.insert(str, e.x .. "," .. -(e.z) .. " ")
  247. table.insert(str, '" />\n')
  248. plcnt = plcnt + 1
  249. end
  250. if cf <= 1 and cf >= 0 then
  251. table.insert(str, pl_header(cf))
  252. end
  253. end
  254. if cf <= 1 and cf >= 0 then
  255. -- Note that we mirror y, so positive z is up
  256. table.insert(str, e.x .. "," .. -(e.z) .. " ")
  257. end
  258. lastcf = cf
  259. end
  260. if lastcf <= 1 and lastcf >= 0 then
  261. table.insert(str, '" />\n')
  262. end
  263. svgfile:write(table.concat(str))
  264. plcnt = plcnt + 1
  265. end
  266. -- while there are entries in the nodedb
  267. -- 1. find a starting point
  268. if not mappath then
  269. local stpos, conns = advtrains.ndb.mapper_find_starting_point()
  270. while stpos do
  271. writec("Restart at position "..pts(stpos))
  272. for connid, conn in ipairs(conns) do
  273. table.insert(bfs_rsp, {pos = stpos, connid = connid, conn = conn})
  274. end
  275. advtrains.ndb.clear(stpos)
  276. -- 2. while there are BFS entries
  277. while #bfs_rsp > 0 do
  278. -- make polylines
  279. local current_rsp = bfs_rsp[#bfs_rsp]
  280. bfs_rsp[#bfs_rsp] = nil
  281. --print("Starting polyline at "..pts(current_rsp.pos).."/"..current_rsp.connid)
  282. current_polyline = {}
  283. gen_rsp_polyline(current_rsp)
  284. polyline_write(current_polyline)
  285. 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")
  286. end
  287. stpos, conns = advtrains.ndb.mapper_find_starting_point()
  288. end
  289. end
  290. -- draw trains
  291. trains = 0
  292. stopped = 0
  293. lines = {}
  294. running = {}
  295. if not no_trains then
  296. for i,v in pairs(advtrains.trains) do
  297. pos = v.last_pos
  298. color = "green"
  299. if v.velocity == 0 then
  300. color = "orange"
  301. stopped = stopped + 1
  302. end
  303. svgfile:write("<circle cx=\""..pos.x.."\" cy=\""..-pos.z.."\" r=\"3\" stroke=\""..color.."\" stroke-width=\"1\" fill=\"none\" />")
  304. if v.line then
  305. lines[v.line] = (lines[v.line] or 0) + 1
  306. if v.velocity ~= 0 then
  307. running[v.line] = (running[v.line] or 0) + 1
  308. end
  309. svgfile:write(" <text x=\""..(pos.x+5).."\" y=\""..-pos.z.."\" class=\"trainline\">"..v.line.."</text>")
  310. end
  311. trains = trains+1
  312. end
  313. end
  314. svgfile:write("</svg>")
  315. svgfile:close()
  316. print("\nWrote",plcnt,"polylines. Processed", ndb_nodes_handled, "track,",ndb_nodes_notrack, "non-track nodes out of", ndb_nodes_total)
  317. print("Drew "..trains.." trains. "..stopped.." stopped trains.")
  318. print("\n Number of trains moving/total:")
  319. for i,v in pairs(lines) do
  320. print(i..": "..(running[i] or 0).."/"..v)
  321. end