gear/rect.lua

136 lines
3.4 KiB
Lua

--- Axis-aligned rectangles.
--
-- Function for basic bounding rectangles building and testing.
--
-- @module gear.rect
-- @copyright 2022 The DoubleFourteen Code Forge
-- @author Lorenzo Cogotti
local rotatesincos = require((...):gsub('rect$', '')..'vec').rotatesincos
local min, max = math.min, math.max
local abs = math.abs
local rect = {}
--- Extend rectangle to include a point.
function rect.expand(x,y,w,h, px,py)
if w < 0 or h < 0 then
return px,py,0,0
end
local xw, yh
x = min(x, px)
y = min(y, py)
xw = max(x+w, px)
yh = max(y+h, py)
return x, y, xw-x, yh-y
end
--- Calculate and return the union of two rectangles.
function rect.union(x1,y1,w1,h1, x2,y2,w2,h2)
local xw1,yh1, xw2,yh2
if w1 < 0 or h1 < 0 then
local huge = math.huge
x1,y1,xw1,yh1 = huge,huge,-huge,-huge
else
xw1,yh1 = x1 + w1,y1 + h1
end
if w2 < 0 or h2 < 0 then
local huge = math.huge
x2,y2,xw2,yh2 = huge,huge,-huge,-huge
else
xw2,yh2 = x2 + w2,y2 + h2
end
x1 = min(x1, x2)
y1 = min(y1, y2)
xw1 = max(xw1, xw2)
yh1 = max(yh1, yh2)
return x1, y1, xw1 - x1, yh1 - y1
end
--- Calculate and return the intersection between two rectangles.
function rect.intersection(x1,y1,w1,h1, x2,y2,w2,h2)
if w1 < 0 or h1 < 0 then
return x1,y1,w1,h1
elseif w2 < 0 or h2 < 0 then
return x2,y2,w2,h2
end
local xw1,yh1 = x1+w1, y1+h1
local xw2,yh2 = x2+w2, y2+h2
x1 = max(x1, x2)
y1 = max(y1, y2)
xw1 = min(xw1, xw2)
yh1 = min(yh1, yh2)
return x1,y1, xw1-x1,yh1-y1
end
--- Rotate rectangle around (ox,oy) about rot radians,
-- and return the result's minimum enclosing
-- axis-aligned rectangle.
--
-- NOTE: This causes precision loss, possibly generating
-- larger bounds than needed for the rotated geometry
-- Don't use this function repeatedly on the same bounds.
function rect.rotate(rx,ry,rw,rh, rot, ox,oy)
if rw < 0 or rh < 0 then
return rx,ry,rw,rh
end
ox = ox or 0
oy = oy or 0
local sina,cosa = sin(rot),cos(rot)
local x1,y1 = rotatesincos(rx, ry, sina,cosa, ox,oy)
local x2,y2 = rotatesincos(rx+rw, ry, sina,cosa, ox,oy)
local x3,y3 = rotatesincos(rx+rw, ry+rh, sina,cosa, ox,oy)
local x4,y4 = rotatesincos(rx, ry+rh, sina,cosa, ox,oy)
local rxw, rxh
rx = min(min(min(x1, x2), x3), x4)
ry = min(min(min(y1, y2), y3), y4)
rxw = max(max(max(x1, x2), x3), x4)
ryh = max(max(max(y1, y2), y3), y4)
return rx,ry, rxw-rx,ryh-ry
end
--- Test whether point (x,y) lies inside a rectangle.
function rect.pointinside(x,y, rx,ry,rw,rh)
return x >= rx and y >= ry and x-rx <= rw and y-ry <= rh
end
--- Test whether the first rectangle lies inside the second.
function rect.rectinside(x1,y1,w1,h1, x2,y2,w2,h2)
return (x1 >= x2 and y1 >= y2 and w1 <= w2 and h1 <= h2 and w2 >= 0 and h2 >= 0)
or ((w1 < 0 or h1 < 0) and (w2 >= 0 and h2 >= 0))
end
--- Test two rectangles for equality with optional epsilon.
function rect.eq(x1,y1,w1,h1, x2,y2,w2,h2, eps)
eps = eps or 0.007
return (abs(x1 - x2) <= eps and
abs(y1 - y2) <= eps and
abs(w1 - w2) <= eps and
abs(h1 - h2) <= eps)
or ((w1 < 0 or h1 < 0) and (w2 < 0 or h2 < 0))
end
--- Test whether a rectangle is empty.
function rect.isempty(x,y,w,h)
return w < 0 or h < 0
end
return rect