1package SDLx::Rect; 2use strict; 3use warnings; 4use Carp; 5use base 'SDL::Rect'; 6 7our $VERSION = 2.548; 8 9sub new { 10 my $class = shift; 11 my $x = shift || 0; 12 my $y = shift || 0; 13 my $w = shift || 0; 14 my $h = shift || 0; 15 16 $class = ref($class) || $class; 17 my $self = $class->SUPER::new( $x, $y, $w, $h ); 18 unless ($$self) { 19 20 #require Carp; 21 Carp::confess SDL::get_error(); 22 } 23 return bless $self, $class; 24} 25 26############################# 27## extra accessors 28############################# 29 30sub left { 31 my $self = shift; 32 $self->x(@_); 33} 34 35sub top { 36 my $self = shift; 37 $self->y(@_); 38} 39 40sub width { 41 my $self = shift; 42 $self->w(@_); 43} 44 45sub height { 46 my $self = shift; 47 $self->h(@_); 48} 49 50sub bottom { 51 my ( $self, $val ) = (@_); 52 if ( defined $val ) { 53 $self->top( $val - $self->height ); # y = val - height 54 } 55 return $self->top + $self->height; # y + height 56} 57 58sub right { 59 my ( $self, $val ) = (@_); 60 if ( defined $val ) { 61 $self->left( $val - $self->width ); # x = val - width 62 } 63 return $self->left + $self->width; # x + width 64} 65 66sub centerx { 67 my ( $self, $val ) = (@_); 68 if ( defined $val ) { 69 $self->left( $val - ( $self->width >> 1 ) ); # x = val - (width/2) 70 } 71 return $self->left + ( $self->width >> 1 ); # x + (width/2) 72} 73 74sub centery { 75 my ( $self, $val ) = (@_); 76 if ( defined $val ) { 77 $self->top( $val - ( $self->height >> 1 ) ); # y = val - (height/2) 78 } 79 return $self->top + ( $self->height >> 1 ); # y + (height/2) 80} 81 82sub size { 83 my ( $self, $w, $h ) = (@_); 84 85 return ( $self->width, $self->height ) # (width, height) 86 unless ( defined $w or defined $h ); 87 88 if ( defined $w ) { 89 $self->width($w); # width 90 } 91 if ( defined $h ) { 92 $self->height($h); # height 93 } 94} 95 96sub topleft { 97 my ( $self, $y, $x ) = (@_); 98 99 return ( $self->top, $self->left ) # (top, left) 100 unless ( defined $y or defined $x ); 101 102 if ( defined $x ) { 103 $self->left($x); # left 104 } 105 if ( defined $y ) { 106 $self->top($y); # top 107 } 108 return; 109} 110 111sub midleft { 112 my ( $self, $centery, $x ) = (@_); 113 114 return ( 115 $self->top + ( $self->height >> 1 ), 116 $self->left 117 ) # (centery, left) 118 unless ( defined $centery or defined $x ); 119 120 if ( defined $x ) { 121 $self->left($x); # left 122 } 123 if ( defined $centery ) { 124 $self->top( $centery - ( $self->height >> 1 ) ); # y = centery - (height/2) 125 } 126 return; 127} 128 129sub bottomleft { 130 my ( $self, $bottom, $x ) = (@_); 131 132 return ( $self->top + $self->height, $self->left ) # (bottom, left) 133 unless ( defined $bottom or defined $x ); 134 135 if ( defined $x ) { 136 $self->left($x); # left 137 } 138 if ( defined $bottom ) { 139 $self->top( $bottom - $self->height ); # y = bottom - height 140 } 141 return; 142} 143 144sub center { 145 my ( $self, $centerx, $centery ) = (@_); 146 147 return ( 148 $self->left + ( $self->width >> 1 ), 149 $self->top + ( $self->height >> 1 ) 150 ) unless ( defined $centerx or defined $centery ); 151 152 if ( defined $centerx ) { 153 $self->left( $centerx - ( $self->width >> 1 ) ); # x = centerx - (width/2) 154 } 155 if ( defined $centery ) { 156 $self->top( $centery - ( $self->height >> 1 ) ); # y = centery - (height/2) 157 } 158 return; 159} 160 161sub topright { 162 my ( $self, $y, $right ) = (@_); 163 164 return ( $self->top, $self->left + $self->width ) # (top, right) 165 unless ( defined $y or defined $right ); 166 167 if ( defined $right ) { 168 $self->left( $right - $self->width ); # x = right - width 169 } 170 if ( defined $y ) { 171 $self->top($y); # top 172 } 173 return; 174} 175 176sub midright { 177 my ( $self, $centery, $right ) = (@_); 178 179 return ( 180 $self->top + ( $self->height >> 1 ), 181 $self->left + $self->width 182 ) # (centery, right) 183 unless ( defined $centery or defined $right ); 184 185 if ( defined $right ) { 186 $self->left( $right - $self->width ); # x = right - width 187 } 188 if ( defined $centery ) { 189 $self->top( $centery - ( $self->height >> 1 ) ); # y = centery - (height/2) 190 } 191 return; 192} 193 194sub bottomright { 195 my ( $self, $bottom, $right ) = (@_); 196 197 return ( 198 $self->top + $self->height, 199 $self->left + $self->width 200 ) # (bottom, right) 201 unless ( defined $bottom or defined $right ); 202 203 if ( defined $right ) { 204 $self->left( $right - $self->width ); # x = right - width 205 } 206 if ( defined $bottom ) { 207 $self->top( $bottom - $self->height ); # y = bottom - height 208 } 209 return; 210} 211 212sub midtop { 213 my ( $self, $centerx, $y ) = (@_); 214 215 return ( $self->left + ( $self->width >> 1 ), $self->top ) # (centerx, top) 216 unless ( defined $centerx or defined $y ); 217 218 if ( defined $y ) { 219 $self->top($y); # top 220 } 221 if ( defined $centerx ) { 222 $self->left( $centerx - ( $self->width >> 1 ) ); # x = centerx - (width/2) 223 } 224 return; 225} 226 227sub midbottom { 228 my ( $self, $centerx, $bottom ) = (@_); 229 230 return ( 231 $self->left + ( $self->width >> 1 ), 232 $self->top + $self->height 233 ) # (centerx, bottom) 234 unless ( defined $centerx or defined $bottom ); 235 236 if ( defined $bottom ) { 237 $self->top( $bottom - $self->height ); # y = bottom - height 238 } 239 if ( defined $centerx ) { 240 $self->left( $centerx - ( $self->width >> 1 ) ); # x = centerx - (width/2) 241 } 242 return; 243} 244 245############################### 246## methods ## 247############################### 248 249{ 250 no strict 'refs'; 251 *{'duplicate'} = *{copy}; 252} 253 254sub copy { 255 my $self = shift; 256 return $self->new( 257 $self->x, 258 $self->y, 259 $self->w, 260 $self->h, 261 ); 262} 263 264sub move { 265 my ( $self, $x, $y ) = (@_); 266 if ( not defined $x or not defined $y ) { 267 268 #require Carp; 269 Carp::confess "must receive x and y positions as argument"; 270 } 271 return $self->new( 272 $self->left + $x, 273 $self->top + $y, 274 $self->width, 275 $self->height, 276 ); 277} 278 279sub move_ip { 280 my ( $self, $x, $y ) = (@_); 281 if ( not defined $x or not defined $y ) { 282 283 #require Carp; 284 Carp::confess "must receive x and y positions as argument"; 285 } 286 $self->x( $self->x + $x ); 287 $self->y( $self->y + $y ); 288 289 return; 290} 291 292sub inflate { 293 my ( $self, $x, $y ) = (@_); 294 if ( not defined $x or not defined $y ) { 295 296 #require Carp; 297 Carp::confess "must receive x and y positions as argument"; 298 } 299 300 return $self->new( 301 $self->left - ( $x / 2 ), 302 $self->top - ( $y / 2 ), 303 $self->width + $x, 304 $self->height + $y, 305 ); 306} 307 308sub inflate_ip { 309 my ( $self, $x, $y ) = (@_); 310 if ( not defined $x or not defined $y ) { 311 312 #require Carp; 313 Carp::confess "must receive x and y positions as argument"; 314 } 315 316 $self->x( $self->x - ( $x / 2 ) ); 317 $self->y( $self->y - ( $y / 2 ) ); 318 319 $self->w( $self->w + $x ); 320 $self->h( $self->h + $y ); 321} 322 323sub _get_clamp_coordinates { 324 my ( $self_pos, $self_len, $rect_pos, $rect_len ) = (@_); 325 326 if ( $self_len >= $rect_len ) { 327 return $rect_pos + ( $rect_len / 2 ) - ( $self_len / 2 ); 328 } elsif ( $self_pos < $rect_pos ) { 329 return $rect_pos; 330 } elsif ( ( $self_pos + $self_len ) > ( $rect_pos + $rect_len ) ) { 331 return $rect_pos + $rect_len - $self_len; 332 } else { 333 return $self_pos; 334 } 335} 336 337sub clamp { 338 my ( $self, $rect ) = (@_); 339 340 unless ( $rect->isa('SDL::Rect') ) { 341 Carp::confess "must receive an SDL::Rect-based object"; 342 } 343 344 my $x = _get_clamp_coordinates( $self->x, $self->w, $rect->x, $rect->w ); 345 my $y = _get_clamp_coordinates( $self->y, $self->h, $rect->y, $rect->h ); 346 347 return $self->new( $x, $y, $self->w, $self->h ); 348} 349 350sub clamp_ip { 351 my ( $self, $rect ) = (@_); 352 353 unless ( $rect->isa('SDL::Rect') ) { 354 Carp::confess "must receive an SDL::Rect-based object"; 355 } 356 357 my $x = _get_clamp_coordinates( $self->x, $self->w, $rect->x, $rect->w ); 358 my $y = _get_clamp_coordinates( $self->y, $self->h, $rect->y, $rect->h ); 359 360 $self->x($x); 361 $self->y($y); 362 363 return; 364} 365 366sub _get_intersection_coordinates { 367 my ( $self, $rect ) = (@_); 368 my ( $x, $y, $w, $h ); 369 370 INTERSECTION: 371 { 372 ### Left 373 if ( ( $self->x >= $rect->x ) 374 && ( $self->x < ( $rect->x + $rect->w ) ) ) 375 { 376 $x = $self->x; 377 } elsif ( ( $rect->x >= $self->x ) 378 && ( $rect->x < ( $self->x + $self->w ) ) ) 379 { 380 $x = $rect->x; 381 } else { 382 last INTERSECTION; 383 } 384 385 ## Right 386 if ( ( ( $self->x + $self->w ) > $rect->x ) 387 && ( ( $self->x + $self->w ) <= ( $rect->x + $rect->w ) ) ) 388 { 389 $w = ( $self->x + $self->w ) - $x; 390 } elsif ( ( ( $rect->x + $rect->w ) > $self->x ) 391 && ( ( $rect->x + $rect->w ) <= ( $self->x + $self->w ) ) ) 392 { 393 $w = ( $rect->x + $rect->w ) - $x; 394 } else { 395 last INTERSECTION; 396 } 397 398 ## Top 399 if ( ( $self->y >= $rect->y ) 400 && ( $self->y < ( $rect->y + $rect->h ) ) ) 401 { 402 $y = $self->y; 403 } elsif ( ( $rect->y >= $self->y ) 404 && ( $rect->y < ( $self->y + $self->h ) ) ) 405 { 406 $y = $rect->y; 407 } else { 408 last INTERSECTION; 409 } 410 411 ## Bottom 412 if ( ( ( $self->y + $self->h ) > $rect->y ) 413 && ( ( $self->y + $self->h ) <= ( $rect->y + $rect->h ) ) ) 414 { 415 $h = ( $self->y + $self->h ) - $y; 416 } elsif ( ( ( $rect->y + $rect->h ) > $self->y ) 417 && ( ( $rect->y + $rect->h ) <= ( $self->y + $self->h ) ) ) 418 { 419 $h = ( $rect->y + $rect->h ) - $y; 420 } else { 421 last INTERSECTION; 422 } 423 424 return ( $x, $y, $w, $h ); 425 } 426 427 # if we got here, the two rects do not intersect 428 return ( $self->x, $self->y, 0, 0 ); 429 430} 431 432sub clip { 433 my ( $self, $rect ) = (@_); 434 435 unless ( $rect->isa('SDL::Rect') ) { 436 Carp::confess "must receive an SDL::Rect-based object"; 437 } 438 439 my ( $x, $y, $w, $h ) = _get_intersection_coordinates( $self, $rect ); 440 441 return $self->new( $x, $y, $w, $h ); 442} 443 444sub clip_ip { 445 my ( $self, $rect ) = (@_); 446 447 unless ( $rect->isa('SDL::Rect') ) { 448 Carp::confess "must receive an SDL::Rect-based object"; 449 } 450 451 my ( $x, $y, $w, $h ) = _get_intersection_coordinates( $self, $rect ); 452 453 $self->x($x); 454 $self->y($y); 455 $self->w($w); 456 $self->h($h); 457 458 return; 459} 460 461sub _test_union { 462 my ( $self, $rect ) = (@_); 463 my ( $x, $y, $w, $h ); 464 465 $x = $self->x < $rect->x ? $self->x : $rect->x; # MIN 466 $y = $self->y < $rect->y ? $self->y : $rect->y; # MIN 467 468 $w = 469 ( $self->x + $self->w ) > ( $rect->x + $rect->w ) 470 ? ( $self->x + $self->w ) - $x 471 : ( $rect->x + $rect->w ) - $x; # MAX 472 473 $h = 474 ( $self->y + $self->h ) > ( $rect->y + $rect->h ) 475 ? ( $self->y + $self->h ) - $y 476 : ( $rect->y + $rect->h ) - $y; # MAX 477 478 return ( $x, $y, $w, $h ); 479} 480 481sub union { 482 my ( $self, $rect ) = (@_); 483 484 unless ( $rect->isa('SDL::Rect') ) { 485 Carp::confess "must receive an SDL::Rect-based object"; 486 } 487 488 my ( $x, $y, $w, $h ) = _test_union( $self, $rect ); 489 return $self->new( $x, $y, $w, $h ); 490} 491 492sub union_ip { 493 my ( $self, $rect ) = (@_); 494 495 unless ( $rect->isa('SDL::Rect') ) { 496 Carp::confess "must receive an SDL::Rect-based object"; 497 } 498 499 my ( $x, $y, $w, $h ) = _test_union( $self, $rect ); 500 501 $self->x($x); 502 $self->y($y); 503 $self->w($w); 504 $self->y($h); 505 506 return; 507} 508 509sub _test_unionall { 510 my ( $self, $rects ) = (@_); 511 512 # initial values for union rect 513 my $left = $self->x; 514 my $top = $self->y; 515 my $right = $self->x + $self->w; 516 my $bottom = $self->y + $self->h; 517 518 foreach my $rect ( @{$rects} ) { 519 unless ( $rect->isa('SDL::Rect') ) { 520 521 # TODO: better error message, maybe saying which item 522 # is the bad one (by list position) 523 Carp::confess "must receive an array reference of SDL::Rect-based objects"; 524 } 525 526 $left = $rect->x if $rect->x < $left; # MIN 527 $top = $rect->y if $rect->y < $top; # MIN 528 $right = ( $rect->x + $rect->w ) 529 if ( $rect->x + $rect->w ) > $right; # MAX 530 $bottom = ( $rect->y + $rect->h ) 531 if ( $rect->y + $rect->h ) > $bottom; # MAX 532 } 533 534 return ( $left, $top, $right - $left, $bottom - $top ); 535} 536 537sub unionall { 538 my ( $self, $rects ) = (@_); 539 540 unless ( defined $rects and ref $rects eq 'ARRAY' ) { 541 Carp::confess "must receive an array reference of SDL::Rect-based objects"; 542 } 543 544 my ( $x, $y, $w, $h ) = _test_unionall( $self, $rects ); 545 546 return $self->new( $x, $y, $w, $h ); 547} 548 549sub unionall_ip { 550 my ( $self, $rects ) = (@_); 551 552 unless ( defined $rects and ref $rects eq 'ARRAY' ) { 553 Carp::confess "must receive an array reference of SDL::Rect-based objects"; 554 } 555 556 my ( $x, $y, $w, $h ) = _test_unionall( $self, $rects ); 557 558 $self->x($x); 559 $self->y($y); 560 $self->w($w); 561 $self->h($h); 562 563 return; 564} 565 566sub _check_fit { 567 my ( $self, $rect ) = (@_); 568 569 my $x_ratio = $self->w / $rect->w; 570 my $y_ratio = $self->h / $rect->h; 571 my $max_ratio = ( $x_ratio > $y_ratio ) ? $x_ratio : $y_ratio; 572 573 my $w = int( $self->w / $max_ratio ); 574 my $h = int( $self->h / $max_ratio ); 575 576 my $x = $rect->x + int( ( $rect->w - $w ) / 2 ); 577 my $y = $rect->y + int( ( $rect->h - $h ) / 2 ); 578 579 return ( $x, $y, $w, $h ); 580} 581 582sub fit { 583 my ( $self, $rect ) = (@_); 584 585 unless ( $rect->isa('SDL::Rect') ) { 586 Carp::confess "must receive an SDL::Rect-based object"; 587 } 588 589 my ( $x, $y, $w, $h ) = _check_fit( $self, $rect ); 590 591 return $self->new( $x, $y, $w, $h ); 592} 593 594sub fit_ip { 595 my ( $self, $rect ) = (@_); 596 597 unless ( $rect->isa('SDL::Rect') ) { 598 Carp::confess "must receive an SDL::Rect-based object"; 599 } 600 601 my ( $x, $y, $w, $h ) = _check_fit( $self, $rect ); 602 603 $self->x($x); 604 $self->y($y); 605 $self->w($w); 606 $self->h($h); 607 608 return; 609} 610 611sub normalize { 612 my $self = shift; 613 614 if ( $self->w < 0 ) { 615 $self->x( $self->x + $self->w ); 616 $self->w( -$self->w ); 617 } 618 619 if ( $self->h < 0 ) { 620 $self->y( $self->y + $self->h ); 621 $self->h( -$self->h ); 622 } 623 return; 624} 625 626sub contains { 627 my ( $self, $rect ) = (@_); 628 629 unless ( $rect->isa('SDL::Rect') ) { 630 Carp::confess "must receive an SDL::Rect-based object"; 631 } 632 633 my $contained = 634 ( $self->x <= $rect->x ) 635 && ( $self->y <= $rect->y ) 636 && ( $self->x + $self->w >= $rect->x + $rect->w ) 637 && ( $self->y + $self->h >= $rect->y + $rect->h ) 638 && ( $self->x + $self->w > $rect->x ) 639 && ( $self->y + $self->h > $rect->y ); 640 641 return $contained; 642} 643 644sub collidepoint { 645 my ( $self, $x, $y ) = (@_); 646 647 unless ( defined $x and defined $y ) { 648 Carp::confess "must receive (x,y) as arguments"; 649 } 650 651 my $inside = 652 $x >= $self->x 653 && $x < $self->x + $self->w 654 && $y >= $self->y 655 && $y < $self->y + $self->h; 656 657 return $inside; 658} 659 660sub _do_rects_intersect { 661 my ( $rect_A, $rect_B ) = (@_); 662 663 return ( 664 ( $rect_A->x >= $rect_B->x && $rect_A->x < $rect_B->x + $rect_B->w ) || ( $rect_B->x >= $rect_A->x 665 && $rect_B->x < $rect_A->x + $rect_A->w ) 666 ) 667 && ( ( $rect_A->y >= $rect_B->y && $rect_A->y < $rect_B->y + $rect_B->h ) 668 || ( $rect_B->y >= $rect_A->y && $rect_B->y < $rect_A->y + $rect_A->h ) ); 669} 670 671sub colliderect { 672 my ( $self, $rect ) = (@_); 673 674 unless ( $rect->isa('SDL::Rect') ) { 675 Carp::confess "must receive an SDL::Rect-based object"; 676 } 677 678 return _do_rects_intersect( $self, $rect ); 679} 680 681sub collidelist { 682 my ( $self, $rects ) = (@_); 683 684 unless ( defined $rects and ref $rects eq 'ARRAY' ) { 685 Carp::confess "must receive an array reference of SDL::Rect-based objects"; 686 } 687 688 for ( my $i = 0; $i < @{$rects}; $i++ ) { 689 if ( _do_rects_intersect( $self, $rects->[$i] ) ) { 690 return $i; 691 } 692 } 693 return; 694} 695 696sub collidelistall { 697 my ( $self, $rects ) = (@_); 698 699 unless ( defined $rects and ref $rects eq 'ARRAY' ) { 700 Carp::confess "must receive an array reference of SDL::Rect-based objects"; 701 } 702 703 my @collisions = (); 704 for ( my $i = 0; $i < @{$rects}; $i++ ) { 705 if ( _do_rects_intersect( $self, $rects->[$i] ) ) { 706 push @collisions, $i; 707 } 708 } 709 return \@collisions; 710} 711 712sub collidehash { 713 my ( $self, $rects ) = (@_); 714 715 unless ( defined $rects and ref $rects eq 'HASH' ) { 716 Carp::confess "must receive an hash reference of SDL::Rect-based objects"; 717 } 718 719 while ( my ( $key, $value ) = each %{$rects} ) { 720 unless ( $value->isa('SDL::Rect') ) { 721 Carp::confess "hash element of key '$key' is not an SDL::Rect-based object"; 722 } 723 724 if ( _do_rects_intersect( $self, $value ) ) { 725 return ( $key, $value ); 726 } 727 } 728 return ( undef, undef ); 729} 730 731sub collidehashall { 732 my ( $self, $rects ) = (@_); 733 734 unless ( defined $rects and ref $rects eq 'HASH' ) { 735 Carp::confess "must receive an hash reference of SDL::Rect-based objects"; 736 } 737 738 my %collisions = (); 739 while ( my ( $key, $value ) = each %{$rects} ) { 740 unless ( $value->isa('SDL::Rect') ) { 741 Carp::confess "hash element of key '$key' is not an SDL::Rect-based object"; 742 } 743 744 if ( _do_rects_intersect( $self, $value ) ) { 745 $collisions{$key} = $value; 746 } 747 } 748 return \%collisions; 749} 750 7511; #NOT 42! 752 753