File Coverage

blib/lib/Algorithm/QuadTree/PP.pm
Criterion Covered Total %
statement 91 99 91.9
branch 36 44 81.8
condition 15 15 100.0
subroutine 14 15 93.3
pod n/a
total 156 173 90.1


line stmt bran cond sub pod time code
1             package Algorithm::QuadTree::PP;
2             $Algorithm::QuadTree::PP::VERSION = '0.6';
3 6     6   65 use strict;
  6         13  
  6         243  
4 6     6   28 use warnings;
  6         11  
  6         293  
5 6     6   64 use Exporter qw(import);
  6         15  
  6         253  
6              
7 6     6   36 use Scalar::Util qw(weaken);
  6         56  
  6         8407  
8              
9             our @EXPORT = qw(
10             _AQT_init
11             _AQT_deinit
12             _AQT_addObject
13             _AQT_findObjects
14             _AQT_delete
15             _AQT_clear
16             );
17              
18             # recursive method which adds levels to the quadtree
19             sub _addLevel
20             {
21 89     89   197 my ($self, $depth, $parent, @coords) = @_;
22 89         301 my $node = {
23             PARENT => $parent,
24             HAS_OBJECTS => 0,
25             AREA => \@coords,
26             };
27              
28 89 100       193 weaken $node->{PARENT} if $parent;
29              
30 89 100       154 if ($depth < $self->{DEPTH}) {
31 21         39 my ($xmin, $ymin, $xmax, $ymax) = @coords;
32 21         52 my $xmid = $xmin + ($xmax - $xmin) / 2;
33 21         38 my $ymid = $ymin + ($ymax - $ymin) / 2;
34 21         29 $depth += 1;
35              
36             # segment in the following order:
37             # top left, top right, bottom left, bottom right
38             $node->{CHILDREN} = [
39 21         62 _addLevel($self, $depth, $node, $xmin, $ymid, $xmid, $ymax),
40             _addLevel($self, $depth, $node, $xmid, $ymid, $xmax, $ymax),
41             _addLevel($self, $depth, $node, $xmin, $ymin, $xmid, $ymid),
42             _addLevel($self, $depth, $node, $xmid, $ymin, $xmax, $ymid),
43             ];
44             }
45             else {
46             # leaves must have empty aref in objects
47 68         140 $node->{OBJECTS} = [];
48             }
49              
50 89         258 return $node;
51             }
52              
53             # this private method executes $code on every leaf node of the tree
54             # which is within the circular shape
55             sub _loopOnNodesInCircle
56             {
57 12     12   29 my ($self, $finding, @coords) = @_;
58              
59             # avoid squaring the radius on each iteration
60 12         65 my $radius_squared = $coords[2] ** 2;
61              
62 12         17 my @nodes;
63 12         28 my @loopargs = $self->{ROOT};
64 12         49 while (my $current = shift @loopargs) {
65 84 100 100     223 next if $finding && !$current->{HAS_OBJECTS};
66              
67 55         68 my ($cxmin, $cymin, $cxmax, $cymax) = @{$current->{AREA}};
  55         114  
68              
69 55 100       158 my $cx = $coords[0] < $cxmin
    100          
70             ? $cxmin - $coords[0]
71             : $coords[0] > $cxmax
72             ? $cxmax - $coords[0]
73             : 0
74             ;
75              
76 55 100       118 my $cy = $coords[1] < $cymin
    100          
77             ? $cymin - $coords[1]
78             : $coords[1] > $cymax
79             ? $cymax - $coords[1]
80             : 0
81             ;
82              
83             # first check if obj overlaps current segment.
84 55 100       140 next if $cx ** 2 + $cy ** 2
85             > $radius_squared;
86              
87 35 100       67 $current->{HAS_OBJECTS} = 1 if !$finding;
88 35 100       60 if ($current->{CHILDREN}) {
89 18         56 push @loopargs, @{$current->{CHILDREN}};
  18         86  
90             } else {
91             # segment is a leaf and overlaps the obj
92 17         41 push @nodes, $current;
93             }
94             }
95              
96 12         48 return \@nodes;
97             }
98              
99             # this private method executes $code on every leaf node of the tree
100             # which is within the rectangular shape
101             sub _loopOnNodesInRectangle
102             {
103 113     113   211 my ($self, $finding, @coords) = @_;
104              
105 113         131 my @nodes;
106 113         183 my @loopargs = $self->{ROOT};
107 113         248 while (my $current = shift @loopargs) {
108 1061 100 100     2115 next if $finding && !$current->{HAS_OBJECTS};
109              
110             # first check if obj overlaps current segment.
111             next if
112             $coords[0] > $current->{AREA}[2] ||
113             $coords[2] < $current->{AREA}[0] ||
114             $coords[1] > $current->{AREA}[3] ||
115 1040 100 100     3919 $coords[3] < $current->{AREA}[1];
      100        
      100        
116              
117 423 100       644 $current->{HAS_OBJECTS} = 1 if !$finding;
118 423 100       592 if ($current->{CHILDREN}) {
119 237         271 push @loopargs, @{$current->{CHILDREN}};
  237         533  
120             } else {
121             # segment is a leaf and overlaps the obj
122 186         371 push @nodes, $current;
123             }
124             }
125              
126 113         338 return \@nodes;
127             }
128              
129             # choose the right function based on argument count
130             # first argument is always $self, second is $finding, the rest are coords
131             sub _loopOnNodes
132             {
133 125 100   125   267 goto \&_loopOnNodesInCircle if @_ == 5;
134 113         305 goto \&_loopOnNodesInRectangle;
135             }
136              
137             sub _clearHasObjects
138             {
139 0     0   0 my $node = shift;
140              
141 0 0       0 if ($node->{CHILDREN}) {
142 0         0 for my $child (@{$node->{CHILDREN}}) {
  0         0  
143 0 0       0 return if $child->{HAS_OBJECTS};
144             }
145             }
146              
147 0         0 $node->{HAS_OBJECTS} = 0;
148 0 0       0 if ($node->{PARENT}) {
149 0         0 _clearHasObjects($node->{PARENT});
150             }
151             }
152              
153             sub _AQT_init
154             {
155 5     5   18 my $obj = shift;
156              
157 5         16 $obj->{BACKREF} = {};
158             $obj->{ROOT} = _addLevel(
159             $obj,
160             1, #current depth
161             undef, # parent - none
162             $obj->{XMIN},
163             $obj->{YMIN},
164             $obj->{XMAX},
165             $obj->{YMAX},
166 5         26 );
167             }
168              
169             sub _AQT_deinit
170       5     {
171             # do nothing in PP implementation
172             }
173              
174             sub _AQT_addObject
175             {
176 55     55   108 my ($self, $object, @coords) = @_;
177              
178 55         71 for my $node (@{_loopOnNodes($self, 0, @coords)}) {
  55         105  
179 60         111 push @{$node->{OBJECTS}}, $object;
  60         163  
180 60         70 push @{$self->{BACKREF}{$object}}, $node;
  60         258  
181             }
182             }
183              
184             sub _AQT_findObjects
185             {
186 70     70   119 my ($self, @coords) = @_;
187              
188             # map returned nodes to an array containing all of
189             # their objects
190             return [
191             map {
192 143         167 @{$_->{OBJECTS}}
  143         523  
193 70         83 } @{_loopOnNodes($self, 1, @coords)}
  70         182  
194             ];
195             }
196              
197             sub _AQT_delete
198             {
199 1     1   3 my ($self, $object) = @_;
200              
201 1 50       5 return unless exists $self->{BACKREF}{$object};
202              
203 1         2 for my $node (@{$self->{BACKREF}{$object}}) {
  1         4  
204 1         2 @{$node->{OBJECTS}} = grep {$_ ne $object} @{$node->{OBJECTS}};
  1         3  
  2         15  
  1         3  
205 1 50       2 _clearHasObjects($node) if !@{$node->{OBJECTS}};
  1         4  
206             }
207              
208 1         3 delete $self->{BACKREF}{$object};
209             }
210              
211             sub _AQT_clear
212             {
213 3     3   9 my ($self) = @_;
214              
215 3         10 my @loopargs = $self->{ROOT};
216 3         15 while (my $current = shift @loopargs) {
217 31 100       102 next unless $current->{HAS_OBJECTS};
218 30         47 $current->{HAS_OBJECTS} = 0;
219              
220 30 100       59 if ($current->{CHILDREN}) {
221 7         15 push @loopargs, @{$current->{CHILDREN}};
  7         55  
222             } else {
223 23         32 @{$current->{OBJECTS}} = ();
  23         69  
224             }
225             }
226              
227 3         32 $self->{BACKREF} = {};
228             }
229              
230             1;
231