File Coverage

blib/lib/CAD/Calc.pm
Criterion Covered Total %
statement 29 487 5.9
branch 1 126 0.7
condition 0 20 0.0
subroutine 9 70 12.8
pod 61 61 100.0
total 100 764 13.0


line stmt bran cond sub pod time code
1             package CAD::Calc;
2             our $VERSION = '0.24_02';
3              
4 2     2   15825 use Math::Vec qw(NewVec :terse);
  2         6286  
  2         225  
5 2     2   1008 use Math::Complex qw(acos);
  2         14310  
  2         136  
6 2     2   826 use Math::Round::Var;
  2         1560  
  2         49  
7 2     2   1499 use Math::BigFloat;
  2         35808  
  2         9  
8              
9             # FIXME: add explicit exports to cleanup my namespace / make
10             # dependencies clearer
11              
12 2         248 use vars qw(
13             $linear_precision
14             $angular_precision
15             $linr
16             $angr
17             $pi
18 2     2   61296 );
  2         4  
19             $linear_precision = 1.0e-7;
20             $angular_precision = 1.0e-6;
21             $pi = atan2(1,1) * 4;
22              
23             require Exporter;
24             @ISA='Exporter';
25             @EXPORT_OK = qw(
26             pi
27             distdivide
28             subdivide
29             shorten_line
30             dist
31             dist2d
32             line_vec
33             slope
34             segs_as_transform
35             chevron_to_ray
36             signdist
37             offset
38             shift_line
39             line_to_rectangle
40             isleft
41             howleft
42             iswithin
43             iswithinc
44             unitleft
45             unitright
46             unit_angle
47             angle_reduce
48             angle_parse
49             angle_quadrant
50             collinear
51             triangle_angles
52             intersection_data
53             line_intersection
54             seg_line_intersection
55             seg_seg_intersection
56             line_ray_intersection
57             seg_ray_intersection
58             ray_pgon_int_index
59             ray_pgon_closest_index
60             perp_through_point
61             foot_on_line
62             foot_on_segment
63             Determinant
64             pgon_as_segs
65             pgon_area
66             pgon_centroid
67             pgon_lengths
68             pgon_angles
69             pgon_deltas
70             pgon_direction
71             pgon_bisectors
72             sort_pgons_lr
73             pgon_start_index
74             re_order_pgon
75             order_pgon
76             stringify
77             stringify_line
78             pol_to_cart
79             cart_to_pol
80             print_line
81             point_avg
82             arc_2pt
83             );
84              
85              
86            
87 2     2   9 use strict;
  2         2  
  2         33  
88 2     2   8 use Carp;
  2         3  
  2         169  
