File Coverage

blib/lib/Math/Polygon/Transform.pm
Criterion Covered Total %
statement 168 175 96.0
branch 66 78 84.6
condition 52 63 82.5
subroutine 13 13 100.0
pod 6 6 100.0
total 305 335 91.0


line stmt bran cond sub pod time code
1             # This code is part of Perl distribution Math-Polygon version 2.00.
2             # The POD got stripped from this file by OODoc version 3.03.
3             # For contributors see file ChangeLog.
4              
5             # This software is copyright (c) 2004-2025 by Mark Overmeer.
6              
7             # This is free software; you can redistribute it and/or modify it under
8             # the same terms as the Perl 5 programming language system itself.
9             # SPDX-License-Identifier: Artistic-1.0-Perl OR GPL-1.0-or-later
10              
11             #oodist: *** DO NOT USE THIS VERSION FOR PRODUCTION ***
12             #oodist: This file contains OODoc-style documentation which will get stripped
13             #oodist: during its release in the distribution. You can use this file for
14             #oodist: testing, however the code of this development version may be broken!
15              
16             package Math::Polygon::Transform;{
17             our $VERSION = '2.00';
18             }
19              
20 9     9   911117 use parent 'Exporter';
  9         2276  
  9         67  
21              
22 9     9   680 use strict;
  9         22  
  9         312  
23 9     9   49 use warnings;
  9         18  
  9         755  
24              
25 9     9   3485 use Log::Report 'math-polygon';
  9         761042  
  9         66  
26 9     9   8342 use Math::Trig qw/deg2rad pi rad2deg/;
  9         134618  
  9         1020  
27 9     9   99 use POSIX qw/floor/;
  9         16  
  9         114  
