implemented clip and clip_ip
[sdlgit/SDL_perl.git] / lib / SDL / Game / Rect.pm
CommitLineData
389e4b7b 1package SDL::Game::Rect;
2use strict;
3use warnings;
f193ea4b 4use Carp;
0abf088c 5use base 'SDL::Rect';
389e4b7b 6
7our $VERSION = '0.01';
8
9sub new {
10 my $class = shift;
0abf088c 11 my $x = shift || 0;
12 my $y = shift || 0;
13 my $w = shift || 0;
14 my $h = shift || 0;
15
43a05747 16 my $self = $class->SUPER::new($x, $y, $w, $h);
0abf088c 17 unless ($$self) {
f193ea4b 18 #require Carp;
19 croak SDL::GetError();
0abf088c 20 }
21 bless $self, $class;
22 return $self;
389e4b7b 23}
24
25#############################
26## extra accessors
27#############################
28sub bottom {
29 my ($self, $val) = (@_);
30 if (defined $val) {
0abf088c 31 $self->top($val - $self->height); # y = val - height
389e4b7b 32 }
0abf088c 33 return $self->top + $self->height; # y + height
389e4b7b 34}
35
36sub right {
37 my ($self, $val) = (@_);
38 if (defined $val) {
0abf088c 39 $self->left($val - $self->width); # x = val - width
389e4b7b 40 }
0abf088c 41 return $self->left + $self->width; # x + width
389e4b7b 42}
43
44sub centerx {
45 my ($self, $val) = (@_);
46 if (defined $val) {
0abf088c 47 $self->left($val - ($self->width >> 1)); # x = val - (width/2)
389e4b7b 48 }
0abf088c 49 return $self->left + ($self->width >> 1); # x + (width/2)
389e4b7b 50}
51
52sub centery {
53 my ($self, $val) = (@_);
54 if (defined $val) {
0abf088c 55 $self->top($val - ($self->height >> 1)); # y = val - (height/2)
389e4b7b 56 }
0abf088c 57 return $self->top + ($self->height >> 1); # y + (height/2)
389e4b7b 58}
59
60sub size {
61 my ($self, $w, $h) = (@_);
62
0abf088c 63 return ($self->width, $self->height) # (width, height)
389e4b7b 64 unless (defined $w or defined $h);
65
66 if (defined $w) {
0abf088c 67 $self->width($w); # width
389e4b7b 68 }
69 if (defined $h) {
0abf088c 70 $self->height($h); # height
389e4b7b 71 }
72}
73
74sub topleft {
75 my ($self, $y, $x) = (@_);
76
0abf088c 77 return ($self->top, $self->left) # (top, left)
389e4b7b 78 unless (defined $y or defined $x);
79
80 if (defined $x) {
0abf088c 81 $self->left($x); # left
389e4b7b 82 }
83 if (defined $y) {
0abf088c 84 $self->top($y); # top
389e4b7b 85 }
86 return;
87}
88
89sub midleft {
90 my ($self, $centery, $x) = (@_);
91
0abf088c 92 return ($self->top + ($self->height >> 1), $self->left) # (centery, left)
389e4b7b 93 unless (defined $centery or defined $x);
94
95 if (defined $x) {
0abf088c 96 $self->left($x); # left
389e4b7b 97 }
98 if (defined $centery) {
0abf088c 99 $self->top($centery - ($self->height >> 1)); # y = centery - (height/2)
389e4b7b 100 }
101 return;
102}
103
104sub bottomleft {
105 my ($self, $bottom, $x) = (@_);
106
0abf088c 107 return ($self->top + $self->height, $self->left) # (bottom, left)
389e4b7b 108 unless (defined $bottom or defined $x);
109
110 if (defined $x) {
0abf088c 111 $self->left($x); # left
389e4b7b 112 }
113 if (defined $bottom) {
0abf088c 114 $self->top($bottom - $self->height); # y = bottom - height
389e4b7b 115 }
116 return;
117}
118
119sub center {
120 my ($self, $centerx, $centery) = (@_);
121
0abf088c 122 return ($self->left + ($self->width >> 1), $self->top + ($self->height >> 1))
389e4b7b 123 unless (defined $centerx or defined $centery);
124
125 if (defined $centerx) {
0abf088c 126 $self->left($centerx - ($self->width >> 1)); # x = centerx - (width/2)
389e4b7b 127 }
128 if (defined $centery) {
0abf088c 129 $self->top($centery - ($self->height >> 1)); # y = centery - (height/2)
389e4b7b 130 }
131 return;
132}
133
134sub topright {
135 my ($self, $y, $right) = (@_);
136
0abf088c 137 return ($self->top, $self->left + $self->width) # (top, right)
389e4b7b 138 unless (defined $y or defined $right);
139
140 if (defined $right) {
0abf088c 141 $self->left($right - $self->width); # x = right - width
389e4b7b 142 }
143 if (defined $y) {
0abf088c 144 $self->top($y); # top
389e4b7b 145 }
146 return;
147}
148
149sub midright {
150 my ($self, $centery, $right) = (@_);
151
0abf088c 152 return ($self->top + ($self->height >> 1), $self->left + $self->width) # (centery, right)
389e4b7b 153 unless (defined $centery or defined $right);
154
155 if (defined $right) {
0abf088c 156 $self->left($right - $self->width); # x = right - width
389e4b7b 157 }
158 if (defined $centery) {
0abf088c 159 $self->top($centery - ($self->height >> 1)); # y = centery - (height/2)
389e4b7b 160 }
161 return;
162}
163
164sub bottomright {
165 my ($self, $bottom, $right) = (@_);
166
0abf088c 167 return ($self->top + $self->height, $self->left + $self->width) # (bottom, right)
389e4b7b 168 unless (defined $bottom or defined $right);
169
170 if (defined $right) {
0abf088c 171 $self->left($right - $self->width); # x = right - width
389e4b7b 172 }
173 if (defined $bottom) {
0abf088c 174 $self->top($bottom - $self->height); # y = bottom - height
389e4b7b 175 }
176 return;
177}
178
179sub midtop {
180 my ($self, $centerx, $y) = (@_);
181
0abf088c 182 return ($self->left + ($self->width >> 1), $self->top) # (centerx, top)
389e4b7b 183 unless (defined $centerx or defined $y);
184
185 if (defined $y) {
0abf088c 186 $self->top($y); # top
389e4b7b 187 }
188 if (defined $centerx) {
0abf088c 189 $self->left($centerx - ($self->width >> 1)); # x = centerx - (width/2)
389e4b7b 190 }
191 return;
192}
193
194sub midbottom {
195 my ($self, $centerx, $bottom) = (@_);
196
0abf088c 197 return ($self->left + ($self->width >> 1), $self->top + $self->height) # (centerx, bottom)
389e4b7b 198 unless (defined $centerx or defined $bottom);
199
200 if (defined $bottom) {
0abf088c 201 $self->top($bottom - $self->height); # y = bottom - height
389e4b7b 202 }
203 if (defined $centerx) {
0abf088c 204 $self->left($centerx - ($self->width >> 1)); # x = centerx - (width/2)
389e4b7b 205 }
206 return;
207}
208
5674d738 209###############################
210## methods ##
211###############################
212
213sub duplicate {
214}
215
216sub copy {
217 my $self = shift;
218 return $self->new(
219 -top => $self->top,
220 -left => $self->left,
221 -width => $self->width,
222 -height => $self->height,
223 );
224}
225
226sub move {
227 my ($self, $x, $y) = (@_);
228 if (not defined $x or not defined $y) {
f193ea4b 229 #require Carp;
230 croak "must receive x and y positions as argument";
5674d738 231 }
232 return $self->new(
233 -top => $self->top + $y,
234 -left => $self->left + $x,
235 -width => $self->width,
236 -height => $self->height,
237 );
238}
239
240sub move_ip {
241 my ($self, $x, $y) = (@_);
242 if (not defined $x or not defined $y) {
f193ea4b 243 #require Carp;
244 croak "must receive x and y positions as argument";
5674d738 245 }
246 $self->x($self->x + $x);
247 $self->y($self->y + $y);
248
249 return;
250}
251
252sub inflate {
253 my ($self, $x, $y) = (@_);
254 if (not defined $x or not defined $y) {
f193ea4b 255 #require Carp;
256 croak "must receive x and y positions as argument";
5674d738 257 }
258
259 return $self->new(
260 -left => $self->left - ($x / 2),
261 -top => $self->top - ($y / 2),
262 -width => $self->width + $x,
263 -height => $self->height + $y,
264 );
265}
266
267sub inflate_ip {
268 my ($self, $x, $y) = (@_);
269 if (not defined $x or not defined $y) {
f193ea4b 270 #require Carp;
271 croak "must receive x and y positions as argument";
5674d738 272 }
273
274 $self->x( $self->x - ($x / 2) );
275 $self->y( $self->y - ($y / 2) );
276
277 $self->w( $self->w + $x );
278 $self->h( $self->h + $y );
279}
280
57f262e8 281sub _get_clamp_coordinates {
282 my ($self_pos, $self_len, $rect_pos, $rect_len) = (@_);
283
284 if ($self_len >= $rect_len) {
285 return $rect_pos + ($rect_len / 2) - ($self_len / 2);
286 }
287 elsif ($self_pos < $rect_pos) {
288 return $rect_pos;
289 }
290 elsif ( ($self_pos + $self_len) > ($rect_pos + $rect_len) ) {
291 return $rect_pos + $rect_len - $self_len;
292 }
293 else {
294 return $self_pos;
295 }
296}
297
298sub clamp {
299 my ($self, $rect) = (@_);
300
301 unless ($rect->isa('SDL::Rect')) {
302 croak "must receive an SDL::Rect-based object";
303 }
304
305 my $x = _get_clamp_coordinates($self->x, $self->w, $rect->x, $rect->w);
306 my $y = _get_clamp_coordinates($self->y, $self->h, $rect->y, $rect->h);
307
308 return $self->new($x, $y, $self->w, $self->h);
309}
310
311sub clamp_ip {
312 my ($self, $rect) = (@_);
313
314 unless ($rect->isa('SDL::Rect')) {
315 croak "must receive an SDL::Rect-based object";
316 }
317
318 my $x = _get_clamp_coordinates($self->x, $self->w, $rect->x, $rect->w);
319 my $y = _get_clamp_coordinates($self->y, $self->h, $rect->y, $rect->h);
320
321 $self->x($x);
322 $self->y($y);
323
324 return;
325}
389e4b7b 326
74f8c259 327sub _get_intersection_coordinates {
328 my ($self, $rect) = (@_);
329 my ($x, $y, $w, $h);
330
331INTERSECTION:
332 {
333 ### Left
334 if (($self->x >= $rect->x) && ($self->x < ($rect->x + $rect->w))) {
335 $x = $self->x;
336 }
337 elsif (($rect->x >= $self->x) && ($rect->x < ($self->x + $self->w))) {
338 $x = $rect->x;
339 }
340 else {
341 last INTERSECTION;
342 }
343
344 ## Right
345 if ((($self->x + $self->w) > $rect->x) && (($self->x + $self->w) <= ($rect->x + $rect->w))) {
346 $w = ($self->x + $self->w) - $x;
347 }
348 elsif ((($rect->x + $rect->w) > $self->x) && (($rect->x + $rect->w) <= ($self->x + $self->w))) {
349 $w = ($rect->x + $rect->w) - $x;
350 }
351 else {
352 last INTERSECTION;
353 }
354
355 ## Top
356 if (($self->y >= $rect->y) && ($self->y < ($rect->y + $rect->h))) {
357 $y = $self->y;
358 }
359 elsif (($rect->y >= $self->y) && ($rect->y < ($self->y + $self->h))) {
360 $y = $rect->y;
361 }
362 else {
363 last INTERSECTION;
364 }
365
366 ## Bottom
367 if ((($self->y + $self->h) > $rect->y) && (($self->y + $self->h) <= ($rect->y + $rect->h))) {
368 $h = ($self->y + $self->h) - $y;
369 }
370 elsif ((($rect->y + $rect->h) > $self->y) && (($rect->y + $rect->h) <= ($self->y + $self->h))) {
371 $h = ($rect->y + $rect->h) - $y;
372 }
373 else {
374 last INTERSECTION;
375 }
376
377 return ($x, $y, $w, $h);
378 }
379
380 # if we got here, the two rects do not intersect
381 return ($self->x, $self->y, 0, 0);
382
383}
384
385sub clip {
386 my ($self, $rect) = (@_);
387
388 unless ($rect->isa('SDL::Rect')) {
389 croak "must receive an SDL::Rect-based object";
390 }
391
392 my ($x, $y, $w, $h) = _get_intersection_coordinates($self, $rect);
393
394 return $self->new($x, $y, $w, $h);
395}
396
397sub clip_ip {
398 my ($self, $rect) = (@_);
399
400 unless ($rect->isa('SDL::Rect')) {
401 croak "must receive an SDL::Rect-based object";
402 }
403
404 my ($x, $y, $w, $h) = _get_intersection_coordinates($self, $rect);
405
406 $self->x($x);
407 $self->y($y);
408 $self->w($w);
409 $self->h($h);
410
411 return;
412}
413
389e4b7b 41442;
415__END__
416
417=head1 NAME
418
419SDL::Game::Rect - SDL::Game object for storing and manipulating rectangular coordinates
420
421=head1 SYNOPSIS
422
423
424=head1 DESCRIPTION
425
426C<< SDL::Game::Rect >> object are used to store and manipulate rectangular areas. Rect objects are created from a combination of left (or x), top (or y), width (or w) and height (or h) values, just like raw C<< SDL::Rect objects >>.
427
428All C<< SDL::Game::Rect >> methods that change either position or size of a Rect return B<a new copy> of the Rect with the affected changes. The original Rect is B<not> modified. If you wish to modify the current Rect object, you can use the equivalent "in-place" methods that do not return but instead affects the original Rect. These "in-place" methods are denoted with the "ip" suffix. Note that changing a Rect's attribute is I<always> an in-place operation.
429
430
431=head2 ATTRIBUTES
432
433All Rect attributes are acessors, meaning you can get them by name, and set them by passing a value:
434
435 $rect->left(15);
436 $rect->left; # 15
437
438The Rect object has several attributes which can be used to resize, move and align the Rect.
439
440
441=over 4
442
443=item * width, w - gets/sets object's width
444
445=item * height, h - gets/sets object's height
446
447=item * left, x - moves the object left position to match the given coordinate
448
449=item * top, y - moves the object top position to match the given coordinate
450
451=item * bottom - moves the object bottom position to match the given coordinate
452
453=item * right - moves the object right position to match the given coordinate
454
455=item * centerx - moves the object's horizontal center to match the given coordinate
456
457=item * centery - moves the object's vertical center to match the given coordinate
458
459=back
460
461Some of the attributes above can be fetched or set in pairs:
462
463 $rect->topleft(10, 15); # top is now 10, left is now 15
464
465 my ($width, $height) = $rect->size;
466
467
468=over 4
469
470=item * size - gets/sets object's size (width, height)
471
472=item * topleft - gets/sets object's top and left positions
473
474=item * midleft - gets/sets object's vertical center and left positions
475
476=item * bottomleft - gets/sets object's bottom and left positions
477
478=item * center - gets/sets object's center (horizontal(x), vertical(y))
479
480=item * topright - gets/sets object's top and right positions
481
482=item * midright - gets/sets object's vertical center and right positions
483
484=item * bottomright - gets/sets object's bottom and right positions
485
486=item * midtop - gets/sets object's horizontal center and top positions
487
488=item * midbottom - gets/sets object's horizontal center and bottom positions
489
490=back
491
492
493=head2 METHODS
494
495Methods denoted as receiving Rect objects can receive either C<<SDL::Game::Rect>> or raw C<<SDL::Rect>> objects.
496
497=head3 new ($left, $top, $width, $height)
498
499Returns a new Rect object with the given coordinates. If any value is omitted (by passing undef), 0 is used instead.
500
501=head3 copy
502
503=head3 duplicate
504
505Returns a new Rect object having the same position and size as the original
506
507=head3 move(x, y)
508
509Returns a new Rect that is moved by the given offset. The x and y arguments can be any integer value, positive or negative.
510
511=head3 move_ip(x, y)
512
513Same as C<<move>> above, but moves the current Rect in place and returns nothing.
514
515=head3 inflate(x, y)
516
517Grows or shrinks the rectangle. Returns a new Rect with the size changed by the given offset. The rectangle remains centered around its current center. Negative values will return a shrinked rectangle instead.
518
519=head3 inflate_ip(x, y)
520
521Same as C<<inflate>> above, but grows/shrinks the current Rect in place and returns nothing.
522
523=head3 clamp($rect)
524
525Returns a new Rect moved to be completely inside the Rect object passed as an argument. If the current Rect is too large to fit inside the passed Rect, it is centered inside it, but its size is not changed.
526
527=head3 clamp_ip($rect)
528
529Same as C<<clamp>> above, but moves the current Rect in place and returns nothing.
530
531=head3 clip($rect)
532
74f8c259 533Returns a new Rect with the intersection between the two Rect objects, that is, returns a new Rect cropped to be completely inside the Rect object passed as an argument. If the two rectangles do not overlap to begin with, a Rect with 0 size is returned, in the original Rect's (x,y) coordinates.
389e4b7b 534
535=head3 clip_ip($rect)
536
74f8c259 537Same as C<<clip>> above, but crops the current Rect in place and returns nothing. As the original method, the Rect becomes zero-sized if the two rectangles do not overlap to begin with, retaining its (x, y) coordinates.
389e4b7b 538
539=head3 union($rect)
540
541Returns a new rectangle that completely covers the area of the current Rect and the one passed as an argument. There may be area inside the new Rect that is not covered by the originals.
542
543=head3 union_ip($rect)
544
545Same as C<<union>> above, but resizes the current Rect in place and returns nothing.
546
547=head3 unionall( [$rect1, $rect2, ...] )
548
549Returns the union of one rectangle with a sequence of many rectangles, passed as an ARRAY REF.
550
551=head3 unionall_ip( [$rect1, $rect2, ...] )
552
553Same as C<<unionall>> above, but resizes the current Rect in place and returns nothing.
554
555=head3 fit($rect)
556
557Returns a new Rect moved and resized to fit the Rect object passed as an argument. The aspect ratio of the original Rect is preserved, so the new rectangle may be smaller than the target in either width or height.
558
559=head3 fit_ip($rect)
560
561Same as C<<fit>> above, but moves/resizes the current Rect in place and returns nothing.
562
563=head3 normalize
564
565Corrects negative sizes, flipping width/height of the Rect if they have a negative size. No repositioning is made so the rectangle will remain in the same place, but the negative sides will be swapped. This method returns nothing.
566
567=head3 contains($rect)
568
569Returns true (non-zero) when the argument is completely inside the Rect. Otherwise returns undef.
570
571=head3 collidepoint(x, y)
572
573Returns true (non-zero) if the given point is inside the Rect, otherwise returns undef. A point along the right or bottom edge is not considered to be inside the rectangle.
574
575=head3 colliderect($rect)
576
577Returns true (non-zero) if any portion of either rectangles overlap (except for the top+bottom or left+right edges).
578
579=head3 collidelist( [$rect1, $rect2, ...] )
580
581Test whether the rectangle collides with any in a sequence of rectangles, passed as an ARRAY REF. The index of the first collision found is returned. Returns undef if no collisions are found.
582
583=head3 collidelistall( [$rect1, $rect2, ...] )
584
585Returns an ARRAY REF of all the indices that contain rectangles that collide with the Rect. If no intersecting rectangles are found, an empty list ref is returned.
586
587=head3 collidehash( {key1 => $rect1, key2 => $rect2, ...} )
588
589Receives a HASH REF and returns the a (key, value) list with the key and value of the first hash item that collides with the Rect. If no collisions are found, returns (undef, undef).
590
591=head3 collidehashall( {key1 => $rect1, key2 => $rect2, ...} )
592
593Returns a HASH REF of all the key and value pairs that intersect with the Rect. If no collisions are found an empty hash ref is returned.
594
595
596=head1 AUTHOR
597
598Breno G. de Oliveira, C<< <garu at cpan.org> >>
599
600=head1 BUGS
601
602Please report any bugs or feature requests to the bug tracker. I will be notified, and then you'll automatically be notified of progress on your bug as we make changes.
603
604
605=head1 SUPPORT
606
607You can find documentation for this module with the perldoc command.
608
609 perldoc SDL::Game::Rect
610
611
612=head1 ACKNOWLEDGEMENTS
613
614Many thanks to all SDL_Perl contributors, and to the authors of pygame.rect, in which this particular module is heavily based.
615
616=head1 COPYRIGHT & LICENSE
617
618Copyright 2009 Breno G. de Oliveira, all rights reserved.
619
620This program is free software; you can redistribute it and/or modify it
621under the same terms as Perl itself.
622
623
624=head1 SEE ALSO
625
626perl, L<SDL>, L<SDL::Rect>, L<SDL::Game>