File Coverage

blib/lib/Math/Polygon/Transform.pm
Criterion Covered Total %
statement 178 179 99.4
branch 60 66 90.9
condition 51 63 80.9
subroutine 14 14 100.0
pod 6 6 100.0
total 309 328 94.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 9     9   85392 use strict;
  9         12  
  9         214  
6 9     9   29 use warnings;
  9         9  
  9         288  
7              
8             package Math::Polygon::Transform;
9 9     9   28 use vars '$VERSION';
  9         10  
  9         472  
10             $VERSION = '1.05';
11              
12 9     9   31 use base 'Exporter';
  9         7  
  9         739  
13              
14 9     9   3898 use Math::Trig qw/deg2rad pi rad2deg/;
  9         98268  
  9         637  
15 9     9   4085 use POSIX qw/floor/;
  9         42640  
  9         40  
16 9     9   7310 use Carp qw/carp/;
  9         13  
  9         12092  
17              
18             our @EXPORT = qw/
19             polygon_resize
20             polygon_move
21             polygon_rotate
22             polygon_grid
23             polygon_mirror
24             polygon_simplify
25             /;
26              
27              
28             sub polygon_resize(@)
29 7     7 1 20 { my %opts;
30 7   66     32 while(@_ && !ref $_[0])
31 8         8 { my $key = shift;
32 8         29 $opts{$key} = shift;
33             }
34              
35 7   100     26 my $sx = $opts{xscale} || $opts{scale} || 1.0;
36 7   100     22 my $sy = $opts{yscale} || $opts{scale} || 1.0;
37 7 100 100     30 return @_ if $sx==1.0 && $sy==1.0;
38              
39 5 100       11 my ($cx, $cy) = defined $opts{center} ? @{$opts{center}} : (0,0);
  1         2  
40              
41 5 100 66     18 return map { [ $_->[0]*$sx, $_->[1]*$sy ] } @_
  20         35  
42             unless $cx || $cy;
43            
44 1         3 map { [ $cx + ($_->[0]-$cx)*$sx, $cy + ($_->[1]-$cy) * $sy ] } @_;
  5         10  
45             }
46              
47              
48             sub polygon_move(@)
49 3     3 1 12 { my %opts;
50 3   66     18 while(@_ && !ref $_[0])
51 4         3 { my $key = shift;
52 4         14 $opts{$key} = shift;
53             }
54              
55 3   100     15 my ($dx, $dy) = ($opts{dx}||0, $opts{dy}||0);
      100        
56 3 100 66     17 return @_ if $dx==0 && $dy==0;
57              
58 1         2 map { [ $_->[0] +$dx, $_->[1] +$dy ] } @_;
  5         10  
59             }
60              
61              
62             sub polygon_rotate(@)
63 6     6 1 19 { my %opts;
64 6   66     30 while(@_ && !ref $_[0])
65 8         9 { my $key = shift;
66 8         29 $opts{$key} = shift;
67             }
68              
69             my $angle
70             = exists $opts{radians} ? $opts{radians}
71             : exists $opts{degrees} ? deg2rad($opts{degrees})
72 6 50       23 : 0;
    100          
73              
74 6 100       59 return @_ unless $angle;
75              
76 3         15 my $sina = sin($angle);
77 3         20 my $cosa = cos($angle);
78              
79 3 100       7 my ($cx, $cy) = defined $opts{center} ? @{$opts{center}} : (0,0);
  1         2  
80 3 100 66     10 unless($cx || $cy)
81 2         19 { return map { [ $cosa * $_->[0] + $sina * $_->[1]
  10         29  
82             , -$sina * $_->[0] + $cosa * $_->[1]
83             ] } @_;
84             }
85              
86 1         2 map { [ $cx + $cosa * ($_->[0]-$cx) + $sina * ($_->[1]-$cy)
  5         16  
87             , $cy + -$sina * ($_->[0]-$cx) + $cosa * ($_->[1]-$cy)
88             ] } @_;
89             }
90              
91              
92             sub polygon_grid(@)
93 4     4 1 17 { my %opts;
94 4   66     23 while(@_ && !ref $_[0])
95 3         5 { my $key = shift;
96 3         17 $opts{$key} = shift;
97             }
98              
99 4 100       10 my $raster = exists $opts{raster} ? $opts{raster} : 1;
100 4 100       15 return @_ if $raster == 0;
101              
102             # use fast "int" for gridsize 1
103 3 100 100     14 return map { [ floor($_->[0] + 0.5), floor($_->[1] + 0.5) ] } @_
  3         29  
104             if $raster > 0.99999 && $raster < 1.00001;
105              
106 2         5 map { [ $raster * floor($_->[0]/$raster + 0.5)
  6         26  
107             , $raster * floor($_->[1]/$raster + 0.5)
108             ] } @_;
109             }
110              
111              
112             sub polygon_mirror(@)
113 8     8 1 25 { my %opts;
114 8   66     42 while(@_ && !ref $_[0])
115 10         12 { my $key = shift;
116 10         43 $opts{$key} = shift;
117             }
118              
119 8 100       18 if(defined $opts{x})
120 1         3 { my $x2 = 2* $opts{x};
121 1         3 return map { [ $x2 - $_->[0], $_->[1] ] } @_;
  5         16  
122             }
123              
124 7 100       15 if(defined $opts{y})
125 1         2 { my $y2 = 2* $opts{y};
126 1         4 return map { [ $_->[0], $y2 - $_->[1] ] } @_;
  5         14  
127             }
128              
129             # Mirror in line
130              
131 6         4 my ($rc, $b);
132 6 100       16 if(exists $opts{rc} )
    50          
133 3         4 { $rc = $opts{rc};
134 3   100     10 $b = $opts{b} || 0;
135             }
136             elsif(my $through = $opts{line})
137 3         6 { my ($p0, $p1) = @$through;
138 3 100       7 if($p0->[0]==$p1->[0])
139 1         2 { $b = $p0->[0]; # vertikal mirror
140             }
141             else
142 2         4 { $rc = ($p1->[1] - $p0->[1]) / ($p1->[0] - $p0->[0]);
143 2         5 $b = $p0->[1] - $p0->[0] * $rc;
144             }
145             }
146             else
147 0         0 { carp "ERROR: you need to specify 'x', 'y', 'rc', or 'line'";
148             }
149              
150 6 100       11 unless(defined $rc) # vertical
151 2         3 { my $x2 = 2* $b;
152 2         5 return map { [ $x2 - $_->[0], $_->[1] ] } @_;
  10         22  
153             }
154              
155             # mirror is y=x*rc+b, y=-x/rc+c through mirrored point
156 4         7 my $yf = 2/($rc*$rc +1);
157 4         6 my $xf = $yf * $rc;
158              
159 4         6 map { my $c = $_->[1] + $_->[0]/$rc;
  20         23  
160 20         44 [ $xf*($c-$b) - $_->[0], $yf*($b-$c) + 2*$c - $_->[1] ] } @_;
161             }
162              
163              
164             sub _angle($$$)
165 10     10   8 { my ($p0, $p1, $p2) = @_;
166 10         19 my $a0 = atan2($p0->[1] - $p1->[1], $p0->[0] - $p1->[0]);
167 10         14 my $a1 = atan2($p2->[1] - $p1->[1], $p2->[0] - $p1->[0]);
168 10         6 my $a = abs($a0 - $a1);
169 10 100       14 $a = 2*pi - $a if $a > pi;
170 10         24 $a;
171             }
172              
173             sub polygon_simplify(@)
174 11     11 1 23 { my %opts;
175 11   66     54 while(@_ && !ref $_[0])
176 8         11 { my $key = shift;
177 8         34 $opts{$key} = shift;
178             }
179              
180 11 50       20 return unless @_;
181              
182 11   100     39 my $is_ring = $_[0][0]==$_[-1][0] && $_[0][1]==$_[-1][1];
183              
184 11   100     27 my $same = $opts{same} || 0.0001;
185 11         11 my $slope = $opts{slope};
186              
187 11         7 my $changes = 1;
188              
189 11   66     40 while($changes && @_)
190             {
191 22         12 $changes = 0;
192 22         17 my @new;
193              
194 22         18 my $p = shift;
195 22         28 while(@_)
196 90         85 { my ($x, $y) = @$p;
197              
198 90         51 my ($nx, $ny) = @{$_[0]};
  90         92  
199 90         131 my $d01 = sqrt(($nx-$x)*($nx-$x) + ($ny-$y)*($ny-$y));
200 90 100       136 if($d01 < $same)
201 16         10 { $changes++;
202              
203             # point within threshold: middle, unless we are at the
204             # start of the polygo description: that one has a slight
205             # preference, to avoid an endless loop.
206 16 100       36 push @new, !@new ? [ ($x,$y) ] : [ ($x+$nx)/2, ($y+$ny)/2 ];
207 16         11 shift; # remove next
208 16         14 $p = shift; # 2nd as new current
209 16         25 next;
210             }
211              
212 74 100 100     178 unless(@_ >= 2 && defined $slope)
213 67         47 { push @new, $p; # keep this
214 67         45 $p = shift; # check next
215 67         88 next;
216             }
217              
218 7         6 my ($sx,$sy) = @{$_[1]};
  7         9  
219 7         10 my $d12 = sqrt(($sx-$nx)*($sx-$nx) + ($sy-$ny)*($sy-$ny));
220 7         9 my $d02 = sqrt(($sx-$x) *($sx-$x) + ($sy-$y) *($sy-$y) );
221              
222 7 100       13 if($d01 + $d12 <= $d02 + $slope)
223             { # three points nearly on a line, remove middle
224 2         1 $changes++;
225 2         3 push @new, $p, $_[1];
226 2         3 shift; shift;
  2         0  
227 2         3 $p = shift; # jump over next
228 2         23 next;
229             }
230              
231 5 100 100     21 if(@_ > 2 && abs($d01-$d12-$d02) < $slope)
232             { # check possibly a Z shape
233 1         2 my ($tx,$ty) = @{$_[2]};
  1         3  
234 1         4 my $d03 = sqrt(($tx-$x) *($tx-$x) + ($ty-$y) *($ty-$y));
235 1         3 my $d13 = sqrt(($tx-$nx)*($tx-$nx) + ($ty-$ny)*($ty-$ny));
236              
237 1 50       5 if($d01 - $d13 <= $d03 + $slope)
238 1         1 { $changes++;
239 1         3 push @new, $p, $_[2]; # accept 1st and 4th
240 1         3 splice @_, 0, 3; # jump over handled three!
241 1         1 $p = shift;
242 1         5 next;
243             }
244             }
245              
246 4         5 push @new, $p; # nothing for this one.
247 4         5 $p = shift;
248             }
249 22 100       33 push @new, $p if defined $p;
250              
251 22 100 66     65 unshift @new, $new[-1] # be sure to keep ring closed
      66        
252             if $is_ring && ($new[0][0]!=$new[-1][0] || $new[0][1]!=$new[-1][1]);
253              
254 22         79 @_ = @new;
255             }
256              
257 11 100       61 return @_ unless exists $opts{max_points};
258              
259             #
260             # Reduce the number of points to $max
261             #
262              
263             # Collect all angles
264 2         1 my $max_angles = $opts{max_points};
265 2         2 my @angles;
266              
267 2 100       4 if($is_ring)
268 1 50       2 { return @_ if @_ <= $max_angles;
269 1         1 pop @_;
270 1         4 push @angles, [0, _angle($_[-1], $_[0], $_[1])]
271             , [$#_, _angle($_[-2], $_[-1], $_[0])];
272             }
273             else
274 1 50       4 { return @_ if @_ <= $max_angles;
275 1         1 $max_angles -= 2;
276             }
277              
278 2         7 foreach (my $i=1; $i<@_-1; $i++)
279 8         12 { push @angles, [$i, _angle($_[$i-1], $_[$i], $_[$i+1]) ];
280             }
281              
282             # Strip widest angles
283 2         8 @angles = sort { $b->[1] <=> $a->[1] } @angles;
  15         13  
284 2         9 while(@angles > $max_angles)
285 3         4 { my $point = shift @angles;
286 3         7 $_[$point->[0]] = undef;
287             }
288              
289             # Return left-over points
290 2         3 @_ = grep {defined} @_;
  12         13  
291 2 100       5 push @_, $_[0] if $is_ring;
292 2         11 @_;
293             }
294              
295             1;