1 package SDL::Game::Rect;
16 my $self = $class->SUPER::new($x, $y, $w, $h);
19 croak SDL::GetError();
25 #############################
27 #############################
29 my ($self, $val) = (@_);
31 $self->top($val - $self->height); # y = val - height
33 return $self->top + $self->height; # y + height
37 my ($self, $val) = (@_);
39 $self->left($val - $self->width); # x = val - width
41 return $self->left + $self->width; # x + width
45 my ($self, $val) = (@_);
47 $self->left($val - ($self->width >> 1)); # x = val - (width/2)
49 return $self->left + ($self->width >> 1); # x + (width/2)
53 my ($self, $val) = (@_);
55 $self->top($val - ($self->height >> 1)); # y = val - (height/2)
57 return $self->top + ($self->height >> 1); # y + (height/2)
61 my ($self, $w, $h) = (@_);
63 return ($self->width, $self->height) # (width, height)
64 unless (defined $w or defined $h);
67 $self->width($w); # width
70 $self->height($h); # height
75 my ($self, $y, $x) = (@_);
77 return ($self->top, $self->left) # (top, left)
78 unless (defined $y or defined $x);
81 $self->left($x); # left
90 my ($self, $centery, $x) = (@_);
92 return ($self->top + ($self->height >> 1), $self->left) # (centery, left)
93 unless (defined $centery or defined $x);
96 $self->left($x); # left
98 if (defined $centery) {
99 $self->top($centery - ($self->height >> 1)); # y = centery - (height/2)
105 my ($self, $bottom, $x) = (@_);
107 return ($self->top + $self->height, $self->left) # (bottom, left)
108 unless (defined $bottom or defined $x);
111 $self->left($x); # left
113 if (defined $bottom) {
114 $self->top($bottom - $self->height); # y = bottom - height
120 my ($self, $centerx, $centery) = (@_);
122 return ($self->left + ($self->width >> 1), $self->top + ($self->height >> 1))
123 unless (defined $centerx or defined $centery);
125 if (defined $centerx) {
126 $self->left($centerx - ($self->width >> 1)); # x = centerx - (width/2)
128 if (defined $centery) {
129 $self->top($centery - ($self->height >> 1)); # y = centery - (height/2)
135 my ($self, $y, $right) = (@_);
137 return ($self->top, $self->left + $self->width) # (top, right)
138 unless (defined $y or defined $right);
140 if (defined $right) {
141 $self->left($right - $self->width); # x = right - width
144 $self->top($y); # top
150 my ($self, $centery, $right) = (@_);
152 return ($self->top + ($self->height >> 1), $self->left + $self->width) # (centery, right)
153 unless (defined $centery or defined $right);
155 if (defined $right) {
156 $self->left($right - $self->width); # x = right - width
158 if (defined $centery) {
159 $self->top($centery - ($self->height >> 1)); # y = centery - (height/2)
165 my ($self, $bottom, $right) = (@_);
167 return ($self->top + $self->height, $self->left + $self->width) # (bottom, right)
168 unless (defined $bottom or defined $right);
170 if (defined $right) {
171 $self->left($right - $self->width); # x = right - width
173 if (defined $bottom) {
174 $self->top($bottom - $self->height); # y = bottom - height
180 my ($self, $centerx, $y) = (@_);
182 return ($self->left + ($self->width >> 1), $self->top) # (centerx, top)
183 unless (defined $centerx or defined $y);
186 $self->top($y); # top
188 if (defined $centerx) {
189 $self->left($centerx - ($self->width >> 1)); # x = centerx - (width/2)
195 my ($self, $centerx, $bottom) = (@_);
197 return ($self->left + ($self->width >> 1), $self->top + $self->height) # (centerx, bottom)
198 unless (defined $centerx or defined $bottom);
200 if (defined $bottom) {
201 $self->top($bottom - $self->height); # y = bottom - height
203 if (defined $centerx) {
204 $self->left($centerx - ($self->width >> 1)); # x = centerx - (width/2)
209 ###############################
211 ###############################
215 *{'duplicate'} = *{copy};
222 -left => $self->left,
223 -width => $self->width,
224 -height => $self->height,
229 my ($self, $x, $y) = (@_);
230 if (not defined $x or not defined $y) {
232 croak "must receive x and y positions as argument";
235 -top => $self->top + $y,
236 -left => $self->left + $x,
237 -width => $self->width,
238 -height => $self->height,
243 my ($self, $x, $y) = (@_);
244 if (not defined $x or not defined $y) {
246 croak "must receive x and y positions as argument";
248 $self->x($self->x + $x);
249 $self->y($self->y + $y);
255 my ($self, $x, $y) = (@_);
256 if (not defined $x or not defined $y) {
258 croak "must receive x and y positions as argument";
262 -left => $self->left - ($x / 2),
263 -top => $self->top - ($y / 2),
264 -width => $self->width + $x,
265 -height => $self->height + $y,
270 my ($self, $x, $y) = (@_);
271 if (not defined $x or not defined $y) {
273 croak "must receive x and y positions as argument";
276 $self->x( $self->x - ($x / 2) );
277 $self->y( $self->y - ($y / 2) );
279 $self->w( $self->w + $x );
280 $self->h( $self->h + $y );
283 sub _get_clamp_coordinates {
284 my ($self_pos, $self_len, $rect_pos, $rect_len) = (@_);
286 if ($self_len >= $rect_len) {
287 return $rect_pos + ($rect_len / 2) - ($self_len / 2);
289 elsif ($self_pos < $rect_pos) {
292 elsif ( ($self_pos + $self_len) > ($rect_pos + $rect_len) ) {
293 return $rect_pos + $rect_len - $self_len;
301 my ($self, $rect) = (@_);
303 unless ($rect->isa('SDL::Rect')) {
304 croak "must receive an SDL::Rect-based object";
307 my $x = _get_clamp_coordinates($self->x, $self->w, $rect->x, $rect->w);
308 my $y = _get_clamp_coordinates($self->y, $self->h, $rect->y, $rect->h);
310 return $self->new($x, $y, $self->w, $self->h);
314 my ($self, $rect) = (@_);
316 unless ($rect->isa('SDL::Rect')) {
317 croak "must receive an SDL::Rect-based object";
320 my $x = _get_clamp_coordinates($self->x, $self->w, $rect->x, $rect->w);
321 my $y = _get_clamp_coordinates($self->y, $self->h, $rect->y, $rect->h);
329 sub _get_intersection_coordinates {
330 my ($self, $rect) = (@_);
336 if (($self->x >= $rect->x) && ($self->x < ($rect->x + $rect->w))) {
339 elsif (($rect->x >= $self->x) && ($rect->x < ($self->x + $self->w))) {
347 if ((($self->x + $self->w) > $rect->x) && (($self->x + $self->w) <= ($rect->x + $rect->w))) {
348 $w = ($self->x + $self->w) - $x;
350 elsif ((($rect->x + $rect->w) > $self->x) && (($rect->x + $rect->w) <= ($self->x + $self->w))) {
351 $w = ($rect->x + $rect->w) - $x;
358 if (($self->y >= $rect->y) && ($self->y < ($rect->y + $rect->h))) {
361 elsif (($rect->y >= $self->y) && ($rect->y < ($self->y + $self->h))) {
369 if ((($self->y + $self->h) > $rect->y) && (($self->y + $self->h) <= ($rect->y + $rect->h))) {
370 $h = ($self->y + $self->h) - $y;
372 elsif ((($rect->y + $rect->h) > $self->y) && (($rect->y + $rect->h) <= ($self->y + $self->h))) {
373 $h = ($rect->y + $rect->h) - $y;
379 return ($x, $y, $w, $h);
382 # if we got here, the two rects do not intersect
383 return ($self->x, $self->y, 0, 0);
388 my ($self, $rect) = (@_);
390 unless ($rect->isa('SDL::Rect')) {
391 croak "must receive an SDL::Rect-based object";
394 my ($x, $y, $w, $h) = _get_intersection_coordinates($self, $rect);
396 return $self->new($x, $y, $w, $h);
400 my ($self, $rect) = (@_);
402 unless ($rect->isa('SDL::Rect')) {
403 croak "must receive an SDL::Rect-based object";
406 my ($x, $y, $w, $h) = _get_intersection_coordinates($self, $rect);
418 my ($self, $rect) = (@_);
421 $x = $self->x < $rect->x ? $self->x : $rect->x; # MIN
422 $y = $self->y < $rect->y ? $self->y : $rect->y; # MIN
424 $w = ($self->x + $self->w) > ($rect->x + $rect->w)
425 ? ($self->x + $self->w) - $x
426 : ($rect->x + $rect->w) - $x
429 $h = ($self->y + $self->h) > ($rect->y + $rect->h)
430 ? ($self->y + $self->h) - $y
431 : ($rect->y + $rect->h) - $y
434 return ($x, $y, $w, $h);
438 my ($self, $rect) = (@_);
440 unless ($rect->isa('SDL::Rect')) {
441 croak "must receive an SDL::Rect-based object";
444 my ($x, $y, $w, $h) = _test_union($self, $rect);
445 return $self->new($x, $y, $w, $h);
449 my ($self, $rect) = (@_);
451 unless ($rect->isa('SDL::Rect')) {
452 croak "must receive an SDL::Rect-based object";
455 my ($x, $y, $w, $h) = _test_union($self, $rect);
466 my ($self, $rects) = (@_);
468 # initial values for union rect
471 my $right = $self->x + $self->w;
472 my $bottom = $self->y + $self->h;
474 foreach my $rect (@{$rects}) {
475 unless ($rect->isa('SDL::Rect')) {
476 # TODO: better error message, maybe saying which item
477 # is the bad one (by list position)
478 croak "must receive an array reference of SDL::Rect-based objects";
481 $left = $rect->x if $rect->x < $left; # MIN
482 $top = $rect->y if $rect->y < $top; # MIN
483 $right = ($rect->x + $rect->w) if ($rect->x + $rect->w) > $right; # MAX
484 $bottom = ($rect->y + $rect->h) if ($rect->y + $rect->h) > $bottom; # MAX
487 return ($left, $top, $right - $left, $bottom - $top);
491 my ($self, $rects) = (@_);
493 unless (defined $rects and ref $rects eq 'ARRAY') {
494 croak "must receive an array reference of SDL::Rect-based objects";
497 my ($x, $y, $w, $h) = _test_unionall($self, $rects);
499 return $self->new($x, $y, $w, $h);
503 my ($self, $rects) = (@_);
505 unless (defined $rects and ref $rects eq 'ARRAY') {
506 croak "must receive an array reference of SDL::Rect-based objects";
509 my ($x, $y, $w, $h) = _test_unionall($self, $rects);
520 my ($self, $rect) = (@_);
522 my $x_ratio = $self->w / $rect->w;
523 my $y_ratio = $self->h / $rect->h;
524 my $max_ratio = ($x_ratio > $y_ratio) ? $x_ratio : $y_ratio;
526 my $w = int ($self->w / $max_ratio);
527 my $h = int ($self->h / $max_ratio);
529 my $x = $rect->x + int (($rect->w - $w) / 2);
530 my $y = $rect->y + int (($rect->h - $h) / 2);
532 return ($x, $y, $w, $h);
536 my ($self, $rect) = (@_);
538 unless ($rect->isa('SDL::Rect')) {
539 croak "must receive an SDL::Rect-based object";
542 my ($x, $y, $w, $h) = _check_fit($self, $rect);
544 return $self->new ($x, $y, $w, $h);
548 my ($self, $rect) = (@_);
550 unless ($rect->isa('SDL::Rect')) {
551 croak "must receive an SDL::Rect-based object";
554 my ($x, $y, $w, $h) = _check_fit($self, $rect);
568 $self->x($self->x + $self->w);
573 $self->y( $self->y + $self->h);
580 my ($self, $rect) = (@_);
582 unless ($rect->isa('SDL::Rect')) {
583 croak "must receive an SDL::Rect-based object";
586 my $contained = ($self->x <= $rect->x)
587 && ($self->y <= $rect->y)
588 && ($self->x + $self->w >= $rect->x + $rect->w)
589 && ($self->y + $self->h >= $rect->y + $rect->h)
590 && ($self->x + $self->w > $rect->x)
591 && ($self->y + $self->h > $rect->y)
598 my ($self, $x, $y) = (@_);
600 unless (defined $x and defined $y) {
601 croak "must receive (x,y) as arguments";
604 my $inside = $x >= $self->x
605 && $x < $self->x + $self->w
607 && $y < $self->y + $self->h
613 sub _do_rects_intersect {
614 my ($rect_A, $rect_B) = (@_);
617 ($rect_A->x >= $rect_B->x && $rect_A->x < $rect_B->x + $rect_B->w)
618 || ($rect_B->x >= $rect_A->x && $rect_B->x < $rect_A->x + $rect_A->w)
622 ($rect_A->y >= $rect_B->y && $rect_A->y < $rect_B->y + $rect_B->h)
623 || ($rect_B->y >= $rect_A->y && $rect_B->y < $rect_A->y + $rect_A->h)
630 my ($self, $rect) = (@_);
632 unless ($rect->isa('SDL::Rect')) {
633 croak "must receive an SDL::Rect-based object";
636 return _do_rects_intersect($self, $rect);
640 my ($self, $rects) = (@_);
642 unless (defined $rects and ref $rects eq 'ARRAY') {
643 croak "must receive an array reference of SDL::Rect-based objects";
646 for(my $i = 0; $i < @{$rects}; $i++) {
647 if ( _do_rects_intersect($self, $rects->[$i]) ) {
655 my ($self, $rects) = (@_);
657 unless (defined $rects and ref $rects eq 'ARRAY') {
658 croak "must receive an array reference of SDL::Rect-based objects";
662 for(my $i = 0; $i < @{$rects}; $i++) {
663 if ( _do_rects_intersect($self, $rects->[$i]) ) {
664 push @collisions, $i;
671 my ($self, $rects) = (@_);
673 unless (defined $rects and ref $rects eq 'HASH') {
674 croak "must receive an hash reference of SDL::Rect-based objects";
677 while ( my ($key, $value) = each %{$rects} ) {
678 unless ($value->isa('SDL::Rect')) {
679 croak "hash element of key '$key' is not an SDL::Rect-based object";
682 if ( _do_rects_intersect($self, $value) ) {
683 return ($key, $value);
686 return (undef, undef);
690 my ($self, $rects) = (@_);
692 unless (defined $rects and ref $rects eq 'HASH') {
693 croak "must receive an hash reference of SDL::Rect-based objects";
697 while ( my ($key, $value) = each %{$rects} ) {
698 unless ($value->isa('SDL::Rect')) {
699 croak "hash element of key '$key' is not an SDL::Rect-based object";
702 if ( _do_rects_intersect($self, $value) ) {
703 $collisions{$key} = $value;
715 SDL::Game::Rect - SDL::Game object for storing and manipulating rectangular coordinates
722 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 >>.
724 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.
729 All Rect attributes are acessors, meaning you can get them by name, and set them by passing a value:
734 The Rect object has several attributes which can be used to resize, move and align the Rect.
739 =item * width, w - gets/sets object's width
741 =item * height, h - gets/sets object's height
743 =item * left, x - moves the object left position to match the given coordinate
745 =item * top, y - moves the object top position to match the given coordinate
747 =item * bottom - moves the object bottom position to match the given coordinate
749 =item * right - moves the object right position to match the given coordinate
751 =item * centerx - moves the object's horizontal center to match the given coordinate
753 =item * centery - moves the object's vertical center to match the given coordinate
757 Some of the attributes above can be fetched or set in pairs:
759 $rect->topleft(10, 15); # top is now 10, left is now 15
761 my ($width, $height) = $rect->size;
766 =item * size - gets/sets object's size (width, height)
768 =item * topleft - gets/sets object's top and left positions
770 =item * midleft - gets/sets object's vertical center and left positions
772 =item * bottomleft - gets/sets object's bottom and left positions
774 =item * center - gets/sets object's center (horizontal(x), vertical(y))
776 =item * topright - gets/sets object's top and right positions
778 =item * midright - gets/sets object's vertical center and right positions
780 =item * bottomright - gets/sets object's bottom and right positions
782 =item * midtop - gets/sets object's horizontal center and top positions
784 =item * midbottom - gets/sets object's horizontal center and bottom positions
791 Methods denoted as receiving Rect objects can receive either C<<SDL::Game::Rect>> or raw C<<SDL::Rect>> objects.
793 =head3 new ($left, $top, $width, $height)
795 Returns a new Rect object with the given coordinates. If any value is omitted (by passing undef), 0 is used instead.
801 Returns a new Rect object having the same position and size as the original
805 Returns a new Rect that is moved by the given offset. The x and y arguments can be any integer value, positive or negative.
809 Same as C<<move>> above, but moves the current Rect in place and returns nothing.
813 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.
815 =head3 inflate_ip(x, y)
817 Same as C<<inflate>> above, but grows/shrinks the current Rect in place and returns nothing.
821 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.
823 =head3 clamp_ip($rect)
825 Same as C<<clamp>> above, but moves the current Rect in place and returns nothing.
829 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.
831 =head3 clip_ip($rect)
833 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.
837 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.
839 =head3 union_ip($rect)
841 Same as C<<union>> above, but resizes the current Rect in place and returns nothing.
843 =head3 unionall( [$rect1, $rect2, ...] )
845 Returns the union of one rectangle with a sequence of many rectangles, passed as an ARRAY REF.
847 =head3 unionall_ip( [$rect1, $rect2, ...] )
849 Same as C<<unionall>> above, but resizes the current Rect in place and returns nothing.
853 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.
857 Same as C<<fit>> above, but moves/resizes the current Rect in place and returns nothing.
861 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.
863 =head3 contains($rect)
865 Returns true (non-zero) when the argument is completely inside the Rect. Otherwise returns undef.
867 =head3 collidepoint(x, y)
869 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.
871 =head3 colliderect($rect)
873 Returns true (non-zero) if any portion of either rectangles overlap (except for the top+bottom or left+right edges).
875 =head3 collidelist( [$rect1, $rect2, ...] )
877 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.
879 =head3 collidelistall( [$rect1, $rect2, ...] )
881 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.
883 =head3 collidehash( {key1 => $rect1, key2 => $rect2, ...} )
885 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).
887 =head3 collidehashall( {key1 => $rect1, key2 => $rect2, ...} )
889 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.
894 Breno G. de Oliveira, C<< <garu at cpan.org> >>
898 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.
903 You can find documentation for this module with the perldoc command.
905 perldoc SDL::Game::Rect
908 =head1 ACKNOWLEDGEMENTS
910 Many thanks to all SDL_Perl contributors, and to the authors of pygame.rect, in which this particular module is heavily based.
912 =head1 COPYRIGHT & LICENSE
914 Copyright 2009 Breno G. de Oliveira, all rights reserved.
916 This program is free software; you can redistribute it and/or modify it
917 under the same terms as Perl itself.
922 perl, L<SDL>, L<SDL::Rect>, L<SDL::Game>