File Coverage

blib/lib/Math/Geometry/Planar/Offset.pm
Criterion Covered Total %
statement 9 318 2.8
branch 0 106 0.0
condition 0 30 0.0
subroutine 3 7 42.8
pod 2 4 50.0
total 14 465 3.0


line stmt bran cond sub pod time code
1             package Math::Geometry::Planar::Offset;
2              
3             $VERSION = '1.03_01';
4              
5 1         82 use vars qw(
6             $VERSION
7             @ISA
8             @EXPORT
9             @EXPORT_OK
10             $precision
11             $debug
12 1     1   14277 );
  1         2  
13              
14 1     1   3 use strict;
  1         1  
  1         18  
15 1     1   3 use Carp;
  1         9  
  1         2108  
16              
17             $debug = 0;
18             $precision = 7;
19              
20             require Exporter;
21             @ISA = qw(Exporter);
22             @EXPORT = qw(OffsetPolygon);
23             @EXPORT_OK = qw(
24             $precision
25             $debug
26             );
27              
28             =pod
29              
30             =head1 NAME
31              
32             Math::Geometry::Planar::Offset - Calculate offset polygons
33              
34             =head1 SYNOPSIS
35              
36             use Math::Geometry::Planar::Offset;
37              
38              
39             =head1 AUTHOR
40              
41             Eric Wilhelm
42              
43             =head1 COPYRIGHT NOTICE
44              
45             Copyright (C) 2003-2005 Eric Wilhelm
46              
47             =head1 NO WARRANTY
48              
49             Absolutely, positively NO WARRANTY, neither express or implied, is
50             offered with this software. You use this software at your own risk. In
51             case of loss, neither Eric Wilhelm, nor anyone else, owes you anything
52             whatseover. You have been warned.
53              
54             Note that this includes NO GUARANTEE of MATHEMATICAL CORRECTNESS. If
55             you are going to use this code in a production environment, it is YOUR
56             RESPONSIBILITY to verify that the methods return the correct values.
57              
58             =head1 LICENSE
59              
60             You may use this software under one of the following licenses:
61              
62             (1) GNU General Public License
63             (found at http://www.gnu.org/copyleft/gpl.html)
64             (2) Artistic License
65             (found at http://www.perl.com/pub/language/misc/Artistic.html)
66              
67             =head1 CHANGES
68              
69             1.02
70             First Public Release
71              
72             1.03
73             Code cleanup
74              
75             1.03_01
76             Build file restructure.
77              
78             =head1 BUGS
79              
80             There are currently some problems with concurrent edge events on outward
81             (and maybe inward) offsets. Some significant changes need to be made.
82              
83             =cut
84              
85             =head1 METHODS
86              
87             These methods are actually defined in Math::Geometry::Planar, which uses
88             this module.
89              
90             =head2 offset_polygon
91              
92             Returns reference to an array of polygons representing the original polygon
93             offsetted by $distance
94              
95             $polygon->offset_polygon($distance);
96              
97             =cut
98              
99             require 5.005;
100              
101             my $delta = 10 ** (-$precision);
102              
103             my $offset_depth;
104             my $screen_height = 600;
105             my $flag;
106             my @bisectors;
107              
108             =head1 Functions
109              
110             Only OffsetPolygon is exported.
111              
112             =head2 pi
113              
114             Returns the constant pi
115              
116             =cut
117             sub pi {
118 0     0 1   atan2(1,1) * 4;
119             }
120              
121             =head2 OffsetPolygon
122              
123             Make offset polygon subroutine.
124              
125             Call with offset distance and ref to array of points for original
126             polygon polygon input must be pre-wrapped so point[n]=point[0]
127              
128             Will return a list of polygons (as refs.) The number of polygons in the
129             output depends on the shape of your input polygon. It may split into
130             several pieces during the offset.
131              
132             my (@results) = OffsetPolygon(\@points, $distance);
133             foreach my $polygon (@results) {
134             # do something with @$polygon
135             }
136              
137             =cut
138             sub OffsetPolygon {
139 0     0 1   my ($points, $offset, $canvas) = @_;
140 0           my %intersects;
141 0           my ($count,$n,$n2);
142 0           my ($time, $time_static);
143             # my $key1,$key2;
144 0           my @angles;
145 0           my @directions;
146 0           my @phi;
147 0           my @bis_dir;
148 0           my @bis_end;
149 0           my @bis_scale;
150 0           my @result1;
151 0           my @result2;
152 0           my @outlist;
153 0           my ($cut1,$cut2,$cut);
154 0           $#outlist = 0;
155 0           my @newpoints;
156             my @newpoints1;
157              
158             # set limit on number of recursions
159             # does perl have its own limit to the number of scopes generated?
160 0 0         if($offset_depth > 100){
161 0           print "reached recursion depth_limit\n";
162 0           return();
163             }
164             #ew: $offset_depth would have had to be reset by calling script
165             # (now is incremented and decremented on each side of recursive call)
166 0           my $npoints = @{$points};
  0            
167 0 0         $debug && print "points: ", $npoints, "\n";
168 0 0         $debug && print "number of points $npoints\n";
169             # print "points: @{$points->[0]}\n";
170             # exit;
171             # my $bis_length = 15;
172             # All polygons should be counter-clockwise !!!
173 0           find_direction($points,\@angles,\@directions);
174 0 0         $debug and print "starting recurse: ",$offset_depth -1,"\n";
175 0 0         $debug and print "offset: $offset\n";
176             # ew removed junk related to null points
177             # bail out if not at least a triangle
178 0 0         if($npoints < 3) {
179 0           carp("Need at least 3 non-colinear points to offset");
180 0           return;
181             }
182             # Now to make the bisectors
183 0           for($n = 0;$n < $npoints; $n++) {
184 0           $phi[$n] = ((pi) - $angles[$n]) / 2;
185 0           $bis_scale[$n] = abs(1 / sin($phi[$n]));
186 0           $bis_dir[$n] = $directions[$n] + $phi[$n];
187 0           $bis_end[$n][0] = $points->[$n][0] + $offset * $bis_scale[$n] * cos($bis_dir[$n]); # X-coordinate
188 0           $bis_end[$n][1] = $points->[$n][1] + $offset * $bis_scale[$n] * sin($bis_dir[$n]); # Y-coordinate
189             }
190              
191             # Draw bisectors
192 0 0         if ($canvas) {
193             # first delete existing bisectors
194 0           for($n = 0; $n<@bisectors;$n++) {
195 0           $canvas ->delete($bisectors[$n]);
196             }
197 0           @bisectors = ();
198             # then draw the bisector points from the vertices.
199 0           for($n = 0; $n < $npoints; $n++) {
200 0           my $x1 = $points->[$n][0];
201 0           my $y1 = $screen_height - $points->[$n][1];
202 0           my $x2 = $bis_end[$n][0];
203 0           my $y2 = $screen_height-$bis_end[$n][1];
204 0           $bisectors[$n] = $canvas->create('line', $x1,$y1,$x2,$y2, '-fill' => 'blue','-arrow'=>'last');
205             }
206             }
207            
208             # Need to find whether a bisector first intersects a bisector,
209             # or a bisector first intersects a "ghost" edge. Also, must know
210             # which one and at what time:
211 0           my ($Ax1,$Ay1,$Ax2,$Ay2,$Bx1,$By1,$Bx2,$By2);
212 0           my ($delAx,$delAy,$delBx,$delBy,$Am,$Ab,$Bm,$Bb);
213 0           my ($x_int,$y_int);
214 0           my ($first_join,$first_split,$split_seg);
215 0           my $first_event = "";
216 0           my $first_time = abs($offset)+1;
217 0           my $time_dir = $offset / abs($offset); # +1 or -1 to give direction
218 0           my $new_offset;
219 0           my $join_time = $first_time;
220 0           my $split_time = $first_time;
221            
222             # first find intersections of adjacent bisectors (if any)
223 0           for($n = 0;$n <$npoints;$n++) {
224 0           $Ax1 = $points->[$n][0];
225 0           $Ay1 = $points->[$n][1];
226 0           $Ax2 = $bis_end[$n][0];
227 0           $Ay2 = $bis_end[$n][1];
228 0           $delAx = $Ax2 - $Ax1;
229 0           $delAy = $Ay2 - $Ay1;
230             # elw: what amateur wrote this!
231 0 0         if($delAx) {
232 0           $Am = $delAy / $delAx; $Ab = $Ay1 - $Ax1 * $Am;
  0            
233             }
234             else {
235 0           $Am = "inf";
236             }
237             # Check against the next bisector:
238 0           $Bx1 = $points->[$n+1-$npoints][0];
239 0           $By1 = $points->[$n+1-$npoints][1];
240 0           $Bx2 = $bis_end[$n+1-$npoints][0];
241 0           $By2 = $bis_end[$n+1-$npoints][1];
242 0           $delBx = $Bx2 - $Bx1;
243 0           $delBy = $By2 - $By1;
244             # elw: maybe you are getting closer to zen now:)
245 0 0         if($delBx) {
246 0           $Bm = $delBy / $delBx;$Bb = $By1 - $Bx1 * $Bm;
  0            
247             }
248             else{
249 0           $Bm = "inf";
250             }
251 0 0 0       if( ($Am == $Bm) || ($Am eq $Bm) ) {
252 0           next;
253             }
254             # Calculate determinants to find if intersection is within the bisector segment.
255 0 0         if (! do_cross($Ax1,$Ay1,$Ax2,$Ay2,$Bx1,$By1,$Bx2,$By2) ){
256 0           next;
257             }
258             # if we have an intersection of the skeleton and only a triangle,
259             # then it has collapsed to a point:
260 0 0         if($npoints < 4) {
261 0 0         $debug && print "collapsed\n";
262 0           return();
263             }
264 0 0         if($Am eq "inf") { # Slope of first line is infinite, so use Ax1.
    0          
265 0           $x_int = $Ax1; # Will always be on vertical line.
266 0           $y_int = $Bm * $x_int + $Bb;
267             }
268             elsif($Bm eq "inf") { # Slope of second line is infinite, so use Bx1.
269 0           $x_int = $Bx1;$y_int = $Am * $x_int + $Ab;
  0            
270             }
271             else {
272 0           $x_int = ($Bb - $Ab) / ($Am - $Bm);$y_int = $Am *$x_int + $Ab;
  0            
273             }
274             # Now, if the lines are not parallel, and the intersection
275             # happens on the line segments, we are here with
276             # the x and y coordinates of the intersection of the two lines.
277             ## $debug and printf ("intersection: %6.0f,%6.0f\n",$x_int, $y_int);
278              
279             # Let's find the time of intersect.
280             # distance formula with adjustment for speed
281 0           $time = sqrt( ($x_int - $points->[$n][0])**2 + ($y_int - $points->[$n][1])**2 ) / $bis_scale[$n];
282 0 0         if( (abs($time) < $first_time) )
283             # && ( $time / abs($time)== $offset / abs($offset) ) )
284             {
285             # note that none of the times loaded here have a sign
286 0           $first_time = abs($time);
287 0           $join_time = $first_time;
288 0           $first_join=$n;
289 0           $first_event="join";
290             }
291              
292              
293             } # end for n (for each bisector vs next neighbor)
294             # Time is smallest relevant offset distance before first join.
295             # first join is address of point to be joined.
296             # first event is "join" if controlling (could be over-ridden by split)
297             # If this is the controlling case, create the joined polygon at that offset time
298             # and make a recursive call.
299             #
300             #
301             # Now to check for intersections of bisectors with edges
302             # Have to check against all segments except adjacent ones.
303             # Nothing will happen if it is a triangle.
304 0 0         if($npoints > 3) {
305 0           for($n = 0; $n < $npoints; $n++) {
306 0           $Ax1 = $points->[$n][0]; # Get bisector endpoints
307 0           $Ay1 = $points->[$n][1];
308 0           $Ax2 = $bis_end[$n][0];
309 0           $Ay2 = $bis_end[$n][1];
310 0 0         $debug && print "starting at $Ax1 $Ay1\n";
311 0 0         $debug && print "bisector toward $Ax2 $Ay2\n";
312             # I need it to be a ray, so I am just making a really long segment here.
313 0           $delAx = 1000* $offset* ($Ax2 - $Ax1);
314 0           $delAy = 1000* $offset* ($Ay2 - $Ay1);
315 0           $Ax2 = $Ax1+ $delAx;
316 0           $Ay2 = $Ay1 + $delAy;
317 0 0         if($delAx) {$Am = $delAy / $delAx; $Ab = $Ay1 - $Ax1 * $Am;}else{$Am = "inf";};
  0            
  0            
  0            
318             # this loop has to compare to all of the polygon sides except the adjacent ones
319 0           for($n2 = 0; $n2 < $npoints; $n2++) {
320 0           $time = abs($offset) +1;
321             # Can't split adjacent segments
322 0 0 0       if( ($n2 == $n) || ($n2 == $n-1) || ( ($n==0)&&($n2 == $npoints-1) ) ){
      0        
      0        
323 0           next;
324             }
325 0 0         $debug && print "inner loop at n2 = $n2\n";
326 0           $Bx1 = $points->[$n2][0]; # Get edge endpoints
327 0           $By1 = $points->[$n2][1];
328 0           $Bx2 = $points->[$n2 + 1 - $npoints][0];
329 0           $By2 = $points->[$n2+1-$npoints][1];
330 0           $delBx = $Bx2 - $Bx1;
331 0           $delBy = $By2 - $By1;
332 0 0         if($delBx) {
333 0           $Bm = $delBy / $delBx;$Bb = $By1 - $Bx1 * $Bm;
  0            
334             }
335             else {
336 0           $Bm = "inf";
337             }
338 0 0 0       if( ($Am == $Bm) || ($Am eq $Bm) ){
339 0           next;
340             }
341             # Note that I am not using the dot product here (it
342             # shows up below. I need to know where the infinite ray
343             # of the bisector crosses the infinite line of the
344             # polygon edge (but this may cause problems as well)
345 0 0         if($Am eq "inf") {
    0          
346             # Slope of first line is infinite, so use Ax1.
347 0           $x_int = $Ax1;$y_int = $Bm * $x_int + $Bb;
  0            
348             }
349             elsif($Bm eq "inf") {
350             # Slope of second line is infinite, so use Bx1.
351 0           $x_int = $Bx1;$y_int = $Am * $x_int + $Ab;
  0            
352             }
353             else {
354 0           $x_int = ($Bb - $Ab) / ($Am - $Bm);$y_int = $Am *$x_int + $Ab;
  0            
355             }
356             # now make sure that the intersection happened on the right side
357             # (point the ray in the offset direction)
358 0           my $delxto_int = $x_int - $Ax1;
359 0           my $delyto_int = $y_int - $Ay1;
360 0           my $next = $n2+1;
361 0 0         if ($next == $npoints) {
362 0           $next = 0;
363             }
364             # This method is not handling the split which happens to edges terminating
365             # at a concave point. This event is a third case because the time calculated
366             # below will be smaller for the extension of the edge before the concavity than
367             # for the edge after the concavity, which is the one to properly split.
368              
369              
370             # I have suspicions about the accuracy here:
371             # Original intent was to make sure that the ray was properly treated
372             # now handled by using a really long segment (1000*offset) for the ray.
373             # if( $bis_dir[$n] == (atan2($delyto_int,$delxto_int)))
374             {
375             # direction is good, calculate distance
376             # note that the int_scale should be naturally signed
377             # to prevent strangeness on internal offsets.
378             # dvdp:
379             # $int_scale = 1 / (sin( $directions[$n2] - $bis_dir[$n])) ;
380             # This could lead to a devision by 0 so we calculate the reverse and check first
381 0           my $int_scale = (sin( $directions[$n2] - $bis_dir[$n])) ;
  0            
382 0 0         $debug && print "bisector scale: $bis_scale[$n]\n";
383 0 0         $debug && print "intersection scale: 1 / $int_scale\n";
384 0 0         if ($int_scale) {
385 0           $int_scale = 1 / $int_scale;
386 0 0         if($bis_scale[$n]+$int_scale) {
387 0           $time_static = sqrt( ($x_int - $points->[$n][0])**2 +
388             ($y_int - $points->[$n][1])**2 ) / ($bis_scale[$n]+$int_scale);
389             }
390             else {
391             # intersection happens only at infinite offset time
392             # die "PGON offset dying at 281 with $bis_scale[$n] and $int_scale\n";
393             }
394             }
395             else {
396             # 1 / $int_scale is arbitrary large so division reduces to 0
397 0           $time_static = 0;
398             }
399             # once you have calculated the time, you need to see if the
400             # intersected segment is still in that place at that time to be struck by the ray
401             # Does time_dir belong here? (yes for test case A)
402 0           $Bx1 = $points->[$n2][0] + $time_static * $bis_scale[$n2] * cos($bis_dir[$n2]);
403 0           $By1 = $points->[$n2][1] + $time_static * $bis_scale[$n2] * sin($bis_dir[$n2]);
404 0           $Bx2 = $points->[$n2 + 1 - $npoints][0] + $time_static * $bis_scale[$next] * cos($bis_dir[$next]);
405 0           $By2 = $points->[$n2 + 1 - $npoints][1] + $time_static * $bis_scale[$next] * sin($bis_dir[$next]);
406 0 0         if(! do_cross($Ax1,$Ay1,$Ax2,$Ay2,$Bx1,$By1,$Bx2,$By2) ) {
407 0           next;
408             }
409 0           $time = $time_static;
410             # make sure the case controls and is in the right direction. (required for case A)
411             # dvdp: replaced division by abs($time) by a multiplication to overcome
412             # potential divide by 0
413 0 0 0       if( (abs($time)<$first_time) and ( $time == $time_dir * abs($time) ) ) {
414             # note that none of the times loaded here have a sign
415 0           $first_time = abs($time);
416 0           $split_time = $first_time;
417 0           $first_split=$n;
418 0           $split_seg = $n2;
419 0           $first_event="split";
420             }
421             }
422             } # end for n2
423             } # end for n (each bisector verses each edge)
424             } # End if not triangle
425            
426             # Time is the smallest relavent split time (unless join controls)
427             # first_split is address of point to be split.
428             # first_event is "split" if controlling (could be controlled by join)(or nothing)
429             # If this is the controlling case, create the split polygons at that offset time
430             # and make a recursive call.
431            
432 0 0         if($first_time <= abs($offset)) {
433 0 0         if($debug) {
434 0 0         if($first_event eq "join") {
435 0           printf("join vertex %d at time %6.2f\n",$first_join,$first_time);
436             }
437 0 0         if($first_event eq "split") {
438 0           printf(
439             "split segment %d with vertex %d at time %6.2f\n",
440             $split_seg,$first_split,$first_time
441             );
442             }
443 0           print "\ttime: $first_time\n";
444             }
445             # get a list of points for the offset polygon at first_time
446              
447 0 0         if($first_event eq "join") {
    0          
448             # How are coincident events handled?
449             # remove the offending point and call yourself with time adjusted.
450 0           @newpoints = ();
451 0           for($n = 0; $n<$npoints; $n++) {
452 0           $newpoints[$n][0] = $points->[$n][0] + $first_time *
453             $time_dir* $bis_scale[$n] * cos($bis_dir[$n]);
454 0           $newpoints[$n][1] = $points->[$n][1] + $first_time *
455             $time_dir* $bis_scale[$n] * sin($bis_dir[$n]);
456             }
457             # keep the one with the smaller scale.
458 0 0         if($bis_scale[$first_join]<$bis_scale[$first_join+1]) {
459 0           splice(@newpoints, $first_join + 1, 1);
460             }
461             else {
462 0           splice(@newpoints, $first_join , 1);
463             }
464 0           $npoints--;
465 0           $new_offset = $offset - $first_time * $time_dir; # put a direction on it.
466 0           $offset_depth++;
467 0           @outlist = OffsetPolygon(\@newpoints,$new_offset) ;
468 0           $offset_depth--;
469 0           return(@outlist);
470             }
471             elsif ($first_event eq "split") {
472             # make two polygons and call yourself with time adjusted.
473 0           @newpoints = ();
474 0           my @split_check;
475             my @split_check1;
476 0           for($n = 0; $n<$npoints; $n++) {
477 0           $newpoints[$n][0] = $points->[$n][0] + $first_time *
478             $time_dir * $bis_scale[$n] * cos($bis_dir[$n]);
479 0           $newpoints[$n][1] = $points->[$n][1] + $first_time *
480             $time_dir * $bis_scale[$n] * sin($bis_dir[$n]);
481 0           $split_check[$n] = $n;
482             }
483 0           $new_offset = $offset - $first_time * $time_dir;
484 0           @newpoints1 = ();
485 0           @newpoints1 = @newpoints;
486 0           @split_check1 = ();
487 0           @split_check1 = @split_check;
488             # have first_split and split_seg, must find a way to wrap around.
489 0           my $cut_startA;
490             my $cut_startB;
491 0           my $cutB;
492 0 0         if($split_seg < $first_split) {
493 0           $cut1 = $#newpoints - $first_split;
494 0           $cut2 = $split_seg+1;
495 0           $cut_startA=$first_split+1;
496 0           $cut_startB = $split_seg+1;
497 0           $cutB = $first_split - $split_seg -1;
498             }
499             else {
500 0           $cut1 = $#newpoints - $split_seg;
501 0           $cut2 = $first_split;
502 0           $cut_startA = $split_seg +1;
503 0           $cut_startB = $first_split + 1;
504 0           $cutB = $split_seg - $first_split;
505             }
506 0           splice(@newpoints,$cut_startA,$cut1);
507 0           splice(@newpoints,0,$cut2);
508 0           splice(@split_check,$cut_startA,$cut1);
509 0           splice(@split_check,0,$cut2);
510 0           splice(@newpoints1,$cut_startB,$cutB);
511 0           splice(@split_check1,$cut_startB,$cutB);
512 0           my $num = @newpoints;
513 0           my $num1 = @newpoints1;
514              
515 0 0         if($debug) {
516 0           print "split was:\n@split_check\n@split_check1\n";
517 0           print "first splitted:\n";
518 0           for($n = 0; $n<$num;$n++) {
519 0           printf ("point: %d (%6.2f,%6.2f)\n",$n,@{$newpoints[$n]});
  0            
520             }
521 0           print "second splitted:\n";
522 0           for($n = 0; $n < $num1;$n++) {
523 0           printf ("point: %d (%6.2f,%6.2f)\n",$n,@{$newpoints1[$n]});
  0            
524             }
525             }
526 0           $#result1 = 0;
527 0           shift(@result1);
528 0           $#result2 = 0;
529 0           shift(@result2);
530 0 0         if($num>2) {
531 0           $offset_depth++;
532 0           @result1 = OffsetPolygon(\@newpoints,$new_offset);
533 0           $offset_depth--;
534             }
535             else {
536 0 0         $debug and print "too few on first\n";
537             }
538 0 0         if($num1>2) {
539 0           $offset_depth++;
540 0           @result2 = OffsetPolygon(\@newpoints1,$new_offset);
541 0           $offset_depth--;
542             }
543             else {
544 0 0         $debug and print "too few on second\n";
545             }
546 0           return(@result1, @result2); # This concatenates the list.
547             } # end elsif split
548             } # end if time <= offset
549             else {
550             # No splits or joins needed, so offset the thing.
551             # Bisector endpoints should be available.
552 0           @newpoints = ();
553 0           for($n = 0; $n<$npoints; $n++) {
554 0           $newpoints[$n][0] = $points->[$n][0] + $offset * $bis_scale[$n] * cos($bis_dir[$n]);
555 0           $newpoints[$n][1] = $points->[$n][1] + $offset * $bis_scale[$n] * sin($bis_dir[$n]);
556             }
557 0           return( \@newpoints);
558             }
559             } # End offset polygon subroutine definition
560             #########################################################################
561             # Find direction subroutine.
562             # Called with (,, ).
563             # Returns total change in angle (as radians (everything is as radians)).
564             # Second two array refs are optional.
565             # Forcing counter-clockwise currently not done here, but maybe should.
566             sub find_direction {
567 0     0 0   my($coords,$del_theta,$thetas) = @_;
568 0           my $sum_theta=0;
569 0           my $sum_delta_theta = 0;
570 0           @{$del_theta} = (); # Clear arrays referenced for in-place modification.
  0            
571 0           @{$thetas} = ();
  0            
572 0           my $n = 0;
573 0           for(;;) {
574 0 0         last if ($n == @{$coords});
  0            
575             # dvdp: take advantage of negative indexing in Perl
576 0           my $n2 = $n - @{$coords} + 1;
  0            
577 0           my $n3 = $n - 1;
578 0           my $Ax1 = ${$coords}[$n][0]; # Line leaving point
  0            
579 0           my $Ay1 = ${$coords}[$n][1]; # this is the one belonging to the point
  0            
580 0           my $Ax2 = ${$coords}[$n2][0];
  0            
581 0           my $Ay2 = ${$coords}[$n2][1];
  0            
582 0           my $delAx = $Ax2 - $Ax1;
583 0           my $delAy = $Ay2 - $Ay1;
584 0           my $Bx1 = ${$coords}[$n3][0]; # Line coming to point n
  0            
585 0           my $By1 = ${$coords}[$n3][1];
  0            
586 0           my $Bx2 = ${$coords}[$n][0];
  0            
587 0           my $By2 = ${$coords}[$n][1];
  0            
588 0           my $delBx = $Bx2 - $Bx1;
589 0           my $delBy = $By2 - $By1;
590             # dvdp remove coinciding points
591 0 0 0       if ( ((abs($delAx) < $delta) && (abs($delAy) < $delta)) ||
      0        
      0        
592             ((abs($delBx) < $delta) && (abs($delBy) < $delta)) ) {
593 0           splice @{$coords},$n,1;
  0            
594 0           next;
595             }
596 0           my $theta_A = atan2($delAy,$delAx); # Angle of leaving line
597 0           my $theta_B = atan2($delBy,$delBx); # Angle of coming line
598 0           my $theta_AB = $theta_A - $theta_B;
599 0 0         if ($theta_AB < -(pi)) {
    0          
600 0           $theta_AB += 2 * (pi);
601             }
602             elsif ($theta_AB > (pi)) {
603 0           $theta_AB -= 2 * (pi);
604             }
605 0 0         if( (abs($theta_AB - (pi)) < $delta) ) {
606 0           splice @{$coords},$n,1;
  0            
607 0 0         $n and $n--; # need to recalc thetas if spike removed !
608 0           next;
609             }
610 0           ${$thetas}[$n] = $theta_A;
  0            
611 0           ${$del_theta}[$n] = $theta_AB;
  0            
612 0           $n++
613             }
614             } # End find_direction sub
615             #########################################################################
616             # Define do_cross subroutine.
617             # Determines if a pair of line segments have an intersection.
618             sub do_cross {
619 0     0 0   my ($Ax1,$Ay1,$Ax2,$Ay2,$Bx1,$By1,$Bx2,$By2) = @_;
620             # Calculate four relevant minor determinants
621 0           my $det_123=($Ax2 - $Ax1)*($By1 - $Ay1) - ($Bx1 - $Ax1)*($Ay2 - $Ay1);
622 0           my $det_124=($Ax2 - $Ax1)*($By2 - $Ay1) - ($Bx2 - $Ax1)*($Ay2 - $Ay1);
623 0           my $det_341=($Bx1 - $Ax1)*($By2 - $Ay1) - ($Bx2 - $Ax1)*($By1 - $Ay1);
624 0           my $det_342 =$det_123-$det_124+$det_341;
625 0 0 0       if( ($det_123*$det_124 > 0) || ($det_341*$det_342 > 0) ) {
626 0           return(0); # segments only intersect if the above two products are both negative
627             }
628             else {
629 0           return(1);
630             }
631             } # End do_cross subroutine definition
632             ########################################################################
633              
634             1;