1 | """ |
---|
2 | Axially aligned bounding box. |
---|
3 | Contains a class describing a bounding box. It contains methods |
---|
4 | to split itself and return child boxes. |
---|
5 | """ |
---|
6 | |
---|
7 | # Allow children to be slightly bigger than their parents to prevent |
---|
8 | # straddling of a boundary |
---|
9 | SPLIT_BORDER_RATIO = 0.55 |
---|
10 | |
---|
11 | class AABB: |
---|
12 | """Axially-aligned bounding box class. |
---|
13 | Defines a box which can check for intersections with other boxes, |
---|
14 | or if a point lies within it. |
---|
15 | """ |
---|
16 | |
---|
17 | def __init__(self, xmin, xmax, ymin, ymax): |
---|
18 | """ Define axially-algned bounding box. |
---|
19 | xmin is minimum x |
---|
20 | xmax is maximum x (absolute coord, ie, not size) |
---|
21 | ymin is minimum y |
---|
22 | ymax is maximum y (absolute coord, ie, not size) |
---|
23 | """ |
---|
24 | self.xmin = xmin |
---|
25 | self.xmax = xmax |
---|
26 | self.ymin = ymin |
---|
27 | self.ymax = ymax |
---|
28 | |
---|
29 | |
---|
30 | def __repr__(self): |
---|
31 | return 'AABB(xmin:%f, xmax:%f, ymin:%f, ymax:%f)' \ |
---|
32 | % (round(self.xmin,1), round(self.xmax,1), \ |
---|
33 | round(self.ymin,1), round(self.ymax, 1)) |
---|
34 | |
---|
35 | |
---|
36 | def grow(self, amount): |
---|
37 | """ Expand region by given amount. |
---|
38 | amount is a multiplier, ie 1.1 will expand border by 10%. |
---|
39 | """ |
---|
40 | self.ymax += (self.ymax-self.ymin)*amount |
---|
41 | self.xmax += (self.xmax-self.xmin)*amount |
---|
42 | self.ymin -= (self.ymax-self.ymin)*amount |
---|
43 | self.xmin -= (self.xmax-self.xmin)*amount |
---|
44 | |
---|
45 | |
---|
46 | def size(self): |
---|
47 | """return size as (w,h)""" |
---|
48 | return self.xmax - self.xmin, self.ymax - self.ymin |
---|
49 | |
---|
50 | |
---|
51 | def split(self, border=SPLIT_BORDER_RATIO): |
---|
52 | """Split box along shorter axis. |
---|
53 | return 2 subdivided AABBs. This is useful for recursive |
---|
54 | algorithms. |
---|
55 | |
---|
56 | border is the overlap between the 2 regions - if set to 0.5 |
---|
57 | it will subdivide them exactly, > 0.5 will create a |
---|
58 | shared overlapping area. |
---|
59 | """ |
---|
60 | |
---|
61 | width, height = self.size() |
---|
62 | assert width >= 0 and height >= 0 |
---|
63 | |
---|
64 | if (width > height): |
---|
65 | # split vertically |
---|
66 | split1 = self.xmin+width*border |
---|
67 | split2 = self.xmax-width*border |
---|
68 | return AABB(self.xmin, split1, self.ymin, self.ymax), \ |
---|
69 | AABB(split2, self.xmax, self.ymin, self.ymax) |
---|
70 | else: |
---|
71 | # split horizontally |
---|
72 | split1 = self.ymin+height*border |
---|
73 | split2 = self.ymax-height*border |
---|
74 | return AABB(self.xmin, self.xmax, self.ymin, split1), \ |
---|
75 | AABB(self.xmin, self.xmax, split2, self.ymax) |
---|
76 | |
---|
77 | |
---|
78 | def is_trivial_in(self, test): |
---|
79 | """ Is trivial in. |
---|
80 | test a box to test against the bounding box |
---|
81 | return True if the test box falls fully within the |
---|
82 | bounding box without intersecting it. |
---|
83 | """ |
---|
84 | if (test.xmin < self.xmin) or (test.xmax > self.xmax): |
---|
85 | return False |
---|
86 | if (test.ymin < self.ymin) or (test.ymax > self.ymax): |
---|
87 | return False |
---|
88 | return True |
---|
89 | |
---|
90 | def contains(self, point): |
---|
91 | """ is point within box |
---|
92 | point is a test point |
---|
93 | return True if the point is contained within the box. |
---|
94 | """ |
---|
95 | return (self.xmin <= point[0] <= self.xmax) \ |
---|
96 | and (self.ymin <= point[1] <= self.ymax) |
---|
97 | |
---|