[7751] | 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. |
---|
[7858] | 5 | |
---|
| 6 | As of June 2010 this module has a pylint quality rating of 10/10. |
---|
[7751] | 7 | """ |
---|
| 8 | |
---|
| 9 | # Allow children to be slightly bigger than their parents to prevent |
---|
| 10 | # straddling of a boundary |
---|
[7710] | 11 | SPLIT_BORDER_RATIO = 0.55 |
---|
| 12 | |
---|
| 13 | class AABB: |
---|
| 14 | """Axially-aligned bounding box class. |
---|
[7721] | 15 | Defines a box which can check for intersections with other boxes, |
---|
| 16 | or if a point lies within it. |
---|
[7710] | 17 | """ |
---|
| 18 | |
---|
[7858] | 19 | def __init__(self, xmin, xmax=None, ymin=None, ymax=None): |
---|
[7710] | 20 | """ Define axially-algned bounding box. |
---|
| 21 | xmin is minimum x |
---|
| 22 | xmax is maximum x (absolute coord, ie, not size) |
---|
| 23 | ymin is minimum y |
---|
| 24 | ymax is maximum y (absolute coord, ie, not size) |
---|
| 25 | """ |
---|
[7858] | 26 | if not xmax: |
---|
| 27 | # try treating first arg as a list of points |
---|
| 28 | try: |
---|
| 29 | xmin[0][0] |
---|
| 30 | except: |
---|
| 31 | raise Exception('Single parameter to AABB must be point list.') |
---|
| 32 | |
---|
| 33 | self.xmin, self.ymin = self.xmax, self.ymax = xmin[0] |
---|
| 34 | self.include(xmin[1:]) |
---|
| 35 | else: |
---|
| 36 | self.xmin = xmin |
---|
| 37 | self.xmax = xmax |
---|
| 38 | self.ymin = ymin |
---|
| 39 | self.ymax = ymax |
---|
[7710] | 40 | |
---|
| 41 | |
---|
| 42 | def __repr__(self): |
---|
| 43 | return 'AABB(xmin:%f, xmax:%f, ymin:%f, ymax:%f)' \ |
---|
[7751] | 44 | % (round(self.xmin,1), round(self.xmax,1), \ |
---|
| 45 | round(self.ymin,1), round(self.ymax, 1)) |
---|
[7710] | 46 | |
---|
| 47 | |
---|
| 48 | def grow(self, amount): |
---|
| 49 | """ Expand region by given amount. |
---|
| 50 | amount is a multiplier, ie 1.1 will expand border by 10%. |
---|
| 51 | """ |
---|
| 52 | self.ymax += (self.ymax-self.ymin)*amount |
---|
| 53 | self.xmax += (self.xmax-self.xmin)*amount |
---|
| 54 | self.ymin -= (self.ymax-self.ymin)*amount |
---|
| 55 | self.xmin -= (self.xmax-self.xmin)*amount |
---|
| 56 | |
---|
| 57 | |
---|
| 58 | def size(self): |
---|
| 59 | """return size as (w,h)""" |
---|
| 60 | return self.xmax - self.xmin, self.ymax - self.ymin |
---|
| 61 | |
---|
| 62 | |
---|
| 63 | def split(self, border=SPLIT_BORDER_RATIO): |
---|
[7721] | 64 | """Split box along shorter axis. |
---|
| 65 | return 2 subdivided AABBs. This is useful for recursive |
---|
| 66 | algorithms. |
---|
| 67 | |
---|
| 68 | border is the overlap between the 2 regions - if set to 0.5 |
---|
| 69 | it will subdivide them exactly, > 0.5 will create a |
---|
| 70 | shared overlapping area. |
---|
[7710] | 71 | """ |
---|
| 72 | |
---|
| 73 | width, height = self.size() |
---|
| 74 | assert width >= 0 and height >= 0 |
---|
| 75 | |
---|
| 76 | if (width > height): |
---|
| 77 | # split vertically |
---|
[7751] | 78 | split1 = self.xmin+width*border |
---|
| 79 | split2 = self.xmax-width*border |
---|
| 80 | return AABB(self.xmin, split1, self.ymin, self.ymax), \ |
---|
| 81 | AABB(split2, self.xmax, self.ymin, self.ymax) |
---|
[7710] | 82 | else: |
---|
| 83 | # split horizontally |
---|
[7751] | 84 | split1 = self.ymin+height*border |
---|
| 85 | split2 = self.ymax-height*border |
---|
| 86 | return AABB(self.xmin, self.xmax, self.ymin, split1), \ |
---|
| 87 | AABB(self.xmin, self.xmax, split2, self.ymax) |
---|
[7710] | 88 | |
---|
| 89 | |
---|
| 90 | def is_trivial_in(self, test): |
---|
| 91 | """ Is trivial in. |
---|
[7721] | 92 | test a box to test against the bounding box |
---|
| 93 | return True if the test box falls fully within the |
---|
| 94 | bounding box without intersecting it. |
---|
[7710] | 95 | """ |
---|
| 96 | if (test.xmin < self.xmin) or (test.xmax > self.xmax): |
---|
| 97 | return False |
---|
| 98 | if (test.ymin < self.ymin) or (test.ymax > self.ymax): |
---|
| 99 | return False |
---|
| 100 | return True |
---|
| 101 | |
---|
[7751] | 102 | def contains(self, point): |
---|
[7721] | 103 | """ is point within box |
---|
[7751] | 104 | point is a test point |
---|
[7721] | 105 | return True if the point is contained within the box. |
---|
| 106 | """ |
---|
[7751] | 107 | return (self.xmin <= point[0] <= self.xmax) \ |
---|
| 108 | and (self.ymin <= point[1] <= self.ymax) |
---|
[7710] | 109 | |
---|
[7858] | 110 | def include(self, point_list): |
---|
| 111 | """ Include points in AABB. |
---|
| 112 | Bounding box will be expanded to include passed points |
---|
| 113 | |
---|
| 114 | point_list is a list of points. |
---|
| 115 | """ |
---|
| 116 | for point in point_list: |
---|
| 117 | pt_x, pt_y = point |
---|
| 118 | if pt_x < self.xmin: |
---|
| 119 | self.xmin = pt_x |
---|
| 120 | if pt_x > self.xmax: |
---|
| 121 | self.xmax = pt_x |
---|
| 122 | if pt_y < self.ymin: |
---|
| 123 | self.ymin = pt_y |
---|
| 124 | if pt_y > self.ymax: |
---|
| 125 | self.ymax = pt_y |
---|