File Coverage

blib/lib/Math/Geometry/Construction/Circle.pm
Criterion Covered Total %
statement 1 3 33.3
branch n/a
condition n/a
subroutine 1 1 100.0
pod n/a
total 2 4 50.0


line stmt bran cond sub pod time code
1             package Math::Geometry::Construction::Circle;
2 1     1   2642 use Moose;
  0            
  0            
3              
4             use 5.008008;
5              
6             use Math::Geometry::Construction::Point;
7             use Math::Geometry::Construction::Types qw(Point);
8             use Carp;
9             use List::Util qw(max);
10              
11             use overload 'x' => '_intersect',
12             # '.' => '_point_on',
13             'bool' => sub { return 1 };
14              
15             =head1 NAME
16              
17             C<Math::Geometry::Construction::Circle> - circle by center and point
18              
19             =head1 VERSION
20              
21             Version 0.024
22              
23             =cut
24              
25             our $VERSION = '0.024';
26              
27              
28             ###########################################################################
29             # #
30             # Class Variables and Methods #
31             # #
32             ###########################################################################
33              
34             our $ID_TEMPLATE = 'C%09d';
35              
36             sub id_template { return $ID_TEMPLATE }
37              
38             ###########################################################################
39             # #
40             # Accessors #
41             # #
42             ###########################################################################
43              
44             with 'Math::Geometry::Construction::Role::Object';
45             with 'Math::Geometry::Construction::Role::PositionSelection';
46             with 'Math::Geometry::Construction::Role::Output';
47             with 'Math::Geometry::Construction::Role::PointSet';
48             with 'Math::Geometry::Construction::Role::Input';
49              
50             has 'center' => (isa => Point,
51             is => 'ro',
52             required => 1);
53              
54             has 'support' => (isa => Point,
55             is => 'ro',
56             required => 1);
57              
58             has '_fixed_radius' => (isa => 'Num',
59             is => 'rw',
60             init_arg => 'radius',
61             trigger => \&_fixed_radius_trigger);
62              
63             has 'partial_draw' => (isa => 'Bool',
64             is => 'rw',
65             builder => '_build_partial_draw',
66             lazy => 1);
67              
68             has 'min_gap' => (isa => 'Num',
69             is => 'rw',
70             builder => '_build_min_gap',
71             lazy => 1);
72              
73             sub BUILDARGS {
74             my ($class, %args) = @_;
75            
76             # implicitly checks $args{construction}
77             $args{center} = $class->import_point($args{construction},
78             $args{center});
79              
80             if(exists($args{support})) {
81             if(exists($args{radius})) {
82             warn sprintf("Ignoring ircle init parameter radius.\n");
83             delete $args{radius};
84             }
85              
86             $args{support} = $class->import_point($args{construction},
87             $args{support});
88             }
89             elsif(exists($args{radius})) {
90             $args{support} = $args{construction}->add_derived_point
91             ('TranslatedPoint',
92             {input => [$args{center}],
93             translator => [$args{radius}, 0]},
94             {hidden => 1});
95             }
96            
97             return \%args;
98             }
99              
100             sub BUILD {
101             my ($self, $args) = @_;
102              
103             $self->style('stroke', 'black') unless($self->style('stroke'));
104             $self->style('fill', 'none') unless($self->style('fill'));
105              
106             # The following call also makes sure that the support has been
107             # set or can be built.
108             $self->register_point($self->support);
109             }
110              
111             sub _build_partial_draw {
112             my ($self) = @_;
113              
114             return $self->construction->partial_circles;
115             }
116              
117             sub _build_min_gap {
118             my ($self) = @_;
119              
120             return $self->construction->min_circle_gap;
121             }
122              
123             sub _fixed_radius_trigger {
124             my ($self, $new, $old) = @_;
125              
126             if(@_ > 2) {
127             # change of value, not init
128             $self->support->derivate->translator([$new, 0]);
129             }
130             }
131              
132             ###########################################################################
133             # #
134             # Retrieve Data #
135             # #
136             ###########################################################################
137              
138             sub positions {
139             my ($self) = @_;
140              
141             return map { $_->position } $self->points;
142             }
143              
144             sub radius {
145             my ($self, @args) = @_;
146              
147             if(@args) {
148             if(defined($self->_fixed_radius)) {
149             $self->_fixed_radius($args[0]);
150             }
151             else {
152             croak sprintf('Refusing to set radius on circle %s without '.
153             'fixed radius', $self->id);
154             }
155             }
156              
157             my $center_p = $self->center->position;
158             my $support_p = $self->support->position;
159              
160             return if(!$center_p or !$support_p);
161             return(abs($support_p - $center_p));
162             }
163              
164             sub _calculate_boundary_positions {
165             my ($self, %args) = @_;
166              
167             my @positions = grep { defined($_) } $self->positions;
168             return([undef, undef], [undef, undef]) if(@positions < 2);
169              
170             # sort positions around the circle; note that @sorted_positions
171             # contains arrayrefs with position and angle
172             my $center_position = $self->center->position; # known to be def
173             my @rich_positions = ();
174             foreach(@positions) {
175             my $relative = $_ - $center_position;
176             my $angle = atan2($relative->[1], $relative->[0]);
177             $angle += 6.28318530717959 if($angle < 0);
178             push(@rich_positions, [$_, $angle]);
179             }
180             my @sorted_positions = sort { $a->[1] <=> $b->[1] } @rich_positions;
181              
182             my $n = @sorted_positions;
183             my @max = (undef, undef);
184             for(my $i=0;$i<$n;$i++) {
185             my $diff;
186             if($i + 1 < $n) {
187             $diff = $sorted_positions[$i + 1]->[1]
188             - $sorted_positions[$i]->[1];
189             }
190             else {
191             $diff = $sorted_positions[0]->[1] + 6.28318530717959
192             - $sorted_positions[$i]->[1];
193             }
194             @max = ($i, $diff) if(!defined($max[1]) or $diff > $max[1]);
195             }
196              
197             my $extend = $self->extend;
198             my $radius = $self->radius; # known to be non-zero
199              
200             # if the gap is two small we return nothing
201             if($max[1] - ($extend->[0] + $extend->[1]) / $radius
202             < $self->min_gap)
203             {
204             return([undef, undef], [undef, undef]);
205             }
206              
207             # calculate the boundary positions; note that the order needs to
208             # be reversed because we now deal with the part that needs to be
209             # drawn, not the gap
210             my @boundary_positions = ();
211             my $j = $max[0];
212             my $i = ($j + 1) % $n;
213             if($extend->[0] == 0) {
214             push(@boundary_positions, $sorted_positions[$i]->[0]);
215             }
216             else {
217             my $phi = $sorted_positions[$i]->[1] - $extend->[0] / $radius;
218             my $boundary = $center_position +
219             [$radius * cos($phi), $radius * sin($phi)];
220             push(@boundary_positions, $boundary);
221             }
222             if($extend->[1] == 0) {
223             push(@boundary_positions, $sorted_positions[$j]->[0]);
224             }
225             else {
226             my $phi = $sorted_positions[$j]->[1] + $extend->[1] / $radius;
227             my $boundary = $center_position +
228             [$radius * cos($phi), $radius * sin($phi)];
229             push(@boundary_positions, $boundary);
230             }
231              
232             return @boundary_positions;
233             }
234              
235             sub draw {
236             my ($self, %args) = @_;
237             return undef if $self->hidden;
238              
239             my $center_position = $self->center->position;
240             my $support_position = $self->support->position;
241              
242             if(!$center_position) {
243             warn sprintf("Undefined center of circle %s, ".
244             "nothing to draw.\n", $self->id);
245             return undef;
246             }
247             if(!$support_position) {
248             warn sprintf("Undefined support of circle %s, ".
249             "nothing to draw.\n", $self->id);
250             return undef;
251             }
252              
253             my $radius = $self->radius;
254             if(!$radius) {
255             warn sprintf("Radius of circle %s vanishes, ".
256             "nothing to draw.\n", $self->id);
257             return undef;
258             }
259              
260             my @boundary_positions = $self->partial_draw
261             ? $self->_calculate_boundary_positions(%args)
262             : ([undef, undef], [undef, undef]);
263              
264             # currently, we just draw the full circle
265             $self->construction->draw_circle
266             (cx => $center_position->[0],
267             cy => $center_position->[1],
268             r => $radius,
269             x1 => $boundary_positions[0]->[0],
270             y1 => $boundary_positions[0]->[1],
271             x2 => $boundary_positions[1]->[0],
272             y2 => $boundary_positions[1]->[1],
273             style => $self->style_hash,
274             id => $self->id);
275              
276             $self->draw_label('x' => $support_position->[0],
277             'y' => $support_position->[1]);
278             }
279              
280             ###########################################################################
281             # #
282             # Overloading #
283             # #
284             ###########################################################################
285              
286             sub _intersect {
287             my ($self, $intersector) = @_;
288             my $class;
289            
290             $class = 'Math::Geometry::Construction::Circle';
291             if(eval { $intersector->isa($class) }) {
292             return $self->construction->add_derived_point
293             ('IntersectionCircleCircle', {input => [$self, $intersector]});
294             }
295              
296             $class = 'Math::Geometry::Construction::Line';
297             if(eval { $intersector->isa($class) }) {
298             return $self->construction->add_derived_point
299             ('IntersectionCircleLine', {input => [$self, $intersector]});
300             }
301             }
302              
303             1;
304              
305              
306             __END__
307              
308             =pod
309              
310             =head1 SYNOPSIS
311              
312              
313             =head1 DESCRIPTION
314              
315              
316             =head1 INTERFACE
317              
318             =head2 Public Attributes
319              
320             =head2 Methods for Users
321              
322             =head2 radius
323              
324             =head2 Methods for Subclass Developers
325              
326             =head3 as_svg
327              
328             =head3 id_template
329              
330              
331             =head1 AUTHOR
332              
333             Lutz Gehlen, C<< <perl at lutzgehlen.de> >>
334              
335              
336             =head1 LICENSE AND COPYRIGHT
337              
338             Copyright 2011-2013 Lutz Gehlen.
339              
340             This program is free software; you can redistribute it and/or modify it
341             under the terms of either: the GNU General Public License as published
342             by the Free Software Foundation; or the Artistic License.
343              
344             See http://dev.perl.org/licenses/ for more information.
345              
346             =cut