File Coverage

blib/lib/CAD/Drawing/Calculate.pm
Criterion Covered Total %
statement 24 165 14.5
branch 0 36 0.0
condition n/a
subroutine 8 21 38.1
pod 11 11 100.0
total 43 233 18.4


line stmt bran cond sub pod time code
1             package CAD::Drawing::Calculate;
2             our $VERSION = '0.12';
3              
4             # use CAD::Drawing;
5 3     3   19 use CAD::Drawing::Defined;
  3         7  
  3         653  
6 3     3   1975 use CAD::Drawing::Calculate::Finite;
  3         7  
  3         150  
7              
8             our @ISA = qw(
9             CAD::Drawing::Calculate::Finite
10             );
11              
12 3         29 use CAD::Calc qw(
13             dist2d
14             line_intersection
15 3     3   21 );
  3         6  
16              
17 3     3   1587 use Math::Vec qw(NewVec);
  3         6  
  3         165  
18              
19 3         136 use vars qw(
20             @orthfunc
21 3     3   17 );
  3         6  
22              
23 3     3   15 use warnings;
  3         6  
  3         94  
24 3     3   54 use strict;
  3         3  
  3         94  
25 3     3   16 use Carp;
  3         7  
  3         7329  
26             ########################################################################
27             =pod
28              
29             =head1 NAME
30              
31             CAD::Drawing::Calculate - Calculations for CAD::Drawing
32              
33             =head1 DESCRIPTION
34              
35             This module provides calculation functions for the CAD::Drawing family
36             of modules.
37              
38             =head1 AUTHOR
39              
40             Eric L. Wilhelm
41              
42             http://scratchcomputing.com
43              
44             =head1 COPYRIGHT
45              
46             This module is copyright (C) 2004-2006 by Eric L. Wilhelm. Portions
47             copyright (C) 2003 by Eric L. Wilhelm and A. Zahner Co.
48              
49             =head1 LICENSE
50              
51             This module is distributed under the same terms as Perl. See the Perl
52             source package for details.
53              
54             You may use this software under one of the following licenses:
55              
56             (1) GNU General Public License
57             (found at http://www.gnu.org/copyleft/gpl.html)
58             (2) Artistic License
59             (found at http://www.perl.com/pub/language/misc/Artistic.html)
60              
61             =head1 NO WARRANTY
62              
63             This software is distributed with ABSOLUTELY NO WARRANTY. The author,
64             his former employer, and any other contributors will in no way be held
65             liable for any loss or damages resulting from its use.
66              
67             =head1 Modifications
68              
69             The source code of this module is made freely available and
70             distributable under the GPL or Artistic License. Modifications to and
71             use of this software must adhere to one of these licenses. Changes to
72             the code should be noted as such and this notification (as well as the
73             above copyright information) must remain intact on all copies of the
74             code.
75              
76             Additionally, while the author is actively developing this code,
77             notification of any intended changes or extensions would be most helpful
78             in avoiding repeated work for all parties involved. Please contact the
79             author with any such development plans.
80              
81             =head1 SEE ALSO
82              
83             CAD::Drawing
84             CAD::Calc
85             Math::Vec
86              
87             =cut
88             ########################################################################
89              
90             =head1 Methods
91              
92             =cut
93             ########################################################################
94              
95             =head1 Extents Calculations
96              
97             =head2 OrthExtents
98              
99             Calculates the extents of a group of objects (selected according to select_addr()) and returns an array: [xmin,xmax],[ymin,ymax].
100              
101             @extents = $drw->OrthExtents(\%opts);
102              
103             =cut
104             sub OrthExtents {
105 0     0 1   my $self = shift;
106 0           my($opts) = @_;
107 0           my $retref = $self->select_addr($opts);
108 0           my @worklist = @{$retref};
  0            
109 0           my(@xvals, @yvals);
110 0           foreach my $addr (@worklist) {
111 0           my ($xdata, $ydata) = $self->EntOrthExtents($addr);
112 0           push(@xvals, @$xdata);
113 0           push(@yvals, @$ydata);
114             }
115 0           @xvals = sort({$a<=>$b} @xvals);
  0            
116 0           @yvals = sort({$a<=>$b} @yvals);
  0            
117 0           return([ $xvals[0], $xvals[-1] ], [$yvals[0], $yvals[-1] ] );
118             } # end subroutine OrthExtents definition
119             ########################################################################
120              
121             =head2 getExtentsRec
122              
123             Alias to OrthExtents() which returns a polyline-form array of points
124             (counter clockwise from lower-left) describing a rectangle.
125              
126             @rec = $drw->getExtentsRec(\%opts);
127              
128             =cut
129             sub getExtentsRec {
130 0     0 1   my $self = shift;
131 0           my($opts) = @_;
132 0           my ($x, $y) = $self->OrthExtents($opts);
133             return(
134 0           [$x->[0], $y->[0]],
135             [$x->[1], $y->[0]],
136             [$x->[1], $y->[1]],
137             [$x->[0], $y->[1]],
138             );
139             } # end subroutine getExtentsRec definition
140             ########################################################################
141              
142             =head2 EntOrthExtents
143              
144             Gets the orthographic extents of the object at $addr. Returns
145             [\@xpts,\@y_pts] (leaving you to sort through them and find which
146             is min or max.)
147              
148             @extents = $drw->EntOrthExtents($addr);
149              
150             =cut
151             sub EntOrthExtents {
152 0     0 1   my $self = shift;
153 0           my ($addr) = @_;
154 0           my $obj = $self->getobj($addr);
155             # FIXME: this will only get the point items
156 0           my $stg = $call_syntax{$addr->{type}}[1];
157 0           my ($xpts, $ypts) = $orthfunc[0]{$stg}->($obj->{$stg});
158             } # end subroutine EntOrthExtents definition
159             ########################################################################
160              
161             =head2 @orthfunc
162              
163             List of hash references containing code references to reduce
164             duplication and facilitate natural flow (rather than ifififif
165             statements.)
166              
167             =cut
168              
169             @orthfunc = (
170             { # stage one hash ref
171             "pt" => sub {
172             my($pt) = @_;
173             return([$pt->[0]], [$pt->[1]]);
174             }, # end subroutine $orthfunc[0]{pt} definition
175             "pts" => sub {
176             my($pts) = @_;
177             my @vals = ([], []);
178             for(my $i = 0; $i < @$pts; $i++) {
179             foreach my $c (0,1) {
180             push(@{$vals[$c]}, $pts->[$i][$c]);
181             }
182             }
183             return(@vals);
184             }, # end subroutine $orthfunc[0]{pts} definition
185             }, # end stage one hash ref
186             { # stage two hash ref
187             # FIXME: here we put the fun stuff about rad and text
188             }, # end stage two hash ref
189             ); # end @orthfunc bundle
190             ########################################################################
191              
192             =head1 Planar Geometry Methods
193              
194             =head2 offset
195              
196             Intended as any-object offset function (not easy).
197              
198             $dist is negative to offset outward
199              
200             $drw->offset($object, $dist);
201              
202             =cut
203             sub offset {
204 0     0 1   carp("no offset function yet");
205             } # end subroutine offset definition
206             ########################################################################
207              
208             =head2 divide
209              
210             $drw->divide();
211              
212             =cut
213             sub divide {
214 0     0 1   carp("no divide function yet");
215             } # end subroutine divide definition
216             ########################################################################
217              
218             =head2 area
219              
220             $drw->area($addr);
221              
222             =cut
223             sub area {
224 0     0 1   my $self = shift;
225 0           my $addr = shift;
226 0 0         ($addr->{type} eq "plines") or croak "only calc area for plines";
227 0           my @pgon = $self->Get("pts", $addr);
228 0           my $tw_area = 0;
229 0           my $x = 0;
230 0           my $y = 1;
231 0           for(my $i = 0; $i < @pgon; $i++) {
232 0           $tw_area += ($pgon[$i][$y] + $pgon[$i-1][$y]) *
233             ($pgon[$i][$x] - $pgon[$i-1][$x]);
234             }
235 0           return( abs($tw_area / 2) );
236             } # end subroutine area definition
237             ########################################################################
238              
239             =head1 Line Manipulations
240              
241             =head2 pline_to_ray
242              
243             Transforms a polyline with a nubbin into a ray (line with direction.)
244              
245             $line_addr = $drw->pline_to_ray($pline_addr);
246              
247             =cut
248             sub pline_to_ray {
249 0     0 1   my $self = shift;
250 0           my ($pl_addr) = @_;
251 0 0         ($pl_addr->{type} eq "plines") || carp("not a polyline");
252 0           my @pts = $self->Get("pts", $pl_addr);
253 0 0         (@pts == 3) || croak("not 3 points to polyline");
254             # print "checking: ", dist2d($pts[0], $pts[1]) ,
255             # "<=>",
256             # dist2d($pts[1], $pts[2]),
257             # "\n";
258 0           my $dir = dist2d($pts[0], $pts[1]) <=> dist2d($pts[1], $pts[2]);
259 0 0         ($dir > 0) || (@pts = reverse(@pts));
260 0           my $obj = $self->getobj($pl_addr);
261 0           my %lineopts = (
262             "layer" => $pl_addr->{layer},
263             "color" => $obj->{color},
264             "linetype" => $obj->{linetype},
265             );
266 0           return($self->addline([@pts[0,1]], \%lineopts) );
267             } # end subroutine pline_to_ray definition
268             ########################################################################
269              
270             =head2 trim_both
271              
272             Trims two lines to their intersection.
273              
274             $drw->trim_both($addr1, $addr2, $tol, \@keep_ends);
275              
276             See CAD::Calc::line_intersection()
277              
278             =cut
279             sub trim_both {
280 0     0 1   my $self = shift;
281 0           my @items = (shift,shift);
282 0           my $tol = shift;
283 0           my $ends = shift;
284 0           my @keep_ends;
285 0 0         if($ends) {
286 0 0         (ref($ends) eq "ARRAY") or croak(
287             'CAD::Drawing::Calculate::trim_both() ' .
288             '\@keep_ends arg must be array'
289             );
290 0           @keep_ends = @$ends;
291             }
292 0           my @lines;
293             my @vecs;
294 0           my @mids;
295 0           foreach my $item (@items) {
296 0 0         $item or die "no item\n";
297 0           my @pts = $self->Get("pts", $item);
298             # @pts or die "problem with $item\n";
299             # print "points: @{$pts[0]}, @{$pts[1]}\n";
300 0           my $vec = NewVec(NewVec(@{$pts[1]})->Minus($pts[0]));
  0            
301 0           my $mid = [NewVec($vec->ScalarMult(0.5))->Plus($pts[0])];
302 0           push(@mids, $mid);
303 0           push(@vecs, $vec);
304 0           push(@lines, [@pts]);
305             }
306 0           my @int = line_intersection(@lines, $tol);
307             ## defined($int[0]) or print("no int\n");
308 0 0         defined($int[0]) or return();
309             ## defined($int[1]) or print("paralell (no)\n");
310 0 0         defined($int[1]) or return(); #parallel
311             # print "making vec from @int\n";
312 0           my $pt = NewVec(@int);
313             # print "got point: @$pt\n";
314 0           foreach my $i (0,1) {
315 0           my $end;
316 0 0         if(@keep_ends) {
317 0           $end = ! $keep_ends[$i];
318             }
319             else {
320 0           my $dot = $vecs[$i]->Dot([$pt->Minus($mids[$i])]);
321             # print "dot product: $dot\n";
322             # if the dot product is positive,
323             # intersection is in front of midpoint.
324 0           $end = ($dot > 0);
325             }
326             # print "end is $end\n";
327 0           $lines[$i][$end] = $pt;
328 0           $self->Set({pts => $lines[$i]}, $items[$i]);
329             }
330              
331 0           return($pt);
332              
333            
334              
335             } # end subroutine trim_both definition
336             ########################################################################
337              
338             =head1 Coordinate Transforms
339              
340             Switch between coordinate system representations.
341              
342             =head2 to_ocs
343              
344             Change the objects coordinates into the object coordinate system.
345              
346             Both of these are relatively quick. A simple test shows that one point
347             can be taken back and forth at about 2KHz, so don't be afraid to use
348             them.
349              
350             $drw->to_ocs($addr);
351              
352             =cut
353             sub to_ocs {
354 0     0 1   my $self = shift;
355 0           my ($addr) = @_;
356 0           my $obj = $self->getobj($addr);
357 0 0         if(my $n = $obj->{normal}) {
358             # FIXME: if direction is Z, kill the flags
359             # print "normal is @$n\n";
360 0 0         if($ac_storage_method{$addr->{type}} eq "ocs") {
361             # need to translate
362 0           my @ocs = _ocs_axes(@{$n});
  0            
363             # print "ocs is: ", join("\n", map({join(",", @{$_})} @ocs)), "\n";
364 0 0         if($obj->{pts}) {
365 0           foreach my $pt (@{$obj->{pts}}) {
  0            
366 0           @{$pt} = map({$ocs[$_]->Comp($pt)} 0..2);
  0            
  0            
367             }
368             }
369             else {
370             # safe to assume it is a point?
371 0           @{$obj->{pt}} = map({$ocs[$_]->Comp($obj->{pt})} 0..2);
  0            
  0            
372             }
373             } # end if stored in ocs
374 0           $obj->{extrusion} = $n;
375 0           delete($obj->{normal});
376             }
377             else { # object is in xy coords with normal in [0,0,1] direction
378 0           return();
379             }
380              
381             } # end subroutine to_ocs definition
382             ########################################################################
383              
384             =head2 to_wcs
385              
386             Change the object's coordinates into the world coordinate system.
387              
388             $drw->to_wcs($addr);
389              
390             =cut
391             sub to_wcs {
392 0     0 1   my $self = shift;
393 0           my ($addr) = @_;
394 0           my $obj = $self->getobj($addr);
395 0 0         if(my $n = $obj->{extrusion}) {
396             # FIXME: if direction is Z, kill the flags
397              
398             # also have to check if this object is stored as WCS or OCS?
399 0 0         if($ac_storage_method{$addr->{type}} eq "ocs") {
400             # need to translate
401 0           my @ocs = _ocs_axes(@{$n});
  0            
402 0           my @tcs = _wcs_axes(@ocs);
403 0 0         if($obj->{pts}) {
404 0           foreach my $pt (@{$obj->{pts}}) {
  0            
405             # warn("pt was: ", join(",", @{$pt}), "\n");
406 0           @{$pt} = map({$tcs[$_]->Comp($pt)} 0..2);
  0            
  0            
407             # warn("pts being transformed for $addr->{type} ",
408             # join(",", @{$pt}), "\n");
409             }
410             }
411             else {
412             # safe to assume it is a point?
413             # warn("pt was: ", join(",", @{$obj->{pt}}), "\n");
414 0           @{$obj->{pt}} = map({$tcs[$_]->Comp($obj->{pt})} 0..2);
  0            
  0            
415             # warn("pt being transformed for $addr->{type} ",
416             # join(",", @{$obj->{pt}}), "\n");
417             }
418             } # end if stored in ocs
419 0           $obj->{normal} = $n;
420 0           delete($obj->{extrusion});
421             }
422             else { # object is in xy coords with normal in [0,0,1] direction
423 0           return();
424             }
425             } # end subroutine to_wcs definition
426             ########################################################################
427              
428             =head2 flatten
429              
430             Puts the object in the wcs, zeros all z-coordinates and deletes the
431             normal vector. Note that this is fine for projecting polylines and
432             lines, but may not be what you want if you are trying to make a circle
433             into an ellipse (at least not yet.)
434              
435             $drw->flatten($addr);
436              
437             =cut
438             sub flatten {
439 0     0 1   my $self = shift;
440 0           my ($addr) = @_;
441 0           $self->to_wcs($addr);
442 0           my $obj = $self->getobj($addr);
443 0 0         if($obj->{pts}) {
444 0           foreach my $pt (@{$obj->{pts}}) {
  0            
445 0           $pt->[2] = 0;
446             }
447             }
448             else {
449 0           $obj->{pt}[2] = 0;
450             }
451 0           delete($obj->{normal});
452             } # end subroutine flatten definition
453             ########################################################################
454              
455             =head1 Functions
456              
457             Non-OO internal-use functions.
458              
459             =head2 _ocs_axes
460              
461             Returns the x,y, and z axes for the ocs described by @normal. These
462             will have arbitrary lengths.
463              
464             @local_axes = _ocs_axes(@normal);
465              
466             =cut
467             sub _ocs_axes {
468 0     0     my $z = NewVec(@_);
469 0           my $x = NewVec(NewVec(0,0,1)->Cross($z));
470 0 0         ($x->Length()) || ($x = NewVec($z->[2],0,0));
471 0           my $y = NewVec($z->Cross($x));
472 0           return($x,$y,$z);
473             } # end subroutine _ocs_axes definition
474             ########################################################################
475              
476             =head2 _wcs_axes
477              
478             Returns the x,y, and z axes for the world coordinate system in terms of
479             the @ocs_axes.
480              
481             @trs_axes = _wcs_axes(@ocs_axes);
482              
483             =cut
484             sub _wcs_axes {
485 0     0     my (@ocs) = map({NewVec(@$_)} @_);
  0            
486 0           my @tcs;
487 0           my @wcs = map({NewVec(@$_)} [1,0,0],[0,1,0],[0,0,1]);
  0            
488 0           foreach my $i (0..2) {
489 0           $tcs[$i] = NewVec(map({$ocs[$_]->Comp($wcs[$i])} 0..2));
  0            
490             }
491 0           return(@tcs);
492             } # end subroutine _wcs_axes definition
493             ########################################################################
494             1;