• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

excessive / cpml / 13079282556

07 May 2022 10:53PM UTC coverage: 57.717% (+43.7%) from 14.013%
13079282556

push

github

shakesoda
fix typo in mat4.mul

0 of 1 new or added line in 1 file covered. (0.0%)

527 existing lines in 19 files now uncovered.

4581 of 7937 relevant lines covered (57.72%)

52.86 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

16.1
/modules/octree.lua
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

6
--- Octree
7
-- @module octree
8

9
local modules = (...):gsub('%.[^%.]+$', '') .. "."
1✔
10
local intersect  = require(modules .. "intersect")
1✔
11
local mat4       = require(modules .. "mat4")
1✔
12
local utils      = require(modules .. "utils")
1✔
13
local vec3       = require(modules .. "vec3")
1✔
14
local Octree     = {}
1✔
15
local OctreeNode = {}
1✔
16
local Node
17

18
Octree.__index     = Octree
1✔
19
OctreeNode.__index = OctreeNode
1✔
20

21
--== Octree ==--
22

23
--- Constructor for the bounds octree.
24
-- @param initialWorldSize Size of the sides of the initial node, in metres. The octree will never shrink smaller than this
25
-- @param initialWorldPos Position of the centre of the initial node
26
-- @param minNodeSize Nodes will stop splitting if the new nodes would be smaller than this (metres)
27
-- @param looseness Clamped between 1 and 2. Values > 1 let nodes overlap
28
local function new(initialWorldSize, initialWorldPos, minNodeSize, looseness)
UNCOV
29
        local tree = setmetatable({}, Octree)
×
30

31
        if minNodeSize > initialWorldSize then
×
32
                print("Minimum node size must be at least as big as the initial world size. Was: " .. minNodeSize .. " Adjusted to: " .. initialWorldSize)
×
UNCOV
33
                minNodeSize = initialWorldSize
×
34
        end
35

36
        -- The total amount of objects currently in the tree
UNCOV
37
        tree.count = 0
×
38

39
        -- Size that the octree was on creation
UNCOV
40
        tree.initialSize = initialWorldSize
×
41

42
        -- Minimum side length that a node can be - essentially an alternative to having a max depth
UNCOV
43
        tree.minSize = minNodeSize
×
44

45
        -- Should be a value between 1 and 2. A multiplier for the base size of a node.
46
        -- 1.0 is a "normal" octree, while values > 1 have overlap
UNCOV
47
        tree.looseness = utils.clamp(looseness, 1, 2)
×
48

49
        -- Root node of the octree
UNCOV
50
        tree.rootNode = Node(tree.initialSize, tree.minSize, tree.looseness, initialWorldPos)
×
51

UNCOV
52
        return tree
×
53
end
54

55
--- Used when growing the octree. Works out where the old root node would fit inside a new, larger root node.
56
-- @param xDir X direction of growth. 1 or -1
57
-- @param yDir Y direction of growth. 1 or -1
58
-- @param zDir Z direction of growth. 1 or -1
59
-- @return Octant where the root node should be
60
local function get_root_pos_index(xDir, yDir, zDir)
61
        local result = xDir > 0 and 1 or 0
×
62
        if yDir < 0 then return result + 4 end
×
UNCOV
63
        if zDir > 0 then return result + 2 end
×
64
end
65

66
--- Add an object.
67
-- @param obj Object to add
68
-- @param objBounds 3D bounding box around the object
69
function Octree:add(obj, objBounds)
1✔
70
        -- Add object or expand the octree until it can be added
UNCOV
71
        local count = 0 -- Safety check against infinite/excessive growth
×
72

73
        while not self.rootNode:add(obj, objBounds) do
×
74
                count = count + 1
×
UNCOV
75
                self:grow(objBounds.center - self.rootNode.center)
×
76

77
                if count > 20 then
×
78
                        print("Aborted Add operation as it seemed to be going on forever (" .. count - 1 .. ") attempts at growing the octree.")
