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

245 lines
8.6 KiB
Lua

--- Area module
-- @module Area
require 'stdlib/core'
require 'stdlib/area/position'
Area = {}
--- Creates an area from the 2 positions p1 and p2
-- @param x1 x-position of left_top, first point
-- @param y1 y-position of left_top, first point
-- @param x2 x-position of right_bottom, second point
-- @param y2 y-position of right_bottom, second point
-- @return Area tabled area
function Area.construct(x1, y1, x2, y2)
return { left_top = Position.construct(x1, y1), right_bottom = Position.construct(x2, y2) }
end
--- Returns the size of the space contained in the 2d area </br>
-- <b>Deprecated</b>, Area.area is misleading. See: Area.size
-- @param area the area
-- @return size of the area
function Area.area(area)
return Area.size(area)
end
--- Returns the size of the space contained in the 2d area
-- @param area the area
-- @return size of the area
function Area.size(area)
fail_if_missing(area, "missing area value")
area = Area.to_table(area)
local left_top = Position.to_table(area.left_top)
local right_bottom = Position.to_table(area.right_bottom)
local dx = math.abs(left_top.x - right_bottom.x)
local dy = math.abs(left_top.y - right_bottom.y)
return dx * dy
end
--- Tests if a position {x, y} is inside (inclusive) of area
-- @param area the area
-- @param pos the position to check
-- @return true if the position is inside of the area
function Area.inside(area, pos)
fail_if_missing(pos, "missing pos value")
fail_if_missing(area, "missing area value")
pos = Position.to_table(pos)
area = Area.to_table(area)
local left_top = Position.to_table(area.left_top)
local right_bottom = Position.to_table(area.right_bottom)
return pos.x >= left_top.x and pos.y >= left_top.y and pos.x <= right_bottom.x and pos.y <= right_bottom.y
end
--- Shrinks the size of an area by the given amount
-- @param area the area
-- @param amount to shrink each edge of the area inwards by
-- @return the shrunk area
function Area.shrink(area, amount)
fail_if_missing(area, "missing area value")
fail_if_missing(amount, "missing amount value")
if amount < 0 then error("Can not shrunk area by a negative amount (see Area.expand)!", 2) end
area = Area.to_table(area)
local left_top = Position.to_table(area.left_top)
local right_bottom = Position.to_table(area.right_bottom)
return {left_top = {x = left_top.x + amount, y = left_top.y + amount}, right_bottom = {x = right_bottom.x - amount, y = right_bottom.y - amount}}
end
--- Expands the size of an area by the given amount
-- @param area the area
-- @param amount to expand each edge of the area outwards by
-- @return the expanded area
function Area.expand(area, amount)
fail_if_missing(area, "missing area value")
fail_if_missing(amount, "missing amount value")
if amount < 0 then error("Can not expand area by a negative amount (see Area.shrink)!", 2) end
area = Area.to_table(area)
local left_top = Position.to_table(area.left_top)
local right_bottom = Position.to_table(area.right_bottom)
return {left_top = {x = left_top.x - amount, y = left_top.y - amount}, right_bottom = {x = right_bottom.x + amount, y = right_bottom.y + amount}}
end
--- Calculates the center of the area and returns the position
-- @param area the area
-- @return area to find the center for
function Area.center(area)
fail_if_missing(area, "missing area value")
area = Area.to_table(area)
local dist_x = area.right_bottom.x - area.left_top.x
local dist_y = area.right_bottom.y - area.left_top.y
return {x = area.left_top.x + (dist_x / 2), y = area.left_top.y + (dist_y / 2)}
end
--- Offsets the area by the {x, y} values
-- @param area the area
-- @param pos the {x, y} amount to offset the area
-- @return offset area by the position values
function Area.offset(area, pos)
fail_if_missing(area, "missing area value")
fail_if_missing(pos, "missing pos value")
area = Area.to_table(area)
return {left_top = Position.add(area.left_top, pos), right_bottom = Position.add(area.right_bottom, pos)}
end
--- Converts an area to the integer representation, by taking the floor of the left_top and the ceiling of the right_bottom
-- @param area the area
-- @return the rounded integer representation
function Area.round_to_integer(area)
fail_if_missing(area, "missing area value")
area = Area.to_table(area)
local left_top = Position.to_table(area.left_top)
local right_bottom = Position.to_table(area.right_bottom)
return {left_top = {x = math.floor(left_top.x), y = math.floor(left_top.y)},
right_bottom = {x = math.ceil(right_bottom.x), y = math.ceil(right_bottom.y)}}
end
--- Iterates an area.
-- @usage
---for x,y in Area.iterate({{0, -5}, {3, -3}}) do
-----...
---end
-- @param area the area
-- @return iterator
function Area.iterate(area)
fail_if_missing(area, "missing area value")
local iterator = {idx = 0}
function iterator.iterate(area)
local rx = area.right_bottom.x - area.left_top.x + 1
local dx = iterator.idx % rx
local dy = math.floor(iterator.idx / rx)
iterator.idx = iterator.idx + 1
if (area.left_top.y + dy) > area.right_bottom.y then
return
end
return (area.left_top.x + dx), (area.left_top.y + dy)
end
return iterator.iterate, Area.to_table(area), 0
end
--- Iterates an area in a spiral inner-most to outer-most fashion.
---<p><i>Example:</i></p>
---<pre>
---for x, y in Area.spiral_iterate({{-2, -1}, {2, 1}}) do
---- print("(" .. x .. ", " .. y .. ")")
---end
--- prints: (0, 0) (1, 0) (1, 1) (0, 1) (-1, 1) (-1, 0) (-1, -1) (0, -1) (1, -1) (2, -1) (2, 0) (2, 1) (-2, 1) (-2, 0) (-2, -1)
---</pre>
-- iterates in the order depicted:<br/>
-- ![](http://i.imgur.com/EwfO0Es.png)
-- @param area the area
-- @return iterator
function Area.spiral_iterate(area)
fail_if_missing(area, "missing area value")
area = Area.to_table(area)
local rx = area.right_bottom.x - area.left_top.x + 1
local ry = area.right_bottom.y - area.left_top.y + 1
local half_x = math.floor(rx / 2)
local half_y = math.floor(ry / 2)
local center_x = area.left_top.x + half_x
local center_y = area.left_top.y + half_y
local x = 0
local y = 0
local dx = 0
local dy = -1
local iterator = {list = {}, idx = 1}
for i = 1, math.max(rx, ry) * math.max(rx, ry) do
if -(half_x) <= x and x <= half_x and -(half_y) <= y and y <= half_y then
table.insert(iterator.list, {x, y})
end
if x == y or (x < 0 and x == -y) or (x > 0 and x == 1 - y) then
local temp = dx
dx = -(dy)
dy = temp
end
x = x + dx
y = y + dy
end
function iterator.iterate(area)
if #iterator.list < iterator.idx then return end
local x, y = unpack(iterator.list[iterator.idx])
iterator.idx = iterator.idx + 1
return (center_x + x), (center_y + y)
end
return iterator.iterate, Area.to_table(area), 0
end
--- Creates a new area, a modified copy of the original, such that left and right x, up and down y are normalized, where left.x < right.x, left.y < right.y order
-- @param area the area to adjust
-- @return a normalized area, always { left_top = {x = ..., y = ...}, right_bottom = {x = ..., y = ...} }
function Area.normalize(area)
fail_if_missing(area, "missing area value")
area = Area.to_table(area)
local left_top = Position.copy(area.left_top)
local right_bottom = Position.copy(area.right_bottom)
if right_bottom.x < left_top.x then
local x = left_top.x
left_top.x = right_bottom.x
right_bottom.x = x
end
if right_bottom.y < left_top.y then
local y = left_top.y
left_top.y = right_bottom.y
right_bottom.y = y
end
return Area.construct(left_top.x, left_top.y, right_bottom.x, right_bottom.y)
end
--- Creates a new area, a modified copy of the original, such that left and right x, up and down y are normalized, where left.x < right.x, left.y < right.y order
-- <b>Deprecated</b>, Area.adjust is ambigious. See: Area.normalize
-- @param area the area to adjust
-- @return a adjusted area, always { left_top = {x = ..., y = ...}, right_bottom = {x = ..., y = ...} }
function Area.adjust(area)
return Area.normalize(area)
end
--- Converts an area in the array format to an array in the table format
-- @param area_arr the area to convert
-- @return a converted area, { left_top = area_arr[1], right_bottom = area_arr[2] }
function Area.to_table(area_arr)
fail_if_missing(area_arr, "missing area value")
if #area_arr == 2 then
return { left_top = Position.to_table(area_arr[1]), right_bottom = Position.to_table(area_arr[2]) }
end
return area_arr
end
return Area