aboutsummaryrefslogtreecommitdiff
path: root/advtrains/helpers.lua
diff options
context:
space:
mode:
Diffstat (limited to 'advtrains/helpers.lua')
-rw-r--r--advtrains/helpers.lua894
1 files changed, 447 insertions, 447 deletions
diff --git a/advtrains/helpers.lua b/advtrains/helpers.lua
index 65f25ba..3b0bedd 100644
--- a/advtrains/helpers.lua
+++ b/advtrains/helpers.lua
@@ -1,447 +1,447 @@
---advtrains by orwell96, see readme.txt
-
-local dir_trans_tbl={
- [0]={x=0, z=1, y=0},
- [1]={x=1, z=2, y=0},
- [2]={x=1, z=1, y=0},
- [3]={x=2, z=1, y=0},
- [4]={x=1, z=0, y=0},
- [5]={x=2, z=-1, y=0},
- [6]={x=1, z=-1, y=0},
- [7]={x=1, z=-2, y=0},
- [8]={x=0, z=-1, y=0},
- [9]={x=-1, z=-2, y=0},
- [10]={x=-1, z=-1, y=0},
- [11]={x=-2, z=-1, y=0},
- [12]={x=-1, z=0, y=0},
- [13]={x=-2, z=1, y=0},
- [14]={x=-1, z=1, y=0},
- [15]={x=-1, z=2, y=0},
-}
-
-local dir_angle_tbl={}
-for d,v in pairs(dir_trans_tbl) do
- local uvec = vector.normalize(v)
- dir_angle_tbl[d] = math.atan2(-uvec.x, uvec.z)
-end
-
-
-function advtrains.dir_to_angle(dir)
- return dir_angle_tbl[dir] or error("advtrains: in helpers.lua/dir_to_angle() given dir="..(dir or "nil"))
-end
-
-function advtrains.dirCoordSet(coord, dir)
- return vector.add(coord, advtrains.dirToCoord(dir))
-end
-advtrains.pos_add_dir = advtrains.dirCoordSet
-
-function advtrains.pos_add_angle(pos, ang)
- -- 0 is +Z -> meaning of sin/cos swapped
- return vector.add(pos, {x = -math.sin(ang), y = 0, z = math.cos(ang)})
-end
-
-function advtrains.dirToCoord(dir)
- return dir_trans_tbl[dir] or error("advtrains: in helpers.lua/dir_to_vector() given dir="..(dir or "nil"))
-end
-advtrains.dir_to_vector = advtrains.dirToCoord
-
-function advtrains.maxN(list, expectstart)
- local n=expectstart or 0
- while list[n] do
- n=n+1
- end
- return n-1
-end
-
-function advtrains.minN(list, expectstart)
- local n=expectstart or 0
- while list[n] do
- n=n-1
- end
- return n+1
-end
-
-function atround(number)
- return math.floor(number+0.5)
-end
-atfloor = math.floor
-
-
-function advtrains.round_vector_floor_y(vec)
- return {x=math.floor(vec.x+0.5), y=math.floor(vec.y), z=math.floor(vec.z+0.5)}
-end
-
-function advtrains.yawToDirection(yaw, conn1, conn2)
- if not conn1 or not conn2 then
- error("given nil to yawToDirection: conn1="..(conn1 or "nil").." conn2="..(conn1 or "nil"))
- end
- local yaw1 = advtrains.dir_to_angle(conn1)
- local yaw2 = advtrains.dir_to_angle(conn2)
- local adiff1 = advtrains.minAngleDiffRad(yaw, yaw1)
- local adiff2 = advtrains.minAngleDiffRad(yaw, yaw2)
-
- if math.abs(adiff2)<math.abs(adiff1) then
- return conn2
- else
- return conn1
- end
-end
-
-function advtrains.yawToAnyDir(yaw)
- local min_conn, min_diff=0, 10
- for conn, vec in pairs(advtrains.dir_trans_tbl) do
- local yaw1 = advtrains.dir_to_angle(conn)
- local diff = math.abs(advtrains.minAngleDiffRad(yaw, yaw1))
- if diff < min_diff then
- min_conn = conn
- min_diff = diff
- end
- end
- return min_conn
-end
-function advtrains.yawToClosestConn(yaw, conns)
- local min_connid, min_diff=1, 10
- for connid, conn in ipairs(conns) do
- local yaw1 = advtrains.dir_to_angle(conn.c)
- local diff = math.abs(advtrains.minAngleDiffRad(yaw, yaw1))
- if diff < min_diff then
- min_connid = connid
- min_diff = diff
- end
- end
- return min_connid
-end
-
-local pi, pi2 = math.pi, 2*math.pi
-function advtrains.minAngleDiffRad(r1, r2)
- while r1>pi2 do
- r1=r1-pi2
- end
- while r1<0 do
- r1=r1+pi2
- end
- while r2>pi2 do
- r2=r2-pi2
- end
- while r1<0 do
- r2=r2+pi2
- end
- local try1=r2-r1
- local try2=r2+pi2-r1
- local try3=r2-pi2-r1
-
- local minabs = math.min(math.abs(try1), math.abs(try2), math.abs(try3))
- if minabs==math.abs(try1) then
- return try1
- end
- if minabs==math.abs(try2) then
- return try2
- end
- if minabs==math.abs(try3) then
- return try3
- end
-end
-
-
--- Takes 2 connections (0...AT_CMAX) as argument
--- Returns the angle median of those 2 positions from the pov
--- of standing on the cdir1 side and looking towards cdir2
--- cdir1 - >NODE> - cdir2
-function advtrains.conn_angle_median(cdir1, cdir2)
- local ang1 = advtrains.dir_to_angle(advtrains.oppd(cdir1))
- local ang2 = advtrains.dir_to_angle(cdir2)
- return ang1 + advtrains.minAngleDiffRad(ang1, ang2)/2
-end
-
-function advtrains.merge_tables(a, ...)
- local new={}
- for _,t in ipairs({a,...}) do
- for k,v in pairs(t) do new[k]=v end
- end
- return new
-end
-function advtrains.save_keys(tbl, keys)
- local new={}
- for _,key in ipairs(keys) do
- new[key] = tbl[key]
- end
- return new
-end
-
-function advtrains.get_real_index_position(path, index)
- if not path or not index then return end
-
- local first_pos=path[math.floor(index)]
- local second_pos=path[math.floor(index)+1]
-
- if not first_pos or not second_pos then return nil end
-
- local factor=index-math.floor(index)
- local actual_pos={x=first_pos.x-(first_pos.x-second_pos.x)*factor, y=first_pos.y-(first_pos.y-second_pos.y)*factor, z=first_pos.z-(first_pos.z-second_pos.z)*factor,}
- return actual_pos
-end
-function advtrains.pos_median(pos1, pos2)
- return {x=pos1.x-(pos1.x-pos2.x)*0.5, y=pos1.y-(pos1.y-pos2.y)*0.5, z=pos1.z-(pos1.z-pos2.z)*0.5}
-end
-function advtrains.abs_ceil(i)
- return math.ceil(math.abs(i))*math.sign(i)
-end
-
-function advtrains.serialize_inventory(inv)
- local ser={}
- local liszts=inv:get_lists()
- for lisztname, liszt in pairs(liszts) do
- ser[lisztname]={}
- for idx, item in ipairs(liszt) do
- local istring=item:to_string()
- if istring~="" then
- ser[lisztname][idx]=istring
- end
- end
- end
- return minetest.serialize(ser)
-end
-function advtrains.deserialize_inventory(sers, inv)
- local ser=minetest.deserialize(sers)
- if ser then
- inv:set_lists(ser)
- return true
- end
- return false
-end
-
---is_protected wrapper that checks for protection_bypass privilege
-function advtrains.is_protected(pos, name)
- if not name then
- error("advtrains.is_protected() called without name parameter!")
- end
- if minetest.check_player_privs(name, {protection_bypass=true}) then
- --player can bypass protection
- return false
- end
- return minetest.is_protected(pos, name)
-end
-
-function advtrains.is_creative(name)
- if not name then
- error("advtrains.is_creative() called without name parameter!")
- end
- if minetest.check_player_privs(name, {creative=true}) then
- return true
- end
- return minetest.settings:get_bool("creative_mode")
-end
-
-function advtrains.is_damage_enabled(name)
- if not name then
- error("advtrains.is_damage_enabled() called without name parameter!")
- end
- if minetest.check_player_privs(name, "train_admin") then
- return false
- end
- return minetest.settings:get_bool("enable_damage")
-end
-
-function advtrains.ms_to_kmh(speed)
- return speed * 3.6
-end
-
--- 4 possible inputs:
--- integer: just do that modulo calculation
--- table with c set: rotate c
--- table with tables: rotate each
--- table with integers: rotate each (probably no use case)
-function advtrains.rotate_conn_by(conn, rotate)
- if tonumber(conn) then
- return (conn+rotate)%AT_CMAX
- elseif conn.c then
- return { c = (conn.c+rotate)%AT_CMAX, y = conn.y}
- end
- local tmp={}
- for connid, data in ipairs(conn) do
- tmp[connid]=advtrains.rotate_conn_by(data, rotate)
- end
- return tmp
-end
-
-
-function advtrains.oppd(dir)
- return advtrains.rotate_conn_by(dir, AT_CMAX/2)
-end
---conn_to_match like rotate_conn_by
---other_conns have to be a table of conn tables!
-function advtrains.conn_matches_to(conn, other_conns)
- if tonumber(conn) then
- for connid, data in ipairs(other_conns) do
- if advtrains.oppd(conn) == data.c then return connid end
- end
- return false
- elseif conn.c then
- for connid, data in ipairs(other_conns) do
- local cmp = advtrains.oppd(conn)
- if cmp.c == data.c and (cmp.y or 0) == (data.y or 0) then return connid end
- end
- return false
- end
- local tmp={}
- for connid, data in ipairs(conn) do
- local backmatch = advtrains.conn_matches_to(data, other_conns)
- if backmatch then return backmatch, connid end --returns <connid of other rail> <connid of this rail>
- end
- return false
-end
-
--- Going from the rail at pos (does not need to be rounded) along connection with id conn_idx, if there is a matching rail, return it and the matching connid
--- returns: <adjacent pos>, <conn index of adjacent>, <my conn index>, <railheight of adjacent>
--- parameter this_conns_p is connection table of this rail and is optional, is determined by get_rail_info_at if not provided.
-function advtrains.get_adjacent_rail(this_posnr, this_conns_p, conn_idx, drives_on)
- local this_pos = advtrains.round_vector_floor_y(this_posnr)
- local this_conns = this_conns_p
- if not this_conns then
- _, this_conns = advtrains.get_rail_info_at(this_pos)
- end
- if not conn_idx then
- for coni, _ in ipairs(this_conns) do
- local adj_pos, adj_conn_idx, _, nry, nco = advtrains.get_adjacent_rail(this_pos, this_conns, coni)
- if adj_pos then return adj_pos,adj_conn_idx,coni,nry, nco end
- end
- return nil
- end
-
- local conn = this_conns[conn_idx]
- local conn_y = conn.y or 0
- local adj_pos = advtrains.dirCoordSet(this_pos, conn.c);
-
- while conn_y>=1 do
- conn_y = conn_y - 1
- adj_pos.y = adj_pos.y + 1
- end
-
- local nextnode_ok, nextconns, nextrail_y=advtrains.get_rail_info_at(adj_pos, drives_on)
- if not nextnode_ok then
- adj_pos.y = adj_pos.y - 1
- conn_y = conn_y + 1
- nextnode_ok, nextconns, nextrail_y=advtrains.get_rail_info_at(adj_pos, drives_on)
- if not nextnode_ok then
- return nil
- end
- end
- local adj_connid = advtrains.conn_matches_to({c=conn.c, y=conn_y}, nextconns)
- if adj_connid then
- return adj_pos, adj_connid, conn_idx, nextrail_y, nextconns
- end
- return nil
-end
-
--- when a train enters a rail on connid 'conn', which connid will it go out?
--- nconns: number of connections in connection table:
--- 2 = straight rail; 3 = turnout, 4 = crossing, 5 = three-way turnout (5th entry is a stub)
--- returns: connid_out
-local connlku={[2]={2,1}, [3]={2,1,1}, [4]={2,1,4,3}, [5]={2,1,1,1}}
-function advtrains.get_matching_conn(conn, nconns)
- return connlku[nconns][conn]
-end
-
-function advtrains.random_id()
- local idst=""
- for i=0,5 do
- idst=idst..(math.random(0,9))
- end
- return idst
-end
--- Shorthand for pos_to_string and round_vector_floor_y
-function advtrains.roundfloorpts(pos)
- return minetest.pos_to_string(advtrains.round_vector_floor_y(pos))
-end
-
--- insert an element into a table if it does not yet exist there
--- equalfunc is a function to compare equality, defaults to ==
--- returns true if the element was inserted
-function advtrains.insert_once(tab, elem, equalfunc)
- for _,e in pairs(tab) do
- if equalfunc and equalfunc(elem, e) or e==elem then return false end
- end
- tab[#tab+1] = elem
- return true
-end
-
-local hext = { [0]="0",[1]="1",[2]="2",[3]="3",[4]="4",[5]="5",[6]="6",[7]="7",[8]="8",[9]="9",[10]="A",[11]="B",[12]="C",[13]="D",[14]="E",[15]="F"}
-local dect = { ["0"]=0,["1"]=1,["2"]=2,["3"]=3,["4"]=4,["5"]=5,["6"]=6,["7"]=7,["8"]=8,["9"]=9,["A"]=10,["B"]=11,["C"]=12,["D"]=13,["E"]=14,["F"]=15}
-
-local f = atfloor
-
-local function hex(i)
- local x=i+32768
- local c4 = x % 16
- x = f(x / 16)
- local c3 = x % 16
- x = f(x / 16)
- local c2 = x % 16
- x = f(x / 16)
- local c1 = x % 16
- return (hext[c1]) .. (hext[c2]) .. (hext[c3]) .. (hext[c4])
-end
-
-local function c(s,i) return dect[string.sub(s,i,i)] end
-
-local function dec(s)
- return (c(s,1)*4096 + c(s,2)*256 + c(s,3)*16 + c(s,4))-32768
-end
--- Takes a position vector and outputs a encoded value suitable as table index
--- This is essentially a hexadecimal representation of the position (+32768)
--- Order (YYY)YXXXXZZZZ
-function advtrains.encode_pos(pos)
- return hex(pos.y) .. hex(pos.x) .. hex(pos.z)
-end
-
--- decodes a position encoded with encode_pos
-function advtrains.decode_pos(pts)
- if not pts or not #pts==6 then return nil end
- local stry = string.sub(pts, 1,4)
- local strx = string.sub(pts, 5,8)
- local strz = string.sub(pts, 9,12)
- return vector.new(dec(strx), dec(stry), dec(strz))
-end
-
---[[ Benchmarking code
-local tdt = {}
-local tlt = {}
-local tet = {}
-
-for i=1,1000000 do
- tdt[i] = vector.new(math.random(-65536, 65535), math.random(-65536, 65535), math.random(-65536, 65535))
- if i%1000 == 0 then
- tlt[#tlt+1] = tdt[i]
- end
-end
-
-local t1=os.clock()
-for i=1,1000000 do
- local pe = advtrains.encode_pos(tdt[i])
- local pb = advtrains.decode_pos(pe)
- tet[pe] = i
-end
-for i,v in ipairs(tlt) do
- local lk = tet[advtrains.encode_pos(v)]
-end
-atdebug("endec",os.clock()-t1,"s")
-
-tet = {}
-
-t1=os.clock()
-for i=1,1000000 do
- local pe = minetest.pos_to_string(tdt[i])
- local pb = minetest.string_to_pos(pe)
- tet[pe] = i
-end
-for i,v in ipairs(tlt) do
- local lk = tet[minetest.pos_to_string(v)]
-end
-atdebug("pts",os.clock()-t1,"s")
-
---Results:
---2018-11-29 16:57:08: ACTION[Main]: [advtrains]endec 1.786451 s
---2018-11-29 16:57:10: ACTION[Main]: [advtrains]pts 2.566377 s
-]]
-
-
+--advtrains by orwell96, see readme.txt
+
+local dir_trans_tbl={
+ [0]={x=0, z=1, y=0},
+ [1]={x=1, z=2, y=0},
+ [2]={x=1, z=1, y=0},
+ [3]={x=2, z=1, y=0},
+ [4]={x=1, z=0, y=0},
+ [5]={x=2, z=-1, y=0},
+ [6]={x=1, z=-1, y=0},
+ [7]={x=1, z=-2, y=0},
+ [8]={x=0, z=-1, y=0},
+ [9]={x=-1, z=-2, y=0},
+ [10]={x=-1, z=-1, y=0},
+ [11]={x=-2, z=-1, y=0},
+ [12]={x=-1, z=0, y=0},
+ [13]={x=-2, z=1, y=0},
+ [14]={x=-1, z=1, y=0},
+ [15]={x=-1, z=2, y=0},
+}
+
+local dir_angle_tbl={}
+for d,v in pairs(dir_trans_tbl) do
+ local uvec = vector.normalize(v)
+ dir_angle_tbl[d] = math.atan2(-uvec.x, uvec.z)
+end
+
+
+function advtrains.dir_to_angle(dir)
+ return dir_angle_tbl[dir] or error("advtrains: in helpers.lua/dir_to_angle() given dir="..(dir or "nil"))
+end
+
+function advtrains.dirCoordSet(coord, dir)
+ return vector.add(coord, advtrains.dirToCoord(dir))
+end
+advtrains.pos_add_dir = advtrains.dirCoordSet
+
+function advtrains.pos_add_angle(pos, ang)
+ -- 0 is +Z -> meaning of sin/cos swapped
+ return vector.add(pos, {x = -math.sin(ang), y = 0, z = math.cos(ang)})
+end
+
+function advtrains.dirToCoord(dir)
+ return dir_trans_tbl[dir] or error("advtrains: in helpers.lua/dir_to_vector() given dir="..(dir or "nil"))
+end
+advtrains.dir_to_vector = advtrains.dirToCoord
+
+function advtrains.maxN(list, expectstart)
+ local n=expectstart or 0
+ while list[n] do
+ n=n+1
+ end
+ return n-1
+end
+
+function advtrains.minN(list, expectstart)
+ local n=expectstart or 0
+ while list[n] do
+ n=n-1
+ end
+ return n+1
+end
+
+function atround(number)
+ return math.floor(number+0.5)
+end
+atfloor = math.floor
+
+
+function advtrains.round_vector_floor_y(vec)
+ return {x=math.floor(vec.x+0.5), y=math.floor(vec.y), z=math.floor(vec.z+0.5)}
+end
+
+function advtrains.yawToDirection(yaw, conn1, conn2)
+ if not conn1 or not conn2 then
+ error("given nil to yawToDirection: conn1="..(conn1 or "nil").." conn2="..(conn1 or "nil"))
+ end
+ local yaw1 = advtrains.dir_to_angle(conn1)
+ local yaw2 = advtrains.dir_to_angle(conn2)
+ local adiff1 = advtrains.minAngleDiffRad(yaw, yaw1)
+ local adiff2 = advtrains.minAngleDiffRad(yaw, yaw2)
+
+ if math.abs(adiff2)<math.abs(adiff1) then
+ return conn2
+ else
+ return conn1
+ end
+end
+
+function advtrains.yawToAnyDir(yaw)
+ local min_conn, min_diff=0, 10
+ for conn, vec in pairs(advtrains.dir_trans_tbl) do
+ local yaw1 = advtrains.dir_to_angle(conn)
+ local diff = math.abs(advtrains.minAngleDiffRad(yaw, yaw1))
+ if diff < min_diff then
+ min_conn = conn
+ min_diff = diff
+ end
+ end
+ return min_conn
+end
+function advtrains.yawToClosestConn(yaw, conns)
+ local min_connid, min_diff=1, 10
+ for connid, conn in ipairs(conns) do
+ local yaw1 = advtrains.dir_to_angle(conn.c)
+ local diff = math.abs(advtrains.minAngleDiffRad(yaw, yaw1))
+ if diff < min_diff then
+ min_connid = connid
+ min_diff = diff
+ end
+ end
+ return min_connid
+end
+
+local pi, pi2 = math.pi, 2*math.pi
+function advtrains.minAngleDiffRad(r1, r2)
+ while r1>pi2 do
+ r1=r1-pi2
+ end
+ while r1<0 do
+ r1=r1+pi2
+ end
+ while r2>pi2 do
+ r2=r2-pi2
+ end
+ while r1<0 do
+ r2=r2+pi2
+ end
+ local try1=r2-r1
+ local try2=r2+pi2-r1
+ local try3=r2-pi2-r1
+
+ local minabs = math.min(math.abs(try1), math.abs(try2), math.abs(try3))
+ if minabs==math.abs(try1) then
+ return try1
+ end
+ if minabs==math.abs(try2) then
+ return try2
+ end
+ if minabs==math.abs(try3) then
+ return try3
+ end
+end
+
+
+-- Takes 2 connections (0...AT_CMAX) as argument
+-- Returns the angle median of those 2 positions from the pov
+-- of standing on the cdir1 side and looking towards cdir2
+-- cdir1 - >NODE> - cdir2
+function advtrains.conn_angle_median(cdir1, cdir2)
+ local ang1 = advtrains.dir_to_angle(advtrains.oppd(cdir1))
+ local ang2 = advtrains.dir_to_angle(cdir2)
+ return ang1 + advtrains.minAngleDiffRad(ang1, ang2)/2
+end
+
+function advtrains.merge_tables(a, ...)
+ local new={}
+ for _,t in ipairs({a,...}) do
+ for k,v in pairs(t) do new[k]=v end
+ end
+ return new
+end
+function advtrains.save_keys(tbl, keys)
+ local new={}
+ for _,key in ipairs(keys) do
+ new[key] = tbl[key]
+ end
+ return new
+end
+
+function advtrains.get_real_index_position(path, index)
+ if not path or not index then return end
+
+ local first_pos=path[math.floor(index)]
+ local second_pos=path[math.floor(index)+1]
+
+ if not first_pos or not second_pos then return nil end
+
+ local factor=index-math.floor(index)
+ local actual_pos={x=first_pos.x-(first_pos.x-second_pos.x)*factor, y=first_pos.y-(first_pos.y-second_pos.y)*factor, z=first_pos.z-(first_pos.z-second_pos.z)*factor,}
+ return actual_pos
+end
+function advtrains.pos_median(pos1, pos2)
+ return {x=pos1.x-(pos1.x-pos2.x)*0.5, y=pos1.y-(pos1.y-pos2.y)*0.5, z=pos1.z-(pos1.z-pos2.z)*0.5}
+end
+function advtrains.abs_ceil(i)
+ return math.ceil(math.abs(i))*math.sign(i)
+end
+
+function advtrains.serialize_inventory(inv)
+ local ser={}
+ local liszts=inv:get_lists()
+ for lisztname, liszt in pairs(liszts) do
+ ser[lisztname]={}
+ for idx, item in ipairs(liszt) do
+ local istring=item:to_string()
+ if istring~="" then
+ ser[lisztname][idx]=istring
+ end
+ end
+ end
+ return minetest.serialize(ser)
+end
+function advtrains.deserialize_inventory(sers, inv)
+ local ser=minetest.deserialize(sers)
+ if ser then
+ inv:set_lists(ser)
+ return true
+ end
+ return false
+end
+
+--is_protected wrapper that checks for protection_bypass privilege
+function advtrains.is_protected(pos, name)
+ if not name then
+ error("advtrains.is_protected() called without name parameter!")
+ end
+ if minetest.check_player_privs(name, {protection_bypass=true}) then
+ --player can bypass protection
+ return false
+ end
+ return minetest.is_protected(pos, name)
+end
+
+function advtrains.is_creative(name)
+ if not name then
+ error("advtrains.is_creative() called without name parameter!")
+ end
+ if minetest.check_player_privs(name, {creative=true}) then
+ return true
+ end
+ return minetest.settings:get_bool("creative_mode")
+end
+
+function advtrains.is_damage_enabled(name)
+ if not name then
+ error("advtrains.is_damage_enabled() called without name parameter!")
+ end
+ if minetest.check_player_privs(name, "train_admin") then
+ return false
+ end
+ return minetest.settings:get_bool("enable_damage")
+end
+
+function advtrains.ms_to_kmh(speed)
+ return speed * 3.6
+end
+
+-- 4 possible inputs:
+-- integer: just do that modulo calculation
+-- table with c set: rotate c
+-- table with tables: rotate each
+-- table with integers: rotate each (probably no use case)
+function advtrains.rotate_conn_by(conn, rotate)
+ if tonumber(conn) then
+ return (conn+rotate)%AT_CMAX
+ elseif conn.c then
+ return { c = (conn.c+rotate)%AT_CMAX, y = conn.y}
+ end
+ local tmp={}
+ for connid, data in ipairs(conn) do
+ tmp[connid]=advtrains.rotate_conn_by(data, rotate)
+ end
+ return tmp
+end
+
+
+function advtrains.oppd(dir)
+ return advtrains.rotate_conn_by(dir, AT_CMAX/2)
+end
+--conn_to_match like rotate_conn_by
+--other_conns have to be a table of conn tables!
+function advtrains.conn_matches_to(conn, other_conns)
+ if tonumber(conn) then
+ for connid, data in ipairs(other_conns) do
+ if advtrains.oppd(conn) == data.c then return connid end
+ end
+ return false
+ elseif conn.c then
+ for connid, data in ipairs(other_conns) do
+ local cmp = advtrains.oppd(conn)
+ if cmp.c == data.c and (cmp.y or 0) == (data.y or 0) then return connid end
+ end
+ return false
+ end
+ local tmp={}
+ for connid, data in ipairs(conn) do
+ local backmatch = advtrains.conn_matches_to(data, other_conns)
+ if backmatch then return backmatch, connid end --returns <connid of other rail> <connid of this rail>
+ end
+ return false
+end
+
+-- Going from the rail at pos (does not need to be rounded) along connection with id conn_idx, if there is a matching rail, return it and the matching connid
+-- returns: <adjacent pos>, <conn index of adjacent>, <my conn index>, <railheight of adjacent>
+-- parameter this_conns_p is connection table of this rail and is optional, is determined by get_rail_info_at if not provided.
+function advtrains.get_adjacent_rail(this_posnr, this_conns_p, conn_idx, drives_on)
+ local this_pos = advtrains.round_vector_floor_y(this_posnr)
+ local this_conns = this_conns_p
+ if not this_conns then
+ _, this_conns = advtrains.get_rail_info_at(this_pos)
+ end
+ if not conn_idx then
+ for coni, _ in ipairs(this_conns) do
+ local adj_pos, adj_conn_idx, _, nry, nco = advtrains.get_adjacent_rail(this_pos, this_conns, coni)
+ if adj_pos then return adj_pos,adj_conn_idx,coni,nry, nco end
+ end
+ return nil
+ end
+
+ local conn = this_conns[conn_idx]
+ local conn_y = conn.y or 0
+ local adj_pos = advtrains.dirCoordSet(this_pos, conn.c);
+
+ while conn_y>=1 do
+ conn_y = conn_y - 1
+ adj_pos.y = adj_pos.y + 1
+ end
+
+ local nextnode_ok, nextconns, nextrail_y=advtrains.get_rail_info_at(adj_pos, drives_on)
+ if not nextnode_ok then
+ adj_pos.y = adj_pos.y - 1
+ conn_y = conn_y + 1
+ nextnode_ok, nextconns, nextrail_y=advtrains.get_rail_info_at(adj_pos, drives_on)
+ if not nextnode_ok then
+ return nil
+ end
+ end
+ local adj_connid = advtrains.conn_matches_to({c=conn.c, y=conn_y}, nextconns)
+ if adj_connid then
+ return adj_pos, adj_connid, conn_idx, nextrail_y, nextconns
+ end
+ return nil
+end
+
+-- when a train enters a rail on connid 'conn', which connid will it go out?
+-- nconns: number of connections in connection table:
+-- 2 = straight rail; 3 = turnout, 4 = crossing, 5 = three-way turnout (5th entry is a stub)
+-- returns: connid_out
+local connlku={[2]={2,1}, [3]={2,1,1}, [4]={2,1,4,3}, [5]={2,1,1,1}}
+function advtrains.get_matching_conn(conn, nconns)
+ return connlku[nconns][conn]
+end
+
+function advtrains.random_id()
+ local idst=""
+ for i=0,5 do
+ idst=idst..(math.random(0,9))
+ end
+ return idst
+end
+-- Shorthand for pos_to_string and round_vector_floor_y
+function advtrains.roundfloorpts(pos)
+ return minetest.pos_to_string(advtrains.round_vector_floor_y(pos))
+end
+
+-- insert an element into a table if it does not yet exist there
+-- equalfunc is a function to compare equality, defaults to ==
+-- returns true if the element was inserted
+function advtrains.insert_once(tab, elem, equalfunc)
+ for _,e in pairs(tab) do
+ if equalfunc and equalfunc(elem, e) or e==elem then return false end
+ end
+ tab[#tab+1] = elem
+ return true
+end
+
+local hext = { [0]="0",[1]="1",[2]="2",[3]="3",[4]="4",[5]="5",[6]="6",[7]="7",[8]="8",[9]="9",[10]="A",[11]="B",[12]="C",[13]="D",[14]="E",[15]="F"}
+local dect = { ["0"]=0,["1"]=1,["2"]=2,["3"]=3,["4"]=4,["5"]=5,["6"]=6,["7"]=7,["8"]=8,["9"]=9,["A"]=10,["B"]=11,["C"]=12,["D"]=13,["E"]=14,["F"]=15}
+
+local f = atfloor
+
+local function hex(i)
+ local x=i+32768
+ local c4 = x % 16
+ x = f(x / 16)
+ local c3 = x % 16
+ x = f(x / 16)
+ local c2 = x % 16
+ x = f(x / 16)
+ local c1 = x % 16
+ return (hext[c1]) .. (hext[c2]) .. (hext[c3]) .. (hext[c4])
+end
+
+local function c(s,i) return dect[string.sub(s,i,i)] end
+
+local function dec(s)
+ return (c(s,1)*4096 + c(s,2)*256 + c(s,3)*16 + c(s,4))-32768
+end
+-- Takes a position vector and outputs a encoded value suitable as table index
+-- This is essentially a hexadecimal representation of the position (+32768)
+-- Order (YYY)YXXXXZZZZ
+function advtrains.encode_pos(pos)
+ return hex(pos.y) .. hex(pos.x) .. hex(pos.z)
+end
+
+-- decodes a position encoded with encode_pos
+function advtrains.decode_pos(pts)
+ if not pts or not #pts==6 then return nil end
+ local stry = string.sub(pts, 1,4)
+ local strx = string.sub(pts, 5,8)
+ local strz = string.sub(pts, 9,12)
+ return vector.new(dec(strx), dec(stry), dec(strz))
+end
+
+--[[ Benchmarking code
+local tdt = {}
+local tlt = {}
+local tet = {}
+
+for i=1,1000000 do
+ tdt[i] = vector.new(math.random(-65536, 65535), math.random(-65536, 65535), math.random(-65536, 65535))
+ if i%1000 == 0 then
+ tlt[#tlt+1] = tdt[i]
+ end
+end
+
+local t1=os.clock()
+for i=1,1000000 do
+ local pe = advtrains.encode_pos(tdt[i])
+ local pb = advtrains.decode_pos(pe)
+ tet[pe] = i
+end
+for i,v in ipairs(tlt) do
+ local lk = tet[advtrains.encode_pos(v)]
+end
+atdebug("endec",os.clock()-t1,"s")
+
+tet = {}
+
+t1=os.clock()
+for i=1,1000000 do
+ local pe = minetest.pos_to_string(tdt[i])
+ local pb = minetest.string_to_pos(pe)
+ tet[pe] = i
+end
+for i,v in ipairs(tlt) do
+ local lk = tet[minetest.pos_to_string(v)]
+end
+atdebug("pts",os.clock()-t1,"s")
+
+--Results:
+--2018-11-29 16:57:08: ACTION[Main]: [advtrains]endec 1.786451 s
+--2018-11-29 16:57:10: ACTION[Main]: [advtrains]pts 2.566377 s
+]]
+
+