File Coverage

blib/lib/Math/Bezier/Convert.pm
Criterion Covered Total %
statement 9 188 4.7
branch 0 52 0.0
condition 0 72 0.0
subroutine 3 12 25.0
pod 6 6 100.0
total 18 330 5.4


line stmt bran cond sub pod time code
1             package Math::Bezier::Convert;
2              
3             require 5.005_62;
4 1     1   12868 use strict;
  1         2  
  1         36  
5 1     1   5 use warnings;
  1         2  
  1         30  
6 1     1   4 use Carp;
  1         5  
  1         3017  
7              
8             require Exporter;
9              
10             our @ISA = qw(Exporter);
11              
12             our %EXPORT_TAGS = ( 'all' => [ qw(
13             divide_cubic
14             divide_quadratic
15             cubic_to_quadratic
16             quadratic_to_cubic
17             cubic_to_lines
18             quadratic_to_lines
19             ) ] );
20              
21             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
22              
23             our @EXPORT = qw(
24            
25             );
26             our $VERSION = '0.02';
27              
28             # Globals
29              
30             our $APPROX_QUADRATIC_TOLERANCE = 1;
31             our $APPROX_LINE_TOLERANCE = 1;
32             our $CTRL_PT_TOLERANCE = 3;
33              
34             sub divide_cubic {
35 0     0 1   my ($p0x, $p0y, $p1x, $p1y, $p2x, $p2y, $p3x, $p3y, $sep) = @_;
36 0           my ($p10x, $p10y, $p11x, $p11y, $p12x, $p12y, $p20x, $p20y, $p21x, $p21y, $p30x, $p30y);
37              
38 0           $p10x = $p0x + $sep * ($p1x - $p0x);
39 0           $p10y = $p0y + $sep * ($p1y - $p0y);
40 0           $p11x = $p1x + $sep * ($p2x - $p1x);
41 0           $p11y = $p1y + $sep * ($p2y - $p1y);
42 0           $p12x = $p2x + $sep * ($p3x - $p2x);
43 0           $p12y = $p2y + $sep * ($p3y - $p2y);
44 0           $p20x = $p10x+ $sep * ($p11x-$p10x);
45 0           $p20y = $p10y+ $sep * ($p11y-$p10y);
46 0           $p21x = $p11x+ $sep * ($p12x-$p11x);
47 0           $p21y = $p11y+ $sep * ($p12y-$p11y);
48 0           $p30x = $p20x+ $sep * ($p21x-$p20x);
49 0           $p30y = $p20y+ $sep * ($p21y-$p20y);
50              
51 0           return ($p0x, $p0y, $p10x, $p10y, $p20x, $p20y, $p30x, $p30y, $p21x, $p21y, $p12x, $p12y, $p3x, $p3y);
52             }
53              
54             sub divide_quadratic {
55 0     0 1   my ($p0x, $p0y, $p1x, $p1y, $p2x, $p2y, $sep) = @_;
56 0           my ($p10x, $p10y, $p11x, $p11y, $p20x, $p20y);
57              
58 0           $p10x = $p0x + $sep * ($p1x - $p0x);
59 0           $p10y = $p0y + $sep * ($p1y - $p0y);
60 0           $p11x = $p1x + $sep * ($p2x - $p1x);
61 0           $p11y = $p1y + $sep * ($p2y - $p1y);
62 0           $p20x = $p10x+ $sep * ($p11x-$p10x);
63 0           $p20y = $p10y+ $sep * ($p11y-$p10y);
64              
65 0           return ($p0x, $p0y, $p10x, $p10y, $p20x, $p20y, $p11x, $p11y, $p2x, $p2y);
66             }
67              
68             sub cubic_to_quadratic {
69 0     0 1   my ($p0x, $p0y, @cp) = @_;
70 0           my ($a1, $b1, $a2, $b2, $cx, $cy) = (undef) x 6;
71 0           my @qp = ($p0x, $p0y);
72              
73 0 0         croak '$CTRL_PT_TOLERANCE must be more than 1.5 ' unless $CTRL_PT_TOLERANCE > 1.5;
74              
75             CURVE:
76 0   0       while (@cp and my @p = my ($p1x, $p1y, $p2x, $p2y, $p3x, $p3y) = splice(@cp, 0, 6)) {
77 0           my @qp1 = ();
78 0           my $revf = 0;
79 0           DIVCURVE:
80             {
81 0           my $step = 0.5;
82 0           my $sep = 1;
83 0           my @cp1 = ();
84 0           my @qp2 = ();
85 0           my ($cp3x, $cp3y);
86              
87 0           while ($step > 0.001) {
88              
89 0           my ($v01x, $v01y) = ($p1x-$p0x, $p1y-$p0y);
90 0           my ($v02x, $v02y) = ($p2x-$p0x, $p2y-$p0y);
91 0           my ($v03x, $v03y) = ($p3x-$p0x, $p3y-$p0y);
92 0           my ($v32x, $v32y) = ($p2x-$p3x, $p2y-$p3y);
93              
94             # skip if all points are almost same position.
95 0 0 0       last DIVCURVE if (abs($v01x)<0.01 and abs($v02x)<0.01 and abs($v03x)<0.01 and
      0        
      0        
      0        
      0        
96             abs($v01y)<0.01 and abs($v02y)<0.01 and abs($v03y)<0.01);
97              
98 0 0 0       if (abs($v01x)<0.01 and abs($v01y)<0.01) {
99 0 0         if ($revf) {
100 0           @qp2 = (($p0x+$p3x)/2, ($p0y+$p3y)/2);
101 0           last;
102             } else {
103 0 0 0       if (abs($v32x) <0.01 and abs($v32y) <0.01) {
104 0           @qp2 = (($p0x+$p3x)/2, ($p0y+$p3y)/2);
105 0           last;
106             }
107 0           $revf = 1;
108 0           @qp1 = ($p[4], $p[5]);
109 0           ($p0x, $p0y, @p) =
110             ($p[4], $p[5], $p[2], $p[3], $p[0], $p[1], $p0x, $p0y);
111 0           ($p1x, $p1y, $p2x, $p2y, $p3x, $p3y) = @p;
112 0           redo DIVCURVE;
113             }
114             }
115            
116 0           my $vp14 = $v01y*$v32x - $v01x*$v32y;
117 0           my $vp12 = $v01x*$v02y - $v01y*$v02x;
118 0           my $vp13 = $v01x*$v03y - $v01y*$v03x;
119 0           my $vp23 = $v02x*$v03y - $v03x*$v02y;
120              
121 0 0         if ($vp14 == 0) {
122             # if v01 and v32 are parallel and not in line, do next step.
123 0 0         if ($vp12) {
124 0           $sep -= $step;
125 0           $step /= 2;
126 0           next;
127             } else {
128             # if anchors and control points are in line,
129 0           @qp2 = ($p0x, $p0y);
130 0           my $deltax = 3*($p1x - $p0x);
131 0           my $deltay = 3*($p1y - $p0y);
132 0           my $betax = 3*($p2x - $p1x) - $deltax;
133 0           my $betay = 3*($p2y - $p1y) - $deltay;
134 0           my $alphax = $p3x - $p0x - $deltax - $betax;
135 0           my $alphay = $p3y - $p0y - $deltay - $betay;
136 0           my $d_x = $betax*$betax - 3*$alphax*$deltax;
137 0           my $d_y = $betay*$betay - 3*$alphay*$deltay;
138 0 0 0       last if ($d_x < 0 or $d_y < 0);
139 0           my ($u1, $u2);
140 0 0 0       if ($deltax == 0 and $betax == 0 and $alphax == 0) {
      0        
141 0           $u1 = (-2*$betay + 2*sqrt($d_y)) / (6*$alphay);
142 0           $u2 = (-2*$betay - 2*sqrt($d_y)) / (6*$alphay);
143             } else {
144 0           $u1 = (-2*$betax + 2*sqrt($d_x)) / (6*$alphax);
145 0           $u2 = (-2*$betax - 2*sqrt($d_x)) / (6*$alphax);
146             }
147 0 0         ($u1, $u2) = ($u2, $u1) if $u1 > $u2;
148 0 0 0       if ($u1 > 0 and $u1 < 1) {
149 0           my @p = (divide_cubic($p0x, $p0y, $p1x, $p1y, $p2x, $p2y, $p3x, $p3y, $u1))[6,7];
150 0           push @qp2, @p, @p;
151             }
152 0 0 0       if ($u2 > 0 and $u2 < 1) {
153 0           my @p = (divide_cubic($p0x, $p0y, $p1x, $p1y, $p2x, $p2y, $p3x, $p3y, $u2))[6,7];
154 0           push @qp2, @p, @p;
155             }
156 0           last;
157             }
158             } else {
159 0           my $n = $vp23 / $vp14;
160 0 0 0       if ($n <= 0 or $n > $CTRL_PT_TOLERANCE or $vp13 / $vp14 <= 0 or $vp13 / $vp14 > $CTRL_PT_TOLERANCE) {
      0        
      0        
161 0           $sep -= $step;
162 0           $step /= 2;
163 0           next;
164             } else {
165 0           $cx = $p0x + $n * $v01x;
166 0           $cy = $p0y + $n * $v01y;
167             }
168 0 0 0       if (defined $cx and _q_c_check($p0x, $p0y, $p1x, $p1y, $p2x, $p2y, $p3x, $p3y, $cx, $cy)) {
169 0           @qp2 = ($cx, $cy);
170 0 0         last if $sep>=1;
171 0           $sep += $step;
172             } else {
173 0           $sep -= $step;
174             }
175             }
176 0           $step /= 2;
177             } continue {
178 0           (undef, undef, $p1x, $p1y, $p2x, $p2y, $p3x, $p3y, @cp1) = divide_cubic($p0x, $p0y, @p, $sep);
179             }
180              
181 0           push @qp1, @qp2, $p3x, $p3y;
182 0           $p0x = $p3x;
183 0           $p0y = $p3y;
184 0 0         if (@cp1) {
185 0           @p = ($p1x, $p1y, $p2x, $p2y, $p3x, $p3y) = @cp1;
186 0           redo DIVCURVE;
187             }
188 0 0         unless (@qp2) {
189 0           die "Can't approx ";
190             }
191             } # DIVCURVE
192 0 0         if ($revf) {
193 0           pop @qp1;
194 0           pop @qp1;
195 0           my ($x, $y);
196 0           while (@qp1) {
197 0           $y = pop @qp1;
198 0           $x = pop @qp1;
199 0           push @qp, $x, $y;
200             }
201 0           $p0x = $x;
202 0           $p0y = $y;
203             } else {
204 0           push @qp, @qp1;
205             }
206             }
207 0           return @qp;
208             }
209              
210             sub _q_c_check {
211 0     0     my ($cx0, $cy0, $cx1, $cy1, $cx2, $cy2, $cx3, $cy3, $qx1, $qy1) = @_;
212 0           my ($a, $b, $c, $d, $sep);
213              
214 0           $a = (($cx0-$cx3)*($cy1-$cy3)-($cy0-$cy3)*($cx1-$cx3)<=>0);
215 0           $b = (($cx0-$cx3)*($cy2-$cy3)-($cy0-$cy3)*($cx2-$cx3)<=>0);
216 0 0 0       return if ($a == 0 or $b == 0 or $a != $b);
      0        
217              
218 0           my ($cx, $cy) = (divide_cubic($cx0,$cy0,$cx1,$cy1,$cx2,$cy2,$cx3,$cy3, 0.5))[6,7];
219 0           $a = $cx0-2*$qx1+$cx3;
220 0           $b = 2*$qx1-2*$cx0;
221 0           $c = $cx0-$cx;
222 0           $d = $b*$b-4*$a*$c;
223 0 0         return if ($d<0);
224 0           my ($qx, $qy);
225 0 0         if ($a!=0) {
226 0           $sep = (-$b-sqrt($d))/2/$a;
227 0 0 0       $sep = (-$b+sqrt($d))/2/$a if ($sep<=0 or $sep>=1);
228 0 0 0       return if ($sep<=0 or $sep>=1);
229 0           ($qx, $qy) = (divide_quadratic($cx0,$cy0,$qx1,$qy1,$cx3,$cy3, $sep))[4, 5];
230             } else {
231 0           ($qx, $qy) = ($qx1, $qy1);
232             }
233 0           return ($cx-$qx)*($cx-$qx)+($cy-$qy)*($cy-$qy) < $APPROX_QUADRATIC_TOLERANCE;
234             }
235              
236             sub quadratic_to_cubic {
237 0     0 1   my ($p0x, $p0y, @qp) = @_;
238 0           my @cp = ($p0x, $p0y);
239 0           my ($p1x, $p1y, $p2x, $p2y);
240              
241 0   0       while (@qp and ($p1x, $p1y, $p2x, $p2y) = splice(@qp, 0, 4)) {
242 0           push @cp, $p0x+($p1x-$p0x)*2/3, $p0y+($p1y-$p0y)*2/3, $p1x+($p2x-$p1x)/3, $p1y+($p2y-$p1y)/3, $p2x, $p2y;
243 0           $p0x = $p2x;
244 0           $p0y = $p2y;
245             }
246 0           return @cp;
247             }
248              
249             sub cubic_to_lines {
250 0     0 1   my @cp = @_;
251 0           my @p;
252 0           my @last = splice(@cp, 0, 2);
253 0           my @lp = @last;
254              
255 0   0       while (@cp and @p = splice(@cp, 0, 6)) {
256 0           push @lp, _c2lsub(@last, @p);
257 0           push @lp, @last = @p[4,5];
258             }
259 0           return @lp;
260             }
261              
262             sub _c2lsub {
263 0     0     my @p = @_;
264 0           my ($p0x, $p0y, $p10x, $p10y, $p20x, $p20y, $p30x, $p30y, $p21x, $p21y, $p12x, $p12y, $p3x, $p3y) =
265             divide_cubic(@p, 0.5);
266 0           my ($cx, $cy) = (($p0x+$p3x)/2, ($p0y+$p3y)/2);
267 0 0         if (($p30x-$cx)*($p30x-$cx)+($p30y-$cy)*($p30y-$cy) < $APPROX_LINE_TOLERANCE) {
268 0           my ($c0x, $c0y) = (($p0x+$p30x)/2, ($p0y+$p30y)/2);
269 0           my ($pp30x, $pp30y) = @{[divide_cubic(@p,0.25)]}[6,7];
  0            
270 0 0         return () if (($pp30x-$c0x)*($pp30x-$c0x)+($pp30y-$c0y)*($pp30y-$c0y) < $APPROX_LINE_TOLERANCE);
271             }
272 0           return (_c2lsub($p0x, $p0y, $p10x, $p10y, $p20x, $p20y, $p30x, $p30y), $p30x, $p30y, _c2lsub($p30x, $p30y, $p21x, $p21y, $p12x, $p12y, $p3x, $p3y));
273             }
274              
275             sub quadratic_to_lines {
276 0     0 1   my @qp = @_;
277 0           my @p;
278 0           my @last = splice(@qp, 0, 2);
279 0           my @lp = @last;
280              
281 0   0       while (@qp and @p = splice(@qp, 0, 4)) {
282 0           push @lp, _q2lsub(@last, @p);
283 0           push @lp, @last = @p[2,3];
284             }
285 0           return @lp;
286             }
287              
288             sub _q2lsub {
289 0     0     my @p = @_;
290 0           my ($p0x, $p0y, $p10x, $p10y, $p20x, $p20y, $p11x, $p11y, $p2x, $p2y) =
291             divide_quadratic(@p, 0.5);
292 0           my ($cx, $cy) = (($p0x+$p2x)/2, ($p0y+$p2y)/2);
293 0 0         return () if (($p20x-$cx)*($p20x-$cx)+($p20y-$cy)*($p20y-$cy) < $APPROX_LINE_TOLERANCE);
294 0           return (_q2lsub($p0x, $p0y, $p10x, $p10y, $p20x, $p20y), $p20x, $p20y, _q2lsub($p20x, $p20y, $p11x, $p11y, $p2x, $p2y));
295             }
296              
297             1;
298             __END__