2017-12-18 12:44:36 -08:00
-- path.lua
-- Functions for pathpredicting, put in a separate file.
2018-02-21 10:32:41 -08:00
-- 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
2019-12-06 01:41:51 -08:00
-- path_dist - The total distance of this path element from path element 0
2018-02-21 10:32:41 -08:00
-- 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
2020-04-23 00:44:17 -07:00
-- path_speed- Populated by the LZB system. The maximum speed (velocity) permitted in the moment this path item is passed.
-- (this saves brake distance calculations every step to determine LZB control). nil means no limit.
2018-02-21 10:32:41 -08:00
--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
2019-12-06 01:41:51 -08:00
--
--Distance and index:
-- There is an important difference between the path index and the actual distance on the track: The distance between two path items can be larger than 1,
-- but the corresponding index increment is still 1.
-- Indexes in advtrains can be fractional values. If they are, it means that the actual position is interpolated between the 2 adjacent path items.
-- If you need to proceed along the path by a specific actual distance, it does NOT work to simply add it to the index. You should use the path_get_index_by_offset() function.
2018-02-21 10:32:41 -08:00
-- 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 }
2019-12-06 01:41:51 -08:00
train.path_dist = { [ 0 ] = 0 }
2018-02-21 10:32:41 -08:00
train.path_dir = {
2018-04-26 14:35:19 -07:00
[ 0 ] = advtrains.conn_angle_median ( conns [ mconnid ] . c , conns [ connid ] . c )
2018-02-21 10:32:41 -08:00
}
2020-04-23 00:44:17 -07:00
train.path_speed = { }
2018-02-21 10:32:41 -08:00
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
2018-05-17 02:16:04 -07:00
advtrains.occ . set_item ( train.id , posr , 0 )
2018-05-29 06:53:34 -07:00
return true
2018-05-17 02:16:04 -07:00
end
-- Sets position and connid to properly restore after a crash, e.g. in order
-- to save the train or to invalidate its path
-- Assumes that the train is in clean state
-- if invert ist true, setrestore will use the end index
function advtrains . path_setrestore ( train , invert )
local idx = train.index
if invert then
idx = train.end_index
end
2018-05-17 12:37:01 -07:00
local pos , connid , frac = advtrains.path_getrestore ( train , idx , invert , true )
2018-05-17 02:16:04 -07:00
train.last_pos = pos
train.last_connid = connid
train.last_frac = frac
end
-- Get restore position, connid and frac (in this order) for a train that will originate at the passed index
-- If invert is set, it will return path_cp and multiply frac by -1, in order to reverse the train there.
2018-05-29 03:27:02 -07:00
function advtrains . path_getrestore ( train , index , invert )
2018-05-17 12:37:01 -07:00
local idx = index
2018-05-17 02:16:04 -07:00
local cns = train.path_cn
if invert then
cns = train.path_cp
end
2018-05-29 03:27:02 -07:00
local fli = atfloor ( index )
2018-05-17 12:37:01 -07:00
advtrains.path_get ( train , fli )
2018-05-17 02:16:04 -07:00
if fli > train.path_trk_f then
fli = train.path_trk_f
end
if fli < train.path_trk_b then
fli = train.path_trk_b
end
return advtrains.path_get ( train , fli ) ,
cns [ fli ] ,
( idx - fli ) * ( invert and - 1 or 1 )
2018-02-21 10:32:41 -08:00
end
2018-04-26 14:35:19 -07:00
-- Invalidates a path
2018-05-17 02:16:04 -07:00
-- this is supposed to clear stuff from the occupation tables
2019-01-22 03:26:31 -08:00
-- This function throws a warning whenever any code calls it while the train steps are run, since that must not happen.
-- The ignore_lock parameter can be used to ignore this, however, it should then be accompanied by a call to train_ensure_init
-- before returning from the calling function.
function advtrains . path_invalidate ( train , ignore_lock )
if advtrains.lock_path_inval and not ignore_lock then
atwarn ( " Train " , train.train_id , " : Illegal path invalidation has occured during train step: " )
atwarn ( debug.traceback ( ) )
end
2018-05-17 02:16:04 -07:00
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
2018-04-25 07:38:12 -07:00
train.path = nil
train.path_dist = nil
train.path_cp = nil
train.path_cn = nil
train.path_dir = nil
2020-04-23 00:44:17 -07:00
train.path_speed = nil
2018-04-25 07:38:12 -07:00
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
2018-07-04 05:04:41 -07:00
train.dirty = true
2020-04-23 00:44:17 -07:00
--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.
2020-06-12 03:32:46 -07:00
function advtrains . path_invalidate_ahead ( train , start_idx , ignore_when_passed )
2021-02-10 12:41:05 -08:00
if not train.path then
-- the path wasn't even initialized. Nothing to do
return
end
2020-04-23 00:44:17 -07:00
local idx = atfloor ( start_idx )
2020-06-12 03:32:46 -07:00
--atdebug("Invalidate_ahead:",train.id,"start_index",start_idx,"cur_idx",train.index)
2020-04-23 00:44:17 -07:00
2020-06-12 03:32:46 -07:00
if ( idx <= train.index - 0.5 ) then
if ignore_when_passed then
--atdebug("ignored passed")
return
end
2020-04-23 00:44:17 -07:00
advtrains.path_print ( train , atwarn )
error ( " Train " + train.id + " : Cannot path_invalidate_ahead start_idx= " + idx + " as train has already passed! " )
end
2020-06-12 03:32:46 -07:00
-- 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
2020-04-23 00:44:17 -07:00
while train.path [ i ] do
advtrains.occ . clear_item ( train.id , advtrains.round_vector_floor_y ( train.path [ i ] ) )
2020-06-12 03:32:46 -07:00
i = i + 1
2020-04-23 00:44:17 -07:00
end
2020-06-12 03:32:46 -07:00
train.path_ext_f = idx
train.path_trk_f = math.min ( idx , train.path_trk_f )
2020-04-23 00:44:17 -07:00
2020-06-12 03:32:46 -07:00
-- callbacks called anyway for current node, because of LZB
2020-04-23 00:44:17 -07:00
advtrains.run_callbacks_invahead ( train.id , train , idx )
2018-04-25 07:38:12 -07:00
end
2018-04-26 14:35:19 -07:00
-- 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 )
2019-01-23 06:35:58 -08:00
printf ( " path_print: tid = " , train.id , " index = " , train.index , " end_index = " , train.end_index , " vel = " , train.velocity )
2018-10-17 08:45:51 -07:00
if not train.path then
printf ( " path_print: Path is invalidated/inexistant. " )
return
end
2020-04-23 00:44:17 -07:00
printf ( " i: CP Position Dir CN Dist Speed " )
2018-04-26 14:35:19 -07:00
for i = train.path_ext_b , train.path_ext_f do
2018-05-29 03:27:02 -07:00
if i == train.path_trk_b then
printf ( " --Back on-track border here-- " )
end
2020-04-23 00:44:17 -07:00
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 ] )
2018-05-29 03:27:02 -07:00
if i == train.path_trk_f then
printf ( " --Front on-track border here-- " )
end
2018-04-26 14:35:19 -07:00
end
end
2018-02-21 10:32:41 -08:00
-- 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 )
2018-04-25 07:38:12 -07:00
if not train.path then
error ( " For train " .. train.id .. " : path_get called but there's no path set yet! " )
end
2018-02-21 10:32:41 -08:00
if index ~= atfloor ( index ) then
error ( " For train " .. train.id .. " : Called path_get() but index= " .. index .. " is not a round number " )
end
2019-12-06 01:41:51 -08:00
2018-04-26 14:35:19 -07:00
local pef = train.path_ext_f
2019-12-06 01:41:51 -08:00
-- generate forward (front of train, positive)
2018-04-26 14:35:19 -07:00
while index > pef do
local pos = train.path [ pef ]
local connid = train.path_cn [ pef ]
2018-04-25 07:38:12 -07:00
local node_ok , this_conns , adj_pos , adj_connid , conn_idx , nextrail_y , next_conns
2018-04-26 14:35:19 -07:00
if pef == train.path_trk_f then
2018-04-25 07:38:12 -07:00
node_ok , this_conns = advtrains.get_rail_info_at ( pos )
2018-04-26 14:35:19 -07:00
if not node_ok then error ( " For train " .. train.id .. " : Path item " .. pef .. " on-track but not a valid node! " ) end
2018-04-25 07:38:12 -07:00
adj_pos , adj_connid , conn_idx , nextrail_y , next_conns = advtrains.get_adjacent_rail ( pos , this_conns , connid , train.drives_on )
2018-02-21 10:32:41 -08:00
end
2018-04-26 14:35:19 -07:00
pef = pef + 1
2018-02-21 10:32:41 -08:00
if adj_pos then
2018-05-17 02:16:04 -07:00
advtrains.occ . set_item ( train.id , adj_pos , pef )
2020-06-10 11:56:35 -07:00
-- 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
2018-02-21 10:32:41 -08:00
adj_pos.y = adj_pos.y + nextrail_y
2018-04-26 14:35:19 -07:00
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
2018-02-21 10:32:41 -08:00
else
-- off-track fallback behavior
2018-04-26 14:35:19 -07:00
adj_pos = advtrains.pos_add_angle ( pos , train.path_dir [ pef - 1 ] )
2018-05-29 03:27:02 -07:00
--atdebug("Offtrack overgenerating(front) at",adj_pos,"index",peb,"trkf",train.path_trk_f)
2018-04-26 14:35:19 -07:00
train.path_dir [ pef ] = train.path_dir [ pef - 1 ]
2018-02-21 10:32:41 -08:00
end
2018-04-26 14:35:19 -07:00
train.path [ pef ] = adj_pos
2019-12-06 01:41:51 -08:00
train.path_dist [ pef ] = train.path_dist [ pef - 1 ] + vector.distance ( pos , adj_pos )
2018-02-21 10:32:41 -08:00
end
2018-04-26 14:35:19 -07:00
train.path_ext_f = pef
2019-12-06 01:41:51 -08:00
2018-04-26 14:35:19 -07:00
local peb = train.path_ext_b
2019-12-06 01:41:51 -08:00
-- generate backward (back of train, negative)
2018-04-26 14:35:19 -07:00
while index < peb do
local pos = train.path [ peb ]
local connid = train.path_cp [ peb ]
2018-04-25 07:38:12 -07:00
local node_ok , this_conns , adj_pos , adj_connid , conn_idx , nextrail_y , next_conns
2018-04-26 14:35:19 -07:00
if peb == train.path_trk_b then
2018-04-25 07:38:12 -07:00
node_ok , this_conns = advtrains.get_rail_info_at ( pos )
2018-04-26 14:35:19 -07:00
if not node_ok then error ( " For train " .. train.id .. " : Path item " .. peb .. " on-track but not a valid node! " ) end
2018-04-25 07:38:12 -07:00
adj_pos , adj_connid , conn_idx , nextrail_y , next_conns = advtrains.get_adjacent_rail ( pos , this_conns , connid , train.drives_on )
2018-02-21 10:32:41 -08:00
end
2018-04-26 14:35:19 -07:00
peb = peb - 1
2018-02-21 10:32:41 -08:00
if adj_pos then
2018-05-17 02:16:04 -07:00
advtrains.occ . set_item ( train.id , adj_pos , peb )
2020-06-10 11:56:35 -07:00
-- 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
2020-06-14 03:24:30 -07:00
-- atdebug(id,"has split points restored at",adj_pos)
2020-06-10 11:56:35 -07:00
mconnid = 3
end
2018-02-21 10:32:41 -08:00
adj_pos.y = adj_pos.y + nextrail_y
2018-04-26 14:35:19 -07:00
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
2018-02-21 10:32:41 -08:00
else
-- off-track fallback behavior
2018-05-17 02:16:04 -07:00
adj_pos = advtrains.pos_add_angle ( pos , train.path_dir [ peb + 1 ] + math.pi )
2018-05-29 03:27:02 -07:00
--atdebug("Offtrack overgenerating(back) at",adj_pos,"index",peb,"trkb",train.path_trk_b)
2018-04-26 14:35:19 -07:00
train.path_dir [ peb ] = train.path_dir [ peb + 1 ]
2018-02-21 10:32:41 -08:00
end
2018-04-26 14:35:19 -07:00
train.path [ peb ] = adj_pos
2019-12-06 01:41:51 -08:00
train.path_dist [ peb ] = train.path_dist [ peb + 1 ] - vector.distance ( pos , adj_pos )
2018-02-21 10:32:41 -08:00
end
2018-04-26 14:35:19 -07:00
train.path_ext_b = peb
2018-02-21 10:32:41 -08:00
2018-04-23 06:51:50 -07:00
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
2018-02-21 10:32:41 -08:00
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
2018-04-19 02:38:00 -07:00
-- returns: pos, angle(yaw), p_floor, p_ceil
2018-02-21 10:32:41 -08:00
function advtrains . path_get_interpolated ( train , index )
local i_floor = atfloor ( index )
local i_ceil = i_floor + 1
local frac = index - i_floor
2018-04-25 07:38:12 -07:00
local p_floor = advtrains.path_get ( train , i_floor )
2018-02-21 10:32:41 -08:00
local p_ceil = advtrains.path_get ( train , i_ceil )
2018-04-19 02:38:00 -07:00
-- Note: minimal code duplication to path_get_adjacent, for performance
2018-02-21 10:32:41 -08:00
2018-04-26 14:35:19 -07:00
local a_floor = train.path_dir [ i_floor ]
local a_ceil = train.path_dir [ i_ceil ]
2018-02-21 10:32:41 -08:00
local ang = advtrains.minAngleDiffRad ( a_floor , a_ceil )
2018-05-17 02:16:04 -07:00
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
2018-04-19 02:38:00 -07:00
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
2018-04-25 07:38:12 -07:00
local p_floor = advtrains.path_get ( train , i_floor )
2018-04-19 02:38:00 -07:00
local p_ceil = advtrains.path_get ( train , i_ceil )
return p_floor , p_ceil , frac
2018-02-21 10:32:41 -08:00
end
2019-12-06 01:41:51 -08:00
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.
2018-04-19 02:38:00 -07:00
function advtrains . path_get_index_by_offset ( train , index , offset )
2020-07-10 03:02:23 -07:00
local advtrains_path_get = advtrains.path_get
2019-12-06 01:41:51 -08:00
-- Step 1: determine my current absolute pos on the path
2020-07-10 03:02:23 -07:00
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 ) )
2019-12-06 01:41:51 -08:00
local dist1 , dist2 = train.path_dist [ start_index_f ] , train.path_dist [ start_index_f + 1 ]
2020-07-10 03:02:23 -07:00
local start_dist = dist1 + ( dist2 - dist1 ) * frac
2019-12-06 01:41:51 -08:00
-- Step 2: determine the total end distance and estimate the index we'd come out
local end_dist = start_dist + offset
2020-07-10 03:02:23 -07:00
local c_idx = atfloor ( index + offset )
2019-12-06 01:41:51 -08:00
-- 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
2018-04-25 07:38:12 -07:00
2019-12-06 01:41:51 -08:00
-- How we'll adjust c_idx
-- Desired position: -------#------
-- Path items : --|--|--|--|--
-- c_idx : ^
2020-07-10 03:02:23 -07:00
2019-12-06 01:41:51 -08:00
while train.path_dist [ c_idx ] < end_dist do
c_idx = c_idx + 1
2018-04-25 07:38:12 -07:00
end
2019-12-06 01:41:51 -08:00
while train.path_dist [ c_idx ] > end_dist do
c_idx = c_idx - 1
2018-02-21 10:32:41 -08:00
end
2018-04-25 07:38:12 -07:00
2019-12-06 01:41:51 -08:00
-- 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
2018-02-21 10:32:41 -08:00
end
2018-04-23 06:51:50 -07:00
2018-04-26 14:35:19 -07:00
local PATH_CLEAR_KEEP = 4
2018-04-23 06:51:50 -07:00
function advtrains . path_clear_unused ( train )
2018-04-26 14:35:19 -07:00
local i
2018-04-23 06:51:50 -07:00
for i = train.path_ext_b , train.path_req_b - PATH_CLEAR_KEEP do
2018-05-17 02:16:04 -07:00
advtrains.occ . clear_item ( train.id , advtrains.round_vector_floor_y ( train.path [ i ] ) )
2018-04-23 06:51:50 -07:00
train.path [ i ] = nil
2018-04-25 07:38:12 -07:00
train.path_dist [ i - 1 ] = nil
2018-04-23 06:51:50 -07:00
train.path_cp [ i ] = nil
train.path_cn [ i ] = nil
train.path_dir [ i ] = nil
2018-04-26 14:35:19 -07:00
train.path_ext_b = i + 1
2018-04-23 06:51:50 -07:00
end
2018-04-26 14:35:19 -07:00
2020-04-23 00:44:17 -07:00
--[[ Why exactly are we clearing path from the front? This doesn't make sense!
2018-04-26 14:35:19 -07:00
for i = train.path_ext_f , train.path_req_f + PATH_CLEAR_KEEP , - 1 do
2018-05-17 02:16:04 -07:00
advtrains.occ . clear_item ( train.id , advtrains.round_vector_floor_y ( train.path [ i ] ) )
2018-04-23 06:51:50 -07:00
train.path [ i ] = nil
2018-04-25 07:38:12 -07:00
train.path_dist [ i ] = nil
2018-04-23 06:51:50 -07:00
train.path_cp [ i ] = nil
train.path_cn [ i ] = nil
train.path_dir [ i + 1 ] = nil
2018-05-17 12:37:01 -07:00
train.path_ext_f = i - 1
2020-04-23 00:44:17 -07:00
end ] ]
2018-05-29 03:27:02 -07:00
train.path_trk_b = math.max ( train.path_trk_b , train.path_ext_b )
2020-04-23 00:44:17 -07:00
--train.path_trk_f = math.min(train.path_trk_f, train.path_ext_f)
2018-04-26 14:35:19 -07:00
2018-04-23 06:51:50 -07:00
train.path_req_f = math.ceil ( train.index )
train.path_req_b = math.floor ( train.end_index or train.index )
end
2020-04-23 00:44:17 -07:00
-- Scan the path of the train for position, without querying the occupation table
-- returns index, or nil if pos is not on the path
2018-04-23 06:51:50 -07:00
function advtrains . path_lookup ( train , pos )
local cp = advtrains.round_vector_floor_y ( pos )
for i = train.path_ext_b , train.path_ext_f do
if vector.equals ( advtrains.round_vector_floor_y ( train.path [ i ] ) , cp ) then
return i
end
end
return nil
end
2021-07-07 13:14:20 -07:00
-- Projects the path of "train" onto the path of "onto_train_id", and returns the index on onto_train's path
-- that corresponds to "index" on "train"'s path, as well as whether both trains face each other
-- index may be fractional
-- returns: res_index, trains_facing
-- returns nil when path can not be projected, either because trains are on different tracks or
-- node at "index" happens to be on a turnout and it's the wrong direction
-- Note - duplicate with similar functionality is in train_step_b() - that code combines train detection with projecting
function advtrains . path_project ( train , index , onto_train_id )
local base_idx = atfloor ( index )
local frac_part = index - base_idx
local base_pos = advtrains.path_get ( train , base_idx )
local base_cn = train.path_cn [ base_idx ]
local otrn = advtrains.trains [ onto_train_id ]
-- query occupation
local occ = advtrains.occ . get_trains_over ( base_pos )
-- is wanted train id contained?
local ob_idx = occ [ onto_train_id ]
if not ob_idx then
return nil
end
-- retrieve other train's cn and cp
local ocn = otrn.path_cn [ ob_idx ]
local ocp = otrn.path_cp [ ob_idx ]
if base_cn == ocn then
-- same direction
return ob_idx + frac_part , false
elseif base_cn == ocp then
-- facing trains - subtract index frac
return ob_idx - frac_part , true
else
-- same path item but no common connections - deny
return nil
end
end