File Coverage

blib/lib/Model3D/WavefrontObject.pm
Criterion Covered Total %
statement 97 459 21.1
branch 12 178 6.7
condition 19 78 24.3
subroutine 13 27 48.1
pod 20 21 95.2
total 161 763 21.1


line stmt bran cond sub pod time code
1             package Model3D::WavefrontObject;
2              
3 1     1   27618 use 5.006;
  1         6  
  1         80  
4 1     1   8 use strict;
  1         2  
  1         84  
5              
6             our($VERSION);
7             $VERSION = 1.00;
8              
9 1     1   42160 use Math::Trig;
  1         162789  
  1         23978  
10              
11             sub new {
12 1     1 0 2323 my $p = shift;
13 1   33     15 my $class = ref $p || $p;
14 1         4 my $obj = {};
15 1         4 bless $obj, $class;
16 1         25 $obj->_init(@_);
17 1         11 return $obj;
18             }
19              
20             sub _init {
21 1     1   2 my $obj = shift;
22              
23 1 50       7 unless (scalar @_ % 2) {
24 1         6 while (@_) {
25 0         0 my $key = shift;
26 0         0 $obj->{$key} = shift;
27             }
28             }
29              
30 1   50     18 $obj->{v} ||= [];
31 1   50     9 $obj->{vt} ||= [];
32 1   50     16 $obj->{vn} ||= [];
33 1   50     7 $obj->{f} ||= [];
34 1   50     12 $obj->{p} ||= [];
35 1   50     27 $obj->{l} ||= [];
36 1   50     8 $obj->{g} ||= {};
37 1   50     10 $obj->{group} ||= {};
38 1   50     7 $obj->{mtl} ||= {};
39 1   50     8 $obj->{comments} ||= [];
40 1   50     6 $obj->{r} ||= {};
41 1   50     8 $obj->{_region} ||= 'none';
42 1   50     13 $obj->{_material} ||= 'default';
43 1   50     10 $obj->{_group} ||= 'NULL';
44              
45 1 50       4 if ($obj->{objfile}) {
46 0         0 $obj->ReadObj($obj->{objfile});
47             }
48              
49 1         2 return 1;
50             }
51              
52             sub ReadObj {
53 1     1 1 703 my $obj = shift;
54 1         4 $obj->{objfile} = shift;
55 1 50 33     17 unless ($obj->{objfile} =~ /\.obj$/ and $obj->{objfile}) {
56 0         0 $obj->{objfile} .= '.obj';
57             }
58 1 50       681 unless (-e $obj->{objfile}) {
59 1         5 return undef;
60             }
61 0 0       0 if (-d $obj->{objfile}) {
62 0         0 $obj->{errstr} = "$obj->{objfile} is a directory.";
63 0         0 return undef;
64             }
65 0 0       0 unless (-s $obj->{objfile}) {
66 0         0 $obj->{errstr} = "$obj->{objfile}: File is zero size.";
67 0 0       0 unless (-w $obj->{objfile}) {
68 0         0 $obj->{errstr} .= " Cannot modify file!";
69             }
70 0         0 return undef;
71             }
72 0         0 my $OBJ;
73 0 0       0 unless (open ($OBJ, $obj->{objfile})) {
74 0         0 $obj->{errstr} = "Can't read $obj->{objfile}: $!";
75 0         0 return undef;
76             }
77 0         0 my $void = 1;
78 0         0 my $vpid = 0;
79 0         0 my $vtoid = 1;
80 0         0 my $vnoid = 1;
81 0         0 while (<$OBJ>) {
82 0         0 chomp;
83 0         0 s/\r//;
84 0         0 s/^\s+//;
85 0         0 s/\s+$//;
86 0 0       0 if (/^v\s+/) { # Vertex line
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
87 0         0 /^v\s+([\d\+\-eE\.]+)\s+([\d\+\-eE\.]+)\s+([\d\+\-eE\.]+)\s*([\d\+\-eE\.]*)/;
88 0         0 my $x = $1 + 0;
89 0         0 my $y = $2 + 0;
90 0         0 my $z = $3 + 0;
91 0   0     0 my $wt = $4 + 0 || 1;
92 0         0 push @{$obj->{v}}, {x => $x,
  0         0  
93             y => $y,
94             z => $z,
95             wt => $wt,
96             id => $void,
97             pid => $vpid};
98 0         0 $void++;
99 0         0 $vpid++;
100             }
101             elsif (/^#\s*r\s+(.*)$/) { # UVMapper Region Extension
102 0         0 push @{$obj->{r}}, $1;
  0         0  
103 0         0 $obj->{r}->{$1} = 1;
104 0         0 $obj->{_region} = $1;
105             }
106             elsif (/^g$/) { # NULL group declaration
107 0         0 my $group = 'NULL';
108 0         0 $obj->{g}->{NULL} = 1;
109 0         0 $obj->{seengroup}->{$group} = 1;
110 0         0 $obj->{_group} = $group;
111             }
112             elsif (/^g\s+(\S*)$/) { # named group declaration
113 0         0 my $group = $1;
114 0 0       0 $group = 'NULL' if lc $group eq '(null)';
115 0         0 $obj->{g}->{$group} = 1;
116 0         0 $obj->{_group} = $group;
117             }
118             elsif (/^usemtl\s*(\S*)/) { # Material declaration
119 0         0 my $material = $1;
120 0         0 $obj->{mtl}->{$material} = '';
121 0         0 $obj->{_material} = $material;
122             }
123             elsif (/^mtllib\s+(.*)\s*$/) { # declare material library
124 0         0 my $mtllib = $1;
125 0         0 $mtllib =~ s/[\\\:]/\//g;
126 0         0 $obj->{mtllib} = $mtllib;
127 0         0 $obj->ReadMtlLib;
128             }
129             elsif (/^vt\s+/) { # UV/UVW line
130 0         0 /^vt\s+([\d\+\-eE\.]+)\s+([\d\+\-eE\.]+)\s*([\d\+\-eE\.]*)/;
131 0         0 my $u = $1 + 0;
132 0         0 my $v = $2 + 0;
133 0         0 my $w = $3 + 0;
134 0         0 push @{$obj->{vt}}, {u => $u,
  0         0  
135             v => $v,
136             w => $w,
137             id => $vtoid};
138 0         0 $vtoid++;
139             }
140             elsif (/^vn\s+/) {
141 0         0 /^vn\s+([\d\+\-eE\.]+)\s+([\d\+\-eE\.]+)\s+([\d\+\-eE\.]+)/;
142 0         0 my $i = $1 + 0;
143 0         0 my $j = $2 + 0;
144 0         0 my $k = $2 + 0;
145 0         0 push @{$obj->{vn}}, {i => $i,
  0         0  
146             j => $j,
147             k => $k,
148             id => $vnoid};
149 0         0 $vnoid++;
150             }
151             elsif (/^fo?\s+(.*)$/) { # Polygon line
152 0         0 my $p = $1;
153 0         0 my @poly = split " ", $p;
154 0         0 my @p;
155 0         0 for my $pv (@poly) {
156 0         0 my ($v, $vt, $vn) = split /\//, $pv;
157             # OBJ files are 1-indexed. We want the right element
158             # BUT counting backwards is as we expect.
159 0 0       0 $v-- if $v > 0;
160 0 0       0 $vt-- if $vt > 0;
161 0 0       0 $vn-- if $vn > 0;
162 0         0 push @p, {v => $obj->{v}->[$v],
163             vt => $obj->{vt}->[$vt],
164             vn => $obj->{vn}->[$vn],
165             g => $obj->{_group},
166             m => $obj->{_material},
167             r => $obj->{_region}};
168 0 0       0 push @{$obj->{group}->{$obj->{_group}}},
  0         0  
169             {v => $obj->{v}->[$v],
170             vt => $obj->{vt}->[$vt],
171             vn => $obj->{vn}->[$vn],
172             m => $obj->{_material},
173             r => $obj->{_region}}
174             unless $obj->{seengroupv}->{$obj->{_group}}->{$v};
175 0         0 $obj->{seengroupv}->{$obj->{_group}}->{$v} = 1;
176             }
177 0         0 push @{$obj->{f}}, {verts => \@p,
  0         0  
178             group => $obj->{_group},
179             material => $obj->{_material},
180             region => $obj->{_region}};
181              
182             # Theoretically, you can now get the x, y, and z coordinates and
183             # UV coordinates and group and material for, say, the third vertex
184             # in the 9th facet like so:
185              
186             # $x = $obj->{f}->[10]->{verts}->[2]->{v}->{x};
187             # $y = $obj->{f}->[10]->{verts}->[2]->{v}->{y};
188             # $z = $obj->{f}->[10]->{verts}->[2]->{v}->{z};
189             # $g = $obj->{f}->[10]->{group};
190             # $m = $obj->{f}->[10]->{material};
191             # $u = $obj->{f}->[10]->{verts}->[2]->{vt}->{u};
192             # $v = $obj->{f}->[10]->{verts}->[2]->{vt}->{v};
193              
194             # Or, to make it even easier:
195              
196             # $fv = $obj->{f}->[10]->{verts}->[2];
197             # $y = $fv->{v}->{y};
198             # $u = $fv->{vt}->{u};
199             }
200             elsif (/^l\s+(.*)$/) { # Line line
201 0         0 my $l = $1;
202 0         0 my @line = split " ", $l;
203 0         0 my @l;
204 0         0 for my $lv (@line) {
205 0         0 my ($v, $vt) = split /\//, $lv;
206 0 0       0 $v-- if $v > 0;
207 0 0       0 $vt-- if $vt > 0;
208 0         0 push @l, {v => $obj->{v}->[$v],
209             vt => $obj->{vt}->[$vt]};
210             }
211 0         0 push @{$obj->{l}}, \@l;
  0         0  
212             }
213             elsif (/^p\s+(.*)$/) { # Point line
214 0         0 my $v = $1;
215 0 0       0 $v-- if $v > 0;
216 0         0 push @{$obj->{p}}, $v;
  0         0  
217             }
218             elsif (/^\s*#\s*(.*)$/) { # comment
219 0         0 push @{$obj->{comments}}, $1;
  0         0  
220             }
221             }
222 0         0 close $OBJ;
223 0         0 return 1;
224             }
225              
226             sub ReadMtlLib {
227 0     0 1 0 my $obj = shift;
228 0         0 my $mtllib = shift;
229 0         0 $obj->{mtllib} = $mtllib;
230 0 0       0 return undef unless $mtllib;
231 0         0 my $MTL;
232 0 0       0 unless (open ($MTL, "$mtllib")) {
233 0         0 $obj->{errstr} = "Can't read material library $mtllib.";
234 0         0 return undef;
235             }
236 0         0 while (<$MTL>) {
237 0         0 chomp;
238 0         0 s/\r//;
239 0         0 s/^\s+//;
240 0         0 s/\s+$//;
241 0 0       0 if (/^newmtl\s+(\S+)/) {
    0          
    0          
    0          
    0          
    0          
    0          
    0          
242 0         0 $obj->{_defmtl} = $1;
243             }
244             elsif (/^Ka\s+([\d\.eE\-\+]+)\s+([[\d\.eE\-\+]+)\s+([[\d\.eE\-\+]+)/) {
245 0         0 my $r = $1;
246 0         0 my $g = $2;
247 0         0 my $b = $3;
248 0         0 $obj->{mtl}->{$obj->{_defmtl}}->{Ka}->{r} = $r * 255;
249 0         0 $obj->{mtl}->{$obj->{_defmtl}}->{Ka}->{g} = $g * 255;
250 0         0 $obj->{mtl}->{$obj->{_defmtl}}->{Ka}->{b} = $b * 255;
251             }
252             elsif (/^Kd\s+([\d\.eE\-\+]+)\s+([[\d\.eE\-\+]+)\s+([[\d\.eE\-\+]+)/) {
253 0         0 my $r = $1;
254 0         0 my $g = $2;
255 0         0 my $b = $3;
256 0         0 $obj->{mtl}->{$obj->{_defmtl}}->{Kd}->{r} = $r * 255;
257 0         0 $obj->{mtl}->{$obj->{_defmtl}}->{Kd}->{g} = $g * 255;
258 0         0 $obj->{mtl}->{$obj->{_defmtl}}->{Kd}->{b} = $b * 255;
259             }
260             elsif (/^Ks\s+([\d\.eE\-\+]+)\s+([[\d\.eE\-\+]+)\s+([[\d\.eE\-\+]+)/) {
261 0         0 my $r = $1;
262 0         0 my $g = $2;
263 0         0 my $b = $3;
264 0         0 $obj->{mtl}->{$obj->{_defmtl}}->{Ks}->{r} = $r * 255;
265 0         0 $obj->{mtl}->{$obj->{_defmtl}}->{Ks}->{g} = $g * 255;
266 0         0 $obj->{mtl}->{$obj->{_defmtl}}->{Ks}->{b} = $b * 255;
267             }
268             elsif (/^illum\s+(\d)/) {
269 0         0 $obj->{mtl}->{$obj->{_defmtl}}->{illum} = $1 + 0;
270             }
271             elsif (/^Ns\s+([\d\.eE\-\+]+)/) {
272 0         0 $obj->{mtl}->{$obj->{_defmtl}}->{Ns} = $1 + 0;
273             }
274             elsif (/^(d|Tr)\s+([\d\.eE\-\+]+)/) {
275 0         0 $obj->{mtl}->{$obj->{_defmtl}}->{Tr} = $1 + 0;
276             }
277             elsif (/^map_Ka\s+(.*)/) {
278 0         0 $obj->{mtl}->{$obj->{_defmtl}}->{textureMap} = $1;
279             }
280             }
281 0         0 close $MTL;
282 0         0 return 1;
283             }
284              
285             sub Translate {
286 1     1 1 3 my $obj = shift;
287 1         2 my $trans;
288 1         5 while (@_) {
289 3         5 my $axis = shift;
290 3         5 my $amount = shift;
291 3         9 $trans->{$axis} = $amount + 0;
292             }
293 1         2 for my $v (@{$obj->{v}}) {
  1         3  
294 0 0       0 if ($trans->{x}) {
295 0         0 $v->{x} += $trans->{x};
296             }
297 0 0       0 if ($trans->{y}) {
298 0         0 $v->{y} += $trans->{y};
299             }
300 0 0       0 if ($trans->{z}) {
301 0         0 $v->{z} += $trans->{z};
302             }
303             }
304 1         5 return 1;
305             }
306              
307             sub _getTransCentre {
308 2     2   4 my $obj = shift;
309 2         7 my $centre = {x => 0,
310             y => 0,
311             z => 0};
312 2         4 my $c = shift;
313 2 50       10 return $centre unless $c;
314 0 0       0 return $obj->GetNaturalCentre if $c eq 'natural';
315 0 0       0 return $obj->GetApparentCentre if $c eq 'apparent';
316 0         0 my @stdrot = qw(x y z);
317 0 0 0     0 if (ref $c and ref $c ne 'SCALAR') {
318             # They can use an arrayref like center => [x,y,z]
319 0 0       0 if (ref $c eq 'ARRAY') {
    0          
320 0         0 for my $p (@{$c}) {
  0         0  
321 0         0 my $ax = shift @stdrot;
322 0         0 $centre->{$ax} = $p;
323             }
324             }
325             # ...or a hashref like center => {x => x, y => y, z => z}
326             elsif (ref $c eq 'HASH') {
327 0         0 for my $k (keys %{$c}) {
  0         0  
328 0         0 $centre->{$k} = $c->{$k};
329             }
330             }
331             }
332             else {
333             # Or a scalarref like center => \$center
334 0 0 0     0 if (ref $c and ref $c eq 'SCALAR') {
335 0         0 $c = ${$c};
  0         0  
336             }
337             # or a real scalar in two ways:
338 0         0 $c =~ s/\s+//g; # (ignoring whitespace)
339 0         0 my @c = split /,/, $c;
340 0         0 for my $p (@c) {
341 0         0 my ($ax, $r);
342             # Either like 'x:x,y:y,z:z'
343 0 0       0 if ($p =~ /:/) {
344 0         0 ($ax, $r) = split /:/, $p;
345             }
346             # ...or like 'x,y,z'
347             else {
348 0         0 $ax = shift @stdrot;
349 0         0 $r = $p;
350             }
351 0         0 $centre->{$ax} = $r;
352             }
353             }
354 0         0 return $centre;
355             }
356              
357             sub GetNaturalCentre {
358 0     0 1 0 my $obj = shift;
359 0         0 my $vcount = scalar @{$obj->{v}};
  0         0  
360 0         0 my $centre = {x => 0,
361             y => 0,
362             z => 0};
363 0 0       0 return $centre unless $vcount;
364 0         0 my ($x, $y, $z) = (0,0,0);
365 0         0 for my $v (@{$obj->{v}}) {
  0         0  
366 0         0 $x += $v->{x};
367 0         0 $y += $v->{y};
368 0         0 $z += $v->{z};
369             }
370 0         0 $centre->{x} = $x / $vcount;
371 0         0 $centre->{y} = $y / $vcount;
372 0         0 $centre->{z} = $z / $vcount;
373 0         0 return $centre;
374             }
375              
376             sub GetApparentCentre {
377 0     0 1 0 my $obj = shift;
378 0         0 my $center = {x => 0,
379             y => 0,
380             z => 0};
381 0 0       0 return $center unless scalar @{$obj->{v}};
  0         0  
382 0         0 my ($min, $max) = $obj->MinMax;
383 0         0 $center->{x} = $max->{x} + $min->{x} / 2;
384 0         0 $center->{y} = $max->{y} + $min->{y} / 2;
385 0         0 $center->{z} = $max->{z} + $min->{z} / 2;
386 0         0 return $center;
387             }
388              
389             sub MinMax {
390 0     0 1 0 my $obj = shift;
391 0         0 my $max = {x => 0,
392             y => 0,
393             z => 0};
394 0         0 my $min = {x => 0,
395             y => 0,
396             z => 0};
397 0 0       0 return ($min, $max) unless scalar @{$obj->{v}};
  0         0  
398 0         0 for my $v (@{$obj->{v}}) {
  0         0  
399 0 0       0 $max->{x} = $v->{x} if $v->{x} > $max->{x};
400 0 0       0 $min->{x} = $v->{x} if $v->{x} < $min->{x};
401 0 0       0 $max->{y} = $v->{y} if $v->{y} > $max->{y};
402 0 0       0 $min->{y} = $v->{y} if $v->{y} < $min->{y};
403 0 0       0 $max->{z} = $v->{z} if $v->{z} > $max->{z};
404 0 0       0 $min->{z} = $v->{z} if $v->{z} < $min->{z};
405             }
406 0         0 return ($min, $max);
407             }
408              
409             sub Top {
410 0     0 1 0 my $obj = shift;
411 0         0 my ($min, $max) = $obj->MinMax;
412 0         0 return $max->{y};
413             }
414              
415             sub Bottom {
416 0     0 1 0 my $obj = shift;
417 0         0 my ($min, $max) = $obj->MinMax;
418 0         0 return $min->{y};
419             }
420              
421             sub Left {
422 0     0 1 0 my $obj = shift;
423 0         0 my ($min, $max) = $obj->MinMax;
424 0         0 return $min->{x};
425             }
426              
427             sub Right {
428 0     0 1 0 my $obj = shift;
429 0         0 my ($min, $max) = $obj->MinMax;
430 0         0 return $max->{x};
431             }
432              
433             sub Front {
434 0     0 1 0 my $obj = shift;
435 0         0 my ($min, $max) = $obj->MinMax;
436 0         0 return $max->{z};
437             }
438              
439             sub Back {
440 0     0 1 0 my $obj = shift;
441 0         0 my ($min, $max) = $obj->MinMax;
442 0         0 return $min->{z};
443             }
444              
445             sub ReverseWinding {
446 1     1 1 1 my $obj = shift;
447 1 50       2 unless (scalar @{$obj->{f}}) {
  1         5  
448 1         4 $obj->{errstr} = 'This object has no facet information';
449 1         5 return undef;
450             }
451 0         0 for my $f (@{$obj->{f}}) {
  0         0  
452 0         0 $f->{verts} = [reverse @{$f->{verts}}];
  0         0  
453             }
454 0         0 return 1;
455             }
456              
457             sub Rotate {
458 1     1 1 680 my $obj = shift;
459 1         2 my $rot;
460 1         4 while (@_) {
461 3         4 my $axis = shift;
462 3         4 my $amount = shift;
463 3         11 $rot->{$axis} = $amount + 0;
464             }
465 1   33     9 my $centre = $obj->_getTransCentre($rot->{centre} || $rot->{center});
466 1 50       4 return undef unless $rot;
467 1         2 for my $v (@{$obj->{v}}) {
  1         5  
468 0 0 0     0 if ($centre->{x} || $centre->{y} || $centre->{z}) {
      0        
469 0 0       0 if ($centre->{x}) {
470 0         0 $v->{x} -= $centre->{x};
471             }
472 0 0       0 if ($centre->{y}) {
473 0         0 $v->{y} -= $centre->{y};
474             }
475 0 0       0 if ($centre->{z}) {
476 0         0 $v->{z} -= $centre->{z};
477             }
478             }
479 0 0       0 if ($rot->{x}) {
480 0         0 my $rad = Math::Trig::deg2rad($rot->{x});
481 0         0 my ($rho, $theta, $phi)
482             = Math::Trig::cartesian_to_spherical($v->{y}, $v->{z}, 0);
483 0         0 $theta += $rad;
484 0         0 ($v->{y}, $v->{z}, undef)
485             = Math::Trig::spherical_to_cartesian($rho, $theta, $phi);
486             }
487 0 0       0 if ($rot->{y}) {
488 0         0 my $rad = Math::Trig::deg2rad($rot->{y});
489 0         0 my ($rho, $theta, $phi)
490             = Math::Trig::cartesian_to_spherical($v->{x}, $v->{z}, 0);
491 0         0 $theta += $rad;
492 0         0 ($v->{x}, $v->{z}, undef)
493             = Math::Trig::spherical_to_cartesian($rho, $theta, $phi);
494             }
495 0 0       0 if ($rot->{z}) {
496 0         0 my $rad = Math::Trig::deg2rad($rot->{z});
497 0         0 my ($rho, $theta, $phi)
498             = Math::Trig::cartesian_to_spherical($v->{x}, $v->{y}, 0);
499 0         0 $theta += $rad;
500 0         0 ($v->{x}, $v->{y}, undef)
501             = Math::Trig::spherical_to_cartesian($rho, $theta, $phi);
502             }
503 0 0 0     0 if ($centre->{x} || $centre->{y} || $centre->{z}) {
      0        
504 0 0       0 if ($centre->{x}) {
505 0         0 $v->{x} += $centre->{x};
506             }
507 0 0       0 if ($centre->{y}) {
508 0         0 $v->{y} += $centre->{y};
509             }
510 0 0       0 if ($centre->{z}) {
511 0         0 $v->{z} += $centre->{z};
512             }
513             }
514             }
515 1         6 return 1;
516             }
517              
518             sub _getScaleVal {
519 3     3   5 my $obj= shift;
520 3         4 my $sv = shift;
521 3 50       7 return 1 unless $sv;
522 3         3 my $op;
523 3         7 $sv =~ s/([\+\-])//;
524 3         5 $op = $1;
525 3 50       15 if ($sv =~ s/\%$//) {
526 3         7 $sv /= 100;
527             }
528 3 50       7 if ($op) {
529 0 0       0 if ($op eq '-') {
    0          
530 0         0 $sv = 1 - $sv;
531             }
532             elsif ($op eq '+') {
533 0         0 $sv = 1 + $sv;
534             }
535             }
536 3         7 return $sv;
537             }
538              
539             sub Scale {
540 1     1 1 3 my $obj = shift;
541 1         4 my $scale = {x => 1,
542             y => 1,
543             z => 1};
544 1 50       21 if (scalar @_ > 1) {
545 0         0 while (@_) {
546 0         0 my $axis = shift;
547 0         0 my $amount = $obj->_getScaleVal(shift);
548 0         0 $scale->{$axis} = $amount;
549             }
550             }
551             else {
552 1         3 my $s = shift;
553 1         4 $scale->{x} = $obj->_getScaleVal($s);
554 1         3 $scale->{y} = $obj->_getScaleVal($s);
555 1         4 $scale->{z} = $obj->_getScaleVal($s);
556             }
557 1 50       5 if ($scale->{scale}) {
558 0         0 $scale->{scale} = $obj->_getScaleVal($scale->{scale});
559 0   0     0 $scale->{x} ||= $scale->{scale};
560 0   0     0 $scale->{y} ||= $scale->{scale};
561 0   0     0 $scale->{z} ||= $scale->{scale};
562             }
563 1   33     11 my $centre = $obj->_getTransCentre($scale->{centre} || $scale->{center});
564 1         1 for my $v (@{$obj->{v}}) {
  1         5  
565 0         0 $v->{x} *= $scale->{x};
566 0         0 $v->{y} *= $scale->{y};
567 0         0 $v->{z} *= $scale->{z};
568             }
569 1         5 return 1;
570             }
571              
572             sub GetVertex {
573 0     0 1 0 my $obj = shift;
574 0         0 my $vert = shift;
575 0 0       0 unless ($vert) {
576 0         0 $obj->{errstr} = 'No vertex specified';
577 0         0 return undef;
578             }
579 0 0       0 unless (exists $obj->{v}->[$vert]) {
580 0         0 $obj->{errstr} = "Vertex $vert does not exist";
581 0         0 return undef;
582             }
583 0 0       0 return wantarray ? ($obj->{v}->[$vert]->{x},
584             $obj->{v}->[$vert]->{y},
585             $obj->{v}->[$vert]->{z})
586             : $obj->{v}->[$vert];
587             }
588              
589             sub GetVertexSpherical {
590 0     0 1 0 my $obj = shift;
591 0         0 my $vert = shift;
592 0 0       0 unless ($vert) {
593 0         0 $obj->{errstr} = 'No vertex specified';
594 0         0 return undef;
595             }
596 0 0       0 unless (exists $obj->{v}->[$vert]) {
597 0         0 $obj->{errstr} = "Vertex $vert does not exist";
598 0         0 return undef;
599             }
600 0         0 my $v = $obj->{v}->[$vert];
601 0         0 my ($rho, $theta, $phi)
602             = Math::Trig::cartesian_to_spherical($v->{x}, $v->{y}, $obj->{z});
603 0         0 $theta = Math::Trig::rad2deg($theta);
604 0         0 $phi = Math::Trig::rad2deg($phi);
605 0 0       0 return wantarray ? ($rho, $theta, $phi) : {rho => $rho,
606             theta => $theta,
607             phi => $phi};
608             }
609              
610             sub Mirror {
611 1     1 1 3 my $obj = shift;
612 1         2 my $ax = shift;
613 1   50     4 $ax ||= 'x';
614 1         2 for my $v (@{$obj->{v}}) {
  1         3  
615 0         0 $v->{$ax} = 0 - $v->{ax};
616             }
617 1         4 $obj->ReverseWinding;
618 1         3 return 1;
619             }
620              
621             sub FlipUVs {
622 0     0 1   my $obj = shift;
623 0           my $ax = shift;
624 0   0       $ax ||= 'u';
625 0           for my $vt (@{$obj->{vt}}) {
  0            
626 0           $vt->{$ax} = 1 - $vt->{$ax};
627             }
628 0           return 1;
629             }
630              
631             sub WriteObj {
632 0     0 1   my $obj = shift;
633 0   0       my $outfile = shift || $obj->{outfile};
634 0           $obj->{outfile} = $outfile;
635              
636 0           my $OBJ;
637 0           my $was_stdout = 0;
638 0 0         if ($obj->{outfile}) {
639 0           open $OBJ, ">$obj->{outfile}";
640             }
641             else {
642 0           $was_stdout = 1;
643 0           $OBJ = *STDOUT{IO};
644             }
645              
646 0   0       my $prec = $obj->{prec} || 8;
647              
648             # Print out file comments
649 0           unshift @{$obj->{comments}},
  0            
650             "File generated by MapShape.pl (c) Dodger",
651 0           scalar @{$obj->{v}} . ' Vertices',
652 0           scalar @{$obj->{vt}} . ' UVs',
653 0           scalar @{$obj->{f}} . ' Polygons',
654 0           scalar(keys(%{$obj->{g}})) . ' Groups',
655 0           scalar(keys(%{$obj->{mtl}})) . 'Materials',
656 0           scalar(keys(%{$obj->{r}})) . 'Regions';
657 0           for my $comment (@{$obj->{comments}}) {
  0            
658 0           print {$OBJ} "# $comment\n";
  0            
659             }
660 0           print {$OBJ} "\n";
  0            
661              
662             # Print out vertices
663 0           for my $v (@{$obj->{v}}) {
  0            
664 0           my $pf = "v %.${prec}f %.${prec}f %.${prec}f\n";
665 0           printf {$OBJ} $pf, $v->{x}, $v->{y}, $v->{z};
  0            
666             }
667 0           print {$OBJ} "\n";
  0            
668              
669             # Print out UVs
670 0           for my $vt (@{$obj->{vt}}) {
  0            
671 0           printf {$OBJ} "vt %f %f %f\n", $vt->{u}, $vt->{v}, $vt->{w};
  0            
672             }
673 0           print {$OBJ} "\n";
  0            
674              
675             # This is for Poser for now, so no normals.
676             # We bailed unless we had UVs, so we assume we have them.
677             # There is a slight chance that a model has SOME UVs but not all.
678             # Fuck that noise. That's a fucked up improper model, and just rude
679             # to do. We're not covering that screwy contingency.
680              
681 0           my ($r, $g, $m);
682 0           for my $f (@{$obj->{f}}) {
  0            
683 0 0         if ($r ne $f->{region}) {
684 0           $r = $f->{region};
685 0           print {$OBJ} "# r $r\n";
  0            
686             }
687 0 0         if ($g ne $f->{group}) {
688 0           $g = $f->{group};
689 0           print {$OBJ} "g $g\n";
  0            
690             }
691 0 0         if ($m ne $f->{material}) {
692 0           $m = $f->{material};
693 0           print {$OBJ} "usemtl $m\n";
  0            
694             }
695 0           my $outpoly = join " ",
696             map "$_->{v}->{id}/$_->{vt}->{id}",
697 0           @{$f->{verts}};
698 0           print {$OBJ} "f $outpoly\n";
  0            
699             }
700 0 0         close $OBJ unless $was_stdout;
701 0           return 1;
702             }
703              
704             1;
705              
706             __END__