File Coverage

blib/lib/Lego/Ldraw/Line.pm
Criterion Covered Total %
statement 49 254 19.2
branch 1 72 1.3
condition 1 7 14.2
subroutine 15 50 30.0
pod 1 30 3.3
total 67 413 16.2


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