File Coverage

blib/lib/Chemistry/File/VRML.pm
Criterion Covered Total %
statement 249 490 50.8
branch 23 86 26.7
condition 0 3 0.0
subroutine 31 41 75.6
pod 1 1 100.0
total 304 621 48.9


line stmt bran cond sub pod time code
1             package Chemistry::File::VRML;
2              
3             $VERSION = '0.10';
4              
5 1     1   20743 use strict;
  1         2  
  1         32  
6 1     1   4 use warnings;
  1         2  
  1         25  
7 1     1   4 use base qw(Chemistry::File);
  1         1  
  1         1038  
8 1     1   22114 use Chemistry::Mol;
  1         49821  
  1         65  
9 1     1   1018 use POSIX qw(acos);
  1         8408  
  1         7  
10              
11             Chemistry::Mol->register_format(vrml => __PACKAGE__);
12              
13             my %OPTS = (
14             center => 'centerAtoms',
15             color => 'setColor',
16             style => 'setStyle',
17             stick_radius => 'setStickRadius',
18             ball_radius => 'setBallRadius',
19             compression => 'setCompression',
20             );
21              
22             sub write_mol {
23 1     1 1 3614 my ($self, $fh, $mol, %opts) = @_;
24 1         9 my $vrml = Chemistry::File::VRML::PDB2VRML->new($fh);
25 1         4 $vrml->add_mol($mol);
26 1         7 while (my ($key, $val) = each %opts) {
27 5 100       22 if (my $method = $OPTS{$key}) {
28 3         11 $vrml->$method($val);
29             }
30             }
31 1         5 $vrml->printVRML;
32             }
33              
34             package Chemistry::File::VRML::PDB2VRML;
35              
36             # Defaults variables
37             my $Compression = 0;
38             my $Style = 'Wireframe'; # default display style
39             my $Color = 'byAtom'; # default color
40             my $PI = 3.14159265;
41              
42             my $RadiusBNS = 0.2;
43             my $RadiusStick = 0.15;
44              
45             # Some global tables
46              
47             # color indizes per atom type
48             my %AtomColors = qw(
49             '' 5 H 3 HE 5 LI 5 BE 5 B 1 C 4 N 1 O 2 F 7 NE 5 NA 5
50             MG 5 AL 5 SI 5 P 6 S 0 CL 7 AR 5 K 5 CA 5 SC 5 TI 5 V 5
51             CR 5 MN 5 FE 5 CO 5 NI 5 CU 5 ZN 5 GA 5 GE 5 AS 5 SE 5 BR 7
52             KR 5 RB 5 SR 5 Y 5 ZR 5 NB 5 MO 5 TC 5 RU 5 RH 5 PD 5 AG 5
53             CD 5 IN 5 SN 5 SB 5 TE 5 I 7 XE 5 CS 5 BA 5 LA 5 CE 5 PR 5
54             ND 5 PM 5 SM 5 EU 5 GD 5 TB 5 DY 5 HO 5 ER 5 TM 5 YB 5 LU 5
55             HF 5 TA 5 W 5 RE 5 OS 5 IR 5 PT 5 AU 5 HG 5 TL 5 PB 5 BI 5
56             PO 5 AT 7 RN 5 FR 5 RA 5 AC 5 TH 5 PA 5 U 5 NP 5 PU 5 AM 5
57             CM 5 BK 5 CF 5 XX 5 FM 5 MD 5 NO 5 LW 5
58             );
59              
60             # VDW radius per atom type
61             my %VDWRadius = qw(
62             '' 1.00 H 1.08 HE 1.00 LI 1.00 BE 1.00 B 1.00 C 1.54 N 1.48
63             O 1.36 F 1.30 NE 1.00 NA 2.30 MG 1.00 AL 2.86 SI 2.10 P 1.75
64             S 1.70 CL 1.65 AR 1 K 1 CA 2.75 SC 1 TI 1 V 1
65             CR 1 MN 1 FE 2.27 CO 1 NI 1 CU 1.4 ZN 1.4 GA 1
66             GE 1 AS 1 SE 1 BR 1.8 KR 1 RB 1 SR 1 Y 1
67             ZR 1 NB 1 MO 1 TC 1 RU 1 RH 1 PD 1 AG 1
68             CD 1 IN 1 SN 1 SB 1 TE 1 I 1 XE 1 CS 1
69             BA 1 LA 1 CE 1 PR 1 ND 1 PM 1 SM 1 EU 1
70             GD 1 TB 1 DY 1 HO 1 ER 1 TM 1 YB 1 LU 1
71             HF 1 TA 1 W 1 RE 1 OS 1 IR 1 PT 1 AU 1
72             HG 1 TL 1 PB 1 BI 1 PO 1 AT 1 RN 1 FR 1
73             RA 1 AC 1 TH 1 PA 1 U 1 NP 1 PU 1 AM 1
74             CM 1 BK 1 CF 1 XX 1 FM 1 MD 1 NO 1 LW 1
75             );
76              
77             # atom radius per atom type
78             my %AtomRadius = qw(
79             '' 0.00 H 0.37 HE 0.70 LI 1.23 BE 0.89 B 0.80 C 0.77 N 0.74
80             O 0.74 F 0.72 NE 0.70 NA 1.57 MG 1.36 AL 1.25 SI 1.17 P 1.10
81             S 1.04 CL 0.99 AR 0.70 K 2.03 CA 1.74 SC 1.44 TI 1.32 V 1.22
82             CR 1.17 MN 1.16 FE 1.16 CO 1.15 NI 1.17 CU 1.25 ZN 1.25 GA 1.22
83             GE 1.21 AS 1.17 SE 0.70 BR 1.24 KR 1.91 RB 1.62 SR 1.45 Y 1.34
84             ZR 1.29 NB 1.29 MO 1.24 TC 1.25 RU 1.28 RH 1.34 PD 1.41 AG 1.50
85             CD 1.40 IN 1.41 SN 1.37 SB 1.33 TE 0.70 I 1.33 XE 1.98 CS 1.69
86             BA 1.69 LA 1.69 CE 1.69 PR 1.69 ND 1.69 PM 1.69 SM 1.69 EU 1.69
87             GD 1.69 TB 1.69 DY 1.69 HO 1.69 ER 1.69 TM 1.69 YB 1.69 LU 1.69
88             HF 1.44 TA 1.34 W 1.30 RE 1.28 OS 1.26 IR 1.29 PT 1.34 AU 1.44
89             HG 1.55 TL 1.54 PB 1.52 BI 1.52 PO 1.40 AT 0.70 RN 2.40 FR 2.00
90             RA 1.90 AC 1.90 TH 1.90 PA 1.90 U 1.90 NP 0.70 PU 0.26 AM 1.00
91             CM 1.00 BK 1.00 CF 1.00 XX 1.00 FM 1.00 MD 1.00 NO 1.00 LW 1.00
92             );
93              
94             # S N O H
95             my (@RGBColors) =
96             ('1 1 0', '0 0 1', '1 0 0', '1 1 1', '.5 .5 .5', '1 0 1', '1 .5 0', '0 1 0');
97              
98             # C rest P Hal
99              
100             my (%ColorNames) = (
101             'yellow' => 0,
102             'blue' => 1,
103             'red' => 2,
104             'white' => 3,
105             'grey' => 4,
106             'purple' => 5,
107             'brown' => 6,
108             'green' => 7
109             );
110              
111             sub new {
112 1     1   2 my ($class, $fh) = @_;
113 1 50       5 $class = ref($class) if (ref($class));
114              
115 1         18 my $this = bless {
116             fh => $fh,
117             atoms => [],
118             bonds => [],
119             points => [],
120             lines => [],
121             style => $Style,
122             color => $Color,
123             lineSets => [],
124             lineColors => [],
125             indent => 0,
126             DefUse => {}, # stores shared instances
127             BLT => {}, # bond lookup table for CONECT list
128             RadiusStick => $RadiusStick,
129             Compression => $Compression,
130             RadiusBNS => $RadiusBNS,
131             }, $class;
132              
133 1         3 return $this;
134             }
135              
136             sub add_mol {
137 1     1   2 my ($this, $mol) = @_;
138              
139 1         2 my %atoms;
140 1         5 for my $atom ($mol->atoms) {
141 9         30 my $label = uc $atom->symbol;
142 9         65 my ($x, $y, $z) = $atom->coords->array;
143 9         51 my $vrml_atom = {
144             x => $x,
145             y => $y,
146             z => $z,
147 9         435 nr => scalar(@{$this->{'atoms'}}),
148             label => $label,
149             radius => $VDWRadius{$label},
150             };
151 9         26 $atoms{$atom} = $vrml_atom;
152 9         63 push(@{$this->{'atoms'}}, $vrml_atom);
  9         26  
153             }
154              
155 1         7 for my $bond ($mol->bonds) {
156 0         0 my ($from, $to) = map { $atoms{$_} } $bond->atoms;
  0         0  
157 0         0 push(@{$this->{'bonds'}}, {from => $from, to => $to});
  0         0  
158              
159             }
160 1         9 1;
161             }
162             ###########################################################################
163             #
164             # Read PDB file
165             # Old atoms and bonds won't be deleted, allowing to
166             # merge several PDB file.
167             # Syntax: $object->readPDB($fileName);
168             # Diag: returns undef on error
169             #
170             # 1 2 3 4 5 6 7
171             #1234567890123456789012345678901234567890123456789012345678901234567890123456789
172             #TOM 5 O5* A A 1 -16.851 -5.543 74.981 1.00 55.62 3CRO 148
173             ###########################################################################
174             sub readPDB {
175 0     0   0 my $this = shift;
176 0         0 my $fileName = shift;
177              
178 0 0       0 open(FILE, "$fileName") or return undef;
179 0         0 while () {
180 0 0       0 if (/^ATOM /) { # only C,H,O,P,N,S allowed
    0          
    0          
181 0         0 my $label = substr($_, 12, 4);
182 0         0 $label =~ s/\s//g;
183 0         0 $label = substr(uc $label, 0, 1); # only the first letter
184 0         0 my $x = substr($_, 30, 8);
185 0         0 my $y = substr($_, 38, 8);
186 0         0 my $z = substr($_, 46, 8);
187 0         0 my (%atom) = (
188             'x' => $x,
189             'y' => $y,
190             'z' => $z,
191 0         0 'nr' => scalar(@{$this->{'atoms'}}),
192             'label' => $label,
193             'radius' => $VDWRadius{$label}
194             );
195 0         0 push(@{$this->{'atoms'}}, \%atom);
  0         0  
196             } elsif (/^HETATM/) {
197 0         0 my $label = substr($_, 12, 4);
198 0         0 $label =~ s/\s//g;
199 0         0 $label =~ s/[^A-Za-z].*$//g;
200 0         0 $label = uc $label;
201 0         0 my $x = substr($_, 30, 8);
202 0         0 my $y = substr($_, 38, 8);
203 0         0 my $z = substr($_, 46, 8);
204 0         0 my (%atom) = (
205             'x' => $x,
206             'y' => $y,
207             'z' => $z,
208 0         0 'nr' => scalar(@{$this->{'atoms'}}),
209             'label' => $label,
210             'radius' => $VDWRadius{$label}
211             );
212 0         0 push(@{$this->{'atoms'}}, \%atom);
  0         0  
213             } elsif (/^CONECT/) {
214 0         0 my $tmp = substr($_, 0, 69);
215 0         0 my ($null, $a, @b) = split(/\s+/, $tmp);
216 0         0 foreach (@b) {
217 0         0 my $n1 = $a - 1;
218 0         0 my $n2 = $_ - 1;
219 0 0       0 if ($n1 > $n2) { my $n3 = $n1; $n1 = $n2; $n2 = $n3; }
  0         0  
  0         0  
  0         0  
220 0         0 my $label = $n1 . '_' . $n2;
221 0 0       0 next if (exists($this->{'BLT'}->{$label}));
222 0         0 my $from = $this->{'atoms'}->[$n1];
223 0         0 my $to = $this->{'atoms'}->[$n2];
224 0         0 my (%bond) = ('from' => $from, 'to' => $to);
225 0         0 push(@{$this->{'bonds'}}, \%bond);
  0         0  
226 0         0 $this->{'BLT'}->{$label} = 1;
227             }
228             }
229             }
230 0         0 CORE::close FILE;
231              
232 0         0 1;
233             }
234              
235             ###########################################################################
236             sub setStyle {
237 1     1   1 my ($this, $style) = @_;
238 1         4 $style = lc $style;
239 1         3 $style =~ s/[_ ]//g;
240 1         4 $this->{'style'} = $style;
241             }
242 1     1   2 sub setColor { my $this = shift; $this->{'color'} = shift; }
  1         6  
