@@ -105,7 +105,7 @@ def merge(cls, aabb1, aabb2):
105
105
106
106
@property
107
107
def perimeter (self ):
108
- r"""Perimeter of AABB
108
+ r"""float: perimeter of AABB
109
109
110
110
The perimeter :math:`p_n` of an AABB with side lengths
111
111
:math:`l_1 \ldots l_n` is:
@@ -132,6 +132,14 @@ def perimeter(self):
132
132
perim += p_edge
133
133
return 2 * perim
134
134
135
+ @property
136
+ def volume (self ):
137
+ """float: volume of AABB"""
138
+ vol = 1
139
+ for lb , ub in self :
140
+ vol *= ub - lb
141
+ return vol
142
+
135
143
def overlaps (self , aabb ):
136
144
"""Determine if two AABBs overlap
137
145
@@ -153,6 +161,38 @@ def overlaps(self, aabb):
153
161
return False
154
162
return True
155
163
164
+ def overlap_volume (self , aabb ):
165
+ r"""Determine volume of overlap between AABBs
166
+
167
+ Let :math:`(x_i^l, x_i^u)` be the i-th dimension
168
+ lower and upper bounds for AABB 1, and
169
+ let :math:`(y_i^l, y_i^u)` be the lower and upper bounds for
170
+ AABB 2. The volume of overlap is:
171
+
172
+ .. math::
173
+
174
+ V = \prod_{i=1}^n \text{max}(0, \text{min}(x_i^u, y_i^u) - \text{max}(x_i^l, y_i^l))
175
+
176
+ Args:
177
+ aabb (AABB): The AABB to calculate for overlap volume
178
+
179
+ Returns:
180
+ float: Volume of overlap
181
+ """ # NOQA: E501
182
+
183
+ volume = 1
184
+ for lims1 , lims2 in zip (self , aabb ):
185
+ min1 , max1 = lims1
186
+ min2 , max2 = lims2
187
+
188
+ overlap_min = max (min1 , min2 )
189
+ overlap_max = min (max1 , max2 )
190
+ if overlap_min >= overlap_max :
191
+ return 0
192
+
193
+ volume *= overlap_max - overlap_min
194
+ return volume
195
+
156
196
157
197
class AABBTree (object ):
158
198
"""Python Implementation of the AABB Tree
@@ -233,20 +273,83 @@ def __eq__(self, aabbtree):
233
273
def __ne__ (self , aabbtree ):
234
274
return not self .__eq__ (aabbtree )
235
275
276
+ def __len__ (self ):
277
+ if self .is_leaf :
278
+ return int (self .aabb != AABB ())
279
+ else :
280
+ return len (self .left ) + len (self .right )
281
+
236
282
@property
237
283
def is_leaf (self ):
238
284
"""bool: returns True if is leaf node"""
239
285
return (self .left is None ) and (self .right is None )
240
286
241
- def add (self , aabb , value = None ):
242
- """Add node to tree
287
+ @property
288
+ def depth (self ):
289
+ """int: Depth of the tree"""
290
+ if self .is_leaf :
291
+ return 0
292
+ else :
293
+ return 1 + max (self .left .depth , self .right .depth )
294
+
295
+ def add (self , aabb , value = None , method = 'volume' ):
296
+ r"""Add node to tree
243
297
244
298
This function inserts a node into the AABB tree.
299
+ The function chooses one of three options for adding the node to
300
+ the tree:
301
+
302
+ * Add it to the left side
303
+ * Add it to the right side
304
+ * Become a leaf node
305
+
306
+ The cost of each option is calculated based on the *method* keyword,
307
+ and the option with the lowest cost is chosen.
245
308
246
309
Args:
247
310
aabb (AABB): The AABB to add.
248
311
value: The value associated with the AABB. Defaults to None.
249
- """
312
+ method (str): The method for deciding how to build the tree.
313
+ Should be one of the following:
314
+
315
+ * 'volume'
316
+
317
+ **'volume'**
318
+ *Costs based on total bounding volume and overlap volume*
319
+
320
+ Let :math:`b` denote the tree, :math:`l` denote the left
321
+ branch, :math:`r` denote the right branch, :math:`x` denote
322
+ the AABB to add, and math:`V` be the volume of an AABB.
323
+ The cost associated with each of these options is:
324
+
325
+ .. math::
326
+
327
+ C(\text{add left}) &= V(b \cup x) - V(b) + V(l \cup x) - V(l) + V((l \cup x) \cap r) \\
328
+ C(\text{add right}) &= V(b \cup x) - V(b) + V(r \cup x) - V(r) + V((r \cup x) \cap l) \\
329
+ C(\text{leaf}) &= V(b \cup x) + V(b \cap x)
330
+
331
+ The first two terms in the 'add left' cost represent the change
332
+ in volume for the tree. The next two terms give the change in
333
+ volume for the left branch specifically (right branch is
334
+ unchanged). The final term is the amount of overlap that would
335
+ be between the new left branch and the right branch.
336
+
337
+ This cost function includes the increases in bounding volumes and
338
+ the amount of overlap- two values a balanced AABB tree should minimize.
339
+
340
+ The 'add right' cost is a mirror opposite of the 'add left cost'.
341
+ The 'leaf' cost is the added bounding volume plus a penalty for
342
+ overlapping with the existing tree.
343
+
344
+ These costs suit the author's current needs.
345
+ Other applications, such as raytracing, are more concerned
346
+ with surface area than volume. Please visit the
347
+ `AABBTree repository`_ if you are interested in implementing
348
+ another cost function.
349
+
350
+ .. _`AABBTree repository`: https://github.com/kip-hart/AABBTree
351
+
352
+ """ # NOQA: E501
250
353
if self .aabb == AABB ():
251
354
self .aabb = aabb
252
355
self .value = value
@@ -258,27 +361,38 @@ def add(self, aabb, value=None):
258
361
self .aabb = AABB .merge (self .aabb , aabb )
259
362
self .value = None
260
363
else :
261
- tree_p = self .aabb .perimeter
262
- tree_merge_p = AABB .merge (self .aabb , aabb ).perimeter
263
-
264
- new_parent_cost = 2 * tree_merge_p
265
- min_pushdown_cost = 2 * (tree_merge_p - tree_p )
266
-
267
- left_merge_p = AABB .merge (self .left .aabb , aabb ).perimeter
268
- cost_left = left_merge_p + min_pushdown_cost
269
- if not self .left .is_leaf :
270
- cost_left -= self .left .aabb .perimeter
271
-
272
- right_merge_p = AABB .merge (self .right .aabb , aabb ).perimeter
273
- cost_right = right_merge_p + min_pushdown_cost
274
- if not self .right .is_leaf :
275
- cost_right -= self .right .aabb .perimeter
364
+ if method == 'volume' :
365
+ # Define merged AABBs
366
+ branch_merge = AABB .merge (self .aabb , aabb )
367
+ left_merge = AABB .merge (self .left .aabb , aabb )
368
+ right_merge = AABB .merge (self .right .aabb , aabb )
369
+
370
+ # Calculate the change in the sum of the bounding volumes
371
+ branch_bnd_cost = branch_merge .volume
372
+
373
+ left_bnd_cost = branch_merge .volume - self .aabb .volume
374
+ left_bnd_cost += left_merge .volume - self .left .aabb .volume
375
+
376
+ right_bnd_cost = branch_merge .volume - self .aabb .volume
377
+ right_bnd_cost += right_merge .volume - self .right .aabb .volume
378
+
379
+ # Calculate amount of overlap
380
+ branch_olap_cost = self .aabb .overlap_volume (aabb )
381
+ left_olap_cost = left_merge .overlap_volume (self .right .aabb )
382
+ right_olap_cost = right_merge .overlap_volume (self .left .aabb )
383
+
384
+ # Calculate total cost
385
+ branch_cost = branch_bnd_cost + branch_olap_cost
386
+ left_cost = left_bnd_cost + left_olap_cost
387
+ right_cost = right_bnd_cost + right_olap_cost
388
+ else :
389
+ raise ValueError ('Unrecognized method: ' + str (method ))
276
390
277
- if new_parent_cost < min ( cost_left , cost_right ) :
391
+ if branch_cost < left_cost and branch_cost < right_cost :
278
392
self .left = copy .deepcopy (self )
279
393
self .right = AABBTree (aabb , value )
280
394
self .value = None
281
- elif cost_left < cost_right :
395
+ elif left_cost < right_cost :
282
396
self .left .add (aabb , value )
283
397
else :
284
398
self .right .add (aabb , value )
0 commit comments