File Coverage

lib/SVG/Estimate/Path/Arc.pm
Criterion Covered Total %
statement 108 108 100.0
branch 17 30 56.6
condition n/a
subroutine 7 7 100.0
pod 1 3 33.3
total 133 148 89.8


line stmt bran cond sub pod time code
1             package SVG::Estimate::Path::Arc;
2             $SVG::Estimate::Path::Arc::VERSION = '1.0107';
3 4     4   873 use Moo;
  4         5  
  4         17  
4 4     4   764 use Math::Trig qw/pi acos deg2rad rad2deg/;
  4         5  
  4         210  
5 4     4   706 use Clone qw/clone/;
  4         3740  
  4         154  
6 4     4   14 use strict;
  4         9  
  4         3077  
7              
8             extends 'SVG::Estimate::Path::Command';
9             with 'SVG::Estimate::Role::Pythagorean';
10             with 'SVG::Estimate::Role::SegmentLength';
11              
12             =head1 NAME
13              
14             SVG::Estimate::Path::Arc - Handles estimating arcs.
15              
16             =head1 VERSION
17              
18             version 1.0107
19              
20             =head1 SYNOPSIS
21              
22             my $arc = SVG::Estimate::Path::Arc->new(
23             transformer => $transform,
24             start_point => [13, 19],
25             x => 45,
26             y => 13,
27             rx => 1,
28             ry => 3,
29             x_axis_rotation => 0,
30             large_arc_flag => 0,
31             sweep_flag => 0,
32             );
33              
34             my $length = $arc->length;
35              
36             =head1 INHERITANCE
37              
38             This class extends L and consumes L, L, and L.
39              
40             =head1 METHODS
41              
42             =head2 new()
43              
44             Constructor.
45              
46             =over
47              
48             =item x
49              
50             The x coordinate for the end-point of the arc.
51              
52             =item y
53              
54             The y coordinate for the end-point of the arc.
55              
56             =item rx
57              
58             Float representing the x radius.
59              
60             =item ry
61              
62             Float representing the y radius.
63              
64             =item x_axis_rotation
65              
66             Float that indicates how the ellipse as a whole is rotated relative to the current coordinate system.
67              
68             =item large_arc_flag
69              
70             Must be 1 or 0. See details L.
71              
72             =item sweep_flag
73              
74             Must be 1 or 0. See details L.
75              
76             =back
77              
78             =cut
79              
80             has rx => (
81             is => 'ro',
82             required => 1,
83             );
84              
85             has ry => (
86             is => 'ro',
87             required => 1,
88             );
89              
90             has x_axis_rotation => (
91             is => 'ro',
92             required => 1,
93             );
94              
95             has large_arc_flag => (
96             is => 'ro',
97             required => 1,
98             );
99              
100             has sweep_flag => (
101             is => 'ro',
102             required => 1,
103             );
104              
105             has x => (
106             is => 'ro',
107             required => 1,
108             );
109              
110             has y => (
111             is => 'ro',
112             required => 1,
113             );
114              
115             ##Used for conversion from endpoint to center parameterization
116             has _delta => (
117             is => 'rw',
118             );
119              
120             has _theta => (
121             is => 'rw',
122             );
123              
124             has _center => (
125             is => 'rw',
126             );
127              
128             sub BUILDARGS {
129 2     2 0 5573 my ($class, @args) = @_;
130             ##Upgrade to hashref
131 2 50       13 my $args = @args % 2 ? $args[0] : { @args };
132 2 100       33 if ($args->{transformer}->has_transforms) {
133             ##The start point and end point are in different coordinate systems (view and user, respectively).
134             ##To make the set of point in the user space, transform the start_point into user space
135             ##Then run all the calculations
136 1         9 my $view_start_point = clone $args->{start_point};
137 1         5 $args->{start_point} = $args->{transformer}->untransform($args->{start_point});
138 1         698 $class->endpoint_to_center($args);
139 1         1 my $point;
140 1         1 my $first = 1;
141 1         1 my $start;
142 1         1 my $length = 0;
143 1         3 POINT: for (my $t=0; $t<=1; $t+=1/12) {
144 13         17 $point = $class->this_point($args, $t);
145 13         25 $point = $args->{transformer}->transform($point);
146 13 100       1138 if ($first) {
147 1         1 $first = 0;
148 1         1 $start = $point;
149 1         2 $args->{min_x} = $args->{max_x} = $point->[0];
150 1         6 $args->{min_y} = $args->{max_y} = $point->[1];
151 1         3 next POINT;
152             }
153 12         22 $length += $class->pythagorean($start, $point);
154 12 50       25 $args->{min_x} = $point->[0] if $point->[0] < $args->{min_x};
155 12 50       19 $args->{min_y} = $point->[1] if $point->[1] < $args->{min_y};
156 12 50       16 $args->{max_x} = $point->[0] if $point->[0] > $args->{max_x};
157 12 50       20 $args->{max_y} = $point->[1] if $point->[1] > $args->{max_y};
158 12         21 $start = $point;
159             }
160             ##Restore the original start point in the viewport coordinate system
161 1         2 $args->{start_point} = $view_start_point;
162 1         2 $args->{end_point} = $point;
163 1         2 $args->{shape_length} = $length;
164 1         1 $args->{travel_length} = 0;
165 1         16 return $args;
166             }
167 1         2 $class->endpoint_to_center($args);
168 1         6 $args->{end_point} = clone $args->{point};
169 1         2 my $start = $class->this_point($args, 0);
170 1         1 my $end = $class->this_point($args, 1);
171 1 50       2 $args->{min_x} = $start->[0] < $end->[0] ? $start->[0] : $end->[0];
172 1 50       3 $args->{max_x} = $start->[0] > $end->[0] ? $start->[0] : $end->[0];
173 1 50       8 $args->{min_y} = $start->[1] < $end->[1] ? $start->[1] : $end->[1];
174 1 50       2 $args->{max_y} = $start->[1] > $end->[1] ? $start->[1] : $end->[1];
175 1         2 $args->{shape_length} = $class->segment_length($args, 0, 1, $start, $end, 1e-4, 5, 0);
176 1         2 $args->{travel_length} = 0;
177 1         24 return $args;
178             }
179              
180             sub endpoint_to_center {
181 2     2 0 3 my $class = shift;
182 2         3 my $args = shift;
183 2         9 my $rotr = deg2rad($args->{x_axis_rotation});
184 2         47 my $cosr = cos $rotr;
185 2         3 my $sinr = sin $rotr;
186 2         5 my $dx = ($args->{start_point}->[0] - $args->{x}) / 2; #*
187 2         3 my $dy = ($args->{start_point}->[1] - $args->{y}) / 2; #*
188              
189 2         4 my $x1prim = $cosr * $dx + $sinr * $dy; #*
190 2         2 my $y1prim = -1*$sinr * $dx + $cosr * $dy; #*
191              
192 2         4 my $x1prim_sq = $x1prim**2; #*
193 2         1 my $y1prim_sq = $y1prim**2; #*
194              
195 2         2 my $rx = $args->{rx}; #*
196 2         2 my $ry = $args->{ry}; #*
197              
198 2         3 my $rx_sq = $rx**2; #*
199 2         2 my $ry_sq = $ry**2; #*
200              
201 2         2 my $t1 = $rx_sq * $y1prim_sq;
202 2         3 my $t2 = $ry_sq * $x1prim_sq;
203 2         2 my $ts = $t1 + $t2;
204 2         3 my $c = sqrt(abs( (($rx_sq * $ry_sq) - $ts) / ($ts) ) );
205              
206 2 50       6 if ($args->{large_arc_flag} == $args->{sweep_flag}) {
207 2         3 $c *= -1;
208             }
209 2         2 my $cxprim = $c * $rx * $y1prim / $ry;
210 2         7 my $cyprim = -1 *$c * $ry * $x1prim / $rx;
211              
212             $args->{_center} = [
213             ($cosr * $cxprim - $sinr * $cyprim) + ( ($args->{start_point}->[0] + $args->{x}) / 2 ),
214 2         10 ($sinr * $cxprim + $cosr * $cyprim) + ( ($args->{start_point}->[1] + $args->{y}) / 2 )
215             ];
216              
217             ##**
218              
219             ##Theta calculation
220 2         2 my $ux = ($x1prim - $cxprim) / $rx; #*
221 2         3 my $uy = ($y1prim - $cyprim) / $ry; #*
222 2         3 my $n = sqrt($ux**2 + $uy**2);
223 2         2 my $p = $ux;
224 2         2 my $d = $p / $n;
225 2         9 my $theta = rad2deg(acos($p/$n));
226 2 50       37 if ($uy < 0) {
227 2         3 $theta *= -1;
228             }
229 2         4 $args->{_theta} = $theta % 360;
230              
231 2         6 my $vx = -1 * ($x1prim + $cxprim) / $rx;
232 2         4 my $vy = -1 * ($y1prim + $cyprim) / $ry;
233 2         5 $n = sqrt( ($ux**2 + $uy**2) * ($vx**2 + $vy**2));
234 2         3 $p = $ux*$vx + $uy*$vy;
235 2         2 $d = $p / $n;
236              
237 2         5 my $delta = rad2deg(acos($d));
238 2 50       17 if (($ux * $vy - $uy * $vx) < 0 ) {
239 2         2 $delta *= -1;
240             }
241 2         2 $delta = $delta % 360;
242              
243 2 50       5 if (! $args->{sweep_flag}) {
244 2         2 $delta -= 360;
245             }
246 2         4 $args->{_delta} = $delta;
247             }
248              
249             =head2 this_point (args, t)
250              
251             Calculate a point on the graph, normalized from start point to end point as t, in 2-D space
252              
253             =cut
254              
255             sub this_point {
256 142     142 1 90 my $class = shift;
257 142         106 my $args = shift;
258 142         74 my $t = shift;
259 142         208 my $angle = deg2rad($args->{_theta} + ($args->{_delta} * $t));
260 142         557 my $rotr = deg2rad($args->{x_axis_rotation});
261 142         411 my $cosr = cos $rotr;
262 142         92 my $sinr = sin $rotr;
263 142         227 my $x = ($cosr * cos($angle) * $args->{rx} - $sinr * sin($angle) * $args->{ry} + $args->{_center}->[0]);
264 142         164 my $y = ($sinr * cos($angle) * $args->{rx} + $cosr * sin($angle) * $args->{ry} + $args->{_center}->[1]);
265 142         197 return [$x, $y];
266             }
267              
268             1;