MicroController/stdlib/area/position.lua
2019-03-05 19:50:21 +08:00

207 lines
7.2 KiB
Lua

--- Position module
-- @module Position
Position = {}
require 'stdlib/core'
--- Creates a table representing the position from x and y
-- @param x x-position
-- @param y y-position
-- @return Position
function Position.construct(x, y)
fail_if_missing(x, "missing x position argument")
fail_if_missing(y, "missing y position argument")
return { x = x, y = y }
end
--- Creates a position that is a copy of the given position
-- @param pos the position to copy
-- @return Position
function Position.copy(pos)
fail_if_missing(pos, "missing position argument")
pos = Position.to_table(pos)
return { x = pos.x, y = pos.y }
end
--- Creates a position that is offset by x,y coordinate pair
-- @param pos the position to offset
-- @param x the amount to offset the position in the x direction
-- @param y the amount to offset the position in the y direction
-- @return a new position, offset by the x,y coordinates
function Position.offset(pos, x, y)
fail_if_missing(pos, "missing position argument")
fail_if_missing(x, "missing x-coordinate value")
fail_if_missing(y, "missing y-coordinate value")
if #pos == 2 then
return { x = pos[1] + x, y = pos[2] + y }
else
return { x = pos.x + x, y = pos.y + y }
end
end
--- Adds 2 positions
-- @param pos1 the first position
-- @param pos2 the second position
-- @return a new position
function Position.add(pos1, pos2)
fail_if_missing(pos1, "missing first position argument")
fail_if_missing(pos2, "missing second position argument")
pos1 = Position.to_table(pos1)
pos2 = Position.to_table(pos2)
return { x = pos1.x + pos2.x, y = pos1.y + pos2.y}
end
--- Subtracts 2 positions
-- @param pos1 the first position
-- @param pos2 the second position
-- @return a new position
function Position.subtract(pos1, pos2)
fail_if_missing(pos1, "missing first position argument")
fail_if_missing(pos2, "missing second position argument")
pos1 = Position.to_table(pos1)
pos2 = Position.to_table(pos2)
return { x = pos1.x - pos2.x, y = pos1.y - pos2.y }
end
--- Translates a position in the given direction
-- @param pos the position to translate
-- @param direction in which direction to translate (see defines.direction)
-- @param distance distance of the translation
-- @return the translated position
function Position.translate(pos, direction, distance)
fail_if_missing(pos, "missing position argument")
fail_if_missing(direction, "missing direction argument")
fail_if_missing(distance, "missing distance argument")
pos = Position.to_table(pos)
if direction == defines.direction.north then
return { x = pos.x, y = pos.y - distance }
elseif direction == defines.direction.northeast then
return { x = pos.x + distance, y = pos.y - distance }
elseif direction == defines.direction.east then
return { x = pos.x + distance, y = pos.y }
elseif direction == defines.direction.southeast then
return { x = pos.x + distance, y = pos.y + distance }
elseif direction == defines.direction.south then
return { x = pos.x, y = pos.y + distance }
elseif direction == defines.direction.southwest then
return { x = pos.x - distance, y = pos.y + distance }
elseif direction == defines.direction.west then
return { x = pos.x - distance, y = pos.y }
elseif direction == defines.direction.northwest then
return { x = pos.x - distance, y = pos.y - distance }
end
end
--- Expands a position to a square area
-- @param pos the position to expand into an area
-- @param radius half the side length of the area
-- @return a bounding box
function Position.expand_to_area(pos, radius)
fail_if_missing(pos, "missing position argument")
fail_if_missing(radius, "missing radius argument")
if #pos == 2 then
return { left_top = { x = pos[1] - radius, y = pos[2] - radius }, right_bottom = { x = pos[1] + radius, y = pos[2] + radius } }
end
return { left_top = { x = pos.x - radius, y = pos.y - radius}, right_bottom = { x = pos.x + radius, y = pos.y + radius } }
end
--- Calculates the Euclidean distance squared between two positions, useful when sqrt is not needed
-- @param pos1 the first position
-- @param pos2 the second position
-- @return the square of the Euclidean distance
function Position.distance_squared(pos1, pos2)
fail_if_missing(pos1, "missing first position argument")
fail_if_missing(pos2, "missing second position argument")
pos1 = Position.to_table(pos1)
pos2 = Position.to_table(pos2)
local axbx = pos1.x - pos2.x
local ayby = pos1.y - pos2.y
return axbx * axbx + ayby * ayby
end
--- Calculates the Euclidean distance between two positions
-- @param pos1 the first position
-- @param pos2 the second position
-- @return the square of the Euclidean distance
function Position.distance(pos1, pos2)
fail_if_missing(pos1, "missing first position argument")
fail_if_missing(pos2, "missing second position argument")
return math.sqrt(Position.distance_squared(pos1, pos2))
end
--- Calculates the manhatten distance between two positions
-- @param pos1 the first position
-- @param pos2 the second position
-- @return the square of the Euclidean distance
function Position.manhattan_distance(pos1, pos2)
fail_if_missing(pos1, "missing first position argument")
fail_if_missing(pos2, "missing second position argument")
pos1 = Position.to_table(pos1)
pos2 = Position.to_table(pos2)
return math.abs(pos2.x - pos1.x) + math.abs(pos2.y - pos1.y)
end
-- see: https://en.wikipedia.org/wiki/Machine_epsilon
Position._epsilon = 1.19e-07
--- Whether 2 positions are equal
-- @param pos1 the first position
-- @param pos2 the second position
-- @return true if positions are equal
function Position.equals(pos1, pos2)
if not pos1 or not pos2 then return false end
-- optimize for a shallow equality check first
if pos1 == pos2 then return true end
local epsilon = Position._epsilon
local abs = math.abs
if #pos1 == 2 and #pos2 == 2 then
return abs(pos1[1] - pos2[1]) < epsilon and abs(pos1[2] - pos2[2]) < epsilon
elseif #pos1 == 2 and #pos2 == 0 then
return abs(pos1[1] - pos2.x) < epsilon and abs(pos1[2] - pos2.y) < epsilon
elseif #pos1 == 0 and #pos2 == 2 then
return abs(pos1.x - pos2[1]) < epsilon and abs(pos1.y - pos2[2]) < epsilon
elseif #pos1 == 0 and #pos2 == 0 then
return abs(pos1.x - pos2.x) < epsilon and abs(pos1.y - pos2.y) < epsilon
end
return false
end
--- Converts a position in the array format to a position in the table format
-- @param pos_arr the position to convert
-- @return a converted position, { x = pos_arr[1], y = pos_arr[2] }
function Position.to_table(pos_arr)
fail_if_missing(pos_arr, "missing position argument")
if #pos_arr == 2 then
return { x = pos_arr[1], y = pos_arr[2] }
end
return pos_arr
end
--- Converts a position to a string
-- @param pos the position to convert
-- @return string representation of pos
function Position.tostring(pos)
fail_if_missing(pos, "missing position argument")
if #pos == 2 then
return "Position {x = " .. pos[1] .. ", y = " .. pos[2] .. "}"
else
return "Position {x = " .. pos.x .. ", y = " .. pos.y .. "}"
end
end
return Position