File Coverage

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


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