File Coverage

blib/lib/Math/Polygon/Clip.pm
Criterion Covered Total %
statement 83 96 86.4
branch 33 50 66.0
condition 36 45 80.0
subroutine 13 15 86.6
pod 2 2 100.0
total 167 208 80.2


line stmt bran cond sub pod time code
1             # Copyrights 2004,2006-2016 by [Mark Overmeer].
2             # For other contributors see ChangeLog.
3             # See the manual pages for details on the licensing terms.
4             # Pod stripped from pm file by OODoc 2.02.
5              
6 6     6   42997 use strict;
  6         7  
  6         162  
7 6     6   23 use warnings;
  6         7  
  6         183  
8              
9             package Math::Polygon::Clip;
10 6     6   19 use vars '$VERSION';
  6         6  
  6         275  
11             $VERSION = '1.05';
12              
13 6     6   19 use base 'Exporter';
  6         7  
  6         482  
14              
15             our @EXPORT = qw/
16             polygon_line_clip
17             polygon_fill_clip1
18             /;
19              
20 6     6   1057 use Math::Polygon::Calc;
  6         9  
  6         517  
21 6     6   27 use List::Util qw/min max/;
  6         7  
  6         5113  
22              
23             sub _inside($$);
24             sub _cross($$$);
25             sub _cross_inside($$$);
26             sub _cross_x($$$);
27             sub _cross_y($$$);
28             sub _remove_doubles(@);
29              
30              
31             sub polygon_fill_clip1($@)
32 2     2 1 16 { my $bbox = shift;
33 2         3 my ($xmin, $ymin, $xmax, $ymax) = @$bbox;
34 2 50       6 @_ or return (); # empty list of points
35              
36             # Collect all crosspoints with axes, plus the original points
37 2         3 my $next = shift;
38 2         3 my @poly = $next;
39 2         4 while(@_)
40 8         8 { $next = shift;
41 8         13 push @poly, _cross($bbox, $poly[-1], $next), $next;
42             }
43              
44             # crop them to the borders: outside is projected on the sides
45 2         2 my @cropped;
46 2         3 foreach (@poly)
47 14         10 { my ($x,$y) = @$_;
48 14 50       16 $x = $xmin if $x < $xmin;
49 14 100       18 $x = $xmax if $x > $xmax;
50 14 50       16 $y = $ymin if $y < $ymin;
51 14 100       16 $y = $ymax if $y > $ymax;
52 14         17 push @cropped, [$x, $y];
53             }
54              
55 2         7 polygon_beautify {despike => 1}, @cropped;
56             }
57              
58              
59             sub polygon_line_clip($@)
60 11     11 1 24937 { my $bbox = shift;
61 11         17 my ($xmin, $ymin, $xmax, $ymax) = @$bbox;
62              
63 11         10 my @frags;
64 11         10 my $from = shift;
65 11         18 my $fromin = _inside $bbox, $from;
66 11 100       24 push @frags, [ $from ] if $fromin;
67              
68 11         21 while(@_)
69 65         45 { my $next = shift;
70 65         61 my $nextin = _inside $bbox, $next;
71              
72 65 100 100     199 if($fromin && $nextin) # stay within
    100 66        
    100          
73 20         16 { push @{$frags[-1]}, $next;
  20         24  
74             }
75             elsif($fromin && !$nextin) # leaving
76 12         11 { push @{$frags[-1]}, _cross_inside $bbox, $from, $next;
  12         19  
77             }
78             elsif($nextin) # entering
79 12         15 { my @cross = _cross_inside $bbox, $from, $next;
80 12         18 push @frags, [ @cross, $next ];
81             }
82             else # pass thru bbox?
83 21         33 { my @cross = _cross_inside $bbox, $from, $next;
84 21 100       31 push @frags, \@cross if @cross;
85             }
86              
87 65         105 ($from, $fromin) = ($next, $nextin);
88             }
89              
90             # Glue last to first?
91 11 100 100     44 if( @frags >= 2
      66        
92             && $frags[0][0][0] == $frags[-1][-1][0] # X
93             && $frags[0][0][1] == $frags[-1][-1][1] # Y
94             )
95 4         8 { my $last = pop @frags;
96 4         4 pop @$last;
97 4         3 unshift @{$frags[0]}, @$last;
  4         10  
98             }
99              
100 11         25 @frags;
101             }
102              
103             #
104             ### Some helper functions
105             #
106              
107             sub _inside($$)
108 101     101   68 { my ($bbox, $point) = @_;
109              
110 101 100 100     553 $bbox->[0] <= $point->[0]+0.00001
      100        
111             && $point->[0] <= $bbox->[2]+0.00001 # X
112             && $bbox->[1] <= $point->[1]+0.00001
113             && $point->[1] <= $bbox->[3]+0.00001; # Y
114             }
115              
116             sub _sector($$) # left-top 678,345,012 right-bottom
117 0     0   0 { my ($bbox, $point) = @_;
118 0 0       0 my $xsector = $point->[0] < $bbox->[0] ? 0
    0          
119             : $point->[0] < $bbox->[2] ? 1
120             : 2;
121 0 0       0 my $ysector = $point->[1] < $bbox->[1] ? 0
    0          
122             : $point->[1] < $bbox->[3] ? 1
123             : 2;
124 0         0 $ysector * 3 + $xsector;
125             }
126              
127             sub _cross($$$)
128 61     61   12629 { my ($bbox, $from, $to) = @_;
129 61         64 my ($xmin, $ymin, $xmax, $ymax) = @$bbox;
130              
131 61         71 my @cross = ( _cross_x($xmin, $from, $to)
132             , _cross_x($xmax, $from, $to)
133             , _cross_y($ymin, $from, $to)
134             , _cross_y($ymax, $from, $to)
135             );
136              
137             # order the results
138 8         18 $from->[0] < $to->[0] ? sort({$a->[0] <=> $b->[0]} @cross)
139 8         31 : $from->[0] > $to->[0] ? sort({$b->[0] <=> $a->[0]} @cross)
140 0         0 : $from->[1] < $to->[1] ? sort({$a->[1] <=> $b->[1]} @cross)
141 61 100       191 : sort({$b->[1] <=> $a->[1]} @cross);
  0 100       0  
    100          
142             }
143              
144             sub _cross_inside($$$)
145 45     45   35 { my ($bbox, $from, $to) = @_;
146 45         48 grep { _inside($bbox, $_) } _cross($bbox, $from, $to);
  25         25  
147             }
148              
149             sub _remove_doubles(@)
150 0 0   0   0 { my $this = shift or return ();
151 0         0 my @ret = $this;
152 0         0 while(@_)
153 0         0 { my $this = shift;
154 0 0 0     0 next if $this->[0]==$ret[-1][0] && $this->[1]==$ret[-1][1];
155 0         0 push @ret, $this;
156             }
157 0         0 @ret;
158             }
159              
160             sub _cross_x($$$)
161 126     126   3581 { my ($x, $from, $to) = @_;
162 126         85 my ($fx, $fy) = @$from;
163 126         89 my ($tx, $ty) = @$to;
164 126 100 100     583 return () unless $fx < $x && $x < $tx || $tx < $x && $x < $fx;
      100        
      66        
165 28         52 my $y = $fy + ($x - $fx)/($tx - $fx) * ($ty - $fy);
166             #warn "X: $x,$y <-- $fx,$fy $tx,$ty\n";
167 28 50 66     154 (($fy <= $y && $y <= $ty) || ($ty <= $y && $y <= $fy)) ? [$x,$y] : ();
168             }
169              
170             sub _cross_y($$$)
171 126     126   4164 { my ($y, $from, $to) = @_;
172 126         91 my ($fx, $fy) = @$from;
173 126         75 my ($tx, $ty) = @$to;
174 126 100 100     488 return () unless $fy < $y && $y < $ty || $ty < $y && $y < $fy;
      100        
      66        
175 27         43 my $x = $fx + ($y - $fy)/($ty - $fy) * ($tx - $fx);
176             #warn "Y: $x,$y <-- $fx,$fy $tx,$ty\n";
177 27 50 66     145 (($fx <= $x && $x <= $tx) || ($tx <= $x && $x <= $fx)) ? [$x,$y] : ();
178             }
179              
180              
181              
182             1;