File Coverage

blib/lib/Collision/2D/Entity/Circle.pm
Criterion Covered Total %
statement 120 125 96.0
branch 48 52 92.3
condition 59 67 88.0
subroutine 12 13 92.3
pod 0 5 0.0
total 239 262 91.2


line stmt bran cond sub pod time code
1             package Collision::2D::Entity::Circle;
2 7     7   40 use strict;
  7         13  
  7         254  
3 7     7   39 use warnings;
  7         12  
  7         715  
4              
5             require DynaLoader;
6             our @ISA = qw(DynaLoader Collision::2D::Entity);
7             bootstrap Collision::2D::Entity::Circle;
8              
9             #in a circle, x and y denote center.
10              
11 249     249   730 sub _p{2} #highish priority
12 7     7   38 use overload '""' => sub{'circle'};
  7     73   30  
  7         64  
  73         195  
13 0     0 0 0 sub typename{'circle'}
14              
15              
16             sub new{
17 179     179 0 690 my ($package, %params) = @_;
18 179   100     3257 my $self = __PACKAGE__->_new (
      100        
      100        
      100        
      100        
      100        
      100        
      100        
19             $params{x} || 0,
20             $params{y} || 0,
21             $params{xv} || 0,
22             $params{yv} || 0,
23             $params{relative_x} || 0,
24             $params{relative_y} || 0,
25             $params{relative_xv} || 0,
26             $params{relative_yv} || 0,
27             $params{radius},
28             );
29 179         500 return $self;
30             }
31              
32              
33             sub intersect_circle{
34 5     5 0 8 my ($self, $other) = @_;
35              
36             #sqrt is more expensive than square
37 5         76 return ($self->radius + $other->radius)**2 >
38             ($self->x - $other->x)**2 +
39             ($self->y - $other->y)**2;
40             }
41              
42              
43             sub intersect_point{
44 63     63 0 79 my ($self, $point) = @_;
45 63         625 return $self->radius**2 >
46             ($self->x - $point->x)**2 +
47             ($self->y - $point->y)**2;
48             }
49              
50             #both stationary
51             sub intersect_rect{
52 794     794 0 1013 my ($self, $rect) = @_;
53 794         1468 my $r = $self->radius;
54 794         1373 my $w = $rect->w;
55 794         1256 my $h = $rect->h;
56 794         2096 my $x = $self->x - $rect->x; #of self, relative to rect!
57 794         1897 my $y = $self->y - $rect->y; #of self, relative to rect!
58            
59            
60 794 100 100     6844 if ($x-$r > $w
      100        
      100        
61             or $y-$r > $h
62             or $x+$r < 0
63             or $y+$r < 0){
64             #warn "$x $y w: $w, h: $h, r: $r";
65 4         18 return 0
66             }
67 790 100       3128 return 1 if ($x**2 + $y**2) < $r**2;
68 437 100       1199 return 1 if (($x-$w)**2 + $y**2) < $r**2;
69 370 100       988 return 1 if (($x-$w)**2 + ($y-$h)**2) < $r**2;
70 312 100       742 return 1 if ($x**2 + ($y-$h)**2) < $r**2;
71             #detect 'imposition', whereall corner+side points are outside the other entity
72 282 100       1071 return 1 if (($x-$w/2)**2 + ($y-$h/2)**2) < $r**2;
73            
74 236         1133 for ([$x,$y-$r], [$x-$r,$y], [$x,$y+$r], [$x+$r,$y]){
75 800         979 my ($x,$y) = @$_;
76 800 100 100     4284 return 1 if $x>0 and $y>0
      100        
      100        
77             and $x<$w and $y<$h;
78             }
79 140         1013 return 0;
80             }
81              
82             sub _collide_rect{
83 44     44   97 my ($self, $rect, %params) = @_;
84 44         44 my @collisions;
85            
86             #my doing this we can consider $self to be stationary and $rect to be moving.
87             #this line segment is path of rect during this interval
88 44         95 my $r = $self->radius;
89 44         78 my $w = $rect->w;
90 44         89 my $h = $rect->h;
91 44         91 my $x1 = -$self->relative_x; #of rect!
92 44         133 my $x2 = $x1 - ($self->relative_xv * $params{interval});
93 44         87 my $y1 = -$self->relative_y;
94 44         104 my $y2 = $y1 - ($self->relative_yv * $params{interval});
95            
96             #now see if point starts and ends on one of 4 sides of this rect.
97             #probably worth it because most things don't collide with each other every frame
98 44 50 66     135 if ($x1 > $r and $x2 > $r ){
99             return
100 0         0 }
101 44 50 66     116 if ($x1+$w < -$r and $x2+$w < -$r){
102             return
103 0         0 }
104 44 100 100     144 if ($y1 > $r and $y2 > $r ){
105             return
106 1         4 }
107 43 50 66     121 if ($y1+$h < -$r and $y2+$h < -$r){
108             return
109 0         0 }
110 43 100       153 if (($x1+$w/2)**2 + ($y1+$h/2)**2 < $r**2) { #imposition?
111 3         18 return $self->null_collision($rect);
112             }
113            
114             #which of rect's 4 points should I consider?
115             # my @start_pts = ([$x1, $y1], [$x1+$w, $y1], [$x1+$w, $y1+$h], [$x1, $y1+$h]);
116             # my @end_pts = ([$x2, $y2], [$x2+$w, $y2], [$x2+$w, $y2+$h], [$x2, $y2+$h]);
117 40         282 my @pts = (
118             {x1 => $x1, y1 => $y1},
119             {x1 => $x1+$w, y1 => $y1},
120             {x1 => $x1+$w, y1 => $y1+$h},
121             {x1 => $x1, y1 => $y1+$h},
122             );
123 40         70 for (@pts){ #calc initial distance from center of circle
124 160         362 $_->{dist} = sqrt($_->{x1}**2 + $_->{y1}**2);
125             }
126 40         132 my $origin_point = Collision::2D::Entity::Point->new(
127             # x => 0,y => 0, #actually not used, since circle is normalized with respect to the point
128             );
129 40         118 @pts = sort {$a->{dist} <=> $b->{dist}} @pts;
  190         312  
130             #now detect null collision of closest rect corner
131 40         41 if (0 and $pts[0]{dist} < $r){
132             return $self->null_collision($rect)
133             }
134 40         78 for (@pts[0,1,2]){ #do this for 3 initially closest rect corners
135 120         635 my $new_relative_circle = Collision::2D::Entity::Circle->new(
136             # x => 0,y => 0, # used
137             relative_x => $_->{x1},
138             relative_y => $_->{y1},
139             relative_xv => -$self->relative_xv,
140             relative_yv => -$self->relative_yv,
141             radius => $self->radius,
142             );
143 120         268 my $collision = $new_relative_circle->_collide_point ($origin_point, interval=>$params{interval});
144 120 100       500 next unless $collision;
145             #$_->{collision} =
146 36         225 push @collisions, Collision::2D::Collision->new(
147             axis => $collision->axis,
148             time => $collision->time,
149             ent1 => $self,
150             ent2 => $rect,
151             );
152             }
153             #return unless @collisions;
154             #@collisions = sort {$a->time <=> $b->time} @collisions;
155             #return $collisions[0] if defined $collisions[0];
156            
157             # that looked at the rect corners. that was half of it.
158             # now look for collisions between a side of the circle
159             # and a side of the rect
160 40         47 my @circ_points; #these are relative coordinates to rect
161 40 100 66     119 if ($x1+$w < -$r and $x2+$w > -$r){
162             #add circle's left point
163 2         9 push @circ_points, [-$x1-$r,-$y1];
164             }
165 40 100 66     116 if ($x1 > $r and $x2 < $r){
166             #add circle's right point
167 13         32 push @circ_points, [-$x1+$r,-$y1];
168             }
169 40 100 66     111 if ($y1+$h < -$r and $y2+$h > -$r ){
170             #add circle's bottom point
171 8         19 push @circ_points, [-$x1,-$y1-$r];
172             }
173 40 100 66     168 if ($y1 > $r and $y2 < $r){
174             #add circle's top point
175 27         58 push @circ_points, [-$x1,-$y1+$r];
176             } # warn @{$circ_points[0]};
177 40         69 for (@circ_points){
178 50         251 my $rpt = Collision::2D::Entity::Point->new(
179             relative_x => $_->[0],
180             relative_y => $_->[1],
181             relative_xv => $self->relative_xv,
182             relative_yv => $self->relative_yv,
183             );
184 50         155 my $collision = $rpt->_collide_rect($rect, interval=>$params{interval});
185 50 100       147 next unless $collision;
186 34         169 push @collisions, new Collision::2D::Collision(
187             time => $collision->time,
188             axis => $collision->axis,
189             ent1 => $self,
190             ent2 => $rect,
191             );
192             }
193 40 100       106 return unless @collisions;
194 37         68 @collisions = sort {$a->time <=> $b->time} @collisions;
  37         110  
195             #warn join ',', @collisions;
196 37         282 return $collisions[0]
197             }
198              
199              
200              
201              
202             #ok, so normal circle is sqrt(x**2+y**2)=r
203             #and line is y=mx+b (invert line if line is vertical)
204             #to find their intersection on the x axis,
205             # sqrt(x**2 + (mx+b)**2) = r
206             # x**2 + (mx)**2 + mxb + b**2 = r**2
207             # (m**2+1)x**2 + (2mb)x + (b**2-r**2) = 0.
208             #solve using quadratic equation
209             # A=m**2+1
210             # B=2mb
211             # C=b**2-r**2
212             # roots (where circle intersects on the x axis) are at
213             # ( -B ± sqrt(B**2 - 4AC) ) / 2A
214             #Then, see which intercept, if any, is the closest after starting point
215             sub _collide_point{
216 157     157   307 my ($self, $point, %params) = @_;
217             #x1,etc. is the path of the point, relative to $self.
218             #it's probably easier to consider the point as stationary.
219 157         362 my $x1 = -$self->relative_x;
220 157         265 my $y1 = -$self->relative_y;
221 157         409 my $x2 = $x1 - $self->relative_xv * $params{interval};
222 157         376 my $y2 = $y1 - $self->relative_yv * $params{interval};
223            
224 157 100       568 if (($x1**2 + $y1**2) < $self->radius**2) {
225 4         27 return $self->null_collision($point);
226             }
227            
228 153 100 66     595 if ($x2-$x1 == 0 or abs(($y2-$y1)/($x2-$x1)) > 100) { #a bit too vertical for my liking. so invert.
229 67 50       119 if ($y2-$y1 == 0){ #relatively motionless.
230             return
231 0         0 }
232 67         109 ($x1, $y1) = ($y1,$x1);
233 67         108 ($x2, $y2) = ($y2,$x2);
234             }
235            
236             #now do quadratic
237 153         268 my $slope = ($y2-$y1)/($x2-$x1);
238 153         183 my $y_intercept = $y1 - $slope*$x1;
239 153         195 my $A = $slope**2 + 1; #really?
240 153         187 my $B = 2 * $slope*$y_intercept;
241 153         332 my $C = $y_intercept**2 - $self->radius**2;
242 153         174 my @xi; #x component of intersections.
243 153         212 my $blah = $B**2 - 4*$A*$C;
244 153 100       378 return unless $blah>0;
245 82         85 $blah = sqrt($blah);
246 82         135 push @xi, (-$B + $blah ) / (2*$A);
247 82         116 push @xi, (-$B - $blah ) / (2*$A);
248             #keep intersections within segment
249 82 100 100     109 @xi = grep {($_>=$x1 and $_<=$x2) or ($_<=$x1 and $_>=$x2)} @xi;
  164   100     996  
250             #sort based on closeness to starting point.
251 82         224 @xi = sort {abs($a-$x1) <=> abs($b-$x1)} @xi;
  18         53  
252 82 100       245 return unless defined $xi[0];
253            
254             #get away from invertedness
255 56         120 my $time = $params{interval} * ($xi[0]-$x1) / ($x2-$x1);
256 56         167 my $x_at_t = $self->relative_x + $self->relative_xv*$time;
257 56         158 my $y_at_t = $self->relative_y + $self->relative_yv*$time;
258 56         123 my $axis = [-$x_at_t, -$y_at_t]; #vector from self to point
259            
260 56         219 my $collision = Collision::2D::Collision->new(
261             time => $time, axis => $axis,
262             ent1 => $self,
263             ent2 => $point,
264             );
265 56         191 return $collision;
266             }
267              
268             #Say, can't we just use the point algorithm by transferring the radius of one circle to the other?
269             sub _collide_circle{
270 6     6   29 my ($self, $other, %params) = @_;
271 6         62 my $double_trouble = Collision::2D::Entity::Circle->new(
272             relative_x => $self->relative_x,
273             relative_y => $self->relative_y,
274             relative_xv => $self->relative_xv,
275             relative_yv => $self->relative_yv,
276             radius => $self->radius + $other->radius,
277             #y=>0,x=>0, #these will not be used, as we're doing all relative calculations
278             );
279            
280 6         28 my $pt = Collision::2D::Entity::Point->new(
281             #y=>44,x=>44, #these willn't be used, as we're doing all relative calculations
282             );
283 6         29 my $collision = $double_trouble->_collide_point($pt, %params);
284 6 100       24 return unless $collision;
285            
286 5         39 return Collision::2D::Collision->new(
287             ent1 => $self,
288             ent2 => $other,
289             time => $collision->time,
290             axis => $collision->axis,
291             #axis => [-$collision->axis->[0], -$collision->axis->[1]],
292             );
293             }
294              
295             1
296              
297             __END__
298             =head1 NAME
299              
300             Collision::2D::Entity::Circle - A circle entity.
301              
302             =head1 DESCRIPTION
303              
304             This is an entity with a radius.
305             Attributes x and y point to the center of the circle.
306              
307             =head1 ATTRIBUTES
308              
309             =head2 radius
310              
311             Each point on the circle is this distance from the center, at C<< ($circ->x, $circ->y) >>
312              
313             =head1 METHODS
314              
315             Anything in L<Collision::2D::Entity>.
316              
317             =head2 collide
318              
319             See L<< Collision::2D::Entity->collide|Collision::2D::Entity/collide >>
320              
321             print 'boom' if $circle->collide($rect);
322             print 'zing' if $circle->collide($circle);
323             print 'yotz' if $circle->collide($grid);
324            
325             =head2 intersect
326              
327             See L<< Collision::2D::Entity->intersect|Collision::2D::Entity/intersect >>
328              
329             print 'bam' if $circle->intersect($rect);
330             # etc..
331              
332              
333