File Coverage

blib/lib/Lego/Ldraw/Line.pm
Criterion Covered Total %
statement 53 276 19.2
branch 1 80 1.2
condition 1 7 14.2
subroutine 16 53 30.1
pod 1 31 3.2
total 72 447 16.1


line stmt bran cond sub pod time code
1             package Lego::Ldraw::Line;
2            
3 1     1   24 use 5.008004;
  1         5  
  1         68  
4 1     1   5 use strict;
  1         2  
  1         28  
5 1     1   6 use warnings;
  1         7  
  1         31  
6            
7 1     1   4 no warnings qw(uninitialized redefine);
  1         2  
  1         63  
8             use overload
9 1         14 '""' => \&stringify,
10 1     1   7478 '*' => \&transform;
  1         2295  
11            
12 1     1   91 use Carp;
  1         2  
  1         88  
13 1     1   1113 use YAML;
  1         14033  
  1         1114  
14 1     1   14 use Lego::Ldraw;
  1         4  
  1         53  
15 1     1   1633 use Data::Dumper;
  1         21552  
  1         103  
16 1     1   3605 use Math::MatrixReal;
  1         47637  
  1         84  
17 1     1   1558 use Math::Trig;
  1         25556  
  1         233  
18 1     1   13 use File::Basename;
  1         3  
  1         1276  