28              
29             our @EXPORT = qw/
30             polygon_resize
31             polygon_move
32             polygon_rotate
33             polygon_grid
34             polygon_mirror
35             polygon_simplify
36             /;
37              
38             #--------------------
39              
40             sub polygon_resize(@)
41 7     7 1 207201 { my $args;
42 7 50       41 if(ref $_[0] eq 'HASH') { $args = shift }
  0         0  
43             else
44 7   66     49 { while(@_ && !ref $_[0])
45 8         19 { my $key = shift;
46 8         44 $args->{$key} = shift;
47             }
48             }
49              
50 7   100     37 my $sx = $args->{xscale} || $args->{scale} || 1.0;
51 7   100     30 my $sy = $args->{yscale} || $args->{scale} || 1.0;
52 7 100 100     42 return @_ if $sx==1.0 && $sy==1.0;
53              
54 5 100       18 my ($cx, $cy) = defined $args->{center} ? @{$args->{center}} : (0,0);
  1         5  
55              
56 5 100 66     77 $cx || $cy
57             ? map +[ $cx + ($_->[0]-$cx)*$sx, $cy + ($_->[1]-$cy) * $sy ], @_
58             : map +[ $_->[0]*$sx, $_->[1]*$sy ], @_;
59             }
60              
61              
62             sub polygon_move(@)
63 3     3 1 172110 { my $args;
64 3 50       22 if(ref $_[0] eq 'HASH') { $args = shift }
  0         0  
65             else
66 3   66     16 { while(@_ && !ref $_[0])
67 4         6 { my $key = shift;
68 4         13 $args->{$key} = shift;
69             }
70             }
71              
72 3   100     25 my ($dx, $dy) = ($args->{dx}||0, $args->{dy}||0);
      100        
73 3 100 66     18 return @_ if $dx==0 && $dy==0;
74              
75 1         8 map +[ $_->[0] +$dx, $_->[1] +$dy ], @_;
76             }
77              
78              
79             sub polygon_rotate(@)
80 6     6 1 255894 { my $args;
81 6 50       36 if(ref $_[0] eq 'HASH') { $args = shift }
  0         0  
82             else
83 6   66     36 { while(@_ && !ref $_[0])
84 8         19 { my $key = shift;
85 8         43 $args->{$key} = shift;
86             }
87             }
88              
89             my $angle
90             = exists $args->{radians} ? $args->{radians}
91             : exists $args->{degrees} ? deg2rad($args->{degrees})
92 6 50       37 : 0;
    100          
93              
94 6 100       115 $angle
95             or return @_;
96              
97 3         31 my $sina = sin($angle);
98 3         16 my $cosa = cos($angle);
99              
100 3 100       13 my ($cx, $cy) = defined $args->{center} ? @{$args->{center}} : (0,0);
  1         5  
101 3 100 66     47 $cx || $cy or return map +[
102             $cosa * $_->[0] + $sina * $_->[1],
103             -$sina * $_->[0] + $cosa * $_->[1],
104             ], @_;
105              
106 1         21 map +[
107             $cx + $cosa * ($_->[0]-$cx) + $sina * ($_->[1]-$cy),
108             $cy + -$sina * ($_->[0]-$cx) + $cosa * ($_->[1]-$cy),
109             ], @_;
110             }
111              
112              
113             sub polygon_grid(@)
114 4     4 1 232765 { my $args;
115 4 50       22 if(ref $_[0] eq 'HASH') { $args = shift }
  0         0  
116             else
117 4   66     18 { while(@_ && !ref $_[0])
118 3         6 { my $key = shift;
119 3         10 $args->{$key} = shift;
120             }
121             }
122              
123 4 100       9 my $raster = exists $args->{raster} ? $args->{raster} : 1;
124 4 100       31 return @_ if $raster == 0;
125              
126             # use fast "int" for gridsize 1
127 3 100 100     36 return map +[ floor($_->[0] + 0.5), floor($_->[1] + 0.5) ], @_
128             if $raster > 0.99999 && $raster < 1.00001;
129              
130 2         24 map +[ $raster * floor($_->[0]/$raster + 0.5), $raster * floor($_->[1]/$raster + 0.5) ], @_;
131             }
132              
133              
134             sub polygon_mirror(@)
135 8     8 1 204864 { my $args;
136 8 50       60 if(ref $_[0] eq 'HASH') { $args = shift }
  0         0  
137             else
138 8   66     66 { while(@_ && !ref $_[0])
139 10         30 { my $key = shift;
140 10         64 $args->{$key} = shift;
141             }
142             }
143              
144 8 100       45 if(defined $args->{x})
145 1         5 { my $x2 = 2* $args->{x};
146 1         47 return map +[ $x2 - $_->[0], $_->[1] ], @_;
147             }
148              
149 7 100       24 if(defined $args->{y})
150 1         4 { my $y2 = 2* $args->{y};
151 1         13 return map +[ $_->[0], $y2 - $_->[1] ], @_;
152             }
153              
154             # Mirror in line
155              
156 6         17 my ($rc, $b);
157 6 100       26 if(exists $args->{rc} )
    50          
158 3         10 { $rc = $args->{rc};
159 3   100     13 $b = $args->{b} || 0;
160             }
161             elsif(my $through = $args->{line})
162 3         10 { my ($p0, $p1) = @$through;
163 3 100       10 if($p0->[0]==$p1->[0])
164 1         5 { $b = $p0->[0]; # vertical mirror
165             }
166             else
167 2         10 { $rc = ($p1->[1] - $p0->[1]) / ($p1->[0] - $p0->[0]);
168 2         7 $b = $p0->[1] - $p0->[0] * $rc;
169             }
170             }
171             else
172 0         0 { error __"you need to specify 'x', 'y', 'rc', or 'line'";
173             }
174              
175 6 100       22 unless(defined $rc) # vertical
176 2         7 { my $x2 = 2* $b;
177 2         28 return map +[ $x2 - $_->[0], $_->[1] ], @_;
178             }
179              
180             # mirror is y=x*rc+b, y=-x/rc+c through mirrored point
181 4         17 my $yf = 2/($rc*$rc +1);
182 4         12 my $xf = $yf * $rc;
183              
184 4         13 map { my $c = $_->[1] + $_->[0]/$rc;
  20         51  
185 20         113 +[ $xf*($c-$b) - $_->[0], $yf*($b-$c) + 2*$c - $_->[1] ]
186             } @_;
187             }
188              
189              
190             sub _angle($$$)
191 10     10   35 { my ($p0, $p1, $p2) = @_;
192 10         35 my $a0 = atan2($p0->[1] - $p1->[1], $p0->[0] - $p1->[0]);
193 10         16 my $a1 = atan2($p2->[1] - $p1->[1], $p2->[0] - $p1->[0]);
194 10         11 my $a = abs($a0 - $a1);
195 10 100       18 $a = 2*pi - $a if $a > pi;
196 10         24 $a;
197             }
198              
199             sub polygon_simplify(@)
200 11     11 1 173160 { my $args;
201 11 50       36 if(ref $_[0] eq 'HASH') { $args = shift }
  0         0  
202             else
203 11   66     51 { while(@_ && !ref $_[0])
204 8         13 { my $key = shift;
205 8         31 $args->{$key} = shift;
206             }
207             }
208              
209 11 50       25 @_ or return ();
210              
211 11   100     64 my $is_ring = $_[0][0]==$_[-1][0] && $_[0][1]==$_[-1][1];
212 11   100     47 my $same = $args->{same} || 0.0001;
213 11         15 my $slope = $args->{slope};
214 11         29 my $changes = 1;
215              
216 11   66     35 while($changes && @_)
217 22         21 { $changes = 0;
218 22         18 my @new;
219              
220 22         23 my $p = shift;
221 22         31 while(@_)
222 90         105 { my ($x, $y) = @$p;
223              
224 90         82 my ($nx, $ny) = @{$_[0]};
  90         108  
225 90         139 my $d01 = sqrt(($nx-$x)*($nx-$x) + ($ny-$y)*($ny-$y));
226 90 100       102 if($d01 < $same)
227 16         15 { $changes++;
228              
229             # point within threshold: middle, unless we are at the
230             # start of the polygo description: that one has a slight
231             # preference, to avoid an endless loop.
232 16 100       36 push @new, !@new ? [ ($x,$y) ] : [ ($x+$nx)/2, ($y+$ny)/2 ];
233 16         13 shift; # remove next
234 16         21 $p = shift; # 2nd as new current
235 16         27 next;
236             }
237              
238 74 100 100     149 unless(@_ >= 2 && defined $slope)
239 67         60 { push @new, $p; # keep this
240 67         55 $p = shift; # check next
241 67         96 next;
242             }
243              
244 7         683 my ($sx,$sy) = @{$_[1]};
  7         13  
245 7         13 my $d12 = sqrt(($sx-$nx)*($sx-$nx) + ($sy-$ny)*($sy-$ny));
246 7         11 my $d02 = sqrt(($sx-$x) *($sx-$x) + ($sy-$y) *($sy-$y) );
247              
248 7 100       15 if($d01 + $d12 <= $d02 + $slope)
249             { # three points nearly on a line, remove middle
250 2         4 $changes++;
251 2         3 push @new, $p, $_[1];
252 2         2 shift; shift;
  2         2  
253 2         3 $p = shift; # jump over next
254 2         4 next;
255             }
256              
257 5 100 100     27 if(@_ > 2 && abs($d01-$d12-$d02) < $slope)
258             { # check possibly a Z shape
259 1         3 my ($tx,$ty) = @{$_[2]};
  1         2  
260 1         3 my $d03 = sqrt(($tx-$x) *($tx-$x) + ($ty-$y) *($ty-$y));
261 1         2 my $d13 = sqrt(($tx-$nx)*($tx-$nx) + ($ty-$ny)*($ty-$ny));
262              
263 1 50       4 if($d01 - $d13 <= $d03 + $slope)
264 1         2 { $changes++;
265 1         2 push @new, $p, $_[2]; # accept 1st and 4th
266 1         3 splice @_, 0, 3; # jump over handled three!
267 1         1 $p = shift;
268 1         3 next;
269             }
270             }
271              
272 4         4 push @new, $p; # nothing for this one.
273 4         7 $p = shift;
274             }
275 22 100       27 push @new, $p if defined $p;
276              
277 22 100 66     63 unshift @new, $new[-1] # be sure to keep ring closed
      100        
278             if $is_ring && ($new[0][0]!=$new[-1][0] || $new[0][1]!=$new[-1][1]);
279              
280 22         74 @_ = @new;
281             }
282              
283             exists $args->{max_points}
284 11 100       74 or return @_;
285              
286             #
287             # Reduce the number of points to $max
288             #
289              
290             # Collect all angles
291 2         3 my $max_angles = $args->{max_points};
292 2         2 my @angles;
293              
294 2 100       6 if($is_ring)
295 1 50       3 { return @_ if @_ <= $max_angles;
296 1         3 pop @_;
297 1         6 push @angles, [0, _angle($_[-1], $_[0], $_[1])], [$#_, _angle($_[-2], $_[-1], $_[0])];
298             }
299             else
300 1 50       3 { return @_ if @_ <= $max_angles;
301 1         2 $max_angles -= 2;
302             }
303              
304 2         5 foreach (my $i=1; $i<@_-1; $i++)
305 8         16 { push @angles, [$i, _angle($_[$i-1], $_[$i], $_[$i+1]) ];
306             }
307              
308             # Strip widest angles
309 2         34 @angles = sort { $b->[1] <=> $a->[1] } @angles;
  15         22  
310 2         8 while(@angles > $max_angles)
311 3         5 { my $point = shift @angles;
312 3         8 $_[$point->[0]] = undef;
313             }
314              
315             # Return left-over points
316 2         9 @_ = grep defined, @_;
317 2 100       4 push @_, $_[0] if $is_ring;
318 2         14 @_;
319             }
320              
321             1;