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