×
UNCOV
79
                        return
×
80
                end
81

UNCOV
82
                self.count = self.count + 1
×
83
        end
84
end
85

86
--- Remove an object. Makes the assumption that the object only exists once in the tree.
87
-- @param obj Object to remove
88
-- @return bool True if the object was removed successfully
89
function Octree:remove(obj)
1✔
UNCOV
90
        local removed = self.rootNode:remove(obj)
×
91

92
        -- See if we can shrink the octree down now that we've removed the item
93
        if removed then
×
94
                self.count = self.count - 1
×
UNCOV
95
                self:shrink()
×
96
        end
97

UNCOV
98
        return removed
×
99
end
100

101
--- Check if the specified bounds intersect with anything in the tree. See also: get_colliding.
102
-- @param checkBounds bounds to check
103
-- @return bool True if there was a collision
104
function Octree:is_colliding(checkBounds)
1✔
UNCOV
105
        return self.rootNode:is_colliding(checkBounds)
×
106
end
107

108
--- Returns an array of objects that intersect with the specified bounds, if any. Otherwise returns an empty array. See also: is_colliding.
109
-- @param checkBounds bounds to check
110
-- @return table Objects that intersect with the specified bounds
111
function Octree:get_colliding(checkBounds)
1✔
UNCOV
112
        return self.rootNode:get_colliding(checkBounds)
×
113
end
114

115
--- Cast a ray through the node and its children
116
-- @param ray Ray with a position and a direction
117
-- @param func Function to execute on any objects within child nodes
118
-- @param out Table to store results of func in
119
-- @return boolean True if an intersect detected
120
function Octree:cast_ray(ray, func, out)
1✔
121
        assert(func)
×
UNCOV
122
        return self.rootNode:cast_ray(ray, func, out)
×
123
end
124

125
--- Draws node boundaries visually for debugging.
126
function Octree:draw_bounds(cube)
1✔
UNCOV
127
        self.rootNode:draw_bounds(cube)
×
128
end
129

130
--- Draws the bounds of all objects in the tree visually for debugging.
131
function Octree:draw_objects(cube, filter)
1✔
UNCOV
132
        self.rootNode:draw_objects(cube, filter)
×
133
end
134

135
--- Grow the octree to fit in all objects.
136
-- @param direction Direction to grow
137
function Octree:grow(direction)
1✔
138
        local xDirection = direction.x >= 0 and 1 or -1
×
139
        local yDirection = direction.y >= 0 and 1 or -1
×
UNCOV
140
        local zDirection = direction.z >= 0 and 1 or -1
×
141

142
        local oldRoot   = self.rootNode
×
143
        local half      = self.rootNode.baseLength / 2
×
144
        local newLength = self.rootNode.baseLength * 2
×
UNCOV
145
        local newCenter = self.rootNode.center + vec3(xDirection * half, yDirection * half, zDirection * half)
×
146

147
        -- Create a new, bigger octree root node
UNCOV
148
        self.rootNode = Node(newLength, self.minSize, self.looseness, newCenter)
×
149

150
        -- Create 7 new octree children to go with the old root as children of the new root
151
        local rootPos  = get_root_pos_index(xDirection, yDirection, zDirection)
×
UNCOV
152
        local children = {}
×
153

154
        for i = 0, 7 do
×
155
                if i == rootPos then
×
UNCOV
156
                        children[i+1] = oldRoot
×
157
                else
158
                        xDirection  = i % 2 == 0 and -1 or 1
×
159
                        yDirection  = i > 3 and -1 or 1
×
160
                        zDirection  = (i < 2 or (i > 3 and i < 6)) and -1 or 1
×
UNCOV
161
                        children[i+1] = Node(self.rootNode.baseLength, self.minSize, self.looseness, newCenter + vec3(xDirection * half, yDirection * half, zDirection * half))
×
162
                end
163
        end
164

165
        -- Attach the new children to the new root node
UNCOV
166
        self.rootNode:set_children(children)
