File Coverage

blib/lib/Math/Polygon/Clip.pm
Criterion Covered Total %
statement 82 95 86.3
branch 33 50 66.0
condition 38 45 84.4
subroutine 13 15 86.6
pod 2 2 100.0
total 168 207 81.1


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::Clip;{
17             our $VERSION = '2.00';
18             }
19              
20 6     6   369651 use parent 'Exporter';
  6         1082  
  6         50  
21              
22 6     6   687 use strict;
  6         31  
  6         183  
23 6     6   33 use warnings;
  6         20  
  6         540  
24              
25             our @EXPORT = qw/
26             polygon_line_clip
27             polygon_fill_clip1
28             /;
29              
30 6     6   1720 use Log::Report 'math-polygon';
  6         439703  
  6         36  
31 6     6   2222 use List::Util qw/min max/;
  6         12  
  6         502  
32              
33 6     6   1976 use Math::Polygon::Calc;
  6         20  
  6         9675  
34              
35             sub _inside($$);
36             sub _cross($$$);
37             sub _cross_inside($$$);
38             sub _cross_x($$$);
39             sub _cross_y($$$);
40             sub _remove_doubles(@);
41              
42             #--------------------
43              
44             sub polygon_fill_clip1($@)
45 2     2 1 193135 { my $bbox = shift;
46 2         7 my ($xmin, $ymin, $xmax, $ymax) = @$bbox;
47 2 50       10 @_ or return (); # empty list of points
48              
49             # Collect all crosspoints with axes, plus the original points
50 2         5 my $next = shift;
51 2         20 my @poly = $next;
52 2         7 while(@_)
53 8         13 { $next = shift;
54 8         22 push @poly, _cross($bbox, $poly[-1], $next), $next;
55             }
56              
57             # crop them to the borders: outside is projected on the sides
58 2         5 my @cropped;
59 2         7 foreach (@poly)
60 14         25 { my ($x,$y) = @$_;
61 14 50       26 $x = $xmin if $x < $xmin;
62 14 100       29 $x = $xmax if $x > $xmax;
63 14 50       30 $y = $ymin if $y < $ymin;
64 14 100       26 $y = $ymax if $y > $ymax;
65 14         38 push @cropped, [$x, $y];
66             }
67              
68 2         15 polygon_beautify +{despike => 1}, @cropped;
69             }
70              
71              
72             sub polygon_line_clip($@)
73 11     11 1 257469 { my $bbox = shift;
74 11         32 my ($xmin, $ymin, $xmax, $ymax) = @$bbox;
75              
76 11         34 my @frags;
77 11         19 my $from = shift;
78 11         44 my $fromin = _inside $bbox, $from;
79 11 100       50 push @frags, [ $from ] if $fromin;
80              
81 11         30 while(@_)
82 65         106 { my $next = shift;
83 65         92 my $nextin = _inside $bbox, $next;
84              
85 65 100 100     247 if($fromin && $nextin) # stay within
    100 66        
    100          
86 20         28 { push @{$frags[-1]}, $next;
  20         63  
87             }
88             elsif($fromin && !$nextin) # leaving
89 12         25 { push @{$frags[-1]}, _cross_inside $bbox, $from, $next;
  12         30  
90             }
91             elsif($nextin) # entering
92 12         33 { my @cross = _cross_inside $bbox, $from, $next;
93 12         30 push @frags, [ @cross, $next ];
94             }
95             else # pass thru bbox?
96 21         46 { my @cross = _cross_inside $bbox, $from, $next;
97 21 100       46 push @frags, \@cross if @cross;
98             }
99              
100 65         166 ($from, $fromin) = ($next, $nextin);
101             }
102              
103             # Glue last to first?
104 11 100 100     75 if( @frags >= 2
      66        
105             && $frags[0][0][0] == $frags[-1][-1][0] # X
106             && $frags[0][0][1] == $frags[-1][-1][1] # Y
107             )
108 4         8 { my $last = pop @frags;
109 4         10 pop @$last;
110 4         7 unshift @{$frags[0]}, @$last;
  4         32  
111             }
112              
113 11         47 @frags;
114             }
115              
116             #
117             ### Some helper functions
118             #
119              
120             sub _inside($$)
121 101     101   145 { my ($bbox, $point) = @_;
122              
123 101 100 100     694 $bbox->[0] <= $point->[0]+0.00001
      100        
124             && $point->[0] <= $bbox->[2]+0.00001 # X
125             && $bbox->[1] <= $point->[1]+0.00001
126             && $point->[1] <= $bbox->[3]+0.00001; # Y
127             }
128              
129             sub _sector($$) # left-top 678,345,012 right-bottom
130 0     0   0 { my ($bbox, $point) = @_;
131 0 0       0 my $xsector = $point->[0] < $bbox->[0] ? 0
    0          
132             : $point->[0] < $bbox->[2] ? 1
133             : 2;
134 0 0       0 my $ysector = $point->[1] < $bbox->[1] ? 0
    0          
135             : $point->[1] < $bbox->[3] ? 1
136             : 2;
137 0         0 $ysector * 3 + $xsector;
138             }
139              
140             sub _cross($$$)
141 61     61   29603 { my ($bbox, $from, $to) = @_;
142 61         126 my ($xmin, $ymin, $xmax, $ymax) = @$bbox;
143              
144 61         192 my @cross = (
145             _cross_x($xmin, $from, $to),
146             _cross_x($xmax, $from, $to),
147             _cross_y($ymin, $from, $to),
148             _cross_y($ymax, $from, $to),
149             );
150              
151             # order the results
152 8         46 $from->[0] < $to->[0] ? sort({$a->[0] <=> $b->[0]} @cross)
153 8         40 : $from->[0] > $to->[0] ? sort({$b->[0] <=> $a->[0]} @cross)
154 0         0 : $from->[1] < $to->[1] ? sort({$a->[1] <=> $b->[1]} @cross)
155 61 100       354 : sort({$b->[1] <=> $a->[1]} @cross);
  0 100       0  
    100          
156             }
157              
158             sub _cross_inside($$$)
159 45     45   72 { my ($bbox, $from, $to) = @_;
160 45         80 grep _inside($bbox, $_), _cross($bbox, $from, $to);
161             }
162              
163             sub _remove_doubles(@)
164 0 0   0   0 { my $this = shift or return ();
165 0         0 my @ret = $this;
166 0         0 while(@_)
167 0         0 { my $this = shift;
168 0 0 0     0 next if $this->[0]==$ret[-1][0] && $this->[1]==$ret[-1][1];
169 0         0 push @ret, $this;
170             }
171 0         0 @ret;
172             }
173              
174             sub _cross_x($$$)
175 126     126   263431 { my ($x, $from, $to) = @_;
176 126         221 my ($fx, $fy) = @$from;
177 126         189 my ($tx, $ty) = @$to;
178 126 100 100     717 return () unless $fx < $x && $x < $tx || $tx < $x && $x < $fx;
      100        
      100        
179 28         101 my $y = $fy + ($x - $fx)/($tx - $fx) * ($ty - $fy);
180 28 50 66     263 (($fy <= $y && $y <= $ty) || ($ty <= $y && $y <= $fy)) ? [$x,$y] : ();
181             }
182              
183             sub _cross_y($$$)
184 126     126   10216 { my ($y, $from, $to) = @_;
185 126         199 my ($fx, $fy) = @$from;
186 126         202 my ($tx, $ty) = @$to;
187 126 100 100     589 return () unless $fy < $y && $y < $ty || $ty < $y && $y < $fy;
      100        
      100        
188 27         116 my $x = $fx + ($y - $fy)/($ty - $fy) * ($tx - $fx);
189 27 50 66     203 (($fx <= $x && $x <= $tx) || ($tx <= $x && $x <= $fx)) ? [$x,$y] : ();
190             }
191              
192              
193              
194             1;