File Coverage

blib/lib/CAD/Calc.pm
Criterion Covered Total %
statement 36 487 7.3
branch 2 126 1.5
condition 0 20 0.0
subroutine 10 70 14.2
pod 61 61 100.0
total 109 764 14.2


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