View Javadoc
1   // ******************************************************************************
2   //
3   // Title:       Force Field X.
4   // Description: Force Field X - Software for Molecular Biophysics.
5   // Copyright:   Copyright (c) Michael J. Schnieders 2001-2021.
6   //
7   // This file is part of Force Field X.
8   //
9   // Force Field X is free software; you can redistribute it and/or modify it
10  // under the terms of the GNU General Public License version 3 as published by
11  // the Free Software Foundation.
12  //
13  // Force Field X is distributed in the hope that it will be useful, but WITHOUT
14  // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
15  // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
16  // details.
17  //
18  // You should have received a copy of the GNU General Public License along with
19  // Force Field X; if not, write to the Free Software Foundation, Inc., 59 Temple
20  // Place, Suite 330, Boston, MA 02111-1307 USA
21  //
22  // Linking this library statically or dynamically with other modules is making a
23  // combined work based on this library. Thus, the terms and conditions of the
24  // GNU General Public License cover the whole combination.
25  //
26  // As a special exception, the copyright holders of this library give you
27  // permission to link this library with independent modules to produce an
28  // executable, regardless of the license terms of these independent modules, and
29  // to copy and distribute the resulting executable under terms of your choice,
30  // provided that you also meet, for each linked independent module, the terms
31  // and conditions of the license of that module. An independent module is a
32  // module which is not derived from or based on this library. If you modify this
33  // library, you may extend this exception to your version of the library, but
34  // you are not obligated to do so. If you do not wish to do so, delete this
35  // exception statement from your version.
36  //
37  // ******************************************************************************
38  package ffx.potential.bonded;
39  
40  import static ffx.numerics.math.DoubleMath.X;
41  import static ffx.numerics.math.DoubleMath.angle;
42  import static ffx.numerics.math.DoubleMath.length;
43  import static ffx.numerics.math.DoubleMath.normalize;
44  import static ffx.numerics.math.DoubleMath.scale;
45  import static ffx.numerics.math.DoubleMath.sub;
46  import static ffx.potential.parameters.BondType.cubic;
47  import static ffx.potential.parameters.BondType.quartic;
48  import static ffx.potential.parameters.BondType.units;
49  import static java.lang.String.format;
50  import static org.apache.commons.math3.util.FastMath.max;
51  import static org.apache.commons.math3.util.FastMath.min;
52  
53  import ffx.numerics.atomic.AtomicDoubleArray3D;
54  import ffx.numerics.math.Double3;
55  import ffx.numerics.math.DoubleMath;
56  import ffx.potential.bonded.RendererCache.ViewModel;
57  import ffx.potential.parameters.AtomType;
58  import ffx.potential.parameters.BondType;
59  import ffx.potential.parameters.ForceField;
60  import java.util.ArrayList;
61  import java.util.HashMap;
62  import java.util.List;
63  import java.util.Map;
64  import java.util.logging.Logger;
65  import org.jogamp.java3d.BranchGroup;
66  import org.jogamp.java3d.Geometry;
67  import org.jogamp.java3d.LineArray;
68  import org.jogamp.java3d.Shape3D;
69  import org.jogamp.java3d.Transform3D;
70  import org.jogamp.java3d.TransformGroup;
71  import org.jogamp.vecmath.AxisAngle4d;
72  import org.jogamp.vecmath.Vector3d;
73  
74  /**
75   * The Bond class represents a covalent bond formed between two atoms.
76   *
77   * @author Michael J. Schnieders
78   * @since 1.0
79   */
80  @SuppressWarnings("CloneableImplementsClone")
81  public class Bond extends BondedTerm {
82  
83    /**
84     * Length in Angstroms that is added to Atomic Radii when determining if two Atoms are within
85     * bonding distance
86     */
87    static final float BUFF = 0.7f;
88  
89    private static final Logger logger = Logger.getLogger(Bond.class.getName());
90    // Some static variables used for computing cylinder orientations
91    private static final float[] a0col = {
92      0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f
93    };
94    private static final float[] f4a = {0.0f, 0.0f, 0.0f, 0.9f};
95    private static final float[] f4b = {0.0f, 0.0f, 0.0f, 0.9f};
96    private static final float[] f16 = {
97      0.0f, 0.0f, 0.0f, 0.9f, 0.0f, 0.0f, 0.0f, 0.9f, 0.0f, 0.0f, 0.0f, 0.9f, 0.0f, 0.0f, 0.0f, 0.9f
98    };
99    private static final double[] a13d = new double[3];
100   private static final double[] a23d = new double[3];
101   private static final double[] mid = new double[3];
102   private static final double[] diff3d = new double[3];
103   private static final double[] sum3d = new double[3];
104   private static final double[] coord = new double[12];
105   private static final double[] y = {0.0d, 1.0d, 0.0d};
106   private static final AxisAngle4d axisAngle = new AxisAngle4d();
107   private static final double[] bcross = new double[4];
108   private static final double[] cstart = new double[3];
109   private static final Vector3d pos3d = new Vector3d();
110   /** List of Bonds that this Bond forms angles with */
111   private final ArrayList<Bond> formsAngleWith = new ArrayList<>();
112   /** The force field BondType for this bond. */
113   public BondType bondType = null;
114   /** Rigid Scale factor. */
115   private double rigidScale = 1.0;
116   // Java3D methods and variables for visualization of this Bond.
117   private RendererCache.ViewModel viewModel = RendererCache.ViewModel.INVISIBLE;
118   private BranchGroup branchGroup;
119   private TransformGroup cy1tg, cy2tg;
120   private Transform3D cy1t3d, cy2t3d;
121   private Shape3D cy1, cy2;
122   private Vector3d scale;
123   private int detail = 3;
124   private LineArray la;
125   private int lineIndex;
126   private boolean wireVisible = true;
127 
128   /**
129    * Simple Bond constructor that is intended to be used with the equals method.
130    *
131    * @param n Bond id
132    */
133   public Bond(String n) {
134     super(n);
135   }
136 
137   /**
138    * Bond constructor.
139    *
140    * @param a1 Atom number 1.
141    * @param a2 Atom number 2.
142    */
143   public Bond(Atom a1, Atom a2) {
144     atoms = new Atom[2];
145     int i1 = a1.getIndex();
146     int i2 = a2.getIndex();
147     if (i1 < i2) {
148       atoms[0] = a1;
149       atoms[1] = a2;
150     } else {
151       atoms[0] = a2;
152       atoms[1] = a1;
153     }
154     setID_Key(false);
155     viewModel = RendererCache.ViewModel.WIREFRAME;
156     a1.setBond(this);
157     a2.setBond(this);
158   }
159 
160   /**
161    * Log that no BondType exists.
162    *
163    * @param a1 Atom 1.
164    * @param a2 Atom 2.
165    * @param forceField The force field in use.
166    */
167   public static void logNoBondType(Atom a1, Atom a2, ForceField forceField) {
168     AtomType atomType1 = a1.getAtomType();
169     AtomType atomType2 = a2.getAtomType();
170     int[] c = {atomType1.atomClass, atomType2.atomClass};
171     String key = BondType.sortKey(c);
172     StringBuilder sb = new StringBuilder(
173         format(" No BondType for key: %s\n %s -> %s\n %s -> %s", key,
174         a1, atomType1, a2, atomType2));
175     int c1 = atomType1.atomClass;
176     int c2 = atomType2.atomClass;
177     List<AtomType> types1 = forceField.getSimilarAtomTypes(atomType1);
178     List<AtomType> types2 = forceField.getSimilarAtomTypes(atomType2);
179     List<BondType> bondTypes = new ArrayList<>();
180     boolean match = false;
181     for (AtomType type1 : types1) {
182       for (AtomType type2 : types2) {
183         // Similar bond type must match at least one class.
184         if ((type1.atomClass != c1) && (type1.atomClass != c2) &&
185             (type2.atomClass != c1) && (type2.atomClass != c2)) {
186           continue;
187         }
188         BondType bondType = forceField.getBondType(type1, type2);
189         if (bondType != null && !bondTypes.contains(bondType)) {
190           if (!match) {
191             match = true;
192             sb.append("\n Similar Bond Types:");
193           }
194           bondTypes.add(bondType);
195           sb.append(format("\n  %s", bondType));
196         }
197       }
198     }
199 
200     logger.severe(sb.toString());
201   }
202 
203   /** {@inheritDoc} */
204   @Override
205   public int compareTo(BondedTerm b) {
206     if (b == null) {
207       throw new NullPointerException();
208     }
209     if (b == this) {
210       return 0;
211     }
212     if (!b.getClass().isInstance(this)) {
213       return super.compareTo(b);
214     }
215     int this0 = atoms[0].getIndex();
216     int a0 = b.atoms[0].getIndex();
217     if (this0 < a0) {
218       return -1;
219     }
220     if (this0 > a0) {
221       return 1;
222     }
223     int this1 = atoms[1].getIndex();
224     int a1 = b.atoms[1].getIndex();
225 
226     return Integer.compare(this1, a1);
227   }
228 
229   /**
230    * {@inheritDoc}
231    *
232    * <p>Evaluate this Bond energy.
233    */
234   @Override
235   public double energy(
236       boolean gradient, int threadID, AtomicDoubleArray3D grad, AtomicDoubleArray3D lambdaGrad) {
237     var atomA = atoms[0];
238     var atomB = atoms[1];
239     var va = atomA.getXYZ();
240     var vb = atomB.getXYZ();
241     var vab = va.sub(vb);
242     value = vab.length();
243     var prefactor = units * rigidScale * bondType.forceConstant;
244     // dv is deviation from ideal
245     var dv = value - bondType.distance;
246     if (bondType.bondFunction.hasFlatBottom()) {
247       if (dv > 0) {
248         dv = max(0, dv - bondType.flatBottomRadius);
249       } else if (dv < 0) {
250         dv = min(0, dv + bondType.flatBottomRadius);
251       } // Else, no adjustments needed.
252     }
253     var dv2 = dv * dv;
254     switch (bondType.bondFunction) {
255       case QUARTIC:
256       case FLAT_BOTTOM_QUARTIC:
257         {
258           energy = prefactor * dv2 * (1.0 + cubic * dv + quartic * dv2);
259           if (gradient) {
260             // Compute the magnitude of the gradient.
261             var dedr = 2.0 * prefactor * dv * (1.0 + 1.5 * cubic * dv + 2.0 * quartic * dv2);
262             computeGradient(threadID, grad, atomA, atomB, vab, dedr);
263           }
264           break;
265         }
266       case HARMONIC:
267       case FLAT_BOTTOM_HARMONIC:
268       default:
269         {
270           energy = prefactor * dv2;
271           if (gradient) {
272             // Compute the magnitude of the gradient.
273             var dedr = 2.0 * prefactor * dv;
274             computeGradient(threadID, grad, atomA, atomB, vab, dedr);
275           }
276           break;
277         }
278     }
279     value = dv;
280     return energy;
281   }
282 
283   /**
284    * Find the other Atom in <b>this</b> Bond. These two atoms are said to be 1-2.
285    *
286    * @param a The known Atom.
287    * @return The other Atom that makes up <b>this</b> Bond, or Null if Atom a is not part of
288    *     <b>this</b> Bond.
289    */
290   public Atom get1_2(Atom a) {
291     if (a == atoms[0]) {
292       return atoms[1];
293     }
294     if (a == atoms[1]) {
295       return atoms[0];
296     }
297     return null; // Atom not found in bond
298   }
299 
300   /**
301    * Gets the current distance between the two Atoms in this Bond.
302    *
303    * @return Current distance.
304    */
305   public double getCurrentDistance() {
306     double[] x1 = new double[3];
307     x1 = atoms[0].getXYZ(x1);
308     double[] x2 = new double[3];
309     x2 = atoms[1].getXYZ(x2);
310     return DoubleMath.dist(x1, x2);
311   }
312 
313   /** Log details for this Bond energy term. */
314   public void log() {
315     logger.info(
316         format(
317             " %-8s %6d-%s %6d-%s %6.4f  %6.4f  %10.4f",
318             "Bond",
319             atoms[0].getIndex(),
320             atoms[0].getAtomType().name,
321             atoms[1].getIndex(),
322             atoms[1].getAtomType().name,
323             bondType.distance,
324             value,
325             energy));
326   }
327 
328   /** {@inheritDoc} */
329   @Override
330   public void removeFromParent() {
331     super.removeFromParent();
332     cy1 = null;
333     cy2 = null;
334     cy1tg = null;
335     cy2tg = null;
336     if (cy1t3d != null) {
337       RendererCache.poolTransform3D(cy1t3d);
338       RendererCache.poolTransform3D(cy2t3d);
339       cy1t3d = null;
340       cy2t3d = null;
341     }
342     if (branchGroup != null) {
343       branchGroup.detach();
344       branchGroup.setUserData(null);
345       RendererCache.poolDoubleCylinder(branchGroup);
346       branchGroup = null;
347     }
348   }
349 
350   /**
351    * Set a reference to the force field parameters.
352    *
353    * @param bondType a {@link ffx.potential.parameters.BondType} object.
354    */
355   public void setBondType(BondType bondType) {
356     this.bondType = bondType;
357   }
358 
359   /**
360    * Return the BondType for this Bond.
361    * @return Returns the BondType.
362    */
363   public BondType getBondType() {
364     return bondType;
365   }
366 
367   /**
368    * Set the color of this Bond's Java3D shapes based on the passed Atom.
369    *
370    * @param a Atom
371    */
372   public void setColor(Atom a) {
373     if (viewModel != ViewModel.INVISIBLE
374         && viewModel != ViewModel.WIREFRAME
375         && branchGroup != null) {
376       if (a == atoms[0]) {
377         cy1.setAppearance(a.getAtomAppearance());
378       } else if (a == atoms[1]) {
379         cy2.setAppearance(a.getAtomAppearance());
380       }
381     }
382     setWireVisible(wireVisible);
383   }
384 
385   /**
386    * Setter for the field <code>rigidScale</code>.
387    *
388    * @param rigidScale a double.
389    */
390   public void setRigidScale(double rigidScale) {
391     this.rigidScale = rigidScale;
392   }
393 
394   /**
395    * {@inheritDoc}
396    *
397    * <p>Polymorphic setView method.
398    */
399   @Override
400   public void setView(RendererCache.ViewModel newViewModel, List<BranchGroup> newShapes) {
401     switch (newViewModel) {
402       case WIREFRAME:
403         viewModel = ViewModel.WIREFRAME;
404         setWireVisible(true);
405         setCylinderVisible(false, newShapes);
406         break;
407       case SPACEFILL:
408       case INVISIBLE:
409       case RMIN:
410         viewModel = ViewModel.INVISIBLE;
411         setWireVisible(false);
412         setCylinderVisible(false, newShapes);
413         break;
414       case RESTRICT:
415         if (!atoms[0].isSelected() || !atoms[1].isSelected()) {
416           viewModel = ViewModel.INVISIBLE;
417           setWireVisible(false);
418           setCylinderVisible(false, newShapes);
419         }
420         break;
421       case BALLANDSTICK:
422       case TUBE:
423         viewModel = newViewModel;
424         // Get the radius to use
425         double rad;
426         double len = getValue() / 2.0d;
427         if (viewModel == RendererCache.ViewModel.BALLANDSTICK) {
428           rad = 0.1d * RendererCache.radius;
429         } else {
430           rad = 0.2d * RendererCache.radius;
431         }
432         if (scale == null) {
433           scale = new Vector3d();
434         }
435         scale.set(rad, len, rad);
436         setWireVisible(false);
437         setCylinderVisible(true, newShapes);
438         break;
439       case DETAIL:
440         int res = RendererCache.detail;
441         if (res != detail) {
442           detail = res;
443           if (branchGroup != null) {
444             Geometry geom1 = RendererCache.getCylinderGeom(0, detail);
445             Geometry geom2 = RendererCache.getCylinderGeom(1, detail);
446             Geometry geom3 = RendererCache.getCylinderGeom(2, detail);
447             cy1.removeAllGeometries();
448             cy2.removeAllGeometries();
449             cy1.addGeometry(geom1);
450             cy1.addGeometry(geom2);
451             cy1.addGeometry(geom3);
452             cy2.addGeometry(geom1);
453             cy2.addGeometry(geom2);
454             cy2.addGeometry(geom3);
455           }
456         }
457         if (scale == null) {
458           scale = new Vector3d();
459         }
460         double newRadius;
461         if (viewModel == RendererCache.ViewModel.BALLANDSTICK) {
462           newRadius = 0.1d * RendererCache.radius;
463         } else if (viewModel == RendererCache.ViewModel.TUBE) {
464           newRadius = 0.2d * RendererCache.radius;
465         } else {
466           break;
467         }
468         if (newRadius != scale.x) {
469           scale.x = newRadius;
470           scale.y = newRadius;
471           if (branchGroup != null) {
472             setView(viewModel, newShapes);
473           }
474         }
475         break;
476       case SHOWHYDROGENS:
477         if (atoms[0].getAtomicNumber() == 1 || atoms[1].getAtomicNumber() == 1) {
478           setView(viewModel, newShapes);
479         }
480         break;
481       case HIDEHYDROGENS:
482         if (atoms[0].getAtomicNumber() == 1 || atoms[1].getAtomicNumber() == 1) {
483           viewModel = ViewModel.INVISIBLE;
484           setWireVisible(false);
485           setCylinderVisible(false, newShapes);
486         }
487         break;
488       case FILL:
489       case POINTS:
490       case LINES:
491         if (branchGroup != null && viewModel != ViewModel.INVISIBLE) {
492           cy1.setAppearance(atoms[0].getAtomAppearance());
493           cy2.setAppearance(atoms[1].getAtomAppearance());
494         }
495         break;
496     }
497   }
498 
499   /**
500    * setWire
501    *
502    * @param l a {@link org.jogamp.java3d.LineArray} object.
503    * @param i a int.
504    */
505   public void setWire(LineArray l, int i) {
506     la = l;
507     lineIndex = i;
508   }
509 
510   /**
511    * {@inheritDoc}
512    *
513    * <p>Update recomputes the bonds length, Wireframe vertices, and Cylinder Transforms
514    */
515   @Override
516   public void update() {
517     // Update the Bond Length
518     atoms[0].getXYZ(a13d);
519     atoms[1].getXYZ(a23d);
520     sub(a13d, a23d, diff3d);
521     double d = length(diff3d);
522     setValue(d);
523     DoubleMath.add(a13d, a23d, sum3d);
524     scale(sum3d, 0.5d, mid);
525     // Update the Wireframe Model.
526     if (la != null) {
527       for (int i = 0; i < 3; i++) {
528         coord[i] = a13d[i];
529         coord[3 + i] = mid[i];
530         coord[6 + i] = mid[i];
531         coord[9 + i] = a23d[i];
532       }
533       la.setCoordinates(lineIndex, coord);
534     }
535     // Update the Bond cylinder transforms.
536     if (branchGroup != null) {
537       normalize(diff3d, diff3d);
538       scale.y = d / 2.0d;
539       setBondTransform3d(cy1t3d, mid, diff3d, d, true);
540       scale(diff3d, -1.0d, diff3d);
541       setBondTransform3d(cy2t3d, mid, diff3d, d, false);
542       cy1tg.setTransform(cy1t3d);
543       cy2tg.setTransform(cy2t3d);
544     }
545   }
546 
547   private void computeGradient(
548       int threadID, AtomicDoubleArray3D grad, Atom atomA, Atom atomB, Double3 vab, double dedr) {
549     var de = 0.0;
550     if (value > 0.0) {
551       de = dedr / value;
552     }
553     var ga = vab.scale(de);
554     var ia = atomA.getIndex() - 1;
555     var ib = atomB.getIndex() - 1;
556     grad.add(threadID, ia, ga);
557     grad.sub(threadID, ib, ga);
558   }
559 
560   /**
561    * Check to see if <b>this</b> Bond and another combine to form an angle
562    *
563    * @param b a {@link ffx.potential.bonded.Bond} object.
564    * @return True if Bond b helps form an angle with <b>this</b> Bond
565    */
566   boolean formsAngleWith(Bond b) {
567     for (Bond bond : formsAngleWith) {
568       if (b == bond) {
569         return true;
570       }
571     }
572     return false;
573   }
574 
575   /**
576    * Finds the common Atom between <b>this</b> Bond and Bond b.
577    *
578    * @param b Bond to compare with.
579    * @return The Atom the Bonds have in common or Null if they are the same Bond or have no atom in
580    *     common
581    */
582   Atom getCommonAtom(Bond b) {
583     if (b == this || b == null) {
584       return null;
585     }
586     if (b.atoms[0] == atoms[0]) {
587       return atoms[0];
588     }
589     if (b.atoms[0] == atoms[1]) {
590       return atoms[1];
591     }
592     if (b.atoms[1] == atoms[0]) {
593       return atoms[0];
594     }
595     if (b.atoms[1] == atoms[1]) {
596       return atoms[1];
597     }
598     return null; // Common atom not found
599   }
600 
601   /**
602    * Find the Atom that <b>this</b> Bond and Bond b do not have in common.
603    *
604    * @param b Bond to compare with
605    * @return The Atom that Bond b and <b>this</b> Bond do not have in common, or Null if they have
606    *     no Atom in common
607    */
608   Atom getOtherAtom(Bond b) {
609     if (b == this || b == null) {
610       return null;
611     }
612     if (b.atoms[0] == atoms[0]) {
613       return atoms[1];
614     }
615     if (b.atoms[0] == atoms[1]) {
616       return atoms[0];
617     }
618     if (b.atoms[1] == atoms[0]) {
619       return atoms[1];
620     }
621     if (b.atoms[1] == atoms[1]) {
622       return atoms[0];
623     }
624     return null;
625   }
626 
627   /**
628    * sameGroup
629    *
630    * @return a boolean.
631    */
632   boolean sameGroup() {
633     return atoms[0].getParent() == atoms[1].getParent();
634   }
635 
636   /**
637    * Specifies <b>this</b> Bond helps form an angle with the given Bond
638    *
639    * @param b Bond that forms an angle with <b>this</b> Bond
640    */
641   void setAngleWith(Bond b) {
642     formsAngleWith.add(b);
643   }
644 
645   /**
646    * Create the Bond Scenegraph Objects.
647    *
648    * @param newShapes List
649    */
650   private void initJ3D(List<BranchGroup> newShapes) {
651     detail = RendererCache.detail;
652     branchGroup = RendererCache.doubleCylinderFactory(atoms[0], atoms[1], detail);
653     cy1tg = (TransformGroup) branchGroup.getChild(0);
654     cy2tg = (TransformGroup) branchGroup.getChild(1);
655     cy1 = (Shape3D) cy1tg.getChild(0);
656     cy2 = (Shape3D) cy2tg.getChild(0);
657     newShapes.add(branchGroup);
658     cy1t3d = RendererCache.transform3DFactory();
659     cy2t3d = RendererCache.transform3DFactory();
660     update();
661   }
662 
663   /**
664    * setBondTransform3d
665    *
666    * @param t3d a {@link org.jogamp.java3d.Transform3D} object.
667    * @param pos an array of double.
668    * @param orient an array of double.
669    * @param len a double.
670    * @param newRot a boolean.
671    */
672   private void setBondTransform3d(
673       Transform3D t3d, double[] pos, double[] orient, double len, boolean newRot) {
674     // Bond Orientation
675     if (newRot) {
676       double angle = angle(orient, y);
677       X(y, orient, bcross);
678       bcross[3] = angle - Math.PI;
679       axisAngle.set(bcross);
680     }
681     // Scale the orientation vector to be a fourth the bond length
682     // and add it to the position vector of the of the first atom
683     scale(orient, len / 4.0d, cstart);
684     DoubleMath.add(cstart, pos, cstart);
685     pos3d.set(cstart);
686     t3d.setTranslation(pos3d);
687     t3d.setRotation(axisAngle);
688     t3d.setScale(scale);
689   }
690 
691   /**
692    * Manage cylinder visibility.
693    *
694    * @param visible boolean
695    * @param newShapes List
696    */
697   private void setCylinderVisible(boolean visible, List<BranchGroup> newShapes) {
698     if (!visible) {
699       // Make this Bond invisible.
700       if (branchGroup != null) {
701         cy1.setPickable(false);
702         cy1.setAppearance(RendererCache.nullAp);
703         cy2.setPickable(false);
704         cy2.setAppearance(RendererCache.nullAp);
705         // branchGroup = null;
706       }
707     } else if (branchGroup == null) {
708       // Get Java3D primitives from the RendererCache
709       initJ3D(newShapes);
710     } else {
711       // Scale the cylinders to match the current ViewModel
712       cy1t3d.setScale(scale);
713       cy1tg.setTransform(cy1t3d);
714       cy2t3d.setScale(scale);
715       cy2tg.setTransform(cy2t3d);
716       cy1.setAppearance(atoms[0].getAtomAppearance());
717       cy2.setAppearance(atoms[1].getAtomAppearance());
718     }
719   }
720 
721   /**
722    * Manage wireframe visibility.
723    *
724    * @param visible a boolean.
725    */
726   private void setWireVisible(boolean visible) {
727     if (!visible) {
728       wireVisible = false;
729       la.setColors(lineIndex, a0col);
730     } else {
731       wireVisible = true;
732       float[] cols = f16;
733       float[] col1 = f4a;
734       float[] col2 = f4b;
735       atoms[0].getAtomColor().get(col1);
736       atoms[1].getAtomColor().get(col2);
737       for (int i = 0; i < 3; i++) {
738         cols[i] = col1[i];
739         cols[4 + i] = col1[i];
740         cols[8 + i] = col2[i];
741         cols[12 + i] = col2[i];
742       }
743       la.setColors(lineIndex, cols);
744     }
745   }
746 }