File Coverage

blib/lib/Math/Polygon/Transform.pm
Criterion Covered Total %
statement 178 179 99.4
branch 60 66 90.9
condition 52 63 82.5
subroutine 14 14 100.0
pod 6 6 100.0
total 310 328 94.5


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