File Coverage

blib/lib/Algorithm/QuadTree/PP.pm
Criterion Covered Total %
statement 95 95 100.0
branch 38 40 95.0
condition 15 15 100.0
subroutine 15 15 100.0
pod n/a
total 163 165 98.7


line stmt bran cond sub pod time code
1             package Algorithm::QuadTree::PP;
2             $Algorithm::QuadTree::PP::VERSION = '0.5';
3 6     6   40 use strict;
  6         13  
  6         170  
4 6     6   24 use warnings;
  6         12  
  6         166  
5 6     6   30 use Exporter qw(import);
  6         11  
  6         286  
6              
7 6     6   39 use Scalar::Util qw(weaken);
  6         12  
  6         7205  
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   204 my ($self, $depth, $parent, @coords) = @_;
22 89         215 my $node = {
23             PARENT => $parent,
24             HAS_OBJECTS => 0,
25             AREA => \@coords,
26             };
27              
28 89 100       263 weaken $node->{PARENT} if $parent;
29              
30 89 100       163 if ($depth < $self->{DEPTH}) {
31 21         43 my ($xmin, $ymin, $xmax, $ymax) = @coords;
32 21         68 my $xmid = $xmin + ($xmax - $xmin) / 2;
33 21         40 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         63 _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         119 $node->{OBJECTS} = [];
48             }
49              
50 89         224 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   46 my ($self, $finding, @coords) = @_;
58              
59             # avoid squaring the radius on each iteration
60 12         35 my $radius_squared = $coords[2] ** 2;
61              
62 12         19 my @nodes;
63 12         36 my @loopargs = $self->{ROOT};
64 12         33 while (my $current = shift @loopargs) {
65 84 100 100     227 next if $finding && !$current->{HAS_OBJECTS};
66              
67 55         76 my ($cxmin, $cymin, $cxmax, $cymax) = @{$current->{AREA}};
  55         102  
68              
69 55 100       135 my $cx = $coords[0] < $cxmin
    100          
70             ? $cxmin
71             : $coords[0] > $cxmax
72             ? $cxmax
73             : $coords[0]
74             ;
75              
76 55 100       132 my $cy = $coords[1] < $cymin
    100          
77             ? $cymin
78             : $coords[1] > $cymax
79             ? $cymax
80             : $coords[1]
81             ;
82              
83             # first check if obj overlaps current segment.
84 55 100       160 next if ($coords[0] - $cx) ** 2 + ($coords[1] - $cy) ** 2
85             > $radius_squared;
86              
87 35 100       64 $current->{HAS_OBJECTS} = 1 if !$finding;
88 35 100       73 if ($current->{CHILDREN}) {
89 18         27 push @loopargs, @{$current->{CHILDREN}};
  18         57  
90             } else {
91             # segment is a leaf and overlaps the obj
92 17         49 push @nodes, $current;
93             }
94             }
95              
96 12         47 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   246 my ($self, $finding, @coords) = @_;
104              
105 113         158 my @nodes;
106 113         205 my @loopargs = $self->{ROOT};
107 113         253 while (my $current = shift @loopargs) {
108 1061 100 100     2493 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     4623 $coords[3] < $current->{AREA}[1];
      100        
      100        
116              
117 423 100       738 $current->{HAS_OBJECTS} = 1 if !$finding;
118 423 100       712 if ($current->{CHILDREN}) {
119 237         316 push @loopargs, @{$current->{CHILDREN}};
  237         661  
120             } else {
121             # segment is a leaf and overlaps the obj
122 186         426 push @nodes, $current;
123             }
124             }
125              
126 113         328 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   305 goto \&_loopOnNodesInCircle if @_ == 5;
134 113         308 goto \&_loopOnNodesInRectangle;
135             }
136              
137             sub _clearHasObjects
138             {
139 88     88   112 my $node = shift;
140              
141 88 100       156 if ($node->{CHILDREN}) {
142 50         60 for my $child (@{$node->{CHILDREN}}) {
  50         80  
143 144 100       263 return if $child->{HAS_OBJECTS};
144             }
145             }
146              
147 58         78 $node->{HAS_OBJECTS} = 0;
148 58 100       122 if ($node->{PARENT}) {
149 50         82 _clearHasObjects($node->{PARENT});
150             }
151             }
152              
153             sub _AQT_init
154             {
155 5     5   13 my $obj = shift;
156              
157 5         18 $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         24 );
167             }
168              
169             sub _AQT_deinit
170       5     {
171             # do nothing in PP implementation
172             }
173              
174             sub _AQT_addObject
175             {
176 55     55   111 my ($self, $object, @coords) = @_;
177              
178 55         82 for my $node (@{_loopOnNodes($self, 0, @coords)}) {
  55         109  
179 60         88 push @{$node->{OBJECTS}}, $object;
  60         116  
180 60         89 push @{$self->{BACKREF}{$object}}, $node;
  60         282  
181             }
182             }
183              
184             sub _AQT_findObjects
185             {
186 70     70   145 my ($self, @coords) = @_;
187              
188             # map returned nodes to an array containing all of
189             # their objects
190             return [
191             map {
192 143         185 @{$_->{OBJECTS}}
  143         493  
193 70         100 } @{_loopOnNodes($self, 1, @coords)}
  70         145  
194             ];
195             }
196              
197             sub _AQT_delete
198             {
199 1     1   2 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}} ];
  2         7  
  1         3  
205 1 50       3 _clearHasObjects($node) if !@{$node->{OBJECTS}};
  1         5  
206             }
207              
208 1         4 delete $self->{BACKREF}{$object};
209             }
210              
211             sub _AQT_clear
212             {
213 3     3   9 my ($self) = @_;
214              
215 3         7 for my $key (keys %{$self->{BACKREF}}) {
  3         14  
216 33         41 for my $node (@{$self->{BACKREF}{$key}}) {
  33         60  
217 38         64 $node->{OBJECTS} = [];
218 38         65 _clearHasObjects($node);
219             }
220             }
221 3         31 $self->{BACKREF} = {};
222             }
223              
224             1;
225