aboutsummaryrefslogtreecommitdiff
path: root/advtrains
diff options
context:
space:
mode:
Diffstat (limited to 'advtrains')
-rw-r--r--advtrains/helpers.lua20
-rw-r--r--advtrains/init.lua4
-rw-r--r--advtrains/path.lua164
-rw-r--r--advtrains/trainlogic.lua40
4 files changed, 204 insertions, 24 deletions
diff --git a/advtrains/helpers.lua b/advtrains/helpers.lua
index 5f5521f..5dbfd8d 100644
--- a/advtrains/helpers.lua
+++ b/advtrains/helpers.lua
@@ -28,9 +28,12 @@ function advtrains.dirCoordSet(coord, dir)
end
return {x=coord.x+x, y=coord.y, z=coord.z+z}
end
+advtrains.pos_add_dir = advtrains.dirCoordSet
+
function advtrains.dirToCoord(dir)
return advtrains.dirCoordSet({x=0, y=0, z=0}, dir)
end
+advtrains.dir_to_vector = advtrains.dirToCoord
function advtrains.maxN(list, expectstart)
local n=expectstart or 0
@@ -51,6 +54,8 @@ 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)}
@@ -60,8 +65,8 @@ 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=math.pi*(conn1/8)
- local yaw2=math.pi*(conn2/8)
+ 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)
@@ -75,8 +80,7 @@ end
function advtrains.yawToAnyDir(yaw)
local min_conn, min_diff=0, 10
for conn, vec in pairs(advtrains.dir_trans_tbl) do
- local uvec = vector.normalize(advtrains.dirToCoord(conn))
- local yaw1 = math.atan2(uvec.z, uvec.x)
+ local yaw1 = advtrains.dir_to_angle(conn)
local diff = advtrains.minAngleDiffRad(yaw, yaw1)
if diff < min_diff then
min_conn = conn
@@ -88,8 +92,7 @@ end
function advtrains.yawToClosestConn(yaw, conns)
local min_connid, min_diff=1, 10
for connid, conn in ipairs(conns) do
- local uvec = vector.normalize(advtrains.dirToCoord(conn.c))
- local yaw1 = math.atan2(uvec.z, uvec.x)
+ local yaw1 = advtrains.dir_to_angle(conn.c)
local diff = advtrains.minAngleDiffRad(yaw, yaw1)
if diff < min_diff then
min_connid = connid
@@ -99,6 +102,11 @@ function advtrains.yawToClosestConn(yaw, conns)
return min_connid
end
+function advtrains.dir_to_angle(dir)
+ local uvec = vector.normalize(advtrains.dirToCoord())
+ local yaw1 = math.atan2(uvec.z, -uvec.x)
+end
+
function advtrains.minAngleDiffRad(r1, r2)
local pi, pi2 = math.pi, 2*math.pi
diff --git a/advtrains/init.lua b/advtrains/init.lua
index 65e5048..0043c22 100644
--- a/advtrains/init.lua
+++ b/advtrains/init.lua
@@ -184,6 +184,10 @@ function advtrains.avt_load()
if tbl.version then
--congrats, we have the new save format.
advtrains.trains = tbl.trains
+ --Save the train id into the train table to avoid having to pass id around
+ for id, train in pairs(advtrains.trains) do
+ train.id = id
+ end
advtrains.wagon_save = tbl.wagon_save
advtrains.player_to_train_mapping = tbl.ptmap or {}
advtrains.ndb.load_data(tbl.ndb)
diff --git a/advtrains/path.lua b/advtrains/path.lua
index 506f27f..07b60b2 100644
--- a/advtrains/path.lua
+++ b/advtrains/path.lua
@@ -24,17 +24,7 @@ function advtrains.conway(midreal, prev, drives_on)--in order prev,mid,return
return vector.add(advtrains.round_vector_floor_y(next), {x=0, y=nextrailheight, z=0}), midconns[nconnid].c
end
---about regular: Used by 1. to ensure path gets generated far enough, since end index is not known at this time.
-function advtrains.pathpredict(id, train, regular)
- --TODO duplicate code under 5b.
- local path_pregen=10
-
- local gen_front= path_pregen
- local gen_back= - train.trainlen - path_pregen
- if regular then
- gen_front=math.max(train.index, train.detector_old_index) + path_pregen
- gen_back=math.min(train.end_index, train.detector_old_end_index) - path_pregen
- end
+function advtrains.pathpredict(id, train, gen_front, gen_back)
local maxn=train.path_extent_max or 0
while maxn < gen_front do--pregenerate
@@ -80,3 +70,155 @@ function advtrains.pathpredict(id, train, regular)
if not train.min_index_on_track then train.min_index_on_track=-1 end
if not train.max_index_on_track then train.max_index_on_track=0 end
end
+
+-- Naming conventions:
+-- 'index' - An index of the train.path table.
+-- 'offset' - A value in meters that determines how far on the path to walk relative to a certain index
+-- 'n' - Referring or pointing towards the 'next' path item, the one with index+1
+-- 'p' - Referring or pointing towards the 'prev' path item, the one with index-1
+-- 'f' - Referring to the positive end of the path (the end with the higher index)
+-- 'b' - Referring to the negative end of the path (the end with the lower index)
+
+-- New path structure of trains:
+--Tables:
+-- path - path positions. 'indices' are relative to this. At the moment, at.round_vector_floor_y(path[i])
+-- is the node this item corresponds to, however, this will change in the future.
+-- path_node - (reserved)
+-- path_cn - Connid of the current node that points towards path[i+1]
+-- path_cp - Connid of the current node that points towards path[i-1]
+-- When the day comes on that path!=node, these will only be set if this index represents a transition between rail nodes
+-- path_dist - The distance (in meters) between this (path[i]) and the next (path[i+1]) item of the path
+-- path_dir - The direction of this path item's transition to the next path item, which is the angle of conns[path_cn[i]].c
+--Variables:
+-- path_ext_f/b - how far path[i] is set
+-- path_trk_f/b - how far the path extends along a track. beyond those values, paths are generated in a straight line.
+-- path_req_f/b - how far path items were requested in the last step
+
+-- creates the path data structure, reconstructing the train from a position and a connid
+-- Important! train.drives_on must exist while calling this method
+-- returns: true - successful
+-- nil - node not yet available/unloaded, please wait
+-- false - node definitely gone, remove train
+function advtrains.path_create(train, pos, connid, rel_index)
+ local posr = advtrains.round_vector_floor_y(pos)
+ local node_ok, conns, rhe = advtrains.get_rail_info_at(pos, train.drives_on)
+ if not node_ok then
+ return node_ok
+ end
+ local mconnid = advtrains.get_matching_conn(connid, #conns)
+ train.index = rel_index
+ train.path = { [0] = { x=posr.x, y=posr.y+rhe, z=posr.z } }
+ train.path_cn = { [0] = connid }
+ train.path_cp = { [0] = mconnid }
+ train.path_dist = {}
+
+ train.path_dir = {
+ [ 0] = conns[connid],
+ [-1] = conns[mconnid]
+ }
+
+ train.path_ext_f=0
+ train.path_ext_b=0
+ train.path_trk_f=0
+ train.path_trk_b=0
+ train.path_req_f=0
+ train.path_req_b=0
+
+end
+
+-- Function to get path entry at a position. This function will automatically calculate more of the path when required.
+-- returns: pos, on_track
+function advtrains.path_get(train, index)
+ if index ~= atfloor(index) then
+ error("For train "..train.id..": Called path_get() but index="..index.." is not a round number")
+ end
+ while index > train.path_ext_f do
+ local pos = train.path[train.path_ext_f]
+ local connid = train.path_cn[train.path_ext_f]
+ local node_ok, this_conns, adj_pos, adj_connid, conn_idx, nextrail_y
+ if train.path_ext_f == train.path_trk_f then
+ node_ok, this_conns = advtrains.get_rail_info_at(this_pos)
+ if not node_ok then error("For train "..train.id..": Path item "..train.path_ext_f.." on-track but not a valid node!") end
+ adj_pos, adj_connid, conn_idx, nextrail_y = advtrains.get_adjacent_rail(pos, this_conns, connid, train.drives_on)
+ end
+ train.path_ext_f = train.path_ext_f + 1
+ if adj_pos then
+ adj_pos.y = adj_pos.y + nextrail_y
+ train.path_cp[train.path_ext_f] = adj_connid
+ local mconnid = advtrains.get_matching_conn(adj_connid)
+ train.path_cn[train.path_ext_f] = mconnid
+ train.path_dir[train.path_ext_f] = this_conns[mconnid]
+ train.path_trk_f = train.path_ext_f
+ else
+ -- off-track fallback behavior
+ adj_pos = advtrains.pos_add_dir(pos, train.path_dir[train.path_ext_f-1])
+ train.path_dir[train.path_ext_f] = train.path_dir[train.path_ext_f-1]
+ end
+ train.path[train.path_ext_f] = adj_pos
+ train.path_dist[train.path_ext_f - 1] = vector.distance(pos, adj_pos)
+ end
+ while index < train.path_ext_b do
+ local pos = train.path[train.path_ext_b]
+ local connid = train.path_cp[train.path_ext_b]
+ local node_ok, this_conns, adj_pos, adj_connid, conn_idx, nextrail_y
+ if train.path_ext_b == train.path_trk_b then
+ node_ok, this_conns = advtrains.get_rail_info_at(this_pos)
+ if not node_ok then error("For train "..train.id..": Path item "..train.path_ext_f.." on-track but not a valid node!") end
+ adj_pos, adj_connid, conn_idx, nextrail_y = advtrains.get_adjacent_rail(pos, this_conns, connid, train.drives_on)
+ end
+ train.path_ext_b = train.path_ext_b - 1
+ if adj_pos then
+ adj_pos.y = adj_pos.y + nextrail_y
+ train.path_cp[train.path_ext_b] = adj_connid
+ local mconnid = advtrains.get_matching_conn(adj_connid)
+ train.path_cn[train.path_ext_b] = mconnid
+ train.path_dir[train.path_ext_b] = advtrains.oppd(this_conns[mconnid]) --we need to rotate this here so that it points in positive path direction
+ train.path_trk_b = train.path_ext_b
+ else
+ -- off-track fallback behavior
+ adj_pos = advtrains.pos_add_dir(pos, train.path_dir[train.path_ext_b-1])
+ train.path_dir[train.path_ext_b] = train.path_dir[train.path_ext_b-1]
+ end
+ train.path[train.path_ext_b] = adj_pos
+ train.path_dist[train.path_ext_b] = vector.distance(pos, adj_pos)
+ end
+
+ return train.path[index], (index<=train.path_trk_f and index>=train.path_trk_b)
+
+end
+
+-- interpolated position to fractional index given, and angle based on path_dir
+-- returns: pos, angle(yaw)
+function advtrains.path_get_interpolated(train, index)
+ local i_floor = atfloor(index)
+ local i_ceil = i_floor + 1
+ local frac = index - i_floor
+ local p_floor, = advtrains.path_get(train, i_floor)
+ local p_ceil = advtrains.path_get(train, i_ceil)
+
+ local d_floor = train.path_dir[i_floor]
+ local d_ceil = train.path_dir[i_ceil]
+ local a_floor = advtrains.dir_to_angle(d_floor)
+ local a_ceil = advtrains.dir_to_angle(d_ceil)
+
+ local ang = advtrains.minAngleDiffRad(a_floor, a_ceil)
+
+ return vector.add(p_floor, vector.multiply(vector.subtract(p_ceil, p_floor), frac), (a_floor + frac * ang)%(2*math.pi) -- TODO does this behave correctly?
+end
+
+function advtrains.path_get_by_offset(train, index, offset)
+ local pos_in_train_left=pit
+ local index=train.index
+ if pos_in_train_left>(index-math.floor(index))*(train.path_dist[math.floor(index)] or 1) then
+ pos_in_train_left=pos_in_train_left - (index-math.floor(index))*(train.path_dist[math.floor(index)] or 1)
+ index=math.floor(index)
+ while pos_in_train_left>(train.path_dist[index-1] or 1) do
+ pos_in_train_left=pos_in_train_left - (train.path_dist[index-1] or 1)
+ index=index-1
+ end
+ index=index-(pos_in_train_left/(train.path_dist[index-1] or 1))
+ else
+ index=index-(pos_in_train_left/(train.path_dist[math.floor(index-1)] or 1))
+ end
+ return index
+end
diff --git a/advtrains/trainlogic.lua b/advtrains/trainlogic.lua
index 449d624..181446c 100644
--- a/advtrains/trainlogic.lua
+++ b/advtrains/trainlogic.lua
@@ -219,7 +219,7 @@ function advtrains.train_step_a(id, train, dtime)
- The next step, mistake is recognized, train leaves some positions. From there, everything works again.
To overcome this, we will generate the full required path here so that path_dist is available for get_train_end_index().
]]
- advtrains.pathpredict(id, train)
+ advtrains.pathpredict(id, train, 3, -train.trainlen-3)
end
--- 2a. set train.end_index which is required in different places, IF IT IS NOT SET YET by STMT afterwards. ---
@@ -359,9 +359,36 @@ function advtrains.train_step_a(id, train, dtime)
--- 4a. update train.end_index to the new position ---
train.end_index=advtrains.get_train_end_index(train)
+ --- 4b calculate how far a path is required ---
+ local path_req_dd = 10 -- path required in driving direction
+ local path_req_ndd = 4 -- path required against driving direction
+
+ -- when using easyBSS (block signalling), we need to make sure that the whole brake distance is known
+ if advtrains_easybss then
+ local acc_all = t_accel_all[1]
+ local acc_eng = t_accel_eng[1]
+ local nwagons = #train.trainparts
+ local acc = acc_all + (acc_eng*train.locomotives_in_train)/nwagons
+ local vel = train.velocity
+ local brakedst = (vel*vel) / (2*acc)
+ path_req_dd = math.ceil(brakedst+10)
+ end
+
+
+ local idx_front=math.max(train.index, train.detector_old_index)
+ local idx_back=math.min(train.end_index, train.detector_old_end_index)
+ local path_req_front, path_req_back
+ if train.movedir == 1 then
+ path_req_front = atround(idx_front + path_req_dd)
+ path_req_back = atround(idx_back - path_req_ndd)
+ else
+ path_req_front = atround(idx_front + path_req_ndd)
+ path_req_back = atround(idx_back - path_req_dd)
+ end
+
--- 5. extend path as necessary ---
--why this is an extra function, see under 3.
- advtrains.pathpredict(id, train, true)
+ advtrains.pathpredict(id, train, path_req_front, path_req_back)
--- 5a. make pos/yaw available for possible recover calls ---
if train.max_index_on_track<train.index then --whoops, train went too far. the saved position will be the last one that lies on a track, and savedpos_off_track_index_offset will hold how far to go from here
@@ -382,13 +409,11 @@ function advtrains.train_step_a(id, train, dtime)
--- 5b. Remove path items that are no longer used ---
-- Necessary since path items are no longer invalidated in save steps
- local path_pregen_keep=20
+ local del_keep=8
local offtrack_keep=4
- local gen_front_keep= path_pregen_keep
- local gen_back_keep= atround(- train.trainlen - path_pregen_keep)
- local delete_min=math.min(train.max_index_on_track - offtrack_keep, atround(train.index)+gen_back_keep)
- local delete_max=math.max(train.min_index_on_track + offtrack_keep, atround(train.index)+gen_front_keep)
+ local delete_min=math.min(train.max_index_on_track - offtrack_keep, path_req_back - del_keep)
+ local delete_max=math.max(train.min_index_on_track + offtrack_keep, path_req_front + del_keep)
if train.path_extent_min<delete_min then
--atprint(sid(id),"clearing path min ",train.path_extent_min," to ",delete_min)
@@ -582,6 +607,7 @@ function advtrains.create_new_train_at(pos, pos_prev)
while advtrains.trains[newtrain_id] do newtrain_id=advtrains.random_id() end--ensure uniqueness
advtrains.trains[newtrain_id]={}
+ advtrains.trains[newtrain_id].id = newtrain_id
advtrains.trains[newtrain_id].last_pos=pos
advtrains.trains[newtrain_id].last_pos_prev=pos_prev
advtrains.trains[newtrain_id].tarvelocity=0
span>"Train ",train.train_id,": Illegal path invalidation has occured during train step:") atwarn(debug.traceback()) end if train.path then for i,p in pairs(train.path) do advtrains.occ.clear_item(train.id, advtrains.round_vector_floor_y(p)) end end train.path = nil train.path_dist = nil train.path_cp = nil train.path_cn = nil train.path_dir = nil train.path_speed = nil train.path_ext_f=0 train.path_ext_b=0 train.path_trk_f=0 train.path_trk_b=0 train.path_req_f=0 train.path_req_b=0 train.dirty = true --atdebug(train.id, "Path invalidated") end -- Keeps the path intact, but invalidates all path nodes from the specified index (inclusive) -- onwards. This has the advantage that we don't need to recalculate the whole path, and we can do it synchronously. function advtrains.path_invalidate_ahead(train, start_idx, ignore_when_passed) if not train.path then -- the path wasn't even initialized. Nothing to do return end local idx = atfloor(start_idx) --atdebug("Invalidate_ahead:",train.id,"start_index",start_idx,"cur_idx",train.index) if(idx <= train.index - 0.5) then if ignore_when_passed then --atdebug("ignored passed") return end advtrains.path_print(train, atwarn) error("Train "+train.id+": Cannot path_invalidate_ahead start_idx="+idx+" as train has already passed!") end -- leave current node in path, it won't change. What might change is the path onward from here (e.g. switch) local i = idx + 1 while train.path[i] do advtrains.occ.clear_item(train.id, advtrains.round_vector_floor_y(train.path[i])) i = i+1 end train.path_ext_f=idx train.path_trk_f=math.min(idx, train.path_trk_f) -- callbacks called anyway for current node, because of LZB advtrains.run_callbacks_invahead(train.id, train, idx) end -- Prints a path using the passed print function -- This function should be 'atprint', 'atlog', 'atwarn' or 'atdebug', because it needs to use print_concat_table function advtrains.path_print(train, printf) printf("path_print: tid =",train.id," index =",train.index," end_index =",train.end_index," vel =",train.velocity) if not train.path then printf("path_print: Path is invalidated/inexistant.") return end printf("i: CP Position Dir CN Dist Speed") for i = train.path_ext_b, train.path_ext_f do if i==train.path_trk_b then printf("--Back on-track border here--") end printf(i,": ",train.path_cp[i]," ",train.path[i]," ",train.path_dir[i]," ",train.path_cn[i]," ",train.path_dist[i]," ",train.path_speed[i]) if i==train.path_trk_f then printf("--Front on-track border here--") end end end -- Function to get path entry at a position. This function will automatically calculate more of the path when required. -- returns: pos, on_track function advtrains.path_get(train, index) if not train.path then error("For train "..train.id..": path_get called but there's no path set yet!") end if index ~= atfloor(index) then error("For train "..train.id..": Called path_get() but index="..index.." is not a round number") end local pef = train.path_ext_f -- generate forward (front of train, positive) while index > pef do local pos = train.path[pef] local connid = train.path_cn[pef] local node_ok, this_conns, adj_pos, adj_connid, conn_idx, nextrail_y, next_conns if pef == train.path_trk_f then node_ok, this_conns = advtrains.get_rail_info_at(pos) if not node_ok then error("For train "..train.id..": Path item "..pef.." on-track but not a valid node!") end adj_pos, adj_connid, conn_idx, nextrail_y, next_conns = advtrains.get_adjacent_rail(pos, this_conns, connid, train.drives_on) end pef = pef + 1 if adj_pos then advtrains.occ.set_item(train.id, adj_pos, pef) -- If we have split points, notify accordingly local mconnid = advtrains.get_matching_conn(adj_connid, #next_conns) if #next_conns==3 and adj_connid==1 and train.points_split and train.points_split[advtrains.encode_pos(adj_pos)] then --atdebug(id,"has split points restored at",adj_pos) mconnid = 3 end adj_pos.y = adj_pos.y + nextrail_y train.path_cp[pef] = adj_connid train.path_cn[pef] = mconnid train.path_dir[pef] = advtrains.conn_angle_median(next_conns[adj_connid].c, next_conns[mconnid].c) train.path_trk_f = pef else -- off-track fallback behavior adj_pos = advtrains.pos_add_angle(pos, train.path_dir[pef-1]) --atdebug("Offtrack overgenerating(front) at",adj_pos,"index",peb,"trkf",train.path_trk_f) train.path_dir[pef] = train.path_dir[pef-1] end train.path[pef] = adj_pos train.path_dist[pef] = train.path_dist[pef-1] + vector.distance(pos, adj_pos) end train.path_ext_f = pef local peb = train.path_ext_b -- generate backward (back of train, negative) while index < peb do local pos = train.path[peb] local connid = train.path_cp[peb] local node_ok, this_conns, adj_pos, adj_connid, conn_idx, nextrail_y, next_conns if peb == train.path_trk_b then node_ok, this_conns = advtrains.get_rail_info_at(pos) if not node_ok then error("For train "..train.id..": Path item "..peb.." on-track but not a valid node!") end adj_pos, adj_connid, conn_idx, nextrail_y, next_conns = advtrains.get_adjacent_rail(pos, this_conns, connid, train.drives_on) end peb = peb - 1 if adj_pos then advtrains.occ.set_item(train.id, adj_pos, peb) -- If we have split points, notify accordingly local mconnid = advtrains.get_matching_conn(adj_connid, #next_conns) if #next_conns==3 and adj_connid==1 and train.points_split and train.points_split[advtrains.encode_pos(adj_pos)] then -- atdebug(id,"has split points restored at",adj_pos) mconnid = 3 end adj_pos.y = adj_pos.y + nextrail_y train.path_cn[peb] = adj_connid train.path_cp[peb] = mconnid train.path_dir[peb] = advtrains.conn_angle_median(next_conns[mconnid].c, next_conns[adj_connid].c) train.path_trk_b = peb else -- off-track fallback behavior adj_pos = advtrains.pos_add_angle(pos, train.path_dir[peb+1] + math.pi) --atdebug("Offtrack overgenerating(back) at",adj_pos,"index",peb,"trkb",train.path_trk_b) train.path_dir[peb] = train.path_dir[peb+1] end train.path[peb] = adj_pos train.path_dist[peb] = train.path_dist[peb+1] - vector.distance(pos, adj_pos) end train.path_ext_b = peb if index < train.path_req_b then train.path_req_b = index end if index > train.path_req_f then train.path_req_f = index end return train.path[index], (index<=train.path_trk_f and index>=train.path_trk_b) end -- interpolated position to fractional index given, and angle based on path_dir -- returns: pos, angle(yaw), p_floor, p_ceil function advtrains.path_get_interpolated(train, index) local i_floor = atfloor(index) local i_ceil = i_floor + 1 local frac = index - i_floor local p_floor = advtrains.path_get(train, i_floor) local p_ceil = advtrains.path_get(train, i_ceil) -- Note: minimal code duplication to path_get_adjacent, for performance local a_floor = train.path_dir[i_floor] local a_ceil = train.path_dir[i_ceil] local ang = advtrains.minAngleDiffRad(a_floor, a_ceil) return vector.add(p_floor, vector.multiply(vector.subtract(p_ceil, p_floor), frac)), (a_floor + frac * ang)%(2*math.pi), p_floor, p_ceil end -- returns the 2 path positions directly adjacent to index and the fraction on how to interpolate between them -- returns: pos_floor, pos_ceil, fraction function advtrains.path_get_adjacent(train, index) local i_floor = atfloor(index) local i_ceil = i_floor + 1 local frac = index - i_floor local p_floor = advtrains.path_get(train, i_floor) local p_ceil = advtrains.path_get(train, i_ceil) return p_floor, p_ceil, frac end local function n_interpolate(s, e, f) return s + (e-s)*f end -- This function determines the index resulting from moving along the path by 'offset' meters -- starting from 'index'. See also the comment on the top of the file. function advtrains.path_get_index_by_offset(train, index, offset) local advtrains_path_get = advtrains.path_get -- Step 1: determine my current absolute pos on the path local start_index_f = atfloor(index) local end_index_f = start_index_f + 1 local c_idx = atfloor(index + offset) local c_idx_f = c_idx + 1 local frac = index - start_index_f advtrains_path_get(train, math.min(start_index_f, end_index_f, c_idx, c_idx_f)) advtrains_path_get(train, math.max(start_index_f, end_index_f, c_idx, c_idx_f)) local dist1, dist2 = train.path_dist[start_index_f], train.path_dist[start_index_f+1] local start_dist = dist1 + (dist2-dist1)*frac -- Step 2: determine the total end distance and estimate the index we'd come out local end_dist = start_dist + offset local c_idx = atfloor(index + offset) -- Step 3: move forward/backward to find real index -- We assume here that the distance between 2 path items is never smaller than 1. -- Our estimated index is therefore either exact or too far over, and we're going to go back -- towards the origin. It is therefore sufficient to query path_get a single time -- How we'll adjust c_idx -- Desired position: -------#------ -- Path items : --|--|--|--|-- -- c_idx : ^ while train.path_dist[c_idx] < end_dist do c_idx = c_idx + 1 end while train.path_dist[c_idx] > end_dist do c_idx = c_idx - 1 end -- Step 4: now c_idx points to the place shown above. Find out the fractional part. dist1, dist2 = train.path_dist[c_idx], train.path_dist[c_idx+1] frac = (end_dist - dist1) / (dist2 - dist1) assert(frac>=0 and frac<1, frac) return c_idx + frac end local PATH_CLEAR_KEEP = 4 function advtrains.path_clear_unused(train) local i for i = train.path_ext_b, train.path_req_b - PATH_CLEAR_KEEP do advtrains.occ.clear_item(train.id, advtrains.round_vector_floor_y(train.path[i])) train.path[i] = nil train.path_dist[i-1] = nil train.path_cp[i] = nil train.path_cn[i] = nil train.path_dir[i] = nil