Axis-aligned bounding box classes for 2 and 3 dimensions

This commit is contained in:
mcc 2018-01-26 10:30:03 -05:00
parent b5beacffc7
commit c9cacf5d58
2 changed files with 324 additions and 0 deletions

162
modules/bound2.lua Normal file
View File

@ -0,0 +1,162 @@
--- A 2 component bounding box.
-- @module bound2
local modules = (...):gsub('%.[^%.]+$', '') .. "."
local vec2 = require(modules .. "vec2")
local bound2 = {}
local bound2_mt = {}
-- Private constructor.
local function new(x, y)
return setmetatable({
-- min: vec2, minimum value for each component
-- max: vec2, maximum value for each component
}, bound2_mt)
end
-- Do the check to see if JIT is enabled. If so use the optimized FFI structs.
local status, ffi
if type(jit) == "table" and jit.status() then
status, ffi = pcall(require, "ffi")
if status then
ffi.cdef "typedef struct { cpml_vec2 min, max; } cpml_bound2;"
new = ffi.typeof("cpml_bound2")
end
end
vec2.zero = new(vec2.zero, vec2.zero)
--- The public constructor.
-- @param min Can be of two types: </br>
-- vec2 min, minimum value for each component
-- nil Create bound at single point 0,0
-- @tparam vec2 max, maximum value for each component
-- @treturn bound2 out
function bound2.new(min, max)
if min and max then
return new(min:clone(), max:clone())
elseif min or max then
error("Unexpected nil argument to bound2.new")
else
return new(vec2.zero, vec2.zero)
end
end
--- Clone a bound.
-- @tparam bound2 a bound to be cloned
-- @treturn bound2 out
function bound2.clone(a)
return new(a.min, a.max)
end
--- Construct a bound covering one or two points
-- @tparam vec2 a Any vector
-- @tparam vec2 b Any second vector (optional)
-- @treturn vec2 Minimum bound containing the given points
function bound2.at(a, b) -- "bounded by". b may be nil
if b then
return bound2.new(a,b):check()
else
return bound2.zero:with_center(a)
end
end
--- Get size of bounding box as a vector
-- @tparam bound2 a bound
-- @treturn vec2 Vector spanning min to max points
function bound2.size(a)
return a.max - a.min
end
--- Resize bounding box from minimum corner
-- @tparam bound2 a bound
-- @tparam vec2 new size
-- @treturn bound2 resized bound
function bound2.with_size(a, size)
return vec2.new(a.min, a.min + size)
end
--- Get half-size of bounding box as a vector. A more correct term for this is probably "apothem"
-- @tparam bound2 a bound
-- @treturn vec2 Vector spanning center to max point
function bound2.radius(a)
return a:size()/2
end
--- Get center of bounding box
-- @tparam bound2 a bound
-- @treturn bound2 Point in center of bound
function bound2.center(a)
return (a.min + a.max)/2
end
--- Move bounding box to new center
-- @tparam bound2 a bound
-- @tparam vec2 new center
-- @treturn bound2 Bound with same size as input but different center
function bound2.with_center(a, center)
return bound2.offset(a, a:center() - center)
end
--- Resize bounding box from center
-- @tparam bound2 a bound
-- @tparam vec2 new size
-- @treturn bound2 resized bound
function bound2.with_size_centered(a, size)
local center = a:center()
local rad = size/2
return bound2.new(center - rad, center + rad)
end
--- Convert possibly-invalid bounding box to valid one
-- @tparam bound2 a bound
-- @treturn bound2 bound with all components corrected for min-max property
function bound2.check(a)
if a.min.x > a.max.x or a.min.y > a.max.y then
return bound2.new(vec2.component_min(a.min, a.max), vec2.component_max(a.min, a.max))
end
return a
end
--- Shrink bounding box with fixed margin
-- @tparam bound2 a bound
-- @tparam vec2 a margin
-- @treturn bound2 bound with margin subtracted from all edges. May not be valid, consider calling check()
function bound2.inset(a, v)
return bound2.new(a.min + v, a.max - v)
end
--- Expand bounding box with fixed margin
-- @tparam bound2 a bound
-- @tparam vec2 a margin
-- @treturn bound2 bound with margin added to all edges. May not be valid, consider calling check()
function bound2.outset(a, v)
return bound2.new(a.min - v, a.max + v)
end
--- Offset bounding box
-- @tparam bound2 a bound
-- @tparam vec2 offset
-- @treturn bound2 bound with same size, but position moved by offset
function bound2.offset(a, v)
return bound2.new(a.min + v, a.max + v)
end
--- Test if point in bound
-- @tparam bound2 a bound
-- @tparam vec2 point to test
-- @treturn boolean true if point in bounding box
function bound2.contains(a, v)
return a.min.x <= v.x and a.min.y <= v.y and a.min.z <= v.z
and a.max.x >= v.x and a.max.y >= v.y and a.max.z >= v.z
end
bound2_mt.__index = bound2
bound2_mt.__tostring = bound2.to_string
if status then
ffi.metatype(new, bound2_mt)
end
return setmetatable({}, bound2_mt)