×
167
end
168

169
--- Shrink the octree if possible, else leave it the same.
170
function Octree:shrink()
1✔
UNCOV
171
        self.rootNode = self.rootNode:shrink_if_possible(self.initialSize)
×
172
end
173

174
--== Octree Node ==--
175

176
--- Constructor.
177
-- @param baseLength Length of this node, not taking looseness into account
178
-- @param minSize Minimum size of nodes in this octree
179
-- @param looseness Multiplier for baseLengthVal to get the actual size
180
-- @param center Centre position of this node
181
local function new_node(baseLength, minSize, looseness, center)
UNCOV
182
        local node = setmetatable({}, OctreeNode)
×
183

184
        -- Objects in this node
UNCOV
185
        node.objects = {}
×
186

187
        -- Child nodes
UNCOV
188
        node.children = {}
×
189

190
        -- If there are already numObjectsAllowed in a node, we split it into children
191
        -- A generally good number seems to be something around 8-15
UNCOV
192
        node.numObjectsAllowed = 8
×
193

UNCOV
194
        node:set_values(baseLength, minSize, looseness, center)
×
195

UNCOV
196
        return node
×
197
end
198

199
local function new_bound(center, size)
UNCOV
200
        return {
×
201
                center = center,
202
                size   = size,
203
                min    = center - (size / 2),
UNCOV
204
                max    = center + (size / 2)
×
205
        }
206
end
207

208
--- Add an object.
209
-- @param obj Object to add
210
-- @param objBounds 3D bounding box around the object
211
-- @return boolean True if the object fits entirely within this node
212
function OctreeNode:add(obj, objBounds)
1✔
213
        if not intersect.encapsulate_aabb(self.bounds, objBounds) then
×
UNCOV
214
                return false
×
215
        end
216

217
        -- We know it fits at this level if we've got this far
218
        -- Just add if few objects are here, or children would be below min size
219
        if #self.objects < self.numObjectsAllowed
×
220
        or self.baseLength / 2 < self.minSize then
×
UNCOV
221
                table.insert(self.objects, {
×
222
                        data   = obj,
UNCOV
223
                        bounds = objBounds
×
224
                })
225
        else
226
                -- Fits at this level, but we can go deeper. Would it fit there?
227

228
                local best_fit_child
229

230
                -- Create the 8 children
231
                if #self.children == 0 then
×
UNCOV
232
                        self:split()
×
233

234
                        if #self.children == 0 then
×
235
                                print("Child creation failed for an unknown reason. Early exit.")
×
UNCOV
236
                                return false
×
237
                        end
238

239
                        -- Now that we have the new children, see if this node's existing objects would fit there
240
                        for i = #self.objects, 1, -1 do
×
UNCOV
241
                                local object = self.objects[i]
×
242
                                -- Find which child the object is closest to based on where the
243
                                -- object's center is located in relation to the octree's center.
UNCOV
244
                                best_fit_child = self:best_fit_child(object.bounds)
×
245

246
                                -- Does it fit?
247
                                if intersect.encapsulate_aabb(self.children[best_fit_child].bounds, object.bounds) then
×
248
                                        self.children[best_fit_child]:add(object.data, object.bounds) -- Go a level deeper
×
UNCOV
249
                                        table.remove(self.objects, i) -- Remove from here
×
250
                                end
251
                        end
252
                end
253

254
                -- Now handle the new object we're adding now
UNCOV
255
                best_fit_child = self:best_fit_child(objBounds)
×
256

257
                if intersect.encapsulate_aabb(self.children[best_fit_child].bounds, objBounds) then
×
UNCOV
258
                        self.children[best_fit_child]:add(obj, objBounds)
×
259
                else
UNCOV
260
                        table.insert(self.objects, {
×
261
                                data   = obj,
UNCOV
262
                                bounds = objBounds
×
263
                        })
264
                end
265
        end
266

UNCOV
267
        return true
×
268
end
269

