octree.lua 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635
  1. -- https://github.com/Nition/UnityOctree
  2. -- https://github.com/Nition/UnityOctree/blob/master/LICENCE
  3. -- https://github.com/Nition/UnityOctree/blob/master/Scripts/BoundsOctree.cs
  4. -- https://github.com/Nition/UnityOctree/blob/master/Scripts/BoundsOctreeNode.cs
  5. --- Octree
  6. -- @module octree
  7. local modules = (...):gsub('%.[^%.]+$', '') .. "."
  8. local intersect = require(modules .. "intersect")
  9. local mat4 = require(modules .. "mat4")
  10. local utils = require(modules .. "utils")
  11. local vec3 = require(modules .. "vec3")
  12. local Octree = {}
  13. local OctreeNode = {}
  14. local Node
  15. Octree.__index = Octree
  16. OctreeNode.__index = OctreeNode
  17. --== Octree ==--
  18. --- Constructor for the bounds octree.
  19. -- @param initialWorldSize Size of the sides of the initial node, in metres. The octree will never shrink smaller than this
  20. -- @param initialWorldPos Position of the centre of the initial node
  21. -- @param minNodeSize Nodes will stop splitting if the new nodes would be smaller than this (metres)
  22. -- @param looseness Clamped between 1 and 2. Values > 1 let nodes overlap
  23. local function new(initialWorldSize, initialWorldPos, minNodeSize, looseness)
  24. local tree = setmetatable({}, Octree)
  25. if minNodeSize > initialWorldSize then
  26. print("Minimum node size must be at least as big as the initial world size. Was: " .. minNodeSize .. " Adjusted to: " .. initialWorldSize)
  27. minNodeSize = initialWorldSize
  28. end
  29. -- The total amount of objects currently in the tree
  30. tree.count = 0
  31. -- Size that the octree was on creation
  32. tree.initialSize = initialWorldSize
  33. -- Minimum side length that a node can be - essentially an alternative to having a max depth
  34. tree.minSize = minNodeSize
  35. -- Should be a value between 1 and 2. A multiplier for the base size of a node.
  36. -- 1.0 is a "normal" octree, while values > 1 have overlap
  37. tree.looseness = utils.clamp(looseness, 1, 2)
  38. -- Root node of the octree
  39. tree.rootNode = Node(tree.initialSize, tree.minSize, tree.looseness, initialWorldPos)
  40. return tree
  41. end
  42. --- Used when growing the octree. Works out where the old root node would fit inside a new, larger root node.
  43. -- @param xDir X direction of growth. 1 or -1
  44. -- @param yDir Y direction of growth. 1 or -1
  45. -- @param zDir Z direction of growth. 1 or -1
  46. -- @return Octant where the root node should be
  47. local function get_root_pos_index(xDir, yDir, zDir)
  48. local result = xDir > 0 and 1 or 0
  49. if yDir < 0 then return result + 4 end
  50. if zDir > 0 then return result + 2 end
  51. end
  52. --- Add an object.
  53. -- @param obj Object to add
  54. -- @param objBounds 3D bounding box around the object
  55. function Octree:add(obj, objBounds)
  56. -- Add object or expand the octree until it can be added
  57. local count = 0 -- Safety check against infinite/excessive growth
  58. while not self.rootNode:add(obj, objBounds) do
  59. count = count + 1
  60. self:grow(objBounds.center - self.rootNode.center)
  61. if count > 20 then
  62. print("Aborted Add operation as it seemed to be going on forever (" .. count - 1 .. ") attempts at growing the octree.")
  63. return
  64. end
  65. self.count = self.count + 1
  66. end
  67. end
  68. --- Remove an object. Makes the assumption that the object only exists once in the tree.
  69. -- @param obj Object to remove
  70. -- @return bool True if the object was removed successfully
  71. function Octree:remove(obj)
  72. local removed = self.rootNode:remove(obj)
  73. -- See if we can shrink the octree down now that we've removed the item
  74. if removed then
  75. self.count = self.count - 1
  76. self:shrink()
  77. end
  78. return removed
  79. end
  80. --- Check if the specified bounds intersect with anything in the tree. See also: get_colliding.
  81. -- @param checkBounds bounds to check
  82. -- @return bool True if there was a collision
  83. function Octree:is_colliding(checkBounds)
  84. return self.rootNode:is_colliding(checkBounds)
  85. end
  86. --- Returns an array of objects that intersect with the specified bounds, if any. Otherwise returns an empty array. See also: is_colliding.
  87. -- @param checkBounds bounds to check
  88. -- @return table Objects that intersect with the specified bounds
  89. function Octree:get_colliding(checkBounds)
  90. return self.rootNode:get_colliding(checkBounds)
  91. end
  92. --- Cast a ray through the node and its children
  93. -- @param ray Ray with a position and a direction
  94. -- @param func Function to execute on any objects within child nodes
  95. -- @param out Table to store results of func in
  96. -- @return boolean True if an intersect detected
  97. function Octree:cast_ray(ray, func, out)
  98. assert(func)
  99. return self.rootNode:cast_ray(ray, func, out)
  100. end
  101. --- Draws node boundaries visually for debugging.
  102. function Octree:draw_bounds(cube)
  103. self.rootNode:draw_bounds(cube)
  104. end
  105. --- Draws the bounds of all objects in the tree visually for debugging.
  106. function Octree:draw_objects(cube, filter)
  107. self.rootNode:draw_objects(cube, filter)
  108. end
  109. --- Grow the octree to fit in all objects.
  110. -- @param direction Direction to grow
  111. function Octree:grow(direction)
  112. local xDirection = direction.x >= 0 and 1 or -1
  113. local yDirection = direction.y >= 0 and 1 or -1
  114. local zDirection = direction.z >= 0 and 1 or -1
  115. local oldRoot = self.rootNode
  116. local half = self.rootNode.baseLength / 2
  117. local newLength = self.rootNode.baseLength * 2
  118. local newCenter = self.rootNode.center + vec3(xDirection * half, yDirection * half, zDirection * half)
  119. -- Create a new, bigger octree root node
  120. self.rootNode = Node(newLength, self.minSize, self.looseness, newCenter)
  121. -- Create 7 new octree children to go with the old root as children of the new root
  122. local rootPos = get_root_pos_index(xDirection, yDirection, zDirection)
  123. local children = {}
  124. for i = 0, 7 do
  125. if i == rootPos then
  126. children[i+1] = oldRoot
  127. else
  128. xDirection = i % 2 == 0 and -1 or 1
  129. yDirection = i > 3 and -1 or 1
  130. zDirection = (i < 2 or (i > 3 and i < 6)) and -1 or 1
  131. children[i+1] = Node(self.rootNode.baseLength, self.minSize, self.looseness, newCenter + vec3(xDirection * half, yDirection * half, zDirection * half))
  132. end
  133. end
  134. -- Attach the new children to the new root node
  135. self.rootNode:set_children(children)
  136. end
  137. --- Shrink the octree if possible, else leave it the same.
  138. function Octree:shrink()
  139. self.rootNode = self.rootNode:shrink_if_possible(self.initialSize)
  140. end
  141. --== Octree Node ==--
  142. --- Constructor.
  143. -- @param baseLength Length of this node, not taking looseness into account
  144. -- @param minSize Minimum size of nodes in this octree
  145. -- @param looseness Multiplier for baseLengthVal to get the actual size
  146. -- @param center Centre position of this node
  147. local function new_node(baseLength, minSize, looseness, center)
  148. local node = setmetatable({}, OctreeNode)
  149. -- Objects in this node
  150. node.objects = {}
  151. -- Child nodes
  152. node.children = {}
  153. -- If there are already numObjectsAllowed in a node, we split it into children
  154. -- A generally good number seems to be something around 8-15
  155. node.numObjectsAllowed = 8
  156. node:set_values(baseLength, minSize, looseness, center)
  157. return node
  158. end
  159. local function new_bound(center, size)
  160. return {
  161. center = center,
  162. size = size,
  163. min = center - (size / 2),
  164. max = center + (size / 2)
  165. }
  166. end
  167. --- Add an object.
  168. -- @param obj Object to add
  169. -- @param objBounds 3D bounding box around the object
  170. -- @return boolean True if the object fits entirely within this node
  171. function OctreeNode:add(obj, objBounds)
  172. if not intersect.encapsulate_aabb(self.bounds, objBounds) then
  173. return false
  174. end
  175. -- We know it fits at this level if we've got this far
  176. -- Just add if few objects are here, or children would be below min size
  177. if #self.objects < self.numObjectsAllowed
  178. or self.baseLength / 2 < self.minSize then
  179. table.insert(self.objects, {
  180. data = obj,
  181. bounds = objBounds
  182. })
  183. else
  184. -- Fits at this level, but we can go deeper. Would it fit there?
  185. local best_fit_child
  186. -- Create the 8 children
  187. if #self.children == 0 then
  188. self:split()
  189. if #self.children == 0 then
  190. print("Child creation failed for an unknown reason. Early exit.")
  191. return false
  192. end
  193. -- Now that we have the new children, see if this node's existing objects would fit there
  194. for i = #self.objects, 1, -1 do
  195. local object = self.objects[i]
  196. -- Find which child the object is closest to based on where the
  197. -- object's center is located in relation to the octree's center.
  198. best_fit_child = self:best_fit_child(object.bounds)
  199. -- Does it fit?
  200. if intersect.encapsulate_aabb(self.children[best_fit_child].bounds, object.bounds) then
  201. self.children[best_fit_child]:add(object.data, object.bounds) -- Go a level deeper
  202. table.remove(self.objects, i) -- Remove from here
  203. end
  204. end
  205. end
  206. -- Now handle the new object we're adding now
  207. best_fit_child = self:best_fit_child(objBounds)
  208. if intersect.encapsulate_aabb(self.children[best_fit_child].bounds, objBounds) then
  209. self.children[best_fit_child]:add(obj, objBounds)
  210. else
  211. table.insert(self.objects, {
  212. data = obj,
  213. bounds = objBounds
  214. })
  215. end
  216. end
  217. return true
  218. end
  219. --- Remove an object. Makes the assumption that the object only exists once in the tree.
  220. -- @param obj Object to remove
  221. -- @return boolean True if the object was removed successfully
  222. function OctreeNode:remove(obj)
  223. local removed = false
  224. for i, object in ipairs(self.objects) do
  225. if object == obj then
  226. removed = table.remove(self.objects, i) and true or false
  227. break
  228. end
  229. end
  230. if not removed then
  231. for _, child in ipairs(self.children) do
  232. removed = child:remove(obj)
  233. if removed then break end
  234. end
  235. end
  236. if removed then
  237. -- Check if we should merge nodes now that we've removed an item
  238. if self:should_merge() then
  239. self:merge()
  240. end
  241. end
  242. return removed
  243. end
  244. --- Check if the specified bounds intersect with anything in the tree. See also: get_colliding.
  245. -- @param checkBounds Bounds to check
  246. -- @return boolean True if there was a collision
  247. function OctreeNode:is_colliding(checkBounds)
  248. -- Are the input bounds at least partially in this node?
  249. if not intersect.aabb_aabb(self.bounds, checkBounds) then
  250. return false
  251. end
  252. -- Check against any objects in this node
  253. for _, object in ipairs(self.objects) do
  254. if intersect.aabb_aabb(object.bounds, checkBounds) then
  255. return true
  256. end
  257. end
  258. -- Check children
  259. for _, child in ipairs(self.children) do
  260. if child:is_colliding(checkBounds) then
  261. return true
  262. end
  263. end
  264. return false
  265. end
  266. --- Returns an array of objects that intersect with the specified bounds, if any. Otherwise returns an empty array. See also: is_colliding.
  267. -- @param checkBounds Bounds to check. Passing by ref as it improve performance with structs
  268. -- @param results List results
  269. -- @return table Objects that intersect with the specified bounds
  270. function OctreeNode:get_colliding(checkBounds, results)
  271. results = results or {}
  272. -- Are the input bounds at least partially in this node?
  273. if not intersect.aabb_aabb(self.bounds, checkBounds) then
  274. return results
  275. end
  276. -- Check against any objects in this node
  277. for _, object in ipairs(self.objects) do
  278. if intersect.aabb_aabb(object.bounds, checkBounds) then
  279. table.insert(results, object.data)
  280. end
  281. end
  282. -- Check children
  283. for _, child in ipairs(self.children) do
  284. results = child:get_colliding(checkBounds, results)
  285. end
  286. return results
  287. end
  288. --- Cast a ray through the node and its children
  289. -- @param ray Ray with a position and a direction
  290. -- @param func Function to execute on any objects within child nodes
  291. -- @param out Table to store results of func in
  292. -- @param depth (used internally)
  293. -- @return boolean True if an intersect is detected
  294. function OctreeNode:cast_ray(ray, func, out, depth)
  295. depth = depth or 1
  296. if intersect.ray_aabb(ray, self.bounds) then
  297. if #self.objects > 0 then
  298. local hit = func(ray, self.objects, out)
  299. if hit then
  300. return hit
  301. end
  302. end
  303. for _, child in ipairs(self.children) do
  304. local hit = child:cast_ray(ray, func, out, depth + 1)
  305. if hit then
  306. return hit
  307. end
  308. end
  309. end
  310. return false
  311. end
  312. --- Set the 8 children of this octree.
  313. -- @param childOctrees The 8 new child nodes
  314. function OctreeNode:set_children(childOctrees)
  315. if #childOctrees ~= 8 then
  316. print("Child octree array must be length 8. Was length: " .. #childOctrees)
  317. return
  318. end
  319. self.children = childOctrees
  320. end
  321. --- We can shrink the octree if:
  322. --- - This node is >= double minLength in length
  323. --- - All objects in the root node are within one octant
  324. --- - This node doesn't have children, or does but 7/8 children are empty
  325. --- We can also shrink it if there are no objects left at all!
  326. -- @param minLength Minimum dimensions of a node in this octree
  327. -- @return table The new root, or the existing one if we didn't shrink
  328. function OctreeNode:shrink_if_possible(minLength)
  329. if self.baseLength < 2 * minLength then
  330. return self
  331. end
  332. if #self.objects == 0 and #self.children == 0 then
  333. return self
  334. end
  335. -- Check objects in root
  336. local bestFit = 0
  337. for i, object in ipairs(self.objects) do
  338. local newBestFit = self:best_fit_child(object.bounds)
  339. if i == 1 or newBestFit == bestFit then
  340. -- In same octant as the other(s). Does it fit completely inside that octant?
  341. if intersect.encapsulate_aabb(self.childBounds[newBestFit], object.bounds) then
  342. if bestFit < 1 then
  343. bestFit = newBestFit
  344. end
  345. else
  346. -- Nope, so we can't reduce. Otherwise we continue
  347. return self
  348. end
  349. else
  350. return self -- Can't reduce - objects fit in different octants
  351. end
  352. end
  353. -- Check objects in children if there are any
  354. if #self.children > 0 then
  355. local childHadContent = false
  356. for i, child in ipairs(self.children) do
  357. if child:has_any_objects() then
  358. if childHadContent then
  359. return self -- Can't shrink - another child had content already
  360. end
  361. if bestFit > 0 and bestFit ~= i then
  362. return self -- Can't reduce - objects in root are in a different octant to objects in child
  363. end
  364. childHadContent = true
  365. bestFit = i
  366. end
  367. end
  368. end
  369. -- Can reduce
  370. if #self.children == 0 then
  371. -- We don't have any children, so just shrink this node to the new size
  372. -- We already know that everything will still fit in it
  373. self:set_values(self.baseLength / 2, self.minSize, self.looseness, self.childBounds[bestFit].center)
  374. return self
  375. end
  376. -- We have children. Use the appropriate child as the new root node
  377. return self.children[bestFit]
  378. end
  379. --- Set values for this node.
  380. -- @param baseLength Length of this node, not taking looseness into account
  381. -- @param minSize Minimum size of nodes in this octree
  382. -- @param looseness Multiplier for baseLengthVal to get the actual size
  383. -- @param center Centre position of this node
  384. function OctreeNode:set_values(baseLength, minSize, looseness, center)
  385. -- Length of this node if it has a looseness of 1.0
  386. self.baseLength = baseLength
  387. -- Minimum size for a node in this octree
  388. self.minSize = minSize
  389. -- Looseness value for this node
  390. self.looseness = looseness
  391. -- Centre of this node
  392. self.center = center
  393. -- Actual length of sides, taking the looseness value into account
  394. self.adjLength = self.looseness * self.baseLength
  395. -- Create the bounding box.
  396. self.size = vec3(self.adjLength, self.adjLength, self.adjLength)
  397. -- Bounding box that represents this node
  398. self.bounds = new_bound(self.center, self.size)
  399. self.quarter = self.baseLength / 4
  400. self.childActualLength = (self.baseLength / 2) * self.looseness
  401. self.childActualSize = vec3(self.childActualLength, self.childActualLength, self.childActualLength)
  402. -- Bounds of potential children to this node. These are actual size (with looseness taken into account), not base size
  403. self.childBounds = {
  404. new_bound(self.center + vec3(-self.quarter, self.quarter, -self.quarter), self.childActualSize),
  405. new_bound(self.center + vec3( self.quarter, self.quarter, -self.quarter), self.childActualSize),
  406. new_bound(self.center + vec3(-self.quarter, self.quarter, self.quarter), self.childActualSize),
  407. new_bound(self.center + vec3( self.quarter, self.quarter, self.quarter), self.childActualSize),
  408. new_bound(self.center + vec3(-self.quarter, -self.quarter, -self.quarter), self.childActualSize),
  409. new_bound(self.center + vec3( self.quarter, -self.quarter, -self.quarter), self.childActualSize),
  410. new_bound(self.center + vec3(-self.quarter, -self.quarter, self.quarter), self.childActualSize),
  411. new_bound(self.center + vec3( self.quarter, -self.quarter, self.quarter), self.childActualSize)
  412. }
  413. end
  414. --- Splits the octree into eight children.
  415. function OctreeNode:split()
  416. if #self.children > 0 then return end
  417. local quarter = self.baseLength / 4
  418. local newLength = self.baseLength / 2
  419. table.insert(self.children, Node(newLength, self.minSize, self.looseness, self.center + vec3(-quarter, quarter, -quarter)))
  420. table.insert(self.children, Node(newLength, self.minSize, self.looseness, self.center + vec3( quarter, quarter, -quarter)))
  421. table.insert(self.children, Node(newLength, self.minSize, self.looseness, self.center + vec3(-quarter, quarter, quarter)))
  422. table.insert(self.children, Node(newLength, self.minSize, self.looseness, self.center + vec3( quarter, quarter, quarter)))
  423. table.insert(self.children, Node(newLength, self.minSize, self.looseness, self.center + vec3(-quarter, -quarter, -quarter)))
  424. table.insert(self.children, Node(newLength, self.minSize, self.looseness, self.center + vec3( quarter, -quarter, -quarter)))
  425. table.insert(self.children, Node(newLength, self.minSize, self.looseness, self.center + vec3(-quarter, -quarter, quarter)))
  426. table.insert(self.children, Node(newLength, self.minSize, self.looseness, self.center + vec3( quarter, -quarter, quarter)))
  427. end
  428. --- Merge all children into this node - the opposite of Split.
  429. --- Note: We only have to check one level down since a merge will never happen if the children already have children,
  430. --- since THAT won't happen unless there are already too many objects to merge.
  431. function OctreeNode:merge()
  432. for _, child in ipairs(self.children) do
  433. for _, object in ipairs(child.objects) do
  434. table.insert(self.objects, object)
  435. end
  436. end
  437. -- Remove the child nodes (and the objects in them - they've been added elsewhere now)
  438. self.children = {}
  439. end
  440. --- Find which child node this object would be most likely to fit in.
  441. -- @param objBounds The object's bounds
  442. -- @return number One of the eight child octants
  443. function OctreeNode:best_fit_child(objBounds)
  444. return (objBounds.center.x <= self.center.x and 0 or 1) + (objBounds.center.y >= self.center.y and 0 or 4) + (objBounds.center.z <= self.center.z and 0 or 2) + 1
  445. end
  446. --- Checks if there are few enough objects in this node and its children that the children should all be merged into this.
  447. -- @return boolean True there are less or the same abount of objects in this and its children than numObjectsAllowed
  448. function OctreeNode:should_merge()
  449. local totalObjects = #self.objects
  450. for _, child in ipairs(self.children) do
  451. if #child.children > 0 then
  452. -- If any of the *children* have children, there are definitely too many to merge,
  453. -- or the child would have been merged already
  454. return false
  455. end
  456. totalObjects = totalObjects + #child.objects
  457. end
  458. return totalObjects <= self.numObjectsAllowed
  459. end
  460. --- Checks if this node or anything below it has something in it.
  461. -- @return boolean True if this node or any of its children, grandchildren etc have something in the
  462. function OctreeNode:has_any_objects()
  463. if #self.objects > 0 then return true end
  464. for _, child in ipairs(self.children) do
  465. if child:has_any_objects() then return true end
  466. end
  467. return false
  468. end
  469. --- Draws node boundaries visually for debugging.
  470. -- @param cube Cube model to draw
  471. -- @param depth Used for recurcive calls to this method
  472. function OctreeNode:draw_bounds(cube, depth)
  473. depth = depth or 0
  474. local tint = depth / 7 -- Will eventually get values > 1. Color rounds to 1 automatically
  475. love.graphics.setColor(tint * 255, 0, (1 - tint) * 255)
  476. local m = mat4()
  477. :translate(self.center)
  478. :scale(vec3(self.adjLength, self.adjLength, self.adjLength))
  479. love.graphics.updateMatrix("transform", m)
  480. love.graphics.setWireframe(true)
  481. love.graphics.draw(cube)
  482. love.graphics.setWireframe(false)
  483. for _, child in ipairs(self.children) do
  484. child:draw_bounds(cube, depth + 1)
  485. end
  486. love.graphics.setColor(255, 255, 255)
  487. end
  488. --- Draws the bounds of all objects in the tree visually for debugging.
  489. -- @param cube Cube model to draw
  490. -- @param filter a function returning true or false to determine visibility.
  491. function OctreeNode:draw_objects(cube, filter)
  492. local tint = self.baseLength / 20
  493. love.graphics.setColor(0, (1 - tint) * 255, tint * 255, 63)
  494. for _, object in ipairs(self.objects) do
  495. if filter and filter(object.data) or not filter then
  496. local m = mat4()
  497. :translate(object.bounds.center)
  498. :scale(object.bounds.size)
  499. love.graphics.updateMatrix("transform", m)
  500. love.graphics.draw(cube)
  501. end
  502. end
  503. for _, child in ipairs(self.children) do
  504. child:draw_objects(cube, filter)
  505. end
  506. love.graphics.setColor(255, 255, 255)
  507. end
  508. Node = setmetatable({
  509. new = new_node
  510. }, {
  511. __call = function(_, ...) return new_node(...) end
  512. })
  513. return setmetatable({
  514. new = new
  515. }, {
  516. __call = function(_, ...) return new(...) end
  517. })