bound3.lua 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. --- A 3-component axis-aligned bounding box.
  2. -- @module bound3
  3. local modules = (...):gsub('%.[^%.]+$', '') .. "."
  4. local vec3 = require(modules .. "vec3")
  5. local bound3 = {}
  6. local bound3_mt = {}
  7. -- Private constructor.
  8. local function new(min, max)
  9. return setmetatable({
  10. min=min, -- min: vec3, minimum value for each component
  11. max=max -- max: vec3, maximum value for each component
  12. }, bound3_mt)
  13. end
  14. -- Do the check to see if JIT is enabled. If so use the optimized FFI structs.
  15. local status, ffi
  16. if type(jit) == "table" and jit.status() then
  17. status, ffi = pcall(require, "ffi")
  18. if status then
  19. ffi.cdef "typedef struct { cpml_vec3 min, max; } cpml_bound3;"
  20. new = ffi.typeof("cpml_bound3")
  21. end
  22. end
  23. bound3.zero = new(vec3.zero, vec3.zero)
  24. --- The public constructor.
  25. -- @param min Can be of two types: </br>
  26. -- vec3 min, minimum value for each component
  27. -- nil Create bound at single point 0,0,0
  28. -- @tparam vec3 max, maximum value for each component
  29. -- @treturn bound3 out
  30. function bound3.new(min, max)
  31. if min and max then
  32. return new(min:clone(), max:clone())
  33. elseif min or max then
  34. error("Unexpected nil argument to bound3.new")
  35. else
  36. return new(vec3.zero, vec3.zero)
  37. end
  38. end
  39. --- Clone a bound.
  40. -- @tparam bound3 a bound to be cloned
  41. -- @treturn bound3 out
  42. function bound3.clone(a)
  43. return new(a.min, a.max)
  44. end
  45. --- Construct a bound covering one or two points
  46. -- @tparam vec3 a Any vector
  47. -- @tparam vec3 b Any second vector (optional)
  48. -- @treturn vec3 Minimum bound containing the given points
  49. function bound3.at(a, b) -- "bounded by". b may be nil
  50. if b then
  51. return bound3.new(a,b):check()
  52. else
  53. return bound3.zero:with_center(a)
  54. end
  55. end
  56. --- Get size of bounding box as a vector
  57. -- @tparam bound3 a bound
  58. -- @treturn vec3 Vector spanning min to max points
  59. function bound3.size(a)
  60. return a.max - a.min
  61. end
  62. --- Resize bounding box from minimum corner
  63. -- @tparam bound3 a bound
  64. -- @tparam vec3 new size
  65. -- @treturn bound3 resized bound
  66. function bound3.with_size(a, size)
  67. return bound3.new(a.min, a.min + size)
  68. end
  69. --- Get half-size of bounding box as a vector. A more correct term for this is probably "apothem"
  70. -- @tparam bound3 a bound
  71. -- @treturn vec3 Vector spanning center to max point
  72. function bound3.radius(a)
  73. return a:size()/2
  74. end
  75. --- Get center of bounding box
  76. -- @tparam bound3 a bound
  77. -- @treturn bound3 Point in center of bound
  78. function bound3.center(a)
  79. return (a.min + a.max)/2
  80. end
  81. --- Move bounding box to new center
  82. -- @tparam bound3 a bound
  83. -- @tparam vec3 new center
  84. -- @treturn bound3 Bound with same size as input but different center
  85. function bound3.with_center(a, center)
  86. return bound3.offset(a, center - a:center())
  87. end
  88. --- Resize bounding box from center
  89. -- @tparam bound3 a bound
  90. -- @tparam vec3 new size
  91. -- @treturn bound3 resized bound
  92. function bound3.with_size_centered(a, size)
  93. local center = a:center()
  94. local rad = size/2
  95. return bound3.new(center - rad, center + rad)
  96. end
  97. --- Convert possibly-invalid bounding box to valid one
  98. -- @tparam bound3 a bound
  99. -- @treturn bound3 bound with all components corrected for min-max property
  100. function bound3.check(a)
  101. if a.min.x > a.max.x or a.min.y > a.max.y or a.min.z > a.max.z then
  102. return bound3.new(vec3.component_min(a.min, a.max), vec3.component_max(a.min, a.max))
  103. end
  104. return a
  105. end
  106. --- Shrink bounding box with fixed margin
  107. -- @tparam bound3 a bound
  108. -- @tparam vec3 a margin
  109. -- @treturn bound3 bound with margin subtracted from all edges. May not be valid, consider calling check()
  110. function bound3.inset(a, v)
  111. return bound3.new(a.min + v, a.max - v)
  112. end
  113. --- Expand bounding box with fixed margin
  114. -- @tparam bound3 a bound
  115. -- @tparam vec3 a margin
  116. -- @treturn bound3 bound with margin added to all edges. May not be valid, consider calling check()
  117. function bound3.outset(a, v)
  118. return bound3.new(a.min - v, a.max + v)
  119. end
  120. --- Offset bounding box
  121. -- @tparam bound3 a bound
  122. -- @tparam vec3 offset
  123. -- @treturn bound3 bound with same size, but position moved by offset
  124. function bound3.offset(a, v)
  125. return bound3.new(a.min + v, a.max + v)
  126. end
  127. --- Test if point in bound
  128. -- @tparam bound3 a bound
  129. -- @tparam vec3 point to test
  130. -- @treturn boolean true if point in bounding box
  131. function bound3.contains(a, v)
  132. return a.min.x <= v.x and a.min.y <= v.y and a.min.z <= v.z
  133. and a.max.x >= v.x and a.max.y >= v.y and a.max.z >= v.z
  134. end
  135. bound3_mt.__index = bound3
  136. bound3_mt.__tostring = bound3.to_string
  137. function bound3_mt.__call(_, a, b)
  138. return bound3.new(a, b)
  139. end
  140. if status then
  141. ffi.metatype(new, bound3_mt)
  142. end
  143. return setmetatable({}, bound3_mt)