162
modules/bound3.lua Normal file
View File

@ -0,0 +1,162 @@
--- A 3-component axis-aligned bounding box.
-- @module bound3
local modules = (...):gsub('%.[^%.]+$', '') .. "."
local vec3 = require(modules .. "vec3")
local bound3 = {}
local bound3_mt = {}
-- Private constructor.
local function new(min, max)
return setmetatable({
min=min, -- min: vec3, minimum value for each component
max=max -- max: vec3, maximum value for each component
}, bound3_mt)
end
-- Do the check to see if JIT is enabled. If so use the optimized FFI structs.
local status, ffi
if type(jit) == "table" and jit.status() then
status, ffi = pcall(require, "ffi")
if status then
ffi.cdef "typedef struct { cpml_vec3 min, max; } cpml_bound3;"
new = ffi.typeof("cpml_bound3")
end
end
vec3.zero = new(vec3.zero, vec3.zero)
--- The public constructor.
-- @param min Can be of two types: </br>
-- vec3 min, minimum value for each component
-- nil Create bound at single point 0,0,0
-- @tparam vec3 max, maximum value for each component
-- @treturn bound3 out
function bound3.new(min, max)
if min and max then
return new(min:clone(), max:clone())
elseif min or max then
error("Unexpected nil argument to bound3.new")
else
return new(vec3.zero, vec3.zero)
end
end
--- Clone a bound.
-- @tparam bound3 a bound to be cloned
-- @treturn bound3 out
function bound3.clone(a)
return new(a.min, a.max)
end
--- Construct a bound covering one or two points
-- @tparam vec3 a Any vector
-- @tparam vec3 b Any second vector (optional)
-- @treturn vec3 Minimum bound containing the given points
function bound3.at(a, b) -- "bounded by". b may be nil
if b then
return bound3.new(a,b):check()
else
return bound3.zero:with_center(a)
end
end
--- Get size of bounding box as a vector
-- @tparam bound3 a bound
-- @treturn vec3 Vector spanning min to max points
function bound3.size(a)
return a.max - a.min
end
--- Resize bounding box from minimum corner
-- @tparam bound3 a bound
-- @tparam vec3 new size
-- @treturn bound3 resized bound
function bound3.with_size(a, size)
return vec3.new(a.min, a.min + size)
end
--- Get half-size of bounding box as a vector. A more correct term for this is probably "apothem"
-- @tparam bound3 a bound
-- @treturn vec3 Vector spanning center to max point
function bound3.radius(a)
return a:size()/2
end
--- Get center of bounding box
-- @tparam bound3 a bound
-- @treturn bound3 Point in center of bound
function bound3.center(a)
return (a.min + a.max)/2
end
--- Move bounding box to new center
-- @tparam bound3 a bound
-- @tparam vec3 new center
-- @treturn bound3 Bound with same size as input but different center
function bound3.with_center(a, center)
return bound3.offset(a, a:center() - center)
end
--- Resize bounding box from center
-- @tparam bound3 a bound
-- @tparam vec3 new size
-- @treturn bound3 resized bound
function bound3.with_size_centered(a, size)
local center = a:center()
local rad = size/2
return bound3.new(center - rad, center + rad)
end
--- Convert possibly-invalid bounding box to valid one
-- @tparam bound3 a bound
-- @treturn bound3 bound with all components corrected for min-max property
function bound3.check(a)
if a.min.x > a.max.x or a.min.y > a.max.y or a.min.z > a.max.z then
return bound3.new(vec3.component_min(a.min, a.max), vec3.component_max(a.min, a.max))
end
return a
end
--- Shrink bounding box with fixed margin
-- @tparam bound3 a bound
-- @tparam vec3 a margin
-- @treturn bound3 bound with margin subtracted from all edges. May not be valid, consider calling check()
function bound3.inset(a, v)
return bound3.new(a.min + v, a.max - v)
end
--- Expand bounding box with fixed margin
-- @tparam bound3 a bound
-- @tparam vec3 a margin
-- @treturn bound3 bound with margin added to all edges. May not be valid, consider calling check()
function bound3.outset(a, v)
return bound3.new(a.min - v, a.max + v)
end
--- Offset bounding box
-- @tparam bound3 a bound
-- @tparam vec3 offset
-- @treturn bound3 bound with same size, but position moved by offset
function bound3.offset(a, v)
return bound3.new(a.min + v, a.max + v)
end
--- Test if point in bound
-- @tparam bound3 a bound
-- @tparam vec3 point to test
-- @treturn boolean true if point in bounding box
function bound3.contains(a, v)
return a.min.x <= v.x and a.min.y <= v.y and a.min.z <= v.z
and a.max.x >= v.x and a.max.y >= v.y and a.max.z >= v.z
end
bound3_mt.__index = bound3
bound3_mt.__tostring = bound3.to_string
if status then
ffi.metatype(new, bound3_mt)
end
return setmetatable({}, bound3_mt)