1
0
mirror of https://github.com/ComfyFactory/ComfyFactorio.git synced 2024-12-30 23:17:53 +02:00
ComfyFactorio/utils/common.lua
2022-07-19 00:25:42 +02:00

586 lines
15 KiB
Lua

local Public = {}
local insert = table.insert
local remove = table.remove
local random = math.random
local sqrt = math.sqrt
local floor = math.floor
local atan2 = math.atan2
--[[
rand_range - Return random integer within the range.
@param start - Start range.
@param stop - Stop range.
--]]
Public.rand_range = function(start, stop)
return random(start, stop)
end
--[[
for_bounding_box_extra - Execute function per every position within bb with parameter.
@param surf - LuaSurface, that will be given into func.
@param bb - BoundingBox
@param func - User supplied callback that will be executed.
@param args - User supplied arguments.
--]]
Public.for_bounding_box_extra = function(surf, bb, func, args)
for x = bb.left_top.x, bb.right_bottom.x do
for y = bb.left_top.y, bb.right_bottom.y do
func(surf, x, y, args)
end
end
end
--[[
for_bounding_box - Execute function per every position within bb.
@param surf - LuaSurface, that will be given into func.
@param bb - BoundingBox
@param func - User supplied callback that will be executed.
--]]
Public.for_bounding_box = function(surf, bb, func)
for x = bb.left_top.x, bb.right_bottom.x do
for y = bb.left_top.y, bb.right_bottom.y do
func(surf, x, y)
end
end
end
local function safe_get(t, k)
local res, value =
pcall(
function()
return t[k]
end
)
if res then
return value
end
return nil
end
--[[
get_axis - Extract axis value from any point format.
@param point - Table with or without explicity axis members.
@param axis - Single character string describing the axis.
--]]
Public.get_axis = function(point, axis)
if point.position then
return Public.get_axis(point.position, axis)
end
if point[axis] then
return point[axis]
end
if safe_get(point, 'target') then
return Public.get_axis(point.target, axis)
end
if #point ~= 2 then
log('get_axis: invalid point format')
return nil
end
if axis == 'x' then
return point[1]
end
return point[2]
end
--[[
get_close_random_position - Gets randomized close position to origin,
@param origin - Position that will be taken as relative
point for calculation.
@param radius - Radius space.
--]]
Public.get_close_random_position = function(origin, radius)
local x = Public.get_axis(origin, 'x')
local y = Public.get_axis(origin, 'y')
x = Public.rand_range(x - radius, x + radius)
y = Public.rand_range(y - radius, y + radius)
return {x = x, y = y}
end
--[[
get_distance - Returns distance in tiles between 2 points.
@param a - Position, first point.
@param b - Position, second point.
--]]
Public.get_distance = function(a, b)
local h = (Public.get_axis(a, 'x') - Public.get_axis(b, 'x')) ^ 2
local v = (Public.get_axis(a, 'y') - Public.get_axis(b, 'y')) ^ 2
return sqrt(h + v)
end
--[[
point_in_bounding_box - Check whatever point is within bb.
@param point - Position
@param bb - BoundingBox
--]]
Public.point_in_bounding_box = function(point, bb)
local x = Public.get_axis(point, 'x')
local y = Public.get_axis(point, 'y')
if bb.left_top.x <= x and bb.right_bottom.x >= x and bb.left_top.y <= y and bb.right_bottom.y >= y then
return true
end
return false
end
Public.direction_lookup = {
[-1] = {
[1] = defines.direction.southwest,
[0] = defines.direction.west,
[-1] = defines.direction.northwest
},
[0] = {
[1] = defines.direction.south,
[-1] = defines.direction.north
},
[1] = {
[1] = defines.direction.southeast,
[0] = defines.direction.east,
[-1] = defines.direction.northeast
}
}
--[[
get_readable_direction - Return readable direction from point a to b.
@param a - Position A
@param b - Position B
--]]
Public.get_readable_direction = function(a, b)
local a_x = Public.get_axis(a, 'x')
local a_y = Public.get_axis(a, 'y')
local b_x = Public.get_axis(b, 'x')
local b_y = Public.get_axis(b, 'y')
local h, v
if a_x < b_x then
h = 1
elseif a_x > b_x then
h = -1
else
h = 0
end
if a_y < b_y then
v = 1
elseif a_y > b_y then
v = -1
else
v = 0
end
local mapping = {
[defines.direction.southwest] = 'south-west',
[defines.direction.west] = 'west',
[defines.direction.northwest] = 'north-west',
[defines.direction.south] = 'south',
[defines.direction.north] = 'north',
[defines.direction.southeast] = 'south-east',
[defines.direction.east] = 'east',
[defines.direction.northeast] = 'north-east'
}
return mapping[Public.direction_lookup[h][v]]
end
--[[
create_bounding_box_by_points - Construct a BoundingBox using points
from any array of objects such as bounding boxes.
@param objects - Array of objects.
--]]
Public.create_bounding_box_by_points = function(objects)
local box = {
left_top = {
x = Public.get_axis(objects[1], 'x'),
y = Public.get_axis(objects[1], 'y')
},
right_bottom = {
x = Public.get_axis(objects[1], 'x'),
y = Public.get_axis(objects[1], 'y')
}
}
for i = 2, #objects do
local object = objects[i]
if object.bounding_box then
local bb = object.bounding_box
if box.left_top.x > bb.left_top.x then
box.left_top.x = bb.left_top.x
end
if box.right_bottom.x < bb.right_bottom.x then
box.right_bottom.x = bb.right_bottom.x
end
if box.left_top.y > bb.left_top.y then
box.left_top.y = bb.left_top.y
end
if box.right_bottom.y < bb.right_bottom.y then
box.right_bottom.y = bb.right_bottom.y
end
else
local x = Public.get_axis(object, 'x')
local y = Public.get_axis(object, 'y')
if box.left_top.x > x then
box.left_top.x = x
elseif box.right_bottom.x < x then
box.right_bottom.x = x
end
if box.left_top.y > y then
box.left_top.y = y
elseif box.right_bottom.y < y then
box.right_bottom.y = y
end
end
end
box.left_top.x = box.left_top.x - 1
box.left_top.y = box.left_top.y - 1
box.right_bottom.x = box.right_bottom.x + 1
box.right_bottom.y = box.right_bottom.y + 1
return box
end
--[[
enlare_bounding_box - Performs enlargement operation on the bounding box.
@param bb - BoundingBox
@param size - By how many tiles to enlarge.
--]]
Public.enlarge_bounding_box = function(bb, size)
bb.left_top.x = bb.left_top.x - size
bb.left_top.y = bb.left_top.y - size
bb.right_bottom.x = bb.right_bottom.x + size
bb.right_bottom.y = bb.right_bottom.y + size
end
--[[
merge_bounding_boxes - Merge array of BoundingBox objects into a single
object.
@param bbs - Array of BoundingBox objects.
--]]
Public.merge_bounding_boxes = function(bbs)
if bbs == nil then
log('common.merge_bounding_boxes: bbs is nil')
return
end
if #bbs <= 0 then
log('common.merge_bounding_boxes: bbs is empty')
return
end
local box = {
left_top = {
x = bbs[1].left_top.x,
y = bbs[1].left_top.y
},
right_bottom = {
x = bbs[1].right_bottom.x,
y = bbs[1].right_bottom.y
}
}
for i = 2, #bbs do
local bb = bbs[i]
if box.left_top.x > bb.left_top.x then
box.left_top.x = bb.left_top.x
end
if box.right_bottom.x < bb.right_bottom.x then
box.right_bottom.x = bb.right_bottom.x
end
if box.left_top.y > bb.left_top.y then
box.left_top.y = bb.left_top.y
end
if box.right_bottom.y < bb.right_bottom.y then
box.right_bottom.y = bb.right_bottom.y
end
end
return box
end
--[[
get_time - Return strigified time of a tick.
@param ticks - Just a ticks.
--]]
Public.get_time = function(ticks)
local seconds = floor((ticks / 60) % 60)
local minutes = floor((ticks / 60 / 60) % 60)
local hours = floor(ticks / 60 / 60 / 60)
local time
if hours > 0 then
time = string.format('%02d:%01d:%02d', hours, minutes, seconds)
elseif minutes > 0 then
time = string.format('%02d:%02d', minutes, seconds)
else
time = string.format('00:%02d', seconds)
end
return time
end
--[[
polygon_insert - Append vertex in clockwise order.
@param vertex - Point to insert,
@param vertices - Tables of vertices.
--]]
Public.polygon_append_vertex = function(vertices, vertex)
insert(vertices, vertex)
local x_avg, y_avg = 0, 0
for _, v in pairs(vertices) do
x_avg = x_avg + Public.get_axis(v, 'x')
y_avg = y_avg + Public.get_axis(v, 'y')
end
x_avg = x_avg / #vertices
y_avg = y_avg / #vertices
local delta_x, delta_y, rad1, rad2
for i = 1, #vertices, 1 do
for j = 1, #vertices - i do
local v = vertices[j]
delta_x = Public.get_axis(v, 'x') - x_avg
delta_y = Public.get_axis(v, 'y') - y_avg
rad1 = ((atan2(delta_x, delta_y) * (180 / 3.14)) + 360) % 360
v = vertices[j + 1]
delta_x = Public.get_axis(v, 'x') - x_avg
delta_y = Public.get_axis(v, 'y') - y_avg
rad2 = ((atan2(delta_x, delta_y) * (180 / 3.14)) + 360) % 360
if rad1 > rad2 then
vertices[j], vertices[j + 1] = vertices[j + 1], vertices[j]
end
end
end
end
--[[
positions_equal - Checks if given positions are equal.
@param a - Position a
@param b - Position b
--]]
Public.positions_equal = function(a, b)
local p1 = Public.get_axis(a, 'x')
local p2 = Public.get_axis(b, 'x')
if p1 ~= p2 then
return false
end
p1 = Public.get_axis(a, 'y')
p2 = Public.get_axis(b, 'y')
if p1 ~= p2 then
return false
end
return true
end
local function rev(array, index)
if index == nil then
index = 0
end
index = #array - index
return array[index]
end
--[[
deepcopy - Makes a deep copy of an object.
@param orig - Object to copy.
--]]
Public.deepcopy = function(orig)
local orig_type = type(orig)
local copy
if orig_type == 'table' then
copy = {}
for orig_key, orig_value in next, orig, nil do
copy[Public.deepcopy(orig_key)] = Public.deepcopy(orig_value)
end
setmetatable(copy, Public.deepcopy(getmetatable(orig)))
else -- number, string, boolean, etc
copy = orig
end
return copy
end
local function convex_hull_turn(a, b, c)
local x1, x2, x3, y1, y2, y3
x1 = Public.get_axis(a, 'x')
x2 = Public.get_axis(b, 'x')
y1 = Public.get_axis(a, 'y')
y2 = Public.get_axis(b, 'y')
if c then
x3 = Public.get_axis(c, 'x')
y3 = Public.get_axis(c, 'y')
return (x2 - x1) * (y3 - y1) - (y2 - y1) * (x3 - x1)
end
return (x1 * y2) - (y1 * x2)
end
--[[
convex_hull - Generate convex hull out of given vertices.
@param vertices - Table of positions.
--]]
Public.get_convex_hull = function(_vertices)
if #_vertices == 0 then
return {}
end
local vertices = Public.deepcopy(_vertices)
-- Get the lowest point
local v, y1, y2, x1, x2, lowest_index
local lowest = vertices[1]
for i = 2, #vertices do
v = vertices[i]
y1 = Public.get_axis(v, 'y')
y2 = Public.get_axis(lowest, 'y')
if y1 < y2 then
lowest = v
lowest_index = i
elseif y1 == y2 then
x1 = Public.get_axis(v, 'x')
x2 = Public.get_axis(lowest, 'x')
if x1 < x2 then
lowest = v
lowest_index = i
end
end
end
remove(vertices, lowest_index)
x1 = Public.get_axis(lowest, 'x')
y1 = Public.get_axis(lowest, 'y')
-- Sort by angle to horizontal axis.
local rad1, rad2, dist1, dist2
local i, j = 1, 1
while i <= #vertices do
while j <= #vertices - i do
v = vertices[j]
x2 = Public.get_axis(v, 'x')
y2 = Public.get_axis(v, 'y')
rad1 = (atan2(y2 - y1, x2 - x1) * (180 / 3.14) + 320) % 360
v = vertices[j + 1]
x2 = Public.get_axis(v, 'x')
y2 = Public.get_axis(v, 'y')
rad2 = (atan2(y2 - y1, x2 - x1) * (180 / 3.14) + 320) % 360
if rad1 > rad2 then
vertices[j + 1], vertices[j] = vertices[j], vertices[j + 1]
elseif rad1 == rad2 then
dist1 = Public.get_distance(lowest, vertices[j])
dist2 = Public.get_distance(lowest, vertices[j + 1])
if dist1 > dist2 then
remove(vertices, j + 1)
else
remove(vertices, j)
end
end
j = j + 1
end
i = i + 1
end
if #vertices <= 3 then
return {}
end
-- Traverse points.
local stack = {
vertices[1],
vertices[2],
vertices[3]
}
local point
for ii = 4, #vertices do
point = vertices[ii]
while #stack > 1 and convex_hull_turn(point, rev(stack, 1), rev(stack)) >= 0 do
remove(stack)
end
insert(stack, point)
end
insert(stack, lowest)
return stack
end
--[[
get_closest_neighbour - Return object whose is closest to given position.
@param position - Position, origin point
@param object - Table of objects that have any sort of position datafield.
--]]
Public.get_closest_neighbour = function(position, objects)
local closest = objects[1]
local min_dist = Public.get_distance(position, closest)
local object, dist
for i = #objects, 1, -1 do
object = objects[i]
if object and not object.player then
dist = Public.get_distance(position, object)
if dist < min_dist then
closest = object
min_dist = dist
end
end
end
return closest
end
--[[
get_closest_neighbour - Return object whose is closest to given position.
@param position - Position, origin point
@param object - Table of objects that have any sort of position datafield.
--]]
Public.get_closest_neighbour_non_player = function(position, objects)
local closest = objects[1]
local min_dist = Public.get_distance(position, closest)
local object, dist
for i = #objects, 1, -1 do
object = objects[i]
if object and object.valid and object.destructible then
dist = Public.get_distance(position, object)
if dist < min_dist then
closest = object
min_dist = dist
end
end
end
if closest and closest.valid and not closest.destructible then
return false
end
return closest
end
return Public