270
--- Remove an object. Makes the assumption that the object only exists once in the tree.
271
-- @param obj Object to remove
272
-- @return boolean True if the object was removed successfully
273
function OctreeNode:remove(obj)
1✔
UNCOV
274
        local removed = false
×
275

276
        for i, object in ipairs(self.objects) do
×
277
                if object == obj then
×
UNCOV
278
                        removed = table.remove(self.objects, i) and true or false
×
279
                        break
280
                end
281
        end
282

283
        if not removed then
×
284
                for _, child in ipairs(self.children) do
×
285
                        removed = child:remove(obj)
×
UNCOV
286
                        if removed then break end
×
287
                end
288
        end
289

UNCOV
290
        if removed then
×
291
                -- Check if we should merge nodes now that we've removed an item
292
                if self:should_merge() then
×
UNCOV
293
                        self:merge()
×
294
                end
295
        end
296

UNCOV
297
        return removed
×
298
end
299

300
--- Check if the specified bounds intersect with anything in the tree. See also: get_colliding.
301
-- @param checkBounds Bounds to check
302
-- @return boolean True if there was a collision
303
function OctreeNode:is_colliding(checkBounds)
1✔
304
        -- Are the input bounds at least partially in this node?
305
        if not intersect.aabb_aabb(self.bounds, checkBounds) then
×
UNCOV
306
                return false
×
307
        end
308

309
        -- Check against any objects in this node
310
        for _, object in ipairs(self.objects) do
×
311
                if intersect.aabb_aabb(object.bounds, checkBounds) then
×
UNCOV
312
                        return true
×
313
                end
314
        end
315

316
        -- Check children
317
        for _, child in ipairs(self.children) do
×
318
                if child:is_colliding(checkBounds) then
×
UNCOV
319
                        return true
×
320
                end
321
        end
322

UNCOV
323
        return false
×
324
end
325

326
--- Returns an array of objects that intersect with the specified bounds, if any. Otherwise returns an empty array. See also: is_colliding.
327
-- @param checkBounds Bounds to check. Passing by ref as it improve performance with structs
328
-- @param results List results
329
-- @return table Objects that intersect with the specified bounds
330
function OctreeNode:get_colliding(checkBounds, results)
1✔
UNCOV
331
        results = results or {}
×
332

333
        -- Are the input bounds at least partially in this node?
334
        if not intersect.aabb_aabb(self.bounds, checkBounds) then
×
UNCOV
335
                return results
×
336
        end
337

338
        -- Check against any objects in this node
339
        for _, object in ipairs(self.objects) do
×
340
                if intersect.aabb_aabb(object.bounds, checkBounds) then
×
UNCOV
341
                        table.insert(results, object.data)
×
342
                end
343
        end
344

345
        -- Check children
346
        for _, child in ipairs(self.children) do
×
UNCOV
347
                results = child:get_colliding(checkBounds, results)
×
348
        end
349

UNCOV
350
        return results
×
351
end
352

353
--- Cast a ray through the node and its children
354
-- @param ray Ray with a position and a direction
355
-- @param func Function to execute on any objects within child nodes
356
-- @param out Table to store results of func in
357
-- @param depth (used internally)
358
-- @return boolean True if an intersect is detected
359
function OctreeNode:cast_ray(ray, func, out, depth)
1✔
UNCOV
360
        depth = depth or 1
×
361

362
        if intersect.ray_aabb(ray, self.bounds) then
×
363
                if #self.objects > 0 then
×
UNCOV
364
                        local hit = func(ray, self.objects, out)
×
365

366
                        if hit then
×
UNCOV
367
                                return hit
×
368
                        end
369
                end
370

371
                for _, child in ipairs(self.children) do
×
UNCOV
372
                        local hit = child:cast_ray(ray, func, out, depth + 1)
×
373

374
                        if hit then
×
UNCOV
375
                                return hit
×
376
                        end
377
                end
378
        end
379

UNCOV
380
        return false
×
381
end
382

