333 lines
9.6 KiB
Lua
333 lines
9.6 KiB
Lua
local position = require("__flib__/position")
|
|
|
|
--- Utilities for manipulating bounding boxes. All functions support both the shorthand and explicit syntaxes for boxes
|
|
--- and positions, and will preserve the syntax that was passed in. Boxes are considered immutable; all functions will
|
|
--- return new boxes.
|
|
--- ```lua
|
|
--- local flib_bounding_box = require("__flib__/bounding-box")
|
|
--- ```
|
|
--- @class flib_bounding_box
|
|
local flib_bounding_box = {}
|
|
|
|
--- Return a new box expanded to the nearest tile edges.
|
|
--- @param box BoundingBox
|
|
--- @return BoundingBox
|
|
function flib_bounding_box.ceil(box)
|
|
if box.left_top then
|
|
return {
|
|
left_top = { x = math.floor(box.left_top.x), y = math.floor(box.left_top.y) },
|
|
right_bottom = { x = math.ceil(box.right_bottom.x), y = math.ceil(box.right_bottom.y) },
|
|
}
|
|
else
|
|
return {
|
|
{ math.floor(box[1][1]), math.floor(box[1][2]) },
|
|
{ math.ceil(box[2][1]), math.ceil(box[2][2]) },
|
|
}
|
|
end
|
|
end
|
|
|
|
--- Calculate the centerpoint of the box.
|
|
--- @param box BoundingBox
|
|
--- @return MapPosition
|
|
function flib_bounding_box.center(box)
|
|
if box.left_top then
|
|
return {
|
|
x = (box.left_top.x + box.right_bottom.x) / 2,
|
|
y = (box.left_top.y + box.right_bottom.y) / 2,
|
|
}
|
|
else
|
|
return {
|
|
(box[1][1] + box[2][1]) / 2,
|
|
(box[1][2] + box[2][2]) / 2,
|
|
}
|
|
end
|
|
end
|
|
|
|
--- Check if the first box contains the second box.
|
|
--- @param box1 BoundingBox
|
|
--- @param box2 BoundingBox
|
|
--- @return boolean
|
|
function flib_bounding_box.contains_box(box1, box2)
|
|
local box1 = flib_bounding_box.ensure_explicit(box1)
|
|
local box2 = flib_bounding_box.ensure_explicit(box2)
|
|
|
|
return box1.left_top.x <= box2.left_top.x
|
|
and box1.left_top.y <= box2.left_top.y
|
|
and box1.right_bottom.x >= box2.right_bottom.x
|
|
and box1.right_bottom.y >= box2.right_bottom.y
|
|
end
|
|
|
|
--- Check if the given box contains the given position.
|
|
--- @param box BoundingBox
|
|
--- @param pos MapPosition
|
|
--- @return boolean
|
|
function flib_bounding_box.contains_position(box, pos)
|
|
local box = flib_bounding_box.ensure_explicit(box)
|
|
local pos = position.ensure_explicit(pos)
|
|
return
|
|
box.left_top.x <= pos.x and box.left_top.y <= pos.y and box.right_bottom.x >= pos.x and box.right_bottom.y >= pos.y
|
|
end
|
|
|
|
--- Return the box in explicit form.
|
|
--- @param box BoundingBox
|
|
--- @return BoundingBox
|
|
function flib_bounding_box.ensure_explicit(box)
|
|
return {
|
|
left_top = position.ensure_explicit(box.left_top or box[1]),
|
|
right_bottom = position.ensure_explicit(box.right_bottom or box[2]),
|
|
}
|
|
end
|
|
|
|
--- Return the box in shorthand form.
|
|
--- @param box BoundingBox
|
|
--- @return BoundingBox
|
|
function flib_bounding_box.ensure_short(box)
|
|
return {
|
|
position.ensure_short(box.left_top or box[1]),
|
|
position.ensure_short(box.right_bottom or box[2]),
|
|
}
|
|
end
|
|
|
|
--- Return a new box with initial dimensions box1, expanded to contain box2.
|
|
--- @param box1 BoundingBox
|
|
--- @param box2 BoundingBox
|
|
--- @return BoundingBox
|
|
function flib_bounding_box.expand_to_contain_box(box1, box2)
|
|
local box2 = flib_bounding_box.ensure_explicit(box2)
|
|
|
|
if box1.left_top then
|
|
return {
|
|
left_top = {
|
|
x = math.min(box1.left_top.x, box2.left_top.x),
|
|
y = math.min(box1.left_top.y, box2.left_top.y),
|
|
},
|
|
right_bottom = {
|
|
x = math.max(box1.right_bottom.x, box2.right_bottom.x),
|
|
y = math.max(box1.right_bottom.y, box2.right_bottom.y),
|
|
},
|
|
}
|
|
else
|
|
return {
|
|
{
|
|
math.min(box1[1][1], box2.left_top.x),
|
|
math.min(box1[1][2], box2.left_top.y),
|
|
},
|
|
{
|
|
math.max(box1[2][1], box2.right_bottom.x),
|
|
math.max(box1[2][2], box2.right_bottom.y),
|
|
},
|
|
}
|
|
end
|
|
end
|
|
|
|
--- Return a new box expanded to contain the given position.
|
|
--- @param box BoundingBox
|
|
--- @param pos MapPosition
|
|
--- @return BoundingBox
|
|
function flib_bounding_box.expand_to_contain_position(box, pos)
|
|
local pos = position.ensure_explicit(pos)
|
|
|
|
if box.left_top then
|
|
return {
|
|
left_top = { x = math.min(box.left_top.x, pos.x), y = math.min(box.left_top.y, pos.y) },
|
|
right_bottom = { x = math.max(box.right_bottom.x, pos.x), y = math.max(box.right_bottom.y, pos.y) },
|
|
}
|
|
else
|
|
return {
|
|
{ math.min(box[1][1], pos.x), math.min(box[1][2], pos.y) },
|
|
{ math.max(box[2][1], pos.x), math.max(box[2][2], pos.y) },
|
|
}
|
|
end
|
|
end
|
|
|
|
--- Return a new box shrunk to the nearest tile edges.
|
|
--- @param box BoundingBox
|
|
--- @return BoundingBox
|
|
function flib_bounding_box.floor(box)
|
|
if box.left_top then
|
|
return {
|
|
left_top = { x = math.ceil(box.left_top.x), y = math.ceil(box.left_top.y) },
|
|
right_bottom = { x = math.floor(box.right_bottom.x), y = math.floor(box.right_bottom.y) },
|
|
}
|
|
else
|
|
return {
|
|
{ math.ceil(box[1][1]), math.ceil(box[1][2]) },
|
|
{ math.floor(box[2][1]), math.floor(box[2][2]) },
|
|
}
|
|
end
|
|
end
|
|
|
|
--- Create a new box from a centerpoint and dimensions.
|
|
--- @param center MapPosition
|
|
--- @param width number
|
|
--- @param height number
|
|
--- @return BoundingBox
|
|
function flib_bounding_box.from_dimensions(center, width, height)
|
|
if center.x then
|
|
return {
|
|
left_top = { x = center.x - width / 2, y = center.y - height / 2 },
|
|
right_bottom = { x = center.x + width / 2, y = center.y + height / 2 },
|
|
}
|
|
else
|
|
return {
|
|
{ center[1] - width / 2, center[2] - height / 2 },
|
|
{ center[1] + width / 2, center[2] + height / 2 },
|
|
}
|
|
end
|
|
end
|
|
|
|
--- Create a 1x1 box from the given position, optionally snapped to the containing tile edges.
|
|
--- @param pos MapPosition
|
|
--- @param snap boolean?
|
|
--- @return BoundingBox
|
|
function flib_bounding_box.from_position(pos, snap)
|
|
if snap then
|
|
pos = position.floor(pos)
|
|
else
|
|
pos = position.sub(pos, { 0.5, 0.5 })
|
|
end
|
|
local x = pos.x or pos[1]
|
|
local y = pos.y or pos[2]
|
|
if pos.x then
|
|
return {
|
|
left_top = { x = x, y = y },
|
|
right_bottom = { x = x + 1, y = y + 1 },
|
|
}
|
|
else
|
|
return {
|
|
{ x, y },
|
|
{ x + 1, y + 1 },
|
|
}
|
|
end
|
|
end
|
|
|
|
--- Calculate the height of the box.
|
|
--- @param box BoundingBox
|
|
--- @return number
|
|
function flib_bounding_box.height(box)
|
|
if box.left_top then
|
|
return box.right_bottom.y - box.left_top.y
|
|
else
|
|
return box[2][2] - box[1][2]
|
|
end
|
|
end
|
|
|
|
--- Check if the first box intersects (overlaps) the second box.
|
|
--- @param box1 BoundingBox
|
|
--- @param box2 BoundingBox
|
|
--- @return boolean
|
|
function flib_bounding_box.intersects_box(box1, box2)
|
|
local box1 = flib_bounding_box.ensure_explicit(box1)
|
|
local box2 = flib_bounding_box.ensure_explicit(box2)
|
|
return
|
|
box1.left_top.x < box2.right_bottom.x
|
|
and box2.left_top.x < box1.right_bottom.x
|
|
and box1.left_top.y < box2.right_bottom.y
|
|
and box2.left_top.y < box1.right_bottom.y
|
|
end
|
|
|
|
--- Return a new box with the same dimensions, moved by the given delta.
|
|
--- @param box BoundingBox
|
|
--- @param delta MapPosition
|
|
--- @return BoundingBox
|
|
function flib_bounding_box.move(box, delta)
|
|
local dx = delta.x or delta[1]
|
|
local dy = delta.y or delta[2]
|
|
if box.left_top then
|
|
return {
|
|
left_top = { x = box.left_top.x + dx, y = box.left_top.y + dy },
|
|
right_bottom = { x = box.right_bottom.x + dx, y = box.right_bottom.y + dy },
|
|
}
|
|
else
|
|
return {
|
|
{ box[1][1] + dx, box[1][2] + dy },
|
|
{ box[2][1] + dx, box[2][2] + dy },
|
|
}
|
|
end
|
|
end
|
|
|
|
--- Return a new box with the same dimensions centered on the given position.
|
|
--- @param box BoundingBox
|
|
--- @param pos MapPosition
|
|
--- @return BoundingBox
|
|
function flib_bounding_box.recenter_on(box, pos)
|
|
local height = flib_bounding_box.height(box)
|
|
local width = flib_bounding_box.width(box)
|
|
|
|
local pos_x = pos.x or pos[1]
|
|
local pos_y = pos.y or pos[2]
|
|
|
|
if box.left_top then
|
|
return {
|
|
left_top = { x = pos_x - (width / 2), y = pos_y - (height / 2) },
|
|
right_bottom = { x = pos_x + (width / 2), y = pos_y + (height / 2) },
|
|
}
|
|
else
|
|
return {
|
|
{ pos_x - (width / 2), pos_y - (height / 2) },
|
|
{ pos_x + (width / 2), pos_y + (height / 2) },
|
|
}
|
|
end
|
|
end
|
|
|
|
--- Return a new box grown or shrunk by the given delta. A positive delta will grow the box, a negative delta will
|
|
--- shrink it.
|
|
--- @param box BoundingBox
|
|
--- @param delta number
|
|
--- @return BoundingBox
|
|
function flib_bounding_box.resize(box, delta)
|
|
if box.left_top then
|
|
return {
|
|
left_top = { x = box.left_top.x - delta, y = box.left_top.y - delta },
|
|
right_bottom = { x = box.right_bottom.x + delta, y = box.right_bottom.y + delta },
|
|
}
|
|
else
|
|
return {
|
|
{ box[1][1] - delta, box[1][2] - delta },
|
|
{ box[2][1] + delta, box[2][2] + delta },
|
|
}
|
|
end
|
|
end
|
|
|
|
--- Return a new box rotated 90 degrees about its center.
|
|
--- @param box BoundingBox
|
|
--- @return BoundingBox
|
|
function flib_bounding_box.rotate(box)
|
|
local center = flib_bounding_box.center(box)
|
|
local radius_x = flib_bounding_box.width(box) / 2
|
|
local radius_y = flib_bounding_box.height(box) / 2
|
|
|
|
if box.left_top then
|
|
return {
|
|
left_top = { x = center.x - radius_y, y = center.y - radius_x },
|
|
right_bottom = { x = center.x + radius_y, y = center.y + radius_x },
|
|
}
|
|
else
|
|
return {
|
|
{ center.x - radius_y, center.y - radius_x },
|
|
{ center.x + radius_y, center.y + radius_x },
|
|
}
|
|
end
|
|
end
|
|
|
|
--- Return a new box expanded to create a square.
|
|
--- @param box BoundingBox
|
|
--- @return BoundingBox
|
|
function flib_bounding_box.square(box)
|
|
local radius = math.max(flib_bounding_box.width(box), flib_bounding_box.height(box))
|
|
return flib_bounding_box.from_dimensions(flib_bounding_box.center(box), radius, radius)
|
|
end
|
|
|
|
--- Calculate the width of the box.
|
|
--- @param box BoundingBox
|
|
--- @return number
|
|
function flib_bounding_box.width(box)
|
|
if box.left_top then
|
|
return box.right_bottom.x - box.left_top.x
|
|
else
|
|
return box[2][1] - box[1][1]
|
|
end
|
|
end
|
|
|
|
return flib_bounding_box
|