memebuilder/mods/railbuilder/advtrain_helpers.lua
2022-08-16 16:12:37 -04:00

255 lines
9.6 KiB
Lua

ADVTRAINS_RAILS_STRAIGHT2 = { "advtrains:dtrack_vst1", "advtrains:dtrack_vst2" }
ADVTRAINS_RAILS_STRAIGHT3 = { "advtrains:dtrack_vst31", "advtrains:dtrack_vst32", "advtrains:dtrack_vst33" }
ADVTRAINS_RAILS_DIAGONAL = { "advtrains:dtrack_vst1_45", "advtrains:dtrack_vst2_45" }
function fixed_atan(y, x)
local v = math.atan(y/x)
if y >= 0 and x < 0 then return v + math.pi end
if y < 0 and x < 0 then return v - math.pi end
return v
end
function signum(x)
if x < 0 then return -1 end
if x > 0 then return 1 end
return 0
end
-- calculate the slope of a given vector
function calc_slope(delta_pos)
if delta_pos.x == 0 then return delta_pos.y / math.abs(delta_pos.z) end
if delta_pos.z == 0 then return delta_pos.y / math.abs(delta_pos.x) end
return delta_pos.y / math.abs(math.sqrt(1/2 * (delta_pos.x * delta_pos.x + delta_pos.z * delta_pos.z)))
end
-- return a vector with the step width of 1 or 2 in x and z dimension and a fractional y dimension
function delta_to_dir(delta_pos)
local slope = calc_slope(delta_pos)
if delta_pos.x == 0 then return vector.new(0, slope, signum(delta_pos.z)) end
if delta_pos.z == 0 then return vector.new(signum(delta_pos.x), slope, 0) end
if math.abs(delta_pos.x) == 2 * math.abs(delta_pos.z) then
return vector.new(2 * signum(delta_pos.x), slope, signum(delta_pos.z))
end
if 2 * math.abs(delta_pos.x) == math.abs(delta_pos.z) then
return vector.new(signum(delta_pos.x), slope, 2 * signum(delta_pos.z))
end
return vector.new(signum(delta_pos.x), slope, signum(delta_pos.z))
end
local function node_is_advtrains_rail(node)
return string.match(node.name, "advtrains:dtrack")
end
local function is_advtrains_rail_at_pos_or_below(pos)
return node_is_advtrains_rail(minetest.get_node(pos)) or node_is_advtrains_rail(minetest.get_node(vector.subtract(pos, vector.new(0, 1, 0))))
end
-- returns advtrains connection index (a value from 0 to 15)
local function direction_delta_to_advtrains_conn(direction_delta)
return (math.floor(fixed_atan(direction_delta.x,direction_delta.z) / math.pi * 8 + 0.5)) % 16
end
-- create a list of steps that can be added when showing possible end points
-- calculate the direction depending on current player position
local function get_closest_directions(player, rail_start_pos)
local delta_pos = vector.subtract(player:get_pos(), rail_start_pos)
local rotation_index = direction_delta_to_advtrains_conn(delta_pos)
local out_directions = {}
for i=-1,1 do
table.insert(out_directions, (rotation_index + i) % 16)
end
return out_directions
end
-- generate a vector in the plain
-- rotation_index values -7 to 8
local function rotation_index_to_advtrains_dir(rotation_index)
local rotation_index_mod = rotation_index % 4
local rotation_index_whole = math.floor(rotation_index / 4)
local v = nil
if rotation_index_mod == 0 then
v = vector.new(0, 0, 1)
elseif rotation_index_mod == 1 then
v = vector.new(1, 0, 2)
elseif rotation_index_mod == 2 then
v = vector.new(1, 0, 1)
elseif rotation_index_mod == 3 then
v = vector.new(2, 0, 1)
end
local rot_v = vector.rotate(v, { x = 0, y = rotation_index_whole * math.pi / 2, z = 0 })
if rotation_index_whole == 1 or rotation_index_whole == 3 then
-- some bug in Minetest version 5.4 ...
return vector.subtract(vector.new(), rot_v)
end
return rot_v
end
-- generate a vector in 3D
-- rotation_index values -7 to 8
-- vertical_direction values in {- 1/2, -1/3, 0, 1/3, 1/2}
local function rotation_index_and_vertical_direction_to_advtrains_dir(rotation_index, vertical_direction)
local plain_dir = rotation_index_to_advtrains_dir(rotation_index)
if vertical_direction == 0 then
return plain_dir
elseif rotation_index % 4 == 0 then
-- straights have two possible inclinations
local multiplier = 1/math.abs(vertical_direction)
return vector.multiply( vector.add(plain_dir, vector.new(0, vertical_direction, 0)), multiplier)
elseif rotation_index % 4 == 2 then
return vector.multiply( vector.add(plain_dir, vector.new(0, signum(vertical_direction)*1/2, 0)), 2)
else
return nil
end
end
-- TODO: tried with rail_and_can_be_bent from advtrains/trackplacer.lua first, but this did not work - can we improve this?
-- returns possible directions starting from a given track at pos
local function get_advtrains_dirs(original_pos, last_horizontal_direction, last_vertical_direction)
local p_rails={}
local p_railpos={}
local node = minetest.get_node(original_pos)
if last_vertical_direction and last_vertical_direction ~= 0 then
table.insert(p_rails, last_horizontal_direction)
return p_rails
end
if not node or not node_is_advtrains_rail(node) then return nil end
local cconns=advtrains.get_track_connections(node.name, node.param2)
for _, conn in ipairs(cconns) do
table.insert(p_rails, conn.c)
end
if #p_rails == 2 then
local a = p_rails[1]
local b = p_rails[2]
local diff = (a-b)%8
local more_conns = nil
if diff == 0 then
more_conns = {(a-1) % 16, (a+1) % 16, (b-1) % 16, (b+1) % 16}
end
if diff == 7 then
more_conns = {(a+1) % 16, (a+2) % 16, (b-2) % 16, (b-1) % 16}
end
if diff == 1 then
more_conns = {(a-2) % 16, (a-1) % 16, (b+1) % 16, (b+2) % 16}
end
table.insert_all(p_rails, more_conns)
end
--if false then return p_rails end
--p_rails = {}
--tp = advtrains.trackplacer
--for i = 0, 15 do
-- pos = vector.add(original_pos, rotation_index_to_advtrains_dir(i))
-- if tp.rail_and_can_be_bent(pos, (i+8)%16) then
-- table.insert(p_rails, i)
-- end
--end
return p_rails
end
-- returns closure that generates item name and params to place a rail in the given direction
local function direction_step_to_rail_params_sequence(dir_step)
local rotation_index = direction_delta_to_advtrains_conn(dir_step) -- maps to values from [0 to 15]
local rotation_index_mod = rotation_index % 4
local rotation_index_whole = math.floor(rotation_index / 4)
if dir_step.y == 0 then
if rotation_index_mod == 0 then
rail_name = "advtrains:dtrack_st"
end
if rotation_index_mod == 1 then
rail_name = "advtrains:dtrack_st_30"
end
if rotation_index_mod == 2 then
rail_name = "advtrains:dtrack_st_45"
end
if rotation_index_mod == 3 then
rail_name = "advtrains:dtrack_st_60"
end
return function()
return { name=rail_name, param1=14, param2=rotation_index_whole }
end
else
local slope = calc_slope(dir_step)
if dir_step.y < 0 then
rotation_index_whole = (rotation_index_whole + 2) % 4
end
local rail_node_names = nil
if rotation_index_mod == 0 and math.abs(slope) == 0.5 then
rail_node_names = ADVTRAINS_RAILS_STRAIGHT2
elseif rotation_index_mod == 0 and math.abs(slope) == 1/3 then
rail_node_names = ADVTRAINS_RAILS_STRAIGHT3
elseif rotation_index_mod == 2 then
rail_node_names = ADVTRAINS_RAILS_DIAGONAL
end
local increment = signum(dir_step.y)
local rail_name_table_length = #rail_node_names
local i = (increment == -1 and rail_name_table_length - 1) or 0
return function()
local rail_name = rail_node_names[i + 1]
i = (i + increment) % rail_name_table_length
return { name=rail_name, param1=14, param2=rotation_index_whole }
end
end
end
local function try_bend_rail_start(start_pos, direction_delta)
if advtrains.trackplacer then
advtrains.trackplacer.bend_rail(vector.add(start_pos, direction_delta), (8 + advtrain_helpers.direction_delta_to_advtrains_conn(direction_delta)) % 16)
end
end
-- rturns a pair of booleans indicating if the rails in the direction or its opposite are connected
local function find_already_connected(pos)
if advtrains.trackplacer then
return advtrains.trackplacer.find_already_connected(pos, direction)
end
return false, false
end
local function node_is_end_of_upper_slope(node)
return table.indexof(ADVTRAINS_RAILS_STRAIGHT2, node.name) == 2 or
table.indexof(ADVTRAINS_RAILS_STRAIGHT3, node.name) == 3 or
table.indexof(ADVTRAINS_RAILS_DIAGONAL, node.name) == 2
end
-- return horizontal direction in [0, 15] and vertical direction in {-1/2,-1/3,0,1/3,1/2}
local function node_params_to_directions(node)
local mod4offset = 0
local vertical_direction = 0
if node.name == "advtrains:dtrack_st_30" then mod4offset = 1
elseif node.name == "advtrains:dtrack_st_45" then mod4offset = 2
elseif node.name == "advtrains:dtrack_st_60" then mod4offset = 3
elseif table.indexof(ADVTRAINS_RAILS_STRAIGHT2, node.name) == 1 then vertical_direction = -1/2
elseif table.indexof(ADVTRAINS_RAILS_STRAIGHT2, node.name) == 2 then vertical_direction = 1/2
elseif table.indexof(ADVTRAINS_RAILS_STRAIGHT3, node.name) == 1 then vertical_direction = -1/3
elseif table.indexof(ADVTRAINS_RAILS_STRAIGHT3, node.name) == 3 then vertical_direction = 1/3
elseif table.indexof(ADVTRAINS_RAILS_DIAGONAL, node.name) > 0 then
mod4offset = 2
if table.indexof(ADVTRAINS_RAILS_DIAGONAL, node.name) == 1 then vertical_direction = -1/2
else vertical_direction = 1/2
end
end
-- invert direction if lower end of ramp
if vertical_direction < 0 then mod4offset = mod4offset + 8 end
return (node.param2 * 4 + mod4offset) % 16, vertical_direction
end
return {
node_is_advtrains_rail = node_is_advtrains_rail,
is_advtrains_rail_at_pos_or_below = is_advtrains_rail_at_pos_or_below,
direction_delta_to_advtrains_conn = direction_delta_to_advtrains_conn,
get_closest_directions = get_closest_directions,
rotation_index_to_advtrains_dir = rotation_index_to_advtrains_dir,
rotation_index_and_vertical_direction_to_advtrains_dir = rotation_index_and_vertical_direction_to_advtrains_dir,
get_advtrains_dirs = get_advtrains_dirs,
direction_step_to_rail_params_sequence = direction_step_to_rail_params_sequence,
try_bend_rail_start = try_bend_rail_start,
find_already_connected = find_already_connected,
node_is_end_of_upper_slope = node_is_end_of_upper_slope,
node_params_to_directions = node_params_to_directions,
}