136 lines
3.4 KiB
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)
|
|
xh1 = max(xh1, xh2)
|
|
|
|
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
|