implemented union and union_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
26622c70 414=head3 union($rect)
415
416Returns 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.
417
418=cut
419
420sub _test_union {
421 my ($self, $rect) = (@_);
422 my ($x, $y, $w, $h);
423
424 $x = $self->x < $rect->x ? $self->x : $rect->x; # MIN
425 $y = $self->y < $rect->y ? $self->y : $rect->y; # MIN
426
427 $w = ($self->x + $self->w) > ($rect->x + $rect->w)
428 ? ($self->x + $self->w) - $x
429 : ($rect->x + $rect->w) - $x
430 ; # MAX
431
432 $h = ($self->y + $self->h) > ($rect->y + $rect->h)
433 ? ($self->y + $self->h) - $y
434 : ($rect->y + $rect->h) - $y
435 ; # MAX
436
437 return ($x, $y, $w, $h);
438}
439
440sub union {
441 my ($self, $rect) = (@_);
442
443 unless ($rect->isa('SDL::Rect')) {
444 croak "must receive an SDL::Rect-based object";
445 }
446
447 my ($x, $y, $w, $h) = _test_union($self, $rect);
448 return $self->new($x, $y, $w, $h);
449}
450
451sub union_ip {
452 my ($self, $rect) = (@_);
453
454 unless ($rect->isa('SDL::Rect')) {
455 croak "must receive an SDL::Rect-based object";
456 }
457
458 my ($x, $y, $w, $h) = _test_union($self, $rect);
459
460 $self->x($x);
461 $self->y($y);
462 $self->w($w);
463 $self->y($h);
464
465 return;
466}
467
389e4b7b 46842;
469__END__
470
471=head1 NAME
472
473SDL::Game::Rect - SDL::Game object for storing and manipulating rectangular coordinates
474
475=head1 SYNOPSIS
476
477
478=head1 DESCRIPTION
479
480C<< 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 >>.
481
482All 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.
483
484
485=head2 ATTRIBUTES
486
487All Rect attributes are acessors, meaning you can get them by name, and set them by passing a value:
488
489 $rect->left(15);
490 $rect->left; # 15
491
492The Rect object has several attributes which can be used to resize, move and align the Rect.
493
494
495=over 4
496
497=item * width, w - gets/sets object's width
498
499=item * height, h - gets/sets object's height
500
501=item * left, x - moves the object left position to match the given coordinate
502
503=item * top, y - moves the object top position to match the given coordinate
504
505=item * bottom - moves the object bottom position to match the given coordinate
506
507=item * right - moves the object right position to match the given coordinate
508
509=item * centerx - moves the object's horizontal center to match the given coordinate
510
511=item * centery - moves the object's vertical center to match the given coordinate
512
513=back
514
515Some of the attributes above can be fetched or set in pairs:
516
517 $rect->topleft(10, 15); # top is now 10, left is now 15
518
519 my ($width, $height) = $rect->size;
520
521
522=over 4
523
524=item * size - gets/sets object's size (width, height)
525
526=item * topleft - gets/sets object's top and left positions
527
528=item * midleft - gets/sets object's vertical center and left positions
529
530=item * bottomleft - gets/sets object's bottom and left positions
531
532=item * center - gets/sets object's center (horizontal(x), vertical(y))
533
534=item * topright - gets/sets object's top and right positions
535
536=item * midright - gets/sets object's vertical center and right positions
537
538=item * bottomright - gets/sets object's bottom and right positions
539
540=item * midtop - gets/sets object's horizontal center and top positions
541
542=item * midbottom - gets/sets object's horizontal center and bottom positions
543
544=back
545
546
547=head2 METHODS
548
549Methods denoted as receiving Rect objects can receive either C<<SDL::Game::Rect>> or raw C<<SDL::Rect>> objects.
550
551=head3 new ($left, $top, $width, $height)
552
553Returns a new Rect object with the given coordinates. If any value is omitted (by passing undef), 0 is used instead.
554
555=head3 copy
556
557=head3 duplicate
558
559Returns a new Rect object having the same position and size as the original
560
561=head3 move(x, y)
562
563Returns a new Rect that is moved by the given offset. The x and y arguments can be any integer value, positive or negative.
564
565=head3 move_ip(x, y)
566
567Same as C<<move>> above, but moves the current Rect in place and returns nothing.
568
569=head3 inflate(x, y)
570
571Grows 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.
572
573=head3 inflate_ip(x, y)
574
575Same as C<<inflate>> above, but grows/shrinks the current Rect in place and returns nothing.
576
577=head3 clamp($rect)
578
579Returns 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.
580
581=head3 clamp_ip($rect)
582
583Same as C<<clamp>> above, but moves the current Rect in place and returns nothing.
584
585=head3 clip($rect)
586
74f8c259 587Returns 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 588
589=head3 clip_ip($rect)
590
74f8c259 591Same 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 592
593=head3 union($rect)
594
595Returns 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.
596
597=head3 union_ip($rect)
598
599Same as C<<union>> above, but resizes the current Rect in place and returns nothing.
600
601=head3 unionall( [$rect1, $rect2, ...] )
602
603Returns the union of one rectangle with a sequence of many rectangles, passed as an ARRAY REF.
604
605=head3 unionall_ip( [$rect1, $rect2, ...] )
606
607Same as C<<unionall>> above, but resizes the current Rect in place and returns nothing.
608
609=head3 fit($rect)
610
611Returns 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.
612
613=head3 fit_ip($rect)
614
615Same as C<<fit>> above, but moves/resizes the current Rect in place and returns nothing.
616
617=head3 normalize
618
619Corrects 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.
620
621=head3 contains($rect)
622
623Returns true (non-zero) when the argument is completely inside the Rect. Otherwise returns undef.
624
625=head3 collidepoint(x, y)
626
627Returns 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.
628
629=head3 colliderect($rect)
630
631Returns true (non-zero) if any portion of either rectangles overlap (except for the top+bottom or left+right edges).
632
633=head3 collidelist( [$rect1, $rect2, ...] )
634
635Test 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.
636
637=head3 collidelistall( [$rect1, $rect2, ...] )
638
639Returns 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.
640
641=head3 collidehash( {key1 => $rect1, key2 => $rect2, ...} )
642
643Receives 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).
644
645=head3 collidehashall( {key1 => $rect1, key2 => $rect2, ...} )
646
647Returns 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.
648
649
650=head1 AUTHOR
651
652Breno G. de Oliveira, C<< <garu at cpan.org> >>
653
654=head1 BUGS
655
656Please 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.
657
658
659=head1 SUPPORT
660
661You can find documentation for this module with the perldoc command.
662
663 perldoc SDL::Game::Rect
664
665
666=head1 ACKNOWLEDGEMENTS
667
668Many thanks to all SDL_Perl contributors, and to the authors of pygame.rect, in which this particular module is heavily based.
669
670=head1 COPYRIGHT & LICENSE
671
672Copyright 2009 Breno G. de Oliveira, all rights reserved.
673
674This program is free software; you can redistribute it and/or modify it
675under the same terms as Perl itself.
676
677
678=head1 SEE ALSO
679
680perl, L<SDL>, L<SDL::Rect>, L<SDL::Game>