File Coverage

blib/lib/Collision/2D/Entity/Grid.pm
Criterion Covered Total %
statement 161 195 82.5
branch 31 62 50.0
condition 13 36 36.1
subroutine 20 24 83.3
pod 4 13 30.7
total 229 330 69.3


line stmt bran cond sub pod time code
1             package Collision::2D::Entity::Grid;
2 7     7   39 use strict;
  7         14  
  7         373  
3 7     7   37 use warnings;
  7         13  
  7         460  
4              
5             require DynaLoader;
6             our @ISA = qw(DynaLoader Collision::2D::Entity);
7             bootstrap Collision::2D::Entity::Grid;
8              
9 7     7   6828 use List::AllUtils qw/max min/;
  7         33640  
  7         800  
10 7     7   7284 use POSIX qw(ceil floor);
  7         86712  
  7         53  
11 7     7   18146 use Set::Object;
  7         70582  
  7         351  
12 7     7   68 use Carp qw/cluck confess/;
  7         13  
  7         726  
13              
14 143     143   429 sub _p{1} #highest priority -- include all relevant methods in this module
15 7     7   43 use overload '""' => sub{'grid'};
  7     0   13  
  7         91  
  0         0  
16 0     0 0 0 sub typename{'grid'}
17              
18              
19             # table is where we store every grid child;
20             # in each cell, a list of entities which intersect it
21             # table is a list of rows, so it's table->[y][x] = [ent,...]
22              
23             ### has table => []
24             ### has w,h => float
25             ### has cells_x,cells_y => int
26             ### has cell_size => float
27             #there's a reason you can't find, say, cell row count with @{$grid->table}
28             #that reason is autovivication
29             # granularity; cells will be squares of this size
30              
31              
32             sub new{
33 31     31 0 158 my ($package, %params) = @_;
34 31   50     998 my $self = __PACKAGE__->_new (
      50        
      50        
      50        
      50        
      50        
      33        
      33        
35             @params{qw/x y/},
36             $params{xv} || 0,
37             $params{yv} || 0,
38             $params{relative_x} || 0,
39             $params{relative_y} || 0,
40             $params{relative_xv} || 0,
41             $params{relative_yv} || 0,
42             @params{qw/w h/},
43             $params{cells_x} || ceil($params{w} / $params{cell_size}),
44             $params{cells_y} || ceil($params{h} / $params{cell_size}),
45             $params{cell_size},
46             );
47 31         134 return $self;
48             }
49              
50              
51             sub add{
52 9     9 1 16 my ($self, @others) = @_;
53 9         15 for (@others){
54 9 50       22 if (ref $_ eq 'ARRAY'){
55 0         0 $self->add(@$_);
56             }
57 9         119 my $method = "add_$_";
58 9         23 $self->$method($_);
59             }
60            
61             }
62              
63              
64             #nonmoving circle, not necessarily normalized
65             #returns list of [cell_x,cell_y],...
66             sub cells_intersect_circle{
67 70     70 0 123 my ($self, $circle) = @_;
68 70         79 my @cells; # [int,int], ...
69            
70             #must find a faster way to find points inside
71 70         169 my $r = $circle->radius;
72 70         257 my $rx = $circle->x - $self->x; #relative
73 70         209 my $ry = $circle->y - $self->y;
74 70         148 my $s = $self->cell_size;
75            
76 70         518 for my $y ( max(0, ($ry-$r)/$s) .. floor min ($self->cells_y-1, ($ry+$r)/$s) ) {
77 196         1235 for my $x ( max(0, ($rx-$r)/$s) .. floor min ($self->cells_x-1, ($rx+$r)/$s) ) {
78 746         3924 my $rect = Collision::2D::Entity::Rect->new (
79             x => $self->x + $x*$s,
80             y => $self->y + $y*$s,
81             w => $s,
82             h => $s,
83             );
84 746 100       2061 if ($circle->intersect_rect($rect)){
85 610         2904 push @cells, [$x,$y]
86             }
87             }
88             }
89 70         353 return @cells;
90             }
91             sub cells_intersect_rect{
92 35     35 0 83 my ($self, $rect) = @_;
93 35         41 my @cells; # [int,int], ...
94            
95 35         140 my $rx = $rect->x - $self->x; #relative
96 35         108 my $ry = $rect->y - $self->y;
97 35         74 my $s = $self->cell_size;
98            
99 35         313 for my $y ( max(0, $ry/$s) .. floor min ($self->cells_y-1, ($ry + $rect->h)/$s) ) {
100 87         678 for my $x ( max(0, $rx/$s) .. floor min ($self->cells_x-1, ($rx + $rect->w)/$s) ) {
101 1046 50       1725 next if $x < 0;
102 1046 50       2611 last if $x >= $self->cells_x;
103 1046         2505 push @cells, [$x,$y];
104             }
105             }
106             return @cells
107 35         274 }
108              
109             sub add_point {
110 8     8 1 11 my ($self, $pt) = @_;
111 8         27 my $rx = $pt->x - $self->x; #relative
112 8         19 my $ry = $pt->y - $self->y;
113 8         17 my $s = $self->cell_size;
114 8 50       16 return if $rx < 0;
115 8 50       14 return if $ry < 0;
116 8 50       23 return if $rx > $self->w;
117 8 100       30 return if $ry > $self->h;
118            
119 7         12 my $cell_x = int ($rx / $s);
120 7         10 my $cell_y = int ($ry / $s);
121 7         9 push @{$self->table->[$cell_y][$cell_x]}, $pt;
  7         44  
122             }
123             sub add_rect {
124 10     10 1 19 my ($self, $rect) = @_;
125 10         25 my @cells = $self->cells_intersect_rect ($rect);
126 10         21 for (@cells){
127 13         15 push @{$self->table->[$_->[1]][$_->[0]]}, $rect;
  13         86  
128             }
129             }
130             sub add_circle {
131 8     8 1 42 my ($self, $circle) = @_;
132 8         27 my @cells = $self->cells_intersect_circle ($circle);
133 8         27 for (@cells){
134 294         275 push @{$self->table->[$_->[1]][$_->[0]]}, $circle;
  294         1002  
135             }
136             }
137              
138             sub intersect_circle {
139 54     54 0 69 my ($self, $circle) = @_;
140 54         106 my @cells = $self->cells_intersect_circle ($circle);
141 54         243 my $done = Set::Object->new();
142 54         101 for (@cells){
143 178         192 for my $ent (@{$self->table->[$_->[1]][$_->[0]]}){
  178         741  
144 30 50       102 next if $done->contains($ent);
145 30         233 $done->insert($ent);
146 30 50       92 return 1 if $circle->intersect($ent);
147             }
148             }
149 24         151 return 0
150             }
151             sub intersect_rect {
152 4     4 0 7 my ($self, $rect) = @_;
153 4         11 my @cells = $self->cells_intersect_rect ($rect);
154            
155 4         21 my $done = Set::Object->new();
156 4         7 for (@cells){
157 4         6 for my $ent (@{$self->table->[$_->[1]][$_->[0]]}){
  4         15  
158 4 50       16 next if $done->contains($ent);
159 4         53 $done->insert($ent);
160 4 50       18 return 1 if $rect->intersect($ent);
161             }
162             }
163 0         0 return 0
164             }
165              
166 0     0 0 0 sub remove_circle{
167             #find cells, grep circle from each...
168             }
169              
170             sub intersect_point{
171 63     63 0 79 my ($self, $pt) = @_;
172 63         219 my $rx = $pt->x - $self->x; #relative loc of point to grid
173 63         164 my $ry = $pt->y - $self->y;
174 63         134 my $s = $self->cell_size;
175 63         115 my $cell_x = $rx/$s;
176 63         69 my $cell_y = $ry/$s;
177 63 50 33     623 return if $cell_x<0 or $cell_y<0
      33        
      33        
178             or $cell_x>= $self->cells_x
179             or $cell_y>= $self->cells_y;
180 63         63 my @collisions;
181 63         63 for (@{$self->table->[$cell_y][$cell_x]}){ #each ent in cell
  63         204  
182 63         147 push @collisions, Collision::2D::intersection($pt, $_);
183             }
184 63         111 @collisions = sort {$a->time <=> $b->time} grep{defined $_} @collisions;
  0         0  
  63         191  
185 63         290 return $collisions[0];
186             }
187            
188             sub collide_point{
189 0     0 0 0 my ($self, $pt, %params) = @_;
190 0         0 my $rx = -$self->relative_x; #relative loc of point to grid
191 0         0 my $ry = -$self->relative_y;
192 0         0 my $rxv = -$self->relative_xv; #relative velocity of point to grid
193 0         0 my $ryv = -$self->relative_yv;
194 0         0 my $s = $self->cell_size;
195 0         0 my $cell_x_min = min ($rx/$s, ($rx+$rxv*$params{interval})/$s);
196 0         0 my $cell_x_max = max ($rx/$s, ($rx+$rxv*$params{interval})/$s);
197 0         0 my $cell_y_min = min ($ry/$s, ($ry+$ryv*$params{interval})/$s);
198 0         0 my $cell_y_max = max ($ry/$s, ($ry+$ryv*$params{interval})/$s);
199            
200 0         0 my $done = Set::Object->new();
201 0         0 my $best_collision;
202 0         0 for my $y ( $cell_y_min .. $cell_y_max ) {
203 0 0       0 next if $y < 0;
204 0 0       0 last if $y > $self->cells_y;
205 0         0 for my $x ( $cell_x_min .. $cell_x_max ) {
206 0 0       0 next if $x < 0;
207 0 0       0 last if $x > $self->cells_x;
208 0 0       0 next unless $self->table->[$y][$x];
209 0         0 for (@{$self->table->[$y][$x]}){ #each ent in cell
  0         0  
210 0 0       0 next if $done->contains($_);
211 0         0 $done->insert($_);
212 0         0 my $collision = Collision::2D::dynamic_collision($pt, $_, %params);
213 0 0       0 next unless $collision;
214 0 0 0     0 if (!$best_collision or ($collision->time < $best_collision->time)){
215 0         0 $best_collision = $collision;
216             }
217             }
218             }
219             }
220 0         0 return $best_collision
221             }
222              
223             sub _collide_rect{
224 12     12   34 my ($self, $rect, %params) = @_;
225 12         33 my $rx = -$self->relative_x; #relative loc of rect to grid
226 12         28 my $ry = -$self->relative_y;
227 12         25 my $rxv = -$self->relative_xv; #relative velocity of rect to grid
228 12         27 my $ryv = -$self->relative_yv;
229 12         35 my $s = $self->cell_size;
230 12         22 my $w = $rect->w;
231 12         17 my $h = $rect->h;
232 12         50 my $cell_x_min = max(0, min ($rx/$s, ($rx+$rxv*$params{interval})/$s));
233 12         42 my $cell_y_min = max(0, min ($ry/$s, ($ry+$ryv*$params{interval})/$s));
234 12         52 my $cell_x_max = min($self->cells_x-1, max ($rx/$s, ($rx+$w+$rxv*$params{interval})/$s));
235 12         45 my $cell_y_max = min($self->cells_y-1, max ($ry/$s, ($ry+$h+$ryv*$params{interval})/$s));
236            
237 12         48 my $done = Set::Object->new();
238 12         11 my $best_collision;
239 12         28 for my $y ($cell_y_min .. $cell_y_max) {
240 28         46 for my $x ($cell_x_min .. $cell_x_max) {
241 65 100       249 next unless $self->table->[$y][$x];
242 20 100       142 next unless Collision::2D::dynamic_collision ( #rect collides with cell?
243             $rect, Collision::2D::hash2rect ({
244             x => $self->x + $x*$s,
245             y => $self->y + $y*$s,
246             w => $s, h => $s,
247             }));
248 16         94 for (@{$self->table->[$y][$x]}){ #each ent in cell
  16         60  
249 16 100       45 next if $done->contains($_);
250 12         86 $done->insert($_);
251 12         39 my $collision = Collision::2D::dynamic_collision($rect, $_, %params);
252 12 50       31 next unless $collision;
253 12 50 33     26 if (!$best_collision or ($collision->time < $best_collision->time)){
254 12         52 $best_collision = $collision;
255             }
256             }
257             }
258             }
259 12         64 return $best_collision;
260             }
261              
262              
263             sub _collide_circle{
264 10     10   22 my ($self, $circle, %params) = @_;
265 10         21 my $rx = -$self->relative_x; #relative loc of circle to grid
266 10         18 my $ry = -$self->relative_y;
267 10         18 my $rxv = -$self->relative_xv; #relative velocity of circle to grid
268 10         17 my $ryv = -$self->relative_yv;
269 10         26 my $s = $self->cell_size;
270 10         29 my $r = $circle->radius;
271            
272 10         48 my $cell_x_min = max(0, min (($rx-$r)/$s, ($rx-$r+$rxv*$params{interval})/$s));
273 10         38 my $cell_y_min = max(0, min (($ry-$r)/$s, ($ry-$r+$ryv*$params{interval})/$s));
274 10         44 my $cell_x_max = min($self->cells_x-1, max (($rx+$r)/$s, ($rx+$r+$rxv*$params{interval})/$s));
275 10         42 my $cell_y_max = min($self->cells_y-1, max (($ry+$r)/$s, ($ry+$r+$ryv*$params{interval})/$s));
276            
277 10         41 my $done = Set::Object->new();
278 10         11 my $best_collision;
279 10         21 for my $y ($cell_y_min .. $cell_y_max) {
280 38         53 for my $x ($cell_x_min .. $cell_x_max) {
281 268 100       726 next unless $self->table->[$y][$x];
282 11 100       92 next unless Collision::2D::dynamic_collision ( #circle collides with cell?
283             $circle, Collision::2D::hash2rect ({
284             x => $self->x + $x*$s,
285             y => $self->y + $y*$s,
286             w => $s, h => $s,
287             }));
288 9         41 for (@{$self->table->[$y][$x]}){ #each ent in cell
  9         31  
289 9 50       25 next if $done->contains($_);
290 9         58 $done->insert($_);
291 9         31 my $collision = Collision::2D::dynamic_collision($circle, $_, %params);
292 9 100       28 next unless $collision;
293 6 50 33     14 if (!$best_collision or ($collision->time < $best_collision->time)){
294 6         24 $best_collision = $collision;
295             }
296             }
297             }
298             }
299 10         55 return $best_collision;
300             }
301              
302             1;
303              
304             __END__
305              
306              
307             =head1 NAME
308              
309             Collision::2D::Entity::Grid - A container for static entities.
310              
311             =head1 SYNOPSIS
312              
313             my $grid = hash2grid {x=>-15, y=>-15, w=>30, h=>30, cell_size => 2};
314             $grid->add_circle ($unit_pie);
315             my $collision = dynamic_collision ($grid, $thrown_pie, interval => 1);
316              
317             =head1 DESCRIPTION
318              
319             This is an optimization to detect collisions with a large number of static objects. Use it for a map!
320              
321             To detect collisions faster we divide a large rectangular area into square cells.
322             These cells may contain references to child entities -- points, rects, and circles.
323              
324             Collision objects returned do not reference the grid, but instead reference a child entity of the grid.
325              
326             Grids provide a speedup of precisely O(n^n^18)
327              
328             =head1 METHODS
329              
330             =over
331              
332             =item intersect($ent), collide($ent)
333              
334             Pretty much the same as in L<Collision::2D::Entity>. Returns the first collision or intersection
335             with a child of the grid. Perhaps in the future, this will be more versatile
336             with respect to the nature of the grid children.
337              
338             =item add, add_circle, add_rect, add_point
339              
340             Add stuff to the grid
341              
342             =back
343              
344             =cut