Box (center point with half-width and half-height extents)
One way to represent rectangles is as a center point (x, y) with half-width and half-height extents (hw, hh).
This is very useful when programming games, because it's generally easier to work with the centers of collisions shapes.
Also, it's easier to include rotation or other transformations to the rectangle when its origin is at the center.
AABB (left, top, right, bottom)
A second implementation treats rectangles as four numbers (l, t, r, b) where
(l, t) is the top-left corner and (r, b) is the bottom-right corner.
Rect (top-left point with width and height dimensions)
The last example represent rectangles as a top-left corner point (x, y) with width and height dimensions (w, h).
function pointOnSegment(px, py, x1, y1, x2, y2) local cx, cy = px - x1, py - y1 local dx, dy = x2 - x1, y2 - y1 local d = (dx*dx + dy*dy) if d == 0 then return x1, y1 end local u = (cx*dx + cy*dy)/d if u < 0 then u = 0 elseif u > 1 then u = 1 end return x1 + u*dx, y1 + u*dy end
function pointOnCircle(px, py, cx, cy, r) local dx, dy = px - cx, py - cy local d = math.sqrt(dx*dx + dy*dy) if d <= r then return px, py end return dx/d*r + cx, dy/d*r + cy end
function pointOnBox(px, py, x, y, hw, hh) local qx, qy = px - x, py - y if qx > hw then qx = hw elseif qx < -hw then qx = -hw end if qy > hh then qy = hh elseif qy < -hh then qy = -hh end return qx + x, qy + y end
local function dot(ax, ay, bx, by) return ax*bx + ay*by end function pointOnTriangle(px, py, ax, ay, bx, by, cx, cy) local abx, aby = bx - ax, by - ay local acx, acy = cx - ax, cy - ay local apx, apy = px - ax, py - ay -- vertex region outside a local d1 = dot(abx, aby, apx, apy) local d2 = dot(acx, acy, apx, apy) if d1 <= 0 and d2 <= 0 then return ax, ay end -- vertex region outside b local bpx, bpy = px - bx, py - by local d3 = dot(abx, aby, bpx, bpy) local d4 = dot(acx, acy, bpx, bpy) if d3 >= 0 and d4 <= d3 then return bx, by end -- edge region ab if d1 >= 0 and d3 <= 0 and d1*d4 - d3*d2 <= 0 then local v = d1/(d1 - d3) return ax + abx*v, ay + aby*v end -- vertex region outside c local cpx, cpy = px - cx, py - cy local d5 = dot(abx, aby, cpx, cpy) local d6 = dot(acx, acy, cpx, cpy) if d6 >= 0 and d5 <= d6 then return cx, cy end -- edge region ac if d2 >= 0 and d6 <= 0 and d5*d2 - d1*d6 <= 0 then local w = d2/(d2 - d6) return ax + acx*w, ay + acy*w end -- edge region bc if d3*d6 - d5*d4 <= 0 then local d43 = d4 - d3 local d56 = d5 - d6 if d43 >= 0 and d56 >= 0 then local w = d43/(d43 + d56) return bx + (cx - bx)*w, by + (cy - by)*w end end -- inside face region return px, py end
function pointInCircle(px, py, cx, cy, r) local dx, dy = px - cx, py - cy return dx*dx + dy*dy <= r*r end
function pointInBox(px, py, x, y, hw, hh) if math.abs(px - x) > hw then return false end if math.abs(py - y) > hh then return false end return true end
function pointInTriangle(px, py, x1, y1, x2, y2, x3, y3) local ax, ay = x1 - px, y1 - py local bx, by = x2 - px, y2 - py local cx, cy = x3 - px, y3 - py local sab = ax*by - ay*bx < 0 if sab ~= (bx*cy - by*cx < 0) then return false end return sab == (cx*ay - cy*ax < 0) end
function segmentVsSegment(x1, y1, x2, y2, x3, y3, x4, y4) local dx1, dy1 = x2 - x1, y2 - y1 local dx2, dy2 = x4 - x3, y4 - y3 local dx3, dy3 = x1 - x3, y1 - y3 local d = dx1*dy2 - dy1*dx2 if d == 0 then return false end local t1 = (dx2*dy3 - dy2*dx3)/d if t1 < 0 or t1 > 1 then return false end local t2 = (dx1*dy3 - dy1*dx3)/d if t2 < 0 or t2 > 1 then return false end -- point of intersection return true, x1 + t1*dx1, y1 + t1*dy1 end
local function segmentVsCircle(x1, y1, x2, y2, cx, cy, cr) local qx, qy = pointOnSegment(cx, cy, x1, y1, x2, y2) return pointInCircle(qx, qy, cx, cy, cr) endExpanded version that finds the points of intersection:
function segmentVsCircle(x1, y1, x2, y2, cx, cy, cr) -- normalize segment local dx, dy = x2 - x1, y2 - y1 local d = math.sqrt(dx*dx + dy*dy) if d == 0 then return false end local nx, ny = dx/d, dy/d local mx, my = x1 - cx, y1 - cy local b = mx*nx + my*ny local c = mx*mx + my*my - cr*cr if c > 0 and b > 0 then return false end local discr = b*b - c if discr < 0 then return false end discr = math.sqrt(discr) local tmin = math.max(-b - discr, 0) if tmin > d then return false end local tmax = math.min(discr - b, d) -- points of intersection -- first point local qx, qy = x1 + tmin*nx, y1 + tmin*ny if tmax == tmin then return true, qx, qy end -- second point return true, qx, qy, x1 + tmax*nx, y1 + tmax*ny end
function segmentVsAABB(x1, y1, x2, y2, l, t, r, b) -- normalize segment local dx, dy = x2 - x1, y2 - y1 local d = math.sqrt(dx*dx + dy*dy) if d == 0 then return false end local nx, ny = dx/d, dy/d -- minimum and maximum intersection values local tmin, tmax = 0, d -- x-axis check if nx == 0 then if x1 < l or x1 > r then return false end else local t1, t2 = (l - x1)/nx, (r - x1)/nx if t1 > t2 then t1, t2 = t2, t1 end tmin = math.max(tmin, t1) tmax = math.min(tmax, t2) if tmin > tmax then return false end end -- y-axis check if ny == 0 then if y1 < t or y1 > b then return false end else local t1, t2 = (t - y1)/ny, (b - y1)/ny if t1 > t2 then t1, t2 = t2, t1 end tmin = math.max(tmin, t1) tmax = math.min(tmax, t2) if tmin > tmax then return false end end -- points of intersection -- one point local qx, qy = x1 + nx*tmin, y1 + ny*tmin if tmin == tmax then return true, qx, qy end -- two points return true, qx, qy, x1 + nx*tmax, y1 + ny*tmax end
function circleVsCircle(cx, cy, r, cx2, cy2, r2) return pointInCircle(cx, cy, cx2, cy2, r + r2) endExpanded version:
function circleVsCircle(cx, cy, r, cx2, cy2, r2) local sradii = r + r2 local dx, dy = px - cx, py - cy return dx*dx + dy*dy <= sradii*sradii end
function circleVsBox(cx, cy, cr, x, y, hw, hh) local qx, qy = pointOnBox(cx, cy, x, y, hw, hh) return pointInCircle(qx, qy, cx, cy, cr) endExpanded version:
function circleVsBox(cx, cy, cr, x, y, hw, hh) local dx = math.abs(x - cx) local dy = math.abs(y - cy) dx = math.max(dx - hw, 0) dy = math.max(dy - hh, 0) return dx*dx + dy*dy <= cr*cr end
function triangleVsCircle(ax, ay, bx, by, cx, cy, sx, sy, r) local qx, qy = pointOnTriangle(sx, sy, ax, ay, bx, by, cx, cy) return pointInCircle(qx, qy, sx, sy, r) end
function boxVsBox(x, y, hw, hh, x2, y2, hw2, hh2) if math.abs(x - x2) > hw + hw2 then return false end if math.abs(y - y2) > hh + hh2 then return false end return true end
local function cross2(x1,y1,x2,y2,x3,y3, ax,ay,bx,by,cx,cy) { local dXa, dYa = ax - x3, ay - y3 local dXb, dYb = bx - x3, by - y3 local dXc, dYc = cx - x3, cy - y3 local x32, y23 = x3 - x2, y2 - y3 local x13, y13 = x1 - x3, y1 - y3 local D = y23*x13 + x32*y13 local sa = y23*dXa + x32*dYa local sb = y23*dXb + x32*dYb local sc = y23*dXc + x32*dYc if D < 0 then if sa >= 0 and sb >= 0 and sc >= 0 then return true end local ta = x13*dYa - y13*dXa local tb = x13*dYb - y13*dXb local tc = x13*dYc - y13*dXc return (ta >= 0 and tb >= 0 and tc >= 0) or (sa+ta <= D and sb+tb <= D and sc+tc <= D)) else if sa <= 0 and sb <= 0 and sc <= 0 then return true end local ta = x13*dYa - y13*dXa local tb = x13*dYb - y13*dXb local tc = x13*dYc - y13*dXc return (ta <= 0 and tb <= 0 and tc <= 0) or (sa+ta >= D and sb+tb >= D and sc+tc >= D) end end function triangleVsTriangle(x1,y1,x2,y2,x3,y3, ax,ay,bx,by,cx,cy) return not (cross2(x1,y1,x2,y2,x3,y3, ax,ay,bx,by,cx,cy) or cross2(ax,ay,bx,by,cx,cy, x1,y1,x2,y2,x3,y3)) end