243 0     0   0 sub setStickRadius { my $this = shift; $this->{RadiusStick} = shift; }
  0         0  
244 0     0   0 sub setBallRadius { my $this = shift; $this->{RadiusBNS} = shift; }
  0         0  
245 0     0   0 sub setCompression { my $this = shift; $this->{Compression} = shift; }
  0         0  
246              
247             ###########################################################################
248             #
249             # Center all atoms
250             # Syntax: $object->centerAtoms();
251             #
252             ###########################################################################
253             sub centerAtoms {
254 1     1   3 my $this = shift;
255              
256 1         17 my ($cogX, $cogY, $cogZ) = (0, 0, 0);
257 1         2 foreach (@{$this->{'atoms'}}) {
  1         3  
258 9         15 $cogX += $_->{'x'};
259 9         16 $cogY += $_->{'y'};
260 9         17 $cogZ += $_->{'z'};
261             }
262 1         4 my $numAtoms = @{$this->{'atoms'}};
  1         2  
263 1         3 $cogX /= $numAtoms;
264 1         2 $cogY /= $numAtoms;
265 1         1 $cogZ /= $numAtoms;
266 1         3 foreach (($cogX, $cogY, $cogZ)) { $_ = sprintf("%.4f", $_); }
  3         19  
267 1         2 foreach (@{$this->{'atoms'}}) {
  1         2  
268 9         9 $_->{'x'} -= $cogX;
269 9         9 $_->{'y'} -= $cogY;
270 9         16 $_->{'z'} -= $cogZ;
271             }
272             }
273              
274             ###########################################################################
275             #
276             # Generate list of lines (cylinders) and points.
277             # Syntax: $object->_genDisplay();
278             #
279             ###########################################################################
280             sub _genDisplay {
281 1     1   1 my $this = shift;
282 1         3 my $color = $this->{'color'};
283              
284             # determine atom colors
285 1 50       4 if ($color eq 'byAtom') {
286 1         2 foreach (@{$this->{'atoms'}}) {
  1         2  
287 9         20 $_->{'color'} = $AtomColors{$_->{'label'}};
288             }
289             } else {
290 0         0 my $c = $ColorNames{$color};
291 0         0 foreach (@{$this->{'atoms'}}) { $_->{'color'} = $c; }
  0         0  
  0         0  
292             }
293              
294             # create one point foreach atom
295 1         2 @{$this->{'points'}} = ();
  1         3  
296 1         2 foreach (@{$this->{'atoms'}}) {
  1         3  
297 9         42 my (%point) = (%$_, 'lines' => []);
298 9         12 push(@{$this->{'points'}}, \%point);
  9         16  
299 9         21 $_->{'point'} = \%point;
300             }
301              
302             # create one (or two) lines per bond
303 1         2 @{$this->{'lines'}} = ();
  1         3  
304 1         2 foreach (@{$this->{'bonds'}}) {
  1         2  
305 0         0 my ($at1, $at2) = ($_->{'from'}, $_->{'to'});
306 0 0       0 if ($at1->{'color'} == $at2->{'color'}) {
307 0         0 my $from = $at1->{'point'};
308 0         0 my (%line) = (
309             'from' => $from,
310             'to' => $at2->{'point'},
311             'label' => $at1->{'nr'} . '_' . $at2->{'nr'}
312             );
313 0         0 push(@{$this->{'lines'}}, \%line);
  0         0  
314 0         0 push(@{$from->{'lines'}}, \%line);
  0         0  
315 0         0 next;
316             }
317              
318             # split bond
319 0         0 my $pt1 = $at1->{'point'};
320 0         0 my $pt2 = $at2->{'point'};
321              
322 0         0 my $x = 0.5 * ($at1->{'x'} + $at2->{'x'});
323 0         0 my $y = 0.5 * ($at1->{'y'} + $at2->{'y'});
324 0         0 my $z = 0.5 * ($at1->{'z'} + $at2->{'z'});
325 0         0 my (%point3) = (
326             'x' => $x,
327             'y' => $y,
328             'z' => $z, # no label,
329             'color' => $at2->{'color'}, # radius or
330 0         0 'nr' => scalar(@{$this->{'points'}})
331             ); # bonds needed
332 0         0 my (%line1) = (
333             'from' => $pt1,
334             'to' => \%point3,
335             'label' => $at1->{'nr'} . '_' . $at2->{'nr'}
336             );
337 0         0 my (%line2) = (
338             'from' => $pt2,
339             'to' => \%point3,
340             'label' => $at2->{'nr'} . '_' . $at1->{'nr'}
341             );
342 0         0 push(@{$this->{'lines'}}, \%line1);
  0         0  
343 0         0 push(@{$this->{'lines'}}, \%line2);
  0         0  
344 0         0 push(@{$pt1->{'lines'}}, \%line1);
  0         0  
345 0         0 push(@{$pt2->{'lines'}}, \%line2);
  0         0  
346 0         0 push(@{$this->{'points'}}, \%point3);
  0         0  
347             }
348             }
349              
350             ###########################################################################
351             #
352             # Optimize the wireframe representation.
353             # Create longer line strips instead of single lines.
354             #
355             ###########################################################################
356             sub _genLineSets {
357 1     1   2 my $this = shift;
358              
359 1         2 @{$this->{'lineSets'}} = ();
  1         2  
360 1         2 @{$this->{'lineColors'}} = ();
  1         2  
361 1         2 foreach (@{$this->{'lines'}}) { $_->{'used'} = 0; }
  1         2  
  0         0  
362 1         2 foreach (@{$this->{'lines'}}) {
  1         7  
363 0 0       0 next if $_->{'used'};
364 0         0 my ($from, $to) = ($_->{'from'}, $_->{'to'});
365 0         0 push(@{$this->{'lineColors'}}, $from->{'color'});
  0         0  
366 0         0 my $set = [$from->{'nr'}, $to->{'nr'}];
367 0         0 push(@{$this->{'lineSets'}}, $set);
  0         0  
368 0         0 $_->{'used'} = 1;
369 0         0 my $next = 1;
370 0         0 my $bonds = $to->{'lines'};
371              
372 0   0     0 while ($next and $bonds) {
373 0         0 $next = 0;
374 0         0 my $b;
375 0         0 foreach $b (@$bonds) {
376 0 0       0 next if $b->{'used'};
377 0         0 my $to = $b->{'to'};
378 0         0 push(@$set, $to->{'nr'});
379 0         0 $bonds = $to->{'lines'};
380 0         0 $b->{'used'} = 1;
381 0         0 $next = 1;
382 0         0 last;
383             }
384             }
385             }
386             }
387              
388             ###########################################################################
389             #
390             # print VRML SceneGraph
391             #
392             ###########################################################################
393             sub printVRML {
394 1     1   2 my $this = shift;
395              
396 1         1 %{$this->{'DefUse'}} = ();
  1         3  
397 1         2 $this->{'indent'} = 0;
398 1         5 $this->_printHead();
399              
400 1         9 $this->_genDisplay();
401              
402 1 50       8 $this->_genLineSets(), $this->_printWire()
403             if ($this->{'style'} =~ /wire/);
404 1 50       11 $this->_printAtoms()
405             if ($this->{'style'} =~ /(ball|stick|cpk)/);
406              
407 1         5 $this->_printTail();
408             }
409              
410             ###########################################################################
411             #
412             # print VRML header
413             #
414             ###########################################################################
415             sub _printHead {
416 1     1   1 my $this = shift;
417 1         2 my $fh = $this->{fh};
418              
419 1         3 print $fh <
420             #VRML V2.0 utf8
421              
422             Transform {
423             children [
424             NavigationInfo { type "EXAMINE" }
425             EOT
426              
427 1         2 $this->{'indent'} = 2;
428             }
429              
430             ###########################################################################
431             sub _printWire {
432 1     1   2 my $this = shift;
433              
434 1         4 $this->_printWireShape();
435             }
436              
437             ###########################################################################
438             sub _printWireShape {
439 1     1   2 my $this = shift;
440              
441 1 50       4 if ($this->{'DefUse'}->{'WireShape'}) {
442 0         0 $this->_printLine('USE WIRESHAPE');
443 0         0 return;
444             }
445              
446 1         4 $this->_printLine('DEF WIRESHAPE Shape {');
447 1         2 $this->{'indent'}++;
448 1         5 $this->_printWireAppearance();
449 1         4 $this->_printWireGeometry();
450 1         1 $this->{'indent'}--;
451 1         4 $this->_printLine('}');
452              
453 1         4 $this->{'DefUse'}->{'WireShape'} = 1;
454             }
455              
456             ###########################################################################
457             sub _printWireAppearance {
458 1     1   1 my $this = shift;
459              
460 1 50       4 if ($this->{'DefUse'}->{'WireApp'}) {
461 0         0 $this->_printLine('USE WIREAPP');
462 0         0 return;
463             }
464              
465 1         3 $this->_printLine("appearance DEF WIREAPP Appearance {");
466 1         2 $this->{'indent'}++;
467 1         3 $this->_printLine("material Material { diffuseColor 1 1 1 }"); # dummy
468 1         3 $this->{'indent'}--;
469 1         6 $this->_printLine('}');
470              
471 1         3 $this->{'DefUse'}->{'WireApp'} = 1;
472             }
473              
474             ###########################################################################
475             sub _printWireGeometry {
476 1     1   2 my $this = shift;
477              
478 1 50       6 if ($this->{'DefUse'}->{'WireGeo'}) {
479 0         0 $this->_printLine('geometry USE WIREGEO');
480 0         0 return;
481             }
482              
483 1         4 $this->_printLine('geometry DEF WIREGEO IndexedLineSet {');
484 1         1 $this->{'indent'}++;
485 1         6 $this->_printWireColor();
486 1         4 $this->_printWireColorIndex();
487 1         3 $this->_printLine('colorPerVertex FALSE');
488 1         4 $this->_printWireCoordinate();
489 1         4 $this->_printWireCoordIndex();
490 1         1 $this->{'indent'}--;
491 1         3 $this->_printLine('}');
492              
493 1         3 $this->{'DefUse'}->{'WireGeo'} = 1;
494             }
495              
496             ###########################################################################
497             sub _printWireColor {
498 1     1   3 my $this = shift;
499              
500 1 50       16 if ($this->{'DefUse'}->{'WireCol'}) {
501 0         0 $this->_printLine('color USE WIRECOL');
502 0         0 return;
503             }
504              
505 1         4 $this->_printLine('color DEF WIRECOL Color {');
506 1         2 $this->{'indent'}++;
507 1         3 $this->_printLine('color [');
508 1         1 $this->{'indent'}++;
509 1         3 foreach (@RGBColors) { $this->_printLine("$_,"); }
  8         22  
510 1         3 $this->{'indent'}--;
511 1         2 $this->_printLine(']');
512 1         2 $this->{'indent'}--;
513 1         3 $this->_printLine('}');
514              
515 1         2 $this->{'DefUse'}->{'WireCol'} = 1;
516             }
517              
518             ###########################################################################
519             sub _printWireColorIndex {
520 1     1   1 my $this = shift;
521              
522 1         3 $this->_printLine('colorIndex [');
523 1         3 $this->{'indent'}++;
524 1         3 my $lc = $this->{'lineColors'};
525 1         1 my $i;
526 1         10 for ($i = 0 ; $i < (@$lc - 8) ; $i += 8) {
527 0         0 $this->_printLine(join(', ', @$lc[$i .. ($i + 7)]) . ',');
528             }
529 1 50       4 $this->_printLine(join(', ', @$lc[$i .. $#$lc]) . ',')
530             if ($i < @$lc);
531 1         2 $this->{'indent'}--;
532 1         3 $this->_printLine(']');
533             }
534              
535             ###########################################################################
536             sub _printWireCoordinate {
537 1     1   1 my $this = shift;
538              
539 1 50       5 if ($this->{'DefUse'}->{'WireCoo'}) {
540 0         0 $this->_printLine('coord USE WIRECOO');
541 0         0 return;
542             }
543              
544 1         4 $this->_printLine('coord DEF WIRECOO Coordinate {');
545 1         1 $this->{'indent'}++;
546 1         3 $this->_printLine('point [');
547 1         2 $this->{'indent'}++;
548 1         3 my ($x, $y, $z);
549 1         2 foreach (@{$this->{'points'}}) {
  1         4  
550 9         34 my $x = sprintf("%.4g", $_->{'x'});
551 9         20 my $y = sprintf("%.4g", $_->{'y'});
552 9         18 my $z = sprintf("%.4g", $_->{'z'});
553 9         24 $this->_printLine("$x $y $z,");
554             }
555 1         3 $this->{'indent'}--;
556 1         23 $this->_printLine(']');
557 1         1 $this->{'indent'}--;
558 1         3 $this->_printLine('}');
559              
560 1         3 $this->{'DefUse'}->{'WireCoo'} = 1;
561             }
562              
563             ###########################################################################
564             sub _printWireCoordIndex {
565 1     1   1 my $this = shift;
566              
567 1         2 $this->_printLine('coordIndex [');
568 1         2 $this->{'indent'}++;
569 1         2 my $ls = $this->{'lineSets'};
570 1         3 foreach (@$ls) { $this->_printLine(join(', ', @$_) . ', -1,'); }
  0         0  
571 1         2 $this->{'indent'}--;
572 1         2 $this->_printLine(']');
573             }
574              
575             ###########################################################################
576             sub _printAtoms {
577 1     1   2 my $this = shift;
578              
579 1         5 foreach (@{$this->{'atoms'}}) {
  1         3  
580 9 50       34 $this->_printAtom($_) if ($_->{'label'});
581             }
582             }
583              
584             ###########################################################################
585             sub _printAtom {
586 9     9   11 my $this = shift;
587 9         10 my $atom = shift;
588              
589 9         45 $this->_printLine("DEF ATOM_$atom->{'nr'} Transform {");
590 9         15 $this->{'indent'}++;
591 9         72 $this->_printLine("translation $atom->{'x'} $atom->{'y'} $atom->{'z'}");
592 9         20 $this->_printLine('children [');
593 9         11 $this->{'indent'}++;
594 9         20 $this->_printAtomShape($atom);
595 9 50       25 if ($this->{'style'} =~ /stick/) {
596 0         0 my $point = $atom->{'point'};
597 0         0 foreach (@{$point->{'lines'}}) { $this->_printBond($_); }
  0         0  
  0         0  
598             }
599 9         11 $this->{'indent'}--;
600 9         16 $this->_printLine(']');
601 9         10 $this->{'indent'}--;
602 9         18 $this->_printLine('}');
603             }
604              
605             ###########################################################################
606             sub _printAtomShape {
607 9     9   14 my $this = shift;
608 9         11 my $atom = shift;
609              
610 9         14 my $l = $atom->{'label'};
611 9 100       27 if ($this->{'DefUse'}->{"AtomShape$l"}) {
612 6         17 $this->_printLine("USE ATOMSHAPE_$l");
613 6         12 return;
614             }
615              
616 3         9 $this->_printLine("DEF ATOMSHAPE_$l Shape {");
617 3         4 $this->{'indent'}++;
618 3         6 $this->_printAtomAppearance($atom);
619 3         6 $this->_printAtomGeometry($atom);
620 3         4 $this->{'indent'}--;
621 3         5 $this->_printLine('}');
622              
623 3         10 $this->{'DefUse'}->{"AtomShape$l"} = 1;
624             }
625              
626             ###########################################################################
627             sub _printAtomAppearance {
628 3     3   4 my $this = shift;
629 3         4 my $atom = shift;
630              
631 3         6 my $c = $atom->{'color'};
632 3 50       11 if ($this->{'DefUse'}->{"AtomApp$c"}) {
633 0         0 $this->_printLine("appearance USE ATOMAPP_$c");
634 0         0 return;
635             }
636              
637 3         9 $this->_printLine("appearance DEF ATOMAPP_$c Appearance {");
638 3         5 $this->{'indent'}++;
639 3         7 $this->_printLine('material Material {');
640 3         4 $this->{'indent'}++;
641 3         10 $this->_printLine("diffuseColor $RGBColors[$c]");
642 3         7 $this->_printLine('specularColor 1 1 1');
643 3         6 $this->_printLine('shininess 0.75');
644 3         5 $this->{'indent'}--;
645 3         6 $this->_printLine('}');
646 3         4 $this->{'indent'}--;
647 3         5 $this->_printLine('}');
648              
649 3         9 $this->{'DefUse'}->{"AtomApp$c"} = 1;
650             }
651              
652             ###########################################################################
653             sub _printAtomGeometry {
654 3     3   4 my $this = shift;
655 3         3 my $atom = shift;
656              
657 3         5 my $l = $atom->{'label'};
658 3 50       9 if ($this->{'DefUse'}->{"AtomGeo$l"}) {
659 0         0 $this->_printLine("geometry USE ATOMGEO_$l");
660 0         0 return;
661             }
662              
663 3         4 my $r = $this->{RadiusStick};
664 3 50       14 $r = $this->{RadiusBNS} * $atom->{'radius'} if ($this->{'style'} =~ /ball/);
665 3 50       10 $r = $atom->{'radius'} if ($this->{'style'} =~ /cpk/);
666 3         45 $this->_printLine("geometry DEF ATOMGEO_$l Sphere { radius $r }");
667              
668 3         9 $this->{'DefUse'}->{"AtomGeo$l"} = 1;
669             }
670              
671             ###########################################################################
672             sub _printBond {
673 0     0   0 my $this = shift;
674 0         0 my $bond = shift;
675              
676 0         0 my ($from, $to) = ($bond->{'from'}, $bond->{'to'});
677 0         0 my ($tx, $ty, $tz, $s, $ax, $ay, $az, $angle) =
678             $this->_calcBond($from, $to);
679              
680 0         0 foreach ($tx, $ty, $tz, $s, $ax, $ay, $az, $angle) {
681 0         0 $_ = sprintf("%.5g", $_);
682             }
683              
684 0         0 my $l = $bond->{'label'};
685 0         0 $this->_printLine("DEF BOND_$l Transform {");
686 0         0 $this->{'indent'}++;
687 0         0 $this->_printLine("translation $tx $ty $tz");
688 0         0 $this->_printLine("scale 1 $s 1");
689 0         0 $this->_printLine("rotation $ax $ay $az $angle");
690 0         0 $this->_printLine('children [');
691 0         0 $this->{'indent'}++;
692 0         0 $this->_printBondShape($from);
693 0         0 $this->{'indent'}--;
694 0         0 $this->_printLine(']');
695 0         0 $this->{'indent'}--;
696 0         0 $this->_printLine('}');
697             }
698              
699             ###########################################################################
700             sub _printBondShape {
701 0     0   0 my $this = shift;
702 0         0 my $from = shift;
703              
704 0         0 my $c = $from->{'color'};
705 0 0       0 if ($this->{'DefUse'}->{"BondShape$c"}) {
706 0         0 $this->_printLine("USE BONDSHAPE_$c");
707 0         0 return;
708             }
709              
710 0         0 $this->_printLine("DEF BONDSHAPE_$c Shape {");
711 0         0 $this->{'indent'}++;
712 0         0 $this->_printAtomAppearance($from); # bond color is the same as atom color
713 0         0 $this->_printBondGeometry();
714 0         0 $this->{'indent'}--;
715 0         0 $this->_printLine('}');
716              
717 0         0 $this->{'DefUse'}->{"BondShape$c"} = 1;
718             }
719              
720             ###########################################################################
721             sub _printBondGeometry {
722 0     0   0 my $this = shift;
723              
724 0 0       0 if ($this->{'DefUse'}->{'BondGeo'}) {
725 0         0 $this->_printLine('geometry USE BONDGEO');
726 0         0 return;
727             }
728              
729             $this->_printLine(
730 0         0 "geometry DEF BONDGEO Cylinder { radius $this->{RadiusStick} top FALSE bottom FALSE}"
731             );
732              
733 0         0 $this->{'DefUse'}->{'BondGeo'} = 1;
734             }
735              
736             ###########################################################################
737             #
738             # print VRML tail
739             #
740             ###########################################################################
741             sub _printTail {
742 1     1   3 my $this = shift;
743 1         2 my $fh = $this->{fh};
744 1         47 print $fh <
745             ]
746             }
747             EOT
748             }
749              
750             ###########################################################################
751             sub _printLine {
752 118     118   356 my $this = shift;
753 118         331 my $str = shift;
754 118         147 my $fh = $this->{fh};
755              
756 118 50       201 if ($this->{Compression}) { print $fh "$str\n"; }
  0         0  
757             else {
758 118         175 my $i = "\t" x int($this->{'indent'} >> 1);
759 118 100       254 $i .= ' ' if ($this->{'indent'} & 0x1);
760 118         279 print $fh "$i$str\n";
761             }
762             }
763              
764             ###########################################################################
765             #
766             # Calculate bond transformation parameters
767             # Syntax: @geometry = _calcBond(\%atom1, \%atom2);
768             #
769             ###########################################################################
770             sub _calcBond {
771 0     0     my $this = shift;
772 0           my $atom1 = shift;
773 0           my $atom2 = shift;
774              
775 0           my ($x1, $y1, $z1) = ($atom1->{'x'}, $atom1->{'y'}, $atom1->{'z'});
776 0           my ($x2, $y2, $z2) = ($atom2->{'x'}, $atom2->{'y'}, $atom2->{'z'});
777              
778 0           my ($dx, $dy, $dz) = ($x2 - $x1, $y2 - $y1, $z2 - $z1);
779              
780             # length
781 0           my $s = sqrt($dx * $dx + $dy * $dy + $dz * $dz);
782              
783             # translation
784 0           my ($tx, $ty, $tz) = (0.5 * $dx, 0.5 * $dy, 0.5 * $dz);
785              
786 0           ($dx, $dy, $dz) = ($dx / $s, $dy / $s, $dz / $s);
787              
788             # rotation axis and angle
789 0           my ($ax, $ay, $az, $angle);
790 0 0         if ($dy > 0.9999) { ($ax, $ay, $az, $angle) = (1, 0, 0, 0); }
  0 0          
791 0           elsif ($dy < -0.9999) { ($ax, $ay, $az, $angle) = (1, 0, 0, $PI); }
792 0           else { ($ax, $ay, $az, $angle) = ($dz, 0, -$dx, acos($dy)); }
793              
794 0           return $tx, $ty, $tz, 0.5 * $s, $ax, $ay, $az, $angle;
795             }
796              
797             ###########################################################################
798             #
799             # Generate connectivities
800             #
801             ###########################################################################
802             sub genBonds {
803 1     1   7115 no warnings 'uninitialized';
  1         2  
  1         951  
804 0     0     my $this = shift;
805              
806             # find largest possible distance
807 0           my $maxR;
808 0 0         foreach (values %AtomRadius) { $maxR = $_ if ($_ > $maxR); }
  0            
809 0           $maxR *= 2.4;
810              
811             # find the most negative coordinates to avoid negative indizes
812 0           my ($minX, $minY, $minZ);
813 0           foreach (@{$this->{'atoms'}}) {
  0            
814 0 0         $minX = $_->{'x'} if ($_->{'x'} < $minX);
815 0 0         $minY = $_->{'y'} if ($_->{'y'} < $minY);
816 0 0         $minZ = $_->{'z'} if ($_->{'z'} < $minZ);
817             }
818 0           $minX -= 2.5 * $maxR;
819 0           $minY -= 2.5 * $maxR;
820 0           $minZ -= 2.5 * $maxR;
821              
822             # distribute atoms in a grid with $maxR cell distance
823 0           my (@grid, $maxI, $maxJ, $maxK);
824 0           foreach (@{$this->{'atoms'}}) {
  0            
825 0           my $i = int(($_->{'x'} - $minX) / $maxR);
826 0           my $j = int(($_->{'y'} - $minY) / $maxR);
827 0           my $k = int(($_->{'z'} - $minZ) / $maxR);
828 0           push(@{$grid[$i][$j][$k]}, $_);
  0            
829 0 0         $maxI = $i if ($i > $maxI);
830 0 0         $maxJ = $j if ($j > $maxJ);
831 0 0         $maxK = $k if ($k > $maxK);
832             }
833              
834             # loop of grid cells and find bonds
835 0           my ($i, $j, $k, $a, $b, $c);
836 0           for ($i = 1 ; $i <= $maxI ; $i++) {
837 0           for ($j = 1 ; $j <= $maxJ ; $j++) {
838 0           for ($k = 1 ; $k <= $maxK ; $k++) {
839 0           foreach (@{$grid[$i][$j][$k]}) {
  0            
840 0           foreach $a (-1 .. 1) {
841 0           foreach $b (-1 .. 1) {
842 0           foreach $c (-1 .. 1) {
843 0           $this->_atomToGrid($_,
844 0           \@{$grid[$i + $a][$j + $b][$k + $c]});
845             }
846             }
847             }
848             }
849             }
850             }
851             }
852             }
853              
854             ###########################################################################
855             sub _atomToGrid {
856 0     0     my $this = shift;
857 0           my $atom1 = shift;
858 0           my $grid = shift;
859              
860 0           my $n1 = $atom1->{'nr'};
861 0           my ($x1, $y1, $z1) = ($atom1->{'x'}, $atom1->{'y'}, $atom1->{'z'});
862 0           my $ar1 = $AtomRadius{$atom1->{'label'}};
863              
864 0           my $atom2;
865 0           foreach $atom2 (@$grid) {
866 0           my $n2 = $atom2->{'nr'};
867 0 0         next unless ($n1 < $n2);
868              
869 0           my $ar2 = $ar1 + $AtomRadius{$atom2->{'label'}};
870 0           $ar2 *= 1.2;
871 0           $ar2 *= $ar2;
872              
873 0           my ($x2, $y2, $z2) = ($atom2->{'x'}, $atom2->{'y'}, $atom2->{'z'});
874 0           my ($dx, $dy, $dz) = ($x2 - $x1, $y2 - $y1, $z2 - $z1);
875 0           my $dist = $dx * $dx + $dy * $dy + $dz * $dz;
876 0 0         next if ($dist > $ar2);
877 0           my $label = $atom1->{'nr'} . '_' . $atom2->{'nr'};
878 0 0         next if (exists($this->{'BLT'}->{$label}));
879 0           my (%bond) = ('from' => $atom1, 'to' => $atom2);
880 0           push(@{$this->{'bonds'}}, \%bond);
  0            
881             }
882             }
883              
884             ###########################################################################
885              
886             1;
887              
888             __END__