Aleksei-bird 7c9c708c92 Первый фикс
Пачки некоторых позиций увеличены
2024-03-01 20:54:33 +03:00

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