89              
90             =pod
91              
92             =head1 NAME
93              
94             CAD::Calc - generic cad-related geometry calculations
95              
96             =head1 AUTHOR
97              
98             Eric L. Wilhelm
99              
100             http://scratchcomputing.com
101              
102             =head1 COPYRIGHT
103              
104             This module is copyright (C) 2004, 2005 Eric L. Wilhelm. Portions copyright
105             (C) 2003 by Eric L. Wilhelm and A. Zahner Co.
106              
107             =head1 LICENSE
108              
109             This module is distributed under the same terms as Perl. See the Perl
110             source package for details.
111              
112             You may use this software under one of the following licenses:
113              
114             (1) GNU General Public License
115             (found at http://www.gnu.org/copyleft/gpl.html)
116             (2) Artistic License
117             (found at http://www.perl.com/pub/language/misc/Artistic.html)
118              
119             =head1 NO WARRANTY
120              
121             This software is distributed with ABSOLUTELY NO WARRANTY. The author,
122             his former employer, and any other contributors will in no way be held
123             liable for any loss or damages resulting from its use.
124              
125             =head1 Modifications
126              
127             The source code of this module is made freely available and
128             distributable under the GPL or Artistic License. Modifications to and
129             use of this software must adhere to one of these licenses. Changes to
130             the code should be noted as such and this notification (as well as the
131             above copyright information) must remain intact on all copies of the
132             code.
133              
134             Additionally, while the author is actively developing this code,
135             notification of any intended changes or extensions would be most helpful
136             in avoiding repeated work for all parties involved. Please contact the
137             author with any such development plans.
138              
139             =cut
140             ########################################################################
141              
142             =head1 Configuration
143              
144             Used to set package global values such as precision.
145              
146             =head2 import
147              
148             Not called directly. Triggered by the use() function.
149              
150             import(%options, @EXPORT_TAGS);
151              
152             Example:
153              
154             use CAD::Calc (
155             -precision => 0.125,
156             -angular => 1.0e-6,
157             qw(
158             seg_seg_intersection
159             dist2d
160             print_line
161             )
162             );
163              
164             =cut
165             sub import {
166             ## print "import called with @_\n";
167 2     2   19 local @ARGV = @_; # shame that Getopt::Long isn't structured better!
168 2     2   1182 use Getopt::Long;
  2         14041  
  2         6  
169 2 50       7 GetOptions( '-',
170             'precision=f' => \$linear_precision,
171             'angular=f' => \$angular_precision,
172             ) or croak("bad import arguments");
173             ## print "using $linear_precision for linear\n";
174             ## print "using $angular_precision for angular\n";
175 2         371 $linr = Math::Round::Var->new($linear_precision);
176 2         105 $angr = Math::Round::Var->new($angular_precision);
177             ## print "my linear rounding will be a ", ref($linr), "\n";
178             ## print "my angular rounding will be a ", ref($angr), "\n";
179 2         160 CAD::Calc->export_to_level(1, @ARGV);
180             } # end subroutine import definition
181             ########################################################################
182              
183             =head1 Constants
184              
185             =head2 pi
186              
187             Returns the value of $CAD::Calc::pi
188              
189             pi;
190              
191             =cut
192             sub pi() {
193 0     0 1   return($pi);
194             } # end subroutine pi definition
195             ########################################################################
196              
197             =head1 Functions
198              
199             These are all exported as options.
200              
201             =head2 distdivide
202              
203             Returns a list of point references resulting from dividing $line into
204             as many parts as possible which are at least $dist apart.
205              
206             @points = distdivide(\@line, $dist);
207              
208             =cut
209             sub distdivide {
210 0     0 1   my($line, $dist) = @_;
211 0 0         $dist or croak("call to distdivide would cause divide by zero");
212 0           my $ptA = NewVec(@{$line->[0]});
  0            
213 0           my $ptB = NewVec(@{$line->[1]});
  0            
214 0           my $seg = NewVec($ptB->Minus($ptA));
215 0           my $length = $seg->Length();
216             # optionally go for fewer points here?
217 0           my $count = $length / $dist;
218 0           $count = int($count);
219 0           return(subdivide($line, $count));
220             } # end subroutine distdivide definition
221             ########################################################################
222              
223             =head2 subdivide
224              
225             Returns a list of point references resulting from subdividing $line
226             into $count parts. The list will be $count-1 items long, (does not
227             include $line->[0] and $line->[1]);
228              
229             $line is of the form: [ [x1, y1, z1], [x2, y2, z2] ] where z1 and z2
230             are optional.
231              
232             @points = subdivide($line, $count);
233              
234             =cut
235             sub subdivide {
236 0     0 1   my ($line, $count) = @_;
237 0 0         $count || croak("cannot divide line into zero segments");
238 0           my $ptA = NewVec(@{$line->[0]});
  0            
239 0           my $ptB = NewVec(@{$line->[1]});
  0            
240             # print "line: @$ptA -- @$ptB\n";
241 0           my $seg = NewVec($ptB->Minus($ptA));
242 0           my @points;
243 0           for(my $st = 1; $st < $count; $st++) {
244 0           push(@points, [$ptA->Plus( [ $seg->ScalarMult($st / $count) ] ) ] );
245             }
246 0           return(@points);
247             } # end subroutine subdivide definition
248             ########################################################################
249              
250             =head2 shorten_line
251              
252             Shortens the line by the distances given in $lead and $tail.
253              
254             @line = shorten_line(\@line, $lead, $tail);
255              
256             =cut
257             sub shorten_line {
258 0     0 1   my ($line, $lead, $tail) = @_;
259 0           my $ptA = NewVec(@{$line->[0]});
  0            
260 0           my $ptB = NewVec(@{$line->[1]});
  0            
261             # print "line: @$ptA -- @$ptB\n";
262 0           my $seg = NewVec($ptB->Minus($ptA));
263 0           my $len = $seg->Length();
264 0 0         ($lead + $tail >= $len) && return();
265             # croak("CAD::Calc::shorten_line($lead, $tail)\n" .
266             # "\t creates inverted line from length: $len\n");
267             return(
268 0           [$ptA->Plus([$seg->ScalarMult($lead / $len)])],
269             [$ptB->Minus([$seg->ScalarMult($tail / $len)])],
270             );
271             } # end subroutine shorten_line definition
272             ########################################################################
273              
274             =head2 dist
275              
276             Returns the direct distance from ptA to ptB.
277              
278             dist($ptA, $ptB);
279              
280             =cut
281             sub dist {
282 0     0 1   my($ptA, $ptB) = @_;
283 0 0         (ref($ptB) eq "ARRAY") || ($ptB = [0,0,0]);
284 0           my $dist = sqrt(
285             ($ptB->[0] - $ptA->[0]) ** 2 +
286             ($ptB->[1] - $ptA->[1]) ** 2 +
287             ($ptB->[2] - $ptA->[2]) ** 2
288             );
289 0           return($dist);
290             } # end subroutine dist definition
291             ########################################################################
292              
293             =head2 dist2d
294              
295             Purposefully ignores a z (2) coordinate.
296              
297             dist2d($ptA, $ptB);
298              
299             =cut
300             sub dist2d {
301 0     0 1   my($ptA, $ptB) = @_;
302             # print "ref is: ", ref($ptB), "\n";
303             # (ref($ptB) eq "ARRAY") || ($ptB = [0,0,0]);
304             # XXX why was this ^-- here?!
305             # print "ptB: @{$ptB}\n";
306 0           my $dist = sqrt(
307             ($ptB->[0] - $ptA->[0]) ** 2 +
308             ($ptB->[1] - $ptA->[1]) ** 2
309             );
310 0           return($dist);
311             } # end subroutine dist2d definition
312             ########################################################################
313              
314             =head2 line_vec
315              
316             Returns a Math::Vec object representing the vector from $ptA to $ptB
317             (which is actually a segment.)
318              
319             $vec = line_vec($ptA, $ptB);
320              
321             =cut
322             sub line_vec {
323 0     0 1   return(NewVec(signdist(@_)));
324             } # end subroutine line_vec definition
325             ########################################################################
326              
327             =head2 slope
328              
329             Calculates the 2D slope between points @ptA and @ptB. Slope is defined
330             as dy / dx (rise over run.)
331              
332             If dx is 0, will return the string "inf", which Perl so kindly treats as
333             you would expect it to (except it doesn't like to answer the question
334             "what is infinity over infinity?") 5.8.? users: sorry, there seems to be
335             some regression here! (now we're using Math::BigFloat to return inf, so
336             rounding has to go through that)
337              
338             $slope = slope(\@ptA, \@ptB);
339              
340             =cut
341             sub slope {
342 0     0 1   my @line = @_;
343 0           my @delta = map({$line[1][$_] - $line[0][$_]} 0..1);
  0            
344 0 0         unless($delta[0]) {
345 0 0         if($delta[1] > 0) {
346 0           return(Math::BigFloat->binf());
347             }
348             else {
349 0           return(Math::BigFloat->binf('-'));
350             }
351             }
352 0           return($delta[1] / $delta[0]);
353             } # end subroutine slope definition
354             ########################################################################
355              
356             =head2 segs_as_transform
357              
358             Allows two segments to specify transform data.
359              
360             Returns: (\@translate, $rotate, $scale),
361            
362             where:
363              
364             @translate is a 2D array [$x, $y] basically describing segment @A
365              
366             $rotate is the angular difference between $A[0]->$B[0] and $A[1]->$B[1]
367              
368             $scale is the length of $A[1]->$B[1] divided by the length of
369             $A[0]->$B[0]
370              
371             my ($translate, $rotate, $scale) = segs_as_transform(\@A, \@B);
372              
373             =cut
374             sub segs_as_transform {
375 0     0 1   my ($A, $B) = @_;
376 0           my $av = line_vec(@$A);
377             # print_line($A);
378 0           my $sd = line_vec($A->[0], $B->[0]);
379 0           my $ed = line_vec($A->[1], $B->[1]);
380 0           my $ang = $ed->Ang() - $sd->Ang();
381 0           my $sl = $sd->Length();
382 0 0         $sl or croak("no length for divisor\n");
383 0           my $scale = $ed->Length() / $sl;
384 0           return([$av->[0], $av->[1]], $ang, $scale);
385             } # end subroutine segs_as_transform definition
386             ########################################################################
387              
388             =head2 chevron_to_ray
389              
390             Converts a chevron into a directional line by finding the midpoint
391             between the midpoints of each edge and connecting to the middle point.
392              
393             @line = chevron_to_ray(@pts);
394              
395             =cut
396             sub chevron_to_ray {
397 0     0 1   my (@pts) = @_;
398 0 0         (scalar(@pts) == 3) or croak("chevron needs three points");
399 0           my @mids;
400 0           foreach my $seg (0,1) {
401 0           ($mids[$seg]) = subdivide([$pts[$seg], $pts[$seg+1]], 2);
402             }
403 0           my ($start) = subdivide(\@mids, 2);
404 0           return($start, $pts[1]);
405             } # end subroutine chevron_to_ray definition
406             ########################################################################
407              
408             =head2 signdist
409              
410             Returns the signed distance
411              
412             signdist(\@ptA, \@ptB);
413              
414             =cut
415             sub signdist {
416 0     0 1   my ($ptA, $ptB) = @_;
417 0           my $b = NewVec(@{$ptB});
  0            
418 0           return($b->Minus($ptA));
419             } # end subroutine signdist definition
420             ########################################################################
421              
422             =head2 offset
423              
424             Creates a contour representing the offset of @polygon by $dist.
425             Positive distances are inward when @polygon is ccw.
426              
427             @polygons = offset(\@polygon, $dist);
428              
429             =cut
430             sub offset {
431 0     0 1   my ($polygon, $dist) = @_;
432             # this gets the OffsetPolygon routine (which still needs work)
433 0           my $helper = "Math::Geometry::Planar::Offset";
434 0           eval("require $helper;");
435 0 0         $@ and croak("cannot offset without $helper\n", $@);
436 0           $helper->import('OffsetPolygon');
437 0           my @pgons = OffsetPolygon($polygon, $dist);
438 0           return(@pgons);
439             } # end subroutine offset definition
440             ########################################################################
441              
442             =head2 intersection_data
443              
444             Calculates the two numerators and the denominator which are required
445             for various (seg-seg, line-line, ray-ray, seg-ray, line-ray, line-seg)
446             intersection calculations.
447              
448             ($k, $l, $d) = intersection_data(\@line, \@line);
449              
450             =cut
451             sub intersection_data {
452 0     0 1   my @l = @_;
453 0           my $n1 = Determinant(
454             $l[1][0][0]-$l[0][0][0],
455             $l[1][0][0]-$l[1][1][0],
456             $l[1][0][1]-$l[0][0][1],
457             $l[1][0][1]-$l[1][1][1],
458             );
459 0           my $n2 = Determinant(
460             $l[0][1][0]-$l[0][0][0],
461             $l[1][0][0]-$l[0][0][0],
462             $l[0][1][1]-$l[0][0][1],
463             $l[1][0][1]-$l[0][0][1],
464             );
465 0           my $d = Determinant(
466             $l[0][1][0]-$l[0][0][0],
467             $l[1][0][0]-$l[1][1][0],
468             $l[0][1][1]-$l[0][0][1],
469             $l[1][0][1]-$l[1][1][1],
470             );
471 0           return($n1, $n2, $d);
472              
473             } # end subroutine intersection_data definition
474             ########################################################################
475              
476             =head2 line_intersection
477              
478             Returns the intersection point of two lines.
479              
480             @pt = line_intersection(\@line, \@line, $tolerance);
481             @pt or die "no intersection";
482              
483             If tolerance is defined, it will be used to sprintf the parallel factor.
484             Beware of this, it is clunky and might change if I come up with
485             something better.
486              
487             =cut
488             sub line_intersection {
489 0     0 1   my @l = (shift, shift);
490 0           my ($tol) = @_;
491 0           foreach my $should (0,1) {
492             # print "should have $should\n";
493             # print $l[$should], "\n";
494 0 0         (ref($l[$should]) eq "ARRAY") or warn "not good\n";
495             }
496 0           my ($n1, $n2, $d) = intersection_data(@l);
497             ## print "d: $d\n";
498 0 0         if(defined($tol)) {
499 0           $d = sprintf("%0.${tol}f", $d);
500             }
501 0 0         if($d == 0) {
502             # print "parallel!\n";
503 0           return(); # parallel
504             }
505 0           my @pt = (
506             $l[0][0][0] + $n1 / $d * ($l[0][1][0] - $l[0][0][0]),
507             $l[0][0][1] + $n1 / $d * ($l[0][1][1] - $l[0][0][1]),
508             );
509             # print "got point: @pt\n";
510 0           return(@pt);
511             } # end subroutine line_intersection definition
512             ########################################################################
513              
514             =head2 seg_line_intersection
515              
516             Finds the intersection of @segment and @line.
517              
518             my @pt = seg_line_intersection(\@segment, \@line);
519             @pt or die "no intersection";
520             unless(defined($pt[1])) {
521             die "lines are parallel";
522             }
523              
524             =cut
525             sub seg_line_intersection {
526 0     0 1   my (@l) = @_;
527 0           my ($n1, $n2, $d) = intersection_data(@l);
528             # XXX not consistent with line_intersection function
529 0 0         if(sprintf("%0.9f", $d) == 0) {
530 0           return(0); # lines are parallel
531             }
532 0 0 0       if( ! (($n1/$d <= 1) && ($n1/$d >=0)) ) {
533 0           return(); # no intersection on segment
534             }
535 0           my @pt = (
536             $l[0][0][0] + $n1 / $d * ($l[0][1][0] - $l[0][0][0]),
537             $l[0][0][1] + $n1 / $d * ($l[0][1][1] - $l[0][0][1]),
538             );
539 0           return(@pt);
540             } # end subroutine seg_line_intersection definition
541             ########################################################################
542              
543             =head2 seg_seg_intersection
544              
545             my @pt = seg_seg_intersection(\@segmenta, \@segmentb);
546              
547             =cut
548             sub seg_seg_intersection {
549 0     0 1   my (@l) = @_;
550 0           my ($n1, $n2, $d) = intersection_data(@l);
551             # print "data $n1, $n2, $d\n";
552 0 0         if(sprintf("%0.9f", $d) == 0) {
553 0           return(0); # lines are parallel
554             }
555 0 0 0       if( ! ((sprintf("%0.9f", $n1/$d) <= 1) && (sprintf("%0.9f", $n1/$d) >=0)) ) {
556             # warn("n1/d is ", $n1/$d);
557 0           return(); # no intersection on segment a
558             }
559 0 0 0       if( ! ((sprintf("%0.9f", $n2/$d) <= 1) && (sprintf("%0.9f", $n2/$d) >=0)) ) {
560             # warn("n2/d is ", $n2/$d);
561 0           return(); # no intersection on segment b
562             }
563 0           my @pt = (
564             $l[0][0][0] + $n1 / $d * ($l[0][1][0] - $l[0][0][0]),
565             $l[0][0][1] + $n1 / $d * ($l[0][1][1] - $l[0][0][1]),
566             );
567 0           return(@pt);
568             } # end subroutine seg_seg_intersection definition
569             ########################################################################
570              
571             =head2 line_ray_intersection
572              
573             Intersects @line with @ray, where $ray[1] is the direction of the
574             infinite ray.
575              
576             line_ray_intersection(\@line, \@ray);
577              
578             =cut
579             sub line_ray_intersection {
580 0     0 1   my (@l) = @_;
581 0           my ($n1, $n2, $d) = intersection_data(@l);
582             # $n1 is distance along segment (must be between 0 and 1)
583             # $n2 is distance along ray (must be greater than 0)
584 0 0         if(sprintf("%0.9f", $d) == 0) {
585             # print "parallel\n";
586 0           return(0); # lines are parallel
587             }
588             # same as seg_ray_intersection(), but we skip the segment check
589 0 0         if($n2 / $d < 0) {
590             # print "nothing on ray\n";
591             # segment intersects behind ray
592 0           return();
593             }
594 0           my @pt = (
595             $l[0][0][0] + $n1 / $d * ($l[0][1][0] - $l[0][0][0]),
596             $l[0][0][1] + $n1 / $d * ($l[0][1][1] - $l[0][0][1]),
597             );
598 0           return(@pt);
599             } # end subroutine line_ray_intersection definition
600             ########################################################################
601              
602             =head2 seg_ray_intersection
603              
604             Intersects @seg with @ray, where $ray[1] is the direction of the
605             infinite ray.
606              
607             seg_ray_intersection(\@seg, \@ray);
608              
609             =cut
610             sub seg_ray_intersection {
611 0     0 1   my (@l) = @_;
612 0           my ($n1, $n2, $d) = intersection_data(@l);
613             # $n1 is distance along segment (must be between 0 and 1)
614             # $n2 is distance along ray (must be greater than 0)
615 0 0         if(sprintf("%0.9f", $d) == 0) {
616             # print "parallel\n";
617 0           return(0); # lines are parallel
618             }
619 0 0 0       if( ! (($n1/$d <= 1) && ($n1/$d >=0)) ) {
620             # print "nothing on segment\n";
621 0           return(); # no intersection on segment
622             }
623 0 0         if($n2 / $d < 0) {
624             # print "nothing on ray\n";
625             # segment intersects behind ray
626 0           return();
627             }
628 0           my @pt = (
629             $l[0][0][0] + $n1 / $d * ($l[0][1][0] - $l[0][0][0]),
630             $l[0][0][1] + $n1 / $d * ($l[0][1][1] - $l[0][0][1]),
631             );
632 0           return(@pt);
633              
634             } # end subroutine seg_ray_intersection definition
635             ########################################################################
636              
637             =head2 ray_pgon_int_index
638              
639             Returns the first (lowest) index of @polygon which has a segment
640             intersected by @ray.
641              
642             $index = ray_pgon_int_index(\@ray, \@polygon);
643              
644             =cut
645             sub ray_pgon_int_index {
646 0     0 1   my ($ray, $pgon) = @_;
647 0 0         (scalar(@$ray) == 2) or croak("not a ray");
648 0           for(my $e = 0; $e < @$pgon; $e++) {
649 0           my $n = $e + 1;
650 0 0         ($n > $#$pgon) && ($n -= @$pgon);
651 0           my $seg = [$pgon->[$e], $pgon->[$n]];
652 0           my @int = seg_ray_intersection($seg, $ray);
653 0 0         if(defined($int[1])) {
654             # print "intersect @int\n";
655 0           return($e);
656             }
657             }
658 0           return();
659             } # end subroutine ray_pgon_int_index definition
660             ########################################################################
661              
662             =head2 ray_pgon_closest_index
663              
664             Returns the closest (according to dist2d) index of @polygon which has a
665             segment intersected by @ray.
666              
667             $index = ray_pgon_closest_index(\@ray, \@polygon);
668              
669             =cut
670             sub ray_pgon_closest_index {
671 0     0 1   my ($ray, $pgon) = @_;
672 0 0         (scalar(@$ray) == 2) or croak("not a ray");
673 0           my @found;
674 0           for(my $e = 0; $e < @$pgon; $e++) {
675 0           my $n = $e + 1;
676 0 0         ($n > $#$pgon) && ($n -= @$pgon);
677 0           my $seg = [$pgon->[$e], $pgon->[$n]];
678 0           my @int = seg_ray_intersection($seg, $ray);
679 0 0         if(defined($int[1])) {
680             # print "intersect @int\n";
681 0           push(@found, [$e, dist2d($ray->[0], \@int)]);
682             }
683             }
684 0 0         if(@found) {
685 0           my $least = (sort({$a->[1] <=> $b->[1]} @found))[0];
  0            
686 0           return($least->[0]);
687             }
688             else {
689 0           return();
690             }
691             } # end subroutine ray_pgon_closest_index definition
692             ########################################################################
693              
694             =head2 perp_through_point
695              
696             @line = perp_through_point(\@pt, \@line);
697              
698             =cut
699             sub perp_through_point {
700 0     0 1   my ($pt, $seg) = @_;
701 0           my @nv = ( # normal vector:
702             $seg->[1][1] - $seg->[0][1],
703             - ($seg->[1][0] - $seg->[0][0]),
704             );
705 0           my @ep = ( # end point of ray
706             $pt->[0] + $nv[0],
707             $pt->[1] + $nv[1],
708             );
709 0           return($pt, \@ep);
710             } # end subroutine perp_through_point definition
711             ########################################################################
712              
713             =head2 foot_on_line
714              
715             @pt = foot_on_line(\@pt, \@line);
716              
717             =cut
718             sub foot_on_line {
719 0     0 1   my ($pt, $seg) = @_;
720 0           return(line_intersection($seg, [perp_through_point($pt, $seg)]));
721             } # end subroutine foot_on_line definition
722             ########################################################################
723              
724             =head2 foot_on_segment
725              
726             Returns the perpendicular foot of @pt on @seg. See seg_ray_intersection.
727              
728             @pt = foot_on_segment(\@pt, \@seg);
729              
730             =cut
731             sub foot_on_segment {
732 0     0 1   my ($pt, $seg) = @_;
733 0           return(seg_line_intersection($seg, [perp_through_point($pt, $seg)]));
734             } # end subroutine foot_on_segment definition
735             ########################################################################
736              
737             =head2 Determinant
738              
739             Determinant($x1, $y1, $x2, $y2);
740              
741             =cut
742             sub Determinant {
743 0     0 1   my ($x1,$y1,$x2,$y2) = @_;
744 0           return($x1*$y2 - $x2*$y1);
745             } # end subroutine Determinant definition
746             ########################################################################
747              
748             =head2 pgon_as_segs
749              
750             Returns a list of [[@ptA],[@ptB]] segments representing the edges of
751             @pgon, where segment "0" is from $pgon[0] to $pgon[1]
752              
753             @segs = pgon_as_segs(@pgon);
754              
755             =cut
756             sub pgon_as_segs {
757 0     0 1   my (@pgon) = @_;
758 0           my @segs = ([])x scalar(@pgon);
759 0           for(my $i = -1; $i < $#pgon; $i++) {
760 0           $segs[$i] = [@pgon[$i, $i+1]];
761             }
762 0           return(@segs);
763             } # end subroutine pgon_as_segs definition
764             ########################################################################
765              
766             =head2 pgon_area
767              
768             $area = pgon_area(@polygon);
769              
770             =cut
771             sub pgon_area {
772 0     0 1   my @pgon = @_;
773 0 0         (@pgon > 2) or return();
774 0           my $atmp = 0;
775 0           for(my $i = -1; $i < $#pgon; $i++) {
776 0           my $j = $i+1;
777 0           my ($xi, $yi) = @{$pgon[$i]};
  0            
778 0           my ($xj, $yj) = @{$pgon[$j]};
  0            
779 0           $atmp += $xi * $yj - $xj * $yi;
780             }
781 0 0         $atmp or return(); # no area
782 0           return($atmp / 2);
783             } # end subroutine pgon_area definition
784             ########################################################################
785              
786             =head2 pgon_centroid
787              
788             @centroid = pgon_centroid(@polygon);
789              
790             =cut
791             sub pgon_centroid {
792 0     0 1   my @pgon = @_;
793 0 0         (@pgon > 2) or return();
794 0           my $atmp = 0;
795 0           my $xtmp = 0;
796 0           my $ytmp = 0;
797 0           for(my $i = -1; $i < $#pgon; $i++) {
798 0           my $j = $i+1;
799 0           my ($xi, $yi) = @{$pgon[$i]};
  0            
800 0           my ($xj, $yj) = @{$pgon[$j]};
  0            
801 0           my $ai = $xi * $yj - $xj * $yi;
802 0           $atmp += $ai;
803 0           $xtmp += ($xj + $xi) * $ai;
804 0           $ytmp += ($yj + $yi) * $ai;
805             }
806 0 0         $atmp or return(); # no area
807 0           my $area = $atmp / 2;
808 0           return($xtmp / (3 * $atmp), $ytmp / (3 * $atmp));
809             } # end subroutine pgon_centroid definition
810             ########################################################################
811              
812             =head2 pgon_lengths
813              
814             @lengths = pgon_lengths(@pgon);
815              
816             =cut
817             sub pgon_lengths {
818 0     0 1   my (@points) = @_;
819 0           my @lengths = (0) x scalar(@points);
820 0           for(my $i = -1; $i < $#points; $i++) {
821 0           $lengths[$i] = dist2d(@points[$i, $i+1]);
822             }
823 0           return(@lengths);
824             } # end subroutine pgon_lengths definition
825             ########################################################################
826              
827             =head2 pgon_angles
828              
829             Returns the angle of each edge of polygon in xy plane. These fall
830             between -$pi and +$pi due to the fact that it is basically just a call
831             to the atan2() builtin.
832              
833             Edges are numbered according to the index of the point which starts
834             the edge.
835              
836             @angles = pgon_angles(@points);
837              
838             =cut
839             sub pgon_angles {
840 0     0 1   my (@points) = @_;
841 0           my @angles = (0) x scalar(@points);
842             # print "number of angles: @angles\n";
843 0           for(my $i = -1; $i < $#points; $i++) {
844 0           my $vec = NewVec(signdist(@points[$i, $i+1]));
845 0           $angles[$i] = $vec->Ang();
846             }
847 0           return(@angles);
848             } # end subroutine pgon_angles definition
849             ########################################################################
850              
851             =head2 pgon_deltas
852              
853             Returns the differences between the angles of each edge of @polygon.
854             These will be indexed according to the point at which they occur, and
855             will be positive radians for ccw angles. Summing the @deltas will yield
856             +/-2pi (negative for cw polygons.)
857              
858             @deltas = pgon_deltas(@pgon);
859              
860             =cut
861             sub pgon_deltas {
862 0     0 1   my (@pts) = @_;
863 0           my @angles = pgon_angles(@pts);
864 0           return(ang_deltas(@angles));
865             } # end subroutine pgon_deltas definition
866             ########################################################################
867              
868             =head2 ang_deltas
869              
870             Returns the same thing as pgon_deltas, but saves a redundant call to
871             pgon_angles.
872              
873             my @angs = pgon_angles(@pts);
874             my @dels = ang_deltas(@angs);
875              
876             =cut
877             sub ang_deltas {
878 0     0 1   my (@angs) = @_;
879 0           my @deltas = (0)x $#angs;
880 0           for(my $i = 0; $i < @angs; $i++) {
881 0           my $ang = angle_reduce($angs[$i] - $angs[$i-1]);
882 0           $deltas[$i] = $ang;
883             }
884 0           return(@deltas);
885             } # end subroutine ang_deltas definition
886             ########################################################################
887              
888             =head2 pgon_direction
889              
890             Returns 1 for counterclockwise and 0 for clockwise. Uses the sum of the
891             differences of angles of @polygon. If this sum is less than 0, the
892             polygon is clockwise.
893              
894             $ang_sum = pgon_direction(@polygon);
895              
896             =cut
897             sub pgon_direction {
898 0     0 1   my (@pgon) = @_;
899 0           my @angs = pgon_deltas(@pgon);
900 0           return(angs_direction(@angs));
901             } # end subroutine pgon_direction definition
902             ########################################################################
903              
904             =head2 angs_direction
905              
906             Returns the same thing as pgon_direction, but saves a redundant call to
907             pgon_deltas.
908              
909             my @angs = pgon_deltas(@pgon);
910             my $dir = angs_direction(@angs);
911              
912             =cut
913             sub angs_direction {
914 0     0 1   my (@angs) = @_;
915 0           my $sum = 0;
916 0           foreach my $ang (@angs) {
917 0           $sum+= $ang;
918             }
919 0           return($sum > 0);
920             } # end subroutine angs_direction definition
921             ########################################################################
922              
923             =head2 pgon_bisectors
924              
925             pgon_bisectors();
926              
927             =cut
928             sub pgon_bisectors {
929 0     0 1   warn "unfinished";
930 0           croak "finish it";
931             } # end subroutine pgon_bisectors definition
932             ########################################################################
933              
934             =head2 sort_pgons_lr
935              
936             Sorts polygons by their average points returning a list which reads from
937             left to right. (Rather odd place for this?)
938              
939             @pgons = sort_pgons_lr(@pgons);
940              
941             =cut
942             sub sort_pgons_lr {
943 0     0 1   my @pgons = @_;
944             # no sense calculating all for naught:
945 0 0         (scalar(@pgons) > 1) || return(@pgons);
946 0           my @avg;
947 0           foreach my $pgon (@pgons) {
948 0           push(@avg, [point_avg(@$pgon)]);
949             }
950 0           my @ord = sort({$avg[$a][0] <=> $avg[$b][0]} 0..$#avg);
  0            
951 0           return(@pgons[@ord]);
952             } # end subroutine sort_pgons_lr definition
953             ########################################################################
954              
955             =head2 pgon_start_index
956              
957             Returns the index of pgon which is at the "lowest left".
958              
959             $i = pgon_start_index(@pgon);
960              
961             =cut
962             sub pgon_start_index {
963 0     0 1   my @pgon = @_;
964             my @ordered = sort({
965 0           my $c;
  0            
966 0   0       $c = $pgon[$a][$_] <=> $pgon[$b][$_] and return $c for 0..1;
967             } 0..$#pgon);
968             # print "index order @ordered\n";
969 0           return($ordered[0]);
970             } # end subroutine pgon_start_index definition
971             ########################################################################
972              
973             =head2 pgon_start_indexb
974              
975             Returns the index of pgon which is at the "lowest left".
976              
977             Different method (is it faster?)
978              
979             $i = pgon_start_indexb(@pgon);
980              
981             =cut
982             sub pgon_start_indexb {
983 0     0 1   my @pgon = @_;
984 0           my %sort_hash;
985 0           for(my $c = 0; $c < @pgon; $c++) {
986             # this is still janky, should really do something about slope?
987 0           my @pt = map({sprintf("%0.2f", $_)} @{$pgon[$c]});
  0            
  0            
988 0           $sort_hash{$pt[0]}{$pt[1]} = $c;
989             }
990 0           my ($least_x) = sort({$a <=> $b} keys(%sort_hash));
  0            
991 0           my ($least_y) = sort({$a <=> $b} keys(%{$sort_hash{$least_x}}));
  0            
  0            
992 0           my $index = $sort_hash{$least_x}{$least_y};
993 0           return($index);
994             } # end subroutine pgon_start_indexb definition
995             ########################################################################
996              
997             =head2 pgon_start_index_z
998              
999             Yet another different method (is this even correct?)
1000              
1001             pgon_start_index_z();
1002              
1003             =cut
1004             sub pgon_start_index_z {
1005 0     0 1   my @pgon = @_;
1006             # use the quarter-length
1007 0           my @minmax = (sort({$a <=> $b} map({$_->[0]} @pgon)))[0,-1];
  0            
  0            
1008 0           my $x_fourth = $minmax[0] + ($minmax[1] - $minmax[0]) / 4;
1009 0           my @contend;
1010 0           for(my $i = 0; $i < @pgon; $i++) {
1011 0 0         if($pgon[$i][0] < $x_fourth) {
1012 0           push(@contend, $i);
1013             }
1014             }
1015             # print scalar(@contend), " contenders\n";
1016 0           my @ordered = sort({$pgon[$a][1] <=> $pgon[$b][1]} @contend);
  0            
1017             # to be even more thorough:
1018 0           my @yminmax = (sort({$a <=> $b} map({$_->[1]} @pgon)))[0,-1];
  0            
  0            
1019             # quantify with 1/4 of the yspan:
1020 0           my $yspan = ($yminmax[1] - $yminmax[0]) / 4;
1021 0           my $choice = shift(@ordered);
1022 0           foreach my $idx (@ordered) {
1023             # if it is below and left, then we already have it, if it above
1024             # and left, then we might want it
1025 0 0         if($pgon[$idx][1] < ($pgon[$choice][1] + $yspan)) {
1026 0 0         ($pgon[$idx][0] < $pgon[$choice][0]) and ($choice = $idx);
1027             }
1028             }
1029 0           return($choice);
1030             } # end subroutine pgon_start_index_z definition
1031             ########################################################################
1032              
1033             =head2 re_order_pgon
1034              
1035             Imposes counter-clockwise from "lower-left" ordering.
1036              
1037             @pgon = re_order_pgon(@pgon);
1038              
1039             =cut
1040             sub re_order_pgon {
1041 0     0 1   my @pgon = @_;
1042 0 0         unless(pgon_direction(@pgon)) {
1043 0           @pgon = reverse(@pgon);
1044             }
1045 0           my $index = pgon_start_index_z(@pgon);
1046 0           return(order_pgon($index, \@pgon));
1047             } # end subroutine re_order_pgon definition
1048             ########################################################################
1049              
1050             =head2 order_pgon
1051              
1052             Rewinds the polygon (e.g. list) to the specified $start index. This is
1053             not restricted to polygons (just continuous (looped) lists.)
1054              
1055             @pgon = order_pgon($start, \@pgon);
1056              
1057             =cut
1058             sub order_pgon {
1059 0     0 1   my $index = shift;
1060 0           my $pg = shift;
1061 0           my @pgon = @{$pg};
  0            
1062 0 0         ($index < 0) and ($index += @pgon);
1063 0           my @new;
1064 0           for(my $d = 0; $d < @pgon; $d++) {
1065 0           my $i = $index + $d;
1066 0 0         ($i > $#pgon) and ($i -= @pgon);
1067             # print "using $i\n";
1068 0           push(@new, $pgon[$i]);
1069             }
1070 0           return(@new);
1071             } # end subroutine order_pgon definition
1072             ########################################################################
1073              
1074             =head2 shift_line
1075              
1076             Shifts line to right or left by $distance.
1077              
1078             @line = shift_line(\@line, $distance, right|left);
1079              
1080             =cut
1081             sub shift_line {
1082 0     0 1   my ($line, $dist, $dir) = @_;
1083 0           my @line = @$line;
1084 0           my $mvec;
1085 0 0         if($dir eq "left") {
    0          
1086 0           $mvec = unitleft(@line);
1087             }
1088             elsif($dir eq "right") {
1089 0           $mvec = unitright(@line);
1090             }
1091             else {
1092 0           croak ("direction must be \"left\" or \"right\"\n");
1093             }
1094 0           $mvec = NewVec($mvec->ScalarMult($dist));
1095 0           my @newline = map({[$mvec->Plus($_)]} @line);
  0            
1096 0           return(@newline);
1097             } # end subroutine shift_line definition
1098             ########################################################################
1099              
1100             =head2 line_to_rectangle
1101              
1102             Creates a rectangle, centered about @line.
1103              
1104             my @rec = line_to_rectangle(\@line, $offset, \%options);
1105              
1106             The direction of the returned points will be counter-clockwise around
1107             the original line, with the first point at the 'lower-left' (e.g. if
1108             your line points up, $rec[0] will be below and to the left of
1109             $line[0].)
1110              
1111             Available options
1112              
1113             ends => 1|0, # extend endpoints by $offset (default = 1)
1114              
1115             =cut
1116             sub line_to_rectangle {
1117 0     0 1   my ($ln, $offset, $opts) = @_;
1118 0           my %options = (ends => 1);
1119 0 0         (ref($opts) eq "HASH") && (%options = %$opts);
1120 0           my @line = @$ln;
1121 0 0         ($offset > 0) or
1122             croak "offset ($offset) must be positive non-zero\n";
1123 0           my $a = NewVec(@{$line[0]});
  0            
1124 0           my $b = NewVec(@{$line[1]});
  0            
1125             # unit vector of line
1126 0           my $vec = NewVec(NewVec($b->Minus($a))->UnitVector());
1127             # crossed with unit vector make unit vector left
1128 0           my $perp = NewVec($vec->Cross([0,0,-1]));
1129 0           my ($back, $forth);
1130 0 0         if($options{ends}) {
1131 0           $back = NewVec($a->Minus([$vec->ScalarMult($offset)]));
1132 0           $forth = NewVec($b->Plus([$vec->ScalarMult($offset)]));
1133             }
1134             else {
1135 0           $back = $a;
1136 0           $forth = $b;
1137             }
1138 0           my $left = NewVec($perp->ScalarMult($offset));
1139 0           my $right = NewVec($perp->ScalarMult(-$offset));
1140             # upper and lower here only mean anything
1141             # if line originally pointed "up"
1142 0           my @ll = $back->Plus($left);
1143 0           my @lr = $back->Plus($right);
1144 0           my @ur = $forth->Plus($right);
1145 0           my @ul = $forth->Plus($left);
1146 0           return(\@ll, \@lr, \@ur, \@ul);
1147             } # end subroutine line_to_rectangle definition
1148             ########################################################################
1149              
1150             =head2 isleft
1151              
1152             Returns true if @point is left of @line.
1153              
1154             $bool = isleft(\@line, \@point);
1155              
1156             =cut
1157             sub isleft {
1158 0     0 1   my ($line, $pt) = @_;
1159 0           my $how = howleft($line, $pt);
1160 0           return($how > 0);
1161             } # end subroutine isleft definition
1162             ########################################################################
1163              
1164             =head2 howleft
1165              
1166             Returns positive if @point is left of @line.
1167              
1168             $number = howleft(\@line, \@point);
1169              
1170             =cut
1171             sub howleft {
1172 0     0 1   my ($line, $pt) = @_;
1173 0           my $isleft = ($line->[1][0] - $line->[0][0]) *
1174             ($pt->[1] - $line->[0][1]) -
1175             ($line->[1][1] - $line->[0][1]) *
1176             ($pt->[0] - $line->[0][0]);
1177 0           return($isleft);
1178             } # end subroutine howleft definition
1179             ########################################################################
1180              
1181             =head2 iswithin
1182              
1183             Returns true if @pt is within the polygon @bound. This will be negative
1184             for clockwise input.
1185              
1186             $fact = iswithin(\@bound, \@pt);
1187              
1188             =cut
1189             sub iswithin {
1190 0     0 1   my ($bnd, $pt) = @_;
1191 0           my $winding = 0;
1192 0           my @bound = @$bnd;
1193 0           for(my $n = -1; $n < $#bound; $n ++) {
1194 0           my $next = $n+1;
1195 0           my @seg = ($bound[$n], $bound[$next]);
1196 0           my $isleft = howleft(\@seg, $pt);
1197 0 0         if($seg[0][1] <= $pt->[1]) {
    0          
1198 0 0         if($seg[1][1] > $pt->[1]) {
1199 0 0         ($isleft > 0) && $winding++;
1200             # print "winding up\n";
1201             }
1202             }
1203             elsif($seg[1][1] <= $pt->[1]) {
1204 0 0         ($isleft < 0) && $winding--;
1205             # print "winding up\n";
1206             }
1207             } # end for $n
1208             # print "winding is $winding\n";
1209 0           return($winding);
1210             } # end subroutine iswithin definition
1211             ########################################################################
1212              
1213             =head2 iswithinc
1214              
1215             Seems to be consistently much faster than the typical winding-number
1216             iswithin. The true return value is always positive regardless of the
1217             polygon's direction.
1218              
1219             $fact = iswithinc(\@bound, \@pt);
1220              
1221             =cut
1222             sub iswithinc {
1223 0     0 1   my ($bnd, $pt) = @_;
1224 0           my $c = 0;
1225 0           my @bound = @$bnd;
1226 0           my ($x, $y) = @$pt;
1227             # straight from the comp.graphics.algorithms faq:
1228 0           for (my $i = 0, my $j = $#bound; $i < @bound; $j = $i++) {
1229             # print "checking from $j to $i\n";
1230 0 0 0       (((($bound[$i][1]<=$y) && ($y<$bound[$j][1])) ||
      0        
1231             (($bound[$j][1]<=$y) && ($y<$bound[$i][1]))) &&
1232             ($x < ($bound[$j][0] - $bound[$i][0]) * ($y - $bound[$i][1]) / ($bound[$j][1] - $bound[$i][1]) + $bound[$i][0]))
1233             and ($c = !$c);
1234             }
1235 0           return($c);
1236             } # end subroutine iswithinc definition
1237             ########################################################################
1238              
1239             =head2 unitleft
1240              
1241             Returns a unit vector which is perpendicular and to the left of @line.
1242             Purposefully ignores any z-coordinates.
1243              
1244             $vec = unitleft(@line);
1245              
1246             =cut
1247             sub unitleft {
1248 0     0 1   my (@line) = @_;
1249             my $ln = NewVec(
1250 0           NewVec(@{$line[1]}[0,1])->Minus([@{$line[0]}[0,1]])
  0            
  0            
1251             );
1252 0           $ln = NewVec($ln->UnitVector());
1253 0           my $left = NewVec($ln->Cross([0,0,-1]));
1254             ## my $isleft = isleft(\@line, [$left->Plus($line[0])]);
1255             ## print "fact said $isleft\n";
1256 0           return($left);
1257             } # end subroutine unitleft definition
1258             ########################################################################
1259              
1260             =head2 unitright
1261              
1262             Negative of unitleft().
1263              
1264             $vec = unitright(@line);
1265              
1266             =cut
1267             sub unitright {
1268 0     0 1   my $vec = unitleft(@_);
1269 0           $vec = NewVec($vec->ScalarMult(-1));
1270 0           return($vec);
1271             } # end subroutine unitright definition
1272             ########################################################################
1273              
1274             =head2 unit_angle
1275              
1276             Returns a Math::Vec vector which has a length of one at angle $ang (in
1277             the XY plane.) $ang is fed through angle_parse().
1278              
1279             $vec = unit_angle($ang);
1280              
1281             =cut
1282             sub unit_angle {
1283 0     0 1   my ($ang) = @_;
1284 0           $ang = angle_parse($ang);
1285 0           my $x = cos($ang);
1286 0           my $y = sin($ang);
1287 0           return(NewVec($x, $y));
1288             } # end subroutine unit_angle definition
1289             ########################################################################
1290              
1291             =head2 angle_reduce
1292              
1293             Reduces $ang (in radians) to be between -pi and +pi.
1294              
1295             $ang = angle_reduce($ang);
1296              
1297             =cut
1298             sub angle_reduce {
1299 0     0 1   my $ang = shift;
1300 0           while($ang > $pi) {
1301 0           $ang -= 2*$pi;
1302             }
1303 0           while($ang <= -$pi) {
1304 0           $ang += 2*$pi;
1305             }
1306 0           return($ang);
1307             } # end subroutine angle_reduce definition
1308             ########################################################################
1309              
1310             =head2 angle_parse
1311              
1312             Parses the variable $ang and returns a variable in radians. To convert
1313             degrees to radians: $rad = angle_parse($deg . "d")
1314              
1315             $rad = angle_parse($ang);
1316              
1317             =cut
1318             sub angle_parse {
1319 0     0 1   my $ang = shift;
1320 0 0         if($ang =~ s/d$//) {
1321 0           $ang *= $pi / 180;
1322             }
1323 0           return($ang);
1324             } # end subroutine angle_parse definition
1325             ########################################################################
1326              
1327             =head2 angle_quadrant
1328              
1329             Returns the index of the quadrant which contains $angle. $angle is in
1330             radians.
1331              
1332             $q = angle_quadrant($angle);
1333             @syms = qw(I II III IV);
1334             print "angle is in quadrant: $syms[$q]\n";
1335              
1336             =cut
1337             sub angle_quadrant {
1338 0     0 1   my $ang = shift;
1339 0           my $x = cos($ang);
1340 0           my $y = sin($ang);
1341 0           my $vert = ($x < 0);
1342 0           my $hori = ($y < 0);
1343 0           my @list = (
1344             [0,3],
1345             [1,2],
1346             );
1347 0           return($list[$vert][$hori]);
1348             } # end subroutine angle_quadrant definition
1349             ########################################################################
1350              
1351             =head2 collinear
1352              
1353             $fact = collinear(\@pt1, \@pt2, \@pt3);
1354              
1355             =cut
1356             sub collinear {
1357 0     0 1   my @pts = @_;
1358 0 0         (@pts == 3) or croak("must call with 3 points");
1359 0           my ($pta, $ptb, $ptc) = @pts;
1360 0           my $va = line_vec($pta, $ptb);
1361 0           my $vb = line_vec($ptb, $ptc);
1362 0           my $cp = NewVec($va->Cross($vb));
1363 0           my $ta = $cp->Length();
1364             # print "my vectors: @$va\n@$vb\n@$cp\n";
1365             # print "angs: ", $va->Ang(), " and ", $vb->Ang(), "\n";
1366             # print "ta: $ta\n";
1367 0           return(abs($ta) < 0.001);
1368             } # end subroutine collinear definition
1369             ########################################################################
1370              
1371             =head2 triangle_angles
1372              
1373             Calculates the angles of a triangle based on it's lengths.
1374              
1375             @angles = triangle_angles(@lengths);
1376              
1377             The order of the returned angle will be "the angle before the edge".
1378              
1379             =cut
1380             sub triangle_angles {
1381 0     0 1   my @len = @_;
1382 0 0         (@len == 3) or croak("triangle must have 3 sides");
1383 0           my @angs = (
1384             acos(
1385             ($len[2]**2 + $len[0]**2 - $len[1]**2) /
1386             (2 * $len[2] * $len[0])
1387             ),
1388             acos(
1389             ($len[1]**2 + $len[0]**2 - $len[2]**2) /
1390             (2 * $len[1] * $len[0])
1391             ),
1392             );
1393 0           $angs[2] = $pi - $angs[0] - $angs[1];
1394 0           print "angs: @angs\n";
1395             } # end subroutine triangle_angles definition
1396             ########################################################################
1397              
1398             =head2 stringify
1399              
1400             Turns point into a string rounded according to $rnd. The optional
1401             $count allows you to specify how many coordinates to use.
1402              
1403             $string = stringify(\@pt, $rnd, $count);
1404              
1405             =cut
1406             sub stringify {
1407 0     0 1   my ($pt, $rnd, $count) = @_;
1408             # FIXME: # rounding should be able to do fancier things here:
1409 0 0         unless(defined($count)) {
1410 0           $count = scalar(@{$pt});
  0            
1411             }
1412 0           my $top = $count - 1;
1413             my $str = join(",",
1414 0           map( {sprintf("%0.${rnd}f", $_)} @{$pt}[0..$top]) );
  0            
  0            
1415 0           return($str);
1416            
1417             } # end subroutine stringify definition
1418             ########################################################################
1419              
1420             =head2 stringify_line
1421              
1422             Turns a line (or polyline) into a string. See stringify().
1423              
1424             stringify_line(\@line, $char, $rnd, $count);
1425              
1426             =cut
1427             sub stringify_line {
1428 0     0 1   my ($line, $char, $rnd, $count) = @_;
1429 0 0         defined($char) or ($char = "\n");
1430 0 0         defined($rnd) or ($rnd = 2);
1431 0 0         defined($count) or ($count = 2);
1432 0           return(join($char, map({stringify($_, $rnd, $count)} @$line)));
  0            
1433             } # end subroutine stringify_line definition
1434             ########################################################################
1435              
1436             =head2 pol_to_cart
1437              
1438             Convert from polar to cartesian coordinates.
1439              
1440             my ($x, $y, $z) = pol_to_cart($radius, $theta, $z);
1441              
1442             =cut
1443             sub pol_to_cart {
1444 0     0 1   my ($r, $th, $z) = @_;
1445 0           my $x = $r * cos($th);
1446 0           my $y = $r * sin($th);
1447 0           return($x, $y, $z);
1448             } # end subroutine pol_to_cart definition
1449             ########################################################################
1450              
1451             =head2 cart_to_pol
1452              
1453             Convert from polar to cartesian coordinates.
1454              
1455             my ($radius, $theta, $z) = cart_to_pol($x, $y, $z);
1456              
1457             =cut
1458             sub cart_to_pol {
1459 0     0 1   my ($x, $y, $z) = @_;
1460 0           my $r = sqrt($x**2 + $y**2);
1461 0           my $th = atan2($y, $x);
1462 0           return($r, $th, $z);
1463             } # end subroutine cart_to_pol definition
1464             ########################################################################
1465              
1466             =head2 print_line
1467              
1468             print_line(\@line, $message);
1469              
1470             =cut
1471             sub print_line {
1472 0     0 1   my ($line, $message) = @_;
1473 0 0         unless($message) {
1474 0           $message = "line:";
1475             }
1476             print join("\n\t", $message,
1477 0           map({join(" ", @$_)} @$line)), "\n";
  0            
1478             } # end subroutine print_line definition
1479             ########################################################################
1480              
1481             =head2 point_avg
1482              
1483             Averages the x and y coordinates of a list of points.
1484              
1485             my ($x, $y) = point_avg(@points);
1486              
1487             =cut
1488             sub point_avg {
1489 0     0 1   my(@points) = @_;
1490 0           my $i;
1491 0           my $num = scalar(@points);
1492 0           my $x_avg = 0;
1493 0           my $y_avg = 0;
1494             # print "num is $num\n";
1495 0           for($i = 0; $i < $num; $i++) {
1496             # print "point: $points[$i][0]\n";
1497 0           $x_avg += $points[$i][0];
1498 0           $y_avg += $points[$i][1];
1499             }
1500             # print "avgs: $x_avg $y_avg\n";
1501 0           $x_avg = $x_avg / $num;
1502 0           $y_avg = $y_avg / $num;
1503 0           return($x_avg, $y_avg);
1504             } # end subroutine point_avg definition
1505              
1506             =head2 arc_2pt
1507              
1508             Given a pair of endpoints and an angle (in radians), returns an arc with
1509             center, radius, and start/end angles.
1510              
1511             my %arc = arc_2pt(\@pts, $angle);
1512              
1513             =cut
1514             sub arc_2pt {
1515 0     0 1   my ($pts, $angle) = @_;
1516 0 0         my $dir = (($angle >= 0) ? 1 : -1);
1517 0           $angle = abs($angle);
1518 0           my %arc;
1519 0           my $chord = V(@{$pts->[1]}) - $pts->[0];
  0            
1520 0           my $clen = abs($chord);
1521             # warn "chord: $chord\n";
1522             # warn "chord length: $clen\n";
1523 0           my $eps = $angle /4;
1524 0 0         (cos($eps) == 0) and die "ack";
1525 0           my $blg = sin($eps)/cos($eps);
1526 0           my $s = $clen / 2 * $blg;
1527 0           my $r = (($clen/2)**2 + $s**2) / (2 * $s);
1528             ## warn "radius: $r\n";
1529             ## my $mid = $pts->[1] + $chord / 2;
1530 0           my $gamma = (pi - $angle) / 2;
1531             ## warn "gamma: $gamma\n";
1532 0           my $cang = $chord->Ang;
1533 0           my $phi = $cang + $dir * $gamma;
1534             ## warn "phi: $phi\n";
1535 0           my $conn = V(pol_to_cart($r, $phi));
1536 0           my $center = $pts->[0] + $conn;
1537             ## warn "center: $center\n";
1538 0           $arc{pt} = [@$center[0,1]];
1539 0           $arc{rad} = $r;
1540             $arc{angs} = [
1541 0           (- $conn)->Ang,
1542             ($pts->[1] - $center)->Ang
1543             ];
1544 0 0         ($dir > 0) or ($arc{angs} = [reverse(@{$arc{angs}})]);
  0            
1545 0           $arc{direction} = $dir;
1546 0           return(%arc);
1547             } # end subroutine arc_2pt definition
1548             ########################################################################
1549              
1550             1;