1 package SDL::Game::Rect;
16 my $self = $class->SUPER::new($x, $y, $w, $h);
19 croak SDL::GetError();
25 #############################
27 #############################
50 my ($self, $val) = (@_);
52 $self->top($val - $self->height); # y = val - height
54 return $self->top + $self->height; # y + height
58 my ($self, $val) = (@_);
60 $self->left($val - $self->width); # x = val - width
62 return $self->left + $self->width; # x + width
66 my ($self, $val) = (@_);
68 $self->left($val - ($self->width >> 1)); # x = val - (width/2)
70 return $self->left + ($self->width >> 1); # x + (width/2)
74 my ($self, $val) = (@_);
76 $self->top($val - ($self->height >> 1)); # y = val - (height/2)
78 return $self->top + ($self->height >> 1); # y + (height/2)
82 my ($self, $w, $h) = (@_);
84 return ($self->width, $self->height) # (width, height)
85 unless (defined $w or defined $h);
88 $self->width($w); # width
91 $self->height($h); # height
96 my ($self, $y, $x) = (@_);
98 return ($self->top, $self->left) # (top, left)
99 unless (defined $y or defined $x);
102 $self->left($x); # left
105 $self->top($y); # top
111 my ($self, $centery, $x) = (@_);
113 return ($self->top + ($self->height >> 1), $self->left) # (centery, left)
114 unless (defined $centery or defined $x);
117 $self->left($x); # left
119 if (defined $centery) {
120 $self->top($centery - ($self->height >> 1)); # y = centery - (height/2)
126 my ($self, $bottom, $x) = (@_);
128 return ($self->top + $self->height, $self->left) # (bottom, left)
129 unless (defined $bottom or defined $x);
132 $self->left($x); # left
134 if (defined $bottom) {
135 $self->top($bottom - $self->height); # y = bottom - height
141 my ($self, $centerx, $centery) = (@_);
143 return ($self->left + ($self->width >> 1), $self->top + ($self->height >> 1))
144 unless (defined $centerx or defined $centery);
146 if (defined $centerx) {
147 $self->left($centerx - ($self->width >> 1)); # x = centerx - (width/2)
149 if (defined $centery) {
150 $self->top($centery - ($self->height >> 1)); # y = centery - (height/2)
156 my ($self, $y, $right) = (@_);
158 return ($self->top, $self->left + $self->width) # (top, right)
159 unless (defined $y or defined $right);
161 if (defined $right) {
162 $self->left($right - $self->width); # x = right - width
165 $self->top($y); # top
171 my ($self, $centery, $right) = (@_);
173 return ($self->top + ($self->height >> 1), $self->left + $self->width) # (centery, right)
174 unless (defined $centery or defined $right);
176 if (defined $right) {
177 $self->left($right - $self->width); # x = right - width
179 if (defined $centery) {
180 $self->top($centery - ($self->height >> 1)); # y = centery - (height/2)
186 my ($self, $bottom, $right) = (@_);
188 return ($self->top + $self->height, $self->left + $self->width) # (bottom, right)
189 unless (defined $bottom or defined $right);
191 if (defined $right) {
192 $self->left($right - $self->width); # x = right - width
194 if (defined $bottom) {
195 $self->top($bottom - $self->height); # y = bottom - height
201 my ($self, $centerx, $y) = (@_);
203 return ($self->left + ($self->width >> 1), $self->top) # (centerx, top)
204 unless (defined $centerx or defined $y);
207 $self->top($y); # top
209 if (defined $centerx) {
210 $self->left($centerx - ($self->width >> 1)); # x = centerx - (width/2)
216 my ($self, $centerx, $bottom) = (@_);
218 return ($self->left + ($self->width >> 1), $self->top + $self->height) # (centerx, bottom)
219 unless (defined $centerx or defined $bottom);
221 if (defined $bottom) {
222 $self->top($bottom - $self->height); # y = bottom - height
224 if (defined $centerx) {
225 $self->left($centerx - ($self->width >> 1)); # x = centerx - (width/2)
230 ###############################
232 ###############################
236 *{'duplicate'} = *{copy};
243 -left => $self->left,
244 -width => $self->width,
245 -height => $self->height,
250 my ($self, $x, $y) = (@_);
251 if (not defined $x or not defined $y) {
253 croak "must receive x and y positions as argument";
256 -top => $self->top + $y,
257 -left => $self->left + $x,
258 -width => $self->width,
259 -height => $self->height,
264 my ($self, $x, $y) = (@_);
265 if (not defined $x or not defined $y) {
267 croak "must receive x and y positions as argument";
269 $self->x($self->x + $x);
270 $self->y($self->y + $y);
276 my ($self, $x, $y) = (@_);
277 if (not defined $x or not defined $y) {
279 croak "must receive x and y positions as argument";
283 -left => $self->left - ($x / 2),
284 -top => $self->top - ($y / 2),
285 -width => $self->width + $x,
286 -height => $self->height + $y,
291 my ($self, $x, $y) = (@_);
292 if (not defined $x or not defined $y) {
294 croak "must receive x and y positions as argument";
297 $self->x( $self->x - ($x / 2) );
298 $self->y( $self->y - ($y / 2) );
300 $self->w( $self->w + $x );
301 $self->h( $self->h + $y );
304 sub _get_clamp_coordinates {
305 my ($self_pos, $self_len, $rect_pos, $rect_len) = (@_);
307 if ($self_len >= $rect_len) {
308 return $rect_pos + ($rect_len / 2) - ($self_len / 2);
310 elsif ($self_pos < $rect_pos) {
313 elsif ( ($self_pos + $self_len) > ($rect_pos + $rect_len) ) {
314 return $rect_pos + $rect_len - $self_len;
322 my ($self, $rect) = (@_);
324 unless ($rect->isa('SDL::Rect')) {
325 croak "must receive an SDL::Rect-based object";
328 my $x = _get_clamp_coordinates($self->x, $self->w, $rect->x, $rect->w);
329 my $y = _get_clamp_coordinates($self->y, $self->h, $rect->y, $rect->h);
331 return $self->new($x, $y, $self->w, $self->h);
335 my ($self, $rect) = (@_);
337 unless ($rect->isa('SDL::Rect')) {
338 croak "must receive an SDL::Rect-based object";
341 my $x = _get_clamp_coordinates($self->x, $self->w, $rect->x, $rect->w);
342 my $y = _get_clamp_coordinates($self->y, $self->h, $rect->y, $rect->h);
350 sub _get_intersection_coordinates {
351 my ($self, $rect) = (@_);
357 if (($self->x >= $rect->x) && ($self->x < ($rect->x + $rect->w))) {
360 elsif (($rect->x >= $self->x) && ($rect->x < ($self->x + $self->w))) {
368 if ((($self->x + $self->w) > $rect->x) && (($self->x + $self->w) <= ($rect->x + $rect->w))) {
369 $w = ($self->x + $self->w) - $x;
371 elsif ((($rect->x + $rect->w) > $self->x) && (($rect->x + $rect->w) <= ($self->x + $self->w))) {
372 $w = ($rect->x + $rect->w) - $x;
379 if (($self->y >= $rect->y) && ($self->y < ($rect->y + $rect->h))) {
382 elsif (($rect->y >= $self->y) && ($rect->y < ($self->y + $self->h))) {
390 if ((($self->y + $self->h) > $rect->y) && (($self->y + $self->h) <= ($rect->y + $rect->h))) {
391 $h = ($self->y + $self->h) - $y;
393 elsif ((($rect->y + $rect->h) > $self->y) && (($rect->y + $rect->h) <= ($self->y + $self->h))) {
394 $h = ($rect->y + $rect->h) - $y;
400 return ($x, $y, $w, $h);
403 # if we got here, the two rects do not intersect
404 return ($self->x, $self->y, 0, 0);
409 my ($self, $rect) = (@_);
411 unless ($rect->isa('SDL::Rect')) {
412 croak "must receive an SDL::Rect-based object";
415 my ($x, $y, $w, $h) = _get_intersection_coordinates($self, $rect);
417 return $self->new($x, $y, $w, $h);
421 my ($self, $rect) = (@_);
423 unless ($rect->isa('SDL::Rect')) {
424 croak "must receive an SDL::Rect-based object";
427 my ($x, $y, $w, $h) = _get_intersection_coordinates($self, $rect);
439 my ($self, $rect) = (@_);
442 $x = $self->x < $rect->x ? $self->x : $rect->x; # MIN
443 $y = $self->y < $rect->y ? $self->y : $rect->y; # MIN
445 $w = ($self->x + $self->w) > ($rect->x + $rect->w)
446 ? ($self->x + $self->w) - $x
447 : ($rect->x + $rect->w) - $x
450 $h = ($self->y + $self->h) > ($rect->y + $rect->h)
451 ? ($self->y + $self->h) - $y
452 : ($rect->y + $rect->h) - $y
455 return ($x, $y, $w, $h);
459 my ($self, $rect) = (@_);
461 unless ($rect->isa('SDL::Rect')) {
462 croak "must receive an SDL::Rect-based object";
465 my ($x, $y, $w, $h) = _test_union($self, $rect);
466 return $self->new($x, $y, $w, $h);
470 my ($self, $rect) = (@_);
472 unless ($rect->isa('SDL::Rect')) {
473 croak "must receive an SDL::Rect-based object";
476 my ($x, $y, $w, $h) = _test_union($self, $rect);
487 my ($self, $rects) = (@_);
489 # initial values for union rect
492 my $right = $self->x + $self->w;
493 my $bottom = $self->y + $self->h;
495 foreach my $rect (@{$rects}) {
496 unless ($rect->isa('SDL::Rect')) {
497 # TODO: better error message, maybe saying which item
498 # is the bad one (by list position)
499 croak "must receive an array reference of SDL::Rect-based objects";
502 $left = $rect->x if $rect->x < $left; # MIN
503 $top = $rect->y if $rect->y < $top; # MIN
504 $right = ($rect->x + $rect->w) if ($rect->x + $rect->w) > $right; # MAX
505 $bottom = ($rect->y + $rect->h) if ($rect->y + $rect->h) > $bottom; # MAX
508 return ($left, $top, $right - $left, $bottom - $top);
512 my ($self, $rects) = (@_);
514 unless (defined $rects and ref $rects eq 'ARRAY') {
515 croak "must receive an array reference of SDL::Rect-based objects";
518 my ($x, $y, $w, $h) = _test_unionall($self, $rects);
520 return $self->new($x, $y, $w, $h);
524 my ($self, $rects) = (@_);
526 unless (defined $rects and ref $rects eq 'ARRAY') {
527 croak "must receive an array reference of SDL::Rect-based objects";
530 my ($x, $y, $w, $h) = _test_unionall($self, $rects);
541 my ($self, $rect) = (@_);
543 my $x_ratio = $self->w / $rect->w;
544 my $y_ratio = $self->h / $rect->h;
545 my $max_ratio = ($x_ratio > $y_ratio) ? $x_ratio : $y_ratio;
547 my $w = int ($self->w / $max_ratio);
548 my $h = int ($self->h / $max_ratio);
550 my $x = $rect->x + int (($rect->w - $w) / 2);
551 my $y = $rect->y + int (($rect->h - $h) / 2);
553 return ($x, $y, $w, $h);
557 my ($self, $rect) = (@_);
559 unless ($rect->isa('SDL::Rect')) {
560 croak "must receive an SDL::Rect-based object";
563 my ($x, $y, $w, $h) = _check_fit($self, $rect);
565 return $self->new ($x, $y, $w, $h);
569 my ($self, $rect) = (@_);
571 unless ($rect->isa('SDL::Rect')) {
572 croak "must receive an SDL::Rect-based object";
575 my ($x, $y, $w, $h) = _check_fit($self, $rect);
589 $self->x($self->x + $self->w);
594 $self->y( $self->y + $self->h);
601 my ($self, $rect) = (@_);
603 unless ($rect->isa('SDL::Rect')) {
604 croak "must receive an SDL::Rect-based object";
607 my $contained = ($self->x <= $rect->x)
608 && ($self->y <= $rect->y)
609 && ($self->x + $self->w >= $rect->x + $rect->w)
610 && ($self->y + $self->h >= $rect->y + $rect->h)
611 && ($self->x + $self->w > $rect->x)
612 && ($self->y + $self->h > $rect->y)
619 my ($self, $x, $y) = (@_);
621 unless (defined $x and defined $y) {
622 croak "must receive (x,y) as arguments";
625 my $inside = $x >= $self->x
626 && $x < $self->x + $self->w
628 && $y < $self->y + $self->h
634 sub _do_rects_intersect {
635 my ($rect_A, $rect_B) = (@_);
638 ($rect_A->x >= $rect_B->x && $rect_A->x < $rect_B->x + $rect_B->w)
639 || ($rect_B->x >= $rect_A->x && $rect_B->x < $rect_A->x + $rect_A->w)
643 ($rect_A->y >= $rect_B->y && $rect_A->y < $rect_B->y + $rect_B->h)
644 || ($rect_B->y >= $rect_A->y && $rect_B->y < $rect_A->y + $rect_A->h)
651 my ($self, $rect) = (@_);
653 unless ($rect->isa('SDL::Rect')) {
654 croak "must receive an SDL::Rect-based object";
657 return _do_rects_intersect($self, $rect);
661 my ($self, $rects) = (@_);
663 unless (defined $rects and ref $rects eq 'ARRAY') {
664 croak "must receive an array reference of SDL::Rect-based objects";
667 for(my $i = 0; $i < @{$rects}; $i++) {
668 if ( _do_rects_intersect($self, $rects->[$i]) ) {
676 my ($self, $rects) = (@_);
678 unless (defined $rects and ref $rects eq 'ARRAY') {
679 croak "must receive an array reference of SDL::Rect-based objects";
683 for(my $i = 0; $i < @{$rects}; $i++) {
684 if ( _do_rects_intersect($self, $rects->[$i]) ) {
685 push @collisions, $i;
692 my ($self, $rects) = (@_);
694 unless (defined $rects and ref $rects eq 'HASH') {
695 croak "must receive an hash reference of SDL::Rect-based objects";
698 while ( my ($key, $value) = each %{$rects} ) {
699 unless ($value->isa('SDL::Rect')) {
700 croak "hash element of key '$key' is not an SDL::Rect-based object";
703 if ( _do_rects_intersect($self, $value) ) {
704 return ($key, $value);
707 return (undef, undef);
711 my ($self, $rects) = (@_);
713 unless (defined $rects and ref $rects eq 'HASH') {
714 croak "must receive an hash reference of SDL::Rect-based objects";
718 while ( my ($key, $value) = each %{$rects} ) {
719 unless ($value->isa('SDL::Rect')) {
720 croak "hash element of key '$key' is not an SDL::Rect-based object";
723 if ( _do_rects_intersect($self, $value) ) {
724 $collisions{$key} = $value;
736 SDL::Game::Rect - SDL::Game object for storing and manipulating rectangular coordinates
743 C<< 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 >>.
745 All 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.
750 All Rect attributes are acessors, meaning you can get them by name, and set them by passing a value:
755 The Rect object has several attributes which can be used to resize, move and align the Rect.
760 =item * width, w - gets/sets object's width
762 =item * height, h - gets/sets object's height
764 =item * left, x - moves the object left position to match the given coordinate
766 =item * top, y - moves the object top position to match the given coordinate
768 =item * bottom - moves the object bottom position to match the given coordinate
770 =item * right - moves the object right position to match the given coordinate
772 =item * centerx - moves the object's horizontal center to match the given coordinate
774 =item * centery - moves the object's vertical center to match the given coordinate
778 Some of the attributes above can be fetched or set in pairs:
780 $rect->topleft(10, 15); # top is now 10, left is now 15
782 my ($width, $height) = $rect->size;
787 =item * size - gets/sets object's size (width, height)
789 =item * topleft - gets/sets object's top and left positions
791 =item * midleft - gets/sets object's vertical center and left positions
793 =item * bottomleft - gets/sets object's bottom and left positions
795 =item * center - gets/sets object's center (horizontal(x), vertical(y))
797 =item * topright - gets/sets object's top and right positions
799 =item * midright - gets/sets object's vertical center and right positions
801 =item * bottomright - gets/sets object's bottom and right positions
803 =item * midtop - gets/sets object's horizontal center and top positions
805 =item * midbottom - gets/sets object's horizontal center and bottom positions
812 Methods denoted as receiving Rect objects can receive either C<<SDL::Game::Rect>> or raw C<<SDL::Rect>> objects.
814 =head3 new ($left, $top, $width, $height)
816 Returns a new Rect object with the given coordinates. If any value is omitted (by passing undef), 0 is used instead.
822 Returns a new Rect object having the same position and size as the original
826 Returns a new Rect that is moved by the given offset. The x and y arguments can be any integer value, positive or negative.
830 Same as C<<move>> above, but moves the current Rect in place and returns nothing.
834 Grows 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.
836 =head3 inflate_ip(x, y)
838 Same as C<<inflate>> above, but grows/shrinks the current Rect in place and returns nothing.
842 Returns 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.
844 =head3 clamp_ip($rect)
846 Same as C<<clamp>> above, but moves the current Rect in place and returns nothing.
850 Returns 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.
852 =head3 clip_ip($rect)
854 Same 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.
858 Returns 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.
860 =head3 union_ip($rect)
862 Same as C<<union>> above, but resizes the current Rect in place and returns nothing.
864 =head3 unionall( [$rect1, $rect2, ...] )
866 Returns the union of one rectangle with a sequence of many rectangles, passed as an ARRAY REF.
868 =head3 unionall_ip( [$rect1, $rect2, ...] )
870 Same as C<<unionall>> above, but resizes the current Rect in place and returns nothing.
874 Returns 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.
878 Same as C<<fit>> above, but moves/resizes the current Rect in place and returns nothing.
882 Corrects 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.
884 =head3 contains($rect)
886 Returns true (non-zero) when the argument is completely inside the Rect. Otherwise returns undef.
888 =head3 collidepoint(x, y)
890 Returns 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.
892 =head3 colliderect($rect)
894 Returns true (non-zero) if any portion of either rectangles overlap (except for the top+bottom or left+right edges).
896 =head3 collidelist( [$rect1, $rect2, ...] )
898 Test 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.
900 =head3 collidelistall( [$rect1, $rect2, ...] )
902 Returns 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.
904 =head3 collidehash( {key1 => $rect1, key2 => $rect2, ...} )
906 Receives 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).
908 =head3 collidehashall( {key1 => $rect1, key2 => $rect2, ...} )
910 Returns 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.
915 Breno G. de Oliveira, C<< <garu at cpan.org> >>
919 Please 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.
924 You can find documentation for this module with the perldoc command.
926 perldoc SDL::Game::Rect
929 =head1 ACKNOWLEDGEMENTS
931 Many thanks to all SDL_Perl contributors, and to the authors of pygame.rect, in which this particular module is heavily based.
933 =head1 COPYRIGHT & LICENSE
935 Copyright 2009 Breno G. de Oliveira, all rights reserved.
937 This program is free software; you can redistribute it and/or modify it
938 under the same terms as Perl itself.
943 perl, L<SDL>, L<SDL::Rect>, L<SDL::Game>