383
--- Set the 8 children of this octree.
384
-- @param childOctrees The 8 new child nodes
385
function OctreeNode:set_children(childOctrees)
1✔
386
        if #childOctrees ~= 8 then
×
387
                print("Child octree array must be length 8. Was length: " .. #childOctrees)
×
UNCOV
388
                return
×
389
        end
390

UNCOV
391
        self.children = childOctrees
×
392
end
393

394
--- We can shrink the octree if:
395
--- - This node is >= double minLength in length
396
--- - All objects in the root node are within one octant
397
--- - This node doesn't have children, or does but 7/8 children are empty
398
--- We can also shrink it if there are no objects left at all!
399
-- @param minLength Minimum dimensions of a node in this octree
400
-- @return table The new root, or the existing one if we didn't shrink
401
function OctreeNode:shrink_if_possible(minLength)
1✔
402
        if self.baseLength < 2 * minLength then
×
UNCOV
403
                return self
×
404
        end
405

406
        if #self.objects == 0 and #self.children == 0 then
×
UNCOV
407
                return self
×
408
        end
409

410
        -- Check objects in root
UNCOV
411
        local bestFit = 0
×
412

413
        for i, object in ipairs(self.objects) do
×
UNCOV
414
                local newBestFit = self:best_fit_child(object.bounds)
×
415

UNCOV
416
                if i == 1 or newBestFit == bestFit then
×
417
                        -- In same octant as the other(s). Does it fit completely inside that octant?
418
                        if intersect.encapsulate_aabb(self.childBounds[newBestFit], object.bounds) then
×
419
                                if bestFit < 1 then
×
UNCOV
420
                                        bestFit = newBestFit
×
421
                                end
422
                        else
423
                                -- Nope, so we can't reduce. Otherwise we continue
UNCOV
424
                                return self
×
425
                        end
426
                else
UNCOV
427
                        return self -- Can't reduce - objects fit in different octants
×
428
                end
429
        end
430

431
        -- Check objects in children if there are any
432
        if #self.children > 0 then
×
UNCOV
433
                local childHadContent = false
×
434

435
                for i, child in ipairs(self.children) do
×
436
                        if child:has_any_objects() then
×
437
                                if childHadContent then
×
UNCOV
438
                                        return self -- Can't shrink - another child had content already
×
439
                                end
440

441
                                if bestFit > 0 and bestFit ~= i then
×
UNCOV
442
                                        return self -- Can't reduce - objects in root are in a different octant to objects in child
×
443
                                end
444

445
                                childHadContent = true
×
UNCOV
446
                                bestFit = i
×
447
                        end
448
                end
449
        end
450

451
        -- Can reduce
UNCOV
452
        if #self.children == 0 then
×
453
                -- We don't have any children, so just shrink this node to the new size
454
                -- We already know that everything will still fit in it
455
                self:set_values(self.baseLength / 2, self.minSize, self.looseness, self.childBounds[bestFit].center)
×
UNCOV
456
                return self
×
457
        end
458

459
        -- We have children. Use the appropriate child as the new root node
UNCOV
460
        return self.children[bestFit]
×
461
end
462

463
--- Set values for this node.
464
-- @param baseLength Length of this node, not taking looseness into account
465
-- @param minSize Minimum size of nodes in this octree
466
-- @param looseness Multiplier for baseLengthVal to get the actual size
467
-- @param center Centre position of this node
468
function OctreeNode:set_values(baseLength, minSize, looseness, center)
1✔
469
        -- Length of this node if it has a looseness of 1.0
UNCOV
470
        self.baseLength = baseLength
×
471

472
        -- Minimum size for a node in this octree
UNCOV
473
        self.minSize = minSize
×
474

475
        -- Looseness value for this node
UNCOV
476
        self.looseness = looseness
×
477

478
        -- Centre of this node
UNCOV
479
        self.center = center
×
480

481
        -- Actual length of sides, taking the looseness value into account
UNCOV
482
        self.adjLength = self.looseness * self.baseLength
×
483

484
        -- Create the bounding box.
UNCOV
485
        self.size = vec3(self.adjLength, self.adjLength, self.adjLength)
×
486

487
        -- Bounding box that represents this node
UNCOV
488
        self.bounds = new_bound(self.center, self.size)
×
489

490
        self.quarter           = self.baseLength / 4
×
491
        self.childActualLength = (self.baseLength / 2) * self.looseness
×
UNCOV
492
        self.childActualSize   = vec3(self.childActualLength, self.childActualLength, self.childActualLength)
×
493

494
        -- Bounds of potential children to this node. These are actual size (with looseness taken into account), not base size
495
        self.childBounds =  {
×
496
                new_bound(self.center + vec3(-self.quarter,  self.quarter, -self.quarter), self.childActualSize),
×
497
                new_bound(self.center + vec3( self.quarter,  self.quarter, -self.quarter), self.childActualSize),
×
498
                new_bound(self.center + vec3(-self.quarter,  self.quarter,  self.quarter), self.childActualSize),
×
499
                new_bound(self.center + vec3( self.quarter,  self.quarter,  self.quarter), self.childActualSize),
×
500
                new_bound(self.center + vec3(-self.quarter, -self.quarter, -self.quarter), self.childActualSize),
×
501
                new_bound(self.center + vec3( self.quarter, -self.quarter, -self.quarter), self.childActualSize),
×
502
                new_bound(self.center + vec3(-self.quarter, -self.quarter,  self.quarter), self.childActualSize),
×
UNCOV
503
                new_bound(self.center + vec3( self.quarter, -self.quarter,  self.quarter), self.childActualSize)
×
504
        }
505
end
506

507
--- Splits the octree into eight children.
508
function OctreeNode:split()
1✔
UNCOV
509
        if #self.children > 0 then return end
×
510

511
        local quarter   = self.baseLength / 4
×
UNCOV
512
        local newLength = self.baseLength / 2
×
513

514
        table.insert(self.children, Node(newLength, self.minSize, self.looseness, self.center + vec3(-quarter,  quarter, -quarter)))
×
515
        table.insert(self.children, Node(newLength, self.minSize, self.looseness, self.center + vec3( quarter,  quarter, -quarter)))
×
516
        table.insert(self.children, Node(newLength, self.minSize, self.looseness, self.center + vec3(-quarter,  quarter,  quarter)))
×
517
        table.insert(self.children, Node(newLength, self.minSize, self.looseness, self.center + vec3( quarter,  quarter,  quarter)))
×
518
        table.insert(self.children, Node(newLength, self.minSize, self.looseness, self.center + vec3(-quarter, -quarter, -quarter)))
×
519
        table.insert(self.children, Node(newLength, self.minSize, self.looseness, self.center + vec3( quarter, -quarter, -quarter)))
×
520
        table.insert(self.children, Node(newLength, self.minSize, self.looseness, self.center + vec3(-quarter, -quarter,  quarter)))
×
UNCOV
521
        table.insert(self.children, Node(newLength, self.minSize, self.looseness, self.center + vec3( quarter, -quarter,  quarter)))
×
522
end
523

524
--- Merge all children into this node - the opposite of Split.
525
--- Note: We only have to check one level down since a merge will never happen if the children already have children,
526
--- since THAT won't happen unless there are already too many objects to merge.
527
function OctreeNode:merge()
1✔
528
        for _, child in ipairs(self.children) do
×
529
                for _, object in ipairs(child.objects) do
×
UNCOV
530
                        table.insert(self.objects, object)
×
531
                end
532
        end
533

534
        -- Remove the child nodes (and the objects in them - they've been added elsewhere now)
UNCOV
535
        self.children = {}
×
536
end
537

538
--- Find which child node this object would be most likely to fit in.
539
-- @param objBounds The object's bounds
540
-- @return number One of the eight child octants
541
function OctreeNode:best_fit_child(objBounds)
1✔
UNCOV
542
        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
×
543
end
544

545
--- Checks if there are few enough objects in this node and its children that the children should all be merged into this.
546
-- @return boolean True there are less or the same abount of objects in this and its children than numObjectsAllowed
547
function OctreeNode:should_merge()
1✔
UNCOV
548
        local totalObjects = #self.objects
×
549

550
        for _, child in ipairs(self.children) do
×
UNCOV
551
                if #child.children > 0 then
×
552
                        -- If any of the *children* have children, there are definitely too many to merge,
553
                        -- or the child would have been merged already
UNCOV
554
                        return false
×
555
                end
556

UNCOV
557
                totalObjects = totalObjects + #child.objects
×
558
        end
559

UNCOV
560
        return totalObjects <= self.numObjectsAllowed
×
561
end
562

563
--- Checks if this node or anything below it has something in it.
564
-- @return boolean True if this node or any of its children, grandchildren etc have something in the
565
function OctreeNode:has_any_objects()
1✔
UNCOV
566
        if #self.objects > 0 then return true end
×
567

568
        for _, child in ipairs(self.children) do
×
UNCOV
569
                if child:has_any_objects() then return true end
×
570
        end
571

UNCOV
572
        return false
×
573
end
574

575
--- Draws node boundaries visually for debugging.
576
-- @param cube Cube model to draw
577
-- @param depth Used for recurcive calls to this method
578
function OctreeNode:draw_bounds(cube, depth)
1✔
UNCOV
579
        depth = depth or 0
×
580
        local tint = depth / 7 -- Will eventually get values > 1. Color rounds to 1 automatically
×
581

582
        love.graphics.setColor(tint * 255, 0, (1 - tint) * 255)
×
583
        local m = mat4()
×
UNCOV
584
                :translate(self.center)
×
UNCOV
585
                :scale(vec3(self.adjLength, self.adjLength, self.adjLength))
×
586

UNCOV
587
        love.graphics.updateMatrix("transform", m)
×
UNCOV
588
        love.graphics.setWireframe(true)
×
UNCOV
589
        love.graphics.draw(cube)
×
UNCOV
590
        love.graphics.setWireframe(false)
×
591

UNCOV
592
        for _, child in ipairs(self.children) do
×
UNCOV
593
                child:draw_bounds(cube, depth + 1)
×
594
        end
595

UNCOV
596
        love.graphics.setColor(255, 255, 255)
×
597
end
598

599
--- Draws the bounds of all objects in the tree visually for debugging.
600
-- @param cube Cube model to draw
601
-- @param filter a function returning true or false to determine visibility.
602
function OctreeNode:draw_objects(cube, filter)
1✔
UNCOV
603
        local tint = self.baseLength / 20
×
UNCOV
604
        love.graphics.setColor(0, (1 - tint) * 255, tint * 255, 63)
×
605

UNCOV
606
        for _, object in ipairs(self.objects) do
×
UNCOV
607
                if filter and filter(object.data) or not filter then
×
UNCOV
608
                        local m = mat4()
×
UNCOV
609
                                :translate(object.bounds.center)
×
UNCOV
610
                                :scale(object.bounds.size)
×
611

UNCOV
612
                        love.graphics.updateMatrix("transform", m)
×
UNCOV
613
                        love.graphics.draw(cube)
×
614
                end
615
        end
616

UNCOV
617
        for _, child in ipairs(self.children) do
×
UNCOV
618
                child:draw_objects(cube, filter)
×
619
        end
620

UNCOV
621
        love.graphics.setColor(255, 255, 255)
×
622
end
623

624
Node = setmetatable({
2✔
625
        new = new_node
1✔
626
}, {
1✔
627
        __call = function(_, ...) return new_node(...) end
1✔
628
})
1✔
629

630
return setmetatable({
2✔
631
        new = new
1✔
632
}, {
1✔
633
        __call = function(_, ...) return new(...) end
1✔
634
})
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc