File Coverage

blib/lib/Math/Bezier/Convert.pm
Criterion Covered Total %
statement 9 139 6.4
branch 0 34 0.0
condition 0 60 0.0
subroutine 3 12 25.0
pod 6 6 100.0
total 18 251 7.1


line stmt bran cond sub pod time code
1             package Math::Bezier::Convert;
2              
3             require 5.005_62;
4 1     1   6 use strict;
  1         1  
  1         38  
5 1     1   10 use warnings;
  1         1  
  1         37  
6 1     1   4 use Carp;
  1         2  
  1         2149  
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.01';
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 ($p1x, $p1y, $p2x, $p2y, $p3x, $p3y);
71 0           my ($a1, $b1, $a2, $b2, $cx, $cy) = (undef) x 6;
72 0           my @qp = ($p0x, $p0y);
73 0           my @p;
74              
75 0 0         croak '$CTRL_PT_TOLERANCE must be more than 1.5 ' unless $CTRL_PT_TOLERANCE > 1.5;
76              
77             CURVE:
78 0   0       while (@cp and @p = ($p1x, $p1y, $p2x, $p2y, $p3x, $p3y) = splice(@cp, 0, 6)) {
79              
80 0           my $step = 0.5;
81 0           my $sep = 1;
82 0           my @qp1 = ();
83 0           my @cp1 = ();
84 0           my ($cp3x, $cp3y);
85              
86 0           while ($step > 0.0000001) {
87              
88 0           my ($v01x, $v01y) = ($p1x-$p0x, $p1y-$p0y);
89 0           my ($v02x, $v02y) = ($p2x-$p0x, $p2y-$p0y);
90 0           my ($v03x, $v03y) = ($p3x-$p0x, $p3y-$p0y);
91 0           my ($v32x, $v32y) = ($p2x-$p3x, $p2y-$p3y);
92              
93 0 0 0       next CURVE if (abs($v01x)<0.0001 and abs($v02x)<0.0001 and abs($v03x)<0.0001 and
      0        
      0        
      0        
      0        
94             abs($v01y)<0.0001 and abs($v02y)<0.0001 and abs($v03y)<0.0001);
95              
96              
97 0 0 0       if (abs($v01x)<0.0001 and abs($v32x)<0.0001 and
      0        
      0        
98             abs($v01y)<0.0001 and abs($v32y)<0.0001) {
99              
100 0           @qp1 = (($p0x+$p3x)/2, ($p0y+$p3y)/2);
101 0           last;
102             }
103              
104 0           my $n = $v01y*$v32x - $v01x*$v32y;
105 0 0         if ($n == 0) {
106 0 0         if ($v02x*$v32y - $v02y*$v32x == 0) {
107 0           @qp1 = (($p0x+$p3x)/2, ($p0y+$p3y)/2);
108 0           last;
109             } else {
110 0           $sep -= $step;
111 0           $step /= 2;
112 0           next;
113             }
114             }
115 0           my $m1 = $v01x*$v03y - $v01y*$v03x;
116 0           my $m2 = $v02x*$v03y - $v03x*$v02y;
117 0 0 0       if ($m1/$n < 1 or $m2/$n < 1 or $m1/$n >$CTRL_PT_TOLERANCE or $m2/$n > $CTRL_PT_TOLERANCE) {
      0        
      0        
118 0           $sep -= $step;
119 0           $step /= 2;
120 0           next;
121             }
122 0           $cx = $p0x + $m2 * $v01x / $n;
123 0           $cy = $p0y + $m2 * $v01y / $n;
124            
125 0 0 0       if (defined $cx and _q_c_check($p0x, $p0y, $p1x, $p1y, $p2x, $p2y, $p3x, $p3y, $cx, $cy)) {
126 0           @qp1 = ($cx, $cy);
127 0 0         last if $sep>=1;
128 0           $sep += $step;
129             } else {
130 0           $sep -= $step;
131             }
132 0           $step /= 2;
133             } continue {
134 0           (undef, undef, $p1x, $p1y, $p2x, $p2y, $p3x, $p3y, @cp1) = divide_cubic($p0x, $p0y, @p, $sep);
135             }
136 0 0         unless (@qp1) {
137 0           die "Can't approx @p";
138             # return @qp;
139             }
140 0           push @qp, @qp1, $p3x, $p3y;
141 0           $p0x = $p3x;
142 0           $p0y = $p3y;
143 0 0         if (@cp1) {
144 0           @p = ($p1x, $p1y, $p2x, $p2y, $p3x, $p3y) = @cp1;
145 0           redo;
146             }
147             }
148 0           return @qp;
149             }
150              
151             sub _q_c_check {
152 0     0     my ($cx0, $cy0, $cx1, $cy1, $cx2, $cy2, $cx3, $cy3, $qx1, $qy1) = @_;
153 0           my ($a, $b, $c, $d, $sep);
154              
155 0           $a = (($cx0-$cx3)*($cy1-$cy3)-($cy0-$cy3)*($cx1-$cx3)<=>0);
156 0           $b = (($cx0-$cx3)*($cy2-$cy3)-($cy0-$cy3)*($cx2-$cx3)<=>0);
157 0 0 0       return if ($a == 0 or $b == 0 or $a != $b);
      0        
158              
159 0           my ($cx, $cy) = (divide_cubic($cx0,$cy0,$cx1,$cy1,$cx2,$cy2,$cx3,$cy3, 0.5))[6,7];
160 0           $a = $cx0-2*$qx1+$cx3;
161 0           $b = 2*$qx1-2*$cx0;
162 0           $c = $cx0-$cx;
163 0           $d = $b*$b-4*$a*$c;
164 0 0         return if ($d<0);
165 0           my ($qx, $qy);
166 0 0         if ($a!=0) {
167 0           $sep = (-$b-sqrt($d))/2/$a;
168 0 0 0       $sep = (-$b+sqrt($d))/2/$a if ($sep<=0 or $sep>=1);
169 0 0 0       return if ($sep<=0 or $sep>=1);
170 0           ($qx, $qy) = (divide_quadratic($cx0,$cy0,$qx1,$qy1,$cx3,$cy3, $sep))[4, 5];
171             } else {
172 0           ($qx, $qy) = ($qx1, $qy1);
173             }
174 0           return ($cx-$qx)*($cx-$qx)+($cy-$qy)*($cy-$qy) < $APPROX_QUADRATIC_TOLERANCE;
175             }
176              
177             sub quadratic_to_cubic {
178 0     0 1   my ($p0x, $p0y, @qp) = @_;
179 0           my @cp = ($p0x, $p0y);
180 0           my ($p1x, $p1y, $p2x, $p2y);
181              
182 0   0       while (@qp and ($p1x, $p1y, $p2x, $p2y) = splice(@qp, 0, 4)) {
183 0           push @cp, $p0x+($p1x-$p0x)*2/3, $p0y+($p1y-$p0y)*2/3, $p1x+($p2x-$p1x)/3, $p1y+($p2y-$p1y)/3, $p2x, $p2y;
184 0           $p0x = $p2x;
185 0           $p0y = $p2y;
186             }
187 0           return @cp;
188             }
189              
190             sub cubic_to_lines {
191 0     0 1   my @cp = @_;
192 0           my @p;
193 0           my @last = splice(@cp, 0, 2);
194 0           my @lp = @last;
195              
196 0   0       while (@cp and @p = splice(@cp, 0, 6)) {
197 0           push @lp, _c2lsub(@last, @p);
198 0           push @lp, @last = @p[4,5];
199             }
200 0           return @lp;
201             }
202              
203             sub _c2lsub {
204 0     0     my @p = @_;
205 0           my ($p0x, $p0y, $p10x, $p10y, $p20x, $p20y, $p30x, $p30y, $p21x, $p21y, $p12x, $p12y, $p3x, $p3y) =
206             divide_cubic(@p[0..7], 0.5);
207 0           my ($cx, $cy) = (($p0x+$p3x)/2, ($p0y+$p3y)/2);
208 0 0         return () if (($p30x-$cx)*($p30x-$cx)+($p30y-$cy)*($p30y-$cy) < $APPROX_LINE_TOLERANCE);
209 0           return (_c2lsub($p0x, $p0y, $p10x, $p10y, $p20x, $p20y, $p30x, $p30y), $p30x, $p30y, _c2lsub($p30x, $p30y, $p21x, $p21y, $p12x, $p12y, $p3x, $p3y));
210             }
211              
212             sub quadratic_to_lines {
213 0     0 1   my @qp = @_;
214 0           my @p;
215 0           my @last = splice(@qp, 0, 2);
216 0           my @lp = @last;
217              
218 0   0       while (@qp and @p = splice(@qp, 0, 4)) {
219 0           push @lp, _q2lsub(@last, @p);
220 0           push @lp, @last = @p[2,3];
221             }
222 0           return @lp;
223             }
224              
225             sub _q2lsub {
226 0     0     my @p = @_;
227 0           my ($p0x, $p0y, $p10x, $p10y, $p20x, $p20y, $p11x, $p11y, $p2x, $p2y) =
228             divide_quadratic(@p[0..5], 0.5);
229 0           my ($cx, $cy) = (($p0x+$p2x)/2, ($p0y+$p2y)/2);
230 0 0         return () if (($p20x-$cx)*($p20x-$cx)+($p20y-$cy)*($p20y-$cy) < $APPROX_LINE_TOLERANCE);
231 0           return (_q2lsub($p0x, $p0y, $p10x, $p10y, $p20x, $p20y), $p20x, $p20y, _q2lsub($p20x, $p20y, $p11x, $p11y, $p2x, $p2y));
232             }
233              
234             1;
235             __END__