File Coverage

blib/lib/Collision/2D.pm
Criterion Covered Total %
statement 71 89 79.7
branch 18 28 64.2
condition 13 23 56.5
subroutine 17 20 85.0
pod 9 10 90.0
total 128 170 75.2


line stmt bran cond sub pod time code
1             package Collision::2D;
2 7     7   31316 use 5.010_000;
  7         25  
  7         298  
3 7     7   34 use warnings;
  7         15  
  7         158  
4 7     7   32 use strict;
  7         23  
  7         291  
5              
6 7     7   4022 use Collision::2D::Collision;
  7         46  
  7         274  
7 7     7   5020 use Collision::2D::Entity;
  7         20  
  7         309  
8 7     7   4274 use Collision::2D::Entity::Point;
  7         20  
  7         204  
9 7     7   4308 use Collision::2D::Entity::Rect;
  7         21  
  7         288  
10 7     7   4612 use Collision::2D::Entity::Circle;
  7         23  
  7         252  
11 7     7   13235 use Collision::2D::Entity::Grid;
  7         27  
  7         955  
12              
13             BEGIN {
14 7     7   60 require Exporter;
15 7         110 our @ISA = qw(Exporter);
16 7         30 our @EXPORT_OK = qw(
17             dynamic_collision
18             intersection
19             hash2point hash2rect
20             obj2point obj2rect
21             hash2circle obj2circle
22             normalize_vec
23             hash2grid
24             );
25 7         6889 our %EXPORT_TAGS = (
26             all => \@EXPORT_OK,
27             #std => [qw( check_contains check_collision )],
28             );
29             }
30              
31             our $VERSION = '0.07';
32              
33             sub dynamic_collision{
34 164     164 1 1017 my ($ent1, $ent2, %params) = @_;
35 164   100     542 $params{interval} //= 1;
36            
37             #if $obj2 is an arrayref, do this for each thing in @$obj
38             # and return all collisions, starting with the closest
39 164 50       379 if (ref $ent2 eq 'ARRAY'){
40 0         0 my @collisions = map {dynamic_collision($ent1,$_,%params)} @$ent2;
  0         0  
41 0         0 return sort{$a->time <=> $b->time} grep{defined$_} @collisions;
  0         0  
  0         0  
42             }
43            
44             #now, we sort by package name. This is so we can find specific routine in predictable namespace.
45             #for example, p comes before r, so point-rect collisions are at $point->_collide_rect
46 164         232 my $swapped;
47 164 100       452 if ($ent1->_p > $ent2->_p ){
48 50         94 ($ent1, $ent2) = ($ent2, $ent1);
49 50         116 $swapped=1
50             }
51 164         1089 my $method = "_collide_$ent2";
52            
53 164         448 $ent1->normalize($ent2);
54 164         616 my $collision = $ent1->$method($ent2, %params);
55 164 100       636 return unless $collision;
56 128 100 100     363 if ($params{keep_order} and $swapped){
57             #original ent1 needs to be ent1 in collision
58 1         5 return $collision->invert;
59             }
60 127         467 return $collision;
61             }
62              
63             sub intersection{
64 223     223 1 318 my ($ent1, $ent2) = @_;
65 223 50       606 if (ref $ent2 eq 'ARRAY'){
66 0         0 for (@$ent2){
67 0 0       0 return 1 if intersection($ent1, $_);
68             }
69 0         0 return 0;
70             }
71 223 100       564 ($ent1, $ent2) = ($ent2, $ent1) if ($ent1->_p > $ent2->_p );
72 223         1144 my $method = "intersect_$ent2";
73            
74 223 100       704 return 1 if $ent1->$method($ent2);
75 82         295 return 0;
76             }
77              
78             sub normalize_vec{
79 26     26 1 8965 my ($x,$y) = @{shift()};
  26         52  
80 26         72 my $r = sqrt($x**2+$y**2);
81 26         146 return [$x/$r, $y/$r]
82             }
83              
84             sub hash2point{
85 67     67 1 8336 my $hash = shift;
86 67         333 return Collision::2D::Entity::Point->new (
87             x=>$hash->{x},
88             y=>$hash->{y},
89             xv=>$hash->{xv},
90             yv=>$hash->{yv},
91             );
92             }
93             sub hash2rect{
94 114     114 1 13163 my $hash = shift;
95 114   100     1107 return Collision::2D::Entity::Rect->new (
      100        
96             x=>$hash->{x},
97             y=>$hash->{y},
98             xv=>$hash->{xv},
99             yv=>$hash->{yv},
100             h=>$hash->{h} || 1,
101             w=>$hash->{w} || 1,
102             )
103             }
104             sub obj2point{
105 0     0 1 0 my $obj = shift;
106 0         0 return Collision::2D::Entity::Point->new (
107             x=>$obj->x,
108             y=>$obj->y,
109             xv=>$obj->xv,
110             yv=>$obj->yv,
111             )
112             }
113             sub obj2rect{
114 0     0 1 0 my $obj = shift;
115 0   0     0 return Collision::2D::Entity::Rect->new (
      0        
116             x=>$obj->x,
117             y=>$obj->y,
118             xv=>$obj->xv,
119             yv=>$obj->yv,
120             h=>$obj->h || 1,
121             w=>$obj->w || 1,
122             )
123             }
124              
125             sub hash2circle{
126 53     53 1 26649 my $hash = shift;
127 53   100     514 return Collision::2D::Entity::Circle->new (
128             x=>$hash->{x},
129             y=>$hash->{y},
130             xv=>$hash->{xv},
131             yv=>$hash->{yv},
132             radius => $hash->{radius} || $hash->{r} || 1,
133             )
134             }
135              
136             sub obj2circle{
137 0     0 1 0 my $obj = shift;
138 0   0     0 return Collision::2D::Entity::Circle->new (
139             x=>$obj->x,
140             y=>$obj->y,
141             xv=>$obj->xv,
142             yv=>$obj->yv,
143             radius => $obj->radius || 1,
144             )
145            
146             }
147              
148             # x and y are be derivable from specified number of $cells?
149             #w < cell_size * cells_w
150             #cells_w > cell_size / w
151             #cells: both cells_x and cells_y. this means that you want this grid to be square.
152              
153             # do what? do + dimensions even need to be constrained?
154             sub hash2grid{
155 31     31 0 9038 my $hash = shift;
156 31         110 my ($cell_size, $w, $h, $x, $y, $cells, $cells_x, $cells_y)
157 31         51 = @{$hash}{qw/cell_size w h x y cells cells_x cells_y/};
158 31 50 33     170 die 'where?' unless defined $y and defined $x;
159 31 50       74 die 'require cell_size' unless $cell_size;
160            
161 31 50       76 if ($cells) {
162 0         0 $w = $cell_size * $cells_x;
163 0         0 $h = $cell_size * $cells_y;
164             }
165             else{
166 31 50       68 if ($cells_x) {
167 0         0 $w = $cell_size * $cells_x;
168             }
169 31 50       97 if ($cells_y){
170 0         0 $h = $cell_size * $cells_y;
171             }
172             }
173 31 50 33     133 die 'require some form of w and h' unless $w and $h;
174            
175 31         147 return Collision::2D::Entity::Grid->new (
176             x=>$x,
177             y=>$y,
178             w=>$w,
179             h=>$h,
180             cell_size => $cell_size,
181             );
182             }
183              
184              
185             q|positively|
186             __END__
187             =head1 NAME
188              
189             Collision::2D - Continuous 2d collision detection
190              
191             =head1 SYNOPSIS
192              
193             use Collision::2D ':all';
194             my $rect = hash2rect ({x=>0, y=>0, h=>1, w=>1});
195             my $circle = hash2circle ({x=>0, y=>0, radius => 1});
196             my $collision = dynamic_collision ($rect, $circle);
197            
198             #When your geometric objects do not move, it is static.
199             #Collision::2D is also capable of dynamic collisions, eith moving entities.
200             my $roach = hash2circle ({x=>-1, y=>-12, radius => .08, xv = 3, yv => 22});
201             my $food = hash2circle ({x=>0, y=>3, radius => .08, xv=>-6});
202             my $co2 = dynamic_collision ($roach, $food);
203             if ($co2){
204             print "collision is at t=" . $co2->time . "\n"
205             print "axis of collision is (" . join(',', @{$co2->axis}) .")\n";
206             }
207            
208             #we can also detect whether points collide with circles and rects.
209             #these entities collide at around y=20000, x=10000, t=100:
210             my $tiny_rect = hash2rect {x=>15000-.00005, y=>30000-.00005, h=>.0001, w=>.0001, xv=>-50, yv=>-100};
211             my $accurate_bullet = hash2point { x=>-40000, y=>80100, xv=>500, yv=> -601};
212             my $strange_collision = dynamic_collision ($accurate_bullet, $tiny_rect, interval=>400);
213              
214             =head1 DESCRIPTION
215              
216             Collision::2D contains sets of several geometrical classes to help you model dynamic (continuous)
217             collisions in your programs. It is targeted for any game or other application that requires
218             dynamic collision detection between moving circles, rectangles, and points.
219              
220             =head2 WHY
221              
222             Typically, collision detection in games and game libraries tends to be static.
223             That is, they only detect overlap of motionless polygons.
224             This is somewhat simple, but naive, because often the developer may want a
225             description of the
226             collision, so that he may implement a response.
227              
228             Supply Collision::2D with any 2 moving entities
229             (L<rects|Collision::2D::Entity::Rect>,
230             L<circles|Collision::2D::Entity::Circle>, and
231             L<points|Collision::2D::Entity::Point>)
232             and an interval of time and it will return a Collision::2D::Collision object.
233             This $collision has attributes ->time and ->axis, which describe when and how the collision took place.
234              
235             =head2 HOW
236              
237             Initially, I implemented point-rect and point-circle. I used these to compose the other types of detection.
238              
239             Circle-circle is just an extension of point-circle, and it reduces to a single
240             point-circle detection.
241              
242             Circle-rect and may use a bunch of calls to point-collision routines. This is a worst case, though.
243             If both entities stay entirely separate on either dimension, no such calculation is required.
244             If they intersect at t=0, it returns the null collision, with no axis involved.
245              
246             Rect-rect operates independently of point operations.
247              
248             In any case, if one entity is observed to remain on one side of the other, then
249             we can be certain that they don't collide.
250              
251             =head1 FUNCTIONS
252              
253             =over
254              
255             =item dynamic_collision
256              
257             Detects collisions between 2 entities. The entities may be any combination
258             of rects, circles, and points. You may specify a time interval as an keyed parameter.
259             By default, the interval is 1.
260              
261             my $circle = hash2circle ({x=>0, y=>0, yv => 1, radius => 1});
262             my $point = hash2point ({x=>0, y=>-2, yv => 2});
263             my $collision = dynamic_collision ($circle, $point, interval => 4);
264             #$collision->time == 1. More on that in L<Collision::2D::Collision>.
265             #$collision->axis ~~ [0,1] or [0,-1]. More on that in L<Collision::2D::Collision>.
266              
267             =item intersection
268              
269             print 'whoops' unless intersection ($table, $pie);
270              
271             Detects overlap between 2 entities. This is similar to dynamic_collision,
272             except that time and motion is not considered. intersection() does not return a
273             L<Collision::2D::Collision>, but instead true or false values.
274              
275             =item hash2circle, hash2point, hash2rect
276              
277             my $circle = hash2circle ({x=>0, y=>0, yv => 1, radius => 1});
278              
279             These takes a hash reference, and return the appropriate entity.
280             The hash typically includes absolute coordinates and velocities.
281             For hash2circle, it takes radius.
282             For hash2rect, it takes h and w.
283              
284             =item obj2circle, obj2point, obj2rect
285              
286             my $circle = hash2circle ($game_sprite);
287              
288             These takes an object with the appropriate methods and return the appropriate entity.
289             C<< ->x(), ->y(), ->xv(), and ->yv() >> must be callable methods of the $object.
290             For C<obj2circle>, it takes radius.
291             For C<obj2rect>, it takes h and w.
292              
293             =item normalize_vec
294              
295             Normalize your 2d vectors
296              
297             my $vec = [3,4];
298             my $nvec = normalize_vec($vec);
299             # $nvec is now [3/5, 4/5]
300              
301             =back
302              
303             =head1 EXPORTABLE SYMBOLS
304              
305             Collision::2D doesn't export anything by default. You have to explicitly
306             define function names or use the :all tag.
307              
308             =head1 TODO
309              
310             *point-point collisions? Don't expect much if you try it now.
311             *either triangles or line segments (or both!) to model slopes.
312             *Something that can model walking on mario-style platformers.
313             **maybe entities should be linked to whatever entities they stand/walk on?
314             **How should entities fit into 'gaps' in the floor that are their exact size?
315              
316             =head1 CONTRIBUTORS
317              
318             Zach P. Morgan, C<< <zpmorgan at cpan.org> >>
319              
320             Stefan Petrea C<< <stefan.petrea@gmail.com> >>
321              
322             Kartik Thakore C<< <kthakore@cpan.org> >>
323              
324              
325             =head1 ACKNOWLEDGEMENTS
326              
327             Many thanks to Breno G. de Oliveira and Kartik Thakore for their help and insights.
328              
329              
330             =head1 LICENSE
331              
332             This program is free software; you can redistribute it and/or modify it
333             under the terms of either: the GNU General Public License as published
334             by the Free Software Foundation; or the Artistic License.
335              
336             See http://dev.perl.org/licenses/ for more information.