19            
20             my $line_formats = [
21             [qw(type command)],
22             [qw(type colour x y z a b c d e f g h i part)],
23             [qw(type colour x1 y1 z1 x2 y2 z2)],
24             [qw(type colour x1 y1 z1 x2 y2 z2 x3 y3 z3)],
25             [qw(type colour x1 y1 z1 x2 y2 z2 x3 y3 z3 x4 y4 z4)],
26             [qw(type colour x1 y1 z1 x2 y2 z2 x3 y3 z3 x4 y4 z4)]
27             ];
28            
29             our $config;
30             our %descriptions;
31            
32             #######################################################################
33             # Constructors
34             #######################################################################
35            
36             sub new {
37 0     0 0   my $proto = shift;
38 0   0       my $class = ref($proto) || $proto;
39 0           my $self = {};
40            
41 0           bless ($self, $class);
42 0           return $self;
43             }
44            
45             sub new_from_string {
46 0     0 0   my $self = shift->new;
47            
48 0           my $line = shift;
49 0           for ($line) {
50 0           s/\s+$//; s/^\s+//;
  0            
51             }
52            
53 0           my @line = split ' ', $line;
54 0 0         @line = ($line[0], join (' ', @line[1..$#line])) unless ($line[0]); # handle comment lines
55            
56 0           my @fields = @{$line_formats->[$line[0]]};
  0            
57 0           @{$self}{@fields} = @line;
  0            
58            
59 0           return $self;
60             }
61            
62             sub new_from_part_name {
63 0     0 0   my $self = shift;
64 0           my $part = shift;
65            
66 0           return $self->new_from_string('1 16 0 0 0 1 0 0 0 1 0 0 0 1 ' . $part);
67             }
68            
69             #######################################################################
70             # Field access functions
71             #######################################################################
72            
73             sub BEGIN {
74             #--------------------------------------------------
75             # Use generated functions for clarity and speed,
76             # with exceptions...
77             #--------------------------------------------------
78 1     1   8 my @field_list = ('colour', 'a'..'i', 'x', 'y', 'z',
79             'x1', 'y1', 'z1', 'x2', 'y2', 'z2',
80             'x3', 'y3', 'z3', 'x4', 'y4', 'z4');
81 1     1   10 no strict 'refs';
  1         2  
  1         151  
82 1         3 for my $field (@field_list) {
83             *$field = sub {
84 0     0   0 my $self = shift;
85 0 0       0 return unless exists $self->{$field};
86 0 0       0 if (@_) {
87 0         0 $self->{$field} = shift;
88 0         0 return $self->{$field};
89             } else {
90 0         0 return $self->{$field};
91             }
92             }
93 25         3722 }
94 1     1   6 use strict 'refs';
  1         3  
  1         24  
95             }
96            
97             #--------------------------------------------------
98             # ...because of uppercasing
99             #--------------------------------------------------
100             sub part {
101 0     0 0   my $self = shift;
102 0 0         return unless exists $self->{'part'};
103 0           my $part = $self->{part};
104 0           $part =~ s/\\/\//g;
105 0           return lc $part;
106             }
107            
108             sub name {
109 0     0 0   return basename(shift->part);
110             }
111            
112             #--------------------------------------------------
113             # ...because it's read-only
114             #--------------------------------------------------
115             sub type {
116 0     0 0   return shift->{type};
117             }
118            
119             #--------------------------------------------------
120             # ...because of spelling
121             #--------------------------------------------------
122             sub color {
123 0     0 0   shift->colour(@_);
124             }
125            
126             #######################################################################
127             # other field access functions
128             #######################################################################
129            
130             sub copy {
131 0     0 0   my $self = shift;
132 0           return bless { %{$self} }, ref $self;
  0            
133             }
134            
135             sub fields {
136 0     0 0   return @{$line_formats->[ shift->type ]}
  0            
137             }
138            
139             sub model {
140 0     0 0   my $self = shift;
141 0 0         if (@_) {
142 0           $self->{model} = shift;
143             }
144 0           return $self->{model};
145             }
146            
147             sub description {
148 0     0 0   my $self = shift;
149 0 0         return unless $self->type == 1;
150            
151 0           return $descriptions{$self->part};
152             }
153            
154            
155             sub values {
156 0     0 0   my $self = shift;
157 0           my @fields = @_;
158 0 0         @fields = $self->fields unless @fields;
159 0           return @{$self}{ @fields }
  0            
160             }
161            
162             sub coords {
163 0     0 0   my $self = shift;
164 0           my @fields = grep { /^[xyz]/ } $self->fields;
  0            
165 0           return $self->values(@fields);
166             }
167            
168             sub points {
169 0     0 0   my $points = shift->type;
170 0 0         return $points > 4 ? 4 : $points;
171             }
172            
173             sub transform_matrix {
174 0     0 0   my $self = shift;
175 0 0         return unless ($self->type == 1);
176            
177 0           my @fields = grep { /^[a-ixyz]$/ } $self->fields;
  0            
178 0           return $self->values(@fields);
179             }
180            
181             sub point {
182 0     0 0   my $self = shift;
183 0           my $point = shift;
184 0 0         return unless my $type = $self->type;
185            
186 0 0         if ($type == 1) {
187 0           return $self->values(qw(x y z));
188             } else {
189 0           return $self->values(map { $_ . $point } qw(x y z))
  0            
190             }
191             }
192            
193             sub format {
194 0     0 0   my $self = shift;
195            
196 0           my @text = $self->values;
197            
198 0           for ($self->type) {
199 0 0         /^0/ && do {
200 0           return "$self";
201             };
202 0 0         /^1/ && do {
203 0           my $string = "%d %7d";
204 0           for (2..$#text-1) {
205 0           $string .= "% 8.2f";
206             }
207 0           $string .= " %12s";
208 0           return sprintf $string, @text;
209             };
210 0           my $string = "%d";
211 0           for (1..$#text) {
212 0           $string .= "% 8.2f";
213             }
214 0           return sprintf $string, @text;
215             }
216             }
217            
218             sub eval {
219 0     0 0   my $self = shift;
220 0           my $expr = shift;
221            
222 0           $expr = lc $expr;
223 0           $expr =~ s/color/colour/g;
224            
225             # substitute % strings with field accesses,
226             # and while doing so check if field exists:
227             # if it doesn't return undef
228 0           while ($expr =~ s/\%([a-z0-9]+)/\$self->{$1}/) {
229 0 0         return unless defined $self->$1;
230             }
231            
232             # substitute & strings with function calls,
233             # and while doing so check if function exists:
234             # if it doesn't return undef
235 0           while ($expr =~ s/\&(\w+)/\$self->$1/) {
236 0 0         return unless defined $self->can($1);
237             }
238            
239             # now we've got a full eval'uable string, and
240             # we eval it
241 0 0         if (eval $expr) {
242 0           return $self
243             } else {
244             return
245 0           }
246             }
247            
248             #######################################################################
249             # inlining
250             #######################################################################
251            
252             sub normalize {
253 0     0 0   my $self = shift;
254 0 0         return unless $self->type == 1;
255            
256 0           @{$self}{qw/x y z a b c d e f g h i/} = qw/0 0 0 1 0 0 0 1 0 0 0 1/;
  0            
257 0           return $self;
258             }
259            
260             sub dir {
261 0     0 0   my $self = shift;
262 0 0         $self->{dir} = shift if @_;
263 0           return $self->{dir};
264             }
265            
266             sub partfile {
267 0     0 0   my $self = shift;
268 0 0         return unless $self->type == 1;
269            
270 0           my $part = $self->part;
271 0 0         return $self->config->{partfiles}->{$part}
272             if $self->config->{partfiles}->{$part};
273            
274 0           my $base = $self->config->{base};
275            
276 0           my @parts = @{$self->config->{parts}};
  0            
277 0           @parts = map { $_ = $base . $_ . $part } @parts;
  0            
278            
279 0           @parts = ('./' . $part, $self->dir . '/' . $part, @parts);
280            
281 0           for (@parts) {
282 0           s/\\/\//g;
283 0 0         if (-e $_) {
284 0           $self->config->{partfiles}->{$part} = $_;
285 0           return $_;
286             }
287             }
288             }
289            
290             sub explode {
291 0     0 0   my $self = shift;
292            
293 0 0         return unless $self->type == 1;
294            
295 0           my $file = $self->partfile;
296            
297 0 0         return unless $file;
298 0           return Lego::Ldraw->new_from_file($file);
299             }
300            
301             sub traslate {
302 0     0 0   my $self = shift;
303 0           my %trans;
304            
305 0 0         if (ref $_[0] eq 'HASH') { %trans = %{ $_[0] } }
  0            
  0            
306 0           else { @trans{qw(x y z)} = @_ };
307            
308 0           for my $axis (keys %trans) {
309 0           for my $field ( grep { /^$axis/ } $self->fields ) {
  0            
310 0           $self->{$field} += $trans{$axis}
311             }
312             }
313 0           return $self;
314             }
315            
316             sub transform {
317 0     0 0   my $self = shift;
318 0           my $line = shift;
319            
320 0 0         return unless $self->type;
321 0 0         return unless $line->type == 1;
322            
323 0 0         $self->color($line->color) if $self->color == 16;
324 0           my $m = $line->_transform_matrix;
325            
326 0 0         if ($self->type == 1) {
327 0           my $x = $self->_transform_matrix();
328 0           $self->_transform_matrix($x * $m);
329             } else {
330 0           for (1..$self->points) {
331 0           my $p = $self->_xyz_matrix(undef, $_);
332 0           $self->_xyz_matrix($p * $m, $_)
333             }
334             }
335             }
336            
337             sub rotate {
338 0     0 0   my $self = shift;
339 0           my ($axis, $degrees) = @_;
340 0 0         return unless $self->type;
341            
342 0           my $x = $self->_transform_matrix();
343 0           my $r = $self->_rotate_matrix($axis, $degrees);
344 0           $self->_transform_matrix($x * $r);
345 0           return $self;
346             }
347            
348             #######################################################################
349             # other stuff
350             #######################################################################
351            
352             sub stringify {
353 0     0 0   my $self = shift;
354 0           my $type = $self->type;
355            
356 0           my @fields = @{$line_formats->[$self->type]};
  0            
357 0           return join ' ', @{$self}{@fields};
  0            
358             }
359            
360             #######################################################################
361             # matrix calculation
362             #######################################################################
363            
364             sub _xyz_matrix {
365 0     0     my $self = shift;
366 0           my $matrix = shift;
367            
368 0 0         if ($matrix) {
369 0 0         my $point = $self->type == 1 ? undef : shift;
370 0           my @fields = map { $_ . $point } ('x', 'y', 'z');
  0            
371            
372             $matrix->each( sub {
373 0     0     my $field = shift @fields;
374 0 0         return unless $field;
375 0           $self->$field(shift)
376 0           } );
377            
378 0           return $self;
379             } else {
380 0           my @point = $self->point(shift);
381 0           my $matrix = Math::MatrixReal->new(1, 4);
382 0           $matrix->[0] = [ [ @point, 1 ] ];
383 0 0         return @point ? $matrix : undef;
384             }
385             }
386            
387             sub _transform_matrix {
388 0     0     my $self = shift;
389 0           my $matrix = shift;
390            
391 0 0         if ($matrix) {
392 0           my @fields = (qw(a d g), undef,
393             qw(b e h), undef,
394             qw(c f i), undef,
395             qw(x y z), undef);
396            
397             # update each field in order with
398             # the matrix' value
399             $matrix->each( sub {
400 0     0     my $field = shift @fields;
401 0 0         return unless $field;
402 0           $self->$field(shift)
403 0           } );
404 0           return $self;
405             } else {
406 0           my $matrix = Math::MatrixReal->new(4, 4);
407 0           $matrix->[0] = [
408             [ $self->values( qw(a d g) ), 0 ],
409             [ $self->values( qw(b e h) ), 0 ],
410             [ $self->values( qw(c f i) ), 0 ],
411             [ $self->values( qw(x y z) ), 1 ]
412             ];
413 0           return $matrix;
414             }
415             }
416            
417             sub _rotate_matrix {
418 0     0     my $self = shift;
419 0           my ($axis, $degrees) = @_;
420 0           my $rad = deg2rad($degrees);
421 0           my $matrix = Math::MatrixReal->new(4, 4);
422            
423 0           for ($axis) {
424 0 0         /^x$/ && do {
425 0           $matrix->[0] = [
426             [ 1, 0, 0, 0 ],
427             [ 0, cos($rad), sin($rad), 0 ],
428             [ 0, -sin($rad), cos($rad), 0 ],
429             [ 0, 0, 0, 1 ]
430             ];
431             };
432 0 0         /^y$/ && do {
433 0           $matrix->[0] = [
434             [cos($rad), 0, -sin($rad), 0],
435             [0, 1, 0, 0],
436             [sin($rad), 0, cos($rad), 0],
437             [0, 0, 0, 1],
438             ];
439             };
440 0 0         /^z$/ && do {
441 0           $matrix->[0] = [
442             [ cos($rad), sin($rad), 0, 0 ],
443             [ -sin($rad), cos($rad), 0, 0 ],
444             [0, 0, 1, 0],
445             [0, 0, 0, 1]
446             ];
447             };
448             }
449            
450 0           return $matrix;
451            
452            
453             }
454            
455             ###############################################################
456             # configuration stuff
457             ###############################################################
458            
459             sub INIT {
460 1 50   1   5 return if $config;
461 1         2 $config = do { local $/; };
  1         5  
  1         33  
462 1         10 $config = Load($config);
463            
464 1         22456 $config->{base} = $ENV{'LDRAWDIR'};
465 1   50     43 open DESCRIPTIONS, $config->{base} . 'parts.lst' || return;
466 1         283 while () {
467 0           chop;
468 0           my ($part, $description) = unpack 'A14A*', $_;
469 0           $descriptions{$part} = $description;
470             }
471             }
472            
473             sub config {
474 0     0 0   return $config;
475             }
476            
477             sub basedir {
478 0     0 0   local $_ = $config->{base};
479 0           s/\/$//;
480 0           s/\\$//;
481 0           return $_;
482             }
483            
484             sub partsdirs {
485 0     0 0   my $self = shift;
486 0           my @d = @{$config->{parts}};
  0            
487 0           my $base = $self->basedir;
488            
489 0           for (@d) {
490 0 0         $_ = join ('/', $base, $_)
491             unless /^\./;
492 0           s/\/$//;
493 0           s/\\$//;
494             }
495 0           return @d;
496             }
497            
498             sub primitives {
499 0     0 0   return %{$config->{primitives}}
  0            
500             }
501            
502             ###############################################################
503             # faster constructor for Matrix::Real
504             ###############################################################
505            
506             sub Math::MatrixReal::new {
507 0     0 1   my ($proto, $rows, $cols) = @_;
508            
509 0   0       my $class = ref($proto) || $proto || 'Math::MatrixReal';
510 0           my($i, $j, $this);
511            
512 0           $this = [ [ ], $rows, $cols ];
513            
514 0           bless($this, $class);
515 0           return($this);
516             }
517            
518             ###############################################################
519             # end of faster constructor for Matrix::Real
520             ###############################################################
521            
522             1;
523            
524             __DATA__